summaryrefslogtreecommitdiff
path: root/accel-pppd/ctrl/l2tp/l2tp.c
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd/ctrl/l2tp/l2tp.c')
-rw-r--r--accel-pppd/ctrl/l2tp/l2tp.c296
1 files changed, 235 insertions, 61 deletions
diff --git a/accel-pppd/ctrl/l2tp/l2tp.c b/accel-pppd/ctrl/l2tp/l2tp.c
index acd0430..70288e6 100644
--- a/accel-pppd/ctrl/l2tp/l2tp.c
+++ b/accel-pppd/ctrl/l2tp/l2tp.c
@@ -44,10 +44,14 @@
#define STATE_WAIT_OCRP 5
#define STATE_WAIT_OCCN 6
#define STATE_ESTB 7
-#define STATE_PPP 8
#define STATE_FIN 9
#define STATE_CLOSE 0
+#define APSTATE_INIT 1
+#define APSTATE_STARTING 2
+#define APSTATE_STARTED 3
+#define APSTATE_FINISHING 4
+
#define DEFAULT_PPP_MAX_MTU 1420
int conf_verbose = 0;
@@ -71,6 +75,7 @@ static const char *conf_ip_pool;
static unsigned int stat_active;
static unsigned int stat_starting;
+static unsigned int stat_finishing;
struct l2tp_serv_t
{
@@ -93,8 +98,13 @@ 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;
+ struct triton_context_t apses_ctx;
+ int apses_state;
struct ap_ctrl ctrl;
struct ppp_t ppp;
};
@@ -146,6 +156,7 @@ 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);
#define log_tunnel(log_func, conn, fmt, ...) \
@@ -591,6 +602,8 @@ 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)
@@ -624,36 +637,41 @@ static void session_hold(struct l2tp_sess_t *sess)
static void __l2tp_session_free(void *data)
{
struct l2tp_sess_t *sess = data;
+ intptr_t cause = TERM_NAS_REQUEST;
+ int res = 1;
switch (sess->state1) {
- case STATE_PPP:
- sess->state1 = STATE_CLOSE;
- ap_session_terminate(&sess->ppp.ses,
- TERM_USER_REQUEST, 1);
- /* 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_WAIT_OCRP:
case STATE_WAIT_OCCN:
- case STATE_ESTB:
- __sync_sub_and_fetch(&stat_starting, 1);
+ log_session(log_info2, sess, "deleting session\n");
break;
- }
+ case STATE_ESTB:
+ log_session(log_info2, sess, "deleting session\n");
- if (sess->state1 == STATE_ESTB || sess->state1 == STATE_CLOSE)
- /* Don't send event if session wasn't fully established */
triton_event_fire(EV_CTRL_FINISHED, &sess->ppp.ses);
+ pthread_mutex_lock(&sess->apses_lock);
+ if (sess->apses_ctx.tpd)
+ res = triton_context_call(&sess->apses_ctx, apses_stop,
+ (void *)cause);
+ pthread_mutex_unlock(&sess->apses_lock);
+
+ if (res < 0)
+ log_session(log_error, sess,
+ "impossible to delete data channel:"
+ " call to data channel context failed\n");
+ else if (res == 0)
+ log_session(log_info2, sess,
+ "deleting data channel\n");
+ break;
+ }
+
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)
@@ -811,37 +829,181 @@ static int l2tp_session_disconnect(struct l2tp_sess_t *sess,
return 0;
}
-static void l2tp_ppp_finished(struct ap_session *ses)
+static void l2tp_session_apses_finished(void *data)
{
struct l2tp_sess_t *sess = l2tp_session_self();
- __sync_sub_and_fetch(&stat_active, 1);
- if (sess->state1 != STATE_CLOSE) {
+ if (sess->state1 == STATE_ESTB) {
log_session(log_info1, sess,
- "PPP session finished (%s:%s),"
- " disconnecting session\n",
- ses->ifname, ses->username ? ses->username : "");
- sess->state1 = STATE_CLOSE;
- if (l2tp_send_CDN(sess, 2, 0) < 0)
- log_session(log_error, 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");
+ "data channel closed, disconnecting session\n");
+ l2tp_session_disconnect(sess, 2, 0);
+ }
+}
+
+static void __apses_destroy(void *data)
+{
+ struct l2tp_sess_t *sess = data;
+
+ pthread_mutex_lock(&sess->apses_lock);
+ triton_context_unregister(&sess->apses_ctx);
+ pthread_mutex_unlock(&sess->apses_lock);
+
+ log_ppp_info2("session destroyed\n");
+
+ __sync_sub_and_fetch(&stat_finishing, 1);
+
+ /* Drop reference to the L2TP session */
+ session_put(sess);
+}
+
+static void apses_finished(struct ap_session *apses)
+{
+ struct l2tp_sess_t *sess = container_of(apses->ctrl, typeof(*sess),
+ ctrl);
+ int res = 1;
+
+ switch (sess->apses_state) {
+ case APSTATE_STARTING:
+ __sync_sub_and_fetch(&stat_starting, 1);
+ __sync_add_and_fetch(&stat_finishing, 1);
+ break;
+ case APSTATE_STARTED:
+ __sync_sub_and_fetch(&stat_active, 1);
+ __sync_add_and_fetch(&stat_finishing, 1);
+ break;
+ case APSTATE_FINISHING:
+ break;
+ default:
+ log_ppp_error("impossible to delete session:"
+ " invalid state %i\n",
+ sess->apses_state);
+ return;
+ }
+
+ sess->apses_state = APSTATE_FINISHING;
+
+ pthread_mutex_lock(&sess->sctx_lock);
+ if (sess->sctx.tpd)
+ res = triton_context_call(&sess->sctx,
+ l2tp_session_apses_finished,
+ NULL);
+ pthread_mutex_unlock(&sess->sctx_lock);
+ if (res < 0)
+ log_ppp_warn("deleting session without notifying L2TP layer:"
+ " call to L2TP control channel context failed\n");
+
+ /* Don't drop the reference to the session now: session_put() may
+ * destroy the L2TP session, but the caller expects it to remain valid
+ * after we return.
+ */
+ if (triton_context_call(&sess->apses_ctx, __apses_destroy, sess) < 0)
+ log_ppp_error("impossible to delete session:"
+ " scheduling session destruction failed\n");
+}
+
+static void apses_stop(void *data)
+{
+ struct l2tp_sess_t *sess = container_of(triton_context_self(),
+ typeof(*sess), apses_ctx);
+ intptr_t cause = (intptr_t)data;
+
+ switch (sess->apses_state) {
+ case APSTATE_INIT:
+ case APSTATE_STARTING:
+ __sync_sub_and_fetch(&stat_starting, 1);
+ __sync_add_and_fetch(&stat_finishing, 1);
+ break;
+ case APSTATE_STARTED:
+ __sync_sub_and_fetch(&stat_active, 1);
+ __sync_add_and_fetch(&stat_finishing, 1);
+ break;
+ case APSTATE_FINISHING:
+ break;
+ default:
+ log_ppp_error("impossible to delete session:"
+ " invalid state %i\n",
+ sess->apses_state);
+ return;
+ }
+
+ if (sess->apses_state == APSTATE_STARTING ||
+ sess->apses_state == APSTATE_STARTED) {
+ sess->apses_state = APSTATE_FINISHING;
+ ap_session_terminate(&sess->ppp.ses, cause, 1);
} else {
- /* Called by __l2tp_session_free() via ap_session_terminate().
- Now, call __l2tp_session_free() again to finish cleanup. */
- __l2tp_session_free(sess);
+ int res = 1;
+
+ pthread_mutex_lock(&sess->sctx_lock);
+ if (sess->sctx.tpd)
+ res = triton_context_call(&sess->sctx,
+ l2tp_session_apses_finished,
+ NULL);
+ pthread_mutex_unlock(&sess->sctx_lock);
+ if (res < 0)
+ log_ppp_warn("deleting session without notifying L2TP layer:"
+ " call to L2TP control channel context failed\n");
+ }
+
+ /* Execution of __apses_destroy() may have been scheduled by
+ * ap_session_terminate() (via apses_finished()). We can
+ * nevertheless call __apses_destroy() synchronously here,
+ * so that the data channel gets destroyed without uselessly
+ * waiting for scheduling.
+ */
+ __apses_destroy(sess);
+}
+
+static void apses_ctx_stop(struct triton_context_t *ctx)
+{
+ intptr_t cause = TERM_ADMIN_RESET;
+
+ log_ppp_info1("context thread is closing, disconnecting session\n");
+ apses_stop((void *)cause);
+}
+
+static void apses_started(struct ap_session *apses)
+{
+ struct l2tp_sess_t *sess = container_of(apses->ctrl, typeof(*sess),
+ ctrl);
+
+ if (sess->apses_state != APSTATE_STARTING) {
+ log_ppp_error("impossible to activate session:"
+ " invalid state %i\n",
+ sess->apses_state);
+ return;
}
+
+ __sync_sub_and_fetch(&stat_starting, 1);
+ __sync_add_and_fetch(&stat_active, 1);
+ sess->apses_state = APSTATE_STARTED;
+
+ log_ppp_info1("session started\n");
}
-static void l2tp_ppp_started(struct ap_session *ses)
+static void apses_start(void *data)
{
- log_session(log_info1, l2tp_session_self(),
- "PPP session started (%s:%s)\n",
- ses->ifname, ses->username ? ses->username : "");
+ struct ap_session *apses = data;
+ struct l2tp_sess_t *sess = container_of(apses->ctrl, typeof(*sess),
+ ctrl);
+
+ if (sess->apses_state != APSTATE_INIT) {
+ log_ppp_error("impossible to start session:"
+ " invalid state %i\n",
+ sess->apses_state);
+ return;
+ }
+
+ log_ppp_info2("starting data channel for l2tp(%s)\n",
+ apses->chan_name);
+
+ if (establish_ppp(&sess->ppp) < 0) {
+ intptr_t cause = TERM_NAS_ERROR;
+
+ log_ppp_error("session startup failed,"
+ " disconnecting session\n");
+ apses_stop((void *)cause);
+ } else
+ sess->apses_state = APSTATE_STARTING;
}
static void l2tp_session_timeout(struct triton_timer_t *t)
@@ -929,12 +1091,14 @@ 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;
+ pthread_mutex_init(&sess->apses_lock, NULL);
ppp_init(&sess->ppp);
/* The tunnel holds a reference to the session */
@@ -969,7 +1133,6 @@ static int l2tp_tunnel_start_session(struct l2tp_sess_t *sess,
goto err_ctx_timer;
}
- __sync_add_and_fetch(&stat_starting, 1);
++conn->sess_count;
return 0;
@@ -1204,12 +1367,15 @@ static inline int l2tp_tunnel_update_peerport(struct l2tp_conn_t *conn,
static int l2tp_session_start_data_channel(struct l2tp_sess_t *sess)
{
- sess->ctrl.ctx = &sess->sctx;
+ sess->apses_ctx.before_switch = log_switch;
+ sess->apses_ctx.close = apses_ctx_stop;
+
+ sess->ctrl.ctx = &sess->apses_ctx;
sess->ctrl.type = CTRL_TYPE_L2TP;
sess->ctrl.ppp = 1;
sess->ctrl.name = "l2tp";
- sess->ctrl.started = l2tp_ppp_started;
- sess->ctrl.finished = l2tp_ppp_finished;
+ sess->ctrl.started = apses_started;
+ sess->ctrl.finished = apses_finished;
sess->ctrl.terminate = ppp_terminate;
sess->ctrl.max_mtu = conf_ppp_max_mtu;
sess->ctrl.mppe = conf_mppe;
@@ -1245,19 +1411,36 @@ static int l2tp_session_start_data_channel(struct l2tp_sess_t *sess)
}
sess->ppp.ses.ctrl = &sess->ctrl;
+ sess->apses_state = APSTATE_INIT;
- if (establish_ppp(&sess->ppp) < 0) {
+ /* The data channel holds a reference to the control session */
+ session_hold(sess);
+
+ if (triton_context_register(&sess->apses_ctx, &sess->ppp.ses) < 0) {
log_session(log_error, sess,
"impossible to start data channel:"
- " PPP establishment failed\n");
- goto err;
+ " context registration failed\n");
+ goto err_put;
}
- __sync_sub_and_fetch(&stat_starting, 1);
- __sync_add_and_fetch(&stat_active, 1);
+ triton_context_wakeup(&sess->apses_ctx);
+
+ if (triton_context_call(&sess->apses_ctx, apses_start,
+ &sess->ppp.ses) < 0) {
+ log_session(log_error, sess,
+ "impossible to start data channel:"
+ " call to data channel context failed\n");
+ goto err_put_ctx;
+ }
+
+ __sync_add_and_fetch(&stat_starting, 1);
return 0;
+err_put_ctx:
+ triton_context_unregister(&sess->apses_ctx);
+err_put:
+ session_put(sess);
err:
if (sess->ppp.ses.ipv4_pool_name) {
_free(sess->ppp.ses.ipv4_pool_name);
@@ -1369,6 +1552,7 @@ static int l2tp_session_connect(struct l2tp_sess_t *sess)
}
triton_event_fire(EV_CTRL_STARTED, &sess->ppp.ses);
+ sess->state1 = STATE_ESTB;
if (l2tp_session_start_data_channel(sess) < 0) {
log_session(log_error, sess, "impossible to connect session:"
@@ -1376,9 +1560,6 @@ static int l2tp_session_connect(struct l2tp_sess_t *sess)
goto out_err;
}
-
- sess->state1 = STATE_PPP;
-
return 0;
out_err:
@@ -2790,8 +2971,6 @@ static int l2tp_recv_ICRP(struct l2tp_sess_t *sess,
return -1;
}
- sess->state1 = STATE_ESTB;
-
if (l2tp_session_connect(sess) < 0) {
log_session(log_error, sess, "impossible to handle ICRP:"
" connecting session failed,"
@@ -2861,8 +3040,6 @@ static int l2tp_recv_ICCN(struct l2tp_sess_t *sess,
return -1;
}
- sess->state1 = STATE_ESTB;
-
if (l2tp_session_connect(sess)) {
log_session(log_error, sess, "impossible to handle ICCN:"
" connecting session failed,"
@@ -2902,8 +3079,6 @@ static void l2tp_session_outcall_reply(void *data)
goto out_err;
}
- sess->state1 = STATE_ESTB;
-
if (l2tp_session_connect(sess) < 0) {
log_session(log_error, sess,
"impossible to reply to outgoing call:"
@@ -3148,8 +3323,6 @@ static int l2tp_recv_OCCN(struct l2tp_sess_t *sess,
return -1;
}
- sess->state1 = STATE_ESTB;
-
if (l2tp_session_connect(sess) < 0) {
log_session(log_error, sess, "impossible to handle OCCN:"
" connecting session failed,"
@@ -3804,6 +3977,7 @@ static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt,
cli_send(client, "l2tp:\r\n");
cli_sendv(client, " starting: %u\r\n", stat_starting);
cli_sendv(client, " active: %u\r\n", stat_active);
+ cli_sendv(client, " finishing: %u\r\n", stat_finishing);
return CLI_CMD_OK;
}