0%

内核模块包过滤防火墙的原型实现

内核模块包过滤防火墙的原型实现

1.原型系统的总体设计

分为两部分独立实现

  1. 运行在应用层的过滤规则配置程序,用来设置和启用包过滤规则和配置相应的规则参数,包括要控制的协议类型、IP地址、端口等
  2. 实现IP报文过滤的内核模块,通过在Netfilter框架中注册钩子函数的方式来实现对IP数据包的过滤和控制
  • 内核模块不能直接访问规则配置程序中的过滤规则,所以通过注册设备文件结点的方式实现应用程序和内核之间的通信,传递所配置的过滤规则和参数

2.规则配置程序的设计

主要完成两方面的工作:

  1. 从用户的输入命令中解析出报文过滤规则
  2. 创建一个新的设备文件,通过写该设备文件,将所设置的过滤规则传递给Linux内核模块

3.内核模块的设计

主要功能是对收到的数据包进行过滤,即依据所配置的过滤规则,丢弃与所配置报文信息特征相匹配的数据包,包括四个部分

  1. 新设备文件的驱动:目的是获取规则配置程序向内核模块发送的过滤规则,也就是接收从规则配置程序写入设备文件的内容,将该内容保存到内核模块的变量中
  2. 报文过滤控制函数:包括一个总入口函数和一组具体规则判断函数。该函数在模块初始化阶段会被注册到Netfilter框架的相应钩子点上,当报文经过时,该函数将会被自动调用,根据过滤规则,对是否过滤该IP报文进行逻辑判断,将判断结果作为函数返回值,Netfilter根据函数的返回值进行报文过滤,放行还是阻止
  3. 内核模块的初始化函数:该函数在内核模块加载时被Linux自动调用,主要包括两方面的初始化工作:
    1. 注册新设备文件的驱动
    2. 将报文过滤控制函数注册到Netfilter框架的相应钩子点上,本系统是挂在IP_POST_ROUTING,即在对收到的报文做路由处理后执行包过滤,注册完成后,每次报文在路由处理后执行包过滤
  4. 内核模块的注销函数:该函数在内核模块注销时被Linux自动调用,主要完成设备文件驱动的注销,以及从Netfilter框架中注销钩子函数

4.测试

  1. ping功能测试
    1
    ping 10.160.101.243
    此时可以ping通,打开防火墙
    1
    ./configure -p ping -y 10.160.101.243
    再次ping该ip,无响应
    使用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!
  2. udp报文过滤测试
    查看本地DNS服务器
    1
    cat /etc/resolv.conf
    配置这条信息后,不能以域名形式访问外网服务器,但能够以IP地址的形式访问外网服务器
    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!
  3. tcp报文过滤测试
    使用 hping3 工具发送一个 TCP SYN 报文到指定的目标 IP 地址和端口
    1
    hping3 -c 1 -S -p 80 10.160.101.243
    配置规则之后
    1
    2
    3
    hping3 -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 permitted
    1
    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.源代码

  1. 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
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>

    // 规则配置程序定义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);
    }
  2. 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
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/types.h>
    #include <linux/if_ether.h>
    #include <linux/netfilter.h>
    #include <linux/netfilter_ipv4.h>
    #include <linux/tcp.h>
    #include <linux/ip.h>
    #include <linux/icmp.h>
    #include <linux/udp.h>
    #include <linux/fs.h>
    #include <linux/uaccess.h> // for copy_from_user

    #define MATCH 1 // 表示端口、ip地址的匹配结果和要控制的IP地址端口一致
    #define NMATCH 0

    // 函数原型
    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");
    }

  3. Makefile
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    CC := 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