0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux內(nèi)核網(wǎng)絡(luò)數(shù)據(jù)包發(fā)送在UDP協(xié)議層的處理

B4Pb_gh_6fde77c ? 來源:linux內(nèi)核之旅 ? 作者:梁金榮 ? 2021-08-04 16:23 ? 次閱讀

1. 前言

本文分享了Linux內(nèi)核網(wǎng)絡(luò)數(shù)據(jù)包發(fā)送在UDP協(xié)議層的處理,主要分析了udp_sendmsg和udp_send_skb函數(shù),并分享了UDP層的數(shù)據(jù)統(tǒng)計和監(jiān)控以及socket發(fā)送隊列大小的調(diào)優(yōu)。

2. udp_sendmsg

這個函數(shù)定義在 net/ipv4/udp.c,函數(shù)很長,分段來看。

2.1 UDP corking

在變量聲明和基本錯誤檢查之后,udp_sendmsg 所做的第一件事就是檢查 socket 是否“ 塞住”了(corked)。UDP corking 是一項優(yōu)化技術(shù),允許內(nèi)核將多次數(shù)據(jù)累積成單個數(shù)據(jù)報發(fā)送。在用戶程序中有兩種方法可以啟用此選項:

使用 setsockopt 系統(tǒng)調(diào)用設(shè)置 socket 的 UDP_CORK 選項

程序調(diào)用 send,sendto 或 sendmsg 時,帶 MSG_MORE 參數(shù)

udp_sendmsg 代碼檢查 up-》pending 以確定 socket 當(dāng)前是否已被塞?。╟orked),如果是, 則直接跳到 do_append_data 進(jìn)行數(shù)據(jù)追加(append)。

int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,

size_t len)

{

/* variables and error checking 。。。 */

fl4 = &inet-》cork.fl.u.ip4;

if (up-》pending) {

/*

* There are pending frames.

* The socket lock must be held while it‘s corked.

*/

lock_sock(sk);

if (likely(up-》pending)) {

if (unlikely(up-》pending != AF_INET)) {

release_sock(sk);

return -EINVAL;

}

goto do_append_data;

}

release_sock(sk);

}

2.2 獲取目的 IP 地址和端口

接下來獲取目的 IP 地址和端口,有兩個可能的來源:

如果之前 socket 已經(jīng)建立連接,那 socket 本身就存儲了目標(biāo)地址

地址通過輔助結(jié)構(gòu)(struct msghdr)傳入,正如我們在 sendto 的內(nèi)核代碼中看到的那樣

具體邏輯:

/*

* Get and verify the address.

*/

if (msg-》msg_name) {

struct sockaddr_in *usin = (struct sockaddr_in *)msg-》msg_name;

if (msg-》msg_namelen 《 sizeof(*usin))

return -EINVAL;

if (usin-》sin_family != AF_INET) {

if (usin-》sin_family != AF_UNSPEC)

return -EAFNOSUPPORT;

}

daddr = usin-》sin_addr.s_addr;

dport = usin-》sin_port;

if (dport == 0)

return -EINVAL;

} else {

if (sk-》sk_state != TCP_ESTABLISHED)

return -EDESTADDRREQ;

daddr = inet-》inet_daddr;

dport = inet-》inet_dport;

/* Open fast path for connected socket.

Route will not be used, if at least one option is set.

*/

connected = 1;

}

UDP 代碼中出現(xiàn)了 TCP_ESTABLISHED!UDP socket 的狀態(tài)使用了 TCP 狀態(tài)來描述。上面的代碼顯示了內(nèi)核如何解析該變量以便設(shè)置 daddr 和 dport。

如果沒有 struct msghdr 變量,內(nèi)核函數(shù)到達(dá) udp_sendmsg 函數(shù)時,會從 socket 本身檢索目的地址和端口,并將 socket 標(biāo)記為“已連接”。

2.3 Socket 發(fā)送:bookkeeping 和打時間戳

接下來,獲取存儲在 socket 上的源地址、設(shè)備索引(device index)和時間戳選項(例如SOCK_TIMESTAMPING_TX_HARDWARE, SOCK_TIMESTAMPING_TX_SOFTWARE, SOCK_WIFI_STATUS):

ipc.addr = inet-》inet_saddr;

ipc.oif = sk-》sk_bound_dev_if;

sock_tx_timestamp(sk, &ipc.tx_flags);

2.4 輔助消息(Ancillary messages)

除了發(fā)送或接收數(shù)據(jù)包之外,sendmsg 和 recvmsg 系統(tǒng)調(diào)用還允許用戶設(shè)置或請求輔助數(shù)據(jù)。用戶程序可以通過將請求信息組織成 struct msghdr 類型變量來利用此輔助數(shù)據(jù)。一些輔助數(shù)據(jù)類型記錄在IP man page中 。

輔助數(shù)據(jù)的一個常見例子是 IP_PKTINFO。對于 sendmsg,IP_PKTINFO 允許程序在發(fā)送數(shù)據(jù)時設(shè)置一個 in_pktinfo 變量。程序可以通過填寫 struct in_pktinfo 變量中的字段來指定要在 packet 上使用的源地址。如果程序是監(jiān)聽多個 IP 地址的服務(wù)端程序,那這是一個很有用的選項。在這種情況下,服務(wù)端可能想使用客戶端連接服務(wù)端的那個 IP 地址來回復(fù)客戶端,IP_PKTINFO 非常適合這種場景。

setsockopt 可以在socket 級別設(shè)置發(fā)送包的 IP_TTL和 IP_TOS。而輔助消息允許在每個數(shù)據(jù)包級別設(shè)置 TTL 和 TOS 值。Linux 內(nèi)核會使用一個數(shù)組將 TOS 轉(zhuǎn)換為優(yōu)先級,后者會影響數(shù)據(jù)包如何以及何時從 qdisc 中發(fā)送出去。

可以看到內(nèi)核如何在 UDP socket 上處理 sendmsg 的輔助消息:

if (msg-》msg_controllen) {

err = ip_cmsg_send(sock_net(sk), msg, &ipc,

sk-》sk_family == AF_INET6);

if (err)

return err;

if (ipc.opt)

free = 1;

connected = 0;

}

解析輔助消息的工作是由 ip_cmsg_send 完成的,定義在 net/ipv4/ip_sockglue.c 。傳遞一個未初始化的輔助數(shù)據(jù),將會把這個 socket 標(biāo)記為“未建立連接的”。

2.5 設(shè)置自定義 IP 選項

接下來,sendmsg 將檢查用戶是否通過輔助消息設(shè)置了的任何自定義 IP 選項。如果設(shè)置了 ,將使用這些自定義值;如果沒有,那就使用 socket 中(已經(jīng)在用)的參數(shù):

if (!ipc.opt) {

struct ip_options_rcu *inet_opt;

rcu_read_lock();

inet_opt = rcu_dereference(inet-》inet_opt);

if (inet_opt) {

memcpy(&opt_copy, inet_opt,

sizeof(*inet_opt) + inet_opt-》opt.optlen);

ipc.opt = &opt_copy.opt;

}

rcu_read_unlock();

}

接下來,該函數(shù)檢查是否設(shè)置了源記錄路由(source record route, SRR)IP 選項。SRR 有兩種類型:寬松源記錄路由和嚴(yán)格源記錄路由。如果設(shè)置了此選項,則會記錄第一跳地址并將其保存到 faddr,并將 socket 標(biāo)記為“未連接”。這將在后面用到:

ipc.addr = faddr = daddr;

if (ipc.opt && ipc.opt-》opt.srr) {

if (!daddr)

return -EINVAL;

faddr = ipc.opt-》opt.faddr;

connected = 0;

}

處理完 SRR 選項后,將處理 TOS 選項,這可以從輔助消息中獲取,或者從 socket 當(dāng)前值中獲取。然后檢查:

是否(使用 setsockopt)在 socket 上設(shè)置了 SO_DONTROUTE,或

是否(調(diào)用 sendto 或 sendmsg 時)指定了 MSG_DONTROUTE 標(biāo)志,或

是否已設(shè)置了 is_strictroute,表示需要嚴(yán)格的 SRR 任何一個為真,tos 字段的 RTO_ONLINK 位將置 1,并且 socket 被視為“未連接”:

tos = get_rttos(&ipc, inet);

if (sock_flag(sk, SOCK_LOCALROUTE) ||

(msg-》msg_flags & MSG_DONTROUTE) ||

(ipc.opt && ipc.opt-》opt.is_strictroute)) {

tos |= RTO_ONLINK;

connected = 0;

}

2.6 多播或單播(Multicast or unicast)

接下來代碼開始處理 multicast。這有點(diǎn)復(fù)雜,因為用戶可以通過 IP_PKTINFO 輔助消息 來指定發(fā)送包的源地址或設(shè)備號,如前所述。

如果目標(biāo)地址是多播地址:

將多播設(shè)備(device)的索引(index)設(shè)置為發(fā)送(寫)這個 packet 的設(shè)備索引,并且

packet 的源地址將設(shè)置為 multicast 源地址

如果目標(biāo)地址不是一個組播地址,則發(fā)送 packet 的設(shè)備制定為 inet-》uc_index(單播), 除非用戶使用 IP_PKTINFO 輔助消息覆蓋了它。

if (ipv4_is_multicast(daddr)) {

if (!ipc.oif)

ipc.oif = inet-》mc_index;

if (!saddr)

saddr = inet-》mc_addr;

connected = 0;

} else if (!ipc.oif)

ipc.oif = inet-》uc_index;

2.7 路由

現(xiàn)在開始路由,UDP 層中處理路由的代碼以快速路徑(fast path)開始。如果 socket 已連接,則直接嘗試獲取路由:

if (connected)

rt = (struct rtable *)sk_dst_check(sk, 0);

如果 socket 未連接,或者雖然已連接,但路由輔助函數(shù) sk_dst_check 認(rèn)定路由已過期,則代碼將進(jìn)入慢速路徑(slow path)以生成一條路由記錄。首先調(diào)用 flowi4_init_output 構(gòu)造一個描述此 UDP 流的變量:

if (rt == NULL) {

struct net *net = sock_net(sk);

fl4 = &fl4_stack;

flowi4_init_output(fl4, ipc.oif, sk-》sk_mark, tos,

RT_SCOPE_UNIVERSE, sk-》sk_protocol,

inet_sk_flowi_flags(sk)|FLOWI_FLAG_CAN_SLEEP,

faddr, saddr, dport, inet-》inet_sport);

然后,socket 及其 flow 實(shí)例會傳遞給安全子系統(tǒng),這樣 SELinux 或 SMACK 這樣的系統(tǒng)就可以在 flow 實(shí)例上設(shè)置安全 ID。接下來,ip_route_output_flow 將調(diào)用 IP 路由代碼,創(chuàng)建一個路由實(shí)例:

security_sk_classify_flow(sk, flowi4_to_flowi(fl4));

rt = ip_route_output_flow(net, fl4, sk);

如果創(chuàng)建路由實(shí)例失敗,并且返回碼是 ENETUNREACH, 則 OUTNOROUTES 計數(shù)器將會加 1。

if (IS_ERR(rt)) {

err = PTR_ERR(rt);

rt = NULL;

if (err == -ENETUNREACH)

IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);

goto out;

}

這些統(tǒng)計計數(shù)器所在的源文件、其他可用的計數(shù)器及其含義,將在下面的 UDP 監(jiān)控部分分享。

接下來,如果是廣播路由,但 socket 的 SOCK_BROADCAST 選項未設(shè)置,則處理過程終止。如果 socket 被視為“已連接”,則路由實(shí)例將緩存到 socket 上:

err = -EACCES;

if ((rt-》rt_flags & RTCF_BROADCAST) &&

!sock_flag(sk, SOCK_BROADCAST))

goto out;

if (connected)

sk_dst_set(sk, dst_clone(&rt-》dst));

2.8 MSG_CONFIRM: 阻止 ARP 緩存過期

如果調(diào)用 send, sendto 或 sendmsg 的時候指定了 MSG_CONFIRM 參數(shù),UDP 協(xié)議層將會如下處理:

if (msg-》msg_flags&MSG_CONFIRM)

goto do_confirm;

back_from_confirm:

該標(biāo)志提示系統(tǒng)去確認(rèn)一下 ARP 緩存條目是否仍然有效,防止其被垃圾回收。 do_confirm 標(biāo)簽位于此函數(shù)末尾處:

do_confirm:

dst_confirm(&rt-》dst);

if (?。╩sg-》msg_flags&MSG_PROBE) || len)

goto back_from_confirm;

err = 0;

goto out;

dst_confirm 函數(shù)只是在相應(yīng)的緩存條目上設(shè)置一個標(biāo)記位,稍后當(dāng)查詢鄰居緩存并找到 條目時將檢查該標(biāo)志,我們后面一些會看到。此功能通常用于 UDP 網(wǎng)絡(luò)應(yīng)用程序,以減少不必要的 ARP 流量。此代碼確認(rèn)緩存條目然后跳回 back_from_confirm 標(biāo)簽。一旦 do_confirm 代碼跳回到 back_from_confirm(或者之前就沒有執(zhí)行到 do_confirm ),代碼接下來將處理 UDP cork 和 uncorked 情況。

2.9 uncorked UDP sockets 快速路徑:準(zhǔn)備待發(fā)送數(shù)據(jù)

如果不需要 corking,數(shù)據(jù)就可以封裝到一個 struct sk_buff 實(shí)例中并傳遞給 udp_send_skb,離 IP 協(xié)議層更進(jìn)了一步。這是通過調(diào)用 ip_make_skb 來完成的。

先前通過調(diào)用 ip_route_output_flow 生成的路由條目也會一起傳進(jìn)來, 它將保存到 skb 里。

/* Lockless fast path for the non-corking case. */

if (!corkreq) {

skb = ip_make_skb(sk, fl4, getfrag, msg-》msg_iov, ulen,

sizeof(struct udphdr), &ipc, &rt,

msg-》msg_flags);

err = PTR_ERR(skb);

if (!IS_ERR_OR_NULL(skb))

err = udp_send_skb(skb, fl4);

goto out;

}

ip_make_skb 函數(shù)將創(chuàng)建一個 skb,其中需要考慮到很多的事情,例如:

MTU

UDP corking(如果啟用)

UDP Fragmentation Offloading(UFO)

Fragmentation(分片):如果硬件不支持 UFO,但是要傳輸?shù)臄?shù)據(jù)大于 MTU,需要軟件做分片

大多數(shù)網(wǎng)絡(luò)設(shè)備驅(qū)動程序不支持 UFO,因為網(wǎng)絡(luò)硬件本身不支持此功能。我們來看下這段代碼,先看 corking 禁用的情況。

2.9.1 ip_make_skb

定義在net/ipv4/ip_output.c,這個函數(shù)有點(diǎn)復(fù)雜。

構(gòu)建 skb 的時候,ip_make_skb 依賴的底層代碼需要使用一個 corking 變量和一個 queue 變量 ,skb 將通過 queue 變量傳入。如果 socket 未被 cork,則會傳入一個假的 corking 變量和一個空隊列。

現(xiàn)在來看看假 corking 變量和空隊列是如何初始化的:

struct sk_buff *ip_make_skb(struct sock *sk, /* more args */)

{

struct inet_cork cork;

struct sk_buff_head queue;

int err;

if (flags & MSG_PROBE)

return NULL;

__skb_queue_head_init(&queue);

cork.flags = 0;

cork.addr = 0;

cork.opt = NULL;

err = ip_setup_cork(sk, &cork, /* more args */);

if (err)

return ERR_PTR(err);

如上所示,cork 和 queue 都是在棧上分配的,ip_make_skb 根本不需要它。 ip_setup_cork 初始化 cork 變量。接下來,調(diào)用__ip_append_data 并傳入 cork 和 queue 變 量:

err = __ip_append_data(sk, fl4, &queue, &cork,

¤t-》task_frag, getfrag,

from, length, transhdrlen, flags);

我們將在后面看到這個函數(shù)是如何工作的,因為不管 socket 是否被 cork,最后都會執(zhí)行它。

現(xiàn)在,我們只需要知道__ip_append_data 將創(chuàng)建一個 skb,向其追加數(shù)據(jù),并將該 skb 添加 到傳入的 queue 變量中。如果追加數(shù)據(jù)失敗,則調(diào)用__ip_flush_pending_frame 丟棄數(shù)據(jù) 并向上返回錯誤(指針類型):

if (err) {

__ip_flush_pending_frames(sk, &queue, &cork);

return ERR_PTR(err);

}

最后,如果沒有發(fā)生錯誤,__ip_make_skb 將 skb 出隊,添加 IP 選項,并返回一個準(zhǔn)備好傳遞給更底層發(fā)送的 skb:

return __ip_make_skb(sk, fl4, &queue, &cork);

2.9.2 發(fā)送數(shù)據(jù)

如果沒有錯誤,skb 就會交給 udp_send_skb,后者會繼續(xù)將其傳給下一層協(xié)議,IP 協(xié)議:

err = PTR_ERR(skb);

if (!IS_ERR_OR_NULL(skb))

err = udp_send_skb(skb, fl4);

goto out;

如果有錯誤,錯誤計數(shù)就會有相應(yīng)增加。后面的“錯誤計數(shù)”部分會詳細(xì)介紹。

2.10 沒有被 cork 的數(shù)據(jù)時的慢路徑

如果使用了 UDP corking,但之前沒有數(shù)據(jù)被 cork,則慢路徑開始:

對 socket 加鎖

檢查應(yīng)用程序是否有 bug:已經(jīng)被 cork 的 socket 是否再次被 cork

設(shè)置該 UDP flow 的一些參數(shù),為 corking 做準(zhǔn)備

將要發(fā)送的數(shù)據(jù)追加到現(xiàn)有數(shù)據(jù)

udp_sendmsg 代碼繼續(xù)向下看,就是這一邏輯:

lock_sock(sk);

if (unlikely(up-》pending)) {

/* The socket is already corked while preparing it. */

/* 。。。 which is an evident application bug. --ANK */

release_sock(sk);

LIMIT_NETDEBUG(KERN_DEBUG pr_fmt(“cork app bug 2

”));

err = -EINVAL;

goto out;

}

/*

* Now cork the socket to pend data.

*/

fl4 = &inet-》cork.fl.u.ip4;

fl4-》daddr = daddr;

fl4-》saddr = saddr;

fl4-》fl4_dport = dport;

fl4-》fl4_sport = inet-》inet_sport;

up-》pending = AF_INET;

do_append_data:

up-》len += ulen;

err = ip_append_data(sk, fl4, getfrag, msg-》msg_iov, ulen,

sizeof(struct udphdr), &ipc, &rt,

corkreq ? msg-》msg_flags|MSG_MORE : msg-》msg_flags);

2.10.1 ip_append_data

這個函數(shù)簡單封裝了__ip_append_data,在調(diào)用后者之前,做了兩件重要的事情:

檢查是否從用戶傳入了 MSG_PROBE 標(biāo)志。該標(biāo)志表示用戶不想真正發(fā)送數(shù)據(jù),只是做路徑探測(例如,確定PMTU)

檢查 socket 的發(fā)送隊列是否為空。如果為空,意味著沒有 cork 數(shù)據(jù)等待處理,因此調(diào)用 ip_setup_cork 來設(shè)置 corking

一旦處理了上述條件,就調(diào)用__ip_append_data 函數(shù),該函數(shù)包含用于將數(shù)據(jù)處理成數(shù)據(jù)包的大量邏輯。

2.10.2 __ip_append_data

如果 socket 是 corked,則從 ip_append_data 調(diào)用此函數(shù);如果 socket 未被 cork,則從 ip_make_skb 調(diào)用此函數(shù)。在任何一種情況下,函數(shù)都將分配一個新緩沖區(qū)來存儲傳入的數(shù)據(jù),或者將數(shù)據(jù)附加到現(xiàn)有數(shù)據(jù)中。這種工作的方式圍繞 socket 的發(fā)送隊列。等待發(fā)送的現(xiàn)有數(shù)據(jù)(例如,如果 socket 被 cork) 將在隊列中有一個對應(yīng)條目,可以被追加數(shù)據(jù)。

這個函數(shù)很復(fù)雜,它執(zhí)行很多計算以確定如何構(gòu)造傳遞給下面的網(wǎng)絡(luò)層的 skb。

該函數(shù)的重點(diǎn)包括:

如果硬件支持,則處理 UDP Fragmentation Offload(UFO)。絕大多數(shù)網(wǎng)絡(luò)硬件不支持 UFO。如果你的網(wǎng)卡驅(qū)動程序支持它,它將設(shè)置 NETIF_F_UFO 標(biāo)記位

處理支持分散/收集( scatter/gather)IO 的網(wǎng)卡。許多 卡都支持此功能,并使用 NETIF_F_SG 標(biāo)志進(jìn)行通告。支持該特性的網(wǎng)卡可以處理數(shù)據(jù) 被分散到多個 buffer 的數(shù)據(jù)包;內(nèi)核不需要花時間將多個緩沖區(qū)合并成一個緩沖區(qū)中。避 免這種額外的復(fù)制會提升性能,大多數(shù)網(wǎng)卡都支持此功能

通過調(diào)用 sock_wmalloc 跟蹤發(fā)送隊列的大小。當(dāng)分配新的 skb 時,skb 的大小由創(chuàng)建它 的 socket 計費(fèi)(charge),并計入 socket 發(fā)送隊列的已分配字節(jié)數(shù)。如果發(fā)送隊列已經(jīng) 沒有足夠的空間(超過計費(fèi)限制),則 skb 并分配失敗并返回錯誤。我們將在下面的調(diào)優(yōu)部分中看到如何設(shè)置 socket 發(fā)送隊列大?。╰xqueuelen)

更新錯誤統(tǒng)計信息。此函數(shù)中的任何錯誤都會增加“discard”計數(shù)。我們將在下面的監(jiān)控部分中看到如何讀取此值

函數(shù)執(zhí)行成功后返回 0,以及一個適用于網(wǎng)絡(luò)設(shè)備傳輸?shù)?skb。

在 unorked 情況下,持有 skb 的 queue 被作為參數(shù)傳遞給上面描述的__ip_make_skb,在那里 它被出隊并通過 udp_send_skb 發(fā)送到更底層。

在 cork 的情況下,__ip_append_data 的返回值向上傳遞。數(shù)據(jù)位于發(fā)送隊列中,直到 udp_sendmsg 確定是時候調(diào)用 udp_push_pending_frames 來完成 skb,后者會進(jìn)一步調(diào)用 udp_send_skb。

2.10.3 Flushing corked sockets

現(xiàn)在,udp_sendmsg 會繼續(xù),檢查__ip_append_skb 的返回值(錯誤碼):

if (err)

udp_flush_pending_frames(sk);

else if (!corkreq)

err = udp_push_pending_frames(sk);

else if (unlikely(skb_queue_empty(&sk-》sk_write_queue)))

up-》pending = 0;

release_sock(sk);

我們來看看每個情況:

如果出現(xiàn)錯誤(錯誤為非零),則調(diào)用 udp_flush_pending_frames,這將取消 cork 并從 socket 的發(fā)送隊列中刪除所有數(shù)據(jù)

如果在未指定 MSG_MORE 的情況下發(fā)送此數(shù)據(jù),則調(diào)用 udp_push_pending_frames,它將數(shù)據(jù)傳遞到更下面的網(wǎng)絡(luò)層

如果發(fā)送隊列為空,請將 socket 標(biāo)記為不再 cork

如果追加操作完成并且有更多數(shù)據(jù)要進(jìn)入 cork,則代碼將做一些清理工作,并返回追加數(shù)據(jù)的長度:

ip_rt_put(rt);

if (free)

kfree(ipc.opt);

if (!err)

return len;

這就是內(nèi)核如何處理 corked UDP sockets 的。

2.11 Error accounting

如果:

non-corking 快速路徑創(chuàng)建 skb 失敗,或 udp_send_skb 返回錯誤,或

ip_append_data 無法將數(shù)據(jù)附加到 corked UDP socket,或

當(dāng) udp_push_pending_frames 調(diào)用 udp_send_skb 發(fā)送 corked skb 時后者返回錯誤

僅當(dāng)返回的錯誤是 ENOBUFS(內(nèi)核無可用內(nèi)存)或 socket 已設(shè)置 SOCK_NOSPACE(發(fā)送隊列已滿)時,SNDBUFERRORS 統(tǒng)計信息才會增加:

/*

* ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space. Reporting

* ENOBUFS might not be good (it’s not tunable per se), but otherwise

* we don‘t have a good statistic (IpOutDiscards but it can be too many

* things)。 We could add another new stat but at least for now that

* seems like overkill.

*/

if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk-》sk_socket-》flags)) {

UDP_INC_STATS_USER(sock_net(sk),

UDP_MIB_SNDBUFERRORS, is_udplite);

}

return err;

我們接下來會在后面的數(shù)據(jù)監(jiān)控里看到如何讀取這些計數(shù)。

3. udp_send_skb

udp_sendmsg 通過調(diào)用 udp_send_skb 函數(shù)將 skb 送到下一網(wǎng)絡(luò)層,在本文中是 IP 協(xié)議層。這個函數(shù)做了一些重要的事情:

向 skb 添加 UDP 頭

處理校驗和:軟件校驗和,硬件校驗和或無校驗和(如果禁用)

調(diào)用 ip_send_skb 將 skb 發(fā)送到 IP 協(xié)議層

更新發(fā)送成功或失敗的統(tǒng)計計數(shù)器

首先,創(chuàng)建 UDP 頭:

static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)

{

/* useful variables 。。。 */

/*

* Create a UDP header

*/

uh = udp_hdr(skb);

uh-》source = inet-》inet_sport;

uh-》dest = fl4-》fl4_dport;

uh-》len = htons(len);

uh-》check = 0;

接下來,處理校驗和。有幾種情況:

首先處理 UDP-Lite 校驗和

接下來,如果 socket 校驗和選項被關(guān)閉(setsockopt 帶 SO_NO_CHECK 參數(shù)),它將被標(biāo)記為校 驗和關(guān)閉

接下來,如果硬件支持 UDP 校驗和,則將調(diào)用 udp4_hwcsum 來設(shè)置它。請注意,如果數(shù) 據(jù)包是分段的,內(nèi)核將在軟件中生成校驗和,可以在 udp4_hwcsum 的源代碼中看到這一點(diǎn)

最后,通過調(diào)用 udp_csum 生成軟件校驗和

if (is_udplite) /* UDP-Lite */

csum = udplite_csum(skb);

else if (sk-》sk_no_check == UDP_CSUM_NOXMIT) { /* UDP csum disabled */

skb-》ip_summed = CHECKSUM_NONE;

goto send;

} else if (skb-》ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */

udp4_hwcsum(skb, fl4-》saddr, fl4-》daddr);

goto send;

} else

csum = udp_csum(skb);

接下來,添加了偽頭 :

uh-》check = csum_tcpudp_magic(fl4-》saddr, fl4-》daddr, len,

sk-》sk_protocol, csum);

if (uh-》check == 0)

uh-》check = CSUM_MANGLED_0;

如果校驗和為 0,則根據(jù) RFC 768,校驗為全 1( transmitted as all ones (the equivalent in one’s complement arithmetic))。最后,將 skb 傳遞給 IP 協(xié)議層并增加統(tǒng)計計數(shù):

send:

err = ip_send_skb(sock_net(sk), skb);

if (err) {

if (err == -ENOBUFS && !inet-》recverr) {

UDP_INC_STATS_USER(sock_net(sk),

UDP_MIB_SNDBUFERRORS, is_udplite);

err = 0;

}

} else

UDP_INC_STATS_USER(sock_net(sk),

UDP_MIB_OUTDATAGRAMS, is_udplite);

return err;

如果 ip_send_skb 成功,將更新 OUTDATAGRAMS 統(tǒng)計。如果 IP 協(xié)議層報告錯誤,并且錯誤 是 ENOBUFS(內(nèi)核缺少內(nèi)存)而且錯誤 queue(inet-》recverr)沒有啟用,則更新 SNDBUFERRORS。

接下來看看如何在 Linux 內(nèi)核中監(jiān)視和調(diào)優(yōu) UDP 協(xié)議層。

4. 監(jiān)控:UDP 層統(tǒng)計

兩個非常有用的獲取 UDP 協(xié)議統(tǒng)計文件:

/proc/net/snmp

/proc/net/udp

4.1 /proc/net/snmp

監(jiān)控 UDP 協(xié)議層統(tǒng)計:

cat /proc/net/snmp | grep Udp:

642fceec-f497-11eb-9bcf-12bb97331649.png

要準(zhǔn)確地理解這些計數(shù),需要仔細(xì)地閱讀內(nèi)核代碼。一些類型的錯誤計數(shù)并不是只出現(xiàn)在一種計數(shù)中,而可能是出現(xiàn)在多個計數(shù)中。

InDatagrams: Incremented when recvmsg was used by a userland program to read datagram. Also incremented when a UDP packet is encapsulated and sent back for processing.

NoPorts: Incremented when UDP packets arrive destined for a port where no program is listening.

InErrors: Incremented in several cases: no memory in the receive queue, when a bad checksum is seen, and if sk_add_backlog fails to add the datagram.

OutDatagrams: Incremented when a UDP packet is handed down without error to the IP protocol layer to be sent.

RcvbufErrors: Incremented when sock_queue_rcv_skb reports that no memory is available; this happens if sk-》sk_rmem_alloc is greater than or equal to sk-》sk_rcvbuf.

SndbufErrors: Incremented if the IP protocol layer reported an error when trying to send the packet and no error queue has been setup. Also incremented if no send queue space or kernel memory are available.

InCsumErrors: Incremented when a UDP checksum failure is detected. Note that in all cases I could find, InCsumErrors is incremented at the same time as InErrors. Thus, InErrors - InCsumErros should yield the count of memory related errors on the receive side.

UDP 協(xié)議層發(fā)現(xiàn)的某些錯誤會出現(xiàn)在其他協(xié)議層的統(tǒng)計信息中。一個例子:路由錯誤 。 udp_sendmsg 發(fā)現(xiàn)的路由錯誤將導(dǎo)致 IP 協(xié)議層的 OutNoRoutes 統(tǒng)計增加。

4.2 /proc/net/udp

監(jiān)控 UDP socket 統(tǒng)計:

cat /proc/net/udp

每一列的意思:

sl: Kernel hash slot for the socket

local_address: Hexadecimal local address of the socket and port number, separated by :。

rem_address: Hexadecimal remote address of the socket and port number, separated by :。

st: The state of the socket. Oddly enough, the UDP protocol layer seems to use some TCP socket states. In the example above, 7 is TCP_CLOSE.

tx_queue: The amount of memory allocated in the kernel for outgoing UDP datagrams.

rx_queue: The amount of memory allocated in the kernel for incoming UDP datagrams.

tr, tm-》when, retrnsmt: These fields are unused by the UDP protocol layer.

uid: The effective user id of the user who created this socket.

timeout: Unused by the UDP protocol layer.

inode: The inode number corresponding to this socket. You can use this to help you determine which user process has this socket open. Check /proc/[pid]/fd, which will contain symlinks to socket[:inode]。

ref: The current reference count for the socket.

pointer: The memory address in the kernel of the struct sock.

drops: The number of datagram drops associated with this socket. Note that this does not include any drops related to sending datagrams (on corked UDP sockets or otherwise); this is only incremented in receive paths as of the kernel version examined by this blog post.

打印這些計數(shù)的代碼在net/ipv4/udp.c。

5. 調(diào)優(yōu):socket 發(fā)送隊列內(nèi)存大小

發(fā)送隊列(也叫“寫隊列”)的最大值可以通過設(shè)置 net.core.wmem_max sysctl 進(jìn)行修改。

$ sudo sysctl -w net.core.wmem_max=8388608

sk-》sk_write_queue 用 net.core.wmem_default 初始化, 這個值也可以調(diào)整。

調(diào)整初始發(fā)送 buffer 大?。?/p>

$ sudo sysctl -w net.core.wmem_default=8388608

也可以通過從應(yīng)用程序調(diào)用 setsockopt 并傳遞 SO_SNDBUF 來設(shè)置 sk-》sk_write_queue 。通過 setsockopt 設(shè)置的最大值是 net.core.wmem_max。

不過,可以通過 setsockopt 并傳遞 SO_SNDBUFFORCE 來覆蓋 net.core.wmem_max 限制, 這需要 CAP_NET_ADMIN 權(quán)限。

每次調(diào)用__ip_append_data 分配 skb 時,sk-》sk_wmem_alloc 都會遞增。正如我們所看到 的,UDP 數(shù)據(jù)報傳輸速度很快,通常不會在發(fā)送隊列中花費(fèi)太多時間。

6. 總結(jié)

本文重點(diǎn)分析了數(shù)據(jù)包在傳輸層(UDP協(xié)議)的發(fā)送過程,并進(jìn)行了監(jiān)控和調(diào)優(yōu),后面數(shù)據(jù)包將到達(dá) IP 協(xié)議層,下次再分享,感謝閱讀。

Reference:https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data

編輯:jq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • LINUX內(nèi)核
    +關(guān)注

    關(guān)注

    1

    文章

    315

    瀏覽量

    21556

原文標(biāo)題:Linux內(nèi)核網(wǎng)絡(luò)udp數(shù)據(jù)包發(fā)送(二)——UDP協(xié)議層分析

文章出處:【微信號:gh_6fde77c41971,微信公眾號:FPGA干貨】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    LINUX內(nèi)核網(wǎng)絡(luò)中的軟中斷KSOFTIRQD

    之前分享過Linux內(nèi)核網(wǎng)絡(luò)數(shù)據(jù)包的接收過程,當(dāng)執(zhí)行到網(wǎng)卡通過硬件中斷(IRQ)通知CPU,告訴它有數(shù)據(jù)來了,CPU會根據(jù)中斷表,調(diào)用已經(jīng)注冊的中斷函數(shù),這個中斷函數(shù)會調(diào)到驅(qū)動程序(N
    發(fā)表于 12-15 11:44 ?1696次閱讀

    Linux系統(tǒng)收發(fā)網(wǎng)絡(luò)數(shù)據(jù)包的工作過程

    Linux 服務(wù)器收到網(wǎng)絡(luò)數(shù)據(jù)包,需要經(jīng)過哪些處理,一步步將數(shù)據(jù)傳給應(yīng)用進(jìn)程的呢?應(yīng)用進(jìn)程發(fā)送
    發(fā)表于 06-08 12:34 ?477次閱讀
    <b class='flag-5'>Linux</b>系統(tǒng)收發(fā)<b class='flag-5'>網(wǎng)絡(luò)</b><b class='flag-5'>數(shù)據(jù)包</b>的工作過程

    Linux場景下數(shù)據(jù)包是如何在協(xié)議傳輸?shù)?/a>

    所有互聯(lián)網(wǎng)服務(wù),均依賴于TCP/IP協(xié)議棧。懂得數(shù)據(jù)是如何在協(xié)議棧傳輸?shù)模瑢椭闾嵘ヂ?lián)網(wǎng)程序的性能和解決TCP相關(guān)問題的能力。 我們講述Li
    的頭像 發(fā)表于 11-11 11:33 ?961次閱讀
    <b class='flag-5'>Linux</b>場景下<b class='flag-5'>數(shù)據(jù)包</b>是如何在<b class='flag-5'>協(xié)議</b><b class='flag-5'>層</b>傳輸?shù)? />    </a>
</div>                            <div   id=

    DPDKAI驅(qū)動的高效數(shù)據(jù)包處理應(yīng)用

    傳統(tǒng)的數(shù)據(jù)包處理方式是數(shù)據(jù)包先到內(nèi)核最后再到用戶進(jìn)行處理。這種方式會增加額外的延遲和CPU開銷
    的頭像 發(fā)表于 02-25 11:28 ?713次閱讀
    DPDK<b class='flag-5'>在</b>AI驅(qū)動的高效<b class='flag-5'>數(shù)據(jù)包</b><b class='flag-5'>處理</b>應(yīng)用

    Linux內(nèi)核網(wǎng)絡(luò)的TCP傳輸控制塊相關(guān)資料分享

    1、Linux內(nèi)核網(wǎng)絡(luò)之傳輸接收消息簡析傳輸發(fā)送消息時,TCP
    發(fā)表于 06-21 16:53

    Linux內(nèi)核網(wǎng)絡(luò)網(wǎng)絡(luò)發(fā)送消息之IP分片簡析

    1、Linux內(nèi)核網(wǎng)絡(luò)網(wǎng)絡(luò)發(fā)送消息之IP分片簡析本文分析下ip的分片。行 IP 分片。IP分片通常發(fā)生在
    發(fā)表于 07-20 15:34

    如何在沒有收到另一個udp數(shù)據(jù)包的情況下簡單地發(fā)送一個udp數(shù)據(jù)包

    \",\'data\')它在n“接收”塊 中完美運(yùn)行,但我可以 找不到沒有收到另一個 udp 數(shù)據(jù)包的情況下簡單地發(fā)送一個 udp
    發(fā)表于 04-27 06:17

    UDP廣播數(shù)據(jù)包并不總是發(fā)送的原因?怎么處理

    UDP 作為廣播工作似乎是一個奇怪的問題: - 始終發(fā)送特定 IP 上發(fā)送UDP 數(shù)據(jù)包。
    發(fā)表于 05-10 08:36

    UDP協(xié)議,UDP協(xié)議是什么意思

    UDP協(xié)議,UDP協(xié)議是什么意思 UDP 是User Datagram Protocol的簡稱, 中文名是用戶
    發(fā)表于 03-29 17:35 ?1462次閱讀

    數(shù)據(jù)包發(fā)送流程

    一個數(shù)據(jù)包,從聊天框里發(fā)出,消息會從聊天軟件所在的用戶空間拷貝到內(nèi)核空間的發(fā)送緩沖區(qū)(send buffer),數(shù)據(jù)包就這樣順著傳輸、
    的頭像 發(fā)表于 08-19 14:38 ?2486次閱讀

    有線網(wǎng)絡(luò)通信實(shí)驗2之UDP協(xié)議

    UDP協(xié)議是TCP/IP協(xié)議棧中傳輸協(xié)議,是一個簡單的面向數(shù)據(jù)報的
    的頭像 發(fā)表于 03-01 14:29 ?2045次閱讀
    有線<b class='flag-5'>網(wǎng)絡(luò)</b>通信實(shí)驗2之<b class='flag-5'>UDP</b><b class='flag-5'>協(xié)議</b>

    UDP協(xié)議原理詳解

    一個典型的使用UDP協(xié)議封裝的數(shù)據(jù)包,包括以太網(wǎng)MAC頭+網(wǎng)絡(luò)IP數(shù)據(jù)頭+傳輸
    的頭像 發(fā)表于 04-24 10:54 ?2337次閱讀
    <b class='flag-5'>UDP</b><b class='flag-5'>協(xié)議</b>原理詳解

    簡述Linux系統(tǒng)收發(fā)網(wǎng)絡(luò)數(shù)據(jù)包的過程

    Linux 服務(wù)器收到網(wǎng)絡(luò)數(shù)據(jù)包,需要經(jīng)過哪些處理,一步步將數(shù)據(jù)傳給應(yīng)用進(jìn)程的呢?應(yīng)用進(jìn)程發(fā)送
    的頭像 發(fā)表于 05-05 10:04 ?554次閱讀
    簡述<b class='flag-5'>Linux</b>系統(tǒng)收發(fā)<b class='flag-5'>網(wǎng)絡(luò)</b><b class='flag-5'>數(shù)據(jù)包</b>的過程

    什么是UDP協(xié)議

    UDP協(xié)議即用戶數(shù)據(jù)報協(xié)議,該協(xié)議主要為應(yīng)用程序提供了一種無需建立連接就可以發(fā)送封裝的 IP 數(shù)據(jù)包
    發(fā)表于 05-06 15:19 ?2176次閱讀

    Linux如何操作將數(shù)據(jù)包發(fā)送出去

    ? Linux 服務(wù)器收到網(wǎng)絡(luò)數(shù)據(jù)包,需要經(jīng)過哪些處理,一步步將數(shù)據(jù)傳給應(yīng)用進(jìn)程的呢?應(yīng)用進(jìn)程發(fā)送
    的頭像 發(fā)表于 06-17 16:00 ?905次閱讀
    <b class='flag-5'>Linux</b>如何操作將<b class='flag-5'>數(shù)據(jù)包</b><b class='flag-5'>發(fā)送</b>出去