Skip to content

Commit

Permalink
Add additional bridge port settings (#410)
Browse files Browse the repository at this point in the history
* Implement additional bridge (port) parameters

hairpin and port-mac-learning for bridge ports

* Move hairpin & port-mac-learning to end of abi struct

* Being precise about settings only applying for bridge ports

* [PATCH] tests: add a couple of tests for the hairpin and port-mac-learning options

* parse:doc: Small schema naming & documentation fixes

---------

Co-authored-by: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
Co-authored-by: Lukas Märdian <slyon@ubuntu.com>
  • Loading branch information
3 people authored Feb 26, 2024
1 parent 2274176 commit c44f6e7
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 3 deletions.
15 changes: 14 additions & 1 deletion doc/netplan-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,20 @@ Match devices by MAC when setting options like: `wakeonlan` or `*-offload`.
- **`neigh-suppress`** (scalar) – since 0.105

> Takes a boolean. Configures whether ARP and ND neighbour suppression is
> enabled for this port. When unset, the kernel's default will be used.
> enabled for this bridge port. When unset, the kernel's default will be used.

- **`hairpin`** (scalar) – since **1.0**

> Takes a boolean. Configures whether traffic may be sent back out of the
> bridge port on which it was received. When this flag is false, then the
> bridge will not forward traffic back out of the receiving port. When
> unset, the backend's default will be used.

- **`port-mac-learning`** (scalar) – since **1.0**

> Takes a boolean. Configures whether MAC address learning is enabled for
> this bridge port. When unset, the kernel's default will be used.
> Currently supported on the `networkd` backend only.

## DHCP Overrides
Several DHCP behaviour overrides are available. Most currently only have any
Expand Down
3 changes: 3 additions & 0 deletions src/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -411,4 +411,7 @@ struct netplan_net_definition {
/* virtual-ethernet */
/* netplan-feature: virtual-ethernet */
NetplanNetDefinition* veth_peer_link;

NetplanTristate bridge_hairpin;
NetplanTristate bridge_learning;
};
2 changes: 2 additions & 0 deletions src/netplan.c
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,8 @@ _serialize_yaml(
}

write_routes(event, emitter, def);
YAML_BOOL_TRISTATE(def, event, emitter, "hairpin", def->bridge_hairpin);
YAML_BOOL_TRISTATE(def, event, emitter, "port-mac-learning", def->bridge_learning);
YAML_BOOL_TRISTATE(def, event, emitter, "neigh-suppress", def->bridge_neigh_suppress);

/* VLAN settings */
Expand Down
9 changes: 7 additions & 2 deletions src/networkd.c
Original file line number Diff line number Diff line change
Expand Up @@ -861,15 +861,20 @@ _netplan_netdef_write_network_file(

if ( def->bridge_params.path_cost
|| def->bridge_params.port_priority
|| def->bridge_hairpin != NETPLAN_TRISTATE_UNSET
|| def->bridge_learning != NETPLAN_TRISTATE_UNSET
|| def->bridge_neigh_suppress != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(network, "\n[Bridge]\n");
if (def->bridge_params.path_cost)
g_string_append_printf(network, "Cost=%u\n", def->bridge_params.path_cost);
if (def->bridge_params.port_priority)
g_string_append_printf(network, "Priority=%u\n", def->bridge_params.port_priority);
if (def->bridge_neigh_suppress != NETPLAN_TRISTATE_UNSET) {
if (def->bridge_hairpin != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(network, "HairPin=%s\n", def->bridge_hairpin ? "true" : "false");
if (def->bridge_learning != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(network, "Learning=%s\n", def->bridge_learning ? "true" : "false");
if (def->bridge_neigh_suppress != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(network, "NeighborSuppression=%s\n", def->bridge_neigh_suppress ? "true" : "false");
}

}
if (def->bond && def->backend != NETPLAN_BACKEND_OVS) {
Expand Down
2 changes: 2 additions & 0 deletions src/nm.c
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,8 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir,
g_key_file_set_uint64(kf, "bridge-port", "path-cost", def->bridge_params.path_cost);
if (def->bridge_params.port_priority)
g_key_file_set_uint64(kf, "bridge-port", "priority", def->bridge_params.port_priority);
if (def->bridge_hairpin != NETPLAN_TRISTATE_UNSET)
g_key_file_set_boolean(kf, "bridge-port", "hairpin-mode", def->bridge_hairpin);
}
if (def->bond) {
g_key_file_set_string(kf, "connection", "slave-type", "bond"); /* wokeignore:rule=slave */
Expand Down
2 changes: 2 additions & 0 deletions src/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,8 @@ static const mapping_entry_handler dhcp6_overrides_handlers[] = {
{"renderer", YAML_SCALAR_NODE, {.generic=handle_netdef_renderer}, NULL}, \
{"routes", YAML_SEQUENCE_NODE, {.generic=handle_routes}, NULL}, \
{"routing-policy", YAML_SEQUENCE_NODE, {.generic=handle_ip_rules}, NULL}, \
{"hairpin", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(bridge_hairpin)}, \
{"port-mac-learning", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(bridge_learning)}, \
{"neigh-suppress", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(bridge_neigh_suppress)}

#define COMMON_BACKEND_HANDLERS \
Expand Down
2 changes: 2 additions & 0 deletions src/types.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ reset_netdef(NetplanNetDefinition* netdef, NetplanDefType new_type, NetplanBacke
FREE_AND_NULLIFY(netdef->modem_params.username);
memset(&netdef->modem_params, 0, sizeof(netdef->modem_params));

netdef->bridge_hairpin = NETPLAN_TRISTATE_UNSET;
netdef->bridge_learning = NETPLAN_TRISTATE_UNSET;
netdef->bridge_neigh_suppress = NETPLAN_TRISTATE_UNSET;
FREE_AND_NULLIFY(netdef->bridge_params.ageing_time);
FREE_AND_NULLIFY(netdef->bridge_params.forward_delay);
Expand Down
7 changes: 7 additions & 0 deletions tests/generator/test_tunnels.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,8 @@ def test_vxlan(self):
extensions: [group-policy, generic-protocol]
port-range: [42, 442]
neigh-suppress: false
hairpin: false
port-mac-learning: false
bridges:
br0:
interfaces: [vxlan1005]''' % {'r': self.backend})
Expand Down Expand Up @@ -631,6 +633,8 @@ def test_vxlan(self):
Bridge=br0
[Bridge]
HairPin=false
Learning=false
NeighborSuppression=false\n''',
'br0.network': '''[Match]
Name=br0
Expand All @@ -652,6 +656,9 @@ def test_vxlan(self):
slave-type=bridge # wokeignore:rule=slave
master=br0 # wokeignore:rule=master
[bridge-port]
hairpin-mode=false
[vxlan]
ageing=42
destination-port=4789
Expand Down
60 changes: 60 additions & 0 deletions tests/integration/bridges.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,36 @@ def test_bridge_port_priority(self):
with open('/sys/class/net/mybr/brif/%s/priority' % self.dev_e2_client) as f:
self.assertEqual(f.read().strip(), '42')

def test_bridge_port_hairpin(self):
self.setup_eth(None)
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL)
with open(self.config, 'w') as f:
f.write('''network:
renderer: %(r)s
ethernets:
ethbr0:
match: {name: %(ec)s}
hairpin: true
ethbr1:
match: {name: %(e2c)s}
hairpin: false
bridges:
mybr:
interfaces: [ethbr0, ethbr1]
dhcp4: false''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client})
self.generate_and_settle([self.dev_e_client, self.dev_e2_client])
self.assert_iface_up(self.dev_e_client, ['master mybr'], ['inet ']) # wokeignore:rule=master
self.assert_iface_up(self.dev_e2_client, ['master mybr'], ['inet ']) # wokeignore:rule=master
lines = subprocess.check_output(['bridge', 'link', 'show', 'mybr'],
text=True).splitlines()
self.assertEqual(len(lines), 2, lines)
self.assertIn(self.dev_e_client, lines[0])
self.assertIn(self.dev_e2_client, lines[1])
with open('/sys/devices/virtual/net/mybr/lower_%s/brport/hairpin_mode' % self.dev_e_client) as f:
self.assertEqual(f.read().strip(), '1')
with open('/sys/devices/virtual/net/mybr/lower_%s/brport/hairpin_mode' % self.dev_e2_client) as f:
self.assertEqual(f.read().strip(), '0')


@unittest.skipIf("networkd" not in test_backends,
"skipping as networkd backend tests are disabled")
Expand Down Expand Up @@ -309,6 +339,36 @@ def test_bridge_isolated(self):
self.generate_and_settle(['mybr'])
self.assert_iface('mybr', ['inet 10.10.10.10/24'])

def test_bridge_port_learning(self):
self.setup_eth(None)
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL)
with open(self.config, 'w') as f:
f.write('''network:
renderer: %(r)s
ethernets:
ethbr0:
match: {name: %(ec)s}
port-mac-learning: true
ethbr1:
match: {name: %(e2c)s}
port-mac-learning: false
bridges:
mybr:
interfaces: [ethbr0, ethbr1]
dhcp4: false''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client})
self.generate_and_settle([self.dev_e_client, self.dev_e2_client])
self.assert_iface_up(self.dev_e_client, ['master mybr'], ['inet ']) # wokeignore:rule=master
self.assert_iface_up(self.dev_e2_client, ['master mybr'], ['inet ']) # wokeignore:rule=master
lines = subprocess.check_output(['bridge', 'link', 'show', 'mybr'],
text=True).splitlines()
self.assertEqual(len(lines), 2, lines)
self.assertIn(self.dev_e_client, lines[0])
self.assertIn(self.dev_e2_client, lines[1])
with open('/sys/devices/virtual/net/mybr/lower_%s/brport/learning' % self.dev_e_client) as f:
self.assertEqual(f.read().strip(), '1')
with open('/sys/devices/virtual/net/mybr/lower_%s/brport/learning' % self.dev_e2_client) as f:
self.assertEqual(f.read().strip(), '0')


@unittest.skipIf("NetworkManager" not in test_backends,
"skipping as NetworkManager backend tests are disabled")
Expand Down

0 comments on commit c44f6e7

Please sign in to comment.