1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-10 07:25:37 -05:00
Files
DankMaterialShell/backend/internal/server/network/backend_networkmanager_vpn.go
2025-11-12 17:18:45 -05:00

528 lines
12 KiB
Go

package network
import (
"fmt"
"sort"
"strings"
"time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log"
"github.com/Wifx/gonetworkmanager/v2"
)
func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
s := b.settings
if s == nil {
var err error
s, err = gonetworkmanager.NewSettings()
if err != nil {
return nil, fmt.Errorf("failed to get settings: %w", err)
}
b.settings = s
}
settingsMgr := s.(gonetworkmanager.Settings)
connections, err := settingsMgr.ListConnections()
if err != nil {
return nil, fmt.Errorf("failed to get connections: %w", err)
}
var profiles []VPNProfile
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
connID, _ := connMeta["id"].(string)
connUUID, _ := connMeta["uuid"].(string)
profile := VPNProfile{
Name: connID,
UUID: connUUID,
Type: connType,
}
if connType == "vpn" {
if vpnSettings, ok := settings["vpn"]; ok {
if svcType, ok := vpnSettings["service-type"].(string); ok {
profile.ServiceType = svcType
}
}
}
profiles = append(profiles, profile)
}
sort.Slice(profiles, func(i, j int) bool {
return strings.ToLower(profiles[i].Name) < strings.ToLower(profiles[j].Name)
})
b.stateMutex.Lock()
b.state.VPNProfiles = profiles
b.stateMutex.Unlock()
return profiles, nil
}
func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
nm := b.nmConn.(gonetworkmanager.NetworkManager)
activeConns, err := nm.GetPropertyActiveConnections()
if err != nil {
return nil, fmt.Errorf("failed to get active connections: %w", err)
}
var active []VPNActive
for _, activeConn := range activeConns {
connType, err := activeConn.GetPropertyType()
if err != nil {
continue
}
if connType != "vpn" && connType != "wireguard" {
continue
}
uuid, _ := activeConn.GetPropertyUUID()
id, _ := activeConn.GetPropertyID()
state, _ := activeConn.GetPropertyState()
var stateStr string
switch state {
case 0:
stateStr = "unknown"
case 1:
stateStr = "activating"
case 2:
stateStr = "activated"
case 3:
stateStr = "deactivating"
case 4:
stateStr = "deactivated"
}
vpnActive := VPNActive{
Name: id,
UUID: uuid,
State: stateStr,
Type: connType,
Plugin: "",
}
if connType == "vpn" {
conn, _ := activeConn.GetPropertyConnection()
if conn != nil {
connSettings, err := conn.GetSettings()
if err == nil {
if vpnSettings, ok := connSettings["vpn"]; ok {
if svcType, ok := vpnSettings["service-type"].(string); ok {
vpnActive.Plugin = svcType
}
}
}
}
}
active = append(active, vpnActive)
}
b.stateMutex.Lock()
b.state.VPNActive = active
b.stateMutex.Unlock()
return active, nil
}
func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool) error {
if singleActive {
active, err := b.ListActiveVPN()
if err == nil && len(active) > 0 {
alreadyConnected := false
for _, vpn := range active {
if vpn.UUID == uuidOrName || vpn.Name == uuidOrName {
alreadyConnected = true
break
}
}
if !alreadyConnected {
if err := b.DisconnectAllVPN(); err != nil {
log.Warnf("Failed to disconnect existing VPNs: %v", err)
}
time.Sleep(500 * time.Millisecond)
} else {
return nil
}
}
}
s := b.settings
if s == nil {
var err error
s, err = gonetworkmanager.NewSettings()
if err != nil {
return fmt.Errorf("failed to get settings: %w", err)
}
b.settings = s
}
settingsMgr := s.(gonetworkmanager.Settings)
connections, err := settingsMgr.ListConnections()
if err != nil {
return fmt.Errorf("failed to get connections: %w", err)
}
var targetConn gonetworkmanager.Connection
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
connID, _ := connMeta["id"].(string)
connUUID, _ := connMeta["uuid"].(string)
if connUUID == uuidOrName || connID == uuidOrName {
targetConn = conn
break
}
}
if targetConn == nil {
return fmt.Errorf("VPN connection not found: %s", uuidOrName)
}
targetSettings, err := targetConn.GetSettings()
if err != nil {
return fmt.Errorf("failed to get connection settings: %w", err)
}
var targetUUID string
if connMeta, ok := targetSettings["connection"]; ok {
if uuid, ok := connMeta["uuid"].(string); ok {
targetUUID = uuid
}
}
b.stateMutex.Lock()
b.state.IsConnectingVPN = true
b.state.ConnectingVPNUUID = targetUUID
b.stateMutex.Unlock()
if b.onStateChange != nil {
b.onStateChange()
}
nm := b.nmConn.(gonetworkmanager.NetworkManager)
activeConn, err := nm.ActivateConnection(targetConn, nil, nil)
if err != nil {
b.stateMutex.Lock()
b.state.IsConnectingVPN = false
b.state.ConnectingVPNUUID = ""
b.stateMutex.Unlock()
if b.onStateChange != nil {
b.onStateChange()
}
return fmt.Errorf("failed to activate VPN: %w", err)
}
if activeConn != nil {
state, _ := activeConn.GetPropertyState()
if state == 2 {
b.stateMutex.Lock()
b.state.IsConnectingVPN = false
b.state.ConnectingVPNUUID = ""
b.stateMutex.Unlock()
b.ListActiveVPN()
if b.onStateChange != nil {
b.onStateChange()
}
}
}
return nil
}
func (b *NetworkManagerBackend) DisconnectVPN(uuidOrName string) error {
nm := b.nmConn.(gonetworkmanager.NetworkManager)
activeConns, err := nm.GetPropertyActiveConnections()
if err != nil {
return fmt.Errorf("failed to get active connections: %w", err)
}
log.Debugf("[DisconnectVPN] Looking for VPN: %s", uuidOrName)
for _, activeConn := range activeConns {
connType, err := activeConn.GetPropertyType()
if err != nil {
continue
}
if connType != "vpn" && connType != "wireguard" {
continue
}
uuid, _ := activeConn.GetPropertyUUID()
id, _ := activeConn.GetPropertyID()
state, _ := activeConn.GetPropertyState()
log.Debugf("[DisconnectVPN] Found active VPN: uuid=%s id=%s state=%d", uuid, id, state)
if uuid == uuidOrName || id == uuidOrName {
log.Infof("[DisconnectVPN] Deactivating VPN: %s (state=%d)", id, state)
if err := nm.DeactivateConnection(activeConn); err != nil {
return fmt.Errorf("failed to deactivate VPN: %w", err)
}
b.ListActiveVPN()
if b.onStateChange != nil {
b.onStateChange()
}
return nil
}
}
log.Warnf("[DisconnectVPN] VPN not found in active connections: %s", uuidOrName)
s := b.settings
if s == nil {
var err error
s, err = gonetworkmanager.NewSettings()
if err != nil {
return fmt.Errorf("VPN connection not active and cannot access settings: %w", err)
}
b.settings = s
}
settingsMgr := s.(gonetworkmanager.Settings)
connections, err := settingsMgr.ListConnections()
if err != nil {
return fmt.Errorf("VPN connection not active: %s", uuidOrName)
}
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
connID, _ := connMeta["id"].(string)
connUUID, _ := connMeta["uuid"].(string)
if connUUID == uuidOrName || connID == uuidOrName {
log.Infof("[DisconnectVPN] VPN connection exists but not active: %s", connID)
return nil
}
}
return fmt.Errorf("VPN connection not found: %s", uuidOrName)
}
func (b *NetworkManagerBackend) DisconnectAllVPN() error {
nm := b.nmConn.(gonetworkmanager.NetworkManager)
activeConns, err := nm.GetPropertyActiveConnections()
if err != nil {
return fmt.Errorf("failed to get active connections: %w", err)
}
var lastErr error
var disconnected bool
for _, activeConn := range activeConns {
connType, err := activeConn.GetPropertyType()
if err != nil {
continue
}
if connType != "vpn" && connType != "wireguard" {
continue
}
if err := nm.DeactivateConnection(activeConn); err != nil {
lastErr = err
log.Warnf("Failed to deactivate VPN connection: %v", err)
} else {
disconnected = true
}
}
if disconnected {
b.ListActiveVPN()
if b.onStateChange != nil {
b.onStateChange()
}
}
return lastErr
}
func (b *NetworkManagerBackend) ClearVPNCredentials(uuidOrName string) error {
s := b.settings
if s == nil {
var err error
s, err = gonetworkmanager.NewSettings()
if err != nil {
return fmt.Errorf("failed to get settings: %w", err)
}
b.settings = s
}
settingsMgr := s.(gonetworkmanager.Settings)
connections, err := settingsMgr.ListConnections()
if err != nil {
return fmt.Errorf("failed to get connections: %w", err)
}
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
connID, _ := connMeta["id"].(string)
connUUID, _ := connMeta["uuid"].(string)
if connUUID == uuidOrName || connID == uuidOrName {
if connType == "vpn" {
if vpnSettings, ok := settings["vpn"]; ok {
delete(vpnSettings, "secrets")
if dataMap, ok := vpnSettings["data"].(map[string]string); ok {
dataMap["password-flags"] = "1"
vpnSettings["data"] = dataMap
}
vpnSettings["password-flags"] = uint32(1)
}
settings["vpn-secrets"] = make(map[string]interface{})
}
if err := conn.Update(settings); err != nil {
return fmt.Errorf("failed to update connection: %w", err)
}
if err := conn.ClearSecrets(); err != nil {
log.Warnf("ClearSecrets call failed (may not be critical): %v", err)
}
log.Infof("Cleared credentials for VPN: %s", connID)
return nil
}
}
return fmt.Errorf("VPN connection not found: %s", uuidOrName)
}
func (b *NetworkManagerBackend) updateVPNConnectionState() {
b.stateMutex.RLock()
isConnectingVPN := b.state.IsConnectingVPN
connectingVPNUUID := b.state.ConnectingVPNUUID
b.stateMutex.RUnlock()
if !isConnectingVPN || connectingVPNUUID == "" {
return
}
nm := b.nmConn.(gonetworkmanager.NetworkManager)
activeConns, err := nm.GetPropertyActiveConnections()
if err != nil {
return
}
foundConnection := false
for _, activeConn := range activeConns {
connType, err := activeConn.GetPropertyType()
if err != nil {
continue
}
if connType != "vpn" && connType != "wireguard" {
continue
}
uuid, err := activeConn.GetPropertyUUID()
if err != nil {
continue
}
state, _ := activeConn.GetPropertyState()
stateReason, _ := activeConn.GetPropertyStateFlags()
if uuid == connectingVPNUUID {
foundConnection = true
switch state {
case 2:
log.Infof("[updateVPNConnectionState] VPN connection successful: %s", uuid)
b.stateMutex.Lock()
b.state.IsConnectingVPN = false
b.state.ConnectingVPNUUID = ""
b.state.LastError = ""
b.stateMutex.Unlock()
return
case 4:
log.Warnf("[updateVPNConnectionState] VPN connection failed/deactivated: %s (state=%d, flags=%d)", uuid, state, stateReason)
b.stateMutex.Lock()
b.state.IsConnectingVPN = false
b.state.ConnectingVPNUUID = ""
b.state.LastError = "VPN connection failed"
b.stateMutex.Unlock()
return
}
}
}
if !foundConnection {
log.Warnf("[updateVPNConnectionState] VPN connection no longer exists: %s", connectingVPNUUID)
b.stateMutex.Lock()
b.state.IsConnectingVPN = false
b.state.ConnectingVPNUUID = ""
b.state.LastError = "VPN connection failed"
b.stateMutex.Unlock()
}
}