Linux - 模塊編程初試

  計算機網絡的課程設計要作防火牆,老師沒有限制在什麼系統上面作,因此決定在Linux上實現。找了一下相關的資料,發現其實Linux有提供Netfilter/Iptables,爲用戶提供防火牆的功能,稍微看了一下,使用Iptables可以很方便地配置用戶想要的防火牆,可是好像只能作過濾、數據報修改以及網絡地址轉換,好像不能作獲取其中信息的功能,並且看了一下網上其餘人的提問或者博客,好像想作相似的功能仍是須要直接使用Netfilter。而若是想要使用Netfiler的話,須要編寫hook函數,這個過程當中不得不避免要編寫模塊。因此這裏記錄一下我在這個過程當中作的一些嘗試以及遇到的問題。html

 

  使用的平臺:Ubuntu 14.10linux

  內核版本: 3.16.0-23-generic  (這個很重要啊,不用的內核可能函數都是不同的,網上的大部分教程用的內核版本都是2.6)shell

 

2015.4.23編程

  第一次我是編寫一個hello world,在加載模塊的時候以及移除模塊的時候各輸出一次,這裏作的都是跟着網上的教程寫的。vim

代碼以下:api

 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.kosocket

  使用指令dmesg可以查看到加載的時候的輸出。函數

  移除模塊: sudo rmmod hello.koui

  再次使用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 運行。

  輸出結果發現沒有問題,除了輸出來的語句格式太噁心了(→_→不要吐槽我)。

  詳細代碼是怎樣跑的,我暫時先不寫了,一是最近時間真不夠用,二是這段代碼仍是比較容易讀懂的,固然,中途可能須要去看一下那些宏定義是什麼意思。

 

 

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

持續更新...

相關文章
相關標籤/搜索