/*
* 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 <sys/types.h>
#include <pdel/util/ghash.h>
#include <netgraph/ng_message.h>
#ifdef __DragonFly__
#include <netgraph/socket/ng_socket.h>
#include <netgraph/ksocket/ng_ksocket.h>
#include <netgraph/l2tp/ng_l2tp.h>
#else
#include <netgraph/ng_socket.h>
#include <netgraph/ng_ksocket.h>
#include <netgraph/ng_l2tp.h>
#endif
#include <netgraph.h>
/*
* DEFINITIONS
*/
#define L2TP_MTU 1600
#define L2TP_MRU L2TP_MTU
#define L2TP_PORT 1701
#define L2TP_MAX_ERRORS 10
#define L2TP_REOPEN_PAUSE 5
#define MAX_IOVEC 32
#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 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 */
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 */
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 */
} 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_ORIGINATE, /* allow originating connections to peer */
L2TP_CONF_INCOMING, /* allow accepting connections from peer */
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 */
};
/*
* INTERNAL FUNCTIONS
*/
static int L2tpInit(PhysInfo p);
static void L2tpOpen(PhysInfo p);
static void L2tpClose(PhysInfo p);
static void L2tpShutdown(PhysInfo p);
static void L2tpStat(Context ctx);
static int L2tpOriginated(PhysInfo p);
static int L2tpIsSync(PhysInfo p);
static int L2tpSetAccm(PhysInfo p, u_int32_t xmit, u_int32_t recv);
static int L2tpPeerAddr(PhysInfo p, void *buf, int buf_len);
static int L2tpPeerPort(PhysInfo p, void *buf, int buf_len);
static int L2tpCallingNum(PhysInfo p, void *buf, int buf_len);
static int L2tpCalledNum(PhysInfo p, void *buf, int buf_len);
static int L2tpSetCallingNum(PhysInfo p, void *buf);
static int L2tpSetCalledNum(PhysInfo p, void *buf);
static void L2tpDoClose(PhysInfo l2tp);
static void L2tpHookUpIncoming(PhysInfo p);
static void L2tpNodeUpdate(PhysInfo p);
static void L2tpListenUpdate(void *arg);
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",
.minReopenDelay = L2TP_REOPEN_PAUSE,
.mtu = L2TP_MTU,
.mru = L2TP_MRU,
.init = L2tpInit,
.open = L2tpOpen,
.close = L2tpClose,
.shutdown = L2tpShutdown,
.showstat = L2tpStat,
.originate = L2tpOriginated,
.issync = L2tpIsSync,
.setaccm = L2tpSetAccm,
.setcallingnum = L2tpSetCallingNum,
.setcallednum = L2tpSetCalledNum,
.peeraddr = L2tpPeerAddr,
.peerport = L2tpPeerPort,
.callingnum = L2tpCallingNum,
.callednum = L2tpCalledNum,
};
const struct cmdtab L2tpSetCmds[] = {
{ "self ip [port]", "Set local IP address",
L2tpSetCommand, NULL, (void *) SET_SELFADDR },
{ "peer ip [port]", "Set remote IP address",
L2tpSetCommand, NULL, (void *) SET_PEERADDR },
{ "callingnum number", "Set calling L2TP telephone number",
L2tpSetCommand, NULL, (void *) SET_CALLINGNUM },
{ "callednum number", "Set called L2TP telephone number",
L2tpSetCommand, NULL, (void *) SET_CALLEDNUM },
{ "hostname name", "Set L2TP local hostname",
L2tpSetCommand, NULL, (void *) SET_HOSTNAME },
{ "secret sec", "Set L2TP tunnel secret",
L2tpSetCommand, NULL, (void *) SET_SECRET },
{ "enable [opt ...]", "Enable option",
L2tpSetCommand, NULL, (void *) SET_ENABLE },
{ "disable [opt ...]", "Disable option",
L2tpSetCommand, NULL, (void *) SET_DISABLE },
{ NULL },
};
/*
* INTERNAL VARIABLES
*/
static struct confinfo gConfList[] = {
{ 0, L2TP_CONF_ORIGINATE, "originate" },
{ 0, L2TP_CONF_INCOMING, "incoming" },
{ 0, L2TP_CONF_OUTCALL, "outcall" },
{ 0, L2TP_CONF_HIDDEN, "hidden" },
{ 0, L2TP_CONF_LENGTH, "length" },
{ 0, L2TP_CONF_DATASEQ, "dataseq" },
{ 0, 0, NULL },
};
int L2tpListenUpdateSheduled = 0;
struct pppTimer L2tpListenUpdateTimer;
static u_char gInitialized = 0;
struct ghash *gL2tpServers;
struct ghash *gL2tpTuns;
int one = 1;
/*
* L2tpInit()
*/
static int
L2tpInit(PhysInfo p)
{
L2tpInfo l2tp;
if (!gInitialized) {
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);
gInitialized = 1;
}
/* Initialize this link */
l2tp = (L2tpInfo) (p->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;
Enable(&l2tp->conf.options, L2TP_CONF_DATASEQ);
return(0);
}
/*
* L2tpOpen()
*/
static void
L2tpOpen(PhysInfo p)
{
L2tpInfo const pi = (L2tpInfo) p->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 peer_sas;
struct sockaddr_storage sas;
char hook[NG_HOOKLEN + 1];
char namebuf[64];
char hostname[MAXHOSTNAMELEN];
ng_ID_t node_id;
int csock = -1;
int dsock = -1;
struct ghash_walk walk;
u_int32_t cap;
pi->opened=1;
if (pi->incoming == 1) {
Log(LG_PHYS2, ("[%s] L2tpOpen() on incoming call", p->name));
if (p->state==PHYS_STATE_READY) {
p->state = PHYS_STATE_UP;
if (pi->outcall) {
pi->sync = 1;
if (p->rep) {
if ((avps = ppp_l2tp_avp_list_create()) == NULL) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_create: %s",
p->name, strerror(errno)));
} else {
uint32_t fr;
if (RepIsSync(p)) {
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_append: %s",
p->name, strerror(errno)));
}
}
} else {
avps = NULL;
}
Log(LG_PHYS, ("[%s] L2TP: Call #%u connected", p->name,
ppp_l2tp_sess_get_serial(pi->sess)));
ppp_l2tp_connected(pi->sess, avps);
if (avps)
ppp_l2tp_avp_list_destroy(&avps);
}
L2tpHookUpIncoming(p);
PhysUp(p);
}
return;
}
/* Sanity check. */
if (p->state != PHYS_STATE_DOWN) {
Log(LG_PHYS, ("[%s] L2TP: allready active", p->name));
return;
};
if (!Enabled(&pi->conf.options, L2TP_CONF_ORIGINATE)) {
Log(LG_ERR, ("[%s] L2TP: originate option is not enabled",
p->name));
PhysDown(p, STR_DEV_NOT_READY, NULL);
return;
};
strlcpy(pi->callingnum, pi->conf.callingnum, sizeof(pi->callingnum));
strlcpy(pi->callednum, pi->conf.callednum, sizeof(pi->callednum));
ghash_walk_init(gL2tpTuns, &walk);
while ((tun = ghash_walk_next(gL2tpTuns, &walk)) != NULL) {
if (tun->ctrl && tun->alive &&
(IpAddrInRange(&pi->conf.peer_addr, &tun->peer_addr)) &&
(pi->conf.peer_port == 0 || pi->conf.peer_port == tun->peer_port)) {
pi->tun = tun;
if (tun->connected) { /* if tun is connected then just initiate */
/* Create number AVPs */
if ((avps = ppp_l2tp_avp_list_create()) == NULL) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_create: %s",
p->name, strerror(errno)));
} else {
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_append: %s",
p->name, strerror(errno)));
}
}
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_append: %s",
p->name, strerror(errno)));
}
}
}
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_initiate: %s",
p->name, strerror(errno)));
PhysDown(p, STR_ERROR, NULL);
ppp_l2tp_avp_list_destroy(&avps);
pi->sess = NULL;
pi->tun = 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",
p->name, (pi->outcall?"Outgoing":"Incoming"),
ppp_l2tp_sess_get_serial(sess), tun->ctrl));
ppp_l2tp_sess_set_cookie(sess, p);
if (!pi->outcall) {
pi->sync = 1;
if (p->rep) {
if ((avps = ppp_l2tp_avp_list_create()) == NULL) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_create: %s",
p->name, strerror(errno)));
} else {
uint32_t fr;
if (RepIsSync(p)) {
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_append: %s",
p->name, strerror(errno)));
}
}
} 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. */
if ((tun = Malloc(MB_PHYS, sizeof(*tun))) == NULL) {
Log(LG_ERR, ("[%s] malloc: %s",
p->name, strerror(errno)));
return;
}
memset(tun, 0, sizeof(*tun));
sockaddrtou_addr(&peer_sas,&tun->peer_addr,&tun->peer_port);
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 */
if ((avps = ppp_l2tp_avp_list_create()) == NULL) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_create: %s",
p->name, strerror(errno)));
goto fail;
}
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);
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)) {
Log(LG_ERR, ("L2TP: ppp_l2tp_avp_list_append: %s", strerror(errno)));
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_ctrl_create: %s",
p->name, strerror(errno)));
goto fail;
}
ppp_l2tp_ctrl_set_cookie(tun->ctrl, tun);
/* Get a temporary netgraph socket node */
if (NgMkSockNode(NULL, &csock, &dsock) == -1) {
Log(LG_ERR, ("[%s] NgMkSockNode: %s",
p->name, strerror(errno)));
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) {
Log(LG_ERR, ("[%s] mkpeer: %s",
p->name, strerror(errno)));
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) {
Log(LG_ERR, ("[%s] setsockopt: %s",
p->name, strerror(errno)));
goto fail;
}
sockopt->name = SO_REUSEPORT;
if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_SETOPT, sockopt, sizeof(sockopt_buf)) == -1) {
Log(LG_ERR, ("[%s] setsockopt: %s",
p->name, strerror(errno)));
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) {
Log(LG_ERR, ("[%s] bind: %s",
p->name, strerror(errno)));
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) {
Log(LG_ERR, ("[%s] connect: %s",
p->name, strerror(errno)));
goto fail;
}
if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_GETNAME, NULL, 0) == -1) {
Log(LG_ERR, ("[%s] getname send: %s",
p->name, strerror(errno)));
} else
if (NgRecvMsg(csock, &ugetsas.reply, sizeof(ugetsas), NULL) == -1) {
Log(LG_ERR, ("[%s] getname recv: %s",
p->name, strerror(errno)));
} else {
sockaddrtou_addr(getsas,&tun->self_addr,&tun->self_port);
}
/* Add peer to our hash table */
if (ghash_put(gL2tpTuns, tun) == -1) {
Log(LG_ERR, ("[%s] ghash_put: %s",
p->name, strerror(errno)));
goto fail;
}
pi->tun = tun;
Log(LG_PHYS, ("L2TP: Control connection %p initiated", tun->ctrl));
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(MB_PHYS, tun);
}
PhysDown(p, STR_DEV_NOT_READY, NULL);
};
/*
* L2tpClose()
*/
static void
L2tpClose(PhysInfo p)
{
L2tpInfo const pi = (L2tpInfo) p->info;
pi->opened = 0;
pi->incoming = 0;
pi->outcall = 0;
if (p->state == PHYS_STATE_DOWN)
return;
PhysDown(p, 0, NULL);
L2tpDoClose(p);
if (pi->sess) {
Log(LG_PHYS, ("[%s] L2TP: Call #%u terminated locally", p->name,
ppp_l2tp_sess_get_serial(pi->sess)));
ppp_l2tp_terminate(pi->sess, L2TP_RESULT_ADMIN, 0, NULL);
pi->sess = NULL;
}
pi->tun = NULL;
pi->callingnum[0]=0;
pi->callednum[0]=0;
p->state = PHYS_STATE_DOWN;
}
/*
* L2tpShutdown()
*/
static void
L2tpShutdown(PhysInfo p)
{
struct ghash_walk walk;
struct l2tp_tun *tun;
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);
}
}
}
/*
* L2tpDoClose()
*/
static void
L2tpDoClose(PhysInfo p)
{
int csock = -1;
L2tpInfo const pi = (L2tpInfo) p->info;
const char *hook;
ng_ID_t node_id;
char path[NG_PATHLEN + 1];
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) {
Log(LG_ERR, ("L2TP: NgMkSockNode: %s", strerror(errno)));
return;
}
/* Disconnect session hook. */
snprintf(path, sizeof(path), "[%lx]:", (u_long)node_id);
NgFuncDisconnect(csock, p->name, path, hook);
close(csock);
}
}
}
/*
* L2tpOriginated()
*/
static int
L2tpOriginated(PhysInfo p)
{
L2tpInfo const l2tp = (L2tpInfo) p->info;
return(l2tp->incoming ? LINK_ORIGINATE_REMOTE : LINK_ORIGINATE_LOCAL);
}
/*
* L2tpIsSync()
*/
static int
L2tpIsSync(PhysInfo p)
{
L2tpInfo const l2tp = (L2tpInfo) p->info;
return (l2tp->sync);
}
static int
L2tpSetAccm(PhysInfo p, u_int32_t xmit, u_int32_t recv)
{
L2tpInfo const l2tp = (L2tpInfo) p->info;
if (!l2tp->sess)
return (-1);
return (ppp_l2tp_set_link_info(l2tp->sess, xmit, recv));
}
static int
L2tpSetCallingNum(PhysInfo p, void *buf)
{
L2tpInfo const l2tp = (L2tpInfo) p->info;
strlcpy(l2tp->conf.callingnum, buf, sizeof(l2tp->conf.callingnum));
return(0);
}
static int
L2tpSetCalledNum(PhysInfo p, void *buf)
{
L2tpInfo const l2tp = (L2tpInfo) p->info;
strlcpy(l2tp->conf.callednum, buf, sizeof(l2tp->conf.callednum));
return(0);
}
static int
L2tpPeerAddr(PhysInfo p, void *buf, int buf_len)
{
L2tpInfo const l2tp = (L2tpInfo) p->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(PhysInfo p, void *buf, int buf_len)
{
L2tpInfo const l2tp = (L2tpInfo) p->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
L2tpCallingNum(PhysInfo p, void *buf, int buf_len)
{
L2tpInfo const l2tp = (L2tpInfo) p->info;
strlcpy((char*)buf, l2tp->callingnum, buf_len);
return(0);
}
static int
L2tpCalledNum(PhysInfo p, void *buf, int buf_len)
{
L2tpInfo const l2tp = (L2tpInfo) p->info;
strlcpy((char*)buf, l2tp->callednum, buf_len);
return(0);
}
/*
* L2tpStat()
*/
void
L2tpStat(Context ctx)
{
L2tpInfo const l2tp = (L2tpInfo) ctx->phys->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 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");
Printf("\tState : %s\r\n", gPhysStateNames[ctx->phys->state]);
if (ctx->phys->state != PHYS_STATE_DOWN) {
Printf("\tIncoming : %s\r\n", (l2tp->incoming?"YES":"NO"));
if (l2tp->tun) {
Printf("\tCurrent self : %s, port %u\r\n",
u_addrtoa(&l2tp->tun->self_addr, buf, sizeof(buf)), l2tp->tun->self_port);
Printf("\tCurrent peer : %s, port %u\r\n",
u_addrtoa(&l2tp->tun->peer_addr, buf, sizeof(buf)), l2tp->tun->peer_port);
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;
int k;
Log(LG_PHYS, ("L2TP: Control connection %p connected", ctrl));
/* Examine all L2TP links. */
for (k = 0; k < gNumPhyses; k++) {
PhysInfo p;
L2tpInfo pi;
if (gPhyses[k] && gPhyses[k]->type != &gL2tpPhysType)
continue;
p = gPhyses[k];
pi = (L2tpInfo)p->info;
if (pi->tun != tun)
continue;
tun->connected = 1;
/* Create number AVPs */
if ((avps = ppp_l2tp_avp_list_create()) == NULL) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_create: %s",
p->name, strerror(errno)));
} else {
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_append: %s",
p->name, strerror(errno)));
}
}
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_append: %s",
p->name, strerror(errno)));
}
}
}
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) {
Log(LG_ERR, ("ppp_l2tp_initiate: %s", strerror(errno)));
PhysDown(p, STR_ERROR, NULL);
pi->sess = NULL;
pi->tun = 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",
p->name, (pi->outcall?"Outgoing":"Incoming"),
ppp_l2tp_sess_get_serial(sess), tun->ctrl));
ppp_l2tp_sess_set_cookie(sess, p);
if (!pi->outcall) {
pi->sync = 1;
if (p->rep) {
if ((avps = ppp_l2tp_avp_list_create()) == NULL) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_create: %s",
p->name, strerror(errno)));
} else {
uint32_t fr;
if (RepIsSync(p)) {
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) {
Log(LG_ERR, ("[%s] ppp_l2tp_avp_list_append: %s",
p->name, strerror(errno)));
}
}
} 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 < gNumPhyses; k++) {
PhysInfo p;
L2tpInfo pi;
if (gPhyses[k] && gPhyses[k]->type != &gL2tpPhysType)
continue;
p = gPhyses[k];
pi = (L2tpInfo)p->info;
if (pi->tun != tun)
continue;
p->state = PHYS_STATE_DOWN;
PhysDown(p, STR_DROPPED, NULL);
L2tpDoClose(p);
pi->sess = NULL;
pi->tun = NULL;
pi->callingnum[0]=0;
pi->callednum[0]=0;
};
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(MB_PHYS, 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;
time_t const now = time(NULL);
PhysInfo p = NULL;
L2tpInfo pi = NULL;
int k;
/* Convert AVP's to friendly form */
if ((ptrs = ppp_l2tp_avp_list2ptrs(avps)) == NULL) {
Log(LG_ERR, ("L2TP: error decoding AVP list: %s", strerror(errno)));
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 < gNumPhyses; k++) {
PhysInfo p2;
L2tpInfo pi2;
if (gPhyses[k] && gPhyses[k]->type != &gL2tpPhysType)
continue;
p2 = gPhyses[k];
pi2 = (L2tpInfo)p2->info;
if ((p2->state == PHYS_STATE_DOWN) &&
(now - p2->lastClose >= L2TP_REOPEN_PAUSE) &&
Enabled(&pi2->conf.options, L2TP_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) {
p = p2;
pi = pi2;
if (u_rangehost(&pi->conf.peer_addr)) {
break; /* Nothing could be better */
}
}
}
}
if (pi != NULL) {
Log(LG_PHYS, ("[%s] L2TP: %s call #%u via control connection %p accepted",
p->name, (out?"Outgoing":"Incoming"),
ppp_l2tp_sess_get_serial(sess), ctrl));
if (out)
p->state = PHYS_STATE_READY;
else
p->state = PHYS_STATE_CONNECTING;
pi->incoming = 1;
pi->outcall = out;
pi->tun = tun;
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(p);
ppp_l2tp_sess_set_cookie(sess, p);
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)
{
PhysInfo p;
L2tpInfo pi;
struct ppp_l2tp_avp_ptrs *ptrs = NULL;
p = ppp_l2tp_sess_get_cookie(sess);
pi = (L2tpInfo)p->info;
Log(LG_PHYS, ("[%s] L2TP: Call #%u connected", p->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) {
Log(LG_ERR, ("L2TP: error decoding AVP list: %s", strerror(errno)));
} else {
if (ptrs->framing && ptrs->framing->sync) {
pi->sync = 1;
} else {
pi->sync = 0;
}
ppp_l2tp_avp_ptrs_destroy(&ptrs);
}
}
if (pi->opened) {
p->state = PHYS_STATE_UP;
L2tpHookUpIncoming(p);
PhysUp(p);
} else {
p->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];
PhysInfo p;
L2tpInfo pi;
p = ppp_l2tp_sess_get_cookie(sess);
pi = (L2tpInfo) p->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", p->name,
ppp_l2tp_sess_get_serial(sess), buf));
p->state = PHYS_STATE_DOWN;
PhysDown(p, STR_DROPPED, NULL);
L2tpDoClose(p);
pi->sess = NULL;
pi->tun = NULL;
pi->callingnum[0]=0;
pi->callednum[0]=0;
}
/*
* 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)
{
PhysInfo p = ppp_l2tp_sess_get_cookie(sess);
if (p->rep != NULL) {
RepSetAccm(p, xmit, recv);
}
};
/*
* Read an incoming packet that might be a new L2TP connection.
*/
static void
L2tpHookUpIncoming(PhysInfo p)
{
int csock = -1;
L2tpInfo pi = (L2tpInfo)p->info;
const char *hook;
ng_ID_t node_id;
char path[NG_PATHLEN + 1];
struct ngm_connect cn;
/* Get a temporary netgraph socket node */
if (NgMkSockNode(NULL, &csock, NULL) == -1) {
Log(LG_ERR, ("L2TP: NgMkSockNode: %s", strerror(errno)));
goto fail;
}
/* Get this link's node and hook */
ppp_l2tp_sess_get_hook(pi->sess, &node_id, &hook);
/* Connect our ng_ppp(4) node link hook and ng_l2tp(4) node. */
if (!PhysGetUpperHook(p, cn.path, cn.peerhook)) {
Log(LG_PHYS, ("[%s] L2TP: can't get upper hook", p->name));
goto fail;
}
snprintf(path, sizeof(path), "[%lx]:", (u_long)node_id);
snprintf(cn.ourhook, sizeof(cn.ourhook), hook);
if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_CONNECT,
&cn, sizeof(cn)) < 0) {
Log(LG_ERR, ("[%s] L2TP: can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\": %s",
p->name, path, cn.ourhook, cn.path, cn.peerhook, strerror(errno)));
goto fail;
}
close(csock);
return;
fail:
/* Clean up after failure */
ppp_l2tp_terminate(pi->sess, L2TP_RESULT_ERROR,
L2TP_ERROR_GENERIC, strerror(errno));
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;
PhysInfo p = NULL;
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_HOOKLEN + 1];
char hostname[MAXHOSTNAMELEN];
socklen_t sas_len;
char namebuf[64];
ng_ID_t node_id;
int csock = -1;
int dsock = -1;
int len;
u_int32_t cap;
int k;
/* Allocate buffer */
if ((buf = Malloc(MB_PHYS, bufsize)) == NULL) {
Log(LG_ERR, ("L2TP: malloc: %s", strerror(errno)));
goto fail;
}
/* Read packet */
sas_len = sizeof(peer_sas);
if ((len = recvfrom(s->sock, buf, bufsize, 0,
(struct sockaddr *)&peer_sas, &sas_len)) == -1) {
Log(LG_ERR, ("L2TP: recvfrom: %s", strerror(errno)));
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 */
if ((tun = Malloc(MB_PHYS, sizeof(*tun))) == NULL) {
Log(LG_ERR, ("L2TP: malloc: %s", strerror(errno)));
goto fail;
}
memset(tun, 0, 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 < gNumPhyses; k++) {
PhysInfo p2;
L2tpInfo pi2;
if (gPhyses[k] && gPhyses[k]->type != &gL2tpPhysType)
continue;
p2 = gPhyses[k];
pi2 = (L2tpInfo)p2->info;
/* Simplified comparation as it is not a final one. */
if ((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) {
p = p2;
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 */
if ((avps = ppp_l2tp_avp_list_create()) == NULL) {
Log(LG_ERR, ("L2TP: ppp_l2tp_avp_list_create: %s", strerror(errno)));
goto fail;
}
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);
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)) {
Log(LG_ERR, ("L2TP: ppp_l2tp_avp_list_append: %s", strerror(errno)));
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) {
Log(LG_ERR, ("L2TP: ppp_l2tp_ctrl_create: %s", strerror(errno)));
goto fail;
}
ppp_l2tp_ctrl_set_cookie(tun->ctrl, tun);
/* Get a temporary netgraph socket node */
if (NgMkSockNode(NULL, &csock, &dsock) == -1) {
Log(LG_ERR, ("L2TP: NgMkSockNode: %s", strerror(errno)));
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) {
Log(LG_ERR, ("L2TP: %s: %s", "connect", strerror(errno)));
goto fail;
}
/* Write the received packet to the node */
if (NgSendData(dsock, hook, (u_char *)buf, len) == -1) {
Log(LG_ERR, ("L2TP: %s: %s", "NgSendData", strerror(errno)));
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) {
Log(LG_ERR, ("L2TP: %s: %s", "rmhook", strerror(errno)));
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) {
Log(LG_ERR, ("L2TP: %s: %s", "mkpeer", strerror(errno)));
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) {
Log(LG_ERR, ("L2TP: setsockopt: %s", strerror(errno)));
goto fail;
}
sockopt->name = SO_REUSEPORT;
if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_SETOPT, sockopt, sizeof(sockopt_buf)) == -1) {
Log(LG_ERR, ("L2TP: setsockopt: %s", strerror(errno)));
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) {
Log(LG_ERR, ("L2TP: bind: %s", strerror(errno)));
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) {
Log(LG_ERR, ("L2TP: connect: %s", strerror(errno)));
goto fail;
}
/* Add peer to our hash table */
if (ghash_put(gL2tpTuns, tun) == -1) {
Log(LG_ERR, ("L2TP: %s: %s", "ghash_put", strerror(errno)));
goto fail;
}
/* Clean up and return */
ppp_l2tp_avp_list_destroy(&avps);
(void)close(csock);
(void)close(dsock);
Freee(MB_PHYS, 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(MB_PHYS, tun);
}
ppp_l2tp_avp_list_destroy(&avps);
Freee(MB_PHYS, buf);
}
/*
* L2tpServerCreate()
*/
static struct l2tp_server *
L2tpServerCreate(L2tpInfo const p)
{
struct l2tp_server *s;
struct sockaddr_storage sa;
char buf[64];
if ((s = Malloc(MB_PHYS, sizeof(struct l2tp_server))) == NULL) {
return (NULL);
}
memset(s, 0, sizeof(*s));
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) {
Log(LG_ERR, ("L2TP: socket: %s", strerror(errno)));
goto fail;
}
if (setsockopt(s->sock, SOL_SOCKET,
SO_REUSEADDR, &one, sizeof(one)) == -1) {
Log(LG_ERR, ("L2TP: setsockopt: %s", strerror(errno)));
goto fail;
}
if (setsockopt(s->sock, SOL_SOCKET,
SO_REUSEPORT, &one, sizeof(one)) == -1) {
Log(LG_ERR, ("L2TP: setsockopt: %s", strerror(errno)));
goto fail;
}
u_addrtosockaddr(&s->self_addr, s->self_port, &sa);
if (bind(s->sock, (struct sockaddr *)&sa, sa.ss_len) == -1) {
Log(LG_ERR, ("L2TP: bind: %s", strerror(errno)));
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));
return (s);
fail:
if (s->sock)
close(s->sock);
Freee(MB_PHYS, s);
return (NULL);
}
/*
* L2tpNodeUpdate()
*/
static void
L2tpNodeUpdate(PhysInfo p)
{
L2tpInfo pe = (L2tpInfo)p->info;
if (Enabled(&pe->conf.options, L2TP_CONF_INCOMING) &&
(!L2tpListenUpdateSheduled)) {
/* Set a timer to run PppoeListenUpdate(). */
TimerInit(&L2tpListenUpdateTimer, "L2tpListenUpdate",
0, L2tpListenUpdate, NULL);
TimerStart(&L2tpListenUpdateTimer);
L2tpListenUpdateSheduled = 1;
}
}
/*
* L2tpListenUpdate()
*/
static void
L2tpListenUpdate(void *arg)
{
int k;
/* Examine all L2TP links */
for (k = 0; k < gNumPhyses; k++) {
if (gPhyses[k] && gPhyses[k]->type == &gL2tpPhysType) {
L2tpInfo const p = (L2tpInfo)gPhyses[k]->info;
if (Enabled(&p->conf.options, L2TP_CONF_INCOMING)) {
struct ghash_walk walk;
struct l2tp_server *srv;
ghash_walk_init(gL2tpServers, &walk);
while ((srv = ghash_walk_next(gL2tpServers, &walk)) != NULL) {
if ((u_addrcompare(&srv->self_addr, &p->conf.self_addr) == 0) &&
srv->self_port == (p->conf.self_port?p->conf.self_port:L2TP_PORT)) {
p->server = srv;
break;
}
}
if (srv == NULL) {
if ((srv = L2tpServerCreate(p)) == NULL) {
Log(LG_ERR, ("L2tpServerCreate error"));
continue;
}
p->server = srv;
ghash_put(gL2tpServers, srv);
}
}
}
}
}
/*
* L2tpSetCommand()
*/
static int
L2tpSetCommand(Context ctx, int ac, char *av[], void *arg)
{
L2tpInfo const l2tp = (L2tpInfo) ctx->phys->info;
struct u_range rng;
int port;
switch ((intptr_t)arg) {
case SET_SELFADDR:
case SET_PEERADDR:
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;
} else {
l2tp->conf.peer_addr = rng;
l2tp->conf.peer_port = port;
}
break;
case SET_CALLINGNUM:
if (ac != 1)
return(-1);
snprintf(l2tp->conf.callingnum, sizeof(l2tp->conf.callingnum), "%s", av[0]);
break;
case SET_CALLEDNUM:
if (ac != 1)
return(-1);
snprintf(l2tp->conf.callednum, sizeof(l2tp->conf.callednum), "%s", av[0]);
break;
case SET_HOSTNAME:
if (ac != 1)
return(-1);
snprintf(l2tp->conf.hostname, sizeof(l2tp->conf.hostname), "%s", av[0]);
break;
case SET_SECRET:
if (ac != 1)
return(-1);
snprintf(l2tp->conf.secret, sizeof(l2tp->conf.secret), "%s", av[0]);
break;
case SET_ENABLE:
EnableCommand(ac, av, &l2tp->conf.options, gConfList);
L2tpNodeUpdate(ctx->phys);
break;
case SET_DISABLE:
DisableCommand(ac, av, &l2tp->conf.options, gConfList);
L2tpNodeUpdate(ctx->phys);
break;
default:
assert(0);
}
return(0);
}
syntax highlighted by Code2HTML, v. 0.9.1