花几天写了个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编写时候类似,在此不多写。

现在献上内核模块的代码

 /*************************************************************************
> File Name: netfilter.c
> Author: ICKelin
> Mail: [email protected]
> Created Time: 2015年02月27日 星期五 02时39分09秒
************************************************************************/ #include "netfilter.h" #define _USER_SPACE_ struct nf_hook_ops hook_in;
struct nf_hook_ops hook_out; static int __init fire_init()
{
hook_in.hook = fire_hook_entry;
hook_in.hooknum = NF_INET_LOCAL_IN;
hook_in.pf = PF_INET;
hook_in.priority = NF_IP_PRI_FIRST; nf_register_hook(&hook_in);
return ;
} static void __exit fire_exit()
{
nf_unregister_hook(&hook_in);
} //有数据包到来调用 unsigned int fire_hook_entry(
unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*)
)
{ #ifdef _USER_SPACE_
return NF_QUEUE;
#endif struct iphdr *ip = ip_hdr(skb);
struct tcphdr *tcp = tcp_hdr(skb);
struct udphdr *udp = udp_hdr(skb); if(ip->protocol == )
{
printk("tcp连接:::: 源ip:%3d.%3d.%3d.%3d 目的ip %3d.%3d.%3d.%3d ", NET_TO_IP((ip->saddr)),NET_TO_IP((ip->daddr))); printk("源端口号 %6d 目的端口号 %6d",ntohs(tcp->source), ntohs(tcp->dest)); if(ntohs(tcp->dest) == )
{
printk("状态:队列\n");
return NF_QUEUE;
}
else
printk("状态:允许通过防火墙");
return NF_ACCEPT;
}
else if(ip->protocol == )
{
printk("udp连接::: 源ip:%3d.%3d.%3d.%3d 目的ip %3d.%3d.%3d.%3d ", NET_TO_IP(ip->saddr), NET_TO_IP(ip->daddr));
printk("源端口号 %d 目的端口号 %d 状态:允许通过防火墙\n", ntohs(udp->source), ntohs(udp->dest));
return NF_ACCEPT;
}
else if(ip->protocol ==)
{
printk("icmp connect come\n");
return NF_QUEUE;
}
else if(ip->protocol == )
{
printk("igmp conect come\n");
return NF_ACCEPT;
}
return NF_ACCEPT; // printk("packet come\n");
return NF_ACCEPT;
} module_init(fire_init);
module_exit(fire_exit);

头文件netfilter.h包含基本文件linux头文件。在此也贴上,以便读者进行探索时候可以找到对应的头文件。

 /*************************************************************************
> File Name: netfilter.h
> Author: ICKelin
> Mail: [email protected]
> Created Time: 2015年02月27日 星期五 02时39分24秒
************************************************************************/ #include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h> #include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/types.h>
#include <linux/inet.h>
#include <linux/netfilter_ipv4.h>
/*
* 防火墙初始化函数,供module_exit的参数使用
* 内部调用钩子注册函数nf_register_hook.填充
* struct nf_hook_ops结构
* struct nf_hook_ops
* {
* struct list_head list;
* nf_hookfn *hook;
* struct module *owner;
* u_int8_t pf;
* unsigned int hooknum;
* int priority;
* }
*
* 详细信息参考netfilter.h头文件
* nf_hookfd指定为fire_hook_entry作为回调函数
*
* */ static int __init fire_init(); /*
* 防火墙退出函数,共module_init的参数使用
* 填充struct nf_hook_ops结构
*
* */ static void __exit fire_exit(); /*
* 防火墙钩子回调。供给nf_register_hook函数的参数
*
* struct nf_hook_ops结构的
* hook成员使用,用与注册回调函数
*
* */ unsigned int fire_hook_entry
(
unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*)
);
/*
*
*
* */ #define NET_TO_IP(addr) \
((unsigned char*)&addr)[],\
((unsigned char*)&addr)[],\
((unsigned char*)&addr)[],\
((unsigned char*)&addr)[]

内核模块需要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), block_port);
            if( (ntohs(tcp->source) == block_port||ntohs(tcp->dest) == block_port) && ntohl(ip->saddr) >=filter.from_ip && ntohl(ip->saddr)<=filter.to_ip)
            {
                //char out[1024];
                //strcpy(out,(char*)tcp + tcp->doff*4);
                //out[strlen(out)-3] = '3';
                //memcpy((char*)(tcp + tcp->doff*4), out, sizeof(out));
                //printf("%s\n", (char*)tcp + tcp->doff*4);
                printf("拦截\n");
                return nfq_set_verdict(qh,ntohl(msg->packet_id), NF_DROP,n,pdata);             }
            else
                printf("通过\n");
            break;         case 1:
            printf("ICMP\t");
            printf("无\t无\t");
            printf("通过\n");
            break;
        default:
            printf("un\t");
            printf("通过\n");
            break;
    }
    return nfq_set_verdict(qh,ntohl(msg->packet_id), NF_ACCEPT,n,pdata);  
} 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", filter.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,读者参考头文件和官方文档探索相信能够很快就能编写自己的包过滤防火墙来。

至于其他功能,读者可以发挥自己的想象力去搞。只要不违反法律,尽情的去玩吧。

05-06 12:18