Skip to content

Commit 7791f53

Browse files
ralflicicron2
authored andcommitted
dco: process messages immediately after read
Currently, reading and processing of incoming DCO messages are decoupled: notifications are read, parsed, and the relevant information is stored in fields of dco_context_t for later processing (with the only exception being stats). This approach is problematic on Linux, since libnl does not allow reading a single netlink message at a time, which can result in loss of information when multiple notifications are available. This change adopts a read -> parse -> process paradigm. On Linux, processing is now invoked directly from within the parsing callback, which libnl calls for each received netlink packet. The other interfaces are adapted accordingly to unify the processing model across all platforms. On Linux, however, a DEL_PEER notification from the kernel triggers a GET_PEER request from userspace, which clutters the netlink communication logic and can lead to errors or even process exit when multiple simultaneous DEL_PEER notifications are received. To avoid this, introduce a lock that prevents requesting stats while we are still busy parsing other messages. Reported-by: Stefan Baranoff <[email protected]> Github: #900 Github: #918 Github: fixes #919 Change-Id: Iefc251cb4483c0b9fb9d6a5207db4445cd884d52 Signed-off-by: Ralf Lici <[email protected]> Acked-by: Gert Doering <[email protected]> Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1403 Message-Id: <[email protected]> URL: https://www.mail-archive.com/[email protected]/msg34785.html Signed-off-by: Gert Doering <[email protected]>
1 parent 0effd6c commit 7791f53

File tree

9 files changed

+88
-44
lines changed

9 files changed

+88
-44
lines changed

src/openvpn/dco.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,13 @@ int open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev);
127127
void close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx);
128128

129129
/**
130-
* Read data from the DCO communication channel (i.e. a control packet)
130+
* Read and process data from the DCO communication channel
131+
* (i.e. a control packet)
131132
*
132133
* @param dco the DCO context
133134
* @return 0 on success or a negative error code otherwise
134135
*/
135-
int dco_do_read(dco_context_t *dco);
136+
int dco_read_and_process(dco_context_t *dco);
136137

137138
/**
138139
* Install a DCO in the main event loop
@@ -305,7 +306,7 @@ close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx)
305306
}
306307

307308
static inline int
308-
dco_do_read(dco_context_t *dco)
309+
dco_read_and_process(dco_context_t *dco)
309310
{
310311
ASSERT(false);
311312
return 0;

src/openvpn/dco_freebsd.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ dco_update_peer_stat(struct multi_context *m, uint32_t peerid, const nvlist_t *n
578578
}
579579

580580
int
581-
dco_do_read(dco_context_t *dco)
581+
dco_read_and_process(dco_context_t *dco)
582582
{
583583
struct ifdrv drv;
584584
uint8_t buf[4096];
@@ -684,11 +684,21 @@ dco_do_read(dco_context_t *dco)
684684

685685
default:
686686
msg(M_WARN, "%s: unknown kernel notification %d", __func__, type);
687+
dco->dco_message_type = 0;
687688
break;
688689
}
689690

690691
nvlist_destroy(nvl);
691692

693+
if (dco->c->mode == CM_TOP)
694+
{
695+
multi_process_incoming_dco(dco);
696+
}
697+
else
698+
{
699+
process_incoming_dco(dco);
700+
}
701+
692702
return 0;
693703
}
694704

src/openvpn/dco_linux.c

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@
4949
#include <netlink/genl/family.h>
5050
#include <netlink/genl/ctrl.h>
5151

52+
/* When parsing multiple DEL_PEER notifications, openvpn tries to request stats
53+
* for each DEL_PEER message (see setenv_stats). This triggers a GET_PEER
54+
* request-reply while we are still parsing the rest of the initial
55+
* notifications, which can lead to NLE_BUSY or even NLE_NOMEM.
56+
*
57+
* This basic lock ensures we don't bite our own tail by issuing a dco_get_peer
58+
* while still busy receiving and parsing other messages.
59+
*/
60+
static bool __is_locked = false;
5261

5362
/* libnl < 3.5.0 does not set the NLA_F_NESTED on its own, therefore we
5463
* have to explicitly do it to prevent the kernel from failing upon
@@ -127,7 +136,9 @@ ovpn_dco_nlmsg_create(dco_context_t *dco, uint8_t cmd)
127136
static int
128137
ovpn_nl_recvmsgs(dco_context_t *dco, const char *prefix)
129138
{
139+
__is_locked = true;
130140
int ret = nl_recvmsgs(dco->nl_sock, dco->nl_cb);
141+
__is_locked = false;
131142

132143
switch (ret)
133144
{
@@ -1094,29 +1105,34 @@ ovpn_handle_msg(struct nl_msg *msg, void *arg)
10941105
* message, that stores the type-specific attributes.
10951106
*
10961107
* the "dco" object is then filled accordingly with the information
1097-
* retrieved from the message, so that the rest of the OpenVPN code can
1098-
* react as need be.
1108+
* retrieved from the message, so that *process_incoming_dco can react
1109+
* as need be.
10991110
*/
1111+
int ret;
11001112
switch (gnlh->cmd)
11011113
{
11021114
case OVPN_CMD_PEER_GET:
11031115
{
1116+
/* return directly, there are no messages to pass to *process_incoming_dco() */
11041117
return ovpn_handle_peer(dco, attrs);
11051118
}
11061119

11071120
case OVPN_CMD_PEER_DEL_NTF:
11081121
{
1109-
return ovpn_handle_peer_del_ntf(dco, attrs);
1122+
ret = ovpn_handle_peer_del_ntf(dco, attrs);
1123+
break;
11101124
}
11111125

11121126
case OVPN_CMD_PEER_FLOAT_NTF:
11131127
{
1114-
return ovpn_handle_peer_float_ntf(dco, attrs);
1128+
ret = ovpn_handle_peer_float_ntf(dco, attrs);
1129+
break;
11151130
}
11161131

11171132
case OVPN_CMD_KEY_SWAP_NTF:
11181133
{
1119-
return ovpn_handle_key_swap_ntf(dco, attrs);
1134+
ret = ovpn_handle_key_swap_ntf(dco, attrs);
1135+
break;
11201136
}
11211137

11221138
default:
@@ -1125,11 +1141,25 @@ ovpn_handle_msg(struct nl_msg *msg, void *arg)
11251141
return NL_STOP;
11261142
}
11271143

1144+
if (ret != NL_OK)
1145+
{
1146+
return ret;
1147+
}
1148+
1149+
if (dco->c->mode == CM_TOP)
1150+
{
1151+
multi_process_incoming_dco(dco);
1152+
}
1153+
else
1154+
{
1155+
process_incoming_dco(dco);
1156+
}
1157+
11281158
return NL_OK;
11291159
}
11301160

11311161
int
1132-
dco_do_read(dco_context_t *dco)
1162+
dco_read_and_process(dco_context_t *dco)
11331163
{
11341164
msg(D_DCO_DEBUG, __func__);
11351165

@@ -1141,6 +1171,12 @@ dco_get_peer(dco_context_t *dco, int peer_id, const bool raise_sigusr1_on_err)
11411171
{
11421172
ASSERT(dco);
11431173

1174+
if (__is_locked)
1175+
{
1176+
msg(D_DCO_DEBUG, "%s: cannot request peer stats while parsing other messages", __func__);
1177+
return 0;
1178+
}
1179+
11441180
/* peer_id == -1 means "dump all peers", but this is allowed in MP mode only.
11451181
* If it happens in P2P mode it means that the DCO peer was deleted and we
11461182
* can simply bail out

src/openvpn/dco_win.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ dco_handle_overlapped_success(dco_context_t *dco, bool queued)
690690
}
691691

692692
int
693-
dco_do_read(dco_context_t *dco)
693+
dco_read_and_process(dco_context_t *dco)
694694
{
695695
if (dco->ifmode != DCO_MODE_MP)
696696
{
@@ -727,6 +727,15 @@ dco_do_read(dco_context_t *dco)
727727
break;
728728
}
729729

730+
if (dco->c->mode == CM_TOP)
731+
{
732+
multi_process_incoming_dco(dco);
733+
}
734+
else
735+
{
736+
process_incoming_dco(dco);
737+
}
738+
730739
return 0;
731740
}
732741

src/openvpn/forward.c

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,19 +1243,11 @@ extract_dco_float_peer_addr(const sa_family_t socket_family, struct openvpn_sock
12431243
}
12441244
}
12451245

1246-
static void
1247-
process_incoming_dco(struct context *c)
1246+
void
1247+
process_incoming_dco(dco_context_t *dco)
12481248
{
12491249
#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
1250-
dco_context_t *dco = &c->c1.tuntap->dco;
1251-
1252-
dco_do_read(dco);
1253-
1254-
/* no message for us to handle - platform specific code has logged details */
1255-
if (dco->dco_message_type == 0)
1256-
{
1257-
return;
1258-
}
1250+
struct context *c = dco->c;
12591251

12601252
/* FreeBSD currently sends us removal notifcation with the old peer-id in
12611253
* p2p mode with the ping timeout reason, so ignore that one to not shoot
@@ -2369,7 +2361,7 @@ process_io(struct context *c, struct link_socket *sock)
23692361
{
23702362
if (!IS_SIG(c))
23712363
{
2372-
process_incoming_dco(c);
2364+
dco_read_and_process(&c->c1.tuntap->dco);
23732365
}
23742366
}
23752367
}

src/openvpn/forward.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@ void process_incoming_link_part2(struct context *c, struct link_socket_info *lsi
209209
void extract_dco_float_peer_addr(sa_family_t socket_family, struct openvpn_sockaddr *out_osaddr,
210210
const struct sockaddr *float_sa);
211211

212+
/**
213+
* Process an incoming DCO message (from kernel space).
214+
*
215+
* @param dco - Pointer to the structure representing the DCO context.
216+
*/
217+
void process_incoming_dco(dco_context_t *dco);
218+
212219
/**
213220
* Write a packet to the external network interface.
214221
* @ingroup external_multiplexer

src/openvpn/multi.c

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3263,14 +3263,12 @@ process_incoming_del_peer(struct multi_context *m, struct multi_instance *mi, dc
32633263
multi_signal_instance(m, mi, SIGTERM);
32643264
}
32653265

3266-
bool
3267-
multi_process_incoming_dco(struct multi_context *m)
3266+
void
3267+
multi_process_incoming_dco(dco_context_t *dco)
32683268
{
3269-
dco_context_t *dco = &m->top.c1.tuntap->dco;
3270-
3271-
struct multi_instance *mi = NULL;
3269+
ASSERT(dco->c->multi);
32723270

3273-
int ret = dco_do_read(&m->top.c1.tuntap->dco);
3271+
struct multi_context *m = dco->c->multi;
32743272

32753273
int peer_id = dco->dco_message_peer_id;
32763274

@@ -3279,12 +3277,12 @@ multi_process_incoming_dco(struct multi_context *m)
32793277
*/
32803278
if (peer_id < 0)
32813279
{
3282-
return ret > 0;
3280+
return;
32833281
}
32843282

32853283
if ((peer_id < m->max_clients) && (m->instances[peer_id]))
32863284
{
3287-
mi = m->instances[peer_id];
3285+
struct multi_instance *mi = m->instances[peer_id];
32883286
set_prefix(mi);
32893287
if (dco->dco_message_type == OVPN_CMD_DEL_PEER)
32903288
{
@@ -3325,11 +3323,6 @@ multi_process_incoming_dco(struct multi_context *m)
33253323
"type %d, del_peer_reason %d",
33263324
peer_id, dco->dco_message_type, dco->dco_del_peer_reason);
33273325
}
3328-
3329-
dco->dco_message_type = 0;
3330-
dco->dco_message_peer_id = -1;
3331-
dco->dco_del_peer_reason = -1;
3332-
return ret > 0;
33333326
}
33343327
#endif /* if defined(ENABLE_DCO) */
33353328

@@ -4462,4 +4455,4 @@ multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,
44624455

44634456
return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits,
44644457
dest));
4465-
}
4458+
}

src/openvpn/multi.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,9 @@ bool multi_process_post(struct multi_context *m, struct multi_instance *mi,
305305
/**
306306
* Process an incoming DCO message (from kernel space).
307307
*
308-
* @param m - The single \c multi_context structure.
309-
*
310-
* @return
311-
* - True, if the message was received correctly.
312-
* - False, if there was an error while reading the message.
308+
* @param dco - Pointer to the structure representing the DCO context.
313309
*/
314-
bool multi_process_incoming_dco(struct multi_context *m);
310+
void multi_process_incoming_dco(dco_context_t *dco);
315311

316312
/**************************************************************************/
317313
/**

src/openvpn/multi_io.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ multi_io_process_io(struct multi_context *m)
505505
/* incoming data on DCO? */
506506
else if (e->arg == MULTI_IO_DCO)
507507
{
508-
multi_process_incoming_dco(m);
508+
dco_read_and_process(&m->top.c1.tuntap->dco);
509509
}
510510
#endif
511511
/* signal received? */

0 commit comments

Comments
 (0)