一、扩展报头粘性选项介绍
根据RFC 3542中的说明,存在两种机制可以用于发送携带HBH扩展报头的IPv6数据包,分别为:
- 使用ancillary data,可以为单个数据包指定扩展报头的选项内容;
- 使用setsockopt函数,可以为一个socket指定扩展报头的选项内容。设置完毕后,可以使该socket发出的所有数据包都携带指定的选项内容,所以也被称为粘性选项("sticky" options)。再次调用setsockopt函数后,可以对扩展报头的选项内容进行更新。
二、设置函数
设置HBH扩展报头的粘性选项需要依次使用4种函数,分别为inet6_opt_init(),inet6_opt_append(),inet6_opt_set_val(),inet6_opt_finish(),且其中3种需要被调用2次。
2.1变量定义
#define OPT_X 0x3c // 定义TLV选项类型,此处自定义为0x3c
void* extbuf; // extension buffer,扩展报头缓存指针
socklen_t extlen; // extension length,扩展报头长度
int currentlen; // 当前长度
void* databuf; // data buffer,扩展报头数据部分缓存指针
int offset; // 偏移
uint32_t value4; // 4字节数据变量
uint64_t value8; // 8字节数据变量
int datalen; // TLV选项数据长度
2.2 第一次调用设置函数
第一次调用函数,用于获得扩展报头长度,为扩展报头分配内存空间,以便第二次调用时存放数据。
2.2.1 inet6_opt_init ()
int inet6_opt_init(void *extension_buffer,
socklen_t extension_length)
该函数返回扩展报头所需的字节数。
currentlen = inet6_opt_init(NULL, 0);
第一次调用该函数,用于开始计算扩展报头大小。
2.2.2 inet6_opt_append()
int inet6_opt_append(void *extension_buffer,
socklen_t extension_length,
int offset,
uint8_t option_type,
socklen_t option_length,
uint32_t alignment,
void **data_bufferp)
该函数返回添加了新TLV选项和对齐后的扩展报头新长度。
currentlen = inet6_opt_append(NULL, 0, currentlen, OPT_X, datalen, 8, NULL);
第一次调用该函数,根据准备设置的TLV选项的类型、数据长度等信息,更新扩展报头长度。OPT_X表示需要设置的TLV选项类型,datalen表示设置该选项的数据部分长度为12字节,8表示该TLV选项是8字节对齐。
2.2.3 inet6_opt_finish()
offset = 0;
value4 = 0x12345678; // 为4字节变量赋值
offset = inet6_opt_set_val(databuf, offset,&value4, sizeof(value4));
value8 = 0x0102030405060708; // 为8字节变量赋值
offset = inet6_opt_set_val(databuf, offset,&value8, sizeof(value8));
该函数返回IPv6扩展报头增加填充字节后的长度,使扩展报头满足字节对齐要求。
currentlen = inet6_opt_finish(NULL, 0, currentlen);
第一次调用该函数,计算增加了填充选项(Pad1、PadN)后的长度,使扩展报头长度满足字节对齐要求。
2.3第二次调用设置函数
首先,应根据第一次调用3种函数后计算得到的扩展报头长度对变量进行更新:
extlen = currentlen; // 更新扩展报头长度
extbuf = malloc(extlen); // 为扩展报头分配内存空间
2.3.1 inet6_opt_init()
currentlen = inet6_opt_init(extbuf, extlen);
第二次调用该函数,基于extlen初始化extbuf。
此时extbuf指向的扩展报头内存空间中,前2字节为HBH头部,第一字节表示下一头部类型,函数暂不设置;第二字节表示扩展报头长度,函数通过extlen设置。
2.3.2 inet6_opt_append()
currentlen = inet6_opt_append(extbuf, extlen, currentlen, OPT_X, datalen, 8, &databuf);
第二次调用该函数,为扩展报头添加TLV选项,并使databuf指向扩展报头内存空间中的数据部分的起始字节处。
此时extbuf指向的扩展报头内存空间中,第三字节被函数填充为TLV选项类型OPT_X,第四字节为TLV选项数据长度,函数通过datalen设置,故databuf实际指向第5字节处。(注意,此处简化分析,实际情况可能并非从第三字节开始填充TLV选项,因为系统会先根据扩展报头长度和对齐要求填充Pad1或PanN选项)。
重复使用该函数,可以为同一个扩展报头添加多个TLV选项。
2.3.3 inet6_opt_set_val()
offset = 0;
value4 = 0x12345678; // 为4字节变量赋值
offset = inet6_opt_set_val(databuf, offset,&value4, sizeof(value4));
value8 = 0x0102030405060708; // 为8字节变量赋值
offset = inet6_opt_set_val(databuf, offset,&value8, sizeof(value8));
两次调用该函数,分别将value4和value8的数据填入databuf指向的扩展报头内存空间的数据部分。
2.3.4 inet6_opt_finish()
currentlen = inet6_opt_finish(extbuf, extlen, currentlen);
第二次调用该函数,系统根据扩展报头长度和对齐要求在扩展报头内存空间中填充Pad1或PanN选项,使满足对齐要求。
2.4 setsockopt()
int setsockopt(int socket_descriptor,
int level,
int option_name,
char *option_value,
int option_length)
该函数用于设置socket。
setsockopt(m_sock, IPPROTO_IPV6,IPV6_HOPOPTS,extbuf, currentlen);
在上述函数调用完毕后,为socket设置HBH扩展头,设置level为IPPROTO_IPV6;option_name为IPV6_HOPOPTS;extbuf指向扩展报头内存空间,使socket能访问HBH扩展头数据。
三、扩展报头内容修改
在依次调用inet6_opt_init(),inet6_opt_append(),inet6_opt_set_val(),inet6_opt_finish()和setsockopt函数后,socket发出的数据包都将带有设置好的HBH扩展报头。
如果需要修改扩展报头内容,而不改变数据长度,再次调用inet6_opt_set_val()和setsockopt即可;如果需要修改数据长度,则需要全部重新设置。在重新设置完成后,socket发出的数据包的HBH扩展报头都会被更新。
参考文献:
RFC 3542 - Advanced Sockets Application Program Interface (API) for IPv6