bpf: modularize ifnet(9) part of bpf

Imagine that bpf(9) tapping can happen at any point in the network stack,
not necessarily at interface transmit or receive.  To achieve that we need
a thin layer of abstraction defined by struct bif_methods, that defines
how generic bpf layer works with a tap point of this kind.

Implement ifnet(9) specific methods in a separate file bpf_ifnet.c.  At
this point there is 100% compatibility for all existing interfaces, there
is no KPI change, yet.  The legacy attaching KPI is layered over new ifnet
agnostic KPI.  The new KPI may change though, as we can implement multiple
DLTs per single tap point in a prettier fashion.

The new abstraction layer allows us to move all the 802.11 radio injection
hacks out of bpf.c into ieee80211_radiotap.c, so do that immediately as a
good proof of concept.

Reviewed by:		bz
Differential Revision:	https://reviews.freebsd.org/D53872
This commit is contained in:
Gleb Smirnoff 2025-12-15 12:50:35 -08:00
parent 9d56f84df5
commit 8774a990ee
6 changed files with 656 additions and 515 deletions

View file

@ -4189,6 +4189,7 @@ net/bpf_buffer.c optional bpf
net/bpf_jitter.c optional bpf_jitter
net/bpf_filter.c optional bpf | netgraph_bpf
net/bpf_zerocopy.c optional bpf
net/bpf_ifnet.c optional bpf
net/bridgestp.c optional bridge | if_bridge
net/dummymbuf.c optional dummymbuf
net/ieee8023ad_lacp.c optional lagg

File diff suppressed because it is too large Load diff

View file

@ -407,9 +407,14 @@ SYSCTL_DECL(_net_bpf);
* Part of this structure is exposed to external callers to speed up
* bpf_peers_present() calls.
*/
struct mbuf;
struct bpf_if;
struct bif_methods;
CK_LIST_HEAD(bpfd_list, bpf_d);
struct bpf_if * bpf_attach(const char *, u_int, u_int,
const struct bif_methods *, void *);
void bpf_detach(struct bpf_if *);
void bpf_bufheld(struct bpf_d *d);
int bpf_validate(const struct bpf_insn *, int);
void bpf_tap(struct bpf_if *, u_char *, u_int);
@ -419,7 +424,6 @@ void bpf_mtap_if(struct ifnet *, struct mbuf *);
void bpf_mtap2(struct bpf_if *, void *, u_int, struct mbuf *);
void bpf_mtap2_if(struct ifnet *, void *, u_int, struct mbuf *);
void bpfattach(struct ifnet *, u_int, u_int);
void bpfattach2(struct ifnet *, u_int, u_int, struct bpf_if **);
void bpfdetach(struct ifnet *);
bool bpf_peers_present_if(struct ifnet *);
#ifdef VIMAGE
@ -444,6 +448,28 @@ bpf_peers_present(const struct bpf_if *bpf)
bpf_mtap_if((_ifp), (_m))
#define BPF_MTAP2(_ifp, _data, _dlen, _m) \
bpf_mtap2_if((_ifp), (_data), (_dlen), (_m))
typedef void bif_attachd_t(void *);
typedef void bif_detachd_t(void *);
typedef bool bif_chkdir_t(void *, const struct mbuf *, int);
typedef int bif_write_t(void *, struct mbuf *, struct mbuf *, int);
typedef uint32_t bif_wrsize_t(void *);
typedef int bif_promisc_t(void *, bool);
typedef int bif_mac_check_receive_t(void *, struct bpf_d *);
struct bif_methods {
bif_attachd_t *bif_attachd;
bif_detachd_t *bif_detachd;
bif_chkdir_t *bif_chkdir;
bif_promisc_t *bif_promisc;
/* Writable taps shall implement the below methods. */
bif_write_t *bif_write;
bif_wrsize_t *bif_wrsize;
bif_mac_check_receive_t *bif_mac_check_receive;
};
/* Ifnet methods implemented in bpf_ifnet.c are shared with net80211. */
extern bif_wrsize_t bpf_ifnet_wrsize;
extern bif_promisc_t bpf_ifnet_promisc;
#endif /* _KERNEL */
/*

257
sys/net/bpf_ifnet.c Normal file
View file

@ -0,0 +1,257 @@
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1990, 1991, 1993
* The Regents of the University of California. All rights reserved.
* Copyright (c) 2019 Andrey V. Elsukov <ae@FreeBSD.org>
* Copyright (c) 2025 Gleb Smirnoff <glebius@FreeBSD.org>
*
* This code is derived from the Stanford/CMU enet packet filter,
* (net/enet.c) distributed as part of 4.3BSD, and code contributed
* to Berkeley by Steven McCanne and Van Jacobson both of Lawrence
* Berkeley Laboratory.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <net/bpf.h>
#include <net/bpfdesc.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_private.h>
#include <net/if_types.h>
#include <net/route.h>
/* We need to know all the ifnets we support. */
#include <net/if_dl.h>
#include <net/ethernet.h>
#include <net/firewire.h>
#include <net/if_pflog.h>
#include <net/if_pfsync.h>
#include <security/mac/mac_framework.h>
static int
bpf_ifnet_write(void *arg, struct mbuf *m, struct mbuf *mc, int flags)
{
struct ifnet *ifp = arg;
struct route ro = {};
struct sockaddr dst = {
.sa_family = AF_UNSPEC,
};
u_int hlen;
int error;
NET_EPOCH_ASSERT();
if (__predict_false((ifp->if_flags & IFF_UP) == 0)) {
m_freem(m);
m_freem(mc);
return (ENETDOWN);
}
switch (ifp->if_type) {
/* DLT_RAW */
case IFT_MBIM: /* umb(4) */
case IFT_OTHER: /* uhso(4), usie */
hlen = 0;
break;
/* DLT_ENC */
case IFT_ENC:
hlen = 12; /* XXXGL: sizeof(struct enchdr); */
break;
/* DLT_EN10MB */
case IFT_ETHER: /* if_ethersubr.c */
case IFT_L2VLAN: /* vlan(4) */
case IFT_IEEE8023ADLAG: /* lagg(4) */
case IFT_INFINIBAND: /* if_infiniband.c */
{
struct ether_header *eh;
eh = mtod(m, struct ether_header *);
if (ETHER_IS_MULTICAST(eh->ether_dhost)) {
if (bcmp(ifp->if_broadcastaddr, eh->ether_dhost,
ETHER_ADDR_LEN) == 0)
m->m_flags |= M_BCAST;
else
m->m_flags |= M_MCAST;
}
if (!(flags & BPFD_HDRCMPLT)) {
memcpy(eh->ether_shost, IF_LLADDR(ifp),
sizeof(eh->ether_shost));
}
hlen = ETHER_HDR_LEN;
break;
}
/* DLT_APPLE_IP_OVER_IEEE1394 */
case IFT_IEEE1394: /* fwip(4) */
hlen = sizeof(struct fw_hwaddr);
break;
/* DLT_NULL */
case IFT_GIF: /* gif(4) */
case IFT_LOOP: /* lo(4), disc(4) */
case IFT_PARA: /* plip(4), iic */
case IFT_PPP: /* tun(4) */
case IFT_PROPVIRTUAL: /* ng_iface(4) */
case IFT_WIREGUARD: /* wg(4) */
case IFT_STF: /* stf(4) */
case IFT_TUNNEL: /* ipsec(4), me(4), gre(4), ovpn(4) */
hlen = sizeof(uint32_t);
break;
/* DLT_PFLOG */
case IFT_PFLOG:
hlen = PFLOG_HDRLEN;
break;
/* DLT_PFSYNC */
case IFT_PFSYNC:
hlen = PFSYNC_HDRLEN;
break;
default:
hlen = 0; /* pacify compiler */
KASSERT(0, ("%s: ifp %p type %u not supported", __func__,
ifp, ifp->if_type));
}
if (__predict_false(hlen > m->m_len)) {
m_freem(m);
m_freem(mc);
return (EMSGSIZE);
};
if (hlen != 0) {
bcopy(mtod(m, const void *), &dst.sa_data, hlen);
ro.ro_prepend = (char *)&dst.sa_data;
ro.ro_plen = hlen;
ro.ro_flags = RT_HAS_HEADER;
m->m_pkthdr.len -= hlen;
m->m_len -= hlen;
m->m_data += hlen;
};
CURVNET_SET(ifp->if_vnet);
error = ifp->if_output(ifp, m, &dst, &ro);
if (error != 0) {
m_freem(mc);
} else if (mc != NULL) {
mc->m_pkthdr.rcvif = ifp;
(void)ifp->if_input(ifp, mc);
}
CURVNET_RESTORE();
return (error);
}
static bool
bpf_ifnet_chkdir(void *arg, const struct mbuf *m, int dir)
{
struct ifnet *ifp = arg;
struct ifnet *rcvif = m_rcvif(m);
return ((dir == BPF_D_IN && ifp != rcvif) ||
(dir == BPF_D_OUT && ifp == rcvif));
}
uint32_t
bpf_ifnet_wrsize(void *arg)
{
struct ifnet *ifp = arg;
return (ifp->if_mtu);
}
int
bpf_ifnet_promisc(void *arg, bool on)
{
struct ifnet *ifp = arg;
int error;
CURVNET_SET(ifp->if_vnet);
if ((error = ifpromisc(ifp, on ? 1 : 0)) != 0)
if_printf(ifp, "%s: ifpromisc failed (%d)\n", __func__, error);
CURVNET_RESTORE();
return (error);
}
#ifdef MAC
static int
bpf_ifnet_mac_check_receive(void *arg, struct bpf_d *d)
{
struct ifnet *ifp = arg;
return (mac_bpfdesc_check_receive(d, ifp));
}
#endif
static const struct bif_methods bpf_ifnet_methods = {
.bif_chkdir = bpf_ifnet_chkdir,
.bif_promisc = bpf_ifnet_promisc,
.bif_wrsize = bpf_ifnet_wrsize,
.bif_write = bpf_ifnet_write,
#ifdef MAC
.bif_mac_check_receive = bpf_ifnet_mac_check_receive,
#endif
};
/*
* Attach an interface to bpf. dlt is the link layer type; hdrlen is the
* fixed size of the link header (variable length headers not yet supported).
* Legacy KPI to be obsoleted soon.
*/
void
bpfattach(struct ifnet *ifp, u_int dlt, u_int hdrlen)
{
ifp->if_bpf = bpf_attach(ifp->if_xname, dlt, hdrlen,
&bpf_ifnet_methods, ifp);
if_ref(ifp);
if (bootverbose && IS_DEFAULT_VNET(curvnet))
if_printf(ifp, "bpf attached\n");
}
/*
* The dead_bpf_if is an ugly plug against races at ifnet destroy time that
* still exist and are not properly covered by epoch(9).
* Legacy KPI to be obsoleted soon.
*/
void
bpfdetach(struct ifnet *ifp)
{
static const struct bpfd_list dead_bpf_if = CK_LIST_HEAD_INITIALIZER();
struct bpf_if *bif;
bif = ifp->if_bpf;
ifp->if_bpf = __DECONST(struct bpf_if *, &dead_bpf_if);
bpf_detach(bif);
if_rele(ifp);
}

View file

@ -1088,35 +1088,7 @@ ieee80211_load_module(const char *modname)
#endif
}
static eventhandler_tag wlan_bpfevent;
static eventhandler_tag wlan_ifllevent;
static void
bpf_track(void *arg, struct ifnet *ifp, int dlt, int attach)
{
/* NB: identify vap's by if_init */
if (dlt == DLT_IEEE802_11_RADIO &&
ifp->if_init == ieee80211_init) {
struct ieee80211vap *vap = ifp->if_softc;
/*
* Track bpf radiotap listener state. We mark the vap
* to indicate if any listener is present and the com
* to indicate if any listener exists on any associated
* vap. This flag is used by drivers to prepare radiotap
* state only when needed.
*/
if (attach) {
ieee80211_syncflag_ext(vap, IEEE80211_FEXT_BPF);
if (vap->iv_opmode == IEEE80211_M_MONITOR)
atomic_add_int(&vap->iv_ic->ic_montaps, 1);
} else if (!bpf_peers_present(vap->iv_rawbpf)) {
ieee80211_syncflag_ext(vap, -IEEE80211_FEXT_BPF);
if (vap->iv_opmode == IEEE80211_M_MONITOR)
atomic_subtract_int(&vap->iv_ic->ic_montaps, 1);
}
}
}
/*
* Change MAC address on the vap (if was not started).
*/
@ -1385,8 +1357,6 @@ wlan_modevent(module_t mod, int type, void *unused)
case MOD_LOAD:
if (bootverbose)
printf("wlan: <802.11 Link Layer>\n");
wlan_bpfevent = EVENTHANDLER_REGISTER(bpf_track,
bpf_track, 0, EVENTHANDLER_PRI_ANY);
wlan_ifllevent = EVENTHANDLER_REGISTER(iflladdr_event,
wlan_iflladdr, NULL, EVENTHANDLER_PRI_ANY);
struct if_clone_addreq req = {
@ -1398,7 +1368,6 @@ wlan_modevent(module_t mod, int type, void *unused)
return 0;
case MOD_UNLOAD:
ifc_detach_cloner(wlan_cloner);
EVENTHANDLER_DEREGISTER(bpf_track, wlan_bpfevent);
EVENTHANDLER_DEREGISTER(iflladdr_event, wlan_ifllevent);
return 0;
}

View file

@ -43,8 +43,10 @@
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_private.h>
#include <net/if_media.h>
#include <net/ethernet.h>
#include <net/route.h>
#include <net80211/ieee80211_var.h>
@ -108,20 +110,6 @@ ieee80211_radiotap_detach(struct ieee80211com *ic)
{
}
void
ieee80211_radiotap_vattach(struct ieee80211vap *vap)
{
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_radiotap_header *th = ic->ic_th;
if (th != NULL && ic->ic_rh != NULL) {
/* radiotap DLT for raw 802.11 frames */
bpfattach2(vap->iv_ifp, DLT_IEEE802_11_RADIO,
sizeof(struct ieee80211_frame) + le16toh(th->it_len),
&vap->iv_rawbpf);
}
}
void
ieee80211_radiotap_vdetach(struct ieee80211vap *vap)
{
@ -372,3 +360,115 @@ radiotap_offset(struct ieee80211_radiotap_header *rh,
}
return -1;
}
static bool
bpf_ieee80211_chkdir(void *arg, const struct mbuf *m, int dir)
{
struct ifnet *ifp = arg;
struct ifnet *rcvif = m_rcvif(m);
return ((dir == BPF_D_IN && ifp != rcvif) ||
(dir == BPF_D_OUT && ifp == rcvif));
}
static void
bpf_ieee80211_attach(void *sc)
{
struct ieee80211vap *vap = if_getsoftc((if_t)sc);
/*
* Track bpf radiotap listener state. We mark the vap
* to indicate if any listener is present and the com
* to indicate if any listener exists on any associated
* vap. This flag is used by drivers to prepare radiotap
* state only when needed.
*/
ieee80211_syncflag_ext(vap, IEEE80211_FEXT_BPF);
if (vap->iv_opmode == IEEE80211_M_MONITOR)
atomic_add_int(&vap->iv_ic->ic_montaps, 1);
}
static void
bpf_ieee80211_detach(void *sc)
{
struct ieee80211vap *vap = if_getsoftc((if_t)sc);
if (!bpf_peers_present(vap->iv_rawbpf)) {
ieee80211_syncflag_ext(vap, -IEEE80211_FEXT_BPF);
if (vap->iv_opmode == IEEE80211_M_MONITOR)
atomic_subtract_int(&vap->iv_ic->ic_montaps, 1);
}
}
static int
bpf_ieee80211_write(void *arg, struct mbuf *m, struct mbuf *mc, int flags)
{
struct ifnet *ifp = arg;
struct sockaddr dst = {
.sa_family = AF_IEEE80211,
/* XXXGL: value and XXX comment from 246b5467621a */
.sa_len = 12, /* XXX != 0 */
};
struct route ro = {
.ro_prepend = (char *)&dst.sa_data,
.ro_flags = RT_HAS_HEADER,
};
const struct ieee80211_bpf_params *p;
u_int hlen;
int error;
NET_EPOCH_ASSERT();
/*
* Collect true length from the parameter header.
* XXX check ibp_vers
*/
p = mtod(m, const struct ieee80211_bpf_params *);
if (p->ibp_len > sizeof(dst.sa_data)) {
m_freem(m);
m_freem(mc);
return (EMSGSIZE);
}
hlen = ro.ro_plen = p->ibp_len;
bcopy(mtod(m, const void *), &dst.sa_data, hlen);
m->m_pkthdr.len -= hlen;
m->m_len -= hlen;
m->m_data += hlen;
CURVNET_SET(ifp->if_vnet);
error = ifp->if_output(ifp, m, &dst, &ro);
if (error != 0) {
m_freem(mc);
} else if (mc != NULL) {
mc->m_pkthdr.rcvif = ifp;
(void)ifp->if_input(ifp, mc);
}
CURVNET_RESTORE();
return (error);
}
static const struct bif_methods bpf_ieee80211_methods = {
.bif_chkdir = bpf_ieee80211_chkdir,
.bif_attachd = bpf_ieee80211_attach,
.bif_detachd = bpf_ieee80211_detach,
.bif_promisc = bpf_ifnet_promisc,
.bif_wrsize = bpf_ifnet_wrsize,
.bif_write = bpf_ieee80211_write,
};
void
ieee80211_radiotap_vattach(struct ieee80211vap *vap)
{
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_radiotap_header *th = ic->ic_th;
if (th != NULL && ic->ic_rh != NULL) {
/* radiotap DLT for raw 802.11 frames */
vap->iv_rawbpf = bpf_attach(if_name(vap->iv_ifp),
DLT_IEEE802_11_RADIO,
sizeof(struct ieee80211_frame) + le16toh(th->it_len),
&bpf_ieee80211_methods, vap->iv_ifp);
if_ref(vap->iv_ifp);
}
}