简介:hostapd 2.10 源码包面向在 Linux 上开发无线接入点、调试 Wi-Fi 热点的嵌入式工程师与无线协议学习者,提供从驱动适配到安全认证的完整实现,可用于搭建软 AP、排查连接与认证故障,或定制企业级热点功能。包内共 655 个文件,压缩后约 2.33MB,以 363 个 .c 源文件与 235 个 .h 头文件为主体,覆盖 nl80211 驱动对接、ieee802_11 协议处理、wpa_auth 认证状态机等核心模块;另有 20 个 Makefile 及 .mk、.conf 配置,结合 defconfig 模板与 hostapd.conf 示例可直接编译裁剪。目录中还附带 hostapd_cli 与 hostapd 手册页、多 AP 与 WPS 说明、RADIUS 与 EAP 用户样例,便于理解 802.11n/ac 多频段配置和日志排错思路。已有 924 人学习下载,适合对照源码梳理热点启动流程、定位认证失败原因并做二次开发。
1. hostapd-2.10 源码包:一块能跑 Linux 的网卡怎么变成可管可调的 AP
手上有块板子,网卡驱动也起来了,iw list里明明白白写着支持 AP 模式,可真要把它变成一个能连的 WiFi 热点,光有驱动远远不够——谁来回 802.11 的管理帧、谁跟客户端跑四次握手、谁把 WPA3 的 SAE 交换算完、谁去处理 PMF 和 802.11k/v 的邻居报告,这些都不在驱动里。hostapd 干的就是这件事,它跑在用户态,通过 nl80211 把网卡切成 AP 模式,接管认证、关联、密钥协商和桥接。2.10 这个源码包的价值不在于「能开热点」——发行版里装一个包就行——而在于你能把热点行为改到字段级:改 beacon 里的能力位、塞私有的 vendor IE、在 EAP 方法里插自己的认证逻辑、把 STA 上下线事件接进自己的业务进程。做嵌入式无线网关、做无线测试仪表、需要 WPA-Enterprise 认证实验、或者只是想弄明白四次握手到底谁先发谁后发的人,这份源码值得逐行拆。只想要一键开热点的,用发行版的网络管理组件更省事。
2. 编译链路拆解:从 defconfig 到 aarch64 上的 hostapd 可执行文件
拿到压缩包解开的第一个感觉往往是「目录怎么这么杂」。hostapd-2.10 里其实装着两个主程序:hostapd/是 AP 侧,wpa_supplicant/是 STA 侧,两者共享src/下绝大部分代码。搞清楚哪些目录真正决定热点行为,比通读一遍README有用得多。
2.1 目录骨架:哪几个文件真正决定热点行为
下面这张表是我自己贴在显示器边上的版本,按改动频率排序,不按字母序:
| 路径 | 作用 | 改动频率 |
|---|---|---|
hostapd/ | 主程序入口、config_file.c配置解析、ctrl_iface.c控制接口、Makefile | 高 |
src/ap/ | AP 核心:ap.c状态机、beacon.c信标构造、ieee802_11.c管理帧处理、wpa_auth.c密钥协商、sta_info.c客户端表 | 高 |
src/drivers/ | 驱动适配层,Linux 主线只有driver_nl80211.c | 中 |
src/common/ | wpa_ctrl.c(客户端库)、hw_features.c(能力探测) | 中 |
src/utils/ | eloop.c单线程事件循环、os_*.c平台抽象 | 中 |
src/eap_server/ | EAP 服务端方法:PEAP、TTLS、TLS、SIM | 按需 |
src/eapol_auth/ | EAPOL 状态机 | 低 |
src/radius/ | RADIUS 客户端与计费报文 | 按需 |
src/crypto/ | AES、SHA、CCMP、GCMP、SAE 的底层实现 | 低 |
tests/ | 基于 hwsim 的自动化测试,改内核行为前先跑一遍 | 低 |
真正把一次连接串起来的是三条链路:控制面从hostapd/main.c进src/ap/ap.c,再分发到ieee802_11.c里的各类管理帧回调;驱动面全部收敛在driver_nl80211.c,任何「配置下发失败」的报错几乎都能在这里找到对应的wpa_printf;密钥面是wpa_auth.c配合src/crypto/,四次握手、SAE 提交与确认、GTK 分发都在这一层。
有个细节必须提前说:src/utils/eloop.c是单线程事件循环,hostapd 的所有回调——包括你自己加进去的钩子——都跑在这个循环里。我曾经在一个AP-STA-CONNECTED的回调里同步调了一次外部 HTTP 接口做准入校验,结果整个 AP 在对方接口超时的 5 秒里完全不响应信标,所有客户端集体掉线。这种设计约束不是 bug,是 hostapd 的模型,改动前必须知道。
2.2 配置与编译:.config里的开关怎么选
hostapd 的构建系统把.config当普通 makefile 变量文件读进来,所以它本质上是一堆CONFIG_XXX=y的变量赋值,同一个变量写两次以后一次为准。起点永远是defconfig:
# defconfig 只在 hostapd 子目录这一层,别在解压根目录找 cd hostapd-2.10/hostapd # 复制成实际生效的配置文件 cp defconfig .config # 先看看默认开了什么,避免后面重复定义看晕自己 grep -E '^CONFIG_(DRIVER|LIBNL|IEEE80211|SAE|OWE|WPS|CTRL)' .config默认的defconfig已经开了 nl80211 和基础控制接口,但 802.11ac/ax、SAE、OWE 这些通常是注释掉的。建议直接编辑.config,把需要的行取消注释或补上,而不是用cat >>追加——追加虽然能覆盖,但文件里出现两份同名配置,下次排查编译问题时很容易被自己坑到。
# 用编辑器打开 .config,确保以下几行处于未注释状态 CONFIG_DRIVER_NL80211=y # 现代 Linux 上唯一实用的驱动后端 CONFIG_LIBNL32=y # 用系统 libnl 3.x,而非内置精简 netlink CONFIG_IEEE80211N=y # HT 能力与 HT IE 构造 CONFIG_IEEE80211AC=y # VHT,5GHz 80/160MHz 的前提 CONFIG_IEEE80211AX=y # HE,Wi-Fi 6 相关 IE 与 OFDMA 参数 CONFIG_IEEE80211W=y # 管理帧保护 PMF,WPA3 强制要求 CONFIG_SAE=y # WPA3-Personal 的 SAE 握手 CONFIG_OWE=y # 无密码的增强开放网络 CONFIG_CTRL_IFACE=y # hostapd_cli 与控制接口的前提 CONFIG_DEBUG_FILE=y # 允许 -f 把调试日志写文件,现场必备几个开关的取舍值得单独说。CONFIG_LIBNL32如果不打开,hostapd 会退回自己实现的一小段 netlink 代码,功能上能起 AP,但涉及NL80211_CMD_SET_BSS的一些高级属性、以及多 BSSID 场景下的部分操作会缺失,表现是配置看着对但某些参数不生效,这种问题极难定位,所以一律打开。CONFIG_WPS代码面很大,还会额外引入一堆 IE 解析路径,不做 WPS 配网就直接关掉,编译产物和攻击面都会小一圈。CONFIG_IEEE80211R是快速漫游(FT),单 AP 场景完全不需要,多 AP 组网才开。反过来,CONFIG_NO_STDOUT_DEBUG是用来减体积的,开了之后-d系列的调试输出会被裁掉,量产版本可以开,调试版本千万别开,否则现场抓瞎。
改完直接编译:
make -j"$(nproc)" # 产物就在当前目录 ls -l hostapd hostapd_cli # 确认架构对不对,交叉编译最容易犯的错就是编出了宿主机版本 file hostapd readelf -d hostapd | head -20hostapd/目录下的make只编 AP 侧和它的命令行客户端,不会去动wpa_supplicant/。如果你要联调 STA 侧行为,得回到解压根目录跑一次顶层make。readelf -d这一步别省,动态依赖里出现宿主机路径的libnl或libssl,说明链接时找错了 sysroot,跑到板子上就是一句No such file or directory,而这句报错不会告诉你是哪个库。
2.3 交叉编译与依赖裁剪
交叉编译 hostapd 的报错有九成集中在两个地方:libnl 头文件路径和 OpenSSL 版本。前者是因为 libnl3 的头文件装在include/libnl3/子目录下,而源码里写的是#include <netlink/genl/genl.h>,不显式加-I就一定找不到;后者是因为宿主机上编出来的libssl和目标 rootfs 里的版本对不上,运行时符号缺失。
make clean make -j"$(nproc)" \ CC=aarch64-linux-gnu-gcc \ CFLAGS="-O2 -ffunction-sections -fdata-sections \ -I${SYSROOT}/usr/include/libnl3" \ LDFLAGS="-Wl,--gc-sections -L${SYSROOT}/usr/lib" \ LIBS="-lnl-3 -lnl-genl-3 -lnl-route-3 -lssl -lcrypto"参数逐条解释:CC指定交叉工具链前缀,工具链名字各家不同,用ls /opt或which aarch64-linux-gnu-gcc确认;-ffunction-sections -fdata-sections配合-Wl,--gc-sections,能让链接器把没有被引用到的函数整段丢掉,对 hostapd 这种带了一堆可选 EAP 方法的程序效果明显,全开和只留 PSK+SAE 的产物体积能差出接近一个数量级;-I${SYSROOT}/usr/include/libnl3就是前面说的那个坑,必须指到libnl3这一层而不是它的父目录;LIBS里三个-lnl-*一个都不能少,少了-lnl-genl-3会报undefined reference to genl_connect,少了-lnl-route-3在多 BSSID 建虚拟接口时会报undefined reference to rtnl_link_*。
如果目标 rootfs 用的是 musl,或者干脆想全静态,那就把 libnl、OpenSSL 都用同一套工具链重编一遍再静态链接进来,产物会大一些但省掉运行期依赖,量产出问题的时候少很多扯皮。体积还有富余的话,用strip去符号表,但保留一份未 strip 的版本配合addr2line用,现场拿到一个崩溃地址能直接定位到源码行,这算是我自己的一条后悔药。
3. hostapd.conf 逐字段落地:把 wlan0 配成 80MHz WPA3 热点的 11 个关键参数
hostapd 的配置文件语法很朴素,key=value一行一条,#注释。但同一个功能往往有好几种写法,写错了不报错、只是行为不对,这才是麻烦的地方。下面这份配置是我在 5GHz 板子上跑通的基线,逐段拆。
3.1 最小可用配置:先让它起来,再谈优化
# /etc/hostapd/hostapd.conf interface=wlan0 # 网卡名,不是 phy 名,也不是 MAC driver=nl80211 # 与编译时的 CONFIG_DRIVER_NL80211 对应 ctrl_interface=/var/run/hostapd ctrl_interface_group=0 # 0 即 root 组,普通用户访问要改成对应 gid ssid=lab-ap-5g hw_mode=a # a = 5GHz,g = 2.4GHz channel=36 ieee80211n=1 ieee80211ac=1 vht_oper_chwidth=1 # 1 = 80MHz vht_oper_centr_freq_seg0_idx=42 auth_algs=1 # 1 = 开放系统认证 wmm_enabled=1 beacon_int=100 # 单位 TU,100 约等于 102.4ms dtim_period=2 max_num_sta=64 ap_isolate=1 # 客户端之间二层隔离,访客网络必开 wpa=2 wpa_key_mgmt=WPA-PSK wpa_passphrase=please-change-me rsn_pairwise=CCMP几个字段容易望文生义。interface填的是内核里的网络设备名,hostapd 会自己去把这个接口设成 AP 模式并 up 起来,所以启动前不需要你手动iw dev wlan0 set type __ap,手动设了反而可能和 hostapd 抢状态。hw_mode只有a、b、g三个值,5GHz 一律用a,2.4GHz 用g,b只在做兼容性测试时用。ctrl_interface写的是目录,实际生成的 socket 是目录/接口名,也就是/var/run/hostapd/wlan0,后面写客户端程序时要用完整路径。
ap_isolate=1是个经常被忽略但很实用的开关,开了之后同一 AP 下的客户端互相不可见,做访客网络、做公共区域覆盖时必须开,否则等于把一堆陌生终端放进同一个广播域。
启动和验证:
# 先用 -t 做配置语法自检,不真正起 AP hostapd -t /etc/hostapd/hostapd.conf # 前台跑并开调试,-d 可叠加,-ddd 最啰嗦 hostapd -dd /etc/hostapd/hostapd.conf # 看到这一行才算真的起来了 # wlan0: AP-ENABLED3.2 加密与认证:WPA2、WPA3 和过渡模式的组合表
这块是配置里最容易配错的部分,因为wpa、wpa_key_mgmt、ieee80211w三个字段是联动的,单看任何一个都判断不出最终协商出什么。
| 目标 | wpa | wpa_key_mgmt | ieee80211w | 说明 |
|---|---|---|---|---|
| WPA2-PSK | 2 | WPA-PSK | 0 | 兼容性最好的基线 |
| WPA2/WPA3 过渡 | 2 | WPA-PSK SAE | 2 | 老终端走 PSK,新终端走 SAE |
| 纯 WPA3-SAE | 2 | SAE | 2 | 强制 PMF,老终端连不上 |
| WPA3-Enterprise | 2 | WPA-EAP-SHA256 | 2 | 需要 RADIUS,ieee80211w=2不可改 |
| 开放+OWE | 2 | OWE | 2 | 无密码但加密,需CONFIG_OWE |
ieee80211w的取值含义要记牢:0 是关闭 PMF,1 是「支持但不强制」,2 是「强制」。WPA3 规范要求强制 PMF,所以纯 SAE 场景下写 1 会导致一部分终端直接拒绝关联,而且日志里未必给出明确原因。过渡模式写 2 是安全的,支持 PMF 的终端会协商上,不支持的会因为ieee80211w是「可选」而不是「必须」而放行——这也是过渡模式存在的意义。
SAE 还有一个隐藏坑:sae_pwe。这个参数控制 SAE 的 PWE(Password Element)推导方式,0 是传统的 hunting-and-pecking,1 是 H2E(Hash-to-Element),2 是两者都发。默认值在不同终端上的表现差异极大,我遇到过一批终端在sae_pwe=0下反复重试最后超时,把日志翻到SAE: hunting-and-pecking loop那几行才反应过来。稳妥的做法是:
# WPA3 场景建议直接给 2,同时兼容 H2E 和传统方式 sae_pwe=2 # 密码长度别偷懒,SAE 对弱口令的握手耗时明显更长 wpa_passphrase=at-least-12-chars另外wpa_passphrase短于 8 个字符时,hostapd 启动阶段就会报Invalid WPA passphrase,这个还算友好;但如果是通过hostapd_cli set在运行期改,失败只体现在返回值上,不主动看就会以为改成功了。
3.3 射频参数:带宽、信道、国家码与 DFS
带宽配置是新手最容易写出「看着配了 80MHz 实际只跑 40MHz」的地方。原因在于 VHT 的中心频率索引和主信道号是两个概念,必须成对配。
vht_oper_centr_freq_seg0_idx的定义是(中心频率 - 5000) / 5,单位 MHz。以信道 36 开 80MHz 为例,80MHz 覆盖信道 36–48,中心落在 5210MHz,所以索引是(5210-5000)/5 = 42。
| 主信道 | 带宽 | vht_oper_chwidth | vht_oper_centr_freq_seg0_idx | 中心频率 |
|---|---|---|---|---|
| 36 | 80MHz | 1 | 42 | 5210 |
| 52 | 80MHz | 1 | 58 | 5290 |
| 100 | 80MHz | 1 | 106 | 5530 |
| 149 | 80MHz | 1 | 155 | 5775 |
| 36 | 40MHz | 0 | 38 | 5190 |
vht_oper_chwidth的取值:0 表示 20 或 40MHz(由 HT 参数决定)、1 表示 80MHz、2 表示 160MHz、3 表示 80+80。开 160MHz 时seg0_idx和中心频率的对应关系会更绕,实际部署里受限于可用信道,能用满 160MHz 的场景不多,我一般先按 80MHz 稳下来。
国家码相关字段决定了内核允不允许你在某个信道上发射。不配country_code的时候,监管域是「世界域」,很多信道被标成 no-IR(不允许主动发射),表现是 hostapd 报Channel not allowed或者干脆起不来。
# 填实际部署地区的监管域代码,写错会导致合法信道被拒 country_code=XX ieee80211d=1 # 在信标里携带 Country IE,客户端才能正确判断可用信道 ieee80211h=1 # 允许 DFS 信道,开了之后 52-64 / 100-144 才可用 # 使用 DFS 信道时,hostapd 会先做 60 秒 CAC 静默期 # 日志里能看到 "DFS-CAC-START" 到 "DFS-CAC-COMPLETED"DFS 信道的 CAC 静默期是硬性的,60 秒内不会发信标,测试时如果盯着AP-ENABLED迟迟不出现,先看是不是配了 DFS 信道。室内小规模部署我一般避开 DFS,直接落在 36 或 149 上,省掉雷达检测带来的中断——一旦检测到雷达信号,hostapd 会发 CSA 把客户端迁走,然后自己停 60 秒再回来,用户体验相当糟糕。
HT/VHT 的能力位可以显式控制:
# 40MHz 时指定主信道上方还是下方做扩展 ht_capab=[HT40+][SHORT-GI-20][SHORT-GI-40][LDPC] # VHT 能力位,MAX-MPDU 影响吞吐上限 vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][SU-BEAMFORMEE]这些方括号里的字符串必须拼写完全正确,写错了 hostapd 会在解析阶段报Invalid VHT capabilities,倒还算友好。但漏写某个能力位不会报错,只是协商结果和你预期不一样,所以改完一定要用客户端实际看一眼链路速率。
3.4 多 BSSID 与 VLAN 隔离
一块网卡上跑多个 SSID 是常见需求:主网络 + 访客网络,或者 2.4G + 5G 双频同名。hostapd 的主从 BSS 结构是「主配置里声明bss=,然后每个bss对应一个独立配置文件」。
# 主配置 /etc/hostapd/hostapd.conf 末尾追加 bss=wlan0.1 bss=wlan0.2# /etc/hostapd/wlan0.1.conf —— 访客网络 driver=nl80211 interface=wlan0.1 bssid=02:00:00:00:01:01 # 从 BSS 必须显式指定,不能留空 ssid=lab-guest bridge=br-guest wpa=2 wpa_key_mgmt=WPA-PSK wpa_passphrase=guest-network-pass rsn_pairwise=CCMP ap_isolate=1 # 动态 VLAN 场景,按 RADIUS 返回的属性分配 VLAN dynamic_vlan=1 vlan_file=/etc/hostapd/vlan.conf从 BSS 的bssid是必须显式配置的,这是新手最常漏的一行。不配的话 hostapd 会尝试让驱动自己分配,部分驱动能成,部分驱动直接报Failed to set BSSID,而且报错位置在driver_nl80211.c深处,不容易联想到是配置缺字段。另外从 BSS 的 BSSID 和主接口 MAC 要在同一个 phy 上且不能重复,瞎填会出现「能起来但客户端扫不到」这种更玄学的情况。
vlan_file的格式是VLANID 接口名一行一条,比如10 br-vlan10。开启动态 VLAN 后,RADIUS 服务器返回Tunnel-Type、Tunnel-Medium-Type、Tunnel-Private-Group-Id三个属性,hostapd 会按 VLAN ID 找到对应的接口名并把该客户端塞进去。这套东西在企业网里很成熟,但要求交换机侧和 RADIUS 侧的属性都得配齐,只配 hostapd 一端是跑不通的。
4. 控制接口二次开发:hostapd_cli 批量管理 + wpa_ctrl 事件订阅
hostapd 起来之后,绝大多数运维动作都不该靠重启进程完成。控制接口就是为这个设计的:它是一条 UNIX domain socket,既可以用命令行客户端打,也可以在自己的程序里挂上去收事件。做无线开发的,这一层基本是绕不开的。
4.1 ctrl_interface 的配置与权限
配置里两行决定一切:
ctrl_interface=/var/run/hostapd ctrl_interface_group=0ctrl_interface是目录,hostapd 启动时会自动创建,并给每个 BSS 建一个同名的 socket 文件。ctrl_interface_group决定 socket 的属组,运行时以 root 跑的话保持 0 就行;如果希望业务进程以非 root 身份访问,就把它改成业务进程所在的 gid,否则会遇到Permission denied,而这个报错在hostapd_cli那边只表现成一句Failed to connect to hostapd,容易被误判成进程没起来。
# 确认 socket 确实建出来了 ls -l /var/run/hostapd/ # 预期看到 wlan0、wlan0.1、wlan0.2 三个 socket 文件 # srw-rw---- 1 root root ... wlan0如果目录存在但 socket 不在,说明 hostapd 在初始化阶段就挂了,回去看前台日志,通常能看到更具体的原因。
4.2 hostapd_cli 常用命令与批量脚本
hostapd_cli支持交互式和单条命令两种用法,单条命令模式适合塞进脚本。
# 指定接口,默认会去找第一个,多 BSS 时必须显式指定 hostapd_cli -p /var/run/hostapd -i wlan0 status # 当前关联的客户端列表 hostapd_cli -p /var/run/hostapd -i wlan0 list_sta # 单个客户端的详细信息,signal 字段就是 RSSI hostapd_cli -p /var/run/hostapd -i wlan0 sta 00:11:22:33:44:55 # 强制踢掉某个客户端 hostapd_cli -p /var/run/hostapd -i wlan0 deauthenticate 00:11:22:33:44:55 # 运行期改一个配置项,注意不是所有字段都支持 hostapd_cli -p /var/run/hostapd -i wlan0 set ap_isolate 0 # 重新读配置文件,支持热更新的字段会生效 hostapd_cli -p /var/run/hostapd -i wlan0 reload # 信道切换,参数顺序是 切换倒计时 目标频率 带宽 中心频率 hostapd_cli -p /var/run/hostapd -i wlan0 chan_switch 10 5180 sec_channel_offset=1 center_freq1=5190 bandwidth=40set命令支持哪些字段是编译期和运行期共同决定的,不是万能开关。ap_isolate、accept_acl、deny_acl、wpa_passphrase这类支持;hw_mode、channel(除 DFS 场景外)这类涉及重建 BSS 的基本不支持,硬改会返回FAIL。判断方法是看返回是不是OK,脚本里一定要检查返回值,别看见命令没报错就当成功了。
批量拉 RSSI 是个高频需求,all_sta的输出是分块文本,每块以 MAC 地址开头,后面跟一堆key=value:
#!/bin/sh # sta_rssi.sh —— 输出所有在线客户端的 MAC 与 RSSI IF=${1:-wlan0} hostapd_cli -p /var/run/hostapd -i "$IF" all_sta | awk ' # MAC 地址单独占一行,长度恰好 17 length($0) == 17 && $0 ~ /^[0-9a-f][0-9a-f]:/ { mac = $0 } # signal 字段是负数,单位 dBm /^signal=/ { split($0, a, "="); print mac, a[2] } '这段 awk 的关键在第一行判断:不能简单用$0 ~ /^[0-9a-f:]+$/,因为十六进制位串也可能匹配上;用长度 17 加前缀格式双重判断更稳。第二行的split取出等号右边的值,输出形如00:11:22:33:44:55 -47,直接可以喂给监控系统。
4.3 源码内嵌:用 wpa_ctrl 订阅上下线事件
hostapd_cli是拉模式,做业务联动需要推模式。hostapd 的事件通知走的是同一条 socket,用src/common/wpa_ctrl.c里的接口就能挂上去。
/* ap_monitor.c —— 订阅 hostapd 事件,打印客户端上下线 */ #include <stdio.h> #include <string.h> #include <unistd.h> #include "wpa_ctrl.h" /* 来自 src/common/wpa_ctrl.h */ #define SOCK_PATH "/var/run/hostapd/wlan0" int main(void) { /* 事件通道要单独 open 一个句柄,和发命令的句柄分开 */ struct wpa_ctrl *mon = wpa_ctrl_open(SOCK_PATH); if (!mon) { fprintf(stderr, "open %s failed\n", SOCK_PATH); return 1; } /* attach 之后 hostapd 才会往这个句柄推事件 */ if (wpa_ctrl_attach(mon) != 0) { fprintf(stderr, "attach failed\n"); wpa_ctrl_close(mon); return 1; } for (;;) { char buf[2048]; size_t len = sizeof(buf); /* pending 是轮询式的,返回 0 表示当前没有数据 */ if (wpa_ctrl_pending(mon) <= 0) { usleep(100 * 1000); /* 100ms 轮询,别设成 0 空转烧 CPU */ continue; } if (wpa_ctrl_recv(mon, buf, &len) != 0) break; buf[len] = '\0'; /* 原始报文形如 "<3>AP-STA-CONNECTED 00:11:22:33:44:55" * 尖括号里是优先级,1 错误 2 警告 3 信息 4 调试 */ char *msg = strchr(buf, '>'); msg = msg ? msg + 1 : buf; if (strncmp(msg, "AP-STA-CONNECTED", 16) == 0) printf("[UP] %s", msg + 17); else if (strncmp(msg, "AP-STA-DISCONNECTED", 19) == 0) printf("[DOWN] %s", msg + 20); else if (strncmp(msg <p> <a href="https://download.csdn.net/download/tiger625/86514729" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>