c - 尽管RAW套接字和HDRINCL,IP源地址仍然由系统填充

标签 c sockets ip dhcp raw-sockets

我正在编写简单的 DHCP 客户端,因此根据 RFC 951,我需要发送 IP 源地址 =“0.0.0.0”的数据包。据我所知,可以使用原始套接字,但显然我的代码中的某些内容是错误的 - 源地址始终由内核填充(到其他接口(interface)的定义地址)。

   if (sockfd==0)
    {
        sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
        if (sockfd<0)
        {
            return result.setError(-1, "Can't create socket.");
        }
        status = setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, m_ifName, strlen(m_ifName));
        if (status<0)
        {
            return result.setError(status, "Can't bind socket to the interface.");
        }

        int optVal = 1;
        status = setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &optVal, sizeof(optVal));
        if (status != 0)
        {
            return result.setError(status, "Can't set IP_HDRINCL option on a socket");
        }

        int broadcastVal = 1;
        status = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,
                            reinterpret_cast<const void*>(&broadcastVal), sizeof(broadcastVal));
        if (status!=0)
        {
            return result.setError(status, "Can't set a broadcast option on a socket.");
        }

        int reuseAddrVal = 1;
        status = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseAddrVal, sizeof(reuseAddrVal));
        if(status != 0)
            return result.setError(status, "Can't set a SO_REUSEADDR opt on socket.");

        struct sockaddr_in src_addr;
        src_addr.sin_family = AF_INET;
        src_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
        src_addr.sin_port = htons(sourcePort);
        status = bind(sockfd, reinterpret_cast<sockaddr *>(&src_addr), sizeof(sockaddr_in));
        if (status != 0)
        {
            return result.setError(status, "Can't bind to the socket.");
        }
    }

    unsigned char buffer[8192];
    struct sockaddr_in dest_addr;
    char* packetData = (char*)(buffer + sizeof(struct ip) + sizeof(struct udphdr));
    struct ip* ip_header = (struct ip*) buffer;
    struct udphdr* udp_header = (struct udphdr*) (buffer + sizeof(struct ip));

    memset(buffer, 0, sizeof(buffer));
    memset(&dest_addr, 0, sizeof(dest_addr));

    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.s_addr = INADDR_BROADCAST;
    dest_addr.sin_port = htons(67);

    ip_header->ip_v = 4;
    ip_header->ip_hl = 5;
    ip_header->ip_tos = 0;
    ip_header->ip_id = 0;
    ip_header->ip_ttl = 63;
    ip_header->ip_p = IPPROTO_UDP;
    ip_header->ip_off = 0;
    ip_header->ip_sum = 0;
    ip_header->ip_src.s_addr = inet_addr("0.0.0.0");
    ip_header->ip_dst.s_addr = htonl(INADDR_BROADCAST);

    udp_header->source = htons(68);
    udp_header->dest = htons(67);
    udp_header->check = htons(0);

    DhcpData data(sizeof(DhcpHdr), 0);
    prepareDhcpPacket(type, data, result);
    if (result.errCode != 0)
    {
        return result;
    }
    strcpy(packetData, (const char*) data.constData());

    ip_header->ip_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + data.size());
    udp_header->len = htons(sizeof(struct udphdr) + data.size());

    ip_header->ip_sum = ComputeChecksum((unsigned char *)ip_header, ip_header->ip_hl*4);

    int segment_len = (sizeof(struct iphdr) + sizeof(struct udphdr) + data.size()) - ip_header->ip_hl*4;
    int header_len = sizeof(PseudoHeader) + segment_len;

    unsigned char* hdr = (unsigned char *)malloc(header_len);
    PseudoHeader* pseudo_header;
    pseudo_header = (PseudoHeader *)hdr;
    pseudo_header->source_ip = ip_header->ip_src.s_addr;
    pseudo_header->dest_ip = ip_header->ip_dst.s_addr;
    pseudo_header->reserved = 0;
    pseudo_header->protocol = ip_header->ip_p;
    pseudo_header->udp_length = htons(segment_len);

    memcpy((hdr + sizeof(PseudoHeader)), (void *)udp_header, 8);
    memcpy((hdr + sizeof(PseudoHeader) + 8), packetData, data.size());
    udp_header->check = ComputeChecksum(hdr, header_len);

    free(hdr);

    int res = sendto(sockfd, buffer, (sizeof(struct ip) + sizeof(struct udphdr) + data.size()), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if(res < 0)
    {
        printf("Errno=%s", strerror(errno));
    }

/* taken from TCP/IP Illustrated Vol. 2(1995) by Gary R. Wright and W. Richard Stevens. Page 236 */
unsigned short ComputeChecksum(unsigned char *data, int len)
{
    long sum = 0;  /* assume 32 bit long, 16 bit short */
    unsigned short *temp = (unsigned short *)data;

    while(len > 1){
        sum += *temp++;
        if(sum & 0x80000000)   /* if high order bit set, fold */
            sum = (sum & 0xFFFF) + (sum >> 16);
        len -= 2;
    }

    if(len)       /* take care of left over byte */
        sum += (unsigned short) *((unsigned char *)temp);

    while(sum>>16)
        sum = (sum & 0xFFFF) + (sum >> 16);

    return ~sum;
}

最佳答案

sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);

不允许更改 0.0.0.0 上的 IP,请参阅 man: raw(7)

Source Address │ Filled in when zero

(0.0.0.0) 为零

您必须使用:

sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

是的,您现在必须生成额外的 14 个字节作为以太网 header

简单示例请参见:simple DHCP client

关于c - 尽管RAW套接字和HDRINCL,IP源地址仍然由系统填充,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30117835/

相关文章:

C: signal() 是进程级的吗?

c++ - 发送批量消息时 BOOST::async_wait 错误

java - 在 Sun Java System Web 服务器中保留客户端 IP 地址

c++ - ffmpeg命令行的c/c++程序

c - typedef 结构混淆中的指针

java - PrintWriter 只发送部分字符串 Java

c++ - 如何使用 boost asio 读取固定大小的数据包?

windows - 如何使用 'findstr' 匹配 IP 地址?或者在 Windows 中的任何其他批处理方法

java - 套接字-在没有端口转发的情况下连接同一网络上的两台计算机?

在我的 Mac 上使用 OpenMP 编译 C