blob: c3ea9ae97f9d1e1a43ae8da8a0e1797e0d070aeb [file] [log] [blame]
// Copyright 2020 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 (
"context"
"time"
"chromiumos/tast/ctxutil"
"chromiumos/tast/remote/network/iw"
remoteiw "chromiumos/tast/remote/network/iw"
"chromiumos/tast/remote/wificell"
"chromiumos/tast/remote/wificell/framesender"
"chromiumos/tast/remote/wificell/hostapd"
"chromiumos/tast/services/cros/wifi"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: CSALeaveChannel,
Desc: "Verifies that DUT will move off-channel after the AP sends a Spectrum Management action frame with a Channel Move element",
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: "wificellFixt",
})
}
func CSALeaveChannel(ctx context.Context, s *testing.State) {
// Note: Not all clients support CSA, but they generally should at least try
// to disconnect from the AP which is what the test expects to see.
tf := s.FixtValue().(*wificell.TestFixture)
router, err := tf.StandardRouterWithFrameSenderSupport()
if err != nil {
s.Fatal("Failed to get legacy router: ", err)
}
// TODO(b/154879577): Currently the action frames sent by FrameSender
// are not buffered for DTIM so if the DUT is in powersave mode, it
// cannot receive the action frame and the test will fail.
// Turn off powersave mode to replicate the behavior of Autotest in
// this test for now.
iwr := remoteiw.NewRemoteRunner(s.DUT().Conn())
iface, err := tf.ClientInterface(ctx)
if err != nil {
s.Fatal("Failed to get the client interface: ", err)
}
psMode, err := iwr.PowersaveMode(ctx, iface)
if err != nil {
s.Fatal("Failed to get the powersave mode: ", err)
}
if psMode {
defer func(ctx context.Context) {
s.Logf("Restoring power save mode to %t", psMode)
if err := iwr.SetPowersaveMode(ctx, iface, psMode); err != nil {
s.Errorf("Failed to restore powersave mode to %t: %v", psMode, err)
}
}(ctx)
var cancel context.CancelFunc
ctx, cancel = ctxutil.Shorten(ctx, time.Second)
defer cancel()
s.Log("Disabling power save in the test")
if err := iwr.SetPowersaveMode(ctx, iface, false); err != nil {
s.Fatal("Failed to turn off powersave: ", err)
}
}
apOps := []hostapd.Option{
hostapd.Mode(hostapd.Mode80211nMixed),
hostapd.Channel(64),
hostapd.HTCaps(hostapd.HTCapHT20),
}
ap, err := tf.ConfigureAP(ctx, apOps, nil)
if err != nil {
s.Fatal("Failed to configure AP: ", err)
}
defer func(ctx context.Context) {
if err := tf.DeconfigAP(ctx, ap); err != nil {
s.Error("Failed to deconfig AP: ", err)
}
}(ctx)
s.Log("AP setup done")
ctx, cancel := tf.ReserveForDeconfigAP(ctx, ap)
defer cancel()
if _, err := tf.ConnectWifiAP(ctx, ap); err != nil {
s.Fatal("Failed to connect to WiFi: ", err)
}
defer func(ctx context.Context) {
if err := tf.DisconnectWifi(ctx); err != nil {
// Do not fail on this error as we're triggering some
// disconnection in this test and the service can be
// inactive at this point.
s.Log("Failed to disconnect WiFi: ", err)
}
req := &wifi.DeleteEntriesForSSIDRequest{Ssid: []byte(ap.Config().SSID)}
if _, err := tf.WifiClient().DeleteEntriesForSSID(ctx, req); err != nil {
s.Errorf("Failed to remove entries for ssid=%s, err: %v", ap.Config().SSID, err)
}
}(ctx)
ctx, cancel = ctxutil.Shorten(ctx, 5*time.Second)
defer cancel()
s.Log("Connected")
// Assert connection.
if err := tf.PingFromDUT(ctx, ap.ServerIP().String()); err != nil {
s.Fatal("Failed to ping from DUT: ", err)
}
sender, err := router.NewFrameSender(ctx, ap.Interface())
if err != nil {
s.Fatal("Failed to create frame sender: ", err)
}
defer func(dCtx context.Context) {
if err := router.CloseFrameSender(dCtx, sender); err != nil {
s.Error("Failed to close frame sender: ", err)
}
}(ctx)
ctx, cancel = router.ReserveForCloseFrameSender(ctx)
defer cancel()
ew, err := iw.NewEventWatcher(ctx, s.DUT())
if err != nil {
s.Fatal("Failed to start iw.EventWatcher: ", err)
}
defer ew.Stop()
const maxRetry = 5
const alterChannel = 36
// Action frame might be lost, give it some retries.
for i := 0; i < maxRetry; i++ {
s.Logf("Try sending channel switch frame %d", i)
if err := sender.Send(ctx, framesender.TypeChannelSwitch, alterChannel); err != nil {
s.Fatal("Failed to send channel switch frame: ", err)
}
// The frame might need some time to reach DUT, wait for a few seconds.
wCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
// TODO(b/154879577): Find some way to know if DUT supports
// channel switch, and only wait for the proper event.
_, err := ew.WaitByType(wCtx, iw.EventTypeChanSwitch, iw.EventTypeDisconnect)
if err == context.DeadlineExceeded {
// Retry if deadline exceeded.
continue
}
if err != nil {
s.Fatal("Failed to wait for iw event: ", err)
}
// Channel switch or client disconnection detected, test passed.
return
}
s.Fatal("Client failed to disconnect or switch channel")
}