[Openvpn-devel] [L] Change in openvpn[master]: oob: Add control message TLV encoding (P_CONTROL_OOB_V1)
"stipa \(Code Review\) via Openvpn-devel" <[email protected]> Wed, 29 Jul 2026 12:22:54 +0000
| Newsgroups | net.sourceforge.lists.openvpn-devel |
|---|---|
| Message-ID | <e7c816b74dcace18a41c2d82544edb04c459e6ce-EmailReplacePatchSet-HTML@gerrit.openvpn.net> |
--===============8141231533105380139==
Content-Transfer-Encoding: 8bit
Content-Disposition: inline
Content-Type: multipart/alternative; boundary="mZYurtwNp64="; charset=UTF-8
--mZYurtwNp64=
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
Attention is currently required from: flichtenheld, plaisthos=2E
Hello fli=
chtenheld, plaisthos,
I'd like you to reexamine a change=2E Please visit
=
http://gerrit=2Eopenvpn=2Enet/c/openvpn/+/1741?usp=3Demail
to look at=
the new patch set (#10)=2E
Change subject: oob: Add control message TLV =
encoding (P_CONTROL_OOB_V1)
=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=
=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=
=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=2E=
=2E=2E=2E=2E=2E
oob: Add control message TLV encoding (P_CONTROL_OOB_V1)
=
Out-of-band control messages carry control data but belong to no session, s=
o a
client can ask a server about itself before connecting=2E Reserve opcod=
e 12 for
them and add the codec for the two messages the probe needs, SERVE=
R_PROBE and
PROBE_REPLY=2E
The spec uses the same TLV framing for all its =
control messages, not just the
out-of-band ones, so the message-type header=
, the TLV header and the TLV scan go
into their own control_msg=2Ec/h; oob=
=2Ec keeps the OOB message and TLV types=2E
P_LAST_OPCODE becomes 12, and =
opcode_valid_in_session() replaces the plain range
check because OOB opcode=
s are answered statelessly and are never legal on an
established session=2E=
See the OOB control message section of the wire protocol specification
(o=
penvpn-rfc PR #30)=2E
Change-Id: I1c8d302ac57c5603d622a7be14be369437388268=
Signed-off-by: Lev Stipakov <lev@openvpn=2Enet>
---
M CMakeLists=2Etxt
M s=
rc/openvpn/Makefile=2Eam
A src/openvpn/control_msg=2Ec
A src/openvpn/contro=
l_msg=2Eh
A src/openvpn/oob=2Ec
A src/openvpn/oob=2Eh
M src/openvpn/ssl=2Ec=
M src/openvpn/ssl_pkt=2Ec
M src/openvpn/ssl_pkt=2Eh
M tests/unit_tests/ope=
nvpn/Makefile=2Eam
A tests/unit_tests/openvpn/test_oob=2Ec
11 files changed=
, 739 insertions(+), 4 deletions(-)
git pull ssh://gerrit=2Eopenvpn=2En=
et:29418/openvpn refs/changes/41/1741/10
diff --git a/CMakeLists=2Etxt b/C=
MakeLists=2Etxt
index 7473f15=2E=2Ef4b1b22 100644
--- a/CMakeLists=2Etxt
++=
+ b/CMakeLists=2Etxt
@@ -444,6 +444,8 @@
src/openvpn/console=2Ec
=
src/openvpn/console_builtin=2Ec
src/openvpn/console=2Eh
+ src/openv=
pn/control_msg=2Ec
+ src/openvpn/control_msg=2Eh
src/openvpn/crypto=
=2Ec
src/openvpn/crypto=2Eh
src/openvpn/crypto_backend=2Eh
@@ -52=
0,6 +522,8 @@
src/openvpn/multi_io=2Ec
src/openvpn/occ=2Ec
s=
rc/openvpn/occ=2Eh
+ src/openvpn/oob=2Ec
+ src/openvpn/oob=2Eh
s=
rc/openvpn/openvpn=2Ec
src/openvpn/openvpn=2Eh
src/openvpn/openvp=
n_win32_resources=2Erc
@@ -667,6 +671,7 @@
"test_mbuf"
"t=
est_misc"
"test_ncp"
+ "test_oob"
"test_options_pa=
rse"
"test_packet_id"
"test_pkt"
@@ -858,6 +863,13 @@
=
src/openvpn/session_id=2Ec
)
+ target_sources(test_oob =
PRIVATE
+ tests/unit_tests/openvpn/mock_get_random=2Ec
+ src/=
openvpn/control_msg=2Ec
+ src/openvpn/oob=2Ec
+ src/openvpn/s=
ession_id=2Ec
+ )
+
target_sources(test_pkt PRIVATE
te=
sts/unit_tests/openvpn/mock_win32_execve=2Ec
src/openvpn/argv=2Ec
=
diff --git a/src/openvpn/Makefile=2Eam b/src/openvpn/Makefile=2Eam
index ff=
8cc54=2E=2Ea67d478 100644
--- a/src/openvpn/Makefile=2Eam
+++ b/src/openvpn=
/Makefile=2Eam
@@ -52,6 +52,7 @@
common=2Eh \
comp=2Ec comp=2Eh compstu=
b=2Ec \
comp-lz4=2Ec comp-lz4=2Eh \
+ control_msg=2Ec control_msg=2Eh \
=
crypto=2Ec crypto=2Eh crypto_backend=2Eh \
crypto_openssl=2Ec crypto_ope=
nssl=2Eh \
crypto_mbedtls_legacy=2Ec crypto_mbedtls_legacy=2Eh \
@@ -106,=
6 +107,7 @@
pkcs11=2Ec pkcs11=2Eh pkcs11_backend=2Eh \
pkcs11_openssl=
=2Ec \
pkcs11_mbedtls=2Ec \
+ oob=2Ec oob=2Eh \
openvpn=2Ec openvpn=2Eh=
\
options=2Ec options=2Eh \
options_util=2Ec options_util=2Eh \
diff -=
-git a/src/openvpn/control_msg=2Ec b/src/openvpn/control_msg=2Ec
new file m=
ode 100644
index 0000000=2E=2E95ff210
--- /dev/null
+++ b/src/openvpn/contr=
ol_msg=2Ec
@@ -0,0 +1,90 @@
+/*
+ * OpenVPN -- An application to securely =
tunnel IP networks
+ * over a single TCP/UDP port, with support=
for SSL/TLS-based
+ * session authentication and key exchange,=
+ * packet encryption, packet authentication, and
+ * =
packet compression=2E
+ *
+ * Copyright (C) 2002-2026 OpenVPN Inc <sal=
es@openvpn=2Enet>
+ *
+ * This program is free software; you can redistrib=
ute it and/or modify
+ * it under the terms of the GNU General Public Lice=
nse version 2
+ * as published by the Free Software Foundation=2E
+ *
+ * =
This program is distributed in the hope that it will be useful,
+ * but W=
ITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABIL=
ITY or FITNESS FOR A PARTICULAR PURPOSE=2E See the
+ * GNU General Public=
License for more details=2E
+ *
+ * You should have received a copy of th=
e GNU General Public License along
+ * with this program; if not, see <htt=
ps://www=2Egnu=2Eorg/licenses/>=2E
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "=
config=2Eh"
+#endif
+
+#include "syshead=2Eh"
+
+#include "control_msg=2Eh"=
+
+bool
+ctrl_msg_read_header(struct buffer *buf, uint16_t expected_msg_ty=
pe)
+{
+ int msg_type =3D buf_read_u16(buf);
+ return msg_type >=3D 0=
&& (uint16_t)msg_type =3D=3D expected_msg_type;
+}
+
+bool
+ctrl_msg_tlv_w=
rite_header(struct buffer *buf, uint16_t type, bool optional, uint16_t valu=
e_len)
+{
+ uint16_t field =3D type & CTRL_MSG_TLV_TYPE_MASK;
+ if (o=
ptional)
+ {
+ field |=3D CTRL_MSG_TLV_OPTIONAL_FLAG;
+ }
+ =
return buf_write_u16(buf, field) && buf_write_u16(buf, value_len);
+}
+
+b=
ool
+ctrl_msg_tlv_read_header(struct buffer *buf, struct ctrl_msg_tlv_heade=
r *hdr)
+{
+ int field =3D buf_read_u16(buf);
+ if (field < 0)
+ {=
+ return false;
+ }
+ int len =3D buf_read_u16(buf);
+ if =
(len < 0)
+ {
+ return false;
+ }
+ hdr->type =3D (uint16_t=
)(field & CTRL_MSG_TLV_TYPE_MASK);
+ hdr->optional =3D (field & CTRL_MSG=
_TLV_OPTIONAL_FLAG) !=3D 0;
+ hdr->value_len =3D (uint16_t)len;
+ ret=
urn true;
+}
+
+bool
+ctrl_msg_find_tlv(struct buffer *payload, uint16_t wa=
nted_type, struct buffer *value)
+{
+ struct ctrl_msg_tlv_header hdr;
+ =
while (ctrl_msg_tlv_read_header(payload, &hdr))
+ {
+ /* Take =
the value out of payload=2E This validates that the header's
+ * le=
ngth is really there and advances past it in one step, so the TLV
+ =
* we are looking for and the ones we skip are bounds-checked alike=2E */
+=
uint8_t *v =3D buf_read_alloc(payload, hdr=2Evalue_len);
+ i=
f (!v)
+ {
+ return false;
+ }
+ if (hdr=2E=
type =3D=3D wanted_type)
+ {
+ buf_set_read(value, v, hdr=
=2Evalue_len);
+ return true;
+ }
+ /* not the TLV=
we want: keep scanning */
+ }
+ return false;
+}
diff --git a/src/op=
envpn/control_msg=2Eh b/src/openvpn/control_msg=2Eh
new file mode 100644
in=
dex 0000000=2E=2Ee4c8b54
--- /dev/null
+++ b/src/openvpn/control_msg=2Eh
@@=
-0,0 +1,101 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP ne=
tworks
+ * over a single TCP/UDP port, with support for SSL/TLS=
-based
+ * session authentication and key exchange,
+ * =
packet encryption, packet authentication, and
+ * packet c=
ompression=2E
+ *
+ * Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn=
=2Enet>
+ *
+ * This program is free software; you can redistribute it and=
/or modify
+ * it under the terms of the GNU General Public License versio=
n 2
+ * as published by the Free Software Foundation=2E
+ *
+ * This prog=
ram is distributed in the hope that it will be useful,
+ * but WITHOUT ANY=
WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FIT=
NESS FOR A PARTICULAR PURPOSE=2E See the
+ * GNU General Public License f=
or more details=2E
+ *
+ * You should have received a copy of the GNU Gene=
ral Public License along
+ * with this program; if not, see <https://www=
=2Egnu=2Eorg/licenses/>=2E
+ */
+
+/**
+ * @file
+ * Framing shared by the =
TLV-based control messages of the wire protocol=2E
+ *
+ * Such a message p=
ayload starts with a 16-bit message type, followed by a
+ * sequence of TLV=
entries=2E Each TLV starts with a 4-byte header: a 16-bit field
+ * whose =
most significant bit is the "optional" flag and whose remaining 15 bits
+ *=
are the type, followed by a 16-bit length giving the size of the value tha=
t
+ * follows the header=2E
+ *
+ * The message-type and TLV-type values th=
emselves are specific to the message
+ * family carried (out-of-band messag=
es define theirs in oob=2Eh), so only the
+ * framing lives here=2E
+ */
+
=
+#ifndef CONTROL_MSG_H
+#define CONTROL_MSG_H
+
+#include "buffer=2Eh"
+
+/=
* TLV header bit layout of the first 16-bit field */
+#define CTRL_MSG_TLV_=
OPTIONAL_FLAG 0x8000
+#define CTRL_MSG_TLV_TYPE_MASK 0x7fff
+
+/* The h=
eader every TLV carries: the 15-bit type and optional flag packed into
+ * =
the first 16-bit field, then the length of the value that follows=2E */
+st=
ruct ctrl_msg_tlv_header
+{
+ uint16_t type; /**< the 15-bit TLV ty=
pe */
+ bool optional; /**< value of the optional flag */
+ uint=
16_t value_len; /**< length of the value following the header */
+};
+
+/**=
+ * Read and verify a message-type header from buf, advancing past it=2E
+=
*
+ * @param buf buffer positioned at the message payload
+=
* @param expected_msg_type the message type the payload must carry
+ * @r=
eturn true if a message type was read and equals expected_msg_type,
+ * =
false on a short buffer or a mismatching type=2E
+ */
+bool ctrl_msg_r=
ead_header(struct buffer *buf, uint16_t expected_msg_type);
+
+/**
+ * Writ=
e a TLV header (type + optional flag + value length) to buf=2E
+ *
+ * @ret=
urn true on success, false if buf has insufficient space=2E
+ */
+bool ctrl=
_msg_tlv_write_header(struct buffer *buf, uint16_t type, bool optional,
+ =
uint16_t value_len);
+
+/**
+ * Read a TLV hea=
der from buf, advancing past it=2E
+ *
+ * @param buf buffer positioned at=
the TLV header
+ * @param hdr filled with the type, optional flag and val=
ue length on success
+ * @return true on success, false if there are not en=
ough bytes for a header=2E
+ */
+bool ctrl_msg_tlv_read_header(struct buffe=
r *buf, struct ctrl_msg_tlv_header *hdr);
+
+/**
+ * Scan payload for the f=
irst TLV of type wanted_type, skipping any other (e=2Eg=2E
+ * future) TLV =
types=2E
+ *
+ * On success value covers exactly the found TLV's value byte=
s=2E The length from
+ * each TLV header is validated against payload as th=
e scan goes, so the whole
+ * value is guaranteed to be present; a header c=
laiming more bytes than payload
+ * holds is rejected rather than reported =
as found=2E payload is consumed as it is
+ * read=2E
+ *
+ * @param payload=
buffer positioned at a TLV header
+ * @param wanted_type the TLV typ=
e to look for
+ * @param value set to a buffer covering the found TL=
V's value
+ * @return true if the TLV was found, false if it is not present=
or a TLV
+ * header or value is malformed or truncated=2E
+ */
+bo=
ol ctrl_msg_find_tlv(struct buffer *payload, uint16_t wanted_type, struct b=
uffer *value);
+
+#endif /* ifndef CONTROL_MSG_H */
diff --git a/src/openvp=
n/oob=2Ec b/src/openvpn/oob=2Ec
new file mode 100644
index 0000000=2E=2Ec80=
7171
--- /dev/null
+++ b/src/openvpn/oob=2Ec
@@ -0,0 +1,83 @@
+/*
+ * Open=
VPN -- An application to securely tunnel IP networks
+ * over a=
single TCP/UDP port, with support for SSL/TLS-based
+ * sessio=
n authentication and key exchange,
+ * packet encryption, packe=
t authentication, and
+ * packet compression=2E
+ *
+ * Copyri=
ght (C) 2002-2026 OpenVPN Inc <sales@openvpn=2Enet>
+ *
+ * This program i=
s free software; you can redistribute it and/or modify
+ * it under the te=
rms of the GNU General Public License version 2
+ * as published by the Fr=
ee Software Foundation=2E
+ *
+ * This program is distributed in the hope =
that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the imp=
lied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE=
=2E See the
+ * GNU General Public License for more details=2E
+ *
+ * Y=
ou should have received a copy of the GNU General Public License along
+ * =
with this program; if not, see <https://www=2Egnu=2Eorg/licenses/>=2E
+ */=
+
+#ifdef HAVE_CONFIG_H
+#include "config=2Eh"
+#endif
+
+#include "syshea=
d=2Eh"
+
+#include "oob=2Eh"
+#include "control_msg=2Eh"
+
+bool
+oob_probe=
_parameter_write(struct buffer *buf, const struct oob_probe_parameter *p)
+=
{
+ return ctrl_msg_tlv_write_header(buf, OOB_TLV_PROBE_PARAMETER, false=
, OOB_PROBE_PARAMETER_LEN)
+ && buf_write_u64(buf, p->timestamp)
=
+ && buf_write_u32(buf, p->flags);
+}
+
+bool
+oob_probe_paramete=
r_read(struct buffer *buf, struct oob_probe_parameter *p)
+{
+ /* One bo=
unds check covers the whole value: every field read below then fits
+ *=
by construction (OOB_PROBE_PARAMETER_LEN is the sum of their sizes), so
+ =
* none of them needs its own error handling=2E Trailing bytes this vers=
ion
+ * does not understand are simply left unread=2E */
+ if (buf_l=
en(buf) < OOB_PROBE_PARAMETER_LEN)
+ {
+ return false;
+ }
+ =
p->timestamp =3D buf_read_u64(buf, NULL);
+ p->flags =3D buf_read_u32(=
buf, NULL);
+ return true;
+}
+
+bool
+oob_probe_reply_write(struct buff=
er *buf, const struct oob_probe_reply *r)
+{
+ return ctrl_msg_tlv_write=
_header(buf, OOB_TLV_PROBE_REPLY, false, OOB_PROBE_REPLY_LEN)
+ &=
& session_id_write(&r->peer_session_id, buf)
+ && buf_write_u16(b=
uf, r->priority)
+ && buf_write_u16(buf, r->weight)
+ &=
& buf_write_u16(buf, r->max_latency_diff)
+ && buf_write_u16(buf,=
r->connect_lifetime)
+ && buf_write_u32(buf, r->flags);
+}
+
+bo=
ol
+oob_probe_reply_read(struct buffer *buf, struct oob_probe_reply *r)
+{
=
+ /* One bounds check for the whole value, as in oob_probe_parameter_rea=
d()=2E */
+ if (buf_len(buf) < OOB_PROBE_REPLY_LEN)
+ {
+ retu=
rn false;
+ }
+ session_id_read(&r->peer_session_id, buf);
+ r->pr=
iority =3D (uint16_t)buf_read_u16(buf);
+ r->weight =3D (uint16_t)buf_re=
ad_u16(buf);
+ r->max_latency_diff =3D (uint16_t)buf_read_u16(buf);
+ =
r->connect_lifetime =3D (uint16_t)buf_read_u16(buf);
+ r->flags =3D buf=
_read_u32(buf, NULL);
+ return true;
+}
diff --git a/src/openvpn/oob=2Eh=
b/src/openvpn/oob=2Eh
new file mode 100644
index 0000000=2E=2E0a9a4bb
--- =
/dev/null
+++ b/src/openvpn/oob=2Eh
@@ -0,0 +1,110 @@
+/*
+ * OpenVPN -- A=
n application to securely tunnel IP networks
+ * over a single =
TCP/UDP port, with support for SSL/TLS-based
+ * session authen=
tication and key exchange,
+ * packet encryption, packet authen=
tication, and
+ * packet compression=2E
+ *
+ * Copyright (C) =
2002-2026 OpenVPN Inc <sales@openvpn=2Enet>
+ *
+ * This program is free s=
oftware; you can redistribute it and/or modify
+ * it under the terms of t=
he GNU General Public License version 2
+ * as published by the Free Softw=
are Foundation=2E
+ *
+ * This program is distributed in the hope that it =
will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied war=
ranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE=2E See t=
he
+ * GNU General Public License for more details=2E
+ *
+ * You should =
have received a copy of the GNU General Public License along
+ * with this=
program; if not, see <https://www=2Egnu=2Eorg/licenses/>=2E
+ */
+
+/**
+ =
* @file
+ * Encoding/decoding of out-of-band (P_CONTROL_OOB_V1) control mes=
sages=2E
+ *
+ * An OOB message payload starts with a 16-bit message type (=
the 0x1xx space:
+ * SERVER_PROBE, PROBE_REPLY, =2E=2E=2E) followed by a se=
quence of TLV entries in the
+ * 0x2xx type space=2E The framing itself is =
shared with the other TLV-based
+ * control messages of the wire protocol a=
nd lives in control_msg=2Eh; this file
+ * defines the OOB message and TLV =
types, their values, and the OOB-specific
+ * decisions taken on them=2E
+ =
*/
+
+#ifndef OOB_H
+#define OOB_H
+
+#include "buffer=2Eh"
+#include "sess=
ion_id=2Eh"
+
+/* OOB message types: the 16-bit value at the start of an OO=
B payload, before
+ * its TLV entries=2E Distinct from the TLV-type space (=
0x2xx); see the "Messages"
+ * table in the OOB section of the wire protoco=
l spec=2E */
+#define OOB_MSG_SERVER_PROBE 0x100
+#define OOB_MSG_PROBE_REP=
LY 0x101
+
+/* TLV types (see the OOB control message section of the wire =
protocol spec) */
+#define OOB_TLV_PROBE_PARAMETER 0x200
+#define OOB_TLV_P=
ROBE_REPLY 0x201
+
+/* Minimum on-wire value length (excluding the 4-by=
te TLV header) of each TLV:
+ * the sizes of its fixed fields in wire order=
(see the structs below)=2E The
+ * value may be longer for forward compati=
bility; trailing bytes that are not
+ * understood are ignored on read=2E *=
/
+#define OOB_PROBE_PARAMETER_LEN \
+ ((uint16_t)(sizeof(uint64_t) /* t=
imestamp */ + sizeof(uint32_t) /* flags */))
+#define OOB_PROBE_REPLY_LEN =
\
+ ((uint16_t)(SID_SIZE =
/* peer_session_id */ \
+ + 4 * sizeof(uint16_t) /* priorit=
y, weight, \
+ * max_latency_diff,=
\
+ * connect_lifetime */ \
+ =
+ sizeof(uint32_t))) /* flags */
+
+/* probe parameter TLV (s=
ent by the client in a SERVER_PROBE) */
+struct oob_probe_parameter
+{
+ =
uint64_t timestamp; /**< client clock as a UNIX timestamp */
+ uint32_t=
flags; /**< client capability flags, currently must be 0 */
+};
+
+/* =
probe reply TLV (sent by the server in a PROBE_REPLY) */
+struct oob_probe_=
reply
+{
+ struct session_id peer_session_id; /**< echoes the session id=
of the request */
+ uint16_t priority; /**< DNS-SRV sty=
le priority (lower is preferred) */
+ uint16_t weight; =
/**< DNS-SRV style weight */
+ uint16_t max_latency_diff; /**< =
advertised candidate-band margin in ms;
+ =
* 0 means "defer to the client's setting" */
+ uint16_t connect_=
lifetime; /**< seconds the reply stays valid as the handshake reset=
*/
+ uint32_t flags; /**< server behaviour flags */
=
+};
+
+/**
+ * Write a complete probe parameter TLV (header + value) to buf=
=2E
+ */
+bool oob_probe_parameter_write(struct buffer *buf, const struct o=
ob_probe_parameter *p);
+
+/**
+ * Read a probe parameter TLV value from bu=
f=2E
+ *
+ * buf must cover exactly the TLV's value, as returned by ctrl_ms=
g_find_tlv()=2E
+ * Trailing bytes beyond the fields understood here are ig=
nored, so a longer
+ * value from a future version still parses=2E
+ *
+ * =
@return true on success, false if buf is shorter than the mandatory fields=
=2E
+ */
+bool oob_probe_parameter_read(struct buffer *buf, struct oob_prob=
e_parameter *p);
+
+/**
+ * Write a complete probe reply TLV (header + valu=
e) to buf=2E
+ */
+bool oob_probe_reply_write(struct buffer *buf, const str=
uct oob_probe_reply *r);
+
+/**
+ * Read a probe reply TLV value from buf=
=2E See oob_probe_parameter_read() for the
+ * calling convention=2E
+ */
+=
bool oob_probe_reply_read(struct buffer *buf, struct oob_probe_reply *r);
+=
+#endif /* OOB_H */
diff --git a/src/openvpn/ssl=2Ec b/src/openvpn/ssl=2Ec=
index 60df7ce=2E=2E9e1a6aa 100644
--- a/src/openvpn/ssl=2Ec
+++ b/src/open=
vpn/ssl=2Ec
@@ -3616,7 +3616,7 @@
struct session_id sid; /* remote ses=
sion ID */
/* verify legal opcode */
- if (op < P_FIRST_OPCODE ||=
op > P_LAST_OPCODE)
+ if (!opcode_valid_in_session(op))
{
=
if (op =3D=3D P_CONTROL_HARD_RESET_CLIENT_V1 || op =3D=3D P_CONTROL_HARD_R=
ESET_SERVER_V1)
{
diff --git a/src/openvpn/ssl_pkt=2Ec b/src/openv=
pn/ssl_pkt=2Ec
index f8444451=2E=2E0d882d9 100644
--- a/src/openvpn/ssl_pkt=
=2Ec
+++ b/src/openvpn/ssl_pkt=2Ec
@@ -166,7 +166,7 @@
=
bool prepend_ack)
{
ASSERT(ks->key_id >=3D 0 && ks->key_id <=3D P_KEY=
_ID_MASK);
- ASSERT(opcode >=3D 0 && opcode <=3D P_LAST_OPCODE);
+ AS=
SERT(opcode_valid_in_session(opcode));
uint8_t header =3D (uint8_t)(ks=
->key_id | (opcode << P_OPCODE_SHIFT));
/* Workaround for Softether =
servers=2E Softether has a bug that it only
diff --git a/src/openvpn/ssl_pk=
t=2Eh b/src/openvpn/ssl_pkt=2Eh
index 82cb5b1=2E=2E25e50bf 100644
--- a/src=
/openvpn/ssl_pkt=2Eh
+++ b/src/openvpn/ssl_pkt=2Eh
@@ -58,11 +58,53 @@
* =
like P_CONTROL_HARD_RESET_CLIENT_V3 */
#define P_CONTROL_WKC_V1 11
-/* d=
efine the range of legal opcodes
+/* Out-of-band control message that does =
not belong to an established
+ * control channel session (e=2Eg=2E a server=
probe)=2E Inherently unreliable:
+ * there is no protocol-level retransmis=
sion=2E
+ *
+ * It is not legal on an established session; see opcode_valid=
_in_session()
+ * below=2E */
+#define P_CONTROL_OOB_V1 12
+
+/* define the=
range of defined opcodes, in- and out-of-band=2E Note this is not
+ * the =
set of opcodes legal on an established session; see
+ * opcode_valid_in_ses=
sion()=2E
* Since we do no longer support key-method 1 we consider
* th=
e v1 op codes invalid */
#define P_FIRST_OPCODE 3
-#define P_LAST_OPCODE =
11
+#define P_LAST_OPCODE 12
+
+static inline bool
+opcode_is_oob(int op)
=
+{
+ return op =3D=3D P_CONTROL_OOB_V1;
+}
+
+/**
+ * Return true if op =
may occur on an established control-channel session=2E
+ *
+ * Out-of-band =
opcodes may not=2E They are answered statelessly on the
+ * new-connection =
path, and being rejected here is a permanent property rather
+ * than a han=
dler that is still missing:
+ *
+ * - an OOB message carries its TLV paylo=
ad directly, with no reliability or
+ * ACK fields (see tls_wrap_oob_sta=
ndalone()), whereas the established-session
+ * path parses an ACK array=
and a control packet-id before anything else=2E The
+ * payload is chos=
en by the sender and session ids are plaintext on the wire,
+ * so those=
bytes can be crafted into a valid ACK array, forging ACKs into a
+ * li=
ve control channel and stalling a handshake or rekey=2E
+ * - nothing on t=
hat path parses an OOB payload, so there is nothing to gain by
+ * accep=
ting one=2E
+ *
+ * This cannot be expressed as an opcode range: the inband=
CONTROL_DATA_V1 of the
+ * wire protocol is legal on an established sessio=
n, so the OOB opcodes sit
+ * between legal ones=2E
+ */
+static inline boo=
l
+opcode_valid_in_session(int op)
+{
+ return op >=3D P_FIRST_OPCODE &&=
op <=3D P_LAST_OPCODE && !opcode_is_oob(op);
+}
/*
* Define number of=
buffers for send and receive in the reliability layer=2E
@@ -264,6 +306,9 =
@@
case P_CONTROL_WKC_V1:
return "P_CONTROL_WKC_V1";
=
+ case P_CONTROL_OOB_V1:
+ return "P_CONTROL_OOB_V1";
+=
case P_ACK_V1:
return "P_ACK_V1";
diff --git a/tes=
ts/unit_tests/openvpn/Makefile=2Eam b/tests/unit_tests/openvpn/Makefile=2Ea=
m
index d861ef9=2E=2E611097a 100644
--- a/tests/unit_tests/openvpn/Makefile=
=2Eam
+++ b/tests/unit_tests/openvpn/Makefile=2Eam
@@ -14,6 +14,7 @@
ncp_=
testdriver \
mbuf_testdriver \
misc_testdriver \
+ oob_testdriver \
o=
ptions_parse_testdriver \
packet_id_testdriver \
pkt_testdriver \
@@ -1=
50,6 +151,20 @@
$(top_srcdir)/src/openvpn/win32-util=2Ec \
$(top_srcdir=
)/src/openvpn/session_id=2Ec
+oob_testdriver_CFLAGS =3D \
+ -I$(top_srcd=
ir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \
+ @TES=
T_CFLAGS@
+oob_testdriver_LDFLAGS =3D @TEST_LDFLAGS@
+oob_testdriver_SOURCE=
S =3D test_oob=2Ec \
+ mock_msg=2Ec mock_msg=2Eh test_common=2Eh \
+ mock_g=
et_random=2Ec \
+ $(top_srcdir)/src/openvpn/buffer=2Ec \
+ $(top_srcdir)/sr=
c/openvpn/control_msg=2Ec \
+ $(top_srcdir)/src/openvpn/oob=2Ec \
+ $(top_s=
rcdir)/src/openvpn/platform=2Ec \
+ $(top_srcdir)/src/openvpn/session_id=2E=
c \
+ $(top_srcdir)/src/openvpn/win32-util=2Ec
+
pkt_testdriver_CFLAGS =
=3D \
-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/=
src/openvpn \
@TEST_CFLAGS@
diff --git a/tests/unit_tests/openvpn/test_oo=
b=2Ec b/tests/unit_tests/openvpn/test_oob=2Ec
new file mode 100644
index 00=
00000=2E=2Eaa4eb23
--- /dev/null
+++ b/tests/unit_tests/openvpn/test_oob=2E=
c
@@ -0,0 +1,277 @@
+/*
+ * OpenVPN -- An application to securely tunnel I=
P networks
+ * over a single TCP/UDP port, with support for SSL=
/TLS-based
+ * session authentication and key exchange,
+ * =
packet encryption, packet authentication, and
+ * pack=
et compression=2E
+ *
+ * Copyright (C) 2002-2026 OpenVPN Inc <sales@openv=
pn=2Enet>
+ *
+ * This program is free software; you can redistribute it a=
nd/or modify
+ * it under the terms of the GNU General Public License vers=
ion 2
+ * as published by the Free Software Foundation=2E
+ *
+ * This pr=
ogram is distributed in the hope that it will be useful,
+ * but WITHOUT A=
NY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or F=
ITNESS FOR A PARTICULAR PURPOSE=2E See the
+ * GNU General Public License=
for more details=2E
+ *
+ * You should have received a copy of the GNU Ge=
neral Public License along
+ * with this program; if not, see <https://www=
=2Egnu=2Eorg/licenses/>=2E
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config=
=2Eh"
+#endif
+
+#include "syshead=2Eh"
+
+#include <stdarg=2Eh>
+#include =
<stddef=2Eh>
+#include <setjmp=2Eh>
+#include <cmocka=2Eh>
+
+#include "con=
trol_msg=2Eh"
+#include "oob=2Eh"
+#include "test_common=2Eh"
+
+/* Write a=
probe parameter TLV and read it back; fields must survive the
+ * round tr=
ip and the whole buffer must be consumed=2E */
+static void
+test_probe_par=
ameter_roundtrip(void **state)
+{
+ struct gc_arena gc =3D gc_new();
+ =
struct buffer buf =3D alloc_buf_gc(128, &gc);
+
+ const struct oob_pro=
be_parameter in =3D {
+ =2Etimestamp =3D 0x0123456789abcdefULL,
+ =
=2Eflags =3D 0,
+ };
+ assert_true(oob_probe_parameter_write(&bu=
f, &in));
+ /* header (4) + value (12) */
+ assert_int_equal(BLEN(&bu=
f), 4 + OOB_PROBE_PARAMETER_LEN);
+
+ /* the header codec, read from a c=
opy so the scan below still sees it */
+ struct buffer peek =3D buf;
+ =
struct ctrl_msg_tlv_header hdr;
+ assert_true(ctrl_msg_tlv_read_header=
(&peek, &hdr));
+ assert_int_equal(hdr=2Etype, OOB_TLV_PROBE_PARAMETER);=
+ assert_false(hdr=2Eoptional);
+ assert_int_equal(hdr=2Evalue_len, =
OOB_PROBE_PARAMETER_LEN);
+
+ struct buffer value;
+ assert_true(ctrl=
_msg_find_tlv(&buf, OOB_TLV_PROBE_PARAMETER, &value));
+ assert_int_equa=
l(BLEN(&value), OOB_PROBE_PARAMETER_LEN);
+
+ struct oob_probe_parameter=
out =3D { 0 };
+ assert_true(oob_probe_parameter_read(&value, &out));
+=
assert_true(in=2Etimestamp =3D=3D out=2Etimestamp);
+ assert_int_equ=
al(in=2Eflags, out=2Eflags);
+ /* the scan consumed header and value ali=
ke */
+ assert_int_equal(BLEN(&buf), 0);
+
+ gc_free(&gc);
+}
+
+/* W=
rite a probe reply TLV and read it back=2E */
+static void
+test_probe_repl=
y_roundtrip(void **state)
+{
+ struct gc_arena gc =3D gc_new();
+ str=
uct buffer buf =3D alloc_buf_gc(128, &gc);
+
+ struct oob_probe_reply in=
=3D {
+ =2Epriority =3D 10,
+ =2Eweight =3D 100,
+ =
=2Econnect_lifetime =3D 30,
+ =2Eflags =3D 1,
+ =2Emax_latenc=
y_diff =3D 25,
+ };
+ memcpy(in=2Epeer_session_id=2Eid, "ABCDEFGH", S=
ID_SIZE);
+
+ assert_true(oob_probe_reply_write(&buf, &in));
+ assert=
_int_equal(BLEN(&buf), 4 + OOB_PROBE_REPLY_LEN);
+
+ struct buffer peek =
=3D buf;
+ struct ctrl_msg_tlv_header hdr;
+ assert_true(ctrl_msg_tlv=
_read_header(&peek, &hdr));
+ assert_int_equal(hdr=2Etype, OOB_TLV_PROBE=
_REPLY);
+ assert_int_equal(hdr=2Evalue_len, OOB_PROBE_REPLY_LEN);
+
+ =
struct buffer value;
+ assert_true(ctrl_msg_find_tlv(&buf, OOB_TLV_PRO=
BE_REPLY, &value));
+
+ struct oob_probe_reply out =3D { 0 };
+ asser=
t_true(oob_probe_reply_read(&value, &out));
+ assert_memory_equal(in=2Ep=
eer_session_id=2Eid, out=2Epeer_session_id=2Eid, SID_SIZE);
+ assert_int=
_equal(in=2Epriority, out=2Epriority);
+ assert_int_equal(in=2Eweight, o=
ut=2Eweight);
+ assert_int_equal(in=2Econnect_lifetime, out=2Econnect_li=
fetime);
+ assert_int_equal(in=2Eflags, out=2Eflags);
+ assert_int_eq=
ual(in=2Emax_latency_diff, out=2Emax_latency_diff);
+ assert_int_equal(B=
LEN(&buf), 0);
+
+ gc_free(&gc);
+}
+
+/* The probe reply wire format is=
locked to the spec's field order
+ * (priority, weight, max_latency_diff, =
connect_lifetime, flags), big-endian=2E */
+static void
+test_probe_reply_w=
ire_format(void **state)
+{
+ struct gc_arena gc =3D gc_new();
+ stru=
ct buffer buf =3D alloc_buf_gc(128, &gc);
+
+ struct oob_probe_reply in =
=3D {
+ =2Epriority =3D 10,
+ =2Eweight =3D 100,
+ =2E=
connect_lifetime =3D 30,
+ =2Eflags =3D 1,
+ =2Emax_latency_d=
iff =3D 25,
+ };
+ memcpy(in=2Epeer_session_id=2Eid, "ABCDEFGH", SID_=
SIZE);
+
+ assert_true(oob_probe_reply_write(&buf, &in));
+
+ const u=
int8_t expected[] =3D {
+ 0x02,
+ 0x01, /* TLV type 0x201 (no=
t optional) */
+ 0x00,
+ 0x14, /* TLV value length =3D 20 */
=
+ 'A',
+ 'B',
+ 'C',
+ 'D',
+ 'E',
+ =
'F',
+ 'G',
+ 'H', /* peer_session_id */
+ 0x00,
=
+ 0x0a, /* priority =3D 10 */
+ 0x00,
+ 0x64, /* weigh=
t =3D 100 */
+ 0x00,
+ 0x19, /* max_latency_diff =3D 25 */
+ =
0x00,
+ 0x1e, /* connect_lifetime =3D 30 */
+ 0x00,
+ =
0x00,
+ 0x00,
+ 0x01, /* flags =3D 1 */
+ };
+ a=
ssert_int_equal(BLEN(&buf), sizeof(expected));
+ assert_memory_equal(BPT=
R(&buf), expected, sizeof(expected));
+
+ gc_free(&gc);
+}
+
+/* A TLV w=
ith a longer-than-known value must still parse: the known fields are
+ * re=
ad and the trailing bytes are skipped (forward compatibility)=2E */
+static=
void
+test_probe_parameter_forward_compat(void **state)
+{
+ struct gc_=
arena gc =3D gc_new();
+ struct buffer buf =3D alloc_buf_gc(128, &gc);
+=
+ const uint16_t extended_len =3D OOB_PROBE_PARAMETER_LEN + 4;
+ ass=
ert_true(ctrl_msg_tlv_write_header(&buf, OOB_TLV_PROBE_PARAMETER, false, ex=
tended_len));
+ assert_true(buf_write_u32(&buf, 0)); /* timesta=
mp high */
+ assert_true(buf_write_u32(&buf, 0xdeadbeef)); /* timestamp =
low */
+ assert_true(buf_write_u32(&buf, 0)); /* flags */
+ =
assert_true(buf_write_u32(&buf, 0x11223344)); /* unknown trailing field */
=
+
+ struct buffer value;
+ assert_true(ctrl_msg_find_tlv(&buf, OOB_TL=
V_PROBE_PARAMETER, &value));
+ assert_int_equal(BLEN(&value), extended_l=
en);
+
+ struct oob_probe_parameter out =3D { 0 };
+ assert_true(oob_=
probe_parameter_read(&value, &out));
+ assert_true(out=2Etimestamp =3D=
=3D 0xdeadbeefULL);
+ assert_int_equal(out=2Eflags, 0);
+ /* the unkn=
own trailing field must have been consumed from the payload */
+ assert_=
int_equal(BLEN(&buf), 0);
+
+ gc_free(&gc);
+}
+
+/* A value shorter tha=
n the mandatory fields must be rejected, even when it
+ * holds enough byte=
s for some of the individual fields to read successfully=2E */
+static void=
+test_probe_parameter_too_short(void **state)
+{
+ struct gc_arena gc =
=3D gc_new();
+ struct buffer buf =3D alloc_buf_gc(128, &gc);
+
+ /* =
4 of the 12 mandatory value bytes: too short for the timestamp, but
+ *=
enough for a u32 read to succeed on its own */
+ assert_true(buf_write_=
u32(&buf, 0xdeadbeef));
+
+ struct oob_probe_parameter out =3D { 0 };
+ =
assert_false(oob_probe_parameter_read(&buf, &out));
+
+ gc_free(&gc);=
+}
+
+/* A TLV header claiming more value bytes than the payload holds mus=
t be
+ * rejected by the scan rather than reported as found -- for the TLV =
being
+ * looked for as much as for one that would merely be skipped=2E */
=
+static void
+test_find_tlv_value_truncated(void **state)
+{
+ struct gc=
_arena gc =3D gc_new();
+ struct buffer value;
+
+ /* the wanted TLV =
declares 12 value bytes, only 4 are present */
+ struct buffer buf =3D a=
lloc_buf_gc(128, &gc);
+ assert_true(ctrl_msg_tlv_write_header(&buf, OOB=
_TLV_PROBE_PARAMETER, false,
+ OOB=
_PROBE_PARAMETER_LEN));
+ assert_true(buf_write_u32(&buf, 0xdeadbeef));
=
+ assert_false(ctrl_msg_find_tlv(&buf, OOB_TLV_PROBE_PARAMETER, &value))=
;
+
+ /* same defect on a TLV that would be skipped: the scan must not w=
alk past
+ * the end of the payload looking for the next header */
+ =
struct buffer buf2 =3D alloc_buf_gc(128, &gc);
+ assert_true(ctrl_msg_t=
lv_write_header(&buf2, 0x7ff, false, 64));
+ assert_true(buf_write_u32(&=
buf2, 0));
+ assert_false(ctrl_msg_find_tlv(&buf2, OOB_TLV_PROBE_PARAMET=
ER, &value));
+
+ gc_free(&gc);
+}
+
+/* Reading a TLV header must fail =
when the buffer holds less data than a
+ * complete 4-byte header (empty, o=
r only the type field), rather than read
+ * past the available data=2E */
=
+static void
+test_tlv_header_truncated(void **state)
+{
+ struct gc_are=
na gc =3D gc_new();
+ struct buffer buf =3D alloc_buf_gc(128, &gc);
+
+ =
struct ctrl_msg_tlv_header hdr;
+
+ /* empty buffer */
+ assert_fa=
lse(ctrl_msg_tlv_read_header(&buf, &hdr));
+
+ /* only the type field pr=
esent, no length */
+ assert_true(buf_write_u16(&buf, OOB_TLV_PROBE_PARA=
METER));
+ assert_false(ctrl_msg_tlv_read_header(&buf, &hdr));
+
+ gc=
_free(&gc);
+}
+
+int
+main(void)
+{
+ openvpn_unit_test_setup();
+ c=
onst struct CMUnitTest tests[] =3D {
+ cmocka_unit_test(test_probe_p=
arameter_roundtrip),
+ cmocka_unit_test(test_probe_reply_roundtrip),=
+ cmocka_unit_test(test_probe_reply_wire_format),
+ cmocka_u=
nit_test(test_probe_parameter_forward_compat),
+ cmocka_unit_test(te=
st_probe_parameter_too_short),
+ cmocka_unit_test(test_find_tlv_valu=
e_truncated),
+ cmocka_unit_test(test_tlv_header_truncated),
+ };=
+
+ return cmocka_run_group_tests_name("oob tests", tests, NULL, NULL);=
+}
--
To view, visit http://gerrit=2Eopenvpn=2Enet/c/openvpn/+/1741?usp=
=3Demail
To unsubscribe, or for help writing mail filters, visit http://ger=
rit=2Eopenvpn=2Enet/settings?usp=3Demail
Gerrit-MessageType: newpatchset
G=
errit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I1c8d302ac57=
c5603d622a7be14be369437388268
Gerrit-Change-Number: 1741
Gerrit-PatchSet: 1=
0
Gerrit-Owner: stipa <lstipakov@gmail=2Ecom>
Gerrit-Reviewer: flichtenheld=
<frank@lichtenheld=2Ecom>
Gerrit-Reviewer: plaisthos <arne-openvpn@rfc2549=
=2Eorg>
Gerrit-CC: openvpn-devel <openvpn-devel@lists=2Esourceforge=2Enet>
=
Gerrit-Attention: plaisthos <arne-openvpn@rfc2549=2Eorg>
Gerrit-Attention: =
flichtenheld <frank@lichtenheld=2Ecom>
--mZYurtwNp64=
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<!DOCTYPE html><html><head><style></style></head><body><p> Attention is cur=
rently required from: flichtenheld, plaisthos=2E </p>
<p>stipa <strong>uplo=
aded patch set #10</strong> to this change=2E</p><p><a href=3D"http://gerri=
t=2Eopenvpn=2Enet/c/openvpn/+/1741?usp=3Demail">View Change</a></p><pre cla=
ss=3D"blocks" style=3D"font-family: monospace,monospace; white-space: pre-w=
rap;">oob: Add control message TLV encoding (P_CONTROL_OOB_V1)<br><br>Out-o=
f-band control messages carry control data but belong to no session, so a<b=
r>client can ask a server about itself before connecting=2E Reserve opcode =
12 for<br>them and add the codec for the two messages the probe needs, SERV=
ER_PROBE and<br>PROBE_REPLY=2E<br><br>The spec uses the same TLV framing fo=
r all its control messages, not just the<br>out-of-band ones, so the messag=
e-type header, the TLV header and the TLV scan go<br>into their own control=
_msg=2Ec/h; oob=2Ec keeps the OOB message and TLV types=2E<br><br>P_LAST_OP=
CODE becomes 12, and opcode_valid_in_session() replaces the plain range<br>=
check because OOB opcodes are answered statelessly and are never legal on a=
n<br>established session=2E<br><br>See the OOB control message section of t=
he wire protocol specification<br>(openvpn-rfc PR #30)=2E<br><br>Change-Id:=
I1c8d302ac57c5603d622a7be14be369437388268<br>Signed-off-by: Lev Stipakov &=
lt;lev@openvpn=2Enet><br>---<br>M CMakeLists=2Etxt<br>M src/openvpn/Make=
file=2Eam<br>A src/openvpn/control_msg=2Ec<br>A src/openvpn/control_msg=2Eh=
<br>A src/openvpn/oob=2Ec<br>A src/openvpn/oob=2Eh<br>M src/openvpn/ssl=2Ec=
<br>M src/openvpn/ssl_pkt=2Ec<br>M src/openvpn/ssl_pkt=2Eh<br>M tests/unit_=
tests/openvpn/Makefile=2Eam<br>A tests/unit_tests/openvpn/test_oob=2Ec<br>1=
1 files changed, 739 insertions(+), 4 deletions(-)<br><br></pre><pre class=
=3D"blocks" style=3D"font-family: monospace,monospace; white-space: pre-wra=
p;">git pull ssh://gerrit=2Eopenvpn=2Enet:29418/openvpn refs/changes/41/174=
1/10</pre><pre style=3D"font-family: monospace,monospace; white-space: pre-=
wrap;"><span>diff --git a/CMakeLists=2Etxt b/CMakeLists=2Etxt</span><br><sp=
an>index 7473f15=2E=2Ef4b1b22 100644</span><br><span>--- a/CMakeLists=2Etxt=
</span><br><span>+++ b/CMakeLists=2Etxt</span><br><span>@@ -444,6 +444,8 @@=
</span><br><span> src/openvpn/console=2Ec</span><br><span> src/open=
vpn/console_builtin=2Ec</span><br><span> src/openvpn/console=2Eh</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ src/openvpn/control_ms=
g=2Ec</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ src/openvp=
n/control_msg=2Eh</span><br><span> src/openvpn/crypto=2Ec</span><br><sp=
an> src/openvpn/crypto=2Eh</span><br><span> src/openvpn/crypto_back=
end=2Eh</span><br><span>@@ -520,6 +522,8 @@</span><br><span> src/openvp=
n/multi_io=2Ec</span><br><span> src/openvpn/occ=2Ec</span><br><span> =
src/openvpn/occ=2Eh</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+ src/openvpn/oob=2Ec</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ src/openvpn/oob=2Eh</span><br><span> src/openvpn/openvpn=2Ec<=
/span><br><span> src/openvpn/openvpn=2Eh</span><br><span> src/openv=
pn/openvpn_win32_resources=2Erc</span><br><span>@@ -667,6 +671,7 @@</span><=
br><span> "test_mbuf"</span><br><span> "test=
_misc"</span><br><span> "test_ncp"</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+ "test_oob"</span><b=
r><span> "test_options_parse"</span><br><span> &q=
uot;test_packet_id"</span><br><span> "test_pkt"</spa=
n><br><span>@@ -858,6 +863,13 @@</span><br><span> src/openvpn/sessi=
on_id=2Ec</span><br><span> )</span><br><span> </span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ target_sources(test_oob PRIVATE</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ tests/unit_tests=
/openvpn/mock_get_random=2Ec</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+ src/openvpn/control_msg=2Ec</span><br><span style=3D"color=
: hsl(120, 100%, 40%);">+ src/openvpn/oob=2Ec</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ src/openvpn/session_id=2Ec</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ )</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+</span><br><span> target_sources=
(test_pkt PRIVATE</span><br><span> tests/unit_tests/openvpn/mock_wi=
n32_execve=2Ec</span><br><span> src/openvpn/argv=2Ec</span><br><spa=
n>diff --git a/src/openvpn/Makefile=2Eam b/src/openvpn/Makefile=2Eam</span>=
<br><span>index ff8cc54=2E=2Ea67d478 100644</span><br><span>--- a/src/openv=
pn/Makefile=2Eam</span><br><span>+++ b/src/openvpn/Makefile=2Eam</span><br>=
<span>@@ -52,6 +52,7 @@</span><br><span> common=2Eh \</span><br><span> co=
mp=2Ec comp=2Eh compstub=2Ec \</span><br><span> comp-lz4=2Ec comp-lz4=2Eh =
\</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ control_msg=2Ec c=
ontrol_msg=2Eh \</span><br><span> crypto=2Ec crypto=2Eh crypto_backend=2Eh=
\</span><br><span> crypto_openssl=2Ec crypto_openssl=2Eh \</span><br><spa=
n> crypto_mbedtls_legacy=2Ec crypto_mbedtls_legacy=2Eh \</span><br><span>@=
@ -106,6 +107,7 @@</span><br><span> pkcs11=2Ec pkcs11=2Eh pkcs11_backend=
=2Eh \</span><br><span> pkcs11_openssl=2Ec \</span><br><span> pkcs11_mbed=
tls=2Ec \</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ oob=2Ec o=
ob=2Eh \</span><br><span> openvpn=2Ec openvpn=2Eh \</span><br><span> opti=
ons=2Ec options=2Eh \</span><br><span> options_util=2Ec options_util=2Eh \=
</span><br><span>diff --git a/src/openvpn/control_msg=2Ec b/src/openvpn/con=
trol_msg=2Ec</span><br><span>new file mode 100644</span><br><span>index 000=
0000=2E=2E95ff210</span><br><span>--- /dev/null</span><br><span>+++ b/src/o=
penvpn/control_msg=2Ec</span><br><span>@@ -0,0 +1,90 @@</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+/*</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ * OpenVPN -- An application to securely tunnel IP netw=
orks</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
over a single TCP/UDP port, with support for SSL/TLS-based</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+ * session authenticatio=
n and key exchange,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
* packet encryption, packet authentication, and</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+ * packet compression=
=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ * Copyright (C) 2002-2026 OpenV=
PN Inc <sales@openvpn=2Enet></span><br><span style=3D"color: hsl(120,=
100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *=
This program is free software; you can redistribute it and/or modify</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ * it under the terms o=
f the GNU General Public License version 2</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ * as published by the Free Software Foundation=2E<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+ * This program is distributed in th=
e hope that it will be useful,</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+ * but WITHOUT ANY WARRANTY; without even the implied warranty =
of</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * MERCHANTABILI=
TY or FITNESS FOR A PARTICULAR PURPOSE=2E See the</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+ * GNU General Public License for more deta=
ils=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+ * You should have received a=
copy of the GNU General Public License along</span><br><span style=3D"colo=
r: hsl(120, 100%, 40%);">+ * with this program; if not, see <https://ww=
w=2Egnu=2Eorg/licenses/>=2E</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+ */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+#ifdef HAVE_CONFIG_H</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+#include "config=
=2Eh"</span><br><span style=3D"color: hsl(120, 100%, 40%);">+#endif</s=
pan><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+#include "syshead=2Eh"</span><b=
r><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"co=
lor: hsl(120, 100%, 40%);">+#include "control_msg=2Eh"</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"colo=
r: hsl(120, 100%, 40%);">+bool</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+ctrl_msg_read_header(struct buffer *buf, uint16_t expected_msg_t=
ype)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+{</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ int msg_type =3D buf_read_u16=
(buf);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ return ms=
g_type >=3D 0 && (uint16_t)msg_type =3D=3D expected_msg_type;</s=
pan><br><span style=3D"color: hsl(120, 100%, 40%);">+}</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+bool</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+ctrl_msg_tlv_write_header(struct buffer *buf, uint16_t type, bool optional=
, uint16_t value_len)</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ uint16_t fie=
ld =3D type & CTRL_MSG_TLV_TYPE_MASK;</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+ if (optional)</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+ {</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ field |=3D CTRL_MSG_TLV_OPTIONAL_FLAG;</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ }</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+ return buf_write_u16(buf, field) && buf_wri=
te_u16(buf, value_len);</span><br><span style=3D"color: hsl(120, 100%, 40%)=
;">+}</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+bool</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ctrl_msg_tlv_read_header(struct buffer *buf, str=
uct ctrl_msg_tlv_header *hdr)</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ int =
field =3D buf_read_u16(buf);</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+ if (field < 0)</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+ {</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
return false;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
}</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ int len =3D=
buf_read_u16(buf);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
if (len < 0)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ return=
false;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ }</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ hdr->type =3D (uint=
16_t)(field & CTRL_MSG_TLV_TYPE_MASK);</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ hdr->optional =3D (field & CTRL_MSG_TLV_O=
PTIONAL_FLAG) !=3D 0;</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+ hdr->value_len =3D (uint16_t)len;</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ return true;</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+}</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+bool</span><br><s=
pan style=3D"color: hsl(120, 100%, 40%);">+ctrl_msg_find_tlv(struct buffer =
*payload, uint16_t wanted_type, struct buffer *value)</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+{</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+ struct ctrl_msg_tlv_header hdr;</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ while (ctrl_msg_tlv_read_header(paylo=
ad, &hdr))</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ {=
</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ /* Take the=
value out of payload=2E This validates that the header's</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ * length is really there=
and advances past it in one step, so the TLV</span><br><span style=3D"colo=
r: hsl(120, 100%, 40%);">+ * we are looking for and the ones we ski=
p are bounds-checked alike=2E */</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ uint8_t *v =3D buf_read_alloc(payload, hdr=2Evalue_len=
);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ if (!v)</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ {</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ return false;</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+ }</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+ if (hdr=2Etype =3D=3D wante=
d_type)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ {</s=
pan><br><span style=3D"color: hsl(120, 100%, 40%);">+ buf_set_re=
ad(value, v, hdr=2Evalue_len);</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+ return true;</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+ }</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ /* not the TLV we want: keep scanning */</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ }</span><br><span style=3D"color: hs=
l(120, 100%, 40%);">+ return false;</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+}</span><br><span>diff --git a/src/openvpn/control_msg=
=2Eh b/src/openvpn/control_msg=2Eh</span><br><span>new file mode 100644</sp=
an><br><span>index 0000000=2E=2Ee4c8b54</span><br><span>--- /dev/null</span=
><br><span>+++ b/src/openvpn/control_msg=2Eh</span><br><span>@@ -0,0 +1,101=
@@</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/*</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ * OpenVPN -- An application to =
securely tunnel IP networks</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+ * over a single TCP/UDP port, with support for SSL/TLS=
-based</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
session authentication and key exchange,</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ * packet encryption, packet authenticat=
ion, and</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
packet compression=2E</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * Copyrig=
ht (C) 2002-2026 OpenVPN Inc <sales@openvpn=2Enet></span><br><span st=
yle=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+ * This program is free software; you can redistribute=
it and/or modify</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *=
it under the terms of the GNU General Public License version 2</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+ * as published by the Free S=
oftware Foundation=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * This progra=
m is distributed in the hope that it will be useful,</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * but WITHOUT ANY WARRANTY; without eve=
n the implied warranty of</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE=2E See the</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * GNU General Publi=
c License for more details=2E</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * You=
should have received a copy of the GNU General Public License along</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ * with this program; if =
not, see <https://www=2Egnu=2Eorg/licenses/>=2E</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ */</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/=
**</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * @file</span><b=
r><span style=3D"color: hsl(120, 100%, 40%);">+ * Framing shared by the TLV=
-based control messages of the wire protocol=2E</span><br><span style=3D"co=
lor: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+ * Such a message payload starts with a 16-bit message type, fol=
lowed by a</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * sequen=
ce of TLV entries=2E Each TLV starts with a 4-byte header: a 16-bit field</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * whose most signifi=
cant bit is the "optional" flag and whose remaining 15 bits</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+ * are the type, followed=
by a 16-bit length giving the size of the value that</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * follows the header=2E</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+ * The message-type and TLV-type values themselves ar=
e specific to the message</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ * family carried (out-of-band messages define theirs in oob=2Eh), so=
only the</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * framing=
lives here=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ */</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+#ifndef CONTROL_MSG_H</span><br><span st=
yle=3D"color: hsl(120, 100%, 40%);">+#define CONTROL_MSG_H</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+#include "buffer=2Eh"</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+/* TLV header bit layout of the first 16-bit field */</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+#define CTRL_MSG_TLV_OPTIO=
NAL_FLAG 0x8000</span><br><span style=3D"color: hsl(120, 100%, 40%);">+#def=
ine CTRL_MSG_TLV_TYPE_MASK 0x7fff</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/=
* The header every TLV carries: the 15-bit type and optional flag packed in=
to</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * the first 16-b=
it field, then the length of the value that follows=2E */</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+struct ctrl_msg_tlv_header</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+{</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ uint16_t type; /**< the 15-bit TLV t=
ype */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ bool opti=
onal; /**< value of the optional flag */</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+ uint16_t value_len; /**< length of the =
value following the header */</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+};</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+/**</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * Read and verify a message-type header =
from buf, advancing past it=2E</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * @pa=
ram buf buffer positioned at the message payload</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ * @param expected_msg_type th=
e message type the payload must carry</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+ * @return true if a message type was read and equals exp=
ected_msg_type,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
false on a short buffer or a mismatching type=2E</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+ */</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+bool ctrl_msg_read_header(struct buffer *buf, uint16_t =
expected_msg_type);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/**</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+ * Write a TLV header (type + optio=
nal flag + value length) to buf=2E</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *=
@return true on success, false if buf has insufficient space=2E</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+ */</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+bool ctrl_msg_tlv_write_header(struct buffer *=
buf, uint16_t type, bool optional,</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+ uint16_t value_len);</span><b=
r><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"co=
lor: hsl(120, 100%, 40%);">+/**</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+ * Read a TLV header from buf, advancing past it=2E</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ * @param buf buffer positioned at the TLV head=
er</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * @param hdr fi=
lled with the type, optional flag and value length on success</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ * @return true on success, false=
if there are not enough bytes for a header=2E</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ */</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+bool ctrl_msg_tlv_read_header(struct buffer *buf, struct ctrl_ms=
g_tlv_header *hdr);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/**</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+ * Scan payload for the first TLV o=
f type wanted_type, skipping any other (e=2Eg=2E</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+ * future) TLV types=2E</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+ * On success value covers exactly the found TLV's val=
ue bytes=2E The length from</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+ * each TLV header is validated against payload as the scan goes, s=
o the whole</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * value=
is guaranteed to be present; a header claiming more bytes than payload</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+ * holds is rejected ra=
ther than reported as found=2E payload is consumed as it is</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+ * read=2E</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, =
100%, 40%);">+ * @param payload buffer positioned at a TLV header</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ * @param wanted_type t=
he TLV type to look for</span><br><span style=3D"color: hsl(120, 100%, 40%)=
;">+ * @param value set to a buffer covering the found TLV's val=
ue</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * @return true i=
f the TLV was found, false if it is not present or a TLV</span><br><span st=
yle=3D"color: hsl(120, 100%, 40%);">+ * header or value is malforme=
d or truncated=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *=
/</span><br><span style=3D"color: hsl(120, 100%, 40%);">+bool ctrl_msg_find=
_tlv(struct buffer *payload, uint16_t wanted_type, struct buffer *value);</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+#endif /* ifndef CONTROL_MSG_H */</span>=
<br><span>diff --git a/src/openvpn/oob=2Ec b/src/openvpn/oob=2Ec</span><br>=
<span>new file mode 100644</span><br><span>index 0000000=2E=2Ec807171</span=
><br><span>--- /dev/null</span><br><span>+++ b/src/openvpn/oob=2Ec</span><b=
r><span>@@ -0,0 +1,83 @@</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+/*</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * OpenVPN =
-- An application to securely tunnel IP networks</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+ * over a single TCP/UDP port, wit=
h support for SSL/TLS-based</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+ * session authentication and key exchange,</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ * packet encryptio=
n, packet authentication, and</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ * packet compression=2E</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ * Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn=2Enet&g=
t;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+ * This program is free software;=
you can redistribute it and/or modify</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ * it under the terms of the GNU General Public License=
version 2</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * as pu=
blished by the Free Software Foundation=2E</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ * This program is distributed in the hope that it will be useful,</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * but WITHOUT ANY W=
ARRANTY; without even the implied warranty of</span><br><span style=3D"colo=
r: hsl(120, 100%, 40%);">+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR P=
URPOSE=2E See the</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
* GNU General Public License for more details=2E</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ * You should have received a copy of the GNU General Public =
License along</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * wi=
th this program; if not, see <https://www=2Egnu=2Eorg/licenses/>=2E</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ */</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+#ifdef HAVE_CONFIG_H</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+#include "config=2Eh"</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+#endif</span><br><span style=3D"color: hs=
l(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+#include "syshead=2Eh"</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+#inc=
lude "oob=2Eh"</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+#include "control_msg=2Eh"</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);=
">+bool</span><br><span style=3D"color: hsl(120, 100%, 40%);">+oob_probe_pa=
rameter_write(struct buffer *buf, const struct oob_probe_parameter *p)</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+{</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ return ctrl_msg_tlv_write_header(buf,=
OOB_TLV_PROBE_PARAMETER, false, OOB_PROBE_PARAMETER_LEN)</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+ && buf_write_u64(b=
uf, p->timestamp)</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+ && buf_write_u32(buf, p->flags);</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+}</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+b=
ool</span><br><span style=3D"color: hsl(120, 100%, 40%);">+oob_probe_parame=
ter_read(struct buffer *buf, struct oob_probe_parameter *p)</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+{</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+ /* One bounds check covers the whole value: every=
field read below then fits</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+ * by construction (OOB_PROBE_PARAMETER_LEN is the sum of their=
sizes), so</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * n=
one of them needs its own error handling=2E Trailing bytes this version</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+ * does not underst=
and are simply left unread=2E */</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ if (buf_len(buf) < OOB_PROBE_PARAMETER_LEN)</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ {</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+ return false;</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ }</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+ p->timestamp =3D buf_read_u64(buf, NULL);</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ p->flags =3D buf_re=
ad_u32(buf, NULL);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
return true;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+}</s=
pan><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+bool</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+oob_probe_reply_write(struct buffer *buf, const struct o=
ob_probe_reply *r)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+{=
</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ return ctrl_msg=
_tlv_write_header(buf, OOB_TLV_PROBE_REPLY, false, OOB_PROBE_REPLY_LEN)</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+ && s=
ession_id_write(&r->peer_session_id, buf)</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+ && buf_write_u16(buf, r->=
;priority)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
&& buf_write_u16(buf, r->weight)</span><br><span style=3D"colo=
r: hsl(120, 100%, 40%);">+ && buf_write_u16(buf, r->ma=
x_latency_diff)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
&& buf_write_u16(buf, r->connect_lifetime)</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+ && buf_write_u3=
2(buf, r->flags);</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+}</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+bool</span><br><span style=3D"color:=
hsl(120, 100%, 40%);">+oob_probe_reply_read(struct buffer *buf, struct oob=
_probe_reply *r)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+{</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ /* One bounds che=
ck for the whole value, as in oob_probe_parameter_read()=2E */</span><br><s=
pan style=3D"color: hsl(120, 100%, 40%);">+ if (buf_len(buf) < OOB_PR=
OBE_REPLY_LEN)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ {=
</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ return fals=
e;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ }</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ session_id_read(&r->=
peer_session_id, buf);</span><br><span style=3D"color: hsl(120, 100%, 40%);=
">+ r->priority =3D (uint16_t)buf_read_u16(buf);</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ r->weight =3D (uint16_t)buf_read_=
u16(buf);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ r->=
max_latency_diff =3D (uint16_t)buf_read_u16(buf);</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+ r->connect_lifetime =3D (uint16_t)buf_=
read_u16(buf);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ r=
->flags =3D buf_read_u32(buf, NULL);</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+ return true;</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+}</span><br><span>diff --git a/src/openvpn/oob=2Eh b/src/o=
penvpn/oob=2Eh</span><br><span>new file mode 100644</span><br><span>index 0=
000000=2E=2E0a9a4bb</span><br><span>--- /dev/null</span><br><span>+++ b/src=
/openvpn/oob=2Eh</span><br><span>@@ -0,0 +1,110 @@</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+/*</span><br><span style=3D"color: hsl(120, =
100%, 40%);">+ * OpenVPN -- An application to securely tunnel IP networks<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * over =
a single TCP/UDP port, with support for SSL/TLS-based</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * session authentication and=
key exchange,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
packet encryption, packet authentication, and</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+ * packet compression=2E</s=
pan><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+ * Copyright (C) 2002-2026 OpenVPN Inc=
<sales@openvpn=2Enet></span><br><span style=3D"color: hsl(120, 100%,=
40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * This=
program is free software; you can redistribute it and/or modify</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+ * it under the terms of the =
GNU General Public License version 2</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+ * as published by the Free Software Foundation=2E</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * This program is distributed in the ho=
pe that it will be useful,</span><br><span style=3D"color: hsl(120, 100%, 4=
0%);">+ * but WITHOUT ANY WARRANTY; without even the implied warranty of</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * MERCHANTABILITY o=
r FITNESS FOR A PARTICULAR PURPOSE=2E See the</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ * GNU General Public License for more details=
=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ * You should have received a co=
py of the GNU General Public License along</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ * with this program; if not, see <https://www=
=2Egnu=2Eorg/licenses/>=2E</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+/**</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * @file</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ * Encoding/decoding of out-of-band (P_CONTROL_OOB_V=
1) control messages=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);=
">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * An OOB mess=
age payload starts with a 16-bit message type (the 0x1xx space:</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ * SERVER_PROBE, PROBE_REPLY, =
=2E=2E=2E) followed by a sequence of TLV entries in the</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+ * 0x2xx type space=2E The framing itse=
lf is shared with the other TLV-based</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+ * control messages of the wire protocol and lives in con=
trol_msg=2Eh; this file</span><br><span style=3D"color: hsl(120, 100%, 40%)=
;">+ * defines the OOB message and TLV types, their values, and the OOB-spe=
cific</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * decisions t=
aken on them=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ */<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+#ifndef OOB_H</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+#define OOB_H</span><br><span style=3D"color:=
hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+#include "buffer=2Eh"</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+#include "session_id=2Eh"</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+/* OOB message types: the 16-bit value at the start of an OO=
B payload, before</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *=
its TLV entries=2E Distinct from the TLV-type space (0x2xx); see the "=
;Messages"</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * t=
able in the OOB section of the wire protocol spec=2E */</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+#define OOB_MSG_SERVER_PROBE 0x100</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+#define OOB_MSG_PROBE_RE=
PLY 0x101</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><b=
r><span style=3D"color: hsl(120, 100%, 40%);">+/* TLV types (see the OOB co=
ntrol message section of the wire protocol spec) */</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+#define OOB_TLV_PROBE_PARAMETER 0x200</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+#define OOB_TLV_PROBE_R=
EPLY 0x201</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+/* Minimum on-wire value=
length (excluding the 4-byte TLV header) of each TLV:</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ * the sizes of its fixed fields in wire=
order (see the structs below)=2E The</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+ * value may be longer for forward compatibility; trailin=
g bytes that are not</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+ * understood are ignored on read=2E */</span><br><span style=3D"color: hs=
l(120, 100%, 40%);">+#define OOB_PROBE_PARAMETER_LEN \</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ ((uint16_t)(sizeof(uint64_t) /* time=
stamp */ + sizeof(uint32_t) /* flags */))</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+#define OOB_PROBE_REPLY_LEN =
\</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ ((uin=
t16_t)(SID_SIZE /* peer_session_id */ \</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ + 4 * sizeof(uint16_t) /=
* priority, weight, \</span><br><span style=3D"color: hsl(120, 100%, 40%)=
;">+ * max_latency_diff, \</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+ =
* connect_lifetime */ \</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+ + sizeof(uint32_t))) /* flags */</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+/* probe parameter TLV (sent by the client in =
a SERVER_PROBE) */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+s=
truct oob_probe_parameter</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ uint64_t=
timestamp; /**< client clock as a UNIX timestamp */</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+ uint32_t flags; /**< client =
capability flags, currently must be 0 */</span><br><span style=3D"color: hs=
l(120, 100%, 40%);">+};</span><br><span style=3D"color: hsl(120, 100%, 40%)=
;">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/* probe reply =
TLV (sent by the server in a PROBE_REPLY) */</span><br><span style=3D"color=
: hsl(120, 100%, 40%);">+struct oob_probe_reply</span><br><span style=3D"co=
lor: hsl(120, 100%, 40%);">+{</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ struct session_id peer_session_id; /**< echoes the session=
id of the request */</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+ uint16_t priority; /**< DNS-SRV style priority (lo=
wer is preferred) */</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+ uint16_t weight; /**< DNS-SRV style weight */</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+ uint16_t max_latenc=
y_diff; /**< advertised candidate-band margin in ms;</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ =
* 0 means "defer to the client's setting" */</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+ uint16_t connect_life=
time; /**< seconds the reply stays valid as the handshake reset =
*/</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ uint32_t flag=
s; /**< server behaviour flags */</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+};</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
/**</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * Write a compl=
ete probe parameter TLV (header + value) to buf=2E</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+ */</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+bool oob_probe_parameter_write(struct buffer *buf, const str=
uct oob_probe_parameter *p);</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/**</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ * Read a probe parameter =
TLV value from buf=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * buf must cov=
er exactly the TLV's value, as returned by ctrl_msg_find_tlv()=2E</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+ * Trailing bytes beyond =
the fields understood here are ignored, so a longer</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * value from a future version still pars=
es=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ * @return true on success, fal=
se if buf is shorter than the mandatory fields=2E</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+ */</span><br><span style=3D"color: hsl(120, =
100%, 40%);">+bool oob_probe_parameter_read(struct buffer *buf, struct oob_=
probe_parameter *p);</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/**</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+ * Write a complete probe reply TL=
V (header + value) to buf=2E</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+ */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+bool oo=
b_probe_reply_write(struct buffer *buf, const struct oob_probe_reply *r);</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+/**</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ * Read a probe reply TLV value from buf=2E See oob_prob=
e_parameter_read() for the</span><br><span style=3D"color: hsl(120, 100%, 4=
0%);">+ * calling convention=2E</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+ */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+bool=
oob_probe_reply_read(struct buffer *buf, struct oob_probe_reply *r);</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+#endif /* OOB_H */</span><br><span>diff --gi=
t a/src/openvpn/ssl=2Ec b/src/openvpn/ssl=2Ec</span><br><span>index 60df7ce=
=2E=2E9e1a6aa 100644</span><br><span>--- a/src/openvpn/ssl=2Ec</span><br><s=
pan>+++ b/src/openvpn/ssl=2Ec</span><br><span>@@ -3616,7 +3616,7 @@</span><=
br><span> struct session_id sid; /* remote session ID */</span><br><spa=
n> </span><br><span> /* verify legal opcode */</span><br><span style=3D=
"color: hsl(0, 100%, 40%);">- if (op < P_FIRST_OPCODE || op > P_LA=
ST_OPCODE)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ if (!=
opcode_valid_in_session(op))</span><br><span> {</span><br><span> =
if (op =3D=3D P_CONTROL_HARD_RESET_CLIENT_V1 || op =3D=3D P_CONTROL_HARD_=
RESET_SERVER_V1)</span><br><span> {</span><br><span>diff --git a/sr=
c/openvpn/ssl_pkt=2Ec b/src/openvpn/ssl_pkt=2Ec</span><br><span>index f8444=
451=2E=2E0d882d9 100644</span><br><span>--- a/src/openvpn/ssl_pkt=2Ec</span=
><br><span>+++ b/src/openvpn/ssl_pkt=2Ec</span><br><span>@@ -166,7 +166,7 @=
@</span><br><span> bool prepend_ack)</span><br><span> {<=
/span><br><span> ASSERT(ks->key_id >=3D 0 && ks->key_i=
d <=3D P_KEY_ID_MASK);</span><br><span style=3D"color: hsl(0, 100%, 40%)=
;">- ASSERT(opcode >=3D 0 && opcode <=3D P_LAST_OPCODE);</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ ASSERT(opcode_val=
id_in_session(opcode));</span><br><span> uint8_t header =3D (uint8_t)(k=
s->key_id | (opcode << P_OPCODE_SHIFT));</span><br><span> </span><=
br><span> /* Workaround for Softether servers=2E Softether has a bug th=
at it only</span><br><span>diff --git a/src/openvpn/ssl_pkt=2Eh b/src/openv=
pn/ssl_pkt=2Eh</span><br><span>index 82cb5b1=2E=2E25e50bf 100644</span><br>=
<span>--- a/src/openvpn/ssl_pkt=2Eh</span><br><span>+++ b/src/openvpn/ssl_p=
kt=2Eh</span><br><span>@@ -58,11 +58,53 @@</span><br><span> * like P_CONTR=
OL_HARD_RESET_CLIENT_V3 */</span><br><span> #define P_CONTROL_WKC_V1 11</sp=
an><br><span> </span><br><span style=3D"color: hsl(0, 100%, 40%);">-/* defi=
ne the range of legal opcodes</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+/* Out-of-band control message that does not belong to an establi=
shed</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * control chan=
nel session (e=2Eg=2E a server probe)=2E Inherently unreliable:</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ * there is no protocol-level r=
etransmission=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * It is not legal o=
n an established session; see opcode_valid_in_session()</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+ * below=2E */</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+#define P_CONTROL_OOB_V1 12</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+/* define the range of defined opcodes, in- and out-of-=
band=2E Note this is not</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+ * the set of opcodes legal on an established session; see</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ * opcode_valid_in_session()=2E=
</span><br><span> * Since we do no longer support key-method 1 we consider=
</span><br><span> * the v1 op codes invalid */</span><br><span> #define P_=
FIRST_OPCODE 3</span><br><span style=3D"color: hsl(0, 100%, 40%);">-#define=
P_LAST_OPCODE 11</span><br><span style=3D"color: hsl(120, 100%, 40%);">+#=
define P_LAST_OPCODE 12</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+static inline =
bool</span><br><span style=3D"color: hsl(120, 100%, 40%);">+opcode_is_oob(i=
nt op)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+{</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ return op =3D=3D P_CONTROL_=
OOB_V1;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+}</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"colo=
r: hsl(120, 100%, 40%);">+/**</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ * Return true if op may occur on an established control-channel =
session=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ * Out-of-band opcodes may=
not=2E They are answered statelessly on the</span><br><span style=3D"color=
: hsl(120, 100%, 40%);">+ * new-connection path, and being rejected here is=
a permanent property rather</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+ * than a handler that is still missing:</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ * - an OOB message carries its TLV payload directly, with no=
reliability or</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
ACK fields (see tls_wrap_oob_standalone()), whereas the established-sessi=
on</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * path parses=
an ACK array and a control packet-id before anything else=2E The</span><br=
><span style=3D"color: hsl(120, 100%, 40%);">+ * payload is chosen by th=
e sender and session ids are plaintext on the wire,</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * so those bytes can be crafted into =
a valid ACK array, forging ACKs into a</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ * live control channel and stalling a handshake or r=
ekey=2E</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * - nothin=
g on that path parses an OOB payload, so there is nothing to gain by</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ * accepting one=2E</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ * This cannot be expressed as an opcode=
range: the inband CONTROL_DATA_V1 of the</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+ * wire protocol is legal on an established session, =
so the OOB opcodes sit</span><br><span style=3D"color: hsl(120, 100%, 40%);=
">+ * between legal ones=2E</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+ */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+static i=
nline bool</span><br><span style=3D"color: hsl(120, 100%, 40%);">+opcode_va=
lid_in_session(int op)</span><br><span style=3D"color: hsl(120, 100%, 40%);=
">+{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ return op &=
gt;=3D P_FIRST_OPCODE && op <=3D P_LAST_OPCODE && !opcod=
e_is_oob(op);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+}</spa=
n><br><span> </span><br><span> /*</span><br><span> * Define number of buff=
ers for send and receive in the reliability layer=2E</span><br><span>@@ -26=
4,6 +306,9 @@</span><br><span> case P_CONTROL_WKC_V1:</span><br><sp=
an> return "P_CONTROL_WKC_V1";</span><br><span> </spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ case P_CONTROL_O=
OB_V1:</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ r=
eturn "P_CONTROL_OOB_V1";</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+</span><br><span> case P_ACK_V1:</span><br><span> =
return "P_ACK_V1";</span><br><span> </span><br><span>d=
iff --git a/tests/unit_tests/openvpn/Makefile=2Eam b/tests/unit_tests/openv=
pn/Makefile=2Eam</span><br><span>index d861ef9=2E=2E611097a 100644</span><b=
r><span>--- a/tests/unit_tests/openvpn/Makefile=2Eam</span><br><span>+++ b/=
tests/unit_tests/openvpn/Makefile=2Eam</span><br><span>@@ -14,6 +14,7 @@</s=
pan><br><span> ncp_testdriver \</span><br><span> mbuf_testdriver \</span>=
<br><span> misc_testdriver \</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ oob_testdriver \</span><br><span> options_parse_testdriver \</s=
pan><br><span> packet_id_testdriver \</span><br><span> pkt_testdriver \</=
span><br><span>@@ -150,6 +151,20 @@</span><br><span> $(top_srcdir)/src/ope=
nvpn/win32-util=2Ec \</span><br><span> $(top_srcdir)/src/openvpn/session_i=
d=2Ec</span><br><span> </span><br><span style=3D"color: hsl(120, 100%, 40%)=
;">+oob_testdriver_CFLAGS =3D \</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ -I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_sr=
cdir)/src/openvpn \</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
@TEST_CFLAGS@</span><br><span style=3D"color: hsl(120, 100%, 40%);">+oob_t=
estdriver_LDFLAGS =3D @TEST_LDFLAGS@</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+oob_testdriver_SOURCES =3D test_oob=2Ec \</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+ mock_msg=2Ec mock_msg=2Eh test_comm=
on=2Eh \</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ mock_get_r=
andom=2Ec \</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ $(top_s=
rcdir)/src/openvpn/buffer=2Ec \</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+ $(top_srcdir)/src/openvpn/control_msg=2Ec \</span><br><span st=
yle=3D"color: hsl(120, 100%, 40%);">+ $(top_srcdir)/src/openvpn/oob=2Ec \</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ $(top_srcdir)/src/op=
envpn/platform=2Ec \</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+ $(top_srcdir)/src/openvpn/session_id=2Ec \</span><br><span style=3D"color=
: hsl(120, 100%, 40%);">+ $(top_srcdir)/src/openvpn/win32-util=2Ec</span><b=
r><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span> pkt_testdr=
iver_CFLAGS =3D \</span><br><span> -I$(top_srcdir)/include -I$(top_srcdir=
)/src/compat -I$(top_srcdir)/src/openvpn \</span><br><span> @TEST_CFLAGS@<=
/span><br><span>diff --git a/tests/unit_tests/openvpn/test_oob=2Ec b/tests/=
unit_tests/openvpn/test_oob=2Ec</span><br><span>new file mode 100644</span>=
<br><span>index 0000000=2E=2Eaa4eb23</span><br><span>--- /dev/null</span><b=
r><span>+++ b/tests/unit_tests/openvpn/test_oob=2Ec</span><br><span>@@ -0,0=
+1,277 @@</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/*</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ * OpenVPN -- An applicat=
ion to securely tunnel IP networks</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+ * over a single TCP/UDP port, with support for =
SSL/TLS-based</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
session authentication and key exchange,</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+ * packet encryption, packet auth=
entication, and</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
packet compression=2E</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * =
Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn=2Enet></span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ *</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ * This program is free software; you can redis=
tribute it and/or modify</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+ * it under the terms of the GNU General Public License version 2</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+ * as published by the=
Free Software Foundation=2E</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * This=
program is distributed in the hope that it will be useful,</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+ * but WITHOUT ANY WARRANTY; witho=
ut even the implied warranty of</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE=2E See=
the</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * GNU General=
Public License for more details=2E</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+ *</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
* You should have received a copy of the GNU General Public License along<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * with this progra=
m; if not, see <https://www=2Egnu=2Eorg/licenses/>=2E</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+ */</span><br><span style=3D"color:=
hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+#ifdef HAVE_CONFIG_H</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+#include "config=2Eh"</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+#endif</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+#include "=
;syshead=2Eh"</span><br><span style=3D"color: hsl(120, 100%, 40%);">+<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+#include <stdarg=
=2Eh></span><br><span style=3D"color: hsl(120, 100%, 40%);">+#include &l=
t;stddef=2Eh></span><br><span style=3D"color: hsl(120, 100%, 40%);">+#in=
clude <setjmp=2Eh></span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+#include <cmocka=2Eh></span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+#inclu=
de "control_msg=2Eh"</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+#include "oob=2Eh"</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+#include "test_common=2Eh"</span><br><span st=
yle=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+/* Write a probe parameter TLV and read it back; fields m=
ust survive the</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * r=
ound trip and the whole buffer must be consumed=2E */</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+static void</span><br><span style=3D"colo=
r: hsl(120, 100%, 40%);">+test_probe_parameter_roundtrip(void **state)</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+{</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ struct gc_arena gc =3D gc_new();</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ struct buffer buf =
=3D alloc_buf_gc(128, &gc);</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ con=
st struct oob_probe_parameter in =3D {</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ =2Etimestamp =3D 0x0123456789abcdefULL,</span><b=
r><span style=3D"color: hsl(120, 100%, 40%);">+ =2Eflags =3D 0,</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ };</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+ assert_true(oob_probe_parameter_w=
rite(&buf, &in));</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ /* header (4) + value (12) */</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+ assert_int_equal(BLEN(&buf), 4 + OOB_PROBE_PARA=
METER_LEN);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+ /* the header codec, re=
ad from a copy so the scan below still sees it */</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+ struct buffer peek =3D buf;</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ struct ctrl_msg_tlv_header hd=
r;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ assert_true(c=
trl_msg_tlv_read_header(&peek, &hdr));</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ assert_int_equal(hdr=2Etype, OOB_TLV_PROBE_P=
ARAMETER);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ asser=
t_false(hdr=2Eoptional);</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+ assert_int_equal(hdr=2Evalue_len, OOB_PROBE_PARAMETER_LEN);</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"=
color: hsl(120, 100%, 40%);">+ struct buffer value;</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ assert_true(ctrl_msg_find_tlv(&b=
uf, OOB_TLV_PROBE_PARAMETER, &value));</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ assert_int_equal(BLEN(&value), OOB_PROBE_PAR=
AMETER_LEN);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ struct oob_probe_param=
eter out =3D { 0 };</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
assert_true(oob_probe_parameter_read(&value, &out));</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+ assert_true(in=2Etimestamp=
=3D=3D out=2Etimestamp);</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ assert_int_equal(in=2Eflags, out=2Eflags);</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ /* the scan consumed header and value=
alike */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ assert=
_int_equal(BLEN(&buf), 0);</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ gc_f=
ree(&gc);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+}</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+/* Write a probe reply TLV and read it ba=
ck=2E */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+static void=
</span><br><span style=3D"color: hsl(120, 100%, 40%);">+test_probe_reply_ro=
undtrip(void **state)</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ struct gc_ar=
ena gc =3D gc_new();</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+ struct buffer buf =3D alloc_buf_gc(128, &gc);</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+ struct oob_probe_reply in =3D {</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ =2Epriority =3D 10,</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ =2Eweight =3D 100,</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ =2Econnect_lifetim=
e =3D 30,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
=2Eflags =3D 1,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
=2Emax_latency_diff =3D 25,</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+ };</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
memcpy(in=2Epeer_session_id=2Eid, "ABCDEFGH", SID_SIZE);</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+ assert_true(oob_probe_reply_write(&b=
uf, &in));</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ a=
ssert_int_equal(BLEN(&buf), 4 + OOB_PROBE_REPLY_LEN);</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ struct buffer peek =3D buf;</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+ struct ctrl_msg_tlv_header hdr;</span><b=
r><span style=3D"color: hsl(120, 100%, 40%);">+ assert_true(ctrl_msg_tlv=
_read_header(&peek, &hdr));</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+ assert_int_equal(hdr=2Etype, OOB_TLV_PROBE_REPLY);</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ assert_int_equal(hdr=
=2Evalue_len, OOB_PROBE_REPLY_LEN);</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
struct buffer value;</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+ assert_true(ctrl_msg_find_tlv(&buf, OOB_TLV_PROBE_REPLY, &val=
ue));</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ struct oob_probe_reply out =
=3D { 0 };</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ asser=
t_true(oob_probe_reply_read(&value, &out));</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ assert_memory_equal(in=2Epeer_session=
_id=2Eid, out=2Epeer_session_id=2Eid, SID_SIZE);</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+ assert_int_equal(in=2Epriority, out=2Eprio=
rity);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ assert_in=
t_equal(in=2Eweight, out=2Eweight);</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+ assert_int_equal(in=2Econnect_lifetime, out=2Econnect_l=
ifetime);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ assert=
_int_equal(in=2Eflags, out=2Eflags);</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+ assert_int_equal(in=2Emax_latency_diff, out=2Emax_late=
ncy_diff);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ asser=
t_int_equal(BLEN(&buf), 0);</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ gc_=
free(&gc);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+}</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+/* The probe reply wire format is locked =
to the spec's field order</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ * (priority, weight, max_latency_diff, connect_lifetime, flags),=
big-endian=2E */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+st=
atic void</span><br><span style=3D"color: hsl(120, 100%, 40%);">+test_probe=
_reply_wire_format(void **state)</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ s=
truct gc_arena gc =3D gc_new();</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+ struct buffer buf =3D alloc_buf_gc(128, &gc);</span><br=
><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ struct oob_probe_reply in =3D {</span><br><s=
pan style=3D"color: hsl(120, 100%, 40%);">+ =2Epriority =3D 10,</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ =2Eweight =3D 10=
0,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =2Econnec=
t_lifetime =3D 30,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
=2Eflags =3D 1,</span><br><span style=3D"color: hsl(120, 100%, 40%);=
">+ =2Emax_latency_diff =3D 25,</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ };</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ memcpy(in=2Epeer_session_id=2Eid, "ABCDEFGH", SID_SIZE)=
;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+ assert_true(oob_probe_reply_write=
(&buf, &in));</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ const uint8_t=
expected[] =3D {</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
0x02,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
0x01, /* TLV type 0x201 (not optional) */</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+ 0x00,</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+ 0x14, /* TLV value length =3D 20 */</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+ 'A',</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+ 'B',</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+ 'C',</span><br><s=
pan style=3D"color: hsl(120, 100%, 40%);">+ 'D',</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ 'E',</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+ 'F',</span><br=
><span style=3D"color: hsl(120, 100%, 40%);">+ 'G',</span><b=
r><span style=3D"color: hsl(120, 100%, 40%);">+ 'H', /* pee=
r_session_id */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
0x00,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ 0x=
0a, /* priority =3D 10 */</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+ 0x00,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
0x64, /* weight =3D 100 */</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+ 0x00,</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+ 0x19, /* max_latency_diff =3D 25 */</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ 0x00,</span><br><span style=3D"co=
lor: hsl(120, 100%, 40%);">+ 0x1e, /* connect_lifetime =3D 30 */</sp=
an><br><span style=3D"color: hsl(120, 100%, 40%);">+ 0x00,</span><br=
><span style=3D"color: hsl(120, 100%, 40%);">+ 0x00,</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+ 0x00,</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ 0x01, /* flags =3D 1 */</span><br=
><span style=3D"color: hsl(120, 100%, 40%);">+ };</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ assert_int_equal(BLEN(&buf), size=
of(expected));</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ a=
ssert_memory_equal(BPTR(&buf), expected, sizeof(expected));</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color=
: hsl(120, 100%, 40%);">+ gc_free(&gc);</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+}</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/* A TLV w=
ith a longer-than-known value must still parse: the known fields are</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ * read and the trailing b=
ytes are skipped (forward compatibility)=2E */</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+static void</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+test_probe_parameter_forward_compat(void **state)</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+{</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+ struct gc_arena gc =3D gc_new();</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+ struct buffer buf =3D a=
lloc_buf_gc(128, &gc);</span><br><span style=3D"color: hsl(120, 100%, 4=
0%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ const ui=
nt16_t extended_len =3D OOB_PROBE_PARAMETER_LEN + 4;</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ assert_true(ctrl_msg_tlv_write_header=
(&buf, OOB_TLV_PROBE_PARAMETER, false, extended_len));</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+ assert_true(buf_write_u32(&b=
uf, 0)); /* timestamp high */</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ assert_true(buf_write_u32(&buf, 0xdeadbeef)); /*=
timestamp low */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
assert_true(buf_write_u32(&buf, 0)); /* flags */</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+ assert_true(buf_write_u32(&=
amp;buf, 0x11223344)); /* unknown trailing field */</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+ struct buffer value;</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+ assert_true(ctrl_msg_find_tlv(&buf, OOB_TLV_PRO=
BE_PARAMETER, &value));</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+ assert_int_equal(BLEN(&value), extended_len);</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ struct oob_probe_parameter out =3D { 0 };</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ assert_true(oob_probe_=
parameter_read(&value, &out));</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ assert_true(out=2Etimestamp =3D=3D 0xdeadbeefULL);</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+ assert_int_equal(=
out=2Eflags, 0);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
/* the unknown trailing field must have been consumed from the payload */<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+ assert_int_equal=
(BLEN(&buf), 0);</span><br><span style=3D"color: hsl(120, 100%, 40%);">=
+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ gc_free(&g=
c);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+}</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+/* A value shorter than the mandatory fields must be =
rejected, even when it</span><br><span style=3D"color: hsl(120, 100%, 40%);=
">+ * holds enough bytes for some of the individual fields to read successf=
ully=2E */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+static vo=
id</span><br><span style=3D"color: hsl(120, 100%, 40%);">+test_probe_parame=
ter_too_short(void **state)</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ struct=
gc_arena gc =3D gc_new();</span><br><span style=3D"color: hsl(120, 100%, 4=
0%);">+ struct buffer buf =3D alloc_buf_gc(128, &gc);</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+ /* 4 of the 12 mandatory value bytes: too short f=
or the timestamp, but</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+ * enough for a u32 read to succeed on its own */</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+ assert_true(buf_write_u32(&buf,=
0xdeadbeef));</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+ struct oob_probe_par=
ameter out =3D { 0 };</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+ assert_false(oob_probe_parameter_read(&buf, &out));</span><br=
><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ gc_free(&gc);</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+}</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+/* A TLV=
header claiming more value bytes than the payload holds must be</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+ * rejected by the scan rather=
than reported as found -- for the TLV being</span><br><span style=3D"color=
: hsl(120, 100%, 40%);">+ * looked for as much as for one that would merely=
be skipped=2E */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+st=
atic void</span><br><span style=3D"color: hsl(120, 100%, 40%);">+test_find_=
tlv_value_truncated(void **state)</span><br><span style=3D"color: hsl(120, =
100%, 40%);">+{</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
struct gc_arena gc =3D gc_new();</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ struct buffer value;</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
/* the wanted TLV declares 12 value bytes, only 4 are present */</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+ struct buffer buf =3D a=
lloc_buf_gc(128, &gc);</span><br><span style=3D"color: hsl(120, 100%, 4=
0%);">+ assert_true(ctrl_msg_tlv_write_header(&buf, OOB_TLV_PROBE_PA=
RAMETER, false,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
OOB_PROBE_PARAMETER_LEN));</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+ assert_true(buf_write_u32(=
&buf, 0xdeadbeef));</span><br><span style=3D"color: hsl(120, 100%, 40%)=
;">+ assert_false(ctrl_msg_find_tlv(&buf, OOB_TLV_PROBE_PARAMETER, &=
amp;value));</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ /* same defect on a TL=
V that would be skipped: the scan must not walk past</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ * the end of the payload looking for=
the next header */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
struct buffer buf2 =3D alloc_buf_gc(128, &gc);</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+ assert_true(ctrl_msg_tlv_write_heade=
r(&buf2, 0x7ff, false, 64));</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ assert_true(buf_write_u32(&buf2, 0));</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+ assert_false(ctrl_msg_find_tlv(&=
amp;buf2, OOB_TLV_PROBE_PARAMETER, &value));</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+ gc_free(&gc);</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+}</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+/* Reading a TLV header m=
ust fail when the buffer holds less data than a</span><br><span style=3D"co=
lor: hsl(120, 100%, 40%);">+ * complete 4-byte header (empty, or only the t=
ype field), rather than read</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+ * past the available data=2E */</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+static void</span><br><span style=3D"color: hsl(120, =
100%, 40%);">+test_tlv_header_truncated(void **state)</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+{</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+ struct gc_arena gc =3D gc_new();</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ struct buffer buf =3D alloc_buf_gc(12=
8, &gc);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ struct ctrl_msg_tlv_he=
ader hdr;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br=
><span style=3D"color: hsl(120, 100%, 40%);">+ /* empty buffer */</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+ assert_false(ctrl_msg_=
tlv_read_header(&buf, &hdr));</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
/* only the type field present, no length */</span><br><span style=3D"co=
lor: hsl(120, 100%, 40%);">+ assert_true(buf_write_u16(&buf, OOB_TLV=
_PROBE_PARAMETER));</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
assert_false(ctrl_msg_tlv_read_header(&buf, &hdr));</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"color=
: hsl(120, 100%, 40%);">+ gc_free(&gc);</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+}</span><br><span style=3D"color: hsl(120, 100%,=
40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+int</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+main(void)</span><br><span=
style=3D"color: hsl(120, 100%, 40%);">+{</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+ openvpn_unit_test_setup();</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+ const struct CMUnitTest tests[] =3D {=
</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ cmocka_unit=
_test(test_probe_parameter_roundtrip),</span><br><span style=3D"color: hsl(=
120, 100%, 40%);">+ cmocka_unit_test(test_probe_reply_roundtrip),</s=
pan><br><span style=3D"color: hsl(120, 100%, 40%);">+ cmocka_unit_te=
st(test_probe_reply_wire_format),</span><br><span style=3D"color: hsl(120, =
100%, 40%);">+ cmocka_unit_test(test_probe_parameter_forward_compat)=
,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ cmocka_uni=
t_test(test_probe_parameter_too_short),</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+ cmocka_unit_test(test_find_tlv_value_truncated)=
,</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ cmocka_uni=
t_test(test_tlv_header_truncated),</span><br><span style=3D"color: hsl(120,=
100%, 40%);">+ };</span><br><span style=3D"color: hsl(120, 100%, 40%);"=
>+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ return cmocka=
_run_group_tests_name("oob tests", tests, NULL, NULL);</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+}</span><br><span></span><br><=
/pre><p>To view, visit <a href=3D"http://gerrit=2Eopenvpn=2Enet/c/openvpn/+=
/1741?usp=3Demail">change 1741</a>=2E To unsubscribe, or for help writing m=
ail filters, visit <a href=3D"http://gerrit=2Eopenvpn=2Enet/settings?usp=3D=
email">settings</a>=2E</p><div itemscope itemtype=3D"http://schema=2Eorg/Em=
ailMessage"><div itemscope itemprop=3D"action" itemtype=3D"http://schema=2E=
org/ViewAction"><link itemprop=3D"url" href=3D"http://gerrit=2Eopenvpn=2Ene=
t/c/openvpn/+/1741?usp=3Demail"/><meta itemprop=3D"name" content=3D"View Ch=
ange"/></div></div>
<div style=3D"display:none"> Gerrit-MessageType: newpa=
tchset </div>
<div style=3D"display:none"> Gerrit-Project: openvpn </div>
<=
div style=3D"display:none"> Gerrit-Branch: master </div>
<div style=3D"disp=
lay:none"> Gerrit-Change-Id: I1c8d302ac57c5603d622a7be14be369437388268 </di=
v>
<div style=3D"display:none"> Gerrit-Change-Number: 1741 </div>
<div styl=
e=3D"display:none"> Gerrit-PatchSet: 10 </div>
<div style=3D"display:none">=
Gerrit-Owner: stipa <lstipakov@gmail=2Ecom> </div>
<div style=3D"dis=
play:none"> Gerrit-Reviewer: flichtenheld <frank@lichtenheld=2Ecom> <=
/div>
<div style=3D"display:none"> Gerrit-Reviewer: plaisthos <arne-open=
vpn@rfc2549=2Eorg> </div>
<div style=3D"display:none"> Gerrit-CC: openvp=
n-devel <openvpn-devel@lists=2Esourceforge=2Enet> </div>
<div style=
=3D"display:none"> Gerrit-Attention: plaisthos <arne-openvpn@rfc2549=2Eo=
rg> </div>
<div style=3D"display:none"> Gerrit-Attention: flichtenheld &=
lt;frank@lichtenheld=2Ecom> </div>
</body></html>
--mZYurtwNp64=--
--===============8141231533105380139==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
--===============8141231533105380139==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
--===============8141231533105380139==--