【问题标题】:UDP checksum calculation not working with newer version of gccUDP 校验和计算不适用于较新版本的 gcc
【发布时间】:2023-01-23 23:18:58
【问题描述】:

下面包含的代码是一个函数的精简实现,它为给定的固定大小的有效负载生成 UDP 数据包并发送它。

切换到较新版本的 gcc 后,此代码突然显示错误:UDP 校验和计算不正确,这可以追溯到该行

pseudoHeader->protocol = IPPROTO_UDP;

如果至少使用-O2优化,编译器似乎不会生成指令。

以下解决方法解决了该问题(每个建议都独立工作,即您不必一次应用所有建议!):

  • 将两次调用之前提到的行移至inet_pton
  • 在校验和计算后删除对memset(ipHeader, 0, sizeof(struct ip))的调用
  • 使ip_checksum()成为该翻译单元之外的外部函数

代码大量使用强制转换以及仅针对 -O2 或更高版本出现的错误这一事实以及解决方法的性质实际上要求这是代码中的别名错误。是否存在实际错误,如果有,如何解决?

#include <string.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netpacket/packet.h>

#define UDP_PORT 2345
#define REPLY_PAYLOAD_SIZE 360

typedef struct UDPPseudoHeader
{
    unsigned long int source_ip;
    unsigned long int dest_ip;
    unsigned char reserved;
    unsigned char protocol;
    unsigned short int udp_length;
} UDPPseudoHeader;

void sendPacket(unsigned char* packet, int len);

static unsigned short ip_checksum(unsigned short *ptr, int len)
{
    int sum = 0;
    unsigned short answer = 0;
    unsigned short *w = ptr;
    int nleft = len;

    while(nleft > 1) {
        sum += *w++;
        nleft -= 2;
    }

    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    answer = ~sum;
    return(answer);
}

void sendBroadcastPacket(uint16_t destPort, char* packet) {
    unsigned char buffer[REPLY_PAYLOAD_SIZE + sizeof(struct ip) + sizeof(struct udphdr)];
    int bufferLen = REPLY_PAYLOAD_SIZE + sizeof(struct ip) + sizeof(struct udphdr);

    /* initialize header pointers */
    struct udphdr* udpHeader = (struct udphdr*)(buffer + sizeof(struct ip));
    UDPPseudoHeader* pseudoHeader = (UDPPseudoHeader*)(buffer + sizeof(struct ip) - sizeof(UDPPseudoHeader));
    struct ip* ipHeader = (struct ip*)(buffer);

    memset(buffer, 0, bufferLen);

    /* copy user data */
    memcpy(buffer + sizeof(struct ip) + sizeof(struct udphdr), packet, REPLY_PAYLOAD_SIZE);

    /* fill in UDP header */
    udpHeader->source = htons(UDP_PORT);
    udpHeader->dest = htons(destPort);
    udpHeader->len = htons(sizeof(struct udphdr) + REPLY_PAYLOAD_SIZE);
    udpHeader->check = 0;

    /* create UDP pseudo header for checksum calculation */
    inet_pton(AF_INET, "0.0.0.0", &pseudoHeader->source_ip);
    inet_pton(AF_INET, "255.255.255.255", &pseudoHeader->dest_ip);
    pseudoHeader->reserved = 0;
    pseudoHeader->protocol = IPPROTO_UDP;
    pseudoHeader->udp_length = htons(sizeof(struct udphdr) + REPLY_PAYLOAD_SIZE);

    /* calculate UDP checksum */
    udpHeader->check = ip_checksum((unsigned short*) pseudoHeader, bufferLen - sizeof(struct ip) + sizeof(UDPPseudoHeader));

    /* fill in IP header */
    memset(ipHeader, 0, sizeof(struct ip));
    ipHeader->ip_v = 4;
    ipHeader->ip_hl = 5;
    ipHeader->ip_tos = IPTOS_LOWDELAY;
    ipHeader->ip_len = htons(bufferLen);
    ipHeader->ip_off = htons(IP_DF);
    ipHeader->ip_id = 0;
    ipHeader->ip_ttl = 16;
    ipHeader->ip_p = IPPROTO_UDP;
    inet_pton(AF_INET, "0.0.0.0", &ipHeader->ip_src);
    inet_pton(AF_INET, "255.255.255.255", &ipHeader->ip_dst);
    ipHeader->ip_sum = 0;

    /* calculate IP checksum */
    ipHeader->ip_sum = ip_checksum((unsigned short*) ipHeader, ipHeader->ip_hl * 4);

    sendPacket(buffer, bufferLen);
}

【问题讨论】:

    标签: c strict-aliasing


    【解决方案1】:

    该代码确实违反了strict aliasing rule。编译器假定对ip_checksum() 的调用不依赖于对结构成员reservedprotocol 的赋值,因为这些修改chars 和ip_checksum() 是在unsigned shorts 的数组上计算的。因此,由于以下对memset() 的调用无论如何都会覆盖内存,因此分配已完全优化。

    一种可能的解决方案是将伪标头声明为

    typedef union {
        struct {
            unsigned long int source_ip;
            unsigned long int dest_ip;
            unsigned char reserved;
            unsigned char protocol;
            unsigned short int udp_length;
        } hdr;
        unsigned short as_short[6];
    } UDPPseudoHeader;
    

    并将伪标头的生成和校验和计算替换为

    /* create UDP pseudo header for checksum calculation */
    inet_pton(AF_INET, "0.0.0.0", &pseudoHeader->hdr.source_ip);
    inet_pton(AF_INET, "255.255.255.255", &pseudoHeader->hdr.dest_ip);
    pseudoHeader->hdr.reserved = 0;
    pseudoHeader->hdr.protocol = IPPROTO_UDP;
    pseudoHeader->hdr.udp_length = htons(sizeof(struct udphdr) + REPLY_PAYLOAD_SIZE);
    
    /* calculate UDP checksum */
    udpHeader->check = ip_checksum(pseudoHeader->as_short, bufferLen - sizeof(struct ip) + sizeof(UDPPseudoHeader));
    

    【讨论】:

      猜你喜欢
      • 2010-12-01
      • 2014-08-16
      • 2010-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多