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

记得在大学时研究 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 结构中几个重要的指针 datatailheadend。还有几个函数(《Understanding Linux Network Internals》第二章这本书讲的很详细):skb_putskb_reserverskb_set_network_headerskb_set_transport_headerdev_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");

遇到的问题

  1. 读取 UDP port 时,要用 udph = (struct udphdr *)sb->data + iph->ihl*4 来获取 UDP header,不能用 udph = udp_hdr(sb)。因为对于 skbuff 结构中的指向各层协议头部的指针,只有当到达该层时才对他们赋值。而 netfilter 处于网络层,所以不能直接访问 skbuff 中的传输层协议头指针,而必须用 skb->data+iph->ihl*4 来得到指向传输层头部的指针。

  2. 下面输出结果中,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

参考文献

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`struct sk_buff` 是 Linux 内核用来表示网络数据包的数据结构。它是一个很重要的数据结构,因为 Linux 内核网络协议栈的所有数据包处理都是基于 sk_buff 进行的。 下面是 `struct sk_buff` 结构体的定义: ```c struct sk_buff { struct sk_buff *next; struct sk_buff *prev; ktime_t tstamp; struct sock *sk; struct net_device *dev; char cb[48]; unsigned int len; unsigned int data_len; __u16 mac_len; __u16 hdr_len; union { __u16 all; struct { __u16 nfmark : 16; } nfctmark; struct { __u16 pkt_type : 3; __u16 ignore_df : 1; __u16 nf_trace : 1; __u16 ip_summed : 2; __u16 ooo_okay : 1; __u16 l4_rxhash : 1; __u16 sw_hash : 1; __u16 sw_hash_valid : 1; __u16 l5_hash_valid : 1; __u16 l4_hash_valid : 1; __u16 fclone : 2; __u16 frag_list : 2; __u16 rxhash : 1; __u16 loopback : 1; __u16 vlan_present : 1; __u16 vlan_tci : 16; __u16 inner_protocol : 16; __u16 inner_transport_header : 16; } parsed; } encapsulation; unsigned char protocol; unsigned char pkt_type: 3; unsigned char fclone: 2; unsigned char ip_summed: 2; unsigned char ooo_okay: 1; __u16 vlan_proto; __u16 vlan_tci; union { struct { __be16 h_vlan_TCI; __be16 h_vlan_encapsulated_proto; }; __be32 ipv4; struct ipv6hdr *ipv6h; struct arphdr *arph; struct tcphdr *hth; struct udphdr *huh; struct icmphdr *icmph; } protocol_headers; union { struct tcphdr *th; struct udphdr *uh; struct icmphdr *icmph; struct igmphdr *igmph; struct iphdr *iph; struct ipv6hdr *ipv6h; struct arphdr *arph; struct pppox_hdr *pppoe; struct snap_header *llc; struct cfm_pdu_header *cfm_pdu; struct batadv_unicast_packet *batman_adv; struct batadv_bcast_packet *batman_adv_bcast; struct batadv_icmp_packet *batman_adv_icmp; struct batadv_tvlv_packet *batman_adv_tvlv; struct batadv_frag_packet *batman_adv_frag; struct batadv_purge_packet *batman_adv_purge; struct batadv_gw_packet *batman_adv_gw; struct batadv_tt_change_packet *batman_adv_tt_change; struct batadv_mcast_packet *batman_adv_mcast; struct batadv_frag_list *batman_adv_frag_list; struct batadv_unicast_4addr_packet *batman_adv_unicast_4addr; struct batadv_bla_claim *batman_adv_bla_claim; struct batadv_bla_backbone_gw *batman_adv_bla_backbone_gw; struct batadv_bla_claim_reply *batman_adv_bla_claim_reply; struct batadv_bla_claim_confirm *batman_adv_bla_claim_confirm; struct batadv_bla_claim_ack *batman_adv_bla_claim_ack; struct batadv_bla_update *batman_adv_bla_update; struct batadv_bla_claim_broadcast *batman_adv_bla_claim_broadcast; struct batadv_bla_claim_broadcast_reply *batman_adv_bla_claim_broadcast_reply; } encapsulated; char *head; char *data; char *tail; char *end; unsigned int truesize; atomic_t users; }; ``` `struct sk_buff` 结构体的字段含义如下: - `next` 和 `prev`:`struct sk_buff` 是一个双向链表,这两个字段用于链表操作; - `tstamp`:时间戳,用于记录数据包的接收或发送时间; - `sk`:指向网络套接字的指针; - `dev`:指向网络设备的指针; - `cb`:可选的控制块,用于保存一些协议栈内部使用的数据; - `len`:数据包的总长度; - `data_len`:数据包实际负载的长度; - `mac_len`:物理层帧头的长度; - `hdr_len`:网络层协议头的长度; - `encapsulation`:封装信息,用于保存协议头的解析结果; - `protocol`:网络协议号; - `pkt_type`:数据包类型; - `fclone`:用于指示是否进行数据包复制; - `ip_summed`:用于指示是否需要计算 IP 校验和; - `ooo_okay`:用于指示是否允许乱序到达的数据包; - `vlan_proto` 和 `vlan_tci`:VLAN 标签的协议号和标识符; - `protocol_headers`:协议头指针的联合体; - `encapsulated`:封装协议的联合体; - `head`:指向数据包缓冲区首地址的指针; - `data`:指向数据包负载首地址的指针; - `tail`:指向数据包负载末地址的指针; - `end`:指向数据包缓冲区末地址的指针; - `truesize`:数据包缓冲区的实际大小; - `users`:用于记录当前正在使用该数据包的线程数的原子计数器。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值