Tiếp nối bài trước, Câu chuyện sử dụng VPN, tôi xin mô tả cách dựng một mạng VPN cho mục đích cá nhân bằng phần mềm WireGuard.
Mục đích sử dụng mạng VPN của tôi chỉ là để truy cập từ xa vào các thiết bị IoT của tôi nên mô hình VPN của tôi chỉ phù hợp với nhu cầu đó. Nếu bạn cần dùng VPN cho mục đích khác, ví dụ để vượt tường lửa, truy cập vào website bị chặn, thì bạn không nên trông đợi gì vào bài viết này.
Trước khi bắt tay vào việc, ta cần mường tượng sơ đồ mạng sẽ như thế nào:
Phải luôn có một máy có địa chỉ IP tĩnh làm server. Nó cũng đóng vai trò router trong mạng ảo (VPN) được tạo ra. Các gói tin từ máy con A sẽ được mã hóa, gửi lên server, server sẽ chuyển tiếp tới máy con B rồi gói tin được giải mã tại đó.
Ta sẽ chọn dải IP cho mạng ảo này. Ví dụ tôi chọn dải 192.168.2.0/24. Trong thực tế, ta cần chọn dải nào sao cho không đụng chạm đến bất cứ mạng LAN thật nào của từng máy con. Vì server đóng vai trò router nên ta sẽ dành địa chỉ 192.168.2.1 cho nó.
Cài đặt WireGuard
Việc cài đặt WireGuard thì khá đơn giản, đã có trên tài liệu. Trước khi cài WireGuard thì bạn phải cài trước header source code của Linux [^1]. Chuyện này khá đơn giản trên desktop và server, vì câu lệnh để cài WireGuard, sudo apt install wireguard
sẽ tự kéo gói header của Linux về, tự cài. Nhưng trên máy tính nhúng như BeagleBone, Raspberry Pi, bạn phải tự chỉ định và cài gói header đó.
Trên Raspberry Pi, tên gói là raspberrypi-kernel-headers
, nên bạn cài bằng:
sudo apt install raspberrypi-kernel-headers
Trên BeagleBone thì phức tạp hơn. Đầu tiên bạn phải xác định xem BeagleBone của bạn đang chạy Linux phiên bản nào:
$ uname -a
Linux beaglebone 4.4.88-ti-r125 #1 SMP Thu Sep 21 19:23:24 UTC 2017 armv7l GNU/Linux
Tìm kiếm gói header theo tên linux-headers-[version]
:
$ apt search linux-headers-4.4.88-ti-r125
Sorting... Done
Full Text Search... Done
linux-headers-4.4.88-ti-r125/unknown,now 1stretch armhf
Linux kernel headers for 4.4.88-ti-r125 on armhf
Ok, vậy là gói linux-headers-4.4.88-ti-r125
có tồn tại trong repo, hãy cài đặt nó:
$ sudo apt install linux-headers-4.4.88-ti-r125
Trong trường hợp bạn cấu hình APT repo cho Raspberry Pi, bạn có thể gặp lỗi này:
$ sudo apt update
Get:1 http://raspbian.raspberrypi.org/raspbian stretch InRelease [15.0 kB]
Get:2 http://deb.debian.org/debian unstable InRelease [233 kB]
Hit:3 http://archive.raspberrypi.org/debian stretch InRelease
Get:4 http://raspbian.raspberrypi.org/raspbian stretch/main armhf Packages [11.7 MB]
Err:2 http://deb.debian.org/debian unstable InRelease
The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 8B48AD6246925553 NO_PUBKEY 7638D0442B90D010 NO_PUBKEY 04EE7237B7D453EC
Reading package lists... Done
W: GPG error: http://deb.debian.org/debian unstable InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 8B48AD6246925553 NO_PUBKEY 7638D0442B90D010 NO_PUBKEY 04EE7237B7D453EC
E: The repository 'http://deb.debian.org/debian unstable InRelease' is not signed.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
Có thể khắc phục bằng cách chạy lệnh sau:
$ sudo apt install dirmngr
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 04EE7237B7D453EC
Cấu hình cho WireGuard
Vì server sẽ đóng vai trò router, ta cần cấu hình kernel của server để cho phép "IPv4 forwarding":
$ echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-ipv4_forwarding.conf
$ sudo sysctl -p /etc/sysctl.d/99-ipv4_forwarding.conf
Trên từng máy, ta làm theo hướng dẫn trên trang chủ để tạo key. Các file sinh ra privatekey, publickey nằm đâu cũng được, vì sau khi cấu hình xong ta có thể xóa nó đi.
Trên máy con A, ta tạo file /etc/wireguard/wg0.conf với nội dung như sau:
[Interface]
PrivateKey = private_key_cua_may_A
Address = 192.168.2.2/24
[Peer]
PublicKey = public_key_cua_server
Endpoint = ip_cua_server:51820
AllowedIPs = 192.168.2.0/24
Trên máy con B, ta tạo file tương tự, với nội dung như sau:
[Interface]
PrivateKey = private_key_cua_may_B
Address = 192.168.2.3/24
[Peer]
PublicKey = public_key_cua_server
Endpoint = ip_cua_server:51820
AllowedIPs = 192.168.2.0/24
Trên server, ta tạo file ở đường dẫn giống vậy, với nội dung như sau:
[Interface]
Address = 192.168.2.1/24
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens18 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o ens18 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens18 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o ens18 -j MASQUERADE
ListenPort = 51820
PrivateKey = private_key_cua_server
[Peer]
# May A
PublicKey = public_key_cua_may_A
AllowedIPs = 192.168.2.2/32
[Peer]
# May B
PublicKey = public_key_cua_may_B
AllowedIPs = 192.168.2.3/32
Lưu ý sự khác nhau giữa các file trên:
-
File cấu hình trên máy con thì chỉ cần 1 mục
[Peer]
để điền thông tin của server. -
File cấu hình trên server thì có nhiều mục
[Peer]
, mỗi cái tương ứng với một máy con. -
Vì server đóng vai trò router nên ta có một chùm cấu hình
iptables
kèm theo. Trong các dòngiptables
này, bạn cần thayens18
bằng tên card mạng trên server của bạn (xem danh sách card mạng bằng lệnhip link
hay ngắn gọn hơn,ip l
).
Một vài lưu ý khác:
- Đừng bao giờ xài
AllowedIPs = 0.0.0.0/0
trên file cấu hình của client (máy con), như một số bài hướng dẫn khác trên mạng, nếu không chức năng DNS sẽ tê liệt.
Kiểm tra
Sau khi ghi các file cấu hình kể trên, thử chạy
$ sudo wg-quick up wg0
$ ip a
để xem đã có network interface nào tên wg0
được tạo ra và gán địa chỉ chưa.
Ví dụ, đây là kết quả trên máy tôi:
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eno1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
link/ether a0:2b:b8:24:0b:55 brd ff:ff:ff:ff:ff:ff
3: wlo1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 9c:d2:1e:7d:f7:e7 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.88/24 brd 192.168.1.255 scope global dynamic noprefixroute wlo1
valid_lft 79700sec preferred_lft 79700sec
inet6 2001:ee0:5511:860:a8c1:937b:b26:8095/64 scope global temporary dynamic
valid_lft 1198sec preferred_lft 1198sec
inet6 2001:ee0:5511:860:64ca:7ee4:ac78:5940/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 1198sec preferred_lft 1198sec
inet6 fe80::236c:9478:b305:1c05/64 scope link noprefixroute
valid_lft forever preferred_lft forever
4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 192.168.2.2/24 scope global wg0
valid_lft forever preferred_lft forever
Bảng route:
$ ip r
default via 192.168.1.1 dev wlo1 proto dhcp metric 600
169.254.0.0/16 dev wlo1 scope link metric 1000
192.168.1.0/24 dev wlo1 proto kernel scope link src 192.168.1.88 metric 600
192.168.2.0/24 dev wg0 proto kernel scope link src 192.168.2.2
Trên các máy con, thử ping về server bằng địa chỉ của mạng VPN:
ping 192.168.2.1
Trên máy con A, thử ping đến máy con B bằng địa chỉ của mạng VPN:
ping 192.168.2.3
Nếu việc ping thành công thì chúc mừng, bạn đã hoàn tất 95%.
Duy trì mạng VPN
Vì mạng VPN chỉ được tạo ra bằng lệnh wg-quick up
, nên sau khi bạn khởi động lại máy, mạng sẽ biến mất. WireGuard có cung cấp file systemd service để máy tự chạy wq-quick up
mỗi lần khởi động. Kích hoạt service này trên server bằng lệnh:
$ sudo systemctl enable wg-quick@wg0
Trong trường hợp của tôi, tôi cần truy cập từ laptop (máy A) vào board Raspberry Pi (máy B) nên tôi cũng cần kích hoạt service kia trên máy B. Riêng laptop thì không cần.
Làm được tới đây, nếu bạn khởi động lại máy B, bạn sẽ không ping được từ A vào B, cho dù wg0
vẫn đang hoạt động trên máy B và trên server. Tại sao? Đó là vì khi gói tin từ A lên server, server không biết máy B ở đâu để mà chuyển tiếp đến (nên nhớ là máy B đang nằm khuất sau 1 router nào đó nên nó không có địa chỉ IP public). Muốn server biết máy B ở đâu thì phải có 1 sự tương tác giữa máy B và server. Ta phải duy trì sự tương tác này để vị trí của máy B luôn được cập nhật. Để làm việc này, ta thêm tùy chọn PersistentKeepalive
vào file của máy B như sau:
[Interface]
PrivateKey = private_key_cua_may_B
Address = 192.168.2.3/24
[Peer]
PublicKey = public_key_cua_server
Endpoint = ip_cua_server:51820
AllowedIPs = 192.168.2.0/24
PersistentKeepalive = 60
Thông số trên có nghĩa là cứ 60s, WireGuard trên máy B sẽ gửi một gói tin đặc biệt đến server để duy trì kết nối VPN.
Phụ chú
Trước đây, khi tôi chưa biết đến tham số PersistentKeepalive
thì giải pháp của tôi là làm cho máy B cứ tự động ping đến server sau mỗi khoảng thời gian nhất định. Tôi sẽ ứng dụng systemd timer để làm việc này.
Tạo 2 file như sau trên máy B:
ping-wireguard-server.service:
[Unit]
Description=Ping WireGuard server
[Service]
Type=oneshot
ExecStart=/bin/ping -c 2 192.168.2.1
ping-wireguard-server.timer:
[Unit]
Description=Ping WireGuard server every minute
[Timer]
OnBootSec=2
OnUnitActiveSec=60
[Install]
WantedBy=timers.target
Copy 2 file trên vào /usr/local/lib/system/system/ rồi chạy lệnh sau để kích hoạt timer:
$ sudo systemctl enable ping-wireguard-server.timer
$ sudo systemctl start ping-wireguard-server.timer
Bạn cũng có thể làm công cụ ping tương tự với cron
, nhưng tôi không thích cron lắm.
Truy cập SSH mà không cần bật WireGuard
Thật ra, nếu chỉ để SSH thì ta chỉ cần duy trì mạng VPN trên máy đích chứ không cần bật WireGuard trên máy của ta. Khi đó ta sẽ sử dụng server làm "đá bước dặm" để "nhảy cóc" đến máy bên kia, và kết nối từ máy ta đến server vẫn là kết nối trực tiếp, không qua VPN. Ví dụ ta sẽ dùng tính năng ProxyCommand của SSH.
Đầu tiên, mở file ~/.ssh/config và đặt lối tắt cho server:
Host my-server
User user_tren_server
Hostname ip_cua_server
Trong đó ip_cua_server
là IP trực tiếp, không phải IP trong mạng VPN của server. Tiếp theo, tạo lối tắt cho máy đích
Host may-b
User user_tren_may_b
Hostname ip_vpn_may_b
ProxyJump my-server
trong đó, ip_vpn_may_b
là IP trong mạng VPN của máy đích.
Như vậy, chỉ cần gõ:
$ ssh may-b
ta đã có thể SSH vào máy B.
Xem tiếp bài viết liên quan: Khắc phục tình trạng VPN WireGuard bị treo
Một số góp ý của độc giả
(Góp ý của @VuNhanUit)
1. Có thể cài linux-headers theo version của kernel ngắn hơn bằng lệnh sau:
apt install linux-headers-$(uname -r)
2. Gần đây wireguard-tools
trên repo unstable
của debian bị outdated, gây ra conflict nếu cài thẳng wireguard
bằng apt. Cách giải quyết: tải gói wireguard-tools.deb
về cài tay, rồi mới cài wireguard được.
Chú thích:
[^1] Khi viết bài này, năm 2018, thì WireGuard chưa được kèm sẵn trong nhân Linux trong các distro, nên quá trình cài phải có bước biên dịch module động, nên cần đến source header của nhân. Các phiên bản mới hơn đã kèm sẵn WireGuard trong nhân Linux nên ta không cần "cài".