将char*转换为结构体

3
在这段代码中,有一行:
struct iphdr * iph = (struct iphdr *)buffer;

ProcessPacket函数中,buffer的类型为char*。在主函数中,recvfrom已经给buffer赋值。如何将简单字符串(buffer)转换为结构体,并如何安全地提取数据? iphdr:
struct iphdr {
    #if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
    #elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
    #else
        #error  "Please fix <asm/byteorder.h>"
    #endif
         __u8   tos;
         __u16  tot_len;
         __u16  id;
         __u16  frag_off;
         __u8   ttl;
         __u8   protocol;
         __u16  check;
         __u32  saddr;
         __u32  daddr;
         /*The options start here. */
};

5
buffer中的数据并不是一个字符串,这是需要知道的重要信息。相反,它只是一大块字节,恰好对应于该结构。 - Some programmer dude
2
如果以这种方式写入缓冲区:unit8_t *buffer = (uint8_t *)&yourStruct,那么返回时将正常工作:yourStruct* str = (yourStruct *)buffer。否则,应执行memcpy - ringbuffer_peek
@Some programmer dude,recvfrom编辑一个数据字符串以填充其中的数据包。如果它对应于结构,则打印编辑后的字符串必须包含头数据,而不仅仅是字符串,但实际上并没有发生这种情况。 - Deepesh Choudhary
3
你收到的数据是二进制数据,它不对应任何可打印的字符。它(再次强调)不是C或C ++中的字符串。这就像从文件中读取非文本二进制数据,你是否期望能够将其打印出来?或者将数据用作字符串? - Some programmer dude
3个回答

5

buffer不是一个string。它是指向原始二进制数据的指针。 recvfrom会用原始IP/TCP帧(也称为数据包)填充buffer(在下面的示例中查看)。因此,buffer的前sizeof(iphdr)个字节是IP-header结构体:iphdr。这正是博客作者使用你提供的代码片段的原因。

struct iphdr * iph = (struct iphdr *)buffer;

如果包含IP头选项,则头的实际大小为iph->ihl*4
然后在博客中的ProcessPacket中检查头部的协议字段(iph->protocol),以确定数据包包含的传输协议。
如果使用的传输协议是TCP,则可以使用以下方法提取TCP头(以及后续的数据)(来自博客的片段):
unsigned short iphdrlen = iph->ihl*4;
struct tcphdr *tcph = (struct tcphdr*)(buffer + iphdrlen);

原始帧

博客作者使用以下代码创建了套接字:

sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_TCP);
  • 第一个参数AF_INET表示您想要IPv4数据包(与AF_INET6代表IPv6相反)。
  • 第二个参数告诉socket您想要原始帧。
  • 第三个参数(IPPROTO_TCP)确保您获取TCP帧。

或者,如果您想要UDP帧,则可以使用:

 sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_UDP);

如果你非常贪心,想使用每个数据包(在使用之前请确保阅读帧格式!):

socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

出色的解释,但另一个答案更适合我的问题背景。谢谢。 - Deepesh Choudhary

3

我认为代码由于程序中执行的以下两行存在未定义行为:

unsigned char *buffer = (unsigned char *)malloc(65536);
...
struct iphdr *iph = (struct iphdr*)buffer;

buffer是一个指向已预留为unsigned char*类型的内存块的指针,然后将其强制转换为struct iphdr类型的指针;但struct iphdr很可能具有不同于char*的对齐限制,这是未定义行为(例如,请参见此在线c11草案标准):

6.3.2.3指针

(7)可以将指向对象类型的指针转换为指向不同对象类型的指针。如果得到的指针未正确对齐引用类型,则行为未定义。 ...

虽然它可能起作用(这仍然是UB的选项之一),但程序的行为也可能与您意图的方式不同。

我建议将信息复制到正确对齐的struct iphdr对象中:

unsigned char *buffer = (unsigned char *)malloc(65536);
...
struct iphdr iphobj;
memcpy(&iphobj,buffer,sizeof(struct iphdr));
...

那么需要注意对象的生命周期。

请注意,您标记了代码 CC++,而这两种语言有不同的规则(例如,关于对malloc结果进行显式转换的规定,在C ++中是必需的,但在C中并不鼓励)。

但是关于UB,我非常确定该代码在C和C ++中都会引入UB。


这只适用于编译器强制执行严格别名规则吗?我看到很多C代码示例中人们这样做,但我有点困惑为什么要这样做。 - Nubcake

0
首先需要了解的是,无论如何转换数据类型,内存中的位都保持不变。只是你现在在告诉编译器,buffer 应该被视为指向 struct iphdr 的指针,而不是先前的类型。这只是让编译器用不同的方式查看位,并相应地进行解释。编译器突然发现 buffer 变成了 struct iphdr *,于是说:“好的,没问题。” 重要的是要确切地知道 buffer 是什么,并将其转换为正确的类型。
如果你想的话,你也可以将 buffer 强制转换成 int *(或任何其他指针类型),编译器不会有任何反应。虽然后面可能会遇到问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接