summaryrefslogtreecommitdiff
path: root/accel-pppd/ctrl/l2tp
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd/ctrl/l2tp')
-rw-r--r--accel-pppd/ctrl/l2tp/CMakeLists.txt2
-rw-r--r--accel-pppd/ctrl/l2tp/l2tp.c291
-rw-r--r--accel-pppd/ctrl/l2tp/l2tp.h3
-rw-r--r--accel-pppd/ctrl/l2tp/packet.c96
-rw-r--r--accel-pppd/ctrl/l2tp/packet_test.c491
5 files changed, 743 insertions, 140 deletions
diff --git a/accel-pppd/ctrl/l2tp/CMakeLists.txt b/accel-pppd/ctrl/l2tp/CMakeLists.txt
index 66dd3fc6..80cf453b 100644
--- a/accel-pppd/ctrl/l2tp/CMakeLists.txt
+++ b/accel-pppd/ctrl/l2tp/CMakeLists.txt
@@ -8,10 +8,10 @@ ADD_LIBRARY(l2tp SHARED
packet.c
# netlink.c
)
+TARGET_LINK_LIBRARIES(l2tp ${crypto_lib})
#TARGET_LINK_LIBRARIES(l2tp nl nl-genl)
INSTALL(TARGETS l2tp LIBRARY DESTINATION lib${LIB_SUFFIX}/accel-ppp)
FILE(GLOB dict "${CMAKE_CURRENT_SOURCE_DIR}/dict/*")
INSTALL(FILES ${dict} DESTINATION share/accel-ppp/l2tp)
-
diff --git a/accel-pppd/ctrl/l2tp/l2tp.c b/accel-pppd/ctrl/l2tp/l2tp.c
index bb1d1699..cf0c502c 100644
--- a/accel-pppd/ctrl/l2tp/l2tp.c
+++ b/accel-pppd/ctrl/l2tp/l2tp.c
@@ -16,6 +16,8 @@
#include <linux/if_ether.h>
#include <linux/if_pppox.h>
+#include <openssl/md5.h>
+
#include "triton.h"
#include "mempool.h"
#include "log.h"
@@ -24,7 +26,6 @@
#include "utils.h"
#include "iprange.h"
#include "cli.h"
-#include "crypto.h"
#include "connlimit.h"
@@ -99,17 +100,22 @@ static const char *conf_ipv6_pool;
static const char *conf_dpv6_pool;
static const char *conf_ifname;
-static unsigned int stat_conn_starting;
-static unsigned int stat_conn_active;
-static unsigned int stat_conn_finishing;
+struct l2tp_stat_t
+{
+ unsigned int conn_starting;
+ unsigned int conn_active;
+ unsigned int conn_finishing;
-static unsigned int stat_sess_starting;
-static unsigned int stat_sess_active;
-static unsigned int stat_sess_finishing;
+ unsigned int sess_starting;
+ unsigned int sess_active;
+ unsigned int sess_finishing;
-static unsigned int stat_active;
-static unsigned int stat_starting;
-static unsigned int stat_finishing;
+ unsigned int data_starting;
+ unsigned int data_active;
+ unsigned int data_finishing;
+};
+
+static struct l2tp_stat_t l2tp_stat;
struct l2tp_serv_t
{
@@ -123,6 +129,11 @@ struct l2tp_sess_t
struct l2tp_conn_t *paren_conn;
uint16_t sid;
uint16_t peer_sid;
+/* We will keep l2tp attributes Calling-Number/Called-Number and their length while the session exists */
+ char *calling_num;
+ int calling_num_len;
+ char *called_num;
+ int called_num_len;
unsigned int ref_count;
int state1;
@@ -199,6 +210,45 @@ static void l2tp_session_free(struct l2tp_sess_t *sess);
static void l2tp_tunnel_free(struct l2tp_conn_t *conn);
static void apses_stop(void *data);
+static void l2tp_stat_inc(unsigned int *stat)
+{
+ __atomic_add_fetch(stat, 1, __ATOMIC_RELAXED);
+}
+
+static void l2tp_stat_dec(unsigned int *stat)
+{
+ __atomic_sub_fetch(stat, 1, __ATOMIC_RELAXED);
+}
+
+static void l2tp_stat_move(unsigned int *from, unsigned int *to)
+{
+ l2tp_stat_dec(from);
+ l2tp_stat_inc(to);
+}
+
+static void l2tp_stat_get(struct l2tp_stat_t *stat)
+{
+ stat->conn_starting = __atomic_load_n(&l2tp_stat.conn_starting, __ATOMIC_RELAXED);
+ stat->conn_active = __atomic_load_n(&l2tp_stat.conn_active, __ATOMIC_RELAXED);
+ stat->conn_finishing = __atomic_load_n(&l2tp_stat.conn_finishing, __ATOMIC_RELAXED);
+ stat->sess_starting = __atomic_load_n(&l2tp_stat.sess_starting, __ATOMIC_RELAXED);
+ stat->sess_active = __atomic_load_n(&l2tp_stat.sess_active, __ATOMIC_RELAXED);
+ stat->sess_finishing = __atomic_load_n(&l2tp_stat.sess_finishing, __ATOMIC_RELAXED);
+ stat->data_starting = __atomic_load_n(&l2tp_stat.data_starting, __ATOMIC_RELAXED);
+ stat->data_active = __atomic_load_n(&l2tp_stat.data_active, __ATOMIC_RELAXED);
+ stat->data_finishing = __atomic_load_n(&l2tp_stat.data_finishing, __ATOMIC_RELAXED);
+}
+
+unsigned int __export l2tp_stat_starting(void)
+{
+ return __atomic_load_n(&l2tp_stat.data_starting, __ATOMIC_RELAXED);
+}
+
+unsigned int __export l2tp_stat_active(void)
+{
+ return __atomic_load_n(&l2tp_stat.data_active, __ATOMIC_RELAXED);
+}
+
#define log_tunnel(log_func, conn, fmt, ...) \
do { \
@@ -848,16 +898,17 @@ out_err:
return -1;
}
+static void l2tp_session_free_ptr(void *ptr)
+{
+ l2tp_session_free((struct l2tp_sess_t *) ptr);
+}
+
static void l2tp_tunnel_free_sessions(struct l2tp_conn_t *conn)
{
void *sessions = conn->sessions;
conn->sessions = NULL;
-#ifdef HAVE_FREE_FN_T
- tdestroy(sessions, (__free_fn_t)l2tp_session_free);
-#else
- tdestroy(sessions, (void(*)(void *))l2tp_session_free);
-#endif
+ tdestroy(sessions, l2tp_session_free_ptr);
/* Let l2tp_session_free() handle the session counter and
* the reference held by the tunnel.
*/
@@ -870,12 +921,10 @@ static int l2tp_tunnel_disconnect(struct l2tp_conn_t *conn,
case STATE_INIT:
case STATE_WAIT_SCCRP:
case STATE_WAIT_SCCCN:
- __sync_sub_and_fetch(&stat_conn_starting, 1);
- __sync_add_and_fetch(&stat_conn_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.conn_starting, &l2tp_stat.conn_finishing);
break;
case STATE_ESTB:
- __sync_sub_and_fetch(&stat_conn_active, 1);
- __sync_add_and_fetch(&stat_conn_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.conn_active, &l2tp_stat.conn_finishing);
break;
case STATE_FIN:
case STATE_FIN_WAIT:
@@ -955,7 +1004,7 @@ static void __tunnel_destroy(struct l2tp_conn_t *conn)
mempool_free(conn);
- __sync_sub_and_fetch(&stat_conn_finishing, 1);
+ l2tp_stat_dec(&l2tp_stat.conn_finishing);
}
static void tunnel_put(struct l2tp_conn_t *conn)
@@ -983,12 +1032,16 @@ static void __session_destroy(struct l2tp_sess_t *sess)
_free(sess->ctrl.calling_station_id);
if (sess->ctrl.called_station_id)
_free(sess->ctrl.called_station_id);
+ if (sess->calling_num)
+ _free(sess->calling_num);
+ if (sess->called_num)
+ _free(sess->called_num);
log_session(log_info2, sess, "session destroyed\n");
mempool_free(sess);
- __sync_sub_and_fetch(&stat_sess_finishing, 1);
+ l2tp_stat_dec(&l2tp_stat.sess_finishing);
/* Now that the session is fully destroyed,
* drop the reference to the tunnel.
@@ -1021,15 +1074,13 @@ static void l2tp_session_free(struct l2tp_sess_t *sess)
case STATE_WAIT_OCCN:
log_session(log_info2, sess, "deleting session\n");
- __sync_sub_and_fetch(&stat_sess_starting, 1);
- __sync_add_and_fetch(&stat_sess_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.sess_starting, &l2tp_stat.sess_finishing);
break;
case STATE_ESTB:
log_session(log_info2, sess, "deleting session\n");
triton_event_fire(EV_CTRL_FINISHED, &sess->ppp.ses);
- __sync_sub_and_fetch(&stat_sess_active, 1);
- __sync_add_and_fetch(&stat_sess_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.sess_active, &l2tp_stat.sess_finishing);
pthread_mutex_lock(&sess->apses_lock);
if (sess->apses_ctx.tpd)
@@ -1124,12 +1175,10 @@ static void l2tp_tunnel_free(struct l2tp_conn_t *conn)
case STATE_INIT:
case STATE_WAIT_SCCRP:
case STATE_WAIT_SCCCN:
- __sync_sub_and_fetch(&stat_conn_starting, 1);
- __sync_add_and_fetch(&stat_conn_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.conn_starting, &l2tp_stat.conn_finishing);
break;
case STATE_ESTB:
- __sync_sub_and_fetch(&stat_conn_active, 1);
- __sync_add_and_fetch(&stat_conn_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.conn_active, &l2tp_stat.conn_finishing);
break;
case STATE_FIN:
case STATE_FIN_WAIT:
@@ -1252,7 +1301,7 @@ static void __apses_destroy(void *data)
log_ppp_info2("session destroyed\n");
- __sync_sub_and_fetch(&stat_finishing, 1);
+ l2tp_stat_dec(&l2tp_stat.data_finishing);
/* Drop reference to the L2TP session */
session_put(sess);
@@ -1267,12 +1316,10 @@ static void apses_finished(struct ap_session *apses)
switch (sess->apses_state) {
case APSTATE_STARTING:
- __sync_sub_and_fetch(&stat_starting, 1);
- __sync_add_and_fetch(&stat_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.data_starting, &l2tp_stat.data_finishing);
break;
case APSTATE_STARTED:
- __sync_sub_and_fetch(&stat_active, 1);
- __sync_add_and_fetch(&stat_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.data_active, &l2tp_stat.data_finishing);
break;
case APSTATE_FINISHING:
break;
@@ -1313,12 +1360,10 @@ static void apses_stop(void *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);
+ l2tp_stat_move(&l2tp_stat.data_starting, &l2tp_stat.data_finishing);
break;
case APSTATE_STARTED:
- __sync_sub_and_fetch(&stat_active, 1);
- __sync_add_and_fetch(&stat_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.data_active, &l2tp_stat.data_finishing);
break;
case APSTATE_FINISHING:
break;
@@ -1377,8 +1422,7 @@ static void apses_started(struct ap_session *apses)
return;
}
- __sync_sub_and_fetch(&stat_starting, 1);
- __sync_add_and_fetch(&stat_active, 1);
+ l2tp_stat_move(&l2tp_stat.data_starting, &l2tp_stat.data_active);
sess->apses_state = APSTATE_STARTED;
log_ppp_info1("session started over l2tp session %hu-%hu, %hu-%hu\n",
@@ -1507,7 +1551,7 @@ static struct l2tp_sess_t *l2tp_tunnel_alloc_session(struct l2tp_conn_t *conn)
tunnel_hold(conn);
session_hold(sess);
- __sync_add_and_fetch(&stat_sess_starting, 1);
+ l2tp_stat_inc(&l2tp_stat.sess_starting);
return sess;
}
@@ -1613,7 +1657,7 @@ static struct l2tp_conn_t *l2tp_tunnel_alloc(const struct sockaddr_in *peer,
strerror(errno));
goto err_conn_fd;
}
- if (bind(conn->hnd.fd, host, sizeof(*host))) {
+ if (bind(conn->hnd.fd, (struct sockaddr*)host, sizeof(*host))) {
log_error("l2tp: impossible to allocate new tunnel:"
" bind() failed: %s\n", strerror(errno));
goto err_conn_fd;
@@ -1646,7 +1690,7 @@ static struct l2tp_conn_t *l2tp_tunnel_alloc(const struct sockaddr_in *peer,
goto err_conn_fd;
}
- if (getsockname(conn->hnd.fd, &conn->host_addr, &hostaddrlen) < 0) {
+ if (getsockname(conn->hnd.fd, (struct sockaddr*)&conn->host_addr, &hostaddrlen) < 0) {
log_error("l2tp: impossible to allocate new tunnel:"
" getsockname() failed: %s\n", strerror(errno));
goto err_conn_fd;
@@ -1726,7 +1770,7 @@ static struct l2tp_conn_t *l2tp_tunnel_alloc(const struct sockaddr_in *peer,
conn->peer_rcv_wnd_sz = DEFAULT_PEER_RECV_WINDOW_SIZE;
tunnel_hold(conn);
- __sync_add_and_fetch(&stat_conn_starting, 1);
+ l2tp_stat_inc(&l2tp_stat.conn_starting);
return conn;
@@ -1747,7 +1791,7 @@ static inline int l2tp_tunnel_update_peerport(struct l2tp_conn_t *conn,
int res;
conn->peer_addr.sin_port = port_nbo;
- res = connect(conn->hnd.fd, &conn->peer_addr, sizeof(conn->peer_addr));
+ res = connect(conn->hnd.fd, (struct sockaddr*)&conn->peer_addr, sizeof(conn->peer_addr));
if (res < 0) {
log_tunnel(log_error, conn,
"impossible to update peer port from %hu to %hu:"
@@ -1775,25 +1819,52 @@ static int l2tp_session_start_data_channel(struct l2tp_sess_t *sess)
sess->ctrl.max_mtu = conf_ppp_max_mtu;
sess->ctrl.mppe = conf_mppe;
- sess->ctrl.calling_station_id = _malloc(17);
- if (sess->ctrl.calling_station_id == NULL) {
- log_session(log_error, sess,
- "impossible to start data channel:"
- " allocation of calling station ID failed\n");
- goto err;
+ /* If l2tp calling number avp exists, we use it, otherwise we use lac ip */
+ if (sess->calling_num != NULL) {
+ sess->ctrl.calling_station_id = _malloc(sess->calling_num_len+1);
+ if (sess->ctrl.calling_station_id == NULL) {
+ log_session(log_error, sess,
+ "impossible to start data channel:"
+ " allocation of calling station ID failed\n");
+ goto err;
+ }else {
+ strcpy(sess->ctrl.calling_station_id, sess->calling_num);
+ }
+ } else {
+ sess->ctrl.calling_station_id = _malloc(17);
+ if (sess->ctrl.calling_station_id == NULL) {
+ log_session(log_error, sess,
+ "impossible to start data channel:"
+ " allocation of calling station ID failed\n");
+ goto err;
+ } else {
+ u_inet_ntoa(sess->paren_conn->peer_addr.sin_addr.s_addr,
+ sess->ctrl.calling_station_id);
+ }
}
- u_inet_ntoa(sess->paren_conn->peer_addr.sin_addr.s_addr,
- sess->ctrl.calling_station_id);
-
- sess->ctrl.called_station_id = _malloc(17);
- if (sess->ctrl.called_station_id == NULL) {
- log_session(log_error, sess,
- "impossible to start data channel:"
- " allocation of called station ID failed\n");
- goto err;
+ /* If l2tp called number avp exists, we use it, otherwise we use my ip */
+ if (sess->called_num != NULL) {
+ sess->ctrl.called_station_id = _malloc(sess->called_num_len+1);
+ if (sess->ctrl.called_station_id == NULL) {
+ log_session(log_error, sess,
+ "impossible to start data channel:"
+ " allocation of called station ID failed\n");
+ goto err;
+ } else {
+ strcpy(sess->ctrl.called_station_id, sess->called_num);
+ }
+ } else {
+ sess->ctrl.called_station_id = _malloc(17);
+ if (sess->ctrl.called_station_id == NULL) {
+ log_session(log_error, sess,
+ "impossible to start data channel:"
+ " allocation of called station ID failed\n");
+ goto err;
+ } else {
+ u_inet_ntoa(sess->paren_conn->host_addr.sin_addr.s_addr,
+ sess->ctrl.called_station_id);
+ }
}
- u_inet_ntoa(sess->paren_conn->host_addr.sin_addr.s_addr,
- sess->ctrl.called_station_id);
if (conf_ip_pool) {
sess->ppp.ses.ipv4_pool_name = _strdup(conf_ip_pool);
@@ -1844,7 +1915,7 @@ static int l2tp_session_start_data_channel(struct l2tp_sess_t *sess)
goto err_put_ctx;
}
- __sync_add_and_fetch(&stat_starting, 1);
+ l2tp_stat_inc(&l2tp_stat.data_starting);
return 0;
@@ -1974,8 +2045,7 @@ static int l2tp_session_connect(struct l2tp_sess_t *sess)
}
triton_event_fire(EV_CTRL_STARTED, &sess->ppp.ses);
- __sync_sub_and_fetch(&stat_sess_starting, 1);
- __sync_add_and_fetch(&stat_sess_active, 1);
+ l2tp_stat_move(&l2tp_stat.sess_starting, &l2tp_stat.sess_active);
sess->state1 = STATE_ESTB;
if (l2tp_session_start_data_channel(sess) < 0) {
@@ -2053,8 +2123,7 @@ static int l2tp_tunnel_connect(struct l2tp_conn_t *conn)
close(tunnel_fd);
- __sync_sub_and_fetch(&stat_conn_starting, 1);
- __sync_add_and_fetch(&stat_conn_active, 1);
+ l2tp_stat_move(&l2tp_stat.conn_starting, &l2tp_stat.conn_active);
conn->state = STATE_ESTB;
return 0;
@@ -2684,12 +2753,10 @@ static void l2tp_tunnel_finwait(struct l2tp_conn_t *conn)
switch (conn->state) {
case STATE_WAIT_SCCRP:
case STATE_WAIT_SCCCN:
- __sync_sub_and_fetch(&stat_conn_starting, 1);
- __sync_add_and_fetch(&stat_conn_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.conn_starting, &l2tp_stat.conn_finishing);
break;
case STATE_ESTB:
- __sync_sub_and_fetch(&stat_conn_active, 1);
- __sync_add_and_fetch(&stat_conn_finishing, 1);
+ l2tp_stat_move(&l2tp_stat.conn_active, &l2tp_stat.conn_finishing);
break;
case STATE_FIN:
break;
@@ -2769,10 +2836,10 @@ static int l2tp_recv_SCCRQ(const struct l2tp_serv_t *serv,
return 0;
}
- if (conf_max_starting && ap_session_stat.starting >= conf_max_starting)
+ if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting)
return 0;
- if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions)
+ if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions)
return 0;
if (triton_module_loaded("connlimit")
@@ -3299,6 +3366,10 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn,
uint16_t sid = 0;
uint16_t res = 0;
uint16_t err = 0;
+ uint8_t calling[L2TP_AVP_LEN_MASK] = {0};
+ uint8_t called[L2TP_AVP_LEN_MASK] = {0};
+ int n = 0;
+ int m = 0;
if (conn->state != STATE_ESTB && conn->lns_mode) {
log_tunnel(log_warn, conn, "discarding unexpected ICRQ\n");
@@ -3311,10 +3382,10 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn,
return 0;
}
- if (conf_max_starting && ap_session_stat.starting >= conf_max_starting)
+ if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting)
return 0;
- if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions)
+ if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions)
return 0;
if (triton_module_loaded("connlimit")
@@ -3336,7 +3407,17 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn,
case Call_Serial_Number:
case Bearer_Type:
case Calling_Number:
+ /* Save Calling-Number L2TP attribute locally */
+ if (attr->attr->id == Calling_Number) {
+ n = attr->length;
+ memcpy(calling,attr->val.octets,n);
+ }
case Called_Number:
+ /* Save Called-Number L2TP attribute locally */
+ if (attr->attr->id == Called_Number) {
+ m = attr->length;
+ memcpy(called,attr->val.octets,m);
+ }
case Sub_Address:
case Physical_Channel_ID:
break;
@@ -3375,6 +3456,30 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn,
sess->peer_sid = peer_sid;
sid = sess->sid;
+ /* Allocate memory for Calling-Number if exists, and put it to l2tp_sess_t structure */
+ if (n > 0) {
+ sess->calling_num = _malloc(n+1);
+ if (sess->calling_num == NULL) {
+ log_tunnel(log_warn, conn, "can't allocate memory for Calling Number attribute. Will use LAC IP instead\n");
+ }else{
+ memcpy(sess->calling_num, calling, n);
+ sess->calling_num[n] = '\0';
+ sess->calling_num_len = n;
+ }
+ }
+
+ /* Allocate memory for Called-Number if exists, and put it to l2tp_sess_t structure */
+ if (m > 1) {
+ sess->called_num = _malloc(m+1);
+ if (sess->called_num == NULL) {
+ log_tunnel(log_warn, conn, "can't allocate memory for Called Number attribute. Will use my IP instead\n");
+ } else {
+ memcpy(sess->called_num, called, m);
+ sess->called_num[m] = '\0';
+ sess->called_num_len = m;
+ }
+ }
+
if (unknown_attr) {
log_tunnel(log_error, conn, "impossible to handle ICRQ:"
" unknown mandatory attribute type %i,"
@@ -3394,8 +3499,8 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn,
goto out_reject;
}
- log_tunnel(log_info1, conn, "new session %hu-%hu created following"
- " reception of ICRQ\n", sid, peer_sid);
+ log_tunnel(log_info1, conn, "new session %hu-%hu with calling num %s len %d, called num %s len %d created following"
+ " reception of ICRQ\n", sid, peer_sid, sess->calling_num, sess->calling_num_len, sess->called_num, sess->called_num_len);
return 0;
@@ -3617,10 +3722,10 @@ static int l2tp_recv_OCRQ(struct l2tp_conn_t *conn,
return 0;
}
- if (conf_max_starting && ap_session_stat.starting >= conf_max_starting)
+ if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting)
return 0;
- if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions)
+ if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions)
return 0;
if (triton_module_loaded("connlimit")
@@ -4637,21 +4742,25 @@ err_fd:
static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client)
{
+ struct l2tp_stat_t stat;
+
+ l2tp_stat_get(&stat);
+
cli_send(client, "l2tp:\r\n");
cli_send(client, " tunnels:\r\n");
- cli_sendv(client, " starting: %u\r\n", stat_conn_starting);
- cli_sendv(client, " active: %u\r\n", stat_conn_active);
- cli_sendv(client, " finishing: %u\r\n", stat_conn_finishing);
+ cli_sendv(client, " starting: %u\r\n", stat.conn_starting);
+ cli_sendv(client, " active: %u\r\n", stat.conn_active);
+ cli_sendv(client, " finishing: %u\r\n", stat.conn_finishing);
cli_send(client, " sessions (control channels):\r\n");
- cli_sendv(client, " starting: %u\r\n", stat_sess_starting);
- cli_sendv(client, " active: %u\r\n", stat_sess_active);
- cli_sendv(client, " finishing: %u\r\n", stat_sess_finishing);
+ cli_sendv(client, " starting: %u\r\n", stat.sess_starting);
+ cli_sendv(client, " active: %u\r\n", stat.sess_active);
+ cli_sendv(client, " finishing: %u\r\n", stat.sess_finishing);
cli_send(client, " sessions (data channels):\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);
+ cli_sendv(client, " starting: %u\r\n", stat.data_starting);
+ cli_sendv(client, " active: %u\r\n", stat.data_active);
+ cli_sendv(client, " finishing: %u\r\n", stat.data_finishing);
return CLI_CMD_OK;
}
@@ -4857,12 +4966,6 @@ static void l2tp_create_session_help(char * const *fields, int fields_cnt,
" - place new call in tunnel <tid>\r\n");
}
-void __export l2tp_get_stat(unsigned int **starting, unsigned int **active)
-{
- *starting = &stat_starting;
- *active = &stat_active;
-}
-
static void load_config(void)
{
const char *opt;
diff --git a/accel-pppd/ctrl/l2tp/l2tp.h b/accel-pppd/ctrl/l2tp/l2tp.h
index 76de867f..2f113a25 100644
--- a/accel-pppd/ctrl/l2tp/l2tp.h
+++ b/accel-pppd/ctrl/l2tp/l2tp.h
@@ -77,6 +77,9 @@ struct l2tp_packet_t
extern int conf_verbose;
extern int conf_avp_permissive;
+unsigned int l2tp_stat_starting(void);
+unsigned int l2tp_stat_active(void);
+
static inline int l2tp_packet_is_ZLB(const struct l2tp_packet_t *pack)
{
return list_empty(&pack->attrs);
diff --git a/accel-pppd/ctrl/l2tp/packet.c b/accel-pppd/ctrl/l2tp/packet.c
index 97e205f3..f134666d 100644
--- a/accel-pppd/ctrl/l2tp/packet.c
+++ b/accel-pppd/ctrl/l2tp/packet.c
@@ -8,7 +8,8 @@
#include <fcntl.h>
#include <arpa/inet.h>
-#include "crypto.h"
+#include <openssl/md5.h>
+
#include "triton.h"
#include "log.h"
#include "mempool.h"
@@ -112,42 +113,26 @@ void l2tp_packet_free(struct l2tp_packet_t *pack)
static void memxor(uint8_t *dst, const uint8_t *src, size_t sz)
{
- const uintmax_t *umax_src = (const uintmax_t *)src;
- uintmax_t *umax_dst = (uintmax_t *)dst;
- size_t left = sz % sizeof(uintmax_t);
size_t indx;
- for (indx = 0; indx < sz / sizeof(uintmax_t); ++indx)
- umax_dst[indx] ^= umax_src[indx];
-
- src += sz - left;
- dst += sz - left;
- while (left) {
- if (left >= sizeof(uint32_t)) {
- *(uint32_t *)dst ^= *(uint32_t *)src;
- src += sizeof(uint32_t);
- dst += sizeof(uint32_t);
- left -= sizeof(uint32_t);
- } else if (left >= sizeof(uint16_t)) {
- *(uint16_t *)dst ^= *(uint16_t *)src;
- src += sizeof(uint16_t);
- dst += sizeof(uint16_t);
- left -= sizeof(uint16_t);
- } else {
- *dst ^= *src;
- src += sizeof(uint8_t);
- dst += sizeof(uint8_t);
- left -= sizeof(uint8_t);
- }
- }
+ for (indx = 0; indx < sz; ++indx)
+ dst[indx] ^= src[indx];
}
/*
* Decipher hidden AVPs, keeping the Hidden AVP Subformat (i.e. the attribute
* value is prefixed by 2 bytes indicating its length in network byte order).
+ *
+ * On success the deciphered original attribute length is stored into
+ * *orig_attr_len, already validated against the size of the received AVP.
+ * Callers must never re-read that length from the AVP body themselves: it is
+ * the output of the cipher, so a peer using a mismatching secret (or an
+ * attacker blindly injecting hidden AVPs) makes it an essentially random
+ * 16 bits value.
*/
static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
- const char *secret, size_t secret_len)
+ const char *secret, size_t secret_len,
+ uint16_t *orig_attr_len_out)
{
MD5_CTX md5_ctx;
uint8_t md5[MD5_DIGEST_LENGTH];
@@ -161,7 +146,7 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
uint16_t last_block_len;
avp_len = avp->flags & L2TP_AVP_LEN_MASK;
- if (avp_len < sizeof(struct l2tp_avp_t) + 2) {
+ if (avp_len < sizeof(struct l2tp_avp_t) + sizeof(uint16_t)) {
/* Hidden AVPs must contain at least two bytes
for storing original attribute length */
log_warn("l2tp: incorrect hidden avp received (type %hu):"
@@ -179,20 +164,22 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
MD5_Final(p1, &md5_ctx);
if (attr_len <= MD5_DIGEST_LENGTH) {
+ /* The whole attribute fits in the first block: it is fully
+ deciphered, nothing more to do but to check its length */
memxor(avp->val, p1, attr_len);
- return 0;
+ goto out;
}
memxor(p1, avp->val, MD5_DIGEST_LENGTH);
- orig_attr_len = ntohs(*(uint16_t *)p1);
+ orig_attr_len = u_read_be16(p1);
- if (orig_attr_len <= MD5_DIGEST_LENGTH - 2) {
+ if (orig_attr_len <= MD5_DIGEST_LENGTH - sizeof(uint16_t)) {
/* Enough bytes decoded already, no need to decode padding */
memcpy(avp->val, p1, MD5_DIGEST_LENGTH);
- return 0;
+ goto out;
}
- if (orig_attr_len > attr_len - 2) {
+ if (orig_attr_len > attr_len - sizeof(uint16_t)) {
log_warn("l2tp: incorrect hidden avp received (type %hu):"
" original attribute length too big (ciphered"
" attribute length: %hu bytes, advertised original"
@@ -203,7 +190,7 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
/* Decode remaining blocks. Start from the last block as
preceding blocks must be kept hidden for computing MD5s */
- bytes_left = orig_attr_len + 2 - MD5_DIGEST_LENGTH;
+ bytes_left = orig_attr_len + sizeof(uint16_t) - MD5_DIGEST_LENGTH;
last_block_len = bytes_left % MD5_DIGEST_LENGTH;
blocks_left = bytes_left / MD5_DIGEST_LENGTH;
if (last_block_len) {
@@ -227,6 +214,23 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
}
memcpy(avp->val, p1, MD5_DIGEST_LENGTH);
+out:
+ /* The length prefix comes out of the cipher, so it is only as
+ trustworthy as the peer's knowledge of the shared secret. Bound it
+ against the room actually available in the received AVP before
+ letting it drive any read of the attribute value */
+ orig_attr_len = u_read_be16(avp->val);
+ if (orig_attr_len > attr_len - sizeof(uint16_t)) {
+ log_warn("l2tp: incorrect hidden avp received (type %hu):"
+ " deciphered attribute length too big (ciphered"
+ " attribute length: %hu bytes, deciphered original"
+ " attribute length: %hu bytes), wrong secret?\n",
+ ntohs(avp->type), attr_len, orig_attr_len);
+ return -1;
+ }
+
+ *orig_attr_len_out = orig_attr_len;
+
return 0;
}
@@ -240,6 +244,7 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
struct sockaddr_in addr;
socklen_t addr_len;
uint16_t orig_avp_len;
+ uint16_t orig_attr_len;
void *orig_avp_val;
uint8_t *buf, *ptr;
int n, length;
@@ -280,7 +285,7 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
ptr = (uint8_t *)(hdr + 1);
addr_len = sizeof(addr);
- n = recvfrom(fd, buf, L2TP_MAX_PACKET_SIZE, 0, &addr, &addr_len);
+ n = recvfrom(fd, buf, L2TP_MAX_PACKET_SIZE, 0, (struct sockaddr*)&addr, &addr_len);
if (n < 0) {
mempool_free(buf);
if (errno == EAGAIN) {
@@ -419,10 +424,11 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
ntohs(avp->type));
goto out_err;
}
- if (decode_avp(avp, RV, secret, secret_len) < 0)
+ if (decode_avp(avp, RV, secret, secret_len,
+ &orig_attr_len) < 0)
goto out_err;
- orig_avp_len = ntohs(*(uint16_t *)avp->val) + sizeof(*avp);
+ orig_avp_len = orig_attr_len + sizeof(*avp);
orig_avp_val = avp->val + sizeof(uint16_t);
} else {
orig_avp_len = avp_len;
@@ -444,17 +450,17 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
case ATTR_TYPE_INT16:
if (orig_avp_len != sizeof(*avp) + 2)
goto out_err_len;
- attr->val.uint16 = ntohs(*(uint16_t *)orig_avp_val);
+ attr->val.uint16 = u_read_be16(orig_avp_val);
break;
case ATTR_TYPE_INT32:
if (orig_avp_len != sizeof(*avp) + 4)
goto out_err_len;
- attr->val.uint32 = ntohl(*(uint32_t *)orig_avp_val);
+ attr->val.uint32 = u_read_be32(orig_avp_val);
break;
case ATTR_TYPE_INT64:
if (orig_avp_len != sizeof(*avp) + 8)
goto out_err_len;
- attr->val.uint64 = be64toh(*(uint64_t *)orig_avp_val);
+ attr->val.uint64 = u_read_be64(orig_avp_val);
break;
case ATTR_TYPE_OCTETS:
attr->val.octets = _malloc(attr->length);
@@ -531,13 +537,13 @@ int l2tp_packet_send(int sock, struct l2tp_packet_t *pack)
else
switch (attr->attr->type) {
case ATTR_TYPE_INT16:
- *(int16_t *)avp->val = htons(attr->val.int16);
+ u_write_be16(avp->val, attr->val.int16);
break;
case ATTR_TYPE_INT32:
- *(int32_t *)avp->val = htonl(attr->val.int32);
+ u_write_be32(avp->val, attr->val.int32);
break;
case ATTR_TYPE_INT64:
- *(uint64_t *)avp->val = htobe64(attr->val.uint64);
+ u_write_be64(avp->val, attr->val.uint64);
break;
case ATTR_TYPE_STRING:
case ATTR_TYPE_OCTETS:
@@ -552,7 +558,7 @@ int l2tp_packet_send(int sock, struct l2tp_packet_t *pack)
memcpy(buf, &pack->hdr, sizeof(pack->hdr));
hdr->flags = htons(pack->hdr.flags);
- n = sendto(sock, buf, len, 0, &pack->addr, sizeof(pack->addr));
+ n = sendto(sock, buf, len, 0, (struct sockaddr*)&pack->addr, sizeof(pack->addr));
mempool_free(buf);
if (n < 0) {
diff --git a/accel-pppd/ctrl/l2tp/packet_test.c b/accel-pppd/ctrl/l2tp/packet_test.c
new file mode 100644
index 00000000..a6c9a182
--- /dev/null
+++ b/accel-pppd/ctrl/l2tp/packet_test.c
@@ -0,0 +1,491 @@
+/*
+ * Standalone regression test for the L2TP control message parser.
+ *
+ * Not part of the cmake build. Compile and run with:
+ * gcc -O1 -g -Wall -fno-strict-aliasing -D_GNU_SOURCE \
+ * -fsanitize=address,undefined -fno-sanitize-recover=all \
+ * -I accel-pppd -I accel-pppd/include -I accel-pppd/ctrl/l2tp \
+ * -o /tmp/l2tp_packet_test \
+ * accel-pppd/ctrl/l2tp/packet_test.c accel-pppd/ctrl/l2tp/packet.c \
+ * -lcrypto && /tmp/l2tp_packet_test
+ *
+ * The interesting part is the hidden AVP subformat: the 2 bytes length prefix
+ * of a hidden AVP is an *output of the cipher*, so a peer using a different
+ * secret -- or an attacker injecting hidden AVPs blindly -- turns it into an
+ * essentially random 16 bits value. It must never be trusted to bound a read
+ * of the attribute value.
+ *
+ * The test drives the real parser through a real UDP socket:
+ * - hand-crafted packets exercise the hidden AVP length checks, including
+ * the single block (attribute <= 16 bytes) cipher path which the accel-ppp
+ * encoder itself never produces (it always pads by >= 16 bytes);
+ * - l2tp_packet_send()/l2tp_recv() round trips exercise the multi block
+ * cipher path and the unaligned AVP accessors.
+ *
+ * Everything packet.c needs besides libcrypto is stubbed below.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <openssl/md5.h>
+
+#include "triton.h"
+#include "log.h"
+#include "mempool.h"
+#include "l2tp.h"
+#include "attr_defs.h"
+
+static int failures;
+#define CHECK(cond) do { if (!(cond)) { \
+ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0)
+
+/* ------------------------------------------------------------------ stubs */
+
+int conf_verbose = 1;
+int conf_avp_permissive = 0;
+
+/* A dictionary just big enough for the attributes used here. Types are the
+ ones accel-ppp's own dictionary uses, except Tie_Breaker which is turned
+ into an INT64 to get coverage of the 64 bits accessor. */
+static struct l2tp_dict_attr_t dict[] = {
+ { .name = "Message-Type", .id = Message_Type, .type = ATTR_TYPE_INT16, .M = 1, .H = 0 },
+ { .name = "Tie-Breaker", .id = Tie_Breaker, .type = ATTR_TYPE_INT64, .M = 0, .H = -1 },
+ { .name = "Host-Name", .id = Host_Name, .type = ATTR_TYPE_STRING, .M = 1, .H = -1 },
+ { .name = "Assigned-Tunnel-Id", .id = Assigned_Tunnel_ID, .type = ATTR_TYPE_INT16, .M = 1, .H = -1 },
+ { .name = "Call-Serial-Number", .id = Call_Serial_Number, .type = ATTR_TYPE_INT32, .M = 1, .H = -1 },
+ { .name = "Random-Vector", .id = Random_Vector, .type = ATTR_TYPE_OCTETS, .M = 1, .H = 0 },
+};
+
+struct l2tp_dict_attr_t *l2tp_dict_find_attr_by_id(int id)
+{
+ size_t indx;
+
+ for (indx = 0; indx < sizeof(dict) / sizeof(dict[0]); ++indx)
+ if (dict[indx].id == id)
+ return &dict[indx];
+
+ return NULL;
+}
+
+const struct l2tp_dict_value_t *l2tp_dict_find_value(const struct l2tp_dict_attr_t *attr,
+ l2tp_value_t val)
+{
+ return NULL;
+}
+
+/* Size carrying mempool: allocations stay exactly as large as the pool's
+ object size, so that ASan traps any read past the end of a packet buffer */
+mempool_t *mempool_create(int size)
+{
+ int *pool = malloc(sizeof(int));
+
+ *pool = size;
+
+ return (mempool_t *)pool;
+}
+
+void *mempool_alloc(mempool_t *pool)
+{
+ return malloc(*(int *)pool);
+}
+
+void mempool_free(void *ptr)
+{
+ free(ptr);
+}
+
+void triton_register_init(int order, void (*func)(void))
+{
+ func();
+}
+
+int u_randbuf(void *buf, size_t buf_len, int *err)
+{
+ uint8_t *u8_buf = buf;
+ size_t indx;
+
+ /* Deterministic on purpose: reproducible failures beat real entropy */
+ for (indx = 0; indx < buf_len; ++indx)
+ u8_buf[indx] = (uint8_t)(indx * 7 + 0x5a);
+
+ return 0;
+}
+
+#define DEFINE_LOG_STUB(name) \
+ void name(const char *fmt, ...) {}
+DEFINE_LOG_STUB(log_emerg)
+DEFINE_LOG_STUB(log_error)
+DEFINE_LOG_STUB(log_warn)
+DEFINE_LOG_STUB(log_ppp_debug)
+
+/* -------------------------------------------------------- packet building */
+
+struct pktbuf {
+ uint8_t data[2048];
+ size_t len;
+};
+
+static void pkt_init(struct pktbuf *pkt)
+{
+ struct l2tp_hdr_t hdr;
+
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.flags = htons(L2TP_FLAG_T | L2TP_FLAG_L | L2TP_FLAG_S | 2);
+
+ memset(pkt, 0, sizeof(*pkt));
+ memcpy(pkt->data, &hdr, sizeof(hdr));
+ pkt->len = sizeof(hdr);
+}
+
+/* Append an AVP and return a pointer to its value */
+static uint8_t *pkt_add_avp(struct pktbuf *pkt, uint16_t extra_flags,
+ uint16_t type, const void *val, size_t val_len)
+{
+ struct l2tp_avp_t avp;
+ uint8_t *ptr = pkt->data + pkt->len;
+
+ memset(&avp, 0, sizeof(avp));
+ avp.flags = htons(extra_flags | ((sizeof(avp) + val_len) & L2TP_AVP_LEN_MASK));
+ avp.type = htons(type);
+
+ memcpy(ptr, &avp, sizeof(avp));
+ if (val_len)
+ memcpy(ptr + sizeof(avp), val, val_len);
+ pkt->len += sizeof(avp) + val_len;
+
+ return ptr + sizeof(avp);
+}
+
+static void pkt_finish(struct pktbuf *pkt)
+{
+ uint16_t length = htons(pkt->len);
+
+ memcpy(pkt->data + offsetof(struct l2tp_hdr_t, length),
+ &length, sizeof(length));
+}
+
+/*
+ * Cipher a hidden AVP whose cleartext (length prefix included) is at most one
+ * MD5 block long, i.e. the path that never validated the length prefix.
+ */
+static void hide_single_block(uint8_t *val, size_t val_len, uint16_t type,
+ const char *secret, size_t secret_len,
+ const uint8_t *rv, size_t rv_len)
+{
+ uint8_t md5[MD5_DIGEST_LENGTH];
+ uint16_t attr_type = htons(type);
+ MD5_CTX md5_ctx;
+ size_t indx;
+
+ MD5_Init(&md5_ctx);
+ MD5_Update(&md5_ctx, &attr_type, sizeof(attr_type));
+ MD5_Update(&md5_ctx, secret, secret_len);
+ MD5_Update(&md5_ctx, rv, rv_len);
+ MD5_Final(md5, &md5_ctx);
+
+ for (indx = 0; indx < val_len && indx < MD5_DIGEST_LENGTH; ++indx)
+ val[indx] ^= md5[indx];
+}
+
+/* --------------------------------------------------------------- plumbing */
+
+static const char secret[] = "s3cr3t";
+static int sock = -1;
+static struct sockaddr_in sock_addr;
+
+static void loopback_socket(void)
+{
+ socklen_t addr_len = sizeof(sock_addr);
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ perror("socket");
+ exit(1);
+ }
+
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0
+ || getsockname(sock, (struct sockaddr *)&sock_addr, &addr_len) < 0) {
+ perror("bind");
+ exit(1);
+ }
+}
+
+/* Feed raw bytes to the parser, NULL means "packet rejected" */
+static struct l2tp_packet_t *parse(const struct pktbuf *pkt)
+{
+ struct l2tp_packet_t *pack = NULL;
+
+ if (sendto(sock, pkt->data, pkt->len, 0,
+ (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) {
+ perror("sendto");
+ exit(1);
+ }
+
+ CHECK(l2tp_recv(sock, &pack, NULL, secret, sizeof(secret) - 1) == 0);
+
+ return pack;
+}
+
+static const struct l2tp_attr_t *find_attr(const struct l2tp_packet_t *pack, int id)
+{
+ const struct l2tp_attr_t *attr;
+
+ list_for_each_entry(attr, &pack->attrs, entry)
+ if (attr->attr->id == id)
+ return attr;
+
+ return NULL;
+}
+
+/* ------------------------------------------------------------------ tests */
+
+/*
+ * A hidden AVP small enough to be ciphered in a single block: its deciphered
+ * length prefix used to be taken at face value, so anything up to 65535 was
+ * handed to the memcpy() feeding attr->val, reading way past the packet
+ * buffer. The parser must accept a prefix only if the attribute value it
+ * announces really fits in the received AVP.
+ */
+static void test_hidden_avp_length_prefix(void)
+{
+ static const struct {
+ const char *name;
+ size_t attr_len; /* ciphered attribute length */
+ uint16_t declared; /* deciphered length prefix */
+ int accept;
+ } cases[] = {
+ { "lies about 64K", 16, 0xffff, 0 },
+ { "lies, minimal avp", 2, 0xffff, 0 },
+ { "off by one", 16, 15, 0 },
+ { "one byte too big", 4, 3, 0 },
+ { "fits exactly", 16, 14, 1 },
+ { "fits", 16, 4, 1 },
+ { "empty value", 2, 0, 1 },
+ { "no length prefix", 1, 0, 0 },
+ };
+ static const uint8_t rv[16] = {
+ 0xf3, 0x1a, 0x00, 0xff, 0x42, 0x7c, 0x91, 0x08,
+ 0x5d, 0xe6, 0x33, 0xb0, 0x14, 0xaa, 0x69, 0xc2,
+ };
+ uint8_t value[MD5_DIGEST_LENGTH];
+ struct l2tp_packet_t *pack;
+ const struct l2tp_attr_t *attr;
+ struct pktbuf pkt;
+ uint16_t declared;
+ size_t indx, i;
+
+ for (indx = 0; indx < sizeof(cases) / sizeof(cases[0]); ++indx) {
+ pkt_init(&pkt);
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M, Random_Vector, rv, sizeof(rv));
+
+ /* Cleartext: 2 bytes length prefix, then the value, then padding.
+ The value is a recognizable pattern so that a short read shows
+ up as wrong content rather than as a lucky pass. */
+ memset(value, 0, sizeof(value));
+ declared = htons(cases[indx].declared);
+ memcpy(value, &declared, cases[indx].attr_len < sizeof(declared)
+ ? cases[indx].attr_len : sizeof(declared));
+ for (i = sizeof(declared); i < cases[indx].attr_len; ++i)
+ value[i] = 'a' + (i % 26);
+
+ hide_single_block(value, cases[indx].attr_len, Host_Name,
+ secret, sizeof(secret) - 1, rv, sizeof(rv));
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name,
+ value, cases[indx].attr_len);
+ pkt_finish(&pkt);
+
+ pack = parse(&pkt);
+ if (!cases[indx].accept) {
+ if (pack) {
+ fprintf(stderr, "FAIL %s:%d: hidden avp accepted"
+ " (%s)\n", __FILE__, __LINE__,
+ cases[indx].name);
+ failures++;
+ l2tp_packet_free(pack);
+ }
+ continue;
+ }
+
+ if (!pack) {
+ fprintf(stderr, "FAIL %s:%d: hidden avp rejected (%s)\n",
+ __FILE__, __LINE__, cases[indx].name);
+ failures++;
+ continue;
+ }
+
+ attr = find_attr(pack, Host_Name);
+ CHECK(attr != NULL);
+ if (attr) {
+ CHECK(attr->length == cases[indx].declared);
+ for (i = 0; i < cases[indx].declared; ++i)
+ CHECK((uint8_t)attr->val.string[i] ==
+ 'a' + ((i + sizeof(declared)) % 26));
+ CHECK(attr->val.string[cases[indx].declared] == '\0');
+ }
+ l2tp_packet_free(pack);
+ }
+}
+
+/*
+ * A hidden AVP is rejected outright when no Random Vector was received, or
+ * when its length cannot even hold the length prefix.
+ */
+static void test_hidden_avp_prerequisites(void)
+{
+ static const uint8_t value[16] = { 0 };
+ struct l2tp_packet_t *pack;
+ struct pktbuf pkt;
+
+ pkt_init(&pkt);
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name,
+ value, sizeof(value));
+ pkt_finish(&pkt);
+ pack = parse(&pkt);
+ CHECK(pack == NULL);
+ if (pack)
+ l2tp_packet_free(pack);
+
+ /* Random Vector present, but the hidden AVP carries no value at all */
+ pkt_init(&pkt);
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M, Random_Vector, value, sizeof(value));
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name, NULL, 0);
+ pkt_finish(&pkt);
+ pack = parse(&pkt);
+ CHECK(pack == NULL);
+ if (pack)
+ l2tp_packet_free(pack);
+}
+
+/*
+ * Round trip through the real encoder. With hide_avps set every attribute but
+ * Message-Type and Random-Vector goes through the multi block cipher, since
+ * encode_attr() always appends at least 16 bytes of padding.
+ */
+static void test_roundtrip(int hide_avps)
+{
+ static const char host_name[] = "accel-ppp regression test host name";
+ struct l2tp_packet_t *pack;
+ const struct l2tp_attr_t *attr;
+ int ret;
+
+ pack = l2tp_packet_alloc(2, Message_Type_Hello, &sock_addr, hide_avps,
+ secret, sizeof(secret) - 1);
+ CHECK(pack != NULL);
+ if (!pack)
+ return;
+
+ /* Odd length string first: everything after it sits on an odd offset,
+ so the integer accessors below run unaligned */
+ CHECK(l2tp_packet_add_string(pack, Host_Name, host_name, 1) == 0);
+ CHECK(l2tp_packet_add_int16(pack, Assigned_Tunnel_ID, 0x1234, 1) == 0);
+ CHECK(l2tp_packet_add_int32(pack, Call_Serial_Number, 0x89abcdef, 1) == 0);
+ CHECK(l2tp_packet_add_int64(pack, Tie_Breaker, 0x0123456789abcdefULL, 0) == 0);
+
+ ret = l2tp_packet_send(sock, pack);
+ CHECK(ret == 0);
+ l2tp_packet_free(pack);
+ if (ret < 0)
+ return;
+
+ pack = NULL;
+ CHECK(l2tp_recv(sock, &pack, NULL, secret, sizeof(secret) - 1) == 0);
+ CHECK(pack != NULL);
+ if (!pack)
+ return;
+
+ attr = find_attr(pack, Message_Type);
+ CHECK(attr && attr->val.uint16 == Message_Type_Hello);
+ attr = find_attr(pack, Host_Name);
+ CHECK(attr && attr->length == (int)strlen(host_name));
+ CHECK(attr && strcmp(attr->val.string, host_name) == 0);
+ attr = find_attr(pack, Assigned_Tunnel_ID);
+ CHECK(attr && attr->val.uint16 == 0x1234);
+ attr = find_attr(pack, Call_Serial_Number);
+ CHECK(attr && attr->val.uint32 == 0x89abcdef);
+ attr = find_attr(pack, Tie_Breaker);
+ CHECK(attr && attr->val.uint64 == 0x0123456789abcdefULL);
+
+ l2tp_packet_free(pack);
+}
+
+/*
+ * A hidden AVP deciphered with the wrong secret yields a random length
+ * prefix. Whatever it is, the parser must not read outside the AVP.
+ */
+static void test_wrong_secret(void)
+{
+ struct l2tp_packet_t *pack;
+ struct pktbuf pkt;
+ uint8_t buf[1024];
+ size_t len, indx;
+ int ret;
+
+ pack = l2tp_packet_alloc(2, Message_Type_Hello, &sock_addr, 1,
+ secret, sizeof(secret) - 1);
+ CHECK(pack != NULL);
+ if (!pack)
+ return;
+
+ CHECK(l2tp_packet_add_string(pack, Host_Name, "hidden", 1) == 0);
+ CHECK(l2tp_packet_send(sock, pack) == 0);
+ l2tp_packet_free(pack);
+
+ len = recv(sock, buf, sizeof(buf), 0);
+ CHECK(len > 0);
+
+ /* Same bytes on the wire, every other secret at the receiving end */
+ for (indx = 0; indx < 64; ++indx) {
+ char wrong[8];
+
+ snprintf(wrong, sizeof(wrong), "wrong%02zu", indx);
+ memcpy(pkt.data, buf, len);
+ pkt.len = len;
+
+ if (sendto(sock, pkt.data, pkt.len, 0,
+ (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) {
+ perror("sendto");
+ exit(1);
+ }
+ pack = NULL;
+ ret = l2tp_recv(sock, &pack, NULL, wrong, strlen(wrong));
+ CHECK(ret == 0);
+ if (pack) {
+ /* Accepting is fine (the random prefix may happen to be
+ plausible), reading out of the AVP is not */
+ const struct l2tp_attr_t *attr = find_attr(pack, Host_Name);
+
+ CHECK(!attr || attr->length <= (int)len);
+ l2tp_packet_free(pack);
+ }
+ }
+}
+
+int main(void)
+{
+ loopback_socket();
+
+ test_hidden_avp_length_prefix();
+ test_hidden_avp_prerequisites();
+ test_roundtrip(0);
+ test_roundtrip(1);
+ test_wrong_secret();
+
+ close(sock);
+
+ if (failures) {
+ fprintf(stderr, "%d failure(s)\n", failures);
+ return 1;
+ }
+
+ printf("all tests passed\n");
+
+ return 0;
+}