Bối cảnh & Lý do
Kết nối hạ tầng on-premises với AWS, Azure hoặc Google Cloud là một thách thức thường gặp. Khi thiết lập tunnel IPsec site-to-site trên Linux, bạn cần lựa chọn giữa hai mô hình: policy-based hoặc route-based.
Các mô hình IPsec truyền thống phụ thuộc vào tunnel dạng policy-based. Linux kernel sẽ kiểm tra mọi gói tin đi ra dựa trên Security Policy Database (SPD). Nếu một gói tin khớp với cặp subnet đã cấu hình—ví dụ 192.168.1.0/24 kết nối tới 10.0.0.0/16—kernel sẽ mã hóa gói tin đó.
Mô hình này hoạt động được nhưng việc khắc phục sự cố rất phức tạp. Bạn sẽ không thấy bất kỳ network interface nào trong lệnh ip link. Bảng định tuyến chuẩn của Linux cũng không thể điều hướng lưu lượng vào tunnel. Tệ hơn nữa, mỗi khi đội ngũ DevOps triển khai thêm một subnet mới như 10.50.0.0/16, bạn buộc phải cập nhật lại ipsec.conf và tái thương lượng Security Associations (SAs), gây gián đoạn kết nối tạm thời.
Route-based VPN giải quyết triệt để vấn đề kiến trúc này bằng cách gắn IPsec vào một Virtual Tunnel Interface (VTI). Thay vì so khớp các dải IP cố định, các policy mã hóa được liên kết với một numeric mark, ví dụ mark = 42. Tunnel sẽ thương lượng với selector dạng đại diện (0.0.0.0/0 <=> 0.0.0.0/0) và định tuyến lưu lượng qua một interface ảo có tên là vti0.
Bạn có thể thao tác với vti0 như một card mạng vật lý thông thường. Bạn có thể chuyển tiếp các subnet bằng lệnh ip route add, giám sát băng thông theo thời gian thực với iftop, lọc lưu lượng qua nftables, hoặc chạy các dynamic routing daemon như FRRouting (FRR) để dự phòng chuyển đổi BGP failover.
Chúng ta sẽ cấu hình một tunnel hoàn chỉnh giữa hai endpoint qua mạng internet công cộng:
- Local Gateway (Văn phòng / Data Center): IP Public
198.51.100.10, LAN nội bộ192.168.1.0/24, IP Tunnel VTI10.254.0.1/30. - Cloud Gateway (AWS EC2 / GCP Compute Engine / Remote VM): IP Public
203.0.113.20, Cloud VPC10.0.0.0/16, IP Tunnel VTI10.254.0.2/30. - XFRM Mark ID:
42(liên kết interface VTI với trạng thái IPsec của kernel).
Cài đặt
Các lệnh dưới đây áp dụng cho Ubuntu 22.04/24.04 và Debian 12. Nếu bạn sử dụng RHEL hoặc Rocky Linux, hãy thay apt bằng dnf và cài đặt strongswan cùng strongswan-libcharon.
1. Cài đặt các gói strongSwan
Cài đặt strongSwan và các module mã hóa từ repository chính thức:
sudo apt update
sudo apt install -y strongswan strongswan-pki libcharon-extra-plugins
2. Kích hoạt các Kernel Module của Linux
VTI cần hai kernel module để hoạt động: ip_vti và xfrm4_tunnel. Nạp chúng ngay lập tức:
sudo modprobe ip_vti
sudo modprobe xfrm4_tunnel
Để các driver này tự động nạp lại sau khi reboot, hãy lưu chúng vào /etc/modules-load.d/ipsec-vti.conf:
echo "ip_vti" | sudo tee -a /etc/modules-load.d/ipsec-vti.conf
echo "xfrm4_tunnel" | sudo tee -a /etc/modules-load.d/ipsec-vti.conf
3. Tối ưu hóa tham số Kernel (sysctl)
Do gateway làm nhiệm vụ chuyển tiếp gói tin giữa các interface, IP forwarding bắt buộc phải được bật. Ngoài ra, cơ chế Reverse Path Filter (rp_filter) của Linux mặc định ở chế độ nghiêm ngặt (1). Chế độ nghiêm ngặt này sẽ âm thầm drop các gói tin phản hồi đi ra từ vti0 do không khớp với bảng định tuyến khi kiểm tra tính hợp lệ. Thiết lập chế độ lỏng (2) sẽ ngăn chặn tình trạng mất gói tin ngầm này.
Ghi các thiết lập mạng này vào /etc/sysctl.d/99-ipsec-vti.conf:
sudo tee /etc/sysctl.d/99-ipsec-vti.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.disable_policy = 1
net.ipv4.conf.all.disable_policy = 1
EOF
sudo sysctl --system
Cấu hình
Bước 1: Cấu hình chính sách IPsec trên strongSwan
Mở file /etc/ipsec.conf trên gateway nội bộ. Cả leftsubnet và rightsubnet đều dùng 0.0.0.0/0 để bắt toàn bộ lưu lượng được định tuyến. Tham số mark=42 chỉ thị cho lớp XFRM kernel chỉ mã hóa những gói tin được gắn ID 42:
sudo tee /etc/ipsec.conf <<'EOF'
config setup
charondebug="ike 2, knl 2, cfg 2, net 1, esp 1"
uniqueids=yes
conn %default
keyexchange=ikev2
ike=aes256-sha256-modp2048!
esp=aes256-sha256-modp2048!
dpdaction=restart
dpddelay=30s
dpdtimeout=120s
auto=start
conn vti-tunnel
left=198.51.100.10
leftid=198.51.100.10
leftsubnet=0.0.0.0/0
right=203.0.113.20
rightid=203.0.113.20
rightsubnet=0.0.0.0/0
mark=42
authby=secret
type=tunnel
EOF
Bước 2: Lưu trữ Pre-Shared Key (PSK)
Tạo một khóa bí mật đủ mạnh (tối thiểu 32 ký tự) và lưu vào /etc/ipsec.secrets. Thiết lập quyền truy cập chặt chẽ để chỉ root mới có thể đọc:
sudo tee /etc/ipsec.secrets <<EOF
198.51.100.10 203.0.113.20 : PSK "K9#mQ2$zL8!vP5*xR7@wT1^bY4&cN3(e"
EOF
sudo chmod 600 /etc/ipsec.secrets
Bước 3: Khởi tạo thiết bị VTI
Liên kết interface tunnel của Linux với mark 42. MTU tiêu chuẩn của Ethernet là 1500 byte. Quá trình đóng gói IPsec với ESP và SHA256 làm tăng khoảng 50 đến 73 byte overhead, do đó hãy hạ MTU của tunnel xuống 1418 để tránh phân mảnh gói tin:
# Tạo thiết bị tunnel liên kết với mark 42
sudo ip tunnel add vti0 mode vti local 198.51.100.10 remote 203.0.113.20 key 42
# Cấu hình IP point-to-point /30 cho tunnel
sudo ip addr add 10.254.0.1/30 peer 10.254.0.2 dev vti0
# Bật interface hoạt động
sudo ip link set dev vti0 up
# Thiết lập MTU an toàn đã trừ overhead của IPsec
sudo ip link set dev vti0 mtu 1418
# Áp dụng loose path filtering cho interface ảo mới
sudo sysctl -w net.ipv4.conf.vti0.rp_filter=2
sudo sysctl -w net.ipv4.conf.vti0.disable_policy=1
Bước 4: Định tuyến các Subnet Cloud qua vti0
Để điều hướng lưu lượng đến cloud VPC (10.0.0.0/16), bạn chỉ cần một bản ghi định tuyến duy nhất. Mọi gói tin đi qua vti0 sẽ được gắn mark 42, được XFRM tiếp nhận và mã hóa:
sudo ip route add 10.0.0.0/16 dev vti0
Cần bổ sung thêm một VPC phụ (như 172.16.0.0/20) sau này? Chỉ cần chạy thêm lệnh ip route add 172.16.0.0/20 dev vti0. Không cần tải lại IPsec, không gặp thời gian chết do tái thương lượng.
Bước 5: Tự động hóa vòng đời Interface
Các lệnh cấu hình thủ công sẽ mất đi sau khi reboot. Hãy sử dụng hook leftupdown của strongSwan để tự động bật vti0 khi Phase 2 được thiết lập và hạ interface xuống an toàn khi tunnel ngắt kết nối.
Lưu script này tại đường dẫn /usr/local/sbin/ipsec-vti.sh:
sudo tee /usr/local/sbin/ipsec-vti.sh <<'EOF'
#!/usr/bin/env bash
VTI_IF="vti0"
VTI_LOCAL="10.254.0.1/30"
VTI_REMOTE="10.254.0.2"
REMOTE_SUBNET="10.0.0.0/16"
VTI_KEY="42"
case "${PLUTO_VERB}" in
up-client)
ip tunnel add "${VTI_IF}" mode vti local "${PLUTO_ME}" remote "${PLUTO_PEER}" key "${VTI_KEY}"
ip addr add "${VTI_LOCAL}" peer "${VTI_REMOTE}" dev "${VTI_IF}"
ip link set dev "${VTI_IF}" mtu 1418 up
sysctl -w "net.ipv4.conf.${VTI_IF}.rp_filter=2" > /dev/null
sysctl -w "net.ipv4.conf.${VTI_IF}.disable_policy=1" > /dev/null
ip route replace "${REMOTE_SUBNET}" dev "${VTI_IF}"
;;
down-client)
ip link set dev "${VTI_IF}" down 2>/dev/null
ip tunnel del "${VTI_IF}" 2>/dev/null
;;
esac
EOF
sudo chmod +x /usr/local/sbin/ipsec-vti.sh
Thêm dòng leftupdown=/usr/local/sbin/ipsec-vti.sh vào bên trong khối conn vti-tunnel trong file /etc/ipsec.conf.
Bước 6: Khởi động strongSwan
Khởi động lại service để kích hoạt IKE daemon và thiết lập kết nối:
sudo systemctl restart strongswan-starter
Xác minh & Giám sát
Kiểm tra kết nối qua bốn giai đoạn: trạng thái IKE/ESP, chỉ số interface tunnel, ping đầu cuối và bắt gói tin trực tiếp.
1. Kiểm tra IKE và ESP SA
Kiểm tra state machine của strongSwan bằng lệnh ipsec statusall:
sudo ipsec statusall
Quan sát trạng thái kết nối Phase 1 và Phase 2 đã được xác nhận:
Security Associations (1 up, 0 connecting):
vti-tunnel[1]: ESTABLISHED 3 minutes ago, 198.51.100.10[198.51.100.10]...203.0.113.20[203.0.113.20]
vti-tunnel{1}: INSTALLED, TUNNEL, reqid 1, ESP SPIs: c3a48e10_a2099df3
vti-tunnel{1}: 0.0.0.0/0 === 0.0.0.0/0, mark 42
Trạng thái INSTALLED tại dòng 0.0.0.0/0 === 0.0.0.0/0, mark 42 xác nhận kernel đã sẵn sàng mã hóa các gói tin khớp với mark 42.
2. Kiểm tra bộ đếm trên Interface
Xác minh xem các gói tin có đang truyền qua interface ảo hay không:
ip -s link show dev vti0
Kiểm tra các bộ đếm byte RX và TX. Nếu TX tăng liên tục nhưng RX vẫn bằng 0, hãy kiểm tra lại firewall phía cloud (AWS Security Group hoặc GCP Firewall Rule). Đảm bảo các cổng UDP 500 và 4500, cùng IP protocol 50 (ESP), đã được mở.
3. Kiểm tra độ trễ đầu cuối và MTU
Gửi gói tin ICMP echo request đến một máy chủ bên trong cloud VPC (chẳng hạn như 10.0.1.15):
ping -c 4 10.0.1.15
Tiếp theo, kiểm tra các sự cố liên quan đến Path MTU. Gửi gói tin 1418 byte có gắn cờ Don’t Fragment (DF) (1390 byte ICMP payload + 20 byte IPv4 header + 8 byte ICMP header = 1418 byte):
ping -M do -s 1390 -c 2 10.0.1.15
Nếu bạn thấy thông báo Frag needed and DF set (mtu = 1418), hãy giảm kích thước payload xuống từng bước 8 byte cho đến khi gói tin đi qua thành công, sau đó hạ MTU trên vti0 tương ứng. Bạn cũng có thể áp dụng cơ chế TCP MSS clamping qua iptables để tránh hiện tượng rớt gói tin đối với các luồng HTTP/SSH kích thước lớn:
sudo iptables -t mangle -A FORWARD -o vti0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
4. Bắt gói tin theo thời gian thực
Xác thực quá trình mã hóa bằng cách quan sát lưu lượng cleartext trên vti0 và ciphertext trên card mạng WAN (eth0).
Trên interface public, xác nhận rằng chỉ có các gói ESP đã mã hóa xuất hiện:
sudo tcpdump -n -i eth0 host 203.0.113.20 and proto 50
Trong một terminal khác, theo dõi vti0 để thấy payload ứng dụng chưa mã hóa:
sudo tcpdump -n -i vti0 icmp
Nếu eth0 nhận được gói tin ESP nhưng vti0 không hiển thị gì, hãy kiểm tra qua dmesg | grep xfrm. Nguyên nhân thường xuất phát từ reverse path filtering hoặc mark ID chưa khớp.

