mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-01 14:14:56 +00:00
wg: Add netmap support
When in netmap (emulated) mode, wireguard interfaces prepend or strip a dummy ethernet header when interfacing with netmap. The netmap application thus sees unencrypted, de-encapsulated frames with a fixed header. In this mode, netmap hooks the if_input and if_transmit routines of the ifnet. Packets from the host TX ring are handled by wg_if_input(), which simply hands them to the netisr layer; packets which would otherwise be tunneled are intercepted in wg_output() and placed in the host RX ring. The "physical" TX ring is processed by wg_transmit(), which behaves identically to wg_output() when netmap is not enabled, and packets appear in the "physical" RX ring by hooking wg_deliver_in(). Reviewed by: vmaffione MFC after: 1 month Sponsored by: Klara, Inc. Sponsored by: Zenarmor Differential Revision: https://reviews.freebsd.org/D43460
This commit is contained in:
parent
278d695094
commit
bf454ca88b
|
@ -121,6 +121,19 @@ as follows:
|
|||
Although a valid Curve25519 key must have 5 bits set to
|
||||
specific values, this is done by the interface and so it
|
||||
will accept any random 32-byte base64 string.
|
||||
.Sh NETMAP
|
||||
.Xr netmap 4
|
||||
applications may open a WireGuard interface in emulated mode.
|
||||
The netmap application will receive decrypted, unencapsulated packets prepended
|
||||
by a dummy Ethernet header.
|
||||
The Ethertype field will be one of
|
||||
.Dv ETHERTYPE_IP
|
||||
or
|
||||
.Dv ETHERTYPE_IPV6
|
||||
depending on the address family of the packet.
|
||||
Packets transmitted by the application should similarly begin with a dummy
|
||||
Ethernet header; this header will be stripped before the packet is encrypted
|
||||
and tunneled.
|
||||
.Sh EXAMPLES
|
||||
Create a
|
||||
.Nm
|
||||
|
@ -183,6 +196,7 @@ is not assigned to the allowed IPs of Peer X.
|
|||
.Xr ip 4 ,
|
||||
.Xr ipsec 4 ,
|
||||
.Xr netintro 4 ,
|
||||
.Xr netmap 4 ,
|
||||
.Xr ovpn 4 ,
|
||||
.Xr ipf 5 ,
|
||||
.Xr pf.conf 5 ,
|
||||
|
|
|
@ -1674,6 +1674,31 @@ wg_deliver_out(struct wg_peer *peer)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef DEV_NETMAP
|
||||
/*
|
||||
* Hand a packet to the netmap RX ring, via netmap's
|
||||
* freebsd_generic_rx_handler().
|
||||
*/
|
||||
static void
|
||||
wg_deliver_netmap(if_t ifp, struct mbuf *m, int af)
|
||||
{
|
||||
struct ether_header *eh;
|
||||
|
||||
M_PREPEND(m, ETHER_HDR_LEN, M_NOWAIT);
|
||||
if (__predict_false(m == NULL)) {
|
||||
if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
eh = mtod(m, struct ether_header *);
|
||||
eh->ether_type = af == AF_INET ?
|
||||
htons(ETHERTYPE_IP) : htons(ETHERTYPE_IPV6);
|
||||
memcpy(eh->ether_shost, "\x02\x02\x02\x02\x02\x02", ETHER_ADDR_LEN);
|
||||
memcpy(eh->ether_dhost, "\xff\xff\xff\xff\xff\xff", ETHER_ADDR_LEN);
|
||||
if_input(ifp, m);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
wg_deliver_in(struct wg_peer *peer)
|
||||
{
|
||||
|
@ -1682,6 +1707,7 @@ wg_deliver_in(struct wg_peer *peer)
|
|||
struct wg_packet *pkt;
|
||||
struct mbuf *m;
|
||||
struct epoch_tracker et;
|
||||
int af;
|
||||
|
||||
while ((pkt = wg_queue_dequeue_serial(&peer->p_decrypt_serial)) != NULL) {
|
||||
if (atomic_load_acq_int(&pkt->p_state) != WG_PACKET_CRYPTED)
|
||||
|
@ -1707,19 +1733,25 @@ wg_deliver_in(struct wg_peer *peer)
|
|||
if (m->m_pkthdr.len == 0)
|
||||
goto done;
|
||||
|
||||
MPASS(pkt->p_af == AF_INET || pkt->p_af == AF_INET6);
|
||||
af = pkt->p_af;
|
||||
MPASS(af == AF_INET || af == AF_INET6);
|
||||
pkt->p_mbuf = NULL;
|
||||
|
||||
m->m_pkthdr.rcvif = ifp;
|
||||
|
||||
NET_EPOCH_ENTER(et);
|
||||
BPF_MTAP2_AF(ifp, m, pkt->p_af);
|
||||
BPF_MTAP2_AF(ifp, m, af);
|
||||
|
||||
CURVNET_SET(if_getvnet(ifp));
|
||||
M_SETFIB(m, if_getfib(ifp));
|
||||
if (pkt->p_af == AF_INET)
|
||||
#ifdef DEV_NETMAP
|
||||
if ((if_getcapenable(ifp) & IFCAP_NETMAP) != 0)
|
||||
wg_deliver_netmap(ifp, m, af);
|
||||
else
|
||||
#endif
|
||||
if (af == AF_INET)
|
||||
netisr_dispatch(NETISR_IP, m);
|
||||
if (pkt->p_af == AF_INET6)
|
||||
else if (af == AF_INET6)
|
||||
netisr_dispatch(NETISR_IPV6, m);
|
||||
CURVNET_RESTORE();
|
||||
NET_EPOCH_EXIT(et);
|
||||
|
@ -2164,13 +2196,36 @@ determine_af_and_pullup(struct mbuf **m, sa_family_t *af)
|
|||
return (0);
|
||||
}
|
||||
|
||||
#ifdef DEV_NETMAP
|
||||
static int
|
||||
determine_ethertype_and_pullup(struct mbuf **m, int *etp)
|
||||
{
|
||||
struct ether_header *eh;
|
||||
|
||||
*m = m_pullup(*m, sizeof(struct ether_header));
|
||||
if (__predict_false(*m == NULL))
|
||||
return (ENOBUFS);
|
||||
eh = mtod(*m, struct ether_header *);
|
||||
*etp = ntohs(eh->ether_type);
|
||||
if (*etp != ETHERTYPE_IP && *etp != ETHERTYPE_IPV6)
|
||||
return (EAFNOSUPPORT);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* This should only be invoked by netmap, via nm_os_generic_xmit_frame(), to
|
||||
* transmit packets from the netmap TX ring.
|
||||
*/
|
||||
static int
|
||||
wg_transmit(if_t ifp, struct mbuf *m)
|
||||
{
|
||||
sa_family_t af;
|
||||
int ret;
|
||||
int et, ret;
|
||||
struct mbuf *defragged;
|
||||
|
||||
KASSERT((if_getcapenable(ifp) & IFCAP_NETMAP) != 0,
|
||||
("%s: ifp %p is not in netmap mode", __func__, ifp));
|
||||
|
||||
defragged = m_defrag(m, M_NOWAIT);
|
||||
if (defragged)
|
||||
m = defragged;
|
||||
|
@ -2180,14 +2235,94 @@ wg_transmit(if_t ifp, struct mbuf *m)
|
|||
return (ENOBUFS);
|
||||
}
|
||||
|
||||
ret = determine_ethertype_and_pullup(&m, &et);
|
||||
if (ret) {
|
||||
xmit_err(ifp, m, NULL, AF_UNSPEC);
|
||||
return (ret);
|
||||
}
|
||||
m_adj(m, sizeof(struct ether_header));
|
||||
|
||||
ret = determine_af_and_pullup(&m, &af);
|
||||
if (ret) {
|
||||
xmit_err(ifp, m, NULL, AF_UNSPEC);
|
||||
return (ret);
|
||||
}
|
||||
return (wg_xmit(ifp, m, af, if_getmtu(ifp)));
|
||||
|
||||
/*
|
||||
* netmap only gets to see transient errors, since it handles errors by
|
||||
* refusing to advance the transmit ring and retrying later.
|
||||
*/
|
||||
ret = wg_xmit(ifp, m, af, if_getmtu(ifp));
|
||||
if (ret == ENOBUFS)
|
||||
return (ret);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* This should only be invoked by netmap, via nm_os_send_up(), to process
|
||||
* packets from the host TX ring.
|
||||
*/
|
||||
static void
|
||||
wg_if_input(if_t ifp, struct mbuf *m)
|
||||
{
|
||||
int et;
|
||||
|
||||
KASSERT((if_getcapenable(ifp) & IFCAP_NETMAP) != 0,
|
||||
("%s: ifp %p is not in netmap mode", __func__, ifp));
|
||||
|
||||
if (determine_ethertype_and_pullup(&m, &et) != 0) {
|
||||
if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
|
||||
m_freem(m);
|
||||
return;
|
||||
}
|
||||
CURVNET_SET(if_getvnet(ifp));
|
||||
switch (et) {
|
||||
case ETHERTYPE_IP:
|
||||
m_adj(m, sizeof(struct ether_header));
|
||||
netisr_dispatch(NETISR_IP, m);
|
||||
break;
|
||||
case ETHERTYPE_IPV6:
|
||||
m_adj(m, sizeof(struct ether_header));
|
||||
netisr_dispatch(NETISR_IPV6, m);
|
||||
break;
|
||||
default:
|
||||
__assert_unreachable();
|
||||
}
|
||||
CURVNET_RESTORE();
|
||||
}
|
||||
|
||||
/*
|
||||
* Deliver a packet to the host RX ring. Because the interface is in netmap
|
||||
* mode, the if_transmit() call should pass the packet to netmap_transmit().
|
||||
*/
|
||||
static int
|
||||
wg_xmit_netmap(if_t ifp, struct mbuf *m, int af)
|
||||
{
|
||||
struct ether_header *eh;
|
||||
|
||||
if (__predict_false(if_tunnel_check_nesting(ifp, m, MTAG_WGLOOP,
|
||||
MAX_LOOPS))) {
|
||||
printf("%s:%d\n", __func__, __LINE__);
|
||||
if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
|
||||
m_freem(m);
|
||||
return (ELOOP);
|
||||
}
|
||||
|
||||
M_PREPEND(m, ETHER_HDR_LEN, M_NOWAIT);
|
||||
if (__predict_false(m == NULL)) {
|
||||
if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
|
||||
return (ENOBUFS);
|
||||
}
|
||||
|
||||
eh = mtod(m, struct ether_header *);
|
||||
eh->ether_type = af == AF_INET ?
|
||||
htons(ETHERTYPE_IP) : htons(ETHERTYPE_IPV6);
|
||||
memcpy(eh->ether_shost, "\x06\x06\x06\x06\x06\x06", ETHER_ADDR_LEN);
|
||||
memcpy(eh->ether_dhost, "\xff\xff\xff\xff\xff\xff", ETHER_ADDR_LEN);
|
||||
return (if_transmit(ifp, m));
|
||||
}
|
||||
#endif /* DEV_NETMAP */
|
||||
|
||||
static int
|
||||
wg_output(if_t ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro)
|
||||
{
|
||||
|
@ -2206,6 +2341,11 @@ wg_output(if_t ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro
|
|||
return (EAFNOSUPPORT);
|
||||
}
|
||||
|
||||
#ifdef DEV_NETMAP
|
||||
if ((if_getcapenable(ifp) & IFCAP_NETMAP) != 0)
|
||||
return (wg_xmit_netmap(ifp, m, af));
|
||||
#endif
|
||||
|
||||
defragged = m_defrag(m, M_NOWAIT);
|
||||
if (defragged)
|
||||
m = defragged;
|
||||
|
@ -2781,7 +2921,10 @@ wg_clone_create(struct if_clone *ifc, char *name, size_t len,
|
|||
if_setinitfn(ifp, wg_init);
|
||||
if_setreassignfn(ifp, wg_reassign);
|
||||
if_setqflushfn(ifp, wg_qflush);
|
||||
#ifdef DEV_NETMAP
|
||||
if_settransmitfn(ifp, wg_transmit);
|
||||
if_setinputfn(ifp, wg_if_input);
|
||||
#endif
|
||||
if_setoutputfn(ifp, wg_output);
|
||||
if_setioctlfn(ifp, wg_ioctl);
|
||||
if_attach(ifp);
|
||||
|
|
Loading…
Reference in a new issue