1. 前言
本文分享了Linux內核網絡數據包發送在UDP協議層的處理,主要分析了udp_sendmsg和udp_send_skb函數,并分享了UDP層的數據統計和監控以及socket發送隊列大小的調優。
2. udp_sendmsg
這個函數定義在 net/ipv4/udp.c,函數很長,分段來看。
2.1 UDP corking
在變量聲明和基本錯誤檢查之后,udp_sendmsg 所做的第一件事就是檢查 socket 是否“ 塞住”了(corked)。UDP corking 是一項優化技術,允許內核將多次數據累積成單個數據報發送。在用戶程序中有兩種方法可以啟用此選項:
使用 setsockopt 系統調用設置 socket 的 UDP_CORK 選項
程序調用 send,sendto 或 sendmsg 時,帶 MSG_MORE 參數
udp_sendmsg 代碼檢查 up-》pending 以確定 socket 當前是否已被塞住(corked),如果是, 則直接跳到 do_append_data 進行數據追加(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 已經建立連接,那 socket 本身就存儲了目標地址
地址通過輔助結構(struct msghdr)傳入,正如我們在 sendto 的內核代碼中看到的那樣
具體邏輯:
/*
* 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 代碼中出現了 TCP_ESTABLISHED!UDP socket 的狀態使用了 TCP 狀態來描述。上面的代碼顯示了內核如何解析該變量以便設置 daddr 和 dport。
如果沒有 struct msghdr 變量,內核函數到達 udp_sendmsg 函數時,會從 socket 本身檢索目的地址和端口,并將 socket 標記為“已連接”。
2.3 Socket 發送:bookkeeping 和打時間戳
接下來,獲取存儲在 socket 上的源地址、設備索引(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)
除了發送或接收數據包之外,sendmsg 和 recvmsg 系統調用還允許用戶設置或請求輔助數據。用戶程序可以通過將請求信息組織成 struct msghdr 類型變量來利用此輔助數據。一些輔助數據類型記錄在IP man page中 。
輔助數據的一個常見例子是 IP_PKTINFO。對于 sendmsg,IP_PKTINFO 允許程序在發送數據時設置一個 in_pktinfo 變量。程序可以通過填寫 struct in_pktinfo 變量中的字段來指定要在 packet 上使用的源地址。如果程序是監聽多個 IP 地址的服務端程序,那這是一個很有用的選項。在這種情況下,服務端可能想使用客戶端連接服務端的那個 IP 地址來回復客戶端,IP_PKTINFO 非常適合這種場景。
setsockopt 可以在socket 級別設置發送包的 IP_TTL和 IP_TOS。而輔助消息允許在每個數據包級別設置 TTL 和 TOS 值。Linux 內核會使用一個數組將 TOS 轉換為優先級,后者會影響數據包如何以及何時從 qdisc 中發送出去。
可以看到內核如何在 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 。傳遞一個未初始化的輔助數據,將會把這個 socket 標記為“未建立連接的”。
2.5 設置自定義 IP 選項
接下來,sendmsg 將檢查用戶是否通過輔助消息設置了的任何自定義 IP 選項。如果設置了 ,將使用這些自定義值;如果沒有,那就使用 socket 中(已經在用)的參數:
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();
}
接下來,該函數檢查是否設置了源記錄路由(source record route, SRR)IP 選項。SRR 有兩種類型:寬松源記錄路由和嚴格源記錄路由。如果設置了此選項,則會記錄第一跳地址并將其保存到 faddr,并將 socket 標記為“未連接”。這將在后面用到:
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 當前值中獲取。然后檢查:
是否(使用 setsockopt)在 socket 上設置了 SO_DONTROUTE,或
是否(調用 sendto 或 sendmsg 時)指定了 MSG_DONTROUTE 標志,或
是否已設置了 is_strictroute,表示需要嚴格的 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。這有點復雜,因為用戶可以通過 IP_PKTINFO 輔助消息 來指定發送包的源地址或設備號,如前所述。
如果目標地址是多播地址:
將多播設備(device)的索引(index)設置為發送(寫)這個 packet 的設備索引,并且
packet 的源地址將設置為 multicast 源地址
如果目標地址不是一個組播地址,則發送 packet 的設備制定為 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 路由
現在開始路由,UDP 層中處理路由的代碼以快速路徑(fast path)開始。如果 socket 已連接,則直接嘗試獲取路由:
if (connected)
rt = (struct rtable *)sk_dst_check(sk, 0);
如果 socket 未連接,或者雖然已連接,但路由輔助函數 sk_dst_check 認定路由已過期,則代碼將進入慢速路徑(slow path)以生成一條路由記錄。首先調用 flowi4_init_output 構造一個描述此 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 實例會傳遞給安全子系統,這樣 SELinux 或 SMACK 這樣的系統就可以在 flow 實例上設置安全 ID。接下來,ip_route_output_flow 將調用 IP 路由代碼,創建一個路由實例:
security_sk_classify_flow(sk, flowi4_to_flowi(fl4));
rt = ip_route_output_flow(net, fl4, sk);
如果創建路由實例失敗,并且返回碼是 ENETUNREACH, 則 OUTNOROUTES 計數器將會加 1。
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
rt = NULL;
if (err == -ENETUNREACH)
IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
goto out;
}
這些統計計數器所在的源文件、其他可用的計數器及其含義,將在下面的 UDP 監控部分分享。
接下來,如果是廣播路由,但 socket 的 SOCK_BROADCAST 選項未設置,則處理過程終止。如果 socket 被視為“已連接”,則路由實例將緩存到 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 緩存過期
如果調用 send, sendto 或 sendmsg 的時候指定了 MSG_CONFIRM 參數,UDP 協議層將會如下處理:
if (msg-》msg_flags&MSG_CONFIRM)
goto do_confirm;
back_from_confirm:
該標志提示系統去確認一下 ARP 緩存條目是否仍然有效,防止其被垃圾回收。 do_confirm 標簽位于此函數末尾處:
do_confirm:
dst_confirm(&rt-》dst);
if (!(msg-》msg_flags&MSG_PROBE) || len)
goto back_from_confirm;
err = 0;
goto out;
dst_confirm 函數只是在相應的緩存條目上設置一個標記位,稍后當查詢鄰居緩存并找到 條目時將檢查該標志,我們后面一些會看到。此功能通常用于 UDP 網絡應用程序,以減少不必要的 ARP 流量。此代碼確認緩存條目然后跳回 back_from_confirm 標簽。一旦 do_confirm 代碼跳回到 back_from_confirm(或者之前就沒有執行到 do_confirm ),代碼接下來將處理 UDP cork 和 uncorked 情況。
2.9 uncorked UDP sockets 快速路徑:準備待發送數據
如果不需要 corking,數據就可以封裝到一個 struct sk_buff 實例中并傳遞給 udp_send_skb,離 IP 協議層更進了一步。這是通過調用 ip_make_skb 來完成的。
先前通過調用 ip_route_output_flow 生成的路由條目也會一起傳進來, 它將保存到 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 函數將創建一個 skb,其中需要考慮到很多的事情,例如:
MTU
UDP corking(如果啟用)
UDP Fragmentation Offloading(UFO)
Fragmentation(分片):如果硬件不支持 UFO,但是要傳輸的數據大于 MTU,需要軟件做分片
大多數網絡設備驅動程序不支持 UFO,因為網絡硬件本身不支持此功能。我們來看下這段代碼,先看 corking 禁用的情況。
2.9.1 ip_make_skb
定義在net/ipv4/ip_output.c,這個函數有點復雜。
構建 skb 的時候,ip_make_skb 依賴的底層代碼需要使用一個 corking 變量和一個 queue 變量 ,skb 將通過 queue 變量傳入。如果 socket 未被 cork,則會傳入一個假的 corking 變量和一個空隊列。
現在來看看假 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 變量。接下來,調用__ip_append_data 并傳入 cork 和 queue 變 量:
err = __ip_append_data(sk, fl4, &queue, &cork,
¤t-》task_frag, getfrag,
from, length, transhdrlen, flags);
我們將在后面看到這個函數是如何工作的,因為不管 socket 是否被 cork,最后都會執行它。
現在,我們只需要知道__ip_append_data 將創建一個 skb,向其追加數據,并將該 skb 添加 到傳入的 queue 變量中。如果追加數據失敗,則調用__ip_flush_pending_frame 丟棄數據 并向上返回錯誤(指針類型):
if (err) {
__ip_flush_pending_frames(sk, &queue, &cork);
return ERR_PTR(err);
}
最后,如果沒有發生錯誤,__ip_make_skb 將 skb 出隊,添加 IP 選項,并返回一個準備好傳遞給更底層發送的 skb:
return __ip_make_skb(sk, fl4, &queue, &cork);
2.9.2 發送數據
如果沒有錯誤,skb 就會交給 udp_send_skb,后者會繼續將其傳給下一層協議,IP 協議:
err = PTR_ERR(skb);
if (!IS_ERR_OR_NULL(skb))
err = udp_send_skb(skb, fl4);
goto out;
如果有錯誤,錯誤計數就會有相應增加。后面的“錯誤計數”部分會詳細介紹。
2.10 沒有被 cork 的數據時的慢路徑
如果使用了 UDP corking,但之前沒有數據被 cork,則慢路徑開始:
對 socket 加鎖
檢查應用程序是否有 bug:已經被 cork 的 socket 是否再次被 cork
設置該 UDP flow 的一些參數,為 corking 做準備
將要發送的數據追加到現有數據
udp_sendmsg 代碼繼續向下看,就是這一邏輯:
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
這個函數簡單封裝了__ip_append_data,在調用后者之前,做了兩件重要的事情:
檢查是否從用戶傳入了 MSG_PROBE 標志。該標志表示用戶不想真正發送數據,只是做路徑探測(例如,確定PMTU)
檢查 socket 的發送隊列是否為空。如果為空,意味著沒有 cork 數據等待處理,因此調用 ip_setup_cork 來設置 corking
一旦處理了上述條件,就調用__ip_append_data 函數,該函數包含用于將數據處理成數據包的大量邏輯。
2.10.2 __ip_append_data
如果 socket 是 corked,則從 ip_append_data 調用此函數;如果 socket 未被 cork,則從 ip_make_skb 調用此函數。在任何一種情況下,函數都將分配一個新緩沖區來存儲傳入的數據,或者將數據附加到現有數據中。這種工作的方式圍繞 socket 的發送隊列。等待發送的現有數據(例如,如果 socket 被 cork) 將在隊列中有一個對應條目,可以被追加數據。
這個函數很復雜,它執行很多計算以確定如何構造傳遞給下面的網絡層的 skb。
該函數的重點包括:
如果硬件支持,則處理 UDP Fragmentation Offload(UFO)。絕大多數網絡硬件不支持 UFO。如果你的網卡驅動程序支持它,它將設置 NETIF_F_UFO 標記位
處理支持分散/收集( scatter/gather)IO 的網卡。許多 卡都支持此功能,并使用 NETIF_F_SG 標志進行通告。支持該特性的網卡可以處理數據 被分散到多個 buffer 的數據包;內核不需要花時間將多個緩沖區合并成一個緩沖區中。避 免這種額外的復制會提升性能,大多數網卡都支持此功能
通過調用 sock_wmalloc 跟蹤發送隊列的大小。當分配新的 skb 時,skb 的大小由創建它 的 socket 計費(charge),并計入 socket 發送隊列的已分配字節數。如果發送隊列已經 沒有足夠的空間(超過計費限制),則 skb 并分配失敗并返回錯誤。我們將在下面的調優部分中看到如何設置 socket 發送隊列大小(txqueuelen)
更新錯誤統計信息。此函數中的任何錯誤都會增加“discard”計數。我們將在下面的監控部分中看到如何讀取此值
函數執行成功后返回 0,以及一個適用于網絡設備傳輸的 skb。
在 unorked 情況下,持有 skb 的 queue 被作為參數傳遞給上面描述的__ip_make_skb,在那里 它被出隊并通過 udp_send_skb 發送到更底層。
在 cork 的情況下,__ip_append_data 的返回值向上傳遞。數據位于發送隊列中,直到 udp_sendmsg 確定是時候調用 udp_push_pending_frames 來完成 skb,后者會進一步調用 udp_send_skb。
2.10.3 Flushing corked sockets
現在,udp_sendmsg 會繼續,檢查__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);
我們來看看每個情況:
如果出現錯誤(錯誤為非零),則調用 udp_flush_pending_frames,這將取消 cork 并從 socket 的發送隊列中刪除所有數據
如果在未指定 MSG_MORE 的情況下發送此數據,則調用 udp_push_pending_frames,它將數據傳遞到更下面的網絡層
如果發送隊列為空,請將 socket 標記為不再 cork
如果追加操作完成并且有更多數據要進入 cork,則代碼將做一些清理工作,并返回追加數據的長度:
ip_rt_put(rt);
if (free)
kfree(ipc.opt);
if (!err)
return len;
這就是內核如何處理 corked UDP sockets 的。
2.11 Error accounting
如果:
non-corking 快速路徑創建 skb 失敗,或 udp_send_skb 返回錯誤,或
ip_append_data 無法將數據附加到 corked UDP socket,或
當 udp_push_pending_frames 調用 udp_send_skb 發送 corked skb 時后者返回錯誤
僅當返回的錯誤是 ENOBUFS(內核無可用內存)或 socket 已設置 SOCK_NOSPACE(發送隊列已滿)時,SNDBUFERRORS 統計信息才會增加:
/*
* 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;
我們接下來會在后面的數據監控里看到如何讀取這些計數。
3. udp_send_skb
udp_sendmsg 通過調用 udp_send_skb 函數將 skb 送到下一網絡層,在本文中是 IP 協議層。這個函數做了一些重要的事情:
向 skb 添加 UDP 頭
處理校驗和:軟件校驗和,硬件校驗和或無校驗和(如果禁用)
調用 ip_send_skb 將 skb 發送到 IP 協議層
更新發送成功或失敗的統計計數器
首先,創建 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 校驗和選項被關閉(setsockopt 帶 SO_NO_CHECK 參數),它將被標記為校 驗和關閉
接下來,如果硬件支持 UDP 校驗和,則將調用 udp4_hwcsum 來設置它。請注意,如果數 據包是分段的,內核將在軟件中生成校驗和,可以在 udp4_hwcsum 的源代碼中看到這一點
最后,通過調用 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,則根據 RFC 768,校驗為全 1( transmitted as all ones (the equivalent in one’s complement arithmetic))。最后,將 skb 傳遞給 IP 協議層并增加統計計數:
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 統計。如果 IP 協議層報告錯誤,并且錯誤 是 ENOBUFS(內核缺少內存)而且錯誤 queue(inet-》recverr)沒有啟用,則更新 SNDBUFERRORS。
接下來看看如何在 Linux 內核中監視和調優 UDP 協議層。
4. 監控:UDP 層統計
兩個非常有用的獲取 UDP 協議統計文件:
/proc/net/snmp
/proc/net/udp
4.1 /proc/net/snmp
監控 UDP 協議層統計:
cat /proc/net/snmp | grep Udp:
要準確地理解這些計數,需要仔細地閱讀內核代碼。一些類型的錯誤計數并不是只出現在一種計數中,而可能是出現在多個計數中。
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 協議層發現的某些錯誤會出現在其他協議層的統計信息中。一個例子:路由錯誤 。 udp_sendmsg 發現的路由錯誤將導致 IP 協議層的 OutNoRoutes 統計增加。
4.2 /proc/net/udp
監控 UDP socket 統計:
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.
打印這些計數的代碼在net/ipv4/udp.c。
5. 調優:socket 發送隊列內存大小
發送隊列(也叫“寫隊列”)的最大值可以通過設置 net.core.wmem_max sysctl 進行修改。
$ sudo sysctl -w net.core.wmem_max=8388608
sk-》sk_write_queue 用 net.core.wmem_default 初始化, 這個值也可以調整。
調整初始發送 buffer 大小:
$ sudo sysctl -w net.core.wmem_default=8388608
也可以通過從應用程序調用 setsockopt 并傳遞 SO_SNDBUF 來設置 sk-》sk_write_queue 。通過 setsockopt 設置的最大值是 net.core.wmem_max。
不過,可以通過 setsockopt 并傳遞 SO_SNDBUFFORCE 來覆蓋 net.core.wmem_max 限制, 這需要 CAP_NET_ADMIN 權限。
每次調用__ip_append_data 分配 skb 時,sk-》sk_wmem_alloc 都會遞增。正如我們所看到 的,UDP 數據報傳輸速度很快,通常不會在發送隊列中花費太多時間。
6. 總結
本文重點分析了數據包在傳輸層(UDP協議)的發送過程,并進行了監控和調優,后面數據包將到達 IP 協議層,下次再分享,感謝閱讀。
Reference:https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data
編輯:jq
-
LINUX內核
+關注
關注
1文章
316瀏覽量
21651
原文標題:Linux內核網絡udp數據包發送(二)——UDP協議層分析
文章出處:【微信號:gh_6fde77c41971,微信公眾號:FPGA干貨】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論