1 điểm bởi GN⁺ 2025-01-06 | 1 bình luận | Chia sẻ qua WhatsApp
  • NAT Traversal là công nghệ nền tảng cho phép các thiết bị phía sau NAT và tường lửa trao đổi trực tiếp các gói UDP, nhờ đó Tailscale có thể kết nối các đường hầm WireGuard mà không cần hub trung tâm
  • Tiền đề cốt lõi là giao thức phải dựa trên UDP, và phải có thể gửi/nhận các gói dùng để dò NAT cũng như các gói truyền thông thực tế trên cùng một socket mạng
  • Tường lửa có trạng thái chỉ cho phép các phản hồi khớp với gói UDP đã đi ra trước đó, nên nếu các peer biết ip:port của nhau và gửi gói gần như đồng thời, chúng có thể mở trạng thái tường lửa
  • Vì NAT thay đổi IP nguồn và cổng nguồn, cần các kỹ thuật bổ trợ như STUN, ánh xạ cổng, xử lý NAT64, dò cổng dựa trên nghịch lý ngày sinh, và relay
  • ICE thử đồng thời các đường đi ứng viên khả dĩ để chọn đường tốt nhất; Tailscale thì kết nối ngay qua DERP relay, rồi chuyển đổi một cách trong suốt khi tìm thấy đường trực tiếp tốt hơn

Điều kiện cơ bản của NAT Traversal

  • Mục tiêu là tạo ra luồng gói UDP hai chiều giữa hai thiết bị, để các giao thức như WireGuard, QUIC, WebRTC có thể chạy bên trên
  • Nếu tự triển khai trực tiếp, có hai điều kiện quan trọng
    • Giao thức phải dựa trên UDP
      • TCP cũng có thể làm được nhưng độ phức tạp cao, và tùy cách triển khai có thể cần sửa kernel
      • Nếu cần kết nối hướng luồng, có thể cân nhắc QUIC, vốn chạy trên UDP
    • Chương trình phải trực tiếp kiểm soát socket mạng dùng để gửi và nhận gói
      • NAT Traversal cần gửi thêm các gói ngoài giao thức chính, nên khó chỉ đơn giản gắn thêm vào thư viện mạng sẵn có
      • Một cấu trúc trong đó logic NAT Traversal và giao thức chính chia sẻ cùng socket và chạy song song sẽ hữu ích
  • Nếu khó truy cập trực tiếp socket, có thể đặt một proxy cục bộ
    • Giao thức gốc giao tiếp với proxy
    • Proxy chịu trách nhiệm NAT Traversal và relay gói tới peer

Xuyên qua tường lửa có trạng thái

  • Tường lửa có trạng thái ghi nhớ các gói đã thấy trước đó để quyết định có cho phép gói mới hay không
    • Có các dạng như Windows Defender firewall, Ubuntu ufw, BSD pf, pf của macOS, AWS Security Groups
    • Cấu hình phổ biến là cho phép mọi kết nối outbound và chặn mọi kết nối inbound
  • Với UDP, quy tắc khá đơn giản
    • Nếu tường lửa thấy một gói UDP đi từ 2.2.2.2:1234 đến 5.5.5.5:5678, nó sẽ cho phép gói đi ngược lại từ 5.5.5.5:5678 vào 2.2.2.2:1234
    • Một số tường lửa lỏng lẻo có thể cho phép lưu lượng từ bất cứ đâu đi vào một cổng cục bộ từng giao tiếp một lần, nhưng kiểu này ngày càng hiếm
  • Trong mô hình server-client, vấn đề không lớn vì thiết bị phía sau tường lửa chỉ cần chủ động bắt đầu kết nối trước
    • Với VPN, đây trở thành cấu trúc hub-and-spoke, trong đó hub không có tường lửa còn các spoke nằm phía sau tường lửa
  • Khi hai client muốn giao tiếp trực tiếp, sẽ phát sinh tình huống tường lửa hai bên chặn lẫn nhau
    • Cả hai bên đều phải gửi ra trước thì mới nhận được phản hồi, nhưng bên kia cũng ở trong cùng điều kiện
    • Cách để người dùng tự cấu hình mở cổng thì bất tiện, và kém khả năng mở rộng trong các mạng mesh như Tailscale
    • Cũng có nhiều tường lửa người dùng không thể kiểm soát, như router ở sân bay hoặc quán cà phê
  • Mấu chốt của giải pháp là quy tắc tường lửa UDP không xác minh quan hệ phản hồi thực sự, mà chỉ nhìn vào tổ hợp IP và cổng
    • Nếu hai peer biết trước ip:port của đối phương và đồng thời gửi gói UDP, một vài gói ban đầu có thể bị chặn nhưng trạng thái tường lửa sẽ được mở
    • Sau đó các gói do phía bên kia gửi trông giống như phản hồi và sẽ đi qua
  • Cách này cần một kênh phụ
    • Hai endpoint phải thử giao tiếp gần như đồng thời
    • Chỉ cần một đường truyền cho phép chậm vài giây và truyền được vài nghìn byte là đủ
    • WebRTC yêu cầu kênh signaling, còn Tailscale dùng coordination server và máy chủ DERP làm kênh phụ
  • Trạng thái tường lửa không tồn tại vĩnh viễn
    • Giá trị phổ biến của timeout phiên UDP là 30 giây
    • Để duy trì kết nối, cần gửi gói định kỳ hoặc khởi động lại kết nối bằng cơ chế out-of-band khi cần
  • Ngay cả khi có nhiều lớp tường lửa có trạng thái, nếu chúng cho phép outbound thì vẫn có thể vượt qua bằng cách gửi đồng thời

NAT làm vấn đề khó hơn như thế nào

  • NAT (Network Address Translator) hoạt động giống tường lửa có trạng thái, đồng thời còn thay đổi cả địa chỉ IP hoặc cổng của gói
  • Vấn đề trong NAT Traversal chủ yếu là Source NAT (SNAT)
    • SNAT cho phép nhiều thiết bị chia sẻ số lượng địa chỉ IP ít hơn, thường là một địa chỉ IPv4 công cộng
    • DNAT cũng tồn tại, nhưng ít liên quan đến vấn đề NAT Traversal đang bàn ở đây
  • Ví dụ, nếu laptop gửi gói UDP từ 192.168.0.20:1234 tới server Internet 7.7.7.7:5678, router gia đình sẽ chọn một cổng trống trên IP công cộng, chẳng hạn 2.2.2.2:4242
    • Router tạo một NAT mapping cho biết 192.168.0.20:12342.2.2.2:4242 tương ứng với nhau
    • Các gói đi ra sau đó được sửa như thể đến từ 2.2.2.2:4242
    • Phản hồi đi vào được sửa ngược lại thành 192.168.0.20:1234
  • Trong mạng doanh nghiệp, nguyên lý tương tự cũng được áp dụng
    • Khác biệt là tầng NAT có thể gồm nhiều thiết bị để đảm bảo tính sẵn sàng cao hoặc dung lượng, và có thể có nhiều IP công cộng

STUN và phát hiện ánh xạ NAT

  • Peer phía sau NAT không thể biết ip:port công cộng của chính nó mà đối phương sẽ nhìn thấy, và ánh xạ NAT thường chỉ được tạo khi có lưu lượng đi ra Internet
  • STUN là giao thức giúp client phía sau NAT biết nó trông như thế nào trên Internet
    • Client hỏi máy chủ STUN: “endpoint của tôi trông như thế nào từ phía bạn?”
    • Máy chủ STUN trả lời bằng ip:port công cộng mà gói UDP đến từ đó
  • Nếu chia sẻ ip:port công cộng do STUN cho biết với peer, có thể áp dụng kỹ thuật gửi đồng thời đã dùng để xuyên tường lửa
  • Đây cũng là lý do logic NAT Traversal và giao thức truyền thông thực tế phải dùng cùng một socket
    • Mỗi socket tạo ra một ánh xạ khác nhau trên thiết bị NAT
    • Nếu thực hiện STUN bằng một socket khác với socket sẽ dùng cho truyền thông thực tế, ta sẽ nhận được một ip:port vô dụng
  • Chỉ STUN thôi không thể xử lý mọi loại NAT
    • Nó có thể hoạt động với hầu hết router gia đình
    • Nó có thể thất bại với một số NAT gateway doanh nghiệp
    • Giả định rằng 2.2.2.2:4242 mà STUN nhìn thấy có cùng ý nghĩa trên toàn Internet không phải lúc nào cũng đúng

NAT dễ và NAT khó

  • Thiết bị NAT có thể tạo ánh xạ khác nhau tùy theo đích, hoặc giữ cùng một ánh xạ bất kể đích là gì
  • RFC 4787 gọi dạng dễ, trong đó ánh xạ được giữ bất kể đích, là Endpoint-Independent Mapping (EIM)
  • Dạng khó, trong đó ánh xạ thay đổi theo đích, là Endpoint-Dependent Mapping (EDM)
    • Nó có thể thay đổi chỉ dựa trên IP đích, hoặc dựa trên cả IP đích và cổng đích
    • Từ góc nhìn NAT Traversal, cả hai đều không tốt
  • Các thuật ngữ cũ như Full Cone, Restricted Cone, Port-Restricted Cone, Symmetric NAT trộn lẫn hành vi ánh xạ NAT và hành vi tường lửa
    • Trong triển khai thực tế, việc phân biệt “Symmetric so với phần còn lại” hoặc EIM so với EDM quan trọng hơn
  • Kỹ thuật gửi đồng thời có thể vượt qua nhiều dạng tường lửa
    • Trong môi trường thực tế, tường lửa phụ thuộc vào IP và cổng chiếm áp đảo
    • Tuy nhiên, chỉ cần có một hard NAT ở đâu đó trên đường đi thì chỉ STUN và gửi đồng thời sẽ gặp vấn đề

Relay khi kết nối trực tiếp thất bại

  • Kết nối trực tiếp có thể thất bại dù đã dùng mọi kỹ thuật
    • NAT khó xử lý, hoặc trong các mạng chặn UDP outbound trừ DNS như Wi-Fi khách của UC Berkeley, các kỹ thuật NAT không thể giải quyết được
  • Trong trường hợp này, hai bên có thể trao đổi gói tin thông qua một relay mà cả hai đều truy cập được
    • Không tốt bằng kết nối trực tiếp, nhưng nếu relay đủ gần trên đường truyền và có đủ băng thông, mức suy giảm chất lượng kết nối có thể không lớn
    • Dù độ trễ tăng hoặc băng thông giảm, vẫn tốt hơn là không có kết nối nào
  • Giao thức relay truyền thống là TURN
    • Client xác thực với máy chủ TURN
    • Máy chủ TURN cấp phát ip:port dùng cho relay
    • Peer giao tiếp tới ip:port đó
  • Tailscale tạo ra DERP(Detoured Encrypted Routing Protocol) thay cho TURN
    • DERP hoạt động trên HTTP
    • Hữu ích trong các mạng có quy tắc outbound nghiêm ngặt
    • Relay payload đã mã hóa dựa trên khóa công khai của đích
  • DERP đảm nhiệm hai vai trò
    • Relay dữ liệu khi NAT Traversal thất bại
    • Kênh phụ trợ giúp NAT Traversal
  • Nếu triển khai STUN, gửi đồng thời và cả relay, ước tính hơn 90% trường hợp có thể kết nối trực tiếp, còn relay luôn có thể đảm bảo một dạng kết nối nào đó

Kỹ thuật bổ sung cho Hard NAT

  • Với hard NAT, peer ở phía dễ hơn không biết NAT ở phía khó đã mở cổng nào
    • Có thể giả định IP nhìn chung là đúng nhờ STUN
    • Thứ không biết là cổng, với 65.535 giá trị khả dĩ
  • Nếu đơn giản quét toàn bộ các cổng, ở mức 100 gói/giây, trường hợp xấu nhất mất khoảng 10 phút và trông giống như quét cổng
  • Có thể giảm chi phí dò tìm bằng nghịch lý sinh nhật
    • Phía hard NAT mở 256 cổng bằng 256 socket, còn phía NAT dễ dò các cổng đích ngẫu nhiên
    • Nếu giả định có 256 cổng đang mở, xác suất thành công như sau
      • 174 lần dò ngẫu nhiên: 50%
      • 256 lần dò ngẫu nhiên: 64%
      • 1024 lần dò ngẫu nhiên: 98%
      • 2048 lần dò ngẫu nhiên: 99,9%
    • Với 100 cổng/giây, một nửa sẽ vượt qua trong vòng 2 giây; khoảng 20 giây thì gần như thành công dù mới dò dưới 4% toàn bộ không gian
  • Nếu cả hai bên đều là hard NAT thì khó hơn nhiều
    • Lúc này cặp {source port, destination port} phải khớp
    • Trong cùng điều kiện, xác suất thành công sau 20 giây là 0,01%
    • Để đạt xác suất thành công 99,9%, hai bên mỗi bên phải gửi 170.000 probe, và với 100 gói/giây sẽ mất 28 phút
  • Phương pháp này có thể cải thiện khả năng kết nối trong các kịch bản home-office, home-cloud, một số office-cloud hoặc cloud-cloud
    • Router gia đình thường là NAT dễ, còn hard NAT thường là router văn phòng hoặc NAT gateway trên cloud

Giao thức ánh xạ cổng

  • Có các giao thức yêu cầu trực tiếp NAT “hãy chuyển tiếp cổng WAN này tới ip:port LAN này”
  • Ba giao thức tiêu biểu như sau
    • UPnP IGD: giao thức xuất hiện từ cuối thập niên 1990, dùng các công nghệ như XML, SOAP và multicast HTTP trên UDP; khó triển khai và bảo mật
    • NAT-PMP: NAT Port Mapping Protocol do Apple tạo ra, chỉ thực hiện port forwarding và khá đơn giản
    • PCP: dạng NAT-PMP v2 phát triển thành Port Control Protocol
  • Có thể thử UPnP IGD, NAT-PMP, PCP với default gateway cục bộ; nếu có phản hồi thì có thể yêu cầu ánh xạ cổng công khai
    • Nếu thành công, không chỉ biết được ip:port công khai giống STUN, mà còn có thể khiến NAT hành xử dễ dãi hơn với cổng đó
    • Bất kỳ gói tin đến từ đâu, miễn tới cổng đã ánh xạ, đều được chuyển tiếp vào thiết bị bên trong
  • Không thể phụ thuộc vào các giao thức này
    • Thiết bị có thể không triển khai chúng
    • Chúng có thể bị tắt theo mặc định
    • Chúng có thể bị vô hiệu hóa theo chính sách
  • Do các lỗ hổng trước đây của UPnP, có trường hợp nó bị tắt theo chính sách
    • Một số thiết bị còn dùng một ô chọn “UPnP” duy nhất để tắt cùng lúc UPnP, NAT-PMP và PCP
  • Nếu dùng được, một NAT trên đường dữ liệu về cơ bản biến mất, khiến việc kết nối dễ hơn

Double NAT và CGNAT

  • Trong double NAT, nơi có hai lớp NAT trước một thiết bị, hành vi của NAT bên ngoài, tức NAT ngay trước Internet, là quan trọng nhất
    • Giống như nhiều lớp tường lửa stateful, các lớp NAT bổ sung nhìn chung không thấy được
    • Các kỹ thuật hiện có có thể hoạt động bất kể số lớp NAT
  • Thứ mà double NAT làm hỏng nặng là các giao thức ánh xạ cổng
    • Ánh xạ cổng tác động lên lớp NAT gần client nhất
    • Nhưng thứ peer từ xa phải đi qua lại là NAT ngoài cùng
    • ip:port thu được là địa chỉ của mạng trung gian nên peer từ xa không thể truy cập
  • Double NAT không hiển hiện với hầu hết ứng dụng thông thường không thực hiện NAT Traversal tường minh
    • Tuy nhiên nó có thể làm xấu trải nghiệm multiplayer của nhiều game, và loại bỏ IPv6, làm giảm lựa chọn kết nối không cần NAT
  • CGNAT(Carrier-Grade NAT) là cấu trúc trong đó ISP áp dụng thêm một lớp SNAT để giải quyết tình trạng thiếu địa chỉ IPv4
    • Router gia đình SNAT thiết bị sang IP trung gian
    • Lớp NAT thứ hai trong mạng ISP ánh xạ các IP trung gian sang ít IP công khai hơn
  • Với CGNAT, người dùng không thể thiết lập lại NAT của ISP
    • Trước đây, người dùng nâng cao có thể tránh vấn đề bằng port forwarding trên router gia đình, nhưng với CGNAT cách đó bị chặn
  • CGNAT về cơ bản cũng là double NAT nên phần lớn kỹ thuật hiện có vẫn tiếp tục hoạt động
    • Giao thức ánh xạ cổng là ngoại lệ có giới hạn

Vấn đề Hairpinning

  • Hai peer cùng nằm sau một CGNAT nhưng sau hai NAT gia đình khác nhau sẽ gặp một vấn đề đặc biệt
    • Máy chủ STUN cho biết ip:port công khai nhìn từ ngoài Internet
    • Nhưng thứ hai peer thực sự cần là ip:port hoạt động được trong mạng trung gian bên trong CGNAT
  • Nếu ít nhất một NAT gia đình hỗ trợ giao thức ánh xạ cổng, kết nối có thể trở nên dễ hơn
    • Do double NAT, việc giao thức ánh xạ cổng cho biết ip:port của mạng trung gian lại trở nên hữu ích
  • Nếu không dùng được ánh xạ cổng thì cần hairpinning
    • Ví dụ peer A gửi gói tin tới 2.2.2.2:5678 của peer B lấy được bằng STUN
    • CGNAT phải không gửi gói tin này ra Internet bên ngoài, mà phải vòng nó lại trong nội bộ tới ánh xạ NAT của peer B
  • Nhiều NAT không hỗ trợ hairpinning
    • Có thiết bị giả định rằng gói tin đi từ mạng nội bộ tới IP không nội bộ thì luôn đi ra Internet
    • Giả định này có thể đã được nhúng trong silicon định tuyến, nên có thể không sửa được nếu không có phần cứng mới
  • Khi có CGNAT tham gia, hairpinning trở nên quan trọng đối với khả năng kết nối
    • Nếu cả hairpinning lẫn ánh xạ cổng đều thất bại, phải dùng relay

IPv6 và NAT64

  • Nếu thế giới chỉ có IPv6 thì vấn đề NAT sẽ đơn giản hơn nhiều
    • Mọi thiết bị đều có thể có địa chỉ có thể truy cập được mà không cần NAT
    • Tuy nhiên tường lửa có trạng thái vẫn còn, nên vẫn cần xuyên tường lửa và kênh phụ
    • Fallback relay dùng các giao thức như HTTP vẫn hữu ích cho các mạng chặn outbound UDP
  • Chỉ IPv6 thôi hiện vẫn chưa đủ
    • Thế giới phần lớn vẫn là IPv4 và chỉ khoảng 33% IPv6
    • Việc triển khai IPv6 không đồng đều, nên tùy tổ hợp peer mà có thể là 100% IPv6, hoặc 0% IPv6
    • Nếu mục tiêu là luôn kết nối được, vẫn phải tiếp tục xử lý IPv4+NAT
  • Sự cùng tồn tại của IPv6 và IPv4 tạo ra một trường hợp bổ sung gọi là NAT64
    • NAT44 chuyển đổi IPv4 sang một IPv4 khác
    • NAT64 chuyển đổi IPv6 nội bộ sang IPv4 bên ngoài
    • Khi dùng cùng DNS64, nó cung cấp truy cập Internet IPv4 trong khi thiết bị đầu cuối thấy như đang ở mạng chỉ có IPv6
  • Ứng dụng chỉ dùng tên DNS hầu như không cần để ý đến NAT64
    • Nhưng NAT Traversal thao tác trực tiếp với IP và cổng cụ thể, nên cần xử lý riêng
  • Nếu thiết bị hỗ trợ CLAT(Customer-side translator), hệ điều hành sẽ xử lý NAT64 phía sau, khiến nó trông như có kết nối IPv4 trực tiếp
    • CLAT phổ biến trên thiết bị di động
    • Nhưng hiếm trên desktop, laptop và máy chủ
  • Nếu không có CLAT, cần tự phát hiện NAT64+DNS64
    • Gửi truy vấn DNS tới ipv4only.arpa.
    • Tên này chỉ được phân giải thành các địa chỉ IPv4 cố định đã biết
    • Nếu nhận về địa chỉ IPv6, đó là do DNS64 chuyển đổi, nên có thể suy ra prefix NAT64
  • Sau đó, để giao tiếp với địa chỉ IPv4, chỉ cần gửi gói IPv6 tới {NAT64 prefix + IPv4 address}
    • Nếu thực hiện STUN qua NAT64 để tìm ip:port công khai, ta lại quay về bài toán NAT Traversal thông thường

Hợp nhất các đường đi ứng viên bằng ICE

  • Cách phân loại chính xác trước xem nên dùng kỹ thuật nào trong tất cả các kỹ thuật có khả năng mở rộng thấp
    • Vì kỹ sư mạng và nhà triển khai thiết bị NAT tạo ra rất nhiều hành vi khác nhau
  • Cốt lõi của ICE(Interactive Connectivity Establishment) là một thuật toán thử đồng thời mọi khả năng có thể và chọn đường đi tốt nhất trong số những đường hoạt động
  • Khi bắt đầu giao tiếp, thu thập danh sách endpoint ứng viên cho socket cục bộ
    • IPv6 ip:ports
    • IPv4 LAN ip:ports
    • IPv4 WAN ip:ports phát hiện bằng STUN
    • IPv4 WAN ip:ports phát hiện thông qua NAT64 translator
    • IPv4 WAN ip:port được cấp bằng giao thức port mapping
    • Endpoint do người vận hành cung cấp, như port forwarding cấu hình tĩnh
  • Sau đó trao đổi danh sách ứng viên qua kênh phụ và gửi gói probe tới mọi endpoint do phía bên kia cung cấp
    • Gói probe đóng vai trò là gói mở tường lửa và NAT
    • Đồng thời cũng đóng vai trò kiểm tra trạng thái dạng ping/pong
  • Sau một khoảng thời gian, chọn đường đi tốt nhất theo heuristic trong số các đường ứng viên đã được xác nhận hoạt động
    • ICE thường dùng điểm ưu tiên định sẵn như LAN > WAN > WAN+NAT
    • Từ v0.100.0, Tailscale dùng độ trễ khứ hồi thay vì thứ tự ưu tiên hard-code
  • Tailscale không chia kết nối một cách nghiêm ngặt thành giai đoạn probe và giai đoạn giao tiếp
    • Mọi kết nối bắt đầu với DERP đã được chọn sẵn
    • Người dùng có thể dùng kết nối ngay qua đường fallback
    • Việc dò đường chạy song song, và nếu sau vài giây phát hiện đường tốt hơn thì sẽ được nâng cấp một cách trong suốt

Duy trì đường đi và bảo mật khi vận hành

  • Cần chú ý đến đường đi bất đối xứng
    • ICE cố gắng để hai peer chọn cùng một đường mạng, duy trì luồng gói tin hai chiều
    • Ngay cả khi không triển khai quy trình ở cùng mức độ, mọi đường đang dùng đều phải có lưu lượng hai chiều
    • Chỉ các probe ping/pong định kỳ cũng có thể duy trì điều này
  • Đường đi hiện được chọn cũng có thể thất bại
    • Ví dụ là trạng thái biến mất do bảo trì NAT
    • Có thể tiếp tục probe mọi đường có thể để duy trì warm fallback
    • Nhưng vì downgrade hiếm xảy ra, có thể hiệu quả hơn nếu rơi về relay như phương án cuối rồi bắt đầu dò đường lại
  • Giả định giao thức tầng trên cung cấp bảo mật riêng là rất quan trọng
    • QUIC dùng chứng chỉ TLS
    • WireGuard dùng khóa công khai riêng
  • Khi chuyển đổi đường đi động, bảo mật dựa trên IP trở nên vô nghĩa
    • Tối thiểu cần xác thực end-to-end
  • Nếu tầng trên có bảo mật end-to-end, thì ngay cả khi probe ping/pong có thể bị giả mạo, trường hợp xấu nhất là kẻ tấn công dụ lưu lượng đi qua chúng
    • Dù vậy, tốt nhất vẫn nên xác thực và mã hóa cả các gói dò đường

Các thành phần của NAT Traversal vững chắc

  • NAT Traversal vững chắc cần các yếu tố sau
    • Giao thức dựa trên UDP để mở rộng
    • Socket có thể truy cập trực tiếp bên trong chương trình
    • Kênh phụ để giao tiếp với peer
    • Một vài máy chủ STUN
    • Mạng fallback relay, không bắt buộc nhưng rất nên có
  • Các bước thực hiện như sau
    • Liệt kê tất cả ip:ports của socket trên các interface được kết nối trực tiếp
    • Truy vấn máy chủ STUN để tìm WAN ip:ports và độ khó của NAT
    • Tìm thêm WAN ip:ports bằng giao thức port mapping
    • Nếu có NAT64, phát hiện nó và cũng tìm WAN ip:port qua đường đó
    • Trao đổi tất cả ip:ports và khóa mã hóa với peer qua kênh phụ
    • Có thể giao tiếp trước qua fallback relay để thiết lập kết nối nhanh
    • Probe mọi ip:ports của phía bên kia, và nếu cần thì thực hiện dò tìm dựa trên nghịch lý ngày sinh để vượt qua hard NAT
    • Khi tìm được đường kết nối tốt hơn đường hiện tại, nâng cấp một cách trong suốt
    • Nếu đường đang hoạt động dừng lại, downgrade khi cần để duy trì khả năng kết nối
    • Mọi giao tiếp phải được mã hóa và xác thực end-to-end

1 bình luận

 
GN⁺ 2025-01-06
Ý kiến trên Hacker News
  • Bài viết rất hay. Thường có một dạng tri thức ngầm rằng TCP hole punching khó hơn UDP nên tốt nhất đừng làm, nhưng trên thực tế có vẻ độ phức tạp tăng thêm không lớn so với luồng UDP vốn đã phức tạp
    Bài viết cũng thừa nhận rằng việc đi qua NAT bằng TCP là khả thi nhưng sẽ làm tăng độ phức tạp, và nếu đi sâu hơn thì có thể cần chỉnh sửa kernel. Tuy vậy, tôi cho rằng chỉ cần thay phần khởi tạo kết nối bằng gói UDP thô bằng gói TCP SYN và hỗ trợ simultaneous open
    Đặc biệt khi nghĩ đến những mạng như guest Wi‑Fi của UC Berkeley, nơi chặn mọi UDP đi ra ngoài trừ DNS, thì thật đáng tiếc nếu chỉ lướt qua TCP hole punching với lý do “khó hơn UDP”. Tôi thấy nó gần như khả thi tương đương và độ phức tạp bổ sung cũng có giới hạn
    https://ttcplinux.sourceforge.net/documents/one/tcpstate/tcp...

    • Do có tường lửa theo dõi trạng thái và vì hầu hết bộ lọc NAT là EDF chứ không phải EIF, nên ngay cả với UDP cũng cần simultaneous open, tức gửi đồng thời
      Vì vậy độ phức tạp tăng thêm khi làm simultaneous open bằng TCP là khá nhỏ. Điểm khó cốt lõi là truyền public mapping và điều phối việc punch/open “đồng thời”, mà chuyện này thường cũng cần với UDP
      Điểm phức tạp hơn một chút ở TCP là phải gọi connect() thật thay vì tạo gói TCP SYN giả. Một số tường lửa có kiểm tra số thứ tự
    • Đúng là một nhận xét rất hay. Tôi đã trực tiếp triển khai TCP hole punching và hiện giờ có một bản triển khai khá ổn; lợi thế lớn khi dùng TCP là sau khi mở được lỗ thì không cần chồng lại một phiên bản TCP nghèo nàn trên UDP nữa
      Tuy vậy, TCP hole punching có thể trông giống SYN flood hơn rất nhiều so với gói UDP, nên trên một số mạng tỷ lệ thành công có thể thấp hơn. Trên thực tế tôi vẫn chưa thấy bị lọc nhiều
      TCP hole punching khá thú vị. Tôi triển khai nó bằng cách dùng nhiều phép đo NTP để tính “độ lệch đồng hồ” giữa đồng hồ hệ thống và NTP, rồi bên khởi tạo chọn một thời điểm hẹn trong tương lai theo mốc NTP. Nó chính xác hơn tôi tưởng, và TCP hole punching giữa các socket trên cùng một interface cũng hoạt động
      Lý do tôi hỗ trợ chế độ punching kiểu local kỳ lạ này là vì nếu punching nội bộ trên host thành công với hiệu quả như vậy, thì nhiều khả năng nó cũng sẽ đủ nhanh trên LAN và Internet. Mã viết bằng Python, và lần thử đầu tiên khá sốc. TCP hole punching nhạy cảm với thời điểm đến mức khi dùng kiểu quản lý socket trực tiếp cổ điển trong Python, threading, và một event loop tạm bợ dựng từ kinh nghiệm socket C, thì nó thất bại
      Để đoạn mã đó chạy được, tôi phải tăng ưu tiên cho tiến trình Python để các tiến trình khác không tạo độ trễ giữa các lần thử punching. Với các bản triển khai kém hiệu quả, nó nhạy thời gian đến mức đó. Bản hiện tại dùng một pool tiến trình, mỗi tiến trình có event loop riêng, rồi tạo một danh sách tác vụ được dàn theo thời gian, sau đó mỗi tác vụ tái sử dụng cùng một socket để mở kết nối. Sau khi thử trên các hệ điều hành chính, tôi kết luận đây là cách tốt nhất trong Python
      Tôi đồng ý rằng độ khó của TCP và UDP hole punching là tương tự nhau. Với cả hai, phần khó nhất đều là giai đoạn dự đoán NAT. Tôi vẫn chưa viết mã vượt qua symmetric NAT, nhưng đã bắt đầu thấy cách để tích hợp nó hoặc biến nó thành một plugin mới
    • Tôi vừa nghĩ ra thêm một bất lợi khác khiến TCP punching kém hơn UDP. Với TCP, router phải ghi lại trạng thái kết nối
      Bảng trạng thái của router rất nhỏ, và một số kỹ thuật punching khá hung hãn. Ví dụ, nếu mở hàng trăm kết nối TCP như trong các thuật toán cố vượt qua symmetric NAT, thì có thể đẩy router vào trạng thái từ chối dịch vụ
      Nhờ các tối ưu quản lý trạng thái, UDP có thể ít có khả năng khiến toàn bộ router bị treo chỉ vì punching hơn. Tuy vậy, đây chỉ là suy đoán
  • Hiệu quả tốt đến mức khá thú vị, nhưng cứ hễ có ai đề xuất đưa thứ này vào mạng doanh nghiệp production thì lại thấy hơi bất an
    Nó tạo cảm giác đang vượt qua NAT và tường lửa truyền thống, rồi chỉ phụ thuộc vào một ACL phần mềm duy nhất nên trông có vẻ rủi ro. Ví dụ, nếu có Tailscale trên một VM bị bỏ quên trong môi trường thử nghiệm AWS và kẻ tấn công truy cập được vào đó, thì dường như sẽ hình thành một đường đi tới cả laptop trong mạng nội bộ doanh nghiệp, nơi việc cho phép/chặn chỉ do mã ACL của Tailscale trong không gian người dùng quyết định sau khi đi qua kernel
    Không rõ liệu có biết được khi một người không được phép đã xâm nhập đến điểm đó hay không

    • Đó là lý do nhiều người cứ lặp đi lặp lại rằng NAT không phải là cơ chế bảo mật
      Việc xuyên qua NAT và phần lớn các bộ lọc theo dõi trạng thái liên quan đến nó là rất dễ. Tôi đã từng triển khai những thứ như vậy thành sản phẩm bán ra trong môi trường production doanh nghiệp thực tế, và đây không phải phép màu gì mà là kỹ thuật đã được người trong nghề biết rõ
      Nếu muốn lọc gói thực sự, tức là muốn có tường lửa, thì cần triển khai một instance tường lửa riêng biệt với NAT và đặt các quy tắc phù hợp. Dù vậy, ngay cả thế thì nó chủ yếu chỉ giúp giảm lưu lượng; lợi ích bảo mật thực sự của bản thân tường lửa giờ cũng không còn lớn. Phần lớn các cuộc tấn công đi vào qua các lớp cao hơn như HTTP/HTTPS, POP/IMAP
    • Nói công bằng thì lý do mọi người đều hiểu lầm NAT là cơ chế bảo mật là vì theo truyền thống NAT được triển khai cùng với tường lửa theo dõi trạng thái
      Trên thực tế, tường lửa theo dõi trạng thái mới là thứ làm phần lớn công việc nhưng NAT lại được nhận công. Tailscale không loại bỏ tường lửa mà cung cấp một cấu hình toàn diện hơn nhiều dựa trên ACL đúng nghĩa
      Tuy vậy, cũng phải thừa nhận rằng công cụ ACL của Tailscale còn rất nhiều chỗ để cải thiện
    • Networking từ lâu đã là kiểu bãi rác độc hại của bảo mật và cấu hình sai. Giờ còn bị trộn thêm mô hình mạng dựa trên host hiện đại cho container nữa
      Hệ quả là cả stack mạng của Windows cũng đã thay đổi khá nhiều và trở nên phức tạp hơn. Từ sau khi WireGuard được đưa vào Linux, ai cũng có ít nhất một VPN nối tới VPS ở đâu đó. Vì người ta không biết những gì mình không biết, nên tình hình thực tế có thể còn tệ hơn nhiều so với tưởng tượng
    • Đây là thứ dùng để vượt NAT, tức loại thiết bị được tạo ra để lách tình trạng thiếu địa chỉ IPv4
      Tường lửa là một khái niệm khác. Nhưng nếu nói về việc gộp kết nối và bảo mật lại với nhau, thì thật đáng buồn và đáng lo khi bảo mật Internet từ trước đến nay vẫn dựa vào việc chặn gói theo cổng đích
      Làm cái dễ thay vì cái đúng, nhưng vẫn được gắn mác là “giải pháp chuyên nghiệp”
    • VoIP từ xưa đến nay đều hoạt động theo kiểu này, và cũng có nhiều hạ tầng công khai tiêu chuẩn để làm cho việc đó dễ hơn. Ví dụ như ICE, TURN
      Dù vậy, vẫn cần một thứ gì đó ở bên trong chủ động bắt chuyện ra bên ngoài trước, nên tường lửa thực sự phải quản lý danh sách cho phép cho cả kết nối đi lẫn kết nối đến
      Nói cách khác, nếu vẫn phụ thuộc vào bảo mật chu vi thì việc ai đó cuối cùng tìm ra “áo phản quang” phiên bản của tổ chức mình chỉ còn là vấn đề thời gian
  • Với các thiết bị vốn đã mã hóa ở tầng ứng dụng, sẽ thật hay nếu có một giải pháp thay thế giống Tailscale nhưng không có mã hóa kết nối. Giống như gần như toàn bộ Internet vận hành như thế, không phải lúc nào cũng cần mã hóa tới tận lớp thấp hơn
    Chi phí tính toán đặc biệt lớn với thiết bị công suất thấp, ví dụ các thiết bị IoT chạy một đường hầm kiểu Tailscale
    Có GRE tunnel và nó thực sự được dùng khá nhiều, nhưng vì không xử lý UDP hole punching nên cần kiến trúc hub-and-spoke. Với GRE, tức ip fou, không thể tạo mesh giữa các peer
    Tôi tự hỏi có thư viện nào cung cấp UDP hole punching và GRE tunnel không mã hóa sau một bắt tay mã hóa để xác thực danh tính hay không

    • Tiêu chuẩn đã được xác lập trong lĩnh vực này là ICE (Interactive Connectivity Establishment), thứ mà WebRTC dựa vào. Có một số thư viện khá ổn triển khai nó hoặc triển khai một phần các thành phần của nó
      Nếu muốn thứ gì đó hướng tới kết nối tổng quát hơn thì libp2p có thể gần với điều bạn muốn
      https://datatracker.ietf.org/doc/html/rfc8445
      https://github.com/pion/webrtc
      https://github.com/algesten/str0m
      https://libp2p.io
    • Không phải UDP, nhưng ở đây có triển khai TCP hole punching và các phương pháp vượt NAT chủ yếu khác: https://github.com/robertsdotpm/p2pd
      Nó được viết bằng Python. Tuy nhiên, giống như phần lớn mã mạng khác, nó không giả định phải dùng interface mặc định. Tôi muốn có thể chạy dịch vụ trên bất kỳ interface nào mình muốn để có thể tạo ra thứ đa dạng và hữu ích hơn
      Phần lớn dựa trên các module thư viện chuẩn. Tôi không thích extension C vì chúng thường xuyên làm vỡ các gói cross-platform
    • Hole punching trong VoIP chính là TURN, STUN và ICE, nên có thể tái sử dụng các thư viện bên đó
    • Cũng có thể thử hồi sinh Teredo
  • Đọc đến đoạn các peer phải biết trước ip:port mà phía kia dùng, rồi còn phải dựng một máy chủ điều phối để đồng bộ việc đó, tôi lại thấy giá mà SIP làm đúng với cái tên của nó thì tốt biết mấy
    SIP là Session Initiation Protocol, tức cái tên nghe như phải có khả năng khởi tạo cả những phiên tùy ý như VPN, nhưng trên thực tế nó là một mớ quá phức tạp nên không còn đáng để đánh đổi nữa. Tôi nghĩ ban đầu nó được tạo ra như một kênh phụ cho liên lạc nhằm thiết lập các luồng RTP P2P

    • SIP làm quá nhiều thứ đến mức đáng sợ nếu phải giữ mọi thứ đó trong đầu cùng lúc
      Nó giống HTTP nhưng có trạng thái, hai chiều, liên hiệp, và còn chạy được trên UDP
      Chỉ cần nhìn lượng thứ mà baresip phải triển khai chỉ để xử lý SIP, bao gồm cả TLS over UDP, là đã thấy khổng lồ. Thậm chí không phải do phình to vô lý, mà những tính năng đó thực sự cần thiết
  • Đây là bài viết từ năm 2020. Các thảo luận trước đó như sau
    Năm 2022: https://news.ycombinator.com/item?id=30707711
    Năm 2020: https://news.ycombinator.com/item?id=24241105

  • Đây chính là bài viết tôi từng gửi cho mọi người khi giải thích về NAT traversal
    Khi chúng ta xây dựng ứng dụng P2P, có lẽ sẽ còn tiếp tục phải dựa vào cách này. IPv6 chưa có đủ động lực để bứt lên, còn NAT và SNI routing thì giải quyết được phần lớn vấn đề cho phần lớn mọi người
    Từ góc nhìn của ISP, cũng không có nhiều động lực để khiến tình hình đó thay đổi

  • Tôi cho rằng đây là một trong những bài viết chi tiết nhất trên toàn Internet về NAT traversal. Tuy vậy, nó thiếu thông tin về hành vi delta
    Không phải chuyện quá phức tạp; ý là một số NAT có những mẫu có thể quan sát được khi gán các cổng ngoài liên tiếp. Mẫu phổ biến nhất là giữ nguyên cổng nguồn, và cũng có thể có các mẫu như tăng thêm 1 so với mapping trước đó
    Về mặt lý thuyết thì đây là một bài rất hay, nhưng tôi vẫn băn khoăn không biết kỹ sư phần mềm có thể tận dụng được đến mức nào trong thực tế. Bài viết giải thích nhiều thứ, nhưng có thể chưa đủ chi tiết để viết thuật toán. Ví dụ, tôi không rõ chỉ dựa vào bài này thì có thể viết thuật toán kiểm tra loại NAT hay tinh chỉnh mã hole punching của riêng mình hay không
    Cá nhân tôi từng thấy có những bài báo mà một bảng đơn giản còn hữu ích hơn một bài dài như thế này. Dù vậy, đây vẫn có thể là một điểm khởi đầu tốt
    Phần cuối của bài viết đặc biệt quan trọng. Nó cho thấy khả năng vượt qua symmetric NAT vốn được dùng trong các hệ thống di động. Các nghiên cứu NAT traversal hiện đại cũng dùng kỹ thuật tương tự và tuyên bố tỷ lệ thành công gần như 100%

  • Đây là một bài thú vị gợi nhớ về quá khứ. Năm 2010 tôi đã tạo ra một mạng mesh P2P vô trạng thái sử dụng kiểu này
    Hồi đó mọi người không quan tâm đến bảo mật nhiều như chúng tôi nghĩ, và đến giờ họ vẫn chưa quan tâm đủ. Thiết bị ngày càng nhiều và giá trị cũng lớn hơn, nhưng chúng vẫn còn khá thiếu an toàn
    Root of trust phần cứng, chuỗi tin cậy an toàn cho xác thực/ủy quyền, các endpoint thực sự an toàn với đặc quyền tạm thời tối thiểu — tất cả những thứ đó đến giờ vẫn khó, và màn kịch bảo mật chu vi mạng vẫn tiếp diễn trong mạng gia đình, mạng doanh nghiệp và cả mạng trung tâm dữ liệu sản xuất quy mô lớn
    Lý do duy nhất khiến những thứ này không có vẻ là nguyên nhân gốc rễ chủ yếu của các vụ xâm phạm bảo mật là vì vẫn còn đầy những con đường tấn công dễ hơn

  • Hơi lạc đề một chút, nhưng vài tuần trước tôi đã đọc thử về lĩnh vực này dù trước đó hoàn toàn không biết gì
    Ấn tượng tôi nhận được là IPv6 dường như sẽ loại bỏ toàn bộ chuyện này và khiến NAT traversal không còn cần thiết nữa. Nếu vậy thì tại sao IPv6 vẫn chưa được dùng rộng rãi hơn, và nếu muốn bắt đầu với mạng gia đình cùng Tailscale VPN thì phải làm như thế nào?

    • Tôi không biết mức độ đóng góp của lý do này vào việc IPv6 kém phổ biến là bao nhiêu, nhưng việc khó dùng với con người luôn là một thách thức
      Các động lực kinh doanh cũng không đủ mạnh
  • Chỉ riêng việc thứ này xuất hiện thay vì IPv6 cũng đã cho thấy sức mạnh của một giải pháp chắp vá đủ tốt