diff options
Diffstat (limited to 'accel-pppd/ctrl/l2tp/l2tp.c')
-rw-r--r-- | accel-pppd/ctrl/l2tp/l2tp.c | 1141 |
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 0000000..ca56051 --- /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); +} + |