Implementation with iwd
This commit is contained in:
parent
2b3a5b89f8
commit
17d665e21a
10 changed files with 755 additions and 171 deletions
170
internal/wifi/iwd/agent.go
Normal file
170
internal/wifi/iwd/agent.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package iwd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/godbus/dbus/v5/introspect"
|
||||
)
|
||||
|
||||
const agentIntrospectXML = `
|
||||
<node>
|
||||
<interface name="net.connman.iwd.Agent">
|
||||
<method name="Release">
|
||||
</method>
|
||||
<method name="RequestPassphrase">
|
||||
<arg name="network" type="o" direction="in"/>
|
||||
<arg name="passphrase" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="RequestPrivateKeyPassphrase">
|
||||
<arg name="network" type="o" direction="in"/>
|
||||
<arg name="passphrase" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="RequestUserNameAndPassword">
|
||||
<arg name="network" type="o" direction="in"/>
|
||||
<arg name="username" type="s" direction="out"/>
|
||||
<arg name="password" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="RequestUserPassword">
|
||||
<arg name="network" type="o" direction="in"/>
|
||||
<arg name="user" type="s" direction="in"/>
|
||||
<arg name="password" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="Cancel">
|
||||
<arg name="reason" type="s" direction="in"/>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="xml" type="s" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>`
|
||||
|
||||
// Agent implements the net.connman.iwd.Agent interface for credential callbacks
|
||||
type Agent struct {
|
||||
conn *dbus.Conn
|
||||
path dbus.ObjectPath
|
||||
passphraseStore map[string]string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewAgent creates a new Agent instance
|
||||
func NewAgent(conn *dbus.Conn, path dbus.ObjectPath) *Agent {
|
||||
return &Agent{
|
||||
conn: conn,
|
||||
path: path,
|
||||
passphraseStore: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// SetPassphrase stores a passphrase for a given network SSID
|
||||
func (a *Agent) SetPassphrase(ssid, passphrase string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.passphraseStore[ssid] = passphrase
|
||||
}
|
||||
|
||||
// ClearPassphrase removes the stored passphrase for a network
|
||||
func (a *Agent) ClearPassphrase(ssid string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
delete(a.passphraseStore, ssid)
|
||||
}
|
||||
|
||||
// Export registers the agent object on D-Bus
|
||||
func (a *Agent) Export() error {
|
||||
err := a.conn.Export(a, a.path, AgentInterface)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to export agent: %v", err)
|
||||
}
|
||||
|
||||
err = a.conn.Export(introspect.Introspectable(agentIntrospectXML), a.path, "org.freedesktop.DBus.Introspectable")
|
||||
if err != nil {
|
||||
a.conn.Export(nil, a.path, AgentInterface)
|
||||
return fmt.Errorf("failed to export introspection: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unexport unregisters the agent from D-Bus
|
||||
func (a *Agent) Unexport() {
|
||||
a.conn.Export(nil, a.path, AgentInterface)
|
||||
a.conn.Export(nil, a.path, "org.freedesktop.DBus.Introspectable")
|
||||
}
|
||||
|
||||
// getNetworkSSID queries the network object to get its SSID (Name property)
|
||||
func (a *Agent) getNetworkSSID(networkPath dbus.ObjectPath) (string, error) {
|
||||
obj := a.conn.Object(Service, networkPath)
|
||||
variant, err := obj.GetProperty(NetworkInterface + ".Name")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get network name: %v", err)
|
||||
}
|
||||
|
||||
name, ok := variant.Value().(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("network name is not a string")
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// RequestPassphrase is called by iwd when connecting to PSK networks
|
||||
func (a *Agent) RequestPassphrase(network dbus.ObjectPath) (string, *dbus.Error) {
|
||||
fmt.Printf("[Agent] RequestPassphrase called for network: %s\n", network)
|
||||
|
||||
ssid, err := a.getNetworkSSID(network)
|
||||
if err != nil {
|
||||
fmt.Printf("[Agent] Failed to get SSID: %v\n", err)
|
||||
return "", dbus.MakeFailedError(fmt.Errorf("failed to get network SSID: %v", err))
|
||||
}
|
||||
|
||||
fmt.Printf("[Agent] Network SSID: %s\n", ssid)
|
||||
|
||||
a.mu.RLock()
|
||||
passphrase, ok := a.passphraseStore[ssid]
|
||||
a.mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
fmt.Printf("[Agent] No passphrase stored for SSID: %s\n", ssid)
|
||||
return "", dbus.MakeFailedError(fmt.Errorf("no passphrase stored for network '%s'", ssid))
|
||||
}
|
||||
|
||||
fmt.Printf("[Agent] Returning passphrase for SSID: %s\n", ssid)
|
||||
return passphrase, nil
|
||||
}
|
||||
|
||||
// RequestPrivateKeyPassphrase is called for encrypted private keys
|
||||
func (a *Agent) RequestPrivateKeyPassphrase(network dbus.ObjectPath) (string, *dbus.Error) {
|
||||
// Not implemented for now
|
||||
return "", dbus.MakeFailedError(fmt.Errorf("RequestPrivateKeyPassphrase not implemented"))
|
||||
}
|
||||
|
||||
// RequestUserNameAndPassword is called for enterprise networks
|
||||
func (a *Agent) RequestUserNameAndPassword(network dbus.ObjectPath) (string, string, *dbus.Error) {
|
||||
// Not implemented for now
|
||||
return "", "", dbus.MakeFailedError(fmt.Errorf("RequestUserNameAndPassword not implemented"))
|
||||
}
|
||||
|
||||
// RequestUserPassword is called for enterprise networks with known username
|
||||
func (a *Agent) RequestUserPassword(network dbus.ObjectPath, user string) (string, *dbus.Error) {
|
||||
// Not implemented for now
|
||||
return "", dbus.MakeFailedError(fmt.Errorf("RequestUserPassword not implemented"))
|
||||
}
|
||||
|
||||
// Cancel is called when a request is canceled
|
||||
func (a *Agent) Cancel(reason string) *dbus.Error {
|
||||
// Nothing to do, just acknowledge
|
||||
return nil
|
||||
}
|
||||
|
||||
// Release is called when the agent is unregistered
|
||||
func (a *Agent) Release() *dbus.Error {
|
||||
// Cleanup if needed
|
||||
a.mu.Lock()
|
||||
a.passphraseStore = make(map[string]string)
|
||||
a.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
39
internal/wifi/iwd/agentmanager.go
Normal file
39
internal/wifi/iwd/agentmanager.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package iwd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// AgentManager handles agent registration with iwd
|
||||
type AgentManager struct {
|
||||
conn *dbus.Conn
|
||||
obj dbus.BusObject
|
||||
}
|
||||
|
||||
// NewAgentManager creates a new AgentManager instance
|
||||
func NewAgentManager(conn *dbus.Conn) *AgentManager {
|
||||
return &AgentManager{
|
||||
conn: conn,
|
||||
obj: conn.Object(Service, "/net/connman/iwd"),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterAgent registers an agent with iwd
|
||||
func (am *AgentManager) RegisterAgent(agentPath dbus.ObjectPath) error {
|
||||
err := am.obj.Call(AgentManagerInterface+".RegisterAgent", 0, agentPath).Err
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register agent: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnregisterAgent unregisters an agent from iwd
|
||||
func (am *AgentManager) UnregisterAgent(agentPath dbus.ObjectPath) error {
|
||||
err := am.obj.Call(AgentManagerInterface+".UnregisterAgent", 0, agentPath).Err
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unregister agent: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
71
internal/wifi/iwd/manager.go
Normal file
71
internal/wifi/iwd/manager.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package iwd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// Manager handles iwd object discovery via ObjectManager
|
||||
type Manager struct {
|
||||
conn *dbus.Conn
|
||||
obj dbus.BusObject
|
||||
}
|
||||
|
||||
// NewManager creates a new Manager instance
|
||||
func NewManager(conn *dbus.Conn) *Manager {
|
||||
return &Manager{
|
||||
conn: conn,
|
||||
obj: conn.Object(Service, dbus.ObjectPath(ManagerPath)),
|
||||
}
|
||||
}
|
||||
|
||||
// GetManagedObjects returns all iwd managed objects
|
||||
func (m *Manager) GetManagedObjects() (map[dbus.ObjectPath]map[string]map[string]dbus.Variant, error) {
|
||||
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||
err := m.obj.Call("org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&objects)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get managed objects: %v", err)
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// FindStation finds the Station object for the given interface name
|
||||
func (m *Manager) FindStation(interfaceName string) (*Station, error) {
|
||||
objects, err := m.GetManagedObjects()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First, find the device with matching interface name
|
||||
var devicePath dbus.ObjectPath
|
||||
for path, interfaces := range objects {
|
||||
if deviceProps, ok := interfaces[DeviceInterface]; ok {
|
||||
if nameVariant, ok := deviceProps["Name"]; ok {
|
||||
if name, ok := nameVariant.Value().(string); ok && name == interfaceName {
|
||||
devicePath = path
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if devicePath == "" {
|
||||
return nil, fmt.Errorf("device with interface '%s' not found", interfaceName)
|
||||
}
|
||||
|
||||
// Now find the station object under this device
|
||||
// Station path is typically the same as device path or a child of it
|
||||
for path, interfaces := range objects {
|
||||
if _, ok := interfaces[StationInterface]; ok {
|
||||
// Check if this station belongs to our device
|
||||
// Station path should be the device path or start with it
|
||||
if path == devicePath || strings.HasPrefix(string(path), string(devicePath)+"/") {
|
||||
return NewStation(m.conn, path), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("station for device '%s' not found", interfaceName)
|
||||
}
|
||||
64
internal/wifi/iwd/network.go
Normal file
64
internal/wifi/iwd/network.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package iwd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// Network represents an iwd Network interface
|
||||
type Network struct {
|
||||
path dbus.ObjectPath
|
||||
conn *dbus.Conn
|
||||
obj dbus.BusObject
|
||||
}
|
||||
|
||||
// NewNetwork creates a new Network instance
|
||||
func NewNetwork(conn *dbus.Conn, path dbus.ObjectPath) *Network {
|
||||
return &Network{
|
||||
path: path,
|
||||
conn: conn,
|
||||
obj: conn.Object(Service, path),
|
||||
}
|
||||
}
|
||||
|
||||
// GetProperties retrieves all network properties
|
||||
func (n *Network) GetProperties() (*NetworkProperties, error) {
|
||||
var props map[string]dbus.Variant
|
||||
err := n.obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, NetworkInterface).Store(&props)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get network properties: %v", err)
|
||||
}
|
||||
|
||||
netProps := &NetworkProperties{}
|
||||
|
||||
if nameVariant, ok := props["Name"]; ok {
|
||||
if name, ok := nameVariant.Value().(string); ok {
|
||||
netProps.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
if typeVariant, ok := props["Type"]; ok {
|
||||
if netType, ok := typeVariant.Value().(string); ok {
|
||||
netProps.Type = netType
|
||||
}
|
||||
}
|
||||
|
||||
if connectedVariant, ok := props["Connected"]; ok {
|
||||
if connected, ok := connectedVariant.Value().(bool); ok {
|
||||
netProps.Connected = connected
|
||||
}
|
||||
}
|
||||
|
||||
return netProps, nil
|
||||
}
|
||||
|
||||
// Connect initiates a connection to this network
|
||||
// Credentials are provided via the registered agent's RequestPassphrase callback
|
||||
func (n *Network) Connect() error {
|
||||
err := n.obj.Call(NetworkInterface+".Connect", 0).Err
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
137
internal/wifi/iwd/station.go
Normal file
137
internal/wifi/iwd/station.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package iwd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// Station represents an iwd Station interface
|
||||
type Station struct {
|
||||
path dbus.ObjectPath
|
||||
conn *dbus.Conn
|
||||
obj dbus.BusObject
|
||||
}
|
||||
|
||||
// NewStation creates a new Station instance
|
||||
func NewStation(conn *dbus.Conn, path dbus.ObjectPath) *Station {
|
||||
return &Station{
|
||||
path: path,
|
||||
conn: conn,
|
||||
obj: conn.Object(Service, path),
|
||||
}
|
||||
}
|
||||
|
||||
// Scan triggers a network scan
|
||||
func (s *Station) Scan() error {
|
||||
err := s.obj.Call(StationInterface+".Scan", 0).Err
|
||||
if err != nil {
|
||||
return fmt.Errorf("scan failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsScanning checks if a scan is currently in progress
|
||||
func (s *Station) IsScanning() (bool, error) {
|
||||
prop, err := s.obj.GetProperty(StationInterface + ".Scanning")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get Scanning property: %v", err)
|
||||
}
|
||||
|
||||
scanning, ok := prop.Value().(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("Scanning property is not a boolean")
|
||||
}
|
||||
|
||||
return scanning, nil
|
||||
}
|
||||
|
||||
// GetOrderedNetworks returns networks sorted by signal strength
|
||||
func (s *Station) GetOrderedNetworks() ([]NetworkInfo, error) {
|
||||
var result []struct {
|
||||
Path dbus.ObjectPath
|
||||
Signal int16
|
||||
}
|
||||
|
||||
err := s.obj.Call(StationInterface+".GetOrderedNetworks", 0).Store(&result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get ordered networks: %v", err)
|
||||
}
|
||||
|
||||
networks := make([]NetworkInfo, len(result))
|
||||
for i, r := range result {
|
||||
networks[i] = NetworkInfo{
|
||||
Path: r.Path,
|
||||
Signal: r.Signal,
|
||||
}
|
||||
}
|
||||
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
// GetState returns the current connection state
|
||||
func (s *Station) GetState() (StationState, error) {
|
||||
prop, err := s.obj.GetProperty(StationInterface + ".State")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get State property: %v", err)
|
||||
}
|
||||
|
||||
state, ok := prop.Value().(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("State property is not a string")
|
||||
}
|
||||
|
||||
return StationState(state), nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects from the current network
|
||||
func (s *Station) Disconnect() error {
|
||||
err := s.obj.Call(StationInterface+".Disconnect", 0).Err
|
||||
if err != nil {
|
||||
return fmt.Errorf("disconnect failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNetwork finds and returns a Network object by SSID
|
||||
func (s *Station) GetNetwork(ssid string) (*Network, error) {
|
||||
networks, err := s.GetOrderedNetworks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find the network with matching SSID
|
||||
for _, netInfo := range networks {
|
||||
network := NewNetwork(s.conn, netInfo.Path)
|
||||
props, err := network.GetProperties()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if props.Name == ssid {
|
||||
return network, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("network '%s' not found", ssid)
|
||||
}
|
||||
|
||||
// GetConnectedNetwork returns the currently connected network
|
||||
func (s *Station) GetConnectedNetwork() (*Network, error) {
|
||||
prop, err := s.obj.GetProperty(StationInterface + ".ConnectedNetwork")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get ConnectedNetwork property: %v", err)
|
||||
}
|
||||
|
||||
path, ok := prop.Value().(dbus.ObjectPath)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("ConnectedNetwork property is not an ObjectPath")
|
||||
}
|
||||
|
||||
// Check if path is empty (not connected)
|
||||
if path == "/" || path == "" {
|
||||
return nil, fmt.Errorf("not connected to any network")
|
||||
}
|
||||
|
||||
return NewNetwork(s.conn, path), nil
|
||||
}
|
||||
38
internal/wifi/iwd/types.go
Normal file
38
internal/wifi/iwd/types.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package iwd
|
||||
|
||||
import "github.com/godbus/dbus/v5"
|
||||
|
||||
const (
|
||||
// D-Bus service and interfaces
|
||||
Service = "net.connman.iwd"
|
||||
ManagerPath = "/"
|
||||
DeviceInterface = "net.connman.iwd.Device"
|
||||
StationInterface = "net.connman.iwd.Station"
|
||||
NetworkInterface = "net.connman.iwd.Network"
|
||||
AgentInterface = "net.connman.iwd.Agent"
|
||||
AgentManagerInterface = "net.connman.iwd.AgentManager"
|
||||
)
|
||||
|
||||
// NetworkInfo represents a network with its signal strength
|
||||
type NetworkInfo struct {
|
||||
Path dbus.ObjectPath
|
||||
Signal int16 // 100 * dBm (0 to -10000)
|
||||
}
|
||||
|
||||
// NetworkProperties holds network properties
|
||||
type NetworkProperties struct {
|
||||
Name string // SSID
|
||||
Type string // "open", "wep", "psk", "8021x"
|
||||
Connected bool
|
||||
}
|
||||
|
||||
// StationState represents the connection state
|
||||
type StationState string
|
||||
|
||||
const (
|
||||
StateConnected StationState = "connected"
|
||||
StateDisconnected StationState = "disconnected"
|
||||
StateConnecting StationState = "connecting"
|
||||
StateDisconnecting StationState = "disconnecting"
|
||||
StateRoaming StationState = "roaming"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue