| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include "networkd-address.h" |
| #include "networkd-address-label.h" |
| #include "networkd-bridge-fdb.h" |
| #include "networkd-bridge-mdb.h" |
| #include "networkd-dhcp-server.h" |
| #include "networkd-ipv6-proxy-ndp.h" |
| #include "networkd-manager.h" |
| #include "networkd-neighbor.h" |
| #include "networkd-nexthop.h" |
| #include "networkd-route.h" |
| #include "networkd-routing-policy-rule.h" |
| #include "networkd-queue.h" |
| #include "networkd-setlink.h" |
| |
| static void request_free_object(RequestType type, void *object) { |
| switch(type) { |
| case REQUEST_TYPE_ACTIVATE_LINK: |
| break; |
| case REQUEST_TYPE_ADDRESS: |
| address_free(object); |
| break; |
| case REQUEST_TYPE_ADDRESS_LABEL: |
| address_label_free(object); |
| break; |
| case REQUEST_TYPE_BRIDGE_FDB: |
| bridge_fdb_free(object); |
| break; |
| case REQUEST_TYPE_BRIDGE_MDB: |
| bridge_mdb_free(object); |
| break; |
| case REQUEST_TYPE_CREATE_STACKED_NETDEV: |
| break; |
| case REQUEST_TYPE_DHCP_SERVER: |
| break; |
| case REQUEST_TYPE_IPV6_PROXY_NDP: |
| free(object); |
| break; |
| case REQUEST_TYPE_NEIGHBOR: |
| neighbor_free(object); |
| break; |
| case REQUEST_TYPE_NEXTHOP: |
| nexthop_free(object); |
| break; |
| case REQUEST_TYPE_ROUTE: |
| route_free(object); |
| break; |
| case REQUEST_TYPE_ROUTING_POLICY_RULE: |
| routing_policy_rule_free(object); |
| break; |
| case REQUEST_TYPE_SET_LINK: |
| case REQUEST_TYPE_UP_DOWN: |
| break; |
| default: |
| assert_not_reached("invalid request type."); |
| } |
| } |
| |
| static Request *request_free(Request *req) { |
| if (!req) |
| return NULL; |
| |
| if (req->link && req->link->manager) |
| /* To prevent from triggering assertions in hash functions, remove this request before |
| * freeing object below. */ |
| ordered_set_remove(req->link->manager->request_queue, req); |
| if (req->on_free) |
| /* on_free() may use object. So, let's call this earlier. */ |
| req->on_free(req); |
| if (req->consume_object) |
| request_free_object(req->type, req->object); |
| link_unref(req->link); |
| |
| return mfree(req); |
| } |
| |
| DEFINE_TRIVIAL_CLEANUP_FUNC(Request*, request_free); |
| |
| void request_drop(Request *req) { |
| if (!req) |
| return; |
| |
| if (req->message_counter) |
| (*req->message_counter)--; |
| |
| request_free(req); |
| } |
| |
| static void request_hash_func(const Request *req, struct siphash *state) { |
| assert(req); |
| assert(req->link); |
| assert(state); |
| |
| siphash24_compress(&req->link->ifindex, sizeof(req->link->ifindex), state); |
| siphash24_compress(&req->type, sizeof(req->type), state); |
| |
| switch(req->type) { |
| case REQUEST_TYPE_ACTIVATE_LINK: |
| break; |
| case REQUEST_TYPE_ADDRESS: |
| address_hash_func(req->address, state); |
| break; |
| case REQUEST_TYPE_ADDRESS_LABEL: |
| case REQUEST_TYPE_BRIDGE_FDB: |
| case REQUEST_TYPE_BRIDGE_MDB: |
| case REQUEST_TYPE_CREATE_STACKED_NETDEV: |
| /* TODO: Currently, these types do not have any specific hash and compare functions. |
| * Fortunately, all these objects are 'static', thus we can use the trivial functions. */ |
| trivial_hash_func(req->object, state); |
| break; |
| case REQUEST_TYPE_DHCP_SERVER: |
| /* This type does not have object. */ |
| break; |
| case REQUEST_TYPE_IPV6_PROXY_NDP: |
| in6_addr_hash_func(req->ipv6_proxy_ndp, state); |
| break; |
| case REQUEST_TYPE_NEIGHBOR: |
| neighbor_hash_func(req->neighbor, state); |
| break; |
| case REQUEST_TYPE_NEXTHOP: |
| nexthop_hash_func(req->nexthop, state); |
| break; |
| case REQUEST_TYPE_ROUTE: |
| route_hash_func(req->route, state); |
| break; |
| case REQUEST_TYPE_ROUTING_POLICY_RULE: |
| routing_policy_rule_hash_func(req->rule, state); |
| break; |
| case REQUEST_TYPE_SET_LINK: { |
| trivial_hash_func(req->set_link_operation_ptr, state); |
| break; |
| } |
| case REQUEST_TYPE_UP_DOWN: |
| break; |
| default: |
| assert_not_reached("invalid request type."); |
| } |
| } |
| |
| static int request_compare_func(const struct Request *a, const struct Request *b) { |
| int r; |
| |
| assert(a); |
| assert(b); |
| assert(a->link); |
| assert(b->link); |
| |
| r = CMP(a->link->ifindex, b->link->ifindex); |
| if (r != 0) |
| return r; |
| |
| r = CMP(a->type, b->type); |
| if (r != 0) |
| return r; |
| |
| switch (a->type) { |
| case REQUEST_TYPE_ACTIVATE_LINK: |
| return 0; |
| case REQUEST_TYPE_ADDRESS: |
| return address_compare_func(a->address, b->address); |
| case REQUEST_TYPE_ADDRESS_LABEL: |
| case REQUEST_TYPE_BRIDGE_FDB: |
| case REQUEST_TYPE_BRIDGE_MDB: |
| case REQUEST_TYPE_CREATE_STACKED_NETDEV: |
| return trivial_compare_func(a->object, b->object); |
| case REQUEST_TYPE_DHCP_SERVER: |
| return 0; |
| case REQUEST_TYPE_IPV6_PROXY_NDP: |
| return in6_addr_compare_func(a->ipv6_proxy_ndp, b->ipv6_proxy_ndp); |
| case REQUEST_TYPE_NEIGHBOR: |
| return neighbor_compare_func(a->neighbor, b->neighbor); |
| case REQUEST_TYPE_NEXTHOP: |
| return nexthop_compare_func(a->nexthop, b->nexthop); |
| case REQUEST_TYPE_ROUTE: |
| return route_compare_func(a->route, b->route); |
| case REQUEST_TYPE_ROUTING_POLICY_RULE: |
| return routing_policy_rule_compare_func(a->rule, b->rule); |
| case REQUEST_TYPE_SET_LINK: |
| return trivial_compare_func(a->set_link_operation_ptr, b->set_link_operation_ptr); |
| case REQUEST_TYPE_UP_DOWN: |
| return 0; |
| default: |
| assert_not_reached("invalid request type."); |
| } |
| } |
| |
| DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( |
| request_hash_ops, |
| Request, |
| request_hash_func, |
| request_compare_func, |
| request_free); |
| |
| int link_queue_request( |
| Link *link, |
| RequestType type, |
| void *object, |
| bool consume_object, |
| unsigned *message_counter, |
| link_netlink_message_handler_t netlink_handler, |
| Request **ret) { |
| |
| _cleanup_(request_freep) Request *req = NULL; |
| Request *existing; |
| int r; |
| |
| assert(link); |
| assert(link->manager); |
| assert(type >= 0 && type < _REQUEST_TYPE_MAX); |
| assert(IN_SET(type, |
| REQUEST_TYPE_ACTIVATE_LINK, |
| REQUEST_TYPE_DHCP_SERVER, |
| REQUEST_TYPE_SET_LINK, |
| REQUEST_TYPE_UP_DOWN) || |
| object); |
| assert(type == REQUEST_TYPE_DHCP_SERVER || netlink_handler); |
| |
| req = new(Request, 1); |
| if (!req) { |
| if (consume_object) |
| request_free_object(type, object); |
| return -ENOMEM; |
| } |
| |
| *req = (Request) { |
| .link = link_ref(link), |
| .type = type, |
| .object = object, |
| .consume_object = consume_object, |
| .message_counter = message_counter, |
| .netlink_handler = netlink_handler, |
| }; |
| |
| existing = ordered_set_get(link->manager->request_queue, req); |
| if (existing) { |
| /* To prevent from removing the existing request. */ |
| req->link = link_unref(req->link); |
| |
| if (ret) |
| *ret = existing; |
| return 0; |
| } |
| |
| r = ordered_set_ensure_put(&link->manager->request_queue, &request_hash_ops, req); |
| if (r < 0) |
| return r; |
| |
| if (req->message_counter) |
| (*req->message_counter)++; |
| |
| if (ret) |
| *ret = req; |
| |
| TAKE_PTR(req); |
| return 1; |
| } |
| |
| int manager_process_requests(sd_event_source *s, void *userdata) { |
| Manager *manager = userdata; |
| int r; |
| |
| assert(manager); |
| |
| for (;;) { |
| bool processed = false; |
| Request *req; |
| |
| ORDERED_SET_FOREACH(req, manager->request_queue) { |
| switch(req->type) { |
| case REQUEST_TYPE_ACTIVATE_LINK: |
| r = request_process_activation(req); |
| break; |
| case REQUEST_TYPE_ADDRESS: |
| r = request_process_address(req); |
| break; |
| case REQUEST_TYPE_ADDRESS_LABEL: |
| r = request_process_address_label(req); |
| break; |
| case REQUEST_TYPE_BRIDGE_FDB: |
| r = request_process_bridge_fdb(req); |
| break; |
| case REQUEST_TYPE_BRIDGE_MDB: |
| r = request_process_bridge_mdb(req); |
| break; |
| case REQUEST_TYPE_CREATE_STACKED_NETDEV: |
| r = request_process_create_stacked_netdev(req); |
| break; |
| case REQUEST_TYPE_DHCP_SERVER: |
| r = request_process_dhcp_server(req); |
| break; |
| case REQUEST_TYPE_IPV6_PROXY_NDP: |
| r = request_process_ipv6_proxy_ndp_address(req); |
| break; |
| case REQUEST_TYPE_NEIGHBOR: |
| r = request_process_neighbor(req); |
| break; |
| case REQUEST_TYPE_NEXTHOP: |
| r = request_process_nexthop(req); |
| break; |
| case REQUEST_TYPE_ROUTE: |
| r = request_process_route(req); |
| break; |
| case REQUEST_TYPE_ROUTING_POLICY_RULE: |
| r = request_process_routing_policy_rule(req); |
| break; |
| case REQUEST_TYPE_SET_LINK: |
| r = request_process_set_link(req); |
| break; |
| case REQUEST_TYPE_UP_DOWN: |
| r = request_process_link_up_or_down(req); |
| break; |
| default: |
| return -EINVAL; |
| } |
| if (r < 0) |
| link_enter_failed(req->link); |
| if (r > 0) { |
| ordered_set_remove(manager->request_queue, req); |
| request_free(req); |
| processed = true; |
| } |
| } |
| |
| if (!processed) |
| break; |
| } |
| |
| return 0; |
| } |