Some routers/AP handle the DHCP broadcast flag incorrectly.
This means that some AP discard the DHCP packet if broadcast
flag is present and some discard it if broadcast flag is missing.

The workaround is to send two DISCOVER packets in INIT state and
two REQUEST packets in REBOOTING state. The first packet does not
set the broadcast flag and the latter one sets it. When response
is received, we check what kind of destination address (unicast or
broadcast) the server used and start to send packets to it having
either broadcast flag set or not.
---
 gdhcp/client.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
 gdhcp/common.c |  8 ++++---
 gdhcp/common.h |  3 ++-
 gdhcp/server.c |  4 ++--
 4 files changed, 74 insertions(+), 13 deletions(-)

diff --git a/gdhcp/client.c b/gdhcp/client.c
index 16fe080..238a301 100644
--- a/gdhcp/client.c
+++ b/gdhcp/client.c
@@ -155,6 +155,7 @@ struct _GDHCPClient {
        uint32_t expire;
        bool retransmit;
        struct timeval start_time;
+       bool request_bcast;
 };
 
 static inline void debug(GDHCPClient *client, const char *format, ...)
@@ -436,6 +437,7 @@ static uint16_t dhcp_attempt_secs(GDHCPClient *dhcp_client)
 static int send_discover(GDHCPClient *dhcp_client, uint32_t requested)
 {
        struct dhcp_packet packet;
+       int ret;
 
        debug(dhcp_client, "sending DHCP discover request");
 
@@ -455,15 +457,30 @@ static int send_discover(GDHCPClient *dhcp_client, 
uint32_t requested)
 
        add_send_options(dhcp_client, &packet);
 
+       /*
+        * We send discover packet twice, first without broadcast flag and
+        * then with broadcast flag. Reason is various buggy routers/ap that
+        * either eat the other or vice versa. In the receiving side we then
+        * find out what kind of packet the server can send.
+        */
+       ret = dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+                               INADDR_BROADCAST, SERVER_PORT,
+                               MAC_BCAST_ADDR, dhcp_client->ifindex, false);
+       if (ret < 0)
+               return ret;
+
        return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
-                                       INADDR_BROADCAST, SERVER_PORT,
-                                       MAC_BCAST_ADDR, dhcp_client->ifindex);
+                               INADDR_BROADCAST, SERVER_PORT,
+                               MAC_BCAST_ADDR, dhcp_client->ifindex, true);
 }
 
 static int send_request(GDHCPClient *dhcp_client)
 {
        struct dhcp_packet packet;
-       debug(dhcp_client, "sending DHCP request");
+       int ret;
+
+       debug(dhcp_client, "sending DHCP request (state %d)",
+               dhcp_client->state);
 
        init_packet(dhcp_client, &packet, DHCPREQUEST);
 
@@ -492,9 +509,23 @@ static int send_request(GDHCPClient *dhcp_client)
                                dhcp_client->requested_ip, CLIENT_PORT,
                                dhcp_client->server_ip, SERVER_PORT);
 
-       return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+       if (dhcp_client->state != REBOOTING)
+               return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
                                        INADDR_BROADCAST, SERVER_PORT,
-                                       MAC_BCAST_ADDR, dhcp_client->ifindex);
+                                       MAC_BCAST_ADDR, dhcp_client->ifindex,
+                                       dhcp_client->request_bcast);
+
+       ret = dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+                               INADDR_BROADCAST, SERVER_PORT,
+                               MAC_BCAST_ADDR, dhcp_client->ifindex,
+                               false);
+       if (ret < 0)
+               return ret;
+
+       return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+                               INADDR_BROADCAST, SERVER_PORT,
+                               MAC_BCAST_ADDR, dhcp_client->ifindex,
+                               true);
 }
 
 static int send_release(GDHCPClient *dhcp_client,
@@ -1186,6 +1217,7 @@ GDHCPClient *g_dhcp_client_new(GDHCPType type,
        dhcp_client->duid_len = 0;
        dhcp_client->last_request = time(NULL);
        dhcp_client->expire = 0;
+       dhcp_client->request_bcast = false;
 
        *error = G_DHCP_CLIENT_ERROR_NONE;
 
@@ -1293,7 +1325,8 @@ static bool sanity_check(struct ip_udp_dhcp_packet 
*packet, int bytes)
        return true;
 }
 
-static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd)
+static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd,
+                               struct sockaddr_in *dst_addr)
 {
        int bytes;
        struct ip_udp_dhcp_packet packet;
@@ -1338,6 +1371,8 @@ static int dhcp_recv_l2_packet(struct dhcp_packet 
*dhcp_pkt, int fd)
        if (dhcp_pkt->cookie != htonl(DHCP_MAGIC))
                return -1;
 
+       dst_addr->sin_addr.s_addr = packet.ip.daddr;
+
        return bytes - (sizeof(packet.ip) + sizeof(packet.udp));
 }
 
@@ -2231,6 +2266,7 @@ static gboolean listener_event(GIOChannel *channel, 
GIOCondition condition,
                                                        gpointer user_data)
 {
        GDHCPClient *dhcp_client = user_data;
+       struct sockaddr_in dst_addr = { 0 };
        struct dhcp_packet packet;
        struct dhcpv6_packet *packet6 = NULL;
        uint8_t *message_type = NULL, *client_id = NULL, *option,
@@ -2256,8 +2292,11 @@ static gboolean listener_event(GIOChannel *channel, 
GIOCondition condition,
        dhcp_client->status_code = 0;
 
        if (dhcp_client->listen_mode == L2) {
+               dst_addr.sin_addr.s_addr = 0;
+
                re = dhcp_recv_l2_packet(&packet,
-                                       dhcp_client->listener_sockfd);
+                                       dhcp_client->listener_sockfd,
+                                       &dst_addr);
                xid = packet.xid;
        } else if (dhcp_client->listen_mode == L3) {
                if (dhcp_client->type == G_DHCP_IPV6) {
@@ -2347,10 +2386,28 @@ static gboolean listener_event(GIOChannel *channel, 
GIOCondition condition,
 
                dhcp_client->state = REQUESTING;
 
+               if (dst_addr.sin_addr.s_addr == INADDR_BROADCAST)
+                       dhcp_client->request_bcast = true;
+               else
+                       dhcp_client->request_bcast = false;
+
+               debug(dhcp_client, "init ip %s -> %sadding broadcast flag",
+                       inet_ntoa(dst_addr.sin_addr),
+                       dhcp_client->request_bcast ? "" : "not ");
+
                start_request(dhcp_client);
 
                return TRUE;
        case REBOOTING:
+               if (dst_addr.sin_addr.s_addr == INADDR_BROADCAST)
+                       dhcp_client->request_bcast = true;
+               else
+                       dhcp_client->request_bcast = false;
+
+               debug(dhcp_client, "ip %s -> %sadding broadcast flag",
+                       inet_ntoa(dst_addr.sin_addr),
+                       dhcp_client->request_bcast ? "" : "not ");
+               /* fall through */
        case REQUESTING:
        case RENEWING:
        case REBINDING:
@@ -2841,6 +2898,7 @@ void g_dhcp_client_stop(GDHCPClient *dhcp_client)
        dhcp_client->requested_ip = 0;
        dhcp_client->state = RELEASED;
        dhcp_client->lease_seconds = 0;
+       dhcp_client->request_bcast = false;
 }
 
 GList *g_dhcp_client_get_option(GDHCPClient *dhcp_client,
diff --git a/gdhcp/common.c b/gdhcp/common.c
index e111150..45278a8 100644
--- a/gdhcp/common.c
+++ b/gdhcp/common.c
@@ -511,8 +511,9 @@ int dhcpv6_send_packet(int index, struct dhcpv6_packet 
*dhcp_pkt, int len)
 }
 
 int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
-               uint32_t source_ip, int source_port, uint32_t dest_ip,
-                       int dest_port, const uint8_t *dest_arp, int ifindex)
+                       uint32_t source_ip, int source_port,
+                       uint32_t dest_ip, int dest_port,
+                       const uint8_t *dest_arp, int ifindex, bool bcast)
 {
        struct sockaddr_ll dest;
        struct ip_udp_dhcp_packet packet;
@@ -529,7 +530,8 @@ int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
        if (fd < 0)
                return -errno;
 
-       dhcp_pkt->flags |= htons(BROADCAST_FLAG);
+       if (bcast)
+               dhcp_pkt->flags |= htons(BROADCAST_FLAG);
 
        memset(&dest, 0, sizeof(dest));
        memset(&packet, 0, sizeof(packet));
diff --git a/gdhcp/common.h b/gdhcp/common.h
index e4a4251..c692799 100644
--- a/gdhcp/common.h
+++ b/gdhcp/common.h
@@ -196,7 +196,8 @@ void dhcpv6_init_header(struct dhcpv6_packet *packet, 
uint8_t type);
 int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
                        uint32_t source_ip, int source_port,
                        uint32_t dest_ip, int dest_port,
-                       const uint8_t *dest_arp, int ifindex);
+                       const uint8_t *dest_arp, int ifindex,
+                       bool bcast);
 int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len);
 int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
                        uint32_t source_ip, int source_port,
diff --git a/gdhcp/server.c b/gdhcp/server.c
index 728992d..aa40488 100644
--- a/gdhcp/server.c
+++ b/gdhcp/server.c
@@ -529,7 +529,7 @@ static void send_packet_to_client(GDHCPServer *dhcp_server,
        dhcp_send_raw_packet(dhcp_pkt,
                dhcp_server->server_nip, SERVER_PORT,
                ciaddr, CLIENT_PORT, chaddr,
-               dhcp_server->ifindex);
+               dhcp_server->ifindex, false);
 }
 
 static void send_offer(GDHCPServer *dhcp_server,
@@ -626,7 +626,7 @@ static void send_NAK(GDHCPServer *dhcp_server,
        dhcp_send_raw_packet(&packet,
                        dhcp_server->server_nip, SERVER_PORT,
                        INADDR_BROADCAST, CLIENT_PORT, MAC_BCAST_ADDR,
-                       dhcp_server->ifindex);
+                       dhcp_server->ifindex, false);
 }
 
 static void send_inform(GDHCPServer *dhcp_server,
-- 
1.8.3.1

_______________________________________________
connman mailing list
connman@connman.net
https://lists.connman.net/mailman/listinfo/connman

Reply via email to