计算机网络的课程设计要做防火墙,老师没有限制在什么系统上面做,所以决定在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. 使用Gitalk实现静态页面评论的功能

    使用静态页面的理由 本人在Github上使用github.io部署了一个静态主页,地址是http://aopstudio.github.io,用于存放一些笔记文件.虽然静态页面功能少,自动化程度低,不 ...

  2. Hibernate 一对多查询对set的排序

    Hibernate可以进行一对多的关联查询,例如:查询了试卷题目,可以自动获取试卷题目的选项对象. 但是关联出来的集合对象是无序的,那么在显示的时候就会有问题,经过百度发现可以对Set进行设置排序. ...

  3. js调试技巧汇总中

    由于设备多样性(PC.ios.安卓.pad.tv)以及各设备对js脚本支持性差异性.js兼容性调试显得越来越重要. 0:尽力模仿真实场景下进行调试,迅速定位问题以及提供解决方案. 1:setTimeo ...

  4. Java 8 (7) 重构、测试和调试

    为改善可读性和灵活性重构代码 看到这里我们已经可以使用lambda和stream API来使代码更简洁,用在新项目上.但大多数并不是全新的项目,而是对现有代码的重构,让它变的更简洁可读,更灵活. 改善 ...

  5. C#知识点-枚举器和迭代器

    一.几个基本概念的理解 问题一:为什么数组可以使用foreach输出各元素 答:数组是可枚举类型,它实现了一个枚举器(enumerator)对象:枚举器知道各元素的次序并跟踪它们的位置,然后返回请求的 ...

  6. (求助)对某一颜色,设置透明度 alpha 后,其他使用该颜色的地方 受到影响!!!!原因未知

    对某一颜色,设置透明度 alpha 后,其他使用该颜色的地方 受到影响!!!!原因未知,有谁碰到过这样的问题?????? 测试了以下三款手机,结果如下: 1.android 4.4.2: 不受影响 2 ...

  7. android开发小内容

    EditText弹出输入数字:android:inputType="phone"

  8. "HybridDB · 性能优化 · Count Distinct的几种实现方式” 读后感

    原文地址:HybridDB · 性能优化 · Count Distinct的几种实现方式 HybridDB是阿里基于GreenPlum开发的一款MPP分析性数据库,而GreenPlum本身基于Post ...

  9. CherryPy 入门

    CherryPy是一个Python的HTTP框架,可以用Python来处理HTTP请求然后返回结果. 1. 安装 可以去这个地址下载 CherryPy-3.1.2.win32.exe .或者去这个链接 ...

  10. MAGENTO 插件

    导航放到右侧:magento-community/RicoNeitzel_VertNav 后台图片管理显示图片:magento-community/TBT_Enhancedgrid magento-c ...