learning:NAT-ED twice-nat功能(1)

2024-06-03 15:30:16 浏览数 (2)

微信公众号:DPDK VPP源码分析 关注公众号可了解更多的学习资源,请留言:资料。 加入vpp相关的微信群,请留言:加群,扫码加我微信,注明加群。 vpp-dpdk相关学习资料:https://github.com/jin13417/dpdk-vpp-learning。 公众号回复:nat 、ipsec,获取对应学习资料 版权归本公众号所有,如需转载请注明出处。 如果你觉得内容对你有帮助,欢迎转发、点赞、赞赏[1]

近期,我们接到客户的一项明确需求:实现外部网络对内部网络特定服务(例如Web服务器、邮件服务)的无地域限制的随机访问。针对这一需求,NAT技术中的“两次NAT”(Twice NAT) 功能脱颖而出,成为理想的解决方案。此功能专为这类场景定制,能够有效地将源自外部、指向公共IP及端口的访问请求,精准桥接到内部网络中相对应的服务主机上。

两次NAT技术赋予在单一规则下同时转换数据包的源头与目标地址的能力,极大增强了网络配置的灵活性。当前实施重点聚焦于NAT44协议,专门支持由外网向内网服务会话的双向转换。值得注意的是,静态映射配置下的两次NAT仅适用于那些由外部网络发起到本地网络的会话初始化过程,确保了对外服务的同时保持了内网结构的安全与隔离性。

正好,VPP(Vector Packet Processing)平台在其NAT-ED模式下内置了对两次NAT(Twice NAT)功能的支持。VPP官方文档提供了详尽的配置指南,为了实践这一特性,本篇文章将遵循这些官方指导,逐步搭建实验环境,以验证两次NAT功能的实际应用效果。

参考官方文档网络架构,在腾讯云主机上搭建验证环境,网络拓扑如下:

代码语言:javascript复制
 -------------------------- 
|   linux netns inside     |
| 10.0.0.2/24 (local host) |
 -------------------------- 
            |
 --------------------------------- 
| 10.0.0.1/24 (tap1) (nat inside) |
| 20.0.0.1/24 (tap2) (nat outside)|
|             vpp                 |
 --------------------------------- 
            |
 --------------------------- 
| 20.0.0.2/24 (remote host) |
|   linux netns outside     |
 --------------------------- 

在linux内核上创建2个命令空间 inside和outside,然后在vpp中分别创建两个tap接口,tap1实现vpp和命名空间inside直接通信。tap2实现vpp和命名空间outside通信。vpp使能NAT功能,并配置路由前NAT(理论上路由后NAT也可以,本文完全参照官方文档配置实现),具体配置如下:

  • 内核创建2个命名空间
代码语言:javascript复制
ip netns add inside
ip netns add outside
  • 创建两个tap接口
代码语言:javascript复制
creat tap id 1 host-ns inside host-ip4-addr 10.0.0.2/24 host-ip4-gw 10.0.0.1 host-if-name tap1

creat tap id 2 host-ns outside host-ip4-addr 20.0.0.2/24 host-ip4-gw 20.0.0.1 host-if-name tap2


set interface state tap1 up
set interface ip address tap1 10.0.0.1/24
set interface state tap2 up
set interface ip address tap2 20.0.0.1/24

在创建tap接口时,需要指定设置内核tap1接口ip地址及默认网关。

  • NAT基本配置
代码语言:javascript复制
#启用nat插件
nat44 plugin enable sessions 1000
#配置内部接口
set interface nat44 in tap1
#配置外部接口
set interface nat44 out tap2
#设置nat地址池和twice-nat地址池
nat44 add address 192.168.60.101
nat44 add address 20.0.0.1 twice-nat

此处NAT地址池twice-nat地址池配置和VPP官方文档中设置正好是相反的。将外部访问地址设置到NAT地址池中,将内网接口设置到twice-nat地址池中。

  • 添加两次NAT的映射
代码语言:javascript复制
nat44 add static mapping tcp local 10.0.0.2 5201 external 192.168.160.101 5201 twice-nat 

这里需要注意,在官方文档中external 地址写的不是twice-nat地址池的地址,这里是存在问题的。上面静态NAT映射配置完之后,我们分别查询当前nat会话为空,flow-hash存在2条记录,如下所示:

代码语言:javascript复制
#查询静态配置
dpdk-vpp源码分析: show nat44 static mappings 
NAT44 static mappings:
 TCP local 10.0.0.2:5201 external 192.168.160.101:5201 vrf 0 twice-nat 
#查询会话表
dpdk-vpp源码分析: show nat44 sessions 
NAT44 ED sessions:
-------- thread 0 vpp_main: 0 sessions --------
#查询hash tables表
dpdk-vpp源码分析: show nat44 hash tables detail
Hash table 'ed-flow-hash'
[288]: heap offset 44083784, len 1, refcnt 2, linear 0
    0: local 10.0.0.2:5201 remote 0.0.0.0:0 proto TCP fib 0 thread-index 0 session-index 0
[875]: heap offset 44144832, len 1, refcnt 2, linear 0
    0: local 0.0.0.0:0 remote 192.168.160.101:5201 proto TCP fib 0 thread-index 0 session-index 0
    2 active elements 2 active buckets
    0 free lists
    0 linear search buckets
    heap: 1 chunk(s) allocated
          bytes: used 104k, scrap 0

配置完NAT映射表之后生成2个2个flow-hash表,其中875对应的是out2in反向;288对应的in2out方向。这两个表的作用就是用来生成NAT会话的。

接下来在inside命名空间开启IPerf3 服务端,然后在outside命名空间设置iperf3客户端连接服务器。连接正常建立。

NAT会话翻译如下:

从外到内翻译: 源地址:20.0.0.2 -> 192.168.160.101 目标地址:20.0.0.1 -> 10.0.0.2 从内到外的翻译: 源地址:10.0.0.2 -> 20.0.0.1 目标地址:192.168.160.101 -> 20.0.0.2

我们抓取了IPerf3建立tcp连接SYN交互trace流程:

代码语言:javascript复制
00:04:48:324556: virtio-input
  virtio: hw_if_index 2 next-index 4 vring 0 len 74
    hdr: flags 0x00 gso_type 0x00 hdr_len 0 gso_size 0 csum_start 0 csum_offset 0 num_buffers 1
00:04:48:324561: ethernet-input
  frame: flags 0x1, hw-if-index 2, sw-if-index 2
  IP4: 02:fe:79:00:b8:be -> 02:fe:b9:84:d0:66
00:04:48:324567: ip4-input
  TCP: 20.0.0.2 -> 192.168.160.101
    tos 0x00, ttl 64, length 60, checksum 0xbf15 dscp CS0 ecn NON_ECN
    fragment id 0x0697, flags DONT_FRAGMENT
  TCP: 52558 -> 5201
    seq. 0xd803aaad ack 0x00000000
    flags 0x02 SYN, tcp header: 40 bytes
    window 64240, checksum 0x539c
    options:
      mss 1460, window scale -139065856, timestamp -139065856, echo/reflected timestamp, sack permitted
00:04:48:324571: ip4-sv-reassembly-feature
  [not-fragmented]
00:04:48:324574: nat-pre-out2in
  out2in next_index 6 arc_next_index 10
00:04:48:324576: nat44-ed-out2in
  NAT44_OUT2IN_ED_FAST_PATH: sw_if_index 2, next index 7
  search key local 20.0.0.2:52558 remote 192.168.160.101:5201 proto TCP fib 0 thread-index 0 session-index 0
  slow path because lookup failed
00:04:48:324579: nat44-ed-out2in-slowpath
  NAT44_OUT2IN_ED_SLOW_PATH: sw_if_index 2, next index 10, session 1, translation result 'success' via o2if
  i2of match: saddr 10.0.0.2 sport 5201 daddr 20.0.0.1 dport 11570 proto TCP fib_idx 0 rewrite: saddr 192.168.160.101 sport 5201 daddr 20.0.0.2 dport 52558 
  o2if match: saddr 20.0.0.2 sport 52558 daddr 192.168.160.101 dport 5201 proto TCP fib_idx 0 rewrite: saddr 20.0.0.1 sport 11570 daddr 10.0.0.2 dport 5201 txfib 0 
  TCP state: closed
00:04:48:324594: ip4-lookup
  fib 0 dpo-idx 5 flow hash: 0x00000000
  TCP: 20.0.0.1 -> 10.0.0.2
    tos 0x00, ttl 64, length 60, checksum 0x1623 dscp CS0 ecn NON_ECN
    fragment id 0x0697, flags DONT_FRAGMENT
  TCP: 11570 -> 5201
    seq. 0xd803aaad ack 0x00000000
    flags 0x02 SYN, tcp header: 40 bytes
    window 64240, checksum 0x4ac6
    options:
      mss 1460, window scale -139065856, timestamp -139065856, echo/reflected timestamp, sack permitted
00:04:48:324597: ip4-rewrite
  tx_sw_if_index 1 dpo-idx 5 : ipv4 via 10.0.0.2 tap1: mtu:9000 next:4 flags:[] 02fe696ff93702fe051276900800 flow hash: 0x00000000
  00000000: 02fe696ff93702fe0512769008004500003c069740003f061723140000010a00
  00000020: 00022d321451d803aaad00000000a002faf04ac60000020405b40402
00:04:48:324599: tap1-output
  tap1 flags 0x00180005
  IP4: 02:fe:05:12:76:90 -> 02:fe:69:6f:f9:37
  TCP: 20.0.0.1 -> 10.0.0.2
    tos 0x00, ttl 63, length 60, checksum 0x1723 dscp CS0 ecn NON_ECN
    fragment id 0x0697, flags DONT_FRAGMENT
  TCP: 11570 -> 5201
    seq. 0xd803aaad ack 0x00000000
    flags 0x02 SYN, tcp header: 40 bytes
    window 64240, checksum 0x4ac6
    options:
      mss 1460, window scale -139065856, timestamp -139065856, echo/reflected timestamp, sack permitted
00:04:48:324601: tap1-tx
    buffer 0x9d232: current data 0, length 74, buffer-pool 0, ref-count 1, trace handle 0x11
                    l2-hdr-offset 0 l3-hdr-offset 14 
  hdr-sz 0 l2-hdr-offset 0 l3-hdr-offset 14 l4-hdr-offset 0 l4-hdr-sz 0
  IP4: 02:fe:05:12:76:90 -> 02:fe:69:6f:f9:37
  TCP: 20.0.0.1 -> 10.0.0.2
    tos 0x00, ttl 63, length 60, checksum 0x1723 dscp CS0 ecn NON_ECN
    fragment id 0x0697, flags DONT_FRAGMENT
  TCP: 11570 -> 5201
    seq. 0xd803aaad ack 0x00000000
    flags 0x02 SYN, tcp header: 40 bytes
    window 64240, checksum 0x4ac6
    options:
      mss 1460, window scale -139065856, timestamp -139065856, echo/reflected timestamp, sack permitted

Packet 19

00:04:48:324645: virtio-input
  virtio: hw_if_index 1 next-index 4 vring 0 len 74
    hdr: flags 0x00 gso_type 0x00 hdr_len 0 gso_size 0 csum_start 0 csum_offset 0 num_buffers 1
00:04:48:324649: ethernet-input
  frame: flags 0x1, hw-if-index 1, sw-if-index 1
  IP4: 02:fe:69:6f:f9:37 -> 02:fe:05:12:76:90
00:04:48:324654: ip4-input
  TCP: 10.0.0.2 -> 20.0.0.1
    tos 0x00, ttl 64, length 60, checksum 0x1cba dscp CS0 ecn NON_ECN
    fragment id 0x0000, flags DONT_FRAGMENT
  TCP: 5201 -> 11570
    seq. 0xf4b46973 ack 0xd803aaae
    flags 0x12 SYN ACK, tcp header: 40 bytes
    window 65160, checksum 0xab86
    options:
      mss 1460, window scale -139065856, timestamp -139065856, echo/reflected timestamp, sack permitted
00:04:48:324656: ip4-sv-reassembly-feature
  [not-fragmented]
00:04:48:324658: nat-pre-in2out
  in2out next_index 2 arc_next_index 10
00:04:48:324660: nat44-ed-in2out
  NAT44_IN2OUT_ED_FAST_PATH: sw_if_index 1, next index 10, session 1, translation result 'success' via i2of
  i2of match: saddr 10.0.0.2 sport 5201 daddr 20.0.0.1 dport 11570 proto TCP fib_idx 0 rewrite: saddr 192.168.160.101 sport 5201 daddr 20.0.0.2 dport 52558 
  o2if match: saddr 20.0.0.2 sport 52558 daddr 192.168.160.101 dport 5201 proto TCP fib_idx 0 rewrite: saddr 20.0.0.1 sport 11570 daddr 10.0.0.2 dport 5201 txfib 0 
  search key local 10.0.0.2:5201 remote 20.0.0.1:11570 proto TCP fib 0 thread-index 0 session-index 0
  TCP state: closed
00:04:48:324664: ip4-lookup
  fib 0 dpo-idx 4 flow hash: 0x00000000
  TCP: 192.168.160.101 -> 20.0.0.2
    tos 0x00, ttl 64, length 60, checksum 0xc5ac dscp CS0 ecn NON_ECN
    fragment id 0x0000, flags DONT_FRAGMENT
  TCP: 5201 -> 52558
    seq. 0xf4b46973 ack 0xd803aaae
    flags 0x12 SYN ACK, tcp header: 40 bytes
    window 65160, checksum 0xb45c
    options:
      mss 1460, window scale -139065856, timestamp -139065856, echo/reflected timestamp, sack permitted
00:04:48:324666: ip4-rewrite
  tx_sw_if_index 2 dpo-idx 4 : ipv4 via 20.0.0.2 tap2: mtu:9000 next:3 flags:[] 02fe7900b8be02feb984d0660800 flow hash: 0x00000000
  00000000: 02fe7900b8be02feb984d06608004500003c000040003f06c6acc0a8a0651400
  00000020: 00021451cd4ef4b46973d803aaaea012fe88b45c0000020405b40402
00:04:48:324668: tap2-output
  tap2 flags 0x00380005
  IP4: 02:fe:b9:84:d0:66 -> 02:fe:79:00:b8:be
  TCP: 192.168.160.101 -> 20.0.0.2
    tos 0x00, ttl 63, length 60, checksum 0xc6ac dscp CS0 ecn NON_ECN
    fragment id 0x0000, flags DONT_FRAGMENT
  TCP: 5201 -> 52558
    seq. 0xf4b46973 ack 0xd803aaae
    flags 0x12 SYN ACK, tcp header: 40 bytes
    window 65160, checksum 0xb45c
    options:
      mss 1460, window scale -139065856, timestamp -139065856, echo/reflected timestamp, sack permitted
00:04:48:324670: tap2-tx
    buffer 0x9f90b: current data 0, length 74, buffer-pool 0, ref-count 1, trace handle 0x12
                    natted l2-hdr-offset 0 l3-hdr-offset 14 
  hdr-sz 0 l2-hdr-offset 0 l3-hdr-offset 14 l4-hdr-offset 0 l4-hdr-sz 0
  IP4: 02:fe:b9:84:d0:66 -> 02:fe:79:00:b8:be
  TCP: 192.168.160.101 -> 20.0.0.2
    tos 0x00, ttl 63, length 60, checksum 0xc6ac dscp CS0 ecn NON_ECN
    fragment id 0x0000, flags DONT_FRAGMENT
  TCP: 5201 -> 52558
    seq. 0xf4b46973 ack 0xd803aaae
    flags 0x12 SYN ACK, tcp header: 40 bytes
    window 65160, checksum 0xb45c
    options:
      mss 1460, window scale -139065856, timestamp -139065856, echo/reflected timestamp, sack permitted

流量从远程主机发起,当NAT设备接收到SYN报文时,在out2in方向slow path处理节点生成了twice-nat的双向会话,会话如下:

代码语言:javascript复制
 i2o 10.0.0.2 proto TCP port 5201 fib 0
    o2i 192.168.160.101 proto TCP port 5201 fib 0
       external host o2i 20.0.0.2:52558 i2o 20.0.0.1:11570
       i2o flow: match: saddr 10.0.0.2 sport 5201 daddr 20.0.0.1 dport 11570 proto TCP fib_idx 0 
           rewrite: saddr 192.168.160.101 sport 5201 daddr 20.0.0.2 dport 52558 
       o2i flow: match: saddr 20.0.0.2 sport 52558 daddr 192.168.160.101 dport 5201 proto TCP fib_idx 0 
          rewrite: saddr 20.0.0.1 sport 11570 daddr 10.0.0.2 dport 5201 txfib 0 
       index 1
       last heard 290.68
       timeout in 12.25
       total pkts 147, total bytes 117745
       static translation
       twice-nat

至此,我们已深入学习了两次NAT(Twice NAT)的基础配置知识。然而,在遵循官方指南配置iperf3进行测试时,遇到了连接无法成功建立的问题。在此过程中,我辨识出配置指南中可能存在两处误导之处,并已在文中详细阐述了这些疑点。尽管通过一系列调试,功能现已正常运行,并且测试结果与期望的两次NAT效果相吻合,我个人对于修改后的配置准确性仍持保留意见,缺乏完全的自信,需要和vpp官方予以确认。

0 人点赞