udhcpd: don't fail ARP check if returned MAC matches client's one
Also, do not unicast replies to yiaddr.
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c
index b10bff6..fa0989d 100644
--- a/networking/udhcp/arpping.c
+++ b/networking/udhcp/arpping.c
@@ -41,7 +41,11 @@
/* Returns 1 if no reply received */
-int FAST_FUNC arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface)
+int FAST_FUNC arpping(uint32_t test_ip,
+ const uint8_t *safe_mac,
+ uint32_t from_ip,
+ uint8_t *from_mac,
+ const char *interface)
{
int timeout_ms;
struct pollfd pfd[1];
@@ -73,7 +77,7 @@
arp.operation = htons(ARPOP_REQUEST); /* ARP op code */
memcpy(arp.sHaddr, from_mac, 6); /* source hardware address */
memcpy(arp.sInaddr, &from_ip, sizeof(from_ip)); /* source IP address */
- /* tHaddr is zero-fiiled */ /* target hardware address */
+ /* tHaddr is zero-filled */ /* target hardware address */
memcpy(arp.tInaddr, &test_ip, sizeof(test_ip)); /* target IP address */
memset(&addr, 0, sizeof(addr));
@@ -98,13 +102,24 @@
r = read(s, &arp, sizeof(arp));
if (r < 0)
break;
+
+ //bb_error_msg("sHaddr %02x:%02x:%02x:%02x:%02x:%02x",
+ // arp.sHaddr[0], arp.sHaddr[1], arp.sHaddr[2],
+ // arp.sHaddr[3], arp.sHaddr[4], arp.sHaddr[5]);
+
if (r >= ARP_MSG_SIZE
&& arp.operation == htons(ARPOP_REPLY)
/* don't check it: Linux doesn't return proper tHaddr (fixed in 2.6.24?) */
/* && memcmp(arp.tHaddr, from_mac, 6) == 0 */
&& *((uint32_t *) arp.sInaddr) == test_ip
) {
- rv = 0;
+ /* if ARP source MAC matches safe_mac
+ * (which is client's MAC), then it's not a conflict
+ * (client simply already has this IP and replies to ARPs!)
+ */
+ if (!safe_mac || memcmp(safe_mac, arp.sHaddr, 6) != 0)
+ rv = 0;
+ //else bb_error_msg("sHaddr == safe_mac");
break;
}
}
diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h
index 5a258c0..ca96847 100644
--- a/networking/udhcp/common.h
+++ b/networking/udhcp/common.h
@@ -92,7 +92,11 @@
int udhcp_raw_socket(int ifindex) FAST_FUNC;
int udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf) FAST_FUNC;
/* Returns 1 if no reply received */
-int arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface) FAST_FUNC;
+int arpping(uint32_t test_ip,
+ const uint8_t *safe_mac,
+ uint32_t from_ip,
+ uint8_t *from_mac,
+ const char *interface) FAST_FUNC;
#if ENABLE_UDHCP_DEBUG
# define DEBUG(str, args...) bb_info_msg("### " str, ## args)
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index 2dd3cd0..ab34b04 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -553,9 +553,10 @@
* the client MUST send a DHCPDECLINE message to the server and restarts
* the configuration process..." */
if (!arpping(packet.yiaddr,
- (uint32_t) 0,
- client_config.arp,
- client_config.interface)
+ NULL,
+ (uint32_t) 0,
+ client_config.arp,
+ client_config.interface)
) {
bb_info_msg("offered address is in use "
"(got ARP reply), declining");
diff --git a/networking/udhcp/dhcpd.h b/networking/udhcp/dhcpd.h
index 9667c61..4b5fcc0 100644
--- a/networking/udhcp/dhcpd.h
+++ b/networking/udhcp/dhcpd.h
@@ -99,7 +99,7 @@
int lease_expired(struct dhcpOfferedAddr *lease) FAST_FUNC;
struct dhcpOfferedAddr *find_lease_by_chaddr(const uint8_t *chaddr) FAST_FUNC;
struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr) FAST_FUNC;
-uint32_t find_free_or_expired_address(void) FAST_FUNC;
+uint32_t find_free_or_expired_address(const uint8_t *chaddr) FAST_FUNC;
/*** static_leases.h ***/
diff --git a/networking/udhcp/leases.c b/networking/udhcp/leases.c
index e17fb9e..b2cdd19 100644
--- a/networking/udhcp/leases.c
+++ b/networking/udhcp/leases.c
@@ -118,7 +118,7 @@
/* check is an IP is taken, if it is, add it to the lease table */
-static int nobody_responds_to_arp(uint32_t addr)
+static int nobody_responds_to_arp(uint32_t addr, const uint8_t *safe_mac)
{
/* 16 zero bytes */
static const uint8_t blank_chaddr[16] = { 0 };
@@ -127,7 +127,9 @@
struct in_addr temp;
int r;
- r = arpping(addr, server_config.server, server_config.arp, server_config.interface);
+ r = arpping(addr, safe_mac,
+ server_config.server, server_config.arp,
+ server_config.interface);
if (r)
return r;
@@ -140,7 +142,7 @@
/* Find a new usable (we think) address. */
-uint32_t FAST_FUNC find_free_or_expired_address(void)
+uint32_t FAST_FUNC find_free_or_expired_address(const uint8_t *chaddr)
{
uint32_t addr;
struct dhcpOfferedAddr *oldest_lease = NULL;
@@ -163,7 +165,7 @@
lease = find_lease_by_yiaddr(net_addr);
if (!lease) {
- if (nobody_responds_to_arp(net_addr))
+ if (nobody_responds_to_arp(net_addr, chaddr))
return net_addr;
} else {
if (!oldest_lease || lease->expires < oldest_lease->expires)
@@ -172,7 +174,7 @@
}
if (oldest_lease && lease_expired(oldest_lease)
- && nobody_responds_to_arp(oldest_lease->yiaddr)
+ && nobody_responds_to_arp(oldest_lease->yiaddr, chaddr)
) {
return oldest_lease->yiaddr;
}
diff --git a/networking/udhcp/serverpacket.c b/networking/udhcp/serverpacket.c
index 8b0f185..157d157 100644
--- a/networking/udhcp/serverpacket.c
+++ b/networking/udhcp/serverpacket.c
@@ -31,7 +31,8 @@
{
DEBUG("Forwarding packet to relay");
- return udhcp_send_kernel_packet(payload, server_config.server, SERVER_PORT,
+ return udhcp_send_kernel_packet(payload,
+ server_config.server, SERVER_PORT,
payload->giaddr, SERVER_PORT);
}
@@ -42,23 +43,31 @@
const uint8_t *chaddr;
uint32_t ciaddr;
- if (force_broadcast) {
- DEBUG("broadcasting packet to client (NAK)");
- ciaddr = INADDR_BROADCAST;
- chaddr = MAC_BCAST_ADDR;
- } else if (payload->ciaddr) {
- DEBUG("unicasting packet to client ciaddr");
- ciaddr = payload->ciaddr;
- chaddr = payload->chaddr;
- } else if (payload->flags & htons(BROADCAST_FLAG)) {
- DEBUG("broadcasting packet to client (requested)");
+ // Was:
+ //if (force_broadcast) { /* broadcast */ }
+ //else if (payload->ciaddr) { /* unicast to payload->ciaddr */ }
+ //else if (payload->flags & htons(BROADCAST_FLAG)) { /* broadcast */ }
+ //else { /* unicast to payload->yiaddr */ }
+ // But this is wrong: yiaddr is _our_ idea what client's IP is
+ // (for example, from lease file). Client may not know that,
+ // and may not have UDP socket listening on that IP!
+ // We should never unicast to payload->yiaddr!
+ // payload->ciaddr, OTOH, comes from client's request packet,
+ // and can be used.
+
+ if (force_broadcast
+ || (payload->flags & htons(BROADCAST_FLAG))
+ || !payload->ciaddr
+ ) {
+ DEBUG("broadcasting packet to client");
ciaddr = INADDR_BROADCAST;
chaddr = MAC_BCAST_ADDR;
} else {
- DEBUG("unicasting packet to client yiaddr");
- ciaddr = payload->yiaddr;
+ DEBUG("unicasting packet to client ciaddr");
+ ciaddr = payload->ciaddr;
chaddr = payload->chaddr;
}
+
return udhcp_send_raw_packet(payload,
/*src*/ server_config.server, SERVER_PORT,
/*dst*/ ciaddr, CLIENT_PORT, chaddr,
@@ -118,17 +127,18 @@
struct dhcpOfferedAddr *lease;
lease = find_lease_by_chaddr(oldpacket->chaddr);
- /* the client is in our lease/offered table */
+ /* The client is in our lease/offered table */
if (lease) {
signed_leasetime_t tmp = lease->expires - time(NULL);
if (tmp >= 0)
lease_time_aligned = tmp;
packet.yiaddr = lease->yiaddr;
- /* Or the client has requested an ip */
- } else if ((req = get_option(oldpacket, DHCP_REQUESTED_IP)) != NULL
- /* Don't look here (ugly hackish thing to do) */
+ }
+ /* Or the client has requested an IP */
+ else if ((req = get_option(oldpacket, DHCP_REQUESTED_IP)) != NULL
+ /* (read IP) */
&& (move_from_unaligned32(req_align, req), 1)
- /* and the ip is in the lease range */
+ /* and the IP is in the lease range */
&& ntohl(req_align) >= server_config.start_ip
&& ntohl(req_align) <= server_config.end_ip
/* and is not already taken/offered */
@@ -137,9 +147,10 @@
|| lease_expired(lease))
) {
packet.yiaddr = req_align;
- /* otherwise, find a free IP */
- } else {
- packet.yiaddr = find_free_or_expired_address();
+ }
+ /* Otherwise, find a free IP */
+ else {
+ packet.yiaddr = find_free_or_expired_address(oldpacket->chaddr);
}
if (!packet.yiaddr) {