]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: dhcp-server: introduce [DHCPServerStaticLease] section
authorborna-blazevic <borna.blazevic@sartura.hr>
Tue, 20 Oct 2020 19:46:15 +0000 (21:46 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 27 May 2021 06:51:26 +0000 (15:51 +0900)
man/systemd.network.xml
src/network/meson.build
src/network/networkd-dhcp-server-static-lease.c [new file with mode: 0644]
src/network/networkd-dhcp-server-static-lease.h [new file with mode: 0644]
src/network/networkd-dhcp-server.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
test/fuzz/fuzz-network-parser/dhcp-server-static-lease.network [new file with mode: 0644]
test/fuzz/fuzz-network-parser/directives.network

index 00bbd0f7d8acfa2b77be30aa66c71d3c01edeb7a..18a2c1030ef0163a461e2aa7c6570718f41ea5ca 100644 (file)
@@ -2535,6 +2535,29 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
     </variablelist>
   </refsect1>
 
+  <refsect1>
+    <title>[DHCPServerStaticLease] Section Options</title>
+    <para>The <literal>[DHCPServerStaticLease]</literal> section configures a static DHCP lease to
+    assign a pre-set IPv4 address to a specific device based on its MAC address. This section can be
+    specified multiple times.</para>
+
+    <variablelist class='network-directives'>
+      <varlistentry>
+        <term><varname>MACAddress=</varname></term>
+
+        <listitem><para>The hardware address of a device which should be assigned IPv4 address
+        specified in <varname>Address=</varname>. This key is mandatory.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>Address=</varname></term>
+
+        <listitem><para>IPv4 address that should be assigned to a device with a hardware address
+        specified in <varname>MACAddress=</varname>. This key is mandatory.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
   <refsect1>
     <title>[IPv6SendRA] Section Options</title>
     <para>The [IPv6SendRA] section contains settings for sending IPv6 Router Advertisements and whether
index 5892f670e211f7c9e787bbc53c7a2fd21d1c41fb..19eadc6d1cd4a1196b06837ed3b497e585718ba2 100644 (file)
@@ -69,6 +69,8 @@ sources = files('''
         networkd-dhcp-common.h
         networkd-dhcp-server-bus.c
         networkd-dhcp-server-bus.h
+        networkd-dhcp-server-static-lease.c
+        networkd-dhcp-server-static-lease.h
         networkd-dhcp-server.c
         networkd-dhcp-server.h
         networkd-dhcp4.c
diff --git a/src/network/networkd-dhcp-server-static-lease.c b/src/network/networkd-dhcp-server-static-lease.c
new file mode 100644 (file)
index 0000000..0d73d32
--- /dev/null
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "hashmap.h"
+#include "networkd-dhcp-server-static-lease.h"
+#include "networkd-network.h"
+#include "networkd-util.h"
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(DHCPStaticLease, dhcp_static_lease_free);
+
+DHCPStaticLease *dhcp_static_lease_free(DHCPStaticLease *static_lease) {
+        if (!static_lease)
+                return NULL;
+
+        if (static_lease->network && static_lease->section)
+                hashmap_remove(static_lease->network->dhcp_static_leases_by_section, static_lease->section);
+
+        network_config_section_free(static_lease->section);
+        free(static_lease->client_id);
+        return mfree(static_lease);
+}
+
+static int dhcp_static_lease_new(DHCPStaticLease **ret) {
+        DHCPStaticLease *p;
+
+        assert(ret);
+
+        p = new0(DHCPStaticLease, 1);
+        if (!p)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(p);
+        return 0;
+}
+
+static int lease_new_static(Network *network, const char *filename, unsigned section_line, DHCPStaticLease **ret) {
+        _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+        _cleanup_(dhcp_static_lease_freep) DHCPStaticLease *static_lease = NULL;
+        int r;
+
+        assert(network);
+        assert(filename);
+        assert(section_line > 0);
+        assert(ret);
+
+        r = network_config_section_new(filename, section_line, &n);
+        if (r < 0)
+                return r;
+
+        static_lease = hashmap_get(network->dhcp_static_leases_by_section, n);
+        if (static_lease) {
+                *ret = TAKE_PTR(static_lease);
+                return 0;
+        }
+
+        r = dhcp_static_lease_new(&static_lease);
+        if (r < 0)
+                return r;
+
+        static_lease->network = network;
+        static_lease->section = TAKE_PTR(n);
+        r = hashmap_ensure_put(&network->dhcp_static_leases_by_section, &network_config_hash_ops, static_lease->section, static_lease);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(static_lease);
+        return 0;
+}
+
+static int static_lease_verify(DHCPStaticLease *static_lease) {
+        if (section_is_invalid(static_lease->section))
+                return -EINVAL;
+
+        if (in4_addr_is_null(&static_lease->address))
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                         "%s: DHCP static lease without Address= field configured. "
+                                         "Ignoring [DHCPServerStaticLease] section from line %u.",
+                                         static_lease->section->filename, static_lease->section->line);
+
+        /* TODO: check that the address is in the pool. */
+
+        if (static_lease->client_id_size == 0 || !static_lease->client_id)
+                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+                                         "%s: DHCP static lease without MACAddress= field configured. "
+                                         "Ignoring [DHCPServerStaticLease] section from line %u.",
+                                         static_lease->section->filename, static_lease->section->line);
+
+        assert(static_lease->client_id_size == ETH_ALEN + 1);
+
+        return 0;
+}
+
+void network_drop_invalid_static_leases(Network *network) {
+        DHCPStaticLease *static_lease;
+
+        assert(network);
+
+        HASHMAP_FOREACH(static_lease, network->dhcp_static_leases_by_section)
+                if (static_lease_verify(static_lease) < 0)
+                        dhcp_static_lease_free(static_lease);
+}
+
+int config_parse_dhcp_static_lease_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) {
+
+        _cleanup_(dhcp_static_lease_free_or_set_invalidp) DHCPStaticLease *lease = NULL;
+        Network *network = userdata;
+        union in_addr_union addr;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(network);
+
+        r = lease_new_static(network, filename, section_line, &lease);
+        if (r < 0)
+                return log_oom();
+
+        if (isempty(rvalue)) {
+                lease->address.s_addr = 0;
+                TAKE_PTR(lease);
+                return 0;
+        }
+
+        r = in_addr_from_string(AF_INET, rvalue, &addr);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse IPv4 address for DHCPv4 static lease, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+        if (in4_addr_is_null(&addr.in)) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "IPv4 address for DHCPv4 static lease cannot be the ANY address, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+
+        lease->address = addr.in;
+
+        TAKE_PTR(lease);
+        return 0;
+}
+
+int config_parse_dhcp_static_lease_hwaddr(
+                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) {
+
+        _cleanup_(dhcp_static_lease_free_or_set_invalidp) DHCPStaticLease *lease = NULL;
+        Network *network = userdata;
+        struct ether_addr hwaddr;
+        uint8_t *c;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(network);
+
+        r = lease_new_static(network, filename, section_line, &lease);
+        if (r < 0)
+                return log_oom();
+
+        if (isempty(rvalue)) {
+                lease->client_id = mfree(lease->client_id);
+                lease->client_id_size = 0;
+                return 0;
+        }
+
+        r = ether_addr_from_string(rvalue, &hwaddr);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse MAC address for DHCPv4 static lease, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+        if (ether_addr_is_null(&hwaddr) || (hwaddr.ether_addr_octet[0] & 0x01)) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "MAC address for DHCPv4 static lease cannot be null or multicast, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+
+        c = new(uint8_t, ETH_ALEN + 1);
+        if (!c)
+                return log_oom();
+
+        /* set client id type to 1: Ethernet Link-Layer (RFC 2132) */
+        c[0] = 0x01;
+        memcpy(c + 1, &hwaddr, ETH_ALEN);
+
+        free_and_replace(lease->client_id, c);
+        lease->client_id_size = ETH_ALEN + 1;
+
+        TAKE_PTR(lease);
+        return 0;
+}
diff --git a/src/network/networkd-dhcp-server-static-lease.h b/src/network/networkd-dhcp-server-static-lease.h
new file mode 100644 (file)
index 0000000..81c3598
--- /dev/null
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+#include "conf-parser.h"
+#include "in-addr-util.h"
+
+typedef struct Network Network;
+typedef struct NetworkConfigSection NetworkConfigSection;
+
+typedef struct DHCPStaticLease {
+        Network *network;
+        NetworkConfigSection *section;
+
+        struct in_addr address;
+        uint8_t *client_id;
+        size_t client_id_size;
+} DHCPStaticLease;
+
+DHCPStaticLease *dhcp_static_lease_free(DHCPStaticLease *lease);
+void network_drop_invalid_static_leases(Network *network);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_static_lease_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_static_lease_hwaddr);
index 36707b6e8f0168a7e2548133d5b5b8ab95a747fe..045c40e9c577762e38293034b631ea1666946cf7 100644 (file)
@@ -9,8 +9,9 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "networkd-address.h"
-#include "networkd-dhcp-server.h"
 #include "networkd-dhcp-server-bus.h"
+#include "networkd-dhcp-server-static-lease.h"
+#include "networkd-dhcp-server.h"
 #include "networkd-link.h"
 #include "networkd-manager.h"
 #include "networkd-network.h"
@@ -291,6 +292,7 @@ static int dhcp4_server_set_dns_from_resolve_conf(Link *link) {
 int dhcp4_server_configure(Link *link) {
         bool acquired_uplink = false;
         sd_dhcp_option *p;
+        DHCPStaticLease *static_lease;
         Link *uplink = NULL;
         Address *address;
         bool bind_to_interface;
@@ -439,6 +441,12 @@ int dhcp4_server_configure(Link *link) {
                         return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
         }
 
+        HASHMAP_FOREACH(static_lease, link->network->dhcp_static_leases_by_section) {
+                r = sd_dhcp_server_set_static_lease(link->dhcp_server, &static_lease->address, static_lease->client_id, static_lease->client_id_size);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Failed to set DHCPv4 static lease for DHCP server: %m");
+        }
+
         if (!sd_dhcp_server_is_running(link->dhcp_server)) {
                 r = sd_dhcp_server_start(link->dhcp_server);
                 if (r < 0)
index c2223b2c16092c9f84c2205a83de68256d884bc6..a0da17ab031b1cc01bc8e467ee2a5404b944f8cc 100644 (file)
@@ -12,6 +12,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
 #include "networkd-bridge-fdb.h"
 #include "networkd-can.h"
 #include "networkd-dhcp-common.h"
+#include "networkd-dhcp-server-static-lease.h"
 #include "networkd-dhcp-server.h"
 #include "networkd-dhcp4.h"
 #include "networkd-dhcp6.h"
@@ -288,6 +289,8 @@ DHCPServer.PoolSize,                         config_parse_uint32,
 DHCPServer.SendVendorOption,                 config_parse_dhcp_send_option,                            0,                             offsetof(Network, dhcp_server_send_vendor_options)
 DHCPServer.SendOption,                       config_parse_dhcp_send_option,                            0,                             offsetof(Network, dhcp_server_send_options)
 DHCPServer.BindToInterface,                  config_parse_bool,                                        0,                             offsetof(Network, dhcp_server_bind_to_interface)
+DHCPServerStaticLease.Address,               config_parse_dhcp_static_lease_address,                   0,                             0
+DHCPServerStaticLease.MACAddress,            config_parse_dhcp_static_lease_hwaddr,                    0,                             0
 Bridge.Cost,                                 config_parse_uint32,                                      0,                             offsetof(Network, cost)
 Bridge.UseBPDU,                              config_parse_tristate,                                    0,                             offsetof(Network, use_bpdu)
 Bridge.HairPin,                              config_parse_tristate,                                    0,                             offsetof(Network, hairpin)
index 5c8b1afd0f58c1732cbec9d71f89f8ae66c09446..3f2dac62c51ac5bdb533758af5ff8de4d7ea00ac 100644 (file)
@@ -17,6 +17,7 @@
 #include "networkd-address.h"
 #include "networkd-bridge-fdb.h"
 #include "networkd-dhcp-common.h"
+#include "networkd-dhcp-server-static-lease.h"
 #include "networkd-dhcp-server.h"
 #include "networkd-manager.h"
 #include "networkd-mdb.h"
@@ -241,6 +242,7 @@ int network_verify(Network *network) {
         network_drop_invalid_routing_policy_rules(network);
         network_drop_invalid_traffic_control(network);
         network_drop_invalid_sr_iov(network);
+        network_drop_invalid_static_leases(network);
 
         network_adjust_dhcp_server(network);
 
@@ -414,6 +416,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
                         "DHCPv6\0"
                         "DHCPv6PrefixDelegation\0"
                         "DHCPServer\0"
+                        "DHCPServerStaticLease\0"
                         "IPv6AcceptRA\0"
                         "IPv6NDPProxyAddress\0"
                         "Bridge\0"
@@ -607,6 +610,7 @@ static Network *network_free(Network *network) {
         hashmap_free_with_destructor(network->prefixes_by_section, prefix_free);
         hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free);
         hashmap_free_with_destructor(network->rules_by_section, routing_policy_rule_free);
+        hashmap_free_with_destructor(network->dhcp_static_leases_by_section, dhcp_static_lease_free);
         ordered_hashmap_free_with_destructor(network->sr_iov_by_section, sr_iov_free);
         ordered_hashmap_free_with_destructor(network->tc_by_section, traffic_control_free);
 
index bd2e25f077e113b51754222c63e65c9a88b75273..cb782c72bc519ed0ab40f3aeb40c3b69a63779f5 100644 (file)
@@ -315,6 +315,7 @@ struct Network {
         Hashmap *prefixes_by_section;
         Hashmap *route_prefixes_by_section;
         Hashmap *rules_by_section;
+        Hashmap *dhcp_static_leases_by_section;
         OrderedHashmap *tc_by_section;
         OrderedHashmap *sr_iov_by_section;
 
diff --git a/test/fuzz/fuzz-network-parser/dhcp-server-static-lease.network b/test/fuzz/fuzz-network-parser/dhcp-server-static-lease.network
new file mode 100644 (file)
index 0000000..e14e7c0
--- /dev/null
@@ -0,0 +1,29 @@
+[Match]
+Name=veth-peer
+
+[Network]
+Address=10.1.1.1/24
+DHCPServer=true
+IPMasquerade=true
+IPForward=true
+
+[DHCPServer]
+PoolOffset=0
+PoolSize=20
+EmitDNS=yes
+DNS=9.9.9.9
+
+[DHCPServerStaticLease]
+MACAddress=12:34:56:78:9a:bc
+Address=10.1.1.2
+
+[DHCPServerStaticLease]
+MACAddress=12:34:56:78:9a:bc
+Address=10.1.1.3
+
+[DHCPServerStaticLease]
+Address=10.1.1.4
+
+[DHCPServerStaticLease]
+MACAddress=12:34:56:78:9a:bf
+Address=10.1.1.5
index 6d2d5f8054eec6fa5be0b1774092aaefa0ab1f05..05b67512ae8c48a9a23a88ebadfb9aa206f8fce1 100644 (file)
@@ -366,6 +366,9 @@ RelayTarget=
 RelayAgentCircuitId=
 RelayAgentRemoteId=
 ServerAddress=
+[DHCPServerStaticLease]
+MACAddress=
+Address=
 [NextHop]
 Id=
 Gateway=