Hello World
今天介紹一個比較繞口的技術。
故事的首先要從測試同學提的一個 BUG 開始
為什麼一個ipv4 地址前面會有::ffff:呢?是不是你的程式寫錯了呢。那我們來深究一下這個是什麼東西。
這種地址叫 ipv4 mapped ipv6。為什麼會有這麼奇怪的東西呢,與 ipv6 部署有關。
我們知道,ipb6 的地址空間範圍非常大。ipv6 改動不光涉及到網路層,例如配置 ipv6 的路由協議,還有應用層的調整,例如程式從 ipv4 監聽改成 ipv6 監聽。但是這個改動的成本巨大,又要涉及到程式碼的調整,又要涉及到地址處理的問題,例如是否監聽多協議等等等,於是有人發明了 ipv6 dual stack mode 技術。通俗來講就是
- 程式只需要監聽 ipv6,如果不做特殊設定(IPV6_ONLY) 那麼socket 同時附帶同時也監聽 ipv4。並且 ipv4 接受到的請求,自動轉換為 ipv6 的格式交給應用層處理
下面我們寫一個python 的小程式來看一下
import socket
import threading
def create_socket():
# 建立一個可以同時支援 IPv4 和 IPv6 的套接字
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
# 設定地址重用選項,允許快速重啟伺服器
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 設定 IPv6 only 選項為 False,允許 IPv4 客戶端連線
if hasattr(socket, 'IPV6_V6ONLY'):
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
return sock
def start_server():
addr = ("", 8080) # 監聽所有網路介面的 8080 埠
# 嘗試建立支援雙棧 IPv6 的套接字
if socket.has_dualstack_ipv6():
sock = socket.create_server(addr, family = socket.AF_INET6, dualstack_ipv6 = True)
else:
# 如果不支援雙棧 IPv6,則建立普通的 IPv6 套接字
sock = socket.create_server(addr)
sock.listen(5)
print("Server listening on 0.0.0.0:8080 (IPv4 and IPv6)")
while True:
conn, addr = sock.accept()
threading.Thread(target = handle_client, args = (conn, addr)).start()
def handle_client(conn, addr):
print(f"New connection from {addr[0]}:{addr[1]}")
try:
while True:
data = conn.recv(1024)
if not data:
break
print(f"Received: {data.decode()}")
conn.sendall(data) # 回顯收到的資料
except Exception as e:
print(f"Error handling client: {e}")
finally:
conn.close()
print(f"Connection closed with {addr[0]}:{addr[1]}")
if __name__ == "__main__":
start_server()
我們透過 nc 使用 ipv4 協議連結 python 的服務端,可以發現正常觸發了ffff::
並且這種監聽會被核心socket 標記為 tcp 46。當然,這裡還有一個重要區別,我們以 linux 為例,AP_ENABLE_V4_MAPPED 。這個引數決定了雙棧模式下,收到的 ipv4 地址是否翻譯為 ipv6 地址去展示。
但是這種技術不知為何鮮為人知,導致測試的同學和絕大多數研發都不知道,以為是 BUG。那我們怎麼解決呢?
-
關閉ipv6 雙棧
-
只監聽 ipv6
-
在 Linux 中,預設情況下,AP_ENABLE_V4_MAPPED 是 1,那麼 httpd 就會直接監聽 ipv6, 因為此時 ipv6 的 socket 能夠處理 ipv4 的請求;另外,bind() 系統呼叫會對使用者空間的程序透明處理 ipv6 沒有開啟的情況,此時會監聽到 ipv4。
而如果我們在編譯 httpd 的時候使用 --disable-v4-mapped 引數禁止 ipv4 mapped,那麼預設情況下, httpd 會分別監聽在 ipv4 和 ipv6,而非只監聽 ipv6
回到我們的業務程式碼,我們的業務是 Nodejs 開發。在 Nodejs 的 Issues 中,很多人都提到了 ipv6 dual stack 很容易誤導人,強烈要求社群關閉該功能。
Nodejs 的 socket 監聽再沒有指定協議的情況下,會使用 Ipv6 dual stack 監聽。並且與作業系統有關,Issues 中有人提到,Mac 會預設監聽 Tcp46。也就是 ipv4 的請求和 ipv6 的請求不會互相摻雜,但是 ubuntu 卻變成了 tcp6.
所以,大約在 21 年左右,Nodejs 終於支援了單獨設定 Ipv6Only 選項,如圖
https://github.com/nodejs/help/issues/4067#:~:text=https://nodejs.org/api/net.html#serverlisten
測試由於年少無知,亂提 BUG