Linux Kernel UDP的部份的程式碼不多,所以極為容易了解大致的工作原理。
UDP的接收端大致工作模型分為四部份:
- Link Layer (就是IP模組),接收封包之後,如果是本機要處理,就將封包送給UDP/TCP層來處理。
- 如果是UDP就呼叫Kernel UDP模組來處理。
- UDP模組對封包進行簡單的檢查,如果沒有問題,就把封包放到接收QUEUE當中。等待使用者模式的程式來處理。
- User Space程式呼叫read()或Socket等system call,讀取已經放入UDP Receive Queue。接收佇列中的資料。
下面的Trace基本上就是看步驟二是如何工作的?
udp_rcv() 是整個 UDP 模組的進入點。程式在 ./net/udp.c 當中。
這個Call函式是在AF_INET protocol init 的時候,由UDP註冊給 Network Layer 的Call Back 。
./net/af_inet.c
...
static const struct net_protocol udp_protocol = {
.handler = udp_rcv,
.err_handler = udp_err,
.gso_send_check = udp4_ufo_send_check,
.gso_segment = udp4_ufo_fragment,
.no_policy = 1,
.netns_ok = 1,
};
...
網路層程式碼處理完一個輸入的資料封包後,如果該封包是發往本機的,並且其上層協議是UDP,那麼會呼叫這個被註冊的Call Back。
{
return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP);
}
//
// All we need to do is get the socket, and then do a checksum.
// __udp4_lib_rcv() 這個程式幾乎就是整個UDP工作的中控,其他程式都是圍繞著這個程式來服務他。
// 先看他的三個輸入:
// *skb : 就是由上層傳下來的封包。
// udp_table: 這個資料結構就是UDP要傳送給應用程式的BUFFER。由於等著要收封包的應用程式,不是一個,所以這是一個很大的Hash Table,UDP模組要分辨出,封包屬於哪一個應用程式,將封包放入這個應用程式的QUEUE。
// proto: 他有兩個L4 Protocol選項: IPPROTO_UDP 或 IPPROTO_UDPLITE
int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
int proto)
{
struct sock *sk;
struct udphdr *uh;
unsigned short ulen;
struct rtable *rt = skb_rtable(skb);
__be32 saddr, daddr;
struct net *net = dev_net(skb->dev);
/*
* Validate the packet.
*/
// pskb_may_pull() 這程式很簡單,只是看一下送進來的封包,如果SIZE比UPD Header的SIZE還小,顯然是沒有用的封包,那就沒有處理的必要了。
// 這是一個inline function,位於 ./include/linux/skbuff.h 之中。
//
if (!pskb_may_pull(skb, sizeof(struct udphdr)))
goto drop; /* No space for header. */
// 把封包當中要用的參數拿出來
uh = udp_hdr(skb);
ulen = ntohs(uh->len);
// 取出封包的來源地址與目的地址
saddr = ip_hdr(skb)->saddr;
daddr = ip_hdr(skb)->daddr;
// skb中的資料長度不能小於UDP Header指示的資料包長度,如果太小就到短封包處理,如果大於,就是說封包之中是有資料的,需要繼續處理。
if (ulen > skb->len)
goto short_packet;
if (proto == IPPROTO_UDP) {
/* UDP validates ulen. */
// 這裡做了兩件事:
// UDP的資料長度必須大於HEADER。
// 由於IP模組對於不合理的UDP短封包,會填入一些垃圾資訊,所以 pskb_trim_rcsum()會處理掉垃圾資訊,重新計算CHECK SUM。
if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))
goto short_packet;
// 由於SIZE有可能改變,所以又重新指定了一次uh變數。
uh = udp_hdr(skb);
}
// 對封包做CHECK SUM檢查
if (udp4_csum_init(skb, uh, proto))
goto csum_error;
// 如果是Multcasting,在這裡就離開,做特殊的處理。
if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
return __udp4_lib_mcast_deliver(net, skb, uh,
saddr, daddr, udptable);
// 如果是一般封包,就要尋找應用程式QUEUE的位置,準備將資料往應用程式傳送
// 根據封包的 source port 和 object port 查詢 udptable,尋找應該接收該資料包的傳輸控制塊
sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
if (sk != NULL) {
// 如果找到了,呼叫 udp_queue_rcv_skb (),將資料放入Queue當中
int ret = udp_queue_rcv_skb(sk, skb);
// Call sock_put, 如果 Queue 已經沒有東西 -> call sk_free()
sock_put(sk);
/* a return value > 0 means to resubmit the input, but
* it wants the return to be -protocol, or 0
*/
if (ret > 0)
return -ret;
return 0;
}
// 接下來,就要處理這些沒有要人要處理的封包。
// 先做一些 IPSec的規則檢查,做了甚麼,這裡沒有細看。
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto drop;
nf_reset(skb);
/* No socket. Drop packet silently, if checksum is wrong */
if (udp_lib_checksum_complete(skb))
goto csum_error;
//累計輸入資料包錯誤統計值,
UDP_INC_STATS_BH(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
// 回覆ICMP,這個PORT,封包無法送達
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
/*
* Hmm. We got an UDP packet to a port to which we
* don't wanna listen. Ignore it.
*/
// 與這些無法處理、送達的封包說再見。
kfree_skb(skb);
return 0;
short_packet:
// 短封包,就在LOG當中顯示一下訊息
LIMIT_NETDEBUG(KERN_DEBUG "UDP%s: short packet: From %pI4:%u %d/%d to %pI4:%u\n",
proto == IPPROTO_UDPLITE ? "-Lite" : "",
&saddr,
ntohs(uh->source),
ulen,
skb->len,
&daddr,
ntohs(uh->dest));
goto drop;
csum_error:
/*
* RFC1122: OK. Discards the bad packet silently (as far as
* the network is concerned, anyway) as per 4.1.3.4 (MUST).
*/
LIMIT_NETDEBUG(KERN_DEBUG "UDP%s: bad checksum. From %pI4:%u to %pI4:%u ulen %d\n",
proto == IPPROTO_UDPLITE ? "-Lite" : "",
&saddr,
ntohs(uh->source),
&daddr,
ntohs(uh->dest),
ulen);
drop:
// 在 /proc/net/ipv4/snmp 當中,紀錄UDPLITE的InErrors 加一
UDP_INC_STATS_BH(net, UDP_MIB_INERRORS, proto == IPPROTO_UDPLITE);
kfree_skb(skb);
return 0;
}
以上大致上可以知道,下一步的處理程式,兵分二路,其中 _udp4_lib_mcast_deliver() 處理Multicasting,其他的封包送到 udp_queue_rcv_skb () 來處理,規屬於UDPLITE 。
static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
struct udphdr *uh,
__be32 saddr, __be32 daddr,
struct udp_table *udptable)
{
struct sock *sk, *stack[256/sizeof(struct sock *)];
struct udp_hslot *hslot = udp_hashslot(udptable, net, ntohs(uh->dest));
int dif;
unsigned int i, count = 0;
spin_lock(&hslot->lock);
sk = sk_nulls_head(&hslot->head);
dif = skb->dev->ifindex;
// Multicasting 的封包比較長,所以要一筆一筆處理到結束
sk = udp_v4_mcast_next(net, sk, uh->dest, daddr, uh->source, saddr, dif);
while (sk) {
// 如果有需多封包,就一筆一筆放到STACK當中
stack[count++] = sk;
// 放好之後,就讀下一筆來處理sk = udp_v4_mcast_next(net, sk_nulls_next(sk), uh->dest, daddr, uh->source, saddr, dif); // 如果STACK滿了封包還沒有結束,必須要離開迴圈。離開之前,還是要把Buffer處理到USER SPACE (Call flush_stack)
if (unlikely(count == ARRAY_SIZE(stack))) {
if (!sk)
break;
flush_stack(stack, count, skb, ~0);
count = 0;
}
}
/*
* before releasing chain lock, we must take a reference on sockets
*/
for (i = 0; i < count; i++)
sock_hold(stack[i]);
spin_unlock(&hslot->lock);
/*
* do the slow work with no lock held
*/
if (count) {
// call flush_stack 把PACKET送到 SOCKET 去。
flush_stack(stack, count, skb, count - 1);
// 把stack的記憶體Free調
for (i = 0; i < count; i++)
sock_put(stack[i]);
} else {
kfree_skb(skb);
}
return 0;
}
// 接下來看 flush_stack(),這就是將Stack中的資料,一筆筆COPY到 SOCKET BUFFER。
static void flush_stack(struct sock **stack, unsigned int count,
struct sk_buff *skb, unsigned int final)
{
unsigned int i;
struct sk_buff *skb1 = NULL;
struct sock *sk;
for (i = 0; i < count; i++) {
sk = stack[i];
if (likely(skb1 == NULL))
skb1 = (i == final) ? skb : skb_clone(skb, GFP_ATOMIC);
// 上面的skb_clone() 先依照 sk 的內容,建立一個資料結構。
// 如果 socket buffer 之中的記憶體已經用光,就做不下去了,於是紀錄一個Error。
if (!skb1) {
atomic_inc(&sk->sk_drops);
UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_RCVBUFERRORS,
IS_UDPLITE(sk));
UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS,
IS_UDPLITE(sk));
}
// 如果可以拿到Buffer,就可以完成最終完成資料傳送的Call
if (skb1 && udp_queue_rcv_skb(sk, skb1) <= 0)
skb1 = NULL;
}
if (unlikely(skb1))
kfree_skb(skb1);
}
大致就這樣,所以 udp_mem 這個 BUFFER的SIZE,的確對於 INERRORS 會有影響。

沒有留言:
張貼留言