为什么在sockaddr_in中需要零填充?

我google了一些人说:“保持与struct sockaddr相同的大小”。 但是Kernel不会直接使用sockaddr(对吗?)。 当使用它。 内核将把它转换回来。 那么为什么零填充需要?

struct sockaddr { unsigned short sa_family; // address family, AF_xxx char sa_data[14]; // 14 bytes of protocol address }; struct sockaddr_in { short sin_family; // eg AF_INET, AF_INET6 unsigned short sin_port; // eg htons(3490) struct in_addr sin_addr; // see struct in_addr, below char sin_zero[8]; // zero this if you want to }; struct in_addr { unsigned long s_addr; // load with inet_pton() }; 

我能找到的两个更相关的信息是

  • 将sin_zero设置为0

谈论清除字节的代码片段

这是一个错误。 我发现它偶尔会发生。 此错误可能会导致应用程序中的未定义行为。

接下来是一些解释

大多数网络代码不使用sockaddr_in,它使用sockaddr。 当你使用像sendto这样的函数时,你必须显式地将sockaddr_in或者你正在使用的任何地址转换成sockaddr。 sockaddr_in与sockaddr的大小相同,但内部大小是相同的,因为轻微的黑客攻击。

那个黑客是sin_zero。 真的,sockaddr_in中的有用数据的长度比sockaddr短。 但差异是使用一个小缓冲区填充在sockaddr_in中; 该缓冲区是sin_zero。

最后是可以在各个地方找到的信息

在某些体系结构上,它不会导致任何不清除sin_zero的问题。 但在其他架构上可能。 它的规范要求清除sin_zero,所以你必须这样做,如果你打算你的代码是现在和将来的bug。

  • 使用sin_zero

回答这个问题

为什么我们需要这8个字节的填充?

和答案

Unix网络编程第3.2章说:“POSIX规范在结构中只需要三个成员:sin_family,sin_addr和sin_port。符合POSIX标准的实现定义额外的结构成员是可以接受的,这对于Internet套接字是正常的地址结构几乎所有的实现都添加了sin_zero成员,这样所有的套接字地址结构的大小至少为16个字节。

这有点像结构填充,也许保留在未来的额外领域。 就像评论一样,你永远不会使用它。

这与第一个环节是一致的。 清除字节告诉接收方“这些字节不在我们这边使用”。

由于struct sockaddr_in需要被转换为struct sockaddr,所以它必须保持相同的大小,sin_zero是一个未使用的成员,其唯一目的是将结构填充到16字节(这是sock_addr的大小)。 此填充大小可能会因地址系列而异。 例如;

 struct sockaddr_in { short int sin_family; // Address family, AF_INET unsigned short int sin_port; // Port number struct in_addr sin_addr; // Internet address unsigned char sin_zero[8]; // For padding, to make it same size as struct sockaddr }; 

现在采取具有不同结构成员的施乐NS系列:

 struct sockaddr_ns { u_short sns_family; // Address family, AF_NS struct ns_addr sns_addr; // the 12-byte XNS address char sns_zero[2]; // unused except for padding }; 

发生结构填充是因为结构的成员必须出现在字节的边界上才能实现此目的,编译器将填充字节(如果位字段正在使用时放入),以使结构成员出现在正确的位置。 此外,结构的大小必须是这样的:在结构的阵列中,所有结构都在存储器中正确对齐。

所以,可能需要忽略内存泄漏。

struct sockaddr是这个结构的抽象的,不完整的版本,只有这个家族。 struct sockaddr_in是这个结构的IPv4版本。 它只使用前8个字节。 struct sockaddr_in6是这个结构的IPv6版本,并且更大。 填充允许更小的结构来适应这种结构的最大变化,所以缓冲区不会过小。

当你将一个地址传递一个函数或系统调用时,额外的字节并不是必须的。 但是检索一个地址,你提供了结果的结构地址。 这个结构需要是所有可能变化中最大的。 如果没有 – 想象你提供了一个IPv4版本,但是得到了一个IPv6地址 – 那么结果将会超出结构,并破坏内存中的任何东西。

为了避免这种内存损坏,大部分相关函数都将结构大小作为参数。 但是现在,当你通过这个IPv4版本和它的太小的尺寸时,你会得到一个不完整的IPv6版本的结构。 纵观家庭,你可以看到它是IPv6。 但是,如果将结构转换为IPv6并尝试使用,则结构太小而无法包含完整的有效数据,因此内容是错误的。

填充较小的结构避免了这些障碍,并避免了任何相关的潜在安全问题。