system: Network autoconfiguration
Add a czechlight-network:dhcp-client leaf into
ietf-interfaces:interfaces/interface/ietf-ip:ipv4 container which
indicates whether the implementation starts an DHCP client on IPv4.
For IPv6, we use
ietf-interfaces:interfaces/interface/ietf-ip:ipv6/autoconf container.
The value of autoconf/create-global-addresses leaf is mapped to
networkd's IPv6AcceptRA config value [1].
The 'DHCP=' option in systemd.network [2] allows multiple values
managing DHCPv4 and DHCPv6. We chose to ignore DHCPv6 because it will
(or won't) be triggered by router advertisement (RA) *regardless* of
this parameter [2]. It is sufficient to have RA on (which is managed by
the ipv6/autoconf leaf). In case RA is on but no routers are found,
systemd-network starts the DHCP client anyway [1]. If RA is off, then
no IPv6 autoconfiguration happens.
[1] https://systemd.network/systemd.network.html#IPv6AcceptRA=
[2] https://systemd.network/systemd.network.html#DHCP=
Change-Id: Ia885ae644d368987b6101f828bddfd0fc4e969dd
diff --git a/src/system/IETFInterfacesConfig.cpp b/src/system/IETFInterfacesConfig.cpp
index 9ac681a..61c68b9 100644
--- a/src/system/IETFInterfacesConfig.cpp
+++ b/src/system/IETFInterfacesConfig.cpp
@@ -127,7 +127,19 @@
configValues["Network"].push_back("LinkLocalAddressing=no");
}
- configValues["Network"].push_back("DHCP=no"); // temporarily disabled
+ // network autoconfiguration
+ if (auto node = utils::getUniqueSubtree(linkEntry, "ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses"); protocolEnabled(linkEntry, "ipv6") && utils::getValueAsString(node.value()) == "true"s) {
+ configValues["Network"].push_back("IPv6AcceptRA=true");
+ } else {
+ configValues["Network"].push_back("IPv6AcceptRA=false");
+ }
+
+ if (auto node = utils::getUniqueSubtree(linkEntry, "ietf-ip:ipv4/czechlight-network:dhcp-client"); protocolEnabled(linkEntry, "ipv4") && utils::getValueAsString(node.value()) == "true"s) {
+ configValues["Network"].push_back("DHCP=ipv4");
+ } else {
+ configValues["Network"].push_back("DHCP=no");
+ }
+
configValues["Network"].push_back("LLDP=true");
configValues["Network"].push_back("EmitLLDP=nearest-bridge");
diff --git a/tests/sysrepo_system-ietfinterfaces-sudo.cpp b/tests/sysrepo_system-ietfinterfaces-sudo.cpp
index e61bd33..c520b29 100644
--- a/tests/sysrepo_system-ietfinterfaces-sudo.cpp
+++ b/tests/sysrepo_system-ietfinterfaces-sudo.cpp
@@ -113,6 +113,7 @@
{"/ietf-ip:ipv6/address[ip='::ffff:192.0.2.1']", ""},
{"/ietf-ip:ipv6/address[ip='::ffff:192.0.2.1']/ip", "::ffff:192.0.2.1"},
{"/ietf-ip:ipv6/address[ip='::ffff:192.0.2.1']/prefix-length", "128"},
+ {"/ietf-ip:ipv6/autoconf", ""},
{"/name", IFACE},
{"/oper-status", "down"},
{"/phys-address", LINK_MAC},
@@ -198,6 +199,7 @@
iproute2_exec_and_wait(WAIT_BRIDGE, "link", "set", "dev", IFACE_BRIDGE, "up");
expectedBridge["/ietf-ip:ipv6"] = "";
+ expectedBridge["/ietf-ip:ipv6/autoconf"] = "";
expectedBridge["/ietf-ip:ipv6/address[ip='fe80::22:22ff:fe22:2222']"] = "";
expectedBridge["/ietf-ip:ipv6/address[ip='fe80::22:22ff:fe22:2222']/ip"] = "fe80::22:22ff:fe22:2222";
expectedBridge["/ietf-ip:ipv6/address[ip='fe80::22:22ff:fe22:2222']/prefix-length"] = "64";
@@ -229,6 +231,7 @@
expectedIface.erase("/ietf-ip:ipv4/address[ip='192.0.2.1']/ip");
expectedIface.erase("/ietf-ip:ipv4/address[ip='192.0.2.1']/prefix-length");
expectedIface.erase("/ietf-ip:ipv4");
+ expectedIface.erase("/ietf-ip:ipv6/autoconf");
expectedIface.erase("/ietf-ip:ipv6");
REQUIRE(dataFromSysrepoNoStatistics(client, "/ietf-interfaces:interfaces/interface[name='" + IFACE + "']", SR_DS_OPERATIONAL) == expectedIface);
REQUIRE(dataFromSysrepoNoStatistics(client, "/ietf-interfaces:interfaces/interface[name='" + IFACE_BRIDGE + "']", SR_DS_OPERATIONAL) == expectedBridge);
diff --git a/tests/sysrepo_system-ietfinterfaces.cpp b/tests/sysrepo_system-ietfinterfaces.cpp
index 2f0469d..dea8177 100644
--- a/tests/sysrepo_system-ietfinterfaces.cpp
+++ b/tests/sysrepo_system-ietfinterfaces.cpp
@@ -37,7 +37,7 @@
lo.erase(it);
}
- REQUIRE(lo == std::map<std::string, std::string> {
+ REQUIRE(lo == std::map<std::string, std::string>{
{"/name", "lo"},
{"/type", "iana-if-type:softwareLoopback"},
{"/phys-address", "00:00:00:00:00:00"},
@@ -47,6 +47,7 @@
{"/ietf-ip:ipv4/address[ip='127.0.0.1']/ip", "127.0.0.1"},
{"/ietf-ip:ipv4/address[ip='127.0.0.1']/prefix-length", "8"},
{"/ietf-ip:ipv6", ""},
+ {"/ietf-ip:ipv6/autoconf", ""},
{"/ietf-ip:ipv6/address[ip='::1']", ""},
{"/ietf-ip:ipv6/address[ip='::1']/ip", "::1"},
{"/ietf-ip:ipv6/address[ip='::1']/prefix-length", "128"},
@@ -167,24 +168,27 @@
client->apply_changes();
}
- SECTION("Enabled IPv4 protocol must have at least one IP")
+ SECTION("Enabled IPv4 protocol must have at least one IP or the autoconfiguration must be on")
{
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
REQUIRE_THROWS_AS(client->apply_changes(), sysrepo::sysrepo_exception);
}
- SECTION("Enabled IPv6 protocol must have at least one IP")
+ SECTION("Enabled IPv6 protocol must have at least one IP or the autoconfiguration must be on")
{
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/enabled", "true");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "false");
REQUIRE_THROWS_AS(client->apply_changes(), sysrepo::sysrepo_exception);
}
}
std::string expectedContents;
+
SECTION("Setting IPs to eth0")
{
const auto expectedFilePath = fakeConfigDir / "eth0.network";
@@ -195,6 +199,7 @@
{
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/description", "Hello world");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
expectedContents = R"([Match]
Name=eth0
@@ -202,6 +207,7 @@
Description=Hello world
Address=192.0.2.1/24
LinkLocalAddressing=no
+IPv6AcceptRA=false
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -212,6 +218,7 @@
{
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.2']/ietf-ip:prefix-length", "24");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
client->delete_item("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6");
expectedContents = R"([Match]
Name=eth0
@@ -220,6 +227,7 @@
Address=192.0.2.1/24
Address=192.0.2.2/24
LinkLocalAddressing=no
+IPv6AcceptRA=false
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -230,12 +238,14 @@
{
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
expectedContents = R"([Match]
Name=eth0
[Network]
Address=192.0.2.1/24
Address=2001:db8::1/32
+IPv6AcceptRA=true
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -246,6 +256,7 @@
{
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/enabled", "false");
expectedContents = R"([Match]
Name=eth0
@@ -253,6 +264,7 @@
[Network]
Address=192.0.2.1/24
LinkLocalAddressing=no
+IPv6AcceptRA=false
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -282,6 +294,7 @@
[Network]
Address=192.0.2.1/24
LinkLocalAddressing=no
+IPv6AcceptRA=false
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -291,6 +304,7 @@
[Network]
Address=2001:db8::1/32
+IPv6AcceptRA=true
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -298,6 +312,7 @@
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth1']/type", "iana-if-type:ethernetCsmacd");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth1']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/ietf-ip:prefix-length", "32");
@@ -328,6 +343,7 @@
[Network]
LinkLocalAddressing=no
+IPv6AcceptRA=false
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -338,6 +354,7 @@
[Network]
Bridge=br0
+IPv6AcceptRA=false
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -348,6 +365,7 @@
[Network]
Bridge=br0
+IPv6AcceptRA=false
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -379,12 +397,14 @@
// assign an IPv4 address to br0
client->set_item_str("/ietf-interfaces:interfaces/interface[name='br0']/enabled", "true");
client->set_item_str("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='br0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
expectedContentsBr0 = R"([Match]
Name=br0
[Network]
Address=192.0.2.1/24
LinkLocalAddressing=no
+IPv6AcceptRA=false
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -407,6 +427,7 @@
[Network]
Address=192.0.2.1/24
Address=2001:db8::1/32
+IPv6AcceptRA=true
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -430,6 +451,7 @@
[Network]
Address=2001:db8::2/32
+IPv6AcceptRA=true
DHCP=no
LLDP=true
EmitLLDP=nearest-bridge
@@ -501,4 +523,136 @@
REQUIRE(!std::filesystem::exists(expectedFilePathEth0));
}
}
+
+ SECTION("Network autoconfiguration")
+ {
+ const auto expectedFilePath = fakeConfigDir / "eth0.network";
+
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/type", "iana-if-type:ethernetCsmacd");
+
+ SECTION("IPv4 on with address, IPv6 disabled, DHCPv4 off, RA off")
+ {
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24"); // in case DHCP is disabled an IP must be present
+
+ expectedContents = R"([Match]
+Name=eth0
+
+[Network]
+Address=192.0.2.1/24
+LinkLocalAddressing=no
+IPv6AcceptRA=false
+DHCP=no
+LLDP=true
+EmitLLDP=nearest-bridge
+)";
+ }
+
+ SECTION("IPv4 on with address, IPv6 disabled, DHCPv4 on, RA on")
+ {
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/ietf-ip:prefix-length", "24"); // in case DHCP is disabled an IP must be present
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "false");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "true");
+
+ expectedContents = R"([Match]
+Name=eth0
+
+[Network]
+Address=192.0.2.1/24
+LinkLocalAddressing=no
+IPv6AcceptRA=false
+DHCP=ipv4
+LLDP=true
+EmitLLDP=nearest-bridge
+)";
+ }
+
+ SECTION("IPv4 disabled, IPv6 enabled, DHCPv4 on, RA on")
+ {
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "false");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "true");
+
+ expectedContents = R"([Match]
+Name=eth0
+
+[Network]
+IPv6AcceptRA=true
+DHCP=no
+LLDP=true
+EmitLLDP=nearest-bridge
+)";
+ }
+
+ SECTION("IPv4 enabled, IPv6 enabled, DHCPv4 on, RA on")
+ {
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "true");
+
+ expectedContents = R"([Match]
+Name=eth0
+
+[Network]
+IPv6AcceptRA=true
+DHCP=ipv4
+LLDP=true
+EmitLLDP=nearest-bridge
+)";
+ }
+
+ SECTION("IPv4 enabled, IPv6 enabled, DHCPv4 off, RA on")
+ {
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:enabled", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/prefix-length", "24");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:enabled", "true");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "true");
+
+ expectedContents = R"([Match]
+Name=eth0
+
+[Network]
+Address=192.0.2.1/24
+IPv6AcceptRA=true
+DHCP=no
+LLDP=true
+EmitLLDP=nearest-bridge
+)";
+ }
+
+ SECTION("IPv4 disabled, IPv6 disabled, DHCPv4 off, RA off")
+ {
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/ietf-ip:address[ip='192.0.2.1']/prefix-length", "24");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv4/czechlight-network:dhcp-client", "false");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:address[ip='2001:db8::1']/prefix-length", "32");
+ client->set_item_str("/ietf-interfaces:interfaces/interface[name='eth0']/ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses", "false");
+
+ expectedContents = R"([Match]
+Name=eth0
+
+[Network]
+Address=192.0.2.1/24
+Address=2001:db8::1/32
+IPv6AcceptRA=false
+DHCP=no
+LLDP=true
+EmitLLDP=nearest-bridge
+)";
+ }
+
+ REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
+ client->apply_changes();
+ REQUIRE(std::filesystem::exists(expectedFilePath));
+ REQUIRE(velia::utils::readFileToString(expectedFilePath) == expectedContents);
+
+ // reset the contents
+ client->delete_item("/ietf-interfaces:interfaces/interface[name='eth0']");
+ REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth0"})).IN_SEQUENCE(seq1);
+ client->apply_changes();
+ REQUIRE(!std::filesystem::exists(expectedFilePath));
+ }
}
diff --git a/yang/czechlight-network@2021-02-22.yang b/yang/czechlight-network@2021-02-22.yang
index 2d67e8b..7c352aa 100644
--- a/yang/czechlight-network@2021-02-22.yang
+++ b/yang/czechlight-network@2021-02-22.yang
@@ -104,25 +104,32 @@
}
}
+ augment /if:interfaces/if:interface/ip:ipv4 {
+ description "Add the IPv4 autoconfiguration option (DHCP client).";
+
+ leaf dhcp-client {
+ type boolean;
+ default true;
+ description "Enable DHCP client.";
+ }
+ }
+
deviation /if:interfaces/if:interface/ip:ipv4 {
deviate add {
- must 'ip:enabled = "false" or count(ip:address) > 0' {
- error-message "There must always be at least one IPv4 address unless the protocol is disabled.";
+ must 'ip:enabled = "false" or count(ip:address) > 0 or cla-network:dhcp-client = "true"' {
+ error-message "There must always be at least one IPv4 address or the autoconfiguration must be turned on unless the protocol is disabled.";
}
}
}
deviation /if:interfaces/if:interface/ip:ipv6 {
deviate add {
- must 'ip:enabled = "false" or count(ip:address) > 0' {
- error-message "There must always be at least one IPv6 address unless the protocol is disabled.";
+ must 'ip:enabled = "false" or count(ip:address) > 0 or ip:autoconf/ip:create-global-addresses = "true"' {
+ error-message "There must always be at least one IPv6 address or the autoconfiguration must be turned on unless the protocol is disabled.";
}
}
}
- // IPv6 address autoconfiguration is not supported
- deviation /if:interfaces/if:interface/ip:ipv6/ip:autoconf { deviate not-supported; }
-
identity dhcp {
base rt:routing-protocol;
description "Identity for route installed by DHCP.";