]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
relay role implementation
authorYegor Alexeyev <yegor.alexeyev@gmail.com>
Mon, 15 Mar 2021 20:19:52 +0000 (21:19 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Tue, 20 Apr 2021 13:11:53 +0000 (15:11 +0200)
17 files changed:
man/systemd.network.xml
src/libsystemd-network/dhcp-server-internal.h
src/libsystemd-network/sd-dhcp-server.c
src/network/networkd-dhcp-server-bus.c
src/network/networkd-dhcp-server.c
src/network/networkd-dhcp-server.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.h
src/systemd/sd-dhcp-server.h
test/fuzz/fuzz-network-parser/directives.network
test/test-network/conf/agent-client-peer.network [new file with mode: 0644]
test/test-network/conf/agent-client.network [new file with mode: 0644]
test/test-network/conf/agent-server-peer.network [new file with mode: 0644]
test/test-network/conf/agent-server.network [new file with mode: 0644]
test/test-network/conf/agent-veth-client.netdev [new file with mode: 0644]
test/test-network/conf/agent-veth-server.netdev [new file with mode: 0644]
test/test-network/systemd-networkd-tests.py

index 5d2db39e4a695f25697e47c92ed828e29b990fc6..554d1f663ce40102dfa12415fbe1542ed0b95c74 100644 (file)
@@ -2294,6 +2294,17 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
 
     <variablelist class='network-directives'>
 
+      <varlistentry>
+        <term><varname>RelayTarget=</varname></term>
+        <listitem>
+          <para>Takes an IPv4 address, which must be in the format
+          described in
+          <citerefentry project='man-pages'><refentrytitle>inet_pton</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+          Turns this DHCP server into a DHCP relay agent. See <ulink url="https://tools.ietf.org/html/rfc1542">RFC 1542</ulink>.
+          The address is the address of DHCP server or another relay agent to forward DHCP messages to and from.</para>
+          Check also BindToInterface= option. Turning it off is required for relaying messages outside.
+        </listitem>
+      </varlistentry>
       <varlistentry>
         <term><varname>PoolOffset=</varname></term>
         <term><varname>PoolSize=</varname></term>
index b905791f3c2aeed671f0117a8a29ef375ff331aa..3628223ae9ac47cd78d76a444fbec7ab4674b788 100644 (file)
@@ -39,6 +39,7 @@ typedef struct DHCPLease {
 } DHCPLease;
 
 struct sd_dhcp_server {
+        struct in_addr relay_target;
         unsigned n_ref;
 
         sd_event *event;
index be61474758d03aaa3dcb86253e12585ec32f450d..e2ad99ebcbdec265069b384efe5c39ff41be5903 100644 (file)
@@ -114,6 +114,12 @@ int sd_dhcp_server_is_running(sd_dhcp_server *server) {
         return !!server->receive_message;
 }
 
+int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) {
+        assert_return(server, -EINVAL);
+
+        return in4_addr_is_set(&server->relay_target);
+}
+
 void client_id_hash_func(const DHCPClientId *id, struct siphash *state) {
         assert(id);
         assert(id->length);
@@ -343,10 +349,27 @@ static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
         return 0;
 }
 
-static bool requested_broadcast(DHCPRequest *req) {
-        assert(req);
+static bool requested_broadcast(DHCPMessage *message) {
+        assert(message);
+        return message->flags & htobe16(0x8000);
+}
 
-        return req->message->flags & htobe16(0x8000);
+static int dhcp_server_send(sd_dhcp_server *server, be32_t destination, uint16_t destination_port,
+                            DHCPPacket *packet, size_t optoffset, bool l2_broadcast) {
+        if (destination != INADDR_ANY)
+                return dhcp_server_send_udp(server, destination,
+                                            destination_port, &packet->dhcp,
+                                            sizeof(DHCPMessage) + optoffset);
+        else if (l2_broadcast)
+                return dhcp_server_send_udp(server, INADDR_BROADCAST,
+                                            destination_port, &packet->dhcp,
+                                            sizeof(DHCPMessage) + optoffset);
+        else
+                /* we cannot send UDP packet to specific MAC address when the
+                   address is not yet configured, so must fall back to raw
+                   packets */
+                return dhcp_server_send_unicast_raw(server, packet,
+                                                    sizeof(DHCPPacket) + optoffset);
 }
 
 int dhcp_server_send_packet(sd_dhcp_server *server,
@@ -404,20 +427,8 @@ int dhcp_server_send_packet(sd_dhcp_server *server,
         } else if (req->message->ciaddr && type != DHCP_NAK)
                 destination = req->message->ciaddr;
 
-        if (destination != INADDR_ANY)
-                return dhcp_server_send_udp(server, destination,
-                                            destination_port, &packet->dhcp,
-                                            sizeof(DHCPMessage) + optoffset);
-        else if (requested_broadcast(req) || type == DHCP_NAK)
-                return dhcp_server_send_udp(server, INADDR_BROADCAST,
-                                            destination_port, &packet->dhcp,
-                                            sizeof(DHCPMessage) + optoffset);
-        else
-                /* we cannot send UDP packet to specific MAC address when the
-                   address is not yet configured, so must fall back to raw
-                   packets */
-                return dhcp_server_send_unicast_raw(server, packet,
-                                                    sizeof(DHCPPacket) + optoffset);
+        bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK;
+        return dhcp_server_send(server, destination, destination_port, packet, optoffset, l2_broadcast);
 }
 
 static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
@@ -701,6 +712,47 @@ static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) {
         return be32toh(requested_ip & ~server->netmask) - server->pool_offset;
 }
 
+static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length) {
+        _cleanup_free_ DHCPPacket *packet = NULL;
+
+        assert(server);
+        assert(message);
+        assert(sd_dhcp_server_is_in_relay_mode(server));
+
+        if (message->op == BOOTREQUEST) {
+                log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid));
+                if (message->hops >= 16)
+                        return -ETIME;
+                message->hops++;
+
+                /* https://tools.ietf.org/html/rfc1542#section-4.1.1 */
+                if (message->giaddr == 0)
+                        message->giaddr = server->address;
+
+                return dhcp_server_send_udp(server, server->relay_target.s_addr, DHCP_PORT_SERVER, message, sizeof(DHCPMessage) + opt_length);
+        } else if (message->op == BOOTREPLY) {
+                log_dhcp_server(server, "(relay agent) BOOTREPLY (0x%x)", be32toh(message->xid));
+                if (message->giaddr != server->address) {
+                        return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG),
+                                                     "(relay agent)  BOOTREPLY giaddr mismatch, discarding");
+                }
+
+                int message_type = dhcp_option_parse(message, sizeof(DHCPMessage) + opt_length, NULL, NULL, NULL);
+                if (message_type < 0)
+                        return message_type;
+
+                packet = malloc0(sizeof(DHCPPacket) + opt_length);
+                if (!packet)
+                        return -ENOMEM;
+                memcpy(&packet->dhcp, message, sizeof(DHCPMessage) + opt_length);
+
+                bool l2_broadcast = requested_broadcast(message) || message_type == DHCP_NAK;
+                const be32_t destination = message_type == DHCP_NAK ? INADDR_ANY : message->ciaddr;
+                return dhcp_server_send(server, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast);
+        }
+        return -EBADMSG;
+}
+
 #define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
 
 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
@@ -999,10 +1051,15 @@ static int server_receive_message(sd_event_source *s, int fd,
                 }
         }
 
-        r = dhcp_server_handle_message(server, message, (size_t) len);
-        if (r < 0)
-                log_dhcp_server_errno(server, r, "Couldn't process incoming message: %m");
-
+        if (sd_dhcp_server_is_in_relay_mode(server)) {
+                r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage));
+                if (r < 0)
+                        log_dhcp_server_errno(server, r, "Couldn't relay message: %m");
+        } else {
+                r = dhcp_server_handle_message(server, message, (size_t) len);
+                if (r < 0)
+                        log_dhcp_server_errno(server, r, "Couldn't process incoming message: %m");
+        }
         return 0;
 }
 
@@ -1238,3 +1295,14 @@ int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_
 
         return 0;
 }
+
+int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address) {
+        assert_return(server, -EINVAL);
+        assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
+
+        if (memcmp(address, &server->relay_target, sizeof(struct in_addr)) == 0)
+                return 0;
+
+        server->relay_target = *address;
+        return 1;
+}
index 32f4baed78c6ff2c97750802fe51ff22ac8e4bf0..91a10dc33a036edc67f420d3b902f5fed0856a72 100644 (file)
@@ -31,6 +31,9 @@ static int property_get_leases(
         if (!s)
                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has no DHCP server.", l->ifname);
 
+        if (sd_dhcp_server_is_in_relay_mode(s))
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has DHCP relay agent active.", l->ifname);
+
         r = sd_bus_message_open_container(reply, 'a', "(uayayayayt)");
         if (r < 0)
                 return r;
index 64bb23f3870b842108a977fa57d366e2f9fcc48c..bd368672da77ae25b1ac7fb2c251b8d20ee1890d 100644 (file)
@@ -352,6 +352,10 @@ int dhcp4_server_configure(Link *link) {
         if (r < 0)
                 return log_link_error_errno(link, r, "Failed to set router emission for DHCP server: %m");
 
+        r = sd_dhcp_server_set_relay_target(link->dhcp_server, &link->network->dhcp_server_relay_target);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to set relay target for DHCP server: %m");
+
         if (link->network->dhcp_server_emit_timezone) {
                 _cleanup_free_ char *buffer = NULL;
                 const char *tz;
@@ -398,6 +402,32 @@ int dhcp4_server_configure(Link *link) {
         return 0;
 }
 
+int config_parse_dhcp_server_relay_target(
+                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) {
+
+        Network *network = userdata;
+        union in_addr_union a;
+        int r;
+
+        r = in_addr_from_string(AF_INET, rvalue, &a);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse %s= address '%s', ignoring: %m", lvalue, rvalue);
+                return 0;
+        }
+        network->dhcp_server_relay_target = a.in;
+        return r;
+}
+
 int config_parse_dhcp_server_emit(
                 const char *unit,
                 const char *filename,
index 4bd5120ea0b2c0d8ac1ef9ce3825b2941cf3d7d5..9e5d24fbe8107200258897429a7251acd9b2ff73 100644 (file)
@@ -9,4 +9,5 @@ typedef struct Link Link;
 
 int dhcp4_server_configure(Link *link);
 
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_target);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit);
index 6c37e32453117c7b5b61e89b7a9e82a3cab75927..4fc368547c25cd3291aa25a485547049f68a7b8a 100644 (file)
@@ -256,6 +256,7 @@ IPv6AcceptRA.PrefixAllowList,                config_parse_ndisc_address_filter,
 IPv6AcceptRA.PrefixDenyList,                 config_parse_ndisc_address_filter,                        0,                             offsetof(Network, ndisc_deny_listed_prefix)
 IPv6AcceptRA.RouteAllowList,                 config_parse_ndisc_address_filter,                        0,                             offsetof(Network, ndisc_allow_listed_route_prefix)
 IPv6AcceptRA.RouteDenyList,                  config_parse_ndisc_address_filter,                        0,                             offsetof(Network, ndisc_deny_listed_route_prefix)
+DHCPServer.RelayTarget,                      config_parse_dhcp_server_relay_target,                    0,                             0
 DHCPServer.MaxLeaseTimeSec,                  config_parse_sec,                                         0,                             offsetof(Network, dhcp_server_max_lease_time_usec)
 DHCPServer.DefaultLeaseTimeSec,              config_parse_sec,                                         0,                             offsetof(Network, dhcp_server_default_lease_time_usec)
 DHCPServer.EmitDNS,                          config_parse_bool,                                        0,                             offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_DNS].emit)
index df77b4261926d073b2a03dcc5a874099e47ebd67..16a445982c6f068b417acecad172bbb76e986bf9 100644 (file)
@@ -186,6 +186,7 @@ struct Network {
         /* DHCP Server Support */
         bool dhcp_server;
         bool dhcp_server_bind_to_interface;
+        struct in_addr dhcp_server_relay_target;
         NetworkDHCPServerEmitAddress dhcp_server_emit[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
         bool dhcp_server_emit_router;
         bool dhcp_server_emit_timezone;
index 511c8daf4077f48394b364f62d7c7a2d44a1e4d6..3cee2c2b062645879ee249aa43af10adeab93223 100644 (file)
@@ -83,6 +83,8 @@ int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t);
 
 int sd_dhcp_server_forcerenew(sd_dhcp_server *server);
 
+int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server);
+int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address);
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref);
 
 _SD_END_DECLARATIONS;
index 016be501ed8b0936589a560664fbd1edc7d1524d..158f0ffad11a011fb994739a0bd3a4f1b30fe986 100644 (file)
@@ -352,6 +352,7 @@ DNS=
 SendOption=
 SendVendorOption=
 BindToInterface=
+RelayTarget=
 [NextHop]
 Id=
 Gateway=
diff --git a/test/test-network/conf/agent-client-peer.network b/test/test-network/conf/agent-client-peer.network
new file mode 100644 (file)
index 0000000..0db83dc
--- /dev/null
@@ -0,0 +1,9 @@
+[Match]
+Name=client-peer
+[Network]
+Address=192.168.6.2/24
+DHCPServer=yes
+IPForward=ipv4
+[DHCPServer]
+RelayTarget=192.168.5.1
+BindToInterface=no
diff --git a/test/test-network/conf/agent-client.network b/test/test-network/conf/agent-client.network
new file mode 100644 (file)
index 0000000..773dc8a
--- /dev/null
@@ -0,0 +1,5 @@
+[Match]
+Name=client
+[Network]
+DHCP=yes
+IPForward=ipv4
diff --git a/test/test-network/conf/agent-server-peer.network b/test/test-network/conf/agent-server-peer.network
new file mode 100644 (file)
index 0000000..fdb235e
--- /dev/null
@@ -0,0 +1,5 @@
+[Match]
+Name=server-peer
+[Network]
+Address=192.168.5.2/24
+IPForward=ipv4
diff --git a/test/test-network/conf/agent-server.network b/test/test-network/conf/agent-server.network
new file mode 100644 (file)
index 0000000..d58abdf
--- /dev/null
@@ -0,0 +1,10 @@
+[Match]
+Name=server
+[Network]
+Address=192.168.5.1/24
+IPForward=ipv4
+DHCPServer=yes
+[DHCPServer]
+BindToInterface=no
+PoolOffset=150
+PoolSize=1
diff --git a/test/test-network/conf/agent-veth-client.netdev b/test/test-network/conf/agent-veth-client.netdev
new file mode 100644 (file)
index 0000000..ace785f
--- /dev/null
@@ -0,0 +1,8 @@
+[NetDev]
+Name=client
+Kind=veth
+MACAddress=12:34:56:78:9a:bc
+
+[Peer]
+Name=client-peer
+MACAddress=12:34:56:78:9a:bd
diff --git a/test/test-network/conf/agent-veth-server.netdev b/test/test-network/conf/agent-veth-server.netdev
new file mode 100644 (file)
index 0000000..3a3d393
--- /dev/null
@@ -0,0 +1,8 @@
+[NetDev]
+Name=server
+Kind=veth
+MACAddress=12:34:56:78:9b:bc
+
+[Peer]
+Name=server-peer
+MACAddress=12:34:56:78:9b:bd
index 28ffe3d8ba291fb9692667d1d5c84b36cea584f9..9d86818610535e558d3e1a54e2255d104e7607f5 100755 (executable)
@@ -3689,6 +3689,43 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
         self.assertRegex(output, '192.168.5.*')
         self.assertRegex(output, 'Europe/Berlin')
 
+class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
+    links = [
+        'client',
+        'server',
+        'client-peer',
+        'server-peer',
+        ]
+
+    units = [
+        'agent-veth-client.netdev',
+        'agent-veth-server.netdev',
+        'agent-client.network',
+        'agent-server.network',
+        'agent-client-peer.network',
+        'agent-server-peer.network',
+        ]
+
+    def setUp(self):
+        remove_links(self.links)
+        stop_networkd(show_logs=False)
+
+    def tearDown(self):
+        remove_links(self.links)
+        remove_unit_from_networkd_path(self.units)
+        stop_networkd(show_logs=True)
+
+    def test_relay_agent(self):
+        copy_unit_to_networkd_unit_path(*self.units)
+        start_networkd()
+
+        #Test is disabled until BindToInterface DHCP server configuration option is supported
+        self.wait_online(['client:routable'])
+
+        output = check_output(*networkctl_cmd, '-n', '0', 'status', 'client', env=env)
+        print(output)
+        self.assertRegex(output, 'Address: 192.168.5.150 \(DHCP4 via 192.168.5.1\)')
+
 class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
     links = [
         'veth99',