* Copyright © 2019 VMware, Inc.
*/
+/* Make sure the net/if.h header is included before any linux/ one */
#include <net/if.h>
#include <linux/nexthop.h>
#include "networkd-network.h"
#include "networkd-nexthop.h"
#include "networkd-queue.h"
+#include "networkd-route.h"
#include "networkd-route-util.h"
#include "parse-util.h"
#include "set.h"
#include "stdio-util.h"
#include "string-util.h"
-NextHop *nexthop_free(NextHop *nexthop) {
- if (!nexthop)
- return NULL;
+static void nexthop_detach_from_group_members(NextHop *nexthop) {
+ assert(nexthop);
+ assert(nexthop->manager);
+ assert(nexthop->id > 0);
+
+ struct nexthop_grp *nhg;
+ HASHMAP_FOREACH(nhg, nexthop->group) {
+ NextHop *nh;
+
+ if (nexthop_get_by_id(nexthop->manager, nhg->id, &nh) < 0)
+ continue;
+
+ set_remove(nh->nexthops, UINT32_TO_PTR(nexthop->id));
+ }
+}
+
+static void nexthop_attach_to_group_members(NextHop *nexthop) {
+ int r;
+
+ assert(nexthop);
+ assert(nexthop->manager);
+ assert(nexthop->id > 0);
+
+ struct nexthop_grp *nhg;
+ HASHMAP_FOREACH(nhg, nexthop->group) {
+ NextHop *nh;
+
+ r = nexthop_get_by_id(nexthop->manager, nhg->id, &nh);
+ if (r < 0) {
+ if (nexthop->manager->manage_foreign_nexthops)
+ log_debug_errno(r, "Nexthop (id=%"PRIu32") has unknown group member (%"PRIu32"), ignoring.",
+ nexthop->id, nhg->id);
+ continue;
+ }
+
+ r = set_ensure_put(&nh->nexthops, NULL, UINT32_TO_PTR(nexthop->id));
+ if (r < 0)
+ log_debug_errno(r, "Failed to save nexthop ID (%"PRIu32") to group member (%"PRIu32"), ignoring: %m",
+ nexthop->id, nhg->id);
+ }
+}
+
+static NextHop* nexthop_detach_impl(NextHop *nexthop) {
+ assert(nexthop);
+ assert(!nexthop->manager || !nexthop->network);
if (nexthop->network) {
assert(nexthop->section);
- hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section);
+ ordered_hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section);
+ nexthop->network = NULL;
+ return nexthop;
}
- config_section_free(nexthop->section);
-
if (nexthop->manager) {
assert(nexthop->id > 0);
+
+ nexthop_detach_from_group_members(nexthop);
+
hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
+ nexthop->manager = NULL;
+ return nexthop;
}
+ return NULL;
+}
+
+static void nexthop_detach(NextHop *nexthop) {
+ nexthop_unref(nexthop_detach_impl(nexthop));
+}
+
+static NextHop* nexthop_free(NextHop *nexthop) {
+ if (!nexthop)
+ return NULL;
+
+ nexthop_detach_impl(nexthop);
+
+ config_section_free(nexthop->section);
hashmap_free_free(nexthop->group);
+ set_free(nexthop->nexthops);
+ set_free(nexthop->routes);
return mfree(nexthop);
}
-DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_free);
+DEFINE_TRIVIAL_REF_UNREF_FUNC(NextHop, nexthop, nexthop_free);
+DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_unref);
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
nexthop_hash_ops,
trivial_hash_func,
trivial_compare_func,
NextHop,
- nexthop_free);
+ nexthop_detach);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ nexthop_section_hash_ops,
+ ConfigSection,
+ config_section_hash_func,
+ config_section_compare_func,
+ NextHop,
+ nexthop_detach);
static int nexthop_new(NextHop **ret) {
- _cleanup_(nexthop_freep) NextHop *nexthop = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL;
nexthop = new(NextHop, 1);
if (!nexthop)
return -ENOMEM;
*nexthop = (NextHop) {
- .family = AF_UNSPEC,
+ .n_ref = 1,
.onlink = -1,
};
static int nexthop_new_static(Network *network, const char *filename, unsigned section_line, NextHop **ret) {
_cleanup_(config_section_freep) ConfigSection *n = NULL;
- _cleanup_(nexthop_freep) NextHop *nexthop = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL;
int r;
assert(network);
if (r < 0)
return r;
- nexthop = hashmap_get(network->nexthops_by_section, n);
+ nexthop = ordered_hashmap_get(network->nexthops_by_section, n);
if (nexthop) {
*ret = TAKE_PTR(nexthop);
return 0;
nexthop->section = TAKE_PTR(n);
nexthop->source = NETWORK_CONFIG_SOURCE_STATIC;
- r = hashmap_ensure_put(&network->nexthops_by_section, &config_section_hash_ops, nexthop->section, nexthop);
+ r = ordered_hashmap_ensure_put(&network->nexthops_by_section, &nexthop_section_hash_ops, nexthop->section, nexthop);
if (r < 0)
return r;
assert(nexthop);
assert(state);
- siphash24_compress(&nexthop->id, sizeof(nexthop->id), state);
+ siphash24_compress_typesafe(nexthop->id, state);
}
static int nexthop_compare_func(const NextHop *a, const NextHop *b) {
}
static int nexthop_dup(const NextHop *src, NextHop **ret) {
- _cleanup_(nexthop_freep) NextHop *dest = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *dest = NULL;
struct nexthop_grp *nhg;
int r;
if (!dest)
return -ENOMEM;
- /* unset all pointers */
+ /* clear the reference counter and all pointers */
+ dest->n_ref = 1;
dest->manager = NULL;
dest->network = NULL;
dest->section = NULL;
if (nexthop_compare_full(nexthop, in) != 0)
continue;
+ /* Even if the configuration matches, it may be configured with another [NextHop] section
+ * that has an explicit ID. If so, the assigned nexthop is not the one we are looking for. */
+ if (set_contains(link->manager->nexthop_ids, UINT32_TO_PTR(nexthop->id)))
+ continue;
+
if (ret)
*ret = nexthop;
return 0;
assert(manager);
+ if (id == 0)
+ return -EINVAL;
+
req = ordered_set_get(
manager->request_queue,
&(Request) {
if (nexthop_compare_full(nexthop, in) != 0)
continue;
+ /* Even if the configuration matches, it may be requested by another [NextHop] section
+ * that has an explicit ID. If so, the request is not the one we are looking for. */
+ if (set_contains(link->manager->nexthop_ids, UINT32_TO_PTR(nexthop->id)))
+ continue;
+
if (ret)
*ret = req;
return 0;
}
static int nexthop_add_new(Manager *manager, uint32_t id, NextHop **ret) {
- _cleanup_(nexthop_freep) NextHop *nexthop = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL;
int r;
assert(manager);
}
static int nexthop_acquire_id(Manager *manager, NextHop *nexthop) {
- _cleanup_set_free_ Set *ids = NULL;
- Network *network;
- int r;
-
assert(manager);
assert(nexthop);
/* Find the lowest unused ID. */
- ORDERED_HASHMAP_FOREACH(network, manager->networks) {
- NextHop *tmp;
-
- HASHMAP_FOREACH(tmp, network->nexthops_by_section) {
- if (tmp->id == 0)
- continue;
-
- r = set_ensure_put(&ids, NULL, UINT32_TO_PTR(tmp->id));
- if (r < 0)
- return r;
- }
- }
-
for (uint32_t id = 1; id < UINT32_MAX; id++) {
if (nexthop_get_by_id(manager, id, NULL) >= 0)
continue;
if (nexthop_get_request_by_id(manager, id, NULL) >= 0)
continue;
- if (set_contains(ids, UINT32_TO_PTR(id)))
+ if (set_contains(manager->nexthop_ids, UINT32_TO_PTR(id)))
continue;
nexthop->id = id;
yes_no(nexthop->blackhole), strna(group), strna(flags));
}
-static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int nexthop_remove_dependents(NextHop *nexthop, Manager *manager) {
+ int r = 0;
+
+ assert(nexthop);
+ assert(manager);
+
+ /* If a nexthop is removed, the kernel silently removes nexthops and routes that depend on the
+ * removed nexthop. Let's remove them for safety (though, they are already removed in the kernel,
+ * hence that should fail), and forget them. */
+
+ void *id;
+ SET_FOREACH(id, nexthop->nexthops) {
+ NextHop *nh;
+
+ if (nexthop_get_by_id(manager, PTR_TO_UINT32(id), &nh) < 0)
+ continue;
+
+ RET_GATHER(r, nexthop_remove(nh, manager));
+ }
+
+ Route *route;
+ SET_FOREACH(route, nexthop->routes)
+ RET_GATHER(r, route_remove(route, manager));
+
+ return r;
+}
+
+static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) {
int r;
assert(m);
+ assert(rreq);
- /* link may be NULL. */
-
- if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
- return 1;
+ Manager *manager = ASSERT_PTR(rreq->manager);
+ NextHop *nexthop = ASSERT_PTR(rreq->userdata);
r = sd_netlink_message_get_errno(m);
- if (r < 0 && r != -ENOENT)
- log_link_message_warning_errno(link, m, r, "Could not drop nexthop, ignoring");
+ if (r < 0) {
+ log_message_full_errno(m,
+ (r == -ENOENT || !nexthop->manager) ? LOG_DEBUG : LOG_WARNING,
+ r, "Could not drop nexthop, ignoring");
+
+ (void) nexthop_remove_dependents(nexthop, manager);
+
+ if (nexthop->manager) {
+ /* If the nexthop cannot be removed, then assume the nexthop is already removed. */
+ log_nexthop_debug(nexthop, "Forgetting", manager);
+
+ Request *req;
+ if (nexthop_get_request_by_id(manager, nexthop->id, &req) >= 0)
+ nexthop_enter_removed(req->userdata);
+
+ nexthop_detach(nexthop);
+ }
+ }
return 1;
}
-static int nexthop_remove(NextHop *nexthop) {
+int nexthop_remove(NextHop *nexthop, Manager *manager) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
- Manager *manager;
Link *link = NULL;
- Request *req;
int r;
assert(nexthop);
assert(nexthop->id > 0);
+ assert(manager);
- manager = ASSERT_PTR(nexthop->manager);
+ /* If the nexthop is remembered, then use the remembered object. */
+ (void) nexthop_get_by_id(manager, PTR_TO_UINT32(nexthop->id), &nexthop);
/* link may be NULL. */
(void) link_get_by_index(manager, nexthop->ifindex, &link);
if (r < 0)
return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m");
- r = netlink_call_async(manager->rtnl, NULL, m, nexthop_remove_handler,
- link ? link_netlink_destroy_callback : NULL, link);
+ r = manager_remove_request_add(manager, nexthop, nexthop, manager->rtnl, m, nexthop_remove_handler);
if (r < 0)
- return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
-
- link_ref(link); /* link may be NULL, link_ref() is OK with that */
+ return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m");
nexthop_enter_removing(nexthop);
- if (nexthop_get_request_by_id(manager, nexthop->id, &req) >= 0)
- nexthop_enter_removing(req->userdata);
-
return 0;
}
return 1;
}
+int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret) {
+ NextHop *nexthop;
+
+ assert(manager);
+
+ if (id == 0)
+ return -EINVAL;
+
+ if (nexthop_get_request_by_id(manager, id, NULL) >= 0)
+ goto not_ready;
+
+ if (nexthop_get_by_id(manager, id, &nexthop) < 0)
+ goto not_ready;
+
+ if (!nexthop_exists(nexthop))
+ goto not_ready;
+
+ if (ret)
+ *ret = nexthop;
+
+ return true;
+
+not_ready:
+ if (ret)
+ *ret = NULL;
+
+ return false;
+}
+
static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) {
struct nexthop_grp *nhg;
+ int r;
assert(link);
assert(nexthop);
/* All group members must be configured first. */
HASHMAP_FOREACH(nhg, nexthop->group) {
- NextHop *g;
-
- if (nexthop_get_by_id(link->manager, nhg->id, &g) < 0)
- return false;
-
- if (!nexthop_exists(g))
- return false;
+ r = nexthop_is_ready(link->manager, nhg->id, NULL);
+ if (r <= 0)
+ return r;
}
return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw);
}
static int link_request_nexthop(Link *link, const NextHop *nexthop) {
- _cleanup_(nexthop_freep) NextHop *tmp = NULL;
+ _cleanup_(nexthop_unrefp) NextHop *tmp = NULL;
NextHop *existing = NULL;
int r;
log_nexthop_debug(tmp, "Requesting", link->manager);
r = link_queue_request_safe(link, REQUEST_TYPE_NEXTHOP,
tmp,
- nexthop_free,
+ nexthop_unref,
nexthop_hash_func,
nexthop_compare_func,
nexthop_process_request,
link->static_nexthops_configured = false;
- HASHMAP_FOREACH(nh, link->network->nexthops_by_section) {
+ ORDERED_HASHMAP_FOREACH(nh, link->network->nexthops_by_section) {
if (only_ipv4 && nh->family != AF_INET)
continue;
continue;
/* When 'foreign' is true, mark only foreign nexthops, and vice versa. */
- if (foreign != (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN))
+ if (nexthop->source != (foreign ? NETWORK_CONFIG_SOURCE_FOREIGN : NETWORK_CONFIG_SOURCE_STATIC))
continue;
/* Ignore nexthops not assigned yet or already removed. */
if (!IN_SET(other->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
continue;
- HASHMAP_FOREACH(nexthop, other->network->nexthops_by_section) {
+ ORDERED_HASHMAP_FOREACH(nexthop, other->network->nexthops_by_section) {
NextHop *existing;
if (nexthop_get(other, nexthop, &existing) < 0)
if (!nexthop_is_marked(nexthop))
continue;
- RET_GATHER(r, nexthop_remove(nexthop));
+ RET_GATHER(r, nexthop_remove(nexthop, link->manager));
}
return r;
}
}
-static int nexthop_update_group(NextHop *nexthop, const struct nexthop_grp *group, size_t size) {
+static int nexthop_update_group(NextHop *nexthop, sd_netlink_message *message) {
_cleanup_hashmap_free_free_ Hashmap *h = NULL;
- size_t n_group;
+ _cleanup_free_ struct nexthop_grp *group = NULL;
+ size_t size = 0, n_group;
int r;
assert(nexthop);
- assert(group || size == 0);
+ assert(message);
+
+ r = sd_netlink_message_read_data(message, NHA_GROUP, &size, (void**) &group);
+ if (r < 0 && r != -ENODATA)
+ return log_debug_errno(r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
- if (size == 0 || size % sizeof(struct nexthop_grp) != 0)
+ nexthop_detach_from_group_members(nexthop);
+
+ if (size % sizeof(struct nexthop_grp) != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"rtnl: received nexthop message with invalid nexthop group size, ignoring.");
hashmap_free_free(nexthop->group);
nexthop->group = TAKE_PTR(h);
+
+ nexthop_attach_to_group_members(nexthop);
return 0;
}
int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
- _cleanup_free_ void *raw_group = NULL;
- size_t raw_group_size;
uint16_t type;
uint32_t id, ifindex;
NextHop *nexthop = NULL;
if (nexthop) {
nexthop_enter_removed(nexthop);
log_nexthop_debug(nexthop, "Forgetting removed", m);
- nexthop_free(nexthop);
+ (void) nexthop_remove_dependents(nexthop, m);
+ nexthop_detach(nexthop);
} else
log_nexthop_debug(&(const NextHop) { .id = id }, "Kernel removed unknown", m);
if (r < 0)
log_debug_errno(r, "rtnl: could not get nexthop flags, ignoring: %m");
- r = sd_netlink_message_read_data(message, NHA_GROUP, &raw_group_size, &raw_group);
- if (r == -ENODATA)
- nexthop->group = hashmap_free_free(nexthop->group);
- else if (r < 0)
- log_debug_errno(r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
- else
- (void) nexthop_update_group(nexthop, raw_group, raw_group_size);
+ (void) nexthop_update_group(nexthop, message);
if (nexthop->family != AF_UNSPEC) {
r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, nexthop->family, &nexthop->gw);
"Ignoring [NextHop] section from line %u.",
nh->section->filename, nh->section->line);
- if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw))
+ if (nh->blackhole)
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: nexthop group cannot be a blackhole. "
"Ignoring [NextHop] section from line %u.",
nh->section->filename, nh->section->line);
+
+ if (nh->onlink > 0)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: nexthop group cannot have on-link flag. "
+ "Ignoring [NextHop] section from line %u.",
+ nh->section->filename, nh->section->line);
} else if (nh->family == AF_UNSPEC)
/* When neither Family=, Gateway=, nor Group= is specified, assume IPv4. */
nh->family = AF_INET;
- if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw))
- return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
- "%s: blackhole nexthop cannot have gateway address. "
- "Ignoring [NextHop] section from line %u.",
- nh->section->filename, nh->section->line);
+ if (nh->blackhole) {
+ if (in_addr_is_set(nh->family, &nh->gw))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: blackhole nexthop cannot have gateway address. "
+ "Ignoring [NextHop] section from line %u.",
+ nh->section->filename, nh->section->line);
+
+ if (nh->onlink > 0)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: blackhole nexthop cannot have on-link flag. "
+ "Ignoring [NextHop] section from line %u.",
+ nh->section->filename, nh->section->line);
+ }
if (nh->onlink < 0 && in_addr_is_set(nh->family, &nh->gw) &&
ordered_hashmap_isempty(nh->network->addresses_by_section)) {
return 0;
}
-void network_drop_invalid_nexthops(Network *network) {
+int network_drop_invalid_nexthops(Network *network) {
+ _cleanup_hashmap_free_ Hashmap *nexthops = NULL;
NextHop *nh;
+ int r;
assert(network);
- HASHMAP_FOREACH(nh, network->nexthops_by_section)
- if (nexthop_section_verify(nh) < 0)
- nexthop_free(nh);
+ ORDERED_HASHMAP_FOREACH(nh, network->nexthops_by_section) {
+ if (nexthop_section_verify(nh) < 0) {
+ nexthop_detach(nh);
+ continue;
+ }
+
+ if (nh->id == 0)
+ continue;
+
+ /* Always use the setting specified later. So, remove the previously assigned setting. */
+ NextHop *dup = hashmap_remove(nexthops, UINT32_TO_PTR(nh->id));
+ if (dup) {
+ log_warning("%s: Duplicated nexthop settings for ID %"PRIu32" is specified at line %u and %u, "
+ "dropping the nexthop setting specified at line %u.",
+ dup->section->filename,
+ nh->id, nh->section->line,
+ dup->section->line, dup->section->line);
+ /* nexthop_detach() will drop the nexthop from nexthops_by_section. */
+ nexthop_detach(dup);
+ }
+
+ r = hashmap_ensure_put(&nexthops, NULL, UINT32_TO_PTR(nh->id), nh);
+ if (r < 0)
+ return log_oom();
+ assert(r > 0);
+ }
+
+ return 0;
+}
+
+int manager_build_nexthop_ids(Manager *manager) {
+ Network *network;
+ int r;
+
+ assert(manager);
+
+ if (!manager->manage_foreign_nexthops)
+ return 0;
+
+ manager->nexthop_ids = set_free(manager->nexthop_ids);
+
+ ORDERED_HASHMAP_FOREACH(network, manager->networks) {
+ NextHop *nh;
+
+ ORDERED_HASHMAP_FOREACH(nh, network->nexthops_by_section) {
+ if (nh->id == 0)
+ continue;
+
+ r = set_ensure_put(&manager->nexthop_ids, NULL, UINT32_TO_PTR(nh->id));
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return 0;
}
int config_parse_nexthop_id(
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
uint32_t id;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
AddressFamily a;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;
void *data,
void *userdata) {
- _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL;
Network *network = userdata;
int r;