序 route 和 firewall 的关系
例如从网卡到来的TCP包,内容为{src_MAC, dst_MAC, src_IP, dst_IP, TCP_flags, ..., user-data}
"TCP/IP包": 参见 OSI七层模型
- 有的"包"不能精确的定义“属于OSI模型的哪一层”,这不重要,模型里提及的基本概念适用就可以了
ether: "虚拟网卡" 可以有IP、 mac地址等。
- 和物理网卡不同:一个“物理网卡”在系统中可拆成很多"虚拟网卡",例如ssh到路由器看到的 "wl0:0", "eth1:4"等等
- VLAN: 每个VLAN可以被当作一个ether
kernel.route: 作用是 从任何一个地方收到一个"IP包",按路由表把这个包"原封不动"丢到目标ether
- kernel.route 只处理IP层 他不关心是TCP还是UDP 而且不改内容,所以不在这里做NAT
- FreeBSD看路由表
netstat -rn
firewall: 插在ether和route中间,不仅是只读的防火墙,还可以修改包头(例如做NAT)
- NAT的原理: 在这里firewall修改了TCP包头,把"实际地址"换成了"当前ether",从而骗过本机route和上游route
- 不仅可以处理"TCP包" 还可以处理例如"ICMP", "EAP", "UDP"等等(对应到OSI模型的不同层)
- 常见的有 linux下的iptable, FreeBSD的IPFW
- 实际还支持例如按user过滤 本文不关心
https://www.freebsd.org/cgi/man.cgi?ipfw#PACKET_FLOW
- to device 当作去往ether (进而调用驱动把binary-data送出去)
- to upper layers 当作去往kernel.route (和监听者app如nginx 下文仅用route代指)
- ipfw在图中的[ip_input/output], [ether_demux], [bdg_forward], [ether_output_frame]这五个框的位置进行匹配拦截+包头变换,匹配规则如下一章。
启动 和 ipfw举例
回到这个简图: https://www.freebsd.org/cgi/man.cgi?ipfw#PACKET_FLOW
- ether和route间共有5个检查点,ipfw是个表 最多有65535行,当有数据包经过检查点时 从头到尾跑一遍这个表决定放行、拒绝或修改
一外来数据包首先到达[ether_demux]检查点,触发MAC地址检查,这个点不看IP地址
- 如果通过 接下来到达[ip_input]检查点,触发IP+TCP/UDP/ICMP/...检查,这个点不看MAC地址
- router->ether方向同理,在[ip_output]和[ether_output_frame]两点检查
- [ether_demux] [ether_output_frame] [bdg_forward] 这三个点位属于"layer2"系列
- 在
/etc/sysctl.conf
中如果写了net.link.ether.ipfw=1
,则表示启用[ether_demux]和[ether_output_frame]检查点(不写则使用默认值)
rc.conf
FreeBSD的服务启动均在rc.conf中配置 当作kernel plugin,开机过程中 rc.conf完全加载后才会显示 "Login: ..." prompt。(linux下大部分采用了systemd)
添加如下内容至/etc/rc.conf
,并在/etc/ipfw.rules
内写启动脚本
## rc.conf
gateway_enable="YES"
firewall_enable="YES"
firewall_nat_enable="YES"
firewall_script="/etc/ipfw.rules"
新增过滤规则
|<-------------------- RULE FORMAT --------------------------------->|
|<--------------------- RULE BODY ----------->|
ipfw add [rule_number] [action] [proto] from [src] to [dst] [options]
ipfw add 100 allow tcp from any to me 22 in via vtnet0 #(1)
ipfw add 100 allow tcp from me 22 to any out via vtnet0 #(2)
ipfw add 101 count tcp from 10.0.0.0/8 to me via vtnet0 #(3)
ipfw add 102 allow tcp from me to any via vtnet0 #(4)
- (1,2) 匹配22端口的出入流量
- (3) 统计10.0.0.0/8发来的数据数量包 (但没说放行)
- (4) 允许tcp类型的数据包从 本机任意端口 发往 任何目的地任何端口
in表示dst-IP为本机,out指dst-IP为其他机器
- in out并非数据流动方向,out数据包也可以经[ip_input]流入route 见NAT一章
keep-state匹配器
现在运行wget https://1.1.1.1/
,系统给wget分配个tcp port作出口(例如tcp 33331),发起TCP连接至1.1.1.1:445
。但上述"规则4"只允许数据包发出,不允许数据包接收,有去无回,TCP没法握手。
于是使用check-state
配合keep-state
让ipfw自适应:rule_102标记了当本机发起新建TCP连接时 记录
|<-------------------- RULE FORMAT ------------------------------------ ->|
|<----------------- RULE BODY ----------------->|
ipfw add [rule_number] [action] [proto] from [src] to [dst] [options............]
ipfw add 102 allow all from me to any via vtnet0 keep-state #(4)
ipfw add 103 check-state #(5)
(4) 修改102号规则后,当本机(me)发起tcp连接时,会将"双方ip + port"记录下来,在(5)匹配刚刚创建的新规
- 如果不写(5) 也会导致数据包有去无回
- 4和5没有顺序要求,4是检测出包,5是检测回包
(5) keep-state: Upon a match, the firewall will create a dynamic rule, whose default behaviour is to match bidirectional traffic between source and destination IP/port using the same protocol.
- https://www.freebsd.org/cgi/man.cgi?ipfw 搜索 "keep-state"
NAT转换器
就是家中的wifi路由器,让局域网能上网,原理是在通过网线把数据包送出之前,改掉包头,欺骗上级网络只有一个用户在用。
回到这个简图: https://www.freebsd.org/cgi/man.cgi?ipfw#PACKET_FLOW
前文说的都是READONLY accept/drop,而NAT转换器在[ip_input]和[ip_output]这两个检查点"修改"数据包头
举例:vtnet0网卡同时连接子网客户端和公网,子网网段10.0.0.0/16,当前venet的IP 47.52.68.90 (下文IP未体现 使用if vtnet0
表示自动跟踪venet0的网卡IP)
ipfw add [rule_num] [action] [proto] from [src] to [dst] [options.....]
ipfw add 203 nat 2 all from 10.0.0.0/16 to any out via vtnet0 (6)
ipfw add 204 nat 2 all from any to me in via vtnet0 (7)
ipfw nat [natid] config [config-options..........]
ipfw nat 2 config if vtnet0 same_ports reset deny_in (8)
- 当前配置:单个数据包,在所有检查点中,仅成功匹配一次,后续便不再检查
(8) 创建一个"2号NAT黑盒",盒中记录了 "子网ip:port" 和 "公网ip:port" 对应关系
- deny_in属性表示:若未找到记录,直接reject当前包(并不再匹配任何其他项目)
ipfw nat show config
显示所有NAT黑盒
(6) 客户端IP=10.0.7.7发起一个包,dst-IP=1.1.1.1(公网)
- 数据包
TCP 10.0.6.1:54588 1.1.1.1:443 out via vtnet0
- 首先进入[ip_input]检查点 成功匹配203记录,然后"2号NAT盒"将src-IP换为vtnet0(47.52.68.90)
- 修改后数据包
TCP 47.52.68.90:54588 1.1.1.1:443 out via vtnet0
- 然后新的数据包进入kernel-route,由于仅匹配一次,故直接丢给venet0驱动送走了
- 在[ip_output]检查点匹配当前203记录,成功后送往"2号NAT盒"
- 2号NAT盒 将这个包的src-IP变为当前vtnet0的ip(47.52.68.90)
- 数据包
(7) 当上述请求包的response 被1.1.1.1返回
- 数据包
TCP 1.1.1.1:443 47.52.68.90:54588 in via vtnet0
- 在[ip_input]检查点匹配到当前204记录,成功后送入"2号NAT盒"修改包头
- 2号NAT盒 查表后 将这个包dst-IP变为10.0.7.7后,(由于已经匹配过)直接送还至响应客户端
- 数据包
注意上述"203, 204两条规则"要在"上一段中104规则"之后,这样外来数据包,先由104规则匹配"the response of 当前主机发出的请求",未找到才"进入2号nat黑盒尝试查找NAT表",若再未找到,则由"2号nat盒"拒绝
- 因为公网传回的数据包,dst-IP都是vtnet0 IP (47.52.68.90),需要在"104规则"和"2号NAT盒(triggered by 204)"两处查表
注:按官方文档,使用in-kernel NAT需要在/etc/rc.conf添加如下:
gateway_enable="YES"
firewall_enable="YES"
firewall_nat_enable="YES"
文档 https://docs.freebsd.org/en/books/handbook/firewalls/#firewalls-ipfw
Debug
- 在 rc.conf中添加
firewall_logging="YES"
,然后tail -f /var/log/security
看一看 - example:
ipfw add 99 count log all from 1.1.1.1 to any
- example:
ipfw add 2099 count log all from 10.0.0.0/16 to me via vtnet0
其他补充
- set: 名词"集合、组" 不是动词
后记
Linux下iptable和route的关系图
来自: https://www.cnblogs.com/EasonJim/p/8424731.html
Reference
- 官方文档的翻译 https://www.twblogs.net/a/5eecccf7fcc715ab3d916331
- route官方文档 https://www.freebsd.org/cgi/man.cgi?route
- ipfw官方文档 https://www.freebsd.org/cgi/man.cgi?ipfw
配置样例
- Jail
Strongswan (swanctl)
https://blog.vrqq.org/archives/58/
#!/bin/sh
WAN="vtnet0"
##clear all
ipfw -q -f flush
## Isolate 'lo0' with other ethernet
ipfw add 1000 allow all from any to any via lo0
ipfw add 1001 deny all from any to 127.0.0.0/8
ipfw add 1001 deny all from 127.0.0.0/8 to any
## allow service via WAN-interface
# TCP22 SSH
ipfw add 2000 allow tcp from any to me 22 via $WAN
ipfw add 2000 allow tcp from me 22 to any via $WAN
# TCP80, TCP443, UDP443 HTTP/HTTPS
ipfw add 2001 allow tcp from any to me 80,443 via $WAN
ipfw add 2001 allow tcp from me 80,443 to any via $WAN
ipfw add 2001 allow udp from me 443 to any via $WAN
ipfw add 2001 allow udp from any to me 443 via $WAN
# StrongSwan IKEv2 Part1: Established
# ESP, AH, IPENCAP, UDP5000, UDP4500
ipfw add 2010 allow udp from any to me 500,4500 via $WAN
ipfw add 2010 allow udp from me 500,4500 to any via $WAN
ipfw add 2012 allow esp from any to any via $WAN
ipfw add 2013 allow ah from any to any via $WAN
ipfw add 2014 allow ipencap from any to any in recv $WAN
## Allow "auto create IN-rules" for each output request
ipfw add 3000 check-state
ipfw add 3002 allow all from me to any keep-state
# StrongSwan IKEv2 Part2: NAT
# masquerade: 10.0.0.0/16 -> $WAN
ipfw nat 2 config if $WAN same_ports reset deny_in
ipfw add 3015 nat 2 all from 10.0.0.0/16 to any out via $WAN
ipfw add 3016 nat 2 all from any to me in via $WAN