blob: da9a5961cfe9b4ef30c9f8bde470647ce4001f8d [file]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package networkui
import (
"context"
"time"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/emptypb"
"go.chromium.org/tast-tests/cros/common/tbdep"
"go.chromium.org/tast-tests/cros/common/wifi/security/wpa"
"go.chromium.org/tast-tests/cros/remote/wificell"
"go.chromium.org/tast-tests/cros/remote/wificell/hostapd"
"go.chromium.org/tast-tests/cros/services/cros/networkui"
"go.chromium.org/tast-tests/cros/services/cros/ui"
"go.chromium.org/tast-tests/cros/services/cros/wifi"
"go.chromium.org/tast/core/ctxutil"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/rpc"
"go.chromium.org/tast/core/testing"
)
const (
connectToKnownNetworkTimeout = 30 * time.Second // Waiting for the known network to be connected could take a while.
configureProxySettingsTimeout = 30 * time.Second // Configure proxy is a series of UI operations and it could take a while.
)
func init() {
testing.AddTest(&testing.Test{
Func: SetProxyForRememberedNetwork,
// This test requires fetch proxy configs from a web page (CrosNetworkConfig),
// however, that page isn't available in lacros, only ash-Chrome is able to browse the page,
// therefore the lacros variant is not needed.
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Verify that proxy settings can be set for shared or non-shared network",
Contacts: []string{
"alfredyu@cienet.com",
"chromeos-connectivity-cienet-external@google.com",
},
BugComponent: "b:1578688", // ChromeOS > External > Cienet > Manual Test Automation > Test stabilization
LifeCycleStage: testing.LifeCycleInDevelopment,
Attr: []string{"group:wificell", "wificell_e2e"},
TestBedDeps: []string{tbdep.Wificell, tbdep.WifiStateNormal, tbdep.BluetoothStateNormal, tbdep.PeripheralWifiStateWorking},
VarDeps: []string{"ui.signinProfileTestExtensionManifestKey"},
ServiceDeps: []string{
wificell.ShillServiceName,
"tast.cros.wifi.WifiService",
"tast.cros.browser.ChromeService",
"tast.cros.networkui.ProxySettingService",
"tast.cros.networkui.CrosNetworkConfigService",
"tast.cros.ui.ChromeUIService",
},
SoftwareDeps: []string{"chrome"},
Fixture: wificell.FixtureID(wificell.TFFeaturesRouters),
// This test contains 3 sub-tests, all of them having 2 steps that consume the
// majority of the time: configuring proxies and connecting to the network.
// Each sub-test configures proxy for up to 2 times and connects to up to 1 other network.
Timeout: 5*time.Minute + 3*(connectToKnownNetworkTimeout+2*configureProxySettingsTimeout),
})
}
// SetProxyForRememberedNetwork verifies that proxy settings can be set for shared or non-shared network.
func SetProxyForRememberedNetwork(ctx context.Context, s *testing.State) {
tf := s.FixtValue().(*wificell.TestFixture)
rpcClient := tf.DUTRPC(wificell.DefaultDUT)
helper := newNetworksHelper(rpcClient, tf)
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 10*time.Second)
defer cancel()
crSvc := ui.NewChromeServiceClient(rpcClient.Conn)
if _, err := crSvc.New(ctx, &ui.NewRequest{}); err != nil {
s.Fatal("Failed to start Chrome: ", err)
}
defer crSvc.Close(cleanupCtx, &emptypb.Empty{})
defer tf.DeconfigAllAPs(cleanupCtx)
for _, cfg := range []struct {
prefix ssidPrefix
// options is the configuration to start hostapd on a router.
// Note that `hostapd.Channel` is essential and it is suggested to have only one network on a channel (b/320811867#comment3).
options []hostapd.Option
shared bool
// routerID is the index of the router to set up the network for.
// Note that it is suggested to configure a maximum of two networks on a router (b/320811867#comment3).
routerID wificell.RouterIdx
}{
{
prefix: sharedAndActive,
options: []hostapd.Option{
hostapd.Mode(hostapd.Mode80211g),
hostapd.Channel(1),
},
shared: true,
routerID: 0,
}, {
prefix: shared,
options: []hostapd.Option{
hostapd.Mode(hostapd.Mode80211nPure),
hostapd.HTCaps(hostapd.HTCapHT20),
hostapd.Channel(48),
},
shared: true,
routerID: 0,
}, {
prefix: nonShared,
options: []hostapd.Option{
hostapd.Mode(hostapd.Mode80211g),
hostapd.Channel(1),
},
shared: false,
routerID: 1,
},
} {
if err := helper.configureNetwork(ctx, tf, rpcClient, cfg.prefix, cfg.options, cfg.shared, cfg.routerID); err != nil {
s.Fatalf("Failed to configure the %q network: %v", cfg.prefix, err)
}
}
wifiSvc := wifi.NewWifiServiceClient(rpcClient.Conn)
// All added networks should be listed in known network.
if _, err := wifiSvc.KnownNetworksControls(ctx, &wifi.KnownNetworksControlsRequest{
Ssids: helper.allNetworkSSID(),
Control: wifi.KnownNetworksControlsRequest_WaitUntilExist},
); err != nil {
s.Fatal("Failed to ensure all added networks are listed in known networks: ", err)
}
// Connect to the network that the test expects to be active.
if err := helper.connectTo(ctx, helper.aps[sharedAndActive].Config().SSID); err != nil {
s.Fatal("Failed to ensure the active network is active: ", err)
}
for _, test := range []struct {
description string
proxiesForSharedNetwork bool
}{
{
description: `user can set proxies for non-shared networks only when "Allow proxies for shared networks" toggle is off`,
proxiesForSharedNetwork: false, // Not allow.
}, {
description: `user can set proxies for shared and non-shared network when "Allow proxies for shared networks" toggle is on`,
proxiesForSharedNetwork: true, // Allow.
}, {
description: `user can not set proxies for shared networks when "Allow proxies for shared networks" toggle is back to off`,
proxiesForSharedNetwork: false, // Not allow.
},
} {
s.Run(ctx, test.description, func(ctx context.Context, s *testing.State) {
if err := helper.toggleAllowProxiesForSharedNetworks(ctx, rpcClient, test.proxiesForSharedNetwork); err != nil {
s.Fatal(`Failed to toggle the "Allow proxies for shared networks" option: `, err)
}
if err := checkWhetherProxiesAreConfigurable(ctx, rpcClient, helper, test.proxiesForSharedNetwork); err != nil {
s.Fatal("Failed to check whether proxies are configurable: ", err)
}
})
}
}
func checkWhetherProxiesAreConfigurable(ctx context.Context, rpcClient *rpc.Client, helper *networksHelper, allowProxies bool) error {
proxy := wificell.DefaultProxyConfig("")
differentProxy := wificell.DefaultProxyConfig("")
// The trailing "9" is a random choice, not an important value as it's just meant to make this proxy different from `defaultProxy`.
differentProxy.HttpPort += "9"
if allowProxies {
// Verify that user can set proxies for shared networks.
ssid := helper.aps[shared].Config().SSID
connectToNetworkBeforeVerify := false
if err := setProxyAndVerify(ctx, rpcClient, helper, proxy, ssid, connectToNetworkBeforeVerify); err != nil {
return errors.Wrap(err, "failed to verify proxy settings can be set correctly")
}
// Verify that user can set proxies for non-shared networks.
ssid = helper.aps[nonShared].Config().SSID
// This is a mimic of manual testing, do an end-to-end connection for this specific
// network before checking the proxy to see if the proxy is correctly applied.
connectToNetworkBeforeVerify = true
if err := setProxyAndVerify(ctx, rpcClient, helper, differentProxy, ssid, connectToNetworkBeforeVerify); err != nil {
return errors.Wrap(err, "failed to verify proxy settings can be set correctly")
}
// Restore the active network back to be active.
if err := helper.connectTo(ctx, helper.aps[sharedAndActive].Config().SSID); err != nil {
return errors.Wrap(err, "failed to ensure the active network is active")
}
} else {
// User can set proxies for non-shared networks only and the proxy settings section should be restricted.
if err := proxySettingsRestricted(ctx, rpcClient, []string{helper.aps[shared].Config().SSID}); err != nil {
return errors.Wrap(err, "failed to verify the proxy settings section is restricted")
}
// Verify that user can set proxies for non-shared networks.
ssid := helper.aps[nonShared].Config().SSID
connectToNetworkBeforeVerify := false
if err := setProxyAndVerify(ctx, rpcClient, helper, proxy, ssid, connectToNetworkBeforeVerify); err != nil {
return errors.Wrap(err, "failed to verify proxy settings can be set correctly")
}
}
return nil
}
func proxySettingsRestricted(ctx context.Context, rpcClient *rpc.Client, ssids []string) error {
proxySvc := networkui.NewProxySettingServiceClient(rpcClient.Conn)
for _, ssid := range ssids {
if resp, err := proxySvc.IsProxySettingsRestricted(ctx, &networkui.IsProxySettingsRestrictedRequest{
NetworkInfo: &networkui.NetworkInfo{
Value: &networkui.NetworkInfo_WifiSsid{WifiSsid: ssid},
},
LoginMode: networkui.LoginMode_LoggedIn,
}); err != nil {
errors.Wrapf(err, "failed to check if proxy settings of network %q is restricted", ssid)
} else if !resp.Value {
return errors.New("proxy settings is not restricted")
}
}
return nil
}
// setProxyAndVerify sets up the proxy settings via the ossettings network detail page,
// and checks if the setup is successful by verifying CrosNetworkConfig.
func setProxyAndVerify(ctx context.Context, rpcClient *rpc.Client, helper *networksHelper, proxyConfig *networkui.ProxyConfigs, ssid string, connectToNetworkBeforeVerify bool) error {
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 5*time.Second)
defer cancel()
proxySvc := networkui.NewProxySettingServiceClient(rpcClient.Conn)
defer proxySvc.ResetConnectionType(cleanupCtx, &networkui.ResetConnectionTypeRequest{
NetworkInfo: &networkui.NetworkInfo{
Value: &networkui.NetworkInfo_WifiSsid{WifiSsid: ssid},
},
LoginMode: networkui.LoginMode_LoggedIn,
})
proxyConfig.NetworkInfo.Value = &networkui.NetworkInfo_WifiSsid{WifiSsid: ssid}
if _, err := proxySvc.SetProxySettings(ctx, &networkui.SetProxySettingsRequest{
Configs: proxyConfig,
LoginMode: networkui.LoginMode_LoggedIn,
}); err != nil {
return errors.Wrap(err, "failed to set up proxy settings")
}
if connectToNetworkBeforeVerify {
if err := helper.connectTo(ctx, ssid); err != nil {
return errors.Wrapf(err, "failed to connect to %q", ssid)
}
}
resp, err := proxySvc.FetchProxySettings(ctx, &networkui.FetchProxySettingsRequest{
NetworkInfo: &networkui.NetworkInfo{
Value: &networkui.NetworkInfo_WifiSsid{WifiSsid: ssid},
},
FetchSource: networkui.FetchProxySettingsRequest_CrosNetworkConfig,
LoginMode: networkui.LoginMode_LoggedIn,
})
if err != nil {
return errors.Wrap(err, "failed to fetch proxy settings")
}
if diff := cmp.Diff(resp, proxyConfig, protocmp.Transform()); diff != "" {
return errors.Errorf("unexpected proxy values (-want +got): %s", diff)
}
return nil
}
type ssidPrefix string
const (
// The main test subject, a network that is not shared (and also not active).
nonShared ssidPrefix = "Network_O_NonShared_"
// The main test subject, a network that is shared (and also not active).
shared ssidPrefix = "Network_N_Shared_"
// The network that will and should remain as active so that other test subjects are
// not active (while setting up the proxy).
// This network will also be used for toggling the "Allow proxies for shared networks".
sharedAndActive ssidPrefix = "Network_M_SharedAndActive_"
)
type networksHelper struct {
aps map[ssidPrefix]*wificell.APIface
rpcClient *rpc.Client
tf *wificell.TestFixture
}
func newNetworksHelper(rpcClient *rpc.Client, tf *wificell.TestFixture) *networksHelper {
return &networksHelper{
aps: make(map[ssidPrefix]*wificell.APIface),
rpcClient: rpcClient,
tf: tf,
}
}
func (helper *networksHelper) allNetworkSSID() []string {
return []string{helper.aps[sharedAndActive].Config().SSID, helper.aps[shared].Config().SSID, helper.aps[nonShared].Config().SSID}
}
func (helper *networksHelper) configureNetwork(ctx context.Context, tf *wificell.TestFixture, rpcClient *rpc.Client, prefix ssidPrefix, apOpts []hostapd.Option, shared bool, routerID wificell.RouterIdx) error {
const testPass string = "chromeos"
securityConfig := wpa.NewConfigFactory(testPass, wpa.Mode(wpa.ModePureWPA2), wpa.Ciphers2(wpa.CipherCCMP, wpa.CipherTKIP))
netConfigSvc := networkui.NewCrosNetworkConfigServiceClient(rpcClient.Conn)
apOpts = append(apOpts, hostapd.SSID(hostapd.RandomSSID(string(prefix))))
ap, err := tf.ConfigureAPOnRouterID(ctx, routerID, apOpts, securityConfig, false /* enableDNS */, false /* enableHTTP */)
if err != nil {
return errors.Wrap(err, "failed to configure AP")
}
helper.aps[prefix] = ap
wifiConfigProperties := &networkui.WiFiConfigProperties{
Ssid: ap.Config().SSID,
HiddenSsidMode: networkui.HiddenSSIDMode_Disabled,
Security: &networkui.WiFiConfigProperties_Psk{
Psk: testPass,
},
}
networkTypeConfigProperties := &networkui.NetworkTypeConfigProperties{
ConfigProperties: &networkui.NetworkTypeConfigProperties_WifiConfigProperties{
WifiConfigProperties: wifiConfigProperties,
},
}
if _, err := netConfigSvc.ConfigureNetwork(ctx, &networkui.ConfigureNetworkRequest{
Shared: shared,
ConfigProperties: networkTypeConfigProperties,
}); err != nil {
return err
}
return nil
}
func (helper *networksHelper) connectTo(ctx context.Context, ssid string) error {
wifiSvc := wifi.NewWifiServiceClient(helper.rpcClient.Conn)
if _, err := wifiSvc.KnownNetworksControls(ctx, &wifi.KnownNetworksControlsRequest{
Ssids: []string{ssid},
Control: wifi.KnownNetworksControlsRequest_Connect,
}); err != nil {
return errors.Wrapf(err, "failed to connect to %q", ssid)
}
return helper.tf.DUTWifiClient(wificell.DefaultDUT).WaitForConnected(ctx, ssid, true /* expectedValue */)
}
func (helper *networksHelper) toggleAllowProxiesForSharedNetworks(ctx context.Context, rpcClient *rpc.Client, isAllow bool) error {
// The "Allow proxies for shared networks" is a shared option across all shared networks,
// toggling it in one network will allow/disallow proxies for all shared networks.
ssid := helper.aps[sharedAndActive].Config().SSID
proxySvc := networkui.NewProxySettingServiceClient(rpcClient.Conn)
if _, err := proxySvc.AllowProxiesForSharedNetwork(ctx, &networkui.AllowProxiesForSharedNetworkRequest{
Allow: isAllow,
NetworkInfo: &networkui.NetworkInfo{
Value: &networkui.NetworkInfo_WifiSsid{WifiSsid: ssid},
},
LoginMode: networkui.LoginMode_LoggedIn,
}); err != nil {
return errors.Wrap(err, `failed to toggle "Allow proxies for shared networks"`)
}
return nil
}