Linux - 模块编程初试

  计算机网络的课程设计要做防火墙,老师没有限制在什么系统上面做,所以决定在Linux上实现。找了一下相关的资料,发现其实Linux有提供Netfilter/Iptables,为用户提供防火墙的功能,稍微看了一下,使用Iptables能够很方便地配置用户想要的防火墙,但是好像只能做过滤、数据报修改以及网络地址转换,好像不能做获取其中信息的功能,而且看了一下网上其他人的提问或者博客,好像想做类似的功能还是需要直接使用Netfilter。而如果想要使用Netfiler的话,需要编写hook函数,这个过程中不得不避免要编写模块。所以这里记录一下我在这个过程中做的一些尝试以及遇到的问题。

 

  使用的平台:Ubuntu 14.10

  内核版本: 3.16.0-23-generic  (这个很重要啊,不用的内核可能函数都是不一样的,网上的大部分教程用的内核版本都是2.6)

 

2015.4.23

  第一次我是编写一个hello world,在加载模块的时候以及移除模块的时候各输出一次,这里做的都是跟着网上的教程写的。

代码如下:

 1 #include <linux/module.h>
 2 #include <linux/kernel.h>
 3 #include <linux/init.h>
 4 
 5 
 6 static int __init lkp_init(void);
 7 static int __exit lkp_exit(void);
 8 
 9 static int __init lkp_init(void){
10         printk("<1>Hello,world!\n");
11         return 0;
12 }
13 
14 static int __exit lkp_exit(void){
15         printk("<2>Hello,world!\n");
16         return 0;
17 }
18 
19 module_init(lkp_init);
20 module_exit(lkp_exit);

Makefile:

 1 ifneq ($(KERNELRELEASE),)
 2 mymodule-objs:=hello.c
 3 obj-m += hello.o
 4 
 5 else
 6 PWD := $(shell pwd)
 7 KVER := $(shell uname -r)
 8 KDIR := /lib/modules/$(KVER)/build
 9 
10 all:
11     $(MAKE) -C $(KDIR) M=$(PWD)
12 clean:
13     rm -rf *.o *.mod.c *.ko *.symvers *order *.markers *-
14 endif

  make一次以后然后加载模块: sudo insmod hello.ko

  使用指令dmesg能够查看到加载的时候的输出。

  移除模块: sudo rmmod hello.ko

  再次使用dmesg能够查看到移除的时候的输出。

  这里这个Makefile是怎么执行的,为什么需要使用dmesg来查看输出的问题我暂时先不写,因为这些在网上都能找到而且能够比较清楚地解释,我打算写的是一些我遇到的问题。

 

2015.4.26

  开始编写与Netfilter有关的函数,首先写的这个也是按照别人的教程给的例子写的程序。写一个钩子挂载到 LOCAL_OUT上。然后每隔四个发出去的数据包就拦截下下一个数据包。

代码如下:

 1 #ifndef __KERNEL__
 2 #define __KERNEL__
 3 #endif
 4 #ifndef MODULE
 5 #define MODULE
 6 #endif
 7 #include <linux/module.h>
 8 #include <linux/kernel.h>
 9 #include <linux/netfilter.h>
10 #include <linux/netfilter_ipv4.h>
11 
12 static int count=0;
13 
14 static unsigned int func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)){
15     count=(count+1)%5;
16     if(count==0){
17         return NF_DROP;
18     }
19     return NF_ACCEPT;
20 }
21 
22 static struct nf_hook_ops nfho;
23 
24 static int __init myhook_init(void){
25     nfho.hook = func;
26     nfho.owner = THIS_MODULE;
27     nfho.pf = PF_INET;
28     nfho.hooknum = NF_INET_LOCAL_OUT;
29     nfho.priority = NF_IP_PRI_FIRST;
30     return  nf_register_hook(&nfho);
31 }
32 
33 static void __exit myhook_fini(void){
34     nf_unregister_hook(&nfho);
35 }
36 
37 module_init(myhook_init);
38 module_exit(myhook_fini);

Makefile:

 1 ifneq ($(KERNELRELEASE),)
 2 mymodule-objs:=test0.c
 3 obj-m += test0.o
 4 
 5 else
 6 PWD := $(shell pwd)
 7 KVER := $(shell uname -r)
 8 KDIR := /lib/modules/$(KVER)/build
 9 
10 all:
11     $(MAKE) -C $(KDIR) M=$(PWD) modules
12 clean:
13     rm -rf *.o *.mod.c *.ko *.symvers *order *.markers *-
14 endif

   问题来了,如果是按照网上的其他例子来写的话,make的时候就会说NF_IP_LOCAL_OUT找不到。当然还有一个警告说nfho.hook = func有问题,这个可能要看看怎样写它才会不警告,这里不理这个警告没有问题。我们继续说NF_IP_LOCAL_OUT,打开保存所有头文件的目录,发现这个宏定义有啊,就在linux/netfilter_ipv4.h里面,是从uapi/linux/netfilter_ipv4.h包含进来的,但是这里又有个问题,它是被ifndef __KERNEL__  ``` endif 包住了,所以它编译的时候没有包含进去,如下面的代码:

 1 #ifndef __KERNEL__
 2 
 3 #include <limits.h> /* for INT_MIN, INT_MAX */
 4 
 5 /* IP Cache bits. */
 6 /* Src IP address. */
 7 #define NFC_IP_SRC        0x0001
 8 /* Dest IP address. */
 9 #define NFC_IP_DST        0x0002
10 /* Input device. */
11 #define NFC_IP_IF_IN        0x0004
12 /* Output device. */
13 #define NFC_IP_IF_OUT        0x0008
14 /* TOS. */
15 #define NFC_IP_TOS        0x0010
16 /* Protocol. */
17 #define NFC_IP_PROTO        0x0020
18 /* IP options. */
19 #define NFC_IP_OPTIONS        0x0040
20 /* Frag & flags. */
21 #define NFC_IP_FRAG        0x0080
22 
23 /* Per-protocol information: only matters if proto match. */
24 /* TCP flags. */
25 #define NFC_IP_TCPFLAGS        0x0100
26 /* Source port. */
27 #define NFC_IP_SRC_PT        0x0200
28 /* Dest port. */
29 #define NFC_IP_DST_PT        0x0400
30 /* Something else about the proto */
31 #define NFC_IP_PROTO_UNKNOWN    0x2000
32 
33 /* IP Hooks */
34 /* After promisc drops, checksum checks. */
35 #define NF_IP_PRE_ROUTING    0
36 /* If the packet is destined for this box. */
37 #define NF_IP_LOCAL_IN        1
38 /* If the packet is destined for another interface. */
39 #define NF_IP_FORWARD        2
40 /* Packets coming from a local process. */
41 #define NF_IP_LOCAL_OUT        3
42 /* Packets about to hit the wire. */
43 #define NF_IP_POST_ROUTING    4
44 #define NF_IP_NUMHOOKS        5
45 #endif /* ! __KERNEL__ */

  原因:在2.6.22以及以后的内核中,NF_IP_PRE_ROUTING以及NF_IP6_PRE_ROUTING都被放在了用户态,而在内核态编程必须统一使用NF_INET_PRE_ROUTING。

  所以解决的办法就是使用NF_INET_XXXXXXX来代替相关的宏就行了。

  这里坑了我比较长的时间。

  修改了以后再编译一次,然后加载模块以后,ping一下,然后就出现效果了,每五个包就会有一个发不出去。

 

2015.4.27

  先说一下在linux下用什么编辑环境完成内核、模块的开发。其实用一个vim来写也是没有问题,但是对于刚入门而且使用vim不熟练的新手来说,还是使用IDE比较好,毕竟用IDE有提示。这里推荐一篇介绍怎样用Eclipse来做内核开发的文章。 http://blog.chinaunix.net/uid-24512513-id-3183457.html

  这一次看了一下内核与用户空间通信的问题,在网上看了一下别人的博客,内核与用户空间通信的方法有很多种,这里我尝试的方法是使用socket来完成两者之间的通信。因为我对socket还不算太了解,所以这一次只能算是尝试一下。

  这一次的实验内容是写一个模块接受用户空间的程序发出来的信息,然后在系统记录里面输出语句,然后在用户空间的程序需要模块的信息时向用户空间的程序发出信息。先上代码:

模块的代码:

modules.c

 1 #ifndef __KERNEL__
 2 #define __KERNEL__
 3 #endif
 4 
 5 #ifndef MODULE
 6 #define MODULE
 7 #endif
 8 
 9 #include <linux/module.h>
10 #include <linux/kernel.h>
11 #include <linux/types.h>
12 #include <linux/string.h>
13 #include <linux/init.h>
14 #include <linux/netfilter_ipv4.h>
15 #include <linux/uaccess.h>
16 #define SOCKET_OPS_BASE    128
17 #define SOCKET_OPS_SET        (SOCKET_OPS_BASE)
18 #define SOCKET_OPS_GET        (SOCKET_OPS_BASE)
19 #define SOCKET_OPS_MAX        (SOCKET_OPS_BASE+1)
20 
21 #define KMSG      "a message from kernel"
22 #define KMSG_LEN        sizeof("a message from kernel")
23 
24 MODULE_LICENSE("GPL");
25 
26 static int recv_msg(struct sock *sk,int cmd,void __user* user,unsigned int len){
27     int ret = 0;
28     printk(KERN_INFO "sockopt: recv_msg()\n");
29     if(cmd == SOCKET_OPS_SET){
30         char umsg[64];
31         int len = sizeof(char)*64;
32         memset(umsg,0,len);
33         ret = copy_from_user(umsg,user,len);
34         printk("recv_msg:umsg=%s. ret=%d\n",umsg,ret);
35     }
36     return 0;
37 }
38 
39 static int send_msg(struct sock *sk,int cmd,void __user *user,int *len){
40     int ret = 0;
41     printk(KERN_INFO "sockopt:send_msg()\n");
42     if(cmd == SOCKET_OPS_GET){
43         ret = copy_to_user(user,KMSG,KMSG_LEN);
44         printk("send_msg:umsg=%s. ret=%d.success\n",KMSG,ret);
45     }
46     return 0;
47 }
48 
49 static struct nf_sockopt_ops test_sockops;
50 
51 static int __init init_sockopt(void){
52     test_sockops.pf = PF_INET;
53     test_sockops.set_optmin = SOCKET_OPS_SET;
54     test_sockops.set_optmax = SOCKET_OPS_MAX;
55     test_sockops.set = recv_msg;
56     test_sockops.get_optmin = SOCKET_OPS_GET;
57     test_sockops.get_optmax = SOCKET_OPS_MAX;
58     test_sockops.get = send_msg;
59     test_sockops.owner = THIS_MODULE;
60 
61     printk(KERN_INFO "sockopt: init_sockopt()\n");
62     return nf_register_sockopt(&test_sockops);
63 }
64 
65 static void __exit fini_sockopt(void){
66     printk(KERN_INFO "sockopt:fini_sockopt()\n");
67     nf_unregister_sockopt(&test_sockops);
68 }
69 
70 module_init(init_sockopt);
71 module_exit(fini_sockopt);

 

用户空间的代码 main.c:

 1 #include <unistd.h>
 2 #include <stdio.h>
 3 #include <sys/socket.h>
 4 #include <linux/in.h>
 5 #include <string.h>
 6 #include <errno.h>
 7 
 8 #define SOCKET_OPS_BASE    128
 9 #define SOCKET_OPS_SET        (SOCKET_OPS_BASE)
10 #define SOCKET_OPS_GET        (SOCKET_OPS_BASE)
11 #define SOCKET_OPS_MAX        (SOCKET_OPS_BASE+1)
12 
13 #define UMSG      "a message from userspace"
14 #define UMSG_LEN        sizeof("a message from userspace")
15 
16 char kmsg[64];
17 
18 int main(void){
19     int sockfd;
20     int len;
21     int ret;
22     //if you want to create the socket success,you must use root right to run this pragramme
23     sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
24     if(sockfd < 0){
25         printf("can not create a socket\n");
26         printf("create socket error : %s",strerror(errno));
27         return -1;
28     }
29 
30     ret = setsockopt(sockfd,IPPROTO_IP,SOCKET_OPS_SET,UMSG,UMSG_LEN);
31     printf("setsockopt: ret = %d.msg=%s\n",ret,UMSG);
32     len = sizeof(char)*64;
33 
34     ret = getsockopt(sockfd,IPPROTO_IP,SOCKET_OPS_GET,kmsg,&len);
35     printf("getsockopt: ret=%d.msg=%s\n",ret,kmsg);
36     if(ret!=0){
37         printf("getsockopt error:errno=%d,errstr=%s\n",errno,strerror(errno));
38     }
39     close(sockfd);
40     return 0;
41 }

 

Makefile:

 1 ifneq ($(KERNELRELEASE),)
 2 mymodule-objs :=modules.c
 3 obj-m += modules.o
 4 else
 5 PWD := $(shell pwd)
 6 KVER := $(shell uname -r)
 7 KDIR := /lib/modules/$(KVER)/build
 8 all:
 9     gcc -o main main.c
10     $(MAKE) -C $(KDIR) M=$(PWD)
11 clean:
12     rm -rf *.o *.mod.c *.symvers *order *.markers *-
13 endif

 

  这是实验的代码其实大部分都是按网上的教程写的,但是这里讲一下我在从编写到运行成功遇到的问题。

  首先,从网上找的样例代码基本没有问题,但是Makefile文件我没有使用样例提供的代码,一是因为他写得有点复杂,我刚入门看得不是很懂,然后我就按照之前hello world的Makefile写了一个这次用的Makefile,结构基本一样,就是多了一步编译用户空间的代码,这里使用gcc编译就可以了。

  其次,编译成功并把模块加载成功以后,我运行用户空间的程序,因为最近才开始对linux有一定的了解,这里所以下怎样在终端运行可运行的文件:直接输入名字好像是无法运行的,例如我运行编译好的main,如果直接输入main,是无法运行的,但是可以使用路径名运行,就是说使用 ./main来运行,当然使用绝对路径来运行应该也是没有问题的。

  运行main,发现有出问题了,无法创建socket,输出sockfd结果为-1,在网上找了一下解决办法,发现可以使用trerror(errno)来输出错误的提示,输出看一下,发现原来是权限不够,所以如果想要运行main,还是得使用sudo ./main 运行。

  输出结果发现没有问题,除了输出来的语句格式太恶心了(→_→不要吐槽我)。

  详细代码是怎样跑的,我暂时先不写了,一是最近时间真不够用,二是这段代码还是比较容易读懂的,当然,中途可能需要去看一下那些宏定义是什么意思。

 

 

/******************************************************************************************************************************************************************************************/

持续更新...

posted @ 2015-04-26 12:57  海拉鲁的林克  阅读(403)  评论(0编辑  收藏  举报