在 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 为例:
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 模式。
sudo emerge -av hostapd dnsmasq wireless-regdb iw另外还需要一个做 NAT 的工具。如果你的系统本来就有防火墙(nftables / iptables / firewalld / ufw),直接用它就行,不用额外装;如果什么防火墙都没有,那就装一个,本文以 nftables 为例:
sudo emerge -av nftables别的发行版包名基本也差不多,只需把安装软件的命令替换成对应发行版的即可。
搭建
hostapd
sudo nano /etc/hostapd/hostapd_2g.confinterface=wlan0driver=nl80211ssid=MyHotspothw_mode=gchannel=6ieee80211n=1wmm_enabled=1
# 监管域country_code=CNieee80211d=1
auth_algs=1wpa=2wpa_passphrase=<你的密码>wpa_key_mgmt=WPA-PSKwpa_pairwise=CCMPrsn_pairwise=CCMP记得把上面配置里的 “<你的密码>” 替换成你实际想设置的 WiFi 密码。
dnsmasq
sudo nano /etc/dnsmasq.d/hotspot.confinterface=wlan0bind-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-dhcplog-facility=/var/log/dnsmasq-hotspot.loglog-async=25内核转发
sudo nano /etc/sysctl.d/99-hotspot.conf# 客户端要能出网,必须打开 IPv4 转发net.ipv4.ip_forward = 1
# 我完全不转发 IPv6,有需要可以自行开启net.ipv6.conf.all.forwarding = 0
# 接上游的有线口不接收 RAnet.ipv6.conf.enp37s0.accept_ra = 0sudo sysctl -p /etc/sysctl.d/99-hotspot.conf放入 /etc/sysctl.d/ 是为了让内核参数单独有个文件管,和 NAT / 防火墙规则分开。
NAT
客户端用的是私网地址,上游不认识,必须在本机出口上把源地址换成 enp37s0 的地址。
下面列出几种做法,挑一种即可:
nftables(net-firewall/nftables 不提供 /etc/nftables.conf,需要自己建。如果这个文件已经存在,则只在末尾追加下面这段,不要整体替换,因为你可能已经有一整套防火墙规则在里面):
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 }}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(很多系统还在用):
sudo iptables -t nat -A POSTROUTING -s 192.168.42.0/24 -o enp37s0 -j MASQUERADE先用 sudo iptables -S FORWARD | head -1 看一眼策略,如果是 DROP,还得放行转发,否则客户端照样上不了网:
sudo iptables -A FORWARD -i wlan0 -o enp37s0 -j ACCEPTsudo iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTFORWARD 默认是 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 MASQUERADECOMMIT改完
sudo ufw reload生效。
NAT 规则怎么开机生效,取决于你用的工具,各发行版一般都有现成方案。
-
nftables:用 Gentoo 自带的
net-firewall/nftables服务。它是快照模型,适合机器上只维护这一份规则的情况,按顺序执行:Terminal window sudo nft -f /etc/nftables.confsudo rc-service nftables savesudo 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 放行,客户端连上了也拿不到 IPwlan0→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=wlan0GATEWAY=192.168.42.1
DNSMASQ_PID=/run/hotspot_dnsmasq.pidHOSTAPD_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}启用服务
sudo chmod +x /etc/init.d/hotspotsudo rc-update add hotspot defaultsudo rc-service hotspot starthotspot 是 rc_need="net",等网络就绪才会启动。
如果你是用 nftables 管理 NAT,还要把它加进开机流程,做法见前面 NAT 那节。
用 systemd 的发行版则需把上面的 start/stop 写成 service unit。别的 OpenRC 发行版思路相同,但网络服务名和配置路径未必一致——本文的 rc_need="net" 依赖 Gentoo netifrc 提供的 net 虚拟服务,Alpine 上对应的服务叫 networking,照抄前先确认。
验证
服务起来之后,按顺序检查,哪一步断了就对应哪一段配置:
第一步:服务都活着。 本机上看:
rc-service hotspot statuspgrep -af hotspot # 应该能看到 dnsmasq 和 hostapd 各一个ip -4 addr show wlan0 # 应该能看到 192.168.42.1/24wlan0 上没有 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 这样的错误。先确认:
rc-service net.wlan0 statuspgrep -a wpa_supplicant如果有就停掉,再重启热点:
sudo rc-service net.wlan0 stopsudo pkill wpa_supplicantsudo rc-service hotspot restart网卡被 rfkill 软阻塞
rfkill list 里 Soft blocked: yes 时 hostapd 也起不来:
rfkill listsudo 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 也会消失。
sudo rc-service dhcpcd --nodeps restartip -4 addr show wlan0 # 确认没有 169.254.x.x 残留如果之前已经产生过 169.254.x.x,denyinterfaces 不会自动删掉它,只能手动清理:
sudo ip addr del 169.254.x.x/16 dev wlan0日常管理
sudo rc-service hotspot start # 开热点sudo rc-service hotspot stop # 关热点(NAT 规则保留,但此时无流量匹配)sudo rc-service hotspot restart # 重启热点改完 NAT / 防火墙规则后,按你所用工具的方式重载。以 nftables 为例:
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 生效。
Share Article
Generate a share poster or copy the link to share this article.
Continue reading
Last updated on , 0 days ago
Some content may be outdated