/*
* 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>
/*
* 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 */
} 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_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, char *av[], 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 },
{ "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 },
};
/*
* INTERNAL VARIABLES
*/
static 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 },
};
int L2tpListenUpdateSheduled = 0;
struct pppTimer L2tpListenUpdateTimer;
struct ghash *gL2tpServers;
struct ghash *gL2tpTuns;
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;
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 (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, 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("[%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)) {
/* 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);
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 (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("\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;
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);
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)) {
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 (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 && ptrs->callingnum->number)
strlcpy(pi->callingnum, ptrs->callingnum->number, sizeof(pi->callingnum));
if (ptrs->callednum && ptrs->callednum->number)
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;
/* 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, char *av[], void *arg)
{
L2tpInfo const l2tp = (L2tpInfo) ctx->lnk->info;
char **fqdn_peer_addr = &l2tp->conf.fqdn_peer_addr;
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_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, char *av[], void *arg)
{
struct l2tp_tun *tun;
struct ghash_walk walk;
char buf1[64], buf2[64], buf3[64];
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>