From 5d896defeb7a9c65de63515eb87542bf8d7c04e2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 5 Oct 2023 06:53:01 +0900 Subject: [PATCH 1/3] network: skip to set request address when anonymized In sd-dhcp-client.c, we do not set the option in the DHCPDISCOVER message when anonymized, and the specified address is ignored anyway. So, this does not change the behavior, but suppress misleading debugging log in dhcp4_set_request_address(). --- src/network/networkd-dhcp4.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index f14e62588f..2a6237c6d5 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1487,6 +1487,10 @@ static int dhcp4_configure(Link *link) { } if (!link->network->dhcp_anonymize) { + r = dhcp4_set_request_address(link); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set initial DHCPv4 address: %m"); + if (link->network->dhcp_use_mtu) { r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_MTU_INTERFACE); if (r < 0) @@ -1616,10 +1620,6 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed set to lease lifetime: %m"); } - r = dhcp4_set_request_address(link); - if (r < 0) - return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set initial DHCPv4 address: %m"); - return dhcp4_set_client_identifier(link); } From b93bf1bf9fb8f091c52588c5fc9edef6225f4ed3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 4 Oct 2023 20:46:55 +0900 Subject: [PATCH 2/3] network: introduce [DHCPv4] RequestAddress= setting This may be useful when requesting a specific address. Closes #29437. --- man/systemd.network.xml | 12 ++++++++++ src/network/networkd-dhcp4.c | 30 +++++++++++++++++++----- src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.h | 1 + 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 8d462615ac..33c559cddb 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2098,6 +2098,17 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix + + RequestAddress= + + Takes an IPv4 address. When specified, the Requested IP Address option (option code 50) is + added with it to the initial DHCPDISCOVER message sent by the DHCP client. Defaults to unset, and + an already assigned dynamic address to the interface is automatically picked. + + + + + SendHostname= @@ -2232,6 +2243,7 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix are implied and these settings in the .network file are silently ignored. Also, Hostname=, MUDURL=, + RequestAddress, RequestOptions=, SendOption=, SendVendorOption=, diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 2a6237c6d5..3e8038ede6 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1383,15 +1383,15 @@ static int dhcp4_set_client_identifier(Link *link) { return 0; } -static int dhcp4_set_request_address(Link *link) { +static int dhcp4_find_dynamic_address(Link *link, struct in_addr *ret) { Address *a; assert(link); assert(link->network); - assert(link->dhcp_client); + assert(ret); if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) - return 0; + return false; SET_FOREACH(a, link->addresses) { if (a->source != NETWORK_CONFIG_SOURCE_FOREIGN) @@ -1403,11 +1403,29 @@ static int dhcp4_set_request_address(Link *link) { } if (!a) + return false; + + *ret = a->in_addr.in; + return true; +} + +static int dhcp4_set_request_address(Link *link) { + struct in_addr a; + + assert(link); + assert(link->network); + assert(link->dhcp_client); + + a = link->network->dhcp_request_address; + + if (in4_addr_is_null(&a)) + (void) dhcp4_find_dynamic_address(link, &a); + + if (in4_addr_is_null(&a)) return 0; - log_link_debug(link, "DHCPv4 CLIENT: requesting " IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(a->in_addr.in)); - - return sd_dhcp_client_set_request_address(link->dhcp_client, &a->in_addr.in); + log_link_debug(link, "DHCPv4 CLIENT: requesting %s.", IN4_ADDR_TO_STRING(&a)); + return sd_dhcp_client_set_request_address(link->dhcp_client, &a); } static bool link_needs_dhcp_broadcast(Link *link) { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index ab456efb9e..42e989e116 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -214,6 +214,7 @@ NextHop.Family, config_parse_nexthop_family, NextHop.OnLink, config_parse_nexthop_onlink, 0, 0 NextHop.Blackhole, config_parse_nexthop_blackhole, 0, 0 NextHop.Group, config_parse_nexthop_group, 0, 0 +DHCPv4.RequestAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_request_address) DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) DHCPv4.UseDNS, config_parse_dhcp_use_dns, AF_INET, 0 DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 34bc179d75..cc2cf36f5c 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -114,6 +114,7 @@ struct Network { /* DHCP Client Support */ AddressFamily dhcp; + struct in_addr dhcp_request_address; DHCPClientIdentifier dhcp_client_identifier; DUID dhcp_duid; uint32_t dhcp_iaid; From 6b524d70e36ce31b3bea15943643816c89c826b6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 4 Oct 2023 21:29:31 +0900 Subject: [PATCH 3/3] test-network: add tests for [DHCPv4] RequestAddress= setting --- .../conf/25-dhcp-client-ipv4-only.network | 1 + test/test-network/systemd-networkd-tests.py | 50 +++++++++++++------ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/test/test-network/conf/25-dhcp-client-ipv4-only.network b/test/test-network/conf/25-dhcp-client-ipv4-only.network index 653d7aa661..5e83bd24dc 100644 --- a/test/test-network/conf/25-dhcp-client-ipv4-only.network +++ b/test/test-network/conf/25-dhcp-client-ipv4-only.network @@ -8,6 +8,7 @@ IPv6AcceptRA=no Address=192.168.5.250/24 [DHCPv4] +RequestAddress=192.168.5.110 UseDomains=yes UseMTU=yes UseRoutes=yes diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 53deb63325..c00c135130 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5204,6 +5204,16 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertRegex(output, r'inet 192.168.5.11[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') self.assertNotIn('2600::', output) + output = check_output('ip -4 --json address show dev veth99') + for i in json.loads(output)[0]['addr_info']: + if i['label'] == 'test-label': + address1 = i['local'] + break + else: + self.assertFalse(True) + + self.assertRegex(address1, r'^192.168.5.11[0-9]$') + print('## ip route show table main dev veth99') output = check_output('ip route show table main dev veth99') print(output) @@ -5218,11 +5228,11 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print('## ip route show table 211 dev veth99') output = check_output('ip route show table 211 dev veth99') print(output) - self.assertRegex(output, 'default via 192.168.5.1 proto dhcp src 192.168.5.11[0-9] metric 24') - self.assertRegex(output, '192.168.5.0/24 proto dhcp scope link src 192.168.5.11[0-9] metric 24') - self.assertRegex(output, '192.168.5.1 proto dhcp scope link src 192.168.5.11[0-9] metric 24') - self.assertRegex(output, '192.168.5.6 proto dhcp scope link src 192.168.5.11[0-9] metric 24') - self.assertRegex(output, '192.168.5.7 proto dhcp scope link src 192.168.5.11[0-9] metric 24') + self.assertRegex(output, f'default via 192.168.5.1 proto dhcp src {address1} metric 24') + self.assertRegex(output, f'192.168.5.0/24 proto dhcp scope link src {address1} metric 24') + self.assertRegex(output, f'192.168.5.1 proto dhcp scope link src {address1} metric 24') + self.assertRegex(output, f'192.168.5.6 proto dhcp scope link src {address1} metric 24') + self.assertRegex(output, f'192.168.5.7 proto dhcp scope link src {address1} metric 24') self.assertIn('10.0.0.0/8 via 192.168.5.1 proto dhcp', output) print('## link state file') @@ -5261,7 +5271,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): output = read_dnsmasq_log_file() print(output) self.assertIn('vendor class: FooBarVendorTest', output) - self.assertIn('DHCPDISCOVER(veth-peer) 12:34:56:78:9a:bc', output) + self.assertIn('DHCPDISCOVER(veth-peer) 192.168.5.110 12:34:56:78:9a:bc', output) self.assertIn('client provides name: test-hostname', output) self.assertIn('26:mtu', output) @@ -5275,7 +5285,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120 print('Wait for the DHCP lease to be expired') - self.wait_address_dropped('veth99', r'inet 192.168.5.11[0-9]*/24', ipv='-4', timeout_sec=120) + self.wait_address_dropped('veth99', f'inet {address1}/24', ipv='-4', timeout_sec=120) self.wait_address('veth99', r'inet 192.168.5.12[0-9]*/24', ipv='-4') self.wait_online(['veth99:routable', 'veth-peer:routable']) @@ -5285,10 +5295,20 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertIn('mtu 1492', output) self.assertIn('inet 192.168.5.250/24 brd 192.168.5.255 scope global veth99', output) - self.assertNotIn('192.168.5.11', output) + self.assertNotIn(f'{address1}', output) self.assertRegex(output, r'inet 192.168.5.12[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') self.assertNotIn('2600::', output) + output = check_output('ip -4 --json address show dev veth99') + for i in json.loads(output)[0]['addr_info']: + if i['label'] == 'test-label': + address2 = i['local'] + break + else: + self.assertFalse(True) + + self.assertRegex(address2, r'^192.168.5.12[0-9]$') + print('## ip route show table main dev veth99') output = check_output('ip route show table main dev veth99') print(output) @@ -5303,12 +5323,12 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print('## ip route show table 211 dev veth99') output = check_output('ip route show table 211 dev veth99') print(output) - self.assertRegex(output, 'default via 192.168.5.1 proto dhcp src 192.168.5.12[0-9] metric 24') - self.assertRegex(output, '192.168.5.0/24 proto dhcp scope link src 192.168.5.12[0-9] metric 24') - self.assertRegex(output, '192.168.5.1 proto dhcp scope link src 192.168.5.12[0-9] metric 24') + self.assertRegex(output, f'default via 192.168.5.1 proto dhcp src {address2} metric 24') + self.assertRegex(output, f'192.168.5.0/24 proto dhcp scope link src {address2} metric 24') + self.assertRegex(output, f'192.168.5.1 proto dhcp scope link src {address2} metric 24') self.assertNotIn('192.168.5.6', output) - self.assertRegex(output, '192.168.5.7 proto dhcp scope link src 192.168.5.12[0-9] metric 24') - self.assertRegex(output, '192.168.5.8 proto dhcp scope link src 192.168.5.12[0-9] metric 24') + self.assertRegex(output, f'192.168.5.7 proto dhcp scope link src {address2} metric 24') + self.assertRegex(output, f'192.168.5.8 proto dhcp scope link src {address2} metric 24') self.assertIn('10.0.0.0/8 via 192.168.5.1 proto dhcp', output) print('## link state file') @@ -5347,7 +5367,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): output = read_dnsmasq_log_file() print(output) self.assertIn('vendor class: FooBarVendorTest', output) - self.assertIn('DHCPDISCOVER(veth-peer) 192.168.5.11', output) + self.assertIn(f'DHCPDISCOVER(veth-peer) {address1} 12:34:56:78:9a:bc', output) self.assertIn('client provides name: test-hostname', output) self.assertIn('26:mtu', output) @@ -5387,7 +5407,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): state = get_dbus_dhcp4_client_state('veth99') print(f"State = {state}") - self.assertEqual(state, 'selecting') + self.assertEqual(state, 'rebooting') start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', '--dhcp-option=option:domain-search,example.com',