WLAN 笔记

本文主要记录 Linux 中与无线网络相关的一些知识,如无线网卡驱动、无线网络协议、hostapd 的使用、2.4G/5G WiFi 的区别等。

网卡驱动

在 Linux 中,驱动一般都是通过内核模块的形式提供的,而内核模块又分为 内置模块可加载模块,本质上没有区别,前者被静态编译进内核,无需通过 modprobeinsmodrmmod 来加载、卸载。核心模块一般会内置到内核中,而一般的驱动,基本都是可加载模块,即 *.ko kernel object 内核对象文件。

kernel object 和 object 都是对象文件,但是 kernel object 不能被链接或者直接运行,需要使用 insmodmodprobe 来加载它,让它成为内核的一部分,扩展内核的功能。不需要时,可以使用 rmmodmodprobe 来卸载它,回收内存以及相应的资源。

默认情况下,Linux 发行版已经自带了大部分硬件的驱动模块,一般放在 /usr/lib/modules/$(uname -r)/kernel 目录下,以 *.ko*.ko.gz 等形式存放,因此可以通过 find 命令,查看当前系统是否已自带指定模块。

当我们插入一张无线网卡时(假设为 USB 无线网卡),如果系统已自带驱动,那么使用 ip addr 就可以看到对应的接口,否则说明系统没有自带相应驱动,需自行编译。

我们先使用 lsusb 命令查看网卡的型号,这是 RPi3B 的输出:

其中第一个就是我插入的 USB 转 RJ45 千兆网卡,其中可以看到对应的网卡型号 AX88179,然后我们可以 Google 找到对应的网卡驱动(一般官网都会提供的,不过实际上,这块网卡的驱动 ArchLinux 已经自带了,所以一插上去我就能看到对应的 eth1 接口)。

假设没有自带,那么下载对应的驱动源码包,解压,一般都有 makefile 文件,熟悉编译安装的同学应该很容易知道,其实就是使用 make 编译就可以了,但是还有些依赖要解决,假设你已经安装了 make、gcc 等工具,那么还需要 linux 内核头文件,在 ArchLinux 中,安装与内核版本对应的 linux-headers 包就可以了,注意必须是与 linux 包相同版本的哦,不然是无法使用的,建议更新系统,再安装,就不会出现问题了。

接下来,查看一下 readme.txt 或者其他说明文件,一般情况下,直接使用 make 命令就能够编译了,如果没有报错,那么可以看到当前目录下的 modname.ko 内核模块,使用 modinfo modname.ko 可以查看这个模块的信息,然后我们就可以加载它了,使用 insmod modname.ko,如果编译没有报错且驱动源码是正常的话,应该不会报错,此时再使用 ip addr 就可以看到新插入的网卡了。不需要时,可以使用 rmmod modname 卸载。

注意,如果 insmod 时出错,提示符号找不到,请先插入 mac80211 模块,即 modprobe mac80211,因为无线网卡的驱动依赖这个模块,而 insmod 不会处理依赖。

但是有没有觉得这样有点麻烦,每次都需要使用 insmod 来插入模块才能使用,能不能像其它模块一样在网卡插入时自动加载呢?肯定是可以的,具体步骤如下:

  • modname.ko 文件放到 /usr/lib/modules/$(uname -r)/kernel/drivers 的相应子目录下(自行分类),当然实际上放在哪并没有要求,但是分类总是好的,别人一看就知道这是什么驱动(/usr/lib/modules/$(uname -r)/extra 目录也可以);
  • 使用 depmod -a 重新建立所有内核模块之间的依赖关系,运行完后,它会更新 /usr/lib/modules/$(uname -r)/modules.* 相应的文件;
  • 最后,使用 modprobe modname 加载模块,modprobe -r modname 卸载模块。当然实际上这是多余的,当我们插入网卡时,udev 守护进程会自动根据 VendorID:DeviceID 来加载对应的模块(这个知识稍后会详细介绍),当然拔掉网卡时 udev 并不会自动卸载模块,这需要手动卸载,但实际上这也没什么必要。

问题:如果使用 lsusb 或 lspci 没有对应的设备型号怎么办?
这问题还是挺常见的,比如恶心的 360wifi3,只有一个 MediaTek,连什么型号都没有提供。那要如何知道网卡的具体型号呢?你看到 lsusb 输出行的 ID 0bda:8153 字段没?如果是 lspci,那么要加参数 -nn 才会显示这个 ID。你只要打开 Google,搜索这个 ID 一般就能够找到对应的网卡型号的。

那么这个 ID 到底是什么意思呢?它分为两个部分,0bda8153,前者是厂商 ID(Vendor ID),后者是设备 ID(Device ID)或产品 ID(Product ID)。其实每个硬件都是由对应的 VID、PID 的,VID 需要向相关机构申请,是全球唯一的,每个厂商的 VID 都是不同的,而 PID 则由厂商自己决定,可以利用 VID、PID 查找对应的设备型号,注意,VID 和 PID 并不是 Linux 独有的概念,它是硬件的概念,在 Windows 上同样存在,使用设备管理器可以看到对应的 VID、PID。

这两个 ID 都是 2 个字节长,即最大值均为 0xFFFF,也就意味着,厂商 ID 最多只有 65535 个,设备 ID 最多只有 65535 个,现在看来这可能有些小,怎么也得 4 个字节长,不过它们组合起来也是不少的,6 万乘以 6 万也是 36 亿个,还是比较足够的。

疑问:系统是如何知道对应的设备要使用哪个驱动模块的呢?
问得好,其实就是根据上面的 VID/PID 来识别的,怎么说呢,每个驱动模块其实都有对应的 alias 别名,使用 modinfo modname | grep alias 可以看到,其中就可以看到对应的 VID、PID,表示这个驱动可以用在哪些设备上,因为可以通过 VID/PID 唯一识别一个设备嘛。因此当一个新设备插入系统时,udev 会先获取设备的 VID/PID,然后查找系统中的 alias 文件(/usr/lib/modules/$(uname -r)/modules.alias 文件),就知道该使用哪个驱动模块了,然后加载对应的模块就能够使用它了!

问题:如何将一个驱动用在未注册的 alias 的设备上?
/sys/module/<modname>/drivers/<bus:modname>/new_id 文件中添加对应设备的 VID、PID 就可以了,如 echo <VID> <PID> > new_id 即可,当然,这通常没有什么用处,除非真的是 VID、PID 错了,否则就算强加进去也是没用的(内核可能会崩溃)。

问题:更新内核后,如何让手动添加的驱动能够正常使用?
因为内核模块和对应的内核版本是息息相关的,一个内核模块只能在对应的内核版本中运行,如果版本不对,是不能运行的,要解决这个问题需要重新在新内核中编译这个内核模块,才能正常使用。这在经常需要升级内核版本的发行版中是非常麻烦的,比如 ArchLinux,基本 3 天就会有一个新的内核更新,我可不想每次更新系统后都要重新编译内核模块,怎么办呢?答案是 DKMS(Dynamic Kernel Module Support,动态内核模块支持),它的办法很简单,就是将模块源码交给 dkms 管理,然后每次更新内核后,都会自动运行 dkms,dkms 就会自动编译适用于新版本的内核模块,然后下次重启后,就能正常使用了,而无需自己编译。

具体怎么操作呢?首先得安装 dkms,使用 pacman -S dkms 安装,然后运行 dkms 可以查看帮助,dkms 用法很简单,具体的帮助信息如下:

常用的 action 有:

  • add:添加内核模块
  • remove:移除内核模块
  • status:查看 dkms 状态
  • build:编译内核模块
  • install:安装内核模块
  • uninstall:卸载内核模块
  • autoinstall:自动安装内核模块

常用的 options 有:

  • -m module:指定模块名称
  • -v module-version:指定模块版本
  • -k kernel-version:指定内核版本
  • -c dkms.conf-location:指定 dkms.conf 文件
  • --all:全部内核模块
  • --verbose:输出详细信息

这里以 rtl8812au 无线网卡驱动为例,README.md 说明如下:

首先,将想要被 dkms 管理的内核模块源码复制到 /usr/src 目录下,源码目录的命名是有规定的,即 $name-$version,前面是模块名,后面是模块版本。后面的 -m-v 参数指定的就是这两个东西。当然要被 dkms 管理还没这么简单,你需要在 /usr/src/$name-$version/ 目录下创建 dkms.conf 配置文件,告诉 dkms 一些必要的信息,这是 rtl8812au 的 dkms.conf 文件内容:

  • PACKAGE_NAME:模板名称
  • PACKAGE_VERSION:模块版本
  • CLEANMAKE[#] 前执行的清理命令
  • MAKE[0]:编译模块的命令,即 make
  • BUILT_MODULE_NAME[0]:生成的内核模块名
  • DEST_MODULE_LOCATION[0]:安装路径,相对于 /usr/lib/modules/$(uname -r)
  • AUTOINSTALL:是否自动安装该模块,当不适用于当前内核时 dkms 会自动安装。

在 dkms.conf 中,可以引用一些预定义的变量,比如上面的 $kernelver

  • $kernelver:当前内核版本;
  • 等等,其它几个变量比较少用。

貌似 dkms.conf 是一个 shell 脚本(我也不确定,但很有可能是),比如你看这个 dkms.conf 配置文件,其中引用了不少的 shell 语法,但却能正常运行:

我现在还没搞懂 dkms 如何取消对指定模块的管理,目前有效且简单的方法就是去 /var/lib/dkms 目录,将对应的模块目录删除,使用 dkms status 查看就干净了。

还有,dkms.conf 里面的模块名不能乱取,建议不要改,要和 make 编译出来的保持一致,否则 dkms build 是很有可能报错(我就是这样,搞得我编译了好多次)。

安全相关

安全协议

  • WEP:Wired Equivalent Privacy(有线等效加密),不安全,不建议使用。
  • WPA:Wi-Fi Protected Access(WiFi 保护访问)第一版,有漏洞,不是很建议。
  • WPA2:Wi-Fi Protected Access(WiFi 保护访问)第二版,目前来说,建议使用。

加密方式

  • TKIP:WPA 中的加密方式,使用 RC4 加密算法,不安全。
  • CCMP:WPA2 中的加密方式,使用 AES 加密算法,更安全。

在 WPA 中,TKIP 为默认方式,CCMP 为可选方式;
在 WPA2 中,CCMP 为默认方式,TKIP 为可选方式。

WiFi 联盟要求 IEEE 802.11n 使用 WPA2 和 CCMP。TKIP 不可用于 802.11n 的传输,它仅支持传统的 802.11a、802.11b、802.11g 传输,最高速率为 54Mbps。

WPA/WPA2 中分为个人版、企业版,它们分别表示:

  • WPA/WPA2 个人版:使用 pre-shared key(PSK,预共享密钥),较简单
  • WPA/WPA2 企业版:需要一台额外的认证服务器,大型企业使用,比较复杂

协议标准

主要有三个:802.11a、802.11b/g/n、802.11ac

  • 802.11a:标准吞吐量为 54Mbps,工作在 5GHz 频段。
  • 802.11b:标准吞吐量为 11Mbps,工作在 2.4GHz 频段。
  • 802.11g:标准吞吐量为 54Mbps,工作在 2.4GHz 频段,兼容 802.11b。
  • 802.11n:标准吞吐量为 300Mbps,工作在 2.4GHz/5GHz 频段,兼容 802.11a/b/g。
  • 802.11ac:标准吞吐量为 1Gbps,工作在 5GHz,兼容 802.11a/b/g/n(需降为 2.4GHz)。

主要讨论 802.11n、802.11ac,一般来说,802.11n 工作在 2.4GHz 频段,802.11ac 工作在 5GHz 频段。部分 802.11ac 无线网卡支持“双频”工作模式,所谓双频是指同时支持 802.11n、802.11ac 两种标准,即这种网卡可以发射/接收 2.4G 的 WiFi,也可以发射/接收 5.8G 的 WiFi,但是同时只能工作在一种模式下,不可同时开启(没有 5G 网卡前我还一直懵里懵懂呢,搞得我以为驱动有问题)。因此所谓的 dual-band 双频 AP,只是有两张物理网卡而已啦。

本质上,802.11n 是在 802.11g 上改良而来的,802.11ac 则是在 802.11a 上改良而来的。因此,在 hostapd.conf 中,802.11n 为 hw_mode=gieee80211n=1、802.11ac 为 hw_mode=aieee80211ac=1。这个应该特别注意,别懵里懵懂的。

802.11n 20MHz vs 40MHz 频宽选择
大家知道,2.4G 频段一共有 1-13 号频道可用,每个频道的宽度只有 5MHz。因此,在 20MHz 带宽时,每个 Wi-Fi 信道实际上已经占用了 4 个频道。而如果继续使用 40MHz 的信道,那么将会有 8 个信道被占据。不可避免的存在大量干扰,有可能 40MHz 甚至不如 20MHz 稳定。因此,如果你周围的 WiFi 很多,请使用 20MHz 频宽,如果周围没有什么 WiFi,则可以选择 40MHz 频宽,速度快一些。

除此之外,40MHz 频宽不是所有设备都支持的,如果路由器支持,但客户端不支持,则会自动将为 20MHz,只有两者都支持才起作用。一般称为 20MHz/40MHz 模式。

hostapd 在开启 40MHz 支持时(启动时扫描),会扫描周围是否已经有使用 40MHz 的 WiFi,如果有则会自动降为 20MHz,在 ArchLinux 的 hostapd 中,打了 noscan 的补丁,因此可以在 hostapd.conf 中加入 noscan=1 告诉 hostapd 不要扫描附近的 WiFi 频宽,具体的配置如下:

802.11n 2.4GHz 下的信道选择为什么是 1、6、11?
WIFI 信号的信道有两部分,其中 2.4G 频段有 13 个左右交叠的信道,其中只能找出 3 个相互不重合的信道(具体请参考文末的链接),最常用的就是 1、6、11 这三个,当然也可以使用其他没有重叠的组合,但是由于一些国家法律不允许使用 12 或 13 信道,所以这个组合是兼容性最好的。如下图所示:
WiFi 信道选择图解

如果你选 1、6、11 信道,那就只会被同信道的设备干扰;
如果你选择 3 信道,那等于同时被 1、6 信道的设备干扰。

hostapd 中的国家/地区代码有什么用?

维基百科:IEEE 802.11d,根据各国无线电规定做的调整。
维基百科:IEEE 802.11h,根据各国无线覆盖半径的调整。

因为每个国家的 WiFi 允许的频段和信道可能不同,因此需要设置国家/地区代码。

工作模式

  • AP 模式:Access Point 无线接入点,发送 WiFi 信号的模式,如无线路由。
  • STA 模式:Station 无线终端,接收 WiFi 信号的模式,如笔记本,手机等。

AP 模式需要的软件包:hostapd
STA 模式需要的软件包:wpa_supplicant

查看/管理 无线网卡 的工具,iw,软件包 iw

hostapd

802.11b/g/n with WPA2-PSK and CCMP

802.11a/n/ac with WPA2-PSK and CCMP

802.11b/g/n triple AP(单网卡,多 SSID/AP,须驱动支持)

其它配置:

RPI3B 板载 WiFi

Tenda U12 2.4GHz 配置

Tenda U12 5.8GHz 配置

STA 配置

其实只需要 wpa_supplicant 就行了,它和 hostapd 软件包是对等的,前者用于连接 WiFi,后者用于创建 WiFi。它们都要运行一个守护进程,守护进程名同对应的包名。

wpa_supplicant 软件包的 3 个主要命令:

  • wpa_cli:wpa_supplicant 的命令行接口,交互配置工具,调试时常用。
  • wpa_passphrase:生成 network 配置段的工具,默认使用密文保存密码。
  • wpa_supplicant:核心守护进程,配置文件目录 /etc/wpa_supplicant/

连接 WiFi 前,一般先 scan 一下附近的可用 WiFi,可以使用 iwwpa_cli

使用 iw 扫描
iw 扫描前,先使用 ip link set wlan0 up 启用接口,然后 iw dev wlan0 scan

使用 wpa_cli 扫描
wpa_cli 扫描前,先运行 wpa_supplicant 守护进程,还得先创建 /etc/wpa_supplicant/wpa_supplicant.conf 配置文件,最简配置如下:

运行 wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf
运行 wpa_cli,键入 scanscan_results 查看扫描结果(可能需要等待一小会)。

配置 wpa_supplicant.conf
使用 wpa_passphrase <SSID> <Password> >> wpa_supplicant.conf 生成 network 段。
具体的配置文件如下,也可以手动配置,wpa_pssphrase 默认使用加密存储的密码:

运行 wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf
如果没有报错,就可以使用 dhcpcd wlan0 来获取 IP 等地址信息了,或静态指定 IP。

在 ArchLinux 中,建议使用 netctl 来管理网络,具体的配置也很简单,比如:

iw 命令用法
iw dev:查看接口信息(简短)
iw dev wlan0 info:查看指定接口信息(简短)

iw list:查看设备信息(详细)
iw phy:查看设备信息(详细)
iw phy phy0 info:查看指定设备信息(详细)
iw phy phy0 channels:查看指定设备的信道信息

iw dev wlan0 scan:查看附近的 WiFi 信息
iw dev wlan0 connect param...:连接到指定 WiFi(仅支持 WEP)
iw dev wlan0 disconnect:断开当前连接的 WiFi

iw dev wlan0 link:查看当前连接的 WiFi 信息
iw dev wlan0 station dump [-v]:查看指定接口连接的 WiFi 信息

因为 iw dev wlan0 scan 的信息非常多,因此这里提供一个实用的解析脚本:

用法很简单,将 wifi-scan 放在 PATH 路径中,然后 wifi-scan wlan0 就可以了。

无线知识

有线网络:全双工,可以同时发送和接收数据,互不影响,相当于有两条道路。
无线网络:半双工,同一时间只能发送或接收,两个状态之间需要不断的切换。

一条网线同一时间只能与两个设备相连,这很容易理解。而一条无线同一时间也只能与两个设备相连,即 AP 和 Sta 之间是一对一连接的。那为什么多个 Sta 连接到 AP 却可以同时上网呢?其实是 AP 与多个 Sta 之间不断切换造成的假象啦,比如第一个 Sta 通信 10ms,然后立即切换到第二个 Sta,与之通信 10ms,以此类推。这其实和单核心计算机上运行多个程序是一样的,都是不断切换来实现的。

基于这个特点,将无线 AP 当作无线的交换机是不太可取的,特别是设备多,网络负载比较重时,尤其明显。当然家用和设备不多的情况下,无线交换机还是挺有用的。

在无线传输领域,存在 MIMO 这样一个技术,MIMO 即 multiple-input and multiple-output,多输入多输出。MIMO 在 WLAN、3G、4G 中被广泛使用。

802.11n 在速率上的提升很大程度上都归功于 MIMO 技术,那 MIMO 究竟是什么东西呢?其实就是多天线,MIMO 不要求 AP 和 Sta 都有多天线,但是如果 AP 和 Sta 的天线数量相同(当然 AP 端更多也可以),效率和效果是最好的。所谓的 MIMO 就是每条天线都可以同时发送或接收数据,即原本的一条传输流变成了多条传输流,这样的直接效果就是传输速率翻倍,假设一条天线的速率是 150Mbps,那么两条天线的速率就是 1502=300Mbps,三条天线的速率就是 1503=450Mbps,四条天线的速率就是 150*4=600Mbps(802.11n 最多允许 4 条天线同时传输,即最大 600Mbps)

注意,两个设备之间的传输速率取决于天线少的一方,比如 AP 有 4 条天线(假设频宽为 40MHz,那么单天线的速率就是 150Mbps),如果 Sta 也有 4 条天线,那么协商速率就是 600Mbps,如果只有两条,那么就是 300Mbps,一条的话就只有 150Mbps 了。

上一段中我提到了 40MHz 的频宽单天线才有 150Mbps(这里仅针对 802.11n 讨论),那么正常情况下的频宽其实是 20MHz 的,因此单天线速率就是 72.2 Mbps。20MHz 的双天线速率就是 144Mbps了,这也是为什么高通手机连接 802.11n 的速率大多是 72Mbps,144Mbps(双天线)。因为高通手机默认关闭了 40MHz 的频宽,需要修改 /system/etc/wifi/WCNSS_qcom_cfg.ini 配置文件(当然需要 root 权限),即 gChannelBondingMode24GHz=1,修改为 1(默认是 0),启用 40MHz 频宽支持。

注意,802.11n 时代的 MIMO 属于 SU-MIMO(Single User MIMO,单用户 MIMO),所谓单用户是指即使你有多个天线,同一时间也只能进行一对一的数据传输。这本质和开头说的一条网线同时只能与两个设备连接是一样的,没有改变,只不过这根网线里面的分线多了几根而已,速度增加了。既然有 SU-MIMO,就有 MU-MIMO(多用户 MIMO),这是 802.11ac wave2 中提出的,意思是:同一个 AP 同一时间内可以与多个支持 MU-MIMO 的 Sta 进行通信(目前仅支持下行链路,即 AP -> Sta 方向)。多用户 MIMO 听起来挺美好的,但是因为不支持上行链路,而且可能会在多条天线之间造成干扰,导致速率反而比单用户 MIMO 低,因此我个人对 MU-MIMO 不是很感冒,还是 SU-MIMO 用的爽一些(主要还是没钱~)。

802.11n 支持的频宽(band-width)有 20MHz、40MHz,而 802.11ac 支持的频宽有 20MHz、40MHz、80MHz、160MHz。频宽越大速率就越大,但是因为“传输的道路”变大了,所以可能被邻边的信道干扰,在 WiFi 多的环境中,一味的提高频宽反而没有效果,还会降低原本的速率。

802.11n 最多可以有 4 条天线
单天线的 802.11n 的 20MHz 频宽的速率为:72.2Mbps
单天线的 802.11n 的 40MHz 频宽的速率为:150Mbps

802.11ac 最多可以有 8 条天线
单天线的 802.11ac 的 20MHz 频宽的速率为:87.6Mbps
单天线的 802.11ac 的 40MHz 频宽的速率为:200Mbps
单天线的 802.11ac 的 80MHz 频宽的速率为:433.3Mbps
单天线的 802.11ac 的 160MHz 频宽的速率为:866.7Mbps(wave2)

目前中国允许使用的 802.11n 的信道有:1-13(但 12、13 可能兼容性不好,因为部分无线网卡不支持 12、13 信道,搜不到信号)。因此实际上常用的为 1-11,而 20MHz 频宽下,建议使用的信道为 1、6、11(因为一条信道实际上会占用前后 2 条信道,即 2 + 2 = 4 条信道),如果是 40MHz,那么实际上会占用 8 条信道,因此如果是 80MHz,建议使用 1、11 信道。

对于 802.11ac,建议使用 80MHz 频宽的信道,才能充分发挥 802.11ac 的速度优势,下表是每个频宽对应的信道列表。很可惜,中国允许使用的信道都是只支持 20MHz 频宽的(CN 地区建议使用 149 信道,网上说的,说这个信道干扰较少,速度比较快,比较稳定),因此建议 802.11ac 网络不要选择 CN 地区,用 US 比较好。
802.11ac 相关信道对应的频宽