summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--accel-pppd/ctrl/l2tp/l2tp.c379
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);