diff options
author | Guillaume Nault <g.nault@alphalink.fr> | 2014-02-06 17:22:09 +0100 |
---|---|---|
committer | Dmitry Kozlov <xeb@mail.ru> | 2014-02-08 09:31:00 +0400 |
commit | 175ff4cedb83ae7bee34734eebe5f81e9bb46ab9 (patch) | |
tree | e12a8ab5a6ced0d8e3fb5d9380615ebb3a87e380 /accel-pppd/ctrl | |
parent | 121194393d17efd00e34947dc064a572f80e7cfa (diff) | |
download | accel-ppp-175ff4cedb83ae7bee34734eebe5f81e9bb46ab9.tar.gz accel-ppp-175ff4cedb83ae7bee34734eebe5f81e9bb46ab9.zip |
l2tp: use same context for tunnels and session control channels
Run the control channels of L2TP sessions in their tunnel context to
avoid context calls between tunnels and sessions.
Tunnels and sessions now use the new state STATE_INIT as their initial
state (allows to differentiate between tunnels and sessions that are
starting or stopping). The STATE_CLOSE state isn't implicitely set to
0 by memset() anymore, so its value is changed for consistency with the
other STATE_* values.
With tunnels and sessions running in the same context, sessions can
remove themselves from their tunnel without having to perform context
calls. Tunnels can also remove sessions directly, without having to
asynchronously notify them. So the set of l2tp_tunnel_free_session*()
functions becomes useless. Sessions can also check if they're the last
session of the tunnel and automatically disconnect the tunnel if need
be. So the l2tp_tunnel_session_freed() callback can be removed.
The 'sess_count' field is now used to track the number of sessions
stored in the 'sessions' field of tunnels. It's incremented as soon
as a session is added and everytime one is removed.
The callback, used by data channels to notify their L2TP control session
of disconnection, is now executed in the tunnel context. The session ID
is used by the tunnel to find which session the disconnection applies
to. The session may well get destroyed by other means between the time
of the context call and the time of the callback execution.
The l2tp_tunnel_start_session() function doesn't need to create a new
context for the session. It now only sets the session establishment
timer and directly runs the session startup callback.
Since l2tp_session_free() can now handle sessions in any state and is
called within the tunnel context, there is no need for a special
l2tp_tunnel_cancel_session() (for now, this function is kept for patch
clarity).
Now that session packets are sent while in tunnel context,
l2tp_session_send() only needs to set the header session ID and can
directly call l2tp_tunnel_send(). For reception, the tunnel also
directly calls l2tp_session_recv().
Signed-off-by: Guillaume Nault <g.nault@alphalink.fr>
Diffstat (limited to 'accel-pppd/ctrl')
-rw-r--r-- | accel-pppd/ctrl/l2tp/l2tp.c | 379 |
1 files changed, 162 insertions, 217 deletions
diff --git a/accel-pppd/ctrl/l2tp/l2tp.c b/accel-pppd/ctrl/l2tp/l2tp.c index 70288e64..3dc65927 100644 --- a/accel-pppd/ctrl/l2tp/l2tp.c +++ b/accel-pppd/ctrl/l2tp/l2tp.c @@ -37,15 +37,16 @@ #define SOL_PPPOL2TP 273 #endif -#define STATE_WAIT_SCCRP 1 -#define STATE_WAIT_SCCCN 2 -#define STATE_WAIT_ICRP 3 -#define STATE_WAIT_ICCN 4 -#define STATE_WAIT_OCRP 5 -#define STATE_WAIT_OCCN 6 -#define STATE_ESTB 7 +#define STATE_INIT 1 +#define STATE_WAIT_SCCRP 2 +#define STATE_WAIT_SCCCN 3 +#define STATE_WAIT_ICRP 4 +#define STATE_WAIT_ICCN 5 +#define STATE_WAIT_OCRP 6 +#define STATE_WAIT_OCCN 7 +#define STATE_ESTB 8 #define STATE_FIN 9 -#define STATE_CLOSE 0 +#define STATE_CLOSE 10 #define APSTATE_INIT 1 #define APSTATE_STARTING 2 @@ -98,8 +99,6 @@ struct l2tp_sess_t uint16_t recv_seq:1; int reorder_timeout; - pthread_mutex_t sctx_lock; - struct triton_context_t sctx; struct triton_timer_t timeout_timer; pthread_mutex_t apses_lock; @@ -111,7 +110,9 @@ struct l2tp_sess_t struct l2tp_conn_t { + pthread_mutex_t ctx_lock; struct triton_context_t ctx; + struct triton_md_handler_t hnd; struct triton_timer_t timeout_timer; struct triton_timer_t rtimeout_timer; @@ -155,7 +156,6 @@ static int l2tp_tunnel_send(struct l2tp_conn_t *conn, static int l2tp_session_send(struct l2tp_sess_t *sess, struct l2tp_packet_t *pack); static int l2tp_conn_read(struct triton_md_handler_t *); -static void l2tp_tunnel_session_freed(void *data); static void apses_stop(void *data); @@ -221,11 +221,6 @@ static inline struct l2tp_conn_t *l2tp_tunnel_self(void) return container_of(triton_context_self(), struct l2tp_conn_t, ctx); } -static inline struct l2tp_sess_t *l2tp_session_self(void) -{ - return container_of(triton_context_self(), struct l2tp_sess_t, sctx); -} - static int sess_cmp(const void *a, const void *b) { const struct l2tp_sess_t *sess_a = a; @@ -567,6 +562,23 @@ out_err: static void l2tp_tunnel_disconnect(struct l2tp_conn_t *conn, int res, int err) { + switch (conn->state) { + case STATE_INIT: + case STATE_WAIT_SCCRP: + case STATE_WAIT_SCCCN: + case STATE_ESTB: + break; + case STATE_FIN: + case STATE_CLOSE: + return; + default: + log_tunnel(log_error, conn, + "impossible to disconnect tunnel:" + " invalid state %i\n", + conn->state); + return; + } + if (l2tp_send_StopCCN(conn, res, err) < 0) log_tunnel(log_error, conn, "impossible to notify peer of tunnel disconnection," @@ -577,6 +589,8 @@ static void l2tp_tunnel_disconnect(struct l2tp_conn_t *conn, int res, int err) static void __tunnel_destroy(struct l2tp_conn_t *conn) { + pthread_mutex_destroy(&conn->ctx_lock); + if (conn->challenge) _free(conn->challenge); if (conn->secret) @@ -603,7 +617,7 @@ static void __session_destroy(struct l2tp_sess_t *sess) struct l2tp_conn_t *conn = sess->paren_conn; pthread_mutex_destroy(&sess->apses_lock); - pthread_mutex_destroy(&sess->sctx_lock); + if (sess->ppp.fd >= 0) close(sess->ppp.fd); if (sess->ppp.ses.chan_name) @@ -634,13 +648,14 @@ static void session_hold(struct l2tp_sess_t *sess) __sync_add_and_fetch(&sess->ref_count, 1); } -static void __l2tp_session_free(void *data) +static void l2tp_session_free(struct l2tp_sess_t *sess) { - struct l2tp_sess_t *sess = data; intptr_t cause = TERM_NAS_REQUEST; int res = 1; switch (sess->state1) { + case STATE_INIT: + case STATE_WAIT_ICRP: case STATE_WAIT_ICCN: case STATE_WAIT_OCRP: case STATE_WAIT_OCCN: @@ -665,19 +680,55 @@ static void __l2tp_session_free(void *data) log_session(log_info2, sess, "deleting data channel\n"); break; + case STATE_CLOSE: + /* Session already removed. Will be freed once its reference + * counter drops to 0. + */ + return; + default: + log_session(log_error, sess, + "impossible to delete session: invalid state %i\n", + sess->state1); + return; } + sess->state1 = STATE_CLOSE; + if (sess->timeout_timer.tpd) triton_timer_del(&sess->timeout_timer); - pthread_mutex_lock(&sess->sctx_lock); - triton_context_unregister(&sess->sctx); - pthread_mutex_unlock(&sess->sctx_lock); - if (triton_context_call(&sess->paren_conn->ctx, - l2tp_tunnel_session_freed, NULL) < 0) - log_session(log_error, sess, - "impossible to notify parent tunnel that" - " session has been freed\n"); + if (sess->paren_conn->sessions) { + if (!tdelete(sess, &sess->paren_conn->sessions, sess_cmp)) { + log_session(log_error, sess, + "impossible to delete session:" + " session unreachable from its parent tunnel\n"); + return; + } + } + /* Parent tunnel doesn't hold the session anymore. This is true even + * if sess->paren_conn->sessions was NULL (which means that + * l2tp_session_free() is being called by tdestroy()). + */ + session_put(sess); + + if (--sess->paren_conn->sess_count == 0) { + switch (sess->paren_conn->state) { + case STATE_ESTB: + log_tunnel(log_info1, sess->paren_conn, + "no more session, disconnecting tunnel\n"); + l2tp_tunnel_disconnect(sess->paren_conn, 1, 0); + break; + case STATE_FIN: + case STATE_CLOSE: + break; + default: + log_tunnel(log_warn, sess->paren_conn, + "avoiding disconnection of empty tunnel:" + " invalid state %i\n", + sess->paren_conn->state); + break; + } + } /* Only drop the reference the session holds to itself. * Reference to the parent tunnel will be dropped by @@ -686,105 +737,49 @@ static void __l2tp_session_free(void *data) session_put(sess); } -static void __l2tp_tunnel_free_session(void *data) -{ - struct l2tp_sess_t *sess = data; - - if (triton_context_call(&sess->sctx, __l2tp_session_free, sess) < 0) - log_tunnel(log_error, l2tp_tunnel_self(), - "impossible to free session %hu/%hu:" - " call to child session failed\n", - sess->sid, sess->peer_sid); - - session_put(sess); -} - -static void l2tp_tunnel_free_session(void *sess) -{ - struct l2tp_conn_t *conn = l2tp_tunnel_self(); - - tdelete(sess, &conn->sessions, sess_cmp); - __l2tp_tunnel_free_session(sess); -} - -static void l2tp_tunnel_free_sessionid(void *data) -{ - uint16_t sid = (intptr_t)data; - struct l2tp_conn_t *conn = l2tp_tunnel_self(); - struct l2tp_sess_t *sess = l2tp_tunnel_get_session(conn, sid); - - if (sess) - l2tp_tunnel_free_session(sess); - else - log_tunnel(log_info2, conn, "avoid freeing session %hu:" - " session already removed from tunnel\n", sid); -} - -static int l2tp_session_free(struct l2tp_sess_t *sess) -{ - intptr_t sid = sess->sid; - - if (triton_context_call(&sess->paren_conn->ctx, - l2tp_tunnel_free_sessionid, (void *)sid) < 0) { - log_session(log_error, sess, "impossible to free session:" - " call to parent tunnel failed\n"); - return -1; - } - - return 0; -} - static void l2tp_tunnel_free(struct l2tp_conn_t *conn) { struct l2tp_packet_t *pack; - if (conn->state != STATE_CLOSE) - conn->state = STATE_CLOSE; - - if (conn->sess_count != 0) { - /* - * There are still sessions in this tunnel: remove the ones - * accessible from conn->sessions then exit. - * - * Each removed session will make an asynchronous call to - * l2tp_tunnel_session_freed(), which is responsible for - * calling l2tp_tunnel_free() again once the last session - * gets removed. - * - * There may be also sessions in this tunnel that are not - * referenced in conn->sessions. This can happen when a - * a session has been removed, but its cleanup function has - * not yet been scheduled. Such sessions will also call - * l2tp_tunnel_session_freed() after cleanup, so - * l2tp_tunnel_free() will be called again once every sessions - * have been cleaned up. - * - * This behaviour ensures that the parent tunnel of a session - * remains valid during this session's lifetime. + switch (conn->state) { + case STATE_INIT: + case STATE_WAIT_SCCRP: + case STATE_WAIT_SCCCN: + case STATE_ESTB: + case STATE_FIN: + break; + case STATE_CLOSE: + /* Tunnel already removed. Will be freed once its reference + * counter drops to 0. */ - if (conn->sessions) { - tdestroy(conn->sessions, __l2tp_tunnel_free_session); - conn->sessions = NULL; - } + return; + default: + log_tunnel(log_error, conn, + "impossible to delete tunnel: invalid state %i\n", + conn->state); return; } + log_tunnel(log_info2, conn, "deleting tunnel\n"); + + conn->state = STATE_CLOSE; + pthread_mutex_lock(&l2tp_lock); l2tp_conn[conn->tid] = NULL; pthread_mutex_unlock(&l2tp_lock); if (conn->hnd.tpd) triton_md_unregister_handler(&conn->hnd); - if (conn->hnd.fd >= 0) + if (conn->hnd.fd >= 0) { close(conn->hnd.fd); + conn->hnd.fd = -1; + } 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->ctx.tpd) - triton_context_unregister(&conn->ctx); while (!list_empty(&conn->send_queue)) { pack = list_entry(conn->send_queue.next, typeof(*pack), entry); @@ -792,24 +787,23 @@ static void l2tp_tunnel_free(struct l2tp_conn_t *conn) l2tp_packet_free(pack); } - /* Drop the reference the tunnel holds to itself */ - tunnel_put(conn); -} + if (conn->sessions) { + void *sessions = conn->sessions; -static void l2tp_tunnel_session_freed(void *data) -{ - struct l2tp_conn_t *conn = l2tp_tunnel_self(); + conn->sessions = NULL; + tdestroy(sessions, (__free_fn_t)l2tp_session_free); + /* Let l2tp_session_free() handle the session counter and + * the reference held by the tunnel. + */ + } - if (--conn->sess_count != 0) - return; + pthread_mutex_lock(&conn->ctx_lock); + if (conn->ctx.tpd) + triton_context_unregister(&conn->ctx); + pthread_mutex_unlock(&conn->ctx_lock); - log_tunnel(log_info1, conn, "no more session, disconnecting tunnel\n"); - if (conn->state != STATE_CLOSE) - if (l2tp_send_StopCCN(conn, 1, 0) < 0) - log_tunnel(log_error, conn, - "impossible to notify peer of tunnel" - " disconnection, disconnecting anyway\n"); - l2tp_tunnel_free(conn); + /* Drop the reference the tunnel holds to itself */ + tunnel_put(conn); } static int l2tp_session_disconnect(struct l2tp_sess_t *sess, @@ -820,23 +814,34 @@ static int l2tp_session_disconnect(struct l2tp_sess_t *sess, "impossible to notify peer of session" " disconnection, disconnecting anyway\n"); - if (l2tp_session_free(sess) < 0) { - log_session(log_error, sess, "impossible to free session," - " session data have been kept\n"); - return -1; - } + l2tp_session_free(sess); return 0; } static void l2tp_session_apses_finished(void *data) { - struct l2tp_sess_t *sess = l2tp_session_self(); + struct l2tp_conn_t *conn = l2tp_tunnel_self(); + struct l2tp_sess_t *sess; + intptr_t sid = (intptr_t)data; + sess = l2tp_tunnel_get_session(conn, sid); + if (sess == NULL) + return; + + /* Here, the only valid session state is STATE_ESTB. If the session's + * state was STATE_CLOSE (which happens if session gets closed before + * l2tp_session_apses_finished() gets scheduled), it wouldn't be found + * by l2tp_tunnel_get_session(). + */ if (sess->state1 == STATE_ESTB) { log_session(log_info1, sess, "data channel closed, disconnecting session\n"); l2tp_session_disconnect(sess, 2, 0); + } else { + log_session(log_warn, sess, + "avoiding disconnection of session with no data channel:" + " invalid state %i\n", sess->state1); } } @@ -860,6 +865,7 @@ static void apses_finished(struct ap_session *apses) { struct l2tp_sess_t *sess = container_of(apses->ctrl, typeof(*sess), ctrl); + intptr_t sid = sess->sid; int res = 1; switch (sess->apses_state) { @@ -882,12 +888,12 @@ static void apses_finished(struct ap_session *apses) sess->apses_state = APSTATE_FINISHING; - pthread_mutex_lock(&sess->sctx_lock); - if (sess->sctx.tpd) - res = triton_context_call(&sess->sctx, + pthread_mutex_lock(&sess->paren_conn->ctx_lock); + if (sess->paren_conn->ctx.tpd) + res = triton_context_call(&sess->paren_conn->ctx, l2tp_session_apses_finished, - NULL); - pthread_mutex_unlock(&sess->sctx_lock); + (void *)sid); + pthread_mutex_unlock(&sess->paren_conn->ctx_lock); if (res < 0) log_ppp_warn("deleting session without notifying L2TP layer:" " call to L2TP control channel context failed\n"); @@ -931,14 +937,15 @@ static void apses_stop(void *data) sess->apses_state = APSTATE_FINISHING; ap_session_terminate(&sess->ppp.ses, cause, 1); } else { + intptr_t sid = sess->sid; int res = 1; - pthread_mutex_lock(&sess->sctx_lock); - if (sess->sctx.tpd) - res = triton_context_call(&sess->sctx, + pthread_mutex_lock(&sess->paren_conn->ctx_lock); + if (sess->paren_conn->ctx.tpd) + res = triton_context_call(&sess->paren_conn->ctx, l2tp_session_apses_finished, - NULL); - pthread_mutex_unlock(&sess->sctx_lock); + (void *)sid); + pthread_mutex_unlock(&sess->paren_conn->ctx_lock); if (res < 0) log_ppp_warn("deleting session without notifying L2TP layer:" " call to L2TP control channel context failed\n"); @@ -1055,6 +1062,8 @@ static struct l2tp_sess_t *l2tp_tunnel_new_session(struct l2tp_conn_t *conn) goto out_err; } + ++conn->sess_count; + return sess; out_err: @@ -1063,16 +1072,6 @@ out_err: return NULL; } -static void l2tp_sess_close(struct triton_context_t *ctx) -{ - struct l2tp_sess_t *sess = container_of(ctx, typeof(*sess), sctx); - - log_session(log_info1, sess, "context thread is closing," - " disconnecting session\n"); - if (l2tp_session_disconnect(sess, 3, 0) < 0) - log_session(log_error, sess, "session disconnection failed\n"); -} - static struct l2tp_sess_t *l2tp_tunnel_alloc_session(struct l2tp_conn_t *conn) { struct l2tp_sess_t *sess = NULL; @@ -1083,7 +1082,7 @@ static struct l2tp_sess_t *l2tp_tunnel_alloc_session(struct l2tp_conn_t *conn) sess->paren_conn = conn; sess->peer_sid = 0; - sess->state1 = STATE_CLOSE; + sess->state1 = STATE_INIT; sess->lns_mode = conn->lns_mode; sess->hide_avps = conn->hide_avps; sess->send_seq = (conf_dataseq == L2TP_DATASEQ_PREFER) || @@ -1091,10 +1090,6 @@ static struct l2tp_sess_t *l2tp_tunnel_alloc_session(struct l2tp_conn_t *conn) sess->recv_seq = (conf_dataseq == L2TP_DATASEQ_REQUIRE); sess->reorder_timeout = conf_reorder_timeout; - pthread_mutex_init(&sess->sctx_lock, NULL); - sess->sctx.before_switch = log_switch; - sess->sctx.close = l2tp_sess_close; - sess->timeout_timer.expire = l2tp_session_timeout; sess->timeout_timer.period = conf_timeout * 1000; @@ -1114,45 +1109,22 @@ static int l2tp_tunnel_start_session(struct l2tp_sess_t *sess, triton_event_func start_func, void *start_param) { - struct l2tp_conn_t *conn = l2tp_tunnel_self(); - - if (triton_context_register(&sess->sctx, &sess->ppp.ses) < 0) { - log_tunnel(log_error, conn, "impossible to start new session:" - " context registration failed\n"); - goto err; - } - triton_context_wakeup(&sess->sctx); - if (triton_timer_add(&sess->sctx, &sess->timeout_timer, 0) < 0) { - log_tunnel(log_error, conn, "impossible to start new session:" - " setting session establishment timer failed\n"); - goto err_ctx; - } - if (triton_context_call(&sess->sctx, start_func, start_param) < 0) { - log_tunnel(log_error, conn, "impossible to start new session:" - " call to session context failed\n"); - goto err_ctx_timer; + if (triton_timer_add(&sess->paren_conn->ctx, + &sess->timeout_timer, 0) < 0) { + log_session(log_error, sess, + "impossible to reply to incoming call:" + " setting establishment timer failed\n"); + return -1; } - ++conn->sess_count; + start_func(start_param); return 0; - -err_ctx_timer: - triton_timer_del(&sess->timeout_timer); -err_ctx: - triton_context_unregister(&sess->sctx); -err: - return -1; } static void l2tp_tunnel_cancel_session(struct l2tp_sess_t *sess) { - tdelete(sess, &sess->paren_conn->sessions, sess_cmp); - if (sess->ctrl.calling_station_id) - _free(sess->ctrl.calling_station_id); - if (sess->ctrl.called_station_id) - _free(sess->ctrl.called_station_id); - mempool_free(sess); + l2tp_session_free(sess); } static void l2tp_conn_close(struct triton_context_t *ctx) @@ -1223,6 +1195,7 @@ static struct l2tp_conn_t *l2tp_tunnel_alloc(const struct sockaddr_in *peer, } memset(conn, 0, sizeof(*conn)); + pthread_mutex_init(&conn->ctx_lock, NULL); INIT_LIST_HEAD(&conn->send_queue); conn->hnd.fd = socket(PF_INET, SOCK_DGRAM, 0); @@ -1319,6 +1292,7 @@ static struct l2tp_conn_t *l2tp_tunnel_alloc(const struct sockaddr_in *peer, goto out_err; } + conn->state = STATE_INIT; conn->framing_cap = framing_cap; conn->ctx.before_switch = log_switch; @@ -1749,27 +1723,12 @@ out_err: return -1; } -static void __l2tp_tunnel_send(void *pack) -{ - l2tp_tunnel_send(l2tp_tunnel_self(), pack); -} - static int l2tp_session_send(struct l2tp_sess_t *sess, struct l2tp_packet_t *pack) { pack->hdr.sid = htons(sess->peer_sid); - if (triton_context_call(&sess->paren_conn->ctx, - __l2tp_tunnel_send, pack) < 0) { - log_session(log_error, sess, "impossible to send packet:" - " call to parent tunnel failed\n"); - goto out_err; - } - - return 0; -out_err: - l2tp_packet_free(pack); - return -1; + return l2tp_tunnel_send(sess->paren_conn, pack); } static int l2tp_send_ZLB(struct l2tp_conn_t *conn) @@ -3415,11 +3374,7 @@ static int l2tp_recv_CDN(struct l2tp_sess_t *sess, if (l2tp_send_ZLB(sess->paren_conn) < 0) log_session(log_warn, sess, "acknowledging CDN failed\n"); - if (l2tp_session_free(sess) < 0) { - log_session(log_error, sess, "impossible to free session," - " session data have been kept\n"); - return -1; - } + l2tp_session_free(sess); return 0; } @@ -3478,10 +3433,7 @@ static void l2tp_session_place_call(void *data) " sending %cCRQ failed, freeing session\n", sess->lns_mode ? "outgoing" : "incoming", sess->lns_mode ? 'O' : 'I'); - if (l2tp_session_free(sess) < 0) - log_session(log_error, sess, - "impossible to free session," - " session data have been kept\n"); + l2tp_session_free(sess); return; } @@ -3520,10 +3472,9 @@ static void l2tp_tunnel_create_session(void *data) " request from command line interface\n", sid); } -static void l2tp_session_recv(void *data) +static void l2tp_session_recv(struct l2tp_sess_t *sess, + struct l2tp_packet_t *pack) { - struct l2tp_sess_t *sess = l2tp_session_self(); - struct l2tp_packet_t *pack = data; const struct l2tp_attr_t *msg_type = NULL; msg_type = list_entry(pack->attrs.next, typeof(*msg_type), entry); @@ -3731,13 +3682,7 @@ static int l2tp_conn_read(struct triton_md_handler_t *h) l2tp_packet_free(pack); continue; } - if (triton_context_call(&sess->sctx, l2tp_session_recv, pack) < 0) { - log_tunnel(log_warn, conn, - "impossible to handle message for session %hu:" - " call to child session failed\n", - sess->sid); - l2tp_packet_free(pack); - } + l2tp_session_recv(sess, pack); continue; case Message_Type_WAN_Error_Notify: l2tp_recv_WEN(conn, pack); |