| // Copyright 2021 The ChromiumOS Authors |
| // 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" |
| "go.chromium.org/tast-tests/cros/common/tbdep" |
| |
| tdreq "go.chromium.org/tast-tests/cros/common/testdevicerequirements" |
| "go.chromium.org/tast-tests/cros/remote/bundles/cros/wifi/wifiutil" |
| "go.chromium.org/tast-tests/cros/remote/network/ip" |
| "go.chromium.org/tast-tests/cros/remote/wificell" |
| "go.chromium.org/tast-tests/cros/remote/wificell/hostapd" |
| "go.chromium.org/tast-tests/cros/remote/wificell/pcap" |
| "go.chromium.org/tast/core/testing" |
| "go.chromium.org/tast/core/testing/hwdep" |
| ) |
| |
| type supportedRatesCase struct { |
| apOpts []hostapd.Option |
| } |
| |
| func init() { |
| testing.AddTest(&testing.Test{ |
| Func: APSupportedRates, |
| Desc: "Verifies that we avoid legacy bitrates on APs that disable them", |
| Contacts: []string{ |
| "chromeos-wifi-champs@google.com", // WiFi oncall rotation |
| }, |
| BugComponent: "b:893827", // ChromeOS > Platform > Connectivity > WiFi |
| Attr: []string{"group:wificell", "wificell_func"}, |
| TestBedDeps: []string{tbdep.Wificell, tbdep.WifiStateNormal, tbdep.BluetoothStateNormal, tbdep.PeripheralWifiStateWorking}, |
| ServiceDeps: []string{wificell.ShillServiceName}, |
| Fixture: wificell.FixtureID(wificell.TFFeaturesNone), |
| // Low flake rate for Marvell devices that are trending towards AUE. Skip on those platforms. |
| HardwareDeps: hwdep.D(hwdep.WifiNotMarvell()), |
| Requirements: []string{tdreq.WiFiProcPassFW, tdreq.WiFiProcPassAVL, tdreq.WiFiProcPassAVLBeforeUpdates, tdreq.WiFiProcPassMatfunc, tdreq.WiFiProcPassMatfuncBeforeUpdates}, |
| VariantCategory: `{"name": "WifiBtChipset_Soc_Kernel"}`, |
| Params: []testing.Param{ |
| { |
| Name: "11g", |
| Val: supportedRatesCase{ |
| apOpts: []hostapd.Option{ |
| hostapd.Mode(hostapd.Mode80211g), hostapd.Channel(1), |
| hostapd.BasicRates(24.0), hostapd.SupportedRates(24.0, 36.0, 48.0, 54.0), |
| }, |
| }, |
| }, |
| { |
| Name: "11ac", |
| Val: supportedRatesCase{ |
| apOpts: []hostapd.Option{ |
| hostapd.Mode(hostapd.Mode80211acMixed), hostapd.Channel(157), hostapd.VHTCenterChannel(155), |
| hostapd.HTCaps(hostapd.HTCapHT40Plus), hostapd.VHTCaps(hostapd.VHTCapSGI80), hostapd.VHTChWidth(hostapd.VHTChWidth80), |
| hostapd.BasicRates(36.0), hostapd.SupportedRates(36.0, 48.0, 54.0), |
| }, |
| }, |
| }, |
| }, |
| }) |
| } |
| |
| func APSupportedRates(ctx context.Context, s *testing.State) { |
| tf := s.FixtValue().(*wificell.TestFixture) |
| |
| param := s.Param().(supportedRatesCase) |
| apOpts := param.apOpts |
| |
| ap, err := tf.ConfigureAP(ctx, apOpts, nil) |
| if err != nil { |
| s.Fatal("Failed to configure the AP: ", err) |
| } |
| defer func(ctx context.Context) { |
| if err := tf.DeconfigAP(ctx, ap); err != nil { |
| s.Error("Failed to deconfig the AP: ", err) |
| } |
| }(ctx) |
| ctx, cancel := tf.ReserveForDeconfigAP(ctx, ap) |
| defer cancel() |
| |
| // 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) |
| } |
| |
| // Operations to perform while monitoring via packet capture. |
| testAction := func(ctx context.Context) error { |
| cleanupCtx := ctx |
| ctx, cancel = tf.ReserveForDisconnect(ctx) |
| defer cancel() |
| |
| if _, err := tf.ConnectWifiAP(ctx, ap); err != nil { |
| return err |
| } |
| defer func(ctx context.Context) { |
| if err := tf.CleanDisconnectWifi(ctx); err != nil { |
| s.Error("Failed to disconnect WiFi: ", err) |
| } |
| }(cleanupCtx) |
| |
| if err := tf.PingFromDUT(ctx, ap.ServerIP().String()); err != nil { |
| s.Fatal("Failed to ping from the DUT: ", err) |
| } |
| |
| if err := tf.PingFromServer(ctx); err != nil { |
| s.Fatal("Failed to ping from the Server: ", err) |
| } |
| |
| return nil |
| } |
| |
| freqOpts, err := ap.Config().PcapFreqOptions() |
| if err != nil { |
| s.Fatal("Failed to get pcap freqency options: ", err) |
| } |
| standardPcap, err := tf.StandardPcapRouter() |
| if err != nil { |
| s.Fatal("Unable to get standard pcap: ", err) |
| } |
| pcapPath, err := wifiutil.CollectPcapForAction(ctx, standardPcap, "connect", ap.Config().Channel, false /*is6GHz*/, freqOpts, testAction) |
| if err != nil { |
| s.Fatal("Failed to collect pcap or perform action: ", err) |
| } |
| |
| s.Log("Start analyzing pcap") |
| filters := []pcap.Filter{ |
| // Use TA (not SA), because multicast may retransmit our |
| // "Source-Addressed" frames at rates we don't control. |
| pcap.TransmitterAddress(mac), |
| pcap.TypeFilter( |
| layers.LayerTypeDot11, |
| func(layer gopacket.Layer) bool { |
| dot11 := layer.(*layers.Dot11) |
| // Skip receiver == MAC. |
| // Some chips use self-addressed (receiver==self) frames |
| // to tune channel performance. They don't carry |
| // host-generated traffic, so filter them out. |
| if bytes.Equal(dot11.Address1, mac) { |
| return false |
| } |
| // Skip RTS. |
| // RTS: all nearby stations need to hear this (not just |
| // those on the current BSS), so a station can't respect only |
| // the current AP's rates. |
| // RTS frame has zero frame payload, gopacket stops parsing |
| // at LayerTypeDot11 thus no LayerTypeDot11CtrlRTS. |
| if dot11.Type == layers.Dot11TypeCtrlRTS { |
| return false |
| } |
| return true |
| }, |
| ), |
| // We skip a few frame types for various reasons: |
| // |
| // (QoS) null: these frames are short (no data payload), and it's more |
| // important that they be reliable (e.g., for PS transitions) than fast. See |
| // b/132825853#comment40, for example. |
| // |
| // Probe request: these frames are not associated with a particular BSS yet. |
| func(p gopacket.Packet) bool { |
| // Skip QoS null data. |
| if l := p.Layer(layers.LayerTypeDot11DataQOSNull); l != nil { |
| return false |
| } |
| // Skip null data. |
| if l := p.Layer(layers.LayerTypeDot11DataNull); l != nil { |
| return false |
| } |
| // Skip probe requests. |
| if l := p.Layer(layers.LayerTypeDot11MgmtProbeReq); l != nil { |
| return false |
| } |
| // Skip Action frames sent by the DUT. See b/261235103 for context. |
| // Against some APs, DUT may send action frames for time sync mechanism. |
| // This looks to be generally sent at the lowest data rate (6mbps on 5ghz band) |
| // Because these are management frames, we allow these to be sent at not the requested |
| // data rate. |
| // The 80211 spec doesn't dictate what rates these action frames must be sent |
| // at and many vendors will send these management frames at the lowest rate. |
| // The iwl7000 driver will explicitly send management frames at the lowest rate |
| // (6Mbps). |
| // See b/261235103#comment3 and b/261235103#comment4 for more details. |
| |
| if l := p.Layer(layers.LayerTypeDot11MgmtAction); l != nil { |
| return false |
| } |
| return true |
| }, |
| // TODO: skip BlockAcks, etc.? The original test did so (see |
| // https://crrev.com/c/1679995) because our test APs don't always (as of 2019-06-28) |
| // respect the Supported Rates IEs that we're configuring, and so DUT ACKs may match |
| // the (incorrect) rate that the AP is using. We may not want to penalize the DUT |
| // for that. |
| } |
| packets, err := pcap.ReadPackets(pcapPath, filters...) |
| if err != nil { |
| s.Fatal("Failed to read packets: ", err) |
| } |
| if len(packets) == 0 { |
| s.Fatal("No valid frames found in pcap") |
| } |
| s.Logf("Total %d candidate frames found", len(packets)) |
| |
| var bad []gopacket.Packet |
| badRates := make(map[float32]interface{}) |
| for _, p := range packets { |
| // Get sender address. |
| layer := p.Layer(layers.LayerTypeRadioTap) |
| if layer == nil { |
| // Not all frames will have radiotap? |
| continue |
| } |
| radioTap, ok := layer.(*layers.RadioTap) |
| if !ok { |
| s.Fatalf("RadioTap layer output %v not *layers.RadioTap", p) |
| } |
| if !radioTap.Present.Rate() { |
| // No rate? Might be non-legacy (e.g., HT), which is a "pass." |
| continue |
| } |
| // Rate field is in units of Mbps*2. |
| rate := float32(radioTap.Rate) / 2 |
| supportedRate := false |
| for _, r := range ap.Config().SupportedRates { |
| if rate == r { |
| supportedRate = true |
| break |
| } |
| } |
| if !supportedRate { |
| bad = append(bad, p) |
| badRates[rate] = true |
| } |
| } |
| |
| if len(bad) != 0 { |
| for i, p := range bad { |
| s.Logf("Bad frame %d: %v", i, p) |
| } |
| var list []float32 |
| for r := range badRates { |
| list = append(list, r) |
| } |
| s.Fatalf("Expected rates: %v; saw: %v", ap.Config().SupportedRates, list) |
| } |
| |
| s.Log("Verified; tearing down") |
| } |