]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkd-route.c
networkd: route - support unicast,blackhole,unreachable and prohibited (#6861)
[thirdparty/systemd.git] / src / network / networkd-route.c
index cedaf47cf8e028a0d158b78979f28b1cfb8daede..5b4874795a7dfd78a1cba054779756fda8c43000 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <linux/icmpv6.h>
+
 #include "alloc-util.h"
 #include "conf-parser.h"
 #include "in-addr-util.h"
 #include "netlink-util.h"
+#include "networkd-manager.h"
 #include "networkd-route.h"
-#include "networkd.h"
 #include "parse-util.h"
 #include "set.h"
 #include "string-util.h"
+#include "sysctl-util.h"
 #include "util.h"
 
-#define ROUTES_PER_LINK_MAX 2048U
-#define STATIC_ROUTES_PER_NETWORK_MAX 1024U
+#define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U
+
+static unsigned routes_max(void) {
+        static thread_local unsigned cached = 0;
+
+        _cleanup_free_ char *s4 = NULL, *s6 = NULL;
+        unsigned val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY;
+
+        if (cached > 0)
+                return cached;
+
+        if (sysctl_read("net/ipv4/route/max_size", &s4) >= 0) {
+                truncate_nl(s4);
+                if (safe_atou(s4, &val4) >= 0 &&
+                    val4 == 2147483647U)
+                        /* This is the default "no limit" value in the kernel */
+                        val4 = ROUTES_DEFAULT_MAX_PER_FAMILY;
+        }
+
+        if (sysctl_read("net/ipv6/route/max_size", &s6) >= 0) {
+                truncate_nl(s6);
+                (void) safe_atou(s6, &val6);
+        }
+
+        cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) +
+                 MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6);
+        return cached;
+}
 
 int route_new(Route **ret) {
         _cleanup_route_free_ Route *route = NULL;
@@ -41,7 +70,8 @@ int route_new(Route **ret) {
         route->family = AF_UNSPEC;
         route->scope = RT_SCOPE_UNIVERSE;
         route->protocol = RTPROT_UNSPEC;
-        route->table = RT_TABLE_DEFAULT;
+        route->type = RTN_UNICAST;
+        route->table = RT_TABLE_MAIN;
         route->lifetime = USEC_INFINITY;
 
         *ret = route;
@@ -50,15 +80,21 @@ int route_new(Route **ret) {
         return 0;
 }
 
-int route_new_static(Network *network, unsigned section, Route **ret) {
+int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) {
+        _cleanup_network_config_section_free_ NetworkConfigSection *n = NULL;
         _cleanup_route_free_ Route *route = NULL;
         int r;
 
         assert(network);
         assert(ret);
+        assert(!!filename == (section_line > 0));
 
-        if (section) {
-                route = hashmap_get(network->routes_by_section, UINT_TO_PTR(section));
+        if (filename) {
+                r = network_config_section_new(filename, section_line, &n);
+                if (r < 0)
+                        return r;
+
+                route = hashmap_get(network->routes_by_section, n);
                 if (route) {
                         *ret = route;
                         route = NULL;
@@ -67,7 +103,7 @@ int route_new_static(Network *network, unsigned section, Route **ret) {
                 }
         }
 
-        if (network->n_static_routes >= STATIC_ROUTES_PER_NETWORK_MAX)
+        if (network->n_static_routes >= routes_max())
                 return -E2BIG;
 
         r = route_new(&route);
@@ -76,10 +112,11 @@ int route_new_static(Network *network, unsigned section, Route **ret) {
 
         route->protocol = RTPROT_STATIC;
 
-        if (section) {
-                route->section = section;
+        if (filename) {
+                route->section = n;
+                n = NULL;
 
-                r = hashmap_put(network->routes_by_section, UINT_TO_PTR(route->section), route);
+                r = hashmap_put(network->routes_by_section, route->section, route);
                 if (r < 0)
                         return r;
         }
@@ -105,9 +142,11 @@ void route_free(Route *route) {
                 route->network->n_static_routes--;
 
                 if (route->section)
-                        hashmap_remove(route->network->routes_by_section, UINT_TO_PTR(route->section));
+                        hashmap_remove(route->network->routes_by_section, route->section);
         }
 
+        network_config_section_free(route->section);
+
         if (route->link) {
                 set_remove(route->link->routes, route);
                 set_remove(route->link->routes_foreign, route);
@@ -192,7 +231,7 @@ int route_get(Link *link,
               unsigned char dst_prefixlen,
               unsigned char tos,
               uint32_t priority,
-              unsigned char table,
+              uint32_t table,
               Route **ret) {
 
         Route route, *existing;
@@ -234,7 +273,7 @@ static int route_add_internal(
                 unsigned char dst_prefixlen,
                 unsigned char tos,
                 uint32_t priority,
-                unsigned char table,
+                uint32_t table,
                 Route **ret) {
 
         _cleanup_route_free_ Route *route = NULL;
@@ -280,7 +319,7 @@ int route_add_foreign(
                 unsigned char dst_prefixlen,
                 unsigned char tos,
                 uint32_t priority,
-                unsigned char table,
+                uint32_t table,
                 Route **ret) {
 
         return route_add_internal(link, &link->routes_foreign, family, dst, dst_prefixlen, tos, priority, table, ret);
@@ -293,7 +332,7 @@ int route_add(
               unsigned char dst_prefixlen,
               unsigned char tos,
               uint32_t priority,
-              unsigned char table,
+              uint32_t table,
               Route **ret) {
 
         Route *route;
@@ -322,7 +361,8 @@ int route_add(
         } else
                 return r;
 
-        *ret = route;
+        if (ret)
+                *ret = route;
 
         return 0;
 }
@@ -333,7 +373,8 @@ int route_update(Route *route,
                  const union in_addr_union *gw,
                  const union in_addr_union *prefsrc,
                  unsigned char scope,
-                 unsigned char protocol) {
+                 unsigned char protocol,
+                 unsigned char type) {
 
         assert(route);
         assert(src);
@@ -346,6 +387,7 @@ int route_update(Route *route,
         route->prefsrc = *prefsrc;
         route->scope = scope;
         route->protocol = protocol;
+        route->type = type;
 
         return 0;
 }
@@ -419,9 +461,15 @@ int route_remove(Route *route, Link *link,
         if (r < 0)
                 return log_error_errno(r, "Could not append RTA_PRIORITY attribute: %m");
 
-        r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
+        r = sd_rtnl_message_route_set_type(req, route->type);
         if (r < 0)
-                return log_error_errno(r, "Could not append RTA_OIF attribute: %m");
+                return log_error_errno(r, "Could not set route type: %m");
+
+        if (!IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE)) {
+                r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
+                if (r < 0)
+                        return log_error_errno(r, "Could not append RTA_OIF attribute: %m");
+        }
 
         r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
         if (r < 0)
@@ -440,20 +488,14 @@ static int route_expire_callback(sd_netlink *rtnl, sd_netlink_message *m, void *
         assert(m);
         assert(link);
         assert(link->ifname);
-        assert(link->link_messages > 0);
 
         if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
                 return 1;
 
-        link->link_messages--;
-
         r = sd_netlink_message_get_errno(m);
         if (r < 0 && r != -EEXIST)
                 log_link_warning_errno(link, r, "could not remove route: %m");
 
-        if (link->link_messages == 0)
-                log_link_debug(link, "route removed");
-
         return 1;
 }
 
@@ -466,11 +508,8 @@ int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) {
         r = route_remove(route, route->link, route_expire_callback);
         if (r < 0)
                 log_warning_errno(r, "Could not remove route: %m");
-        else {
-                /* route may not be exist in kernel. If we fail still remove it */
-                route->link->link_messages++;
+        else
                 route_free(route);
-        }
 
         return 1;
 }
@@ -492,7 +531,7 @@ int route_configure(
         assert(route->family == AF_INET || route->family == AF_INET6);
 
         if (route_get(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL) <= 0 &&
-            set_size(link->routes) >= ROUTES_PER_LINK_MAX)
+            set_size(link->routes) >= routes_max())
                 return -E2BIG;
 
         r = sd_rtnl_message_new_route(link->manager->rtnl, &req,
@@ -557,14 +596,12 @@ int route_configure(
         if (r < 0)
                 return log_error_errno(r, "Could not set flags: %m");
 
-        if (route->table != RT_TABLE_DEFAULT) {
-
+        if (route->table != RT_TABLE_MAIN) {
                 if (route->table < 256) {
                         r = sd_rtnl_message_route_set_table(req, route->table);
                         if (r < 0)
                                 return log_error_errno(r, "Could not set route table: %m");
                 } else {
-
                         r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC);
                         if (r < 0)
                                 return log_error_errno(r, "Could not set route table: %m");
@@ -584,9 +621,29 @@ int route_configure(
         if (r < 0)
                 return log_error_errno(r, "Could not append RTA_PREF attribute: %m");
 
-        r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
+        r = sd_rtnl_message_route_set_type(req, route->type);
+        if (r < 0)
+                return log_error_errno(r, "Could not set route type: %m");
+
+        if (!IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE)) {
+                r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
+                if (r < 0)
+                        return log_error_errno(r, "Could not append RTA_OIF attribute: %m");
+        }
+
+        r = sd_netlink_message_open_container(req, RTA_METRICS);
+        if (r < 0)
+                return log_error_errno(r, "Could not append RTA_METRICS attribute: %m");
+
+        if (route->mtu > 0) {
+                r = sd_netlink_message_append_u32(req, RTAX_MTU, route->mtu);
+                if (r < 0)
+                        return log_error_errno(r, "Could not append RTAX_MTU attribute: %m");
+        }
+
+        r = sd_netlink_message_close_container(req);
         if (r < 0)
-                return log_error_errno(r, "Could not append RTA_OIF attribute: %m");
+                return log_error_errno(r, "Could not append RTA_METRICS attribute: %m");
 
         r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
         if (r < 0)
@@ -642,10 +699,10 @@ int config_parse_gateway(const char *unit,
         if (streq(section, "Network")) {
                 /* we are not in an Route section, so treat
                  * this as the special '0' section */
-                section_line = 0;
-        }
+                r = route_new_static(network, NULL, 0, &n);
+        } else
+                r = route_new_static(network, filename, section_line, &n);
 
-        r = route_new_static(network, section_line, &n);
         if (r < 0)
                 return r;
 
@@ -684,7 +741,7 @@ int config_parse_preferred_src(const char *unit,
         assert(rvalue);
         assert(data);
 
-        r = route_new_static(network, section_line, &n);
+        r = route_new_static(network, filename, section_line, &n);
         if (r < 0)
                 return r;
 
@@ -715,10 +772,9 @@ int config_parse_destination(const char *unit,
 
         Network *network = userdata;
         _cleanup_route_free_ Route *n = NULL;
-        const char *address, *e;
         union in_addr_union buffer;
         unsigned char prefixlen;
-        int r, f;
+        int r;
 
         assert(filename);
         assert(section);
@@ -726,49 +782,24 @@ int config_parse_destination(const char *unit,
         assert(rvalue);
         assert(data);
 
-        r = route_new_static(network, section_line, &n);
+        r = route_new_static(network, filename, section_line, &n);
         if (r < 0)
                 return r;
 
-        /* Destination|Source=address/prefixlen */
-
-        /* address */
-        e = strchr(rvalue, '/');
-        if (e)
-                address = strndupa(rvalue, e - rvalue);
-        else
-                address = rvalue;
-
-        r = in_addr_from_string_auto(address, &f, &buffer);
+        r = in_addr_prefix_from_string(rvalue, AF_INET, &buffer, &prefixlen);
         if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r, "Destination is invalid, ignoring assignment: %s", address);
-                return 0;
-        }
-
-        if (f != AF_INET && f != AF_INET6) {
-                log_syntax(unit, LOG_ERR, filename, line, 0, "Unknown address family, ignoring assignment: %s", address);
-                return 0;
-        }
-
-        /* prefixlen */
-        if (e) {
-                r = safe_atou8(e + 1, &prefixlen);
+                r = in_addr_prefix_from_string(rvalue, AF_INET6, &buffer, &prefixlen);
                 if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, r, "Route destination prefix length is invalid, ignoring assignment: %s", e + 1);
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Route %s= prefix is invalid, ignoring assignment: %s",
+                                   lvalue, rvalue);
                         return 0;
                 }
-        } else {
-                switch (f) {
-                        case AF_INET:
-                                prefixlen = 32;
-                                break;
-                        case AF_INET6:
-                                prefixlen = 128;
-                                break;
-                }
-        }
 
-        n->family = f;
+                n->family = AF_INET6;
+        } else
+                n->family = AF_INET;
+
         if (streq(lvalue, "Destination")) {
                 n->dst = buffer;
                 n->dst_prefixlen = prefixlen;
@@ -804,7 +835,7 @@ int config_parse_route_priority(const char *unit,
         assert(rvalue);
         assert(data);
 
-        r = route_new_static(network, section_line, &n);
+        r = route_new_static(network, filename, section_line, &n);
         if (r < 0)
                 return r;
 
@@ -841,7 +872,7 @@ int config_parse_route_scope(const char *unit,
         assert(rvalue);
         assert(data);
 
-        r = route_new_static(network, section_line, &n);
+        r = route_new_static(network, filename, section_line, &n);
         if (r < 0)
                 return r;
 
@@ -882,7 +913,7 @@ int config_parse_route_table(const char *unit,
         assert(rvalue);
         assert(data);
 
-        r = route_new_static(network, section_line, &n);
+        r = route_new_static(network, filename, section_line, &n);
         if (r < 0)
                 return r;
 
@@ -899,3 +930,147 @@ int config_parse_route_table(const char *unit,
 
         return 0;
 }
+
+int config_parse_gateway_onlink(const char *unit,
+                                const char *filename,
+                                unsigned line,
+                                const char *section,
+                                unsigned section_line,
+                                const char *lvalue,
+                                int ltype,
+                                const char *rvalue,
+                                void *data,
+                                void *userdata) {
+        Network *network = userdata;
+        _cleanup_route_free_ Route *n = NULL;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = route_new_static(network, filename, section_line, &n);
+        if (r < 0)
+                return r;
+
+        r = parse_boolean(rvalue);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Could not parse gateway onlink \"%s\", ignoring assignment: %m", rvalue);
+                return 0;
+        }
+
+        SET_FLAG(n->flags, RTNH_F_ONLINK, r);
+        n = NULL;
+
+        return 0;
+}
+
+int config_parse_ipv6_route_preference(const char *unit,
+                                       const char *filename,
+                                       unsigned line,
+                                       const char *section,
+                                       unsigned section_line,
+                                       const char *lvalue,
+                                       int ltype,
+                                       const char *rvalue,
+                                       void *data,
+                                       void *userdata) {
+        Network *network = userdata;
+        _cleanup_route_free_ Route *n = NULL;
+        int r;
+
+        r = route_new_static(network, filename, section_line, &n);
+        if (r < 0)
+                return r;
+
+        if (streq(rvalue, "low"))
+                n->pref = ICMPV6_ROUTER_PREF_LOW;
+        else if (streq(rvalue, "medium"))
+                n->pref = ICMPV6_ROUTER_PREF_MEDIUM;
+        else if (streq(rvalue, "high"))
+                n->pref = ICMPV6_ROUTER_PREF_HIGH;
+        else {
+                log_syntax(unit, LOG_ERR, filename, line, 0, "Unknown route preference: %s", rvalue);
+                return 0;
+        }
+
+        n = NULL;
+
+        return 0;
+}
+
+int config_parse_route_protocol(const char *unit,
+                                const char *filename,
+                                unsigned line,
+                                const char *section,
+                                unsigned section_line,
+                                const char *lvalue,
+                                int ltype,
+                                const char *rvalue,
+                                void *data,
+                                void *userdata) {
+        Network *network = userdata;
+        _cleanup_route_free_ Route *n = NULL;
+        int r;
+
+        r = route_new_static(network, filename, section_line, &n);
+        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;
+        else {
+                r = safe_atou8(rvalue , &n->protocol);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse route protocol \"%s\", ignoring assignment: %m", rvalue);
+                        return 0;
+                }
+        }
+
+        n = NULL;
+
+        return 0;
+}
+
+int config_parse_route_type(const char *unit,
+                            const char *filename,
+                            unsigned line,
+                            const char *section,
+                            unsigned section_line,
+                            const char *lvalue,
+                            int ltype,
+                            const char *rvalue,
+                            void *data,
+                            void *userdata) {
+        Network *network = userdata;
+        _cleanup_route_free_ Route *n = NULL;
+        int 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 {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse route type \"%s\", ignoring assignment: %m", rvalue);
+                return 0;
+        }
+
+        n = NULL;
+
+        return 0;
+}