|
26 | 26 | #include <unistd.h> |
27 | 27 |
|
28 | 28 | #define DHCP_BUFFER_SIZE 576 |
| 29 | +#define DHCP_MSG_OFFER 2 |
| 30 | +#define DHCP_MSG_ACK 5 |
29 | 31 |
|
30 | 32 | /* Helper function to send netlink message */ |
31 | 33 | static int nl_send(int sock, struct nlmsghdr *nlh) |
@@ -255,6 +257,137 @@ static unsigned char count_leading_ones(uint32_t val) |
255 | 257 | return count; |
256 | 258 | } |
257 | 259 |
|
| 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 | + |
258 | 391 | /* Send DISCOVER with Rapid Commit, process ACK, configure address and route */ |
259 | 392 | int do_dhcp(const char *iface) |
260 | 393 | { |
@@ -386,106 +519,90 @@ int do_dhcp(const char *iface) |
386 | 519 | goto cleanup; |
387 | 520 | } |
388 | 521 |
|
389 | | - /* Get and process response (DHCPACK) if any */ |
| 522 | + /* Get response: DHCPACK (Rapid Commit) or DHCPOFFER */ |
390 | 523 | struct sockaddr_in from_addr; |
391 | 524 | socklen_t from_len = sizeof(from_addr); |
392 | 525 | ssize_t len = recvfrom(sock, response, sizeof(response), 0, |
393 | 526 | (struct sockaddr *)&from_addr, &from_len); |
394 | 527 |
|
395 | | - close(sock); |
396 | | - sock = -1; |
397 | | - |
398 | | - if (len > 0) { |
399 | | - /* Parse DHCP response */ |
400 | | - struct in_addr addr; |
401 | | - /* yiaddr is at offset 16-19 in network byte order */ |
402 | | - memcpy(&addr.s_addr, &response[16], sizeof(addr.s_addr)); |
| 528 | + if (len <= 0) |
| 529 | + goto done; /* No DHCP response — not an error, VM may be IPv6-only */ |
403 | 530 |
|
404 | | - struct in_addr netmask = {.s_addr = INADDR_ANY}; |
405 | | - struct in_addr router = {.s_addr = INADDR_ANY}; |
406 | | - /* Clamp MTU to passt's limit */ |
407 | | - uint16_t mtu = 65520; |
| 531 | + unsigned char msg_type = get_dhcp_msg_type(response, len); |
408 | 532 |
|
409 | | - FILE *resolv = fopen("/etc/resolv.conf", "w"); |
410 | | - if (!resolv) { |
411 | | - perror("Failed to open /etc/resolv.conf"); |
| 533 | + if (msg_type == DHCP_MSG_ACK) { |
| 534 | + /* Rapid Commit — server sent ACK directly */ |
| 535 | + close(sock); |
| 536 | + sock = -1; |
| 537 | + if (handle_dhcp_ack(nl_sock, iface_index, response, len) != 0) |
| 538 | + goto cleanup; |
| 539 | + } else if (msg_type == DHCP_MSG_OFFER) { |
| 540 | + /* |
| 541 | + * DHCPOFFER — complete the 4-way handshake by sending DHCPREQUEST |
| 542 | + * and waiting for DHCPACK. Servers without Rapid Commit (e.g. |
| 543 | + * gvproxy) require this. |
| 544 | + */ |
| 545 | + struct in_addr offered_addr; |
| 546 | + memcpy(&offered_addr.s_addr, &response[16], |
| 547 | + sizeof(offered_addr.s_addr)); |
| 548 | + |
| 549 | + /* Build DHCPREQUEST */ |
| 550 | + memset(request.options, 0, sizeof(request.options)); |
| 551 | + opt_offset = 0; |
| 552 | + |
| 553 | + /* Option 53: DHCP Message Type = REQUEST (3) */ |
| 554 | + request.options[opt_offset++] = 53; |
| 555 | + request.options[opt_offset++] = 1; |
| 556 | + request.options[opt_offset++] = 3; |
| 557 | + |
| 558 | + /* Option 50: Requested IP Address */ |
| 559 | + request.options[opt_offset++] = 50; |
| 560 | + request.options[opt_offset++] = 4; |
| 561 | + memcpy(&request.options[opt_offset], &offered_addr.s_addr, 4); |
| 562 | + opt_offset += 4; |
| 563 | + |
| 564 | + /* Option 54: Server Identifier (from_addr) */ |
| 565 | + request.options[opt_offset++] = 54; |
| 566 | + request.options[opt_offset++] = 4; |
| 567 | + memcpy(&request.options[opt_offset], &from_addr.sin_addr.s_addr, 4); |
| 568 | + opt_offset += 4; |
| 569 | + |
| 570 | + /* Option 255: End */ |
| 571 | + request.options[opt_offset++] = 0xff; |
| 572 | + |
| 573 | + if (sendto(sock, &request, sizeof(request), 0, |
| 574 | + (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) { |
| 575 | + perror("sendto DHCPREQUEST failed"); |
| 576 | + goto cleanup; |
412 | 577 | } |
413 | 578 |
|
414 | | - /* Parse DHCP options (start at offset 240 after magic cookie) */ |
415 | | - size_t p = 240; |
416 | | - while (p < (size_t)len) { |
417 | | - unsigned char opt = response[p]; |
| 579 | + from_len = sizeof(from_addr); |
| 580 | + len = recvfrom(sock, response, sizeof(response), 0, |
| 581 | + (struct sockaddr *)&from_addr, &from_len); |
418 | 582 |
|
419 | | - if (opt == 0xff) { |
420 | | - /* Option 255: End (of options) */ |
421 | | - break; |
422 | | - } |
423 | | - |
424 | | - if (opt == 0) { /* Padding */ |
425 | | - p++; |
426 | | - continue; |
427 | | - } |
428 | | - |
429 | | - unsigned char opt_len = response[p + 1]; |
430 | | - p += 2; /* Length doesn't include code and length field itself */ |
431 | | - |
432 | | - if (p + opt_len > (size_t)len) { |
433 | | - /* Malformed packet, option length exceeds packet boundary */ |
434 | | - break; |
435 | | - } |
436 | | - |
437 | | - if (opt == 1) { |
438 | | - /* Option 1: Subnet Mask */ |
439 | | - memcpy(&netmask.s_addr, &response[p], sizeof(netmask.s_addr)); |
440 | | - } else if (opt == 3) { |
441 | | - /* Option 3: Router */ |
442 | | - memcpy(&router.s_addr, &response[p], sizeof(router.s_addr)); |
443 | | - } else if (opt == 6) { |
444 | | - /* Option 6: Domain Name Server */ |
445 | | - if (resolv) { |
446 | | - for (int dns_p = p; dns_p + 3 < p + opt_len; dns_p += 4) { |
447 | | - fprintf(resolv, "nameserver %d.%d.%d.%d\n", |
448 | | - response[dns_p], response[dns_p + 1], |
449 | | - response[dns_p + 2], response[dns_p + 3]); |
450 | | - } |
451 | | - } |
452 | | - } else if (opt == 26) { |
453 | | - /* Option 26: Interface MTU */ |
454 | | - mtu = (response[p] << 8) | response[p + 1]; |
455 | | - |
456 | | - /* We don't know yet if IPv6 is available: don't go below 1280 B |
457 | | - */ |
458 | | - if (mtu < 1280) |
459 | | - mtu = 1280; |
460 | | - if (mtu > 65520) |
461 | | - mtu = 65520; |
462 | | - } |
463 | | - |
464 | | - p += opt_len; |
465 | | - } |
| 583 | + close(sock); |
| 584 | + sock = -1; |
466 | 585 |
|
467 | | - if (resolv) { |
468 | | - fclose(resolv); |
| 586 | + if (len <= 0) { |
| 587 | + printf("no DHCPACK received\n"); |
| 588 | + goto cleanup; |
469 | 589 | } |
470 | 590 |
|
471 | | - /* Calculate prefix length from netmask */ |
472 | | - unsigned char prefix_len = count_leading_ones(ntohl(netmask.s_addr)); |
473 | | - |
474 | | - if (mod_addr4(nl_sock, iface_index, RTM_NEWADDR, addr, prefix_len) != |
475 | | - 0) { |
476 | | - printf("couldn't add the address provided by the DHCP server\n"); |
| 591 | + if (get_dhcp_msg_type(response, len) != DHCP_MSG_ACK) { |
| 592 | + printf("expected DHCPACK but got message type %d\n", |
| 593 | + get_dhcp_msg_type(response, len)); |
477 | 594 | goto cleanup; |
478 | 595 | } |
479 | | - if (mod_route4(nl_sock, iface_index, RTM_NEWROUTE, router) != 0) { |
480 | | - printf( |
481 | | - "couldn't add the default route provided by the DHCP server\n"); |
| 596 | + |
| 597 | + if (handle_dhcp_ack(nl_sock, iface_index, response, len) != 0) |
482 | 598 | goto cleanup; |
483 | | - } |
484 | | - set_mtu(nl_sock, iface_index, mtu); |
| 599 | + } else { |
| 600 | + printf("unexpected DHCP message type %d\n", msg_type); |
| 601 | + goto cleanup; |
485 | 602 | } |
486 | 603 |
|
| 604 | +done: |
487 | 605 | ret = 0; |
488 | | - |
489 | 606 | cleanup: |
490 | 607 | if (sock >= 0) { |
491 | 608 | close(sock); |
|
0 commit comments