一、udp层的数据接收
udp层的数据接收,对于socket而言,就是接收队列的入队操作。在ip层中,如果是本地数据,则会交由ip_local_deliver_finish()函数处理,它会根据传输层协议的类型,交由相应的处理函数,对于udp协议而言,就是udp_rcv():
[code]
/*
* All we need to do is get the socket, and then do a checksum.
*/
int udp_rcv(struct sk_buff *skb)
{
struct sock *sk;
struct udphdr *uh;
unsigned short ulen;
struct rtable *rt = (struct rtable*)skb->dst;
u32 saddr = skb->nh.iph->saddr;
u32 daddr = skb->nh.iph->daddr;
int len = skb->len;
/*
* 数据包至少应有UDP首部长度.
*/
if (!pskb_may_pull(skb, sizeof(struct udphdr)))
goto no_header;
/*获取udp首部指针*/
uh = skb->h.uh;
/* 数据长度,含首部长度 */
ulen = ntohs(uh->len);
/* 数据包长度不够:UDP长度比skb长度小,意味着数据的丢失,而udp长度比udp首部还要小,好像这个不太可能,除非封包出错 ^o^*/
if (ulen > len || ulen < sizeof(*uh))
goto short_packet;
/* 截去udp报文后面的多余报文 */
if (pskb_trim(skb, ulen))
goto short_packet;
/* 开始udp校验和计算,主要查依赖于skb的ip_summumed字段的设置来决定是否需要进行校验和计算 */
if (udp_checksum_init(skb, uh, ulen, saddr, daddr) < 0)
goto csum_error;
/* 转换多播或广播处理例程 */
if(rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
return udp_v4_mcast_deliver(skb, uh, saddr, daddr);
/* 查找数据段对应的socket结构的sk */
sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex);
if (sk != NULL) {
/* 找到了,数据包进入UDP的socket的接收队列*/
int ret = udp_queue_rcv_skb(sk, skb);
sock_put(sk);
/* a return value > 0 means to resubmit the input, but
* it it wants the return to be -protocol, or 0
*/
if (ret > 0)
return -ret;
return 0;
}
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto drop;
/* 没有对应的socket. 如果校验和错误,则丢弃它 */
if (udp_checksum_complete(skb))
goto csum_error;
/* 发送一个目的不可达报文 */
UDP_INC_STATS_BH(UDP_MIB_NOPORTS);
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:
NETDEBUG(if (net_ratelimit())
printk(KERN_DEBUG "UDP: short packet: From %u.%u.%u.%u:%u %d/%d to %u.%u.%u.%u:%u/n",
NIPQUAD(saddr),
ntohs(uh->source),
ulen,
len,
NIPQUAD(daddr),
ntohs(uh->dest)));
no_header:
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
kfree_skb(skb);
return(0);
csum_error:
/*
* RFC1122: OK. Discards the bad packet silently (as far as
* the network is concerned, anyway) as per 4.1.3.4 (MUST).
*/
NETDEBUG(if (net_ratelimit())
printk(KERN_DEBUG "UDP: bad checksum. From %d.%d.%d.%d:%d to %d.%d.%d.%d:%d ulen %d/n",
NIPQUAD(saddr),
ntohs(uh->source),
NIPQUAD(daddr),
ntohs(uh->dest),
ulen));
drop:
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
kfree_skb(skb);
return(0);
}[/code]
函数的核心思想,是根据skb,查找到与之对应的sk,调用udp_v4_lookup()函数实现——udp与tcp一样,socket有一个hash表,这个查找,就是查找hash表的过程。
如果找到了对应的sk,则进入udp_queue_rcv_skb()函数:
[code]/* returns:
* -1: error
* 0: success
* >0: "udp encap" protocol resubmission
*
* Note that in the success and error cases, the skb is assumed to
* have either been requeued or freed.
*/
static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)
{
/* 获取sk对应的udp_sock结构指针 */
struct udp_sock *up = udp_sk(sk);
/*
* Charge it to the socket, dropping if the queue is full.
*/
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
return -1;
}
if (up->encap_type) {
/*
* This is an encapsulation socket, so let's see if this is
* an encapsulated packet.
* If it's a keepalive packet, then just eat it.
* If it's an encapsulateed packet, then pass it to the
* IPsec xfrm input and return the response
* appropriately. Otherwise, just fall through and
* pass this up the UDP socket.
*/
int ret;
ret = udp_encap_rcv(sk, skb);
if (ret == 0) {
/* Eat the packet .. */
kfree_skb(skb);
return 0;
}
if (ret < 0) {
/* process the ESP packet */
ret = xfrm4_rcv_encap(skb, up->encap_type);
UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
return -ret;
}
/* FALLTHROUGH -- it's a UDP Packet */
}
/* 如果需要校验 */
if (sk->sk_filter && skb->ip_summed != CHECKSUM_UNNECESSARY) {
/* 那就校验它吧 */
if (__udp_checksum_complete(skb)) {
/* 结果校验出错,那就算了吧 */
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
kfree_skb(skb);
return -1;
}
/* 已经校验过了,就设置不用再校验了 */
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
/* 设用sock_queue_rcv_skb入队 */
if (sock_queue_rcv_skb(sk,skb)<0) {
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
kfree_skb(skb);
return -1;
}
UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
return 0;
}[/code]
encap_type字段用于判断udp包,是否是一个IPSEC协议的封装报文,这里不关分ipsec,所以接下来的工作,就是校验和计算,然后紧跟着调用sock_queue_rcv_skb():
[code]static inline int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
int err = 0;
int skb_len;
/* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
number of warnings when compiling with -W --ANK
*/
if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=
(unsigned)sk->sk_rcvbuf) {
err = -ENOMEM;
goto out;
}
/* 进入socket层的包过滤 */
err = sk_filter(sk, skb, 1);
if (err)
goto out;
/* 设置skb的一些必要的指针和计数器变量
dev:关连备设指针;
sk:所对应的sk结构;
destructor:函数指针可以初始化成一个在缓冲区释放时完成某些动作的函数。
如果skb不属于一个socket,则其常为NULL。反之,这个函数会被赋值为sock_rfree或sock_wfree.
用于更新socket的队列中的内存容量。*/
skb->dev = NULL;
skb_set_owner_r(skb, sk);
/* 在skb入队之前,保存其长度至临时变量skb_len,这是因为一旦skb入队后,它将被
其它线程处理,skb命运未知。。。。。。,而len值后面还会用到。
*/
skb_len = skb->len;
/* 将skb加入sk的接收队列 */
skb_queue_tail(&sk->sk_receive_queue, skb);
/* 唤醒socket上的接收线程? */
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_data_ready(sk, skb_len);
out:
return err;
}[/code]
对于udp而言,直接调用skb_queue_tail(),将skb加入至sk的sk_receive_queue队列即可。
[color=Red][size=5]一、udp层的数据出队操作[/size][/color]
了解了数据包如何被加入队列,上层socket的数据接收,就是从这个队列中来取数据。udp对应的取数据的函数udp_recvmsg(),后面再来看它是如何被调用的,现在先来分析它的实现:
[code]
/*
* This should be easy, if there is something there we
* return it, otherwise we block.
*/
static int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len, int noblock, int flags, int *addr_len)
{
struct inet_sock *inet = inet_sk(sk); /* 取得sk对应的inet_sock指针 */
struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
struct sk_buff *skb;
int copied, err;
/*
* 校验地址长度
*/
if (addr_len)
*addr_len=sizeof(*sin);
/* 如果sk队列中有错误的信息,则设用ip_recv_error函数 */
if (flags & MSG_ERRQUEUE)
return ip_recv_error(sk, msg, len);
try_again:
/* 出队 */
skb = skb_recv_datagram(sk, flags, noblock, &err);
if (!skb)
goto out;
/* 需要拷贝的数据不包含UDP首部 */
copied = skb->len - sizeof(struct udphdr);
/* 如果用户提供的缓存不够,则设置为只拷贝用户需要的长度,并设置截短数据标志 */
if (copied > len) {
copied = len;
msg->msg_flags |= MSG_TRUNC;
}
if (skb->ip_summed==CHECKSUM_UNNECESSARY) {
/* 拷贝数据至用户空间 */
err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
copied);
} else if (msg->msg_flags&MSG_TRUNC) {
if (__udp_checksum_complete(skb))
goto csum_copy_err;
err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
copied);
} else {
err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov);
if (err == -EINVAL)
goto csum_copy_err;
}
if (err)
goto out_free;
/* 更新sk的接收时间戳,根据SOCK_RCVTSTAMP标志的设置来决定:选择当前时间还是skb buffer的接收时间 */
sock_recv_timestamp(msg, sk, skb);
/* 拷贝地址等信息. */
if (sin)
{
sin->sin_family = AF_INET;
sin->sin_port = skb->h.uh->source;
sin->sin_addr.s_addr = skb->nh.iph->saddr;
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
}
/* 查看是否设置了一些控制标志,查看某些IP socket选项是否被设置,例如,设置了IP_TOS socket选项,把IP首部中的
tos字段拷贝至用户空间等等,这个工作是由ip_cmsg_recv函数完成的 */
if (inet->cmsg_flags)
ip_cmsg_recv(msg, skb);
/* 设置拷贝的字节数,如果数据段已经被截短,则返回原始实际的长度 */
err = copied;
if (flags & MSG_TRUNC)
err = skb->len - sizeof(struct udphdr);
out_free:
skb_free_datagram(sk, skb);
out:
return err;
csum_copy_err:
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
/* Clear queue. */
if (flags&MSG_PEEK) {
int clear = 0;
spin_lock_bh(&sk->sk_receive_queue.lock);
if (skb == skb_peek(&sk->sk_receive_queue)) {
__skb_unlink(skb, &sk->sk_receive_queue);
clear = 1;
}
spin_unlock_bh(&sk->sk_receive_queue.lock);
if (clear)
kfree_skb(skb);
}
skb_free_datagram(sk, skb);
if (noblock)
return -EAGAIN;
goto try_again;
}[/code]
skb的出队,是通过调用skb_recv_datagram()函数,出队操作时,队列中可能没有数据,如果是阻塞,则需要一直睡眠等待,直到超时或队 列中有数据而被唤醒。另外出队操作,还分为两种情况,一种是将数据包从队列中取中来,将其从原有队列中删除,还有一种可能是设置了MSG_PEEK标志, 它意味着:“查看当前数据,数据将被复制到缓冲区中,但并不从输入队列中删除”。
[code]struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags,
int noblock, int *err)
{
struct sk_buff *skb;
long timeo;
/*
* Caller is allowed not to check sk->sk_err before skb_recv_datagram()
*/
int error = sock_error(sk);
if (error)
goto no_packet;
/* 获取超时时间*/
timeo = sock_rcvtimeo(sk, noblock);
/* 这个循环将一直等到队列中有数据包,直到超时 */
do {
/* Again only user level code calls this function, so nothing
* interrupt level will suddenly eat the receive_queue.
*
* Look at current nfs client by the way...
* However, this function was corrent in any case. 8)
*/
if (flags & MSG_PEEK) {
unsigned long cpu_flags;
/* 如果设置了MSG_PEEK,则设用skb_peek,这里要对接收队列加锁的原因在于:
*/
spin_lock_irqsave(&sk->sk_receive_queue.lock,
cpu_flags);
skb = skb_peek(&sk->sk_receive_queue);
if (skb)
atomic_inc(&skb->users);
spin_unlock_irqrestore(&sk->sk_receive_queue.lock,
cpu_flags);
} else
skb = skb_dequeue(&sk->sk_receive_queue); /* 直接出队 */
/* 找到要收获的葫芦了,摘下来 */
if (skb)
return skb;
/* 如果是非阻塞,就不等了,直接跳出循环,返回 */
error = -EAGAIN;
if (!timeo)
goto no_packet;
} while (!wait_for_packet(sk, err, &timeo));
return NULL;
no_packet:
*err = error;
return NULL;
}[/code]
继续回到udp_recvmsg函数中来,当取出skb后,需要将它拷贝至用户空间。用户空间的数据缓存,用了一个很重要的数据结构struct msghdr来表示:
[code]struct msghdr {
void * msg_name; /* Socket name */
int msg_namelen; /* Length of name */
struct iovec * msg_iov; /* Data blocks */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};[/code]
结构中的msg_name、msg_namelen以及msg_flags分别对应于sys_sendto()[其它接收/发送函数类似]的参数 addr、addr_len以及flags。指针msg_control可以指向一个附加的数据结构,用来提供一些附加的控制信息。后面可以看到 ip_cmsg_recv()使用了它。最重要的是,结构中的指针msg_iov,指向一个iovec结构数据,而msg_iovlen则指明了这个数组 的大小:
[code]struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};[/code]
数组中的每一个元互素,都是struct iovec结构,称之为"io向量",由指向数据缓冲区的指针iov_base和表示缓冲区中数据长度的iov_len构成。这样,一个msghdr中的数据,就由控制信息msg_control和若干个"io向量"组成。
一个疑问是,为什么不设置为一个缓冲区,而要分为若干个缓冲区呢?一个很重要的原因就是,每个数据报文的大小一致,对于ip包而言。46-1500都是为 可能的,那么如果是一个缓冲区的话,就得定义为一个“最大值”,但是如果绝大部份的包,都小于这个最大值,例如为256或512,那么内存空间就浪费得太 多了……。所以,一个好的办法是,用一个个小的缓冲区将数据切分,要浪费,也浪费最后一个缓冲区的空间。——这种设计跟Unix上著名的mbuf好像是一 致的。
OK,了解了msghdr的结构后,再来看数据的拷贝工作,拷贝的核心函数是memcpy_toiovec:
[code]/*
* Copy kernel to iovec. Returns -EFAULT on error.
*
* Note: this modifies the original iovec.
*/
int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len)
{
while (len > 0) {
if (iov->iov_len) {
int copy = min_t(unsigned int, iov->iov_len, len);
if (copy_to_user(iov->iov_base, kdata, copy))
return -EFAULT;
kdata += copy;
len -= copy;
iov->iov_len -= copy;
iov->iov_base += copy;
}
iov++;
}
return 0;
}[/code]
因为每个io向量的缓冲区可能很小,例如100字节,而要拷贝的数据很长,例如1000字节。这样,需要在一个循环里,将数据拷贝至若干个
io向量数组元素当中去。
回到udp_recvmsg中来,它通过设用skb_copy_datagram_iovec()函数完成数据的拷贝工作。来看看它是如何调用memcpy_toiovec的,当然,类似于这样:
[code]int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset,
struct iovec *to, int len)
{
return memcpy_toiovec(to, skb->data+offset, len);
}[/code]
这样的调用,该是多么美好呀,这样的日子曾经是有过,不过已经一去不复返了。
考虑到skb 结构的复杂性,例如分片,拷贝工作要复杂得多:
[code]
/**
* skb_copy_datagram_iovec - Copy a datagram to an iovec.
* @skb: buffer to copy
* @offset: offset in the buffer to start copying from
* @to: io vector to copy to
* @len: amount of data to copy from buffer to iovec
*
* Note: the iovec is modified during the copy.
*/
int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset,
struct iovec *to, int len)
{
int start = skb_headlen(skb); /* start表示要从skb的哪里开始拷贝数据 */
int i, copy = start - offset; /* offset 表示调用者指出的缓存开始拷贝的偏移字节,一般情况下,它
与start是重叠的,表示不需要拷贝数据包的首部 */
/* 需要拷贝首部. */
if (copy > 0) {
if (copy > len)
copy = len;
if (memcpy_toiovec(to, skb->data + offset, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
}
/* 遍历每一个分片 */
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
int err;
u8 *vaddr;
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
struct page *page = frag->page;
if (copy > len)
copy = len;
vaddr = kmap(page);
err = memcpy_toiovec(to, vaddr + frag->page_offset +
offset - start, copy);
kunmap(page);
if (err)
goto fault;
if (!(len -= copy))
return 0;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
if (skb_copy_datagram_iovec(list,
offset - start,
to, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
}
start = end;
}
}
if (!len)
return 0;
fault:
return -EFAULT;
}[/code]
现在还没有分析ip的分片,所以,要完全理解这个函数的代码有点难度,等到ip分片分析完了,再来补充完整它。
[code]三、socket层的数据接收[/code]
recv(2)、recvfrom(2)和recvmsg(3),在sys_socketcall()中,最终都会归于一个统一的系统调用sock_recvmsg。例如:
[code]asmlinkage long sys_recv(int fd, void __user * ubuf, size_t size, unsigned flags)
{
return sys_recvfrom(fd, ubuf, size, flags, NULL, NULL);
}[/code]
sys_recv转向了sys_recvfrom():
[code]
/*
* Receive a frame from the socket and optionally record the address of the
* sender. We verify the buffers are writable and if needed move the
* sender address from kernel to user space.
*/
asmlinkage long sys_recvfrom(int fd, void __user * ubuf, size_t size, unsigned flags,
struct sockaddr __user *addr, int __user *addr_len)
{
struct socket *sock;
struct iovec iov;
struct msghdr msg;
char address[MAX_SOCK_ADDR];
int err,err2;
sock = sockfd_lookup(fd, &err);
if (!sock)
goto out;
msg.msg_control=NULL;
msg.msg_controllen=0;
msg.msg_iovlen=1;
msg.msg_iov=&iov;
iov.iov_len=size;
iov.iov_base=ubuf;
msg.msg_name=address;
msg.msg_namelen=MAX_SOCK_ADDR;
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
err=sock_recvmsg(sock, &msg, size, flags);
if(err >= 0 && addr != NULL)
{
err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);
if(err2<0)
err=err2;
}
sockfd_put(sock);
out:
return err;
}[/code]
函数先调用sockfd_lookup,根据socket描述符查找以相应的sock结构,然后封装了一个msghdr结构后,接着调用sock_recvmsg()。
[code]int sock_recvmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
struct kiocb iocb;
struct sock_iocb siocb;
int ret;
init_sync_kiocb(&iocb, NULL);
iocb.private = &siocb;
ret = __sock_recvmsg(&iocb, sock, msg, size, flags);
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&iocb);
return ret;
}[/code]
iocb和siocb用于内核和socket的io控制,函数中主要初始化了iocb,siocb的初始化,在__sock_recvmsg中完成。
进一步的按收动作,是通过__sock_recvmsg函数设用完成的:
[code]static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
int err;
struct sock_iocb *si = kiocb_to_siocb(iocb);
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size;
si->flags = flags;
err = security_socket_recvmsg(sock, msg, size, flags);
if (err)
return err;
return sock->ops->recvmsg(iocb, sock, msg, size, flags);
}
[/code]
初始化完siocb,也就是si后,调用协议簇的recvmsg函数,对于UDP而言,是sock_common_recvmsg():
[code]int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
struct sock *sk = sock->sk; /*取得sock结构对应的sk指针*/
int addr_len = 0;
int err;
err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
flags & ~MSG_DONTWAIT, &addr_len);
if (err >= 0)
msg->msg_namelen = addr_len;
return err;
}[/code]
于是,udp_recvmsg就被调用了。整个过程也就结束了。
最后再回到sys_recvfrom中来,如果用户调用时,需要返回对方的地址信息:
[code] if(err >= 0 && addr != NULL)
{
err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);
if(err2<0)
err=err2;
}[/code]
就需要调用move_addr_to_user() 函数来完成,前面分析msghdr结构时已经提到其msg_name成员变量,它包含了地址的相应信息,msg.msg_namelen成员变量,决定了地址的长度信息:
[code]int move_addr_to_user(void *kaddr, int klen, void __user *uaddr, int __user *ulen)
{
int err;
int len;
if((err=get_user(len, ulen))) //缓存长度校验
return err;
if(len>klen)
len=klen;
if(len<0 || len> MAX_SOCK_ADDR)
return -EINVAL;
if(len)
{
if(copy_to_user(uaddr,kaddr,len)) //拷贝地址信息
return -EFAULT;
}
/*
* "fromlen shall refer to the value before truncation.."
* 1003.1g
*/
return __put_user(klen, ulen);
}[/code]
udp层的数据接收,对于socket而言,就是接收队列的入队操作。在ip层中,如果是本地数据,则会交由ip_local_deliver_finish()函数处理,它会根据传输层协议的类型,交由相应的处理函数,对于udp协议而言,就是udp_rcv():
[code]
/*
* All we need to do is get the socket, and then do a checksum.
*/
int udp_rcv(struct sk_buff *skb)
{
struct sock *sk;
struct udphdr *uh;
unsigned short ulen;
struct rtable *rt = (struct rtable*)skb->dst;
u32 saddr = skb->nh.iph->saddr;
u32 daddr = skb->nh.iph->daddr;
int len = skb->len;
/*
* 数据包至少应有UDP首部长度.
*/
if (!pskb_may_pull(skb, sizeof(struct udphdr)))
goto no_header;
/*获取udp首部指针*/
uh = skb->h.uh;
/* 数据长度,含首部长度 */
ulen = ntohs(uh->len);
/* 数据包长度不够:UDP长度比skb长度小,意味着数据的丢失,而udp长度比udp首部还要小,好像这个不太可能,除非封包出错 ^o^*/
if (ulen > len || ulen < sizeof(*uh))
goto short_packet;
/* 截去udp报文后面的多余报文 */
if (pskb_trim(skb, ulen))
goto short_packet;
/* 开始udp校验和计算,主要查依赖于skb的ip_summumed字段的设置来决定是否需要进行校验和计算 */
if (udp_checksum_init(skb, uh, ulen, saddr, daddr) < 0)
goto csum_error;
/* 转换多播或广播处理例程 */
if(rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
return udp_v4_mcast_deliver(skb, uh, saddr, daddr);
/* 查找数据段对应的socket结构的sk */
sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex);
if (sk != NULL) {
/* 找到了,数据包进入UDP的socket的接收队列*/
int ret = udp_queue_rcv_skb(sk, skb);
sock_put(sk);
/* a return value > 0 means to resubmit the input, but
* it it wants the return to be -protocol, or 0
*/
if (ret > 0)
return -ret;
return 0;
}
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto drop;
/* 没有对应的socket. 如果校验和错误,则丢弃它 */
if (udp_checksum_complete(skb))
goto csum_error;
/* 发送一个目的不可达报文 */
UDP_INC_STATS_BH(UDP_MIB_NOPORTS);
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:
NETDEBUG(if (net_ratelimit())
printk(KERN_DEBUG "UDP: short packet: From %u.%u.%u.%u:%u %d/%d to %u.%u.%u.%u:%u/n",
NIPQUAD(saddr),
ntohs(uh->source),
ulen,
len,
NIPQUAD(daddr),
ntohs(uh->dest)));
no_header:
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
kfree_skb(skb);
return(0);
csum_error:
/*
* RFC1122: OK. Discards the bad packet silently (as far as
* the network is concerned, anyway) as per 4.1.3.4 (MUST).
*/
NETDEBUG(if (net_ratelimit())
printk(KERN_DEBUG "UDP: bad checksum. From %d.%d.%d.%d:%d to %d.%d.%d.%d:%d ulen %d/n",
NIPQUAD(saddr),
ntohs(uh->source),
NIPQUAD(daddr),
ntohs(uh->dest),
ulen));
drop:
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
kfree_skb(skb);
return(0);
}[/code]
函数的核心思想,是根据skb,查找到与之对应的sk,调用udp_v4_lookup()函数实现——udp与tcp一样,socket有一个hash表,这个查找,就是查找hash表的过程。
如果找到了对应的sk,则进入udp_queue_rcv_skb()函数:
[code]/* returns:
* -1: error
* 0: success
* >0: "udp encap" protocol resubmission
*
* Note that in the success and error cases, the skb is assumed to
* have either been requeued or freed.
*/
static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)
{
/* 获取sk对应的udp_sock结构指针 */
struct udp_sock *up = udp_sk(sk);
/*
* Charge it to the socket, dropping if the queue is full.
*/
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
return -1;
}
if (up->encap_type) {
/*
* This is an encapsulation socket, so let's see if this is
* an encapsulated packet.
* If it's a keepalive packet, then just eat it.
* If it's an encapsulateed packet, then pass it to the
* IPsec xfrm input and return the response
* appropriately. Otherwise, just fall through and
* pass this up the UDP socket.
*/
int ret;
ret = udp_encap_rcv(sk, skb);
if (ret == 0) {
/* Eat the packet .. */
kfree_skb(skb);
return 0;
}
if (ret < 0) {
/* process the ESP packet */
ret = xfrm4_rcv_encap(skb, up->encap_type);
UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
return -ret;
}
/* FALLTHROUGH -- it's a UDP Packet */
}
/* 如果需要校验 */
if (sk->sk_filter && skb->ip_summed != CHECKSUM_UNNECESSARY) {
/* 那就校验它吧 */
if (__udp_checksum_complete(skb)) {
/* 结果校验出错,那就算了吧 */
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
kfree_skb(skb);
return -1;
}
/* 已经校验过了,就设置不用再校验了 */
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
/* 设用sock_queue_rcv_skb入队 */
if (sock_queue_rcv_skb(sk,skb)<0) {
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
kfree_skb(skb);
return -1;
}
UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
return 0;
}[/code]
encap_type字段用于判断udp包,是否是一个IPSEC协议的封装报文,这里不关分ipsec,所以接下来的工作,就是校验和计算,然后紧跟着调用sock_queue_rcv_skb():
[code]static inline int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
int err = 0;
int skb_len;
/* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
number of warnings when compiling with -W --ANK
*/
if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=
(unsigned)sk->sk_rcvbuf) {
err = -ENOMEM;
goto out;
}
/* 进入socket层的包过滤 */
err = sk_filter(sk, skb, 1);
if (err)
goto out;
/* 设置skb的一些必要的指针和计数器变量
dev:关连备设指针;
sk:所对应的sk结构;
destructor:函数指针可以初始化成一个在缓冲区释放时完成某些动作的函数。
如果skb不属于一个socket,则其常为NULL。反之,这个函数会被赋值为sock_rfree或sock_wfree.
用于更新socket的队列中的内存容量。*/
skb->dev = NULL;
skb_set_owner_r(skb, sk);
/* 在skb入队之前,保存其长度至临时变量skb_len,这是因为一旦skb入队后,它将被
其它线程处理,skb命运未知。。。。。。,而len值后面还会用到。
*/
skb_len = skb->len;
/* 将skb加入sk的接收队列 */
skb_queue_tail(&sk->sk_receive_queue, skb);
/* 唤醒socket上的接收线程? */
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_data_ready(sk, skb_len);
out:
return err;
}[/code]
对于udp而言,直接调用skb_queue_tail(),将skb加入至sk的sk_receive_queue队列即可。
[color=Red][size=5]一、udp层的数据出队操作[/size][/color]
了解了数据包如何被加入队列,上层socket的数据接收,就是从这个队列中来取数据。udp对应的取数据的函数udp_recvmsg(),后面再来看它是如何被调用的,现在先来分析它的实现:
[code]
/*
* This should be easy, if there is something there we
* return it, otherwise we block.
*/
static int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len, int noblock, int flags, int *addr_len)
{
struct inet_sock *inet = inet_sk(sk); /* 取得sk对应的inet_sock指针 */
struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
struct sk_buff *skb;
int copied, err;
/*
* 校验地址长度
*/
if (addr_len)
*addr_len=sizeof(*sin);
/* 如果sk队列中有错误的信息,则设用ip_recv_error函数 */
if (flags & MSG_ERRQUEUE)
return ip_recv_error(sk, msg, len);
try_again:
/* 出队 */
skb = skb_recv_datagram(sk, flags, noblock, &err);
if (!skb)
goto out;
/* 需要拷贝的数据不包含UDP首部 */
copied = skb->len - sizeof(struct udphdr);
/* 如果用户提供的缓存不够,则设置为只拷贝用户需要的长度,并设置截短数据标志 */
if (copied > len) {
copied = len;
msg->msg_flags |= MSG_TRUNC;
}
if (skb->ip_summed==CHECKSUM_UNNECESSARY) {
/* 拷贝数据至用户空间 */
err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
copied);
} else if (msg->msg_flags&MSG_TRUNC) {
if (__udp_checksum_complete(skb))
goto csum_copy_err;
err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
copied);
} else {
err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov);
if (err == -EINVAL)
goto csum_copy_err;
}
if (err)
goto out_free;
/* 更新sk的接收时间戳,根据SOCK_RCVTSTAMP标志的设置来决定:选择当前时间还是skb buffer的接收时间 */
sock_recv_timestamp(msg, sk, skb);
/* 拷贝地址等信息. */
if (sin)
{
sin->sin_family = AF_INET;
sin->sin_port = skb->h.uh->source;
sin->sin_addr.s_addr = skb->nh.iph->saddr;
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
}
/* 查看是否设置了一些控制标志,查看某些IP socket选项是否被设置,例如,设置了IP_TOS socket选项,把IP首部中的
tos字段拷贝至用户空间等等,这个工作是由ip_cmsg_recv函数完成的 */
if (inet->cmsg_flags)
ip_cmsg_recv(msg, skb);
/* 设置拷贝的字节数,如果数据段已经被截短,则返回原始实际的长度 */
err = copied;
if (flags & MSG_TRUNC)
err = skb->len - sizeof(struct udphdr);
out_free:
skb_free_datagram(sk, skb);
out:
return err;
csum_copy_err:
UDP_INC_STATS_BH(UDP_MIB_INERRORS);
/* Clear queue. */
if (flags&MSG_PEEK) {
int clear = 0;
spin_lock_bh(&sk->sk_receive_queue.lock);
if (skb == skb_peek(&sk->sk_receive_queue)) {
__skb_unlink(skb, &sk->sk_receive_queue);
clear = 1;
}
spin_unlock_bh(&sk->sk_receive_queue.lock);
if (clear)
kfree_skb(skb);
}
skb_free_datagram(sk, skb);
if (noblock)
return -EAGAIN;
goto try_again;
}[/code]
skb的出队,是通过调用skb_recv_datagram()函数,出队操作时,队列中可能没有数据,如果是阻塞,则需要一直睡眠等待,直到超时或队 列中有数据而被唤醒。另外出队操作,还分为两种情况,一种是将数据包从队列中取中来,将其从原有队列中删除,还有一种可能是设置了MSG_PEEK标志, 它意味着:“查看当前数据,数据将被复制到缓冲区中,但并不从输入队列中删除”。
[code]struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags,
int noblock, int *err)
{
struct sk_buff *skb;
long timeo;
/*
* Caller is allowed not to check sk->sk_err before skb_recv_datagram()
*/
int error = sock_error(sk);
if (error)
goto no_packet;
/* 获取超时时间*/
timeo = sock_rcvtimeo(sk, noblock);
/* 这个循环将一直等到队列中有数据包,直到超时 */
do {
/* Again only user level code calls this function, so nothing
* interrupt level will suddenly eat the receive_queue.
*
* Look at current nfs client by the way...
* However, this function was corrent in any case. 8)
*/
if (flags & MSG_PEEK) {
unsigned long cpu_flags;
/* 如果设置了MSG_PEEK,则设用skb_peek,这里要对接收队列加锁的原因在于:
*/
spin_lock_irqsave(&sk->sk_receive_queue.lock,
cpu_flags);
skb = skb_peek(&sk->sk_receive_queue);
if (skb)
atomic_inc(&skb->users);
spin_unlock_irqrestore(&sk->sk_receive_queue.lock,
cpu_flags);
} else
skb = skb_dequeue(&sk->sk_receive_queue); /* 直接出队 */
/* 找到要收获的葫芦了,摘下来 */
if (skb)
return skb;
/* 如果是非阻塞,就不等了,直接跳出循环,返回 */
error = -EAGAIN;
if (!timeo)
goto no_packet;
} while (!wait_for_packet(sk, err, &timeo));
return NULL;
no_packet:
*err = error;
return NULL;
}[/code]
继续回到udp_recvmsg函数中来,当取出skb后,需要将它拷贝至用户空间。用户空间的数据缓存,用了一个很重要的数据结构struct msghdr来表示:
[code]struct msghdr {
void * msg_name; /* Socket name */
int msg_namelen; /* Length of name */
struct iovec * msg_iov; /* Data blocks */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};[/code]
结构中的msg_name、msg_namelen以及msg_flags分别对应于sys_sendto()[其它接收/发送函数类似]的参数 addr、addr_len以及flags。指针msg_control可以指向一个附加的数据结构,用来提供一些附加的控制信息。后面可以看到 ip_cmsg_recv()使用了它。最重要的是,结构中的指针msg_iov,指向一个iovec结构数据,而msg_iovlen则指明了这个数组 的大小:
[code]struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};[/code]
数组中的每一个元互素,都是struct iovec结构,称之为"io向量",由指向数据缓冲区的指针iov_base和表示缓冲区中数据长度的iov_len构成。这样,一个msghdr中的数据,就由控制信息msg_control和若干个"io向量"组成。
一个疑问是,为什么不设置为一个缓冲区,而要分为若干个缓冲区呢?一个很重要的原因就是,每个数据报文的大小一致,对于ip包而言。46-1500都是为 可能的,那么如果是一个缓冲区的话,就得定义为一个“最大值”,但是如果绝大部份的包,都小于这个最大值,例如为256或512,那么内存空间就浪费得太 多了……。所以,一个好的办法是,用一个个小的缓冲区将数据切分,要浪费,也浪费最后一个缓冲区的空间。——这种设计跟Unix上著名的mbuf好像是一 致的。
OK,了解了msghdr的结构后,再来看数据的拷贝工作,拷贝的核心函数是memcpy_toiovec:
[code]/*
* Copy kernel to iovec. Returns -EFAULT on error.
*
* Note: this modifies the original iovec.
*/
int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len)
{
while (len > 0) {
if (iov->iov_len) {
int copy = min_t(unsigned int, iov->iov_len, len);
if (copy_to_user(iov->iov_base, kdata, copy))
return -EFAULT;
kdata += copy;
len -= copy;
iov->iov_len -= copy;
iov->iov_base += copy;
}
iov++;
}
return 0;
}[/code]
因为每个io向量的缓冲区可能很小,例如100字节,而要拷贝的数据很长,例如1000字节。这样,需要在一个循环里,将数据拷贝至若干个
io向量数组元素当中去。
回到udp_recvmsg中来,它通过设用skb_copy_datagram_iovec()函数完成数据的拷贝工作。来看看它是如何调用memcpy_toiovec的,当然,类似于这样:
[code]int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset,
struct iovec *to, int len)
{
return memcpy_toiovec(to, skb->data+offset, len);
}[/code]
这样的调用,该是多么美好呀,这样的日子曾经是有过,不过已经一去不复返了。
考虑到skb 结构的复杂性,例如分片,拷贝工作要复杂得多:
[code]
/**
* skb_copy_datagram_iovec - Copy a datagram to an iovec.
* @skb: buffer to copy
* @offset: offset in the buffer to start copying from
* @to: io vector to copy to
* @len: amount of data to copy from buffer to iovec
*
* Note: the iovec is modified during the copy.
*/
int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset,
struct iovec *to, int len)
{
int start = skb_headlen(skb); /* start表示要从skb的哪里开始拷贝数据 */
int i, copy = start - offset; /* offset 表示调用者指出的缓存开始拷贝的偏移字节,一般情况下,它
与start是重叠的,表示不需要拷贝数据包的首部 */
/* 需要拷贝首部. */
if (copy > 0) {
if (copy > len)
copy = len;
if (memcpy_toiovec(to, skb->data + offset, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
}
/* 遍历每一个分片 */
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
int err;
u8 *vaddr;
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
struct page *page = frag->page;
if (copy > len)
copy = len;
vaddr = kmap(page);
err = memcpy_toiovec(to, vaddr + frag->page_offset +
offset - start, copy);
kunmap(page);
if (err)
goto fault;
if (!(len -= copy))
return 0;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
if (skb_copy_datagram_iovec(list,
offset - start,
to, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
}
start = end;
}
}
if (!len)
return 0;
fault:
return -EFAULT;
}[/code]
现在还没有分析ip的分片,所以,要完全理解这个函数的代码有点难度,等到ip分片分析完了,再来补充完整它。
[code]三、socket层的数据接收[/code]
recv(2)、recvfrom(2)和recvmsg(3),在sys_socketcall()中,最终都会归于一个统一的系统调用sock_recvmsg。例如:
[code]asmlinkage long sys_recv(int fd, void __user * ubuf, size_t size, unsigned flags)
{
return sys_recvfrom(fd, ubuf, size, flags, NULL, NULL);
}[/code]
sys_recv转向了sys_recvfrom():
[code]
/*
* Receive a frame from the socket and optionally record the address of the
* sender. We verify the buffers are writable and if needed move the
* sender address from kernel to user space.
*/
asmlinkage long sys_recvfrom(int fd, void __user * ubuf, size_t size, unsigned flags,
struct sockaddr __user *addr, int __user *addr_len)
{
struct socket *sock;
struct iovec iov;
struct msghdr msg;
char address[MAX_SOCK_ADDR];
int err,err2;
sock = sockfd_lookup(fd, &err);
if (!sock)
goto out;
msg.msg_control=NULL;
msg.msg_controllen=0;
msg.msg_iovlen=1;
msg.msg_iov=&iov;
iov.iov_len=size;
iov.iov_base=ubuf;
msg.msg_name=address;
msg.msg_namelen=MAX_SOCK_ADDR;
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
err=sock_recvmsg(sock, &msg, size, flags);
if(err >= 0 && addr != NULL)
{
err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);
if(err2<0)
err=err2;
}
sockfd_put(sock);
out:
return err;
}[/code]
函数先调用sockfd_lookup,根据socket描述符查找以相应的sock结构,然后封装了一个msghdr结构后,接着调用sock_recvmsg()。
[code]int sock_recvmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
struct kiocb iocb;
struct sock_iocb siocb;
int ret;
init_sync_kiocb(&iocb, NULL);
iocb.private = &siocb;
ret = __sock_recvmsg(&iocb, sock, msg, size, flags);
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&iocb);
return ret;
}[/code]
iocb和siocb用于内核和socket的io控制,函数中主要初始化了iocb,siocb的初始化,在__sock_recvmsg中完成。
进一步的按收动作,是通过__sock_recvmsg函数设用完成的:
[code]static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
int err;
struct sock_iocb *si = kiocb_to_siocb(iocb);
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size;
si->flags = flags;
err = security_socket_recvmsg(sock, msg, size, flags);
if (err)
return err;
return sock->ops->recvmsg(iocb, sock, msg, size, flags);
}
[/code]
初始化完siocb,也就是si后,调用协议簇的recvmsg函数,对于UDP而言,是sock_common_recvmsg():
[code]int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
struct sock *sk = sock->sk; /*取得sock结构对应的sk指针*/
int addr_len = 0;
int err;
err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
flags & ~MSG_DONTWAIT, &addr_len);
if (err >= 0)
msg->msg_namelen = addr_len;
return err;
}[/code]
于是,udp_recvmsg就被调用了。整个过程也就结束了。
最后再回到sys_recvfrom中来,如果用户调用时,需要返回对方的地址信息:
[code] if(err >= 0 && addr != NULL)
{
err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);
if(err2<0)
err=err2;
}[/code]
就需要调用move_addr_to_user() 函数来完成,前面分析msghdr结构时已经提到其msg_name成员变量,它包含了地址的相应信息,msg.msg_namelen成员变量,决定了地址的长度信息:
[code]int move_addr_to_user(void *kaddr, int klen, void __user *uaddr, int __user *ulen)
{
int err;
int len;
if((err=get_user(len, ulen))) //缓存长度校验
return err;
if(len>klen)
len=klen;
if(len<0 || len> MAX_SOCK_ADDR)
return -EINVAL;
if(len)
{
if(copy_to_user(uaddr,kaddr,len)) //拷贝地址信息
return -EFAULT;
}
/*
* "fromlen shall refer to the value before truncation.."
* 1003.1g
*/
return __put_user(klen, ulen);
}[/code]