1. 程式人生 > >寫了個linux包過濾防火牆

寫了個linux包過濾防火牆

花幾天寫了個so easy的Linux包過濾防火牆,估計實際意義不是很大。防火牆包括使用者態執行程式和核心模組,核心模組完全可以用iptable代替。由於在編寫的過程一開始寫的是核心模組所以就直接用上來。

程式碼結構如下:

.
├── kernelspace
│   ├── Makefile
│   ├── Makefile_netlink
│   ├── modules.order
│   ├── Module.symvers
│   ├── netfilter.c
│   ├── netfilter.h
│   ├── netfilter.ko
│   ├── netfilter.mod.c
│   ├── netfilter.mod.o
│   ├── netfilter.o
│   ├── out.temp
│   └── tags
└── userspace
    ├── filter
    ├── filter_1
    ├── filter.c
    ├── filter.c~
    ├── load.sh
    └── tags
由於在開發的過程中誤刪了filter原始檔。後面再重新寫過,過程也是挺艱辛的。後來想想自己寫過一個rm命令吧,把原來的rm命令替換掉,或者還可以這樣子,自己寫一個命令,姑且叫delete命令吧。delete刪除資料可以恢復的,再或者,用git吧,git管理想誤刪可不是這麼容易的。

ok,閒話少扯,上程式碼上分析上開發過程。

先從核心模組看起,也就是檔案樹下的以kernelspace為根的檔案。嗯~~檔案挺多的,不過自己寫的就三個,Makefile,netfilter.c and netfilter.h

核心模組採用linux的netfilter框架。

通俗的說,netfilter的架構就是在整個網路流程的若干位置放置了一些檢測點(HOOK),而在每個檢測點上登記了一些處理函式進行處理(如包過濾,NAT等,甚至可以是 使用者自定義的功能)。

IP層的五個HOOK點如下:

[1]:NF_IP_PRE_ROUTING:剛剛進入網路層的資料包通過此點(剛剛進行完版本號,校驗和等檢測), 目的地址轉換在此點進行; [2]:NF_IP_LOCAL_IN:經路由查詢後,送往本機的通過此檢查點,INPUT包過濾在此點進行; [3]:NF_IP_FORWARD:要轉發的包通過此檢測點,FORWARD包過濾在此點進行; [4]:NF_IP_POST_ROUTING:所有馬上便要通過網路裝置出去的包通過此檢測點,內建的源地址轉換功能(包括地址偽裝)在此點進行; [5]:NF_IP_LOCAL_OUT:本機程序發出的包通過此檢測點,OUTPUT包過濾在此點進行。

(摘自百度百科)

更多關注點在怎樣使用netfilter上。兩個函式

nf_register_hook ========> nf_unregister_hook。顧名思義,註冊鉤子,釋放鉤子。關鍵在於引數結構struct nf_hook_ops *reg的填充。函式和struct nf_hook_ops結構都可以在netfilter.h標頭檔案中找到。作者netfilter.h目錄為/usr/src/linux-head***/include/linux下找到。

struct nf_hook_ops {
    struct list_head list;

    /* User fills in from here down. */
    nf_hookfn *hook;
    struct module *owner;
    u_int8_t pf;
    unsigned int hooknum;
    /* Hooks are ordered in ascending priority. */
    int priority;
};

關注更多的是這些成員的具體意義以及程式設計時候如何選擇這些成員。

nf_hookfn *hook 是你自己定義的回撥函式。當有符合條件的資料包到來時候會呼叫。

hooknum為前面提到的IP層的五個hook點的取值

prority根據uapi/linux/netfiler_ipv4.h的定義,可以取以下值

enum nf_ip_hook_priorities {
    NF_IP_PRI_FIRST = INT_MIN,
    NF_IP_PRI_CONNTRACK_DEFRAG = -400,
    NF_IP_PRI_RAW = -300,
    NF_IP_PRI_SELINUX_FIRST = -225,
    NF_IP_PRI_CONNTRACK = -200,
    NF_IP_PRI_MANGLE = -150,
    NF_IP_PRI_NAT_DST = -100,
    NF_IP_PRI_FILTER = 0,
    NF_IP_PRI_SECURITY = 50,
    NF_IP_PRI_NAT_SRC = 100,
    NF_IP_PRI_SELINUX_LAST = 225,
    NF_IP_PRI_CONNTRACK_HELPER = 300,
    NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
    NF_IP_PRI_LAST = INT_MAX,
};

pf根socket編寫時候類似,在此不多寫。

現在獻上核心模組的程式碼

複製程式碼
 1 /*************************************************************************
 2     > File Name: netfilter.c
 3     > Author: ICKelin
 4     > Mail: [email protected] 
 5     > Created Time: 2015年02月27日 星期五 02時39分09秒
 6  ************************************************************************/
 7 
 8 #include "netfilter.h"
 9 
10 #define _USER_SPACE_
11 
12 struct nf_hook_ops hook_in;
13 struct nf_hook_ops hook_out;
14 
15 static int __init fire_init()
16 {
17     hook_in.hook = fire_hook_entry;
18     hook_in.hooknum = NF_INET_LOCAL_IN;
19     hook_in.pf = PF_INET;
20     hook_in.priority = NF_IP_PRI_FIRST;
21 
22     nf_register_hook(&hook_in);
23     return 0;
24 }
25 
26 static void __exit fire_exit()
27 {
28     nf_unregister_hook(&hook_in);
29 }
30 
31 //有資料包到來呼叫
32 
33 unsigned int fire_hook_entry(
34          unsigned int hooknum,
35          struct sk_buff *skb,
36          const struct net_device *in,
37          const struct net_device *out,
38          int (*okfn)(struct sk_buff*)
39         )
40 {
41 
42 #ifdef _USER_SPACE_
43     return NF_QUEUE;
44 #endif
45 
46     struct iphdr *ip = ip_hdr(skb);
47     struct tcphdr *tcp = tcp_hdr(skb);
48     struct udphdr *udp = udp_hdr(skb);
49 
50     if(ip->protocol == 6)
51     {
52         printk("tcp連線:::: 源ip:%3d.%3d.%3d.%3d 目的ip %3d.%3d.%3d.%3d ", NET_TO_IP((ip->saddr)),NET_TO_IP((ip->daddr)));
53         
54         printk("源埠號 %6d 目的埠號 %6d",ntohs(tcp->source), ntohs(tcp->dest));
55 
56         if(ntohs(tcp->dest) == 80)
57         {
58             printk("狀態:佇列\n");
59             return NF_QUEUE;
60         }
61         else
62             printk("狀態:允許通過防火牆");
63         return NF_ACCEPT;
64     }
65     else if(ip->protocol == 17)
66     {
67         printk("udp連線::: 源ip:%3d.%3d.%3d.%3d 目的ip %3d.%3d.%3d.%3d ", NET_TO_IP(ip->saddr), NET_TO_IP(ip->daddr));
68         printk("源埠號 %d 目的埠號 %d 狀態:允許通過防火牆\n", ntohs(udp->source), ntohs(udp->dest));
69         return NF_ACCEPT;
70     }
71     else if(ip->protocol ==1)
72     {
73         printk("icmp connect come\n");
74         return NF_QUEUE;
75     }
76     else if(ip->protocol == 2)
77     {
78         printk("igmp conect come\n");
79         return NF_ACCEPT;
80     }
81     return NF_ACCEPT;
82 
83 //    printk("packet come\n");
84     return NF_ACCEPT;
85 }
86 
87 module_init(fire_init);
88 module_exit(fire_exit);
複製程式碼

標頭檔案netfilter.h包含基本檔案linux標頭檔案。在此也貼上,以便讀者進行探索時候可以找到對應的標頭檔案。

複製程式碼
 1 /*************************************************************************
 2     > File Name: netfilter.h
 3     > Author: ICKelin
 4     > Mail: [email protected] 
 5     > Created Time: 2015年02月27日 星期五 02時39分24秒
 6  ************************************************************************/
 7 
 8 #include <linux/in.h>
 9 #include <linux/ip.h>
10 #include <linux/tcp.h>
11 #include <linux/udp.h>
12 #include <linux/icmp.h>
13 
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/netdevice.h>
17 #include <linux/init.h>
18 #include <linux/skbuff.h>
19 #include <linux/types.h>
20 #include <linux/inet.h>
21 #include <linux/netfilter_ipv4.h>
22 /*
23  * 防火牆初始化函式,供module_exit的引數使用
24  * 內部呼叫鉤子註冊函式nf_register_hook.填充
25  * struct nf_hook_ops結構
26  * struct nf_hook_ops
27  * {
28  *    struct list_head list;
29  *    nf_hookfn *hook;
30  *    struct module *owner;
31  *    u_int8_t pf;
32  *    unsigned int hooknum;
33  *    int priority;
34  * }
35  *
36  * 詳細資訊參考netfilter.h標頭檔案
37  * nf_hookfd指定為fire_hook_entry作為回撥函式
38  *
39  * */
40 
41 static int __init fire_init();
42 
43 /*
44  * 防火牆退出函式,共module_init的引數使用
45  * 填充struct nf_hook_ops結構
46  *
47  * */
48 
49 static void __exit fire_exit();
50 
51 /*
52  * 防火牆鉤子回撥。供給nf_register_hook函式的引數
53  *
54  * struct nf_hook_ops結構的
55  * hook成員使用,用與註冊回撥函式
56  *
57  * */
58 
59 unsigned int fire_hook_entry
60         (
61          unsigned int hooknum,
62          struct sk_buff *skb,
63          const struct net_device *in,
64          const struct net_device *out,
65          int (*okfn)(struct sk_buff*)
66         );
67 /*
68  * 
69  *
70  * */
71 
72 #define NET_TO_IP(addr) \
73         ((unsigned char*)&addr)[0],\
74         ((unsigned char*)&addr)[1],\
75         ((unsigned char*)&addr)[2],\
76         ((unsigned char*)&addr)[3]
複製程式碼

核心模組需要make

Makefile

複製程式碼
obj-m := netfilter.o  
    KERNELBUILD :=/lib/modules/$(shell uname -r)/build  
default:  
    make -C $(KERNELBUILD) M=$(shell pwd) modules  
clean:  
    rm -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers .tmp_versions  
複製程式碼

核心模組其實還是挺簡單的。如果編寫使用者層的包過濾防火牆的話沒有必要在核心模組上花費太多功夫,以上核心模組實現的功能用iptable都可以實現。
至於協議解析部分,也不是三言兩語能寫的完。但是作者寫過利用原始套接字進行抓包的程式,不過正在準備筆試就沒有多大時間寫部落格總結。讀者能看懂包解析部分的程式碼的是沒什麼問題的。

使用者空間模組。使用者空間模組採用的是netfilter_queue函式庫。原本找資料的時候看到ipq這個庫,不過後來到netfilter官網上找資料,ipq函式庫被取代來。

libnetfilter_queue is a userspace library providing an API to packets that have been queued by the kernel packet filter. It is is part of a system that deprecates the old ip_queue / libipq mechanism.

libnetfilter_queue has been previously known as libnfnetlink_queue.

使用者空間動起來也不難。關鍵是官網有api參考。看著官網的api再結合之前抓包的程式寫起來就easy了。

這部分誤刪過一次,我那個淚奔啊,rm命令害死人,原本註釋打得完美了,重寫一次就沒有打註釋的慾望了。忘見諒。

複製程式碼
/*************************************************************************
    > File Name: filter.c
    > Author: ICKelin
    > Mail: [email protected] 
    > Created Time: 2015年03月02日 星期一 01時04分38秒
 ************************************************************************/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <asm/byteorder.h>
#include <linux/netfilter.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>

#define BUFF_SIZE    1024*10
#define IP_SIZE        50

#define AUTHOR "ICKelin"
#define VERSION "v1.1"
#define _DEBUG_

#define error(msg) \
    {fprintf(stderr, "%s error with %s\n", msg, strerror(errno));exit(-1);}

struct filter_info
{
    long from_ip;
    long to_ip;
    char *protocol_type;
}filter;


int parse_cmd(char *protocol_type, char *from, char *to);
void fire_help();
void fire_version();
int get_port_by_service(char *service);


static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,struct nfq_data *nfa, void *data)
{
    int is_block = 0;
    struct nfqnl_msg_packet_hdr *msg = nfq_get_msg_packet_hdr(nfa);
    if(msg == NULL)
        error("nfqnl_msg_packet_hdr");
    char *pdata;
    
    int n = nfq_get_payload(nfa, (char**)&pdata);
    struct iphdr *ip = (struct iphdr*)pdata;
    struct tcphdr *tcp;
    int block_port = get_port_by_service(filter.protocol_type);
    struct in_addr add;
    
    add.s_addr = ip->saddr;
    printf("%s\t", inet_ntoa(add));
    add.s_addr = ip->daddr;
    printf("%s\t", inet_ntoa(add));

    switch(ip->protocol)
    {
        //udp
        case 17:
            printf("UDP\t");
            struct udphdr *udp = (struct udphdr*)(pdata + sizeof(struct iphdr));
            printf("%d\t%d\t", ntohs(udp->source), ntohs(udp->dest));
            printf("通過\n");
            break;
        case 6:
            printf("TCP\t");
            tcp = (struct tcphdr *)(pdata + sizeof(struct iphdr));
            printf("%d\t%d\t", ntohs(tcp->source), ntohs(tcp->dest));
            if(ntohs(tcp->dest) == block_port && ntohl(ip->saddr) >=filter.from_ip && ntohl(ip->saddr)<=filter.to_ip)
                printf("攔截\n");
            else
                printf("通過\n");
            break;

        case 1:
            printf("ICMP\t");
            printf("無\t無\t");
            printf("通過\n");
            break;
        default:
            printf("un\t");
            printf("通過\n");
            break;
    }
    return 0;  
}

int main(int argc, char **argv)
{
    char *protocol_type,*from, *to;
    char opt;
    int flag = 0;
    while((opt = getopt(argc, argv, "hvf:t:p:")) != EOF)
    {

        switch(opt)
        {
            case 'h':
                fire_help();
                return 0;
            case 'v':
                fire_version();
                return 0;
            case 'f':
                from = optarg;
                flag=flag|1;
                break;
            case 't':
                to = optarg;
                flag|=2;
                break;
            case 'p':
                protocol_type = optarg;
                flag|=4;
                break;
            default:
                fire_help();
                break;
        }
    }
    if((flag^7) != 0)
    {
        fprintf(stderr, "command line options error\nyou should use \n\t-f begin ip you are going to block\n-t end ip you are going to block\n\t-p for the protocol or port you are going to block\n");
        fprintf(stderr,"\tfor example:filter -f 192.168.15.* -t 192.16.120.* -p http\n");
        fprintf(stderr,"more information use -h\n");
        exit(-1);
    }
    
    parse_cmd(protocol_type, from, to);

    printf("\nfirewall setup successfully\n\n");
    printf("  you filter information:\n");
    printf("\tfrom:%s     net byte order %ld\n", from, filter.from_ip);
    printf("\tto  :%s     net byte order %ld\n", to, filter.to_ip);
    printf("\tprotocol:%s\n\n", protocol_type);
    printf("now let's firework for firewall\n\n");


    printf("源ip\t\t目的ip\t\t協議\t源埠 目的埠 狀態\n");

    struct nfq_handle *h;
    struct nfq_q_handle *qh;
    int fd;
    int rv;
    char buf[4096];

    h = nfq_open();
    if (!h)
        error("nfq_open");
    nfq_unbind_pf(h, AF_INET);

    if (nfq_bind_pf(h, AF_INET) < 0) 
        error("nfq_bind_pf");

    qh = nfq_create_queue(h, 0, &cb, NULL);
    if (!qh)
        error("nfq_create_queue");
    if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0)
        error("nfq_set_mode");
    fd = nfq_fd(h);

    while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0)
        nfq_handle_packet(h, buf, rv);

    nfq_destroy_queue(qh);

    nfq_close(h);

    return 0;
}

void fire_help()
{
    printf("welcome to use my network filter firework\n");
    printf("how to set your own match to filter packets:\n\n");
    printf("\t-p\tfilter protocol,like http,ftp...maybe you want to use port instead\n");
    printf("\t-f\tfilter ip from argument\n");
    printf("\t-h\tshow help information\n");
    printf("\t-v\tshow sortware information and the author information\n\n");
    printf("  author:%s\n", AUTHOR);
    printf("  come form:CHINA\n");
    printf("  email:[email protected]\n");
    printf("  version:%s\n\n",VERSION);

}

void fire_version()
{

}

int parse_cmd(char *protocol_type, char *from, char *to)
{
    char temp[IP_SIZE];
    int index;
    
    if(strcasecmp(protocol_type, "http") == 0)
        filter.protocol_type = "http";
    else if(strcasecmp(protocol_type, "ftp") == 0)
        filter.protocol_type = "ftp";
    else if(strcasecmp(protocol_type, "smtp") == 0)
        filter.protocol_type = "smtp";
    else
    {
        fprintf(stderr, "not support protocol.\nversion %s only support http,ftp or smtp protocol\nmore information see -h option\n", VERSION);
        exit(-1);
    }
    while(*from)
    {
        if(*from != '.' && *from !='*' &&(*from<'0'||*from>'9'))
        {
            fprintf(stderr, "from ip address format error! format:###.###.##.#\nexample:192.168.*.*\nmore information use -h option\n");
            exit(-1);
        }
        if(*from == '*')
            temp[index++] = '0';
        else 
            temp[index++] = *from;
        from++;
    }
    temp[index] = 0;
    filter.from_ip = ntohl(inet_addr(temp));
    
    memset(temp, 0, sizeof(temp));
    index = 0;
    while(*to)
    {
        if(*to != '.' && *to !='*' &&(*to<'0'||*to>'9'))
        {
            fprintf(stderr, "to ip address format error! format:###.###.##.#\nexample:192.168.*.*\nmore information use -h option");
            exit(-1);
        }
        if(*to == '*')
            temp[index++] = '0';
        else 
            temp[index++] = *to;
        to++;
    }
    temp[index] = 0;
    filter.to_ip = ntohl(inet_addr(temp));
    if(filter.from_ip > filter.to_ip)
    {
        fprintf(stderr, "hello guys, there is no ip between %s to %s\ni advice you to check your input\nmore information see -h option", from, to);
        exit(-1);
    }
    return 1;
}

int get_port_by_service(char *service)
{
    if(strcasecmp(service, "HTTP") == 0)
        return 80;
    if(strcasecmp(service, "FTP") == 0)
        return 21;
    if(strcasecmp(service, "smtp") == 0)
        return 25;
    return 0;
}
複製程式碼


關於命令列選項和ip地址驗證這塊不多說。netfilter_queue庫的使用參考連結:libnetfilter_quue,讀者參考標頭檔案和官方文件探索相信能夠很快就能編寫自己的包過濾防火牆來。

至於其他功能,讀者可以發揮自己的想象力去搞。只要不違反法律,盡情的去玩吧。