
/*
 * ngfunc.c
 *
 * Written by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 *
 * TCP MSSFIX contributed by Sergey Korolew <dsATbittu.org.ru>
 *
 * Routines for doing netgraph stuff
 *
 */

#include "ppp.h"
#include "bund.h"
#include "ngfunc.h"
#include "input.h"
#include "ccp.h"
#include "netgraph.h"

#include <net/bpf.h>

#include <netgraph/ng_message.h>
#include <netgraph/ng_socket.h>
#include <netgraph/ng_ksocket.h>
#include <netgraph/ng_iface.h>
#include <netgraph/ng_ppp.h>
#include <netgraph/ng_vjc.h>
#include <netgraph/ng_bpf.h>

/*
 * DEFINITIONS
 */

  #define TEMPHOOK		"temphook"
  #define MAX_IFACE_CREATE	128

/*
 * INTERNAL FUNCTIONS
 */

  static void	NgFuncDataEvent(int type, void *cookie);
  static void	NgFuncCtrlEvent(int type, void *cookie);
  static int	NgFuncCreateIface(Bund b,
			const char *ifname, char *buf, int max);
  static int	NgFuncIfaceExists(Bund b,
			const char *ifname, char *buf, int max);
  static void	NgFuncShutdownInternal(Bund b, int iface, int ppp);

  static void	NgFuncErrx(const char *fmt, ...);
  static void	NgFuncErr(const char *fmt, ...);

/*
 * INTERNAL VARIABLES
 */

  /* A BPF filter for matching an IP packet if it constitutes 'demand' */
  static const struct bpf_insn gDemandProg[] = {

	/* Load IP protocol number and IP header length */
/*00*/	BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 9),		/* A <- IP protocol */
/*01*/	BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, 0),		/* X <- header len */

	/* Compare to interesting possibilities */
/*02*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_IGMP, 4, 0),	/* -> 07 */
/*03*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_ICMP, 4, 0),	/* -> 08 */
/*04*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_UDP, 11, 0),	/* -> 16 */
/*05*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_TCP, 16, 0),	/* -> 22 */

	/* Some other protocol -> accept */
/*06*/	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),

	/* Protocol is IGMP -> reject (no multicast stuff) */
/*07*/	BPF_STMT(BPF_RET+BPF_K, 0),

	/* Protocol is ICMP -> reject ICMP replies */
/*08*/	BPF_STMT(BPF_LD+BPF_B+BPF_IND, 0),		/* A <- ICMP type */
/*09*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ICMP_ECHOREPLY, 0, 1),
/*10*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject ECHOREPLY */
/*11*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ICMP_UNREACH, 0, 1),
/*12*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject UNREACH */
/*13*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ICMP_REDIRECT, 0, 1),
/*14*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject REDIRECT */
/*15*/	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),		/* OK, accept */

	/* Protocol is UDP -> reject NTP and port 24 traffic */
#define NTP_PORT	123
#define U24_PORT	24			/* XXX InterJet-specific hack */
/*16*/	BPF_STMT(BPF_LD+BPF_H+BPF_IND, 2),		/* A <- UDP dest port */
/*17*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, NTP_PORT, 0, 1),/* compare NTP_PORT */
/*18*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject NTP */
/*19*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, U24_PORT, 0, 1),/* compare port 24 */
/*20*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject port 24 */
/*21*/	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),		/* OK, accept */

	/* Protocol is TCP -> reject if TH_RST bit set */
/*22*/	BPF_STMT(BPF_LD+BPF_B+BPF_IND, 13),		/* A <- TCP flags */
/*23*/	BPF_STMT(BPF_ALU+BPF_AND+BPF_K, TH_RST),	/* A <- A & TH_RST */
/*24*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 1),	/* compare to zero */
/*25*/	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),		/* accept packet */
/*26*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject packet */

  };

  #define DEMAND_PROG_LEN	(sizeof(gDemandProg) / sizeof(*gDemandProg))

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

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

  /* A BPF filter that matches TCP SYN packets */
  static const struct bpf_insn gTCPSYNProg[] = {

	/* Load IP protocol number and IP header length */
/*00*/	BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 9),		/* A <- IP protocol */
/*01*/	BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, 0),		/* X <- header len */

/*02*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_TCP, 1, 0),	/* -> 04 */
/*03*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject packet */

	/* Protocol is TCP -> accept if TH_SYN bit set */
/*04*/	BPF_STMT(BPF_LD+BPF_B+BPF_IND, 13),		/* A <- TCP flags */
/*05*/	BPF_STMT(BPF_ALU+BPF_AND+BPF_K, TH_SYN),	/* A <- A & TH_SYN */
/*06*/	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 1, 0),	/* compare to zero */
/*07*/	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),		/* accept packet */
/*08*/	BPF_STMT(BPF_RET+BPF_K, 0),			/* reject packet */
  };

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

/*
 * NgFuncInit()
 *
 * 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.
 */

int
NgFuncInit(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_connect	cn;
  struct ngm_name	nm;
  char			path[NG_PATHLEN + 1];
  int			newIface = 0;
  int			newPpp = 0;

  /* Set up libnetgraph logging */
  NgSetErrLog(NgFuncErr, NgFuncErrx);

  /* 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(NULL);
  }
  (void) fcntl(b->csock, F_SETFD, 1);
  (void) fcntl(b->dsock, F_SETFD, 1);

  /* 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;
  }
 
  /* 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: %s",
      b->name, mp.type, strerror(errno)));
    goto fail;
  }
  newPpp = 1;

  /* Give it a name */
  snprintf(nm.name, sizeof(nm.name), "mpd%d-%s", getpid(), 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",
      b->name, NG_PPP_NODE_TYPE, strerror(errno)));
    goto fail;
  }
  Log(LG_ALWAYS, ("[%s] %s node is \"%s\"",
    b->name, NG_PPP_NODE_TYPE, nm.name));

  /* 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;

  /* Add a bpf node to the PPP node on the "inet" hook */
  snprintf(mp.type, sizeof(mp.type), "%s", NG_BPF_NODE_TYPE);
  snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", NG_PPP_HOOK_INET);
  snprintf(mp.peerhook, sizeof(mp.peerhook), "%s", BPF_HOOK_PPP);
  if (NgSendMsg(b->csock, MPD_HOOK_PPP,
      NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
    Log(LG_ERR, ("[%s] can't create %s node: %s",
      b->name, NG_BPF_NODE_TYPE, strerror(errno)));
    goto fail;
  }

  /* Connect the other side of the bpf node to the iface node */
  snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, NG_PPP_HOOK_INET);
  snprintf(cn.path, sizeof(cn.path), "%s:", b->iface.ifname);
  snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", BPF_HOOK_IFACE);
  snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", NG_IFACE_HOOK_INET);
  if (NgSendMsg(b->csock, path,
      NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
    Log(LG_ERR, ("[%s] can't connect %s and %s: %s",
      b->name, BPF_HOOK_IFACE, NG_IFACE_HOOK_INET, strerror(errno)));
    goto fail;
  }

  /* Connect a hook from the bpf node to our socket node */
  snprintf(cn.path, sizeof(cn.path), "%s.%s", MPD_HOOK_PPP, NG_PPP_HOOK_INET);
  snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", MPD_HOOK_DEMAND_TAP);
  snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", BPF_HOOK_MPD);
  if (NgSendMsg(b->csock, ".",
      NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
    Log(LG_ERR, ("[%s] can't connect %s and %s: %s",
      b->name, BPF_HOOK_MPD, MPD_HOOK_DEMAND_TAP, strerror(errno)));
    goto fail;
  }

  /* Configure bpf(8) node */
  NgFuncConfigBPF(b, BPF_MODE_OFF);

  /* Add a VJ compression node */
  snprintf(mp.type, sizeof(mp.type), "%s", NG_VJC_NODE_TYPE);
  snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", NG_PPP_HOOK_VJC_IP);
  snprintf(mp.peerhook, sizeof(mp.peerhook), "%s", NG_VJC_HOOK_IP);
  if (NgSendMsg(b->csock, MPD_HOOK_PPP,
      NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
    Log(LG_ERR, ("[%s] can't create %s node: %s",
      b->name, NG_VJC_NODE_TYPE, strerror(errno)));
    goto fail;
  }

  /* Connect the other three hooks between the ppp and vjc nodes */
  snprintf(cn.path, sizeof(cn.path), "%s", NG_PPP_HOOK_VJC_IP);
  snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", NG_PPP_HOOK_VJC_COMP);
  snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", NG_VJC_HOOK_VJCOMP);
  if (NgSendMsg(b->csock, MPD_HOOK_PPP,
      NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
    Log(LG_ERR, ("[%s] can't connect %s and %s: %s",
      b->name, NG_PPP_HOOK_VJC_COMP, NG_VJC_HOOK_VJCOMP, strerror(errno)));
    goto fail;
  }
  snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", NG_PPP_HOOK_VJC_UNCOMP);
  snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", NG_VJC_HOOK_VJUNCOMP);
  if (NgSendMsg(b->csock, MPD_HOOK_PPP,
      NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
    Log(LG_ERR, ("[%s] can't connect %s and %s: %s", b->name,
      NG_PPP_HOOK_VJC_UNCOMP, NG_VJC_HOOK_VJUNCOMP, strerror(errno)));
    goto fail;
  }
  snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", NG_PPP_HOOK_VJC_VJIP);
  snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", NG_VJC_HOOK_VJIP);
  if (NgSendMsg(b->csock, MPD_HOOK_PPP,
      NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
    Log(LG_ERR, ("[%s] can't connect %s and %s: %s",
      b->name, NG_PPP_HOOK_VJC_VJIP, NG_VJC_HOOK_VJIP, strerror(errno)));
    goto fail;
  }

  /* Listen for happenings on our node */
  EventRegister(&b->dataEvent, EVENT_READ,
    b->dsock, DEV_PRIO, NgFuncDataEvent, b);
  EventRegister(&b->ctrlEvent, EVENT_READ,
    b->csock, DEV_PRIO, NgFuncCtrlEvent, b);

  /* OK */
  return(0);

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

/*
 * NgFuncIfaceExists()
 *
 * Test if a netgraph interface exists. Returns:
 *
 *	0	Netgraph interface does not exist
 *	1	Netgraph interface exists
 *     -1	Interface is not a netgraph interface
 */

static int
NgFuncIfaceExists(Bund b, const char *ifname, char *buf, int max)
{
  union {
      u_char		buf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
      struct ng_mesg	reply;
  }			u;
  char		path[NG_PATHLEN + 1];
  char		*eptr;
  int		ifnum;

  /* Check interface name */
  if (strncmp(ifname, NG_IFACE_IFACE_NAME, strlen(NG_IFACE_IFACE_NAME)) != 0)
    return(-1);
  ifnum = (int)strtoul(ifname + strlen(NG_IFACE_IFACE_NAME), &eptr, 10);
  if (ifnum < 0 || *eptr != '\0')
    return(-1);

  /* See if interface exists */
  snprintf(path, sizeof(path), "%s%d:", NG_IFACE_IFACE_NAME, ifnum);
  if (NgSendMsg(b->csock, path, NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) < 0)
    return(0);
  if (NgRecvMsg(b->csock, &u.reply, sizeof(u), NULL) < 0) {
    Log(LG_ERR, ("[%s] node \"%s\" reply: %s", b->name, path, strerror(errno)));
    return(-1);
  }

  /* It exists */
  if (buf != NULL)
    snprintf(buf, max, "%s%d", NG_IFACE_IFACE_NAME, ifnum);
  return(1);
}

/*
 * NgFuncCreateIface()
 *
 * Create a new netgraph interface, optionally with a specific name.
 * If "ifname" is not NULL, then create interfaces until "ifname" is
 * created.  Interfaces are consecutively numbered when created, so
 * we have no other choice but to create all lower numbered interfaces
 * in order to create one with a given index.
 */

static int
NgFuncCreateIface(Bund b, const char *ifname, char *buf, int max)
{
  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_rmhook	rm;
  struct ngm_mkpeer	mp;
  int			rtn = 0;

  /* If ifname is not null, create interfaces until it gets created */
  if (ifname != NULL) {
    int count;

    for (count = 0; count < MAX_IFACE_CREATE; count++) {
      switch (NgFuncIfaceExists(b, ifname, buf, max)) {
      case 1:				/* ok now it exists */
	return(0);
      case 0:				/* nope, create another one */
	NgFuncCreateIface(b, NULL, NULL, 0);
	break;
      case -1:				/* something weird happened */
	return(-1);
      default:
	assert(0);
      }
    }
    Log(LG_ERR, ("[%s] created %d interfaces, that's too many!",
      b->name, count));
    return(-1);
  }

  /* Create iface node (as a temporary peer of the socket node) */
  snprintf(mp.type, sizeof(mp.type), "%s", NG_IFACE_NODE_TYPE);
  snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", TEMPHOOK);
  snprintf(mp.peerhook, sizeof(mp.peerhook), "%s", NG_IFACE_HOOK_INET);
  if (NgSendMsg(b->csock, ".",
      NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
    Log(LG_ERR, ("[%s] can't create %s node: %s",
      b->name, NG_IFACE_NODE_TYPE, strerror(errno)));
    return(-1);
  }

  /* Get the new node's name */
  if (NgSendMsg(b->csock, TEMPHOOK,
      NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) < 0) {
    Log(LG_ERR, ("[%s] %s: %s", b->name, "NGM_NODEINFO", strerror(errno)));
    rtn = -1;
    goto done;
  }
  if (NgRecvMsg(b->csock, &u.reply, sizeof(u), NULL) < 0) {
    Log(LG_ERR, ("[%s] reply from %s: %s",
      b->name, NG_IFACE_NODE_TYPE, strerror(errno)));
    rtn = -1;
    goto done;
  }
  snprintf(buf, max, "%s", ni->name);

done:
  /* Disconnect temporary hook */
  snprintf(rm.ourhook, sizeof(rm.ourhook), "%s", TEMPHOOK);
  if (NgSendMsg(b->csock, ".",
      NGM_GENERIC_COOKIE, NGM_RMHOOK, &rm, sizeof(rm)) < 0) {
    Log(LG_ERR, ("[%s] can't remove hook %s: %s",
      b->name, TEMPHOOK, strerror(errno)));
    rtn = -1;
  }

  /* Done */
  return(rtn);
}

/*
 * NgFuncConfigBPF()
 *
 * Configure the BPF node for one of three modes: either total pass through,
 * total blockage, or else block all traffic and redirect outgoing demand
 * to mpd's socket node.
 */

void
NgFuncConfigBPF(Bund b, int mode)
{
  union {
      u_char			buf[NG_BPF_HOOKPROG_SIZE(DEMAND_PROG_LEN)];
      struct ng_bpf_hookprog	hprog;
  }				u;
  struct ng_bpf_hookprog	*const hp = &u.hprog;
  char				path[NG_PATHLEN + 1];

  /* Get absolute path to bpf node */
  snprintf(path, sizeof(path), "%s:%s", b->iface.ifname, NG_IFACE_HOOK_INET);

  /* First, configure the hook on the interface node side of the BPF node */
  memset(&u, 0, sizeof(u));
  snprintf(hp->thisHook, sizeof(hp->thisHook), "%s", BPF_HOOK_IFACE);
  hp->bpf_prog_len = DEMAND_PROG_LEN;
  memcpy(&hp->bpf_prog, &gDemandProg, DEMAND_PROG_LEN * sizeof(*gDemandProg));
  switch (mode) {
    case BPF_MODE_OFF:
      memset(&hp->ifMatch, 0, sizeof(hp->ifMatch));
      memset(&hp->ifNotMatch, 0, sizeof(hp->ifNotMatch));
      break;
    case BPF_MODE_ON:
    case BPF_MODE_MSSFIX:
      snprintf(hp->ifMatch, sizeof(hp->ifMatch), "%s", BPF_HOOK_PPP);
      snprintf(hp->ifNotMatch, sizeof(hp->ifNotMatch), "%s", BPF_HOOK_PPP);
      break;
    case BPF_MODE_DEMAND:
      snprintf(hp->ifMatch, sizeof(hp->ifMatch), "%s", BPF_HOOK_MPD);
      memset(&hp->ifNotMatch, 0, sizeof(hp->ifNotMatch));
      break;
    default:
      assert(0);
  }

  /* Set new program on the BPF_HOOK_IFACE hook */
  if (NgSendMsg(b->csock, path, NGM_BPF_COOKIE,
      NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) {
    Log(LG_ERR, ("[%s] can't set %s node program: %s",
      b->name, NG_BPF_NODE_TYPE, strerror(errno)));
    DoExit(EX_ERRDEAD);
  }

  /* Now, configure the hook on the PPP node side of the BPF node */
  memset(&u, 0, sizeof(u));
  snprintf(hp->thisHook, sizeof(hp->thisHook), "%s", BPF_HOOK_PPP);
  hp->bpf_prog_len = TCPSYN_PROG_LEN;
  memcpy(&hp->bpf_prog,
    &gTCPSYNProg, TCPSYN_PROG_LEN * sizeof(*gTCPSYNProg));
  switch (mode) {
    case BPF_MODE_OFF:
    case BPF_MODE_DEMAND:
      memset(&hp->ifMatch, 0, sizeof(hp->ifMatch));
      memset(&hp->ifNotMatch, 0, sizeof(hp->ifNotMatch));
      break;
    case BPF_MODE_ON:
      snprintf(hp->ifMatch, sizeof(hp->ifMatch), "%s", BPF_HOOK_IFACE);
      snprintf(hp->ifNotMatch, sizeof(hp->ifNotMatch), "%s", BPF_HOOK_IFACE);
      break;
    case BPF_MODE_MSSFIX:
      snprintf(hp->ifMatch, sizeof(hp->ifMatch), "%s", BPF_HOOK_MPD);
      snprintf(hp->ifNotMatch, sizeof(hp->ifNotMatch), "%s", BPF_HOOK_IFACE);
      break;
    default:
      assert(0);
  }

  /* Set new program on the BPF_HOOK_IFACE hook */
  if (NgSendMsg(b->csock, path, NGM_BPF_COOKIE,
      NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) {
    Log(LG_ERR, ("[%s] can't set %s node program: %s",
      b->name, NG_BPF_NODE_TYPE, strerror(errno)));
    DoExit(EX_ERRDEAD);
  }
  /* Configure the hook on the MPD node side of the BPF node */
  memset(&u, 0, sizeof(u));
  snprintf(hp->thisHook, sizeof(hp->thisHook), "%s", BPF_HOOK_MPD);
  hp->bpf_prog_len = NOMATCH_PROG_LEN;
  memcpy(&hp->bpf_prog,
    &gNoMatchProg, NOMATCH_PROG_LEN * sizeof(*gNoMatchProg));
  switch (mode) {
    case BPF_MODE_OFF:
    case BPF_MODE_DEMAND:
      memset(&hp->ifMatch, 0, sizeof(hp->ifMatch));
      memset(&hp->ifNotMatch, 0, sizeof(hp->ifNotMatch));
      break;
    case BPF_MODE_ON:
    case BPF_MODE_MSSFIX:
      snprintf(hp->ifMatch, sizeof(hp->ifMatch), "%s", BPF_HOOK_IFACE);
      snprintf(hp->ifNotMatch, sizeof(hp->ifNotMatch), "%s", BPF_HOOK_IFACE);
      break;
    default:
      assert(0);
  }

  /* Set new program on the BPF_HOOK_IFACE hook */
  if (NgSendMsg(b->csock, path, NGM_BPF_COOKIE,
      NGM_BPF_SET_PROGRAM, hp, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) < 0) {
    Log(LG_ERR, ("[%s] can't set %s node program: %s",
      b->name, NG_BPF_NODE_TYPE, strerror(errno)));
    DoExit(EX_ERRDEAD);
  }
}

/*
 * NgFuncShutdown()
 *
 * Shutdown the netgraph stuff associated with the current bundle
 */

void
NgFuncShutdown(Bund b)
{
  NgFuncShutdownInternal(b, 1, 1);
}

/*
 * NgFuncShutdownInternal()
 */

static void
NgFuncShutdownInternal(Bund b, int iface, int ppp)
{
  char	path[NG_PATHLEN + 1];
  Bund	bund_save;
  Link	lnk_save;
  int	k;

  if (iface) {
    snprintf(path, sizeof(path), "%s:", b->iface.ifname);
    NgFuncShutdownNode(b, b->name, path);
  }
  lnk_save = lnk;
  bund_save = bund;
  for (k = 0; k < b->n_links; k++) {
    lnk = b->links[k];
    bund = lnk->bund;
    if (lnk && lnk->phys && lnk->phys->type && lnk->phys->type->shutdown)
      (*lnk->phys->type->shutdown)(lnk->phys);
  }
  bund = bund_save;
  lnk = lnk_save;
  if (ppp) {
    snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, NG_PPP_HOOK_INET);
    NgFuncShutdownNode(b, b->name, path);
    NgFuncShutdownNode(b, b->name, MPD_HOOK_PPP);
  }
  close(b->csock);
  b->csock = -1;
  EventUnRegister(&b->ctrlEvent);
  close(b->dsock);
  b->dsock = -1;
  EventUnRegister(&b->dataEvent);
}

/*
 * NgFuncShutdownNode()
 */

int
NgFuncShutdownNode(Bund b, const char *label, const char *path)
{
  int rtn;

  if ((rtn = NgSendMsg(b->csock, path,
      NGM_GENERIC_COOKIE, NGM_SHUTDOWN, NULL, 0)) < 0) {
    if (errno != ENOENT) {
      Log(LG_ERR, ("[%s] can't shutdown \"%s\": %s",
	label, path, strerror(errno)));
    }
  }
  return(rtn);
}

/*
 * NgFuncSetConfig()
 */

void
NgFuncSetConfig(void)
{
  if (NgSendMsg(bund->csock, MPD_HOOK_PPP, NGM_PPP_COOKIE,
      NGM_PPP_SET_CONFIG, &bund->pppConfig, sizeof(bund->pppConfig)) < 0) {
    Log(LG_ERR, ("[%s] can't config %s: %s",
      bund->name, MPD_HOOK_PPP, strerror(errno)));
    DoExit(EX_ERRDEAD);
  }
}

/*
 * NgFuncDataEvent()
 */

static void
NgFuncDataEvent(int type, void *cookie)
{
  u_char		buf[8192];
  struct sockaddr_ng	naddr;
  int			nread, nsize = sizeof(naddr);

  /* Set bundle */
  bund = (Bund) cookie;
  lnk = bund->links[0];

  /* Re-register event */
  EventRegister(&bund->dataEvent, EVENT_READ,
    bund->dsock, DEV_PRIO, NgFuncDataEvent, bund);

  /* Read data */
  if ((nread = recvfrom(bund->dsock, buf, sizeof(buf),
      0, (struct sockaddr *)&naddr, &nsize)) < 0) {
    if (errno == EAGAIN)
      return;
    Log(LG_BUND, ("[%s] socket read: %s", bund->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",
      bund->name, (int16_t)linkNum, proto);

    /* Set link */
    assert(linkNum == NG_PPP_BUNDLE_LINKNUM || linkNum < bund->n_links);
    lnk = (linkNum < bund->n_links) ? bund->links[linkNum] : NULL;

    /* Input frame */
    InputFrame(linkNum, proto,
      mbwrite(mballoc(MB_FRAME_IN, nread - 4), 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 outgoing IP frame", bund->name);
    IfaceListenInput(PROTO_IP,
      mbwrite(mballoc(MB_FRAME_IN, nread), buf, nread));
    return;
  }

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

/*
 * NgFuncCtrlEvent()
 */

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

  /* Set bundle */
  bund = (Bund) cookie;
  lnk = bund->links[0];

  /* Re-register */
  EventRegister(&bund->ctrlEvent, EVENT_READ,
    bund->csock, DEV_PRIO, NgFuncCtrlEvent, bund);

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

  /* Examine message */
  switch (u.msg.header.typecookie) {
#ifdef COMPRESSION_MPPC
    case NGM_MPPC_COOKIE:
      CcpRecvMsg(&u.msg, len);
      return;
#endif
    case NGM_KSOCKET_COOKIE:		/* XXX ignore NGM_KSOCKET_CONNECT */
      if (u.msg.header.cmd == NGM_KSOCKET_CONNECT)
	return;
      break;
    default:
      break;
  }

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

/*
 * NgFuncConnect()
 */

int
NgFuncConnect(const char *path, const char *hook,
	const char *path2, const char *hook2)
{
  struct ngm_connect	cn;

  snprintf(cn.path, sizeof(cn.path), "%s", path2);
  snprintf(cn.ourhook, sizeof(cn.ourhook), "%s", hook);
  snprintf(cn.peerhook, sizeof(cn.peerhook), "%s", hook2);
  if (NgSendMsg(bund->csock, path,
      NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
    Log(LG_ERR, ("[%s] can't connect %s,%s and %s,%s: %s",
      bund->name, path, hook, path2, hook2, strerror(errno)));
    return(-1);
  }
  return(0);
}

/*
 * NgFuncDisconnect()
 */

int
NgFuncDisconnect(const char *path, const char *hook)
{
  struct ngm_rmhook	rm;

  /* Disconnect hook */
  snprintf(rm.ourhook, sizeof(rm.ourhook), "%s", hook);
  if (NgSendMsg(bund->csock, path,
      NGM_GENERIC_COOKIE, NGM_RMHOOK, &rm, sizeof(rm)) < 0) {
    Log(LG_ERR, ("[%s] can't remove hook %s from node \"%s\": %s",
      bund->name, hook, path, strerror(errno)));
    return(-1);
  }
  return(0);
}

/*
 * NgFuncWritePppFrame()
 *
 * Consumes the mbuf.
 */

int
NgFuncWritePppFrame(int linkNum, int proto, Mbuf bp)
{
  Mbuf		hdr;
  u_int16_t	temp;

  /* Prepend ppp node bypass header */
  hdr = mballoc(bp->type, 4);
  temp = htons(linkNum);
  memcpy(MBDATA(hdr), &temp, 2);
  temp = htons(proto);
  memcpy(MBDATA(hdr) + 2, &temp, 2);
  hdr->next = bp;
  bp = hdr;

  /* Debugging */
  LogDumpBp(LG_FRAME, bp,
    "[%s] xmit bypass frame link=%d proto=0x%04x",
    bund->name, (int16_t)linkNum, proto);

  /* Write frame */
  return NgFuncWriteFrame(
    linkNum == NG_PPP_BUNDLE_LINKNUM ? bund->name : bund->links[linkNum]->name,
    MPD_HOOK_PPP, bp);
}

/*
 * NgFuncWriteFrame()
 *
 * Consumes the mbuf.
 */

int
NgFuncWriteFrame(const char *label, const char *hookname, Mbuf bp)
{
  u_char		buf[sizeof(struct sockaddr_ng) + NG_HOOKLEN];
  struct sockaddr_ng	*ng = (struct sockaddr_ng *)buf;
  int			rtn;

  /* Set dest address */
  memset(&buf, 0, sizeof(buf));
  snprintf(ng->sg_data, NG_HOOKLEN + 1, "%s", hookname);
  ng->sg_family = AF_NETGRAPH;
  ng->sg_len = 3 + strlen(ng->sg_data);

  /* Write frame */
  bp = mbunify(bp);
  rtn = sendto(bund->dsock, MBDATA(bp), MBLEN(bp),
    0, (struct sockaddr *)ng, ng->sg_len);

  /* ENOBUFS can be expected on some links, e.g., ng_pptpgre(4) */
  if (rtn < 0 && errno != ENOBUFS) {
    Log(LG_ERR, ("[%s] error writing len %d frame to %s: %s",
      label, MBLEN(bp), hookname, strerror(errno)));
  }
  PFREE(bp);
  return rtn;
}

/*
 * NgFuncGetStats()
 *
 * Get (and optionally clear) link or whole bundle statistics
 */

int
NgFuncGetStats(u_int16_t linkNum, int clear, struct ng_ppp_link_stat *statp)
{
  union {
      u_char			buf[sizeof(struct ng_mesg)
				  + sizeof(struct ng_ppp_link_stat)];
      struct ng_mesg		reply;
  }				u;
  int				cmd;

  /* Get stats */
  cmd = clear ? NGM_PPP_GETCLR_LINK_STATS : NGM_PPP_GET_LINK_STATS;
  if (NgSendMsg(bund->csock, MPD_HOOK_PPP,
      NGM_PPP_COOKIE, cmd, &linkNum, sizeof(linkNum)) < 0) {
    Log(LG_ERR, ("[%s] can't get stats, link=%d: %s",
      bund->name, linkNum, strerror(errno)));
    return(-1);
  }
  if (NgRecvMsg(bund->csock, &u.reply, sizeof(u), NULL) < 0) {
    Log(LG_ERR, ("[%s] node \"%s\" reply: %s",
      bund->name, MPD_HOOK_PPP, strerror(errno)));
    return(-1);
  }
  if (statp != NULL)
    memcpy(statp, u.reply.data, sizeof(*statp));
  return(0);
}

/*
 * NgFuncErrx()
 */

static void
NgFuncErrx(const char *fmt, ...)
{
  char		buf[1024];
  va_list	args;

  va_start(args, fmt);
  vsnprintf(buf, sizeof(buf), fmt, args);
  va_end(args);
  Log(LG_ERR, ("[%s] netgraph: %s", bund ? bund->name : "", buf));
}

/*
 * NgFuncErr()
 */

static void
NgFuncErr(const char *fmt, ...)
{
  char		buf[100];
  va_list	args;

  va_start(args, fmt);
  vsnprintf(buf, sizeof(buf), fmt, args);
  va_end(args);
  Log(LG_ERR, ("[%s] netgraph: %s: %s", bund ? bund->name : "",
    buf, strerror(errno)));
}



