Subject: BPF support for writing raw IEEE 802.11 frames
To: None <tech-net@netbsd.org, vraghuna@bbn.com, gdt@ir.bbn.com>
From: Vivek raghunathan <vivek.raghunathan@gmail.com>
List: tech-net
Date: 08/01/2006 14:19:15
Hello all,

(Long email)

I am currently working at BBN Technologies with Greg Troxel on a project to
virtualize an IEEE 802.11 interface over a network. The broad
architecture is as follows: (real/virtual host refers to the host on which
the real/virtual interface is attached.)

1. Frames arriving on the real interface are read by a userspace
daemon using BPF, encapsulated in UDP packets and passed off to a
userspace daemon running on the virtual host. This daemon decapsulates
the UDP packets and injects the extracted 802.11 frames into the
kernel stack at the virtual host. This injection is done by extending
the tap(4) interface to support 802.11 in addition to Ethernet.

2. When a packet is to be transmitted out the virtual interface, it is
encapsulated in 802.11 headers and passed to userspace using the
extended tap(4) interface mentioned above. The userspace daemon at the
virtual host encapsulates the frames in UDP, and sends them to the
userspace daemon at the real host, where they are injected into the
real interface using BPF.

(There are a lot of additional details here on what it means to
 virtualize an IEEE 802.11 interface over a network - that is the
 subject of another email.)

As part of this project, we need the ability to inject raw IEEE 802.11
frames from userspace using BPF. It seems like the current NetBSD
bpf_write path does not support DLT_IEEE802_11, or
DLT_IEEE802_11_RADIO. This is because:

1. (minor) net/bpf.c: bpf_write calls bpf_movein, which does not
recognize the dlt DLT_IEEE802_11/DLT_IEEE802_11_RADIO and returns EIO.
This is easily fixable.

2. net/bpf.c: bpf_write passes an mbuf to ifp->if_output. All 802.11
drivers invoke ether_ifattach via ieee80211_ifattach, and
ether_ifattach sets ifp->if_output = ether_output. Thus, bpf_write
ends up passing raw DLT_IEEE802_11 mbufs to ether_output. This causes
DLT_IEEE80211 raw frame writes on a BPF to break:

a. ether_output expects raw frames with an attached Ethernet header.
DLT_IEEE80211 raw frames do not have an Ethernet header.

b. ether_output enqueues the frame and calls the driver-provided
foo_start routine, which typically calls ieee80211_encap to do
IEEE 802.11 encapsulation. If this worked, it would incorrectly put an
802.11 header on the raw 802.11 frame. (In practice, it won't work
          because ieee80211_encap expects its input to be Ethernet
          encapsulated; it strips off the
          Ethernet header and adds an IEEE 802.11 header and a LLC
          header. Here the raw DLT_IEEE802_11 frame does not have an
          Ethernet header.)


I am currently working on fixing this, and adding support for
DLT_IEEE802_11 to the BPF write path. We expect raw DLT_IEEE802_11
frames to be injected in the same format as they are read: data frames
consist of a 802.11 header, and LLC header+IP packet in the 802.11
payload. Management frames consist of an 802.11 header and the
management frame. There are multiple options on how to implement this,
           and as I am fairly new to the NetBSD kernel, I was hoping
           to get some feedback on which way to proceed:

Option 1: When net80211 code registers DLT_IEEE802_11 with bpf using
bpfattach, it also pass a callback to be invoked whenever a raw packet
is written to the bpf device. This callback enqueues the raw 802.11
frames in the management queue (ieee80211com->mgtq) (whose present
          semantics are post-802.11 encap).

Advantage: no changes to driver specific code
Disadvantage: won't work with drivers that don't poll the management
queue (e.g., dev/ic/an.c); hack.

Details:
a. net/bpf.c: Add a bpf_output function pointer to struct
bpf_if, and a bpfattach3 function, which is basically bpfattach2 with
an additional bpf_output parameter that is used to set bpf_output in
the bpf_if structure.

b. net/bpf.c: bpf_write - On the bpf_write path,
     if(bpf_d->bd_bif->bpf_output), call it instead of calling
     bpf_d->bd_bif->bif_ifp->if_output.

c. net80211/ieee80211.c:ieee80211_ifattach - use bpfattach3 to install
DLT_IEEE802_11

d. (HACK) net80211/ieee80211_output.c: ieee80211_bpf_output - simply
enqueues the raw frame in the management queue (ic->mgtq), and does
ifp->if_start

The hack ensures that we do not need to change each driver's foo_start
function.

Option 2: When net80211 code registers DLT_IEEE802_11 with bpf using
bpfattach, it also pass a callback to be invoked whenever a raw packet
is written to the bpf device. This callback enqueues the raw 802.11
frames in a new queue (rawq) in the ieee80211com structure. For each
driver that wishes to support raw frame injection, we will have to
(trivially) modify it to poll the raw frame queue in foo_start. This
option is the same as option 1, except that we add a raw frame queue to
the ieee80211com structure (ic->rawq) instead of using the management
queue (ic->mgtq).

Advantage: cleaner than option 1
Disadvantage: need to modify each driver's foo_start function.

Details:
a, b, c same as Option 1.

d*. net80211/ieee80211_output.c: ieee80211_bpf_output - enqueues the
raw frame in the raw frame queue (ic->rawq) and does ifp->if_start

e. dev/ic/foo.c:foo_start - Drivers that support raw frame BPF writes
will service the raw frame queue in foo_start similar to how
management frames are serviced. In other words, no 802.11 encap, and
foo_start does no checks on the state of the IEEE 802.11 state machine
for these packets. (Thus, potentially it would be possible to inject a
          data frame before associating with an AP.)

Option 3: Change the semantics of BIOCSHDRCMPLT to indicate which
headers are already present on the written frame. These flags are
copied into a m_tag and passed around. The current code path is
modified so that all functions that add headers check the m_tag and
skip header encapsulation if the corresponding flag is set.

Advantage: ?
Disadvantage: ?

Details:
a. net/bpf.c:bpf_ioctl - change the semantics of BIOCSHDRCMPLT: the
value is now a combination of flags representing which headers are
switched on. Only two are defined for now: HDR_ETHER and
HDR_IEEE80211.

b. net/bpf.c:bpf_write - For Ethernet as well as IEEE 802.11,
     bpf_write constructs and passes the mbuf as before to
     ifp->if_output = ether_output. It also attaches an m_tag to the
     mbuf indicating which headers are already present. If the dlt =
     DLT_IEEE802_11, then m_tag indicates that Ethernet is present,
     even though it is not actually there.

c. net/if_ethersubr.c:ether_output - code now checks if the Ethernet
header is already complete from the m_tag and skips header
construction.

d. dev/ic/foo.c:foo_start - driver's foo_start fn checks if the 802.11
header is already complete from the m_tag and skips IEEE 802.11
encapsulation.

-Vivek


-- 

---

*************************************
Vivek Raghunathan,
Intern - Internetwork Research Group,
BBN Technologies.

PhD student,
University of Illinois, Urbana-Champaign

Contact Details:
1012 W. Clark St #31,
Urbana IL 61801

ph: 217-766-1868 (cell)
    217-333-7541 (off)