Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'bridge-allow-keeping-local-fdb-entries-only-on-vlan-0'

Petr Machata says:

====================
bridge: Allow keeping local FDB entries only on VLAN 0

The bridge FDB contains one local entry per port per VLAN, for the MAC of
the port in question, and likewise for the bridge itself. This allows
bridge to locally receive and punt "up" any packets whose destination MAC
address matches that of one of the bridge interfaces or of the bridge
itself.

The number of these local "service" FDB entries grows linearly with number
of bridge-global VLAN memberships, but that in turn will tend to grow
quadratically with number of ports and per-port VLAN memberships. While
that does not cause issues during forwarding lookups, it does make dumps
impractically slow.

As an example, with 100 interfaces, each on 4K VLANs, a full dump of FDB
that just contains these 400K local entries, takes 6.5s. That's _without_
considering iproute2 formatting overhead, this is just how long it takes to
walk the FDB (repeatedly), serialize it into netlink messages, and parse
the messages back in userspace.

This is to illustrate that with growing number of ports and VLANs, the time
required to dump this repetitive information blows up. Arguably 4K VLANs
per interface is not a very realistic configuration, but then modern
switches can instead have several hundred interfaces, and we have fielded
requests for >1K VLAN memberships per port among customers.

FDB entries are currently all kept on a single linked list, and then
dumping uses this linked list to walk all entries and dump them in order.
When the message buffer is full, the iteration is cut short, and later
restarted. Of course, to restart the iteration, it's first necessary to
walk the already-dumped front part of the list before starting dumping
again. So one possibility is to organize the FDB entries in different
structure more amenable to walk restarts.

One option is to walk directly the hash table. The advantage is that no
auxiliary structure needs to be introduced. With a rough sketch of this
approach, the above scenario gets dumped in not quite 3 s, saving over 50 %
of time. However hash table iteration requires maintaining an active cursor
that must be collected when the dump is aborted. It looks like that would
require changes in the NDO protocol to allow to run this cleanup. Moreover,
on hash table resize the iteration is simply restarted. FDB dumps are
currently not guaranteed to correspond to any one particular state: entries
can be missed, or be duplicated. But with hash table iteration we would get
that plus the much less graceful resize behavior, where swaths of FDB are
duplicated.

Another option is to maintain the FDB entries in a red-black tree. We have
a PoC of this approach on hand, and the above scenario is dumped in about
2.5 s. Still not as snappy as we'd like it, but better than the hash table.
However the savings come at the expense of a more expensive insertion, and
require locking during dumps, which blocks insertion.

The upside of these approaches is that they provide benefits whatever the
FDB contents. But it does not seem like either of these is workable.
However we intend to clean up the RB tree PoC and present it for
consideration later on in case the trade-offs are considered acceptable.

Yet another option might be to use in-kernel FDB filtering, and to filter
the local entries when dumping. Unfortunately, this does not help all that
much either, because the linked-list walk still needs to happen. Also, with
the obvious filtering interface built around ndm_flags / ndm_state
filtering, one can't just exclude pure local entries in one query. One
needs to dump all non-local entries first, and then to get permanent
entries in another run filter local & added_by_user. I.e. one needs to pay
the iteration overhead twice, and then integrate the result in userspace.
To get significant savings, one would need a very specific knob like "dump,
but skip/only include local entries". But if we are adding a local-specific
knobs, maybe let's have an option to just not duplicate them in the first
place.

All this FDB duplication is there merely to make things snappy during
forwarding. But high-radix switches with thousands of VLANs typically do
not process much traffic in the SW datapath at all, but rather offload vast
majority of it. So we could exchange some of the runtime performance for a
neater FDB.

To that end, in this patchset, introduce a new bridge option,
BR_BOOLOPT_FDB_LOCAL_VLAN_0, which when enabled, has local FDB entries
installed only on VLAN 0, instead of duplicating them across all VLANs.
Then to maintain the local termination behavior, on FDB miss, the bridge
does a second lookup on VLAN 0.

Enabling this option changes the bridge behavior in expected ways. Since
the entries are only kept on VLAN 0, FDB get, flush and dump will not
perceive them on non-0 VLANs. And deleting the VLAN 0 entry affects
forwarding on all VLANs.

This patchset is loosely based on a privately circulated patch by Nikolay
Aleksandrov.

The patchset progresses as follows:

- Patch #1 introduces a bridge option to enable the above feature. Then
patches #2 to #5 gradually patch the bridge to do the right thing when
the option is enabled. Finally patch #6 adds the UAPI knob and the code
for when the feature is enabled or disabled.
- Patches #7, #8 and #9 contain fixes and improvements to selftest
libraries
- Patch #10 contains a new selftest
====================

Link: https://patch.msgid.link/cover.1757004393.git.petrm@nvidia.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+559 -28
+3
include/uapi/linux/if_bridge.h
··· 823 823 /* bridge boolean options 824 824 * BR_BOOLOPT_NO_LL_LEARN - disable learning from link-local packets 825 825 * BR_BOOLOPT_MCAST_VLAN_SNOOPING - control vlan multicast snooping 826 + * BR_BOOLOPT_FDB_LOCAL_VLAN_0 - local FDB entries installed by the bridge 827 + * driver itself should only be added on VLAN 0 826 828 * 827 829 * IMPORTANT: if adding a new option do not forget to handle 828 830 * it in br_boolopt_toggle/get and bridge sysfs ··· 834 832 BR_BOOLOPT_MCAST_VLAN_SNOOPING, 835 833 BR_BOOLOPT_MST_ENABLE, 836 834 BR_BOOLOPT_MDB_OFFLOAD_FAIL_NOTIFICATION, 835 + BR_BOOLOPT_FDB_LOCAL_VLAN_0, 837 836 BR_BOOLOPT_MAX 838 837 }; 839 838
+22
net/bridge/br.c
··· 259 259 .notifier_call = br_switchdev_blocking_event, 260 260 }; 261 261 262 + static int 263 + br_toggle_fdb_local_vlan_0(struct net_bridge *br, bool on, 264 + struct netlink_ext_ack *extack) 265 + { 266 + int err; 267 + 268 + if (br_opt_get(br, BROPT_FDB_LOCAL_VLAN_0) == on) 269 + return 0; 270 + 271 + err = br_fdb_toggle_local_vlan_0(br, on, extack); 272 + if (err) 273 + return err; 274 + 275 + br_opt_toggle(br, BROPT_FDB_LOCAL_VLAN_0, on); 276 + return 0; 277 + } 278 + 262 279 /* br_boolopt_toggle - change user-controlled boolean option 263 280 * 264 281 * @br: bridge device ··· 304 287 case BR_BOOLOPT_MDB_OFFLOAD_FAIL_NOTIFICATION: 305 288 br_opt_toggle(br, BROPT_MDB_OFFLOAD_FAIL_NOTIFICATION, on); 306 289 break; 290 + case BR_BOOLOPT_FDB_LOCAL_VLAN_0: 291 + err = br_toggle_fdb_local_vlan_0(br, on, extack); 292 + break; 307 293 default: 308 294 /* shouldn't be called with unsupported options */ 309 295 WARN_ON(1); ··· 327 307 return br_opt_get(br, BROPT_MST_ENABLED); 328 308 case BR_BOOLOPT_MDB_OFFLOAD_FAIL_NOTIFICATION: 329 309 return br_opt_get(br, BROPT_MDB_OFFLOAD_FAIL_NOTIFICATION); 310 + case BR_BOOLOPT_FDB_LOCAL_VLAN_0: 311 + return br_opt_get(br, BROPT_FDB_LOCAL_VLAN_0); 330 312 default: 331 313 /* shouldn't be called with unsupported options */ 332 314 WARN_ON(1);
+108 -6
net/bridge/br_fdb.c
··· 459 459 struct net_bridge_fdb_entry *f; 460 460 struct net_bridge *br = p->br; 461 461 struct net_bridge_vlan *v; 462 + bool local_vlan_0; 463 + 464 + local_vlan_0 = br_opt_get(br, BROPT_FDB_LOCAL_VLAN_0); 462 465 463 466 spin_lock_bh(&br->hash_lock); 464 467 vg = nbp_vlan_group(p); ··· 471 468 /* delete old one */ 472 469 fdb_delete_local(br, p, f); 473 470 474 - /* if this port has no vlan information 475 - * configured, we can safely be done at 476 - * this point. 471 + /* if this port has no vlan information configured, or 472 + * local entries are only kept on VLAN 0, we can safely 473 + * be done at this point. 477 474 */ 478 - if (!vg || !vg->num_vlans) 475 + if (!vg || !vg->num_vlans || local_vlan_0) 479 476 goto insert; 480 477 } 481 478 } ··· 484 481 /* insert new address, may fail if invalid address or dup. */ 485 482 fdb_add_local(br, p, newaddr, 0); 486 483 487 - if (!vg || !vg->num_vlans) 484 + if (!vg || !vg->num_vlans || local_vlan_0) 488 485 goto done; 489 486 490 487 /* Now add entries for every VLAN configured on the port. ··· 503 500 struct net_bridge_vlan_group *vg; 504 501 struct net_bridge_fdb_entry *f; 505 502 struct net_bridge_vlan *v; 503 + bool local_vlan_0; 504 + 505 + local_vlan_0 = br_opt_get(br, BROPT_FDB_LOCAL_VLAN_0); 506 506 507 507 spin_lock_bh(&br->hash_lock); 508 508 ··· 517 511 518 512 fdb_add_local(br, NULL, newaddr, 0); 519 513 vg = br_vlan_group(br); 520 - if (!vg || !vg->num_vlans) 514 + if (!vg || !vg->num_vlans || local_vlan_0) 521 515 goto out; 522 516 /* Now remove and add entries for every VLAN configured on the 523 517 * bridge. This function runs under RTNL so the bitmap will not ··· 580 574 /* Cleanup minimum 10 milliseconds apart */ 581 575 work_delay = max_t(unsigned long, work_delay, msecs_to_jiffies(10)); 582 576 mod_delayed_work(system_long_wq, &br->gc_work, work_delay); 577 + } 578 + 579 + static void br_fdb_delete_locals_per_vlan_port(struct net_bridge *br, 580 + struct net_bridge_port *p) 581 + { 582 + struct net_bridge_vlan_group *vg; 583 + struct net_bridge_vlan *v; 584 + struct net_device *dev; 585 + 586 + if (p) { 587 + vg = nbp_vlan_group(p); 588 + dev = p->dev; 589 + } else { 590 + vg = br_vlan_group(br); 591 + dev = br->dev; 592 + } 593 + 594 + list_for_each_entry(v, &vg->vlan_list, vlist) 595 + br_fdb_find_delete_local(br, p, dev->dev_addr, v->vid); 596 + } 597 + 598 + static void br_fdb_delete_locals_per_vlan(struct net_bridge *br) 599 + { 600 + struct net_bridge_port *p; 601 + 602 + ASSERT_RTNL(); 603 + 604 + list_for_each_entry(p, &br->port_list, list) 605 + br_fdb_delete_locals_per_vlan_port(br, p); 606 + 607 + br_fdb_delete_locals_per_vlan_port(br, NULL); 608 + } 609 + 610 + static int br_fdb_insert_locals_per_vlan_port(struct net_bridge *br, 611 + struct net_bridge_port *p, 612 + struct netlink_ext_ack *extack) 613 + { 614 + struct net_bridge_vlan_group *vg; 615 + struct net_bridge_vlan *v; 616 + struct net_device *dev; 617 + int err; 618 + 619 + if (p) { 620 + vg = nbp_vlan_group(p); 621 + dev = p->dev; 622 + } else { 623 + vg = br_vlan_group(br); 624 + dev = br->dev; 625 + } 626 + 627 + list_for_each_entry(v, &vg->vlan_list, vlist) { 628 + if (!br_vlan_should_use(v)) 629 + continue; 630 + 631 + err = br_fdb_add_local(br, p, dev->dev_addr, v->vid); 632 + if (err) 633 + return err; 634 + } 635 + 636 + return 0; 637 + } 638 + 639 + static int br_fdb_insert_locals_per_vlan(struct net_bridge *br, 640 + struct netlink_ext_ack *extack) 641 + { 642 + struct net_bridge_port *p; 643 + int err; 644 + 645 + ASSERT_RTNL(); 646 + 647 + list_for_each_entry(p, &br->port_list, list) { 648 + err = br_fdb_insert_locals_per_vlan_port(br, p, extack); 649 + if (err) 650 + goto rollback; 651 + } 652 + 653 + err = br_fdb_insert_locals_per_vlan_port(br, NULL, extack); 654 + if (err) 655 + goto rollback; 656 + 657 + return 0; 658 + 659 + rollback: 660 + NL_SET_ERR_MSG_MOD(extack, "fdb_local_vlan_0 toggle: FDB entry insertion failed"); 661 + br_fdb_delete_locals_per_vlan(br); 662 + return err; 663 + } 664 + 665 + int br_fdb_toggle_local_vlan_0(struct net_bridge *br, bool on, 666 + struct netlink_ext_ack *extack) 667 + { 668 + if (!on) 669 + return br_fdb_insert_locals_per_vlan(br, extack); 670 + 671 + br_fdb_delete_locals_per_vlan(br); 672 + return 0; 583 673 } 584 674 585 675 static bool __fdb_flush_matches(const struct net_bridge *br,
+8
net/bridge/br_input.c
··· 202 202 break; 203 203 case BR_PKT_UNICAST: 204 204 dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid); 205 + if (unlikely(!dst && vid && 206 + br_opt_get(br, BROPT_FDB_LOCAL_VLAN_0))) { 207 + dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, 0); 208 + if (dst && 209 + (!test_bit(BR_FDB_LOCAL, &dst->flags) || 210 + test_bit(BR_FDB_ADDED_BY_USER, &dst->flags))) 211 + dst = NULL; 212 + } 205 213 break; 206 214 default: 207 215 break;
+3
net/bridge/br_private.h
··· 487 487 BROPT_MCAST_VLAN_SNOOPING_ENABLED, 488 488 BROPT_MST_ENABLED, 489 489 BROPT_MDB_OFFLOAD_FAIL_NOTIFICATION, 490 + BROPT_FDB_LOCAL_VLAN_0, 490 491 }; 491 492 492 493 struct net_bridge { ··· 844 843 void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr); 845 844 void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr); 846 845 void br_fdb_cleanup(struct work_struct *work); 846 + int br_fdb_toggle_local_vlan_0(struct net_bridge *br, bool on, 847 + struct netlink_ext_ack *extack); 847 848 void br_fdb_delete_by_port(struct net_bridge *br, 848 849 const struct net_bridge_port *p, u16 vid, int do_all); 849 850 struct net_bridge_fdb_entry *br_fdb_find_rcu(struct net_bridge *br,
+6 -4
net/bridge/br_vlan.c
··· 331 331 332 332 /* Add the dev mac and count the vlan only if it's usable */ 333 333 if (br_vlan_should_use(v)) { 334 - err = br_fdb_add_local(br, p, dev->dev_addr, v->vid); 335 - if (err) { 336 - br_err(br, "failed insert local address into bridge forwarding table\n"); 337 - goto out_filt; 334 + if (!br_opt_get(br, BROPT_FDB_LOCAL_VLAN_0)) { 335 + err = br_fdb_add_local(br, p, dev->dev_addr, v->vid); 336 + if (err) { 337 + br_err(br, "failed insert local address into bridge forwarding table\n"); 338 + goto out_filt; 339 + } 338 340 } 339 341 vg->num_vlans++; 340 342 }
+1
tools/testing/selftests/net/forwarding/Makefile
··· 5 5 bridge_fdb_learning_limit.sh \ 6 6 bridge_igmp.sh \ 7 7 bridge_locked_port.sh \ 8 + bridge_fdb_local_vlan_0.sh \ 8 9 bridge_mdb.sh \ 9 10 bridge_mdb_host.sh \ 10 11 bridge_mdb_max.sh \
+374
tools/testing/selftests/net/forwarding/bridge_fdb_local_vlan_0.sh
··· 1 + #!/bin/bash 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + # +-----------------------+ +-----------------------+ +-----------------------+ 5 + # | H1 (vrf) | | H2 (vrf) | | H3 (vrf) | 6 + # | + $h1 | | + $h2 | | + $h3 | 7 + # | | 192.0.2.1/28 | | | 192.0.2.2/28 | | | 192.0.2.18/28 | 8 + # | | 2001:db8:1::1/64 | | | 2001:db8:1::2/64 | | | 2001:db8:2::2/64 | 9 + # | | | | | | | | | 10 + # +----|------------------+ +----|------------------+ +----|------------------+ 11 + # | | | 12 + # +----|-------------------------|-------------------------|------------------+ 13 + # | +--|-------------------------|------------------+ | | 14 + # | | + $swp1 + $swp2 | + $swp3 | 15 + # | | | 192.0.2.17/28 | 16 + # | | BR1 (802.1q) | 2001:db8:2::1/64 | 17 + # | | 192.0.2.3/28 | | 18 + # | | 2001:db8:1::3/64 | | 19 + # | +-----------------------------------------------+ SW | 20 + # +---------------------------------------------------------------------------+ 21 + # 22 + #shellcheck disable=SC2317 # SC doesn't see our uses of functions. 23 + #shellcheck disable=SC2034 # ... and global variables 24 + 25 + ALL_TESTS=" 26 + test_d_no_sharing 27 + test_d_sharing 28 + test_q_no_sharing 29 + test_q_sharing 30 + " 31 + 32 + NUM_NETIFS=6 33 + source lib.sh 34 + 35 + pMAC=00:11:22:33:44:55 36 + bMAC=00:11:22:33:44:66 37 + mMAC=00:11:22:33:44:77 38 + xMAC=00:11:22:33:44:88 39 + 40 + host_create() 41 + { 42 + local h=$1; shift 43 + local ipv4=$1; shift 44 + local ipv6=$1; shift 45 + 46 + simple_if_init "$h" "$ipv4" "$ipv6" 47 + defer simple_if_fini "$h" "$ipv4" "$ipv6" 48 + 49 + ip_route_add vrf "v$h" 192.0.2.16/28 nexthop via 192.0.2.3 50 + ip_route_add vrf "v$h" 2001:db8:2::/64 nexthop via 2001:db8:1::3 51 + } 52 + 53 + h3_create() 54 + { 55 + simple_if_init "$h3" 192.0.2.18/28 2001:db8:2::2/64 56 + defer simple_if_fini "$h3" 192.0.2.18/28 2001:db8:2::2/64 57 + 58 + ip_route_add vrf "v$h3" 192.0.2.0/28 nexthop via 192.0.2.17 59 + ip_route_add vrf "v$h3" 2001:db8:1::/64 nexthop via 2001:db8:2::1 60 + 61 + tc qdisc add dev "$h3" clsact 62 + defer tc qdisc del dev "$h3" clsact 63 + 64 + tc filter add dev "$h3" ingress proto ip pref 104 \ 65 + flower skip_hw ip_proto udp dst_port 4096 \ 66 + action pass 67 + defer tc filter del dev "$h3" ingress proto ip pref 104 68 + 69 + tc qdisc add dev "$h2" clsact 70 + defer tc qdisc del dev "$h2" clsact 71 + 72 + tc filter add dev "$h2" ingress proto ip pref 104 \ 73 + flower skip_hw ip_proto udp dst_port 4096 \ 74 + action pass 75 + defer tc filter del dev "$h2" ingress proto ip pref 104 76 + } 77 + 78 + switch_create() 79 + { 80 + ip_link_set_up "$swp1" 81 + 82 + ip_link_set_up "$swp2" 83 + 84 + ip_addr_add "$swp3" 192.0.2.17/28 85 + ip_addr_add "$swp3" 2001:db8:2::1/64 86 + ip_link_set_up "$swp3" 87 + } 88 + 89 + setup_prepare() 90 + { 91 + h1=${NETIFS[p1]} 92 + swp1=${NETIFS[p2]} 93 + 94 + swp2=${NETIFS[p3]} 95 + h2=${NETIFS[p4]} 96 + 97 + swp3=${NETIFS[p5]} 98 + h3=${NETIFS[p6]} 99 + 100 + vrf_prepare 101 + defer vrf_cleanup 102 + 103 + forwarding_enable 104 + defer forwarding_restore 105 + 106 + host_create "$h1" 192.0.2.1/28 2001:db8:1::1/64 107 + host_create "$h2" 192.0.2.2/28 2001:db8:1::2/64 108 + h3_create 109 + 110 + switch_create 111 + } 112 + 113 + adf_bridge_create() 114 + { 115 + local dev 116 + local mac 117 + 118 + ip_link_add br up type bridge vlan_default_pvid 0 "$@" 119 + mac=$(mac_get br) 120 + ip_addr_add br 192.0.2.3/28 121 + ip_addr_add br 2001:db8:1::3/64 122 + 123 + bridge_vlan_add dev br vid 1 pvid untagged self 124 + bridge_vlan_add dev br vid 2 self 125 + bridge_vlan_add dev br vid 3 self 126 + 127 + for dev in "$swp1" "$swp2"; do 128 + ip_link_set_master "$dev" br 129 + bridge_vlan_add dev "$dev" vid 1 pvid untagged 130 + bridge_vlan_add dev "$dev" vid 2 131 + bridge_vlan_add dev "$dev" vid 3 132 + done 133 + 134 + ip_link_set_addr br "$mac" 135 + } 136 + 137 + check_fdb_local_vlan_0_support() 138 + { 139 + if ip_link_add XXbr up type bridge vlan_filtering 1 fdb_local_vlan_0 1 \ 140 + &>/dev/null; then 141 + return 0 142 + fi 143 + 144 + log_test_skip "FDB sharing" \ 145 + "iproute 2 or the kernel do not support fdb_local_vlan_0" 146 + } 147 + 148 + check_mac_presence() 149 + { 150 + local should_fail=$1; shift 151 + local dev=$1; shift 152 + local vlan=$1; shift 153 + local mac 154 + 155 + mac=$(mac_get "$dev") 156 + 157 + if ((vlan == 0)); then 158 + vlan=null 159 + fi 160 + 161 + bridge -j fdb show dev "$dev" | 162 + jq -e --arg mac "$mac" --argjson vlan "$vlan" \ 163 + '.[] | select(.mac == $mac) | select(.vlan == $vlan)' > /dev/null 164 + check_err_fail "$should_fail" $? "FDB dev $dev vid $vlan addr $mac exists" 165 + } 166 + 167 + do_sharing_test() 168 + { 169 + local should_fail=$1; shift 170 + local what=$1; shift 171 + local dev 172 + 173 + RET=0 174 + 175 + for dev in "$swp1" "$swp2" br; do 176 + check_mac_presence 0 "$dev" 0 177 + check_mac_presence "$should_fail" "$dev" 1 178 + check_mac_presence "$should_fail" "$dev" 2 179 + check_mac_presence "$should_fail" "$dev" 3 180 + done 181 + 182 + log_test "$what" 183 + } 184 + 185 + do_end_to_end_test() 186 + { 187 + local mac=$1; shift 188 + local what=$1; shift 189 + local probe_dev=${1-$h3}; shift 190 + local expect=${1-10}; shift 191 + 192 + local t0 193 + local t1 194 + local dd 195 + 196 + RET=0 197 + 198 + # In mausezahn, use $dev MAC as the destination MAC. In the MAC sharing 199 + # context, that will cause an FDB miss on VLAN 1 and prompt a second 200 + # lookup in VLAN 0. 201 + 202 + t0=$(tc_rule_stats_get "$probe_dev" 104 ingress) 203 + 204 + $MZ "$h1" -c 10 -p 64 -a own -b "$mac" \ 205 + -A 192.0.2.1 -B 192.0.2.18 -t udp "dp=4096,sp=2048" -q 206 + sleep 1 207 + 208 + t1=$(tc_rule_stats_get "$probe_dev" 104 ingress) 209 + dd=$((t1 - t0)) 210 + 211 + ((dd == expect)) 212 + check_err $? "Expected $expect packets on $probe_dev got $dd" 213 + 214 + log_test "$what" 215 + } 216 + 217 + do_tests() 218 + { 219 + local should_fail=$1; shift 220 + local what=$1; shift 221 + local swp1_mac 222 + local br_mac 223 + 224 + swp1_mac=$(mac_get "$swp1") 225 + br_mac=$(mac_get br) 226 + 227 + do_sharing_test "$should_fail" "$what" 228 + do_end_to_end_test "$swp1_mac" "$what: end to end, $swp1 MAC" 229 + do_end_to_end_test "$br_mac" "$what: end to end, br MAC" 230 + } 231 + 232 + bridge_standard() 233 + { 234 + local vlan_filtering=$1; shift 235 + 236 + if ((vlan_filtering)); then 237 + echo 802.1q 238 + else 239 + echo 802.1d 240 + fi 241 + } 242 + 243 + nonexistent_fdb_test() 244 + { 245 + local vlan_filtering=$1; shift 246 + local standard 247 + 248 + standard=$(bridge_standard "$vlan_filtering") 249 + 250 + # We expect flooding, so $h2 should get the traffic. 251 + do_end_to_end_test "$xMAC" "$standard: Nonexistent FDB" "$h2" 252 + } 253 + 254 + misleading_fdb_test() 255 + { 256 + local vlan_filtering=$1; shift 257 + local standard 258 + 259 + standard=$(bridge_standard "$vlan_filtering") 260 + 261 + defer_scope_push 262 + # Add an FDB entry on VLAN 0. The lookup on VLAN-aware bridge 263 + # shouldn't pick this up even with fdb_local_vlan_0 enabled, so 264 + # the traffic should be flooded. This all holds on 265 + # vlan_filtering bridge, on non-vlan_filtering one the FDB entry 266 + # is expected to be found as usual, no flooding takes place. 267 + # 268 + # Adding only on VLAN 0 is a bit tricky, because bridge is 269 + # trying to be nice and interprets the request as if the FDB 270 + # should be added on each VLAN. 271 + 272 + bridge fdb add "$mMAC" dev "$swp1" master 273 + bridge fdb del "$mMAC" dev "$swp1" vlan 1 master 274 + bridge fdb del "$mMAC" dev "$swp1" vlan 2 master 275 + bridge fdb del "$mMAC" dev "$swp1" vlan 3 master 276 + 277 + local expect=$((vlan_filtering ? 10 : 0)) 278 + do_end_to_end_test "$mMAC" \ 279 + "$standard: Lookup of non-local MAC on VLAN 0" \ 280 + "$h2" "$expect" 281 + defer_scope_pop 282 + } 283 + 284 + change_mac() 285 + { 286 + local dev=$1; shift 287 + local mac=$1; shift 288 + local cur_mac 289 + 290 + cur_mac=$(mac_get "$dev") 291 + 292 + log_info "Change $dev MAC $cur_mac -> $mac" 293 + ip_link_set_addr "$dev" "$mac" 294 + defer log_info "Change $dev MAC back" 295 + } 296 + 297 + do_test_no_sharing() 298 + { 299 + local vlan_filtering=$1; shift 300 + local standard 301 + 302 + standard=$(bridge_standard "$vlan_filtering") 303 + 304 + adf_bridge_create vlan_filtering "$vlan_filtering" 305 + setup_wait 306 + 307 + do_tests 0 "$standard, no FDB sharing" 308 + 309 + change_mac "$swp1" "$pMAC" 310 + change_mac br "$bMAC" 311 + 312 + do_tests 0 "$standard, no FDB sharing after MAC change" 313 + 314 + in_defer_scope check_fdb_local_vlan_0_support || return 315 + 316 + log_info "Set fdb_local_vlan_0=1" 317 + ip link set dev br type bridge fdb_local_vlan_0 1 318 + 319 + do_tests 1 "$standard, fdb sharing after toggle" 320 + } 321 + 322 + do_test_sharing() 323 + { 324 + local vlan_filtering=$1; shift 325 + local standard 326 + 327 + standard=$(bridge_standard "$vlan_filtering") 328 + 329 + in_defer_scope check_fdb_local_vlan_0_support || return 330 + 331 + adf_bridge_create vlan_filtering "$vlan_filtering" fdb_local_vlan_0 1 332 + setup_wait 333 + 334 + do_tests 1 "$standard, FDB sharing" 335 + 336 + nonexistent_fdb_test "$vlan_filtering" 337 + misleading_fdb_test "$vlan_filtering" 338 + 339 + change_mac "$swp1" "$pMAC" 340 + change_mac br "$bMAC" 341 + 342 + do_tests 1 "$standard, FDB sharing after MAC change" 343 + 344 + log_info "Set fdb_local_vlan_0=0" 345 + ip link set dev br type bridge fdb_local_vlan_0 0 346 + 347 + do_tests 0 "$standard, No FDB sharing after toggle" 348 + } 349 + 350 + test_d_no_sharing() 351 + { 352 + do_test_no_sharing 0 353 + } 354 + 355 + test_d_sharing() 356 + { 357 + do_test_sharing 0 358 + } 359 + 360 + test_q_no_sharing() 361 + { 362 + do_test_no_sharing 1 363 + } 364 + 365 + test_q_sharing() 366 + { 367 + do_test_sharing 1 368 + } 369 + 370 + 371 + trap cleanup EXIT 372 + 373 + setup_prepare 374 + tests_run
+16 -16
tools/testing/selftests/net/lib.sh
··· 547 547 { 548 548 local name=$1; shift 549 549 550 - ip link add name "$name" "$@" 551 - defer ip link del dev "$name" 550 + ip link add name "$name" "$@" && \ 551 + defer ip link del dev "$name" 552 552 } 553 553 554 554 ip_link_set_master() ··· 556 556 local member=$1; shift 557 557 local master=$1; shift 558 558 559 - ip link set dev "$member" master "$master" 560 - defer ip link set dev "$member" nomaster 559 + ip link set dev "$member" master "$master" && \ 560 + defer ip link set dev "$member" nomaster 561 561 } 562 562 563 563 ip_link_set_addr() ··· 566 566 local addr=$1; shift 567 567 568 568 local old_addr=$(mac_get "$name") 569 - ip link set dev "$name" address "$addr" 570 - defer ip link set dev "$name" address "$old_addr" 569 + ip link set dev "$name" address "$addr" && \ 570 + defer ip link set dev "$name" address "$old_addr" 571 571 } 572 572 573 573 ip_link_has_flag() ··· 590 590 local name=$1; shift 591 591 592 592 if ! ip_link_is_up "$name"; then 593 - ip link set dev "$name" up 594 - defer ip link set dev "$name" down 593 + ip link set dev "$name" up && \ 594 + defer ip link set dev "$name" down 595 595 fi 596 596 } 597 597 ··· 600 600 local name=$1; shift 601 601 602 602 if ip_link_is_up "$name"; then 603 - ip link set dev "$name" down 604 - defer ip link set dev "$name" up 603 + ip link set dev "$name" down && \ 604 + defer ip link set dev "$name" up 605 605 fi 606 606 } 607 607 ··· 609 609 { 610 610 local name=$1; shift 611 611 612 - ip addr add dev "$name" "$@" 613 - defer ip addr del dev "$name" "$@" 612 + ip addr add dev "$name" "$@" && \ 613 + defer ip addr del dev "$name" "$@" 614 614 } 615 615 616 616 ip_route_add() 617 617 { 618 - ip route add "$@" 619 - defer ip route del "$@" 618 + ip route add "$@" && \ 619 + defer ip route del "$@" 620 620 } 621 621 622 622 bridge_vlan_add() 623 623 { 624 - bridge vlan add "$@" 625 - defer bridge vlan del "$@" 624 + bridge vlan add "$@" && \ 625 + defer bridge vlan del "$@" 626 626 } 627 627 628 628 wait_local_port_listen()
+18 -2
tools/testing/selftests/net/lib/sh/defer.sh
··· 1 1 #!/bin/bash 2 2 # SPDX-License-Identifier: GPL-2.0 3 3 4 + # Whether to pause and allow debugging when an executed deferred command has a 5 + # non-zero exit code. 6 + : "${DEFER_PAUSE_ON_FAIL:=no}" 7 + 4 8 # map[(scope_id,track,cleanup_id) -> cleanup_command] 5 9 # track={d=default | p=priority} 6 10 declare -A __DEFER__JOBS ··· 42 38 local track=$1; shift 43 39 local defer_ix=$1; shift 44 40 local defer_key=$(__defer__defer_key $track $defer_ix) 41 + local ret 45 42 46 - ${__DEFER__JOBS[$defer_key]} 43 + eval ${__DEFER__JOBS[$defer_key]} 44 + ret=$? 45 + 46 + if [[ "$DEFER_PAUSE_ON_FAIL" == yes && "$ret" -ne 0 ]]; then 47 + echo "Deferred command (track $track index $defer_ix):" 48 + echo " ${__DEFER__JOBS[$defer_key]}" 49 + echo "... ended with an exit status of $ret" 50 + echo "Hit enter to continue, 'q' to quit" 51 + read a 52 + [[ "$a" == q ]] && exit 1 53 + fi 54 + 47 55 unset __DEFER__JOBS[$defer_key] 48 56 } 49 57 ··· 65 49 local ndefers=$(__defer__ndefers $track) 66 50 local ndefers_key=$(__defer__ndefer_key $track) 67 51 local defer_key=$(__defer__defer_key $track $ndefers) 68 - local defer="$@" 52 + local defer="${@@Q}" 69 53 70 54 __DEFER__JOBS[$defer_key]="$defer" 71 55 __DEFER__NJOBS[$ndefers_key]=$((ndefers + 1))