自己动手编写FreeBSD内核防火墙模块

时间:2024-04-05 08:44:58

1. 概述

ipfw是BSD系统中重要的防火墙和通信控制工具,防火墙和NAT都可以通过ipfw的相关指令来实现。

pf (包过滤Packet Filter) 是FreeBSD 系统上进行TCP/IP流量过滤和网络地址转换的软件系统。 PF 同样也能提供TCP/IP流量的整形和控制,并且提供带宽控制和数据包优先集控制。

本文不讲解pf和ipfw命令的用法,通过阅读FreeBSD内核协议栈源码,通过pf和ipfw探讨FreeBSD的防火墙实现原理

2. FreeBSD协议栈包过滤处理流程

 

熟悉Linux协议栈的朋友都知道,Linux的包过滤是通过Netfilter实现的,Netfilter5个关键节点巧妙的挂在了钩子,可以改变报文行为。

FreeBSD也有Netfilter的基本机制pfil,也有比Netfilter功能更强大的NetGraph,本文主要讲解pfilNetGraph后续会进行阐述。

自己动手编写FreeBSD内核防火墙模块

FreeBSD协议栈IP报文收发包处理流程参考上图,一个数据包在几个内核变量的控制下,在协议栈的多个地方被防火墙检查。这些地方和变量如上图所示,记住包过滤的关键流程,对于理解和规划FreeBSD防火墙是很有帮助的。

在网卡注册设备时,会将ether_inputether_input函数挂在ifnet数据结构上,网卡中断会调用ether_input函数,接下来会分别被ether_demuxip_input处理,如果是需要转发的报文,会调用ip_tryforwardip_outputether_output_frame,最后调用ehter_output将报文发送至网卡。

input方向的报文,在ether_demuxip_input会调用pfipfw挂载的钩子函数,处理报文,处理的结果可以是passdrop或改变报文内容。

 

3. ipfw实现概要

 

PFIL模块通过调用pfil_head_register注册,通常在指定协议的入口和出口进行注册。以IP的注册为例,IP使用AP_INET注册了IP协议

       /* Initialize packet filter hooks. */

        inet_pfil_hook.ph_type = PFIL_TYPE_AF;

        inet_pfil_hook.ph_af = AF_INET;

        if ((i = pfil_head_register(inet_pfil_hook)) != 0)

   

 /usr/src/sys/netinet/ip_input.c:ip_init [7]    

pfil_run_hooks遍历inet_pfil_hook的队列,运行过滤检查,IP协议的报文过滤代码如下:

       if (pfil_run_hooks( inet_pfil_hook,  m, m->m_pkthdr.rcvif,            PFIL_IN, NULL) != 0)                return; 

/usr/src/sys/netinet/ip_input.c:ip_input [7]

    再看看ipfw初始化流程,在/usr/src/sys/netinet/ip_fw_pfil.c文件中,调用pfil_add_hook注册了钩子函数ipfw_check_packet,用于检查IP报文。

int

ipfw_attach_hooks(int arg)

{

int error = 0;

 

if (arg == 0) /* detach */

ipfw_hook(0, AF_INET);

else if (V_fw_enable && ipfw_hook(1, AF_INET) != 0) {

                error = ENOENT; /* see ip_fw_pfil.c::ipfw_hook() */

                printf("ipfw_hook() error\n");

        }

#ifdef INET6

if (arg == 0) /* detach */

ipfw_hook(0, AF_INET6);

else if (V_fw6_enable && ipfw_hook(1, AF_INET6) != 0) {

                error = ENOENT;

                printf("ipfw6_hook() error\n");

        }

#endif

if (arg == 0) /* detach */

ipfw_hook(0, AF_LINK);

else if (V_fwlink_enable && ipfw_hook(1, AF_LINK) != 0) {

                error = ENOENT;

                printf("ipfw_link_hook() error\n");

        }

return error;

}

 

 

static int

ipfw_hook(int onoff, int pf)

{

struct pfil_head *pfh;

void *hook_func;

 

pfh = pfil_head_get(PFIL_TYPE_AF, pf);

if (pfh == NULL)

return ENOENT;

 

hook_func = (pf == AF_LINK) ? ipfw_check_frame : ipfw_check_packet;

 

if (onoff)

(void) pfil_add_named_hook

(hook_func, NULL, "ipfw", PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh);

else

(void) pfil_remove_hook

    (hook_func, NULL, PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh);

 

return 0;

}

   

4. 编写自己的包过滤内核模块

现在我们知道了ipw如何使用PFIL_HOOKS的原理,我们就可以自己编写一个简单的内核模块用来截获BSD IPv4协议栈的报文。

我们的模块将统计主机接收和发送的所有IPv4报文数目,因为本文将要编写一个内核模块,如果你对FreeBSD的内核模块开发没有了解过,建议参考《FreeBSD Architecture Handbook》的第九章《Writing FreeBSD Device Drivers》里的如何编写简单”Hello world”内核模块的例子。

上面的章节对FreeBSDpfil hook机制有介绍,下面直接附上源码。

源码如下:

Makefile:

SRCS=hisar.c

KMOD=hisar

 

rclean:

    @make clean

    rm -f *~

 

.include  <bsd.kmod.mk>

       

Hisar.c:

/*

 *  A simple network flow accountant

 *  examplifying PFIL_HOOKS

 *

 *  Murat Balaban

 *  $Id: article.sgml,v 1.15 2005/11/25 20:07:04 murat Exp $

 *

*/

 

#include <sys/types.h>

#include <sys/module.h>

#include <sys/systm.h>

#include <sys/errno.h>

#include <sys/param.h>

#include <sys/kernel.h>

#include <sys/conf.h>

#include <sys/uio.h>

#include <sys/malloc.h>

#include <sys/ioccom.h>

#include <sys/mbuf.h>

#include <sys/socket.h>

#include <sys/sysent.h>

#include <sys/sysproto.h>

#include <sys/proc.h>

#include <sys/syscall.h>

 

#include <machine/iodev.h>

 

#include <netinet/in_systm.h>

#include <netinet/in.h>

#include <netinet/ip.h>

#include <net/if.h>

#include <net/pfil.h>

 

#define MODNAME "EnderUNIX HISAR - A simple network flow accountant"

#define MODVERSION  "1.0"

#define FLWACCT_MINOR   11

 

static volatile int hisar_hooked = 0;

 

d_open_t dev_open;

d_close_t dev_close;

d_read_t dev_read;

d_write_t dev_write;

static struct cdev *sdev;

static int count = 0;       /* Device Busy flag */

static int in_bytes = 0;    /* Bytes IN */

static int out_bytes = 0;   /* Bytes OUT */

static char *flwbuf = NULL; /* Priv. Buffer */

 

static struct cdevsw flwacct_cdevsw = {

        .d_version = D_VERSION,

        .d_open = dev_open,

        .d_close = dev_close,

        .d_read = dev_read,

        .d_name = "flwacct", /*FreeBSD较新的版本不再用主设备号表示内核设备,此处和linux有不一样的地方*/

        .d_flags = D_TTY,

};

 

/*ip_input的钩子函数,统计收包字节数*/

static int

hisar_chkinput(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,

        struct inpcb *inp)

{

    in_bytes += (*m)->m_len;

    return 0;

}

 

/*ip_output的钩子函数,统计发包字节数*/

static int

hisar_chkoutput(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,

        struct inpcb *inp)

{

    out_bytes += (*m)->m_len;

    return 0;

}

 

/*内核模块初始化函数*/

static int

init_module(void)

{

    struct pfil_head *pfh_inet;

 

    if (hisar_hooked)

        return (0);

    pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);

    if (pfh_inet == NULL)

        return ESRCH;

 

    /*注册钩子函数*/

    pfil_add_hook(hisar_chkinput, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);

    pfil_add_hook(hisar_chkoutput, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);

    hisar_hooked = 1;

    flwbuf = (void *)malloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO);

    

    /*创建设备,内核模块加载后,会在dev目录生成/dev/flwacct设备,用户态程序直接打开/dev/flwacct设备即可,不再使用MAJORMINOR表示设备*/

    sdev = make_dev(&flwacct_cdevsw,

                        FLWACCT_MINOR,

                        UID_ROOT,

                        GID_WHEEL,

                        0600,

                        "flwacct");

    uprintf("Loaded %s %s\n", MODNAME, MODVERSION);

    return 0;

}

 

static int

deinit_module(void)

{

    struct pfil_head *pfh_inet;

 

    if (!hisar_hooked)

        return (0);

    pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);

    if (pfh_inet == NULL)

        return ESRCH;

    pfil_remove_hook(hisar_chkinput, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);

    pfil_remove_hook(hisar_chkoutput, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);

    hisar_hooked = 0;

    free(flwbuf, M_TEMP);

    destroy_dev(sdev);

    uprintf("Unloaded %s %s\n", MODNAME, MODVERSION);

    return 0;

}

 

/* Module event handler */

static int

mod_evhandler(struct module *m, int what, void *arg)

{

    int err = 0;

    

    switch(what) {

    case MOD_LOAD:

        err = init_module();

        break;

    case MOD_UNLOAD:

        err = deinit_module();

        break;

    default:

        err = EINVAL;

        break;

    }

    return err;

}

 

int

dev_open(struct cdev *dev, int oflags, int devtype, struct thread *td)

{

        int err = 0;

 

        if (count > 0)

                return EBUSY;

        count = 1;

        return (err);

}

 

int

dev_close(struct cdev *dev, int fflag, int devtype, struct thread *td)

{

        int err = 0;

        count = 0;

        return (err);

}

 

/*给用户态返回内核收包字节数*/

int

dev_read(struct cdev *dev, struct uio *uio, int ioflag)

{

    int rv = 0;

 

    sprintf(flwbuf, "%016d,%016d\n", in_bytes, out_bytes);

    rv = uiomove(flwbuf, MIN(uio->uio_resid, strlen(flwbuf)), uio);

    return rv;

}

 

DEV_MODULE(hisarmodule, mod_evhandler, NULL);

MODULE_VERSION(hisarmodule, 1);

 

执行make命令,生成内核模块hisar.ko,执行kldload ./hisar.ko安装内核模块

用户态flwcnt.c:

/* flwcnt.c read from /dev/flwacct */

 

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

 

int

main(void)

{

        char buf[1024];

        int fd;

 

        if ((fd = open("/dev/flwacct", O_RDONLY)) == -1) {

                perror("open");

                exit(1);

        }

        while(read(fd, buf, sizeof(buf) - 1) > 0) {

                printf("IN: %.16s bytes, OUT: %.16s bytes\n", buf, buf + 17);

                sleep(1);

        }

        close(fd);

        return 0;

}

执行命令 gcc -o flwcnt flwcnt.c,生成用户态可执行文件 flwcnt,执行./flwcnt,打印接收和发送的字节数:

# ./flwcnt

IN: 0000000015755973 bytes, OUT: 0000000000524175 bytes

IN: 0000000015756541 bytes, OUT: 0000000000524715 bytes

IN: 0000000015756877 bytes, OUT: 0000000000525067 bytes

IN: 0000000015756933 bytes, OUT: 0000000000525123 bytes

IN: 0000000015757233 bytes, OUT: 0000000000525507 bytes

IN: 0000000015757437 bytes, OUT: 0000000000525727 bytes

IN: 0000000015757493 bytes, OUT: 0000000000525783 bytes

IN: 0000000015757549 bytes, OUT: 0000000000525839 bytes

IN: 0000000015757605 bytes, OUT: 0000000000525895 bytes

IN: 0000000015757697 bytes, OUT: 0000000000525951 bytes

IN: 0000000015757753 bytes, OUT: 0000000000526007 bytes

IN: 0000000015757841 bytes, OUT: 0000000000526063 bytes

IN: 0000000015757897 bytes, OUT: 0000000000526119 bytes


本文主要参考:http://www.enderunix.org/docs/en/pfil_hook.html