blob: c207b53b3a7d78a72161575ef16cfd2fc48471e2 [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/common/crypto/certificate"
"chromiumos/tast/common/shillconst"
"chromiumos/tast/common/wifi/security/wpa"
"chromiumos/tast/common/wifi/security/wpaeap"
"chromiumos/tast/remote/wificell"
"chromiumos/tast/remote/wificell/hostapd"
"chromiumos/tast/services/cros/wifi"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: PMKSACaching,
Desc: "Verifies that 802.1x authentication (EAP exchange) is bypassed and PMKSA is done using PMK caching when it is available",
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 PMKSACaching(ctx context.Context, s *testing.State) {
const (
ap0Channel = 1
ap1Channel = 44
roamTimeout = 30 * time.Second
)
// Generate BSSIDs for the two APs.
mac0, err := hostapd.RandomMAC()
if err != nil {
s.Fatal("Failed to generate BSSID: ", err)
}
mac1, err := hostapd.RandomMAC()
if err != nil {
s.Fatal("Failed to generate BSSID: ", err)
}
ap0BSSID := mac0.String()
ap1BSSID := mac1.String()
tf := s.FixtValue().(*wificell.TestFixture)
cert := certificate.TestCert1()
secConfFac := wpaeap.NewConfigFactory(
cert.CACred.Cert, cert.ServerCred,
wpaeap.ClientCACert(cert.CACred.Cert),
wpaeap.ClientCred(cert.ClientCred),
// PMKSA caching is only defined for WPA2.
wpaeap.Mode(wpa.ModePureWPA2),
)
ssid := hostapd.RandomSSID("TAST_TEST_")
configureAP := func(ctx context.Context, channel int, bssid string) (*wificell.APIface, error) {
apOps := []hostapd.Option{
hostapd.SSID(ssid), hostapd.BSSID(bssid), hostapd.Mode(hostapd.Mode80211nPure),
hostapd.Channel(channel), hostapd.HTCaps(hostapd.HTCapHT20),
}
return tf.ConfigureAP(ctx, apOps, secConfFac)
}
roamProps := func(bssid string) []*wificell.ShillProperty {
return []*wificell.ShillProperty{{
Property: shillconst.ServicePropertyWiFiBSSID,
ExpectedValues: []interface{}{bssid},
Method: wifi.ExpectShillPropertyRequest_ON_CHANGE,
}}
}
ap0, err := configureAP(ctx, ap0Channel, ap0BSSID)
if err != nil {
s.Fatal("Failed to configure AP0: ", err)
}
defer func(ctx context.Context) {
if err := tf.DeconfigAP(ctx, ap0); err != nil {
s.Error("Failed to deconfig AP0: ", err)
}
}(ctx)
ctx, cancel := tf.ReserveForDeconfigAP(ctx, ap0)
defer cancel()
s.Log("AP0 setup done; connecting")
connResp, err := tf.ConnectWifiAP(ctx, ap0)
if err != nil {
s.Fatal("Failed to connect to WiFi: ", err)
}
defer func(ctx context.Context) {
if err := tf.CleanDisconnectWifi(ctx); err != nil {
s.Error("Failed to disconnect WiFi: ", err)
}
}(ctx)
ctx, cancel = tf.ReserveForDisconnect(ctx)
defer cancel()
if err := tf.PingFromDUT(ctx, ap0.ServerIP().String()); err != nil {
s.Fatal("Failed to ping from the DUT: ", err)
}
// ap1Ctx should only be used by configure/deconfigure AP1.
ap1Ctx := ctx
// Reserve time for deconfig ap1. Note that ap1 should be created after the waitForRoam property watcher, we borrow ap0 to reserve time for DeconfigAP.
ctx, cancel = tf.ReserveForDeconfigAP(ctx, ap0)
defer cancel()
roamCtx, cancel := context.WithTimeout(ctx, roamTimeout)
defer cancel()
waitForRoam, err := tf.WifiClient().ExpectShillProperty(roamCtx, connResp.ServicePath, roamProps(ap1BSSID), nil)
if err != nil {
s.Fatal("Failed to create a property watcher on DUT: ", err)
}
skippedRecver, err := tf.WifiClient().EAPAuthSkipped(roamCtx)
if err != nil {
s.Fatal("Failed to create a EAP authentication watcher: ", err)
}
// Configure AP1 after ExpectShillProperty() because a roaming may happen automatically right after AP1 is up.
ap1, err := configureAP(ap1Ctx, ap1Channel, ap1BSSID)
if err != nil {
s.Fatal("Failed to configure AP1: ", err)
}
defer func(ctx context.Context) {
if ap1 == nil {
// AP1 is already closed.
return
}
if err := tf.DeconfigAP(ctx, ap1); err != nil {
s.Error("Failed to deconfig AP1: ", err)
}
}(ap1Ctx)
iface, err := tf.ClientInterface(roamCtx)
if err != nil {
s.Fatal("Failed to get interface from DUT: ", err)
}
if err := tf.WifiClient().DiscoverBSSID(roamCtx, ap1BSSID, iface, []byte(ssid)); err != nil {
s.Fatal("Failed to discover AP1's BSSID: ", err)
}
if err := tf.WifiClient().RequestRoam(roamCtx, iface, ap1BSSID, 5*time.Second); err != nil {
s.Errorf("Failed to roam from %s to %s: %v", ap0BSSID, ap1BSSID, err)
}
s.Log("Waiting for roaming to AP1")
if _, err := waitForRoam(); err != nil {
s.Fatal("Failed to wait for roaming to AP1: ", err)
}
skipped, err := skippedRecver()
if err != nil {
s.Fatal("Failed to wait for confirming skipping EAP authentication: ", err)
}
if skipped {
s.Error("EAP authentication is skipped when connecting to a new AP")
} else {
s.Log("EAP authentication is not skipped as expected")
}
roamCtx, cancel = context.WithTimeout(ctx, roamTimeout)
defer cancel()
waitForRoam, err = tf.WifiClient().ExpectShillProperty(roamCtx, connResp.ServicePath, roamProps(ap0BSSID), nil)
if err != nil {
s.Fatal("Failed to create a property watcher on DUT: ", err)
}
skippedRecver, err = tf.WifiClient().EAPAuthSkipped(roamCtx)
if err != nil {
s.Fatal("Failed to create a EAP authentication watcher: ", err)
}
if err := tf.DeconfigAP(roamCtx, ap1); err != nil {
s.Fatal("Failed to deconfig AP: ", err)
}
ap1 = nil
s.Log("Waiting for falling back to AP0")
if _, err := waitForRoam(); err != nil {
s.Fatal("Failed to wait for falling back to AP0: ", err)
}
skipped, err = skippedRecver()
if err != nil {
s.Fatal("Failed to wait for confirming skipping EAP authentication: ", err)
}
if !skipped {
s.Error("EAP authentication is not skipped while expecting the PMKSA is cached")
} else {
s.Log("EAP authentication is skipped as expected")
}
// Shill may keep IsConnected during the whole roaming process if wpa_supplicant didn't explicitly tell that the DUT has disconnected.
// However, we may still lose the connectivity for a while after roaming to the other BSSID. Use polling to verify the connection.
if err := testing.Poll(ctx, func(ctx context.Context) error {
return tf.VerifyConnection(ctx, ap0)
}, &testing.PollOptions{
Timeout: time.Second * 20,
Interval: time.Second,
}); err != nil {
s.Error("Failed to wait for the connection to recover: ", err)
}
}