commit 8a251ea4efe904210dbb032616ab3959ce1b5dbb Author: Dave Pifke Date: Tue May 23 19:02:04 2017 -0700 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. diff --git a/unixgram.go b/unixgram.go new file mode 100644 index 0000000..14074c1 --- /dev/null +++ b/unixgram.go @@ -0,0 +1,187 @@ +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 +} diff --git a/wpasupplicant.go b/wpasupplicant.go new file mode 100644 index 0000000..9b8ae20 --- /dev/null +++ b/wpasupplicant.go @@ -0,0 +1,78 @@ +package wpasupplicant + +import ( + "net" +) + +type Cipher int + +const ( + CIPHER_NONE Cipher = 1 << iota + WEP40 + WEP104 + TKIP + CCMP + AES_128_CMAC + GCMP + SMS4 + GCMP_256 + CCMP_256 + _ + BIP_GMAC_128 + BIP_GMAC_256 + BIP_CMAC_256 + GTK_NOT_USED +) + +type KeyMgmt int + +const ( + IEEE8021X KeyMgmt = 1 << iota + PSK + KEY_MGMT_NONE + IEEE8021X_NO_WPA + WPA_NONE + FT_IEEE8021X + FT_PSK + IEEE8021X_SHA256 + PSK_SHA256 + WPS + SAE + FT_SAE + WAPI_PSK + WAPI_CERT + CCKM + OSEN + IEEE8021X_SUITE_B + IEEE8021X_SUITE_B_192 +) + +type Algorithm int + +type ScanResult interface { + BSSID() net.HardwareAddr + SSID() string + Frequency() int + RSSI() int + Flags() []string +} + +type scanResult struct { + bssid net.HardwareAddr + ssid string + frequency int + rssi int + flags []string +} + +func (r *scanResult) BSSID() net.HardwareAddr { return r.bssid } +func (r *scanResult) SSID() string { return r.ssid } +func (r *scanResult) Frequency() int { return r.frequency } +func (r *scanResult) RSSI() int { return r.rssi } +func (r *scanResult) Flags() []string { return r.flags } + +type Conn interface { + Ping() error + + ScanResults() ([]ScanResult, error) +}