#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <syslog.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdarg.h>
#include <event.h>
#ifdef USE_LIBSOCKUTIL
#include <seil/sockfromto.h>
#else
#include "recvfromto.h"
#endif
#include "bytebuf.h"
#include "hash.h"
#include "slist.h"
#include "debugutil.h"
#include "l2tp.h"
#include "l2tp_subr.h"
#include "l2tp_local.h"
#include "addr_range.h"
#include "net_utils.h"
#ifdef L2TPD_DEBUG
#define L2TPD_ASSERT(x) ASSERT(x)
#define L2TPD_DBG(x) l2tpd_log x
#else
#define L2TPD_ASSERT(x)
#endif
#define L2TPD_IPSEC_POLICY_IN "in ipsec esp/transport//require"
#define L2TPD_IPSEC_POLICY_OUT "out ipsec esp/transport//require"
static void l2tpd_io_event (int, short, void *);
static inline int short_cmp (const void *, const void *);
static inline uint32_t short_hash (const void *, int);
static u_int l2tpd_id_seq = 0;
int
l2tpd_init(l2tpd *_this)
{
int i, off;
u_int id;
L2TPD_ASSERT(_this != NULL);
memset(_this, 0, sizeof(l2tpd));
slist_init(&_this->listener);
slist_init(&_this->free_session_id_list);
_this->id = l2tpd_id_seq++;
if ((_this->ctrl_map = hash_create(short_cmp, short_hash,
L2TPD_TUNNEL_HASH_SIZ)) == NULL) {
log_printf(LOG_ERR, "hash_create() failed in %s(): %m",
__func__);
return 1;
}
if (slist_add(&_this->free_session_id_list,
(void *)L2TP_SESSION_ID_SHUFFLE_MARK) == NULL) {
l2tpd_log(_this, LOG_ERR, "slist_add() failed on %s(): %m",
__func__);
return 1;
}
off = arc4random() & L2TP_SESSION_ID_MASK;
for (i = 0; i < L2TP_NCALL; i++) {
id = (i + off) & L2TP_SESSION_ID_MASK;
if (id == 0)
id = (off - 1) & L2TP_SESSION_ID_MASK;
if (slist_add(&_this->free_session_id_list,
(void *)(uintptr_t)id) == NULL) {
l2tpd_log(_this, LOG_ERR,
"slist_add() failed on %s(): %m", __func__);
return 1;
}
}
_this->purge_ipsec_sa = 1;
_this->state = L2TPD_STATE_INIT;
return 0;
}
int
l2tpd_add_listener(l2tpd *_this, int idx, struct l2tp_conf *conf,
struct sockaddr *addr)
{
l2tpd_listener *plistener, *plsnr;
plistener = NULL;
if (idx == 0 && slist_length(&_this->listener) > 0) {
slist_itr_first(&_this->listener);
while (slist_itr_has_next(&_this->listener)) {
slist_itr_next(&_this->listener);
plsnr = slist_itr_remove(&_this->listener);
L2TPD_ASSERT(plsnr != NULL);
L2TPD_ASSERT(plsnr->sock == -1);
free(plsnr);
}
}
L2TPD_ASSERT(slist_length(&_this->listener) == idx);
if (slist_length(&_this->listener) != idx) {
l2tpd_log(_this, LOG_ERR,
"Invalid argument error on %s(): idx must be %d but %d",
__func__, slist_length(&_this->listener), idx);
goto fail;
}
if ((plistener = calloc(1, sizeof(l2tpd_listener))) == NULL) {
l2tpd_log(_this, LOG_ERR, "calloc() failed in %s: %m",
__func__);
goto fail;
}
L2TPD_ASSERT(sizeof(plistener->bind) >= addr->sa_len);
memcpy(&plistener->bind, addr, addr->sa_len);
if (plistener->bind.sin6.sin6_port == 0)
plistener->bind.sin6.sin6_port = htons(L2TPD_DEFAULT_UDP_PORT);
plistener->sock = -1;
plistener->self = _this;
plistener->index = idx;
plistener->conf = conf;
strlcpy(plistener->tun_name, conf->name, sizeof(plistener->tun_name));
if (slist_add(&_this->listener, plistener) == NULL) {
l2tpd_log(_this, LOG_ERR, "slist_add() failed in %s: %m",
__func__);
goto fail;
}
return 0;
fail:
free(plistener);
return 1;
}
void
l2tpd_uninit(l2tpd *_this)
{
l2tpd_listener *plsnr;
L2TPD_ASSERT(_this != NULL);
slist_fini(&_this->free_session_id_list);
if (_this->ctrl_map != NULL) {
hash_free(_this->ctrl_map);
_this->ctrl_map = NULL;
}
slist_itr_first(&_this->listener);
while (slist_itr_has_next(&_this->listener)) {
plsnr = slist_itr_next(&_this->listener);
L2TPD_ASSERT(plsnr != NULL);
L2TPD_ASSERT(plsnr->sock == -1);
free(plsnr);
}
slist_fini(&_this->listener);
event_del(&_this->ev_timeout);
_this->state = L2TPD_STATE_STOPPED;
}
int
l2tpd_assign_call(l2tpd *_this, l2tp_call *call)
{
int shuffle_cnt;
u_int session_id;
shuffle_cnt = 0;
do {
session_id = (uintptr_t)slist_remove_first(
&_this->free_session_id_list);
if (session_id != L2TP_SESSION_ID_SHUFFLE_MARK)
break;
L2TPD_ASSERT(shuffle_cnt == 0);
if (shuffle_cnt++ > 0) {
l2tpd_log(_this, LOG_ERR,
"unexpected error in %s(): free_session_id_list "
"full", __func__);
slist_add(&_this->free_session_id_list,
(void *)L2TP_SESSION_ID_SHUFFLE_MARK);
return 1;
}
slist_shuffle(&_this->free_session_id_list);
slist_add(&_this->free_session_id_list,
(void *)L2TP_SESSION_ID_SHUFFLE_MARK);
} while (1);
call->id = session_id;
return 0;
}
void
l2tpd_release_call(l2tpd *_this, l2tp_call *call)
{
slist_add(&_this->free_session_id_list, (void *)(uintptr_t)call->id);
}
static int
l2tpd_listener_start(l2tpd_listener *_this)
{
l2tpd *_l2tpd;
int af, lvl, opt, sock, ival;
char hbuf[NI_MAXHOST + NI_MAXSERV + 16];
_l2tpd = _this->self;
sock = -1;
af = _this->bind.sin6.sin6_family;
lvl = (af == AF_INET)? IPPROTO_IP : IPPROTO_IPV6;
if (_this->tun_name[0] == '\0')
strlcpy(_this->tun_name, L2TPD_DEFAULT_LAYER2_LABEL,
sizeof(_this->tun_name));
if ((sock = socket(_this->bind.sin6.sin6_family,
SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP)) < 0) {
l2tpd_log(_l2tpd, LOG_ERR,
"socket() failed in %s(): %m", __func__);
goto fail;
}
#if defined(IP_STRICT_RCVIF) && defined(USE_STRICT_RCVIF)
ival = 1;
if (setsockopt(sock, IPPROTO_IP, IP_STRICT_RCVIF, &ival, sizeof(ival))
!= 0)
l2tpd_log(_l2tpd, LOG_WARNING,
"%s(): setsockopt(IP_STRICT_RCVIF) failed: %m", __func__);
#endif
ival = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &ival, sizeof(ival))
!= 0) {
l2tpd_log(_l2tpd, LOG_ERR,
"setsockopt(,,SO_REUSEPORT) failed in %s(): %m", __func__);
goto fail;
}
if (bind(sock, (struct sockaddr *)&_this->bind,
_this->bind.sin6.sin6_len) != 0) {
l2tpd_log(_l2tpd, LOG_ERR, "Binding %s/udp: %m",
addrport_tostring((struct sockaddr *)&_this->bind,
_this->bind.sin6.sin6_len, hbuf, sizeof(hbuf)));
goto fail;
}
#ifdef USE_LIBSOCKUTIL
if (setsockoptfromto(sock) != 0) {
l2tpd_log(_l2tpd, LOG_ERR,
"setsockoptfromto() failed in %s(): %m", __func__);
goto fail;
}
#else
opt = (af == AF_INET)? IP_RECVDSTADDR : IPV6_RECVPKTINFO;
ival = 1;
if (setsockopt(sock, lvl, opt, &ival, sizeof(ival)) != 0) {
l2tpd_log(_l2tpd, LOG_ERR,
"setsockopt(,,IP{,V6}_RECVDSTADDR) failed in %s(): %m",
__func__);
goto fail;
}
#endif
#ifdef USE_SA_COOKIE
if (af == AF_INET) {
ival = 1;
if (setsockopt(sock, IPPROTO_IP, IP_IPSECFLOWINFO, &ival,
sizeof(ival)) != 0) {
l2tpd_log(_l2tpd, LOG_ERR,
"setsockopt(,,IP_IPSECFLOWINFO) failed in %s(): %m",
__func__);
goto fail;
}
}
#endif
#ifdef IP_PIPEX
opt = (af == AF_INET)? IP_PIPEX : IPV6_PIPEX;
ival = 1;
if (setsockopt(sock, lvl, opt, &ival, sizeof(ival)) != 0)
l2tpd_log(_l2tpd, LOG_WARNING,
"%s(): setsockopt(IP{,V6}_PIPEX) failed: %m", __func__);
#endif
if (_this->conf->require_ipsec) {
#ifdef IP_IPSEC_POLICY
caddr_t ipsec_policy_in, ipsec_policy_out;
opt = (af == AF_INET)? IP_IPSEC_POLICY : IPV6_IPSEC_POLICY;
if ((ipsec_policy_in = ipsec_set_policy(L2TPD_IPSEC_POLICY_IN,
strlen(L2TPD_IPSEC_POLICY_IN))) == NULL) {
l2tpd_log(_l2tpd, LOG_ERR,
"ipsec_set_policy(L2TPD_IPSEC_POLICY_IN) failed "
"at %s(): %s: %m", __func__, ipsec_strerror());
} else if (setsockopt(sock, lvl, opt, ipsec_policy_in,
ipsec_get_policylen(ipsec_policy_in)) < 0) {
l2tpd_log(_l2tpd, LOG_WARNING,
"setsockopt(,,IP_IPSEC_POLICY(in)) failed "
"in %s(): %m", __func__);
}
if ((ipsec_policy_out = ipsec_set_policy(L2TPD_IPSEC_POLICY_OUT,
strlen(L2TPD_IPSEC_POLICY_OUT))) == NULL) {
l2tpd_log(_l2tpd, LOG_ERR,
"ipsec_set_policy(L2TPD_IPSEC_POLICY_OUT) failed "
"at %s(): %s: %m", __func__, ipsec_strerror());
}
if (ipsec_policy_out != NULL &&
setsockopt(sock, lvl, opt, ipsec_policy_out,
ipsec_get_policylen(ipsec_policy_out)) < 0) {
l2tpd_log(_l2tpd, LOG_WARNING,
"setsockopt(,,IP_IPSEC_POLICY(out)) failed "
"in %s(): %m", __func__);
}
free(ipsec_policy_in);
free(ipsec_policy_out);
#elif defined(IP_ESP_TRANS_LEVEL)
opt = (af == AF_INET)
? IP_ESP_TRANS_LEVEL : IPV6_ESP_TRANS_LEVEL;
ival = IPSEC_LEVEL_REQUIRE;
if (setsockopt(sock, lvl, opt, &ival, sizeof(ival)) != 0) {
l2tpd_log(_l2tpd, LOG_WARNING,
"setsockopt(,,IP{,V6}_ESP_TRANS_LEVEL(out)) failed "
"in %s(): %m", __func__);
}
#else
#error IP_IPSEC_POLICY or IP_ESP_TRANS_LEVEL must be usable.
#endif
}
_this->sock = sock;
event_set(&_this->ev_sock, _this->sock, EV_READ | EV_PERSIST,
l2tpd_io_event, _this);
event_add(&_this->ev_sock, NULL);
l2tpd_log(_l2tpd, LOG_INFO, "Listening %s/udp (L2TP LNS) [%s]",
addrport_tostring((struct sockaddr *)&_this->bind,
_this->bind.sin6.sin6_len, hbuf, sizeof(hbuf)), _this->tun_name);
return 0;
fail:
if (sock >= 0)
close(sock);
return 1;
}
int
l2tpd_start(l2tpd *_this)
{
int rval;
l2tpd_listener *plsnr;
rval = 0;
L2TPD_ASSERT(_this->state == L2TPD_STATE_INIT);
if (_this->state != L2TPD_STATE_INIT) {
l2tpd_log(_this, LOG_ERR, "Failed to start l2tpd: illegal "
"state.");
return -1;
}
slist_itr_first(&_this->listener);
while (slist_itr_has_next(&_this->listener)) {
plsnr = slist_itr_next(&_this->listener);
rval |= l2tpd_listener_start(plsnr);
}
if (rval == 0)
_this->state = L2TPD_STATE_RUNNING;
return rval;
}
static void
l2tpd_listener_stop(l2tpd_listener *_this)
{
char hbuf[NI_MAXHOST + NI_MAXSERV + 16];
if (_this->sock >= 0) {
event_del(&_this->ev_sock);
close(_this->sock);
l2tpd_log(_this->self, LOG_INFO,
"Shutdown %s/udp (L2TP LNS)",
addrport_tostring((struct sockaddr *)&_this->bind,
_this->bind.sin6.sin6_len, hbuf, sizeof(hbuf)));
_this->sock = -1;
}
}
void
l2tpd_stop_immediatly(l2tpd *_this)
{
l2tpd_listener *plsnr;
slist_itr_first(&_this->listener);
while (slist_itr_has_next(&_this->listener)) {
plsnr = slist_itr_next(&_this->listener);
l2tpd_listener_stop(plsnr);
}
event_del(&_this->ev_timeout);
_this->state = L2TPD_STATE_STOPPED;
}
void
l2tpd_ctrl_finished_notify(l2tpd *_this)
{
if (_this->state != L2TPD_STATE_SHUTTING_DOWN)
return;
if (hash_first(_this->ctrl_map) != NULL)
return;
l2tpd_stop_immediatly(_this);
}
static void
l2tpd_stop_timeout(int fd, short evtype, void *ctx)
{
hash_link *hl;
l2tp_ctrl *ctrl;
l2tpd *_this;
_this = ctx;
l2tpd_log(_this, LOG_INFO, "Shutdown timeout");
for (hl = hash_first(_this->ctrl_map); hl != NULL;
hl = hash_next(_this->ctrl_map)) {
ctrl = hl->item;
l2tp_ctrl_stop(ctrl, 0);
}
l2tpd_stop_immediatly(_this);
}
void
l2tpd_stop(l2tpd *_this)
{
int nctrls = 0;
hash_link *hl;
l2tp_ctrl *ctrl;
nctrls = 0;
event_del(&_this->ev_timeout);
if (l2tpd_is_stopped(_this))
return;
if (l2tpd_is_shutting_down(_this)) {
l2tpd_stop_immediatly(_this);
return;
}
for (hl = hash_first(_this->ctrl_map); hl != NULL;
hl = hash_next(_this->ctrl_map)) {
ctrl = hl->item;
l2tp_ctrl_stop(ctrl, L2TP_STOP_CCN_RCODE_SHUTTING_DOWN);
nctrls++;
}
_this->state = L2TPD_STATE_SHUTTING_DOWN;
if (nctrls > 0) {
struct timeval tv0;
tv0.tv_usec = 0;
tv0.tv_sec = L2TPD_SHUTDOWN_TIMEOUT;
evtimer_set(&_this->ev_timeout, l2tpd_stop_timeout, _this);
evtimer_add(&_this->ev_timeout, &tv0);
return;
}
l2tpd_stop_immediatly(_this);
}
int
l2tpd_reload(l2tpd *_this, struct l2tp_confs *l2tp_conf)
{
int i;
struct l2tp_conf *conf;
l2tpd_listener *listener;
struct l2tp_listen_addr *addr;
if (slist_length(&_this->listener) > 0) {
slist_itr_first(&_this->listener);
while (slist_itr_has_next(&_this->listener)) {
listener = slist_itr_next(&_this->listener);
TAILQ_FOREACH(conf, l2tp_conf, entry) {
if (strcmp(listener->tun_name,
conf->name) == 0) {
listener->conf = conf;
break;
}
}
}
return 0;
}
i = 0;
TAILQ_FOREACH(conf, l2tp_conf, entry) {
TAILQ_FOREACH(addr, &conf->listen, entry)
l2tpd_add_listener(_this, i++, conf,
(struct sockaddr *)&addr->addr);
}
if (l2tpd_start(_this) != 0)
return -1;
return 0;
}
void
l2tpd_log_access_deny(l2tpd *_this, const char *reason, struct sockaddr *peer)
{
char buf[BUFSIZ];
l2tpd_log(_this, LOG_ALERT, "Received packet from %s/udp: "
"%s", addrport_tostring(peer, peer->sa_len, buf, sizeof(buf)),
reason);
}
static void
l2tpd_io_event(int fd, short evtype, void *ctx)
{
int sz;
l2tpd *_l2tpd;
l2tpd_listener *_this;
socklen_t peerlen, socklen;
struct sockaddr_storage peer, sock;
u_char buf[8192];
void *nat_t;
_this = ctx;
_l2tpd = _this->self;
if ((evtype & EV_READ) != 0) {
peerlen = sizeof(peer);
socklen = sizeof(sock);
while (!l2tpd_is_stopped(_l2tpd)) {
#if defined(USE_LIBSOCKUTIL) || defined(USE_SA_COOKIE)
int sa_cookie_len;
struct in_ipsec_sa_cookie sa_cookie;
sa_cookie_len = sizeof(sa_cookie);
if ((sz = recvfromto_nat_t(_this->sock, buf,
sizeof(buf), 0,
(struct sockaddr *)&peer, &peerlen,
(struct sockaddr *)&sock, &socklen,
&sa_cookie, &sa_cookie_len)) == -1) {
#else
if ((sz = recvfromto(_this->sock, buf,
sizeof(buf), 0,
(struct sockaddr *)&peer, &peerlen,
(struct sockaddr *)&sock, &socklen)) == -1) {
#endif
if (errno == EAGAIN || errno == EINTR)
break;
l2tpd_log(_l2tpd, LOG_ERR,
"recvfrom() failed in %s(): %m",
__func__);
l2tpd_stop(_l2tpd);
return;
}
switch (peer.ss_family) {
case AF_INET:
#if defined(USE_LIBSOCKUTIL) || defined(USE_SA_COOKIE)
if (sa_cookie_len > 0)
nat_t = &sa_cookie;
else
nat_t = NULL;
#else
nat_t = NULL;
#endif
l2tp_ctrl_input(_l2tpd, _this->index,
(struct sockaddr *)&peer,
(struct sockaddr *)&sock, nat_t,
buf, sz);
break;
case AF_INET6:
l2tp_ctrl_input(_l2tpd, _this->index,
(struct sockaddr *)&peer,
(struct sockaddr *)&sock, NULL,
buf, sz);
break;
default:
l2tpd_log(_l2tpd, LOG_ERR,
"received from unknown address family = %d",
peer.ss_family);
break;
}
}
}
}
l2tp_ctrl *
l2tpd_get_ctrl(l2tpd *_this, unsigned tunid)
{
hash_link *hl;
hl = hash_lookup(_this->ctrl_map, (void *)(uintptr_t)tunid);
if (hl == NULL)
return NULL;
return hl->item;
}
void
l2tpd_add_ctrl(l2tpd *_this, l2tp_ctrl *ctrl)
{
hash_insert(_this->ctrl_map, (void *)(uintptr_t)ctrl->tunnel_id, ctrl);
}
void
l2tpd_remove_ctrl(l2tpd *_this, unsigned tunid)
{
hash_delete(_this->ctrl_map, (void *)(uintptr_t)tunid, 0);
}
void
l2tpd_log(l2tpd *_this, int prio, const char *fmt, ...)
{
char logbuf[BUFSIZ];
va_list ap;
va_start(ap, fmt);
#ifdef L2TPD_MULTIPLE
snprintf(logbuf, sizeof(logbuf), "l2tpd id=%u %s", _this->id, fmt);
#else
snprintf(logbuf, sizeof(logbuf), "l2tpd %s", fmt);
#endif
vlog_printf(prio, logbuf, ap);
va_end(ap);
}