網絡子係統43_ip選項預處理
//選項格式: // 1.type中指示該選項在分片時是否需要被拷貝 // 2.ptr從1算起,1為type的位置 // 3.len不包括type字段,其餘都包括(len,ptr,選項內容)

//type字段:

ip選項type字段的常見代碼值:

//inet_addr_type(addr)返回l3 addr的路由類型: // 1.RTN_LOCAL 該ip地址屬於一個本地接口 // 2.RTN_UNICAST 根據路由表,該ip地址可以抵達,而且是單播地址 // 3.RTN_MULTICAST 該地址是多播地址 // 4.RTN_BROADCAST 該地址是廣播地址
//此函數分析ip報文中的如下選項,並設置到skb->cb中
// 1.IPOPT_END 處理辦法,使用IPOPT_END覆蓋之後出現的所有選項,並標示ip頭被更改過
// 2.IPOPT_NOOP 處理辦法,跳過
// 3.IPOPT_SSRR IPOPT_LSRR 處理辦法,設置opt->ss為選項相對ip頭的偏移量,在之後對ip報文的處理上,填充該選項,該選項隻能出現一次
// 4.IPOPT_RR 處理辦法,拷貝路由緩存中的首選源地址到選項中,更新選項的ptr字段,使其指向下一個空閑位置
// 5.IPOPT_TIMESTAMP 處理辦法,分析子選項
// 5.1 IPOPT_TS_TSONLY 隻記錄時間戳
// 5.2 IPOPT_TS_TSANDADDR 記錄時間戳和ip地址
// 5.3 IPOPT_TS_PRESPEC 本機ip等於ptr當前所指的ip時,填入本機時間,否則更新ptr到下一個ip地址
// 6.IPOPT_SEC IPOPT_SID 處理辦法,不處理
// 3,4在處理時,如果選項空間不足夠,則通過icmp向發送主機報告錯誤並丟棄封包
// 5在處理時,如果選項空間不足夠,則遞增溢出次數,如果溢出次數已達15次,則通過icmp項主機發送報告錯誤並丟棄封包
//調用路徑ip_rcv->ip_rcv_finish->ip_options_compile
//opt = NULL
1.1 int ip_options_compile(struct ip_options * opt, struct sk_buff * skb)
{
int l;
unsigned char * iph;
unsigned char * optptr;
int optlen;
unsigned char * pp_ptr = NULL;
struct rtable *rt = skb ? (struct rtable*)skb->dst : NULL;
if (!opt) {//在接收路徑上opt=null
opt = &(IPCB(skb)->opt);//skb->cb強轉成struct ip_options結構
memset(opt, 0, sizeof(struct ip_options));
iph = skb->nh.raw;
opt->optlen = ((struct iphdr *)iph)->ihl*4 - sizeof(struct iphdr);//ip選項的長度 = ihl*4 - 20(ip報頭長度)
optptr = iph + sizeof(struct iphdr);//選項的第一個字節
opt->is_data = 0;
} else {
optptr = opt->is_data ? opt->__data : (unsigned char*)&(skb->nh.iph[1]);
iph = optptr - sizeof(struct iphdr);
}
for (l = opt->optlen; l > 0; ) {
switch (*optptr) {
case IPOPT_END://在IPOPT_END之後的所有選項,均會被IPOP_END覆蓋
for (optptr++, l--; l>0; optptr++, l--) {
if (*optptr != IPOPT_END) {
*optptr = IPOPT_END;
opt->is_changed = 1;//記錄報頭被修改
}
}
goto eol;
case IPOPT_NOOP://IPOPT_NOOP用於填補選項之間的空白
l--;
optptr++;
continue;
}
//非單字節選項
//1.通過第二個字節optptr[1]指示選項長度
//2.通過第三個字節optptr[2]指示選項內容的指針,起始值為1,表示type字段
optlen = optptr[1];
if (optlen<2 || optlen>l) {//選項的健康性檢查,非單字節選項長度至少為2,該選項長度不能超過選項剩餘的總長度
pp_ptr = optptr;
goto error;
}
switch (*optptr) {
case IPOPT_SSRR://嚴格源路由選項,發送者列出沿途上的每一台路由器ip地址,並且沿途不能修改
case IPOPT_LSRR://寬鬆源路由選項,中間路由器可以使用另一台不在列表中的路由器,作為通向列表中下一個路由器的路徑,發送者指定的路由器必須按照指定的次序使用
if (optlen < 3) {//健康性檢查
pp_ptr = optptr + 1;
goto error;
}
if (optptr[2] < 4) {
pp_ptr = optptr + 2;
goto error;
}
if (opt->srr) {//opt->srr記錄源路由選項相對於ip頭的起始位置;隻能有一個該選項
pp_ptr = optptr;
goto error;
}
//輸入路徑上,skb!=NULL
if (!skb) {
if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {
pp_ptr = optptr + 1;
goto error;
}
memcpy(&opt->faddr, &optptr[3], 4);
if (optlen > 7)
memmove(&optptr[3], &optptr[7], optlen-7);
}
opt->is_strictroute = (optptr[0] == IPOPT_SSRR);//is_strictroute指示是否為嚴源路由選項
opt->srr = optptr - iph;//源路由選項的起始位置
break;
case IPOPT_RR://record route選項
if (opt->rr) {//opt->rr記錄record route選項相對於ip頭的偏移量;隻能有一個該選項
pp_ptr = optptr;
goto error;
}
if (optlen < 3) {
pp_ptr = optptr + 1;
goto error;
}
if (optptr[2] < 4) {
pp_ptr = optptr + 2;
goto error;
}
if (optptr[2] <= optlen) {//說明有空閑空間
if (optptr[2]+3 > optlen) {//不足4字節
pp_ptr = optptr + 2;
goto error;
}
if (skb) {
memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);//入口路徑上skb->dst在處理ip選項之前被初始化
opt->is_changed = 1;//複製rt中的首選源地址,首選源地址在ip_route_input_slow中,根據被路由封包的目的地址被設置
}
optptr[2] += 4;//指向下一個可用的空閑位置
opt->rr_needaddr = 1;
}
opt->rr = optptr - iph;
break;
case IPOPT_TIMESTAMP://時間戳選項
if (opt->ts) {//opt->ts記錄time stamp選項相對於ip頭的偏移量;隻能有一個該選項
pp_ptr = optptr;
goto error;
}
if (optlen < 4) {
pp_ptr = optptr + 1;
goto error;
}
if (optptr[2] < 5) {//IPOPT_TIMESTAMP選項頭格式為:[type len ptr (overflow:4 | flag:4)],因此ptr指示位置至少為5,(type默認為1)
pp_ptr = optptr + 2;
goto error;
}
if (optptr[2] <= optlen) {
__u32 * timeptr = NULL;
if (optptr[2]+3 > optptr[1]) {
pp_ptr = optptr + 2;
goto error;
}
//處理子選項,flag字段,指示子選項
switch (optptr[3]&0xF) {
case IPOPT_TS_TSONLY://記錄時間戳
opt->ts = optptr - iph;//opt->ts指示time stamp選項相對於ip頭的偏移量
if (skb)
timeptr = (__u32*)&optptr[optptr[2]-1];//本機記錄time stamp的位置
opt->ts_needtime = 1;//告訴本機,需要記錄time stamp
optptr[2] += 4;//選項指針移動4個字節,下一個主機記錄time stamp的起始位置
break;
case IPOPT_TS_TSANDADDR://記錄時間戳和地址
if (optptr[2]+7 > optptr[1]) {//不足8字節
pp_ptr = optptr + 2;
goto error;
}
opt->ts = optptr - iph;
if (skb) {
memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);//首選源地址
timeptr = (__u32*)&optptr[optptr[2]+3];//time stamp填充位置,之後填充
}
opt->ts_needaddr = 1;//指示time stamp子選項要求位置和時間
opt->ts_needtime = 1;
optptr[2] += 8;//長度更新8字節
break;
case IPOPT_TS_PRESPEC://隻針對發送者指定的ip地址,記錄time stamp選項
if (optptr[2]+7 > optptr[1]) {
pp_ptr = optptr + 2;
goto error;
}
opt->ts = optptr - iph;
{
u32 addr;
memcpy(&addr, &optptr[optptr[2]-1], 4);//檢查該地址的路由類型
if (inet_addr_type(addr) == RTN_UNICAST)//非本機ip地址,但是該ip可達,而且是單播地址
break;
if (skb)
timeptr = (__u32*)&optptr[optptr[2]+3];
}
opt->ts_needtime = 1;
optptr[2] += 8;//如果指定的ip非本機地址,也會掉過該選項位置,後續的主機,從下一個選項位置開始
break;
default:
if (!skb && !capable(CAP_NET_RAW)) {
pp_ptr = optptr + 3;
goto error;
}
break;
}
if (timeptr) {//填充time stamp的位置
struct timeval tv;
__u32 midtime;
do_gettimeofday(&tv);//獲取係統時間
midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);//在一天內,已經過多少秒
memcpy(timeptr, &midtime, sizeof(__u32));
opt->is_changed = 1;//ip報頭被修改,因此需要重新計算校驗和
}
} else {//time stamp選項,空閑空間不夠
unsigned overflow = optptr[3]>>4;//溢出的次數
if (overflow == 15) {//已經達到最大的溢出次數
pp_ptr = optptr + 3;
goto error;
}
opt->ts = optptr - iph;
if (skb) {
optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4);//遞增溢出次數
opt->is_changed = 1;
}
}
break;
case IPOPT_RA:
if (optlen < 4) {
pp_ptr = optptr + 1;
goto error;
}
if (optptr[2] == 0 && optptr[3] == 0)
opt->router_alert = optptr - iph;
break;
case IPOPT_SEC://security選項
case IPOPT_SID://stream id選項
default://不處理這兩種選項
if (!skb && !capable(CAP_NET_RAW)) {
pp_ptr = optptr;
goto error;
}
break;
}
l -= optlen;
optptr += optlen;
}
eol:
if (!pp_ptr)
return 0;
error:
if (skb) {//通過icmp,返回錯誤
icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((pp_ptr-iph)<<24));
}
return -EINVAL;
}
最後更新:2017-04-03 14:53:38