diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 612450dbd8..c9d6fd4d72 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1086,8 +1086,9 @@ Table= - Specifies the routing table identifier to lookup if the rule - selector matches. The table identifier for a route (a number between 1 and 4294967295). + Specifies the routing table identifier to lookup if the rule selector matches. Takes + one of default, main, and local, + or a number between 1 and 4294967295. Defaults to main. @@ -1232,7 +1233,11 @@ Type= - Specifies the type for the route. If unicast, a regular route is defined, i.e. a + Specifies the type for the route. Takes one of unicast, + local, broadcast, anycast, + multicast, blackhole, unreachable, + prohibit, throw, nat, and + xresolve. If unicast, a regular route is defined, i.e. a route indicating the path to take to a destination network address. If blackhole, packets to the defined route are discarded silently. If unreachable, packets to the defined route are discarded and the ICMP message "Host Unreachable" is generated. If prohibit, packets diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 33b7ec1d6c..ec3bb47ea6 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -298,7 +298,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); if (r == -ENODATA) { - log_debug("rtnl: received route without ifindex, ignoring"); + log_debug("rtnl: received route message without ifindex, ignoring"); return 0; } else if (r < 0) { log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m"); @@ -313,7 +313,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo /* when enumerating we might be out of sync, but we will * get the route again, so just ignore it */ if (!m->enumerating) - log_warning("rtnl: received route for link (%d) we do not know about, ignoring", ifindex); + log_warning("rtnl: received route message for link (%d) we do not know about, ignoring", ifindex); return 0; } @@ -391,43 +391,43 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo r = sd_rtnl_message_route_get_dst_prefixlen(message, &dst_prefixlen); if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route with invalid destination prefixlen, ignoring: %m"); + log_link_warning_errno(link, r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m"); return 0; } r = sd_rtnl_message_route_get_src_prefixlen(message, &src_prefixlen); if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route with invalid source prefixlen, ignoring: %m"); + log_link_warning_errno(link, r, "rtnl: received route message with invalid source prefixlen, ignoring: %m"); return 0; } r = sd_rtnl_message_route_get_scope(message, &scope); if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route with invalid scope, ignoring: %m"); + log_link_warning_errno(link, r, "rtnl: received route message with invalid scope, ignoring: %m"); return 0; } r = sd_rtnl_message_route_get_tos(message, &tos); if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route with invalid tos, ignoring: %m"); + log_link_warning_errno(link, r, "rtnl: received route message with invalid tos, ignoring: %m"); return 0; } r = sd_rtnl_message_route_get_type(message, &rt_type); if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route with invalid type, ignoring: %m"); + log_link_warning_errno(link, r, "rtnl: received route message with invalid type, ignoring: %m"); return 0; } r = sd_rtnl_message_route_get_table(message, &table); if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route with invalid table, ignoring: %m"); + log_link_warning_errno(link, r, "rtnl: received route message with invalid table, ignoring: %m"); return 0; } r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &priority); if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route with invalid priority, ignoring: %m"); + log_link_warning_errno(link, r, "rtnl: received route message with invalid priority, ignoring: %m"); return 0; } @@ -436,6 +436,8 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo if (DEBUG_LOGGING) { _cleanup_free_ char *buf_dst = NULL, *buf_dst_prefixlen = NULL, *buf_src = NULL, *buf_gw = NULL, *buf_prefsrc = NULL; + char buf_scope[ROUTE_SCOPE_STR_MAX], buf_table[ROUTE_TABLE_STR_MAX], + buf_protocol[ROUTE_PROTOCOL_STR_MAX]; if (!in_addr_is_null(family, &dst)) { (void) in_addr_to_string(family, &dst, &buf_dst); @@ -449,10 +451,14 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo (void) in_addr_to_string(family, &prefsrc, &buf_prefsrc); log_link_debug(link, - "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s", + "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", type == RTM_DELROUTE ? "Forgetting" : route ? "Updating remembered" : "Remembering", strna(buf_dst), strempty(buf_dst_prefixlen), - strna(buf_src), strna(buf_gw), strna(buf_prefsrc)); + strna(buf_src), strna(buf_gw), strna(buf_prefsrc), + format_route_scope(scope, buf_scope, sizeof buf_scope), + format_route_table(table, buf_table, sizeof buf_table), + format_route_protocol(protocol, buf_protocol, sizeof buf_protocol), + strna(route_type_to_string(rt_type))); } switch (type) { diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index f79d5b2057..ddc0fe279f 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -12,7 +12,9 @@ #include "networkd-route.h" #include "parse-util.h" #include "set.h" +#include "string-table.h" #include "string-util.h" +#include "strxcpyx.h" #include "sysctl-util.h" #include "util.h" @@ -412,6 +414,29 @@ int route_remove(Route *route, Link *link, if (r < 0) return log_link_error_errno(link, r, "Could not create RTM_DELROUTE message: %m"); + if (DEBUG_LOGGING) { + _cleanup_free_ char *dst = NULL, *dst_prefixlen = NULL, *src = NULL, *gw = NULL, *prefsrc = NULL; + char scope[ROUTE_SCOPE_STR_MAX], table[ROUTE_TABLE_STR_MAX], protocol[ROUTE_PROTOCOL_STR_MAX]; + + if (!in_addr_is_null(route->family, &route->dst)) { + (void) in_addr_to_string(route->family, &route->dst, &dst); + (void) asprintf(&dst_prefixlen, "/%u", route->dst_prefixlen); + } + if (!in_addr_is_null(route->family, &route->src)) + (void) in_addr_to_string(route->family, &route->src, &src); + if (!in_addr_is_null(route->family, &route->gw)) + (void) in_addr_to_string(route->family, &route->gw, &gw); + if (!in_addr_is_null(route->family, &route->prefsrc)) + (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); + + log_link_debug(link, "Removing route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", + strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc), + format_route_scope(route->scope, scope, sizeof(scope)), + format_route_table(route->table, table, sizeof(table)), + format_route_protocol(route->protocol, protocol, sizeof(protocol)), + strna(route_type_to_string(route->type))); + } + if (in_addr_is_null(route->family, &route->gw) == 0) { r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->family, &route->gw); if (r < 0) @@ -513,6 +538,7 @@ int route_configure( if (DEBUG_LOGGING) { _cleanup_free_ char *dst = NULL, *dst_prefixlen = NULL, *src = NULL, *gw = NULL, *prefsrc = NULL; + char scope[ROUTE_SCOPE_STR_MAX], table[ROUTE_TABLE_STR_MAX], protocol[ROUTE_PROTOCOL_STR_MAX]; if (!in_addr_is_null(route->family, &route->dst)) { (void) in_addr_to_string(route->family, &route->dst, &dst); @@ -525,8 +551,12 @@ int route_configure( if (!in_addr_is_null(route->family, &route->prefsrc)) (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); - log_link_debug(link, "Configuring route: dst: %s%s, src: %s, gw: %s, prefsrc: %s", - strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc)); + log_link_debug(link, "Configuring route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s", + strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc), + format_route_scope(route->scope, scope, sizeof(scope)), + format_route_table(route->table, table, sizeof(table)), + format_route_protocol(route->protocol, protocol, sizeof(protocol)), + strna(route_type_to_string(route->type))); } r = sd_rtnl_message_new_route(link->manager->rtnl, &req, @@ -718,6 +748,8 @@ int network_add_ipv4ll_route(Network *network) { n->family = AF_INET; n->dst_prefixlen = 16; n->scope = RT_SCOPE_LINK; + n->scope_set = true; + n->table_set = true; n->priority = IPV4LL_ROUTE_METRIC; n->protocol = RTPROT_STATIC; @@ -749,6 +781,88 @@ int network_add_default_route_on_device(Network *network) { return 0; } +static const char * const route_type_table[__RTN_MAX] = { + [RTN_UNICAST] = "unicast", + [RTN_LOCAL] = "local", + [RTN_BROADCAST] = "broadcast", + [RTN_ANYCAST] = "anycast", + [RTN_MULTICAST] = "multicast", + [RTN_BLACKHOLE] = "blackhole", + [RTN_UNREACHABLE] = "unreachable", + [RTN_PROHIBIT] = "prohibit", + [RTN_THROW] = "throw", + [RTN_NAT] = "nat", + [RTN_XRESOLVE] = "xresolve", +}; + +assert_cc(__RTN_MAX <= UCHAR_MAX); +DEFINE_STRING_TABLE_LOOKUP(route_type, int); + +static const char * const route_scope_table[] = { + [RT_SCOPE_UNIVERSE] = "global", + [RT_SCOPE_SITE] = "site", + [RT_SCOPE_LINK] = "link", + [RT_SCOPE_HOST] = "host", + [RT_SCOPE_NOWHERE] = "nowhere", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_scope, int); + +const char *format_route_scope(int scope, char *buf, size_t size) { + const char *s; + char *p = buf; + + s = route_scope_to_string(scope); + if (s) + strpcpy(&p, size, s); + else + strpcpyf(&p, size, "%d", scope); + + return buf; +} + +static const char * const route_table_table[] = { + [RT_TABLE_DEFAULT] = "default", + [RT_TABLE_MAIN] = "main", + [RT_TABLE_LOCAL] = "local", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table, int); + +const char *format_route_table(int table, char *buf, size_t size) { + const char *s; + char *p = buf; + + s = route_table_to_string(table); + if (s) + strpcpy(&p, size, s); + else + strpcpyf(&p, size, "%d", table); + + return buf; +} + +static const char * const route_protocol_table[] = { + [RTPROT_KERNEL] = "kernel", + [RTPROT_BOOT] = "boot", + [RTPROT_STATIC] = "static", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_protocol, int); + +const char *format_route_protocol(int protocol, char *buf, size_t size) { + const char *s; + char *p = buf; + + s = route_protocol_to_string(protocol); + if (s) + strpcpy(&p, size, s); + else + strpcpyf(&p, size, "%d", protocol); + + return buf; +} + int config_parse_gateway( const char *unit, const char *filename, @@ -948,17 +1062,14 @@ int config_parse_route_scope( if (r < 0) return r; - if (streq(rvalue, "host")) - n->scope = RT_SCOPE_HOST; - else if (streq(rvalue, "link")) - n->scope = RT_SCOPE_LINK; - else if (streq(rvalue, "global")) - n->scope = RT_SCOPE_UNIVERSE; - else { + r = route_scope_from_string(rvalue); + if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, 0, "Unknown route scope: %s", rvalue); return 0; } + n->scope = r; + n->scope_set = true; TAKE_PTR(n); return 0; } @@ -989,13 +1100,19 @@ int config_parse_route_table( if (r < 0) return r; - r = safe_atou32(rvalue, &n->table); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, - "Could not parse route table number \"%s\", ignoring assignment: %m", rvalue); - return 0; + r = route_table_from_string(rvalue); + if (r >= 0) + n->table = r; + else { + r = safe_atou32(rvalue, &n->table); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Could not parse route table number \"%s\", ignoring assignment: %m", rvalue); + return 0; + } } + n->table_set = true; TAKE_PTR(n); return 0; } @@ -1094,12 +1211,9 @@ int config_parse_route_protocol( if (r < 0) return r; - if (streq(rvalue, "kernel")) - n->protocol = RTPROT_KERNEL; - else if (streq(rvalue, "boot")) - n->protocol = RTPROT_BOOT; - else if (streq(rvalue, "static")) - n->protocol = RTPROT_STATIC; + r = route_protocol_from_string(rvalue); + if (r >= 0) + n->protocol = r; else { r = safe_atou8(rvalue , &n->protocol); if (r < 0) { @@ -1127,28 +1241,21 @@ int config_parse_route_type( Network *network = userdata; _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - int r; + int t, r; r = route_new_static(network, filename, section_line, &n); if (r < 0) return r; - if (streq(rvalue, "unicast")) - n->type = RTN_UNICAST; - else if (streq(rvalue, "blackhole")) - n->type = RTN_BLACKHOLE; - else if (streq(rvalue, "unreachable")) - n->type = RTN_UNREACHABLE; - else if (streq(rvalue, "prohibit")) - n->type = RTN_PROHIBIT; - else if (streq(rvalue, "throw")) - n->type = RTN_THROW; - else { - log_syntax(unit, LOG_ERR, filename, line, r, + t = route_type_from_string(rvalue); + if (t < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Could not parse route type \"%s\", ignoring assignment: %m", rvalue); return 0; } + n->type = (unsigned char) t; + TAKE_PTR(n); return 0; } @@ -1366,6 +1473,18 @@ int route_section_verify(Route *route, Network *network) { route->section->filename, route->section->line); } + if (route->family != AF_INET6) { + if (!route->table_set && IN_SET(route->type, RTN_LOCAL, RTN_BROADCAST, RTN_ANYCAST, RTN_NAT)) + route->table = RT_TABLE_LOCAL; + + if (!route->scope_set) { + if (IN_SET(route->type, RTN_LOCAL, RTN_NAT)) + route->scope = RT_SCOPE_HOST; + else if (IN_SET(route->type, RTN_BROADCAST, RTN_ANYCAST)) + route->scope = RT_SCOPE_LINK; + } + } + if (network->n_static_addresses == 0 && in_addr_is_null(route->family, &route->gw) == 0 && route->gateway_onlink < 0) { diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 3c0ac896ae..86a7a82617 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -2,6 +2,7 @@ #pragma once #include "conf-parser.h" +#include "macro.h" typedef struct Route Route; typedef struct NetworkConfigSection NetworkConfigSection; @@ -23,11 +24,13 @@ struct Route { unsigned char dst_prefixlen; unsigned char src_prefixlen; unsigned char scope; + bool scope_set; unsigned char protocol; /* RTPROT_* */ unsigned char type; /* RTN_* */ unsigned char tos; uint32_t priority; /* note that ip(8) calls this 'metric' */ uint32_t table; + bool table_set; uint32_t mtu; uint32_t initcwnd; uint32_t initrwnd; @@ -65,6 +68,18 @@ DEFINE_NETWORK_SECTION_FUNCTIONS(Route, route_free); int network_add_ipv4ll_route(Network *network); int network_add_default_route_on_device(Network *network); +const char* route_type_to_string(int t) _const_; +int route_type_from_string(const char *s) _pure_; + +#define ROUTE_SCOPE_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("nowhere") + 1) +const char *format_route_scope(int scope, char *buf, size_t size); + +#define ROUTE_TABLE_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("default") + 1) +const char *format_route_table(int table, char *buf, size_t size); + +#define ROUTE_PROTOCOL_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("kernel") + 1) +const char *format_route_protocol(int protocol, char *buf, size_t size); + CONFIG_PARSER_PROTOTYPE(config_parse_gateway); CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src); CONFIG_PARSER_PROTOTYPE(config_parse_destination); diff --git a/test/test-network/conf/25-route-static.network b/test/test-network/conf/25-route-static.network index 9e12480a65..9c8269e3b8 100644 --- a/test/test-network/conf/25-route-static.network +++ b/test/test-network/conf/25-route-static.network @@ -46,3 +46,19 @@ Destination=202.54.1.3 [Route] Type=prohibit Destination=202.54.1.4 + +[Route] +Type=local +Destination=149.10.123.1 + +[Route] +Type=anycast +Destination=149.10.123.2 + +[Route] +Type=broadcast +Destination=149.10.123.3 + +[Route] +Type=multicast +Destination=149.10.123.4 diff --git a/test/test-network/conf/26-bridge-slave-interface-1.network b/test/test-network/conf/26-bridge-slave-interface-1.network index 1f8c5b5648..6aed178525 100644 --- a/test/test-network/conf/26-bridge-slave-interface-1.network +++ b/test/test-network/conf/26-bridge-slave-interface-1.network @@ -13,3 +13,6 @@ MulticastFlood = false MulticastToUnicast = true NeighborSuppression = true Learning = false +Priority = 23 +UseBPDU = true +AllowPortToBeRoot=true diff --git a/test/test-network/conf/26-bridge-slave-interface-2.network b/test/test-network/conf/26-bridge-slave-interface-2.network index 45ec2de999..5fccfec5d1 100644 --- a/test/test-network/conf/26-bridge-slave-interface-2.network +++ b/test/test-network/conf/26-bridge-slave-interface-2.network @@ -3,3 +3,6 @@ Name=test1 [Network] Bridge=bridge99 + +[Bridge] +Priority=0 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 399824af5c..e396af05da 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1552,14 +1552,18 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): start_networkd() wait_online(['dummy98:routable']) + print('### ip -6 route show dev dummy98') output = check_output('ip -6 route show dev dummy98') print(output) self.assertRegex(output, '2001:1234:5:8fff:ff:ff:ff:ff proto static') self.assertRegex(output, '2001:1234:5:8f63::1 proto kernel') + print('### ip -6 route show dev dummy98 default') output = check_output('ip -6 route show dev dummy98 default') + print(output) self.assertRegex(output, 'default via 2001:1234:5:8fff:ff:ff:ff:ff proto static metric 1024 pref medium') + print('### ip -4 route show dev dummy98') output = check_output('ip -4 route show dev dummy98') print(output) self.assertRegex(output, '149.10.124.48/28 proto kernel scope link src 149.10.124.58') @@ -1567,20 +1571,33 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, '169.254.0.0/16 proto static scope link metric 2048') self.assertRegex(output, '192.168.1.1 proto static initcwnd 20') self.assertRegex(output, '192.168.1.2 proto static initrwnd 30') + self.assertRegex(output, 'multicast 149.10.123.4 proto static') + print('### ip -4 route show dev dummy98 default') output = check_output('ip -4 route show dev dummy98 default') + print(output) self.assertRegex(output, 'default via 149.10.125.65 proto static onlink') self.assertRegex(output, 'default via 149.10.124.64 proto static') self.assertRegex(output, 'default proto static') + print('### ip -4 route show table local dev dummy98') + output = check_output('ip -4 route show table local dev dummy98') + print(output) + self.assertRegex(output, 'local 149.10.123.1 proto static scope host') + self.assertRegex(output, 'anycast 149.10.123.2 proto static scope link') + self.assertRegex(output, 'broadcast 149.10.123.3 proto static scope link') + + print('### ip route show type blackhole') output = check_output('ip route show type blackhole') print(output) self.assertRegex(output, 'blackhole 202.54.1.2 proto static') + print('### ip route show type unreachable') output = check_output('ip route show type unreachable') print(output) self.assertRegex(output, 'unreachable 202.54.1.3 proto static') + print('### ip route show type prohibit') output = check_output('ip route show type prohibit') print(output) self.assertRegex(output, 'prohibit 202.54.1.4 proto static') @@ -2007,18 +2024,24 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities): output = check_output('bridge -d link show dummy98') print(output) - self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode'), '1') self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'path_cost'), '400') + self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode'), '1') + self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave'), '1') self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood'), '1') self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood'), '0') - self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave'), '1') - if (os.path.exists('/sys/devices/virtual/net/bridge99/lower_dummy98/brport/neigh_suppress')): - self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress'), '1') - self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'learning'), '0') - # CONFIG_BRIDGE_IGMP_SNOOPING=y if (os.path.exists('/sys/devices/virtual/net/bridge00/lower_dummy98/brport/multicast_to_unicast')): self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast'), '1') + if (os.path.exists('/sys/devices/virtual/net/bridge99/lower_dummy98/brport/neigh_suppress')): + self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress'), '1') + self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'learning'), '0') + self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'priority'), '23') + self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard'), '1') + self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'root_block'), '1') + + output = check_output('bridge -d link show test1') + print(output) + self.assertEqual(read_bridge_port_attr('bridge99', 'test1', 'priority'), '0') check_output('ip address add 192.168.0.16/24 dev bridge99') time.sleep(1)