|
25 | 25 | #include <unistd.h> |
26 | 26 |
|
27 | 27 | #define DHCP_BUFFER_SIZE 576 |
| 28 | +#define DHCP_MSG_OFFER 2 |
| 29 | +#define DHCP_MSG_ACK 5 |
28 | 30 |
|
29 | 31 | /* Helper function to send netlink message */ |
30 | 32 | static int nl_send(int sock, struct nlmsghdr *nlh) |
@@ -254,6 +256,126 @@ static unsigned char count_leading_ones(uint32_t val) |
254 | 256 | return count; |
255 | 257 | } |
256 | 258 |
|
| 259 | +/* Return the DHCP message type (option 53) from a response, or 0 */ |
| 260 | +static unsigned char get_dhcp_msg_type(const unsigned char *response, |
| 261 | + ssize_t len) |
| 262 | +{ |
| 263 | + /* Walk DHCP options (TLV chain starting after the magic cookie) */ |
| 264 | + size_t p = 240; |
| 265 | + while (p < (size_t)len) { |
| 266 | + unsigned char opt = response[p]; |
| 267 | + |
| 268 | + if (opt == 0xff) /* end */ |
| 269 | + break; |
| 270 | + if (opt == 0) { /* padding */ |
| 271 | + p++; |
| 272 | + continue; |
| 273 | + } |
| 274 | + |
| 275 | + unsigned char opt_len = response[p + 1]; |
| 276 | + p += 2; |
| 277 | + |
| 278 | + if (p + opt_len > (size_t)len) |
| 279 | + break; |
| 280 | + if (opt == 53 && opt_len >= 1) /* Message Type */ |
| 281 | + return response[p]; |
| 282 | + |
| 283 | + p += opt_len; |
| 284 | + } |
| 285 | + return 0; |
| 286 | +} |
| 287 | + |
| 288 | +/* Parse a DHCP ACK and configure the interface. Returns 0 or -1 on error. */ |
| 289 | +static int handle_dhcp_ack(int nl_sock, int iface_index, |
| 290 | + const unsigned char *response, ssize_t len) |
| 291 | +{ |
| 292 | + /* Parse DHCP response */ |
| 293 | + struct in_addr addr; |
| 294 | + /* yiaddr is at offset 16-19 in network byte order */ |
| 295 | + memcpy(&addr.s_addr, &response[16], sizeof(addr.s_addr)); |
| 296 | + |
| 297 | + struct in_addr netmask = {.s_addr = INADDR_ANY}; |
| 298 | + struct in_addr router = {.s_addr = INADDR_ANY}; |
| 299 | + /* Clamp MTU to passt's limit */ |
| 300 | + uint16_t mtu = 65520; |
| 301 | + |
| 302 | + FILE *resolv = fopen("/etc/resolv.conf", "w"); |
| 303 | + if (!resolv) { |
| 304 | + perror("Failed to open /etc/resolv.conf"); |
| 305 | + } |
| 306 | + |
| 307 | + /* Parse DHCP options (start at offset 240 after magic cookie) */ |
| 308 | + size_t p = 240; |
| 309 | + while (p < (size_t)len) { |
| 310 | + unsigned char opt = response[p]; |
| 311 | + |
| 312 | + if (opt == 0xff) { |
| 313 | + /* Option 255: End (of options) */ |
| 314 | + break; |
| 315 | + } |
| 316 | + |
| 317 | + if (opt == 0) { /* Padding */ |
| 318 | + p++; |
| 319 | + continue; |
| 320 | + } |
| 321 | + |
| 322 | + unsigned char opt_len = response[p + 1]; |
| 323 | + p += 2; /* Length doesn't include code and length field itself */ |
| 324 | + |
| 325 | + if (p + opt_len > (size_t)len) { |
| 326 | + /* Malformed packet, option length exceeds packet boundary */ |
| 327 | + break; |
| 328 | + } |
| 329 | + |
| 330 | + if (opt == 1) { |
| 331 | + /* Option 1: Subnet Mask */ |
| 332 | + memcpy(&netmask.s_addr, &response[p], sizeof(netmask.s_addr)); |
| 333 | + } else if (opt == 3) { |
| 334 | + /* Option 3: Router */ |
| 335 | + memcpy(&router.s_addr, &response[p], sizeof(router.s_addr)); |
| 336 | + } else if (opt == 6) { |
| 337 | + /* Option 6: Domain Name Server */ |
| 338 | + if (resolv) { |
| 339 | + for (int dns_p = p; dns_p + 3 < p + opt_len; dns_p += 4) { |
| 340 | + fprintf(resolv, "nameserver %d.%d.%d.%d\n", response[dns_p], |
| 341 | + response[dns_p + 1], response[dns_p + 2], |
| 342 | + response[dns_p + 3]); |
| 343 | + } |
| 344 | + } |
| 345 | + } else if (opt == 26) { |
| 346 | + /* Option 26: Interface MTU */ |
| 347 | + mtu = (response[p] << 8) | response[p + 1]; |
| 348 | + |
| 349 | + /* We don't know yet if IPv6 is available: don't go below 1280 B |
| 350 | + */ |
| 351 | + if (mtu < 1280) |
| 352 | + mtu = 1280; |
| 353 | + if (mtu > 65520) |
| 354 | + mtu = 65520; |
| 355 | + } |
| 356 | + |
| 357 | + p += opt_len; |
| 358 | + } |
| 359 | + |
| 360 | + if (resolv) { |
| 361 | + fclose(resolv); |
| 362 | + } |
| 363 | + |
| 364 | + /* Calculate prefix length from netmask */ |
| 365 | + unsigned char prefix_len = count_leading_ones(ntohl(netmask.s_addr)); |
| 366 | + |
| 367 | + if (mod_addr4(nl_sock, iface_index, RTM_NEWADDR, addr, prefix_len) != 0) { |
| 368 | + printf("couldn't add the address provided by the DHCP server\n"); |
| 369 | + return -1; |
| 370 | + } |
| 371 | + if (mod_route4(nl_sock, iface_index, RTM_NEWROUTE, router) != 0) { |
| 372 | + printf("couldn't add the default route provided by the DHCP server\n"); |
| 373 | + return -1; |
| 374 | + } |
| 375 | + set_mtu(nl_sock, iface_index, mtu); |
| 376 | + return 0; |
| 377 | +} |
| 378 | + |
257 | 379 | /* Send DISCOVER with Rapid Commit, process ACK, configure address and route */ |
258 | 380 | int do_dhcp(const char *iface) |
259 | 381 | { |
@@ -376,106 +498,84 @@ int do_dhcp(const char *iface) |
376 | 498 | goto cleanup; |
377 | 499 | } |
378 | 500 |
|
379 | | - /* Get and process response (DHCPACK) if any */ |
| 501 | + /* Get response: DHCPACK (Rapid Commit) or DHCPOFFER */ |
380 | 502 | struct sockaddr_in from_addr; |
381 | 503 | socklen_t from_len = sizeof(from_addr); |
382 | 504 | ssize_t len = recvfrom(sock, response, sizeof(response), 0, |
383 | 505 | (struct sockaddr *)&from_addr, &from_len); |
384 | 506 |
|
385 | | - close(sock); |
386 | | - sock = -1; |
387 | | - |
388 | | - if (len > 0) { |
389 | | - /* Parse DHCP response */ |
390 | | - struct in_addr addr; |
391 | | - /* yiaddr is at offset 16-19 in network byte order */ |
392 | | - memcpy(&addr.s_addr, &response[16], sizeof(addr.s_addr)); |
393 | | - |
394 | | - struct in_addr netmask = {.s_addr = INADDR_ANY}; |
395 | | - struct in_addr router = {.s_addr = INADDR_ANY}; |
396 | | - /* Clamp MTU to passt's limit */ |
397 | | - uint16_t mtu = 65520; |
398 | | - |
399 | | - FILE *resolv = fopen("/etc/resolv.conf", "w"); |
400 | | - if (!resolv) { |
401 | | - perror("Failed to open /etc/resolv.conf"); |
402 | | - } |
403 | | - |
404 | | - /* Parse DHCP options (start at offset 240 after magic cookie) */ |
405 | | - size_t p = 240; |
406 | | - while (p < (size_t)len) { |
407 | | - unsigned char opt = response[p]; |
408 | | - |
409 | | - if (opt == 0xff) { |
410 | | - /* Option 255: End (of options) */ |
411 | | - break; |
412 | | - } |
413 | | - |
414 | | - if (opt == 0) { /* Padding */ |
415 | | - p++; |
416 | | - continue; |
417 | | - } |
418 | | - |
419 | | - unsigned char opt_len = response[p + 1]; |
420 | | - p += 2; /* Length doesn't include code and length field itself */ |
421 | | - |
422 | | - if (p + opt_len > (size_t)len) { |
423 | | - /* Malformed packet, option length exceeds packet boundary */ |
424 | | - break; |
425 | | - } |
| 507 | + if (len <= 0) |
| 508 | + goto done; /* No DHCP response — not an error, VM may be IPv6-only */ |
426 | 509 |
|
427 | | - if (opt == 1) { |
428 | | - /* Option 1: Subnet Mask */ |
429 | | - memcpy(&netmask.s_addr, &response[p], sizeof(netmask.s_addr)); |
430 | | - } else if (opt == 3) { |
431 | | - /* Option 3: Router */ |
432 | | - memcpy(&router.s_addr, &response[p], sizeof(router.s_addr)); |
433 | | - } else if (opt == 6) { |
434 | | - /* Option 6: Domain Name Server */ |
435 | | - if (resolv) { |
436 | | - for (int dns_p = p; dns_p + 3 < p + opt_len; dns_p += 4) { |
437 | | - fprintf(resolv, "nameserver %d.%d.%d.%d\n", |
438 | | - response[dns_p], response[dns_p + 1], |
439 | | - response[dns_p + 2], response[dns_p + 3]); |
440 | | - } |
441 | | - } |
442 | | - } else if (opt == 26) { |
443 | | - /* Option 26: Interface MTU */ |
444 | | - mtu = (response[p] << 8) | response[p + 1]; |
445 | | - |
446 | | - /* We don't know yet if IPv6 is available: don't go below 1280 B |
447 | | - */ |
448 | | - if (mtu < 1280) |
449 | | - mtu = 1280; |
450 | | - if (mtu > 65520) |
451 | | - mtu = 65520; |
452 | | - } |
| 510 | + unsigned char msg_type = get_dhcp_msg_type(response, len); |
453 | 511 |
|
454 | | - p += opt_len; |
| 512 | + if (msg_type == DHCP_MSG_ACK) { |
| 513 | + /* Rapid Commit — server sent ACK directly */ |
| 514 | + close(sock); |
| 515 | + sock = -1; |
| 516 | + if (handle_dhcp_ack(nl_sock, iface_index, response, len) != 0) |
| 517 | + goto cleanup; |
| 518 | + } else if (msg_type == DHCP_MSG_OFFER) { |
| 519 | + /* |
| 520 | + * DHCPOFFER — complete the 4-way handshake by sending DHCPREQUEST |
| 521 | + * and waiting for DHCPACK. Servers without Rapid Commit (e.g. |
| 522 | + * gvproxy) require this. |
| 523 | + */ |
| 524 | + struct in_addr offered_addr; |
| 525 | + memcpy(&offered_addr.s_addr, &response[16], |
| 526 | + sizeof(offered_addr.s_addr)); |
| 527 | + |
| 528 | + /* Build DHCPREQUEST */ |
| 529 | + memset(request.options, 0, sizeof(request.options)); |
| 530 | + opt_offset = 0; |
| 531 | + |
| 532 | + /* Option 53: DHCP Message Type = REQUEST (3) */ |
| 533 | + request.options[opt_offset++] = 53; |
| 534 | + request.options[opt_offset++] = 1; |
| 535 | + request.options[opt_offset++] = 3; |
| 536 | + |
| 537 | + /* Option 50: Requested IP Address */ |
| 538 | + request.options[opt_offset++] = 50; |
| 539 | + request.options[opt_offset++] = 4; |
| 540 | + memcpy(&request.options[opt_offset], &offered_addr.s_addr, 4); |
| 541 | + opt_offset += 4; |
| 542 | + |
| 543 | + /* Option 54: Server Identifier (from_addr) */ |
| 544 | + request.options[opt_offset++] = 54; |
| 545 | + request.options[opt_offset++] = 4; |
| 546 | + memcpy(&request.options[opt_offset], &from_addr.sin_addr.s_addr, 4); |
| 547 | + opt_offset += 4; |
| 548 | + |
| 549 | + /* Option 255: End */ |
| 550 | + request.options[opt_offset++] = 0xff; |
| 551 | + |
| 552 | + if (sendto(sock, &request, sizeof(request), 0, |
| 553 | + (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) { |
| 554 | + perror("sendto DHCPREQUEST failed"); |
| 555 | + goto cleanup; |
455 | 556 | } |
456 | 557 |
|
457 | | - if (resolv) { |
458 | | - fclose(resolv); |
459 | | - } |
| 558 | + from_len = sizeof(from_addr); |
| 559 | + len = recvfrom(sock, response, sizeof(response), 0, |
| 560 | + (struct sockaddr *)&from_addr, &from_len); |
460 | 561 |
|
461 | | - /* Calculate prefix length from netmask */ |
462 | | - unsigned char prefix_len = count_leading_ones(ntohl(netmask.s_addr)); |
| 562 | + close(sock); |
| 563 | + sock = -1; |
463 | 564 |
|
464 | | - if (mod_addr4(nl_sock, iface_index, RTM_NEWADDR, addr, prefix_len) != |
465 | | - 0) { |
466 | | - printf("couldn't add the address provided by the DHCP server\n"); |
| 565 | + if (len <= 0) { |
| 566 | + printf("no DHCPACK received\n"); |
467 | 567 | goto cleanup; |
468 | 568 | } |
469 | | - if (mod_route4(nl_sock, iface_index, RTM_NEWROUTE, router) != 0) { |
470 | | - printf( |
471 | | - "couldn't add the default route provided by the DHCP server\n"); |
| 569 | + |
| 570 | + if (handle_dhcp_ack(nl_sock, iface_index, response, len) != 0) |
472 | 571 | goto cleanup; |
473 | | - } |
474 | | - set_mtu(nl_sock, iface_index, mtu); |
| 572 | + } else { |
| 573 | + printf("unexpected DHCP message type %d\n", msg_type); |
| 574 | + goto cleanup; |
475 | 575 | } |
476 | 576 |
|
| 577 | +done: |
477 | 578 | ret = 0; |
478 | | - |
479 | 579 | cleanup: |
480 | 580 | if (sock >= 0) { |
481 | 581 | close(sock); |
|
0 commit comments