/*      $NetBSD: rsrr.c,v 1.10 2008/08/26 17:38:21 seanb Exp $  */

/*
* Copyright (c) 1993, 1998-2001.
* The University of Southern California/Information Sciences Institute.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

/* RSRR code written by Daniel Zappala, USC Information Sciences Institute,
* April 1995.
*/

/* May 1995 -- Added support for Route Change Notification */

#ifdef RSRR

#include "defs.h"
#include <sys/param.h>
#if (defined(BSD) && (BSD >= 199103))
#include <stddef.h>
#endif

/* Taken from prune.c */
/*
* checks for scoped multicast addresses
*/
#define GET_SCOPE(gt) { \
       int _i; \
       if (((gt)->gt_mcastgrp & 0xff000000) == 0xef000000) \
           for (_i = 0; _i < numvifs; _i++) \
               if (scoped_addr(_i, (gt)->gt_mcastgrp)) \
                   VIFM_SET(_i, (gt)->gt_scope); \
       }

/*
* Exported variables.
*/
int rsrr_socket;                        /* interface to reservation protocol */

/*
* Global RSRR variables.
*/
char rsrr_recv_buf[RSRR_MAX_LEN];       /* RSRR receive buffer */
char rsrr_send_buf[RSRR_MAX_LEN];       /* RSRR send buffer */

struct sockaddr_un client_addr;
socklen_t client_length = sizeof(client_addr);


/*
* Procedure definitions needed internally.
*/
static void     rsrr_accept(int recvlen);
static void     rsrr_accept_iq(void);
static int      rsrr_accept_rq(struct rsrr_rq *route_query, int flags,
                              struct gtable *gt_notify);
static int      rsrr_send(int sendlen);
static void     rsrr_cache(struct gtable *gt, struct rsrr_rq *route_query);

/* Initialize RSRR socket */
void
rsrr_init()
{
   int servlen;
   struct sockaddr_un serv_addr;

   if ((rsrr_socket = socket(AF_LOCAL, SOCK_DGRAM, 0)) < 0)
       logit(LOG_ERR, errno, "Can't create RSRR socket");

   unlink(RSRR_SERV_PATH);
   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sun_family = AF_LOCAL;
   strlcpy(serv_addr.sun_path, RSRR_SERV_PATH, sizeof(serv_addr.sun_path));
#if (defined(BSD) && (BSD >= 199103))
   servlen = offsetof(struct sockaddr_un, sun_path) +
               strlen(serv_addr.sun_path);
   serv_addr.sun_len = servlen;
#else
   servlen = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);
#endif

   if (bind(rsrr_socket, (struct sockaddr *) &serv_addr, servlen) < 0)
       logit(LOG_ERR, errno, "Can't bind RSRR socket");

   if (register_input_handler(rsrr_socket,rsrr_read) < 0)
       logit(LOG_WARNING, 0, "Couldn't register RSRR as an input handler");
}

/* Read a message from the RSRR socket */
void
rsrr_read(f, rfd)
       int f;
       fd_set *rfd;
{
   int rsrr_recvlen;
   int omask;

   bzero((char *) &client_addr, sizeof(client_addr));
   rsrr_recvlen = recvfrom(rsrr_socket, rsrr_recv_buf, sizeof(rsrr_recv_buf),
                           0, (struct sockaddr *)&client_addr, &client_length);
   if (rsrr_recvlen < 0) {
       if (errno != EINTR)
           logit(LOG_ERR, errno, "RSRR recvfrom");
       return;
   }
   /* Use of omask taken from main() */
   omask = sigblock(sigmask(SIGALRM));
   rsrr_accept(rsrr_recvlen);
   (void)sigsetmask(omask);
}

/* Accept a message from the reservation protocol and take
* appropriate action.
*/
static void
rsrr_accept(recvlen)
   int recvlen;
{
   struct rsrr_header *rsrr;
   struct rsrr_rq *route_query;

   if (recvlen < RSRR_HEADER_LEN) {
       logit(LOG_WARNING, 0,
           "Received RSRR packet of %d bytes, which is less than min size",
           recvlen);
       return;
   }

   rsrr = (struct rsrr_header *) rsrr_recv_buf;

   if (rsrr->version > RSRR_MAX_VERSION) {
       logit(LOG_WARNING, 0,
           "Received RSRR packet version %d, which I don't understand",
           rsrr->version);
       return;
   }

   switch (rsrr->version) {
     case 1:
       switch (rsrr->type) {
         case RSRR_INITIAL_QUERY:
           /* Send Initial Reply to client */
           logit(LOG_INFO, 0, "Received Initial Query\n");
           rsrr_accept_iq();
           break;
         case RSRR_ROUTE_QUERY:
           /* Check size */
           if (recvlen < RSRR_RQ_LEN) {
               logit(LOG_WARNING, 0,
                   "Received Route Query of %d bytes, which is too small",
                   recvlen);
               break;
           }
           /* Get the query */
           route_query = (struct rsrr_rq *) (rsrr_recv_buf + RSRR_HEADER_LEN);
           logit(LOG_INFO, 0,
               "Received Route Query for src %s grp %s notification %d",
               inet_fmt(route_query->source_addr.s_addr),
               inet_fmt(route_query->dest_addr.s_addr),
               BIT_TST(rsrr->flags,RSRR_NOTIFICATION_BIT));
           /* Send Route Reply to client */
           rsrr_accept_rq(route_query,rsrr->flags,NULL);
           break;
         default:
           logit(LOG_WARNING, 0,
               "Received RSRR packet type %d, which I don't handle",
               rsrr->type);
           break;
       }
       break;

     default:
       logit(LOG_WARNING, 0,
           "Received RSRR packet version %d, which I don't understand",
           rsrr->version);
       break;
   }
}

/* Send an Initial Reply to the reservation protocol. */
static void
rsrr_accept_iq()
{
   struct rsrr_header *rsrr;
   struct rsrr_vif *vif_list;
   struct uvif *v;
   int vifi, sendlen;

   /* Check for space.  There should be room for plenty of vifs,
    * but we should check anyway.
    */
   if (numvifs > RSRR_MAX_VIFS) {
       logit(LOG_WARNING, 0,
           "Can't send RSRR Route Reply because %d is too many vifs",
           numvifs);
       return;
   }

   /* Set up message */
   rsrr = (struct rsrr_header *) rsrr_send_buf;
   rsrr->version = 1;
   rsrr->type = RSRR_INITIAL_REPLY;
   rsrr->flags = 0;
   rsrr->num = numvifs;

   vif_list = (struct rsrr_vif *) (rsrr_send_buf + RSRR_HEADER_LEN);

   /* Include the vif list. */
   for (vifi=0, v = uvifs; vifi < numvifs; vifi++, v++) {
       vif_list[vifi].id = vifi;
       vif_list[vifi].status = 0;
       if (v->uv_flags & VIFF_DISABLED)
           BIT_SET(vif_list[vifi].status,RSRR_DISABLED_BIT);
       vif_list[vifi].threshold = v->uv_threshold;
       vif_list[vifi].local_addr.s_addr = v->uv_lcl_addr;
   }

   /* Get the size. */
   sendlen = RSRR_HEADER_LEN + numvifs*RSRR_VIF_LEN;

   /* Send it. */
   logit(LOG_INFO, 0, "Send RSRR Initial Reply");
   rsrr_send(sendlen);
}

/* Send a Route Reply to the reservation protocol.  The Route Query
* contains the query to which we are responding.  The flags contain
* the incoming flags from the query or, for route change
* notification, the flags that should be set for the reply.  The
* kernel table entry contains the routing info to use for a route
* change notification.
*/
static int
rsrr_accept_rq(route_query,flags,gt_notify)
   struct rsrr_rq *route_query;
   int flags;
   struct gtable *gt_notify;
{
   struct rsrr_header *rsrr;
   struct rsrr_rr *route_reply;
   struct gtable *gt,local_g;
   struct rtentry *r;
   int sendlen,i;
   u_long mcastgrp;

   /* Set up message */
   rsrr = (struct rsrr_header *) rsrr_send_buf;
   rsrr->version = 1;
   rsrr->type = RSRR_ROUTE_REPLY;
   rsrr->flags = 0;
   rsrr->num = 0;

   route_reply = (struct rsrr_rr *) (rsrr_send_buf + RSRR_HEADER_LEN);
   route_reply->dest_addr.s_addr = route_query->dest_addr.s_addr;
   route_reply->source_addr.s_addr = route_query->source_addr.s_addr;
   route_reply->query_id = route_query->query_id;

   /* Blank routing entry for error. */
   route_reply->in_vif = 0;
   route_reply->reserved = 0;
   route_reply->out_vif_bm = 0;

   /* Get the size. */
   sendlen = RSRR_RR_LEN;

   /* If kernel table entry is defined, then we are sending a Route Reply
    * due to a Route Change Notification event.  Use the kernel table entry
    * to supply the routing info.
    */
   if (gt_notify) {
       /* Set flags */
       rsrr->flags = flags;
       /* Include the routing entry. */
       route_reply->in_vif = gt_notify->gt_route->rt_parent;
       route_reply->out_vif_bm = gt_notify->gt_grpmems;

   } else if (find_src_grp(route_query->source_addr.s_addr, 0,
                           route_query->dest_addr.s_addr)) {

       /* Found kernel entry. Code taken from add_table_entry() */
       gt = gtp ? gtp->gt_gnext : kernel_table;

       /* Include the routing entry. */
       route_reply->in_vif = gt->gt_route->rt_parent;
       route_reply->out_vif_bm = gt->gt_grpmems;

       /* Cache reply if using route change notification. */
       if BIT_TST(flags,RSRR_NOTIFICATION_BIT) {
           rsrr_cache(gt,route_query);
           BIT_SET(rsrr->flags,RSRR_NOTIFICATION_BIT);
       }

   } else {
       /* No kernel entry; use routing table. */
       r = determine_route(route_query->source_addr.s_addr);

       if (r != NULL) {
           /* We need to mimic what will happen if a data packet
            * is forwarded by multicast routing -- the kernel will
            * make an upcall and mrouted will install a route in the kernel.
            * Our outgoing vif bitmap should reflect what that table
            * will look like.  Grab code from add_table_entry().
            * This is gross, but it's probably better to be accurate.
            */

           gt = &local_g;
           mcastgrp = route_query->dest_addr.s_addr;

           gt->gt_mcastgrp     = mcastgrp;
           gt->gt_grpmems      = 0;
           gt->gt_scope        = 0;
           gt->gt_route        = r;

           /* obtain the multicast group membership list */
           for (i = 0; i < numvifs; i++) {
               if (VIFM_ISSET(i, r->rt_children) &&
                   !(VIFM_ISSET(i, r->rt_leaves)))
                   VIFM_SET(i, gt->gt_grpmems);

               if (VIFM_ISSET(i, r->rt_leaves) && grplst_mem(i, mcastgrp))
                   VIFM_SET(i, gt->gt_grpmems);
           }

           GET_SCOPE(gt);
           gt->gt_grpmems &= ~gt->gt_scope;

           /* Include the routing entry. */
           route_reply->in_vif = gt->gt_route->rt_parent;
           route_reply->out_vif_bm = gt->gt_grpmems;

       } else {
           /* Set error bit. */
           BIT_SET(rsrr->flags,RSRR_ERROR_BIT);
       }
   }

   if (gt_notify)
       logit(LOG_INFO, 0, "Route Change: Send RSRR Route Reply");

   else
       logit(LOG_INFO, 0, "Send RSRR Route Reply");

   logit(LOG_INFO, 0, "for src %s dst %s in vif %d out vif %lu\n",
       inet_fmt(route_reply->source_addr.s_addr),
       inet_fmt(route_reply->dest_addr.s_addr),
       route_reply->in_vif, route_reply->out_vif_bm);

   /* Send it. */
   return rsrr_send(sendlen);
}

/* Send an RSRR message. */
static int
rsrr_send(sendlen)
   int sendlen;
{
   int error;

   /* Send it. */
   error = sendto(rsrr_socket, rsrr_send_buf, sendlen, 0,
                  (struct sockaddr *)&client_addr, client_length);

   /* Check for errors. */
   if (error < 0) {
       logit(LOG_WARNING, errno, "Failed send on RSRR socket");
   } else if (error != sendlen) {
       logit(LOG_WARNING, 0,
           "Sent only %d out of %d bytes on RSRR socket\n", error, sendlen);
   }
   return error;
}

/* Cache a message being sent to a client.  Currently only used for
* caching Route Reply messages for route change notification.
*/
static void
rsrr_cache(gt,route_query)
   struct gtable *gt;
   struct rsrr_rq *route_query;
{
   struct rsrr_cache *rc, **rcnp;
   struct rsrr_header *rsrr;

   rsrr = (struct rsrr_header *) rsrr_send_buf;

   rcnp = &gt->gt_rsrr_cache;
   while ((rc = *rcnp) != NULL) {
       if ((rc->route_query.source_addr.s_addr ==
            route_query->source_addr.s_addr) &&
           (rc->route_query.dest_addr.s_addr ==
            route_query->dest_addr.s_addr) &&
           (!strcmp(rc->client_addr.sun_path,client_addr.sun_path))) {
           /* Cache entry already exists.
            * Check if route notification bit has been cleared.
            */
           if (!BIT_TST(rsrr->flags,RSRR_NOTIFICATION_BIT)) {
               /* Delete cache entry. */
               *rcnp = rc->next;
               free(rc);
           } else {
               /* Update */
               rc->route_query.query_id = route_query->query_id;
               logit(LOG_DEBUG, 0,
                       "Update cached query id %ld from client %s\n",
                       rc->route_query.query_id, rc->client_addr.sun_path);
           }
           return;
       }
       rcnp = &rc->next;
   }

   /* Cache entry doesn't already exist.  Create one and insert at
    * front of list.
    */
   rc = (struct rsrr_cache *) malloc(sizeof(struct rsrr_cache));
   if (rc == NULL)
       logit(LOG_ERR, 0, "ran out of memory");
   rc->route_query.source_addr.s_addr = route_query->source_addr.s_addr;
   rc->route_query.dest_addr.s_addr = route_query->dest_addr.s_addr;
   rc->route_query.query_id = route_query->query_id;
   strlcpy(rc->client_addr.sun_path, client_addr.sun_path,
       sizeof(rc->client_addr.sun_path));
   rc->client_length = client_length;
   rc->next = gt->gt_rsrr_cache;
   gt->gt_rsrr_cache = rc;
   logit(LOG_DEBUG, 0, "Cached query id %ld from client %s\n",
          rc->route_query.query_id,rc->client_addr.sun_path);
}

/* Send all the messages in the cache.  Currently this is used to send
* all the cached Route Reply messages for route change notification.
*/
void
rsrr_cache_send(gt,notify)
   struct gtable *gt;
   int notify;
{
   struct rsrr_cache *rc, **rcnp;
   int flags = 0;

   if (notify)
       BIT_SET(flags,RSRR_NOTIFICATION_BIT);

   rcnp = &gt->gt_rsrr_cache;
   while ((rc = *rcnp) != NULL) {
       if (rsrr_accept_rq(&rc->route_query,flags,gt) < 0) {
           logit(LOG_DEBUG, 0, "Deleting cached query id %ld from client %s\n",
                  rc->route_query.query_id,rc->client_addr.sun_path);
           /* Delete cache entry. */
           *rcnp = rc->next;
           free(rc);
       } else {
           rcnp = &rc->next;
       }
   }
}

/* Clean the cache by deleting all entries. */
void
rsrr_cache_clean(gt)
   struct gtable *gt;
{
   struct rsrr_cache *rc,*rc_next;

   printf("cleaning cache for group %s\n",
           inet_fmt(gt->gt_mcastgrp));
   rc = gt->gt_rsrr_cache;
   while (rc) {
       rc_next = rc->next;
       free(rc);
       rc = rc_next;
   }
   gt->gt_rsrr_cache = NULL;
}

void
rsrr_clean()
{
   unlink(RSRR_SERV_PATH);
}

#endif /* RSRR */