SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-27

WireGuard hoạt động thế nào: cryptokey routing

Hiểu vì sao AllowedIPs vừa là routing table vừa là access list, cùng Noise handshake, key rotation và cách đọc rõ từng dòng trong wg0.conf.

WireGuard hoạt động như thế nào, qua một ý chính

WireGuard gắn mỗi packet với một public key. Cơ chế này có tên là định tuyến cryptokey, và đó là toàn bộ thiết kế: dòng AllowedIPs bên cạnh một peer vừa là routing table cho các packet rời khỏi máy của bạn, vừa là access control list cho các packet đi vào từ peer đó. Một setting, hai nhiệm vụ. Hãy đọc AllowedIPs theo cách này thì mọi file cấu hình WireGuard đều trở nên dễ hiểu.

Không có session table được lập theo địa chỉ IP và cũng không có user database. Một peer gồm một public key và tập hợp các địa chỉ mà key đó được phép sử dụng. Handshake và các timer giúp duy trì ràng buộc này khi network bên dưới thay đổi. Nếu muốn có tunnel hoạt động trước khi tìm hiểu lý thuyết, hãy dựng một tunnel bằng WireGuard VPN tự host trên VPS của bạn, rồi quay lại đây khi một dòng cấu hình khiến bạn thấy bất ngờ.

AllowedIPs là bảng định tuyến và danh sách kiểm soát truy cập

Xét hướng outbound trước. Kernel định tuyến packet đến device wg0 theo cách thông thường, thông qua main routing table. Sau đó, WireGuard đối chiếu địa chỉ đích của packet với một bảng chứa các prefix được phép của mọi peer, theo thứ tự prefix dài nhất trước. Kết quả khớp xác định một peer, từ đó xác định public key, session key và UDP endpoint. Packet được mã hóa cho peer đó rồi gửi đi.

Nếu AllowedIPs của không peer nào bao phủ địa chỉ đích, không có gì được gửi vì không có key để mã hóa packet.

ping: sendmsg: Required key not available

Lỗi này chỉ có một nguyên nhân: địa chỉ bạn muốn truy cập không được liệt kê dưới bất kỳ peer nào. Lỗi khác, ping: sendmsg: Destination address required, có nghĩa là đã tìm thấy peer phù hợp nhưng WireGuard chưa có endpoint cho peer đó vì chưa được cấu hình và cũng chưa học được endpoint nào.

Bây giờ xét hướng inbound. Một UDP packet đến listen port. WireGuard tìm session dựa trên receiver index trong header, kiểm tra counter theo sliding replay window, sau đó giải mã và xác thực payload. Chỉ sau bước đó, WireGuard mới đọc packet bên trong; địa chỉ nguồn của packet này phải nằm trong AllowedIPs của peer gửi. Nếu không, packet bị drop. Khi bật dynamic debug, kernel in lý do, với một dòng tương tự như sau:

wg0: Packet has unallowed src IP (10.8.0.9) from peer 2 (203.0.113.10:51820)

Đây là lý do peer ở phía server nhận lỗi /32. Một peer được cấu hình với AllowedIPs = 10.8.0.2/32 có thể gửi packet từ 10.8.0.2 và không được gửi từ địa chỉ nào khác. Nếu thay bằng 0.0.0.0/0, client đó sẽ được phép inject packet nhận bất kỳ địa chỉ nguồn nào bên trong tunnel, kể cả địa chỉ của client khác.

Các prefix chồng lấn được xử lý theo độ cụ thể vì lookup dùng longest prefix match. Các prefix giống hệt nhau trên hai peer hoạt động khác: entry được chuyển sang peer được cấu hình sau cùng, còn peer đầu tiên ngừng nhận traffic đó mà không có lỗi nào được in ra. wg show wg0 allowed-ips in ra bảng hiện đang nằm trong kernel. Đây mới là trạng thái thực tế khi file trên disk và trạng thái đang chạy không còn đồng bộ.

Đọc file cấu hình với định tuyến cryptokey

Phía server:

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server private key>

[Peer]
PublicKey = <laptop public key>
AllowedIPs = 10.8.0.2/32

Phía client:

[Interface]
Address = 10.8.0.2/32
PrivateKey = <laptop private key>

[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Cùng một keyword có ý nghĩa trái ngược ở hai phía. Ở client, nó có nghĩa là “gửi mọi đích đến peer này”. Ở server, nó có nghĩa là “chỉ chấp nhận địa chỉ này từ peer”. Sự bất đối xứng nằm trong các giá trị, không nằm ở vai trò nào.

Một nửa số key này không thuộc protocol. Address, DNS, MTU, PostUp và SaveConfig thuộc về wg-quick, shell script dùng để đưa interface lên. Kernel không bao giờ thấy chúng. wg-quick strip wg0 in ra config đã được rút gọn mà tool wg thực sự load. Đây là cách nhanh nhất để thấy sự phân tách đó.

Cơ chế handshake thực sự hoạt động

Handshake của WireGuard là Noise_IKpsk2, thuộc Noise Protocol Framework. Phần IK hữu ích cho sysadmin là: static public key của responder đã được initiator biết trước, vì đó là PublicKey trong block [Peer] của bạn; initiator gửi static public key của chính nó bên trong message đầu tiên và message này được mã hóa. Vì vậy không có quá trình trao đổi certificate hay round trip để xác minh danh tính. Observer thụ động không thể biết key nào đang gọi, trừ khi có private key của responder.

Chi phí là một round trip. Initiation message có 148 byte, response có 92 byte, và data bắt đầu được truyền ngay sau đó. Mỗi phía tạo một cặp ephemeral Curve25519 key mới cho mỗi handshake. Session key được tạo từ một chuỗi kết quả Diffie-Hellman, kết hợp static key và ephemeral key. Sau đó ephemeral private key bị xóa, tạo ra forward secrecy: nếu ai đó ghi lại traffic của bạn hôm nay rồi đánh cắp private key của server vào năm sau, họ vẫn không thể đọc phần traffic đã ghi.

Handshake initiation mang theo timestamp TAI64N. Mỗi peer ghi nhớ timestamp lớn nhất đã nhận từ peer bên kia, nên initiation bị replay sẽ bị từ chối. Data packet mang theo counter 64-bit dùng làm nonce. Receiver duy trì một sliding window gồm các counter đã thấy gần đây, nên có thể xử lý replay và việc sắp xếp packet sai nhiều mà không cần connection state kiểu TCP.

Session key không tồn tại lâu, và các timer được compile sẵn thay vì cho phép cấu hình.

ChartWireGuard protocol timers, in seconds
The data behind this chart
[
  {
    "label": "REKEY_TIMEOUT",
    "seconds": 5,
    "notes": "resend a handshake initiation that got no answer"
  },
  {
    "label": "KEEPALIVE_TIMEOUT",
    "seconds": 10,
    "notes": "send a keepalive after receiving data and sending none back"
  },
  {
    "label": "REKEY_ATTEMPT_TIME",
    "seconds": 90,
    "notes": "give up on the handshake and report the peer as down"
  },
  {
    "label": "REKEY_AFTER_TIME",
    "seconds": 120,
    "notes": "sender begins a fresh handshake for a new session key"
  },
  {
    "label": "REJECT_AFTER_TIME",
    "seconds": 180,
    "notes": "the old session key is refused and traffic stops"
  }
]

Đây là các hằng số trong protocol specification, không phải số đo thực tế. 5 hằng số này điều khiển toàn bộ vòng đời session. Sau 120 giây sử dụng, sender bắt đầu một handshake mới. Sau 180 giây, key cũ bị từ chối hoàn toàn, nên traffic dừng cho đến khi handshake mới hoàn tất. Initiation không nhận được response sẽ được gửi lại sau mỗi 5 giây và bị bỏ sau 90 giây. Vì vậy wg show hiển thị latest handshake dưới dạng thời gian đã trôi qua tương đối, và một tunnel đang hoạt động bình thường sẽ giữ giá trị này ở mức nhỏ. Nếu giá trị này tăng trong khi bạn vẫn đang chủ động gửi traffic, nghĩa là handshake đang thất bại, không phải tunnel đang idle.

Vì sao một peer không có vai trò client hoặc server

Cả hai đầu đều chạy cùng một code và cùng một định dạng cấu hình. Không có server mode. Sự bất đối xứng bạn thấy đến từ Endpoint, còn Endpoint là tùy chọn.

Peer đã cấu hình endpoint có thể bắt đầu handshake. Peer không có endpoint sẽ chờ, sau đó nhận địa chỉ và port của phía bên kia từ packet đầu tiên được xác thực đúng. Endpoint đã học được này sẽ được lưu lại và cập nhật mỗi khi có packet hợp lệ đến từ một địa chỉ mới. Đây là cách roaming hoạt động: laptop chuyển từ Wi-Fi sang mạng di động vẫn giữ nguyên tunnel, vì session được xác định bằng key và index thay vì địa chỉ IP. Không có thao tác reconnect nào xảy ra, vì theo nghĩa TCP thì chưa từng có kết nối nào được thiết lập.

Cơ chế tương tự tạo ra một thông tin đáng lưu ý: peer có địa chỉ public luôn giữ public IP được biết gần đây nhất của phía bên kia, và wg show sẽ in địa chỉ đó.

Các primitive cố định, không cần thương lượng

WireGuard không có danh sách ciphersuite. ChaCha20-Poly1305 dùng cho mã hóa có xác thực, Curve25519 dùng cho thỏa thuận khóa, BLAKE2s dùng để băm và HKDF dùng để dẫn xuất khóa. Mọi deployment đều sử dụng các primitive này. Vì vậy không có giai đoạn thương lượng cần phân tích và cũng không có đường hạ cấp xuống tùy chọn yếu hơn. Đây là một đánh đổi thực sự: nếu một trong các primitive này bị phá, cách khắc phục là phát hành phiên bản mới của toàn bộ protocol và cập nhật ở cả hai đầu, không phải thay đổi config. Quyết định này loại bỏ phần lớn code và phần lớn dạng lỗi mà một tunnel dựa trên TLS thường có. Đây cũng là điểm cốt lõi của phần so sánh trong WireGuard và OpenVPN.

Vì sao port không phản hồi scanner

Mỗi thông điệp handshake đều có một trường gọi là mac1. Đây là một MAC (mã xác thực thông điệp) được tính trên thông điệp bằng một key suy ra từ public key tĩnh của responder. Sender không biết public key đó sẽ không thể tạo mac1 hợp lệ, nên receiver loại bỏ packet này mà không trả lời. Không có lỗi, không có reset và không có thông báo ICMP.

Kết quả quan sát được là UDP scan không nhận lại gì.

sudo nmap -sU -p 51820 vpn.example.com

nmap báo open|filtered. Đây cũng là kết quả nmap trả về với một port bị firewall âm thầm drop. Port hoạt động giống hệt nhau dù WireGuard có đang listen hay không, ít nhất với những ai không có public key của bạn.

Một trường thứ hai, mac2, xử lý áp lực denial of service. Khi receiver đang chịu tải, nó trả lời một initiation hợp lệ bằng cookie reply dài 64-byte gắn với source address của sender. Receiver sẽ không thực hiện thao tác public key tốn tài nguyên cho đến khi sender gửi lại cookie đó. Cơ chế này xác nhận source address là có thật trước khi tiêu tốn CPU và chỉ được kích hoạt khi hệ thống chịu tải.

Vì sao 0.0.0.0/0 biến một peer thành default route của bạn

Vì AllowedIPs là routing table, AllowedIPs = 0.0.0.0/0, ::/0 nhận mọi đích đến cho peer đó. Đó là toàn bộ thiết lập full tunnel.

Cách định tuyến giúp cấu hình này hoạt động thú vị hơn chính dòng cấu hình đó. Một default route thông thường qua wg0 sẽ tạo loop, vì encrypted UDP packet mang traffic của bạn cũng phải rời khỏi máy và sẽ khớp với chính default route đó. wg-quick giải quyết việc này bằng policy routing. Nó đánh dấu các packet đi ra của WireGuard bằng fwmark, đặt default route của tunnel vào một routing table riêng và thêm các rule để chỉ traffic không có mark mới đi vào table đó. Chạy ip rule show, bạn sẽ thấy kết quả:

32764:	from all lookup main suppress_prefixlength 0
32765:	not from all fwmark 0xca6c lookup 51820
32766:	from all lookup main

0xca6c là 51820 ở dạng hex, và 51820 cũng là số của table. Rule suppress_prefixlength 0 khiến main table bỏ qua default route của chính nó, vì vậy các route cụ thể như subnet local vẫn được ưu tiên, còn mọi traffic khác sẽ chuyển xuống tunnel table. Split tunnel không cần các thiết lập này: danh sách hẹp hơn như AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 sẽ trở thành các route thông thường trong main table.

Một việc full tunnel không tự giải quyết được là phân giải tên miền, vì resolver mà client nhận từ mạng local thường vẫn được giữ nguyên và route đến resolver đó có độ cụ thể cao hơn. Đây là một phần việc riêng, được trình bày trong DNS bị rò rỉ ra ngoài WireGuard tunnel.

PersistentKeepalive thực sự dùng để làm gì

WireGuard không gửi gì khi không có lưu lượng. Không heartbeat, không làm mới session, không có dữ liệu nào đi trên đường truyền. Việc im lặng này giúp tiết kiệm pin và hỗ trợ trường hợp scanner ở trên, nhưng sẽ gây lỗi trong một kiểu triển khai cụ thể.

Một peer nằm sau NAT (network address translation) hoặc firewall stateful chỉ có thể nhận kết nối từ bên ngoài khi thiết bị đó còn một mapping được tạo bởi packet gửi đi. Thời gian tồn tại phổ biến của UDP mapping bắt đầu từ khoảng 30 giây. Khi mapping hết hạn, middlebox sẽ drop packet từ phía public, làm tunnel trông như đã chết cho đến khi peer phía sau NAT gửi dữ liệu. PersistentKeepalive = 25 gửi một packet rỗng có authentication mỗi 25 giây. Khoảng thời gian này thấp hơn thời gian tồn tại phổ biến ngắn nhất, nên mapping luôn được duy trì.

Đặt tùy chọn này trên peer phía sau NAT. Server có địa chỉ public và UDP port đang mở không cần tùy chọn này. Đặt nó trên server chỉ làm tăng lưu lượng. Không nhầm tùy chọn này với keepalive tự động. Keepalive tự động được gửi 10 giây sau khi peer nhận dữ liệu nhưng không có dữ liệu riêng để gửi lại. Cơ chế này luôn bật và không thể cấu hình.

Định tuyến mạng LAN qua tunnel không phải là tính năng của WireGuard

Giả sử peer B nằm trên mạng gia đình 192.168.50.0/24 và peer A cần truy cập mạng đó. Hai hệ thống riêng biệt phải được cấu hình thống nhất, nhưng chỉ một hệ thống thuộc về WireGuard.

Phần việc của WireGuard: thêm 192.168.50.0/24 vào AllowedIPs của B trên A. Việc này khiến A định tuyến prefix đó đến B và chấp nhận từ B các packet có source address tương ứng. Nếu thiếu cấu hình này, cryptokey routing không có key cho destination và không có quyền cho source.

Phần việc của kernel: trên B, net.ipv4.ip_forward phải là 1, nếu không kernel sẽ drop mọi packet đã decrypt nhưng không được gửi đến chính B. Forward chain của firewall trên B cũng phải cho phép traffic này. Các host trong LAN cần có route quay lại 10.8.0.0/24, hoặc B phải áp dụng source NAT để reply quay lại qua B.

WireGuard kết thúc nhiệm vụ khi giao packet đã giải mã cho kernel. Mọi bước sau đó đều là routing và filtering thông thường của Linux. Vì vậy, lỗi này xuất hiện trong các counter của nft list ruleset hoặc trong ip -s link show wg0, chứ không phải trong wg show. Nếu muốn quản lý peer qua web interface, chạy wg-easy trong Docker sẽ tự tạo các peer entry cho bạn. Tuy nhiên, forwarding rule vẫn thuộc về host. Cách phân tách này vẫn được giữ nguyên khi một coordination layer phân phối prefix cho bạn: quảng bá mạng riêng từ VPS bằng Tailscale subnet router thay cho việc chỉnh sửa AllowedIPs thủ công trên từng peer. Tuy nhiên, forwarding sysctl và firewall rule trên chính router vẫn do bạn cấu hình.

Vì sao WireGuard chạy trong kernel

wg0 là một network device driver. Packet đi qua nó bằng routing stack thông thường, được mã hóa trong softirq context, rồi đi ra qua một UDP socket mà không cần chuyển sang userspace. Đây là lý do WireGuard đạt throughput cao. Đây cũng là lý do module chỉ có khoảng bốn nghìn dòng code, đủ nhỏ để review và được merge vào mainline Linux 5.6 vào tháng 3 năm 2020. Ubuntu 24.04 và Debian 13 đã ship module này, nên chỉ còn thiếu package wireguard-tools.

Việc hoạt động như một interface thông thường có các hệ quả thực tế. tcpdump -ni wg0 hiển thị các inner packet dạng plaintext, còn tcpdump -ni eth0 udp port 51820 hiển thị các outer packet đã mã hóa. So sánh hai bên sẽ cho biết ngay hướng nào đang bị lỗi. netfilter và traffic shaping xử lý wg0 như mọi link khác. Khi không có kernel module, chẳng hạn trong container virtualisation dùng chung kernel của host, wireguard-go triển khai cùng protocol trong userspace thông qua một TUN device. Cách này làm throughput giảm đáng kể vì mỗi packet phải đi qua ranh giới kernel hai lần.

WireGuard không bảo vệ bạn khỏi những gì

Mô hình mối đe dọa được cố ý giới hạn, và một protocol kín đáo như vậy dễ khiến người dùng suy diễn sai. Hãy nói rõ:

  • WireGuard không che giấu việc bạn đang sử dụng WireGuard. Các handshake message có kích thước cố định, byte đầu tiên cho biết loại message, và transport là UDP. Deep packet inspection dễ dàng nhận diện protocol này, còn network không cho phép VPN có thể chặn nó. Obfuscation bị cố ý loại khỏi thiết kế.
  • WireGuard không che giấu lưu lượng hoặc thời điểm truyền. Payload chỉ được padding đến boundary 16-byte, vì vậy bên quan sát vẫn biết khi nào bạn gửi dữ liệu và ước lượng được dung lượng.
  • WireGuard lưu endpoint được biết gần nhất. Peer có public address sẽ lưu public IP hiện tại của phía bên kia, và wg show hiển thị địa chỉ đó. Kết hợp với tunnel address cố định trong config, thông tin này trở thành một identifier ổn định có thể theo dõi user giữa các network. Trên VPS của bạn, điều này không có vấn đề. Đây cũng là lý do các dịch vụ thương mại bổ sung một layer phía trên protocol.
  • WireGuard xác thực key, không xác thực người dùng. Bất kỳ ai giữ file private key đều là peer. Đặt /etc/wireguard ở mode 700 và các file key ở mode 600.
  • WireGuard không có revocation list và không có thời hạn. Quyền truy cập kết thúc khi bạn xóa entry của peer khỏi mọi server đang lưu entry đó, còn static key vẫn có hiệu lực cho đến khi bạn xóa chúng.

Không điều nào trong số này khiến WireGuard yếu. Nó khiến WireGuard nhỏ gọn, và đó chính là mục đích: WireGuard xác thực và mã hóa, còn việc quản lý identity và cấp phát address được giao cho thành phần bạn xây dựng phía trên. Một coordination layer như mô tả trong So sánh WireGuard với Tailscale tồn tại để lấp đúng khoảng trống đó, bằng cách sử dụng cùng data plane mà bạn vừa đọc. Khi layer này đã hoạt động, quyết định tiếp theo là ai được phép truy cập một service bạn chạy bên trong tunnel. Đây là điều lựa chọn giữa Tailscale serve và funnel giải quyết cho một port duy nhất.

FAQ

Cryptokey routing trong WireGuard là gì?

Cryptokey routing là quy tắc liên kết từng packet với một public key. Mỗi entry của peer chứa một danh sách prefix trong AllowedIPs. Khi gửi đi, WireGuard chọn peer bằng cách so khớp destination của packet với danh sách của từng peer, ưu tiên prefix dài nhất, nên danh sách này hoạt động như một routing table. Khi nhận vào, sau khi packet được decrypt và authenticate, source address bên trong packet phải nằm trong chính danh sách của peer đó. Nếu không, packet sẽ bị drop. Vì vậy, danh sách này đồng thời hoạt động như một access control list. WireGuard không có cấu hình routing riêng và cũng không có internal firewall riêng, vì một danh sách đó đảm nhiệm cả hai chức năng.

Tôi có cần PersistentKeepalive trên cả hai peer không?

Không. Hãy đặt nó ở phía nằm sau NAT (network address translation) hoặc stateful firewall. Đây thường là client. WireGuard không gửi gì khi idle, nên mapping cho phép phía bên kia kết nối đến peer đó sẽ hết hạn, thường trong vòng một phút. Khi đó tunnel trông như bị chết theo một chiều. PersistentKeepalive = 25 gửi một packet rỗng đã được authenticate mỗi 25 giây và giữ mapping mở. Peer có public address và UDP port đang mở thì không cần PersistentKeepalive.

Tại sao ping qua tunnel báo lỗi "Required key not available"?

Vì destination address không nằm trong AllowedIPs của bất kỳ peer nào. Do đó, cryptokey routing không tìm thấy key để encrypt packet và kernel từ chối gửi packet. Chạy wg show wg0 allowed-ips rồi so sánh output với address bạn đang ping. Lỗi tương tự Destination address required là một vấn đề khác: một peer đã được match, nhưng WireGuard không có endpoint cho peer đó vì chưa có endpoint nào được cấu hình và chưa có packet nào được authenticate từ peer đó gửi đến.

Firewall có thể phát hiện và block WireGuard không?

Có. WireGuard authenticate và encrypt traffic của bạn, đồng thời không cố che giấu chính nó. Handshake message có kích thước cố định là 148 và 92 bytes. Byte đầu tiên của mỗi message xác định loại message. Transport sử dụng UDP, nên deep packet inspection có thể nhận diện protocol mà không gặp khó khăn. Các network block UDP hoặc fingerprint protocol sẽ chặn được WireGuard. Muốn ẩn tunnel, bạn phải bọc nó trong một thứ khác. Đây là một tool riêng, không phải setting của WireGuard.