← Back to team overview

netplan-developers team mailing list archive

[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