You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C/C++中从DHCP配置获取TFTP服务器(next-server)地址?

在C/C++中获取DHCP的Next-Server(TFTP服务器)地址

完全可以在C/C++中实现从DHCP服务器获取next-server地址的功能,核心是通过构造DHCP客户端报文,广播发送后解析服务器回复中的siaddr字段(该字段对应DHCP协议定义的Next Server IP Address,也就是PXE使用的TFTP服务器地址)。

实现思路

  • 创建UDP套接字:DHCP客户端使用UDP 68端口,需要root权限绑定该端口。
  • 构造DHCP DISCOVER报文:向广播地址255.255.255.255:67发送DISCOVER请求,触发DHCP服务器回复OFFER/ACK报文。
  • 解析DHCP回复:从收到的DHCP报文中提取siaddr字段,转换为可读的IPv4地址。

代码示例

以下是一个简化的C语言实现,可直接编译运行(需root权限):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>

// DHCP报文结构简化定义
typedef struct dhcp_packet {
    uint8_t op;          // 操作码: 1=请求, 2=回复
    uint8_t htype;       // 硬件类型: 1=以太网
    uint8_t hlen;        // 硬件地址长度: 6
    uint8_t hops;        // 跳数
    uint32_t xid;        // 事务ID
    uint16_t secs;       // 秒数
    uint16_t flags;      // 标志位
    uint32_t ciaddr;     // 客户端IP(已分配)
    uint32_t yiaddr;     // 服务器分配的IP
    uint32_t siaddr;     // Next Server IP(TFTP服务器地址)
    uint32_t giaddr;     // 中继代理IP
    uint8_t chaddr[16];  // 客户端硬件地址
    uint8_t sname[64];   // 服务器主机名
    uint8_t file[128];   // 启动文件名
    uint8_t options[312];// 选项字段
} dhcp_packet_t;

#define DHCP_MAGIC_COOKIE 0x63825363

int main() {
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    dhcp_packet_t dhcp_discover;
    char buffer[1024];
    socklen_t addr_len = sizeof(server_addr);

    // 创建UDP套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置套接字选项,允许广播
    int broadcast_enable = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast_enable, sizeof(broadcast_enable)) < 0) {
        perror("setsockopt SO_BROADCAST failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 绑定到DHCP客户端端口(68)
    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(68);
    client_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
        perror("bind failed (need root?)");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 构造DHCP DISCOVER报文
    memset(&dhcp_discover, 0, sizeof(dhcp_discover));
    dhcp_discover.op = 1; // BOOTREQUEST
    dhcp_discover.htype = 1;
    dhcp_discover.hlen = 6;
    dhcp_discover.xid = htonl(rand()); // 随机事务ID
    dhcp_discover.flags = htons(0x8000); // 广播标志

    // 获取本地MAC地址(以eth0为例,可根据实际网卡修改)
    struct ifreq ifr;
    strcpy(ifr.ifr_name, "eth0");
    if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) < 0) {
        perror("ioctl get MAC failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    memcpy(dhcp_discover.chaddr, ifr.ifr_hwaddr.sa_data, 6);

    // 填充DHCP选项:Magic Cookie + DHCPDISCOVER
    uint8_t *opts = dhcp_discover.options;
    *(uint32_t *)opts = htonl(DHCP_MAGIC_COOKIE);
    opts += 4;
    *opts++ = 53; // DHCP消息类型选项
    *opts++ = 1;
    *opts++ = 1; // DHCPDISCOVER
    *opts++ = 255; // 选项结束

    // 设置服务器地址(广播)
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(67);
    server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);

    // 发送DHCP DISCOVER
    if (sendto(sockfd, &dhcp_discover, sizeof(dhcp_discover), 0,
               (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("sendto failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Sent DHCP DISCOVER, waiting for response...\n");

    // 接收DHCP回复
    ssize_t recv_len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                                (struct sockaddr *)&server_addr, &addr_len);
    if (recv_len < 0) {
        perror("recvfrom failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 解析DHCP回复中的siaddr字段
    dhcp_packet_t *dhcp_reply = (dhcp_packet_t *)buffer;
    if (dhcp_reply->op == 2) { // BOOTREPLY
        char tftp_server_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &dhcp_reply->siaddr, tftp_server_ip, INET_ADDRSTRLEN);
        printf("TFTP Server (next-server) address: %s\n", tftp_server_ip);
    } else {
        printf("Received non-BOOTREPLY packet\n");
    }

    close(sockfd);
    return 0;
}

注意事项

  • 需要root权限运行,因为绑定UDP 68端口是特权操作。
  • 代码中默认使用eth0网卡,若你的系统网卡名称不同(如ens33),需修改strcpy(ifr.ifr_name, "eth0")中的网卡名。
  • 该代码仅处理第一个DHCP回复,实际场景中可能需要筛选目标DHCP服务器的回复(可通过解析选项中的服务器标识符来实现)。
  • 若需要完整的DHCP客户端功能(如获取ACK报文、处理租约),可参考Busybox的udhcpc源码实现逻辑。

内容的提问来源于stack exchange,提问作者alexanderzhirov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 14:46:07