Hướng dẫn cấu hình Transparent Proxy với TPROXY trên Linux: Chuyển hướng toàn bộ TCP/UDP mà không cần cấu hình từng client

Network tutorial - IT technology blog
Network tutorial - IT technology blog

Quick Start: Chạy TPROXY trong 5 phút

Trước khi đi vào lý thuyết, mình sẽ cho bạn có một transparent proxy chạy được ngay. Môi trường: Ubuntu 22.04, kernel 5.15+, Squid làm proxy daemon.

Bước 1: Cài Squid với hỗ trợ TPROXY

sudo apt update && sudo apt install squid

# Kiểm tra Squid có hỗ trợ TPROXY không
squid -v | grep -i netfilter

Output phải có --enable-linux-netfilter. Package mặc định trên Ubuntu đã include flag này rồi.

Bước 2: Cấu hình Squid lắng nghe TPROXY port

Thêm vào /etc/squid/squid.conf:

# Port cho TPROXY (khác với port proxy thường 3128)
http_port 3129 tproxy

# Tạm thời cho phép tất cả để test
http_access allow all

Bước 3: Cấu hình kernel routing + iptables

Tạo file /usr/local/bin/tproxy_setup.sh:

#!/bin/bash
# Tạo routing table riêng cho traffic có fwmark=1
ip rule add fwmark 1 lookup 100 2>/dev/null || true
ip route add local default dev lo table 100 2>/dev/null || true

# Load kernel module
modprobe xt_TPROXY

# Chain riêng để dễ quản lý
iptables -t mangle -N TPROXY_MARK 2>/dev/null || iptables -t mangle -F TPROXY_MARK

# Redirect TCP port 80 và 443 từ LAN về Squid
iptables -t mangle -A TPROXY_MARK -p tcp -m multiport --dports 80,443 \
  -j TPROXY --tproxy-mark 0x1/0x1 --on-port 3129

# Không redirect traffic từ proxy daemon (tránh loop)
iptables -t mangle -A PREROUTING -m owner --uid-owner proxy -j RETURN

# Áp dụng cho traffic từ LAN (thay subnet cho đúng môi trường)
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j TPROXY_MARK
sudo chmod +x /usr/local/bin/tproxy_setup.sh
sudo bash /usr/local/bin/tproxy_setup.sh
sudo systemctl restart squid

Test ngay từ một client trong LAN — không cấu hình proxy trên browser — truy cập bất kỳ trang HTTP nào rồi kiểm tra:

sudo tail -f /var/log/squid/access.log

Nếu thấy request của client xuất hiện trong log, TPROXY đã hoạt động đúng.

Hiểu rõ tại sao cần TPROXY

Vấn đề với proxy truyền thống

Proxy thông thường có một điểm yếu cố hữu: phải cấu hình từng thiết bị. Công ty 200 máy tính — phải push setting qua GPO hoặc MDM. Smart TV, game console, thiết bị IoT — đa số không có tùy chọn proxy. Camera IP, máy in network — gần như không bao giờ hỗ trợ proxy.

Giải pháp nằm ở kernel level. Traffic bị chặn ngay tại gateway trước khi rời khỏi mạng nội bộ — client không cần biết, không cần cấu hình. Smart TV vẫn duyệt web bình thường, nhưng mọi request đều đi qua proxy của bạn.

Cách TPROXY khác với redirect NAT thông thường

Dùng NAT redirect thông thường (REDIRECT target), proxy nhận packet nhưng chỉ thấy địa chỉ của chính nó — thông tin về đích thật bị mất hoàn toàn. Giống như nhận thư mà không biết người gửi định giao cho ai.

TPROXY khác ở điểm mấu chốt: địa chỉ đích gốc được giữ nguyên khi deliver packet lên userspace. Proxy đọc đích thật qua socket option IP_TRANSPARENT, rồi kết nối ra internet với source IP của client. Server đích thấy request đến từ 192.168.1.100, không biết proxy tồn tại.

Flow xử lý packet đầy đủ

  1. Client gửi TCP SYN đến 93.184.216.34:80
  2. Packet vào interface eth0, đi qua PREROUTING chain bảng mangle
  3. Rule TPROXY match, đánh dấu packet với fwmark=1, chuyển đến port 3129
  4. Kernel tra cứu socket cục bộ — tìm thấy Squid đang lắng nghe trên port 3129
  5. Packet được deliver lên Squid, nhưng giữ nguyên địa chỉ đích gốc
  6. Squid đọc địa chỉ đích 93.184.216.34:80 qua getsockname()
  7. Squid mở connection ra internet, bind source IP thành 192.168.1.100 (IP của client)

Cấu hình nâng cao

Xử lý UDP và DNS trong suốt

UDP không có handshake nên cơ chế khác TCP, nhưng TPROXY vẫn handle được. Một use case hay gặp là intercept DNS để filter hoặc ghi log toàn bộ query:

# Cần module xt_socket cho UDP transparency
modprobe xt_socket

# Thêm rule UDP DNS
iptables -t mangle -A TPROXY_MARK -p udp --dport 53 \
  -j TPROXY --tproxy-mark 0x1/0x1 --on-port 5353

Viết UDP proxy với Python cần set socket option đặc biệt:

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Cho phép bind địa chỉ không thuộc về máy
sock.setsockopt(socket.SOL_IP, socket.IP_TRANSPARENT, 1)
# Nhận địa chỉ đích gốc của mỗi packet
sock.setsockopt(socket.SOL_IP, socket.IP_RECVORIGDSTADDR, 1)
sock.bind(('0.0.0.0', 5353))

Chuyển sang nftables (khuyến nghị cho kernel mới)

Từ Debian 11 và Ubuntu 22.04+, nftables là default. Syntax gọn hơn iptables nhiều, và kernel team đang ưu tiên phát triển nftables thay vì tiếp tục mở rộng iptables:

table ip tproxy_table {
    chain prerouting {
        type filter hook prerouting priority mangle; policy accept;
        
        # Bỏ qua traffic từ proxy daemon
        meta skuid proxy return
        
        # Redirect TCP 80/443 từ LAN
        ip saddr 192.168.1.0/24 tcp dport { 80, 443 } \
            tproxy ip to 127.0.0.1:3129 meta mark set 0x1
    }

    chain divert {
        type filter hook prerouting priority mangle;
        
        # Traffic đã có socket established thì không cần TPROXY nữa
        meta l4proto tcp socket transparent 1 meta mark set 0x1 accept
    }
}
sudo nft -f /etc/nftables-tproxy.conf
# Kiểm tra
sudo nft list ruleset

SSL Bump để inspect HTTPS

Trong môi trường enterprise thường phải inspect cả HTTPS. Squid handle được với SSL bump — nhưng cần deploy CA certificate xuống toàn bộ client trước, không thì browser sẽ báo lỗi certificate:

# Tạo CA certificate trước
openssl req -new -newkey rsa:2048 -sha256 -days 365 -nodes -x509 \
  -keyout /etc/squid/ssl_cert/myCA.key \
  -out /etc/squid/ssl_cert/myCA.pem

# squid.conf
https_port 3130 tproxy ssl-bump \
  cert=/etc/squid/ssl_cert/myCA.pem \
  key=/etc/squid/ssl_cert/myCA.key

# Chỉ peek SNI, không decrypt (ít xâm phạm hơn)
ssl_bump peek step1
ssl_bump splice all

Troubleshooting và tips vận hành

Debug khi traffic không bị redirect

Checklist mình dùng khi TPROXY không hoạt động:

# 1. Kiểm tra packet có vào rule không
iptables -t mangle -L TPROXY_MARK -v -n

# 2. Kiểm tra routing table 100
ip route show table 100
ip rule show

# 3. Kiểm tra Squid có bind đúng port không
ss -tlnp | grep 3129

# 4. Kiểm tra capability của process Squid
cat /proc/$(pgrep squid | head -1)/status | grep -i cap

Lỗi phổ biến nhất: Squid không có CAP_NET_ADMIN. Fix bằng cách thêm vào systemd service:

# /etc/systemd/system/squid.service.d/tproxy.conf
[Service]
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

# Reload và restart
sudo systemctl daemon-reload && sudo systemctl restart squid

Tính subnet khi setup cho nhiều VLAN

Khi cần thêm rule cho nhiều subnet cùng lúc, mình hay dùng toolcraft.app/vi/tools/developer/ip-subnet-calculator — nhập CIDR là ra ngay network range, broadcast, số host. Tiện hơn nhiều so với tính tay khi phải add rule cho từng VLAN.

Tối ưu performance với nhiều connection

# Tăng connection tracking table
sysctl -w net.netfilter.nf_conntrack_max=262144

# Tăng socket buffer
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216

# Disable conntrack cho traffic đã được TPROXY xử lý (giảm overhead)
iptables -t raw -A PREROUTING -p tcp -m multiport --dports 80,443 \
  -m mark --mark 0x1 -j NOTRACK

Auto-start sau reboot với systemd

# /etc/systemd/system/tproxy-setup.service
[Unit]
Description=TPROXY iptables setup
Before=squid.service
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/tproxy_setup.sh
ExecStop=/usr/local/bin/tproxy_cleanup.sh

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now tproxy-setup.service

Setup xong là hết việc. Mọi thiết bị trong LAN — smart TV, camera IP, thiết bị IoT — đều đi qua proxy trong suốt. Không cần động vào cấu hình từng máy.

Share: