netplan-developers team mailing list archive
-
netplan-developers team
-
Mailing list archive
-
Message #00022
[Merge] netplan:cyphermox/routes into netplan:master
Mathieu Trudel-Lapierre has proposed merging netplan:cyphermox/routes into netplan:master.
Requested reviews:
Martin Pitt (pitti)
For more details, see:
https://code.launchpad.net/~netplan-developers/netplan/+git/netplan/+merge/312683
Add support for defining routes.
--
Your team Developers of netplan is subscribed to branch netplan:master.
diff --git a/src/networkd.c b/src/networkd.c
index 2d0e586..47cf916 100644
--- a/src/networkd.c
+++ b/src/networkd.c
@@ -168,6 +168,16 @@ write_network_file(net_definition* def, const char* rootdir, const char* path)
if (nd->vlan_link == def)
g_string_append_printf(s, "VLAN=%s\n", nd->id);
}
+ if (def->has_routes) {
+ for (unsigned i = 0; i < def->routes->len; ++i) {
+ g_string_append(s, "\n");
+ ip_route* cur_route = g_array_index (def->routes, ip_route*, i);
+ g_string_append_printf(s, "[Route]\nDestination=%s\nGateway=%s\n",
+ cur_route->to, cur_route->via);
+ if (cur_route->metric != G_MAXUINT)
+ g_string_append_printf(s, "Metric=%d\n", cur_route->metric);
+ }
+ }
/* NetworkManager compatible route metrics */
if (def->dhcp4 || def->dhcp6)
diff --git a/src/parse.c b/src/parse.c
index 2db2b11..5fecf1d 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -38,6 +38,8 @@ net_definition* cur_netdef;
/* wifi AP that is currently being processed */
wifi_access_point* cur_access_point;
+ip_route* cur_route;
+
netdef_backend backend_global, backend_cur_type;
/* Global ID → net_definition* map for all parsed config files */
@@ -625,6 +627,128 @@ handle_nameservers_addresses(yaml_document_t* doc, yaml_node_t* node, const void
return TRUE;
}
+
+static int
+get_ip_family(const char* address)
+{
+ struct in_addr a4;
+ struct in6_addr a6;
+ char *ip_str, *prefix_len;
+ int family;
+ int ret = -1;
+
+ ip_str = g_strdup(address);
+ prefix_len = strrchr(ip_str, '/');
+ if (prefix_len)
+ *prefix_len = '\0';
+
+ family = AF_INET;
+ ret = inet_pton(AF_INET, ip_str, &a4);
+ g_assert(ret >= 0);
+ if (ret > 0) {
+ ret = family;
+ goto family_ret;
+ }
+
+ family = AF_INET6;
+ ret = inet_pton(AF_INET6, ip_str, &a6);
+ g_assert(ret >= 0);
+ if (ret > 0)
+ ret = family;
+
+family_ret:
+ return ret;
+}
+
+static gboolean
+check_and_set_family(int family)
+{
+ if (cur_route->family != -1 && cur_route->family != family)
+ return FALSE;
+
+ cur_route->family = family;
+
+ return TRUE;
+}
+
+static gboolean
+handle_routes_to(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error)
+{
+ int family = get_ip_family(scalar(node));
+ g_assert(family >= 0);
+ if (!check_and_set_family(family))
+ return yaml_error(node, error, "IP family mismatch in route to %s", scalar(node));
+ cur_route->to = g_strdup(scalar(node));
+
+ return TRUE;
+}
+
+static gboolean
+handle_routes_via(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error)
+{
+ int family = get_ip_family(scalar(node));
+ g_assert(family >= 0);
+ if (!check_and_set_family(family))
+ return yaml_error(node, error, "IP family mismatch in route via %s", scalar(node));
+ cur_route->via = g_strdup(scalar(node));
+
+ return TRUE;
+}
+
+static gboolean
+handle_routes_metric(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error)
+{
+ guint64 v;
+ gchar* endptr;
+
+ v = g_ascii_strtoull(scalar(node), &endptr, 10);
+ if (*endptr != '\0' || v > G_MAXUINT)
+ return yaml_error(node, error, "invalid unsigned int value %s", scalar(node));
+
+ cur_route->metric = (guint) v;
+ return TRUE;
+}
+
+/****************************************************
+ * Grammar and handlers for network config "routes" entry
+ ****************************************************/
+
+const mapping_entry_handler routes_handlers[] = {
+ {"to", YAML_SCALAR_NODE, handle_routes_to},
+ {"via", YAML_SCALAR_NODE, handle_routes_via},
+ {"metric", YAML_SCALAR_NODE, handle_routes_metric},
+ {NULL}
+};
+
+static gboolean
+handle_routes(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error)
+{
+ for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
+ yaml_node_t *entry = yaml_document_get_node(doc, *i);
+
+ cur_route = g_new0(ip_route, 1);
+ cur_route->family = G_MAXUINT; /* 0 is a valid family ID */
+ cur_route->metric = G_MAXUINT; /* 0 is a valid metric */
+
+ if (process_mapping(doc, entry, routes_handlers, error)) {
+ if (!cur_netdef->routes) {
+ cur_netdef->has_routes = TRUE;
+ cur_netdef->routes = g_array_new(FALSE, FALSE, sizeof(ip_route*));
+ }
+
+ g_array_append_val(cur_netdef->routes, cur_route);
+ }
+
+ if (error && *error)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/****************************************************
+ * Grammar and handlers for network devices
+ ****************************************************/
+
const mapping_entry_handler nameservers_handlers[] = {
{"search", YAML_SEQUENCE_NODE, handle_nameservers_search},
{"addresses", YAML_SEQUENCE_NODE, handle_nameservers_addresses},
@@ -642,6 +766,7 @@ const mapping_entry_handler ethernet_def_handlers[] = {
{"gateway4", YAML_SCALAR_NODE, handle_gateway4},
{"gateway6", YAML_SCALAR_NODE, handle_gateway6},
{"nameservers", YAML_MAPPING_NODE, NULL, nameservers_handlers},
+ {"routes", YAML_SEQUENCE_NODE, handle_routes},
{NULL}
};
@@ -657,6 +782,7 @@ const mapping_entry_handler wifi_def_handlers[] = {
{"gateway6", YAML_SCALAR_NODE, handle_gateway6},
{"nameservers", YAML_MAPPING_NODE, NULL, nameservers_handlers},
{"access-points", YAML_MAPPING_NODE, handle_wifi_access_points},
+ {"routes", YAML_MAPPING_NODE, NULL, routes_handlers},
{NULL}
};
@@ -669,6 +795,7 @@ const mapping_entry_handler bridge_def_handlers[] = {
{"gateway6", YAML_SCALAR_NODE, handle_gateway6},
{"nameservers", YAML_MAPPING_NODE, NULL, nameservers_handlers},
{"interfaces", YAML_SEQUENCE_NODE, handle_interfaces, NULL, netdef_offset(bridge)},
+ {"routes", YAML_MAPPING_NODE, NULL, routes_handlers},
{NULL}
};
@@ -681,6 +808,7 @@ const mapping_entry_handler bond_def_handlers[] = {
{"gateway6", YAML_SCALAR_NODE, handle_gateway6},
{"nameservers", YAML_MAPPING_NODE, NULL, nameservers_handlers},
{"interfaces", YAML_SEQUENCE_NODE, handle_interfaces, NULL, netdef_offset(bond)},
+ {"routes", YAML_MAPPING_NODE, NULL, routes_handlers},
{NULL}
};
@@ -694,6 +822,7 @@ const mapping_entry_handler vlan_def_handlers[] = {
{"nameservers", YAML_MAPPING_NODE, NULL, nameservers_handlers},
{"id", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(vlan_id)},
{"link", YAML_SCALAR_NODE, handle_netdef_id_ref, NULL, netdef_offset(vlan_link)},
+ {"routes", YAML_MAPPING_NODE, NULL, routes_handlers},
{NULL}
};
diff --git a/src/parse.h b/src/parse.h
index 7c5bf2b..10250a1 100644
--- a/src/parse.h
+++ b/src/parse.h
@@ -61,6 +61,8 @@ typedef struct net_definition {
GArray* ip4_nameservers;
GArray* ip6_nameservers;
GArray* search_domains;
+ GArray* routes;
+ gboolean has_routes;
/* master ID for slave devices */
char* bridge;
@@ -97,6 +99,14 @@ typedef struct {
char* password;
} wifi_access_point;
+typedef struct {
+ guint family;
+
+ char* to;
+ char* via;
+
+ guint metric;
+} ip_route;
/* Written/updated by parse_yaml(): char* id → net_definition */
extern GHashTable* netdefs;
diff --git a/tests/generate.py b/tests/generate.py
index b3a127b..83dedbd 100755
--- a/tests/generate.py
+++ b/tests/generate.py
@@ -510,6 +510,152 @@ Address=2001:FFfe::1/64
RouteMetric=100
'''})
+ def test_device_bad_routes(self):
+ self.generate('''network:
+ version: 2
+ ethernets:
+ engreen:
+ routes:
+ - to: badlocation
+ via: 192.168.14.20
+ metric: 100
+ addresses:
+ - 192.168.14.2/24
+ - 2001:FFfe::1/64''', expect_fail=True)
+
+ self.generate('''network:
+ version: 2
+ ethernets:
+ engreen:
+ routes:
+ - to: 10.10.0.0/16
+ via: badgateway
+ metric: 100
+ addresses:
+ - 192.168.14.2/24
+ - 2001:FFfe::1/64''', expect_fail=True)
+
+ self.generate('''network:
+ version: 2
+ ethernets:
+ engreen:
+ routes:
+ - to: 10.10.0.0/16
+ via: 10.1.1.1
+ metric: -1
+ addresses:
+ - 192.168.14.2/24
+ - 2001:FFfe::1/64''', expect_fail=True)
+
+ self.generate('''network:
+ version: 2
+ ethernets:
+ engreen:
+ routes:
+ - to: 2001:dead:beef::0/16
+ via: 10.1.1.1
+ metric: 1
+ addresses:
+ - 192.168.14.2/24
+ - 2001:FFfe::1/64''', expect_fail=True)
+
+ self.generate('''network:
+ version: 2
+ ethernets:
+ engreen:
+ routes:
+ - via: 2001:dead:beef::2
+ to: 10.10.10.0/24
+ metric: 1
+ addresses:
+ - 192.168.14.2/24
+ - 2001:FFfe::1/64''', expect_fail=True)
+
+ def test_route_v4_single(self):
+ self.generate('''network:
+ version: 2
+ ethernets:
+ engreen:
+ addresses: ["192.168.14.2/24"]
+ routes:
+ - to: 10.10.10.0/24
+ via: 192.168.14.20
+ metric: 100
+ ''')
+
+ self.assert_networkd({'engreen.network': '''[Match]
+Name=engreen
+
+[Network]
+Address=192.168.14.2/24
+
+[Route]
+Destination=10.10.10.0/24
+Gateway=192.168.14.20
+Metric=100
+'''})
+
+ def test_route_v4_multiple(self):
+ self.generate('''network:
+ version: 2
+ ethernets:
+ engreen:
+ addresses: ["192.168.14.2/24"]
+ routes:
+ - to: 8.8.0.0/16
+ via: 192.168.1.1
+ metric: 10
+ - to: 10.10.10.8
+ via: 192.168.1.2
+ metric: 5000
+ - to: 11.11.11.0/24
+ via: 192.168.1.3
+ metric: 9999
+ ''')
+
+ self.assert_networkd({'engreen.network': '''[Match]
+Name=engreen
+
+[Network]
+Address=192.168.14.2/24
+
+[Route]
+Destination=8.8.0.0/16
+Gateway=192.168.1.1
+Metric=10
+
+[Route]
+Destination=10.10.10.8
+Gateway=192.168.1.2
+Metric=5000
+
+[Route]
+Destination=11.11.11.0/24
+Gateway=192.168.1.3
+Metric=9999
+'''})
+
+ def test_route_v6_single(self):
+ self.generate('''network:
+ version: 2
+ ethernets:
+ enblue:
+ addresses: ["192.168.1.3/24"]
+ routes:
+ - to: 2001:dead:beef::2/64
+ via: 2001:beef:beef::1''')
+
+ self.assert_networkd({'enblue.network': '''[Match]
+Name=enblue
+
+[Network]
+Address=192.168.1.3/24
+
+[Route]
+Destination=2001:dead:beef::2/64
+Gateway=2001:beef:beef::1
+'''})
+
def test_wifi(self):
self.generate('''network:
version: 2
Follow ups