mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-10 07:25:37 -05:00
308 lines
7.1 KiB
Go
308 lines
7.1 KiB
Go
package network
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log"
|
|
"github.com/Wifx/gonetworkmanager/v2"
|
|
"github.com/godbus/dbus/v5"
|
|
)
|
|
|
|
const (
|
|
dbusNMPath = "/org/freedesktop/NetworkManager"
|
|
dbusNMInterface = "org.freedesktop.NetworkManager"
|
|
dbusNMDeviceInterface = "org.freedesktop.NetworkManager.Device"
|
|
dbusNMWirelessInterface = "org.freedesktop.NetworkManager.Device.Wireless"
|
|
dbusNMAccessPointInterface = "org.freedesktop.NetworkManager.AccessPoint"
|
|
dbusPropsInterface = "org.freedesktop.DBus.Properties"
|
|
|
|
NmDeviceStateReasonWrongPassword = 8
|
|
NmDeviceStateReasonSupplicantTimeout = 24
|
|
NmDeviceStateReasonSupplicantFailed = 25
|
|
NmDeviceStateReasonSecretsRequired = 7
|
|
NmDeviceStateReasonNoSecrets = 6
|
|
NmDeviceStateReasonNoSsid = 10
|
|
NmDeviceStateReasonDhcpClientFailed = 14
|
|
NmDeviceStateReasonIpConfigUnavailable = 18
|
|
NmDeviceStateReasonSupplicantDisconnect = 23
|
|
NmDeviceStateReasonCarrier = 40
|
|
NmDeviceStateReasonNewActivation = 60
|
|
)
|
|
|
|
type NetworkManagerBackend struct {
|
|
nmConn interface{}
|
|
ethernetDevice interface{}
|
|
wifiDevice interface{}
|
|
settings interface{}
|
|
wifiDev interface{}
|
|
|
|
dbusConn *dbus.Conn
|
|
signals chan *dbus.Signal
|
|
sigWG sync.WaitGroup
|
|
stopChan chan struct{}
|
|
|
|
secretAgent *SecretAgent
|
|
promptBroker PromptBroker
|
|
|
|
state *BackendState
|
|
stateMutex sync.RWMutex
|
|
|
|
lastFailedSSID string
|
|
lastFailedTime int64
|
|
failedMutex sync.RWMutex
|
|
|
|
onStateChange func()
|
|
}
|
|
|
|
func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*NetworkManagerBackend, error) {
|
|
var nm gonetworkmanager.NetworkManager
|
|
var err error
|
|
|
|
if len(nmConn) > 0 && nmConn[0] != nil {
|
|
// Use injected connection (for testing)
|
|
nm = nmConn[0]
|
|
} else {
|
|
// Create real connection
|
|
nm, err = gonetworkmanager.NewNetworkManager()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to connect to NetworkManager: %w", err)
|
|
}
|
|
}
|
|
|
|
backend := &NetworkManagerBackend{
|
|
nmConn: nm,
|
|
stopChan: make(chan struct{}),
|
|
state: &BackendState{
|
|
Backend: "networkmanager",
|
|
},
|
|
}
|
|
|
|
return backend, nil
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) Initialize() error {
|
|
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
|
|
|
if s, err := gonetworkmanager.NewSettings(); err == nil {
|
|
b.settings = s
|
|
}
|
|
|
|
devices, err := nm.GetDevices()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get devices: %w", err)
|
|
}
|
|
|
|
for _, dev := range devices {
|
|
devType, err := dev.GetPropertyDeviceType()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
switch devType {
|
|
case gonetworkmanager.NmDeviceTypeEthernet:
|
|
if managed, _ := dev.GetPropertyManaged(); !managed {
|
|
continue
|
|
}
|
|
b.ethernetDevice = dev
|
|
if err := b.updateEthernetState(); err != nil {
|
|
continue
|
|
}
|
|
_, err := b.listEthernetConnections()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get wired configurations: %w", err)
|
|
}
|
|
|
|
case gonetworkmanager.NmDeviceTypeWifi:
|
|
b.wifiDevice = dev
|
|
if w, err := gonetworkmanager.NewDeviceWireless(dev.GetPath()); err == nil {
|
|
b.wifiDev = w
|
|
}
|
|
wifiEnabled, err := nm.GetPropertyWirelessEnabled()
|
|
if err == nil {
|
|
b.stateMutex.Lock()
|
|
b.state.WiFiEnabled = wifiEnabled
|
|
b.stateMutex.Unlock()
|
|
}
|
|
if err := b.updateWiFiState(); err != nil {
|
|
continue
|
|
}
|
|
if wifiEnabled {
|
|
if _, err := b.updateWiFiNetworks(); err != nil {
|
|
log.Warnf("Failed to get initial networks: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := b.updatePrimaryConnection(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := b.ListVPNProfiles(); err != nil {
|
|
log.Warnf("Failed to get initial VPN profiles: %v", err)
|
|
}
|
|
|
|
if _, err := b.ListActiveVPN(); err != nil {
|
|
log.Warnf("Failed to get initial active VPNs: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) Close() {
|
|
close(b.stopChan)
|
|
b.StopMonitoring()
|
|
|
|
if b.secretAgent != nil {
|
|
b.secretAgent.Close()
|
|
}
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) GetCurrentState() (*BackendState, error) {
|
|
b.stateMutex.RLock()
|
|
defer b.stateMutex.RUnlock()
|
|
|
|
state := *b.state
|
|
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
|
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
|
state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
|
|
state.VPNActive = append([]VPNActive(nil), b.state.VPNActive...)
|
|
|
|
return &state, nil
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) StartMonitoring(onStateChange func()) error {
|
|
b.onStateChange = onStateChange
|
|
|
|
if err := b.startSecretAgent(); err != nil {
|
|
return fmt.Errorf("failed to start secret agent: %w", err)
|
|
}
|
|
|
|
if err := b.startSignalPump(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) StopMonitoring() {
|
|
b.stopSignalPump()
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) GetPromptBroker() PromptBroker {
|
|
return b.promptBroker
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) SetPromptBroker(broker PromptBroker) error {
|
|
if broker == nil {
|
|
return fmt.Errorf("broker cannot be nil")
|
|
}
|
|
|
|
hadAgent := b.secretAgent != nil
|
|
|
|
b.promptBroker = broker
|
|
|
|
if b.secretAgent != nil {
|
|
b.secretAgent.Close()
|
|
b.secretAgent = nil
|
|
}
|
|
|
|
if hadAgent {
|
|
return b.startSecretAgent()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) SubmitCredentials(token string, secrets map[string]string, save bool) error {
|
|
if b.promptBroker == nil {
|
|
return fmt.Errorf("prompt broker not initialized")
|
|
}
|
|
|
|
return b.promptBroker.Resolve(token, PromptReply{
|
|
Secrets: secrets,
|
|
Save: save,
|
|
Cancel: false,
|
|
})
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) CancelCredentials(token string) error {
|
|
if b.promptBroker == nil {
|
|
return fmt.Errorf("prompt broker not initialized")
|
|
}
|
|
|
|
return b.promptBroker.Resolve(token, PromptReply{
|
|
Cancel: true,
|
|
})
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) ensureWiFiDevice() error {
|
|
if b.wifiDev != nil {
|
|
return nil
|
|
}
|
|
|
|
if b.wifiDevice == nil {
|
|
return fmt.Errorf("no WiFi device available")
|
|
}
|
|
|
|
dev := b.wifiDevice.(gonetworkmanager.Device)
|
|
wifiDev, err := gonetworkmanager.NewDeviceWireless(dev.GetPath())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get wireless device: %w", err)
|
|
}
|
|
b.wifiDev = wifiDev
|
|
return nil
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) startSecretAgent() error {
|
|
if b.promptBroker == nil {
|
|
return fmt.Errorf("prompt broker not set")
|
|
}
|
|
|
|
agent, err := NewSecretAgent(b.promptBroker, nil, b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.secretAgent = agent
|
|
return nil
|
|
}
|
|
|
|
func (b *NetworkManagerBackend) getActiveConnections() (map[string]bool, error) {
|
|
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
|
|
|
activeUUIDs := make(map[string]bool)
|
|
|
|
activeConns, err := nm.GetPropertyActiveConnections()
|
|
if err != nil {
|
|
return activeUUIDs, fmt.Errorf("failed to get active connections: %w", err)
|
|
}
|
|
|
|
for _, activeConn := range activeConns {
|
|
connType, err := activeConn.GetPropertyType()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if connType != "802-3-ethernet" {
|
|
continue
|
|
}
|
|
|
|
state, err := activeConn.GetPropertyState()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if state < 1 || state > 2 {
|
|
continue
|
|
}
|
|
|
|
uuid, err := activeConn.GetPropertyUUID()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
activeUUIDs[uuid] = true
|
|
}
|
|
return activeUUIDs, nil
|
|
}
|