diff --git a/share/man/man4/wg.4 b/share/man/man4/wg.4 --- a/share/man/man4/wg.4 +++ b/share/man/man4/wg.4 @@ -121,6 +121,19 @@ 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 @@ .Xr ip 4 , .Xr ipsec 4 , .Xr netintro 4 , +.Xr netmap 4 , .Xr ovpn 4 , .Xr ipf 5 , .Xr pf.conf 5 , diff --git a/sys/dev/wg/if_wg.c b/sys/dev/wg/if_wg.c --- a/sys/dev/wg/if_wg.c +++ b/sys/dev/wg/if_wg.c @@ -1674,6 +1674,31 @@ } } +#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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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);