udhcpc: paranoia when using kernel UDP mode for sending renew: server ID may be bogus
With new code, we request that target IP (server ID) must be directly reachable.
If it's not, this happens:
udhcpc: waiting 2000 seconds
udhcpc: entering listen mode: kernel
udhcpc: opening listen socket on *:68 wlan0
udhcpc: entering renew state
udhcpc: sending renew to 1.1.1.1
udhcpc: send: Network is unreachable
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1.1.1.1 needs routing, this is fishy!
udhcpc: entering rebinding state
udhcpc: entering listen mode: raw
udhcpc: created raw socket
udhcpc: sending renew to 0.0.0.0
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ going to use broadcast
which is the desired behavior. Before the patch, packet to 1.1.1.1 was routed
over eth0 (!) and maybe even into Internet (!!!).
function old new delta
udhcpc_main 2752 2763 +11
udhcp_send_kernel_packet 295 301 +6
send_renew 82 84 +2
send_packet 166 168 +2
bcast_or_ucast 23 25 +2
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 5/0 up/down: 23/0) Total: 23 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index 6c74996..2ae8bcc 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -694,10 +694,16 @@
static int bcast_or_ucast(struct dhcp_packet *packet, uint32_t ciaddr, uint32_t server)
{
- if (server)
+ if (server) {
+ /* Without MSG_DONTROUTE, the packet was seen routed over
+ * _other interface_ if server ID is bogus (example: 1.1.1.1).
+ */
return udhcp_send_kernel_packet(packet,
ciaddr, CLIENT_PORT,
- server, SERVER_PORT);
+ server, SERVER_PORT,
+ /*send_flags: "to hosts only on directly connected networks" */ MSG_DONTROUTE
+ );
+ }
return raw_bcast_from_client_config_ifindex(packet, ciaddr);
}
@@ -735,7 +741,7 @@
static NOINLINE int send_select(uint32_t xid, uint32_t server, uint32_t requested)
{
struct dhcp_packet packet;
- struct in_addr addr;
+ struct in_addr temp_addr;
/*
* RFC 2131 4.3.2 DHCPREQUEST message
@@ -766,8 +772,8 @@
*/
add_client_options(&packet);
- addr.s_addr = requested;
- bb_error_msg("sending select for %s", inet_ntoa(addr));
+ temp_addr.s_addr = requested;
+ bb_error_msg("sending select for %s", inet_ntoa(temp_addr));
return raw_bcast_from_client_config_ifindex(&packet, INADDR_ANY);
}
@@ -776,6 +782,7 @@
static NOINLINE int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr)
{
struct dhcp_packet packet;
+ struct in_addr temp_addr;
/*
* RFC 2131 4.3.2 DHCPREQUEST message
@@ -806,7 +813,8 @@
*/
add_client_options(&packet);
- bb_error_msg("sending %s", "renew");
+ temp_addr.s_addr = server;
+ bb_error_msg("sending renew to %s", inet_ntoa(temp_addr));
return bcast_or_ucast(&packet, ciaddr, server);
}
@@ -1524,11 +1532,17 @@
* Anyway, it does recover by eventually failing through
* into INIT_SELECTING state.
*/
- send_renew(xid, server_addr, requested_ip);
- timeout >>= 1;
- continue;
+ if (send_renew(xid, server_addr, requested_ip) >= 0) {
+ timeout >>= 1;
+ continue;
+ }
+ /* else: error sending.
+ * example: ENETUNREACH seen with server
+ * which gave us bogus server ID 1.1.1.1
+ * which wasn't reachable (and probably did not exist).
+ */
}
- /* Timed out, enter rebinding state */
+ /* Timed out or error, enter rebinding state */
log1("entering rebinding state");
state = REBINDING;
/* fall right through */