Confusion about MediumType, UpperRange/LowerRange and FilterMediaTypes

So I’m writting a lightweight filter driver and I need it to be able to parse frames and positively identify IPV4/IPV6 packet types.
I was able to get it working pretty well on simulated Ethernet on a VM, and I started to look into how to implement it for Wifi and other media types.
However when I attached to a Wifi Adapter, I was surprised to learn that I am actually attaching to Medium803_2 which is Ethernet ? So I went down the rabbit hole of figuring out what the hell does UpperRange, LowerRange, FilterMediaTypes and MediumType means, and the more I tried to read about them the more confused I got.
NDIS docs are really unclear what all of these means (for a person that had nothing to do with Ndis previously).
Can I rely on all Wifi adapters I attach to being Medium803_2 and giving me Ethernet frames instead of Wifi frames ?
What about other types of network adapters, will those also be Ethernetmedium ? Will they also give me ethernet frames?
What the hell are those “Wan Miniport” things I get to attach to ?
Can someone explain how all these mediums stackup and how that translates to what frames I need to parse in NET_BUFFER packets?

Yeah, it’s quite the rabbit-hole. Most of the weirdness comes from the fact that LWFs were grafted onto the system later (NDIS 6.0), so the “obvious” way to do it would have broken any driver that didn’t expect them.

The most common framing types:

  • ndis5, despite the misleading name, just means that you bind to ethernet-like things, where the packet has a 14-byte header, you can do ethernet-style multicast/broadcast, and there is no connection management. Use ARP/NS as defined over Ethernet to bootstrap your layer-3.
  • flpp4andflpp6` mean that the layer2 header is 0 bytes, and the packet begins on a raw IPv4 or IPv6 header respectively. (You can use NetBufferListProtocolId to distinguish, if you handle both types through the same code.) There’s no layer-2 multicast/broadcast or connection management. Layer 3 is manually injected by the NIC (e.g., it will directly update the local unicast IP address and gateway address.)
  • There are others defined, but they’re for legacy media types or for niche scenarios. You can define your own, if you want to have a special NIC coordinate with a special protocol.

If you’re a protocol driver, use those keywords like this:

  • UpperRange is only used for TDI bindings, which you shouldn’t be using in the year 2020. So just set UpperRange to noupper.
  • LowerRange tells you which type of network adapter you bind to. Most commonly, you’ll just set it to ndis5, but you may also want to handle flpp4 and flpp6, since most mobile broadband traffic presents itself that way.

If you’re a miniport driver, use those keywords like this:

  • UpperRange: put either ndis5 if you’re Ethernet, WLAN, or most virtual NICs; or put flpp4,flpp6 if you’re mobile broadband. There are a few other special scenarios, like sriov that have different tokens.
  • LowerRange: put a token that best describes the physical media type. Many possible tokens are documented here. Typically you’ll use ethernet for Ethernet and virtual adapters, wlan for WLAN, and ppip for mobile broadband.

If you’re a filter driver, use those keywords like this:

  • UpperRange: put noupper
  • LowerRange: put nolower
  • FilterMediaTypes: put the media types of the miniports that you want to bind to. Commonly, this is ethernet,wlan,bluetooth if you want ethernet-like things. Also add ppip if you want mobile broadband, and can handle raw IP framing. There are other tokens for niche scenarios like vSwitch extensions.

The binding logic works like this.

For protocols:

  1. Build a candidate set of network interfaces. Start by populating it with all network interfaces on the system.
  2. Remove any network interface whose UpperRange has empty intersection with your protocol’s LowerRange.
  3. Any network interfaces that remain in the candidate set can bind to your protocol.

For lightweight filters:

  1. Build a candidate set of network interfaces. Start by populating it with all network interfaces on the system.
  2. Remove any network interface from the candidate set whose LowerRange has empty intersection with your filter’s FilterMediaTypes.
  3. Any network interfaces that remain in the candidate set can bind to your filter.

(In reality, the algorithms have a few extra steps. For example, there’s a very rarely-used LowerExclude directive that removes any miniport whose UpperRange has a nonempty intersection. But you can ignore that.)

As you’ve noticed, it’s a little confusing for filter drivers, because you’re two steps away from the actual framing type used in kernel mode. You bind via media type keywords, but you have to just know the mapping of media type to framing type. So for example, binding to ppip gets you a framing type of flpp4,flpp6, which gets you raw IP frames at runtime.

Can I rely on all Wifi adapters I attach to being Medium803_2 and giving me Ethernet frames instead of Wifi frames ?

WLAN is a special case. WLAN starts out as “native 802.11” at the bottom of the stack, but then it gets swizzled into something that resembles 802.3 by the NWIFI filter driver. If you bind below NWIFI, you’ll see the raw 802.11 behavior. If you bind above NWIFI, you’ll see something that’s close enough to Ethernet. If you’re writing a monitoring filter, you’ll bind at both places, so you have to look at the mediatype in your filter attach parameters to see where this filter instance is binding. Otherwise, a modifying filter should land above NWIFI, so you can assume Ethernet. But by putting wlan into your FilterMediaTypes, you’ve promised that you’ve tested this case and won’t just bugcheck if presented with a WLAN adapter.

Wow, thank’s that made things quite a bit clearer. What I’m doing is a filter that can prevent ipv6 traffic from getting trough, I’d love to just deal with IP packets, or Ethernet at worst, but I need to be able to filter all possible setups (to a degree).