From 2b341db89548ebc4abcf86e41f82f46e22fb23a3 Mon Sep 17 00:00:00 2001
Date: Tue, 31 Jan 2023 11:40:33 -0700
Subject: [PATCH] Enable IPv4LLStartAddress
Enable IPv4LLStartAddress
---
man/systemd.network.xml | 13 +++++-
src/basic/in-addr-util.c | 14 +++++++
src/basic/in-addr-util.h | 1 +
src/libsystemd-network/sd-ipv4ll.c | 11 +----
src/libsystemd-network/test-ipv4ll-manual.c | 26 ++++++++----
src/libsystemd-network/test-ipv4ll.c | 19 ++++++++-
src/network/networkd-dhcp4.c | 12 ++++++
src/network/networkd-ipv4ll.c | 42 +++++++++++++++++++
src/network/networkd-ipv4ll.h | 1 +
src/network/networkd-link.c | 6 +++
src/network/networkd-network-gperf.gperf | 1 +
src/network/networkd-network.h | 1 +
.../fuzz-network-parser/directives.network | 1 +
13 files changed, 127 insertions(+), 21 deletions(-)
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 3e8e5357cc..d1488a7e6e 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -479,6 +479,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>IPv4LLStartAddress=</varname></term>
+ <listitem>
+ <para>Specifies the first IPv4 link-local address to try. Takes an IPv4 address for example
+ 169.254.1.2, from the link-local address range: 169.254.0.0/16 except for 169.254.0.0/24 and
+ 169.254.255.0/24. This setting may be useful if the device should always have the same address
+ as long as there is no address conflict. When unset, a random address will be automatically
+ selected. Defaults to unset.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>IPv4LLRoute=</varname></term>
<listitem>
@@ -2592,7 +2603,7 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
<varlistentry>
<term><varname>ServerAddress=</varname></term>
<listitem><para>Specifies server address for the DHCP server. Takes an IPv4 address with prefix
- length, for example <literal>192.168.0.1/24</literal>. This setting may be useful when the link on
+ length, for example 192.168.0.1/24. This setting may be useful when the link on
which the DHCP server is running has multiple static addresses. When unset, one of static addresses
in the link will be automatically selected. Defaults to unset.</para></listitem>
</varlistentry>
diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c
index a43a831991..2d81ff709d 100644
--- a/src/basic/in-addr-util.c
+++ b/src/basic/in-addr-util.c
@@ -48,6 +48,20 @@ bool in4_addr_is_link_local(const struct in_addr *a) {
return (be32toh(a->s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16);
}
+bool in4_addr_is_link_local_dynamic(const struct in_addr *a) {
+ assert(a);
+
+ if (!in4_addr_is_link_local(a))
+ return false;
+
+ /* 169.254.0.0/24 and 169.254.255.0/24 must not be used for the dynamic IPv4LL assignment.
+ * See RFC 3927 Section 2.1:
+ * The IPv4 prefix 169.254/16 is registered with the IANA for this purpose. The first 256 and last
+ * 256 addresses in the 169.254/16 prefix are reserved for future use and MUST NOT be selected by a
+ * host using this dynamic configuration mechanism. */
+ return !IN_SET(be32toh(a->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U);
+}
+
bool in6_addr_is_link_local(const struct in6_addr *a) {
assert(a);
diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h
index 0178391e5f..e07bcba8e5 100644
--- a/src/basic/in-addr-util.h
+++ b/src/basic/in-addr-util.h
@@ -43,6 +43,7 @@ static inline bool in_addr_data_is_set(const struct in_addr_data *a) {
int in_addr_is_multicast(int family, const union in_addr_union *u);
bool in4_addr_is_link_local(const struct in_addr *a);
+bool in4_addr_is_link_local_dynamic(const struct in_addr *a);
bool in6_addr_is_link_local(const struct in6_addr *a);
int in_addr_is_link_local(int family, const union in_addr_union *u);
bool in6_addr_is_link_local_all_nodes(const struct in6_addr *a);
diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c
index ff065fd1b6..e4ef56b958 100644
--- a/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/libsystemd-network/sd-ipv4ll.c
@@ -212,21 +212,12 @@ int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
return sd_ipv4acd_is_running(ll->acd);
}
-static bool ipv4ll_address_is_valid(const struct in_addr *address) {
- assert(address);
-
- if (!in4_addr_is_link_local(address))
- return false;
-
- return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U);
-}
-
int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
int r;
assert_return(ll, -EINVAL);
assert_return(address, -EINVAL);
- assert_return(ipv4ll_address_is_valid(address), -EINVAL);
+ assert_return(in4_addr_is_link_local_dynamic(address), -EINVAL);
r = sd_ipv4acd_set_address(ll->acd, address);
if (r < 0)
diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c
index 3fea894f30..57244cb5d7 100644
--- a/src/libsystemd-network/test-ipv4ll-manual.c
+++ b/src/libsystemd-network/test-ipv4ll-manual.c
@@ -42,7 +42,7 @@ static void ll_handler(sd_ipv4ll *ll, int event, void *userdata) {
}
}
-static int client_run(int ifindex, const char *seed_str, const struct ether_addr *ha, sd_event *e) {
+static int client_run(int ifindex, const char *seed_str, const struct in_addr *start_address, const struct ether_addr *ha, sd_event *e) {
sd_ipv4ll *ll;
assert_se(sd_ipv4ll_new(&ll) >= 0);
@@ -60,6 +60,9 @@ static int client_run(int ifindex, const char *seed_str, const struct ether_addr
assert_se(sd_ipv4ll_set_address_seed(ll, seed) >= 0);
}
+ if (start_address && in4_addr_is_set(start_address))
+ assert_se(sd_ipv4ll_set_address(ll, start_address) >= 0);
+
log_info("starting IPv4LL client");
assert_se(sd_ipv4ll_start(ll) >= 0);
@@ -71,7 +74,7 @@ static int client_run(int ifindex, const char *seed_str, const struct ether_addr
return EXIT_SUCCESS;
}
-static int test_ll(const char *ifname, const char *seed) {
+static int test_ll(const char *ifname, const char *seed, const struct in_addr *start_address) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL;
@@ -90,7 +93,7 @@ static int test_ll(const char *ifname, const char *seed) {
assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0);
assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0);
- client_run(ifindex, seed, &ha, e);
+ client_run(ifindex, seed, start_address, &ha, e);
return EXIT_SUCCESS;
}
@@ -99,12 +102,19 @@ int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
if (argc == 2)
- return test_ll(argv[1], NULL);
- else if (argc == 3)
- return test_ll(argv[1], argv[2]);
- else {
+ return test_ll(argv[1], NULL, NULL);
+ else if (argc == 3) {
+ int r;
+ union in_addr_union a;
+
+ r = in_addr_from_string(AF_INET, argv[2], &a);
+ if (r < 0)
+ return test_ll(argv[1], argv[2], NULL);
+ else
+ return test_ll(argv[1], NULL, &a.in);
+ } else {
log_error("This program takes one or two arguments.\n"
- "\t %s <ifname> [<seed>]", program_invocation_short_name);
+ "\t %s <ifname> [<seed>|<start_address>]", program_invocation_short_name);
return EXIT_FAILURE;
}
}
diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c
index e90e73459d..17dc34333c 100644
--- a/src/libsystemd-network/test-ipv4ll.c
+++ b/src/libsystemd-network/test-ipv4ll.c
@@ -122,7 +122,7 @@ static void test_public_api_setters(sd_event *e) {
assert_se(sd_ipv4ll_unref(ll) == NULL);
}
-static void test_basic_request(sd_event *e) {
+static void test_basic_request(sd_event *e, const struct in_addr *start_address) {
sd_ipv4ll *ll;
struct ether_arp arp;
@@ -133,6 +133,8 @@ static void test_basic_request(sd_event *e) {
printf("* %s\n", __func__);
assert_se(sd_ipv4ll_new(&ll) == 0);
+ if (in4_addr_is_set(start_address))
+ assert_se(sd_ipv4ll_set_address(ll, start_address) >= 0);
assert_se(sd_ipv4ll_start(ll) == -EINVAL);
assert_se(sd_ipv4ll_attach_event(ll, e, 0) == 0);
@@ -168,6 +170,13 @@ static void test_basic_request(sd_event *e) {
sd_event_run(e, UINT64_MAX);
assert_se(basic_request_handler_bind == 1);
+
+ if (in4_addr_is_set(start_address)) {
+ struct in_addr address;
+
+ assert_se(sd_ipv4ll_get_address(ll, &address) >= 0);
+ assert_se(start_address->s_addr == address.s_addr);
+ }
}
sd_ipv4ll_stop(ll);
@@ -179,6 +188,7 @@ static void test_basic_request(sd_event *e) {
}
int main(int argc, char *argv[]) {
+ struct in_addr start_address = {};
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
test_setup_logging(LOG_DEBUG);
@@ -186,7 +196,12 @@ int main(int argc, char *argv[]) {
assert_se(sd_event_new(&e) >= 0);
test_public_api_setters(e);
- test_basic_request(e);
+ test_basic_request(e, &start_address);
+
+ basic_request_handler_bind = 0;
+ basic_request_handler_stop = 0;
+ start_address.s_addr = htobe32(169U << 24 | 254U << 16 | 1U << 8 | 2U);
+ test_basic_request(e, &start_address);
return 0;
}
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index cb9c428ae9..9320b346d4 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -1128,6 +1128,12 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
if (link->ipv4ll) {
log_link_debug(link, "DHCP client is stopped. Acquiring IPv4 link-local address");
+ if (in4_addr_is_set(&link->network->ipv4ll_start_address)) {
+ r = sd_ipv4ll_set_address(link->ipv4ll, &link->network->ipv4ll_start_address);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not set IPv4 link-local start address: %m");;
+ }
+
r = sd_ipv4ll_start(link->ipv4ll);
if (r < 0)
return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m");
@@ -1211,6 +1217,12 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
if (link->ipv4ll && !sd_ipv4ll_is_running(link->ipv4ll)) {
log_link_debug(link, "Problems acquiring DHCP lease, acquiring IPv4 link-local address");
+ if (in4_addr_is_set(&link->network->ipv4ll_start_address)) {
+ r = sd_ipv4ll_set_address(link->ipv4ll, &link->network->ipv4ll_start_address);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not set IPv4 link-local start address: %m");;
+ }
+
r = sd_ipv4ll_start(link->ipv4ll);
if (r < 0)
return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m");
diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c
index d05182fde7..21ce6eac68 100644
--- a/src/network/networkd-ipv4ll.c
+++ b/src/network/networkd-ipv4ll.c
@@ -263,3 +263,45 @@ int config_parse_ipv4ll(
return 0;
}
+
+int config_parse_ipv4ll_address(
+ const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ union in_addr_union a;
+ struct in_addr *ipv4ll_address = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *ipv4ll_address = (struct in_addr) {};
+ return 0;
+ }
+
+ r = in_addr_from_string(AF_INET, rvalue, &a);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+ if (!in4_addr_is_link_local_dynamic(&a.in)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Specified address cannot be used as an IPv4 link local address, ignoring assignment: %s",
+ rvalue);
+ return 0;
+ }
+
+ *ipv4ll_address = a.in;
+ return 0;
+}
\ No newline at end of file
diff --git a/src/network/networkd-ipv4ll.h b/src/network/networkd-ipv4ll.h
index 82acc2ec70..f5c6928535 100644
--- a/src/network/networkd-ipv4ll.h
+++ b/src/network/networkd-ipv4ll.h
@@ -11,3 +11,4 @@ int ipv4ll_configure(Link *link);
int ipv4ll_update_mac(Link *link);
CONFIG_PARSER_PROTOTYPE(config_parse_ipv4ll);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipv4ll_address);
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index b62a154828..beb911fae5 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -675,6 +675,12 @@ static int link_acquire_dynamic_ipv4_conf(Link *link) {
log_link_debug(link, "Acquiring DHCPv4 lease.");
} else if (link->ipv4ll) {
+ if (in4_addr_is_set(&link->network->ipv4ll_start_address)) {
+ r = sd_ipv4ll_set_address(link->ipv4ll, &link->network->ipv4ll_start_address);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not set IPv4 link-local start address: %m");
+ }
+
r = sd_ipv4ll_start(link->ipv4ll);
if (r < 0)
return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m");
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 4c3bf97311..58b42b9f07 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -104,6 +104,7 @@ Network.DHCPServer, config_parse_bool,
Network.LinkLocalAddressing, config_parse_link_local_address_family, 0, offsetof(Network, link_local)
Network.IPv6LinkLocalAddressGenerationMode, config_parse_ipv6_link_local_address_gen_mode, 0, offsetof(Network, ipv6ll_address_gen_mode)
Network.IPv6StableSecretAddress, config_parse_in_addr_non_null, AF_INET6, offsetof(Network, ipv6ll_stable_secret)
+Network.IPv4LLStartAddress, config_parse_ipv4ll_address, 0, offsetof(Network, ipv4ll_start_address)
Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route)
Network.DefaultRouteOnDevice, config_parse_bool, 0, offsetof(Network, default_route_on_device)
Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode)
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index f7eb37aced..732b7ef502 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -213,6 +213,7 @@ struct Network {
AddressFamily link_local;
IPv6LinkLocalAddressGenMode ipv6ll_address_gen_mode;
struct in6_addr ipv6ll_stable_secret;
+ struct in_addr ipv4ll_start_address;
bool ipv4ll_route;
/* IPv6 RA support */
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 48f9ad6fba..f149a72cf3 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -233,6 +233,7 @@ VXLAN=
L2TP=
MACsec=
LinkLocalAddressing=
+IPv4LLStartAddress=
IPv6LinkLocalAddressGenerationMode=
IPv6StableSecretAddress=
ConfigureWithoutCarrier=