diff options
author | Guillaume Nault <g.nault@alphalink.fr> | 2012-09-07 12:36:39 +0200 |
---|---|---|
committer | Kozlov Dmitry <xeb@mail.ru> | 2012-09-07 14:54:14 +0400 |
commit | b34b73e2c86238ffb808d51d52983b84cafedb4e (patch) | |
tree | 371c5818f6f09cb32780921ac983563e4e1056ef | |
parent | 795346e2bb5d4f3dea29719d9677c1c0c2031987 (diff) | |
download | accel-ppp-b34b73e2c86238ffb808d51d52983b84cafedb4e.tar.gz accel-ppp-b34b73e2c86238ffb808d51d52983b84cafedb4e.zip |
L2TP: Handle multiple sessions per tunnel
Accept to create several sessions for each tunnel. Session IDs
are generated randomly and stored in a binary search tree in
their corresponding tunnel (i.e. field "sessions" of the
l2tp_conn_t structure). A new mempool is defined for session
allocations.
Generation of the session IDs is simple but quite limited: it
selects a 16 bits random ID and checks if a session with this
ID already exists. If so the allocation fails and the session
is closed. Otherwise the selected ID is used for the new
session. So tunnels that handle many sessions may reject new
ones, even if unused session IDs are available (just because
the randomly choosen ID is already in use).
Signed-off-by: Guillaume Nault <g.nault@alphalink.fr>
-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 33f08dcc..edbdb98d 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(); |