mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 04:53:00 +08:00
185 lines
6.2 KiB
Go
185 lines
6.2 KiB
Go
package openwrt
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/matthewlu070111/smart-srun/core/internal/domain"
|
|
)
|
|
|
|
// WirelessRadio is one radio as netifd has it running right now.
|
|
//
|
|
// The configured state is in UCI and says what should happen; this says what
|
|
// netifd made of it. The two disagree exactly when something went wrong, which
|
|
// is when this is worth asking.
|
|
type WirelessRadio struct {
|
|
Name string
|
|
// Up is netifd's own verdict on the radio, and Pending says it is still
|
|
// working on it. A radio that is pending is neither up nor failed yet, and
|
|
// reading it as either is how a wait ends early.
|
|
Up bool
|
|
Pending bool
|
|
// Disabled is the configuration's, not a fault.
|
|
Disabled bool
|
|
// RetrySetupFailed means netifd gave up bringing the radio up. Without it,
|
|
// a failed radio looks exactly like one that is merely slow.
|
|
RetrySetupFailed bool
|
|
Interfaces []WirelessInterface
|
|
}
|
|
|
|
// WirelessInterface is one wifi-iface on that radio.
|
|
//
|
|
// IfName is the field this exists for: it is the name iwinfo wants, and it
|
|
// cannot be derived from anything else. A station section on radio1 might be
|
|
// phy1-sta0, and a disabled one has no device at all -- measured on a real
|
|
// router, where `iwinfo devices` listed only the two access points because the
|
|
// station sections were disabled.
|
|
type WirelessInterface struct {
|
|
// Section is the UCI section that produced it, which is how this program
|
|
// finds the interface it owns among the household's.
|
|
Section string
|
|
IfName string
|
|
Mode string
|
|
SSID string
|
|
Network []string
|
|
}
|
|
|
|
// Station reports that this interface is a client rather than an access point.
|
|
func (i WirelessInterface) Station() bool {
|
|
return strings.EqualFold(strings.TrimSpace(i.Mode), "sta")
|
|
}
|
|
|
|
// wirelessStatusJSON decodes only the fields above.
|
|
//
|
|
// Deliberately not a map[string]any, and deliberately not every field. The
|
|
// `config` object in this reply contains the wireless passphrase in clear --
|
|
// for the household's own access point as well as this program's client. A
|
|
// struct that took the whole object would carry that value into anything that
|
|
// logged, serialised or reported a radio, and the only reliable way to keep a
|
|
// secret out of a log is not to have read it.
|
|
type wirelessStatusJSON struct {
|
|
Up bool `json:"up"`
|
|
Pending bool `json:"pending"`
|
|
Disabled bool `json:"disabled"`
|
|
RetrySetupFailed bool `json:"retry_setup_failed"`
|
|
Interfaces []struct {
|
|
Section string `json:"section"`
|
|
IfName string `json:"ifname"`
|
|
Config struct {
|
|
Mode string `json:"mode"`
|
|
SSID string `json:"ssid"`
|
|
Network []string `json:"network"`
|
|
} `json:"config"`
|
|
} `json:"interfaces"`
|
|
}
|
|
|
|
// ParseWirelessStatus reads `ubus call network.wireless status`.
|
|
//
|
|
// The reply is an object keyed by radio name, so the radios come back sorted
|
|
// rather than in whatever order the encoder used: a caller picking "the first
|
|
// radio" would otherwise pick a different one between calls.
|
|
func ParseWirelessStatus(data []byte) ([]WirelessRadio, error) {
|
|
if len(data) == 0 {
|
|
return nil, domain.Errorf(domain.CodeInternal,
|
|
"无线状态查询没有返回内容")
|
|
}
|
|
var raw map[string]wirelessStatusJSON
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return nil, domain.Errorf(domain.CodeInternal,
|
|
"无法解析无线状态").Wrap(err)
|
|
}
|
|
|
|
names := make([]string, 0, len(raw))
|
|
for name := range raw {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
|
|
radios := make([]WirelessRadio, 0, len(names))
|
|
for _, name := range names {
|
|
entry := raw[name]
|
|
radio := WirelessRadio{
|
|
Name: name,
|
|
Up: entry.Up,
|
|
Pending: entry.Pending,
|
|
Disabled: entry.Disabled,
|
|
RetrySetupFailed: entry.RetrySetupFailed,
|
|
}
|
|
for _, iface := range entry.Interfaces {
|
|
radio.Interfaces = append(radio.Interfaces, WirelessInterface{
|
|
Section: iface.Section,
|
|
IfName: iface.IfName,
|
|
Mode: iface.Config.Mode,
|
|
SSID: iface.Config.SSID,
|
|
Network: iface.Config.Network,
|
|
})
|
|
}
|
|
radios = append(radios, radio)
|
|
}
|
|
return radios, nil
|
|
}
|
|
|
|
// WirelessStatus asks netifd what it has made of the wireless configuration.
|
|
func (a *Adapter) WirelessStatus(ctx context.Context) ([]WirelessRadio, error) {
|
|
result, err := a.runner.Run(ctx, "ubus", "call", "network.wireless", "status")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if result.StdoutTruncated {
|
|
// Half this reply is half the radios, and the caller uses it to find
|
|
// the interface it owns. A short answer would read as "the interface is
|
|
// not there", which is the one conclusion that must not be guessed.
|
|
return nil, domain.Errorf(domain.CodeInternal,
|
|
"无线状态过长,已截断")
|
|
}
|
|
return ParseWirelessStatus(result.Stdout)
|
|
}
|
|
|
|
// FindRadio returns one radio by name.
|
|
func FindRadio(radios []WirelessRadio, name string) (WirelessRadio, bool) {
|
|
for _, radio := range radios {
|
|
if radio.Name == name {
|
|
return radio, true
|
|
}
|
|
}
|
|
return WirelessRadio{}, false
|
|
}
|
|
|
|
// FindInterface returns the interface one UCI section produced.
|
|
//
|
|
// Absent is an ordinary answer: a disabled section produces no interface, and
|
|
// netifd simply does not list one. Reporting that as a failure would make a
|
|
// client this program has not enabled yet look like a broken radio.
|
|
func (r WirelessRadio) FindInterface(section string) (WirelessInterface, bool) {
|
|
for _, iface := range r.Interfaces {
|
|
if iface.Section == section {
|
|
return iface, true
|
|
}
|
|
}
|
|
return WirelessInterface{}, false
|
|
}
|
|
|
|
// AnyDevice returns a device name on this radio, for operations that need the
|
|
// radio rather than a particular client -- a scan, most of all.
|
|
//
|
|
// A scan needs some interface to be up on the radio, and the one this program
|
|
// manages is exactly the one that may not be. Preferring a station when there
|
|
// is one keeps the scan on the interface that will do the associating; falling
|
|
// back to the access point is what makes scanning work at all on a router whose
|
|
// client section is still disabled.
|
|
func (r WirelessRadio) AnyDevice() (string, bool) {
|
|
for _, iface := range r.Interfaces {
|
|
if iface.Station() && iface.IfName != "" {
|
|
return iface.IfName, true
|
|
}
|
|
}
|
|
for _, iface := range r.Interfaces {
|
|
if iface.IfName != "" {
|
|
return iface.IfName, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|