summaryrefslogtreecommitdiff
path: root/accel-pppd/ctrl/l2tp/l2tp.c
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd/ctrl/l2tp/l2tp.c')
-rw-r--r--accel-pppd/ctrl/l2tp/l2tp.c1141
1 files changed, 1141 insertions, 0 deletions
diff --git a/accel-pppd/ctrl/l2tp/l2tp.c b/accel-pppd/ctrl/l2tp/l2tp.c
new file mode 100644
index 00000000..ca56051b
--- /dev/null
+++ b/accel-pppd/ctrl/l2tp/l2tp.c
@@ -0,0 +1,1141 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <pthread.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <linux/socket.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_pppox.h>
+
+#include "triton.h"
+#include "mempool.h"
+#include "log.h"
+#include "ppp.h"
+#include "events.h"
+#include "utils.h"
+#include "iprange.h"
+#include "cli.h"
+
+#include "memdebug.h"
+
+#include "l2tp.h"
+#include "attr_defs.h"
+
+#ifndef SOL_PPPOL2TP
+#define SOL_PPPOL2TP 273
+#endif
+
+#define STATE_WAIT_SCCCN 1
+#define STATE_WAIT_ICRQ 2
+#define STATE_WAIT_ICCN 3
+#define STATE_WAIT_OCRP 4
+#define STATE_WAIT_OCCN 5
+#define STATE_ESTB 6
+#define STATE_PPP 7
+#define STATE_FIN 8
+#define STATE_CLOSE 0
+
+int conf_verbose = 0;
+int conf_timeout = 60;
+int conf_rtimeout = 5;
+int conf_retransmit = 5;
+int conf_hello_interval = 60;
+char *conf_host_name = NULL;
+
+static unsigned int stat_active;
+static unsigned int stat_starting;
+
+struct l2tp_serv_t
+{
+ struct triton_context_t ctx;
+ struct triton_md_handler_t hnd;
+ struct sockaddr_in addr;
+};
+
+struct l2tp_conn_t
+{
+ struct triton_context_t ctx;
+ struct triton_md_handler_t hnd;
+ struct triton_timer_t timeout_timer;
+ struct triton_timer_t rtimeout_timer;
+ struct triton_timer_t hello_timer;
+
+ int tunnel_fd;
+
+ struct sockaddr_in addr;
+ uint16_t tid;
+ uint16_t sid;
+ uint16_t peer_tid;
+ uint16_t peer_sid;
+ uint32_t framing_cap;
+
+ int retransmit;
+ uint16_t Ns, Nr;
+ struct list_head send_queue;
+
+ int state;
+ int state1;
+ int state2;
+
+ struct ppp_ctrl_t ctrl;
+ struct ppp_t ppp;
+};
+
+static pthread_mutex_t l2tp_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct l2tp_conn_t **l2tp_conn;
+static uint16_t l2tp_tid;
+
+static mempool_t l2tp_conn_pool;
+
+static void l2tp_timeout(struct triton_timer_t *t);
+static void l2tp_rtimeout(struct triton_timer_t *t);
+static void l2tp_send_HELLO(struct triton_timer_t *t);
+static void l2tp_send_SCCRP(struct l2tp_conn_t *conn);
+static int l2tp_send(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack, int log_debug);
+static int l2tp_conn_read(struct triton_md_handler_t *);
+
+static void l2tp_disconnect(struct l2tp_conn_t *conn)
+{
+ struct l2tp_packet_t *pack;
+
+ triton_md_unregister_handler(&conn->hnd);
+ close(conn->hnd.fd);
+
+ if (conn->timeout_timer.tpd)
+ triton_timer_del(&conn->timeout_timer);
+
+ if (conn->rtimeout_timer.tpd)
+ triton_timer_del(&conn->rtimeout_timer);
+
+ if (conn->hello_timer.tpd)
+ triton_timer_del(&conn->hello_timer);
+
+ if (conn->state == STATE_PPP) {
+ __sync_sub_and_fetch(&stat_active, 1);
+ conn->state = STATE_FIN;
+ ppp_terminate(&conn->ppp, TERM_USER_REQUEST, 1);
+ } else if (conn->state != STATE_FIN)
+ __sync_sub_and_fetch(&stat_starting, 1);
+
+ pthread_mutex_lock(&l2tp_lock);
+ l2tp_conn[conn->tid] = NULL;
+ pthread_mutex_unlock(&l2tp_lock);
+
+ if (conn->ppp.fd != -1)
+ close(conn->ppp.fd);
+
+ if (conn->tunnel_fd != -1)
+ close(conn->tunnel_fd);
+
+ triton_event_fire(EV_CTRL_FINISHED, &conn->ppp);
+
+ log_ppp_info1("disconnected\n");
+
+ triton_context_unregister(&conn->ctx);
+
+ while (!list_empty(&conn->send_queue)) {
+ pack = list_entry(conn->send_queue.next, typeof(*pack), entry);
+ list_del(&pack->entry);
+ l2tp_packet_free(pack);
+ }
+
+ if (conn->ppp.chan_name)
+ _free(conn->ppp.chan_name);
+
+ _free(conn->ctrl.calling_station_id);
+ _free(conn->ctrl.called_station_id);
+
+ mempool_free(conn);
+}
+
+static int l2tp_terminate(struct l2tp_conn_t *conn, int res, int err)
+{
+ struct l2tp_packet_t *pack;
+ struct l2tp_avp_result_code rc = {res, err};
+
+ log_ppp_debug("l2tp: terminate (%i, %i)\n", res, err);
+
+ pack = l2tp_packet_alloc(2, Message_Type_Stop_Ctrl_Conn_Notify, &conn->addr);
+ if (!pack)
+ return -1;
+
+ if (l2tp_packet_add_int16(pack, Assigned_Tunnel_ID, conn->tid, 1))
+ goto out_err;
+ if (l2tp_packet_add_octets(pack, Result_Code, (uint8_t *)&rc, sizeof(rc), 0))
+ goto out_err;
+
+ l2tp_send(conn, pack, 0);
+
+ conn->state = STATE_FIN;
+
+ return 0;
+
+out_err:
+ l2tp_packet_free(pack);
+ return -1;
+}
+
+static void l2tp_ppp_started(struct ppp_t *ppp)
+{
+ struct l2tp_conn_t *conn = container_of(ppp, typeof(*conn), ppp);
+
+ log_ppp_debug("l2tp: ppp started\n");
+
+ if (conf_hello_interval)
+ triton_timer_add(&conn->ctx, &conn->hello_timer, 0);
+}
+
+static void l2tp_ppp_finished(struct ppp_t *ppp)
+{
+ struct l2tp_conn_t *conn = container_of(ppp, typeof(*conn), ppp);
+
+ log_ppp_debug("l2tp: ppp finished\n");
+
+ if (conn->state != STATE_FIN) {
+ __sync_sub_and_fetch(&stat_active, 1);
+ if (l2tp_terminate(conn, 0, 0))
+ triton_context_call(&conn->ctx, (triton_event_func)l2tp_disconnect, conn);
+ }
+}
+
+static void l2tp_conn_close(struct triton_context_t *ctx)
+{
+ struct l2tp_conn_t *conn = container_of(ctx, typeof(*conn), ctx);
+
+ if (conn->state == STATE_PPP) {
+ __sync_sub_and_fetch(&stat_active, 1);
+ conn->state = STATE_FIN;
+ ppp_terminate(&conn->ppp, TERM_ADMIN_RESET, 1);
+ }
+
+ if (l2tp_terminate(conn, 0, 0))
+ l2tp_disconnect(conn);
+}
+
+static int l2tp_tunnel_alloc(struct l2tp_serv_t *serv, struct l2tp_packet_t *pack, struct in_pktinfo *pkt_info, struct l2tp_attr_t *assigned_tid, struct l2tp_attr_t *framing_cap)
+{
+ struct l2tp_conn_t *conn;
+ struct sockaddr_in addr;
+ uint16_t tid;
+ //char *opt;
+ int flag = 1;
+
+ conn = mempool_alloc(l2tp_conn_pool);
+ if (!conn) {
+ log_emerg("l2tp: out of memory\n");
+ return -1;
+ }
+
+ memset(conn, 0, sizeof(*conn));
+ INIT_LIST_HEAD(&conn->send_queue);
+
+ conn->hnd.fd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (conn->hnd.fd < 0) {
+ log_error("l2tp: socket: %s\n", strerror(errno));
+ mempool_free(conn);
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr = pkt_info->ipi_addr;
+ addr.sin_port = htons(L2TP_PORT);
+
+ setsockopt(conn->hnd.fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
+ if (bind(conn->hnd.fd, &addr, sizeof(addr))) {
+ log_error("l2tp: bind: %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ if (connect(conn->hnd.fd, (struct sockaddr *)&pack->addr, sizeof(addr))) {
+ log_error("l2tp: connect: %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ if (fcntl(conn->hnd.fd, F_SETFL, O_NONBLOCK)) {
+ log_emerg("l2tp: failed to set nonblocking mode: %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ pthread_mutex_lock(&l2tp_lock);
+ for (tid = l2tp_tid + 1; tid != l2tp_tid; tid++) {
+ if (tid == L2TP_MAX_TID)
+ tid = 1;
+ if (!l2tp_conn[tid]) {
+ l2tp_conn[tid] = conn;
+ conn->tid = tid;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&l2tp_lock);
+
+ if (!conn->tid) {
+ if (conf_verbose)
+ log_warn("l2tp: no free tid available\n");
+ mempool_free(conn);
+ return -1;
+ }
+
+ conn->sid = 1;
+
+ memcpy(&conn->addr, &pack->addr, sizeof(pack->addr));
+ conn->peer_tid = assigned_tid->val.uint16;
+ conn->framing_cap = framing_cap->val.uint32;
+
+ conn->ctx.before_switch = log_switch;
+ conn->ctx.close = l2tp_conn_close;
+ conn->hnd.read = l2tp_conn_read;
+ conn->timeout_timer.expire = l2tp_timeout;
+ conn->timeout_timer.period = conf_timeout * 1000;
+ conn->rtimeout_timer.expire = l2tp_rtimeout;
+ conn->rtimeout_timer.period = conf_rtimeout * 1000;
+ conn->hello_timer.expire = l2tp_send_HELLO;
+ conn->hello_timer.period = conf_hello_interval * 1000;
+ conn->ctrl.ctx = &conn->ctx;
+ conn->ctrl.name = "l2tp";
+ conn->ctrl.started = l2tp_ppp_started;
+ conn->ctrl.finished = l2tp_ppp_finished;
+ conn->ctrl.max_mtu = 1420;
+
+ conn->ctrl.calling_station_id = _malloc(17);
+ conn->ctrl.called_station_id = _malloc(17);
+ u_inet_ntoa(conn->addr.sin_addr.s_addr, conn->ctrl.calling_station_id);
+ u_inet_ntoa(addr.sin_addr.s_addr, conn->ctrl.called_station_id);
+
+ ppp_init(&conn->ppp);
+ conn->ppp.ctrl = &conn->ctrl;
+ conn->ppp.fd = -1;
+ conn->tunnel_fd = -1;
+
+ triton_context_register(&conn->ctx, &conn->ppp);
+ triton_md_register_handler(&conn->ctx, &conn->hnd);
+ triton_md_enable_handler(&conn->hnd, MD_MODE_READ);
+ triton_context_wakeup(&conn->ctx);
+
+ if (conf_verbose) {
+ log_switch(&conn->ctx, &conn->ppp);
+ log_ppp_info2("recv ");
+ l2tp_packet_print(pack, log_ppp_info2);
+ }
+
+ triton_context_call(&conn->ctx, (triton_event_func)l2tp_send_SCCRP, conn);
+
+ __sync_add_and_fetch(&stat_starting, 1);
+
+ return 0;
+
+out_err:
+ close(conn->hnd.fd);
+ mempool_free(conn);
+ return -1;
+}
+
+static int l2tp_connect(struct l2tp_conn_t *conn)
+{
+ struct sockaddr_pppol2tp pppox_addr;
+ int arg = 1;
+
+ memset(&pppox_addr, 0, sizeof(pppox_addr));
+ pppox_addr.sa_family = AF_PPPOX;
+ pppox_addr.sa_protocol = PX_PROTO_OL2TP;
+ pppox_addr.pppol2tp.fd = conn->hnd.fd;
+ memcpy(&pppox_addr.pppol2tp.addr, &conn->addr, sizeof(conn->addr));
+ pppox_addr.pppol2tp.s_tunnel = conn->tid;
+ pppox_addr.pppol2tp.d_tunnel = conn->peer_tid;
+
+ conn->tunnel_fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP);
+ if (!conn->ppp.fd) {
+ log_ppp_error("l2tp: socket(AF_PPPOX): %s\n", strerror(errno));
+ return -1;
+ }
+
+ conn->ppp.fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP);
+ if (!conn->ppp.fd) {
+ close(conn->tunnel_fd);
+ conn->tunnel_fd = -1;
+ log_ppp_error("l2tp: socket(AF_PPPOX): %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (connect(conn->tunnel_fd, (struct sockaddr *)&pppox_addr, sizeof(pppox_addr)) < 0) {
+ log_ppp_error("l2tp: connect(tunnel): %s\n", strerror(errno));
+ return -1;
+ }
+
+ pppox_addr.pppol2tp.s_session = conn->sid;
+ pppox_addr.pppol2tp.d_session = conn->peer_sid;
+
+ if (connect(conn->ppp.fd, (struct sockaddr *)&pppox_addr, sizeof(pppox_addr)) < 0) {
+ log_ppp_error("l2tp: connect(session): %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (setsockopt(conn->ppp.fd, SOL_PPPOL2TP, PPPOL2TP_SO_LNSMODE, &arg, sizeof(arg))) {
+ log_ppp_error("l2tp: setsockopt: %s\n", strerror(errno));
+ return -1;
+ }
+
+ conn->ppp.chan_name = _strdup(inet_ntoa(conn->addr.sin_addr));
+
+ triton_event_fire(EV_CTRL_STARTED, &conn->ppp);
+
+ if (establish_ppp(&conn->ppp))
+ return -1;
+
+ __sync_sub_and_fetch(&stat_starting, 1);
+ __sync_add_and_fetch(&stat_active, 1);
+
+ conn->state = STATE_PPP;
+
+ return 0;
+}
+
+static void l2tp_rtimeout(struct triton_timer_t *t)
+{
+ struct l2tp_conn_t *conn = container_of(t, typeof(*conn), rtimeout_timer);
+ struct l2tp_packet_t *pack;
+
+ if (!list_empty(&conn->send_queue)) {
+ log_ppp_debug("l2tp: retransmit (%i)\n", conn->retransmit);
+ if (++conn->retransmit <= conf_retransmit) {
+ pack = list_entry(conn->send_queue.next, typeof(*pack), entry);
+ pack->hdr.Nr = htons(conn->Nr + 1);
+ if (conf_verbose) {
+ log_ppp_debug("send ");
+ l2tp_packet_print(pack, log_ppp_debug);
+ }
+ if (l2tp_packet_send(conn->hnd.fd, pack) == 0)
+ return;
+ } else
+ l2tp_disconnect(conn);
+ }
+}
+
+static void l2tp_timeout(struct triton_timer_t *t)
+{
+ struct l2tp_conn_t *conn = container_of(t, typeof(*conn), timeout_timer);
+ log_ppp_debug("l2tp: timeout\n");
+ l2tp_disconnect(conn);
+}
+
+static int l2tp_send(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack, int log_debug)
+{
+ conn->retransmit = 0;
+
+ pack->hdr.tid = htons(conn->peer_tid);
+ //pack->hdr.sid = htons(conn->peer_sid);
+ pack->hdr.Nr = htons(conn->Nr + 1);
+ pack->hdr.Ns = htons(conn->Ns);
+
+ if (!list_empty(&pack->attrs))
+ conn->Ns++;
+
+ if (conf_verbose) {
+ if (log_debug) {
+ log_ppp_debug("send ");
+ l2tp_packet_print(pack, log_ppp_debug);
+ } else {
+ log_ppp_info2("send ");
+ l2tp_packet_print(pack, log_ppp_info2);
+ }
+ }
+
+ if (l2tp_packet_send(conn->hnd.fd, pack))
+ goto out_err;
+
+ if (!list_empty(&pack->attrs)) {
+ list_add_tail(&pack->entry, &conn->send_queue);
+ if (!conn->rtimeout_timer.tpd)
+ triton_timer_add(&conn->ctx, &conn->rtimeout_timer, 0);
+ } else
+ l2tp_packet_free(pack);
+
+ return 0;
+
+out_err:
+ l2tp_packet_free(pack);
+ return -1;
+}
+
+static int l2tp_send_ZLB(struct l2tp_conn_t *conn)
+{
+ struct l2tp_packet_t *pack;
+
+ pack = l2tp_packet_alloc(2, 0, &conn->addr);
+ if (!pack)
+ return -1;
+
+ if (l2tp_send(conn, pack, 1))
+ return -1;
+
+ return 0;
+}
+
+static void l2tp_send_HELLO(struct triton_timer_t *t)
+{
+ struct l2tp_conn_t *conn = container_of(t, typeof(*conn), hello_timer);
+ struct l2tp_packet_t *pack;
+
+ pack = l2tp_packet_alloc(2, Message_Type_Hello, &conn->addr);
+ if (!pack) {
+ l2tp_disconnect(conn);
+ return;
+ }
+
+ if (l2tp_send(conn, pack, 1))
+ l2tp_disconnect(conn);
+}
+
+static void l2tp_send_SCCRP(struct l2tp_conn_t *conn)
+{
+ struct l2tp_packet_t *pack;
+
+ pack = l2tp_packet_alloc(2, Message_Type_Start_Ctrl_Conn_Reply, &conn->addr);
+ if (!pack)
+ goto out;
+
+ if (l2tp_packet_add_int16(pack, Protocol_Version, L2TP_V2_PROTOCOL_VERSION, 1))
+ goto out_err;
+ if (conf_host_name && l2tp_packet_add_string(pack, Host_Name, conf_host_name, 1))
+ goto out_err;
+ if (l2tp_packet_add_int32(pack, Framing_Capabilities, conn->framing_cap, 1))
+ goto out_err;
+ if (l2tp_packet_add_int16(pack, Assigned_Tunnel_ID, conn->tid, 1))
+ goto out_err;
+
+ if (l2tp_send(conn, pack, 0))
+ goto out;
+
+ if (!conn->timeout_timer.tpd)
+ triton_timer_add(&conn->ctx, &conn->timeout_timer, 0);
+ else
+ triton_timer_mod(&conn->timeout_timer, 0);
+
+ conn->state = STATE_WAIT_SCCCN;
+
+ return;
+
+out_err:
+ l2tp_packet_free(pack);
+out:
+ l2tp_disconnect(conn);
+}
+
+static int l2tp_send_ICRP(struct l2tp_conn_t *conn)
+{
+ struct l2tp_packet_t *pack;
+
+ pack = l2tp_packet_alloc(2, Message_Type_Incoming_Call_Reply, &conn->addr);
+ if (!pack)
+ return -1;
+
+ pack->hdr.sid = htons(conn->peer_sid);
+
+ if (l2tp_packet_add_int16(pack, Assigned_Session_ID, conn->sid, 1))
+ goto out_err;
+
+ l2tp_send(conn, pack, 0);
+
+ if (!conn->timeout_timer.tpd)
+ triton_timer_add(&conn->ctx, &conn->timeout_timer, 0);
+ else
+ triton_timer_mod(&conn->timeout_timer, 0);
+
+ conn->state1 = STATE_WAIT_ICCN;
+
+ return 0;
+
+out_err:
+ l2tp_packet_free(pack);
+ return -1;
+}
+
+static int l2tp_send_OCRQ(struct l2tp_conn_t *conn)
+{
+ struct l2tp_packet_t *pack;
+
+ pack = l2tp_packet_alloc(2, Message_Type_Outgoing_Call_Request, &conn->addr);
+ if (!pack)
+ return -1;
+
+ pack->hdr.sid = htons(conn->peer_sid);
+
+ if (l2tp_packet_add_int16(pack, Assigned_Session_ID, conn->sid, 1))
+ goto out_err;
+ if (l2tp_packet_add_int32(pack, Call_Serial_Number, 0, 1))
+ goto out_err;
+ if (l2tp_packet_add_int32(pack, Minimum_BPS, 100, 1))
+ goto out_err;
+ if (l2tp_packet_add_int32(pack, Maximum_BPS, 100000, 1))
+ goto out_err;
+ if (l2tp_packet_add_int32(pack, Bearer_Type, 3, 1))
+ goto out_err;
+ if (l2tp_packet_add_int32(pack, Framing_Type, 3, 1))
+ goto out_err;
+ if (l2tp_packet_add_string(pack, Called_Number, "", 1))
+ goto out_err;
+
+ if (l2tp_send(conn, pack, 0))
+ return -1;
+
+ if (!conn->timeout_timer.tpd)
+ triton_timer_add(&conn->ctx, &conn->timeout_timer, 0);
+ else
+ triton_timer_mod(&conn->timeout_timer, 0);
+
+ conn->state2 = STATE_WAIT_OCRP;
+
+ return 0;
+
+out_err:
+ l2tp_packet_free(pack);
+ return -1;
+}
+
+
+static int l2tp_recv_SCCRQ(struct l2tp_serv_t *serv, struct l2tp_packet_t *pack, struct in_pktinfo *pkt_info)
+{
+ struct l2tp_attr_t *attr;
+ struct l2tp_attr_t *protocol_version = NULL;
+ struct l2tp_attr_t *assigned_tid = NULL;
+ struct l2tp_attr_t *assigned_cid = NULL;
+ struct l2tp_attr_t *framing_cap = NULL;
+ struct l2tp_attr_t *router_id = NULL;
+
+ if (ppp_shutdown)
+ return 0;
+
+ list_for_each_entry(attr, &pack->attrs, entry) {
+ switch (attr->attr->id) {
+ case Protocol_Version:
+ protocol_version = attr;
+ break;
+ case Framing_Capabilities:
+ framing_cap = attr;
+ break;
+ case Assigned_Tunnel_ID:
+ assigned_tid = attr;
+ break;
+ case Challenge:
+ if (conf_verbose)
+ log_warn("l2tp: Challenge in SCCRQ is not supported\n");
+ return -1;
+ case Assigned_Connection_ID:
+ assigned_cid = attr;
+ break;
+ case Router_ID:
+ router_id = attr;
+ break;
+ case Message_Digest:
+ if (conf_verbose)
+ log_warn("l2tp: Message-Digest is not supported\n");
+ return -1;
+ }
+ }
+
+ if (assigned_tid) {
+ if (!protocol_version) {
+ if (conf_verbose)
+ log_warn("l2tp: SCCRQ: no Protocol-Version present in message\n");
+ return -1;
+ }
+ if (protocol_version->val.uint16 != L2TP_V2_PROTOCOL_VERSION) {
+ if (conf_verbose)
+ log_warn("l2tp: protocol version %02x is not supported\n", protocol_version->val.uint16);
+ return -1;
+ }
+ if (!framing_cap) {
+ if (conf_verbose)
+ log_warn("l2tp: SCCRQ: no Framing-Capabilities present in message\n");
+ return -1;
+ }
+
+ if (l2tp_tunnel_alloc(serv, pack, pkt_info, assigned_tid, framing_cap))
+ return -1;
+
+ } else if (assigned_cid) {
+ // not yet implemented
+ return 0;
+ } else {
+ if (conf_verbose)
+ log_warn("l2tp: SCCRQ: no Assigned-Tunnel-ID or Assigned-Connection-ID present in message\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int l2tp_recv_SCCCN(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ if (conn->state == STATE_WAIT_SCCCN) {
+ triton_timer_mod(&conn->timeout_timer, 0);
+ conn->state = STATE_ESTB;
+ conn->state1 = STATE_WAIT_ICRQ;
+ }
+ else
+ log_ppp_warn("l2tp: unexpected SCCCN\n");
+
+ return 0;
+}
+
+static int l2tp_recv_StopCCN(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ l2tp_send_ZLB(conn);
+ return -1;
+}
+
+static int l2tp_recv_HELLO(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ if (l2tp_send_ZLB(conn))
+ return -1;
+
+ return 0;
+}
+
+static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ struct l2tp_attr_t *attr;
+ struct l2tp_attr_t *assigned_sid = NULL;
+
+ if (conn->state1 != STATE_WAIT_ICRQ) {
+ log_ppp_warn("l2tp: unexpected ICRQ\n");
+ return 0;
+ }
+
+ list_for_each_entry(attr, &pack->attrs, entry) {
+ switch(attr->attr->id) {
+ case Assigned_Session_ID:
+ assigned_sid = attr;
+ break;
+ case Message_Type:
+ case Call_Serial_Number:
+ case Bearer_Type:
+ case Calling_Number:
+ case Called_Number:
+ case Sub_Address:
+ case Physical_Channel_ID:
+ break;
+ default:
+ if (attr->M) {
+ if (conf_verbose) {
+ log_ppp_warn("l2tp: ICRQ: unknown attribute %i\n", attr->attr->id);
+ if (l2tp_terminate(conn, 2, 8))
+ return -1;
+ return 0;
+ }
+ }
+ }
+ }
+
+ if (!assigned_sid) {
+ if (conf_verbose)
+ log_ppp_warn("l2tp: ICRQ: no Assigned-Session-ID attribute present in message\n");
+ if (l2tp_terminate(conn, 2, 0))
+ return -1;
+ }
+
+ conn->peer_sid = assigned_sid->val.uint16;
+
+ if (l2tp_send_ICRP(conn))
+ return -1;
+
+ if (l2tp_send_OCRQ(conn))
+ return -1;
+
+ return 0;
+}
+
+static int l2tp_recv_ICCN(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ if (conn->state1 != STATE_WAIT_ICCN) {
+ log_ppp_warn("l2tp: unexpected ICCN\n");
+ return 0;
+ }
+
+ conn->state1 = STATE_ESTB;
+
+ if (l2tp_connect(conn)) {
+ if (l2tp_terminate(conn, 2, 0))
+ return -1;
+ return 0;
+ }
+
+ if (l2tp_send_ZLB(conn))
+ return -1;
+
+ triton_timer_del(&conn->timeout_timer);
+
+ return 0;
+}
+
+static int l2tp_recv_OCRP(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ if (conn->state2 != STATE_WAIT_OCRP) {
+ log_ppp_warn("l2tp: unexpected OCRP\n");
+ return 0;
+ }
+
+ conn->state2 = STATE_WAIT_OCCN;
+
+ return 0;
+}
+
+static int l2tp_recv_OCCN(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ if (conn->state2 != STATE_WAIT_OCCN) {
+ log_ppp_warn("l2tp: unexpected OCCN\n");
+ return 0;
+ }
+
+ conn->state2 = STATE_ESTB;
+
+ return 0;
+}
+
+static int l2tp_recv_CDN(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ if (ntohs(pack->hdr.sid) != conn->sid) {
+ if (conf_verbose)
+ log_warn("l2tp: sid %i is incorrect\n", ntohs(pack->hdr.sid));
+ return 0;
+ }
+
+ if (conn->state == STATE_PPP) {
+ __sync_sub_and_fetch(&stat_active, 1);
+ conn->state = STATE_FIN;
+ ppp_terminate(&conn->ppp, TERM_USER_REQUEST, 1);
+ }
+
+ if (l2tp_terminate(conn, 0, 0))
+ return -1;
+
+ return 0;
+}
+
+static int l2tp_recv_SLI(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack)
+{
+ return 0;
+}
+
+static int l2tp_conn_read(struct triton_md_handler_t *h)
+{
+ struct l2tp_conn_t *conn = container_of(h, typeof(*conn), hnd);
+ struct l2tp_packet_t *pack, *p;
+ struct l2tp_attr_t *msg_type;
+
+ while (1) {
+ if (l2tp_recv(h->fd, &pack, NULL))
+ return 0;
+
+ if (!pack)
+ continue;
+
+ if (ntohs(pack->hdr.tid) != conn->tid) {
+ if (conf_verbose)
+ log_warn("l2tp: incorrect tid %i in tunnel %i\n", ntohs(pack->hdr.tid), conn->tid);
+ l2tp_packet_free(pack);
+ continue;
+ }
+
+ if (ntohs(pack->hdr.Ns) == conn->Nr + 1) {
+ if (!list_empty(&pack->attrs))
+ conn->Nr++;
+ if (!list_empty(&conn->send_queue)) {
+ p = list_entry(conn->send_queue.next, typeof(*pack), entry);
+ list_del(&p->entry);
+ l2tp_packet_free(p);
+ conn->retransmit = 0;
+ }
+ if (!list_empty(&conn->send_queue))
+ triton_timer_mod(&conn->rtimeout_timer, 0);
+ else {
+ if (conn->rtimeout_timer.tpd)
+ triton_timer_del(&conn->rtimeout_timer);
+ if (conn->state == STATE_FIN)
+ goto drop;
+ }
+ } else {
+ if (ntohs(pack->hdr.Ns) < conn->Nr + 1 || (ntohs(pack->hdr.Ns > 32767 && conn->Nr + 1 < 32767))) {
+ log_ppp_debug("duplicate packet\n");
+ if (l2tp_send_ZLB(conn))
+ goto drop;
+ } else
+ log_ppp_debug("reordered packet\n");
+ l2tp_packet_free(pack);
+ continue;
+ }
+
+ if (list_empty(&pack->attrs)) {
+ l2tp_packet_free(pack);
+ continue;
+ }
+
+ msg_type = list_entry(pack->attrs.next, typeof(*msg_type), entry);
+
+ if (msg_type->attr->id != Message_Type) {
+ if (conf_verbose)
+ log_ppp_error("l2tp: first attribute is not Message-Type, dropping connection...\n");
+ goto drop;
+ }
+
+ if (conf_verbose) {
+ if (msg_type->val.uint16 == Message_Type_Hello) {
+ log_ppp_debug("recv ");
+ l2tp_packet_print(pack, log_ppp_debug);
+ } else {
+ log_ppp_info2("recv ");
+ l2tp_packet_print(pack, log_ppp_info2);
+ }
+ }
+
+ switch (msg_type->val.uint16) {
+ case Message_Type_Start_Ctrl_Conn_Connected:
+ if (l2tp_recv_SCCCN(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Stop_Ctrl_Conn_Notify:
+ if (l2tp_recv_StopCCN(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Hello:
+ if (l2tp_recv_HELLO(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Incoming_Call_Request:
+ if (l2tp_recv_ICRQ(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Incoming_Call_Connected:
+ if (l2tp_recv_ICCN(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Outgoing_Call_Reply:
+ if (l2tp_recv_OCRP(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Outgoing_Call_Connected:
+ if (l2tp_recv_OCCN(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Call_Disconnect_Notify:
+ if (l2tp_recv_CDN(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Set_Link_Info:
+ if (l2tp_recv_SLI(conn, pack))
+ goto drop;
+ break;
+ case Message_Type_Start_Ctrl_Conn_Request:
+ case Message_Type_Start_Ctrl_Conn_Reply:
+ case Message_Type_Outgoing_Call_Request:
+ case Message_Type_Incoming_Call_Reply:
+ case Message_Type_WAN_Error_Notify:
+ if (conf_verbose)
+ log_warn("l2tp: unexpected Message-Type %i\n", msg_type->val.uint16);
+ break;
+ default:
+ if (conf_verbose)
+ log_warn("l2tp: unknown Message-Type %i\n", msg_type->val.uint16);
+ if (msg_type->M) {
+ if (l2tp_terminate(conn, 2, 8))
+ goto drop;
+ }
+ }
+
+ l2tp_packet_free(pack);
+ }
+
+drop:
+ l2tp_packet_free(pack);
+ l2tp_disconnect(conn);
+ return -1;
+}
+
+static int l2tp_udp_read(struct triton_md_handler_t *h)
+{
+ struct l2tp_serv_t *serv = container_of(h, typeof(*serv), hnd);
+ struct l2tp_packet_t *pack;
+ struct l2tp_attr_t *msg_type;
+ struct in_pktinfo pkt_info;
+
+ while (1) {
+ if (l2tp_recv(h->fd, &pack, &pkt_info))
+ break;
+
+ if (!pack)
+ continue;
+
+ if (iprange_client_check(pack->addr.sin_addr.s_addr)) {
+ log_warn("l2tp: IP is out of client-ip-range, droping connection...\n");
+ goto skip;
+ }
+
+ if (pack->hdr.tid)
+ goto skip;
+
+ if (list_empty(&pack->attrs)) {
+ if (conf_verbose)
+ log_warn("l2tp: to Message-Type attribute present\n");
+ goto skip;
+ }
+
+ msg_type = list_entry(pack->attrs.next, typeof(*msg_type), entry);
+ if (msg_type->attr->id != Message_Type) {
+ if (conf_verbose)
+ log_warn("l2tp: first attribute is not Message-Type\n");
+ goto skip;
+ }
+
+ if (msg_type->val.uint16 == Message_Type_Start_Ctrl_Conn_Request)
+ l2tp_recv_SCCRQ(serv, pack, &pkt_info);
+ else {
+ if (conf_verbose) {
+ log_warn("recv (unexpected) ");
+ l2tp_packet_print(pack, log_ppp_warn);
+ }
+ }
+skip:
+ l2tp_packet_free(pack);
+ }
+
+ return 0;
+}
+
+static void l2tp_udp_close(struct triton_context_t *ctx)
+{
+ struct l2tp_serv_t *serv = container_of(ctx, typeof(*serv), ctx);
+ triton_md_unregister_handler(&serv->hnd);
+ close(serv->hnd.fd);
+ triton_context_unregister(&serv->ctx);
+}
+
+static struct l2tp_serv_t udp_serv =
+{
+ .hnd.read = l2tp_udp_read,
+ .ctx.close = l2tp_udp_close,
+ .ctx.before_switch = log_switch,
+};
+
+/*static struct l2tp_serv_t ip_serv =
+{
+ .hnd.read=l2t_ip_read,
+ .ctx.close=l2tp_ip_close,
+};*/
+
+static void start_udp_server(void)
+{
+ struct sockaddr_in addr;
+ char *opt;
+ int flag = 1;
+
+ udp_serv.hnd.fd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (udp_serv.hnd.fd < 0) {
+ log_emerg("l2tp: socket: %s\n", strerror(errno));
+ return;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(L2TP_PORT);
+
+ opt = conf_get_opt("l2tp", "bind");
+ if (opt)
+ addr.sin_addr.s_addr = inet_addr(opt);
+ else
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ setsockopt(udp_serv.hnd.fd, SOL_SOCKET, SO_REUSEADDR, &udp_serv.hnd.fd, sizeof(udp_serv.hnd.fd));
+ setsockopt(udp_serv.hnd.fd, SOL_SOCKET, SO_NO_CHECK, &udp_serv.hnd.fd, sizeof(udp_serv.hnd.fd));
+
+ if (bind (udp_serv.hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
+ log_emerg("l2tp: bind: %s\n", strerror(errno));
+ close(udp_serv.hnd.fd);
+ return;
+ }
+
+ if (fcntl(udp_serv.hnd.fd, F_SETFL, O_NONBLOCK)) {
+ log_emerg("l2tp: failed to set nonblocking mode: %s\n", strerror(errno));
+ close(udp_serv.hnd.fd);
+ return;
+ }
+
+ if (setsockopt(udp_serv.hnd.fd, IPPROTO_IP, IP_PKTINFO, &flag, sizeof(flag))) {
+ log_emerg("l2tp: setsockopt(IP_PKTINFO): %s\n", strerror(errno));
+ close(udp_serv.hnd.fd);
+ return;
+ }
+
+ memcpy(&udp_serv.addr, &addr, sizeof(addr));
+
+ triton_context_register(&udp_serv.ctx, NULL);
+ triton_md_register_handler(&udp_serv.ctx, &udp_serv.hnd);
+ triton_md_enable_handler(&udp_serv.hnd, MD_MODE_READ);
+ triton_context_wakeup(&udp_serv.ctx);
+}
+
+static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client)
+{
+ cli_send(client, "l2tp:\r\n");
+ cli_sendv(client, " starting: %u\r\n", stat_starting);
+ cli_sendv(client, " active: %u\r\n", stat_active);
+
+ return CLI_CMD_OK;
+}
+
+static void load_config(void)
+{
+ char *opt;
+
+ opt = conf_get_opt("l2tp", "verbose");
+ if (opt && atoi(opt) > 0)
+ conf_verbose = 1;
+
+ opt = conf_get_opt("l2tp", "hello-interval");
+ if (opt && atoi(opt) > 0)
+ conf_hello_interval = atoi(opt);
+
+ opt = conf_get_opt("l2tp", "timeout");
+ if (opt && atoi(opt) > 0)
+ conf_timeout = atoi(opt);
+
+ opt = conf_get_opt("l2tp", "rtimeout");
+ if (opt && atoi(opt) > 0)
+ conf_rtimeout = atoi(opt);
+
+ opt = conf_get_opt("l2tp", "retransmit");
+ if (opt && atoi(opt) > 0)
+ conf_retransmit = atoi(opt);
+
+ if (conf_host_name)
+ _free(conf_host_name);
+ opt = conf_get_opt("l2tp", "host-name");
+ if (opt)
+ conf_host_name = _strdup(opt);
+ else
+ conf_host_name = NULL;
+}
+
+static void __init l2tp_init(void)
+{
+ l2tp_conn = malloc(L2TP_MAX_TID * sizeof(void *));
+ memset(l2tp_conn, 0, L2TP_MAX_TID * sizeof(void *));
+
+ l2tp_conn_pool = mempool_create(sizeof(struct l2tp_conn_t));
+
+ load_config();
+
+ start_udp_server();
+
+ cli_register_simple_cmd2(&show_stat_exec, NULL, 2, "show", "stat");
+
+ triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config);
+}
+