内核模块包过滤防火墙的原型实现
1.原型系统的总体设计
分为两部分独立实现
- 运行在应用层的过滤规则配置程序,用来设置和启用包过滤规则和配置相应的规则参数,包括要控制的协议类型、IP地址、端口等
- 实现IP报文过滤的内核模块,通过在Netfilter框架中注册钩子函数的方式来实现对IP数据包的过滤和控制
- 内核模块不能直接访问规则配置程序中的过滤规则,所以通过注册设备文件结点的方式实现应用程序和内核之间的通信,传递所配置的过滤规则和参数
2.规则配置程序的设计
主要完成两方面的工作:
- 从用户的输入命令中解析出报文过滤规则
- 创建一个新的设备文件,通过写该设备文件,将所设置的过滤规则传递给Linux内核模块
3.内核模块的设计
主要功能是对收到的数据包进行过滤,即依据所配置的过滤规则,丢弃与所配置报文信息特征相匹配的数据包,包括四个部分
- 新设备文件的驱动:目的是获取规则配置程序向内核模块发送的过滤规则,也就是接收从规则配置程序写入设备文件的内容,将该内容保存到内核模块的变量中
- 报文过滤控制函数:包括一个总入口函数和一组具体规则判断函数。该函数在模块初始化阶段会被注册到Netfilter框架的相应钩子点上,当报文经过时,该函数将会被自动调用,根据过滤规则,对是否过滤该IP报文进行逻辑判断,将判断结果作为函数返回值,Netfilter根据函数的返回值进行报文过滤,放行还是阻止
- 内核模块的初始化函数:该函数在内核模块加载时被Linux自动调用,主要包括两方面的初始化工作:
- 注册新设备文件的驱动
- 将报文过滤控制函数注册到Netfilter框架的相应钩子点上,本系统是挂在
IP_POST_ROUTING
,即在对收到的报文做路由处理后执行包过滤,注册完成后,每次报文在路由处理后执行包过滤
- 内核模块的注销函数:该函数在内核模块注销时被Linux自动调用,主要完成设备文件驱动的注销,以及从Netfilter框架中注销钩子函数
4.测试
- ping功能测试此时可以ping通,打开防火墙
1
ping 10.160.101.243
再次ping该ip,无响应1
./configure -p ping -y 10.160.101.243
使用dmesg | tail
查看控制信息1
2
3
4[12103.702553] input info: p=1, x=0 y=-211443702 m=0 n=0
[12212.302296] An ICMP packet is denied!
[12213.304010] An ICMP packet is denied!
[12214.327981] An ICMP packet is denied! - udp报文过滤测试
查看本地DNS服务器配置这条信息后,不能以域名形式访问外网服务器,但能够以IP地址的形式访问外网服务器1
cat /etc/resolv.conf
1
./configure -p udp -y 127.0.0.53
1
2
3
4
5[ 8225.935267] input info: p=17, x=0 y=889192575 m=0 n=0
[ 8234.208966] A UDP packet is denied!
[ 8234.209040] A UDP packet is denied!
[ 8234.209100] A UDP packet is denied!
[ 8234.209212] A UDP packet is denied! - tcp报文过滤测试
使用 hping3 工具发送一个 TCP SYN 报文到指定的目标 IP 地址和端口配置规则之后1
hping3 -c 1 -S -p 80 10.160.101.243
1
2
3hping3 -c 1 -S -p 80 10.160.101.243
HPING 10.160.101.243 (ens33 10.160.101.243): S set, 40 headers + 0 data bytes
[send_ip] sendto: Operation not permitted1
2
3[13043.943593] input info: p=6, x=0 y=-211443702 m=0 n=20480
[13051.969525] A TCP packet is denied!
[13068.720651] A TCP packet is denied!
5.源代码
- configure.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// 规则配置程序定义5个全局变量,保存用户通过命令行选项配置的控制信息
unsigned int g_controlledProtocol = 0;
unsigned short g_controlledSrcport = 0;
unsigned short g_controlledDstport = 0;
unsigned int g_controlledSaddr = 0;
unsigned int g_controlledDaddr = 0;
int getpara(int argc, char* argv[]);
void display_usage(char* commandname);
int main(int argc, char* argv[])
{
char controlinfo[128]; // 规则信息的缓冲区
int controlinfoLen = 0; // 规则信息长度,0:关闭防火墙
int fd; // 设备文件打开后的文件描述符
struct stat buf; // 用于获取设备文件是否存在的临时缓冲区
if (argc == 1) { // 不带参数表示要关闭防火墙,把传入的规则长度置为0,关闭防火墙的报文检查控制功能
controlinfoLen = 0;
}
else {
getpara(argc, argv); // 获取命令行选项中规则信息到相应的全局变量
// 规则信息顺序:协议类型、源IP、目的IP、源端口、目的端口
// 每个字段占四个字节
*(int*)controlinfo = g_controlledProtocol;
*(int*)(controlinfo + 4) = g_controlledSaddr;
*(int*)(controlinfo + 8) = g_controlledDaddr;
*(int*)(controlinfo + 12) = g_controlledSrcport;
*(int*)(controlinfo + 16) = g_controlledDstport;
controlinfoLen = 20; // 设置规则信息的长度
}
if (stat("/dev/controlinfo", &buf) != 0) { // 用stat调用来测试设备文件是否已经存在
/* 如果设备文件不存在,则先调用mknod创建设备文件,
注意创建设备文件的参数要和内核中设备注册的相应参数一致 */
if (system("mknod /dev/controlinfo c 124 0") == -1) {
/* 创建设备文件:mknod /dev/controlinfo c 124 0:
创建一个字符型设备文件"/dev/controlinfo",
并指定设备号为124,设备的权限为0。 */
printf("Cannot create the device file!\n");
printf("Please check and try again!\n");
exit(1);
}
}
// O_RDWR:读写方式打开,S_IRUSR:用户可读权限,S_IWUSR:用户可写权限
fd = open("/dev/controlinfo", O_RDWR, S_IRUSR | S_IWUSR); // 打开设备文件
if (fd > 0) {
write(fd, controlinfo, controlinfoLen);
}
else {
perror("Cannot open /dev/controlinfo \n");
exit(1);
}
close(fd);
}
int getpara(int argc, char* argv[])
{
int optret;
unsigned short tmpport; // 保存端口的临时变量
optret = getopt(argc, argv, "pxymnh");
while (optret != -1) {
switch (optret) {
case 'p': // 协议类型
// ICMP/TCP/UDP的协议类型分别为1,6,17
if (strncmp(argv[optind], "ping", 4) == 0)
g_controlledProtocol = 1;
else if (strncmp(argv[optind], "tcp", 3) == 0)
g_controlledProtocol = 6;
else if (strncmp(argv[optind], "udp", 3) == 0)
g_controlledProtocol = 17;
else {
printf("Unknown protocol!Please check and try again\n");
exit(1);
};
break;
case 'x': // 源IP
// inet_aton用于将十进制点分IP地址格式转化为32位整数
if (inet_aton(argv[optind], (struct in_addr*)&g_controlledSaddr) == 0) {
// 不是有效的ip地址
printf("Invalid source ip address!Please check and try again\n");
exit(1);
}
break;
case 'y': // 目的IP
if (inet_aton(argv[optind], (struct in_addr*)&g_controlledDaddr) == 0) {
printf("Invalid destination ip address!Please check and try again\n");
exit(1);
}
break;
case 'm': // 源端口
tmpport = atoi(argv[optind]);
if (tmpport == 0) { // 转换失败或者非整数
printf("Invalid source port!Please check and try again\n");
exit(1);
}
g_controlledSrcport = htons(tmpport); // 转化为网络字节序
break;
case 'n': // 目的端口
tmpport = atoi(argv[optind]);
if (tmpport == 0) { // 转换失败或者非整数
printf("Invalid destination port!Please check and try again\n");
exit(1);
}
g_controlledDstport = htons(tmpport); // 转化为网络字节序
break;
case 'h': // 帮助信息
display_usage(argv[0]);
exit(1);
default:
display_usage(argv[0]);
exit(1);
}
optret = getopt(argc, argv, "pxymnh");
}
}
void display_usage(char* commandname)
{
printf("Usage1: %s \n", commandname); // 后不跟任何参数,即关闭防火墙功能
printf("Usage2: %s -p protocol -x sourceip -y destinationip -m sourceport -n destinationport\n", commandname);
} - mod_firewall.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// 函数原型
int port_check(unsigned short srcport, unsigned short dstport);
int ipaddr_check(unsigned int saddr, unsigned int daddr);
int icmp_check(void);
int tcp_check(void);
int udp_check(void);
unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state);
ssize_t write_controlinfo(struct file *file, const char __user *buf, size_t len, loff_t *offset);
static int __init initmodule(void);
static void __exit cleanupmodule(void);
// 过滤信息类变量
unsigned int g_controlledProtocol = 0; // 表示要控制的协议类型,1:ICMP,6:TCP,17:UDP
unsigned short g_controlledSrcport = 0; // 表示要控制的源端口,该变量只对TCP和UDP报文有效
unsigned short g_controlledDstport = 0; // 该变量只对TCP和UDP报文有效
unsigned int g_controlledSaddr = 0;
unsigned int g_controlledDaddr = 0;
int g_enableFlag = 0;
// 设备驱动变量
struct file_operations fops = {
.owner = THIS_MODULE,
.write = write_controlinfo,
};
static struct nf_hook_ops myhook;
struct sk_buff* tmpskb;
struct iphdr* piphdr;
module_init(initmodule);
module_exit(cleanupmodule);
MODULE_LICENSE("GPL");
// 函数实现
int port_check(unsigned short srcport, unsigned short dstport)
{
if (g_controlledSrcport == 0 && g_controlledDstport == 0) {
return MATCH;
}
if (g_controlledSrcport != 0 && g_controlledDstport == 0) {
if (g_controlledSrcport == srcport) {
return MATCH;
}
else {
return NMATCH;
}
}
if (g_controlledSrcport == 0 && g_controlledDstport != 0) {
if (g_controlledDstport == dstport) {
return MATCH;
}
else {
return NMATCH;
}
}
if (g_controlledSrcport != 0 && g_controlledDstport != 0) {
if (g_controlledSrcport == srcport && g_controlledDstport == dstport) {
return MATCH;
}
else {
return NMATCH;
}
}
return NMATCH; // 正常情况不会运行到这
}
int ipaddr_check(unsigned int saddr, unsigned int daddr)
{
if (g_controlledSaddr == 0 && g_controlledDaddr == 0) {
return MATCH;
}
if (g_controlledSaddr != 0 && g_controlledDaddr == 0) {
if (g_controlledSaddr == saddr) {
return MATCH;
}
else {
return NMATCH;
}
}
if (g_controlledSaddr == 0 && g_controlledDaddr != 0) {
if (g_controlledDaddr == daddr) {
return MATCH;
}
else {
return NMATCH;
}
}
if (g_controlledSaddr != 0 && g_controlledDaddr != 0) {
if (g_controlledSaddr == saddr && g_controlledDaddr == daddr) {
return MATCH;
}
else {
return NMATCH;
}
}
return NMATCH; // 正常情况不会运行到这
}
int icmp_check(void)
{
struct icmphdr* picmphdr;
// ICMP报文紧跟在IP头部之后,IP报文头长piphdr->ihl*4,ip报文头右移32bit
picmphdr = (struct icmphdr*)(tmpskb->data + (piphdr->ihl * 4));
if (picmphdr->type == 8) { // 客户端向远程主机发出的ping请求报文
if (ipaddr_check(piphdr->saddr, piphdr->daddr) == MATCH) {
printk("An ICMP packet is denied!\n");
return NF_DROP;
}
}
if (picmphdr->type == 0) { // 远程主机向客户端发出的ping响应报文
// 注意将响应报文的目的ip与要控制的源ip,报文的源ip跟要控制的目的ip作比较
if (ipaddr_check(piphdr->daddr, piphdr->saddr) == MATCH) {
printk("An ICMP packet is denied!\n");
return NF_DROP;
}
}
// 其他类型ICMP或者不是要控制的,通行
return NF_ACCEPT;
}
int tcp_check(void)
{
struct tcphdr* ptcphdr;
// TCP报文紧跟在IP头部之后,IP报文头长piphdr->ihl*4,ip报文头右移32bit
ptcphdr = (struct tcphdr*)(tmpskb->data + (piphdr->ihl * 4));
if (ipaddr_check(piphdr->saddr, piphdr->daddr) == MATCH && port_check(ptcphdr->source, ptcphdr->dest) == MATCH) {
printk("A TCP packet is denied!\n");
return NF_DROP;
}
else {
return NF_ACCEPT;
}
}
int udp_check(void)
{
struct udphdr* pudphdr;
pudphdr = (struct udphdr*)(tmpskb->data + (piphdr->ihl * 4));
if (ipaddr_check(piphdr->saddr, piphdr->daddr) == MATCH && port_check(pudphdr->source, pudphdr->dest) == MATCH) {
printk("A UDP packet is denied!\n");
return NF_DROP;
}
else {
return NF_ACCEPT;
}
}
unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
// skb 为指向 netfilter 正在处理报文缓冲区的指针
if (!g_enableFlag) {
return NF_ACCEPT;
}
tmpskb = skb; // 获得报文缓冲区的指针
piphdr = (struct iphdr *)skb_network_header(tmpskb);
if (piphdr->protocol != g_controlledProtocol) {
return NF_ACCEPT;
}
if (piphdr->protocol == 1) {
return icmp_check();
}
else if (piphdr->protocol == 6) {
return tcp_check();
}
else if (piphdr->protocol == 17) {
return udp_check();
}
else { // 其他协议,默认放行,一般不会运行到这
printk("Unknown type of packet!\n");
return NF_ACCEPT;
}
}
ssize_t write_controlinfo(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{
char controlinfo[128]; // 用于保存所写入数据的内核空间缓冲区
char* pchar; // 临时缓冲区指针
pchar = controlinfo;
if (len == 0) { // 写入内容为0,表示关闭防火墙
g_enableFlag = 0;
printk("Firewall is closed!\n");
return len;
}
// 调用函数 copy_from_user 将规则配置程序传入的用户配置信息复制到内核空间缓存区
if (copy_from_user(controlinfo, buf, len) != 0) {
printk("Can't get the control rule!\n");
printk("Something may be wrong, please check it!\n");
return 0;
}
// 将规则赋给全局变量
g_controlledProtocol = *((int*)pchar);
pchar = pchar + 4;
g_controlledSaddr = *((int*)pchar);
pchar = pchar + 4;
g_controlledDaddr= *((int*)pchar);
pchar = pchar + 4;
g_controlledSrcport= *((int*)pchar);
pchar = pchar + 4;
g_controlledDstport= *((int*)pchar);
g_enableFlag = 1;
printk("input info: p=%d, x=%d y=%d m=%d n=%d\n", g_controlledProtocol, g_controlledSaddr, g_controlledDaddr, g_controlledSrcport, g_controlledDstport);
return len;
}
static int __init initmodule(void) {
int ret;
printk("Init Module\n");
myhook.hook = hook_func;
myhook.hooknum = NF_INET_POST_ROUTING;
myhook.pf = PF_INET;
myhook.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &myhook);
ret = register_chrdev(124, "/dev/controlinfo", &fops);
if (ret != 0) {
printk("Can't register device file!\n");
}
return 0;
}
static void __exit cleanupmodule(void) {
nf_unregister_net_hook(&init_net, &myhook);
unregister_chrdev(124, "controlinfo");
printk("CleanUp\n");
} - Makefile
1
2
3
4
5
6
7
8
9
10
11CC := gcc-13
obj-m += mod_firewall.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
$(CC) -o configure configure.c
clean:
rm -f *.o *.ko *.mod.c *.mod.o *.symvers *.order configure *.mod