Linux - 模块编程初试
计算机网络的课程设计要做防火墙,老师没有限制在什么系统上面做,所以决定在Linux上实现。找了一下相关的资料,发现其实Linux有提供Netfilter/Iptables,为用户提供防火墙的功能,稍微看了一下,使用Iptables能够很方便地配置用户想要的防火墙,但是好像只能做过滤、数据报修改以及网络地址转换,好像不能做获取其中信息的功能,而且看了一下网上其他人的提问或者博客,好像想做类似的功能还是需要直接使用Netfilter。而如果想要使用Netfiler的话,需要编写hook函数,这个过程中不得不避免要编写模块。所以这里记录一下我在这个过程中做的一些尝试以及遇到的问题。
使用的平台:Ubuntu 14.10
内核版本: 3.16.0-23-generic (这个很重要啊,不用的内核可能函数都是不一样的,网上的大部分教程用的内核版本都是2.6)
2015.4.23
第一次我是编写一个hello world,在加载模块的时候以及移除模块的时候各输出一次,这里做的都是跟着网上的教程写的。
代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h> static int __init lkp_init(void);
static int __exit lkp_exit(void); static int __init lkp_init(void){
printk("<1>Hello,world!\n");
return ;
} static int __exit lkp_exit(void){
printk("<2>Hello,world!\n");
return ;
} module_init(lkp_init);
module_exit(lkp_exit);
Makefile:
ifneq ($(KERNELRELEASE),)
mymodule-objs:=hello.c
obj-m += hello.o else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf *.o *.mod.c *.ko *.symvers *order *.markers *-
endif
make一次以后然后加载模块: sudo insmod hello.ko
使用指令dmesg能够查看到加载的时候的输出。
移除模块: sudo rmmod hello.ko
再次使用dmesg能够查看到移除的时候的输出。
这里这个Makefile是怎么执行的,为什么需要使用dmesg来查看输出的问题我暂时先不写,因为这些在网上都能找到而且能够比较清楚地解释,我打算写的是一些我遇到的问题。
2015.4.26
开始编写与Netfilter有关的函数,首先写的这个也是按照别人的教程给的例子写的程序。写一个钩子挂载到 LOCAL_OUT上。然后每隔四个发出去的数据包就拦截下下一个数据包。
代码如下:
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h> static int count=; 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 *)){
count=(count+)%;
if(count==){
return NF_DROP;
}
return NF_ACCEPT;
} static struct nf_hook_ops nfho; static int __init myhook_init(void){
nfho.hook = func;
nfho.owner = THIS_MODULE;
nfho.pf = PF_INET;
nfho.hooknum = NF_INET_LOCAL_OUT;
nfho.priority = NF_IP_PRI_FIRST;
return nf_register_hook(&nfho);
} static void __exit myhook_fini(void){
nf_unregister_hook(&nfho);
} module_init(myhook_init);
module_exit(myhook_fini);
Makefile:
ifneq ($(KERNELRELEASE),)
mymodule-objs:=test0.c
obj-m += test0.o else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.ko *.symvers *order *.markers *-
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 包住了,所以它编译的时候没有包含进去,如下面的代码:
#ifndef __KERNEL__ #include <limits.h> /* for INT_MIN, INT_MAX */ /* IP Cache bits. */
/* Src IP address. */
#define NFC_IP_SRC 0x0001
/* Dest IP address. */
#define NFC_IP_DST 0x0002
/* Input device. */
#define NFC_IP_IF_IN 0x0004
/* Output device. */
#define NFC_IP_IF_OUT 0x0008
/* TOS. */
#define NFC_IP_TOS 0x0010
/* Protocol. */
#define NFC_IP_PROTO 0x0020
/* IP options. */
#define NFC_IP_OPTIONS 0x0040
/* Frag & flags. */
#define NFC_IP_FRAG 0x0080 /* Per-protocol information: only matters if proto match. */
/* TCP flags. */
#define NFC_IP_TCPFLAGS 0x0100
/* Source port. */
#define NFC_IP_SRC_PT 0x0200
/* Dest port. */
#define NFC_IP_DST_PT 0x0400
/* Something else about the proto */
#define NFC_IP_PROTO_UNKNOWN 0x2000 /* IP Hooks */
/* After promisc drops, checksum checks. */
#define NF_IP_PRE_ROUTING 0
/* If the packet is destined for this box. */
#define NF_IP_LOCAL_IN 1
/* If the packet is destined for another interface. */
#define NF_IP_FORWARD 2
/* Packets coming from a local process. */
#define NF_IP_LOCAL_OUT 3
/* Packets about to hit the wire. */
#define NF_IP_POST_ROUTING 4
#define NF_IP_NUMHOOKS 5
#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
#ifndef __KERNEL__
#define __KERNEL__
#endif #ifndef MODULE
#define MODULE
#endif #include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/netfilter_ipv4.h>
#include <linux/uaccess.h>
#define SOCKET_OPS_BASE 128
#define SOCKET_OPS_SET (SOCKET_OPS_BASE)
#define SOCKET_OPS_GET (SOCKET_OPS_BASE)
#define SOCKET_OPS_MAX (SOCKET_OPS_BASE+1) #define KMSG "a message from kernel"
#define KMSG_LEN sizeof("a message from kernel") MODULE_LICENSE("GPL"); static int recv_msg(struct sock *sk,int cmd,void __user* user,unsigned int len){
int ret = ;
printk(KERN_INFO "sockopt: recv_msg()\n");
if(cmd == SOCKET_OPS_SET){
char umsg[];
int len = sizeof(char)*;
memset(umsg,,len);
ret = copy_from_user(umsg,user,len);
printk("recv_msg:umsg=%s. ret=%d\n",umsg,ret);
}
return ;
} static int send_msg(struct sock *sk,int cmd,void __user *user,int *len){
int ret = ;
printk(KERN_INFO "sockopt:send_msg()\n");
if(cmd == SOCKET_OPS_GET){
ret = copy_to_user(user,KMSG,KMSG_LEN);
printk("send_msg:umsg=%s. ret=%d.success\n",KMSG,ret);
}
return ;
} static struct nf_sockopt_ops test_sockops; static int __init init_sockopt(void){
test_sockops.pf = PF_INET;
test_sockops.set_optmin = SOCKET_OPS_SET;
test_sockops.set_optmax = SOCKET_OPS_MAX;
test_sockops.set = recv_msg;
test_sockops.get_optmin = SOCKET_OPS_GET;
test_sockops.get_optmax = SOCKET_OPS_MAX;
test_sockops.get = send_msg;
test_sockops.owner = THIS_MODULE; printk(KERN_INFO "sockopt: init_sockopt()\n");
return nf_register_sockopt(&test_sockops);
} static void __exit fini_sockopt(void){
printk(KERN_INFO "sockopt:fini_sockopt()\n");
nf_unregister_sockopt(&test_sockops);
} module_init(init_sockopt);
module_exit(fini_sockopt);
用户空间的代码 main.c:
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <string.h>
#include <errno.h> #define SOCKET_OPS_BASE 128
#define SOCKET_OPS_SET (SOCKET_OPS_BASE)
#define SOCKET_OPS_GET (SOCKET_OPS_BASE)
#define SOCKET_OPS_MAX (SOCKET_OPS_BASE+1) #define UMSG "a message from userspace"
#define UMSG_LEN sizeof("a message from userspace") char kmsg[]; int main(void){
int sockfd;
int len;
int ret;
//if you want to create the socket success,you must use root right to run this pragramme
sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
if(sockfd < ){
printf("can not create a socket\n");
printf("create socket error : %s",strerror(errno));
return -;
} ret = setsockopt(sockfd,IPPROTO_IP,SOCKET_OPS_SET,UMSG,UMSG_LEN);
printf("setsockopt: ret = %d.msg=%s\n",ret,UMSG);
len = sizeof(char)*; ret = getsockopt(sockfd,IPPROTO_IP,SOCKET_OPS_GET,kmsg,&len);
printf("getsockopt: ret=%d.msg=%s\n",ret,kmsg);
if(ret!=){
printf("getsockopt error:errno=%d,errstr=%s\n",errno,strerror(errno));
}
close(sockfd);
return ;
}
Makefile:
ifneq ($(KERNELRELEASE),)
mymodule-objs :=modules.c
obj-m += modules.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
gcc -o main main.c
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf *.o *.mod.c *.symvers *order *.markers *-
endif
这是实验的代码其实大部分都是按网上的教程写的,但是这里讲一下我在从编写到运行成功遇到的问题。
首先,从网上找的样例代码基本没有问题,但是Makefile文件我没有使用样例提供的代码,一是因为他写得有点复杂,我刚入门看得不是很懂,然后我就按照之前hello world的Makefile写了一个这次用的Makefile,结构基本一样,就是多了一步编译用户空间的代码,这里使用gcc编译就可以了。
其次,编译成功并把模块加载成功以后,我运行用户空间的程序,因为最近才开始对linux有一定的了解,这里所以下怎样在终端运行可运行的文件:直接输入名字好像是无法运行的,例如我运行编译好的main,如果直接输入main,是无法运行的,但是可以使用路径名运行,就是说使用 ./main来运行,当然使用绝对路径来运行应该也是没有问题的。
运行main,发现有出问题了,无法创建socket,输出sockfd结果为-1,在网上找了一下解决办法,发现可以使用trerror(errno)来输出错误的提示,输出看一下,发现原来是权限不够,所以如果想要运行main,还是得使用sudo ./main 运行。
输出结果发现没有问题,除了输出来的语句格式太恶心了(→_→不要吐槽我)。
详细代码是怎样跑的,我暂时先不写了,一是最近时间真不够用,二是这段代码还是比较容易读懂的,当然,中途可能需要去看一下那些宏定义是什么意思。
/******************************************************************************************************************************************************************************************/
持续更新...
Linux - 模块编程初试的更多相关文章
- Linux模块编程框架
Linux是单内核系统,可通用计算平台的外围设备是频繁变化的,不可能将所有的(包括将来即将出现的)设备的驱动程序都一次性编译进内核,为了解决这个问题,Linux提出了可加载内核模块(Loadable ...
- 初探linux内核编程,参数传递以及模块间函数调用
一.前言 我们一起从3个小例子来体验一下linux内核编程.如下: 1.内核编程之hello world 2.模块参数传递 3.模块间 ...
- Linux内核模块编程——Hello World模块
Linux内核模块编程 编程环境 Ubuntu 16.04 LTS 什么是模块 内核模块的全称是动态可加载内核模块(Loadable Kernel Modul,KLM),可以动态载入内核,让它成为内核 ...
- linux网络编程 no route to host 解决方案
linux网络编程 no route to host 解决方案 [整合资料] (2013-05-13 21:38:12) 转载▼ 标签: net iptables it 分类: Linux 参考资料h ...
- Linux网络编程入门 (转载)
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
- [转] - Linux网络编程 -- 网络知识介绍
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
- 【转】Linux网络编程入门
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
- 《转》Linux网络编程入门
原地址:http://www.cnblogs.com/duzouzhe/archive/2009/06/19/1506699.html (一)Linux网络编程--网络知识介绍 Linux网络编程-- ...
- Linux系统编程【转】
转自:https://blog.csdn.net/majiakun1/article/details/8558308 一.Linux系统编程概论 1.1 系统编程基石 syscall: libc:标准 ...
随机推荐
- 小记 react 数据存储位置
react 中状态的六个存储位置 state 我想大家都知道这个地方,而且在使用 setState 时会触发组件的更新 class prop 将值存在 class 的对象中,如: class App ...
- 搞定springboot项目连接远程服务器上kafka遇到的坑以及完整的例子
版本 springboot 2.1.5.RELEASE kafka 2.2 遇到的坑 用最新的springboot就要用最新的kafka版本! 当我启动云服务器上的zk后,再启动kafka后台日志也没 ...
- [Usaco2018 Open]Milking Order
Description Farmer John的N头奶牛(1≤N≤10^5),仍然编号为1-N,正好闲得发慌.因此,她们发展了一个与Farmer John每天早上为她们挤牛奶的时候的排队顺序相关的复杂 ...
- hdu1166 敌兵布阵(树状数组)
敌兵布阵 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submi ...
- 使用QTP录制自带Flight小实例
1.双击打开QTP10.0,启动过程中测试类型选择“WEB”. 2.进入主界面,New——Test,新建一个测试用例. 3.点击Record按钮,Record and settings对话框中,可以选 ...
- UVM基础之---------uvm factory机制base
从名字上面就知道,uvm_factory用来制造uvm_objects和component.在一个仿真过程中,只有一个factory的例化存在. 用户定义的object和component types ...
- JavaScript开发心得--如何传递某行数据给下一页
1, 应用场景 在某个html页面显示一批数据,如20个用户的名称.年龄等,每行都要一个编辑按钮,点击编辑后,将此行数据带入某个专门的编辑页进行显示,修改后保存. 问题是 点击编辑按钮后,如何得知要编 ...
- CherryPy 入门
CherryPy是一个Python的HTTP框架,可以用Python来处理HTTP请求然后返回结果. 1. 安装 可以去这个地址下载 CherryPy-3.1.2.win32.exe .或者去这个链接 ...
- canvas一周一练 -- canvas绘制奥运五环(1)
运行效果: <!DOCTYPE html> <html> <head> </head> <body> <canvas id=" ...
- js删除局部变量
alert('value:'+str+'\ttype:'+typeof(str)) //声明变量前,引用 var str="dd"; alert('value:'+str+'\tt ...