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

#include "ppp.h"
#include "iface.h"
#include "ipcp.h"
#include "auth.h"
#include "custom.h"
#include "ngfunc.h"
#include "netgraph.h"
#include "radius.h"
#include "util.h"
#include <sys/sockio.h>
#include <net/if.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_iface.h>
#include <netgraph/ng_bpf.h>

/*
 * DEFINITIONS
 */

  #define MAX_INTERFACES	32

  /*
   * We are in a liberal position about MSS
   * (RFC 879, section 7).
   */
  #define MAXMSS(mtu) (mtu - sizeof(struct ip) - sizeof(struct tcphdr))

/* Set menu options */

  enum {
    SET_IDLE,
    SET_SESSION,
    SET_ADDRS,
    SET_ROUTE,
    SET_MTU,
    SET_UP_SCRIPT,
    SET_DOWN_SCRIPT,
    SET_ENABLE,
    SET_DISABLE,
  };

/* Configuration options */

  enum {
    IFACE_CONF_ONDEMAND,
    IFACE_CONF_PROXY,
    IFACE_CONF_RADIUSMTU,
    IFACE_CONF_RADIUSIDLE,
    IFACE_CONF_RADIUSSESSION,
    IFACE_CONF_RADIUSROUTE,
    IFACE_CONF_RADIUSACL,
    IFACE_CONF_TCPMSSFIX,
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int	IfaceSetCommand(int ac, char *av[], void *arg);
  static void	IfaceIpIfaceUp(int ready);
  static void	IfaceIpIfaceDown(void);
  static void	IfaceIpIfaceReady(int ready);
  static void	IfaceSessionTimeout(void *arg);
  static void	IfaceIdleTimeout(void *arg);
  static void	IfaceIdleTimerExpired(void *arg);

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

  static int	IfaceAllocACL (struct acl_pool **ap, int start, char * ifname, int number);
  static int	IfaceFindACL (struct acl_pool *ap, char * ifname, int number);
  static char *	IFaceParseACL (char * src, char * ifname);

  static void	IfaceCorrectMSS(struct tcphdr *tc, ssize_t pktlen, u_int16_t maxmss);
  
/*
 * GLOBAL VARIABLES
 */

  const struct cmdtab IfaceSetCmds[] = {
    { "addrs self peer",		"Set interface addresses",
	IfaceSetCommand, NULL, (void *) SET_ADDRS },
    { "route dest[/width]",		"Add IP route",
	IfaceSetCommand, NULL, (void *) SET_ROUTE },
    { "mtu size",			"Set max allowed interface MTU",
	IfaceSetCommand, NULL, (void *) SET_MTU },
    { "up-script [progname]",		"Interface up script",
	IfaceSetCommand, NULL, (void *) SET_UP_SCRIPT },
    { "down-script [progname]",		"Interface down script",
	IfaceSetCommand, NULL, (void *) SET_DOWN_SCRIPT },
    { "idle seconds",			"Idle timeout",
	IfaceSetCommand, NULL, (void *) SET_IDLE },
    { "session seconds",		"Session timeout",
	IfaceSetCommand, NULL, (void *) SET_SESSION },
    { "enable [opt ...]",		"Enable option",
	IfaceSetCommand, NULL, (void *) SET_ENABLE },
    { "disable [opt ...]",		"Disable option",
	IfaceSetCommand, NULL, (void *) SET_DISABLE },
    { NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static const struct confinfo	gConfList[] = {
    { 0,	IFACE_CONF_ONDEMAND,		"on-demand"	},
    { 0,	IFACE_CONF_PROXY,		"proxy-arp"	},
    { 0,	IFACE_CONF_RADIUSMTU,		"radius-mtu"	},
    { 0,	IFACE_CONF_RADIUSIDLE,		"radius-idle"	},
    { 0,	IFACE_CONF_RADIUSSESSION,	"radius-session"},
    { 0,	IFACE_CONF_RADIUSROUTE,		"radius-route"	},
    { 0,	IFACE_CONF_RADIUSACL,		"radius-acl"	},
    { 0,	IFACE_CONF_TCPMSSFIX,           "tcpmssfix"	},
    { 0,	0,				NULL		},
  };

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

/*
 * IfaceInit()
 */

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

  /* Default configuration */
  iface->mtu = NG_IFACE_MTU_DEFAULT;
  iface->max_mtu = NG_IFACE_MTU_DEFAULT;
  Disable(&iface->options, IFACE_CONF_ONDEMAND);
  Disable(&iface->options, IFACE_CONF_PROXY);
  Disable(&iface->options, IFACE_CONF_TCPMSSFIX);
  Log(LG_BUND|LG_IFACE, ("[%s] using interface %s",
    bund->name, bund->iface.ifname));
}

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

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

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

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

  /* If on-demand, bring up system interface immediately and start
     listening for outgoing packets. The next outgoing packet will
     cause us to open the lower layer(s) */
  if (Enabled(&iface->options, IFACE_CONF_ONDEMAND)) {
    IfaceIpIfaceUp(0);
    NgFuncConfigBPF(bund, BPF_MODE_DEMAND);
    SetStatus(ADLG_WAN_WAIT_FOR_DEMAND, STR_READY_TO_DIAL);
    return;
  }

  /* Open lower layer(s) and wait for them to come up */
  IfaceOpenNcps();
}

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

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

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

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

  /* Take down system interface */
  if (iface->ip_up) {
    NgFuncConfigBPF(bund, BPF_MODE_OFF);
    IfaceIpIfaceDown();
  }

  /* Close lower layer(s) */
  IfaceCloseNcps();
}

/*
 * IfaceUp()
 *
 * Our underlying PPP bundle is ready for traffic.
 * Note, while this assumes we're talking about IP traffic
 * here, in general a parameter could specify which type
 * of traffic, IP vs. AppleTalk vs. whatever, along with
 * additional protocol specific information (in this case,
 * the IP addresses of each end of the point-to-point link).
 */

void
IfaceUp(struct in_addr self, struct in_addr peer)
{
  IfaceState	const iface = &bund->iface;
  struct radius	*rad = &bund->radius;  

  Log(LG_IFACE, ("[%s] IFACE: Up event", bund->name));
  SetStatus(ADLG_WAN_CONNECTED, STR_CONN_ESTAB);

  /* Open ourselves if necessary (we in effect slave off IPCP) */
  if (!iface->open) {
    Log(LG_IFACE, ("[%s] IFACE: Opening", bund->name));
    iface->open = TRUE;		/* Would call IfaceOpen(); effect is same */
  }

  /* Start Session timer */
  TimerStop(&iface->sessionTimer);

  if (Enabled(&iface->options, IFACE_CONF_RADIUSSESSION) && rad->valid) {
    iface->session_timeout = rad->session_timeout;
    Log(LG_IFACE, ("[%s] IFACE: using RADIUS session-timeout: %d seconds", 
      bund->name, iface->session_timeout));
  }

  if (iface->session_timeout > 0) {
    TimerInit(&iface->sessionTimer, "IfaceSession",
      iface->session_timeout * SECONDS, IfaceSessionTimeout, NULL);
    TimerStart(&iface->sessionTimer);
  }

  /* Start idle timer */
  TimerStop(&iface->idleTimer);

  if (Enabled(&iface->options, IFACE_CONF_RADIUSIDLE) && rad->valid) {
    iface->idle_timeout = rad->idle_timeout;
    Log(LG_IFACE, ("[%s] IFACE: using RADIUS idle-timeout: %d seconds", 
      bund->name, iface->idle_timeout));
  }

  if (iface->idle_timeout > 0) {
    char	path[NG_PATHLEN + 1];

    TimerInit(&iface->idleTimer, "IfaceIdle",
      iface->idle_timeout * SECONDS / IFACE_IDLE_SPLIT, IfaceIdleTimeout, NULL);
    TimerStart(&iface->idleTimer);
    iface->traffic[1] = TRUE;
    iface->traffic[0] = FALSE;

    /* Reset bpf node statistics */
    memset(&iface->idleStats, 0, sizeof(iface->idleStats));
    snprintf(path, sizeof(path), "%s:%s", iface->ifname, NG_IFACE_HOOK_INET);
    if (NgSendMsg(bund->csock, path, NGM_BPF_COOKIE,
	NGM_BPF_CLR_STATS, BPF_HOOK_IFACE, sizeof(BPF_HOOK_IFACE)) < 0)
      Log(LG_ERR, ("[%s] can't clear %s stats: %s",
	bund->name, NG_BPF_NODE_TYPE, strerror(errno)));
  }

  /* (Re)number interface as necessary */
  if (!iface->ip_up
    || self.s_addr != iface->self_addr.s_addr
    || peer.s_addr != iface->peer_addr.s_addr) {

    /* Bring down interface if already up */
    if (iface->ip_up)
      IfaceIpIfaceDown();

    /* Bring up interface with new addresses */
    iface->self_addr = self;
    iface->peer_addr = peer;
    IfaceIpIfaceUp(1);
  } else {
    if (!iface->ready)
      IfaceIpIfaceReady(1);
  }

  /* Customization */
#ifdef IA_CUSTOM
  CustomIpIfaceUp(iface->self_addr, iface->peer_addr);
#endif

  /* Turn on interface traffic flow */
  if (Enabled(&iface->options, IFACE_CONF_TCPMSSFIX))
    NgFuncConfigBPF(bund, BPF_MODE_MSSFIX);
  else 
    NgFuncConfigBPF(bund, BPF_MODE_ON);

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

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

void
IfaceDown(void)
{
  IfaceState	const iface = &bund->iface;

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

  /* Customization */
#ifdef IA_CUSTOM
  CustomIpIfaceDown();
#endif

  /* If we're not open, it doesn't matter to us anyway */
  TimerStop(&iface->idleTimer);
  if (!iface->open)
    return;

  /* If dial-on-demand, this is OK; just listen for future demand */
  if (Enabled(&iface->options, IFACE_CONF_ONDEMAND)) {
    SetStatus(ADLG_WAN_WAIT_FOR_DEMAND, STR_READY_TO_DIAL);
    NgFuncConfigBPF(bund, BPF_MODE_DEMAND);
    IfaceIpIfaceReady(0);
    IfaceCloseNcps();
    return;
  }

  /* Take down system interface */
  if (iface->ip_up)
    IfaceIpIfaceDown();
  NgFuncConfigBPF(bund, BPF_MODE_OFF);
}

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

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

  /* Does this count as demand traffic? */
  if (isDemand)
    iface->traffic[0] = TRUE;

  /* Get FSM for protocol (for now, we know it's IP) */
  assert(proto == PROTO_IP);
  fsm = &bund->ipcp.fsm;

  /* Maybe do dial-on-demand here */
  if (OPEN_STATE(fsm->state)) {
    if (bund->bm.n_up > 0) {
      if (Enabled(&iface->options, IFACE_CONF_TCPMSSFIX)) {
	struct ip	*iphdr;
	int		plen, hlen;

        if (proto == PROTO_IP) {
	  iphdr = (struct ip *)MBDATA(pkt);
	  hlen = iphdr->ip_hl << 2;
	  plen = plength(pkt);
	  /*
	   * Check for MSS option only for TCP packets with zero fragment offsets
	   * and correct total and header lengths.
	   */
	  if (iphdr->ip_p == IPPROTO_TCP
	      && (ntohs(iphdr->ip_off) & IP_OFFMASK) == 0
	      &&  ntohs(iphdr->ip_len) == plen
	      && hlen <= plen
	      && plen - hlen >= sizeof(struct tcphdr))
	    IfaceCorrectMSS((struct tcphdr *)(MBDATA(pkt) + hlen), plen - hlen,
	      MAXMSS(iface->mtu));
        }
      } else
	Log(LG_IFACE, ("[%s] unexpected outgoing packet, len=%d",
	  bund->name, MBLEN(pkt)));
      NgFuncWriteFrame(bund->name, MPD_HOOK_DEMAND_TAP, pkt);
    } else {
      IfaceCachePkt(proto, pkt);
    }
  } else if (iface->open && isDemand) {
    Log(LG_IFACE, ("[%s] outgoing packet is demand", bund->name));
    RecordLinkUpDownReason(NULL, 1, STR_DEMAND, "%s", AsciifyPacket(pkt));
    IfaceOpenNcps();
    IfaceCachePkt(proto, pkt);
  } else {
    PFREE(pkt);
  }
}

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

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

    rp1 = Malloc(MB_UTIL, sizeof(struct acl_pool));
    strncpy(rp1->ifname, ifname, IFNAMSIZ);
    rp1->acl_number = number;

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

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

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

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

/*
 * IFaceParseACL ()
 *
 * Parces ACL and replaces %r, %p and %q macroses 
 * by the real numbers of rules, queues and pipes.
 */

static char *
IFaceParseACL (char * src, char * ifname)
{
    char *buf,*buf1;
    char *begin,*param,*end;
    char t;
    int num,real_number;
    struct acl_pool *ap;
    
    buf = Malloc(MB_UTIL,ACL_LEN+1);
    buf1 = Malloc(MB_UTIL,ACL_LEN+1);
    
    strncpy(buf,src,ACL_LEN);
    do {
        end = buf;
	begin = strsep(&end,"%");
	param = strsep(&end," ");
	if (param != NULL) {
	    if (sscanf(param,"%c%d",&t,&num) == 2) {
		switch (t) {
		    case 'r':
			ap = rule_pool;
			break;
		    case 'p':
			ap = pipe_pool;
			break;
		    case 'q':
			ap = queue_pool;
			break;
		    default:
			ap = NULL;
		};
		real_number = IfaceFindACL(ap,ifname,num);
		if (end != NULL) {
		    snprintf(buf1,ACL_LEN,"%s%d %s",begin,real_number,end);
		} else {
		    snprintf(buf1,ACL_LEN,"%s%d",begin,real_number);
		};
		strncpy(buf,buf1,ACL_LEN);
	    };
	};
    } while (end != NULL);
    Freee(buf1);
    return(buf);
};

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

static void
IfaceIpIfaceUp(int ready)
{
  IfaceState		const iface = &bund->iface;
  int			k,i;
  struct sockaddr_dl	hwa;
  char			hisaddr[20];
  u_char		*ether;
  struct radius_acl	*acls;
  char			*buf;

  /* Sanity */
  assert(!iface->ip_up);

  /* For good measure */
  BundUpdateParams();

  /* Set addresses and bring interface up */
  snprintf(hisaddr, sizeof(hisaddr), "%s", inet_ntoa(iface->peer_addr));
  ExecCmd(LG_IFACE, "%s %s %s %s netmask 0xffffffff %slink0",
    PATH_IFCONFIG, iface->ifname, inet_ntoa(iface->self_addr), hisaddr,
    ready ? "-" : "");
  iface->ready = ready;

  /* Proxy ARP for peer if desired and peer's address is known */
  iface->proxy_addr.s_addr = 0;
  if (Enabled(&iface->options, IFACE_CONF_PROXY)) {
    if (iface->peer_addr.s_addr == 0) {
      Log(LG_IFACE,
	("[%s] can't proxy arp for %s",
	bund->name, inet_ntoa(iface->peer_addr)));
    } else if (IfaceGetEther(&iface->peer_addr, &hwa) < 0) {
      Log(LG_IFACE,
	("[%s] no interface to proxy arp on for %s",
	bund->name, inet_ntoa(iface->peer_addr)));
    } else {
      ether = (u_char *) LLADDR(&hwa);
      if (ExecCmd(LG_IFACE,
	  "%s -s %s %x:%x:%x:%x:%x:%x pub",
	  PATH_ARP, inet_ntoa(iface->peer_addr),
	  ether[0], ether[1], ether[2],
	  ether[3], ether[4], ether[5]) == 0)
	iface->proxy_addr = iface->peer_addr;
    }
  }

  /* Add loopback route */
  ExecCmd(LG_IFACE, "%s add %s -iface lo0",
    PATH_ROUTE, inet_ntoa(iface->self_addr));

  if (Enabled(&iface->options, IFACE_CONF_RADIUSROUTE)) {
    for (i=0; (i < bund->radius.n_routes) && (bund->iface.n_routes < IFACE_MAX_ROUTES); i++) {
      memcpy(&(iface->routes[iface->n_routes++]), &(bund->radius.routes[i]), sizeof(struct ifaceroute));
    };
    Log(LG_IFACE, ("[%s] IFACE: using %d RADIUS routes", 
      bund->name, bund->radius.n_routes));
  }
  
  /* Add routes */
  for (k = 0; k < iface->n_routes; k++) {
    IfaceRoute	const r = &iface->routes[k];
    char	nmbuf[40], peerbuf[40];

    if (r->netmask.s_addr) {
      snprintf(nmbuf, sizeof(nmbuf),
	" -netmask 0x%08lx", (u_long)ntohl(r->netmask.s_addr));
    } else
      *nmbuf = 0;
    snprintf(peerbuf, sizeof(peerbuf), "%s", inet_ntoa(iface->peer_addr));
    r->ok = (ExecCmd(LG_IFACE, "%s add %s %s%s",
      PATH_ROUTE, inet_ntoa(r->dest), peerbuf, nmbuf) == 0);
  }

  /* Add ACLs */
  if (Enabled(&iface->options, IFACE_CONF_RADIUSACL)) {
    Log(LG_IFACE, ("[%s] IFACE: using RADIUS ACLs", 
      bund->name));
    /* Allocate ACLs */
    acls = bund->radius.acl_pipe;
    while (acls != NULL) {
	IfaceAllocACL(&pipe_pool,pipe_pool_start,iface->ifname,acls->number);
	acls = acls->next;
    };
    acls = bund->radius.acl_queue;
    while (acls != NULL) {
	IfaceAllocACL(&queue_pool,queue_pool_start,iface->ifname,acls->number);
	acls = acls->next;
    };
    acls = bund->radius.acl_rule;
    while (acls != NULL) {
	IfaceAllocACL(&rule_pool,rule_pool_start,iface->ifname,acls->number);
	acls = acls->next;
    };

    /* Set ACLs */
    acls = bund->radius.acl_pipe;
    while (acls != NULL) {
	i=IfaceFindACL(pipe_pool,iface->ifname,acls->number);

	buf=IFaceParseACL(acls->rule,iface->ifname);
	ExecCmd(LG_IFACE, "%s pipe %d config %s",
    	    PATH_IPFW, i, acls->rule);
	Freee(buf);

	acls = acls->next;
    };
    acls = bund->radius.acl_queue;
    while (acls != NULL) {
	i = IfaceFindACL(queue_pool,iface->ifname,acls->number);

	buf = IFaceParseACL(acls->rule,iface->ifname);
	ExecCmd(LG_IFACE, "%s queue %d config %s",
    	    PATH_IPFW, i, buf);
	Freee(buf);
	
	acls = acls->next;
    };
    acls = bund->radius.acl_rule;
    while (acls != NULL) {
	i = IfaceFindACL(rule_pool,iface->ifname,acls->number);

	buf = IFaceParseACL(acls->rule,iface->ifname);
	ExecCmd(LG_IFACE, "%s add %d %s via %s",
    	    PATH_IPFW, i, buf, iface->ifname);
	Freee(buf);
	
	acls = acls->next;
    };
  }

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

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

    snprintf(peerbuf, sizeof(peerbuf), "%s", inet_ntoa(iface->peer_addr));
    ExecCmd(LG_IFACE, "%s %s inet %s %s %s %s %s",
      iface->up_script, iface->ifname, inet_ntoa(iface->self_addr),
      peerbuf, 
      *bund->peer_authname ? bund->peer_authname : bund->conf.authname,
      ns1buf, ns2buf);
  }

  /* Done */
  iface->ip_up = TRUE;
}

/*
 * IfaceIpIfaceReady()
 *
 * (Un)set the interface IFF_LINK0 flag because IPCP is now up or down.
 * Call this when the addressing is already set correctly and you
 * just want to change the flag.
 */

static void
IfaceIpIfaceReady(int ready)
{
  IfaceState	const iface = &bund->iface;

  ExecCmd(LG_IFACE, "%s %s %slink0",
    PATH_IFCONFIG, iface->ifname, ready ? "-" : "");
  iface->ready = ready;
}

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

static void
IfaceIpIfaceDown(void)
{
  IfaceState	const iface = &bund->iface;
  int		k;
  struct acl_pool	**rp,*rp1;

  /* Sanity */
  assert(iface->ip_up);

  /* Call "down" script */
  if (*iface->down_script) {
    ExecCmd(LG_IFACE, "%s %s inet %s",
      iface->down_script, iface->ifname, 
      *bund->peer_authname ? bund->peer_authname : bund->conf.authname);
  }

  /* Remove ACLs */
  if (Enabled(&iface->options, IFACE_CONF_RADIUSACL)) {
	/* Remove rule ACLs */
	rp = &rule_pool;
	while (*rp != NULL) {
	    if (strncmp((*rp)->ifname,iface->ifname,IFNAMSIZ) == 0) {
		ExecCmd(LG_IFACE, "%s delete %d",
    		    PATH_IPFW, (*rp)->real_number);
		rp1 = *rp;
		*rp = (*rp)->next;
		Freee(rp1);
	    } else {
		rp = &((*rp)->next);
	    };
	};
	/* Remove queue ACLs */
	rp = &queue_pool;
	while (*rp != NULL) {
	    if (strncmp((*rp)->ifname,iface->ifname,IFNAMSIZ) == 0) {
		ExecCmd(LG_IFACE, "%s queue %d delete",
    		    PATH_IPFW, (*rp)->real_number);
		rp1 = *rp;
		*rp = (*rp)->next;
		Freee(rp1);
	    } else {
		rp = &((*rp)->next);
	    };
	};
	/* Remove pipe ACLs */
	rp = &pipe_pool;
	while (*rp != NULL) {
	    if (strncmp((*rp)->ifname,iface->ifname,IFNAMSIZ) == 0) {
		ExecCmd(LG_IFACE, "%s pipe %d delete",
    		    PATH_IPFW, (*rp)->real_number);
		rp1 = *rp;
		*rp = (*rp)->next;
		Freee(rp1);
	    } else {
		rp = &((*rp)->next);
	    };
	};
  };

  /* Delete routes */
  for (k = 0; k < iface->n_routes; k++) {
    IfaceRoute	const r = &iface->routes[k];
    char	nmbuf[40], peerbuf[40];

    if (!r->ok)
      continue;
    if (r->netmask.s_addr) {
      snprintf(nmbuf, sizeof(nmbuf),
	" -netmask 0x%08lx", (u_long)ntohl(r->netmask.s_addr));
    } else
      *nmbuf = 0;
    snprintf(peerbuf, sizeof(peerbuf), "%s", inet_ntoa(iface->peer_addr));
    ExecCmd(LG_IFACE, "%s delete %s %s%s",
      PATH_ROUTE, inet_ntoa(r->dest), peerbuf, nmbuf);
    r->ok = 0;
  }

  if (Enabled(&iface->options, IFACE_CONF_RADIUSROUTE)) {
    iface->n_routes=0;
  };

  /* Delete loopback route */
  ExecCmd(LG_IFACE, "%s delete %s -iface lo0",
    PATH_ROUTE, inet_ntoa(iface->self_addr));

  /* Delete any proxy arp entry */
  if (iface->proxy_addr.s_addr)
    ExecCmd(LG_IFACE, "%s -d %s", PATH_ARP, inet_ntoa(iface->proxy_addr));
  iface->proxy_addr.s_addr = 0;

  /* Bring down system interface */
  ExecCmd(LG_IFACE, "%s %s down delete -link0", PATH_IFCONFIG, iface->ifname);
  iface->ready = 0;

  /* Done */
  iface->ip_up = FALSE;
}

/*
 * IfaceIdleTimeout()
 */

static void
IfaceIdleTimeout(void *arg)
{
  IfaceState			const iface = &bund->iface;
  char				path[NG_PATHLEN + 1];
  struct ng_bpf_hookstat	oldStats;
  union {
      u_char			buf[sizeof(struct ng_mesg) + sizeof(oldStats)];
      struct ng_mesg		reply;
  }				u;
  int				k;

  /* Get updated bpf node traffic statistics */
  oldStats = iface->idleStats;
  snprintf(path, sizeof(path), "%s:%s", iface->ifname, NG_IFACE_HOOK_INET);
  if (NgSendMsg(bund->csock, path, NGM_BPF_COOKIE,
      NGM_BPF_GET_STATS, BPF_HOOK_IFACE, sizeof(BPF_HOOK_IFACE)) < 0) {
    Log(LG_ERR, ("[%s] can't get %s stats: %s",
      bund->name, NG_BPF_NODE_TYPE, strerror(errno)));
    return;
  }
  if (NgRecvMsg(bund->csock, &u.reply, sizeof(u), NULL) < 0) {
    Log(LG_ERR, ("[%s] node \"%s\" reply: %s",
      bund->name, path, strerror(errno)));
    return;
  }
  memcpy(&iface->idleStats, u.reply.data, sizeof(iface->idleStats));

  /* Mark current traffic period if there was traffic */
  if (iface->idleStats.recvMatchFrames > oldStats.recvMatchFrames)
    iface->traffic[0] = TRUE;
  else {		/* no demand traffic for a whole idle timeout period? */
    for (k = 0; k < IFACE_IDLE_SPLIT && !iface->traffic[k]; k++);
    if (k == IFACE_IDLE_SPLIT) {
      IfaceIdleTimerExpired(NULL);
      return;
    }
  }

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

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

/*
 * IfaceIdleTimerExpired()
 *
 * The idle timeout expired with no demand traffic. Shutdown the
 * link gracefully. Give custom code a chance to do any last minute
 * things before shutting down though. At this point, the shutdown
 * is going to happen, even if there is subsequent demand.
 */

static void
IfaceIdleTimerExpired(void *arg)
{
  IfaceState	const iface = &bund->iface;
#ifdef IA_CUSTOM
  int		delay;
#endif

  /* We already did the final short delay, really shut down now */
  if (arg != NULL) {
    RecordLinkUpDownReason(NULL, 0, STR_IDLE_TIMEOUT, NULL);
    IfaceCloseNcps();
    return;
  }

  /* Idle timeout first detected */
  Log(LG_BUND, ("[%s] idle timeout after %d seconds",
    bund->name, iface->idleTimer.load * IFACE_IDLE_SPLIT / SECONDS));

  /* Get delay and do it */
#ifdef IA_CUSTOM
  if ((delay = CustomIdleTimeoutAction()) > 0) {
    TimerInit(&iface->idleTimer, "IfaceIdle",
      delay * SECONDS, IfaceIdleTimerExpired, (void *)1);
    TimerStart(&iface->idleTimer);
  } else
#endif
  IfaceIdleTimerExpired((void *)1);
}

/*
 * IfaceSessionTimeout()
 */

static void
IfaceSessionTimeout(void *arg)
{
  Log(LG_BUND, ("[%s] session timeout ", bund->name));

  RecordLinkUpDownReason(NULL, 0, STR_SESSION_TIMEOUT, NULL);

  if (Enabled(&bund->conf.options, BUND_CONF_NORETRY)) {
    IfaceCloseNcps();
  } else {
    BundCloseLinks();
  }

}

/*
 * IfaceOpenNcps()
 */

void
IfaceOpenNcps(void)
{
  IpcpOpen();
}

/*
 * IfaceCloseNcps()
 */

void
IfaceCloseNcps(void)
{
  IfaceState	const iface = &bund->iface;

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

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

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

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

  /* Release previously cached packet, if any, and save this one */
  if (iface->dodCache.pkt)
    PFREE(iface->dodCache.pkt);
  iface->dodCache.pkt = pkt;
  iface->dodCache.proto = proto;
  iface->dodCache.ts = time(NULL);
}

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

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

  if (iface->dodCache.pkt) {
    if (iface->dodCache.ts + MAX_DOD_CACHE_DELAY < time(NULL))
      PFREE(iface->dodCache.pkt);
    else {
      assert(iface->dodCache.proto == PROTO_IP);
      if (NgFuncWriteFrame(bund->name,
	  MPD_HOOK_DEMAND_TAP, iface->dodCache.pkt) < 0) {
	Log(LG_ERR, ("[%s] can't write cached pkt: %s",
	  bund->name, strerror(errno)));
      }
    }
    iface->dodCache.pkt = NULL;
  }
}

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

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

	memcpy(&iphdr, MBDATA(pkt), sizeof(iphdr));
	switch (ip->ip_p) {
	  case IPPROTO_IGMP:		/* No multicast stuff */
	    return(0);
	  case IPPROTO_ICMP:
	    {
	      struct icmp	*const icmp =
		(struct icmp *) ((u_int32_t *) ip + ip->ip_hl);

	      switch (icmp->icmp_type)	/* No ICMP replies */
	      {
		case ICMP_ECHOREPLY:
		case ICMP_UNREACH:
		case ICMP_REDIRECT:
		  return(0);
		default:
		  break;
	      }
	    }
	    break;
	  case IPPROTO_UDP:
	    {
	      struct udphdr	*const udp =
		(struct udphdr *) ((u_int32_t *) ip + ip->ip_hl);

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

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

/*
 * IfaceSetCommand()
 */

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

  if (ac == 0)
    return(-1);
  switch ((intptr_t)arg) {
    case SET_IDLE:
      iface->idle_timeout = atoi(*av);
      break;
    case SET_SESSION:
      iface->session_timeout = atoi(*av);
      break;
    case SET_ADDRS:
      {
	struct in_addr	self_addr;
	struct in_addr	peer_addr;

	/* Parse */
	if (ac != 2)
	  return(-1);
	if (!inet_aton(av[0], &self_addr)) {
	  Log(LG_ERR, ("mpd: bad IP address \"%s\"", av[0]));
	  break;
	}
	if (!inet_aton(av[1], &peer_addr)) {
	  Log(LG_ERR, ("mpd: bad IP address \"%s\"", av[1]));
	  break;
	}

	/* OK */
	iface->self_addr = self_addr;
	iface->peer_addr = peer_addr;
      }
      break;

    case SET_ROUTE:
      {
	struct ifaceroute	r;
	struct in_range		range;

	/* Check */
	if (ac != 1)
	  return(-1);
	if (iface->n_routes >= IFACE_MAX_ROUTES) {
	  Log(LG_ERR, ("iface: too many routes"));
	  break;
	}

	/* Get dest address */
	if (!strcasecmp(av[0], "default"))
	  memset(&range, 0, sizeof(range));
	else if (!ParseAddr(av[0], &range)) {
	  Log(LG_ERR, ("route: bad dest address \"%s\"", av[0]));
	  break;
	}
	r.netmask.s_addr = range.width ?
	  htonl(~0 << (32 - range.width)) : 0;
	r.dest.s_addr = (range.ipaddr.s_addr & r.netmask.s_addr);
	iface->routes[iface->n_routes++] = r;
      }
      break;

    case SET_MTU:
      {
	int	max_mtu;

	max_mtu = atoi(av[0]);
	if (max_mtu < IFACE_MIN_MTU || max_mtu > IFACE_MAX_MTU) {
	  Log(LG_ERR, ("invalid interface mtu %d", max_mtu));
	  break;
	}
	iface->max_mtu = max_mtu;
      }
      break;

    case SET_UP_SCRIPT:
      switch (ac) {
	case 0:
	  *iface->up_script = 0;
	  break;
	case 1:
	  snprintf(iface->up_script,
	    sizeof(iface->up_script), "%s", av[0]);
	  break;
	default:
	  return(-1);
      }
      break;

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

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

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

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

/*
 * IfaceStat()
 */

int
IfaceStat(int ac, char *av[], void *arg)
{
  IfaceState	const iface = &bund->iface;
  int		k;

  printf("Interface %s:\n", iface->ifname);
  printf("\tStatus       : %s\n", iface->open ? "OPEN" : "CLOSED");
  printf("\tIP Addresses : %s -> ", inet_ntoa(iface->self_addr));
  printf("%s\n", inet_ntoa(iface->peer_addr));
  printf("\tMaximum MTU  : %d bytes\n", iface->max_mtu);
  printf("\tCurrent MTU  : %d bytes\n", iface->mtu);
  printf("\tIdle timeout : %d seconds\n", iface->idle_timeout);
  printf("\tSession timeout : %d seconds\n", iface->session_timeout);
  printf("\tEvent scripts: UP: \"%s\"  DOWN: \"%s\"\n",
    *iface->up_script ? iface->up_script : "<none>",
    *iface->down_script ? iface->down_script : "<none>");
  printf("Static routes via peer:\n");
  for (k = 0; k < iface->n_routes; k++) {
    printf("\t%s ", iface->routes[k].dest.s_addr ?
      inet_ntoa(iface->routes[k].dest) : "default");
    if (iface->routes[k].netmask.s_addr)
      printf("\tnetmask %s", inet_ntoa(iface->routes[k].netmask));
    printf("\n");
  }
  printf("Interface level options:\n");
  OptStat(&iface->options, gConfList);
  return(0);
}

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

void
IfaceSetMTU(int mtu, int speed)
{
  IfaceState	const iface = &bund->iface;
  struct ifreq	ifr;
  int		s;
  struct radius	*rad = &bund->radius;

  /* Get socket */
  if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
    Perror("socket");
    DoExit(EX_ERRDEAD);
  }

  if (Enabled(&iface->options, IFACE_CONF_RADIUSMTU) && rad->valid && (rad->mtu > 0)) {
    iface->max_mtu = rad->mtu;
    Log(LG_IFACE, ("[%s] IFACE: using RADIUS max. mtu: %d",
      bund->name, iface->max_mtu));
  }

  /* Limit MTU to configured maximum */
  if (mtu > iface->max_mtu) {
      mtu = iface->max_mtu;
  }

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

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

/*
 * IfaceGetAnyIpAddress()
 *
 * Get any non-loopback IP address owned by this machine
 * Prefer addresses from non-point-to-point interfaces.
 */

int
IfaceGetAnyIpAddress(struct in_addr *ipaddr)
{
  int			s, p2p = 0;
  struct in_addr	ipa = { 0 };
  struct ifreq		*ifr, *ifend;
  struct ifreq		ifreq;
  struct ifconf		ifc;
  struct ifreq		ifs[MAX_INTERFACES];

  /* Get interface list */
  if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
    Perror("socket");
    DoExit(EX_ERRDEAD);
  }

  ifc.ifc_len = sizeof(ifs);
  ifc.ifc_req = ifs;
  if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
    Perror("ioctl(SIOCGIFCONF)");
    close(s);
    return(-1);
  }

  for (ifend = (struct ifreq *)(void *)(ifc.ifc_buf + ifc.ifc_len),
	ifr = ifc.ifc_req;
      ifr < ifend;
      ifr = (struct ifreq *)(void *)((char *) &ifr->ifr_addr
	+ MAX(ifr->ifr_addr.sa_len, sizeof(ifr->ifr_addr)))) {
    if (ifr->ifr_addr.sa_family == AF_INET) {

      /* Check that the interface is up, and not loopback; prefer non-p2p */
      strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name));
      if (ioctl(s, SIOCGIFFLAGS, &ifreq) < 0)
	continue;
      if ((ifreq.ifr_flags & (IFF_UP|IFF_LOOPBACK)) != IFF_UP)
	continue;
      if ((ifreq.ifr_flags & IFF_POINTOPOINT) && ipa.s_addr && !p2p)
	continue;

      /* Save IP address and interface name */
      ipa = ((struct sockaddr_in *)(void *)&ifr->ifr_addr)->sin_addr;
      p2p = (ifreq.ifr_flags & IFF_POINTOPOINT) != 0;
    }
  }
  close(s);

  /* Found? */
  if (ipa.s_addr == 0)
    return(-1);
  *ipaddr = ipa;
  return(0);
}

/*
 * IfaceGetEther()
 *
 * Get the hardware address of an interface on the the same subnet as addr.
 * If addr == NULL, finds the address of any local ethernet interface.
 */

int
IfaceGetEther(struct in_addr *addr, struct sockaddr_dl *hwaddr)
{
  int			s;
  struct ifreq		*ifr, *ifend, *ifp;
  u_long		ina, mask;
  struct ifreq		ifreq;
  struct ifconf		ifc;
  struct ifreq		ifs[MAX_INTERFACES];

  /* Get interface list */
  if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
    Perror("socket");
    DoExit(EX_ERRDEAD);
  }

  ifc.ifc_len = sizeof(ifs);
  ifc.ifc_req = ifs;
  if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
    Perror("ioctl(SIOCGIFCONF)");
    close(s);
    return(-1);
  }

  /*
   * Scan through looking for an interface with an IP
   * address on same subnet as `addr'.
   */
  for (ifend = (struct ifreq *)(void *)(ifc.ifc_buf + ifc.ifc_len),
	ifr = ifc.ifc_req;
      ifr < ifend;
      ifr = (struct ifreq *)(void *)((char *) &ifr->ifr_addr
	+ MAX(ifr->ifr_addr.sa_len, sizeof(ifr->ifr_addr)))) {
    if (ifr->ifr_addr.sa_family == AF_INET) {

      /* Save IP address and interface name */
      ina = ((struct sockaddr_in *)(void *)&ifr->ifr_addr)->sin_addr.s_addr;
      strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name));

      /* Check that the interface is up, and not point-to-point or loopback */
      if (ioctl(s, SIOCGIFFLAGS, &ifreq) < 0)
	continue;
      if ((ifreq.ifr_flags &
	  (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT|IFF_LOOPBACK|IFF_NOARP))
	  != (IFF_UP|IFF_BROADCAST))
	continue;

      /* Get its netmask and check that it's on the right subnet */
      if (ioctl(s, SIOCGIFNETMASK, &ifreq) < 0)
	continue;
      mask = ((struct sockaddr_in *)(void *)&ifreq.ifr_addr)->sin_addr.s_addr;
      if (addr && (addr->s_addr & mask) != (ina & mask))
	continue;

      /* OK */
      break;
    }
  }
  close(s);

  /* Found? */
  if (ifr >= ifend)
    return(-1);

  /* Now scan again looking for a link-level address for this interface */
  for (ifp = ifr, ifr = ifc.ifc_req; ifr < ifend; ) {
    if (strcmp(ifp->ifr_name, ifr->ifr_name) == 0
	&& ifr->ifr_addr.sa_family == AF_LINK) {
      memcpy(hwaddr, (struct sockaddr_dl *)(void *)&ifr->ifr_addr,
	sizeof(*hwaddr));
      return(0);
    }
    ifr = (struct ifreq *)(void *)((char *)&ifr->ifr_addr
      + MAX(ifr->ifr_addr.sa_len, sizeof(ifr->ifr_addr)));
  }

  /* Not found! */
  return(-1);
}

static void
IfaceCorrectMSS(struct tcphdr *tc, ssize_t pktlen, u_int16_t maxmss)
{
  int hlen, olen, optlen;
  u_char *opt;
  u_int16_t *mss;
  int accumulate;

  hlen = tc->th_off << 2;

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

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

  for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1);
	olen > 0; olen -= optlen, opt += optlen) {
    if (*opt == TCPOPT_EOL)
      break;
    else if (*opt == TCPOPT_NOP)
      optlen = 1;
    else {
      optlen = *(opt + 1);
      if (optlen <= 0 || optlen > olen)
	break;
      if (*opt == TCPOPT_MAXSEG) {
	if (optlen != TCPOLEN_MAXSEG)
	  continue;
	mss = (u_int16_t *)(opt + 2);
	if (ntohs(*mss) > maxmss) {
#if 0
	  Log(LG_IFACE, ("[%s] MSS: %u -> %u",
	    bund->name, ntohs(*mss), maxmss));
#endif
	  accumulate = *mss;
	  *mss = htons(maxmss);
	  accumulate -= *mss;
	  ADJUST_CHECKSUM(accumulate, tc->th_sum);
	}
      }
    }
  }
}
                                                                                                 


syntax highlighted by Code2HTML, v. 0.9.1