Skip to content

Commit 797079b

Browse files
mtjhrcslp
authored andcommitted
init: Implement fallback for DHCP servers without Rapid Commit
When a server answers DHCPDISCOVER with DHCPOFFER instead of an immediate ACK, send DHCPREQUEST for the and wait for the final ACK. This makes DHCP work on macOS hosts when using gvproxy for networking. Signed-off-by: Matej Hrica <mhrica@redhat.com>
1 parent 5c28233 commit 797079b

1 file changed

Lines changed: 198 additions & 81 deletions

File tree

init/dhcp.c

Lines changed: 198 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include <unistd.h>
2727

2828
#define DHCP_BUFFER_SIZE 576
29+
#define DHCP_MSG_OFFER 2
30+
#define DHCP_MSG_ACK 5
2931

3032
/* Helper function to send netlink message */
3133
static int nl_send(int sock, struct nlmsghdr *nlh)
@@ -255,6 +257,137 @@ static unsigned char count_leading_ones(uint32_t val)
255257
return count;
256258
}
257259

260+
/* Return the DHCP message type (option 53) from a response, or 0 */
261+
static unsigned char get_dhcp_msg_type(const unsigned char *response,
262+
ssize_t len)
263+
{
264+
/* Walk DHCP options (TLV chain starting after the magic cookie) */
265+
size_t p = 240;
266+
while (p < (size_t)len) {
267+
unsigned char opt = response[p];
268+
269+
if (opt == 0xff) /* end */
270+
break;
271+
if (opt == 0) { /* padding */
272+
p++;
273+
continue;
274+
}
275+
276+
unsigned char opt_len = response[p + 1];
277+
p += 2;
278+
279+
if (p + opt_len > (size_t)len)
280+
break;
281+
if (opt == 53 && opt_len >= 1) /* Message Type */
282+
return response[p];
283+
284+
p += opt_len;
285+
}
286+
return 0;
287+
}
288+
289+
/* Parse a DHCP ACK and configure the interface. Returns 0 or -1 on error. */
290+
static int handle_dhcp_ack(int nl_sock, int iface_index,
291+
const unsigned char *response, ssize_t len)
292+
{
293+
/* Need at least 240 bytes (DHCP header + magic cookie) + 1 for options */
294+
if (len < 241) {
295+
printf("DHCPACK too short (%zd bytes)\n", len);
296+
return -1;
297+
}
298+
299+
/* Parse DHCP response */
300+
struct in_addr addr;
301+
/* yiaddr is at offset 16-19 in network byte order */
302+
memcpy(&addr.s_addr, &response[16], sizeof(addr.s_addr));
303+
304+
if (addr.s_addr == INADDR_ANY) {
305+
printf("DHCPACK has no address (yiaddr is 0.0.0.0)\n");
306+
return -1;
307+
}
308+
309+
struct in_addr netmask = {.s_addr = INADDR_ANY};
310+
struct in_addr router = {.s_addr = INADDR_ANY};
311+
/* Clamp MTU to passt's limit */
312+
uint16_t mtu = 65520;
313+
314+
FILE *resolv = fopen("/etc/resolv.conf", "w");
315+
if (!resolv) {
316+
perror("Failed to open /etc/resolv.conf");
317+
}
318+
319+
/* Parse DHCP options (start at offset 240 after magic cookie) */
320+
size_t p = 240;
321+
while (p < (size_t)len) {
322+
unsigned char opt = response[p];
323+
324+
if (opt == 0xff) {
325+
/* Option 255: End (of options) */
326+
break;
327+
}
328+
329+
if (opt == 0) { /* Padding */
330+
p++;
331+
continue;
332+
}
333+
334+
unsigned char opt_len = response[p + 1];
335+
p += 2; /* Length doesn't include code and length field itself */
336+
337+
if (p + opt_len > (size_t)len) {
338+
/* Malformed packet, option length exceeds packet boundary */
339+
break;
340+
}
341+
342+
if (opt == 1 && opt_len >= 4) {
343+
/* Option 1: Subnet Mask */
344+
memcpy(&netmask.s_addr, &response[p], sizeof(netmask.s_addr));
345+
} else if (opt == 3 && opt_len >= 4) {
346+
/* Option 3: Router */
347+
memcpy(&router.s_addr, &response[p], sizeof(router.s_addr));
348+
} else if (opt == 6 && opt_len >= 4) {
349+
/* Option 6: Domain Name Server */
350+
if (resolv) {
351+
for (int dns_p = p; dns_p + 4 <= p + opt_len; dns_p += 4) {
352+
fprintf(resolv, "nameserver %d.%d.%d.%d\n", response[dns_p],
353+
response[dns_p + 1], response[dns_p + 2],
354+
response[dns_p + 3]);
355+
}
356+
}
357+
} else if (opt == 26 && opt_len >= 2) {
358+
/* Option 26: Interface MTU */
359+
mtu = (response[p] << 8) | response[p + 1];
360+
361+
/* We don't know yet if IPv6 is available: don't go below 1280 B
362+
*/
363+
if (mtu < 1280)
364+
mtu = 1280;
365+
if (mtu > 65520)
366+
mtu = 65520;
367+
}
368+
369+
p += opt_len;
370+
}
371+
372+
if (resolv) {
373+
fclose(resolv);
374+
}
375+
376+
/* Calculate prefix length from netmask */
377+
unsigned char prefix_len = count_leading_ones(ntohl(netmask.s_addr));
378+
379+
if (mod_addr4(nl_sock, iface_index, RTM_NEWADDR, addr, prefix_len) != 0) {
380+
printf("couldn't add the address provided by the DHCP server\n");
381+
return -1;
382+
}
383+
if (mod_route4(nl_sock, iface_index, RTM_NEWROUTE, router) != 0) {
384+
printf("couldn't add the default route provided by the DHCP server\n");
385+
return -1;
386+
}
387+
set_mtu(nl_sock, iface_index, mtu);
388+
return 0;
389+
}
390+
258391
/* Send DISCOVER with Rapid Commit, process ACK, configure address and route */
259392
int do_dhcp(const char *iface)
260393
{
@@ -383,106 +516,90 @@ int do_dhcp(const char *iface)
383516
goto cleanup;
384517
}
385518

386-
/* Get and process response (DHCPACK) if any */
519+
/* Get response: DHCPACK (Rapid Commit) or DHCPOFFER */
387520
struct sockaddr_in from_addr;
388521
socklen_t from_len = sizeof(from_addr);
389522
ssize_t len = recvfrom(sock, response, sizeof(response), 0,
390523
(struct sockaddr *)&from_addr, &from_len);
391524

392-
close(sock);
393-
sock = -1;
394-
395-
if (len > 0) {
396-
/* Parse DHCP response */
397-
struct in_addr addr;
398-
/* yiaddr is at offset 16-19 in network byte order */
399-
memcpy(&addr.s_addr, &response[16], sizeof(addr.s_addr));
525+
if (len <= 0)
526+
goto done; /* No DHCP response — not an error, VM may be IPv6-only */
400527

401-
struct in_addr netmask = {.s_addr = INADDR_ANY};
402-
struct in_addr router = {.s_addr = INADDR_ANY};
403-
/* Clamp MTU to passt's limit */
404-
uint16_t mtu = 65520;
528+
unsigned char msg_type = get_dhcp_msg_type(response, len);
405529

406-
FILE *resolv = fopen("/etc/resolv.conf", "w");
407-
if (!resolv) {
408-
perror("Failed to open /etc/resolv.conf");
530+
if (msg_type == DHCP_MSG_ACK) {
531+
/* Rapid Commit — server sent ACK directly */
532+
close(sock);
533+
sock = -1;
534+
if (handle_dhcp_ack(nl_sock, iface_index, response, len) != 0)
535+
goto cleanup;
536+
} else if (msg_type == DHCP_MSG_OFFER) {
537+
/*
538+
* DHCPOFFER — complete the 4-way handshake by sending DHCPREQUEST
539+
* and waiting for DHCPACK. Servers without Rapid Commit (e.g.
540+
* gvproxy) require this.
541+
*/
542+
struct in_addr offered_addr;
543+
memcpy(&offered_addr.s_addr, &response[16],
544+
sizeof(offered_addr.s_addr));
545+
546+
/* Build DHCPREQUEST */
547+
memset(request.options, 0, sizeof(request.options));
548+
opt_offset = 0;
549+
550+
/* Option 53: DHCP Message Type = REQUEST (3) */
551+
request.options[opt_offset++] = 53;
552+
request.options[opt_offset++] = 1;
553+
request.options[opt_offset++] = 3;
554+
555+
/* Option 50: Requested IP Address */
556+
request.options[opt_offset++] = 50;
557+
request.options[opt_offset++] = 4;
558+
memcpy(&request.options[opt_offset], &offered_addr.s_addr, 4);
559+
opt_offset += 4;
560+
561+
/* Option 54: Server Identifier (from_addr) */
562+
request.options[opt_offset++] = 54;
563+
request.options[opt_offset++] = 4;
564+
memcpy(&request.options[opt_offset], &from_addr.sin_addr.s_addr, 4);
565+
opt_offset += 4;
566+
567+
/* Option 255: End */
568+
request.options[opt_offset++] = 0xff;
569+
570+
if (sendto(sock, &request, sizeof(request), 0,
571+
(struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
572+
perror("sendto DHCPREQUEST failed");
573+
goto cleanup;
409574
}
410575

411-
/* Parse DHCP options (start at offset 240 after magic cookie) */
412-
size_t p = 240;
413-
while (p < (size_t)len) {
414-
unsigned char opt = response[p];
576+
from_len = sizeof(from_addr);
577+
len = recvfrom(sock, response, sizeof(response), 0,
578+
(struct sockaddr *)&from_addr, &from_len);
415579

416-
if (opt == 0xff) {
417-
/* Option 255: End (of options) */
418-
break;
419-
}
420-
421-
if (opt == 0) { /* Padding */
422-
p++;
423-
continue;
424-
}
425-
426-
unsigned char opt_len = response[p + 1];
427-
p += 2; /* Length doesn't include code and length field itself */
428-
429-
if (p + opt_len > (size_t)len) {
430-
/* Malformed packet, option length exceeds packet boundary */
431-
break;
432-
}
433-
434-
if (opt == 1) {
435-
/* Option 1: Subnet Mask */
436-
memcpy(&netmask.s_addr, &response[p], sizeof(netmask.s_addr));
437-
} else if (opt == 3) {
438-
/* Option 3: Router */
439-
memcpy(&router.s_addr, &response[p], sizeof(router.s_addr));
440-
} else if (opt == 6) {
441-
/* Option 6: Domain Name Server */
442-
if (resolv) {
443-
for (int dns_p = p; dns_p + 3 < p + opt_len; dns_p += 4) {
444-
fprintf(resolv, "nameserver %d.%d.%d.%d\n",
445-
response[dns_p], response[dns_p + 1],
446-
response[dns_p + 2], response[dns_p + 3]);
447-
}
448-
}
449-
} else if (opt == 26) {
450-
/* Option 26: Interface MTU */
451-
mtu = (response[p] << 8) | response[p + 1];
452-
453-
/* We don't know yet if IPv6 is available: don't go below 1280 B
454-
*/
455-
if (mtu < 1280)
456-
mtu = 1280;
457-
if (mtu > 65520)
458-
mtu = 65520;
459-
}
460-
461-
p += opt_len;
462-
}
580+
close(sock);
581+
sock = -1;
463582

464-
if (resolv) {
465-
fclose(resolv);
583+
if (len <= 0) {
584+
printf("no DHCPACK received\n");
585+
goto cleanup;
466586
}
467587

468-
/* Calculate prefix length from netmask */
469-
unsigned char prefix_len = count_leading_ones(ntohl(netmask.s_addr));
470-
471-
if (mod_addr4(nl_sock, iface_index, RTM_NEWADDR, addr, prefix_len) !=
472-
0) {
473-
printf("couldn't add the address provided by the DHCP server\n");
588+
if (get_dhcp_msg_type(response, len) != DHCP_MSG_ACK) {
589+
printf("expected DHCPACK but got message type %d\n",
590+
get_dhcp_msg_type(response, len));
474591
goto cleanup;
475592
}
476-
if (mod_route4(nl_sock, iface_index, RTM_NEWROUTE, router) != 0) {
477-
printf(
478-
"couldn't add the default route provided by the DHCP server\n");
593+
594+
if (handle_dhcp_ack(nl_sock, iface_index, response, len) != 0)
479595
goto cleanup;
480-
}
481-
set_mtu(nl_sock, iface_index, mtu);
596+
} else {
597+
printf("unexpected DHCP message type %d\n", msg_type);
598+
goto cleanup;
482599
}
483600

601+
done:
484602
ret = 0;
485-
486603
cleanup:
487604
if (sock >= 0) {
488605
close(sock);

0 commit comments

Comments
 (0)