golang-wpasupplicant/unixgram.go
Dave Pifke 8a251ea4ef
Initial interface to wpa_supplicant scan results
This code connects to wpa_supplicant's control interface using a UNIX
datagram socket.  It can ping the daemon and fetch wifi network scan
results.

Documentation, tests, and additional functionality is forthcoming.
2017-05-23 19:02:04 -07:00

188 lines
3.4 KiB
Go

package wpasupplicant
import (
"bufio"
"bytes"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"path"
"strconv"
"strings"
"syscall"
)
type message struct {
priority int
data []byte
err error
}
type unixgramConn struct {
c *net.UnixConn
fd uintptr
solicited, unsolicited chan message
}
func Unixgram(ifName string) (Conn, error) {
var err error
uc := &unixgramConn{}
local, err := ioutil.TempFile("/tmp", "wpa_supplicant")
if err != nil {
panic(err)
}
os.Remove(local.Name())
uc.c, err = net.DialUnix("unixgram",
&net.UnixAddr{Name: local.Name(), Net: "unixgram"},
&net.UnixAddr{Name: path.Join("/run/wpa_supplicant", ifName), Net: "unixgram"})
if err != nil {
return nil, err
}
file, err := uc.c.File()
if err != nil {
return nil, err
}
uc.fd = file.Fd()
uc.solicited = make(chan message)
uc.unsolicited = make(chan message)
go uc.readLoop()
return uc, nil
}
func (uc *unixgramConn) readLoop() {
for {
n, _, err := syscall.Recvfrom(int(uc.fd), []byte{}, syscall.MSG_PEEK|syscall.MSG_TRUNC)
if err != nil {
continue
}
buf := make([]byte, n)
_, err = uc.c.Read(buf[:])
if err != nil {
uc.solicited <- message{
err: err,
}
}
var p int
var c chan message
if len(buf) >= 3 && buf[0] == '<' && buf[2] == '>' {
switch buf[1] {
case '0', '1', '2', '3', '4':
c = uc.unsolicited
p, _ = strconv.Atoi(string(buf[1]))
buf = buf[3:]
default:
c = uc.solicited
p = 2
}
} else {
c = uc.solicited
p = 2
}
c <- message{
priority: p,
data: buf,
}
}
}
func (uc *unixgramConn) cmd(cmd string) ([]byte, error) {
// TODO: block if any other commands are running
_, err := uc.c.Write([]byte(cmd))
if err != nil {
return nil, err
}
msg := <-uc.solicited
return msg.data, msg.err
}
func (uc *unixgramConn) Ping() error {
resp, err := uc.cmd("PING")
if err != nil {
return err
}
if bytes.Compare(resp, []byte("PONG\n")) == 0 {
return nil
}
return fmt.Errorf("expected %q, got %q", "PONG", resp)
}
func (uc *unixgramConn) ScanResults() ([]ScanResult, error) {
resp, err := uc.cmd("SCAN_RESULTS")
if err != nil {
return nil, err
}
s := bufio.NewScanner(bytes.NewBuffer(resp))
if !s.Scan() {
return nil, errors.New("failed to parse scan results")
}
var bssidCol, freqCol, rssiCol, flagsCol, ssidCol, maxCol int
for n, col := range strings.Split(s.Text(), " / ") {
switch col {
case "bssid":
bssidCol = n
case "frequency":
freqCol = n
case "signal level":
rssiCol = n
case "flags":
flagsCol = n
case "ssid":
ssidCol = n
}
maxCol = n
}
var res []ScanResult
for s.Scan() {
fields := strings.Split(s.Text(), "\t")
if len(fields) < maxCol {
continue // TODO: log error
}
bssid, err := net.ParseMAC(fields[bssidCol])
if err != nil {
continue // TODO: log error
}
freq, err := strconv.Atoi(fields[freqCol])
if err != nil {
continue // TODO: log error
}
rssi, err := strconv.Atoi(fields[rssiCol])
if err != nil {
continue // TODO: log error
}
var flags []string
if len(fields[flagsCol]) >= 2 && fields[flagsCol][0] != '[' && fields[flagsCol][len(fields[flagsCol])-1] != ']' {
flags = strings.Split(fields[flagsCol][1:len(fields[flagsCol])-2], "][")
}
res = append(res, &scanResult{
bssid: bssid,
frequency: freq,
rssi: rssi,
flags: flags,
ssid: fields[ssidCol],
})
}
return res, nil
}