diff --git a/man/systemd.network.xml b/man/systemd.network.xml index eae27389ede..67177183042 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -398,6 +398,18 @@ + + IPv4LLStartAddress= + + 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. + 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. + + + + IPv4LLRoute= @@ -2534,7 +2546,7 @@ Token=prefixstable:2002:da8:1:: ServerAddress= Specifies server address for the DHCP server. Takes an IPv4 address with prefix - length, for example 192.168.0.1/24. 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. diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c index e79758c5f5c..d5ef3535ea8 100644 --- a/src/libsystemd-network/test-ipv4ll-manual.c +++ b/src/libsystemd-network/test-ipv4ll-manual.c @@ -39,7 +39,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); @@ -57,6 +57,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); @@ -68,7 +71,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; @@ -87,7 +90,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; } @@ -96,12 +99,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 []", program_invocation_short_name); + "\t %s [|]", program_invocation_short_name); return EXIT_FAILURE; } } diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c index 44d0bfe17c3..2a078a502e7 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 20db4f5f837..79d73804605 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1071,6 +1071,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"); @@ -1154,6 +1160,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 67eb9d2aa1c..8833bee2330 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -263,3 +263,44 @@ 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 = ASSERT_PTR(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(&a.in)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Not a IPv4 link local address, ignoring assignment: %s", rvalue); + return 0; + } + + *ipv4ll_address = a.in; + return 0; +} diff --git a/src/network/networkd-ipv4ll.h b/src/network/networkd-ipv4ll.h index 82acc2ec709..f5c69285350 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 b314f5db8f2..94ef20b3eaa 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -611,6 +611,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 ceaaa6a0f7c..0b0c8da27b5 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -106,6 +106,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 98e6159040e..c653124a9c6 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -208,6 +208,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 b/test/fuzz/fuzz-network-parser/directives index 276f3c93076..ea0de6660c7 100644 --- a/test/fuzz/fuzz-network-parser/directives +++ b/test/fuzz/fuzz-network-parser/directives @@ -235,6 +235,7 @@ VXLAN= L2TP= MACsec= LinkLocalAddressing= +IPv4LLStartAddress= IPv6LinkLocalAddressGenerationMode= IPv6StableSecretAddress= ConfigureWithoutCarrier=