diff options
-rw-r--r-- | src/network/networkd-link.h | 2 | ||||
-rw-r--r-- | src/network/networkd-manager.c | 235 | ||||
-rw-r--r-- | src/network/networkd-route.c | 166 | ||||
-rw-r--r-- | src/network/networkd-route.h | 8 | ||||
-rw-r--r-- | src/network/networkd.c | 6 | ||||
-rw-r--r-- | src/network/networkd.h | 2 | ||||
-rw-r--r-- | units/systemd-networkd.socket | 2 |
7 files changed, 419 insertions, 2 deletions
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 2a69f1c16b..a22041870e 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -85,6 +85,8 @@ struct Link { Set *addresses; Set *addresses_foreign; + Set *routes; + Set *routes_foreign; sd_dhcp_client *dhcp_client; sd_dhcp_lease *dhcp_lease; diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 6b2a661ca7..a5701001c1 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -279,6 +279,196 @@ static int manager_connect_udev(Manager *m) { return 0; } +int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) { + Manager *m = userdata; + Link *link = NULL; + uint16_t type; + uint32_t ifindex, priority = 0; + unsigned char protocol, scope, tos, table; + int family; + unsigned char dst_prefixlen, src_prefixlen; + union in_addr_union dst = {}, gw = {}, src = {}, prefsrc = {}; + Route *route = NULL; + int r; + + assert(rtnl); + assert(message); + assert(m); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_warning_errno(r, "rtnl: failed to receive route: %m"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type: %m"); + return 0; + } else if (type != RTM_NEWROUTE && type != RTM_DELROUTE) { + log_warning("rtnl: received unexpected message type when processing route"); + return 0; + } + + r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); + if (r == -ENODATA) { + log_debug("rtnl: received route without ifindex, ignoring"); + return 0; + } else if (r < 0) { + log_warning_errno(r, "rtnl: could not get ifindex from route, ignoring: %m"); + return 0; + } else if (ifindex <= 0) { + log_warning("rtnl: received route message with invalid ifindex, ignoring: %d", ifindex); + return 0; + } else { + r = link_get(m, ifindex, &link); + if (r < 0 || !link) { + /* 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 nonexistent link (%d), ignoring", ifindex); + return 0; + } + } + + r = sd_rtnl_message_route_get_family(message, &family); + if (r < 0 || !IN_SET(family, AF_INET, AF_INET6)) { + log_link_warning(link, "rtnl: received address with invalid family, ignoring."); + return 0; + } + + r = sd_rtnl_message_route_get_protocol(message, &protocol); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get route protocol: %m"); + return 0; + } + + switch (family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(message, RTA_DST, &dst.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route without valid destination, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in_addr(message, RTA_GATEWAY, &gw.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route with invalid gateway, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in_addr(message, RTA_SRC, &src.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route with invalid source, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in_addr(message, RTA_PREFSRC, &prefsrc.in); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route with invalid preferred source, ignoring: %m"); + return 0; + } + + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(message, RTA_DST, &dst.in6); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route without valid destination, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in6_addr(message, RTA_GATEWAY, &gw.in6); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route with invalid gateway, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in6_addr(message, RTA_SRC, &src.in6); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route with invalid source, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_in6_addr(message, RTA_PREFSRC, &prefsrc.in6); + if (r < 0 && r != -ENODATA) { + log_link_warning_errno(link, r, "rtnl: received route with invalid preferred source, ignoring: %m"); + return 0; + } + + break; + + default: + log_link_debug(link, "rtnl: ignoring unsupported address family: %d", family); + return 0; + } + + 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"); + 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"); + 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"); + 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"); + 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"); + 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"); + return 0; + } + + route_get(link, family, &dst, dst_prefixlen, tos, priority, table, &route); + + switch (type) { + case RTM_NEWROUTE: + if (!route) { + /* A route appeared that we did not request */ + r = route_add_foreign(link, family, &dst, dst_prefixlen, tos, priority, table, &route); + if (r < 0) + return 0; + } + + route_update(route, &src, src_prefixlen, &gw, &prefsrc, scope, protocol); + + break; + + case RTM_DELROUTE: + + if (route) + route_drop(route); + + break; + default: + assert_not_reached("Received invalid RTNL message type"); + } + + return 1; +} + int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) { Manager *m = userdata; Link *link = NULL; @@ -377,7 +567,7 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, break; default: - assert_not_reached("invalid address family"); + log_link_debug(link, "rtnl: ignoring unsupported address family: %d", family); } if (!inet_ntop(family, &in_addr, buf, INET6_ADDRSTRLEN)) { @@ -572,6 +762,14 @@ static int manager_connect_rtnl(Manager *m) { if (r < 0) return r; + r = sd_netlink_add_match(m->rtnl, RTM_NEWROUTE, &manager_rtnl_process_route, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_DELROUTE, &manager_rtnl_process_route, m); + if (r < 0) + return r; + return 0; } @@ -1019,6 +1217,41 @@ int manager_rtnl_enumerate_addresses(Manager *m) { return r; } +int manager_rtnl_enumerate_routes(Manager *m) { + _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *route; + int r; + + assert(m); + assert(m->rtnl); + + r = sd_rtnl_message_new_route(m->rtnl, &req, RTM_GETROUTE, 0, 0); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (route = reply; route; route = sd_netlink_message_next(route)) { + int k; + + m->enumerating = true; + + k = manager_rtnl_process_route(m->rtnl, route, m); + if (k < 0) + r = k; + + m->enumerating = false; + } + + return r; +} + int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found) { AddressPool *p; int r; diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 078d9c1618..7c0d03cdc7 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -26,6 +26,7 @@ #include "networkd-route.h" #include "networkd.h" #include "parse-util.h" +#include "set.h" #include "string-util.h" #include "util.h" @@ -95,6 +96,11 @@ void route_free(Route *route) { UINT_TO_PTR(route->section)); } + if (route->link) { + set_remove(route->link->routes, route); + set_remove(route->link->routes_foreign, route); + } + free(route); } @@ -166,6 +172,162 @@ static const struct hash_ops route_hash_ops = { .compare = route_compare_func }; +int route_get(Link *link, + int family, + union in_addr_union *dst, + unsigned char dst_prefixlen, + unsigned char tos, + uint32_t priority, + unsigned char table, + Route **ret) { + Route route = { + .family = family, + .dst_prefixlen = dst_prefixlen, + .tos = tos, + .priority = priority, + .table = table, + }, *existing; + + assert(link); + assert(dst); + assert(ret); + + route.dst = *dst; + + existing = set_get(link->routes, &route); + if (existing) { + *ret = existing; + return 1; + } else { + existing = set_get(link->routes_foreign, &route); + if (!existing) + return -ENOENT; + } + + *ret = existing; + + return 0; +} + +static int route_add_internal(Link *link, Set **routes, + int family, + union in_addr_union *dst, + unsigned char dst_prefixlen, + unsigned char tos, + uint32_t priority, + unsigned char table, Route **ret) { + _cleanup_route_free_ Route *route = NULL; + int r; + + assert(link); + assert(routes); + assert(dst); + + r = route_new(&route); + if (r < 0) + return r; + + route->family = family; + route->dst = *dst; + route->dst_prefixlen = dst_prefixlen; + route->tos = tos; + route->priority = priority; + route->table = table; + + r = set_ensure_allocated(routes, &route_hash_ops); + if (r < 0) + return r; + + r = set_put(*routes, route); + if (r < 0) + return r; + + route->link = link; + + if (ret) + *ret = route; + + route = NULL; + + return 0; +} + +int route_add_foreign(Link *link, + int family, + union in_addr_union *dst, + unsigned char dst_prefixlen, + unsigned char tos, + uint32_t priority, + unsigned char table, Route **ret) { + return route_add_internal(link, &link->routes_foreign, family, dst, dst_prefixlen, tos, priority, table, ret); +} + +int route_add(Link *link, + int family, + union in_addr_union *dst, + unsigned char dst_prefixlen, + unsigned char tos, + uint32_t priority, + unsigned char table, Route **ret) { + Route *route; + int r; + + r = route_get(link, family, dst, dst_prefixlen, tos, priority, table, &route); + if (r == -ENOENT) { + /* Route does not exist, create a new one */ + r = route_add_internal(link, &link->routes, family, dst, dst_prefixlen, tos, priority, table, &route); + if (r < 0) + return r; + } else if (r == 0) { + /* Take over a foreign route */ + r = set_ensure_allocated(&link->routes, &route_hash_ops); + if (r < 0) + return r; + + r = set_put(link->routes, route); + if (r < 0) + return r; + + set_remove(link->routes_foreign, route); + } else if (r == 1) { + /* Route exists, do nothing */ + ; + } else + return r; + + *ret = route; + + return 0; +} + +int route_update(Route *route, + union in_addr_union *src, + unsigned char src_prefixlen, + union in_addr_union *gw, + union in_addr_union *prefsrc, + unsigned char scope, + unsigned char protocol) { + assert(route); + assert(src); + assert(gw); + assert(prefsrc); + + route->src = *src; + route->src_prefixlen = src_prefixlen; + route->gw = *gw; + route->prefsrc = *prefsrc; + route->scope = scope; + route->protocol = protocol; + + return 0; +} + +void route_drop(Route *route) { + assert(route); + + route_free(route); +} + int route_remove(Route *route, Link *link, sd_netlink_message_handler_t callback) { _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL; @@ -327,6 +489,10 @@ int route_configure(Route *route, Link *link, link_ref(link); + r = route_add(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL); + if (r < 0) + return log_error_errno(r, "Could not add route: %m"); + return 0; } diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index b1ed5d1085..785bd1663a 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -30,6 +30,8 @@ struct Route { Network *network; unsigned section; + Link *link; + int family; unsigned char dst_prefixlen; unsigned char src_prefixlen; @@ -53,6 +55,12 @@ void route_free(Route *route); int route_configure(Route *route, Link *link, sd_netlink_message_handler_t callback); int route_remove(Route *route, Link *link, sd_netlink_message_handler_t callback); +int route_get(Link *link, int family, union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret); +int route_add(Link *link, int family, union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret); +int route_add_foreign(Link *link, int family, union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret); +int route_update(Route *route, union in_addr_union *src, unsigned char src_prefixlen, union in_addr_union *gw, union in_addr_union *prefsrc, unsigned char scope, unsigned char protocol); +void route_drop(Route *route); + DEFINE_TRIVIAL_CLEANUP_FUNC(Route*, route_free); #define _cleanup_route_free_ _cleanup_(route_freep) diff --git a/src/network/networkd.c b/src/network/networkd.c index c03ac69e27..ef394e0c04 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -109,6 +109,12 @@ int main(int argc, char *argv[]) { goto out; } + r = manager_rtnl_enumerate_routes(m); + if (r < 0) { + log_error_errno(r, "Could not enumerate routes: %m"); + goto out; + } + log_info("Enumeration completed"); sd_notify(false, diff --git a/src/network/networkd.h b/src/network/networkd.h index 6c5a9939be..97665fac7a 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -82,8 +82,10 @@ bool manager_should_reload(Manager *m); int manager_rtnl_enumerate_links(Manager *m); int manager_rtnl_enumerate_addresses(Manager *m); +int manager_rtnl_enumerate_routes(Manager *m); int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, void *userdata); +int manager_rtnl_process_route(sd_netlink *nl, sd_netlink_message *message, void *userdata); int manager_send_changed(Manager *m, const char *property, ...) _sentinel_; void manager_dirty(Manager *m); diff --git a/units/systemd-networkd.socket b/units/systemd-networkd.socket index 2c20935d83..9e4e9dd338 100644 --- a/units/systemd-networkd.socket +++ b/units/systemd-networkd.socket @@ -14,7 +14,7 @@ Before=sockets.target [Socket] ReceiveBuffer=8M -ListenNetlink=route 273 +ListenNetlink=route 1361 PassCredentials=yes [Install] |