]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: implement varlink LinkUp and LinkDown methods
authorgvenugo3 <gvenugo3@asu.edu>
Thu, 20 Nov 2025 03:35:03 +0000 (20:35 -0700)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 16 Feb 2026 05:28:52 +0000 (14:28 +0900)
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 <watanabe.yu+github@gmail.com>
src/network/networkd-manager-varlink.c
src/network/networkd-setlink.c
src/network/networkd-setlink.h
src/network/org.freedesktop.network1.policy
src/shared/varlink-io.systemd.Network.c

index 5e0d8866857cba26598440d5b9c1cac995f31b10..41f1d9b9a5851f7f7ae315a58c226bd907e89dec 100644 (file)
@@ -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);
index c4abb7776316d10301cb3f9e5e21a1001654536b..7069b101f9f5548a7eb25903cd03a3e5737408e6 100644 (file)
@@ -5,7 +5,9 @@
 #include <netinet/in.h>
 
 #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;
index 41ebe52f0d410bafca6a6892e163593d7d03a2ec..12a9dd7b9420748f8a582d481a1e057ae89ced51 100644 (file)
@@ -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);
 }
index eed31694e4f536da1ce6acd23271466ebf06a108..9d3ed87d6a70ea0efc22842eb312e3a8c61be335 100644 (file)
                 <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate>
         </action>
 
+        <action id="org.freedesktop.network1.manage-links">
+                <description gettext-domain="systemd">Manage network links</description>
+                <message gettext-domain="systemd">Authentication is required to manage network links.</message>
+                <defaults>
+                        <allow_any>auth_admin</allow_any>
+                        <allow_inactive>auth_admin</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+                <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate>
+        </action>
+
 </policyconfig>
index 1fdb6a7b6594c91add33354a289fdc6a9a812311..f7d29521b015fbd984260198d9f50bac7e491086 100644 (file)
@@ -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,