From 953d0d8bc93dc510d0abbb4bf689c950bfc58da5 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Wed, 24 Jun 2026 18:29:59 -0500 Subject: [PATCH] Rewrite entire network package and update wifi page --- internal/network/network.go | 107 +++++++++++++++++++++++++++------ internal/page/wifiSetupPage.go | 57 ++++++++---------- 2 files changed, 114 insertions(+), 50 deletions(-) diff --git a/internal/network/network.go b/internal/network/network.go index 08a1c99..c31128a 100644 --- a/internal/network/network.go +++ b/internal/network/network.go @@ -28,6 +28,22 @@ const ( nmDeviceTypeWifi uint32 = 2 nmDeviceStateActivated uint32 = 100 APFlagsPrivacy uint32 = 0x00000001 // Equivalent to NM_802_11_AP_FLAGS_PRIVACY + + // Device states (from NM spec) + nmDeviceStateUnknown uint32 = 0 + nmDeviceStateUnmanaged uint32 = 10 + nmDeviceStateUnavailable uint32 = 20 + nmDeviceStateDisconnected uint32 = 30 + nmDeviceStateFailed uint32 = 120 + + // AP Security Flags (from NM_802_11_AP_SEC_*) + APFlagsWpaPsk uint32 = 0x00000100 // WPA-Personal + APFlagsWpaEap uint32 = 0x00000200 // WPA-Enterprise + APFlagsRsnPsk uint32 = 0x00000100 // WPA2-Personal (same flag as WPA-PSK) + APFlagsRsnSae uint32 = 0x00000400 // WPA3-Personal (SAE) + APFlagsRsnEap uint32 = 0x00000200 // WPA2-Enterprise (same flag as WPA-EAP) + APFlagsRsnOwe uint32 = 0x00000800 // OWE (Opportunistic Wireless Encryption) + APFlagsRsnEapSuiteB uint32 = 0x00001000 // WPA3-Enterprise ) // ErrScanAborted is returned when a scan is cancelled via the stop channel. @@ -128,7 +144,11 @@ func (m *Manager) FindDevices() error { log.Printf("network: Skipping device %s, could not get DeviceType: %v", path, err) continue } - deviceType, _ := typeVariant.Value().(uint32) + deviceType, ok := typeVariant.Value().(uint32) + if !ok { + log.Printf("network: Skipping device %s, invalid DeviceType", path) + continue + } var newDevice Device switch deviceType { @@ -141,10 +161,29 @@ func (m *Manager) FindDevices() error { } newDevice.Path = path - ifaceVariant, _ := deviceObj.GetProperty(nmDeviceInterface + ".Interface") - newDevice.InterfaceName, _ = ifaceVariant.Value().(string) - stateVariant, _ := deviceObj.GetProperty(nmDeviceInterface + ".State") - newDevice.State, _ = stateVariant.Value().(uint32) + + // Safely get Interface property + ifaceVariant, err := deviceObj.GetProperty(nmDeviceInterface + ".Interface") + if err != nil { + log.Printf("network: Skipping device %s, could not get Interface: %v", path, err) + continue + } + if newDevice.InterfaceName, ok = ifaceVariant.Value().(string); !ok { + log.Printf("network: Skipping device %s, invalid Interface", path) + continue + } + + // Safely get State property + stateVariant, err := deviceObj.GetProperty(nmDeviceInterface + ".State") + if err != nil { + log.Printf("network: Skipping device %s, could not get State: %v", path, err) + continue + } + if newDevice.State, ok = stateVariant.Value().(uint32); !ok { + log.Printf("network: Skipping device %s, invalid State", path) + continue + } + m.Devices = append(m.Devices, newDevice) } return nil @@ -259,8 +298,8 @@ func (m *Manager) scanAccessPoints(apPaths []dbus.ObjectPath) ([]AccessPoint, er continue } - ssidBytes, _ := props["Ssid"].Value().([]byte) - if len(ssidBytes) == 0 { + ssidBytes, ok := props["Ssid"].Value().([]byte) + if !ok || len(ssidBytes) == 0 { continue } @@ -272,12 +311,30 @@ func (m *Manager) scanAccessPoints(apPaths []dbus.ObjectPath) ([]AccessPoint, er isProtected := (flags&APFlagsPrivacy != 0) || (wpaFlags != 0) || (rsnFlags != 0) - // Determine the key management type + // Determine key management type var keyMgmt string - if wpaFlags != 0 { - keyMgmt = "wpa-psk" + if !isProtected { + keyMgmt = "none" + } else if wpaFlags != 0 { + // WPA (not WPA2/WPA3) + if wpaFlags&APFlagsWpaPsk != 0 { + keyMgmt = "wpa-psk" + } else if wpaFlags&APFlagsWpaEap != 0 { + keyMgmt = "wpa-eap" + } else { + keyMgmt = "wpa-psk" // Fallback + } } else if rsnFlags != 0 { - keyMgmt = "wpa-psk" + // WPA2 or WPA3 (RSN) + if rsnFlags&APFlagsRsnSae != 0 { + keyMgmt = "sae" // WPA3-Personal + } else if rsnFlags&APFlagsRsnEapSuiteB != 0 { + keyMgmt = "eap-suite-b-192" // WPA3-Enterprise + } else if rsnFlags&APFlagsRsnEap != 0 { + keyMgmt = "wpa-eap" // WPA2-Enterprise + } else { + keyMgmt = "wpa-psk" // WPA2-Personal (default for RSN) + } } else { keyMgmt = "none" } @@ -313,6 +370,12 @@ func (m *Manager) Connect(wifiDevice Device, ap AccessPoint, passphrase string) return fmt.Errorf("cannot connect: provided device is not a Wi-Fi device") } + if wifiDevice.State == nmDeviceStateFailed || + wifiDevice.State == nmDeviceStateUnmanaged || + wifiDevice.State == nmDeviceStateUnavailable { + return fmt.Errorf("cannot connect: device is in an error state (state=%d)", wifiDevice.State) + } + settings := map[string]map[string]dbus.Variant{ "connection": { "type": dbus.MakeVariant("802-11-wireless"), @@ -365,14 +428,22 @@ func (m *Manager) Connect(wifiDevice Device, ap AccessPoint, passphrase string) // pingTest with a short timeout to prevent the UI loop from hanging func pingTest() bool { - // Use a dialer with a timeout instead of LookupHost which can hang - timeout := 2 * time.Second - conn, err := net.DialTimeout("tcp", "8.8.8.8:53", timeout) - if err != nil { - return false + targets := []string{ + "1.1.1.1:53", // Cloudflare DNS + "8.8.8.8:53", // Google DNS + "9.9.9.9:53", // Quad9 DNS + "149.112.112.112:53", // Quad9 Alternate } - conn.Close() - return true + timeout := 2 * time.Second + + for _, target := range targets { + conn, err := net.DialTimeout("tcp", target, timeout) + if err == nil { + conn.Close() + return true + } + } + return false } // getAllProperties is a helper to fetch all properties for a D-Bus object interface. diff --git a/internal/page/wifiSetupPage.go b/internal/page/wifiSetupPage.go index 92747c1..3da1405 100644 --- a/internal/page/wifiSetupPage.go +++ b/internal/page/wifiSetupPage.go @@ -91,13 +91,10 @@ func (p *WiFiSetupPage) Init(ctx *PageCtx) error { // Find Wi-Fi device if err := p.networkManager.FindDevices(); err != nil { - } - for _, dev := range p.networkManager.Devices { - if dev.Type == network.TypeWifi { - d := dev - p.wifiDevice = &d - break - } + // Clean up before returning error + p.networkManager.Close() + p.networkManager = nil + return fmt.Errorf("failed to find network devices: %w. Try rebooting the computer. If this is your 3rd reboot, call for help", err) } // Create UI elements @@ -161,17 +158,11 @@ func (p *WiFiSetupPage) startScan() { // HandleEvent processes user input. func (p *WiFiSetupPage) HandleEvent(event *sdl.Event) error { switch e := (*event).(type) { - case *sdl.TextInputEvent: if p.isEnteringPassword { - // If we get a TextInputEvent, the system supports high-level input. - // Mark it supported so we don't double-type from KeyboardEvents. - if !p.textInputSupported { - p.textInputSupported = true - } else { - p.passwordInput += e.GetText() - p.updatePasswordTexture() - } + p.passwordInput += e.GetText() // Always append text + p.textInputSupported = true // Mark as supported + p.updatePasswordTexture() } case *sdl.KeyboardEvent: @@ -180,13 +171,11 @@ func (p *WiFiSetupPage) HandleEvent(event *sdl.Event) error { } if p.isEnteringPassword { - // Pass the Modifiers (Shift/Caps) to the handler p.handlePasswordInput(e.Keysym.Sym, e.Keysym.Mod) } else { p.handleNetworkSelection(e.Keysym.Sym) } } - return nil } @@ -323,7 +312,7 @@ func (p *WiFiSetupPage) handleNetworkSelection(sym sdl.Keycode) { case sdl.K_RETURN: if len(p.accessPoints) > 0 && p.selectedIndex < len(p.accessPoints) { p.selectedAP = &p.accessPoints[p.selectedIndex].AP - p.pendingSSID = p.selectedAP.SSID + p.pendingSSID = p.selectedAP.SSID // Set for ALL networks (protected or not) p.connectionError = "" if p.selectedAP.IsProtected { p.isEnteringPassword = true @@ -338,7 +327,7 @@ func (p *WiFiSetupPage) handleNetworkSelection(sym sdl.Keycode) { if err != nil { p.isConnecting = false p.pendingSSID = "" - p.connectionError = "Failed to initiate connection" + p.connectionError = fmt.Sprintf("Failed to connect: %s", err.Error()) // Include actual error } } } @@ -384,18 +373,17 @@ func (p *WiFiSetupPage) Update(dt float32) error { // handleConnectionStatusUpdate CHECKS0 if connection succeeded or failed func (p *WiFiSetupPage) handleConnectionStatusUpdate(status *network.ConnectionStatus) { - if !p.isConnecting { + if !p.isConnecting || p.pendingSSID == "" { return } - // milestone 1: We are associated with an AP (even if internet is still pending) - isConnected := status.StatusCode == network.StatusWifiConnectedInternetUp || + // Only consider it successful if we're connected to the EXACT network we tried to join + isConnectedToPending := status.StatusCode == network.StatusWifiConnectedInternetUp || status.StatusCode == network.StatusWifiConnectedNoInternet - if isConnected { + if isConnectedToPending { activeSSID := p.getActiveSSID() - // If we see our SSID, OR if we're connected to anything while trying, call it a win. - if activeSSID == p.pendingSSID || activeSSID != "" { + if activeSSID == p.pendingSSID { // ONLY accept if it's the network we tried logger.Info("UI synced: Connected to %s", activeSSID) p.isConnecting = false p.pendingSSID = "" @@ -405,14 +393,12 @@ func (p *WiFiSetupPage) handleConnectionStatusUpdate(status *network.ConnectionS } } - // milestone 2: Handle the "Not Connected" state during the handshake + // Handle failure: If we've been trying for too long, show error if status.StatusCode == network.StatusWifiAvailableNotConnected { - // INCREASE THIS: 5 seconds is way too aggressive. - // Give it at least 20 seconds before assuming the password was wrong. if time.Since(p.connectionAttemptTime) > 20*time.Second { - if time.Since(p.connectionAttemptTime) > 15*time.Second { - p.isConnecting = false - p.connectionError = "Connection failed - Check password or signal" + p.isConnecting = false + p.connectionError = "Connection failed - Check password or signal" + if p.selectedAP != nil && p.selectedAP.IsProtected { p.isEnteringPassword = true p.enterPasswordMode() p.updatePasswordErrorTexture() @@ -553,6 +539,13 @@ func (p *WiFiSetupPage) updateConnectionStatusItem() { // buildNetworkUIList creates the renderable textures for the list of discovered networks. func (p *WiFiSetupPage) buildNetworkUIList(aps []network.AccessPoint) { + // Delete old textures to prevent memory leaks + for _, apItem := range p.accessPoints { + if apItem.Item.ID != 0 { + gl.DeleteTextures(1, &apItem.Item.ID) + } + } + // Get the currently active Wi-Fi SSID activeSSID := p.getActiveSSID() p.activeSSID = activeSSID