From 9f4f63ce8dbad7b194a4f2111b17550f051dfaab Mon Sep 17 00:00:00 2001 From: Thulashitharan Date: Sat, 4 Jan 2025 00:50:52 +0530 Subject: [PATCH 1/5] Init --- client.go | 6 + client_linux.go | 361 ++++++++++++++++++++++++++++++++++++++++++++++++ wifi.go | 213 ++++++++++++++++++++++++++++ 3 files changed, 580 insertions(+) diff --git a/client.go b/client.go index 3361aff..425f9b5 100644 --- a/client.go +++ b/client.go @@ -52,6 +52,12 @@ func (c *Client) BSS(ifi *Interface) (*BSS, error) { return c.c.BSS(ifi) } +// PHYs returns a list of the system's WiFi devices. +func (c *Client) PHYs() ([]*PHY, error) { return c.c.PHYs() } + +// PHY returns the WiFi device corresponding to the specified index. +func (c *Client) PHY(index int) (*PHY, error) { return c.c.PHY(index) } + // StationInfo retrieves all station statistics about a WiFi interface. // // Since v0.2.0: if there are no stations, an empty slice is returned instead diff --git a/client_linux.go b/client_linux.go index cc8c584..d26ae9f 100644 --- a/client_linux.go +++ b/client_linux.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto/sha1" "encoding/binary" + "fmt" "net" "os" "time" @@ -86,6 +87,66 @@ func (c *client) Interfaces() ([]*Interface, error) { return parseInterfaces(msgs) } +// PHY requests that nl80211 return information for the physical device +// specified by the index. +func (c *client) PHY(n int) (*PHY, error) { + attrs := []netlink.Attribute{ + { + Type: unix.NL80211_ATTR_WIPHY, + Data: nlenc.Uint32Bytes(uint32(n)), + }, + } + phys, err := c.getPHYs(attrs) + if err != nil { + return nil, err + } + if len(phys) == 0 { + return nil, fmt.Errorf("No PHY with index %d", n) + } + return phys[0], nil +} + +// PHYs requests that nl80211 return information for all wireless physical +// devices. +func (c *client) PHYs() ([]*PHY, error) { + attrs := make([]netlink.Attribute, 0) + return c.getPHYs(attrs) +} + +// getPHYs is the back-end for PHY() and PHYs(): building and making the netlink +// call, and parsing the response. +func (c *client) getPHYs(attrs []netlink.Attribute) ([]*PHY, error) { + // The kernel, as of 3713b4e364eff (3.10), doesn't emit all information + // unless SplitWiphyDump is set. We could check for it by issuing + // CmdGetProtocolFeatures and seeing if ProtocolFeatureSplitWiphyDump is + // set, if we care about kernels that old ... + attrs = append(attrs, netlink.Attribute{Type: unix.NL80211_ATTR_SPLIT_WIPHY_DUMP}) + nlattrs, err := netlink.MarshalAttributes(attrs) + if err != nil { + return nil, err + } + + req := genetlink.Message{ + Header: genetlink.Header{ + Command: unix.NL80211_CMD_GET_WIPHY, + Version: c.familyVersion, + }, + Data: nlattrs, + } + + flags := netlink.Request | netlink.Dump + msgs, err := c.c.Execute(req, c.familyID, flags) + if err != nil { + return nil, err + } + + //if err := c.checkMessages(msgs, unix.NL80211_CMD_NEW_WIPHY); err != nil { + // return nil, err + //} + + return parsePHYs(msgs) +} + // Connect starts connecting the interface to the specified ssid. func (c *client) Connect(ifi *Interface, ssid string) error { // Ask nl80211 to connect to the specified SSID. @@ -366,6 +427,306 @@ func parseBSS(msgs []genetlink.Message) (*BSS, error) { return nil, os.ErrNotExist } +func parsePHYs(msgs []genetlink.Message) ([]*PHY, error) { + phys := make([]*PHY, 0) + var phy *PHY + curphynum := -1 + for _, m := range msgs { + attrs, err := netlink.UnmarshalAttributes(m.Data) + if err != nil { + return nil, err + } + + // Because we get a single stream of messages spanning multiple + // PHYs, we have to peek into the attributes to see if it's the + // same PHY as we've been processing. + phynum, err := phyNumber(attrs) + if err != nil { + return nil, err + } + if phynum != curphynum { + phy = new(PHY) + phy.Extra = make(map[uint16][]byte, 0) + phys = append(phys, phy) + curphynum = phynum + } + + if err := phy.parseAttributes(attrs); err != nil { + return nil, err + } + } + return phys, nil +} + +// phyNumber extracts the first integer device index (AttrWiphy) from a list of +// netlink attributes. +func phyNumber(attrs []netlink.Attribute) (int, error) { + for _, a := range attrs { + switch a.Type { + case unix.NL80211_ATTR_WIPHY: + return int(nlenc.Uint32(a.Data)), nil + } + } + return 0, fmt.Errorf("there was no wiphy attribute") +} + +// parseAttributes parses netlink attributes into a PHY's fields. +func (p *PHY) parseAttributes(attrs []netlink.Attribute) error { + for _, a := range attrs { + switch a.Type { + case unix.NL80211_ATTR_WIPHY: + p.Index = int(nlenc.Uint32(a.Data)) + + case unix.NL80211_ATTR_WIPHY_NAME: + p.Name = nlenc.String(a.Data) + + case unix.NL80211_ATTR_SUPPORTED_IFTYPES: + // This contains nested attributes with no data; the + // data we care about is the type. + nattrs, err := netlink.UnmarshalAttributes(a.Data) + if err != nil { + return err + } + for _, na := range nattrs { + p.SupportedIftypes = append(p.SupportedIftypes, InterfaceType(na.Type)) + } + + case unix.NL80211_ATTR_SOFTWARE_IFTYPES: + // This contains nested attributes with no data; the + // data we care about is the type. + nattrs, err := netlink.UnmarshalAttributes(a.Data) + if err != nil { + return err + } + for _, na := range nattrs { + p.SoftwareIftypes = append(p.SoftwareIftypes, InterfaceType(na.Type)) + } + + case unix.NL80211_ATTR_WIPHY_BANDS: + nattrs, err := netlink.UnmarshalAttributes(a.Data) + if err != nil { + return err + } + for i, band := range nattrs { + // band.Type has the band number + err := p.parseBandAttributes(band) + if err != nil { + return fmt.Errorf("Couldn't decode band %d (attr#%d) data: %s", + band.Type, i, err) + } + } + + case unix.NL80211_ATTR_INTERFACE_COMBINATIONS: + nattrs, err := netlink.UnmarshalAttributes(a.Data) + if err != nil { + return err + } + for i, combo := range nattrs { + c, err := parseCombo(combo) + if err != nil { + return fmt.Errorf("Couldn't decode combo %d data: %s", i, err) + } + p.InterfaceCombinations = append(p.InterfaceCombinations, *c) + } + + default: + p.Extra[a.Type] = a.Data + } + } + return nil +} + +// parseCombo parses a netlink attribute into an InterfaceCombination. +func parseCombo(comboNLA netlink.Attribute) (*InterfaceCombination, error) { + attrs, err := netlink.UnmarshalAttributes(comboNLA.Data) + if err != nil { + return nil, err + } + + combo := &InterfaceCombination{} + for _, attr := range attrs { + switch attr.Type { + case unix.NL80211_IFACE_COMB_LIMITS: + lattrs, err := netlink.UnmarshalAttributes(attr.Data) + if err != nil { + return nil, err + } + + for _, l := range lattrs { + comboLimit := InterfaceCombinationLimit{} + ltypes, err := netlink.UnmarshalAttributes(l.Data) + if err != nil { + return nil, err + } + + for _, la := range ltypes { + switch la.Type { + case unix.NL80211_IFACE_LIMIT_MAX: + comboLimit.Max = int(nlenc.Uint32(la.Data)) + case unix.NL80211_IFACE_LIMIT_TYPES: + types, err := netlink.UnmarshalAttributes(la.Data) + if err != nil { + return nil, err + } + + for _, typ := range types { + comboLimit.InterfaceTypes = append(comboLimit.InterfaceTypes, InterfaceType(typ.Type)) + } + } + } + combo.CombinationLimits = append(combo.CombinationLimits, comboLimit) + } + + case unix.NL80211_IFACE_COMB_NUM_CHANNELS: + combo.NumChannels = int(nlenc.Uint32(attr.Data)) + + case unix.NL80211_IFACE_COMB_MAXNUM: + combo.Total = int(nlenc.Uint32(attr.Data)) + + case unix.NL80211_IFACE_COMB_STA_AP_BI_MATCH: + combo.StaApBiMatch = true + } + } + return combo, nil +} + +// parseBandAttributes parses a netlink attribute into the band-specific data of +// a PHY. +func (p *PHY) parseBandAttributes(nlband netlink.Attribute) error { + attrs, err := netlink.UnmarshalAttributes(nlband.Data) + if err != nil { + return err + } + + // We'll get called multiple times for individual attributes of a band, + // so be sure to use the right element of the BandAttributes array, or + // add new ones if we haven't seen the band before. The expectation is + // that we'll get them in order, 0..n, but this should work for any + // ordering. + for int(nlband.Type)+1 > len(p.BandAttributes) { + ba := &BandAttributes{} + p.BandAttributes = append(p.BandAttributes, *ba) + } + ba := &p.BandAttributes[nlband.Type] + + for _, attr := range attrs { + switch attr.Type { + case unix.NL80211_BAND_ATTR_HT_CAPA: + ba.HTCapabilities = decodeHTCapabilities(ba.HTCapabilities, nlenc.Uint16(attr.Data)) + + case unix.NL80211_BAND_ATTR_HT_AMPDU_FACTOR: + exponent := nlenc.Uint8(attr.Data) + // The exponent comes from three bits of OTA data, but + // netlink gives it to us as an 8-bit value. + if exponent < 4 { + // If we haven't seen BandAttrHtCapa yet, we + // need to create the struct first. + if ba.HTCapabilities == nil { + ba.HTCapabilities = new(HTCapabilities) + } + ba.HTCapabilities.MaxRxAMPDULength = (1 << (13 + exponent)) - 1 + } + + case unix.NL80211_BAND_ATTR_HT_AMPDU_DENSITY: + spacing := nlenc.Uint8(attr.Data) + if spacing > 0 { + ba.MinRxAMPDUSpacing = (1 << (spacing - 1)) * time.Microsecond / 4 + } + + case unix.NL80211_BAND_ATTR_HT_MCS_SET: + case unix.NL80211_BAND_ATTR_VHT_CAPA: + case unix.NL80211_BAND_ATTR_VHT_MCS_SET: + // TODO Handle these + + case unix.NL80211_BAND_ATTR_RATES: + nattrs, err := netlink.UnmarshalAttributes(attr.Data) + if err != nil { + return err + } + // It doesn't look like we need to take as much care to + // build up the BitrateAttributes array as we do the + // FrequenceAttributes array, since it appears we get + // all of the former back in a single message. But just + // in case ... + for _, nlbra := range nattrs { + brattrs, err := netlink.UnmarshalAttributes(nlbra.Data) + if err != nil { + return err + } + var bra BitrateAttrs + for _, bra2 := range brattrs { + switch bra2.Type { + case unix.NL80211_BITRATE_ATTR_RATE: + bra.Bitrate = 0.1 * float32(nlenc.Uint32(bra2.Data)) + case unix.NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE: + bra.ShortPreamble = true + } + } + ba.BitrateAttributes = append(ba.BitrateAttributes, bra) + } + + case unix.NL80211_BAND_ATTR_FREQS: + nattrs, err := netlink.UnmarshalAttributes(attr.Data) + if err != nil { + return err + } + for _, nlfa := range nattrs { + fattrs, err := netlink.UnmarshalAttributes(nlfa.Data) + if err != nil { + return err + } + var fa FrequencyAttrs + for _, fa2 := range fattrs { + switch fa2.Type { + case unix.NL80211_FREQUENCY_ATTR_FREQ: + fa.Frequency = int(nlenc.Uint32(fa2.Data)) + case unix.NL80211_FREQUENCY_ATTR_DISABLED: + fa.Disabled = true + // In 8fe02e167efa8 (3.14), Linux renamed the + // PASSIVE_SCAN frequency attribute to NO_IR, + // and deprecated NO_IBSS (4). It sends both, + // but we don't need to support old kernels. + case unix.NL80211_FREQUENCY_ATTR_NO_IR: + fa.NoIR = true + case unix.NL80211_FREQUENCY_ATTR_RADAR: + fa.RadarDetection = true + case unix.NL80211_FREQUENCY_ATTR_MAX_TX_POWER: + fa.MaxTxPower = 0.01 * float32(nlenc.Uint32(fa2.Data)) + } + } + ba.FrequencyAttributes = append(ba.FrequencyAttributes, fa) + } + } + } + + return nil +} + +// decodeHTCapabilities parses a 16-bit integer into an HTCapabilities struct +// based on information from an HT Capabilities Info field (BandAttrHtCapa). +// Create a new one if nil is passed in, but allow for the struct to have other +// fields already set. +func decodeHTCapabilities(htcap *HTCapabilities, cap uint16) *HTCapabilities { + if htcap == nil { + htcap = new(HTCapabilities) + } + + htcap.RxLDPC = cap&(1<<0) != 0 + htcap.CW40 = cap&(1<<1) != 0 + htcap.HTGreenfield = cap&(1<<4) != 0 + htcap.SGI20 = cap&(1<<5) != 0 + htcap.SGI40 = cap&(1<<6) != 0 + htcap.TxSTBC = cap&(1<<7) != 0 + htcap.RxSTBCStreams = uint8((cap >> 8) & 0x3) + htcap.HTDelayedBlockAck = cap&(1<<10) != 0 + htcap.LongMaxAMSDULength = cap&(1<<11) != 0 + htcap.DSSSCCKHT40 = cap&(1<<12) != 0 + htcap.FortyMhzIntolerant = cap&(1<<14) != 0 + htcap.LSIGTxOPProtection = cap&(1<<15) != 0 + + return htcap +} + // parseAttributes parses netlink attributes into a BSS's fields. func (b *BSS) parseAttributes(attrs []netlink.Attribute) error { for _, a := range attrs { diff --git a/wifi.go b/wifi.go index 475b76e..979c4c4 100644 --- a/wifi.go +++ b/wifi.go @@ -3,6 +3,7 @@ package wifi import ( "errors" "fmt" + "golang.org/x/sys/unix" "net" "time" ) @@ -81,6 +82,8 @@ func (t InterfaceType) String() string { return "station" case InterfaceTypeAP: return "access point" + case InterfaceTypeAPVLAN: + return "access point/VLAN" case InterfaceTypeWDS: return "wireless distribution" case InterfaceTypeMonitor: @@ -257,6 +260,216 @@ func (s BSSStatus) String() string { } } +// A PHY represents the physical attributes of a wireless device. +type PHY struct { + // The index of the interface. + Index int + + // The name of the interface. + Name string + + // The interface types this device supports. + SupportedIftypes []InterfaceType + + // The software-only interface types this device supports. + SoftwareIftypes []InterfaceType + + // An array of attributes related to each radio frequency band. + BandAttributes []BandAttributes + + // A description of what combinations of interfaces the device can + // support running simultaneously, on virtual MACs. + InterfaceCombinations []InterfaceCombination + + // All the attributes the kernel has told us about, but we haven't + // parsed. + Extra map[uint16][]byte +} + +// BandAttributes represent the RF band-specific attributes. +type BandAttributes struct { + // High Throughput (802.11n) device capabilities (nil if not supported). + HTCapabilities *HTCapabilities + + // Minimum spacing between A-MPDU frames. Used for both HT and VHT + // capable devices. + MinRxAMPDUSpacing time.Duration + + // Per-frequency (channel) attributes. + FrequencyAttributes []FrequencyAttrs + + // Per-bitrate attributes. + BitrateAttributes []BitrateAttrs +} + +// HTCapabilities represents 802.11n (High Throughput) capabilities. This group +// of attributes is specific to each band of frequencies. Failure to support +// any given attribute may be due to lack support in the driver or the firmware, +// not only in the hardware. Some of them may also be overridden during station +// association. +// +// The fields represent those in the HT Capabilities element (802.11-2016, +// 9.4.2.56). Notably missing is information about the device's Spatial +// Multiplexing Power Save (SMPS) capability. SMPS support must be determined +// by retrieving the device feature flags (not yet supported). +type HTCapabilities struct { + // Device supports Low Density Parity Check codes. + RxLDPC bool + + // Device supports 40MHz channels (in addition to 20MHz channels). + CW40 bool + + // Device supports HT Greenfield (802.11n-only) mode, in which a/b/g + // frames will be ignored. + HTGreenfield bool + + // Device supports short guard intervals in 20MHz channels. + SGI20 bool + + // Device supports short guard intervals in 40MHz channels. + SGI40 bool + + // Device supports Space-Time Block Coding transmission. + TxSTBC bool + + // Number of STBC receive streams supported by the device. Valid values + // are 0-3. + RxSTBCStreams uint8 + + // Device supports delayed Block Ack frames when acknowledging an + // A-MPDU. + HTDelayedBlockAck bool + + // Device supports long (7935 bytes) maximum A-MSDU length, compared to + // standard 3839 bytes. + LongMaxAMSDULength bool + + // Device supports DSSS/CCK in 40MHz channels. + DSSSCCKHT40 bool + + // (2.4GHz) Band cannot tolerate 40MHz channels because someone has + // requested it support 20MHz channels. + FortyMhzIntolerant bool + + // Device supports L-SIG (non-HT) Transmit Oppportunity protection. + LSIGTxOPProtection bool + + // Maximum receivable A-MPDU (Aggregated MAC Protocol Data Unit) frame + // size. + MaxRxAMPDULength int +} + +// FrequencyAttrs represents the attributes of a WiFi frequency/channel. +type FrequencyAttrs struct { + // Frequency is the radio frequency in MHz. + Frequency int + + // Disabled indicates that the channel is disabled due to regulatory + // requirements. + Disabled bool + + // NoIR indicates that no mechanisms that initiate radiation are + // permitted on this channel. + NoIR bool + + // RadarDetection indicates that radar detection is mandatory on this + // channel. + RadarDetection bool + + // MaxTxPower gives the maximum transmission power in mBm (100 * dBm). + MaxTxPower float32 +} + +// BitrateAttrs represents the attributes of a bitrate. +type BitrateAttrs struct { + // Bitrate is the bitrate in units of 100kbps. + Bitrate float32 + + // ShortPreamble indicates that a short preamble is supported in the + // 2.4GHz band. + ShortPreamble bool +} + +// InterfaceCombination represents a group of valid combinations of interface +// types which can be simultaneously supported on a device. +type InterfaceCombination struct { + CombinationLimits []InterfaceCombinationLimit + + // Total is the maximum number of interfaces that can be created in this + // group. + Total int + + // NumChannels is the number of different channels which may be used in + // this group. + NumChannels int + + // StaApBiMatch indicates that beacon intervals within this group must + // all be the same, regardless of interface type. + StaApBiMatch bool +} + +// InterfaceCombinationLimit represents a single combination of interface types +// which may be run simultaneously on a device. +type InterfaceCombinationLimit struct { + InterfaceTypes []InterfaceType + + // Max is the maximum number of interfaces that can be chosen from the + // set of interface types in InterfaceTypes. + Max int +} + +// FrequencyToChannel returns the channel number given the frequency in MHz, as +// defined by IEEE802.11-2007, 17.3.8.3.2 and Annex J. +func FrequencyToChannel(freq int) int { + if freq == 2484 { + return 14 + } else if freq < 2484 { + return (freq - 2407) / 5 + } else if freq >= 4910 && freq <= 4980 { + return (freq - 4000) / 5 + } else if freq <= 45000 { + return (freq - 5000) / 5 + } else if freq >= 58320 && freq <= 64800 { + return (freq - 56160) / 2160 + } else { + return 0 + } +} + +// Constants representing the standard WiFi frequency bands. +const ( + Band2GHz = unix.NL80211_BAND_2GHZ + Band5GHz = unix.NL80211_BAND_5GHZ + Band60GHz = unix.NL80211_BAND_60GHZ +) + +// ChannelToFrequency returns the frequency given the channel number and the +// band, as there are overlapping channel numbers between bands. +func ChannelToFrequency(channel int, band int) int { + if channel <= 0 { + return 0 + } + + switch band { + case Band2GHz: + if channel == 14 { + return 2484 + } else if channel < 14 { + return 2407 + channel*5 + } + case Band5GHz: + if channel >= 182 && channel <= 196 { + return 4000 + channel*5 + } + return 5000 + channel*5 + case Band60GHz: + if channel < 5 { + return 56160 + channel*2160 + } + } + return 0 +} + // List of 802.11 Information Element types. const ( ieSSID = 0 From ceb2f580f904ded5d62dca6f528eefe0f90afd6f Mon Sep 17 00:00:00 2001 From: supernovatux Date: Sat, 4 Jan 2025 12:37:44 +0530 Subject: [PATCH 2/5] Modernize some parts Use c.get instead of c.execute Use netlink.AttributeEncoder for Attrs --- client.go | 2 +- client_linux.go | 44 +++++++++++--------------------------------- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/client.go b/client.go index 425f9b5..edb6fb5 100644 --- a/client.go +++ b/client.go @@ -56,7 +56,7 @@ func (c *Client) BSS(ifi *Interface) (*BSS, error) { func (c *Client) PHYs() ([]*PHY, error) { return c.c.PHYs() } // PHY returns the WiFi device corresponding to the specified index. -func (c *Client) PHY(index int) (*PHY, error) { return c.c.PHY(index) } +func (c *Client) PHY(index uint32) (*PHY, error) { return c.c.PHY(index) } // StationInfo retrieves all station statistics about a WiFi interface. // diff --git a/client_linux.go b/client_linux.go index d26ae9f..697fafb 100644 --- a/client_linux.go +++ b/client_linux.go @@ -89,19 +89,13 @@ func (c *client) Interfaces() ([]*Interface, error) { // PHY requests that nl80211 return information for the physical device // specified by the index. -func (c *client) PHY(n int) (*PHY, error) { - attrs := []netlink.Attribute{ - { - Type: unix.NL80211_ATTR_WIPHY, - Data: nlenc.Uint32Bytes(uint32(n)), - }, - } - phys, err := c.getPHYs(attrs) +func (c *client) PHY(n uint32) (*PHY, error) { + phys, err := c.getPHYs(&n) if err != nil { return nil, err } if len(phys) == 0 { - return nil, fmt.Errorf("No PHY with index %d", n) + return nil, fmt.Errorf("no PHY with index %d", n) } return phys[0], nil } @@ -109,41 +103,25 @@ func (c *client) PHY(n int) (*PHY, error) { // PHYs requests that nl80211 return information for all wireless physical // devices. func (c *client) PHYs() ([]*PHY, error) { - attrs := make([]netlink.Attribute, 0) - return c.getPHYs(attrs) + return c.getPHYs(nil) } // getPHYs is the back-end for PHY() and PHYs(): building and making the netlink // call, and parsing the response. -func (c *client) getPHYs(attrs []netlink.Attribute) ([]*PHY, error) { +func (c *client) getPHYs(n *uint32) ([]*PHY, error) { // The kernel, as of 3713b4e364eff (3.10), doesn't emit all information // unless SplitWiphyDump is set. We could check for it by issuing // CmdGetProtocolFeatures and seeing if ProtocolFeatureSplitWiphyDump is // set, if we care about kernels that old ... - attrs = append(attrs, netlink.Attribute{Type: unix.NL80211_ATTR_SPLIT_WIPHY_DUMP}) - nlattrs, err := netlink.MarshalAttributes(attrs) - if err != nil { - return nil, err - } - - req := genetlink.Message{ - Header: genetlink.Header{ - Command: unix.NL80211_CMD_GET_WIPHY, - Version: c.familyVersion, - }, - Data: nlattrs, - } - - flags := netlink.Request | netlink.Dump - msgs, err := c.c.Execute(req, c.familyID, flags) + msgs, err := c.get(unix.NL80211_CMD_GET_WIPHY, netlink.Dump, nil, func(ae *netlink.AttributeEncoder) { + ae.Flag(unix.NL80211_ATTR_SPLIT_WIPHY_DUMP, true) + if n != nil { + ae.Uint32(unix.NL80211_ATTR_WIPHY, *n) + } + }) if err != nil { return nil, err } - - //if err := c.checkMessages(msgs, unix.NL80211_CMD_NEW_WIPHY); err != nil { - // return nil, err - //} - return parsePHYs(msgs) } From d9087d75d1f5037e450ffa4fb6b22342bb099c66 Mon Sep 17 00:00:00 2001 From: supernovatux Date: Sat, 4 Jan 2025 20:09:22 +0530 Subject: [PATCH 3/5] Add VHT capabilities --- client_linux.go | 40 ++++++++++++++++++++++++-- wifi.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/client_linux.go b/client_linux.go index 697fafb..9176857 100644 --- a/client_linux.go +++ b/client_linux.go @@ -254,7 +254,7 @@ func (c *client) get( cmd uint8, flags netlink.HeaderFlags, ifi *Interface, - // May be nil; used to apply optional parameters. +// May be nil; used to apply optional parameters. params func(ae *netlink.AttributeEncoder), ) ([]genetlink.Message, error) { ae := netlink.NewAttributeEncoder() @@ -611,8 +611,9 @@ func (p *PHY) parseBandAttributes(nlband netlink.Attribute) error { ba.MinRxAMPDUSpacing = (1 << (spacing - 1)) * time.Microsecond / 4 } - case unix.NL80211_BAND_ATTR_HT_MCS_SET: case unix.NL80211_BAND_ATTR_VHT_CAPA: + ba.VHTCapabilities = decodeVHTCapabilities(ba.VHTCapabilities, nlenc.Uint32(attr.Data)) + case unix.NL80211_BAND_ATTR_HT_MCS_SET: case unix.NL80211_BAND_ATTR_VHT_MCS_SET: // TODO Handle these @@ -704,6 +705,41 @@ func decodeHTCapabilities(htcap *HTCapabilities, cap uint16) *HTCapabilities { return htcap } +func decodeVHTCapabilities(vhtcap *VHTCapabilities, cap uint32) *VHTCapabilities { + if vhtcap == nil { + vhtcap = new(VHTCapabilities) + } + switch int(cap & 0x3) { + case 0: + vhtcap.MaxMPDULength = 3895 + case 1: + vhtcap.MaxMPDULength = 7991 + case 2: + vhtcap.MaxMPDULength = 11454 + } + vhtcap.VHT160 = cap&(1<<2) != 0 + vhtcap.VHT8080 = cap&(1<<3) != 0 + vhtcap.RXLDPC = cap&(1<<4) != 0 + vhtcap.ShortGI80 = cap&(1<<5) != 0 + vhtcap.ShortGI160 = cap&(1<<6) != 0 + vhtcap.TXSTBC = cap&(1<<7) != 0 + vhtcap.RXSTBC = int((cap >> 8) & 0x7) + vhtcap.SuBeamFormer = cap&(1<<11) != 0 + vhtcap.SuBeamFormee = cap&(1<<12) != 0 + vhtcap.BFAntenna = int((cap>>13)&0x7) - 1 + vhtcap.SoundingDimension = int((cap >> 16) & 0x7) + vhtcap.MuBeamformer = cap&(1<<19) != 0 + vhtcap.MuBeamformee = cap&(1<<20) != 0 + vhtcap.VTHTXOPPS = cap&(1<<21) != 0 + vhtcap.HTCVHT = cap&(1<<22) != 0 + vhtcap.MaxAMPDU = 2 ^ (13 + int((cap>>23)&0x2)) - 1 + vhtcap.VHTLinkAdapt = int((cap >> 27) & 0x3) + vhtcap.RXAntennaPattern = cap&(1<<28) != 0 + vhtcap.TXAntennaPattern = cap&(1<<29) != 0 + vhtcap.ExtendedNSSBW = int((cap >> 30) & 0x7) + fmt.Printf("%d\n", cap) + return vhtcap +} // parseAttributes parses netlink attributes into a BSS's fields. func (b *BSS) parseAttributes(attrs []netlink.Attribute) error { diff --git a/wifi.go b/wifi.go index 979c4c4..361ab39 100644 --- a/wifi.go +++ b/wifi.go @@ -291,6 +291,9 @@ type BandAttributes struct { // High Throughput (802.11n) device capabilities (nil if not supported). HTCapabilities *HTCapabilities + // Very High Throughput (802.11ac) device capabilities (nil if not supported). + VHTCapabilities *VHTCapabilities + // Minimum spacing between A-MPDU frames. Used for both HT and VHT // capable devices. MinRxAMPDUSpacing time.Duration @@ -359,6 +362,78 @@ type HTCapabilities struct { MaxRxAMPDULength int } +// VHTCapabilities represents 802.11ac (Very High Throughput) capabilities. +// +// The fields represent those in the VHT Capabilities element (802.11-2020, +// 9.4.2.157). +type VHTCapabilities struct { + // Maximum MPDU length supported by the device. + MaxMPDULength int + + // Device supports 160MHz channel width. + VHT160 bool + + // Device supports 80+80MHz channel width (non-contiguous 160MHz) along with 160MHz channel. + VHT8080 bool + + // Device supports receiving Low Density Parity Check codes. + RXLDPC bool + + // Device supports short guard intervals in 80MHz channels. + ShortGI80 bool + + // Device supports short guard intervals in 160MHz and 80+80MHz channels. + ShortGI160 bool + + // Device supports transmission of at least 2x1 Space-Time Block Coding transmission. + TXSTBC bool + + // Number of STBC receive streams supported by the device. Valid values are 0-4. + RXSTBC int + + // Device supports SU (Single User) Beamforming as a transmitter. + SuBeamFormer bool + + // Device supports SU (Single User) Beamforming as a receiver. + SuBeamFormee bool + + // Number of sounding antennas supported by the device for SU Beamforming transmission. + BFAntenna int + + // Maximum sounding dimensions supported by the device for SU Beamforming. + SoundingDimension int + + // Device supports MU (Multi-User) Beamforming as a transmitter. + MuBeamformer bool + + // Device supports MU (Multi-User) Beamforming as a receiver. + MuBeamformee bool + + // Device supports VHT TXOP power save mode. + VTHTXOPPS bool + + // Device supports HT Control field when operating in VHT mode. + HTCVHT bool + + // Maximum A-MPDU (Aggregated MAC Protocol Data Unit) frame size supported by the device. + MaxAMPDU int + + // Device supports VHT Link Adaptation capabilities. Valid values + // specify the type of link adaptation supported (e.g., no feedback, + // unsolicited feedback, or both). + VHTLinkAdapt int + + // Device supports receive antenna pattern consistency. + RXAntennaPattern bool + + // Device supports transmit antenna pattern consistency. + TXAntennaPattern bool + + //Indicates whether the STA is capable of interpreting the Extended NSS BW + //Support subfield of the VHT Capabilities Information field. + ExtendedNSSBW int +} + // FrequencyAttrs represents the attributes of a WiFi frequency/channel. type FrequencyAttrs struct { // Frequency is the radio frequency in MHz. From 37b7778c4243cf1ef82acfec9d487d0c201fa908 Mon Sep 17 00:00:00 2001 From: supernovatux Date: Sat, 4 Jan 2025 22:11:24 +0530 Subject: [PATCH 4/5] Handle HT and VHT MCS --- client_linux.go | 20 ++++++++++++++++---- wifi.go | 10 ++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/client_linux.go b/client_linux.go index 9176857..dc38f6a 100644 --- a/client_linux.go +++ b/client_linux.go @@ -254,7 +254,7 @@ func (c *client) get( cmd uint8, flags netlink.HeaderFlags, ifi *Interface, -// May be nil; used to apply optional parameters. + // May be nil; used to apply optional parameters. params func(ae *netlink.AttributeEncoder), ) ([]genetlink.Message, error) { ae := netlink.NewAttributeEncoder() @@ -614,8 +614,15 @@ func (p *PHY) parseBandAttributes(nlband netlink.Attribute) error { case unix.NL80211_BAND_ATTR_VHT_CAPA: ba.VHTCapabilities = decodeVHTCapabilities(ba.VHTCapabilities, nlenc.Uint32(attr.Data)) case unix.NL80211_BAND_ATTR_HT_MCS_SET: + if ba.HTCapabilities == nil { + ba.HTCapabilities = new(HTCapabilities) + } + copy(ba.HTCapabilities.SupportedMCS[:], attr.Data) case unix.NL80211_BAND_ATTR_VHT_MCS_SET: - // TODO Handle these + if ba.VHTCapabilities == nil { + ba.VHTCapabilities = new(VHTCapabilities) + } + copy(ba.VHTCapabilities.SupportedMCS[:], attr.Data) case unix.NL80211_BAND_ATTR_RATES: nattrs, err := netlink.UnmarshalAttributes(attr.Data) @@ -682,7 +689,7 @@ func (p *PHY) parseBandAttributes(nlband netlink.Attribute) error { } // decodeHTCapabilities parses a 16-bit integer into an HTCapabilities struct -// based on information from an HT Capabilities Info field (BandAttrHtCapa). +// based on information from an HT Capabilities Info field (NL80211_BAND_ATTR_HT_CAPA). // Create a new one if nil is passed in, but allow for the struct to have other // fields already set. func decodeHTCapabilities(htcap *HTCapabilities, cap uint16) *HTCapabilities { @@ -705,6 +712,11 @@ func decodeHTCapabilities(htcap *HTCapabilities, cap uint16) *HTCapabilities { return htcap } + +// decodeVHTCapabilities parses a 32-bit integer into an VHTCapabilities struct +// based on information from an VHT Capabilities Info field (NL80211_BAND_ATTR_VHT_CAPA). +// Create a new one if nil is passed in, but allow for the struct to have other +// fields already set. func decodeVHTCapabilities(vhtcap *VHTCapabilities, cap uint32) *VHTCapabilities { if vhtcap == nil { vhtcap = new(VHTCapabilities) @@ -737,7 +749,7 @@ func decodeVHTCapabilities(vhtcap *VHTCapabilities, cap uint32) *VHTCapabilities vhtcap.RXAntennaPattern = cap&(1<<28) != 0 vhtcap.TXAntennaPattern = cap&(1<<29) != 0 vhtcap.ExtendedNSSBW = int((cap >> 30) & 0x7) - fmt.Printf("%d\n", cap) + return vhtcap } diff --git a/wifi.go b/wifi.go index 361ab39..604a14f 100644 --- a/wifi.go +++ b/wifi.go @@ -360,6 +360,11 @@ type HTCapabilities struct { // Maximum receivable A-MPDU (Aggregated MAC Protocol Data Unit) frame // size. MaxRxAMPDULength int + + // Supported MCS for HT mode + // Todo: + // - Parse them are according to Section 7.3.2.56.4 IEEE 80211n + SupportedMCS [16]byte } // VHTCapabilities represents 802.11ac (Very High Throughput) capabilities. @@ -432,6 +437,11 @@ type VHTCapabilities struct { //Indicates whether the STA is capable of interpreting the Extended NSS BW //Support subfield of the VHT Capabilities Information field. ExtendedNSSBW int + + // Supported MCS for VHT mode + // Todo: + // - Parse them according to Section 8.4.2.160.3 IEEE Std 80211ac-2013 + SupportedMCS [8]byte } // FrequencyAttrs represents the attributes of a WiFi frequency/channel. From 5dc8f40a09290706da920a887466b53d8e63c2f5 Mon Sep 17 00:00:00 2001 From: supernovatux Date: Sat, 4 Jan 2025 23:17:59 +0530 Subject: [PATCH 5/5] Update client_others --- client_others.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client_others.go b/client_others.go index 84eebe2..f0ab586 100644 --- a/client_others.go +++ b/client_others.go @@ -20,6 +20,8 @@ func newClient() (*client, error) { return nil, errUnimplemented } func (*client) Close() error { return errUnimplemented } func (*client) Interfaces() ([]*Interface, error) { return nil, errUnimplemented } +func (c *client) PHY(_ uint32) (*PHY, error) { return nil, errUnimplemented } +func (c *client) PHYs() ([]*PHY, error) { return nil, errUnimplemented } func (*client) BSS(_ *Interface) (*BSS, error) { return nil, errUnimplemented } func (*client) StationInfo(_ *Interface) ([]*StationInfo, error) { return nil, errUnimplemented } func (*client) Connect(_ *Interface, _ string) error { return errUnimplemented }