IPv4允許在20字節(jié)的首部固定部分后跟最多共40字節(jié)的選項(xiàng)。盡管已經(jīng)定義了10種IPv4選項(xiàng),但最常用的是源路徑選項(xiàng)。我們可通過存取IP_OPTIONS套接字選項(xiàng)訪問這些選項(xiàng),我們存取該套接字選項(xiàng)時(shí),所用的緩沖區(qū)中的值就是它們置于IP數(shù)據(jù)報(bào)中的格式。
IPv6允許在固定長(zhǎng)度40字節(jié)的IPv6首部之后,傳輸層首部(如ICMPv6、TCP、UDP)之前出現(xiàn)擴(kuò)展首部,目前定義了6種擴(kuò)展首部。與IPv4不同的是,IPv6擴(kuò)展首部的訪問途徑是函數(shù)接口,而非強(qiáng)求用戶理解這些首部如何呈現(xiàn)在IPv6分組中的真實(shí)細(xì)節(jié)。
IPv4選項(xiàng)跟在20字節(jié)IPv4首部固定部分之后,且IPv4首部中的首部長(zhǎng)度字段長(zhǎng)4位,首部長(zhǎng)度字段的單位是32位字,因此IP首部的總長(zhǎng)度為15個(gè)32位字(60字節(jié)),因此IPv4選項(xiàng)字段最長(zhǎng)40字節(jié)。IPv4定義了10種不同的選項(xiàng):
1.NOP:no-operation。單字節(jié)選項(xiàng),用途是為某個(gè)后續(xù)選項(xiàng)落在4字節(jié)邊界上提供填充。
2.EOL:end-of-list。單字節(jié)選項(xiàng),終止選項(xiàng)列表。既然各個(gè)IP選項(xiàng)的總長(zhǎng)度必須是4字節(jié)的倍數(shù),因此最后一個(gè)有效選項(xiàng)后可能跟以0~3個(gè)EOL字節(jié)。
3.LSRR:loose source and record route。
4.SSRR:strict source and record route。
5.Timestamp。
6.Record route。
7.Basic security(已作廢)。
8.Extended security(已作廢)。
9.Stream identifier(已作廢)。
10.Router alert。它是在RFC 2113中敘述的一種選項(xiàng),所有轉(zhuǎn)發(fā)數(shù)據(jù)報(bào)的路由器都應(yīng)查看該選項(xiàng)。
RFC 1108給出了以上兩種安全選項(xiàng)(7和8)的細(xì)節(jié),但它們沒有得到廣泛使用。
讀取和設(shè)置IP選項(xiàng)字段使用getsockopt和setsockopt函數(shù),level參數(shù)為IPPROTO_IP,optname參數(shù)為IP_OPTIONS。這兩個(gè)函數(shù)的第4個(gè)參數(shù)是指向某個(gè)緩沖區(qū)(其大小小于等于44字節(jié))的一個(gè)指針,第5個(gè)參數(shù)是該緩沖區(qū)的大小。該緩沖區(qū)大小可以比選項(xiàng)字段的最大長(zhǎng)度多出4字節(jié)是由于源路徑選項(xiàng)的處理方式,稍后介紹。除了兩種源路徑選項(xiàng)外,其他選項(xiàng)在該緩沖區(qū)中的格式就是把它們置于IP數(shù)據(jù)報(bào)中的格式。
使用setsockopt函數(shù)設(shè)置IP選項(xiàng)后,相應(yīng)套接字上發(fā)送的所有IP數(shù)據(jù)報(bào)都包含這些選項(xiàng)??梢栽赥CP、UDP、原始IP套接字上設(shè)置IP選項(xiàng)。清除這些選項(xiàng)同樣使用setsockopt函數(shù),既可把第4個(gè)參數(shù)設(shè)為空指針,也可把第5個(gè)參數(shù)設(shè)為0。
對(duì)于已經(jīng)設(shè)置了IP_HDRINCL套接字選項(xiàng)(作用為,使用原始套接字時(shí)用于自定義IP頭部的選項(xiàng))的一個(gè)原始IP套接字,并非所有實(shí)現(xiàn)都支持再為它設(shè)置IP選項(xiàng)。許多源自Berkeley的實(shí)現(xiàn)在IP_HDRINCL選項(xiàng)開啟時(shí)不使用IP_OPTIONS設(shè)置的IP選項(xiàng),因?yàn)閼?yīng)用可能在它構(gòu)造的IP首部中設(shè)置了它自己的IP選項(xiàng)(即要發(fā)送的原始套接字?jǐn)?shù)據(jù)報(bào)中可能已經(jīng)由應(yīng)用處理過IP頭部了,此時(shí)忽略通過IP_OPTIONS設(shè)置的IP選項(xiàng)),其他系統(tǒng)(如FreeBSD)允許應(yīng)用進(jìn)程或使用IP_OPTIONS套接字選項(xiàng)設(shè)置IP選項(xiàng),或者通過開啟IP_HDRINCL并在自己構(gòu)造的IP首部中包括IP選項(xiàng)達(dá)到設(shè)置目的,但不能混用兩種方式。
當(dāng)調(diào)用getsockopt獲取由accept函數(shù)創(chuàng)建的某個(gè)已連接TCP套接字的IP選項(xiàng)時(shí),返回的是在相應(yīng)監(jiān)聽套接字上收到的客戶SYN分節(jié)所在IP數(shù)據(jù)報(bào)中可能出現(xiàn)的源路徑選項(xiàng)的逆轉(zhuǎn),源路徑被TCP自動(dòng)逆轉(zhuǎn)順序,因?yàn)橛煽蛻糁付ǖ氖菑目蛻舻椒?wù)器的源路徑,服務(wù)器需要在發(fā)送到客戶的數(shù)據(jù)報(bào)中使用該路徑的逆轉(zhuǎn),如果沒有源路徑伴隨SYN分節(jié),那么由getsockopt函數(shù)返回的第5個(gè)值-結(jié)果參數(shù)長(zhǎng)度為0。對(duì)于所有其他TCP套接字、所有UDP套接字、原始IP套接字,調(diào)用getsockopt獲取IP選項(xiàng)返回的是以前對(duì)于同一個(gè)套接字調(diào)用setsockopt設(shè)置的IP選項(xiàng)的一個(gè)副本。對(duì)于一個(gè)原始IP套接字,輸入函數(shù)(讀函數(shù))總是返回包括IP選項(xiàng)在內(nèi)的接收到的IP首部,因此到達(dá)的IP選項(xiàng)總是可得的。
源自Berkeley的內(nèi)核從不為UDP套接字返回所收取的源路徑選項(xiàng)或其他任何IP選項(xiàng),TCPv2中所示的返回IP選項(xiàng)的代碼從BSD 4.3 Reno以來一直存在,但它一直被注釋掉,因?yàn)檫@段代碼有問題,是無效的,這使得UDP接收進(jìn)程不可能在發(fā)送響應(yīng)IP數(shù)據(jù)報(bào)時(shí)使用接收路徑的逆轉(zhuǎn)。
許多源自Berkeley的內(nèi)核在為原始IP套接字調(diào)用getsockopt或setsockopt時(shí)發(fā)生系統(tǒng)停機(jī),普通用戶無法使用該手段攻擊系統(tǒng),因?yàn)閯?chuàng)建原始IP套接字要求具備超級(jí)用戶權(quán)限,而超級(jí)用戶權(quán)限擁有者本就可以對(duì)系統(tǒng)進(jìn)行更為惡意的活動(dòng)。
源路徑是由IP數(shù)據(jù)報(bào)的發(fā)送者指定的一個(gè)IP地址列表,如果源路徑是嚴(yán)格的,那么數(shù)據(jù)報(bào)只能逐一經(jīng)過所列的節(jié)點(diǎn),即列在源路徑中的節(jié)點(diǎn)必須前后互為鄰居,如果源路徑是寬松的,那么數(shù)據(jù)報(bào)需要逐一經(jīng)過所列節(jié)點(diǎn),但兩個(gè)節(jié)點(diǎn)之間可經(jīng)過其他節(jié)點(diǎn)。
IPv4的源路由是有爭(zhēng)議的,盡管它可能對(duì)網(wǎng)絡(luò)排障非常有用,但也可能用于源地址欺騙等攻擊中。[Cheswick, Bellovin, and Rubin 2003]
倡議在所有路由器上禁用此特性,許多組織機(jī)構(gòu)和服務(wù)提供商也這么做了。源路由的合理用途之一是使用traceroute程序檢測(cè)非對(duì)稱的路徑,但隨著因特網(wǎng)上越來越多的路由器禁用源路由,這個(gè)用途也將消失。不論如何指定和收取源路徑是套接字API的一部分。
IPv4源路徑稱為源和記錄路徑(source and record routes,SRR,其中LSRR表示寬松的選項(xiàng),SSRR表示嚴(yán)格的選項(xiàng)),因?yàn)殡S著數(shù)據(jù)報(bào)逐一經(jīng)過所列的節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)都把列在源路徑中的自己的地址替換為外出接口的地址。SRR允許接收者逆轉(zhuǎn)新的列表的順序,得到沿相反方向回到發(fā)送者的路徑。
我們把源路徑指定為一個(gè)IPv4地址數(shù)組,并冠以3個(gè)單字節(jié)字段,下圖就是我們傳遞給setsockopt函數(shù)的緩沖區(qū)的格式:
我們?cè)谠绰窂竭x項(xiàng)之前放置一個(gè)NOP選項(xiàng),使得所有IP地址在各自的4字節(jié)邊界對(duì)齊,這么做并非必須,但這樣做無須占用額外空間(IP選項(xiàng)總是填充成4字節(jié)的倍數(shù)),還對(duì)齊了地址。如果我們沒有在緩沖區(qū)開始處放置一個(gè)NOP,由于setsockopt函數(shù)設(shè)置IP_OPTIONS套接字選項(xiàng)時(shí)指定的緩沖區(qū)長(zhǎng)度必須是4字節(jié)的倍數(shù),因此我們可以在緩沖區(qū)末尾放置一個(gè)EOL(值為0的單個(gè)字節(jié))。
上圖我們展示了源路徑最多有10個(gè)IP地址,但所列的第一個(gè)地址將在相應(yīng)套接字的每個(gè)外出IP數(shù)據(jù)報(bào)即將離開源主機(jī)時(shí)被移出源路徑選項(xiàng)(通過改變ptr字段來移出),并成為外出IP數(shù)據(jù)報(bào)的目的地址。40字節(jié)的IP選項(xiàng)空間只能存放9個(gè)IP地址(還有3字節(jié)的選項(xiàng)首部所占空間)。
code字段對(duì)于LSRR為0x83,對(duì)于SSRR為0x89。len字段指定選項(xiàng)的字節(jié)長(zhǎng)度,包括3字節(jié)選項(xiàng)首部和處于末尾的額外的目的IP地址(該地址不屬于源路徑),由1個(gè)IP地址構(gòu)成的源路徑len為11,由2個(gè)IP地址構(gòu)成的源路徑len為15,以此類推,直到由9個(gè)IP地址構(gòu)成的源路徑len為最大值43。上圖中的NOP不屬于SSR選項(xiàng),它自成一個(gè)單字節(jié)IP選項(xiàng),因此不包括在len字段的涵蓋范圍內(nèi),但包含在給setsockopt函數(shù)指定的緩沖區(qū)大小中。當(dāng)源路徑地址列表中第一個(gè)地址被移走(通過改變ptr字段來移走)并置于IP首部的目的地址字段時(shí),這個(gè)len字段值減去4。ptr字段是一個(gè)指針,即路徑中下一個(gè)待處理IP地址的偏移量,初始值為4,表示指向第一個(gè)IP地址,該字段值隨IP數(shù)據(jù)報(bào)被每個(gè)所列節(jié)點(diǎn)處理而逐次加上4。
我們現(xiàn)在開發(fā)3個(gè)函數(shù),分別初始化、創(chuàng)建、處理一個(gè)源路徑選項(xiàng),這些函數(shù)只處理源路徑IP選項(xiàng),盡管源路徑結(jié)合其他IP選項(xiàng)(如路由器警告選項(xiàng))也是可能的,以下是這3個(gè)函數(shù)其中兩個(gè)的實(shí)現(xiàn):
#include "unp.h"
#include <netinet/in_systm.h>
#include <netinet/ip.h>
// 用于構(gòu)造內(nèi)容的一些靜態(tài)變量
static u_char *optr; /* pointer into options being formed */
static u_char *lenptr; /* pointer to length byte in SRR option */
static int ocnt; /* count of # addresses */
// 函數(shù)inet_srcrt_init為構(gòu)建一個(gè)源路徑進(jìn)行初始化
u_char *inet_srcrt_init(int type) {
// 分配一個(gè)長(zhǎng)44字節(jié)的緩沖區(qū)
optr = Malloc(44); /* NOP, code, len, ptr, up to 10 addresses */
// EOL選項(xiàng)的值為0,清零操作把整個(gè)選項(xiàng)緩沖區(qū)初始化為EOL字節(jié)
bzero(optr, 44); /* guarantees EOLS at end */
ocnt = 0;
*optr++ = IPOPT_NOP; /* NOP for alignment */
*optr++ = type ? IPOPT_SSRR : IPOPT_LSRR;
// 保存指向len字段的指針,以后每往地址列表中加入一個(gè)地址,就在該字段中存入新值
lenptr = optr++; /* we fill in length later */
*optr++ = 4; /* offset to first address */
// 把指向選項(xiàng)緩沖區(qū)的指針返回給調(diào)用者,以便作為第4個(gè)參數(shù)傳遞給setsockopt函數(shù)
return optr - 4; /* pointer for setsockopt() */
}
// 向源路徑中加入一個(gè)IPv4地址,參數(shù)指向一個(gè)主機(jī)名或點(diǎn)分十進(jìn)制數(shù)串IP地址
int inet_srcrt_add(char *hostptr) {
int len;
struct addrinfo *ai;
struct sockaddr_in *sin;
if (ocnt > 9) {
err_quit("too many source routes with: %s", hostptr);
}
// 調(diào)用自定義的host_serv函數(shù)轉(zhuǎn)換主機(jī)名或點(diǎn)分十進(jìn)制數(shù)串到二進(jìn)制地址
ai = Host_serv(hostptr, NULL, AF_INET, 0);
sin = (struct sockaddr_in *)ai->ai_addr;
// 把二進(jìn)制地址存入地址列表
memcpy(optr, &sin->sin_addr, sizeof(struct in_addr));
freeaddrinfo(ai);
// 更新len字段值
optr += sizeof(struct in_addr);
++ocnt;
len = 3 + (ocnt * sizeof(struct in_addr));
*lenptr = len;
// 返回緩沖區(qū)總長(zhǎng)度,包括NOP,以便調(diào)用者把該長(zhǎng)度作為第5個(gè)參數(shù)傳遞給setsockopt函數(shù)
return len + 1; /* size for setsockopt() */
}
以下是getsockopt函數(shù)返回給應(yīng)用的接收到的源路徑格式,它不同于圖27-1所示的發(fā)送源路徑格式:
返回給應(yīng)用進(jìn)程的地址順序是所收取的源路徑被內(nèi)核逆轉(zhuǎn)后的順序,如果收到的源路徑按順序包含A、B、C、D 4個(gè)地址(A為發(fā)送端發(fā)送到的第一個(gè)路由器的地址,D為到達(dá)目的端的前一個(gè)路由器的地址),該路徑返回給應(yīng)用的順序(即逆轉(zhuǎn)后的順序)是D、C、B、A(實(shí)際上返回給應(yīng)用的地址有5個(gè),還有發(fā)送端的地址作為目的IP地址,但目的IP地址不算在源路徑中)。如上圖,前4個(gè)字節(jié)是該列表的第一個(gè)IP地址(即收到的源路徑中的地址D),后跟一個(gè)單字節(jié)NOP(為了對(duì)齊),再跟以3字節(jié)源路徑選項(xiàng)首部,最后再跟以其余的IP地址,3字節(jié)選項(xiàng)首部后最多可跟以9個(gè)IP地址,所返回首部中l(wèi)en字段的最大值為39。由于存在NOP做填充,因此由getsockopt函數(shù)返回的長(zhǎng)度總是4字節(jié)的倍數(shù)。
上圖格式在netinet/ip_var.h頭文件中定義為如下結(jié)構(gòu):
以上getsockopt函數(shù)返回的源路徑選項(xiàng)的結(jié)構(gòu)不同于我們傳遞給setsockopt函數(shù)的格式,如果要把圖27-4中的格式轉(zhuǎn)換為圖27-1中的格式,我們需要對(duì)換頭4個(gè)字節(jié)和隨后4個(gè)字節(jié),再給len字段加4,但我們并非必須這么做,源自Berkeley的實(shí)現(xiàn)對(duì)于TCP套接字自動(dòng)使用來自SYN所在IP數(shù)據(jù)報(bào)的接收源路徑的逆轉(zhuǎn),即我們不必調(diào)用setsockopt告訴內(nèi)核使用該路徑發(fā)送相應(yīng)TCP連接上的外出IP數(shù)據(jù)報(bào),內(nèi)核會(huì)自動(dòng)這么做,圖27-4展示的由getsockopt函數(shù)返回的源路徑信息純粹用于了解目的。
以下函數(shù)顯示一個(gè)接收到的源路徑:
void inet_srcrt_print(u_char *ptr, int len) {
u_char c;
char str[INET_ADDRSTRLEN];
struct in_addr hop1;
// 保存緩沖區(qū)中的第1個(gè)IP地址
memcpy(&hop1, ptr, sizeof(struct in_addr));
ptr += sizeof(struct in_addr);
// 跳過后續(xù)的NOP
while ((c = *ptr++) == IPOPT_NOP); /* skip any leading NOPs */
if (c == IPOPT_LSRR) {
printf("received LSRR: ");
} else if (c == IPOPT_SSRR) {
printf("received SSRR: ");
} else {
printf("received option type %s\n", c);
return;
}
printf("%s ", Inet_ntop(AF_INET, &hop1, str, sizeof(str)));
// 不顯示末尾的那個(gè)目的IP地址
len = *ptr++ - sizeof(struct in_addr); /* subtract dest IP addr */
++ptr; /* skip over pointer */
while (len > 0) {
printf("%s ", Inet_ntop(AF_INET, ptr, str, sizeof(str)));
ptr += sizeof(struct in_addr);
len -= sizeof(struct in_addr);
}
printf("\n");
}
修改TCP回射客戶程序?yàn)橹付ㄒ粋€(gè)源路徑:
#include "unp.h"
int main(int argc, char **argv) {
int c, sockfd, len = 0;
u_char *ptr = NULL;
struct addrinfo *ai;
if (argc < 2) {
err_quit("usage: tcpcli01 [ -[gG] <hostname> ... ] <hostname>");
}
opterr = 0; /* don't want getopt() writing to stderr */
while ((c = getopt(argc, argv, "gG")) != -1) {
switch (c) {
case 'g': /* loose source route */
if (ptr) {
err_quit("can't use both -g and -G");
}
// 初始化源路徑
ptr = inet_srcrt_init(0);
break;
case 'G': /* strict source route */
if (ptr) {
err_quit("can't use both -g and -G");
}
// 初始化源路徑
ptr = inet_srcrt_init(1);
break;
case '?':
err_quit("unrecognized option: %c", c);
}
}
// 如果初始化成功,ptr不為空
if (ptr) {
// 把命令行指定的每個(gè)中間地址加到源路徑中
// optind變量用于跟蹤下一個(gè)要解析的命令行參數(shù)的索引位置,它是與getopt函數(shù)一起使用的
while (optind < argc - 1) {
len = inet_srcrt_add(argv[optind++]);
}
// 否則如果剩余命令行參數(shù)不止一個(gè),意味著用戶指定了路徑卻沒有指定其類型,顯示錯(cuò)誤消息并退出
} else if (optind < argc - 1) {
err_quit("need -g or -G to specify route");
}
// 最后一個(gè)參數(shù)是服務(wù)器主機(jī)的主機(jī)名或點(diǎn)分十進(jìn)制數(shù)串地址
if (optind != argc - 1) {
err_quit("missing <hostname>");
}
ai = Host_serv(argv[optind], SERV_PORT_STR, AF_INET, SOCK_STREAM);
sockfd = Socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
// 如果用戶指定了源路徑,我們需要把服務(wù)器的IP地址加到地址列表末尾
if (ptr) {
// inet_srcrt_add的參數(shù)可以是主機(jī)名或點(diǎn)分十進(jìn)制數(shù)串地址
len = inet_srcrt_add(argv[optind]); /* dest at end */
// 設(shè)置外出分節(jié)的IP首部選項(xiàng)字段
Setsockopt(sockfd, IPPROTO_IP, IP_OPTIONS, ptr, len);
free(ptr);
}
// connect函數(shù)將發(fā)起三路握手,我們期望SYN分節(jié)所在初始外出分組和后續(xù)外出分組都使用此源路徑
Connect(sockfd, ai->ai_addr, ai->ai_addrlen);
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
修改TCP回射服務(wù)器程序?yàn)轱@示一個(gè)接收源路徑,改動(dòng)后的TCP服務(wù)器程序幾乎等同于第五章中的版本,只有兩處改動(dòng),首先為IP選項(xiàng)分配空間:
int len;
u_char *opts;
opts = Malloc(44);
然后在調(diào)用accept后,調(diào)用fork前獲取并顯示IP選項(xiàng):
len = 44;
Getsockopt(connfd, IPROTO_IP, IP_IPTIONS, opts, &len);
if (len > 0) {
printf("received IP options, len = %d\n", len);
inet_srcrt_print(opts, len);
}
如果所收取的來自客戶的SYN分節(jié)所在IP數(shù)據(jù)報(bào)中不包含IP選項(xiàng),則由getsockopt函數(shù)返回的len變量結(jié)果將是0(len是一個(gè)值-結(jié)果參數(shù))。為了使用所收取源路徑的逆轉(zhuǎn)作為回送數(shù)據(jù)報(bào)的選項(xiàng),我們不必做任何事,這是由內(nèi)核自動(dòng)完成的。我們調(diào)用getsockopt只是為了獲取逆轉(zhuǎn)后的接收源路徑的一個(gè)副本,如果不希望TCP使用此路徑,我們可在accept函數(shù)返回后通過指定第5個(gè)參數(shù)(長(zhǎng)度)為0調(diào)用setsockopt,從而去除當(dāng)前正在用的IP選項(xiàng)。TCP已在三路握手的第二個(gè)分節(jié)所在IP數(shù)據(jù)報(bào)中使用接收源路徑的逆轉(zhuǎn),但我們可以在accept函數(shù)返回后去除這些選項(xiàng),去除后相應(yīng)TCP連接中以后發(fā)送到客戶的分組將由客戶IP地址確定外出路徑。
運(yùn)行以上指定源路徑的回射客戶和服務(wù)器程序,我們?cè)谥鳈C(jī)freebsd4上按以下方式運(yùn)行客戶:
該命令導(dǎo)致IP數(shù)據(jù)報(bào)從freebsd4轉(zhuǎn)發(fā)到macosx,再轉(zhuǎn)發(fā)回freebsd4,最后到達(dá)服務(wù)器主機(jī)macosx。macosx和freebsd4必須進(jìn)行適當(dāng)配置,以接受并轉(zhuǎn)發(fā)源路由的數(shù)據(jù)報(bào),從而使本例工作。
連接建立時(shí)服務(wù)器的輸出如下:
172.24.37.94是freebsd4的IP,172.24.37.78是macosx的IP,可見顯示的第一個(gè)IP地址是逆轉(zhuǎn)路徑的第一跳。
如果上例使用-G代替-g,則不會(huì)有任何變化,因?yàn)樯侠兴邢到y(tǒng)都是鄰居,因此嚴(yán)格的源路徑等同于寬松的源路徑。
不幸的是,IP_OPTIONS套接字選項(xiàng)的操作從未有過正式文檔,因此在不是源自Berkeley源代碼的系統(tǒng)上可能會(huì)有變化,例如,Solaris 2.5上由getsockopt函數(shù)返回的緩沖區(qū)的頭部地址不是反轉(zhuǎn)路徑的第一跳的地址,而是對(duì)端主機(jī)的地址,但TCP使用的逆轉(zhuǎn)路徑仍是正確的。另外,Solaris 2.5總是在源路徑選項(xiàng)前填充4個(gè)NOP,從而限制源路徑最多有8個(gè)地址,而非9個(gè)。
但源路徑對(duì)于單純使用源IP地址進(jìn)行認(rèn)證的服務(wù)器程序來說存在一個(gè)安全漏洞,如果某個(gè)黑客作為客戶發(fā)送的分組以一個(gè)受服務(wù)器信任的地址作為源地址,又把本地地址包括在源路徑中,那么由服務(wù)器使用逆轉(zhuǎn)后的接收源路徑返回的分組將無需經(jīng)過列在源路徑中的所有節(jié)點(diǎn)就到達(dá)黑客的本地主機(jī)。從Net/1版本(一個(gè)早期的Unix操作系統(tǒng)版本)開始,rlogind和rshd這兩個(gè)服務(wù)器程序就有了如下類似代碼:
u_char buf[44];
char lbuf[BUFSIZ];
int optsize;
optsize = sizeof(buf);
// 如果到達(dá)的已完成連接含有IP選項(xiàng)(即getsockopt函數(shù)返回的optsize不為0)
if (getsockopt(0, IPPROTO_IP, IP_OPTIONS, buf, &optsize) == 0 && optsize != 0) {
/* format the options as hex numbers to print in lbuf[] */
// 使用syslog函數(shù)登記一條消息
syslog(LOG_NOTICE, "Connection received using IP options (ignored): %s", lbuf);
// 調(diào)用setsockopt去除所有IP選項(xiàng)
// 防止該連接上以后發(fā)送的TCP分節(jié)使用接收源路徑的逆轉(zhuǎn)(實(shí)際由承載TCP分節(jié)的IP數(shù)據(jù)報(bào)使用)
setsockopt(0, IPPROTO_IP, IP_OPTIONS, NULL, 0);
}
以上代碼中,清除源路徑選項(xiàng)的代碼曾經(jīng)是這樣的:
optsize = 0;
setsockopt(0, IPPROTO_IP, IP_OPTIONS, NULL, &optsize);
區(qū)別在于第5個(gè)參數(shù)是指向長(zhǎng)度的指針,而非長(zhǎng)度本身,這是一個(gè)bug,它可能在開始使用ANSI C原型時(shí)被修復(fù),但這個(gè)bug是無害的,因?yàn)榻笽P_OPTIONS套接字選項(xiàng)既可指定一個(gè)空指針作為第4個(gè)參數(shù),也可使用0值作為第5個(gè)參數(shù)。
以上代碼中,getsockopt和setsockopt函數(shù)的描述符參數(shù)為0,這是由于rlogind是由inetd派生的,而描述符0正是通往客戶的套接字。
以上代碼所用技巧是不充分的,因?yàn)榈綉?yīng)用進(jìn)程接受該連接前,TCP三路握手已經(jīng)完成,而三路握手的第二個(gè)分節(jié)已經(jīng)沿所收取源路徑的逆轉(zhuǎn)回到客戶(或回到了列在源路徑中的某個(gè)黑客所在的中間節(jié)點(diǎn)),既然黑客已經(jīng)看到兩個(gè)方向上的TCP序列號(hào),即使來自服務(wù)器的后續(xù)分組不再使用源路徑發(fā)送,黑客仍能夠以正確的序列號(hào)向服務(wù)器發(fā)送分組。
解決以上問題的唯一方法是:當(dāng)使用源IP地址進(jìn)行某種形式的認(rèn)證時(shí),禁止使用源路徑到達(dá)的所有TCP連接。在以上給出的代碼中,把setsockopt函數(shù)替換為關(guān)閉剛接受的連接并終止新派生的服務(wù)器,這樣盡管三路握手的第二個(gè)分節(jié)已經(jīng)送出,但連接不會(huì)仍然打開著。
IPv6首部后可跟如下幾種可選的擴(kuò)展首部:
1.步跳選項(xiàng)(hop_by_hop options)。如果有此選項(xiàng),它必須緊跟40字節(jié)的IPv6首部。目前沒有定義可供應(yīng)用程序使用的此類選項(xiàng)。
2.目的地選項(xiàng)(destination options)。目前沒有定義可供應(yīng)用程序使用的此類選項(xiàng)。
3.路由首部(routing header)。類似于IPv4源路徑選項(xiàng)。
4.分片首部(fragmentation header)。該首部由對(duì)IPv6數(shù)據(jù)報(bào)執(zhí)行分片的主機(jī)自動(dòng)產(chǎn)生,然后由最終目的主機(jī)在重組片段時(shí)處理。
5.認(rèn)證首部(authentication header,AH)。該首部用法在RFC 2402中說明。
6.安全凈荷封裝(encapsulating security payload,ESP)。該首部用法在RFC 2406中說明。
其中分片首部完全由內(nèi)核處理,AH和ESP這兩個(gè)首部可以由內(nèi)核基于SADB和SPDB自動(dòng)處理,SADB和SPDB使用PF_KEY套接字維護(hù)(第十九章)。RFC 3542定義了指定和獲取以上擴(kuò)展首部的API。
步跳選項(xiàng)和目的地選項(xiàng)有類似的格式,如下圖:
8位的下一個(gè)首部字段標(biāo)識(shí)出跟在本擴(kuò)展首部之后的下一個(gè)擴(kuò)展首部。8位的首部擴(kuò)展長(zhǎng)度字段是本擴(kuò)展首部的長(zhǎng)度,以8字節(jié)為單位,但不包括它自己,例如,如果本擴(kuò)展首部(不包含下一個(gè)首部字段)占8字節(jié),其首部擴(kuò)展長(zhǎng)度字段值就為0,如果本擴(kuò)展首部占16字節(jié),其首部擴(kuò)展長(zhǎng)度字段值就為1。這兩種首部選項(xiàng)(步跳選項(xiàng)和目的地選項(xiàng))都被填充成8字節(jié)的整數(shù)倍,所用填充方式有兩種:pad1和padN。
步跳選項(xiàng)首部和目的地選項(xiàng)首部都容納任意數(shù)量的個(gè)體選項(xiàng),個(gè)體選項(xiàng)的格式如下:
個(gè)體選項(xiàng)的編排格式稱為TVL編碼,因?yàn)槊總€(gè)個(gè)體選項(xiàng)都由類型(type)、長(zhǎng)度(length)、值(value)三個(gè)字段組成。8位的類型字段標(biāo)識(shí)選項(xiàng)的類型,此外,該字段的高2位指定IPv6節(jié)點(diǎn)在不理解本選項(xiàng)時(shí)如何處理它:
1.00
:跳過本個(gè)體選項(xiàng),繼續(xù)處理本首部。
2.01
:丟棄本分組。
3.10
:丟棄本分組,且不論本分組的目的地址是否為一個(gè)多播地址,均發(fā)送一個(gè)ICMPv6錯(cuò)誤給發(fā)送者,此ICMPv6消息的類型為4,代碼為2,下圖是部分ICMPv6消息:
4.11
:丟棄本分組,且只有當(dāng)本分組的目的地址不是多播地址時(shí),發(fā)送一個(gè)ICMPv6錯(cuò)誤給發(fā)送者,ICMPv6消息類型為4,代碼為2。
下一個(gè)高序位(第3高序位)指定本個(gè)體選項(xiàng)的數(shù)據(jù)在途中是否會(huì)有變化:
1.0
:選項(xiàng)數(shù)據(jù)在途中無變化。
2.1
:選項(xiàng)數(shù)據(jù)在途中可能變化。
低序5位指定選項(xiàng)本身,但低序5位不能標(biāo)識(shí)一個(gè)選項(xiàng),而是需要由高序3位共同標(biāo)識(shí),盡管如此,類型字段的賦值仍盡可能保持低序5位的唯一性。
8位的長(zhǎng)度字段指定個(gè)體選項(xiàng)數(shù)據(jù)字段的字節(jié)長(zhǎng)度,類型字段和本長(zhǎng)度字段不計(jì)算在內(nèi)。
pad1和padN兩種填充方式定義在RFC 2460中,在步跳選項(xiàng)首部和目的地選項(xiàng)首部中都可使用。特大凈荷長(zhǎng)度是一個(gè)步跳選項(xiàng),定義在RFC 2675中,它完全由內(nèi)核在需要時(shí)產(chǎn)生,在收到時(shí)處理。路由器告警也是一個(gè)步跳選項(xiàng),它定義在RFC 2711中,類似于IPv4的路由器告警。下圖展示了這些選項(xiàng):
pad1選項(xiàng)是唯一沒有長(zhǎng)度和值字段的選項(xiàng),它提供1字節(jié)的填充。padN選項(xiàng)用于需要2個(gè)或多個(gè)字節(jié)填充的場(chǎng)合,對(duì)于2字節(jié)填充,本選項(xiàng)的長(zhǎng)度字段為0,整個(gè)選項(xiàng)只由類型和長(zhǎng)度這兩個(gè)字段構(gòu)成;對(duì)于3字節(jié)填充,本選項(xiàng)的長(zhǎng)度字段值為1,后跟1字節(jié)的0值。特大凈荷長(zhǎng)度選項(xiàng)提供一個(gè)32位的數(shù)據(jù)報(bào)長(zhǎng)度,用于展示16位凈荷長(zhǎng)度字段不夠大的場(chǎng)合。路由器告警選項(xiàng)指示本分組應(yīng)由沿途路由器截取,其值指出哪些路由器需關(guān)注本分組。
我們展示以上選項(xiàng)的原因在于,每個(gè)步跳選項(xiàng)和目的地選項(xiàng)都有一個(gè)對(duì)齊要求,可用xn+y表示,含義為這個(gè)個(gè)體選項(xiàng)必須出現(xiàn)在距離所在擴(kuò)展首部開始處x字節(jié)整數(shù)倍加y字節(jié)的位置,例如,特大凈荷長(zhǎng)度選項(xiàng)的對(duì)齊要求是4n+2,該要求迫使4字節(jié)的選項(xiàng)值處于某個(gè)4字節(jié)邊界,y值取2是因?yàn)樵谶x項(xiàng)值字段之前有各1字節(jié)的類型字段和長(zhǎng)度字段。路由器告警選項(xiàng)的對(duì)齊要求是2n+0,該要求迫使2字節(jié)的選項(xiàng)值處于某2字節(jié)邊界。
步跳選項(xiàng)和目的地選項(xiàng)通常作為輔助數(shù)據(jù)通過sendmsg函數(shù)指定,并由recvmsg函數(shù)作為輔助數(shù)據(jù)返回。進(jìn)程無需為發(fā)送這兩類選項(xiàng)做任何特別之事,只需在某個(gè)sendmsg調(diào)用中指定它們。為了接收這兩類選項(xiàng),應(yīng)用必須開啟對(duì)應(yīng)的套接字選項(xiàng),步跳選項(xiàng)對(duì)應(yīng)IPV6_RECVHOPOPTS,目的地選項(xiàng)對(duì)應(yīng)IPV6_RECVDSTOPTS。允許這兩類選項(xiàng)都返回的代碼如下:
const int on = 1;
setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVHOPOPTS, &on, sizeof(on));
setsockopt(sockfd, IPOROTO_IPV6, IPV6_RECVDSTOPTS, &on, sizeof(on));
下圖展示了用于發(fā)送和接收步跳選項(xiàng)和目的地選項(xiàng)的輔助數(shù)據(jù)對(duì)象的格式:
這兩類選項(xiàng)首部的實(shí)際內(nèi)容作為輔助數(shù)據(jù)對(duì)象的cmsg_data部分在進(jìn)程和內(nèi)核之間傳遞,為了避免直接定義上圖內(nèi)容,相關(guān)API定義了7個(gè)用于創(chuàng)建和處理這些輔助數(shù)據(jù)對(duì)象數(shù)據(jù)部分的函數(shù)。以下4個(gè)函數(shù)用于構(gòu)造待發(fā)送的選項(xiàng):
inet6_opt_init函數(shù)返回容納一個(gè)空擴(kuò)展首部(即沒有任何選項(xiàng)的首部)所需的字節(jié)數(shù),如果extbuf參數(shù)非空,會(huì)在其指向的緩沖區(qū)中初始化這個(gè)擴(kuò)展首部,此時(shí),如果extlen參數(shù)不是8的倍數(shù)(所有IPv6步跳和目的地選項(xiàng)擴(kuò)展首部必須是8的倍數(shù)),inet6_opt_init函數(shù)會(huì)返回-1。
inet6_opt_append函數(shù)返回添加指定的個(gè)體選項(xiàng)后的擴(kuò)展首部總長(zhǎng)度,如果extbuf參數(shù)非空,本函數(shù)會(huì)初始化該個(gè)體選項(xiàng)并按對(duì)齊要求插入必要的填充,如果所提供的的緩沖區(qū)放不下新選項(xiàng),它就失敗返回-1。offset參數(shù)是當(dāng)前extbuf參數(shù)指向的緩沖區(qū)中擴(kuò)展首部的總長(zhǎng)度,該值必須是先前某個(gè)inet6_opt_init或inet6_opt_append調(diào)用的返回值。type和len參數(shù)分別指定了選項(xiàng)的類型和長(zhǎng)度,并被直接復(fù)制到選項(xiàng)首部中。align參數(shù)指定對(duì)齊要求,即xn+y中的x值,而y值可由align參數(shù)和len參數(shù)算出,不用顯式指定。databufp參數(shù)用于返回指向所添加選項(xiàng)值的填寫位置的指針,調(diào)用者隨后可使用inet6_opt_set_val函數(shù)或其他方法往這個(gè)位置復(fù)制選項(xiàng)值。
inet6_opt_finish函數(shù)用于結(jié)束一個(gè)擴(kuò)展首部的設(shè)置,添加任何必要的填充,使得總長(zhǎng)度為8字節(jié)的倍數(shù)。如果extbuf參數(shù)非空,就把填充真正插入緩沖區(qū)中,否則只是計(jì)算并返回新的總長(zhǎng)度。offset參數(shù)是當(dāng)前extbuf參數(shù)指向的緩沖區(qū)中擴(kuò)展首部的總長(zhǎng)度,該值必須是先前某個(gè)inet6_opt_init或inet6_opt_append調(diào)用的返回值。本函數(shù)返回已完成設(shè)置的擴(kuò)展首部總長(zhǎng)度,但如果所提供的緩沖區(qū)放不下所需的填充,則返回-1。
inet6_opt_set_val函數(shù)用于把給定的選項(xiàng)值復(fù)制到由inet6_opt_addend函數(shù)返回的數(shù)據(jù)緩沖區(qū)中。databuf參數(shù)是由inet6_opt_addend函數(shù)返回的指針。offset參數(shù)指定選項(xiàng)值要插入到緩沖區(qū)中的位置,該參數(shù)是前一個(gè)inet6_opt_set_val函數(shù)的返回值,首次調(diào)用時(shí),該參數(shù)必須初始化為0。參數(shù)val和valen用于指定復(fù)制到選項(xiàng)值緩沖區(qū)中的值。
以上4個(gè)函數(shù)的期望用法是遍歷兩趟待添加的個(gè)體選項(xiàng)列表,第一趟用于計(jì)算預(yù)期的長(zhǎng)度,第二趟用于把各個(gè)選項(xiàng)實(shí)際構(gòu)造到大小合適的緩沖區(qū)中。兩趟都是先調(diào)用inet6_opt_init,再為每個(gè)待添加的選項(xiàng)調(diào)用一次inet6_opt_append,最后以調(diào)用inet6_opt_finish結(jié)束。第一趟中傳遞給extbuf和extlen參數(shù)的值分別為NULL和0。第一趟結(jié)束后使用由inet6_opt_finish函數(shù)返回的大熊動(dòng)態(tài)分配用于存放選項(xiàng)擴(kuò)展首部的緩沖區(qū),第二趟中就使用指向該緩沖區(qū)的一個(gè)指針及該緩沖區(qū)長(zhǎng)度作為extbuf和extlen參數(shù)的值。第二趟中,每個(gè)選項(xiàng)的值或者手動(dòng)復(fù)制,或者調(diào)用inet6_opt_set_val復(fù)制。我們也可預(yù)先分配一個(gè)足夠大的緩沖區(qū),從而省略掉第一趟,但緩沖區(qū)的大小有時(shí)不易預(yù)估,省略第一趟時(shí)有可能導(dǎo)致第二趟失敗。
以下函數(shù)用于處理所接收的選項(xiàng):
inet6_opt_next函數(shù)處理某緩沖區(qū)中的下一個(gè)選項(xiàng)。extbuf和extlen參數(shù)用于指定存放擴(kuò)展首部的緩沖區(qū)。offset參數(shù)是當(dāng)前extbuf參數(shù)指向的緩沖區(qū)中當(dāng)前處理到的選項(xiàng)偏移,與inet6_opt_append函數(shù)的offset函數(shù)類似,首次調(diào)用本函數(shù)時(shí)應(yīng)將offset參數(shù)指定為0,后續(xù)調(diào)用就使用前一個(gè)調(diào)用的返回值。typep、lenp、databufp參數(shù)分別用于返回當(dāng)前處理到的選項(xiàng)的類型、長(zhǎng)度、值。如果緩沖區(qū)中內(nèi)容不符合選項(xiàng)擴(kuò)展首部格式或已經(jīng)到達(dá)該緩沖區(qū)末尾,函數(shù)就返回-1。
inet6_opt_find函數(shù)類似inet6_opt_next函數(shù),但讓調(diào)用者指定待搜索的選項(xiàng)類型(type參數(shù))。
inet6_opt_get_val函數(shù)從databuf參數(shù)指定的某個(gè)選項(xiàng)中獲取值(有些選項(xiàng)值中有多個(gè)字段)。databuf參數(shù)是由inet6_opt_next或inet6_opt_find函數(shù)返回的databufp參數(shù)指針。offset參數(shù)類似inet_6_opt_next函數(shù),首次調(diào)用時(shí)需要用0作為參數(shù)值,后續(xù)調(diào)用使用前一個(gè)調(diào)用的返回值作為offset參數(shù)。
IPv6路由首部用于IPv6的源路由:
下一個(gè)首部和首部擴(kuò)展長(zhǎng)度字段與圖27-7中的含義相同。路由類型字段當(dāng)前只定義了一種類型,它的此字段值為0。剩余網(wǎng)段字段是所列節(jié)點(diǎn)中還有幾個(gè)需要拜訪(即尚未路由到)。
路由首部中可以出現(xiàn)的地址數(shù)目?jī)H受限于分組允許長(zhǎng)度等外在因素,剩余網(wǎng)段字段的值必須小于等于所列的地址數(shù)目。RFC 2460說明了一個(gè)具有路由首部的分組在發(fā)送到最終目的地的過程中,各個(gè)途徑節(jié)點(diǎn)如何處理該路由首部的具體細(xì)節(jié)。
路由首部通常作為輔助數(shù)據(jù)由sendmsg函數(shù)指定,并由recvmsg函數(shù)作為輔助數(shù)據(jù)返回。應(yīng)用發(fā)送此首部時(shí),需要做的僅僅是在某個(gè)sendmsg調(diào)用中指定它。為了接收路由首部,應(yīng)用進(jìn)程需要開啟IPV6_RECVRTHDR套接字選項(xiàng):
const int on = 1;
setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVRTHDR, &on, sizeof(on));
用于發(fā)送和接收路由首部的輔助數(shù)據(jù)對(duì)象的格式:
相關(guān)API為創(chuàng)建和處理路由首部定義了6個(gè)函數(shù),以下3個(gè)函數(shù)用于構(gòu)造待發(fā)送的路由首部:
inet6_rth_space函數(shù)返回容納一個(gè)類型為type參數(shù)(該參數(shù)通常為IPV6_RTHDR_TYPE_0),網(wǎng)段總數(shù)為segments參數(shù)值的路由首部所需的字節(jié)數(shù)。
inet6_rth_init函數(shù)初始化由rthbuf參數(shù)指向的緩沖區(qū),以容納一個(gè)類型為type參數(shù)值,網(wǎng)段總數(shù)為segments參數(shù)值的路由首部。返回值是指向該緩沖區(qū)的指針,但如果發(fā)生錯(cuò)誤(如所提供的緩沖區(qū)不夠大)則為空指針。非空指針的返回值用作inet6_rth_add函數(shù)的第1個(gè)參數(shù)。
inet6_rth_add函數(shù)把a(bǔ)ddr參數(shù)指向的IPv6地址加到構(gòu)建中的路由首部的末尾,調(diào)用成功時(shí),該路由首部的剩余網(wǎng)段字段會(huì)被更新為新的地址數(shù)目。
以下3個(gè)函數(shù)用于處理所接收的路由首部:
inet6_rth_reverse函數(shù)的in參數(shù)所指緩沖區(qū)中存放的是某個(gè)接收路由首部,本函數(shù)將該路由首部逆轉(zhuǎn),并存放在由out參數(shù)所指的緩沖區(qū)中,以便接收進(jìn)程沿逆轉(zhuǎn)的路徑發(fā)送回?cái)?shù)據(jù)報(bào)。in和out參數(shù)可以指向同一個(gè)緩沖區(qū),即原地逆轉(zhuǎn)。
inet6_rth_segments函數(shù)返回由rhbuf所指路由首部中的網(wǎng)段數(shù)目,調(diào)用成功時(shí)返回值應(yīng)大于0。
inet6_rth_getaddr函數(shù)用于返回由rthbuf參數(shù)所指的路由首部中,索引號(hào)為index的那個(gè)IPv6地址,返回值是指向該地址所在位置的指針。index參數(shù)的值必須在0和inet6_rth_segments函數(shù)的返回值減去1為界限的閉區(qū)間內(nèi)。
為展示IPv6路由首部的用法,我們編寫一對(duì)UDP客戶程序和服務(wù)器程序,客戶程序從命令行接受一個(gè)源路徑,服務(wù)器程序顯示所接收IPv6數(shù)據(jù)報(bào)的接收源路徑,再把該數(shù)據(jù)報(bào)沿接收源路徑的逆轉(zhuǎn)發(fā)送回客戶。以下是客戶程序:
#include "unp.h"
int main(int argc, char **argv) {
int sockfd, len = 0;
u_char *ptr = NULL;
struct addrinfo *ai;
if (argc < 2) {
// 如果提供的主機(jī)名參數(shù)不止一個(gè),那么源路徑由除最后一個(gè)之外的所有參數(shù)構(gòu)成
err_quit("usage: udpcli01 [ <hostname> ... ] <hostname>");
}
if (argc > 2) {
int i;
// 首先調(diào)用inet6_rth_space確定創(chuàng)建路由首部需要多大空間
len = Inet6_rth_space(IPV6_RTHDR_TYPE_0, argc - 2);
// 分配所需空間
ptr = Malloc(len);
// 初始化所分配的緩沖區(qū)
Inet6_rth_init(ptr, len, IPV6_RTHDR_TYPE_0, argc - 2);
for (i = 1; i < argc - 1; ++i) {
// 對(duì)源路徑中每個(gè)地址先調(diào)用host_serv把它轉(zhuǎn)換成in6_addr結(jié)構(gòu)
ai = Host_serv(argv[i], NULL, AF_INET6, 0);
// 把該源路徑添加到構(gòu)建中的源路徑中
Inet6_rth_add(ptr, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr);
}
}
// 獲取目的主機(jī)名的套接字地址結(jié)構(gòu)
ai = Host_serv(argv[argc - 1], SERV_PORT_STR, AF_INET6, SOCK_DGRAM);
sockfd = Socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (ptr) {
// 通過設(shè)置IPV6_RTHDR套接字選項(xiàng)可以把一個(gè)路由首部應(yīng)用于從某個(gè)套接字發(fā)送的所有分組
// 以取代為每個(gè)分組發(fā)送同樣的輔助數(shù)據(jù)的做法
Setsockopt(sockfd, IPPROTO_IPV6, IPV6_RTHDR, ptr, len);
free(ptr);
}
dg_cli(stdin, sockfd, ai->ai_addr, ai->ai_addrlen); /* do it all */
exit(0);
}
服務(wù)器程序比較簡(jiǎn)單,它打開一個(gè)UDP套接字并調(diào)用dg_echo,我們不給出其main函數(shù),但給出它調(diào)用的dg_echo函數(shù)版本,該函數(shù)如果收到一個(gè)攜帶源路徑的分組,就打印這個(gè)源路徑,并逆轉(zhuǎn)它用于回射該分組:
#include "unp.h"
void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen) {
int n;
char mesg[MAXLINE];
int on;
char control[MAXLINE];
struct msghdr msg;
struct cmsghdr *cmsg;
struct iovec iov[1];
// 為接收外來源路徑,必須開啟IPV6_RECVRTHDR套接字選項(xiàng)
on = 1;
Setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVRTHDR, &on, sizeof(on));
// 設(shè)置用于接收外來源路徑的msghdr結(jié)構(gòu)中不變的字段
bzero(&msg, sizeof(msg));
iov[0].iov_base = mesg;
msg.msg_name = pcliaddr;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = control;
for (; ; ) {
// msghdr結(jié)構(gòu)中的以下3項(xiàng)會(huì)被recvmsg函數(shù)修改,每次調(diào)用recvmsg前重置它們
msg.msg_namelen = clilen;
msg.msg_controllen = sizeof(control);
iov[0].iov_len = MAXLINE;
n = Recvmsg(sockfd, &msg, 0);
// 遍歷輔助數(shù)據(jù)以尋找路由首部
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_RTHDR) {
// 調(diào)用inet6_srcrt_print打印接收到的源路徑
inet6_srcrt_print(CMSG_DATA(cmsg));
// 逆轉(zhuǎn)接收到的源路徑,以便沿同樣的路徑返送所接收的分組,此處原地逆轉(zhuǎn)路徑
Inet6_rth_reverse(CMSG_DATA(cmsg), CMSG_DATA(cmsg));
}
}
// 設(shè)置回送數(shù)據(jù)的長(zhǎng)度,然后回射所接收的分組
iov[0].iov_len = n;
Sendmsg(sockfd, &msg, 0);
}
}
inet6_srcrt_print函數(shù):
#include "unp.h"
void inet6_srcrt_print(void *ptr) {
int i, segments;
char str[INET6_ADDRSTRLEN];
// 調(diào)用inet6_rth_segments確定源路徑中存在的網(wǎng)段數(shù)
segments = Inet6_rth_segments(ptr);
printf("received source route: ");
for (i = 0; i < segments; ++i) {
// 調(diào)用inet_ntop把IP地址從網(wǎng)絡(luò)字節(jié)序(in6_addr結(jié)構(gòu))轉(zhuǎn)換為點(diǎn)分十進(jìn)制格式
printf("%s ", Inet_ntop(AF_INET6, Inet6_rth_getaddr(ptr, i), str, sizeof(str)));
}
printf("\n");
}
處理IPv6原路徑的客戶和服務(wù)器程序無需了解源路徑在分組中是如何格式化的,API提供的庫(kù)函數(shù)隱藏了分組格式的細(xì)節(jié)。
我們已經(jīng)講解了sendmsg和recvmsg函數(shù)發(fā)送和接收的7種IPv6輔助數(shù)據(jù)對(duì)象:
1.IPv6分組信息:in6_pktinfo結(jié)構(gòu)或者包含目的地址和外出接口索引,或者包含源地址和到達(dá)接口索引。(第二十二章)
2.外出跳限或接收跳限。(第二十二章)
3.下一跳地址。只能發(fā)送不能接受。(第二十二章)
4.外出流通類別或接收流通類別。(第二十二章)
5.步跳選項(xiàng)。(第二十七章)
6.目的地選項(xiàng)。(第二十七章)
7.路由首部。(第二十七章)
這些輔助數(shù)據(jù)對(duì)象可通過設(shè)置相應(yīng)的套接字選項(xiàng),使得從某個(gè)套接字發(fā)送的所有分組都使用相同的輔助數(shù)據(jù)值,這些套接字選項(xiàng)所用常值與輔助數(shù)據(jù)對(duì)象一致,即調(diào)用setsockopt的level參數(shù)總是IPPROTO_IPV6,選項(xiàng)名參數(shù)按以上IPv6輔助數(shù)據(jù)列出的順序分別為:IPV6_PKTINFO、IPV6_HOPLIMIT、IPV6_NEXTHOP、IPV6_TCLASS、IPV6_HOPOPTS、IPV6_DSTOPTS、IPV6_RTHDR。對(duì)于UDP套接字和原始IPv6套接字,我們可以通過在sendmsg調(diào)用中指定相應(yīng)輔助數(shù)據(jù),使得針對(duì)每個(gè)分組覆寫這些粘附性選項(xiàng)。
粘附性選項(xiàng)的概念也適用于TCP,由于TCP套接字上不能使用sendmsg或recvmsg函數(shù)發(fā)送或接收輔助數(shù)據(jù),可通過設(shè)置相應(yīng)套接字選項(xiàng)指定輔助數(shù)據(jù)對(duì)象,這些對(duì)象隨后影響在相應(yīng)套接字上發(fā)送的所有分組。但如果某個(gè)分組需要重傳,且發(fā)送原分組和重傳的分組之間粘附性選項(xiàng)的設(shè)置發(fā)生變更,那么重傳的分組上的粘附性選項(xiàng)既可能是舊的,也可能是新的。
希望在某個(gè)套接字上調(diào)用recvmsg接收輔助數(shù)據(jù)的進(jìn)程,必須預(yù)先在該套接字上開啟相應(yīng)的套接字選項(xiàng),選項(xiàng)名參數(shù)按以上IPv6輔助數(shù)據(jù)列出的順序(跳過了下一跳地址選項(xiàng),因?yàn)樵撨x項(xiàng)不能接收只能發(fā)送)分別為:IPV6_RECVPKTINFO、IPV6_RECVHOPLIMIT、IPV6_RECVTCLASS、IPV6_RECVHOPOPTS、IPV6_RECVDSTOPTS、IPV6_RECVRTHDR。TCP應(yīng)用也能使用同樣的方式獲取這些輔助數(shù)據(jù)對(duì)象,但TCP套接字上不能使用recvmsg函數(shù)與用戶數(shù)據(jù)一道接收輔助數(shù)據(jù),由recvmsg函數(shù)返回的這些粘附性選項(xiàng)實(shí)際來自最近收取的分節(jié)所在IPv6分組,這些選項(xiàng)除非對(duì)端TCP修改過,否則所有IPv6分組都具有相同的選項(xiàng)。
RFC 2292定義了本章中IPv6 API的一個(gè)早期版本。文章來源:http://www.zghlxwxcb.cn/news/detail-634620.html
ping程序會(huì)創(chuàng)建一個(gè)原始套接字,能通過recvfrom函數(shù)讀入每個(gè)數(shù)據(jù)報(bào)的完整IP首部。文章來源地址http://www.zghlxwxcb.cn/news/detail-634620.html
到了這里,關(guān)于UNIX網(wǎng)絡(luò)編程卷一 學(xué)習(xí)筆記 第二十七章 IP選項(xiàng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!