avoid calling if_enqueue from an smr critical section.
authordlg <dlg@openbsd.org>
Sun, 15 May 2022 03:18:41 +0000 (03:18 +0000)
committerdlg <dlg@openbsd.org>
Sun, 15 May 2022 03:18:41 +0000 (03:18 +0000)
commit91ccd43470895fd953b17ab4f09d79040c33870c
tree0076113e99b96c847649cdc1e767294ac9d40b02
parent12cb42b453dc42185f2641a3e7b04f4a148161e0
avoid calling if_enqueue from an smr critical section.

claudio@ is right that as a rule of thumb it is a bad idea to call
arbitrary code from an smr crit section because the scope of what
is called is very hard to keep in your head. in this particular
case sashan@ points out that if_enqueue can call vport handlers,
which calls if_vinput, which will push a packet into the network
stack, which will call pf and try to take an rwlock. you can't sleep
in an smr crit section.

SMRs in this situation are protecting references to ports in the
list of span and actual ports attached to a veb. when we needed to
send a packet to an unknown unicast, broadcast, or multicast packet
the code would SMR_TAILQ_FOREACH over all the ports, duplicating
the mbuf and calling if_enqueue against the port. span port handling
is basically the same, but we unconditionally send to them.

this replaces the SMR_TAILQ with maps (arrays) of ports. the veb
port map data structure contains a struct refcnt and the number of
ports. the forwarding paths use an SMR crit section to get a reference
to the map, increase the refcnt, and then leaves the smr crit section
before iterating over the array of ports in the map. after the
iteration it releases the refcnt.

this does add a couple of atomic ops in the forwarding path, but
only in the uncommon case (most packets are (should be) to known
unicast addresses), and it's only one set of ops for all ports
instead of ops per port. the known unicast case follows this pattern
too.

reported by Barbaros Bilek on bugs@
fix tested by me and hrvoje popovski
ok claudio@ sashan@ bluhm@ (who also did a lot of the initial analysis)
sys/net/if_veb.c