From: Tobias Brunner Date: Tue, 10 Apr 2018 15:04:10 +0000 (+0200) Subject: dhcp: Bind server port when a specific server address is specified X-Git-Tag: 5.6.3dr2~6^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=becf027cd9b0af162247015a9fff6c00e59fd6ce;p=thirdparty%2Fstrongswan.git dhcp: Bind server port when a specific server address is specified 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). --- diff --git a/src/libcharon/plugins/dhcp/dhcp_socket.c b/src/libcharon/plugins/dhcp/dhcp_socket.c index 02aa298536..765171ff44 100644 --- a/src/libcharon/plugins/dhcp/dhcp_socket.c +++ b/src/libcharon/plugins/dhcp/dhcp_socket.c @@ -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 * @@ -179,6 +182,16 @@ typedef struct __attribute__((packed)) { u_char options[252]; } 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 */ @@ -186,7 +199,7 @@ 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)); diff --git a/testing/tests/ikev2/dhcp-dynamic/hosts/moon/etc/iptables.rules b/testing/tests/ikev2/dhcp-dynamic/hosts/moon/etc/iptables.rules index 2d9a466b0a..792fc56bc0 100644 --- a/testing/tests/ikev2/dhcp-dynamic/hosts/moon/etc/iptables.rules +++ b/testing/tests/ikev2/dhcp-dynamic/hosts/moon/etc/iptables.rules @@ -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 diff --git a/testing/tests/ikev2/dhcp-static-client-id/hosts/moon/etc/iptables.rules b/testing/tests/ikev2/dhcp-static-client-id/hosts/moon/etc/iptables.rules index 2d9a466b0a..792fc56bc0 100644 --- a/testing/tests/ikev2/dhcp-static-client-id/hosts/moon/etc/iptables.rules +++ b/testing/tests/ikev2/dhcp-static-client-id/hosts/moon/etc/iptables.rules @@ -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 diff --git a/testing/tests/ikev2/dhcp-static-mac/hosts/moon/etc/iptables.rules b/testing/tests/ikev2/dhcp-static-mac/hosts/moon/etc/iptables.rules index 2d9a466b0a..792fc56bc0 100644 --- a/testing/tests/ikev2/dhcp-static-mac/hosts/moon/etc/iptables.rules +++ b/testing/tests/ikev2/dhcp-static-mac/hosts/moon/etc/iptables.rules @@ -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 diff --git a/testing/tests/swanctl/dhcp-dynamic/hosts/moon/etc/iptables.rules b/testing/tests/swanctl/dhcp-dynamic/hosts/moon/etc/iptables.rules index 2d9a466b0a..792fc56bc0 100644 --- a/testing/tests/swanctl/dhcp-dynamic/hosts/moon/etc/iptables.rules +++ b/testing/tests/swanctl/dhcp-dynamic/hosts/moon/etc/iptables.rules @@ -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