Linux内核分析_UDP协议中数据包的收发处理过程
Posted dangol
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核分析_UDP协议中数据包的收发处理过程相关的知识,希望对你有一定的参考价值。
1. 前言
实验基于Linux kernel 3.18.6,实验内容包括:
(1)编写UDP客户端和服务端
(2)将UDP客户端和服务端集成到MenuOS中
(3)UDP发送数据的过程
(4)UDP接收数据的过程
本文中完整源码:https://github.com/dangolqy/udp
实验楼环境:https://www.shiyanlou.com/courses/1198
linux-3.18.6内核代码:http://codelab.shiyanlou.com/source/xref/linux-3.18.6/
2. UDP客户端和服务端
参考博客:https://blog.csdn.net/lell3538/article/details/53335472
服务端server.c
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<sys/types.h> 6 #include<sys/socket.h> 7 #include<netinet/in.h> 8 #include<string.h> 9 10 #define MYPORT 5678 11 12 13 #define ERR_EXIT(m) \\ 14 do { \\ 15 perror(m); \\ 16 exit(EXIT_FAILURE); \\ 17 } while (0) 18 19 void echo_ser(int sock) 20 { 21 char recvbuf[1024] = {0}; 22 struct sockaddr_in peeraddr; 23 socklen_t peerlen; 24 int n; 25 26 while (1) 27 { 28 29 peerlen = sizeof(peeraddr); 30 memset(recvbuf, 0, sizeof(recvbuf)); 31 n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, 32 (struct sockaddr *)&peeraddr, &peerlen); 33 if (n <= 0) 34 { 35 36 if (errno == EINTR) 37 continue; 38 39 ERR_EXIT("recvfrom error"); 40 } 41 else if(n > 0) 42 { 43 printf("接收到的数据:%s\\n",recvbuf); 44 sendto(sock, recvbuf, n, 0, 45 (struct sockaddr *)&peeraddr, peerlen); 46 printf("回送的数据:%s\\n",recvbuf); 47 } 48 } 49 close(sock); 50 } 51 52 int main(void) 53 { 54 int sock; 55 if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) 56 ERR_EXIT("socket error"); 57 58 struct sockaddr_in servaddr; 59 memset(&servaddr, 0, sizeof(servaddr)); 60 servaddr.sin_family = AF_INET; 61 servaddr.sin_port = htons(MYPORT); 62 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 63 64 printf("监听%d端口\\n",MYPORT); 65 if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) 66 ERR_EXIT("bind error"); 67 68 echo_ser(sock); 69 70 return 0; 71 }
客户端代码client.c
1 #include <unistd.h> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <netinet/in.h> 5 #include <arpa/inet.h> 6 #include <stdlib.h> 7 #include <stdio.h> 8 #include <errno.h> 9 #include <string.h> 10 11 #define MYPORT 5678 12 char* SERVERIP = "127.0.0.1"; 13 14 #define ERR_EXIT(m) \\ 15 do \\ 16 { \\ 17 perror(m); \\ 18 exit(EXIT_FAILURE); \\ 19 } while(0) 20 21 void echo_cli(int sock) 22 { 23 struct sockaddr_in servaddr; 24 memset(&servaddr, 0, sizeof(servaddr)); 25 servaddr.sin_family = AF_INET; 26 servaddr.sin_port = htons(MYPORT); 27 servaddr.sin_addr.s_addr = inet_addr(SERVERIP); 28 29 int ret; 30 char sendbuf[1024] = {0}; 31 char recvbuf[1024] = {0}; 32 33 fgets(sendbuf, sizeof(sendbuf), stdin); 34 35 printf("向服务器发送:%s\\n",sendbuf); 36 sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); 37 38 ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL); 39 printf("从服务器接收:%s\\n",recvbuf); 40 41 close(sock); 42 43 44 } 45 46 int main(void) 47 { 48 int sock; 49 if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) 50 ERR_EXIT("socket"); 51 52 echo_cli(sock); 53 54 return 0; 55 }
在实验楼环境中的运行结果:
3. 将UDP客户端和服务端集成到MenuOS中
仿照老师写好的TCP代码,将上面两个文件中的代码添加进main.c中。
其中,server.c和client.c中的函数部分略作修改写进.h文件中作为要引用的头文件,main函数部分作为新的函数写进main.c中,最后再在main.c的main函数中增加udp菜单选项。具体如下:
server.h
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<errno.h> 5 #include<sys/types.h> 6 #include<sys/socket.h> 7 #include<netinet/in.h> 8 #include<string.h> 9 10 #define MYPORT 5678 11 12 13 #define ERR_EXIT(m) \\ 14 do { \\ 15 perror(m); \\ 16 exit(EXIT_FAILURE); \\ 17 } while (0) 18 19 void echo_ser(int sock) 20 { 21 char recvbuf[1024] = {0}; 22 char reply[1024]={\'h\',\'i\'}; 23 struct sockaddr_in peeraddr; 24 socklen_t peerlen; 25 int n; 26 27 while(1){ 28 peerlen = sizeof(peeraddr); 29 memset(recvbuf, 0, sizeof(recvbuf)); 30 n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, 31 (struct sockaddr *)&peeraddr, &peerlen); 32 33 if(n > 0) 34 { 35 printf("server receive:%s\\n",recvbuf); 36 sendto(sock, reply, strlen(reply), 0, 37 (struct sockaddr *)&peeraddr, peerlen); 38 printf("server reply:%s\\n",reply); 39 } 40 else 41 break; 42 } 43 44 close(sock); 45 }
client.h
1 #include <unistd.h> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <netinet/in.h> 5 #include <arpa/inet.h> 6 #include <stdlib.h> 7 #include <stdio.h> 8 #include <errno.h> 9 #include <string.h> 10 11 #define MYPORT 5678 12 char* SERVERIP = "127.0.0.1"; 13 14 15 void echo_cli(int sock) 16 { 17 struct sockaddr_in servaddr; 18 memset(&servaddr, 0, sizeof(servaddr)); 19 servaddr.sin_family = AF_INET; 20 servaddr.sin_port = htons(MYPORT); 21 servaddr.sin_addr.s_addr = inet_addr(SERVERIP); 22 23 int ret; 24 char sendbuf[1024] = {\'h\',\'e\',\'l\',\'l\',\'o\'}; 25 char recvbuf[1024] = {0}; 26 27 printf("client send to server:%s\\n",sendbuf); 28 sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); 29 30 ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL); 31 printf("client receive from server:%s\\n",recvbuf); 32 33 close(sock); 34 35 }
main.c(部分)
1 #include "server.h" 2 #include "client.h" 3 4 int UdpReplyhi() 5 { 6 int sock; 7 if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) 8 ERR_EXIT("socket error"); 9 10 struct sockaddr_in servaddr; 11 memset(&servaddr, 0, sizeof(servaddr)); 12 servaddr.sin_family = AF_INET; 13 servaddr.sin_port = htons(MYPORT); 14 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 15 16 printf("server listen to port:%d\\n",MYPORT); 17 if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) 18 ERR_EXIT("bind error"); 19 20 echo_ser(sock); 21 22 return 0; 23 } 24 25 int StartUdpReplyHi(int argc, char *argv[]) 26 { 27 int pid; 28 /* fork another process */ 29 pid = fork(); 30 if (pid < 0) 31 { 32 /* error occurred */ 33 fprintf(stderr, "Fork Failed!"); 34 exit(-1); 35 } 36 else if (pid == 0) 37 { 38 /* child process */ 39 UdpReplyhi(); 40 printf("Reply hi UDP Service Started!\\n"); 41 } 42 else 43 { 44 /* parent process */ 45 printf("Please input hello...\\n"); 46 } 47 } 48 49 int UdpHello(int argc, char *argv[]) 50 { 51 int sock; 52 if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) 53 ERR_EXIT("socket"); 54 55 echo_cli(sock); 56 57 return 0; 58 }
1 MenuConfig("udpreplyhi", "Reply hi UDP Service", StartUdpReplyHi); 2 MenuConfig("udphello", "Hello UDP Client", UdpHello);
在实验楼中的运行结果:
4. UDP发送数据的过程
参考博客:https://blog.csdn.net/u010246947/article/details/18253345
http://blog.chinaunix.net/uid-14528823-id-4468600.html
UDP报文发送的内核主要调用流程如下图:
在udp_sendmsg处设置断点,查看函数调用栈:
查看inet_sendmsg函数的代码(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/af_inet.c#721),在红色框标注出来的地方,调用对应传输层协议的sendmsg方法,在这里就是udp_sendmsg。
单步执行至ip_make_skb,查看该函数的源码,它调用了__ip_make_skb(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/ip_output.c#1320)。
在这个函数中,主要进行从缓冲队列中拿出数据送至skb中,添加IP协议头等操作。
1342 while ((tmp_skb = __skb_dequeue(queue)) != NULL) { 1343 __skb_pull(tmp_skb, skb_network_header_len(skb)); 1344 *tail_skb = tmp_skb; 1345 tail_skb = &(tmp_skb->next); 1346 skb->len += tmp_skb->len; 1347 skb->data_len += tmp_skb->len; 1348 skb->truesize += tmp_skb->truesize; 1349 tmp_skb->destructor = NULL; 1350 tmp_skb->sk = NULL; 1351 }
1353 /* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow 1354 * to fragment the frame generated here. No matter, what transforms 1355 * how transforms change size of the packet, it will come out. 1356 */ 1357 skb->ignore_df = ip_sk_ignore_df(sk); 1358 1359 /* DF bit is set when we want to see DF on outgoing frames. 1360 * If ignore_df is set too, we still allow to fragment this frame 1361 * locally. */ 1362 if (inet->pmtudisc == IP_PMTUDISC_DO || 1363 inet->pmtudisc == IP_PMTUDISC_PROBE || 1364 (skb->len <= dst_mtu(&rt->dst) && 1365 ip_dont_fragment(sk, &rt->dst))) 1366 df = htons(IP_DF); 1367 1368 if (cork->flags & IPCORK_OPT) 1369 opt = cork->opt; 1370 1371 if (cork->ttl != 0) 1372 ttl = cork->ttl; 1373 else if (rt->rt_type == RTN_MULTICAST) 1374 ttl = inet->mc_ttl; 1375 else 1376 ttl = ip_select_ttl(inet, &rt->dst); 1377 1378 iph = ip_hdr(skb); 1379 iph->version = 4; 1380 iph->ihl = 5; 1381 iph->tos = (cork->tos != -1) ? cork->tos : inet->tos; 1382 iph->frag_off = df; 1383 iph->ttl = ttl; 1384 iph->protocol = sk->sk_protocol; 1385 ip_copy_addrs(iph, fl4); 1386 ip_select_ident(skb, sk); 1387 1388 if (opt) { 1389 iph->ihl += opt->optlen>>2; 1390 ip_options_build(skb, opt, cork->addr, rt, 0); 1391 } 1392 1393 skb->priority = (cork->tos != -1) ? cork->priority: sk->sk_priority; 1394 skb->mark = sk->sk_mark; 1395 /* 1396 * Steal rt from cork.dst to avoid a pair of atomic_inc/atomic_dec 1397 * on dst refcount 1398 */ 1399 cork->dst = NULL; 1400 skb_dst_set(skb, &rt->dst); 1401 1402 if (iph->protocol == IPPROTO_ICMP) 1403 icmp_out_count(net, ((struct icmphdr *) 1404 skb_transport_header(skb))->type); 1405 1406 ip_cork_release(cork);
继续单步执行到udp_send_skb函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#783)。
该函数中为数据包添加了udp报头,包括计算校验和等内容。
794 /* 795 * Create a UDP header 796 */ 797 uh = udp_hdr(skb); 798 uh->source = inet->inet_sport; 799 uh->dest = fl4->fl4_dport; 800 uh->len = htons(len); 801 uh->check = 0; 802 803 if (is_udplite) /* UDP-Lite */ 804 csum = udplite_csum(skb); 805 806 else if (sk->sk_no_check_tx) { /* UDP csum disabled */ 807 808 skb->ip_summed = CHECKSUM_NONE; 809 goto send; 810 811 } else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */ 812 813 udp4_hwcsum(skb, fl4->saddr, fl4->daddr); 814 goto send; 815 816 } else 817 csum = udp_csum(skb); 818 819 /* add protocol-dependent pseudo-header */ 820 uh->check = csum_tcpudp_magic(fl4->saddr, fl4->daddr, len, 821 sk->sk_protocol, csum); 822 if (uh->check == 0) 823 uh->check = CSUM_MANGLED_0;
继续单步执行,到这里udp数据包已经准备好,即将交给IP层进行发送。传输层的数据发送相关流程到此结束。
5. UDP接收数据的过程
参考博客:https://blog.csdn.net/xiaoyu_750516366/article/details/83473758
https://blog.csdn.net/xiaoyu_750516366/article/details/83552187
UDP数据包的接收分为两部分:
(1)网络层将数据包递交给UDP,UDP接收数据包并对其进行校验,校验成功后放入接收队列中等待用户进程的读取;
(2)用户进程使用系统调用来读取已经在接收队列中的数据。
下面逐步介绍。
5.1 从IP层接收数据包
这部分函数的调用过程如下图:
在udp_rcv处设置断点,首先调用__udp4_lib_rcv函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#1730)。
该函数中首先对数据包进行验证,获取数据包的源IP和目的IP,检查数据包是否完整,计算校验和等。
1740 /* 1741 * Validate the packet. 1742 */ 1743 if (!pskb_may_pull(skb, sizeof(struct udphdr))) 1744 goto drop; /* No space for header. */ 1745 1746 uh = udp_hdr(skb); 1747 ulen = ntohs(uh->len); 1748 saddr = ip_hdr(skb)->saddr; 1749 daddr = ip_hdr(skb)->daddr; 1750 1751 if (ulen > skb->len) 1752 goto short_packet; 1753 1754 if (proto == IPPROTO_UDP) { 1755 /* UDP validates ulen. */ 1756 if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen)) 1757 goto short_packet; 1758 uh = udp_hdr(skb); 1759 } 1760 1761 if (udp4_csum_init(skb, uh, proto)) 1762 goto csum_error;
接着执行,调用到udp_queue_rcv_skb函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#1484)。
该函数用于接收数据包,继续执行看到该函数调用__udp_queue_rcv_skb函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#1441)。
这个函数用于把接收数据放入接收队列之中,主要通过调用sock_queue_rcv_skb函数实现(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/core/sock.c#437)。
1450 rc = sock_queue_rcv_skb(sk, skb);
sk_receive_queue即是udp的接收队列。
437int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) 438{ 439 int err; 440 unsigned long flags; 441 struct sk_buff_head *list = &sk->sk_receive_queue;
如果数据过大或内存不足时,接收失败。
443 if (atomic_read(&sk->sk_rmem_alloc) >= sk->sk_rcvbuf) { 444 atomic_inc(&sk->sk_drops); 445 trace_sock_rcvqueue_full(sk, skb); 446 return -ENOMEM; 447 } 448 449 err = sk_filter(sk, skb); 450 if (err) 451 return err; 452 453 if (!sk_rmem_schedule(sk, skb, skb->truesize)) { 454 atomic_inc(&sk->sk_drops); 455 return -ENOBUFS; 456 }
将数据放入队列中,等待用户进程的读取。
468 __skb_queue_tail(list, skb); 469 spin_unlock_irqrestore(&list->lock, flags); 470 471 if (!sock_flag(sk, SOCK_DEAD)) 472 sk->sk_data_ready(sk);
5.2 用户进程从接收队列中读取数据
接口函数是udp_recvmsg,在这里设置断点,查看函数调用栈(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#1244)。
单步执行至__skb_recv_datagram函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/core/datagram.c#164)。
该函数从接收队列中获取skb,使用do while循环来判断是否有新的数据包可取。
179 do { 180 /* Again only user level code calls this function, so nothing 181 * interrupt level will suddenly eat the receive_queue. 182 * 183 * Look at current nfs client by the way... 184 * However, this function was correct in any case. 8) 185 */ 186 unsigned long cpu_flags; 187 struct sk_buff_head *queue = &sk->sk_receive_queue; 188 int _off = *off; 189 190 last = (struct sk_buff *)queue; 191 spin_lock_irqsave(&queue->lock, cpu_flags); 192 skb_queue_walk(queue, skb) { 193 last = skb; 194 *peeked = skb->peeked; 195 if (flags & MSG_PEEK) { 196 if (_off >= skb->len && (skb->len || _off || 197 skb->peeked)) { 198 _off -= skb->len; 199 continue; 200 } 201 skb->peeked = 1; 202 atomic_inc(&skb->users); 203 } else 204 __skb_unlink(skb, queue); 205 206 spin_unlock_irqrestore(&queue->lock, cpu_flags); 207 *off = _off; 208 return skb; 209 } 210 spin_unlock_irqrestore(&queue->lock, cpu_flags); 211 212 if (sk_can_busy_loop(sk) && 213 sk_busy_loop(sk, flags & MSG_DONTWAIT)) 214 continue; 215 216 /* User doesn\'t want to wait */ 217 error = -EAGAIN; 218 if (!timeo) 219 goto no_packet; 220 221 } while (!wait_for_more_packets(sk, err, &timeo, last));
回到udp_recvmsg函数中,下面将刚才获取的数据
以上是关于Linux内核分析_UDP协议中数据包的收发处理过程的主要内容,如果未能解决你的问题,请参考以下文章