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


Reply via email to