}
c->c2.tls_multi->dco_peer_id = multi->peer_id;
- c->c2.link_socket->dco_installed = true;
return 0;
}
c->c2.tls_multi->dco_peer_id = peer_id;
- if (c->mode == CM_CHILD_TCP)
- {
- multi_tcp_dereference_instance(m->mtcp, mi);
- if (close(sd))
- {
- msg(D_DCO|M_ERRNO, "error closing TCP socket after DCO handover");
- }
- c->c2.link_socket->dco_installed = true;
- c->c2.link_socket->sd = SOCKET_UNDEFINED;
- }
-
return 0;
}
*/
int dco_do_read(dco_context_t *dco);
-/**
- * Write data to the DCO communication channel (control packet expected)
- *
- * @param dco the DCO context
- * @param peer_id the ID of the peer to send the data to
- * @param buf the buffer containing the data to send
- */
-int dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf);
-
/**
* Install a DCO in the main event loop
*/
return 0;
}
-static inline int
-dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf)
-{
- ASSERT(false);
- return 0;
-}
-
static inline void
dco_event_set(dco_context_t *dco, struct event_set *es, void *arg)
{
{
dco->open = true;
}
- dco->dco_packet_in = alloc_buf(PAGE_SIZE);
return dco->fd;
}
return 0;
}
-int
-dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf)
-{
- /* Control packets are passed through the socket, so this should never get
- * called. See should_use_dco_socket(). */
- ASSERT(0);
- return -EINVAL;
-}
-
bool
dco_available(int msglevel)
{
char ifname[IFNAMSIZ];
- struct buffer dco_packet_in;
-
int dco_message_type;
int dco_message_peer_id;
int dco_del_peer_reason;
{
msg(M_ERR, "%s: failed to join groups: %d", __func__, ret);
}
-
- /* Register for non-data packets that ovpn-dco may receive. They will be
- * forwarded to userspace
- */
- struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_REGISTER_PACKET);
- if (!nl_msg)
- {
- msg(M_ERR, "%s: cannot allocate message to register for control packets",
- __func__);
- }
-
- ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__);
- if (ret)
- {
- msg(M_ERR, "%s: failed to register for control packets: %d", __func__,
- ret);
- }
- nlmsg_free(nl_msg);
}
int
}
tt->actual_name = string_alloc(dev, NULL);
- uint8_t *dcobuf = malloc(65536);
- buf_set_write(&tt->dco.dco_packet_in, dcobuf, 65536);
tt->dco.dco_message_peer_id = -1;
ovpn_dco_register(&tt->dco);
net_iface_del(ctx, tt->actual_name);
ovpn_dco_uninit_netlink(&tt->dco);
- free(tt->dco.dco_packet_in.data);
}
int
break;
}
- case OVPN_CMD_PACKET:
- {
- if (!attrs[OVPN_ATTR_PACKET])
- {
- msg(D_DCO, "ovpn-dco: no packet in OVPN_CMD_PACKET message");
- return NL_SKIP;
- }
- struct nlattr *pkt_attrs[OVPN_PACKET_ATTR_MAX + 1];
-
- if (nla_parse_nested(pkt_attrs, OVPN_PACKET_ATTR_MAX,
- attrs[OVPN_ATTR_PACKET], NULL))
- {
- msg(D_DCO, "received bogus cmd packet data from ovpn-dco");
- return NL_SKIP;
- }
- if (!pkt_attrs[OVPN_PACKET_ATTR_PEER_ID])
- {
- msg(D_DCO, "ovpn-dco: Received OVPN_CMD_PACKET message without peer id");
- return NL_SKIP;
- }
- if (!pkt_attrs[OVPN_PACKET_ATTR_PACKET])
- {
- msg(D_DCO, "ovpn-dco: Received OVPN_CMD_PACKET message without packet");
- return NL_SKIP;
- }
-
- unsigned int peerid = nla_get_u32(pkt_attrs[OVPN_PACKET_ATTR_PEER_ID]);
-
- uint8_t *data = nla_data(pkt_attrs[OVPN_PACKET_ATTR_PACKET]);
- int len = nla_len(pkt_attrs[OVPN_PACKET_ATTR_PACKET]);
-
- msg(D_DCO_DEBUG, "ovpn-dco: received OVPN_PACKET_ATTR_PACKET, ifindex: %d peer-id: %d, len %d",
- ifindex, peerid, len);
- if (BLEN(&dco->dco_packet_in) > 0)
- {
- msg(D_DCO, "DCO packet buffer still full?!");
- return NL_SKIP;
- }
- buf_init(&dco->dco_packet_in, 0);
- buf_write(&dco->dco_packet_in, data, len);
- dco->dco_message_peer_id = peerid;
- dco->dco_message_type = OVPN_CMD_PACKET;
- break;
- }
-
default:
msg(D_DCO, "ovpn-dco: received unknown command: %d", gnlh->cmd);
dco->dco_message_type = 0;
return ovpn_nl_recvmsgs(dco, __func__);
}
-int
-dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf)
-{
- packet_size_type len = BLEN(buf);
- dmsg(D_STREAM_DEBUG, "DCO: WRITE %d offset=%d", (int)len, buf->offset);
-
- msg(D_DCO_DEBUG, "%s: peer-id %d, len=%d", __func__, peer_id, len);
-
- struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PACKET);
-
- if (!nl_msg)
- {
- return -ENOMEM;
- }
-
- struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_PACKET);
- int ret = -EMSGSIZE;
- NLA_PUT_U32(nl_msg, OVPN_PACKET_ATTR_PEER_ID, peer_id);
- NLA_PUT(nl_msg, OVPN_PACKET_ATTR_PACKET, len, BSTR(buf));
- nla_nest_end(nl_msg, attr);
-
- ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__);
- if (ret)
- {
- goto nla_put_failure;
- }
-
- /* return the length of the written data in case of success */
- ret = len;
-
-nla_put_failure:
- nlmsg_free(nl_msg);
- return ret;
-}
-
int
dco_get_peer_stats_multi(dco_context_t *dco, struct multi_context *m)
{
unsigned int ifindex;
- struct buffer dco_packet_in;
-
int dco_message_type;
int dco_message_peer_id;
int dco_del_peer_reason;
return 0;
}
-int
-dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf)
-{
- /* no-op on windows */
- ASSERT(0);
- return 0;
-}
-
int
dco_get_peer_stats_multi(dco_context_t *dco, struct multi_context *m)
{
process_incoming_dco(struct context *c)
{
#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
- struct link_socket_info *lsi = get_link_socket_info(c);
dco_context_t *dco = &c->c1.tuntap->dco;
dco_do_read(dco);
msg(D_DCO_DEBUG, "%s: received message for mismatching peer-id %d, "
"expected %d", __func__, dco->dco_message_peer_id,
c->c2.tls_multi->dco_peer_id);
- /* ensure we also drop a message if there is one in the buffer */
- buf_init(&dco->dco_packet_in, 0);
return;
}
- if ((dco->dco_message_type == OVPN_CMD_DEL_PEER)
- && (dco->dco_del_peer_reason == OVPN_DEL_PEER_REASON_EXPIRED))
+ if (dco->dco_message_type != OVPN_CMD_DEL_PEER)
{
- msg(D_DCO_DEBUG, "%s: received peer expired notification of for peer-id "
- "%d", __func__, dco->dco_message_peer_id);
- trigger_ping_timeout_signal(c);
+ msg(D_DCO_DEBUG, "%s: received message of type %u - ignoring", __func__,
+ dco->dco_message_type);
return;
}
- if (dco->dco_message_type != OVPN_CMD_PACKET)
+ if (dco->dco_del_peer_reason == OVPN_DEL_PEER_REASON_EXPIRED)
{
- msg(D_DCO_DEBUG, "%s: received message of type %u - ignoring", __func__,
- dco->dco_message_type);
+ msg(D_DCO_DEBUG, "%s: received peer expired notification of for peer-id "
+ "%d", __func__, dco->dco_message_peer_id);
+ trigger_ping_timeout_signal(c);
return;
}
-
- struct buffer orig_buff = c->c2.buf;
- c->c2.buf = dco->dco_packet_in;
- c->c2.from = lsi->lsa->actual;
-
- process_incoming_link(c);
-
- c->c2.buf = orig_buff;
- buf_init(&dco->dco_packet_in, 0);
#endif /* if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) */
}
}
}
-/*
- * Linux DCO implementations pass the socket to the kernel and
- * disallow usage of it from userland for TCP, so (control) packets
- * sent and received by OpenVPN need to go through the DCO interface.
- *
- * Windows DCO needs control packets to be sent via the normal
- * standard Overlapped I/O.
- *
- * FreeBSD DCO allows control packets to pass through the socket in both
- * directions.
- *
- * Hide that complexity (...especially if more platforms show up
- * in the future...) in a small inline function.
- */
-static inline bool
-should_use_dco_socket(struct link_socket *ls)
-{
-#if defined(TARGET_LINUX)
- return ls->dco_installed && proto_is_tcp(ls->info.proto);
-#else
- return false;
-#endif
-}
-
/*
* Input: c->c2.to_link
*/
socks_preprocess_outgoing_link(c, &to_addr, &size_delta);
/* Send packet */
- if (should_use_dco_socket(c->c2.link_socket))
- {
- size = dco_do_write(&c->c1.tuntap->dco,
- c->c2.tls_multi->dco_peer_id,
- &c->c2.to_link);
- }
- else
- {
- size = link_socket_write(c->c2.link_socket, &c->c2.to_link,
- to_addr);
- }
+ size = link_socket_write(c->c2.link_socket, &c->c2.to_link, to_addr);
/* Undo effect of prepend */
link_socket_write_post_size_adjust(&size, size_delta, &c->c2.to_link);
/* in dco-win case, link socket is a tun handle which is
* closed in do_close_tun(). Set it to UNDEFINED so
* we won't use WinSock API to close it. */
- if (tuntap_is_dco_win(c->c1.tuntap) && c->c2.link_socket
- && c->c2.link_socket->dco_installed)
+ if (tuntap_is_dco_win(c->c1.tuntap) && c->c2.link_socket)
{
c->c2.link_socket->sd = SOCKET_UNDEFINED;
}
tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */
- if (mi && mi->context.c2.link_socket->dco_installed)
- {
- /* If we got a socket that has been handed over to the kernel
- * we must not call the normal socket function to figure out
- * if it is readable or writable */
- /* Assert that we only have the DCO exptected flags */
- ASSERT(action & (TA_SOCKET_READ | TA_SOCKET_WRITE));
-
- /* We are always ready! */
- return action;
- }
-
switch (action)
{
case TA_TUN_READ:
case TA_INITIAL:
ASSERT(mi);
- if (!mi->context.c2.link_socket->dco_installed)
- {
- multi_tcp_set_global_rw_flags(m, mi);
- }
+ multi_tcp_set_global_rw_flags(m, mi);
multi_process_post(m, mi, mpp_flags);
break;
}
else
{
- if (!c->c2.link_socket->dco_installed)
- {
- multi_tcp_set_global_rw_flags(m, mi);
- }
+ multi_tcp_set_global_rw_flags(m, mi);
}
break;
#endif
#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
-static void
-process_incoming_dco_packet(struct multi_context *m, struct multi_instance *mi,
- dco_context_t *dco)
-{
- if (BLEN(&dco->dco_packet_in) < 1)
- {
- msg(D_DCO, "Received too short packet for peer %d",
- dco->dco_message_peer_id);
- goto done;
- }
-
- uint8_t *ptr = BPTR(&dco->dco_packet_in);
- uint8_t op = ptr[0] >> P_OPCODE_SHIFT;
- if ((op == P_DATA_V1) || (op == P_DATA_V2))
- {
- msg(D_DCO, "DCO: received data channel packet for peer %d",
- dco->dco_message_peer_id);
- goto done;
- }
-
- struct buffer orig_buf = mi->context.c2.buf;
- mi->context.c2.buf = dco->dco_packet_in;
-
- multi_process_incoming_link(m, mi, 0);
-
- mi->context.c2.buf = orig_buf;
-
-done:
- buf_init(&dco->dco_packet_in, 0);
-}
-
static void
process_incoming_del_peer(struct multi_context *m, struct multi_instance *mi,
dco_context_t *dco)
if ((peer_id < m->max_clients) && (m->instances[peer_id]))
{
mi = m->instances[peer_id];
- if (dco->dco_message_type == OVPN_CMD_PACKET)
- {
- process_incoming_dco_packet(m, mi, dco);
- }
- else if (dco->dco_message_type == OVPN_CMD_DEL_PEER)
+ if (dco->dco_message_type == OVPN_CMD_DEL_PEER)
{
process_incoming_del_peer(m, mi, dco);
}
msg(msglevel, "Received DCO message for unknown peer-id: %d, "
"type %d, del_peer_reason %d", peer_id, dco->dco_message_type,
dco->dco_del_peer_reason);
- /* Also clear the buffer if this was incoming packet for a dropped peer */
- buf_init(&dco->dco_packet_in, 0);
}
dco->dco_message_type = 0;
#ifndef _UAPI_LINUX_OVPN_DCO_H_
#define _UAPI_LINUX_OVPN_DCO_H_
-#define OVPN_NL_NAME "ovpn-dco"
+#define OVPN_NL_NAME "ovpn-dco-v2"
#define OVPN_NL_MULTICAST_GROUP_PEERS "peers"
OVPN_CMD_DEL_KEY,
- /**
- * @OVPN_CMD_REGISTER_PACKET: Register for specific packet types to be
- * forwarded to userspace
- */
- OVPN_CMD_REGISTER_PACKET,
-
- /**
- * @OVPN_CMD_PACKET: Send a packet from userspace to kernelspace. Also
- * used to send to userspace packets for which a process had registered
- * with OVPN_CMD_REGISTER_PACKET
- */
- OVPN_CMD_PACKET,
-
/**
* @OVPN_CMD_GET_PEER: Retrieve the status of a peer or all peers
*/
OVPN_ATTR_NEW_KEY,
OVPN_ATTR_SWAP_KEYS,
OVPN_ATTR_DEL_KEY,
- OVPN_ATTR_PACKET,
OVPN_ATTR_GET_PEER,
__OVPN_ATTR_AFTER_LAST,
get_server_poll_remaining_time(sock->server_poll_timeout),
sig_info);
- sock->dco_installed = true;
+ sock->sockflags |= SF_DCO_WIN;
if (sig_info->signal_received)
{
static int
socket_get_last_error(const struct link_socket *sock)
{
- if (sock->dco_installed)
+ if (socket_is_dco_win(sock))
{
return GetLastError();
}
ASSERT(ResetEvent(sock->reads.overlapped.hEvent));
sock->reads.flags = 0;
- if (sock->dco_installed)
+ if (socket_is_dco_win(sock))
{
status = ReadFile((HANDLE)sock->sd, wsabuf[0].buf, wsabuf[0].len,
&sock->reads.size, &sock->reads.overlapped);
ASSERT(ResetEvent(sock->writes.overlapped.hEvent));
sock->writes.flags = 0;
- if (sock->dco_installed)
+ if (socket_is_dco_win(sock))
{
status = WriteFile((HANDLE)sock->sd, wsabuf[0].buf, wsabuf[0].len,
&sock->writes.size, &sock->writes.overlapped);
socket_descriptor_t sd;
socket_descriptor_t ctrl_sd; /* only used for UDP over Socks */
- bool dco_installed;
#ifdef _WIN32
struct overlapped_io reads;
#define SF_PORT_SHARE (1<<2)
#define SF_HOST_RANDOMIZE (1<<3)
#define SF_GETADDRINFO_DGRAM (1<<4)
+#define SF_DCO_WIN (1<<5)
unsigned int sockflags;
int mark;
const char *bind_dev;
}
}
+/**
+ * Returns true if we are on Windows and this link is running on DCO-WIN.
+ * This helper is used to enable DCO-WIN specific logic that is not relevant
+ * to other platforms.
+ */
+static inline bool
+socket_is_dco_win(const struct link_socket *s)
+{
+ return s->sockflags & SF_DCO_WIN;
+}
+
/*
* Socket Read Routines
*/
struct link_socket_actual *from)
{
sockethandle_t sh = { .s = sock->sd };
- if (sock->dco_installed)
+ if (socket_is_dco_win(sock))
{
*from = sock->info.lsa->actual;
sh.is_handle = true;
struct buffer *buf,
struct link_socket_actual *from)
{
-#ifdef _WIN32
- if (proto_is_udp(sock->info.proto) || sock->dco_installed)
-#else
- if (proto_is_udp(sock->info.proto))
-#endif
- /* unified UDPv4 and UDPv6, for DCO the kernel
+ if (proto_is_udp(sock->info.proto) || socket_is_dco_win(sock))
+ /* unified UDPv4 and UDPv6, for DCO-WIN the kernel
* will strip the length header */
{
int res;
{
int err = 0;
int status = 0;
- sockethandle_t sh = { .s = sock->sd, .is_handle = sock->dco_installed };
+ sockethandle_t sh = { .s = sock->sd, .is_handle = socket_is_dco_win(sock) };
if (overlapped_io_active(&sock->writes))
{
status = sockethandle_finalize(sh, &sock->writes, NULL, NULL);
struct buffer *buf,
struct link_socket_actual *to)
{
- if (proto_is_udp(sock->info.proto) || sock->dco_installed)
+ if (proto_is_udp(sock->info.proto) || socket_is_dco_win(sock))
{
- /* unified UDPv4 and UDPv6 and DCO (kernel adds size header) */
+ /* unified UDPv4, UDPv6 and DCO-WIN (driver adds length header) */
return link_socket_write_udp(sock, buf, to);
}
else if (proto_is_tcp(sock->info.proto)) /* unified TCPv4 and TCPv6 */