计算机网络的课程设计要做防火墙,老师没有限制在什么系统上面做,所以决定在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 - 模块编程初试的更多相关文章

  1. Linux模块编程框架

    Linux是单内核系统,可通用计算平台的外围设备是频繁变化的,不可能将所有的(包括将来即将出现的)设备的驱动程序都一次性编译进内核,为了解决这个问题,Linux提出了可加载内核模块(Loadable ...

  2. 初探linux内核编程,参数传递以及模块间函数调用

    一.前言                                  我们一起从3个小例子来体验一下linux内核编程.如下: 1.内核编程之hello world 2.模块参数传递 3.模块间 ...

  3. Linux内核模块编程——Hello World模块

    Linux内核模块编程 编程环境 Ubuntu 16.04 LTS 什么是模块 内核模块的全称是动态可加载内核模块(Loadable Kernel Modul,KLM),可以动态载入内核,让它成为内核 ...

  4. linux网络编程 no route to host 解决方案

    linux网络编程 no route to host 解决方案 [整合资料] (2013-05-13 21:38:12) 转载▼ 标签: net iptables it 分类: Linux 参考资料h ...

  5. Linux网络编程入门 (转载)

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  6. [转] - Linux网络编程 -- 网络知识介绍

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  7. 【转】Linux网络编程入门

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  8. 《转》Linux网络编程入门

    原地址:http://www.cnblogs.com/duzouzhe/archive/2009/06/19/1506699.html (一)Linux网络编程--网络知识介绍 Linux网络编程-- ...

  9. Linux系统编程【转】

    转自:https://blog.csdn.net/majiakun1/article/details/8558308 一.Linux系统编程概论 1.1 系统编程基石 syscall: libc:标准 ...

随机推荐

  1. 1617: [Usaco2008 Mar]River Crossing渡河问题(dp)

    1617: [Usaco2008 Mar]River Crossing渡河问题 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1219  Solved:  ...

  2. CentOS 7 配置 Nginx 正向代理 http、https 最详解

    手头项目中有使用到 nginx,因为使用的三方云服务器,想上外网需要购买外网IP的,可是有些需要用到外网却不常用的主机也挂个外网IP有点浪费了,便想使用nginx的反向代理来实现多台内网服务器使用一台 ...

  3. 【洛谷3239_BZOJ4008】[HNOI2015] 亚瑟王(期望 DP)

    题目: 洛谷 3239 分析: 卡牌造成的伤害是互相独立的,所以 \(ans=\sum f_i\cdot d_i\) ,其中 \(f_i\) 表示第 \(i\) 张牌 在整局游戏中 发动技能的概率.那 ...

  4. linux学习之路6 Vi文本编辑器

    vim是vi的增强版本 vim拥有三种模式: 命令模式(常规模式) vim启动后,默认进入命令模式.任何模式都可以通过按esc键回到命令模式(可以多按几次.命令模式可以通过键入不同的命令完成选择.复制 ...

  5. 网站开发综合技术 HTML

    HTML            内容(Hyper Text Markup Language,超文本标记语言) CSS             网页美化 Javascript      脚本语言 第一部 ...

  6. mac当你有多个版本的命令存在是怎么使用最新版本

    例如你安装了一个最新的git.然而系统中由于xcode等自带的git的存在.使得/usr/bin/git 是xcode的版本. 只需要再 ~/.bash_profile 中添加一行优先path即可 e ...

  7. ADPU 大全

    APDU协议 APDU协议,即是智能卡与读写器间的应用层协议,在ISO7816-4[7]中定义了该协议的结构格式.APDU数据有两种结构,读写器使用的APDU结构为命令APDU,C-APDU(Comm ...

  8. Pro ASP.Net Core MVC 6th 第四章

    第四章 C# 关键特征 在本章中,我描述了Web应用程序开发中使用的C#特征,这些特征尚未被广泛理解或经常引起混淆. 这不是关于C#的书,但是,我仅为每个特征提供一个简单的例子,以便您可以按照本书其余 ...

  9. Hibernate+Spring整合开发步骤

    Hibernate是一款ORM关系映射框架+Spring是结合第三方插件的大杂烩,Hibernate+Spring整合开发效率大大提升. 整合开发步骤如下: 第一步:导入架包: 1.Hibernate ...

  10. Flask框架 之重定向、cookie和session

    一.URL重定向(redirect) @app.route("/login") def login(): # 使用url_for函数通过视图函数的名字找到url路径 url = u ...