← Back to team overview

netplan-developers team mailing list archive

Re: [Merge] netplan:cyphermox/routes into netplan:master

 

Review: Needs Fixing

The general structure looks good to me, I have a bunch of inline comments.

This is missing the NM implementation and extending the integration test case to cover this.

Diff comments:

> diff --git a/src/parse.c b/src/parse.c
> index 2db2b11..5fecf1d 100644
> --- a/src/parse.c
> +++ b/src/parse.c
> @@ -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;

ip_str is missing a g_autofree. But actually I don't see any reason to strdup this at all, you should be able to call inet_pton() directly on "address" (the argument is const).

> +    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;

This could just return the family directly

> +    }
> +
> +    family = AF_INET6;
> +    ret = inet_pton(AF_INET6, ip_str, &a6);
> +    g_assert(ret >= 0);
> +    if (ret > 0)
> +        ret = family;
> +

... and then do a g_assert_not_reached() here or some error message about an invalid value.

> +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;
> +}

This does almost exactly the same. I'd suggest to take the offset of the target field in the userdata, like with netdef_offset(). Then you can also fold check_and_set_family() into this function.

> +
> +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);
> +        }

For robustness/better debuggability I think it would make sense to set cur_route back to NULL here.

> +
> +        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},

must be MAPPING_NODE

>      {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;

Please drop this, it's redundant with routes != NULL.

>  
>      /* master ID for slave devices */
>      char* bridge;
> 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)

Can you please move this to TestConfigErrors for consistency?

Also, please add some tests what happens if a dict entry is missing via or to, and for specifying an IPv4 to and IPv6 to.

> +
> +    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


-- 
https://code.launchpad.net/~netplan-developers/netplan/+git/netplan/+merge/312683
Your team Developers of netplan is subscribed to branch netplan:master.


References