成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

探索Docker默認(rèn)網(wǎng)絡(luò)NAT映射的分配與過濾行為

網(wǎng)絡(luò) 網(wǎng)絡(luò)管理
本文探討了Docker默認(rèn)網(wǎng)絡(luò)的NAT(網(wǎng)絡(luò)地址轉(zhuǎn)換)行為。我們通過構(gòu)建實驗環(huán)境,使用兩個自制程序(nat-hole-puncher和udp-client-addr-display)以及nc工具,來測試和分析Docker NAT的端口分配行為和過濾行為。

在《WebRTC第一課:網(wǎng)絡(luò)架構(gòu)與NAT工作原理》一文中,我們對WebRTC的網(wǎng)路架構(gòu)進行說明,了解到了NAT的工作原理、RFC 3489[2]對NAT的四種傳統(tǒng)分類以及較新的RFC 4787[3]中按分配行為和過濾行為對NAT行為的分類。

不過,“紙上得來終覺淺,絕知此事要躬行”,在這篇文章中,我打算選取一個具體的NAT實現(xiàn)進行案例研究(Case Study)。在市面上的NAT實現(xiàn)中,Docker容器的網(wǎng)絡(luò)NAT絕對是最容易獲得的一種實現(xiàn)。因此,我們將把Docker默認(rèn)網(wǎng)絡(luò)[4]的NAT實現(xiàn)機制作為本篇的研究對象,探索該NAT的分配行為和過濾行為,以確定Docker默認(rèn)網(wǎng)絡(luò)的NAT類型。

為了這次探索,我們首選需要構(gòu)建實驗網(wǎng)絡(luò)環(huán)境。

1. 構(gòu)建實驗環(huán)境

Docker默認(rèn)網(wǎng)絡(luò)使用NAT(網(wǎng)絡(luò)地址轉(zhuǎn)換)來允許容器訪問外部網(wǎng)絡(luò)。創(chuàng)建容器時,如果未指定網(wǎng)絡(luò)設(shè)置,容器會連接到默認(rèn)的"bridge"網(wǎng)絡(luò),并分配一個內(nèi)部IP地址(通常在172.17.0.0/16范圍內(nèi))。Docker在宿主機上創(chuàng)建一個虛擬網(wǎng)橋(docker0),作為容器與外部網(wǎng)絡(luò)的接口。當(dāng)容器嘗試訪問外部網(wǎng)絡(luò)時,使用源網(wǎng)絡(luò)地址轉(zhuǎn)換(SNAT),將內(nèi)部IP和端口轉(zhuǎn)換為宿主機的IP和一個隨機高位端口,以便與外部網(wǎng)絡(luò)通信。Docker通過配置iptables規(guī)則來實現(xiàn)這些NAT功能,處理數(shù)據(jù)包的轉(zhuǎn)發(fā)、地址轉(zhuǎn)換和過濾。

基于上述描述,我們用兩臺主機來構(gòu)建一個實驗環(huán)境,拓?fù)鋱D如下:

圖片圖片

從上圖可以看到:我們的實驗環(huán)境有兩臺主機:192.168.0.124和192.168.0.125。在124上,我們基于docker默認(rèn)網(wǎng)絡(luò)啟動一個容器,在該容器中放置一個用于NAT打洞驗證的nat-hole-puncher程序,該程序通過訪問192.168.0.125上的udp-client-addr-display程序在Docker的NAT上留下一個“洞”,然后我們在125上使用nc(natcat)工具[5]驗證是否可以通過這個洞向容器發(fā)送數(shù)據(jù)。

我們要確定Docker默認(rèn)網(wǎng)絡(luò)NAT的具體類型,需要進行一些測試來觀察其行為。具體來說,主要需要關(guān)注兩個方面:

  • 端口分配行為:觀察NAT是如何為內(nèi)部主機(容器)分配外部端口的。
  • 過濾行為:檢查NAT如何處理和過濾入站數(shù)據(jù)的,是否與源IP、源Port有關(guān)等。

接下來,我們來準(zhǔn)備一下驗證NAT類型需要的兩個程序:nat-hole-puncher和udp-client-addr-display。

2. 準(zhǔn)備nat-hole-puncher程序和udp-client-addr-display程序

下圖描述了nat-hole-puncher、udp-client-addr-display以及nc命令的交互流程:

圖片圖片

三者的交互流程在圖中已經(jīng)用文字標(biāo)記的十分清楚了。

根據(jù)該圖中的邏輯,我們分別實現(xiàn)一下nat-hole-puncher和udp-client-addr-display。

下面是nat-hole-puncher的源碼:

// docker-default-nat/nat-hole-puncher/main.go

package main

import (
 "fmt"
 "net"
 "os"
 "strconv"
)

func main() {
 if len(os.Args) != 5 {
  fmt.Println("Usage: nat-hole-puncher <local_ip> <local_port> <target_ip> <target_port>")
  return
 }

 localIP := os.Args[1]
 localPort := os.Args[2]
 targetIP := os.Args[3]
 targetPort := os.Args[4]

 // 向target_ip:target_port發(fā)送數(shù)據(jù)
 err := sendUDPMessage("Hello, World!", localIP, localPort, targetIP+":"+targetPort)
 if err != nil {
  fmt.Println("Error sending message:", err)
  return
 }
 fmt.Println("sending message to", targetIP+":"+targetPort, "ok")

 // 向target_ip:target_port+1發(fā)送數(shù)據(jù)
 p, _ := strconv.Atoi(targetPort)
 nextTargetPort := fmt.Sprintf("%d", p+1)
 err = sendUDPMessage("Hello, World!", localIP, localPort, targetIP+":"+nextTargetPort)
 if err != nil {
  fmt.Println("Error sending message:", err)
  return
 }
 fmt.Println("sending message to", targetIP+":"+nextTargetPort, "ok")

 // 重新監(jiān)聽local addr
 startUDPReceiver(localIP, localPort)
}

func sendUDPMessage(message, localIP, localPort, target string) error {
 addr, err := net.ResolveUDPAddr("udp", target)
 if err != nil {
  return err
 }

 lport, _ := strconv.Atoi(localPort)
 conn, err := net.DialUDP("udp", &net.UDPAddr{
  IP:   net.ParseIP(localIP),
  Port: lport,
 }, addr)
 if err != nil {
  return err
 }
 defer conn.Close()

 // 發(fā)送數(shù)據(jù)
 _, err = conn.Write([]byte(message))
 if err != nil {
  return err
 }

 return nil
}

func startUDPReceiver(ip, port string) {
 addr, err := net.ResolveUDPAddr("udp", ip+":"+port)
 if err != nil {
  fmt.Println("Error resolving address:", err)
  return
 }

 conn, err := net.ListenUDP("udp", addr)
 if err != nil {
  fmt.Println("Error listening:", err)
  return
 }
 defer conn.Close()
 fmt.Println("listen address:", ip+":"+port, "ok")

 buf := make([]byte, 1024)
 for {
  n, senderAddr, err := conn.ReadFromUDP(buf)
  if err != nil {
   fmt.Println("Error reading:", err)
   return
  }
  fmt.Printf("Received message: %s from %s\n", string(buf[:n]), senderAddr.String())
 }
}

我們將其編譯完打到鏡像中去,Makefile和Dockerfile如下:

// docker-default-nat/nat-hole-puncher/Makefile

all:
 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o nat-hole-puncher main.go
image:
 docker build -t nat-hole-puncher .

// docker-default-nat/nat-hole-puncher/Dockerfile

# 使用 Alpine 作為基礎(chǔ)鏡像
FROM alpine:latest

# 創(chuàng)建工作目錄
WORKDIR /app

# 復(fù)制已編譯的可執(zhí)行文件到鏡像中
COPY nat-hole-puncher .

# 設(shè)置文件權(quán)限
RUN chmod +x nat-hole-puncher

執(zhí)行構(gòu)建和打鏡像命令:

$ make
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o nat-hole-puncher main.go
$ make image
docker build -t nat-hole-puncher .
[+] Building 0.7s (9/9) FINISHED                                                                                   docker:default
 => [internal] load .dockerignore                                                                                            0.0s
 => => transferring context: 2B                                                                                              0.0s
 => [internal] load build definition from Dockerfile                                                                         0.0s
 => => transferring dockerfile: 265B                                                                                         0.0s
 => [internal] load metadata for docker.io/library/alpine:latest                                                             0.0s
 => [1/4] FROM docker.io/library/alpine:latest                                                                               0.0s
 => [internal] load build context                                                                                            0.0s
 => => transferring context: 2.70MB                                                                                          0.0s
 => CACHED [2/4] WORKDIR /app                                                                                                0.0s
 => [3/4] COPY nat-hole-puncher .                                                                                            0.2s
 => [4/4] RUN chmod +x nat-hole-puncher                                                                                      0.3s
 => exporting to image                                                                                                       0.1s
 => => exporting layers                                                                                                      0.1s
 => => writing image sha256:fec6c105f36b1acce5e3b0a5fb173f3cac5c700c2b07d1dc0422a5917f934530                                 0.0s
 => => naming to docker.io/library/nat-hole-puncher                                                                          0.0s

接下來,我們再來看看udp-client-addr-display源碼:

// docker-default-nat/udp-client-addr-display/main.go
package main

import (
 "fmt"
 "net"
 "os"
 "strconv"
 "sync"
)

func main() {
 if len(os.Args) != 3 {
  fmt.Println("Usage: udp-client-addr-display <local_ip> <local_port>")
  return
 }

 localIP := os.Args[1]
 localPort := os.Args[2]

 var wg sync.WaitGroup
 wg.Add(2)

 go func() {
  defer wg.Done()
  startUDPReceiver(localIP, localPort)
 }()

 go func() {
  defer wg.Done()
  p, _ := strconv.Atoi(localPort)
  nextLocalPort := fmt.Sprintf("%d", p+1)
  startUDPReceiver(localIP, nextLocalPort)
 }()

 wg.Wait()
}

func startUDPReceiver(localIP, localPort string) {
 addr, err := net.ResolveUDPAddr("udp", localIP+":"+localPort)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }

 conn, err := net.ListenUDP("udp", addr)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }
 defer conn.Close()

 buf := make([]byte, 1024)

 n, clientAddr, err := conn.ReadFromUDP(buf)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }

 fmt.Printf("Received message: %s from %s\n", string(buf[:n]), clientAddr.String())
}

現(xiàn)在兩個程序都就緒了,接下來我們就開始我們的探索。

3. 探索步驟

我們先在192.168.0.125上啟動udp-client-addr-display,監(jiān)聽6000和6001 UDP端口:

// 在192.168.0.125上執(zhí)行

$./udp-client-addr-display 192.168.0.125 6000

然后在192.168.0.124上創(chuàng)建client1容器:

// 在192.168.0.124上執(zhí)行
$docker run -d --name client1 nat-hole-puncher:latest sleep infinity
eeebc0fbe3c7d56e7f43cd5af19a18e65a703b3f987115c521e81bb8cdc6c0be

獲取client1容器的IP地址:

// 在192.168.0.124上執(zhí)行
$docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' client1
172.17.0.5

啟動client1容器中的nat-hole-puncher程序,綁定本地5000端口,然后向192.168.0.125的6000和6001端口發(fā)送數(shù)據(jù)包:

$ docker exec client1 /app/nat-hole-puncher 172.17.0.5 5000 192.168.0.125 6000
sending message to 192.168.0.125:6000 ok
sending message to 192.168.0.125:6001 ok
listen address: 172.17.0.5:5000 ok

之后,我們會在125的udp-client-addr-display輸出中看到如下結(jié)果:

./udp-client-addr-display 192.168.0.125 6000
Received message: Hello, World! from 192.168.0.124:5000
Received message: Hello, World! from 192.168.0.124:5000

通過這個結(jié)果我們得到了NAT映射后的源地址和端口:192.168.0.124:5000。

現(xiàn)在我們在125上用nc程序向該映射后的地址發(fā)送三個UDP包:

$ echo "hello from 192.168.0.125:6000" | nc -u -p 6000 -v 192.168.0.124 5000
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 192.168.0.124:5000.
Ncat: 30 bytes sent, 0 bytes received in 0.01 seconds.

$ echo "hello from 192.168.0.125:6001" | nc -u -p 6001 -v 192.168.0.124 5000
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 192.168.0.124:5000.
Ncat: 30 bytes sent, 0 bytes received in 0.01 seconds.

$ echo "hello from 192.168.0.125:6002" | nc -u -p 6002 -v 192.168.0.124 5000
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 192.168.0.124:5000.
Ncat: 30 bytes sent, 0 bytes received in 0.01 seconds.

在124上,我們看到nat-hole-puncher程序輸出如下結(jié)果:

Received message: hello from 192.168.0.125:6000
 from 192.168.0.125:6000
Received message: hello from 192.168.0.125:6001
 from 192.168.0.125:6001

4. 探索后的結(jié)論

通過上面的執(zhí)行步驟以及輸出的結(jié)果,我們從端口分配行為和過濾行為這兩方面分析一下Docker默認(rèn)網(wǎng)絡(luò)NAT的行為特征。

首先,我們先來看端口分配行為。

在上面的探索步驟中,我們先后執(zhí)行了:

  • 172.17.0.5:5000 -> 192.168.0.125:6000
  • 172.17.0.5:5000 -> 192.168.0.125:6001

但從udp-client-addr-display的輸出來看:

Received message: Hello, World! from 192.168.0.124:5000
Received message: Hello, World! from 192.168.0.124:5000

Docker默認(rèn)網(wǎng)絡(luò)的NAT的端口分配行為肯定不是Address and Port-Dependent Mapping,那么到底是不是Address-Dependent Mapping的呢?你可以將nat-hole-puncher/main.go中的startUDPReceiver調(diào)用注釋掉,然后再在另外一臺機器192.168.0.126上啟動一個udp-client-addr-display(監(jiān)聽7000和7001),然后在124上分別執(zhí)行:

$ docker exec client1 /app/nat-hole-puncher 172.17.0.5 5000 192.168.0.125 6000
sending message to 192.168.0.125:6000 ok
sending message to 192.168.0.125:6001 ok

$ docker exec client1 /app/nat-hole-puncher 172.17.0.4 5000 192.168.0.126 7000
sending message to 192.168.0.126:7000 ok
sending message to 192.168.0.126:7001 ok

而從125和126上的udp-client-addr-display的輸出來看:

//125:
./udp-client-addr-display 192.168.0.125 6000
Received message: Hello, World! from 192.168.0.124:5000
Received message: Hello, World! from 192.168.0.124:5000

//126:
 ./udp-client-addr-display 192.168.0.126 7000
Received message: Hello, World! from 192.168.0.124:5000
Received message: Hello, World! from 192.168.0.124:5000

可以看出:即便是target ip不同,只要源ip+port一致,NAT也只會分配同一個端口(這里是5000),顯然在端口分配行為上,Docker默認(rèn)網(wǎng)絡(luò)的NAT是Endpoint-Independent Mapping類型的!

我們再來看過濾行為。nat-hole-puncher在NAT打洞后,我們在125上使用nc工具向該“洞”發(fā)UDP包,結(jié)果是只有nat-hole-puncher發(fā)過的目的ip和端口(比如6000和6001)才可以成功將數(shù)據(jù)通過“洞”發(fā)給nat-hole-puncher。換個端口(比如6002),數(shù)據(jù)都會被丟棄掉。即便我們沒有測試從不同IP向“洞”發(fā)送udp數(shù)據(jù),但上述過濾行為已經(jīng)足夠讓我們判定Docker默認(rèn)網(wǎng)絡(luò)的NAT過濾行為屬于Address and Port-Dependent Filtering。

綜合上述兩個行為特征,如果按照傳統(tǒng)NAT類型劃分,Docker默認(rèn)網(wǎng)絡(luò)的NAT應(yīng)該屬于端口受限錐形。

5. 小結(jié)

本文探討了Docker默認(rèn)網(wǎng)絡(luò)的NAT(網(wǎng)絡(luò)地址轉(zhuǎn)換)行為。我們通過構(gòu)建實驗環(huán)境,使用兩個自制程序(nat-hole-puncher和udp-client-addr-display)以及nc工具,來測試和分析Docker NAT的端口分配行為和過濾行為。

主要的探索結(jié)論如下:

  • 端口分配行為:Docker默認(rèn)網(wǎng)絡(luò)的NAT表現(xiàn)為Endpoint-Independent Mapping類型。即無論目標(biāo)IP和端口如何變化,只要源IP和端口相同,NAT就會分配相同的外部端口。
  • 過濾行為:Docker默認(rèn)網(wǎng)絡(luò)的NAT表現(xiàn)為Address and Port-Dependent Filtering類型。只有之前通信過的特定IP和端口組合才能成功穿透NAT發(fā)送數(shù)據(jù)包到內(nèi)部網(wǎng)絡(luò)。

基于這兩種行為特征,我們可以得出結(jié)論:按照傳統(tǒng)NAT類型劃分,Docker默認(rèn)網(wǎng)絡(luò)的NAT屬于端口受限錐形(Port Restricted Cone)NAT。

不過,在真正實踐中判斷一個NAT的類型無需如此費勁,RFC3489給出檢測NAT類型(傳統(tǒng)四種類別)的流程圖[6]:

圖片圖片

github上也有上述算法的開源的實現(xiàn),比如:pystun3[7]。下面是利用pystun3檢測網(wǎng)絡(luò)NAT類型的方法:

$docker run -it python:3-alpine /bin/sh
/ # pip install pystun3
/ # pystun3 
NAT Type: Symmetric NAT
External IP: xxx.xxx.xxx.xxx
External Port: yyyy

注:這里pystun3的檢測結(jié)果是多層NAT的結(jié)果,并非單純的Docker默認(rèn)網(wǎng)絡(luò)的NAT類型。

本文涉及的源碼可以在這里[8]下載 - https://github.com/bigwhite/experiments/blob/master/docker-default-nat

參考資料

[1] WebRTC第一課:網(wǎng)絡(luò)架構(gòu)與NAT工作原理: https://tonybai.com/2024/11/27/webrtc-first-lesson-network-architecture-and-how-nat-work/

[2] RFC 3489: https://datatracker.ietf.org/doc/html/rfc3489

[3] RFC 4787: https://datatracker.ietf.org/doc/html/rfc4787

[4] Docker默認(rèn)網(wǎng)絡(luò): https://tonybai.com/2016/01/15/understanding-container-networking-on-single-host/

[5] nc(natcat)工具: https://man.openbsd.org/nc.1

[6] RFC3489給出檢測NAT類型(傳統(tǒng)四種類別)的流程圖: https://www.rfc-editor.org/rfc/rfc3489#section-10.2

[7] pystun3: https://github.com/talkiq/pystun3

[8] 這里: https://github.com/bigwhite/experiments/blob/master/docker-default-nat

責(zé)任編輯:武曉燕 來源: TonyBai
相關(guān)推薦

2024-12-05 10:42:51

網(wǎng)絡(luò)架構(gòu)NAT

2011-03-17 13:55:23

iptablesNAT端口映射

2021-11-29 05:32:47

內(nèi)存規(guī)避安全工具惡意軟件

2013-10-12 13:01:51

Linux運維內(nèi)存管理

2022-07-28 16:47:32

漏洞網(wǎng)絡(luò)安全風(fēng)險

2011-04-21 15:36:33

微博管理方案過濾不良行為

2009-09-03 10:26:07

C#修改DataRea

2020-05-08 17:05:11

VMware網(wǎng)絡(luò)NAT

2024-12-05 12:01:09

2023-12-06 09:52:07

2012-02-22 10:36:35

OpenFlowOpenFlow網(wǎng)絡(luò)

2010-09-01 16:36:20

DHCPNAT

2020-01-07 13:34:03

網(wǎng)絡(luò)攻擊惡意軟件網(wǎng)絡(luò)安全

2016-08-04 15:18:51

2017-11-02 07:03:26

網(wǎng)絡(luò)SDNEONs

2011-03-15 09:59:52

2010-08-26 21:52:55

DHCP分配

2016-05-31 10:11:51

2016-08-23 09:16:46

Docker鏡像容器

2011-09-28 10:17:54

點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 中文在线观看视频 | 在线观看视频一区 | 日韩在线精品 | a视频在线播放 | 成人精品鲁一区一区二区 | 一级免费看片 | 韩日免费视频 | 91免费在线| 一区二区日本 | 久久久成人免费一区二区 | 日韩www视频 | 国产一区不卡 | 国产偷录视频叫床高潮对白 | 国产h视频 | 亚洲色图插插插 | 婷婷久久综合 | 中文成人在线 | 色综合一区二区三区 | 亚洲一区二区三区在线视频 | 91中文视频| 亚洲一区视频在线播放 | 91国内精品久久 | 三级成人片 | 91.xxx.高清在线 | 狠狠爱一区二区三区 | 天堂亚洲网 | 成人免费网站视频 | 伊人免费网 | 国产美女h视频 | 九九在线 | 日韩精品在线一区 | 国产精品不卡 | 黄色精品 | 黄色一级特级片 | 亚洲精品高清视频 | 欧美日产国产成人免费图片 | 日韩精品一区二区三区在线播放 | 成人av免费在线观看 | 日本精品久久久久久久 | 激情 婷婷 | 国产精品国产精品国产专区不片 |