Attention is currently required from: Arne Schwabe.
Hello Arne Schwabe,
I'd like you to reexamine a change. Please visit
https://gerrit.openvpn.net/c/openvpn/+/1983?usp=email
to look at the new patch set (#2).
Change subject: dco_linux: enable asymmetric peer IDs when supported
......................................................................
dco_linux: enable asymmetric peer IDs when supported
When notifying ovpn about a new peer, also send the TX peer ID so the
module can forward packets with the correct peer ID value. This
attribute is optional: if tx_peer_id is missing, the module falls back
to the non-multipeer configuration, where tx_peer_id equals rx_peer_id.
The attribute is sent only to a kernel that knows it, since older ones
reject OVPN_CMD_PEER_NEW with EINVAL on an attribute they cannot parse.
Define the first DCO capability, DCO_CAP_ASYM_PEER_ID, as a row in the
requirement table: the doit policy of OVPN_CMD_PEER_NEW must expose
OVPN_A_PEER_TX_ID as a u32 inside OVPN_A_PEER.
The bit is then consumed. It travels in tls_options, so a peer announces
its own ID only when it can use asymmetric peer-ids, and a server
replies with its own only when the client announced one and the local
kernel can do it too. A peer that announces nothing leaves the other
side on symmetric peer-ids, so there is nothing to exchange over the
wire. The local ID is generated under the same bit, which also lifts the
blanket skip tls_multi_init_finalize() applied whenever DCO was enabled.
Change-Id: Ic5c421ec2e9627340924a05509bb8aff0bbf17da
Signed-off-by: Ralf Lici <[email protected]>
Signed-off-by: Gianmarco De Gregori <[email protected]>
---
M src/openvpn/dco.c
M src/openvpn/dco.h
M src/openvpn/dco_freebsd.c
M src/openvpn/dco_internal.h
M src/openvpn/dco_linux.c
M src/openvpn/dco_win.c
M src/openvpn/init.c
M src/openvpn/ovpn_dco_linux.h
M src/openvpn/ssl.c
M src/openvpn/ssl_common.h
M src/openvpn/ssl_ncp.c
11 files changed, 118 insertions(+), 49 deletions(-)
git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/83/1983/2
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index 0681c81..50691fb 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -57,8 +57,10 @@
{
bool epoch = ks->crypto_options.flags & CO_EPOCH_DATA_KEY_FORMAT;
- msg(D_DCO_DEBUG, "%s: rx_peer_id=%d keyid=%d epoch=%d, currently %d keys
installed",
- __func__, multi->dco_rx_peer_id, ks->key_id, epoch,
multi->dco_keys_installed);
+ msg(D_DCO_DEBUG,
+ "%s: rx_peer_id=%d tx_peer_id=%u keyid=%d epoch=%d, currently %d keys
installed",
+ __func__, multi->dco_rx_peer_id, multi->tx_peer_id, ks->key_id, epoch,
+ multi->dco_keys_installed);
/* Install a key in the PRIMARY slot only when no other key exist.
* From that moment on, any new key will be installed in the SECONDARY
@@ -510,9 +512,11 @@
}
bool
-dco_check_pull_options(msglvl_t msglevel, const struct options *o)
+dco_check_pull_options(msglvl_t msglevel, const struct tls_multi *multi)
{
- if (!o->use_peer_id)
+ /* asymmetric peer-ids are negotiated, not pushed, so options.use_peer_id
+ * is not yet in sync when this runs */
+ if (!multi->use_peer_id)
{
msg(msglevel, "OPTIONS IMPORT: Server did not request DATA_V2 packet "
"format required for data channel offload");
@@ -544,8 +548,8 @@
c->c2.tls_multi->dco_rx_peer_id = -1;
}
#endif
- int ret = dco_new_peer(&c->c1.tuntap->dco, multi->rx_peer_id, sock->sd,
NULL,
- proto_is_dgram(sock->info.proto) ? remoteaddr :
NULL,
+ int ret = dco_new_peer(&c->c1.tuntap->dco, multi->rx_peer_id,
multi->tx_peer_id,
+ sock->sd, NULL, proto_is_dgram(sock->info.proto) ?
remoteaddr : NULL,
NULL, NULL);
if (ret < 0)
{
@@ -628,6 +632,7 @@
const struct context *c = &mi->context;
uint32_t rx_peer_id = c->c2.tls_multi->rx_peer_id;
+ uint32_t tx_peer_id = c->c2.tls_multi->tx_peer_id;
struct sockaddr *remoteaddr, *localaddr = NULL;
struct sockaddr_storage local = { 0 };
const socket_descriptor_t sd = c->c2.link_sockets[0]->sd;
@@ -665,7 +670,7 @@
}
int ret =
- dco_new_peer(&c->c1.tuntap->dco, rx_peer_id, sd, localaddr,
remoteaddr, vpn_addr4, vpn_addr6);
+ dco_new_peer(&c->c1.tuntap->dco, rx_peer_id, tx_peer_id, sd,
localaddr, remoteaddr, vpn_addr4, vpn_addr6);
if (ret < 0)
{
return ret;
diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h
index e9a0348..d20acc3 100644
--- a/src/openvpn/dco.h
+++ b/src/openvpn/dco.h
@@ -46,6 +46,9 @@
#define DCO_IROUTE_METRIC 100
#define DCO_DEFAULT_METRIC 200
+/** Local DCO capability bits, derived from kernel introspection */
+#define DCO_CAP_ASYM_PEER_ID (1u << 0) /**< kernel supports OVPN_A_PEER_TX_ID
*/
+
#if defined(ENABLE_DCO)
/**
@@ -95,10 +98,10 @@
* for the first conflicting option found and return false.
*
* @param msglevel the msg level to use to print the warnings
- * @param o the options struct that hold the options
+ * @param multi the TLS state holding the negotiated parameters
* @return true if no conflict was detected, false otherwise
*/
-bool dco_check_pull_options(msglvl_t msglevel, const struct options *o);
+bool dco_check_pull_options(msglvl_t msglevel, const struct tls_multi *multi);
/**
* Initialize the DCO context
@@ -265,6 +268,7 @@
* of them and when it could not be asked
*/
unsigned int dco_probe_capabilities(void);
+
#else /* if defined(ENABLE_DCO) */
typedef void *dco_context_t;
@@ -294,7 +298,7 @@
}
static inline bool
-dco_check_pull_options(msglvl_t msglevel, const struct options *o)
+dco_check_pull_options(msglvl_t msglevel, const struct tls_multi *multi)
{
return false;
}
@@ -405,5 +409,6 @@
{
return 0;
}
+
#endif /* defined(ENABLE_DCO) */
#endif /* ifndef DCO_H */
diff --git a/src/openvpn/dco_freebsd.c b/src/openvpn/dco_freebsd.c
index 7130398..be7608c 100644
--- a/src/openvpn/dco_freebsd.c
+++ b/src/openvpn/dco_freebsd.c
@@ -133,9 +133,9 @@
}
int
-dco_new_peer(dco_context_t *dco, unsigned int rx_peer_id, int sd, struct
sockaddr *localaddr,
- struct sockaddr *remoteaddr, const struct in_addr *vpn_ipv4,
- const struct in6_addr *vpn_ipv6)
+dco_new_peer(dco_context_t *dco, unsigned int rx_peer_id, unsigned int
tx_peer_id, int sd,
+ struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+ const struct in_addr *vpn_ipv4, const struct in6_addr *vpn_ipv6)
{
struct ifdrv drv;
nvlist_t *nvl, *local_nvl, *remote_nvl;
@@ -143,7 +143,8 @@
nvl = nvlist_create(0);
- msg(D_DCO_DEBUG, "%s: rx-peer-id %u, fd %d", __func__, rx_peer_id, sd);
+ msg(D_DCO_DEBUG, "%s: rx-peer-id %u, tx-peer-id %u, fd %d",
+ __func__, rx_peer_id, tx_peer_id, sd);
if (localaddr)
{
diff --git a/src/openvpn/dco_internal.h b/src/openvpn/dco_internal.h
index a9203163..b4821d3 100644
--- a/src/openvpn/dco_internal.h
+++ b/src/openvpn/dco_internal.h
@@ -59,8 +59,8 @@
* They are implemented by dco_linux.c
*/
-int dco_new_peer(dco_context_t *dco, unsigned int rx_peer_id,
socket_descriptor_t sd,
- struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+int dco_new_peer(dco_context_t *dco, unsigned int rx_peer_id, unsigned int
tx_peer_id,
+ socket_descriptor_t sd, struct sockaddr *localaddr, struct
sockaddr *remoteaddr,
const struct in_addr *vpn_ipv4, const struct in6_addr
*vpn_ipv6);
int dco_del_peer(dco_context_t *dco, unsigned int rx_peer_id);
diff --git a/src/openvpn/dco_linux.c b/src/openvpn/dco_linux.c
index 1235207..265d74b 100644
--- a/src/openvpn/dco_linux.c
+++ b/src/openvpn/dco_linux.c
@@ -120,6 +120,10 @@
int expected_type;
};
+static const struct dco_capability_req dco_capability_reqs[] = {
+ { DCO_CAP_ASYM_PEER_ID, OVPN_CMD_PEER_NEW, OVPN_A_PEER, OVPN_A_PEER_TX_ID,
NL_ATTR_TYPE_U32 },
+};
+
/** the outcome of resolving one capability requirement */
enum ovpn_cap_status
{
@@ -141,7 +145,8 @@
};
/* What the local kernel supports. It cannot change while we run, so the first
- * caller resolves it and the rest just read it. */
+ * caller resolves it and the rest just read it. That caller is
+ * do_init_crypto_tls(), which runs before any peer is added. */
static unsigned int dco_caps = 0;
static bool dco_caps_resolved = false;
@@ -390,14 +395,30 @@
return dco_caps;
}
- /* no capability is defined yet: resolve one whose answer we already know
*/
- static const struct dco_capability_req self_test = { 0, OVPN_CMD_PEER_NEW,
OVPN_A_PEER,
- OVPN_A_PEER_ID,
NL_ATTR_TYPE_U32 };
- int err = 0;
- enum ovpn_cap_status status = ovpn_cap_probe_one(&self_test, &err);
+ for (size_t i = 0; i < SIZE(dco_capability_reqs); i++)
+ {
+ const struct dco_capability_req *req = &dco_capability_reqs[i];
+ int err = 0;
- msg(D_DCO_DEBUG, "%s: policy introspection self-test: %s (status %d, err
%d)", __func__,
- status == OVPN_CAP_SUPPORTED ? "ok" : "unexpected", (int)status, err);
+ switch (ovpn_cap_probe_one(req, &err))
+ {
+ case OVPN_CAP_SUPPORTED:
+ dco_caps |= req->cap;
+ break;
+
+ case OVPN_CAP_UNSUPPORTED:
+ msg(D_DCO_DEBUG, "%s: kernel does not support DCO capability
0x%x", __func__,
+ req->cap);
+ break;
+
+ case OVPN_CAP_UNKNOWN:
+ msg(M_WARN,
+ "Note: cannot determine DCO capability 0x%x from the
kernel (%s), "
+ "assuming it is unsupported",
+ req->cap, err ? nl_geterror(err) : "incomplete policy
dump");
+ break;
+ }
+ }
dco_caps_resolved = true;
msg(D_DCO_DEBUG, "%s: DCO local capabilities: 0x%x", __func__, dco_caps);
@@ -523,9 +544,9 @@
}
int
-dco_new_peer(dco_context_t *dco, unsigned int rx_peer_id, int sd, struct
sockaddr *localaddr,
- struct sockaddr *remoteaddr, const struct in_addr *vpn_ipv4,
- const struct in6_addr *vpn_ipv6)
+dco_new_peer(dco_context_t *dco, unsigned int rx_peer_id, unsigned int
tx_peer_id, int sd,
+ struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+ const struct in_addr *vpn_ipv4, const struct in6_addr *vpn_ipv6)
{
struct gc_arena gc = gc_new();
const char *remotestr = "[undefined]";
@@ -533,13 +554,21 @@
{
remotestr = print_sockaddr(remoteaddr, &gc);
}
- msg(D_DCO_DEBUG, "%s: rx-peer-id %u, fd %d, remote addr: %s", __func__,
rx_peer_id, sd, remotestr);
+ msg(D_DCO_DEBUG, "%s: rx-peer-id %u, tx-peer-id %u, fd %d, remote addr:
%s",
+ __func__, rx_peer_id, tx_peer_id, sd, remotestr);
struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PEER_NEW);
struct nlattr *attr = nla_nest_start(nl_msg, OVPN_A_PEER);
int ret = -EMSGSIZE;
NLA_PUT_U32(nl_msg, OVPN_A_PEER_ID, rx_peer_id);
+ /* old kernels reject the whole command if they see an attribute they do
not
+ * know. This is the same answer the TLS layer used to decide whether to
+ * negotiate an asymmetric ID, so the two cannot disagree */
+ if (dco_caps & DCO_CAP_ASYM_PEER_ID)
+ {
+ NLA_PUT_U32(nl_msg, OVPN_A_PEER_TX_ID, tx_peer_id);
+ }
NLA_PUT_U32(nl_msg, OVPN_A_PEER_SOCKET, sd);
/* Set the remote endpoint if defined (for UDP) */
@@ -719,12 +748,6 @@
{
dco->ovpn_dco_id = resolve_ovpn_netlink_id(M_FATAL);
- /* nothing consumes the bitmap yet; resolve it here to exercise the path */
- if (check_debug_level(D_DCO_DEBUG))
- {
- dco_probe_capabilities();
- }
-
dco->nl_sock = nl_socket_alloc();
if (!dco->nl_sock)
diff --git a/src/openvpn/dco_win.c b/src/openvpn/dco_win.c
index fbe1624..27f5b92 100644
--- a/src/openvpn/dco_win.c
+++ b/src/openvpn/dco_win.c
@@ -406,11 +406,12 @@
}
int
-dco_new_peer(dco_context_t *dco, unsigned int rx_peer_id, socket_descriptor_t
sd,
- struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+dco_new_peer(dco_context_t *dco, unsigned int rx_peer_id, unsigned int
tx_peer_id,
+ socket_descriptor_t sd, struct sockaddr *localaddr, struct
sockaddr *remoteaddr,
const struct in_addr *vpn_ipv4, const struct in6_addr *vpn_ipv6)
{
- msg(D_DCO_DEBUG, "%s: rx-peer-id %u, fd " SOCKET_PRINTF, __func__,
rx_peer_id, sd);
+ msg(D_DCO_DEBUG, "%s: rx-peer-id %u, tx-peer-id %u, fd " SOCKET_PRINTF,
+ __func__, rx_peer_id, tx_peer_id, sd);
if (dco->ifmode == DCO_MODE_P2P)
{
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index ddcfaf4..c8b6bf0 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -2677,7 +2677,7 @@
}
/* Check if pushed options are compatible with DCO, if enabled */
- if (dco_enabled(&c->options) && !dco_check_pull_options(D_PUSH_ERRORS,
&c->options))
+ if (dco_enabled(&c->options) && !dco_check_pull_options(D_PUSH_ERRORS,
c->c2.tls_multi))
{
msg(D_PUSH_ERRORS, "OPTIONS ERROR: pushed options are incompatible
"
"with data channel offload. Use --disable-dco
to connect to "
@@ -3447,6 +3447,10 @@
/* let the TLS engine know if keys have to be installed in DCO or not */
to.dco_enabled = dco_enabled(options);
+ if (to.dco_enabled)
+ {
+ to.dco_capabilities = dco_probe_capabilities();
+ }
/*
* Initialize OpenVPN's master TLS-mode object.
diff --git a/src/openvpn/ovpn_dco_linux.h b/src/openvpn/ovpn_dco_linux.h
index b3c9ff0..28cf97a 100644
--- a/src/openvpn/ovpn_dco_linux.h
+++ b/src/openvpn/ovpn_dco_linux.h
@@ -54,6 +54,7 @@
OVPN_A_PEER_LINK_TX_BYTES,
OVPN_A_PEER_LINK_RX_PACKETS,
OVPN_A_PEER_LINK_TX_PACKETS,
+ OVPN_A_PEER_TX_ID,
__OVPN_A_PEER_MAX,
OVPN_A_PEER_MAX = (__OVPN_A_PEER_MAX - 1)
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 8e8d4a0..0a7dffb 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -1202,9 +1202,10 @@
tls_session_init(multi, &multi->session[TM_ACTIVE]);
tls_session_init(multi, &multi->session[TM_INITIAL]);
- if (!multi->opt.dco_enabled)
+ /* Calculate the asymmetric peer-id. Leaving it unset is what keeps us from
+ * announcing one to the peer, so DCO needs the kernel to support it */
+ if (dco_can_asym_peer_id(&multi->opt))
{
- /* Calculate the asymmetric peer-id */
if (multi->rx_peer_id == MAX_PEER_ID &&
multi->session[TM_INITIAL].opt->mode != MODE_SERVER)
{
multi->rx_peer_id = (uint32_t)(get_random() % (MAX_PEER_ID - 1));
@@ -1934,9 +1935,16 @@
push_peer_info_peerid(struct buffer *out, const struct tls_multi *multi,
const struct tls_session *session)
{
- if (multi->rx_peer_id == MAX_PEER_ID || session->opt->dco_enabled)
+ if (multi->rx_peer_id == MAX_PEER_ID)
{
- /* No valid peer id or DCO is enabled. Cannot use this feature */
+ /* No valid peer id */
+ return true;
+ }
+
+ if (!dco_can_asym_peer_id(session->opt))
+ {
+ /* DCO is in use but the local kernel cannot do asymmetric peer-ids:
+ * do not announce our ID so that the peer falls back as well */
return true;
}
@@ -2373,16 +2381,18 @@
{
output_peer_info_env(session->opt->es, multi->peer_info);
uint32_t peer_id = extract_asymmetric_peer_id(multi->peer_info);
- if (peer_id != MAX_PEER_ID && !session->opt->dco_enabled)
+ if (peer_id != MAX_PEER_ID && dco_can_asym_peer_id(session->opt))
{
+ /* set before key_method_2_write() runs, so that a server knows
+ * whether to announce its own ID back */
multi->tx_peer_id = peer_id;
multi->use_asymmetric_peer_id = true;
multi->use_peer_id = true;
}
else
{
- /* Peer has no support for asymmetric peer-id, and DCO currently
- * can only handle symmetric peer IDs */
+ /* the peer announced nothing, or we cannot use asymmetric peer-ids
+ * locally: stay symmetric */
multi->tx_peer_id = multi->rx_peer_id;
}
}
diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
index 38b360a..d678da6 100644
--- a/src/openvpn/ssl_common.h
+++ b/src/openvpn/ssl_common.h
@@ -456,9 +456,21 @@
size_t ekm_label_size;
size_t ekm_size;
- bool dco_enabled; /**< Whether keys have to be installed in DCO or not */
+ bool dco_enabled; /**< Whether keys have to be installed in
DCO or not */
+ unsigned int dco_capabilities; /**< local kernel DCO capability bitmap
(DCO_CAP_*) */
};
+/**
+ * Whether asymmetric peer-ids can be used: either DCO is not in the way, or
the
+ * local kernel supports them. When false we neither generate nor announce an
+ * ID, so the remote peer falls back as well.
+ */
+static inline bool
+dco_can_asym_peer_id(const struct tls_options *o)
+{
+ return !o->dco_enabled || (o->dco_capabilities & DCO_CAP_ASYM_PEER_ID);
+}
+
/** @addtogroup control_processor
* @{ */
/** @name Index of key_state objects within a tls_session structure
diff --git a/src/openvpn/ssl_ncp.c b/src/openvpn/ssl_ncp.c
index f13f507..6cfe408 100644
--- a/src/openvpn/ssl_ncp.c
+++ b/src/openvpn/ssl_ncp.c
@@ -398,7 +398,7 @@
{
/* will return 0 if peer_info is null */
const unsigned int iv_proto_peer = extract_iv_proto(multi->peer_info);
- const unsigned int tx_peer_id =
extract_asymmetric_peer_id(multi->peer_info);
+ uint32_t tx_peer_id = extract_asymmetric_peer_id(multi->peer_info);
/* The other peer does not support P2P NCP */
if (!(iv_proto_peer & IV_PROTO_NCP_P2P))
@@ -406,6 +406,14 @@
return;
}
+ if (!dco_can_asym_peer_id(session->opt))
+ {
+ /* We cannot use asymmetric peer-ids locally: ignore the announced
+ * ID and use the legacy shared peer-id selection below */
+ tx_peer_id = MAX_PEER_ID;
+ multi->use_asymmetric_peer_id = false;
+ }
+
if (iv_proto_peer & IV_PROTO_DATA_V2)
{
multi->use_peer_id = true;
@@ -436,8 +444,7 @@
if (multi->use_peer_id)
{
/* The asymmetric peer-id trumps on the EKM generated ones */
- if ((iv_proto_peer & IV_PROTO_TLS_KEY_EXPORT)
- && (tx_peer_id != MAX_PEER_ID) && !multi->opt.dco_enabled)
+ if ((iv_proto_peer & IV_PROTO_TLS_KEY_EXPORT) && (tx_peer_id !=
MAX_PEER_ID))
{
multi->tx_peer_id = tx_peer_id;
multi->use_asymmetric_peer_id = true;
--
To view, visit https://gerrit.openvpn.net/c/openvpn/+/1983?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.openvpn.net/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: Ic5c421ec2e9627340924a05509bb8aff0bbf17da
Gerrit-Change-Number: 1983
Gerrit-PatchSet: 2
Gerrit-Owner: Gianmarco De Gregori <[email protected]>
Gerrit-Reviewer: Arne Schwabe <[email protected]>
Gerrit-CC: Ralf Lici <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>
Gerrit-Attention: Arne Schwabe <[email protected]>
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel