| // Copyright 2022 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 ( |
| "context" |
| "net" |
| "reflect" |
| "time" |
| |
| "go.chromium.org/tast-tests/cros/common/tbdep" |
| tdreq "go.chromium.org/tast-tests/cros/common/testdevicerequirements" |
| "go.chromium.org/tast-tests/cros/common/wifi/wpacli" |
| "go.chromium.org/tast-tests/cros/remote/bundles/cros/wifi/wifiutil" |
| "go.chromium.org/tast-tests/cros/remote/network/cmd" |
| "go.chromium.org/tast-tests/cros/remote/wificell" |
| "go.chromium.org/tast-tests/cros/remote/wificell/hostapd" |
| "go.chromium.org/tast/core/errors" |
| "go.chromium.org/tast/core/testing" |
| ) |
| |
| type anqpTestCase struct { |
| infos hostapd.VenueInfo |
| names []hostapd.VenueName |
| roamingConsortiums []string |
| domains []string |
| realms []hostapd.NAIRealm |
| } |
| |
| var ( |
| methodEapTLS = hostapd.EAPMethod{ |
| Type: hostapd.EAPMethodTypeTLS, |
| Params: []hostapd.EAPAuthParam{ |
| { |
| Type: hostapd.AuthParamCredential, |
| Value: hostapd.AuthCredentialsCertificate, |
| }, |
| }, |
| } |
| methodEapTTLS = hostapd.EAPMethod{ |
| Type: hostapd.EAPMethodTypeTTLS, |
| Params: []hostapd.EAPAuthParam{ |
| {Type: hostapd.AuthParamInnerNonEAP, Value: hostapd.AuthNonEAPAuthMSCHAPV2}, |
| {Type: hostapd.AuthParamCredential, Value: hostapd.AuthCredentialsUsernamePassword}, |
| }, |
| } |
| tlsAndTtlsRealm = hostapd.NAIRealm{ |
| Domains: []string{"example.com"}, |
| Encoding: hostapd.RealmEncodingRFC4282, |
| Methods: []hostapd.EAPMethod{ |
| methodEapTLS, |
| methodEapTTLS, |
| }, |
| } |
| ) |
| |
| func init() { |
| // ANQP test aims to verify a DUT is able to fetch ANQP data from an access |
| // point. A test access point is configured with a set of ANQP data, then |
| // the DUT fetches it using fetch_anqp. The test obtains the data from the |
| // DUT and verifies it is consistent with the access point configuration. |
| testing.AddTest(&testing.Test{ |
| Func: ANQP, |
| Desc: "Verifies that a DUT is able to perform ANQP requests and process replies", |
| Contacts: []string{"cros-networking@google.com", "damiendejean@chromium.org"}, |
| // ChromeOS > Platform > System > Networking > Continuous Maintenance |
| BugComponent: "b:1493959", |
| Attr: []string{"group:wificell", "wificell_func", "wificell_unstable"}, |
| TestBedDeps: []string{tbdep.Wificell, tbdep.WifiStateNormal, tbdep.BluetoothStateNormal, tbdep.PeripheralWifiStateWorking}, |
| ServiceDeps: []string{wificell.ShillServiceName}, |
| Fixture: wificell.FixtureID(wificell.TFFeaturesNone), |
| Timeout: 10 * time.Minute, |
| Requirements: []string{tdreq.WiFiGenSupportPasspoint}, |
| VariantCategory: `{"name": "WifiBtChipset_Soc_Kernel"}`, |
| Params: []testing.Param{ |
| { |
| Name: "anqp_basic_info", |
| Val: anqpTestCase{ |
| infos: hostapd.VenueInfoBar, |
| names: []hostapd.VenueName{{Lang: "eng", Name: "Foo bar"}}, |
| roamingConsortiums: []string{"ab56cd8971"}, |
| domains: []string{"example.com"}, |
| realms: []hostapd.NAIRealm{tlsAndTtlsRealm}, |
| }, |
| }, { |
| Name: "anqp_multiple_names", |
| Val: anqpTestCase{ |
| infos: hostapd.VenueInfoZooOrAquarium, |
| names: []hostapd.VenueName{ |
| {Lang: "eng", Name: "Local zoo park"}, |
| {Lang: "fra", Name: "Parc zoologique"}, |
| }, |
| roamingConsortiums: []string{"9836fc"}, |
| domains: []string{"example.com"}, |
| realms: []hostapd.NAIRealm{tlsAndTtlsRealm}, |
| }, |
| }, { |
| Name: "anqp_multiple_ois", |
| Val: anqpTestCase{ |
| infos: hostapd.VenueInfoHotelOrMotel, |
| names: []hostapd.VenueName{{Lang: "eng", Name: "My favorite hotel"}}, |
| roamingConsortiums: []string{"001bc50050", "001bc500b5"}, |
| domains: []string{"example.com"}, |
| realms: []hostapd.NAIRealm{tlsAndTtlsRealm}, |
| }, |
| }, { |
| Name: "anqp_multiple_domains", |
| Val: anqpTestCase{ |
| infos: hostapd.VenueInfoGasStation, |
| names: []hostapd.VenueName{{Lang: "eng", Name: "Foo station"}}, |
| roamingConsortiums: []string{"2233445566"}, |
| domains: []string{"blue.net", "red.com", "green.com"}, |
| realms: []hostapd.NAIRealm{tlsAndTtlsRealm}, |
| }, |
| }, { |
| Name: "anqp_multiple_realms", |
| Val: anqpTestCase{ |
| infos: hostapd.VenueInfoAmphitheater, |
| names: []hostapd.VenueName{{Lang: "eng", Name: "Bar amphitheater"}}, |
| roamingConsortiums: []string{"0105b5c5"}, |
| domains: []string{"foo.net", "bar.com", "example.net"}, |
| realms: []hostapd.NAIRealm{ |
| { |
| Domains: []string{"foo.net"}, |
| Encoding: hostapd.RealmEncodingRFC4282, |
| Methods: []hostapd.EAPMethod{methodEapTLS}, |
| }, { |
| Domains: []string{"bar.com", "example.net"}, |
| Encoding: hostapd.RealmEncodingUTF8, |
| Methods: []hostapd.EAPMethod{methodEapTTLS}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }) |
| } |
| |
| type anqpTestContext struct { |
| ssid string |
| ops []hostapd.Option |
| bssid net.HardwareAddr |
| venueInfo hostapd.VenueInfo |
| roamingConsortiums []string |
| venueNames []hostapd.VenueName |
| domains []string |
| realms []hostapd.NAIRealm |
| } |
| |
| func ANQP(ctx context.Context, s *testing.State) { |
| tf := s.FixtValue().(*wificell.TestFixture) |
| |
| params := s.Param().(anqpTestCase) |
| |
| tc, err := prepareTestContext(params) |
| if err != nil { |
| s.Fatal("Failed to generate hostapd configuration: ", err) |
| } |
| |
| ap, err := tf.ConfigureAP(ctx, tc.ops, nil) |
| if err != nil { |
| s.Fatal("Failed to configure access point: ", err) |
| } |
| defer tf.DeconfigAP(ctx, ap) |
| |
| runner := wpacli.NewRunner(&cmd.RemoteCmdRunner{Host: s.DUT().Conn()}) |
| |
| // Trigger a scan and wait for the network 'tc.ssid' to be available in |
| // scan results. |
| if err := runner.DiscoverNetwork(ctx, tc.ssid); err != nil { |
| s.Fatal("Network discovery failed: ", err) |
| } |
| |
| // Ask wpa_supplicant to fetch ANQP data for all the compatible access point |
| // found in range during last scan. |
| if err := runner.FetchANQP(ctx, tc.bssid.String()); err != nil { |
| s.Fatal("ANQP fetch failed: ", err) |
| } |
| |
| // Gather the data for the test access point. |
| bss, err := runner.BSS(ctx, tc.bssid) |
| if err != nil { |
| s.Fatal("Failed to obtain BSS information: ", err) |
| } |
| |
| // Check venue information match the test case. |
| info, names, err := wifiutil.DecodeVenueGroupTypeName(bss["anqp_venue_name"]) |
| if err != nil { |
| s.Fatal("Failed to parse venue info and names: ", err) |
| } |
| |
| if !reflect.DeepEqual(info, tc.venueInfo) { |
| s.Fatalf("Venue information does not match: got %v want %v", info, tc.venueInfo) |
| } |
| if !reflect.DeepEqual(names, tc.venueNames) { |
| s.Fatalf("Venue names are not matching: got %v want %v", names, tc.venueNames) |
| } |
| |
| // Check roaming consortium OIs are consistent with the test case. |
| rcs, err := wifiutil.DecodeRoamingConsortiums(bss["anqp_roaming_consortium"]) |
| if err != nil { |
| s.Fatal("Failed to parse roaming consortiums: ", err) |
| } |
| if !reflect.DeepEqual(rcs, tc.roamingConsortiums) { |
| s.Fatalf("Romaing consortium OI are not matching: got %v want %v", rcs, tc.roamingConsortiums) |
| } |
| |
| // Check domain names are gathered from the AP. |
| domains, err := wifiutil.DecodeDomainNames(bss["anqp_domain_name"]) |
| if err != nil { |
| s.Fatal("Failed to parse domain names: ", err) |
| } |
| if !reflect.DeepEqual(domains, tc.domains) { |
| s.Fatalf("Domain names list are not matching: got %v want %v", domains, tc.domains) |
| } |
| |
| // Check the realms are identical to the ones configured. |
| realms, err := wifiutil.DecodeNAIRealms(bss["anqp_nai_realm"]) |
| if err != nil { |
| s.Fatal("Failed to parse NAI Realms: ", err) |
| } |
| if !reflect.DeepEqual(realms, tc.realms) { |
| s.Fatalf("Realms are not matching: got %v want %v", realms, tc.realms) |
| } |
| } |
| |
| func prepareTestContext(t anqpTestCase) (*anqpTestContext, error) { |
| ssid := hostapd.RandomSSID("TAST_ANQP_") |
| |
| randAddr, err := hostapd.RandomMAC() |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to generate BSSID") |
| } |
| |
| ops := []hostapd.Option{ |
| hostapd.SSID(ssid), |
| hostapd.Channel(1), |
| hostapd.BSSID(randAddr.String()), |
| hostapd.Mode(hostapd.Mode80211nMixed), |
| hostapd.HTCaps(hostapd.HTCapHT20), |
| hostapd.Interworking(), |
| hostapd.VenueInfos(t.infos), |
| hostapd.VenueNames(t.names...), |
| hostapd.RoamingConsortiums(t.roamingConsortiums...), |
| hostapd.DomainNames(t.domains...), |
| hostapd.Realms(t.realms...), |
| } |
| |
| ois := make(map[string]bool) |
| for _, oi := range t.roamingConsortiums { |
| ois[oi] = true |
| } |
| |
| domains := make(map[string]bool) |
| for _, domain := range t.domains { |
| domains[domain] = true |
| } |
| |
| return &anqpTestContext{ |
| ssid: ssid, |
| ops: ops, |
| bssid: randAddr, |
| venueInfo: t.infos, |
| venueNames: t.names, |
| roamingConsortiums: t.roamingConsortiums, |
| domains: t.domains, |
| realms: t.realms, |
| }, nil |
| } |