mirror of
https://github.com/taigrr/golang-wpasupplicant
synced 2025-01-18 04:43:18 -08:00
Merge branch 'matiasdoyle-master'
I just noticed that Matias forked this library on Github and ran with it. He's added a bunch of additional functionality, which I'm happily merging back into my branch.
This commit is contained in:
commit
d8b63b5cd9
258
unixgram.go
258
unixgram.go
@ -58,6 +58,7 @@ type unixgramConn struct {
|
|||||||
c *net.UnixConn
|
c *net.UnixConn
|
||||||
fd uintptr
|
fd uintptr
|
||||||
solicited, unsolicited chan message
|
solicited, unsolicited chan message
|
||||||
|
wpaEvents chan WPAEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
// socketPath is where to find the the AF_UNIX sockets for each interface. It
|
// socketPath is where to find the the AF_UNIX sockets for each interface. It
|
||||||
@ -72,7 +73,7 @@ func Unixgram(ifName string) (Conn, error) {
|
|||||||
|
|
||||||
local, err := ioutil.TempFile("/tmp", "wpa_supplicant")
|
local, err := ioutil.TempFile("/tmp", "wpa_supplicant")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
os.Remove(local.Name())
|
os.Remove(local.Name())
|
||||||
|
|
||||||
@ -91,12 +92,15 @@ func Unixgram(ifName string) (Conn, error) {
|
|||||||
|
|
||||||
uc.solicited = make(chan message)
|
uc.solicited = make(chan message)
|
||||||
uc.unsolicited = make(chan message)
|
uc.unsolicited = make(chan message)
|
||||||
|
uc.wpaEvents = make(chan WPAEvent)
|
||||||
|
|
||||||
go uc.readLoop()
|
go uc.readLoop()
|
||||||
|
go uc.readUnsolicited()
|
||||||
// TODO: issue an ACCEPT command so as to receive unsolicited
|
// Issue an ATTACH command to start receiving unsolicited events.
|
||||||
// messages. (We don't do this yet, since we don't yet have any way
|
err = uc.runCommand("ATTACH")
|
||||||
// to consume them.)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return uc, nil
|
return uc, nil
|
||||||
}
|
}
|
||||||
@ -160,6 +164,47 @@ func (uc *unixgramConn) readLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readUnsolicited handles messages sent to the unsolicited channel and parse them
|
||||||
|
// into a WPAEvent. At the moment we only handle `CTRL-EVENT-*` events and only events
|
||||||
|
// where the 'payload' is formatted with key=val.
|
||||||
|
func (uc *unixgramConn) readUnsolicited() {
|
||||||
|
for {
|
||||||
|
mgs := <-uc.unsolicited
|
||||||
|
data := bytes.NewBuffer(mgs.data).String()
|
||||||
|
|
||||||
|
parts := strings.Split(data, " ")
|
||||||
|
if len(parts) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Index(parts[0], "CTRL-") != 0 {
|
||||||
|
uc.wpaEvents <- WPAEvent{
|
||||||
|
Event: "MESSAGE",
|
||||||
|
Line: data,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
event := WPAEvent{
|
||||||
|
Event: strings.TrimPrefix(parts[0], "CTRL-EVENT-"),
|
||||||
|
Arguments: make(map[string]string),
|
||||||
|
Line: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, args := range parts[1:] {
|
||||||
|
if strings.Contains(args, "=") {
|
||||||
|
keyval := strings.Split(args, "=")
|
||||||
|
if len(keyval) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event.Arguments[keyval[0]] = keyval[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uc.wpaEvents <- event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// cmd executes a command and waits for a reply.
|
// cmd executes a command and waits for a reply.
|
||||||
func (uc *unixgramConn) cmd(cmd string) ([]byte, error) {
|
func (uc *unixgramConn) cmd(cmd string) ([]byte, error) {
|
||||||
// TODO: block if any other commands are running
|
// TODO: block if any other commands are running
|
||||||
@ -199,6 +244,18 @@ func (err *ParseError) Error() string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) EventQueue() chan WPAEvent {
|
||||||
|
return uc.wpaEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) Close() error {
|
||||||
|
if err := uc.runCommand("DETACH"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uc.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (uc *unixgramConn) Ping() error {
|
func (uc *unixgramConn) Ping() error {
|
||||||
resp, err := uc.cmd("PING")
|
resp, err := uc.cmd("PING")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -211,6 +268,73 @@ func (uc *unixgramConn) Ping() error {
|
|||||||
return &ParseError{Line: string(resp)}
|
return &ParseError{Line: string(resp)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) AddNetwork() (int, error) {
|
||||||
|
resp, err := uc.cmd("ADD_NETWORK")
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytes.NewBuffer(resp)
|
||||||
|
return strconv.Atoi(strings.Trim(b.String(), "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) EnableNetwork(networkID int) error {
|
||||||
|
return uc.runCommand(fmt.Sprintf("ENABLE_NETWORK %d", networkID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) EnableAllNetworks() error {
|
||||||
|
return uc.runCommand("ENABLE_NETWORK all")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) SelectNetwork(networkID int) error {
|
||||||
|
return uc.runCommand(fmt.Sprintf("SELECT_NETWORK %d", networkID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) DisableNetwork(networkID int) error {
|
||||||
|
return uc.runCommand(fmt.Sprintf("DISABLE_NETWORK %d", networkID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) RemoveNetwork(networkID int) error {
|
||||||
|
return uc.runCommand(fmt.Sprintf("REMOVE_NETWORK %d", networkID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) RemoveAllNetworks() error {
|
||||||
|
return uc.runCommand("REMOVE_NETWORK all")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) SetNetwork(networkID int, variable string, value string) error {
|
||||||
|
var cmd string
|
||||||
|
|
||||||
|
// Since key_mgmt expects the value to not be wrapped in "" we do a little check here.
|
||||||
|
if variable == "key_mgmt" {
|
||||||
|
cmd = fmt.Sprintf("SET_NETWORK %d %s %s", networkID, variable, value)
|
||||||
|
} else {
|
||||||
|
cmd = fmt.Sprintf("SET_NETWORK %d %s \"%s\"", networkID, variable, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uc.runCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) SaveConfig() error {
|
||||||
|
return uc.runCommand("SAVE_CONFIG")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) Reconfigure() error {
|
||||||
|
return uc.runCommand("RECONFIGURE")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) Reassociate() error {
|
||||||
|
return uc.runCommand("REASSOCIATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) Reconnect() error {
|
||||||
|
return uc.runCommand("RECONNECT")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) Scan() error {
|
||||||
|
return uc.runCommand("SCAN")
|
||||||
|
}
|
||||||
|
|
||||||
func (uc *unixgramConn) ScanResults() ([]ScanResult, []error) {
|
func (uc *unixgramConn) ScanResults() ([]ScanResult, []error) {
|
||||||
resp, err := uc.cmd("SCAN_RESULTS")
|
resp, err := uc.cmd("SCAN_RESULTS")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -220,6 +344,130 @@ func (uc *unixgramConn) ScanResults() ([]ScanResult, []error) {
|
|||||||
return parseScanResults(bytes.NewBuffer(resp))
|
return parseScanResults(bytes.NewBuffer(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) Status() (StatusResult, error) {
|
||||||
|
resp, err := uc.cmd("STATUS")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseStatusResults(bytes.NewBuffer(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *unixgramConn) ListNetworks() ([]ConfiguredNetwork, error) {
|
||||||
|
resp, err := uc.cmd("LIST_NETWORKS")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseListNetworksResult(bytes.NewBuffer(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCommand is a wrapper around the uc.cmd command which makes sure the
|
||||||
|
// command returned a successful (OK) response.
|
||||||
|
func (uc *unixgramConn) runCommand(cmd string) error {
|
||||||
|
resp, err := uc.cmd(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Compare(resp, []byte("OK\n")) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ParseError{Line: string(resp)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseListNetworksResult(resp io.Reader) (res []ConfiguredNetwork, err error) {
|
||||||
|
s := bufio.NewScanner(resp)
|
||||||
|
if !s.Scan() {
|
||||||
|
return nil, &ParseError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
networkIDCol, ssidCol, bssidCol, flagsCol, maxCol := -1, -1, -1, -1, -1
|
||||||
|
for n, col := range strings.Split(s.Text(), " / ") {
|
||||||
|
switch col {
|
||||||
|
case "network id":
|
||||||
|
networkIDCol = n
|
||||||
|
case "ssid":
|
||||||
|
ssidCol = n
|
||||||
|
case "bssid":
|
||||||
|
bssidCol = n
|
||||||
|
case "flags":
|
||||||
|
flagsCol = n
|
||||||
|
}
|
||||||
|
|
||||||
|
maxCol = n
|
||||||
|
}
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
ln := s.Text()
|
||||||
|
fields := strings.Split(ln, "\t")
|
||||||
|
if len(fields) < maxCol {
|
||||||
|
return nil, &ParseError{Line: ln}
|
||||||
|
}
|
||||||
|
|
||||||
|
var networkID string
|
||||||
|
if networkIDCol != -1 {
|
||||||
|
networkID = fields[networkIDCol]
|
||||||
|
}
|
||||||
|
|
||||||
|
var ssid string
|
||||||
|
if ssidCol != -1 {
|
||||||
|
ssid = fields[ssidCol]
|
||||||
|
}
|
||||||
|
|
||||||
|
var bssid string
|
||||||
|
if bssidCol != -1 {
|
||||||
|
bssid = fields[bssidCol]
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags []string
|
||||||
|
if flagsCol != -1 {
|
||||||
|
if len(fields[flagsCol]) >= 2 && fields[flagsCol][0] == '[' && fields[flagsCol][len(fields[flagsCol])-1] == ']' {
|
||||||
|
flags = strings.Split(fields[flagsCol][1:len(fields[flagsCol])-1], "][")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, &configuredNetwork{
|
||||||
|
networkID: networkID,
|
||||||
|
ssid: ssid,
|
||||||
|
bssid: bssid,
|
||||||
|
flags: flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStatusResults(resp io.Reader) (StatusResult, error) {
|
||||||
|
s := bufio.NewScanner(resp)
|
||||||
|
|
||||||
|
res := &statusResult{}
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
ln := s.Text()
|
||||||
|
fields := strings.Split(ln, "=")
|
||||||
|
if len(fields) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fields[0] {
|
||||||
|
case "wpa_state":
|
||||||
|
res.wpaState = fields[1]
|
||||||
|
case "key_mgmt":
|
||||||
|
res.keyMgmt = fields[1]
|
||||||
|
case "ip_address":
|
||||||
|
res.ipAddr = fields[1]
|
||||||
|
case "ssid":
|
||||||
|
res.ssid = fields[1]
|
||||||
|
case "address":
|
||||||
|
res.address = fields[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseScanResults parses the SCAN_RESULTS output from wpa_supplicant. This
|
// parseScanResults parses the SCAN_RESULTS output from wpa_supplicant. This
|
||||||
// is split out from ScanResults() to make testing easier.
|
// is split out from ScanResults() to make testing easier.
|
||||||
func parseScanResults(resp io.Reader) (res []ScanResult, errs []error) {
|
func parseScanResults(resp io.Reader) (res []ScanResult, errs []error) {
|
||||||
|
@ -131,3 +131,37 @@ func TestParseScanResults(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseStatusResults(t *testing.T) {
|
||||||
|
testData := "bssid=02:00:01:02:03:04\n" +
|
||||||
|
"ssid=test network\n" +
|
||||||
|
"pairwise_cipher=CCMP\n" +
|
||||||
|
"group_cipher=CCMP\n" +
|
||||||
|
"key_mgmt=WPA-PSK\n" +
|
||||||
|
"wpa_state=COMPLETED\n" +
|
||||||
|
"ip_address=192.168.1.21\n" +
|
||||||
|
"Supplicant PAE state=AUTHENTICATED\n" +
|
||||||
|
"suppPortStatus=Authorized\n" +
|
||||||
|
"EAP state=SUCCESS"
|
||||||
|
|
||||||
|
res, err := parseStatusResults(bytes.NewBufferString(testData))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing status result %t", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.WPAState() != "COMPLETED" {
|
||||||
|
t.Errorf("WPAState was not COMPLETED. Was %s", res.WPAState())
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.IPAddr() != "192.168.1.21" {
|
||||||
|
t.Errorf("IPAddr was not 192.168.1.21. Was %s", res.IPAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.KeyMgmt() != "WPA-PSK" {
|
||||||
|
t.Errorf("KeyMgmt was not WPA-PSK. Was %s", res.KeyMgmt())
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Address() != "" {
|
||||||
|
t.Errorf("Address should be empty. Was %s", res.Address())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
105
wpasupplicant.go
105
wpasupplicant.go
@ -120,15 +120,120 @@ func (r *scanResult) Frequency() int { return r.frequency }
|
|||||||
func (r *scanResult) RSSI() int { return r.rssi }
|
func (r *scanResult) RSSI() int { return r.rssi }
|
||||||
func (r *scanResult) Flags() []string { return r.flags }
|
func (r *scanResult) Flags() []string { return r.flags }
|
||||||
|
|
||||||
|
// ConfiguredNetwork is a configured network (from LIST_NETWORKS)
|
||||||
|
type ConfiguredNetwork interface {
|
||||||
|
NetworkID() string
|
||||||
|
SSID() string
|
||||||
|
BSSID() string
|
||||||
|
Flags() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type configuredNetwork struct {
|
||||||
|
networkID string
|
||||||
|
ssid string
|
||||||
|
bssid string // Since bssid can be any
|
||||||
|
flags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *configuredNetwork) NetworkID() string { return r.networkID }
|
||||||
|
func (r *configuredNetwork) BSSID() string { return r.bssid }
|
||||||
|
func (r *configuredNetwork) SSID() string { return r.ssid }
|
||||||
|
func (r *configuredNetwork) Flags() []string { return r.flags }
|
||||||
|
|
||||||
|
type StatusResult interface {
|
||||||
|
WPAState() string
|
||||||
|
KeyMgmt() string
|
||||||
|
IPAddr() string
|
||||||
|
SSID() string
|
||||||
|
Address() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusResult struct {
|
||||||
|
wpaState string
|
||||||
|
keyMgmt string
|
||||||
|
ipAddr string
|
||||||
|
ssid string
|
||||||
|
address string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *statusResult) WPAState() string { return s.wpaState }
|
||||||
|
func (s *statusResult) KeyMgmt() string { return s.keyMgmt }
|
||||||
|
func (s *statusResult) IPAddr() string { return s.ipAddr }
|
||||||
|
func (s *statusResult) SSID() string { return s.ssid }
|
||||||
|
func (s *statusResult) Address() string { return s.address }
|
||||||
|
|
||||||
|
type WPAEvent struct {
|
||||||
|
Event string
|
||||||
|
Arguments map[string]string
|
||||||
|
Line string
|
||||||
|
}
|
||||||
|
|
||||||
// Conn is a connection to wpa_supplicant over one of its communication
|
// Conn is a connection to wpa_supplicant over one of its communication
|
||||||
// channels.
|
// channels.
|
||||||
type Conn interface {
|
type Conn interface {
|
||||||
|
// Close closes the unixgram connection
|
||||||
|
Close() error
|
||||||
|
|
||||||
// Ping tests the connection. It returns nil if wpa_supplicant is
|
// Ping tests the connection. It returns nil if wpa_supplicant is
|
||||||
// responding.
|
// responding.
|
||||||
Ping() error
|
Ping() error
|
||||||
|
|
||||||
|
// AddNetwork creates an empty network configuration. Returns the network
|
||||||
|
// ID.
|
||||||
|
AddNetwork() (int, error)
|
||||||
|
|
||||||
|
// SetNetwork configures a network property. Returns error if the property
|
||||||
|
// configuration failed.
|
||||||
|
SetNetwork(int, string, string) error
|
||||||
|
|
||||||
|
// EnableNetwork enables a network. Returns error if the command fails.
|
||||||
|
EnableNetwork(int) error
|
||||||
|
|
||||||
|
// EnableAllNetworks enables all configured networks. Returns error if the command fails.
|
||||||
|
EnableAllNetworks() error
|
||||||
|
|
||||||
|
// SelectNetwork selects a network (and disables the others).
|
||||||
|
SelectNetwork(int) error
|
||||||
|
|
||||||
|
// DisableNetwork disables a network.
|
||||||
|
DisableNetwork(int) error
|
||||||
|
|
||||||
|
// RemoveNetwork removes a network from the configuration.
|
||||||
|
RemoveNetwork(int) error
|
||||||
|
|
||||||
|
// RemoveAllNetworks removes all networks (basically running `REMOVE_NETWORK all`).
|
||||||
|
// Returns error if command fails.
|
||||||
|
RemoveAllNetworks() error
|
||||||
|
|
||||||
|
// SaveConfig stores the current network configuration to disk.
|
||||||
|
SaveConfig() error
|
||||||
|
|
||||||
|
// Reconfigure sends a RECONFIGURE command to the wpa_supplicant. Returns error when
|
||||||
|
// command fails.
|
||||||
|
Reconfigure() error
|
||||||
|
|
||||||
|
// Reassociate sends a REASSOCIATE command to the wpa_supplicant. Returns error when
|
||||||
|
// command fails.
|
||||||
|
Reassociate() error
|
||||||
|
|
||||||
|
// Reconnect sends a RECONNECT command to the wpa_supplicant. Returns error when
|
||||||
|
// command fails.
|
||||||
|
Reconnect() error
|
||||||
|
|
||||||
|
// ListNetworks returns the currently configured networks.
|
||||||
|
ListNetworks() ([]ConfiguredNetwork, error)
|
||||||
|
|
||||||
|
// Status returns current wpa_supplicant status
|
||||||
|
Status() (StatusResult, error)
|
||||||
|
|
||||||
|
// Scan triggers a new scan. Returns error if the wpa_supplicant does not
|
||||||
|
// return OK.
|
||||||
|
Scan() error
|
||||||
|
|
||||||
// ScanResult returns the latest scanning results. It returns a slice
|
// ScanResult returns the latest scanning results. It returns a slice
|
||||||
// of scanned BSSs, and/or a slice of errors representing problems
|
// of scanned BSSs, and/or a slice of errors representing problems
|
||||||
// communicating with wpa_supplicant or parsing its output.
|
// communicating with wpa_supplicant or parsing its output.
|
||||||
ScanResults() ([]ScanResult, []error)
|
ScanResults() ([]ScanResult, []error)
|
||||||
|
|
||||||
|
EventQueue() chan WPAEvent
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user