From 2291cf06267fbcfd67af71c67f12ff80a111ef0e Mon Sep 17 00:00:00 2001 From: gvenugo3 Date: Wed, 19 Nov 2025 20:35:03 -0700 Subject: [PATCH] network: implement varlink LinkUp and LinkDown methods The new varlink methods are basically equivalent to 'ip link set INTERFACE up/down', but they support polkit authentication. Also, on LinkDown, it gracefully stops dynamic engines like DHCP client/server before the interface is bring down. Hence, e.g. an empty RA on stop should be sent. Co-authored-by: Yu Watanabe --- src/network/networkd-manager-varlink.c | 54 ++++++++++- src/network/networkd-setlink.c | 99 +++++++++++++++++++++ src/network/networkd-setlink.h | 1 + src/network/org.freedesktop.network1.policy | 11 +++ src/shared/varlink-io.systemd.Network.c | 21 +++++ 5 files changed, 183 insertions(+), 3 deletions(-) diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 5e0d8866857..41f1d9b9a58 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -16,6 +16,7 @@ #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-manager-varlink.h" +#include "networkd-setlink.h" #include "stat-util.h" #include "varlink-io.systemd.Network.h" #include "varlink-io.systemd.service.h" @@ -92,7 +93,7 @@ static int vl_method_get_namespace_id(sd_varlink *link, sd_json_variant *paramet SD_JSON_BUILD_PAIR_CONDITION(nsid != UINT32_MAX, "NamespaceNSID", SD_JSON_BUILD_UNSIGNED(nsid))); } -static int dispatch_interface(sd_varlink *vlink, sd_json_variant *parameters, Manager *manager, Link **ret) { +static int dispatch_interface(sd_varlink *vlink, sd_json_variant *parameters, Manager *manager, bool polkit, Link **ret) { struct { int ifindex; const char *ifname; @@ -104,13 +105,18 @@ static int dispatch_interface(sd_varlink *vlink, sd_json_variant *parameters, Ma { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, voffsetof(info, ifindex), SD_JSON_RELAX }, { "InterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(info, ifname), 0 }, {} + }, dispatch_polkit_table[] = { + { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, voffsetof(info, ifindex), SD_JSON_RELAX }, + { "InterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(info, ifname), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} }; assert(vlink); assert(manager); assert(ret); - r = sd_varlink_dispatch(vlink, parameters, dispatch_table, &info); + r = sd_varlink_dispatch(vlink, parameters, polkit ? dispatch_polkit_table : dispatch_table, &info); if (r != 0) return r; @@ -157,7 +163,7 @@ static int vl_method_get_lldp_neighbors(sd_varlink *vlink, sd_json_variant *para assert(vlink); assert(manager); - r = dispatch_interface(vlink, parameters, manager, &link); + r = dispatch_interface(vlink, parameters, manager, /* polkit= */ false, &link); if (r != 0) return r; @@ -278,6 +284,46 @@ static int vl_method_set_persistent_storage(sd_varlink *vlink, sd_json_variant * return sd_varlink_reply(vlink, NULL); } +static int vl_method_link_up_or_down(sd_varlink *vlink, sd_json_variant *parameters, Manager *manager, bool up) { + Link *link; + int r; + + assert(vlink); + assert(manager); + + r = dispatch_interface(vlink, parameters, manager, /* polkit= */ true, &link); + if (r != 0) + return r; + + /* Require a specific link to be specified. */ + if (!link) + return sd_varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceIndex")); + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.manage-links", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + if (!up) + /* Stop all network engines while interface is still up to allow proper cleanup, + * e.g. sending IPv6 shutdown RA messages before the interface is brought down. */ + (void) link_stop_engines(link, /* may_keep_dynamic = */ false); + + return link_up_or_down_now_by_varlink(link, up, vlink); +} + +static int vl_method_link_up(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_method_link_up_or_down(vlink, parameters, userdata, /* up= */ true); +} + +static int vl_method_link_down(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_method_link_up_or_down(vlink, parameters, userdata, /* up= */ false); +} + int manager_varlink_init(Manager *m, int fd) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; _unused_ _cleanup_close_ int fd_close = fd; /* take possession */ @@ -312,6 +358,8 @@ int manager_varlink_init(Manager *m, int fd) { "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage, + "io.systemd.Network.LinkUp", vl_method_link_up, + "io.systemd.Network.LinkDown", vl_method_link_down, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, "io.systemd.service.GetEnvironment", varlink_method_get_environment); diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index c4abb777631..7069b101f9f 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -5,7 +5,9 @@ #include #include "sd-netlink.h" +#include "sd-varlink.h" +#include "alloc-util.h" #include "device-private.h" #include "missing-network.h" #include "netif-util.h" @@ -1341,6 +1343,103 @@ int link_up_or_down_now(Link *link, bool up) { return 0; } +typedef struct SetLinkVarlinkContext { + Link *link; + sd_varlink *vlink; + bool up; +} SetLinkVarlinkContext; + +static SetLinkVarlinkContext* set_link_varlink_context_free(SetLinkVarlinkContext *ctx) { + if (!ctx) + return NULL; + + if (ctx->vlink) + sd_varlink_unref(ctx->vlink); + if (ctx->link) + link_unref(ctx->link); + return mfree(ctx); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(SetLinkVarlinkContext*, set_link_varlink_context_free); + +static void set_link_varlink_context_destroy(SetLinkVarlinkContext *ctx) { + set_link_varlink_context_free(ctx); +} + +static int link_up_or_down_now_varlink_handler(sd_netlink *rtnl, sd_netlink_message *m, SetLinkVarlinkContext *ctx) { + int r; + + assert(m); + assert(ctx); + + Link *link = ASSERT_PTR(ctx->link); + sd_varlink *vlink = ASSERT_PTR(ctx->vlink); + bool up = ctx->up; + + assert(link->set_flags_messages > 0); + + link->set_flags_messages--; + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + (void) sd_varlink_error_errno(vlink, r); + log_link_message_warning_errno(link, m, r, "Could not bring %s interface", up_or_down(up)); + } else + (void) sd_varlink_reply(vlink, NULL); + + if (link->state == LINK_STATE_LINGER) + return 0; + + r = link_call_getlink(link, get_link_update_flag_handler); + if (r < 0) { + link_enter_failed(link); + return 0; + } + + link->set_flags_messages++; + return 0; +} + +int link_up_or_down_now_by_varlink(Link *link, bool up, sd_varlink *vlink) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + + log_link_debug(link, "Bringing link %s (varlink)", up_or_down(up)); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_warning_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_rtnl_message_link_set_flags(req, up ? IFF_UP : 0, IFF_UP); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set link flags: %m"); + + _cleanup_(set_link_varlink_context_freep) SetLinkVarlinkContext *ctx = new(SetLinkVarlinkContext, 1); + if (!ctx) + return log_oom(); + + *ctx = (SetLinkVarlinkContext) { + .link = link_ref(link), + .vlink = sd_varlink_ref(vlink), + .up = up, + }; + + r = netlink_call_async(link->manager->rtnl, NULL, req, + link_up_or_down_now_varlink_handler, + set_link_varlink_context_destroy, + ctx); + if (r < 0) + return log_link_warning_errno(link, r, "Could not send rtnetlink message: %m"); + + TAKE_PTR(ctx); + link->set_flags_messages++; + return 0; +} + int link_down_slave_links(Link *link) { Link *slave; int r; diff --git a/src/network/networkd-setlink.h b/src/network/networkd-setlink.h index 41ebe52f0d4..12a9dd7b942 100644 --- a/src/network/networkd-setlink.h +++ b/src/network/networkd-setlink.h @@ -22,6 +22,7 @@ int link_request_to_activate(Link *link); int link_request_to_bring_up_or_down(Link *link, bool up); int link_up_or_down_now(Link *link, bool up); +int link_up_or_down_now_by_varlink(Link *link, bool up, sd_varlink *vlink); static inline int link_up_now(Link *link) { return link_up_or_down_now(link, true); } diff --git a/src/network/org.freedesktop.network1.policy b/src/network/org.freedesktop.network1.policy index eed31694e4f..9d3ed87d6a7 100644 --- a/src/network/org.freedesktop.network1.policy +++ b/src/network/org.freedesktop.network1.policy @@ -194,4 +194,15 @@ unix-user:systemd-network + + Manage network links + Authentication is required to manage network links. + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-network + + diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index 1fdb6a7b659..f7d29521b01 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "bus-polkit.h" #include "varlink-io.systemd.Network.h" /* Helper macro to define address fields with both binary and string representation */ @@ -593,6 +594,22 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("Whether persistent storage is ready and writable"), SD_VARLINK_DEFINE_INPUT(Ready, SD_VARLINK_BOOL, 0)); +static SD_VARLINK_DEFINE_METHOD( + LinkUp, + SD_VARLINK_FIELD_COMMENT("Index of the interface. If specified together with InterfaceName, both must reference the same link."), + SD_VARLINK_DEFINE_INPUT(InterfaceIndex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Name of the interface. If specified together with InterfaceIndex, both must reference the same link."), + SD_VARLINK_DEFINE_INPUT(InterfaceName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + LinkDown, + SD_VARLINK_FIELD_COMMENT("Index of the interface. If specified together with InterfaceName, both must reference the same link."), + SD_VARLINK_DEFINE_INPUT(InterfaceIndex, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Name of the interface. If specified together with InterfaceIndex, both must reference the same link."), + SD_VARLINK_DEFINE_INPUT(InterfaceName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + static SD_VARLINK_DEFINE_ERROR(StorageReadOnly); SD_VARLINK_DEFINE_INTERFACE( @@ -603,6 +620,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_GetNamespaceId, &vl_method_GetLLDPNeighbors, &vl_method_SetPersistentStorage, + SD_VARLINK_SYMBOL_COMMENT("Bring the specified link up."), + &vl_method_LinkUp, + SD_VARLINK_SYMBOL_COMMENT("Bring the specified link down."), + &vl_method_LinkDown, &vl_type_Address, &vl_type_DHCPLease, &vl_type_DHCPServer, -- 2.47.3