Linux Kernel 下 udp packet 的收发(sk_buff+Netfiler)
2015-08-13 16:54
519 查看
记得在大学时研究 Linux Kernel 下的网络编程时,写的一个小程序,用于理解 UDP 包的构建和发送原理。这篇文章的内容是我在 ChinaUnix 论坛上面发的一个帖子。由于现在经常在用 CSDN 博客,所以将该帖子的内容转过来 CSDN 博客中。
此程序开始遇到的问题是 checksum 部分,一开始忘了写
下面输出结果中,printk data 时出现乱码。原因是:
Hacking the Linux Kernel Network Stack(译本)
教你修改以及重构 skb
Linux内核发送构造数据包的方式
《Understanding Linux Network Internals》
《Linux Device Driver 3rd》
《Professional Linux Kernel Architecture》
介绍
这篇文章主要的工作是:在一台计算机 C1(AMD Athlon 64*2 Dual Core Processor 4600+, Ubuntu10.04 desktop)通过加载模块sendUDPWithKernelModule.ko发送一个 udp packet(data是”hello world”)。另一台计算机 C2(Intel Atom N450, Ubuntu10.04 netbook)通过加载模块
packetCaptureWithNetfilter.ko接收 C1 发过来的 packet,并打印出 ip header, udp header 和 data 的内容(两台计算机的内核都是: 2.6.32.33)。
发送模块
sendUDPWithKernelModule.c
/*********************************************************************** * File: sendUDPWithkernelModule.c * Abstract Description: * To send a UDP packet to another in kernel space. * *------------------------Revision History------------------------ * No. Date Revised By Description * 1 2011/7/28 Sam make the program work on my machines * IP address: [10.14.1.122] -> [10.14.1.21] * MAC address: * 00:e0:4d:8b:3c:d7 -> 20:cf:30:57:1a:18 * 2 2011/8/7 Sam To correct the checksum.(fail) * 3 2011/8/9 Sam To correct the checksum.(success) ***********************************************************************/ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/workqueue.h> #include <linux/in.h> #include <linux/inet.h> #include <linux/socket.h> #include <linux/ip.h> #include <linux/udp.h> #include <net/sock.h> #define IF_NAME "eth0" #define DIP "10.14.1.20" #define SIP "10.14.1.122" #define SPORT 319 #define DPORT 319 #define SRC_MAC {0x00, 0xe0, 0x4d, 0x8b, 0x3c, 0xd7} #define DST_MAC {0x20, 0xcf, 0x30, 0x57, 0x1a, 0x18} struct socket *sock; static void sock_init() { /* "struct ifreq" is defined in linux/if.h. * Interface request structure used for socket * ioctl's. All interface ioctl's must have parameter * definitions which begin with ifr_name. The * remainder may be interface specific. */ struct ifreq ifr; /* sock_create_kern():创建socket结构. * SIOCSIFNAME is defined in include/linux/sockios.h. * It is used to set interface name. */ sock_create_kern(PF_INET, SOCK_DGRAM, 0, &sock); // copy the interface name to the ifrn_name. strcpy(ifr.ifr_ifrn.ifrn_name, IF_NAME); kernel_sock_ioctl(sock, SIOCSIFNAME, (unsigned long) &ifr); } static void send_by_skb() { struct net_device *netdev = NULL; struct net *net = NULL; struct sk_buff *skb = NULL; struct ethhdr *eth_header = NULL; struct iphdr *ip_header = NULL; struct udphdr *udp_header = NULL; __be32 dip = in_aton(DIP); __be32 sip = in_aton(SIP); u8 buf[16] = {"hello world"}; u16 data_len = sizeof(buf); //u16 expand_len = 16; /* for skb align */ u8 *pdata = NULL; u32 skb_len; u8 dst_mac[ETH_ALEN] = DST_MAC; /* dst MAC */ u8 src_mac[ETH_ALEN] = SRC_MAC; /* src MAC */ /* construct skb * sock_net() is defined in include/net/sock.h * dev_get_by_name()函数用来取得设备指针,使用该函数 * 后一定要使用dev_put()函数取消设备引用. */ sock_init(); net = sock_net((const struct sock *) sock->sk); netdev = dev_get_by_name(net, IF_NAME); /* LL_RESERVED_SPACE is defined in include/netdevice.h. */ /*skb_len = LL_RESERVED_SPACE(netdev) + sizeof(struct iphdr) + sizeof(struct udphdr) + data_len + expand_len;*/ skb_len = data_len + sizeof(struct iphdr) + sizeof(struct udphdr) +LL_RESERVED_SPACE(netdev); printk("iphdr: %d\n", sizeof(struct iphdr)); printk("udphdr: %d\n", sizeof(struct udphdr)); printk("data_len: %d\n", data_len); printk("skb_len: %d\n", skb_len); /* dev_alloc_skb是一个缓冲区分配函数,主要被设备驱动使用. * 这是一个alloc_skb的包装函数, 它会在请求分配的大小上增加 * 16 Bytes的空间以优化缓冲区的读写效率.*/ skb = dev_alloc_skb(skb_len); //skb = alloc_skb(skb_len, GFP_ATOMIC); if (!skb) { return; } /* fill the skb.具体参照struct sk_buff. * skb_reserve()用来为协议头预留空间. * PACKET_OTHERHOST: packet type is "to someone else". * ETH_P_IP: Internet Protocol packet. * CHECKSUM_NONE表示完全由软件来执行校验和. */ skb_reserve(skb, LL_RESERVED_SPACE(netdev)); skb->dev = netdev; skb->pkt_type = PACKET_OTHERHOST; skb->protocol = htons(ETH_P_IP); skb->ip_summed = CHECKSUM_NONE; skb->priority = 0; /* 分配内存给ip头 */ skb_set_network_header(skb, 0); skb_put(skb, sizeof(struct iphdr)); /* 分配内存给udp头 */ skb_set_transport_header(skb, sizeof(struct iphdr)); skb_put(skb, sizeof(struct udphdr)); /* construct udp header in skb */ udp_header = udp_hdr(skb); udp_header->source = htons(SPORT); udp_header->dest = htons(DPORT); udp_header->check = 0; /* construct ip header in skb */ ip_header = ip_hdr(skb); ip_header->version = 4; ip_header->ihl = sizeof(struct iphdr) >> 2; ip_header->frag_off = 0; ip_header->protocol = IPPROTO_UDP; ip_header->tos = 0; ip_header->daddr = dip; ip_header->saddr = sip; ip_header->ttl = 0x40; ip_header->tot_len = htons(skb->len); ip_header->check = 0; /* caculate checksum */ skb->csum = skb_checksum(skb, ip_header->ihl*4, skb->len-ip_header->ihl*4, 0); ip_header->check = ip_fast_csum(ip_header, ip_header->ihl); udp_header->check = csum_tcpudp_magic(sip, dip, skb->len-ip_header->ihl*4, IPPROTO_UDP, skb->csum); /* insert data in skb */ pdata = skb_put(skb, data_len); if (pdata) { memcpy(pdata, buf, data_len); } printk("payload:%20s\n", pdata); /* construct ethernet header in skb */ eth_header = (struct ehthdr *)skb_push(skb, 14); memcpy(eth_header->h_dest, dst_mac, ETH_ALEN); memcpy(eth_header->h_source, src_mac, ETH_ALEN); eth_header->h_proto = htons(ETH_P_IP); /* send packet */ if (dev_queue_xmit(skb) < 0) { dev_put(netdev); kfree_skb(skb); printk("send packet by skb failed.\n"); return; } printk("send packet by skb success.\n"); } static int __init sendUDP_init(void) { /* send_by_sock(); */ send_by_skb(); printk("testmod kernel module load!\n"); return 0; } static void __exit sendUDP_exit(void) { sock_release(sock); printk("testmod kernel module removed!\n"); } module_init(sendUDP_init); module_exit(sendUDP_exit); MODULE_LICENSE("GPL");
遇到的问题
写这个程序一定要搞清楚sk_buff结构中几个重要的指针
data、
tail、
head和
end。还有几个函数(《Understanding Linux Network Internals》第二章这本书讲的很详细):
skb_put、
skb_reserver、
skb_set_network_header、
skb_set_transport_header和
dev_alloc_skb。
此程序开始遇到的问题是 checksum 部分,一开始忘了写
ip_fast_csum这一步,导致用 tcpdump 抓包时出现 “Bad checksum 0”。后来加上就好了。
输出
这是在加载模块后,在电脑 C1 里面查看/var/log/message得到的结果(注意 “hello world” 的那一行):
Aug 10 20:25:05 sam-desktop kernel: [21389.116002] iphdr: 20 Aug 10 20:25:05 sam-desktop kernel: [21389.116008] udphdr: 8 Aug 10 20:25:05 sam-desktop kernel: [21389.116012] data_len: 16 Aug 10 20:25:05 sam-desktop kernel: [21389.116016] skb_len: 60 Aug 10 20:25:05 sam-desktop kernel: [21389.116022] hello world Aug 10 20:25:05 sam-desktop kernel: [21389.116043] send packet by skb success. Aug 10 20:25:05 sam-desktop kernel: [21389.116047] testmod kernel module load! Aug 10 20:25:06 sam-desktop kernel: [21390.550944] testmod kernel module removed!
接收模块
packetCaptureWithNetfilter.c
/*********************************************************************** * File: packetCaptureWithNetfilter.c * Abstract Description: * To catch the packet from the network with netfilter. * *------------------------Revision History------------------------ * No. Date Revised By Description * 1 2011/7/28 Sam +print the payload.(unsuccessful) * 2 2011/7/30 Sam +get the packet from the specific ip. * 3 2011/8/10 Sam correct the checksum and * +print the payload. ***********************************************************************/ #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #include <linux/module.h> #include <linux/kernel.h> #include <linux/netfilter.h> #include <linux/skbuff.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/netdevice.h> #include <linux/if_arp.h> #include <linux/if_ether.h> #include <linux/if_packet.h> #include <net/tcp.h> #include <net/udp.h> #include <linux/netfilter_ipv4.h> static struct nf_hook_ops nfho; unsigned int hook_func(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { if(skb){ struct sk_buff *sb = skb; struct tcphdr *tcph = NULL; struct udphdr *udph = NULL; struct iphdr *iph = NULL; u8 *payload; // The pointer for the tcp payload. char sourceAddr[20]; char myAddr[20]; iph = ip_hdr(sb); if(iph){ /* NIPQUAD() was defined in the linux/kernel.h. * Display an IP address in readable format.*/ /* These two sprintf are used to get the packet * from the specific ip.*/ sprintf(myAddr, "10.14.1.122"); sprintf(sourceAddr, "%u.%u.%u.%u", NIPQUAD(iph->saddr)); if(!(strcmp(sourceAddr, myAddr))){ printk("IP:[%u.%u.%u.%u]-->[%u.%u.%u.%u];\n", NIPQUAD(iph->saddr), NIPQUAD(iph->daddr)); printk("IP (version %u, ihl %u, tos 0x%x, ttl %u, id %u, length %u, ", iph->version, iph->ihl, iph->tos, iph->ttl, ntohs(iph->id), ntohs(iph->tot_len)); /* 此处读取udp或tcp报头时, 不能用udp_hdr或tcp_hdr. * 因为对于skbuff结构中的指向各层协议头部的指针, 只 * 有当到达该层时才对他们赋值.而netfilter处于网络层. * 所以不能直接访问skbuff中的传输层协议头指针,而必 * 须用skb->data+iph->ihl*4来得到指向传输层头部的指 * 针。 */ switch(iph->protocol){ case IPPROTO_UDP: /*get the udp information*/ udph = (struct udphdr *)(sb->data + iph->ihl*4); printk("UDP: [%u]-->[%u];\n", ntohs(udph->source), ntohs(udph->dest)); payload = udph + ntohs(udph->len); /* 此处不能用"printk("payload: %20s\n", payload);" * 否则会出现乱码并且"hello world"打印不出来. * 不过用下面的方法打印出来的是"hello world" * 前面有一些乱码. */ char i; for(i=0;i<20;i++){ printk("%c", payload[i]); } break; case IPPROTO_TCP: /*get the tcp header*/ tcph = sb->data + iph->ihl*4; //payload = (char *)((__u32 *)tcph+tcph->doff); printk("TCP: [%u]-->[%u];\n", ntohs(tcph->source), ntohs(tcph->dest)); break; default: printk("unkown protocol!\n"); break; } } } else { printk("iph is null\n"); } } else { printk("skb is null,hooknum:%d\n", hooknum); } return NF_ACCEPT; } int init_module() { nfho.hook = hook_func; nfho.hooknum = NF_INET_PRE_ROUTING; nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; nf_register_hook(&nfho); printk("init module----------------ok\n"); return 0; } void cleanup_module() { nf_unregister_hook(&nfho); printk("exit module----------------ok\n"); } MODULE_LICENSE("GPL");
遇到的问题
读取 UDP port 时,要用udph = (struct udphdr *)sb->data + iph->ihl*4来获取 UDP header,不能用
udph = udp_hdr(sb)。因为对于
skbuff结构中的指向各层协议头部的指针,只有当到达该层时才对他们赋值。而 netfilter 处于网络层,所以不能直接访问
skbuff中的传输层协议头指针,而必须用
skb->data+iph->ihl*4来得到指向传输层头部的指针。
下面输出结果中,printk data 时出现乱码。原因是:
ntohs(udph->len)这一步没有获得 UDP header 的长度(发送端没有给
udp_header->len赋值!)。将
payload = (char *)udph + ntohs(udph->len);改为
payload = (char *)udph + (char)sizeof(struct udphdr);即可。
输出
sam@sam-laptop:~$ cat /var/log/messages | grep 'Aug 10 20:25' Aug 10 20:24:27 sam-laptop kernel: [16363.962297] init module----------------ok Aug 10 20:24:45 sam-laptop kernel: [16382.101649] IP:[10.14.1.122]-->[224.0.0.251]; Aug 10 20:24:45 sam-laptop kernel: [16382.101662] IP (version 4, ihl 6, tos 0xc0, ttl 1, id 0, length 32, unkown protocol! Aug 10 20:24:55 sam-laptop kernel: [16392.189151] device eth0 entered promiscuous mode Aug 10 20:25:08 sam-laptop kernel: [16404.803001] IP:[10.14.1.122]-->[10.14.1.20]; Aug 10 20:25:08 sam-laptop kernel: [16404.803013] IP (version 4, ihl 5, tos 0x0, ttl 64, id 212, length 28, UDP: [319]-->[319]; Aug 10 20:25:08 sam-laptop kernel: [16404.803025] ????hello world Aug 10 20:25:13 sam-laptop kernel: [16409.792133] device eth0 left promiscuous mode Aug 10 20:25:18 sam-laptop kernel: [16414.416250] exit module----------------ok sam@sam-laptop:~$ sudo tcpdump -w result -v host 10.14.1.122 [sudo] password for sam: tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes ^C1 packets captured 1 packets received by filter 0 packets dropped by kernel sam@sam-laptop:~$ vim result ??2?^B^@^D^@^@^@^@^@^@^@^@^@`^@^@^@^A^@^@^@¤xBN??^G^@<^@^@^@<^@^@^@ ?0W^Z^X^@àM<8b><×^H^@E^@^@^\^@?^@^@@^QcT ^N^Az ^N^A^T^A?^A?^@^@??hello world^@^@^@^@^@^@^@
Makefile 文件
加上下面的 Makefile,直接 make 一下就可以使用了。Makefile: obj-m := sendUDPWithKernelModule.o/packetCaptureWithNetfilter.o all: make -C /lib/modules/$(shell uname -r)/build \ M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build \ M=$(PWD) clean
参考文献
http://www.cnblogs.com/piky/articles/1587767.htmlHacking the Linux Kernel Network Stack(译本)
教你修改以及重构 skb
Linux内核发送构造数据包的方式
《Understanding Linux Network Internals》
《Linux Device Driver 3rd》
《Professional Linux Kernel Architecture》
相关文章推荐
- 第二十章、Linux进程管理
- 第十九章、Linux软件管理
- 手把手带你自制Linux系统之二 简易Linux的制作
- 手把手带你自制Linux系统之一 准备工作
- CentOS Linux解决 Device eth0 does not seem to be present
- 第十五章、Linux压缩及归档
- 第十三章、facl及用户及Linux终端
- 第十一章、Linux文件查找详解
- 第五章、Linux用户及权限详解
- 第三章、Linux根文件系统及文件管理命令详解
- 第二章、Linux操作系统及常用命令
- 马哥Linux笔记整理
- Centos 6.4 Error: Could not retrieve mirrorlist
- Linux基础学习笔记-第三课:快捷键、终端、帮助
- linux有用的一些yum源
- centos 7 yum安装kubernetes cluster 1.0
- 日记 - 一些常用linux命令
- linux socket的select函数实例
- Linux系统管理员需要知道的16个服务器监控命令
- Linux集群系列01-LVS_NAT模型配置