/* routing.c    Routing table manipulation routines
 *
 * Copyright 1991, Michael Westerhof, Sun Microsystems, Inc.
 * This software may be freely used, distributed, or modified, providing
 * this header is not removed.
 *
 */

#include <memory.h>
#include <stdio.h>
#include <syslog.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>

#include "ax25ipd.h"

#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif

/* The routing table structure is not visible outside this module. */

struct route_table_entry {
	unsigned char callsign[7];	/* the callsign and ssid */
	unsigned char padcall;	/* always set to zero */
	union {
		unsigned char ip_addr[4];	/* the IP address */
		struct in_addr ip_addr_in;
	};
	unsigned short udp_port;	/* the port number if udp */
	unsigned char pad1;
	unsigned char pad2;
	unsigned int flags;	/* route flags */
	struct route_table_entry *next;
};

static struct route_table_entry *route_tbl;
static struct route_table_entry *default_route;

/* The Broadcast address structure is not visible outside this module either */

struct bcast_table_entry {
	unsigned char callsign[7];	/* The broadcast address */
	struct bcast_table_entry *next;
};

static struct bcast_table_entry *bcast_tbl;

/* Initialize the routing module */
void route_init(void)
{
	route_tbl = NULL;
	default_route = NULL;
	bcast_tbl = NULL;
}

/* Add a new route entry */
void route_add(unsigned char *ip, unsigned char *call, int udpport,
	unsigned int flags)
{
	struct route_table_entry *rl, *rn;
	int i;

	/* Check we have an IP address */
	if (ip == NULL)
		return;

	/* Check we have a callsign */
	if (call == NULL)
		return;

	/* Find the last entry in the list */
	rl = route_tbl;
	if (route_tbl)
		while (rl->next)
			rl = rl->next;

	rn = (struct route_table_entry *)
	    malloc(sizeof(struct route_table_entry));

	/* Build this entry ... */
	for (i = 0; i < 6; i++)
		rn->callsign[i] = call[i] & 0xfe;
	rn->callsign[6] = (call[6] & 0x1e) | 0x60;
	rn->padcall = 0;
	memcpy(rn->ip_addr, ip, 4);
	rn->udp_port = htons(udpport);
	rn->pad1 = 0;
	rn->pad2 = 0;
	rn->flags = flags;
	rn->next = NULL;

	/* Update the default_route pointer if this is a default route */
	if (flags & AXRT_DEFAULT)
		default_route = rn;

	if (rl)			/* ... the list is already started add the new route */
		rl->next = rn;
	else			/* ... start the list off */
		route_tbl = rn;

	/* Log this entry ... */
	LOGL4("added route: %s %s %s %d %d\n",
	      call_to_a(rn->callsign),
	      inet_ntoa(rn->ip_addr_in),
	      rn->udp_port ? "udp" : "ip", ntohs(rn->udp_port), flags);
}

/* Add a new broadcast address entry */
void bcast_add(unsigned char *call)
{
	struct bcast_table_entry *bl, *bn;
	int i;

	/* Check we have a callsign */
	if (call == NULL)
		return;

	/* Find the last entry in the list */
	bl = bcast_tbl;
	if (bcast_tbl)
		while (bl->next)
			bl = bl->next;

	bn = (struct bcast_table_entry *)
	    malloc(sizeof(struct bcast_table_entry));

	/* Build this entry ... */
	for (i = 0; i < 6; i++)
		bn->callsign[i] = call[i] & 0xfe;
	bn->callsign[6] = (call[6] & 0x1e) | 0x60;

	bn->next = NULL;

	if (bl)			/* ... the list is already started add the new route */
		bl->next = bn;
	else			/* ... start the list off */
		bcast_tbl = bn;

	/* Log this entry ... */
	LOGL4("added broadcast address: %s\n", call_to_a(bn->callsign));
}

/*
 * Return an IP address and port number given a callsign.
 * We return a pointer to the address; the port number can be found
 * immediately following the IP address. (UGLY coding; to be fixed later!)
 */

unsigned char *call_to_ip(unsigned char *call)
{
	struct route_table_entry *rp;
	unsigned char mycall[7];
	int i;

	if (call == NULL)
		return NULL;

	for (i = 0; i < 6; i++)
		mycall[i] = call[i] & 0xfe;

	mycall[6] = (call[6] & 0x1e) | 0x60;

	LOGL4("lookup call %s ", call_to_a(mycall));

	rp = route_tbl;
	while (rp) {
		if (addrmatch(mycall, rp->callsign)) {
			LOGL4("found ip addr %s\n",
			      inet_ntoa(rp->ip_addr_in));
			return rp->ip_addr;
		}
		rp = rp->next;
	}

	/*
	 * No match found in the routing table, use the default route if
	 * we have one defined.
	 */
	if (default_route) {
		LOGL4("failed, using default ip addr %s\n",
		      inet_ntoa(default_route->ip_addr_in));
		return default_route->ip_addr;
	}

	LOGL4("failed.\n");
	return NULL;
}

/*
 * Accept a callsign and return true if it is a broadcast address, or false
 * if it is not found on the list
 */
int is_call_bcast(unsigned char *call)
{
	struct bcast_table_entry *bp;
	unsigned char bccall[7];
	int i;

	if (call == NULL)
		return FALSE;

	for (i = 0; i < 6; i++)
		bccall[i] = call[i] & 0xfe;

	bccall[6] = (call[6] & 0x1e) | 0x60;

	LOGL4("lookup broadcast %s ", call_to_a(bccall));

	bp = bcast_tbl;
	while (bp) {
		if (addrmatch(bccall, bp->callsign)) {
			LOGL4("found broadcast %s\n",
			      call_to_a(bp->callsign));
			return TRUE;
		}
		bp = bp->next;
	}
	return FALSE;
}

/* Traverse the routing table, transmitting the packet to each bcast route */
void send_broadcast(unsigned char *buf, int l)
{
	struct route_table_entry *rp;

	rp = route_tbl;
	while (rp) {
		if (rp->flags & AXRT_BCAST) {
			send_ip(buf, l, rp->ip_addr);
		}
		rp = rp->next;
	}
}

/* print out the list of routes */
void dump_routes(void)
{
	struct route_table_entry *rp;
	int i;

	for (rp = route_tbl, i = 0; rp; rp = rp->next)
		i++;

	LOGL1("\n%d active routes.\n", i);

	rp = route_tbl;
	while (rp) {
		LOGL1("  %s\t%s\t%s\t%d\t%d\n",
		      call_to_a(rp->callsign),
		      inet_ntoa(rp->ip_addr_in),
		      rp->udp_port ? "udp" : "ip",
		      ntohs(rp->udp_port), rp->flags);
		rp = rp->next;
	}
	fflush(stdout);
}
