小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

十年碼農(nóng)內(nèi)功:收包(二)

 天選小丑 2023-08-25 發(fā)布于廣西

基于 Linux 內(nèi)核 6.0、64 位系統(tǒng)和 Intel 網(wǎng)卡驅(qū)動(dòng) igb。由于篇幅過(guò)長(zhǎng)切分多篇,前一篇地址《十年碼農(nóng)內(nèi)功:收包(一)》,下一篇是《十年碼農(nóng)內(nèi)功:收包(三)》,所有參考內(nèi)容在最后一篇。

Image

整體流程圖

三、網(wǎng)絡(luò)接口層

3.1 概述

數(shù)據(jù)包在本層主要處理流程有五:

  1. 網(wǎng)卡收到數(shù)據(jù)包,DMA 方式寫(xiě)入Ring Buffer,發(fā)出硬中斷;

  2. 內(nèi)核收到硬中斷,NAPI 加入本 CPU 的輪詢列表,發(fā)出軟中斷;

  3. 內(nèi)核收到軟中斷,輪詢 NAPI 并執(zhí)行poll函數(shù)從Ring Buffer取數(shù)據(jù);

  4. GRO 操作(默認(rèn)開(kāi)啟),合并多個(gè)數(shù)據(jù)包為一個(gè)數(shù)據(jù)包,如果 RPS 關(guān)閉,則把數(shù)據(jù)包遞交到協(xié)議棧;

  5. RPS 操作(默認(rèn)關(guān)閉),如果開(kāi)啟,使數(shù)據(jù)包通過(guò)別的(也可能是當(dāng)前的) CPU 遞交到協(xié)議棧;

Image

圖4 L1流程圖

Image

圖5 L1調(diào)用鏈

3.1.1 Ring Buffer

這一層里有一個(gè)大名鼎鼎的數(shù)據(jù)結(jié)構(gòu)Ring Buffer,后面統(tǒng)稱它的中文名字叫環(huán)形緩沖區(qū)。

接收Ring Buffer實(shí)際上有兩個(gè)環(huán)形隊(duì)列,一個(gè)是 CPU 使用的rx_buffer_info數(shù)組,數(shù)組元素是rx_buffer,另一個(gè)是網(wǎng)卡硬件使用的desc數(shù)組,元素是rx_desc,它們都存儲(chǔ)在主內(nèi)存上的內(nèi)核空間。igb網(wǎng)卡驅(qū)動(dòng)中rx_bufferigb_rx_buffer,rx_desce1000_adv_rx_desc

igb_rx_buffer 結(jié)構(gòu)體代碼如下,結(jié)構(gòu)圖如圖2:

struct igb_rx_buffer {
/* DMA 地址 */
dma_addr_t dma
;
/* 物理頁(yè),與 dma 指向同一個(gè)內(nèi)存區(qū)域 */
struct page *page;
#if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
__u32 page_offset
;
#else
__u16 page_offset
;
#endif
__u16 pagecnt_bias
;
};

Image

圖6

e1000_adv_rx_desc 結(jié)構(gòu)體代碼如下,結(jié)構(gòu)圖如圖3:

union e1000_adv_rx_desc {
struct {
__le64 pkt_addr
; /* Packet buffer address */
__le64 hdr_addr
; /* Header buffer address */
} read;
struct {
struct {
struct {
__le16 pkt_info
; /* RSS type, Packet type */
__le16 hdr_info
; /* Split Head, buf len */
} lo_dword;
union {
__le32 rss
; /* RSS Hash */
struct {
__le16 ip_id
; /* IP id */
__le16 csum
; /* Packet Checksum */
} csum_ip;
} hi_dword;
} lower;
struct {
__le32 status_error
; /* ext status/error */
__le16 length
; /* Packet length */
__le16 vlan
; /* VLAN tag */
} upper;
} wb; /* writeback */
};

Image

圖7

3.2 網(wǎng)卡收到數(shù)據(jù)包

網(wǎng)卡收到數(shù)據(jù)包后,通過(guò) DMA 寫(xiě)入Ring Bufferrx_ring)內(nèi)rx_buffer_info數(shù)組的下一個(gè)可用元素(igb_rx_buffer)的 dma 指向的內(nèi)核內(nèi)存,dma實(shí)際是網(wǎng)卡可以使用的總線地址,一個(gè)網(wǎng)絡(luò)幀可能占用多個(gè)igb_rx_buffer。

這是第一次復(fù)制,從網(wǎng)卡到 Ring Buffer 的復(fù)制。

3.3 內(nèi)核收到硬中斷

復(fù)制完后,如果硬中斷沒(méi)有被關(guān)閉,則網(wǎng)卡發(fā)出硬中斷。假設(shè)啟動(dòng)時(shí)硬中斷類型選擇的是MSI-X,那么硬中斷注冊(cè)的函數(shù)就 igb_msix_ring,從下面的代碼可以看出,硬中斷處理函數(shù)邏輯非常簡(jiǎn)單,僅僅調(diào)用了 igb_write_itrnapi_schedule 兩個(gè)函數(shù),igb_msix_ring 函數(shù)代碼如下:

static irqreturn_t igb_msix_ring(int irq, void *data)
{
struct igb_q_vector *q_vector = data;
/* Write the ITR value calculated from the previous interrupt. */
igb_write_itr(q_vector);
napi_schedule(&q_vector->napi);

return IRQ_HANDLED;
}

igb_write_itr 負(fù)責(zé)更新特定的硬件中的寄存器,而 napi_schedule 才是重點(diǎn)工作,負(fù)責(zé)調(diào)度 NAPI,napi_schedule 調(diào)用__napi_schedule 再調(diào)用 ____napi_schedule 函數(shù),后者部分代碼如下:

static inline void ____napi_schedule(struct softnet_data *sd, struct napi_struct *napi)
{
list_add_tail(&napi->poll_list, &sd->poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

其主要邏輯有二:

  1. napi_struct結(jié)構(gòu)體的poll_list添加到當(dāng)前 CPU 所關(guān)聯(lián)的softnet_data結(jié)構(gòu)體的poll_list鏈表尾部;

  2. 然后調(diào)用 __raise_softirq_irqoff 函數(shù)觸發(fā)NET_RX_SOFTIRQ軟中斷,從而內(nèi)核執(zhí)行網(wǎng)絡(luò)子系統(tǒng)初始化時(shí)注冊(cè)的 net_rx_action 軟中斷處理函數(shù)。

3.4 內(nèi)核收到軟中斷

處理硬中斷的 CPU 同樣也會(huì)執(zhí)行該硬中斷觸發(fā)的軟中斷注冊(cè)的處理函數(shù),軟中斷函數(shù) net_rx_actionksoftirqd內(nèi)核進(jìn)程執(zhí)行。net_rx_action 遍歷當(dāng)前 CPU 隊(duì)列中的 NAPI 列表,依次取出列表中的 NAPI 結(jié)構(gòu)對(duì)其進(jìn)行 napi_poll 操作。net_rx_action 函數(shù)非常重要。

3.4.1 net_rx_action 函數(shù)

// 部分代碼
static __latent_entropy void net_rx_action(struct softirq_action *h)
{
struct softnet_data *sd = this_cpu_ptr(&softnet_data);
unsigned long time_limit = jiffies + usecs_to_jiffies(READ_ONCE(netdev_budget_usecs));
int budget = READ_ONCE(netdev_budget);
LIST_HEAD(list);
LIST_HEAD(repoll);

local_irq_disable();
list_splice_init(&sd->poll_list, &list);
local_irq_enable();

for (;;) {
struct napi_struct *n;

skb_defer_free_flush(sd);

if (list_empty(&list)) {
if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))
goto end;
break;
}

n
= list_first_entry(&list, struct napi_struct, poll_list);
budget
-= napi_poll(n, &repoll);

/* If softirq window is exhausted then punt.
* Allow this to run for 2 jiffies since which will allow
* an average latency of 1.5/HZ.
*/

if (unlikely(budget <= 0 || time_after_eq(jiffies, time_limit))) {
sd
->time_squeeze++;
break;
}
}

local_irq_disable();

list_splice_tail_init(&sd->poll_list, &list);
list_splice_tail(&repoll, &list);
list_splice(&list, &sd->poll_list);
if (!list_empty(&sd->poll_list))
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
/* 通過(guò) smp_call_function_single_async 遠(yuǎn)程激活 sd->rps_ipi_list 中的其他 CPU 的軟中斷,
* 使其他 CPU 執(zhí)行初始化時(shí)注冊(cè)的軟中斷函數(shù) csd = rps_trigger_softirq 來(lái)處理數(shù)據(jù)包 */

net_rps_action_and_irq_enable(sd);
end
:;
}

napi_poll 函數(shù)調(diào)用 __napi_poll 函數(shù)對(duì)napi_struct進(jìn)行操作,然后判斷napi_struct是否加回poll_list列表尾部,budget是控制消費(fèi)rx_buffer的數(shù)量,避免 CPU 一直被軟中斷占用。

3.4.2 __napi_poll 函數(shù)

// 部分代碼
static int __napi_poll(struct napi_struct *n, bool *repoll)
{
int work, weight;
weight
= n->weight;

work
= 0;
if (test_bit(NAPI_STATE_SCHED, &n->state)) {
work
= n->poll(n, weight);
trace_napi_poll(n, work, weight);
}

if (likely(work < weight))
return work;

if (unlikely(napi_disable_pending(n))) {
napi_complete(n);
return work;
}

*repoll = true;

return work;
}

上面代碼中weight代表 RX 隊(duì)列的處理優(yōu)先級(jí)(網(wǎng)卡驅(qū)動(dòng)對(duì)應(yīng)權(quán)重是固定的 64),napi_struct里的poll函數(shù)被調(diào)用,igb驅(qū)動(dòng)對(duì)應(yīng)的是先前注冊(cè)的 igb_poll 函數(shù)。

3.4.3 igb_poll 函數(shù)

// 部分代碼
static int igb_poll(struct napi_struct *napi, int budget)
{
struct igb_q_vector *q_vector = container_of(napi, struct igb_q_vector, napi);
bool clean_complete
= true;
int work_done = 0;

#ifdef CONFIG_IGB_DCA
if (q_vector->adapter->flags & IGB_FLAG_DCA_ENABLED)
igb_update_dca(q_vector);
#endif
if (q_vector->tx.ring)
clean_complete
= igb_clean_tx_irq(q_vector, budget);
if (q_vector->rx.ring) {
int cleaned = igb_clean_rx_irq(q_vector, budget);
work_done
+= cleaned;
if (cleaned >= budget)
clean_complete
= false;
}
/* If all work not completed, return budget and keep polling */
if (!clean_complete)
return budget;
/* Exit the polling mode, but don't re-enable interrupts if stack might
* poll us due to busy-polling
*/

if (likely(napi_complete_done(napi, work_done)))
igb_ring_irq_enable(q_vector);

return work_done;
}

其主要邏輯如下:

  1. 如果內(nèi)核支持 DCA(Direct Cache Access),CPU 緩存命中率將會(huì)提升;

  2. 調(diào)用 igb_clean_rx_irq 循環(huán)處理數(shù)據(jù)包,直到處理完畢或者budget耗盡,下面詳細(xì)解讀;

  3. 檢查clean_complete判斷是否所有的工作已經(jīng)完成;

  4. 如果不是,返回剩下的budget值;

  5. 否則調(diào)用 napi_complete_done 函數(shù)繼續(xù)處理。

  6. 調(diào)用 gro_normal_list 函數(shù),因?yàn)閿?shù)據(jù)包處理完了,及時(shí)把 igb_clean_rx_irq 處理完的多個(gè)包一次性送到協(xié)議棧;

  7. 然后檢查 NAPI 的poll_list是否都處理完,如果是則關(guān)閉 NAPI,并通過(guò) igb_ring_irq_enable 重新打開(kāi)硬中斷,以保證下次中斷會(huì)重新打開(kāi) NAPI。

3.4.4 igb_clean_rx_irq 函數(shù)

//部分代碼
static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)
{
struct igb_adapter *adapter = q_vector->adapter;
struct igb_ring *rx_ring = q_vector->rx.ring;
struct sk_buff *skb = rx_ring->skb;
unsigned int total_bytes = 0, total_packets = 0;
u16 cleaned_count
= igb_desc_unused(rx_ring);
int rx_buf_pgcnt;

while (likely(total_packets < budget)) {
union e1000_adv_rx_desc *rx_desc;
struct igb_rx_buffer *rx_buffer;
ktime_t timestamp
= 0;
unsigned int size;
/* return some buffers to hardware, one at a time is too slow */
if (cleaned_count >= IGB_RX_BUFFER_WRITE) {
/* 1 */
igb_alloc_rx_buffers(rx_ring, cleaned_count);
cleaned_count
= 0;
}
/* 2 */
rx_desc
= IGB_RX_DESC(rx_ring, rx_ring->next_to_clean);
size
= le16_to_cpu(rx_desc->wb.upper.length);
/* 3 */
rx_buffer
= igb_get_rx_buffer(rx_ring, size, &rx_buf_pgcnt);
/* 4 retrieve a buffer from the ring */
if (!skb) {
unsigned char *hard_start = pktbuf - igb_rx_offset(rx_ring);
unsigned int offset = pkt_offset + igb_rx_offset(rx_ring);

xdp_prepare_buff(&xdp, hard_start, offset, size, true);
xdp_buff_clear_frags_flag(&xdp);
#if (PAGE_SIZE > 4096)
/* At larger PAGE_SIZE, frame_sz depend on len size */
xdp
.frame_sz = igb_rx_frame_truesize(rx_ring, size);
#endif
skb
= igb_run_xdp(adapter, rx_ring, &xdp);
}
/* 5 retrieve a buffer from the ring */
if (skb)
igb_add_rx_frag(rx_ring, rx_buffer, skb, size);
else if (ring_uses_build_skb(rx_ring))
skb
= igb_build_skb(rx_ring, rx_buffer, &xdp, timestamp);
else
skb
= igb_construct_skb(rx_ring, rx_buffer, &xdp, timestamp);
/* 6 */
igb_put_rx_buffer(rx_ring, rx_buffer, rx_buf_pgcnt);
cleaned_count
++;
/* 7 fetch next buffer in frame if non-eop */
if (igb_is_non_eop(rx_ring, rx_desc))
continue;
/* 8 verify the packet layout is correct */
if (igb_cleanup_headers(rx_ring, rx_desc, skb)) {
skb
= NULL;
continue;
}
/* 9 probably a little skewed due to removing CRC */
total_bytes
+= skb->len;
/* 10 populate checksum, timestamp, VLAN, and protocol */
igb_process_skb_fields(rx_ring, rx_desc, skb);
/* 11 GRO,合并數(shù)據(jù)包 */
napi_gro_receive(&q_vector->napi, skb);
/* reset skb pointer */
skb
= NULL;
/* 12 update budget accounting */
total_packets
++;
}
/* place incomplete frames back on ring for completion */
rx_ring
->skb = skb;
if (cleaned_count)
igb_alloc_rx_buffers(rx_ring, cleaned_count);

return total_packets;
}

igb_clean_rx_irq 函數(shù)中的 while 循環(huán)完成下面操作:

  1. 首先申請(qǐng)一批 rx_buffer 和 rx_desc,通常 IGB_RX_BUFFER_WRITE(16)個(gè),避免一個(gè)個(gè)申請(qǐng),效率低,操作由 igb_alloc_rx_buffers 函數(shù)完成:使用 dev_alloc_pages 申請(qǐng)新的物理頁(yè)保存到 rx_buffer->page,然后通過(guò) dma_map_page_attrs 將 page 映射結(jié)果保存到 rx_buffer->dma ;修改 rx_desc->read.pkt_addr(rx_buffer->dma + rx_buffer->page_offset),rx_desc->wb.upper.length = 0,方便網(wǎng)卡將收到的數(shù)據(jù)包 DMA 到 rx_desc->read.pkt_addr 地址,這是第一次復(fù)制,從網(wǎng)卡到 Ring Buffer 的復(fù)制;

  2. 從 Ring Buffer 中取出下一個(gè)可讀位置(next_to_clean)的 rx_desc,檢查它狀態(tài)是否正常,然后從 rx_desc 獲取接收的數(shù)據(jù) buffer 大小(wb.upper.length);

  3. 通過(guò) igb_get_rx_buffer 函數(shù)將下一個(gè)可讀位置(next_to_clean)的 rx_buffer 獲取到;

  4. 計(jì)算數(shù)據(jù)包開(kāi)始地址,page_address(rx_buffer->page) + rx_buffer->page_offset,轉(zhuǎn)換成 xdp_buff 地址,然后交給 BPF 的 xdp 處理;

  5. 內(nèi)核把 rx_buffer 的 page(物理頁(yè))對(duì)應(yīng)的 buffer 數(shù)據(jù)拷貝到 Ring Buffer 的 skb(sk_buff)中,然后把 skb 直接傳給協(xié)議棧,這是第二次復(fù)制,從 Ring Buffer 到網(wǎng)絡(luò)協(xié)議棧的復(fù)制。為了減少?gòu)?fù)制次數(shù),skb 直到上層處理完以后才會(huì)被 __kfree_skb 釋放;

  6. 通過(guò) igb_put_rx_buffer 函數(shù)將 rx_buffer->page=NULL,如果可以重用,將 page、dma 等數(shù)據(jù)移動(dòng)到rx_ring->next_to_alloc 位置的 rx_buffer;反之,解除 DMA 映射,回收內(nèi)存;

  7. 通過(guò) igb_is_non_eop 函數(shù)檢查 rx_desc 是不是包含 eop(End of Packet),如果包含,說(shuō)明 skb 中已經(jīng)收錄一個(gè)完整的網(wǎng)絡(luò)包(幀);反之,需要獲取下一個(gè) rx_buffer 里的數(shù)據(jù)繼續(xù)復(fù)制到 skb 中直到 rx_desc 包含 eop;也就是說(shuō)一個(gè)網(wǎng)絡(luò)包(存儲(chǔ)在 skb 中)可能包含 1 個(gè)或多個(gè) rx_buffer 中的 buffer 數(shù)據(jù),也可以說(shuō) 1 個(gè) skb 對(duì)應(yīng) 1 個(gè)或多個(gè) Ring Buffer 隊(duì)列里連續(xù)的元素;

  8. 通過(guò) igb_cleanup_headers 檢查網(wǎng)絡(luò)包(skb)的頭部等信息是否正確;

  9. 把 skb 的長(zhǎng)度累計(jì)到 total_bytes,用于統(tǒng)計(jì)數(shù)據(jù);

  10. 調(diào)用 igb_process_skb_fields 設(shè)置skb 的 checksum、timestamp、VLAN 和 protocol 等信息,這些信息由硬件提供;

  11. 將構(gòu)建好的 skb 通過(guò) napi_gro_receive 函數(shù)上交到網(wǎng)絡(luò)協(xié)議棧,具體細(xì)節(jié)移步 2.4 章節(jié);

  12. 累加處理數(shù)據(jù)包個(gè)數(shù) total_packets,用于消耗 budget;

  13. 如果沒(méi)數(shù)據(jù)或者 budget 耗盡就退出循環(huán),否則回到 1;

上面第 5 步中,skb 的創(chuàng)建有兩種情況,當(dāng)網(wǎng)卡配置了 legacy 模式,使用 igb_build_skbnapi_build_skb)創(chuàng)建 skb 并復(fù)制 rx_buffer->page 數(shù)據(jù);否則使用 igb_construct_skbnapi_alloc_skb) 創(chuàng)建 skb 并復(fù)制 rx_buffer->page 數(shù)據(jù)。當(dāng) skb 不為空時(shí),就是前一個(gè)包被 GRO 合并了,使用 igb_add_rx_frag 復(fù)制數(shù)據(jù)。

budget 的大小會(huì)影響到 CPU 的利用率,當(dāng)數(shù)據(jù)包特別多的情況下,budget 越大可以減少數(shù)據(jù)包的延時(shí),但是會(huì)影響 CPU 處理其他任務(wù)。budget 默認(rèn) 300,可以調(diào)整使用下面命令修改:

$ sysctl -w net.core.netdev_budget=500

前面收包過(guò)程都是內(nèi)核跟網(wǎng)卡硬件和驅(qū)動(dòng)配合來(lái)完成的,也就是說(shuō)不同網(wǎng)卡收包的具體實(shí)現(xiàn)可能不同(同一家廠商的網(wǎng)卡的實(shí)現(xiàn)基本相同),但是大體實(shí)現(xiàn)思路上是一樣的,都是用到了 Ring Buffer、DMA、硬中斷和軟中斷等操作。

后面就是由內(nèi)核和用戶程序來(lái)完成了,與網(wǎng)卡沒(méi)有關(guān)系了。

3.5 GRO

3.5.1 概述

GRO(Generic Receive Offloading)是 LGO(Large Receive Offload,多數(shù)是在 NIC 上實(shí)現(xiàn)的一種硬件優(yōu)化機(jī)制)的一種軟件實(shí)現(xiàn),從而能讓所有 NIC 都支持這個(gè)功能。網(wǎng)絡(luò)上大部分 MTU 都是 1500 字節(jié),開(kāi)啟 Jumbo Frame 后能到 9000 字節(jié),如果發(fā)送的數(shù)據(jù)超過(guò) MTU 就需要切割成多個(gè)數(shù)據(jù)包。通過(guò)合并「足夠類似」的包來(lái)減少傳送給網(wǎng)絡(luò)協(xié)議棧的包數(shù),有助于減少 CPU 的使用量。GRO 使協(xié)議層只需處理一個(gè) header,而將包含大量數(shù)據(jù)的整個(gè)大包送到用戶程序。如果用tcpdump抓包看到機(jī)器收到了不現(xiàn)實(shí)的、非常大的包,這很可能是系統(tǒng)開(kāi)啟了 GRO。

GRO 和硬中斷合并的思想類似,不過(guò)階段不同。硬中斷合并是在中斷發(fā)起之前,而 GRO 已經(jīng)在軟中斷處理中了。

查看 GRO 是否開(kāi)啟命令:

$ ethtool -k eth0 | grep generic-receive-offload
generic-receive-offload: on

開(kāi)啟 GRO 命令:

$ ethtool -K eth0 gro on

napi_gro_receive 就是實(shí)現(xiàn) GRO 機(jī)制的入口函數(shù)之一。

3.5.2 napi_gro_receive 函數(shù)

// 部分代碼
gro_result_t
napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
gro_result_t ret
;

skb_mark_napi_id(skb, napi);
trace_napi_gro_receive_entry(skb);

skb_gro_reset_offset(skb, 0);

ret
= napi_skb_finish(napi, skb, dev_gro_receive(napi, skb));
trace_napi_gro_receive_exit(ret);

return ret;
}

其主要邏輯有二:

  1. 調(diào)用 dev_gro_receive 函數(shù)具體完成多個(gè)數(shù)據(jù)包的合并,即把skb加入到 NAPI 中,這個(gè)操作調(diào)用鏈很長(zhǎng),根據(jù)包類型 TCP/UDP 分別判斷數(shù)據(jù)包的完整性和判斷需不需要合并;

  2. 把上步的返回結(jié)果傳入 napi_skb_finish 函數(shù)繼續(xù)處理。

3.5.3 napi_skb_finish 函數(shù)

static gro_result_t napi_skb_finish(struct napi_struct *napi, struct sk_buff *skb, gro_result_t ret) {
switch (ret) {
case GRO_NORMAL:
gro_normal_one(napi, skb, 1);
break;

case GRO_MERGED_FREE:
if (NAPI_GRO_CB(skb)->free == NAPI_GRO_FREE_STOLEN_HEAD)
napi_skb_free_stolen_head(skb);
else if (skb->fclone != SKB_FCLONE_UNAVAILABLE)
__kfree_skb(skb);
else
__kfree_skb_defer(skb);
break;

case GRO_HELD:
case GRO_MERGED:
case GRO_CONSUMED:
break;
}

return ret;
}
  • 如果是 ret 是 GRO_MERGED_FREE,說(shuō)明 skb 已經(jīng)被合并,釋放 skb;

  • 如果是 ret 是 GRO_NORMAL,會(huì)調(diào)用 gro_normal_one,它會(huì)更新當(dāng)前 napi->rx_count 計(jì)數(shù), 當(dāng)數(shù)量足夠多時(shí),將調(diào)用 gro_normal_list 函數(shù),將多個(gè)包一次性送到協(xié)議棧。

3.5.4 gro_normal_one 函數(shù)

static inline void gro_normal_one(struct napi_struct *napi, struct sk_buff *skb, int segs) {
list_add_tail(&skb->list, &napi->rx_list);
napi
->rx_count += segs;
if (napi->rx_count >= READ_ONCE(gro_normal_batch))
gro_normal_list(napi);
}

這里的閾值gro_normal_batch默認(rèn)是 8,即攢夠 8 個(gè)數(shù)據(jù)包一起送到協(xié)議棧,可以通過(guò)sysctl修改,命令如下:

$ sysctl net.core.gro_normal_batch
net.core.gro_normal_batch = 8

3.5.5 gro_normal_list 函數(shù)

/* Pass the currently batched GRO_NORMAL SKBs up to the stack. */
static inline void gro_normal_list(struct napi_struct *napi)
{
if (!napi->rx_count) // 沒(méi)有包直接返回
return;
netif_receive_skb_list_internal(&napi->rx_list);
INIT_LIST_HEAD(&napi->rx_list); // 初始化 napi->rx_list
napi
->rx_count = 0; // 計(jì)數(shù)清零
}

到這里 GRO 的工作就完成了,然后經(jīng)過(guò) netif_receive_skb_list_internal 函數(shù)多層調(diào)用,最終調(diào)用 __netif_receive_skb_core 函數(shù)把數(shù)據(jù)包遞交網(wǎng)絡(luò)協(xié)議棧

3.5.6 napi_complete_done 函數(shù)

// 部分代碼
bool
napi_complete_done(struct napi_struct *n, int work_done)
{
unsigned long flags, val, new, timeout = 0;
bool ret
= true;

if (unlikely(n->state & (NAPIF_STATE_NPSVC | NAPIF_STATE_IN_BUSY_POLL)))
return false;

if (work_done) {
if (n->gro_bitmask)
timeout
= READ_ONCE(n->dev->gro_flush_timeout);
n
->defer_hard_irqs_count = READ_ONCE(n->dev->napi_defer_hard_irqs);
}
if (n->defer_hard_irqs_count > 0) {
n
->defer_hard_irqs_count--;
timeout
= READ_ONCE(n->dev->gro_flush_timeout);
if (timeout)
ret
= false;
}
if (n->gro_bitmask) {
napi_gro_flush(n, !!timeout);
}

gro_normal_list(n);
...
}

3.4.4 中提到過(guò),poll 函數(shù)(igb_poll)在檢查是否已經(jīng)將現(xiàn)有的所有數(shù)據(jù)包合并完成,如果完成了,則調(diào)用 napi_complete_done 函數(shù)直接調(diào)用 gro_normal_list 函數(shù),及時(shí)把 dev_gro_receive 處理完的多個(gè)包一次性送到協(xié)議棧;

3.6 RPS

3.6.1 概述

RPS(Receive Packet Steering)是 RSS 的一種軟件實(shí)現(xiàn)。

  • 因?yàn)槭擒浖?shí)現(xiàn)的,所以任何網(wǎng)卡都可以使用 RPS,單隊(duì)列和多隊(duì)列網(wǎng)卡都可以使用;

  • RPS 在數(shù)據(jù)包從 Ring Buffer 中取出來(lái)后開(kāi)始工作,將 Packet hash 到對(duì)應(yīng) CPU 的 backlog 中,并觸發(fā) IPI(Inter-processorInterrupt,進(jìn)程間中斷)告知目標(biāo) CPU 來(lái)處理 backlog。該 Packet 將被目標(biāo) CPU 交到協(xié)議棧。從而實(shí)現(xiàn)將負(fù)載分散到多個(gè) CPU 的目的;

  • 單隊(duì)列網(wǎng)卡使用 RPS 可以提升傳輸效率,多隊(duì)列網(wǎng)卡在硬中斷不均勻時(shí)同樣可以使用來(lái)提升效率;

IPI 既像軟件中斷又像硬件中斷,它的產(chǎn)生像軟件中斷,是在程序中用代碼發(fā)送的,而它的處理像硬件中斷

3.6.2 GRO 后執(zhí)行 RPS

GRO 機(jī)制的最后一個(gè)函數(shù) gro_normal_list 調(diào)用了 netif_receive_skb_list_internal 函數(shù),后者和 netif_receive_skb_internal 函數(shù)均有下面類似的代碼:

void netif_receive_skb_list_internal(struct list_head *head)
{
#ifdef CONFIG_RPS
if (static_branch_unlikely(&rps_needed)) {
list_for_each_entry_safe(skb, next, head, list) {
struct rps_dev_flow voidflow, *rflow = &voidflow;
/* 目標(biāo) CPU 的 id */
int cpu = get_rps_cpu(skb->dev, skb, &rflow);
if (cpu >= 0) {
/* Will be handled, remove from list */
skb_list_del_init(skb);
enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
}
}
}
#endif
}

上面代碼判斷是否設(shè)置了 RPS 對(duì)數(shù)據(jù)包進(jìn)行不同的處理:

  • 如果沒(méi)有配置 RPS,netif_receive_skb* 將數(shù)據(jù)包交到網(wǎng)絡(luò)協(xié)議棧;

  • 如果配置了 RPS,netif_receive_skb* 調(diào)用 get_rps_cpu 來(lái)計(jì)算網(wǎng)絡(luò)包的 hash 并決定壓入哪個(gè) CPU 的 backlog,具體壓入操作由 enqueue_to_backlog 函數(shù)完成。

3.6.3 壓入 backlog 隊(duì)列

enqueue_to_backlog 函數(shù)如下:

// 部分代碼
static int enqueue_to_backlog(struct sk_buff *skb, int cpu, unsigned int *qtail) {
enum skb_drop_reason reason;
struct softnet_data *sd;
unsigned long flags;
unsigned int qlen;

reason
= SKB_DROP_REASON_NOT_SPECIFIED;
sd
= &per_cpu(softnet_data, cpu);

rps_lock_irqsave(sd, &flags);
if (!netif_running(skb->dev))
goto drop;
qlen
= skb_queue_len(&sd->input_pkt_queue);
if (qlen <= READ_ONCE(netdev_max_backlog) && !skb_flow_limit(skb, qlen)) {
if (qlen) {
enqueue
:
__skb_queue_tail(&sd->input_pkt_queue, skb);
input_queue_tail_incr_save(sd, qtail);
rps_unlock_irq_restore(sd, &flags);
return NET_RX_SUCCESS;
}

if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state))
// 將目標(biāo) CPU 的 sd 掛到當(dāng)前 CPU 的 sd 的 rps_ipi_list 便于后續(xù)向目標(biāo) CPU 發(fā)送 IPI 信號(hào)。
napi_schedule_rps(sd);
goto enqueue;
}
reason
= SKB_DROP_REASON_CPU_BACKLOG;

drop
:
sd
->dropped++;
rps_unlock_irq_restore(sd, &flags);

dev_core_stats_rx_dropped_inc(skb->dev);
kfree_skb_reason(skb, reason);
return NET_RX_DROP;
}
  1. 當(dāng)目標(biāo) CPU 的sd(softnet_data )中input_pkt_queue隊(duì)列長(zhǎng)度同時(shí)不超過(guò)netdev_max_backlogflow limit的值,將skb數(shù)據(jù)包壓入input_pkt_queue,否則將會(huì)被丟棄。

  2. 調(diào)用 napi_schedule_rps,將目標(biāo) CPU 的sd掛到本 CPU 的sdrps_ipi_list便于后續(xù)向目標(biāo) CPU 發(fā)送 IPI 信號(hào);

  3. 當(dāng)返回到 net_rx_action 函數(shù)中,最后一步經(jīng)過(guò)調(diào)用鏈 net_rps_action_and_irq_enable -> net_rps_send_ipi -> smp_call_function_single_async 遠(yuǎn)程激活sd->rps_ipi_list中的其他 CPU 的軟中斷,使其他 CPU 執(zhí)行初始化時(shí)注冊(cè)的軟中斷函數(shù) csd = rps_trigger_softirq 來(lái)處理數(shù)據(jù)包;

  4. rps_trigger_softirq 函數(shù)將 backlog(napi)加入 poll_list 里,然后發(fā)出軟中斷信號(hào) NET_RX_SOFTIRQ

  5. 當(dāng)處理軟中斷函數(shù) net_rx_action 處理poll_list時(shí),backlogpollprocess_backlog 函數(shù),process_backlog 函數(shù)消費(fèi) CPU 的input_pkt_queue隊(duì)列數(shù)據(jù)包,經(jīng)過(guò) __netif_receive_skb 函數(shù)多層調(diào)用,最終也調(diào)用 __netif_receive_skb_core 函數(shù)把數(shù)據(jù)包遞交網(wǎng)絡(luò)協(xié)議棧。

偽文件 /proc/net/softnet_stat 的第 10 列記錄了每個(gè) CPU 收到了多少次 IPI。

Image

圖8

上面的命令最終會(huì)調(diào)用下面的 softnet_seq_show 函數(shù):

static int softnet_seq_show(struct seq_file *seq, void *v)
{
struct softnet_data *sd = v;
unsigned int flow_limit_count = 0;

#ifdef CONFIG_NET_FLOW_LIMIT
struct sd_flow_limit *fl;

rcu_read_lock();
fl
= rcu_dereference(sd->flow_limit);
if (fl)
flow_limit_count
= fl->count;
rcu_read_unlock();
#endif

seq_printf(seq, '%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n',
sd
->processed, sd->dropped, sd->time_squeeze, 0, 0, 0, 0,
0, /* was fastroute */
0, /* was cpu_collision */
sd
->received_rps, flow_limit_count, softnet_backlog_len(sd),
(int)seq->index);
return 0;
}

從代碼可以看出 IPI 的次數(shù)記錄在了 softnet_data 的 received_rps 里,除了 IPI 還有第 12 列的 backlog 隊(duì)列長(zhǎng)度。

3.7 遞交協(xié)議棧

如圖4 調(diào)用鏈所示,netif_receive_skb_list_internal 函數(shù)經(jīng)過(guò)多層調(diào)用,最后會(huì)執(zhí)行 __netif_receive_skb_core 函數(shù),這是網(wǎng)絡(luò)數(shù)據(jù)包接收的核心函數(shù),負(fù)責(zé)處理接收到的數(shù)據(jù)包并決定如何傳遞給上層協(xié)議處理。這里面做的事情非常多, 按順序包括:

  1. 準(zhǔn)備工作;

  2. XDP 處理;

  3. VLAN 標(biāo)記;

  4. TAP 處理;

  5. TC 處理;

  6. Netfilter 處理;

  7. 遞交協(xié)議棧。

有的網(wǎng)卡會(huì)在 poll 函數(shù)里調(diào)用 netif_receive_skb 將數(shù)據(jù)包交到上層網(wǎng)絡(luò)棧繼續(xù)處理。最后發(fā)現(xiàn)同樣會(huì)調(diào)用到 __netif_receive_skb_core 函數(shù)。

__netif_receive_skb_core 函數(shù)代碼如下:

static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc, struct packet_type **ppt_prev)
{
struct packet_type *ptype, *pt_prev;
rx_handler_func_t
*rx_handler;
struct sk_buff *skb = *pskb;
struct net_device *orig_dev;
bool deliver_exact
= false;
int ret = NET_RX_DROP;
__be16 type
;
// 檢查網(wǎng)絡(luò)包的時(shí)間戳。
net_timestamp_check(!READ_ONCE(netdev_tstamp_prequeue), skb);
// 跟蹤網(wǎng)絡(luò)數(shù)據(jù)包的接收過(guò)程,用于調(diào)試和性能分析。
trace_netif_receive_skb(skb);
// 將接收到的數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備指針保存到 orig_dev 變量中,以備后續(xù)使用。
orig_dev
= skb->dev;
// 重置網(wǎng)絡(luò)頭部的偏移量,使其指向正確的位置。
skb_reset_network_header(skb);
if (!skb_transport_header_was_set(skb))
//如果傳輸頭部未設(shè)置,則重置傳輸層頭部的偏移量,使其指向正確的位置。
skb_reset_transport_header(skb);
// 重置數(shù)據(jù)包的 MAC 長(zhǎng)度。
skb_reset_mac_len(skb);
// 將 pt_prev 變量初始化為空,用于存儲(chǔ)上一個(gè)處理函數(shù)。
pt_prev
= NULL;

another_round
: //這是一個(gè)標(biāo)簽,用于在處理過(guò)程中跳轉(zhuǎn)到此處重新執(zhí)行一輪處理。
// 設(shè)置數(shù)據(jù)包的 skb_iif 字段,表示skb 是從哪個(gè)網(wǎng)絡(luò)設(shè)備接收的。
skb
->skb_iif = skb->dev->ifindex;
// 增加當(dāng)前 CPU 上的 softnet_data.processed 字段的計(jì)數(shù)。
__this_cpu_inc(softnet_data.processed);
// 如果啟用了 Generic XDP(軟件實(shí)現(xiàn) XDP 功能),則調(diào)用do_xdp_generic()函數(shù)執(zhí)行 XDP 通用程序的處理。
if (static_branch_unlikely(&generic_xdp_needed_key)) {
int ret2;
migrate_disable();
ret2
= do_xdp_generic(rcu_dereference(skb->dev->xdp_prog), skb);
migrate_enable();
// 如果返回結(jié)果不是XDP_PASS,則將返回值設(shè)置為NET_RX_DROP并跳轉(zhuǎn)到標(biāo)簽out處。
if (ret2 != XDP_PASS) {
ret
= NET_RX_DROP;
goto out;
}
}
// 如果數(shù)據(jù)包是以太網(wǎng) VLAN 數(shù)據(jù)包,則調(diào)用skb_vlan_untag()函數(shù)將 VLAN 標(biāo)簽從數(shù)據(jù)包中移除。
if (eth_type_vlan(skb->protocol)) {
skb
= skb_vlan_untag(skb);
// 如果 skb 為空,則跳轉(zhuǎn)到 out 標(biāo)簽
if (unlikely(!skb))
goto out;
}
// 如果需要跳過(guò) TC 分類,則直接跳轉(zhuǎn)到 skip_classify 標(biāo)簽。
if (skb_skip_tc_classify(skb))
goto skip_classify;
// 如果 pfmemalloc 為 true,則跳轉(zhuǎn)到 skip_taps 標(biāo)簽。
if (pfmemalloc)
goto skip_taps;
// 這個(gè)循環(huán)遍歷全局的注冊(cè)的協(xié)議處理函數(shù) ptype_all 鏈表,依次調(diào)用 deliver_skb 函數(shù)傳遞數(shù)據(jù)包給每個(gè)注冊(cè)的協(xié)議處理程序。
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (pt_prev)
ret
= deliver_skb(skb, pt_prev, orig_dev); // 抓包:dev_add_pack(&po->prot_hook) 注冊(cè)的鉤子函數(shù)
pt_prev
= ptype;
}
// 這個(gè)循環(huán)遍歷接收數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備的協(xié)議處理函數(shù) ptype_all 鏈表,同樣依次調(diào)用 deliver_skb 函數(shù)傳遞數(shù)據(jù)包給每個(gè)注冊(cè)的協(xié)議處理程序。
list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) {
if (pt_prev)
ret
= deliver_skb(skb, pt_prev, orig_dev); // 抓包:dev_add_pack(&po->prot_hook) 注冊(cè)的鉤子函數(shù)
pt_prev
= ptype;
}

skip_taps
: // 如果是使用 goto 跳轉(zhuǎn)過(guò)來(lái)的,那跳過(guò)了抓包邏輯(libpcap、tcpdump 等)
#ifdef CONFIG_NET_INGRESS // 這部分代碼用于處理網(wǎng)絡(luò)數(shù)據(jù)包的入口(ingress)功能,即在數(shù)據(jù)包進(jìn)入網(wǎng)絡(luò)協(xié)議棧之前進(jìn)行處理。
// 如果需要進(jìn)行 TC ingress 處理
if (static_branch_unlikely(&ingress_needed_key)) {
bool another
= false;
// 跳過(guò) egress
nf_skip_egress(skb, true);
// 處理 ingress
skb
= sch_handle_ingress(skb, &pt_prev, &ret, orig_dev, &another);
// 如果還需要進(jìn)行下一輪處理,則跳轉(zhuǎn)到 another_round 標(biāo)簽
if (another) //TC BPF 優(yōu)化,通過(guò) another round 將包從宿主機(jī)網(wǎng)卡直接送到容器 netns 內(nèi)網(wǎng)卡 ?
goto another_round;
if (!skb)
goto out;
// 跳過(guò) egress
nf_skip_egress(skb, false);
// 處理 Netfilter ingress
if (nf_ingress(skb, &pt_prev, &ret, orig_dev) < 0)
goto out;
}
#endif
// 重置數(shù)據(jù)包的重定向標(biāo)志
skb_reset_redirect(skb);
skip_classify
: // 如果是使用 goto 跳轉(zhuǎn)過(guò)來(lái)的,那跳過(guò)了抓包、TC、Netfilter 邏輯
// 如果 pfmemalloc 為 true,并且 skb 沒(méi)有設(shè)置 pfmemalloc 協(xié)議,則跳轉(zhuǎn)到 drop 標(biāo)簽
if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
goto drop;
if (skb_vlan_tag_present(skb)) {
// 如果數(shù)據(jù)包中存在 VLAN 標(biāo)簽,則調(diào)用 deliver_skb() 函數(shù)將數(shù)據(jù)包傳遞給之前注冊(cè)的協(xié)議處理函數(shù)進(jìn)行處理
if (pt_prev) {
ret
= deliver_skb(skb, pt_prev, orig_dev);
pt_prev
= NULL;
}
// 調(diào)用 vlan_do_receive() 函數(shù)處理 VLAN 相關(guān)操作
if (vlan_do_receive(&skb))
goto another_round;
else if (unlikely(!skb))
goto out;
}
// 獲取接收該數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備的接收處理函數(shù)(rx_handler)
rx_handler
= rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
// 如果接收處理函數(shù)存在,則調(diào)用 deliver_skb() 函數(shù)將數(shù)據(jù)包傳遞給接收處理函數(shù)進(jìn)行處理
if (pt_prev) {
ret
= deliver_skb(skb, pt_prev, orig_dev);
pt_prev
= NULL;
}
// 根據(jù)接收處理函數(shù)的返回值,有不同的處理邏輯
switch (rx_handler(&skb)) {
case RX_HANDLER_CONSUMED:
ret
= NET_RX_SUCCESS;
goto out;
case RX_HANDLER_ANOTHER:
goto another_round;
case RX_HANDLER_EXACT:
deliver_exact
= true;
break;
case RX_HANDLER_PASS:
break;
default:
BUG();
}
}
// 如果存在 VLAN 標(biāo)簽,并且網(wǎng)絡(luò)設(shè)備不使用 DSA(Distributed Switch Architecture)
if (unlikely(skb_vlan_tag_present(skb)) && !netdev_uses_dsa(skb->dev)) {
check_vlan_id
:
if (skb_vlan_tag_get_id(skb)) {
// VLAN ID 非 0,并且無(wú)法找到 VLAN 設(shè)備
skb
->pkt_type = PACKET_OTHERHOST;
} else if (eth_type_vlan(skb->protocol)) {
// 外部頭部是 802.1P 帶有 VLAN 0,內(nèi)部頭部是 802.1Q 或 802.1AD,并且無(wú)法找到 VLAN ID 0 對(duì)應(yīng)的 VLAN 設(shè)備
__vlan_hwaccel_clear_tag(skb);
skb
= skb_vlan_untag(skb);
if (unlikely(!skb))
goto out;
if (vlan_do_receive(&skb))
goto another_round;
else if (unlikely(!skb))
goto out;
else
goto check_vlan_id;
}
__vlan_hwaccel_clear_tag(skb);
}
// 獲取數(shù)據(jù)包的協(xié)議類型
type
= skb->protocol;

if (likely(!deliver_exact))
// 如果沒(méi)有設(shè)置精確匹配,將調(diào)用 deliver_ptype_list_skb() 函數(shù)傳遞數(shù)據(jù)包給指定的注冊(cè)的協(xié)議處理函數(shù)處理。
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type, &ptype_base[ntohs(type) & PTYPE_HASH_MASK]);
// 調(diào)用 deliver_ptype_list_skb() 函數(shù)傳遞數(shù)據(jù)包給指定的協(xié)議處理函數(shù)處理
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type, &orig_dev->ptype_specific);
if (unlikely(skb->dev != orig_dev))
// 如果數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備與接收時(shí)的網(wǎng)絡(luò)設(shè)備不一致,將調(diào)用 deliver_ptype_list_skb() 函數(shù)傳遞數(shù)據(jù)包給指定的協(xié)議處理函數(shù)處理。
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type, &skb->dev->ptype_specific);

if (pt_prev) {
// 如果存在上一個(gè)協(xié)議處理函數(shù),將調(diào)用該處理函數(shù)來(lái)處理數(shù)據(jù)包。說(shuō)明數(shù)據(jù)包有未處理的分片數(shù)據(jù),調(diào)用 skb_orphan_frags_rx 函數(shù)處理剩余的分片數(shù)據(jù)。
if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))
goto drop;
*ppt_prev = pt_prev;
} else {
// 如果不存在上一個(gè)協(xié)議處理函數(shù),表示沒(méi)有合適的處理函數(shù)來(lái)處理數(shù)據(jù)包,將丟棄數(shù)據(jù)包并增加接收丟棄計(jì)數(shù)。
drop
:
if (!deliver_exact)
// 更新網(wǎng)卡的 rx_dropped 統(tǒng)計(jì)
dev_core_stats_rx_dropped_inc(skb->dev);
else
// 更新網(wǎng)卡的 rx_nohandler 統(tǒng)計(jì)
dev_core_stats_rx_nohandler_inc(skb->dev);
kfree_skb_reason(skb, SKB_DROP_REASON_UNHANDLED_PROTO);
ret
= NET_RX_DROP;
}

out
:
//將處理完的 skb 賦值回 pskb 指針
*pskb = skb;
return ret;
}

3.7.1 準(zhǔn)備工作

函數(shù)開(kāi)始時(shí),對(duì)傳入的數(shù)據(jù)包進(jìn)行一些準(zhǔn)備工作,如:

  1. 處理 skb 時(shí)間戳;

  2. 重置網(wǎng)絡(luò)頭;

  3. 重置傳輸頭;

  4. 重置 MAC 長(zhǎng)度;

  5. 設(shè)置數(shù)據(jù)包的接收接口索引。

3.7.2 XDP(eXpress Data Path)處理

這里的 XDP 是軟件層面的實(shí)現(xiàn),當(dāng)硬件網(wǎng)卡不支持 offload 模式的 XDP,可以選擇 Generic 模式的 XDP。

  • 前者早在 igb_clean_rx_irq 中執(zhí)行(前面講過(guò))避免了后面很多流程所以效率很高;

  • 后者效率低,做了很多無(wú)用功,所以主要用來(lái)功能驗(yàn)證和測(cè)試。

3.7.3 VLAN 處理

如果數(shù)據(jù)包使用了 VLAN 標(biāo)記,首先去除 VLAN 標(biāo)記,并判斷是否成功。

  • 如果成功,繼續(xù)處理去除標(biāo)記后的數(shù)據(jù)包;

  • 否則,跳過(guò)該數(shù)據(jù)包。

3.7.4 TAP 處理

根據(jù)數(shù)據(jù)包的 packet_type,按照 ptype_all 鏈表中的順序遍歷所有的 packet_type,逐個(gè)嘗試將數(shù)據(jù)包交給相應(yīng)的處理函數(shù)進(jìn)行處理。比如交給前面初始化注冊(cè)的函數(shù),一般通過(guò) libpcap 庫(kù)埋的探測(cè)點(diǎn)(TAP),用于 tcpdump 抓包。

net/packet/af_packet.c: dev_add_pack(&po->prot_hook); //用于抓包
net/packet/af_packet.c: dev_add_pack(&f->prot_hook); //用于抓包

3.7.5 TC 處理

TC(Traffic Control)是 Linux 的流量控制子系統(tǒng),通過(guò)調(diào)用 sch_handle_ingress 函數(shù)進(jìn)入 TC ingress 處理。

  • 以前主要用于限速;

  • 5.10 版本之后,可以使用 TC BPF 編程來(lái)做流量的透明攔截和負(fù)載均衡。

3.7.6 Netfilter 處理

Netfilter 是 Linux 的包過(guò)濾子系統(tǒng),iptables 是其用戶空間的客戶端。通過(guò)調(diào)用 nf_ingress 函數(shù)進(jìn)入 Netfilter ingress 處理。

3.7.7 遞交協(xié)議棧

根據(jù)協(xié)議類型packet_type.typeptype_base 哈希表中找到對(duì)應(yīng)函數(shù)保存在packet_type.func,最終通過(guò) deliver_skb 函數(shù)調(diào)用packet_type.funcskb交到對(duì)應(yīng)的處理函數(shù)處理。

  • 例如 packet_type.func = prot_hook,就會(huì)遞交到 af_packe,可以被 tcpdump 抓包;

  • 例如 packet_type.func = ip_rcv,就會(huì)遞交到協(xié)議棧入口。

static inline int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev, struct net_device *orig_dev)
{
if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))
return -ENOMEM;
refcount_inc(&skb->users);
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多