Cấu hình Captive Portal với CoovaChilli và FreeRADIUS trên Linux để bảo mật Wi-Fi cho khách

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

Vào lúc 2:15 sáng thứ Bảy, máy nhắn tin của tôi báo động. Một subnet khách /23 mở tại chi nhánh khu vực của chúng tôi đã bị tràn ngập bởi các thiết bị cá nhân trái phép, làm cạn kiệt toàn bộ 508 DHCP lease chỉ trong vòng chưa đầy 20 phút.

Tệ hơn nữa, lưu lượng broadcast không kiểm soát đã làm tê liệt switch uplink và khiến các DNS resolver nội bộ bị nghẽn hoàn toàn. Các mạng khách mở không có sự cô lập chặt chẽ ở Layer 2/3 chẳng khác nào những quả bom hẹn giờ. Để chặn đứng lưu lượng bất hợp pháp ngay từ ranh giới mạng, bạn cần một access gateway có khả năng chặn bắt lưu lượng trước khi quá trình định tuyến diễn ra, bắt buộc thực hiện bắt tay xác thực và áp đặt giới hạn băng thông nghiêm ngặt trên từng phiên.

Đánh giá các kiến trúc Gateway cho mạng khách

Các kỹ sư thường lựa chọn giữa ba mô hình phổ biến khi cần cô lập lưu lượng mạng khách công cộng chịu tải cao.

Kiến trúc Cơ chế điều hướng Accounting & Giới hạn tốc độ Đánh đổi trong vận hành
iptables / nftables tự viết + Web App Quy tắc DNAT kết hợp danh sách ipset động Các hàng đợi Linux tc (Traffic Control) thủ công và cron script Trạng thái conntrack thiếu ổn định; rò rỉ bộ nhớ khi lượng kết nối đóng mở liên tục ở tần suất cao.
Thiết bị nhúng (pfSense / OpenWrt) Engine portal tích hợp sẵn (Nodogsplash, pfSense captiveportal) Cơ sở dữ liệu SQLite cục bộ hoặc module RADIUS client tiêu chuẩn Giới hạn phần cứng của thiết bị chuyên dụng; gặp sự cố khi số lượng client đồng thời vượt quá 300–400 thiết bị.
CoovaChilli + FreeRADIUS trên Linux Driver TUN/TAP ở kernel (tun0) với khả năng chặn bắt UAM gốc RADIUS accounting theo RFC 2866, thuộc tính tốc độ WISPr, timeout phiên động Đòi hỏi tinh chỉnh daemon thủ công, nhưng dễ dàng xử lý hơn 2.500 phiên hoạt động trên phần cứng phổ thông khiêm tốn.

Việc chắp vá các quy tắc iptables thuần trông có vẻ nhanh chóng trên lý thuyết. Nhưng trong thực tế, tình trạng mất đồng bộ trạng thái connection tracking sẽ nhanh chóng làm hỏng các phiên kết nối, và việc chặn bắt các kết nối HTTPS ra ngoài sẽ kích hoạt cảnh báo bảo mật TLS trên trình duyệt.

Các thiết bị phần cứng chuyên dụng cũng chật vật trong các đợt cao điểm truy cập ồ ạt, chẳng hạn như trong một buổi thuyết trình hội nghị. CoovaChilli giải quyết triệt để những cạm bẫy này. Bằng cách đẩy quyền kiểm soát truy cập sang một interface TUN riêng biệt và ủy quyền xác thực cho FreeRADIUS, hệ thống mang lại khả năng cô lập phiên ở cấp độ nhà mạng ngay trên phần cứng Linux tiêu chuẩn.

Đánh đổi thực tế trong môi trường Production: Stack CoovaChilli + FreeRADIUS

Trước khi chuẩn hóa hệ thống theo stack này, hãy cân nhắc kỹ giữa lợi thế hiệu năng và chi phí bảo trì của nó.

Ưu điểm cốt lõi

  • Cô lập lưu lượng ở cấp độ Kernel: CoovaChilli quản lý một interface ảo chuyên dụng (tun0). Những thiết bị khách chưa xác thực sẽ không thể tiếp cận gateway uplink hay chạm tới các subnet nội bộ như 192.168.1.0/24.
  • Accounting AAA chuẩn hóa: FreeRADIUS ghi lại chính xác bộ đếm byte và thời lượng phiên thông qua các gói tin RADIUS accounting. Bạn có thể dễ dàng truy vấn dung lượng sử dụng qua PostgreSQL, MySQL hoặc thư mục LDAP.
  • Thiết lập Walled Garden chi tiết: Bạn có thể cho phép các truy vấn DNS, các endpoint thanh toán như Stripe (*.stripe.com), và các máy chủ xác thực CRL/OCSP hoạt động mà không cần mở toàn bộ quyền truy cập web.
  • QoS động theo từng người dùng: FreeRADIUS trả về các thuộc tính VSA của WISPr khi xác thực thành công. CoovaChilli sẽ áp dụng ngay các token bucket riêng cho luồng tải lên và tải xuống mà không cần viết script tc thủ công.

Điểm nghẽn trong vận hành

  • Overhead từ MTU và Tunnel: CoovaChilli bổ sung đóng gói qua tunnel. Nếu bạn giữ nguyên MTU ở mức 1500 mà không kẹp MSS về 1460 byte, client sẽ gặp hiện tượng treo bắt tay TLS và lỗi hỏng file khi tải xuống.
  • Đặc tính phát hiện HTTPS trên thiết bị hiện đại: Điện thoại thông minh ngày nay sử dụng các probe chuyên dụng, như Apple Captive Network Assistant (CNA) và Android ConnectivityCheck. Web server của bạn phải xử lý chính xác các endpoint thăm dò này, nếu không màn hình đăng nhập sẽ không thể tự động bật lên.
  • Gỡ lỗi trên nhiều dịch vụ: Quá trình khắc phục sự cố đòi hỏi phải kiểm tra ba lớp riêng biệt: trạng thái daemon CoovaChilli, hook chuyển hướng của Nginx, và nhật ký xác thực của FreeRADIUS.

Kiến trúc Production khuyến nghị

Đối với môi trường phục vụ từ 500 đến 1.500 khách kết nối đồng thời, hãy triển khai Ubuntu 22.04 hoặc Debian 12 trên một gateway có hai cổng mạng (ví dụ: 4 vCPU, 8 GB RAM, 2 cổng mạng 1GbE/10GbE):

  • WAN (eth0): Nhận IP transit chiều ra (ví dụ: 198.51.100.2/29) với tính năng NAT chiều ra (MASQUERADE) được bật trong nhân Linux.
  • VLAN Trunk cho mạng khách (eth1): Kết nối trực tiếp đến VLAN trunk của access point Wi-Fi khách (ví dụ: VLAN 100). Hãy để interface vật lý này ở trạng thái không gán IP; CoovaChilli sẽ liên kết trực tiếp với nó tại tầng liên kết dữ liệu (link layer).

Việc phân tách interface vật lý này đảm bảo các broadcast frame không dây trái phép sẽ không bao giờ lọt vào bảng định tuyến của mạng doanh nghiệp.

Các bước triển khai Gateway chi tiết

Thực hiện theo các bước sau để triển khai và cấu hình toàn bộ stack gateway trên máy chủ Ubuntu.

Bước 1: Tinh chỉnh Kernel và cài đặt các gói phụ thuộc

Bật tính năng chuyển tiếp gói tin IPv4 trong sysctl, đồng thời cài đặt các công cụ build, web stack và máy chủ FreeRADIUS cần thiết.

# Bật chuyển tiếp gói tin ở kernel và tối ưu hóa connection tracking
sudo tee -a /etc/sysctl.d/99-captive-portal.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv4.tcp_syncookies = 1
net.netfilter.nf_conntrack_max = 262144
EOF
sudo sysctl --system

# Cài đặt bộ công cụ build, các thư viện cần thiết, FreeRADIUS và Nginx
sudo apt update
sudo apt install -y build-essential libssl-dev libcurl4-openssl-dev \
    libjson-c-dev freeradius freeradius-utils nginx php8.1-fpm iptables-persistent

Bước 2: Cấu hình FreeRADIUS và chính sách người dùng

Đầu tiên, phân quyền cho CoovaChilli làm RADIUS client đáng tin cậy trên giao diện loopback. Chỉnh sửa file /etc/freeradius/3.0/clients.conf:

client coovachilli {
    ipaddr = 127.0.0.1
    secret = S3cur3Sh4redK3y#2026
    shortname = coovachilli
    require_message_authenticator = no
}

Tiếp theo, định nghĩa chính sách tài khoản khách và giới hạn băng thông trong /etc/freeradius/3.0/users. Lưu ý rằng các thuộc tính băng thông WISPr phải được khai báo theo đơn vị bit trên giây (bps):

# Hồ sơ khách: phiên tối đa 2 giờ, ngắt kết nối khi nhàn rỗi 10 phút, 10 Mbps tải xuống / 2 Mbps tải lên
guest_visitor Cleartext-Password := "WelcomeWifi2026!"
    Session-Timeout = 7200,
    Idle-Timeout = 600,
    ChilliSpot-Max-Input-Octets = 1073741824,
    ChilliSpot-Max-Output-Octets = 1073741824,
    WISPr-Bandwidth-Max-Down = 10485760,
    WISPr-Bandwidth-Max-Up = 2097152

Khởi động lại FreeRADIUS và kiểm tra tính hợp lệ của thông tin đăng nhập cục bộ bằng lệnh radtest:

sudo systemctl restart freeradius
radtest guest_visitor "WelcomeWifi2026!" 127.0.0.1 0 S3cur3Sh4redK3y#2026

Tiện ích sẽ trả về phản hồi Access-Accept ngay lập tức, hiển thị các thuộc tính WISPr-Bandwidth-Max-Down và Session-Timeout đã được cấu hình.

Bước 3: Biên dịch và cấu hình CoovaChilli

Biên dịch CoovaChilli với hỗ trợ SSL và proxy redirection để kích hoạt các quá trình bắt tay UAM an toàn:

cd /usr/local/src
sudo wget https://github.com/coova/coova-chilli/archive/refs/tags/1.6.tar.gz -O coovachilli-1.6.tar.gz
sudo tar -zxvf coovachilli-1.6.tar.gz
cd coova-chilli-1.6

sudo ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var \
    --enable-chilliproxy --enable-chilliredir --enable-binstatusfile --with-openssl
sudo make -j$(nproc)
sudo make install

Kế tiếp, cấu hình các ràng buộc interface mạng và thiết lập portal trong /etc/chilli/local.conf:

# Ràng buộc giao diện mạng
HS_WANIF=eth0             # Uplink chiều ra tới router
HS_LANIF=eth1             # Giao diện trunk thuê bao (không gán IP)
HS_NETWORK=10.10.0.0      # Subnet cấp phát cho các thiết bị khách
HS_NETMASK=255.255.248.0  # Subnet /21 hỗ trợ tối đa 2.046 địa chỉ cấp phát
HS_UAMLISTEN=10.10.0.1    # IP gateway ảo trên tun0
HS_UAMPORT=3990

# Cấu hình xác thực với RADIUS server
HS_RADIUS=127.0.0.1
HS_RADIUS2=127.0.0.1
HS_RADSECRET=S3cur3Sh4redK3y#2026

# Đích điều hướng của portal
HS_UAMSERVER=portal.guestwifi.net
HS_UAMHOMEPAGE=http://10.10.0.1:3990/prelogin
HS_UAMFORMAT=http://portal.guestwifi.net/login.php

# Danh sách ngoại lệ không cần xác thực (walled garden)
HS_UAMDOMAINS=".guestwifi.net,connectivitycheck.gstatic.com,captive.apple.com"
HS_DNS1=1.1.1.1
HS_DNS2=8.8.8.8

# Kẹp TCP MSS để chống phân mảnh gói tin qua tunnel
HS_TCPMSS=1460

Bước 4: Triển khai trang Splash UAM

Cấu hình Nginx phản hồi các yêu cầu tới portal.guestwifi.net trên địa chỉ 10.10.0.1:80. Khi một thiết bị chưa xác thực khởi tạo truy vấn web, CoovaChilli sẽ chặn bắt và truyền các tham số truy vấn đến trình xử lý đăng nhập của bạn:

<?php
// /var/www/html/login.php
$challenge = $_GET['challenge'] ?? '';
$userurl   = $_GET['userurl'] ?? 'http://connectivitycheck.gstatic.com/generate_204';
$uamip     = $_GET['uamip'] ?? '10.10.0.1';
$uamport   = $_GET['uamport'] ?? '3990';
?>
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="utf-8">
    <title>Xác thực Cổng Mạng Khách</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; text-align: center; padding: 40px;">
    <h2>Xác thực Mạng Khách</h2>
    <p>Nhập thông tin xác thực để truy cập internet:</p>
    <form method="GET" action="http://<?= htmlspecialchars($uamip) ?>:<?= htmlspecialchars($uamport) ?>/logon">
        <input type="hidden" name="challenge" value="<?= htmlspecialchars($challenge) ?>">
        <input type="hidden" name="userurl" value="<?= htmlspecialchars($userurl) ?>">
        <div style="margin: 15px;">
            <input type="text" name="username" placeholder="Tên đăng nhập" required style="padding: 8px; width: 220px;">
        </div>
        <div style="margin: 15px;">
            <input type="password" name="password" placeholder="Mật khẩu" required style="padding: 8px; width: 220px;">
        </div>
        <button type="submit" style="padding: 10px 24px; background: #0066cc; color: #fff; border: none; border-radius: 4px; cursor: pointer;">Đăng nhập</button>
    </form>
</body>
</html>

Khi người dùng nhấn Đăng nhập, trình duyệt sẽ gửi biểu mẫu trực tiếp tới HTTP server nội bộ của CoovaChilli trên cổng 3990. Daemon sẽ thực hiện trao đổi challenge MD5 với FreeRADIUS qua địa chỉ 127.0.0.1. Sau khi xác thực thành công, CoovaChilli gỡ bỏ bộ lọc, đăng ký client vào bảng trạng thái của nó và cho phép các gói tin định tuyến ra ngoài thông qua eth0.

Bước 5: Xác minh trạng thái phiên theo thời gian thực

Khởi động daemon và kiểm tra interface ảo vừa được tạo:

sudo chilli --conf /etc/chilli/local.conf
ip addr show dev tun0

Để kiểm tra các phiên đang hoạt động, theo dõi bộ đếm băng thông hoặc buộc ngắt kết nối các thiết bị trái phép, hãy truy vấn daemon đang chạy bằng lệnh chilli_query:

# Hiển thị các lease đang hoạt động và trạng thái phiên của thuê bao
sudo chilli_query list

Bạn sẽ thấy kết quả hiển thị có dạng như sau:

bc:d0:74:11:22:33 10.10.0.14 1 64e29b100000001 guest_visitor 314 0 1482910/10485760 384192/2097152 ...

Cờ trạng thái 1 xác nhận client đã được xác thực, trong khi các bộ đếm theo dõi lượng băng thông tiêu thụ thực tế so với giới hạn 10 Mbps và 2 Mbps do FreeRADIUS phân bổ. Nếu một client có hành vi bất thường hoặc sử dụng quá thời gian quy định, hãy chấm dứt phiên của họ ngay lập tức bằng lệnh:

sudo chilli_query logout bc:d0:74:11:22:33

Lease sẽ được đặt lại, các bộ lọc gói tin sẽ lập tức đóng lại và thiết bị sẽ bị điều hướng ngay về trang splash đăng nhập trong lần gửi yêu cầu duyệt web tiếp theo.

Share: