]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp-server: support packet from non-Ethernet interface 22222/head
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 22 Jan 2022 17:27:32 +0000 (02:27 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 23 Jan 2022 19:18:20 +0000 (04:18 +0900)
Fixes #22217.

src/libsystemd-network/dhcp-server-internal.h
src/libsystemd-network/sd-dhcp-server.c
src/libsystemd-network/test-dhcp-server.c

index 0193ab9503acfbb04fd60997ddd03b0cd3987a08..51510b9d22e61964b5c8395f3742ea95c9576776 100644 (file)
@@ -34,6 +34,8 @@ typedef struct DHCPLease {
 
         DHCPClientId client_id;
 
+        uint8_t htype; /* e.g. ARPHRD_ETHER */
+        uint8_t hlen;  /* e.g. ETH_ALEN */
         be32_t address;
         be32_t gateway;
         uint8_t chaddr[16];
index 91069c078288252333cfb48d1a1e5f902715cb15..38c202975fa64570e39ec69ad9407d5d3e480db1 100644 (file)
@@ -15,6 +15,7 @@
 #include "fd-util.h"
 #include "in-addr-util.h"
 #include "io-util.h"
+#include "memory-util.h"
 #include "network-common.h"
 #include "ordered-set.h"
 #include "siphash24.h"
@@ -294,22 +295,29 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
         return 0;
 }
 
-static int dhcp_server_send_unicast_raw(sd_dhcp_server *server,
-                                        DHCPPacket *packet, size_t len) {
+static int dhcp_server_send_unicast_raw(
+                sd_dhcp_server *server,
+                uint8_t hlen,
+                const uint8_t *chaddr,
+                DHCPPacket *packet,
+                size_t len) {
+
         union sockaddr_union link = {
                 .ll.sll_family = AF_PACKET,
                 .ll.sll_protocol = htobe16(ETH_P_IP),
                 .ll.sll_ifindex = server->ifindex,
-                .ll.sll_halen = ETH_ALEN,
+                .ll.sll_halen = hlen,
         };
 
         assert(server);
         assert(server->ifindex > 0);
         assert(server->address);
+        assert(hlen > 0);
+        assert(chaddr);
         assert(packet);
         assert(len > sizeof(DHCPPacket));
 
-        memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
+        memcpy(link.ll.sll_addr, chaddr, hlen);
 
         dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
                                       packet->dhcp.yiaddr,
@@ -378,8 +386,16 @@ static bool requested_broadcast(DHCPMessage *message) {
         return 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) {
+static int dhcp_server_send(
+                sd_dhcp_server *server,
+                uint8_t hlen,
+                const uint8_t *chaddr,
+                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,
@@ -392,7 +408,7 @@ static int dhcp_server_send(sd_dhcp_server *server, be32_t destination, uint16_t
                 /* 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,
+                return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet,
                                                     sizeof(DHCPPacket) + optoffset);
 }
 
@@ -462,7 +478,8 @@ int dhcp_server_send_packet(sd_dhcp_server *server,
                 destination = req->message->ciaddr;
 
         bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK;
-        return dhcp_server_send(server, destination, destination_port, packet, optoffset, l2_broadcast);
+        return dhcp_server_send(server, req->message->hlen, req->message->chaddr,
+                                destination, destination_port, packet, optoffset, l2_broadcast);
 }
 
 static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
@@ -606,8 +623,14 @@ static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) {
         return dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
 }
 
-static int server_send_forcerenew(sd_dhcp_server *server, be32_t address,
-                                  be32_t gateway, const uint8_t chaddr[]) {
+static int server_send_forcerenew(
+                sd_dhcp_server *server,
+                be32_t address,
+                be32_t gateway,
+                uint8_t htype,
+                uint8_t hlen,
+                const uint8_t *chaddr) {
+
         _cleanup_free_ DHCPPacket *packet = NULL;
         size_t optoffset = 0;
         int r;
@@ -621,7 +644,7 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address,
                 return -ENOMEM;
 
         r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0,
-                              DHCP_FORCERENEW, ARPHRD_ETHER, ETH_ALEN, chaddr,
+                              DHCP_FORCERENEW, htype, hlen, chaddr,
                               DHCP_MIN_OPTIONS_SIZE, &optoffset);
         if (r < 0)
                 return r;
@@ -701,22 +724,45 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes
 
         req->message = message;
 
-        /* set client id based on MAC address if client did not send an explicit
-           one */
+        /* set client id based on MAC address if client did not send an explicit one */
         if (!req->client_id.data) {
                 uint8_t *data;
 
-                data = new0(uint8_t, ETH_ALEN + 1);
+                if (message->hlen == 0)
+                        return -EBADMSG;
+
+                data = new0(uint8_t, message->hlen + 1);
                 if (!data)
                         return -ENOMEM;
 
                 data[0] = 0x01;
-                memcpy(data + 1, message->chaddr, ETH_ALEN);
+                memcpy(data + 1, message->chaddr, message->hlen);
 
-                req->client_id.length = ETH_ALEN + 1;
+                req->client_id.length = message->hlen + 1;
                 req->client_id.data = data;
         }
 
+        if (message->hlen > sizeof(message->chaddr))
+                return -EBADMSG;
+
+        if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) {
+                /* See RFC2131 section 4.1.1.
+                 * hlen and chaddr may not be set for non-ethernet interface.
+                 * Let's try to retrieve it from the client ID. */
+
+                if (!req->client_id.data)
+                        return -EBADMSG;
+
+                if (req->client_id.length <= 1 || req->client_id.length > sizeof(message->chaddr) + 1)
+                        return -EBADMSG;
+
+                if (req->client_id.data[0] != 0x01)
+                        return -EBADMSG;
+
+                message->hlen = req->client_id.length - 1;
+                memcpy(message->chaddr, req->client_id.data + 1, message->hlen);
+        }
+
         if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
                 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
 
@@ -771,6 +817,10 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag
         assert(message);
         assert(sd_dhcp_server_is_in_relay_mode(server));
 
+        if (message->hlen == 0 || message->hlen > sizeof(message->chaddr) || memeqzero(message->chaddr, message->hlen))
+                return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG),
+                                             "(relay agent) received message without/invalid hardware address, discarding.");
+
         if (message->op == BOOTREQUEST) {
                 log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid));
                 if (message->hops >= 16)
@@ -810,7 +860,7 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag
 
                 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 dhcp_server_send(server, message->hlen, message->chaddr, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast);
         }
         return -EBADMSG;
 }
@@ -819,7 +869,9 @@ static int prepare_new_lease(
                 DHCPLease **ret_lease,
                 be32_t address,
                 const DHCPClientId *client_id,
-                const uint8_t chaddr[static ETH_ALEN],
+                uint8_t htype,
+                uint8_t hlen,
+                const uint8_t *chaddr,
                 be32_t gateway,
                 usec_t expiration) {
 
@@ -832,6 +884,8 @@ static int prepare_new_lease(
         *lease = (DHCPLease) {
                 .address = address,
                 .client_id.length = client_id->length,
+                .htype = htype,
+                .hlen = hlen,
                 .gateway = gateway,
                 .expiration = expiration,
         };
@@ -839,7 +893,7 @@ static int prepare_new_lease(
         if (!lease->client_id.data)
                 return -ENOMEM;
 
-        memcpy(&lease->chaddr, chaddr, ETH_ALEN);
+        memcpy(lease->chaddr, chaddr, hlen);
 
         *ret_lease = TAKE_PTR(lease);
 
@@ -888,9 +942,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
         assert(server);
         assert(message);
 
-        if (message->op != BOOTREQUEST ||
-            message->htype != ARPHRD_ETHER ||
-            message->hlen != ETHER_ADDR_LEN)
+        if (message->op != BOOTREQUEST)
                 return 0;
 
         req = new0(DHCPRequest, 1);
@@ -903,7 +955,6 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
 
         r = ensure_sane_request(server, req, message);
         if (r < 0)
-                /* this only fails on critical errors */
                 return r;
 
         r = dhcp_server_cleanup_expired_leases(server);
@@ -1042,6 +1093,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
                         expiration = usec_add(req->lifetime * USEC_PER_SEC, time_now);
 
                         r = prepare_new_lease(&lease, static_lease->address, &req->client_id,
+                                              req->message->htype, req->message->hlen,
                                               req->message->chaddr, req->message->giaddr, expiration);
                         if (r < 0)
                                 return r;
@@ -1085,6 +1137,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
 
                         if (!existing_lease) {
                                 r = prepare_new_lease(&new_lease, address, &req->client_id,
+                                                      req->message->htype, req->message->hlen,
                                                       req->message->chaddr, req->message->giaddr, expiration);
                                 if (r < 0)
                                         return r;
@@ -1313,7 +1366,8 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
         log_dhcp_server(server, "FORCERENEW");
 
         HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) {
-                k = server_send_forcerenew(server, lease->address, lease->gateway, lease->chaddr);
+                k = server_send_forcerenew(server, lease->address, lease->gateway,
+                                           lease->htype, lease->hlen, lease->chaddr);
                 if (k < 0)
                         r = k;
         }
@@ -1519,7 +1573,6 @@ int sd_dhcp_server_set_static_lease(
 
         assert_return(server, -EINVAL);
         assert_return(client_id, -EINVAL);
-        assert_return(client_id_size == ETH_ALEN + 1, -EINVAL);
         assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
 
         /* Static lease with an empty or omitted address is a valid entry,
index a5c9bfeafbe40dfb9525ac8f325423095060d3fa..b0e37352c35f16eaae7123f4aa4781724439c9bc 100644 (file)
@@ -137,12 +137,12 @@ static void test_message_handler(void) {
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 
         test.message.htype = 0;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
         test.message.htype = ARPHRD_ETHER;
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 
         test.message.hlen = 0;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == -EBADMSG);
         test.message.hlen = ETHER_ADDR_LEN;
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);