/*
 * ng_fwdswitchswitch.c
 *
 * Copyright (c) 2002, Iscanet Internet Services S.r.l.
 * Copyright (c) 2000, Whistle Communications, Inc.
 * 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 or object code must include the above
 *    copyright notice, this list of conditions, the following disclaimer
 *    in the documentation and/or other materials provider with the
 *    distribution.
 * 2. Neither the name of the copyright holders 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 COPYRIGHT HOLDERS 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 COPYRIGHT
 * OWNER 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.
 *
 * Author: Rocco Lucia <rlucia@iscanet.com>
 *
 * $Id: ng_fwdswitch.c,v 1.5 2004/02/12 01:52:09 rlucia Exp $
 */

/*
 * ng_fwdswitch(4) netgraph node type
 *
 * Packets received on the "in" hook are sent out each of the
 * "out" hooks in round-robin fashion. Packets received on any
 * "out" hook are always delivered to the "one" hook.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/ctype.h>
#include <sys/mbuf.h>
#include <sys/errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/ng_parse.h>
#include "ng_fwdswitch.h"

#define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0))

/* Internal forwarding table */
struct fwd_networks_priv {
	struct in_addr	ipnetwork;
	struct in_addr	netmask;
	u_int16_t	dsti;
	u_int32_t	mtchcnt;
};

/* Per-link private data */
struct ng_fwdswitch_link {
	hook_p				hook;	/* netgraph hook */
	struct ng_fwdswitch_link_stats	stats;	/* link stats */
};

/* Per-node private data */
struct ng_fwdswitch_private {
	struct ng_fwdswitch_config	conf;		/* node configuration */
	struct ng_fwdswitch_link	one;		/* "one" hook */
	struct ng_fwdswitch_link	two;		/* "two" hook */
	struct ng_fwdswitch_link	out[NG_FWDSWITCH_MAX_LINKS];
	u_int16_t			nextOut;	/* next round-robin */
	u_int16_t			numActiveOut;	/* # active "out" */
	u_int16_t			activeOut[NG_FWDSWITCH_MAX_LINKS];
	u_int16_t			fwdtable_len;
	struct ng_fwdswitch_fwdconfig	*fwdconfig;
	struct fwd_networks_priv	*fwdtable;
	u_int8_t			fwd_deflink;
};
typedef struct ng_fwdswitch_private *priv_p;

/*
struct fwd_networks {
	u_int32_t	ipnetwork;
	u_int32_t	netmask;
	u_int16_t	interface;
	u_int32_t	matchcount;
};
*/

int fwd_netnum = 0;

/* Netgraph node methods */
static ng_constructor_t	ng_fwdswitch_constructor;
static ng_rcvmsg_t	ng_fwdswitch_rcvmsg;
static ng_shutdown_t	ng_fwdswitch_rmnode;
static ng_newhook_t	ng_fwdswitch_newhook;
static ng_rcvdata_t	ng_fwdswitch_rcvdata;
static ng_disconnect_t	ng_fwdswitch_disconnect;

/* Other functions */
static void		ng_fwdswitch_update_out(priv_p priv);

/* Store each hook's link number in the private field */
#define LINK_NUM(hook)		(*(int16_t *)(&(hook)->private))




/******************************************************************
		    NETGRAPH PARSE TYPES
******************************************************************/

/* Parse type for struct ng_fwdswitch_config */
static const struct ng_parse_fixedarray_info
    ng_fwdswitch_enableLinks_array_type_info = {
	&ng_parse_uint8_type,
	NG_FWDSWITCH_MAX_LINKS
};
static const struct ng_parse_type ng_fwdswitch_enableLinks_array_type = {
	&ng_parse_fixedarray_type,
	&ng_fwdswitch_enableLinks_array_type_info,
};
static const struct ng_parse_struct_field ng_fwdswitch_config_type_fields[]
	= NG_FWDSWITCH_CONFIG_TYPE_INFO(&ng_fwdswitch_enableLinks_array_type);
static const struct ng_parse_type ng_fwdswitch_config_type = {
	&ng_parse_struct_type,
	&ng_fwdswitch_config_type_fields,
};

/* Parse type for struct ng_fwdswitch_link_stats */
static const struct ng_parse_struct_field
	ng_fwdswitch_link_stats_type_fields[] = NG_FWDSWITCH_LINK_STATS_TYPE_INFO;
static const struct ng_parse_type ng_fwdswitch_link_stats_type = {
	&ng_parse_struct_type,
	&ng_fwdswitch_link_stats_type_fields
};

static int	ng_fwdswitch_setfwdconfig(priv_p priv, const struct ng_fwdswitch_fwdconfig *fc);
static int	ng_fwdswitch_buildfwdtable(priv_p priv, const struct ng_fwdswitch_fwdconfig *fc);

/* Parse type for struct ng_fwdswitch_fwdcfg */
static const struct ng_parse_struct_field ng_fwdswitch_fwdconfig_table_type_fields[] = {
		{ "ipaddr",	&ng_parse_ipaddr_type	},
		{ "netmask",	&ng_parse_ipaddr_type	},
		{ "dstlink",	&ng_parse_uint8_type	},
		{ "matchcount",	&ng_parse_uint32_type	},
		{ NULL }
};
static const struct ng_parse_type ng_fwdswitch_fwdconfig_table_type = {
        &ng_parse_struct_type,
        &ng_fwdswitch_fwdconfig_table_type_fields
};

/* Parse type for the field 'fwd_table' in struct ng_fwdswitch_fwdconfig */
static int
ng_fwdswitch_fwdconfigary_getLength(const struct ng_parse_type *type,
        const u_char *start, const u_char *buf)
{
        const struct ng_fwdswitch_fwdconfig *fc;

        fc = (const struct ng_fwdswitch_fwdconfig *)
            (buf - OFFSETOF(struct ng_fwdswitch_fwdconfig, fwd_table));
        return fc->fwd_table_len;
}

static const struct ng_parse_array_info ng_fwdswitch_fwdconfigary_info = {
        &ng_fwdswitch_fwdconfig_table_type,
        &ng_fwdswitch_fwdconfigary_getLength,
        NULL
};
static const struct ng_parse_type ng_fwdswitch_fwdconfigary_type = {
        &ng_parse_array_type,
        &ng_fwdswitch_fwdconfigary_info
};

/* Parse type for struct ng_fwdswitch_fwdconfig */
static const struct ng_parse_struct_field ng_fwdswitch_fwdconfig_type_fields[]
	= NG_FWDSWITCH_FWDCONFIG_TYPE_INFO(&ng_fwdswitch_fwdconfigary_type);
static const struct ng_parse_type ng_fwdswitch_fwdconfig_type = {
        &ng_parse_struct_type,
        &ng_fwdswitch_fwdconfig_type_fields
};


/* List of commands and how to convert arguments to/from ASCII */
static const struct ng_cmdlist ng_fwdswitch_cmdlist[] = {
	{
	  NGM_FWDSWITCH_COOKIE,
	  NGM_FWDSWITCH_SET_CONFIG,
	  "setconfig",
	  &ng_fwdswitch_config_type,
	  NULL
	},
	{
	  NGM_FWDSWITCH_COOKIE,
	  NGM_FWDSWITCH_GET_CONFIG,
	  "getconfig",
	  NULL,
	  &ng_fwdswitch_config_type
	},
	{
	  NGM_FWDSWITCH_COOKIE,
	  NGM_FWDSWITCH_GET_STATS,
	  "getstats",
	  &ng_parse_int32_type,
	  &ng_fwdswitch_link_stats_type
	},
	{
	  NGM_FWDSWITCH_COOKIE,
	  NGM_FWDSWITCH_CLR_STATS,
	  "clrstats",
	  &ng_parse_int32_type,
	  NULL,
	},
	{
	  NGM_FWDSWITCH_COOKIE,
	  NGM_FWDSWITCH_GETCLR_STATS,
	  "getclrstats",
	  &ng_parse_int32_type,
	  &ng_fwdswitch_link_stats_type
	},
	{
	  NGM_FWDSWITCH_COOKIE,
	  NGM_FWDSWITCH_SET_FWDCONFIG,
	  "setfwdconfig",
	  &ng_fwdswitch_fwdconfig_type,
	  NULL
	},
	{
	  NGM_FWDSWITCH_COOKIE,
	  NGM_FWDSWITCH_GET_FWDCONFIG,
	  "getfwdconfig",
	  NULL,
	  &ng_fwdswitch_fwdconfig_type
	},
	{ 0 }
};

/* Node type descriptor */
static struct ng_type ng_fwdswitch_typestruct = {
	NG_VERSION,
	NG_FWDSWITCH_NODE_TYPE,
	NULL,
	ng_fwdswitch_constructor,
	ng_fwdswitch_rcvmsg,
	ng_fwdswitch_rmnode,
	ng_fwdswitch_newhook,
	NULL,
	NULL,
	ng_fwdswitch_rcvdata,
	ng_fwdswitch_rcvdata,
	ng_fwdswitch_disconnect,
	ng_fwdswitch_cmdlist,
};
NETGRAPH_INIT(fwdswitch, &ng_fwdswitch_typestruct);

/******************************************************************
		    NETGRAPH NODE METHODS
******************************************************************/

/*
 * Node constructor
 */
static int
ng_fwdswitch_constructor(node_p *nodep)
{
	priv_p priv;
	int error;

	/* Allocate and initialize private info */
	MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO);
	if (priv == NULL)
		return (ENOMEM);
	priv->conf.xmitAlg = NG_FWDSWITCH_XMIT_ROUNDROBIN;
	priv->conf.failAlg = NG_FWDSWITCH_FAIL_MANUAL;
	priv->fwdtable_len = 0;

	/* Call superclass constructor */
	if ((error = ng_make_node_common(&ng_fwdswitch_typestruct, nodep))) {
		FREE(priv, M_NETGRAPH);
		return (error);
	}
	(*nodep)->private = priv;

	/* Done */
	return (0);
}

/*
 * Method for attaching a new hook
 */
static	int
ng_fwdswitch_newhook(node_p node, hook_p hook, const char *name)
{
	const priv_p priv = node->private;
	struct ng_fwdswitch_link *link;
	int linkNum;
	u_long i;

	/* Which hook? */
	if (strncmp(name, NG_FWDSWITCH_HOOK_MANY_PREFIX,
	    strlen(NG_FWDSWITCH_HOOK_MANY_PREFIX)) == 0) {
		const char *cp;
		char *eptr;

		cp = name + strlen(NG_FWDSWITCH_HOOK_MANY_PREFIX);
		if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
			return (EINVAL);
		i = strtoul(cp, &eptr, 10);
		if (*eptr != '\0' || i < 0 || i >= NG_FWDSWITCH_MAX_LINKS)
			return (EINVAL);
		linkNum = (int)i;
		link = &priv->out[linkNum];
	} else if (strcmp(name, NG_FWDSWITCH_HOOK_ONE) == 0) {
		linkNum = NG_FWDSWITCH_ONE_LINKNUM;
		link = &priv->one;
	} else if (strcmp(name, NG_FWDSWITCH_HOOK_TWO) == 0) {
		linkNum = NG_FWDSWITCH_TWO_LINKNUM;
		link = &priv->two;
	} else
		return (EINVAL);

	/* Is hook already connected? (should never happen) */
	if (link->hook != NULL)
		return (EISCONN);

	/* Setup private info for this link */
	LINK_NUM(hook) = linkNum;
	link->hook = hook;
	bzero(&link->stats, sizeof(link->stats));
	if (linkNum != NG_FWDSWITCH_ONE_LINKNUM && linkNum != NG_FWDSWITCH_TWO_LINKNUM) {
		priv->conf.enabledLinks[linkNum] = 1;	/* auto-enable link */
		ng_fwdswitch_update_out(priv);
	}

	/* Done */
	return (0);
}

/*
 * Receive a control message
 */
static int
ng_fwdswitch_rcvmsg(node_p node, struct ng_mesg *msg,
	const char *retaddr, struct ng_mesg **rptr)
{
	const priv_p priv = node->private;
	struct fwd_networks_priv *fwdtable;
	struct ng_mesg *resp = NULL;
	int error = 0, i = 0;


	switch (msg->header.typecookie) {
	case NGM_FWDSWITCH_COOKIE:
		switch (msg->header.cmd) {
		case NGM_FWDSWITCH_SET_CONFIG:
		    {
			struct ng_fwdswitch_config *conf;
			int i;

			/* Check that new configuration is valid */
			if (msg->header.arglen != sizeof(*conf)) {
				error = EINVAL;
				break;
			}
			conf = (struct ng_fwdswitch_config *)msg->data;
			switch (conf->xmitAlg) {
			case NG_FWDSWITCH_XMIT_ROUNDROBIN:
				break;
			default:
				error = EINVAL;
				break;
			}
			switch (conf->failAlg) {
			case NG_FWDSWITCH_FAIL_MANUAL:
				break;
			default:
				error = EINVAL;
				break;
			}
			if (error != 0)
				break;

			/* Normalized out link enabled bits */ 
			for (i = 0; i < NG_FWDSWITCH_MAX_LINKS; i++)
				conf->enabledLinks[i] = !!conf->enabledLinks[i];

			/* Copy config and reset */
			bcopy(conf, &priv->conf, sizeof(*conf));
			ng_fwdswitch_update_out(priv);
			break;
		    }
		case NGM_FWDSWITCH_GET_CONFIG:
		    {
			struct ng_fwdswitch_config *conf;

			NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT);
			if (resp == NULL) {
				error = ENOMEM;
				break;
			}
			conf = (struct ng_fwdswitch_config *)resp->data;
			bcopy(&priv->conf, conf, sizeof(priv->conf));
			break;
		    }
		case NGM_FWDSWITCH_GET_STATS:
		case NGM_FWDSWITCH_CLR_STATS:
		case NGM_FWDSWITCH_GETCLR_STATS:
		    {
			struct ng_fwdswitch_link *link;
			int linkNum;

			/* Get link */
			if (msg->header.arglen != sizeof(int32_t)) {
				error = EINVAL;
				break;
			}
			linkNum = *((int32_t *)msg->data);
			if (linkNum == NG_FWDSWITCH_ONE_LINKNUM)
				link = &priv->one;
			else if (linkNum == NG_FWDSWITCH_TWO_LINKNUM)
				link = &priv->two;
			else if (linkNum >= 0
			    && linkNum < NG_FWDSWITCH_MAX_LINKS) {
				link = &priv->out[linkNum];
			} else {
				error = EINVAL;
				break;
			}

			/* Get/clear stats */
			if (msg->header.cmd != NGM_FWDSWITCH_CLR_STATS) {
				NG_MKRESPONSE(resp, msg,
				    sizeof(link->stats), M_NOWAIT);
				if (resp == NULL) {
					error = ENOMEM;
					break;
				}
				bcopy(&link->stats,
				    resp->data, sizeof(link->stats));
			}
			if (msg->header.cmd != NGM_FWDSWITCH_GET_STATS)
				bzero(&link->stats, sizeof(link->stats));
			break;
		    }
		case NGM_FWDSWITCH_SET_FWDCONFIG:
		    {
			struct ng_fwdswitch_fwdconfig *const
				fc = (struct ng_fwdswitch_fwdconfig *)msg->data;

                        /* Sanity check */
/*
                        if (msg->header.arglen < sizeof(*fc)
                            || msg->header.arglen
                              != NG_FWDSWITCH_FWDCONFIG_SIZE(fc->fwd_table_len)) {
                                error = EINVAL;
				break;
			}
*/

                        /* Import the switch configuration */
                        if ((error = ng_fwdswitch_setfwdconfig(priv, fc)) != 0) {
                                break;
			}
                        break;
		    }
                case NGM_FWDSWITCH_GET_FWDCONFIG:
                    {   
                        struct ng_fwdswitch_fwdconfig *const
			fc = (struct ng_fwdswitch_fwdconfig *) priv->fwdconfig;

			fwdtable = priv->fwdtable;

			for(i=0;i < fc->fwd_table_len; i++ ) {
				fc->fwd_table[i].matchcount = fwdtable[i].mtchcnt;
			};

                        /* Sanity check */
/*
                        if (msg->header.arglen == 0) {
                                error = EINVAL;
				break;
			}
*/
                        msg->data[msg->header.arglen - 1] = '\0';

                        /* Build response */
                        NG_MKRESPONSE(resp, msg, NG_FWDSWITCH_FWDCONFIG_SIZE(fc->fwd_table_len), M_NOWAIT);
                        if (resp == NULL) {
                                error = ENOMEM;
				break;
			}
                        bcopy(fc, resp->data,
                           NG_FWDSWITCH_FWDCONFIG_SIZE(fc->fwd_table_len));
                        break;
                    }
		default:
			error = EINVAL;
			break;
		}
		break;
	default:
		error = EINVAL;
		break;
	}

	/* Done */
	if (rptr)
		*rptr = resp;
	else if (resp != NULL)
		FREE(resp, M_NETGRAPH);
	FREE(msg, M_NETGRAPH);
	return (error);
}

/*
 * Receive data on a hook
 */
static int
ng_fwdswitch_rcvdata(hook_p hook, struct mbuf *m, meta_p meta)
{
	const node_p node = hook->node;
	const priv_p priv = node->private;
	struct ng_fwdswitch_link *src;
	struct ng_fwdswitch_link *dst;
	struct fwd_networks_priv *fwdtable;
	struct ip		*ip;
	struct ether_header	*eh;

	int error = 0, net;
	int linkNum;

	/* Get link number */
	linkNum = LINK_NUM(hook);
	KASSERT(linkNum == NG_FWDSWITCH_ONE_LINKNUM
	    || linkNum == NG_FWDSWITCH_TWO_LINKNUM
	    || (linkNum >= 0 && linkNum < NG_FWDSWITCH_MAX_LINKS),
	    ("%s: linkNum=%d", __FUNCTION__, linkNum));

	/* Figure out source link */
	if( linkNum == NG_FWDSWITCH_ONE_LINKNUM ) {
		src = &priv->one;
	}
	else if( linkNum == NG_FWDSWITCH_TWO_LINKNUM ) {
		src = &priv->two;
	}
	else {
		NG_FREE_DATA(m, meta);
		return(error);
	}

	KASSERT(src->hook != NULL, ("%s: no src%d", __FUNCTION__, linkNum));

	/* Update receive stats */
	src->stats.recvPackets++;
	src->stats.recvOctets += m->m_pkthdr.len;

	if (priv->numActiveOut == 0) {
		NG_FREE_DATA(m, meta);
		return (ENOTCONN);
	}
	if (m->m_len < (sizeof(*eh)+sizeof(*ip)) ) {
		m = m_pullup(m, (sizeof(*eh)+sizeof(*ip)) );
		if (m == NULL) {
			NG_FREE_META(meta);
			return(ENOBUFS);
		};
	}

	fwdtable = priv->fwdtable;

	eh = mtod(m, struct ether_header *);
	if (eh->ether_type == htons(ETHERTYPE_IP) ) {
		ip = m->m_data + sizeof(*eh);
		for(net=0; net < priv->fwdtable_len; net++) {
			if ( ((ip->ip_src.s_addr & fwdtable[net].netmask.s_addr) == fwdtable[net].ipnetwork.s_addr ) 
			  || ((ip->ip_dst.s_addr & fwdtable[net].netmask.s_addr) == fwdtable[net].ipnetwork.s_addr ) ) {
				dst = &priv->out[fwdtable[net].dsti];
				dst->stats.xmitPackets++;
				dst->stats.xmitOctets += m->m_pkthdr.len;
				fwdtable[net].mtchcnt++;
				NG_SEND_DATA(error, dst->hook, m, meta);
				return(error);
			}
		}
	}
	
	dst = &priv->out[priv->fwd_deflink];
	dst->stats.xmitPackets++;
	dst->stats.xmitOctets += m->m_pkthdr.len;
	NG_SEND_DATA(error, dst->hook, m, meta);

	return (error);
}

/*
 * Shutdown node
 */
static int
ng_fwdswitch_rmnode(node_p node)
{
	const priv_p priv = node->private;

	ng_unname(node);
	ng_cutlinks(node);
	KASSERT(priv->numActiveOut == 0,
	    ("%s: numActiveOut=%d", __FUNCTION__, priv->numActiveOut));
	FREE(priv, M_NETGRAPH);
	node->private = NULL;
	ng_unref(node);
	return (0);
}

/*
 * Hook disconnection.
 */
static int
ng_fwdswitch_disconnect(hook_p hook)
{
	const priv_p priv = hook->node->private;
	int linkNum;

	/* Get link number */
	linkNum = LINK_NUM(hook);
	KASSERT(linkNum == NG_FWDSWITCH_ONE_LINKNUM
	    || linkNum == NG_FWDSWITCH_TWO_LINKNUM
	    || (linkNum >= 0 && linkNum < NG_FWDSWITCH_MAX_LINKS),
	    ("%s: linkNum=%d", __FUNCTION__, linkNum));

	/* Nuke the link */
	if (linkNum == NG_FWDSWITCH_ONE_LINKNUM)
		priv->one.hook = NULL;
	else if (linkNum == NG_FWDSWITCH_TWO_LINKNUM)
		priv->two.hook = NULL;
	else {
		priv->out[linkNum].hook = NULL;
		priv->conf.enabledLinks[linkNum] = 0;
		ng_fwdswitch_update_out(priv);
	}

	/* If no hooks left, go away */
	if (hook->node->numhooks == 0)
		ng_rmnode(hook->node);
	return (0);
}

/*
 * Update internal state after the addition or removal of a "out" link
 */
static void
ng_fwdswitch_update_out(priv_p priv)
{
	int linkNum;

	/* Update list of which "out" links are up */
	priv->numActiveOut = 0;
	for (linkNum = 0; linkNum < NG_FWDSWITCH_MAX_LINKS; linkNum++) {
		switch (priv->conf.failAlg) {
		case NG_FWDSWITCH_FAIL_MANUAL:
			if (priv->out[linkNum].hook != NULL
			    && priv->conf.enabledLinks[linkNum]) {
				priv->activeOut[priv->numActiveOut] = linkNum;
				priv->numActiveOut++;
			}
			break;
#ifdef INVARIANTS
		default:
			panic("%s: invalid failAlg", __FUNCTION__);
#endif
		}
	}

	/* Update transmit algorithm state */
	switch (priv->conf.xmitAlg) {
	case NG_FWDSWITCH_XMIT_ROUNDROBIN:
		if (priv->numActiveOut > 0)
			priv->nextOut %= priv->numActiveOut;
		break;
#ifdef INVARIANTS
	default:
		panic("%s: invalid xmitAlg", __FUNCTION__);
#endif
	}
}

static int
ng_fwdswitch_setfwdconfig(priv_p priv, const struct ng_fwdswitch_fwdconfig *fc0)
{
	struct ng_fwdswitch_fwdconfig *fc;
	int size,error;

	/* Save this configuration */
	size = NG_FWDSWITCH_FWDCONFIG_SIZE(fc0->fwd_table_len);
	MALLOC(fc, struct ng_fwdswitch_fwdconfig *, size, M_NETGRAPH, M_NOWAIT);
	if (fc == NULL)
		return (ENOMEM);
	bcopy(fc0, fc, size);

	/* Free old config, and use the new one */
	if (priv->fwdconfig != NULL)
		FREE(priv->fwdconfig, M_NETGRAPH);
	priv->fwdconfig = fc;

	/* Make the configuration effective */
	if ((error = ng_fwdswitch_buildfwdtable(priv, fc)) != 0) {
		return(error);
	}
	
	return (0);
}

static int
ng_fwdswitch_buildfwdtable(priv_p priv, const struct ng_fwdswitch_fwdconfig *fc)
{
	struct fwd_networks_priv *fwdtable;
	int size,i;

	/* Allocate a new forwarding table */
	priv->fwdtable_len = fc->fwd_table_len;
	size = sizeof(struct fwd_networks_priv)*priv->fwdtable_len;
	MALLOC(fwdtable, struct fwd_networks_priv *, size, M_NETGRAPH, M_NOWAIT);
	if (fwdtable == NULL)
		return(ENOMEM);

	/* Import the configuration into the table */
	for(i = 0; i < priv->fwdtable_len; i++ ) {
		fwdtable[i].ipnetwork.s_addr = fc->fwd_table[i].ipaddr.s_addr;
		fwdtable[i].netmask.s_addr = fc->fwd_table[i].netmask.s_addr;
		fwdtable[i].dsti = fc->fwd_table[i].dstlink;
		fwdtable[i].mtchcnt = fc->fwd_table[i].matchcount;
	}

	/* Be sure to stop the processing for a while, everything will goes
           to the default hook, but I'll fix this  */
	priv->fwdtable_len = 0;

	/* Free the old table, and use this one */
	if (priv->fwdtable != NULL)
		FREE(priv->fwdtable, M_NETGRAPH);
	priv->fwdtable = fwdtable;
	priv->fwd_deflink = fc->fwd_deflink;

	/* Let's start again the packet processing */
	priv->fwdtable_len = fc->fwd_table_len;

	return(0);
}
