`
Michaelmatrix
  • 浏览: 211149 次
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Linux下网桥式防火墙的构建与实现

 
阅读更多

1 文档说明

1.1 写作目的

本文档为该子系统设计与实现人员提供参考。

1.2 写作背景

本文简单介绍在 Linux 下如何构建和实现网桥式防火墙,以及如何扩展该网桥式防火墙。

构建企业网关的一个重要前提是在企业网关上实现一个防火墙。该防火墙除了对进出包进行包过滤外,还应能够选择性地将特定的包投递给特定的目的主机(保持企业的网络架构不变)。这就要求该防火墙还具备网桥功能,因此网桥式防火墙是构建企业网关的重要基础。

Linux 构建和实现网桥式防火墙首先应该了解怎么构建简单的网桥式防火墙,然后了解如何扩展基本的网桥式防火墙以满足特定的需求。

本文正是基于上述理由而撰写的。

写作指导者:范兵;

写作执笔者:彭令鹏;

1.3 文档组织

本文档结构组织如下。

第一章:文档说明,简要介绍本文档;

第二章:构建简单的网桥式防火墙,介绍在 Linux 下构建一个简单的网桥式防火墙的基本流程;

第三章:网桥式防火墙的内核实现,介绍 Linux 内核是怎么实现网桥式防火墙的,这是扩展网桥式防火墙的重要基础;

第四章:扩展网桥式防火墙,扩展简单的网桥式防火墙以满足特定的需求。

1.4 补充说明

因为水平有限,并且学习的深度不够,本文档的错误一定很多,所以仅供参考。当然,本文档的更新与校正会一直进行。

2 构建简单的网桥式防火墙

2.1 何谓网桥式防火墙

网桥是一个链路层转发设备,它根据源 MAC 地址将数据包从特定的端口转发给目的主机。透明式网桥是本文的研究对象,它可以不影响原网络架构地插入网络中。对终端用户来说,在插入透明式网桥前后,所有网络功能都是一样的,因此透明式网桥对终端用户透明。

现在的防火墙虽然有包过滤、透明转发和记录攻击等多种功能,但包过滤仍是其最基本且最有用的功能。通过包过滤,防火墙可以部分地将攻击阻挡在防火墙之外,从而为防火墙内部用户提供一个较安全的网络环境。

网桥式防火墙简单地来说就是网桥 + 防 火墙,我们所研究的是透明网桥式防火墙。传统的商用防火墙一般需要更改待保护的网络的架构,而透明网桥式防火墙则不需要。更为重要的是,有时候我们需要将 进出包投递到特定的主机(如将数据包投递经监控服务器,以实现对网络内容的监控),这样透明网桥式则是一种不错的选择。

2.2 Netfilter Iptables

为支持网桥式防火墙, Linux2.4 及以上内核提供了 Netfilter Iptables

Netfilter Linux 内核中实现网桥式防火墙的模块,它通过在 TCP/IP 协议栈中插入钩子点的办法来尽量减小对 TCP/IP 协议栈的影响。需要网桥式防火墙功能时,应将 Netfilter 模块编译进内核,这个时候 Netfilter 在协议栈中提供钩子点,钩取数据包内容供钩子函数处理。而钩子点处的钩子函数的配置则可由 Iptalbes 等工具来实现。

Iptables 则利用内核中 Netfilter 的功能,提供了一个配置使用 Netfilter 的用户态接口。通过这个接口,用户可以在 Netfilter 各钩子点处放置钩子函数,来实现对网络数据的处理。需要注意的是 Iptables 也同 Netfilter 一样整合在内核里。

收发的数据流经钩子点时,将被钩子函数截取并处理,然后根据处理结果来决定数据的走向(是丢弃,是拒绝,还是继续往上走正常的协议栈)。

更为详细的介绍请参考 3.2.2 Linux防火墙的工作流程

2.3 创建简单的网桥式防火墙

考虑如 1 所示的一个网络。中间的 192.168.0.1 为一台装有 4 个网卡的 Linux 服务器。我们需要其为另外 4 台机器转发数据包,并且能够选择性地拒绝某些转发请求(如拒绝 ping 请求)。

2.3.1 创建网桥

Linux 中配置网桥的工具是 bridge-utils 2.6 及以上的内核已自带了这个工具。具体步骤如下。

brctl addbr br_192 /* 为网桥建立一个逻辑网段,命名为 br_192 */

brctl addif br_192 eth0 /* 绑定 eth0 br_192 网桥的一个端口 */

brctl addif br_192 eth1 /* 绑定 eth1 br_192 网桥的一个端口 */

brctl addif br_192 e th2 /* 绑定 eth2 br_192 网桥的一个端口 */

brctl addif br_192 eth3 /* 绑定 eth3 br_192 网桥的一个端口 */

ifconfig eth0 0.0.0.0 /* eth0 作为网桥的一个端口,已不需要 IP */

ifconfig eth1 0.0.0.0 /* eth1 作为网桥的一个端口,已不需要 IP */

ifconfig eth2 0.0.0.0 /* eth2 作为网桥的一个端口,已不需要 IP */

ifconfig eth3 0.0.0.0 /* eth3 作为网桥的一个端口,已不需要 IP */

ifconfig eth0 promisc up /* 网桥端口需要接收转发所有数据,因此开启混杂模式 */

ifconfig eth1 promisc up /* 网桥端口需要接收转发所有数据,因此开启混杂模式 */

ifconfig eth2 promisc up /* 网桥端口需要接收转发所有数据,因此开启混杂模式 */

ifconfig eth3 promisc up /* 网桥端口需要接收转发所有数据,因此开启混杂模式 */

ifconfig br_192 192.168.1.1 /* 为网桥指定一个 IP ,以方便远程管理等 */

以上步骤需要重点指出如下几点。

作为网桥端口的各物理网卡的工作任务是接收转发所有链路层数据包,因此已经不需要 IP ,并且要工作在混杂模式。

Linux 网桥可以为网桥配置一个 IP 和外部通信,因此网桥 br_192 也常被称为虚拟网络设备,这个配置的 IP 不是附着在某个特定的物理网卡上,而是可以附在每个网桥端口网卡上(网卡 eth0 接收到目的 IP 192.168.1.1 的数据包,会将该数据包传递给网桥本机处理, eth1 eth2 eth3 也如此)。

更详细的相关内容请参考 2.3.1 Linux 下网桥的工作原理

1 一个简单的网桥的拓扑图

2.3.2 配置防火墙

防火墙可用 Iptables 来配置。首先看一个简单的例子。

如果要拒绝 192.168.1.2 主机的 ping 请求或转发请求。可用如下两条语句来实现。

iptables -t filter -A FORWARD -p ICMP -s 192.168.1.2 -j REJECT /* 拒绝转发 192.168.1.2 ping 请求 */

iptables -t filter -A INPUT -p ICMP -s 192.168.1.2 -j REJECT /* 拒绝 192.168.1.2 ping 请求(即 ping 网桥本机的请求) */

实际中的防火墙常用来控制某些服务的访问,其配置可参考上述用法。例如假设 192.168.1.5 上开启了 http 服务( 80 端口),现要禁止 192.168.1.3 访问该服务,可用下面的语句来实现。

iptables -t filter -A FORWARD -p tcp -s 192.168.1.3 -d 192.168.1.5 --dport 80 -j REJECT

事实上网络上已经有丰富的 Iptables 配置脚本供参考,而且对于应用层检测和跟踪也有免费的模块可以利用。

很多情况下,我们需要将 iptables 设置的过滤规则备份,或导入 iptalbes 过滤规则,可以用下面的质量来实现。

iptables-save –c > /etc/iptables_rules_backup /* 保存过滤规则到 /etc/iptables_rules_backup –c 说明要保存计数器 */

iptables-restore –c –n < /etc/iptables_rules_backup /* 导入 /etc/iptables_rules_backup 中过滤规则, -c 用来恢复计数器, -n 指不覆盖掉当前已有的过滤规则 */

关于 iptables 的介绍与使用,请参考相关文档。

3 网桥式防火墙的内核实现

该部分简要介绍网桥式防火墙在 Linux 内核中的实现。首先介绍网桥的实现,然后介绍网桥基础上的防火墙的实现。

3.1 网桥的内核实现

网桥的内核实现代码在 net/bridge 文件夹下。下面先介绍 Linux 下网桥的工作原理,然后介绍 Linux 下网桥的工作流程,最后介绍数据结构和算法的实现。

3.1.1 Linux 下网桥的工作原理

前面说了,网桥是在链路层根据源 MAC 地址来转发数据包到特定端口的。在实现细节中,各网桥有些不同,这里的研究对象仅限于 Linux 下网桥(请参考 2.4 及其以上的内核源码)。

根据源 MAC 地址来转发数据包所涉及到的就是查找转发表,这个表在 Linux 中叫 CAM 表。因而建立 CAM 表以及如何查找 CAM 表是网桥的基本工作原理。

网桥采用逆向学习法( backward learning )来建 CAM 表。当它从端口 X 收到一个源 MAC Y 的数据包时,它就可以认为目的 MAC 地址为 Y 的数据包应该转发到端口 X ,因此它就可以将 Y à X 这个映射添加到 CAM 表中。

具体应用中,计算机网络架构可能会变更(例如更换网卡和搬移计算机等),所以 CAM 表还必须是动态的。 Linux 采取了动态扫描 CAM 表的方法,它每隔一段时间就扫描一次 CAM 表,如果发现某转发项对应的 MAC 很久没有从对应的端口发来数据包时(即超过指定时间),则删除该转发项。

当一个网络中有多个网桥工作时,为避免转发循环等问题, Linux 利用 STP Spanning Tree Protoco )生成树协议来管理 CAM 表的更新。 STP 定义在 IEEE 802.1D 中,是一种桥到桥的链路管理协议,它在防止产生自循环的基础上提供路径冗余。该协议规定了网桥创建无环回( loop-free )逻辑拓扑结构的算法,可以用来生成遍历整个链路层网络结点(同一个广播域)的无环回树。

查找转发表的流程请参考 Linux

3.1.2 Linux 下网桥的工作流程

网桥的基本工作流程如下,该流程包括建立 CAM 表和查找 CAM 表的基本流程。 2 给出了 Linux 下网桥的基本的工作流程。

1) 在某个端口收到数据包,都要学习该 MAC 地址。

2) 如果数据包是多播包或广播包,则向除接收端口外的其它所有端口转发该数据包,同时如果上层协议栈(本机协议栈)对数据包感兴趣,则需要把数据包提交给上层协议栈;否则转 3 )。

3) 如果在 CAM 表中可以找到转发项,则根据转发项从转发端口转发数据包,但是如果转发端口与接收端口是同一端口,则不转发;否则转 4 )。

4) 向同一个网段中除接收端口外的所有端口转发数据包。

2 Linux 网桥的基本工作流程

需要说明的是 2 只给出了基本的工作流程,其它工作细节,例如利用 STP 协议更新 CAM 表都未体现在图中。

3.1.3 与网桥相关的数据结构和算法

Linux 网桥的主要的数据结构如 3 所示。

3 Linux 网桥的主要的数据结构

3 做些说明。

逻辑网段 net_bridge 在本文给出的如 1 所示的网络环境中中即网桥 br_192 。逻辑网段 net_bridge 这个结构体包括物理端口链表、虚拟网卡信息(用来实现网桥可以指定 IP )、 CAM 转发表、 STP 生成树信息等。具体结构如下。

struct net_bridge

{

spinlock_t lock;

struct list_head port_list; // 网桥的端口列表

struct net_device *dev; // 网桥的虚拟网卡设备,能偶给网桥指定 IP 靠的就是它

struct net_device_stats statistics; // 网桥的虚拟网卡的统计数据(同普通网卡差不多)

spinlock_t hash_lock; //CAM hash 表的读写锁

struct hlist_head hash[BR_HASH_SIZE]; //CAM

struct list_head age_list; // 网桥链表

unsigned long feature_mask;

/* STP 相关 */

bridge_id designated_root;

bridge_id bridge_id;

u32 root_path_cost;

unsigned long max_age;

unsigned long hello_time;

unsigned long forward_delay;

unsigned long bridge_max_age;

unsigned long ageing_time;

unsigned long bridge_hello_time;

unsigned long bridge_forward_delay;

u8 group_addr[ETH_ALEN];

u16 root_port;

unsigned char stp_enabled;

unsigned char topology_change;

unsigned char topology_change_detected;

struct timer_list hello_timer;

struct timer_list tcn_timer;

struct timer_list topology_change_timer;

struct timer_list gc_timer;

struct kobject ifobj;

};

CAM 表的每个表项用 net_bridge_fdb_entry 结构体来实现,该结构体如下所示。

struct net_bridge_fdb_entry

{

struct net_bridge_fdb_entry *next_hash; // 用来链接表项的指针

struct net_bridge_fdb_entry **pprev_hash; // 为什么是 pprev 不是 prev 呢?还没有仔细去研究

atomic_t use_count; // 表项当前的引用计数器

mac_addr addr; //MAC 地址

struct net_bridge_port *dst; / 表此项所对应的物理转发端口

unsigned long ageing_timer; // 表项的存活时间

unsigned is_local:1; // 是否是本机的 MAC 地址

unsigned is_static:1; // 是否是静态 MAC 地址

};

网桥的实现还有很多细节,这里就不赘述了。

3.2 防火墙的内核实现

Linux 内核的防火墙是通过 Netfilter Iptables 来实现的。该部分的代码在 net/netfilter 下。同介绍网桥的实现一样,下面首先讲述防火墙实现的原理,然后介绍防火墙的工作流程,最后介绍一些重要的数据结构和算法。

3.2.1 Linux 防火墙实现的原理

前面已提到 Netfilter 是防火墙实现的基础, Iptables 则为用户提供一个配置防火墙的接口。 Iptables 根据用户需求在 Netfilter 提供的钩子点处,插上相应的钩子函数来实现对防火墙的配置。

为了降低对 TCP/IP 协议栈的影响, Netfilter Linux 内核中是一个可编译的部件。当用户将 Netfilter 编译进内核后, Netfilter TCP/IP 协议栈中插入 5 个 钩子点。当数据包沿着协议栈投递途径某个钩子点时,将被这个钩子点处的钩子函数(注意,有可能有多个钩子函数构成一个钩子函数链表)处理,经所有钩子函数 链表中所有钩子函数处理后,根据处理结果原有的数据包进行相应的下步操作(或被丢弃,或被拒绝,或被修改或继续沿着协议栈往上走等)。 5 个钩子点的位置如 4 所示。

4 钩子点的位置示意图

对图中的 5 个钩子点的作用时间作些说明。

NF_IP_PRE_ROUTING ,在报文作路由以前执行;

NF_IP_FORWARD ,在报文转向另一个 NIC 以前执行;

NF_IP_POST_ROUTING ,在报文流出以前执行;

NF_IP_LOCAL_IN ,在流入本地的报文作路由以后执行;

NF_IP_LOCAL_OUT ,在本地报文做流出路由前执行。

更详细的说明请参考相关的文档。

Netfilter 虽然定义了 5 个钩子点,但如果没有钩子函数,则 5 个钩子点也就是形同虚设了。 Iptables 是用来设置钩子函数的。

Iptables 通过各个 tables Netfilter 注册放置钩子函数。默认情况下, Iptables 有三个 table filter mangle nat 。在 Linux2.6 中每个表可能就是一个内核 module ,这些 module 在初始化时向 Netfilter 钩子函数。下面这条 Iptables 指令,在 NF_IP_FORWARD 这个钩子点处安了一个钩子函数。

iptables -t filter -A FORWARD -p ICMP -s 192.168.1.2 -j REJECT /* 拒绝转发 192.168.1.2 ping 请求 */

钩子函数就是用来过滤数据包,是实现防火墙功能的实体。

每个钩子函数有一些匹配条件( match )和一个目标( target ),当数据包满足该钩子函数的所有 match 时,这个钩子函数的 target 将被执行,这个 target 可能是如下几种之一。

NF_ACCEPT :继续正常的报文处理;

NF_DROP :将报文丢弃;

NF_STOLEN :由钩子函数处理了该报文,不要再继续传送;

NF_QUEUE :将报文入队,通常交由用户程序处理;

NF_REPEAT :再次调用该钩子函数。

下面这条 Iptables 指令对应的钩子函数有 2 match :源 IP 192.168.1.2 和协议是 ICMP ,有 1 target REJECT

iptables -t filter -A FORWARD -p ICMP -s 192.168.1.2 -j REJECT /* 拒绝转发 192.168.1.2 ping 请求 */

最后简要总结下防火墙的实现原理: Netfilter TCP/IP 协议栈中安装钩子点( HOOK ), Iptables 用来在钩子点处安放钩子函数( HOOK FUNCTION ),每个钩子函数用来过滤处理数据包。

3.2.2 Linux 防火墙的工作流程

在配置好防火墙后,网络数据包在 Linux 内核中的流动如 5 所示。

5 Linux 防火墙的工作流程

需要注意的是图下角“继续沿协议栈向下投递”的处理过程程如再遇到钩子函数点,同样会遵循图中的钩子函数处理流程。

可以看到配置防火墙的重点操作就是:设置合适的钩子函数(第四章中将重点讲述),这可通过 Iptables 或手动添加钩子函数 module 来实现。

Netfilter 是怎么在 TCP/IP 协议栈中安放钩子函数点的呢? Iptables 又是怎样向 Netfilter 注册钩子函数的呢? 6 试图来说明这些问题。

6 看出, Netfilter 通过 NF_HOOK 宏在协议栈中中插入钩子点, Iptables 的默认表则是通过 ipt_register_tabl e Netfilter 注册的,而钩子函数则是通过 nf_register_hook Netfilter 注册的。然而, Iptables 用户通过命令行输入的过滤规则是由 Iptables 通过 nf_sockopt 配置 Netfilter 生效的。

6 Iptables Netfilter 和协议栈的交互关系图

3.2.3 与防火墙相关的数据结构和算法

nf_register_hook 注册钩子函数,用到的一个结构体形参定义如下。

struct nf_hook_ops

{

struct list_head list; //

/* User fills in from here down. */

nf_hookfn *hook; // 要注册的钩子函数的指针

struct module *owner;

int pf; // 协议簇, PF_INET 指的是 ipv4 协议簇

int hooknum; //hook 类型,用来指定注册在哪个钩子点上

/* Hooks are ordered in ascending priority. */

int priority; // 优先级

};

Iptables 用表来组织规则,表结构如下。

struct ipt_table

{

/* 表链 */

struct list_head list;

/* 表名,如 "filter" "nat" 等,为了满足自动模块加载的设计,包含该表的模块应命名为 iptable_'name'.o */

char name[IPT_TABLE_MAXNAMELEN];

/* 表模子,初始为 initial_table.repl */

struct ipt_replace *table;

/* 位向量,标示本表所影响的 HOOK */

unsigned int valid_hooks;

/* 读写锁,初始为打开状态 */

rwlock_t lock;

/* iptable 的数据区,见下 */

struct ipt_table_info *private;

/* 是否在模块中定义 */

struct module *me;

};

Ipt_table 中存放的是表的一些基本的统计信息,而更为详细的表信息则定义在如下的 ipt_table_info 中。

struct ipt_table_info

{

/* 表大小 */

unsigned int size;

/* 表中的规则数 */

unsigned int number;

/* 初始的规则数,用于模块计数 */

unsigned int initial_entries;

/* 记录所影响的 HOOK 的规则入口相对于下面的 entries 变量的偏移量 */

unsigned int hook_entry[NF_IP_NUMHOOKS];

/* hook_entry 相对应的规则表上限偏移量,当无规则录入时,相应的 hook_entry underflow 均为 0 */

unsigned int underflow[NF_IP_NUMHOOKS];

/* 规则表入口 */

char entries[0] ____cacheline_aligned;

};

表是用来存放规则的一些统计信息的,而规则的具体内容则存放在如下的 ipt_entry 中。

struct ipt_entry

{

/* 所要匹配的报文的 IP 头信息 */

struct ipt_ip ip;

/* 位向量,标示本规则关心报文的什么部分,暂未使用 */

unsigned int nfcache;

/* target 区的偏移,通常 target 区位于 match 区之后,而 match 区则在 ipt_entry 的末尾;

初始化为 sizeof(struct ipt_entry) ,即假定没有 match */

u_int16_t target_offset;

/* 下一条规则相对于本规则的偏移,也即本规则所用空间的总和,

初始化为 sizeof(struct ipt_entry)+sizeof(struct ipt_target) ,即没有 match */

u_int16_t next_offset;

/* 规则返回点,标记调用本规则的 HOOK 号,可用于检查规则的有效性 */

unsigned int comefrom;

/* 记录该规则处理过的报文数和报文总字节数 */

struct ipt_counters counters;

/*target 或者是 match 的起始位置 */

unsigned char elems[0];

}

4 扩展网桥式防火墙

前面介绍了构建简单的网桥式防火墙的一般步骤,在实际的项目中,常需要修改或扩展网桥式防火墙以满足特定的需求。

扩展网桥式防火墙通常有 2 种方法:添加 Iptables 模块和自定义钩子函数模块。

4.1 添加 Iptables 模块

Iptalbes 默认只有三个表: filter mangle nat 。而事实上,针对各种应用,已有专门的 Iptalbes 扩展模块供参考,这些模块是开源的,有些模块甚至做到了能分析跟踪应用层协议, Iptables 的官方主页上就有一个 patch-o-matic 子项目的链接,这个子项目开发并收集了大量的 Iptalbes 模块。

Iptalbes 为添加 Iptables 模块提供了统一的接口。可以先从网上下到与自己需求最接近的模块,然后修改这个模块以符合自己的需求。

4.2 自定义钩子函数模块

利用 Netfilter 提供的注册接口,可以通过自定义钩子函数模块来满足特定的需求。下面是一个简单的自定义钩子函数模块,该模块将拒绝接收 100 字节长的数据包。

#include <linux/module.h>

#include <linux/netfilter_ipv4.h>

static drop_cnt = 0; /* 当前丢弃的数据包总数 */

statoc struct nf_hook_ops hook_test;

static unsigned int drop_pkt100(unsigned int hook, struct sk_buff **pskb, const struct net_device *indev, const struct net_device *outdev, int(* okfn)(struct sk_buff *))

{

if((* pskb)->len == 100)

{

drop_cnt++;

printk(“drop the %d packet!/n”, drop_cnt);

retrun NF_DROP;

}

return NF_ACCEPT;

}

static void init_hook(void)

{

/* 初始化 hook 函数 */

hook_test.pf = PF_INTE;

hook_test.hooknum = NF_IP_LOCAL_IN;

hook_test.hook = drop_pkt100;

/* 注册 hook 函数 */

nf_register_hook(&hook_test);

}

static void del_hook(void)

{

printk(“Totally have dropped %d packets!/n”, drop_cnt)

nf_unregister_hook(&hook_test); /* 注销掉 hook 函数 */

}

module_init(init_hook);

module_exit(del_hook);

编译后,并将其 insmod 后( 192.168.0.111 服务器上),在 192.168.0.155 上输入如下指令。

ping –s72 192.168.0.111

提示 111 服务器 unreachable ,这是因为 ping 包加上 28 字节包头后刚好是 100 字节,因而被丢弃了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics