一次关于智能家居网关的折腾

原标题: 一次超出能力范围的尝试

最近家里的智能家居被修复了(喊工程师折腾一下午)
于是想把控制智能家居的接口扒出来,自己封装成wx小程序

应用类型: 智能家居控制
特征: 支持米家第三方控制, 局域网直连(无视账户绑定), APP控制.

先占个坑, 贴几个教程链接.
等我折腾完再完善.

网关通信数据获取

抓包

首先想到的是抓包, 直接扒出接口.

应用的用户鉴权是走的HTTPS协议, 可以直接扒出来.
用户登录, 注册, 找回密码, 查询网关绑定(改绑), 用户信息, 修改密码, 操作日志. 这些都有接口(没错就是没有用户登出).
从查询绑定接口可以获得网关ID, 网关Code(类似校验码); 从操作日志可以获得各个智能家居元件的ID.
服务器是杭州阿里云, 鉴权接口是Java写的.(1/26)
但是关键的控制数据走的是TLSv1.2.
而且包裹的不是HTTP协议, 而是其他数据, 尝试了Fiddler/Wireshark/Charles均扒不出内容无奈放弃.
借此熟悉了TCP三次握手/TLS四次握手/中间人攻击, 也算没有白折腾一趟吧.(1/28)

米家

随后想了一下能不能通过米家曲线救国.
然后跑去米家官网查阅了第三方接入的文档
发现是通过OAuth进行交互的, 那就没我啥事了...(1/28)

这几天没事又看了一遍米家文档(顺便熟悉OAuth)(2/21)
这里是米家和第三方服务器鉴权和通信流程:

OAuth登录流程

局域网

那最后只有通过局域网方式解决了, 不行的话只能放弃, 用他开发的APP.
使用tcpdump抓取的APP局域网数据:

C -> 255.255.255.255:8818 574d05383838383838 //UDP广播五次
S -> C 574d0500c0a800670000000000000000000000000000000000158d00020365e8 //UDP回复 8-16是IP地址 -16是网关ID
//TCP三次握手 (直到程序退出或其他原因断线)
C <-> S:8018
C -> S 085f0100006801
S -> C 085f05000068019a5399bc //类似于FTP的Hello.(1/30)
//随后是传输所有设备数据/区域分类/情景模.
//如果客户端已经有缓存, 就只传输设备开关状态.(2/1)
//控制数据已经可以复现请求了, 但是是无序的(最少我没找到规律).(2/2)

好消息是成功获取到了局域网下APP请求网关的控制数据, 并且网关没有校验流程(直接TCP连上就可以发送控制数据, 没有任何校验), 且控制数据看来长期不会变.
坏消息是每个设备的控制数据是无序的, 只能挨个请求并记录.
已经在着手编写PC的模拟请求了, 使用的是E(复现和记录控制数据用).(2/3)
着手编写基于PHP的模拟请求.

为啥不直接内网穿透+DDNS呢?

  1. 首先当然是没钱, 家里的路由器过老, 没有各自服务商的DDNS服务, 只有向日葵的DDNS.(流量少还不支持自定义域名)
  2. 自己有云服务器可以整frp穿透.
  3. wx小程序不支持TCP Socket, 所以需要"第三方"才能传递数据到网关.
  4. 直接暴露网关的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不会因为这个断连).

最后找到解决方案:

socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, Array("sec"=>2, "usec"=>0)); //RECV超时两秒就返回
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, Array("sec"=>2, "usec"=>0)); //SEND超时两秒就返回
  • 因为TCP/UDP的返回数据是十六进制的, 刚开始我是直接 json_encode() 就走WebSocket了.
    然后在传输有些十六进制数据的时候 json_encode() 会FALSE, 返回空数据.

解决方法非常简单: (我还在 pack() 上纠结了许久)
hex2bin()/bin2hex() 俩兄弟用一下就不会出现这些问题了

PHP端实现通过WebSocket与网关通信(UDP/TCP).(2/7)

PS. 推荐一个WebSocket测试的网址http://www.easyswoole.com/wstool.html, 带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 使用第三方组件 Layui
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-1
wireshark-2
tcpdump-下载
tcpdump-使用
tcpdump-实时wireshark-1
tcpdump-实时wireshark-2
tcp
[wx-mini-programs]12

坑-1:
adb版本过旧导致的安卓设备offline
adb下载

坑-2:
WOL唤醒(Wake On Lan), 不能用半吊子的工具唤醒.
用正经的...害得我BIOS/网卡设置折腾了半天(最后是2006年的一个26K的exe唤醒的).
稍后会尝试frpc的WOL唤醒.成功是成功了, 但是不打算用.(没有第二台设备搭载frp实现WOL唤醒)

坑-3:
Vue配合axios使用时json数据保存失败的情况.
详情 -> forum.vuejs.org
最后也没找到解决方案, 只能换用 vue-resource.

/data/local/tcpdump -i any -p -s 0 -w /sdcard/capture.pcap
adb pull /sdcard/capture.pcap capture.pcap 
最后修改:2021 年 03 月 02 日 08 : 15 PM