日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

协议栈数据包快速转发的实现(2)

發(fā)布時(shí)間:2023/12/20 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 协议栈数据包快速转发的实现(2) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

上一篇博客大體上講解了什么是SNAT和DNAT。然后在博客的最后引入了一個(gè)知識點(diǎn)-連接跟蹤(conntrack)。今天我們就來看看連接跟蹤和我們的數(shù)據(jù)包快轉(zhuǎn)實(shí)現(xiàn)有什么關(guān)系,怎么利用連接跟蹤來實(shí)現(xiàn)數(shù)據(jù)包快轉(zhuǎn)的功能

數(shù)據(jù)報(bào)文唯一性

四元組是:
源IP地址、目的IP地址、源端口、目的端口

五元組是:
源IP地址、目的IP地址、協(xié)議號、源端口、目的端口

七元組是:
源IP地址、目的IP地址、協(xié)議號、源端口、目的端口,服務(wù)類型以及接口索引

協(xié)議號:IP是網(wǎng)絡(luò)層協(xié)議,IP頭中的協(xié)議號用來說明IP報(bào)文中承載的是哪種協(xié)議,協(xié)議號標(biāo)識上層是什么協(xié)議(一般是傳輸層協(xié)議,比如6 TCP,17 UDP;但也可能是網(wǎng)絡(luò)層協(xié)議,比如1 ICMP;也可能是應(yīng)用層協(xié)議,比如89 OSPF)。

TCP/UDP是傳輸層協(xié)議,TCP/UDP的端口號用來說明是哪種上層應(yīng)用,比如TCP 80代表WWW,TCP 23代表Telnet,UDP 69代表TFTP。
目的主機(jī)收到IP包后,根據(jù)IP協(xié)議號確定送給哪個(gè)模塊(TCP/UDP/ICMP…)處理,送給TCP/UDP模塊的報(bào)文根據(jù)端口號確定送給哪個(gè)應(yīng)用程序處理。

我們這里的快速轉(zhuǎn)發(fā)tcp和udp,其實(shí)就是利用了發(fā)送和接收報(bào)文的五元組來實(shí)現(xiàn)手動的快速轉(zhuǎn)發(fā)。而五元組的信息都可以通過連接跟蹤拿到。這也是鏈接跟蹤對于我們自己的快速轉(zhuǎn)發(fā)的重要性。

連接跟蹤

連接跟蹤是netfilter中重要的一部分(關(guān)于netfilter在這里不會進(jìn)行深入的講解,只會講解和連接跟蹤相關(guān)的)。連接跟蹤顧名思義代表的是數(shù)據(jù)包的鏈接的狀態(tài)。
在這里貼出幾篇博客,該博客中講解netfilter和連接跟蹤的相關(guān)的基礎(chǔ)知識,希望大家可以仔細(xì)看看

http://www.zsythink.net/archives/1199
https://segmentfault.com/a/1190000019605260
http://blog.chinaunix.net/uid-20786208-id-5137728.html

上面這三篇文章講解的比價(jià)好,在我學(xué)習(xí)連接跟蹤的時(shí)候,我也是查了比較多的資料然后結(jié)合代碼學(xué)習(xí)。

連接跟蹤數(shù)據(jù)結(jié)構(gòu)

雖然在上面的博客中都講解了連接跟蹤的數(shù)據(jù)結(jié)構(gòu)。但是我覺得在這里還是必須要提及一下。算是對于上面博客的一個(gè)補(bǔ)充。

struct nf_conn {/* Usage count in here is 1 for hash table/destruct timer, 1 per skb,plus 1 for any connection(s) we are `master' for */struct nf_conntrack ct_general;spinlock_t lock;/* XXX should I move this to the tail ? - Y.K *//* These are my tuples; original and reply */struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];/* Have we seen traffic both ways yet? (bitset) */unsigned long status;/* If we were expected by an expectation, this will be it */struct nf_conn *master;/* Timer function; drops refcnt when it goes off. */struct timer_list timeout;#if defined(CONFIG_NF_CONNTRACK_MARK)u_int32_t mark; #endif#ifdef CONFIG_NF_CONNTRACK_SECMARKu_int32_t secmark; #endif/* Extensions */struct nf_ct_ext *ext; #ifdef CONFIG_NET_NSstruct net *ct_net; #endif/* Storage reserved for other modules, must be the last member */union nf_conntrack_proto proto; };

在上一篇博客中我們講解連接跟蹤會記錄發(fā)送的tuple和接收tuple的信息。該字段就是struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];該數(shù)組有兩個(gè)取值,分別是發(fā)送和接收方向的tuple.

enum ip_conntrack_dir {IP_CT_DIR_ORIGINAL,IP_CT_DIR_REPLY,IP_CT_DIR_MAX };
  • IP_CT_DIR_ORIGINAL代表的是發(fā)送方向的tuple
  • IP_CT_DIR_REPLY代表的是接收發(fā)現(xiàn)的tuple

我們在來看看struct nf_conntrack_tuple_hash 結(jié)構(gòu)

struct nf_conntrack_tuple_hash {struct hlist_nulls_node hnnode;struct nf_conntrack_tuple tuple; }; struct nf_conntrack_tuple {struct nf_conntrack_man src;/* These are the parts of the tuple which are fixed. */struct {union nf_inet_addr u3;union {/* Add other protocols here. */__be16 all;struct {__be16 port;} tcp;struct {__be16 port;} udp;struct {u_int8_t type, code;} icmp;struct {__be16 port;} dccp;struct {__be16 port;} sctp;struct {__be16 key;} gre;} u;/* The protocol. */u_int8_t protonum;/* The direction (for tuplehash) */u_int8_t dir;} dst; }; union nf_inet_addr {__u32 all[4];__be32 ip;__be32 ip6[4];struct in_addr in;struct in6_addr in6; };struct nf_conntrack_man {union nf_inet_addr u3;union nf_conntrack_man_proto u;/* Layer 3 protocol */u_int16_t l3num; };

數(shù)據(jù)包文到達(dá)內(nèi)核協(xié)議棧時(shí),使用sk_buff{}(即skb),其類型為struct nf_conntrack *;該結(jié)構(gòu)記錄了連接記錄被公開應(yīng)用的計(jì)數(shù),也方便其他地方對連接跟蹤的引用;

在這里就必須做一個(gè)總結(jié)了。這里也是說明為什么我們的快轉(zhuǎn)模塊需要借助連接跟蹤的信息。

  • 在連接跟蹤的結(jié)構(gòu)體中分別有兩個(gè)方向的tuple-發(fā)送和接收
  • 每一個(gè)tuple我們可以獲取到發(fā)送的源ip地址以及目的ip地址,根據(jù)協(xié)議類型獲取源端口號以及目的端口號,以及協(xié)議號。

從連接跟蹤中我們可以獲取到什么?很明顯我們已經(jīng)獲取了決定一個(gè)數(shù)據(jù)包(TCP/UDP)唯一性的五元組。

那么是不是只靠連接跟蹤,我們就可以寫出我們的快轉(zhuǎn)模塊了?答案是不行的,我們還需要一些其他的額外信息,這個(gè)后面我們在引入和講解。



連接跟蹤的建立

在這里我直接應(yīng)用了第三篇博客中的圖,第三篇博客其實(shí)已經(jīng)講解了初始化的過程。但是有些相關(guān)的知識點(diǎn)還是需要補(bǔ)充和提及一下

同樣的我們通過上面的博客知道,連接跟蹤的初始化是注冊在PRE_ROUTING鏈。而PRE_ROUTING鏈?zhǔn)窃趇p_rcv函數(shù)中調(diào)用的。

在這里需要說明一下ip_rcv函數(shù)。該函數(shù)是ip層的入口函數(shù),該函數(shù)十分的重要。他首先會檢測數(shù)據(jù)報(bào)文是否合法。然后會調(diào)用注冊在PRE_ROUTING鏈的函數(shù)。最后調(diào)用ip_rcv_finish(在linux內(nèi)核中一般都是先調(diào)用檢測函數(shù),再調(diào)用處理函數(shù)。而且命名有一定的規(guī)律可循。檢測函數(shù)一般為do_something,處理函數(shù)一般為do_something_finish。或者是do_something和do_something2)。


我們來看看ip_rcv的函數(shù)源碼

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) {const struct iphdr *iph;u32 len;/* When the interface is in promisc. mode, drop all the crap* that it receives, do not try to analyse it.*/if (skb->pkt_type == PACKET_OTHERHOST)goto drop;IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);goto out;}if (!pskb_may_pull(skb, sizeof(struct iphdr)))goto inhdr_error;iph = ip_hdr(skb);/** RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.** Is the datagram acceptable?** 1. Length at least the size of an ip header* 2. Version of 4* 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]* 4. Doesn't have a bogus length*/if (iph->ihl < 5 || iph->version != 4)goto inhdr_error;if (!pskb_may_pull(skb, iph->ihl*4))goto inhdr_error;iph = ip_hdr(skb);if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))goto inhdr_error;len = ntohs(iph->tot_len);if (skb->len < len) {IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);goto drop;} else if (len < (iph->ihl*4))goto inhdr_error;/* Our transport medium may have padded the buffer out. Now we know it* is IP we can trim to the true length of the frame.* Note this now means skb->len holds ntohs(iph->tot_len).*/if (pskb_trim_rcsum(skb, len)) {IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);goto drop;}/* Remove any debris in the socket control block */memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));/* Must drop socket now because of tproxy. */skb_orphan(skb);return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish);inhdr_error:IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS); drop:kfree_skb(skb); out:return NET_RX_DROP; }

這里需要說明一下。ip_rcv_finish函數(shù)是路由的重要函數(shù)。他會判斷數(shù)據(jù)包是發(fā)往本地的,方式需要nat轉(zhuǎn)發(fā),發(fā)送到上一級路由器的。大家有興趣可以去了解一下數(shù)據(jù)包在協(xié)議棧中的流向。

NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish);

這里就是在循環(huán)調(diào)用Pre_ROUTING注冊的函數(shù)(會根據(jù)優(yōu)先級調(diào)用)。那這里我們也得出了一個(gè)重要的結(jié)論。PRE_ROUTING是在ip_rcv函數(shù)中被調(diào)用的,即ip層封包的入口處。

連接跟蹤的初始化

通過上文,我們知道在PRE_ROUTING處注冊了2個(gè)函數(shù)(參照給出的第三個(gè)博客)。分別為ipv4_conntrack_defrag和ipv4_conntrack_in。并且ipv4_conntrack_defrag優(yōu)先級更高(netfilter注冊的每一條規(guī)則都是有優(yōu)先級的,由priority字段指定)。
在這里我就直接給出結(jié)論了。ipv4_conntrack_defrag函數(shù)主要是檢測是否被分片,如果被分片就重組。而連接跟蹤的建立實(shí)際上在第二個(gè)函數(shù)ipv4_conntrack_in中。ipv4_conntrack_in實(shí)際調(diào)用了nf_conntrack_in函數(shù)。


關(guān)于nf_conntrack_in函數(shù)的分析,可以參考https://blog.csdn.net/City_of_skey/article/details/84934016
http://blog.chinaunix.net/uid-26517122-id-4293135.html
在這里我就不在詳細(xì)介紹了。在這里只是說明幾個(gè)點(diǎn)。

  • ipv4_conntrack_in函數(shù)會調(diào)用resolve_normal_ct函數(shù)
  • resolve_normal_ct函數(shù)會判斷連接跟蹤是否存在,不存在就去創(chuàng)建。然后設(shè)置連接的狀態(tài)

在這里我只分析到連接跟蹤的建立過程。因?yàn)槲覀冏罱K的快轉(zhuǎn)模塊在需要利用到連接跟蹤的建立。

連接跟蹤發(fā)送tuple和接收tuple

這里我還是以畫圖的方式來講解一下發(fā)送和接收tuple的變化過程。這個(gè)過程非常重要。希望大家能夠理解
現(xiàn)在在這里假設(shè)我們的pc 192.168.100.100 訪問百度 14.215.177.38網(wǎng)頁
路由器的lan口ip地址為192.168.100.254, 路由器wan口的ip地址為192.168.1.2。

發(fā)送tuple

根據(jù)我們上面講解的。首先我們的pc發(fā)送的數(shù)據(jù)包到達(dá)路由器。由路由器的ip_rcv函數(shù)接收。這因?yàn)槭莻€(gè)新的數(shù)據(jù)包。所以會創(chuàng)建一條新的連接跟蹤

這里的發(fā)送tuple正如上圖所示。

接收tuple

在這里我需要提醒一下大家,ip_rcv新建tuple。ip_rcv是在ip層數(shù)據(jù)包入口處。那么此時(shí)肯定沒有經(jīng)過nat轉(zhuǎn)換。接收的tuple會有一點(diǎn)出乎大家的意料。說實(shí)話最開始我也難以理解。大家理解是在nat之前,ip_rcv新建的就不難了

當(dāng)pc訪問到百度服務(wù)器的時(shí)候。百度服務(wù)器會將數(shù)據(jù)回復(fù)到我們。這個(gè)時(shí)候就會新建接收的tuple。

此時(shí)接收tuple的源地址變成了百度的。目的地址不再是路由器lan口的地址。而是wan口的地址。源端口號,目的端口號也發(fā)生了改變。只有當(dāng)該數(shù)據(jù)包經(jīng)過nat轉(zhuǎn)發(fā)之后,目的地址才會編程pc的ip地址。目的端口號編程以前的5678。這一點(diǎn)大家必須注意。


連接跟蹤中的helper

在這里還需要引入一個(gè)新的模塊-期望連接(helper)。那么該模塊有什么用呢?其實(shí)是為了根據(jù)現(xiàn)存的一個(gè)連接去創(chuàng)建一個(gè)新的連接。比如我們用的FTP協(xié)議。FTP協(xié)議使用了兩個(gè)端口號(20 21)。為了將這兩個(gè)端口號聯(lián)系起來。同時(shí)也是將20和21創(chuàng)建的連接跟蹤進(jìn)行一個(gè)綁定。所以內(nèi)核設(shè)計(jì)出來一套期望連接的模塊
關(guān)于期望連接,內(nèi)核最經(jīng)典的即是FTP的實(shí)現(xiàn)。這里貼出兩篇博客,希望對大家有用
https://blog.csdn.net/a1558451960/article/details/95329827
https://blog.csdn.net/jasonchen_gbd/article/details/44877343?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf

結(jié)束語

在下一篇博客中,我就會上實(shí)際的代碼。到時(shí)候我們再來分析。怎樣實(shí)現(xiàn)我們的快轉(zhuǎn)模塊。歡迎大家一起交流。歡迎加入qq群:610849576

總結(jié)

以上是生活随笔為你收集整理的协议栈数据包快速转发的实现(2)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。