一次关于智能家居网关的折腾
原标题: 一次超出能力范围的尝试
最近家里的智能家居被修复了(喊人修了一下午)
于是想把控制智能家居的接口扒出来,自己封装成wx小程序
应用类型: 智能家居控制
特征: 支持米家第三方控制, 局域网直连(无视账户绑定), APP控制.
网关通信数据获取
抓包
首先想到的是抓包, 直接扒出接口.
应用的用户鉴权是走的HTTPS协议, 可以直接扒出来.
用户登录, 注册, 找回密码, 查询网关绑定(改绑), 用户信息, 修改密码, 操作日志. 这些都有接口(没错就是没有用户登出).
从查询绑定接口可以获得网关ID, 网关Code(类似校验码); 从操作日志可以获得各个智能家居元件的ID.
服务器是杭州阿里云, 鉴权接口是Java写的.(1/26)
但是关键的控制数据走的是TLSv1.2.
而且包裹的不是HTTP协议, 而是其他数据, 尝试了Fiddler/Wireshark/Charles均扒不出内容无奈放弃.
借此熟悉了TCP三次握手/TLS四次握手/中间人攻击, 也算没有白折腾一趟吧.(1/28)
米家
随后想了一下能不能通过米家曲线救国.
然后跑去米家官网查阅了第三方接入的文档 小米IoT开发者平台文档
发现是通过OAuth进行交互的, 那就没我啥事了…(1/28)
这几天没事又看了一遍米家文档(顺便熟悉OAuth)(2/21)
局域网
那最后只有通过局域网方式解决了, 不行的话只能放弃, 用他开发的APP.
使用tcpdump抓取的APP局域网数据:
1 | C -> 255.255.255.255:8818 574d05383838383838 //UDP广播五次 |
好消息是成功获取到了局域网下APP请求网关的控制数据, 并且网关没有校验流程(直接TCP连上就可以发送控制数据, 没有任何校验), 且控制数据看来长期不会变.
坏消息是每个设备的控制数据是无序的, 只能挨个请求并记录.
已经在着手编写PC的模拟请求了, 使用的是E(复现和记录控制数据用).(2/3)
着手编写基于PHP的模拟请求.
为啥不直接内网穿透+DDNS呢?
首先当然是没钱, 家里的路由器过老, 没有各自服务商的DDNS服务, 只有向日葵的DDNS.(流量少还不支持自定义域名)
自己有云服务器可以整frp穿透.
wx小程序不支持TCP Socket, 所以需要”第三方”才能传递数据到网关.
直接暴露网关的TCP端口是非常不安全的, 所以我准备wx小程序封装->云主机反向代理->内网穿透->内网PHP来实现.
网关通讯程序编写
E
自带的数据报(UDP广播)组件不支持显示对方IP.
随后折腾了HP-Socket, 尝试了”UDPCast””UDPServer””UDPClient”+各种参数组合后仍然拿不到对方IP.
最后突发奇想提取了网关UDP回应里第二段字符才发现这是十六进制的IPv4地址!!!
随后就是tcpdump抓取APP请求->Wireshark跟踪TCP流->E复现并记录数据, 记录了家里所有设备的控制数据.(2/5)
PHP
最开始想的是HTTPS请求, 然后在翻PHP TCP/UDP通信相关文档时发现了WebSocket也许更适合这个项目.(wx小程序也支持WebSocket)
主要实现WebSocket的框架有俩, 一个是Swoole, 一个是Workerman. Swoole几乎没有对Win的支持, 放弃.(内网机器是Win兼任的)
Workerman实现WebSocket的方法异常简单, 直接照抄代码在对应的接收事件里写自己的代码即可.
我在WebSocket传输数据是使用JSON的, 共三个操作(UDP/TCP/HEATBEAT).
但是实际编写代码时还是遇到一些困难:
socket_recv() 在接收TCP/UDP数据时必须要收到才会返回, 收不到就阻塞线程. 定位问题就费了老大功夫(因为WebSocket不会因为这个断连).
最后找到解决方案:
1 | socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, Array("sec"=>2, "usec"=>0)); //RECV超时两秒就返回 |
因为TCP/UDP的返回数据是十六进制的, 刚开始我是直接 json_encode() 就走WebSocket了.
然后在传输有些十六进制数据的时候 json_encode() 会FALSE, 返回空数据.
解决方法非常简单: (我还在 pack() 上纠结了许久)
hex2bin()/bin2hex() 俩兄弟用一下就不会出现这些问题了
PHP端实现通过WebSocket与网关通信(UDP/TCP).(2/7)
PS. 推荐一个WebSocket测试的网址(EasySwoole-WebSocket在线测试工具)带cookie记忆和心跳包发送功能, 我WebSocket测试全程用的这个.
在部署的是使用小皮面板(XP.CN), 全身上下都是坑(比如重启Apache覆盖httpd.conf配置), 都是解决的就不说了. 完成了frp穿透, 实现wss访问.(2/8)
wx小程序编写
几乎没接触过, 这个真的要容我折腾几天.
写了个demo, 实现了设备列表交互和ws通信
放弃, 这个小程序的功能分类和我脆弱的CSS能力几乎过不了审核.(2/9)
控制网页的编写
我倾向于使用Vue来写网页.(虽然我只会写单页, 路由和Cli都不会)
虽然基础薄弱, 也就当练手的项目吧. 网页相比wx小程序限制更小, 也可以用第三方组件库.弥补我脆弱的CSS基础
网页会部署在内网服务器, 走frp(HTTPS)穿透来实现外网访问.(主要还是内网使用, 穿透只是为了以防万一)
更新日志
页面完成.(2/11)
Vue.js 复习.(2/12)(春节快乐!)
完成demo.(2/13)
修改页面, 对小屏(手机)页面更加友好.(2/15)
完善错误处理流程, 按钮点击流程.(2/17)(过年这几天出去玩了)
项目总览
下面回顾一下我为了方便控制家里的智能家居都做了什么事吧
使用网页进行交互.
HTML/CSS 使用第三方组件 www.layui.com
JS 使用Vue.js
Vue.js
提前使用第三方组件搭建出家里的布局, 绑定 @click + 传递参数 传递点击的具体区域.
使用Json存储家中不同区域的设备, 根据 @click 传递的参数再用 v-for 显示设备按钮, 以供点击.
使用WebSocket与后端进行通信, 做了错误处理和消息返回.
后端
后端使用PHP, Workerman实现Websocket通信, 使用PHP自带的TCP通讯函数实现与网关的TCP通讯.
服务器在家中内网, 使用frp穿透实现远程控制, 但是大多数操作都在内网完成, 穿透仅作为备用手段.
服务器使用定时任务上报状态到我的云主机, 云主机也使用定时任务检测家中服务器是否上报.
如无上报就通过 Server酱->Bark 推送消息到我的手机提醒.
控制智能设备的网关品牌为HBANG, 其他具体的情况我在上文已经写出了.(是我这的本地品牌哦)
总结
这次尝试让我拓展了许多知识: TCP/TLS握手挥手过程, 如何在电脑和手机上进行抓包, PHP中TCP通讯相关函数, WX小程序编写(萌新), Vue编写页面实战.
从开始到结束跨度半个多月(1/27 - 2/17), 上一次有这个热情还是在折腾MC服务器的时候.
感谢父母的鼓励和神奇的百度和支持.
如果没有意外的话, 这次的尝试就到此为止了, 我已经实现了我想要的功能, 也获得不少新的姿势.
2021 / 12 / 17 —-Cnzw
真难
Wireshark使用入门 - Cocowool - 博客园
wireshark抓包新手使用教程 - jack_Meng - 博客园
tcpdump Binary Downloads (32 Bit) | Android tcpdump
Android实时抓包:tcpdump+nc+wireshark
使用wireshark 动态显示Android应用中的联网数据_51CTO博客_Android wireshark
TCP 握手和挥手图解(有限状态机)_tlcp 连接-CSDN博客
坑-1:
adb版本过旧导致的安卓设备offline
坑-2:
WOL唤醒(Wake On Lan), 不能用半吊子的工具唤醒.
用正经的…害得我BIOS/网卡设置折腾了半天(最后是2006年的一个26K的exe唤醒的).
稍后会尝试frpc的WOL唤醒.成功是成功了, 但是不打算用.(没有第二台设备搭载frp实现WOL唤醒)
坑-3:
Vue配合axios使用时json数据保存失败的情况.
详情 -> vuejs/core · Discussions
最后也没找到解决方案, 只能换用 vue-resource.
/data/local/tcpdump -i any -p -s 0 -w /sdcard/capture.pcap\r\nadb pull /sdcard/capture.pcap capture.pcap