3184 words
16 minutes
在 Linux 下用 USB 网卡开 WiFi 热点

在 Linux 下用 USB 网卡开 WiFi 热点#

主机:Gentoo Linux(OpenRC) 日期:2026-10-18

前言#

此文章用于记录我自己在一台 Gentoo 机器上用 USB 无线网卡开热点的过程。

免责声明

本文介绍的是通用 WiFi 热点(AP)搭建技术,仅适用于完全可控的网络环境,例如:

  • 家庭独立宽带
  • 实验室或工作场所分配的独立网络
  • 自己的流量卡

严禁在以下环境使用:

  • 校园网
  • 公司内网
  • 公共 WiFi

请遵守所在网络的用户协议,任何滥用行为与本文作者无关。

先理解数据流向#

手机连上 wlan0,dnsmasq 给它发一个 192.168.42.x 的地址。之后它的上网流量到达本机,内核转发(net.ipv4.ip_forward=1),再做一次源地址转换(masquerade),最后从 enp37s0 出去。

整条链路上有四件事要做。hostapd 管无线信号和认证,dnsmasq 只管发 IP,NAT 管地址转换,sysctl 管打开转发。

其中 NAT 是绕不过去的:192.168.42.0/24 是私网网段,上游路由器不认识它,回程流量回不来就上不了网。区别只在于用什么工具做(nftables、iptables、firewalld、ufw 都行,见下文)。防火墙过滤则是可选的,本来就有防火墙的机器才需要额外放行。

前提#

先确认两张网卡的名字。下面以 wlan0 和 enp37s0 为例:

Terminal window
ip -br link | grep -i wl # 无线网卡,做热点的那个
ip -br link | grep -i en # 有线网卡,接上游的那个

无线网卡需要支持 AP 模式(nl80211)。USB 网卡里 mt7921u、rtl8812au 这些通常都支持。不支持的话,hostapd 启动时会报 nl80211: Failed to set interface wlan0 into AP mode 这样的错误。

拿不准的话,装好 iw(见下节)后可以用 iw list 查:Supported interface modes 下列出 * AP 就说明支持。

依赖包#

要搭建这样一个热点需要安装 hostapd、dnsmasq、wireless-regdb、iw 四个软件包。wireless-regdb 提供各国的无线监管域数据,hostapd 里的 country_code=CN 要用到它;iw 用来确认网卡是否支持 AP 模式。

Terminal window
sudo emerge -av hostapd dnsmasq wireless-regdb iw

另外还需要一个做 NAT 的工具。如果你的系统本来就有防火墙(nftables / iptables / firewalld / ufw),直接用它就行,不用额外装;如果什么防火墙都没有,那就装一个,本文以 nftables 为例:

Terminal window
sudo emerge -av nftables

别的发行版包名基本也差不多,只需把安装软件的命令替换成对应发行版的即可。

搭建#

hostapd#

Terminal window
sudo nano /etc/hostapd/hostapd_2g.conf
interface=wlan0
driver=nl80211
ssid=MyHotspot
hw_mode=g
channel=6
ieee80211n=1
wmm_enabled=1
# 监管域
country_code=CN
ieee80211d=1
auth_algs=1
wpa=2
wpa_passphrase=<你的密码>
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
rsn_pairwise=CCMP

记得把上面配置里的 “<你的密码>” 替换成你实际想设置的 WiFi 密码。

dnsmasq#

Terminal window
sudo nano /etc/dnsmasq.d/hotspot.conf
interface=wlan0
bind-interfaces
# 网段需要与下面 NAT 规则里的网段一致
dhcp-range=192.168.42.100,192.168.42.200,12h
dhcp-option=3,192.168.42.1 # 本机默认网关
dhcp-option=6,223.5.5.5 # 公共 DNS,客户端不经过本机
# 记录每次 DHCP 交互,排查 "连上但分配不到 IP" 时很有用,不需要时也可以注释掉
log-dhcp
log-facility=/var/log/dnsmasq-hotspot.log
log-async=25

内核转发#

Terminal window
sudo nano /etc/sysctl.d/99-hotspot.conf
# 客户端要能出网,必须打开 IPv4 转发
net.ipv4.ip_forward = 1
# 我完全不转发 IPv6,有需要可以自行开启
net.ipv6.conf.all.forwarding = 0
# 接上游的有线口不接收 RA
net.ipv6.conf.enp37s0.accept_ra = 0
Terminal window
sudo sysctl -p /etc/sysctl.d/99-hotspot.conf

放入 /etc/sysctl.d/ 是为了让内核参数单独有个文件管,和 NAT / 防火墙规则分开。

NAT#

客户端用的是私网地址,上游不认识,必须在本机出口上把源地址换成 enp37s0 的地址。

下面列出几种做法,挑一种即可:

nftables(net-firewall/nftables 不提供 /etc/nftables.conf,需要自己建。如果这个文件已经存在,则只在末尾追加下面这段,不要整体替换,因为你可能已经有一整套防火墙规则在里面):

Terminal window
sudo nano /etc/nftables.conf
#!/usr/bin/nft -f
define WAN = "enp37s0"
define LANNET = 192.168.42.0/24
destroy table ip hotspot_nat
table ip hotspot_nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname $WAN ip saddr $LANNET masquerade
}
}
Terminal window
sudo nft -f /etc/nftables.conf

追加到已有文件时注意两点:

  • 首行的 #!/usr/bin/nft -f 只在新建文件时需要,追加时不要带上。
  • define 必须在使用之前出现。两行 define 放进文件靠前的 define 区,destroy table ip hotspot_nat 和 table ip hotspot_nat {...} 放到文件末尾。

整段贴在末尾会让 define 出现在使用之后,nft 会直接报错。

LANNET 必须和上面 dnsmasq 的 dhcp-range 对得上,否则 masquerade 匹配不到。

iptables(很多系统还在用):

Terminal window
sudo iptables -t nat -A POSTROUTING -s 192.168.42.0/24 -o enp37s0 -j MASQUERADE

先用 sudo iptables -S FORWARD | head -1 看一眼策略,如果是 DROP,还得放行转发,否则客户端照样上不了网:

Terminal window
sudo iptables -A FORWARD -i wlan0 -o enp37s0 -j ACCEPT
sudo iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

FORWARD 默认是 ACCEPT 的机器不用加这两条。

firewalld 和 ufw 都要改”默认转发策略 + masquerade”,但改的位置完全不同:

  • firewalld:把 enp37s0 划进 external 区并打开 masquerade,两条都带 --permanent:firewall-cmd --zone=external --change-interface=enp37s0 --permanent、firewall-cmd --zone=external --add-masquerade --permanent,最后 firewall-cmd --reload 生效。

  • ufw:没有 zone 这个概念,要在 /etc/ufw/before.rules 最前面加一段 nat 规则,再把 /etc/default/ufw 里的 DEFAULT_FORWARD_POLICY 改成 ACCEPT:

    *nat
    :POSTROUTING ACCEPT [0:0]
    -A POSTROUTING -s 192.168.42.0/24 -o enp37s0 -j MASQUERADE
    COMMIT

    改完 sudo ufw reload 生效。

NAT 规则怎么开机生效,取决于你用的工具,各发行版一般都有现成方案。

  • nftables:用 Gentoo 自带的 net-firewall/nftables 服务。它是快照模型,适合机器上只维护这一份规则的情况,按顺序执行:

    Terminal window
    sudo nft -f /etc/nftables.conf
    sudo rc-service nftables save
    sudo rc-update add nftables default

    这个服务启动时会检查 /var/lib/nftables/rules-save,文件不存在就直接拒绝启动。rc-update 只是加开机链接、不启动服务,所以 save 真正要赶在下次重启之前跑过。

    快照的含义是:/etc/nftables.conf 只在 nft -f 时读一次,save 把当时内核里的整份规则写进 rules-save,开机加载的是它。所以每次改完规则,除了 nft -f,还要再 rc-service nftables save 一次,否则重启会回退到上一次快照。

    正因为快照会把内核里所有表一起写进去,而服务加载前又会 flush ruleset,所以机器上要是有 tailscale、libvirt 这类自己建表的软件,就别用这个服务,改用 接管服务的做法。

  • iptables:sudo rc-service iptables save + sudo rc-update add iptables default。

  • firewalld:本身就是常驻服务,--permanent 的改动自动持久,不用额外操作。

防火墙过滤(可选)#

如果机器没有配置防火墙,或者防火墙默认放行,等下面 hotspot 服务跑起来热点就能用了,这节可以跳过。

如果机器上有 policy drop 这类默认拒绝的防火墙,需要额外放行这几样:

  • wlan0 上的 DHCP(udp/67):不给 dnsmasq 放行,客户端连上了也拿不到 IP
  • wlan0 → enp37s0 的转发
  • 转发链里的 ct state established,related accept

(如果把 dnsmasq 改成下发本机地址 dhcp-option=6,192.168.42.1,让客户端走本机 DNS,那还要再加一条 udp/53 的放行。本文下发的是 223.5.5.5,客户端不经过本机,所以不用开。)

最后一条最容易漏:policy drop 只放行”客户端 → 上游”这一个方向,服务器回来的包方向是反的,匹配不到任何规则,全靠 conntrack 放行。少了它客户端能拿到 IP,但什么都连不上——DNS 查询和网页访问一起失败。

hotspot#

新建 /etc/init.d/hotspot:

#!/sbin/openrc-run
# 职责:配置无线接口 + dnsmasq + hostapd
# - NAT / 防火墙 -> 由你选定的那套工具负责
# - ip_forward -> /etc/sysctl.d/99-hotspot.conf (由 sysctl 服务加载)
rc_need="net"
IFACE=wlan0
GATEWAY=192.168.42.1
DNSMASQ_PID=/run/hotspot_dnsmasq.pid
HOSTAPD_PID=/run/hotspot_hostapd.pid
# 等某个 pidfile 出现、且对应进程还活着
_wait_pidfile() {
local pf="$1" timeout="$2" i=0 pid
local max=$(( timeout * 10 )) # 每次 0.1 秒
while [ "$i" -lt "$max" ]; do
if [ -r "$pf" ]; then
pid="$(cat "$pf" 2>/dev/null)"
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
return 0
fi
fi
sleep 0.1
i=$(( i + 1 ))
done
return 1
}
start() {
ebegin "Starting hotspot"
# USB 网卡由 udev 异步注册,开机时未必已经就绪,所以轮询等待(上限 15 秒)
local i=0
while [ "$i" -lt 150 ]; do
ip link show "$IFACE" >/dev/null 2>&1 && break
sleep 0.1
i=$(( i + 1 ))
done
if ! ip link show "$IFACE" >/dev/null 2>&1; then
eerror "接口 $IFACE 在 15 秒内未出现,热点无法启动"
return 1
fi
ip link set "$IFACE" up
ip addr add "$GATEWAY/24" dev "$IFACE" 2>/dev/null
# --- dnsmasq ---
dnsmasq --conf-file=/etc/dnsmasq.d/hotspot.conf --pid-file="$DNSMASQ_PID"
if ! _wait_pidfile "$DNSMASQ_PID" 5; then
eerror "dnsmasq 启动失败或未在 5 秒内就绪"
return 1
fi
# --- hostapd ---
hostapd -B -P "$HOSTAPD_PID" /etc/hostapd/hostapd_2g.conf
if ! _wait_pidfile "$HOSTAPD_PID" 10; then
ewarn "hostapd 未在 10 秒内就绪(可能需要更久初始化射频),但它已在后台运行"
fi
eend 0
}
stop() {
ebegin "Stopping hotspot"
pkill -f "hostapd.*hostapd_2g.conf" 2>/dev/null || true
pkill -f "dnsmasq.*hotspot.conf" 2>/dev/null || true
rm -f "$HOSTAPD_PID" "$DNSMASQ_PID" 2>/dev/null || true
ip addr del "$GATEWAY/24" dev "$IFACE" 2>/dev/null || true
eend 0
}

启用服务#

Terminal window
sudo chmod +x /etc/init.d/hotspot
sudo rc-update add hotspot default
sudo rc-service hotspot start

hotspot 是 rc_need="net",等网络就绪才会启动。

如果你是用 nftables 管理 NAT,还要把它加进开机流程,做法见前面 NAT 那节。

用 systemd 的发行版则需把上面的 start/stop 写成 service unit。别的 OpenRC 发行版思路相同,但网络服务名和配置路径未必一致——本文的 rc_need="net" 依赖 Gentoo netifrc 提供的 net 虚拟服务,Alpine 上对应的服务叫 networking,照抄前先确认。

验证#

服务起来之后,按顺序检查,哪一步断了就对应哪一段配置:

第一步:服务都活着。 本机上看:

Terminal window
rc-service hotspot status
pgrep -af hotspot # 应该能看到 dnsmasq 和 hostapd 各一个
ip -4 addr show wlan0 # 应该能看到 192.168.42.1/24

wlan0 上没有 192.168.42.1/24,说明 hotspot 的 start 没走完,往上翻它启动时的报错。

顺手确认 NAT 规则在:nftables 用 sudo nft list table ip hotspot_nat,iptables 用 sudo iptables -t nat -L POSTROUTING -n,能看到 masquerade 就行。

第二步:手机拿到了地址。 手机连上热点后,看它分配的 IP 是不是 192.168.42.100–192.168.42.200 之间的地址。

  • 如果连不上热点:hostapd 没起来,看「无线接口被占用」和「网卡被 rfkill 软阻塞」两节
  • 如果连上了但一直转圈拿不到 IP:dnsmasq 没应答,看 /var/log/dnsmasq-hotspot.log

第三步:能通到上游。 在手机上依次 ping 三个地址,断在哪一层很清楚:

目标通了说明不通说明
192.168.42.1到本机这一跳正常wlan0 地址或 dnsmasq 网关选项有问题
223.5.5.5转发和 NAT 都正常ip_forward 没开,或 NAT / 转发规则没生效
任意域名DNS 解析也正常DNS 不通,检查 dhcp-option=6 下发的是否可达

192.168.42.1 通、223.5.5.5 不通,先查 NAT 和转发这一层:回到 NAT 那节确认规则真的加载了(nft list ruleset 或 iptables -t nat -L POSTROUTING -n),以及 net.ipv4.ip_forward 是 1。

最后:重启再走一遍。 上面几步只证明了这一次运行是好的。快照没 save、rc-update 没加上、dhcpcd 又抢了地址,这些问题全都要等重启才会暴露。sudo reboot 之后按第一步和第三步复查一遍,都正常才算真的搭好了。

故障排除#

无线接口被占用#

如果这块网卡之前当客户端用过,net.wlan0 服务或 wpa_supplicant 进程可能还握着它。此时 hostapd 会报 nl80211: Interface wlan0 is in use 这样的错误。先确认:

Terminal window
rc-service net.wlan0 status
pgrep -a wpa_supplicant

如果有就停掉,再重启热点:

Terminal window
sudo rc-service net.wlan0 stop
sudo pkill wpa_supplicant
sudo rc-service hotspot restart

网卡被 rfkill 软阻塞#

rfkill list 里 Soft blocked: yes 时 hostapd 也起不来:

Terminal window
rfkill list
sudo rfkill unblock wlan

如果看到的是 Hard blocked: yes,那是机身上的物理开关或 BIOS 关掉了无线,软件解不开,得先去把开关打开。

dhcpcd 抢占地址#

dhcpcd 可能会在这块网卡上尝试 DHCP,失败后留下一个 169.254.x.x 的地址和 /16 路由。因此需要在 /etc/dhcpcd.conf 里加一行:

denyinterfaces wlan0

然后重启 dhcpcd,注意要加 --nodeps。dhcpcd 被 hotspot、libvirtd、tailscale 的服务引用,普通 rc-service dhcpcd restart 会让 OpenRC 先把这些依赖服务停掉,却不会自动拉起来,结果是热点、tailscale、虚拟机一起掉线,wlan0 上的 192.168.42.1/24 也会消失。

Terminal window
sudo rc-service dhcpcd --nodeps restart
ip -4 addr show wlan0 # 确认没有 169.254.x.x 残留

如果之前已经产生过 169.254.x.x,denyinterfaces 不会自动删掉它,只能手动清理:

Terminal window
sudo ip addr del 169.254.x.x/16 dev wlan0

日常管理#

Terminal window
sudo rc-service hotspot start # 开热点
sudo rc-service hotspot stop # 关热点(NAT 规则保留,但此时无流量匹配)
sudo rc-service hotspot restart # 重启热点

改完 NAT / 防火墙规则后,按你所用工具的方式重载。以 nftables 为例:

Terminal window
sudo nft -f /etc/nftables.conf # 立即生效
sudo nft list ruleset # 确认规则在

如果用的是前面提到的 Gentoo 官方快照模型,还要再 sudo rc-service nftables save 一次,否则重启后会回退到上一次快照。

周围 AP 多、连接不稳时可以换信道。默认 channel=6,换成 1 或 11 通常明显好些,改完执行 sudo rc-service hotspot restart 生效。

在 Linux 下用 USB 网卡开 WiFi 热点
https://cyclicspace.site/posts/gentoo_hotspot/
Author
kjrin710
Published at
2026-08-27

Share Article

Generate a share poster or copy the link to share this article.

Continue reading

Related reading

Based on shared tags and categories