强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 08 章:访问控制与过滤

第 08 章:访问控制与过滤

8.1 访问控制基础

8.1.1 接口级访问控制

# /etc/dnsmasq.d/50-acl.conf

# 只在指定接口提供服务
bind-interfaces
interface=eth1

# 排除特定接口
except-interface=eth0

# 监听特定 IP(自动限制访问)
listen-address=127.0.0.1,192.168.1.1

# 限制 DHCP 只在特定接口
interface=eth1
no-dhcp-interface=eth0

8.1.2 端口控制

# 自定义 DNS 端口(隐藏默认端口)
port=5353

# 禁止 DNS(只提供 DHCP)
port=0

# 允许所有端口的查询(用于端口转发)
# Dnsmasq 不直接支持,需要 iptables 配合

8.1.3 客户端认证(基于 MAC)

# 只为已知 MAC 地址提供 DHCP 服务
# /etc/dnsmasq.d/51-mac-auth.conf

# 静态绑定列表即白名单
dhcp-host=aa:bb:cc:dd:ee:01,192.168.1.10,device1
dhcp-host=aa:bb:cc:dd:ee:02,192.168.1.11,device2

# 只分配静态绑定的地址(拒绝未知设备)
dhcp-range=192.168.1.10,192.168.1.50,static
dhcp-authoritative

注意:MAC 地址可以被伪造,这仅是基本防护,不能替代真正的网络认证(如 802.1X)。

8.2 域名过滤

8.2.1 黑名单模式

# /etc/dnsmasq.d/52-domain-filter.conf

# 方法 1:address 指令屏蔽(返回 0.0.0.0)
address=/ads.example.com/0.0.0.0
address=/tracker.example.com/0.0.0.0
address=/malware.example.com/0.0.0.0

# 方法 2:返回 NXDOMAIN(域名不存在)
address=/ads.example.com/#

# 方法 3:使用外部黑名单文件
# 每行格式:address=/域名/0.0.0.0
conf-file=/etc/dnsmasq.blacklist

8.2.2 白名单模式

# 只允许解析特定域名,其他全部拒绝
# 这种模式较为极端,仅适用于高度安全场景

# 指定允许的域名
server=/allowed-domain.com/223.5.5.5
server=/another-allowed.com/223.5.5.5

# 拒绝所有其他域名(返回 NXDOMAIN)
address=/#/0.0.0.0

# 或者将未列出的域名指向特定 IP
address=/#/192.168.1.100

8.2.3 按域名分类过滤

# 社交媒体过滤
address=/facebook.com/0.0.0.0
address=/twitter.com/0.0.0.0
address=/instagram.com/0.0.0.0
address=/tiktok.com/0.0.0.0

# 广告域名过滤
address=/doubleclick.net/0.0.0.0
address=/googlesyndication.com/0.0.0.0

8.2.4 域名通配符过滤

# 屏蔽整个域名及其所有子域名
# address=/example.com/0.0.0.0 会屏蔽:
# - example.com
# - www.example.com
# - any.subdomain.example.com

# 屏蔽特定子域名
address=/ads.example.com/0.0.0.0
# 不影响 example.com 或 www.example.com

8.3 广告屏蔽

8.3.1 手动维护广告屏蔽列表

# /etc/dnsmasq.d/53-adblock.conf

# 创建屏蔽列表文件
# /etc/dnsmasq.adblock
# address=/ads.googlevideo.com/0.0.0.0
# address=/pagead2.googlesyndication.com/0.0.0.0
# address=/ads.facebook.com/0.0.0.0
# address=/analytics.tiktok.com/0.0.0.0

# 引用列表
conf-file=/etc/dnsmasq.adblock

8.3.2 自动下载并更新屏蔽列表

#!/bin/bash
# /usr/local/bin/update-adblock.sh

BLOCKLIST_DIR="/etc/dnsmasq.blocklists"
OUTPUT="/etc/dnsmasq.adblock"
TEMP="/tmp/adblock-temp.txt"

mkdir -p "$BLOCKLIST_DIR"

# 清空旧列表
> "$TEMP"

# 下载多个广告列表
URLS=(
    "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
    "https://adaway.org/hosts.txt"
    "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext"
    "https://raw.githubusercontent.com/notracking/hosts-blocklists/master/hostnames.txt"
)

for url in "${URLS[@]}"; do
    echo "Downloading: $url"
    curl -sL "$url" >> "$TEMP" 2>/dev/null
done

# 转换为 Dnsmasq 格式
awk '
    /^0\.0\.0\.0[[:space:]]/ {
        gsub(/\r/, "");
        split($0, a, /[[:space:]]+/);
        if (a[2] !~ /^(localhost|localhost\.localdomain|127\.0\.0\.1|::1|0\.0\.0\.0)$/) {
            print "address=/" a[2] "/0.0.0.0"
        }
    }
' "$TEMP" | sort -u > "$OUTPUT"

# 重新加载 Dnsmasq
sudo systemctl reload dnsmasq

echo "Updated $(wc -l < "$OUTPUT") blocked domains"
# 设置自动更新(每天凌晨 3 点)
echo "0 3 * * * root /usr/local/bin/update-adblock.sh" | sudo tee /etc/cron.d/adblock-update

8.3.3 使用 AdGuard Home 风格的列表

# AdGuard 格式的域名列表
# 转换脚本
#!/bin/bash
# convert-adguard.sh

INPUT="$1"
OUTPUT="${INPUT%.txt}.dnsmasq"

awk '
    /^\|\|[a-z]/ && !/\^/ && !/\*/ {
        gsub(/\|\|/, "");
        gsub(/\^.*$/, "");
        if ($0 !~ /^#/) {
            print "address=/" $0 "/0.0.0.0"
        }
    }
' "$INPUT" > "$OUTPUT"

echo "Converted $(wc -l < "$OUTPUT") domains"

8.3.4 白名单(豁免列表)

# 某些域名即使在屏蔽列表中也需要放行
# /etc/dnsmasq.d/54-whitelist.conf

# 强制覆盖屏蔽规则(address 指令的后加载优先)
# 将白名单放在单独文件中,确保在屏蔽列表之后加载

# /etc/dnsmasq.whitelist
# server=/example.com/223.5.5.5
# server=/ads.example.com/223.5.5.5

conf-file=/etc/dnsmasq.whitelist

注意:在 Dnsmasq 中,后加载的 address 指令会覆盖先加载的。因此白名单文件应放在屏蔽列表文件之后加载。

# 配置加载顺序
# /etc/dnsmasq.d/50-filter.conf
conf-file=/etc/dnsmasq.adblock       # 屏蔽列表
conf-file=/etc/dnsmasq.whitelist     # 白名单(后加载,优先级高)

8.4 基于时间的访问控制

8.4.1 定时开关 DNS 过滤

#!/bin/bash
# /usr/local/bin/time-filter.sh

FILTER_CONF="/etc/dnsmasq.timed-filter"

case "$1" in
    enable)
        # 启用过滤
        cp /etc/dnsmasq.adblock "$FILTER_CONF"
        sudo systemctl reload dnsmasq
        echo "Filter enabled"
        ;;
    disable)
        # 禁用过滤
        > "$FILTER_CONF"
        sudo systemctl reload dnsmasq
        echo "Filter disabled"
        ;;
esac
# 在 /etc/dnsmasq.d/ 中引用
# /etc/dnsmasq.d/55-timed-filter.conf
conf-file=/etc/dnsmasq.timed-filter

# Cron 定时任务
# 工作日 22:00 到次日 06:00 启用社交媒体过滤
# 0 22 * * 1-5 /usr/local/bin/time-filter.sh enable
# 0 6 * * 1-5 /usr/local/bin/time-filter.sh disable
# 周末全天启用
# 0 0 * * 6 /usr/local/bin/time-filter.sh enable
# 0 22 * * 0 /usr/local/bin/time-filter.sh disable

8.4.2 业务场景:儿童上网时间管理

# 完整的儿童上网时间管理方案

# 1. 创建不同过滤级别的配置
# /etc/dnsmasq.filter-kids-strict  (严格过滤)
# /etc/dnsmasq.filter-kids-normal  (普通过滤)
# /etc/dnsmasq.filter-kids-off     (关闭过滤)

# 2. 定时切换
# 学习时间(周一-周五 16:00-18:00):严格过滤
# 0 16 * * 1-5 cp /etc/dnsmasq.filter-kids-strict /etc/dnsmasq.active-filter && sudo systemctl reload dnsmasq

# 自由时间(周一-周五 18:00-21:00):普通过滤
# 0 18 * * 1-5 cp /etc/dnsmasq.filter-kids-normal /etc/dnsmasq.active-filter && sudo systemctl reload dnsmasq

# 睡觉时间(21:00-次日 07:00):断网(通过 DHCP 或 iptables)

8.5 DHCP 访问控制

8.5.1 限制 DHCP 客户端

# /etc/dnsmasq.d/56-dhcp-acl.conf

# 只为已知设备分配地址
dhcp-range=192.168.1.10,192.168.1.50,static
dhcp-authoritative

# 按 MAC 前缀过滤(厂商白名单)
dhcp-vendorclass=set:known,MagicVerse
dhcp-ignore=tag:!known

# 拒绝特定 MAC 地址
# Dnsmasq 不直接支持 MAC 黑名单,但可以通过 iptables 实现
# iptables -A INPUT -m mac --mac-source AA:BB:CC:DD:EE:FF -j DROP

8.5.2 基于标签的访问控制

# 为不同类型的设备分配不同的策略

# 设备类型标记
dhcp-vendorclass=set:mobile,android
dhcp-vendorclass=set:mobile,iPhone
dhcp-vendorclass=set:pc,MSFT
dhcp-vendorclass=set:printer,Hewlett-Packard

# 移动设备短租约
dhcp-range=tag:mobile,set:mobile-net,192.168.1.100,192.168.1.150,2h
dhcp-option=tag:mobile-net,option:dns-server,192.168.1.1

# PC 长租约
dhcp-range=tag:pc,set:pc-net,192.168.1.200,192.168.1.250,24h
dhcp-option=tag:pc-net,option:dns-server,192.168.1.1

# 打印机静态地址
dhcp-range=tag:printer,192.168.1.20,static

8.6 高级过滤技术

8.6.1 正则表达式过滤(配合脚本)

Dnsmasq 本身不支持正则表达式,但可以通过脚本生成域名列表:

#!/bin/bash
# /usr/local/bin/generate-blocklist.sh
# 生成通配符屏蔽列表

OUTPUT="/etc/dnsmasq.dynamic-blocklist"
> "$OUTPUT"

# 屏蔽所有包含特定关键词的域名
KEYWORDS="ads|tracking|analytics|telemetry|beacon|pixel|stat|metrics"

# 从已知列表中筛选
grep -iE "$KEYWORDS" /etc/dnsmasq.adblock >> "$OUTPUT" 2>/dev/null

# 补充特定模式
cat >> "$OUTPUT" << 'EOF'
address=/ads.*\.com/0.0.0.0
address=/tracking.*\.com/0.0.0.0
address=/pixel.*\.com/0.0.0.0
EOF

sudo systemctl reload dnsmasq

8.6.2 使用 ipset/nftset 配合 iptables

# /etc/dnsmasq.d/57-ipset.conf

# 将特定域名解析结果加入 ipset
# 当 Dnsmasq 编译时启用了 ipset 支持

# 格式:ipset=/<域名>/<ipset名称>
ipset=/ads.example.com/adblock
ipset=/tracker.example.com/adblock

# 创建 ipset
sudo ipset create adblock hash:ip

# 使用 iptables 阻止 ipset 中的 IP
sudo iptables -A OUTPUT -m set --match-set adblock dst -j DROP

8.6.3 nftables 集成(Dnsmasq 2.86+)

# /etc/dnsmasq.d/58-nftset.conf

# 将解析结果加入 nftables 集合
# 格式:nftset=/<域名>/<family>/<table>/<set>
nftset=/ads.example.com/inet#dnsmasq#adblock
nftset=/tracker.example.com/inet#dnsmasq#adblock

# 创建 nftables 集合
sudo nft add set inet dnsmasq adblock { type ipv4_addr \; }
sudo nft add rule inet dnsmasq output ip daddr @adblock drop

8.7 日志与审计

8.7.1 查询日志

# /etc/dnsmasq.d/59-logging.conf

# 记录所有 DNS 查询
log-queries

# 记录 DHCP 事件
log-dhcp

# 日志文件
log-facility=/var/log/dnsmasq/dnsmasq.log

# 日志轮转
# /etc/logrotate.d/dnsmasq
# /var/log/dnsmasq/*.log {
#     daily
#     rotate 30
#     compress
#     delaycompress
#     missingok
#     notifempty
#     create 0640 dnsmasq adm
#     postrotate
#         sudo systemctl reload dnsmasq
#     endscript
# }

8.7.2 分析查询日志

# 统计查询最多的域名
sudo awk '/query\[A\]/ {print $6}' /var/log/dnsmasq/dnsmasq.log | \
  sort | uniq -c | sort -rn | head -20

# 统计查询来源 IP
sudo awk '/query\[/ {print $NF}' /var/log/dnsmasq/dnsmasq.log | \
  sort | uniq -c | sort -rn | head -20

# 查找被屏蔽的查询
sudo grep "/0.0.0.0" /var/log/dnsmasq/dnsmasq.log | tail -20

# 实时监控被拦截的广告域名
sudo tail -f /var/log/dnsmasq/dnsmasq.log | grep "0.0.0.0"

8.8 完整访问控制配置示例

# /etc/dnsmasq.d/50-acl-full.conf

# === 接口控制 ===
bind-interfaces
listen-address=127.0.0.1,192.168.1.1
except-interface=eth0

# === DHCP 访问控制 ===
dhcp-range=192.168.1.10,192.168.1.50,static
dhcp-authoritative

# 已知设备白名单
dhcp-host=aa:bb:cc:dd:ee:01,192.168.1.10,device1
dhcp-host=aa:bb:cc:dd:ee:02,192.168.1.11,device2

# === 域名过滤 ===
# 广告屏蔽列表
conf-file=/etc/dnsmasq.adblock

# 白名单(覆盖屏蔽规则)
conf-file=/etc/dnsmasq.whitelist

# 本地域名放行
local=/home.lan/
address=/home.lan/192.168.1.1

# === 安全防护 ===
# 防 DNS 重绑定
stop-dns-rebind
rebind-localhost-ok

# 防 Bogon
bogus-priv

# 禁止 DNSSEC 不安全的响应(可选)
# dnssec-check-unsigned

# === 日志 ===
log-queries
log-dhcp
log-facility=/var/log/dnsmasq/dnsmasq.log

8.9 小结

功能实现方式适用场景
接口控制interface= / bind-interfaces基础网络隔离
MAC 过滤dhcp-host + static设备准入控制
域名黑名单address=/域名/0.0.0.0广告屏蔽、恶意域名
域名白名单conf-file= 加载顺序豁免特定域名
时间控制cron + reload家长控制
ipset/nftsetipset= / nftset=防火墙联动

8.10 扩展阅读