/* tr.c

   token ring interface support
   Contributed in May of 1999 by Andrew Chittenden */

/*
 * Copyright (c) 2004-2007,2009 by Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 1996-2003 by Internet Software Consortium
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *   Internet Systems Consortium, Inc.
 *   950 Charter Street
 *   Redwood City, CA 94063
 *   <info@isc.org>
 *   https://www.isc.org/
 */

#include "dhcpd.h"

#if defined (HAVE_TR_SUPPORT) && \
	(defined (PACKET_ASSEMBLY) || defined (PACKET_DECODING))
#include "includes/netinet/ip.h"
#include "includes/netinet/udp.h"
#include "includes/netinet/if_ether.h"
#include "netinet/if_tr.h"
#include <sys/time.h>

/*
 * token ring device handling subroutines.  These are required as token-ring
 * does not have a simple on-the-wire header but requires the use of
 * source routing
 */

static int insert_source_routing PROTO ((struct trh_hdr *trh, struct interface_info* interface));
static void save_source_routing PROTO ((struct trh_hdr *trh, struct interface_info* interface));
static void expire_routes PROTO ((void));

/*
 * As we keep a list of interesting routing information only, a singly
 * linked list is all we need
 */
struct routing_entry {
        struct routing_entry *next;
        unsigned char addr[TR_ALEN];
        unsigned char iface[5];
        u_int16_t rcf;			/* route control field */
        u_int16_t rseg[8];		/* routing registers */
        unsigned long access_time;	/* time we last used this entry */
};

static struct routing_entry *routing_info = NULL;

static int routing_timeout = 10;
static struct timeval routing_timer;

void assemble_tr_header (interface, buf, bufix, to)
	struct interface_info *interface;
	unsigned char *buf;
	unsigned *bufix;
	struct hardware *to;
{
        struct trh_hdr *trh;
        int hdr_len;
        struct trllc *llc;


        /* set up the token header */
        trh = (struct trh_hdr *) &buf[*bufix];
        if (interface -> hw_address.hlen - 1 == sizeof (trh->saddr))
                memcpy (trh->saddr, &interface -> hw_address.hbuf [1],
                                    sizeof (trh->saddr));
        else
                memset (trh->saddr, 0x00, sizeof (trh->saddr));

        if (to && to -> hlen == 7) /* XXX */
                memcpy (trh->daddr, &to -> hbuf [1], sizeof trh->daddr);
        else
                memset (trh->daddr, 0xff, sizeof (trh->daddr));

	hdr_len = insert_source_routing (trh, interface);

        trh->ac = AC;
        trh->fc = LLC_FRAME;

        /* set up the llc header for snap encoding after the tr header */
        llc = (struct trllc *)(buf + *bufix + hdr_len);
        llc->dsap = EXTENDED_SAP;
        llc->ssap = EXTENDED_SAP;
        llc->llc = UI_CMD;
        llc->protid[0] = 0;
        llc->protid[1] = 0;
        llc->protid[2] = 0;
        llc->ethertype = htons(ETHERTYPE_IP);

        hdr_len += sizeof(struct trllc);

        *bufix += hdr_len;
}


static unsigned char tr_broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

/*
 * decoding the token header is a bit complex as you can see here. It is
 * further complicated by the linux kernel stripping off some valuable
 * information (see comment below) even though we've asked for the raw
 * packets.
 */
ssize_t decode_tr_header (interface, buf, bufix, from)
        struct interface_info *interface;
        unsigned char *buf;
        unsigned bufix;
        struct hardware *from;
{
        struct trh_hdr *trh = (struct trh_hdr *) buf + bufix;
        struct trllc *llc;
	struct ip *ip;
	struct udphdr *udp;
        unsigned int route_len = 0;
        ssize_t hdr_len;
        struct timeval now;

        /* see whether any source routing information has expired */
        gettimeofday(&now, NULL);

	if (routing_timer.tv_sec == 0)
                routing_timer.tv_sec = now.tv_sec + routing_timeout;
        else if ((now.tv_sec - routing_timer.tv_sec) > 0)
                expire_routes();

        /* the kernel might have stripped off the source
         * routing bit. We try a heuristic to determine whether
         * this is the case and put it back on if so
         */
        route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
        llc = (struct trllc *)(buf + bufix + sizeof(struct trh_hdr)-TR_MAXRIFLEN+route_len);
        if (llc->dsap == EXTENDED_SAP
                        && llc->ssap == EXTENDED_SAP
                        && llc->llc == UI_CMD
                        && llc->protid[0] == 0
                        && llc->protid[1] == 0
                        && llc->protid[2] == 0) {
                /* say there is source routing information present */
                trh->saddr[0] |= TR_RII;	
        }

	if (trh->saddr[0] & TR_RII)
                route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
        else
                route_len = 0;

        hdr_len = sizeof (struct trh_hdr) - TR_MAXRIFLEN + route_len;

        /* now filter out unwanted packets: this is based on the packet
         * filter code in bpf.c */
        llc = (struct trllc *)(buf + bufix + hdr_len);
        ip = (struct ip *) (llc + 1);
        udp = (struct udphdr *) ((unsigned char*) ip + IP_HL (ip));

        /* make sure it is a snap encoded, IP, UDP, unfragmented packet sent
         * to our port */
        if (llc->dsap != EXTENDED_SAP
                        || ntohs(llc->ethertype) != ETHERTYPE_IP
                        || ip->ip_p != IPPROTO_UDP
                        || (ntohs (ip->ip_off) & IP_OFFMASK) != 0
                        || udp->uh_dport != local_port)
                return -1;

        /* only save source routing information for packets from valued hosts */
        save_source_routing(trh, interface);

        return hdr_len + sizeof (struct trllc);
}

/* insert_source_routing inserts source route information into the token ring
 * header
 */
static int insert_source_routing (trh, interface)
        struct trh_hdr *trh;
        struct interface_info* interface;
{
	struct routing_entry *rover;
        struct timeval now;
        unsigned int route_len = 0;

        gettimeofday(&now, NULL);

	/* single route broadcasts as per rfc 1042 */
	if (memcmp(trh->daddr, tr_broadcast,TR_ALEN) == 0) {
		trh->saddr[0] |= TR_RII;
		trh->rcf = ((sizeof(trh->rcf)) << 8) & TR_RCF_LEN_MASK;  
                trh->rcf |= (TR_RCF_FRAME2K | TR_RCF_LIMITED_BROADCAST);
		trh->rcf = htons(trh->rcf);
	} else {
		/* look for a routing entry */
                for (rover = routing_info; rover != NULL; rover = rover->next) {
                        if (memcmp(rover->addr, trh->daddr, TR_ALEN) == 0)
                                break;
                }

		if (rover != NULL) {
                        /* success: route that frame */
                        if ((rover->rcf & TR_RCF_LEN_MASK) >> 8) {
                                u_int16_t rcf = rover->rcf;
				memcpy(trh->rseg,rover->rseg,sizeof(trh->rseg));
				rcf ^= TR_RCF_DIR_BIT;	
				rcf &= ~TR_RCF_BROADCAST_MASK;
                                trh->rcf = htons(rcf);
				trh->saddr[0] |= TR_RII;
			}
			rover->access_time = now.tv_sec;
		} else {
                        /* we don't have any routing information so send a
                         * limited broadcast */
                        trh->saddr[0] |= TR_RII;
                        trh->rcf = ((sizeof(trh->rcf)) << 8) & TR_RCF_LEN_MASK;  
                        trh->rcf |= (TR_RCF_FRAME2K | TR_RCF_LIMITED_BROADCAST);
                        trh->rcf = htons(trh->rcf);
		}
	}

	/* return how much of the header we've actually used */
	if (trh->saddr[0] & TR_RII)
                route_len = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
        else
                route_len = 0;

        return sizeof (struct trh_hdr) - TR_MAXRIFLEN + route_len;
}

/*
 * save any source routing information
 */
static void save_source_routing(trh, interface)
        struct trh_hdr *trh;
        struct interface_info *interface;
{
        struct routing_entry *rover;
        struct timeval now;
        unsigned char saddr[TR_ALEN];
        u_int16_t rcf = 0;

        gettimeofday(&now, NULL);

        memcpy(saddr, trh->saddr, sizeof(saddr));
        saddr[0] &= 0x7f;   /* strip off source routing present flag */

        /* scan our table to see if we've got it */
        for (rover = routing_info; rover != NULL; rover = rover->next) {
                if (memcmp(&rover->addr[0], &saddr[0], TR_ALEN) == 0)
                        break;
        }

        /* found an entry so update it with fresh information */
        if (rover != NULL) {
                if ((trh->saddr[0] & TR_RII) &&
		    ((ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8) > 2) {
                        rcf = ntohs(trh->rcf);
                        rcf &= ~TR_RCF_BROADCAST_MASK;
                        memcpy(rover->rseg, trh->rseg, sizeof(rover->rseg));
                }
                rover->rcf = rcf;
                rover->access_time = now.tv_sec;
                return;     /* that's all folks */
        }

        /* no entry found, so create one */
        rover = dmalloc (sizeof (struct routing_entry), MDL);
        if (rover == NULL) {
                fprintf(stderr,
			"%s: unable to save source routing information\n",
			__FILE__);
                return;
        }

        memcpy(rover->addr, saddr, sizeof(rover->addr));
        memcpy(rover->iface, interface->name, 5);
        rover->access_time = now.tv_sec;
        if (trh->saddr[0] & TR_RII) {
                if (((ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8) > 2) {
                        rcf = ntohs(trh->rcf);
                        rcf &= ~TR_RCF_BROADCAST_MASK;
                        memcpy(rover->rseg, trh->rseg, sizeof(rover->rseg));
                }
                rover->rcf = rcf;
        }

        /* insert into list */
        rover->next = routing_info;
        routing_info = rover;

        return;
}

/*
 * get rid of old routes
 */
static void expire_routes()
{
        struct routing_entry *rover;
        struct routing_entry **prover = &routing_info;
        struct timeval now;

        gettimeofday(&now, NULL);

        while((rover = *prover) != NULL) {
                if ((now.tv_sec - rover->access_time) > routing_timeout) {
                        *prover = rover->next;
                        dfree (rover, MDL);
                } else
                        prover = &rover->next;
        }

        /* Reset the timer */
        routing_timer.tv_sec = now.tv_sec + routing_timeout;
        routing_timer.tv_usec = now.tv_usec;
}

#endif
