mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-10 07:25:37 -05:00
663 lines
15 KiB
Go
663 lines
15 KiB
Go
package network
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/backend/internal/errdefs"
|
|
"github.com/godbus/dbus/v5"
|
|
)
|
|
|
|
func (b *IWDBackend) updateState() error {
|
|
if b.devicePath == "" {
|
|
return nil
|
|
}
|
|
|
|
obj := b.conn.Object(iwdBusName, b.devicePath)
|
|
|
|
poweredVar, err := obj.GetProperty(iwdDeviceInterface + ".Powered")
|
|
if err == nil {
|
|
if powered, ok := poweredVar.Value().(bool); ok {
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiEnabled = powered
|
|
b.stateMutex.Unlock()
|
|
}
|
|
}
|
|
|
|
if b.stationPath == "" {
|
|
return nil
|
|
}
|
|
|
|
stationObj := b.conn.Object(iwdBusName, b.stationPath)
|
|
|
|
stateVar, err := stationObj.GetProperty(iwdStationInterface + ".State")
|
|
if err == nil {
|
|
if state, ok := stateVar.Value().(string); ok {
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiConnected = (state == "connected")
|
|
if state == "connected" {
|
|
b.state.NetworkStatus = StatusWiFi
|
|
} else {
|
|
b.state.NetworkStatus = StatusDisconnected
|
|
}
|
|
b.stateMutex.Unlock()
|
|
}
|
|
}
|
|
|
|
connNetVar, err := stationObj.GetProperty(iwdStationInterface + ".ConnectedNetwork")
|
|
if err == nil && connNetVar.Value() != nil {
|
|
if netPath, ok := connNetVar.Value().(dbus.ObjectPath); ok && netPath != "/" {
|
|
netObj := b.conn.Object(iwdBusName, netPath)
|
|
|
|
nameVar, err := netObj.GetProperty(iwdNetworkInterface + ".Name")
|
|
if err == nil {
|
|
if name, ok := nameVar.Value().(string); ok {
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiSSID = name
|
|
b.stateMutex.Unlock()
|
|
}
|
|
}
|
|
|
|
var orderedNetworks [][]dbus.Variant
|
|
err = stationObj.Call(iwdStationInterface+".GetOrderedNetworks", 0).Store(&orderedNetworks)
|
|
if err == nil {
|
|
for _, netData := range orderedNetworks {
|
|
if len(netData) < 2 {
|
|
continue
|
|
}
|
|
currentNetPath, ok := netData[0].Value().(dbus.ObjectPath)
|
|
if !ok || currentNetPath != netPath {
|
|
continue
|
|
}
|
|
signalStrength, ok := netData[1].Value().(int16)
|
|
if !ok {
|
|
continue
|
|
}
|
|
signalDbm := signalStrength / 100
|
|
signal := uint8(signalDbm + 100)
|
|
if signalDbm > 0 {
|
|
signal = 100
|
|
} else if signalDbm < -100 {
|
|
signal = 0
|
|
}
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiSignal = signal
|
|
b.stateMutex.Unlock()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
networks, err := b.updateWiFiNetworks()
|
|
if err == nil {
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiNetworks = networks
|
|
b.stateMutex.Unlock()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *IWDBackend) GetWiFiEnabled() (bool, error) {
|
|
b.stateMutex.RLock()
|
|
defer b.stateMutex.RUnlock()
|
|
return b.state.WiFiEnabled, nil
|
|
}
|
|
|
|
func (b *IWDBackend) SetWiFiEnabled(enabled bool) error {
|
|
if b.devicePath == "" {
|
|
return fmt.Errorf("no WiFi device available")
|
|
}
|
|
|
|
obj := b.conn.Object(iwdBusName, b.devicePath)
|
|
call := obj.Call(dbusPropertiesInterface+".Set", 0, iwdDeviceInterface, "Powered", dbus.MakeVariant(enabled))
|
|
if call.Err != nil {
|
|
return fmt.Errorf("failed to set WiFi enabled: %w", call.Err)
|
|
}
|
|
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiEnabled = enabled
|
|
b.stateMutex.Unlock()
|
|
|
|
if b.onStateChange != nil {
|
|
b.onStateChange()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *IWDBackend) ScanWiFi() error {
|
|
if b.stationPath == "" {
|
|
return fmt.Errorf("no WiFi device available")
|
|
}
|
|
|
|
obj := b.conn.Object(iwdBusName, b.stationPath)
|
|
|
|
scanningVar, err := obj.GetProperty(iwdStationInterface + ".Scanning")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check scanning state: %w", err)
|
|
}
|
|
|
|
if scanning, ok := scanningVar.Value().(bool); ok && scanning {
|
|
return fmt.Errorf("scan already in progress")
|
|
}
|
|
|
|
call := obj.Call(iwdStationInterface+".Scan", 0)
|
|
if call.Err != nil {
|
|
return fmt.Errorf("scan request failed: %w", call.Err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *IWDBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
|
if b.stationPath == "" {
|
|
return nil, fmt.Errorf("no WiFi device available")
|
|
}
|
|
|
|
obj := b.conn.Object(iwdBusName, b.stationPath)
|
|
|
|
var orderedNetworks [][]dbus.Variant
|
|
err := obj.Call(iwdStationInterface+".GetOrderedNetworks", 0).Store(&orderedNetworks)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get networks: %w", err)
|
|
}
|
|
|
|
knownNetworks, err := b.getKnownNetworks()
|
|
if err != nil {
|
|
knownNetworks = make(map[string]bool)
|
|
}
|
|
|
|
autoconnectMap, err := b.getAutoconnectSettings()
|
|
if err != nil {
|
|
autoconnectMap = make(map[string]bool)
|
|
}
|
|
|
|
b.stateMutex.RLock()
|
|
currentSSID := b.state.WiFiSSID
|
|
wifiConnected := b.state.WiFiConnected
|
|
b.stateMutex.RUnlock()
|
|
|
|
networks := make([]WiFiNetwork, 0, len(orderedNetworks))
|
|
for _, netData := range orderedNetworks {
|
|
if len(netData) < 2 {
|
|
continue
|
|
}
|
|
|
|
networkPath, ok := netData[0].Value().(dbus.ObjectPath)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
signalStrength, ok := netData[1].Value().(int16)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
netObj := b.conn.Object(iwdBusName, networkPath)
|
|
|
|
nameVar, err := netObj.GetProperty(iwdNetworkInterface + ".Name")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
name, ok := nameVar.Value().(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
typeVar, err := netObj.GetProperty(iwdNetworkInterface + ".Type")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
netType, ok := typeVar.Value().(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
signalDbm := signalStrength / 100
|
|
signal := uint8(signalDbm + 100)
|
|
if signalDbm > 0 {
|
|
signal = 100
|
|
} else if signalDbm < -100 {
|
|
signal = 0
|
|
}
|
|
|
|
secured := netType != "open"
|
|
|
|
network := WiFiNetwork{
|
|
SSID: name,
|
|
Signal: signal,
|
|
Secured: secured,
|
|
Connected: wifiConnected && name == currentSSID,
|
|
Saved: knownNetworks[name],
|
|
Autoconnect: autoconnectMap[name],
|
|
Enterprise: netType == "8021x",
|
|
}
|
|
|
|
networks = append(networks, network)
|
|
}
|
|
|
|
sortWiFiNetworks(networks)
|
|
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiNetworks = networks
|
|
b.stateMutex.Unlock()
|
|
|
|
now := time.Now()
|
|
b.recentScansMu.Lock()
|
|
for _, net := range networks {
|
|
b.recentScans[net.SSID] = now
|
|
}
|
|
b.recentScansMu.Unlock()
|
|
|
|
return networks, nil
|
|
}
|
|
|
|
func (b *IWDBackend) getKnownNetworks() (map[string]bool, error) {
|
|
obj := b.conn.Object(iwdBusName, iwdObjectPath)
|
|
|
|
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
|
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
known := make(map[string]bool)
|
|
for _, interfaces := range objects {
|
|
if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
|
|
if nameVar, ok := knownProps["Name"]; ok {
|
|
if name, ok := nameVar.Value().(string); ok {
|
|
known[name] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return known, nil
|
|
}
|
|
|
|
func (b *IWDBackend) getAutoconnectSettings() (map[string]bool, error) {
|
|
obj := b.conn.Object(iwdBusName, iwdObjectPath)
|
|
|
|
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
|
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
autoconnectMap := make(map[string]bool)
|
|
for _, interfaces := range objects {
|
|
if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
|
|
if nameVar, ok := knownProps["Name"]; ok {
|
|
if name, ok := nameVar.Value().(string); ok {
|
|
autoconnect := true
|
|
if acVar, ok := knownProps["AutoConnect"]; ok {
|
|
if ac, ok := acVar.Value().(bool); ok {
|
|
autoconnect = ac
|
|
}
|
|
}
|
|
autoconnectMap[name] = autoconnect
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return autoconnectMap, nil
|
|
}
|
|
|
|
func (b *IWDBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error) {
|
|
b.stateMutex.RLock()
|
|
networks := b.state.WiFiNetworks
|
|
b.stateMutex.RUnlock()
|
|
|
|
var found *WiFiNetwork
|
|
for i := range networks {
|
|
if networks[i].SSID == ssid {
|
|
found = &networks[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if found == nil {
|
|
return nil, fmt.Errorf("network not found: %s", ssid)
|
|
}
|
|
|
|
return &NetworkInfoResponse{
|
|
SSID: ssid,
|
|
Bands: []WiFiNetwork{*found},
|
|
}, nil
|
|
}
|
|
|
|
func (b *IWDBackend) setConnectError(code string) {
|
|
b.stateMutex.Lock()
|
|
b.state.IsConnecting = false
|
|
b.state.ConnectingSSID = ""
|
|
b.state.LastError = code
|
|
b.stateMutex.Unlock()
|
|
}
|
|
|
|
func (b *IWDBackend) seenInRecentScan(ssid string) bool {
|
|
b.recentScansMu.Lock()
|
|
defer b.recentScansMu.Unlock()
|
|
lastSeen, ok := b.recentScans[ssid]
|
|
return ok && time.Since(lastSeen) < 30*time.Second
|
|
}
|
|
|
|
func (b *IWDBackend) classifyAttempt(att *connectAttempt) string {
|
|
att.mu.Lock()
|
|
defer att.mu.Unlock()
|
|
|
|
if att.sawPromptRetry {
|
|
return errdefs.ErrBadCredentials
|
|
}
|
|
|
|
if !att.connectedAt.IsZero() && !att.sawIPConfig {
|
|
connDuration := time.Since(att.connectedAt)
|
|
if connDuration > 500*time.Millisecond && connDuration < 3*time.Second {
|
|
return errdefs.ErrBadCredentials
|
|
}
|
|
}
|
|
|
|
if (att.sawAuthish || !att.connectedAt.IsZero()) && !att.sawIPConfig {
|
|
if time.Since(att.start) > 12*time.Second {
|
|
return errdefs.ErrDhcpTimeout
|
|
}
|
|
}
|
|
|
|
if !att.sawAuthish && att.connectedAt.IsZero() {
|
|
if !b.seenInRecentScan(att.ssid) {
|
|
return errdefs.ErrNoSuchSSID
|
|
}
|
|
return errdefs.ErrAssocTimeout
|
|
}
|
|
|
|
return errdefs.ErrAssocTimeout
|
|
}
|
|
|
|
func (b *IWDBackend) finalizeAttempt(att *connectAttempt, code string) {
|
|
att.mu.Lock()
|
|
if att.finalized {
|
|
att.mu.Unlock()
|
|
return
|
|
}
|
|
att.finalized = true
|
|
att.mu.Unlock()
|
|
|
|
b.stateMutex.Lock()
|
|
b.state.IsConnecting = false
|
|
b.state.ConnectingSSID = ""
|
|
b.state.LastError = code
|
|
b.stateMutex.Unlock()
|
|
|
|
b.updateState()
|
|
|
|
if b.onStateChange != nil {
|
|
b.onStateChange()
|
|
}
|
|
}
|
|
|
|
func (b *IWDBackend) startAttemptWatchdog(att *connectAttempt) {
|
|
b.sigWG.Add(1)
|
|
go func() {
|
|
defer b.sigWG.Done()
|
|
|
|
ticker := time.NewTicker(250 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
att.mu.Lock()
|
|
finalized := att.finalized
|
|
att.mu.Unlock()
|
|
|
|
if finalized || time.Now().After(att.deadline) {
|
|
if !finalized {
|
|
b.finalizeAttempt(att, b.classifyAttempt(att))
|
|
}
|
|
return
|
|
}
|
|
|
|
station := b.conn.Object(iwdBusName, b.stationPath)
|
|
stVar, err := station.GetProperty(iwdStationInterface + ".State")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
state, _ := stVar.Value().(string)
|
|
|
|
cnVar, err := station.GetProperty(iwdStationInterface + ".ConnectedNetwork")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
var connPath dbus.ObjectPath
|
|
if cnVar.Value() != nil {
|
|
connPath, _ = cnVar.Value().(dbus.ObjectPath)
|
|
}
|
|
|
|
att.mu.Lock()
|
|
if connPath == att.netPath && state == "connected" && att.connectedAt.IsZero() {
|
|
att.connectedAt = time.Now()
|
|
}
|
|
if state == "configuring" {
|
|
att.sawIPConfig = true
|
|
}
|
|
att.mu.Unlock()
|
|
|
|
case <-b.stopChan:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (b *IWDBackend) mapIwdDBusError(name string) string {
|
|
switch name {
|
|
case "net.connman.iwd.Error.AlreadyConnected":
|
|
return errdefs.ErrAlreadyConnected
|
|
case "net.connman.iwd.Error.AuthenticationFailed",
|
|
"net.connman.iwd.Error.InvalidKey",
|
|
"net.connman.iwd.Error.IncorrectPassphrase":
|
|
return errdefs.ErrBadCredentials
|
|
case "net.connman.iwd.Error.NotFound":
|
|
return errdefs.ErrNoSuchSSID
|
|
case "net.connman.iwd.Error.NotSupported":
|
|
return errdefs.ErrConnectionFailed
|
|
case "net.connman.iwd.Agent.Error.Canceled":
|
|
return errdefs.ErrUserCanceled
|
|
default:
|
|
return errdefs.ErrConnectionFailed
|
|
}
|
|
}
|
|
|
|
func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
|
|
if b.stationPath == "" {
|
|
b.setConnectError(errdefs.ErrWifiDisabled)
|
|
if b.onStateChange != nil {
|
|
b.onStateChange()
|
|
}
|
|
return fmt.Errorf("no WiFi device available")
|
|
}
|
|
|
|
networkPath, err := b.findNetworkPath(req.SSID)
|
|
if err != nil {
|
|
b.setConnectError(errdefs.ErrNoSuchSSID)
|
|
if b.onStateChange != nil {
|
|
b.onStateChange()
|
|
}
|
|
return fmt.Errorf("network not found: %w", err)
|
|
}
|
|
|
|
att := &connectAttempt{
|
|
ssid: req.SSID,
|
|
netPath: networkPath,
|
|
start: time.Now(),
|
|
deadline: time.Now().Add(15 * time.Second),
|
|
}
|
|
|
|
b.attemptMutex.Lock()
|
|
b.curAttempt = att
|
|
b.attemptMutex.Unlock()
|
|
|
|
b.stateMutex.Lock()
|
|
b.state.IsConnecting = true
|
|
b.state.ConnectingSSID = req.SSID
|
|
b.state.LastError = ""
|
|
b.stateMutex.Unlock()
|
|
|
|
if b.onStateChange != nil {
|
|
b.onStateChange()
|
|
}
|
|
|
|
netObj := b.conn.Object(iwdBusName, networkPath)
|
|
go func() {
|
|
call := netObj.Call(iwdNetworkInterface+".Connect", 0)
|
|
if call.Err != nil {
|
|
var code string
|
|
if dbusErr, ok := call.Err.(dbus.Error); ok {
|
|
code = b.mapIwdDBusError(dbusErr.Name)
|
|
} else if dbusErrPtr, ok := call.Err.(*dbus.Error); ok {
|
|
code = b.mapIwdDBusError(dbusErrPtr.Name)
|
|
} else {
|
|
code = errdefs.ErrConnectionFailed
|
|
}
|
|
|
|
att.mu.Lock()
|
|
if att.sawPromptRetry {
|
|
code = errdefs.ErrBadCredentials
|
|
}
|
|
att.mu.Unlock()
|
|
|
|
b.finalizeAttempt(att, code)
|
|
return
|
|
}
|
|
|
|
b.startAttemptWatchdog(att)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *IWDBackend) findNetworkPath(ssid string) (dbus.ObjectPath, error) {
|
|
obj := b.conn.Object(iwdBusName, iwdObjectPath)
|
|
|
|
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
|
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for path, interfaces := range objects {
|
|
if netProps, ok := interfaces[iwdNetworkInterface]; ok {
|
|
if nameVar, ok := netProps["Name"]; ok {
|
|
if name, ok := nameVar.Value().(string); ok && name == ssid {
|
|
return path, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("network not found")
|
|
}
|
|
|
|
func (b *IWDBackend) DisconnectWiFi() error {
|
|
if b.stationPath == "" {
|
|
return fmt.Errorf("no WiFi device available")
|
|
}
|
|
|
|
obj := b.conn.Object(iwdBusName, b.stationPath)
|
|
call := obj.Call(iwdStationInterface+".Disconnect", 0)
|
|
if call.Err != nil {
|
|
return fmt.Errorf("failed to disconnect: %w", call.Err)
|
|
}
|
|
|
|
b.updateState()
|
|
|
|
if b.onStateChange != nil {
|
|
b.onStateChange()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *IWDBackend) ForgetWiFiNetwork(ssid string) error {
|
|
b.stateMutex.RLock()
|
|
currentSSID := b.state.WiFiSSID
|
|
isConnected := b.state.WiFiConnected
|
|
b.stateMutex.RUnlock()
|
|
|
|
obj := b.conn.Object(iwdBusName, iwdObjectPath)
|
|
|
|
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
|
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for path, interfaces := range objects {
|
|
if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
|
|
if nameVar, ok := knownProps["Name"]; ok {
|
|
if name, ok := nameVar.Value().(string); ok && name == ssid {
|
|
knownObj := b.conn.Object(iwdBusName, path)
|
|
call := knownObj.Call(iwdKnownNetworkInterface+".Forget", 0)
|
|
if call.Err != nil {
|
|
return fmt.Errorf("failed to forget network: %w", call.Err)
|
|
}
|
|
|
|
if isConnected && currentSSID == ssid {
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiConnected = false
|
|
b.state.WiFiSSID = ""
|
|
b.state.WiFiSignal = 0
|
|
b.state.WiFiIP = ""
|
|
b.state.NetworkStatus = StatusDisconnected
|
|
b.stateMutex.Unlock()
|
|
}
|
|
|
|
if b.onStateChange != nil {
|
|
b.onStateChange()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("network not found")
|
|
}
|
|
|
|
func (b *IWDBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
|
obj := b.conn.Object(iwdBusName, iwdObjectPath)
|
|
|
|
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
|
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for path, interfaces := range objects {
|
|
if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
|
|
if nameVar, ok := knownProps["Name"]; ok {
|
|
if name, ok := nameVar.Value().(string); ok && name == ssid {
|
|
knownObj := b.conn.Object(iwdBusName, path)
|
|
call := knownObj.Call(dbusPropertiesInterface+".Set", 0, iwdKnownNetworkInterface, "AutoConnect", dbus.MakeVariant(autoconnect))
|
|
if call.Err != nil {
|
|
return fmt.Errorf("failed to set autoconnect: %w", call.Err)
|
|
}
|
|
|
|
b.updateState()
|
|
|
|
if b.onStateChange != nil {
|
|
b.onStateChange()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("network not found")
|
|
}
|