> 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


Reply via email to