說實話,IP協議的逐跳選項用的真不是很多,但是卻必不可少。
在IPv4中,它作為一個普通的IP選項塞入變長的IPv4頭中,加以必須被路由器處理之約束。
在IPv6中,它則作為一個獨立的擴展頭被定長IPv6頭的next header字段指引,加以必須被路由器處理之約束。
哪個更優雅,不用多說。如果你是路由器實現者,你會覺得哪一個IP版本處理逐跳選項更加方便,你會如何設計處理邏輯?
話題有點發散,但這是后面准備寫的內容,本文談另一個相關的話題,即逐跳選項是如何被處理的,我以 逐跳路由器告警選項(Router Alert Option) 描述之。
第一個問題,路由器進行IP轉發的邏輯是怎樣的。
將目標IP地址不是本機IP地址的數據包收到本機的方案很多,以Linux系統為例,包括但不限於:
使用NAT Redirect。
使用Netfilter nfqueue。
使用PACKET/PF_Ring等機制來鏡像或者旁路。
Netmap/DPDK。
…
一般的DPI設備都是采用這些方式來 “捕獲” 數據報文。但是本文不談這些。
排除數據包被有意截獲不說,僅從IP協議的層面上來講,逐跳被路由的IP數據包 只有在目標IP地址是其自身時才會被接收,除非IP數據包攜帶逐跳路由器告警選項(Router Alert Option)! 即在下面的情況下,數據包會被當前路由器接收:
數據包的目標IP地址就是當前路由器的IP地址之一。
數據包的目標IP地址不屬於當前路由器,但是它攜帶了路由告警選項(Router Alert Option)。
其中第1點是顯而易見的,這是IP路由的基本常識,本文主要說說第2點。
第一次接觸RSVP協議是在2004年,那時真的是猛學了一段網絡協議,特別是感興趣的路由交換這塊,此外為了考華為3Com的HCSE,也接觸了一些高層的協議,RSVP就是其中之一。
我在當時不理解RSVP協議是如何工作的。疑問大致就是 既然數據報文的目標IP地址不是途徑的路由器,那么路由器又是如何接收到數據報文並且處理資源預留的呢? 我不清楚是什么機制讓路由器能收下這個目標並不它的報文。
后來我知道這是路由器告警選項(Router Alert Option)做的,它在RFC2113(https://tools.ietf.org/html/rfc2113)中被描述:
This memo describes a new IP Option type that alerts transit routers
to more closely examine the contents of an IP packet. This is useful
for, but not limited to, new protocols that are addressed to a
destination but require relatively complex processing in routers
along the path.
換句話說,就是 攜帶路由器告警選項的數據包,單憑標准的IP轉發處理是不夠的,必須根據數據包數據載荷的內容進行進一步的處理。
那么,可以想象,路由器告警選項(Router Alert Option)的處理邏輯是下面的樣子:
然后,由於數據包載荷的內容以及所需的處理邏輯高度策略化,所以路由器告警選項(Router Alert Option)都是在用戶態進行處理,處理完之后,將數據包的TTL/Hoplimit字段遞減后原封不動注入協議棧,使其繼續被逐跳轉發前行。
因此,數據包轉發也就有了兩條路徑:
由於IPv4對IP選項的處理非標准化處理,以下全部基於IPv6討論。
說實話,IPv6的IP轉發邏輯更加清晰直接:
IPv6協議頭固定長度,處理起來更規則。
逐跳選項頭在經由路由器上被無條件解析並處理。
除了逐跳頭,其余所有擴展頭都是目標地址是本機時按照next頭的邏輯才被處理, 就像處理普通上層一樣 。
美中不足就是這個逐跳頭,破壞了規矩,使得目的地址不是本機也要被處理,不過又該如何呢?誰讓它叫做 逐跳頭 呢?無論如何,我都覺得應該有更加優雅的方法,只是目前還沒有想到。
在實現上,路由器告警選項(Router Alert Option)是藏匿在IPv6逐跳擴展頭里面的,不管是IPv4還是IPv6,標准規定,只要是數據包途徑的路由器,都必須處理逐跳選項,這是硬性的規定。至於說路由器告警選項(Router Alert Option),只是逐跳頭的內容而已,它決定了數據包應該如何被處理。
逐跳頭決定了逐跳頭的內容一定會被處理。
逐跳頭的內容決定了數據包如何被進一步處理。
在原理方面,以上基本就是全部了。下面就是代碼實現和Demo樣例了。
先看路由器告警選項(Router Alert Option)的格式:
關鍵的就是那個 select value,它決定了途徑的路由器要處理哪些攜帶路由器告警選項(Router Alert Option)的包。
IP Forward的邏輯如下:
hop_by_hop_process_done(skb)
{
other_hhop_process(skb);
// Router Alert option
list_for_each(ra_entry, router_alert_list) {
if (ra_entry.select_value == skb.hhop.ra.select_value) {
recv_to_user_process(skb);
return 1;
}
}
return 0;
}
ip_forward(skb)
{
...
if (hop_by_hop_process_done(skb)) {
return 0;
}
continue_forward(skb);
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
也就是說,數據包攜帶的逐跳選項頭里的路由器告警(Router Alert)select value和當前路由器本地監聽的路由器告警(Router Alert)處理進程的select value相等,說明本地有進程會 進一步處理該數據報文。
接下來的問題是,我們看得見摸得着的比如Linux系統,提供了哪些現成的API讓我們可以親眼見到上面的過程呢?
我們看一下IPv6的編程Manual:
http://man7.org/linux/man-pages/man7/ipv6.7.html
其中有關於路由器告警(Router Alert)的sockopt以及其用法:
按照這個Manual,我來簡單地實現一個監聽進程:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define LENGTH 1500
#define __u8 unsigned char
#define __be16 unsigned short
struct ipv6hdr {
__u8 priority:4,
version:4;
__u8 flow_lbl[3];
__be16 payload_len;
__u8 nexthdr;
__u8 hop_limit;
struct in6_addr saddr;
struct in6_addr daddr;
};
#define MY_VALUE 5
int do_it()
{
// TODO
return 0;
}
int main(int argc, char **argv)
{
int ret;
int sd;
struct ipv6hdr *iphdr;
struct sockaddr_in6 dest;
int length = LENGTH;
unsigned char data[LENGTH] = { 0 };
int ra_select_value = MY_VALUE;
// 按照要求,只能RAW
sd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
if (sd < 0) {
perror("create socket\n");
}
// 按照要求,偵聽數值5這個select value
if(setsockopt(sd, IPPROTO_IPV6, IPV6_ROUTER_ALERT, (const void *) &ra_select_value, sizeof(ra_select_value)) < 0) {
printf("setsockopt ");
}
// 接收被IP forward邏輯給遞送上來的需要進一步策略化處理的數據包
ret = recvfrom(sd, data, length, 0, NULL, NULL);
if (ret < 0) {
printf("Error in sending message : %s (%d)\n", strerror(errno), errno);
return -1;
}
// 數據本身就是一個IP數據報文
iphdr = (struct ipv6hdr *)&data[0];
{ // 查看一下內容
int i;
printf("begin\n");
for (i = 0; i < ret; i++) {
printf("%.2X ", data[i]);
}
printf("\nend\n\n");
}
{ // 這就是我們的所謂的進一步處理!
unsigned char cmd[LENGTH] = {0};
strncpy(cmd, (unsigned char *)&data[0] + sizeof(struct ipv6hdr) + 8 + 20, ret - sizeof(struct ipv6hdr) - 8 - 20);
printf("%s\n", cmd);
do_it(); // 做的像個樣子
}
// 遞減hop limit
iphdr->hop_limit --;
memset(&dest, 0, sizeof(dest));
dest.sin6_family = AF_INET6;
memcpy(dest.sin6_addr.s6_addr, &iphdr->daddr, sizeof(struct in6_addr));
// 按照要求,it is the user's responsibility to send them out again.
ret = sendto(sd, data, ret, 0, (struct sockaddr*) &dest, sizeof(dest));
if (ret < 0) {
printf("Error in sending message : %s (%d)\n", strerror(errno), errno);
return -1;
}
close(sd);
return(0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
我們把它編譯成 ra_policy 以備用。
現在我們構造一個攜帶路由器告警選項的數據包,讓其經過部署 ra_policy 程序的路由器,看看會發生什么。
先來看一下我的演示拓撲,非常簡易:
這么設計這個測試拓撲的理由非常簡單直接。我們假設一個數據包逐跳經由的路由器分別為Router0,Router1,Router2,我們准備在Router1上演示逐跳頭的路由器告警選項(Router Alert Option)如何被處理,那么就要假想一個數據包從Router0發過來,在Router1上被處理后發送給下一跳Router2,因此我們在Router0上構造這個數據包P並注入,值得注意的是,數據包P的源地址並非Router0上的地址,目標地址也不是Router1和Router2。
現在就剩下攜帶路由器告警選項(Router Alert Option)的數據包構造程序還沒有。
可以非常容易地用RAW socket寫一個,但是我找到一個現成的,來自下面的網站:
http://www.pdbuchan.com/rawsock/rawsock.html
我使用的是下面的程序:
http://www.pdbuchan.com/rawsock/tcp6_hop_frag.c
我對其進行了簡單的修改:
由於本文我並不care分片這個特性,我將data數據文件進行了修改。
為了體現所謂的 根據數據包的內容進一步處理, data的內容改成了:
[root@localhost hopbyhop]# cat data
請將網卡的MTU縮小為1300,並且調整下面的內核參數:
net.core.rmem_default = 400382
我嚴格按照路由器R的enp0s9的MAC地址進行了以太幀封裝,而不是使用廣播MAC地址。
其它就沒有什么改動了。編譯成 tcp6_hop_frag 以備用。
程序本身的代碼並不重要,說實話,tcp6_hop_frag.c這個代碼寫得並不是很優雅,我不是很喜歡這個代碼,比代碼更重要的是它發出去的報文的格式,我們要充分理解這個格式:
現在看看上面注入的數據包P到達Router1時,發生了什么:
[root@localhost hopbyhop]# ./ra_policy
begin
60 00 00 00 00 82 00 FF 20 01 0D B8 00 00 00 00 02 14 51 FF FE 2F 15 56 24 04 68 00 40 05 08 03 00 00 00 00 00 00 20 0E 06 00 05 02 00 05 01 00 00 50 00 50 00 00 00 00 00 00 00 00 50 02 FF FF 7B BA 00 00 E8 AF B7 E5 B0 86 E7 BD 91 E5 8D A1 E7 9A 84 4D 54 55 E7 BC A9 E5 B0 8F E4 B8 BA 31 33 30 30 EF BC 8C E5 B9 B6 E4 B8 94 E8 B0 83 E6 95 B4 E4 B8 8B E9 9D A2 E7 9A 84 E5 86 85 E6 A0 B8 E5 8F 82 E6 95 B0 EF BC 9A 0A 6E 65 74 2E 63 6F 72 65 2E 72 6D 65 6D 5F 64 65 66 61 75 6C 74 20 3D 20 34 30 30 33 38 32 0A
end
請將網卡的MTU縮小為1300,並且調整下面的內核參數:
net.core.rmem_default = 400382
[root@localhost hopbyhop]#
1
2
3
4
5
6
7
8
9
嗯,打印出了數據包的內容,原來數據包的內容就是指示這個路由器做一些資源配置方面的事情啊,然后Router1照着做就是了,當然,它也是可以拒絕的,不管怎樣,Router1的ra_policy進程最終將數據包傳給了下一跳Router2…在Router2看來,數據包是下面的樣子:
當然,數據包到達Router2的時候,Router2依然不是其最終的目的地(它的最終目的地是Google!),那么如果上一個Router沒有把逐跳頭的路由器告警選項(Router Alert Option)摘除,那么每一個途徑的路由器都要做類似Router1的處理。
有意思的是, Router1可以修改數據包的內容,讓后續的路由器作出不同的策略決策!
是的,RSVP跟這個也差不多,就是這么個意思。
以上就是關於路由器告警選項(Router Alert Option)如何工作的簡單演示,總結一下就是:
逐跳選項(hop by hop)每個途徑的路由器必須處理。
路由器告警(Router Alert Option)選項藏匿在逐跳選項里。
路由器告警選項被應用層進程進行策略化處理。
攜帶路由器選項的數據包被拉到應用層進程處理后重新注入協議棧繼續前往下一跳。
————————————————
版權聲明:本文為CSDN博主「dog250」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/dog250/article/details/89153594