1. 程式人生 > >基於Linux核心的UDP協議原始碼分析

基於Linux核心的UDP協議原始碼分析

  當一個數據包(package)經過IP層的處理之後,最終呼叫ip_local_deliever()函式,這個函式會根據這個資料包(packet)的傳輸層頭兒確定其採用的傳輸協議,如果是UDP協議,將會呼叫udp_rcv()函式。至此,進入傳輸層的範圍。
UDP協議棧的報頭定義如下:

struct udphdr {  
  unsigned short    source;//源埠   
  unsigned short    dest;//目的埠   
  unsigned short    len;//資料包長度   
  unsigned short    check;//檢驗和   
}; 

  udp_rcv()函式的原始碼如下:

/*
 *	All we need to do is get the socket, and then do a checksum. 
 */
 
int udp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
	unsigned long daddr, unsigned short len,
	unsigned long saddr, int redo, struct inet_protocol *protocol)
{
  	struct sock *sk;
  	struct udphdr *uh;
	unsigned short ulen;
	int addr_type = IS_MYADDR;
	
	if(!dev || dev->pa_addr!=daddr) //檢查這個資料包是不是傳送給本地的資料包
		addr_type=ip_chk_addr(daddr); //該函式定義在devinet.c中,用於檢查ip地址是否是本地或多播、廣播地址
		
	/*
	 *	Get the header.
	 */
  	uh = (struct udphdr *) skb->h.uh; //獲得UDP資料報的報頭   
  	
  	ip_statistics.IpInDelivers++;

	/*
	 *	Validate the packet and the UDP length.
	 */
	 
	ulen = ntohs(uh->len);
    //引數len表示ip負載長度(IP資料報的資料部分長度)= UDP資料包頭+UDP資料包的資料部分+填充部分長度   
    //ulen表示的是UDP資料報首部和負載部分的長度,所以正常情況下len>=ulen  
	if (ulen > len || len < sizeof(*uh) || ulen < sizeof(*uh))  //進行UDP資料包校驗 
	{
		printk("UDP: short packet: %d/%d\n", ulen, len);
		udp_statistics.UdpInErrors++;
		kfree_skb(skb, FREE_WRITE);
		return(0);
	}

	if (uh->check && udp_check(uh, len, saddr, daddr)) 
	{
		/* <
[email protected]
> wants to know, who sent it, to go and stomp on the garbage sender... */ printk("UDP: bad checksum. From %08lX:%d to %08lX:%d ulen %d\n", ntohl(saddr),ntohs(uh->source), ntohl(daddr),ntohs(uh->dest), ulen); udp_statistics.UdpInErrors++; kfree_skb(skb, FREE_WRITE); return(0); } len=ulen; //對len賦值為實際的UDP資料報長度 #ifdef CONFIG_IP_MULTICAST if (addr_type!=IS_MYADDR) { /* * Multicasts and broadcasts go to each listener. */ struct sock *sknext=NULL; //next指標 /*get_sock_mcast 獲取在對應埠的多播套接字佇列 *下面函式的引數依次表示:sock結構指標,本地埠,遠端地址,遠端埠,本地地址 */ sk=get_sock_mcast(udp_prot.sock_array[ntohs(uh->dest)&(SOCK_ARRAY_SIZE-1)], uh->dest, saddr, uh->source, daddr); if(sk) { do { struct sk_buff *skb1; sknext=get_sock_mcast(sk->next, uh->dest, saddr, uh->source, daddr); //下一個滿足條件的套接字 if(sknext) skb1=skb_clone(skb,GFP_ATOMIC); else skb1=skb; if(skb1) udp_deliver(sk, uh, skb1, dev,saddr,daddr,len); //對滿足條件的套接字呼叫傳送函式傳送 sk=sknext; } while(sknext!=NULL); } else kfree_skb(skb, FREE_READ); return 0; } #endif sk = get_sock(&udp_prot, uh->dest, saddr, uh->source, daddr); if (sk == NULL) //沒有找到本地對應的套接字,則進行出錯處理 { udp_statistics.UdpNoPorts++; if (addr_type == IS_MYADDR) { icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0, dev); //回覆ICMP出錯報文,目的主機不可達 } /* * Hmm. We got an UDP broadcast to a port to which we * don't wanna listen. Ignore it. */ skb->sk = NULL; kfree_skb(skb, FREE_WRITE); return(0); } return udp_deliver(sk,uh,skb,dev, saddr, daddr, len); //呼叫函式傳送套接字 }

  這裡首先會獲取到這個pakcet的UDP頭部資訊(582-584),同時獲取到UDP的長度(590),接著根據長度判斷這個UDP包是否壞了(592-598),如果壞了,直接把這個包對應的記憶體空間釋放(596 kfree_skb(skb, FREE_WRITE);)。如果通過了,就做UDP的checksum(600~611),如果checksum通不過,就把這個包對應的記憶體空間釋放(609).

  從617行開始,根據之前取得得包頭資訊判斷這個包是否要進行mcast,如果不是(這裡暫時不討論mcast的情況),接著走下去。

  從647行開始UDP資料包需要找到對應的“寄生體”,也就是struct sock *結構。sock將是這個資料包在linux核心裡的最終歸宿(它的核心之旅將在這裡終結)。
  一個數據包如何找到它對應的sock結構?這需要兩個要點:1、搜尋的物件。2、搜尋的索引。

sk = get_sock(&udp_prot, uh->dest, saddr, uh->source, daddr);

  這行程式碼表示了搜尋的物件是udp_prot,搜尋的索引條件包括目的埠、源地址、源埠、目的地址。該函式定義的位置在檔案af_inet.c中,具體程式碼為:

/* 
 *  Deliver a datagram to broadcast/multicast sockets. 
 */  
   
struct sock *get_sock_mcast(struct sock *sk, //套接字指標   
     unsigned short num,//本地埠   
     unsigned long raddr,//遠端地址   
     unsigned short rnum,//遠端埠   
     unsigned long laddr)//本地地址    
{  
    struct sock *s;  
    unsigned short hnum;  
  
    hnum = ntohs(num);  
  
    /* 
     * SOCK_ARRAY_SIZE must be a power of two.  This will work better 
     * than a prime unless 3 or more sockets end up using the same 
     * array entry.  This should not be a problem because most 
     * well known sockets don't overlap that much, and for 
     * the other ones, we can just be careful about picking our 
     * socket number when we choose an arbitrary one. 
     */  
      
    s=sk;  
  
    for(; s != NULL; s = s->next)   
    {  
        if (s->num != hnum) //本地埠不符合,跳過   
            continue;  
        if(s->dead && (s->state == TCP_CLOSE))//dead=1表示該sock結構已經處於釋放狀態   
            continue;  
        if(s->daddr && s->daddr!=raddr)//sock的遠端地址不等於條件中的遠端地址   
            continue;  
        if (s->dummy_th.dest != rnum && s->dummy_th.dest != 0)  
            continue;  
        if(s->saddr  && s->saddr!=laddr)//sock的本地地址不等於條件的本地地址   
            continue;  
        return(s);  
    }  
    return(NULL);  
}  

  找到sk之後,就可以把對應的資料包掛接到sk上去了(呼叫udp_deliver(sk,uh,skb,dev, saddr, daddr, len) 664)。若沒有找到,說明出錯了,對於UDP而言,出錯了就把資料包刪除,也就是直接把資料包所有的記憶體都釋放掉。(kfree_skb(skb, FREE_WRITE); 660)
  上述過程中呼叫了udp_deliver函式,下面分析udp_deliver函式:

static int udp_deliver(struct sock *sk,  //sock結構指標
			struct udphdr *uh, //UDP頭指標
 			struct sk_buff *skb,  //sk_buff
 			struct device *dev,  //接收的網路裝置
			 long saddr,  //本地地址
 			long daddr, //遠端地址
 			int len) //資料包的長度
{
    //對skb結構相應欄位賦值
	skb->sk = sk;
	skb->dev = dev;
	skb->len = len;

	/*
	 *	These are supposed to be switched. 
	 */
	 
	skb->daddr = saddr;
	skb->saddr = daddr;


	/*
	 *	Charge it to the socket, dropping if the queue is full.
	 */

	skb->len = len - sizeof(*uh);  
	 
	if (sock_queue_rcv_skb(sk,skb)<0)  //呼叫sock_queu_rcv_skb()函式,將skb掛到sk接構中的接收佇列中
	{
		udp_statistics.UdpInErrors++;
		ip_statistics.IpInDiscards++;
		ip_statistics.IpInDelivers--;
		skb->sk = NULL;
		kfree_skb(skb, FREE_WRITE);
		release_sock(sk);
		return(0);
	}
  	udp_statistics.UdpInDatagrams++;
	release_sock(sk);
	return(0);
}

  這個函式的關鍵部分是: if (sock_queue_rcv_skb(sk,skb)<0) (687)。這行程式碼呼叫了sock_queue_rcv_skb函式,這個函式是傳輸層的關鍵,其實也就是把skb與struct sock *sk關聯起來,把skb放入sk裡的skb接收隊裡。
  下面分析sock_queue_rcv_skb函式:

/*
 *	Queue a received datagram if it will fit. Stream and sequenced protocols
 *	can't normally use this as they need to fit buffers in and play with them.
 */

int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
	unsigned long flags;
	if(sk->rmem_alloc + skb->mem_len >= sk->rcvbuf)
		return -ENOMEM;
	save_flags(flags);
	cli();
	sk->rmem_alloc+=skb->mem_len;
	skb->sk=sk;
	restore_flags(flags);
	skb_queue_tail(&sk->receive_queue,skb);
	if(!sk->dead)
		sk->data_ready(sk,skb->len);
	return 0;
}

  到這裡,struct sk_buff *skb就與struct sock *sk關聯上了,而struct socket *sock又包含了struct sock *sk,所以,使用者可以通過socket系統呼叫操作對應的資料包了。 其實UDP簡單概括起來就是把資料從IP層掛接到sock結構上去。因為簡單,所以效能比TCP要好很多,在一些對效能要求很高,同時對質量要求不怎麼高的情況下,非常適用,比如視訊、聊天資訊等。