Merge pull request #111 from ifupdown-ng/feature/deprecate-brctl

vlan aware bridging
This commit is contained in:
Ariadne Conill 2020-10-22 13:42:00 -08:00 committed by GitHub
commit 19a5a671eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 471 additions and 36 deletions

View file

@ -138,6 +138,7 @@ install: all
${SCDOC} < $< > $@
MANPAGES_5 = \
doc/ifupdown-ng.conf.5 \
doc/interfaces.5 \
doc/interfaces-bond.5 \
doc/interfaces-batman.5 \

View file

@ -272,6 +272,12 @@ ifquery_main(int argc, char *argv[])
return EXIT_FAILURE;
}
if (!lif_compat_apply(&collection))
{
fprintf(stderr, "%s: failed to apply compatibility glue\n", argv0);
return EXIT_FAILURE;
}
/* --list --state is not allowed */
if (listing && (listing_stat || listing_running))
generic_usage(self_applet, EXIT_FAILURE);

View file

@ -17,6 +17,22 @@ allow_addon_scripts = 1
# templates. Valid values are 0 and 1, the default is 1.
allow_any_iface_as_template = 1
# compat_create_interfaces:
# Denotes where or not to create interfaces when compat_* settings are
# active and it would be necessary to create an interface to be fully
# compliant. This could happen when inheriting bridge VLAN settings to
# an interface within a bridges bridge-ports setting but no interface
# stanza is found. Valid values are 0 and 1, the default is 1.
compat_create_interfaces = 1
# compat_ifupdown2_bridge_ports_inherit_vlans:
# In ifupdown2 <bridge-vids> as well as the <bridge-pvid> set on a bridge
# interface will be inherited by all member ports if not set explicitly.
# When set to 1 ifupdown-ng behaves the same way and will internally copy
# both options from the bridge member ports if they are not set on the
# member port. Valid values are 0 and 1, the default is 1.
compat_ifupdown2_bridge_ports_inherit_vlans = 1
# implicit_template_conversion:
# In some legacy configs, a template may be declared as an iface, and
# ifupdown-ng automatically converts those declarations to a proper

View file

@ -60,6 +60,20 @@ Currently the following settings are supported in
in order to require inheritance from specified templates.
Valid values are `0` and `1`, the default is `1`.
* `compat_create_interfaces`:
Denotes where or not to create interfaces when compat\_* settings are
active and it would be necessary to create an interface to be fully
compliant. This could happen when inheriting bridge VLAN settings to
an interface within a bridges bridge-ports setting but no interface
stanza is found. Valid values are `0` and `1`, the default is `1`.
* `compat_ifupdown2_bridge_ports_inherit_vlans`: In ifupdown2 `bridge-vids`
as well as the <bridge-pvid> set on a bridge interface will be inherited
by all member ports if not set explicitly. When set to `1` ifupdown-ng
behaves the same way and will internally copy both options from the
bridge member ports if they are not set on the member port.
Valid values are `0` and `1`, the default is `1`.
* `implicit_template_conversion`: In some legacy configs, a template
may be declared as an iface, and ifupdown-ng automatically converts
those declarations to a proper template. If this setting is

View file

@ -54,8 +54,9 @@ configured in the configuration database.
# SEE ALSO
*ifup*(8)++
*ifquery*(8)++
*ifupdown-ng.conf*(5)
*ifup*(8)
*ifquery*(8)
*interfaces*(5)
# AUTHORS

View file

@ -57,8 +57,9 @@ configured in the configuration database.
# SEE ALSO
*ifdown*(8)++
*ifquery*(8)++
*ifupdown-ng.conf*(5)
*ifdown*(8)
*ifquery*(8)
*interfaces*(5)
# AUTHORS

70
doc/ifupdown-ng.conf.scd Normal file
View file

@ -0,0 +1,70 @@
ifupdown-ng.conf(5)
# NAME
*ifupdown-ng.conf* - Global configuration file for ifupdown-ng
# DESCRIPTION
ifupdown-ng allows to configure some parts of it's behaviour via global
configuration options.
# GENERAL CONFIGURATION OPTIONS
*allow_addon_scripts* _bool_
Enable support for /etc/if-X.d addon scripts. These are used for
compatibility with legacy setups, and may be disabled for
performance improvements in setups where only ifupdown-ng executors
are used. Valid values are _0_ and _1_, the default is _1_.
*use_hostname_for_dhcp* _bool_
Automatically learn the hostname property, used for DHCP
configuration by querying the system hostname using uname(2).
This is basically equivalent to `hostname $(hostname)` without
having to specify any configuration. Valid values are _0_ and
_1_, the default is _1_.
# TEMPLATE RELATED OPTIONS
*allow_any_iface_as_template* _bool_
Enable any interface to act as a template for another interface.
This is presently the default, but is deprecated. An admin may
choose to disable this setting in order to require inheritance
from specified templates. Valid values are _0_ and _1_, the
default is _1_.
*implicit_template_conversion* _bool_
In some legacy configs, a template may be declared as an iface, and
ifupdown-ng automatically converts those declarations to a proper
template. If this setting is disabled, inheritance will continue
to work against non-template interfaces without converting them to
a template. Valid values are _0_ and _1_, the default is _1_.
# COMPATIBILITY RELATED OPTIONS
*compat_create_interfaces* _bool_
Denotes where or not to create interfaces when compat_* settings are
active and it would be necessary to create an interface to be fully
compliant. This could happen when inheriting bridge VLAN settings to
an interface within a bridges bridge-ports setting but no interface
stanza is found. Valid values are _0_ and _1_, the default is _1_.
compat_ifupdown2_bridge_ports_inherit_vlans _bool_
In ifupdown2 <bridge-vids> as well as the <bridge-pvid> set on a
bridge interface will be inherited by all member ports if not set
explicitly. When set to 1 ifupdown-ng behaves the same way and will
internally copy both options from the bridge member ports if they
are not set on the member port. Valid values are _0_ and _1_, the
default is _1_.
# FILES
/etc/network/ifupdown-ng.conf
# SEE ALSO
*interfaces*(5)
# AUTHORS
Maximilian Wilhelm <max@sdn.clinic>

View file

@ -27,6 +27,11 @@ See *ip-link*(8) for more details about the options listed below.
removed from the Forwarding DataBase (FDB) after not having
seen a frame with this source address.
*bridge-vlan-aware* _bool_
Denotes wether or not the bridge should be aware of 802.1q VLANs.
_bool_ can be given as _yes_/_no_ or _0_/_1_. The defaul is _no_.
See related options for configuring vlan-aware bridges, below.
# SPANNING TREE RELATED BRIDGE OPTIONS
*bridge-stp* _state_
@ -52,6 +57,51 @@ See *ip-link*(8) for more details about the options listed below.
after reception of its last STP hello message. Valid values
are between 6 and 40.
# OPTIONS FOR VLAN-AWARE-BRIDGES
The following options only have an effect on vlan-aware bridges and
their ports.
All settings can be applied on the bridge interface itself and all member
port iface stanzas. If applied on the bridge interface they take effect
for the bridge interface itself and might be inherited to _bridge-ports_
depending on the compatibility settings configured in *ifupdown-ng.conf*(5).
Configuring VLAN options on the bridge interface might be required for
setting up a VLAN interface to one of the VLANs carried within the bridge.
See the EXAMPLES section for an example for this scenario.
See *ifupdown-ng.conf*(5) for more information about compatiblity settings
mentioned below.
*bridge-access* _vlan ID_
Configure the given _vlan ID_ for untagged ingress and egress
on this interface. The common description for this kind of
configuration is called "access port".
*bridge-pvid* _vlan ID_
Denotes the _vlan ID_ to considered a PVID at ingress.
Any untagged frames received on this interface will be
assigned to this _vlan ID_. The default PVID is _1_.
If compatibility to ifupdown2 bridge port inheritance is active
a _bridge-pvid_ set on the bridge will be inherited to any
interface configured in _bridge-ports_ without a _bridge-pvid_ set.
*bridge-vids* _list of vlan IDs_
Denotes the space separated list of VLANs to be allowed tagged
ingress/egress on this interface.
If compatibility to ifupdown2 bridge port inheritance is active
a _bridge-vids_ set on the bridge will be inherited to any
interface configured in _bridge-ports_ without _bridge-vids_ set.
*bridge-allow-untagged* _bool_
Denotes wether or not the bridge should allow untagged frames on
ingress as well as egress. If set to _no_ untagged frames will be
droppped on ingress and none will be sent. _bool_ can be given as
_yes_/_no_ or _0_/_1_. The defaul is _yes_.
# EXAMPLES
A simple layer 2 only bridge:
@ -77,10 +127,46 @@ iface br0
address 2001:db8::42/64
```
A layer 2 only vlan-aware bridge:
```
auto bond0
iface bond0
bond-members eth0 eth1
bridge-vids 23 42 84 1337
auto br0
iface br0
bridge-ports bond0
```
A vlan-aware bridge with a VLAN interface on top:
```
auto eth0
iface eth0
bridge-vids 23 42 84 1337
auto br0
iface br0
bridge-ports eth0
bridge-vlan-aware yes
bridge-vids 42
auto vlan42
iface vlan42
vlan-raw-device br0
#
address 192.0.2.42/24
address 2001:db8::42/64
```
# SEE ALSO
*ip-link*(8)
*interfaces*(5)
*ifupdown-ng.conf*(5)
*ip-link*(8)
*bridge*(8)
# AUTHORS

View file

@ -216,6 +216,7 @@ iface eth0
# SEE ALSO
*ifupdown-ng.conf*(5)
*ifup*(8)
*ifdown*(8)
*ifquery*(8)

View file

@ -1,6 +1,7 @@
#!/bin/sh
set -e
[ -n "$VERBOSE" ] && set -x
# Copyright (C) 2012, 2020 Natanael Copa <ncopa@alpinelinux.org>
# Copyright (C) 2020 Ariadne Conill <ariadne@dereferenced.org>
@ -14,6 +15,10 @@ set -e
# implied. In no event shall the authors be liable for any damages arising
# from the use of this software.
################################################################################
# Bridge management functions #
################################################################################
all_ports_exist() {
local i=
for i in "$@"; do
@ -52,13 +57,10 @@ all_ports() {
add_ports() {
local port=
for port in $PORTS; do
if [ -x /etc/network/if-pre-up.d/vlan ]; then
env IFACE=$port /etc/network/if-pre-up.d/vlan
fi
if [ -n "$IF_BRIDGE_HW" ]; then
ip link set dev $port addr $IF_BRIDGE_HW
fi
brctl addif $IFACE $port && ip link set dev $port up
ip link set dev $port master $IFACE && ip link set dev $port up
done
}
@ -66,22 +68,17 @@ del_ports() {
local port=
for port in $PORTS; do
ip link set dev $port down
brctl delif $IFACE $port
if [ -x /etc/network/ip-post-down.d/vlan ]; then
env IFACE=$port /etc/network/if-post-down.d/vlan
fi
ip link set dev $port nomaster
done
}
set_bridge_opts() {
set_bridge_opts_brctl() {
[ -n "$IF_BRIDGE_AGEING" ] \
&& brctl setageing $IFACE $IF_BRIDGE_AGEING
[ -n "$IF_BRIDGE_BRIDGEPRIO" ] \
&& brctl setbridgeprio $IFACE $IF_BRIDGE_BRIDGEPRIO
[ -n "$IF_BRIDGE_FD" ] \
&& brctl setfd $IFACE $IF_BRIDGE_FD
[ -n "$IF_BRIDGE_GCINT" ] \
&& brctl setgcint $IFACE $IF_BRIDGE_GCINT
[ -n "$IF_BRIDGE_HELLO" ] \
&& brctl sethello $IFACE $IF_BRIDGE_HELLO
[ -n "$IF_BRIDGE_MAXAGE" ] \
@ -94,6 +91,44 @@ set_bridge_opts() {
&& brctl stp $IFACE $IF_BRIDGE_STP
}
yesno() {
case "$1" in
yes|YES|true|TRUE|1)
echo 1
;;
*)
echo 0
;;
esac
}
set_bridge_opts_iproute2() {
[ -n "$IF_BRIDGE_AGEING" ] \
&& ip link set dev $IFACE type bridge ageing_time $IF_BRIDGE_AGEING
[ -n "$IF_BRIDGE_BRIDGEPRIO" ] \
&& ip link set dev $IFACE type bridge priority $IF_BRIDGE_BRIDGEPRIO
[ -n "$IF_BRIDGE_FD" ] \
&& ip link set dev $IFACE type bridge forward_delay $IF_BRIDGE_FD
[ -n "$IF_BRIDGE_HELLO" ] \
&& ip link set dev $IFACE type bridge hello_time $IF_BRIDGE_HELLO
[ -n "$IF_BRIDGE_MAXAGE" ] \
&& ip link set dev $IFACE type bridge max_age $IF_BRIDGE_MAXAGE
[ -n "$IF_BRIDGE_PATHCOST" ] \
&& bridge link set dev $IFACE cost $IF_BRIDGE_PATHCOST
[ -n "$IF_BRIDGE_PORTPRIO" ] \
&& bridge link set dev $IFACE priority $IF_BRIDGE_PORTPRIO
[ -n "$IF_BRIDGE_STP" ] \
&& ip link set dev $IFACE type bridge stp $(yesno $IF_BRIDGE_STP)
[ -n "$IF_BRIDGE_VLAN_AWARE" ] \
&& ip link set dev $IFACE type bridge vlan_filtering $(yesno $IF_BRIDGE_VLAN_AWARE)
}
set_bridge_opts() {
[ -x /sbin/bridge ] && set_bridge_opts_iproute2 && return 0
[ -x /sbin/brctl ] && set_bridge_opts_brctl && return 0
}
all_ports_ready() {
local port=
for port in $PORTS; do
@ -123,7 +158,92 @@ wait_bridge() {
done
}
[ -z "$IF_BRIDGE_PORTS" ] && IF_BRIDGE_PORTS="$IF_REQUIRES"
################################################################################
# Bridge port management functions #
################################################################################
configure_access_port() {
port="$1"
vlan="$2"
self="$3"
# Cleans all existing VLANs (probably at least VLAN 1)
bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | while read vid flags; do
bridge vlan del vid "${vid}" dev "${port}" ${self}
done
bridge vlan add vid "${vlan}" pvid untagged dev "${port}" ${self}
}
configure_trunk_port() {
port="$1"
self="$2"
# Working on the bridge itself?
if [ "${self}" ]; then
allow_untagged="${IF_BRIDGE_ALLOW_UNTAGGED}"
pvid="${IF_BRIDGE_PVID}"
vids="${IF_BRIDGE_VIDS}"
else
allow_untagged=$(ifquery -p bridge-allow-untagged ${port} 2>/dev/null || true)
pvid=$(ifquery -p bridge-pvid ${port} 2>/dev/null || true)
vids=$(ifquery -p bridge-vids ${port} 2>/dev/null || true)
fi
# If bridge-allow-untagged if set to off, remove untagged VLAN. If it's
# one of our bridge-vids, it will be set again later.
if [ "${allow_untagged}" -a "$(yesno ${allow_untagged})" = 0 ]; then
untagged_vid=$(bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | awk '/Untagged/ { print $1 }')
if [ "${untagged_vid}" ]; then
bridge vlan del vid "${untagged_vid}" dev "${port}" ${self}
fi
fi
# The vlan specified is to be considered a PVID at ingress.
# Any untagged frames will be assigned to this VLAN.
if [ "${pvid}" ]; then
cur_pvid=$(bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | awk '/PVID/ { print $1 }')
if [ "${cur_pvid}" ]; then
bridge vlan del vid ${cur_pvid} dev "${port}" ${self}
fi
bridge vlan add vid "${pvid}" dev "${port}" pvid untagged ${self}
fi
# Add regular tagged VLANs
for vid in ${vids}; do
bridge vlan add vid $vid dev "${port}" ${self}
done
}
# Configure VLANs on the bridge interface itself
set_bridge_vlans() {
# Shall the bridge interface be an untagged port?
if [ "${IF_BRIDGE_ACCESS}" ]; then
configure_access_port "${IFACE}" "${IF_BRIDGE_ACCESS}" "self"
# Configure bridge interface as trunk port
else
configure_trunk_port "${IFACE}" "self"
fi
}
# Configure VLANs on the bridge-ports
set_bridge_port_vlans() {
for port in ${PORTS}; do
access_vlan=$(ifquery -p bridge-access ${port} 2>/dev/null || true)
# Shall this prot interface be an untagged port?
if [ "${access_vlan}" ]; then
configure_access_port "${port}" "${access_vlan}"
# Configure port as trunk
else
configure_trunk_port "${port}"
fi
done
}
case "$IF_BRIDGE_PORTS" in
"") ;;
@ -132,30 +252,53 @@ all) PORTS=$(all_ports);;
*) PORTS="$IF_BRIDGE_PORTS";;
esac
[ -z "$PORTS" ] && ! env | grep -q "^IF_BRIDGE" && exit
case "$PHASE" in
depend)
echo "$PORTS"
;;
create)
if [ ! -d "/sys/class/net/${IFACE}" ]; then
brctl addbr "${IFACE}"
# Called for the bridge interface
if [ "${IF_BRIDGE_PORTS}" ]; then
echo "$PORTS"
fi
;;
create)
# Called for the bridge interface
if [ "${IF_BRIDGE_PORTS}" -a ! -d "/sys/class/net/${IFACE}" ]; then
ip link add "${IFACE}" type bridge
fi
;;
pre-up)
wait_ports
set_bridge_opts
add_ports
wait_bridge
# Called for the bridge interface
if [ "${IF_BRIDGE_PORTS}" ]; then
wait_ports
set_bridge_opts
set_bridge_vlans
add_ports
set_bridge_port_vlans
wait_bridge
# Called for a bridge member port
elif [ "${IF_BRIDGE_VIDS}" -o "${IF_BRIDGE_PVID}" -o "${IF_BRIDGE_ACCESS}" -o "${IF_BRIDGE_ALLOW_UNTAGGED}" ]; then
# Eventually we want to configure VLAN settings of member ports here.
# The current execution model does not allow this, so this is a no-op
# for now and we work around this by configuring ports while configuring
# the bridge.
true
fi
;;
post-down)
del_ports
ip link set dev $IFACE down
# Called for the bridge interface
if [ "${IF_BRIDGE_PORTS}" ]; then
del_ports
ip link set dev $IFACE down
fi
;;
destroy)
if [ -d "/sys/class/net/${IFACE}" ]; then
brctl delbr "${IFACE}"
# Called for the bridge interface
if [ "${IF_BRIDGE_PORTS}" -a -d "/sys/class/net/${IFACE}" ]; then
ip link del "${IFACE}"
fi
;;
esac

View file

@ -14,14 +14,100 @@
*/
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "libifupdown/config-file.h"
#include "libifupdown/dict.h"
#include "libifupdown/interface.h"
#include "libifupdown/tokenize.h"
static bool
compat_ifupdown2_bridge_ports_inherit_vlans(struct lif_dict *collection)
{
struct lif_node *iter;
/* Loop through all interfaces and search for bridges */
LIF_DICT_FOREACH(iter, collection)
{
struct lif_dict_entry *entry = iter->data;
struct lif_interface *bridge = entry->data;
/* We only care for bridges */
if (!bridge->is_bridge)
continue;
struct lif_dict_entry *bridge_pvid = lif_dict_find(&bridge->vars, "bridge-pvid");
struct lif_dict_entry *bridge_vids = lif_dict_find(&bridge->vars, "bridge-vids");
/* If there's nothing to inherit here, carry on */
if (bridge_pvid == NULL && bridge_vids == NULL)
continue;
struct lif_dict_entry *bridge_ports_entry = lif_dict_find(&bridge->vars, "bridge-ports");
/* This SHOULD not happen, but better save than sorry */
if (bridge_ports_entry == NULL)
continue;
char bridge_ports_str[4096] = {};
strlcpy(bridge_ports_str, bridge_ports_entry->data, sizeof bridge_ports_str);
/* If there are no bridge-ports configured, carry on */
if (strcmp(bridge_ports_str, "none") == 0)
continue;
/* Loop over all bridge-ports and set bridge-pvid and bridge-vid if not set already */
char *bufp = bridge_ports_str;
for (char *tokenp = lif_next_token(&bufp); *tokenp; tokenp = lif_next_token(&bufp))
{
entry = lif_dict_find(collection, tokenp);
/* There might be interfaces give within the bridge-ports for which there is no
* interface stanza. If this is the case, we add one, so we can inherit the
* bridge-vids/pvid to it. */
struct lif_interface *bridge_port;
if (entry)
bridge_port = entry->data;
else if (lif_config.compat_create_interfaces)
{
bridge_port = lif_interface_collection_find(collection, tokenp);
if (bridge_port == NULL)
{
fprintf(stderr, "Failed to add interface \"%s\"", tokenp);
return false;
}
}
/* We would have to creaet an interface, but shouldn't */
else
{
fprintf(stderr, "compat: Missing interface stanza for bridge-port \"%s\" but should not create one.\n",
tokenp);
continue;
}
/* Maybe pimp bridge-pvid */
struct lif_dict_entry *port_pvid = lif_dict_find(&bridge_port->vars, "bridge-pvid");
if (bridge_pvid && !port_pvid)
lif_dict_add(&bridge_port->vars, "bridge-pvid", bridge_pvid->data);
/* Maybe pimp bridge-vids */
struct lif_dict_entry *port_vids = lif_dict_find(&bridge_port->vars, "bridge-vids");
if (bridge_vids && !port_vids)
lif_dict_add(&bridge_port->vars, "bridge-vids", bridge_vids->data);
}
}
return true;
}
extern bool
lif_compat_apply(struct lif_dict *collection)
{
(void) collection;
/* Mangle interfaces according to some config options here */
if (lif_config.compat_ifupdown2_bridge_ports_inherit_vlans &&
!compat_ifupdown2_bridge_ports_inherit_vlans(collection))
return false;
return true;
}

View file

@ -21,6 +21,8 @@
struct lif_config_file lif_config = {
.allow_addon_scripts = true,
.allow_any_iface_as_template = true,
.compat_create_interfaces = true,
.compat_ifupdown2_bridge_ports_inherit_vlans = true,
.implicit_template_conversion = true,
.use_hostname_for_dhcp = true,
};
@ -47,6 +49,8 @@ set_bool_value(const char *key, const char *value, void *opaque)
static struct lif_config_handler handlers[] = {
{"allow_addon_scripts", set_bool_value, &lif_config.allow_addon_scripts},
{"allow_any_iface_as_template", set_bool_value, &lif_config.allow_any_iface_as_template},
{"compat_create_interfaces", set_bool_value, &lif_config.compat_create_interfaces},
{"compat_ifupdown2_bridge_ports_inherit_vlans", set_bool_value, &lif_config.compat_ifupdown2_bridge_ports_inherit_vlans},
{"implicit_template_conversion", set_bool_value, &lif_config.implicit_template_conversion},
{"use_hostname_for_dhcp", set_bool_value, &lif_config.use_hostname_for_dhcp},
};

View file

@ -21,6 +21,8 @@
struct lif_config_file {
bool allow_addon_scripts;
bool allow_any_iface_as_template;
bool compat_create_interfaces;
bool compat_ifupdown2_bridge_ports_inherit_vlans;
bool implicit_template_conversion;
bool use_hostname_for_dhcp;
};

View file

@ -206,6 +206,10 @@ handle_generic(struct lif_interface_file_parse_state *state, char *token, char *
token = maybe_remap_token(token);
/* This smells like a bridge */
if (strcmp(token, "bridge-ports") == 0)
state->cur_iface->is_bridge = true;
/* Skip any leading whitespaces in value for <token> */
while (isspace (*bufp))
bufp++;