
/*
 * 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 <netgraph/ng_iface.h>

/*
 * DEFINITIONS
 */

  /* #define DEBUG_BOD */

  #define BUND_MSG_TIMEOUT	3
  #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_MAX_LOGINS,
    SET_RETRY,
    SET_ACCEPT,
    SET_DENY,
    SET_ENABLE,
    SET_DISABLE,
    SET_YES,
    SET_NO,
  };

/*
 * INTERNAL FUNCTIONS
 */

  static void	BundBmStart(void);
  static void	BundBmStop(void);
  static void	BundMsgTimeout(void *arg);
  static void	BundBmTimeout(void *arg);

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

  static void	BundUpNcps(void);
  static void	BundDownNcps(void);

  static void	BundOpenLinks(void);
  static void	BundOpenLink(Link l);
  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 },
    { "authname name",			"Authentication name",
	BundSetCommand, NULL, (void *) SET_AUTHNAME },
    { "password pass",			"Authentication password",
	BundSetCommand, NULL, (void *) SET_PASSWORD },
    { "max-logins num",			"Max concurrent logins",
	BundSetCommand, NULL, (void *) SET_MAX_LOGINS },
    { "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_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_RADIUSAUTH,	"radius-auth"	},
    { 0,	BUND_CONF_RADIUSFALLBACK,	"radius-fallback"	},
    { 0,	BUND_CONF_RADIUSACCT,	"radius-acct"	},    
    { 0,	BUND_CONF_NORETRY,	"noretry"	},
    { 0,	BUND_CONF_TCPWRAPPER,	"tcp-wrapper"	},
    { 0,	0,			NULL		},
  };

/*
 * BundOpen()
 */

void
BundOpen(void)
{
  MsgSend(bund->msgs, MSG_OPEN, NULL);
}

/*
 * BundClose()
 */

void
BundClose(void)
{
  MsgSend(bund->msgs, MSG_CLOSE, NULL);
}

/*
 * 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(void)
{
  BundBm	const bm = &bund->bm;
  LcpState	const lcp = &lnk->lcp;

  /* 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 (!bund->multilink || !lcp->peer_multilink) {
      Log(LG_LCP,
	("[%s] multi-link is not active on this bundle", lnk->name));
      return(0);
    }

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

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

    /* Record peer's authname */
    strcpy(bund->peer_authname, lnk->peer_authname);

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

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

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

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

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

    /* Copy over peer's IP address range if specified in secrets file */
    if (lnk->range_valid)
      bund->peer_allow = lnk->peer_allow;
    else
      memset(&bund->peer_allow, 0, sizeof(bund->peer_allow));

    /* Make sure IPCP, et.al. renegotiate */
    if (bm->ncps_up)
      BundDownNcps();
    BundUpNcps();

    /* Configure the bundle */
#if NGM_PPP_COOKIE < 940897794
    bund->pppConfig.enableMultilink = lcp->peer_multilink;
    bund->pppConfig.mrru = lcp->peer_mrru;
    bund->pppConfig.xmitShortSeq = lcp->peer_shortseq;
    bund->pppConfig.recvShortSeq = lcp->want_shortseq;
    bund->pppConfig.enableRoundRobin =
      Enabled(&bund->conf.options, BUND_CONF_ROUNDROBIN);
#else
    bund->pppConfig.bund.enableMultilink = lcp->peer_multilink;
    bund->pppConfig.bund.mrru = lcp->peer_mrru;
    bund->pppConfig.bund.xmitShortSeq = lcp->peer_shortseq;
    bund->pppConfig.bund.recvShortSeq = lcp->want_shortseq;
    bund->pppConfig.bund.enableRoundRobin =
      Enabled(&bund->conf.options, BUND_CONF_ROUNDROBIN);
#endif
  }

  /* Update PPP node configuration */
  NgFuncSetConfig();
  
  if (Enabled(&bund->conf.options, BUND_CONF_RADIUSACCT)) {
    u_long updateInterval = 0;

    RadiusAccount(RAD_START);

    if (bund->radius.interim_interval > 0)
      updateInterval = bund->radius.interim_interval;
    else if (bund->radius.conf.acct_update > 0)
      updateInterval = bund->radius.conf.acct_update;

    if (updateInterval > 0) {
      TimerInit(&lnk->radius.radUpdate, "RadiusAcctUpdate",
	updateInterval * SECONDS, RadiusAcctUpdate, NULL);
      TimerStart(&lnk->radius.radUpdate);
    }
  }

  /* starting link statistics timer */
  TimerInit(&lnk->stats.updateTimer, "LinkUpdateStats", 
    LINK_STATS_UPDATE_INTERVAL, LinkUpdateStatsTimer, NULL);
  TimerStart(&lnk->stats.updateTimer);

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

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

void
BundLeave(void)
{
  BundBm	const bm = &bund->bm;

  /* Elvis has left the bundle */
  assert(bm->n_up > 0);
  
  /* stopping link statistics timer */
  TimerStop(&lnk->stats.updateTimer);

  if (Enabled(&bund->conf.options, BUND_CONF_RADIUSACCT)) 
  {
    TimerStop(&lnk->radius.radUpdate);
    RadiusAccount(RAD_STOP);
  }
  BundReasses(0);
  
  /* Disable link */
  bund->pppConfig.links[lnk->bundleIndex].enableLink = 0;
  NgFuncSetConfig();

  /* Special stuff when last link goes down... */
  if (bm->n_up == 0) {
  
    if (Enabled(&bund->conf.options, BUND_CONF_RADIUSAUTH) || 
        Enabled(&bund->conf.options, BUND_CONF_RADIUSACCT)) 
      RadiusDown();

    /* Reset statistics and auth information */
    BundBmStop();
    if (bm->ncps_up)
      BundDownNcps();
    memset(bund->peer_msChal, 0, sizeof(bund->peer_msChal));
    memset(bund->self_msChal, 0, sizeof(bund->self_msChal));
    memset(bund->peer_authname, 0, sizeof(bund->peer_authname));
    memset(bund->msPassword, 0, sizeof(bund->msPassword));
    memset(bund->peer_ntResp, 0, sizeof(bund->peer_ntResp));
    memset(bund->self_ntResp, 0, sizeof(bund->self_ntResp));

    /* Close links, or else wait and try to open again later */
    if (!bund->open || Enabled(&bund->conf.options, BUND_CONF_NORETRY)) {
      BundCloseLinks();
      if (Enabled(&bund->conf.options, BUND_CONF_NORETRY))
	IfaceCloseNcps();
    } else {		/* wait BUND_REOPEN_DELAY to see if it comes back up */
      TimerStop(&bund->reOpenTimer);
      TimerInit(&bund->reOpenTimer, "BundReOpen",
	BUND_REOPEN_DELAY * SECONDS, BundReOpenLinks, NULL);
      TimerStart(&bund->reOpenTimer);
    }
  }
}

/*
 * 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)
{
  BundCloseLinks();
  TimerStop(&bund->reOpenTimer);
  TimerInit(&bund->reOpenTimer, "BundOpen",
    BUND_REOPEN_PAUSE * SECONDS, (void (*)(void *)) BundOpenLinks, NULL);
  TimerStart(&bund->reOpenTimer);
  RecordLinkUpDownReason(NULL, 1, STR_REDIAL, NULL);
}

/*
 * BundLinkGaveUp()
 *
 * This is called when one of our links we've told to open has
 * been unable to do so and is now giving up (due to a maximum
 * consecutive redial limitation, or whatever). This may result
 * in us closing the whole bundle.
 */

void
BundLinkGaveUp(void)
{
  int	k;

  /* Close this link */
  BundCloseLink(lnk);

  /* If links are not supposed to be open anyway, do nothing */
  if (!bund->bm.links_open)
    return;

  /* See if any other links are still open; if not, close down everything */
  for (k = 0; k < bund->n_links; k++) {
    if (bund->links[k] != lnk && OPEN_STATE(bund->links[k]->lcp.fsm.state))
      break;
  }
  if (k == bund->n_links)
    IfaceCloseNcps();
}

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

static void
BundMsg(int type, void *arg)
{
  Log(LG_BUND, ("[%s] bundle: %s event in state %s",
    bund->name, MsgName(type), bund->open ? "OPENED" : "CLOSED"));
  TimerStop(&bund->reOpenTimer);
  switch (type) {
    case MSG_OPEN:
      bund->open = TRUE;
      BundOpenLinks();
      break;

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

    default:
      assert(FALSE);
  }
}

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

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

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

/*
 * BundOpenLink()
 */

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

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

void
BundCloseLinks(void)
{
  int	k;

  TimerStop(&bund->reOpenTimer);
  for (k = 0; k < bund->n_links; k++)
    if (OPEN_STATE(bund->links[k]->lcp.fsm.state))
      BundCloseLink(bund->links[k]);
  TimerStop(&bund->msgTimer);
  TimerInit(&bund->msgTimer, "BundMsg",
    BUND_MSG_TIMEOUT * SECONDS, BundMsgTimeout, NULL);
  TimerStart(&bund->msgTimer);
  bund->bm.links_open = 0;
}

/*
 * BundCloseLink()
 */

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

/*
 * BundUpNcps()
 */

static void
BundUpNcps(void)
{
  IpcpUp();
  if (Enabled(&bund->conf.options, BUND_CONF_COMPRESSION)) {
    CcpOpen();
    CcpUp();
  }
  if (Enabled(&bund->conf.options, BUND_CONF_ENCRYPTION)) {
    EcpOpen();
    EcpUp();
  }
  bund->bm.ncps_up = TRUE;
}

/*
 * BundDownNcps()
 */

static void
BundDownNcps(void)
{
  IpcpDown();
  if (bund->ccp.fsm.state != ST_INITIAL) {
    CcpDown();
    CcpClose();
  }
  if (bund->ecp.fsm.state != ST_INITIAL) {
    EcpDown();
    EcpClose();
  }
  bund->bm.ncps_up = FALSE;
}

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

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

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

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

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

  /* Do any custom stuff */
#ifdef IA_CUSTOM
  CustomLinkStateChange(add);
#endif
}

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

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

  /* Recalculate how much bandwidth we have */
  for (bm->total_bw = k = 0; k < bund->n_links; k++) {
    if (bund->links[k]->lcp.phase == PHASE_NETWORK) {
      bm->total_bw += bund->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 (!bund->multilink) {		/* If no multilink, use peer MRU */
	mtu = MIN(bund->links[the_link]->lcp.peer_mru,
		  bund->links[the_link]->phys->type->mtu);
	break;
      }
      /* FALLTHROUGH */
    default:			/* We fragment everything, use bundle MRRU */
      mtu = bund->mp.peer_mrru;
      break;
  }

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

  /* Update interface MTU */
  IfaceSetMTU(mtu, bm->total_bw);
}

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

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

  switch (ac) {
    case 0:

      #define BUND_FMT "\t%-15s"

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

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

    case 1:

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

    default:
      return(-1);
  }
  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(int ac, char *av[], void *arg)
{
  Bund	old_bund = bund;
  Link	new_link;
  char	*reqIface = NULL;
  int	k;

  /* Args */
  if (ac < 2)
    return(-1);
  if (ac > 0 && !strcmp(av[0], "-i")) {
    ac--, av++;
    if (ac == 0)
      return(-1);
    reqIface = av[0];
    ac--, av++;
  }

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

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

  /* Setup netgraph stuff */
  if (NgFuncInit(bund, reqIface) < 0) {
    Log(LG_ERR, ("[%s] netgraph initialization failed", bund->name));
    goto fail2;
  }

  /* Create each link and add it to the bundle */
  bund->links = Malloc(MB_BUND, (ac - 1) * sizeof(*bund->links));
  for (k = 1; k < ac; k++) {
    if ((new_link = LinkNew(av[k])) == NULL)
      Log(LG_ERR, ("[%s] addition of link \"%s\" failed", av[0], av[k]));
    else {
      new_link->bund = bund;
      new_link->bundleIndex = bund->n_links;
      bund->links[bund->n_links] = new_link;
      bund->n_links++;
    }
  }

  /* We need at least one link in the bundle */
  if (bund->n_links == 0) {
    Log(LG_ERR, ("mpd: bundle \"%s\" creation failed: no links", av[0]));
    Freee(bund->links);
    NgFuncShutdown(bund);
fail2:
    Freee(bund);
fail:
    bund = old_bund;
    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);
  gBundles[k] = bund;

  /* Init interface stuff */
  IfaceInit();

  /* Get message channel */
  bund->msgs = MsgRegister(BundMsg, BUND_PRIO);

  /* Initialize bundle configuration */
  bund->conf.mrru = MP_DEFAULT_MRRU;
  bund->conf.retry_timeout = BUND_DEFAULT_RETRY;
  bund->conf.bm_S = BUND_BM_DFL_S;
  bund->conf.bm_Hi = BUND_BM_DFL_Hi;
  bund->conf.bm_Lo = BUND_BM_DFL_Lo;
  bund->conf.bm_Mc = BUND_BM_DFL_Mc;
  bund->conf.bm_Md = BUND_BM_DFL_Md;
  bund->conf.max_logins = 0;	/* unlimited concurrent logins */

  Enable(&bund->conf.options, BUND_CONF_MULTILINK);
  Enable(&bund->conf.options, BUND_CONF_SHORTSEQ);
  Accept(&bund->conf.options, BUND_CONF_SHORTSEQ);

  Disable(&bund->conf.options, BUND_CONF_BWMANAGE);
  Disable(&bund->conf.options, BUND_CONF_COMPRESSION);
  Disable(&bund->conf.options, BUND_CONF_ENCRYPTION);
  Disable(&bund->conf.options, BUND_CONF_CRYPT_REQD);
  Disable(&bund->conf.options, BUND_CONF_RADIUSAUTH);
  Disable(&bund->conf.options, BUND_CONF_RADIUSFALLBACK);

  /* Init NCP's */
  IpcpInit();
  CcpInit();
  EcpInit();

  /* Done */
  return(0);
}

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

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

  /* Find bundle they're talking about */
  switch (ac) {
    case 0:
      sb = bund;
      break;
    case 1:
      if ((sb = BundFind(av[0])) == NULL) {
	printf("Bundle \"%s\" not defined.\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:\n", sb->name);
  printf("\tLinks          : ");
  BundShowLinks(sb);
  printf("\tStatus         : %s\n", sb->open ? "OPEN" : "CLOSED");
  printf("\tTotal bandwidth: %u\n", tbw);
  printf("\tAvail bandwidth: %u\n", bw);
  printf("\tPeer authname  : \"%s\"\n", sb->peer_authname);
  printf("\tPeer discrim.  : %s\n", MpDiscrimText(&sb->peer_discrim));

  /* Show configuration */
  printf("Configuration:\n");
  printf("\tMy auth name   : \"%s\"\n", sb->conf.authname);
  printf("\tMy MRRU        : %d bytes\n", sb->conf.mrru);
  printf("\tRetry timeout  : %d seconds\n", sb->conf.retry_timeout);
  printf("\tSample period  : %d seconds\n", sb->conf.bm_S);
  printf("\tLow water mark : %d%%\n", sb->conf.bm_Lo);
  printf("\tHigh water mark: %d%%\n", sb->conf.bm_Hi);
  printf("\tMin connected  : %d seconds\n", sb->conf.bm_Mc);
  printf("\tMax connected  : %d seconds\n", sb->conf.bm_Md);
  printf("\tMax logins     : %ld\n", sb->conf.max_logins);
  printf("Bundle level options:\n");
  OptStat(&sb->conf.options, gConfList);

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

  /* Show stats */
  LinkUpdateStats();
  printf("Traffic stats:\n");

  printf("\tOctets input   : %llu\n", lnk->stats.recvOctets);
  printf("\tFrames input   : %llu\n", lnk->stats.recvFrames);
  printf("\tOctets output  : %llu\n", lnk->stats.xmitOctets);
  printf("\tFrames output  : %llu\n", lnk->stats.xmitFrames);
  printf("\tBad protocols  : %llu\n", lnk->stats.badProtos);
#if NGM_PPP_COOKIE >= 940897794
  printf("\tRunts          : %llu\n", lnk->stats.runts);
#endif
  printf("\tDup fragments  : %llu\n", lnk->stats.dupFragments);
#if NGM_PPP_COOKIE >= 940897794
  printf("\tDrop fragments : %llu\n", lnk->stats.dropFragments);
#endif

  return(0);
}

/*
 * BundShowLinks()
 */

static void
BundShowLinks(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),
	PhysState(sb->links[j]->phys));
  }
  printf("\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);
}

/*
 * BundMsgTimeout()
 */

static void
BundMsgTimeout(void *arg)
{
  SetStatus(ADLG_WAN_WAIT_FOR_DEMAND, STR_READY_TO_DIAL);
}

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

static void
BundBmStart(void)
{
  int	k;

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

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

/*
 * BundBmStop()
 */

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

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

static void
BundBmTimeout(void *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 < bund->n_links; k++) {
    Link	const l = bund->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->bundleIndex, FALSE, &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 < bund->n_links; k++) {
      Link	const l = bund->links[k];

      if (l->bm.wasUp[j]) {
	avail += (l->bandwidth * bund->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 < bund->n_links; k++) {
	Link	const l = bund->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), " ");
    }
    LogConsole("%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]);
    }
    LogConsole("  %s", ins);
    LogConsole("  %s", outs);
  }
#endif

  /* See if it's time to bring up another link */
  if (now - bund->bm.last_close >= bund->conf.bm_Md
      && (inUtilTotal >= bund->conf.bm_Hi || outUtilTotal >= bund->conf.bm_Hi)
      && bund->bm.n_open < bund->n_links) {
    for (k = 0; k < bund->n_links; k++) {
      if (!OPEN_STATE(bund->links[k]->lcp.fsm.state)) {
	Log(LG_BUND, ("[%s] opening link %s due to increased demand",
	  bund->name, bund->links[k]->name));
	BundOpenLink(bund->links[k]);
	break;
      }
    }
  }

  /* See if it's time to bring down a link */
  if (bund->bm.n_up > 1) {
    for (j = 0; j < LINK_BM_N; j++) {
      if (inUtil[j] + outUtil[j] >= 2 * bund->conf.bm_Lo)
	break;
    }
    if (j == LINK_BM_N) {
      for (k = 0; k < bund->n_links; k++) {
	if (OPEN_STATE(bund->links[k]->lcp.fsm.state)
	    && (now - bund->links[k]->bm.last_open < bund->conf.bm_Mc))
	  break;
      }
      if (k == bund->n_links) {
	while (--k >= 0 && !OPEN_STATE(bund->links[k]->lcp.fsm.state));
	assert(k >= 0);
	Log(LG_BUND, ("[%s] closing link %s due to reduced demand",
	  bund->name, bund->links[k]->name));
	BundCloseLink(bund->links[k]);
      }
    }
  }

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

/*
 * BundSetCommand()
 */

static int
BundSetCommand(int ac, char *av[], void *arg)
{
  if (ac == 0)
    return(-1);
  switch ((intptr_t)arg) {
    case SET_PERIOD:
      bund->conf.bm_S = atoi(*av);
      break;
    case SET_LOW_WATER:
      bund->conf.bm_Lo = atoi(*av);
      break;
    case SET_HIGH_WATER:
      bund->conf.bm_Hi = atoi(*av);
      break;
    case SET_MIN_CONNECT:
      bund->conf.bm_Mc = atoi(*av);
      break;
    case SET_MIN_DISCONNECT:
      bund->conf.bm_Md = atoi(*av);
      break;

    case SET_AUTHNAME:
      snprintf(bund->conf.authname, sizeof(bund->conf.authname), "%s", *av);
      break;

    case SET_PASSWORD:
      snprintf(bund->conf.password, sizeof(bund->conf.password), "%s", *av);
      break;

    case SET_MAX_LOGINS:
      bund->conf.max_logins = atoi(*av);
      break;

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

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

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

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

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

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

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

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

