[Openvpn-devel] [M] Change in openvpn[master]: oob: Wrap the client SERVER_PROBE with tls-auth/tls-crypt

"stipa \(Code Review\) via Openvpn-devel" <[email protected]> Wed, 29 Jul 2026 12:22:54 +0000
Newsgroups net.sourceforge.lists.openvpn-devel
Message-ID <2c0b50a8ecffb6689c386e2081ed6742b47bc7fc-EmailReplacePatchSet-HTML@gerrit.openvpn.net>
--===============0594241806466639992==
Content-Transfer-Encoding: 8bit
Content-Disposition: inline
Content-Type: multipart/alternative; boundary="dcUkYMGIcJ8="; charset=UTF-8

--dcUkYMGIcJ8=
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable

Attention is currently required from: plaisthos=2E

Hello plaisthos, 

I'd =
like you to reexamine a change=2E Please visit

    http://gerrit=2Eopenvpn=
=2Enet/c/openvpn/+/1750?usp=3Demail

to look at the new patch set (#14)=2E
=


Change subject: oob: Wrap the client SERVER_PROBE with tls-auth/tls-crypt=

=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: Wrap =
the client SERVER_PROBE with tls-auth/tls-crypt

The client --server-probe =
previously sent a plaintext SERVER_PROBE and
parsed replies by hand, so it =
only worked against a server with no
control-channel wrapping=2E Build a st=
andalone wrapping context for the
probe (mirroring the server's tls_auth_st=
andalone) and route both the
outgoing probe and the incoming replies throug=
h the same control-channel
path the rest of the code uses:

  - tls_wrap_oo=
b_standalone() wraps the probe payload, applying the
    tls-auth HMAC or t=
ls-crypt encryption (or nothing, when neither is
    configured)=2E
  - rea=
d_control_auth() unwraps each reply, verifying the HMAC /
    decrypting an=
d stripping the opcode + session id, on a per-packet
    copy of the wrappi=
ng context (as tls_pre_decrypt_lite() does)=2E It
    decrypts in place, so=
 oob_probe_handle_reply() now takes a mutable
    buffer=2E

With neither t=
ls-auth nor tls-crypt configured the context stays in
TLS_WRAP_NONE and the=
 on-wire probe is byte-for-byte identical to before,
so the plaintext case =
is unchanged=2E

tls-crypt-v2 is not supported yet: the server only learns =
the client key
from the WKc carried in the TLS handshake, which an out-of-b=
and probe
cannot provide=2E Such configurations skip probing and keep the c=
onfigured
remote order=2E

Change-Id: I1f9d7b5a5ec19bf77c0c212795f583c5ba4c=
03ac
Signed-off-by: Lev Stipakov <lev@openvpn=2Enet>
---
M doc/man-sections=
/client-options=2Erst
M src/openvpn/oob_client=2Ec
2 files changed, 117 ins=
ertions(+), 33 deletions(-)


  git pull ssh://gerrit=2Eopenvpn=2Enet:29418=
/openvpn refs/changes/50/1750/14

diff --git a/doc/man-sections/client-opti=
ons=2Erst b/doc/man-sections/client-options=2Erst
index 10ca808=2E=2Ef253a9=
4 100644
--- a/doc/man-sections/client-options=2Erst
+++ b/doc/man-sections=
/client-options=2Erst
@@ -616,9 +616,10 @@
   answering server is used, fal=
ling back to a built-in default of
   :code:`10` ms=2E
 
-  The probe is cu=
rrently sent without control-channel wrapping, so it only
-  works against =
a server configured without ``--tls-auth``,
-  ``--tls-crypt`` or ``--tls-c=
rypt-v2``=2E
+  The probe carries the same control-channel wrapping as a no=
rmal
+  connection (``--tls-auth`` or ``--tls-crypt``, when configured)=2E =
With
+  ``--tls-crypt-v2`` the remotes are left in their configured order,
=
+  because the server cannot unwrap an out-of-band probe yet=2E
 
   Only U=
DP remotes are probed; TCP remotes keep their configured
   position=2E Pro=
bing runs once, before the first connection attempt=2E
diff --git a/src/ope=
nvpn/oob_client=2Ec b/src/openvpn/oob_client=2Ec
index 3de791f=2E=2E78dc432=
 100644
--- a/src/openvpn/oob_client=2Ec
+++ b/src/openvpn/oob_client=2Ec
@=
@ -29,6 +29,8 @@
 #include "oob_client=2Eh"
 #include "openvpn=2Eh"
 #inclu=
de "oob=2Eh"
+#include "init=2Eh"
+#include "ssl=2Eh"
 #include "ssl_pkt=2E=
h"
 #include "session_id=2Eh"
 #include "socket=2Eh"
@@ -80,21 +82,54 @@
  =
   return (af =3D=3D AF_INET6) ? PROBE_AF_V6 : PROBE_AF_V4;
 }
 
-/* Build =
a plaintext SERVER_PROBE packet:
- *   [opcode | key_id=3D0] [client sessio=
n id] [SERVER_PROBE message]
- * This is the unauthenticated OOB wire forma=
t; adding tls-auth/tls-crypt
- * wrapping for the probe is a follow-up (it =
only works against a server with
- * no control-channel wrapping for now)=
=2E */
-static bool
-oob_probe_build_packet(struct buffer *buf, const struc=
t session_id *client_sid)
+/* Build a standalone control-channel wrapping c=
ontext for the probe, mirroring
+ * the tls_auth_standalone the server uses=
 to answer it=2E With neither tls-auth
+ * nor tls-crypt configured the con=
text stays in TLS_WRAP_NONE and the probe is
+ * sent in plaintext, exactly=
 as before; with either configured the probe is
+ * authenticated/encrypted=
 like any other control packet=2E Returns NULL (and
+ * logs) for configura=
tions the probe cannot wrap yet, in which case the caller
+ * skips probing=
 and keeps the configured remote order=2E */
+static struct tls_auth_standa=
lone *
+oob_probe_init_tls_auth_standalone(struct context *c, struct gc_are=
na *gc)
 {
-    const struct oob_probe_parameter param =3D {
-        =2Eti=
mestamp =3D (uint64_t)now,
-        =2Eflags =3D 0,
-    };
-    uint8_t he=
ader =3D (uint8_t)(P_CONTROL_OOB_V1 << P_OPCODE_SHIFT);
-    return buf_wri=
te_u8(buf, header) && session_id_write(client_sid, buf)
-           && oob_=
server_probe_write(buf, &param);
+    /* The probe runs before next_connect=
ion_entry() maps a connection entry
+     * into options=2Ece, so take the =
wrapping config from the first connection
+     * entry directly=2E A globa=
l --tls-auth/--tls-crypt is copied into every
+     * connection-list entry=
 by options_postprocess_mutate_ce(), so the first
+     * entry carries it =
even though options=2Ece does not yet=2E (Probing wraps a
+     * single pa=
cket for all remotes, so per-connection-block keys are not
+     * supporte=
d; the first entry's wrapping is used for all=2E) */
+    const struct conn=
ection_entry *ce =3D c->options=2Econnection_list->array[0];
+
+    /* tls-=
crypt-v2 wraps with a per-client key the server only learns from the
+     =
* wrapped client key (WKc) carried in the TLS handshake=2E An out-of-band
+=
     * probe carries no WKc, so the server cannot unwrap it; skip probing r=
ather
+     * than send something unverifiable=2E */
+    if (ce->tls_crypt=
_v2_file)
+    {
+        msg(D_LOW, "server-probe: not supported with tls-=
crypt-v2; using configured order");
+        return NULL;
+    }
+
+    /* =
Load the tls-auth/tls-crypt key material into c->c1=2Eks (a no-op if neithe=
r
+     * is configured)=2E This is run again per-connection later; calling=
 it early
+     * here is harmless=2E */
+    do_init_tls_wrap_key(c, ce);
=
+
+    struct tls_options to;
+    CLEAR(to);
+    init_tls_wrap_ctx(&to=2E=
tls_wrap, ce, c->options=2Etls_client, &c->c1=2Eks, &c->c1=2Epid_persist);
=
+    to=2Ereplay_window =3D c->options=2Ereplay_window;
+    to=2Ereplay_ti=
me =3D c->options=2Ereplay_time;
+
+    struct tls_auth_standalone *tas =3D=
 tls_auth_standalone_init(&to, gc);
+
+    /* Control-channel frame and wor=
k buffers, mirroring do_init_frame_tls()=2E */
+    tls_init_control_channe=
l_frame_parameters(&tas->frame, ce->tls_mtu);
+    tas->tls_wrap=2Ework =3D=
 alloc_buf_gc(BUF_SIZE(&tas->frame), gc);
+    tas->workbuf =3D alloc_buf_g=
c(BUF_SIZE(&tas->frame), gc);
+
+    return tas;
 }
 
 /* Open one native U=
DP probe socket per address family (v4 and v6)=2E Each is
@@ -145,9 +180,9 =
@@
 /* Parse one received datagram as a PROBE_REPLY and, if valid and match=
ing one
  * of the probes we sent, record the reply in results=2E */
 stati=
c void
-oob_probe_handle_reply(const uint8_t *data, int len, const struct s=
ession_id *client_sid,
-                       const struct sockaddr_storag=
e *from, const struct probe_target *targets,
-                       struct=
 oob_probe_result *results, int n)
+oob_probe_handle_reply(uint8_t *data, i=
nt len, const struct session_id *client_sid,
+                       const =
struct tls_wrap_ctx *base_wrap, const struct sockaddr_storage *from,
+     =
                  const struct probe_target *targets, struct oob_probe_resu=
lt *results, int n)
 {
     /* Need at least the opcode byte and the sessio=
n id=2E */
     if (len < 1 + (int)SID_SIZE || (data[0] >> P_OPCODE_SHIFT) =
!=3D P_CONTROL_OOB_V1)
@@ -157,7 +192,20 @@
 
     struct buffer buf;
     =
buf_set_read(&buf, data, (size_t)len);
-    buf_advance(&buf, 1 + SID_SIZE)=
; /* skip opcode + server session id */
+
+    /* Unwrap the reply with the=
 same control-channel path the server used to
+     * wrap it: this verifie=
s the tls-auth HMAC / decrypts tls-crypt, and (in all
+     * modes) strips=
 the opcode + server session id, leaving buf at the TLV
+     * payload=2E =
With no tls-auth/tls-crypt it just strips those header bytes, as
+     * be=
fore=2E read_control_auth() mutates the wrapping context, so we work on a
+=
     * per-packet copy (as tls_pre_decrypt_lite() does on the server)=2E Th=
e peer
+     * address is only used for log messages, and tls_options only =
for
+     * tls-crypt-v2 metadata checks, so both are passed as NULL=2E */
=
+    struct tls_wrap_ctx wrap =3D *base_wrap;
+    if (!read_control_auth(&=
buf, &wrap, NULL, NULL))
+    {
+        return; /* not for us, or failed a=
uthentication */
+    }
 
     struct oob_probe_reply reply;
     if (!oob_=
client_reply_read(&buf, &reply))
@@ -208,8 +256,9 @@
  * matches a probe we=
 sent=2E Returns true if every sent probe has been answered=2E */
 static b=
ool
 oob_probe_receive_slice(const struct probe_ctx *pc, const struct timev=
al *deadline,
-                        const struct session_id *client_sid,=
 const struct probe_target *targets,
-                        struct oob_pr=
obe_result *results, int n, int outstanding)
+                        const=
 struct session_id *client_sid, const struct tls_wrap_ctx *wrap,
+         =
               const struct probe_target *targets, struct oob_probe_result =
*results, int n,
+                        int outstanding)
 {
     while (t=
rue)
     {
@@ -259,7 +308,7 @@
                                     (struc=
t sockaddr *)&from, &fromlen);
             if (len > 0)
             {
-  =
              oob_probe_handle_reply(data, len, client_sid, &from, targets,=
 results, n);
+                oob_probe_handle_reply(data, len, client_sid=
, wrap, &from, targets, results, n);
             }
         }
         if =
(oob_count_answered(targets, results, n) >=3D outstanding)
@@ -296,8 +345,8=
 @@
  * once the window elapses or every sent probe has been answered=2E */=

 static void
 oob_probe_collect(const struct probe_ctx *pc, const struct b=
uffer *probe,
-                  const struct session_id *client_sid, const=
 struct probe_target *targets,
-                  struct oob_probe_result *=
results, int n)
+                  const struct session_id *client_sid, con=
st struct tls_wrap_ctx *wrap,
+                  const struct probe_target =
*targets, struct oob_probe_result *results, int n)
 {
     /* number of pro=
bes we actually sent: stop early once they all answer */
     int want =3D =
0;
@@ -321,7 +370,7 @@
             deadline=2Etv_usec -=3D 1000000;
      =
   }
 
-        if (oob_probe_receive_slice(pc, &deadline, client_sid, targ=
ets, results, n, want))
+        if (oob_probe_receive_slice(pc, &deadline,=
 client_sid, wrap, targets, results, n, want))
         {
             retu=
rn; /* all answered */
         }
@@ -363,6 +412,16 @@
 
     struct gc_are=
na gc =3D gc_new();
 
+    /* Wrapping context for the probe (tls-auth/tls-=
crypt, or plaintext if
+     * neither)=2E NULL means this configuration ca=
nnot be probed; keep the
+     * configured order=2E */
+    struct tls_aut=
h_standalone *tas =3D oob_probe_init_tls_auth_standalone(c, &gc);
+    if (=
!tas)
+    {
+        gc_free(&gc);
+        return;
+    }
+
     /* A sin=
gle random session id identifies all of our probes; servers echo it
      *=
 back in the reply's peer_session_id, letting us reject spoofed replies=2E =
*/
     struct session_id client_sid;
@@ -372,6 +431,7 @@
     if (oob_prob=
e_sockets_open(&pc) =3D=3D 0)
     {
         msg(D_LOW, "server-probe: cou=
ld not open probe socket; using configured order");
+        tls_auth_stand=
alone_free(tas);
         gc_free(&gc);
         return;
     }
@@ -379,17 =
+439,39 @@
     struct probe_target *targets =3D gc_malloc(sizeof(*targets)=
 * l->len, true, &gc);
     struct oob_probe_result *results =3D gc_malloc(=
sizeof(*results) * l->len, true, &gc);
 
-    struct buffer probe =3D alloc=
_buf_gc(256, &gc);
-    if (!oob_probe_build_packet(&probe, &client_sid))
+=
    /* Build the probe once and reuse the same bytes for every remote and f=
or
+     * resends: the SERVER_PROBE payload is a single probe_parameter TL=
V, wrapped
+     * (or sent in plaintext) like any other control packet=2E =
The client session
+     * id is prepended as the sender session id=2E */
+=
    struct buffer payload =3D alloc_buf_gc(64, &gc);
+    const struct oob_=
probe_parameter param =3D {
+        =2Etimestamp =3D (uint64_t)now,
+     =
   =2Eflags =3D 0,
+    };
+    if (!oob_server_probe_write(&payload, &para=
m))
     {
-        msg(D_LOW, "server-probe: could not build probe packet;=
 using configured order");
+        msg(D_LOW, "server-probe: could not bui=
ld probe payload; using configured order");
         oob_probe_sockets_clos=
e(&pc);
+        tls_auth_standalone_free(tas);
         gc_free(&gc);
    =
     return;
     }
 
-    msg(D_LOW, "server-probe: probing %d remote(s) w=
ith a %d ms window", l->len,
-        OOB_PROBE_WINDOW_MS);
+    struct buf=
fer probe =3D tls_wrap_oob_standalone(&tas->tls_wrap, tas, &client_sid, &pa=
yload);
+    if (!BLEN(&probe))
+    {
+        msg(D_LOW, "server-probe: c=
ould not wrap probe packet; using configured order");
+        oob_probe_so=
ckets_close(&pc);
+        tls_auth_standalone_free(tas);
+        gc_free(=
&gc);
+        return;
+    }
+
+    const char *wrap_name =3D (tas->tls_wr=
ap=2Emode =3D=3D TLS_WRAP_CRYPT)  ? "tls-crypt"
+                          =
  : (tas->tls_wrap=2Emode =3D=3D TLS_WRAP_AUTH) ? "tls-auth"
+             =
                                                       : "none (plaintext)"=
;
+    msg(D_LOW, "server-probe: probing %d remote(s) with a %d ms window, =
control-channel wrapping: %s",
+        l->len, OOB_PROBE_WINDOW_MS, wrap_n=
ame);
 
     /* Send a probe to each configured remote=2E */
     int sent_=
count =3D 0;
@@ -450,7 +532,7 @@
 
     if (sent_count > 0)
     {
-       =
 oob_probe_collect(&pc, &probe, &client_sid, targets, results, l->len);
+  =
      oob_probe_collect(&pc, &probe, &client_sid, &tas->tls_wrap, targets, =
results, l->len);
     }
     oob_probe_sockets_close(&pc);
 
@@ -496,5 +57=
8,6 @@
     msg(M_INFO, "server-probe: %d of %d remote(s) answered; connect=
ing best-first", responded,
         l->len);
 
+    tls_auth_standalone_fr=
ee(tas);
     gc_free(&gc);
 }

-- 
To view, visit http://gerrit=2Eopenvpn=
=2Enet/c/openvpn/+/1750?usp=3Demail
To unsubscribe, or for help writing mai=
l filters, visit http://gerrit=2Eopenvpn=2Enet/settings?usp=3Demail

Gerrit=
-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Ger=
rit-Change-Id: I1f9d7b5a5ec19bf77c0c212795f583c5ba4c03ac
Gerrit-Change-Numb=
er: 1750
Gerrit-PatchSet: 14
Gerrit-Owner: stipa <lstipakov@gmail=2Ecom>
Ge=
rrit-Reviewer: plaisthos <arne-openvpn@rfc2549=2Eorg>
Gerrit-CC: openvpn-de=
vel <openvpn-devel@lists=2Esourceforge=2Enet>
Gerrit-Attention: plaisthos <=
arne-openvpn@rfc2549=2Eorg>

--dcUkYMGIcJ8=
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: plaisthos=2E </p>
<p>stipa <strong>uploaded patch set=
 #14</strong> to this change=2E</p><p><a href=3D"http://gerrit=2Eopenvpn=2E=
net/c/openvpn/+/1750?usp=3Demail">View Change</a></p><pre class=3D"blocks" =
style=3D"font-family: monospace,monospace; white-space: pre-wrap;">oob: Wra=
p the client SERVER_PROBE with tls-auth/tls-crypt<br><br>The client --serve=
r-probe previously sent a plaintext SERVER_PROBE and<br>parsed replies by h=
and, so it only worked against a server with no<br>control-channel wrapping=
=2E Build a standalone wrapping context for the<br>probe (mirroring the ser=
ver&#39;s tls_auth_standalone) and route both the<br>outgoing probe and the=
 incoming replies through the same control-channel<br>path the rest of the =
code uses:<br><br>  - tls_wrap_oob_standalone() wraps the probe payload, ap=
plying the<br>    tls-auth HMAC or tls-crypt encryption (or nothing, when n=
either is<br>    configured)=2E<br>  - read_control_auth() unwraps each rep=
ly, verifying the HMAC /<br>    decrypting and stripping the opcode + sessi=
on id, on a per-packet<br>    copy of the wrapping context (as tls_pre_decr=
ypt_lite() does)=2E It<br>    decrypts in place, so oob_probe_handle_reply(=
) now takes a mutable<br>    buffer=2E<br><br>With neither tls-auth nor tls=
-crypt configured the context stays in<br>TLS_WRAP_NONE and the on-wire pro=
be is byte-for-byte identical to before,<br>so the plaintext case is unchan=
ged=2E<br><br>tls-crypt-v2 is not supported yet: the server only learns the=
 client key<br>from the WKc carried in the TLS handshake, which an out-of-b=
and probe<br>cannot provide=2E Such configurations skip probing and keep th=
e configured<br>remote order=2E<br><br>Change-Id: I1f9d7b5a5ec19bf77c0c2127=
95f583c5ba4c03ac<br>Signed-off-by: Lev Stipakov &lt;lev@openvpn=2Enet&gt;<b=
r>---<br>M doc/man-sections/client-options=2Erst<br>M src/openvpn/oob_clien=
t=2Ec<br>2 files changed, 117 insertions(+), 33 deletions(-)<br><br></pre><=
pre class=3D"blocks" style=3D"font-family: monospace,monospace; white-space=
: pre-wrap;">git pull ssh://gerrit=2Eopenvpn=2Enet:29418/openvpn refs/chang=
es/50/1750/14</pre><pre style=3D"font-family: monospace,monospace; white-sp=
ace: pre-wrap;"><span>diff --git a/doc/man-sections/client-options=2Erst b/=
doc/man-sections/client-options=2Erst</span><br><span>index 10ca808=2E=2Ef2=
53a94 100644</span><br><span>--- a/doc/man-sections/client-options=2Erst</s=
pan><br><span>+++ b/doc/man-sections/client-options=2Erst</span><br><span>@=
@ -616,9 +616,10 @@</span><br><span>   answering server is used, falling ba=
ck to a built-in default of</span><br><span>   :code:`10` ms=2E</span><br><=
span> </span><br><span style=3D"color: hsl(0, 100%, 40%);">-  The probe is =
currently sent without control-channel wrapping, so it only</span><br><span=
 style=3D"color: hsl(0, 100%, 40%);">-  works against a server configured w=
ithout ``--tls-auth``,</span><br><span style=3D"color: hsl(0, 100%, 40%);">=
-  ``--tls-crypt`` or ``--tls-crypt-v2``=2E</span><br><span style=3D"color:=
 hsl(120, 100%, 40%);">+  The probe carries the same control-channel wrappi=
ng as a normal</span><br><span style=3D"color: hsl(120, 100%, 40%);">+  con=
nection (``--tls-auth`` or ``--tls-crypt``, when configured)=2E With</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+  ``--tls-crypt-v2`` the r=
emotes are left in their configured order,</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+  because the server cannot unwrap an out-of-band pr=
obe yet=2E</span><br><span> </span><br><span>   Only UDP remotes are probed=
; TCP remotes keep their configured</span><br><span>   position=2E Probing =
runs once, before the first connection attempt=2E</span><br><span>diff --gi=
t a/src/openvpn/oob_client=2Ec b/src/openvpn/oob_client=2Ec</span><br><span=
>index 3de791f=2E=2E78dc432 100644</span><br><span>--- a/src/openvpn/oob_cl=
ient=2Ec</span><br><span>+++ b/src/openvpn/oob_client=2Ec</span><br><span>@=
@ -29,6 +29,8 @@</span><br><span> #include &quot;oob_client=2Eh&quot;</span=
><br><span> #include &quot;openvpn=2Eh&quot;</span><br><span> #include &quo=
t;oob=2Eh&quot;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+#inc=
lude &quot;init=2Eh&quot;</span><br><span style=3D"color: hsl(120, 100%, 40=
%);">+#include &quot;ssl=2Eh&quot;</span><br><span> #include &quot;ssl_pkt=
=2Eh&quot;</span><br><span> #include &quot;session_id=2Eh&quot;</span><br><=
span> #include &quot;socket=2Eh&quot;</span><br><span>@@ -80,21 +82,54 @@</=
span><br><span>     return (af =3D=3D AF_INET6) ? PROBE_AF_V6 : PROBE_AF_V4=
;</span><br><span> }</span><br><span> </span><br><span style=3D"color: hsl(=
0, 100%, 40%);">-/* Build a plaintext SERVER_PROBE packet:</span><br><span =
style=3D"color: hsl(0, 100%, 40%);">- *   [opcode | key_id=3D0] [client ses=
sion id] [SERVER_PROBE message]</span><br><span style=3D"color: hsl(0, 100%=
, 40%);">- * This is the unauthenticated OOB wire format; adding tls-auth/t=
ls-crypt</span><br><span style=3D"color: hsl(0, 100%, 40%);">- * wrapping f=
or the probe is a follow-up (it only works against a server with</span><br>=
<span style=3D"color: hsl(0, 100%, 40%);">- * no control-channel wrapping f=
or now)=2E */</span><br><span style=3D"color: hsl(0, 100%, 40%);">-static b=
ool</span><br><span style=3D"color: hsl(0, 100%, 40%);">-oob_probe_build_pa=
cket(struct buffer *buf, const struct session_id *client_sid)</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+/* Build a standalone control-cha=
nnel wrapping context for the probe, mirroring</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+ * the tls_auth_standalone the server uses to an=
swer it=2E With neither tls-auth</span><br><span style=3D"color: hsl(120, 1=
00%, 40%);">+ * nor tls-crypt configured the context stays in TLS_WRAP_NONE=
 and the probe is</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ *=
 sent in plaintext, exactly as before; with either configured the probe is<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+ * authenticated/enc=
rypted like any other control packet=2E Returns NULL (and</span><br><span s=
tyle=3D"color: hsl(120, 100%, 40%);">+ * logs) for configurations the probe=
 cannot wrap yet, in which case the caller</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+ * skips probing and keeps the configured remote ord=
er=2E */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+static stru=
ct tls_auth_standalone *</span><br><span style=3D"color: hsl(120, 100%, 40%=
);">+oob_probe_init_tls_auth_standalone(struct context *c, struct gc_arena =
*gc)</span><br><span> {</span><br><span style=3D"color: hsl(0, 100%, 40%);"=
>-    const struct oob_probe_parameter param =3D {</span><br><span style=3D=
"color: hsl(0, 100%, 40%);">-        =2Etimestamp =3D (uint64_t)now,</span>=
<br><span style=3D"color: hsl(0, 100%, 40%);">-        =2Eflags =3D 0,</spa=
n><br><span style=3D"color: hsl(0, 100%, 40%);">-    };</span><br><span sty=
le=3D"color: hsl(0, 100%, 40%);">-    uint8_t header =3D (uint8_t)(P_CONTRO=
L_OOB_V1 &lt;&lt; P_OPCODE_SHIFT);</span><br><span style=3D"color: hsl(0, 1=
00%, 40%);">-    return buf_write_u8(buf, header) &amp;&amp; session_id_wri=
te(client_sid, buf)</span><br><span style=3D"color: hsl(0, 100%, 40%);">-  =
         &amp;&amp; oob_server_probe_write(buf, &amp;param);</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+    /* The probe runs before next_=
connection_entry() maps a connection entry</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+     * into options=2Ece, so take the wrapping confi=
g from the first connection</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+     * entry directly=2E A global --tls-auth/--tls-crypt is copied =
into every</span><br><span style=3D"color: hsl(120, 100%, 40%);">+     * co=
nnection-list entry by options_postprocess_mutate_ce(), so the first</span>=
<br><span style=3D"color: hsl(120, 100%, 40%);">+     * entry carries it ev=
en though options=2Ece does not yet=2E (Probing wraps a</span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+     * single packet for all remotes, s=
o per-connection-block keys are not</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+     * supported; the first entry&#39;s wrapping is used fo=
r all=2E) */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    con=
st struct connection_entry *ce =3D c-&gt;options=2Econnection_list-&gt;arra=
y[0];</span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><sp=
an style=3D"color: hsl(120, 100%, 40%);">+    /* tls-crypt-v2 wraps with a =
per-client key the server only learns from the</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+     * wrapped client key (WKc) carried in the T=
LS handshake=2E An out-of-band</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+     * probe carries no WKc, so the server cannot unwrap it; ski=
p probing rather</span><br><span style=3D"color: hsl(120, 100%, 40%);">+   =
  * than send something unverifiable=2E */</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+    if (ce-&gt;tls_crypt_v2_file)</span><br><span st=
yle=3D"color: hsl(120, 100%, 40%);">+    {</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+        msg(D_LOW, &quot;server-probe: not supported=
 with tls-crypt-v2; using configured order&quot;);</span><br><span style=3D=
"color: hsl(120, 100%, 40%);">+        return NULL;</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%);">=
+    /* Load the tls-auth/tls-crypt key material into c-&gt;c1=2Eks (a no-o=
p if neither</span><br><span style=3D"color: hsl(120, 100%, 40%);">+     * =
is configured)=2E This is run again per-connection later; calling it early<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+     * here is harml=
ess=2E */</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    do_ini=
t_tls_wrap_key(c, ce);</span><br><span style=3D"color: hsl(120, 100%, 40%);=
">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    struct tls_o=
ptions to;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    CLEAR=
(to);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    init_tls_w=
rap_ctx(&amp;to=2Etls_wrap, ce, c-&gt;options=2Etls_client, &amp;c-&gt;c1=
=2Eks, &amp;c-&gt;c1=2Epid_persist);</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+    to=2Ereplay_window =3D c-&gt;options=2Ereplay_window;<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+    to=2Ereplay_time=
 =3D c-&gt;options=2Ereplay_time;</span><br><span style=3D"color: hsl(120, =
100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    s=
truct tls_auth_standalone *tas =3D tls_auth_standalone_init(&amp;to, gc);</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+    /* Control-channel frame and work bu=
ffers, mirroring do_init_frame_tls()=2E */</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+    tls_init_control_channel_frame_parameters(&amp;t=
as-&gt;frame, ce-&gt;tls_mtu);</span><br><span style=3D"color: hsl(120, 100=
%, 40%);">+    tas-&gt;tls_wrap=2Ework =3D alloc_buf_gc(BUF_SIZE(&amp;tas-&=
gt;frame), gc);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    =
tas-&gt;workbuf =3D alloc_buf_gc(BUF_SIZE(&amp;tas-&gt;frame), gc);</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+</span><br><span style=3D"c=
olor: hsl(120, 100%, 40%);">+    return tas;</span><br><span> }</span><br><=
span> </span><br><span> /* Open one native UDP probe socket per address fam=
ily (v4 and v6)=2E Each is</span><br><span>@@ -145,9 +180,9 @@</span><br><s=
pan> /* Parse one received datagram as a PROBE_REPLY and, if valid and matc=
hing one</span><br><span>  * of the probes we sent, record the reply in res=
ults=2E */</span><br><span> static void</span><br><span style=3D"color: hsl=
(0, 100%, 40%);">-oob_probe_handle_reply(const uint8_t *data, int len, cons=
t struct session_id *client_sid,</span><br><span style=3D"color: hsl(0, 100=
%, 40%);">-                       const struct sockaddr_storage *from, cons=
t struct probe_target *targets,</span><br><span style=3D"color: hsl(0, 100%=
, 40%);">-                       struct oob_probe_result *results, int n)</=
span><br><span style=3D"color: hsl(120, 100%, 40%);">+oob_probe_handle_repl=
y(uint8_t *data, int len, const struct session_id *client_sid,</span><br><s=
pan style=3D"color: hsl(120, 100%, 40%);">+                       const str=
uct tls_wrap_ctx *base_wrap, const struct sockaddr_storage *from,</span><br=
><span style=3D"color: hsl(120, 100%, 40%);">+                       const =
struct probe_target *targets, struct oob_probe_result *results, int n)</spa=
n><br><span> {</span><br><span>     /* Need at least the opcode byte and th=
e session id=2E */</span><br><span>     if (len &lt; 1 + (int)SID_SIZE || (=
data[0] &gt;&gt; P_OPCODE_SHIFT) !=3D P_CONTROL_OOB_V1)</span><br><span>@@ =
-157,7 +192,20 @@</span><br><span> </span><br><span>     struct buffer buf;=
</span><br><span>     buf_set_read(&amp;buf, data, (size_t)len);</span><br>=
<span style=3D"color: hsl(0, 100%, 40%);">-    buf_advance(&amp;buf, 1 + SI=
D_SIZE); /* skip opcode + server session id */</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+</span><br><span style=3D"color: hsl(120, 100%, =
40%);">+    /* Unwrap the reply with the same control-channel path the serv=
er used to</span><br><span style=3D"color: hsl(120, 100%, 40%);">+     * wr=
ap it: this verifies the tls-auth HMAC / decrypts tls-crypt, and (in all</s=
pan><br><span style=3D"color: hsl(120, 100%, 40%);">+     * modes) strips t=
he opcode + server session id, leaving buf at the TLV</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+     * payload=2E With no tls-auth/tls-cr=
ypt it just strips those header bytes, as</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+     * before=2E read_control_auth() mutates the wrap=
ping context, so we work on a</span><br><span style=3D"color: hsl(120, 100%=
, 40%);">+     * per-packet copy (as tls_pre_decrypt_lite() does on the ser=
ver)=2E The peer</span><br><span style=3D"color: hsl(120, 100%, 40%);">+   =
  * address is only used for log messages, and tls_options only for</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+     * tls-crypt-v2 metadat=
a checks, so both are passed as NULL=2E */</span><br><span style=3D"color: =
hsl(120, 100%, 40%);">+    struct tls_wrap_ctx wrap =3D *base_wrap;</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+    if (!read_control_auth(=
&amp;buf, &amp;wrap, NULL, NULL))</span><br><span style=3D"color: hsl(120, =
100%, 40%);">+    {</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
        return; /* not for us, or failed authentication */</span><br><span =
style=3D"color: hsl(120, 100%, 40%);">+    }</span><br><span> </span><br><s=
pan>     struct oob_probe_reply reply;</span><br><span>     if (!oob_client=
_reply_read(&amp;buf, &amp;reply))</span><br><span>@@ -208,8 +256,9 @@</spa=
n><br><span>  * matches a probe we sent=2E Returns true if every sent probe=
 has been answered=2E */</span><br><span> static bool</span><br><span> oob_=
probe_receive_slice(const struct probe_ctx *pc, const struct timeval *deadl=
ine,</span><br><span style=3D"color: hsl(0, 100%, 40%);">-                 =
       const struct session_id *client_sid, const struct probe_target *targ=
ets,</span><br><span style=3D"color: hsl(0, 100%, 40%);">-                 =
       struct oob_probe_result *results, int n, int outstanding)</span><br>=
<span style=3D"color: hsl(120, 100%, 40%);">+                        const =
struct session_id *client_sid, const struct tls_wrap_ctx *wrap,</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+                        const s=
truct probe_target *targets, struct oob_probe_result *results, int n,</span=
><br><span style=3D"color: hsl(120, 100%, 40%);">+                        i=
nt outstanding)</span><br><span> {</span><br><span>     while (true)</span>=
<br><span>     {</span><br><span>@@ -259,7 +308,7 @@</span><br><span>      =
                               (struct sockaddr *)&amp;from, &amp;fromlen);=
</span><br><span>             if (len &gt; 0)</span><br><span>             =
{</span><br><span style=3D"color: hsl(0, 100%, 40%);">-                oob_=
probe_handle_reply(data, len, client_sid, &amp;from, targets, results, n);<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+                oob_=
probe_handle_reply(data, len, client_sid, wrap, &amp;from, targets, results=
, n);</span><br><span>             }</span><br><span>         }</span><br><=
span>         if (oob_count_answered(targets, results, n) &gt;=3D outstandi=
ng)</span><br><span>@@ -296,8 +345,8 @@</span><br><span>  * once the window=
 elapses or every sent probe has been answered=2E */</span><br><span> stati=
c void</span><br><span> oob_probe_collect(const struct probe_ctx *pc, const=
 struct buffer *probe,</span><br><span style=3D"color: hsl(0, 100%, 40%);">=
-                  const struct session_id *client_sid, const struct probe_=
target *targets,</span><br><span style=3D"color: hsl(0, 100%, 40%);">-     =
             struct oob_probe_result *results, int n)</span><br><span style=
=3D"color: hsl(120, 100%, 40%);">+                  const struct session_id=
 *client_sid, const struct tls_wrap_ctx *wrap,</span><br><span style=3D"col=
or: hsl(120, 100%, 40%);">+                  const struct probe_target *tar=
gets, struct oob_probe_result *results, int n)</span><br><span> {</span><br=
><span>     /* number of probes we actually sent: stop early once they all =
answer */</span><br><span>     int want =3D 0;</span><br><span>@@ -321,7 +3=
70,7 @@</span><br><span>             deadline=2Etv_usec -=3D 1000000;</span=
><br><span>         }</span><br><span> </span><br><span style=3D"color: hsl=
(0, 100%, 40%);">-        if (oob_probe_receive_slice(pc, &amp;deadline, cl=
ient_sid, targets, results, n, want))</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+        if (oob_probe_receive_slice(pc, &amp;deadline, cl=
ient_sid, wrap, targets, results, n, want))</span><br><span>         {</spa=
n><br><span>             return; /* all answered */</span><br><span>       =
  }</span><br><span>@@ -363,6 +412,16 @@</span><br><span> </span><br><span>=
     struct gc_arena gc =3D gc_new();</span><br><span> </span><br><span sty=
le=3D"color: hsl(120, 100%, 40%);">+    /* Wrapping context for the probe (=
tls-auth/tls-crypt, or plaintext if</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+     * neither)=2E NULL means this configuration cannot be =
probed; keep the</span><br><span style=3D"color: hsl(120, 100%, 40%);">+   =
  * configured order=2E */</span><br><span style=3D"color: hsl(120, 100%, 4=
0%);">+    struct tls_auth_standalone *tas =3D oob_probe_init_tls_auth_stan=
dalone(c, &amp;gc);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+=
    if (!tas)</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    {<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+        gc_free(&amp=
;gc);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+        return=
;</span><br><span style=3D"color: hsl(120, 100%, 40%);">+    }</span><br><s=
pan style=3D"color: hsl(120, 100%, 40%);">+</span><br><span>     /* A singl=
e random session id identifies all of our probes; servers echo it</span><br=
><span>      * back in the reply&#39;s peer_session_id, letting us reject s=
poofed replies=2E */</span><br><span>     struct session_id client_sid;</sp=
an><br><span>@@ -372,6 +431,7 @@</span><br><span>     if (oob_probe_sockets=
_open(&amp;pc) =3D=3D 0)</span><br><span>     {</span><br><span>         ms=
g(D_LOW, &quot;server-probe: could not open probe socket; using configured =
order&quot;);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+      =
  tls_auth_standalone_free(tas);</span><br><span>         gc_free(&amp;gc);=
</span><br><span>         return;</span><br><span>     }</span><br><span>@@=
 -379,17 +439,39 @@</span><br><span>     struct probe_target *targets =3D g=
c_malloc(sizeof(*targets) * l-&gt;len, true, &amp;gc);</span><br><span>    =
 struct oob_probe_result *results =3D gc_malloc(sizeof(*results) * l-&gt;le=
n, true, &amp;gc);</span><br><span> </span><br><span style=3D"color: hsl(0,=
 100%, 40%);">-    struct buffer probe =3D alloc_buf_gc(256, &amp;gc);</spa=
n><br><span style=3D"color: hsl(0, 100%, 40%);">-    if (!oob_probe_build_p=
acket(&amp;probe, &amp;client_sid))</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+    /* Build the probe once and reuse the same bytes for ev=
ery remote and for</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
    * resends: the SERVER_PROBE payload is a single probe_parameter TLV, wr=
apped</span><br><span style=3D"color: hsl(120, 100%, 40%);">+     * (or sen=
t in plaintext) like any other control packet=2E The client session</span><=
br><span style=3D"color: hsl(120, 100%, 40%);">+     * id is prepended as t=
he sender session id=2E */</span><br><span style=3D"color: hsl(120, 100%, 4=
0%);">+    struct buffer payload =3D alloc_buf_gc(64, &amp;gc);</span><br><=
span style=3D"color: hsl(120, 100%, 40%);">+    const struct oob_probe_para=
meter param =3D {</span><br><span style=3D"color: hsl(120, 100%, 40%);">+  =
      =2Etimestamp =3D (uint64_t)now,</span><br><span style=3D"color: hsl(1=
20, 100%, 40%);">+        =2Eflags =3D 0,</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+    };</span><br><span style=3D"color: hsl(120, 100%,=
 40%);">+    if (!oob_server_probe_write(&amp;payload, &amp;param))</span><=
br><span>     {</span><br><span style=3D"color: hsl(0, 100%, 40%);">-      =
  msg(D_LOW, &quot;server-probe: could not build probe packet; using config=
ured order&quot;);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+ =
       msg(D_LOW, &quot;server-probe: could not build probe payload; using =
configured order&quot;);</span><br><span>         oob_probe_sockets_close(&=
amp;pc);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+        tls=
_auth_standalone_free(tas);</span><br><span>         gc_free(&amp;gc);</spa=
n><br><span>         return;</span><br><span>     }</span><br><span> </span=
><br><span style=3D"color: hsl(0, 100%, 40%);">-    msg(D_LOW, &quot;server=
-probe: probing %d remote(s) with a %d ms window&quot;, l-&gt;len,</span><b=
r><span style=3D"color: hsl(0, 100%, 40%);">-        OOB_PROBE_WINDOW_MS);<=
/span><br><span style=3D"color: hsl(120, 100%, 40%);">+    struct buffer pr=
obe =3D tls_wrap_oob_standalone(&amp;tas-&gt;tls_wrap, tas, &amp;client_sid=
, &amp;payload);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+   =
 if (!BLEN(&amp;probe))</span><br><span style=3D"color: hsl(120, 100%, 40%)=
;">+    {</span><br><span style=3D"color: hsl(120, 100%, 40%);">+        ms=
g(D_LOW, &quot;server-probe: could not wrap probe packet; using configured =
order&quot;);</span><br><span style=3D"color: hsl(120, 100%, 40%);">+      =
  oob_probe_sockets_close(&amp;pc);</span><br><span style=3D"color: hsl(120=
, 100%, 40%);">+        tls_auth_standalone_free(tas);</span><br><span styl=
e=3D"color: hsl(120, 100%, 40%);">+        gc_free(&amp;gc);</span><br><spa=
n style=3D"color: hsl(120, 100%, 40%);">+        return;</span><br><span st=
yle=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%)=
;">+    const char *wrap_name =3D (tas-&gt;tls_wrap=2Emode =3D=3D TLS_WRAP_=
CRYPT)  ? &quot;tls-crypt&quot;</span><br><span style=3D"color: hsl(120, 10=
0%, 40%);">+                            : (tas-&gt;tls_wrap=2Emode =3D=3D T=
LS_WRAP_AUTH) ? &quot;tls-auth&quot;</span><br><span style=3D"color: hsl(12=
0, 100%, 40%);">+                                                          =
          : &quot;none (plaintext)&quot;;</span><br><span style=3D"color: h=
sl(120, 100%, 40%);">+    msg(D_LOW, &quot;server-probe: probing %d remote(=
s) with a %d ms window, control-channel wrapping: %s&quot;,</span><br><span=
 style=3D"color: hsl(120, 100%, 40%);">+        l-&gt;len, OOB_PROBE_WINDOW=
_MS, wrap_name);</span><br><span> </span><br><span>     /* Send a probe to =
each configured remote=2E */</span><br><span>     int sent_count =3D 0;</sp=
an><br><span>@@ -450,7 +532,7 @@</span><br><span> </span><br><span>     if =
(sent_count &gt; 0)</span><br><span>     {</span><br><span style=3D"color: =
hsl(0, 100%, 40%);">-        oob_probe_collect(&amp;pc, &amp;probe, &amp;cl=
ient_sid, targets, results, l-&gt;len);</span><br><span style=3D"color: hsl=
(120, 100%, 40%);">+        oob_probe_collect(&amp;pc, &amp;probe, &amp;cli=
ent_sid, &amp;tas-&gt;tls_wrap, targets, results, l-&gt;len);</span><br><sp=
an>     }</span><br><span>     oob_probe_sockets_close(&amp;pc);</span><br>=
<span> </span><br><span>@@ -496,5 +578,6 @@</span><br><span>     msg(M_INFO=
, &quot;server-probe: %d of %d remote(s) answered; connecting best-first&qu=
ot;, responded,</span><br><span>         l-&gt;len);</span><br><span> </spa=
n><br><span style=3D"color: hsl(120, 100%, 40%);">+    tls_auth_standalone_=
free(tas);</span><br><span>     gc_free(&amp;gc);</span><br><span> }</span>=
<br><span></span><br></pre><p>To view, visit <a href=3D"http://gerrit=2Eope=
nvpn=2Enet/c/openvpn/+/1750?usp=3Demail">change 1750</a>=2E To unsubscribe,=
 or for help writing mail filters, visit <a href=3D"http://gerrit=2Eopenvpn=
=2Enet/settings?usp=3Demail">settings</a>=2E</p><div itemscope itemtype=3D"=
http://schema=2Eorg/EmailMessage"><div itemscope itemprop=3D"action" itemty=
pe=3D"http://schema=2Eorg/ViewAction"><link itemprop=3D"url" href=3D"http:/=
/gerrit=2Eopenvpn=2Enet/c/openvpn/+/1750?usp=3Demail"/><meta itemprop=3D"na=
me" content=3D"View Change"/></div></div>

<div style=3D"display:none"> Ger=
rit-MessageType: newpatchset </div>
<div style=3D"display:none"> Gerrit-Pro=
ject: openvpn </div>
<div style=3D"display:none"> Gerrit-Branch: master </d=
iv>
<div style=3D"display:none"> Gerrit-Change-Id: I1f9d7b5a5ec19bf77c0c212=
795f583c5ba4c03ac </div>
<div style=3D"display:none"> Gerrit-Change-Number:=
 1750 </div>
<div style=3D"display:none"> Gerrit-PatchSet: 14 </div>
<div s=
tyle=3D"display:none"> Gerrit-Owner: stipa &lt;lstipakov@gmail=2Ecom&gt; </=
div>
<div style=3D"display:none"> Gerrit-Reviewer: plaisthos &lt;arne-openv=
pn@rfc2549=2Eorg&gt; </div>
<div style=3D"display:none"> Gerrit-CC: openvpn=
-devel &lt;openvpn-devel@lists=2Esourceforge=2Enet&gt; </div>
<div style=3D=
"display:none"> Gerrit-Attention: plaisthos &lt;arne-openvpn@rfc2549=2Eorg&=
gt; </div>

</body></html>
--dcUkYMGIcJ8=--


--===============0594241806466639992==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline


--===============0594241806466639992==
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

--===============0594241806466639992==--