Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/openvpn/dco.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,15 @@ int open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev);
void close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx);

/**
* Read data from the DCO communication channel (i.e. a control packet)
* Read and process DCO messages from the kernel.
*
* Each message is processed immediately inside the netlink callback
* to prevent message loss when multiple notifications arrive simultaneously.
*
* @param dco the DCO context
* @return 0 on success or a negative error code otherwise
*/
int dco_do_read(dco_context_t *dco);
int dco_read_and_process(dco_context_t *dco);

/**
* Install a DCO in the main event loop
Expand Down Expand Up @@ -301,7 +304,7 @@ close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx)
}

static inline int
dco_do_read(dco_context_t *dco)
dco_read_and_process(dco_context_t *dco)
{
ASSERT(false);
return 0;
Expand Down
2 changes: 1 addition & 1 deletion src/openvpn/dco_freebsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ dco_set_peer(dco_context_t *dco, unsigned int peerid,
}

int
dco_do_read(dco_context_t *dco)
dco_read_and_process(dco_context_t *dco)
{
struct ifdrv drv;
uint8_t buf[4096];
Expand Down
149 changes: 118 additions & 31 deletions src/openvpn/dco_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,20 @@
nla_nest_start(_msg, (_type) | NLA_F_NESTED)

static int ovpn_get_mcast_id(dco_context_t *dco);
static int ovpn_handle_msg(struct nl_msg *msg, void *arg);
static int mcast_family_handler(struct nl_msg *msg, void *arg);
static int dco_parse_peer_multi(struct nl_msg *msg, void *arg);
static int dco_parse_peer(struct nl_msg *msg, void *arg);

/* Forward declarations for immediate processing in callback */
void multi_process_incoming_dco(dco_context_t *dco);
void process_incoming_dco(dco_context_t *dco);

void dco_check_key_ctx(const struct key_ctx_bi *key);

typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg);
/* Lock to prevent recursive stats requests during message processing.
* When set, dco_get_peer_stats() returns early to avoid NLE_BUSY errors. */
static bool __is_locked = false;

/**
* @brief resolves the netlink ID for ovpn-dco
Expand Down Expand Up @@ -131,7 +141,9 @@ ovpn_dco_nlmsg_create(dco_context_t *dco, enum ovpn_nl_commands cmd)
static int
ovpn_nl_recvmsgs(dco_context_t *dco, const char *prefix)
{
__is_locked = true;
int ret = nl_recvmsgs(dco->nl_sock, dco->nl_cb);
__is_locked = false;

switch (ret)
{
Expand Down Expand Up @@ -167,23 +179,22 @@ ovpn_nl_recvmsgs(dco_context_t *dco, const char *prefix)
}

/**
* Send a prepared netlink message and registers cb as callback if non-null.
* Send a prepared netlink message.
*
* The callback is permanently registered in ovpn_dco_init_netlink() and routes
* messages to appropriate handlers based on type.
*
* The method will also free nl_msg
* @param dco The dco context to use
* @param nl_msg the message to use
* @param cb An optional callback if the caller expects an answer
* @param cb_arg An optional param to pass to the callback
* @param prefix A prefix to report in the error message to give the user context
* @return status of sending the message
*/
static int
ovpn_nl_msg_send(dco_context_t *dco, struct nl_msg *nl_msg, ovpn_nl_cb cb,
void *cb_arg, const char *prefix)
ovpn_nl_msg_send(dco_context_t *dco, struct nl_msg *nl_msg, const char *prefix)
{
dco->status = 1;

nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, cb, cb_arg);
/* Callback permanently set in ovpn_dco_init_netlink() - don't register here */
nl_send_auto(dco->nl_sock, nl_msg);

while (dco->status == 1)
Expand Down Expand Up @@ -274,7 +285,7 @@ dco_new_peer(dco_context_t *dco, unsigned int peerid, int sd,
}
nla_nest_end(nl_msg, attr);

ret = ovpn_nl_msg_send(dco, nl_msg, NULL, NULL, __func__);
ret = ovpn_nl_msg_send(dco, nl_msg, __func__);

nla_put_failure:
nlmsg_free(nl_msg);
Expand Down Expand Up @@ -379,6 +390,14 @@ ovpn_dco_init_netlink(dco_context_t *dco)
nl_cb_set(dco->nl_cb, NL_CB_ACK, NL_CB_CUSTOM, ovpn_nl_cb_finish,
&dco->status);

/* Store NLCTRL family ID for message routing in dispatcher */
dco->ctrlid = genl_ctrl_resolve(dco->nl_sock, "nlctrl");

/* Register permanent callback - all messages route through ovpn_handle_msg.
* This prevents message loss when async notifications (like DEL_PEER) arrive
* while a different callback would otherwise be registered for stats requests. */
nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, dco);

/* The async PACKET messages confuse libnl and it will drop them with
* wrong sequence numbers (NLE_SEQ_MISMATCH), so disable libnl's sequence
* number check */
Expand Down Expand Up @@ -495,7 +514,7 @@ dco_swap_keys(dco_context_t *dco, unsigned int peerid)
NLA_PUT_U32(nl_msg, OVPN_SWAP_KEYS_ATTR_PEER_ID, peerid);
nla_nest_end(nl_msg, attr);

ret = ovpn_nl_msg_send(dco, nl_msg, NULL, NULL, __func__);
ret = ovpn_nl_msg_send(dco, nl_msg, __func__);

nla_put_failure:
nlmsg_free(nl_msg);
Expand All @@ -519,7 +538,7 @@ dco_del_peer(dco_context_t *dco, unsigned int peerid)
NLA_PUT_U32(nl_msg, OVPN_DEL_PEER_ATTR_PEER_ID, peerid);
nla_nest_end(nl_msg, attr);

ret = ovpn_nl_msg_send(dco, nl_msg, NULL, NULL, __func__);
ret = ovpn_nl_msg_send(dco, nl_msg, __func__);

nla_put_failure:
nlmsg_free(nl_msg);
Expand All @@ -545,7 +564,7 @@ dco_del_key(dco_context_t *dco, unsigned int peerid,
NLA_PUT_U8(nl_msg, OVPN_DEL_KEY_ATTR_KEY_SLOT, slot);
nla_nest_end(nl_msg, attr);

ret = ovpn_nl_msg_send(dco, nl_msg, NULL, NULL, __func__);
ret = ovpn_nl_msg_send(dco, nl_msg, __func__);

nla_put_failure:
nlmsg_free(nl_msg);
Expand Down Expand Up @@ -602,7 +621,7 @@ dco_new_key(dco_context_t *dco, unsigned int peerid, int keyid,

nla_nest_end(nl_msg, attr);

ret = ovpn_nl_msg_send(dco, nl_msg, NULL, NULL, __func__);
ret = ovpn_nl_msg_send(dco, nl_msg, __func__);

nla_put_failure:
nlmsg_free(nl_msg);
Expand Down Expand Up @@ -631,7 +650,7 @@ dco_set_peer(dco_context_t *dco, unsigned int peerid,
keepalive_timeout);
nla_nest_end(nl_msg, attr);

ret = ovpn_nl_msg_send(dco, nl_msg, NULL, NULL, __func__);
ret = ovpn_nl_msg_send(dco, nl_msg, __func__);

nla_put_failure:
nlmsg_free(nl_msg);
Expand Down Expand Up @@ -697,37 +716,61 @@ ovpn_get_mcast_id(dco_context_t *dco)
{
dco->ovpn_dco_mcast_id = -ENOENT;

/* Even though 'nlctrl' is a constant, there seem to be no library
* provided define for it */
int ctrlid = genl_ctrl_resolve(dco->nl_sock, "nlctrl");

struct nl_msg *nl_msg = nlmsg_alloc();
if (!nl_msg)
{
return -ENOMEM;
}

genlmsg_put(nl_msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
/* Use ctrlid stored in dco during init */
genlmsg_put(nl_msg, 0, 0, dco->ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);

int ret = -EMSGSIZE;
NLA_PUT_STRING(nl_msg, CTRL_ATTR_FAMILY_NAME, OVPN_NL_NAME);

ret = ovpn_nl_msg_send(dco, nl_msg, mcast_family_handler, dco, __func__);
ret = ovpn_nl_msg_send(dco, nl_msg, __func__);

nla_put_failure:
nlmsg_free(nl_msg);
return ret;
}

/* This function parses any netlink message sent by ovpn-dco to userspace */
/**
* Central message dispatcher for all netlink messages.
*
* This function is registered as the permanent NL_CB_VALID callback and routes
* messages to appropriate handlers based on type. This prevents message loss
* when async notifications (like DEL_PEER) arrive during other operations.
*/
static int
ovpn_handle_msg(struct nl_msg *msg, void *arg)
{
dco_context_t *dco = arg;
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct genlmsghdr *gnlh = nlmsg_data(nlh);

struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
/* Route NLCTRL messages to mcast handler */
if (nlh->nlmsg_type == dco->ctrlid)
{
return mcast_family_handler(msg, dco);
}

/* Route GET_PEER responses to stats handlers */
if (gnlh->cmd == OVPN_CMD_GET_PEER)
{
if (dco->m)
{
return dco_parse_peer_multi(msg, dco);
}
else if (dco->c)
{
return dco_parse_peer(msg, dco);
}
return NL_SKIP;
}

/* Handle async notifications (DEL_PEER, etc.) */
struct nlattr *attrs[OVPN_ATTR_MAX + 1];
struct nlmsghdr *nlh = nlmsg_hdr(msg);

if (!genlmsg_valid_hdr(nlh, 0))
{
Expand Down Expand Up @@ -813,15 +856,27 @@ ovpn_handle_msg(struct nl_msg *msg, void *arg)
return NL_SKIP;
}

/* Process each message immediately to prevent data loss when multiple
* messages arrive simultaneously. Previously, storing in dco fields and
* processing later caused only the last message to be handled. */
if (dco->c && dco->c->mode == CM_TOP)
{
multi_process_incoming_dco(dco);
}
else if (dco->c)
{
process_incoming_dco(dco);
}

return NL_OK;
}

int
dco_do_read(dco_context_t *dco)
dco_read_and_process(dco_context_t *dco)
{
msg(D_DCO_DEBUG, __func__);
nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, dco);

/* Callback permanently registered in ovpn_dco_init_netlink().
* Each message is processed immediately inside ovpn_handle_msg(). */
return ovpn_nl_recvmsgs(dco, __func__);
}

Expand Down Expand Up @@ -877,14 +932,22 @@ dco_update_peer_stat(struct context_2 *c2, struct nlattr *tb[], uint32_t id)
}
}

int
static int
dco_parse_peer_multi(struct nl_msg *msg, void *arg)
{
dco_context_t *dco = arg;
struct multi_context *m = dco->m;
struct nlattr *tb[OVPN_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

msg(D_DCO_DEBUG, "%s: parsing message...", __func__);

if (!m)
{
msg(D_DCO_DEBUG, "%s: no multi context set", __func__);
return NL_SKIP;
}

nla_parse(tb, OVPN_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);

Expand All @@ -905,7 +968,6 @@ dco_parse_peer_multi(struct nl_msg *msg, void *arg)
return NL_SKIP;
}

struct multi_context *m = arg;
uint32_t peer_id = nla_get_u32(tb_peer[OVPN_GET_PEER_RESP_ATTR_PEER_ID]);

if (peer_id >= m->max_clients || !m->instances[peer_id])
Expand All @@ -925,11 +987,20 @@ dco_get_peer_stats_multi(dco_context_t *dco, struct multi_context *m)
{
msg(D_DCO_DEBUG, "%s", __func__);

/* Skip stats request if we're processing DCO messages to avoid recursion */
if (__is_locked)
{
return 0;
}

struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_GET_PEER);

nlmsg_hdr(nl_msg)->nlmsg_flags |= NLM_F_DUMP;

int ret = ovpn_nl_msg_send(dco, nl_msg, dco_parse_peer_multi, m, __func__);
/* Store context for dispatcher to route to dco_parse_peer_multi */
dco->m = m;
int ret = ovpn_nl_msg_send(dco, nl_msg, __func__);
dco->m = NULL;

nlmsg_free(nl_msg);
return ret;
Expand All @@ -938,12 +1009,19 @@ dco_get_peer_stats_multi(dco_context_t *dco, struct multi_context *m)
static int
dco_parse_peer(struct nl_msg *msg, void *arg)
{
struct context *c = arg;
dco_context_t *dco = arg;
struct context *c = dco->c;
struct nlattr *tb[OVPN_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

msg(D_DCO_DEBUG, "%s: parsing message...", __func__);

if (!c)
{
msg(D_DCO_DEBUG, "%s: no context set", __func__);
return NL_SKIP;
}

nla_parse(tb, OVPN_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);

Expand Down Expand Up @@ -987,6 +1065,12 @@ dco_get_peer_stats(struct context *c)
return 0;
}

/* Skip stats request if we're processing DCO messages to avoid recursion */
if (__is_locked)
{
return 0;
}

dco_context_t *dco = &c->c1.tuntap->dco;
struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_GET_PEER);
struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_GET_PEER);
Expand All @@ -995,7 +1079,10 @@ dco_get_peer_stats(struct context *c)
NLA_PUT_U32(nl_msg, OVPN_GET_PEER_ATTR_PEER_ID, peer_id);
nla_nest_end(nl_msg, attr);

ret = ovpn_nl_msg_send(dco, nl_msg, dco_parse_peer, c, __func__);
/* Store context for dispatcher to route to dco_parse_peer */
dco->c = c;
ret = ovpn_nl_msg_send(dco, nl_msg, __func__);
dco->c = NULL;

nla_put_failure:
nlmsg_free(nl_msg);
Expand Down
8 changes: 8 additions & 0 deletions src/openvpn/dco_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
typedef enum ovpn_key_slot dco_key_slot_t;
typedef enum ovpn_cipher_alg dco_cipher_t;

/* Forward declarations for callback context */
struct multi_context;
struct context;

typedef struct
{
Expand All @@ -47,6 +50,7 @@ typedef struct

int ovpn_dco_id;
int ovpn_dco_mcast_id;
int ctrlid; /* NLCTRL family ID for message routing */

unsigned int ifindex;

Expand All @@ -55,6 +59,10 @@ typedef struct
int dco_del_peer_reason;
uint64_t dco_read_bytes;
uint64_t dco_write_bytes;

/* Callback context for stats handlers */
struct multi_context *m; /* For multi-peer stats */
struct context *c; /* For single-peer stats */
} dco_context_t;

#endif /* defined(ENABLE_DCO) && defined(TARGET_LINUX) */
Expand Down
Loading