diff options
Diffstat (limited to 'accel-pppd/ctrl/l2tp/l2tp.c')
-rw-r--r-- | accel-pppd/ctrl/l2tp/l2tp.c | 169 |
1 files changed, 137 insertions, 32 deletions
diff --git a/accel-pppd/ctrl/l2tp/l2tp.c b/accel-pppd/ctrl/l2tp/l2tp.c index 33f08dc..edbdb98 100644 --- a/accel-pppd/ctrl/l2tp/l2tp.c +++ b/accel-pppd/ctrl/l2tp/l2tp.c @@ -1,4 +1,5 @@ #include <unistd.h> +#include <search.h> #include <stdlib.h> #include <stdio.h> #include <stdarg.h> @@ -104,7 +105,7 @@ struct l2tp_conn_t struct list_head send_queue; int state; - struct l2tp_sess_t sess; + void *sessions; }; static pthread_mutex_t l2tp_lock = PTHREAD_MUTEX_INITIALIZER; @@ -112,6 +113,7 @@ static struct l2tp_conn_t **l2tp_conn; static uint16_t l2tp_tid; static mempool_t l2tp_conn_pool; +static mempool_t l2tp_sess_pool; static void l2tp_timeout(struct triton_timer_t *t); static void l2tp_rtimeout(struct triton_timer_t *t); @@ -120,6 +122,25 @@ 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 int sess_cmp(const void *a, const void *b) +{ + const struct l2tp_sess_t *sess_a = a; + const struct l2tp_sess_t *sess_b = b; + + return (sess_a->sid > sess_b->sid) - (sess_a->sid < sess_b->sid); +} + +static struct l2tp_sess_t *l2tp_tunnel_get_session(struct l2tp_conn_t *conn, + uint16_t sid) +{ + struct l2tp_sess_t sess = {.sid = sid, 0}; + struct l2tp_sess_t **res = NULL; + + res = tfind(&sess, &conn->sessions, sess_cmp); + + return (res) ? *res : NULL; +} + static int l2tp_send_StopCCN(struct l2tp_conn_t *conn, uint16_t res, uint16_t err) { @@ -170,38 +191,55 @@ out_err: return -1; } -static void l2tp_session_free(struct l2tp_sess_t *sess) +static void __l2tp_session_free(void *data) { + struct l2tp_sess_t *sess = data; + switch (sess->state1) { case STATE_PPP: __sync_sub_and_fetch(&stat_active, 1); + sess->state1 = STATE_CLOSE; ap_session_terminate(&sess->ppp.ses, TERM_USER_REQUEST, 1); - break; + triton_event_fire(EV_CTRL_FINISHED, &sess->ppp.ses); + /* No cleanup here, "sess" must remain a valid session + pointer (even if it no l2tp_conn_t points to it anymore). + This is because the above call to ap_session_terminate() + ends up in calling the l2tp_ppp_finished() callback, + which expects a valid session pointer. It is then the + responsibility of l2tp_ppp_finished() to eventually + cleanup the session structure by calling again + __l2tp_session_free(). */ + return; case STATE_WAIT_ICCN: case STATE_ESTB: __sync_sub_and_fetch(&stat_starting, 1); + triton_event_fire(EV_CTRL_FINISHED, &sess->ppp.ses); break; - default: - return; } - if (sess->ppp.fd != -1) - close(sess->ppp.fd); - - triton_event_fire(EV_CTRL_FINISHED, &sess->ppp.ses); - log_ppp_info1("disconnected\n"); - if (sess->ppp.ses.chan_name) - _free(sess->ppp.ses.chan_name); if (sess->timeout_timer.tpd) triton_timer_del(&sess->timeout_timer); + if (sess->ppp.fd != -1) + close(sess->ppp.fd); + if (sess->ppp.ses.chan_name) + _free(sess->ppp.ses.chan_name); + if (sess->ctrl.calling_station_id) + _free(sess->ctrl.calling_station_id); + if (sess->ctrl.called_station_id) + _free(sess->ctrl.called_station_id); - _free(sess->ctrl.calling_station_id); - _free(sess->ctrl.called_station_id); + mempool_free(sess); +} - sess->state1 = STATE_CLOSE; +static void l2tp_session_free(struct l2tp_sess_t *sess) +{ + if (tdelete(sess, &sess->paren_conn->sessions, sess_cmp) == NULL) { + log_warn("ERROR: impossible to delete unexisting session %hu\n", sess->sid); + } + __l2tp_session_free(sess); } static void l2tp_tunnel_free(struct l2tp_conn_t *conn) @@ -211,7 +249,7 @@ static void l2tp_tunnel_free(struct l2tp_conn_t *conn) if (conn->state == STATE_CLOSE) return; - l2tp_session_free(&conn->sess); + tdestroy(conn->sessions, __l2tp_session_free); triton_md_unregister_handler(&conn->hnd); close(conn->hnd.fd); @@ -268,19 +306,30 @@ static int l2tp_session_disconnect(struct l2tp_sess_t *sess, l2tp_session_free(sess); /* Free the tunnel when all sessions have been closed */ - return l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); + if (sess->paren_conn->sessions == NULL) + return l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); + else + return 0; } static void l2tp_ppp_session_disconnect(void *param) { struct l2tp_sess_t *sess = param; - l2tp_send_CDN(sess, 2, 0); - l2tp_session_free(sess); + if (sess->state1 != STATE_CLOSE) { + l2tp_send_CDN(sess, 2, 0); + l2tp_session_free(sess); + } else { + /* Called by __l2tp_session_free() via ap_session_terminate(). + Now, call __l2tp_session_free() again to finish cleanup. */ + __l2tp_session_free(sess); + } /* Disconnect the tunnel when all sessions have been closed */ - l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); - sess->paren_conn->state = STATE_FIN; + if (sess->paren_conn->sessions == NULL) { + l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); + sess->paren_conn->state = STATE_FIN; + } } static void l2tp_ppp_started(struct ap_session *ses) @@ -306,19 +355,66 @@ static void l2tp_session_timeout(struct triton_timer_t *t) l2tp_session_free(sess); /* Disconnect the tunnel when all sessions have been closed */ - l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); - sess->paren_conn->state = STATE_FIN; + if (sess->paren_conn->sessions == NULL) { + l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); + sess->paren_conn->state = STATE_FIN; + } +} + +static struct l2tp_sess_t *l2tp_tunnel_new_session(struct l2tp_conn_t *conn) +{ + struct l2tp_sess_t *sess = NULL; + struct l2tp_sess_t **sess_search = NULL; + ssize_t rdlen = 0; + + sess = mempool_alloc(l2tp_sess_pool); + if (sess == NULL) { + log_warn("l2tp: Impossible to allocate new session for" + " tunnel %hu: memory allocation error\n", conn->tid); + goto out_err; + } + memset(sess, 0, sizeof(*sess)); + + rdlen = read(urandom_fd, &sess->sid, sizeof(sess->sid)); + if (rdlen != sizeof(sess->sid)) { + log_warn("l2tp: Impossible to allocate new session for" + " tunnel %hu: could not get random number (%s)\n", + conn->tid, + (rdlen < 0) ? strerror(errno) : "short read"); + goto out_err; + } + if (sess->sid == 0) { + log_warn("l2tp: Impossible to allocate new session for" + " tunnel %hu: could not get a valid session ID\n", + conn->tid); + goto out_err; + } + + sess_search = tsearch(sess, &conn->sessions, sess_cmp); + if (*sess_search != sess) { + log_warn("l2tp: Impossible to allocate new session for" + " tunnel %hu: could not find any unused session ID\n", + conn->tid); + goto out_err; + } + + return sess; + +out_err: + if (sess) + mempool_free(sess); + return NULL; } static struct l2tp_sess_t *l2tp_session_alloc(struct l2tp_conn_t *conn) { - struct l2tp_sess_t *sess = &conn->sess; + struct l2tp_sess_t *sess = NULL; - if (sess->state1 != STATE_CLOSE) + sess = l2tp_tunnel_new_session(conn); + if (sess == NULL) return NULL; sess->paren_conn = conn; - sess->sid = 1; sess->peer_sid = 0; sess->state1 = STATE_CLOSE; sess->state2 = STATE_CLOSE; @@ -470,7 +566,7 @@ static int l2tp_tunnel_alloc(struct l2tp_serv_t *serv, struct l2tp_packet_t *pac l2tp_packet_print(pack, log_ppp_info2); } - conn->sess.state1 = STATE_CLOSE; + conn->sessions = NULL; triton_context_call(&conn->ctx, (triton_event_func)l2tp_send_SCCRP, conn); return 0; @@ -1036,7 +1132,10 @@ static int l2tp_recv_CDN(struct l2tp_sess_t *sess, struct l2tp_packet_t *pack) l2tp_session_free(sess); /* Free the tunnel when all sessions have been closed */ - return l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); + if (sess->paren_conn->sessions == NULL) + return l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); + else + return 0; } static int l2tp_recv_SLI(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack) @@ -1047,6 +1146,7 @@ static int l2tp_recv_SLI(struct l2tp_conn_t *conn, struct l2tp_packet_t *pack) static int l2tp_conn_read(struct triton_md_handler_t *h) { struct l2tp_conn_t *conn = container_of(h, typeof(*conn), hnd); + struct l2tp_sess_t *sess = NULL; struct l2tp_packet_t *pack, *p; struct l2tp_attr_t *msg_type; int res; @@ -1139,19 +1239,23 @@ static int l2tp_conn_read(struct triton_md_handler_t *h) goto drop; break; case Message_Type_Incoming_Call_Connected: - if (l2tp_recv_ICCN(&conn->sess, pack)) + sess = l2tp_tunnel_get_session(conn, ntohs(pack->hdr.sid)); + if (sess == NULL || l2tp_recv_ICCN(sess, pack) < 0) goto drop; break; case Message_Type_Outgoing_Call_Reply: - if (l2tp_recv_OCRP(&conn->sess, pack)) + sess = l2tp_tunnel_get_session(conn, ntohs(pack->hdr.sid)); + if (sess == NULL || l2tp_recv_OCRP(sess, pack) < 0) goto drop; break; case Message_Type_Outgoing_Call_Connected: - if (l2tp_recv_OCCN(&conn->sess, pack)) + sess = l2tp_tunnel_get_session(conn, ntohs(pack->hdr.sid)); + if (sess == NULL || l2tp_recv_OCCN(sess, pack) < 0) goto drop; break; case Message_Type_Call_Disconnect_Notify: - if (l2tp_recv_CDN(&conn->sess, pack)) + sess = l2tp_tunnel_get_session(conn, ntohs(pack->hdr.sid)); + if (sess == NULL || l2tp_recv_CDN(sess, pack) < 0) goto drop; break; case Message_Type_Set_Link_Info: @@ -1388,6 +1492,7 @@ static void l2tp_init(void) memset(l2tp_conn, 0, L2TP_MAX_TID * sizeof(void *)); l2tp_conn_pool = mempool_create(sizeof(struct l2tp_conn_t)); + l2tp_sess_pool = mempool_create(sizeof(struct l2tp_sess_t)); load_config(); |