blob: a3c8ed1dec5e4bd5999663ac54fda31a1a9ae0fd [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package wifi
import (
"bytes"
"context"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"chromiumos/tast/errors"
"chromiumos/tast/remote/bundles/cros/wifi/wifiutil"
"chromiumos/tast/remote/network/ip"
"chromiumos/tast/remote/wificell"
"chromiumos/tast/remote/wificell/hostapd"
"chromiumos/tast/remote/wificell/pcap"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: ConnectMBO,
Desc: "Verifies that the MBO IE and other MBO-related capability bits are set",
Contacts: []string{
"chromeos-wifi-champs@google.com", // WiFi oncall rotation; or http://b/new?component=893827
},
Attr: []string{"group:wificell", "wificell_func"},
ServiceDeps: []string{wificell.TFServiceName},
Fixture: "wificellFixtRouterAsPcap",
SoftwareDeps: []string{"mbo", "rrm_support"},
Params: []testing.Param{
{
ExtraHardwareDeps: hwdep.D(hwdep.WifiNotMarvell()),
Val: true,
},
{
Name: "marvell",
ExtraHardwareDeps: hwdep.D(hwdep.WifiMarvell()),
Val: false,
},
},
})
}
func ConnectMBO(ctx context.Context, s *testing.State) {
tf := s.FixtValue().(*wificell.TestFixture)
ctx, restore, err := tf.WifiClient().DisableMACRandomize(ctx)
if err != nil {
s.Fatal("Failed to disable MAC randomization: ", err)
}
defer func() {
if err := restore(); err != nil {
s.Error("Failed to restore MAC randomization: ", err)
}
}()
// Get the MAC address of WiFi interface.
iface, err := tf.ClientInterface(ctx)
if err != nil {
s.Fatal("Failed to get WiFi interface of DUT: ", err)
}
ipr := ip.NewRemoteRunner(s.DUT().Conn())
mac, err := ipr.MAC(ctx, iface)
if err != nil {
s.Fatal("Failed to get MAC of WiFi interface: ", err)
}
apOps := []hostapd.Option{
hostapd.MBO(),
hostapd.RRMBeaconReport(),
hostapd.Channel(1),
hostapd.Mode(hostapd.Mode80211acMixed),
hostapd.HTCaps(hostapd.HTCapHT40),
hostapd.VHTChWidth(hostapd.VHTChWidth20Or40),
}
pcapPath, _, err := wifiutil.ConnectAndCollectPcap(ctx, tf, apOps)
if err != nil {
s.Fatal("Failed to collect packet: ", err)
}
s.Log("Start analyzing pcap")
filters := []pcap.Filter{
pcap.Dot11FCSValid(),
pcap.TransmitterAddress(mac),
}
probePackets, err := pcap.ReadPackets(pcapPath, append(filters, pcap.TypeFilter(layers.LayerTypeDot11MgmtProbeReq, nil))...)
if err != nil {
s.Fatal("Failed to read probe request packets: ", err)
}
s.Logf("Total %d probe requests found", len(probePackets))
assocPackets, err := pcap.ReadPackets(pcapPath, append(filters, pcap.TypeFilter(layers.LayerTypeDot11MgmtAssociationReq, nil))...)
if err != nil {
s.Fatal("Failed to read association request packets: ", err)
}
s.Logf("Total %d assoc requests found", len(assocPackets))
checkIEs := func(p gopacket.Packet, isProbe bool) error {
containsExt := false
containsMBO := false
containsRM := false
for _, l := range p.Layers() {
element, ok := l.(*layers.Dot11InformationElement)
if !ok {
continue
}
if element.ID == layers.Dot11InformationElementIDExtCapability {
containsExt = true
if int(element.Length) < 3 {
return errors.New("Extended Capability IE not long enough")
}
if (element.Info[2] & 0x08) == 0 {
return errors.New("Extended Capability IE does not contain BSS Transition capability")
}
}
if element.ID == layers.Dot11InformationElementIDVendor {
if int(element.Length) < 7 ||
bytes.Compare(element.OUI[:3], []byte{0x50, 0x6F, 0x9A}) != 0 ||
element.OUI[3] != 0x16 {
continue
}
for i := 0; i < len(element.Info); {
attrID := element.Info[i]
attrLen := element.Info[i+1]
// Check that the Cellular Data Capabilities attribute is present
if attrID == 0x03 && attrLen == 1 && element.Info[i+2] >= 0x01 && element.Info[i+2] <= 0x03 {
containsMBO = true
break
}
i += 2 + int(attrLen)
}
}
if element.ID == layers.Dot11InformationElementIDRMEnabledCapabilities {
containsRM = true
if int(element.Length) < 1 {
return errors.New("RM Enabled Capabilities IE not long enough")
}
if (element.Info[0] & 0x10) == 0 {
return errors.New("RM Enabled Capabilities IE missing Passive Measurement support")
}
if (element.Info[0] & 0x20) == 0 {
return errors.New("RM Enabled Capabilities IE missing Active Measurement support")
}
if (element.Info[0] & 0x40) == 0 {
return errors.New("RM Enabled Capabilities IE missing Table Measurement support")
}
}
}
if !containsExt {
return errors.New("Extended Capabilities IE missing")
} else if !containsMBO {
return errors.New("MBO-OCE IE missing")
} else if !isProbe && !containsRM {
return errors.New("RM Enabled Capabilities IE missing")
}
return nil
}
s.Log("Checking probe request packets")
for _, p := range probePackets {
layer := p.Layer(layers.LayerTypeDot11MgmtProbeReq)
if layer == nil {
s.Fatal("Found packet without ProbeReq layer")
}
req := layer.(*layers.Dot11MgmtProbeReq)
content := req.LayerContents()
e := gopacket.NewPacket(content, layers.LayerTypeDot11InformationElement, gopacket.NoCopy)
if err := e.ErrorLayer(); err != nil {
s.Log("Error: ", err)
continue
}
if err := checkIEs(e, true); err != nil {
s.Fatal("Probe request IEs missing: ", err)
}
}
// We skip the assoc request packet check for Marvell devices because
// they are fullMAC devices, meaning wpa_supplicant can't inject the MBO
// IEs into the assoc request packet like it does in softMAC devices.
// We still check to make sure it can associate properly above, but it's
// less important to check that the IEs are there.
notMarvell := s.Param().(bool)
if notMarvell {
s.Log("Checking assoc request packets")
for _, p := range assocPackets {
if err := checkIEs(p, false); err != nil {
s.Fatal("Assoc request IEs missing: ", err)
}
}
}
}