我正在尝试在C中为SCTP协议编写数据包解码器,并在解除引用指向结构联合(表示SCTP块)的指针时遇到一些问题。
不是每个人都熟悉SCTP(流控制传输协议),所以这里有一些简短的引物:
Stream Control Transmission Protocol
SCTP packet structure
RFC 4960
在坚果壳中,SCTP Common标题之后是一系列一个或多个“块”。大多数情况下,每个SCTP数据包只有一个块类型,但您可以使用块捆绑,其中某些块类型可以捆绑在一起形成一个数据包。由于这种捆绑,我不能只在我的SCTP头结构中定义一个联合并将其称为一天。
所以这就是我对struct sctp_header
所拥有的:
struct sctp_header {
uint16_t srcport;
uint16_t dstport;
uint32_t vtag;
uint32_t chksum;
uint8_t ctype;
uint8_t cflags;
uint16_t clength;
void *chunks;
};
对于union sctp_chunks
(此问题仅截断为两个块类型):
struct sctp_data {
uint32_t tsn;
uint16_t stream_id;
uint16_t stream_seq;
uint32_t pp_id;
void *data;
};
struct sctp_init {
uint32_t initate_tag;
uint32_t a_rwnd;
uint16_t num_out_streams;
uint16_t num_in_streams;
uint32_t tsn;
void *params;
};
union sctp_chunks {
struct sctp_data *data;
struct sctp_init *init;
};
现在,我将sctp_chunks
覆盖到sctp_header->chunks
上(一旦我完成了所有其他必要的检查以确保我坐在SCTP数据包上)。然后我在switch语句中阅读了sctp_header->ctype
,基于此,我知道我是否可以访问sctp_header->chunks->data->tsn
或sctp_header->chunks->init->initate_tag
(在转换为(sctp_chunks *)
之后),依此类推其他块类型。稍后,我将进行数学运算并检查剩余的块,并将sctp_chunks
联合重新覆盖到剩余数据上,直到我处理完所有块。现在,我只是在第一个块上运行。
问题是,尝试访问data->tsn
或init->initiate_tag
(或联盟的任何其他成员)会导致SIGSEGV。我没有GDB方便(我只是机器上的用户正在编码),所以我很难弄清楚为什么我的程序是segfaulting。我相信我对结构/联合的使用是合理的,但因此是C的本质,它可能是一个非常微妙的东西,在这里挂钩我。
打印chunks->data
或chunks->init
的指针地址显示我看起来不是空指针的地址,并且我没有收到gcc中的任何重大错误或警告,所以我有点难住了。
有什么不同寻常的,或者有更好的方法来解决这个问题吗?
答案 0 :(得分:3)
当你说你在数据包上“覆盖”你的数据结构时(至少就是你所说的那样),你void*
指针数据的四个或八个字节中出现的值-member(取决于您的平台)很可能是sctp_init
或scpt_data
块内的值,而不是指向该数据块的实际指针。这很可能是为什么你的指针不是NULL,而是在解引用指针中的值时你的seg-faulting。
其次,在没有编译指示和/或编译器指令的情况下将结构覆盖在序列化数据上可能是危险的...有时你认为编译器可能分配/填充结构的方式不是它实际上最终如何做它,然后结构与数据包的实际格式不匹配,你最终会得到无效的值。
所以我现在假设你正试图通过用scpt_header
结构直接覆盖数据包的前N个字节来做这样的事情:
|scpt_header .........|void*|
|packet information ..|scpt_chunk.................|
这不是非常便携,并且可能会引起很多麻烦,特别是当您将网络字节顺序问题插入到这种情况中时。您真正想要的是将SCPT数据包的内容复制到内部数据结构中,如链接列表,然后您可以正确操作。
一种方式可能如下所示:
#include <arpa/inet.h>
unsigned char* packet_buffer = malloc(sizeof(PACKET_SIZE));
//... proceed to copy into the buffer however you are reading your packet
//now fill in your structure
unsigned char* temp = packet_buffer;
struct sctp_header header_data;
header_data.src_port = ntohs(*((uint16_t*)temp));
temp += sizeof(uint16_t);
header_data.dstport = ntohs(*((uint16_t*)temp));
temp += sizeof(uint16_t);
header_data.vtag = ntohl(*((uint32_t*)temp));
temp += sizeof(uint32_t);
//... keep going for all data members
//allocate memory for the first chunk (we'll assume you checked and it's a data chunk)
header_data.chunks = malloc(sizeof(sctp_data));
scpt_data* temp_chunk_ptr = header_data.chunks;
//copy the rest of the packet chunks into your linked-list data-structure
while (temp < (packet_buffer + PACKET_SIZE))
{
temp_chunk_ptr->tsn = ntohl(*((uint32_t*)temp));
temp += sizeof(uint32_t);
//... keep going for the rest of this data chunk
//allocate memory in your linked list for the next data-chunk
temp_chunk_ptr->data = malloc(sizeof(scpt_data));
temp_chunk_ptr = temp_chunk_ptr->data;
}
//free the packet buffer when you're done since you now have copied the data into a linked
//list data-structure in memory
free(packet_buffer);
这种方法可能看起来很麻烦,但不幸的是,如果您要处理字节序和网络字节顺序问题,那么您将无法简单地将数据结构覆盖在数据包数据中。平台可移植的方式,即使您将其声明为打包数据结构。声明打包的数据结构将正确对齐字节,但它不会纠正字节序问题,这就像ntohs
和ntohl
之类的函数一样。
另外,不要让scpt_header
超出范围而不破坏它“拥有”的链接列表,否则最终会导致内存泄漏。
更新:如果您仍想要覆盖路由,请首先确保使用编译器指令打包结构,以便不添加填充。在gcc中,这将是
typdef struct sctp_header {
uint16_t srcport;
uint16_t dstport;
uint32_t vtag;
uint32_t chksum;
} __attribute__((packed)) sctp_header;
typedef struct sctp_data
{
uint8_t ctype;
uint8_t cflags;
uint16_t clength;
uint32_t tsn;
uint16_t stream_id;
uint16_t stream_seq;
uint32_t pp_id;
} __attribute__((packed)) sctp_data;
typedef struct sctp_init
{
uint8_t ctype;
uint8_t cflags;
uint16_t clength;
uint32_t initate_tag;
uint32_t a_rwnd;
uint16_t num_out_streams;
uint16_t num_in_streams;
uint32_t tsn;
} __attribute__((packed)) sctp_init;
但在其他编译器中也会出现其他问题。另请注意,我已经改变了一些结构,以更好地反映内存中SCTP数据包实际表示的结果。因为两种不同数据包类型的大小不同,我们实际上无法在内存中进行联合和覆盖...工具在技术上将是最大块类型成员的大小,并且这会产生问题我们试图创建数组等。我也正在摆脱指针...我看到你正在尝试用它们做什么,但同样,因为你想在数据包数据上覆盖这些数据结构,因为你实际上试图“转移”数据,这又会引起问题。对原始数据结构的修改实际上反映了数据在内存中的布局方式,而没有任何花哨的移位或指针转换。由于每个数据包的类型由unsigned char
表示,我们现在可以执行以下操作:
enum chunk_type { DATA = 0, INIT = 1 };
unsigned char* packet_buffer = malloc(sizeof(PACKET_SIZE));
//... copy the packet into your buffer
unsigned char* temp = packet_buffer;
sctp_header* header_ptr = temp;
temp += sizeof(sctp_header);
//... do something with your header
//now read the rest of the packets
while (temp < (packet_buffer + PACKET_SIZE))
{
switch(*temp)
{
case DATA:
sctp_data* data_ptr = temp;
//... do something with the data
temp += data_ptr->clength;
break;
case INIT:
sctp_init* init_ptr = temp;
// ... do something with your init type
temp += init_ptr->clength;
break;
default:
//do some error correction here
}
}
再次请记住,此方法仅纠正对齐问题...它不能纠正字节顺序,因此在读取多字节数据类型中的任何值时要小心。
答案 1 :(得分:1)
问题是你根本不应该使用指针。 sctp_chunks
应该是:
union sctp_chunks {
struct sctp_data data;
struct sctp_init init;
};
chunks
中的sctp_header
应为union sctp_chunks chunks[1]
(如果您知道数据有效,则可以安全地将块编入索引超过1。
我刚看到Jason的答案,他对结构包装是正确的。如果您正在使用gcc,请定义如下结构:
struct sctp_header __attribute__ ((packed)) {
...
};
你必须为每个结构做到这一点。