通过iptables实现端口转发和内网共享上网

1. netfilter

iptables操作的是2.4以上内核的netfilter,所以需要 linux的内核在2.4以上。其功能与安全性远远比其前辈 ipfwadm, ipchains强大,iptables大致是工作在OSI七层的二、三、四层,其前辈ipchains不能单独实现对tcp/udp port以及对mac地址的的定义与操作,所以我想ipchains应该是仅仅工作在三层的。

我们先简单介绍一下netfilter的大致工作流程,也就是一个数据包(或者叫分组、packet,我个人习惯叫包)在到达linux的网络接口的时候 (网卡)如何处理这个包,然后再介绍一下如何用iptables改变或者说控制对这个数据包进行操作。

netfilter内部分为三个表,分别是 filter,nat,mangle,每个表又有不同的操作链(Chains)。

  • filter表:filter表用于防火墙功能,定义了三个 Chain,分别是INPUT(入), FORWARD(转发), OUTPUT(出)。
  • nat(Network Address Translation)表:nat表用以实现地址转换和端口转发功能,定义了三个Chain,分别是PREROUTING,POSTROUTING,OUTPUT。
  • mangle表:mangle表是一个自定义表,包括filter表、nat表中的各种chains,它可以让我们进行一些自定义的操作,同时这个mangle表中的chains在netfilter对包的处理流程中处在一个比较优先的位置。

下面有一张图清晰的描绘了netfilter对包的处理流程(该图摘自网上,不知作者是谁,在此深表敬意!),一般情况下,我们用不到这个mangle表,在这里我们就不做介绍了。

netfilter包处理流程

1.1 PREROUTING链

当一个包来到Linux的网络接口的时候先执行PREROUTING操作,依次经过mangle、nat的PREROUTING链。从这个Chain的名字我们可以看出,这个Chain是在路由之前(pre-routing)要过的。为什么要在路由之前过呢?因为在这个链里面我们对包的操作是DNAT,也就是改变目的地址和(或端口),通常用在端口转发(修改P
ort),或者NAT到内网的DMZ区(修改地址)。

环境配置如下:

//节点1 - 公网节点
eth0: 60.1.1.1/24 公网ip
eth1:10.1.1.1/24 内网ip

//节点2 - Web服务器
eth1: 10.1.1.2/24

我们怎么样能让Internet用户通过公网IP访问内部的web服务器呢? 在这个PREROUTING链上定义一个规则,把访问60.1.1.1:80的用户的目的地址改变一下,改变为10.1.1.2:80,这样就实现了internet用户对内网服务器的访问了。当然,这个端口是比较灵活的,我们可以定义任何一个端口的转发,不一定是80–>80。具 体的命令我们在下面的例子中介绍,这里我们只谈流程与概念上的实现方法。

1.2 FORWARD链

好了,我们接着往下走,来到图中下方的那个菱形(FORWARD),转发!

  • 如果目的IP是本机IP,那么包向上走,走入INPUT链处理,然后进入LOCAL PROCESS,随后执行OUTPUT。我们在这里就不介绍INPUT,OUTPUT的处理了,因为那主要是对于本机安全的一 种处理,我们这里主要说对转发的过滤和NAT的实现。
  • 如果非本地,那么就进入FORWARD链进行过滤。

默认情况下,当Linux收到了一个目的IP地址不是本地IP的包,Linux会把这个包丢弃。因为默认情况下,Linux的三层包转发功能是关闭的,如果要让我们的Linux实现转发,则需要打开这个转发功能,可以 改变它的一个系统参数,使用如下命令打开转发功能:

# 方法一
sysctl net.ipv4.ip_forward=1
# 方法二
echo "1" > /proc/sys/net/ipv4/ip_forward

处理顺序上,依然是mangle优先、随后流经filter的FORWOARD链。我们操作任何一个链都会影响到这个包的命运,在 下面的介绍中,我们就忽略掉mangle表,我们基本用不到操作它,所以我们假设它是透明的。假设这个包被我们的规则放过去了,也就是ACCEPT了,它将进入POSTROUTING部分。

注意!这里我注意到一个细节问题,也就是上面的图中数据包过了FORWARD链之后直接进入了POSTROUITNG 链,我觉得这中间缺少一个环节,也就是ROUTING。对于转发的包来说,Linux同样需要在选路(路由)之后才能将它送出,这个图却没有标明这一点,我认为它是在过了ROUTING之后才进入的POSTROUITNG。当然了,这对于我们讨论iptables的过滤转发来说不是很重要,只是我觉得流程上有这个问题,还是要说明 一下。

1.3 POSTROUITNG链

POSTROUTING链是数据包要送出这台Linux的最后一个环节了,也是极其重要的一个环节。这个时候Linux已经完成了对这个包的路由(选路工作),已经找到了合适的接口送出这个包了,在这个链里面我们要进行重要的操作,就是被Linux称为 SNAT的一个动作,修改源IP地址!

为什么修改源IP地址?最常见的就是我们内网多台机器需要共享一个或几个公网IP访问 Internet。因为我们的内网地址是私有的,假如就让Linux给路由出去,源地址也不变,这个包能访问到目的地,但却回不来。因为 Internet上的路由节点不会转发私有地址的数据包,也就是说,不用合法IP,我们的数据包有去无回。

有人会说:“既然是这样,我就不用私有IP了,我自己分配自己合法的地址不行吗?那样包就会回来了吧?”。答案是否定的,IP地址是ICANN来分配的,Internet上的路由器会把这个返回包送到合法的IP去,你同样收不到。而你这种行为有可能被定义为一种ip欺骗,很多设备会把这样的包在接入端就给滤掉了。

那么Linux如何做SNAT 呢?环境配置如下:

//节点1 - 公网节点
eth0: 60.1.1.1/24 公网ip
eth1:10.1.1.1/24 内网ip

//节点2
eth1: 10.1.1.2/24

当内网节点10.1.1.12需要访问202.2.2.2的web服务器,发送数据包时先路由到10.1.1.1节点,随后(在10.1.1.1节点配置SNAT)将源IP改为60.1.1.1后送出。同时在ip_conntrack表里面做一个记录:内网的哪一个ip的哪个端口访问的这个web服务器,自己把它的源地址改成多少了,端口改成多少了,以便这个web服务器返回数据包的时候linux将它准确的送回给发送请求的这个pc.

2. iptables命令

大体的数据转发流程我们说完了,我们看看iptables使用什么样的参数来完成这些操作。在描述这些具体的操作之前,我还要说几个我对iptables的概念的理解(未必完全正确),这将有助于大家理解这些规则,以实现更精确的控制。

上文中我们提到过,对包的控制是由我们在不同的Chain(链)上面添加不同的规则来实现的。那么既然叫链,一定就是一条或者多条规则组成的了,这时就有一个问题了,如果多个规则对同一种包进行了定义,会发生什么事情呢?

在Chain中,所有的规则都是从上向下来执行的,也就是说,如果匹配了第一行,那么就按照第一行的规则执行,一行一行的往下找,直到找到 符合这个类型的包的规则为止。如果找了一遍没有找到符合这个包的规则怎么办呢?iptables里面有一个概念,就是Policy(策略),如果找了一遍找不到符合处理这个包的规则,就按照policy来办。iptables 使用-P来设置Chain的策略。

2.1 链(Chain)操作

对链的操作就那么几种:

  • I(插入):放到链首
  • A(追加):放到链尾
  • R(替换)
  • D(删除)
  • L(列表显示)

比如我们要添加一个规则到filter表的FORWARD链:

# 追加一个规则至filter表中的FORWARD链尾,允许(-j ACCEPT)源地址为10.1.1.11目的地址为202.1.1.1的数据包通过。
iptables -t filter -A FORWARD -s 10.1.1.11 -d 202.1.1.1 -j ACCEPT

在iptables中,默认的表名就是filter,所以这里可以省略-t filter直接写成:

iptables -A FORWARD -s 10.1.1.11 -d 202.1.1.1 -j ACCEPT

2.2 参数项

iptables中的匹配参数: 我们在这里就介绍几种常用的参数,详细地用法可以man iptables看它的联机文档,你会有意外的收获。

  • -s 匹配源地址
    可以指定某一个单播ip地址,也可以指定一个网络.
    如果单个的ip地址其实隐含了一个32位的子网掩码,比如-s 10.1.1.11 其实就是-s 10.1.1.11/32,
    指定不同的掩码用以实现源网络地址的规则,比如一个C类地址我们可以用-s 10.1.1.0/24来指定。
  • -d 匹配目的地址
    规则和-s相同
  • -p 协议匹配
    如tcp, udp, udplite, icmp, esp, ah, sctp
  • -i 入接口匹配
    指定网络接口,比如我仅仅允许从eth3接口过来的包通过FORWARD链,就可以这样指定iptables -A FORWARD -i eth3 -j ACCEPT
    -o 出接口匹配
    规则同-i
  • –sport 源端口
  • –dport 源端口
  • -j 跳转
    也就是包的方向,包括ACCEPT, DROP, QUEUE or RETURN几种。

3. 操作示例

3.1 实例一:内网节点访问外网服务

环境信息:

#服务器节点
> SUSE Linux Enterprise Server 11 (x86_64)
eth0: 8.1.234.73(外网IP)
服务端口: 80

#外网节点
> SUSE Linux Enterprise Server 11 (x86_64)
eth0: 192.168.234.71(内网IP)
eth11: 8.1.234.71(外网IP)

#内网节点
> SUSE Linux Enterprise Server 11 (x86_64)
eth0:192.168.234.72(内网IP)

3.1.1 内网节点配置

  • 将内网节点的网关指向外网节点的内部IP:192.168.234.71。
vi /etc/sysconfig/network/routes 
------
default 192.168.234.71 - -
  • 重启网络服务
service network restart
  • 查看路由是否生效
route
------
 Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.234.71  0.0.0.0         UG    0      0        0 eth0
...

3.1.2 外网节点配置

  • 打开linux的转发功能
 sysctl net.ipv4.ip_forward=1

或者使用如下命令

echo 1 > /proc/sys/net/ipv4/ip_forward 
  • 配置FORWARD
//
# 将FORWARD链的策略设置为DROP,这样做的目的是做到对内网ip的控制
# 你允许哪一个访问internet就可以增加一个规则,不在规则中的ip将无法访问internet.
iptables -t filter -P FORWARD DROP

# 允许任何地址 --> 任何地址的确认包和关联包通过。
# 一定要加这一条,否则你只允许Lan IP访问没有用,至于为什么,下面我们再详细说。
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

# 允许内网IP数据转发
iptables -A FORWARD -s 192.168.234.72 -j ACCEPT
  • 配置SNAT
    将来自192.168.234.0/24的地址转换为8.1.234.71。因为是让内网上网,对于代理服务器而言POSTROUTING(经过路由之后的包应该要把源地址改变为8.1.234.71,否则包无法返回)。
iptables -t nat -A POSTROUTING -s 192.168.234.72/24 -j SNAT --to 8.1.234.71

配置完成,在内网节点(192.168.234.72)发起的服务器节点(8.1.234.73)请求,会由外网节点修改源ip后转发出去。
在8.1.234.73上启动nginx服务,在192.168.234.72上通过curl访问效果如下:

curl 8.1.234.73
------
Hello, this is 8.1.234.73 server

可以将上述配置写到一个文件中,以便重复执行。

//
# 清除filter配置后重新配置
iptables -t filter -F
iptables -t filter -P FORWARD DROP
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -s 192.168.234.72 -j ACCEPT
# 清除nat配置后重新配置
iptables -t nat -F
iptables -t nat -A POSTROUTING -s 192.168.234.0/24 -j SNAT --to 8.1.234.71

除此之外,也可以精确控制他的访问地址,比如我就允许10.1.1.99访问3.3.3.3这个ip

iptables -A FORWARD -s 10.1.1.99 -d 3.3.3.3 -j ACCEPT

或者只允许他们访问80端口

iptables -A FORWARD -s 10.1.1.0/24 -p tcp --dport http -j ACCEPT

更多的控制可以自己灵活去做,或者查阅iptables的联机文档。

3.2 实例二:外网节点访问内网服务

环境信息:

* 外网节点
SUSE Linux Enterprise Server 11 (x86_64)
eth0: 8.1.234.73(外网IP)

*  外网节点(网关节点)
SUSE Linux Enterprise Server 11 (x86_64)
eth0: 192.168.234.71(内网IP)
eth11: 8.1.234.71(外网IP)

* 内网节点(服务节点)
SUSE Linux Enterprise Server 11 (x86_64)
eth0:192.168.234.72(内网IP)
服务端口: 80

3.2.1 内网节点配置

  • 开启nginx服务
/etc/nginx/nginx

确保服务监听的是ip是内网ip

    server {
        listen     192.168.234.72:80;
        server_name  192.168.234.72;
        ......
  • 将内网节点的网关指向外网节点的内部IP:192.168.234.71。
vi /etc/sysconfig/network/routes 
------
default 192.168.234.71 - -
  • 重启网络服务
# service network restart
  • 查看路由是否生效
# route
 Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.234.71  0.0.0.0         UG    0      0        0 eth0
...

3.2.2 外网节点(网关节点)配置

  • 打开linux的转发功能
sysctl net.ipv4.ip_forward=1

或者使用如下命令

echo 1 > /proc/sys/net/ipv4/ip_forward 
  • 配置FORWARD
# 将FORWARD链的策略设置为DROP,这样做的目的是做到对内网ip的控制
# 你允许哪一个访问internet就可以增加一个规则,不在规则中的ip将无法访问internet.
iptables -t filter -P FORWARD DROP

# 允许任何地址 --> 任何地址的确认包和关联包通过。
# 一定要加这一条,否则你只允许lan IP访问没有用,至于为什么,下面我们再详细说。
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

# 将目的地址为外网地址的数据包改为内网地址
iptables -t nat -A PREROUTING -d 8.1.234.71 -p tcp --dport 80 -j DNAT --to 192.168.234.72

# 路由前,数据包先经过PREROUTING处理,目的地址已改为内网地址(192.168.234.72)
# 配置转发规则
iptables -t filter -A FORWARD -d 192.168.234.72 -p tcp --dport 80 -j ACCEPT

同样,可以将这部分配置放到一个文件中:

iptables -t filter -F
iptables -t nat -F
iptables -t filter -P FORWARD DROP

iptables -t filter -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A PREROUTING -d 8.1.234.71 -p tcp --dport 80 -j DNAT --to 192.168.234.72
iptables -t filter -A FORWARD -d 192.168.234.72 -p tcp --dport 80 -j ACCEPT

OK,至此配置完成,我们可以尝试在外网节点(8.1.234.73)上,通过网关节点(8.1.234.71)访问内网(192.168.234.72)提供的服务(80端口)。

Hello, this is 72 server

3.2.3 另一种配置方式

在前面的配置中,内网节点将互通的外网节点做为网关,由网关节点修改目的地址,源地址保持不变。其实,还存在另外一种配置方式,同时修改源地址、目的地址。将内网地址中的默认路由配置删除

route del default gw 192.168.234.71

增加一个SNAT配置

iptables -t nat -A POSTROUTING -d 192.168.234.72 -p tcp --dport 80 -j SNAT --to 192.168.234.71

这条命令不太好懂?其实很简单,如果使用这条命令,那么你的web server不需要再设置默认网关,就能收到这个请求,只要他和linux的lan ip地址是能互访的(也就是说web server和Linux的Lan ip在一个广播域)。我们在根据上面的netfilter流程图来分析这个包到底被我们怎么样了:

  1. 一个请求8.1.234.73:1333访问8.1.234.71:80被linux收到了,进入PREROUTING;发现一个匹配规则
iptables -t nat -A PREROUTING -d 8.1.234.71 -p tcp --dport 80 -j DNAT --to 192.168.234.72

修改目的地址,于是这个包变成了8.1.234.73:1333–>192.168.234.72:80。

  1. 进入FORWARD链,也有一条规则允许通过
iptables -A FORWARD -d 192.168.234.72 -p tcp --dport 80 -j ACCEPT

进入route box选路,找到合适路径,此时这个包依旧是8.1.234.73:1333–>192.168.234.72:80。

  1. 随后进入POSTROUTING链;又发现一个符合的规则
iptables -t nat -A POSTROUTING -d 192.168.234.72 -p tcp --dport 80 -j SNAT --to 192.168.234.71

原来是一个SNAT,改你的源地址,于是这个包变成了192.168.234.71:xxxx(随机端口)–>192.168.234.72:80。

  1. 整个的两次变化的过程都会记录在linux的ip_conntrack中。
  2. 当web server收到这个包的时候,发现,原来是一个内网自己兄弟来的请求阿,又在一个广播域,不用找网关,把返回包直接扔给交换机了;
  3. linux在收到返回包之后,会根据他的ip_conntrack中的条目进行两次变换,返回真正的internet用户,于是完成这一次的访问。

看了上面的两个例子,不知道大家是否清楚了iptables的转发流程,希望对大家有所帮助。

4. 状态机制

下面来讲前面提到的ESTABLISHED,RELATED规则是怎么回事,到底有什么用处。

我们知道,网络的访问是双向的,也就是说一个Client与Server之间完成数据交换需要双方的发包与收包。在netfilter中,有几种状态,也就是New, Established,Related,Invalid。

当一个客户端,在本文例一中,内网的一台机器访问外网,我们设置了规则允许他出去,但是没有设置允许回来的规则啊,怎么完成访问呢?这就是netfilter的 状态机制 ,当一个Lan用户通过这个Linux访问外网的时候,它发送了一个请求包,这个包的状态是New(配置了内网IP的转发规则,放行)。当外网回包的时候他的状态就是Established,所以,Linux知道,哦,这个包是我的内网的一台机器发出去的应答包,他就放行了。

而外网试图对内发起一个新的连接的时候,他的状态是New,所以Linux压根不去理会它。这就是我们为什么要加这一句的原因。

还有那个Related,他是一个关联状态,什么会用到呢?sftp、ftp都会用到,因为他们的传输机制决定了,它不像http访问那样,Client_IP: port–>Server:80然后server:80–>Client_IP:port,ftp使用tcp21建立连接,使用20端口发送数据,其中又有两种方式,一种主动active mode,一种被动passive mode。主动模式下,client使用port命令告诉server我用哪一个端口接受数据,然后server主动发起对这个端口的请求。被动模式下,server使用port命令告诉客户端,它用那个端口监听,然后客户端发起对他的数据传输,所以这对于一个防火墙来说就是比较麻烦的事情,因为有可能会有New状态的数据包,但是它又是合理的请求,这个时候就用到这个Related状态了,他就是一种关联,在linux中,有个叫 ftp_conntrack的模块,它能识别port命令,然后对相应的端口进行放行。

5. 实用命令

对了,还有几个在实际中比较实用(也比较受用:-))的命令参数,写出来供大家参考

  • 有的时候使用iptables -L会比较慢,因为linux会尝试解析ip的域名,真是罗嗦,如果你的dns server比较不爽的话,iptables -L就会让你很不爽,加一个-n参数就好了。列表刷的就出来。当然了,如果你的linux就是做防火墙,建议把nameserver去掉,在 /etc/resolve.conf里面,因为有时候使用route命令也会比较慢列出来,很是不爽。
iptables -L -n
  • 这个命令会显示链中规则的包和流量计数,嘿嘿,看看哪些小子用的流量那么多,用tc限了他。
iptables -L -v
  • 查看nat表中的规则。
iptables -t nat -L -vn
  • 查看目前的conntrack,可能会比较多哦,最好加一个|grep “关键字”,看看你感兴趣的链接跟踪
cat /proc/net/ip_conntrack
  • 看看总链接有多少条。
wc -l /proc/net/ip_conntrack
  • 把当前的所有链备份一下,之所以放到/etc下面叫iptables,因为这样重起机器的时候会自动加载所有的链,经常地备份一下吧,否则如果链多,万一掉电重启,你还是会比较痛苦。
iptables-save >/etc/iptables

作者:随安居士
链接:https://www.jianshu.com/p/2312dd32361a
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

发表评论

电子邮件地址不会被公开。 必填项已用*标注