Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple ScanAP functionality #79

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ func (c *Client) BSS(ifi *Interface) (*BSS, error) {
return c.c.BSS(ifi)
}

func (c *Client) AllBSS(ifi *Interface) ([]*BSS, error) {
return c.c.AllBSS(ifi)
}

func (c *Client) TriggerScan(ifi *Interface) error {
return c.c.TriggerScan(ifi)
}

// StationInfo retrieves all station statistics about a WiFi interface.
//
// Since v0.2.0: if there are no stations, an empty slice is returned instead
Expand Down
117 changes: 117 additions & 0 deletions client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"bytes"
"crypto/sha1"
"encoding/binary"
"errors"
"fmt"
"net"
"os"
"time"
Expand Down Expand Up @@ -168,6 +170,87 @@ func (c *client) BSS(ifi *Interface) (*BSS, error) {
return parseBSS(msgs)
}

// AllBSS requests that nl80211 return all the BSS around the specified Interface.
func (c *client) AllBSS(ifi *Interface) ([]*BSS, error) {
msgs, err := c.get(
unix.NL80211_CMD_GET_SCAN,
netlink.Dump,
ifi,
nil,
)
if err != nil {
return nil, err
}
return parseAllBSS(msgs)
}

// trigger the kernel to scan APs. It will take about 5 seconds.
func (c *client) TriggerScan(ifi *Interface) error {
// need a new socket to receive multicast message about scan status from kernel
informer := make(chan uint8)
var err error
go func(informer chan<- uint8) {
conn, err := genetlink.Dial(nil)
if err != nil {
return
}
defer conn.Close()
family, err := conn.GetFamily(unix.NL80211_GENL_NAME)
if err != nil {
return
}
for _, group := range family.Groups {
if group.Name == unix.NL80211_MULTICAST_GROUP_SCAN {
err = conn.JoinGroup(group.ID)
if err != nil {
return
}
}
}
informer<-scan_start
for {
genl_msgs, _, err := conn.Receive()
if err != nil {
return
}
for _, msg := range genl_msgs {
switch msg.Header.Command {
case unix.NL80211_CMD_TRIGGER_SCAN:
continue
case unix.NL80211_CMD_SCAN_ABORTED:
informer<-scan_abort
goto END
case unix.NL80211_CMD_NEW_SCAN_RESULTS:
informer<-scan_done
goto END
}
}
}
END:
return
}(informer)
if err != nil {
return err
}
// scan starts
<-informer
_, err = c.get(
unix.NL80211_CMD_TRIGGER_SCAN,
netlink.Acknowledge,
ifi,
nil,
)
if err != nil {
return err
}
// wait for kernel to inform the scan status
if status := <-informer; status == scan_abort {
return errors.New("NL80211 Scan aborted by kernel")
}

return nil
}

// StationInfo requests that nl80211 return all station info for the specified
// Interface.
func (c *client) StationInfo(ifi *Interface) ([]*StationInfo, error) {
Expand Down Expand Up @@ -351,6 +434,40 @@ func parseBSS(msgs []genetlink.Message) (*BSS, error) {
return nil, os.ErrNotExist
}

// parseAllBSS parses all the BSS from nl80211 BSS messages
func parseAllBSS(msgs []genetlink.Message) ([]*BSS, error) {
fmt.Println(len(msgs))
all_bss := make([]*BSS, 0, len(msgs))
for _, m := range msgs {
attrs, err := netlink.UnmarshalAttributes(m.Data)
if err != nil {
return nil, err
}

var bss BSS
for _, a := range attrs {
if a.Type != unix.NL80211_ATTR_BSS {
continue
}

nattrs, err := netlink.UnmarshalAttributes(a.Data)
if err != nil {
return nil, err
}

if !attrsContain(nattrs, unix.NL80211_BSS_STATUS) {
bss.Status = BSSStatusDisAssociated
}

if err := (&bss).parseAttributes(nattrs); err != nil {
continue
}
}
all_bss = append(all_bss, &bss)
}
return all_bss, nil
}

// parseAttributes parses netlink attributes into a BSS's fields.
func (b *BSS) parseAttributes(attrs []netlink.Attribute) error {
for _, a := range attrs {
Expand Down
12 changes: 12 additions & 0 deletions wifi.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ const (

// BSSStatusIBSSJoined indicates that a client has joined an independent BSS.
BSSStatusIBSSJoined

// BSSStatusDisAssociated indicates that the current interface is not associated with the BSS
BSSStatusDisAssociated
)

// String returns the string representation of a BSSStatus.
Expand All @@ -252,6 +255,8 @@ func (s BSSStatus) String() string {
return "associated"
case BSSStatusIBSSJoined:
return "IBSS joined"
case BSSStatusDisAssociated:
return "disassociated"
default:
return fmt.Sprintf("unknown(%d)", s)
}
Expand Down Expand Up @@ -304,3 +309,10 @@ func parseIEs(b []byte) ([]ie, error) {

return ies, nil
}

// List of scan status
const (
scan_start = iota
scan_abort
scan_done
)
28 changes: 28 additions & 0 deletions wifi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package wifi
import (
"reflect"
"testing"
"net"
)

func TestInterfaceTypeString(t *testing.T) {
Expand Down Expand Up @@ -179,3 +180,30 @@ func Test_parseIEs(t *testing.T) {
})
}
}

func TestScanBSS(t *testing.T) {
ifi := &Interface{
Name: "wlp0s20f3",
Index: 3,
HardwareAddr: net.HardwareAddr{0x08, 0xd2, 0x3e, 0x65, 0xb8, 0x91},
PHY: 0,
Device: 2,
Frequency: 5765,
}
c, err := New()
if err != nil {
t.Fatalf("%+v\n", err)
}
defer c.Close()
err = c.c.TriggerScan(ifi)
if err != nil {
t.Fatalf("%+v\n", err)
}
all_bss, err := c.c.AllBSS(ifi)
if err != nil {
t.Fatalf("%+v\n", err)
}
for _, bss := range all_bss {
t.Logf("%+v\n", bss)
}
}