File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / mpd / src / iface.c
Revision 1.1.1.5.2.1: download - view: text, annotated - select for diffs - revision graph
Wed Sep 27 11:08:00 2023 UTC (15 months ago) by misho
Branches: v5_9p16
Diff to: branchpoint 1.1.1.5: preferred, unified
Version 5.9p16


/*
 * iface.c
 *
 * Written by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 *
 * TCP MSSFIX code copyright (c) 2000 Ruslan Ermilov
 * TCP MSSFIX contributed by Sergey Korolew <dsATbittu.org.ru>
 *
 */

#include "ppp.h"
#include "iface.h"
#include "ipcp.h"
#include "auth.h"
#include "ngfunc.h"
#include "netgraph.h"
#include "util.h"

#include <sys/limits.h>
#include <sys/types.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/param.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <net/if_var.h>
#include <net/route.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/if_ether.h>
#include <netinet6/nd6.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_iface.h>
#ifdef USE_NG_BPF
#include <netgraph/ng_bpf.h>
#endif
#include <netgraph/ng_tee.h>
#include <netgraph/ng_ksocket.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#ifdef USE_NG_TCPMSS
#include <netgraph/ng_tcpmss.h>
#endif
#ifdef USE_NG_IPACCT
#include <netgraph/ng_ipacct.h>
#undef r_ip_p	/* XXX:DIRTY CONFLICT! */
#endif
#ifdef USE_NG_NETFLOW
#include <netgraph/netflow/ng_netflow.h>
#endif
#ifdef USE_NG_CAR
#include <netgraph/ng_car.h>
#endif

#ifdef USE_NG_BPF
#include <pcap.h>
#endif

#include <string.h>

#ifdef SIOCSIFDESCR
#include <time.h>
#endif

/*
 * DEFINITIONS
 */

/* Set menu options */

  enum {
    SET_IDLE,
    SET_SESSION,
    SET_ADDRS,
    SET_ROUTE,
    SET_MTU,
    SET_NAME,
#ifdef SIOCSIFDESCR
    SET_DESCR,
#endif
#ifdef SIOCAIFGROUP
    SET_GROUP,
#endif
    SET_UP_SCRIPT,
    SET_DOWN_SCRIPT,
    SET_ENABLE,
    SET_DISABLE
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int	IfaceNgIpInit(Bund b, int ready);
  static void	IfaceNgIpShutdown(Bund b);
  static int	IfaceNgIpv6Init(Bund b, int ready);
  static void	IfaceNgIpv6Shutdown(Bund b);

#ifdef USE_NG_NETFLOW
  static int	IfaceInitNetflow(Bund b, char *path, char *hook, char out, int v6);
  static int	IfaceSetupNetflow(Bund b, char in, char out, int v6);
  static void	IfaceShutdownNetflow(Bund b, char out, int v6);
#endif

#ifdef USE_NG_IPACCT
  static int	IfaceInitIpacct(Bund b, char *path, char *hook);
  static void	IfaceShutdownIpacct(Bund b);
#endif

#ifdef USE_NG_NAT
  static int	IfaceInitNAT(Bund b, char *path, char *hook);
  static int	IfaceSetupNAT(Bund b);
  static void	IfaceShutdownNAT(Bund b);
#endif

  static int	IfaceInitTee(Bund b, char *path, char *hook, int v6);
  static void	IfaceShutdownTee(Bund b, int v6);

#if defined(USE_NG_TCPMSS) || (!defined(USE_NG_TCPMSS) && defined(USE_NG_BPF))
  static int    IfaceInitMSS(Bund b, char *path, char *hook);
  static void	IfaceSetupMSS(Bund b, uint16_t maxMSS);
  static void	IfaceShutdownMSS(Bund b);
#endif

#ifdef USE_NG_BPF
  static int    IfaceInitLimits(Bund b, char *path, char *hook);
  static void	IfaceSetupLimits(Bund b);
  static void	IfaceShutdownLimits(Bund b);
#endif

  static int	IfaceSetCommand(Context ctx, int ac, const char *const av[], const void *arg);
  static void	IfaceSessionTimeout(void *arg);
  static void	IfaceIdleTimeout(void *arg);

  static void	IfaceCacheSend(Bund b);
  static void	IfaceCachePkt(Bund b, int proto, Mbuf pkt);
  static int	IfaceIsDemand(int proto, Mbuf pkt);

#ifdef USE_IPFW
  static int	IfaceAllocACL (struct acl_pool ***ap, int start, char * ifname, int number);
  static int	IfaceFindACL (struct acl_pool *ap, char * ifname, int number);
  static char *	IfaceParseACL (char * src, IfaceState iface);
  static char *	IfaceFixAclForDelete(char *r, char *buf, size_t len);
#endif

  static int	IfaceSetName(Bund b, const char * ifname);
#ifdef SIOCSIFDESCR
  static int	IfaceSetDescr(Bund b, const char * ifdescr);
  static void	IfaceFreeDescr(IfaceState iface);
#endif
#ifdef SIOCAIFGROUP
  static int	IfaceAddGroup(Bund b, const char * ifgroup);
  static int	IfaceDelGroup(Bund b, const char * ifgroup);
#endif
/*
 * GLOBAL VARIABLES
 */

  const struct cmdtab IfaceSetCmds[] = {
    { "addrs {self} {peer}",		"Set interface addresses",
	IfaceSetCommand, NULL, 2, (void *) SET_ADDRS },
    { "route {dest}[/{width}]",		"Add IP route",
	IfaceSetCommand, NULL, 2, (void *) SET_ROUTE },
    { "mtu {size} [override]",		"Set max allowed or override interface MTU",
	IfaceSetCommand, NULL, 2, (void *) SET_MTU },
    { "name [{name}]",			"Set interface name",
	IfaceSetCommand, NULL, 2, (void *) SET_NAME },
#ifdef SIOCSIFDESCR
    { "description [{descr}]",		"Set interface description",
	IfaceSetCommand, NULL, 2, (void *) SET_DESCR },
#endif
#ifdef SIOCAIFGROUP
    { "group [{group}]",		"Set interface group",
	IfaceSetCommand, NULL, 2, (void *) SET_GROUP },
#endif
    { "up-script [{progname}]",		"Interface up script",
	IfaceSetCommand, NULL, 2, (void *) SET_UP_SCRIPT },
    { "down-script [{progname}]",	"Interface down script",
	IfaceSetCommand, NULL, 2, (void *) SET_DOWN_SCRIPT },
#ifdef USE_NG_BPF
    { "idle {seconds}",			"Idle timeout",
	IfaceSetCommand, NULL, 2, (void *) SET_IDLE },
#endif
    { "session {seconds}",		"Session timeout",
	IfaceSetCommand, NULL, 2, (void *) SET_SESSION },
    { "enable [opt ...]",		"Enable option",
	IfaceSetCommand, NULL, 2, (void *) SET_ENABLE },
    { "disable [opt ...]",		"Disable option",
	IfaceSetCommand, NULL, 2, (void *) SET_DISABLE },
    { NULL, NULL, NULL, NULL, 0, NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static const struct confinfo	gConfList[] = {
    { 0,	IFACE_CONF_ONDEMAND,		"on-demand"	},
    { 0,	IFACE_CONF_PROXY,		"proxy-arp"	},
    { 0,	IFACE_CONF_KEEP_TIMEOUT,	"keep-timeout"	},
#ifdef USE_NG_TCPMSS
    { 0,	IFACE_CONF_TCPMSSFIX,           "tcpmssfix"	},
#endif
    { 0,	IFACE_CONF_TEE,			"tee"		},
#ifdef USE_NG_NAT
    { 0,	IFACE_CONF_NAT,			"nat"		},
#endif
#ifdef USE_NG_NETFLOW
    { 0,	IFACE_CONF_NETFLOW_IN,		"netflow-in"	},
    { 0,	IFACE_CONF_NETFLOW_OUT,		"netflow-out"	},
#ifdef NG_NETFLOW_CONF_ONCE
    { 0,	IFACE_CONF_NETFLOW_ONCE,	"netflow-once"	},
#endif
#endif
#ifdef USE_NG_IPACCT
    { 0,	IFACE_CONF_IPACCT,		"ipacct"	},
#endif
    { 0,	0,				NULL		},
  };

#ifdef USE_IPFW
  struct acl_pool * rule_pool = NULL; /* Pointer to the first element in the list of rules */
  struct acl_pool * pipe_pool = NULL; /* Pointer to the first element in the list of pipes */
  struct acl_pool * queue_pool = NULL; /* Pointer to the first element in the list of queues */
  struct acl_pool * table_pool = NULL; /* Pointer to the first element in the list of tables */
  int rule_pool_start = 10000; /* Initial number of ipfw rules pool */
  int pipe_pool_start = 10000; /* Initial number of ipfw dummynet pipe pool */
  int queue_pool_start = 10000; /* Initial number of ipfw dummynet queue pool */
  int table_pool_start = 32; /* Initial number of ipfw tables pool */
#endif

#ifdef USE_NG_BPF
  /* A BPF filter that matches TCP SYN packets */
  static const struct bpf_insn gTCPSYNProg[] __attribute__((used)) = {
/*00*/	BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 9),		/* A <- IP protocol */
/*01*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_TCP, 0, 6), /* !TCP => 8 */
/*02*/	BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 6),	/* A <- fragmentation offset */
/*03*/	BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, 0x1fff, 4, 0),	/* fragment => 8 */
/*04*/	BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, 0),		/* X <- header len */
/*05*/	BPF_STMT(BPF_LD+BPF_B+BPF_IND, 13),		/* A <- TCP flags */
/*06*/	BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, TH_SYN, 0, 1),	/* !TH_SYN => 8 */
/*07*/	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),		/* accept packet */
/*08*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject packet */
  };

  #define TCPSYN_PROG_LEN	(sizeof(gTCPSYNProg) / sizeof(*gTCPSYNProg))

  /* A BPF filter that matches nothing */
  static const struct bpf_insn gNoMatchProg[] = {
	BPF_STMT(BPF_RET+BPF_K, 0)
  };

  #define NOMATCH_PROG_LEN	(sizeof(gNoMatchProg) / sizeof(*gNoMatchProg))

  /* A BPF filter that matches everything */
  static const struct bpf_insn gMatchProg[] = {
	BPF_STMT(BPF_RET+BPF_K, (u_int)-1)
  };

  #define MATCH_PROG_LEN	(sizeof(gMatchProg) / sizeof(*gMatchProg))
#endif /* USE_NG_BPF */

#define IN6MASK128	{{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
			    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}
static const struct in6_addr in6mask128 = IN6MASK128;

#ifdef SIOCSIFDESCR
void
IfaceFreeDescr(IfaceState iface)
{
  if (iface->ifdescr != NULL)
	Freee(iface->ifdescr);
  if (iface->conf.ifdescr != NULL)
	Freee(iface->conf.ifdescr);
  iface->ifdescr = iface->conf.ifdescr = NULL;
}
#endif

/*
 * IfaceInit()
 */

void
IfaceInit(Bund b)
{
  IfaceState	const iface = &b->iface;

  /* Default configuration */
  iface->mtu = NG_IFACE_MTU_DEFAULT;
  iface->max_mtu = NG_IFACE_MTU_DEFAULT;
  iface->mtu_override = 0;
#ifdef SIOCSIFDESCR
  iface->ifdescr = NULL;
  iface->conf.ifdescr = NULL;
#endif
  Disable(&iface->options, IFACE_CONF_ONDEMAND);
  Disable(&iface->options, IFACE_CONF_PROXY);
  Disable(&iface->options, IFACE_CONF_KEEP_TIMEOUT);
  Disable(&iface->options, IFACE_CONF_TCPMSSFIX);
#ifdef	USE_NG_NAT
  NatInit(b);
#endif
#ifdef USE_NG_BPF
  SLIST_INIT(&iface->ss[0]);
  SLIST_INIT(&iface->ss[1]);
#endif
}

/*
 * IfaceInst()
 */

void
IfaceInst(Bund b, Bund bt)
{
    IfaceState	const iface = &b->iface;

    memcpy(iface, &bt->iface, sizeof(*iface));

#ifdef SIOCSIFDESCR
    /* Copy interface description from template config to current */
    if (bt->iface.conf.ifdescr)
	iface->conf.ifdescr = Mstrdup(MB_IFACE, bt->iface.conf.ifdescr);
    if (bt->iface.ifdescr)
	iface->ifdescr = Mstrdup(MB_IFACE, bt->iface.ifdescr);
#endif
    /* Copy interface name from template config to current */
    if (bt->iface.conf.ifname[0] != 0 && b->tmpl == 0) {
        snprintf(iface->conf.ifname, sizeof(iface->conf.ifname), "%s%d",
             bt->iface.conf.ifname, b->id);
        Log(LG_IFACE2, ("[%s] IFACE: Set conf.ifname to ", iface->conf.ifname));
    }
}

/*
 * IfaceDestroy()
 */

void
IfaceDestroy(Bund b)
{
#ifdef SIOCSIFDESCR
    IfaceState	const iface = &b->iface;

    IfaceFreeDescr(iface);
#endif
}

/*
 * IfaceOpen()
 *
 * Open the interface layer
 */

void
IfaceOpen(Bund b)
{
    IfaceState	const iface = &b->iface;

    Log(LG_IFACE, ("[%s] IFACE: Open event", b->name));

    /* Open is useless without on-demand. */
    if (!Enabled(&iface->options, IFACE_CONF_ONDEMAND)) {
	Log(LG_ERR, ("[%s] 'open iface' is useless without on-demand enabled", b->name));
	return;
    }

    /* If interface is already open do nothing */
    if (iface->open)
	return;
    iface->open = TRUE;

    /* If on-demand, bring up system interface immediately and start
     listening for outgoing packets. The next outgoing packet will
     cause us to open the lower layer(s) */
    BundNcpsJoin(b, NCP_NONE);
}

/*
 * IfaceClose()
 *
 * Close the interface layer
 */

void
IfaceClose(Bund b)
{
    IfaceState	const iface = &b->iface;

    Log(LG_IFACE, ("[%s] IFACE: Close event", b->name));

    /* If interface is already closed do nothing */
    if (!iface->open)
	return;
    iface->open = FALSE;

    /* If there was on-demand, tell that it is not needed anymore */
    BundNcpsLeave(b, NCP_NONE);
}

/*
 * IfaceOpenCmd()
 *
 * Open the interface layer
 */

int
IfaceOpenCmd(Context ctx)
{
    if (ctx->bund->tmpl)
	Error("impossible to open template");
    IfaceOpen(ctx->bund);
    return (0);
}

/*
 * IfaceCloseCmd()
 *
 * Close the interface layer
 */

int
IfaceCloseCmd(Context ctx)
{
    if (ctx->bund->tmpl)
	Error("impossible to close template");
    IfaceClose(ctx->bund);
    return (0);
}

/*
 * IfaceUp()
 *
 * Our underlying PPP bundle is ready for traffic.
 * We may signal that the interface is in DoD with the IFF_LINK0 flag.
 */

void
IfaceUp(Bund b, int ready)
{
  IfaceState	const iface = &b->iface;
  int		session_timeout = 0, idle_timeout = 0;
#ifdef USE_IPFW
  struct acl	*acls, *acl;
  char			*buf;
  struct acl_pool 	**poollast;
  int 			poollaststart;
  int		prev_number;
  int		prev_real_number;
#endif

  Log(LG_IFACE, ("[%s] IFACE: Up event", b->name));
  iface->last_up = time(NULL);

  if (ready) {

    /* Start Session timer */
    if (b->params.session_timeout > 0) {
	if (Enabled(&iface->options, IFACE_CONF_KEEP_TIMEOUT)) {
	    session_timeout = b->params.session_timeout - \
		(iface->last_up - b->last_up);
	    Log(LG_IFACE2, ("[%s] IFACE: keep session-timeout at: %d seconds",
		b->name, session_timeout));
	} else {
	    session_timeout = b->params.session_timeout;
	}
    } else if (iface->session_timeout > 0) {
	session_timeout = iface->session_timeout;
    }

    if (session_timeout > 0) {
	Log(LG_IFACE2, ("[%s] IFACE: session-timeout: %d seconds", 
    	    b->name, session_timeout));
	if (session_timeout > INT_MAX / 1100) {
	    session_timeout = INT_MAX / 1100;
	    Log(LG_ERR, ("[%s] IFACE: session-timeout limited to %d seconds", 
		b->name, session_timeout));
	}
	TimerInit(&iface->sessionTimer, "IfaceSession",
    	    session_timeout * SECONDS, IfaceSessionTimeout, b);
	TimerStart(&iface->sessionTimer);
    }

    /* Start idle timer */
    if (b->params.idle_timeout > 0) {
	idle_timeout = b->params.idle_timeout;
    } else if (iface->idle_timeout > 0) {
	idle_timeout = iface->idle_timeout;
    }
    
    if (idle_timeout > 0) {
	Log(LG_IFACE2, ("[%s] IFACE: idle-timeout: %d seconds", 
    	    b->name, idle_timeout));
	if (idle_timeout > INT_MAX / 1100 * IFACE_IDLE_SPLIT) {
	    idle_timeout = INT_MAX / 1100 * IFACE_IDLE_SPLIT;
	    Log(LG_ERR, ("[%s] IFACE: idle-timeout limited to %d seconds", 
    		b->name, idle_timeout));
	}
	TimerInit(&iface->idleTimer, "IfaceIdle",
    	    idle_timeout * SECONDS / IFACE_IDLE_SPLIT, IfaceIdleTimeout, b);
	TimerStart(&iface->idleTimer);
	iface->traffic[1] = TRUE;
	iface->traffic[0] = FALSE;

	/* Reset statistics */
	memset(&iface->idleStats, 0, sizeof(iface->idleStats));
    }

    /* Update interface name and description */
    if (b->params.ifname[0] != 0) {
       if (IfaceSetName(b, b->params.ifname) != -1)
	    Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Rename interface %s to %s",
		b->name, iface->ngname, b->params.ifname));
    } else if (iface->conf.ifname[0] != 0) {
       if (IfaceSetName(b, iface->conf.ifname) != -1)
	    Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Rename interface %s to %s",
		b->name, iface->ngname, iface->conf.ifname));
    }
#ifdef SIOCSIFDESCR
    if (b->params.ifdescr != NULL) {
       if (IfaceSetDescr(b, b->params.ifdescr) != -1) {
	    Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Add description \"%s\"",
		b->name, iface->ifdescr));
	}
    } else if (iface->conf.ifdescr != NULL) {
       if (IfaceSetDescr(b, iface->conf.ifdescr) != -1) {
	    Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Add description \"%s\"",
		b->name, iface->ifdescr));
	}
    }
#endif
#ifdef SIOCAIFGROUP
    if (iface->conf.ifgroup[0] != 0) {
       if (IfaceAddGroup(b, iface->conf.ifgroup) != -1)
	    Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Add group %s to %s",
		b->name, iface->conf.ifgroup, iface->ngname));
    }
    if (b->params.ifgroup[0] != 0) {
       if (IfaceAddGroup(b, b->params.ifgroup) != -1)
	    Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Add group %s to %s",
		b->name, b->params.ifgroup, iface->ngname));
    }
#endif
#ifdef USE_IPFW
  /* Allocate ACLs */
  acls = b->params.acl_pipe;
  poollast = &pipe_pool;
  poollaststart = pipe_pool_start;
  while (acls != NULL) {
    acls->real_number = IfaceAllocACL(&poollast, poollaststart, iface->ifname, acls->number);
    poollaststart = acls->real_number;
    acls = acls->next;
  };
  acls = b->params.acl_queue;
  poollast = &queue_pool;
  poollaststart = queue_pool_start;
  while (acls != NULL) {
    acls->real_number = IfaceAllocACL(&poollast, poollaststart, iface->ifname, acls->number);
    poollaststart = acls->real_number;
    acls = acls->next;
  };
  prev_number = -1;
  prev_real_number = -1;
  acls = b->params.acl_table;
  poollast = &table_pool;
  poollaststart = table_pool_start;
  while (acls != NULL) {
    if (acls->real_number == 0) {
	if (acls->number == prev_number) { /* ACL list is presorted so we need not allocate if equal */
	    acls->real_number = prev_real_number;
	} else {
	    acls->real_number = IfaceAllocACL(&poollast, poollaststart, iface->ifname, acls->number);
	    poollaststart = acls->real_number;
	    prev_number = acls->number;
	    prev_real_number = acls->real_number;
	}
    }
    acls = acls->next;
  };
  acls = b->params.acl_rule;
  poollast = &rule_pool;
  poollaststart = rule_pool_start;
  while (acls != NULL) {
    acls->real_number = IfaceAllocACL(&poollast, poollaststart, iface->ifname, acls->number);
    poollaststart = acls->real_number;
    acls = acls->next;
  };

  /* Set ACLs */
  acls = b->params.acl_pipe;
  while (acls != NULL) {
    ExecCmd(LG_IFACE2, b->name, "%s pipe %d config %s", PATH_IPFW, acls->real_number, acls->rule);
    acls = acls->next;
  }
  acls = b->params.acl_queue;
  while (acls != NULL) {
    buf = IfaceParseACL(acls->rule, iface);
    ExecCmd(LG_IFACE2, b->name, "%s queue %d config %s", PATH_IPFW, acls->real_number, buf);
    Freee(buf);
    acls = acls->next;
  }
  acls = b->params.acl_table;
  while (acls != NULL) {
    /* allow both %aX and `peer_addr` macros */
    buf = IfaceParseACL(acls->rule, iface);
    acl = Mdup2(MB_IPFW, acls, sizeof(struct acl), sizeof(struct acl) + strlen(buf));
    strcpy(acl->rule, buf);
    Freee(buf);
    acl->next = iface->tables;
    iface->tables = acl;
    if (strncmp(acl->rule, "peer_addr", 9) == 0) {
	char hisaddr[20];
	ExecCmd(LG_IFACE2, b->name, "%s -q table %d add %s",
	    PATH_IPFW, acl->real_number,
	    u_addrtoa(&iface->peer_addr, hisaddr, sizeof(hisaddr)));
    } else {
	ExecCmd(LG_IFACE2, b->name, "%s -q table %d add %s", PATH_IPFW, acl->real_number, acl->rule);
    }
    acls = acls->next;
  };
  acls = b->params.acl_rule;
  while (acls != NULL) {
    buf = IfaceParseACL(acls->rule, iface);
    ExecCmd(LG_IFACE2, b->name, "%s add %d %s via %s", PATH_IPFW, acls->real_number, buf, iface->ifname);
    Freee(buf);
    acls = acls->next;
  };
#endif /* USE_IPFW */

  };

  /* Bring up system interface */
  IfaceChangeFlags(b, 0, IFF_UP | (ready?0:IFF_LINK0));

  /* Send any cached packets */
  IfaceCacheSend(b);

}

/*
 * IfaceDown()
 *
 * Our packet transport mechanism is no longer ready for traffic.
 */

void
IfaceDown(Bund b)
{
  IfaceState	const iface = &b->iface;
#ifdef USE_IPFW
  struct acl_pool	**rp, *rp1;
  char		cb[LINE_MAX - sizeof(PATH_IPFW) - 14];
  struct acl    *acl, *aclnext;
#endif

  Log(LG_IFACE, ("[%s] IFACE: Down event", b->name));

  /* Bring down system interface */
  IfaceChangeFlags(b, IFF_UP | IFF_LINK0, 0);

  TimerStop(&iface->idleTimer);
  TimerStop(&iface->sessionTimer);

#ifdef USE_IPFW
  /* Remove rule ACLs */
  rp = &rule_pool;
  cb[0]=0;
  while (*rp != NULL) {
    if (strncmp((*rp)->ifname, iface->ifname, IFNAMSIZ) == 0) {
      sprintf(cb+strlen(cb), " %d", (*rp)->real_number);
      rp1 = *rp;
      *rp = (*rp)->next;
      Freee(rp1);
    } else {
      rp = &((*rp)->next);
    };
  };
  if (cb[0]!=0)
    ExecCmdNosh(LG_IFACE2, b->name, "%s delete%s",
      PATH_IPFW, cb);

  /* Remove table ACLs */
  rp = &table_pool;
  while (*rp != NULL) {
    if (strncmp((*rp)->ifname, iface->ifname, IFNAMSIZ) == 0) {
      rp1 = *rp;
      *rp = (*rp)->next;
      Freee(rp1);
    } else {
      rp = &((*rp)->next);
    };
  };
  acl = iface->tables;
  while (acl != NULL) {
    if (strncmp(acl->rule, "peer_addr", 9) == 0) {
      char hisaddr[20];
      ExecCmd(LG_IFACE2, b->name, "%s -q table %d delete %s",
        PATH_IPFW, acl->real_number,
        u_addrtoa(&iface->peer_addr, hisaddr, sizeof(hisaddr)));
    } else {
      char buf[ACL_LEN];
      ExecCmd(LG_IFACE2, b->name, "%s -q table %d delete %s",
        PATH_IPFW, acl->real_number,
        IfaceFixAclForDelete(acl->rule, buf, sizeof(buf)));
    }
    aclnext = acl->next;
    Freee(acl);
    acl = aclnext;
  };
  iface->tables = NULL;

  /* Remove queue ACLs */
  rp = &queue_pool;
  cb[0]=0;
  while (*rp != NULL) {
    if (strncmp((*rp)->ifname, iface->ifname, IFNAMSIZ) == 0) {
      sprintf(cb+strlen(cb), " %d", (*rp)->real_number);
      rp1 = *rp;
      *rp = (*rp)->next;
      Freee(rp1);
    } else {
      rp = &((*rp)->next);
    };
  };
  if (cb[0]!=0)
    ExecCmdNosh(LG_IFACE2, b->name, "%s queue delete%s",
      PATH_IPFW, cb);

  /* Remove pipe ACLs */
  rp = &pipe_pool;
  cb[0]=0;
  while (*rp != NULL) {
    if (strncmp((*rp)->ifname, iface->ifname, IFNAMSIZ) == 0) {
      sprintf(cb+strlen(cb), " %d", (*rp)->real_number);
      rp1 = *rp;
      *rp = (*rp)->next;
      Freee(rp1);
    } else {
      rp = &((*rp)->next);
    };
  };
  if (cb[0]!=0)
    ExecCmdNosh(LG_IFACE2, b->name, "%s pipe delete%s",
      PATH_IPFW, cb);
#endif /* USE_IPFW */

    /* Clearing self and peer addresses */
    u_rangeclear(&iface->self_addr);
    u_addrclear(&iface->peer_addr);
    u_addrclear(&iface->self_ipv6_addr);
    u_addrclear(&iface->peer_ipv6_addr);

    /* Revert interface name and description */

    if (strcmp(iface->ngname, iface->ifname) != 0) {
	if (iface->conf.ifname[0] != 0) {
	    /* Restore to config defined */
	    if (IfaceSetName(b, iface->conf.ifname) != -1)
		Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Rename interface %s to %s",
		    b->name, iface->ifname, iface->conf.ifname));
	} else {
	    /* Restore to original interface name */
	    if (IfaceSetName(b, iface->ngname) != -1)
		Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Rename interface %s to %s",
		    b->name, iface->ifname, iface->ngname));
	}
    }
#ifdef SIOCSIFDESCR
    if ((iface->ifdescr != NULL)
    && (IfaceSetDescr(b, iface->conf.ifdescr) != -1)) {
	if (iface->conf.ifdescr != NULL) {
		Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Set description \"%s\"",
		    b->name, iface->ifdescr));
	} else {
		Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Clear description",
		    b->name));
	}
    }
#endif
#ifdef SIOCAIFGROUP
    if (b->params.ifgroup[0] != 0) {
       if (IfaceDelGroup(b, b->params.ifgroup) != -1)
	    Log(LG_BUND|LG_IFACE, ("[%s] IFACE: Remove group %s from %s",
		b->name, b->params.ifgroup, iface->ngname));
    }
#endif
}

/*
 * IfaceListenInput()
 *
 * A packet was received on our demand snooping hook. Stimulate a connection.
 */

void
IfaceListenInput(Bund b, int proto, Mbuf pkt)
{
    IfaceState	const iface = &b->iface;
    int		const isDemand = IfaceIsDemand(proto, pkt);

    /* Does this count as demand traffic? */
    if (iface->open && isDemand) {
	iface->traffic[0] = TRUE;
        Log(LG_IFACE, ("[%s] IFACE: Outgoing %s packet demands connection", b->name,
	    (proto==PROTO_IP)?"IP":"IPv6"));
	RecordLinkUpDownReason(b, NULL, 1, STR_DEMAND, NULL);
        BundOpen(b);
        IfaceCachePkt(b, proto, pkt);
    } else {
	mbfree(pkt);
    }
}

#ifdef USE_IPFW
/*
 * IfaceAllocACL ()
 *
 * Allocates unique real number for new ACL and adds it to the list of used ones.
 */

static int
IfaceAllocACL(struct acl_pool ***ap, int start, char *ifname, int number)
{
    int	i;
    struct acl_pool **rp,*rp1;

    rp1 = Malloc(MB_IPFW, sizeof(struct acl_pool));
    strlcpy(rp1->ifname, ifname, sizeof(rp1->ifname));
    rp1->acl_number = number;

    rp = *ap;
    i = start;
    while (*rp != NULL && (*rp)->real_number <= i) {
        i = (*rp)->real_number+1;
        rp = &((*rp)->next);
    };
    if (*rp == NULL) {
        rp1->next = NULL;
    } else {
        rp1->next = *rp;
    };
    rp1->real_number = i;
    *rp = rp1;
    *ap = rp;
    return(i);
}

/*
 * IfaceFindACL ()
 *
 * Finds ACL in the list and gets its real number.
 */

static int
IfaceFindACL (struct acl_pool *ap, char * ifname, int number)
{
    int	i;
    struct acl_pool *rp;

    rp=ap;
    i=-1;
    while (rp != NULL) {
	if ((rp->acl_number == number) && (strncmp(rp->ifname,ifname,IFNAMSIZ) == 0)) {
    	    i = rp->real_number;
	    break;
	};
        rp = rp->next;
    };
    return(i);
}

/*
 * IfaceParseACL ()
 *
 * Parses ACL and replaces %r, %p and %q macroses 
 * by the real numbers of rules, queues and pipes.
 *
 * Also replaces %a1 and a2 with the remote(peer)
 * or local(self) IP address respectively.
 */

static char *
IfaceParseACL (char * src, IfaceState iface)
{
    char *buf,*buf1;
    char *begin,*param,*end;
    char t;
    int num,real_number;
    struct acl_pool *ap;
    char hisaddr[20];
    int ipmode = 0; /* 0 - normal macro, 1 - IP address macro */
    
    buf = Malloc(MB_IPFW, ACL_LEN);
    buf1 = Malloc(MB_IPFW, ACL_LEN);

    strlcpy(buf, src, ACL_LEN);
    do {
        end = buf;
	begin = strsep(&end, "%");
	param = strsep(&end, " ");
	if (param != NULL) {
	    if (sscanf(param,"%c%d", &t, &num) == 2) {
		switch (t) {
		    case 'r':
			ap = rule_pool;
			break;
		    case 'p':
			ap = pipe_pool;
			break;
		    case 'q':
			ap = queue_pool;
			break;
		    case 't':
			ap = table_pool;
			break;
		    case 'a':
			ipmode = 1;
			if (num == 1)
			   u_addrtoa(&iface->peer_addr, hisaddr, sizeof(hisaddr));
			else if (num == 2)
			   u_rangetoa(&iface->self_addr, hisaddr, sizeof(hisaddr));
			else
			   ipmode = 0;
			ap = NULL;
                        break;
		    default:
			ap = NULL;
		};
		if (ipmode)
		{
		    if (end != NULL)
			snprintf(buf1, ACL_LEN, "%s%s %s", begin, hisaddr, end);
		    else
			snprintf(buf1, ACL_LEN, "%s%s", begin, hisaddr);
		    ipmode = 0;
		}
		else
		{
		    real_number = IfaceFindACL(ap, iface->ifname, num);
		    if (end != NULL)
			snprintf(buf1, ACL_LEN, "%s%d %s", begin, real_number, end);
		    else
		 	snprintf(buf1, ACL_LEN, "%s%d", begin, real_number);
		}
		strlcpy(buf, buf1, ACL_LEN);
	    };
	};
    } while (end != NULL);
    Freee(buf1);
    return(buf);
}

/*
 * IfaceFixAclForDelete()
 *
 * Removes values from ipfw 'table-key value [...]' expression r, if any.
 * Returns buf pointer for modified expression or original r pointer
 * if no modifications were performed when no values were found or
 * buf found too short.
 *
 * len is size of buf. Strings are zero-terminated.
 * r and buf must point to non-overlapping memory areas.
 */

static char*
IfaceFixAclForDelete(char *r, char *buf, size_t len)
{
  static const char sep[] = " \t";
  char *limit, *orig, *s;
  int  i, state = 0; 

/*
 * Possible state values:
 *
 * -1: skip value (otherwise copy);
 *  0: first iteration, do copy;
 *  1: not first iteration, do copy.
*/

  orig = r;
  s = buf;
  limit = buf + len;

  for (r += strspn(r, sep);		/* Skip leading spaces. 	    */
       *r;				/* Check for end of string. 	    */
       r += i, r += strspn(r, sep))	/* Advance and skip spaces again.   */
  {
    i = strcspn(r, sep);		/* Find separator or end of string. */
    if (state == 0 && r[i] == '\0')	/* No separators in the rule?	    */
      return r;
    if (state < 0) {			/* Skip value.			    */
      state = 1;
      continue;
    }
    if (limit - s < i + 1 + state)	/* Check space.			    */
      return orig;
    if (state != 0)			/* Insert separator.		    */
      *s++ = ' ';
    memcpy(s, r, i);			/* Copy IP address from the rule.   */
    s += i;
    state = -1;
  }
  *s = '\0';

  return buf;
}
#endif /* USE_IPFW */

/*
 * IfaceIpIfaceUp()
 *
 * Bring up the IP interface. The "ready" flag means that
 * IPCP is also up and we can deliver packets immediately.
 */

int
IfaceIpIfaceUp(Bund b, int ready)
{
    IfaceState		const iface = &b->iface;
    struct sockaddr_dl	hwa;
    char		hisaddr[20];
    IfaceRoute		r;
    u_char		*ether;

    if (ready && !iface->conf.self_addr_force) {
	in_addrtou_range(&b->ipcp.want_addr, 32, &iface->self_addr);
    } else {
	u_rangecopy(&iface->conf.self_addr, &iface->self_addr);
    }
    if (ready && !iface->conf.peer_addr_force) {
	in_addrtou_addr(&b->ipcp.peer_addr, &iface->peer_addr);
    } else {
	u_addrcopy(&iface->conf.peer_addr, &iface->peer_addr);
    }

    if (IfaceNgIpInit(b, ready)) {
	Log(LG_ERR, ("[%s] IFACE: IfaceNgIpInit() error, closing IPCP", b->name));
	FsmFailure(&b->ipcp.fsm, FAIL_NEGOT_FAILURE);
	return (-1);
    };

    /* Set addresses */
    if (!u_rangeempty(&iface->self_addr) &&
	    IfaceChangeAddr(b, 1, &iface->self_addr, &iface->peer_addr)) {
	Log(LG_ERR, ("[%s] IFACE: IfaceChangeAddr() error, closing IPCP", b->name));
	FsmFailure(&b->ipcp.fsm, FAIL_NEGOT_FAILURE);
	return (-1);
    };

    /* Proxy ARP for peer if desired and peer's address is known */
    u_addrclear(&iface->proxy_addr);
    if (Enabled(&iface->options, IFACE_CONF_PROXY)) {
	u_addrtoa(&iface->peer_addr,hisaddr,sizeof(hisaddr));
	if (u_addrempty(&iface->peer_addr)) {
    	    Log(LG_IFACE, ("[%s] IFACE: Can't proxy arp for %s",
		b->name, hisaddr));
	} else if (GetEther(&iface->peer_addr, &hwa) < 0) {
    	    Log(LG_IFACE, ("[%s] IFACE: No interface to proxy arp on for %s",
		b->name, hisaddr));
	} else {
    	    ether = (u_char *) LLADDR(&hwa);
    	    if (ExecCmdNosh(LG_IFACE2, b->name, 
		"%s -S %s %x:%x:%x:%x:%x:%x pub",
		PATH_ARP, hisaddr,
		ether[0], ether[1], ether[2],
		ether[3], ether[4], ether[5]) == 0)
		iface->proxy_addr = iface->peer_addr;
	}
    }
  
    /* Add static routes */
    SLIST_FOREACH(r, &iface->routes, next) {
	if (u_rangefamily(&r->dest)==AF_INET) {
	    r->ok = (IfaceSetRoute(b, RTM_ADD, &r->dest, &iface->peer_addr) == 0);
	}
    }
    /* Add dynamic routes */
    SLIST_FOREACH(r, &b->params.routes, next) {
	if (u_rangefamily(&r->dest)==AF_INET) {
	    r->ok = (IfaceSetRoute(b, RTM_ADD, &r->dest, &iface->peer_addr) == 0);
	}
    }

#ifdef USE_NG_NAT
    /* Set NAT IP */
    if (iface->nat_up)
	IfaceSetupNAT(b);
#endif

    /* Call "up" script */
    if (*iface->up_script) {
	char	selfbuf[40],peerbuf[40];
	char	ns1buf[21], ns2buf[21];
	int	res;

	if(b->ipcp.want_dns[0].s_addr != 0)
    	    snprintf(ns1buf, sizeof(ns1buf), "dns1 %s", inet_ntoa(b->ipcp.want_dns[0]));
	else
    	    ns1buf[0] = '\0';
	if(b->ipcp.want_dns[1].s_addr != 0)
    	    snprintf(ns2buf, sizeof(ns2buf), "dns2 %s", inet_ntoa(b->ipcp.want_dns[1]));
	else
    	    ns2buf[0] = '\0';

	res = ExecCmd(LG_IFACE2, b->name, "%s %s inet %s %s '%s' '%s' '%s' '%s' '%s'",
	    iface->up_script, iface->ifname,
	    u_rangetoa(&iface->self_addr,selfbuf, sizeof(selfbuf)),
    	    u_addrtoa(&iface->peer_addr, peerbuf, sizeof(peerbuf)), 
    	    *b->params.authname ? b->params.authname : "-", 
    	    ns1buf, ns2buf, *b->params.peeraddr ? b->params.peeraddr : "-",
#ifdef USE_RADIUS
    	    b->params.filter_id ? b->params.filter_id :
#endif
    	    "-");
	if (res != 0) {
	    FsmFailure(&b->ipcp.fsm, FAIL_NEGOT_FAILURE);
	    return (-1);
	}
    }
    return (0);
}

/*
 * IfaceIpIfaceDown()
 *
 * Bring down the IP interface. This implies we're no longer ready.
 */

void
IfaceIpIfaceDown(Bund b)
{
    IfaceState	const iface = &b->iface;
    IfaceRoute	r;
    char	buf[48];

    /* Call "down" script */
    if (*iface->down_script) {
	char	selfbuf[40],peerbuf[40];

	ExecCmd(LG_IFACE2, b->name, "%s %s inet %s %s '%s' '%s' '%s'",
    	    iface->down_script, iface->ifname,
    	    u_rangetoa(&iface->self_addr,selfbuf, sizeof(selfbuf)),
    	    u_addrtoa(&iface->peer_addr, peerbuf, sizeof(peerbuf)), 
    	    *b->params.authname ? b->params.authname : "-",
    	    *b->params.peeraddr ? b->params.peeraddr : "-",
#ifdef USE_RADIUS
    	    b->params.filter_id ? b->params.filter_id :
#endif
    	    "-");
    }

    /* Delete dynamic routes */
    SLIST_FOREACH(r, &b->params.routes, next) {
	if (u_rangefamily(&r->dest)==AF_INET) {
	    if (!r->ok)
		continue;
	    IfaceSetRoute(b, RTM_DELETE, &r->dest, &iface->peer_addr);
	    r->ok = 0;
	}
    }
    /* Delete static routes */
    SLIST_FOREACH(r, &iface->routes, next) {
	if (u_rangefamily(&r->dest)==AF_INET) {
	    if (!r->ok)
		continue;
	    IfaceSetRoute(b, RTM_DELETE, &r->dest, &iface->peer_addr);
	    r->ok = 0;
	}
    }

    /* Delete any proxy arp entry */
    if (!u_addrempty(&iface->proxy_addr))
	ExecCmdNosh(LG_IFACE2, b->name, "%s -d %s", PATH_ARP, u_addrtoa(&iface->proxy_addr, buf, sizeof(buf)));
    u_addrclear(&iface->proxy_addr);

    /* Remove address from interface */
    if (!u_rangeempty(&iface->self_addr))
	IfaceChangeAddr(b, 0, &iface->self_addr, &iface->peer_addr);
    
    IfaceNgIpShutdown(b);
}

/*
 * IfaceIpv6IfaceUp()
 *
 * Bring up the IPv6 interface. The "ready" flag means that
 * IPv6CP is also up and we can deliver packets immediately.
 */

int
IfaceIpv6IfaceUp(Bund b, int ready)
{
    IfaceState		const iface = &b->iface;
    IfaceRoute		r;

    if (ready && !iface->conf.self_ipv6_addr_force) {
        iface->self_ipv6_addr.family = AF_INET6;
        iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[0] = 0x80fe;  /* Network byte order */
        iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[1] = 0x0000;
        iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[2] = 0x0000;
        iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[3] = 0x0000;
        bcopy(b->ipv6cp.myintid, &iface->self_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[4], sizeof(b->ipv6cp.myintid));
        bcopy(&iface->self_ipv6_addr.u.ip6, &b->ipv6cp.want_addr, sizeof(struct in6_addr));
    } else {
	u_addrcopy(&iface->conf.self_ipv6_addr, &iface->self_ipv6_addr);
    }
    if (ready && !iface->conf.peer_ipv6_addr_force) {
        iface->peer_ipv6_addr.family = AF_INET6;
        iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[0] = 0x80fe;  /* Network byte order */
        iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[1] = 0x0000;
        iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[2] = 0x0000;
        iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[3] = 0x0000;
        bcopy(b->ipv6cp.hisintid, &iface->peer_ipv6_addr.u.ip6.__u6_addr.__u6_addr16[4], sizeof(b->ipv6cp.hisintid));
        bcopy(&iface->peer_ipv6_addr.u.ip6, &b->ipv6cp.peer_addr, sizeof(struct in6_addr));
    } else {
	u_addrcopy(&iface->conf.peer_ipv6_addr, &iface->peer_ipv6_addr);
    }

    if (IfaceNgIpv6Init(b, ready)) {
        Log(LG_ERR, ("[%s] IFACE: IfaceNgIpv6Init() failed, closing IPv6CP", b->name));
        FsmFailure(&b->ipv6cp.fsm, FAIL_NEGOT_FAILURE);
        return (-1);
    };
  
    /* Set addresses */
    if (!u_addrempty(&iface->self_ipv6_addr)) {
	struct u_range	rng;
	rng.addr = iface->self_ipv6_addr;
	rng.width = 64;
	if (IfaceChangeAddr(b, 1, &rng, &iface->peer_ipv6_addr)) {
	    Log(LG_ERR, ("[%s] IFACE: IfaceChangeAddr() failed, closing IPv6CP", b->name));
	    FsmFailure(&b->ipv6cp.fsm, FAIL_NEGOT_FAILURE);
    	    return (-1);
	}
    };
  
    /* Add static routes */
    SLIST_FOREACH(r, &iface->routes, next) {
	if (u_rangefamily(&r->dest)==AF_INET6) {
	    r->ok = (IfaceSetRoute(b, RTM_ADD, &r->dest, &iface->peer_ipv6_addr) == 0);
	}
    }
    /* Add dynamic routes */
    SLIST_FOREACH(r, &b->params.routes, next) {
	if (u_rangefamily(&r->dest)==AF_INET6) {
	    r->ok = (IfaceSetRoute(b, RTM_ADD, &r->dest, &iface->peer_ipv6_addr) == 0);
	}
    }

    /* Call "up" script */
    if (*iface->up_script) {
	char	selfbuf[48],peerbuf[48];
	int	res;

	res = ExecCmd(LG_IFACE2, b->name, "%s %s inet6 %s%%%s %s%%%s '%s' '%s' '%s'",
    	    iface->up_script, iface->ifname, 
    	    u_addrtoa(&iface->self_ipv6_addr, selfbuf, sizeof(selfbuf)), iface->ifname,
    	    u_addrtoa(&iface->peer_ipv6_addr, peerbuf, sizeof(peerbuf)), iface->ifname, 
    	    *b->params.authname ? b->params.authname : "-",
    	    *b->params.peeraddr ? b->params.peeraddr : "-",
#ifdef USE_RADIUS
    	    b->params.filter_id ? b->params.filter_id :
#endif
    	    "-");
	if (res != 0) {
	    FsmFailure(&b->ipv6cp.fsm, FAIL_NEGOT_FAILURE);
	    return (-1);
	}
    }
    return (0);

}

/*
 * IfaceIpv6IfaceDown()
 *
 * Bring down the IPv6 interface. This implies we're no longer ready.
 */

void
IfaceIpv6IfaceDown(Bund b)
{
    IfaceState		const iface = &b->iface;
    IfaceRoute		r;
    struct u_range	rng;

    /* Call "down" script */
    if (*iface->down_script) {
	char	selfbuf[48],peerbuf[48];

	ExecCmd(LG_IFACE2, b->name, "%s %s inet6 %s%%%s %s%%%s '%s' '%s' '%s'",
    	    iface->down_script, iface->ifname, 
    	    u_addrtoa(&iface->self_ipv6_addr, selfbuf, sizeof(selfbuf)), iface->ifname,
    	    u_addrtoa(&iface->peer_ipv6_addr, peerbuf, sizeof(peerbuf)), iface->ifname, 
    	    *b->params.authname ? b->params.authname : "-",
    	    *b->params.peeraddr ? b->params.peeraddr : "-",
#ifdef USE_RADIUS
    	    b->params.filter_id ? b->params.filter_id :
#endif
    	    "-");
    }

    /* Delete dynamic routes */
    SLIST_FOREACH(r, &b->params.routes, next) {
	if (u_rangefamily(&r->dest)==AF_INET6) {
	    if (!r->ok)
		continue;
	    IfaceSetRoute(b, RTM_DELETE, &r->dest, &iface->peer_ipv6_addr);
	    r->ok = 0;
	}
    }
    /* Delete static routes */
    SLIST_FOREACH(r, &iface->routes, next) {
	if (u_rangefamily(&r->dest)==AF_INET6) {
	    if (!r->ok)
		continue;
	    IfaceSetRoute(b, RTM_DELETE, &r->dest, &iface->peer_ipv6_addr);
	    r->ok = 0;
	}
    }

    if (!u_addrempty(&iface->self_ipv6_addr)) {
	/* Remove address from interface */
	rng.addr = iface->self_ipv6_addr;
	rng.width = 64;
	IfaceChangeAddr(b, 0, &rng, &iface->peer_ipv6_addr);
    }

    IfaceNgIpv6Shutdown(b);
}

/*
 * IfaceIdleTimeout()
 */

static void
IfaceIdleTimeout(void *arg)
{
    Bund b = (Bund)arg;

  IfaceState			const iface = &b->iface;
  int				k;

  /* Get updated bpf node traffic statistics */
  BundUpdateStats(b);

  /* Mark current traffic period if there was traffic */
  if (iface->idleStats.recvFrames + iface->idleStats.xmitFrames < 
	b->stats.recvFrames + b->stats.xmitFrames) {
    iface->traffic[0] = TRUE;
  } else {		/* no demand traffic for a whole idle timeout period? */
    for (k = 0; k < IFACE_IDLE_SPLIT && !iface->traffic[k]; k++);
    if (k == IFACE_IDLE_SPLIT) {
      Log(LG_BUND, ("[%s] IFACE: Idle timeout", b->name));
      RecordLinkUpDownReason(b, NULL, 0, STR_IDLE_TIMEOUT, NULL);
      BundClose(b);
      return;
    }
  }

  iface->idleStats = b->stats;

  /* Shift traffic history */
  memmove(iface->traffic + 1,
    iface->traffic, (IFACE_IDLE_SPLIT - 1) * sizeof(*iface->traffic));
  iface->traffic[0] = FALSE;

  /* Restart timer */
  TimerStart(&iface->idleTimer);
}

/*
 * IfaceSessionTimeout()
 */

static void
IfaceSessionTimeout(void *arg)
{
    Bund b = (Bund)arg;

  Log(LG_BUND, ("[%s] IFACE: Session timeout", b->name));

  RecordLinkUpDownReason(b, NULL, 0, STR_SESSION_TIMEOUT, NULL);

  BundClose(b);

}

/*
 * IfaceCachePkt()
 *
 * A packet caused dial-on-demand; save it for later if possible.
 * Consumes the mbuf in any case.
 */

static void
IfaceCachePkt(Bund b, int proto, Mbuf pkt)
{
    IfaceState	const iface = &b->iface;

    /* Only cache network layer data */
    if (!PROT_NETWORK_DATA(proto)) {
	mbfree(pkt);
	return;
    }

    /* Release previously cached packet, if any, and save this one */
    if (iface->dodCache.pkt)
	mbfree(iface->dodCache.pkt);

    iface->dodCache.pkt = pkt;
    iface->dodCache.proto = proto;
    iface->dodCache.ts = time(NULL);
}

/*
 * IfaceCacheSend()
 *
 * Send cached packet
 */

static void
IfaceCacheSend(Bund b)
{
    IfaceState	const iface = &b->iface;

    if (iface->dodCache.pkt) {
	if (iface->dodCache.ts + MAX_DOD_CACHE_DELAY < time(NULL))
    	    mbfree(iface->dodCache.pkt);
	else {
    	    if (NgFuncWritePppFrame(b, NG_PPP_BUNDLE_LINKNUM,
		    iface->dodCache.proto, iface->dodCache.pkt) < 0) {
		Perror("[%s] can't write cached pkt", b->name);
    	    }
	}
	iface->dodCache.pkt = NULL;
    }
}

/*
 * IfaceIsDemand()
 *
 * Determine if this outgoing packet qualifies for dial-on-demand
 */

static int
IfaceIsDemand(int proto, Mbuf pkt)
{
  switch (proto) {
    case PROTO_IP:
      {
        struct ip	*ip;

        if (MBLEN(pkt) < sizeof(struct ip))
	    return (0);

	ip = (struct ip *)(void *)MBDATA(pkt);
	switch (ip->ip_p) {
	  case IPPROTO_IGMP:		/* No multicast stuff */
	    return(0);
#if (!defined(__FreeBSD__) || __FreeBSD_version >= 600025)
	  case IPPROTO_ICMP:
	    {
	      struct icmphdr	*icmp;
	      
    	      if (MBLEN(pkt) < (ip->ip_hl * 4 + sizeof(struct icmphdr)))
		return (0);

	      icmp = (struct icmphdr *) ((u_int32_t *)(void *) ip + ip->ip_hl);

	      switch (icmp->icmp_type)	/* No ICMP replies */
	      {
		case ICMP_ECHOREPLY:
		case ICMP_UNREACH:
		case ICMP_REDIRECT:
		  return(0);
		default:
		  break;
	      }
	    }
	    break;
#endif
	  case IPPROTO_UDP:
	    {
	      struct udphdr	*udp;

    	      if (MBLEN(pkt) < (ip->ip_hl * 4 + sizeof(struct udphdr)))
		return (0);

	      udp = (struct udphdr *) ((u_int32_t *)(void *) ip + ip->ip_hl);

#define NTP_PORT	123
	      if (ntohs(udp->uh_dport) == NTP_PORT)	/* No NTP packets */
		return(0);
	    }
	    break;
	  case IPPROTO_TCP:
	    {
	      struct tcphdr	*tcp;

    	      if (MBLEN(pkt) < (ip->ip_hl * 4 + sizeof(struct tcphdr)))
		return (0);

	      tcp = (struct tcphdr *) ((u_int32_t *)(void *) ip + ip->ip_hl);

	      if (tcp->th_flags & TH_RST)	/* No TCP reset packets */
		return(0);
	    }
	    break;
	  default:
	    break;
	}
	break;
      }
    default:
      break;
  }
  return(1);
}

/*
 * IfaceSetCommand()
 */

static int
IfaceSetCommand(Context ctx, int ac, const char *const av[], const void *arg)
{
  IfaceState	const iface = &ctx->bund->iface;
  int		empty_arg;

  switch ((intptr_t)arg) {
    case SET_NAME:
#ifdef SIOCSIFDESCR
    case SET_DESCR:
#endif
    case SET_UP_SCRIPT:
    case SET_DOWN_SCRIPT:
      empty_arg = TRUE;
      break;
    default:
      empty_arg = FALSE;
      break;
  }

  if ((ac == 0) && (empty_arg == FALSE))
    return(-1);
  switch ((intptr_t)arg) {
    case SET_IDLE:
      iface->idle_timeout = atoi(*av);
      break;
    case SET_SESSION:
      iface->session_timeout = atoi(*av);
      break;
    case SET_ADDRS:
      {
	struct u_range	self_addr;
	struct u_addr	peer_addr;
	int	self_addr_force = 0, peer_addr_force = 0;
	const char	*arg1;

	/* Parse */
	if (ac != 2)
	  return(-1);
	arg1 = av[0];
	if (arg1[0] == '!') {
	    self_addr_force = 1;
	    arg1++;
	}
	if (!ParseRange(arg1, &self_addr, ALLOW_IPV4|ALLOW_IPV6))
	  Error("Bad IP address \"%s\"", av[0]);
	arg1 = av[1];
	if (arg1[0] == '!') {
	    peer_addr_force = 1;
	    arg1++;
	}
	if (!ParseAddr(arg1, &peer_addr, ALLOW_IPV4|ALLOW_IPV6))
	  Error("Bad IP address \"%s\"", av[1]);
	if (self_addr.addr.family != peer_addr.family)
	  Error("Addresses must be from the same protocol family");

	/* OK */
	if (peer_addr.family == AF_INET) {
	    iface->conf.self_addr = self_addr;
	    iface->conf.peer_addr = peer_addr;
	    iface->conf.self_addr_force = self_addr_force;
	    iface->conf.peer_addr_force = peer_addr_force;
	} else {
	    iface->conf.self_ipv6_addr = self_addr.addr;
	    iface->conf.peer_ipv6_addr = peer_addr;
	    iface->conf.self_ipv6_addr_force = self_addr_force;
	    iface->conf.peer_ipv6_addr_force = peer_addr_force;
	}
      }
      break;

    case SET_ROUTE:
      {
	struct u_range		range;
	IfaceRoute		r;

	/* Check */
	if (ac != 1)
	  return(-1);

	/* Get dest address */
	if (!strcasecmp(av[0], "default")) {
	  u_rangeclear(&range);
	  range.addr.family=AF_INET;
	}
	else if (!ParseRange(av[0], &range, ALLOW_IPV4|ALLOW_IPV6))
	  Error("Bad route dest address \"%s\"", av[0]);
	r = Malloc(MB_IFACE, sizeof(struct ifaceroute));
	r->dest = range;
	r->ok = 0;
	SLIST_INSERT_HEAD(&iface->routes, r, next);
      }
      break;

    case SET_MTU:
      {
	int	max_mtu;
	int	override;

	/* Check */
	if (ac < 1 || ac > 2)
	  return(-1);

	max_mtu = atoi(av[0]);
	override = 0;

	if (ac == 2 && av[1][0]) {
	  if (strcmp(av[1], "override") == 0)
	    override = 1;
	  else
	    Error("Invalid keyword %s", av[1]);
	}
	
	if (max_mtu < IFACE_MIN_MTU || max_mtu > IFACE_MAX_MTU)
	  if (!override || max_mtu != 0)
	    Error("Invalid interface mtu %d", max_mtu);

	if (max_mtu != 0)
	  iface->max_mtu = max_mtu;
	if (override)
	  iface->mtu_override = max_mtu;
      }
      break;

    case SET_NAME:
	switch (ac) {
	  case 0:
	    /* Restore original interface name */
	    if (strcmp(iface->ifname, iface->ngname) != 0) {
		iface->conf.ifname[0] = '\0';
		return IfaceSetName(ctx->bund, iface->ngname);
	    }
	    break;
	  case 1:
	    if (strcmp(iface->ifname, av[0]) != 0) {
		unsigned ifmaxlen = IF_NAMESIZE - ctx->bund->tmpl * IFNUMLEN;
		if (strlen(av[0]) >= ifmaxlen)
		    Error("Interface name too long, >%u characters", ifmaxlen-1);
		if ((strncmp(av[0], "ng", 2) == 0) &&
		  ((ctx->bund->tmpl && av[0][2] == 0) ||
		  (av[0][2] >= '0' && av[0][2] <= '9')))
		    Error("This interface name is reserved");
		strlcpy(iface->conf.ifname, av[0], sizeof(iface->conf.ifname));
		return IfaceSetName(ctx->bund, av[0]);
	    }
	    break;
	  default:
	    return(-1);
	}
	break;
#ifdef SIOCSIFDESCR
    case SET_DESCR:
	IfaceFreeDescr(iface);
	switch (ac) {
	  case 0:
	    return IfaceSetDescr(ctx->bund, "");
	    break;
	  case 1:
	    iface->conf.ifdescr = Mstrdup(MB_IFACE, av[0]);
	    return IfaceSetDescr(ctx->bund, av[0]);
	    break;
	  default:
	    return(-1);
	}
	break;
#endif
#ifdef SIOCAIFGROUP
    case SET_GROUP:
	if (ac != 1)
	  return(-1);

	if (av[0][0] && isdigit(av[0][strlen(av[0]) - 1]))
	    Error("Groupnames may not end in a digit");
	if (strlen(av[0]) >= IFNAMSIZ)
	    Error("Group name %s too long", av[0]);
	if (iface->conf.ifgroup[0] != 0)
	    IfaceDelGroup(ctx->bund, iface->conf.ifgroup);
	strlcpy(iface->conf.ifgroup, av[0], IFNAMSIZ);
	return IfaceAddGroup(ctx->bund, av[0]);
      break;
#endif
    case SET_UP_SCRIPT:
      switch (ac) {
	case 0:
	  *iface->up_script = 0;
	  break;
	case 1:
	  strlcpy(iface->up_script, av[0], sizeof(iface->up_script));
	  break;
	default:
	  return(-1);
      }
      break;

    case SET_DOWN_SCRIPT:
      switch (ac) {
	case 0:
	  *iface->down_script = 0;
	  break;
	case 1:
	  strlcpy(iface->down_script, av[0], sizeof(iface->down_script));
	  break;
	default:
	  return(-1);
      }
      break;

    case SET_ENABLE:
      EnableCommand(ac, av, &iface->options, gConfList);
      break;

    case SET_DISABLE:
      DisableCommand(ac, av, &iface->options, gConfList);
      break;

    default:
      assert(0);
  }
  return(0);
}

/*
 * IfaceStat()
 */

int
IfaceStat(Context ctx, int ac, const char *const av[], const void *arg)
{
    Bund	const b = ctx->bund;
    IfaceState	const iface = &b->iface;
    IfaceRoute	r;
#ifdef USE_NG_BPF
    int		k;
#endif
    char	buf[48];
#if defined(USE_NG_BPF) || defined(USE_IPFW)
    struct acl	*a;
#endif

    (void)ac;
    (void)av;
    (void)arg;

    Printf("Interface configuration:\r\n");
    Printf("\tName            : %s\r\n", iface->conf.ifname);
#ifdef SIOCSIFDESCR
    Printf("\tDescription     : \"%s\"\r\n",
	(iface->ifdescr != NULL) ? iface->ifdescr : "<none>");
#endif
#ifdef SIOCAIFGROUP
    Printf("\tGroup           : %s\r\n", iface->conf.ifgroup);
#endif
    Printf("\tMaximum MTU     : %d bytes\r\n", iface->max_mtu);
    Printf("\tMTU override    : %d bytes\r\n", iface->mtu_override);
    Printf("\tIdle timeout    : %d seconds\r\n", iface->idle_timeout);
    Printf("\tSession timeout : %d seconds\r\n", iface->session_timeout);
    if (!u_rangeempty(&iface->conf.self_addr)) {
	Printf("\tIP Addresses    : %s%s -> ", iface->conf.self_addr_force?"!":"",
	    u_rangetoa(&iface->conf.self_addr,buf,sizeof(buf)));
	Printf("%s%s\r\n",  iface->conf.peer_addr_force?"!":"",
	    u_addrtoa(&iface->conf.peer_addr,buf,sizeof(buf)));
    }
    if (!u_addrempty(&iface->conf.self_ipv6_addr)) {
	Printf("\tIPv6 Addresses  : %s%s%%%s -> ",  iface->conf.self_ipv6_addr_force?"!":"",
	    u_addrtoa(&iface->conf.self_ipv6_addr,buf,sizeof(buf)), iface->ifname);
	Printf("%s%s%%%s\r\n",  iface->conf.peer_ipv6_addr_force?"!":"",
	    u_addrtoa(&iface->conf.peer_ipv6_addr,buf,sizeof(buf)), iface->ifname);
    }
    Printf("\tEvent scripts\r\n");
    Printf("\t  up-script     : \"%s\"\r\n",
	*iface->up_script ? iface->up_script : "<none>");
    Printf("\t  down-script   : \"%s\"\r\n",
	*iface->down_script ? iface->down_script : "<none>");
    Printf("Interface options:\r\n");
    OptStat(ctx, &iface->options, gConfList);
    if (!SLIST_EMPTY(&iface->routes)) {
	Printf("Static routes via peer:\r\n");
	SLIST_FOREACH(r, &iface->routes, next) {
	    Printf("\t%s\r\n", u_rangetoa(&r->dest,buf,sizeof(buf)));
	}
    }
    Printf("Interface status:\r\n");
    Printf("\tAdmin status    : %s\r\n", iface->open ? "OPEN" : "CLOSED");
    Printf("\tStatus          : %s\r\n", iface->up ? (iface->dod?"DoD":"UP") : "DOWN");
    Printf("\tName            : %s\r\n", iface->ifname);
#ifdef SIOCSIFDESCR
    Printf("\tDescription     : \"%s\"\r\n",
	(iface->ifdescr != NULL) ? iface->ifdescr : "<none>");
#endif
    if (iface->up) {
	Printf("\tSession time    : %ld seconds\r\n", (long int)(time(NULL) - iface->last_up));
	if (b->params.idle_timeout || iface->idle_timeout)
	    Printf("\tIdle timeout    : %d seconds\r\n", b->params.idle_timeout?b->params.idle_timeout:iface->idle_timeout);
	if (b->params.session_timeout || iface->session_timeout)
	    Printf("\tSession timeout : %d seconds\r\n", b->params.session_timeout?b->params.session_timeout:iface->session_timeout);
	Printf("\tMTU             : %d bytes\r\n", iface->mtu);
    }
    if (iface->ip_up && !u_rangeempty(&iface->self_addr)) {
	Printf("\tIP Addresses    : %s -> ", u_rangetoa(&iface->self_addr,buf,sizeof(buf)));
	Printf("%s\r\n", u_addrtoa(&iface->peer_addr,buf,sizeof(buf)));
    }
    if (iface->ipv6_up && !u_addrempty(&iface->self_ipv6_addr)) {
	Printf("\tIPv6 Addresses  : %s%%%s -> ", 
	    u_addrtoa(&iface->self_ipv6_addr,buf,sizeof(buf)), iface->ifname);
	Printf("%s%%%s\r\n", u_addrtoa(&iface->peer_ipv6_addr,buf,sizeof(buf)), iface->ifname);
    }
    if (iface->up) {
        Printf("Dynamic routes via peer:\r\n");
	SLIST_FOREACH(r, &ctx->bund->params.routes, next) {
	    Printf("\t%s\r\n", u_rangetoa(&r->dest,buf,sizeof(buf)));
	}
#ifdef USE_IPFW
	Printf("IPFW pipes:\r\n");
	a = ctx->bund->params.acl_pipe;
	while (a) {
	    Printf("\t%d (%d)\t: '%s'\r\n", a->number, a->real_number, a->rule);
	    a = a->next;
	}
	Printf("IPFW queues:\r\n");
	a = ctx->bund->params.acl_queue;
	while (a) {
	    Printf("\t%d (%d)\t: '%s'\r\n", a->number, a->real_number, a->rule);
	    a = a->next;
	}
	Printf("IPFW tables:\r\n");
	a = ctx->bund->params.acl_table;
	while (a) {
	    if (a->number != 0)
		Printf("\t%d (%d)\t: '%s'\r\n", a->number, a->real_number, a->rule);
	    else
		Printf("\t(%d)\t: '%s'\r\n", a->real_number, a->rule);
	    a = a->next;
	}
	Printf("IPFW rules:\r\n");
	a = ctx->bund->params.acl_rule;
	while (a) {
	    Printf("\t%d (%d)\t: '%s'\r\n", a->number, a->real_number, a->rule);
	    a = a->next;
	}
#endif /* USE_IPFW */
#ifdef USE_NG_BPF
	Printf("Traffic filters:\r\n");
	for (k = 0; k < ACL_FILTERS; k++) {
	    a = ctx->bund->params.acl_filters[k];
	    if (a == NULL)
		a = acl_filters[k];
	    while (a) {
		Printf("\t%d#%d\t: '%s'\r\n", (k + 1), a->number, a->rule);
		a = a->next;
	    }
	}
	Printf("Traffic limits:\r\n");
	for (k = 0; k < 2; k++) {
	    a = ctx->bund->params.acl_limits[k];
	    while (a) {
		Printf("\t%s#%d%s%s\t: '%s'\r\n", (k?"out":"in"), a->number,
		    ((a->name[0])?"#":""), a->name, a->rule);
		a = a->next;
	    }
	}
#endif /* USE_NG_BPF */
    }
    return(0);
}

/*
 * IfaceSetMTU()
 *
 * Set MTU and bandwidth on bundle's interface
 */

void
IfaceSetMTU(Bund b, int mtu)
{
    IfaceState		const iface = &b->iface;
    struct ifreq	ifr;
    int			s;

    /* Get socket */
    if ((s = socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0) {
	Perror("[%s] IFACE: Can't get socket to set MTU", b->name);
	return;
    }

    if (!iface->mtu_override && (b->params.mtu > 0) && (mtu > b->params.mtu)) {
	mtu = b->params.mtu;
	Log(LG_IFACE2, ("[%s] IFACE: forcing MTU of auth backend: %d bytes",
	    b->name, mtu));
    }

    /* Limit MTU to configured maximum/override */
    if (iface->mtu_override) {
	mtu = iface->mtu_override;
	Log(LG_IFACE2, ("[%s] IFACE: forcing MTU override: %d bytes",
	    b->name, mtu));
    } else if (mtu > iface->max_mtu)
        mtu = iface->max_mtu;

    /* Set MTU on interface */
    memset(&ifr, 0, sizeof(ifr));
    strlcpy(ifr.ifr_name, b->iface.ifname, sizeof(ifr.ifr_name));
    ifr.ifr_mtu = mtu;
    Log(LG_IFACE2, ("[%s] IFACE: setting %s MTU to %d bytes",
	b->name, b->iface.ifname, mtu));
    if (ioctl(s, SIOCSIFMTU, (char *)&ifr) < 0)
	Perror("[%s] IFACE: ioctl(%s, %s)", b->name, b->iface.ifname, "SIOCSIFMTU");
    close(s);

    /* Save MTU */
    iface->mtu = mtu;

#if defined(USE_NG_TCPMSS) || (!defined(USE_NG_TCPMSS) && defined(USE_NG_BPF))
    /* Update tcpmssfix config */
    if (iface->mss_up)
        IfaceSetupMSS(b, MAXMSS(mtu));
#endif

}

void
IfaceChangeFlags(Bund b, int clear, int set)
{
    struct ifreq ifrq;
    int s, new_flags;

    Log(LG_IFACE2, ("[%s] IFACE: Change interface %s flags: -%d +%d",
	b->name, b->iface.ifname, clear, set));

    if ((s = socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0) {
	Perror("[%s] IFACE: Can't get socket to change interface flags", b->name);
	return;
    }

    memset(&ifrq, '\0', sizeof(ifrq));
    strlcpy(ifrq.ifr_name, b->iface.ifname, sizeof(ifrq.ifr_name));
    if (ioctl(s, SIOCGIFFLAGS, &ifrq) < 0) {
	Perror("[%s] IFACE: ioctl(%s, %s)", b->name, b->iface.ifname, "SIOCGIFFLAGS");
	close(s);
	return;
    }
    new_flags = (ifrq.ifr_flags & 0xffff) | (ifrq.ifr_flagshigh << 16);

    new_flags &= ~clear;
    new_flags |= set;

    ifrq.ifr_flags = new_flags & 0xffff;
    ifrq.ifr_flagshigh = new_flags >> 16;

    if (ioctl(s, SIOCSIFFLAGS, &ifrq) < 0) {
	Perror("[%s] IFACE: ioctl(%s, %s)", b->name, b->iface.ifname, "SIOCSIFFLAGS");
	close(s);
	return;
    }
    close(s);
}

#if defined(__KAME__) && !defined(NOINET6)
static void
add_scope(struct sockaddr *sa, int ifindex)
{
  struct sockaddr_in6 *sa6;

  if (sa->sa_family != AF_INET6)
    return;
  sa6 = (struct sockaddr_in6 *)(void *)sa;
  if (!IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) &&
      !IN6_IS_ADDR_MC_LINKLOCAL(&sa6->sin6_addr))
    return;
  if (sa6->sin6_addr.__u6_addr.__u6_addr16[1] != 0)
    return;
  sa6->sin6_addr.__u6_addr.__u6_addr16[1] = htons(ifindex);
}
#endif

int
IfaceChangeAddr(Bund b, int add, struct u_range *self, struct u_addr *peer)
{
    struct ifaliasreq ifra;
    struct in6_aliasreq ifra6;
    struct sockaddr_in *me4, *msk4, *peer4;
    struct sockaddr_storage ssself, sspeer, ssmsk;
    int res = 0;
    int s;
    char buf[48], buf1[48];

    Log(LG_IFACE2, ("[%s] IFACE: %s address %s->%s %s %s",
	b->name, add?"Add":"Remove", u_rangetoa(self, buf, sizeof(buf)), 
	((peer != NULL)?u_addrtoa(peer, buf1, sizeof(buf1)):""),
	add?"to":"from", b->iface.ifname));

    u_rangetosockaddrs(self, &ssself, &ssmsk);
    if (peer)
	u_addrtosockaddr(peer, 0, &sspeer);

    if ((s = socket(self->addr.family, SOCK_DGRAM, 0)) < 0) {
	Perror("[%s] IFACE: Can't get socket to change interface address", b->name);
	return (s);
    }

    switch (self->addr.family) {
      case AF_INET:
	memset(&ifra, '\0', sizeof(ifra));
	strlcpy(ifra.ifra_name, b->iface.ifname, sizeof(ifra.ifra_name));

	me4 = (struct sockaddr_in *)(void *)&ifra.ifra_addr;
	memcpy(me4, &ssself, sizeof(*me4));

	msk4 = (struct sockaddr_in *)(void *)&ifra.ifra_mask;
	memcpy(msk4, &ssmsk, sizeof(*msk4));

	peer4 = (struct sockaddr_in *)(void *)&ifra.ifra_broadaddr;
	if (peer == NULL || peer->family == AF_UNSPEC) {
    	    peer4->sin_family = AF_INET;
    	    peer4->sin_len = sizeof(*peer4);
    	    peer4->sin_addr.s_addr = INADDR_NONE;
	} else
    	    memcpy(peer4, &sspeer, sizeof(*peer4));

	res = ioctl(s, add?SIOCAIFADDR:SIOCDIFADDR, &ifra);
	if (res == -1) {
	    Perror("[%s] IFACE: %s IPv4 address %s %s failed", 
		b->name, add?"Adding":"Removing", add?"to":"from", b->iface.ifname);
	}
	break;

      case AF_INET6:
	memset(&ifra6, '\0', sizeof(ifra6));
	strlcpy(ifra6.ifra_name, b->iface.ifname, sizeof(ifra6.ifra_name));

	memcpy(&ifra6.ifra_addr, &ssself, sizeof(ifra6.ifra_addr));
	memcpy(&ifra6.ifra_prefixmask, &ssmsk, sizeof(ifra6.ifra_prefixmask));
	if (peer == NULL || peer->family == AF_UNSPEC)
    	    ifra6.ifra_dstaddr.sin6_family = AF_UNSPEC;
	else if (memcmp(&((struct sockaddr_in6 *)&ssmsk)->sin6_addr, &in6mask128,
		    sizeof(in6mask128)) == 0)
    	    memcpy(&ifra6.ifra_dstaddr, &sspeer, sizeof(ifra6.ifra_dstaddr));
	ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
	ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;

	res = ioctl(s, add?SIOCAIFADDR_IN6:SIOCDIFADDR_IN6, &ifra6);
	if (res == -1) {
		if (add && errno == EEXIST) {
			/* this can happen if the kernel has already automatically added
			   the same link-local address - ignore the error */
			res = 0;
		} else {
			Perror("[%s] IFACE: %s IPv6 address %s %s failed", 
				b->name, add?"Adding":"Removing", add?"to":"from", b->iface.ifname);
		}
	}
	break;

      default:
        res = -1;
	break;
    }
    close(s);
    return (res);
}

struct rtmsg {
  struct rt_msghdr m_rtm;
  char m_space[256];
};

static size_t
memcpy_roundup(char *cp, const void *data, size_t len)
{
  size_t padlen;

#define ROUNDUP(x) ((x) ? (1 + (((x) - 1) | (sizeof(long) - 1))) : sizeof(long))
  padlen = ROUNDUP(len);
  memcpy(cp, data, len);
  if (padlen > len)
    memset(cp + len, '\0', padlen - len);

  return padlen;
}

int
IfaceSetRoute(Bund b, int cmd, struct u_range *dst,
       struct u_addr *gw)
{
    struct rtmsg rtmes;
    int s, nb, wb;
    char *cp;
    const char *cmdstr = (cmd == RTM_ADD ? "Add" : "Delete");
    struct sockaddr_storage sadst, samask, sagw;
    char buf[48], buf1[48];

    s = socket(PF_ROUTE, SOCK_RAW, 0);
    if (s < 0) {
	Perror("[%s] IFACE: Can't get route socket", b->name);
	return (-1);
    }
    memset(&rtmes, '\0', sizeof(rtmes));
    rtmes.m_rtm.rtm_version = RTM_VERSION;
    rtmes.m_rtm.rtm_type = cmd;
    rtmes.m_rtm.rtm_addrs = RTA_DST;
    rtmes.m_rtm.rtm_seq = ++gRouteSeq;
    rtmes.m_rtm.rtm_pid = gPid;
    rtmes.m_rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;

    u_rangetosockaddrs(dst, &sadst, &samask);
#if defined(__KAME__) && !defined(NOINET6)
    add_scope((struct sockaddr *)&sadst, b->iface.ifindex);
#endif

    cp = rtmes.m_space;
    cp += memcpy_roundup(cp, &sadst, sadst.ss_len);
    if (gw != NULL) {
	u_addrtosockaddr(gw, 0, &sagw);
#if defined(__KAME__) && !defined(NOINET6)
	add_scope((struct sockaddr *)&sagw, b->iface.ifindex);
#endif
    	cp += memcpy_roundup(cp, &sagw, sagw.ss_len);
    	rtmes.m_rtm.rtm_addrs |= RTA_GATEWAY;
    } else if (cmd == RTM_ADD) {
    	Log(LG_ERR, ("[%s] IfaceSetRoute: gw is not set\n", b->name));
    	close(s);
    	return (-1);
    }

    if (u_rangehost(dst)) {
	rtmes.m_rtm.rtm_flags |= RTF_HOST;
    } else {
	cp += memcpy_roundup(cp, &samask, samask.ss_len);
	rtmes.m_rtm.rtm_addrs |= RTA_NETMASK;
    }

    nb = cp - (char *)&rtmes;
    rtmes.m_rtm.rtm_msglen = nb;
    wb = write(s, &rtmes, nb);
    if (wb < 0) {
    	Log(LG_ERR, ("[%s] IFACE: %s route %s %s failed: %s",
	    b->name, cmdstr, u_rangetoa(dst, buf, sizeof(buf)), 
	    ((gw != NULL)?u_addrtoa(gw, buf1, sizeof(buf1)):""),
	    (rtmes.m_rtm.rtm_errno != 0)?strerror(rtmes.m_rtm.rtm_errno):strerror(errno)));
	close(s);
	return (-1);
    }
    close(s);
    Log(LG_IFACE2, ("[%s] IFACE: %s route %s %s",
	    b->name, cmdstr, u_rangetoa(dst, buf, sizeof(buf)), 
	    ((gw != NULL)?u_addrtoa(gw, buf1, sizeof(buf1)):"")));
    return (0);
}

#ifndef USE_NG_TCPMSS
void
IfaceCorrectMSS(Mbuf pkt, uint16_t maxmss)
{
  struct ip	*iphdr;
  struct tcphdr	*tc;
  int		pktlen, hlen, olen, optlen, accumulate;
  uint16_t	*mss;
  u_char	*opt;

  if (pkt == NULL)
    return;

  iphdr = (struct ip *)(void *)MBDATAU(pkt);
  hlen = iphdr->ip_hl << 2;
  pktlen = MBLEN(pkt) - hlen;
  tc = (struct tcphdr *)(void *)(MBDATAU(pkt) + hlen);
  hlen = tc->th_off << 2;

  /* Invalid header length or header without options. */
  if (hlen <= sizeof(struct tcphdr) || hlen > pktlen)
    return;

  /* MSS option only allowed within SYN packets. */  
  if (!(tc->th_flags & TH_SYN))
    return;

  for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1);
	olen > 0; olen -= optlen, opt += optlen) {
    if (*opt == TCPOPT_EOL)
      break;
    else if (*opt == TCPOPT_NOP)
      optlen = 1;
    else {
      optlen = *(opt + 1);
      if (optlen <= 0 || optlen > olen)
	break;
      if (*opt == TCPOPT_MAXSEG) {
	if (optlen != TCPOLEN_MAXSEG)
	  continue;
	mss = (u_int16_t *)(void *)(opt + 2);
	if (ntohs(*mss) > maxmss) {
	  accumulate = *mss;
	  *mss = htons(maxmss);
	  accumulate -= *mss;
	  ADJUST_CHECKSUM(accumulate, tc->th_sum);
	}
      }
    }
  }
}
#endif

static int
IfaceNgIpInit(Bund b, int ready)
{
    struct ngm_connect	cn;
    char		path[NG_PATHSIZ];
    char		hook[NG_HOOKSIZ];

    if (!ready) {
	/* Dial-on-Demand mode */
	/* Use demand hook of the socket node */
	snprintf(path, sizeof(path), ".:");
	snprintf(hook, sizeof(hook), "4%d", b->id);

    } else {
	snprintf(path, sizeof(path), "[%x]:", b->nodeID);
	strcpy(hook, NG_PPP_HOOK_INET);

#ifdef USE_NG_NAT
	/* Add a nat node if configured */
	if (Enabled(&b->iface.options, IFACE_CONF_NAT)) {
	    if (IfaceInitNAT(b, path, hook))
		goto fail;
	    b->iface.nat_up = 1;
	}
#endif

	/* Add a tee node if configured */
	if (Enabled(&b->iface.options, IFACE_CONF_TEE)) {
	    if (IfaceInitTee(b, path, hook, 0))
		goto fail;
	    b->iface.tee_up = 1;
	}
  
#ifdef USE_NG_IPACCT
	/* Connect a ipacct node if configured */
	if (Enabled(&b->iface.options, IFACE_CONF_IPACCT)) {
	    if (IfaceInitIpacct(b, path, hook))
		goto fail;
	    b->iface.ipacct_up = 1;
	}
#endif	/* USE_NG_IPACCT */

#ifdef USE_NG_NETFLOW
#ifdef NG_NETFLOW_CONF_INGRESS
	/* Connect a netflow node if configured */
	if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_IN) ||
	    Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT)) {
	    if (IfaceInitNetflow(b, path, hook, 
		Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT)?1:0, 0))
		goto fail;
	    if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_IN))
		b->iface.nfin_up = 1;
	    if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT))
		b->iface.nfout_up = 1;
	}
#else	/* NG_NETFLOW_CONF_INGRESS */
	/* Connect a netflow node if configured */
	if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_IN)) {
	    if (IfaceInitNetflow(b, path, hook, 0, 0))
		goto fail;
	    b->iface.nfin_up = 1;
	}

	if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT)) {
	    if (IfaceInitNetflow(b, path, hook, 1, 0))
		goto fail;
	    b->iface.nfout_up = 1;
	}
#endif	/* NG_NETFLOW_CONF_INGRESS */
#endif	/* USE_NG_NETFLOW */

    }

#if defined(USE_NG_TCPMSS) || (!defined(USE_NG_TCPMSS) && defined(USE_NG_BPF))
    if (Enabled(&b->iface.options, IFACE_CONF_TCPMSSFIX)) {
	if (IfaceInitMSS(b, path, hook))
    	    goto fail;
	b->iface.mss_up = 1;
    }
#endif

#ifdef USE_NG_BPF
    if (IfaceInitLimits(b, path, hook))
	goto fail;
#endif

    /* Connect graph to the iface node. */
    memset(&cn, 0, sizeof(cn));
    strcpy(cn.ourhook, hook);
    snprintf(cn.path, sizeof(cn.path), "%s:", b->iface.ngname);
    strcpy(cn.peerhook, NG_IFACE_HOOK_INET);
    if (NgSendMsg(gLinksCsock, path,
    	    NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
	Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
	    b->name, path, cn.ourhook, cn.path, cn.peerhook);
	goto fail;
    }

    if (ready) {
#ifdef USE_NG_NETFLOW
#ifdef NG_NETFLOW_CONF_INGRESS
	if (b->iface.nfin_up || b->iface.nfout_up)
	    IfaceSetupNetflow(b, b->iface.nfin_up, b->iface.nfout_up, 0);
#else /* NG_NETFLOW_CONF_INGRESS */
	if (b->iface.nfin_up)
	    IfaceSetupNetflow(b, 1, 0, 0);

	if (b->iface.nfout_up)
	    IfaceSetupNetflow(b, 0, 1, 0);
#endif /* NG_NETFLOW_CONF_INGRESS */
#endif /* USE_NG_NETFLOW */
    }

#if defined(USE_NG_TCPMSS) || (!defined(USE_NG_TCPMSS) && defined(USE_NG_BPF))
    if (b->iface.mss_up)
        IfaceSetupMSS(b, MAXMSS(b->iface.mtu));
#endif
    
#ifdef USE_NG_BPF
    IfaceSetupLimits(b);
#endif

    /* OK */
    return(0);

fail:
    return(-1);
}

/*
 * IfaceNgIpShutdown()
 */

static void
IfaceNgIpShutdown(Bund b)
{
    char		path[NG_PATHSIZ];

#ifdef USE_NG_BPF
    IfaceShutdownLimits(b); /* Limits must shutdown first to save final stats. */
#endif
#ifdef USE_NG_NAT
    if (b->iface.nat_up)
	IfaceShutdownNAT(b);
    b->iface.nat_up = 0;
#endif
    if (b->iface.tee_up)
	IfaceShutdownTee(b, 0);
    b->iface.tee_up = 0;
#ifdef USE_NG_NETFLOW
#ifdef NG_NETFLOW_CONF_INGRESS
    if (b->iface.nfin_up || b->iface.nfout_up)
	IfaceShutdownNetflow(b, b->iface.nfout_up, 0);
    b->iface.nfin_up = 0;
    b->iface.nfout_up = 0;
#else /* NG_NETFLOW_CONF_INGRESS */
    if (b->iface.nfin_up)
	IfaceShutdownNetflow(b, 0, 0);
    b->iface.nfin_up = 0;
    if (b->iface.nfout_up)
	IfaceShutdownNetflow(b, 1, 0);
    b->iface.nfout_up = 0;
#endif /* NG_NETFLOW_CONF_INGRESS */
#endif
#ifdef USE_NG_IPACCT
    if (b->iface.ipacct_up)
	IfaceShutdownIpacct(b);
    b->iface.ipacct_up = 0;
#endif
#if defined(USE_NG_TCPMSS) || (!defined(USE_NG_TCPMSS) && defined(USE_NG_BPF))
    if (b->iface.mss_up)
	IfaceShutdownMSS(b);
#endif
    b->iface.mss_up = 0;

    snprintf(path, sizeof(path), "[%x]:", b->nodeID);
    NgFuncDisconnect(gLinksCsock, b->name, path, NG_PPP_HOOK_INET);

    snprintf(path, sizeof(path), "%s:", b->iface.ngname);
    NgFuncDisconnect(gLinksCsock, b->name, path, NG_IFACE_HOOK_INET);
}

static int
IfaceNgIpv6Init(Bund b, int ready)
{
    struct ngm_connect	cn;
    char		path[NG_PATHSIZ];
    char		hook[NG_HOOKSIZ];

    if (!ready) {
	/* Dial-on-Demand mode */
	/* Use demand hook of the socket node */
	snprintf(path, sizeof(path), ".:");
	snprintf(hook, sizeof(hook), "6%d", b->id);
    } else {
	snprintf(path, sizeof(path), "[%x]:", b->nodeID);
	strcpy(hook, NG_PPP_HOOK_IPV6);

	/* Add a tee node if configured */
	if (Enabled(&b->iface.options, IFACE_CONF_TEE)) {
	    if (IfaceInitTee(b, path, hook, 1))
		goto fail;
	    b->iface.tee6_up = 1;
	}
  
#ifdef USE_NG_NETFLOW
#ifdef NG_NETFLOW_CONF_INGRESS
	/* Connect a netflow node if configured */
	if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_IN) ||
	    Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT)) {
	    if (IfaceInitNetflow(b, path, hook, 
		Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT)?1:0, 1))
		goto fail;
	    if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_IN))
		b->iface.nfin_up = 1;
	    if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT))
		b->iface.nfout_up = 1;
	}
#else	/* NG_NETFLOW_CONF_INGRESS */
	/* Connect a netflow node if configured */
	if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_IN)) {
	    if (IfaceInitNetflow(b, path, hook, 0, 1))
		goto fail;
	    b->iface.nfin_up = 1;
	}

	if (Enabled(&b->iface.options, IFACE_CONF_NETFLOW_OUT)) {
	    if (IfaceInitNetflow(b, path, hook, 1, 1))
		goto fail;
	    b->iface.nfout_up = 1;
	}
#endif	/* NG_NETFLOW_CONF_INGRESS */
#endif	/* USE_NG_NETFLOW */
    }

#ifdef USE_NG_BPF
    if (IfaceInitLimits(b, path, hook))
	goto fail;
#endif

    /* Connect graph to the iface node. */
    strcpy(cn.ourhook, hook);
    snprintf(cn.path, sizeof(cn.path), "%s:", b->iface.ngname);
    strcpy(cn.peerhook, NG_IFACE_HOOK_INET6);
    if (NgSendMsg(gLinksCsock, path,
    	    NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
	Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
	    b->name, path, cn.ourhook, cn.path, cn.peerhook);
	goto fail;
    }

    if (ready) {
#ifdef USE_NG_NETFLOW
#ifdef NG_NETFLOW_CONF_INGRESS
	if (b->iface.nfin_up || b->iface.nfout_up)
	    IfaceSetupNetflow(b, b->iface.nfin_up, b->iface.nfout_up, 1);
#else /* NG_NETFLOW_CONF_INGRESS */
	if (b->iface.nfin_up)
	    IfaceSetupNetflow(b, 1, 0, 1);

	if (b->iface.nfout_up)
	    IfaceSetupNetflow(b, 0, 1, 1);
#endif /* NG_NETFLOW_CONF_INGRESS */
#endif /* USE_NG_NETFLOW */
    }

#ifdef USE_NG_BPF
    IfaceSetupLimits(b);
#endif

    /* OK */
    return(0);

fail:
    return(-1);
}

/*
 * IfaceNgIpv6Shutdown()
 */

static void
IfaceNgIpv6Shutdown(Bund b)
{
    char		path[NG_PATHSIZ];

#ifdef USE_NG_BPF
    IfaceShutdownLimits(b); /* Limits must shutdown first to save final stats. */
#endif
    if (b->iface.tee6_up)
	IfaceShutdownTee(b, 1);
    b->iface.tee6_up = 0;
#ifdef USE_NG_NETFLOW
#ifdef NG_NETFLOW_CONF_INGRESS
    if (b->iface.nfin_up || b->iface.nfout_up)
	IfaceShutdownNetflow(b, b->iface.nfout_up, 1);
    b->iface.nfin_up = 0;
    b->iface.nfout_up = 0;
#else /* NG_NETFLOW_CONF_INGRESS */
    if (b->iface.nfin_up)
	IfaceShutdownNetflow(b, 0, 1);
    b->iface.nfin_up = 0;
    if (b->iface.nfout_up)
	IfaceShutdownNetflow(b, 1, 1);
    b->iface.nfout_up = 0;
#endif /* NG_NETFLOW_CONF_INGRESS */
#endif

    snprintf(path, sizeof(path), "[%x]:", b->nodeID);
    NgFuncDisconnect(gLinksCsock, b->name, path, NG_PPP_HOOK_IPV6);

    snprintf(path, sizeof(path), "%s:", b->iface.ngname);
    NgFuncDisconnect(gLinksCsock, b->name, path, NG_IFACE_HOOK_INET6);
}

#ifdef USE_NG_NAT
static int
IfaceInitNAT(Bund b, char *path, char *hook)
{
    NatState      const nat = &b->iface.nat;
    struct ngm_mkpeer	mp;
    struct ngm_name	nm;
    struct in_addr	ip;
#ifdef NG_NAT_LOG
    struct ng_nat_mode	mode;
#endif  
    Log(LG_IFACE2, ("[%s] IFACE: Connecting NAT", b->name));
  
    strcpy(mp.type, NG_NAT_NODE_TYPE);
    strcpy(mp.ourhook, hook);
    strcpy(mp.peerhook, NG_NAT_HOOK_IN);
    if (NgSendMsg(gLinksCsock, path,
	NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
      Perror("[%s] can't create %s node at \"%s\"->\"%s\"",
	b->name, NG_NAT_NODE_TYPE, path, mp.ourhook);
      return(-1);
    }
    strlcat(path, ".", NG_PATHSIZ);
    strlcat(path, hook, NG_PATHSIZ);
    snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-nat", gPid, b->name);
    if (NgSendMsg(gLinksCsock, path,
	NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
      Perror("[%s] can't name %s node", b->name, NG_NAT_NODE_TYPE);
      return(-1);
    }
    strcpy(hook, NG_NAT_HOOK_OUT);

    /* Set NAT IP */
    if (u_addrempty(&nat->alias_addr)) {
	ip.s_addr = 1; // Set something just to make it ready
    } else {
	ip = nat->alias_addr.u.ip4;
    }
    if (NgSendMsg(gLinksCsock, path,
	    NGM_NAT_COOKIE, NGM_NAT_SET_IPADDR, &ip, sizeof(ip)) < 0)
	Perror("[%s] can't set NAT ip", b->name);

#ifdef NG_NAT_LOG
    /* Set NAT mode */
    mode.flags = 0;
    if (Enabled(&nat->options, NAT_CONF_LOG))
	mode.flags |= NG_NAT_LOG;
    if (!Enabled(&nat->options, NAT_CONF_INCOMING))
	mode.flags |= NG_NAT_DENY_INCOMING;
    if (Enabled(&nat->options, NAT_CONF_SAME_PORTS))
	mode.flags |= NG_NAT_SAME_PORTS;
    if (Enabled(&nat->options, NAT_CONF_UNREG_ONLY))
	mode.flags |= NG_NAT_UNREGISTERED_ONLY;
    
    mode.mask = NG_NAT_LOG | NG_NAT_DENY_INCOMING | 
	NG_NAT_SAME_PORTS | NG_NAT_UNREGISTERED_ONLY;
    if (NgSendMsg(gLinksCsock, path,
	    NGM_NAT_COOKIE, NGM_NAT_SET_MODE, &mode, sizeof(mode)) < 0)
	Perror("[%s] can't set NAT mode", b->name);

    /* Set NAT target IP */
    if (!u_addrempty(&nat->target_addr)) {
	ip = nat->target_addr.u.ip4;
	if (NgSendMsg(gLinksCsock, path,
		NGM_NAT_COOKIE, NGM_NAT_SET_IPADDR, &ip, sizeof(ip)) < 0) {
	    Perror("[%s] can't set NAT target IP", b->name);
	}
    }
#endif

    return(0);
}

static int
IfaceSetupNAT(Bund b)
{
    NatState	const nat = &b->iface.nat;
    char	path[NG_PATHSIZ];
#ifdef NG_NAT_DESC_LENGTH
    int k;
    union {
        u_char buf[sizeof(struct ng_mesg) + sizeof(uint32_t)];
        struct ng_mesg reply;
    } u;
    uint32_t *const nat_id = (uint32_t *)(void *)u.reply.data;
#endif

    snprintf(path, sizeof(path), "mpd%d-%s-nat:", gPid, b->name);
    if (u_addrempty(&nat->alias_addr)) {
	if (NgSendMsg(gLinksCsock, path,
    		NGM_NAT_COOKIE, NGM_NAT_SET_IPADDR,
		&b->iface.self_addr.addr.u.ip4,
		sizeof(b->iface.self_addr.addr.u.ip4)) < 0) {
	    Perror("[%s] can't set NAT ip", b->name);
	    return (-1);
	}
    }
#ifdef NG_NAT_DESC_LENGTH
    /* redirect-port */
    for(k = 0; k < NM_PORT; k++) {
      if(nat->nrpt_id[k] == -1) {
	if (NgSendMsg(gLinksCsock, path,
		NGM_NAT_COOKIE, NGM_NAT_REDIRECT_PORT, &nat->nrpt[k],
		sizeof(struct ng_nat_redirect_port)) < 0) {
	    Perror("[%s] can't set NAT redirect-port", b->name);
	} else {
	    if (NgRecvMsg(gLinksCsock, &u.reply, sizeof(u), NULL) < 0) {
		Perror("[%s] can't recv NAT redirect-port message", b->name);
	    } else
		nat->nrpt_id[k] = *nat_id;
	}
      }
    }
    /* redirect-addr */
    for(k = 0; k < NM_ADDR; k++) {
      if(nat->nrad_id[k] == -1) {
	if (NgSendMsg(gLinksCsock, path,
		NGM_NAT_COOKIE, NGM_NAT_REDIRECT_ADDR, &nat->nrad[k],
		sizeof(struct ng_nat_redirect_addr)) < 0) {
	    Perror("[%s] can't set NAT redirect-addr", b->name);
	} else {
	    if (NgRecvMsg(gLinksCsock, &u.reply, sizeof(u), NULL) < 0) {
		Perror("[%s] can't recv NAT redirect-addr message", b->name);
	    } else
		nat->nrad_id[k] = *nat_id;
	}
      }
    }
    /* redirect-proto */
    for(k = 0; k < NM_PROTO; k++) {
      if(nat->nrpr_id[k] == -1) {
	if (NgSendMsg(gLinksCsock, path,
		NGM_NAT_COOKIE, NGM_NAT_REDIRECT_PROTO, &nat->nrpr[k],
		sizeof(struct ng_nat_redirect_proto)) < 0) {
	    Perror("[%s] can't set NAT redirect-proto", b->name);
	} else {
	    if (NgRecvMsg(gLinksCsock, &u.reply, sizeof(u), NULL) < 0) {
		Perror("[%s] can't recv NAT redirect-proto message", b->name);
	    } else
		nat->nrpr_id[k] = *nat_id;
	}
      }
    }
#endif
    return (0);
}

static void
IfaceShutdownNAT(Bund b)
{
    char	path[NG_PATHSIZ];
#ifdef NG_NAT_DESC_LENGTH
    NatState	const nat = &b->iface.nat;
    int		k;
#endif

    snprintf(path, sizeof(path), "mpd%d-%s-nat:", gPid, b->name);
    NgFuncShutdownNode(gLinksCsock, b->name, path);
#ifdef NG_NAT_DESC_LENGTH
    /* redirect-port */
    for(k = 0; k < NM_PORT; k++)
      if(nat->nrpt_id[k] > 0)
        nat->nrpt_id[k] = -1;
    /* redirect-addr */
    for(k = 0; k < NM_ADDR; k++)
      if(nat->nrad_id[k] > 0)
        nat->nrad_id[k] = -1;
    /* redirect-proto */
    for(k = 0; k < NM_PROTO; k++)
      if(nat->nrpr_id[k] > 0)
        nat->nrpr_id[k] = -1;
#endif
}
#endif /* USE_NG_NAT */

static int
IfaceInitTee(Bund b, char *path, char *hook, int v6)
{
    struct ngm_mkpeer	mp;
    struct ngm_name	nm;

    Log(LG_IFACE2, ("[%s] IFACE: Connecting tee%s", b->name, v6?"6":""));
  
    strcpy(mp.type, NG_TEE_NODE_TYPE);
    strcpy(mp.ourhook, hook);
    strcpy(mp.peerhook, NG_TEE_HOOK_RIGHT);
    if (NgSendMsg(gLinksCsock, path,
	NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
      Perror("[%s] can't create %s node at \"%s\"->\"%s\"",
	b->name, NG_TEE_NODE_TYPE, path, mp.ourhook);
      return(-1);
    }
    strlcat(path, ".", NG_PATHSIZ);
    strlcat(path, hook, NG_PATHSIZ);
    snprintf(nm.name, sizeof(nm.name), "%s-tee%s", b->iface.ifname, v6?"6":"");
    if (NgSendMsg(gLinksCsock, path,
	NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
      Perror("[%s] can't name %s node", b->name, NG_TEE_NODE_TYPE);
      return(-1);
    }
    strcpy(hook, NG_TEE_HOOK_LEFT);

    return(0);
}

static void
IfaceShutdownTee(Bund b, int v6)
{
    char	path[NG_PATHSIZ];

    snprintf(path, sizeof(path), "%s-tee%s:", b->iface.ifname, v6?"6":"");
    NgFuncShutdownNode(gLinksCsock, b->name, path);
}

#ifdef USE_NG_IPACCT
static int
IfaceInitIpacct(Bund b, char *path, char *hook)
{
    struct ngm_mkpeer	mp;
    struct ngm_name	nm;
    struct ngm_connect  cn;
    char		path1[NG_PATHSIZ];
    struct {
	struct ng_ipacct_mesg m;
	int		data;
    } ipam;

    Log(LG_IFACE2, ("[%s] IFACE: Connecting ipacct", b->name));
  
    strcpy(mp.type, NG_TEE_NODE_TYPE);
    strcpy(mp.ourhook, hook);
    strcpy(mp.peerhook, NG_TEE_HOOK_RIGHT);
    if (NgSendMsg(gLinksCsock, path,
	NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
      Perror("[%s] can't create %s node at \"%s\"->\"%s\"",
	b->name, NG_TEE_NODE_TYPE, path, mp.ourhook);
      return(-1);
    }
    strlcat(path, ".", NG_PATHSIZ);
    strlcat(path, hook, NG_PATHSIZ);
    snprintf(nm.name, sizeof(nm.name), "%s_acct_tee", b->iface.ifname);
    if (NgSendMsg(gLinksCsock, path,
	NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
      Perror("[%s] can't name %s node", b->name, NG_TEE_NODE_TYPE);
      return(-1);
    }
    strcpy(hook, NG_TEE_HOOK_LEFT);

    strcpy(mp.type, NG_IPACCT_NODE_TYPE);
    strcpy(mp.ourhook, NG_TEE_HOOK_RIGHT2LEFT);
    snprintf(mp.peerhook, sizeof(mp.peerhook), "%s_in", b->iface.ifname);
    if (NgSendMsg(gLinksCsock, path,
	NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
      Perror("[%s] can't create %s node at \"%s\"->\"%s\"",
	b->name, NG_IPACCT_NODE_TYPE, path, mp.ourhook);
      return(-1);
    }
    snprintf(path1, sizeof(path1), "%s.%s", path, NG_TEE_HOOK_RIGHT2LEFT);
    snprintf(nm.name, sizeof(nm.name), "%s_ip_acct", b->iface.ifname);
    if (NgSendMsg(gLinksCsock, path1,
	NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
      Perror("[%s] can't name %s node", b->name, NG_IPACCT_NODE_TYPE);
      return(-1);
    }
    strcpy(cn.ourhook, NG_TEE_HOOK_LEFT2RIGHT);
    strcpy(cn.path, NG_TEE_HOOK_RIGHT2LEFT);
    snprintf(cn.peerhook, sizeof(cn.peerhook), "%s_out", b->iface.ifname);
    if (NgSendMsg(gLinksCsock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &cn,
	sizeof(cn)) < 0) {
      Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
        b->name, path, cn.ourhook, cn.path, cn.peerhook);
      return (-1);
    }
    
    snprintf(ipam.m.hname, sizeof(ipam.m.hname), "%s_in", b->iface.ifname);
    ipam.data = DLT_RAW;
    if (NgSendMsg(gLinksCsock, path1, NGM_IPACCT_COOKIE, NGM_IPACCT_SETDLT, 
	&ipam, sizeof(ipam)) < 0) {
      Perror("[%s] can't set DLT \"%s\"->\"%s\"", b->name, path, ipam.m.hname);
      return (-1);
    }
    ipam.data = 10000;
    if (NgSendMsg(gLinksCsock, path1, NGM_IPACCT_COOKIE, NGM_IPACCT_STHRS, 
	&ipam, sizeof(ipam)) < 0) {
      Perror("[%s] can't set DLT \"%s\"->\"%s\"", b->name, path, ipam.m.hname);
      return (-1);
    }
    
    snprintf(ipam.m.hname, sizeof(ipam.m.hname), "%s_out", b->iface.ifname);
    ipam.data = DLT_RAW;
    if (NgSendMsg(gLinksCsock, path1, NGM_IPACCT_COOKIE, NGM_IPACCT_SETDLT, 
	&ipam, sizeof(ipam)) < 0) {
      Perror("[%s] can't set DLT \"%s\"->\"%s\"", b->name, path, ipam.m.hname);
      return (-1);
    }
    ipam.data = 10000;
    if (NgSendMsg(gLinksCsock, path1, NGM_IPACCT_COOKIE, NGM_IPACCT_STHRS, 
	&ipam, sizeof(ipam)) < 0) {
      Perror("[%s] can't set DLT \"%s\"->\"%s\"", b->name, path, ipam.m.hname);
      return (-1);
    }

    return(0);
}

static void
IfaceShutdownIpacct(Bund b)
{
    char	path[NG_PATHSIZ];

    snprintf(path, sizeof(path), "%s_acct_tee:", b->iface.ifname);
    NgFuncShutdownNode(gLinksCsock, b->name, path);
}
#endif

#ifdef USE_NG_NETFLOW
static int
IfaceInitNetflow(Bund b, char *path, char *hook, char out, int v6)
{
    struct ngm_connect	cn;
    int nif;

#ifdef NG_NETFLOW_CONF_INGRESS
    nif = gNetflowIface + b->id*2;
#else
    nif = gNetflowIface + b->id*4 + out*2;
#endif
    nif += v6 ? 1 : 0;

    Log(LG_IFACE2, ("[%s] IFACE: Connecting netflow%s (%s)",
	b->name, v6?"6":"",  out?"out":"in"));
  
    /* Create global ng_netflow(4) node if not yet. */
    if (gNetflowNodeID == 0) {
	if (NgFuncInitGlobalNetflow())
	    return(-1);
    }

    /* Connect ng_netflow(4) node to the ng_bpf(4)/ng_tee(4) node. */
    strcpy(cn.ourhook, hook);
    snprintf(cn.path, sizeof(cn.path), "[%x]:", gNetflowNodeID);
#ifndef NG_NETFLOW_CONF_INGRESS
    if (out) {
	snprintf(cn.peerhook, sizeof(cn.peerhook), "%s%d", NG_NETFLOW_HOOK_OUT,
	    nif);
    } else {
#endif
	snprintf(cn.peerhook, sizeof(cn.peerhook), "%s%d", NG_NETFLOW_HOOK_DATA,
	    nif);
#ifndef NG_NETFLOW_CONF_INGRESS
    }
#endif
    if (NgSendMsg(gLinksCsock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &cn,
	sizeof(cn)) < 0) {
      Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
        b->name, path, cn.ourhook, cn.path, cn.peerhook);
      return (-1);
    }
    strlcat(path, ".", NG_PATHSIZ);
    strlcat(path, hook, NG_PATHSIZ);
#ifndef NG_NETFLOW_CONF_INGRESS
    if (out) {
	snprintf(hook, NG_HOOKSIZ, "%s%d", NG_NETFLOW_HOOK_DATA, nif);
    } else {
#endif
	snprintf(hook, NG_HOOKSIZ, "%s%d", NG_NETFLOW_HOOK_OUT, nif);
#ifndef NG_NETFLOW_CONF_INGRESS
    }
#endif
    return (0);
}

static int
IfaceSetupNetflow(Bund b, char in, char out, int v6)
{
    char path[NG_PATHSIZ];
    struct ng_netflow_setdlt	 nf_setdlt;
    struct ng_netflow_setifindex nf_setidx;
#ifdef NG_NETFLOW_CONF_INGRESS
    struct ng_netflow_setconfig  nf_setconf;
#endif
    int nif;

#ifdef NG_NETFLOW_CONF_INGRESS
    nif = gNetflowIface + b->id*2;
#else
    nif = gNetflowIface + b->id*4 + out*2;
#endif
    nif += v6 ? 1 : 0;

    /* Configure data link type and interface index. */
    snprintf(path, sizeof(path), "[%x]:", gNetflowNodeID);
    nf_setdlt.iface = nif;
    nf_setdlt.dlt = DLT_RAW;
    if (NgSendMsg(gLinksCsock, path, NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETDLT,
	&nf_setdlt, sizeof(nf_setdlt)) < 0) {
      Perror("[%s] can't configure data link type on %s", b->name, path);
      goto fail;
    }
#ifdef NG_NETFLOW_CONF_INGRESS
    nf_setconf.iface = nif;
    nf_setconf.conf = 
	(in?NG_NETFLOW_CONF_INGRESS:0) |
	(out?NG_NETFLOW_CONF_EGRESS:0) |
	(Enabled(&b->iface.options, IFACE_CONF_NETFLOW_ONCE)?NG_NETFLOW_CONF_ONCE:0);
    if (NgSendMsg(gLinksCsock, path, NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETCONFIG,
	&nf_setconf, sizeof(nf_setconf)) < 0) {
      Perror("[%s] can't set config on %s", b->name, path);
      goto fail;
    }
#endif
#ifndef NG_NETFLOW_CONF_INGRESS
    if (!out) {
#endif
	nf_setidx.iface = nif;
	nf_setidx.index = if_nametoindex(b->iface.ifname);
	if (NgSendMsg(gLinksCsock, path, NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETIFINDEX,
	    &nf_setidx, sizeof(nf_setidx)) < 0) {
    	  Perror("[%s] can't configure interface index on %s", b->name, path);
    	  goto fail;
	}
#ifndef NG_NETFLOW_CONF_INGRESS
    }
#endif

    return 0;
fail:
    return -1;
}

static void
IfaceShutdownNetflow(Bund b, char out, int v6)
{
    char	path[NG_PATHSIZ];
    char	hook[NG_HOOKSIZ];
    int nif;

#ifdef NG_NETFLOW_CONF_INGRESS
    (void)out;
    nif = gNetflowIface + b->id*2;
#else
    nif = gNetflowIface + b->id*4 + out*2;
#endif
    nif += v6 ? 1 : 0;

    snprintf(path, NG_PATHSIZ, "[%x]:", gNetflowNodeID);
    snprintf(hook, NG_HOOKSIZ, "%s%d", NG_NETFLOW_HOOK_DATA, nif);
    NgFuncDisconnect(gLinksCsock, b->name, path, hook);
    snprintf(hook, NG_HOOKSIZ, "%s%d", NG_NETFLOW_HOOK_OUT, nif);
    NgFuncDisconnect(gLinksCsock, b->name, path, hook);
}
#endif

#if defined(USE_NG_TCPMSS) || (!defined(USE_NG_TCPMSS) && defined(USE_NG_BPF))
static int
IfaceInitMSS(Bund b, char *path, char *hook)
{
	struct ngm_mkpeer	mp;
	struct ngm_name		nm;
#ifndef USE_NG_TCPMSS
	struct ngm_connect	cn;
#endif

	Log(LG_IFACE2, ("[%s] IFACE: Connecting tcpmssfix", b->name));
  
#ifdef USE_NG_TCPMSS
	/* Create ng_tcpmss(4) node. */
	strcpy(mp.type, NG_TCPMSS_NODE_TYPE);
	strlcpy(mp.ourhook, hook, sizeof(mp.ourhook));
	strcpy(mp.peerhook, "in");
	if (NgSendMsg(gLinksCsock, path,
    		NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
    	    Perror("can't create %s node at \"%s\"->\"%s\"",
    		NG_TCPMSS_NODE_TYPE, path, mp.ourhook);
	    goto fail;
	}

	strlcat(path, ".", NG_PATHSIZ);
	strlcat(path, hook, NG_PATHSIZ);
	snprintf(hook, NG_HOOKSIZ, "out");

	/* Set the new node's name. */
	snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-mss", gPid, b->name);
	if (NgSendMsg(gLinksCsock, path,
    		NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
    	    Perror("can't name %s node", NG_TCPMSS_NODE_TYPE);
	    goto fail;
	}

#else
    /* Create a bpf node for SYN detection. */
    strcpy(mp.type, NG_BPF_NODE_TYPE);
    strlcpy(mp.ourhook, hook, sizeof(mp.ourhook));
    strcpy(mp.peerhook, "ppp");
    if (NgSendMsg(gLinksCsock, path,
	    NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
	Perror("can't create %s node at \"%s\"->\"%s\"",
	    NG_BPF_NODE_TYPE, path, mp.ourhook);
	goto fail;
    }

    strlcat(path, ".", NG_PATHSIZ);
    strlcat(path, hook, NG_PATHSIZ);
    strcpy(hook, "iface");

    /* Set the new node's name. */
    snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-mss", gPid, b->name);
    if (NgSendMsg(gLinksCsock, path,
	    NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
	Perror("can't name tcpmssfix %s node", NG_BPF_NODE_TYPE);
	goto fail;
    }

    /* Connect to the bundle socket node. */
    strlcpy(cn.path, path, sizeof(cn.path));
    snprintf(cn.ourhook, sizeof(cn.ourhook), "i%d", b->id);
    strcpy(cn.peerhook, MPD_HOOK_TCPMSS_IN);
    if (NgSendMsg(gLinksCsock, ".:", NGM_GENERIC_COOKIE, NGM_CONNECT, &cn,
    	    sizeof(cn)) < 0) {
    	Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
    	    b->name, path, cn.ourhook, cn.path, cn.peerhook);
    	goto fail;
    }

    strlcpy(cn.path, path, sizeof(cn.path));
    snprintf(cn.ourhook, sizeof(cn.ourhook), "o%d", b->id);
    strcpy(cn.peerhook, MPD_HOOK_TCPMSS_OUT);
    if (NgSendMsg(gLinksCsock, ".:", NGM_GENERIC_COOKIE, NGM_CONNECT, &cn,
    	    sizeof(cn)) < 0) {
    	Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
    	    b->name, path, cn.ourhook, cn.path, cn.peerhook);
    	goto fail;
    }
#endif /* USE_NG_TCPMSS */

    return (0);
fail:
    return (-1);
}

/*
 * BundConfigMSS()
 *
 * Configure the tcpmss node to reduce MSS to given value.
 */

static void
IfaceSetupMSS(Bund b, uint16_t maxMSS)
{
#ifdef USE_NG_TCPMSS
  struct	ng_tcpmss_config tcpmsscfg;
  char		path[NG_PATHSIZ];

  snprintf(path, sizeof(path), "mpd%d-%s-mss:", gPid, b->name);

  /* Send configure message. */
  memset(&tcpmsscfg, 0, sizeof(tcpmsscfg));
  tcpmsscfg.maxMSS = maxMSS;

  Log(LG_IFACE2, ("[%s] IFACE: Configuring ng_tcpmss %s %u",
      b->name, path, (unsigned)tcpmsscfg.maxMSS));

  snprintf(tcpmsscfg.inHook, sizeof(tcpmsscfg.inHook), "in");
  snprintf(tcpmsscfg.outHook, sizeof(tcpmsscfg.outHook), "out");
  if (NgSendMsg(gLinksCsock, path, NGM_TCPMSS_COOKIE, NGM_TCPMSS_CONFIG,
      &tcpmsscfg, sizeof(tcpmsscfg)) < 0) {
    Perror("[%s] can't configure %s node program", b->name, NG_TCPMSS_NODE_TYPE);
  }
  snprintf(tcpmsscfg.inHook, sizeof(tcpmsscfg.inHook), "out");
  snprintf(tcpmsscfg.outHook, sizeof(tcpmsscfg.outHook), "in");
  if (NgSendMsg(gLinksCsock, path, NGM_TCPMSS_COOKIE, NGM_TCPMSS_CONFIG,
      &tcpmsscfg, sizeof(tcpmsscfg)) < 0) {
    Perror("[%s] can't configure %s node program", b->name, NG_TCPMSS_NODE_TYPE);
  }
#else
    union {
	u_char			buf[NG_BPF_HOOKPROG_SIZE(TCPSYN_PROG_LEN)];
	struct ng_bpf_hookprog	hprog;
    }				u;
    struct ng_bpf_hookprog	*const hp = &u.hprog;
    char			hook[NG_HOOKSIZ];

    /* Setup programs for ng_bpf hooks */
    snprintf(hook, sizeof(hook), "i%d", b->id);

    memset(&u, 0, sizeof(u));
    strcpy(hp->thisHook, "ppp");
    hp->bpf_prog_len = TCPSYN_PROG_LEN;
    memcpy(&hp->bpf_prog, &gTCPSYNProg,
        TCPSYN_PROG_LEN * sizeof(*gTCPSYNProg));
    strcpy(hp->ifMatch, MPD_HOOK_TCPMSS_IN);
    strcpy(hp->ifNotMatch, "iface");

    if (NgSendMsg(gLinksCsock, hook, NGM_BPF_COOKIE,
	    NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0)
	Perror("[%s] can't set %s node program", b->name, NG_BPF_NODE_TYPE);

    memset(&u, 0, sizeof(u));
    strcpy(hp->thisHook, MPD_HOOK_TCPMSS_IN);
    hp->bpf_prog_len = NOMATCH_PROG_LEN;
    memcpy(&hp->bpf_prog, &gNoMatchProg,
        NOMATCH_PROG_LEN * sizeof(*gNoMatchProg));
    strcpy(hp->ifMatch, "ppp");
    strcpy(hp->ifNotMatch, "ppp");

    if (NgSendMsg(gLinksCsock, hook, NGM_BPF_COOKIE,
	    NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0)
	Perror("[%s] can't set %s node program", b->name, NG_BPF_NODE_TYPE);

    snprintf(hook, sizeof(hook), "o%d", b->id);
    memset(&u, 0, sizeof(u));
    strcpy(hp->thisHook, "iface");
    hp->bpf_prog_len = TCPSYN_PROG_LEN;
    memcpy(&hp->bpf_prog, &gTCPSYNProg,
        TCPSYN_PROG_LEN * sizeof(*gTCPSYNProg));
    strcpy(hp->ifMatch, MPD_HOOK_TCPMSS_OUT);
    strcpy(hp->ifNotMatch, "ppp");

    if (NgSendMsg(gLinksCsock, hook, NGM_BPF_COOKIE,
	    NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0)
	Perror("[%s] can't set %s node program", b->name, NG_BPF_NODE_TYPE);

    memset(&u, 0, sizeof(u));
    strcpy(hp->thisHook, MPD_HOOK_TCPMSS_OUT);
    hp->bpf_prog_len = NOMATCH_PROG_LEN;
    memcpy(&hp->bpf_prog, &gNoMatchProg,
        NOMATCH_PROG_LEN * sizeof(*gNoMatchProg));
    strcpy(hp->ifMatch, "iface");
    strcpy(hp->ifNotMatch, "iface");

    if (NgSendMsg(gLinksCsock, hook, NGM_BPF_COOKIE,
	    NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0)
	Perror("[%s] can't set %s node program", b->name, NG_BPF_NODE_TYPE);

#endif /* USE_NG_TCPMSS */
}

static void
IfaceShutdownMSS(Bund b)
{
	char	path[NG_PATHSIZ];

#ifdef USE_NG_TCPMSS
	snprintf(path, sizeof(path), "mpd%d-%s-mss:", gPid, b->name);
	NgFuncShutdownNode(gLinksCsock, b->name, path);
#else
	snprintf(path, sizeof(path), "i%d", b->id);
	NgFuncShutdownNode(gLinksCsock, b->name, path);
#endif
}
#endif /* defined(USE_NG_TCPMSS) || (!defined(USE_NG_TCPMSS) && defined(USE_NG_BPF)) */

#ifdef USE_NG_BPF
static int
IfaceInitLimits(Bund b, char *path, char *hook)
{
    struct ngm_mkpeer	mp;
    struct ngm_name	nm;

    if (b->params.acl_limits[0] || b->params.acl_limits[1]) {

	Log(LG_IFACE2, ("[%s] IFACE: Connecting limits", b->name));
  
	/* Create a bpf node for traffic filtering. */
	strcpy(mp.type, NG_BPF_NODE_TYPE);
	strlcpy(mp.ourhook, hook, sizeof(mp.ourhook));
	strcpy(mp.peerhook, "ppp");
	if (NgSendMsg(gLinksCsock, path,
		NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
	    Perror("can't create %s node at \"%s\"->\"%s\"",
		NG_BPF_NODE_TYPE, path, mp.ourhook);
	    goto fail;
	}

	strlcat(path, ".", NG_PATHSIZ);
	strlcat(path, hook, NG_PATHSIZ);
	strcpy(hook, "iface");

	b->iface.limitID = NgGetNodeID(gLinksCsock, path);
	if (b->iface.limitID == 0)
	    Perror("can't get limits %s node ID", NG_BPF_NODE_TYPE);

	/* Set the new node's name. */
	snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-lim", gPid, b->name);
	if (NgSendMsg(gLinksCsock, path,
		NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
	    Perror("can't name limits %s node", NG_BPF_NODE_TYPE);
	    goto fail;
	}

    }

    return (0);
fail:
    return (-1);
}

/*
 * BundConfigLimits()
 *
 * Configure the bpf & car nodes.
 */

static void
IfaceSetupLimits(Bund b)
{
#define	ACL_MAX_PROGLEN	65536
    union {
	u_char			buf[NG_BPF_HOOKPROG_SIZE(ACL_MAX_PROGLEN)];
	struct ng_bpf_hookprog	hprog;
    }				*hpu;
    struct ng_bpf_hookprog	*hp;
    struct ngm_connect  cn;
    int			i;
    
    hpu = Malloc(MB_ACL, sizeof(*hpu));
    hp = &hpu->hprog;

    if (b->params.acl_limits[0] || b->params.acl_limits[1]) {
	char		path[NG_PATHSIZ];
	int		num, dir;

	snprintf(path, sizeof(path), "mpd%d-%s-lim:", gPid, b->name);
	
	for (dir = 0; dir < 2; dir++) {
	    char	inhook[2][NG_HOOKSIZ];
	    char	inhookn[2][NG_HOOKSIZ];
	    char	outhook[NG_HOOKSIZ];
	    struct acl	*l;

	    if (dir == 0) {
		strcpy(inhook[0], "ppp");
		strcpy(outhook, "iface");
	    } else {
		strcpy(inhook[0], "iface");
		strcpy(outhook, "ppp");
	    }
	    strcpy(inhook[1], "");
	    num = 0;
	    for (l = b->params.acl_limits[dir]; l; l = l->next) {
	        char		str[ACL_LEN];
#define	ACL_MAX_PARAMS	7	/* one more then max number of arguments */
	        int		ac;
	        char		*av[ACL_MAX_PARAMS];
		int		p;
		char		stathook[NG_HOOKSIZ];
		struct svcs	*ss = NULL;
		struct svcssrc	*sss = NULL;

		Log(LG_IFACE2, ("[%s] IFACE: limit %s#%d%s%s: '%s'",
        	    b->name, (dir?"out":"in"), l->number,
		    ((l->name[0])?"#":""), l->name, l->rule));
		strlcpy(str, l->rule, sizeof(str));
    		ac = ParseLine(str, av, ACL_MAX_PARAMS, 0);
	        if (ac < 1 || ac >= ACL_MAX_PARAMS) {
		    Log(LG_ERR, ("[%s] IFACE: incorrect limit: '%s'",
    			b->name, l->rule));
		    continue;
		}
		
		stathook[0] = 0;
	    	memset(hpu, 0, sizeof(*hpu));
		/* Prepare filter */
		if (strcasecmp(av[0], "all") == 0) {
		    hp->bpf_prog_len = MATCH_PROG_LEN;
		    memcpy(&hp->bpf_prog, &gMatchProg,
    		        MATCH_PROG_LEN * sizeof(*gMatchProg));
		} else if (strncasecmp(av[0], "flt", 3) == 0) {
		    struct acl  *f;
		    int		flt;
		    
		    flt = atoi(av[0] + 3);
		    if (flt <= 0 || flt > ACL_FILTERS) {
			Log(LG_ERR, ("[%s] IFACE: incorrect filter number: '%s'",
    			    b->name, av[0]));
		    } else if ((f = b->params.acl_filters[flt - 1]) == NULL &&
			(f = acl_filters[flt - 1]) == NULL) {
			Log(LG_ERR, ("[%s] IFACE: Undefined filter: '%s'",
    			    b->name, av[0]));
		    } else {
			struct bpf_program pr;
		    	char		*buf;
		    	int		bufbraces;

#define ACL_BUF_SIZE	256*1024
			buf = Malloc(MB_ACL, ACL_BUF_SIZE);
			buf[0] = 0;
			bufbraces = 0;
			while (f) {
			    char	*b1, *b2, *sbuf;
			    sbuf = Mstrdup(MB_ACL, f->rule);
			    b2 = sbuf;
			    b1 = strsep(&b2, " ");
			    if (b2 != NULL) {
			        if (strcasecmp(b1, "match") == 0) {
			    	    strlcat(buf, "( ", ACL_BUF_SIZE);
				    strlcat(buf, b2, ACL_BUF_SIZE);
				    strlcat(buf, " ) ", ACL_BUF_SIZE);
				    if (f->next) {
				        strlcat(buf, "|| ( ", ACL_BUF_SIZE);
				        bufbraces++;
				    }
				} else if (strcasecmp(b1, "nomatch") == 0) {
				    strlcat(buf, "( not ( ", ACL_BUF_SIZE);
				    strlcat(buf, b2, ACL_BUF_SIZE);
				    strlcat(buf, " ) ) ", ACL_BUF_SIZE);
				    if (f->next) {
				        strlcat(buf, "&& ( ", ACL_BUF_SIZE);
				        bufbraces++;
				    }
				} else {
			    	    Log(LG_ERR, ("[%s] IFACE: filter action '%s' is unknown",
        		    		b->name, b1));
				}
			    };
			    Freee(sbuf);
			    f = f->next;
			}
			for (i = 0; i < bufbraces; i++)
			    strlcat(buf, ") ", ACL_BUF_SIZE);
			Log(LG_IFACE2, ("[%s] IFACE: flt%d: '%s'",
        		    b->name, flt, buf));
			
			if (pcap_compile_nopcap((u_int)-1, DLT_RAW, &pr, buf, 1, 0xffffff00)) {
			    Log(LG_ERR, ("[%s] IFACE: filter '%s' compilation error",
    			        b->name, av[0]));
			    /* Incorrect matches nothing. */
			    hp->bpf_prog_len = NOMATCH_PROG_LEN;
			    memcpy(&hp->bpf_prog, &gNoMatchProg,
    		    		NOMATCH_PROG_LEN * sizeof(*gNoMatchProg));
			} else if (pr.bf_len > ACL_MAX_PROGLEN) {
			    Log(LG_ERR, ("[%s] IFACE: filter '%s' is too long",
        		        b->name, av[0]));
			    pcap_freecode(&pr);
			    /* Incorrect matches nothing. */
			    hp->bpf_prog_len = NOMATCH_PROG_LEN;
			    memcpy(&hp->bpf_prog, &gNoMatchProg,
    		    		NOMATCH_PROG_LEN * sizeof(*gNoMatchProg));
			} else {
			    hp->bpf_prog_len = pr.bf_len;
			    memcpy(&hp->bpf_prog, pr.bf_insns,
    			        pr.bf_len * sizeof(struct bpf_insn));
			    pcap_freecode(&pr);
			}
			Freee(buf);
		    }
		} else {
		    Log(LG_ERR, ("[%s] IFACE: incorrect filter: '%s'",
    		        b->name, av[0]));
		    /* Incorrect matches nothing. */
		    hp->bpf_prog_len = NOMATCH_PROG_LEN;
		    memcpy(&hp->bpf_prog, &gNoMatchProg,
    		        NOMATCH_PROG_LEN * sizeof(*gNoMatchProg));
		}
		
		/* Prepare action */
		p = 1;
		if (ac == 1) {
		    if (!l->next) {
			strcpy(hp->ifMatch, outhook);
			strcpy(inhookn[0], "");
		    } else {
			sprintf(hp->ifMatch, "%d-%d-m", dir, num);
			sprintf(inhookn[0], "%d-%d-mi", dir, num);

			/* Connect nomatch hook to bpf itself. */
			strcpy(cn.ourhook, hp->ifMatch);
			strcpy(cn.path, path);
			strcpy(cn.peerhook, inhookn[0]);
			if (NgSendMsg(gLinksCsock, path,
		        	NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
			    Perror("[%s] IFACE: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
			        b->name, path, cn.ourhook, cn.path, cn.peerhook);
			}
			strcpy(stathook, inhookn[0]);
		    }
		} else if (strcasecmp(av[p], "pass") == 0) {
		    strcpy(hp->ifMatch, outhook);
		    strcpy(inhookn[0], "");
		} else if (strcasecmp(av[p], "deny") == 0) {
		    strcpy(hp->ifMatch, "deny");
		    strcpy(inhookn[0], "");
#ifdef USE_NG_CAR
		} else if ((strcasecmp(av[p], "shape") == 0) ||
			   (strcasecmp(av[p], "rate-limit") == 0)) {
		    struct ngm_mkpeer 	mp;
		    struct ng_car_bulkconf car;
		    char		tmppath[NG_PATHSIZ];

		    sprintf(hp->ifMatch, "%d-%d-m", dir, num);

		    /* Create a car node for traffic shaping. */
		    strcpy(mp.type, NG_CAR_NODE_TYPE);
		    snprintf(mp.ourhook, sizeof(mp.ourhook), "%d-%d-m", dir, num);
		    strcpy(mp.peerhook, ((dir == 0)?NG_CAR_HOOK_LOWER:NG_CAR_HOOK_UPPER));
		    if (NgSendMsg(gLinksCsock, path,
			    NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
			Perror("[%s] IFACE: can't create %s node at \"%s\"->\"%s\"", 
			    b->name, NG_CAR_NODE_TYPE, path, mp.ourhook);
		    }

		    snprintf(tmppath, sizeof(tmppath), "%s%d-%d-m", path, dir, num);

		    /* Connect car to bpf. */
		    snprintf(cn.ourhook, sizeof(cn.ourhook), "%d-%d-mi", dir, num);
		    strlcpy(cn.path, tmppath, sizeof(cn.path));
		    strcpy(cn.peerhook, ((dir == 0)?NG_CAR_HOOK_UPPER:NG_CAR_HOOK_LOWER));
		    if (NgSendMsg(gLinksCsock, path,
		            NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
			Perror("[%s] IFACE: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
			    b->name, path, cn.ourhook, cn.path, cn.peerhook);
		    }
			
		    bzero(&car, sizeof(car));
			
		    if (strcasecmp(av[p], "shape") == 0) {
		        car.upstream.mode = NG_CAR_SHAPE;
		    } else {
		        car.upstream.mode = NG_CAR_RED;
		    }
		    p++;

		    if ((ac > p) && (av[p][0] >= '0') && (av[p][0] <= '9')) {
		        car.upstream.cir = atol(av[p]);
		        p++;
		        if ((ac > p) && (av[p][0] >= '0') && (av[p][0] <= '9')) {
		    	    car.upstream.cbs = atol(av[p]);
			    p++;
			    if ((ac > p) && (av[p][0] >= '0') && (av[p][0] <= '9')) {
			        car.upstream.ebs = atol(av[p]);
			        p++;
			    } else {
			        car.upstream.ebs = car.upstream.cbs * 2;
			    }
			} else {
			    car.upstream.cbs = car.upstream.cir / 8;
			    car.upstream.ebs = car.upstream.cbs * 2;
			}
		    } else {
		        car.upstream.cir = 8000;
		        car.upstream.cbs = car.upstream.cir / 8;
		        car.upstream.ebs = car.upstream.cbs * 2;
		    }
		    car.upstream.green_action = NG_CAR_ACTION_FORWARD;
		    car.upstream.yellow_action = NG_CAR_ACTION_FORWARD;
		    car.upstream.red_action = NG_CAR_ACTION_DROP;
			
		    car.downstream = car.upstream;
						
		    if (NgSendMsg(gLinksCsock, tmppath,
		            NGM_CAR_COOKIE, NGM_CAR_SET_CONF, &car, sizeof(car)) < 0) {
		        Perror("[%s] IFACE: can't set %s configuration",
			    b->name, NG_CAR_NODE_TYPE);
		    }
			
		    if (ac > p) {
			if (strcasecmp(av[p], "pass") == 0) {
			    union {
		    		u_char	buf[NG_BPF_HOOKPROG_SIZE(MATCH_PROG_LEN)];
		    		struct ng_bpf_hookprog	hprog;
			    } hpu1;
			    struct ng_bpf_hookprog	*const hp1 = &hpu1.hprog;

			    memset(&hpu1, 0, sizeof(hpu1));
			    strcpy(hp1->ifMatch, outhook);
			    strcpy(hp1->ifNotMatch, outhook);
			    hp1->bpf_prog_len = MATCH_PROG_LEN;
			    memcpy(&hp1->bpf_prog, &gMatchProg,
    			        MATCH_PROG_LEN * sizeof(*gMatchProg));
		    	    sprintf(hp1->thisHook, "%d-%d-mi", dir, num);
			    if (NgSendMsg(gLinksCsock, path, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM,
			    	    hp1, NG_BPF_HOOKPROG_SIZE(hp1->bpf_prog_len)) < 0) {
				Perror("[%s] IFACE: can't set %s node program",
	    			    b->name, NG_BPF_NODE_TYPE);
			    }
			    			    
			    strcpy(stathook, hp1->thisHook);
			    strcpy(inhookn[0], "");
			} else {
			    Log(LG_ERR, ("[%s] IFACE: unknown action: '%s'",
    			        b->name, av[p]));
			    strcpy(inhookn[0], "");
			}
		    } else {
			sprintf(inhookn[0], "%d-%d-mi", dir, num);
			strcpy(stathook, inhookn[0]);
		    }
#endif /* USE_NG_CAR */
	        } else {
		    Log(LG_ERR, ("[%s] IFACE: unknown action: '%s'",
    		        b->name, av[1]));
		    strcpy(inhookn[0], "");
		}
		
		/* Prepare nomatch */
		if (l->next && strcasecmp(av[0], "all")) {
		    /* If there is next limit and there is possible nomatch,
		     * then pass nomatch there. */
		    sprintf(hp->ifNotMatch, "%d-%d-n", dir, num);
		    sprintf(inhookn[1], "%d-%d-ni", dir, num);

		    /* Connect nomatch hook to bpf itself. */
		    strcpy(cn.ourhook, hp->ifNotMatch);
		    strcpy(cn.path, path);
		    strcpy(cn.peerhook, inhookn[1]);
		    if (NgSendMsg(gLinksCsock, path,
		            NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
			Perror("[%s] IFACE: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
			    b->name, path, cn.ourhook, cn.path, cn.peerhook);
		    }
		} else {
		    /* There is no next limit, pass nomatch. */
		    strcpy(hp->ifNotMatch, outhook);
		    strcpy(inhookn[1], "");
		}
		
		/* Remember how to collect stats for this limit */
		if (l->name[0]) {
		    SLIST_FOREACH(ss, &b->iface.ss[dir], next) {
			if (strcmp(ss->name, l->name) == 0)
			    break;
		    }
		    if (ss == NULL) {
			ss = Malloc(MB_ACL, sizeof(*ss));
			strlcpy(ss->name, l->name, sizeof(ss->name));
			SLIST_INIT(&ss->src);
			SLIST_INSERT_HEAD(&b->iface.ss[dir], ss, next);
		    }
		    if (stathook[0]) {
			sss = Malloc(MB_ACL, sizeof(*sss));
			strlcpy(sss->hook, stathook, sizeof(sss->hook));
			sss->type = SSSS_IN;
			SLIST_INSERT_HEAD(&ss->src, sss, next);
		    }
		}
		
		for (i = 0; i < 2; i++) {
		    if (inhook[i][0] != 0) {
			if (l->name[0] && !stathook[0]) {
			    sss = Malloc(MB_ACL, sizeof(*sss));
			    strlcpy(sss->hook, inhook[i], sizeof(sss->hook));
			    sss->type = SSSS_MATCH;
			    SLIST_INSERT_HEAD(&ss->src, sss, next);
			}
		
		        strcpy(hp->thisHook, inhook[i]);
		        if (NgSendMsg(gLinksCsock, path, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM,
		    		hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) {
			    Perror("[%s] IFACE: can't set %s node program",
	    		        b->name, NG_BPF_NODE_TYPE);
			}
		    }
		    strcpy(inhook[i], inhookn[i]);
		}

		num++;
	    }
	
	    /* Connect left hooks to output */
	    for (i = 0; i < 2; i++) {
		if (inhook[i][0] != 0) {
		    memset(hpu, 0, sizeof(*hpu));
		    strcpy(hp->thisHook, inhook[i]);
		    hp->bpf_prog_len = MATCH_PROG_LEN;
		    memcpy(&hp->bpf_prog, &gMatchProg,
    			MATCH_PROG_LEN * sizeof(*gMatchProg));
		    strcpy(hp->ifMatch, outhook);
		    strcpy(hp->ifNotMatch, outhook);
		    if (NgSendMsg(gLinksCsock, path, NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, 
			    hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) {
			Perror("[%s] IFACE: can't set %s node %s %s program (2)",
			    b->name, NG_BPF_NODE_TYPE, path, hp->thisHook);
		    }
		}
	    }
	}
    }
    Freee(hpu);
}

static void
IfaceShutdownLimits(Bund b)
{
    char path[NG_PATHSIZ];
    struct svcs *ss;
    struct svcssrc *sss;
    struct svcstat curstats;
    int		i;

    if (b->n_up > 0) {
	bzero(&curstats, sizeof(curstats));
    	IfaceGetStats(b, &curstats);
    	IfaceAddStats(&b->iface.prevstats, &curstats);
	IfaceFreeStats(&curstats);
    }

    if (b->params.acl_limits[0] || b->params.acl_limits[1]) {
	snprintf(path, sizeof(path), "[%x]:", b->iface.limitID);
	NgFuncShutdownNode(gLinksCsock, b->name, path);
    }

    for (i = 0; i < ACL_DIRS; i++) {
	while ((ss = SLIST_FIRST(&b->iface.ss[i])) != NULL) {
	    while ((sss = SLIST_FIRST(&ss->src)) != NULL) {
    		SLIST_REMOVE_HEAD(&ss->src, next);
		Freee(sss);
	    }
	    SLIST_REMOVE_HEAD(&b->iface.ss[i], next);
	    Freee(ss);
	}
    }
}

void
IfaceGetStats(Bund b, struct svcstat *stat)
{
    char path[NG_PATHSIZ];
    struct svcs 	*ss;
    struct svcssrc	*sss;
    int	dir;

    union {
        u_char          buf[sizeof(struct ng_mesg) + sizeof(struct ng_bpf_hookstat)];
	struct ng_mesg  reply;
    }                   u;
    struct ng_bpf_hookstat     *const hs = (struct ng_bpf_hookstat *)(void *)u.reply.data;

    snprintf(path, sizeof(path), "[%x]:", b->iface.limitID);
    for (dir = 0; dir < ACL_DIRS; dir++) {
	SLIST_FOREACH(ss, &b->iface.ss[dir], next) {
	    struct svcstatrec *ssr;
	
	    SLIST_FOREACH(ssr, &stat->stat[dir], next) {
		if (strcmp(ssr->name, ss->name) == 0)
		    break;
	    }
	    if (!ssr) {
		ssr = Malloc(MB_ACL, sizeof(*ssr));
		strlcpy(ssr->name, ss->name, sizeof(ssr->name));
		SLIST_INSERT_HEAD(&stat->stat[dir], ssr, next);
	    }
    
	    SLIST_FOREACH(sss, &ss->src, next) {
		if (NgSendMsg(gLinksCsock, path,
    		    NGM_BPF_COOKIE, NGM_BPF_GET_STATS, sss->hook, strlen(sss->hook)+1) < 0)
    		    continue;
		if (NgRecvMsg(gLinksCsock, &u.reply, sizeof(u), NULL) < 0)
    		    continue;
		
		switch(sss->type) {
		case SSSS_IN:
		    ssr->Packets += hs->recvFrames;
		    ssr->Octets += hs->recvOctets;
		    break;
		case SSSS_MATCH:
		    ssr->Packets += hs->recvMatchFrames;
		    ssr->Octets += hs->recvMatchOctets;
		    break;
		case SSSS_NOMATCH:
		    ssr->Packets += hs->recvFrames - hs->recvMatchFrames;
		    ssr->Octets += hs->recvOctets - hs->recvMatchOctets;
		    break;
		case SSSS_OUT:
		    ssr->Packets += hs->xmitFrames;
		    ssr->Octets += hs->xmitOctets;
		    break;
		}
	    }
	}
    }
}

void
IfaceAddStats(struct svcstat *stat1, struct svcstat *stat2)
{
    struct svcstatrec   *ssr1, *ssr2;
    int                 dir;

    for (dir = 0; dir < ACL_DIRS; dir++) {
	SLIST_FOREACH(ssr2, &stat2->stat[dir], next) {
	    SLIST_FOREACH(ssr1, &stat1->stat[dir], next)
		if (strcmp(ssr1->name, ssr2->name) == 0) {
		    break;
	    }
	    if (!ssr1) {
		ssr1 = Malloc(MB_ACL, sizeof(*ssr1));
		strlcpy(ssr1->name, ssr2->name, sizeof(ssr1->name));
		SLIST_INSERT_HEAD(&stat1->stat[dir], ssr1, next);
	    }
	    ssr1->Packets += ssr2->Packets;
	    ssr1->Octets += ssr2->Octets;
	}
    }
}

void
IfaceFreeStats(struct svcstat *stat)
{
    struct svcstatrec   *ssr;
    int                 i;

    for (i = 0; i < ACL_DIRS; i++) {
	while ((ssr = SLIST_FIRST(&stat->stat[i])) != NULL) {
    	    SLIST_REMOVE_HEAD(&stat->stat[i], next);
	    Freee(ssr);
	}
    }
}
#endif /* USE_NG_BPF */

/*
 * IfaceSetName()
 */

int
IfaceSetName(Bund b, const char * ifname)
{
    IfaceState	const iface = &b->iface;
    struct ifreq ifr;
    int s;

    /* Do not rename interface on template */
    if (b->tmpl)
	return(0);

    /* Do not wait ioctl error "file already exist" */
    if (strncmp(iface->ifname, ifname, sizeof(iface->ifname)) == 0)
	return(0);

    /* Get socket */
    if ((s = socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0) {
	Perror("[%s] IFACE: Can't get socket to set name", b->name);
	return(-1);
    }

    /* Set name of interface */
    memset(&ifr, 0, sizeof(ifr));
    strlcpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name));

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-qual"
#endif
    ifr.ifr_data = (char *)ifname;
#ifdef __clang__
#pragma clang diagnostic pop
#endif

    Log(LG_IFACE2, ("[%s] IFACE: setting \"%s\" name to \"%s\"",
	b->name, iface->ifname, ifname));

    if (ioctl(s, SIOCSIFNAME, (caddr_t)&ifr) < 0) {
	if (errno != EEXIST) {
	    Perror("[%s] IFACE: ioctl(%s, %s)", b->name, iface->ifname, "SIOCSIFNAME");
	    close(s);
	    return(-1);
	}
    }

    close(s);
    /* Save name */
    strlcpy(iface->ifname, ifname, sizeof(iface->ifname));
    return(0);
}

#ifdef SIOCSIFDESCR
/*
 * IfaceSetDescr()
 *
 * Set/clear our own interface description accessible in console/web interface
 * and kernel level interface description if it is available.
 *
 * Template may contain conversion specifications:
 *
 * %% expands to single % sign;
 * %a for interface local address;
 * %A for peer address;
 * %i for system interface index;
 * %I for interface name;
 * %l for name of bundle's first link
 * %M for peer MAC address of bundle's first link
 * %o for local outer ("physical") address of bundle's first link
 * %O for peer outer ("physical") address of bundle's first link
 * %P for peer outer ("physical") port of bundle's first link
 * %s for last interface description change time, in unixtime (seconds)
 * %S for interface status (DoD/UP/DOWN)
 * %t for type of bundle's first link (pppoe, pptp, l2tp etc.)
 * %u for self auth name (or dash if self auth name not used)
 * %U for peer auth name (or dash if peer has not authenticated)
 */
int
IfaceSetDescr(Bund b, const char * template)
{
    IfaceState	const iface = &b->iface;
    struct	ifreq ifr;
    int		s;
    unsigned	int ifdescr_maxlen = 1024;	/* used as limit for old kernels */
    char	*newdescr;
    size_t	sz = sizeof(ifdescr_maxlen);
    char	*limit, *ifname;
    const char	*src;
    int		proceed;
    char	buf[64];

    static	int mib[2] = { -1, 0 };	/* MIB for net.ifdescr_maxlen */
    size_t	miblen = sizeof(mib) / sizeof(mib[0]);

    /*
     * Check whether running kernel supports interface description.
     * Perform the check only once.
     */
    if (mib[0] < 0 && sysctlnametomib("net.ifdescr_maxlen", mib, &miblen) < 0) {
      mib[0] = 0;
      Perror("[%s] IFACE: sysctl net.ifdescr_maxlen failed", b->name);
    }

    /*
     * Fetch net.ifdescr_maxlen value every time to catch up with changes
     */
    if (mib[0] && sysctl(mib, 2, &ifdescr_maxlen, &sz, NULL, 0) < 0) {
      /* unexpected error from the kernel, use default value */
      Perror("[%s] IFACE: sysctl net.ifdescr_maxlen  failed", b->name);
      ifdescr_maxlen = 1024;
    }

    newdescr = NULL;
    ifname = iface->ifname;
    if (iface->ifdescr != NULL) {
	Freee(iface->ifdescr);
	iface->ifdescr = NULL;
    }
    if ((src = template) != NULL) {

      /* Will use Mstrdup() later for iface->ifdescr to free extra memory */
      if ((iface->ifdescr = newdescr = Malloc(MB_IFACE, ifdescr_maxlen)) == NULL) {
	Log(LG_IFACE2, ("[%s] IFACE: no memory for interface %s description",
    	    b->name, ifname ? ifname : ""));
        return(-1);
      }

      /* ifdescr_maxlen includes terminating zero */
      limit = newdescr + ifdescr_maxlen - 1;

      /*
       * Perform template expansion
       */
      proceed = 1;
      while (proceed && *src && newdescr < limit) {
	if (*src != '%') {	/* ordinary symbol, just copy it and proceed */
	  *newdescr++ = *src++;
	  continue;
	}
	if (!*(src+1)) {	/* '%' at the end of template, just copy */
	  *newdescr++ = *src++;
	  continue;
	}
	switch(*++src) {	/* expand */
	  case '%':		/* %% got replaced with single % */
	    *newdescr++ = *src;
	    break;

#define DST_COPY(a)				\
  do { const char *temp = a;			\
    if (temp && *temp) {			\
      if ((newdescr + strlen (temp)) <= limit) {\
	newdescr = stpcpy (newdescr, temp);	\
      } else {					\
	proceed = 0;				\
      }						\
    } else {					\
      *newdescr++ = '-';			\
    }						\
  } while(0)

	  /* self address */
	  case 'a':
	    {
	      char *sep;
	      u_rangetoa(&iface->self_addr, buf, sizeof(buf));
	      /* cut netmask */
	      if ((sep = strchr(buf, '/')))
	        *sep = '\0';
	      DST_COPY(buf);
	    }
	    break;
	  /* peer address */
	  case 'A':
	    {
	      u_addrtoa(&iface->peer_addr, buf, sizeof(buf));
	      DST_COPY(buf);
	    }
	    break;
	  /* interface index */
	  case 'i':
	    {
	      snprintf(buf, sizeof(buf), "%u", iface->ifindex);
	      DST_COPY(buf);
	    }
	    break;
	  /* interface name */
	  case 'I':
	    DST_COPY(iface->ifname);
	    break;
	  /* first link name */
	  case 'l':
	    DST_COPY(b->links[0] ? b->links[0]->name : NULL);
	    break;
	  /* peer MAC address */
	  case 'M':
	    if (b->links[0]) {
	      const struct phystype * pt = b->links[0]->type;
	      if (pt && pt->peermacaddr) {
		(*pt->peermacaddr)(b->links[0], buf, sizeof(buf));
		DST_COPY(buf);
	      } else {
		  DST_COPY("-");
	      }
	    } else {
		DST_COPY("-");
	    }
	    break;
	  /* local "physical" address */
	  case 'o':
	    if (b->links[0] && PhysGetSelfAddr(b->links[0], buf, sizeof(buf)) == 0) {
		DST_COPY(buf);
	    } else {
		DST_COPY("-");
	    }
	    break;
	  /* peer "physical" address */
	  case 'O':
	    if (b->links[0] && PhysGetPeerAddr(b->links[0], buf, sizeof(buf)) == 0) {
		DST_COPY(buf);
	    } else {
		DST_COPY("-");
	    }
	    break;
	  /* peer port */
	  case 'P':
	    if (b->links[0] && PhysGetPeerPort(b->links[0], buf, sizeof(buf)) == 0) {
		DST_COPY(buf);
	    } else {
		DST_COPY("-");
	    }
	    break;
	  /* timestamp of interface description change */
	  case 's':
	    snprintf(buf, sizeof(buf), "%jd", (intmax_t)time(NULL));
	    DST_COPY(buf);
	    break;
	  /* interface status */
	  case 'S':
	    DST_COPY(iface->up ? (iface->dod ? "DoD" : "UP") : "DOWN");
	    break;
	  /* first link type */
	  case 't':
	    DST_COPY(b->links[0] ? b->links[0]->type->name : NULL);
	    break;
	  /* self auth name */
	  case 'u':
	    DST_COPY(b->links[0] ? b->links[0]->lcp.auth.conf.authname : NULL);
	    break;
	  /* peer auth name */
	  case 'U':
	    DST_COPY(b->params.authname);
	    break;
#undef DST_COPY
	  default: /* unrecognized specification, just copy */
	    *newdescr++ = '%';
	    if (newdescr < limit)
		*newdescr++ = *src;
	} /* switch(*++src) */
	++src;
      } /* while */
      *newdescr = '\0';

      /* includes terminating zero */
      sz = newdescr - iface->ifdescr + 1;
      if ((newdescr = Mstrdup(MB_IFACE, iface->ifdescr)) == NULL) {
        Log(LG_IFACE2, ("[%s] IFACE: no memory for interface %s description",
			b->name, ifname ? ifname : ""));
        Freee(iface->ifdescr);
	iface->ifdescr = NULL;
        return(-1);
      }
      Freee(iface->ifdescr);
      iface->ifdescr = newdescr;
    } /* template != NULL */

    /* Set description of interface */
    if (mib[0] == 0)
	return(0);		/* do not bother kernel if it is too old */

    if (ifname == NULL || *ifname == '\0')
	return(0);		/* we have not set system interface name yet */

    /* Get socket */
    if ((s = socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0) {
	Perror("[%s] IFACE: Can't get socket to set description for %s",
	        b->name, ifname);
	return(-1);
    }

    memset(&ifr, 0, sizeof(ifr));
    strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));

    if (!newdescr || !*newdescr) {	/* empty description or clearing request */
	ifr.ifr_buffer.buffer = NULL;
	ifr.ifr_buffer.length = 0;
	Log(LG_IFACE2, ("[%s] IFACE: clearing \"%s\" description",
	    b->name, ifname));
    } else {
	ifr.ifr_buffer.length = (unsigned)sz;
	ifr.ifr_buffer.buffer = newdescr;
	Log(LG_IFACE2, ("[%s] IFACE: setting \"%s\" description to \"%s\"",
	    b->name, ifname, newdescr));
    }

    if (ioctl(s, SIOCSIFDESCR, (caddr_t)&ifr) < 0) {
	Perror("[%s] IFACE: ioctl(%s, SIOCSIFDESCR, \"%s\")",
	        b->name, ifname, newdescr ? newdescr : "" );
	close(s);
	return(-1);
    }
    close(s);
    return(0);
}
#endif /* SIOCSIFDESCR */
#ifdef SIOCAIFGROUP
/*
 * IfaceAddGroup()
 */

int
IfaceAddGroup(Bund b, const char * ifgroup)
{
    IfaceState	const iface = &b->iface;
    struct ifgroupreq	ifgr;
    int	s, i;

    /* Do not add group on template */
    if (b->tmpl)
	return(0);

    if (ifgroup[0] && isdigit(ifgroup[strlen(ifgroup) - 1])) {
	Perror("[%s] IFACE: groupnames may not end in a digit", b->name);
	return(-1);
    }

    /* Get socket */
    if ((s = socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0) {
	Perror("[%s] IFACE: Can't get socket to add group", b->name);
	return(-1);
    }

    /* Add interface group */
    memset(&ifgr, 0, sizeof(ifgr));
    strlcpy(ifgr.ifgr_name, iface->ifname, sizeof(ifgr.ifgr_name));
    strlcpy(ifgr.ifgr_group, ifgroup, sizeof(ifgr.ifgr_group));

    Log(LG_IFACE2, ("[%s] IFACE: adding interface %s to group %s",
	b->name, iface->ifname, ifgroup));

    i = ioctl(s, SIOCAIFGROUP, (caddr_t)&ifgr);
    if (i < 0 && i != EEXIST) {
	Perror("[%s] IFACE: ioctl(%s, %s)", b->name, iface->ifname, "SIOCAIFGROUP");
        close(s);
        return(-1);
    }

    close(s);
    return(0);
}

/*
 * IfaceDelGroup()
 */
int
IfaceDelGroup(Bund b, const char * ifgroup)
{
    IfaceState	const iface = &b->iface;
    struct ifgroupreq	ifgr;
    int	s;

    /* Get socket */
    if ((s = socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0) {
	Perror("[%s] IFACE: Can't get socket to delete from group", b->name);
	return(-1);
    }

    if (ifgroup[0] && isdigit(ifgroup[strlen(ifgroup) - 1])) {
	Perror("[%s] IFACE: groupnames may not end in a digit", b->name);
	return(-1);
    }

    /* Set interface group */
    memset(&ifgr, 0, sizeof(ifgr));
    strlcpy(ifgr.ifgr_name, iface->ifname, sizeof(ifgr.ifgr_name));
    strlcpy(ifgr.ifgr_group, ifgroup, sizeof(ifgr.ifgr_group));

    Log(LG_IFACE2, ("[%s] IFACE: remove interface %s from group %s",
	b->name, iface->ifname, ifgroup));

    if (ioctl(s, SIOCDIFGROUP, (caddr_t)&ifgr) == -1) {
	Perror("[%s] IFACE: ioctl(%s, %s)", b->name, iface->ifname, "SIOCDIFGROUP");
	close(s);
	return(-1);
    }
    close(s);
    return(0);
}
#endif

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>