linux 内核源码udp,Linux内核源代码解析之——UDP协议实现

本文将会分析下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要好很多,在一些对性能要求很高,同时对质量要求不怎么高的情况下,非常适用,比如视频、聊天信息等。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值