訂閱
糾錯(cuò)
加入自媒體

Linux內(nèi)核源代碼:tcp/ip協(xié)議棧的調(diào)用

這里共維護(hù)了三個(gè)隊(duì)列:prequeue、backlog、receive_queue,分別為預(yù)處理隊(duì)列,后備隊(duì)列和接收隊(duì)列,在連接建立后,若沒(méi)有數(shù)據(jù)到來(lái),接收隊(duì)列為空,進(jìn)程會(huì)在sk_busy_loop函數(shù)內(nèi)循環(huán)等待,知道接收隊(duì)列不為空,并調(diào)用函數(shù)數(shù)skb_copy_datagram_msg將接收到的數(shù)據(jù)拷貝到用戶態(tài),實(shí)際調(diào)用的是__skb_datagram_iter,這里同樣用了struct msghdr *msg來(lái)實(shí)現(xiàn)。__skb_datagram_iter函數(shù)如下:

int __skb_datagram_iter(const struct sk_buff *skb, int offset,
struct iov_iter *to, int len, bool fault_short,
           size_t (*cb)(const void *, size_t, void *, struct iov_iter *),
           void *data)

int start = skb_h(yuǎn)eadlen(skb);
int i, copy = start - offset, start_off = offset, n;
struct sk_buff *frag_iter;
拷貝tcp頭部
if (copy > 0) {
if (copy > len)
copy = len;
       n = cb(skb->data + offset, copy, data, to);
       offset += n;
if (n 。 copy)
goto short_copy;
if ((len -= copy) == 0)
return 0;
   }
拷貝數(shù)據(jù)部分
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
       WARN_ON(start > offset + len);
       end = start + skb_frag_size(frag);
if ((copy = end - offset) > 0) {
struct page *page = skb_frag_page(frag);
           u8 *vaddr = kmap(page);
if (copy > len)
copy = len;
           n = cb(vaddr + frag->page_offset +
               offset - start, copy, data, to);
           kunmap(page);
           offset += n;
if (n 。 copy)
goto short_copy;
if (。╨en -= copy))
return 0;
       }
       start = end;
   }

拷貝完成后,函數(shù)返回,整個(gè)接收的過(guò)程也就完成了。
用一張函數(shù)間的相互調(diào)用圖可以表示:

通過(guò)gdb調(diào)試驗(yàn)證如下:

Breakpoint 1, __sys_recvfrom (fd=5, ubuf=0x7ffd9428d960, size=1024, flags=0,
addr=0x0 <fixed_percpu_data>, addr_len=0x0 <fixed_percpu_data>)
at net/socket.c:1990
1990    {
(gdb) c
Continuing.
Breakpoint 2, sock_recvmsg (sock=0xffff888006df1900, msg=0xffffc900001f7e28,
flags=0) at net/socket.c:891
891    {
(gdb) c
Continuing.
Breakpoint 3, tcp_recvmsg (sk=0xffff888006479100, msg=0xffffc900001f7e28,
len=1024, nonblock=0, flags=0, addr_len=0xffffc900001f7df4)
at net/ipv4/tcp.c:1933
1933    {
(gdb) cBreakpoint 1, __sys_recvfrom (fd=5, ubuf=0x7ffd9428d960, size=1024, flags=0,
addr=0x0 <fixed_percpu_data>, addr_len=0x0 <fixed_percpu_data>)
at net/socket.c:1990
1990    {
(gdb) c
Continuing.
Breakpoint 2, sock_recvmsg (sock=0xffff888006df1900, msg=0xffffc900001f7e28,
flags=0) at net/socket.c:891
891    {
(gdb) c
Continuing.
Breakpoint 3, tcp_recvmsg (sk=0xffff888006479100, msg=0xffffc900001f7e28,
len=1024, nonblock=0, flags=0, addr_len=0xffffc900001f7df4)
at net/ipv4/tcp.c:1933
1933    {
(gdb) c
Continuing.
Breakpoint 4, __skb_datagram_iter (skb=0xffff8880068714e0, offset=0,
to=0xffffc900001efe38, len=2, fault_short=false,
cb=0xffffffff817ff860 <simple_copy_to_iter>, data=0x0 <fixed_percpu_data>)
at net/core/datagram.c:414
414    {

符合我們之前的分析。

5 IP層流程

5.1 發(fā)送端

網(wǎng)絡(luò)層的任務(wù)就是選擇合適的網(wǎng)間路由和交換結(jié)點(diǎn), 確保數(shù)據(jù)及時(shí)傳送。網(wǎng)絡(luò)層將數(shù)據(jù)鏈路層提供的幀組成數(shù)據(jù)包,包中封裝有網(wǎng)絡(luò)層包頭,其中含有邏輯地址信息- -源站點(diǎn)和目的站點(diǎn)地址的網(wǎng)絡(luò)地址。其主要任務(wù)包括 (1)路由處理,即選擇下一跳 (2)添加 IP header(3)計(jì)算 IP header checksum,用于檢測(cè) IP 報(bào)文頭部在傳播過(guò)程中是否出錯(cuò) (4)可能的話,進(jìn)行 IP 分片(5)處理完畢,獲取下一跳的 MAC 地址,設(shè)置鏈路層報(bào)文頭,然后轉(zhuǎn)入鏈路層處理。

IP 頭:

IP 棧基本處理過(guò)程如下圖所示:

首先,ip_queue_xmit(skb)會(huì)檢查skb->dst路由信息。如果沒(méi)有,比如套接字的第一個(gè)包,就使用ip_route_output()選擇一個(gè)路由。

接著,填充IP包的各個(gè)字段,比如版本、包頭長(zhǎng)度、TOS等。

中間的一些分片等,可參閱相關(guān)文檔。基本思想是,當(dāng)報(bào)文的長(zhǎng)度大于mtu,gso的長(zhǎng)度不為0就會(huì)調(diào)用 ip_fragment 進(jìn)行分片,否則就會(huì)調(diào)用ip_finish_output2把數(shù)據(jù)發(fā)送出去。ip_fragment 函數(shù)中,會(huì)檢查 IP_DF 標(biāo)志位,如果待分片IP數(shù)據(jù)包禁止分片,則調(diào)用 icmp_send()向發(fā)送方發(fā)送一個(gè)原因?yàn)樾枰制O(shè)置了不分片標(biāo)志的目的不可達(dá)ICMP報(bào)文,并丟棄報(bào)文,即設(shè)置IP狀態(tài)為分片失敗,釋放skb,返回消息過(guò)長(zhǎng)錯(cuò)誤碼。

接下來(lái)就用 ip_finish_ouput2 設(shè)置鏈路層報(bào)文頭了。如果,鏈路層報(bào)頭緩存有(即hh不為空),那就拷貝到skb里。如果沒(méi),那么就調(diào)用neigh_resolve_output,使用 ARP 獲取。

具體代碼分析如下:

入口函數(shù)是ip_queue_xmit,函數(shù)如下:

發(fā)現(xiàn)調(diào)用了__ip_queue_xmit函數(shù):

發(fā)現(xiàn)調(diào)用了skb_rtable函數(shù),實(shí)際上是開(kāi)始找路由緩存,繼續(xù)看:

發(fā)現(xiàn)調(diào)用ip_local_out進(jìn)行數(shù)據(jù)發(fā)送:

發(fā)現(xiàn)調(diào)用__ip_local_out函數(shù):

發(fā)現(xiàn)返回一個(gè)nf_h(yuǎn)ook函數(shù),里面調(diào)用了dst_output,這個(gè)函數(shù)實(shí)質(zhì)上是調(diào)用ip_finish__output函數(shù):

發(fā)現(xiàn)調(diào)用__ip_finish_output函數(shù):

如果分片就調(diào)用ip_fragment,否則就調(diào)用IP_finish_output2函數(shù):

在構(gòu)造好 ip 頭,檢查完分片之后,會(huì)調(diào)用鄰居子系統(tǒng)的輸出函數(shù) neigh_output 進(jìn)行輸 出。neigh_output函數(shù)如下:

輸出分為有二層頭緩存和沒(méi)有兩種情況,有緩存時(shí)調(diào)用 neigh_h(yuǎn)h_output 進(jìn)行快速輸 出,沒(méi)有緩存時(shí),則調(diào)用鄰居子系統(tǒng)的輸出回調(diào)函數(shù)進(jìn)行慢速輸出。這個(gè)函數(shù)如下:

最后調(diào)用dev_queue_xmit函數(shù)進(jìn)行向鏈路層發(fā)送包,到此結(jié)束。gdb驗(yàn)證如下:

5.2 接收端

IP 層的入口函數(shù)在 ip_rcv 函數(shù)。該函數(shù)首先會(huì)做包括 package checksum 在內(nèi)的各種檢查,如果需要的話會(huì)做 IP defragment(將多個(gè)分片合并),然后 packet 調(diào)用已經(jīng)注冊(cè)的 Pre-routing netfilter hook ,完成后最終到達(dá) ip_rcv_finish 函數(shù)。

ip_rcv_finish 函數(shù)會(huì)調(diào)用 ip_router_input 函數(shù),進(jìn)入路由處理環(huán)節(jié)。它首先會(huì)調(diào)用 ip_route_input 來(lái)更新路由,然后查找 route,決定該 package 將會(huì)被發(fā)到本機(jī)還是會(huì)被轉(zhuǎn)發(fā)還是丟棄:

如果是發(fā)到本機(jī)的話,調(diào)用 ip_local_deliver 函數(shù),可能會(huì)做 de-fragment(合并多個(gè) IP packet),然后調(diào)用 ip_local_deliver 函數(shù)。該函數(shù)根據(jù) package 的下一個(gè)處理層的 protocal number,調(diào)用下一層接口,包括 tcp_v4_rcv (TCP), udp_rcv (UDP),icmp_rcv (ICMP),igmp_rcv(IGMP)。對(duì)于 TCP 來(lái)說(shuō),函數(shù) tcp_v4_rcv 函數(shù)會(huì)被調(diào)用,從而處理流程進(jìn)入 TCP 棧。

如果需要轉(zhuǎn)發(fā) (forward),則進(jìn)入轉(zhuǎn)發(fā)流程。該流程需要處理 TTL,再調(diào)用 dst_input 函數(shù)。該函數(shù)會(huì)

(1)處理 Netfilter Hook

(2)執(zhí)行 IP fragmentation

(3)調(diào)用 dev_queue_xmit,進(jìn)入鏈路層處理流程。

接收相對(duì)簡(jiǎn)單,入口在ip_rcv,這個(gè)函數(shù)如下:

里面調(diào)用ip_rcv_finish函數(shù):

發(fā)現(xiàn)調(diào)用dst_input函數(shù),實(shí)際上是調(diào)用ip_local_deliver函數(shù):

如果分片,就調(diào)用ip_defrag函數(shù),沒(méi)有則調(diào)用ip_local_deliver_finish函數(shù):

發(fā)現(xiàn)調(diào)用ip_protocol_deliver_rcu函數(shù):

調(diào)用完畢之后進(jìn)入tcp棧,調(diào)用完畢,通過(guò)gdb驗(yàn)證如下:

聲明: 本文由入駐維科號(hào)的作者撰寫(xiě),觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

    掃碼關(guān)注公眾號(hào)
    OFweek人工智能網(wǎng)
    獲取更多精彩內(nèi)容
    文章糾錯(cuò)
    x
    *文字標(biāo)題:
    *糾錯(cuò)內(nèi)容:
    聯(lián)系郵箱:
    *驗(yàn) 證 碼:

    粵公網(wǎng)安備 44030502002758號(hào)