dhcp: Bind server port when a specific server address is specified
authorTobias Brunner <tobias@strongswan.org>
Tue, 10 Apr 2018 15:04:10 +0000 (17:04 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 18 May 2018 16:04:01 +0000 (18:04 +0200)
DHCP servers will respond to port 67 if giaddr is non-zero, which we set
if we are not broadcasting.  While such messages are received fine via
RAW socket the kernel will respond with an ICMP port unreachable if no
socket is bound to that port.  Instead of opening a dummy socket on port
67 just to avoid the ICMPs we can also just operate with a single
socket, bind it to port 67 and send our requests from that port.

Since SO_REUSEADDR behaves on Linux like SO_REUSEPORT does on other
systems we can bind that port even if a DHCP server is running on the
same host as the daemon (this might have to be adapted to make this work
on other systems, but due to the raw socket the plugin is not that portable
anyway).

src/libcharon/plugins/dhcp/dhcp_socket.c
testing/tests/ikev2/dhcp-dynamic/hosts/moon/etc/iptables.rules
testing/tests/ikev2/dhcp-static-client-id/hosts/moon/etc/iptables.rules
testing/tests/ikev2/dhcp-static-mac/hosts/moon/etc/iptables.rules
testing/tests/swanctl/dhcp-dynamic/hosts/moon/etc/iptables.rules

index 02aa298..765171f 100644 (file)
@@ -1,4 +1,7 @@
 /*
+ * Copyright (C) 2012-2018 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
  * Copyright (C) 2010 Martin Willi
  * Copyright (C) 2010 revosec AG
  *
@@ -180,13 +183,23 @@ typedef struct __attribute__((packed)) {
 } dhcp_t;
 
 /**
+ * Check if the given address equals the broadcast address
+ */
+static inline bool is_broadcast(host_t *host)
+{
+       chunk_t broadcast = chunk_from_chars(0xFF,0xFF,0xFF,0xFF);
+
+       return chunk_equals(broadcast, host->get_address(host));
+}
+
+/**
  * Prepare a DHCP message for a given transaction
  */
 static int prepare_dhcp(private_dhcp_socket_t *this,
                                                dhcp_transaction_t *transaction,
                                                dhcp_message_type_t type, dhcp_t *dhcp)
 {
-       chunk_t chunk, broadcast = chunk_from_chars(0xFF,0xFF,0xFF,0xFF);
+       chunk_t chunk;
        identification_t *identity;
        dhcp_option_t *option;
        int optlen = 0;
@@ -198,7 +211,7 @@ static int prepare_dhcp(private_dhcp_socket_t *this,
        dhcp->hw_type = ARPHRD_ETHER;
        dhcp->hw_addr_len = 6;
        dhcp->transaction_id = transaction->get_id(transaction);
-       if (chunk_equals(broadcast, this->dst->get_address(this->dst)))
+       if (is_broadcast(this->dst))
        {
                /* Set broadcast flag to get broadcasted replies, as we actually
                 * do not own the MAC we request an address for. */
@@ -766,6 +779,17 @@ dhcp_socket_t *dhcp_socket_create()
                destroy(this);
                return NULL;
        }
+       if (!is_broadcast(this->dst))
+       {
+               /* when setting giaddr (which we do when we don't broadcast), the server
+                * should respond to the server port on that IP, according to RFC 2131,
+                * section 4.1.  while we do receive such messages via raw socket, the
+                * kernel will respond with an ICMP port unreachable if there is no
+                * socket bound to that port, which might be problematic with certain
+                * DHCP servers.  instead of opening an additional socket, that we don't
+                * actually use, we can also just send our requests from port 67 */
+               src.sin_port = htons(DHCP_SERVER_PORT);
+       }
        if (bind(this->send, (struct sockaddr*)&src, sizeof(src)) == -1)
        {
                DBG1(DBG_CFG, "unable to bind DHCP send socket: %s", strerror(errno));
index 2d9a466..792fc56 100644 (file)
@@ -5,8 +5,8 @@
 -P OUTPUT DROP
 -P FORWARD DROP
 
-# allow bootpc and bootps
--A OUTPUT -p udp --sport bootpc --dport bootps -j ACCEPT
+# allow bootps (in relay mode also in OUTPUT)
+-A OUTPUT -p udp --sport bootps --dport bootps -j ACCEPT
 -A INPUT  -p udp --sport bootps --dport bootps -j ACCEPT
 
 # allow broadcasts from eth1
index 2d9a466..792fc56 100644 (file)
@@ -5,8 +5,8 @@
 -P OUTPUT DROP
 -P FORWARD DROP
 
-# allow bootpc and bootps
--A OUTPUT -p udp --sport bootpc --dport bootps -j ACCEPT
+# allow bootps (in relay mode also in OUTPUT)
+-A OUTPUT -p udp --sport bootps --dport bootps -j ACCEPT
 -A INPUT  -p udp --sport bootps --dport bootps -j ACCEPT
 
 # allow broadcasts from eth1
index 2d9a466..792fc56 100644 (file)
@@ -5,8 +5,8 @@
 -P OUTPUT DROP
 -P FORWARD DROP
 
-# allow bootpc and bootps
--A OUTPUT -p udp --sport bootpc --dport bootps -j ACCEPT
+# allow bootps (in relay mode also in OUTPUT)
+-A OUTPUT -p udp --sport bootps --dport bootps -j ACCEPT
 -A INPUT  -p udp --sport bootps --dport bootps -j ACCEPT
 
 # allow broadcasts from eth1
index 2d9a466..792fc56 100644 (file)
@@ -5,8 +5,8 @@
 -P OUTPUT DROP
 -P FORWARD DROP
 
-# allow bootpc and bootps
--A OUTPUT -p udp --sport bootpc --dport bootps -j ACCEPT
+# allow bootps (in relay mode also in OUTPUT)
+-A OUTPUT -p udp --sport bootps --dport bootps -j ACCEPT
 -A INPUT  -p udp --sport bootps --dport bootps -j ACCEPT
 
 # allow broadcasts from eth1