/*
 * bund.c
 *
 * Written by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 *
 * Bundle handling stuff
 */

#include "ppp.h"
#include "bund.h"
#include "ipcp.h"
#include "ccp.h"
#include "mp.h"
#include "iface.h"
#include "link.h"
#include "msg.h"
#include "custom.h"
#include "ngfunc.h"
#include "log.h"
#include "util.h"
#include "input.h"

#include <netgraph.h>
#include <netgraph/ng_message.h>
#ifdef __DragonFly__
#include <netgraph/socket/ng_socket.h>
#include <netgraph/iface/ng_iface.h>
#include <netgraph/vjc/ng_vjc.h>
#else
#include <netgraph/ng_socket.h>
#include <netgraph/ng_iface.h>
#include <netgraph/ng_vjc.h>
#endif

/*
 * DEFINITIONS
 */

  /* #define DEBUG_BOD */

  #define BUND_REOPEN_DELAY	3	/* wait this long before closing */
  #define BUND_REOPEN_PAUSE	3	/* wait this long before re-opening */

  #define BUND_MIN_TOT_BW	9600

  /* Set menu options */
  enum {
    SET_PERIOD,
    SET_LOW_WATER,
    SET_HIGH_WATER,
    SET_MIN_CONNECT,
    SET_MIN_DISCONNECT,
    SET_AUTHNAME,
    SET_PASSWORD,
    SET_RETRY,
    SET_ACCEPT,
    SET_DENY,
    SET_ENABLE,
    SET_DISABLE,
    SET_YES,
    SET_NO,
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int	BundNgInit(Bund b, const char *reqIface);
  static void	BundNgShutdown(Bund b, int iface, int ppp);

  static void	BundNgDataEvent(int type, void *cookie);

  static void	BundBmStart(Bund b);
  static void	BundBmStop(Bund b);
  static void	BundBmTimeout(void *arg);

  static Bund	BundFind(char *name);
  static void	BundReasses(Bund b, int add);
  static int	BundSetCommand(Context ctx, int ac, char *av[], void *arg);
  static void	BundShowLinks(Context ctx, Bund sb);

  static void	BundNcpsUp(Bund b);
  static void	BundNcpsDown(Bund b);

  static void	BundReOpenLinks(void *arg);
  static void	BundCloseLink(Link l);

  static void	BundMsg(int type, void *cookie);

/*
 * GLOBAL VARIABLES
 */

  struct discrim	self_discrim;

  const struct cmdtab BundSetCmds[] = {
    { "period seconds",			"BOD sampling period",
	BundSetCommand, NULL, (void *) SET_PERIOD },
    { "lowat percent",			"BOD low water mark",
	BundSetCommand, NULL, (void *) SET_LOW_WATER },
    { "hiwat percent",			"BOD high water mark",
	BundSetCommand, NULL, (void *) SET_HIGH_WATER },
    { "min-con seconds",		"BOD min connected time",
	BundSetCommand, NULL, (void *) SET_MIN_CONNECT },
    { "min-dis seconds",		"BOD min disconnected time",
	BundSetCommand, NULL, (void *) SET_MIN_DISCONNECT },
    { "retry seconds",			"FSM retry timeout",
	BundSetCommand, NULL, (void *) SET_RETRY },
    { "accept [opt ...]",		"Accept option",
	BundSetCommand, NULL, (void *) SET_ACCEPT },
    { "deny [opt ...]",			"Deny option",
	BundSetCommand, NULL, (void *) SET_DENY },
    { "enable [opt ...]",		"Enable option",
	BundSetCommand, NULL, (void *) SET_ENABLE },
    { "disable [opt ...]",		"Disable option",
	BundSetCommand, NULL, (void *) SET_DISABLE },
    { "yes [opt ...]",			"Enable and accept option",
	BundSetCommand, NULL, (void *) SET_YES },
    { "no [opt ...]",			"Disable and deny option",
	BundSetCommand, NULL, (void *) SET_NO },
    { NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static const struct confinfo	gConfList[] = {
    { 0,	BUND_CONF_MULTILINK,	"multilink"	},
    { 1,	BUND_CONF_SHORTSEQ,	"shortseq"	},
    { 0,	BUND_CONF_IPCP,		"ipcp"		},
    { 0,	BUND_CONF_IPV6CP,	"ipv6cp"	},
    { 0,	BUND_CONF_COMPRESSION,	"compression"	},
    { 0,	BUND_CONF_ENCRYPTION,	"encryption"	},
    { 0,	BUND_CONF_CRYPT_REQD,	"crypt-reqd"	},
    { 0,	BUND_CONF_BWMANAGE,	"bw-manage"	},
    { 0,	BUND_CONF_ROUNDROBIN,	"round-robin"	},
    { 0,	BUND_CONF_NORETRY,	"noretry"	},
    { 0,	0,			NULL		},
  };

/*
 * BundOpen()
 */

void
BundOpen(Bund b)
{
  MsgSend(b->msgs, MSG_OPEN, b);
}

/*
 * BundClose()
 */

void
BundClose(Bund b)
{
  MsgSend(b->msgs, MSG_CLOSE, b);
}

/*
 * BundOpenCmd()
 */

void
BundOpenCmd(Context ctx)
{
  MsgSend(ctx->bund->msgs, MSG_OPEN, ctx->bund);
}

/*
 * BundCloseCmd()
 */

void
BundCloseCmd(Context ctx)
{
  MsgSend(ctx->bund->msgs, MSG_CLOSE, ctx->bund);
}

/*
 * BundJoin()
 *
 * This is called when a link enters the NETWORK phase.
 *
 * Verify that link is OK to come up as part of it's bundle.
 * If so, join it to the bundle. Returns FALSE if there's a problem.
 * If this is the first link to join, and it's not supporting
 * multi-link, then prevent any further links from joining.
 *
 * Right now this is fairly simple minded: you have to define
 * the links in a bundle first, then stick to that plan. For
 * a server this might be too restrictive a policy.
 *
 * Returns zero if fails, otherwise the new number of up links.
 */

int
BundJoin(Link l)
{
    Bund	b = l->bund;
    BundBm	const bm = &l->bund->bm;
    LcpState	const lcp = &l->lcp;

    if (gShutdownInProgress) {
	Log(LG_PHYS, ("Shutdown sequence in progress, BundJoin() denied"));
        return(0);
    }

  if (!b->open) b->open = TRUE; /* Open bundle on incoming */

  /* Other links in this bundle yet? If so, enforce bundling */
  if (bm->n_up > 0) {

    /* First of all, we have to be doing multi-link */
    if (!b->multilink || !lcp->peer_multilink) {
      Log(LG_LCP,
	("[%s] multi-link is not active on this bundle", l->name));
      return(0);
    }

    /* Discriminator and authname must match */
    if (!MpDiscrimEqual(&l->peer_discrim, &b->peer_discrim)) {
      Log(LG_LCP,
	("[%s] multi-link peer discriminator mismatch", l->name));
      return(0);
    }
    if (strcmp(l->lcp.auth.params.authname, b->params.authname)) {
      Log(LG_LCP,
	("[%s] multi-link peer authorization name mismatch", l->name));
      return(0);
    }
  } else {

    /* Cancel re-open timer; we've come up somehow (eg, LCP renegotiation) */
    TimerStop(&b->reOpenTimer);

    /* Copy auth params from the first link */
    authparamsCopy(&l->lcp.auth.params,&b->params);

    /* Initialize multi-link stuff */
    if ((b->multilink = lcp->peer_multilink)) {
      b->peer_discrim = l->peer_discrim;
      MpInit(b, l);
    }

    /* Start bandwidth management */
    BundBmStart(b);
  }

  /* Reasses MTU, bandwidth, etc. */
  BundReasses(b, 1);

  /* Configure this link */
  b->pppConfig.links[l->bundleIndex].enableLink = 1;
  b->pppConfig.links[l->bundleIndex].mru = lcp->peer_mru;
  b->pppConfig.links[l->bundleIndex].enableACFComp = lcp->peer_acfcomp;
  b->pppConfig.links[l->bundleIndex].enableProtoComp = lcp->peer_protocomp;
  b->pppConfig.links[l->bundleIndex].bandwidth = (l->bandwidth / 8 + 5) / 10;
  b->pppConfig.links[l->bundleIndex].latency = (l->latency + 500) / 1000;

  /* What to do when the first link comes up */
  if (bm->n_up == 1) {

    /* Configure the bundle */
    b->pppConfig.bund.enableMultilink = lcp->peer_multilink;
    b->pppConfig.bund.mrru = lcp->peer_mrru;
    b->pppConfig.bund.xmitShortSeq = lcp->peer_shortseq;
    b->pppConfig.bund.recvShortSeq = lcp->want_shortseq;
    b->pppConfig.bund.enableRoundRobin =
      Enabled(&b->conf.options, BUND_CONF_ROUNDROBIN);

    /* generate a uniq session id */
    snprintf(b->msession_id, AUTH_MAX_SESSIONID, "%d-%s",
      (int)(time(NULL) % 10000000), b->name);
      
    b->originate = l->originate;
  }

  /* Update PPP node configuration */
  NgFuncSetConfig(b);

  /* copy multysession-id to link */
  strncpy(l->msession_id, b->msession_id,
    sizeof(l->msession_id));

  /* generate a uniq session id */
  snprintf(l->session_id, AUTH_MAX_SESSIONID, "%d-%s",
    (int)(time(NULL) % 10000000), l->name);

  /* What to do when the first link comes up */
  if (bm->n_up == 1) {

    BundNcpsOpen(b);

    BundNcpsUp(b);

    BundResetStats(b);

#ifndef NG_PPP_STATS64    
    /* starting bundle statistics timer */
    TimerInit(&b->statsUpdateTimer, "BundUpdateStats", 
	BUND_STATS_UPDATE_INTERVAL, BundUpdateStatsTimer, b);
    TimerStartRecurring(&b->statsUpdateTimer);
#endif
  }

  AuthAccountStart(l, AUTH_ACCT_START);

  /* Done */
  return(bm->n_up);
}

/*
 * BundLeave()
 *
 * This is called when a link leaves the NETWORK phase.
 */

void
BundLeave(Link l)
{
    Bund	b = l->bund;
  BundBm	const bm = &b->bm;

  /* Elvis has left the bundle */
  assert(bm->n_up > 0);
  
  AuthAccountStart(l, AUTH_ACCT_STOP);

  BundReasses(b, 0);
  
  /* Disable link */
  b->pppConfig.links[l->bundleIndex].enableLink = 0;
  NgFuncSetConfig(b);

  /* Special stuff when last link goes down... */
  if (bm->n_up == 0) {
  
#ifndef NG_PPP_STATS64
    /* stopping bundle statistics timer */
    TimerStop(&b->statsUpdateTimer);
#endif

    /* Reset statistics and auth information */
    BundBmStop(b);

    BundNcpsClose(b);
    BundNcpsDown(b);

    authparamsDestroy(&b->params);
    memset(&b->ccp.mppc, 0, sizeof(b->ccp.mppc));
 
    /* try to open again later */
    if (b->open && !Enabled(&b->conf.options, BUND_CONF_NORETRY) && !gShutdownInProgress) {
	/* wait BUND_REOPEN_DELAY to see if it comes back up */
      int delay = BUND_REOPEN_DELAY;
      delay += ((random() ^ gPid ^ time(NULL)) & 1);
      Log(LG_BUND, ("[%s] Last link has gone and no noretry option, will reopen in %d seconds", 
        b->name, delay));
      TimerStop(&b->reOpenTimer);
      TimerInit(&b->reOpenTimer, "BundReOpen",
	delay * SECONDS, BundReOpenLinks, b);
      TimerStart(&b->reOpenTimer);
    } else if (b->open) {
	b->open = FALSE;
    }
  }
}

/*
 * BundReOpenLinks()
 *
 * The last link went down, and we waited BUND_REOPEN_DELAY seconds for
 * it to come back up. It didn't, so close all the links and re-open them
 * BUND_REOPEN_PAUSE seconds from now.
 *
 * The timer calling this is cancelled whenever any link comes up.
 */

static void
BundReOpenLinks(void *arg)
{
    Bund b = (Bund)arg;
    
  Log(LG_BUND, ("[%s] Last link has gone and no noretry option, reopening in %d seconds", b->name, BUND_REOPEN_PAUSE));
  BundCloseLinks(b);
  TimerStop(&b->reOpenTimer);
  TimerInit(&b->reOpenTimer, "BundOpen",
    BUND_REOPEN_PAUSE * SECONDS, (void (*)(void *)) BundOpenLinks, b);
  TimerStart(&b->reOpenTimer);
  RecordLinkUpDownReason(b, NULL, 1, STR_REDIAL, NULL);
}

/*
 * BundMsg()
 *
 * Deal with incoming message to the bundle
 */

static void
BundMsg(int type, void *arg)
{
    Bund	b = (Bund)arg;

  Log(LG_BUND, ("[%s] bundle: %s event in state %s",
    b->name, MsgName(type), b->open ? "OPENED" : "CLOSED"));
  TimerStop(&b->reOpenTimer);
  switch (type) {
    case MSG_OPEN:
      b->open = TRUE;
      break;

    case MSG_CLOSE:
      b->open = FALSE;
      BundCloseLinks(b);
      break;

    default:
      assert(FALSE);
  }
}

/*
 * BundOpenLinks()
 *
 * Open one link or all links, depending on whether bandwidth
 * management is in effect or not.
 */

void
BundOpenLinks(Bund b)
{
  TimerStop(&b->reOpenTimer);
  if (Enabled(&b->conf.options, BUND_CONF_BWMANAGE)) {
    if (!b->bm.links_open || b->bm.n_open == 0)
      BundOpenLink(b->links[0]);
  } else {
    int	k;

    for (k = 0; k < b->n_links; k++)
      BundOpenLink(b->links[k]);
  }
}

/*
 * BundOpenLink()
 */

void
BundOpenLink(Link l)
{
  Log(LG_BUND, ("[%s] opening link \"%s\"...", l->bund->name, l->name));
  LinkOpen(l);
  l->bund->bm.links_open = 1;
}

/*
 * BundCloseLinks()
 *
 * Close all links
 */

void
BundCloseLinks(Bund b)
{
  int	k;

  TimerStop(&b->reOpenTimer);
  for (k = 0; k < b->n_links; k++)
    if (OPEN_STATE(b->links[k]->lcp.fsm.state))
      BundCloseLink(b->links[k]);
  b->bm.links_open = 0;
}

/*
 * BundCloseLink()
 */

static void
BundCloseLink(Link l)
{
  Log(LG_BUND, ("[%s] closing link \"%s\"...", l->bund->name, l->name));
  LinkClose(l);
}

/*
 * BundNcpsOpen()
 */

void
BundNcpsOpen(Bund b)
{
  if (Enabled(&b->conf.options, BUND_CONF_IPCP))
    IpcpOpen(b);
  if (Enabled(&b->conf.options, BUND_CONF_IPV6CP))
    Ipv6cpOpen(b);
  if (Enabled(&b->conf.options, BUND_CONF_COMPRESSION))
    CcpOpen(b);
  if (Enabled(&b->conf.options, BUND_CONF_ENCRYPTION))
    EcpOpen(b);
}

/*
 * BundNcpsUp()
 */

static void
BundNcpsUp(Bund b)
{
  if (Enabled(&b->conf.options, BUND_CONF_IPCP))
    IpcpUp(b);
  if (Enabled(&b->conf.options, BUND_CONF_IPV6CP))
    Ipv6cpUp(b);
  if (Enabled(&b->conf.options, BUND_CONF_COMPRESSION))
    CcpUp(b);
  if (Enabled(&b->conf.options, BUND_CONF_ENCRYPTION))
    EcpUp(b);
}

void
BundNcpsStart(Bund b, int proto)
{
    b->ncpstarted |= ((1<<proto)>>1);
}

void
BundNcpsFinish(Bund b, int proto)
{
    b->ncpstarted &= (~((1<<proto)>>1));
    if (!b->ncpstarted) {
	Log(LG_BUND, ("[%s] No NCPs left. Closing links...", b->name));
	RecordLinkUpDownReason(b, NULL, 0, STR_PROTO_ERR, NULL);
	BundCloseLinks(b); /* We have nothing to live for */
    }
}

void
BundNcpsJoin(Bund b, int proto)
{
	IfaceState	iface = &b->iface;

	if (iface->dod) {
		if (iface->ip_up) {
			iface->ip_up=0;
			IfaceIpIfaceDown(b);
		}
		if (iface->ipv6_up) {
			iface->ipv6_up=0;
			IfaceIpv6IfaceDown(b);
		}
		iface->dod = 0;
		iface->up = 0;
		IfaceDown(b);
	}
    
	if (!iface->up) {
		iface->up=1;
		if (proto == NCP_NONE) {
			iface->dod=1;
			IfaceUp(b, 0);
		} else {
			IfaceUp(b, 1);
		}
	}

	switch(proto) {
	case NCP_IPCP:
		if (!iface->ip_up) {
			iface->ip_up=1;
			IfaceIpIfaceUp(b, 1);
		}
		break;
	case NCP_IPV6CP:
		if (!iface->ipv6_up) {
			iface->ipv6_up=1;
			IfaceIpv6IfaceUp(b, 1);
		}
		break;
	case NCP_NONE: /* Manual call by 'open iface' */
		if (Enabled(&b->conf.options, BUND_CONF_IPCP) &&
		    iface->dod && !iface->ip_up) {
			iface->ip_up=1;
			IfaceIpIfaceUp(b, 0);
		}
		if (Enabled(&b->conf.options, BUND_CONF_IPV6CP) &&
		    iface->dod && !iface->ipv6_up) {
			iface->ipv6_up=1;
			IfaceIpv6IfaceUp(b, 0);
		}
		break;
	}
}

void
BundNcpsLeave(Bund b, int proto)
{
	IfaceState	iface = &b->iface;
	switch(proto) {
	case NCP_IPCP:
		if (iface->ip_up && !iface->dod) {
			iface->ip_up=0;
			IfaceIpIfaceDown(b);
		}
		break;
	case NCP_IPV6CP:
		if (iface->ipv6_up && !iface->dod) {
			iface->ipv6_up=0;
			IfaceIpv6IfaceDown(b);
		}
		break;
	case NCP_NONE:
		if (iface->ip_up && iface->dod) {
			iface->ip_up=0;
			IfaceIpIfaceDown(b);
		}
		if (iface->ipv6_up && iface->dod) {
			iface->ipv6_up=0;
			IfaceIpv6IfaceDown(b);
		}
		break;
	}
    
	if ((iface->up) && (!iface->ip_up) && (!iface->ipv6_up)) {
		iface->dod=0;
		iface->up=0;
		IfaceDown(b);
    		if (iface->open) {
			iface->dod=1;
			iface->up=1;
			IfaceUp(b, 0);
			if (Enabled(&b->conf.options, BUND_CONF_IPCP)) {
				iface->ip_up=1;
				IfaceIpIfaceUp(b, 0);
			}
			if (Enabled(&b->conf.options, BUND_CONF_IPV6CP)) {
				iface->ipv6_up=1;
				IfaceIpv6IfaceUp(b, 0);
			}
		}
	}
}

/*
 * BundNcpsDown()
 */

static void
BundNcpsDown(Bund b)
{
  if (Enabled(&b->conf.options, BUND_CONF_IPCP))
    IpcpDown(b);
  if (Enabled(&b->conf.options, BUND_CONF_IPV6CP))
    Ipv6cpDown(b);
  if (Enabled(&b->conf.options, BUND_CONF_COMPRESSION))
    CcpDown(b);
  if (Enabled(&b->conf.options, BUND_CONF_ENCRYPTION))
    EcpDown(b);
}

/*
 * BundNcpsClose()
 */

void
BundNcpsClose(Bund b)
{
  if (Enabled(&b->conf.options, BUND_CONF_IPCP))
    IpcpClose(b);
  if (Enabled(&b->conf.options, BUND_CONF_IPV6CP))
    Ipv6cpClose(b);
  if (Enabled(&b->conf.options, BUND_CONF_COMPRESSION))
    CcpClose(b);
  if (Enabled(&b->conf.options, BUND_CONF_ENCRYPTION))
    EcpClose(b);
}

/*
 * BundReasses()
 *
 * Here we do a reassessment of things after a new link has been
 * added to or removed from the bundle.
 */

static void
BundReasses(Bund b, int add)
{
  BundBm	const bm = &b->bm;

  /* Add or subtract link */
  if (add)
    bm->n_up++;
  else
    bm->n_up--;

  /* Update system interface parameters */
  BundUpdateParams(b);

  Log(LG_BUND, ("[%s] Bundle up: %d link%s, total bandwidth %d bps",
    b->name, bm->n_up, bm->n_up == 1 ? "" : "s", bm->total_bw));

}

/*
 * BundUpdateParams()
 *
 * Recalculate interface MTU and bandwidth.
 */

void
BundUpdateParams(Bund b)
{
  BundBm	const bm = &b->bm;
  int		k, mtu, the_link = 0;

  /* Recalculate how much bandwidth we have */
  for (bm->total_bw = k = 0; k < b->n_links; k++) {
    if (b->links[k]->lcp.phase == PHASE_NETWORK) {
      bm->total_bw += b->links[k]->bandwidth;
      the_link = k;
    }
  }
  if (bm->total_bw < BUND_MIN_TOT_BW)
    bm->total_bw = BUND_MIN_TOT_BW;

  /* Recalculate MTU corresponding to peer's MRU */
  switch (bm->n_up) {
    case 0:
      mtu = NG_IFACE_MTU_DEFAULT;	/* Reset to default settings */
      break;
    case 1:
      if (!b->multilink) {		/* If no multilink, use peer MRU */
	mtu = MIN(b->links[the_link]->lcp.peer_mru,
		  b->links[the_link]->phys->type->mtu);
	break;
      }
      /* FALLTHROUGH */
    default:			/* We fragment everything, use bundle MRRU */
      mtu = b->mp.peer_mrru;
      break;
  }

  /* Subtract to make room for various frame-bloating protocols */
  if (bm->n_up > 0) {
    if (Enabled(&b->conf.options, BUND_CONF_COMPRESSION))
      mtu = CcpSubtractBloat(b, mtu);
    if (Enabled(&b->conf.options, BUND_CONF_ENCRYPTION))
      mtu = EcpSubtractBloat(b, mtu);
  }

  /* Update interface MTU */
  IfaceSetMTU(b, mtu);
}

/*
 * BundCommand()
 *
 * Show list of all bundles or set bundle
 */

int
BundCommand(Context ctx, int ac, char *av[], void *arg)
{
  Bund	sb;
  int	k;

  switch (ac) {
    case 0:

      #define BUND_FMT "\t%-15s"

      Printf("Defined bundles:\r\n");
      Printf(BUND_FMT "Links\r\n", "Bundle");
      Printf(BUND_FMT "-----\r\n", "------");

      for (k = 0; k < gNumBundles; k++)
	if ((sb = gBundles[k]) != NULL) {
	  Printf(BUND_FMT, sb->name);
	  BundShowLinks(ctx, sb);
	}
      break;

    case 1:

      /* Change bundle, and link also if needed */
      if ((sb = BundFind(av[0])) != NULL) {
	ctx->bund = sb;
	if (ctx->lnk == NULL || ctx->lnk->bund != ctx->bund) {
	    ctx->lnk = ctx->bund->links[0];
	}
	ctx->phys = ctx->lnk->phys;
	ctx->rep = NULL;
      } else {
	Printf("Bundle \"%s\" not defined.\r\n", av[0]);
	ctx->lnk = NULL;
	ctx->bund = NULL;
	ctx->phys = NULL;
	ctx->rep = NULL;
      }
      break;

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

/*
 * MSessionCommand()
 */

int
MSessionCommand(Context ctx, int ac, char *av[], void *arg)
{
    int		k;

    if (ac != 1)
	return(-1);

    /* Find link */
    for (k = 0;
	k < gNumBundles && (gBundles[k] == NULL || 
	    strcmp(gBundles[k]->msession_id, av[0]));
	k++);
    if (k == gNumBundles) {
	Printf("MultySession \"%s\" is not found\r\n", av[0]);
	/* Change default link and bundle */
	ctx->lnk = NULL;
	ctx->bund = NULL;
	ctx->phys = NULL;
	ctx->rep = NULL;
    } else {
	/* Change default link and bundle */
	ctx->bund = gBundles[k];
	if (ctx->lnk == NULL || ctx->lnk->bund != ctx->bund) {
	    ctx->lnk = ctx->bund->links[0];
	}
	ctx->phys = ctx->lnk->phys;
	ctx->rep = NULL;
    }

    return(0);
}

/*
 * BundCreateCmd()
 *
 * Create a new bundle. If some of the links can't be added,
 * then we just use the ones that could.
 */

int
BundCreateCmd(Context ctx, int ac, char *av[], void *arg)
{
  Bund	b;
  Link	new_link;
  char	*reqIface = NULL;
  u_char tee = 0;
  u_char netflow_in = 0;
  u_char netflow_out = 0;
  u_char nat = 0;
  int	k;

  /* Args */
  if (ac < 2)
    return(-1);

  if (ac > 0 && av[0][0] == '-') {
    optreset = 1; 
    optind = 0;
    while ((k = getopt(ac, av, "nNati:")) != -1) {
      switch (k) {
      case 'i':
	reqIface = optarg;
	break;
      case 't':
	tee = 1;
	break;
      case 'n':
#ifdef USE_NG_NETFLOW
	netflow_in = 1;
#endif
	break;
      case 'N':
#ifdef USE_NG_NETFLOW
	netflow_out = 1;
#endif
	break;
      case 'a':
#ifdef USE_NG_NAT
	nat = 1;
#endif
	break;
      default:
	return (-1);
      }
    }
    ac -= optind;
    av += optind;
  }

#if NG_NODESIZ>=32
  if (strlen(av[0])>16) {
#else
  if (strlen(av[0])>6) {
#endif
    Log(LG_ERR, ("bundle name \"%s\" is too long", av[0]));
    return(0);
  }

  /* See if bundle name already taken */
  if ((b = BundFind(av[0])) != NULL) {
    Log(LG_ERR, ("bundle \"%s\" already exists", av[0]));
    return(0);
  }

  /* Create a new bundle structure */
  b = Malloc(MB_BUND, sizeof(*b));
  snprintf(b->name, sizeof(b->name), "%s", av[0]);
  b->csock = b->dsock = -1;

  /* Setup netgraph stuff */
  if (BundNgInit(b, reqIface) < 0) {
    Log(LG_ERR, ("[%s] netgraph initialization failed", b->name));
    Freee(MB_BUND, b);
    return(0);
  }

  /* Create each link and add it to the bundle */
  b->links = Malloc(MB_LINK, (ac - 1) * sizeof(*b->links));
  for (k = 1; k < ac; k++) {
#if NG_NODESIZ>=32
    if (strlen(av[k])>16) {
#else
    if (strlen(av[k])>6) {
#endif
	Log(LG_ERR, ("link name \"%s\" is too long", av[k]));
	BundShutdown(b);
	return(0);
    }
    if ((new_link = LinkNew(av[k], b, b->n_links)) == NULL)
      Log(LG_ERR, ("[%s] addition of link \"%s\" failed", av[0], av[k]));
    else {
      b->links[b->n_links] = new_link;
      b->n_links++;
    }
  }

  /* We need at least one link in the bundle */
  if (b->n_links == 0) {
    Log(LG_ERR, ("bundle \"%s\" creation failed: no links", av[0]));
    BundShutdown(b);
    return(0);
  }

  /* Add bundle to the list of bundles and make it the current active bundle */
  for (k = 0; k < gNumBundles && gBundles[k] != NULL; k++);
  if (k == gNumBundles)			/* add a new bundle pointer */
    LengthenArray(&gBundles, sizeof(*gBundles), &gNumBundles, MB_BUND);

  b->id = k;
  gBundles[k] = b;

  /* Init interface stuff */
  IfaceInit(b);

  if (tee)
    Enable(&b->iface.options, IFACE_CONF_TEE);
  if (nat)
    Enable(&b->iface.options, IFACE_CONF_NAT);
  if (netflow_in)
    Enable(&b->iface.options, IFACE_CONF_NETFLOW_IN);
  if (netflow_out)
    Enable(&b->iface.options, IFACE_CONF_NETFLOW_OUT);

  /* Get message channel */
  b->msgs = MsgRegister(BundMsg);

  /* Initialize bundle configuration */
  b->conf.mrru = MP_DEFAULT_MRRU;
  b->conf.retry_timeout = BUND_DEFAULT_RETRY;
  b->conf.bm_S = BUND_BM_DFL_S;
  b->conf.bm_Hi = BUND_BM_DFL_Hi;
  b->conf.bm_Lo = BUND_BM_DFL_Lo;
  b->conf.bm_Mc = BUND_BM_DFL_Mc;
  b->conf.bm_Md = BUND_BM_DFL_Md;

  if (b->n_links > 1)
    Enable(&b->conf.options, BUND_CONF_MULTILINK);
  Enable(&b->conf.options, BUND_CONF_SHORTSEQ);
  Accept(&b->conf.options, BUND_CONF_SHORTSEQ);

  Enable(&b->conf.options, BUND_CONF_IPCP);
  Disable(&b->conf.options, BUND_CONF_IPV6CP);

  Disable(&b->conf.options, BUND_CONF_BWMANAGE);
  Disable(&b->conf.options, BUND_CONF_COMPRESSION);
  Disable(&b->conf.options, BUND_CONF_ENCRYPTION);
  Disable(&b->conf.options, BUND_CONF_CRYPT_REQD);
  
  Enable(&b->conf.options, BUND_CONF_NORETRY);

  /* Init NCP's */
  IpcpInit(b);
  Ipv6cpInit(b);
  CcpInit(b);
  EcpInit(b);
  
  ctx->bund = b;
  ctx->lnk = b->links[0];
  ctx->phys = b->links[0]->phys;
  ctx->rep = NULL;
  
  /* Done */
  return(0);
}

/*
 * BundShutdown()
 *
 * Shutdown the netgraph stuff associated with bundle
 */

void
BundShutdown(Bund b)
{
  Link	l;
  int	k;

  for (k = 0; k < b->n_links; k++) {
    l = b->links[k];
    if (l)
	LinkShutdown(l);
  }
  Freee(MB_LINK, b->links);

  BundNgShutdown(b, 1, 1);
  gBundles[b->id] = NULL;
  Freee(MB_BUND, b);
}

/*
 * BundStat()
 *
 * Show state of a bundle
 */

int
BundStat(Context ctx, int ac, char *av[], void *arg)
{
  Bund	sb;
  int	k, bw, tbw, nup;
  char	buf[64];

  /* Find bundle they're talking about */
  switch (ac) {
    case 0:
      sb = ctx->bund;
      break;
    case 1:
      if ((sb = BundFind(av[0])) == NULL) {
	Printf("Bundle \"%s\" not defined.\r\n", av[0]);
	return(0);
      }
      break;
    default:
      return(-1);
  }

  /* Show stuff about the bundle */
  for (tbw = bw = nup = k = 0; k < sb->n_links; k++) {
    if (sb->links[k]->lcp.phase == PHASE_NETWORK) {
      nup++;
      bw += sb->links[k]->bandwidth;
    }
    tbw += sb->links[k]->bandwidth;
  }

  Printf("Bundle %s:\r\n", sb->name);
  Printf("\tLinks          : ");
  BundShowLinks(ctx, sb);
  Printf("\tStatus         : %s\r\n", sb->open ? "OPEN" : "CLOSED");
  Printf("\tM-Session-Id   : %s\r\n", sb->msession_id);
  Printf("\tTotal bandwidth: %u bits/sec\r\n", tbw);
  Printf("\tAvail bandwidth: %u bits/sec\r\n", bw);
  Printf("\tPeer authname  : \"%s\"\r\n", sb->params.authname);
  Printf("\tPeer discrim.  : %s\r\n", MpDiscrimText(&sb->peer_discrim, buf, sizeof(buf)));

  /* Show configuration */
  Printf("Configuration:\r\n");
  Printf("\tMy MRRU        : %d bytes\r\n", sb->conf.mrru);
  Printf("\tRetry timeout  : %d seconds\r\n", sb->conf.retry_timeout);
  Printf("\tBW-manage:\r\n");
  Printf("\t  Period       : %d seconds\r\n", sb->conf.bm_S);
  Printf("\t  Low mark     : %d%%\r\n", sb->conf.bm_Lo);
  Printf("\t  High mark    : %d%%\r\n", sb->conf.bm_Hi);
  Printf("\t  Min conn     : %d seconds\r\n", sb->conf.bm_Mc);
  Printf("\t  Min disc     : %d seconds\r\n", sb->conf.bm_Md);
  Printf("Bundle level options:\r\n");
  OptStat(ctx, &sb->conf.options, gConfList);

  /* Show peer info */
  if (sb->bm.n_up > 0) {
    Printf("Multilink PPP:\r\n");
    Printf("\tStatus         : %s\r\n",
      sb->multilink ? "Active" : "Inactive\r\n");
    if (sb->multilink) {
      Printf("\tPeer auth name : \"%s\"\r\n", sb->params.authname);
      Printf("\tPeer discrimin.: %s\r\n", MpDiscrimText(&sb->peer_discrim, buf, sizeof(buf)));
    }
  }

  /* Show stats */
  BundUpdateStats(ctx->bund);
  Printf("Traffic stats:\r\n");

  Printf("\tOctets input   : %llu\r\n", (unsigned long long)ctx->bund->stats.recvOctets);
  Printf("\tFrames input   : %llu\r\n", (unsigned long long)ctx->bund->stats.recvFrames);
  Printf("\tOctets output  : %llu\r\n", (unsigned long long)ctx->bund->stats.xmitOctets);
  Printf("\tFrames output  : %llu\r\n", (unsigned long long)ctx->bund->stats.xmitFrames);
  Printf("\tBad protocols  : %llu\r\n", (unsigned long long)ctx->bund->stats.badProtos);
  Printf("\tRunts          : %llu\r\n", (unsigned long long)ctx->bund->stats.runts);
  Printf("\tDup fragments  : %llu\r\n", (unsigned long long)ctx->bund->stats.dupFragments);
  Printf("\tDrop fragments : %llu\r\n", (unsigned long long)ctx->bund->stats.dropFragments);

  return(0);
}

/* 
 * BundUpdateStats()
 */

void
BundUpdateStats(Bund b)
{
#ifndef NG_PPP_STATS64
  struct ng_ppp_link_stat	stats;
#endif
  int	l = NG_PPP_BUNDLE_LINKNUM;

#if (__FreeBSD_version < 602104 || (__FreeBSD_version >= 700000 && __FreeBSD_version < 700029))
  /* Workaround for broken ng_ppp bundle stats */
  if (!b->multilink)
    l = 0;
#endif

#ifndef NG_PPP_STATS64
  if (NgFuncGetStats(b, l, &stats) != -1) {
    b->stats.xmitFrames += abs(stats.xmitFrames - b->oldStats.xmitFrames);
    b->stats.xmitOctets += abs(stats.xmitOctets - b->oldStats.xmitOctets);
    b->stats.recvFrames += abs(stats.recvFrames - b->oldStats.recvFrames);
    b->stats.recvOctets += abs(stats.recvOctets - b->oldStats.recvOctets);
    b->stats.badProtos  += abs(stats.badProtos - b->oldStats.badProtos);
    b->stats.runts	  += abs(stats.runts - b->oldStats.runts);
    b->stats.dupFragments += abs(stats.dupFragments - b->oldStats.dupFragments);
    b->stats.dropFragments += abs(stats.dropFragments - b->oldStats.dropFragments);
  }

  b->oldStats = stats;
#else
    NgFuncGetStats64(b, l, &b->stats);
#endif
}

/* 
 * BundUpdateStatsTimer()
 */

void
BundUpdateStatsTimer(void *cookie)
{
    Bund	b = (Bund)cookie;
    int		k;
  
    BundUpdateStats(b);
    for (k = 0; k < b->n_links; k++) {
	if (b->links[k]->joined_bund)
	    LinkUpdateStats(b->links[k]);
    }
}

/*
 * BundResetStats()
 */

void
BundResetStats(Bund b)
{
  NgFuncClrStats(b, NG_PPP_BUNDLE_LINKNUM);
  memset(&b->stats, 0, sizeof(b->stats));
#ifndef NG_PPP_STATS64
  memset(&b->oldStats, 0, sizeof(b->oldStats));
#endif
}

/*
 * BundShowLinks()
 */

static void
BundShowLinks(Context ctx, Bund sb)
{
    int		j;

  for (j = 0; j < sb->n_links; j++) {
    Printf("%s", sb->links[j]->name);
    if (!sb->links[j]->phys->type)
      Printf("[no type] ");
    else
      Printf("[%s/%s] ", FsmStateName(sb->links[j]->lcp.fsm.state),
	gPhysStateNames[sb->links[j]->phys->state]);
  }
  Printf("\r\n");
}

/*
 * BundFind()
 *
 * Find a bundle structure
 */

static Bund
BundFind(char *name)
{
  int	k;

  for (k = 0;
    k < gNumBundles && (!gBundles[k] || strcmp(gBundles[k]->name, name));
    k++);
  return((k < gNumBundles) ? gBundles[k] : NULL);
}

/*
 * BundBmStart()
 *
 * Start bandwidth management timer
 */

static void
BundBmStart(Bund b)
{
  int	k;

  /* Reset bandwidth management stats */
  for (k = 0; k < b->n_links; k++) {
    memset(&b->links[k]->bm.traffic, 0, sizeof(b->links[k]->bm.traffic));
    memset(&b->links[k]->bm.wasUp, 0, sizeof(b->links[k]->bm.wasUp));
    memset(&b->links[k]->bm.idleStats,
      0, sizeof(b->links[k]->bm.idleStats));
  }

  /* Start bandwidth management timer */
  TimerStop(&b->bm.bmTimer);
  if (Enabled(&b->conf.options, BUND_CONF_BWMANAGE)) {
    TimerInit(&b->bm.bmTimer, "BundBm",
      b->conf.bm_S * SECONDS / LINK_BM_N,
      BundBmTimeout, b);
    TimerStart(&b->bm.bmTimer);
  }
}

/*
 * BundBmStop()
 */

static void
BundBmStop(Bund b)
{
  TimerStop(&b->bm.bmTimer);
}

/*
 * BundBmTimeout()
 *
 * Do a bandwidth management update
 */

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

  const time_t	now = time(NULL);
  u_int		availTotal;
  u_int		inUtilTotal = 0, outUtilTotal = 0;
  u_int		inBitsTotal, outBitsTotal;
  u_int		inUtil[LINK_BM_N];	/* Incoming % utilization */
  u_int		outUtil[LINK_BM_N];	/* Outgoing % utilization */
  int		j, k;

  /* Shift and update stats */
  for (k = 0; k < b->n_links; k++) {
    Link	const l = b->links[k];

    /* Shift stats */
    memmove(&l->bm.wasUp[1], &l->bm.wasUp[0],
      (LINK_BM_N - 1) * sizeof(l->bm.wasUp[0]));
    l->bm.wasUp[0] = (l->lcp.fsm.state == ST_OPENED);
    memmove(&l->bm.traffic[0][1], &l->bm.traffic[0][0],
      (LINK_BM_N - 1) * sizeof(l->bm.traffic[0][0]));
    memmove(&l->bm.traffic[1][1], &l->bm.traffic[1][0],
      (LINK_BM_N - 1) * sizeof(l->bm.traffic[1][0]));
    if (!l->bm.wasUp[0]) {
      l->bm.traffic[0][0] = 0;
      l->bm.traffic[1][0] = 0;
    } else {
      struct ng_ppp_link_stat	oldStats;

      /* Get updated link traffic statistics */
      oldStats = l->bm.idleStats;
      NgFuncGetStats(l->bund, l->bundleIndex, &l->bm.idleStats);
      l->bm.traffic[0][0] = l->bm.idleStats.recvOctets - oldStats.recvOctets;
      l->bm.traffic[1][0] = l->bm.idleStats.xmitOctets - oldStats.xmitOctets;
    }
  }

  /* Compute utilizations */
  memset(&inUtil, 0, sizeof(inUtil));
  memset(&outUtil, 0, sizeof(outUtil));
  for (availTotal = inBitsTotal = outBitsTotal = j = 0; j < LINK_BM_N; j++) {
    u_int	avail, inBits, outBits;

    /* Sum up over all links */
    for (avail = inBits = outBits = k = 0; k < b->n_links; k++) {
      Link	const l = b->links[k];

      if (l->bm.wasUp[j]) {
	avail += (l->bandwidth * b->conf.bm_S) / LINK_BM_N;
	inBits += l->bm.traffic[0][j] * 8;
	outBits += l->bm.traffic[1][j] * 8;
      }
    }
    availTotal += avail;
    inBitsTotal += inBits;
    outBitsTotal += outBits;

    /* Compute bandwidth utilizations as percentages */
    if (avail != 0) {
      inUtil[j] = ((float) inBits / avail) * 100;
      outUtil[j] = ((float) outBits / avail) * 100;
    }
  }

  /* Compute total averaged utilization */
  if (availTotal != 0) {
    inUtilTotal = ((float) inBitsTotal / availTotal) * 100;
    outUtilTotal = ((float) outBitsTotal / availTotal) * 100;
  }

#ifdef DEBUG_BOD
  {
    char	ins[100], outs[100];

    snprintf(ins, sizeof(ins), ">>Link status:             ");
    for (j = 0; j < LINK_BM_N; j++) {
      for (k = 0; k < b->n_links; k++) {
	Link	const l = b->links[k];

	snprintf(ins + strlen(ins), sizeof(ins) - strlen(ins),
	  l->bm.wasUp[LINK_BM_N - 1 - j] ? "Up" : "Dn");
      }
      snprintf(ins + strlen(ins), sizeof(ins) - strlen(ins), " ");
    }
    Log(LG_BUND, ("%s", ins));

    snprintf(ins, sizeof(ins), " IN util: total %3u%%  ", inUtilTotal);
    snprintf(outs, sizeof(outs), "OUT util: total %3u%%  ", outUtilTotal);
    for (j = 0; j < LINK_BM_N; j++) {
      snprintf(ins + strlen(ins), sizeof(ins) - strlen(ins),
	" %3u%%", inUtil[LINK_BM_N - 1 - j]);
      snprintf(outs + strlen(outs), sizeof(outs) - strlen(outs),
	" %3u%%", outUtil[LINK_BM_N - 1 - j]);
    }
    Log(LG_BUND, ("  %s", ins));
    Log(LG_BUND, ("  %s", outs));
  }
#endif

  /* See if it's time to bring up another link */
  if (now - b->bm.last_open >= b->conf.bm_Mc
      && (inUtilTotal >= b->conf.bm_Hi || outUtilTotal >= b->conf.bm_Hi)
      && b->bm.n_open < b->n_links) {
    k = 0;
    while (k < b->n_links && OPEN_STATE(b->links[k]->lcp.fsm.state))
	k++;
    assert(k < b->n_links);
    Log(LG_BUND, ("[%s] opening link %s due to increased demand",
      b->name, b->links[k]->name));
    b->bm.last_open = now;
    RecordLinkUpDownReason(NULL, b->links[k], 1, STR_PORT_NEEDED, NULL);
    BundOpenLink(b->links[k]);
  }

  /* See if it's time to bring down a link */
  if (now - b->bm.last_close >= b->conf.bm_Md
      && (inUtilTotal < b->conf.bm_Lo && outUtilTotal < b->conf.bm_Lo)
      && b->bm.n_up > 1) {
    k = b->n_links - 1;
    while (k >= 0 && !OPEN_STATE(b->links[k]->lcp.fsm.state))
	k--;
    assert(k >= 0);
    Log(LG_BUND, ("[%s] closing link %s due to reduced demand",
      b->name, b->links[k]->name));
    b->bm.last_close = now;
    RecordLinkUpDownReason(NULL, b->links[k], 0, STR_PORT_UNNEEDED, NULL);
    BundCloseLink(b->links[k]);
  }

  /* Restart timer */
  TimerStart(&b->bm.bmTimer);
}

/*
 * BundNgInit()
 *
 * Setup the initial PPP netgraph framework. Initializes these fields
 * in the supplied bundle structure:
 *
 *	iface.ifname	- Interface name
 *	csock		- Control socket for socket netgraph node
 *	dsock		- Data socket for socket netgraph node
 *
 * Returns -1 if error.
 */

static int
BundNgInit(Bund b, const char *reqIface)
{
  union {
      u_char		buf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
      struct ng_mesg	reply;
  }			u;
  struct nodeinfo	*const ni = (struct nodeinfo *)(void *)u.reply.data;
  struct ngm_mkpeer	mp;
  struct ngm_name	nm;
  int			newIface = 0;
  int			newPpp = 0;

  /* Create a netgraph socket node */
  if (NgMkSockNode(NULL, &b->csock, &b->dsock) < 0) {
    Log(LG_ERR, ("[%s] can't create %s node: %s",
      b->name, NG_SOCKET_NODE_TYPE, strerror(errno)));
    return(-1);
  }
  (void) fcntl(b->csock, F_SETFD, 1);
  (void) fcntl(b->dsock, F_SETFD, 1);

#if NG_NODESIZ>=32
  /* Give it a name */
  snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-so", gPid, b->name);
  if (NgSendMsg(b->csock, ".",
      NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
    Log(LG_ERR, ("[%s] can't name %s node: %s",
      b->name, NG_SOCKET_NODE_TYPE, strerror(errno)));
    goto fail;
  }
#endif

  /* Create new iface node if necessary, else find the one specified */
  if (reqIface != NULL) {
    switch (NgFuncIfaceExists(b,
	reqIface, b->iface.ifname, sizeof(b->iface.ifname))) {
    case -1:			/* not a netgraph interface */
      Log(LG_ERR, ("[%s] interface \"%s\" is not a netgraph interface",
	b->name, reqIface));
      goto fail;
      break;
    case 0:			/* interface does not exist */
      if (NgFuncCreateIface(b,
	  reqIface, b->iface.ifname, sizeof(b->iface.ifname)) < 0) {
	Log(LG_ERR, ("[%s] can't create interface \"%s\"", b->name, reqIface));
	goto fail;
      }
      break;
    case 1:			/* interface exists */
      break;
    default:
      assert(0);
    }
  } else {
    if (NgFuncCreateIface(b,
	NULL, b->iface.ifname, sizeof(b->iface.ifname)) < 0) {
      Log(LG_ERR, ("[%s] can't create netgraph interface", b->name));
      goto fail;
    }
    newIface = 1;
  }
  b->iface.ifindex = if_nametoindex(b->iface.ifname);
 
  /* Create new PPP node */
  snprintf(mp.type, sizeof(mp.type), "%s", NG_PPP_NODE_TYPE);
  snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", MPD_HOOK_PPP);
  snprintf(mp.peerhook, sizeof(mp.peerhook), "%s", NG_PPP_HOOK_BYPASS);
  if (NgSendMsg(b->csock, ".",
      NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
    Log(LG_ERR, ("[%s] can't create %s node at \"%s\"->\"%s\": %s",
      b->name, mp.type, ".", mp.ourhook, strerror(errno)));
    goto fail;
  }
  newPpp = 1;

  /* Give it a name */
  snprintf(nm.name, sizeof(nm.name), "mpd%d-%s", gPid, b->name);
  if (NgSendMsg(b->csock, MPD_HOOK_PPP,
      NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
    Log(LG_ERR, ("[%s] can't name %s node \"%s\": %s",
      b->name, NG_PPP_NODE_TYPE, MPD_HOOK_PPP, strerror(errno)));
    goto fail;
  }

  /* Get PPP node ID */
  if (NgSendMsg(b->csock, MPD_HOOK_PPP,
      NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) < 0) {
    Log(LG_ERR, ("[%s] ppp nodeinfo: %s", b->name, strerror(errno)));
    goto fail;
  }
  if (NgRecvMsg(b->csock, &u.reply, sizeof(u), NULL) < 0) {
    Log(LG_ERR, ("[%s] node \"%s\" reply: %s",
      b->name, MPD_HOOK_PPP, strerror(errno)));
    goto fail;
  }
  b->nodeID = ni->id;

  /* Listen for happenings on our node */
  EventRegister(&b->dataEvent, EVENT_READ,
    b->dsock, EVENT_RECURRING, BundNgDataEvent, b);
  /* Control events used only by CCP so Register events there */

  /* OK */
  return(0);

fail:
  BundNgShutdown(b, newIface, newPpp);
  return(-1);
}

/*
 * NgFuncShutdown()
 */

void
BundNgShutdown(Bund b, int iface, int ppp)
{
  char	path[NG_PATHLEN + 1];

  if (iface) {
    snprintf(path, sizeof(path), "%s:", b->iface.ifname);
    NgFuncShutdownNode(b->csock, b->name, path);
  }
  if (ppp) {
    NgFuncShutdownNode(b->csock, b->name, MPD_HOOK_PPP);
  }
  close(b->csock);
  b->csock = -1;
  EventUnRegister(&b->ctrlEvent);
  close(b->dsock);
  b->dsock = -1;
  EventUnRegister(&b->dataEvent);
}


/*
 * BundNgDataEvent()
 */

static void
BundNgDataEvent(int type, void *cookie)
{
    Bund	b = (Bund)cookie;
  u_char		buf[8192];
  struct sockaddr_ng	naddr;
  int			nread;
  socklen_t		nsize = sizeof(naddr);
  Mbuf 			nbp;

  /* Read data */
  if ((nread = recvfrom(b->dsock, buf, sizeof(buf),
      0, (struct sockaddr *)&naddr, &nsize)) < 0) {
    if (errno == EAGAIN)
      return;
    Log(LG_BUND, ("[%s] socket read: %s", b->name, strerror(errno)));
    DoExit(EX_ERRDEAD);
  }

  /* A PPP frame from the bypass hook? */
  if (strcmp(naddr.sg_data, MPD_HOOK_PPP) == 0) {
    u_int16_t	linkNum, proto;

    /* Extract link number and protocol */
    memcpy(&linkNum, buf, 2);
    linkNum = ntohs(linkNum);
    memcpy(&proto, buf + 2, 2);
    proto = ntohs(proto);

    /* Debugging */
    LogDumpBuf(LG_FRAME, buf, nread,
      "[%s] rec'd bypass frame link=%d proto=0x%04x",
      b->name, (int16_t)linkNum, proto);

    /* Set link */
    assert(linkNum == NG_PPP_BUNDLE_LINKNUM || linkNum < b->n_links);

    /* Input frame */
    InputFrame(b, linkNum, proto,
      mbufise(MB_FRAME_IN, buf + 4, nread - 4));
    return;
  }

  /* A snooped, outgoing IP frame? */
  if (strcmp(naddr.sg_data, MPD_HOOK_DEMAND_TAP) == 0) {

    /* Debugging */
    LogDumpBuf(LG_FRAME, buf, nread,
      "[%s] rec'd IP frame on demand/mssfix-in hook", b->name);
    IfaceListenInput(b, PROTO_IP,
      mbufise(MB_FRAME_IN, buf, nread));
    return;
  }
#ifndef USE_NG_TCPMSS
  /* A snooped, outgoing TCP SYN frame? */
  if (strcmp(naddr.sg_data, MPD_HOOK_TCPMSS_OUT) == 0) {
    /* Debugging */
    LogDumpBuf(LG_FRAME, buf, nread,
      "[%s] rec'd IP frame on mssfix-out hook", b->name);
    nbp = mbufise(MB_FRAME_IN, buf, nread);
    IfaceCorrectMSS(nbp, MAXMSS(b->iface.mtu));
    NgFuncWriteFrame(b, MPD_HOOK_TCPMSS_IN, nbp);
    return;
  }
  /* A snooped, incoming TCP SYN frame? */
  if (strcmp(naddr.sg_data, MPD_HOOK_TCPMSS_IN) == 0) {
    /* Debugging */
    LogDumpBuf(LG_FRAME, buf, nread,
      "[%s] rec'd IP frame on mssfix-in hook", b->name);
    nbp = mbufise(MB_FRAME_IN, buf, nread);
    IfaceCorrectMSS(nbp, MAXMSS(b->iface.mtu));
    NgFuncWriteFrame(b, MPD_HOOK_TCPMSS_OUT, nbp);
    return;
  }
#endif

  /* Packet requiring compression */
  if (strcmp(naddr.sg_data, NG_PPP_HOOK_COMPRESS) == 0) {

    /* Debugging */
    LogDumpBuf(LG_FRAME, buf, nread,
      "[%s] rec'd frame on %s hook", b->name, NG_PPP_HOOK_COMPRESS);

    nbp = CcpDataOutput(b, mbufise(MB_COMP, buf, nread));
    if (nbp)
	NgFuncWriteFrame(b, NG_PPP_HOOK_COMPRESS, nbp);

    return;
  }

  /* Packet requiring decompression */
  if (strcmp(naddr.sg_data, NG_PPP_HOOK_DECOMPRESS) == 0) {
    /* Debugging */
    LogDumpBuf(LG_FRAME, buf, nread,
      "[%s] rec'd frame on %s hook", b->name, NG_PPP_HOOK_DECOMPRESS);

    nbp = CcpDataInput(b, mbufise(MB_COMP, buf, nread));
    if (nbp)
	NgFuncWriteFrame(b, NG_PPP_HOOK_DECOMPRESS, nbp);

    return;
  }

  /* Packet requiring encryption */
  if (strcmp(naddr.sg_data, NG_PPP_HOOK_ENCRYPT) == 0) {

    /* Debugging */
    LogDumpBuf(LG_FRAME, buf, nread,
      "[%s] rec'd frame on %s hook", b->name, NG_PPP_HOOK_ENCRYPT);

    nbp = EcpDataOutput(b, mbufise(MB_CRYPT, buf, nread));
    if (nbp)
	NgFuncWriteFrame(b, NG_PPP_HOOK_ENCRYPT, nbp);

    return;
  }

  /* Packet requiring decryption */
  if (strcmp(naddr.sg_data, NG_PPP_HOOK_DECRYPT) == 0) {
    /* Debugging */
    LogDumpBuf(LG_FRAME, buf, nread,
      "[%s] rec'd frame on %s hook", b->name, NG_PPP_HOOK_DECRYPT);

    nbp = EcpDataInput(b, mbufise(MB_CRYPT, buf, nread));
    if (nbp) 
	NgFuncWriteFrame(b, NG_PPP_HOOK_DECRYPT, nbp);

    return;
  }

  /* Unknown hook! */
  LogDumpBuf(LG_FRAME, buf, nread,
    "[%s] rec'd data on unknown hook \"%s\"", b->name, naddr.sg_data);
  DoExit(EX_ERRDEAD);
}

/*
 * BundNgCtrlEvent()
 *
 */

void
BundNgCtrlEvent(int type, void *cookie)
{
    Bund	b = (Bund)cookie;
  union {
      u_char		buf[8192];
      struct ng_mesg	msg;
  }			u;
  char			raddr[NG_PATHLEN + 1];
  int			len;

  /* Read message */
  if ((len = NgRecvMsg(b->csock, &u.msg, sizeof(u), raddr)) < 0) {
    Log(LG_ERR, ("[%s] can't read unexpected message: %s",
      b->name, strerror(errno)));
    return;
  }

  /* Examine message */
  switch (u.msg.header.typecookie) {

    case NGM_MPPC_COOKIE:
#ifdef USE_NG_DEFLATE
    case NGM_DEFLATE_COOKIE:
#endif
#ifdef USE_NG_PRED1
    case NGM_PRED1_COOKIE:
#endif
      CcpRecvMsg(b, &u.msg, len);
      return;

    default:
      break;
  }

  /* Unknown message */
  Log(LG_ERR, ("[%s] rec'd unknown ctrl message, cookie=%d cmd=%d",
    b->name, u.msg.header.typecookie, u.msg.header.cmd));
}


/*
 * BundSetCommand()
 */

static int
BundSetCommand(Context ctx, int ac, char *av[], void *arg)
{
    Bund	b = ctx->bund;

  if (ac == 0)
    return(-1);
  switch ((intptr_t)arg) {
    case SET_PERIOD:
      b->conf.bm_S = atoi(*av);
      break;
    case SET_LOW_WATER:
      b->conf.bm_Lo = atoi(*av);
      break;
    case SET_HIGH_WATER:
      b->conf.bm_Hi = atoi(*av);
      break;
    case SET_MIN_CONNECT:
      b->conf.bm_Mc = atoi(*av);
      break;
    case SET_MIN_DISCONNECT:
      b->conf.bm_Md = atoi(*av);
      break;

    case SET_RETRY:
      b->conf.retry_timeout = atoi(*av);
      if (b->conf.retry_timeout < 1 || b->conf.retry_timeout > 10)
	b->conf.retry_timeout = BUND_DEFAULT_RETRY;
      break;

    case SET_ACCEPT:
      AcceptCommand(ac, av, &b->conf.options, gConfList);
      break;

    case SET_DENY:
      DenyCommand(ac, av, &b->conf.options, gConfList);
      break;

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

    case SET_DISABLE:
      DisableCommand(ac, av, &b->conf.options, gConfList);
      if (!Enabled(&b->conf.options, BUND_CONF_MULTILINK)
	  && b->n_links > 1) {
	Log(LG_ERR, ("[%s] multilink option required for %d links",
	  b->name, b->n_links));
	Enable(&b->conf.options, BUND_CONF_MULTILINK);
      }
      break;

    case SET_YES:
      YesCommand(ac, av, &b->conf.options, gConfList);
      break;

    case SET_NO:
      NoCommand(ac, av, &b->conf.options, gConfList);
      break;

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



syntax highlighted by Code2HTML, v. 0.9.1