--- title: Dựng mạng VPN với WireGuard date: 2018-09-30 03:15:39.389362 UTC --- Tiếp nối bài trước, [Câu chuyện sử dụng VPN](/blog/2018/9/65-cau-chuyen-su-dung-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](https://www.wireguard.com). 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: ![Sơ đồ](https://quan-images.b-cdn.net/blogs/imgur/2026/fVI8pMu.png) 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](https://www.wireguard.com/install/#packages). 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: ```sh 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: ```sh $ 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ó: ```sh $ 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: ```sh $ 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": ```sh $ 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](https://www.wireguard.com/quickstart/#key-generation) 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: ```ini [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: ```ini [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: ```ini [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òng `iptables` này, bạn cần thay `ens18` bằng tên card mạng trên server của bạn (xem danh sách card mạng bằng lệnh `ip 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 ```sh $ 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: 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: 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: 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: 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: ```sh 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: ```sh 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: ```sh $ 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: ```ini [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_: ```ini [Unit] Description=Ping WireGuard server [Service] Type=oneshot ExecStart=/bin/ping -c 2 192.168.2.1 ``` _ping-wireguard-server.timer_: ```ini [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: ```sh $ 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: ```txt 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 ```txt 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õ: ```sh $ 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](https://quan.hoabinh.vn/blog/2021/6/91-khac-phuc-tinh-trang-vpn-wireguard-bi-treo) ### Một số góp ý của độc giả (Góp ý của [@VuNhanUit](https://twitter.com/VuNhanUit)) 1\. Có thể cài linux-headers theo version của kernel ngắn hơn bằng lệnh sau: ```sh 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".