Configure Access Point as Personal Hotspot (OpenWrt)

手里有了一台很不错的便携无线路由器,也插了一张流量足够多(20GB 每月)的手机卡,但是因为所有连上去的设备都不知道这是个手机热点,所以流量每次都跑的很快。Windows 还好,可以手动设置成低数据模式;macOS 个辣鸡就根本没办法设置这个。

后来一想,macOS 是可以区分出普通的 Wi-Fi 接入点和 iOS 设备热点的,接入 iOS 设备热点的时候流量消耗似乎会小很多。但它是怎么区分的呢?

Search

用关键词 macOS wifi personal hotspot 搜,首先搜到的是下面这个五年前的问题:How does iOS and OS X detect when a Wi-Fi network is a personal hotspot?

虽然里面没有什么特别有用的回答,不过这段逆向出来的代码里面提到了一个叫 IOS_IE 的东西,但怎么搜也搜不到这是个啥,只好作罢。

char -[CWNetwork(Private) isPersonalHotspot](void * self, void * _cmd) {
    eax = [*(self + 0x4) objectForKey:@"IOS_IE"];
    eax = LOBYTE(eax != 0x0 ? 0x1 : 0x0) & 0xff;
    return eax;
}

后来又看了看里面的回答,基本都说的是「这是个黑盒」「说不定是通过写死的 mac 地址啥的来判断的」之类,顿时觉得也没啥希望了。

直到有一天突然发现手里的 Redmi K30 Pro 的热点也可以在 macOS 里面识别成手机热点,虽然不能显示网络类型、信号强度、电池电量啥的,但是确实也是展示成个人热点的,感觉说不定搞清楚原理还是有戏的。。。

后来干脆广撒网,Windows 印象中也有这个特性,于是干脆搜 detect wifi as metered,又找到了这个:Windows 10 does not detect Wi-Fi as metered connection

认真看了一遍,学到了一种叫做 vendor-specific information elements (IEs) 的东西。恰好这个题主也是在 OpenWrt 下配置,而且他介绍的十分清楚。说起来这个 IE 看起来好像有点眼熟,好像上面刚刚提过。。。

那这个 IE 到底长啥样呢,它下面恰好举了个在 Windows 下可用的例子

0x0000   80 00 00 00 FF FF FF FF-FF FF XX XX XX XX XX XX   €...ÿÿÿÿÿÿXXXXXX
0x0010   EC 41 18 50 A7 35 00 8E-99 81 50 23 98 00 00 00   ìA.P§5.Ž™.P#˜...
0x0020   64 00 31 04 00 1B YY YY-YY YY YY YY YY YY YY YY   d.1...YYYYYYYYYY
0x0030   YY YY YY YY YY YY YY YY-YY YY YY YY YY YY YY YY   YYYYYYYYYYYYYYYY
0x0040   XX 01 08 82 84 8B 96 0C-12 18 24 03 01 0B 05 04   Y..‚„‹–...$.....
0x0050   00 02 00 00 2A 01 00 32-04 30 48 60 6C 30 14 01   ....*..2.0H`l0..
0x0060   00 00 0F AC 04 01 00 00-0F AC 04 01 00 00 0F AC   ...¬.....¬.....¬
0x0070   02 0C 00 0B 05 04 00 08-00 00 3B 02 51 00 2D 1A   ..........;.Q.-.
0x0080   EC 01 17 FF FF 00 00 00-00 00 00 00 00 00 00 01   ì..ÿÿ...........
0x0090   00 00 00 00 00 00 00 00-00 00 3D 16 0B 00 04 00   ..........=.....
0x00A0   00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
0x00B0   00 00 7F 08 04 00 00 00-00 00 01 40 DD 18 00 50   ...........@Ý..P
0x00C0   F2 02 01 01 01 00 03 A4-00 00 27 A4 00 00 42 43   ò......¤..'¤..BC
0x00D0   5E 00 62 32 2F 00 DD 08-00 50 F2 11 00 00 00 02   ^.b2/.Ý..Pò.....

其中最后 10 个字节代表的就是这个 vendor-specific information elements 的一个例子

0xDD (Vendor-specific record)
0x08 (Record length : 8 bytes)
0x00 0x50 0xF2 (Vendor: Microsoft)
0x11 (OUI Type: Network Cost)
0x00 0x00 0x00 0x02 (Portable Hotspot Default: Metered network; limit unknown or not yet reached; matches Windows default for mobile broadband connections.)

至于怎么用,OpenWrt 下面只需要用一条简单的 uci 命令,加到 hostapd 的配置上,就可以生效。亲自试了一下,Windows 下面的确好使。

uci add_list wireless.radio0.hostapd_options='vendor_elements=DD080050F21102000200'
uci commit
reboot

到现在大概可以很合理的去想:Windows 是这么干的,那说不定 macOS 也是这么干的。所以问题就变成了如何获得一个 iOS 设备的 IE?

Dump

上面那个回答的作者是用了一个 Windows 下的软件抓包。看了一下价格(CommView),可以说十分劝退了。。。

后来又看到一些用 WireShark 抓包的帖子,但是手头恰好没有很方便的无线网卡,于是只好掏出了吃灰半年多的树莓派。。。

等树莓派更新的功夫顺便看到了个在 macOS 下抓包的方式,有一个 macOS 自带的工具 airport

虽然一开始没抱什么希望,不过它好像有个 XML 格式的输出,于是试了一下

> /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s --xml

得到一个 plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
   <array>
      <dict>
         <key>AGE</key>
         <integer>0</integer>
         <key>AP_MODE</key>
         <integer>2</integer>
         <key>BEACON_INT</key>
         <integer>100</integer>
         <key>BSSID</key>
         <string>7a:27:c0:89:1a:b3</string>
         <key>CAPABILITIES</key>
         <integer>4401</integer>
         <key>CHANNEL</key>
         <integer>149</integer>
         <key>CHANNEL_FLAGS</key>
         <integer>1040</integer>
         <key>EXT_CAPS</key>
         <dict>
            <key>BSS_TRANS_MGMT</key>
            <integer>1</integer>
         </dict>
         <key>HE_CAP</key>
         <data>/xYjAAAAAAAAAAAAAAAAAAAAAADw//D/</data>
         <key>HE_OP</key>
         <data>/wok9H8AD/z/AZsA</data>
         <key>HT_CAPS_IE</key>
         <dict>
            <key>AMPDU_PARAMS</key>
            <integer>19</integer>
            <key>ASEL_CAPS</key>
            <integer>0</integer>
            <key>CAPS</key>
            <integer>239</integer>
            <key>EXT_CAPS</key>
            <integer>0</integer>
            <key>MCS_SET</key>
            <data>//8AAAAAAAAAAAAAAAAAAA==</data>
            <key>TXBF_CAPS</key>
            <integer>16777216</integer>
         </dict>
         <key>HT_IE</key>
         <dict>
            <key>HT_BASIC_MCS_SET</key>
            <data>AAAAAAAAAAAAAAAAAAAAAA==</data>
            <key>HT_DUAL_BEACON</key>
            <false />
            <key>HT_DUAL_CTS_PROT</key>
            <false />
            <key>HT_LSIG_TXOP_PROT_FULL</key>
            <false />
            <key>HT_NON_GF_STAS_PRESENT</key>
            <false />
            <key>HT_OBSS_NON_HT_STAS_PRESENT</key>
            <true />
            <key>HT_OP_MODE</key>
            <integer>1</integer>
            <key>HT_PCO_ACTIVE</key>
            <false />
            <key>HT_PCO_PHASE</key>
            <false />
            <key>HT_PRIMARY_CHAN</key>
            <integer>149</integer>
            <key>HT_PSMP_STAS_ONLY</key>
            <false />
            <key>HT_RIFS_MODE</key>
            <false />
            <key>HT_SECONDARY_BEACON</key>
            <false />
            <key>HT_SECONDARY_CHAN_OFFSET</key>
            <integer>1</integer>
            <key>HT_SERVICE_INT</key>
            <integer>0</integer>
            <key>HT_STA_CHAN_WIDTH</key>
            <true />
            <key>HT_TX_BURST_LIMIT</key>
            <false />
         </dict>
         <key>IE</key>
         <data>
         AA9hcF9taXNoYXJlX2NmNjABCIwSmCSwSGBsIAEAOwUAU1R9gTAUAQAAD6wE
         AQAAD6wEAQAAD6wCDAAtGu8AE///AAAAAAAAAAAAAAAAAAAAAAAAAAEAPRaV
         BREAAAAAAAAAAAAAAAAAAAAAAAAA3RgAUPICAQGBAAIyAAAiMgAAQjJeAGIy
         LwC/DLIAgDP6/2ID+v9iA8AFAZsA+v9/CAUACAAAAABA/xYjAAAAAAAAAAAA
         AAAAAAAAAADw//D//wok9H8AD/z/AZsA3QoAF/IGAQEDAQAA
         </data>
         <key>IOS_IE</key>
         <dict>
            <key>IOS_IE_FEATURES</key>
            <data>AQAA</data>
            <key>IOS_IE_FEATURE_VERSION</key>
            <integer>1</integer>
            <key>IOS_IE_FEATURE_WOW_DISALLOWED</key>
            <true />
         </dict>
         <key>NOISE</key>
         <integer>-93</integer>
         <key>RATES</key>
         <array>
            <integer>6</integer>
            <integer>9</integer>
            <integer>12</integer>
            <integer>18</integer>
            <integer>24</integer>
            <integer>36</integer>
            <integer>48</integer>
            <integer>54</integer>
         </array>
         <key>RSN_IE</key>
         <dict>
            <key>IE_KEY_RSN_AUTHSELS</key>
            <array>
               <integer>2</integer>
            </array>
            <key>IE_KEY_RSN_CAPS</key>
            <dict>
               <key>GTKSA_REPLAY_COUNTERS</key>
               <integer>1</integer>
               <key>MFP_CAPABLE</key>
               <false />
               <key>MFP_REQUIRED</key>
               <false />
               <key>NO_PAIRWISE</key>
               <false />
               <key>PRE_AUTH</key>
               <false />
               <key>PTKSA_REPLAY_COUNTERS</key>
               <integer>16</integer>
               <key>RSN_CAPABILITIES</key>
               <integer>12</integer>
            </dict>
            <key>IE_KEY_RSN_MCIPHER</key>
            <integer>4</integer>
            <key>IE_KEY_RSN_UCIPHERS</key>
            <array>
               <integer>4</integer>
            </array>
            <key>IE_KEY_RSN_VERSION</key>
            <integer>1</integer>
         </dict>
         <key>RSSI</key>
         <integer>-30</integer>
         <key>SSID</key>
         <data>YXBfbWlzaGFyZV9jZjYw</data>
         <key>SSID_STR</key>
         <string>ap_mishare_cf60</string>
         <key>VHT_CAPS</key>
         <dict>
            <key>INFO</key>
            <integer>864026802</integer>
            <key>SUPPORTED_MCS_SET</key>
            <data>+v9iA/r/YgM=</data>
         </dict>
         <key>VHT_OP</key>
         <dict>
            <key>BASIC_MCS_SET</key>
            <integer>-6</integer>
            <key>CHANNEL_CENTER_FREQUENCY_SEG0</key>
            <integer>-101</integer>
            <key>CHANNEL_CENTER_FREQUENCY_SEG1</key>
            <integer>0</integer>
            <key>CHANNEL_WIDTH</key>
            <integer>1</integer>
         </dict>
      </dict>
   </array>
</plist>

结果就在里面看到了 IE,甚至还有 IOS_IE,狂喜。找到中间的 IE,复制出来解析一下

> echo AA9hcF9taXNoYXJlX2NmNjABCIwSmCSwSGBsIAEAOwUAU1R9gTAUAQAAD6wEAQAAD6wEAQAAD6wCDAAtGu8AE///AAAAAAAAAAAAAAAAAAAAAAAAAAEAPRaVBREAAAAAAAAAAAAAAAAAAAAAAAAA3RgAUPICAQGBAAIyAAAiMgAAQjJeAGIyLwC/DLIAgDP6/2ID+v9iA8AFAZsA+v9/CAUACAAAAABA/xYjAAAAAAAAAAAAAAAAAAAAAADw//D//wok9H8AD/z/AZsA3QoAF/IGAQEDAQAA | base64 -d | hexdump -C
00000000  00 0f 61 70 5f 6d 69 73  68 61 72 65 5f 63 66 36  |..ap_mishare_cf6|
00000010  30 01 08 8c 12 98 24 b0  48 60 6c 20 01 00 3b 05  |0.....$.H`l ..;.|
00000020  00 53 54 7d 81 30 14 01  00 00 0f ac 04 01 00 00  |.ST}.0..........|
00000030  0f ac 04 01 00 00 0f ac  02 0c 00 2d 1a ef 00 13  |...........-....|
00000040  ff ff 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 01 00 3d  16 95 05 11 00 00 00 00  |.......=........|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 dd  |................|
00000070  18 00 50 f2 02 01 01 81  00 02 32 00 00 22 32 00  |..P.......2.."2.|
00000080  00 42 32 5e 00 62 32 2f  00 bf 0c b2 00 80 33 fa  |.B2^.b2/......3.|
00000090  ff 62 03 fa ff 62 03 c0  05 01 9b 00 fa ff 7f 08  |.b...b..........|
000000a0  05 00 08 00 00 00 00 40  ff 16 23 00 00 00 00 00  |.......@..#.....|
000000b0  00 00 00 00 00 00 00 00  00 00 00 00 f0 ff f0 ff  |................|
000000c0  ff 0a 24 f4 7f 00 0f fc  ff 01 9b 00 dd 0a 00 17  |..$.............|
000000d0  f2 06 01 01 03 01 00 00                           |........|
000000d8

可以看到两个 dd 开头的 vendor-specific 串,第一个是 dd 18 00 50 f2 ...,从上面可以了解到这个是 Microsoft 定义的,也就不是我们需要的了。那另一个 dd 0a 00 17 f2 ... 大概就是我们要找的了。

Solution

到现在其实要做的事情就很简单了:照着上面那个帖子把配置写进去,重启路由器

uci add_list wireless.radio0.hostapd_options='vendor_elements=DD0A0017F206010103010000'
uci commit
reboot

果然好使。

虽然不知道是不是真的会省流量,但是看起来就牛逼了很多。

Comments