Merge pull request #12511 from ssahani/high-avilability-12050

networkd: add new KeepConfiguration= setting
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2019-06-06 18:51:46 +02:00
committed by GitHub
13 changed files with 223 additions and 33 deletions

View File

@@ -838,6 +838,22 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>KeepConfiguration=</varname></term>
<listitem>
<para>Takes a boolean or one of <literal>static</literal>, <literal>dhcp-on-stop</literal>,
<literal>dhcp</literal>. When <literal>static</literal>, <command>systemd-networkd</command>
will not drop static addresses and routes on starting up process. When set to
<literal>dhcp-on-stop</literal>, <command>systemd-networkd</command> will not drop addresses
and routes on stopping the daemon. When <literal>dhcp</literal>,
the addresses and routes provided by a DHCP server will never be dropped even if the DHCP
lease expires. This is contrary to the DHCP specification, but may be the best choice if,
e.g., the root filesystem relies on this connection. The setting <literal>dhcp</literal>
implies <literal>dhcp-on-stop</literal>, and <literal>yes</literal> implies
<literal>dhcp</literal> and <literal>static</literal>. Defaults to
<literal>dhcp-on-stop</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
@@ -1381,17 +1397,6 @@
system. Defaults to <literal>no</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>CriticalConnection=</varname></term>
<listitem>
<para>When true, the connection will never be torn down
even if the DHCP lease expires. This is contrary to the
DHCP specification, but may be the best choice if, say,
the root filesystem relies on this connection. Defaults to
false.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>ClientIdentifier=</varname></term>
<listitem>

View File

@@ -464,7 +464,7 @@ static int dhcp_lease_renew(sd_dhcp_client *client, Link *link) {
if (r < 0)
return log_link_warning_errno(link, r, "DHCP error: no netmask: %m");
if (!link->network->dhcp_critical) {
if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
r = sd_dhcp_lease_get_lifetime(link->dhcp_lease, &lifetime);
if (r < 0)
return log_link_warning_errno(link, r, "DHCP error: no lifetime: %m");
@@ -581,7 +581,7 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
}
}
if (!link->network->dhcp_critical) {
if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
r = sd_dhcp_lease_get_lifetime(link->dhcp_lease, &lifetime);
if (r < 0)
return log_link_warning_errno(link, r, "DHCP error: no lifetime: %m");
@@ -649,7 +649,7 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m");
}
if (link->network->dhcp_critical) {
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it.");
return 0;
}
@@ -669,7 +669,7 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
case SD_DHCP_CLIENT_EVENT_EXPIRED:
case SD_DHCP_CLIENT_EVENT_IP_CHANGE:
if (link->network->dhcp_critical) {
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it.");
return 0;
}

View File

@@ -680,14 +680,15 @@ static void link_enter_unmanaged(Link *link) {
link_dirty(link);
}
int link_stop_clients(Link *link) {
int link_stop_clients(Link *link, bool may_keep_dhcp) {
int r = 0, k;
assert(link);
assert(link->manager);
assert(link->manager->event);
if (link->dhcp_client) {
if (link->dhcp_client && (!may_keep_dhcp || !link->network ||
!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP_ON_STOP))) {
k = sd_dhcp_client_stop(link->dhcp_client);
if (k < 0)
r = log_link_warning_errno(link, k, "Could not stop DHCPv4 client: %m");
@@ -731,7 +732,7 @@ void link_enter_failed(Link *link) {
link_set_state(link, LINK_STATE_FAILED);
link_stop_clients(link);
link_stop_clients(link, false);
link_dirty(link);
}
@@ -2485,6 +2486,33 @@ static bool link_is_static_route_configured(Link *link, Route *route) {
return false;
}
static bool link_address_is_dynamic(Link *link, Address *address) {
Route *route;
Iterator i;
assert(link);
assert(address);
if (address->cinfo.ifa_prefered != CACHE_INFO_INFINITY_LIFE_TIME)
return true;
/* Even when the address is leased from a DHCP server, networkd assign the address
* without lifetime when KeepConfiguration=dhcp. So, let's check that we have
* corresponding routes with RTPROT_DHCP. */
SET_FOREACH(route, link->routes_foreign, i) {
if (route->protocol != RTPROT_DHCP)
continue;
if (address->family != route->family)
continue;
if (in_addr_equal(address->family, &address->in_addr, &route->prefsrc))
return true;
}
return false;
}
static int link_drop_foreign_config(Link *link) {
Address *address;
Route *route;
@@ -2496,6 +2524,12 @@ static int link_drop_foreign_config(Link *link) {
if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1)
continue;
if (link_address_is_dynamic(link, address)) {
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP))
continue;
} else if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC))
continue;
if (link_is_static_address_configured(link, address)) {
r = address_add(link, address->family, &address->in_addr, address->prefixlen, NULL);
if (r < 0)
@@ -2512,6 +2546,14 @@ static int link_drop_foreign_config(Link *link) {
if (route->protocol == RTPROT_KERNEL)
continue;
if (route->protocol == RTPROT_STATIC &&
FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC))
continue;
if (route->protocol == RTPROT_DHCP &&
FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP))
continue;
if (link_is_static_route_configured(link, route)) {
r = route_add(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL);
if (r < 0)
@@ -2578,7 +2620,8 @@ static int link_configure(Link *link) {
/* Drop foreign config, but ignore loopback or critical devices.
* We do not want to remove loopback address or addresses used for root NFS. */
if (!(link->flags & IFF_LOOPBACK) && !(link->network->dhcp_critical)) {
if (!(link->flags & IFF_LOOPBACK) &&
link->network->keep_configuration != KEEP_CONFIGURATION_YES) {
r = link_drop_foreign_config(link);
if (r < 0)
return r;
@@ -3264,7 +3307,7 @@ static int link_carrier_lost(Link *link) {
if (link->setting_mtu)
return 0;
r = link_stop_clients(link);
r = link_stop_clients(link, false);
if (r < 0) {
link_enter_failed(link);
return r;

View File

@@ -175,7 +175,7 @@ int dhcp6_configure(Link *link);
int dhcp6_request_address(Link *link, int ir);
int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link);
int link_stop_clients(Link *link);
int link_stop_clients(Link *link, bool may_keep_dhcp);
const char* link_state_to_string(LinkState s) _const_;
LinkState link_state_from_string(const char *s) _pure_;

View File

@@ -1447,7 +1447,7 @@ void manager_free(Manager *m) {
if (link->dhcp6_client)
(void) dhcp6_lease_pd_prefix_lost(link->dhcp6_client, link);
link_stop_clients(link);
(void) link_stop_clients(link, true);
link_unref(link);
}

View File

@@ -87,6 +87,7 @@ Network.IPv6ProxyNDPAddress, config_parse_ipv6_proxy_ndp_address,
Network.BindCarrier, config_parse_strv, 0, offsetof(Network, bind_carrier)
Network.ConfigureWithoutCarrier, config_parse_bool, 0, offsetof(Network, configure_without_carrier)
Network.IgnoreCarrierLoss, config_parse_bool, 0, offsetof(Network, ignore_carrier_loss)
Network.KeepConfiguration, config_parse_keep_configuration, 0, offsetof(Network, keep_configuration)
Address.Address, config_parse_address, 0, 0
Address.Peer, config_parse_address, 0, 0
Address.Broadcast, config_parse_broadcast, 0, 0
@@ -143,7 +144,7 @@ DHCP.Anonymize, config_parse_bool,
DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname)
DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast)
DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical)
DHCP.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) /* deprecated */
DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier)
DHCP.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0
DHCP.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class)

View File

@@ -239,6 +239,22 @@ int network_verify(Network *network) {
network->dhcp_use_mtu = false;
}
if (network->dhcp_critical >= 0) {
if (network->keep_configuration >= 0)
log_warning("%s: Both KeepConfiguration= and deprecated CriticalConnection= are set. "
"Ignoring CriticalConnection=.", network->filename);
else if (network->dhcp_critical)
/* CriticalConnection=yes also preserve foreign static configurations. */
network->keep_configuration = KEEP_CONFIGURATION_YES;
else
/* For backward compatibility, we do not release DHCP addresses on manager stop. */
network->keep_configuration = KEEP_CONFIGURATION_DHCP_ON_STOP;
}
if (network->keep_configuration < 0)
/* For backward compatibility, we do not release DHCP addresses on manager stop. */
network->keep_configuration = KEEP_CONFIGURATION_DHCP_ON_STOP;
LIST_FOREACH_SAFE(addresses, address, address_next, network->static_addresses)
if (address_section_verify(address) < 0)
address_free(address);
@@ -324,6 +340,7 @@ int network_load_one(Manager *manager, const char *filename) {
.required_for_online = true,
.required_operstate_for_online = LINK_OPERSTATE_DEGRADED,
.dhcp = ADDRESS_FAMILY_NO,
.dhcp_critical = -1,
.dhcp_use_ntp = true,
.dhcp_use_dns = true,
.dhcp_use_hostname = true,
@@ -392,6 +409,8 @@ int network_load_one(Manager *manager, const char *filename) {
.ipv6_accept_ra_route_table = RT_TABLE_MAIN,
.ipv6_accept_ra_route_table_set = false,
.keep_configuration = _KEEP_CONFIGURATION_INVALID,
.can_triple_sampling = -1,
};
@@ -1752,3 +1771,16 @@ int config_parse_required_for_online(
return 0;
}
DEFINE_CONFIG_PARSE_ENUM(config_parse_keep_configuration, keep_configuration, KeepConfiguration,
"Failed to parse KeepConfiguration= setting");
static const char* const keep_configuration_table[_KEEP_CONFIGURATION_MAX] = {
[KEEP_CONFIGURATION_NO] = "no",
[KEEP_CONFIGURATION_DHCP_ON_STOP] = "dhcp-on-stop",
[KEEP_CONFIGURATION_DHCP] = "dhcp",
[KEEP_CONFIGURATION_STATIC] = "static",
[KEEP_CONFIGURATION_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(keep_configuration, KeepConfiguration, KEEP_CONFIGURATION_YES);

View File

@@ -78,6 +78,17 @@ typedef enum RADVPrefixDelegation {
_RADV_PREFIX_DELEGATION_INVALID = -1,
} RADVPrefixDelegation;
typedef enum KeepConfiguration {
KEEP_CONFIGURATION_NO = 0,
KEEP_CONFIGURATION_DHCP_ON_START = 1 << 0,
KEEP_CONFIGURATION_DHCP_ON_STOP = 1 << 1,
KEEP_CONFIGURATION_DHCP = KEEP_CONFIGURATION_DHCP_ON_START | KEEP_CONFIGURATION_DHCP_ON_STOP,
KEEP_CONFIGURATION_STATIC = 1 << 2,
KEEP_CONFIGURATION_YES = KEEP_CONFIGURATION_DHCP | KEEP_CONFIGURATION_STATIC,
_KEEP_CONFIGURATION_MAX,
_KEEP_CONFIGURATION_INVALID = -1,
} KeepConfiguration;
typedef struct Manager Manager;
struct Network {
@@ -119,7 +130,7 @@ struct Network {
bool dhcp_anonymize;
bool dhcp_send_hostname;
bool dhcp_broadcast;
bool dhcp_critical;
int dhcp_critical;
bool dhcp_use_dns;
bool dhcp_use_ntp;
bool dhcp_use_mtu;
@@ -227,6 +238,7 @@ struct Network {
bool unmanaged;
bool configure_without_carrier;
bool ignore_carrier_loss;
KeepConfiguration keep_configuration;
uint32_t iaid;
DUID duid;
@@ -318,6 +330,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_ntp);
CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_max_attempts);
CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration);
/* Legacy IPv4LL support */
CONFIG_PARSER_PROTOTYPE(config_parse_ipv4ll);
@@ -336,3 +349,6 @@ DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_;
const char* radv_prefix_delegation_to_string(RADVPrefixDelegation i) _const_;
RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_;
const char* keep_configuration_to_string(KeepConfiguration i) _const_;
KeepConfiguration keep_configuration_from_string(const char *s) _pure_;

View File

@@ -135,6 +135,7 @@ DHCPServer=
BindCarrier=
VRF=
IgnoreCarrierLoss=
KeepConfiguration=
[IPv6Prefix]
Prefix=
OnLink=

View File

@@ -0,0 +1,5 @@
[Match]
Name=dummy98
[Network]
KeepConfiguration=static

View File

@@ -4,6 +4,4 @@ Name=veth99
[Network]
DHCP=ipv4
IPv6AcceptRA=false
[DHCP]
CriticalConnection=true
KeepConfiguration=dhcp-on-stop

View File

@@ -0,0 +1,7 @@
[Match]
Name=veth99
[Network]
DHCP=ipv4
IPv6AcceptRA=false
KeepConfiguration=dhcp

View File

@@ -1235,6 +1235,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
'11-dummy.netdev',
'12-dummy.netdev',
'23-active-slave.network',
'24-keep-configuration-static.network',
'24-search-domain.network',
'25-address-link-section.network',
'25-address-preferred-lifetime-zero-ipv6.network',
@@ -1658,6 +1659,28 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertRegex(output, 'DNS: 192.168.42.1')
self.assertRegex(output, 'Search Domains: one')
def test_keep_configuration_static(self):
check_output('systemctl stop systemd-networkd')
check_output('ip link add name dummy98 type dummy')
check_output('ip address add 10.1.2.3/16 dev dummy98')
check_output('ip address add 10.2.3.4/16 dev dummy98 valid_lft 600 preferred_lft 500')
output = check_output('ip address show dummy98')
print(output)
self.assertRegex(output, 'inet 10.1.2.3/16 scope global dummy98')
self.assertRegex(output, 'inet 10.2.3.4/16 scope global dynamic dummy98')
output = check_output('ip route show dev dummy98')
print(output)
copy_unit_to_networkd_unit_path('24-keep-configuration-static.network')
start_networkd(0)
wait_online(['dummy98:routable'])
output = check_output('ip address show dummy98')
print(output)
self.assertRegex(output, 'inet 10.1.2.3/16 scope global dummy98')
self.assertNotRegex(output, 'inet 10.2.3.4/16 scope global dynamic dummy98')
class NetworkdBondTests(unittest.TestCase, Utilities):
links = [
'bond199',
@@ -2026,13 +2049,14 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
'25-vrf.netdev',
'25-vrf.network',
'dhcp-client-anonymize.network',
'dhcp-client-critical-connection.network',
'dhcp-client-gateway-onlink-implicit.network',
'dhcp-client-ipv4-dhcp-settings.network',
'dhcp-client-ipv4-only-ipv6-disabled.network',
'dhcp-client-ipv4-only.network',
'dhcp-client-ipv6-only.network',
'dhcp-client-ipv6-rapid-commit.network',
'dhcp-client-keep-configuration-dhcp-on-stop.network',
'dhcp-client-keep-configuration-dhcp.network',
'dhcp-client-listen-port.network',
'dhcp-client-route-metric.network',
'dhcp-client-route-table.network',
@@ -2210,26 +2234,84 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
print(output)
self.assertRegex(output, 'metric 24')
def test_dhcp_route_criticalconnection_true(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-v4-server-veth-peer.network', 'dhcp-client-critical-connection.network')
def test_dhcp_keep_configuration_dhcp(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-v4-server-veth-peer.network', 'dhcp-client-keep-configuration-dhcp.network')
start_networkd(0)
wait_online(['veth-peer:carrier'])
start_dnsmasq()
start_dnsmasq(lease_time='2m')
wait_online(['veth99:routable', 'veth-peer:routable'])
output = check_output('ip address show dev veth99 scope global')
print(output)
self.assertRegex(output, r'192.168.5.*')
output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
print(output)
self.assertRegex(output, '192.168.5.*')
self.assertRegex(output, r'192.168.5.*')
# Stopping dnsmasq as networkd won't be allowed to renew the DHCP lease.
stop_dnsmasq(dnsmasq_pid_file)
# Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120
print('Wait for the dynamic address to be expired')
time.sleep(125)
print('The lease address should be kept after lease expired')
output = check_output('ip address show dev veth99 scope global')
print(output)
self.assertRegex(output, r'192.168.5.*')
output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
print(output)
self.assertRegex(output, '192.168.5.*')
self.assertRegex(output, r'192.168.5.*')
check_output('systemctl stop systemd-networkd')
print('The lease address should be kept after networkd stopped')
output = check_output('ip address show dev veth99 scope global')
print(output)
self.assertRegex(output, r'192.168.5.*')
output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
print(output)
self.assertRegex(output, r'192.168.5.*')
check_output('systemctl start systemd-networkd')
wait_online(['veth-peer:routable'])
print('Still the lease address should be kept after networkd restarted')
output = check_output('ip address show dev veth99 scope global')
print(output)
self.assertRegex(output, r'192.168.5.*')
output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
print(output)
self.assertRegex(output, r'192.168.5.*')
def test_dhcp_keep_configuration_dhcp_on_stop(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-v4-server-veth-peer.network', 'dhcp-client-keep-configuration-dhcp-on-stop.network')
start_networkd(0)
wait_online(['veth-peer:carrier'])
start_dnsmasq(lease_time='2m')
wait_online(['veth99:routable', 'veth-peer:routable'])
output = check_output('ip address show dev veth99 scope global')
print(output)
self.assertRegex(output, r'192.168.5.*')
stop_dnsmasq(dnsmasq_pid_file)
check_output('systemctl stop systemd-networkd')
output = check_output('ip address show dev veth99 scope global')
print(output)
self.assertRegex(output, r'192.168.5.*')
start_networkd(0)
wait_online(['veth-peer:routable'])
output = check_output('ip address show dev veth99 scope global')
print(output)
self.assertNotRegex(output, r'192.168.5.*')
def test_dhcp_client_reuse_address_as_static(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', 'dhcp-client.network')