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

Support for Linux AP scanning #29

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ At this time, package `wifi` supports the following operating systems:

If you would like to contribute support for another operating system, your
contributions would be very welcome! Feel free to file an issue to discuss
your plans.
your plans.
162 changes: 159 additions & 3 deletions client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ import (
"github.com/mdlayher/genetlink"
"github.com/mdlayher/netlink"
"github.com/mdlayher/netlink/nlenc"
"github.com/mdlayher/wifi/internal/nl80211"
"github.com/howardstark/wifi/internal/nl80211"

)

// Errors which may occur when interacting with generic netlink.
var (
errInvalidCommand = errors.New("invalid generic netlink response command")
errInvalidFamilyVersion = errors.New("invalid generic netlink response family version")
errMultipleMessages = errors.New("expected only one generic netlink message")
errInvalidCommand = errors.New("invalid generic netlink response command")
errInvalidFamilyVersion = errors.New("invalid generic netlink response family version")
errMissingMulticastGroupScan = errors.New("scan multicast group unavailable")
errScanAborted = errors.New("scan aborted")
)

var _ osClient = &client{}
Expand Down Expand Up @@ -119,6 +123,139 @@ func (c *client) BSS(ifi *Interface) (*BSS, error) {
return parseBSS(msgs)
}

<<<<<<< HEAD
func (c *client) ScanAPs(ifi *Interface) ([]*BSS, error) {
family, err := c.c.GetFamily(nl80211.GenlName)
if err != nil {
return nil, err
}
var mcastScan genetlink.MulticastGroup
for _, mcast := range family.Groups {
if mcast.Name == nl80211.MulticastGroupScan {
mcastScan = mcast
}
}
if mcastScan.Name != nl80211.MulticastGroupScan {
return nil, errMissingMulticastGroupScan
}

err = c.c.JoinGroup(mcastScan.ID)
if err != nil {
return nil, err
}

nestedAttrs, err := netlink.MarshalAttributes([]netlink.Attribute{
{
Type: nl80211.SchedScanMatchAttrSsid,
Length: 0,
Data: nlenc.Bytes(""),
},
})
if err != nil {
return nil, err
}

attrs, err := netlink.MarshalAttributes([]netlink.Attribute{
{
Type: nl80211.AttrScanSsids,
Nested: true,
Length: uint16(len(nestedAttrs)),
Data: nestedAttrs,
},
{
Type: nl80211.AttrIfindex,
Data: nlenc.Uint32Bytes(uint32(ifi.Index)),
},
})
if err != nil {
return nil, err
}

req := genetlink.Message{
Header: genetlink.Header{
Command: nl80211.CmdTriggerScan,
Version: c.familyVersion,
},
Data: attrs,
}

flags := netlink.HeaderFlagsRequest | netlink.HeaderFlagsDump
msgs, err := c.c.Execute(req, c.familyID, flags)
if err != nil {
return nil, err
}

for _, m := range msgs {
if m.Header.Version != c.familyVersion {
return nil, errInvalidFamilyVersion
}
if m.Header.Command == nl80211.CmdScanAborted {
return nil, errScanAborted
}
if m.Header.Command == nl80211.CmdNewScanResults {
break
}
}

err = c.c.LeaveGroup(mcastScan.ID)
if err != nil {
return nil, err
}

attrs, err = netlink.MarshalAttributes([]netlink.Attribute{
{
Type: nl80211.AttrIfindex,
Data: nlenc.Uint32Bytes(uint32(ifi.Index)),
},
})

if err != nil {
return nil, err
}

req = genetlink.Message{
Header: genetlink.Header{
Command: nl80211.CmdGetScan,
Version: c.familyVersion,
},
Data: attrs,
}

flags = netlink.HeaderFlagsRequest | netlink.HeaderFlagsDump
msgs, err = c.c.Execute(req, c.familyID, flags)
if err != nil {
return nil, err
}

if err := c.checkMessages(msgs, nl80211.CmdNewScanResults); err != nil {
return nil, err
}

bssm, err := parseBSSMulti(msgs)
if err != nil {
return nil, err
}

return bssm, nil
}

//func (c *client) SetChannel(ifi *Interface, channel int) error {
// b, err := netlink.MarshalAttributes(ifi.idAttrs())
// if err != nil {
// return err
// }
//
// req := genetlink.Message{
// Header: genetlink.Header{
// Command: nl80211.CmdSetChannel,
// Version: c.familyVersion,
// },
// Data: b,
// }
//
// flags := netlink.HeaderFlagsRequest
//}

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

func parseBSSMulti(msgs []genetlink.Message) ([]*BSS, error) {
bssm := make([]*BSS, 0, len(msgs))
for _, m := range msgs {
attrs, err := netlink.UnmarshalAttributes(m.Data)
if err != nil {
return nil, err
}

var bss BSS
if err := (&bss).parseAttributes(attrs); err != nil {
return nil, err
}

bssm = append(bssm, &bss)
}

return bssm, nil
}

// parseAttributes parses netlink attributes into a BSS's fields.
func (b *BSS) parseAttributes(attrs []netlink.Attribute) error {
for _, a := range attrs {
Expand Down
80 changes: 79 additions & 1 deletion client_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"github.com/mdlayher/genetlink/genltest"
"github.com/mdlayher/netlink"
"github.com/mdlayher/netlink/nlenc"
"github.com/mdlayher/wifi/internal/nl80211"
"github.com/howardstark/wifi/internal/nl80211"
)

func TestLinux_clientInterfacesBadResponseCommand(t *testing.T) {
Expand Down Expand Up @@ -264,6 +264,80 @@ func TestLinux_clientBSSOK(t *testing.T) {
}
}

func TestLinux_clientScanAPOK(t *testing.T) {
ifi := &Interface{
Index: 1,
HardwareAddr: net.HardwareAddr{0xe, 0xad, 0xbe, 0xef, 0xde, 0xad},
}

want := []*BSS{
{
SSID: "Welcome",
BSSID: net.HardwareAddr{0x01, 0x18, 0x99, 0x98, 0x81, 0x99},
Frequency: 2462,
BeaconInterval: 100 * 1024 * time.Microsecond,
LastSeen: 10 * time.Second,
},
{
SSID: "吃了吗?",
BSSID: net.HardwareAddr{0x91, 0x19, 0x72, 0x53, 0x00, 0x00},
Frequency: 2462,
BeaconInterval: 100 * 1024 * time.Microsecond,
LastSeen: 10 * time.Second,
},
}

const flags = netlink.HeaderFlagsRequest | netlink.HeaderFlagsDump

msgsFn := mustMessages(t, nl80211.CmdNewScanResults, want)

c := testClient(t, checkRequest(nl80211.CmdTriggerScan, flags,
func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) {
nestedAttrs, err := netlink.MarshalAttributes([]netlink.Attribute{
{
Type: nl80211.SchedScanMatchAttrSsid,
Length: 0,
Data: nlenc.Bytes(""),
},
})
if err != nil {
return nil, err
}
expAttrs := []netlink.Attribute{
{
Type: nl80211.AttrScanSsids,
Nested: true,
Length: uint16(len(nestedAttrs)),
Data: nestedAttrs,
},
{
Type: nl80211.AttrIfindex,
Data: nlenc.Uint32Bytes(uint32(ifi.Index)),
},
}

attrs, err := netlink.UnmarshalAttributes(greq.Data)
if err != nil {
t.Fatalf("failed to unmarshal attributes: %v", err)
}

if diff := diffNetlinkAttributes(expAttrs, attrs); diff != "" {
t.Fatalf("unexpected request netlink attributes (-want +got):\n%s", diff)
}

return msgsFn(greq, nreq)
},
))
got, err := c.ScanAPs(ifi)
if err != nil {
log.Fatalf("unexpected error: %v", err)
}

if !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected BSS:\n- want: %v\n- got: %v", want, got)
}
}

func TestLinux_clientStationInfoMissingAttributeIsNotExist(t *testing.T) {
c := testClient(t, func(_ genetlink.Message, _ netlink.Message) ([]genetlink.Message, error) {
// One message without station info attribute
Expand Down Expand Up @@ -568,6 +642,10 @@ func mustMessages(t *testing.T, command uint8, want interface{}) genltest.Func {
for _, x := range xs {
as = append(as, x)
}
case []*BSS:
for _, x := range xs {
as = append(as, x)
}
case *BSS:
as = append(as, xs)

Expand Down
17 changes: 17 additions & 0 deletions internal/nl80211/multicast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package nl80211

// nl80211_multicast_group enumeration from nl80211/nl80211.c:39
//
// WARNING: THIS IS MANUALLY CREATED. CURRENTLY THE c-for-go LIB DOES NOT
// SUPPORT .c FILES AND ENUMERATIONS DEFINED THEREWITH.
//
// SEE https://github.com/xlab/nl80211/issues/1 FOR FOLLOWUP ON SOLUTIONS.
const (
McgrpConfig = iota
McgrpScan
McgrpRegulatory
McgrpMlme
McgrpVendor
McgrpNan
McgrpTestmode
)
23 changes: 23 additions & 0 deletions wifi.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,26 @@ func parseIEs(b []byte) ([]ie, error) {

return ies, nil
}

// FreqToChannel returns the channel of the specified
// frequency (in MHz) for the 2.4GHz and 5GHz ranges.
func FreqToChannel(freq int) int {
if freq == 2484 {
return 14
}
if freq < 2484 {
return (freq - 2407) / 5
}
return freq / 5 - 1000
}

func ChannelToFreq5GHz(channel int) int {
return (channel + 1000) * 5
}

func ChannelToFreq2Ghz(channel int) int {
if channel == 14 {
return 2484
}
return (channel * 5) + 2407
}