• netfilter编程实例——一个简单的防火墙


    一.iptables防火墙netfilter介绍

    Linux 防火墙包含两部分,内核 netfilter 和用户空间工具 iptables。管理员通过 iptables 工具集和内核打交道,将防火墙规则写入内核中。内核 netfilter 执行报文过滤规则。

    netfilter五个钩子点在协议栈的位置。

      netfilter分别在这五个钩子点嵌入NF_HOOK函数,然后通过NF_HOOK函数进入netfilter框架

    不同方向报文所经过的挂接点

    发往本机的报文

                                                

    从本机发出的报文         

                                              

    从本机转发的报文          

                             

    前面说过,Netfilter 是通过在网络协议中的不同位置挂载钩子函数来对数据包进行过滤和处理,而且每个挂载点能够挂载多个钩子函数,所以 Netfilter 使用链表结构来存储这些钩子函数,如图所示

                               

    Netfilter 的每个挂载点都使用一个链表来存储钩子函数列表。在内核中,定义了一个名为 nf_hooks 的数组来存储这些链表,struct list_head nf_hooks[32][5];

    钩子函数 前面我们介绍过,Netfilter 通过链表来存储钩子函数,而钩子函数是通过结构 nf_hook_ops 来描述的,其定义如下

    struct nf_hook_ops

    {    

    struct list_head list;   // 连接相同挂载点的钩子函数    

    nf_hookfn *hook;     // 钩子函数指针  

      int pf;                      // 协议类型    

    int hooknum;            // 钩子函数所在链    

    int priority;                // 优先级

    };

     其中 hook 字段的类型为 nf_hookfn,nf_hookfn 类型的定义如下 ypedef unsigned int nf_hookfn(unsigned int hooknum,   struct sk_buff **skb,const struct net_device *in,const struct net_device *out,    int (*okfn)(struct sk_buff *)); hooknum:钩子函数所在链(挂载点),如 NF_IP_PRE_ROUTING。 skb:数据包对象,就是要处理或者过滤的数据包。 in:接收数据包的设备对象。 out:发送数据包的设备对象。 okfn:当挂载点上所有的钩子函数都处理过数据包后,将会调用这个函数来对数据包进行下一步处理。

    注册钩子函数 当定义好一个钩子函数结构后,需要调用 nf_register_hook 函数来将其注册到 nf_hooks 数组中

     如图 所示,我们要把优先级为 20 的钩子函数插入到 PRE_ROUTING 这个链中,而 PRE_ROUTING 链已经存在两个钩子函数,一个优先级为 10, 另外一个优先级30。

    发调用钩子函数 钩子函数已经被保存到不同的链上,那么什么时候才会触发调用这些钩子函数来处理数据包呢? 要触发调用某个挂载点上(链)的所有钩子函数,需要使用 NF_HOOK 宏来实现,其定义如下: 首先介绍一下 NF_HOOK 宏的各个参数的作用: pf:协议类型,就是 nf_hooks 数组的第一个维度,如 IPv4 协议就是 PF_INET。 hook:要调用哪一条链(挂载点)上的钩子函数,如 NF_IP_PRE_ROUTING。 indev:进来的设备,以struct net_device结构表示。 outdev:出去的设备,以struct net_device结构表示; okfn:当链上的所有钩子函数都处理完成,将会调用此函数继续对数据包进行处理。 而 NF_HOOK 宏的实现也比较简单,首先判断一下钩子函数链表是否为空,如果是空的话,就直接调用 okfn 函数来处理数据包,否则就调用 nf_hook_slow 函数来处理数据包。我们来看看 nf_hook_slow 函数的功能:

    nf_hook_slow 函数的实现也比较简单,过程如下: 首先调用 nf_iterate 函数来遍历钩子函数链表,并调用链表上的钩子函数来处理数据包。 如果处理结果为 NF_ACCEPT,表示数据包通过所有钩子函数的处理, 那么就调用 okfn 函数继续处理数据包。 如果处理结果为 NF_DROP,表示数据包没有通过钩子函数的处理,应该丢弃此数据包。 既然 Netfilter 是通过调用 NF_HOOK 宏来调用钩子函数链表上的钩子函数,那么内核在什么地方调用这个宏呢?

    内核协议栈角度来看待五个钩子的回调

    数据接收过程走的是 1 和 2,发送过程走的是 4 、5,转发过程是 1、3、5。

    netfilter在内核的实现 以接收过程为例 Linux 在网络包接收在 IP 层的入口函数是 ip_rcv。网络在这里包碰到的第一个 HOOK 就是 PREROUTING。去检查过滤点是否已经有注册了相关的用于处理数据包的钩子函数。如果有挨个去遍历链表nf_hooks去寻找target ,当该钩子上的规则都处理完后,会进行路由选择。如果发现是本设备的网络包,进入 ip_local_deliver 中,在这里又会遇到 INPUT 钩子。 int ip_rcv(struct sk_buff *skb, ......){     ...... return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish); } / /遍历钩子函数链表,并且调用钩子函数对数据包进行处理     verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev, outdev, &elem, okfn);     ...     // 如果处理结果为 NF_ACCEPT, 表示数据包通过所有钩子函数的处理, 那么就调用 okfn 函数继续处理数据包     // 如果处理结果为 NF_DROP, 表示数据包被拒绝, 应该丢弃此数据包 。

    netfilter编程实例——一个简单的防火墙 

    二、内核层代码实现

    头文件初始化一些状态宏,设定驱动程序处理的最小值与最大值,然后在这之间设置防火墙不同功能的编号,尽量设置大一点避免与内核中某些命令值重复。结构体则用于存储防火墙的过滤规则信息

    firewall/filter/fwfilter.h

    1. #define SOE_MIN 0x6000 //驱动程序处理最小值
    2. #define BANPING 0x6001 //过滤程序和驱动程序对话时禁ping功能编号
    3. #define BANIP 0x6002 //禁ip功能编号
    4. #define BANPORT 0x6003 //禁port功能编号
    5. #define NOWRULE 0x6004 //获取防火墙当前规则功能编号
    6. #define SOE_MAX 0x6100 //驱动程序处理最大值
    7. typedef struct ban_status{
    8. int ping_status; //是否禁ping,1禁止,0未设置
    9. int ip_status; //是否禁ip,1禁止,0未设置
    10. int port_status; //是否禁port,1禁止,0未设置
    11. unsigned int ban_ip; //禁ip数值
    12. unsigned short ban_port; //禁port数值
    13. }ban_status;

     过滤网络报文功能的实现主要使用 hookLocalIn 函数。ban ping 功能的实现是如果检测到是 ICMP 数据包,并且设置了禁用 ping 命令规则,则丢弃该 ICMP 数据包。

     ban port 功能的实现是如果设置了禁用端口规则,并且源端口符合用户设置的端口,则丢弃发往该端口的所有的数据包

      ban ip 功能的实现是如果设置了禁用 ip 规则,并且源 ip 地址符合,丢弃该源 ip 发送的数据包   

    从《Linux网络编程》书中截取的这三个功能流程图

    内核空间和用户空间的通信则是使用  hookSockoptSet() 函数和 hookSockoptGet() 函数,hookSockoptSet() 函数获取用户空间的命令信息,主要用到 copy_from_user() 函数,复制用户空间的数据到内核模块,然后按照用户的命令执行过滤规则

    hookSockoptGet() 函数将内核模块的防火墙的规则情况传输给用户空间,主要用到 copy_to_user() 函数,复制内核空间的数据到用户空间

    三、应用层代码实现

    get_status() 函数分别获取当前防火墙三个功能的状态,若有禁止的 ip 或端口则打印出来详细禁止信息

    改变 ping 规则时函数将当前 ping 状态取反,再把信息传到内核

    改变 ip 规则函数,首先判断当前是否有 ip 已封禁,若当前无封禁 ip,则改变 ip 状态,由用户输入需封禁 ip,这里需要注意使用 inet_addr 函数转换 ip 格式。若当前已有封禁 ip,则直接取消封禁,再把信息传到内核

     改变端口规则函数,首先判断当前是否有端口已封禁,若当前无封禁端口,则改变端口状态,由用户输入需封禁端口。若当前已有封禁端口,则直接取消封禁,再把信息传到内核

    四、编译运行

    编译内核的 Makefile 文件中指定内核模块的编译文件和头文件路径,编译模块的名称,当前模块的路径

    firewall/filter/Makefile

    1. # Makefile 4.0
    2. obj-m := fwfilter.o
    3. CURRENT_PATH := $(shell pwd)
    4. LINUX_KERNEL := $(shell uname -r)
    5. LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
    6. all:
    7. make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
    8. clean:
    9. make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

    在 /firewall/filter 路径下编译、加载内核

    1. make //编译
    2. insmod fwfilter.ko //加载内核(rmmod 卸载内核)

    在 /firewall/app 路径下编译应用层测试代码,运行应用层程序

    1. gcc fwctl.c -o fwctl
    2. ./fwctl

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <sys/types.h>
    5. #include <sys/socket.h>
    6. #include <arpa/inet.h>
    7. #include <unistd.h>
    8. #include <pthread.h>
    9. #include <signal.h>
    10. #include <errno.h>
    11. #include "../filter/fwfilter.h"
    12. ban_status rules;
    13. void printError(char * msg)
    14. {
    15. printf("%s error %d: %s\n", msg, errno, strerror(errno));
    16. }
    17. void get_status(); //获得当前防火墙规则函数
    18. void change_status(int sockfd, socklen_t len); //改变防火墙规则函数
    19. void change_ping(int sockfd, socklen_t len); //改变ping规则函数
    20. void change_ip(int sockfd, socklen_t len); //改变ip规则函数
    21. void change_port(int sockfd, socklen_t len); //改变端口规则函数
    22. int main(void)
    23. {
    24. int sockfd;
    25. socklen_t len;
    26. if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
    27. printError("socket");
    28. else{
    29. len = sizeof(rules);
    30. if(getsockopt(sockfd, IPPROTO_IP, NOWRULE, (void *)&rules, &len))
    31. printError("getsockopt");
    32. else{
    33. //打印当前防火墙规则和改变规则菜单
    34. while(1){
    35. get_status();
    36. change_status(sockfd, len);
    37. }
    38. }
    39. }
    40. return 0;
    41. }
    42. void get_status()
    43. {
    44. printf("\ncurrent firewall status:\n");
    45. //rules.ping_status1时禁止ping
    46. if(rules.ping_status == 1)
    47. printf("ban ping\n");
    48. else
    49. printf("no ban ping\n");
    50. //打印当前禁止ip值
    51. if(rules.ip_status == 1){
    52. printf("ban ip:%d.%d.%d.%d\n",
    53. (rules.ban_ip & 0x000000ff) >> 0,
    54. (rules.ban_ip & 0x0000ff00) >> 8,
    55. (rules.ban_ip & 0x00ff0000) >> 16,
    56. (rules.ban_ip & 0xff000000) >> 24);
    57. }else{
    58. printf("no ban ip\n");
    59. }
    60. //打印当前禁止ip值
    61. if(rules.port_status == 1)
    62. printf("ban port:%hu\n", rules.ban_port);
    63. else
    64. printf("no ban port\n");
    65. }
    66. void change_status(int sockfd, socklen_t len)
    67. {
    68. int choice;
    69. printf("\n1.ping 2.ip 3.port 4.exit");
    70. printf("\nchange firewall status:\n");
    71. scanf("%d", &choice);
    72. switch (choice){
    73. case 1: //改变封禁ping状态
    74. change_ping(sockfd, len);
    75. break;
    76. case 2: //改变封禁ip状态
    77. change_ip(sockfd, len);
    78. break;
    79. case 3: //改变封禁端口状态
    80. change_port(sockfd, len);
    81. break;
    82. case 4:
    83. exit(0);
    84. default:
    85. printf("error");
    86. }
    87. }
    88. void change_ping(int sockfd, socklen_t len)
    89. {
    90. //当前封禁ping状态取反
    91. rules.ping_status = !rules.ping_status;
    92. if(setsockopt(sockfd, IPPROTO_IP, BANPING, &rules, len))
    93. printf("setsockopt");
    94. }
    95. void change_ip(int sockfd, socklen_t len)
    96. {
    97. char str_ip[20];
    98. int choice;
    99. if(rules.ip_status == 0){ //若当前无封禁ip,则改变ip状态,由用户输入需封禁ip
    100. rules.ip_status = 1;
    101. printf("enter one ip:");
    102. getchar();
    103. gets(str_ip);
    104. rules.ban_ip = inet_addr(str_ip); //转换ip格式
    105. if (setsockopt(sockfd, IPPROTO_IP, BANIP, &rules, len))
    106. printf("setsockopt");
    107. }else{ //若当前已有封禁ip,则取消封禁
    108. rules.ip_status = 0;
    109. rules.ban_ip = 0;
    110. if(setsockopt(sockfd, IPPROTO_IP, BANIP, &rules, len))
    111. printf("setsockopt");
    112. }
    113. }
    114. void change_port(int sockfd, socklen_t len)
    115. {
    116. if(rules.port_status == 0) //若当前无封禁端口,则改变端口状态,由用户输入需封禁端口
    117. {
    118. rules.port_status = 1;
    119. printf("enter one port:");
    120. scanf("%hu", &rules.ban_port);
    121. if(setsockopt(sockfd, IPPROTO_IP, BANPORT, &rules, len))
    122. printf("setsockopt");
    123. }else{ //若当前已有封禁端口,则取消封禁
    124. rules.port_status = 0;
    125. if(setsockopt(sockfd, IPPROTO_IP, BANPORT, &rules, len))
    126. printf("setsockopt");
    127. }
    128. }
    1. #include <linux/module.h>
    2. #include <linux/kernel.h>
    3. #include <linux/skbuff.h>
    4. #include <net/tcp.h>
    5. #include <linux/netdevice.h>
    6. #include <linux/netfilter.h>
    7. #include <linux/netfilter_ipv4.h>
    8. #include "fwfilter.h"
    9. //和数据包处理有关钩子
    10. static struct nf_hook_ops nfhoLocalIn;
    11. static struct nf_hook_ops nfhoLocalOut;
    12. static struct nf_hook_ops nfhoPreRouting;
    13. static struct nf_hook_ops nfhoForwarding;
    14. static struct nf_hook_ops nfhoPostRouting;
    15. //处理应用通信钩子
    16. static struct nf_sockopt_ops nfhoSockopt;
    17. ban_status rules, recv;
    18. unsigned int hookLocalIn(void* priv, struct sk_buff* skb, const struct nf_hook_state* state)
    19. {
    20. struct iphdr *iph = ip_hdr(skb);
    21. struct tcphdr *tcph = NULL;
    22. struct udphdr *udph = NULL;
    23. unsigned short port = ntohs(rules.ban_port);
    24. //ban ping
    25. //如果数据包是icmp并且rules.ping_status1则丢弃数据包
    26. if(iph->protocol == IPPROTO_ICMP && rules.ping_status == 1){
    27. return NF_DROP;
    28. }
    29. //ban port
    30. //rules.port_status1并且源端口符合,丢弃该端口udp或tcp的数据包
    31. if(rules.port_status == 1){
    32. switch(iph->protocol){ //选择协议类型
    33. case IPPROTO_TCP:
    34. tcph = tcp_hdr(skb); //获得tcp头
    35. if(tcph->dest == port){
    36. return NF_DROP;
    37. break;
    38. }
    39. case IPPROTO_UDP:
    40. udph = udp_hdr(skb); //获得udp头
    41. if(udph->dest == port){
    42. return NF_DROP;
    43. break;
    44. }
    45. }
    46. }
    47. //ban ip
    48. //rules.ip_status1并且源ip地址符合,丢弃该源ip发送的数据包
    49. if (rules.ip_status == 1){
    50. if (rules.ban_ip == iph->saddr){
    51. return NF_DROP;
    52. }
    53. }
    54. //以上情况都不符合接收数据包
    55. return NF_ACCEPT;
    56. }
    57. //其他函数接收数据包并打印信息
    58. unsigned int hookLocalOut(void* priv, struct sk_buff* skb, const struct nf_hook_state* state)
    59. {
    60. printk("hookLocalOut");
    61. return NF_ACCEPT;
    62. }
    63. unsigned int hookPreRouting(void* priv, struct sk_buff* skb, const struct nf_hook_state* state)
    64. {
    65. printk("hookPreRouting");
    66. return NF_ACCEPT;
    67. }
    68. unsigned int hookPostRouting(void* priv, struct sk_buff* skb, const struct nf_hook_state* state)
    69. {
    70. printk("hookPostRouting");
    71. return NF_ACCEPT;
    72. }
    73. unsigned int hookForwarding(void* priv, struct sk_buff* skb, const struct nf_hook_state* state)
    74. {
    75. printk("hookForwarding");
    76. return NF_ACCEPT;
    77. }
    78. int hookSockoptSet(struct sock* sock, int cmd, void __user* user, unsigned int len)
    79. {
    80. int ret;
    81. printk("hookSockoptSet");
    82. //从用户空间复制数据
    83. ret = copy_from_user(&recv, user, sizeof(recv));
    84. //命令类型
    85. switch(cmd){
    86. case BANPING: //禁止ping
    87. rules.ping_status = recv.ping_status;
    88. break;
    89. case BANIP: //禁止ip
    90. rules.ip_status = recv.ip_status;
    91. rules.ban_ip = recv.ban_ip;
    92. break;
    93. case BANPORT: //禁止端口
    94. rules.port_status = recv.port_status;
    95. rules.ban_port = recv.ban_port;
    96. break;
    97. default:
    98. break;
    99. }
    100. if (ret != 0)
    101. {
    102. ret = -EINVAL;
    103. printk("copy_from_user error");
    104. }
    105. return ret;
    106. }
    107. int hookSockoptGet(struct sock* sock, int cmd, void __user* user, int* len)
    108. {
    109. int ret;
    110. printk("hookSockoptGet");
    111. //将数据从内核复制到用户空间
    112. ret = copy_to_user(user, &rules, sizeof(rules));
    113. if (ret != 0)
    114. {
    115. ret = -EINVAL;
    116. printk("copy_to_user error");
    117. }
    118. return ret;
    119. }
    120. //初始化模块
    121. int init_module()
    122. {
    123. rules.ping_status = 0; //初始化ping状态,设置为0不封禁
    124. rules.ip_status = 0; //初始化ip状态,设置为0不封禁
    125. rules.port_status = 0; //初始化端口状态,设置为0不封禁
    126. nfhoLocalIn.hook = hookLocalIn;
    127. nfhoLocalIn.pf = PF_INET;
    128. nfhoLocalIn.priority = NF_IP_PRI_FIRST;
    129. nf_register_net_hook(&init_net, &nfhoLocalIn);
    130. nfhoLocalOut.hook = hookLocalOut;
    131. nfhoLocalOut.pf = PF_INET;
    132. nfhoLocalOut.priority = NF_IP_PRI_FIRST;
    133. nf_register_net_hook(&init_net, &nfhoLocalOut);
    134. nfhoPreRouting.hook = hookPreRouting;
    135. nfhoPreRouting.pf = PF_INET;
    136. nfhoPreRouting.priority = NF_IP_PRI_FIRST;
    137. nf_register_net_hook(&init_net, &nfhoPreRouting);
    138. nfhoForwarding.hook = hookForwarding;
    139. nfhoForwarding.pf = PF_INET;
    140. nfhoForwarding.priority = NF_IP_PRI_FIRST;
    141. nf_register_net_hook(&init_net, &nfhoForwarding);
    142. nfhoPostRouting.hook = hookPostRouting;
    143. nfhoPostRouting.pf = PF_INET;
    144. nfhoPostRouting.priority = NF_IP_PRI_FIRST;
    145. nf_register_net_hook(&init_net, &nfhoPostRouting);
    146. nfhoSockopt.pf = PF_INET;
    147. nfhoSockopt.set_optmin = SOE_MIN; //指定最小值
    148. nfhoSockopt.set_optmax = SOE_MAX; //指定最大值
    149. nfhoSockopt.set = hookSockoptSet;
    150. nfhoSockopt.get_optmin = SOE_MIN;
    151. nfhoSockopt.get_optmax = SOE_MAX;
    152. nfhoSockopt.get = hookSockoptGet;
    153. nf_register_sockopt(&nfhoSockopt);
    154. printk("My nf register\n");
    155. return 0;
    156. }
    157. //清理模块
    158. void cleanup_module()
    159. {
    160. //注销钩子
    161. nf_unregister_net_hook(&init_net, &nfhoLocalIn);
    162. nf_unregister_net_hook(&init_net, &nfhoLocalOut);
    163. nf_unregister_net_hook(&init_net, &nfhoPreRouting);
    164. nf_unregister_net_hook(&init_net, &nfhoForwarding);
    165. nf_unregister_net_hook(&init_net, &nfhoPostRouting);
    166. //注销扩展套接字
    167. nf_unregister_sockopt(&nfhoSockopt);
    168. printk("My nf unregister\n");
    169. }
    170. MODULE_LICENSE("GPL");
    1. #define SOE_MIN 0x6000 //驱动程序处理最小值
    2. #define BANPING 0x6001 //过滤程序和驱动程序对话时禁ping功能编号
    3. #define BANIP 0x6002 //禁ip功能编号
    4. #define BANPORT 0x6003 //禁port功能编号
    5. #define NOWRULE 0x6004 //获取防火墙当前规则功能编号
    6. #define SOE_MAX 0x6100 //驱动程序处理最大值
    7. typedef struct ban_status{
    8. int ping_status; //是否禁ping,1禁止,0未设置
    9. int ip_status; //是否禁ip,1禁止,0未设置
    10. int port_status; //是否禁port,1禁止,0未设置
    11. unsigned int ban_ip; //禁ip数值
    12. unsigned short ban_port; //禁port数值
    13. }ban_status;

     -- 查找所有规则
    iptables -nvL INPUT --line-numbers

    -- 删除一条规则
    iptables -D INPUT 11 (注意,这个11是行号,是iptables -L INPUT --line-numbers 所打印出来的行号)

    文章部分来源

    netfilter编程实例——一个简单的防火墙 - beiwo - 博客园 (cnblogs.com)

  • 相关阅读:
    高德地图通过画面中的一个覆盖物设置图中心点和zoom
    TS 入门指南
    Docker
    【LeetCode48:旋转图像(附Java代码)】
    在Windows上使用nginx具体步骤
    PMP_第12章章节试题
    Bioinformatics2022 | AdvProp+:基于集成网络的分子性质预测与药物研发
    RabbitMQ 使用细节 → 优先级队列与ACK超时
    【图像变换】基于matlab实现HSI和RGB域图像转换附matlab代码
    手写消息队列(基于RabbitMQ)
  • 原文地址:https://blog.csdn.net/buhuidage/article/details/127439675