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


/*
 * l2tp.c
 *
 * Written by Alexander Motin <mav@FreeBSD.org>
 */

#include "ppp.h"
#include "phys.h"
#include "mbuf.h"
#include "ngfunc.h"
#include "l2tp.h"
#include "l2tp_avp.h"
#include "l2tp_ctrl.h"
#include "log.h"
#include "util.h"

#include <sys/types.h>
#ifdef NOLIBPDEL
#include "contrib/libpdel/util/ghash.h"
#else
#include <pdel/util/ghash.h>
#endif

#include <net/ethernet.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_socket.h>
#include <netgraph/ng_ksocket.h>
#include <netgraph/ng_l2tp.h>
#include <netgraph.h>
#include <fnmatch.h>

/*
 * DEFINITIONS
 */

  #define L2TP_MTU              1600
  #define L2TP_MRU		L2TP_MTU
  
  #define L2TP_PORT		1701

  #define L2TP_CALL_MIN_BPS	56000
  #define L2TP_CALL_MAX_BPS	64000

  struct l2tp_server {
    struct u_addr	self_addr;	/* self IP address */
    in_port_t		self_port;	/* self port */
    int			refs;
    int			sock;		/* server listen socket */
    EventRef		event;		/* listen for data messages */
  };
  
  struct l2tp_tun {
    struct u_addr	self_addr;	/* self IP address */
    struct u_addr	peer_addr;	/* peer IP address */
    char                peer_iface[IFNAMSIZ];	/* Peer iface */
    u_char		peer_mac_addr[6];	/* Peer MAC address */
    in_port_t		self_port;	/* self port */
    in_port_t		peer_port;	/* peer port */
    u_char		connected;	/* control connection is connected */
    u_char		alive;		/* control connection is not dying */
    u_int		active_sessions;/* number of calls in this sunnels */
    struct ppp_l2tp_ctrl *ctrl;		/* control connection for this tunnel */
  };
  
  struct l2tpinfo {
    struct {
	struct u_addr	self_addr;	/* self IP address */
	struct u_range	peer_addr;	/* Peer IP addresses allowed */
	in_port_t	self_port;	/* self port */
	in_port_t	peer_port;	/* Peer port required (or zero) */
	struct optinfo	options;
	char		callingnum[64];	/* L2TP phone number to use */
	char		callednum[64];	/* L2TP phone number to use */
	char 		hostname[MAXHOSTNAMELEN]; /* L2TP local hostname */
	char		secret[64];	/* L2TP tunnel secret */
	char		*fqdn_peer_addr;	/* FQDN Peer address */
	char		*peer_mask;	/* L2TP peer hostname mask */
    } conf;
    u_char		opened;		/* L2TP opened by phys */
    u_char		incoming;	/* Call is incoming vs. outgoing */
    u_char		outcall;	/* incall or outcall */
    u_char		sync;		/* sync or async call */
    struct l2tp_server	*server;	/* server associated with link */
    struct l2tp_tun	*tun;		/* tunnel associated with link */
    struct ppp_l2tp_sess *sess;		/* current session for this link */
    char		callingnum[64];	/* current L2TP phone number */
    char		callednum[64];	/* current L2TP phone number */
  };
  typedef struct l2tpinfo	*L2tpInfo;

  /* Set menu options */
  enum {
    SET_SELFADDR,
    SET_PEERADDR,
    SET_CALLINGNUM,
    SET_CALLEDNUM,
    SET_HOSTNAME,
    SET_PEERMASK,
    SET_SECRET,
    SET_ENABLE,
    SET_DISABLE
  };

  /* Binary options */
  enum {
    L2TP_CONF_OUTCALL,		/* when originating, calls are "outgoing" */
    L2TP_CONF_HIDDEN,		/* enable AVP hidding */
    L2TP_CONF_LENGTH,		/* enable Length field in data packets */
    L2TP_CONF_DATASEQ,		/* enable sequence fields in data packets */
    L2TP_CONF_RESOLVE_ONCE	/* Only once resolve peer_addr */
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int	L2tpTInit(void);
  static void	L2tpTShutdown(void);
  static int	L2tpInit(Link l);
  static int	L2tpInst(Link l, Link lt);
  static void	L2tpOpen(Link l);
  static void	L2tpClose(Link l);
  static void	L2tpShutdown(Link l);
  static void	L2tpStat(Context ctx);
  static int	L2tpOriginated(Link l);
  static int	L2tpIsSync(Link l);
  static int	L2tpSetAccm(Link l, u_int32_t xmit, u_int32_t recv);
  static int	L2tpSelfName(Link l, void *buf, size_t buf_len);
  static int	L2tpPeerName(Link l, void *buf, size_t buf_len);
  static int	L2tpSelfAddr(Link l, void *buf, size_t buf_len);
  static int	L2tpPeerAddr(Link l, void *buf, size_t buf_len);
  static int	L2tpPeerPort(Link l, void *buf, size_t buf_len);
  static int	L2tpPeerMacAddr(Link l, void *buf, size_t buf_len);
  static int	L2tpPeerIface(Link l, void *buf, size_t buf_len);
  static int	L2tpCallingNum(Link l, void *buf, size_t buf_len);
  static int	L2tpCalledNum(Link l, void *buf, size_t buf_len);
  static int	L2tpSetCallingNum(Link l, void *buf);
  static int	L2tpSetCalledNum(Link l, void *buf);

  static void	L2tpHookUp(Link l);
  static void	L2tpUnhook(Link l);

  static void	L2tpNodeUpdate(Link l);
  static int	L2tpListen(Link l);
  static void	L2tpUnListen(Link l);
  static int	L2tpSetCommand(Context ctx, int ac, const char *const av[], const void *arg);

  /* L2TP control callbacks */
  static ppp_l2tp_ctrl_connected_t	ppp_l2tp_ctrl_connected_cb;
  static ppp_l2tp_ctrl_terminated_t	ppp_l2tp_ctrl_terminated_cb;
  static ppp_l2tp_ctrl_destroyed_t	ppp_l2tp_ctrl_destroyed_cb;
  static ppp_l2tp_initiated_t		ppp_l2tp_initiated_cb;
  static ppp_l2tp_connected_t		ppp_l2tp_connected_cb;
  static ppp_l2tp_terminated_t		ppp_l2tp_terminated_cb;
  static ppp_l2tp_set_link_info_t	ppp_l2tp_set_link_info_cb;

  static const struct ppp_l2tp_ctrl_cb ppp_l2tp_server_ctrl_cb = {
	ppp_l2tp_ctrl_connected_cb,
	ppp_l2tp_ctrl_terminated_cb,
	ppp_l2tp_ctrl_destroyed_cb,
	ppp_l2tp_initiated_cb,
	ppp_l2tp_connected_cb,
	ppp_l2tp_terminated_cb,
	ppp_l2tp_set_link_info_cb,
	NULL,
  };

/*
 * GLOBAL VARIABLES
 */

  const struct phystype	gL2tpPhysType = {
    .name		= "l2tp",
    .descr		= "Layer Two Tunneling Protocol",
    .mtu		= L2TP_MTU,
    .mru		= L2TP_MRU,
    .tmpl		= 1,
    .tinit		= L2tpTInit,
    .tshutdown		= L2tpTShutdown,
    .init		= L2tpInit,
    .inst		= L2tpInst,
    .open		= L2tpOpen,
    .close		= L2tpClose,
    .update		= L2tpNodeUpdate,
    .shutdown		= L2tpShutdown,
    .showstat		= L2tpStat,
    .originate		= L2tpOriginated,
    .issync		= L2tpIsSync,
    .setaccm 		= L2tpSetAccm,
    .setcallingnum	= L2tpSetCallingNum,
    .setcallednum	= L2tpSetCalledNum,
    .selfname		= L2tpSelfName,
    .peername		= L2tpPeerName,
    .selfaddr		= L2tpSelfAddr,
    .peeraddr		= L2tpPeerAddr,
    .peerport		= L2tpPeerPort,
    .peermacaddr	= L2tpPeerMacAddr,
    .peeriface		= L2tpPeerIface,
    .callingnum		= L2tpCallingNum,
    .callednum		= L2tpCalledNum,
  };

  const struct cmdtab	L2tpSetCmds[] = {
    { "self {ip} [{port}]",		"Set local IP address",
	L2tpSetCommand, NULL, 2, (void *) SET_SELFADDR },
    { "peer {ip} [{port}]",		"Set remote IP address",
	L2tpSetCommand, NULL, 2, (void *) SET_PEERADDR },
    { "callingnum {number}",		"Set calling L2TP telephone number",
	L2tpSetCommand, NULL, 2, (void *) SET_CALLINGNUM },
    { "callednum {number}",		"Set called L2TP telephone number",
	L2tpSetCommand, NULL, 2, (void *) SET_CALLEDNUM },
    { "hostname {name}",		"Set L2TP local hostname",
	L2tpSetCommand, NULL, 2, (void *) SET_HOSTNAME },
    { "pmask {mask}",			"Set L2TP peer hostname mask",
	L2tpSetCommand, NULL, 2, (void *) SET_PEERMASK },
    { "secret {sec}",			"Set L2TP tunnel secret",
	L2tpSetCommand, NULL, 2, (void *) SET_SECRET },
    { "enable [opt ...]",		"Enable option",
	L2tpSetCommand, NULL, 2, (void *) SET_ENABLE },
    { "disable [opt ...]",		"Disable option",
	L2tpSetCommand, NULL, 2, (void *) SET_DISABLE },
    { NULL, NULL, NULL, NULL, 0, NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static const struct confinfo	gConfList[] = {
    { 0,	L2TP_CONF_OUTCALL,	"outcall"	},
    { 0,	L2TP_CONF_HIDDEN,	"hidden"	},
    { 0,	L2TP_CONF_LENGTH,	"length"	},
    { 0,	L2TP_CONF_DATASEQ,	"dataseq"	},
    { 0,	L2TP_CONF_RESOLVE_ONCE,	"resolve-once"	},
    { 0,	0,			NULL		},
  };

static struct ghash	*gL2tpServers;
static struct ghash	*gL2tpTuns;
static int	one = 1;

/*
 * L2tpTInit()
 */

static int
L2tpTInit(void)
{
    if ((gL2tpServers = ghash_create(NULL, 0, 0, MB_PHYS, NULL, NULL, NULL, NULL))
	  == NULL)
	return(-1);
    if ((gL2tpTuns = ghash_create(NULL, 0, 0, MB_PHYS, NULL, NULL, NULL, NULL))
	  == NULL)
	return(-1);
    return(0);
}

/*
 * L2tpTShutdown()
 */

static void
L2tpTShutdown(void)
{
    struct ghash_walk walk;
    struct l2tp_tun *tun;

    Log(LG_PHYS2, ("L2TP: Total shutdown"));
    ghash_walk_init(gL2tpTuns, &walk);
    while ((tun = ghash_walk_next(gL2tpTuns, &walk)) != NULL) {
        if (tun->ctrl) {
    	    if (tun->alive)
	        ppp_l2tp_ctrl_shutdown(tun->ctrl,
		    L2TP_RESULT_SHUTDOWN, 0, NULL);
	    ppp_l2tp_ctrl_destroy(&tun->ctrl);
	}
    }
    ghash_destroy(&gL2tpServers);
    ghash_destroy(&gL2tpTuns);
}

/*
 * L2tpInit()
 */

static int
L2tpInit(Link l)
{
    L2tpInfo	l2tp;

    /* Initialize this link */
    l2tp = (L2tpInfo) (l->info = Malloc(MB_PHYS, sizeof(*l2tp)));
  
    u_addrclear(&l2tp->conf.self_addr);
    l2tp->conf.self_addr.family = AF_INET;
    l2tp->conf.self_port = 0;
    u_rangeclear(&l2tp->conf.peer_addr);
    l2tp->conf.peer_addr.addr.family = AF_INET;
    l2tp->conf.peer_addr.width = 0;
    l2tp->conf.peer_port = 0;
    l2tp->conf.fqdn_peer_addr = NULL;
    l2tp->conf.peer_mask = NULL;

    Enable(&l2tp->conf.options, L2TP_CONF_DATASEQ);
    Enable(&l2tp->conf.options, L2TP_CONF_RESOLVE_ONCE);
  
    return(0);
}

/*
 * L2tpInst()
 */

static int
L2tpInst(Link l, Link lt)
{
	L2tpInfo pi;
	L2tpInfo	const pit = (L2tpInfo) lt->info;

	/* Initialize this link */
	pi = (L2tpInfo) (l->info = Mdup(MB_PHYS, lt->info, sizeof(*pit)));
	if (pit->conf.fqdn_peer_addr != NULL)
	    pi->conf.fqdn_peer_addr =
	        Mstrdup(MB_PHYS, pit->conf.fqdn_peer_addr);
	if (pit->conf.peer_mask != NULL)
	    pi->conf.peer_mask = Mstrdup(MB_PHYS, pit->conf.peer_mask);
	if (pi->server)
	    pi->server->refs++;
	
	return(0);
}

/*
 * L2tpOpen()
 */

static void
L2tpOpen(Link l)
{
	L2tpInfo const pi = (L2tpInfo) l->info;

	struct l2tp_tun *tun = NULL;
	struct ppp_l2tp_sess *sess;
	struct ppp_l2tp_avp_list *avps = NULL;
	union {
	    u_char buf[sizeof(struct ng_ksocket_sockopt) + sizeof(int)];
	    struct ng_ksocket_sockopt sockopt;
	} sockopt_buf;
	struct ng_ksocket_sockopt *const sockopt = &sockopt_buf.sockopt;
	union {
	    u_char	buf[sizeof(struct ng_mesg) + sizeof(struct sockaddr_storage)];
	    struct ng_mesg	reply;
	} ugetsas;
	struct sockaddr_storage	*const getsas = (struct sockaddr_storage *)(void *)ugetsas.reply.data;
	struct ngm_mkpeer mkpeer;
	struct sockaddr_storage sas;
	char hook[NG_HOOKSIZ];
	char namebuf[64];
	char buf[32], buf2[32];
	char hostname[MAXHOSTNAMELEN];
	ng_ID_t node_id;
	int csock = -1;
	int dsock = -1;
	struct ghash_walk walk;
	u_int32_t       cap;
	u_int16_t	win;

	pi->opened=1;
	
	if (pi->incoming == 1) {
		Log(LG_PHYS2, ("[%s] L2tpOpen() on incoming call", l->name));
		if (l->state==PHYS_STATE_READY) {
		    l->state = PHYS_STATE_UP;
		    if (pi->outcall) {
			pi->sync = 1;
			if (l->rep) {
			    uint32_t fr;
			    avps = ppp_l2tp_avp_list_create();
			    if (RepIsSync(l)) {
				fr = htonl(L2TP_FRAMING_SYNC);
			    } else {
				fr = htonl(L2TP_FRAMING_ASYNC);
				pi->sync = 0;
			    }
			    if (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_FRAMING_TYPE,
	        	        &fr, sizeof(fr)) == -1) {
				    Perror("[%s] ppp_l2tp_avp_list_append",
				        l->name);
			    }
			} else {
			    avps = NULL;
			}
			Log(LG_PHYS, ("[%s] L2TP: Call #%u connected", l->name, 
			    ppp_l2tp_sess_get_serial(pi->sess)));
			ppp_l2tp_connected(pi->sess, avps);
			if (avps)
			    ppp_l2tp_avp_list_destroy(&avps);
		    }
		    L2tpHookUp(l);
		    PhysUp(l);
		}
		return;
	}

	/* Sanity check. */
	if (l->state != PHYS_STATE_DOWN) {
		Log(LG_PHYS, ("[%s] L2TP: allready active", l->name));
		return;
	};

	l->state = PHYS_STATE_CONNECTING;
	strlcpy(pi->callingnum, pi->conf.callingnum, sizeof(pi->callingnum));
	strlcpy(pi->callednum, pi->conf.callednum, sizeof(pi->callednum));

	if ((!Enabled(&pi->conf.options, L2TP_CONF_RESOLVE_ONCE)) &&
	    (pi->conf.fqdn_peer_addr != NULL)) {
	    struct u_range	rng;
	    if (ParseRange(pi->conf.fqdn_peer_addr, &rng, ALLOW_IPV4|ALLOW_IPV6))
		pi->conf.peer_addr = rng;
	}

	ghash_walk_init(gL2tpTuns, &walk);
	while ((tun = ghash_walk_next(gL2tpTuns, &walk)) != NULL) {
	    if (tun->ctrl && tun->alive && tun->active_sessions < gL2TPtunlimit &&
		(IpAddrInRange(&pi->conf.peer_addr, &tun->peer_addr)) &&
		(u_addrempty(&pi->conf.self_addr) || u_addrempty(&tun->self_addr) ||
		    u_addrcompare(&pi->conf.self_addr, &tun->self_addr) == 0) &&
		(pi->conf.peer_port == 0 || pi->conf.peer_port == tun->peer_port)) {
		    pi->tun = tun;
		    tun->active_sessions++;
		    if (tun->connected) { /* if tun is connected then just initiate */
		    
			/* Create number AVPs */
			avps = ppp_l2tp_avp_list_create();
			if (pi->conf.callingnum[0]) {
			  if (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_CALLING_NUMBER,
	        	    pi->conf.callingnum, strlen(pi->conf.callingnum)) == -1) {
				Perror("[%s] ppp_l2tp_avp_list_append", l->name);
			  }
			}
			if (pi->conf.callednum[0]) {
			  if (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_CALLED_NUMBER,
	        	    pi->conf.callednum, strlen(pi->conf.callednum)) == -1) {
				Perror("[%s] ppp_l2tp_avp_list_append", l->name);
			  }
			}
			if ((sess = ppp_l2tp_initiate(tun->ctrl, 
				Enabled(&pi->conf.options, L2TP_CONF_OUTCALL)?1:0,
				Enabled(&pi->conf.options, L2TP_CONF_LENGTH)?1:0,
				Enabled(&pi->conf.options, L2TP_CONF_DATASEQ)?1:0,
				avps)) == NULL) {
			    Perror("[%s] ppp_l2tp_initiate", l->name);
			    ppp_l2tp_avp_list_destroy(&avps);
			    pi->sess = NULL;
			    pi->tun = NULL;
			    tun->active_sessions--;
			    l->state = PHYS_STATE_DOWN;
			    PhysDown(l, STR_ERROR, NULL);
			    return;
			};
			ppp_l2tp_avp_list_destroy(&avps);
			pi->sess = sess;
			pi->outcall = Enabled(&pi->conf.options, L2TP_CONF_OUTCALL);
			Log(LG_PHYS, ("[%s] L2TP: %s call #%u via control connection %p initiated", 
			    l->name, (pi->outcall?"Outgoing":"Incoming"), 
			    ppp_l2tp_sess_get_serial(sess), tun->ctrl));
			ppp_l2tp_sess_set_cookie(sess, l);
			if (!pi->outcall) {
			    pi->sync = 1;
			    if (l->rep) {
				uint32_t fr;
				avps = ppp_l2tp_avp_list_create();
				if (RepIsSync(l)) {
				    fr = htonl(L2TP_FRAMING_SYNC);
				} else {
				    fr = htonl(L2TP_FRAMING_ASYNC);
				    pi->sync = 0;
				}
				if (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_FRAMING_TYPE,
	        		    &fr, sizeof(fr)) == -1) {
					Perror("[%s] ppp_l2tp_avp_list_append",
					    l->name);
				}
			    } else {
				avps = NULL;
			    }
			    ppp_l2tp_connected(pi->sess, avps);
			    if (avps)
				ppp_l2tp_avp_list_destroy(&avps);
			}
		    } /* Else wait while it will be connected */
		    return;
	    }
	}

	/* There is no tun which we need. Create a new one. */
	tun = Malloc(MB_PHYS, sizeof(*tun));
	memset(tun, 0, sizeof(*tun));
	u_addrcopy(&pi->conf.peer_addr.addr, &tun->peer_addr);
	tun->peer_port = pi->conf.peer_port?pi->conf.peer_port:L2TP_PORT;
	u_addrcopy(&pi->conf.self_addr, &tun->self_addr);
	tun->self_port = pi->conf.self_port;
	tun->alive = 1;
	tun->connected = 0;

	/* Create vendor name AVP */
	avps = ppp_l2tp_avp_list_create();

	if (pi->conf.hostname[0] != 0) {
	    strlcpy(hostname, pi->conf.hostname, sizeof(hostname));
	} else {
	    (void)gethostname(hostname, sizeof(hostname) - 1);
	    hostname[sizeof(hostname) - 1] = '\0';
	}
	cap = htonl(L2TP_BEARER_DIGITAL|L2TP_BEARER_ANALOG);
	win = htons(8); /* XXX: this value is empirical. */
	if ((ppp_l2tp_avp_list_append(avps, 1, 0, AVP_HOST_NAME,
	      hostname, strlen(hostname)) == -1) ||
	    (ppp_l2tp_avp_list_append(avps, 0, 0, AVP_VENDOR_NAME,
	      MPD_VENDOR, strlen(MPD_VENDOR)) == -1) ||
	    (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_BEARER_CAPABILITIES,
	      &cap, sizeof(cap)) == -1) ||
	    (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_RECEIVE_WINDOW_SIZE,
	      &win, sizeof(win)) == -1)) {
		Perror("L2TP: ppp_l2tp_avp_list_append");
		goto fail;
	}

	/* Create a new control connection */
	if ((tun->ctrl = ppp_l2tp_ctrl_create(gPeventCtx, &gGiantMutex,
	    &ppp_l2tp_server_ctrl_cb, u_addrtoid(&tun->peer_addr),
	    &node_id, hook, avps, 
	    pi->conf.secret, strlen(pi->conf.secret),
	    Enabled(&pi->conf.options, L2TP_CONF_HIDDEN))) == NULL) {
		Perror("[%s] ppp_l2tp_ctrl_create", l->name);
		goto fail;
	}
	ppp_l2tp_ctrl_set_cookie(tun->ctrl, tun);

	Log(LG_PHYS, ("L2TP: Initiating control connection %p %s %u <-> %s %u",
	    tun->ctrl, u_addrtoa(&tun->self_addr,buf,sizeof(buf)), tun->self_port,
	    u_addrtoa(&tun->peer_addr,buf2,sizeof(buf2)), tun->peer_port));

	/* Get a temporary netgraph socket node */
	if (NgMkSockNode(NULL, &csock, &dsock) == -1) {
	    Perror("[%s] NgMkSockNode", l->name);
	    goto fail;
	}

	/* Attach a new UDP socket to "lower" hook */
	snprintf(namebuf, sizeof(namebuf), "[%lx]:", (u_long)node_id);
	memset(&mkpeer, 0, sizeof(mkpeer));
	strlcpy(mkpeer.type, NG_KSOCKET_NODE_TYPE, sizeof(mkpeer.type));
	strlcpy(mkpeer.ourhook, hook, sizeof(mkpeer.ourhook));
	if (tun->peer_addr.family==AF_INET6) {
		snprintf(mkpeer.peerhook, sizeof(mkpeer.peerhook), "%d/%d/%d", PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
	} else {
	        snprintf(mkpeer.peerhook, sizeof(mkpeer.peerhook), "inet/dgram/udp");
	}
	if (NgSendMsg(csock, namebuf, NGM_GENERIC_COOKIE,
	    NGM_MKPEER, &mkpeer, sizeof(mkpeer)) == -1) {
		Perror("[%s] mkpeer", l->name);
		goto fail;
	}

	/* Point name at ksocket node */
	strlcat(namebuf, hook, sizeof(namebuf));

	/* Make UDP port reusable */
	memset(&sockopt_buf, 0, sizeof(sockopt_buf));
	sockopt->level = SOL_SOCKET;
	sockopt->name = SO_REUSEADDR;
	memcpy(sockopt->value, &one, sizeof(int));
	if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_SETOPT, sockopt, sizeof(sockopt_buf)) == -1) {
		Perror("[%s] setsockopt", l->name);
		goto fail;
	}
	sockopt->name = SO_REUSEPORT;
	if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_SETOPT, sockopt, sizeof(sockopt_buf)) == -1) {
		Perror("[%s] setsockopt", l->name);
		goto fail;
	}

	if (!u_addrempty(&tun->self_addr) || tun->self_port != 0) {
	    /* Bind socket to a new port */
	    u_addrtosockaddr(&tun->self_addr,tun->self_port,&sas);
	    if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
		NGM_KSOCKET_BIND, &sas, sas.ss_len) == -1) {
		    Perror("[%s] bind", l->name);
		    goto fail;
	    }
	}
	/* Connect socket to remote peer's IP and port */
	u_addrtosockaddr(&tun->peer_addr,tun->peer_port,&sas);
	if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
	      NGM_KSOCKET_CONNECT, &sas, sas.ss_len) == -1
	    && errno != EINPROGRESS) {
		Perror("[%s] connect", l->name);
		goto fail;
	}

	if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_GETNAME, NULL, 0) == -1) {
		Perror("[%s] getname send", l->name);
	} else 
	if (NgRecvMsg(csock, &ugetsas.reply, sizeof(ugetsas), NULL) == -1) {
		Perror("[%s] getname recv", l->name);
	} else {
	    sockaddrtou_addr(getsas,&tun->self_addr,&tun->self_port);
	}

	/* Add peer to our hash table */
	if (ghash_put(gL2tpTuns, tun) == -1) {
		Perror("[%s] ghash_put", l->name);
		goto fail;
	}
	pi->tun = tun;
	tun->active_sessions++;
	Log(LG_PHYS2, ("L2TP: Control connection %p %s %u <-> %s %u initiated",
	    tun->ctrl, u_addrtoa(&tun->self_addr,buf,sizeof(buf)), tun->self_port,
	    u_addrtoa(&tun->peer_addr,buf2,sizeof(buf2)), tun->peer_port));
	ppp_l2tp_ctrl_initiate(tun->ctrl);

	/* Clean up and return */
	ppp_l2tp_avp_list_destroy(&avps);
	(void)close(csock);
	(void)close(dsock);
	return;

fail:
	/* Clean up after failure */
	if (csock != -1)
		(void)close(csock);
	if (dsock != -1)
		(void)close(dsock);
	if (tun != NULL) {
		ppp_l2tp_ctrl_destroy(&tun->ctrl);
		Freee(tun);
	}
	l->state = PHYS_STATE_DOWN;
	PhysDown(l, STR_ERROR, NULL);
}

/*
 * L2tpClose()
 */

static void
L2tpClose(Link l)
{
    L2tpInfo      const pi = (L2tpInfo) l->info;

    pi->opened = 0;
    pi->incoming = 0;
    pi->outcall = 0;
    if (l->state == PHYS_STATE_DOWN)
    	return;
    L2tpUnhook(l);
    if (pi->sess) {
	Log(LG_PHYS, ("[%s] L2TP: Call #%u terminated locally", l->name, 
	    ppp_l2tp_sess_get_serial(pi->sess)));
	ppp_l2tp_terminate(pi->sess, L2TP_RESULT_ADMIN, 0, NULL);
	pi->sess = NULL;
    }
    if (pi->tun)
	pi->tun->active_sessions--;
    pi->tun = NULL;
    pi->callingnum[0]=0;
    pi->callednum[0]=0;
    l->state = PHYS_STATE_DOWN;
    PhysDown(l, STR_MANUALLY, NULL);
}

/*
 * L2tpShutdown()
 */

static void
L2tpShutdown(Link l)
{
    L2tpInfo const pi = (L2tpInfo) l->info;

    if (pi->conf.fqdn_peer_addr)
        Freee(pi->conf.fqdn_peer_addr);
    if (pi->conf.peer_mask)
        Freee(pi->conf.peer_mask);
    L2tpUnListen(l);
    Freee(l->info);
}

/*
 * L2tpUnhook()
 */

static void
L2tpUnhook(Link l)
{
    int		csock = -1;
    L2tpInfo	const pi = (L2tpInfo) l->info;
    const char	*hook;
    ng_ID_t	node_id;
    char	path[NG_PATHSIZ];
	
    if (pi->sess) {		/* avoid double close */

	/* Get this link's node and hook */
	ppp_l2tp_sess_get_hook(pi->sess, &node_id, &hook);
	
	if (node_id != 0) {

	    /* Get a temporary netgraph socket node */
	    if (NgMkSockNode(NULL, &csock, NULL) == -1) {
		Perror("L2TP: NgMkSockNode");
		return;
	    }
	
	    /* Disconnect session hook. */
	    snprintf(path, sizeof(path), "[%lx]:", (u_long)node_id);
	    NgFuncDisconnect(csock, l->name, path, hook);
	
	    close(csock);
	}
    }
}

/*
 * L2tpOriginated()
 */

static int
L2tpOriginated(Link l)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    return(l2tp->incoming ? LINK_ORIGINATE_REMOTE : LINK_ORIGINATE_LOCAL);
}

/*
 * L2tpIsSync()
 */

static int
L2tpIsSync(Link l)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    return (l2tp->sync);
}

static int
L2tpSetAccm(Link l, u_int32_t xmit, u_int32_t recv)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;
    
    if (!l2tp->sess)
	    return (-1);

    return (ppp_l2tp_set_link_info(l2tp->sess, xmit, recv));
}

static int
L2tpSetCallingNum(Link l, void *buf)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    strlcpy(l2tp->conf.callingnum, buf, sizeof(l2tp->conf.callingnum));
    return(0);
}

static int
L2tpSetCalledNum(Link l, void *buf)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    strlcpy(l2tp->conf.callednum, buf, sizeof(l2tp->conf.callednum));
    return(0);
}

static int
L2tpSelfName(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    if (l2tp->tun && l2tp->tun->ctrl)
	return (ppp_l2tp_ctrl_get_self_name(l2tp->tun->ctrl, buf, buf_len));
    ((char*)buf)[0]=0;
    return (0);
}

static int
L2tpPeerName(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    if (l2tp->tun && l2tp->tun->ctrl)
	return (ppp_l2tp_ctrl_get_peer_name(l2tp->tun->ctrl, buf, buf_len));
    ((char*)buf)[0]=0;
    return (0);
}

static int
L2tpSelfAddr(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    if (l2tp->tun && !u_addrempty(&l2tp->tun->self_addr)) {
	if (u_addrtoa(&l2tp->tun->self_addr, buf, buf_len))
	    return (0);
	else {
	    ((char*)buf)[0]=0;
	    return (-1);
	}
    }
    ((char*)buf)[0]=0;
    return (0);
}

static int
L2tpPeerAddr(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    if (l2tp->tun) {
	if (u_addrtoa(&l2tp->tun->peer_addr, buf, buf_len))
	    return(0);
	else {
	    ((char*)buf)[0]=0;
	    return(-1);
	}
    }
    ((char*)buf)[0]=0;
    return(0);
}

static int
L2tpPeerPort(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    if (l2tp->tun) {
	if (snprintf(buf, buf_len, "%d", l2tp->tun->peer_port))
	    return(0);
	else {
	    ((char*)buf)[0]=0;
	    return(-1);
	}
    }
    ((char*)buf)[0]=0;
    return(0);
}

static int
L2tpPeerMacAddr(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    if (buf_len < 18)
    	return 1;
    if (l2tp->tun && l2tp->tun->peer_iface[0]) {
	ether_ntoa_r((struct ether_addr *)l2tp->tun->peer_mac_addr, buf);
	return (0);
    }
    ((char*)buf)[0]=0;
    return(0);
}

static int
L2tpPeerIface(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    if (l2tp->tun && l2tp->tun->peer_iface[0]) {
	strlcpy(buf, l2tp->tun->peer_iface, buf_len);
	return (0);
    }
    ((char*)buf)[0]=0;
    return(0);
}

static int
L2tpCallingNum(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    strlcpy((char*)buf, l2tp->callingnum, buf_len);
    return(0);
}

static int
L2tpCalledNum(Link l, void *buf, size_t buf_len)
{
    L2tpInfo	const l2tp = (L2tpInfo) l->info;

    strlcpy((char*)buf, l2tp->callednum, buf_len);
    return(0);
}

/*
 * L2tpStat()
 */

void
L2tpStat(Context ctx)
{
    L2tpInfo	const l2tp = (L2tpInfo) ctx->lnk->info;
    char	buf[32];

    Printf("L2TP configuration:\r\n");
    Printf("\tSelf addr    : %s, port %u",
	u_addrtoa(&l2tp->conf.self_addr, buf, sizeof(buf)), l2tp->conf.self_port);
    Printf("\r\n");
    Printf("\tPeer FQDN    : %s\r\n", l2tp->conf.fqdn_peer_addr);
    Printf("\tPeer range   : %s",
	u_rangetoa(&l2tp->conf.peer_addr, buf, sizeof(buf)));
    if (l2tp->conf.peer_port)
	Printf(", port %u", l2tp->conf.peer_port);
    Printf("\r\n");
    Printf("\tHostname     : %s\r\n", l2tp->conf.hostname);
    Printf("\tPeer mask    : %s\r\n", l2tp->conf.peer_mask);
    Printf("\tSecret       : %s\r\n", (l2tp->conf.callingnum[0])?"******":"");
    Printf("\tCalling number: %s\r\n", l2tp->conf.callingnum);
    Printf("\tCalled number: %s\r\n", l2tp->conf.callednum);
    Printf("L2TP options:\r\n");
    OptStat(ctx, &l2tp->conf.options, gConfList);
    Printf("L2TP status:\r\n");
    if (ctx->lnk->state != PHYS_STATE_DOWN) {
	Printf("\tIncoming     : %s\r\n", (l2tp->incoming?"YES":"NO"));
	if (l2tp->tun) {
	    Printf("\tCurrent self : %s, port %u",
		u_addrtoa(&l2tp->tun->self_addr, buf, sizeof(buf)), l2tp->tun->self_port);
	    L2tpSelfName(ctx->lnk, buf, sizeof(buf));
	    Printf(" (%s)\r\n", buf);
	    Printf("\tCurrent peer : %s, port %u",
		u_addrtoa(&l2tp->tun->peer_addr, buf, sizeof(buf)), l2tp->tun->peer_port);
	    L2tpPeerName(ctx->lnk, buf, sizeof(buf));
	    Printf(" (%s)\r\n", buf);
	    if (l2tp->tun->peer_iface[0]) {
		ether_ntoa_r((struct ether_addr *)l2tp->tun->peer_mac_addr, buf);
		Printf("\tCurrent peer : %s at %s\r\n", buf,
		    l2tp->tun->peer_iface);
	    }
	    Printf("\tFraming      : %s\r\n", (l2tp->sync?"Sync":"Async"));
	}
	Printf("\tCalling number: %s\r\n", l2tp->callingnum);
	Printf("\tCalled number: %s\r\n", l2tp->callednum);
    }
}

/*
 * This is called when a control connection gets opened.
 */
static void
ppp_l2tp_ctrl_connected_cb(struct ppp_l2tp_ctrl *ctrl)
{
	struct l2tp_tun *tun = ppp_l2tp_ctrl_get_cookie(ctrl);
	struct ppp_l2tp_sess *sess;
	struct ppp_l2tp_avp_list *avps = NULL;
	struct sockaddr_dl  hwa;
	char	buf[32], buf2[32];
	int	k;

	Log(LG_PHYS, ("L2TP: Control connection %p %s %u <-> %s %u connected",
	    ctrl, u_addrtoa(&tun->self_addr,buf,sizeof(buf)), tun->self_port,
	    u_addrtoa(&tun->peer_addr,buf2,sizeof(buf2)), tun->peer_port));
	
	if (GetPeerEther(&tun->peer_addr, &hwa)) {
	    if_indextoname(hwa.sdl_index, tun->peer_iface);
	    memcpy(tun->peer_mac_addr, LLADDR(&hwa), sizeof(tun->peer_mac_addr));
	};

	/* Examine all L2TP links. */
	for (k = 0; k < gNumLinks; k++) {
		Link l;
	        L2tpInfo pi;

		if (!gLinks[k] || gLinks[k]->type != &gL2tpPhysType)
			continue;

		l = gLinks[k];
		pi = (L2tpInfo)l->info;

		if (pi->tun != tun)
			continue;

		tun->connected = 1;
		/* Create number AVPs */
		avps = ppp_l2tp_avp_list_create();
		if (pi->conf.callingnum[0]) {
		   if (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_CALLING_NUMBER,
	            pi->conf.callingnum, strlen(pi->conf.callingnum)) == -1) {
			Perror("[%s] ppp_l2tp_avp_list_append", l->name);
		   }
		}
		if (pi->conf.callednum[0]) {
		   if (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_CALLED_NUMBER,
	            pi->conf.callednum, strlen(pi->conf.callednum)) == -1) {
			Perror("[%s] ppp_l2tp_avp_list_append", l->name);
		   }
		}
		if ((sess = ppp_l2tp_initiate(tun->ctrl,
			    Enabled(&pi->conf.options, L2TP_CONF_OUTCALL)?1:0, 
			    Enabled(&pi->conf.options, L2TP_CONF_LENGTH)?1:0,
			    Enabled(&pi->conf.options, L2TP_CONF_DATASEQ)?1:0,
			    avps)) == NULL) {
			Perror("ppp_l2tp_initiate");
			pi->sess = NULL;
			pi->tun = NULL;
			tun->active_sessions--;
			l->state = PHYS_STATE_DOWN;
			PhysDown(l, STR_ERROR, NULL);
			continue;
		};
		ppp_l2tp_avp_list_destroy(&avps);
		pi->sess = sess;
		pi->outcall = Enabled(&pi->conf.options, L2TP_CONF_OUTCALL);
		Log(LG_PHYS, ("[%s] L2TP: %s call #%u via control connection %p initiated", 
		    l->name, (pi->outcall?"Outgoing":"Incoming"), 
		    ppp_l2tp_sess_get_serial(sess), tun->ctrl));
		ppp_l2tp_sess_set_cookie(sess, l);
		if (!pi->outcall) {
		    pi->sync = 1;
		    if (l->rep) {
			uint32_t fr;
			avps = ppp_l2tp_avp_list_create();
			if (RepIsSync(l)) {
			    fr = htonl(L2TP_FRAMING_SYNC);
			} else {
			    fr = htonl(L2TP_FRAMING_ASYNC);
			    pi->sync = 0;
			}
			if (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_FRAMING_TYPE,
	        	    &fr, sizeof(fr)) == -1) {
			        Perror("[%s] ppp_l2tp_avp_list_append", l->name);
			}
		    } else {
			avps = NULL;
		    }
		    ppp_l2tp_connected(pi->sess, avps);
		    if (avps)
			ppp_l2tp_avp_list_destroy(&avps);
		}
	};
}

/*
 * This is called when a control connection is terminated for any reason
 * other than a call ppp_l2tp_ctrl_destroy().
 */
static void
ppp_l2tp_ctrl_terminated_cb(struct ppp_l2tp_ctrl *ctrl,
	u_int16_t result, u_int16_t error, const char *errmsg)
{
	struct l2tp_tun *tun = ppp_l2tp_ctrl_get_cookie(ctrl);
	int	k;

	(void)result;
	Log(LG_PHYS, ("L2TP: Control connection %p terminated: %d (%s)", 
	    ctrl, error, errmsg));

	/* Examine all L2TP links. */
	for (k = 0; k < gNumLinks; k++) {
		Link l;
	        L2tpInfo pi;

		if (!gLinks[k] || gLinks[k]->type != &gL2tpPhysType)
			continue;

		l = gLinks[k];
		pi = (L2tpInfo)l->info;

		if (pi->tun != tun)
			continue;

		l->state = PHYS_STATE_DOWN;
		L2tpUnhook(l);
		pi->sess = NULL;
		pi->tun = NULL;
		tun->active_sessions--;
		pi->callingnum[0]=0;
	        pi->callednum[0]=0;
		PhysDown(l, STR_DROPPED, NULL);
	};
	
	tun->alive = 0;
}

/*
 * This is called before control connection is destroyed for any reason
 * other than a call ppp_l2tp_ctrl_destroy().
 */
static void
ppp_l2tp_ctrl_destroyed_cb(struct ppp_l2tp_ctrl *ctrl)
{
	struct l2tp_tun *tun = ppp_l2tp_ctrl_get_cookie(ctrl);

	Log(LG_PHYS, ("L2TP: Control connection %p destroyed", ctrl));

	ghash_remove(gL2tpTuns, tun);
	Freee(tun);
}

/*
 * This callback is used to report the peer's initiating a new incoming
 * or outgoing call.
 */
static void
ppp_l2tp_initiated_cb(struct ppp_l2tp_ctrl *ctrl,
	struct ppp_l2tp_sess *sess, int out,
	const struct ppp_l2tp_avp_list *avps,
	u_char *include_length, u_char *enable_dseq)
{
	struct	l2tp_tun *const tun = ppp_l2tp_ctrl_get_cookie(ctrl);
	char   *peername = ppp_l2tp_ctrl_get_peer_name_p(ctrl);
	struct	ppp_l2tp_avp_ptrs *ptrs = NULL;
	Link 	l = NULL;
	L2tpInfo pi = NULL;
	int	k;

	/* Convert AVP's to friendly form */
	if ((ptrs = ppp_l2tp_avp_list2ptrs(avps)) == NULL) {
		Perror("L2TP: error decoding AVP list");
		ppp_l2tp_terminate(sess, L2TP_RESULT_ERROR,
		    L2TP_ERROR_GENERIC, strerror(errno));
		return;
	}

	Log(LG_PHYS, ("L2TP: %s call #%u via connection %p received", 
	    (out?"Outgoing":"Incoming"), 
	    ppp_l2tp_sess_get_serial(sess), ctrl));

	if (gShutdownInProgress) {
		Log(LG_PHYS, ("Shutdown sequence in progress, ignoring request."));
		goto failed;
	}

	if (OVERLOAD()) {
		Log(LG_PHYS, ("Daemon overloaded, ignoring request."));
		goto failed;
	}

	/* Examine all L2TP links. */
	for (k = 0; k < gNumLinks; k++) {
		Link l2;
	        L2tpInfo pi2;

		if (!gLinks[k] || gLinks[k]->type != &gL2tpPhysType)
			continue;

		l2 = gLinks[k];
		pi2 = (L2tpInfo)l2->info;

		if ((!PhysIsBusy(l2)) &&
		    Enabled(&l2->conf.options, LINK_CONF_INCOMING) &&
		    ((u_addrempty(&pi2->conf.self_addr)) || (u_addrcompare(&pi2->conf.self_addr, &tun->self_addr) == 0)) &&
		    (pi2->conf.self_port == 0 || pi2->conf.self_port == tun->self_port) &&
		    (IpAddrInRange(&pi2->conf.peer_addr, &tun->peer_addr)) &&
		    (pi2->conf.peer_port == 0 || pi2->conf.peer_port == tun->peer_port) &&
		    (peername == NULL || *peername == 0 || pi2->conf.peer_mask == 0 || fnmatch(pi2->conf.peer_mask, peername, 0) == 0)) {
			
			if (pi == NULL || pi2->conf.peer_addr.width > pi->conf.peer_addr.width) {
				l = l2;
				pi = pi2;
				if (u_rangehost(&pi->conf.peer_addr)) {
					break;	/* Nothing could be better */
				}
			}
		}
	}
	if (l != NULL && l->tmpl)
    		l = LinkInst(l, NULL, 0, 0);

	if (l != NULL) {
    		pi = (L2tpInfo)l->info;
		Log(LG_PHYS, ("[%s] L2TP: %s call #%u via control connection %p accepted", 
		    l->name, (out?"Outgoing":"Incoming"), 
		    ppp_l2tp_sess_get_serial(sess), ctrl));
		if (peername && *peername)
		    Log(LG_PHYS2, ("[%s] L2TP: Call #%u remote hostname is %s",
			l->name, ppp_l2tp_sess_get_serial(sess), peername));

		if (out)
		    l->state = PHYS_STATE_READY;
		else
		    l->state = PHYS_STATE_CONNECTING;
		pi->incoming = 1;
		pi->outcall = out;
		pi->tun = tun;
		tun->active_sessions++;
		pi->sess = sess;
		if (ptrs->callingnum)
		    strlcpy(pi->callingnum, ptrs->callingnum->number, sizeof(pi->callingnum));
		if (ptrs->callednum)
		    strlcpy(pi->callednum, ptrs->callednum->number, sizeof(pi->callednum));
		    
		*include_length = (Enabled(&pi->conf.options, L2TP_CONF_LENGTH)?1:0);
		*enable_dseq = (Enabled(&pi->conf.options, L2TP_CONF_DATASEQ)?1:0);

		PhysIncoming(l);

		ppp_l2tp_sess_set_cookie(sess, l);
		ppp_l2tp_avp_ptrs_destroy(&ptrs);
		return;
	}
	Log(LG_PHYS, ("L2TP: No free link with requested parameters "
	    "was found"));
failed:
	ppp_l2tp_terminate(sess, L2TP_RESULT_AVAIL_TEMP, 0, NULL);
	ppp_l2tp_avp_ptrs_destroy(&ptrs);
}

/*
 * This callback is used to report successful connection of a remotely
 * initiated incoming call (see ppp_l2tp_initiated_t) or a locally initiated
 * outgoing call (see ppp_l2tp_initiate()).
 */
static void
ppp_l2tp_connected_cb(struct ppp_l2tp_sess *sess,
	const struct ppp_l2tp_avp_list *avps)
{
	Link l;
	L2tpInfo pi;
	struct ppp_l2tp_avp_ptrs *ptrs = NULL;

	l = ppp_l2tp_sess_get_cookie(sess);
	pi = (L2tpInfo)l->info;

	Log(LG_PHYS, ("[%s] L2TP: Call #%u connected", l->name, 
	    ppp_l2tp_sess_get_serial(sess)));

	if ((pi->incoming != pi->outcall) && avps != NULL) {
		/* Convert AVP's to friendly form */
		if ((ptrs = ppp_l2tp_avp_list2ptrs(avps)) == NULL) {
			Perror("L2TP: error decoding AVP list");
		} else {
			if (ptrs->framing && ptrs->framing->sync) {
				pi->sync = 1;
			} else {
				pi->sync = 0;
			}
			ppp_l2tp_avp_ptrs_destroy(&ptrs);
		}
	}

	if (pi->opened) {
	    l->state = PHYS_STATE_UP;
	    L2tpHookUp(l);
	    PhysUp(l);
	} else {
	    l->state = PHYS_STATE_READY;
	}
}

/*
 * This callback is called when any call, whether successfully connected
 * or not, is terminated for any reason other than explict termination
 * from the link side (via a call to either ppp_l2tp_terminate() or
 * ppp_l2tp_ctrl_destroy()).
 */
static void
ppp_l2tp_terminated_cb(struct ppp_l2tp_sess *sess,
	u_int16_t result, u_int16_t error, const char *errmsg)
{
	char buf[128];
	Link l;
	L2tpInfo pi;

	l = ppp_l2tp_sess_get_cookie(sess);
	pi = (L2tpInfo) l->info;

	/* Control side is notifying us session is down */
	snprintf(buf, sizeof(buf), "result=%u error=%u errmsg=\"%s\"",
	    result, error, (errmsg != NULL) ? errmsg : "");
	Log(LG_PHYS, ("[%s] L2TP: call #%u terminated: %s", l->name, 
	    ppp_l2tp_sess_get_serial(sess), buf));

	l->state = PHYS_STATE_DOWN;
	L2tpUnhook(l);
	pi->sess = NULL;
	if (pi->tun)
	    pi->tun->active_sessions--;
	pi->tun = NULL;
	pi->callingnum[0]=0;
	pi->callednum[0]=0;
	PhysDown(l, STR_DROPPED, NULL);
}

/*
 * This callback called on receiving link info from peer.
 */
void
ppp_l2tp_set_link_info_cb(struct ppp_l2tp_sess *sess,
			u_int32_t xmit, u_int32_t recv)
{
	Link l = ppp_l2tp_sess_get_cookie(sess);

	if (l->rep != NULL) {
		RepSetAccm(l, xmit, recv);
	}
}

/*
 * Connect L2TP and link hooks.
 */
 
static void
L2tpHookUp(Link l)
{
	int		csock = -1;
	L2tpInfo	pi = (L2tpInfo)l->info;
        const char 	*hook;
        ng_ID_t		node_id;
	char		path[NG_PATHSIZ];
	struct ngm_connect      cn;

	/* Get a temporary netgraph socket node */
	if (NgMkSockNode(NULL, &csock, NULL) == -1) {
		Perror("L2TP: NgMkSockNode");
		goto fail;
	}

	/* Get this link's node and hook */
	ppp_l2tp_sess_get_hook(pi->sess, &node_id, &hook);

	/* Initialize cn */
	memset(&cn, 0, sizeof(cn));

	/* Connect our ng_ppp(4) node link hook and ng_l2tp(4) node. */
	if (!PhysGetUpperHook(l, cn.path, cn.peerhook)) {
	    Log(LG_PHYS, ("[%s] L2TP: can't get upper hook", l->name));
	    goto fail;
	}
	snprintf(path, sizeof(path), "[%lx]:", (u_long)node_id);
	strlcpy(cn.ourhook, hook, sizeof(cn.ourhook));
	if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT, 
	    &cn, sizeof(cn)) < 0) {
		Perror("[%s] L2TP: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
    		    l->name, path, cn.ourhook, cn.path, cn.peerhook);
		goto fail;
	}
	ppp_l2tp_sess_hooked(pi->sess);
	close(csock);
	return;

fail:
	/* Clean up after failure */
	ppp_l2tp_terminate(pi->sess, L2TP_RESULT_ERROR,
	    L2TP_ERROR_GENERIC, strerror(errno));
	pi->sess = NULL;
	if (csock != -1)
		(void)close(csock);
}

/*
 * Read an incoming packet that might be a new L2TP connection.
 */
 
static void
L2tpServerEvent(int type, void *arg)
{
	struct l2tp_server *const s = arg;
	L2tpInfo pi = NULL;
	struct ppp_l2tp_avp_list *avps = NULL;
	struct l2tp_tun *tun = NULL;
	union {
	    u_char buf[sizeof(struct ng_ksocket_sockopt) + sizeof(int)];
	    struct ng_ksocket_sockopt sockopt;
	} sockopt_buf;
	struct ng_ksocket_sockopt *const sockopt = &sockopt_buf.sockopt;
	struct ngm_connect connect;
	struct ngm_rmhook rmhook;
	struct ngm_mkpeer mkpeer;
	struct sockaddr_storage peer_sas;
	struct sockaddr_storage sas;
	const size_t bufsize = 8192;
	u_int16_t *buf = NULL;
	char hook[NG_HOOKSIZ];
	char hostname[MAXHOSTNAMELEN];
	socklen_t sas_len;
	char namebuf[64];
	char buf1[32], buf2[32];
	ng_ID_t node_id;
	int csock = -1;
	int dsock = -1;
	int len;
	u_int32_t	cap;
	u_int16_t	win;
	int	k;

	(void)type;
	/* Allocate buffer */
	buf = Malloc(MB_PHYS, bufsize);

	/* Read packet */
	sas_len = sizeof(peer_sas);
	if ((len = recvfrom(s->sock, buf, bufsize, 0,
	    (struct sockaddr *)&peer_sas, &sas_len)) == -1) {
		Perror("L2TP: recvfrom");
		goto fail;
	}

	/* Drop it if it's not an initial L2TP packet */
	if (len < 12)
		goto fail;
	if ((ntohs(buf[0]) & 0xcb0f) != 0xc802 || ntohs(buf[1]) < 12
	    || buf[2] != 0 || buf[3] != 0 || buf[4] != 0 || buf[5] != 0)
		goto fail;

	/* Create a new tun */
	tun = Malloc(MB_PHYS, sizeof(*tun));
	sockaddrtou_addr(&peer_sas,&tun->peer_addr,&tun->peer_port);
	u_addrcopy(&s->self_addr, &tun->self_addr);
	tun->self_port = s->self_port;
	tun->alive = 1;

	Log(LG_PHYS, ("Incoming L2TP packet from %s %d", 
		u_addrtoa(&tun->peer_addr, namebuf, sizeof(namebuf)), tun->peer_port));

	/* Examine all L2TP links to get best possible fit tunnel parameters. */
	for (k = 0; k < gNumLinks; k++) {
		Link l2;
	        L2tpInfo pi2;

		if (!gLinks[k] || gLinks[k]->type != &gL2tpPhysType)
			continue;

		l2 = gLinks[k];
		pi2 = (L2tpInfo)l2->info;

		/* Simplified comparation as it is not a final one. */
		if ((!PhysIsBusy(l2)) &&
		    (pi2->server == s) &&
		    (IpAddrInRange(&pi2->conf.peer_addr, &tun->peer_addr)) &&
		    (pi2->conf.peer_port == 0 || pi2->conf.peer_port == tun->peer_port)) {
			
			if (pi == NULL || pi2->conf.peer_addr.width > pi->conf.peer_addr.width) {
				pi = pi2;
				if (u_rangehost(&pi->conf.peer_addr)) {
					break;	/* Nothing could be better */
				}
			}
		}
	}
	if (pi == NULL) {
		Log(LG_PHYS, ("L2TP: No link with requested parameters "
		    "was found"));
		goto fail;
	}

	/* Create vendor name AVP */
	avps = ppp_l2tp_avp_list_create();

	if (pi->conf.hostname[0] != 0) {
	    strlcpy(hostname, pi->conf.hostname, sizeof(hostname));
	} else {
	    (void)gethostname(hostname, sizeof(hostname) - 1);
	    hostname[sizeof(hostname) - 1] = '\0';
	}
	cap = htonl(L2TP_BEARER_DIGITAL|L2TP_BEARER_ANALOG);
	win = htons(8); /* XXX: this value is empirical. */
	if ((ppp_l2tp_avp_list_append(avps, 1, 0, AVP_HOST_NAME,
	      hostname, strlen(hostname)) == -1) ||
	    (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_VENDOR_NAME,
	      MPD_VENDOR, strlen(MPD_VENDOR)) == -1) ||
	    (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_BEARER_CAPABILITIES,
	      &cap, sizeof(cap)) == -1) ||
	    (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_RECEIVE_WINDOW_SIZE,
	      &win, sizeof(win)) == -1)) {
		Perror("L2TP: ppp_l2tp_avp_list_append");
		goto fail;
	}

	/* Create a new control connection */
	if ((tun->ctrl = ppp_l2tp_ctrl_create(gPeventCtx, &gGiantMutex,
	    &ppp_l2tp_server_ctrl_cb, u_addrtoid(&tun->peer_addr),
	    &node_id, hook, avps, 
	    pi->conf.secret, strlen(pi->conf.secret),
	    Enabled(&pi->conf.options, L2TP_CONF_HIDDEN))) == NULL) {
		Perror("L2TP: ppp_l2tp_ctrl_create");
		goto fail;
	}
	ppp_l2tp_ctrl_set_cookie(tun->ctrl, tun);

	/* Get a temporary netgraph socket node */
	if (NgMkSockNode(NULL, &csock, &dsock) == -1) {
		Perror("L2TP: NgMkSockNode");
		goto fail;
	}

	/* Connect to l2tp netgraph node "lower" hook */
	snprintf(namebuf, sizeof(namebuf), "[%lx]:", (u_long)node_id);
	memset(&connect, 0, sizeof(connect));
	strlcpy(connect.path, namebuf, sizeof(connect.path));
	strlcpy(connect.ourhook, hook, sizeof(connect.ourhook));
	strlcpy(connect.peerhook, hook, sizeof(connect.peerhook));
	if (NgSendMsg(csock, ".:", NGM_GENERIC_COOKIE,
	    NGM_CONNECT, &connect, sizeof(connect)) == -1) {
		Perror("L2TP: connect");
		goto fail;
	}

	/* Write the received packet to the node */
	if (NgSendData(dsock, hook, (u_char *)buf, len) == -1) {
		Perror("L2TP: NgSendData");
		goto fail;
	}

	/* Disconnect from netgraph node "lower" hook */
	memset(&rmhook, 0, sizeof(rmhook));
	strlcpy(rmhook.ourhook, hook, sizeof(rmhook.ourhook));
	if (NgSendMsg(csock, ".:", NGM_GENERIC_COOKIE,
	    NGM_RMHOOK, &rmhook, sizeof(rmhook)) == -1) {
		Perror("L2TP: rmhook");
		goto fail;
	}

	/* Attach a new UDP socket to "lower" hook */
	memset(&mkpeer, 0, sizeof(mkpeer));
	strlcpy(mkpeer.type, NG_KSOCKET_NODE_TYPE, sizeof(mkpeer.type));
	strlcpy(mkpeer.ourhook, hook, sizeof(mkpeer.ourhook));
	if (s->self_addr.family==AF_INET6) {
		snprintf(mkpeer.peerhook, sizeof(mkpeer.peerhook), "%d/%d/%d", PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
	} else {
	        snprintf(mkpeer.peerhook, sizeof(mkpeer.peerhook), "inet/dgram/udp");
	}
	if (NgSendMsg(csock, namebuf, NGM_GENERIC_COOKIE,
	    NGM_MKPEER, &mkpeer, sizeof(mkpeer)) == -1) {
		Perror("L2TP: mkpeer");
		goto fail;
	}

	/* Point name at ksocket node */
	strlcat(namebuf, hook, sizeof(namebuf));

	/* Make UDP port reusable */
	memset(&sockopt_buf, 0, sizeof(sockopt_buf));
	sockopt->level = SOL_SOCKET;
	sockopt->name = SO_REUSEADDR;
	memcpy(sockopt->value, &one, sizeof(int));
	if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_SETOPT, sockopt, sizeof(sockopt_buf)) == -1) {
		Perror("L2TP: setsockopt");
		goto fail;
	}
	sockopt->name = SO_REUSEPORT;
	if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_SETOPT, sockopt, sizeof(sockopt_buf)) == -1) {
		Perror("L2TP: setsockopt");
		goto fail;
	}

	/* Bind socket to a new port */
	u_addrtosockaddr(&s->self_addr,s->self_port,&sas);
	if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_BIND, &sas, sas.ss_len) == -1) {
		Perror("L2TP: bind");
		goto fail;
	}

	/* Connect socket to remote peer's IP and port */
	if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
	      NGM_KSOCKET_CONNECT, &peer_sas, peer_sas.ss_len) == -1
	    && errno != EINPROGRESS) {
		Perror("L2TP: connect");
		goto fail;
	}

	/* Add peer to our hash table */
	if (ghash_put(gL2tpTuns, tun) == -1) {
		Perror("L2TP: ghash_put");
		goto fail;
	}

	Log(LG_PHYS2, ("L2TP: Control connection %p %s %u <-> %s %u accepted",
	    tun->ctrl, u_addrtoa(&tun->self_addr,buf1,sizeof(buf1)), tun->self_port,
	    u_addrtoa(&tun->peer_addr,buf2,sizeof(buf2)), tun->peer_port));

	/* Clean up and return */
	ppp_l2tp_avp_list_destroy(&avps);
	(void)close(csock);
	(void)close(dsock);
	Freee(buf);
	return;

fail:
	/* Clean up after failure */
	if (csock != -1)
		(void)close(csock);
	if (dsock != -1)
		(void)close(dsock);
	if (tun != NULL) {
		ppp_l2tp_ctrl_destroy(&tun->ctrl);
		Freee(tun);
	}
	ppp_l2tp_avp_list_destroy(&avps);
	Freee(buf);
}


/*
 * L2tpListen()
 */

static int
L2tpListen(Link l)
{
	L2tpInfo	p = (L2tpInfo)l->info;
	struct l2tp_server *s;
	struct sockaddr_storage sa;
	char buf[48];
	struct ghash_walk walk;

	if (p->server)
	    return(1);

	ghash_walk_init(gL2tpServers, &walk);
	while ((s = ghash_walk_next(gL2tpServers, &walk)) != NULL) {
	    if ((u_addrcompare(&s->self_addr, &p->conf.self_addr) == 0) && 
		s->self_port == (p->conf.self_port?p->conf.self_port:L2TP_PORT)) {
		    s->refs++;
		    p->server = s;
		    return(1);
	    }
	}

	s = Malloc(MB_PHYS, sizeof(struct l2tp_server));
	s->refs = 1;
	u_addrcopy(&p->conf.self_addr, &s->self_addr);
	s->self_port = p->conf.self_port?p->conf.self_port:L2TP_PORT;
	
	/* Setup UDP socket that listens for new connections */
	if (s->self_addr.family==AF_INET6) {
		s->sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
	} else {
		s->sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	}
	if (s->sock == -1) {
		Perror("L2TP: socket");
		goto fail;
	}
	if (setsockopt(s->sock, SOL_SOCKET,
	    SO_REUSEADDR, &one, sizeof(one)) == -1) {
		Perror("L2TP: setsockopt");
		goto fail;
	}
	if (setsockopt(s->sock, SOL_SOCKET,
	    SO_REUSEPORT, &one, sizeof(one)) == -1) {
		Perror("L2TP: setsockopt");
		goto fail;
	}
	u_addrtosockaddr(&s->self_addr, s->self_port, &sa);
	if (bind(s->sock, (struct sockaddr *)&sa, sa.ss_len) == -1) {
		Perror("L2TP: bind");
		goto fail;
	}

	EventRegister(&s->event, EVENT_READ, s->sock,
	    EVENT_RECURRING, L2tpServerEvent, s);

	Log(LG_PHYS, ("L2TP: waiting for connection on %s %u",
	    u_addrtoa(&s->self_addr, buf, sizeof(buf)), s->self_port));
	
	p->server = s;
	ghash_put(gL2tpServers, s);
	return (1);
fail:
	if (s->sock)
	    close(s->sock);
	Freee(s);
	return (0);
}

/*
 * L2tpUnListen()
 */

static void
L2tpUnListen(Link l)
{
	L2tpInfo	p = (L2tpInfo)l->info;
	struct l2tp_server *s = p->server;
	char buf[48];

	if (!s)
	    return;

	s->refs--;
	if (s->refs == 0) {
	    Log(LG_PHYS, ("L2TP: stop waiting for connection on %s %u",
		u_addrtoa(&s->self_addr, buf, sizeof(buf)), s->self_port));
	
	    ghash_remove(gL2tpServers, s);
	    EventUnRegister(&s->event);
	    if (s->sock)
		close(s->sock);
	    Freee(s);
	    p->server = NULL;
	}
	return;
}

/*
 * L2tpNodeUpdate()
 */

static void
L2tpNodeUpdate(Link l)
{
    L2tpInfo const pi = (L2tpInfo) l->info;
    if (!pi->server) {
	if (Enabled(&l->conf.options, LINK_CONF_INCOMING))
	    L2tpListen(l);
    } else {
	if (!Enabled(&l->conf.options, LINK_CONF_INCOMING))
	    L2tpUnListen(l);
    }
}

/*
 * L2tpSetCommand()
 */

static int
L2tpSetCommand(Context ctx, int ac, const char *const av[], const void *arg)
{
    L2tpInfo		const l2tp = (L2tpInfo) ctx->lnk->info;
    char		**fqdn_peer_addr = &l2tp->conf.fqdn_peer_addr;
    char		**peer_mask = &l2tp->conf.peer_mask;
    struct u_range	rng;
    int			port;

    switch ((intptr_t)arg) {
	case SET_SELFADDR:
	case SET_PEERADDR:
	    if ((ac == 1 || ac == 2) && (intptr_t)arg == SET_PEERADDR) {
		if (*fqdn_peer_addr)
		    Freee(*fqdn_peer_addr);
		*fqdn_peer_addr = Mstrdup(MB_PHYS, av[0]);
	    }
    	    if (ac < 1 || ac > 2 || !ParseRange(av[0], &rng, ALLOW_IPV4|ALLOW_IPV6))
		return(-1);
    	    if (ac > 1) {
		if ((port = atoi(av[1])) < 0 || port > 0xffff)
		    return(-1);
    	    } else {
		port = 0;
    	    }
    	    if ((intptr_t)arg == SET_SELFADDR) {
		l2tp->conf.self_addr = rng.addr;
		l2tp->conf.self_port = port;
		if (l2tp->server) {
		    L2tpUnListen(ctx->lnk);
		    L2tpListen(ctx->lnk);
		}
    	    } else {
		l2tp->conf.peer_addr = rng;
		l2tp->conf.peer_port = port;
    	    }
    	    break;
	case SET_CALLINGNUM:
    	    if (ac != 1)
		return(-1);
    	    strlcpy(l2tp->conf.callingnum, av[0], sizeof(l2tp->conf.callingnum));
    	    break;
	case SET_CALLEDNUM:
    	    if (ac != 1)
		return(-1);
    	    strlcpy(l2tp->conf.callednum, av[0], sizeof(l2tp->conf.callednum));
    	    break;
	case SET_HOSTNAME:
    	    if (ac != 1)
		return(-1);
    	    strlcpy(l2tp->conf.hostname, av[0], sizeof(l2tp->conf.hostname));
    	    break;
	case SET_PEERMASK:
    	    if (ac != 1)
		return(-1);
	    if (*peer_mask)
		Freee(*peer_mask);
	    *peer_mask = Mstrdup(MB_PHYS, av[0]);
    	    break;
	case SET_SECRET:
    	    if (ac != 1)
		return(-1);
    	    strlcpy(l2tp->conf.secret, av[0], sizeof(l2tp->conf.secret));
    	    break;
	case SET_ENABLE:
    	    EnableCommand(ac, av, &l2tp->conf.options, gConfList);
    	    break;
	case SET_DISABLE:
    	    DisableCommand(ac, av, &l2tp->conf.options, gConfList);
    	    break;
	default:
    	    assert(0);
    }
    return(0);
}

/*
 * L2tpsStat()
 */

int
L2tpsStat(Context ctx, int ac, const char *const av[], const void *arg)
{
    struct l2tp_tun	*tun;
    struct ghash_walk	walk;
    char	buf1[64], buf2[64], buf3[64];

    (void)ac;
    (void)av;
    (void)arg;

    Printf("Active L2TP tunnels:\r\n");
    ghash_walk_init(gL2tpTuns, &walk);
    while ((tun = ghash_walk_next(gL2tpTuns, &walk)) != NULL) {

	u_addrtoa(&tun->self_addr, buf1, sizeof(buf1));
	u_addrtoa(&tun->peer_addr, buf2, sizeof(buf2));
	ppp_l2tp_ctrl_stats(tun->ctrl, buf3, sizeof(buf3));
	Printf("%p\t %s %d <=> %s %d\t%s %d calls\r\n",
    	    tun->ctrl, buf1, tun->self_port, buf2, tun->peer_port,
	    buf3, tun->active_sessions);
    }

    return 0;
}

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