通过小米路由器api实现小米路由器的登录和实时信息的获取。文章中代码以Golang为例。
使用到的api:
http://<ip>/cgi-bin/luci/api/xqsystem/init_info
该api不需要登录,可以查看:
- 路由器的名称和型号
- 连接设备的数量
- 加密方式等信息
http://<ip>/cgi-bin/luci/api/xqsystem/login
登录接口的api,登录成功后可以得到token
http://<ip>/cgi-bin/luci/;stok=<token>/api/misystem/status
查看路由器运行状态,可以得到连接的设备的信息,路由器的实时网速,流量情况,cpu的情况和温度,运行时间,内存使用情况
登录
登录参数
小米路由器登陆的api是:http://192.168.31.1/cgi-bin/luci/api/xqsystem/login
登录发送post请求,通过浏览器抓包可以看到携带4个登陆的参数;
- username:小米路由器默认admin
- password:经过加密规则加密后的密码
- logtype:默认为2
- nonce:携带着设备信息,时间,随机数的字符串
登录得到token
- 获取加密规则
小米路由器不同的型号有两种不同的加密规则:
- 第一种是旧的加密方式:sha1加密
- 第二种是新加密方式:sha256加密
通过访问apihttp://<ip>/cgi-bin/luci/api/xqsystem/init_info
获得加密规则。
func GetName(ip string) (bool, string, string) {
url := fmt.Sprintf("http://%s/cgi-bin/luci/api/xqsystem/init_info", ip)
resp, err := http.Get(url)
if err != nil {
fmt.Println("http.Get", err)
}
pageBytes, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("io.ReadAll", err)
}
resultMap := make(map[string]interface{})
err = json.Unmarshal(pageBytes, &resultMap)
if err != nil {
fmt.Println("json.Unmarshal", err)
}
// 路由器名称
routerName := resultMap["routername"].(string)
// 路由器型号
hardwareName := resultMap["hardware"].(string)
// 获取加密方式
encryptMode, ok := resultMap["newEncryptMode"].(float64)
if !ok {
fmt.Println("使用旧加密模式")
return false, routerName, hardwareName
}
if encryptMode != 0 {
fmt.Println("使用新加密模式")
return true, routerName, hardwareName
}
return false, routerName, hardwareName
}
- 获得参数nonce
nonce参数的值主要由四部分组成:类型的值、设备的id、时间戳、随机数
其中设备id可以省略,也可以查看登录页面源代码中的 deviceId =
获得:
生成nonce的值的golang代码为:
func createNonce() string {
typeVar := 0
deviceID := ""
timeVar := int(time.Now().Unix())
randomVar := rand.Intn(10000)
return fmt.Sprintf("%d_%s_%d_%d", typeVar, deviceID, timeVar, randomVar)
}
- 获得加密后的密码:
查看小米路由器登录页面的源代码,可以看到两种加密方式的规则:
将两种加密方式转换为golang代码:
- 旧加密方式:nonce为前面生成的值,key的值是登陆页面源代码中
Encrypt
中的key的值。
func hashPassword(pwd string, nonce string, key string) string {
pwdKey := pwd + key
pwdKeyHash := sha1.New()
pwdKeyHash.Write([]byte(pwdKey))
pwdKeyHashStr := fmt.Sprintf("%x", pwdKeyHash.Sum(nil))
noncePwdKey := nonce + pwdKeyHashStr
noncePwdKeyHash := sha1.New()
noncePwdKeyHash.Write([]byte(noncePwdKey))
noncePwdKeyHashStr := fmt.Sprintf("%x", noncePwdKeyHash.Sum(nil))
return noncePwdKeyHashStr
}
- 新加密方式
func newhashPassword(pwd string, nonce string, key string) string {
pwdKey := pwd + key
pwdKeyHash := sha256.Sum256([]byte(pwdKey))
pwdKeyHashStr := hex.EncodeToString(pwdKeyHash[:])
noncePwdKey := nonce + pwdKeyHashStr
noncePwdKeyHash := sha256.Sum256([]byte(noncePwdKey))
noncePwdKeyHashStr := hex.EncodeToString(noncePwdKeyHash[:])
return noncePwdKeyHashStr
}
- 登录小米路由器,获得token
func GetToken(ip string, password string, key string) string {
ourl := fmt.Sprintf("http://%s/cgi-bin/luci/api/xqsystem/login", ip)
nonce := createNonce()
// 我的路由器使用旧的加密方式
hashedPassword := hashPassword(password, nonce, key)
params := url.Values{}
params.Set("username", "admin")
params.Set("password", hashedPassword)
params.Set("logtype", "2")
params.Set("nonce", nonce)
resp, err := http.PostForm(ourl, params)
if err != nil {
fmt.Println("http.PostForm", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("io.ReadAll", err)
}
resultMap := make(map[string]interface{})
err = json.Unmarshal(body, &resultMap)
if err != nil {
fmt.Println("json.Unmarshal", err)
}
var token string
if resultMap["code"].(float64) == 0 {
token = resultMap["token"].(string)
} else {
fmt.Println("登陆失败")
}
return token
}
- 携带token发送get请求,获得路由器实时状态
下述代码中只获取了部分状态信息。
func GetStatus(ip string, token string) map[string]interface{} {
url := fmt.Sprintf("http://%s/cgi-bin/luci/;stok=%s/api/misystem/status", ip, token)
resp, err := http.Get(url)
if err != nil {
fmt.Println("http.Get", err)
}
pageBytes, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("io.ReadAll", err)
}
defer resp.Body.Close()
var resultMap map[string]interface{}
err = json.Unmarshal(pageBytes, &resultMap)
if err != nil {
fmt.Println("json.Unmarshal", err)
}
status := make(map[string]interface{})
// 总上传量
upload, _ := strconv.ParseFloat(resultMap["wan"].(map[string]interface{})["upload"].(string), 64)
// 总下载量
download, _ := strconv.ParseFloat(resultMap["wan"].(map[string]interface{})["download"].(string), 64)
// 下载速度
downSpeed, _ := strconv.ParseFloat(resultMap["wan"].(map[string]interface{})["downspeed"].(string), 64)
// 上传速度
upSpeed, _ := strconv.ParseFloat(resultMap["wan"].(map[string]interface{})["upspeed"].(string), 64)
// cpu占用率
cpuload := resultMap["cpu"].(map[string]interface{})["load"].(float64)
// cpu温度
cputp := int(resultMap["temperature"].(float64))
// 内存占用
memusage := resultMap["mem"].(map[string]interface{})["usage"].(float64)
upTime, _ := strconv.ParseFloat(resultMap["upTime"].(string), 64)
// fmt.Println(utils.FormatSpeed(upload), download, downSpeed, upSpeed, cpuload, cputp, memusage, upTime)
status["upload"] = utils.FormatSpeed(upload)
status["download"] = utils.FormatSpeed(download)
status["downSpeed"] = utils.FormatSpeed(downSpeed)
status["upSpeed"] = utils.FormatSpeed(upSpeed)
status["cpuload"] = math.Round(cpuload*10) / 10
status["cputp"] = cputp
status["memusage"] = memusage
status["upTime"] = fmt.Sprint(math.Round(upTime/3600/24*10)/10, "天")
return status
}
FormatSpeed
函数:
func FormatSpeed(speed float64) string {
switch {
case speed >= 1024*1024*1024:
return fmt.Sprintf("%.2fG", speed/(1024*1024*1024))
case speed >= 1024*1024:
return fmt.Sprintf("%.2fM", speed/(1024*1024))
case speed >= 1024:
return fmt.Sprintf("%.2fKB", speed/1024)
default:
return fmt.Sprintf("%.2fB", speed)
}
}