您的位置:首页 > 运维架构 > Linux

Linux Kernel 下 udp packet 的收发(sk_buff+Netfiler)

2015-08-13 16:54 519 查看
记得在大学时研究 Linux Kernel 下的网络编程时,写的一个小程序,用于理解 UDP 包的构建和发送原理。这篇文章的内容是我在 ChinaUnix 论坛上面发的一个帖子。由于现在经常在用 CSDN 博客,所以将该帖子的内容转过来 CSDN 博客中。

介绍

这篇文章主要的工作是:在一台计算机 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.html

Hacking the Linux Kernel Network Stack(译本)

教你修改以及重构 skb

Linux内核发送构造数据包的方式

《Understanding Linux Network Internals》

《Linux Device Driver 3rd》

《Professional Linux Kernel Architecture》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: