diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 1b4d303fc8..f1becd4627 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -3233,6 +3233,7 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix UseDNS= UseDNR= UseNTP= + UseSIP= UseHostname= UseDomains= NetLabel= diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index ac7fb588ef..a841ce6271 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -43,6 +43,9 @@ struct sd_dhcp6_lease { char **ntp_fqdn; struct in6_addr *sntp; size_t sntp_count; + struct in6_addr *sip; + size_t sip_count; + char **sip_domains; char *fqdn; char *captive_portal; struct sd_dhcp6_option **sorted_vendor_options; @@ -62,6 +65,8 @@ int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t opt int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); +int dhcp6_lease_add_sip_addrs(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); +int dhcp6_lease_add_sip_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); int dhcp6_lease_set_captive_portal(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 80ae1b775f..278962808a 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -629,6 +629,56 @@ int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) { return strv_length(lease->ntp_fqdn); } +int dhcp6_lease_add_sip_addrs(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { + assert(lease); + assert(optval || optlen == 0); + + if (optlen == 0) + return 0; + + return dhcp6_option_parse_addresses(optval, optlen, &lease->sip, &lease->sip_count); +} + +int sd_dhcp6_lease_get_sip_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret) { + assert_return(lease, -EINVAL); + + if (lease->sip) { + if (ret) + *ret = lease->sip; + return lease->sip_count; + } + + return -ENODATA; +} + +int dhcp6_lease_add_sip_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { + _cleanup_strv_free_ char **domains = NULL; + int r; + + assert(lease); + assert(optval || optlen == 0); + + if (optlen == 0) + return 0; + + r = dhcp6_option_parse_domainname_list(optval, optlen, &domains); + if (r < 0) + return r; + + return strv_extend_strv_consume(&lease->sip_domains, TAKE_PTR(domains), /* filter_duplicates = */ true); +} + +int sd_dhcp6_lease_get_sip_domains(sd_dhcp6_lease *lease, char ***ret) { + assert_return(lease, -EINVAL); + assert_return(ret, -EINVAL); + + if (!lease->sip_domains) + return -ENODATA; + + *ret = lease->sip_domains; + return strv_length(lease->sip_domains); +} + int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { char *fqdn; int r; @@ -930,6 +980,19 @@ static int dhcp6_lease_parse_message( break; + case SD_DHCP6_OPTION_SIP_SERVER_ADDRESS: + r = dhcp6_lease_add_sip_addrs(lease, optval, optlen); + if (r < 0) + log_dhcp6_client_errno(client, r, "Failed to parse SIP server address option, ignoring: %m"); + + break; + + case SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME: + r = dhcp6_lease_add_sip_domains(lease, optval, optlen); + if (r < 0) + log_dhcp6_client_errno(client, r, "Failed to parse SIP server domain name option, ignoring: %m"); + break; + case SD_DHCP6_OPTION_CAPTIVE_PORTAL: r = dhcp6_lease_set_captive_portal(lease, optval, optlen); if (r < 0) @@ -1020,6 +1083,8 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { free(lease->ntp); strv_free(lease->ntp_fqdn); free(lease->sntp); + free(lease->sip); + strv_free(lease->sip_domains); return mfree(lease); } diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 0775209e02..f2b6ce8598 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -47,6 +47,10 @@ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 #define NTP2_BYTES \ 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 +#define SIP1_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 +#define SIP2_BYTES \ + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 #define CLIENT_ID_BYTES \ 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 #define SERVER_ID_BYTES \ @@ -67,6 +71,8 @@ static const struct in6_addr sntp1 = { { { SNTP1_BYTES } } }; static const struct in6_addr sntp2 = { { { SNTP2_BYTES } } }; static const struct in6_addr ntp1 = { { { NTP1_BYTES } } }; static const struct in6_addr ntp2 = { { { NTP2_BYTES } } }; +static const struct in6_addr sip1 = { { { SIP1_BYTES } } }; +static const struct in6_addr sip2 = { { { SIP2_BYTES } } }; static const uint8_t client_id[] = { CLIENT_ID_BYTES }; static const uint8_t server_id[] = { SERVER_ID_BYTES }; static uint8_t vendor_suboption_data[] = { VENDOR_SUBOPTION_BYTES }; @@ -107,6 +113,8 @@ TEST(client_basic) { assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVER) >= 0); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SIP_SERVER_ADDRESS) >= 0); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_VENDOR_OPTS) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, 10) == -EINVAL); @@ -737,6 +745,13 @@ static const uint8_t msg_reply[] = { /* NTP server (fqdn suboption) */ 0x00, DHCP6_NTP_SUBOPTION_SRV_FQDN, 0x00, 0x0b, 0x03, 'n', 't', 'p', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, + /* SIP server addresses */ + 0x00, SD_DHCP6_OPTION_SIP_SERVER_ADDRESS, 0x00, 0x20, + SIP1_BYTES, + SIP2_BYTES, + /* SIP server domains */ + 0x00, SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME, 0x00, 0x0b, + 0x03, 's', 'i', 'p', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, /* Domain list */ 0x00, SD_DHCP6_OPTION_DOMAIN, 0x00, 0x0b, 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, @@ -819,6 +834,13 @@ static const uint8_t msg_advertise[] = { /* NTP server (fqdn suboption) */ 0x00, DHCP6_NTP_SUBOPTION_SRV_FQDN, 0x00, 0x0b, 0x03, 'n', 't', 'p', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, + /* SIP server addresses */ + 0x00, SD_DHCP6_OPTION_SIP_SERVER_ADDRESS, 0x00, 0x20, + SIP1_BYTES, + SIP2_BYTES, + /* SIP server domains */ + 0x00, SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME, 0x00, 0x0b, + 0x03, 's', 'i', 'p', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, /* Domain list */ 0x00, SD_DHCP6_OPTION_DOMAIN, 0x00, 0x0b, 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, @@ -899,6 +921,14 @@ static void test_lease_common(sd_dhcp6_client *client) { assert_se(streq(strv[0], "ntp.intra")); assert_se(!strv[1]); + assert_se(sd_dhcp6_lease_get_sip_addrs(lease, &addrs) == 2); + assert_se(in6_addr_equal(&addrs[0], &sip1)); + assert_se(in6_addr_equal(&addrs[1], &sip2)); + + assert_se(sd_dhcp6_lease_get_sip_domains(lease, &strv) == 1); + assert_se(streq(strv[0], "sip.intra")); + assert_se(!strv[1]); + assert_se(lease->sntp_count == 2); assert_se(in6_addr_equal(&lease->sntp[0], &sntp1)); assert_se(in6_addr_equal(&lease->sntp[1], &sntp2)); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 6570e13d66..cae497278f 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -687,6 +687,16 @@ static int dhcp6_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request SNTP servers: %m"); } + if (link->network->dhcp6_use_sip > 0) { + r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SIP_SERVER_ADDRESS); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request SIP servers: %m"); + + r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request SIP server domain names: %m"); + } + SET_FOREACH(request_options, link->network->dhcp6_request_options) { uint32_t option = PTR_TO_UINT32(request_options); diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index b6b4b02c06..aa65379acc 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -819,39 +819,6 @@ static int ntp_append_json(Link *link, sd_json_variant **v) { return json_variant_set_field_non_null(v, "NTP", array); } -static int sip_append_json(Link *link, sd_json_variant **v) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - const struct in_addr *sip; - union in_addr_union s; - int n_sip, r; - - assert(link); - assert(v); - - if (!link->network || !link->network->dhcp_use_sip || !link->dhcp_lease) - return 0; - - n_sip = sd_dhcp_lease_get_sip(link->dhcp_lease, &sip); - if (n_sip <= 0) - return 0; - - r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &s.in); - if (r < 0) - return r; - - for (int i = 0; i < n_sip; i++) { - r = server_append_json_one_addr(AF_INET, - &(union in_addr_union) { .in = sip[i], }, - NETWORK_CONFIG_SOURCE_DHCP4, - &s, - &array); - if (r < 0) - return r; - } - - return json_variant_set_field_non_null(v, "SIP", array); -} - static int domain_append_json(int family, const char *domain, NetworkConfigSource s, const union in_addr_union *p, sd_json_variant **array) { assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6)); assert(domain); @@ -864,6 +831,78 @@ static int domain_append_json(int family, const char *domain, NetworkConfigSourc JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family)); } +static int sip_append_json(Link *link, sd_json_variant **v) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + int r; + + assert(link); + assert(v); + + if (!link->network) + return 0; + + if (link->dhcp_lease && link->network->dhcp_use_sip) { + const struct in_addr *sip; + union in_addr_union s; + int n_sip; + + n_sip = sd_dhcp_lease_get_sip(link->dhcp_lease, &sip); + if (n_sip <= 0) + return 0; + + r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &s.in); + if (r < 0) + return r; + + for (int i = 0; i < n_sip; i++) { + r = server_append_json_one_addr(AF_INET, + &(union in_addr_union) { .in = sip[i], }, + NETWORK_CONFIG_SOURCE_DHCP4, + &s, + &array); + if (r < 0) + return r; + } + + } + + if (link->dhcp6_lease && link->network->dhcp6_use_sip) { + const struct in6_addr *sip_addr; + union in_addr_union s; + char **domains; + int n_sip; + + r = sd_dhcp6_lease_get_server_address(link->dhcp6_lease, &s.in6); + if (r < 0) + return r; + + n_sip = sd_dhcp6_lease_get_sip_addrs(link->dhcp6_lease, &sip_addr); + for (int i = 0; i < n_sip; i++) { + r = server_append_json_one_addr(AF_INET6, + &(union in_addr_union) { .in6 = sip_addr[i], }, + NETWORK_CONFIG_SOURCE_DHCP6, + &s, + &array); + if (r < 0) + return r; + } + + if (sd_dhcp6_lease_get_sip_domains(link->dhcp6_lease, &domains) >= 0) + STRV_FOREACH(p, domains) { + r = domain_append_json(AF_INET6, + *p, + NETWORK_CONFIG_SOURCE_DHCP6, + &s, + &array); + if (r < 0) + return r; + } + + } + + return json_variant_set_field_non_null(v, "SIP", array); +} + static int domains_append_json(Link *link, bool is_route, sd_json_variant **v) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; OrderedSet *link_domains, *network_domains; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 577dccc9eb..52fc0ab444 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -310,6 +310,7 @@ DHCPv6.UseDNR, config_parse_tristate, DHCPv6.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp6_use_hostname) DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp6_use_domains) DHCPv6.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp6_use_ntp) +DHCPv6.UseSIP, config_parse_bool, 0, offsetof(Network, dhcp6_use_sip) DHCPv6.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, dhcp6_use_captive_portal) DHCPv6.MUDURL, config_parse_mud_url, 0, offsetof(Network, dhcp6_mudurl) DHCPv6.SendHostname, config_parse_dhcp_send_hostname, AF_INET6, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 961b431a68..e27fabea81 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -413,6 +413,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp6_use_domains = _USE_DOMAINS_INVALID, .dhcp6_use_hostname = true, .dhcp6_use_ntp = -1, + .dhcp6_use_sip = true, .dhcp6_use_captive_portal = true, .dhcp6_use_rapid_commit = true, .dhcp6_send_hostname = true, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 0e14cfa7b5..677d337e0f 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -181,6 +181,7 @@ typedef struct Network { int dhcp6_use_dnr; bool dhcp6_use_hostname; int dhcp6_use_ntp; + bool dhcp6_use_sip; bool dhcp6_use_captive_portal; bool dhcp6_use_rapid_commit; UseDomains dhcp6_use_domains; diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index abd8b5a4ef..21bf4b1b34 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -264,6 +264,25 @@ static int link_put_sip(Link *link, OrderedSet **s) { } } + if (link->dhcp6_lease && link->network->dhcp6_use_sip) { + const struct in6_addr *addresses; + char **domains; + + r = sd_dhcp6_lease_get_sip_addrs(link->dhcp6_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in6_addrv(s, addresses, r); + if (r < 0) + return r; + } + + r = sd_dhcp6_lease_get_sip_domains(link->dhcp6_lease, &domains); + if (r >= 0) { + r = ordered_set_put_strdupv_full(s, &dns_name_hash_ops_free, domains); + if (r < 0) + return r; + } + } + return 0; } @@ -842,7 +861,10 @@ static int link_save(Link *link) { link->dhcp_lease, link->network->dhcp_use_sip, SD_DHCP_LEASE_SIP, - NULL, false, NULL, NULL); + link->dhcp6_lease, + link->network->dhcp6_use_sip, + sd_dhcp6_lease_get_sip_addrs, + sd_dhcp6_lease_get_sip_domains); /************************************************************/ diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h index 766adf3121..02852fc575 100644 --- a/src/systemd/sd-dhcp6-lease.h +++ b/src/systemd/sd-dhcp6-lease.h @@ -77,6 +77,8 @@ int sd_dhcp6_lease_get_dnr(sd_dhcp6_lease *lease, sd_dns_resolver **ret); int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret); int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret); int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret); +int sd_dhcp6_lease_get_sip_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret); +int sd_dhcp6_lease_get_sip_domains(sd_dhcp6_lease *lease, char ***ret); int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret); int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret); int sd_dhcp6_lease_get_vendor_options(sd_dhcp6_lease *lease, sd_dhcp6_option ***ret); diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index e8ddf15a84..0ea0f03b77 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -8433,6 +8433,52 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): check(self, False, True, needs_reconfigure=True) check(self, False, False) + def test_dhcp_client_use_sip(self): + def check(self, ipv4, ipv6, needs_reconfigure=False): + os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) + with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + f.write('[DHCPv4]\nUseSIP=') + f.write('yes' if ipv4 else 'no') + f.write('\n[DHCPv6]\nUseSIP=') + f.write('yes' if ipv6 else 'no') + + networkctl_reload() + if needs_reconfigure: + networkctl_reconfigure('veth99') + self.wait_online('veth99:routable') + + # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. + self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') + self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + + output = networkctl_status('veth99') + print(output) + if ipv4 and ipv6: + self.assertRegex(output, 'SIP: 192.168.5.1\n *2600::1\n *foo.example.com') + elif ipv4: + self.assertIn('SIP: 192.168.5.1', output) + elif ipv6: + self.assertRegex(output, 'SIP: 2600::1\n *foo.example.com') + else: + self.assertNotIn('SIP: 192.168.5.1', output) + self.assertNotIn('SIP: 2600::1', output) + self.assertNotIn('SIP: foo.example.com', output) + + check_json(networkctl_json()) + + copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + + start_networkd() + self.wait_online('veth-peer:carrier') + start_dnsmasq('--dhcp-option=option:sip-server,192.168.5.1', + '--dhcp-option=option6:sip-server,[2600::1]', + '--dhcp-option=option6:sip-server-domain,foo.example.com') + + check(self, True, True) + check(self, True, False) + check(self, False, True, needs_reconfigure=True) + check(self, False, False) + def test_dhcp_client_use_captive_portal(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True)