bridge: allow IP addresses on members to be disabled

add a new sysctl, net.link.bridge.member_ifaddrs, which defaults to 1.

if it is set to 1, bridge behaviour is unchanged.

if it is set to 0:

- an interface which has AF_INET6 or AF_INET addresses assigned cannot
  be added to a bridge.
- an interface in a bridge cannot have an AF_INET6 or AF_INET address
  assigned to it.
- the bridge will no longer consider the lladdrs on bridge members to be
  local addresses, i.e. frames sent to member lladdrs will not be
  processed by the host.

update bridge.4 to document this behaviour, as well as the existing
recommendation that IP addresses should not be configured on bridge
members anyway, even if it currently partially works.

in testing, setting this to 0 on a bridge with 50 member interfaces
improved throughput by 22% (4.61Gb/s -> 5.67Gb/s) across two member
epairs due to eliding the bridge member list walk in GRAB_OUR_PACKETS.

Reviewed by:	kp, des
Approved by:	des (mentor)
Differential Revision:	https://reviews.freebsd.org/D49995

(cherry picked from commit 0a1294f6c610948d7447ae276df74a6d5269b62e)
This commit is contained in:
Lexi Winter 2025-05-05 22:44:44 +01:00 committed by Franco Fichtner
parent b69907d463
commit cd5b92eb56
7 changed files with 162 additions and 10 deletions

View file

@ -36,7 +36,7 @@
.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd April 10, 2024
.Dd May 5, 2025
.Dt IF_BRIDGE 4
.Os
.Sh NAME
@ -158,6 +158,19 @@ This can be used to multiplex the input of two or more interfaces into a single
stream.
This is useful for reconstructing the traffic for network taps
that transmit the RX/TX signals out through two separate interfaces.
.Pp
To allow the host to communicate with bridge members, IP addresses
should be assigned to the
.Nm
interface itself, not to the bridge's member interfaces.
Assigning IP addresses to bridge member interfaces is unsupported, but
for backward compatibility, it is permitted if the
.Xr sysctl 8
variable
.Va net.link.bridge.member_ifaddrs
is set to 1, which is the default.
In a future release, this sysctl may be set to 0 by default, or may be
removed entirely.
.Sh IPV6 SUPPORT
.Nm
supports the

View file

@ -331,6 +331,7 @@ static void bridge_rtdelete(struct bridge_softc *, struct ifnet *ifp, int);
static void bridge_forward(struct bridge_softc *, struct bridge_iflist *,
struct mbuf *m);
static bool bridge_member_ifaddrs(void);
static void bridge_timer(void *);
@ -497,6 +498,19 @@ SYSCTL_BOOL(_net_link_bridge, OID_AUTO, log_mac_flap,
CTLFLAG_RW | CTLFLAG_VNET, &VNET_NAME(log_mac_flap), true,
"Log MAC address port flapping");
/* allow IP addresses on bridge members */
VNET_DEFINE_STATIC(bool, member_ifaddrs) = true;
#define V_member_ifaddrs VNET(member_ifaddrs)
SYSCTL_BOOL(_net_link_bridge, OID_AUTO, member_ifaddrs,
CTLFLAG_RW | CTLFLAG_VNET, &VNET_NAME(member_ifaddrs), true,
"Allow layer 3 addresses on bridge members");
static bool
bridge_member_ifaddrs(void)
{
return (V_member_ifaddrs);
}
VNET_DEFINE_STATIC(int, log_interval) = 5;
VNET_DEFINE_STATIC(int, log_count) = 0;
VNET_DEFINE_STATIC(struct timeval, log_last) = { 0 };
@ -658,6 +672,7 @@ bridge_modevent(module_t mod, int type, void *data)
switch (type) {
case MOD_LOAD:
bridge_dn_p = bridge_dummynet;
bridge_member_ifaddrs_p = bridge_member_ifaddrs;
bridge_detach_cookie = EVENTHANDLER_REGISTER(
ifnet_departure_event, bridge_ifdetach, NULL,
EVENTHANDLER_PRI_ANY);
@ -666,6 +681,7 @@ bridge_modevent(module_t mod, int type, void *data)
EVENTHANDLER_DEREGISTER(ifnet_departure_event,
bridge_detach_cookie);
bridge_dn_p = NULL;
bridge_member_ifaddrs_p = NULL;
break;
default:
return (EOPNOTSUPP);
@ -1273,6 +1289,25 @@ bridge_ioctl_add(struct bridge_softc *sc, void *arg)
return (EINVAL);
}
/*
* If member_ifaddrs is disabled, do not allow an interface with
* assigned IP addresses to be added to a bridge.
*/
if (!V_member_ifaddrs) {
struct ifaddr *ifa;
CK_STAILQ_FOREACH(ifa, &ifs->if_addrhead, ifa_link) {
#ifdef INET
if (ifa->ifa_addr->sa_family == AF_INET)
return (EINVAL);
#endif
#ifdef INET6
if (ifa->ifa_addr->sa_family == AF_INET6)
return (EINVAL);
#endif
}
}
#ifdef INET6
/*
* Two valid inet6 addresses with link-local scope must not be
@ -2699,17 +2734,25 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
do { GRAB_OUR_PACKETS(bifp) } while (0);
/*
* Give a chance for ifp at first priority. This will help when the
* packet comes through the interface like VLAN's with the same MACs
* on several interfaces from the same bridge. This also will save
* some CPU cycles in case the destination interface and the input
* interface (eq ifp) are the same.
* We only need to check members interfaces if member_ifaddrs is
* enabled; otherwise we should have never traffic destined for a
* member's lladdr.
*/
do { GRAB_OUR_PACKETS(ifp) } while (0);
/* Now check the all bridge members. */
CK_LIST_FOREACH(bif2, &sc->sc_iflist, bif_next) {
GRAB_OUR_PACKETS(bif2->bif_ifp)
if (V_member_ifaddrs) {
/*
* Give a chance for ifp at first priority. This will help when
* the packet comes through the interface like VLAN's with the
* same MACs on several interfaces from the same bridge. This
* also will save some CPU cycles in case the destination
* interface and the input interface (eq ifp) are the same.
*/
do { GRAB_OUR_PACKETS(ifp) } while (0);
/* Now check the all bridge members. */
CK_LIST_FOREACH(bif2, &sc->sc_iflist, bif_next) {
GRAB_OUR_PACKETS(bif2->bif_ifp)
}
}
#undef CARP_CHECK_WE_ARE_DST

View file

@ -320,5 +320,6 @@ struct ifbpstpconf {
} while (0)
extern void (*bridge_dn_p)(struct mbuf *, struct ifnet *);
extern bool (*bridge_member_ifaddrs_p)(void);
#endif /* _KERNEL */

View file

@ -112,6 +112,7 @@ void (*vlan_input_p)(struct ifnet *, struct mbuf *);
/* if_bridge(4) support */
void (*bridge_dn_p)(struct mbuf *, struct ifnet *);
bool (*bridge_member_ifaddrs_p)(void);
/* if_lagg(4) support */
struct mbuf *(*lagg_input_ethernet_p)(struct ifnet *, struct mbuf *);

View file

@ -59,6 +59,7 @@
#include <net/if_llatbl.h>
#include <net/if_private.h>
#include <net/if_types.h>
#include <net/if_bridgevar.h>
#include <net/route.h>
#include <net/route/nhop.h>
#include <net/route/route_ctl.h>
@ -499,6 +500,13 @@ in_aifaddr_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp, struct ucred *cred
return (error);
#endif
/*
* Check if bridge wants to allow adding addrs to member interfaces.
*/
if (ifp->if_bridge && bridge_member_ifaddrs_p &&
!bridge_member_ifaddrs_p())
return (EINVAL);
/*
* See whether address already exist.
*/

View file

@ -88,6 +88,7 @@
#include <net/if_var.h>
#include <net/if_private.h>
#include <net/if_types.h>
#include <net/if_bridgevar.h>
#include <net/route.h>
#include <net/route/route_ctl.h>
#include <net/route/nhop.h>
@ -1236,6 +1237,13 @@ in6_addifaddr(struct ifnet *ifp, struct in6_aliasreq *ifra, struct in6_ifaddr *i
int carp_attached = 0;
int error;
/* Check if this interface is a bridge member */
if (ifp->if_bridge && bridge_member_ifaddrs_p &&
!bridge_member_ifaddrs_p()) {
error = EINVAL;
goto out;
}
/*
* first, make or update the interface address structure,
* and link it to the list.

View file

@ -703,6 +703,82 @@ many_bridge_members_cleanup()
vnet_cleanup
}
atf_test_case "member_ifaddrs_enabled" "cleanup"
member_ifaddrs_enabled_head()
{
atf_set descr 'bridge with member_ifaddrs=1'
atf_set require.user root
}
member_ifaddrs_enabled_body()
{
vnet_init
vnet_init_bridge
ep=$(vnet_mkepair)
ifconfig ${ep}a inet 192.0.2.1/24 up
vnet_mkjail one ${ep}b
jexec one sysctl net.link.bridge.member_ifaddrs=1
jexec one ifconfig ${ep}b inet 192.0.2.2/24 up
jexec one ifconfig bridge0 create addm ${ep}b
atf_check -s exit:0 -o ignore ping -c3 -t1 192.0.2.2
}
member_ifaddrs_enabled_cleanup()
{
vnet_cleanup
}
atf_test_case "member_ifaddrs_disabled" "cleanup"
member_ifaddrs_disabled_head()
{
atf_set descr 'bridge with member_ifaddrs=0'
atf_set require.user root
}
member_ifaddrs_disabled_body()
{
vnet_init
vnet_init_bridge
vnet_mkjail one
jexec one sysctl net.link.bridge.member_ifaddrs=0
bridge=$(jexec one ifconfig bridge create)
# adding an interface with an IPv4 address
ep=$(jexec one ifconfig epair create)
jexec one ifconfig ${ep} 192.0.2.1/32
atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
# adding an interface with an IPv6 address
ep=$(jexec one ifconfig epair create)
jexec one ifconfig ${ep} inet6 2001:db8::1/128
atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
# adding an interface with an IPv6 link-local address
ep=$(jexec one ifconfig epair create)
jexec one ifconfig ${ep} inet6 -ifdisabled auto_linklocal up
atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
# adding an IPv4 address to a member
ep=$(jexec one ifconfig epair create)
jexec one ifconfig ${bridge} addm ${ep}
atf_check -s exit:1 -e ignore jexec one ifconfig ${ep} inet 192.0.2.2/32
# adding an IPv6 address to a member
ep=$(jexec one ifconfig epair create)
jexec one ifconfig ${bridge} addm ${ep}
atf_check -s exit:1 -e ignore jexec one ifconfig ${ep} inet6 2001:db8::1/128
}
member_ifaddrs_disabled_cleanup()
{
vnet_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "bridge_transmit_ipv4_unicast"
@ -718,4 +794,6 @@ atf_init_test_cases()
atf_add_test_case "mtu"
atf_add_test_case "vlan"
atf_add_test_case "many_bridge_members"
atf_add_test_case "member_ifaddrs_enabled"
atf_add_test_case "member_ifaddrs_disabled"
}