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 <watanabe.yu+github@gmail.com>
This commit is contained in:
gvenugo3
2026-02-16 14:28:52 +09:00
committed by Yu Watanabe
co-authored by Yu Watanabe
parent a48145203c
commit 2291cf0626
5 changed files with 183 additions and 3 deletions
+51 -3
View 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);
+99
View 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;
+1
View 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);
}
@@ -194,4 +194,15 @@
<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>
+21
View 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,