既然名曰“防火墙”,其对安全的主要性不言而喻,而防火墙分为硬件防火墙和软件防护墙,iptables/netfileter是软件防火墙,它又分为主机防火墙和网络防火墙。至于工作机制说白了就是预先定下规则然后对于进出本网络或主机的数据报文进行规则检测,然后对于满足规则的报文做出规则动作处理。而iptables组件就是在用户空间来让用户操作规则的工具,然后将这些规则放到内核空间的netfilter组件中来让内核对这些报文做指定的处理的(因为网络管理必然是要追根溯源到内核空间中的,那么就需要iptables这个用户空间组件通过系统调用来将规则交给内核处理)。
再之由于是对报文做规则匹配与处理,因此必须清楚的知道报文的流向是什么,如下图
对于流进本机的数据报文:首先网络先到底PREROUTING,然后经过路由判断是否是到达本机内部,如果是则通过INPUT进入本机与本机的应用进程通信,然后将通信结果通过OUTPUT到达POSTROUTING,再经由POSTROUTING发出去;如果不是则通过FORWARD转发到POSTROUTING,然后流出本机。
对于本机的数据报文流出本机时候:从用户空间经过OUTPUT到达POSTROUTING然后流出本机
此图中的英语代表的是:
netfilter自带的四个规则表(按优先级):
raw:关闭在nat表中启用的连接追踪机制。
mangle:拆解报文,按需修改再重新封装。
nat:对ip地址和传输层地址进行地址转换。
filter:对数据报文进行过滤。
netfilter自带的五链:
PREROUTING:刚到主机还未经过路由
INPUT:经过路由到达本机内部
FORWARD:经由本机转发的不到本机内部
OUTPUT:由本机内部发出的
POSTROUTING:即将离开本机的
说明:此五个链对应于五个钩子函数,而iptables写的规则就是按需要放置到这五个钩子函数的位置。
下面来查看netfilter自带的四个表的默认链。
raw:由图可知raw表中默认有PREROUTING和OUTPUT两个链
mangle:由图可知mangle表中默认五个链都有
nat:由图可知nat表默认有PREROUTING,OUTPUT,POSTROUTING三个链
filter:由图可知filter表中默认有INPUT,FORWARD,OUTPUT三个链
那么现在上图中的各表的位置就一目了然了。
好了,已经知道了数据报文的流向和netfilter的默认表与链,现在就开始看看怎么使用iptables来操作表与链并赋予规则。
iptables的语法格式为:
iptables [-t 表] 额外命令 [链] 匹配规则 处理动作
-t:指定表,默认为filter
额外命令:
查看:
-L:查看
-n:以数字格式显示地址和端口
-v:详细信息 -vv -vvv
-x:显示计数器的精确值而非单位换算值
--line-numbers:显示链上规则的编号
-S:以命令格式显示链上规则
链处理:
-N:新增一个链
-E:链重命令
-X:删除一个链
-P:链默认规则
以下的说一部分再举例子比较好明白些。
规则处理:
-A [n]:追加一条新规则,默认为最后一条
-I [n]:插入一条新规则,默认为第一条
-D 链名 n:删除一条指定规则
-R 链名 n:修改指定规则
-F:清空所有规则
-z:置零
规则匹配:
基本匹配:
-s:源地址,可以是网络地址
-d:目标地址,可以是网络地址
-i:进入的接口
-o:出去的接口
扩展匹配:分为隐式扩展和显式扩展
隐式扩展(主要):-p {tcp|udp|icmp}
tcp:[!] --sport port:[port]:匹配tcp报文源地址,port:port表示端口范围,!表示取反
[!] --dport port:[port]:匹配tcp报文目标地址,port:port表示端口范围,!表示取反
[!] --tcp-flags mask comp:检查tcp报文的mask指定的标志位,而且这些标志位的comp必须为1
[!} --syn:三次握手的第一次(与--tcp-flags syn,ack,rst,fin syn等价)
在这里必须说明一下tcp的三次握手四次断开的有限状态机,放在这个位置的原因是为了比较方便结合理解-p tcp --tcp-flags以及--syn(详情可看TCP/IP详解 卷一)。
tcp报文:
这里只说明字面意思不明显的项:32位序号对每个字节进行计数,32位序号期望的下一个序号(对序号加1),4位首部长度是表示tcp的首部长度,UGR:紧急指针,ACK;确认序号有效,PSH:接收方应该尽快将报文交给应用层,RST;重新连接,SYN:第一次请求连接,FIN:第一次请求断开。
tcp的三次握手与四次断开:
三次握手:
首先请求方A由于是第一次请求连接因此将SYN标志位置1并发送一个随机数n给接收方B(SYN_SENT阶段),,接收方B通过发送一个n+1的确认数来说明此次是回应请求方A的,因此ACK置1,因为也是第一次与请求方A通信所以SYN也置1以ACK置1.,并且也发送一个随机数m(SYN_RCVD阶段),接收方A由于已经不是第一次请求了所以只通过m+1来确认确实是此接收方B的连接回应(ESTABLISHED阶段)
四次断开:
首先请求方A由于是第一次发起断开连接请求(通常谁发起连接请求就谁发起断开请求,虽然并不绝对),所以FIN置1,并发送一个随机数o,为了让接收方B知道确实是A请求断开,因此还要发送一个确认数m+1(请求方A处于FIN1阶段),接收方B通过发送一个确认数m+1+1来确认请求方不再发送请求数据(接收方B处于CLOSE_WAITE阶段,请求方A处于FIN2阶段),但是此时只是请求方A断开,但是接收方B还可以发送数据给A(半关闭阶段),因此接收方B要断开的时候必须发送一个随机数给A同样的因为是第一次请求断开所以SYN置1并且为了让请求方A知道确实是B请求断开所以发送确认数m+1+1+1给A,因此ACK置1(接收方B处于LAST_ACK阶段,请求方处于TIMEOUT阶段),最后请求方A发送确认数m+1+1+1来完全断开(接收方B处于CLOSE状态)
下面两张图第一个是C/S架构的套接字函数通信过程(可参考Unix网络编程),第二张为TCP的有限状态机(TCP/IP详解 卷一),这里只说明第一张图是为了更好的理解第二张图,以为第二张图在上面其实已经说明了
TCP Server端创建套接字socket(),绑定套接字bind(),监听状态listen(),接收请求accept()
TCP Client端创建套接字socket(),请求连接connect()
建立连接后
然后后面的内容就是TCP Client端写请求write(),TCP Server端读请求read()。。直到TCP Client发出断开请求close(),然后TCP Server读取此请求并关闭断开连接。
(上图虚线为服务端,实线为请求方)
现在我想应该对--tcp-flags和--syn很容易理解了吧,那就继续来说iptables的语法了。
udp:[!] --sprot port:[:port]:匹配udp报文的源地址,port:port表示端口范围,!表示取反
[!] --dport port:[port]:匹配udp报文的目标地址,port;port表示端口范围
icmp:--icmp-type {8|0}:此选项有很多,但是这里只说8和0,其中8表示请求,0表示回应(ping)
此上三种其实都隐含了-m {tcp|udp|icmp},例如,-p tcp等价-p tcp -m tcp。
显式扩展:显式扩展都是-m value。
multiport:多端口匹配,可离散表示,也可范围表示。
[!]--dports port[,port:port]:目标端口匹配,逗号分隔为离散端口,冒号分隔为端口范围。!表示取反
[!]--sports port[,port:port]:源端口匹配,逗号分隔为离散端口,冒号分隔为端口范围。!表示取反
[!]--ports port[,port;port];端口匹配(不分是源端口还是目标端口),逗号分隔为离散端口,冒号分隔为端口范围。!表示取反
iprange:指明连续的地址范围作为源地址和目标地址匹配。
[!] --src-range ip[-ip]:源地址匹配,ip-ip表示连续的ip地址
[!] --dst-range ip[-ip]:目标地址匹配,ip-ip表示连续的ip地址。
string:
--argo {bm|kmp}:指定算法
[!} --string 模式:给定要检查的字符串模式,!表示取反
[!] --hex-sring 模式:给定要检查的字符串模式(16进制),!表示取反。
time:根据收到报文的时间/日期与指定的时间/日期范围进行匹配;
--datestart YYYY[-MM[-DD[Thh[:mm[:ss]]]]]:起始日期时间;
--datestop YYYY[-MM[-DD[Thh[:mm[:ss]]]]]:结束日期时间;
--timestart hh:mm[:ss]:起始时间;
--timestop hh:mm[:ss]:结束时间;
[!] --monthdays day[,day...]:匹配一个月中的哪些天;
[!] --weekdays day[,day...]:匹配一个周中的哪些天;
connlimit:根据每客户端主机做并发连接数限制,即每客户端最多可同时发起的连接数量;
--connlimit-upto n:连接数量小于等于n则匹配;
--connlimit-above n:连接数量大于n则匹配;
limit:基于令牌桶算法对报文的速率做匹配
--limit rate[/second|/minute|/hour|/day]:指定速率
--limit-burst number:突发速率
state:
--state:
NEW:模板中没有记录的,表示一个新请求
ESTABLISHED:模板中有记录的,表示已经连接的
RELATED:虽然是第一次请求,但是确是有关联的(例如ftp的命令连接和数据连接)
INVALID:无法识别的连接
UNTRACKED:为追踪的连接。
对于这些状态一旦追踪到会在/proc/net/nf_conntrack中记录下来,但是conntrack所能够追踪的连接数量的最大值取决于/proc/sys/net/nf_conntrack_max的设定;已经追踪到的并记录下来的连接位于/proc/net/nf_conntrack文件中,超时的连接将会被删除;当模板满载时,后续的新连接有可能会超时;解决办法:
(1) 加大nf_conntrack_max的值;
(2) 降低nf_conntrack条目的超时时长;而不同协议的连接追踪时长在/proc/sys/net/netfilter/目录下。
如何放行被动模式的ftp服务?
(1) 内核加载nf_conntrack_ftp模块:modprobe nf_conntrack_ftp
(2) 放行命令连接
iptables -A INPUT -d $sip -p tcp --dport 21 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -s $sip -p tcp --sport 21 -m state --state ESTABLISHED -j ACCEPT
(3) 放行数据连接
iptables -A INPUT -d $sip -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -s $sip -p tcp -m state --state ESTABLISHED -j ACCEPT
处理动作:
-j targetname [per-target-options]
ACCEPT:接受
DROP:丢弃
REJECT:拒绝但是会回应信息给对方
RETURN:返回调用的链
REDIRECT:端口重定向
LOG:日志
MARK:防火墙标记
DNAT:目标地址转换
SNAT:源地址转换
MASQERADE:地址伪装
练习:INPUT和OUTPUT默认策略为DROP;
1、限制本地主机的web服务器在周一不允许访问;新请求的速率不能超过100个每秒;web服务器包含了admin字符串的页面不允许访问;web服务器仅允许响应报文离开本机;
2、在工作时间,即周一到周五的8:30-18:00,开放本机的ftp服务给172.18.0.0网络中的主机访问;数据下载请求的次数每分钟不得超过5个;
3、开放本机的ssh服务给172.18.55.1-172.18.55.100中的主机,新请求建立的速率一分钟不得超过2个;仅允许响应报文通过其服务端口离开本机;
4、拒绝TCP标志位全部为1的报文访问本机;
看下面的练习题
5、允许本机ping别的主机;但不开放别的主机ping本机;
练习:判断下述规则的意义:
# iptables -N clean_in:新增一名为clean_in的链
# iptables -A clean_in -d 255.255.255.255 -p icmp -j DROP:
在clean_in的这链上新增一条规则为:全局广播不论是请求还是回应全部取消
# iptables -A clean_in -d 172.16.255.255 -p icmp -j DROP
在clean_in链上再加一条规则为:目标地址为172.16网络中的广播不论请求还是接受全部取消
# iptables -A clean_in -p tcp ! --syn -m state --state NEW -j DROP
在clean_in链上再加一条规则为:非三次握手中的第一次并且状态部位NEW的全部丢弃
# iptables -A clean_in -p tcp --tcp-flags ALL ALL -j DROP
# iptables -A clean_in -p tcp --tcp-flags ALL NONE -j DROP
在clean_in链上新增两条规则:tcp的所有标志位都为1或者都为0的全部丢弃
# iptables -A clean_in -d 172.16.100.7 -j RETURN
在clean_in链上新增一条规则:所有目标主机为172.16.100.7的都返回给主链
# iptables -A INPUT -d 172.16.100.7 -j clean_in
在filter的INPUT链上新增一条规则:所有目标主机为172.16.100.7的都调用clean_in这条链上规则来过滤,如果全部不匹配,则返回主链INPUT,按默认规则处理
# iptables -A INPUT -i lo -j ACCEPT
在filter的INPUT链上新增一条规则:所有从本机环路进入的统统接收
# iptables -A OUTPUT -o lo -j ACCEPT
在filter的OUTPUT链上新增一条规则:所有从本地环路流出的统统接收
# iptables -A INPUT -i eth0 -m multiport -p tcp --dports 53,113,135,137,139,445 -j DROP
在filter的INPUT链上新增一条规则:所有经过网卡eth0进入的并且通过tcp协议想要到底53,113,135,137,139,445端口的统统丢弃
# iptables -A INPUT -i eth0 -p udp --dport 1026 -j DROP
在filter的INPUT链上新增一条规则:所有经由eth0的通过udp协议想要到底1026端口的统统丢弃
# iptables -A INPUT -p icmp -m limit --limit 10/second -j ACCEPT
在filter的INPUT链上新增一条规则:所有请求与回应全部限速在10次每秒。
现在来说说网络防火墙,也就是将规则放在FORWARD(不到主机内部,只是转发出去)链上,为了演示说明,准备两台虚拟主机,给其中一台虚拟主机两块网卡,一块网卡A用于外网通信(网络为192.168.1。0/24),一块网卡B用于内网通信(网络为10.0.1.0/24),其中A地址为192.168.1.125.
B地址为10.0.1.100,另一台主机一块网卡只用于内网通信,地址为10.0.1.10。
A主机:
下面先看看没有开启转发功能以及B主机没有指定网关地址的情况下的通信状态。
首先用B主机pingA主机的两块网卡地址。
然后用A主机的两块网卡互ping。
注意:这是因为同一主机上的网卡都能互相通信,而不必关系网卡的网络地址是不是在同一网络上,所以不需要转发。
现在将B主机的网管指向A主机的10.0.1.100
现在能ping通192.168.1.125了,只是因为B主机的默认网关为10.0.1.100,而10.0.1.100的那块网卡与192.168.1.125在同一主机上,因此可以通信。
现在来看看能不能ping通192.168.1.126这台外部主机(没有开启转发功能)
此次没有开启转发功能因此虽然能到达10.0.0.100,但是10.0.0.100并不知道192.168.1.108在哪里,这是因为没有开启转发功能并且与192.168.1.126不在同一主机,因此不能通过192.168.1.125到达192.168.1.108这台主机。
现在开启转发功能。
再ping一下192.168.1.126试试
看见没,显示还是ping不通,其实报文已经送达了,再192.168.1.126主机上使用tcpdump看看发现只接受到了请求,但是回应不知道去哪了。
那么为什么没有显示呢,这是因为192.168.1.126的默认网关是192.168.1.1,而192.168.1.1并不知道10.0.1.10在什么地方,因此需要加一条路由
现在再ping试试:
显示通了,再抓包看看(在192.168.1.126主机上)。发现请求和回应都出现了。
好了,现在来指定规则比如让10.0.1.10能ping通192.168.1.126,但是192.168.1.126不能ping通10.0.1.10
现在来看看效果,在10.0.1.10上ping192.168.1.126,然后在192.168.1.126上ping10.0.1.10
也可以抓包看看,因为上面已经演示了很多次的抓包了,所以这里不演示了,只要明白了这其中的原理规则就可以制定了,因此其他规则可自行制定。
下面说说其他的处理动作(放在这个位置主要因为上面的都是filter表的INPUT与OUTPUT)
LOG:开启内核中匹配到的报文的日志功能,注意这个动作必须放在ACCEPT和DROP前面,不然都直接通过了,何谈记录日志?其日志记录在/var/log/messages中
--log-level:指定日记级别
--log-prefix:记录日志的前缀,但是不能超过29各字符
RETURN:可以自己建链,但是自己建的链必须被默认链调用,不然插在哪里来“防火”?所以当很多规则在自建的链中,如果都不匹配,就必须回到调用此链的默认链,使用默认链的规则来处理,当然自己可以在自建的链中的规则中间(只是这样没什么意义)
REDIRECT:能够将指明的端口进行映射,加入在内部web服务器上启动的是8080端口,但是别人并不知道,别人还是默认使用的80端口来访问,那么想要正常被访问就需要映射到8080端口,因此显而易见,其位置必须在PREROUTING或者OUTPUT位置(至于为什么不能再POSTROUTING我也不知道),并且只能在nat表中(地址转换嘛)。
--to-ports port[-port]:port-port表示地址范围。
NAT:Network Address Translation也就是地址转换(那么很明显,放在nat表上)
SNAT: source NAT(放在POSTROUTING和INPUT链上)
修改IP报文中的源IP地址;
让本地网络中的主机可使用统一地址与外部主机通信,从而实现地址伪装;
请求:由内网主机发起,修改源IP,如果修改则由管理员定义;
响应:修改目标IP,由nat自动根据会话表中追踪机制实现相应修改;
--to-source [ipaddr[-ipaddr]]:实现源地址转换(其实也可以端口转换)
例如:将所有10.0.1.0/24的内网地址访问外网的时候全部转换成192.168.1.125来实现与外网通信
现在用10.0.1.10ping192.168.1.126,再在192.168.1.126上抓包看看源地址显示的是谁:
DNAT: destination NAT(放在PREROUTING和OUTPUT链上)
修改IP报文中的目标IP地址;
让本地网络中服务器使用统一的地址向外提供服务(发布服务),但隐藏了自己的真实地址;
请求:由外网主机发起,修改其目标地址,由管理员定义;
响应:修改源地址,但由nat自动根据会话表中的追踪机制实现对应修改;
--to-destination [ipaddr[-ipaddr]][:port[-port]]:目标地址端口转换
例如:将所有192.168.1.0/24的所有地址转换成本地的10.0.1.10地址
现在在192.168.1.126上ping10.168.1.125,然后在10.0.1.10上抓包看看。
MASQUERADE(放在nat表的POSTROUTING链上):当外网网卡是通过动态获得的ip的时候,怎么使得内部主机去访问外网呢(使用SNAT的地址是静态的啊),这时候就需要它了,它会自动的挑选一个ip使用。
例如:与上面一样(先清空规则),再来抓包看看
最后来说说规则的保存于恢复;
规则的有效期限:iptables命令添加的规则,手动删除之前,其生效期限为kernel的生命周期;
保存规则:
对于CentOS 6:
~]# service iptables save
~]# iptables-save > /etc/sysconfig/iptables
~]# iptables-save > /PATH/TO/SOME_RULE_FILE
对于CentOS 7:
~]# iptables -S > /PATH/TO/SOME_RULE_FILE
~]# iptables-save > /PATH/TO/SOME_RULE_FILE
重载预存的规则
~]# iptables-restore < /PATH/FROM/SOME_RULE_FILE
但是对于CentOS 6而言不用这样因为开启iptables的时候会自动读取/etc/sysconfig/iptables文件重载规则
自动生效规则文件中的规则:
(1) 把iptables命令放在脚本文件中,让脚本文件开机自动运行;
/etc/rc.d/rc.local
/usr/bin/iptables.sh
(2) 用规则文件保存规则,开机自动重载命令;
/etc/rc.d/rc.local
iptables-restore < /PATH/FROM/SOME_RULE_FILE