通过端口转发分析iptables经过链路过程

220 阅读8分钟

背景

在特定网络环境中,可能存在如下场景:电脑A能够直接访问服务器A,但无法直接访问服务器B,而服务器A与服务器B之间通信无障碍。为了使电脑A能够访问服务器B,通常有两种方法:

  1. 手动中转:用户首先从电脑A登录到服务器A,然后从服务器A再次登录到服务器B。这种方法虽然可行,但操作繁琐,效率低下。
  2. 端口转发:利用服务器A作为中介,设置端口转发规则,将来自电脑A的请求通过服务器A转发至服务器B。完成配置后,电脑A可以直接向指定端口发送请求,实现对服务器B的间接访问。这种方式不仅简化了访问流程,还提高了工作效率。
graph TB
  subgraph "网络环境"
    direction LR
    A[电脑A]:::client
    B[服务器A]:::server
    C[服务器B]:::server
  end

  A -->|直接访问| B
  A --x|禁止访问| C
  B -->|可访问| C

  classDef client fill:#87CEFA,stroke:#1E90FF,stroke-width:3px,rounded-corners;
  classDef server fill:#FFDAB9,stroke:#FFA07A,stroke-width:3px,rounded-corners;
  class A client;
  class B,C server;

  %% 增加箭头的样式
  linkStyle 0 stroke:#228B22,stroke-width:3px;
  linkStyle 1 stroke:#FF6347,stroke-width:3px,stroke-dasharray: 5,5;
  linkStyle 2 stroke:#228B22,stroke-width:3px;

端口转发

在 Linux 下,可以通过多种方式实现端口转发,常见的方法包括使用 iptablessshd 配置。这里我们使用和讨论的是iptables

iptables流程

graph LR
    A[进入数据包] --> B[PREROUTING]
    B --> C[路由决策]
    
    C -->|本地目标| D[INPUT]
    C -->|非本地目标| E[FORWARD]
    
    D --> F[本地进程处理]
    F --> G[路由决策]
    G --> H[OUTPUT]
    H --> I[POSTROUTING]
    
    E --> I[POSTROUTING]
    
    classDef inputOutput fill:#f9f,stroke:#333,stroke-width:2px;
    class D,F,G,H,I inputOutput;
    classDef forward fill:#ccf,stroke:#333,stroke-width:2px;
    class E forward;


作用流量类型使用场景
INPUT处理目标是本机的数据包进入本机的数据包控制哪些流量可以访问本机的服务
FORWARD处理转发的数据包目标是其他主机的流量控制路由器或网关转发的流量
OUTPUT处理本机发出的数据包本机生成并发出的流量控制本机发出的流量
PREROUTING处理进入系统的数据包,在路由前进行处理进入本机的数据包,路由之前用于端口转发、目标地址转换(DNAT)等
POSTROUTING处理离开系统的数据包,在路由后进行处理离开本机的数据包,路由之后用于源地址转换(SNAT)、出口流量修改等

分析

IP
电脑 A192.168.31.149
服务器 A172.30.243.12
服务器 B192.168.31.132

要实现的是:电脑A访问的是服务器A的10022端口,等于访问服务器B的22端口,就是将服务器A端口10022转发到服务器22端口上。

先尝试用电脑A去telnet服务器A的10022看看

PS C:\Users\22787> telnet 172.31.88.37 10022
正在连接172.31.88.37...无法打开到主机的连接。 在端口 10022: 连接失败
PS C:\Users\22787>

在服务器A通过 tcpdump 抓包看看,可以发现发送了5次都被rst。

root@root:~# tcpdump -i any  port 10022 -n
tcpdump: WARNING: any: That device doesn't support promiscuous mode
(Promiscuous mode not supported on the "any" device)
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
00:34:49.114827 eth0  In  IP 172.30.240.1.63532 > 172.30.243.12.10022: Flags [S], seq 4070433852, win 65535, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
00:34:49.114867 eth0  Out IP 172.30.243.12.10022 > 172.30.240.1.63532: Flags [R.], seq 0, ack 4070433853, win 0, length 0
00:34:49.619129 eth0  In  IP 172.30.240.1.63532 > 172.30.243.12.10022: Flags [S], seq 4070433852, win 65535, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
00:34:49.619174 eth0  Out IP 172.30.243.12.10022 > 172.30.240.1.63532: Flags [R.], seq 0, ack 1, win 0, length 0
00:34:50.131152 eth0  In  IP 172.30.240.1.63532 > 172.30.243.12.10022: Flags [S], seq 4070433852, win 65535, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
00:34:50.131192 eth0  Out IP 172.30.243.12.10022 > 172.30.240.1.63532: Flags [R.], seq 0, ack 1, win 0, length 0
00:34:50.631726 eth0  In  IP 172.30.240.1.63532 > 172.30.243.12.10022: Flags [S], seq 4070433852, win 65535, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
00:34:50.631766 eth0  Out IP 172.30.243.12.10022 > 172.30.240.1.63532: Flags [R.], seq 0, ack 1, win 0, length 0
00:34:51.144974 eth0  In  IP 172.30.240.1.63532 > 172.30.243.12.10022: Flags [S], seq 4070433852, win 65535, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
00:34:51.145011 eth0  Out IP 172.30.243.12.10022 > 172.30.240.1.63532: Flags [R.], seq 0, ack 1, win 0, length 0

通过在iptablse添加规则,查看上面telnet走了那个链路。

## 添加规则到 `INPUT` 链
sudo iptables -A INPUT -p tcp --sport 10022 -j ACCEPT 
sudo iptables -A INPUT -p tcp --dport 10022 -j ACCEPT
## 添加规则到 `OUTPUT` 链
sudo iptables -A OUTPUT -p tcp --sport 10022 -j ACCEPT 
sudo iptables -A OUTPUT -p tcp --dport 10022 -j ACCEPT
## 添加规则到  `FORWARD` 链
sudo iptables -A FORWARD -p tcp --sport 10022 -j ACCEPT 
sudo iptables -A FORWARD -p tcp --dport 10022 -j ACCEPT
## 添加规则到  `PREROUTING` 链
sudo iptables -t nat -A PREROUTING -p tcp --sport 10022 -j REDIRECT --to-ports 10022
sudo iptables -t nat -A PREROUTING -p tcp --dport 10022 -j REDIRECT --to-ports 10022
## 添加规则到  `POSTROUTING` 链
sudo iptables -t nat -A POSTROUTING -p tcp --sport 10022 -j SNAT --to-source :10022
sudo iptables -t nat -A POSTROUTING -p tcp --dport 10022 -j SNAT --to-source :10022

查看链路信息 iptables -L -v -n:

root@root:~# sudo iptables -L -v -n
Chain INPUT (policy ACCEPT 28 packets, 2492 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022

Chain OUTPUT (policy ACCEPT 21 packets, 3508 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022

查看PREROUTING链路信息 iptables -t nat -L PREROUTING -v -n:

root@root:~# sudo iptables -t nat -L PREROUTING -v -n
Chain PREROUTING (policy ACCEPT 4 packets, 813 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 REDIRECT   6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022 redir ports 10022
    0     0 REDIRECT   6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022 redir ports 10022

查看POSTROUTING链路信息 iptables -t nat -L POSTROUTING -v -n:

root@root:~# sudo iptables -t nat -L POSTROUTING -v -n
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 SNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022 to::10022
    0     0 SNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022 to::10022

再次telnet看看数据包走了哪些链路:

root@root:~# sudo iptables -L -v -n
Chain INPUT (policy ACCEPT 359 packets, 24340 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022
    5   260 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022

Chain OUTPUT (policy ACCEPT 263 packets, 29108 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    5   200 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022
    
root@root:~# sudo iptables -t nat -L PREROUTING -v -n
Chain PREROUTING (policy ACCEPT 4 packets, 813 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 REDIRECT   6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022 redir ports 10022
    5   260 REDIRECT   6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022 redir ports 10022

root@root:~# sudo iptables -t nat -L POSTROUTING -v -n
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 SNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:10022 to::10022
    0     0 SNAT       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10022 to::10022


通过上面可以看到 5次包走 PREROUTING-> INPUT-> OUTPUT, FORWARD没走因为目标地址172.30.243.12是本机,POSTROUTING没走因为目标地址172.30.243.12是本机,数据包没走出去。

我们要实现端口转发的话,首先将目标端10022和目标地址172.30.243.12(服务器A),改成目标端22和目标地址192.168.31.132(服务器B),通过PREROUTING链路修改

sudo iptables -t nat -I PREROUTING -p tcp --dport 10022 -j DNAT --to-destination 192.168.31.132:22

因为我们修改了目标地址,目标地址非本机地址导致链路变了:PREROUTING-> FORWARD-> PREROUTING

我们允许数据包通过FORWARD

sudo iptables -A FORWARD -p tcp --dport 22 -j ACCEPT

为了让数据包能找到回来的路,需要SNAT就是源地址转换(如果两个设备都在同一个私有网络或子网内,它们之间的流量可以直接路由,不需要进行 SNAT),172.30.243.12和192.168.31.132不是同一个网络。

sudo iptables -t nat -A POSTROUTING -d 192.168.31.132 -j SNAT --to-source 172.30.243.12

还可以这样写,从eth0出去的数据包,都会被SNAT,转换的源地址是eth0的ip

sudo iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE

如果默认FORWARD是允许所有数据包通过的话,上面就完成端口转发了,可以telnet了。

如果我们给FORWARD添加一个拒绝所有流量规则:

sudo iptables -A FORWARD -j DROP

你会发现telnet就不行,那是你没有允许回来的数据包通过,这个是往往容易忽略的,添加一个允许通过:

sudo iptables -I FORWARD -p tcp --sport 22 -j ACCEPT

这样就可以了。

总结

  • 目标地址是本机的链路:PREROUTING-> INPUT-> OUTPUT
  • 目标地址不是本机的链路:PREROUTING-> FORWARD-> POSTROUTING
  • PREROUTING中nat转换目标不是本机地址,转换的地址和本机地址不是通过网络,则需要在POSTROUTING中SNAT转换。
  • 注意回来数据包会走FORWARD