1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 #include <netinet/in.h>
5 #include <sys/capability.h>
7 #include "alloc-util.h"
8 #include "bus-common-errors.h"
9 #include "bus-get-properties.h"
10 #include "bus-message-util.h"
11 #include "bus-polkit.h"
12 #include "dns-domain.h"
13 #include "networkd-json.h"
14 #include "networkd-link-bus.h"
15 #include "networkd-link.h"
16 #include "networkd-manager.h"
17 #include "networkd-state-file.h"
18 #include "parse-util.h"
19 #include "resolve-util.h"
20 #include "socket-netlink.h"
22 #include "user-util.h"
24 BUS_DEFINE_PROPERTY_GET_ENUM(property_get_operational_state
, link_operstate
, LinkOperationalState
);
25 BUS_DEFINE_PROPERTY_GET_ENUM(property_get_carrier_state
, link_carrier_state
, LinkCarrierState
);
26 BUS_DEFINE_PROPERTY_GET_ENUM(property_get_address_state
, link_address_state
, LinkAddressState
);
27 BUS_DEFINE_PROPERTY_GET_ENUM(property_get_online_state
, link_online_state
, LinkOnlineState
);
28 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_administrative_state
, link_state
, LinkState
);
30 static int property_get_bit_rates(
33 const char *interface
,
35 sd_bus_message
*reply
,
37 sd_bus_error
*error
) {
39 Link
*link
= userdata
;
48 manager
= link
->manager
;
50 if (!manager
->use_speed_meter
||
51 manager
->speed_meter_usec_old
== 0 ||
53 return sd_bus_message_append(reply
, "(tt)", UINT64_MAX
, UINT64_MAX
);
55 assert(manager
->speed_meter_usec_new
> manager
->speed_meter_usec_old
);
56 interval_sec
= (manager
->speed_meter_usec_new
- manager
->speed_meter_usec_old
) / USEC_PER_SEC
;
58 if (link
->stats_new
.tx_bytes
> link
->stats_old
.tx_bytes
)
59 tx
= (uint64_t) ((link
->stats_new
.tx_bytes
- link
->stats_old
.tx_bytes
) / interval_sec
);
61 tx
= (uint64_t) ((UINT64_MAX
- (link
->stats_old
.tx_bytes
- link
->stats_new
.tx_bytes
)) / interval_sec
);
63 if (link
->stats_new
.rx_bytes
> link
->stats_old
.rx_bytes
)
64 rx
= (uint64_t) ((link
->stats_new
.rx_bytes
- link
->stats_old
.rx_bytes
) / interval_sec
);
66 rx
= (uint64_t) ((UINT64_MAX
- (link
->stats_old
.rx_bytes
- link
->stats_new
.rx_bytes
)) / interval_sec
);
68 return sd_bus_message_append(reply
, "(tt)", tx
, rx
);
71 static int verify_managed_link(Link
*l
, sd_bus_error
*error
) {
74 if (l
->flags
& IFF_LOOPBACK
)
75 return sd_bus_error_setf(error
, BUS_ERROR_LINK_BUSY
, "Link %s is loopback device.", l
->ifname
);
80 int bus_link_method_set_ntp_servers(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
81 _cleanup_strv_free_
char **ntp
= NULL
;
89 r
= verify_managed_link(l
, error
);
93 r
= sd_bus_message_read_strv(message
, &ntp
);
97 STRV_FOREACH(i
, ntp
) {
98 r
= dns_name_is_valid_or_address(*i
);
102 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid NTP server: %s", *i
);
105 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
106 "org.freedesktop.network1.set-ntp-servers",
107 NULL
, true, UID_INVALID
,
108 &l
->manager
->polkit_registry
, error
);
112 return 1; /* Polkit will call us back */
114 strv_free_and_replace(l
->ntp
, ntp
);
117 r
= link_save_and_clean(l
);
121 return sd_bus_reply_method_return(message
, NULL
);
124 static int bus_link_method_set_dns_servers_internal(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
, bool extended
) {
125 struct in_addr_full
**dns
;
133 r
= verify_managed_link(l
, error
);
137 r
= bus_message_read_dns_servers(message
, error
, extended
, &dns
, &n
);
141 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
142 "org.freedesktop.network1.set-dns-servers",
143 NULL
, true, UID_INVALID
,
144 &l
->manager
->polkit_registry
, error
);
148 r
= 1; /* Polkit will call us back */
152 if (l
->n_dns
!= UINT_MAX
)
153 for (unsigned i
= 0; i
< l
->n_dns
; i
++)
154 in_addr_full_free(l
->dns
[i
]);
156 free_and_replace(l
->dns
, dns
);
160 r
= link_save_and_clean(l
);
164 return sd_bus_reply_method_return(message
, NULL
);
167 for (size_t i
= 0; i
< n
; i
++)
168 in_addr_full_free(dns
[i
]);
174 int bus_link_method_set_dns_servers(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
175 return bus_link_method_set_dns_servers_internal(message
, userdata
, error
, false);
178 int bus_link_method_set_dns_servers_ex(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
179 return bus_link_method_set_dns_servers_internal(message
, userdata
, error
, true);
182 int bus_link_method_set_domains(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
183 _cleanup_ordered_set_free_ OrderedSet
*search_domains
= NULL
, *route_domains
= NULL
;
190 r
= verify_managed_link(l
, error
);
194 r
= sd_bus_message_enter_container(message
, 'a', "(sb)");
199 _cleanup_free_
char *str
= NULL
;
200 OrderedSet
**domains
;
204 r
= sd_bus_message_read(message
, "(sb)", &name
, &route_only
);
210 r
= dns_name_is_valid(name
);
214 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid search domain %s", name
);
215 if (!route_only
&& dns_name_is_root(name
))
216 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Root domain is not suitable as search domain");
218 r
= dns_name_normalize(name
, 0, &str
);
220 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid search domain %s", name
);
222 domains
= route_only
? &route_domains
: &search_domains
;
223 r
= ordered_set_ensure_allocated(domains
, &string_hash_ops_free
);
227 r
= ordered_set_consume(*domains
, TAKE_PTR(str
));
234 r
= sd_bus_message_exit_container(message
);
238 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
239 "org.freedesktop.network1.set-domains",
240 NULL
, true, UID_INVALID
,
241 &l
->manager
->polkit_registry
, error
);
245 return 1; /* Polkit will call us back */
247 ordered_set_free(l
->search_domains
);
248 ordered_set_free(l
->route_domains
);
249 l
->search_domains
= TAKE_PTR(search_domains
);
250 l
->route_domains
= TAKE_PTR(route_domains
);
253 r
= link_save_and_clean(l
);
257 return sd_bus_reply_method_return(message
, NULL
);
260 int bus_link_method_set_default_route(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
267 r
= verify_managed_link(l
, error
);
271 r
= sd_bus_message_read(message
, "b", &b
);
275 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
276 "org.freedesktop.network1.set-default-route",
277 NULL
, true, UID_INVALID
,
278 &l
->manager
->polkit_registry
, error
);
282 return 1; /* Polkit will call us back */
284 if (l
->dns_default_route
!= b
) {
285 l
->dns_default_route
= b
;
288 r
= link_save_and_clean(l
);
293 return sd_bus_reply_method_return(message
, NULL
);
296 int bus_link_method_set_llmnr(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
305 r
= verify_managed_link(l
, error
);
309 r
= sd_bus_message_read(message
, "s", &llmnr
);
314 mode
= RESOLVE_SUPPORT_YES
;
316 mode
= resolve_support_from_string(llmnr
);
318 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid LLMNR setting: %s", llmnr
);
321 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
322 "org.freedesktop.network1.set-llmnr",
323 NULL
, true, UID_INVALID
,
324 &l
->manager
->polkit_registry
, error
);
328 return 1; /* Polkit will call us back */
330 if (l
->llmnr
!= mode
) {
334 r
= link_save_and_clean(l
);
339 return sd_bus_reply_method_return(message
, NULL
);
342 int bus_link_method_set_mdns(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
351 r
= verify_managed_link(l
, error
);
355 r
= sd_bus_message_read(message
, "s", &mdns
);
360 mode
= RESOLVE_SUPPORT_NO
;
362 mode
= resolve_support_from_string(mdns
);
364 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid MulticastDNS setting: %s", mdns
);
367 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
368 "org.freedesktop.network1.set-mdns",
369 NULL
, true, UID_INVALID
,
370 &l
->manager
->polkit_registry
, error
);
374 return 1; /* Polkit will call us back */
376 if (l
->mdns
!= mode
) {
380 r
= link_save_and_clean(l
);
385 return sd_bus_reply_method_return(message
, NULL
);
388 int bus_link_method_set_dns_over_tls(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
390 const char *dns_over_tls
;
397 r
= verify_managed_link(l
, error
);
401 r
= sd_bus_message_read(message
, "s", &dns_over_tls
);
405 if (isempty(dns_over_tls
))
406 mode
= _DNS_OVER_TLS_MODE_INVALID
;
408 mode
= dns_over_tls_mode_from_string(dns_over_tls
);
410 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid DNSOverTLS setting: %s", dns_over_tls
);
413 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
414 "org.freedesktop.network1.set-dns-over-tls",
415 NULL
, true, UID_INVALID
,
416 &l
->manager
->polkit_registry
, error
);
420 return 1; /* Polkit will call us back */
422 if (l
->dns_over_tls_mode
!= mode
) {
423 l
->dns_over_tls_mode
= mode
;
426 r
= link_save_and_clean(l
);
431 return sd_bus_reply_method_return(message
, NULL
);
434 int bus_link_method_set_dnssec(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
443 r
= verify_managed_link(l
, error
);
447 r
= sd_bus_message_read(message
, "s", &dnssec
);
452 mode
= _DNSSEC_MODE_INVALID
;
454 mode
= dnssec_mode_from_string(dnssec
);
456 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid DNSSEC setting: %s", dnssec
);
459 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
460 "org.freedesktop.network1.set-dnssec",
461 NULL
, true, UID_INVALID
,
462 &l
->manager
->polkit_registry
, error
);
466 return 1; /* Polkit will call us back */
468 if (l
->dnssec_mode
!= mode
) {
469 l
->dnssec_mode
= mode
;
472 r
= link_save_and_clean(l
);
477 return sd_bus_reply_method_return(message
, NULL
);
480 int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
481 _cleanup_set_free_free_ Set
*ns
= NULL
;
482 _cleanup_strv_free_
char **ntas
= NULL
;
490 r
= verify_managed_link(l
, error
);
494 r
= sd_bus_message_read_strv(message
, &ntas
);
498 STRV_FOREACH(i
, ntas
) {
499 r
= dns_name_is_valid(*i
);
503 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid negative trust anchor domain: %s", *i
);
506 ns
= set_new(&dns_name_hash_ops
);
510 STRV_FOREACH(i
, ntas
) {
511 r
= set_put_strdup(&ns
, *i
);
516 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
517 "org.freedesktop.network1.set-dnssec-negative-trust-anchors",
518 NULL
, true, UID_INVALID
,
519 &l
->manager
->polkit_registry
, error
);
523 return 1; /* Polkit will call us back */
525 set_free_free(l
->dnssec_negative_trust_anchors
);
526 l
->dnssec_negative_trust_anchors
= TAKE_PTR(ns
);
529 r
= link_save_and_clean(l
);
533 return sd_bus_reply_method_return(message
, NULL
);
536 int bus_link_method_revert_ntp(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
543 r
= verify_managed_link(l
, error
);
547 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
548 "org.freedesktop.network1.revert-ntp",
549 NULL
, true, UID_INVALID
,
550 &l
->manager
->polkit_registry
, error
);
554 return 1; /* Polkit will call us back */
556 link_ntp_settings_clear(l
);
559 r
= link_save_and_clean(l
);
563 return sd_bus_reply_method_return(message
, NULL
);
566 int bus_link_method_revert_dns(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
573 r
= verify_managed_link(l
, error
);
577 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
578 "org.freedesktop.network1.revert-dns",
579 NULL
, true, UID_INVALID
,
580 &l
->manager
->polkit_registry
, error
);
584 return 1; /* Polkit will call us back */
586 link_dns_settings_clear(l
);
589 r
= link_save_and_clean(l
);
593 return sd_bus_reply_method_return(message
, NULL
);
596 int bus_link_method_force_renew(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
603 return sd_bus_error_setf(error
, BUS_ERROR_UNMANAGED_INTERFACE
,
604 "Interface %s is not managed by systemd-networkd",
607 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
608 "org.freedesktop.network1.forcerenew",
609 NULL
, true, UID_INVALID
,
610 &l
->manager
->polkit_registry
, error
);
614 return 1; /* Polkit will call us back */
616 if (l
->dhcp_server
) {
617 r
= sd_dhcp_server_forcerenew(l
->dhcp_server
);
622 return sd_bus_reply_method_return(message
, NULL
);
625 int bus_link_method_renew(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
632 return sd_bus_error_setf(error
, BUS_ERROR_UNMANAGED_INTERFACE
,
633 "Interface %s is not managed by systemd-networkd",
636 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
637 "org.freedesktop.network1.renew",
638 NULL
, true, UID_INVALID
,
639 &l
->manager
->polkit_registry
, error
);
643 return 1; /* Polkit will call us back */
645 if (l
->dhcp_client
) {
646 r
= sd_dhcp_client_send_renew(l
->dhcp_client
);
651 return sd_bus_reply_method_return(message
, NULL
);
654 int bus_link_method_reconfigure(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
661 r
= bus_verify_polkit_async(message
, CAP_NET_ADMIN
,
662 "org.freedesktop.network1.reconfigure",
663 NULL
, true, UID_INVALID
,
664 &l
->manager
->polkit_registry
, error
);
668 return 1; /* Polkit will call us back */
670 r
= link_reconfigure(l
, /* force = */ true);
674 link_set_state(l
, LINK_STATE_INITIALIZED
);
675 r
= link_save_and_clean(l
);
680 return sd_bus_reply_method_return(message
, NULL
);
683 int bus_link_method_describe(sd_bus_message
*message
, void *userdata
, sd_bus_error
*error
) {
684 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
685 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
686 _cleanup_free_
char *text
= NULL
;
687 Link
*link
= userdata
;
693 r
= link_build_json(link
, &v
);
695 return log_link_error_errno(link
, r
, "Failed to build JSON data: %m");
697 r
= json_variant_format(v
, 0, &text
);
699 return log_link_error_errno(link
, r
, "Failed to format JSON data: %m");
701 r
= sd_bus_message_new_method_return(message
, &reply
);
705 r
= sd_bus_message_append(reply
, "s", text
);
709 return sd_bus_send(NULL
, reply
, NULL
);
712 const sd_bus_vtable link_vtable
[] = {
713 SD_BUS_VTABLE_START(0),
715 SD_BUS_PROPERTY("OperationalState", "s", property_get_operational_state
, offsetof(Link
, operstate
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
716 SD_BUS_PROPERTY("CarrierState", "s", property_get_carrier_state
, offsetof(Link
, carrier_state
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
717 SD_BUS_PROPERTY("AddressState", "s", property_get_address_state
, offsetof(Link
, address_state
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
718 SD_BUS_PROPERTY("IPv4AddressState", "s", property_get_address_state
, offsetof(Link
, ipv4_address_state
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
719 SD_BUS_PROPERTY("IPv6AddressState", "s", property_get_address_state
, offsetof(Link
, ipv6_address_state
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
720 SD_BUS_PROPERTY("OnlineState", "s", property_get_online_state
, offsetof(Link
, online_state
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
721 SD_BUS_PROPERTY("AdministrativeState", "s", property_get_administrative_state
, offsetof(Link
, state
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
722 SD_BUS_PROPERTY("BitRates", "(tt)", property_get_bit_rates
, 0, 0),
724 SD_BUS_METHOD_WITH_ARGS("SetNTP",
725 SD_BUS_ARGS("as", servers
),
727 bus_link_method_set_ntp_servers
,
728 SD_BUS_VTABLE_UNPRIVILEGED
),
729 SD_BUS_METHOD_WITH_ARGS("SetDNS",
730 SD_BUS_ARGS("a(iay)", addresses
),
732 bus_link_method_set_dns_servers
,
733 SD_BUS_VTABLE_UNPRIVILEGED
),
734 SD_BUS_METHOD_WITH_ARGS("SetDNSEx",
735 SD_BUS_ARGS("a(iayqs)", addresses
),
737 bus_link_method_set_dns_servers_ex
,
738 SD_BUS_VTABLE_UNPRIVILEGED
),
739 SD_BUS_METHOD_WITH_ARGS("SetDomains",
740 SD_BUS_ARGS("a(sb)", domains
),
742 bus_link_method_set_domains
,
743 SD_BUS_VTABLE_UNPRIVILEGED
),
744 SD_BUS_METHOD_WITH_ARGS("SetDefaultRoute",
745 SD_BUS_ARGS("b", enable
),
747 bus_link_method_set_default_route
,
748 SD_BUS_VTABLE_UNPRIVILEGED
),
749 SD_BUS_METHOD_WITH_ARGS("SetLLMNR",
750 SD_BUS_ARGS("s", mode
),
752 bus_link_method_set_llmnr
,
753 SD_BUS_VTABLE_UNPRIVILEGED
),
754 SD_BUS_METHOD_WITH_ARGS("SetMulticastDNS",
755 SD_BUS_ARGS("s", mode
),
757 bus_link_method_set_mdns
,
758 SD_BUS_VTABLE_UNPRIVILEGED
),
759 SD_BUS_METHOD_WITH_ARGS("SetDNSOverTLS",
760 SD_BUS_ARGS("s", mode
),
762 bus_link_method_set_dns_over_tls
,
763 SD_BUS_VTABLE_UNPRIVILEGED
),
764 SD_BUS_METHOD_WITH_ARGS("SetDNSSEC",
765 SD_BUS_ARGS("s", mode
),
767 bus_link_method_set_dnssec
,
768 SD_BUS_VTABLE_UNPRIVILEGED
),
769 SD_BUS_METHOD_WITH_ARGS("SetDNSSECNegativeTrustAnchors",
770 SD_BUS_ARGS("as", names
),
772 bus_link_method_set_dnssec_negative_trust_anchors
,
773 SD_BUS_VTABLE_UNPRIVILEGED
),
774 SD_BUS_METHOD_WITH_ARGS("RevertNTP",
777 bus_link_method_revert_ntp
,
778 SD_BUS_VTABLE_UNPRIVILEGED
),
779 SD_BUS_METHOD_WITH_ARGS("RevertDNS",
782 bus_link_method_revert_dns
,
783 SD_BUS_VTABLE_UNPRIVILEGED
),
784 SD_BUS_METHOD_WITH_ARGS("Renew",
787 bus_link_method_renew
,
788 SD_BUS_VTABLE_UNPRIVILEGED
),
789 SD_BUS_METHOD_WITH_ARGS("ForceRenew",
792 bus_link_method_force_renew
,
793 SD_BUS_VTABLE_UNPRIVILEGED
),
794 SD_BUS_METHOD_WITH_ARGS("Reconfigure",
797 bus_link_method_reconfigure
,
798 SD_BUS_VTABLE_UNPRIVILEGED
),
799 SD_BUS_METHOD_WITH_ARGS("Describe",
801 SD_BUS_RESULT("s", json
),
802 bus_link_method_describe
,
803 SD_BUS_VTABLE_UNPRIVILEGED
),
808 char *link_bus_path(Link
*link
) {
809 _cleanup_free_
char *ifindex
= NULL
;
814 assert(link
->ifindex
> 0);
816 if (asprintf(&ifindex
, "%d", link
->ifindex
) < 0)
819 r
= sd_bus_path_encode("/org/freedesktop/network1/link", ifindex
, &p
);
826 int link_node_enumerator(sd_bus
*bus
, const char *path
, void *userdata
, char ***nodes
, sd_bus_error
*error
) {
827 _cleanup_strv_free_
char **l
= NULL
;
828 Manager
*m
= userdata
;
837 l
= new0(char*, hashmap_size(m
->links
) + 1);
841 HASHMAP_FOREACH(link
, m
->links
) {
844 p
= link_bus_path(link
);
852 *nodes
= TAKE_PTR(l
);
857 int link_object_find(sd_bus
*bus
, const char *path
, const char *interface
, void *userdata
, void **found
, sd_bus_error
*error
) {
858 _cleanup_free_
char *identifier
= NULL
;
859 Manager
*m
= userdata
;
869 r
= sd_bus_path_decode(path
, "/org/freedesktop/network1/link", &identifier
);
873 ifindex
= parse_ifindex(identifier
);
877 r
= link_get(m
, ifindex
, &link
);
881 if (streq(interface
, "org.freedesktop.network1.DHCPServer") && !link
->dhcp_server
)
889 int link_send_changed_strv(Link
*link
, char **properties
) {
890 _cleanup_free_
char *p
= NULL
;
893 assert(link
->manager
);
896 if (sd_bus_is_ready(link
->manager
->bus
) <= 0)
899 p
= link_bus_path(link
);
903 return sd_bus_emit_properties_changed_strv(
906 "org.freedesktop.network1.Link",
910 int link_send_changed(Link
*link
, const char *property
, ...) {
913 properties
= strv_from_stdarg_alloca(property
);
915 return link_send_changed_strv(link
, properties
);