本文将会分析下Linux内核UDP协议实现。分析UDP协议主要是为了解开传输层协议的神秘面纱。在国内大多数核心技术都喜欢钻研理论,不谈代码,盘桓在意淫阶段。
本文将会以Linux内核1.2.4分析UDP协议实现。
该版本的UDP作者是:
Ross Biro,
Fred N. van Kempen,
感兴趣可以找他们聊聊天。
分析UDP协议只是个开始,希望能通过UDP协议把传输层的套路和核心把握好,然后再深入学习TCP协议,TCP协议才是大头。
当一个数据包(packet)经过IP层的处理之后,最终调用ip_local_deliever()函数,这个函数会根据这个数据包(packet)的传输层头儿确定其采用的传输协议,如果是UDP协议,将会调用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);
/*
*Get the header.
*/
uh = (struct udphdr *) skb->h.uh;
ip_statistics.IpInDelivers++;
/*
*Validate the packet and the UDP length.
*/
ulen = ntohs(uh->len);
if (ulen > len || len < sizeof(*uh) || ulen < sizeof(*uh))
{
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))
{
/* 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;
#ifdef CONFIG_IP_MULTICAST
if (addr_type!=IS_MYADDR)
{
/*
*Multicasts and broadcasts go to each listener.
*/
struct sock *sknext=NULL;
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);
}
/*
* 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。
找到sk之后,就可以把对应的数据包挂接到sk上去了(调用udp_deliver(sk,uh,skb,dev, saddr, daddr, len) 664),而没有找到呢?
没有找到说明出错了,对于UDP而言,出错了就把数据包删除,也就是直接把数据包所有的内存都释放掉。(kfree_skb(skb, FREE_WRITE); 660)
return udp_deliver(sk,uh,skb,dev, saddr, daddr, len);664
这一行代码调用了udp_deliver函数,我们看下udp_deliver函数做了些什么。
static int udp_deliver(struct sock *sk, struct udphdr *uh, struct sk_buff *skb, struct device *dev, long saddr, long daddr, int len)
{
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)
{
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接收队里。
/*
*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要好很多,在一些对性能要求很高,同时对质量要求不怎么高的情况下,非常适用,比如视频、聊天信息等。