File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / mpd / src / bund.c
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 00:39:23 2021 UTC (3 years, 2 months ago) by misho
Branches: mpd, MAIN
CVS tags: v5_9p16, v5_9, HEAD
mpd 5.9


/*
 * 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 "ngfunc.h"
#include "log.h"
#include "util.h"
#include "input.h"

#include <netgraph.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_socket.h>
#include <netgraph/ng_iface.h>
#ifdef USE_NG_VJC
#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_LINKS,
    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);
  static void	BundNgShutdown(Bund b, int iface, int ppp);

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

  static void	BundReasses(Bund b);
  static int	BundSetCommand(Context ctx, int ac, const char *const av[], const void *arg);

  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, 2, (void *) SET_PERIOD },
    { "lowat {percent}",		"BoD low water mark",
	BundSetCommand, NULL, 2, (void *) SET_LOW_WATER },
    { "hiwat {percent}",		"BoD high water mark",
	BundSetCommand, NULL, 2, (void *) SET_HIGH_WATER },
    { "min-con {seconds}",		"BoD min connected time",
	BundSetCommand, NULL, 2, (void *) SET_MIN_CONNECT },
    { "min-dis {seconds}",		"BoD min disconnected time",
	BundSetCommand, NULL, 2, (void *) SET_MIN_DISCONNECT },
    { "links {link list ...}",		"Links list for BoD/DoD",
	BundSetCommand, NULL, 2, (void *) SET_LINKS },
    { "fsm-timeout {seconds}",		"FSM retry timeout",
	BundSetCommand, NULL, 2, (void *) SET_RETRY },
    { "accept {opt ...}",		"Accept option",
	BundSetCommand, NULL, 2, (void *) SET_ACCEPT },
    { "deny {opt ...}",			"Deny option",
	BundSetCommand, NULL, 2, (void *) SET_DENY },
    { "enable {opt ...}",		"Enable option",
	BundSetCommand, NULL, 2, (void *) SET_ENABLE },
    { "disable {opt ...}",		"Disable option",
	BundSetCommand, NULL, 2, (void *) SET_DISABLE },
    { "yes {opt ...}",			"Enable and accept option",
	BundSetCommand, NULL, 2, (void *) SET_YES },
    { "no {opt ...}",			"Disable and deny option",
	BundSetCommand, NULL, 2, (void *) SET_NO },
    { NULL, NULL, NULL, NULL, 0, NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static const struct confinfo	gConfList[] = {
    { 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,	0,			NULL		},
  };

/*
 * BundOpen()
 */

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

/*
 * BundClose()
 */

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

/*
 * BundOpenCmd()
 */

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

/*
 * BundCloseCmd()
 */

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

/*
 * 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, bt;
    LcpState	const lcp = &l->lcp;
    int		k;

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

    if (!l->bund) {
	b = NULL;
	if (lcp->peer_mrru) {
	    for (k = 0; k < gNumBundles; k++) {
		if (gBundles[k] && !gBundles[k]->tmpl && gBundles[k]->peer_mrru &&
		    MpDiscrimEqual(&lcp->peer_discrim, &gBundles[k]->peer_discrim) &&
		    !strcmp(lcp->auth.params.authname, gBundles[k]->params.authname)) {
		        break;
		}
	    }
	    if (k != gNumBundles) {
	        b = gBundles[k];
	    }
	}
	if (!b) {
	    const char	*bundt;
	    if (strncmp(l->lcp.auth.params.action, "bundle ", 7) == 0) {
		bundt = l->lcp.auth.params.action + 7;
	    } else {
		bundt = LinkMatchAction(l, 3, l->lcp.auth.params.authname);
	    }
	    if (bundt) {
		if (strcmp(bundt,"##DROP##") == 0) {
		    /* Action told we must drop this connection */
    		    Log(LG_BUND, ("[%s] Drop link", l->name));
		    return (0);
		}
		if ((bt = BundFind(bundt))) {
		    if (bt->tmpl) {
    			Log(LG_BUND, ("[%s] Creating new bundle using template \"%s\".", l->name, bundt));
    			b = BundInst(bt, NULL, 0, 0);
		    } else {
			b = bt;
		    }
		} else {
    		    Log(LG_BUND, ("[%s] Bundle \"%s\" not found.", l->name, bundt));
		    return (0);
		}
	    } else {
    		Log(LG_BUND, ("[%s] No bundle specified", l->name));
		return (0);
	    }
	    if (!b) {
    		Log(LG_BUND, ("[%s] Bundle creation error", l->name));
		return (0);
	    }
	}
	if (b->n_up > 0 &&
	  (b->peer_mrru == 0 || lcp->peer_mrru == 0 || lcp->want_mrru == 0)) {
    	    Log(LG_BUND, ("[%s] Can't join bundle %s without "
		"multilink negotiated.", l->name, b->name));
	    return (0);
	}
	if (b->n_up > 0 &&
	  (!MpDiscrimEqual(&lcp->peer_discrim, &b->peer_discrim) ||
	  strcmp(lcp->auth.params.authname, b->params.authname))) {
    	    Log(LG_BUND, ("[%s] Can't join bundle %s with different "
		"peer discriminator/authname.", l->name, b->name));
	    return (0);
	}
	k = 0;
	while (k < NG_PPP_MAX_LINKS && b->links[k] != NULL)
	    k++;
	if (k < NG_PPP_MAX_LINKS) {
	    l->bund = b;
	    l->bundleIndex = k;
	    b->links[k] = l;
	    b->n_links++;
	} else {
    	    Log(LG_BUND, ("[%s] No more then %d links per bundle allowed. "
		"Can't join budle.", l->name, NG_PPP_MAX_LINKS));
	    return (0);
	}
    }

    b = l->bund;

    Log(LG_LINK, ("[%s] Link: Join bundle \"%s\"", l->name, b->name));

    b->open = TRUE; /* Open bundle on incoming */

    if (LinkNgJoin(l)) {
	Log(LG_ERR, ("[%s] Bundle netgraph join failed", l->name));
	l->bund = NULL;
	b->links[l->bundleIndex] = NULL;
	if (!b->stay)
	    BundShutdown(b);
	return(0);
    }
    l->joined_bund = 1;
    b->n_up++;

    LinkResetStats(l);

    if (b->n_up == 1) {

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

	b->last_up = time(NULL);

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

	/* Initialize multi-link stuff */
	if ((b->peer_mrru = lcp->peer_mrru)) {
    	    b->peer_discrim = lcp->peer_discrim;
	}

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

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

    /* Configure this link */
    b->pppConfig.links[l->bundleIndex].enableLink = 1;
    b->pppConfig.links[l->bundleIndex].mru = b->iface.mtu_override ?
	b->iface.mtu_override : 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 =
	MIN((l->bandwidth / 8 + 5) / 10, NG_PPP_MAX_BANDWIDTH);
    b->pppConfig.links[l->bundleIndex].latency =
	MIN((l->latency + 500) / 1000, NG_PPP_MAX_LATENCY);

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

	/* Configure the bundle */
	b->pppConfig.bund.enableMultilink = (lcp->peer_mrru && lcp->want_mrru)?1:0;
	/* ng_ppp does not allow MRRU less then 1500 bytes. */
	b->pppConfig.bund.mrru = (lcp->peer_mrru < 1500) ? 1500 : 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 msession_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 msession_id to link */
    strlcpy(l->msession_id, b->msession_id, sizeof(l->msession_id));

    /* What to do when the first link comes up */
    if (b->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);

    return(b->n_up);
}

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

void
BundLeave(Link l)
{
    Bund	b = l->bund;

    /* Elvis has left the bundle */
    assert(b->n_up > 0);
  
    Log(LG_LINK, ("[%s] Link: Leave bundle \"%s\"", l->name, b->name));

    AuthAccountStart(l, AUTH_ACCT_STOP);

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

    LinkNgLeave(l);
    l->joined_bund = 0;
    b->n_up--;
    
    /* Divorce link and bundle */
    b->links[l->bundleIndex] = NULL;
    b->n_links--;
    l->bund = NULL;

    BundReasses(b);
    
    /* Forget session_ids */
    l->msession_id[0] = 0;
  
    /* Special stuff when last link goes down... */
    if (b->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);
	
#ifdef USE_NG_BPF
	IfaceFreeStats(&b->iface.prevstats);
#endif

	authparamsDestroy(&b->params);

	b->msession_id[0] = 0;
 
	/* try to open again later */
	if (b->open && Enabled(&b->conf.options, BUND_CONF_BWMANAGE) &&
	  !Enabled(&b->iface.options, IFACE_CONF_ONDEMAND) && !gShutdownInProgress) {
	    if (b->n_links != 0 || b->conf.linkst[0][0]) {
		/* 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] Bundle: Last link has gone, reopening in %d seconds", 
    		    b->name, delay));
    	        TimerStop(&b->reOpenTimer);
    		TimerInit(&b->reOpenTimer, "BundReOpen",
		    delay * SECONDS, BundReOpenLinks, b);
    		TimerStart(&b->reOpenTimer);
		return;
	    } else {
    		Log(LG_BUND, ("[%s] Bundle: Last link has gone, no links for bw-manage defined", 
    		    b->name));
	    }
	}
	b->open = FALSE;
	if (!b->stay)
	    BundShutdown(b);
    }
}

/*
 * 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] Bundle: Last link has gone, reopening...", b->name));
    BundOpenLinks(b);
}

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

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

    if (b->dead) {
	UNREF(b);
	return;
    }
    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;
	BundOpenLinks(b);
        break;

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

    default:
        assert(FALSE);
    }
    UNREF(b);
}

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

void
BundOpenLinks(Bund b)
{
    int	k;

    TimerStop(&b->reOpenTimer);
    if (Enabled(&b->conf.options, BUND_CONF_BWMANAGE)) {
	if (b->n_links != 0)
	    return;
	for (k = 0; k < NG_PPP_MAX_LINKS; k++) {
	    if (b->links[k]) {
    		BundOpenLink(b->links[k]);
		break;
	    } else if (b->conf.linkst[k][0]) {
		BundCreateOpenLink(b, k);
		break;
	    }
	}
    } else {
	for (k = 0; k < NG_PPP_MAX_LINKS; k++) {
	    if (b->links[k])
    		BundOpenLink(b->links[k]);
	    else if (b->conf.linkst[k][0])
		BundCreateOpenLink(b, k);
	}
    }
}

/*
 * BundCreateOpenLink()
 */

int
BundCreateOpenLink(Bund b, int n)
{
    if (!b->links[n]) {
	if (b->conf.linkst[n][0]) {
	    Link l;
	    Link lt = LinkFind(b->conf.linkst[n]);
	    if (!lt) {
		Log(LG_BUND, ("[%s] Bund: Link \"%s\" not found", b->name, b->conf.linkst[n]));
		return (-1);
	    }
	    if (PhysIsBusy(lt)) {
		Log(LG_BUND, ("[%s] Bund: Link \"%s\" is busy", b->name, b->conf.linkst[n]));
		return (-1);
	    }
	    if (lt->tmpl) {
		l = LinkInst(lt, NULL, 0, 0);
	    } else
		l = lt;
	    if (!l) {
		Log(LG_BUND, ("[%s] Bund: Link \"%s\" creation error", b->name, b->conf.linkst[n]));
		return (-1);
	    }
	    b->links[n] = l;
	    b->n_links++;
	    l->bund = b;
	    l->bundleIndex = n;
	    l->conf.max_redial = -1;
	} else {
	    Log(LG_BUND, ("[%s] Bund: Link %d name not specified", b->name, n));
	    return (-1);
	}
    }
    BundOpenLink(b->links[n]);
    return (0);
}

/*
 * BundOpenLink()
 */

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

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

void
BundCloseLinks(Bund b)
{
  int	k;

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

/*
 * BundCloseLink()
 */

static void
BundCloseLink(Link l)
{
    Log(LG_BUND, ("[%s] Bundle: 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] Bundle: 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);
	}
    
	switch(proto) {
	case NCP_IPCP:
		if (!iface->ip_up) {
			iface->ip_up = 1;
			if (IfaceIpIfaceUp(b, 1)) {
			    iface->ip_up = 0;
			    return;
			};
		}
		break;
	case NCP_IPV6CP:
		if (!iface->ipv6_up) {
			iface->ipv6_up = 1;
			if (IfaceIpv6IfaceUp(b, 1)) {
			    iface->ipv6_up = 0;
			    return;
			};
		}
		break;
	case NCP_NONE: /* Manual call by 'open iface' */
		if (Enabled(&b->conf.options, BUND_CONF_IPCP) &&
		    !iface->ip_up) {
			iface->ip_up = 1;
			if (IfaceIpIfaceUp(b, 0)) {
			    iface->ip_up = 0;
			    return;
			};
		}
		if (Enabled(&b->conf.options, BUND_CONF_IPV6CP) &&
		    !iface->ipv6_up) {
			iface->ipv6_up = 1;
			if (IfaceIpv6IfaceUp(b, 0)) {
			    iface->ipv6_up = 0;
			    return;
			};
		}
		break;
	}

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

void
BundNcpsLeave(Bund b, int proto)
{
	IfaceState	iface = &b->iface;
	switch(proto) {
	case NCP_IPCP:
		if (iface->ip_up) {
			iface->ip_up=0;
			IfaceIpIfaceDown(b);
		}
		break;
	case NCP_IPV6CP:
		if (iface->ipv6_up) {
			iface->ipv6_up=0;
			IfaceIpv6IfaceDown(b);
		}
		break;
	case NCP_NONE:
		if (iface->ip_up) {
			iface->ip_up=0;
			IfaceIpIfaceDown(b);
		}
		if (iface->ipv6_up) {
			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) {
			if (Enabled(&b->conf.options, BUND_CONF_IPCP)) {
				iface->ip_up=1;
				if (IfaceIpIfaceUp(b, 0))
				    iface->ip_up = 0;
			}
			if (Enabled(&b->conf.options, BUND_CONF_IPV6CP)) {
				iface->ipv6_up=1;
				if (IfaceIpv6IfaceUp(b, 0))
				    iface->ipv6_up = 0;
			}
			if (iface->ip_up || iface->ipv6_up) {
			    iface->dod=1;
			    iface->up=1;
			    IfaceUp(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)
{
  BundBm	const bm = &b->bm;

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

  Log(LG_BUND, ("[%s] Bundle: Status update: up %d link%s, total bandwidth %d bps",
    b->name, b->n_up, b->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 */
    bm->total_bw = 0;
    for (k = 0; k < NG_PPP_MAX_LINKS; k++) {
	if (b->links[k] && 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 */
    if (b->n_up == 0) {
        mtu = NG_IFACE_MTU_DEFAULT;	/* Reset to default settings */

    } else if (!b->peer_mrru) {		/* If no multilink, use peer MRU */
	mtu = MIN(b->links[the_link]->lcp.peer_mru,
		  PhysGetMtu(b->links[the_link], 0));

    } else {	  	/* Multilink, use peer MRRU */
        mtu = MIN(b->peer_mrru, MP_MAX_MRRU);
    }

    /* Subtract to make room for various frame-bloating protocols */
    if (b->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, const char *const av[], const void *arg)
{
    Bund	sb;
    int		j, k;

    (void)arg;

    if (ac > 1)
	return (-1);

    if (ac == 0) {
    	Printf("Defined bundles:\r\n");
    	for (k = 0; k < gNumBundles; k++) {
	    if ((sb = gBundles[k]) != NULL) {
	        Printf("\t%-15s", sb->name);
	        for (j = 0; j < NG_PPP_MAX_LINKS; j++) {
		    if (sb->links[j])
		        Printf("%s ", sb->links[j]->name);
		}
		Printf("\r\n");
	    }
    	}
	return (0);
    }

    if ((sb = BundFind(av[0])) == NULL) {
        RESETREF(ctx->lnk, NULL);
	RESETREF(ctx->bund, NULL);
	RESETREF(ctx->rep, NULL);
        Error("Bundle \"%s\" not defined.", av[0]);
    }

    /* Change bundle, and link also if needed */
    RESETREF(ctx->bund, sb);
    if (ctx->lnk == NULL || ctx->lnk->bund != ctx->bund) {
        RESETREF(ctx->lnk, ctx->bund->links[0]);
    }
    RESETREF(ctx->rep, NULL);
    return(0);
}

/*
 * MSessionCommand()
 */

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

    (void)arg;

    if (ac > 1)
	return (-1);

    if (ac == 0) {
    	Printf("Present msessions:\r\n");
	for (k = 0; k < gNumBundles; k++) {
	    if (gBundles[k] && gBundles[k]->msession_id[0])
    		Printf("\t%s\r\n", gBundles[k]->msession_id);
	}
	return (0);
    }

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

    /* Change default link and bundle */
    RESETREF(ctx->bund, gBundles[k]);
    if (ctx->lnk == NULL || ctx->lnk->bund != ctx->bund) {
        RESETREF(ctx->lnk, ctx->bund->links[0]);
    }
    RESETREF(ctx->rep, NULL);

    return(0);
}

/*
 * IfaceCommand()
 */

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

    (void)arg;

    if (ac > 1)
	return (-1);

    if (ac == 0) {
    	Printf("Present ifaces:\r\n");
	for (k = 0; k < gNumBundles; k++) {
	    if (gBundles[k] && gBundles[k]->iface.ifname[0])
    		Printf("\t%s\t%s\r\n", gBundles[k]->iface.ifname, gBundles[k]->name);
	}
	return (0);
    }

    /* Find bundle */
    for (k = 0;
	k < gNumBundles && (gBundles[k] == NULL || 
	    strcmp(gBundles[k]->iface.ifname, av[0]));
	k++);
    if (k == gNumBundles) {
	/* Change default link and bundle */
	RESETREF(ctx->lnk, NULL);
	RESETREF(ctx->bund, NULL);
	RESETREF(ctx->rep, NULL);
	Error("iface \"%s\" is not found", av[0]);
    }

    /* Change default link and bundle */
    RESETREF(ctx->bund, gBundles[k]);
    if (ctx->lnk == NULL || ctx->lnk->bund != ctx->bund) {
        RESETREF(ctx->lnk, ctx->bund->links[0]);
    }
    RESETREF(ctx->rep, NULL);

    return(0);
}

/*
 * BundCreate()
 */

int
BundCreate(Context ctx, int ac, const char *const av[], const void *arg)
{
    Bund	b, bt = NULL;
    u_char	tmpl = 0;
    u_char	stay = 0;
    int	k;

    (void)arg;

    RESETREF(ctx->lnk, NULL);
    RESETREF(ctx->bund, NULL);
    RESETREF(ctx->rep, NULL);

    if (ac < 1)
	return(-1);

    if (strcmp(av[0], "template") == 0) {
	tmpl = 1;
	stay = 1;
    } else if (strcmp(av[0], "static") == 0)
	stay = 1;

    if (ac - stay < 1 || ac - stay > 2)
	return(-1);

    if (strlen(av[0 + stay]) >= (LINK_MAX_NAME - tmpl * (IFNUMLEN + 1)))
	Error("Bundle name \"%s\" is too long", av[0 + stay]);

    /* See if bundle name already taken */
    if ((b = BundFind(av[0 + stay])) != NULL)
	Error("Bundle \"%s\" already exists", av[0 + stay]);

    if (ac - stay == 2) {
	/* See if template name specified */
	if ((bt = BundFind(av[1 + stay])) == NULL)
	    Error("Bundle template \"%s\" not found", av[1 + stay]);
	if (!bt->tmpl)
	    Error("Bundle \"%s\" is not a template", av[1 + stay]);
    }

    if (bt) {
	b = BundInst(bt, av[0 + stay], tmpl, stay);
    } else {
	/* Create a new bundle structure */
	b = Malloc(MB_BUND, sizeof(*b));
	strlcpy(b->name, av[0 + stay], sizeof(b->name));
	b->tmpl = tmpl;
	b->stay = stay;

	/* 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;
	REF(b);

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

	/* Initialize bundle configuration */
	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;

	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);
  
        /* Init iface and NCP's */
	IfaceInit(b);
        IpcpInit(b);
        Ipv6cpInit(b);
        CcpInit(b);
        EcpInit(b);

	if (!tmpl) {
	    /* Setup netgraph stuff */
	    if (BundNgInit(b) < 0) {
		gBundles[b->id] = NULL;
		IfaceDestroy(b);
		Freee(b);
		Error("Bundle netgraph initialization failed");
	    }
	}
    }
  
    RESETREF(ctx->bund, b);
  
    /* Done */
    return(0);
}

/*
 * BundDestroy()
 */

int
BundDestroy(Context ctx, int ac, const char *const av[], const void *arg)
{
    Bund 	b;

    (void)arg;

    if (ac > 1)
	return(-1);

    if (ac == 1) {
	if ((b = BundFind(av[0])) == NULL)
	    Error("Bund \"%s\" not found", av[0]);
    } else {
	if (ctx->bund) {
	    b = ctx->bund;
	} else
	    Error("No bundle selected to destroy");
    }
    
    if (b->tmpl) {
	b->tmpl = 0;
	b->stay = 0;
	BundShutdown(b);
    } else {
	b->stay = 0;
	if (b->n_up) {
	    BundClose(b);
	} else {
	    BundShutdown(b);
	}
    }

    return (0);
}

/*
 * BundInst()
 */

Bund
BundInst(Bund bt, const char *name, int tmpl, int stay)
{
    Bund	b;
    int	k;

    /* Create a new bundle structure */
    b = Mdup(MB_BUND, bt, sizeof(*b));
    b->tmpl = tmpl;
    b->stay = stay;
    b->refs = 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;
    if (name)
	strlcpy(b->name, name, sizeof(b->name));
    else
	snprintf(b->name, sizeof(b->name), "%s-%d", bt->name, k);
    gBundles[k] = b;
    REF(b);

    /* Inst iface and NCP's */
    IfaceInst(b, bt);
    IpcpInst(b, bt);
    Ipv6cpInst(b, bt);
    CcpInst(b, bt);
    EcpInst(b, bt);

    if (!tmpl) {
	/* Setup netgraph stuff */
	if (BundNgInit(b) < 0) {
	    Log(LG_ERR, ("[%s] Bundle netgraph initialization failed", b->name));
	    gBundles[b->id] = NULL;
	    Freee(b);
	    return(0);
	}
    }

    return (b);
}

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

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

    Log(LG_BUND, ("[%s] Bundle: Shutdown", b->name));
    for (k = 0; k < NG_PPP_MAX_LINKS; k++) {
	if ((l = b->links[k]) != NULL) {
	    if (!l->stay)
		LinkShutdown(l);
	    else {
		l->bund = NULL;
		b->links[k] = NULL;
	    }
	}
    }

    if (b->hook[0])
	BundNgShutdown(b, 1, 1);
    gBundles[b->id] = NULL;
    MsgUnRegister(&b->msgs);
    b->dead = 1;
    IfaceDestroy(b);
    UNREF(b);
}

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

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

  (void)arg;

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

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

  Printf("Bundle '%s'%s:\r\n", sb->name, sb->tmpl?" (template)":(sb->stay?" (static)":""));
  Printf("\tLinks          : ");
  BundShowLinks(ctx, sb);
  Printf("\tStatus         : %s\r\n", sb->open ? "OPEN" : "CLOSED");
  if (sb->n_up)
    Printf("\tSession time   : %ld seconds\r\n", (long int)(time(NULL) - sb->last_up));
  Printf("\tMultiSession 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);

  /* Show configuration */
  Printf("Configuration:\r\n");
#ifdef SIOCSIFDESCR
  Printf("\tDesc. template : %s\r\n",
	sb->iface.conf.ifdescr ? sb->iface.conf.ifdescr : "<none>");
  Printf("\tDescription    : %s\r\n",
	sb->iface.ifdescr ? sb->iface.ifdescr : "<none>");
#endif
  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("\t  Links        : ");
  for (k = 0; k < NG_PPP_MAX_LINKS; k++)
    Printf("%s ", sb->conf.linkst[k]);
  Printf("\r\n");
  Printf("Bundle level options:\r\n");
  OptStat(ctx, &sb->conf.options, gConfList);

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

    if (!sb->tmpl) {
	/* Show stats */
	BundUpdateStats(sb);
	Printf("Traffic stats:\r\n");

	Printf("\tInput octets   : %llu\r\n", (unsigned long long)sb->stats.recvOctets);
	Printf("\tInput frames   : %llu\r\n", (unsigned long long)sb->stats.recvFrames);
	Printf("\tOutput octets  : %llu\r\n", (unsigned long long)sb->stats.xmitOctets);
	Printf("\tOutput frames  : %llu\r\n", (unsigned long long)sb->stats.xmitFrames);
	Printf("\tBad protocols  : %llu\r\n", (unsigned long long)sb->stats.badProtos);
	Printf("\tRunts          : %llu\r\n", (unsigned long long)sb->stats.runts);
	Printf("\tDup fragments  : %llu\r\n", (unsigned long long)sb->stats.dupFragments);
	Printf("\tDrop fragments : %llu\r\n", (unsigned long long)sb->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->peer_mrru)
    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 < NG_PPP_MAX_LINKS; k++) {
	if (b->links[k] && 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()
 */

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

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

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

Bund
BundFind(const 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 */
    memset(&b->bm.traffic, 0, sizeof(b->bm.traffic));
    memset(&b->bm.avail, 0, sizeof(b->bm.avail));
    memset(&b->bm.wasUp, 0, sizeof(b->bm.wasUp));
    for (k = 0; k < NG_PPP_MAX_LINKS; k++) {
	if (b->links[k]) {
	    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) / BUND_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[BUND_BM_N];	/* Incoming % utilization */
    u_int		outUtil[BUND_BM_N];	/* Outgoing % utilization */
    int			j, k;

    /* Shift and update stats */
    memmove(&b->bm.wasUp[1], &b->bm.wasUp[0],
	(BUND_BM_N - 1) * sizeof(b->bm.wasUp[0]));
    b->bm.wasUp[0] = b->n_up;
    memmove(&b->bm.avail[1], &b->bm.avail[0],
	(BUND_BM_N - 1) * sizeof(b->bm.avail[0]));
    b->bm.avail[0] = b->bm.total_bw;

    /* Shift stats */
    memmove(&b->bm.traffic[0][1], &b->bm.traffic[0][0],
	(BUND_BM_N - 1) * sizeof(b->bm.traffic[0][0]));
    memmove(&b->bm.traffic[1][1], &b->bm.traffic[1][0],
	(BUND_BM_N - 1) * sizeof(b->bm.traffic[1][0]));
    b->bm.traffic[0][0] = 0;
    b->bm.traffic[1][0] = 0;
    for (k = 0; k < NG_PPP_MAX_LINKS; k++) {
	if (b->links[k] && b->links[k]->joined_bund) {
	    Link	const l = b->links[k];

#ifndef NG_PPP_STATS64
	    struct ng_ppp_link_stat	oldStats;
#else
	    struct ng_ppp_link_stat64	oldStats;
#endif
	
	    /* Get updated link traffic statistics */
	    oldStats = l->bm.idleStats;
#ifndef NG_PPP_STATS64
	    NgFuncGetStats(l->bund, l->bundleIndex, &l->bm.idleStats);
#else
	    NgFuncGetStats64(l->bund, l->bundleIndex, &l->bm.idleStats);
#endif
	    b->bm.traffic[0][0] += l->bm.idleStats.recvOctets - oldStats.recvOctets;
	    b->bm.traffic[1][0] += l->bm.idleStats.xmitOctets - oldStats.xmitOctets;
	}
    }

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

	avail = (b->bm.avail[j] * b->conf.bm_S) / BUND_BM_N;
	inBits = b->bm.traffic[0][j] * 8;
	outBits = b->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;
    }

  {
    char	ins[100], outs[100];

    ins[0] = 0;
    for (j = 0; j < BUND_BM_N; j++) {
	snprintf(ins + strlen(ins), sizeof(ins) - strlen(ins),
		" %3u ", b->bm.wasUp[BUND_BM_N - 1 - j]);
    }
    Log(LG_BUND2, ("[%s]                       %s", b->name, ins));

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

  /* 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)) {
    for (k = 0; k < NG_PPP_MAX_LINKS; k++) {
cont:
	if (!b->links[k] && b->conf.linkst[k][0])
		break;
    }
    if (k < NG_PPP_MAX_LINKS) {
	Log(LG_BUND, ("[%s] opening link \"%s\" due to increased demand",
    	    b->name, b->conf.linkst[k]));
	b->bm.last_open = now;
	if (b->links[k]) {
	    RecordLinkUpDownReason(NULL, b->links[k], 1, STR_PORT_NEEDED, NULL);
	    BundOpenLink(b->links[k]);
	} else {
	    if (BundCreateOpenLink(b, k)) {
		if (k < NG_PPP_MAX_LINKS) {
		    k++;
		    goto cont;
		}
	    } else
		RecordLinkUpDownReason(NULL, b->links[k], 1, STR_PORT_NEEDED, NULL);
	}
    }
  }

  /* 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->n_links > 1) {
    k = NG_PPP_MAX_LINKS - 1;
    while (k >= 0 && (!b->links[k] || !OPEN_STATE(b->links[k]->lcp.fsm.state)))
	k--;
    assert(k >= 0);
    Log(LG_BUND, ("[%s] Bundle: 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)
{
    struct ngm_mkpeer	mp;
    struct ngm_name	nm;
    int			newIface = 0;
    int			newPpp = 0;

    /* Create new iface node */
    if (NgFuncCreateIface(b,
	b->iface.ifname, sizeof(b->iface.ifname)) < 0) {
      Log(LG_ERR, ("[%s] can't create netgraph interface", b->name));
      goto fail;
    }
    strlcpy(b->iface.ngname, b->iface.ifname, sizeof(b->iface.ngname));
    newIface = 1;
    b->iface.ifindex = if_nametoindex(b->iface.ifname);
    Log(LG_BUND|LG_IFACE, ("[%s] Bundle: Interface %s created",
	b->name, b->iface.ifname));

    /* Create new PPP node */
    snprintf(b->hook, sizeof(b->hook), "b%d", b->id);
    memset(&mp, 0, sizeof(mp));
    strcpy(mp.type, NG_PPP_NODE_TYPE);
    strcpy(mp.ourhook, b->hook);
    strcpy(mp.peerhook, NG_PPP_HOOK_BYPASS);
    if (NgSendMsg(gLinksCsock, ".:",
    	    NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
	Perror("[%s] can't create %s node at \"%s\"->\"%s\"",
    	    b->name, mp.type, ".:", mp.ourhook);
	goto fail;
    }
    newPpp = 1;

    /* Get PPP node ID */
    if ((b->nodeID = NgGetNodeID(gLinksCsock, b->hook)) == 0) {
	Perror("[%s] Cannot get %s node id", b->name, NG_PPP_NODE_TYPE);
	goto fail;
    }

    /* Give it a name */
    memset(&nm, 0, sizeof(nm));
    snprintf(nm.name, sizeof(nm.name), "mpd%d-%s", gPid, b->name);
    if (NgSendMsg(gLinksCsock, b->hook,
    	    NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
	Perror("[%s] can't name %s node \"%s\"",
    	    b->name, NG_PPP_NODE_TYPE, b->hook);
	goto fail;
    }

    /* OK */
    return(0);

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

/*
 * NgFuncShutdown()
 */

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

    if (iface) {
	snprintf(path, sizeof(path), "%s:", b->iface.ngname);
	NgFuncShutdownNode(gLinksCsock, b->name, path);
    }
    if (ppp) {
	snprintf(path, sizeof(path), "[%x]:", b->nodeID);
	NgFuncShutdownNode(gLinksCsock, b->name, path);
    }
    b->hook[0] = 0;
}

/*
 * BundSetCommand()
 */

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

    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_LINKS:
	    if (ac > NG_PPP_MAX_LINKS)
		return (-1);
    	    for (i = 0; i < ac; i++)
		strlcpy(b->conf.linkst[i], av[i], LINK_MAX_NAME);
    	    for (; i < NG_PPP_MAX_LINKS; i++)
	        b->conf.linkst[i][0] = 0;
    	    break;

	case SET_RETRY:
    	    val = atoi(*av);
    	    if (val < 1 || val > 10)
		Error("[%s] incorrect fsm-timeout value %d", b->name, val);
	    else
		b->conf.retry_timeout = val;
    	    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);
    	    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);
}


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