Re: promisc/netgraph question

Zhenlei Huang <[email protected]>
Newsgroups gmane.os.freebsd.devel.net
Message-ID <[email protected]>

> On Jan 31, 2026, at 3:44 PM, Martin Mayer <[email protected]> wrote:
> 
> Let me explain my intend from a higher level and forget about the code details for a moment.
> 
> If a packet ingresses an interface where
>  ( dst MAC != interface MAC ) &&
>  ( dst MAC != multicast joined groups ) &&
>  ( dst MAC != broadcast )
> I'd expect
>  - the packet to be directly discarded if !( IFF_PROMISC || IFF_PPROMISC )
>  - the packet to be accepted but not passed to upper layers if ( IFF_PROMISC || IFF_PPROMISC )
> 
> A special case is CARP where IFF_PROMISC is set and should be passed to upper layers if
>  ( dst MAC == CARP instance MAC && CARP enabled )
> 
> I think we have a consensus until here. This is the current (normal) behavior.
> 
> This behavior changes as soon as netgraph is involved.
> If ng_ether hooks are connected, the host skips these checks and passes the packet to the upper layers.
> 
> The practical issue to my question is:
> https://github.com/opnsense/src/issues/279
> 
> I want to understand the root cause of this behavior and want to identify if this is intentionally or if it's a bug.
> As per my understanding, this happens in if_ethersubr.c because the hook for the packet to be claimed by netgraph is BEFORE the destination addresses are checked.
> 
> [...]
> 	/* Allow ng_ether(4) to claim this frame. */
> 	if (ifp->if_l2com != NULL) {
> 		KASSERT(ng_ether_input_p != NULL,
> 		    ("%s: ng_ether_input_p is NULL", __func__));
> 		m->m_flags &= ~M_PROMISC;
> 		(*ng_ether_input_p)(ifp, &m);
> 		if (m == NULL) {
> 			CURVNET_RESTORE();
> 			return;
> 		}
> 		eh = mtod(m, struct ether_header *);
> 	}
> [...]
> 	if (ifp->if_carp && (*carp_forus_p)(ifp, eh->ether_dhost)) {
> 		m->m_flags &= ~M_PROMISC;
> 	} else	{
> 		/*
> 		 * If the frame received was not for our MAC address, set the
> 		 * M_PROMISC flag on the mbuf chain. The frame may need to
> 		 * be seen by the rest of the Ethernet input path in case of
> 		 * re-entry (e.g. bridge, vlan, netgraph) but should not be
> 		 * seen by upper protocol layers.
> 		 */
> 		if (!ETHER_IS_MULTICAST(eh->ether_dhost) &&
> 		    memcmp(IF_LLADDR(ifp), eh->ether_dhost, ETHER_ADDR_LEN) != 0)
> 			m->m_flags |= M_PROMISC;
> 	}
> 
> 	ether_demux(ifp, m);
> [...]
> 
> ether_demux() (which is the re-entry point from ng_ether) won't be able to correctly evaluate (m->m_flags & M_PROMISC).
> 
> Thanks, Martin
> 
> 


The logic from ng_ether(4),

```
/*
 * Handle an mbuf received on the "upper" hook.
 */
static int
ng_ether_rcv_upper(hook_p hook, item_p item)
{
....
        m->m_pkthdr.rcvif = ifp;

        /* Pass the packet to the bridge, it may come back to us */
        if (ifp->if_bridge) {
                BRIDGE_INPUT(ifp, m);
                if (m == NULL)
                        return (0);
        }

        /* Route packet back in */
        ether_demux(ifp, m);
        return (0);
}
```

it lacks checking of the dest MAC of the inbound packets, and unconditionally inject packets ( no matter
whether they're for us or not ) via ether_demux() to the net stack.

Probably we should 
 1. check the dest MAC address prior to passing the packets to ng_ether(4), or
 2. teach ng_ether(4) to check the dest MAC address, or
 3. Move the checking to ether_demux().

The 3rd one has the minimal impact. I'm CCing Gleb who is more familiar with netgraph.

Best regards,
Zhenlei
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.