Merge pull request #29442 from yuwata/network-dhcp4-preferred-address

network: introduce [DHCPv4] PreferredAddress= setting
This commit is contained in:
Luca Boccassi 2023-10-05 12:25:42 +01:00 committed by GitHub
commit 9482d34fb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 25 deletions

View file

@ -2098,6 +2098,17 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
<!-- DHCP packet contents -->
<varlistentry>
<term><varname>RequestAddress=</varname></term>
<listitem>
<para>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.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>SendHostname=</varname></term>
<listitem>
@ -2232,6 +2243,7 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
are implied and these settings in the .network file are silently ignored. Also,
<varname>Hostname=</varname>,
<varname>MUDURL=</varname>,
<varname>RequestAddress</varname>,
<varname>RequestOptions=</varname>,
<varname>SendOption=</varname>,
<varname>SendVendorOption=</varname>,

View file

@ -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) {
@ -1487,6 +1505,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 +1638,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);
}

View file

@ -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)

View file

@ -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;

View file

@ -8,6 +8,7 @@ IPv6AcceptRA=no
Address=192.168.5.250/24
[DHCPv4]
RequestAddress=192.168.5.110
UseDomains=yes
UseMTU=yes
UseRoutes=yes

View file

@ -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',