From 7bd860a4bc38c853a8eeac949a48f323ce2e8615 Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 10:57:10 +0800 Subject: [PATCH 01/10] wpa_supplicant_8 add orig files --- aosp/external/wpa_supplicant_8/Android.mk | 15 + .../wpa_supplicant/Android.mk | 2097 ++++ .../wpa_supplicant/aidl/init.wifi.rc | 0 .../wpa_supplicant/aidl/sta_iface.cpp | 2580 +++++ .../wpa_supplicant/android.config | 567 + .../wpa_supplicant_8/wpa_supplicant/defconfig | 679 ++ .../wpa_supplicant/wpa_drv_helpers.c | 0 .../wpa_supplicant/wpa_drv_helpers.h | 0 .../wpa_supplicant/wpa_supplicant.c | 9422 +++++++++++++++++ 9 files changed, 15360 insertions(+) create mode 100644 aosp/external/wpa_supplicant_8/Android.mk create mode 100644 aosp/external/wpa_supplicant_8/wpa_supplicant/Android.mk create mode 100644 aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/init.wifi.rc create mode 100644 aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/sta_iface.cpp create mode 100644 aosp/external/wpa_supplicant_8/wpa_supplicant/android.config create mode 100644 aosp/external/wpa_supplicant_8/wpa_supplicant/defconfig create mode 100644 aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.c create mode 100644 aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.h create mode 100644 aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.c diff --git a/aosp/external/wpa_supplicant_8/Android.mk b/aosp/external/wpa_supplicant_8/Android.mk new file mode 100644 index 000000000..bb8326cba --- /dev/null +++ b/aosp/external/wpa_supplicant_8/Android.mk @@ -0,0 +1,15 @@ +S_LOCAL_PATH := $(call my-dir) + +ifneq ($(filter VER_0_8_X VER_2_1_DEVEL,$(WPA_SUPPLICANT_VERSION)),) +# The order of the 2 Android.mks does matter! +# TODO: Clean up the Android.mks, reset all the temporary variables at the +# end of each Android.mk, so that one Android.mk doesn't depend on variables +# set up in the other Android.mk. +include $(S_LOCAL_PATH)/hostapd/Android.mk \ + $(S_LOCAL_PATH)/wpa_supplicant/Android.mk +ifneq ($(TARGET_BUILD_VARIANT), user) +ifeq ($(shell test $(PLATFORM_VERSION_LAST_STABLE) -ge 8 ; echo $$?), 0) +include $(S_LOCAL_PATH)/hs20/client/Android.mk +endif #End of Check for platform version +endif #End of Check for target build variant +endif diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/Android.mk b/aosp/external/wpa_supplicant_8/wpa_supplicant/Android.mk new file mode 100644 index 000000000..509dbbc07 --- /dev/null +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/Android.mk @@ -0,0 +1,2097 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# This software may be distributed under the terms of the BSD license. +# See README for more details. +# + +LOCAL_PATH := $(call my-dir) +PKG_CONFIG ?= pkg-config + +$(eval $(call declare-copy-files-license-metadata,external/wpa_supplicant_8/wpa_supplicant,.conf,SPDX-license-identifier-BSD-3-Clause,notice,external/wpa_supplicant_8/LICENSE,)) + +ifneq ($(BOARD_WPA_SUPPLICANT_DRIVER),) + CONFIG_DRIVER_$(BOARD_WPA_SUPPLICANT_DRIVER) := y +endif + +ifeq ($(BOARD_WLAN_DEVICE), qcwcn) + CONFIG_DRIVER_NL80211_QCA=y +endif + +include $(LOCAL_PATH)/android.config + +# To ignore possible wrong network configurations +L_CFLAGS = -DWPA_IGNORE_CONFIG_ERRORS + +L_CFLAGS += -DVERSION_STR_POSTFIX=\"-$(PLATFORM_VERSION)\" + +# Set Android log name +L_CFLAGS += -DANDROID_LOG_NAME=\"wpa_supplicant\" + +L_CFLAGS += -Wall -Werror + +# Keep sometimes uninitialized warnings +L_CFLAGS += -Wno-error=sometimes-uninitialized + +# Disable incompatible pointer type warnings +L_CFLAGS += -Wno-incompatible-pointer-types +L_CFLAGS += -Wno-incompatible-pointer-types-discards-qualifiers + +# Disable extraneous parentheses warnings +L_CFLAGS += -Wno-parentheses-equality + +# Disable sign compare warnings +L_CFLAGS += -Wno-sign-compare + +# Disable unused function warnings +L_CFLAGS += -Wno-unused-function + +# Disable unused variable warnings +L_CFLAGS += -Wno-unused-variable + +# Disable unused parameter warnings +L_CFLAGS += -Wno-unused-parameter + +# Disable redefined macro warnings +L_CFLAGS += -Wno-macro-redefined + +# Set Android extended P2P functionality +L_CFLAGS += -DANDROID_P2P + +ifeq ($(BOARD_WPA_SUPPLICANT_PRIVATE_LIB),) +L_CFLAGS += -DANDROID_LIB_STUB +endif + +ifneq ($(BOARD_WPA_SUPPLICANT_PRIVATE_LIB_EVENT),) +L_CFLAGS += -DANDROID_LIB_EVENT +endif + +# Disable roaming in wpa_supplicant +ifdef CONFIG_NO_ROAMING +L_CFLAGS += -DCONFIG_NO_ROAMING +endif + +ifeq ($(WIFI_PRIV_CMD_UPDATE_MBO_CELL_STATUS), enabled) +L_CFLAGS += -DENABLE_PRIV_CMD_UPDATE_MBO_CELL_STATUS +endif + +# Use Android specific directory for control interface sockets +L_CFLAGS += -DCONFIG_CTRL_IFACE_CLIENT_DIR=\"/data/vendor/wifi/wpa/sockets\" +L_CFLAGS += -DCONFIG_CTRL_IFACE_DIR=\"/data/vendor/wifi/wpa/sockets\" + +# Use Android specific directory for wpa_cli command completion history +L_CFLAGS += -DCONFIG_WPA_CLI_HISTORY_DIR=\"/data/vendor/wifi/wpa\" + +# To force sizeof(enum) = 4 +ifeq ($(TARGET_ARCH),arm) +L_CFLAGS += -mabi=aapcs-linux +endif + +# C++ flags for aidl interface +L_CPPFLAGS := -Wall -Werror +# TODO: Remove these allowed warnings later. +L_CPPFLAGS += -Wno-unused-variable -Wno-unused-parameter +L_CPPFLAGS += -Wno-unused-private-field + +INCLUDES = $(LOCAL_PATH) +INCLUDES += $(LOCAL_PATH)/src +INCLUDES += $(LOCAL_PATH)/src/common +# INCLUDES += $(LOCAL_PATH)/src/crypto # To force proper includes +INCLUDES += $(LOCAL_PATH)/src/drivers +INCLUDES += $(LOCAL_PATH)/src/eap_common +INCLUDES += $(LOCAL_PATH)/src/eapol_supp +INCLUDES += $(LOCAL_PATH)/src/eap_peer +INCLUDES += $(LOCAL_PATH)/src/eap_server +INCLUDES += $(LOCAL_PATH)/src/l2_packet +INCLUDES += $(LOCAL_PATH)/src/radius +INCLUDES += $(LOCAL_PATH)/src/rsn_supp +INCLUDES += $(LOCAL_PATH)/src/tls +INCLUDES += $(LOCAL_PATH)/src/utils +INCLUDES += $(LOCAL_PATH)/src/wps +INCLUDES += $(LOCAL_PATH)/src/pasn +INCLUDES += system/security/keystore/include +ifdef CONFIG_DRIVER_NL80211 +ifneq ($(wildcard external/libnl),) +INCLUDES += external/libnl/include +else +INCLUDES += external/libnl-headers +endif +endif + +ifdef CONFIG_FIPS +CONFIG_NO_RANDOM_POOL= +endif + +OBJS = config.c +OBJS += notify.c +OBJS += bss.c +OBJS += eap_register.c +OBJS += src/utils/common.c +OBJS += src/utils/config.c +OBJS += src/utils/wpa_debug.c +OBJS += src/utils/wpabuf.c +OBJS += src/utils/bitfield.c +OBJS += src/utils/ip_addr.c +OBJS += src/utils/crc32.c +OBJS += wmm_ac.c +OBJS += op_classes.c +OBJS += rrm.c +OBJS += twt.c +OBJS += robust_av.c +OBJS_p = wpa_passphrase.c +OBJS_p += src/utils/common.c +OBJS_p += src/utils/wpa_debug.c +OBJS_p += src/utils/wpabuf.c +OBJS_c = wpa_cli.c src/common/wpa_ctrl.c +OBJS_c += src/utils/wpa_debug.c +OBJS_c += src/utils/common.c +OBJS_c += src/common/cli.c +OBJS_d = +OBJS_priv = + +ifndef CONFIG_OS +ifdef CONFIG_NATIVE_WINDOWS +CONFIG_OS=win32 +else +CONFIG_OS=unix +endif +endif + +ifeq ($(CONFIG_OS), internal) +L_CFLAGS += -DOS_NO_C_LIB_DEFINES +endif + +OBJS += src/utils/os_$(CONFIG_OS).c +OBJS_p += src/utils/os_$(CONFIG_OS).c +OBJS_c += src/utils/os_$(CONFIG_OS).c + +ifdef CONFIG_WPA_TRACE +L_CFLAGS += -DWPA_TRACE +OBJS += src/utils/trace.c +OBJS_p += src/utils/trace.c +OBJS_c += src/utils/trace.c +LDFLAGS += -rdynamic +L_CFLAGS += -funwind-tables +ifdef CONFIG_WPA_TRACE_BFD +L_CFLAGS += -DWPA_TRACE_BFD +LIBS += -lbfd +LIBS_p += -lbfd +LIBS_c += -lbfd +endif +endif + +ifndef CONFIG_ELOOP +CONFIG_ELOOP=eloop +endif +OBJS += src/utils/$(CONFIG_ELOOP).c +OBJS_c += src/utils/$(CONFIG_ELOOP).c + +ifdef CONFIG_ELOOP_POLL +L_CFLAGS += -DCONFIG_ELOOP_POLL +endif + +ifdef CONFIG_ELOOP_EPOLL +L_CFLAGS += -DCONFIG_ELOOP_EPOLL +endif + +ifdef CONFIG_EAPOL_TEST +L_CFLAGS += -Werror -DEAPOL_TEST +endif + +ifdef CONFIG_HT_OVERRIDES +L_CFLAGS += -DCONFIG_HT_OVERRIDES +endif + +ifdef CONFIG_VHT_OVERRIDES +L_CFLAGS += -DCONFIG_VHT_OVERRIDES +endif + +ifdef CONFIG_HE_OVERRIDES +L_CFLAGS += -DCONFIG_HE_OVERRIDES +endif + +ifndef CONFIG_BACKEND +CONFIG_BACKEND=file +endif + +ifeq ($(CONFIG_BACKEND), file) +OBJS += config_file.c +ifndef CONFIG_NO_CONFIG_BLOBS +NEED_BASE64=y +endif +L_CFLAGS += -DCONFIG_BACKEND_FILE +endif + +ifeq ($(CONFIG_BACKEND), winreg) +OBJS += config_winreg.c +endif + +ifeq ($(CONFIG_BACKEND), none) +OBJS += config_none.c +endif + +ifdef CONFIG_NO_CONFIG_WRITE +L_CFLAGS += -DCONFIG_NO_CONFIG_WRITE +endif + +ifdef CONFIG_NO_CONFIG_BLOBS +L_CFLAGS += -DCONFIG_NO_CONFIG_BLOBS +endif + +ifdef CONFIG_NO_SCAN_PROCESSING +L_CFLAGS += -DCONFIG_NO_SCAN_PROCESSING +endif + +ifdef CONFIG_SUITEB +L_CFLAGS += -DCONFIG_SUITEB +endif + +ifdef CONFIG_SUITEB192 +L_CFLAGS += -DCONFIG_SUITEB192 +NEED_SHA384=y +endif + +ifdef CONFIG_OCV +L_CFLAGS += -DCONFIG_OCV +OBJS += src/common/ocv.c +endif + +ifdef CONFIG_IEEE80211R +L_CFLAGS += -DCONFIG_IEEE80211R +OBJS += src/rsn_supp/wpa_ft.c +endif + +ifdef CONFIG_MESH +NEED_80211_COMMON=y +NEED_AES_SIV=y +CONFIG_SAE=y +CONFIG_AP=y +L_CFLAGS += -DCONFIG_MESH +OBJS += mesh.c +OBJS += mesh_mpm.c +OBJS += mesh_rsn.c +endif + +ifdef CONFIG_SAE +L_CFLAGS += -DCONFIG_SAE +OBJS += src/common/sae.c +ifdef CONFIG_SAE_PK +L_CFLAGS += -DCONFIG_SAE_PK +NEED_AES_SIV=y +OBJS += src/common/sae_pk.c +endif +NEED_ECC=y +NEED_DH_GROUPS=y +NEED_HMAC_SHA256_KDF=y +NEED_DRAGONFLY=y +ifdef CONFIG_TESTING_OPTIONS +NEED_DH_GROUPS_ALL=y +endif +endif + +ifdef CONFIG_DPP +L_CFLAGS += -DCONFIG_DPP +OBJS += src/common/dpp.c +OBJS += src/common/dpp_auth.c +OBJS += src/common/dpp_backup.c +OBJS += src/common/dpp_crypto.c +OBJS += src/common/dpp_pkex.c +OBJS += src/common/dpp_reconfig.c +OBJS += src/common/dpp_tcp.c +OBJS += dpp_supplicant.c +NEED_AES_SIV=y +NEED_HMAC_SHA256_KDF=y +NEED_HMAC_SHA384_KDF=y +NEED_HMAC_SHA512_KDF=y +NEED_SHA384=y +NEED_SHA512=y +NEED_ECC=y +NEED_JSON=y +NEED_GAS_SERVER=y +NEED_BASE64=y +NEED_ASN1=y +ifdef CONFIG_DPP2 +L_CFLAGS += -DCONFIG_DPP2 +endif +ifdef CONFIG_DPP3 +L_CFLAGS += -DCONFIG_DPP3 +endif +endif + +ifdef CONFIG_OWE +L_CFLAGS += -DCONFIG_OWE +NEED_ECC=y +NEED_HMAC_SHA256_KDF=y +NEED_HMAC_SHA384_KDF=y +NEED_HMAC_SHA512_KDF=y +NEED_SHA384=y +NEED_SHA512=y +endif + +ifdef CONFIG_WAPI_INTERFACE +L_CFLAGS += -DCONFIG_WAPI_INTERFACE +endif + +ifdef CONFIG_FILS +L_CFLAGS += -DCONFIG_FILS +NEED_SHA384=y +NEED_AES_SIV=y +ifdef CONFIG_FILS_SK_PFS +L_CFLAGS += -DCONFIG_FILS_SK_PFS +NEED_ECC=y +endif +endif + +ifdef CONFIG_MBO +CONFIG_WNM=y +endif + +ifdef CONFIG_WNM +L_CFLAGS += -DCONFIG_WNM +OBJS += wnm_sta.c +endif + +ifdef CONFIG_TDLS +L_CFLAGS += -DCONFIG_TDLS +OBJS += src/rsn_supp/tdls.c +endif + +ifdef CONFIG_TDLS_TESTING +L_CFLAGS += -DCONFIG_TDLS_TESTING +endif + +ifdef CONFIG_PMKSA_CACHE_EXTERNAL +L_CFLAGS += -DCONFIG_PMKSA_CACHE_EXTERNAL +endif + +ifndef CONFIG_NO_WPA +OBJS += src/rsn_supp/wpa.c +OBJS += src/rsn_supp/preauth.c +OBJS += src/rsn_supp/pmksa_cache.c +OBJS += src/rsn_supp/wpa_ie.c +OBJS += src/common/wpa_common.c +NEED_AES=y +NEED_SHA1=y +NEED_MD5=y +NEED_RC4=y +else +L_CFLAGS += -DCONFIG_NO_WPA +endif + +ifdef CONFIG_IBSS_RSN +NEED_RSN_AUTHENTICATOR=y +L_CFLAGS += -DCONFIG_IBSS_RSN +L_CFLAGS += -DCONFIG_NO_VLAN +OBJS += ibss_rsn.c +endif + +ifdef CONFIG_P2P +OBJS += p2p_supplicant.c +OBJS += p2p_supplicant_sd.c +OBJS += src/p2p/p2p.c +OBJS += src/p2p/p2p_utils.c +OBJS += src/p2p/p2p_parse.c +OBJS += src/p2p/p2p_build.c +OBJS += src/p2p/p2p_go_neg.c +OBJS += src/p2p/p2p_sd.c +OBJS += src/p2p/p2p_pd.c +OBJS += src/p2p/p2p_invitation.c +OBJS += src/p2p/p2p_dev_disc.c +OBJS += src/p2p/p2p_group.c +OBJS += src/ap/p2p_hostapd.c +L_CFLAGS += -DCONFIG_P2P +NEED_GAS=y +NEED_OFFCHANNEL=y +CONFIG_WPS=y +CONFIG_AP=y +ifdef CONFIG_P2P_STRICT +L_CFLAGS += -DCONFIG_P2P_STRICT +endif +ifdef CONFIG_WIFI_DISPLAY +L_CFLAGS += -DCONFIG_WIFI_DISPLAY +OBJS += wifi_display.c +endif +endif + +ifdef CONFIG_PASN +L_CFLAGS += -DCONFIG_PASN +L_CFLAGS += -DCONFIG_PTKSA_CACHE +NEED_HMAC_SHA256_KDF=y +NEED_HMAC_SHA384_KDF=y +NEED_SHA256=y +NEED_SHA384=y +OBJS += src/common/ptksa_cache.c +OBJS += src/pasn/pasn_initiator.c +OBJS += pasn_supplicant.c +endif + +ifdef CONFIG_HS20 +OBJS += hs20_supplicant.c +L_CFLAGS += -DCONFIG_HS20 +CONFIG_INTERWORKING=y +endif + +ifdef CONFIG_INTERWORKING +OBJS += interworking.c +L_CFLAGS += -DCONFIG_INTERWORKING +NEED_GAS=y +endif + +ifdef CONFIG_FST +L_CFLAGS += -DCONFIG_FST +OBJS += src/fst/fst.c +OBJS += src/fst/fst_session.c +OBJS += src/fst/fst_iface.c +OBJS += src/fst/fst_group.c +OBJS += src/fst/fst_ctrl_aux.c +ifdef CONFIG_FST_TEST +L_CFLAGS += -DCONFIG_FST_TEST +endif +ifdef CONFIG_CTRL_IFACE +OBJS += src/fst/fst_ctrl_iface.c +endif +endif + +ifdef CONFIG_WEP +L_CFLAGS += -DCONFIG_WEP +endif + +ifdef CONFIG_NO_TKIP +L_CFLAGS += -DCONFIG_NO_TKIP +endif + + +include $(LOCAL_PATH)/src/drivers/drivers.mk + +ifdef CONFIG_AP +OBJS_d += $(DRV_BOTH_OBJS) +L_CFLAGS += $(DRV_BOTH_CFLAGS) +LDFLAGS += $(DRV_BOTH_LDFLAGS) +LIBS += $(DRV_BOTH_LIBS) +else +NEED_AP_MLME= +OBJS_d += $(DRV_WPA_OBJS) +L_CFLAGS += $(DRV_WPA_CFLAGS) +LDFLAGS += $(DRV_WPA_LDFLAGS) +LIBS += $(DRV_WPA_LIBS) +endif + +ifndef CONFIG_L2_PACKET +CONFIG_L2_PACKET=linux +endif + +OBJS_l2 += src/l2_packet/l2_packet_$(CONFIG_L2_PACKET).c + +ifeq ($(CONFIG_L2_PACKET), pcap) +ifdef CONFIG_WINPCAP +L_CFLAGS += -DCONFIG_WINPCAP +LIBS += -lwpcap -lpacket +LIBS_w += -lwpcap +else +LIBS += -ldnet -lpcap +endif +endif + +ifeq ($(CONFIG_L2_PACKET), winpcap) +LIBS += -lwpcap -lpacket +LIBS_w += -lwpcap +endif + +ifeq ($(CONFIG_L2_PACKET), freebsd) +LIBS += -lpcap +endif + +ifdef CONFIG_ERP +L_CFLAGS += -DCONFIG_ERP +NEED_HMAC_SHA256_KDF=y +endif + +ifdef CONFIG_EAP_TLS +# EAP-TLS +ifeq ($(CONFIG_EAP_TLS), dyn) +L_CFLAGS += -DEAP_TLS_DYNAMIC +EAPDYN += src/eap_peer/eap_tls.so +else +L_CFLAGS += -DEAP_TLS +OBJS += src/eap_peer/eap_tls.c +endif +TLS_FUNCS=y +CONFIG_IEEE8021X_EAPOL=y +ifdef CONFIG_EAP_TLSV1_3 +L_CFLAGS += -DEAP_TLSV1_3 +endif +endif + +ifdef CONFIG_EAP_UNAUTH_TLS +# EAP-UNAUTH-TLS +L_CFLAGS += -DEAP_UNAUTH_TLS +ifndef CONFIG_EAP_TLS +OBJS += src/eap_peer/eap_tls.c +TLS_FUNCS=y +endif +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_PEAP +# EAP-PEAP +ifeq ($(CONFIG_EAP_PEAP), dyn) +L_CFLAGS += -DEAP_PEAP_DYNAMIC +EAPDYN += src/eap_peer/eap_peap.so +else +L_CFLAGS += -DEAP_PEAP +OBJS += src/eap_peer/eap_peap.c +OBJS += src/eap_common/eap_peap_common.c +endif +TLS_FUNCS=y +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_TTLS +# EAP-TTLS +ifeq ($(CONFIG_EAP_TTLS), dyn) +L_CFLAGS += -DEAP_TTLS_DYNAMIC +EAPDYN += src/eap_peer/eap_ttls.so +else +L_CFLAGS += -DEAP_TTLS +OBJS += src/eap_peer/eap_ttls.c +endif +TLS_FUNCS=y +ifndef CONFIG_FIPS +MS_FUNCS=y +CHAP=y +endif +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_MD5 +# EAP-MD5 +ifeq ($(CONFIG_EAP_MD5), dyn) +L_CFLAGS += -DEAP_MD5_DYNAMIC +EAPDYN += src/eap_peer/eap_md5.so +else +L_CFLAGS += -DEAP_MD5 +OBJS += src/eap_peer/eap_md5.c +endif +CHAP=y +CONFIG_IEEE8021X_EAPOL=y +endif + +# backwards compatibility for old spelling +ifdef CONFIG_MSCHAPV2 +ifndef CONFIG_EAP_MSCHAPV2 +CONFIG_EAP_MSCHAPV2=y +endif +endif + +ifdef CONFIG_EAP_MSCHAPV2 +# EAP-MSCHAPv2 +ifeq ($(CONFIG_EAP_MSCHAPV2), dyn) +L_CFLAGS += -DEAP_MSCHAPv2_DYNAMIC +EAPDYN += src/eap_peer/eap_mschapv2.so +EAPDYN += src/eap_peer/mschapv2.so +else +L_CFLAGS += -DEAP_MSCHAPv2 +OBJS += src/eap_peer/eap_mschapv2.c +OBJS += src/eap_peer/mschapv2.c +endif +MS_FUNCS=y +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_GTC +# EAP-GTC +ifeq ($(CONFIG_EAP_GTC), dyn) +L_CFLAGS += -DEAP_GTC_DYNAMIC +EAPDYN += src/eap_peer/eap_gtc.so +else +L_CFLAGS += -DEAP_GTC +OBJS += src/eap_peer/eap_gtc.c +endif +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_OTP +# EAP-OTP +ifeq ($(CONFIG_EAP_OTP), dyn) +L_CFLAGS += -DEAP_OTP_DYNAMIC +EAPDYN += src/eap_peer/eap_otp.so +else +L_CFLAGS += -DEAP_OTP +OBJS += src/eap_peer/eap_otp.c +endif +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_SIM +# EAP-SIM +ifeq ($(CONFIG_EAP_SIM), dyn) +L_CFLAGS += -DEAP_SIM_DYNAMIC +EAPDYN += src/eap_peer/eap_sim.so +else +L_CFLAGS += -DEAP_SIM +OBJS += src/eap_peer/eap_sim.c +endif +CONFIG_IEEE8021X_EAPOL=y +CONFIG_EAP_SIM_COMMON=y +NEED_AES_CBC=y +endif + +ifdef CONFIG_EAP_LEAP +# EAP-LEAP +ifeq ($(CONFIG_EAP_LEAP), dyn) +L_CFLAGS += -DEAP_LEAP_DYNAMIC +EAPDYN += src/eap_peer/eap_leap.so +else +L_CFLAGS += -DEAP_LEAP +OBJS += src/eap_peer/eap_leap.c +endif +MS_FUNCS=y +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_PSK +# EAP-PSK +ifeq ($(CONFIG_EAP_PSK), dyn) +L_CFLAGS += -DEAP_PSK_DYNAMIC +EAPDYN += src/eap_peer/eap_psk.so +else +L_CFLAGS += -DEAP_PSK +OBJS += src/eap_peer/eap_psk.c src/eap_common/eap_psk_common.c +endif +CONFIG_IEEE8021X_EAPOL=y +NEED_AES=y +NEED_AES_ENCBLOCK=y +NEED_AES_EAX=y +endif + +ifdef CONFIG_EAP_AKA +# EAP-AKA +ifeq ($(CONFIG_EAP_AKA), dyn) +L_CFLAGS += -DEAP_AKA_DYNAMIC +EAPDYN += src/eap_peer/eap_aka.so +else +L_CFLAGS += -DEAP_AKA +OBJS += src/eap_peer/eap_aka.c +endif +CONFIG_IEEE8021X_EAPOL=y +CONFIG_EAP_SIM_COMMON=y +NEED_AES_CBC=y +endif + +ifdef CONFIG_EAP_PROXY +L_CFLAGS += -DCONFIG_EAP_PROXY +OBJS += src/eap_peer/eap_proxy_$(CONFIG_EAP_PROXY).c +include $(LOCAL_PATH)/eap_proxy_$(CONFIG_EAP_PROXY).mk +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_AKA_PRIME +# EAP-AKA' +ifeq ($(CONFIG_EAP_AKA_PRIME), dyn) +L_CFLAGS += -DEAP_AKA_PRIME_DYNAMIC +else +L_CFLAGS += -DEAP_AKA_PRIME +endif +endif + +ifdef CONFIG_EAP_SIM_COMMON +OBJS += src/eap_common/eap_sim_common.c +NEED_AES=y +NEED_FIPS186_2_PRF=y +endif + +ifdef CONFIG_EAP_FAST +# EAP-FAST +ifeq ($(CONFIG_EAP_FAST), dyn) +L_CFLAGS += -DEAP_FAST_DYNAMIC +EAPDYN += src/eap_peer/eap_fast.so +EAPDYN += src/eap_common/eap_fast_common.c +else +L_CFLAGS += -DEAP_FAST +OBJS += src/eap_peer/eap_fast.c src/eap_peer/eap_fast_pac.c +OBJS += src/eap_common/eap_fast_common.c +endif +TLS_FUNCS=y +CONFIG_IEEE8021X_EAPOL=y +NEED_T_PRF=y +endif + +ifdef CONFIG_EAP_TEAP +# EAP-TEAP +ifeq ($(CONFIG_EAP_TEAP), dyn) +L_CFLAGS += -DEAP_YEAP_DYNAMIC +EAPDYN += src/eap_peer/eap_teap.so +EAPDYN += src/eap_common/eap_teap_common.c +else +L_CFLAGS += -DEAP_TEAP +OBJS += src/eap_peer/eap_teap.c src/eap_peer/eap_teap_pac.c +OBJS += src/eap_common/eap_teap_common.c +endif +TLS_FUNCS=y +CONFIG_IEEE8021X_EAPOL=y +NEED_T_PRF=y +NEED_SHA384=y +NEED_TLS_PRF_SHA256=y +NEED_TLS_PRF_SHA384=y +endif + +ifdef CONFIG_EAP_PAX +# EAP-PAX +ifeq ($(CONFIG_EAP_PAX), dyn) +L_CFLAGS += -DEAP_PAX_DYNAMIC +EAPDYN += src/eap_peer/eap_pax.so +else +L_CFLAGS += -DEAP_PAX +OBJS += src/eap_peer/eap_pax.c src/eap_common/eap_pax_common.c +endif +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_SAKE +# EAP-SAKE +ifeq ($(CONFIG_EAP_SAKE), dyn) +L_CFLAGS += -DEAP_SAKE_DYNAMIC +EAPDYN += src/eap_peer/eap_sake.so +else +L_CFLAGS += -DEAP_SAKE +OBJS += src/eap_peer/eap_sake.c src/eap_common/eap_sake_common.c +endif +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_GPSK +# EAP-GPSK +ifeq ($(CONFIG_EAP_GPSK), dyn) +L_CFLAGS += -DEAP_GPSK_DYNAMIC +EAPDYN += src/eap_peer/eap_gpsk.so +else +L_CFLAGS += -DEAP_GPSK +OBJS += src/eap_peer/eap_gpsk.c src/eap_common/eap_gpsk_common.c +endif +CONFIG_IEEE8021X_EAPOL=y +ifdef CONFIG_EAP_GPSK_SHA256 +L_CFLAGS += -DEAP_GPSK_SHA256 +endif +endif + +ifdef CONFIG_EAP_PWD +L_CFLAGS += -DEAP_PWD +OBJS += src/eap_peer/eap_pwd.c src/eap_common/eap_pwd_common.c +CONFIG_IEEE8021X_EAPOL=y +NEED_ECC=y +NEED_DRAGONFLY=y +MS_FUNCS=y +endif + +ifdef CONFIG_EAP_EKE +# EAP-EKE +ifeq ($(CONFIG_EAP_EKE), dyn) +L_CFLAGS += -DEAP_EKE_DYNAMIC +EAPDYN += src/eap_peer/eap_eke.so +else +L_CFLAGS += -DEAP_EKE +OBJS += src/eap_peer/eap_eke.c src/eap_common/eap_eke_common.c +endif +CONFIG_IEEE8021X_EAPOL=y +NEED_DH_GROUPS=y +NEED_DH_GROUPS_ALL=y +NEED_AES_CBC=y +endif + +ifdef CONFIG_WPS +# EAP-WSC +L_CFLAGS += -DCONFIG_WPS -DEAP_WSC +OBJS += wps_supplicant.c +OBJS += src/utils/uuid.c +OBJS += src/eap_peer/eap_wsc.c src/eap_common/eap_wsc_common.c +OBJS += src/wps/wps.c +OBJS += src/wps/wps_common.c +OBJS += src/wps/wps_attr_parse.c +OBJS += src/wps/wps_attr_build.c +OBJS += src/wps/wps_attr_process.c +OBJS += src/wps/wps_dev_attr.c +OBJS += src/wps/wps_enrollee.c +OBJS += src/wps/wps_registrar.c +CONFIG_IEEE8021X_EAPOL=y +NEED_DH_GROUPS=y +NEED_BASE64=y +NEED_AES_CBC=y +NEED_MODEXP=y + +ifdef CONFIG_WPS_NFC +L_CFLAGS += -DCONFIG_WPS_NFC +OBJS += src/wps/ndef.c +NEED_WPS_OOB=y +endif + +ifdef NEED_WPS_OOB +L_CFLAGS += -DCONFIG_WPS_OOB +endif + +ifdef CONFIG_WPS_ER +CONFIG_WPS_UPNP=y +L_CFLAGS += -DCONFIG_WPS_ER +OBJS += src/wps/wps_er.c +OBJS += src/wps/wps_er_ssdp.c +endif + +ifdef CONFIG_WPS_UPNP +L_CFLAGS += -DCONFIG_WPS_UPNP +OBJS += src/wps/wps_upnp.c +OBJS += src/wps/wps_upnp_ssdp.c +OBJS += src/wps/wps_upnp_web.c +OBJS += src/wps/wps_upnp_event.c +OBJS += src/wps/wps_upnp_ap.c +OBJS += src/wps/upnp_xml.c +OBJS += src/wps/httpread.c +OBJS += src/wps/http_client.c +OBJS += src/wps/http_server.c +endif + +ifdef CONFIG_WPS_STRICT +L_CFLAGS += -DCONFIG_WPS_STRICT +OBJS += src/wps/wps_validate.c +endif + +ifdef CONFIG_WPS_TESTING +L_CFLAGS += -DCONFIG_WPS_TESTING +endif + +ifdef CONFIG_WPS_REG_DISABLE_OPEN +L_CFLAGS += -DCONFIG_WPS_REG_DISABLE_OPEN +endif + +endif + +ifdef CONFIG_EAP_IKEV2 +# EAP-IKEv2 +ifeq ($(CONFIG_EAP_IKEV2), dyn) +L_CFLAGS += -DEAP_IKEV2_DYNAMIC +EAPDYN += src/eap_peer/eap_ikev2.so src/eap_peer/ikev2.c +EAPDYN += src/eap_common/eap_ikev2_common.c src/eap_common/ikev2_common.c +else +L_CFLAGS += -DEAP_IKEV2 +OBJS += src/eap_peer/eap_ikev2.c src/eap_peer/ikev2.c +OBJS += src/eap_common/eap_ikev2_common.c src/eap_common/ikev2_common.c +endif +CONFIG_IEEE8021X_EAPOL=y +NEED_DH_GROUPS=y +NEED_DH_GROUPS_ALL=y +NEED_MODEXP=y +NEED_CIPHER=y +endif + +ifdef CONFIG_EAP_VENDOR_TEST +ifeq ($(CONFIG_EAP_VENDOR_TEST), dyn) +L_CFLAGS += -DEAP_VENDOR_TEST_DYNAMIC +EAPDYN += src/eap_peer/eap_vendor_test.so +else +L_CFLAGS += -DEAP_VENDOR_TEST +OBJS += src/eap_peer/eap_vendor_test.c +endif +CONFIG_IEEE8021X_EAPOL=y +endif + +ifdef CONFIG_EAP_TNC +# EAP-TNC +L_CFLAGS += -DEAP_TNC +OBJS += src/eap_peer/eap_tnc.c +OBJS += src/eap_peer/tncc.c +NEED_BASE64=y +ifndef CONFIG_NATIVE_WINDOWS +ifndef CONFIG_DRIVER_BSD +LIBS += -ldl +endif +endif +endif + +ifdef CONFIG_IEEE8021X_EAPOL +# IEEE 802.1X/EAPOL state machines (e.g., for RADIUS authentication) +L_CFLAGS += -DIEEE8021X_EAPOL +OBJS += src/eapol_supp/eapol_supp_sm.c +OBJS += src/eap_peer/eap.c src/eap_peer/eap_methods.c +NEED_EAP_COMMON=y +ifdef CONFIG_DYNAMIC_EAP_METHODS +L_CFLAGS += -DCONFIG_DYNAMIC_EAP_METHODS +LIBS += -ldl -rdynamic +endif +endif + +ifdef CONFIG_AP +NEED_EAP_COMMON=y +NEED_RSN_AUTHENTICATOR=y +L_CFLAGS += -DCONFIG_AP +OBJS += ap.c +L_CFLAGS += -DCONFIG_NO_RADIUS +L_CFLAGS += -DCONFIG_NO_ACCOUNTING +L_CFLAGS += -DCONFIG_NO_VLAN +OBJS += src/ap/hostapd.c +OBJS += src/ap/wpa_auth_glue.c +OBJS += src/ap/utils.c +OBJS += src/ap/authsrv.c +OBJS += src/ap/ap_config.c +OBJS += src/ap/sta_info.c +OBJS += src/ap/tkip_countermeasures.c +OBJS += src/ap/ap_mlme.c +OBJS += src/ap/ieee802_1x.c +OBJS += src/eapol_auth/eapol_auth_sm.c +OBJS += src/ap/ieee802_11_auth.c +OBJS += src/ap/ieee802_11_shared.c +OBJS += src/ap/drv_callbacks.c +OBJS += src/ap/ap_drv_ops.c +OBJS += src/ap/beacon.c +OBJS += src/ap/bss_load.c +OBJS += src/ap/eap_user_db.c +OBJS += src/ap/neighbor_db.c +OBJS += src/ap/rrm.c +OBJS += src/ap/ieee802_11_ht.c +ifdef CONFIG_IEEE80211AC +OBJS += src/ap/ieee802_11_vht.c +endif +ifdef CONFIG_IEEE80211AX +OBJS += src/ap/ieee802_11_he.c +endif +ifdef CONFIG_IEEE80211BE +OBJS += src/ap/ieee802_11_eht.c +endif +ifdef CONFIG_WNM_AP +L_CFLAGS += -DCONFIG_WNM_AP +OBJS += src/ap/wnm_ap.c +endif +ifdef CONFIG_MBO +OBJS += src/ap/mbo_ap.c +endif +ifdef CONFIG_FILS +OBJS += src/ap/fils_hlp.c +endif +ifdef CONFIG_CTRL_IFACE +OBJS += src/ap/ctrl_iface_ap.c +endif + +L_CFLAGS += -DEAP_SERVER -DEAP_SERVER_IDENTITY +OBJS += src/eap_server/eap_server.c +OBJS += src/eap_server/eap_server_identity.c +OBJS += src/eap_server/eap_server_methods.c + +ifdef CONFIG_IEEE80211AC +L_CFLAGS += -DCONFIG_IEEE80211AC +endif +ifdef CONFIG_IEEE80211BE +CONFIG_IEEE80211AX=y +L_CFLAGS += -DCONFIG_IEEE80211BE +endif +ifdef CONFIG_IEEE80211AX +L_CFLAGS += -DCONFIG_IEEE80211AX +endif + +ifdef NEED_AP_MLME +OBJS += src/ap/wmm.c +OBJS += src/ap/ap_list.c +OBJS += src/ap/comeback_token.c +OBJS += src/pasn/pasn_responder.c +OBJS += src/ap/ieee802_11.c +OBJS += src/ap/hw_features.c +OBJS += src/ap/dfs.c +L_CFLAGS += -DNEED_AP_MLME +endif +ifdef CONFIG_WPS +L_CFLAGS += -DEAP_SERVER_WSC +OBJS += src/ap/wps_hostapd.c +OBJS += src/eap_server/eap_server_wsc.c +endif +ifdef CONFIG_DPP +OBJS += src/ap/dpp_hostapd.c +OBJS += src/ap/gas_query_ap.c +NEED_AP_GAS_SERV=y +endif +ifdef CONFIG_INTERWORKING +NEED_AP_GAS_SERV=y +endif +ifdef NEED_AP_GAS_SERV +OBJS += src/ap/gas_serv.c +endif +ifdef CONFIG_HS20 +OBJS += src/ap/hs20.c +endif +endif + +ifdef CONFIG_MBO +OBJS += mbo.c +L_CFLAGS += -DCONFIG_MBO +endif + +ifdef CONFIG_TESTING_OPTIONS +L_CFLAGS += -DCONFIG_TESTING_OPTIONS +endif + +ifdef NEED_RSN_AUTHENTICATOR +L_CFLAGS += -DCONFIG_NO_RADIUS +NEED_AES_WRAP=y +OBJS += src/ap/wpa_auth.c +OBJS += src/ap/wpa_auth_ie.c +OBJS += src/ap/pmksa_cache_auth.c +endif + +ifdef CONFIG_ACS +L_CFLAGS += -DCONFIG_ACS +OBJS += src/ap/acs.c +LIBS += -lm +endif + +ifdef CONFIG_PCSC +# PC/SC interface for smartcards (USIM, GSM SIM) +L_CFLAGS += -DPCSC_FUNCS -I/usr/include/PCSC +OBJS += src/utils/pcsc_funcs.c +# -lpthread may not be needed depending on how pcsc-lite was configured +ifdef CONFIG_NATIVE_WINDOWS +#Once MinGW gets support for WinScard, -lwinscard could be used instead of the +#dynamic symbol loading that is now used in pcsc_funcs.c +#LIBS += -lwinscard +else +LIBS += -lpcsclite -lpthread +endif +endif + +ifdef CONFIG_SIM_SIMULATOR +L_CFLAGS += -DCONFIG_SIM_SIMULATOR +NEED_MILENAGE=y +endif + +ifdef CONFIG_USIM_SIMULATOR +L_CFLAGS += -DCONFIG_USIM_SIMULATOR +NEED_MILENAGE=y +endif + +ifdef NEED_MILENAGE +OBJS += src/crypto/milenage.c +NEED_AES_ENCBLOCK=y +endif + +ifdef CONFIG_PKCS12 +L_CFLAGS += -DPKCS12_FUNCS +endif + +ifdef CONFIG_SMARTCARD +L_CFLAGS += -DCONFIG_SMARTCARD +endif + +ifdef NEED_DRAGONFLY +OBJS += src/common/dragonfly.c +endif + +ifdef MS_FUNCS +OBJS += src/crypto/ms_funcs.c +NEED_DES=y +NEED_MD4=y +endif + +ifdef CHAP +OBJS += src/eap_common/chap.c +endif + +ifdef TLS_FUNCS +NEED_DES=y +# Shared TLS functions (needed for EAP_TLS, EAP_PEAP, EAP_TTLS, and EAP_FAST) +OBJS += src/eap_peer/eap_tls_common.c +ifndef CONFIG_FIPS +NEED_TLS_PRF=y +NEED_SHA1=y +NEED_MD5=y +endif +endif + +ifndef CONFIG_TLS +CONFIG_TLS=openssl +L_CFLAGS += -DCONFIG_USE_OPENSSL_RNG +endif + +ifdef CONFIG_TLSV11 +L_CFLAGS += -DCONFIG_TLSV11 +endif + +ifdef CONFIG_TLSV12 +L_CFLAGS += -DCONFIG_TLSV12 +endif + +ifeq ($(CONFIG_TLS), openssl) +L_CFLAGS += -DCRYPTO_RSA_OAEP_SHA256 +ifdef TLS_FUNCS +L_CFLAGS += -DEAP_TLS_OPENSSL +OBJS += src/crypto/tls_openssl.c +OBJS += src/crypto/tls_openssl_ocsp.c +LIBS += -lssl +endif +OBJS += src/crypto/crypto_openssl.c +OBJS_p += src/crypto/crypto_openssl.c +ifdef NEED_FIPS186_2_PRF +OBJS += src/crypto/fips_prf_openssl.c +endif +NEED_TLS_PRF_SHA256=y +LIBS += -lcrypto +LIBS_p += -lcrypto +ifdef CONFIG_TLS_ADD_DL +LIBS += -ldl +LIBS_p += -ldl +endif +ifndef CONFIG_TLS_DEFAULT_CIPHERS +CONFIG_TLS_DEFAULT_CIPHERS = "DEFAULT:!EXP:!LOW" +endif +L_CFLAGS += -DTLS_DEFAULT_CIPHERS=\"$(CONFIG_TLS_DEFAULT_CIPHERS)\" +endif + +ifeq ($(CONFIG_TLS), gnutls) +ifndef CONFIG_CRYPTO +# default to libgcrypt +CONFIG_CRYPTO=gnutls +endif +ifdef TLS_FUNCS +OBJS += src/crypto/tls_gnutls.c +LIBS += -lgnutls -lgpg-error +endif +OBJS += src/crypto/crypto_$(CONFIG_CRYPTO).c +OBJS_p += src/crypto/crypto_$(CONFIG_CRYPTO).c +ifdef NEED_FIPS186_2_PRF +OBJS += src/crypto/fips_prf_internal.c +OBJS += src/crypto/sha1-internal.c +endif +ifeq ($(CONFIG_CRYPTO), gnutls) +LIBS += -lgcrypt +LIBS_p += -lgcrypt +CONFIG_INTERNAL_RC4=y +CONFIG_INTERNAL_DH_GROUP5=y +endif +ifeq ($(CONFIG_CRYPTO), nettle) +LIBS += -lnettle -lgmp +LIBS_p += -lnettle -lgmp +CONFIG_INTERNAL_RC4=y +CONFIG_INTERNAL_DH_GROUP5=y +endif +endif + +ifeq ($(CONFIG_TLS), internal) +ifndef CONFIG_CRYPTO +CONFIG_CRYPTO=internal +endif +ifdef TLS_FUNCS +OBJS += src/crypto/crypto_internal-rsa.c +OBJS += src/crypto/tls_internal.c +OBJS += src/tls/tlsv1_common.c +OBJS += src/tls/tlsv1_record.c +OBJS += src/tls/tlsv1_cred.c +OBJS += src/tls/tlsv1_client.c +OBJS += src/tls/tlsv1_client_write.c +OBJS += src/tls/tlsv1_client_read.c +OBJS += src/tls/tlsv1_client_ocsp.c +NEED_ASN1=y +OBJS += src/tls/rsa.c +OBJS += src/tls/x509v3.c +OBJS += src/tls/pkcs1.c +OBJS += src/tls/pkcs5.c +OBJS += src/tls/pkcs8.c +NEED_BASE64=y +NEED_TLS_PRF=y +ifdef CONFIG_TLSV12 +NEED_TLS_PRF_SHA256=y +endif +NEED_MODEXP=y +NEED_CIPHER=y +L_CFLAGS += -DCONFIG_TLS_INTERNAL_CLIENT +endif +ifdef NEED_CIPHER +NEED_DES=y +OBJS += src/crypto/crypto_internal-cipher.c +endif +ifdef NEED_MODEXP +OBJS += src/crypto/crypto_internal-modexp.c +OBJS += src/tls/bignum.c +endif +ifeq ($(CONFIG_CRYPTO), libtomcrypt) +OBJS += src/crypto/crypto_libtomcrypt.c +OBJS_p += src/crypto/crypto_libtomcrypt.c +LIBS += -ltomcrypt -ltfm +LIBS_p += -ltomcrypt -ltfm +CONFIG_INTERNAL_SHA256=y +CONFIG_INTERNAL_RC4=y +CONFIG_INTERNAL_DH_GROUP5=y +endif +ifeq ($(CONFIG_CRYPTO), internal) +OBJS += src/crypto/crypto_internal.c +OBJS_p += src/crypto/crypto_internal.c +NEED_AES_ENC=y +L_CFLAGS += -DCONFIG_CRYPTO_INTERNAL +ifdef CONFIG_INTERNAL_LIBTOMMATH +L_CFLAGS += -DCONFIG_INTERNAL_LIBTOMMATH +ifdef CONFIG_INTERNAL_LIBTOMMATH_FAST +L_CFLAGS += -DLTM_FAST +endif +else +LIBS += -ltommath +LIBS_p += -ltommath +endif +CONFIG_INTERNAL_AES=y +CONFIG_INTERNAL_DES=y +CONFIG_INTERNAL_SHA1=y +CONFIG_INTERNAL_MD4=y +CONFIG_INTERNAL_MD5=y +CONFIG_INTERNAL_SHA256=y +CONFIG_INTERNAL_SHA384=y +CONFIG_INTERNAL_SHA512=y +CONFIG_INTERNAL_RC4=y +CONFIG_INTERNAL_DH_GROUP5=y +endif +ifeq ($(CONFIG_CRYPTO), cryptoapi) +OBJS += src/crypto/crypto_cryptoapi.c +OBJS_p += src/crypto/crypto_cryptoapi.c +L_CFLAGS += -DCONFIG_CRYPTO_CRYPTOAPI +CONFIG_INTERNAL_SHA256=y +CONFIG_INTERNAL_RC4=y +endif +endif + +ifeq ($(CONFIG_TLS), none) +ifdef TLS_FUNCS +OBJS += src/crypto/tls_none.c +L_CFLAGS += -DEAP_TLS_NONE +CONFIG_INTERNAL_AES=y +CONFIG_INTERNAL_SHA1=y +CONFIG_INTERNAL_MD5=y +endif +OBJS += src/crypto/crypto_none.c +OBJS_p += src/crypto/crypto_none.c +CONFIG_INTERNAL_SHA256=y +CONFIG_INTERNAL_RC4=y +endif + +ifdef TLS_FUNCS +ifdef CONFIG_SMARTCARD +ifndef CONFIG_NATIVE_WINDOWS +ifneq ($(CONFIG_L2_PACKET), freebsd) +LIBS += -ldl +endif +endif +endif +endif + +ifndef TLS_FUNCS +OBJS += src/crypto/tls_none.c +ifeq ($(CONFIG_TLS), internal) +CONFIG_INTERNAL_AES=y +CONFIG_INTERNAL_SHA1=y +CONFIG_INTERNAL_MD5=y +CONFIG_INTERNAL_RC4=y +endif +endif + +AESOBJS = # none so far (see below) +ifdef CONFIG_INTERNAL_AES +AESOBJS += src/crypto/aes-internal.c src/crypto/aes-internal-dec.c +endif + +ifneq ($(CONFIG_TLS), openssl) +NEED_INTERNAL_AES_WRAP=y +endif +ifdef CONFIG_OPENSSL_INTERNAL_AES_WRAP +# Seems to be needed at least with BoringSSL +NEED_INTERNAL_AES_WRAP=y +L_CFLAGS += -DCONFIG_OPENSSL_INTERNAL_AES_WRAP +endif +ifdef CONFIG_FIPS +# Have to use internal AES key wrap routines to use OpenSSL EVP since the +# OpenSSL AES_wrap_key()/AES_unwrap_key() API is not available in FIPS mode. +NEED_INTERNAL_AES_WRAP=y +endif + +ifdef NEED_INTERNAL_AES_WRAP +AESOBJS += src/crypto/aes-unwrap.c +endif +ifdef NEED_AES_EAX +AESOBJS += src/crypto/aes-eax.c +NEED_AES_CTR=y +endif +ifdef NEED_AES_SIV +AESOBJS += src/crypto/aes-siv.c +NEED_AES_CTR=y +endif +ifdef NEED_AES_CTR +AESOBJS += src/crypto/aes-ctr.c +endif +ifdef NEED_AES_ENCBLOCK +AESOBJS += src/crypto/aes-encblock.c +endif +NEED_AES_ENC=y +ifneq ($(CONFIG_TLS), openssl) +AESOBJS += src/crypto/aes-omac1.c +endif +ifdef NEED_AES_WRAP +NEED_AES_ENC=y +ifdef NEED_INTERNAL_AES_WRAP +AESOBJS += src/crypto/aes-wrap.c +endif +endif +ifdef NEED_AES_CBC +NEED_AES_ENC=y +ifneq ($(CONFIG_TLS), openssl) +AESOBJS += src/crypto/aes-cbc.c +endif +endif +ifdef NEED_AES_ENC +ifdef CONFIG_INTERNAL_AES +AESOBJS += src/crypto/aes-internal-enc.c +endif +endif +ifdef NEED_AES +OBJS += $(AESOBJS) +endif + +SHA1OBJS = +ifdef NEED_SHA1 +ifneq ($(CONFIG_TLS), openssl) +ifneq ($(CONFIG_TLS), gnutls) +SHA1OBJS += src/crypto/sha1.c +endif +endif +SHA1OBJS += src/crypto/sha1-prf.c +ifdef CONFIG_INTERNAL_SHA1 +SHA1OBJS += src/crypto/sha1-internal.c +ifdef NEED_FIPS186_2_PRF +SHA1OBJS += src/crypto/fips_prf_internal.c +endif +endif +ifdef CONFIG_NO_WPA_PASSPHRASE +L_CFLAGS += -DCONFIG_NO_PBKDF2 +else +ifneq ($(CONFIG_TLS), openssl) +SHA1OBJS += src/crypto/sha1-pbkdf2.c +endif +endif +ifdef NEED_T_PRF +SHA1OBJS += src/crypto/sha1-tprf.c +endif +ifdef NEED_TLS_PRF +SHA1OBJS += src/crypto/sha1-tlsprf.c +endif +endif + +MD5OBJS = +ifndef CONFIG_FIPS +ifneq ($(CONFIG_TLS), openssl) +ifneq ($(CONFIG_TLS), gnutls) +MD5OBJS += src/crypto/md5.c +endif +endif +endif +ifdef NEED_MD5 +ifdef CONFIG_INTERNAL_MD5 +MD5OBJS += src/crypto/md5-internal.c +endif +OBJS += $(MD5OBJS) +OBJS_p += $(MD5OBJS) +endif + +ifdef NEED_MD4 +ifdef CONFIG_INTERNAL_MD4 +OBJS += src/crypto/md4-internal.c +endif +endif + +DESOBJS = # none needed when not internal +ifdef NEED_DES +ifdef CONFIG_INTERNAL_DES +DESOBJS += src/crypto/des-internal.c +endif +endif + +ifdef CONFIG_NO_RC4 +L_CFLAGS += -DCONFIG_NO_RC4 +endif + +ifdef NEED_RC4 +ifdef CONFIG_INTERNAL_RC4 +ifndef CONFIG_NO_RC4 +OBJS += src/crypto/rc4.c +endif +endif +endif + +ifdef CONFIG_SAE +ifdef NEED_SHA384 +# Need to add HMAC-SHA384 KDF as well, if SHA384 was enabled. +NEED_HMAC_SHA384_KDF=y +endif +ifdef NEED_SHA512 +# Need to add HMAC-SHA512 KDF as well, if SHA512 was enabled. +NEED_HMAC_SHA512_KDF=y +endif +endif + +SHA256OBJS = # none by default +L_CFLAGS += -DCONFIG_SHA256 +ifneq ($(CONFIG_TLS), openssl) +ifneq ($(CONFIG_TLS), gnutls) +SHA256OBJS += src/crypto/sha256.c +endif +endif +SHA256OBJS += src/crypto/sha256-prf.c +ifdef CONFIG_INTERNAL_SHA256 +SHA256OBJS += src/crypto/sha256-internal.c +endif +ifdef CONFIG_INTERNAL_SHA384 +L_CFLAGS += -DCONFIG_INTERNAL_SHA384 +SHA256OBJS += src/crypto/sha384-internal.c +endif +ifdef CONFIG_INTERNAL_SHA512 +L_CFLAGS += -DCONFIG_INTERNAL_SHA512 +SHA256OBJS += src/crypto/sha512-internal.c +endif +ifdef NEED_TLS_PRF_SHA256 +SHA256OBJS += src/crypto/sha256-tlsprf.c +endif +ifdef NEED_TLS_PRF_SHA384 +SHA256OBJS += src/crypto/sha384-tlsprf.c +endif +ifdef NEED_HMAC_SHA256_KDF +L_CFLAGS += -DCONFIG_HMAC_SHA256_KDF +SHA256OBJS += src/crypto/sha256-kdf.c +endif +ifdef NEED_HMAC_SHA384_KDF +L_CFLAGS += -DCONFIG_HMAC_SHA384_KDF +SHA256OBJS += src/crypto/sha384-kdf.c +endif +ifdef NEED_HMAC_SHA512_KDF +L_CFLAGS += -DCONFIG_HMAC_SHA512_KDF +SHA256OBJS += src/crypto/sha512-kdf.c +endif +OBJS += $(SHA256OBJS) +ifdef NEED_SHA384 +L_CFLAGS += -DCONFIG_SHA384 +ifneq ($(CONFIG_TLS), openssl) +ifneq ($(CONFIG_TLS), gnutls) +OBJS += src/crypto/sha384.c +endif +endif +OBJS += src/crypto/sha384-prf.c +endif +ifdef NEED_SHA512 +L_CFLAGS += -DCONFIG_SHA512 +ifneq ($(CONFIG_TLS), openssl) +ifneq ($(CONFIG_TLS), gnutls) +OBJS += src/crypto/sha512.c +endif +endif +OBJS += src/crypto/sha512-prf.c +endif + +ifdef NEED_ASN1 +OBJS += src/tls/asn1.c +endif + +ifdef NEED_DH_GROUPS +OBJS += src/crypto/dh_groups.c +endif +ifdef NEED_DH_GROUPS_ALL +L_CFLAGS += -DALL_DH_GROUPS +endif +ifdef CONFIG_INTERNAL_DH_GROUP5 +ifdef NEED_DH_GROUPS +OBJS += src/crypto/dh_group5.c +endif +endif + +ifdef NEED_ECC +L_CFLAGS += -DCONFIG_ECC +endif + +ifdef CONFIG_NO_RANDOM_POOL +L_CFLAGS += -DCONFIG_NO_RANDOM_POOL +else +OBJS += src/crypto/random.c +endif + +ifdef CONFIG_CTRL_IFACE +ifeq ($(CONFIG_CTRL_IFACE), y) +ifdef CONFIG_NATIVE_WINDOWS +CONFIG_CTRL_IFACE=named_pipe +else +CONFIG_CTRL_IFACE=unix +endif +endif +L_CFLAGS += -DCONFIG_CTRL_IFACE +ifeq ($(CONFIG_CTRL_IFACE), unix) +L_CFLAGS += -DCONFIG_CTRL_IFACE_UNIX +OBJS += src/common/ctrl_iface_common.c +endif +ifeq ($(CONFIG_CTRL_IFACE), udp) +L_CFLAGS += -DCONFIG_CTRL_IFACE_UDP +endif +ifeq ($(CONFIG_CTRL_IFACE), named_pipe) +L_CFLAGS += -DCONFIG_CTRL_IFACE_NAMED_PIPE +endif +ifeq ($(CONFIG_CTRL_IFACE), udp-remote) +CONFIG_CTRL_IFACE=udp +L_CFLAGS += -DCONFIG_CTRL_IFACE_UDP +L_CFLAGS += -DCONFIG_CTRL_IFACE_UDP_REMOTE +endif +OBJS += ctrl_iface.c ctrl_iface_$(CONFIG_CTRL_IFACE).c +endif + +ifdef CONFIG_CTRL_IFACE_DBUS_NEW +L_CFLAGS += -DCONFIG_CTRL_IFACE_DBUS_NEW +OBJS += dbus/dbus_dict_helpers.c +OBJS += dbus/dbus_new_helpers.c +OBJS += dbus/dbus_new.c dbus/dbus_new_handlers.c +OBJS += dbus/dbus_common.c +ifdef CONFIG_WPS +OBJS += dbus/dbus_new_handlers_wps.c +endif +ifdef CONFIG_P2P +OBJS += dbus/dbus_new_handlers_p2p.c +endif +ifdef CONFIG_CTRL_IFACE_DBUS_INTRO +OBJS += dbus/dbus_new_introspect.c +L_CFLAGS += -DCONFIG_CTRL_IFACE_DBUS_INTRO +endif +L_CFLAGS += $(DBUS_INCLUDE) +endif + +ifdef CONFIG_CTRL_IFACE_AIDL +WPA_SUPPLICANT_USE_AIDL=y +L_CFLAGS += -DCONFIG_AIDL -DCONFIG_CTRL_IFACE_AIDL +endif + +ifdef CONFIG_READLINE +OBJS_c += src/utils/edit_readline.c +LIBS_c += -lncurses -lreadline +else +ifdef CONFIG_WPA_CLI_EDIT +OBJS_c += src/utils/edit.c +else +OBJS_c += src/utils/edit_simple.c +endif +endif + +ifdef CONFIG_NATIVE_WINDOWS +L_CFLAGS += -DCONFIG_NATIVE_WINDOWS +LIBS += -lws2_32 -lgdi32 -lcrypt32 +LIBS_c += -lws2_32 +LIBS_p += -lws2_32 -lgdi32 +ifeq ($(CONFIG_CRYPTO), cryptoapi) +LIBS_p += -lcrypt32 +endif +endif + +ifdef CONFIG_NO_STDOUT_DEBUG +L_CFLAGS += -DCONFIG_NO_STDOUT_DEBUG +ifndef CONFIG_CTRL_IFACE +L_CFLAGS += -DCONFIG_NO_WPA_MSG +endif +endif + +ifdef CONFIG_ANDROID_LOG +L_CFLAGS += -DCONFIG_ANDROID_LOG +endif + +ifdef CONFIG_IPV6 +# for eapol_test only +L_CFLAGS += -DCONFIG_IPV6 +endif + +ifdef NEED_BASE64 +OBJS += src/utils/base64.c +endif + +ifdef NEED_SME +OBJS += sme.c +L_CFLAGS += -DCONFIG_SME +endif + +OBJS += src/common/ieee802_11_common.c +OBJS += src/common/hw_features_common.c + +ifdef NEED_EAP_COMMON +OBJS += src/eap_common/eap_common.c +endif + +ifndef CONFIG_MAIN +CONFIG_MAIN=main +endif + +ifdef CONFIG_DEBUG_SYSLOG +L_CFLAGS += -DCONFIG_DEBUG_SYSLOG +ifdef CONFIG_DEBUG_SYSLOG_FACILITY +L_CFLAGS += -DLOG_HOSTAPD="$(CONFIG_DEBUG_SYSLOG_FACILITY)" +endif +endif + +ifdef CONFIG_DEBUG_LINUX_TRACING +L_CFLAGS += -DCONFIG_DEBUG_LINUX_TRACING +endif + +ifdef CONFIG_DEBUG_FILE +L_CFLAGS += -DCONFIG_DEBUG_FILE +endif + +ifdef CONFIG_DELAYED_MIC_ERROR_REPORT +L_CFLAGS += -DCONFIG_DELAYED_MIC_ERROR_REPORT +endif + +ifdef CONFIG_FIPS +L_CFLAGS += -DCONFIG_FIPS +endif + +OBJS += $(SHA1OBJS) $(DESOBJS) + +OBJS_p += $(SHA1OBJS) +OBJS_p += $(SHA256OBJS) + +ifdef CONFIG_BGSCAN_SIMPLE +L_CFLAGS += -DCONFIG_BGSCAN_SIMPLE +OBJS += bgscan_simple.c +NEED_BGSCAN=y +endif + +ifdef CONFIG_BGSCAN_LEARN +L_CFLAGS += -DCONFIG_BGSCAN_LEARN +OBJS += bgscan_learn.c +NEED_BGSCAN=y +endif + +ifdef NEED_BGSCAN +L_CFLAGS += -DCONFIG_BGSCAN +OBJS += bgscan.c +endif + +ifdef CONFIG_AUTOSCAN_EXPONENTIAL +L_CFLAGS += -DCONFIG_AUTOSCAN_EXPONENTIAL +OBJS += autoscan_exponential.c +NEED_AUTOSCAN=y +endif + +ifdef CONFIG_AUTOSCAN_PERIODIC +L_CFLAGS += -DCONFIG_AUTOSCAN_PERIODIC +OBJS += autoscan_periodic.c +NEED_AUTOSCAN=y +endif + +ifdef NEED_AUTOSCAN +L_CFLAGS += -DCONFIG_AUTOSCAN +OBJS += autoscan.c +endif + +ifdef CONFIG_EXT_PASSWORD_TEST +OBJS += src/utils/ext_password_test.c +L_CFLAGS += -DCONFIG_EXT_PASSWORD_TEST +NEED_EXT_PASSWORD=y +endif + +ifdef CONFIG_EXT_PASSWORD_FILE +OBJS += src/utils/ext_password_file.c +L_CFLAGS += -DCONFIG_EXT_PASSWORD_FILE +NEED_EXT_PASSWORD=y +endif + +ifdef NEED_EXT_PASSWORD +OBJS += src/utils/ext_password.c +L_CFLAGS += -DCONFIG_EXT_PASSWORD +endif + +ifdef NEED_GAS_SERVER +OBJS += src/common/gas_server.c +L_CFLAGS += -DCONFIG_GAS_SERVER +NEED_GAS=y +endif + +ifdef NEED_GAS +OBJS += src/common/gas.c +OBJS += gas_query.c +L_CFLAGS += -DCONFIG_GAS +NEED_OFFCHANNEL=y +endif + +ifdef NEED_OFFCHANNEL +OBJS += offchannel.c +L_CFLAGS += -DCONFIG_OFFCHANNEL +endif + +ifdef NEED_JSON +OBJS += src/utils/json.c +L_CFLAGS += -DCONFIG_JSON +endif + +OBJS += src/drivers/driver_common.c + +OBJS += wpa_supplicant.c events.c bssid_ignore.c wpas_glue.c scan.c +OBJS_t := $(OBJS) $(OBJS_l2) eapol_test.c +OBJS_t += src/radius/radius_client.c +OBJS_t += src/radius/radius.c +OBJS_t2 := $(OBJS) $(OBJS_l2) preauth_test.c +OBJS += $(CONFIG_MAIN).c + +ifdef CONFIG_PRIVSEP +OBJS_priv += $(OBJS_d) src/drivers/drivers.c +OBJS_priv += $(OBJS_l2) +OBJS_priv += src/utils/os_$(CONFIG_OS).c +OBJS_priv += src/utils/$(CONFIG_ELOOP).c +OBJS_priv += src/utils/common.c +OBJS_priv += src/utils/wpa_debug.c +OBJS_priv += src/utils/wpabuf.c +OBJS_priv += wpa_priv.c +ifdef CONFIG_DRIVER_NL80211 +OBJS_priv += src/common/ieee802_11_common.c +endif +OBJS += src/l2_packet/l2_packet_privsep.c +OBJS += src/drivers/driver_privsep.c +EXTRA_progs += wpa_priv +else +OBJS += $(OBJS_d) src/drivers/drivers.c +OBJS += $(OBJS_l2) +endif + +ifdef CONFIG_NDIS_EVENTS_INTEGRATED +L_CFLAGS += -DCONFIG_NDIS_EVENTS_INTEGRATED +OBJS += src/drivers/ndis_events.c +EXTRALIBS += -loleaut32 -lole32 -luuid +ifdef PLATFORMSDKLIB +EXTRALIBS += $(PLATFORMSDKLIB)/WbemUuid.Lib +else +EXTRALIBS += WbemUuid.Lib +endif +endif + +ifndef LDO +LDO=$(CC) +endif + +PASNOBJS = +PASNOBJS += src/utils/$(CONFIG_ELOOP).c +PASNOBJS += src/utils/wpa_debug.c +PASNOBJS += src/utils/wpabuf.c +PASNOBJS += src/utils/os_$(CONFIG_OS).c +PASNOBJS += src/utils/config.c +PASNOBJS += src/utils/common.c + +ifdef NEED_BASE64 +PASNOBJS += src/utils/base64.c +endif + +ifdef CONFIG_WPA_TRACE +PASNOBJS += src/utils/trace.c +endif + +ifdef CONFIG_EXT_PASSWORD_FILE +PASNOBJS += src/utils/ext_password_file.c +endif + +ifdef CONFIG_EXT_PASSWORD_TEST +PASNOBJS += src/utils/ext_password_test.c +endif + +ifdef NEED_EXT_PASSWORD +PASNOBJS += src/utils/ext_password.c +endif + +ifdef CONFIG_SAE +PASNOBJS += src/common/sae.c +endif + +ifdef CONFIG_SAE_PK +PASNOBJS += src/common/sae_pk.c +endif + +ifndef CONFIG_NO_WPA +PASNOBJS += src/common/wpa_common.c +endif + +PASNOBJS += src/common/ieee802_11_common.c + +ifdef NEED_DRAGONFLY +PASNOBJS += src/common/dragonfly.c +endif + +PASNOBJS += src/common/ptksa_cache.c + +ifndef CONFIG_NO_WPA +PASNOBJS += src/rsn_supp/pmksa_cache.c +PASNOBJS += src/rsn_supp/wpa_ie.c +endif + +PASNOBJS += src/ap/comeback_token.c +PASNOBJS += src/ap/pmksa_cache_auth.c + +ifdef NEED_EAP_COMMON +PASNOBJS += src/eap_common/eap_common.c +endif + +ifdef CHAP +PASNOBJS += src/eap_common/chap.c +endif + +ifdef CONFIG_IEEE8021X_EAPOL +PASNOBJS += src/eap_peer/eap.c +PASNOBJS += src/eap_peer/eap_methods.c +PASNOBJS += src/eapol_supp/eapol_supp_sm.c +endif + +ifeq ($(CONFIG_TLS), openssl) +PASNOBJS += src/crypto/crypto_openssl.c +ifdef TLS_FUNCS +PASNOBJS += src/crypto/tls_openssl.c +PASNOBJS += src/crypto/tls_openssl_ocsp.c +NEED_TLS_PRF_SHA256=y +endif +endif + +ifeq ($(CONFIG_TLS), gnutls) +PASNOBJS += src/crypto/crypto_$(CONFIG_CRYPTO).c +ifdef TLS_FUNCS +PASNOBJS += src/crypto/tls_gnutls.c +PASNOBJS += -lgnutls -lgpg-error +PASNOBJS += -lgcrypt +endif +endif + +ifdef NEED_TLS_PRF_SHA256 +PASNOBJS += src/crypto/sha256-tlsprf.c +endif + +ifdef NEED_SHA512 +PASNOBJS += src/crypto/sha512-prf.c +endif + +ifdef NEED_SHA384 +PASNOBJS += src/crypto/sha384-prf.c +endif + +PASNOBJS += src/crypto/sha256-prf.c + +ifdef NEED_HMAC_SHA512_KDF +PASNOBJS += src/crypto/sha512-kdf.c +endif + +ifdef NEED_HMAC_SHA384_KDF +PASNOBJS += src/crypto/sha384-kdf.c +endif + +ifdef NEED_HMAC_SHA256_KDF +PASNOBJS += src/crypto/sha256-kdf.c +endif + +ifdef NEED_DH_GROUPS +PASNOBJS += src/crypto/dh_groups.c +endif + +ifdef NEED_AES_SIV +PASNOBJS += src/crypto/aes-siv.c +endif + +ifdef NEED_AES_CTR +PASNOBJS += src/crypto/aes-ctr.c +endif + +ifdef NEED_SHA1 +PASNOBJS += src/crypto/sha1-prf.c +ifdef NEED_TLS_PRF +PASNOBJS += src/crypto/sha1-tlsprf.c +endif +endif + +PASNOBJS += src/pasn/pasn_initiator.c +PASNOBJS += src/pasn/pasn_responder.c + +######################## + +include $(CLEAR_VARS) +LOCAL_MODULE := wpa_cli +LOCAL_LICENSE_KINDS := SPDX-license-identifier-BSD SPDX-license-identifier-BSD-3-Clause SPDX-license-identifier-ISC legacy_unencumbered +LOCAL_LICENSE_CONDITIONS := notice unencumbered +LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../LICENSE +LOCAL_PROPRIETARY_MODULE := true +LOCAL_SHARED_LIBRARIES := libc libcutils liblog +LOCAL_CFLAGS := $(L_CFLAGS) +LOCAL_SRC_FILES := $(OBJS_c) +LOCAL_C_INCLUDES := $(INCLUDES) +include $(BUILD_EXECUTABLE) + +######################## +# Build wpa_supplicant +# +# $(1): if defined build wpa_supplicant with macsec support (with different executable name wpa_supplicant_macsec +# +define wpa_supplicant_gen + +include $(CLEAR_VARS) +LOCAL_LICENSE_KINDS := SPDX-license-identifier-BSD SPDX-license-identifier-BSD-3-Clause SPDX-license-identifier-ISC legacy_unencumbered +LOCAL_LICENSE_CONDITIONS := notice unencumbered +LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../LICENSE +LOCAL_PROPRIETARY_MODULE := true +LOCAL_MODULE_RELATIVE_PATH := hw +ifdef CONFIG_DRIVER_CUSTOM +LOCAL_STATIC_LIBRARIES := libCustomWifi +endif +ifneq ($(BOARD_WPA_SUPPLICANT_PRIVATE_LIB),) +LOCAL_STATIC_LIBRARIES += $(BOARD_WPA_SUPPLICANT_PRIVATE_LIB) +endif +LOCAL_SHARED_LIBRARIES := libc libcutils liblog +ifdef CONFIG_EAP_PROXY +LOCAL_STATIC_LIBRARIES += $(LIB_STATIC_EAP_PROXY) +LOCAL_SHARED_LIBRARIES += $(LIB_SHARED_EAP_PROXY) +endif +ifeq ($(CONFIG_TLS), openssl) +LOCAL_SHARED_LIBRARIES += libcrypto libssl +endif + +# With BoringSSL we need libkeystore-engine in order to provide access to +# keystore keys. +LOCAL_SHARED_LIBRARIES += libkeystore-engine-wifi-hidl + +ifdef CONFIG_DRIVER_NL80211 +ifneq ($(wildcard external/libnl),) +LOCAL_SHARED_LIBRARIES += libnl +else +LOCAL_STATIC_LIBRARIES += libnl_2 +endif +endif +LOCAL_SRC_FILES := $(OBJS) +LOCAL_C_INCLUDES := $(INCLUDES) +ifeq ($(DBUS), y) +LOCAL_SHARED_LIBRARIES += libdbus +endif + +ifneq ($(1),) +# wpa_supplicant for wifi +LOCAL_CFLAGS := $(L_CFLAGS) +LOCAL_MODULE := wpa_supplicant + +ifeq ($(WPA_SUPPLICANT_USE_AIDL), y) +LOCAL_SHARED_LIBRARIES += android.hardware.wifi.supplicant-V3-ndk +LOCAL_SHARED_LIBRARIES += android.system.keystore2-V1-ndk +LOCAL_SHARED_LIBRARIES += libutils libbase +LOCAL_SHARED_LIBRARIES += libbinder_ndk +LOCAL_STATIC_LIBRARIES += libwpa_aidl +LOCAL_VINTF_FRAGMENTS := aidl/android.hardware.wifi.supplicant.xml +ifeq ($(WIFI_HIDL_UNIFIED_SUPPLICANT_SERVICE_RC_ENTRY), true) +LOCAL_INIT_RC=aidl/android.hardware.wifi.supplicant-service.rc +endif +endif + +else +# wpa_supplicant for macsec +# remove aidl control interface, standalone +LOCAL_CFLAGS := $(patsubst -DCONFIG_CTRL_IFACE_AIDL,,$(patsubst -DCONFIG_AIDL,,$(L_CFLAGS))) +LOCAL_CFLAGS += -DCONFIG_MACSEC -DCONFIG_DRIVER_MACSEC_LINUX +# config macsec to use AIDL interface for CAK key. +LOCAL_CFLAGS += -DCONFIG_AIDL_MACSEC_PSK_METHODS +LOCAL_SRC_FILES += ../src/drivers/driver_macsec_linux.c \ + ../src/drivers/driver_wired_common.c +LOCAL_SRC_FILES += wpas_kay.c \ + src/pae/ieee802_1x_cp.c \ + src/pae/ieee802_1x_kay.c \ + src/pae/ieee802_1x_key.c \ + src/pae/ieee802_1x_secy_ops.c +LOCAL_SRC_FILES += src/pae/aidl/aidl_psk.cpp +LOCAL_SHARED_LIBRARIES += android.hardware.macsec-V1-ndk \ + libbinder_ndk +LOCAL_C_INCLUDES += $(LOCAL_PATH)/aidl + +ifdef CONFIG_AP +LOCAL_SRC_FILES += src/ap/wpa_auth_kay.c +endif +LOCAL_MODULE := wpa_supplicant_macsec +endif + +include $(BUILD_EXECUTABLE) +endef + +$(eval $(call wpa_supplicant_gen,)) +$(eval $(call wpa_supplicant_gen, macsec)) + +######################## +# +#include $(CLEAR_VARS) +#LOCAL_MODULE := eapol_test +#ifdef CONFIG_DRIVER_CUSTOM +#LOCAL_STATIC_LIBRARIES := libCustomWifi +#endif +#LOCAL_SHARED_LIBRARIES := libc libcrypto libssl +#LOCAL_CFLAGS := $(L_CFLAGS) +#LOCAL_SRC_FILES := $(OBJS_t) +#LOCAL_C_INCLUDES := $(INCLUDES) +#include $(BUILD_EXECUTABLE) +# +######################## +# +#local_target_dir := $(TARGET_OUT)/etc/wifi +# +#include $(CLEAR_VARS) +#LOCAL_MODULE := wpa_supplicant.conf +#LOCAL_MODULE_CLASS := ETC +#LOCAL_MODULE_PATH := $(local_target_dir) +#LOCAL_SRC_FILES := $(LOCAL_MODULE) +#include $(BUILD_PREBUILT) +# +######################## + +include $(CLEAR_VARS) +LOCAL_MODULE = libwpa_client +LOCAL_LICENSE_KINDS := SPDX-license-identifier-BSD SPDX-license-identifier-BSD-3-Clause SPDX-license-identifier-ISC legacy_unencumbered +LOCAL_LICENSE_CONDITIONS := notice unencumbered +LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../LICENSE +LOCAL_PROPRIETARY_MODULE := true +LOCAL_CFLAGS = $(L_CFLAGS) +LOCAL_SRC_FILES = src/common/wpa_ctrl.c src/utils/os_$(CONFIG_OS).c +LOCAL_C_INCLUDES = $(INCLUDES) +LOCAL_SHARED_LIBRARIES := libcutils liblog +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/wpa_client_include $(LOCAL_PATH)/wpa_client_include/libwpa_client +include $(BUILD_SHARED_LIBRARY) + +ifeq ($(WPA_SUPPLICANT_USE_AIDL), y) +### Aidl service library ### +######################## +include $(CLEAR_VARS) +LOCAL_MODULE := libwpa_aidl +LOCAL_LICENSE_KINDS := SPDX-license-identifier-BSD SPDX-license-identifier-BSD-3-Clause SPDX-license-identifier-ISC legacy_unencumbered +LOCAL_LICENSE_CONDITIONS := notice unencumbered +LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../LICENSE +LOCAL_VENDOR_MODULE := true +LOCAL_CPPFLAGS := $(L_CPPFLAGS) +LOCAL_CFLAGS := $(L_CFLAGS) +LOCAL_C_INCLUDES := $(INCLUDES) +LOCAL_SRC_FILES := \ + aidl/aidl.cpp \ + aidl/aidl_manager.cpp \ + aidl/certificate_utils.cpp \ + aidl/iface_config_utils.cpp \ + aidl/p2p_iface.cpp \ + aidl/p2p_network.cpp \ + aidl/sta_iface.cpp \ + aidl/sta_network.cpp \ + aidl/supplicant.cpp +LOCAL_SHARED_LIBRARIES := \ + android.hardware.wifi.supplicant-V3-ndk \ + android.system.keystore2-V1-ndk \ + libbinder_ndk \ + libbase \ + libutils \ + liblog \ + libssl +LOCAL_EXPORT_C_INCLUDE_DIRS := \ + $(LOCAL_PATH)/aidl +include $(BUILD_STATIC_LIBRARY) +endif # WPA_SUPPLICANT_USE_AIDL == y + +ifeq ($(CONFIG_PASN), y) +include $(CLEAR_VARS) +LOCAL_MODULE = libpasn +LOCAL_LICENSE_KINDS := SPDX-license-identifier-BSD SPDX-license-identifier-BSD-3-Clause SPDX-license-identifier-ISC legacy_unencumbered +LOCAL_LICENSE_CONDITIONS := notice unencumbered +LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../LICENSE +LOCAL_VENDOR_MODULE := true +LOCAL_CFLAGS = $(L_CFLAGS) +LOCAL_SRC_FILES = $(PASNOBJS) +LOCAL_C_INCLUDES = $(INCLUDES) +LOCAL_SHARED_LIBRARIES := libc libcutils liblog +ifeq ($(CONFIG_TLS), openssl) +LOCAL_SHARED_LIBRARIES += libcrypto libssl libkeystore-wifi-hidl +LOCAL_SHARED_LIBRARIES += libkeystore-engine-wifi-hidl +endif +include $(BUILD_SHARED_LIBRARY) +endif # CONFIG_PASN == y diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/init.wifi.rc b/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/init.wifi.rc new file mode 100644 index 000000000..e69de29bb diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/sta_iface.cpp b/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/sta_iface.cpp new file mode 100644 index 000000000..a4c2c3649 --- /dev/null +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/sta_iface.cpp @@ -0,0 +1,2580 @@ +/* + * WPA Supplicant - Sta Iface Aidl interface + * Copyright (c) 2021, Google Inc. All rights reserved. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "aidl_manager.h" +#include "aidl_return_util.h" +#include "iface_config_utils.h" +#include "misc_utils.h" +#include "sta_iface.h" + +extern "C" +{ +#include "utils/eloop.h" +#include "gas_query.h" +#include "interworking.h" +#include "hs20_supplicant.h" +#include "wps_supplicant.h" +#include "dpp.h" +#include "dpp_supplicant.h" +#include "rsn_supp/wpa.h" +#include "rsn_supp/pmksa_cache.h" +} + +namespace { +using aidl::android::hardware::wifi::supplicant::AidlManager; +using aidl::android::hardware::wifi::supplicant::BtCoexistenceMode; +using aidl::android::hardware::wifi::supplicant::ConnectionCapabilities; +using aidl::android::hardware::wifi::supplicant::DppCurve; +using aidl::android::hardware::wifi::supplicant::DppResponderBootstrapInfo; +using aidl::android::hardware::wifi::supplicant::ISupplicant; +using aidl::android::hardware::wifi::supplicant::ISupplicantStaIface; +using aidl::android::hardware::wifi::supplicant::ISupplicantStaNetwork; +using aidl::android::hardware::wifi::supplicant::KeyMgmtMask; +using aidl::android::hardware::wifi::supplicant::LegacyMode; +using aidl::android::hardware::wifi::supplicant::RxFilterType; +using aidl::android::hardware::wifi::supplicant::SupplicantStatusCode; +using aidl::android::hardware::wifi::supplicant::WifiTechnology; +using aidl::android::hardware::wifi::supplicant::misc_utils::createStatus; + +// Enum definition copied from the Vendor HAL interface. +// See android.hardware.wifi.WifiChannelWidthInMhz +enum WifiChannelWidthInMhz { + WIDTH_20 = 0, + WIDTH_40 = 1, + WIDTH_80 = 2, + WIDTH_160 = 3, + WIDTH_80P80 = 4, + WIDTH_5 = 5, + WIDTH_10 = 6, + WIDTH_320 = 7, + WIDTH_INVALID = -1 +}; + +constexpr uint32_t kMaxAnqpElems = 100; +constexpr char kGetMacAddress[] = "MACADDR"; +constexpr char kStartRxFilter[] = "RXFILTER-START"; +constexpr char kStopRxFilter[] = "RXFILTER-STOP"; +constexpr char kAddRxFilter[] = "RXFILTER-ADD"; +constexpr char kRemoveRxFilter[] = "RXFILTER-REMOVE"; +constexpr char kSetBtCoexistenceMode[] = "BTCOEXMODE"; +constexpr char kSetBtCoexistenceScanStart[] = "BTCOEXSCAN-START"; +constexpr char kSetBtCoexistenceScanStop[] = "BTCOEXSCAN-STOP"; +constexpr char kSetSupendModeEnabled[] = "SETSUSPENDMODE 1"; +constexpr char kSetSupendModeDisabled[] = "SETSUSPENDMODE 0"; +constexpr char kSetCountryCode[] = "COUNTRY"; +constexpr uint32_t kExtRadioWorkDefaultTimeoutInSec = + static_cast(ISupplicant::EXT_RADIO_WORK_TIMEOUT_IN_SECS); +constexpr char kExtRadioWorkNamePrefix[] = "ext:"; + +uint8_t convertAidlRxFilterTypeToInternal( + RxFilterType type) +{ + switch (type) { + case RxFilterType::V4_MULTICAST: + return 2; + case RxFilterType::V6_MULTICAST: + return 3; + }; + WPA_ASSERT(false); +} + +uint8_t convertAidlBtCoexModeToInternal( + BtCoexistenceMode mode) +{ + switch (mode) { + case BtCoexistenceMode::ENABLED: + return 0; + case BtCoexistenceMode::DISABLED: + return 1; + case BtCoexistenceMode::SENSE: + return 2; + }; + WPA_ASSERT(false); +} + +ndk::ScopedAStatus doZeroArgDriverCommand( + struct wpa_supplicant *wpa_s, const char *cmd) +{ + std::vector cmd_vec(cmd, cmd + strlen(cmd) + 1); + char driver_cmd_reply_buf[4096] = {}; + if (wpa_drv_driver_cmd( + wpa_s, cmd_vec.data(), driver_cmd_reply_buf, + sizeof(driver_cmd_reply_buf))) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus doOneArgDriverCommand( + struct wpa_supplicant *wpa_s, const char *cmd, uint8_t arg) +{ + std::string cmd_str = std::string(cmd) + " " + std::to_string(arg); + return doZeroArgDriverCommand(wpa_s, cmd_str.c_str()); +} + +ndk::ScopedAStatus doOneArgDriverCommand( + struct wpa_supplicant *wpa_s, const char *cmd, const std::string &arg) +{ + std::string cmd_str = std::string(cmd) + " " + arg; + return doZeroArgDriverCommand(wpa_s, cmd_str.c_str()); +} + +void endExtRadioWork(struct wpa_radio_work *work) +{ + auto *ework = static_cast(work->ctx); + work->wpa_s->ext_work_in_progress = 0; + radio_work_done(work); + os_free(ework); +} + +void extRadioWorkTimeoutCb(void *eloop_ctx, void *timeout_ctx) +{ + auto *work = static_cast(eloop_ctx); + auto *ework = static_cast(work->ctx); + wpa_dbg( + work->wpa_s, MSG_DEBUG, "Timing out external radio work %u (%s)", + ework->id, work->type); + + AidlManager *aidl_manager = AidlManager::getInstance(); + WPA_ASSERT(aidl_manager); + aidl_manager->notifyExtRadioWorkTimeout(work->wpa_s, ework->id); + + endExtRadioWork(work); +} + +void startExtRadioWork(struct wpa_radio_work *work) +{ + auto *ework = static_cast(work->ctx); + work->wpa_s->ext_work_in_progress = 1; + if (!ework->timeout) { + ework->timeout = kExtRadioWorkDefaultTimeoutInSec; + } + eloop_register_timeout( + ework->timeout, 0, extRadioWorkTimeoutCb, work, nullptr); +} + +void extRadioWorkStartCb(struct wpa_radio_work *work, int deinit) +{ + // deinit==1 is invoked during interface removal. Since the AIDL + // interface does not support interface addition/removal, we don't + // need to handle this scenario. + WPA_ASSERT(!deinit); + + auto *ework = static_cast(work->ctx); + wpa_dbg( + work->wpa_s, MSG_DEBUG, "Starting external radio work %u (%s)", + ework->id, ework->type); + + AidlManager *aidl_manager = AidlManager::getInstance(); + WPA_ASSERT(aidl_manager); + aidl_manager->notifyExtRadioWorkStart(work->wpa_s, ework->id); + + startExtRadioWork(work); +} + +KeyMgmtMask convertWpaKeyMgmtCapabilitiesToAidl ( + struct wpa_supplicant *wpa_s, struct wpa_driver_capa *capa) { + + uint32_t mask = 0; + /* Logic from ctrl_iface.c, NONE and IEEE8021X have no capability + * flags and always enabled. + */ + mask |= + (static_cast(KeyMgmtMask::NONE) | + static_cast(KeyMgmtMask::IEEE8021X)); + + if (capa->key_mgmt & + (WPA_DRIVER_CAPA_KEY_MGMT_WPA | WPA_DRIVER_CAPA_KEY_MGMT_WPA2)) { + mask |= static_cast(KeyMgmtMask::WPA_EAP); + } + + if (capa->key_mgmt & (WPA_DRIVER_CAPA_KEY_MGMT_WPA_PSK | + WPA_DRIVER_CAPA_KEY_MGMT_WPA2_PSK)) { + mask |= static_cast(KeyMgmtMask::WPA_PSK); + } +#ifdef CONFIG_SUITEB192 + if (capa->key_mgmt & WPA_DRIVER_CAPA_KEY_MGMT_SUITE_B_192) { + mask |= static_cast(KeyMgmtMask::SUITE_B_192); + } +#endif /* CONFIG_SUITEB192 */ +#ifdef CONFIG_OWE + if (capa->key_mgmt & WPA_DRIVER_CAPA_KEY_MGMT_OWE) { + mask |= static_cast(KeyMgmtMask::OWE); + } +#endif /* CONFIG_OWE */ +#ifdef CONFIG_SAE + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE) { + mask |= static_cast(KeyMgmtMask::SAE); + } +#endif /* CONFIG_SAE */ +#ifdef CONFIG_DPP + if (capa->key_mgmt & WPA_DRIVER_CAPA_KEY_MGMT_DPP) { + mask |= static_cast(KeyMgmtMask::DPP); + } +#endif +#ifdef CONFIG_WAPI_INTERFACE + mask |= static_cast(KeyMgmtMask::WAPI_PSK); + mask |= static_cast(KeyMgmtMask::WAPI_CERT); +#endif /* CONFIG_WAPI_INTERFACE */ +#ifdef CONFIG_FILS + if (capa->key_mgmt & WPA_DRIVER_CAPA_KEY_MGMT_FILS_SHA256) { + mask |= static_cast(KeyMgmtMask::FILS_SHA256); + } + if (capa->key_mgmt & WPA_DRIVER_CAPA_KEY_MGMT_FILS_SHA384) { + mask |= static_cast(KeyMgmtMask::FILS_SHA384); + } +#endif /* CONFIG_FILS */ + return static_cast(mask); +} + +const std::string getDppListenChannel(struct wpa_supplicant *wpa_s, int32_t *listen_channel) +{ + struct hostapd_hw_modes *mode; + int chan44 = 0, chan149 = 0; + *listen_channel = 0; + + /* Check if device support 2.4GHz band*/ + mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, + HOSTAPD_MODE_IEEE80211G, 0); + if (mode) { + *listen_channel = 6; + return "81/6"; + } + /* Check if device support 5GHz band */ + mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, + HOSTAPD_MODE_IEEE80211A, 0); + if (mode) { + for (int i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + + if (chan->flag & (HOSTAPD_CHAN_DISABLED | + HOSTAPD_CHAN_RADAR)) + continue; + if (chan->freq == 5220) + chan44 = 1; + if (chan->freq == 5745) + chan149 = 1; + } + if (chan149) { + *listen_channel = 149; + return "124/149"; + } else if (chan44) { + *listen_channel = 44; + return "115/44"; + } + } + + return ""; +} + +const std::string convertCurveTypeToName(DppCurve curve) +{ + switch (curve) { + case DppCurve::PRIME256V1: + return "prime256v1"; + case DppCurve::SECP384R1: + return "secp384r1"; + case DppCurve::SECP521R1: + return "secp521r1"; + case DppCurve::BRAINPOOLP256R1: + return "brainpoolP256r1"; + case DppCurve::BRAINPOOLP384R1: + return "brainpoolP384r1"; + case DppCurve::BRAINPOOLP512R1: + return "brainpoolP512r1"; + } + WPA_ASSERT(false); +} + +inline std::array macAddrToArray(const uint8_t* mac_addr) { + std::array arr; + std::copy(mac_addr, mac_addr + ETH_ALEN, std::begin(arr)); + return arr; +} + +} // namespace + +namespace aidl { +namespace android { +namespace hardware { +namespace wifi { +namespace supplicant { +using aidl_return_util::validateAndCall; +using misc_utils::createStatus; + +StaIface::StaIface(struct wpa_global *wpa_global, const char ifname[]) + : wpa_global_(wpa_global), ifname_(ifname), is_valid_(true) +{} + +void StaIface::invalidate() { is_valid_ = false; } +bool StaIface::isValid() +{ + return (is_valid_ && (retrieveIfacePtr() != nullptr)); +} + +::ndk::ScopedAStatus StaIface::getName( + std::string* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::getNameInternal, _aidl_return); +} + +::ndk::ScopedAStatus StaIface::getType( + IfaceType* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::getTypeInternal, _aidl_return); +} + +::ndk::ScopedAStatus StaIface::addNetwork( + std::shared_ptr* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::addNetworkInternal, _aidl_return); +} + +::ndk::ScopedAStatus StaIface::removeNetwork( + int32_t in_id) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::removeNetworkInternal, in_id); +} + +::ndk::ScopedAStatus StaIface::filsHlpFlushRequest() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::filsHlpFlushRequestInternal); +} + +::ndk::ScopedAStatus StaIface::filsHlpAddRequest( + const std::vector& in_dst_mac, + const std::vector& in_pkt) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::filsHlpAddRequestInternal, in_dst_mac, in_pkt); +} + +::ndk::ScopedAStatus StaIface::getNetwork( + int32_t in_id, std::shared_ptr* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::getNetworkInternal, _aidl_return, in_id); +} + +::ndk::ScopedAStatus StaIface::listNetworks( + std::vector* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::listNetworksInternal, _aidl_return); +} + +::ndk::ScopedAStatus StaIface::registerCallback( + const std::shared_ptr& in_callback) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::registerCallbackInternal, in_callback); +} + +::ndk::ScopedAStatus StaIface::reassociate() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::reassociateInternal); +} + +::ndk::ScopedAStatus StaIface::reconnect() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::reconnectInternal); +} + +::ndk::ScopedAStatus StaIface::disconnect() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::disconnectInternal); +} + +::ndk::ScopedAStatus StaIface::setPowerSave( + bool in_enable) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setPowerSaveInternal, in_enable); +} + +::ndk::ScopedAStatus StaIface::initiateTdlsDiscover( + const std::vector& in_macAddress) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::initiateTdlsDiscoverInternal, in_macAddress); +} + +::ndk::ScopedAStatus StaIface::initiateTdlsSetup( + const std::vector& in_macAddress) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::initiateTdlsSetupInternal, in_macAddress); +} + +::ndk::ScopedAStatus StaIface::initiateTdlsTeardown( + const std::vector& in_macAddress) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::initiateTdlsTeardownInternal, in_macAddress); +} + +::ndk::ScopedAStatus StaIface::initiateAnqpQuery( + const std::vector& in_macAddress, + const std::vector& in_infoElements, + const std::vector& in_subTypes) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::initiateAnqpQueryInternal, in_macAddress, + in_infoElements, in_subTypes); +} + +::ndk::ScopedAStatus StaIface::initiateVenueUrlAnqpQuery( + const std::vector& in_macAddress) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::initiateVenueUrlAnqpQueryInternal, in_macAddress); +} + +::ndk::ScopedAStatus StaIface::initiateHs20IconQuery( + const std::vector& in_macAddress, + const std::string& in_fileName) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::initiateHs20IconQueryInternal, in_macAddress, + in_fileName); +} + +::ndk::ScopedAStatus StaIface::getMacAddress( + std::vector* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::getMacAddressInternal, _aidl_return); +} + +::ndk::ScopedAStatus StaIface::startRxFilter() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::startRxFilterInternal); +} + +::ndk::ScopedAStatus StaIface::stopRxFilter() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::stopRxFilterInternal); +} + +::ndk::ScopedAStatus StaIface::addRxFilter( + RxFilterType in_type) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::addRxFilterInternal, in_type); +} + +::ndk::ScopedAStatus StaIface::removeRxFilter( + RxFilterType in_type) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::removeRxFilterInternal, in_type); +} + +::ndk::ScopedAStatus StaIface::setBtCoexistenceMode( + BtCoexistenceMode in_mode) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setBtCoexistenceModeInternal, in_mode); +} + +::ndk::ScopedAStatus StaIface::setBtCoexistenceScanModeEnabled( + bool in_enable) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setBtCoexistenceScanModeEnabledInternal, + in_enable); +} + +::ndk::ScopedAStatus StaIface::setSuspendModeEnabled( + bool in_enable) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setSuspendModeEnabledInternal, in_enable); +} + +::ndk::ScopedAStatus StaIface::setCountryCode( + const std::vector& in_code) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setCountryCodeInternal, in_code); +} + +::ndk::ScopedAStatus StaIface::startWpsRegistrar( + const std::vector& in_bssid, + const std::string& in_pin) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::startWpsRegistrarInternal, in_bssid, in_pin); +} + +::ndk::ScopedAStatus StaIface::startWpsPbc( + const std::vector& in_bssid) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::startWpsPbcInternal, in_bssid); +} + +::ndk::ScopedAStatus StaIface::startWpsPinKeypad( + const std::string& in_pin) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::startWpsPinKeypadInternal, in_pin); +} + +::ndk::ScopedAStatus StaIface::startWpsPinDisplay( + const std::vector& in_bssid, + std::string* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::startWpsPinDisplayInternal, _aidl_return, in_bssid); +} + +::ndk::ScopedAStatus StaIface::cancelWps() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::cancelWpsInternal); +} + +::ndk::ScopedAStatus StaIface::setWpsDeviceName( + const std::string& in_name) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setWpsDeviceNameInternal, in_name); +} + +::ndk::ScopedAStatus StaIface::setWpsDeviceType( + const std::vector& in_type) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setWpsDeviceTypeInternal, in_type); +} + +::ndk::ScopedAStatus StaIface::setWpsManufacturer( + const std::string& in_manufacturer) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setWpsManufacturerInternal, in_manufacturer); +} + +::ndk::ScopedAStatus StaIface::setWpsModelName( + const std::string& in_modelName) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setWpsModelNameInternal, in_modelName); +} + +::ndk::ScopedAStatus StaIface::setWpsModelNumber( + const std::string& in_modelNumber) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setWpsModelNumberInternal, in_modelNumber); +} + +::ndk::ScopedAStatus StaIface::setWpsSerialNumber( + const std::string& in_serialNumber) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setWpsSerialNumberInternal, in_serialNumber); +} + +::ndk::ScopedAStatus StaIface::setWpsConfigMethods( + WpsConfigMethods in_configMethods) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setWpsConfigMethodsInternal, in_configMethods); +} + +::ndk::ScopedAStatus StaIface::setExternalSim( + bool in_useExternalSim) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::setExternalSimInternal, in_useExternalSim); +} + +::ndk::ScopedAStatus StaIface::addExtRadioWork( + const std::string& in_name, int32_t in_freqInMhz, + int32_t in_timeoutInSec, + int32_t* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::addExtRadioWorkInternal, _aidl_return, in_name, in_freqInMhz, + in_timeoutInSec); +} + +::ndk::ScopedAStatus StaIface::removeExtRadioWork( + int32_t in_id) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::removeExtRadioWorkInternal, in_id); +} + +::ndk::ScopedAStatus StaIface::enableAutoReconnect( + bool in_enable) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::enableAutoReconnectInternal, in_enable); +} + +::ndk::ScopedAStatus StaIface::getKeyMgmtCapabilities( + KeyMgmtMask* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_NETWORK_INVALID, + &StaIface::getKeyMgmtCapabilitiesInternal, _aidl_return); +} + +::ndk::ScopedAStatus StaIface::addDppPeerUri( + const std::string& in_uri, int32_t* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_NETWORK_INVALID, + &StaIface::addDppPeerUriInternal, _aidl_return, in_uri); +} + +::ndk::ScopedAStatus StaIface::removeDppUri( + int32_t in_id) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_NETWORK_INVALID, + &StaIface::removeDppUriInternal, in_id); +} + +::ndk::ScopedAStatus StaIface::startDppConfiguratorInitiator( + int32_t in_peerBootstrapId, int32_t in_ownBootstrapId, + const std::string& in_ssid, const std::string& in_password, + const std::string& in_psk, DppNetRole in_netRole, + DppAkm in_securityAkm, const std::vector& in_privEcKey, + std::vector* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_NETWORK_INVALID, + &StaIface::startDppConfiguratorInitiatorInternal, _aidl_return, + in_peerBootstrapId,in_ownBootstrapId, in_ssid, in_password, + in_psk, in_netRole, in_securityAkm, in_privEcKey); +} + +::ndk::ScopedAStatus StaIface::startDppEnrolleeInitiator( + int32_t in_peerBootstrapId, int32_t in_ownBootstrapId) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_NETWORK_INVALID, + &StaIface::startDppEnrolleeInitiatorInternal, in_peerBootstrapId, + in_ownBootstrapId); +} + +::ndk::ScopedAStatus StaIface::stopDppInitiator() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_NETWORK_INVALID, + &StaIface::stopDppInitiatorInternal); +} + +::ndk::ScopedAStatus StaIface::getConnectionCapabilities( + ConnectionCapabilities* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::getConnectionCapabilitiesInternal, + _aidl_return); +} + +::ndk::ScopedAStatus StaIface::generateDppBootstrapInfoForResponder( + const std::vector& in_macAddress, const std::string& in_deviceInfo, + DppCurve in_curve, DppResponderBootstrapInfo* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::generateDppBootstrapInfoForResponderInternal, _aidl_return, + in_macAddress, in_deviceInfo, in_curve); +} + +::ndk::ScopedAStatus StaIface::startDppEnrolleeResponder( + int32_t in_listenChannel) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::startDppEnrolleeResponderInternal, in_listenChannel); +} + +::ndk::ScopedAStatus StaIface::stopDppResponder( + int32_t in_ownBootstrapId) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::stopDppResponderInternal, in_ownBootstrapId); +} + +::ndk::ScopedAStatus StaIface::generateSelfDppConfiguration( + const std::string& in_ssid, const std::vector& in_privEcKey) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_IFACE_INVALID, + &StaIface::generateSelfDppConfigurationInternal, in_ssid, in_privEcKey); +} + +::ndk::ScopedAStatus StaIface::getWpaDriverCapabilities( + WpaDriverCapabilitiesMask* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::getWpaDriverCapabilitiesInternal, _aidl_return); +} + +::ndk::ScopedAStatus StaIface::setMboCellularDataStatus( + bool in_available) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::setMboCellularDataStatusInternal, in_available); +} + +::ndk::ScopedAStatus StaIface::setQosPolicyFeatureEnabled( + bool in_enable) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::setQosPolicyFeatureEnabledInternal, in_enable); +} + +::ndk::ScopedAStatus StaIface::sendQosPolicyResponse( + int32_t in_qosPolicyRequestId, bool in_morePolicies, + const std::vector& in_qosPolicyStatusList) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::sendQosPolicyResponseInternal, in_qosPolicyRequestId, + in_morePolicies, in_qosPolicyStatusList); +} + +::ndk::ScopedAStatus StaIface::removeAllQosPolicies() +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::removeAllQosPoliciesInternal); +} + +::ndk::ScopedAStatus StaIface::getConnectionMloLinksInfo(MloLinksInfo* _aidl_return) { + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::getConnectionMloLinksInfoInternal, _aidl_return); +} + +::ndk::ScopedAStatus StaIface::getSignalPollResults( + std::vector *results) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::getSignalPollResultsInternal, results); +} + +::ndk::ScopedAStatus StaIface::addQosPolicyRequestForScs( + const std::vector& in_qosPolicyData, + std::vector* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::addQosPolicyRequestForScsInternal, _aidl_return, in_qosPolicyData); +} + +::ndk::ScopedAStatus StaIface::removeQosPolicyForScs( + const std::vector& in_scsPolicyIds, + std::vector* _aidl_return) +{ + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::removeQosPolicyForScsInternal, _aidl_return, in_scsPolicyIds); +} + +::ndk::ScopedAStatus StaIface::configureMscs(const MscsParams& in_params) { + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::configureMscsInternal, in_params); +} + +::ndk::ScopedAStatus StaIface::disableMscs() { + return validateAndCall( + this, SupplicantStatusCode::FAILURE_UNKNOWN, + &StaIface::disableMscsInternal); +} + +std::pair StaIface::getNameInternal() +{ + return {ifname_, ndk::ScopedAStatus::ok()}; +} + +std::pair StaIface::getTypeInternal() +{ + return {IfaceType::STA, ndk::ScopedAStatus::ok()}; +} + +ndk::ScopedAStatus StaIface::filsHlpFlushRequestInternal() +{ +#ifdef CONFIG_FILS + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + + wpas_flush_fils_hlp_req(wpa_s); + return ndk::ScopedAStatus::ok(); +#else /* CONFIG_FILS */ + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN, ""); +#endif /* CONFIG_FILS */ +} + +ndk::ScopedAStatus StaIface::filsHlpAddRequestInternal( + const std::vector &dst_mac, const std::vector &pkt) +{ +#ifdef CONFIG_FILS + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct fils_hlp_req *req; + + if (!pkt.size()) + return createStatus(SupplicantStatusCode::FAILURE_ARGS_INVALID); + if (dst_mac.size() != ETH_ALEN) + return createStatus(SupplicantStatusCode::FAILURE_ARGS_INVALID); + + + req = (struct fils_hlp_req *)os_zalloc(sizeof(*req)); + if (!req) + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + + os_memcpy(req->dst, dst_mac.data(), ETH_ALEN); + + req->pkt = wpabuf_alloc_copy(pkt.data(), pkt.size()); + if (!req->pkt) { + os_free(req); + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + + dl_list_add_tail(&wpa_s->fils_hlp_req, &req->list); + return ndk::ScopedAStatus::ok(); +#else /* CONFIG_FILS */ + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); +#endif /* CONFIG_FILS */ +} + +std::pair, ndk::ScopedAStatus> +StaIface::addNetworkInternal() +{ + std::shared_ptr network; + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct wpa_ssid *ssid = wpa_supplicant_add_network(wpa_s); + if (!ssid) { + return {network, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + AidlManager *aidl_manager = AidlManager::getInstance(); + if (!aidl_manager || + aidl_manager->getStaNetworkAidlObjectByIfnameAndNetworkId( + wpa_s->ifname, ssid->id, &network)) { + return {network, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + return {network, ndk::ScopedAStatus::ok()}; +} + +ndk::ScopedAStatus StaIface::removeNetworkInternal(int32_t id) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + int result = wpa_supplicant_remove_network(wpa_s, id); + if (result == -1) { + return createStatus(SupplicantStatusCode::FAILURE_NETWORK_UNKNOWN); + } + if (result != 0) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +std::pair, ndk::ScopedAStatus> +StaIface::getNetworkInternal(int32_t id) +{ + std::shared_ptr network; + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct wpa_ssid *ssid = wpa_config_get_network(wpa_s->conf, id); + if (!ssid) { + return {network, createStatus(SupplicantStatusCode::FAILURE_NETWORK_UNKNOWN)}; + } + AidlManager *aidl_manager = AidlManager::getInstance(); + if (!aidl_manager || + aidl_manager->getStaNetworkAidlObjectByIfnameAndNetworkId( + wpa_s->ifname, ssid->id, &network)) { + return {network, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + return {network, ndk::ScopedAStatus::ok()}; +} + +std::pair, ndk::ScopedAStatus> +StaIface::listNetworksInternal() +{ + std::vector network_ids; + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + for (struct wpa_ssid *wpa_ssid = wpa_s->conf->ssid; wpa_ssid; + wpa_ssid = wpa_ssid->next) { + network_ids.emplace_back(wpa_ssid->id); + } + return {std::move(network_ids), ndk::ScopedAStatus::ok()}; +} + +ndk::ScopedAStatus StaIface::registerCallbackInternal( + const std::shared_ptr &callback) +{ + AidlManager *aidl_manager = AidlManager::getInstance(); + if (!aidl_manager || + aidl_manager->addStaIfaceCallbackAidlObject(ifname_, callback)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::reassociateInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + return createStatus(SupplicantStatusCode::FAILURE_IFACE_DISABLED); + } + wpas_request_connection(wpa_s); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::reconnectInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + return createStatus(SupplicantStatusCode::FAILURE_IFACE_DISABLED); + } + if (!wpa_s->disconnected) { + return createStatus(SupplicantStatusCode::FAILURE_IFACE_NOT_DISCONNECTED); + } + wpas_request_connection(wpa_s); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::disconnectInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + return createStatus(SupplicantStatusCode::FAILURE_IFACE_DISABLED); + } + wpas_request_disconnection(wpa_s); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::setPowerSaveInternal(bool enable) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + return createStatus(SupplicantStatusCode::FAILURE_IFACE_DISABLED); + } + if (wpa_drv_set_p2p_powersave(wpa_s, enable, -1, -1)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::initiateTdlsDiscoverInternal( + const std::vector &mac_address) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + int ret; + if (mac_address.size() != ETH_ALEN) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + const u8 *peer = mac_address.data(); + if (wpa_tdls_is_external_setup(wpa_s->wpa)) { + ret = wpa_tdls_send_discovery_request(wpa_s->wpa, peer); + } else { + ret = wpa_drv_tdls_oper(wpa_s, TDLS_DISCOVERY_REQ, peer); + } + if (ret) { + wpa_printf(MSG_INFO, "StaIface: TDLS discover failed: %d", ret); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::initiateTdlsSetupInternal( + const std::vector &mac_address) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + int ret; + if (mac_address.size() != ETH_ALEN) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + const u8 *peer = mac_address.data(); + if (wpa_tdls_is_external_setup(wpa_s->wpa) && + !(wpa_s->conf->tdls_external_control)) { + wpa_tdls_remove(wpa_s->wpa, peer); + ret = wpa_tdls_start(wpa_s->wpa, peer); + } else { + ret = wpa_drv_tdls_oper(wpa_s, TDLS_SETUP, peer); + } + if (ret) { + wpa_printf(MSG_INFO, "StaIface: TDLS setup failed: %d", ret); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::initiateTdlsTeardownInternal( + const std::vector &mac_address) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + int ret; + if (mac_address.size() != ETH_ALEN) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + const u8 *peer = mac_address.data(); + if (wpa_tdls_is_external_setup(wpa_s->wpa) && + !(wpa_s->conf->tdls_external_control)) { + ret = wpa_tdls_teardown_link( + wpa_s->wpa, peer, WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); + } else { + ret = wpa_drv_tdls_oper(wpa_s, TDLS_TEARDOWN, peer); + } + if (ret) { + wpa_printf(MSG_INFO, "StaIface: TDLS teardown failed: %d", ret); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::initiateAnqpQueryInternal( + const std::vector &mac_address, + const std::vector &info_elements, + const std::vector &sub_types) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (info_elements.size() > kMaxAnqpElems) { + return createStatus(SupplicantStatusCode::FAILURE_ARGS_INVALID); + } +#ifdef CONFIG_INTERWORKING + uint16_t info_elems_buf[kMaxAnqpElems]; + uint32_t num_info_elems = 0; + for (const auto &info_element : info_elements) { + info_elems_buf[num_info_elems++] = + static_cast::type>(info_element); + } + uint32_t sub_types_bitmask = 0; + for (const auto &type : sub_types) { + sub_types_bitmask |= BIT( + static_cast::type>(type)); + } + if (mac_address.size() != ETH_ALEN) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + + if (anqp_send_req( + wpa_s, mac_address.data(), 0, info_elems_buf, num_info_elems, + sub_types_bitmask, 0)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +#else + return createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED); +#endif /* CONFIG_INTERWORKING */ +} + +ndk::ScopedAStatus StaIface::initiateVenueUrlAnqpQueryInternal( + const std::vector &mac_address) +{ +#ifdef CONFIG_INTERWORKING + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + uint16_t info_elems_buf[1] = {ANQP_VENUE_URL}; + if (mac_address.size() != ETH_ALEN) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + + if (anqp_send_req( + wpa_s, mac_address.data(), 0, info_elems_buf, 1, 0, 0)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +#else + return createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED); +#endif /* CONFIG_INTERWORKING */ +} + +ndk::ScopedAStatus StaIface::initiateHs20IconQueryInternal( + const std::vector &mac_address, const std::string &file_name) +{ +#ifdef CONFIG_HS20 + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (mac_address.size() != ETH_ALEN) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + wpa_s->fetch_osu_icon_in_progress = 0; + if (hs20_anqp_send_req( + wpa_s, mac_address.data(), BIT(HS20_STYPE_ICON_REQUEST), + reinterpret_cast(file_name.c_str()), + file_name.size(), true)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +#else + return createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED); +#endif /* CONFIG_HS20 */ +} + +std::pair, ndk::ScopedAStatus> +StaIface::getMacAddressInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + std::vector cmd( + kGetMacAddress, kGetMacAddress + sizeof(kGetMacAddress)); + char driver_cmd_reply_buf[4096] = {}; + int ret = wpa_drv_driver_cmd( + wpa_s, cmd.data(), driver_cmd_reply_buf, + sizeof(driver_cmd_reply_buf)); + // Reply is of the format: "Macaddr = XX:XX:XX:XX:XX:XX" + std::string reply_str = driver_cmd_reply_buf; + if (ret < 0 || reply_str.empty() || + reply_str.find("=") == std::string::npos) { + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + // Remove all whitespace first and then split using the delimiter "=". + reply_str.erase( + remove_if(reply_str.begin(), reply_str.end(), isspace), + reply_str.end()); + std::string mac_addr_str = + reply_str.substr(reply_str.find("=") + 1, reply_str.size()); + std::vector mac_addr(6); + if (hwaddr_aton(mac_addr_str.c_str(), mac_addr.data())) { + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + return {mac_addr, ndk::ScopedAStatus::ok()}; +} + +ndk::ScopedAStatus StaIface::startRxFilterInternal() +{ + return doZeroArgDriverCommand(retrieveIfacePtr(), kStartRxFilter); +} + +ndk::ScopedAStatus StaIface::stopRxFilterInternal() +{ + return doZeroArgDriverCommand(retrieveIfacePtr(), kStopRxFilter); +} + +ndk::ScopedAStatus StaIface::addRxFilterInternal( + RxFilterType type) +{ + return doOneArgDriverCommand( + retrieveIfacePtr(), kAddRxFilter, + convertAidlRxFilterTypeToInternal(type)); +} + +ndk::ScopedAStatus StaIface::removeRxFilterInternal( + RxFilterType type) +{ + return doOneArgDriverCommand( + retrieveIfacePtr(), kRemoveRxFilter, + convertAidlRxFilterTypeToInternal(type)); +} + +ndk::ScopedAStatus StaIface::setBtCoexistenceModeInternal( + BtCoexistenceMode mode) +{ + return doOneArgDriverCommand( + retrieveIfacePtr(), kSetBtCoexistenceMode, + convertAidlBtCoexModeToInternal(mode)); +} + +ndk::ScopedAStatus StaIface::setBtCoexistenceScanModeEnabledInternal(bool enable) +{ + const char *cmd; + if (enable) { + cmd = kSetBtCoexistenceScanStart; + } else { + cmd = kSetBtCoexistenceScanStop; + } + return doZeroArgDriverCommand(retrieveIfacePtr(), cmd); +} + +ndk::ScopedAStatus StaIface::setSuspendModeEnabledInternal(bool enable) +{ + const char *cmd; + if (enable) { + cmd = kSetSupendModeEnabled; + } else { + cmd = kSetSupendModeDisabled; + } + return doZeroArgDriverCommand(retrieveIfacePtr(), cmd); +} + +ndk::ScopedAStatus StaIface::setCountryCodeInternal( + const std::vector &code) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + //2-Character alphanumeric country code + if (code.size() != 2) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + ndk::ScopedAStatus status = doOneArgDriverCommand( + wpa_s, kSetCountryCode, + std::string(std::begin(code), std::end(code))); + if (!status.isOk()) { + return status; + } + struct p2p_data *p2p = wpa_s->global->p2p; + if (p2p) { + char country[3]; + country[0] = code[0]; + country[1] = code[1]; + country[2] = 0x04; + p2p_set_country(p2p, country); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::startWpsRegistrarInternal( + const std::vector &bssid, const std::string &pin) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (bssid.size() != ETH_ALEN) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + if (wpas_wps_start_reg(wpa_s, bssid.data(), pin.c_str(), nullptr)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::startWpsPbcInternal( + const std::vector &bssid) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (bssid.size() != ETH_ALEN) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + const uint8_t *bssid_addr = + is_zero_ether_addr(bssid.data()) ? nullptr : bssid.data(); + if (wpas_wps_start_pbc(wpa_s, bssid_addr, 0, 0)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::startWpsPinKeypadInternal(const std::string &pin) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (wpas_wps_start_pin( + wpa_s, nullptr, pin.c_str(), 0, DEV_PW_DEFAULT)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +std::pair StaIface::startWpsPinDisplayInternal( + const std::vector &bssid) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (bssid.size() != ETH_ALEN) { + return {"", createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + const uint8_t *bssid_addr = + is_zero_ether_addr(bssid.data()) ? nullptr : bssid.data(); + int pin = + wpas_wps_start_pin(wpa_s, bssid_addr, nullptr, 0, DEV_PW_DEFAULT); + if (pin < 0) { + return {"", createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + return {misc_utils::convertWpsPinToString(pin), + ndk::ScopedAStatus::ok()}; +} + +ndk::ScopedAStatus StaIface::cancelWpsInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + if (wpas_wps_cancel(wpa_s)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::setWpsDeviceNameInternal(const std::string &name) +{ + return iface_config_utils::setWpsDeviceName(retrieveIfacePtr(), name); +} + +ndk::ScopedAStatus StaIface::setWpsDeviceTypeInternal( + const std::vector &type) +{ + std::array type_arr; + std::copy_n(type.begin(), 8, type_arr.begin()); + return iface_config_utils::setWpsDeviceType(retrieveIfacePtr(), type_arr); +} + +ndk::ScopedAStatus StaIface::setWpsManufacturerInternal( + const std::string &manufacturer) +{ + return iface_config_utils::setWpsManufacturer( + retrieveIfacePtr(), manufacturer); +} + +ndk::ScopedAStatus StaIface::setWpsModelNameInternal( + const std::string &model_name) +{ + return iface_config_utils::setWpsModelName( + retrieveIfacePtr(), model_name); +} + +ndk::ScopedAStatus StaIface::setWpsModelNumberInternal( + const std::string &model_number) +{ + return iface_config_utils::setWpsModelNumber( + retrieveIfacePtr(), model_number); +} + +ndk::ScopedAStatus StaIface::setWpsSerialNumberInternal( + const std::string &serial_number) +{ + return iface_config_utils::setWpsSerialNumber( + retrieveIfacePtr(), serial_number); +} + +ndk::ScopedAStatus StaIface::setWpsConfigMethodsInternal(WpsConfigMethods config_methods) +{ + return iface_config_utils::setWpsConfigMethods( + retrieveIfacePtr(), static_cast(config_methods)); +} + +ndk::ScopedAStatus StaIface::setExternalSimInternal(bool useExternalSim) +{ + return iface_config_utils::setExternalSim( + retrieveIfacePtr(), useExternalSim); +} + +std::pair StaIface::addExtRadioWorkInternal( + const std::string &name, uint32_t freq_in_mhz, uint32_t timeout_in_sec) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + auto *ework = static_cast( + os_zalloc(sizeof(struct wpa_external_work))); + if (!ework) { + return {UINT32_MAX, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + + std::string radio_work_name = kExtRadioWorkNamePrefix + name; + os_strlcpy(ework->type, radio_work_name.c_str(), sizeof(ework->type)); + ework->timeout = timeout_in_sec; + wpa_s->ext_work_id++; + if (wpa_s->ext_work_id == 0) { + wpa_s->ext_work_id++; + } + ework->id = wpa_s->ext_work_id; + + if (radio_add_work( + wpa_s, freq_in_mhz, ework->type, 0, extRadioWorkStartCb, + ework)) { + os_free(ework); + return {UINT32_MAX, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + return {ework->id, ndk::ScopedAStatus::ok()}; +} + +ndk::ScopedAStatus StaIface::removeExtRadioWorkInternal(uint32_t id) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct wpa_radio_work *work; + dl_list_for_each(work, &wpa_s->radio->work, struct wpa_radio_work, list) + { + if (os_strncmp( + work->type, kExtRadioWorkNamePrefix, + sizeof(kExtRadioWorkNamePrefix)) != 0) + continue; + + auto *ework = + static_cast(work->ctx); + if (ework->id != id) + continue; + + wpa_dbg( + wpa_s, MSG_DEBUG, "Completed external radio work %u (%s)", + ework->id, ework->type); + eloop_cancel_timeout(extRadioWorkTimeoutCb, work, NULL); + endExtRadioWork(work); + + return ndk::ScopedAStatus::ok(); + } + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); +} + +ndk::ScopedAStatus StaIface::enableAutoReconnectInternal(bool enable) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + wpa_s->auto_reconnect_disabled = enable ? 0 : 1; + return ndk::ScopedAStatus::ok(); +} + +std::pair +StaIface::addDppPeerUriInternal(const std::string& uri) +{ +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + int32_t id; + + id = wpas_dpp_qr_code(wpa_s, uri.c_str()); + + if (id > 0) { + return {id, ndk::ScopedAStatus::ok()}; + } +#endif + return {-1, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; +} + +ndk::ScopedAStatus StaIface::removeDppUriInternal(uint32_t bootstrap_id) +{ +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + std::string bootstrap_id_str; + + if (bootstrap_id == 0) { + bootstrap_id_str = "*"; + } + else { + bootstrap_id_str = std::to_string(bootstrap_id); + } + + if (dpp_bootstrap_remove(wpa_s->dpp, bootstrap_id_str.c_str()) >= 0) { + return ndk::ScopedAStatus::ok(); + } +#endif + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); +} + +std::pair, ndk::ScopedAStatus> +StaIface::startDppConfiguratorInitiatorInternal( + uint32_t peer_bootstrap_id, uint32_t own_bootstrap_id, + const std::string& ssid, const std::string& password, + const std::string& psk, DppNetRole net_role, DppAkm security_akm, + const std::vector &privEcKey) +{ +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + std::string cmd = ""; + std::string cmd2 = ""; + int32_t id; + char key[1024]; + + if (net_role != DppNetRole::AP && + net_role != DppNetRole::STA) { + wpa_printf(MSG_ERROR, + "DPP: Error: Invalid network role specified: %d", net_role); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + + cmd += " peer=" + std::to_string(peer_bootstrap_id); + cmd += (own_bootstrap_id > 0) ? + " own=" + std::to_string(own_bootstrap_id) : ""; + + /* Check for supported AKMs */ + if (security_akm != DppAkm::PSK && security_akm != DppAkm::SAE && + security_akm != DppAkm::PSK_SAE && security_akm != DppAkm::DPP) { + wpa_printf(MSG_ERROR, "DPP: Error: invalid AKM specified: %d", + security_akm); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + + /* SAE AKM requires SSID and password to be initialized */ + if ((security_akm == DppAkm::SAE || + security_akm == DppAkm::PSK_SAE) && + (ssid.empty() || password.empty())) { + wpa_printf(MSG_ERROR, "DPP: Error: Password or SSID not specified"); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } else if (security_akm == DppAkm::PSK || + security_akm == DppAkm::PSK_SAE) { + /* PSK AKM requires SSID and password/psk to be initialized */ + if (ssid.empty()) { + wpa_printf(MSG_ERROR, "DPP: Error: SSID not specified"); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + if (password.empty() && psk.empty()) { + wpa_printf(MSG_ERROR, "DPP: Error: Password or PSK not specified"); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + } + + cmd += " role=configurator"; + cmd += (ssid.empty()) ? "" : " ssid=" + ssid; + + if (!psk.empty()) { + cmd += " psk=" + psk; + } else { + cmd += (password.empty()) ? "" : " pass=" + password; + } + + std::string role = ""; + if (net_role == DppNetRole::AP) { + role = "ap-"; + } + else { + role = "sta-"; + } + + switch (security_akm) { + case DppAkm::PSK: + role += "psk"; + break; + + case DppAkm::SAE: + role += "sae"; + break; + + case DppAkm::PSK_SAE: + role += "psk-sae"; + break; + + case DppAkm::DPP: + role += "dpp"; + break; + + default: + wpa_printf(MSG_ERROR, + "DPP: Invalid or unsupported security AKM specified: %d", security_akm); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + + cmd += " conf="; + cmd += role; + + if (net_role == DppNetRole::STA) { + /* DPP R2 connection status request */ + cmd += " conn_status=1"; + } + + if (security_akm == DppAkm::DPP) { + if (!privEcKey.empty()) { + cmd2 += " key=" + std::string(privEcKey.begin(), privEcKey.end()); + } + id = dpp_configurator_add(wpa_s->dpp, cmd2.c_str()); + if (id < 0 || (privEcKey.empty() && + (dpp_configurator_get_key_id(wpa_s->dpp, id, key, sizeof(key)) < 0))) + { + wpa_printf(MSG_ERROR, "DPP configurator add failed. " + "Input key might be incorrect"); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + + cmd += " configurator=" + std::to_string(id); + } + + wpa_printf(MSG_DEBUG, + "DPP initiator command: %s", cmd.c_str()); + + if (wpas_dpp_auth_init(wpa_s, cmd.c_str()) == 0) { + // Return key if input privEcKey was null/empty. + if (security_akm == DppAkm::DPP && privEcKey.empty()) { + std::string k(key); + std::vector vKey(k.begin(), k.end()); + return {vKey, ndk::ScopedAStatus::ok()}; + } + return {std::vector(), ndk::ScopedAStatus::ok()}; + } +#endif + return {std::vector(), createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; +} + +ndk::ScopedAStatus StaIface::startDppEnrolleeInitiatorInternal( + uint32_t peer_bootstrap_id, uint32_t own_bootstrap_id) { +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + std::string cmd = ""; + + /* Report received configuration to AIDL and create an internal profile */ + wpa_s->conf->dpp_config_processing = 1; + + cmd += " peer=" + std::to_string(peer_bootstrap_id); + cmd += (own_bootstrap_id > 0) ? + " own=" + std::to_string(own_bootstrap_id) : ""; + + cmd += " role=enrollee"; + + wpa_printf(MSG_DEBUG, + "DPP initiator command: %s", cmd.c_str()); + + if (wpas_dpp_auth_init(wpa_s, cmd.c_str()) == 0) { + return ndk::ScopedAStatus::ok(); + } +#endif + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); +} +ndk::ScopedAStatus StaIface::stopDppInitiatorInternal() +{ +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + + wpas_dpp_stop(wpa_s); + return ndk::ScopedAStatus::ok(); +#else + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); +#endif +} + +std::pair +StaIface::generateDppBootstrapInfoForResponderInternal( + const std::vector &mac_address, + const std::string& device_info, DppCurve curve) +{ +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + std::string cmd = "type=qrcode"; + int32_t id; + int32_t listen_channel = 0; + DppResponderBootstrapInfo bootstrap_info; + const char *uri; + std::string listen_channel_str; + std::string mac_addr_str; + char buf[3] = {0}; + + cmd += (device_info.empty()) ? "" : " info=" + device_info; + + listen_channel_str = getDppListenChannel(wpa_s, &listen_channel); + if (listen_channel == 0) { + wpa_printf(MSG_ERROR, "StaIface: Failed to derive DPP listen channel"); + return {bootstrap_info, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + cmd += " chan=" + listen_channel_str; + + if (mac_address.size() != ETH_ALEN) { + return {bootstrap_info, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + cmd += " mac="; + for (int i = 0;i < 6;i++) { + snprintf(buf, sizeof(buf), "%02x", mac_address[i]); + mac_addr_str.append(buf); + } + cmd += mac_addr_str; + + cmd += " curve=" + convertCurveTypeToName(curve); + + id = dpp_bootstrap_gen(wpa_s->dpp, cmd.c_str()); + wpa_printf(MSG_DEBUG, + "DPP generate bootstrap QR code command: %s id: %d", cmd.c_str(), id); + if (id > 0) { + uri = dpp_bootstrap_get_uri(wpa_s->dpp, id); + if (uri) { + wpa_printf(MSG_DEBUG, "DPP Bootstrap info: id: %d " + "listen_channel: %d uri: %s", id, listen_channel, uri); + bootstrap_info.bootstrapId = id; + bootstrap_info.listenChannel = listen_channel; + bootstrap_info.uri = uri; + return {bootstrap_info, ndk::ScopedAStatus::ok()}; + } + } + return {bootstrap_info, createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; +#else + return {bootstrap_info, createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED)}; +#endif +} + +ndk::ScopedAStatus StaIface::startDppEnrolleeResponderInternal(uint32_t listen_channel) +{ +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + std::string cmd = ""; + uint32_t freq = (listen_channel <= 14 ? 2407 : 5000) + listen_channel * 5; + + /* Report received configuration to AIDL and create an internal profile */ + wpa_s->conf->dpp_config_processing = 1; + + cmd += std::to_string(freq); + cmd += " role=enrollee netrole=sta"; + + wpa_printf(MSG_DEBUG, + "DPP Enrollee Responder command: %s", cmd.c_str()); + + if (wpas_dpp_listen(wpa_s, cmd.c_str()) == 0) { + return ndk::ScopedAStatus::ok(); + } + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); +#else + return createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED); +#endif +} + +ndk::ScopedAStatus StaIface::stopDppResponderInternal(uint32_t own_bootstrap_id) +{ +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + std::string bootstrap_id_str; + + if (own_bootstrap_id == 0) { + bootstrap_id_str = "*"; + } + else { + bootstrap_id_str = std::to_string(own_bootstrap_id); + } + + wpa_printf(MSG_DEBUG, "DPP Stop DPP Responder id: %d ", own_bootstrap_id); + wpas_dpp_stop(wpa_s); + wpas_dpp_listen_stop(wpa_s); + + if (dpp_bootstrap_remove(wpa_s->dpp, bootstrap_id_str.c_str()) < 0) { + wpa_printf(MSG_ERROR, "StaIface: dpp_bootstrap_remove failed"); + } + + return ndk::ScopedAStatus::ok(); +#else + return createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED); +#endif +} + +ndk::ScopedAStatus StaIface::generateSelfDppConfigurationInternal(const std::string& ssid, + const std::vector &privEcKey) +{ +#ifdef CONFIG_DPP + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + std::string cmd = ""; + char *ssid_hex_str; + int len; + int32_t id; + + if (ssid.empty() || privEcKey.empty()) { + wpa_printf(MSG_ERROR, "DPP generate self configuration failed. ssid/key empty"); + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + + cmd += " key=" + std::string(privEcKey.begin(), privEcKey.end()); + + id = dpp_configurator_add(wpa_s->dpp, cmd.c_str()); + if (id < 0) { + wpa_printf(MSG_ERROR, "DPP configurator add failed. Input key might be incorrect"); + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + + cmd = " conf=sta-dpp"; + cmd += " configurator=" + std::to_string(id); + + ssid_hex_str = (char *) os_zalloc(ssid.size() * 2 + 1); + if (!ssid_hex_str) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + + wpa_snprintf_hex(ssid_hex_str, ssid.size() * 2 + 1, (u8*)ssid.data(), ssid.size()); + cmd += " ssid=" + std::string(ssid_hex_str); + + /* Report received configuration to AIDL and create an internal profile */ + wpa_s->conf->dpp_config_processing = 1; + + if (wpas_dpp_configurator_sign(wpa_s, cmd.c_str()) == 0) { + os_free(ssid_hex_str); + return ndk::ScopedAStatus::ok(); + } + + os_free(ssid_hex_str); + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); +#else + return createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED); +#endif +} + +std::pair +StaIface::getConnectionCapabilitiesInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + ConnectionCapabilities capa; + + if (wpa_s->connection_set) { + capa.legacyMode = LegacyMode::UNKNOWN; + if (wpa_s->connection_eht) { + capa.technology = WifiTechnology::EHT; + } else if (wpa_s->connection_he) { + capa.technology = WifiTechnology::HE; + } else if (wpa_s->connection_vht) { + capa.technology = WifiTechnology::VHT; + } else if (wpa_s->connection_ht) { + capa.technology = WifiTechnology::HT; + } else { + capa.technology = WifiTechnology::LEGACY; + if (wpas_freq_to_band(wpa_s->assoc_freq) == BAND_2_4_GHZ) { + capa.legacyMode = (wpa_s->connection_11b_only) ? LegacyMode::B_MODE + : LegacyMode::G_MODE; + } else { + capa.legacyMode = LegacyMode::A_MODE; + } + } + switch (wpa_s->connection_channel_bandwidth) { + case CHAN_WIDTH_20: + capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_20; + break; + case CHAN_WIDTH_40: + capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_40; + break; + case CHAN_WIDTH_80: + capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_80; + break; + case CHAN_WIDTH_160: + capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_160; + break; + case CHAN_WIDTH_80P80: + capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_80P80; + break; + case CHAN_WIDTH_320: + capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_320; + break; + default: + capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_20; + break; + } + capa.maxNumberRxSpatialStreams = wpa_s->connection_max_nss_rx; + capa.maxNumberTxSpatialStreams = wpa_s->connection_max_nss_tx; + capa.apTidToLinkMapNegotiationSupported = wpa_s->ap_t2lm_negotiation_support; + } else { + capa.technology = WifiTechnology::UNKNOWN; + capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_20; + capa.maxNumberTxSpatialStreams = 1; + capa.maxNumberRxSpatialStreams = 1; + capa.legacyMode = LegacyMode::UNKNOWN; + } + return {capa, ndk::ScopedAStatus::ok()}; +} + +std::pair +StaIface::getWpaDriverCapabilitiesInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + uint32_t mask = 0; + +#ifdef CONFIG_MBO + /* MBO has no capability flags. It's mainly legacy 802.11v BSS + * transition + Cellular steering. 11v is a default feature in + * supplicant. And cellular steering is handled in framework. + */ + mask |= static_cast(WpaDriverCapabilitiesMask::MBO); + if (wpa_s->enable_oce & OCE_STA) { + mask |= static_cast(WpaDriverCapabilitiesMask::OCE); + } +#endif +#ifdef CONFIG_SAE_PK + mask |= static_cast(WpaDriverCapabilitiesMask::SAE_PK); +#endif + mask |= static_cast(WpaDriverCapabilitiesMask::WFD_R2); + + mask |= static_cast(WpaDriverCapabilitiesMask::TRUST_ON_FIRST_USE); + + mask |= static_cast(WpaDriverCapabilitiesMask::SET_TLS_MINIMUM_VERSION); + +#ifdef EAP_TLSV1_3 + mask |= static_cast(WpaDriverCapabilitiesMask::TLS_V1_3); +#endif + + wpa_printf(MSG_DEBUG, "Driver capability mask: 0x%x", mask); + + return {static_cast(mask), + ndk::ScopedAStatus::ok()}; +} + +ndk::ScopedAStatus StaIface::setMboCellularDataStatusInternal(bool available) +{ +#ifdef CONFIG_MBO + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + enum mbo_cellular_capa mbo_cell_capa; + + if (available) { + mbo_cell_capa = MBO_CELL_CAPA_AVAILABLE; + } else { + mbo_cell_capa = MBO_CELL_CAPA_NOT_AVAILABLE; + } + +#ifdef ENABLE_PRIV_CMD_UPDATE_MBO_CELL_STATUS + char mbo_cmd[32]; + char buf[32]; + + os_snprintf(mbo_cmd, sizeof(mbo_cmd), "%s %d", "MBO CELL_DATA_CAP", mbo_cell_capa); + if (wpa_drv_driver_cmd(wpa_s, mbo_cmd, buf, sizeof(buf)) < 0) { + wpa_printf(MSG_ERROR, "MBO CELL_DATA_CAP cmd failed CAP:%d", mbo_cell_capa); + } +#else + wpas_mbo_update_cell_capa(wpa_s, mbo_cell_capa); +#endif + + return ndk::ScopedAStatus::ok(); +#else + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); +#endif +} + +std::pair +StaIface::getKeyMgmtCapabilitiesInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct wpa_driver_capa capa; + + /* Get capabilities from driver and populate the key management mask */ + if (wpa_drv_get_capa(wpa_s, &capa) < 0) { + return {static_cast(0), + createStatus(SupplicantStatusCode::FAILURE_UNKNOWN)}; + } + + return {convertWpaKeyMgmtCapabilitiesToAidl(wpa_s, &capa), + ndk::ScopedAStatus::ok()}; +} + +ndk::ScopedAStatus StaIface::setQosPolicyFeatureEnabledInternal(bool enable) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + wpa_s->enable_dscp_policy_capa = enable ? 1 : 0; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::sendQosPolicyResponseInternal( + int32_t qos_policy_request_id, bool more_policies, + const std::vector& qos_policy_status_list) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct dscp_resp_data resp_data; + int num_policies = qos_policy_status_list.size(); + + memset(&resp_data, 0, sizeof(resp_data)); + + resp_data.more = more_policies ? 1 : 0; + resp_data.policy = (struct dscp_policy_status *) malloc( + sizeof(struct dscp_policy_status) * num_policies); + if (num_policies && !resp_data.policy){ + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + + resp_data.solicited = true; + wpa_s->dscp_req_dialog_token = qos_policy_request_id; + + for (int i = 0; i < num_policies; i++) { + resp_data.policy[i].id = qos_policy_status_list.at(i).policyId; + resp_data.policy[i].status = + static_cast(qos_policy_status_list.at(i).status); + } + resp_data.num_policies = num_policies; + + if (wpas_send_dscp_response(wpa_s, &resp_data)) { + free(resp_data.policy); + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + + free(resp_data.policy); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StaIface::removeAllQosPoliciesInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct dscp_resp_data resp_data; + + memset(&resp_data, 0, sizeof(resp_data)); + resp_data.reset = true; + resp_data.solicited = false; + wpa_s->dscp_req_dialog_token = 0; + + if (wpas_send_dscp_response(wpa_s, &resp_data)) { + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); + } + return ndk::ScopedAStatus::ok(); +} + +std::pair StaIface::getConnectionMloLinksInfoInternal() +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct driver_sta_mlo_info mlo; + MloLinksInfo linksInfo; + MloLink link; + + linksInfo.apMldMacAddress = macAddrToArray(wpa_s->ap_mld_addr); + if (!wpa_s->valid_links) + return {linksInfo, ndk::ScopedAStatus::ok()}; + + wpas_drv_get_sta_mlo_info(wpa_s, &mlo); + for (int i = 0; i < MAX_NUM_MLD_LINKS; i++) { + if (!(wpa_s->valid_links & BIT(i))) + continue; + + wpa_printf(MSG_DEBUG, "Add MLO Link ID %d info", i); + // Associated link id. + if (os_memcmp(wpa_s->links[i].bssid, wpa_s->bssid, ETH_ALEN) == 0) { + linksInfo.apMloLinkId = i; + } + link.linkId = i; + link.staLinkMacAddress.assign( + wpa_s->links[i].addr, wpa_s->links[i].addr + ETH_ALEN); + link.apLinkMacAddress = macAddrToArray(wpa_s->links[i].bssid); + link.frequencyMHz = wpa_s->links[i].freq; + // TODO (b/259710591): Once supplicant implements TID-to-link + // mapping, copy it here. Mapping can be changed in two + // scenarios + // 1. Mandatory mapping from AP + // 2. Negotiated mapping + // After association, framework call this API to get + // MloLinksInfo. If there is an update in mapping later, notify + // framework on the change using the callback, + // ISupplicantStaIfaceCallback.onMloLinksInfoChanged() with + // reason code as TID_TO_LINK_MAP. In absence of an advertised + // mapping by the AP, a default TID-to-link mapping is assumed + // unless an individual TID-to-link mapping is successfully + // negotiated. + if (!mlo.default_map) { + link.tidsUplinkMap = mlo.links[i].t2lmap.uplink; + link.tidsDownlinkMap = mlo.links[i].t2lmap.downlink; + } else { + link.tidsUplinkMap = 0xFF; + link.tidsDownlinkMap = 0xFF; + } + linksInfo.links.push_back(link); + } + + return {linksInfo, ndk::ScopedAStatus::ok()}; +} + +std::pair, ndk::ScopedAStatus> +StaIface::getSignalPollResultsInternal() +{ + std::vector results; + struct wpa_signal_info si; + struct wpa_mlo_signal_info mlo_si; + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + + if (wpa_s->valid_links && (wpa_drv_mlo_signal_poll(wpa_s, &mlo_si) == 0)) { + for (int i = 0; i < MAX_NUM_MLD_LINKS; i++) { + if (!(mlo_si.valid_links & BIT(i))) + continue; + + SignalPollResult result; + result.linkId = i; + result.currentRssiDbm = mlo_si.links[i].data.signal; + result.txBitrateMbps = mlo_si.links[i].data.current_tx_rate / 1000; + result.rxBitrateMbps = mlo_si.links[i].data.current_rx_rate / 1000; + result.frequencyMhz = mlo_si.links[i].frequency; + results.push_back(result); + } + } else if (wpa_drv_signal_poll(wpa_s, &si) == 0) { + SignalPollResult result; + result.linkId = 0; + result.currentRssiDbm = si.data.signal; + result.txBitrateMbps = si.data.current_tx_rate / 1000; + result.rxBitrateMbps = si.data.current_rx_rate / 1000; + result.frequencyMhz = si.frequency; + results.push_back(result); + } + + return {results, ndk::ScopedAStatus::ok()}; +} + +static int set_type4_frame_classifier(QosPolicyScsData qos_policy_data, + struct type4_params *param) +{ + u8 classifier_mask = 0; + uint32_t inMask = static_cast(qos_policy_data.classifierParams.classifierParamMask); + + if (qos_policy_data.classifierParams.ipVersion == + IpVersion::VERSION_4) { + param->ip_version = IPV4; + } else if (qos_policy_data.classifierParams.ipVersion == + IpVersion::VERSION_6) { + param->ip_version = IPV6; + } else { + wpa_printf(MSG_ERROR, "IP version missing/invalid"); + return -1; + } + + /* Classifier Mask - bit 0 = Ip Version */ + classifier_mask |= BIT(0); + + if (inMask & static_cast(QosPolicyClassifierParamsMask::SRC_IP)) { + if (param->ip_version == IPV4) { + if (qos_policy_data.classifierParams.srcIp.size() != + sizeof(param->ip_params.v4.src_ip)) { + wpa_printf(MSG_ERROR, "Invalid source IP"); + return -1; + } + os_memcpy(¶m->ip_params.v4.src_ip, qos_policy_data.classifierParams.srcIp.data(), 4); + } else { + if (qos_policy_data.classifierParams.srcIp.size() != + sizeof(param->ip_params.v6.src_ip)) { + wpa_printf(MSG_ERROR, "Invalid source IP"); + return -1; + } + os_memcpy(¶m->ip_params.v6.src_ip, qos_policy_data.classifierParams.srcIp.data(), 16); + } + + /* Classifier Mask - bit 1 = Source IP Address */ + classifier_mask |= BIT(1); + } + + if (inMask & static_cast(QosPolicyClassifierParamsMask::DST_IP)) { + if (param->ip_version == IPV4) { + if (qos_policy_data.classifierParams.dstIp.size() != + sizeof(param->ip_params.v4.dst_ip)) { + wpa_printf(MSG_ERROR, "Invalid destination IP"); + return -1; + } + os_memcpy(¶m->ip_params.v4.dst_ip, qos_policy_data.classifierParams.dstIp.data(), 4); + } else { + if (qos_policy_data.classifierParams.dstIp.size() != + sizeof(param->ip_params.v6.dst_ip)) { + wpa_printf(MSG_ERROR, "Invalid destination IP"); + return -1; + } + os_memcpy(¶m->ip_params.v6.dst_ip, qos_policy_data.classifierParams.dstIp.data(), 16); + } + + /* Classifier Mask - bit 2 = Destination IP Address */ + classifier_mask |= BIT(2); + } + + if ((inMask & static_cast(QosPolicyClassifierParamsMask::SRC_PORT)) + && (qos_policy_data.classifierParams.srcPort > 0)) { + if (param->ip_version == IPV4) + param->ip_params.v4.src_port = qos_policy_data.classifierParams.srcPort; + else + param->ip_params.v6.src_port = qos_policy_data.classifierParams.srcPort; + + /* Classifier Mask - bit 3 = Source Port */ + classifier_mask |= BIT(3); + } + + if ((inMask & static_cast(QosPolicyClassifierParamsMask::DST_PORT_RANGE)) + && (qos_policy_data.classifierParams.dstPortRange.startPort > 0)) { + if (param->ip_version == IPV4) + param->ip_params.v4.dst_port = qos_policy_data.classifierParams.dstPortRange.startPort; + else + param->ip_params.v6.dst_port = qos_policy_data.classifierParams.dstPortRange.startPort; + + /* Classifier Mask - bit 4 = Destination Port range */ + classifier_mask |= BIT(4); + } + + if ((inMask & static_cast(QosPolicyClassifierParamsMask::DSCP)) + && (qos_policy_data.classifierParams.dscp > 0)) { + if (param->ip_version == IPV4) + param->ip_params.v4.dscp = qos_policy_data.classifierParams.dscp; + else + param->ip_params.v6.dscp = qos_policy_data.classifierParams.dscp; + + /* Classifier Mask - bit 5 = DSCP */ + classifier_mask |= BIT(5); + } + + if (inMask & static_cast(QosPolicyClassifierParamsMask::PROTOCOL_NEXT_HEADER)) { + if (!((qos_policy_data.classifierParams.protocolNextHdr == + ProtocolNextHeader::TCP) || + (qos_policy_data.classifierParams.protocolNextHdr == + ProtocolNextHeader::UDP) || + (qos_policy_data.classifierParams.protocolNextHdr == + ProtocolNextHeader::ESP))) { + wpa_printf(MSG_ERROR, "Invalid protocol"); + return -1; + } + if (param->ip_version == IPV4) { + param->ip_params.v4.protocol = + (u8)qos_policy_data.classifierParams.protocolNextHdr; + } else { + param->ip_params.v6.next_header = + (u8)qos_policy_data.classifierParams.protocolNextHdr; + } + + /* Classifier Mask - bit 6 = Protocol Number*/ + classifier_mask |= BIT(6); + } + + if (inMask & static_cast(QosPolicyClassifierParamsMask::FLOW_LABEL)) { + if (qos_policy_data.classifierParams.flowLabelIpv6.size() != + sizeof(param->ip_params.v6.flow_label)) { + wpa_printf(MSG_ERROR, "Invalid flow label"); + return -1; + } + os_memcpy(param->ip_params.v6.flow_label, qos_policy_data.classifierParams.flowLabelIpv6.data(), + sizeof(qos_policy_data.classifierParams.flowLabelIpv6)); + + /* Classifier Mask - bit 7 = flow level */ + classifier_mask |= BIT(7); + } + + param->classifier_mask = classifier_mask; + return 0; +} + +static int scs_parse_type4(struct tclas_element *elem, QosPolicyScsData qos_policy_data) +{ + struct type4_params type4_param; + memset(&type4_param, 0, sizeof(type4_param)); + + if (set_type4_frame_classifier(qos_policy_data, &type4_param) < 0) { + wpa_printf(MSG_ERROR, "Failed to set frame_classifier 4"); + return -1; + } + + os_memcpy(&elem->frame_classifier.type4_param, + &type4_param, sizeof(struct type4_params)); + return 0; +} + +inline bool hasOptQosCharField(QosCharacteristics chars, QosCharacteristics::QosCharacteristicsMask field) { + return chars.optionalFieldMask & static_cast(field); +} + +static int parseQosCharacteristics(struct scs_desc_elem *descElem, QosPolicyScsData qosPolicy) { + struct qos_characteristics* suppChars = &descElem->qos_char_elem; + if (!qosPolicy.QosCharacteristics) { + suppChars->available = false; + return 0; + } + + QosCharacteristics inputChars = qosPolicy.QosCharacteristics.value(); + suppChars->available = true; + + if (qosPolicy.direction == QosPolicyScsData::LinkDirection::DOWNLINK) { + suppChars->direction = SCS_DIRECTION_DOWN; + } else if (qosPolicy.direction == QosPolicyScsData::LinkDirection::UPLINK) { + suppChars->direction = SCS_DIRECTION_UP; + } else { + wpa_printf(MSG_ERROR, "Invalid QoS direction: %d", static_cast(qosPolicy.direction)); + return -1; + } + + // Mandatory fields + suppChars->min_si = inputChars.minServiceIntervalUs; + suppChars->max_si = inputChars.maxServiceIntervalUs; + suppChars->min_data_rate = inputChars.minDataRateKbps; + suppChars->delay_bound = inputChars.delayBoundUs; + + // Optional fields + uint16_t suppMask = 0; + if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::MAX_MSDU_SIZE)) { + suppMask |= SCS_QOS_BIT_MAX_MSDU_SIZE; + suppChars->max_msdu_size = inputChars.maxMsduSizeOctets; + } + if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::SERVICE_START_TIME)) { + // Client must provide both the service start time and the link ID if this field exists. + suppMask |= SCS_QOS_BIT_SERVICE_START_TIME | SCS_QOS_BIT_SERVICE_START_TIME_LINKID; + suppChars->service_start_time = inputChars.serviceStartTimeUs; + suppChars->service_start_time_link_id = inputChars.serviceStartTimeLinkId; + } + if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::MEAN_DATA_RATE)) { + suppMask |= SCS_QOS_BIT_MEAN_DATA_RATE; + suppChars->mean_data_rate = inputChars.meanDataRateKbps; + } + if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::BURST_SIZE)) { + suppMask |= SCS_QOS_BIT_DELAYED_BOUNDED_BURST_SIZE; + suppChars->burst_size = inputChars.burstSizeOctets; + } + if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::MSDU_LIFETIME)) { + suppMask |= SCS_QOS_BIT_MSDU_LIFETIME; + suppChars->msdu_lifetime = inputChars.msduLifetimeMs; + } + if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::MSDU_DELIVERY_INFO)) { + suppMask |= SCS_QOS_BIT_MSDU_DELIVERY_INFO; + // Expects the delivery ratio in the lower 4 bits and the count exponent + // in the upper 4 bits. See Figure 9-1001aw in the 802.11be spec. + suppChars->msdu_delivery_info = inputChars.msduDeliveryInfo.countExponent << 4 + | (uint8_t) inputChars.msduDeliveryInfo.deliveryRatio; + } + suppChars->mask = suppMask; + return 0; +} + +/** + * This is a request to the AP (if it supports the feature) to apply the QoS policy + * on traffic in the Downlink or Uplink direction. + */ +std::pair, ndk::ScopedAStatus> +StaIface::addQosPolicyRequestForScsInternal(const std::vector& qosPolicyData) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct scs_robust_av_data *scs_data = &wpa_s->scs_robust_av_req; + struct scs_desc_elem desc_elem; + int user_priority, num_qos_policies; + unsigned int num_scs_ids = 0; + std::vector reports; + + if (wpa_s->ongoing_scs_req) { + wpa_printf(MSG_ERROR, "AIDL: SCS Request already in queue"); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_ONGOING_REQUEST)}; + } + free_up_scs_desc(scs_data); + + // Uplink policies are not supported before AIDL V3. + AidlManager *aidl_manager = AidlManager::getInstance(); + WPA_ASSERT(aidl_manager); + bool supportsUplink = aidl_manager->isAidlServiceVersionAtLeast(3); + + /** + * format: + * [scs_id=] [scs_up=<0-7>] + * [classifier params based on classifier type] + * [scs_id=] ... + */ + num_qos_policies = qosPolicyData.size(); + for (int i = 0; i < num_qos_policies; i++) { + struct scs_desc_elem *new_desc_elems; + struct active_scs_elem *active_scs_desc; + struct tclas_element *elem; + bool scsid_active = false; + QosPolicyScsRequestStatus status; + + memset(&desc_elem, 0, sizeof(desc_elem)); + desc_elem.scs_id = qosPolicyData[i].policyId; + status.policyId = desc_elem.scs_id; + desc_elem.request_type = SCS_REQ_ADD; + dl_list_for_each(active_scs_desc, &wpa_s->active_scs_ids, + struct active_scs_elem, list) { + if (desc_elem.scs_id == active_scs_desc->scs_id) { + scsid_active = true; + break; + } + } + + if (scsid_active) { + wpa_printf(MSG_ERROR, "SCSID %d already active", + desc_elem.scs_id); + status.qosPolicyScsRequestStatusCode = QosPolicyScsRequestStatusCode::ALREADY_ACTIVE; + reports.push_back(status); + continue; + } + + status.qosPolicyScsRequestStatusCode = QosPolicyScsRequestStatusCode::INVALID; + if (parseQosCharacteristics(&desc_elem, qosPolicyData[i])) { + reports.push_back(status); + continue; + } + + // TCLAS elements only need to be processed for downlink policies. + QosPolicyScsData::LinkDirection policyDirection = supportsUplink + ? qosPolicyData[i].direction : QosPolicyScsData::LinkDirection::DOWNLINK; + if (policyDirection == QosPolicyScsData::LinkDirection::DOWNLINK) { + user_priority = qosPolicyData[i].userPriority; + if (user_priority < 0 || user_priority > 7) { + wpa_printf(MSG_ERROR, + "Intra-Access user priority invalid %d", user_priority); + reports.push_back(status); + continue; + } + + desc_elem.intra_access_priority = user_priority; + desc_elem.scs_up_avail = true; + + /** + * Supported classifier type 4. + */ + desc_elem.tclas_elems = (struct tclas_element *) os_malloc(sizeof(struct tclas_element)); + if (!desc_elem.tclas_elems) { + wpa_printf(MSG_ERROR, + "Classifier type4 failed with Bad malloc"); + reports.push_back(status); + continue; + } + + elem = desc_elem.tclas_elems; + memset(elem, 0, sizeof(struct tclas_element)); + elem->classifier_type = 4; + if (scs_parse_type4(elem, qosPolicyData[i]) < 0) { + os_free(elem); + reports.push_back(status); + continue; + } + + desc_elem.num_tclas_elem = 1; + } + + /* Reallocate memory to scs_desc_elems to accomodate further policies */ + new_desc_elems = static_cast(os_realloc(scs_data->scs_desc_elems, + (num_scs_ids + 1) * sizeof(struct scs_desc_elem))); + if (!new_desc_elems) { + os_free(elem); + reports.push_back(status); + continue; + } + + scs_data->scs_desc_elems = new_desc_elems; + os_memcpy((u8 *) scs_data->scs_desc_elems + num_scs_ids * + sizeof(desc_elem), &desc_elem, sizeof(desc_elem)); + num_scs_ids++; + scs_data->num_scs_desc = num_scs_ids; + status.qosPolicyScsRequestStatusCode = QosPolicyScsRequestStatusCode::SENT; + reports.push_back(status); + } + wpas_send_scs_req(wpa_s); + return {std::vector(reports), + ndk::ScopedAStatus::ok()}; +} + +std::pair, ndk::ScopedAStatus> +StaIface::removeQosPolicyForScsInternal(const std::vector& scsPolicyIds) +{ + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct scs_robust_av_data *scs_data = &wpa_s->scs_robust_av_req; + struct scs_desc_elem desc_elem; + int count; + unsigned int num_scs_ids = 0; + std::vector reports; + struct active_scs_elem *scs_desc; + + if (wpa_s->ongoing_scs_req) { + wpa_printf(MSG_ERROR, "AIDL: SCS Request already in queue"); + return {std::vector(), + createStatus(SupplicantStatusCode::FAILURE_ONGOING_REQUEST)}; + } + free_up_scs_desc(scs_data); + + count = scsPolicyIds.size(); + for (int i = 0; i < count; i++) { + struct scs_desc_elem *new_desc_elems; + QosPolicyScsRequestStatus status; + bool policy_id_exists = false; + + memset(&desc_elem, 0, sizeof(desc_elem)); + desc_elem.scs_id = scsPolicyIds[i]; + status.policyId = scsPolicyIds[i]; + desc_elem.request_type = SCS_REQ_REMOVE; + dl_list_for_each(scs_desc, &wpa_s->active_scs_ids, + struct active_scs_elem, list) { + if (desc_elem.scs_id == scs_desc->scs_id) { + policy_id_exists = true; + break; + } + } + if (policy_id_exists == false) { + status.qosPolicyScsRequestStatusCode = QosPolicyScsRequestStatusCode::NOT_EXIST; + reports.push_back(status); + continue; + } + + new_desc_elems = static_cast(os_realloc(scs_data->scs_desc_elems, (num_scs_ids + 1) * + sizeof(struct scs_desc_elem))); + if (!new_desc_elems) { + status.qosPolicyScsRequestStatusCode = QosPolicyScsRequestStatusCode::INVALID; + reports.push_back(status); + continue; + } + + scs_data->scs_desc_elems = new_desc_elems; + os_memcpy((u8 *) scs_data->scs_desc_elems + num_scs_ids * + sizeof(desc_elem), &desc_elem, sizeof(desc_elem)); + num_scs_ids++; + scs_data->num_scs_desc = num_scs_ids; + status.qosPolicyScsRequestStatusCode = QosPolicyScsRequestStatusCode::SENT; + reports.push_back(status); + } + wpas_send_scs_req(wpa_s); + + return {std::vector(reports), + ndk::ScopedAStatus::ok()}; +} + +::ndk::ScopedAStatus StaIface::configureMscsInternal(const MscsParams& params) { + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct robust_av_data *robust_av = &wpa_s->robust_av; + os_memset(robust_av, 0, sizeof(struct robust_av_data)); + + if (params.upLimit < 0 || params.upLimit > 7) { + wpa_printf(MSG_ERROR, "Invalid MSCS params - upLimit=%d", params.upLimit); + return createStatus(SupplicantStatusCode::FAILURE_ARGS_INVALID); + } + if (params.streamTimeoutUs < 0 || params.streamTimeoutUs > 60000000 /* 60 sec */) { + wpa_printf(MSG_ERROR, "Invalid MSCS params - streamTimeoutUs=%d", params.streamTimeoutUs); + return createStatus(SupplicantStatusCode::FAILURE_ARGS_INVALID); + } + + robust_av->request_type = SCS_REQ_ADD; + robust_av->up_bitmap = params.upBitmap; + robust_av->up_limit = params.upLimit; + robust_av->stream_timeout = params.streamTimeoutUs; + robust_av->frame_classifier[0] = params.frameClassifierMask; // single type-4 frame classifier mask + robust_av->frame_classifier_len = 1; + + int status = wpas_send_mscs_req(wpa_s); + wpa_printf(MSG_INFO, "MSCS add request status: %d", status); + + // Mark config as invalid to avoid retransmitting automatically. + robust_av->valid_config = false; + return ndk::ScopedAStatus::ok(); +} + +::ndk::ScopedAStatus StaIface::disableMscsInternal() { + struct wpa_supplicant *wpa_s = retrieveIfacePtr(); + struct robust_av_data *robust_av = &wpa_s->robust_av; + os_memset(robust_av, 0, sizeof(struct robust_av_data)); + + robust_av->request_type = SCS_REQ_REMOVE; + robust_av->valid_config = false; + + int status = wpas_send_mscs_req(wpa_s); + wpa_printf(MSG_INFO, "MSCS remove request status: %d", status); + + return ndk::ScopedAStatus::ok(); +} + +/** + * Retrieve the underlying |wpa_supplicant| struct + * pointer for this iface. + * If the underlying iface is removed, then all RPC method calls on this object + * will return failure. + */ +wpa_supplicant *StaIface::retrieveIfacePtr() +{ + return wpa_supplicant_get_iface(wpa_global_, ifname_.c_str()); +} +} // namespace supplicant +} // namespace wifi +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/android.config b/aosp/external/wpa_supplicant_8/wpa_supplicant/android.config new file mode 100644 index 000000000..4cc380844 --- /dev/null +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/android.config @@ -0,0 +1,567 @@ +# Example wpa_supplicant build time configuration +# +# This file lists the configuration options that are used when building the +# wpa_supplicant binary. All lines starting with # are ignored. Configuration +# option lines must be commented out complete, if they are not to be included, +# i.e., just setting VARIABLE=n is not disabling that variable. +# +# This file is included in Makefile, so variables like CFLAGS and LIBS can also +# be modified from here. In most cases, these lines should use += in order not +# to override previous values of the variables. + + +# Uncomment following two lines and fix the paths if you have installed OpenSSL +# or GnuTLS in non-default location +#CFLAGS += -I/usr/local/openssl/include +#LIBS += -L/usr/local/openssl/lib + +# Some Red Hat versions seem to include kerberos header files from OpenSSL, but +# the kerberos files are not in the default include path. Following line can be +# used to fix build issues on such systems (krb5.h not found). +#CFLAGS += -I/usr/include/kerberos + +# Driver interface for generic Linux wireless extensions +# Note: WEXT is deprecated in the current Linux kernel version and no new +# functionality is added to it. nl80211-based interface is the new +# replacement for WEXT and its use allows wpa_supplicant to properly control +# the driver to improve existing functionality like roaming and to support new +# functionality. +#CONFIG_DRIVER_WEXT=y + +# Driver interface for Linux drivers using the nl80211 kernel interface +#CONFIG_DRIVER_NL80211=y +CONFIG_LIBNL20=y + +# Driver interface for FreeBSD net80211 layer (e.g., Atheros driver) +#CONFIG_DRIVER_BSD=y +#CFLAGS += -I/usr/local/include +#LIBS += -L/usr/local/lib +#LIBS_p += -L/usr/local/lib +#LIBS_c += -L/usr/local/lib + +# Driver interface for Windows NDIS +#CONFIG_DRIVER_NDIS=y +#CFLAGS += -I/usr/include/w32api/ddk +#LIBS += -L/usr/local/lib +# For native build using mingw +#CONFIG_NATIVE_WINDOWS=y +# Additional directories for cross-compilation on Linux host for mingw target +#CFLAGS += -I/opt/mingw/mingw32/include/ddk +#LIBS += -L/opt/mingw/mingw32/lib +#CC=mingw32-gcc +# By default, driver_ndis uses WinPcap for low-level operations. This can be +# replaced with the following option which replaces WinPcap calls with NDISUIO. +# However, this requires that WZC is disabled (net stop wzcsvc) before starting +# wpa_supplicant. +# CONFIG_USE_NDISUIO=y + +# Driver interface for wired Ethernet drivers +#CONFIG_DRIVER_WIRED=y + +# Driver interface for the Broadcom RoboSwitch family +#CONFIG_DRIVER_ROBOSWITCH=y + +# Driver interface for no driver (e.g., WPS ER only) +#CONFIG_DRIVER_NONE=y + +# Solaris libraries +#LIBS += -lsocket -ldlpi -lnsl +#LIBS_c += -lsocket + +# Enable IEEE 802.1X Supplicant (automatically included if any EAP method is +# included) +CONFIG_IEEE8021X_EAPOL=y + +# EAP-MD5 +CONFIG_EAP_MD5=y + +# EAP-MSCHAPv2 +CONFIG_EAP_MSCHAPV2=y + +# EAP-TLS +CONFIG_EAP_TLS=y +CONFIG_EAP_TLSV1_3=y + +# EAL-PEAP +CONFIG_EAP_PEAP=y + +# EAP-TTLS +CONFIG_EAP_TTLS=y + +# EAP-FAST +#CONFIG_EAP_FAST=y + +# EAP-GTC +CONFIG_EAP_GTC=y + +# EAP-OTP +CONFIG_EAP_OTP=y + +# EAP-SIM (enable CONFIG_PCSC, if EAP-SIM is used) +CONFIG_EAP_SIM=y + +# EAP-PSK (experimental; this is _not_ needed for WPA-PSK) +#CONFIG_EAP_PSK=y + +# EAP-pwd (secure authentication using only a password) +CONFIG_EAP_PWD=y + +# EAP-PAX +#CONFIG_EAP_PAX=y + +# LEAP +CONFIG_EAP_LEAP=y + +# EAP-AKA (enable CONFIG_PCSC, if EAP-AKA is used) +CONFIG_EAP_AKA=y + +# EAP-AKA' (enable CONFIG_PCSC, if EAP-AKA' is used). +# This requires CONFIG_EAP_AKA to be enabled, too. +CONFIG_EAP_AKA_PRIME=y + +# Enable USIM simulator (Milenage) for EAP-AKA +#CONFIG_USIM_SIMULATOR=y + +# EAP-SAKE +#CONFIG_EAP_SAKE=y + +# EAP-GPSK +#CONFIG_EAP_GPSK=y +# Include support for optional SHA256 cipher suite in EAP-GPSK +#CONFIG_EAP_GPSK_SHA256=y + +# EAP-TNC and related Trusted Network Connect support (experimental) +#CONFIG_EAP_TNC=y + +# Wi-Fi Protected Setup (WPS) +CONFIG_WPS=y +# Enable WPS external registrar functionality +CONFIG_WPS_ER=y +# Disable credentials for an open network by default when acting as a WPS +# registrar. +#CONFIG_WPS_REG_DISABLE_OPEN=y +# Enable WPS support with NFC config method +CONFIG_WPS_NFC=y + +# EAP-IKEv2 +#CONFIG_EAP_IKEV2=y + +# EAP-EKE +#CONFIG_EAP_EKE=y + +# PKCS#12 (PFX) support (used to read private key and certificate file from +# a file that usually has extension .p12 or .pfx) +CONFIG_PKCS12=y + +# Smartcard support (i.e., private key on a smartcard), e.g., with openssl +# engine. +CONFIG_SMARTCARD=y + +# PC/SC interface for smartcards (USIM, GSM SIM) +# Enable this if EAP-SIM or EAP-AKA is included +#CONFIG_PCSC=y + +# Support HT overrides (disable HT/HT40, mask MCS rates, etc.) +#CONFIG_HT_OVERRIDES=y + +# Support VHT overrides (disable VHT, mask MCS rates, etc.) +#CONFIG_VHT_OVERRIDES=y + +# Development testing +#CONFIG_EAPOL_TEST=y + +# Select control interface backend for external programs, e.g, wpa_cli: +# unix = UNIX domain sockets (default for Linux/*BSD) +# udp = UDP sockets using localhost (127.0.0.1) +# udp6 = UDP IPv6 sockets using localhost (::1) +# named_pipe = Windows Named Pipe (default for Windows) +# udp-remote = UDP sockets with remote access (only for tests systems/purpose) +# udp6-remote = UDP IPv6 sockets with remote access (only for tests purpose) +# y = use default (backwards compatibility) +# If this option is commented out, control interface is not included in the +# build. +CONFIG_CTRL_IFACE=y + +# Include support for GNU Readline and History Libraries in wpa_cli. +# When building a wpa_cli binary for distribution, please note that these +# libraries are licensed under GPL and as such, BSD license may not apply for +# the resulting binary. +#CONFIG_READLINE=y + +# Include internal line edit mode in wpa_cli. This can be used as a replacement +# for GNU Readline to provide limited command line editing and history support. +CONFIG_WPA_CLI_EDIT=y + +# Remove debugging code that is printing out debug message to stdout. +# This can be used to reduce the size of the wpa_supplicant considerably +# if debugging code is not needed. The size reduction can be around 35% +# (e.g., 90 kB). +#CONFIG_NO_STDOUT_DEBUG=y + +# Remove WPA support, e.g., for wired-only IEEE 802.1X supplicant, to save +# 35-50 kB in code size. +#CONFIG_NO_WPA=y + +# Remove IEEE 802.11i/WPA-Personal ASCII passphrase support +# This option can be used to reduce code size by removing support for +# converting ASCII passphrases into PSK. If this functionality is removed, the +# PSK can only be configured as the 64-octet hexstring (e.g., from +# wpa_passphrase). This saves about 0.5 kB in code size. +#CONFIG_NO_WPA_PASSPHRASE=y + +# Disable scan result processing (ap_mode=1) to save code size by about 1 kB. +# This can be used if ap_scan=1 mode is never enabled. +#CONFIG_NO_SCAN_PROCESSING=y + +# Select configuration backend: +# file = text file (e.g., wpa_supplicant.conf; note: the configuration file +# path is given on command line, not here; this option is just used to +# select the backend that allows configuration files to be used) +# winreg = Windows registry (see win_example.reg for an example) +CONFIG_BACKEND=file + +# Remove configuration write functionality (i.e., to allow the configuration +# file to be updated based on runtime configuration changes). The runtime +# configuration can still be changed, the changes are just not going to be +# persistent over restarts. This option can be used to reduce code size by +# about 3.5 kB. +#CONFIG_NO_CONFIG_WRITE=y + +# Remove support for configuration blobs to reduce code size by about 1.5 kB. +#CONFIG_NO_CONFIG_BLOBS=y + +# Select program entry point implementation: +# main = UNIX/POSIX like main() function (default) +# main_winsvc = Windows service (read parameters from registry) +# main_none = Very basic example (development use only) +#CONFIG_MAIN=main + +# Select wrapper for operating system and C library specific functions +# unix = UNIX/POSIX like systems (default) +# win32 = Windows systems +# none = Empty template +CONFIG_OS=unix + +# Select event loop implementation +# eloop = select() loop (default) +# eloop_win = Windows events and WaitForMultipleObject() loop +CONFIG_ELOOP=eloop + +# Should we use poll instead of select? Select is used by default. +#CONFIG_ELOOP_POLL=y + +# Should we use epoll instead of select? Select is used by default. +#CONFIG_ELOOP_EPOLL=y + +# Should we use kqueue instead of select? Select is used by default. +#CONFIG_ELOOP_KQUEUE=y + +# Select layer 2 packet implementation +# linux = Linux packet socket (default) +# pcap = libpcap/libdnet/WinPcap +# freebsd = FreeBSD libpcap +# winpcap = WinPcap with receive thread +# ndis = Windows NDISUIO (note: requires CONFIG_USE_NDISUIO=y) +# none = Empty template +CONFIG_L2_PACKET=linux + +# Disable Linux packet socket workaround applicable for station interface +# in a bridge for EAPOL frames. This should be uncommented only if the kernel +# is known to not have the regression issue in packet socket behavior with +# bridge interfaces (commit 'bridge: respect RFC2863 operational state')'). +#CONFIG_NO_LINUX_PACKET_SOCKET_WAR=y + +# Support Operating Channel Validation +#CONFIG_OCV=y + +# Select TLS implementation +# openssl = OpenSSL (default) +# gnutls = GnuTLS +# internal = Internal TLSv1 implementation (experimental) +# none = Empty template +#CONFIG_TLS=openssl + +# TLS-based EAP methods require at least TLS v1.0. Newer version of TLS (v1.1) +# can be enabled to get a stronger construction of messages when block ciphers +# are used. It should be noted that some existing TLS v1.0 -based +# implementation may not be compatible with TLS v1.1 message (ClientHello is +# sent prior to negotiating which version will be used) +#CONFIG_TLSV11=y + +# TLS-based EAP methods require at least TLS v1.0. Newer version of TLS (v1.2) +# can be enabled to enable use of stronger crypto algorithms. It should be +# noted that some existing TLS v1.0 -based implementation may not be compatible +# with TLS v1.2 message (ClientHello is sent prior to negotiating which version +# will be used) +#CONFIG_TLSV12=y + +# Select which ciphers to use by default with OpenSSL if the user does not +# specify them. +#CONFIG_TLS_DEFAULT_CIPHERS="DEFAULT:!EXP:!LOW" + +# If CONFIG_TLS=internal is used, additional library and include paths are +# needed for LibTomMath. Alternatively, an integrated, minimal version of +# LibTomMath can be used. See beginning of libtommath.c for details on benefits +# and drawbacks of this option. +#CONFIG_INTERNAL_LIBTOMMATH=y +#ifndef CONFIG_INTERNAL_LIBTOMMATH +#LTM_PATH=/usr/src/libtommath-0.39 +#CFLAGS += -I$(LTM_PATH) +#LIBS += -L$(LTM_PATH) +#LIBS_p += -L$(LTM_PATH) +#endif +# At the cost of about 4 kB of additional binary size, the internal LibTomMath +# can be configured to include faster routines for exptmod, sqr, and div to +# speed up DH and RSA calculation considerably +#CONFIG_INTERNAL_LIBTOMMATH_FAST=y + +# Include NDIS event processing through WMI into wpa_supplicant/wpasvc. +# This is only for Windows builds and requires WMI-related header files and +# WbemUuid.Lib from Platform SDK even when building with MinGW. +#CONFIG_NDIS_EVENTS_INTEGRATED=y +#PLATFORMSDKLIB="/opt/Program Files/Microsoft Platform SDK/Lib" + +# Add support for new DBus control interface +# (fi.w1.hostap.wpa_supplicant1) +#CONFIG_CTRL_IFACE_DBUS_NEW=y + +# Add introspection support for new DBus control interface +#CONFIG_CTRL_IFACE_DBUS_INTRO=y + +# Add support for Aidl control interface +# Only applicable for Android platforms. +CONFIG_CTRL_IFACE_AIDL=y + +# Add support for loading EAP methods dynamically as shared libraries. +# When this option is enabled, each EAP method can be either included +# statically (CONFIG_EAP_=y) or dynamically (CONFIG_EAP_=dyn). +# Dynamic EAP methods are build as shared objects (eap_*.so) and they need to +# be loaded in the beginning of the wpa_supplicant configuration file +# (see load_dynamic_eap parameter in the example file) before being used in +# the network blocks. +# +# Note that some shared parts of EAP methods are included in the main program +# and in order to be able to use dynamic EAP methods using these parts, the +# main program must have been build with the EAP method enabled (=y or =dyn). +# This means that EAP-TLS/PEAP/TTLS/FAST cannot be added as dynamic libraries +# unless at least one of them was included in the main build to force inclusion +# of the shared code. Similarly, at least one of EAP-SIM/AKA must be included +# in the main build to be able to load these methods dynamically. +# +# Please also note that using dynamic libraries will increase the total binary +# size. Thus, it may not be the best option for targets that have limited +# amount of memory/flash. +#CONFIG_DYNAMIC_EAP_METHODS=y + +# IEEE Std 802.11r-2008 (Fast BSS Transition) for station mode +CONFIG_IEEE80211R=y + +# Add support for writing debug log to a file (/tmp/wpa_supplicant-log-#.txt) +#CONFIG_DEBUG_FILE=y + +# Send debug messages to syslog instead of stdout +#CONFIG_DEBUG_SYSLOG=y +# Set syslog facility for debug messages +#CONFIG_DEBUG_SYSLOG_FACILITY=LOG_DAEMON + +# Add support for sending all debug messages (regardless of debug verbosity) +# to the Linux kernel tracing facility. This helps debug the entire stack by +# making it easy to record everything happening from the driver up into the +# same file, e.g., using trace-cmd. +#CONFIG_DEBUG_LINUX_TRACING=y + +# Add support for writing debug log to Android logcat instead of standard +# output +CONFIG_ANDROID_LOG=y + +# Enable privilege separation (see README 'Privilege separation' for details) +#CONFIG_PRIVSEP=y + +# Enable mitigation against certain attacks against TKIP by delaying Michael +# MIC error reports by a random amount of time between 0 and 60 seconds +#CONFIG_DELAYED_MIC_ERROR_REPORT=y + +# Enable tracing code for developer debugging +# This tracks use of memory allocations and other registrations and reports +# incorrect use with a backtrace of call (or allocation) location. +#CONFIG_WPA_TRACE=y +# For BSD, uncomment these. +#LIBS += -lexecinfo +#LIBS_p += -lexecinfo +#LIBS_c += -lexecinfo + +# Use libbfd to get more details for developer debugging +# This enables use of libbfd to get more detailed symbols for the backtraces +# generated by CONFIG_WPA_TRACE=y. +#CONFIG_WPA_TRACE_BFD=y +# For BSD, uncomment these. +#LIBS += -lbfd -liberty -lz +#LIBS_p += -lbfd -liberty -lz +#LIBS_c += -lbfd -liberty -lz + +# wpa_supplicant depends on strong random number generation being available +# from the operating system. os_get_random() function is used to fetch random +# data when needed, e.g., for key generation. On Linux and BSD systems, this +# works by reading /dev/urandom. It should be noted that the OS entropy pool +# needs to be properly initialized before wpa_supplicant is started. This is +# important especially on embedded devices that do not have a hardware random +# number generator and may by default start up with minimal entropy available +# for random number generation. +# +# As a safety net, wpa_supplicant is by default trying to internally collect +# additional entropy for generating random data to mix in with the data fetched +# from the OS. This by itself is not considered to be very strong, but it may +# help in cases where the system pool is not initialized properly. However, it +# is very strongly recommended that the system pool is initialized with enough +# entropy either by using hardware assisted random number generator or by +# storing state over device reboots. +# +# wpa_supplicant can be configured to maintain its own entropy store over +# restarts to enhance random number generation. This is not perfect, but it is +# much more secure than using the same sequence of random numbers after every +# reboot. This can be enabled with -e command line option. The +# specified file needs to be readable and writable by wpa_supplicant. +# +# If the os_get_random() is known to provide strong random data (e.g., on +# Linux/BSD, the board in question is known to have reliable source of random +# data from /dev/urandom), the internal wpa_supplicant random pool can be +# disabled. This will save some in binary size and CPU use. However, this +# should only be considered for builds that are known to be used on devices +# that meet the requirements described above. + +# Wpa_supplicant's random pool is not necessary on Android. Randomness is +# already provided by the entropymixer service which ensures sufficient +# entropy is maintained across reboots. Commit b410eb1913 'Initialize +# /dev/urandom earlier in boot' seeds /dev/urandom with that entropy before +# either wpa_supplicant or hostapd are run. +CONFIG_NO_RANDOM_POOL=y + +# IEEE 802.11ac (Very High Throughput) support (mainly for AP mode) +#CONFIG_IEEE80211AC=y + +# Wireless Network Management (IEEE Std 802.11v-2011) +# Note: This is experimental and not complete implementation. +CONFIG_WNM=y + +# Interworking (IEEE 802.11u) +# This can be used to enable functionality to improve interworking with +# external networks (GAS/ANQP to learn more about the networks and network +# selection based on available credentials). +CONFIG_INTERWORKING=y + +# Hotspot 2.0 +CONFIG_HS20=y + +# Enable interface matching in wpa_supplicant +#CONFIG_MATCH_IFACE=y + +# Disable roaming in wpa_supplicant +CONFIG_NO_ROAMING=y + +# AP mode operations with wpa_supplicant +# This can be used for controlling AP mode operations with wpa_supplicant. It +# should be noted that this is mainly aimed at simple cases like +# WPA2-Personal while more complex configurations like WPA2-Enterprise with an +# external RADIUS server can be supported with hostapd. +CONFIG_AP=y + +# P2P (Wi-Fi Direct) +# This can be used to enable P2P support in wpa_supplicant. See README-P2P for +# more information on P2P operations. +CONFIG_P2P=y + +# Enable TDLS support +CONFIG_TDLS=y + +# Wi-Fi Display +# This can be used to enable Wi-Fi Display extensions for P2P using an external +# program to control the additional information exchanges in the messages. +CONFIG_WIFI_DISPLAY=y + +# Autoscan +# This can be used to enable automatic scan support in wpa_supplicant. +# See wpa_supplicant.conf for more information on autoscan usage. +# +# Enabling directly a module will enable autoscan support. +# For exponential module: +#CONFIG_AUTOSCAN_EXPONENTIAL=y +# For periodic module: +#CONFIG_AUTOSCAN_PERIODIC=y + +# Password (and passphrase, etc.) backend for external storage +# These optional mechanisms can be used to add support for storing passwords +# and other secrets in external (to wpa_supplicant) location. This allows, for +# example, operating system specific key storage to be used +# +# External password backend for testing purposes (developer use) +#CONFIG_EXT_PASSWORD_TEST=y + +# Enable Fast Session Transfer (FST) +#CONFIG_FST=y + +# Support Multi Band Operation +CONFIG_MBO=y + +# Fast Initial Link Setup (FILS) (IEEE 802.11ai) +CONFIG_FILS=y + +# EAP Re-authentication protocol +CONFIG_ERP=y + +# Support RSN on IBSS networks +# This is needed to be able to use mode=1 network profile with proto=RSN and +# key_mgmt=WPA-PSK (i.e., full key management instead of WPA-None). +#CONFIG_IBSS_RSN=y + +# External PMKSA cache control +# This can be used to enable control interface commands that allow the current +# PMKSA cache entries to be fetched and new entries to be added. +#CONFIG_PMKSA_CACHE_EXTERNAL=y + +# Mesh Networking (IEEE 802.11s) +#CONFIG_MESH=y + +# Background scanning modules +# These can be used to request wpa_supplicant to perform background scanning +# operations for roaming within an ESS (same SSID). See the bgscan parameter in +# the wpa_supplicant.conf file for more details. +# Periodic background scans based on signal strength +#CONFIG_BGSCAN_SIMPLE=y +# Learn channels used by the network and try to avoid bgscans on other +# channels (experimental) +#CONFIG_BGSCAN_LEARN=y + +# Opportunistic Wireless Encryption (OWE) +CONFIG_OWE=y + +# Easy Connect (Device Provisioning Protocol - DPP R1 & R2) +CONFIG_DPP=y +CONFIG_DPP2=y + +# WPA3-Personal (SAE) +CONFIG_SAE=y + +# PASN +CONFIG_PASN=y + +# WPA3-Enterprise (SuiteB-192) +CONFIG_SUITEB=y +CONFIG_SUITEB192=y + +# WLAN Authentication and Privacy Infrastructure (WAPI): interface only. +# Configure the building of the interface which allows WAPI configuration. +# Note: does not configure WAPI implementation itself. +#CONFIG_WAPI_INTERFACE=y + +# Wired equivalent privacy (WEP) +# WEP is an obsolete cryptographic data confidentiality algorithm that is not +# considered secure. It should not be used for anything anymore. The +# functionality needed to use WEP is available in the current wpa_supplicant +# release under this optional build parameter. This functionality is subject to +# be completely removed in a future release. +CONFIG_WEP=y + +# WPA3-Personal (SAE) PK (Public Key) mode +CONFIG_SAE_PK=y + +include $(wildcard $(LOCAL_PATH)/android_config_*.inc) diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/defconfig b/aosp/external/wpa_supplicant_8/wpa_supplicant/defconfig new file mode 100644 index 000000000..1ae5a9f62 --- /dev/null +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/defconfig @@ -0,0 +1,679 @@ +# Example wpa_supplicant build time configuration +# +# This file lists the configuration options that are used when building the +# wpa_supplicant binary. All lines starting with # are ignored. Configuration +# option lines must be commented out complete, if they are not to be included, +# i.e., just setting VARIABLE=n is not disabling that variable. +# +# This file is included in Makefile, so variables like CFLAGS and LIBS can also +# be modified from here. In most cases, these lines should use += in order not +# to override previous values of the variables. + + +# Uncomment following two lines and fix the paths if you have installed OpenSSL +# or GnuTLS in non-default location +#CFLAGS += -I/usr/local/openssl/include +#LIBS += -L/usr/local/openssl/lib + +# Some Red Hat versions seem to include kerberos header files from OpenSSL, but +# the kerberos files are not in the default include path. Following line can be +# used to fix build issues on such systems (krb5.h not found). +#CFLAGS += -I/usr/include/kerberos + +# Driver interface for generic Linux wireless extensions +# Note: WEXT is deprecated in the current Linux kernel version and no new +# functionality is added to it. nl80211-based interface is the new +# replacement for WEXT and its use allows wpa_supplicant to properly control +# the driver to improve existing functionality like roaming and to support new +# functionality. +CONFIG_DRIVER_WEXT=y + +# Driver interface for Linux drivers using the nl80211 kernel interface +CONFIG_DRIVER_NL80211=y + +# QCA vendor extensions to nl80211 +#CONFIG_DRIVER_NL80211_QCA=y + +# driver_nl80211.c requires libnl. If you are compiling it yourself +# you may need to point hostapd to your version of libnl. +# +#CFLAGS += -I$ +#LIBS += -L$ + +# Use libnl v2.0 (or 3.0) libraries. +#CONFIG_LIBNL20=y + +# Use libnl 3.2 libraries (if this is selected, CONFIG_LIBNL20 is ignored) +CONFIG_LIBNL32=y + + +# Driver interface for FreeBSD net80211 layer (e.g., Atheros driver) +#CONFIG_DRIVER_BSD=y +#CFLAGS += -I/usr/local/include +#LIBS += -L/usr/local/lib +#LIBS_p += -L/usr/local/lib +#LIBS_c += -L/usr/local/lib + +# Driver interface for Windows NDIS +#CONFIG_DRIVER_NDIS=y +#CFLAGS += -I/usr/include/w32api/ddk +#LIBS += -L/usr/local/lib +# For native build using mingw +#CONFIG_NATIVE_WINDOWS=y +# Additional directories for cross-compilation on Linux host for mingw target +#CFLAGS += -I/opt/mingw/mingw32/include/ddk +#LIBS += -L/opt/mingw/mingw32/lib +#CC=mingw32-gcc +# By default, driver_ndis uses WinPcap for low-level operations. This can be +# replaced with the following option which replaces WinPcap calls with NDISUIO. +# However, this requires that WZC is disabled (net stop wzcsvc) before starting +# wpa_supplicant. +# CONFIG_USE_NDISUIO=y + +# Driver interface for wired Ethernet drivers +CONFIG_DRIVER_WIRED=y + +# Driver interface for MACsec capable Qualcomm Atheros drivers +#CONFIG_DRIVER_MACSEC_QCA=y + +# Driver interface for Linux MACsec drivers +CONFIG_DRIVER_MACSEC_LINUX=y + +# Driver interface for the Broadcom RoboSwitch family +#CONFIG_DRIVER_ROBOSWITCH=y + +# Driver interface for no driver (e.g., WPS ER only) +#CONFIG_DRIVER_NONE=y + +# Solaris libraries +#LIBS += -lsocket -ldlpi -lnsl +#LIBS_c += -lsocket + +# Enable IEEE 802.1X Supplicant (automatically included if any EAP method or +# MACsec is included) +CONFIG_IEEE8021X_EAPOL=y + +# EAP-MD5 +CONFIG_EAP_MD5=y + +# EAP-MSCHAPv2 +CONFIG_EAP_MSCHAPV2=y + +# EAP-TLS +CONFIG_EAP_TLS=y +# Enable EAP-TLSv1.3 support by default (currently disabled unless explicitly +# enabled in network configuration) +#CONFIG_EAP_TLSV1_3=y + +# EAL-PEAP +CONFIG_EAP_PEAP=y + +# EAP-TTLS +CONFIG_EAP_TTLS=y + +# EAP-FAST +CONFIG_EAP_FAST=y + +# EAP-TEAP +# Note: The current EAP-TEAP implementation is experimental and should not be +# enabled for production use. The IETF RFC 7170 that defines EAP-TEAP has number +# of conflicting statements and missing details and the implementation has +# vendor specific workarounds for those and as such, may not interoperate with +# any other implementation. This should not be used for anything else than +# experimentation and interoperability testing until those issues has been +# resolved. +#CONFIG_EAP_TEAP=y + +# EAP-GTC +CONFIG_EAP_GTC=y + +# EAP-OTP +CONFIG_EAP_OTP=y + +# EAP-SIM (enable CONFIG_PCSC, if EAP-SIM is used) +#CONFIG_EAP_SIM=y + +# Enable SIM simulator (Milenage) for EAP-SIM +#CONFIG_SIM_SIMULATOR=y + +# EAP-PSK (experimental; this is _not_ needed for WPA-PSK) +#CONFIG_EAP_PSK=y + +# EAP-pwd (secure authentication using only a password) +CONFIG_EAP_PWD=y + +# EAP-PAX +CONFIG_EAP_PAX=y + +# LEAP +CONFIG_EAP_LEAP=y + +# EAP-AKA (enable CONFIG_PCSC, if EAP-AKA is used) +#CONFIG_EAP_AKA=y + +# EAP-AKA' (enable CONFIG_PCSC, if EAP-AKA' is used). +# This requires CONFIG_EAP_AKA to be enabled, too. +#CONFIG_EAP_AKA_PRIME=y + +# Enable USIM simulator (Milenage) for EAP-AKA +#CONFIG_USIM_SIMULATOR=y + +# EAP-SAKE +CONFIG_EAP_SAKE=y + +# EAP-GPSK +CONFIG_EAP_GPSK=y +# Include support for optional SHA256 cipher suite in EAP-GPSK +CONFIG_EAP_GPSK_SHA256=y + +# EAP-TNC and related Trusted Network Connect support (experimental) +CONFIG_EAP_TNC=y + +# Wi-Fi Protected Setup (WPS) +CONFIG_WPS=y +# Enable WPS external registrar functionality +#CONFIG_WPS_ER=y +# Disable credentials for an open network by default when acting as a WPS +# registrar. +#CONFIG_WPS_REG_DISABLE_OPEN=y +# Enable WPS support with NFC config method +#CONFIG_WPS_NFC=y + +# EAP-IKEv2 +CONFIG_EAP_IKEV2=y + +# EAP-EKE +#CONFIG_EAP_EKE=y + +# MACsec +CONFIG_MACSEC=y + +# PKCS#12 (PFX) support (used to read private key and certificate file from +# a file that usually has extension .p12 or .pfx) +CONFIG_PKCS12=y + +# Smartcard support (i.e., private key on a smartcard), e.g., with openssl +# engine. +CONFIG_SMARTCARD=y + +# PC/SC interface for smartcards (USIM, GSM SIM) +# Enable this if EAP-SIM or EAP-AKA is included +#CONFIG_PCSC=y + +# Support HT overrides (disable HT/HT40, mask MCS rates, etc.) +#CONFIG_HT_OVERRIDES=y + +# Support VHT overrides (disable VHT, mask MCS rates, etc.) +#CONFIG_VHT_OVERRIDES=y + +# Support HE overrides +#CONFIG_HE_OVERRIDES=y + +# Development testing +#CONFIG_EAPOL_TEST=y + +# Support IPv6 +CONFIG_IPV6=y + +# Select control interface backend for external programs, e.g, wpa_cli: +# unix = UNIX domain sockets (default for Linux/*BSD) +# udp = UDP sockets using localhost (127.0.0.1) +# udp6 = UDP IPv6 sockets using localhost (::1) +# named_pipe = Windows Named Pipe (default for Windows) +# udp-remote = UDP sockets with remote access (only for tests systems/purpose) +# udp6-remote = UDP IPv6 sockets with remote access (only for tests purpose) +# y = use default (backwards compatibility) +# If this option is commented out, control interface is not included in the +# build. +CONFIG_CTRL_IFACE=y + +# Include support for GNU Readline and History Libraries in wpa_cli. +# When building a wpa_cli binary for distribution, please note that these +# libraries are licensed under GPL and as such, BSD license may not apply for +# the resulting binary. +#CONFIG_READLINE=y + +# Include internal line edit mode in wpa_cli. This can be used as a replacement +# for GNU Readline to provide limited command line editing and history support. +#CONFIG_WPA_CLI_EDIT=y + +# Remove debugging code that is printing out debug message to stdout. +# This can be used to reduce the size of the wpa_supplicant considerably +# if debugging code is not needed. The size reduction can be around 35% +# (e.g., 90 kB). +#CONFIG_NO_STDOUT_DEBUG=y + +# Remove WPA support, e.g., for wired-only IEEE 802.1X supplicant, to save +# 35-50 kB in code size. +#CONFIG_NO_WPA=y + +# Remove IEEE 802.11i/WPA-Personal ASCII passphrase support +# This option can be used to reduce code size by removing support for +# converting ASCII passphrases into PSK. If this functionality is removed, the +# PSK can only be configured as the 64-octet hexstring (e.g., from +# wpa_passphrase). This saves about 0.5 kB in code size. +#CONFIG_NO_WPA_PASSPHRASE=y + +# Simultaneous Authentication of Equals (SAE), WPA3-Personal +CONFIG_SAE=y + +# SAE Public Key, WPA3-Personal +#CONFIG_SAE_PK=y + +# Disable scan result processing (ap_scan=1) to save code size by about 1 kB. +# This can be used if ap_scan=1 mode is never enabled. +#CONFIG_NO_SCAN_PROCESSING=y + +# Select configuration backend: +# file = text file (e.g., wpa_supplicant.conf; note: the configuration file +# path is given on command line, not here; this option is just used to +# select the backend that allows configuration files to be used) +# winreg = Windows registry (see win_example.reg for an example) +CONFIG_BACKEND=file + +# Remove configuration write functionality (i.e., to allow the configuration +# file to be updated based on runtime configuration changes). The runtime +# configuration can still be changed, the changes are just not going to be +# persistent over restarts. This option can be used to reduce code size by +# about 3.5 kB. +#CONFIG_NO_CONFIG_WRITE=y + +# Remove support for configuration blobs to reduce code size by about 1.5 kB. +#CONFIG_NO_CONFIG_BLOBS=y + +# Select program entry point implementation: +# main = UNIX/POSIX like main() function (default) +# main_winsvc = Windows service (read parameters from registry) +# main_none = Very basic example (development use only) +#CONFIG_MAIN=main + +# Select wrapper for operating system and C library specific functions +# unix = UNIX/POSIX like systems (default) +# win32 = Windows systems +# none = Empty template +#CONFIG_OS=unix + +# Select event loop implementation +# eloop = select() loop (default) +# eloop_win = Windows events and WaitForMultipleObject() loop +#CONFIG_ELOOP=eloop + +# Should we use poll instead of select? Select is used by default. +#CONFIG_ELOOP_POLL=y + +# Should we use epoll instead of select? Select is used by default. +#CONFIG_ELOOP_EPOLL=y + +# Should we use kqueue instead of select? Select is used by default. +#CONFIG_ELOOP_KQUEUE=y + +# Select layer 2 packet implementation +# linux = Linux packet socket (default) +# pcap = libpcap/libdnet/WinPcap +# freebsd = FreeBSD libpcap +# winpcap = WinPcap with receive thread +# ndis = Windows NDISUIO (note: requires CONFIG_USE_NDISUIO=y) +# none = Empty template +#CONFIG_L2_PACKET=linux + +# Disable Linux packet socket workaround applicable for station interface +# in a bridge for EAPOL frames. This should be uncommented only if the kernel +# is known to not have the regression issue in packet socket behavior with +# bridge interfaces (commit 'bridge: respect RFC2863 operational state')'). +#CONFIG_NO_LINUX_PACKET_SOCKET_WAR=y + +# Support Operating Channel Validation +#CONFIG_OCV=y + +# Select TLS implementation +# openssl = OpenSSL (default) +# gnutls = GnuTLS +# internal = Internal TLSv1 implementation (experimental) +# linux = Linux kernel AF_ALG and internal TLSv1 implementation (experimental) +# none = Empty template +#CONFIG_TLS=openssl + +# TLS-based EAP methods require at least TLS v1.0. Newer version of TLS (v1.1) +# can be enabled to get a stronger construction of messages when block ciphers +# are used. It should be noted that some existing TLS v1.0 -based +# implementation may not be compatible with TLS v1.1 message (ClientHello is +# sent prior to negotiating which version will be used) +#CONFIG_TLSV11=y + +# TLS-based EAP methods require at least TLS v1.0. Newer version of TLS (v1.2) +# can be enabled to enable use of stronger crypto algorithms. It should be +# noted that some existing TLS v1.0 -based implementation may not be compatible +# with TLS v1.2 message (ClientHello is sent prior to negotiating which version +# will be used) +#CONFIG_TLSV12=y + +# Select which ciphers to use by default with OpenSSL if the user does not +# specify them. +#CONFIG_TLS_DEFAULT_CIPHERS="DEFAULT:!EXP:!LOW" + +# If CONFIG_TLS=internal is used, additional library and include paths are +# needed for LibTomMath. Alternatively, an integrated, minimal version of +# LibTomMath can be used. See beginning of libtommath.c for details on benefits +# and drawbacks of this option. +#CONFIG_INTERNAL_LIBTOMMATH=y +#ifndef CONFIG_INTERNAL_LIBTOMMATH +#LTM_PATH=/usr/src/libtommath-0.39 +#CFLAGS += -I$(LTM_PATH) +#LIBS += -L$(LTM_PATH) +#LIBS_p += -L$(LTM_PATH) +#endif +# At the cost of about 4 kB of additional binary size, the internal LibTomMath +# can be configured to include faster routines for exptmod, sqr, and div to +# speed up DH and RSA calculation considerably +#CONFIG_INTERNAL_LIBTOMMATH_FAST=y + +# Include NDIS event processing through WMI into wpa_supplicant/wpasvc. +# This is only for Windows builds and requires WMI-related header files and +# WbemUuid.Lib from Platform SDK even when building with MinGW. +#CONFIG_NDIS_EVENTS_INTEGRATED=y +#PLATFORMSDKLIB="/opt/Program Files/Microsoft Platform SDK/Lib" + +# Add support for new DBus control interface +# (fi.w1.wpa_supplicant1) +CONFIG_CTRL_IFACE_DBUS_NEW=y + +# Add introspection support for new DBus control interface +CONFIG_CTRL_IFACE_DBUS_INTRO=y + +# Add support for loading EAP methods dynamically as shared libraries. +# When this option is enabled, each EAP method can be either included +# statically (CONFIG_EAP_=y) or dynamically (CONFIG_EAP_=dyn). +# Dynamic EAP methods are build as shared objects (eap_*.so) and they need to +# be loaded in the beginning of the wpa_supplicant configuration file +# (see load_dynamic_eap parameter in the example file) before being used in +# the network blocks. +# +# Note that some shared parts of EAP methods are included in the main program +# and in order to be able to use dynamic EAP methods using these parts, the +# main program must have been build with the EAP method enabled (=y or =dyn). +# This means that EAP-TLS/PEAP/TTLS/FAST cannot be added as dynamic libraries +# unless at least one of them was included in the main build to force inclusion +# of the shared code. Similarly, at least one of EAP-SIM/AKA must be included +# in the main build to be able to load these methods dynamically. +# +# Please also note that using dynamic libraries will increase the total binary +# size. Thus, it may not be the best option for targets that have limited +# amount of memory/flash. +#CONFIG_DYNAMIC_EAP_METHODS=y + +# Dynamic library loading + +# Add the ability to configure libraries to load at compile time. +# If set, these disable dynamic configuration. +#CONFIG_PKCS11_ENGINE_PATH - pkcs11_engine library location. +#CONFIG_PKCS11_MODULE_PATH - pkcs11_module library location. +#CONFIG_OPENSC_ENGINE_PATH - opensc_engine library location. +# +# Prevent library loading at runtime +#CONFIG_NO_PKCS11_ENGINE_PATH=y # prevents loading pkcs11_engine library. +#CONFIG_NO_PKCS11_MODULE_PATH=y # prevents loading pkcs11_module library. +# CONFIG_NO_OPENSC_ENGINE_PATH=y # prevents loading opensc_engine library. + +# Prevents loading EAP libraries at runtime +#CONFIG_NO_LOAD_DYNAMIC_EAP=y + +# IEEE Std 802.11r-2008 (Fast BSS Transition) for station mode +CONFIG_IEEE80211R=y + +# Add support for writing debug log to a file (/tmp/wpa_supplicant-log-#.txt) +CONFIG_DEBUG_FILE=y + +# Send debug messages to syslog instead of stdout +CONFIG_DEBUG_SYSLOG=y +# Set syslog facility for debug messages +#CONFIG_DEBUG_SYSLOG_FACILITY=LOG_DAEMON + +# Add support for sending all debug messages (regardless of debug verbosity) +# to the Linux kernel tracing facility. This helps debug the entire stack by +# making it easy to record everything happening from the driver up into the +# same file, e.g., using trace-cmd. +#CONFIG_DEBUG_LINUX_TRACING=y + +# Add support for writing debug log to Android logcat instead of standard +# output +#CONFIG_ANDROID_LOG=y + +# Enable privilege separation (see README 'Privilege separation' for details) +#CONFIG_PRIVSEP=y + +# Enable mitigation against certain attacks against TKIP by delaying Michael +# MIC error reports by a random amount of time between 0 and 60 seconds +#CONFIG_DELAYED_MIC_ERROR_REPORT=y + +# Enable tracing code for developer debugging +# This tracks use of memory allocations and other registrations and reports +# incorrect use with a backtrace of call (or allocation) location. +#CONFIG_WPA_TRACE=y +# For BSD, uncomment these. +#LIBS += -lexecinfo +#LIBS_p += -lexecinfo +#LIBS_c += -lexecinfo + +# Use libbfd to get more details for developer debugging +# This enables use of libbfd to get more detailed symbols for the backtraces +# generated by CONFIG_WPA_TRACE=y. +#CONFIG_WPA_TRACE_BFD=y +# For BSD, uncomment these. +#LIBS += -lbfd -liberty -lz +#LIBS_p += -lbfd -liberty -lz +#LIBS_c += -lbfd -liberty -lz + +# wpa_supplicant depends on strong random number generation being available +# from the operating system. os_get_random() function is used to fetch random +# data when needed, e.g., for key generation. On Linux and BSD systems, this +# works by reading /dev/urandom. It should be noted that the OS entropy pool +# needs to be properly initialized before wpa_supplicant is started. This is +# important especially on embedded devices that do not have a hardware random +# number generator and may by default start up with minimal entropy available +# for random number generation. +# +# As a safety net, wpa_supplicant is by default trying to internally collect +# additional entropy for generating random data to mix in with the data fetched +# from the OS. This by itself is not considered to be very strong, but it may +# help in cases where the system pool is not initialized properly. However, it +# is very strongly recommended that the system pool is initialized with enough +# entropy either by using hardware assisted random number generator or by +# storing state over device reboots. +# +# wpa_supplicant can be configured to maintain its own entropy store over +# restarts to enhance random number generation. This is not perfect, but it is +# much more secure than using the same sequence of random numbers after every +# reboot. This can be enabled with -e command line option. The +# specified file needs to be readable and writable by wpa_supplicant. +# +# If the os_get_random() is known to provide strong random data (e.g., on +# Linux/BSD, the board in question is known to have reliable source of random +# data from /dev/urandom), the internal wpa_supplicant random pool can be +# disabled. This will save some in binary size and CPU use. However, this +# should only be considered for builds that are known to be used on devices +# that meet the requirements described above. +#CONFIG_NO_RANDOM_POOL=y + +# Should we attempt to use the getrandom(2) call that provides more reliable +# yet secure randomness source than /dev/random on Linux 3.17 and newer. +# Requires glibc 2.25 to build, falls back to /dev/random if unavailable. +#CONFIG_GETRANDOM=y + +# IEEE 802.11ac (Very High Throughput) support (mainly for AP mode) +CONFIG_IEEE80211AC=y + +# IEEE 802.11ax HE support (mainly for AP mode) +CONFIG_IEEE80211AX=y + +# IEEE 802.11be EHT support (mainly for AP mode) +# CONFIG_IEEE80211AX is mandatory for setting CONFIG_IEEE80211BE. +# Note: This is experimental and work in progress. The definitions are still +# subject to change and this should not be expected to interoperate with the +# final IEEE 802.11be version. +#CONFIG_IEEE80211BE=y + +# Wireless Network Management (IEEE Std 802.11v-2011) +# Note: This is experimental and not complete implementation. +#CONFIG_WNM=y + +# Interworking (IEEE 802.11u) +# This can be used to enable functionality to improve interworking with +# external networks (GAS/ANQP to learn more about the networks and network +# selection based on available credentials). +CONFIG_INTERWORKING=y + +# Hotspot 2.0 +CONFIG_HS20=y + +# Enable interface matching in wpa_supplicant +#CONFIG_MATCH_IFACE=y + +# Disable roaming in wpa_supplicant +#CONFIG_NO_ROAMING=y + +# AP mode operations with wpa_supplicant +# This can be used for controlling AP mode operations with wpa_supplicant. It +# should be noted that this is mainly aimed at simple cases like +# WPA2-Personal while more complex configurations like WPA2-Enterprise with an +# external RADIUS server can be supported with hostapd. +CONFIG_AP=y + +# P2P (Wi-Fi Direct) +# This can be used to enable P2P support in wpa_supplicant. See README-P2P for +# more information on P2P operations. +CONFIG_P2P=y + +# Enable TDLS support +CONFIG_TDLS=y + +# Wi-Fi Display +# This can be used to enable Wi-Fi Display extensions for P2P using an external +# program to control the additional information exchanges in the messages. +CONFIG_WIFI_DISPLAY=y + +# Autoscan +# This can be used to enable automatic scan support in wpa_supplicant. +# See wpa_supplicant.conf for more information on autoscan usage. +# +# Enabling directly a module will enable autoscan support. +# For exponential module: +#CONFIG_AUTOSCAN_EXPONENTIAL=y +# For periodic module: +#CONFIG_AUTOSCAN_PERIODIC=y + +# Password (and passphrase, etc.) backend for external storage +# These optional mechanisms can be used to add support for storing passwords +# and other secrets in external (to wpa_supplicant) location. This allows, for +# example, operating system specific key storage to be used +# +# External password backend for testing purposes (developer use) +#CONFIG_EXT_PASSWORD_TEST=y +# File-based backend to read passwords from an external file. +#CONFIG_EXT_PASSWORD_FILE=y + +# Enable Fast Session Transfer (FST) +#CONFIG_FST=y + +# Enable CLI commands for FST testing +#CONFIG_FST_TEST=y + +# OS X builds. This is only for building eapol_test. +#CONFIG_OSX=y + +# Automatic Channel Selection +# This will allow wpa_supplicant to pick the channel automatically when channel +# is set to "0". +# +# TODO: Extend parser to be able to parse "channel=acs_survey" as an alternative +# to "channel=0". This would enable us to eventually add other ACS algorithms in +# similar way. +# +# Automatic selection is currently only done through initialization, later on +# we hope to do background checks to keep us moving to more ideal channels as +# time goes by. ACS is currently only supported through the nl80211 driver and +# your driver must have survey dump capability that is filled by the driver +# during scanning. +# +# TODO: In analogy to hostapd be able to customize the ACS survey algorithm with +# a newly to create wpa_supplicant.conf variable acs_num_scans. +# +# Supported ACS drivers: +# * ath9k +# * ath5k +# * ath10k +# +# For more details refer to: +# http://wireless.kernel.org/en/users/Documentation/acs +#CONFIG_ACS=y + +# Support Multi Band Operation +#CONFIG_MBO=y + +# Fast Initial Link Setup (FILS) (IEEE 802.11ai) +#CONFIG_FILS=y +# FILS shared key authentication with PFS +#CONFIG_FILS_SK_PFS=y + +# Support RSN on IBSS networks +# This is needed to be able to use mode=1 network profile with proto=RSN and +# key_mgmt=WPA-PSK (i.e., full key management instead of WPA-None). +CONFIG_IBSS_RSN=y + +# External PMKSA cache control +# This can be used to enable control interface commands that allow the current +# PMKSA cache entries to be fetched and new entries to be added. +#CONFIG_PMKSA_CACHE_EXTERNAL=y + +# Mesh Networking (IEEE 802.11s) +#CONFIG_MESH=y + +# Background scanning modules +# These can be used to request wpa_supplicant to perform background scanning +# operations for roaming within an ESS (same SSID). See the bgscan parameter in +# the wpa_supplicant.conf file for more details. +# Periodic background scans based on signal strength +CONFIG_BGSCAN_SIMPLE=y +# Learn channels used by the network and try to avoid bgscans on other +# channels (experimental) +#CONFIG_BGSCAN_LEARN=y + +# Opportunistic Wireless Encryption (OWE) +# Experimental implementation of draft-harkins-owe-07.txt +#CONFIG_OWE=y + +# Device Provisioning Protocol (DPP) (also known as Wi-Fi Easy Connect) +CONFIG_DPP=y +# DPP version 2 support +CONFIG_DPP2=y +# DPP version 3 support (experimental and still changing; do not enable for +# production use) +#CONFIG_DPP3=y + +# WLAN Authentication and Privacy Infrastructure (WAPI): interface only. +# Configure the building of the interface which allows WAPI configuration. +# Note: does not configure WAPI implementation itself. +#CONFIG_WAPI_INTERFACE=y + +# Wired equivalent privacy (WEP) +# WEP is an obsolete cryptographic data confidentiality algorithm that is not +# considered secure. It should not be used for anything anymore. The +# functionality needed to use WEP is available in the current wpa_supplicant +# release under this optional build parameter. This functionality is subject to +# be completely removed in a future release. +#CONFIG_WEP=y + +# Remove all TKIP functionality +# TKIP is an old cryptographic data confidentiality algorithm that is not +# considered secure. It should not be used anymore for anything else than a +# backwards compatibility option as a group cipher when connecting to APs that +# use WPA+WPA2 mixed mode. For now, the default wpa_supplicant build includes +# support for this by default, but that functionality is subject to be removed +# in the future. +#CONFIG_NO_TKIP=y + +# Pre-Association Security Negotiation (PASN) +# Experimental implementation based on IEEE P802.11z/D2.6 and the protocol +# design is still subject to change. As such, this should not yet be enabled in +# production use. +#CONFIG_PASN=y + diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.c b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.c new file mode 100644 index 000000000..e69de29bb diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.h b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.h new file mode 100644 index 000000000..e69de29bb diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.c b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.c new file mode 100644 index 000000000..6e34e8710 --- /dev/null +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.c @@ -0,0 +1,9422 @@ +/* + * WPA Supplicant + * Copyright (c) 2003-2022, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * This file implements functions for registering and unregistering + * %wpa_supplicant interfaces. In addition, this file contains number of + * functions for managing network connections. + */ + +#include "includes.h" +#ifdef CONFIG_MATCH_IFACE +#include +#include +#endif /* CONFIG_MATCH_IFACE */ + +#include "common.h" +#include "crypto/crypto.h" +#include "crypto/random.h" +#include "crypto/sha1.h" +#include "eapol_supp/eapol_supp_sm.h" +#include "eap_peer/eap.h" +#include "eap_peer/eap_proxy.h" +#include "eap_server/eap_methods.h" +#include "rsn_supp/wpa.h" +#include "eloop.h" +#include "config.h" +#include "utils/ext_password.h" +#include "l2_packet/l2_packet.h" +#include "wpa_supplicant_i.h" +#include "driver_i.h" +#include "ctrl_iface.h" +#include "pcsc_funcs.h" +#include "common/version.h" +#include "rsn_supp/preauth.h" +#include "rsn_supp/pmksa_cache.h" +#include "common/wpa_ctrl.h" +#include "common/ieee802_11_common.h" +#include "common/ieee802_11_defs.h" +#include "common/hw_features_common.h" +#include "common/gas_server.h" +#include "common/dpp.h" +#include "common/ptksa_cache.h" +#include "p2p/p2p.h" +#include "fst/fst.h" +#include "bssid_ignore.h" +#include "wpas_glue.h" +#include "wps_supplicant.h" +#include "ibss_rsn.h" +#include "sme.h" +#include "gas_query.h" +#include "ap.h" +#include "p2p_supplicant.h" +#include "wifi_display.h" +#include "notify.h" +#include "bgscan.h" +#include "autoscan.h" +#include "bss.h" +#include "scan.h" +#include "offchannel.h" +#include "hs20_supplicant.h" +#include "wnm_sta.h" +#include "wpas_kay.h" +#include "mesh.h" +#include "dpp_supplicant.h" +#ifdef CONFIG_MESH +#include "ap/ap_config.h" +#include "ap/hostapd.h" +#endif /* CONFIG_MESH */ +#include "aidl/aidl.h" + +const char *const wpa_supplicant_version = +"wpa_supplicant v" VERSION_STR "\n" +"Copyright (c) 2003-2022, Jouni Malinen and contributors"; + +const char *const wpa_supplicant_license = +"This software may be distributed under the terms of the BSD license.\n" +"See README for more details.\n" +#ifdef EAP_TLS_OPENSSL +"\nThis product includes software developed by the OpenSSL Project\n" +"for use in the OpenSSL Toolkit (http://www.openssl.org/)\n" +#endif /* EAP_TLS_OPENSSL */ +; + +#ifndef CONFIG_NO_STDOUT_DEBUG +/* Long text divided into parts in order to fit in C89 strings size limits. */ +const char *const wpa_supplicant_full_license1 = +""; +const char *const wpa_supplicant_full_license2 = +"This software may be distributed under the terms of the BSD license.\n" +"\n" +"Redistribution and use in source and binary forms, with or without\n" +"modification, are permitted provided that the following conditions are\n" +"met:\n" +"\n"; +const char *const wpa_supplicant_full_license3 = +"1. Redistributions of source code must retain the above copyright\n" +" notice, this list of conditions and the following disclaimer.\n" +"\n" +"2. Redistributions in binary form must reproduce the above copyright\n" +" notice, this list of conditions and the following disclaimer in the\n" +" documentation and/or other materials provided with the distribution.\n" +"\n"; +const char *const wpa_supplicant_full_license4 = +"3. Neither the name(s) of the above-listed copyright holder(s) nor the\n" +" names of its contributors may be used to endorse or promote products\n" +" derived from this software without specific prior written permission.\n" +"\n" +"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n" +"\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n" +"LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n" +"A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n"; +const char *const wpa_supplicant_full_license5 = +"OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n" +"SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n" +"LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n" +"DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n" +"THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" +"(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n" +"OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" +"\n"; +#endif /* CONFIG_NO_STDOUT_DEBUG */ + + +static void wpa_bss_tmp_disallow_timeout(void *eloop_ctx, void *timeout_ctx); +#if defined(CONFIG_FILS) && defined(IEEE8021X_EAPOL) +static void wpas_update_fils_connect_params(struct wpa_supplicant *wpa_s); +#endif /* CONFIG_FILS && IEEE8021X_EAPOL */ +#ifdef CONFIG_OWE +static void wpas_update_owe_connect_params(struct wpa_supplicant *wpa_s); +#endif /* CONFIG_OWE */ + + +#ifdef CONFIG_WEP +/* Configure default/group WEP keys for static WEP */ +int wpa_set_wep_keys(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) +{ + int i, set = 0; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (ssid->wep_key_len[i] == 0) + continue; + + set = 1; + wpa_drv_set_key(wpa_s, -1, WPA_ALG_WEP, NULL, + i, i == ssid->wep_tx_keyidx, NULL, 0, + ssid->wep_key[i], ssid->wep_key_len[i], + i == ssid->wep_tx_keyidx ? + KEY_FLAG_GROUP_RX_TX_DEFAULT : + KEY_FLAG_GROUP_RX_TX); + } + + return set; +} +#endif /* CONFIG_WEP */ + + +int wpa_supplicant_set_wpa_none_key(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid) +{ + u8 key[32]; + size_t keylen; + enum wpa_alg alg; + u8 seq[6] = { 0 }; + int ret; + + /* IBSS/WPA-None uses only one key (Group) for both receiving and + * sending unicast and multicast packets. */ + + if (ssid->mode != WPAS_MODE_IBSS) { + wpa_msg(wpa_s, MSG_INFO, "WPA: Invalid mode %d (not " + "IBSS/ad-hoc) for WPA-None", ssid->mode); + return -1; + } + + if (!ssid->psk_set) { + wpa_msg(wpa_s, MSG_INFO, "WPA: No PSK configured for " + "WPA-None"); + return -1; + } + + switch (wpa_s->group_cipher) { + case WPA_CIPHER_CCMP: + os_memcpy(key, ssid->psk, 16); + keylen = 16; + alg = WPA_ALG_CCMP; + break; + case WPA_CIPHER_GCMP: + os_memcpy(key, ssid->psk, 16); + keylen = 16; + alg = WPA_ALG_GCMP; + break; + case WPA_CIPHER_TKIP: + /* WPA-None uses the same Michael MIC key for both TX and RX */ + os_memcpy(key, ssid->psk, 16 + 8); + os_memcpy(key + 16 + 8, ssid->psk + 16, 8); + keylen = 32; + alg = WPA_ALG_TKIP; + break; + default: + wpa_msg(wpa_s, MSG_INFO, "WPA: Invalid group cipher %d for " + "WPA-None", wpa_s->group_cipher); + return -1; + } + + /* TODO: should actually remember the previously used seq#, both for TX + * and RX from each STA.. */ + + ret = wpa_drv_set_key(wpa_s, -1, alg, NULL, 0, 1, seq, 6, key, keylen, + KEY_FLAG_GROUP_RX_TX_DEFAULT); + os_memset(key, 0, sizeof(key)); + return ret; +} + + +static void wpa_supplicant_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + const u8 *bssid = wpa_s->bssid; + if (!is_zero_ether_addr(wpa_s->pending_bssid) && + (wpa_s->wpa_state == WPA_AUTHENTICATING || + wpa_s->wpa_state == WPA_ASSOCIATING)) + bssid = wpa_s->pending_bssid; + wpa_msg(wpa_s, MSG_INFO, "Authentication with " MACSTR " timed out.", + MAC2STR(bssid)); + wpa_bssid_ignore_add(wpa_s, bssid); + wpas_notify_auth_timeout(wpa_s); + wpa_sm_notify_disassoc(wpa_s->wpa); + wpa_supplicant_deauthenticate(wpa_s, WLAN_REASON_DEAUTH_LEAVING); + wpa_s->reassociate = 1; + + /* + * If we timed out, the AP or the local radio may be busy. + * So, wait a second until scanning again. + */ + wpa_supplicant_req_scan(wpa_s, 1, 0); +} + + +/** + * wpa_supplicant_req_auth_timeout - Schedule a timeout for authentication + * @wpa_s: Pointer to wpa_supplicant data + * @sec: Number of seconds after which to time out authentication + * @usec: Number of microseconds after which to time out authentication + * + * This function is used to schedule a timeout for the current authentication + * attempt. + */ +void wpa_supplicant_req_auth_timeout(struct wpa_supplicant *wpa_s, + int sec, int usec) +{ + if (wpa_s->conf->ap_scan == 0 && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_WIRED)) + return; + + wpa_dbg(wpa_s, MSG_DEBUG, "Setting authentication timeout: %d sec " + "%d usec", sec, usec); + eloop_cancel_timeout(wpa_supplicant_timeout, wpa_s, NULL); + wpa_s->last_auth_timeout_sec = sec; + eloop_register_timeout(sec, usec, wpa_supplicant_timeout, wpa_s, NULL); +} + + +/* + * wpas_auth_timeout_restart - Restart and change timeout for authentication + * @wpa_s: Pointer to wpa_supplicant data + * @sec_diff: difference in seconds applied to original timeout value + */ +void wpas_auth_timeout_restart(struct wpa_supplicant *wpa_s, int sec_diff) +{ + int new_sec = wpa_s->last_auth_timeout_sec + sec_diff; + + if (eloop_is_timeout_registered(wpa_supplicant_timeout, wpa_s, NULL)) { + wpa_dbg(wpa_s, MSG_DEBUG, + "Authentication timeout restart: %d sec", new_sec); + eloop_cancel_timeout(wpa_supplicant_timeout, wpa_s, NULL); + eloop_register_timeout(new_sec, 0, wpa_supplicant_timeout, + wpa_s, NULL); + } +} + + +/** + * wpa_supplicant_cancel_auth_timeout - Cancel authentication timeout + * @wpa_s: Pointer to wpa_supplicant data + * + * This function is used to cancel authentication timeout scheduled with + * wpa_supplicant_req_auth_timeout() and it is called when authentication has + * been completed. + */ +void wpa_supplicant_cancel_auth_timeout(struct wpa_supplicant *wpa_s) +{ + wpa_dbg(wpa_s, MSG_DEBUG, "Cancelling authentication timeout"); + eloop_cancel_timeout(wpa_supplicant_timeout, wpa_s, NULL); + wpa_bssid_ignore_del(wpa_s, wpa_s->bssid); + os_free(wpa_s->last_con_fail_realm); + wpa_s->last_con_fail_realm = NULL; + wpa_s->last_con_fail_realm_len = 0; +} + + +/** + * wpa_supplicant_initiate_eapol - Configure EAPOL state machine + * @wpa_s: Pointer to wpa_supplicant data + * + * This function is used to configure EAPOL state machine based on the selected + * authentication mode. + */ +void wpa_supplicant_initiate_eapol(struct wpa_supplicant *wpa_s) +{ +#ifdef IEEE8021X_EAPOL + struct eapol_config eapol_conf; + struct wpa_ssid *ssid = wpa_s->current_ssid; + +#ifdef CONFIG_IBSS_RSN + if (ssid->mode == WPAS_MODE_IBSS && + wpa_s->key_mgmt != WPA_KEY_MGMT_NONE && + wpa_s->key_mgmt != WPA_KEY_MGMT_WPA_NONE) { + /* + * RSN IBSS authentication is per-STA and we can disable the + * per-BSSID EAPOL authentication. + */ + eapol_sm_notify_portControl(wpa_s->eapol, ForceAuthorized); + eapol_sm_notify_eap_success(wpa_s->eapol, true); + eapol_sm_notify_eap_fail(wpa_s->eapol, false); + return; + } +#endif /* CONFIG_IBSS_RSN */ + + eapol_sm_notify_eap_success(wpa_s->eapol, false); + eapol_sm_notify_eap_fail(wpa_s->eapol, false); + + if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE || + wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) + eapol_sm_notify_portControl(wpa_s->eapol, ForceAuthorized); + else + eapol_sm_notify_portControl(wpa_s->eapol, Auto); + + os_memset(&eapol_conf, 0, sizeof(eapol_conf)); + if (wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA) { + eapol_conf.accept_802_1x_keys = 1; + eapol_conf.required_keys = 0; + if (ssid->eapol_flags & EAPOL_FLAG_REQUIRE_KEY_UNICAST) { + eapol_conf.required_keys |= EAPOL_REQUIRE_KEY_UNICAST; + } + if (ssid->eapol_flags & EAPOL_FLAG_REQUIRE_KEY_BROADCAST) { + eapol_conf.required_keys |= + EAPOL_REQUIRE_KEY_BROADCAST; + } + + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_WIRED) + eapol_conf.required_keys = 0; + } + eapol_conf.fast_reauth = wpa_s->conf->fast_reauth; + eapol_conf.workaround = ssid->eap_workaround; + eapol_conf.eap_disabled = + !wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt) && + wpa_s->key_mgmt != WPA_KEY_MGMT_IEEE8021X_NO_WPA && + wpa_s->key_mgmt != WPA_KEY_MGMT_WPS; + eapol_conf.external_sim = wpa_s->conf->external_sim; + +#ifdef CONFIG_WPS + if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) { + eapol_conf.wps |= EAPOL_LOCAL_WPS_IN_USE; + if (wpa_s->current_bss) { + struct wpabuf *ie; + ie = wpa_bss_get_vendor_ie_multi(wpa_s->current_bss, + WPS_IE_VENDOR_TYPE); + if (ie) { + if (wps_is_20(ie)) + eapol_conf.wps |= + EAPOL_PEER_IS_WPS20_AP; + wpabuf_free(ie); + } + } + } +#endif /* CONFIG_WPS */ + + eapol_sm_notify_config(wpa_s->eapol, &ssid->eap, &eapol_conf); + +#ifdef CONFIG_MACSEC + if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE && ssid->mka_psk_set) + ieee802_1x_create_preshared_mka(wpa_s, ssid); + else + ieee802_1x_alloc_kay_sm(wpa_s, ssid); +#endif /* CONFIG_MACSEC */ +#endif /* IEEE8021X_EAPOL */ +} + + +/** + * wpa_supplicant_set_non_wpa_policy - Set WPA parameters to non-WPA mode + * @wpa_s: Pointer to wpa_supplicant data + * @ssid: Configuration data for the network + * + * This function is used to configure WPA state machine and related parameters + * to a mode where WPA is not enabled. This is called as part of the + * authentication configuration when the selected network does not use WPA. + */ +void wpa_supplicant_set_non_wpa_policy(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid) +{ +#ifdef CONFIG_WEP + int i; +#endif /* CONFIG_WEP */ + struct wpa_sm_mlo mlo; + + if (ssid->key_mgmt & WPA_KEY_MGMT_WPS) + wpa_s->key_mgmt = WPA_KEY_MGMT_WPS; + else if (ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) + wpa_s->key_mgmt = WPA_KEY_MGMT_IEEE8021X_NO_WPA; + else + wpa_s->key_mgmt = WPA_KEY_MGMT_NONE; + wpa_sm_set_ap_wpa_ie(wpa_s->wpa, NULL, 0); + wpa_sm_set_ap_rsn_ie(wpa_s->wpa, NULL, 0); + wpa_sm_set_ap_rsnxe(wpa_s->wpa, NULL, 0); + wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, NULL, 0); + wpa_sm_set_assoc_rsnxe(wpa_s->wpa, NULL, 0); + wpa_s->rsnxe_len = 0; + wpa_s->pairwise_cipher = WPA_CIPHER_NONE; + wpa_s->group_cipher = WPA_CIPHER_NONE; + wpa_s->mgmt_group_cipher = 0; + +#ifdef CONFIG_WEP + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (ssid->wep_key_len[i] > 5) { + wpa_s->pairwise_cipher = WPA_CIPHER_WEP104; + wpa_s->group_cipher = WPA_CIPHER_WEP104; + break; + } else if (ssid->wep_key_len[i] > 0) { + wpa_s->pairwise_cipher = WPA_CIPHER_WEP40; + wpa_s->group_cipher = WPA_CIPHER_WEP40; + break; + } + } +#endif /* CONFIG_WEP */ + + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_RSN_ENABLED, 0); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_KEY_MGMT, wpa_s->key_mgmt); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_PAIRWISE, + wpa_s->pairwise_cipher); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_GROUP, wpa_s->group_cipher); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_MGMT_GROUP, + wpa_s->mgmt_group_cipher); + + pmksa_cache_clear_current(wpa_s->wpa); + os_memset(&mlo, 0, sizeof(mlo)); + wpa_sm_set_mlo_params(wpa_s->wpa, &mlo); +} + + +void free_hw_features(struct wpa_supplicant *wpa_s) +{ + int i; + if (wpa_s->hw.modes == NULL) + return; + + for (i = 0; i < wpa_s->hw.num_modes; i++) { + os_free(wpa_s->hw.modes[i].channels); + os_free(wpa_s->hw.modes[i].rates); + } + + os_free(wpa_s->hw.modes); + wpa_s->hw.modes = NULL; +} + + +static void remove_bss_tmp_disallowed_entry(struct wpa_supplicant *wpa_s, + struct wpa_bss_tmp_disallowed *bss) +{ + eloop_cancel_timeout(wpa_bss_tmp_disallow_timeout, wpa_s, bss); + dl_list_del(&bss->list); + os_free(bss); +} + + +void free_bss_tmp_disallowed(struct wpa_supplicant *wpa_s) +{ + struct wpa_bss_tmp_disallowed *bss, *prev; + + dl_list_for_each_safe(bss, prev, &wpa_s->bss_tmp_disallowed, + struct wpa_bss_tmp_disallowed, list) + remove_bss_tmp_disallowed_entry(wpa_s, bss); +} + + +void wpas_flush_fils_hlp_req(struct wpa_supplicant *wpa_s) +{ + struct fils_hlp_req *req; + + while ((req = dl_list_first(&wpa_s->fils_hlp_req, struct fils_hlp_req, + list)) != NULL) { + dl_list_del(&req->list); + wpabuf_free(req->pkt); + os_free(req); + } +} + + +void wpas_clear_disabled_interface(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + + if (wpa_s->wpa_state != WPA_INTERFACE_DISABLED) + return; + wpa_dbg(wpa_s, MSG_DEBUG, "Clear cached state on disabled interface"); + wpa_bss_flush(wpa_s); +} + + +#ifdef CONFIG_TESTING_OPTIONS +void wpas_clear_driver_signal_override(struct wpa_supplicant *wpa_s) +{ + struct driver_signal_override *dso; + + while ((dso = dl_list_first(&wpa_s->drv_signal_override, + struct driver_signal_override, list))) { + dl_list_del(&dso->list); + os_free(dso); + } +} +#endif /* CONFIG_TESTING_OPTIONS */ + + +static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) +{ + int i; + + bgscan_deinit(wpa_s); + autoscan_deinit(wpa_s); + scard_deinit(wpa_s->scard); + wpa_s->scard = NULL; + wpa_sm_set_scard_ctx(wpa_s->wpa, NULL); + eapol_sm_register_scard_ctx(wpa_s->eapol, NULL); + l2_packet_deinit(wpa_s->l2); + wpa_s->l2 = NULL; + if (wpa_s->l2_br) { + l2_packet_deinit(wpa_s->l2_br); + wpa_s->l2_br = NULL; + } +#ifdef CONFIG_TESTING_OPTIONS + l2_packet_deinit(wpa_s->l2_test); + wpa_s->l2_test = NULL; + os_free(wpa_s->get_pref_freq_list_override); + wpa_s->get_pref_freq_list_override = NULL; + wpabuf_free(wpa_s->last_assoc_req_wpa_ie); + wpa_s->last_assoc_req_wpa_ie = NULL; + os_free(wpa_s->extra_sae_rejected_groups); + wpa_s->extra_sae_rejected_groups = NULL; + wpabuf_free(wpa_s->rsne_override_eapol); + wpa_s->rsne_override_eapol = NULL; + wpabuf_free(wpa_s->rsnxe_override_assoc); + wpa_s->rsnxe_override_assoc = NULL; + wpabuf_free(wpa_s->rsnxe_override_eapol); + wpa_s->rsnxe_override_eapol = NULL; + wpas_clear_driver_signal_override(wpa_s); +#endif /* CONFIG_TESTING_OPTIONS */ + + if (wpa_s->conf != NULL) { + struct wpa_ssid *ssid; + for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) + wpas_notify_network_removed(wpa_s, ssid); + } + + os_free(wpa_s->confname); + wpa_s->confname = NULL; + + os_free(wpa_s->confanother); + wpa_s->confanother = NULL; + + os_free(wpa_s->last_con_fail_realm); + wpa_s->last_con_fail_realm = NULL; + wpa_s->last_con_fail_realm_len = 0; + + wpa_sm_set_eapol(wpa_s->wpa, NULL); + eapol_sm_deinit(wpa_s->eapol); + wpa_s->eapol = NULL; + + rsn_preauth_deinit(wpa_s->wpa); + +#ifdef CONFIG_TDLS + wpa_tdls_deinit(wpa_s->wpa); +#endif /* CONFIG_TDLS */ + + wmm_ac_clear_saved_tspecs(wpa_s); + pmksa_candidate_free(wpa_s->wpa); + ptksa_cache_deinit(wpa_s->ptksa); + wpa_s->ptksa = NULL; + wpa_sm_deinit(wpa_s->wpa); + wpa_s->wpa = NULL; + wpa_bssid_ignore_clear(wpa_s); + +#ifdef CONFIG_PASN + wpas_pasn_auth_stop(wpa_s); +#endif /* CONFIG_PASN */ + + wpa_bss_deinit(wpa_s); + + wpa_supplicant_cancel_delayed_sched_scan(wpa_s); + wpa_supplicant_cancel_scan(wpa_s); + wpa_supplicant_cancel_auth_timeout(wpa_s); + eloop_cancel_timeout(wpa_supplicant_stop_countermeasures, wpa_s, NULL); +#ifdef CONFIG_DELAYED_MIC_ERROR_REPORT + eloop_cancel_timeout(wpa_supplicant_delayed_mic_error_report, + wpa_s, NULL); +#endif /* CONFIG_DELAYED_MIC_ERROR_REPORT */ + + eloop_cancel_timeout(wpas_network_reenabled, wpa_s, NULL); + eloop_cancel_timeout(wpas_clear_disabled_interface, wpa_s, NULL); + + wpas_wps_deinit(wpa_s); + + wpabuf_free(wpa_s->pending_eapol_rx); + wpa_s->pending_eapol_rx = NULL; + +#ifdef CONFIG_IBSS_RSN + ibss_rsn_deinit(wpa_s->ibss_rsn); + wpa_s->ibss_rsn = NULL; +#endif /* CONFIG_IBSS_RSN */ + + sme_deinit(wpa_s); + +#ifdef CONFIG_AP + wpa_supplicant_ap_deinit(wpa_s); +#endif /* CONFIG_AP */ + + wpas_p2p_deinit(wpa_s); + +#ifdef CONFIG_OFFCHANNEL + offchannel_deinit(wpa_s); +#endif /* CONFIG_OFFCHANNEL */ + + wpa_supplicant_cancel_sched_scan(wpa_s); + + os_free(wpa_s->next_scan_freqs); + wpa_s->next_scan_freqs = NULL; + + os_free(wpa_s->manual_scan_freqs); + wpa_s->manual_scan_freqs = NULL; + os_free(wpa_s->select_network_scan_freqs); + wpa_s->select_network_scan_freqs = NULL; + + os_free(wpa_s->manual_sched_scan_freqs); + wpa_s->manual_sched_scan_freqs = NULL; + + wpas_mac_addr_rand_scan_clear(wpa_s, MAC_ADDR_RAND_ALL); + + /* + * Need to remove any pending gas-query radio work before the + * gas_query_deinit() call because gas_query::work has not yet been set + * for works that have not been started. gas_query_free() will be unable + * to cancel such pending radio works and once the pending gas-query + * radio work eventually gets removed, the deinit notification call to + * gas_query_start_cb() would result in dereferencing freed memory. + */ + if (wpa_s->radio) + radio_remove_works(wpa_s, "gas-query", 0); + gas_query_deinit(wpa_s->gas); + wpa_s->gas = NULL; + gas_server_deinit(wpa_s->gas_server); + wpa_s->gas_server = NULL; + + free_hw_features(wpa_s); + + ieee802_1x_dealloc_kay_sm(wpa_s); + + os_free(wpa_s->bssid_filter); + wpa_s->bssid_filter = NULL; + + os_free(wpa_s->disallow_aps_bssid); + wpa_s->disallow_aps_bssid = NULL; + os_free(wpa_s->disallow_aps_ssid); + wpa_s->disallow_aps_ssid = NULL; + + wnm_bss_keep_alive_deinit(wpa_s); +#ifdef CONFIG_WNM + wnm_deallocate_memory(wpa_s); +#endif /* CONFIG_WNM */ + + ext_password_deinit(wpa_s->ext_pw); + wpa_s->ext_pw = NULL; + + wpabuf_free(wpa_s->last_gas_resp); + wpa_s->last_gas_resp = NULL; + wpabuf_free(wpa_s->prev_gas_resp); + wpa_s->prev_gas_resp = NULL; + + os_free(wpa_s->last_scan_res); + wpa_s->last_scan_res = NULL; + +#ifdef CONFIG_HS20 + if (wpa_s->drv_priv) + wpa_drv_configure_frame_filters(wpa_s, 0); + hs20_deinit(wpa_s); +#endif /* CONFIG_HS20 */ + + for (i = 0; i < NUM_VENDOR_ELEM_FRAMES; i++) { + wpabuf_free(wpa_s->vendor_elem[i]); + wpa_s->vendor_elem[i] = NULL; + } + + wmm_ac_notify_disassoc(wpa_s); + + wpa_s->sched_scan_plans_num = 0; + os_free(wpa_s->sched_scan_plans); + wpa_s->sched_scan_plans = NULL; + +#ifdef CONFIG_MBO + wpa_s->non_pref_chan_num = 0; + os_free(wpa_s->non_pref_chan); + wpa_s->non_pref_chan = NULL; +#endif /* CONFIG_MBO */ + + free_bss_tmp_disallowed(wpa_s); + + wpabuf_free(wpa_s->lci); + wpa_s->lci = NULL; + wpas_clear_beacon_rep_data(wpa_s); + +#ifdef CONFIG_PMKSA_CACHE_EXTERNAL +#ifdef CONFIG_MESH + { + struct external_pmksa_cache *entry; + + while ((entry = dl_list_last(&wpa_s->mesh_external_pmksa_cache, + struct external_pmksa_cache, + list)) != NULL) { + dl_list_del(&entry->list); + os_free(entry->pmksa_cache); + os_free(entry); + } + } +#endif /* CONFIG_MESH */ +#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */ + + wpas_flush_fils_hlp_req(wpa_s); + + wpabuf_free(wpa_s->ric_ies); + wpa_s->ric_ies = NULL; + +#ifdef CONFIG_DPP + wpas_dpp_deinit(wpa_s); + dpp_global_deinit(wpa_s->dpp); + wpa_s->dpp = NULL; +#endif /* CONFIG_DPP */ + +#ifdef CONFIG_PASN + wpas_pasn_auth_stop(wpa_s); +#endif /* CONFIG_PASN */ + wpas_scs_deinit(wpa_s); + wpas_dscp_deinit(wpa_s); + +#ifdef CONFIG_OWE + os_free(wpa_s->owe_trans_scan_freq); + wpa_s->owe_trans_scan_freq = NULL; +#endif /* CONFIG_OWE */ +} + + +/** + * wpa_clear_keys - Clear keys configured for the driver + * @wpa_s: Pointer to wpa_supplicant data + * @addr: Previously used BSSID or %NULL if not available + * + * This function clears the encryption keys that has been previously configured + * for the driver. + */ +void wpa_clear_keys(struct wpa_supplicant *wpa_s, const u8 *addr) +{ + int i, max = 6; + + /* MLME-DELETEKEYS.request */ + for (i = 0; i < max; i++) { + if (wpa_s->keys_cleared & BIT(i)) + continue; + wpa_drv_set_key(wpa_s, -1, WPA_ALG_NONE, NULL, i, 0, NULL, 0, + NULL, 0, KEY_FLAG_GROUP); + } + /* Pairwise Key ID 1 for Extended Key ID is tracked in bit 15 */ + if (~wpa_s->keys_cleared & (BIT(0) | BIT(15)) && addr && + !is_zero_ether_addr(addr)) { + if (!(wpa_s->keys_cleared & BIT(0))) + wpa_drv_set_key(wpa_s, -1, WPA_ALG_NONE, addr, 0, 0, + NULL, 0, NULL, 0, KEY_FLAG_PAIRWISE); + if (!(wpa_s->keys_cleared & BIT(15))) + wpa_drv_set_key(wpa_s, -1, WPA_ALG_NONE, addr, 1, 0, + NULL, 0, NULL, 0, KEY_FLAG_PAIRWISE); + /* MLME-SETPROTECTION.request(None) */ + wpa_drv_mlme_setprotection( + wpa_s, addr, + MLME_SETPROTECTION_PROTECT_TYPE_NONE, + MLME_SETPROTECTION_KEY_TYPE_PAIRWISE); + } + wpa_s->keys_cleared = (u32) -1; +} + + +/** + * wpa_supplicant_state_txt - Get the connection state name as a text string + * @state: State (wpa_state; WPA_*) + * Returns: The state name as a printable text string + */ +const char * wpa_supplicant_state_txt(enum wpa_states state) +{ + switch (state) { + case WPA_DISCONNECTED: + return "DISCONNECTED"; + case WPA_INACTIVE: + return "INACTIVE"; + case WPA_INTERFACE_DISABLED: + return "INTERFACE_DISABLED"; + case WPA_SCANNING: + return "SCANNING"; + case WPA_AUTHENTICATING: + return "AUTHENTICATING"; + case WPA_ASSOCIATING: + return "ASSOCIATING"; + case WPA_ASSOCIATED: + return "ASSOCIATED"; + case WPA_4WAY_HANDSHAKE: + return "4WAY_HANDSHAKE"; + case WPA_GROUP_HANDSHAKE: + return "GROUP_HANDSHAKE"; + case WPA_COMPLETED: + return "COMPLETED"; + default: + return "UNKNOWN"; + } +} + + +#ifdef CONFIG_BGSCAN + +static void wpa_supplicant_stop_bgscan(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->bgscan_ssid) { + bgscan_deinit(wpa_s); + wpa_s->bgscan_ssid = NULL; + } +} + + +/** + * wpa_supplicant_reset_bgscan - Reset the bgscan for the current SSID. + * @wpa_s: Pointer to the wpa_supplicant data + * + * Stop, start, or reconfigure the scan parameters depending on the method. + */ +void wpa_supplicant_reset_bgscan(struct wpa_supplicant *wpa_s) +{ + const char *name; + + if (wpa_s->current_ssid && wpa_s->current_ssid->bgscan) + name = wpa_s->current_ssid->bgscan; + else + name = wpa_s->conf->bgscan; + if (!name || name[0] == '\0') { + wpa_supplicant_stop_bgscan(wpa_s); + return; + } + if (wpas_driver_bss_selection(wpa_s)) + return; +#ifdef CONFIG_P2P + if (wpa_s->p2p_group_interface != NOT_P2P_GROUP_INTERFACE) + return; +#endif /* CONFIG_P2P */ + + bgscan_deinit(wpa_s); + if (wpa_s->current_ssid) { + if (bgscan_init(wpa_s, wpa_s->current_ssid, name)) { + wpa_dbg(wpa_s, MSG_DEBUG, "Failed to initialize " + "bgscan"); + /* + * Live without bgscan; it is only used as a roaming + * optimization, so the initial connection is not + * affected. + */ + } else { + struct wpa_scan_results *scan_res; + wpa_s->bgscan_ssid = wpa_s->current_ssid; + scan_res = wpa_supplicant_get_scan_results(wpa_s, NULL, + 0); + if (scan_res) { + bgscan_notify_scan(wpa_s, scan_res); + wpa_scan_results_free(scan_res); + } + } + } else + wpa_s->bgscan_ssid = NULL; +} + +#endif /* CONFIG_BGSCAN */ + + +static void wpa_supplicant_start_autoscan(struct wpa_supplicant *wpa_s) +{ + if (autoscan_init(wpa_s, 0)) + wpa_dbg(wpa_s, MSG_DEBUG, "Failed to initialize autoscan"); +} + + +static void wpa_supplicant_stop_autoscan(struct wpa_supplicant *wpa_s) +{ + autoscan_deinit(wpa_s); +} + + +void wpa_supplicant_reinit_autoscan(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->wpa_state == WPA_DISCONNECTED || + wpa_s->wpa_state == WPA_SCANNING) { + autoscan_deinit(wpa_s); + wpa_supplicant_start_autoscan(wpa_s); + } +} + + +/** + * wpa_supplicant_set_state - Set current connection state + * @wpa_s: Pointer to wpa_supplicant data + * @state: The new connection state + * + * This function is called whenever the connection state changes, e.g., + * association is completed for WPA/WPA2 4-Way Handshake is started. + */ +void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s, + enum wpa_states state) +{ + enum wpa_states old_state = wpa_s->wpa_state; +#if defined(CONFIG_FILS) && defined(IEEE8021X_EAPOL) + bool update_fils_connect_params = false; +#endif /* CONFIG_FILS && IEEE8021X_EAPOL */ + + wpa_dbg(wpa_s, MSG_DEBUG, "State: %s -> %s", + wpa_supplicant_state_txt(wpa_s->wpa_state), + wpa_supplicant_state_txt(state)); + + if (state == WPA_COMPLETED && + os_reltime_initialized(&wpa_s->roam_start)) { + os_reltime_age(&wpa_s->roam_start, &wpa_s->roam_time); + wpa_s->roam_start.sec = 0; + wpa_s->roam_start.usec = 0; + wpas_notify_auth_changed(wpa_s); + wpas_notify_roam_time(wpa_s); + wpas_notify_roam_complete(wpa_s); + } else if (state == WPA_DISCONNECTED && + os_reltime_initialized(&wpa_s->roam_start)) { + wpa_s->roam_start.sec = 0; + wpa_s->roam_start.usec = 0; + wpa_s->roam_time.sec = 0; + wpa_s->roam_time.usec = 0; + wpas_notify_roam_complete(wpa_s); + } + + if (state == WPA_INTERFACE_DISABLED) { + /* Assure normal scan when interface is restored */ + wpa_s->normal_scans = 0; + } + + if (state == WPA_COMPLETED) { + wpas_connect_work_done(wpa_s); + /* Reinitialize normal_scan counter */ + wpa_s->normal_scans = 0; + } + +#ifdef CONFIG_P2P + /* + * P2PS client has to reply to Probe Request frames received on the + * group operating channel. Enable Probe Request frame reporting for + * P2P connected client in case p2p_cli_probe configuration property is + * set to 1. + */ + if (wpa_s->conf->p2p_cli_probe && wpa_s->current_ssid && + wpa_s->current_ssid->mode == WPAS_MODE_INFRA && + wpa_s->current_ssid->p2p_group) { + if (state == WPA_COMPLETED && !wpa_s->p2p_cli_probe) { + wpa_dbg(wpa_s, MSG_DEBUG, + "P2P: Enable CLI Probe Request RX reporting"); + wpa_s->p2p_cli_probe = + wpa_drv_probe_req_report(wpa_s, 1) >= 0; + } else if (state != WPA_COMPLETED && wpa_s->p2p_cli_probe) { + wpa_dbg(wpa_s, MSG_DEBUG, + "P2P: Disable CLI Probe Request RX reporting"); + wpa_s->p2p_cli_probe = 0; + wpa_drv_probe_req_report(wpa_s, 0); + } + } +#endif /* CONFIG_P2P */ + + if (state != WPA_SCANNING) + wpa_supplicant_notify_scanning(wpa_s, 0); + + if (state == WPA_COMPLETED && wpa_s->new_connection) { + struct wpa_ssid *ssid = wpa_s->current_ssid; + int fils_hlp_sent = 0; + char mld_addr[50]; + + mld_addr[0] = '\0'; + if (wpa_s->valid_links) + os_snprintf(mld_addr, sizeof(mld_addr), + " ap_mld_addr=" MACSTR, + MAC2STR(wpa_s->ap_mld_addr)); + +#ifdef CONFIG_SME + if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && + wpa_auth_alg_fils(wpa_s->sme.auth_alg)) + fils_hlp_sent = 1; +#endif /* CONFIG_SME */ + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && + wpa_auth_alg_fils(wpa_s->auth_alg)) + fils_hlp_sent = 1; + +#if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG) + wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_CONNECTED "- Connection to " + MACSTR " completed [id=%d id_str=%s%s]%s", + MAC2STR(wpa_s->bssid), + ssid ? ssid->id : -1, + ssid && ssid->id_str ? ssid->id_str : "", + fils_hlp_sent ? " FILS_HLP_SENT" : "", mld_addr); +#endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */ + wpas_clear_temp_disabled(wpa_s, ssid, 1); + wpa_s->consecutive_conn_failures = 0; + wpa_s->new_connection = 0; + wpa_drv_set_operstate(wpa_s, 1); +#ifndef IEEE8021X_EAPOL + wpa_drv_set_supp_port(wpa_s, 1); +#endif /* IEEE8021X_EAPOL */ + wpa_s->after_wps = 0; + wpa_s->known_wps_freq = 0; + wpas_p2p_completed(wpa_s); + + sme_sched_obss_scan(wpa_s, 1); + +#if defined(CONFIG_FILS) && defined(IEEE8021X_EAPOL) + if (!fils_hlp_sent && ssid && ssid->eap.erp) + update_fils_connect_params = true; +#endif /* CONFIG_FILS && IEEE8021X_EAPOL */ +#ifdef CONFIG_OWE + if (ssid && (ssid->key_mgmt & WPA_KEY_MGMT_OWE)) + wpas_update_owe_connect_params(wpa_s); +#endif /* CONFIG_OWE */ +#ifdef CONFIG_HS20 + hs20_configure_frame_filters(wpa_s); +#endif + } else if (state == WPA_DISCONNECTED || state == WPA_ASSOCIATING || + state == WPA_ASSOCIATED) { + wpa_s->new_connection = 1; + wpa_drv_set_operstate(wpa_s, 0); +#ifndef IEEE8021X_EAPOL + wpa_drv_set_supp_port(wpa_s, 0); +#endif /* IEEE8021X_EAPOL */ + sme_sched_obss_scan(wpa_s, 0); + } + wpa_s->wpa_state = state; + +#ifdef CONFIG_BGSCAN + if (state == WPA_COMPLETED && wpa_s->current_ssid != wpa_s->bgscan_ssid) + wpa_supplicant_reset_bgscan(wpa_s); + else if (state < WPA_ASSOCIATED) + wpa_supplicant_stop_bgscan(wpa_s); +#endif /* CONFIG_BGSCAN */ + + if (state > WPA_SCANNING) + wpa_supplicant_stop_autoscan(wpa_s); + + if (state == WPA_DISCONNECTED || state == WPA_INACTIVE) + wpa_supplicant_start_autoscan(wpa_s); + + if (old_state >= WPA_ASSOCIATED && wpa_s->wpa_state < WPA_ASSOCIATED) + wmm_ac_notify_disassoc(wpa_s); + + if (wpa_s->wpa_state != old_state) { + wpas_notify_state_changed(wpa_s, wpa_s->wpa_state, old_state); + + /* + * Notify the P2P Device interface about a state change in one + * of the interfaces. + */ + wpas_p2p_indicate_state_change(wpa_s); + + if (wpa_s->wpa_state == WPA_COMPLETED || + old_state == WPA_COMPLETED) + wpas_notify_auth_changed(wpa_s); +#ifdef CONFIG_DPP2 + if (wpa_s->wpa_state == WPA_COMPLETED) + wpas_dpp_connected(wpa_s); +#endif /* CONFIG_DPP2 */ + } +#if defined(CONFIG_FILS) && defined(IEEE8021X_EAPOL) + if (update_fils_connect_params) + wpas_update_fils_connect_params(wpa_s); +#endif /* CONFIG_FILS && IEEE8021X_EAPOL */ +} + + +void wpa_supplicant_terminate_proc(struct wpa_global *global) +{ + int pending = 0; +#ifdef CONFIG_WPS + struct wpa_supplicant *wpa_s = global->ifaces; + while (wpa_s) { + struct wpa_supplicant *next = wpa_s->next; + if (wpas_wps_terminate_pending(wpa_s) == 1) + pending = 1; +#ifdef CONFIG_P2P + if (wpa_s->p2p_group_interface != NOT_P2P_GROUP_INTERFACE || + (wpa_s->current_ssid && wpa_s->current_ssid->p2p_group)) + wpas_p2p_disconnect(wpa_s); +#endif /* CONFIG_P2P */ + wpa_s = next; + } +#endif /* CONFIG_WPS */ + if (pending) + return; + eloop_terminate(); +} + + +static void wpa_supplicant_terminate(int sig, void *signal_ctx) +{ + struct wpa_global *global = signal_ctx; + wpa_supplicant_terminate_proc(global); +} + + +void wpa_supplicant_clear_status(struct wpa_supplicant *wpa_s) +{ + enum wpa_states old_state = wpa_s->wpa_state; + enum wpa_states new_state; + + if (old_state == WPA_SCANNING) + new_state = WPA_SCANNING; + else + new_state = WPA_DISCONNECTED; + + wpa_s->pairwise_cipher = 0; + wpa_s->group_cipher = 0; + wpa_s->mgmt_group_cipher = 0; + wpa_s->key_mgmt = 0; + wpa_s->allowed_key_mgmts = 0; + if (wpa_s->wpa_state != WPA_INTERFACE_DISABLED) + wpa_supplicant_set_state(wpa_s, new_state); + + if (wpa_s->wpa_state != old_state) + wpas_notify_state_changed(wpa_s, wpa_s->wpa_state, old_state); +} + + +/** + * wpa_supplicant_reload_configuration - Reload configuration data + * @wpa_s: Pointer to wpa_supplicant data + * Returns: 0 on success or -1 if configuration parsing failed + * + * This function can be used to request that the configuration data is reloaded + * (e.g., after configuration file change). This function is reloading + * configuration only for one interface, so this may need to be called multiple + * times if %wpa_supplicant is controlling multiple interfaces and all + * interfaces need reconfiguration. + */ +int wpa_supplicant_reload_configuration(struct wpa_supplicant *wpa_s) +{ + struct wpa_config *conf; + int reconf_ctrl; + int old_ap_scan; + + if (wpa_s->confname == NULL) + return -1; + conf = wpa_config_read(wpa_s->confname, NULL, false); + if (conf == NULL) { + wpa_msg(wpa_s, MSG_ERROR, "Failed to parse the configuration " + "file '%s' - exiting", wpa_s->confname); + return -1; + } + if (wpa_s->confanother && + !wpa_config_read(wpa_s->confanother, conf, true)) { + wpa_msg(wpa_s, MSG_ERROR, + "Failed to parse the configuration file '%s' - exiting", + wpa_s->confanother); + return -1; + } + + conf->changed_parameters = (unsigned int) -1; + + reconf_ctrl = !!conf->ctrl_interface != !!wpa_s->conf->ctrl_interface + || (conf->ctrl_interface && wpa_s->conf->ctrl_interface && + os_strcmp(conf->ctrl_interface, + wpa_s->conf->ctrl_interface) != 0); + + if (reconf_ctrl) { + wpa_supplicant_ctrl_iface_deinit(wpa_s, wpa_s->ctrl_iface); + wpa_s->ctrl_iface = NULL; + } + + eapol_sm_invalidate_cached_session(wpa_s->eapol); + if (wpa_s->current_ssid) { + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) + wpa_s->own_disconnect_req = 1; + wpa_supplicant_deauthenticate(wpa_s, + WLAN_REASON_DEAUTH_LEAVING); + } + + /* + * TODO: should notify EAPOL SM about changes in opensc_engine_path, + * pkcs11_engine_path, pkcs11_module_path, openssl_ciphers. + */ + if (wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) || + wpa_s->key_mgmt == WPA_KEY_MGMT_OWE || + wpa_s->key_mgmt == WPA_KEY_MGMT_DPP) { + /* + * Clear forced success to clear EAP state for next + * authentication. + */ + eapol_sm_notify_eap_success(wpa_s->eapol, false); + } + eapol_sm_notify_config(wpa_s->eapol, NULL, NULL); + wpa_sm_set_config(wpa_s->wpa, NULL); + wpa_sm_pmksa_cache_flush(wpa_s->wpa, NULL); + wpa_sm_set_fast_reauth(wpa_s->wpa, wpa_s->conf->fast_reauth); + rsn_preauth_deinit(wpa_s->wpa); + + old_ap_scan = wpa_s->conf->ap_scan; + wpa_config_free(wpa_s->conf); + wpa_s->conf = conf; + if (old_ap_scan != wpa_s->conf->ap_scan) + wpas_notify_ap_scan_changed(wpa_s); + + if (reconf_ctrl) + wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s); + + wpa_supplicant_update_config(wpa_s); + + wpa_supplicant_clear_status(wpa_s); + if (wpa_supplicant_enabled_networks(wpa_s)) { + wpa_s->reassociate = 1; + wpa_supplicant_req_scan(wpa_s, 0, 0); + } + wpa_bssid_ignore_clear(wpa_s); + wpa_dbg(wpa_s, MSG_DEBUG, "Reconfiguration completed"); + return 0; +} + + +static void wpa_supplicant_reconfig(int sig, void *signal_ctx) +{ + struct wpa_global *global = signal_ctx; + struct wpa_supplicant *wpa_s; + for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) { + wpa_dbg(wpa_s, MSG_DEBUG, "Signal %d received - reconfiguring", + sig); + if (wpa_supplicant_reload_configuration(wpa_s) < 0) { + wpa_supplicant_terminate_proc(global); + } + } + + if (wpa_debug_reopen_file() < 0) { + /* Ignore errors since we cannot really do much to fix this */ + wpa_printf(MSG_DEBUG, "Could not reopen debug log file"); + } +} + + +static int wpa_supplicant_suites_from_ai(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, + struct wpa_ie_data *ie) +{ + int ret = wpa_sm_parse_own_wpa_ie(wpa_s->wpa, ie); + if (ret) { + if (ret == -2) { + wpa_msg(wpa_s, MSG_INFO, "WPA: Failed to parse WPA IE " + "from association info"); + } + return -1; + } + + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Using WPA IE from AssocReq to set " + "cipher suites"); + if (!(ie->group_cipher & ssid->group_cipher)) { + wpa_msg(wpa_s, MSG_INFO, "WPA: Driver used disabled group " + "cipher 0x%x (mask 0x%x) - reject", + ie->group_cipher, ssid->group_cipher); + return -1; + } + if (!(ie->pairwise_cipher & ssid->pairwise_cipher)) { + wpa_msg(wpa_s, MSG_INFO, "WPA: Driver used disabled pairwise " + "cipher 0x%x (mask 0x%x) - reject", + ie->pairwise_cipher, ssid->pairwise_cipher); + return -1; + } + if (!(ie->key_mgmt & ssid->key_mgmt)) { + wpa_msg(wpa_s, MSG_INFO, "WPA: Driver used disabled key " + "management 0x%x (mask 0x%x) - reject", + ie->key_mgmt, ssid->key_mgmt); + return -1; + } + + if (!(ie->capabilities & WPA_CAPABILITY_MFPC) && + wpas_get_ssid_pmf(wpa_s, ssid) == MGMT_FRAME_PROTECTION_REQUIRED) { + wpa_msg(wpa_s, MSG_INFO, "WPA: Driver associated with an AP " + "that does not support management frame protection - " + "reject"); + return -1; + } + + return 0; +} + + +static int matching_ciphers(struct wpa_ssid *ssid, struct wpa_ie_data *ie, + int freq) +{ + if (!ie->has_group) + ie->group_cipher = wpa_default_rsn_cipher(freq); + if (!ie->has_pairwise) + ie->pairwise_cipher = wpa_default_rsn_cipher(freq); + return (ie->group_cipher & ssid->group_cipher) && + (ie->pairwise_cipher & ssid->pairwise_cipher); +} + + +void wpas_set_mgmt_group_cipher(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, struct wpa_ie_data *ie) +{ + int sel; + + sel = ie->mgmt_group_cipher; + if (ssid->group_mgmt_cipher) + sel &= ssid->group_mgmt_cipher; + if (wpas_get_ssid_pmf(wpa_s, ssid) == NO_MGMT_FRAME_PROTECTION || + !(ie->capabilities & WPA_CAPABILITY_MFPC)) + sel = 0; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: AP mgmt_group_cipher 0x%x network profile mgmt_group_cipher 0x%x; available mgmt_group_cipher 0x%x", + ie->mgmt_group_cipher, ssid->group_mgmt_cipher, sel); + if (sel & WPA_CIPHER_AES_128_CMAC) { + wpa_s->mgmt_group_cipher = WPA_CIPHER_AES_128_CMAC; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using MGMT group cipher AES-128-CMAC"); + } else if (sel & WPA_CIPHER_BIP_GMAC_128) { + wpa_s->mgmt_group_cipher = WPA_CIPHER_BIP_GMAC_128; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using MGMT group cipher BIP-GMAC-128"); + } else if (sel & WPA_CIPHER_BIP_GMAC_256) { + wpa_s->mgmt_group_cipher = WPA_CIPHER_BIP_GMAC_256; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using MGMT group cipher BIP-GMAC-256"); + } else if (sel & WPA_CIPHER_BIP_CMAC_256) { + wpa_s->mgmt_group_cipher = WPA_CIPHER_BIP_CMAC_256; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using MGMT group cipher BIP-CMAC-256"); + } else { + wpa_s->mgmt_group_cipher = 0; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: not using MGMT group cipher"); + } + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_MGMT_GROUP, + wpa_s->mgmt_group_cipher); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_MFP, + wpas_get_ssid_pmf(wpa_s, ssid)); +} + +/** + * wpa_supplicant_get_psk - Get PSK from config or external database + * @wpa_s: Pointer to wpa_supplicant data + * @bss: Scan results for the selected BSS, or %NULL if not available + * @ssid: Configuration data for the selected network + * @psk: Buffer for the PSK + * Returns: 0 on success or -1 if configuration parsing failed + * + * This function obtains the PSK for a network, either included inline in the + * config or retrieved from an external database. + */ +static int wpa_supplicant_get_psk(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, struct wpa_ssid *ssid, + u8 *psk) +{ + if (ssid->psk_set) { + wpa_hexdump_key(MSG_MSGDUMP, "PSK (set in config)", + ssid->psk, PMK_LEN); + os_memcpy(psk, ssid->psk, PMK_LEN); + return 0; + } + +#ifndef CONFIG_NO_PBKDF2 + if (bss && ssid->bssid_set && ssid->ssid_len == 0 && ssid->passphrase) { + if (pbkdf2_sha1(ssid->passphrase, bss->ssid, bss->ssid_len, + 4096, psk, PMK_LEN) != 0) { + wpa_msg(wpa_s, MSG_WARNING, "Error in pbkdf2_sha1()"); + return -1; + } + wpa_hexdump_key(MSG_MSGDUMP, "PSK (from passphrase)", + psk, PMK_LEN); + return 0; + } +#endif /* CONFIG_NO_PBKDF2 */ + +#ifdef CONFIG_EXT_PASSWORD + if (ssid->ext_psk) { + struct wpabuf *pw = ext_password_get(wpa_s->ext_pw, + ssid->ext_psk); + char pw_str[64 + 1]; + + if (!pw) { + wpa_msg(wpa_s, MSG_INFO, + "EXT PW: No PSK found from external storage"); + return -1; + } + + if (wpabuf_len(pw) < 8 || wpabuf_len(pw) > 64) { + wpa_msg(wpa_s, MSG_INFO, + "EXT PW: Unexpected PSK length %d in external storage", + (int) wpabuf_len(pw)); + ext_password_free(pw); + return -1; + } + + os_memcpy(pw_str, wpabuf_head(pw), wpabuf_len(pw)); + pw_str[wpabuf_len(pw)] = '\0'; + +#ifndef CONFIG_NO_PBKDF2 + if (wpabuf_len(pw) >= 8 && wpabuf_len(pw) < 64 && bss) + { + if (pbkdf2_sha1(pw_str, bss->ssid, bss->ssid_len, + 4096, psk, PMK_LEN) != 0) { + wpa_msg(wpa_s, MSG_WARNING, + "Error in pbkdf2_sha1()"); + forced_memzero(pw_str, sizeof(pw_str)); + ext_password_free(pw); + return -1; + } + wpa_hexdump_key(MSG_MSGDUMP, + "PSK (from external passphrase)", + psk, PMK_LEN); + } else +#endif /* CONFIG_NO_PBKDF2 */ + if (wpabuf_len(pw) == 2 * PMK_LEN) { + if (hexstr2bin(pw_str, psk, PMK_LEN) < 0) { + wpa_msg(wpa_s, MSG_INFO, + "EXT PW: Invalid PSK hex string"); + forced_memzero(pw_str, sizeof(pw_str)); + ext_password_free(pw); + return -1; + } + wpa_hexdump_key(MSG_MSGDUMP, "PSK (from external PSK)", + psk, PMK_LEN); + } else { + wpa_msg(wpa_s, MSG_INFO, + "EXT PW: No suitable PSK available"); + forced_memzero(pw_str, sizeof(pw_str)); + ext_password_free(pw); + return -1; + } + + forced_memzero(pw_str, sizeof(pw_str)); + ext_password_free(pw); + + return 0; + } +#endif /* CONFIG_EXT_PASSWORD */ + + return -1; +} + + +static void wpas_update_allowed_key_mgmt(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid) +{ + int akm_count = wpa_s->max_num_akms; + u8 capab = 0; + + if (akm_count < 2) + return; + + akm_count--; + wpa_s->allowed_key_mgmts = 0; + switch (wpa_s->key_mgmt) { + case WPA_KEY_MGMT_PSK: + if (ssid->key_mgmt & WPA_KEY_MGMT_SAE) { + akm_count--; + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_SAE; + } + if (!akm_count) + break; + if (ssid->key_mgmt & WPA_KEY_MGMT_SAE_EXT_KEY) { + akm_count--; + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_SAE_EXT_KEY; + } + if (!akm_count) + break; + if (ssid->key_mgmt & WPA_KEY_MGMT_PSK_SHA256) + wpa_s->allowed_key_mgmts |= + WPA_KEY_MGMT_PSK_SHA256; + break; + case WPA_KEY_MGMT_PSK_SHA256: + if (ssid->key_mgmt & WPA_KEY_MGMT_SAE) { + akm_count--; + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_SAE; + } + if (!akm_count) + break; + if (ssid->key_mgmt & WPA_KEY_MGMT_SAE_EXT_KEY) { + akm_count--; + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_SAE_EXT_KEY; + } + if (!akm_count) + break; + if (ssid->key_mgmt & WPA_KEY_MGMT_PSK) + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_PSK; + break; + case WPA_KEY_MGMT_SAE: + if (ssid->key_mgmt & WPA_KEY_MGMT_PSK) { + akm_count--; + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_PSK; + } + if (!akm_count) + break; + if (ssid->key_mgmt & WPA_KEY_MGMT_SAE_EXT_KEY) { + akm_count--; + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_SAE_EXT_KEY; + } + if (!akm_count) + break; + if (ssid->key_mgmt & WPA_KEY_MGMT_PSK_SHA256) + wpa_s->allowed_key_mgmts |= + WPA_KEY_MGMT_PSK_SHA256; + break; + case WPA_KEY_MGMT_SAE_EXT_KEY: + if (ssid->key_mgmt & WPA_KEY_MGMT_SAE) { + akm_count--; + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_SAE; + } + if (!akm_count) + break; + if (ssid->key_mgmt & WPA_KEY_MGMT_PSK) { + akm_count--; + wpa_s->allowed_key_mgmts |= WPA_KEY_MGMT_PSK; + } + if (!akm_count) + break; + if (ssid->key_mgmt & WPA_KEY_MGMT_PSK_SHA256) + wpa_s->allowed_key_mgmts |= + WPA_KEY_MGMT_PSK_SHA256; + break; + default: + return; + } + + if (wpa_s->conf->sae_pwe != SAE_PWE_HUNT_AND_PECK && + wpa_s->conf->sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK) + capab |= BIT(WLAN_RSNX_CAPAB_SAE_H2E); +#ifdef CONFIG_SAE_PK + if (ssid->sae_pk) + capab |= BIT(WLAN_RSNX_CAPAB_SAE_PK); +#endif /* CONFIG_SAE_PK */ + + if (!((wpa_s->allowed_key_mgmts & + (WPA_KEY_MGMT_SAE | WPA_KEY_MGMT_SAE_EXT_KEY)) && capab)) + return; + + if (!wpa_s->rsnxe_len) { + wpa_s->rsnxe_len = 3; + wpa_s->rsnxe[0] = WLAN_EID_RSNX; + wpa_s->rsnxe[1] = 1; + wpa_s->rsnxe[2] = 0; + } + + wpa_s->rsnxe[2] |= capab; +} + + +/** + * wpa_supplicant_set_suites - Set authentication and encryption parameters + * @wpa_s: Pointer to wpa_supplicant data + * @bss: Scan results for the selected BSS, or %NULL if not available + * @ssid: Configuration data for the selected network + * @wpa_ie: Buffer for the WPA/RSN IE + * @wpa_ie_len: Maximum wpa_ie buffer size on input. This is changed to be the + * used buffer length in case the functions returns success. + * @skip_default_rsne: Whether to skip setting of the default RSNE/RSNXE + * Returns: 0 on success or -1 on failure + * + * This function is used to configure authentication and encryption parameters + * based on the network configuration and scan result for the selected BSS (if + * available). + */ +int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, struct wpa_ssid *ssid, + u8 *wpa_ie, size_t *wpa_ie_len, + bool skip_default_rsne) +{ + struct wpa_ie_data ie; + int sel, proto; + enum sae_pwe sae_pwe; + const u8 *bss_wpa, *bss_rsn, *bss_rsnx, *bss_osen; + bool wmm; + + if (bss) { + bss_wpa = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); + bss_rsn = wpa_bss_get_ie(bss, WLAN_EID_RSN); + bss_rsnx = wpa_bss_get_ie(bss, WLAN_EID_RSNX); + bss_osen = wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE); + } else { + bss_wpa = bss_rsn = bss_rsnx = bss_osen = NULL; + } + + if (bss_rsn && (ssid->proto & WPA_PROTO_RSN) && + wpa_parse_wpa_ie(bss_rsn, 2 + bss_rsn[1], &ie) == 0 && + matching_ciphers(ssid, &ie, bss->freq) && + (ie.key_mgmt & ssid->key_mgmt)) { + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: using IEEE 802.11i/D9.0"); + proto = WPA_PROTO_RSN; + } else if (bss_wpa && (ssid->proto & WPA_PROTO_WPA) && + wpa_parse_wpa_ie(bss_wpa, 2 + bss_wpa[1], &ie) == 0 && + (ie.group_cipher & ssid->group_cipher) && + (ie.pairwise_cipher & ssid->pairwise_cipher) && + (ie.key_mgmt & ssid->key_mgmt)) { + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using IEEE 802.11i/D3.0"); + proto = WPA_PROTO_WPA; +#ifdef CONFIG_HS20 + } else if (bss_osen && (ssid->proto & WPA_PROTO_OSEN) && + wpa_parse_wpa_ie(bss_osen, 2 + bss_osen[1], &ie) == 0 && + (ie.group_cipher & ssid->group_cipher) && + (ie.pairwise_cipher & ssid->pairwise_cipher) && + (ie.key_mgmt & ssid->key_mgmt)) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: using OSEN"); + proto = WPA_PROTO_OSEN; + } else if (bss_rsn && (ssid->proto & WPA_PROTO_OSEN) && + wpa_parse_wpa_ie(bss_rsn, 2 + bss_rsn[1], &ie) == 0 && + (ie.group_cipher & ssid->group_cipher) && + (ie.pairwise_cipher & ssid->pairwise_cipher) && + (ie.key_mgmt & ssid->key_mgmt)) { + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: using OSEN (within RSN)"); + proto = WPA_PROTO_RSN; +#endif /* CONFIG_HS20 */ + } else if (bss) { + wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to select WPA/RSN"); + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: ssid proto=0x%x pairwise_cipher=0x%x group_cipher=0x%x key_mgmt=0x%x", + ssid->proto, ssid->pairwise_cipher, ssid->group_cipher, + ssid->key_mgmt); + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: BSS " MACSTR " ssid='%s'%s%s%s", + MAC2STR(bss->bssid), + wpa_ssid_txt(bss->ssid, bss->ssid_len), + bss_wpa ? " WPA" : "", + bss_rsn ? " RSN" : "", + bss_osen ? " OSEN" : ""); + if (bss_rsn) { + wpa_hexdump(MSG_DEBUG, "RSN", bss_rsn, 2 + bss_rsn[1]); + if (wpa_parse_wpa_ie(bss_rsn, 2 + bss_rsn[1], &ie)) { + wpa_dbg(wpa_s, MSG_DEBUG, + "Could not parse RSN element"); + } else { + wpa_dbg(wpa_s, MSG_DEBUG, + "RSN: pairwise_cipher=0x%x group_cipher=0x%x key_mgmt=0x%x", + ie.pairwise_cipher, ie.group_cipher, + ie.key_mgmt); + } + } + if (bss_wpa) { + wpa_hexdump(MSG_DEBUG, "WPA", bss_wpa, 2 + bss_wpa[1]); + if (wpa_parse_wpa_ie(bss_wpa, 2 + bss_wpa[1], &ie)) { + wpa_dbg(wpa_s, MSG_DEBUG, + "Could not parse WPA element"); + } else { + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: pairwise_cipher=0x%x group_cipher=0x%x key_mgmt=0x%x", + ie.pairwise_cipher, ie.group_cipher, + ie.key_mgmt); + } + } + return -1; + } else { + if (ssid->proto & WPA_PROTO_OSEN) + proto = WPA_PROTO_OSEN; + else if (ssid->proto & WPA_PROTO_RSN) + proto = WPA_PROTO_RSN; + else + proto = WPA_PROTO_WPA; + if (wpa_supplicant_suites_from_ai(wpa_s, ssid, &ie) < 0) { + os_memset(&ie, 0, sizeof(ie)); + ie.group_cipher = ssid->group_cipher; + ie.pairwise_cipher = ssid->pairwise_cipher; + ie.key_mgmt = ssid->key_mgmt; + ie.mgmt_group_cipher = 0; + if (ssid->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + if (ssid->group_mgmt_cipher & + WPA_CIPHER_BIP_GMAC_256) + ie.mgmt_group_cipher = + WPA_CIPHER_BIP_GMAC_256; + else if (ssid->group_mgmt_cipher & + WPA_CIPHER_BIP_CMAC_256) + ie.mgmt_group_cipher = + WPA_CIPHER_BIP_CMAC_256; + else if (ssid->group_mgmt_cipher & + WPA_CIPHER_BIP_GMAC_128) + ie.mgmt_group_cipher = + WPA_CIPHER_BIP_GMAC_128; + else + ie.mgmt_group_cipher = + WPA_CIPHER_AES_128_CMAC; + } +#ifdef CONFIG_OWE + if ((ssid->key_mgmt & WPA_KEY_MGMT_OWE) && + !ssid->owe_only && + !bss_wpa && !bss_rsn && !bss_osen) { + wpa_supplicant_set_non_wpa_policy(wpa_s, ssid); + wpa_s->wpa_proto = 0; + *wpa_ie_len = 0; + return 0; + } +#endif /* CONFIG_OWE */ + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Set cipher suites " + "based on configuration"); + } else + proto = ie.proto; + } + + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Selected cipher suites: group %d " + "pairwise %d key_mgmt %d proto %d", + ie.group_cipher, ie.pairwise_cipher, ie.key_mgmt, proto); + if (ssid->ieee80211w) { + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Selected mgmt group cipher %d", + ie.mgmt_group_cipher); + } + + wpa_s->wpa_proto = proto; + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_PROTO, proto); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_RSN_ENABLED, + !!(ssid->proto & (WPA_PROTO_RSN | WPA_PROTO_OSEN))); + + if (bss || !wpa_s->ap_ies_from_associnfo) { + if (wpa_sm_set_ap_wpa_ie(wpa_s->wpa, bss_wpa, + bss_wpa ? 2 + bss_wpa[1] : 0) || + wpa_sm_set_ap_rsn_ie(wpa_s->wpa, bss_rsn, + bss_rsn ? 2 + bss_rsn[1] : 0) || + wpa_sm_set_ap_rsnxe(wpa_s->wpa, bss_rsnx, + bss_rsnx ? 2 + bss_rsnx[1] : 0)) + return -1; + } + +#ifdef CONFIG_NO_WPA + wpa_s->group_cipher = WPA_CIPHER_NONE; + wpa_s->pairwise_cipher = WPA_CIPHER_NONE; +#else /* CONFIG_NO_WPA */ + sel = ie.group_cipher & ssid->group_cipher; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: AP group 0x%x network profile group 0x%x; available group 0x%x", + ie.group_cipher, ssid->group_cipher, sel); + wpa_s->group_cipher = wpa_pick_group_cipher(sel); + if (wpa_s->group_cipher < 0) { + wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to select group " + "cipher"); + return -1; + } + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using GTK %s", + wpa_cipher_txt(wpa_s->group_cipher)); + + sel = ie.pairwise_cipher & ssid->pairwise_cipher; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: AP pairwise 0x%x network profile pairwise 0x%x; available pairwise 0x%x", + ie.pairwise_cipher, ssid->pairwise_cipher, sel); + wpa_s->pairwise_cipher = wpa_pick_pairwise_cipher(sel, 1); + if (wpa_s->pairwise_cipher < 0) { + wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to select pairwise " + "cipher"); + return -1; + } + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using PTK %s", + wpa_cipher_txt(wpa_s->pairwise_cipher)); +#endif /* CONFIG_NO_WPA */ + + sel = ie.key_mgmt & ssid->key_mgmt; +#ifdef CONFIG_SAE + if ((!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE) && + !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA)) || + wpas_is_sae_avoided(wpa_s, ssid, &ie)) + sel &= ~(WPA_KEY_MGMT_SAE | WPA_KEY_MGMT_SAE_EXT_KEY | + WPA_KEY_MGMT_FT_SAE | WPA_KEY_MGMT_FT_SAE_EXT_KEY); +#endif /* CONFIG_SAE */ +#ifdef CONFIG_IEEE80211R + if (!(wpa_s->drv_flags & (WPA_DRIVER_FLAGS_SME | + WPA_DRIVER_FLAGS_UPDATE_FT_IES))) + sel &= ~WPA_KEY_MGMT_FT; +#endif /* CONFIG_IEEE80211R */ + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: AP key_mgmt 0x%x network profile key_mgmt 0x%x; available key_mgmt 0x%x", + ie.key_mgmt, ssid->key_mgmt, sel); + if (0) { +#ifdef CONFIG_IEEE80211R +#ifdef CONFIG_SHA384 + } else if ((sel & WPA_KEY_MGMT_FT_IEEE8021X_SHA384) && + os_strcmp(wpa_supplicant_get_eap_mode(wpa_s), "LEAP") != 0) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X_SHA384; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using KEY_MGMT FT/802.1X-SHA384"); + if (!ssid->ft_eap_pmksa_caching && + pmksa_cache_get_current(wpa_s->wpa)) { + /* PMKSA caching with FT may have interoperability + * issues, so disable that case by default for now. */ + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: Disable PMKSA caching for FT/802.1X connection"); + pmksa_cache_clear_current(wpa_s->wpa); + } +#endif /* CONFIG_SHA384 */ +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_SUITEB192 + } else if (sel & WPA_KEY_MGMT_IEEE8021X_SUITE_B_192) { + wpa_s->key_mgmt = WPA_KEY_MGMT_IEEE8021X_SUITE_B_192; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using KEY_MGMT 802.1X with Suite B (192-bit)"); +#endif /* CONFIG_SUITEB192 */ +#ifdef CONFIG_SUITEB + } else if (sel & WPA_KEY_MGMT_IEEE8021X_SUITE_B) { + wpa_s->key_mgmt = WPA_KEY_MGMT_IEEE8021X_SUITE_B; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using KEY_MGMT 802.1X with Suite B"); +#endif /* CONFIG_SUITEB */ +#ifdef CONFIG_SHA384 + } else if (sel & WPA_KEY_MGMT_IEEE8021X_SHA384) { + wpa_s->key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA384; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using KEY_MGMT 802.1X with SHA384"); +#endif /* CONFIG_SHA384 */ +#ifdef CONFIG_FILS +#ifdef CONFIG_IEEE80211R + } else if (sel & WPA_KEY_MGMT_FT_FILS_SHA384) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA384; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT FT-FILS-SHA384"); +#endif /* CONFIG_IEEE80211R */ + } else if (sel & WPA_KEY_MGMT_FILS_SHA384) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FILS_SHA384; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT FILS-SHA384"); +#ifdef CONFIG_IEEE80211R + } else if (sel & WPA_KEY_MGMT_FT_FILS_SHA256) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA256; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT FT-FILS-SHA256"); +#endif /* CONFIG_IEEE80211R */ + } else if (sel & WPA_KEY_MGMT_FILS_SHA256) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FILS_SHA256; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT FILS-SHA256"); +#endif /* CONFIG_FILS */ +#ifdef CONFIG_IEEE80211R + } else if ((sel & WPA_KEY_MGMT_FT_IEEE8021X) && + os_strcmp(wpa_supplicant_get_eap_mode(wpa_s), "LEAP") != 0) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT FT/802.1X"); + if (!ssid->ft_eap_pmksa_caching && + pmksa_cache_get_current(wpa_s->wpa)) { + /* PMKSA caching with FT may have interoperability + * issues, so disable that case by default for now. */ + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: Disable PMKSA caching for FT/802.1X connection"); + pmksa_cache_clear_current(wpa_s->wpa); + } +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_DPP + } else if (sel & WPA_KEY_MGMT_DPP) { + wpa_s->key_mgmt = WPA_KEY_MGMT_DPP; + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: using KEY_MGMT DPP"); +#endif /* CONFIG_DPP */ +#ifdef CONFIG_SAE + } else if (sel & WPA_KEY_MGMT_FT_SAE_EXT_KEY) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FT_SAE_EXT_KEY; + wpa_dbg(wpa_s, MSG_DEBUG, + "RSN: using KEY_MGMT FT/SAE (ext key)"); + } else if (sel & WPA_KEY_MGMT_SAE_EXT_KEY) { + wpa_s->key_mgmt = WPA_KEY_MGMT_SAE_EXT_KEY; + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: using KEY_MGMT SAE (ext key)"); + } else if (sel & WPA_KEY_MGMT_FT_SAE) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FT_SAE; + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: using KEY_MGMT FT/SAE"); + } else if (sel & WPA_KEY_MGMT_SAE) { + wpa_s->key_mgmt = WPA_KEY_MGMT_SAE; + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: using KEY_MGMT SAE"); +#endif /* CONFIG_SAE */ +#ifdef CONFIG_IEEE80211R + } else if (sel & WPA_KEY_MGMT_FT_PSK) { + wpa_s->key_mgmt = WPA_KEY_MGMT_FT_PSK; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT FT/PSK"); +#endif /* CONFIG_IEEE80211R */ + } else if (sel & WPA_KEY_MGMT_IEEE8021X_SHA256) { + wpa_s->key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA256; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using KEY_MGMT 802.1X with SHA256"); + } else if (sel & WPA_KEY_MGMT_PSK_SHA256) { + wpa_s->key_mgmt = WPA_KEY_MGMT_PSK_SHA256; + wpa_dbg(wpa_s, MSG_DEBUG, + "WPA: using KEY_MGMT PSK with SHA256"); + } else if (sel & WPA_KEY_MGMT_IEEE8021X) { + wpa_s->key_mgmt = WPA_KEY_MGMT_IEEE8021X; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT 802.1X"); + } else if (sel & WPA_KEY_MGMT_PSK) { + wpa_s->key_mgmt = WPA_KEY_MGMT_PSK; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT WPA-PSK"); + } else if (sel & WPA_KEY_MGMT_WPA_NONE) { + wpa_s->key_mgmt = WPA_KEY_MGMT_WPA_NONE; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT WPA-NONE"); +#ifdef CONFIG_HS20 + } else if (sel & WPA_KEY_MGMT_OSEN) { + wpa_s->key_mgmt = WPA_KEY_MGMT_OSEN; + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: using KEY_MGMT OSEN"); +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_OWE + } else if (sel & WPA_KEY_MGMT_OWE) { + wpa_s->key_mgmt = WPA_KEY_MGMT_OWE; + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: using KEY_MGMT OWE"); +#endif /* CONFIG_OWE */ + } else { + wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to select " + "authenticated key management type"); + return -1; + } + + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_KEY_MGMT, wpa_s->key_mgmt); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_PAIRWISE, + wpa_s->pairwise_cipher); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_GROUP, wpa_s->group_cipher); + + if (!(ie.capabilities & WPA_CAPABILITY_MFPC) && + (wpas_get_ssid_pmf(wpa_s, ssid) == MGMT_FRAME_PROTECTION_REQUIRED || + (bss && is_6ghz_freq(bss->freq)))) { + wpa_msg(wpa_s, MSG_INFO, + "RSN: Management frame protection required but the selected AP does not enable it"); + return -1; + } + + wpas_set_mgmt_group_cipher(wpa_s, ssid, &ie); +#ifdef CONFIG_OCV + if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) || + (wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_OCV)) + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_OCV, ssid->ocv); +#endif /* CONFIG_OCV */ + sae_pwe = wpa_s->conf->sae_pwe; + if ((ssid->sae_password_id || + wpa_key_mgmt_sae_ext_key(wpa_s->key_mgmt)) && + sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK) + sae_pwe = SAE_PWE_HASH_TO_ELEMENT; + if (bss && is_6ghz_freq(bss->freq) && + sae_pwe == SAE_PWE_HUNT_AND_PECK) { + wpa_dbg(wpa_s, MSG_DEBUG, + "RSN: Enable SAE hash-to-element mode for 6 GHz BSS"); + sae_pwe = SAE_PWE_BOTH; + } + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_SAE_PWE, sae_pwe); +#ifdef CONFIG_SAE_PK + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_SAE_PK, + wpa_key_mgmt_sae(ssid->key_mgmt) && + ssid->sae_pk != SAE_PK_MODE_DISABLED && + ((ssid->sae_password && + sae_pk_valid_password(ssid->sae_password)) || + (!ssid->sae_password && ssid->passphrase && + sae_pk_valid_password(ssid->passphrase)))); +#endif /* CONFIG_SAE_PK */ + if (bss && is_6ghz_freq(bss->freq) && + wpas_get_ssid_pmf(wpa_s, ssid) != MGMT_FRAME_PROTECTION_REQUIRED) { + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: Force MFPR=1 on 6 GHz"); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_MFP, + MGMT_FRAME_PROTECTION_REQUIRED); + } +#ifdef CONFIG_TESTING_OPTIONS + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_FT_RSNXE_USED, + wpa_s->ft_rsnxe_used); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_OCI_FREQ_EAPOL, + wpa_s->oci_freq_override_eapol); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_OCI_FREQ_EAPOL_G2, + wpa_s->oci_freq_override_eapol_g2); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_OCI_FREQ_FT_ASSOC, + wpa_s->oci_freq_override_ft_assoc); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_OCI_FREQ_FILS_ASSOC, + wpa_s->oci_freq_override_fils_assoc); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_DISABLE_EAPOL_G2_TX, + wpa_s->disable_eapol_g2_tx); +#endif /* CONFIG_TESTING_OPTIONS */ + + /* Extended Key ID is only supported in infrastructure BSS so far */ + if (ssid->mode == WPAS_MODE_INFRA && wpa_s->conf->extended_key_id && + (ssid->proto & WPA_PROTO_RSN) && + ssid->pairwise_cipher & (WPA_CIPHER_CCMP | WPA_CIPHER_CCMP_256 | + WPA_CIPHER_GCMP | WPA_CIPHER_GCMP_256) && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_EXTENDED_KEY_ID)) { + int use_ext_key_id = 0; + + wpa_msg(wpa_s, MSG_DEBUG, + "WPA: Enable Extended Key ID support"); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_EXT_KEY_ID, + wpa_s->conf->extended_key_id); + if (bss_rsn && + wpa_s->conf->extended_key_id && + wpa_s->pairwise_cipher != WPA_CIPHER_TKIP && + (ie.capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) + use_ext_key_id = 1; + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_USE_EXT_KEY_ID, + use_ext_key_id); + } else { + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_EXT_KEY_ID, 0); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_USE_EXT_KEY_ID, 0); + } + + /* Mark WMM enabled for any HT/VHT/HE/EHT association to get more + * appropriate advertisement of the supported number of PTKSA receive + * counters. In theory, this could be based on a driver capability, but + * in practice all cases using WMM support at least eight replay + * counters, so use a hardcoded value for now since there is no explicit + * driver capability indication for this. + * + * In addition, claim WMM to be enabled if the AP supports it since it + * is far more likely for any current device to support WMM. */ + wmm = wpa_s->connection_set && + (wpa_s->connection_ht || wpa_s->connection_vht || + wpa_s->connection_he || wpa_s->connection_eht); + if (!wmm && bss) + wmm = wpa_bss_get_vendor_ie(bss, WMM_IE_VENDOR_TYPE); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_WMM_ENABLED, wmm); + + if (!skip_default_rsne) { + if (wpa_sm_set_assoc_wpa_ie_default(wpa_s->wpa, wpa_ie, + wpa_ie_len)) { + wpa_msg(wpa_s, MSG_WARNING, + "RSN: Failed to generate RSNE/WPA IE"); + return -1; + } + + wpa_s->rsnxe_len = sizeof(wpa_s->rsnxe); + if (wpa_sm_set_assoc_rsnxe_default(wpa_s->wpa, wpa_s->rsnxe, + &wpa_s->rsnxe_len)) { + wpa_msg(wpa_s, MSG_WARNING, + "RSN: Failed to generate RSNXE"); + return -1; + } + } + + if (0) { +#ifdef CONFIG_DPP + } else if (wpa_s->key_mgmt == WPA_KEY_MGMT_DPP) { + /* Use PMK from DPP network introduction (PMKSA entry) */ + wpa_sm_set_pmk_from_pmksa(wpa_s->wpa); +#ifdef CONFIG_DPP2 + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_DPP_PFS, ssid->dpp_pfs); +#endif /* CONFIG_DPP2 */ +#endif /* CONFIG_DPP */ + } else if (wpa_key_mgmt_wpa_psk(ssid->key_mgmt)) { + int psk_set = 0; + + if (wpa_key_mgmt_wpa_psk_no_sae(ssid->key_mgmt)) { + u8 psk[PMK_LEN]; + + if (wpa_supplicant_get_psk(wpa_s, bss, ssid, + psk) == 0) { + wpa_sm_set_pmk(wpa_s->wpa, psk, PMK_LEN, NULL, + NULL); + psk_set = 1; + } + forced_memzero(psk, sizeof(psk)); + } + + if (wpa_key_mgmt_sae(ssid->key_mgmt) && + (ssid->sae_password || ssid->passphrase || ssid->ext_psk)) + psk_set = 1; + + if (!psk_set) { + wpa_msg(wpa_s, MSG_INFO, + "No PSK available for association"); + wpas_auth_failed(wpa_s, "NO_PSK_AVAILABLE", NULL); + return -1; + } +#ifdef CONFIG_OWE + } else if (wpa_s->key_mgmt == WPA_KEY_MGMT_OWE) { + /* OWE Diffie-Hellman exchange in (Re)Association + * Request/Response frames set the PMK, so do not override it + * here. */ +#endif /* CONFIG_OWE */ + } else + wpa_sm_set_pmk_from_pmksa(wpa_s->wpa); + + if (ssid->mode != WPAS_MODE_IBSS && + !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_WIRED) && + (ssid->wpa_deny_ptk0_rekey == PTK0_REKEY_ALLOW_NEVER || + (ssid->wpa_deny_ptk0_rekey == PTK0_REKEY_ALLOW_LOCAL_OK && + !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAFE_PTK0_REKEYS)))) { + wpa_msg(wpa_s, MSG_INFO, + "Disable PTK0 rekey support - replaced with reconnect"); + wpa_s->deny_ptk0_rekey = 1; + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_DENY_PTK0_REKEY, 1); + } else { + wpa_s->deny_ptk0_rekey = 0; + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_DENY_PTK0_REKEY, 0); + } + +#if defined(CONFIG_DRIVER_NL80211_BRCM) || defined(CONFIG_DRIVER_NL80211_SYNA) + if ((wpa_s->key_mgmt & WPA_KEY_MGMT_CROSS_AKM_ROAM) && + IS_CROSS_AKM_ROAM_KEY_MGMT(ssid->key_mgmt) && + (wpa_s->group_cipher == WPA_CIPHER_CCMP) && + (wpa_s->pairwise_cipher == WPA_CIPHER_CCMP) && + (wpa_s->wpa_proto == WPA_PROTO_RSN)) { + wpa_s->key_mgmt = WPA_KEY_MGMT_SAE | WPA_KEY_MGMT_PSK; + wpa_dbg(wpa_s, MSG_INFO, + "WPA: Updating to KEY_MGMT SAE+PSK for seamless roaming"); + } +#else + if (wpa_key_mgmt_cross_akm(wpa_s->key_mgmt) && + !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)) + wpas_update_allowed_key_mgmt(wpa_s, ssid); +#endif /* CONFIG_DRIVER_NL80211_BRCM || CONFIG_DRIVER_NL80211_SYNA */ + + return 0; +} + + +static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx, + struct wpa_bss *bss) +{ + bool scs = true, mscs = true; + + *pos = 0x00; + + switch (idx) { + case 0: /* Bits 0-7 */ + break; + case 1: /* Bits 8-15 */ + if (wpa_s->conf->coloc_intf_reporting) { + /* Bit 13 - Collocated Interference Reporting */ + *pos |= 0x20; + } + break; + case 2: /* Bits 16-23 */ +#ifdef CONFIG_WNM + *pos |= 0x02; /* Bit 17 - WNM-Sleep Mode */ + if (!wpa_s->disable_mbo_oce && !wpa_s->conf->disable_btm) + *pos |= 0x08; /* Bit 19 - BSS Transition */ +#endif /* CONFIG_WNM */ + break; + case 3: /* Bits 24-31 */ +#ifdef CONFIG_WNM + *pos |= 0x02; /* Bit 25 - SSID List */ +#endif /* CONFIG_WNM */ +#ifdef CONFIG_INTERWORKING + if (wpa_s->conf->interworking) + *pos |= 0x80; /* Bit 31 - Interworking */ +#endif /* CONFIG_INTERWORKING */ + break; + case 4: /* Bits 32-39 */ +#ifdef CONFIG_INTERWORKING + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_QOS_MAPPING) + *pos |= 0x01; /* Bit 32 - QoS Map */ +#endif /* CONFIG_INTERWORKING */ + break; + case 5: /* Bits 40-47 */ +#ifdef CONFIG_HS20 + if (wpa_s->conf->hs20) + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_MBO + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_MBO */ + break; + case 6: /* Bits 48-55 */ +#ifdef CONFIG_TESTING_OPTIONS + if (wpa_s->disable_scs_support) + scs = false; +#endif /* CONFIG_TESTING_OPTIONS */ + if (bss && !wpa_bss_ext_capab(bss, WLAN_EXT_CAPAB_SCS)) { + /* Drop own SCS capability indication since the AP does + * not support it. This is needed to avoid + * interoperability issues with APs that get confused + * with Extended Capabilities element. */ + scs = false; + } + if (scs) + *pos |= 0x40; /* Bit 54 - SCS */ + break; + case 7: /* Bits 56-63 */ + break; + case 8: /* Bits 64-71 */ + if (wpa_s->conf->ftm_responder) + *pos |= 0x40; /* Bit 70 - FTM responder */ + if (wpa_s->conf->ftm_initiator) + *pos |= 0x80; /* Bit 71 - FTM initiator */ + break; + case 9: /* Bits 72-79 */ +#ifdef CONFIG_FILS + if (!wpa_s->disable_fils) + *pos |= 0x01; +#endif /* CONFIG_FILS */ + break; + case 10: /* Bits 80-87 */ +#ifdef CONFIG_TESTING_OPTIONS + if (wpa_s->disable_mscs_support) + mscs = false; +#endif /* CONFIG_TESTING_OPTIONS */ + if (bss && !wpa_bss_ext_capab(bss, WLAN_EXT_CAPAB_MSCS)) { + /* Drop own MSCS capability indication since the AP does + * not support it. This is needed to avoid + * interoperability issues with APs that get confused + * with Extended Capabilities element. */ + mscs = false; + } + if (mscs) + *pos |= 0x20; /* Bit 85 - Mirrored SCS */ + break; + } +} + + +int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf, + size_t buflen, struct wpa_bss *bss) +{ + u8 *pos = buf; + u8 len = 11, i; + + if (len < wpa_s->extended_capa_len) + len = wpa_s->extended_capa_len; + if (buflen < (size_t) len + 2) { + wpa_printf(MSG_INFO, + "Not enough room for building extended capabilities element"); + return -1; + } + + *pos++ = WLAN_EID_EXT_CAPAB; + *pos++ = len; + for (i = 0; i < len; i++, pos++) { + wpas_ext_capab_byte(wpa_s, pos, i, bss); + + if (i < wpa_s->extended_capa_len) { + *pos &= ~wpa_s->extended_capa_mask[i]; + *pos |= wpa_s->extended_capa[i]; + } + } + + while (len > 0 && buf[1 + len] == 0) { + len--; + buf[1] = len; + } + if (len == 0) + return 0; + + return 2 + len; +} + + +static int wpas_valid_bss(struct wpa_supplicant *wpa_s, + struct wpa_bss *test_bss) +{ + struct wpa_bss *bss; + + dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { + if (bss == test_bss) + return 1; + } + + return 0; +} + + +static int wpas_valid_ssid(struct wpa_supplicant *wpa_s, + struct wpa_ssid *test_ssid) +{ + struct wpa_ssid *ssid; + + for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) { + if (ssid == test_ssid) + return 1; + } + + return 0; +} + + +int wpas_valid_bss_ssid(struct wpa_supplicant *wpa_s, struct wpa_bss *test_bss, + struct wpa_ssid *test_ssid) +{ + if (test_bss && !wpas_valid_bss(wpa_s, test_bss)) + return 0; + + return test_ssid == NULL || wpas_valid_ssid(wpa_s, test_ssid); +} + + +void wpas_connect_work_free(struct wpa_connect_work *cwork) +{ + if (cwork == NULL) + return; + os_free(cwork); +} + + +void wpas_connect_work_done(struct wpa_supplicant *wpa_s) +{ + struct wpa_connect_work *cwork; + struct wpa_radio_work *work = wpa_s->connect_work; + + if (!work) + return; + + wpa_s->connect_work = NULL; + cwork = work->ctx; + work->ctx = NULL; + wpas_connect_work_free(cwork); + radio_work_done(work); +} + + +int wpas_update_random_addr(struct wpa_supplicant *wpa_s, + enum wpas_mac_addr_style style, + struct wpa_ssid *ssid) +{ + struct os_reltime now; + u8 addr[ETH_ALEN]; + + os_get_reltime(&now); + /* Random addresses are valid within a given ESS so check + * expiration/value only when continuing to use the same ESS. */ + if (wpa_s->last_mac_addr_style == style && wpa_s->reassoc_same_ess) { + if (style == WPAS_MAC_ADDR_STYLE_DEDICATED_PER_ESS) { + /* Pregenerated addresses do not expire but their value + * might have changed, so let's check that. */ + if (os_memcmp(wpa_s->own_addr, ssid->mac_value, + ETH_ALEN) == 0) + return 0; + } else if ((wpa_s->last_mac_addr_change.sec != 0 || + wpa_s->last_mac_addr_change.usec != 0) && + !os_reltime_expired( + &now, + &wpa_s->last_mac_addr_change, + wpa_s->conf->rand_addr_lifetime)) { + wpa_msg(wpa_s, MSG_DEBUG, + "Previously selected random MAC address has not yet expired"); + return 0; + } + } + + switch (style) { + case WPAS_MAC_ADDR_STYLE_RANDOM: + if (random_mac_addr(addr) < 0) + return -1; + break; + case WPAS_MAC_ADDR_STYLE_RANDOM_SAME_OUI: + os_memcpy(addr, wpa_s->perm_addr, ETH_ALEN); + if (random_mac_addr_keep_oui(addr) < 0) + return -1; + break; + case WPAS_MAC_ADDR_STYLE_DEDICATED_PER_ESS: + if (!ssid) { + wpa_msg(wpa_s, MSG_INFO, + "Invalid 'ssid' for address policy 3"); + return -1; + } + os_memcpy(addr, ssid->mac_value, ETH_ALEN); + break; + default: + return -1; + } + + if (wpa_drv_set_mac_addr(wpa_s, addr) < 0) { + wpa_msg(wpa_s, MSG_INFO, + "Failed to set random MAC address"); + return -1; + } + + os_get_reltime(&wpa_s->last_mac_addr_change); + wpa_s->mac_addr_changed = 1; + wpa_s->last_mac_addr_style = style; + + if (wpa_supplicant_update_mac_addr(wpa_s) < 0) { + wpa_msg(wpa_s, MSG_INFO, + "Could not update MAC address information"); + return -1; + } + + wpa_msg(wpa_s, MSG_DEBUG, "Using random MAC address " MACSTR, + MAC2STR(addr)); + + return 1; +} + + +int wpas_update_random_addr_disassoc(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->wpa_state >= WPA_AUTHENTICATING || + !wpa_s->conf->preassoc_mac_addr) + return 0; + + return wpas_update_random_addr(wpa_s, wpa_s->conf->preassoc_mac_addr, + NULL); +} + + +void wpa_s_setup_sae_pt(struct wpa_config *conf, struct wpa_ssid *ssid, + bool force) +{ +#ifdef CONFIG_SAE + int *groups = conf->sae_groups; + int default_groups[] = { 19, 20, 21, 0 }; + const char *password; + + if (!groups || groups[0] <= 0) + groups = default_groups; + + password = ssid->sae_password; + if (!password) + password = ssid->passphrase; + + if (!password || + (conf->sae_pwe == SAE_PWE_HUNT_AND_PECK && !ssid->sae_password_id && + !wpa_key_mgmt_sae_ext_key(ssid->key_mgmt) && + !force && + !sae_pk_valid_password(password)) || + conf->sae_pwe == SAE_PWE_FORCE_HUNT_AND_PECK) { + /* PT derivation not needed */ + sae_deinit_pt(ssid->pt); + ssid->pt = NULL; + return; + } + + if (ssid->pt) + return; /* PT already derived */ + ssid->pt = sae_derive_pt(groups, ssid->ssid, ssid->ssid_len, + (const u8 *) password, os_strlen(password), + ssid->sae_password_id); +#endif /* CONFIG_SAE */ +} + + +static void wpa_s_clear_sae_rejected(struct wpa_supplicant *wpa_s) +{ +#if defined(CONFIG_SAE) && defined(CONFIG_SME) + os_free(wpa_s->sme.sae_rejected_groups); + wpa_s->sme.sae_rejected_groups = NULL; +#ifdef CONFIG_TESTING_OPTIONS + if (wpa_s->extra_sae_rejected_groups) { + int i, *groups = wpa_s->extra_sae_rejected_groups; + + for (i = 0; groups[i]; i++) { + wpa_printf(MSG_DEBUG, + "TESTING: Indicate rejection of an extra SAE group %d", + groups[i]); + int_array_add_unique(&wpa_s->sme.sae_rejected_groups, + groups[i]); + } + } +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_SAE && CONFIG_SME */ +} + + +int wpas_restore_permanent_mac_addr(struct wpa_supplicant *wpa_s) +{ + if (wpa_drv_set_mac_addr(wpa_s, NULL) < 0) { + wpa_msg(wpa_s, MSG_INFO, + "Could not restore permanent MAC address"); + return -1; + } + wpa_s->mac_addr_changed = 0; + if (wpa_supplicant_update_mac_addr(wpa_s) < 0) { + wpa_msg(wpa_s, MSG_INFO, + "Could not update MAC address information"); + return -1; + } + wpa_msg(wpa_s, MSG_DEBUG, "Using permanent MAC address"); + return 0; +} + + +static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit); + +/** + * wpa_supplicant_associate - Request association + * @wpa_s: Pointer to wpa_supplicant data + * @bss: Scan results for the selected BSS, or %NULL if not available + * @ssid: Configuration data for the selected network + * + * This function is used to request %wpa_supplicant to associate with a BSS. + */ +void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, struct wpa_ssid *ssid) +{ + struct wpa_connect_work *cwork; + enum wpas_mac_addr_style rand_style; + + wpa_s->own_disconnect_req = 0; + wpa_s->own_reconnect_req = 0; + + /* + * If we are starting a new connection, any previously pending EAPOL + * RX cannot be valid anymore. + */ + wpabuf_free(wpa_s->pending_eapol_rx); + wpa_s->pending_eapol_rx = NULL; + + if (ssid->mac_addr == WPAS_MAC_ADDR_STYLE_NOT_SET) + rand_style = wpa_s->conf->mac_addr; + else + rand_style = ssid->mac_addr; + + wpa_s->eapol_failed = 0; + wpa_s->multi_ap_ie = 0; + wmm_ac_clear_saved_tspecs(wpa_s); + wpa_s->reassoc_same_bss = 0; + wpa_s->reassoc_same_ess = 0; +#ifdef CONFIG_TESTING_OPTIONS + wpa_s->testing_resend_assoc = 0; +#endif /* CONFIG_TESTING_OPTIONS */ + + if (wpa_s->last_ssid == ssid) { + wpa_dbg(wpa_s, MSG_DEBUG, "Re-association to the same ESS"); + wpa_s->reassoc_same_ess = 1; + if (wpa_s->current_bss && wpa_s->current_bss == bss) { + wmm_ac_save_tspecs(wpa_s); + wpa_s->reassoc_same_bss = 1; + } else if (wpa_s->current_bss && wpa_s->current_bss != bss) { + os_get_reltime(&wpa_s->roam_start); + } + } else { +#ifdef CONFIG_SAE + wpa_s_clear_sae_rejected(wpa_s); +#endif /* CONFIG_SAE */ + } +#ifdef CONFIG_SAE + wpa_s_setup_sae_pt(wpa_s->conf, ssid, false); +#endif /* CONFIG_SAE */ + + if (rand_style > WPAS_MAC_ADDR_STYLE_PERMANENT) { + int status = wpas_update_random_addr(wpa_s, rand_style, ssid); + + if (status < 0) + return; + if (rand_style != WPAS_MAC_ADDR_STYLE_DEDICATED_PER_ESS && + status > 0) /* MAC changed */ + wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid); + } else if (rand_style == WPAS_MAC_ADDR_STYLE_PERMANENT && + wpa_s->mac_addr_changed) { + if (wpas_restore_permanent_mac_addr(wpa_s) < 0) + return; + } + wpa_s->last_ssid = ssid; + +#ifdef CONFIG_IBSS_RSN + ibss_rsn_deinit(wpa_s->ibss_rsn); + wpa_s->ibss_rsn = NULL; +#else /* CONFIG_IBSS_RSN */ + if (ssid->mode == WPAS_MODE_IBSS && + !(ssid->key_mgmt & (WPA_KEY_MGMT_NONE | WPA_KEY_MGMT_WPA_NONE))) { + wpa_msg(wpa_s, MSG_INFO, + "IBSS RSN not supported in the build"); + return; + } +#endif /* CONFIG_IBSS_RSN */ + + if (ssid->mode == WPAS_MODE_AP || ssid->mode == WPAS_MODE_P2P_GO || + ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) { +#ifdef CONFIG_AP + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_AP)) { + wpa_msg(wpa_s, MSG_INFO, "Driver does not support AP " + "mode"); + return; + } + if (wpa_supplicant_create_ap(wpa_s, ssid) < 0) { + wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); + if (ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) + wpas_p2p_ap_setup_failed(wpa_s); + return; + } + wpa_s->current_bss = bss; +#else /* CONFIG_AP */ + wpa_msg(wpa_s, MSG_ERROR, "AP mode support not included in " + "the build"); +#endif /* CONFIG_AP */ + return; + } + + if (ssid->mode == WPAS_MODE_MESH) { +#ifdef CONFIG_MESH + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_MESH)) { + wpa_msg(wpa_s, MSG_INFO, + "Driver does not support mesh mode"); + return; + } + if (bss) + ssid->frequency = bss->freq; + if (wpa_supplicant_join_mesh(wpa_s, ssid) < 0) { + wpa_supplicant_set_state(wpa_s, WPA_INACTIVE); + wpa_msg(wpa_s, MSG_ERROR, "Could not join mesh"); + return; + } + wpa_s->current_bss = bss; +#else /* CONFIG_MESH */ + wpa_msg(wpa_s, MSG_ERROR, + "mesh mode support not included in the build"); +#endif /* CONFIG_MESH */ + return; + } + + /* + * Set WPA state machine configuration to match the selected network now + * so that the information is available before wpas_start_assoc_cb() + * gets called. This is needed at least for RSN pre-authentication where + * candidate APs are added to a list based on scan result processing + * before completion of the first association. + */ + wpa_supplicant_rsn_supp_set_config(wpa_s, ssid); + +#ifdef CONFIG_DPP + if (wpas_dpp_check_connect(wpa_s, ssid, bss) != 0) + return; +#endif /* CONFIG_DPP */ + +#ifdef CONFIG_TDLS + if (bss) + wpa_tdls_ap_ies(wpa_s->wpa, wpa_bss_ie_ptr(bss), bss->ie_len); +#endif /* CONFIG_TDLS */ + +#ifdef CONFIG_MBO + wpas_mbo_check_pmf(wpa_s, bss, ssid); +#endif /* CONFIG_MBO */ + + if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && + ssid->mode == WPAS_MODE_INFRA) { + sme_authenticate(wpa_s, bss, ssid); + return; + } + + if (wpa_s->connect_work) { + wpa_dbg(wpa_s, MSG_DEBUG, "Reject wpa_supplicant_associate() call since connect_work exist"); + return; + } + + if (radio_work_pending(wpa_s, "connect")) { + wpa_dbg(wpa_s, MSG_DEBUG, "Reject wpa_supplicant_associate() call since pending work exist"); + return; + } + +#ifdef CONFIG_SME + if (ssid->mode == WPAS_MODE_IBSS || ssid->mode == WPAS_MODE_MESH) { + /* Clear possibly set auth_alg, if any, from last attempt. */ + wpa_s->sme.auth_alg = WPA_AUTH_ALG_OPEN; + } +#endif /* CONFIG_SME */ + + wpas_abort_ongoing_scan(wpa_s); + + cwork = os_zalloc(sizeof(*cwork)); + if (cwork == NULL) + return; + + cwork->bss = bss; + cwork->ssid = ssid; + + if (radio_add_work(wpa_s, bss ? bss->freq : 0, "connect", 1, + wpas_start_assoc_cb, cwork) < 0) { + os_free(cwork); + } +} + + +static int bss_is_ibss(struct wpa_bss *bss) +{ + return (bss->caps & (IEEE80211_CAP_ESS | IEEE80211_CAP_IBSS)) == + IEEE80211_CAP_IBSS; +} + + +static int drv_supports_vht(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid) +{ + enum hostapd_hw_mode hw_mode; + struct hostapd_hw_modes *mode = NULL; + u8 channel; + int i; + + hw_mode = ieee80211_freq_to_chan(ssid->frequency, &channel); + if (hw_mode == NUM_HOSTAPD_MODES) + return 0; + for (i = 0; wpa_s->hw.modes && i < wpa_s->hw.num_modes; i++) { + if (wpa_s->hw.modes[i].mode == hw_mode) { + mode = &wpa_s->hw.modes[i]; + break; + } + } + + if (!mode) + return 0; + + return mode->vht_capab != 0; +} + + +static bool ibss_mesh_is_80mhz_avail(int channel, struct hostapd_hw_modes *mode) +{ + int i; + + for (i = channel; i < channel + 16; i += 4) { + struct hostapd_channel_data *chan; + + chan = hw_get_channel_chan(mode, i, NULL); + if (!chan || + chan->flag & (HOSTAPD_CHAN_DISABLED | HOSTAPD_CHAN_NO_IR)) + return false; + } + + return true; +} + + +static struct wpa_bss * ibss_find_existing_bss(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid) +{ + unsigned int j; + + for (j = 0; j < wpa_s->last_scan_res_used; j++) { + struct wpa_bss *bss = wpa_s->last_scan_res[j]; + + if (!bss_is_ibss(bss)) + continue; + + if (ssid->ssid_len == bss->ssid_len && + os_memcmp(ssid->ssid, bss->ssid, bss->ssid_len) == 0) + return bss; + } + return NULL; +} + + +static bool ibss_mesh_can_use_ht(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid, + struct hostapd_hw_modes *mode) +{ + /* For IBSS check HT_IBSS flag */ + if (ssid->mode == WPAS_MODE_IBSS && + !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_HT_IBSS)) + return false; + + if (wpa_s->group_cipher == WPA_CIPHER_WEP40 || + wpa_s->group_cipher == WPA_CIPHER_WEP104 || + wpa_s->pairwise_cipher == WPA_CIPHER_TKIP) { + wpa_printf(MSG_DEBUG, + "IBSS: WEP/TKIP detected, do not try to enable HT"); + return false; + } + + if (!ht_supported(mode)) + return false; + +#ifdef CONFIG_HT_OVERRIDES + if (ssid->disable_ht) + return false; +#endif /* CONFIG_HT_OVERRIDES */ + + return true; +} + + +static bool ibss_mesh_can_use_vht(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid, + struct hostapd_hw_modes *mode) +{ + if (mode->mode != HOSTAPD_MODE_IEEE80211A) + return false; + + if (!drv_supports_vht(wpa_s, ssid)) + return false; + + /* For IBSS check VHT_IBSS flag */ + if (ssid->mode == WPAS_MODE_IBSS && + !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_VHT_IBSS)) + return false; + + if (!vht_supported(mode)) + return false; + +#ifdef CONFIG_VHT_OVERRIDES + if (ssid->disable_vht) + return false; +#endif /* CONFIG_VHT_OVERRIDES */ + + return true; +} + + +static bool ibss_mesh_can_use_he(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid, + const struct hostapd_hw_modes *mode, + int ieee80211_mode) +{ +#ifdef CONFIG_HE_OVERRIDES + if (ssid->disable_he) + return false; +#endif /* CONFIG_HE_OVERRIDES */ + + switch (mode->mode) { + case HOSTAPD_MODE_IEEE80211G: + case HOSTAPD_MODE_IEEE80211B: + case HOSTAPD_MODE_IEEE80211A: + return mode->he_capab[ieee80211_mode].he_supported; + default: + return false; + } +} + + +static bool ibss_mesh_can_use_eht(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid, + const struct hostapd_hw_modes *mode, + int ieee80211_mode) +{ + if (ssid->disable_eht) + return false; + + switch(mode->mode) { + case HOSTAPD_MODE_IEEE80211G: + case HOSTAPD_MODE_IEEE80211B: + case HOSTAPD_MODE_IEEE80211A: + return mode->eht_capab[ieee80211_mode].eht_supported; + default: + return false; + } +} + + +static void ibss_mesh_select_40mhz(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid, + struct hostapd_hw_modes *mode, + struct hostapd_freq_params *freq, + int obss_scan) { + int chan_idx; + struct hostapd_channel_data *pri_chan = NULL, *sec_chan = NULL; + int i, res; + unsigned int j; + static const int ht40plus[] = { + 36, 44, 52, 60, 100, 108, 116, 124, 132, 140, + 149, 157, 165, 173, 184, 192 + }; + int ht40 = -1; + + if (!freq->ht_enabled) + return; + + for (chan_idx = 0; chan_idx < mode->num_channels; chan_idx++) { + pri_chan = &mode->channels[chan_idx]; + if (pri_chan->chan == freq->channel) + break; + pri_chan = NULL; + } + if (!pri_chan) + return; + + /* Check primary channel flags */ + if (pri_chan->flag & (HOSTAPD_CHAN_DISABLED | HOSTAPD_CHAN_NO_IR)) + return; + +#ifdef CONFIG_HT_OVERRIDES + if (ssid->disable_ht40) + return; +#endif + + /* Check/setup HT40+/HT40- */ + for (j = 0; j < ARRAY_SIZE(ht40plus); j++) { + if (ht40plus[j] == freq->channel) { + ht40 = 1; + break; + } + } + + /* Find secondary channel */ + for (i = 0; i < mode->num_channels; i++) { + sec_chan = &mode->channels[i]; + if (sec_chan->chan == freq->channel + ht40 * 4) + break; + sec_chan = NULL; + } + if (!sec_chan) + return; + + /* Check secondary channel flags */ + if (sec_chan->flag & (HOSTAPD_CHAN_DISABLED | HOSTAPD_CHAN_NO_IR)) + return; + + if (ht40 == -1) { + if (!(pri_chan->flag & HOSTAPD_CHAN_HT40MINUS)) + return; + } else { + if (!(pri_chan->flag & HOSTAPD_CHAN_HT40PLUS)) + return; + } + freq->sec_channel_offset = ht40; + + if (obss_scan) { + struct wpa_scan_results *scan_res; + + scan_res = wpa_supplicant_get_scan_results(wpa_s, NULL, 0); + if (scan_res == NULL) { + /* Back to HT20 */ + freq->sec_channel_offset = 0; + return; + } + + res = check_40mhz_5g(scan_res, pri_chan, sec_chan); + switch (res) { + case 0: + /* Back to HT20 */ + freq->sec_channel_offset = 0; + break; + case 1: + /* Configuration allowed */ + break; + case 2: + /* Switch pri/sec channels */ + freq->freq = hw_get_freq(mode, sec_chan->chan); + freq->sec_channel_offset = -freq->sec_channel_offset; + freq->channel = sec_chan->chan; + break; + default: + freq->sec_channel_offset = 0; + break; + } + + wpa_scan_results_free(scan_res); + } + + wpa_printf(MSG_DEBUG, + "IBSS/mesh: setup freq channel %d, sec_channel_offset %d", + freq->channel, freq->sec_channel_offset); +} + + +static bool ibss_mesh_select_80_160mhz(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid, + struct hostapd_hw_modes *mode, + struct hostapd_freq_params *freq, + int ieee80211_mode, bool is_6ghz) { + static const int bw80[] = { + 5180, 5260, 5500, 5580, 5660, 5745, 5825, + 5955, 6035, 6115, 6195, 6275, 6355, 6435, + 6515, 6595, 6675, 6755, 6835, 6915, 6995 + }; + static const int bw160[] = { + 5955, 6115, 6275, 6435, 6595, 6755, 6915 + }; + struct hostapd_freq_params vht_freq; + int i; + unsigned int j, k; + int chwidth, seg0, seg1; + u32 vht_caps = 0; + u8 channel = freq->channel; + + if (!freq->vht_enabled && !freq->he_enabled) + return true; + + vht_freq = *freq; + + chwidth = CONF_OPER_CHWIDTH_USE_HT; + seg0 = freq->channel + 2 * freq->sec_channel_offset; + seg1 = 0; + if (freq->sec_channel_offset == 0) { + seg0 = 0; + /* Don't try 80 MHz if 40 MHz failed, except in 6 GHz */ + if (freq->ht_enabled && !is_6ghz) + goto skip_80mhz; + } + if (ssid->max_oper_chwidth == CONF_OPER_CHWIDTH_USE_HT) + goto skip_80mhz; + + /* setup center_freq1, bandwidth */ + for (j = 0; j < ARRAY_SIZE(bw80); j++) { + if (freq->freq >= bw80[j] && + freq->freq < bw80[j] + 80) + break; + } + + if (j == ARRAY_SIZE(bw80) || + ieee80211_freq_to_chan(bw80[j], &channel) == NUM_HOSTAPD_MODES) + goto skip_80mhz; + + /* Use 40 MHz if channel not usable */ + if (!ibss_mesh_is_80mhz_avail(channel, mode)) + goto skip_80mhz; + + chwidth = CONF_OPER_CHWIDTH_80MHZ; + seg0 = channel + 6; + seg1 = 0; + + /* In 160 MHz, the initial four 20 MHz channels were validated + * above. If 160 MHz is supported, check the remaining four 20 MHz + * channels for the total of 160 MHz bandwidth for 6 GHz. + */ + if ((mode->he_capab[ieee80211_mode].phy_cap[ + HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] & + HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G) && is_6ghz && + ibss_mesh_is_80mhz_avail(channel + 16, mode)) { + for (j = 0; j < ARRAY_SIZE(bw160); j++) { + if (freq->freq == bw160[j]) { + chwidth = CONF_OPER_CHWIDTH_160MHZ; + seg0 = channel + 14; + break; + } + } + } + + if (ssid->max_oper_chwidth == CONF_OPER_CHWIDTH_80P80MHZ) { + /* setup center_freq2, bandwidth */ + for (k = 0; k < ARRAY_SIZE(bw80); k++) { + /* Only accept 80 MHz segments separated by a gap */ + if (j == k || abs(bw80[j] - bw80[k]) == 80) + continue; + + if (ieee80211_freq_to_chan(bw80[k], &channel) == + NUM_HOSTAPD_MODES) + break; + + for (i = channel; i < channel + 16; i += 4) { + struct hostapd_channel_data *chan; + + chan = hw_get_channel_chan(mode, i, NULL); + if (!chan) + continue; + + if (chan->flag & (HOSTAPD_CHAN_DISABLED | + HOSTAPD_CHAN_NO_IR | + HOSTAPD_CHAN_RADAR)) + continue; + + /* Found a suitable second segment for 80+80 */ + chwidth = CONF_OPER_CHWIDTH_80P80MHZ; + if (!is_6ghz) + vht_caps |= + VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; + seg1 = channel + 6; + } + + if (chwidth == CONF_OPER_CHWIDTH_80P80MHZ) + break; + } + } else if (ssid->max_oper_chwidth == CONF_OPER_CHWIDTH_160MHZ) { + if (freq->freq == 5180) { + chwidth = CONF_OPER_CHWIDTH_160MHZ; + vht_caps |= VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; + seg0 = 50; + } else if (freq->freq == 5520) { + chwidth = CONF_OPER_CHWIDTH_160MHZ; + vht_caps |= VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; + seg0 = 114; + } + } + +skip_80mhz: + if (hostapd_set_freq_params(&vht_freq, mode->mode, freq->freq, + freq->channel, ssid->enable_edmg, + ssid->edmg_channel, freq->ht_enabled, + freq->vht_enabled, freq->he_enabled, + freq->eht_enabled, + freq->sec_channel_offset, + chwidth, seg0, seg1, vht_caps, + &mode->he_capab[ieee80211_mode], + &mode->eht_capab[ieee80211_mode]) != 0) + return false; + + *freq = vht_freq; + + wpa_printf(MSG_DEBUG, "IBSS: VHT setup freq cf1 %d, cf2 %d, bw %d", + freq->center_freq1, freq->center_freq2, freq->bandwidth); + return true; +} + + +void ibss_mesh_setup_freq(struct wpa_supplicant *wpa_s, + const struct wpa_ssid *ssid, + struct hostapd_freq_params *freq) +{ + int ieee80211_mode = wpas_mode_to_ieee80211_mode(ssid->mode); + enum hostapd_hw_mode hw_mode; + struct hostapd_hw_modes *mode = NULL; + int i, obss_scan = 1; + u8 channel; + bool is_6ghz; + + freq->freq = ssid->frequency; + + if (ssid->mode == WPAS_MODE_IBSS && !ssid->fixed_freq) { + struct wpa_bss *bss = ibss_find_existing_bss(wpa_s, ssid); + + if (bss) { + wpa_printf(MSG_DEBUG, + "IBSS already found in scan results, adjust control freq: %d", + bss->freq); + freq->freq = bss->freq; + obss_scan = 0; + } + } + + hw_mode = ieee80211_freq_to_chan(freq->freq, &channel); + for (i = 0; wpa_s->hw.modes && i < wpa_s->hw.num_modes; i++) { + if (wpa_s->hw.modes[i].mode == hw_mode && + hw_mode_get_channel(&wpa_s->hw.modes[i], freq->freq, + NULL) != NULL) { + mode = &wpa_s->hw.modes[i]; + break; + } + } + + if (!mode) + return; + + is_6ghz = is_6ghz_freq(freq->freq); + + freq->ht_enabled = 0; + freq->vht_enabled = 0; + freq->he_enabled = 0; + freq->eht_enabled = 0; + + if (!is_6ghz) + freq->ht_enabled = ibss_mesh_can_use_ht(wpa_s, ssid, mode); + if (freq->ht_enabled) + freq->vht_enabled = ibss_mesh_can_use_vht(wpa_s, ssid, mode); + if (freq->vht_enabled || is_6ghz) + freq->he_enabled = ibss_mesh_can_use_he(wpa_s, ssid, mode, + ieee80211_mode); + freq->channel = channel; + /* Setup higher BW only for 5 GHz */ + if (mode->mode == HOSTAPD_MODE_IEEE80211A) { + ibss_mesh_select_40mhz(wpa_s, ssid, mode, freq, obss_scan); + if (!ibss_mesh_select_80_160mhz(wpa_s, ssid, mode, freq, + ieee80211_mode, is_6ghz)) + freq->he_enabled = freq->vht_enabled = false; + } + + if (freq->he_enabled) + freq->eht_enabled = ibss_mesh_can_use_eht(wpa_s, ssid, mode, + ieee80211_mode); +} + + +#ifdef CONFIG_FILS +static size_t wpas_add_fils_hlp_req(struct wpa_supplicant *wpa_s, u8 *ie_buf, + size_t ie_buf_len) +{ + struct fils_hlp_req *req; + size_t rem_len, hdr_len, hlp_len, len, ie_len = 0; + const u8 *pos; + u8 *buf = ie_buf; + + dl_list_for_each(req, &wpa_s->fils_hlp_req, struct fils_hlp_req, + list) { + rem_len = ie_buf_len - ie_len; + pos = wpabuf_head(req->pkt); + hdr_len = 1 + 2 * ETH_ALEN + 6; + hlp_len = wpabuf_len(req->pkt); + + if (rem_len < 2 + hdr_len + hlp_len) { + wpa_printf(MSG_ERROR, + "FILS: Cannot fit HLP - rem_len=%lu to_fill=%lu", + (unsigned long) rem_len, + (unsigned long) (2 + hdr_len + hlp_len)); + break; + } + + len = (hdr_len + hlp_len) > 255 ? 255 : hdr_len + hlp_len; + /* Element ID */ + *buf++ = WLAN_EID_EXTENSION; + /* Length */ + *buf++ = len; + /* Element ID Extension */ + *buf++ = WLAN_EID_EXT_FILS_HLP_CONTAINER; + /* Destination MAC address */ + os_memcpy(buf, req->dst, ETH_ALEN); + buf += ETH_ALEN; + /* Source MAC address */ + os_memcpy(buf, wpa_s->own_addr, ETH_ALEN); + buf += ETH_ALEN; + /* LLC/SNAP Header */ + os_memcpy(buf, "\xaa\xaa\x03\x00\x00\x00", 6); + buf += 6; + /* HLP Packet */ + os_memcpy(buf, pos, len - hdr_len); + buf += len - hdr_len; + pos += len - hdr_len; + + hlp_len -= len - hdr_len; + ie_len += 2 + len; + rem_len -= 2 + len; + + while (hlp_len) { + len = (hlp_len > 255) ? 255 : hlp_len; + if (rem_len < 2 + len) + break; + *buf++ = WLAN_EID_FRAGMENT; + *buf++ = len; + os_memcpy(buf, pos, len); + buf += len; + pos += len; + + hlp_len -= len; + ie_len += 2 + len; + rem_len -= 2 + len; + } + } + + return ie_len; +} + + +int wpa_is_fils_supported(struct wpa_supplicant *wpa_s) +{ + return (((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SUPPORT_FILS)) || + (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_FILS_SK_OFFLOAD))); +} + + +int wpa_is_fils_sk_pfs_supported(struct wpa_supplicant *wpa_s) +{ +#ifdef CONFIG_FILS_SK_PFS + return (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SUPPORT_FILS); +#else /* CONFIG_FILS_SK_PFS */ + return 0; +#endif /* CONFIG_FILS_SK_PFS */ +} + +#endif /* CONFIG_FILS */ + + +bool wpa_is_non_eht_scs_traffic_desc_supported(struct wpa_bss *bss) +{ + const u8 *wfa_capa; + + if (!bss) + return false; + + /* Get WFA capability from Beacon or Probe Response frame elements */ + wfa_capa = wpa_bss_get_vendor_ie(bss, WFA_CAPA_IE_VENDOR_TYPE); + if (!wfa_capa) + wfa_capa = wpa_bss_get_vendor_ie_beacon( + bss, WFA_CAPA_IE_VENDOR_TYPE); + + if (!wfa_capa || wfa_capa[1] < 6 || wfa_capa[6] < 1 || + !(wfa_capa[7] & WFA_CAPA_QM_NON_EHT_SCS_TRAFFIC_DESC)) { + /* AP does not enable QM non EHT traffic description policy */ + return false; + } + + return true; +} + + +static int wpas_populate_wfa_capa(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, + u8 *wpa_ie, size_t wpa_ie_len, + size_t max_wpa_ie_len) +{ + struct wpabuf *wfa_ie = NULL; + u8 wfa_capa[1]; + size_t wfa_ie_len, buf_len; + + os_memset(wfa_capa, 0, sizeof(wfa_capa)); + if (wpa_s->enable_dscp_policy_capa) + wfa_capa[0] |= WFA_CAPA_QM_DSCP_POLICY; + + if (wpa_is_non_eht_scs_traffic_desc_supported(bss)) + wfa_capa[0] |= WFA_CAPA_QM_NON_EHT_SCS_TRAFFIC_DESC; + + if (!wfa_capa[0]) + return wpa_ie_len; + + /* Wi-Fi Alliance element */ + buf_len = 1 + /* Element ID */ + 1 + /* Length */ + 3 + /* OUI */ + 1 + /* OUI Type */ + 1 + /* Capabilities Length */ + sizeof(wfa_capa); /* Capabilities */ + wfa_ie = wpabuf_alloc(buf_len); + if (!wfa_ie) + return wpa_ie_len; + + wpabuf_put_u8(wfa_ie, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(wfa_ie, buf_len - 2); + wpabuf_put_be24(wfa_ie, OUI_WFA); + wpabuf_put_u8(wfa_ie, WFA_CAPA_OUI_TYPE); + wpabuf_put_u8(wfa_ie, sizeof(wfa_capa)); + wpabuf_put_data(wfa_ie, wfa_capa, sizeof(wfa_capa)); + + wfa_ie_len = wpabuf_len(wfa_ie); + if (wpa_ie_len + wfa_ie_len <= max_wpa_ie_len) { + wpa_hexdump_buf(MSG_MSGDUMP, "WFA Capabilities element", + wfa_ie); + os_memcpy(wpa_ie + wpa_ie_len, wpabuf_head(wfa_ie), + wfa_ie_len); + wpa_ie_len += wfa_ie_len; + } + + wpabuf_free(wfa_ie); + return wpa_ie_len; +} + + +static u8 * wpas_populate_assoc_ies( + struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, struct wpa_ssid *ssid, + struct wpa_driver_associate_params *params, + enum wpa_drv_update_connect_params_mask *mask) +{ + u8 *wpa_ie; + size_t max_wpa_ie_len = 500; + size_t wpa_ie_len; + int algs = WPA_AUTH_ALG_OPEN; +#ifdef CONFIG_MBO + const u8 *mbo_ie; +#endif +#if defined(CONFIG_SAE) || defined(CONFIG_FILS) + int pmksa_cached = 0; +#endif /* CONFIG_SAE || CONFIG_FILS */ +#ifdef CONFIG_FILS + const u8 *realm, *username, *rrk; + size_t realm_len, username_len, rrk_len; + u16 next_seq_num; + struct fils_hlp_req *req; + + dl_list_for_each(req, &wpa_s->fils_hlp_req, struct fils_hlp_req, + list) { + max_wpa_ie_len += 3 + 2 * ETH_ALEN + 6 + wpabuf_len(req->pkt) + + 2 + 2 * wpabuf_len(req->pkt) / 255; + } +#endif /* CONFIG_FILS */ + + wpa_ie = os_malloc(max_wpa_ie_len); + if (!wpa_ie) { + wpa_printf(MSG_ERROR, + "Failed to allocate connect IE buffer for %lu bytes", + (unsigned long) max_wpa_ie_len); + return NULL; + } + + if (bss && (wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE) || + wpa_bss_get_ie(bss, WLAN_EID_RSN)) && + wpa_key_mgmt_wpa(ssid->key_mgmt)) { + int try_opportunistic; + const u8 *cache_id = NULL; + const u8 *addr = bss->bssid; + + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && + (wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) && + !is_zero_ether_addr(bss->mld_addr)) + addr = bss->mld_addr; + + if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && + wpa_s->valid_links) + addr = wpa_s->ap_mld_addr; + + try_opportunistic = (ssid->proactive_key_caching < 0 ? + wpa_s->conf->okc : + ssid->proactive_key_caching) && + (ssid->proto & WPA_PROTO_RSN); +#ifdef CONFIG_FILS + if (wpa_key_mgmt_fils(ssid->key_mgmt)) + cache_id = wpa_bss_get_fils_cache_id(bss); +#endif /* CONFIG_FILS */ + if (pmksa_cache_set_current(wpa_s->wpa, NULL, addr, + ssid, try_opportunistic, + cache_id, 0, false) == 0) { + eapol_sm_notify_pmkid_attempt(wpa_s->eapol); +#if defined(CONFIG_SAE) || defined(CONFIG_FILS) + pmksa_cached = 1; +#endif /* CONFIG_SAE || CONFIG_FILS */ + } + wpa_ie_len = max_wpa_ie_len; + if (wpa_supplicant_set_suites(wpa_s, bss, ssid, + wpa_ie, &wpa_ie_len, false)) { + wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to set WPA " + "key management and encryption suites"); + os_free(wpa_ie); + return NULL; + } +#ifdef CONFIG_HS20 + } else if (bss && wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE) && + (ssid->key_mgmt & WPA_KEY_MGMT_OSEN)) { + /* No PMKSA caching, but otherwise similar to RSN/WPA */ + wpa_ie_len = max_wpa_ie_len; + if (wpa_supplicant_set_suites(wpa_s, bss, ssid, + wpa_ie, &wpa_ie_len, false)) { + wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to set WPA " + "key management and encryption suites"); + os_free(wpa_ie); + return NULL; + } +#endif /* CONFIG_HS20 */ + } else if ((ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) && bss && + wpa_key_mgmt_wpa_ieee8021x(ssid->key_mgmt)) { + /* + * Both WPA and non-WPA IEEE 802.1X enabled in configuration - + * use non-WPA since the scan results did not indicate that the + * AP is using WPA or WPA2. + */ + wpa_supplicant_set_non_wpa_policy(wpa_s, ssid); + wpa_ie_len = 0; + wpa_s->wpa_proto = 0; + } else if (wpa_key_mgmt_wpa_any(ssid->key_mgmt)) { + wpa_ie_len = max_wpa_ie_len; + if (wpa_supplicant_set_suites(wpa_s, NULL, ssid, + wpa_ie, &wpa_ie_len, false)) { + wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to set WPA " + "key management and encryption suites (no " + "scan results)"); + os_free(wpa_ie); + return NULL; + } +#ifdef CONFIG_WPS + } else if (ssid->key_mgmt & WPA_KEY_MGMT_WPS) { + struct wpabuf *wps_ie; + wps_ie = wps_build_assoc_req_ie(wpas_wps_get_req_type(ssid)); + if (wps_ie && wpabuf_len(wps_ie) <= max_wpa_ie_len) { + wpa_ie_len = wpabuf_len(wps_ie); + os_memcpy(wpa_ie, wpabuf_head(wps_ie), wpa_ie_len); + } else + wpa_ie_len = 0; + wpabuf_free(wps_ie); + wpa_supplicant_set_non_wpa_policy(wpa_s, ssid); + if (!bss || (bss->caps & IEEE80211_CAP_PRIVACY)) + params->wps = WPS_MODE_PRIVACY; + else + params->wps = WPS_MODE_OPEN; + wpa_s->wpa_proto = 0; +#endif /* CONFIG_WPS */ + } else { + wpa_supplicant_set_non_wpa_policy(wpa_s, ssid); + wpa_ie_len = 0; + wpa_s->wpa_proto = 0; + } + +#ifdef IEEE8021X_EAPOL + if (ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) { + if (ssid->leap) { + if (ssid->non_leap == 0) + algs = WPA_AUTH_ALG_LEAP; + else + algs |= WPA_AUTH_ALG_LEAP; + } + } + +#ifdef CONFIG_FILS + /* Clear FILS association */ + wpa_sm_set_reset_fils_completed(wpa_s->wpa, 0); + + if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_FILS_SK_OFFLOAD) && + ssid->eap.erp && wpa_key_mgmt_fils(wpa_s->key_mgmt) && + eapol_sm_get_erp_info(wpa_s->eapol, &ssid->eap, &username, + &username_len, &realm, &realm_len, + &next_seq_num, &rrk, &rrk_len) == 0 && + (!wpa_s->last_con_fail_realm || + wpa_s->last_con_fail_realm_len != realm_len || + os_memcmp(wpa_s->last_con_fail_realm, realm, realm_len) != 0)) { + algs = WPA_AUTH_ALG_FILS; + params->fils_erp_username = username; + params->fils_erp_username_len = username_len; + params->fils_erp_realm = realm; + params->fils_erp_realm_len = realm_len; + params->fils_erp_next_seq_num = next_seq_num; + params->fils_erp_rrk = rrk; + params->fils_erp_rrk_len = rrk_len; + + if (mask) + *mask |= WPA_DRV_UPDATE_FILS_ERP_INFO; + } else if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_FILS_SK_OFFLOAD) && + ssid->eap.erp && wpa_key_mgmt_fils(wpa_s->key_mgmt) && + pmksa_cached) { + algs = WPA_AUTH_ALG_FILS; + } +#endif /* CONFIG_FILS */ +#endif /* IEEE8021X_EAPOL */ +#ifdef CONFIG_SAE + if (wpa_key_mgmt_sae(wpa_s->key_mgmt)) + algs = WPA_AUTH_ALG_SAE; +#endif /* CONFIG_SAE */ + + wpa_dbg(wpa_s, MSG_DEBUG, "Automatic auth_alg selection: 0x%x", algs); + if (ssid->auth_alg) { + algs = ssid->auth_alg; + wpa_dbg(wpa_s, MSG_DEBUG, + "Overriding auth_alg selection: 0x%x", algs); + } + +#ifdef CONFIG_SAE + if (pmksa_cached && algs == WPA_AUTH_ALG_SAE) { + wpa_dbg(wpa_s, MSG_DEBUG, + "SAE: Use WPA_AUTH_ALG_OPEN for PMKSA caching attempt"); + algs = WPA_AUTH_ALG_OPEN; + } +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_P2P + if (wpa_s->global->p2p) { + u8 *pos; + size_t len; + int res; + pos = wpa_ie + wpa_ie_len; + len = max_wpa_ie_len - wpa_ie_len; + res = wpas_p2p_assoc_req_ie(wpa_s, bss, pos, len, + ssid->p2p_group); + if (res >= 0) + wpa_ie_len += res; + } + + wpa_s->cross_connect_disallowed = 0; + if (bss) { + struct wpabuf *p2p; + p2p = wpa_bss_get_vendor_ie_multi(bss, P2P_IE_VENDOR_TYPE); + if (p2p) { + wpa_s->cross_connect_disallowed = + p2p_get_cross_connect_disallowed(p2p); + wpabuf_free(p2p); + wpa_dbg(wpa_s, MSG_DEBUG, "P2P: WLAN AP %s cross " + "connection", + wpa_s->cross_connect_disallowed ? + "disallows" : "allows"); + } + } + + os_memset(wpa_s->p2p_ip_addr_info, 0, sizeof(wpa_s->p2p_ip_addr_info)); +#endif /* CONFIG_P2P */ + + if (bss) { + wpa_ie_len += wpas_supp_op_class_ie(wpa_s, ssid, bss, + wpa_ie + wpa_ie_len, + max_wpa_ie_len - + wpa_ie_len); + } + + /* + * Workaround: Add Extended Capabilities element only if the AP + * included this element in Beacon/Probe Response frames. Some older + * APs seem to have interoperability issues if this element is + * included, so while the standard may require us to include the + * element in all cases, it is justifiable to skip it to avoid + * interoperability issues. + */ + if (ssid->p2p_group) + wpa_drv_get_ext_capa(wpa_s, WPA_IF_P2P_CLIENT); + else + wpa_drv_get_ext_capa(wpa_s, WPA_IF_STATION); + + if (!bss || wpa_bss_get_ie(bss, WLAN_EID_EXT_CAPAB)) { + u8 ext_capab[18]; + int ext_capab_len; + ext_capab_len = wpas_build_ext_capab(wpa_s, ext_capab, + sizeof(ext_capab), bss); + if (ext_capab_len > 0 && + wpa_ie_len + ext_capab_len <= max_wpa_ie_len) { + u8 *pos = wpa_ie; + if (wpa_ie_len > 0 && pos[0] == WLAN_EID_RSN) + pos += 2 + pos[1]; + os_memmove(pos + ext_capab_len, pos, + wpa_ie_len - (pos - wpa_ie)); + wpa_ie_len += ext_capab_len; + os_memcpy(pos, ext_capab, ext_capab_len); + } + } + +#ifdef CONFIG_HS20 + if (is_hs20_network(wpa_s, ssid, bss) +#ifndef ANDROID /* Android does not use the native HS 2.0 config */ + && is_hs20_config(wpa_s) +#endif /* ANDROID */ + ) { + struct wpabuf *hs20; + + hs20 = wpabuf_alloc(20 + MAX_ROAMING_CONS_OI_LEN); + if (hs20) { + int pps_mo_id = hs20_get_pps_mo_id(wpa_s, ssid); + size_t len; + + wpas_hs20_add_indication(hs20, pps_mo_id, + get_hs20_version(bss)); + wpas_hs20_add_roam_cons_sel(hs20, ssid); + len = max_wpa_ie_len - wpa_ie_len; + if (wpabuf_len(hs20) <= len) { + os_memcpy(wpa_ie + wpa_ie_len, + wpabuf_head(hs20), wpabuf_len(hs20)); + wpa_ie_len += wpabuf_len(hs20); + } + wpabuf_free(hs20); + } + } + hs20_configure_frame_filters(wpa_s); +#endif /* CONFIG_HS20 */ + + if (wpa_s->vendor_elem[VENDOR_ELEM_ASSOC_REQ]) { + struct wpabuf *buf = wpa_s->vendor_elem[VENDOR_ELEM_ASSOC_REQ]; + size_t len; + + len = max_wpa_ie_len - wpa_ie_len; + if (wpabuf_len(buf) <= len) { + os_memcpy(wpa_ie + wpa_ie_len, + wpabuf_head(buf), wpabuf_len(buf)); + wpa_ie_len += wpabuf_len(buf); + } + } + +#ifdef CONFIG_FST + if (wpa_s->fst_ies) { + int fst_ies_len = wpabuf_len(wpa_s->fst_ies); + + if (wpa_ie_len + fst_ies_len <= max_wpa_ie_len) { + os_memcpy(wpa_ie + wpa_ie_len, + wpabuf_head(wpa_s->fst_ies), fst_ies_len); + wpa_ie_len += fst_ies_len; + } + } +#endif /* CONFIG_FST */ + +#ifdef CONFIG_MBO + mbo_ie = bss ? wpa_bss_get_vendor_ie(bss, MBO_IE_VENDOR_TYPE) : NULL; + if (!wpa_s->disable_mbo_oce && mbo_ie) { + int len; + + len = wpas_mbo_ie(wpa_s, wpa_ie + wpa_ie_len, + max_wpa_ie_len - wpa_ie_len, + !!mbo_attr_from_mbo_ie(mbo_ie, + OCE_ATTR_ID_CAPA_IND)); + if (len >= 0) + wpa_ie_len += len; + } +#endif /* CONFIG_MBO */ + +#ifdef CONFIG_FILS + if (algs == WPA_AUTH_ALG_FILS) { + size_t len; + + len = wpas_add_fils_hlp_req(wpa_s, wpa_ie + wpa_ie_len, + max_wpa_ie_len - wpa_ie_len); + wpa_ie_len += len; + } +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_OWE +#ifdef CONFIG_TESTING_OPTIONS + if (get_ie_ext(wpa_ie, wpa_ie_len, WLAN_EID_EXT_OWE_DH_PARAM)) { + wpa_printf(MSG_INFO, "TESTING: Override OWE DH element"); + } else +#endif /* CONFIG_TESTING_OPTIONS */ + if (algs == WPA_AUTH_ALG_OPEN && + ssid->key_mgmt == WPA_KEY_MGMT_OWE && + !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA)) { + struct wpabuf *owe_ie; + u16 group; + + if (ssid->owe_group) { + group = ssid->owe_group; + } else if (wpa_s->assoc_status_code == + WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED) { + if (wpa_s->last_owe_group == 19) + group = 20; + else if (wpa_s->last_owe_group == 20) + group = 21; + else + group = OWE_DH_GROUP; + } else { + group = OWE_DH_GROUP; + } + + wpa_s->last_owe_group = group; + wpa_printf(MSG_DEBUG, "OWE: Try to use group %u", group); + owe_ie = owe_build_assoc_req(wpa_s->wpa, group); + if (owe_ie && + wpabuf_len(owe_ie) <= max_wpa_ie_len - wpa_ie_len) { + os_memcpy(wpa_ie + wpa_ie_len, + wpabuf_head(owe_ie), wpabuf_len(owe_ie)); + wpa_ie_len += wpabuf_len(owe_ie); + } + wpabuf_free(owe_ie); + } +#endif /* CONFIG_OWE */ + +#ifdef CONFIG_DPP2 + if (DPP_VERSION > 1 && + wpa_sm_get_key_mgmt(wpa_s->wpa) == WPA_KEY_MGMT_DPP && + ssid->dpp_netaccesskey && + ssid->dpp_pfs != 2 && !ssid->dpp_pfs_fallback) { + struct rsn_pmksa_cache_entry *pmksa; + + pmksa = pmksa_cache_get_current(wpa_s->wpa); + if (!pmksa || !pmksa->dpp_pfs) + goto pfs_fail; + + dpp_pfs_free(wpa_s->dpp_pfs); + wpa_s->dpp_pfs = dpp_pfs_init(ssid->dpp_netaccesskey, + ssid->dpp_netaccesskey_len); + if (!wpa_s->dpp_pfs) { + wpa_printf(MSG_DEBUG, "DPP: Could not initialize PFS"); + /* Try to continue without PFS */ + goto pfs_fail; + } + if (wpabuf_len(wpa_s->dpp_pfs->ie) <= + max_wpa_ie_len - wpa_ie_len) { + os_memcpy(wpa_ie + wpa_ie_len, + wpabuf_head(wpa_s->dpp_pfs->ie), + wpabuf_len(wpa_s->dpp_pfs->ie)); + wpa_ie_len += wpabuf_len(wpa_s->dpp_pfs->ie); + } + } +pfs_fail: +#endif /* CONFIG_DPP2 */ + +#ifdef CONFIG_IEEE80211R + /* + * Add MDIE under these conditions: the network profile allows FT, + * the AP supports FT, and the mobility domain ID matches. + */ + if (bss && wpa_key_mgmt_ft(wpa_sm_get_key_mgmt(wpa_s->wpa))) { + const u8 *mdie = wpa_bss_get_ie(bss, WLAN_EID_MOBILITY_DOMAIN); + + if (mdie && mdie[1] >= MOBILITY_DOMAIN_ID_LEN) { + size_t len = 0; + const u8 *md = mdie + 2; + const u8 *wpa_md = wpa_sm_get_ft_md(wpa_s->wpa); + + if (os_memcmp(md, wpa_md, + MOBILITY_DOMAIN_ID_LEN) == 0) { + /* Add mobility domain IE */ + len = wpa_ft_add_mdie( + wpa_s->wpa, wpa_ie + wpa_ie_len, + max_wpa_ie_len - wpa_ie_len, mdie); + wpa_ie_len += len; + } +#ifdef CONFIG_SME + if (len > 0 && wpa_s->sme.ft_used && + wpa_sm_has_ft_keys(wpa_s->wpa, md)) { + wpa_dbg(wpa_s, MSG_DEBUG, + "SME: Trying to use FT over-the-air"); + algs |= WPA_AUTH_ALG_FT; + } +#endif /* CONFIG_SME */ + } + } +#endif /* CONFIG_IEEE80211R */ + +#ifdef CONFIG_TESTING_OPTIONS + if (wpa_s->rsnxe_override_assoc && + wpabuf_len(wpa_s->rsnxe_override_assoc) <= + max_wpa_ie_len - wpa_ie_len) { + wpa_printf(MSG_DEBUG, "TESTING: RSNXE AssocReq override"); + os_memcpy(wpa_ie + wpa_ie_len, + wpabuf_head(wpa_s->rsnxe_override_assoc), + wpabuf_len(wpa_s->rsnxe_override_assoc)); + wpa_ie_len += wpabuf_len(wpa_s->rsnxe_override_assoc); + } else +#endif /* CONFIG_TESTING_OPTIONS */ + if (wpa_s->rsnxe_len > 0 && + wpa_s->rsnxe_len <= max_wpa_ie_len - wpa_ie_len) { + os_memcpy(wpa_ie + wpa_ie_len, wpa_s->rsnxe, wpa_s->rsnxe_len); + wpa_ie_len += wpa_s->rsnxe_len; + } + +#ifdef CONFIG_TESTING_OPTIONS + if (wpa_s->disable_mscs_support) + goto mscs_end; +#endif /* CONFIG_TESTING_OPTIONS */ + if (wpa_bss_ext_capab(bss, WLAN_EXT_CAPAB_MSCS) && + wpa_s->robust_av.valid_config) { + struct wpabuf *mscs_ie; + size_t mscs_ie_len, buf_len; + + buf_len = 3 + /* MSCS descriptor IE header */ + 1 + /* Request type */ + 2 + /* User priority control */ + 4 + /* Stream timeout */ + 3 + /* TCLAS Mask IE header */ + wpa_s->robust_av.frame_classifier_len; + mscs_ie = wpabuf_alloc(buf_len); + if (!mscs_ie) { + wpa_printf(MSG_INFO, + "MSCS: Failed to allocate MSCS IE"); + goto mscs_end; + } + + wpas_populate_mscs_descriptor_ie(&wpa_s->robust_av, mscs_ie); + if ((wpa_ie_len + wpabuf_len(mscs_ie)) <= max_wpa_ie_len) { + wpa_hexdump_buf(MSG_MSGDUMP, "MSCS IE", mscs_ie); + mscs_ie_len = wpabuf_len(mscs_ie); + os_memcpy(wpa_ie + wpa_ie_len, wpabuf_head(mscs_ie), + mscs_ie_len); + wpa_ie_len += mscs_ie_len; + } + + wpabuf_free(mscs_ie); + } +mscs_end: + + wpa_ie_len = wpas_populate_wfa_capa(wpa_s, bss, wpa_ie, wpa_ie_len, + max_wpa_ie_len); + + if (ssid->multi_ap_backhaul_sta) { + size_t multi_ap_ie_len; + + multi_ap_ie_len = add_multi_ap_ie(wpa_ie + wpa_ie_len, + max_wpa_ie_len - wpa_ie_len, + MULTI_AP_BACKHAUL_STA); + if (multi_ap_ie_len == 0) { + wpa_printf(MSG_ERROR, + "Multi-AP: Failed to build Multi-AP IE"); + os_free(wpa_ie); + return NULL; + } + wpa_ie_len += multi_ap_ie_len; + } + + params->wpa_ie = wpa_ie; + params->wpa_ie_len = wpa_ie_len; + params->auth_alg = algs; + if (mask) + *mask |= WPA_DRV_UPDATE_ASSOC_IES | WPA_DRV_UPDATE_AUTH_TYPE; + + return wpa_ie; +} + + +#ifdef CONFIG_OWE +static void wpas_update_owe_connect_params(struct wpa_supplicant *wpa_s) +{ + struct wpa_driver_associate_params params; + u8 *wpa_ie; + + os_memset(¶ms, 0, sizeof(params)); + wpa_ie = wpas_populate_assoc_ies(wpa_s, wpa_s->current_bss, + wpa_s->current_ssid, ¶ms, NULL); + if (!wpa_ie) + return; + + wpa_drv_update_connect_params(wpa_s, ¶ms, WPA_DRV_UPDATE_ASSOC_IES); + os_free(wpa_ie); +} +#endif /* CONFIG_OWE */ + + +#if defined(CONFIG_FILS) && defined(IEEE8021X_EAPOL) +static void wpas_update_fils_connect_params(struct wpa_supplicant *wpa_s) +{ + struct wpa_driver_associate_params params; + enum wpa_drv_update_connect_params_mask mask = 0; + u8 *wpa_ie; + + if (wpa_s->auth_alg != WPA_AUTH_ALG_OPEN) + return; /* nothing to do */ + + os_memset(¶ms, 0, sizeof(params)); + wpa_ie = wpas_populate_assoc_ies(wpa_s, wpa_s->current_bss, + wpa_s->current_ssid, ¶ms, &mask); + if (!wpa_ie) + return; + + if (params.auth_alg == WPA_AUTH_ALG_FILS) { + wpa_s->auth_alg = params.auth_alg; + wpa_drv_update_connect_params(wpa_s, ¶ms, mask); + } + + os_free(wpa_ie); +} +#endif /* CONFIG_FILS && IEEE8021X_EAPOL */ + + +static u8 wpa_ie_get_edmg_oper_chans(const u8 *edmg_ie) +{ + if (!edmg_ie || edmg_ie[1] < 6) + return 0; + return edmg_ie[EDMG_BSS_OPERATING_CHANNELS_OFFSET]; +} + + +static u8 wpa_ie_get_edmg_oper_chan_width(const u8 *edmg_ie) +{ + if (!edmg_ie || edmg_ie[1] < 6) + return 0; + return edmg_ie[EDMG_OPERATING_CHANNEL_WIDTH_OFFSET]; +} + + +/* Returns the intersection of two EDMG configurations. + * Note: The current implementation is limited to CB2 only (CB1 included), + * i.e., the implementation supports up to 2 contiguous channels. + * For supporting non-contiguous (aggregated) channels and for supporting + * CB3 and above, this function will need to be extended. + */ +static struct ieee80211_edmg_config +get_edmg_intersection(struct ieee80211_edmg_config a, + struct ieee80211_edmg_config b, + u8 primary_channel) +{ + struct ieee80211_edmg_config result; + int i, contiguous = 0; + int max_contiguous = 0; + + result.channels = b.channels & a.channels; + if (!result.channels) { + wpa_printf(MSG_DEBUG, + "EDMG not possible: cannot intersect channels 0x%x and 0x%x", + a.channels, b.channels); + goto fail; + } + + if (!(result.channels & BIT(primary_channel - 1))) { + wpa_printf(MSG_DEBUG, + "EDMG not possible: the primary channel %d is not one of the intersected channels 0x%x", + primary_channel, result.channels); + goto fail; + } + + /* Find max contiguous channels */ + for (i = 0; i < 6; i++) { + if (result.channels & BIT(i)) + contiguous++; + else + contiguous = 0; + + if (contiguous > max_contiguous) + max_contiguous = contiguous; + } + + /* Assuming AP and STA supports ONLY contiguous channels, + * bw configuration can have value between 4-7. + */ + if ((b.bw_config < a.bw_config)) + result.bw_config = b.bw_config; + else + result.bw_config = a.bw_config; + + if ((max_contiguous >= 2 && result.bw_config < EDMG_BW_CONFIG_5) || + (max_contiguous >= 1 && result.bw_config < EDMG_BW_CONFIG_4)) { + wpa_printf(MSG_DEBUG, + "EDMG not possible: not enough contiguous channels %d for supporting CB1 or CB2", + max_contiguous); + goto fail; + } + + return result; + +fail: + result.channels = 0; + result.bw_config = 0; + return result; +} + + +static struct ieee80211_edmg_config +get_supported_edmg(struct wpa_supplicant *wpa_s, + struct hostapd_freq_params *freq, + struct ieee80211_edmg_config request_edmg) +{ + enum hostapd_hw_mode hw_mode; + struct hostapd_hw_modes *mode = NULL; + u8 primary_channel; + + if (!wpa_s->hw.modes) + goto fail; + + hw_mode = ieee80211_freq_to_chan(freq->freq, &primary_channel); + if (hw_mode == NUM_HOSTAPD_MODES) + goto fail; + + mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, hw_mode, false); + if (!mode) + goto fail; + + return get_edmg_intersection(mode->edmg, request_edmg, primary_channel); + +fail: + request_edmg.channels = 0; + request_edmg.bw_config = 0; + return request_edmg; +} + + +#ifdef CONFIG_MBO +void wpas_update_mbo_connect_params(struct wpa_supplicant *wpa_s) +{ + struct wpa_driver_associate_params params; + u8 *wpa_ie; + + /* + * Update MBO connect params only in case of change of MBO attributes + * when connected, if the AP support MBO. + */ + + if (wpa_s->wpa_state != WPA_COMPLETED || !wpa_s->current_ssid || + !wpa_s->current_bss || + !wpa_bss_get_vendor_ie(wpa_s->current_bss, MBO_IE_VENDOR_TYPE)) + return; + + os_memset(¶ms, 0, sizeof(params)); + wpa_ie = wpas_populate_assoc_ies(wpa_s, wpa_s->current_bss, + wpa_s->current_ssid, ¶ms, NULL); + if (!wpa_ie) + return; + + wpa_drv_update_connect_params(wpa_s, ¶ms, WPA_DRV_UPDATE_ASSOC_IES); + os_free(wpa_ie); +} +#endif /* CONFIG_MBO */ + + +static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) +{ + struct wpa_connect_work *cwork = work->ctx; + struct wpa_bss *bss = cwork->bss; + struct wpa_ssid *ssid = cwork->ssid; + struct wpa_supplicant *wpa_s = work->wpa_s; + u8 *wpa_ie; + const u8 *edmg_ie_oper; + int use_crypt, ret, bssid_changed; + unsigned int cipher_pairwise, cipher_group, cipher_group_mgmt; + struct wpa_driver_associate_params params; + u8 psk[PMK_LEN]; +#if defined(CONFIG_WEP) || defined(IEEE8021X_EAPOL) + int wep_keys_set = 0; +#endif /* CONFIG_WEP || IEEE8021X_EAPOL */ + int assoc_failed = 0; + struct wpa_ssid *old_ssid; + u8 prev_bssid[ETH_ALEN]; +#ifdef CONFIG_HT_OVERRIDES + struct ieee80211_ht_capabilities htcaps; + struct ieee80211_ht_capabilities htcaps_mask; +#endif /* CONFIG_HT_OVERRIDES */ +#ifdef CONFIG_VHT_OVERRIDES + struct ieee80211_vht_capabilities vhtcaps; + struct ieee80211_vht_capabilities vhtcaps_mask; +#endif /* CONFIG_VHT_OVERRIDES */ + + wpa_s->roam_in_progress = false; +#ifdef CONFIG_WNM + wpa_s->bss_trans_mgmt_in_progress = false; +#endif /* CONFIG_WNM */ + + if (deinit) { + if (work->started) { + wpa_s->connect_work = NULL; + + /* cancel possible auth. timeout */ + eloop_cancel_timeout(wpa_supplicant_timeout, wpa_s, + NULL); + } + wpas_connect_work_free(cwork); + return; + } + + wpa_s->connect_work = work; + + if (cwork->bss_removed || !wpas_valid_bss_ssid(wpa_s, bss, ssid) || + wpas_network_disabled(wpa_s, ssid)) { + wpa_dbg(wpa_s, MSG_DEBUG, "BSS/SSID entry for association not valid anymore - drop connection attempt"); + wpas_connect_work_done(wpa_s); + return; + } + + /* + * Set the current AP's BSSID (for non-MLO connection) or MLD address + * (for MLO connection) as the previous BSSID for reassociation requests + * handled by SME-in-driver. If wpa_supplicant is in disconnected state, + * prev_bssid will be zero as both wpa_s->valid_links and wpa_s->bssid + * will be zero. + */ + os_memcpy(prev_bssid, + wpa_s->valid_links ? wpa_s->ap_mld_addr : wpa_s->bssid, + ETH_ALEN); + os_memset(¶ms, 0, sizeof(params)); + wpa_s->reassociate = 0; + wpa_s->eap_expected_failure = 0; + + /* Starting new association, so clear the possibly used WPA IE from the + * previous association. */ + wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, NULL, 0); + wpa_sm_set_assoc_rsnxe(wpa_s->wpa, NULL, 0); + wpa_s->rsnxe_len = 0; + wpa_s->mscs_setup_done = false; + + wpa_ie = wpas_populate_assoc_ies(wpa_s, bss, ssid, ¶ms, NULL); + if (!wpa_ie) { + wpas_connect_work_done(wpa_s); + return; + } + + if (bss && + (!wpas_driver_bss_selection(wpa_s) || wpas_wps_searching(wpa_s))) { +#ifdef CONFIG_IEEE80211R + const u8 *ie, *md = NULL; +#endif /* CONFIG_IEEE80211R */ + wpa_msg(wpa_s, MSG_INFO, "Trying to associate with " MACSTR + " (SSID='%s' freq=%d MHz)", MAC2STR(bss->bssid), + wpa_ssid_txt(bss->ssid, bss->ssid_len), bss->freq); + bssid_changed = !is_zero_ether_addr(wpa_s->bssid); + os_memset(wpa_s->bssid, 0, ETH_ALEN); + os_memcpy(wpa_s->pending_bssid, bss->bssid, ETH_ALEN); + if (bssid_changed) + wpas_notify_bssid_changed(wpa_s); +#ifdef CONFIG_IEEE80211R + ie = wpa_bss_get_ie(bss, WLAN_EID_MOBILITY_DOMAIN); + if (ie && ie[1] >= MOBILITY_DOMAIN_ID_LEN) + md = ie + 2; + wpa_sm_set_ft_params(wpa_s->wpa, ie, ie ? 2 + ie[1] : 0); + if (md) { + /* Prepare for the next transition */ + wpa_ft_prepare_auth_request(wpa_s->wpa, ie); + } +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_WPS + } else if ((ssid->ssid == NULL || ssid->ssid_len == 0) && + wpa_s->conf->ap_scan == 2 && + (ssid->key_mgmt & WPA_KEY_MGMT_WPS)) { + /* Use ap_scan==1 style network selection to find the network + */ + wpas_connect_work_done(wpa_s); + wpa_s->scan_req = MANUAL_SCAN_REQ; + wpa_s->reassociate = 1; + wpa_supplicant_req_scan(wpa_s, 0, 0); + os_free(wpa_ie); + return; +#endif /* CONFIG_WPS */ + } else { + wpa_msg(wpa_s, MSG_INFO, "Trying to associate with SSID '%s'", + wpa_ssid_txt(ssid->ssid, ssid->ssid_len)); + if (bss) + os_memcpy(wpa_s->pending_bssid, bss->bssid, ETH_ALEN); + else + os_memset(wpa_s->pending_bssid, 0, ETH_ALEN); + } + if (!wpa_s->pno) + wpa_supplicant_cancel_sched_scan(wpa_s); + + wpa_supplicant_cancel_scan(wpa_s); + + wpa_clear_keys(wpa_s, bss ? bss->bssid : NULL); + use_crypt = 1; + cipher_pairwise = wpa_s->pairwise_cipher; + cipher_group = wpa_s->group_cipher; + cipher_group_mgmt = wpa_s->mgmt_group_cipher; + if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE || + wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA) { + if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE) + use_crypt = 0; +#ifdef CONFIG_WEP + if (wpa_set_wep_keys(wpa_s, ssid)) { + use_crypt = 1; + wep_keys_set = 1; + } +#endif /* CONFIG_WEP */ + } + if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) + use_crypt = 0; + +#ifdef IEEE8021X_EAPOL + if (wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA) { + if ((ssid->eapol_flags & + (EAPOL_FLAG_REQUIRE_KEY_UNICAST | + EAPOL_FLAG_REQUIRE_KEY_BROADCAST)) == 0 && + !wep_keys_set) { + use_crypt = 0; + } else { + /* Assume that dynamic WEP-104 keys will be used and + * set cipher suites in order for drivers to expect + * encryption. */ + cipher_pairwise = cipher_group = WPA_CIPHER_WEP104; + } + } +#endif /* IEEE8021X_EAPOL */ + + if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) { + /* Set the key before (and later after) association */ + wpa_supplicant_set_wpa_none_key(wpa_s, ssid); + } + + /* Set current_ssid before changing state to ASSOCIATING, so that the + * selected SSID is available to wpas_notify_state_changed(). */ + old_ssid = wpa_s->current_ssid; + wpa_s->current_ssid = ssid; + + wpa_supplicant_set_state(wpa_s, WPA_ASSOCIATING); + if (bss) { + params.ssid = bss->ssid; + params.ssid_len = bss->ssid_len; + if (!wpas_driver_bss_selection(wpa_s) || ssid->bssid_set || + wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) { + wpa_printf(MSG_DEBUG, "Limit connection to BSSID " + MACSTR " freq=%u MHz based on scan results " + "(bssid_set=%d wps=%d)", + MAC2STR(bss->bssid), bss->freq, + ssid->bssid_set, + wpa_s->key_mgmt == WPA_KEY_MGMT_WPS); + params.bssid = bss->bssid; + params.freq.freq = bss->freq; + } + params.bssid_hint = bss->bssid; + params.freq_hint = bss->freq; + params.pbss = bss_is_pbss(bss); + } else { + if (ssid->bssid_hint_set) + params.bssid_hint = ssid->bssid_hint; + + params.ssid = ssid->ssid; + params.ssid_len = ssid->ssid_len; + params.pbss = (ssid->pbss != 2) ? ssid->pbss : 0; + } + + if (ssid->mode == WPAS_MODE_IBSS && ssid->bssid_set && + wpa_s->conf->ap_scan == 2) { + params.bssid = ssid->bssid; + params.fixed_bssid = 1; + } + + /* Initial frequency for IBSS/mesh */ + if ((ssid->mode == WPAS_MODE_IBSS || ssid->mode == WPAS_MODE_MESH) && + ssid->frequency > 0 && params.freq.freq == 0) + ibss_mesh_setup_freq(wpa_s, ssid, ¶ms.freq); + + if (ssid->mode == WPAS_MODE_IBSS) { + params.fixed_freq = ssid->fixed_freq; + if (ssid->beacon_int) + params.beacon_int = ssid->beacon_int; + else + params.beacon_int = wpa_s->conf->beacon_int; + } + + if (bss && ssid->enable_edmg) + edmg_ie_oper = wpa_bss_get_ie_ext(bss, + WLAN_EID_EXT_EDMG_OPERATION); + else + edmg_ie_oper = NULL; + + if (edmg_ie_oper) { + params.freq.edmg.channels = + wpa_ie_get_edmg_oper_chans(edmg_ie_oper); + params.freq.edmg.bw_config = + wpa_ie_get_edmg_oper_chan_width(edmg_ie_oper); + wpa_printf(MSG_DEBUG, + "AP supports EDMG channels 0x%x, bw_config %d", + params.freq.edmg.channels, + params.freq.edmg.bw_config); + + /* User may ask for specific EDMG channel for EDMG connection + * (must be supported by AP) + */ + if (ssid->edmg_channel) { + struct ieee80211_edmg_config configured_edmg; + enum hostapd_hw_mode hw_mode; + u8 primary_channel; + + hw_mode = ieee80211_freq_to_chan(bss->freq, + &primary_channel); + if (hw_mode == NUM_HOSTAPD_MODES) + goto edmg_fail; + + hostapd_encode_edmg_chan(ssid->enable_edmg, + ssid->edmg_channel, + primary_channel, + &configured_edmg); + + if (ieee802_edmg_is_allowed(params.freq.edmg, + configured_edmg)) { + params.freq.edmg = configured_edmg; + wpa_printf(MSG_DEBUG, + "Use EDMG channel %d for connection", + ssid->edmg_channel); + } else { + edmg_fail: + params.freq.edmg.channels = 0; + params.freq.edmg.bw_config = 0; + wpa_printf(MSG_WARNING, + "EDMG channel %d not supported by AP, fallback to DMG", + ssid->edmg_channel); + } + } + + if (params.freq.edmg.channels) { + wpa_printf(MSG_DEBUG, + "EDMG before: channels 0x%x, bw_config %d", + params.freq.edmg.channels, + params.freq.edmg.bw_config); + params.freq.edmg = get_supported_edmg(wpa_s, + ¶ms.freq, + params.freq.edmg); + wpa_printf(MSG_DEBUG, + "EDMG after: channels 0x%x, bw_config %d", + params.freq.edmg.channels, + params.freq.edmg.bw_config); + } + } + + params.pairwise_suite = cipher_pairwise; + params.group_suite = cipher_group; + params.mgmt_group_suite = cipher_group_mgmt; + params.key_mgmt_suite = wpa_s->key_mgmt; + params.allowed_key_mgmts = wpa_s->allowed_key_mgmts; + params.wpa_proto = wpa_s->wpa_proto; + wpa_s->auth_alg = params.auth_alg; + params.mode = ssid->mode; + params.bg_scan_period = ssid->bg_scan_period; +#ifdef CONFIG_WEP + { + int i; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (ssid->wep_key_len[i]) + params.wep_key[i] = ssid->wep_key[i]; + params.wep_key_len[i] = ssid->wep_key_len[i]; + } + params.wep_tx_keyidx = ssid->wep_tx_keyidx; + } +#endif /* CONFIG_WEP */ + + if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_PSK) && +#if defined(CONFIG_DRIVER_NL80211_BRCM) || defined(CONFIG_DRIVER_NL80211_SYNA) + ((params.key_mgmt_suite & WPA_KEY_MGMT_PSK) || + (params.key_mgmt_suite == WPA_KEY_MGMT_FT_PSK))) { +#else + (params.key_mgmt_suite == WPA_KEY_MGMT_PSK || + params.key_mgmt_suite == WPA_KEY_MGMT_FT_PSK || + (params.allowed_key_mgmts & + (WPA_KEY_MGMT_PSK | WPA_KEY_MGMT_FT_PSK)))) { +#endif /* CONFIG_DRIVER_NL80211_BRCM || CONFIG_DRIVER_NL80211_SYNA */ + params.passphrase = ssid->passphrase; + if (wpa_supplicant_get_psk(wpa_s, bss, ssid, psk) == 0) + params.psk = psk; + } + + if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_8021X) && + (params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X || + params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA256 || + params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SUITE_B || + params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 || + params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA384)) + params.req_handshake_offload = 1; + + if (wpa_s->conf->key_mgmt_offload) { + if (params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X || + params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA256 || + params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SUITE_B || + params.key_mgmt_suite == + WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 || + params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA384) + params.req_key_mgmt_offload = + ssid->proactive_key_caching < 0 ? + wpa_s->conf->okc : ssid->proactive_key_caching; + else + params.req_key_mgmt_offload = 1; + +#if defined(CONFIG_DRIVER_NL80211_BRCM) || defined(CONFIG_DRIVER_NL80211_SYNA) + if (((params.key_mgmt_suite & WPA_KEY_MGMT_PSK) || + params.key_mgmt_suite == WPA_KEY_MGMT_PSK_SHA256 || + params.key_mgmt_suite == WPA_KEY_MGMT_FT_PSK) && +#else + if ((wpa_key_mgmt_wpa_psk_no_sae(params.key_mgmt_suite) || + wpa_key_mgmt_wpa_psk_no_sae(params.allowed_key_mgmts)) && +#endif /* CONFIG_DRIVER_NL80211_BRCM || CONFIG_DRIVER_NL80211_SYNA */ + wpa_supplicant_get_psk(wpa_s, bss, ssid, psk) == 0) + params.psk = psk; + } + + if ((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA) && + wpa_key_mgmt_sae(params.key_mgmt_suite)) { + params.auth_alg = WPA_AUTH_ALG_SAE; + if (ssid->sae_password) { + params.sae_password = ssid->sae_password; + params.sae_password_id = ssid->sae_password_id; + } else if (ssid->passphrase) { + params.passphrase = ssid->passphrase; + } + } + + params.drop_unencrypted = use_crypt; + + params.mgmt_frame_protection = wpas_get_ssid_pmf(wpa_s, ssid); + if (params.mgmt_frame_protection != NO_MGMT_FRAME_PROTECTION && bss) { + const u8 *rsn = wpa_bss_get_ie(bss, WLAN_EID_RSN); + struct wpa_ie_data ie; + if (!wpas_driver_bss_selection(wpa_s) && rsn && + wpa_parse_wpa_ie(rsn, 2 + rsn[1], &ie) == 0 && + ie.capabilities & + (WPA_CAPABILITY_MFPC | WPA_CAPABILITY_MFPR)) { + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Selected AP supports " + "MFP: require MFP"); + params.mgmt_frame_protection = + MGMT_FRAME_PROTECTION_REQUIRED; +#ifdef CONFIG_OWE + } else if (!rsn && (ssid->key_mgmt & WPA_KEY_MGMT_OWE) && + !ssid->owe_only) { + params.mgmt_frame_protection = NO_MGMT_FRAME_PROTECTION; +#endif /* CONFIG_OWE */ + } + } + + params.p2p = ssid->p2p_group; + + if (wpa_s->p2pdev->set_sta_uapsd) + params.uapsd = wpa_s->p2pdev->sta_uapsd; + else + params.uapsd = -1; + +#ifdef CONFIG_HT_OVERRIDES + os_memset(&htcaps, 0, sizeof(htcaps)); + os_memset(&htcaps_mask, 0, sizeof(htcaps_mask)); + params.htcaps = (u8 *) &htcaps; + params.htcaps_mask = (u8 *) &htcaps_mask; + wpa_supplicant_apply_ht_overrides(wpa_s, ssid, ¶ms); +#endif /* CONFIG_HT_OVERRIDES */ +#ifdef CONFIG_VHT_OVERRIDES + os_memset(&vhtcaps, 0, sizeof(vhtcaps)); + os_memset(&vhtcaps_mask, 0, sizeof(vhtcaps_mask)); + params.vhtcaps = &vhtcaps; + params.vhtcaps_mask = &vhtcaps_mask; + wpa_supplicant_apply_vht_overrides(wpa_s, ssid, ¶ms); +#endif /* CONFIG_VHT_OVERRIDES */ +#ifdef CONFIG_HE_OVERRIDES + wpa_supplicant_apply_he_overrides(wpa_s, ssid, ¶ms); +#endif /* CONFIG_HE_OVERRIDES */ + wpa_supplicant_apply_eht_overrides(wpa_s, ssid, ¶ms); + +#ifdef CONFIG_P2P + /* + * If multi-channel concurrency is not supported, check for any + * frequency conflict. In case of any frequency conflict, remove the + * least prioritized connection. + */ + if (wpa_s->num_multichan_concurrent < 2) { + int freq, num; + num = get_shared_radio_freqs(wpa_s, &freq, 1, false); + if (num > 0 && freq > 0 && freq != params.freq.freq) { + wpa_printf(MSG_DEBUG, + "Assoc conflicting freq found (%d != %d)", + freq, params.freq.freq); + if (wpas_p2p_handle_frequency_conflicts( + wpa_s, params.freq.freq, ssid) < 0) { + wpas_connect_work_done(wpa_s); + os_free(wpa_ie); + return; + } + } + } +#endif /* CONFIG_P2P */ + + if (wpa_s->reassoc_same_ess && !is_zero_ether_addr(prev_bssid) && + old_ssid) + params.prev_bssid = prev_bssid; + +#ifdef CONFIG_SAE + params.sae_pwe = wpa_s->conf->sae_pwe; +#endif /* CONFIG_SAE */ + + ret = wpa_drv_associate(wpa_s, ¶ms); + forced_memzero(psk, sizeof(psk)); + os_free(wpa_ie); + if (ret < 0) { + wpa_msg(wpa_s, MSG_INFO, "Association request to the driver " + "failed"); + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_VALID_ERROR_CODES) { + /* + * The driver is known to mean what is saying, so we + * can stop right here; the association will not + * succeed. + */ + wpas_connection_failed(wpa_s, wpa_s->pending_bssid); + wpa_s->assoc_status_code = WLAN_STATUS_UNSPECIFIED_FAILURE; + wpas_notify_assoc_status_code(wpa_s, wpa_s->pending_bssid, 0, NULL, 0); + wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); + os_memset(wpa_s->pending_bssid, 0, ETH_ALEN); + return; + } + /* try to continue anyway; new association will be tried again + * after timeout */ + assoc_failed = 1; + } + + if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) { + /* Set the key after the association just in case association + * cleared the previously configured key. */ + wpa_supplicant_set_wpa_none_key(wpa_s, ssid); + /* No need to timeout authentication since there is no key + * management. */ + wpa_supplicant_cancel_auth_timeout(wpa_s); + wpa_supplicant_set_state(wpa_s, WPA_COMPLETED); +#ifdef CONFIG_IBSS_RSN + } else if (ssid->mode == WPAS_MODE_IBSS && + wpa_s->key_mgmt != WPA_KEY_MGMT_NONE && + wpa_s->key_mgmt != WPA_KEY_MGMT_WPA_NONE) { + /* + * RSN IBSS authentication is per-STA and we can disable the + * per-BSSID authentication. + */ + wpa_supplicant_cancel_auth_timeout(wpa_s); +#endif /* CONFIG_IBSS_RSN */ + } else { + /* Timeout for IEEE 802.11 authentication and association */ + int timeout = 60; + + if (assoc_failed) { + /* give IBSS a bit more time */ + timeout = ssid->mode == WPAS_MODE_IBSS ? 10 : 5; + } else if (wpa_s->conf->ap_scan == 1) { + /* give IBSS a bit more time */ + timeout = ssid->mode == WPAS_MODE_IBSS ? 20 : 10; + } + wpa_supplicant_req_auth_timeout(wpa_s, timeout, 0); + } + +#ifdef CONFIG_WEP + if (wep_keys_set && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC)) { + /* Set static WEP keys again */ + wpa_set_wep_keys(wpa_s, ssid); + } +#endif /* CONFIG_WEP */ + + if (old_ssid && old_ssid != ssid) { + /* + * Do not allow EAP session resumption between different + * network configurations. + */ + eapol_sm_invalidate_cached_session(wpa_s->eapol); + } + + if (!wpas_driver_bss_selection(wpa_s) || ssid->bssid_set) { + wpa_s->current_bss = bss; +#ifdef CONFIG_HS20 + hs20_configure_frame_filters(wpa_s); +#endif /* CONFIG_HS20 */ + } + + wpa_supplicant_rsn_supp_set_config(wpa_s, wpa_s->current_ssid); + wpa_supplicant_initiate_eapol(wpa_s); + if (old_ssid != wpa_s->current_ssid) + wpas_notify_network_changed(wpa_s); + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)) + wpas_notify_auth_changed(wpa_s); +} + + +static void wpa_supplicant_clear_connection(struct wpa_supplicant *wpa_s, + const u8 *addr) +{ + struct wpa_ssid *old_ssid; + + wpa_s->ml_connect_probe_ssid = NULL; + wpa_s->ml_connect_probe_bss = NULL; + wpas_connect_work_done(wpa_s); + wpa_clear_keys(wpa_s, addr); + old_ssid = wpa_s->current_ssid; + wpa_supplicant_mark_disassoc(wpa_s); + wpa_sm_set_config(wpa_s->wpa, NULL); + eapol_sm_notify_config(wpa_s->eapol, NULL, NULL); + if (old_ssid != wpa_s->current_ssid) + wpas_notify_network_changed(wpa_s); + + wpas_scs_deinit(wpa_s); + wpas_dscp_deinit(wpa_s); + eloop_cancel_timeout(wpa_supplicant_timeout, wpa_s, NULL); +} + + +/** + * wpa_supplicant_deauthenticate - Deauthenticate the current connection + * @wpa_s: Pointer to wpa_supplicant data + * @reason_code: IEEE 802.11 reason code for the deauthenticate frame + * + * This function is used to request %wpa_supplicant to deauthenticate from the + * current AP. + */ +void wpa_supplicant_deauthenticate(struct wpa_supplicant *wpa_s, + u16 reason_code) +{ + u8 *addr = NULL; + union wpa_event_data event; + int zero_addr = 0; + + wpa_dbg(wpa_s, MSG_DEBUG, "Request to deauthenticate - bssid=" MACSTR + " pending_bssid=" MACSTR " reason=%d (%s) state=%s", + MAC2STR(wpa_s->bssid), MAC2STR(wpa_s->pending_bssid), + reason_code, reason2str(reason_code), + wpa_supplicant_state_txt(wpa_s->wpa_state)); + + if (!is_zero_ether_addr(wpa_s->pending_bssid) && + (wpa_s->wpa_state == WPA_AUTHENTICATING || + wpa_s->wpa_state == WPA_ASSOCIATING)) + addr = wpa_s->pending_bssid; + else if (!is_zero_ether_addr(wpa_s->bssid)) + addr = wpa_s->bssid; + else if (wpa_s->wpa_state == WPA_ASSOCIATING) { + /* + * When using driver-based BSS selection, we may not know the + * BSSID with which we are currently trying to associate. We + * need to notify the driver of this disconnection even in such + * a case, so use the all zeros address here. + */ + addr = wpa_s->bssid; + zero_addr = 1; + } + + if (wpa_s->enabled_4addr_mode && wpa_drv_set_4addr_mode(wpa_s, 0) == 0) + wpa_s->enabled_4addr_mode = 0; + +#ifdef CONFIG_TDLS + wpa_tdls_teardown_peers(wpa_s->wpa); +#endif /* CONFIG_TDLS */ + +#ifdef CONFIG_MESH + if (wpa_s->ifmsh) { + struct mesh_conf *mconf; + + mconf = wpa_s->ifmsh->mconf; + wpa_msg(wpa_s, MSG_INFO, MESH_GROUP_REMOVED "%s", + wpa_s->ifname); + wpas_notify_mesh_group_removed(wpa_s, mconf->meshid, + mconf->meshid_len, reason_code); + wpa_supplicant_leave_mesh(wpa_s, true); + } +#endif /* CONFIG_MESH */ + + if (addr) { + wpa_drv_deauthenticate(wpa_s, addr, reason_code); + os_memset(&event, 0, sizeof(event)); + event.deauth_info.reason_code = reason_code; + event.deauth_info.locally_generated = 1; + wpa_supplicant_event(wpa_s, EVENT_DEAUTH, &event); + if (zero_addr) + addr = NULL; + } + + wpa_supplicant_clear_connection(wpa_s, addr); +} + + +void wpa_supplicant_reconnect(struct wpa_supplicant *wpa_s) +{ + wpa_s->own_reconnect_req = 1; + wpa_supplicant_deauthenticate(wpa_s, WLAN_REASON_UNSPECIFIED); + +} + + +static void wpa_supplicant_enable_one_network(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid) +{ + if (!ssid || !ssid->disabled || ssid->disabled == 2) + return; + + ssid->disabled = 0; + ssid->owe_transition_bss_select_count = 0; + wpas_clear_temp_disabled(wpa_s, ssid, 1); + wpas_notify_network_enabled_changed(wpa_s, ssid); + + /* + * Try to reassociate since there is no current configuration and a new + * network was made available. + */ + if (!wpa_s->current_ssid && !wpa_s->disconnected) + wpa_s->reassociate = 1; +} + + +/** + * wpa_supplicant_add_network - Add a new network + * @wpa_s: wpa_supplicant structure for a network interface + * Returns: The new network configuration or %NULL if operation failed + * + * This function performs the following operations: + * 1. Adds a new network. + * 2. Send network addition notification. + * 3. Marks the network disabled. + * 4. Set network default parameters. + */ +struct wpa_ssid * wpa_supplicant_add_network(struct wpa_supplicant *wpa_s) +{ + struct wpa_ssid *ssid; + + ssid = wpa_config_add_network(wpa_s->conf); + if (!ssid) + return NULL; + wpas_notify_network_added(wpa_s, ssid); + ssid->disabled = 1; + wpa_config_set_network_defaults(ssid); + + return ssid; +} + + +/** + * wpa_supplicant_remove_network - Remove a configured network based on id + * @wpa_s: wpa_supplicant structure for a network interface + * @id: Unique network id to search for + * Returns: 0 on success, or -1 if the network was not found, -2 if the network + * could not be removed + * + * This function performs the following operations: + * 1. Removes the network. + * 2. Send network removal notification. + * 3. Update internal state machines. + * 4. Stop any running sched scans. + */ +int wpa_supplicant_remove_network(struct wpa_supplicant *wpa_s, int id) +{ + struct wpa_ssid *ssid, *prev = wpa_s->current_ssid; + int was_disabled; + + ssid = wpa_config_get_network(wpa_s->conf, id); + if (!ssid) + return -1; + wpas_notify_network_removed(wpa_s, ssid); + + if (ssid == prev || !prev) { +#ifdef CONFIG_SME + wpa_s->sme.prev_bssid_set = 0; +#endif /* CONFIG_SME */ + /* + * Invalidate the EAP session cache if the current or + * previously used network is removed. + */ + eapol_sm_invalidate_cached_session(wpa_s->eapol); + } + + if (ssid == prev) { + wpa_sm_set_config(wpa_s->wpa, NULL); + eapol_sm_notify_config(wpa_s->eapol, NULL, NULL); + + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) + wpa_s->own_disconnect_req = 1; + wpa_supplicant_deauthenticate(wpa_s, + WLAN_REASON_DEAUTH_LEAVING); + } + + was_disabled = ssid->disabled; + + if (wpa_config_remove_network(wpa_s->conf, id) < 0) + return -2; + + if (!was_disabled && wpa_s->sched_scanning) { + wpa_printf(MSG_DEBUG, + "Stop ongoing sched_scan to remove network from filters"); + wpa_supplicant_cancel_sched_scan(wpa_s); + wpa_supplicant_req_scan(wpa_s, 0, 0); + } + + return 0; +} + + +/** + * wpa_supplicant_remove_all_networks - Remove all configured networks + * @wpa_s: wpa_supplicant structure for a network interface + * Returns: 0 on success (errors are currently ignored) + * + * This function performs the following operations: + * 1. Remove all networks. + * 2. Send network removal notifications. + * 3. Update internal state machines. + * 4. Stop any running sched scans. + */ +int wpa_supplicant_remove_all_networks(struct wpa_supplicant *wpa_s) +{ + struct wpa_ssid *ssid; + + if (wpa_s->drv_flags2 & + (WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA | + WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA)) + wpa_drv_flush_pmkid(wpa_s); + + if (wpa_s->sched_scanning) + wpa_supplicant_cancel_sched_scan(wpa_s); + + eapol_sm_invalidate_cached_session(wpa_s->eapol); + if (wpa_s->current_ssid) { +#ifdef CONFIG_SME + wpa_s->sme.prev_bssid_set = 0; +#endif /* CONFIG_SME */ + wpa_sm_set_config(wpa_s->wpa, NULL); + eapol_sm_notify_config(wpa_s->eapol, NULL, NULL); + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) + wpa_s->own_disconnect_req = 1; + wpa_supplicant_deauthenticate( + wpa_s, WLAN_REASON_DEAUTH_LEAVING); + } + ssid = wpa_s->conf->ssid; + while (ssid) { + struct wpa_ssid *remove_ssid = ssid; + int id; + + id = ssid->id; + ssid = ssid->next; + wpas_notify_network_removed(wpa_s, remove_ssid); + wpa_config_remove_network(wpa_s->conf, id); + } + return 0; +} + + +/** + * wpa_supplicant_enable_network - Mark a configured network as enabled + * @wpa_s: wpa_supplicant structure for a network interface + * @ssid: wpa_ssid structure for a configured network or %NULL + * + * Enables the specified network or all networks if no network specified. + */ +void wpa_supplicant_enable_network(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid) +{ + if (ssid == NULL) { + for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) + wpa_supplicant_enable_one_network(wpa_s, ssid); + } else + wpa_supplicant_enable_one_network(wpa_s, ssid); + + if (wpa_s->reassociate && !wpa_s->disconnected && + (!wpa_s->current_ssid || + wpa_s->wpa_state == WPA_DISCONNECTED || + wpa_s->wpa_state == WPA_SCANNING)) { + if (wpa_s->sched_scanning) { + wpa_printf(MSG_DEBUG, "Stop ongoing sched_scan to add " + "new network to scan filters"); + wpa_supplicant_cancel_sched_scan(wpa_s); + } + + if (wpa_supplicant_fast_associate(wpa_s) != 1) { + wpa_s->scan_req = NORMAL_SCAN_REQ; + wpa_supplicant_req_scan(wpa_s, 0, 0); + } + } +} + + +/** + * wpa_supplicant_disable_network - Mark a configured network as disabled + * @wpa_s: wpa_supplicant structure for a network interface + * @ssid: wpa_ssid structure for a configured network or %NULL + * + * Disables the specified network or all networks if no network specified. + */ +void wpa_supplicant_disable_network(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid) +{ + struct wpa_ssid *other_ssid; + int was_disabled; + + if (ssid == NULL) { + if (wpa_s->sched_scanning) + wpa_supplicant_cancel_sched_scan(wpa_s); + + for (other_ssid = wpa_s->conf->ssid; other_ssid; + other_ssid = other_ssid->next) { + was_disabled = other_ssid->disabled; + if (was_disabled == 2) + continue; /* do not change persistent P2P group + * data */ + + other_ssid->disabled = 1; + + if (was_disabled != other_ssid->disabled) + wpas_notify_network_enabled_changed( + wpa_s, other_ssid); + } + if (wpa_s->current_ssid) { + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) + wpa_s->own_disconnect_req = 1; + wpa_supplicant_deauthenticate( + wpa_s, WLAN_REASON_DEAUTH_LEAVING); + } + } else if (ssid->disabled != 2) { + if (ssid == wpa_s->current_ssid) { + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) + wpa_s->own_disconnect_req = 1; + wpa_supplicant_deauthenticate( + wpa_s, WLAN_REASON_DEAUTH_LEAVING); + } + + was_disabled = ssid->disabled; + + ssid->disabled = 1; + + if (was_disabled != ssid->disabled) { + wpas_notify_network_enabled_changed(wpa_s, ssid); + if (wpa_s->sched_scanning) { + wpa_printf(MSG_DEBUG, "Stop ongoing sched_scan " + "to remove network from filters"); + wpa_supplicant_cancel_sched_scan(wpa_s); + wpa_supplicant_req_scan(wpa_s, 0, 0); + } + } + } +} + + +/** + * wpa_supplicant_select_network - Attempt association with a network + * @wpa_s: wpa_supplicant structure for a network interface + * @ssid: wpa_ssid structure for a configured network or %NULL for any network + */ +void wpa_supplicant_select_network(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid) +{ + + struct wpa_ssid *other_ssid; + int disconnected = 0; + + if (ssid && ssid != wpa_s->current_ssid && wpa_s->current_ssid) { + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) + wpa_s->own_disconnect_req = 1; + wpa_supplicant_deauthenticate( + wpa_s, WLAN_REASON_DEAUTH_LEAVING); + disconnected = 1; + } + + if (ssid) + wpas_clear_temp_disabled(wpa_s, ssid, 1); + + /* + * Mark all other networks disabled or mark all networks enabled if no + * network specified. + */ + for (other_ssid = wpa_s->conf->ssid; other_ssid; + other_ssid = other_ssid->next) { + int was_disabled = other_ssid->disabled; + if (was_disabled == 2) + continue; /* do not change persistent P2P group data */ + + other_ssid->disabled = ssid ? (ssid->id != other_ssid->id) : 0; + if (was_disabled && !other_ssid->disabled) + wpas_clear_temp_disabled(wpa_s, other_ssid, 0); + + if (was_disabled != other_ssid->disabled) + wpas_notify_network_enabled_changed(wpa_s, other_ssid); + } + + if (ssid && ssid == wpa_s->current_ssid && wpa_s->current_ssid && + wpa_s->wpa_state >= WPA_AUTHENTICATING) { + /* We are already associated with the selected network */ + wpa_printf(MSG_DEBUG, "Already associated with the " + "selected network - do nothing"); + return; + } + + if (ssid) { + wpa_s->current_ssid = ssid; + eapol_sm_notify_config(wpa_s->eapol, NULL, NULL); + wpa_s->connect_without_scan = + (ssid->mode == WPAS_MODE_MESH || + ssid->mode == WPAS_MODE_AP) ? ssid : NULL; + + /* + * Don't optimize next scan freqs since a new ESS has been + * selected. + */ + os_free(wpa_s->next_scan_freqs); + wpa_s->next_scan_freqs = NULL; + } else { + wpa_s->connect_without_scan = NULL; + } + + wpa_s->disconnected = 0; + wpa_s->reassociate = 1; + wpa_s_clear_sae_rejected(wpa_s); + wpa_s->last_owe_group = 0; + if (ssid) { + ssid->owe_transition_bss_select_count = 0; + wpa_s_setup_sae_pt(wpa_s->conf, ssid, false); + } + + if (wpa_s->connect_without_scan || + wpa_supplicant_fast_associate(wpa_s) != 1) { + wpa_s->scan_req = NORMAL_SCAN_REQ; + wpas_scan_reset_sched_scan(wpa_s); + wpa_supplicant_req_scan(wpa_s, 0, disconnected ? 100000 : 0); + } + + if (ssid) + wpas_notify_network_selected(wpa_s, ssid); +} + + +/** + * wpas_remove_cred - Remove the specified credential and all the network + * entries created based on the removed credential + * @wpa_s: wpa_supplicant structure for a network interface + * @cred: The credential to remove + * Returns: 0 on success, -1 on failure + */ +int wpas_remove_cred(struct wpa_supplicant *wpa_s, struct wpa_cred *cred) +{ + struct wpa_ssid *ssid, *next; + int id; + + if (!cred) { + wpa_printf(MSG_DEBUG, "Could not find cred"); + return -1; + } + + id = cred->id; + if (wpa_config_remove_cred(wpa_s->conf, id) < 0) { + wpa_printf(MSG_DEBUG, "Could not find cred %d", id); + return -1; + } + + wpa_msg(wpa_s, MSG_INFO, CRED_REMOVED "%d", id); + + /* Remove any network entry created based on the removed credential */ + ssid = wpa_s->conf->ssid; + while (ssid) { + next = ssid->next; + + if (ssid->parent_cred == cred) { + wpa_printf(MSG_DEBUG, + "Remove network id %d since it used the removed credential", + ssid->id); + if (wpa_supplicant_remove_network(wpa_s, ssid->id) == + -1) { + wpa_printf(MSG_DEBUG, + "Could not find network id=%d", + ssid->id); + } + } + + ssid = next; + } + + return 0; +} + + +/** + * wpas_remove_cred - Remove all the Interworking credentials + * @wpa_s: wpa_supplicant structure for a network interface + * Returns: 0 on success, -1 on failure + */ +int wpas_remove_all_creds(struct wpa_supplicant *wpa_s) +{ + int res, ret = 0; + struct wpa_cred *cred, *prev; + + cred = wpa_s->conf->cred; + while (cred) { + prev = cred; + cred = cred->next; + res = wpas_remove_cred(wpa_s, prev); + if (res < 0) { + wpa_printf(MSG_DEBUG, + "Removal of all credentials failed - failed to remove credential id=%d", + prev->id); + ret = -1; + } + } + + return ret; +} + + +/** + * wpas_set_pkcs11_engine_and_module_path - Set PKCS #11 engine and module path + * @wpa_s: wpa_supplicant structure for a network interface + * @pkcs11_engine_path: PKCS #11 engine path or NULL + * @pkcs11_module_path: PKCS #11 module path or NULL + * Returns: 0 on success; -1 on failure + * + * Sets the PKCS #11 engine and module path. Both have to be NULL or a valid + * path. If resetting the EAPOL state machine with the new PKCS #11 engine and + * module path fails the paths will be reset to the default value (NULL). + */ +int wpas_set_pkcs11_engine_and_module_path(struct wpa_supplicant *wpa_s, + const char *pkcs11_engine_path, + const char *pkcs11_module_path) +{ + char *pkcs11_engine_path_copy = NULL; + char *pkcs11_module_path_copy = NULL; + + if (pkcs11_engine_path != NULL) { + pkcs11_engine_path_copy = os_strdup(pkcs11_engine_path); + if (pkcs11_engine_path_copy == NULL) + return -1; + } + if (pkcs11_module_path != NULL) { + pkcs11_module_path_copy = os_strdup(pkcs11_module_path); + if (pkcs11_module_path_copy == NULL) { + os_free(pkcs11_engine_path_copy); + return -1; + } + } + +#ifndef CONFIG_PKCS11_ENGINE_PATH + os_free(wpa_s->conf->pkcs11_engine_path); + wpa_s->conf->pkcs11_engine_path = pkcs11_engine_path_copy; +#endif /* CONFIG_PKCS11_ENGINE_PATH */ +#ifndef CONFIG_PKCS11_MODULE_PATH + os_free(wpa_s->conf->pkcs11_module_path); + wpa_s->conf->pkcs11_module_path = pkcs11_module_path_copy; +#endif /* CONFIG_PKCS11_MODULE_PATH */ + + wpa_sm_set_eapol(wpa_s->wpa, NULL); + eapol_sm_deinit(wpa_s->eapol); + wpa_s->eapol = NULL; + if (wpa_supplicant_init_eapol(wpa_s)) { + /* Error -> Reset paths to the default value (NULL) once. */ + if (pkcs11_engine_path != NULL && pkcs11_module_path != NULL) + wpas_set_pkcs11_engine_and_module_path(wpa_s, NULL, + NULL); + + return -1; + } + wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol); + + return 0; +} + + +/** + * wpa_supplicant_set_ap_scan - Set AP scan mode for interface + * @wpa_s: wpa_supplicant structure for a network interface + * @ap_scan: AP scan mode + * Returns: 0 if succeed or -1 if ap_scan has an invalid value + * + */ +int wpa_supplicant_set_ap_scan(struct wpa_supplicant *wpa_s, int ap_scan) +{ + + int old_ap_scan; + + if (ap_scan < 0 || ap_scan > 2) + return -1; + + if (ap_scan == 2 && os_strcmp(wpa_s->driver->name, "nl80211") == 0) { + wpa_printf(MSG_INFO, + "Note: nl80211 driver interface is not designed to be used with ap_scan=2; this can result in connection failures"); + } + +#ifdef ANDROID + if (ap_scan == 2 && ap_scan != wpa_s->conf->ap_scan && + wpa_s->wpa_state >= WPA_ASSOCIATING && + wpa_s->wpa_state < WPA_COMPLETED) { + wpa_printf(MSG_ERROR, "ap_scan = %d (%d) rejected while " + "associating", wpa_s->conf->ap_scan, ap_scan); + return 0; + } +#endif /* ANDROID */ + + old_ap_scan = wpa_s->conf->ap_scan; + wpa_s->conf->ap_scan = ap_scan; + + if (old_ap_scan != wpa_s->conf->ap_scan) + wpas_notify_ap_scan_changed(wpa_s); + + return 0; +} + + +/** + * wpa_supplicant_set_bss_expiration_age - Set BSS entry expiration age + * @wpa_s: wpa_supplicant structure for a network interface + * @expire_age: Expiration age in seconds + * Returns: 0 if succeed or -1 if expire_age has an invalid value + * + */ +int wpa_supplicant_set_bss_expiration_age(struct wpa_supplicant *wpa_s, + unsigned int bss_expire_age) +{ + if (bss_expire_age < 10) { + wpa_msg(wpa_s, MSG_ERROR, "Invalid bss expiration age %u", + bss_expire_age); + return -1; + } + wpa_msg(wpa_s, MSG_DEBUG, "Setting bss expiration age: %d sec", + bss_expire_age); + wpa_s->conf->bss_expiration_age = bss_expire_age; + + return 0; +} + + +/** + * wpa_supplicant_set_bss_expiration_count - Set BSS entry expiration scan count + * @wpa_s: wpa_supplicant structure for a network interface + * @expire_count: number of scans after which an unseen BSS is reclaimed + * Returns: 0 if succeed or -1 if expire_count has an invalid value + * + */ +int wpa_supplicant_set_bss_expiration_count(struct wpa_supplicant *wpa_s, + unsigned int bss_expire_count) +{ + if (bss_expire_count < 1) { + wpa_msg(wpa_s, MSG_ERROR, "Invalid bss expiration count %u", + bss_expire_count); + return -1; + } + wpa_msg(wpa_s, MSG_DEBUG, "Setting bss expiration scan count: %u", + bss_expire_count); + wpa_s->conf->bss_expiration_scan_count = bss_expire_count; + + return 0; +} + + +/** + * wpa_supplicant_set_scan_interval - Set scan interval + * @wpa_s: wpa_supplicant structure for a network interface + * @scan_interval: scan interval in seconds + * Returns: 0 if succeed or -1 if scan_interval has an invalid value + * + */ +int wpa_supplicant_set_scan_interval(struct wpa_supplicant *wpa_s, + int scan_interval) +{ + if (scan_interval < 0) { + wpa_msg(wpa_s, MSG_ERROR, "Invalid scan interval %d", + scan_interval); + return -1; + } + wpa_msg(wpa_s, MSG_DEBUG, "Setting scan interval: %d sec", + scan_interval); + wpa_supplicant_update_scan_int(wpa_s, scan_interval); + + return 0; +} + + +/** + * wpa_supplicant_set_debug_params - Set global debug params + * @global: wpa_global structure + * @debug_level: debug level + * @debug_timestamp: determines if show timestamp in debug data + * @debug_show_keys: determines if show keys in debug data + * Returns: 0 if succeed or -1 if debug_level has wrong value + */ +int wpa_supplicant_set_debug_params(struct wpa_global *global, int debug_level, + int debug_timestamp, int debug_show_keys) +{ + + int old_level, old_timestamp, old_show_keys; + + /* check for allowed debuglevels */ + if (debug_level != MSG_EXCESSIVE && + debug_level != MSG_MSGDUMP && + debug_level != MSG_DEBUG && + debug_level != MSG_INFO && + debug_level != MSG_WARNING && + debug_level != MSG_ERROR) + return -1; + + old_level = wpa_debug_level; + old_timestamp = wpa_debug_timestamp; + old_show_keys = wpa_debug_show_keys; + + wpa_debug_level = debug_level; + wpa_debug_timestamp = debug_timestamp ? 1 : 0; + wpa_debug_show_keys = debug_show_keys ? 1 : 0; + + if (wpa_debug_level != old_level) + wpas_notify_debug_level_changed(global); + if (wpa_debug_timestamp != old_timestamp) + wpas_notify_debug_timestamp_changed(global); + if (wpa_debug_show_keys != old_show_keys) + wpas_notify_debug_show_keys_changed(global); + + return 0; +} + + +#ifdef CONFIG_OWE +static int owe_trans_ssid_match(struct wpa_supplicant *wpa_s, const u8 *bssid, + const u8 *entry_ssid, size_t entry_ssid_len) +{ + const u8 *owe, *pos, *end; + u8 ssid_len; + struct wpa_bss *bss; + + /* Check network profile SSID aganst the SSID in the + * OWE Transition Mode element. */ + + bss = wpa_bss_get_bssid_latest(wpa_s, bssid); + if (!bss) + return 0; + + owe = wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE); + if (!owe) + return 0; + + pos = owe + 6; + end = owe + 2 + owe[1]; + + if (end - pos < ETH_ALEN + 1) + return 0; + pos += ETH_ALEN; + ssid_len = *pos++; + if (end - pos < ssid_len || ssid_len > SSID_MAX_LEN) + return 0; + + return entry_ssid_len == ssid_len && + os_memcmp(pos, entry_ssid, ssid_len) == 0; +} +#endif /* CONFIG_OWE */ + + +/** + * wpa_supplicant_get_ssid - Get a pointer to the current network structure + * @wpa_s: Pointer to wpa_supplicant data + * Returns: A pointer to the current network structure or %NULL on failure + */ +struct wpa_ssid * wpa_supplicant_get_ssid(struct wpa_supplicant *wpa_s) +{ + struct wpa_ssid *entry; + u8 ssid[SSID_MAX_LEN]; + int res; + size_t ssid_len; + u8 bssid[ETH_ALEN]; + int wired; + + res = wpa_drv_get_ssid(wpa_s, ssid); + if (res < 0) { + wpa_msg(wpa_s, MSG_WARNING, "Could not read SSID from " + "driver"); + return NULL; + } + ssid_len = res; + + if (wpa_drv_get_bssid(wpa_s, bssid) < 0) { + wpa_msg(wpa_s, MSG_WARNING, "Could not read BSSID from " + "driver"); + return NULL; + } + + wired = wpa_s->conf->ap_scan == 0 && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_WIRED); + + entry = wpa_s->conf->ssid; + while (entry) { + if (!wpas_network_disabled(wpa_s, entry) && + ((ssid_len == entry->ssid_len && + (!entry->ssid || + os_memcmp(ssid, entry->ssid, ssid_len) == 0)) || + wired) && + (!entry->bssid_set || + os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0)) + return entry; +#ifdef CONFIG_WPS + if (!wpas_network_disabled(wpa_s, entry) && + (entry->key_mgmt & WPA_KEY_MGMT_WPS) && + (entry->ssid == NULL || entry->ssid_len == 0) && + (!entry->bssid_set || + os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0)) + return entry; +#endif /* CONFIG_WPS */ + +#ifdef CONFIG_OWE + if (!wpas_network_disabled(wpa_s, entry) && + (entry->ssid && + owe_trans_ssid_match(wpa_s, bssid, entry->ssid, + entry->ssid_len)) && + (!entry->bssid_set || + os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0)) + return entry; +#endif /* CONFIG_OWE */ + + if (!wpas_network_disabled(wpa_s, entry) && entry->bssid_set && + entry->ssid_len == 0 && + os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0) + return entry; + + entry = entry->next; + } + + return NULL; +} + + +static int select_driver(struct wpa_supplicant *wpa_s, int i) +{ + struct wpa_global *global = wpa_s->global; + + if (wpa_drivers[i]->global_init && global->drv_priv[i] == NULL) { + global->drv_priv[i] = wpa_drivers[i]->global_init(global); + if (global->drv_priv[i] == NULL) { + wpa_printf(MSG_ERROR, "Failed to initialize driver " + "'%s'", wpa_drivers[i]->name); + return -1; + } + } + + wpa_s->driver = wpa_drivers[i]; + wpa_s->global_drv_priv = global->drv_priv[i]; + + return 0; +} + + +static int wpa_supplicant_set_driver(struct wpa_supplicant *wpa_s, + const char *name) +{ + int i; + size_t len; + const char *pos, *driver = name; + + if (wpa_s == NULL) + return -1; + + if (wpa_drivers[0] == NULL) { + wpa_msg(wpa_s, MSG_ERROR, "No driver interfaces build into " + "wpa_supplicant"); + return -1; + } + + if (name == NULL) { + /* Default to first successful driver in the list */ + for (i = 0; wpa_drivers[i]; i++) { + if (select_driver(wpa_s, i) == 0) + return 0; + } + /* Drivers have each reported failure, so no wpa_msg() here. */ + return -1; + } + + do { + pos = os_strchr(driver, ','); + if (pos) + len = pos - driver; + else + len = os_strlen(driver); + + for (i = 0; wpa_drivers[i]; i++) { + if (os_strlen(wpa_drivers[i]->name) == len && + os_strncmp(driver, wpa_drivers[i]->name, len) == + 0) { + /* First driver that succeeds wins */ + if (select_driver(wpa_s, i) == 0) + return 0; + } + } + + driver = pos + 1; + } while (pos); + + wpa_msg(wpa_s, MSG_ERROR, "Unsupported driver '%s'", name); + return -1; +} + + +/** + * wpa_supplicant_rx_eapol - Deliver a received EAPOL frame to wpa_supplicant + * @ctx: Context pointer (wpa_s); this is the ctx variable registered + * with struct wpa_driver_ops::init() + * @src_addr: Source address of the EAPOL frame + * @buf: EAPOL data starting from the EAPOL header (i.e., no Ethernet header) + * @len: Length of the EAPOL data + * @encrypted: Whether the frame was encrypted + * + * This function is called for each received EAPOL frame. Most driver + * interfaces rely on more generic OS mechanism for receiving frames through + * l2_packet, but if such a mechanism is not available, the driver wrapper may + * take care of received EAPOL frames and deliver them to the core supplicant + * code by calling this function. + */ +void wpa_supplicant_rx_eapol(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len, + enum frame_encryption encrypted) +{ + struct wpa_supplicant *wpa_s = ctx; + const u8 *connected_addr = wpa_s->valid_links ? + wpa_s->ap_mld_addr : wpa_s->bssid; + + wpa_dbg(wpa_s, MSG_DEBUG, "RX EAPOL from " MACSTR " (encrypted=%d)", + MAC2STR(src_addr), encrypted); + wpa_hexdump(MSG_MSGDUMP, "RX EAPOL", buf, len); + + if (wpa_s->own_disconnect_req) { + wpa_printf(MSG_DEBUG, + "Drop received EAPOL frame as we are disconnecting"); + return; + } + +#ifdef CONFIG_TESTING_OPTIONS + wpa_msg_ctrl(wpa_s, MSG_INFO, "EAPOL-RX " MACSTR " %zu", + MAC2STR(src_addr), len); + if (wpa_s->ignore_auth_resp) { + wpa_printf(MSG_INFO, "RX EAPOL - ignore_auth_resp active!"); + return; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + if (wpa_s->wpa_state < WPA_ASSOCIATED || + (wpa_s->last_eapol_matches_bssid && +#ifdef CONFIG_AP + !wpa_s->ap_iface && +#endif /* CONFIG_AP */ + os_memcmp(src_addr, connected_addr, ETH_ALEN) != 0)) { + /* + * There is possible race condition between receiving the + * association event and the EAPOL frame since they are coming + * through different paths from the driver. In order to avoid + * issues in trying to process the EAPOL frame before receiving + * association information, lets queue it for processing until + * the association event is received. This may also be needed in + * driver-based roaming case, so also use src_addr != BSSID as a + * trigger if we have previously confirmed that the + * Authenticator uses BSSID as the src_addr (which is not the + * case with wired IEEE 802.1X). + */ + wpa_dbg(wpa_s, MSG_DEBUG, + "Not associated - Delay processing of received EAPOL frame (state=%s connected_addr=" + MACSTR ")", + wpa_supplicant_state_txt(wpa_s->wpa_state), + MAC2STR(connected_addr)); + wpabuf_free(wpa_s->pending_eapol_rx); + wpa_s->pending_eapol_rx = wpabuf_alloc_copy(buf, len); + if (wpa_s->pending_eapol_rx) { + os_get_reltime(&wpa_s->pending_eapol_rx_time); + os_memcpy(wpa_s->pending_eapol_rx_src, src_addr, + ETH_ALEN); + wpa_s->pending_eapol_encrypted = encrypted; + } + return; + } + + wpa_s->last_eapol_matches_bssid = + os_memcmp(src_addr, connected_addr, ETH_ALEN) == 0; + +#ifdef CONFIG_AP + if (wpa_s->ap_iface) { + wpa_supplicant_ap_rx_eapol(wpa_s, src_addr, buf, len, + encrypted); + return; + } +#endif /* CONFIG_AP */ + + if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE) { + wpa_dbg(wpa_s, MSG_DEBUG, "Ignored received EAPOL frame since " + "no key management is configured"); + return; + } + + if (wpa_s->eapol_received == 0 && + (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_PSK) || + !wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) || + wpa_s->wpa_state != WPA_COMPLETED) && + (wpa_s->current_ssid == NULL || + wpa_s->current_ssid->mode != WPAS_MODE_IBSS)) { + /* Timeout for completing IEEE 802.1X and WPA authentication */ + int timeout = 10; + + if (wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt) || + wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA || + wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) { + /* Use longer timeout for IEEE 802.1X/EAP */ + timeout = 70; + } + +#ifdef CONFIG_WPS + if (wpa_s->current_ssid && wpa_s->current_bss && + (wpa_s->current_ssid->key_mgmt & WPA_KEY_MGMT_WPS) && + eap_is_wps_pin_enrollee(&wpa_s->current_ssid->eap)) { + /* + * Use shorter timeout if going through WPS AP iteration + * for PIN config method with an AP that does not + * advertise Selected Registrar. + */ + struct wpabuf *wps_ie; + + wps_ie = wpa_bss_get_vendor_ie_multi( + wpa_s->current_bss, WPS_IE_VENDOR_TYPE); + if (wps_ie && + !wps_is_addr_authorized(wps_ie, wpa_s->own_addr, 1)) + timeout = 10; + wpabuf_free(wps_ie); + } +#endif /* CONFIG_WPS */ + + wpa_supplicant_req_auth_timeout(wpa_s, timeout, 0); + } + wpa_s->eapol_received++; + + if (wpa_s->countermeasures) { + wpa_msg(wpa_s, MSG_INFO, "WPA: Countermeasures - dropped " + "EAPOL packet"); + return; + } + +#ifdef CONFIG_IBSS_RSN + if (wpa_s->current_ssid && + wpa_s->current_ssid->mode == WPAS_MODE_IBSS) { + ibss_rsn_rx_eapol(wpa_s->ibss_rsn, src_addr, buf, len, + encrypted); + return; + } +#endif /* CONFIG_IBSS_RSN */ + + /* Source address of the incoming EAPOL frame could be compared to the + * current BSSID. However, it is possible that a centralized + * Authenticator could be using another MAC address than the BSSID of + * an AP, so just allow any address to be used for now. The replies are + * still sent to the current BSSID (if available), though. */ + + os_memcpy(wpa_s->last_eapol_src, src_addr, ETH_ALEN); + if (!wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) && + wpa_s->key_mgmt != WPA_KEY_MGMT_OWE && + wpa_s->key_mgmt != WPA_KEY_MGMT_DPP && + eapol_sm_rx_eapol(wpa_s->eapol, src_addr, buf, len, + encrypted) > 0) + return; + wpa_drv_poll(wpa_s); + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_PSK)) + wpa_sm_rx_eapol(wpa_s->wpa, src_addr, buf, len, encrypted); + else if (wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt)) { + /* + * Set portValid = true here since we are going to skip 4-way + * handshake processing which would normally set portValid. We + * need this to allow the EAPOL state machines to be completed + * without going through EAPOL-Key handshake. + */ + eapol_sm_notify_portValid(wpa_s->eapol, true); + } +} + + +static void wpa_supplicant_rx_eapol_cb(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len) +{ + wpa_supplicant_rx_eapol(ctx, src_addr, buf, len, + FRAME_ENCRYPTION_UNKNOWN); +} + + +static int wpas_eapol_needs_l2_packet(struct wpa_supplicant *wpa_s) +{ + return !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_CONTROL_PORT) || + !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_CONTROL_PORT_RX); +} + + +int wpa_supplicant_update_mac_addr(struct wpa_supplicant *wpa_s) +{ + u8 prev_mac_addr[ETH_ALEN]; + + os_memcpy(prev_mac_addr, wpa_s->own_addr, ETH_ALEN); + + if ((!wpa_s->p2p_mgmt || + !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_DEDICATED_P2P_DEVICE)) && + !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_DEDICATED_INTERFACE)) { + l2_packet_deinit(wpa_s->l2); + wpa_s->l2 = l2_packet_init(wpa_s->ifname, + wpa_drv_get_mac_addr(wpa_s), + ETH_P_EAPOL, + wpas_eapol_needs_l2_packet(wpa_s) ? + wpa_supplicant_rx_eapol_cb : NULL, + wpa_s, 0); + if (wpa_s->l2 == NULL) + return -1; + + if (l2_packet_set_packet_filter(wpa_s->l2, + L2_PACKET_FILTER_PKTTYPE)) + wpa_dbg(wpa_s, MSG_DEBUG, + "Failed to attach pkt_type filter"); + + if (l2_packet_get_own_addr(wpa_s->l2, wpa_s->own_addr)) { + wpa_msg(wpa_s, MSG_ERROR, + "Failed to get own L2 address"); + return -1; + } + } else { + const u8 *addr = wpa_drv_get_mac_addr(wpa_s); + if (addr) + os_memcpy(wpa_s->own_addr, addr, ETH_ALEN); + } + + wpa_sm_set_own_addr(wpa_s->wpa, wpa_s->own_addr); + wpas_wps_update_mac_addr(wpa_s); + +#ifdef CONFIG_FST + if (wpa_s->fst) + fst_update_mac_addr(wpa_s->fst, wpa_s->own_addr); +#endif /* CONFIG_FST */ + + if (os_memcmp(prev_mac_addr, wpa_s->own_addr, ETH_ALEN) != 0) + wpas_notify_mac_address_changed(wpa_s); + + return 0; +} + + +static void wpa_supplicant_rx_eapol_bridge(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len) +{ + struct wpa_supplicant *wpa_s = ctx; + const struct l2_ethhdr *eth; + + if (len < sizeof(*eth)) + return; + eth = (const struct l2_ethhdr *) buf; + + if (os_memcmp(eth->h_dest, wpa_s->own_addr, ETH_ALEN) != 0 && + !(eth->h_dest[0] & 0x01)) { + wpa_dbg(wpa_s, MSG_DEBUG, "RX EAPOL from " MACSTR " to " MACSTR + " (bridge - not for this interface - ignore)", + MAC2STR(src_addr), MAC2STR(eth->h_dest)); + return; + } + + wpa_dbg(wpa_s, MSG_DEBUG, "RX EAPOL from " MACSTR " to " MACSTR + " (bridge)", MAC2STR(src_addr), MAC2STR(eth->h_dest)); + wpa_supplicant_rx_eapol(wpa_s, src_addr, buf + sizeof(*eth), + len - sizeof(*eth), FRAME_ENCRYPTION_UNKNOWN); +} + + +int wpa_supplicant_update_bridge_ifname(struct wpa_supplicant *wpa_s, + const char *bridge_ifname) +{ + if (wpa_s->wpa_state > WPA_SCANNING) + return -EBUSY; + + if (bridge_ifname && + os_strlen(bridge_ifname) >= sizeof(wpa_s->bridge_ifname)) + return -EINVAL; + + if (!bridge_ifname) + bridge_ifname = ""; + + if (os_strcmp(wpa_s->bridge_ifname, bridge_ifname) == 0) + return 0; + + if (wpa_s->l2_br) { + l2_packet_deinit(wpa_s->l2_br); + wpa_s->l2_br = NULL; + } + + os_strlcpy(wpa_s->bridge_ifname, bridge_ifname, + sizeof(wpa_s->bridge_ifname)); + + if (wpa_s->bridge_ifname[0]) { + wpa_dbg(wpa_s, MSG_DEBUG, + "Receiving packets from bridge interface '%s'", + wpa_s->bridge_ifname); + wpa_s->l2_br = l2_packet_init_bridge( + wpa_s->bridge_ifname, wpa_s->ifname, wpa_s->own_addr, + ETH_P_EAPOL, wpa_supplicant_rx_eapol_bridge, wpa_s, 1); + if (!wpa_s->l2_br) { + wpa_msg(wpa_s, MSG_ERROR, + "Failed to open l2_packet connection for the bridge interface '%s'", + wpa_s->bridge_ifname); + goto fail; + } + } + +#ifdef CONFIG_TDLS + if (!wpa_s->p2p_mgmt && wpa_tdls_init(wpa_s->wpa)) + goto fail; +#endif /* CONFIG_TDLS */ + + return 0; +fail: + wpa_s->bridge_ifname[0] = 0; + if (wpa_s->l2_br) { + l2_packet_deinit(wpa_s->l2_br); + wpa_s->l2_br = NULL; + } +#ifdef CONFIG_TDLS + if (!wpa_s->p2p_mgmt) + wpa_tdls_init(wpa_s->wpa); +#endif /* CONFIG_TDLS */ + return -EIO; +} + + +/** + * wpa_supplicant_driver_init - Initialize driver interface parameters + * @wpa_s: Pointer to wpa_supplicant data + * Returns: 0 on success, -1 on failure + * + * This function is called to initialize driver interface parameters. + * wpa_drv_init() must have been called before this function to initialize the + * driver interface. + */ +int wpa_supplicant_driver_init(struct wpa_supplicant *wpa_s) +{ + static int interface_count = 0; + + if (wpa_supplicant_update_mac_addr(wpa_s) < 0) + return -1; + + wpa_dbg(wpa_s, MSG_DEBUG, "Own MAC address: " MACSTR, + MAC2STR(wpa_s->own_addr)); + os_memcpy(wpa_s->perm_addr, wpa_s->own_addr, ETH_ALEN); + wpa_sm_set_own_addr(wpa_s->wpa, wpa_s->own_addr); + + if (wpa_s->bridge_ifname[0] && wpas_eapol_needs_l2_packet(wpa_s)) { + wpa_dbg(wpa_s, MSG_DEBUG, "Receiving packets from bridge " + "interface '%s'", wpa_s->bridge_ifname); + wpa_s->l2_br = l2_packet_init_bridge( + wpa_s->bridge_ifname, wpa_s->ifname, wpa_s->own_addr, + ETH_P_EAPOL, wpa_supplicant_rx_eapol_bridge, wpa_s, 1); + if (wpa_s->l2_br == NULL) { + wpa_msg(wpa_s, MSG_ERROR, "Failed to open l2_packet " + "connection for the bridge interface '%s'", + wpa_s->bridge_ifname); + return -1; + } + } + + if (wpa_s->conf->ap_scan == 2 && + os_strcmp(wpa_s->driver->name, "nl80211") == 0) { + wpa_printf(MSG_INFO, + "Note: nl80211 driver interface is not designed to be used with ap_scan=2; this can result in connection failures"); + } + + wpa_clear_keys(wpa_s, NULL); + + /* Make sure that TKIP countermeasures are not left enabled (could + * happen if wpa_supplicant is killed during countermeasures. */ + wpa_drv_set_countermeasures(wpa_s, 0); + + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: flushing PMKID list in the driver"); + wpa_drv_flush_pmkid(wpa_s); + + wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN; + wpa_s->prev_scan_wildcard = 0; + + if (wpa_supplicant_enabled_networks(wpa_s)) { + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); + interface_count = 0; + } +#ifndef CONFIG_CTRL_IFACE_AIDL + if (!wpa_s->p2p_mgmt && + wpa_supplicant_delayed_sched_scan(wpa_s, + interface_count % 3, + 100000)) + wpa_supplicant_req_scan(wpa_s, interface_count % 3, + 100000); +#endif /* CONFIG_CTRL_IFACE_AIDL */ + interface_count++; + } else + wpa_supplicant_set_state(wpa_s, WPA_INACTIVE); + + return 0; +} + + +static int wpa_supplicant_daemon(const char *pid_file) +{ + wpa_printf(MSG_DEBUG, "Daemonize.."); + return os_daemonize(pid_file); +} + + +static struct wpa_supplicant * +wpa_supplicant_alloc(struct wpa_supplicant *parent) +{ + struct wpa_supplicant *wpa_s; + + wpa_s = os_zalloc(sizeof(*wpa_s)); + if (wpa_s == NULL) + return NULL; + wpa_s->scan_req = INITIAL_SCAN_REQ; + wpa_s->scan_interval = 5; + wpa_s->new_connection = 1; + wpa_s->parent = parent ? parent : wpa_s; + wpa_s->p2pdev = wpa_s->parent; + wpa_s->sched_scanning = 0; + wpa_s->setband_mask = WPA_SETBAND_AUTO; + + dl_list_init(&wpa_s->bss_tmp_disallowed); + dl_list_init(&wpa_s->fils_hlp_req); +#ifdef CONFIG_TESTING_OPTIONS + dl_list_init(&wpa_s->drv_signal_override); +#endif /* CONFIG_TESTING_OPTIONS */ + dl_list_init(&wpa_s->active_scs_ids); + wpa_s->ml_probe_mld_id = -1; + + return wpa_s; +} + + +#ifdef CONFIG_HT_OVERRIDES + +static int wpa_set_htcap_mcs(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + const char *ht_mcs) +{ + /* parse ht_mcs into hex array */ + int i; + const char *tmp = ht_mcs; + char *end = NULL; + + /* If ht_mcs is null, do not set anything */ + if (!ht_mcs) + return 0; + + /* This is what we are setting in the kernel */ + os_memset(&htcaps->supported_mcs_set, 0, IEEE80211_HT_MCS_MASK_LEN); + + wpa_msg(wpa_s, MSG_DEBUG, "set_htcap, ht_mcs -:%s:-", ht_mcs); + + for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) { + long v; + + errno = 0; + v = strtol(tmp, &end, 16); + + if (errno == 0) { + wpa_msg(wpa_s, MSG_DEBUG, + "htcap value[%i]: %ld end: %p tmp: %p", + i, v, end, tmp); + if (end == tmp) + break; + + htcaps->supported_mcs_set[i] = v; + tmp = end; + } else { + wpa_msg(wpa_s, MSG_ERROR, + "Failed to parse ht-mcs: %s, error: %s\n", + ht_mcs, strerror(errno)); + return -1; + } + } + + /* + * If we were able to parse any values, then set mask for the MCS set. + */ + if (i) { + os_memset(&htcaps_mask->supported_mcs_set, 0xff, + IEEE80211_HT_MCS_MASK_LEN - 1); + /* skip the 3 reserved bits */ + htcaps_mask->supported_mcs_set[IEEE80211_HT_MCS_MASK_LEN - 1] = + 0x1f; + } + + return 0; +} + + +static int wpa_disable_max_amsdu(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int disabled) +{ + le16 msk; + + if (disabled == -1) + return 0; + + wpa_msg(wpa_s, MSG_DEBUG, "set_disable_max_amsdu: %d", disabled); + + msk = host_to_le16(HT_CAP_INFO_MAX_AMSDU_SIZE); + htcaps_mask->ht_capabilities_info |= msk; + if (disabled) + htcaps->ht_capabilities_info &= msk; + else + htcaps->ht_capabilities_info |= msk; + + return 0; +} + + +static int wpa_set_ampdu_factor(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int factor) +{ + if (factor == -1) + return 0; + + wpa_msg(wpa_s, MSG_DEBUG, "set_ampdu_factor: %d", factor); + + if (factor < 0 || factor > 3) { + wpa_msg(wpa_s, MSG_ERROR, "ampdu_factor: %d out of range. " + "Must be 0-3 or -1", factor); + return -EINVAL; + } + + htcaps_mask->a_mpdu_params |= 0x3; /* 2 bits for factor */ + htcaps->a_mpdu_params &= ~0x3; + htcaps->a_mpdu_params |= factor & 0x3; + + return 0; +} + + +static int wpa_set_ampdu_density(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int density) +{ + if (density == -1) + return 0; + + wpa_msg(wpa_s, MSG_DEBUG, "set_ampdu_density: %d", density); + + if (density < 0 || density > 7) { + wpa_msg(wpa_s, MSG_ERROR, + "ampdu_density: %d out of range. Must be 0-7 or -1.", + density); + return -EINVAL; + } + + htcaps_mask->a_mpdu_params |= 0x1C; + htcaps->a_mpdu_params &= ~(0x1C); + htcaps->a_mpdu_params |= (density << 2) & 0x1C; + + return 0; +} + + +static int wpa_set_disable_ht40(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int disabled) +{ + if (disabled) + wpa_msg(wpa_s, MSG_DEBUG, "set_disable_ht40: %d", disabled); + + set_disable_ht40(htcaps, disabled); + set_disable_ht40(htcaps_mask, 0); + + return 0; +} + + +static int wpa_set_disable_sgi(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int disabled) +{ + /* Masking these out disables SGI */ + le16 msk = host_to_le16(HT_CAP_INFO_SHORT_GI20MHZ | + HT_CAP_INFO_SHORT_GI40MHZ); + + if (disabled) + wpa_msg(wpa_s, MSG_DEBUG, "set_disable_sgi: %d", disabled); + + if (disabled) + htcaps->ht_capabilities_info &= ~msk; + else + htcaps->ht_capabilities_info |= msk; + + htcaps_mask->ht_capabilities_info |= msk; + + return 0; +} + + +static int wpa_set_disable_ldpc(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int disabled) +{ + /* Masking these out disables LDPC */ + le16 msk = host_to_le16(HT_CAP_INFO_LDPC_CODING_CAP); + + if (disabled) + wpa_msg(wpa_s, MSG_DEBUG, "set_disable_ldpc: %d", disabled); + + if (disabled) + htcaps->ht_capabilities_info &= ~msk; + else + htcaps->ht_capabilities_info |= msk; + + htcaps_mask->ht_capabilities_info |= msk; + + return 0; +} + + +static int wpa_set_tx_stbc(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int tx_stbc) +{ + le16 msk = host_to_le16(HT_CAP_INFO_TX_STBC); + + if (tx_stbc == -1) + return 0; + + wpa_msg(wpa_s, MSG_DEBUG, "set_tx_stbc: %d", tx_stbc); + + if (tx_stbc < 0 || tx_stbc > 1) { + wpa_msg(wpa_s, MSG_ERROR, + "tx_stbc: %d out of range. Must be 0-1 or -1", tx_stbc); + return -EINVAL; + } + + htcaps_mask->ht_capabilities_info |= msk; + htcaps->ht_capabilities_info &= ~msk; + htcaps->ht_capabilities_info |= (tx_stbc << 7) & msk; + + return 0; +} + + +static int wpa_set_rx_stbc(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int rx_stbc) +{ + le16 msk = host_to_le16(HT_CAP_INFO_RX_STBC_MASK); + + if (rx_stbc == -1) + return 0; + + wpa_msg(wpa_s, MSG_DEBUG, "set_rx_stbc: %d", rx_stbc); + + if (rx_stbc < 0 || rx_stbc > 3) { + wpa_msg(wpa_s, MSG_ERROR, + "rx_stbc: %d out of range. Must be 0-3 or -1", rx_stbc); + return -EINVAL; + } + + htcaps_mask->ht_capabilities_info |= msk; + htcaps->ht_capabilities_info &= ~msk; + htcaps->ht_capabilities_info |= (rx_stbc << 8) & msk; + + return 0; +} + + +void wpa_supplicant_apply_ht_overrides( + struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, + struct wpa_driver_associate_params *params) +{ + struct ieee80211_ht_capabilities *htcaps; + struct ieee80211_ht_capabilities *htcaps_mask; + + if (!ssid) + return; + + params->disable_ht = ssid->disable_ht; + if (!params->htcaps || !params->htcaps_mask) + return; + + htcaps = (struct ieee80211_ht_capabilities *) params->htcaps; + htcaps_mask = (struct ieee80211_ht_capabilities *) params->htcaps_mask; + wpa_set_htcap_mcs(wpa_s, htcaps, htcaps_mask, ssid->ht_mcs); + wpa_disable_max_amsdu(wpa_s, htcaps, htcaps_mask, + ssid->disable_max_amsdu); + wpa_set_ampdu_factor(wpa_s, htcaps, htcaps_mask, ssid->ampdu_factor); + wpa_set_ampdu_density(wpa_s, htcaps, htcaps_mask, ssid->ampdu_density); + wpa_set_disable_ht40(wpa_s, htcaps, htcaps_mask, ssid->disable_ht40); + wpa_set_disable_sgi(wpa_s, htcaps, htcaps_mask, ssid->disable_sgi); + wpa_set_disable_ldpc(wpa_s, htcaps, htcaps_mask, ssid->disable_ldpc); + wpa_set_rx_stbc(wpa_s, htcaps, htcaps_mask, ssid->rx_stbc); + wpa_set_tx_stbc(wpa_s, htcaps, htcaps_mask, ssid->tx_stbc); + + if (ssid->ht40_intolerant) { + le16 bit = host_to_le16(HT_CAP_INFO_40MHZ_INTOLERANT); + htcaps->ht_capabilities_info |= bit; + htcaps_mask->ht_capabilities_info |= bit; + } +} + +#endif /* CONFIG_HT_OVERRIDES */ + + +#ifdef CONFIG_VHT_OVERRIDES +void wpa_supplicant_apply_vht_overrides( + struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, + struct wpa_driver_associate_params *params) +{ + struct ieee80211_vht_capabilities *vhtcaps; + struct ieee80211_vht_capabilities *vhtcaps_mask; + + if (!ssid) + return; + + params->disable_vht = ssid->disable_vht; + + vhtcaps = (void *) params->vhtcaps; + vhtcaps_mask = (void *) params->vhtcaps_mask; + + if (!vhtcaps || !vhtcaps_mask) + return; + + vhtcaps->vht_capabilities_info = host_to_le32(ssid->vht_capa); + vhtcaps_mask->vht_capabilities_info = host_to_le32(ssid->vht_capa_mask); + +#ifdef CONFIG_HT_OVERRIDES + if (ssid->disable_sgi) { + vhtcaps_mask->vht_capabilities_info |= (VHT_CAP_SHORT_GI_80 | + VHT_CAP_SHORT_GI_160); + vhtcaps->vht_capabilities_info &= ~(VHT_CAP_SHORT_GI_80 | + VHT_CAP_SHORT_GI_160); + wpa_msg(wpa_s, MSG_DEBUG, + "disable-sgi override specified, vht-caps: 0x%x", + vhtcaps->vht_capabilities_info); + } + + /* if max ampdu is <= 3, we have to make the HT cap the same */ + if (ssid->vht_capa_mask & VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX) { + int max_ampdu; + + max_ampdu = (ssid->vht_capa & + VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX) >> + VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX_SHIFT; + + max_ampdu = max_ampdu < 3 ? max_ampdu : 3; + wpa_set_ampdu_factor(wpa_s, + (void *) params->htcaps, + (void *) params->htcaps_mask, + max_ampdu); + } +#endif /* CONFIG_HT_OVERRIDES */ + +#define OVERRIDE_MCS(i) \ + if (ssid->vht_tx_mcs_nss_ ##i >= 0) { \ + vhtcaps_mask->vht_supported_mcs_set.tx_map |= \ + host_to_le16(3 << 2 * (i - 1)); \ + vhtcaps->vht_supported_mcs_set.tx_map |= \ + host_to_le16(ssid->vht_tx_mcs_nss_ ##i << \ + 2 * (i - 1)); \ + } \ + if (ssid->vht_rx_mcs_nss_ ##i >= 0) { \ + vhtcaps_mask->vht_supported_mcs_set.rx_map |= \ + host_to_le16(3 << 2 * (i - 1)); \ + vhtcaps->vht_supported_mcs_set.rx_map |= \ + host_to_le16(ssid->vht_rx_mcs_nss_ ##i << \ + 2 * (i - 1)); \ + } + + OVERRIDE_MCS(1); + OVERRIDE_MCS(2); + OVERRIDE_MCS(3); + OVERRIDE_MCS(4); + OVERRIDE_MCS(5); + OVERRIDE_MCS(6); + OVERRIDE_MCS(7); + OVERRIDE_MCS(8); +} +#endif /* CONFIG_VHT_OVERRIDES */ + + +#ifdef CONFIG_HE_OVERRIDES +void wpa_supplicant_apply_he_overrides( + struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, + struct wpa_driver_associate_params *params) +{ + if (!ssid) + return; + + params->disable_he = ssid->disable_he; +} +#endif /* CONFIG_HE_OVERRIDES */ + + +void wpa_supplicant_apply_eht_overrides( + struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, + struct wpa_driver_associate_params *params) +{ + if (!ssid) + return; + + params->disable_eht = ssid->disable_eht; +} + + +static int pcsc_reader_init(struct wpa_supplicant *wpa_s) +{ +#ifdef PCSC_FUNCS + size_t len; + + if (!wpa_s->conf->pcsc_reader) + return 0; + + wpa_s->scard = scard_init(wpa_s->conf->pcsc_reader); + if (!wpa_s->scard) + return 1; + + if (wpa_s->conf->pcsc_pin && + scard_set_pin(wpa_s->scard, wpa_s->conf->pcsc_pin) < 0) { + scard_deinit(wpa_s->scard); + wpa_s->scard = NULL; + wpa_msg(wpa_s, MSG_ERROR, "PC/SC PIN validation failed"); + return -1; + } + + len = sizeof(wpa_s->imsi) - 1; + if (scard_get_imsi(wpa_s->scard, wpa_s->imsi, &len)) { + scard_deinit(wpa_s->scard); + wpa_s->scard = NULL; + wpa_msg(wpa_s, MSG_ERROR, "Could not read IMSI"); + return -1; + } + wpa_s->imsi[len] = '\0'; + + wpa_s->mnc_len = scard_get_mnc_len(wpa_s->scard); + + wpa_printf(MSG_DEBUG, "SCARD: IMSI %s (MNC length %d)", + wpa_s->imsi, wpa_s->mnc_len); + + wpa_sm_set_scard_ctx(wpa_s->wpa, wpa_s->scard); + eapol_sm_register_scard_ctx(wpa_s->eapol, wpa_s->scard); +#endif /* PCSC_FUNCS */ + + return 0; +} + + +int wpas_init_ext_pw(struct wpa_supplicant *wpa_s) +{ + char *val, *pos; + + ext_password_deinit(wpa_s->ext_pw); + wpa_s->ext_pw = NULL; + eapol_sm_set_ext_pw_ctx(wpa_s->eapol, NULL); + + if (!wpa_s->conf->ext_password_backend) + return 0; + + val = os_strdup(wpa_s->conf->ext_password_backend); + if (val == NULL) + return -1; + pos = os_strchr(val, ':'); + if (pos) + *pos++ = '\0'; + + wpa_printf(MSG_DEBUG, "EXT PW: Initialize backend '%s'", val); + + wpa_s->ext_pw = ext_password_init(val, pos); + os_free(val); + if (wpa_s->ext_pw == NULL) { + wpa_printf(MSG_DEBUG, "EXT PW: Failed to initialize backend"); + return -1; + } + eapol_sm_set_ext_pw_ctx(wpa_s->eapol, wpa_s->ext_pw); + + return 0; +} + + +#ifdef CONFIG_FST + +static const u8 * wpas_fst_get_bssid_cb(void *ctx) +{ + struct wpa_supplicant *wpa_s = ctx; + + return (is_zero_ether_addr(wpa_s->bssid) || + wpa_s->wpa_state != WPA_COMPLETED) ? NULL : wpa_s->bssid; +} + + +static void wpas_fst_get_channel_info_cb(void *ctx, + enum hostapd_hw_mode *hw_mode, + u8 *channel) +{ + struct wpa_supplicant *wpa_s = ctx; + + if (wpa_s->current_bss) { + *hw_mode = ieee80211_freq_to_chan(wpa_s->current_bss->freq, + channel); + } else if (wpa_s->hw.num_modes) { + *hw_mode = wpa_s->hw.modes[0].mode; + } else { + WPA_ASSERT(0); + *hw_mode = 0; + } +} + + +static int wpas_fst_get_hw_modes(void *ctx, struct hostapd_hw_modes **modes) +{ + struct wpa_supplicant *wpa_s = ctx; + + *modes = wpa_s->hw.modes; + return wpa_s->hw.num_modes; +} + + +static void wpas_fst_set_ies_cb(void *ctx, const struct wpabuf *fst_ies) +{ + struct wpa_supplicant *wpa_s = ctx; + + wpa_hexdump_buf(MSG_DEBUG, "FST: Set IEs", fst_ies); + wpa_s->fst_ies = fst_ies; +} + + +static int wpas_fst_send_action_cb(void *ctx, const u8 *da, struct wpabuf *data) +{ + struct wpa_supplicant *wpa_s = ctx; + + if (os_memcmp(wpa_s->bssid, da, ETH_ALEN) != 0) { + wpa_printf(MSG_INFO, "FST:%s:bssid=" MACSTR " != da=" MACSTR, + __func__, MAC2STR(wpa_s->bssid), MAC2STR(da)); + return -1; + } + return wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, + wpa_s->own_addr, wpa_s->bssid, + wpabuf_head(data), wpabuf_len(data), + 0); +} + + +static const struct wpabuf * wpas_fst_get_mb_ie_cb(void *ctx, const u8 *addr) +{ + struct wpa_supplicant *wpa_s = ctx; + + WPA_ASSERT(os_memcmp(wpa_s->bssid, addr, ETH_ALEN) == 0); + return wpa_s->received_mb_ies; +} + + +static void wpas_fst_update_mb_ie_cb(void *ctx, const u8 *addr, + const u8 *buf, size_t size) +{ + struct wpa_supplicant *wpa_s = ctx; + struct mb_ies_info info; + + WPA_ASSERT(os_memcmp(wpa_s->bssid, addr, ETH_ALEN) == 0); + + if (!mb_ies_info_by_ies(&info, buf, size)) { + wpabuf_free(wpa_s->received_mb_ies); + wpa_s->received_mb_ies = mb_ies_by_info(&info); + } +} + + +static const u8 * wpas_fst_get_peer_first(void *ctx, + struct fst_get_peer_ctx **get_ctx, + bool mb_only) +{ + struct wpa_supplicant *wpa_s = ctx; + + *get_ctx = NULL; + if (!is_zero_ether_addr(wpa_s->bssid)) + return (wpa_s->received_mb_ies || !mb_only) ? + wpa_s->bssid : NULL; + return NULL; +} + + +static const u8 * wpas_fst_get_peer_next(void *ctx, + struct fst_get_peer_ctx **get_ctx, + bool mb_only) +{ + return NULL; +} + +void fst_wpa_supplicant_fill_iface_obj(struct wpa_supplicant *wpa_s, + struct fst_wpa_obj *iface_obj) +{ + os_memset(iface_obj, 0, sizeof(*iface_obj)); + iface_obj->ctx = wpa_s; + iface_obj->get_bssid = wpas_fst_get_bssid_cb; + iface_obj->get_channel_info = wpas_fst_get_channel_info_cb; + iface_obj->get_hw_modes = wpas_fst_get_hw_modes; + iface_obj->set_ies = wpas_fst_set_ies_cb; + iface_obj->send_action = wpas_fst_send_action_cb; + iface_obj->get_mb_ie = wpas_fst_get_mb_ie_cb; + iface_obj->update_mb_ie = wpas_fst_update_mb_ie_cb; + iface_obj->get_peer_first = wpas_fst_get_peer_first; + iface_obj->get_peer_next = wpas_fst_get_peer_next; +} +#endif /* CONFIG_FST */ + +static int wpas_set_wowlan_triggers(struct wpa_supplicant *wpa_s, + const struct wpa_driver_capa *capa) +{ + struct wowlan_triggers *triggers; + int ret = 0; + + if (!wpa_s->conf->wowlan_triggers) + return 0; + + triggers = wpa_get_wowlan_triggers(wpa_s->conf->wowlan_triggers, capa); + if (triggers) { + ret = wpa_drv_wowlan(wpa_s, triggers); + os_free(triggers); + } + return ret; +} + + +enum wpa_radio_work_band wpas_freq_to_band(int freq) +{ + if (freq < 3000) + return BAND_2_4_GHZ; + if (freq > 50000) + return BAND_60_GHZ; + return BAND_5_GHZ; +} + + +unsigned int wpas_get_bands(struct wpa_supplicant *wpa_s, const int *freqs) +{ + int i; + unsigned int band = 0; + + if (freqs) { + /* freqs are specified for the radio work */ + for (i = 0; freqs[i]; i++) + band |= wpas_freq_to_band(freqs[i]); + } else { + /* + * freqs are not specified, implies all + * the supported freqs by HW + */ + for (i = 0; i < wpa_s->hw.num_modes; i++) { + if (wpa_s->hw.modes[i].num_channels != 0) { + if (wpa_s->hw.modes[i].mode == + HOSTAPD_MODE_IEEE80211B || + wpa_s->hw.modes[i].mode == + HOSTAPD_MODE_IEEE80211G) + band |= BAND_2_4_GHZ; + else if (wpa_s->hw.modes[i].mode == + HOSTAPD_MODE_IEEE80211A) + band |= BAND_5_GHZ; + else if (wpa_s->hw.modes[i].mode == + HOSTAPD_MODE_IEEE80211AD) + band |= BAND_60_GHZ; + else if (wpa_s->hw.modes[i].mode == + HOSTAPD_MODE_IEEE80211ANY) + band = BAND_2_4_GHZ | BAND_5_GHZ | + BAND_60_GHZ; + } + } + } + + return band; +} + + +static struct wpa_radio * radio_add_interface(struct wpa_supplicant *wpa_s, + const char *rn) +{ + struct wpa_supplicant *iface = wpa_s->global->ifaces; + struct wpa_radio *radio; + + while (rn && iface) { + radio = iface->radio; + if (radio && os_strcmp(rn, radio->name) == 0) { + wpa_printf(MSG_DEBUG, "Add interface %s to existing radio %s", + wpa_s->ifname, rn); + dl_list_add(&radio->ifaces, &wpa_s->radio_list); + return radio; + } + + iface = iface->next; + } + + wpa_printf(MSG_DEBUG, "Add interface %s to a new radio %s", + wpa_s->ifname, rn ? rn : "N/A"); + radio = os_zalloc(sizeof(*radio)); + if (radio == NULL) + return NULL; + + if (rn) + os_strlcpy(radio->name, rn, sizeof(radio->name)); + dl_list_init(&radio->ifaces); + dl_list_init(&radio->work); + dl_list_add(&radio->ifaces, &wpa_s->radio_list); + + return radio; +} + + +static void radio_work_free(struct wpa_radio_work *work) +{ + if (work->wpa_s->scan_work == work) { + /* This should not really happen. */ + wpa_dbg(work->wpa_s, MSG_INFO, "Freeing radio work '%s'@%p (started=%d) that is marked as scan_work", + work->type, work, work->started); + work->wpa_s->scan_work = NULL; + } + +#ifdef CONFIG_P2P + if (work->wpa_s->p2p_scan_work == work) { + /* This should not really happen. */ + wpa_dbg(work->wpa_s, MSG_INFO, "Freeing radio work '%s'@%p (started=%d) that is marked as p2p_scan_work", + work->type, work, work->started); + work->wpa_s->p2p_scan_work = NULL; + } +#endif /* CONFIG_P2P */ + + if (work->started) { + work->wpa_s->radio->num_active_works--; + wpa_dbg(work->wpa_s, MSG_DEBUG, + "radio_work_free('%s'@%p): num_active_works --> %u", + work->type, work, + work->wpa_s->radio->num_active_works); + } + + dl_list_del(&work->list); + os_free(work); +} + + +static int radio_work_is_connect(struct wpa_radio_work *work) +{ + return os_strcmp(work->type, "sme-connect") == 0 || + os_strcmp(work->type, "connect") == 0; +} + + +static int radio_work_is_scan(struct wpa_radio_work *work) +{ + return os_strcmp(work->type, "scan") == 0 || + os_strcmp(work->type, "p2p-scan") == 0; +} + + +static struct wpa_radio_work * radio_work_get_next_work(struct wpa_radio *radio) +{ + struct wpa_radio_work *active_work = NULL; + struct wpa_radio_work *tmp; + + /* Get the active work to know the type and band. */ + dl_list_for_each(tmp, &radio->work, struct wpa_radio_work, list) { + if (tmp->started) { + active_work = tmp; + break; + } + } + + if (!active_work) { + /* No active work, start one */ + radio->num_active_works = 0; + dl_list_for_each(tmp, &radio->work, struct wpa_radio_work, + list) { + if (os_strcmp(tmp->type, "scan") == 0 && + external_scan_running(radio) && + (((struct wpa_driver_scan_params *) + tmp->ctx)->only_new_results || + tmp->wpa_s->clear_driver_scan_cache)) + continue; + return tmp; + } + return NULL; + } + + if (radio_work_is_connect(active_work)) { + /* + * If the active work is either connect or sme-connect, + * do not parallelize them with other radio works. + */ + wpa_dbg(active_work->wpa_s, MSG_DEBUG, + "Do not parallelize radio work with %s", + active_work->type); + return NULL; + } + + dl_list_for_each(tmp, &radio->work, struct wpa_radio_work, list) { + if (tmp->started) + continue; + + /* + * If connect or sme-connect are enqueued, parallelize only + * those operations ahead of them in the queue. + */ + if (radio_work_is_connect(tmp)) + break; + + /* Serialize parallel scan and p2p_scan operations on the same + * interface since the driver_nl80211 mechanism for tracking + * scan cookies does not yet have support for this. */ + if (active_work->wpa_s == tmp->wpa_s && + radio_work_is_scan(active_work) && + radio_work_is_scan(tmp)) { + wpa_dbg(active_work->wpa_s, MSG_DEBUG, + "Do not start work '%s' when another work '%s' is already scheduled", + tmp->type, active_work->type); + continue; + } + /* + * Check that the radio works are distinct and + * on different bands. + */ + if (os_strcmp(active_work->type, tmp->type) != 0 && + (active_work->bands != tmp->bands)) { + /* + * If a scan has to be scheduled through nl80211 scan + * interface and if an external scan is already running, + * do not schedule the scan since it is likely to get + * rejected by kernel. + */ + if (os_strcmp(tmp->type, "scan") == 0 && + external_scan_running(radio) && + (((struct wpa_driver_scan_params *) + tmp->ctx)->only_new_results || + tmp->wpa_s->clear_driver_scan_cache)) + continue; + + wpa_dbg(active_work->wpa_s, MSG_DEBUG, + "active_work:%s new_work:%s", + active_work->type, tmp->type); + return tmp; + } + } + + /* Did not find a radio work to schedule in parallel. */ + return NULL; +} + + +static void radio_start_next_work(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_radio *radio = eloop_ctx; + struct wpa_radio_work *work; + struct os_reltime now, diff; + struct wpa_supplicant *wpa_s; + + work = dl_list_first(&radio->work, struct wpa_radio_work, list); + if (work == NULL) { + radio->num_active_works = 0; + return; + } + + wpa_s = dl_list_first(&radio->ifaces, struct wpa_supplicant, + radio_list); + + if (!(wpa_s && + wpa_s->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS)) { + if (work->started) + return; /* already started and still in progress */ + + if (wpa_s && external_scan_running(wpa_s->radio)) { + wpa_printf(MSG_DEBUG, "Delay radio work start until externally triggered scan completes"); + return; + } + } else { + work = NULL; + if (radio->num_active_works < MAX_ACTIVE_WORKS) { + /* get the work to schedule next */ + work = radio_work_get_next_work(radio); + } + if (!work) + return; + } + + wpa_s = work->wpa_s; + os_get_reltime(&now); + os_reltime_sub(&now, &work->time, &diff); + wpa_dbg(wpa_s, MSG_DEBUG, + "Starting radio work '%s'@%p after %ld.%06ld second wait", + work->type, work, diff.sec, diff.usec); + work->started = 1; + work->time = now; + radio->num_active_works++; + + work->cb(work, 0); + + if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS) && + radio->num_active_works < MAX_ACTIVE_WORKS) + radio_work_check_next(wpa_s); +} + + +/* + * This function removes both started and pending radio works running on + * the provided interface's radio. + * Prior to the removal of the radio work, its callback (cb) is called with + * deinit set to be 1. Each work's callback is responsible for clearing its + * internal data and restoring to a correct state. + * @wpa_s: wpa_supplicant data + * @type: type of works to be removed + * @remove_all: 1 to remove all the works on this radio, 0 to remove only + * this interface's works. + */ +void radio_remove_works(struct wpa_supplicant *wpa_s, + const char *type, int remove_all) +{ + struct wpa_radio_work *work, *tmp; + struct wpa_radio *radio = wpa_s->radio; + + dl_list_for_each_safe(work, tmp, &radio->work, struct wpa_radio_work, + list) { + if (type && os_strcmp(type, work->type) != 0) + continue; + + /* skip other ifaces' works */ + if (!remove_all && work->wpa_s != wpa_s) + continue; + + wpa_dbg(wpa_s, MSG_DEBUG, "Remove radio work '%s'@%p%s", + work->type, work, work->started ? " (started)" : ""); + work->cb(work, 1); + radio_work_free(work); + } + + /* in case we removed the started work */ + radio_work_check_next(wpa_s); +} + + +void radio_remove_pending_work(struct wpa_supplicant *wpa_s, void *ctx) +{ + struct wpa_radio_work *work; + struct wpa_radio *radio = wpa_s->radio; + + dl_list_for_each(work, &radio->work, struct wpa_radio_work, list) { + if (work->ctx != ctx) + continue; + wpa_dbg(wpa_s, MSG_DEBUG, "Free pending radio work '%s'@%p%s", + work->type, work, work->started ? " (started)" : ""); + radio_work_free(work); + break; + } +} + + +static void radio_remove_interface(struct wpa_supplicant *wpa_s) +{ + struct wpa_radio *radio = wpa_s->radio; + + if (!radio) + return; + + wpa_printf(MSG_DEBUG, "Remove interface %s from radio %s", + wpa_s->ifname, radio->name); + dl_list_del(&wpa_s->radio_list); + radio_remove_works(wpa_s, NULL, 0); + /* If the interface that triggered the external scan was removed, the + * external scan is no longer running. */ + if (wpa_s == radio->external_scan_req_interface) + radio->external_scan_req_interface = NULL; + wpa_s->radio = NULL; + if (!dl_list_empty(&radio->ifaces)) + return; /* Interfaces remain for this radio */ + + wpa_printf(MSG_DEBUG, "Remove radio %s", radio->name); + eloop_cancel_timeout(radio_start_next_work, radio, NULL); + os_free(radio); +} + + +void radio_work_check_next(struct wpa_supplicant *wpa_s) +{ + struct wpa_radio *radio = wpa_s->radio; + + if (dl_list_empty(&radio->work)) + return; + if (wpa_s->ext_work_in_progress) { + wpa_printf(MSG_DEBUG, + "External radio work in progress - delay start of pending item"); + return; + } + eloop_cancel_timeout(radio_start_next_work, radio, NULL); + eloop_register_timeout(0, 0, radio_start_next_work, radio, NULL); +} + + +/** + * radio_add_work - Add a radio work item + * @wpa_s: Pointer to wpa_supplicant data + * @freq: Frequency of the offchannel operation in MHz or 0 + * @type: Unique identifier for each type of work + * @next: Force as the next work to be executed + * @cb: Callback function for indicating when radio is available + * @ctx: Context pointer for the work (work->ctx in cb()) + * Returns: 0 on success, -1 on failure + * + * This function is used to request time for an operation that requires + * exclusive radio control. Once the radio is available, the registered callback + * function will be called. radio_work_done() must be called once the exclusive + * radio operation has been completed, so that the radio is freed for other + * operations. The special case of deinit=1 is used to free the context data + * during interface removal. That does not allow the callback function to start + * the radio operation, i.e., it must free any resources allocated for the radio + * work and return. + * + * The @freq parameter can be used to indicate a single channel on which the + * offchannel operation will occur. This may allow multiple radio work + * operations to be performed in parallel if they apply for the same channel. + * Setting this to 0 indicates that the work item may use multiple channels or + * requires exclusive control of the radio. + */ +int radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq, + const char *type, int next, + void (*cb)(struct wpa_radio_work *work, int deinit), + void *ctx) +{ + struct wpa_radio *radio = wpa_s->radio; + struct wpa_radio_work *work; + int was_empty; + + work = os_zalloc(sizeof(*work)); + if (work == NULL) + return -1; + wpa_dbg(wpa_s, MSG_DEBUG, "Add radio work '%s'@%p", type, work); + os_get_reltime(&work->time); + work->freq = freq; + work->type = type; + work->wpa_s = wpa_s; + work->cb = cb; + work->ctx = ctx; + + if (freq) + work->bands = wpas_freq_to_band(freq); + else if (os_strcmp(type, "scan") == 0 || + os_strcmp(type, "p2p-scan") == 0) + work->bands = wpas_get_bands(wpa_s, + ((struct wpa_driver_scan_params *) + ctx)->freqs); + else + work->bands = wpas_get_bands(wpa_s, NULL); + + was_empty = dl_list_empty(&wpa_s->radio->work); + if (next) + dl_list_add(&wpa_s->radio->work, &work->list); + else + dl_list_add_tail(&wpa_s->radio->work, &work->list); + if (was_empty) { + wpa_dbg(wpa_s, MSG_DEBUG, "First radio work item in the queue - schedule start immediately"); + radio_work_check_next(wpa_s); + } else if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS) + && radio->num_active_works < MAX_ACTIVE_WORKS) { + wpa_dbg(wpa_s, MSG_DEBUG, + "Try to schedule a radio work (num_active_works=%u)", + radio->num_active_works); + radio_work_check_next(wpa_s); + } + + return 0; +} + + +/** + * radio_work_done - Indicate that a radio work item has been completed + * @work: Completed work + * + * This function is called once the callback function registered with + * radio_add_work() has completed its work. + */ +void radio_work_done(struct wpa_radio_work *work) +{ + struct wpa_supplicant *wpa_s = work->wpa_s; + struct os_reltime now, diff; + unsigned int started = work->started; + + os_get_reltime(&now); + os_reltime_sub(&now, &work->time, &diff); + wpa_dbg(wpa_s, MSG_DEBUG, "Radio work '%s'@%p %s in %ld.%06ld seconds", + work->type, work, started ? "done" : "canceled", + diff.sec, diff.usec); + radio_work_free(work); + if (started) + radio_work_check_next(wpa_s); +} + + +struct wpa_radio_work * +radio_work_pending(struct wpa_supplicant *wpa_s, const char *type) +{ + struct wpa_radio_work *work; + struct wpa_radio *radio = wpa_s->radio; + + dl_list_for_each(work, &radio->work, struct wpa_radio_work, list) { + if (work->wpa_s == wpa_s && os_strcmp(work->type, type) == 0) + return work; + } + + return NULL; +} + + +static int wpas_init_driver(struct wpa_supplicant *wpa_s, + const struct wpa_interface *iface) +{ + const char *ifname, *driver, *rn; + + driver = iface->driver; +next_driver: + if (wpa_supplicant_set_driver(wpa_s, driver) < 0) + return -1; + + wpa_s->drv_priv = wpa_drv_init(wpa_s, wpa_s->ifname); + if (wpa_s->drv_priv == NULL) { + const char *pos; + int level = MSG_ERROR; + + pos = driver ? os_strchr(driver, ',') : NULL; + if (pos) { + wpa_dbg(wpa_s, MSG_DEBUG, "Failed to initialize " + "driver interface - try next driver wrapper"); + driver = pos + 1; + goto next_driver; + } + +#ifdef CONFIG_MATCH_IFACE + if (wpa_s->matched == WPA_IFACE_MATCHED_NULL) + level = MSG_DEBUG; +#endif /* CONFIG_MATCH_IFACE */ + wpa_msg(wpa_s, level, "Failed to initialize driver interface"); + return -1; + } + if (wpa_drv_set_param(wpa_s, wpa_s->conf->driver_param) < 0) { + wpa_msg(wpa_s, MSG_ERROR, "Driver interface rejected " + "driver_param '%s'", wpa_s->conf->driver_param); + return -1; + } + + ifname = wpa_drv_get_ifname(wpa_s); + if (ifname && os_strcmp(ifname, wpa_s->ifname) != 0) { + wpa_dbg(wpa_s, MSG_DEBUG, "Driver interface replaced " + "interface name with '%s'", ifname); + os_strlcpy(wpa_s->ifname, ifname, sizeof(wpa_s->ifname)); + } + + rn = wpa_driver_get_radio_name(wpa_s); + if (rn && rn[0] == '\0') + rn = NULL; + + wpa_s->radio = radio_add_interface(wpa_s, rn); + if (wpa_s->radio == NULL) + return -1; + + return 0; +} + + +#ifdef CONFIG_GAS_SERVER + +static void wpas_gas_server_tx_status(struct wpa_supplicant *wpa_s, + unsigned int freq, const u8 *dst, + const u8 *src, const u8 *bssid, + const u8 *data, size_t data_len, + enum offchannel_send_action_result result) +{ + wpa_printf(MSG_DEBUG, "GAS: TX status: freq=%u dst=" MACSTR + " result=%s", + freq, MAC2STR(dst), + result == OFFCHANNEL_SEND_ACTION_SUCCESS ? "SUCCESS" : + (result == OFFCHANNEL_SEND_ACTION_NO_ACK ? "no-ACK" : + "FAILED")); + gas_server_tx_status(wpa_s->gas_server, dst, data, data_len, + result == OFFCHANNEL_SEND_ACTION_SUCCESS); +} + + +static void wpas_gas_server_tx(void *ctx, int freq, const u8 *da, + struct wpabuf *buf, unsigned int wait_time) +{ + struct wpa_supplicant *wpa_s = ctx; + const u8 broadcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + if (wait_time > wpa_s->max_remain_on_chan) + wait_time = wpa_s->max_remain_on_chan; + + offchannel_send_action(wpa_s, freq, da, wpa_s->own_addr, broadcast, + wpabuf_head(buf), wpabuf_len(buf), + wait_time, wpas_gas_server_tx_status, 0); +} + +#endif /* CONFIG_GAS_SERVER */ + +static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, + const struct wpa_interface *iface) +{ + struct wpa_driver_capa capa; + int capa_res; + u8 dfs_domain; + + wpa_printf(MSG_DEBUG, "Initializing interface '%s' conf '%s' driver " + "'%s' ctrl_interface '%s' bridge '%s'", iface->ifname, + iface->confname ? iface->confname : "N/A", + iface->driver ? iface->driver : "default", + iface->ctrl_interface ? iface->ctrl_interface : "N/A", + iface->bridge_ifname ? iface->bridge_ifname : "N/A"); + + if (iface->confname) { +#ifdef CONFIG_BACKEND_FILE + wpa_s->confname = os_rel2abs_path(iface->confname); + if (wpa_s->confname == NULL) { + wpa_printf(MSG_ERROR, "Failed to get absolute path " + "for configuration file '%s'.", + iface->confname); + return -1; + } + wpa_printf(MSG_DEBUG, "Configuration file '%s' -> '%s'", + iface->confname, wpa_s->confname); +#else /* CONFIG_BACKEND_FILE */ + wpa_s->confname = os_strdup(iface->confname); +#endif /* CONFIG_BACKEND_FILE */ + wpa_s->conf = wpa_config_read(wpa_s->confname, NULL, false); + if (wpa_s->conf == NULL) { + wpa_printf(MSG_ERROR, "Failed to read or parse " + "configuration '%s'.", wpa_s->confname); + return -1; + } + wpa_s->confanother = os_rel2abs_path(iface->confanother); + if (wpa_s->confanother && + !wpa_config_read(wpa_s->confanother, wpa_s->conf, true)) { + wpa_printf(MSG_ERROR, + "Failed to read or parse configuration '%s'.", + wpa_s->confanother); + return -1; + } + + /* + * Override ctrl_interface and driver_param if set on command + * line. + */ + if (iface->ctrl_interface) { + os_free(wpa_s->conf->ctrl_interface); + wpa_s->conf->ctrl_interface = + os_strdup(iface->ctrl_interface); + if (!wpa_s->conf->ctrl_interface) { + wpa_printf(MSG_ERROR, + "Failed to duplicate control interface '%s'.", + iface->ctrl_interface); + return -1; + } + } + + if (iface->driver_param) { + os_free(wpa_s->conf->driver_param); + wpa_s->conf->driver_param = + os_strdup(iface->driver_param); + if (!wpa_s->conf->driver_param) { + wpa_printf(MSG_ERROR, + "Failed to duplicate driver param '%s'.", + iface->driver_param); + return -1; + } + } + + if (iface->p2p_mgmt && !iface->ctrl_interface) { + os_free(wpa_s->conf->ctrl_interface); + wpa_s->conf->ctrl_interface = NULL; + } + } else + wpa_s->conf = wpa_config_alloc_empty(iface->ctrl_interface, + iface->driver_param); + + if (wpa_s->conf == NULL) { + wpa_printf(MSG_ERROR, "\nNo configuration found."); + return -1; + } + + if (iface->ifname == NULL) { + wpa_printf(MSG_ERROR, "\nInterface name is required."); + return -1; + } + if (os_strlen(iface->ifname) >= sizeof(wpa_s->ifname)) { + wpa_printf(MSG_ERROR, "\nToo long interface name '%s'.", + iface->ifname); + return -1; + } + os_strlcpy(wpa_s->ifname, iface->ifname, sizeof(wpa_s->ifname)); +#ifdef CONFIG_MATCH_IFACE + wpa_s->matched = iface->matched; +#endif /* CONFIG_MATCH_IFACE */ + + if (iface->bridge_ifname) { + if (os_strlen(iface->bridge_ifname) >= + sizeof(wpa_s->bridge_ifname)) { + wpa_printf(MSG_ERROR, "\nToo long bridge interface " + "name '%s'.", iface->bridge_ifname); + return -1; + } + os_strlcpy(wpa_s->bridge_ifname, iface->bridge_ifname, + sizeof(wpa_s->bridge_ifname)); + } + + /* RSNA Supplicant Key Management - INITIALIZE */ + eapol_sm_notify_portEnabled(wpa_s->eapol, false); + eapol_sm_notify_portValid(wpa_s->eapol, false); + + /* Initialize driver interface and register driver event handler before + * L2 receive handler so that association events are processed before + * EAPOL-Key packets if both become available for the same select() + * call. */ + if (wpas_init_driver(wpa_s, iface) < 0) + return -1; + + if (wpa_supplicant_init_wpa(wpa_s) < 0) + return -1; + + wpa_sm_set_ifname(wpa_s->wpa, wpa_s->ifname, + wpa_s->bridge_ifname[0] ? wpa_s->bridge_ifname : + NULL); + wpa_sm_set_fast_reauth(wpa_s->wpa, wpa_s->conf->fast_reauth); + + if (wpa_s->conf->dot11RSNAConfigPMKLifetime && + wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_LIFETIME, + wpa_s->conf->dot11RSNAConfigPMKLifetime)) { + wpa_msg(wpa_s, MSG_ERROR, "Invalid WPA parameter value for " + "dot11RSNAConfigPMKLifetime"); + return -1; + } + + if (wpa_s->conf->dot11RSNAConfigPMKReauthThreshold && + wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_REAUTH_THRESHOLD, + wpa_s->conf->dot11RSNAConfigPMKReauthThreshold)) { + wpa_msg(wpa_s, MSG_ERROR, "Invalid WPA parameter value for " + "dot11RSNAConfigPMKReauthThreshold"); + return -1; + } + + if (wpa_s->conf->dot11RSNAConfigSATimeout && + wpa_sm_set_param(wpa_s->wpa, RSNA_SA_TIMEOUT, + wpa_s->conf->dot11RSNAConfigSATimeout)) { + wpa_msg(wpa_s, MSG_ERROR, "Invalid WPA parameter value for " + "dot11RSNAConfigSATimeout"); + return -1; + } + + wpa_s->hw.modes = wpa_drv_get_hw_feature_data(wpa_s, + &wpa_s->hw.num_modes, + &wpa_s->hw.flags, + &dfs_domain); + if (wpa_s->hw.modes) { + u16 i; + + for (i = 0; i < wpa_s->hw.num_modes; i++) { + if (wpa_s->hw.modes[i].vht_capab) { + wpa_s->hw_capab = CAPAB_VHT; + break; + } + + if (wpa_s->hw.modes[i].ht_capab & + HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) + wpa_s->hw_capab = CAPAB_HT40; + else if (wpa_s->hw.modes[i].ht_capab && + wpa_s->hw_capab == CAPAB_NO_HT_VHT) + wpa_s->hw_capab = CAPAB_HT; + } + wpa_s->support_6ghz = wpas_is_6ghz_supported(wpa_s, false); + } + + capa_res = wpa_drv_get_capa(wpa_s, &capa); + if (capa_res == 0) { + wpa_s->drv_capa_known = 1; + wpa_s->drv_flags = capa.flags; + wpa_s->drv_flags2 = capa.flags2; + wpa_s->drv_enc = capa.enc; + wpa_s->drv_rrm_flags = capa.rrm_flags; + wpa_s->drv_max_acl_mac_addrs = capa.max_acl_mac_addrs; + wpa_s->probe_resp_offloads = capa.probe_resp_offloads; + wpa_s->max_scan_ssids = capa.max_scan_ssids; + wpa_s->max_sched_scan_ssids = capa.max_sched_scan_ssids; + wpa_s->max_sched_scan_plans = capa.max_sched_scan_plans; + wpa_s->max_sched_scan_plan_interval = + capa.max_sched_scan_plan_interval; + wpa_s->max_sched_scan_plan_iterations = + capa.max_sched_scan_plan_iterations; + wpa_s->sched_scan_supported = capa.sched_scan_supported; + wpa_s->max_match_sets = capa.max_match_sets; + wpa_s->max_remain_on_chan = capa.max_remain_on_chan; + wpa_s->max_stations = capa.max_stations; + wpa_s->extended_capa = capa.extended_capa; + wpa_s->extended_capa_mask = capa.extended_capa_mask; + wpa_s->extended_capa_len = capa.extended_capa_len; + wpa_s->num_multichan_concurrent = + capa.num_multichan_concurrent; + wpa_s->wmm_ac_supported = capa.wmm_ac_supported; + wpa_s->max_num_akms = capa.max_num_akms; + + if (capa.mac_addr_rand_scan_supported) + wpa_s->mac_addr_rand_supported |= MAC_ADDR_RAND_SCAN; + if (wpa_s->sched_scan_supported && + capa.mac_addr_rand_sched_scan_supported) + wpa_s->mac_addr_rand_supported |= + (MAC_ADDR_RAND_SCHED_SCAN | MAC_ADDR_RAND_PNO); + + wpa_drv_get_ext_capa(wpa_s, WPA_IF_STATION); + if (wpa_s->extended_capa && + wpa_s->extended_capa_len >= 3 && + wpa_s->extended_capa[2] & 0x40) + wpa_s->multi_bss_support = 1; + } +#ifdef CONFIG_PASN + wpa_pasn_sm_set_caps(wpa_s->wpa, wpa_s->drv_flags2); +#endif /* CONFIG_PASN */ + wpa_sm_set_driver_bss_selection(wpa_s->wpa, + !!(wpa_s->drv_flags & + WPA_DRIVER_FLAGS_BSS_SELECTION)); + if (wpa_s->max_remain_on_chan == 0) + wpa_s->max_remain_on_chan = 1000; + + /* + * Only take p2p_mgmt parameters when P2P Device is supported. + * Doing it here as it determines whether l2_packet_init() will be done + * during wpa_supplicant_driver_init(). + */ + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_DEDICATED_P2P_DEVICE) + wpa_s->p2p_mgmt = iface->p2p_mgmt; + + if (wpa_s->num_multichan_concurrent == 0) + wpa_s->num_multichan_concurrent = 1; + + if (wpa_supplicant_driver_init(wpa_s) < 0) + return -1; + +#ifdef CONFIG_TDLS + if (!iface->p2p_mgmt && wpa_tdls_init(wpa_s->wpa)) + return -1; +#endif /* CONFIG_TDLS */ + + if (wpa_s->conf->country[0] && wpa_s->conf->country[1] && + wpa_drv_set_country(wpa_s, wpa_s->conf->country)) { + wpa_dbg(wpa_s, MSG_DEBUG, "Failed to set country"); + return -1; + } + +#ifdef CONFIG_FST + if (wpa_s->conf->fst_group_id) { + struct fst_iface_cfg cfg; + struct fst_wpa_obj iface_obj; + + fst_wpa_supplicant_fill_iface_obj(wpa_s, &iface_obj); + os_strlcpy(cfg.group_id, wpa_s->conf->fst_group_id, + sizeof(cfg.group_id)); + cfg.priority = wpa_s->conf->fst_priority; + cfg.llt = wpa_s->conf->fst_llt; + + wpa_s->fst = fst_attach(wpa_s->ifname, wpa_s->own_addr, + &iface_obj, &cfg); + if (!wpa_s->fst) { + wpa_msg(wpa_s, MSG_ERROR, + "FST: Cannot attach iface %s to group %s", + wpa_s->ifname, cfg.group_id); + return -1; + } + } +#endif /* CONFIG_FST */ + + if (wpas_wps_init(wpa_s)) + return -1; + +#ifdef CONFIG_GAS_SERVER + wpa_s->gas_server = gas_server_init(wpa_s, wpas_gas_server_tx); + if (!wpa_s->gas_server) { + wpa_printf(MSG_ERROR, "Failed to initialize GAS server"); + return -1; + } +#endif /* CONFIG_GAS_SERVER */ + +#ifdef CONFIG_DPP + if (wpas_dpp_init(wpa_s) < 0) + return -1; +#endif /* CONFIG_DPP */ + + if (wpa_supplicant_init_eapol(wpa_s) < 0) + return -1; + wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol); + + wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s); + if (wpa_s->ctrl_iface == NULL) { + wpa_printf(MSG_ERROR, + "Failed to initialize control interface '%s'.\n" + "You may have another wpa_supplicant process " + "already running or the file was\n" + "left by an unclean termination of wpa_supplicant " + "in which case you will need\n" + "to manually remove this file before starting " + "wpa_supplicant again.\n", + wpa_s->conf->ctrl_interface); + return -1; + } + + wpa_s->gas = gas_query_init(wpa_s); + if (wpa_s->gas == NULL) { + wpa_printf(MSG_ERROR, "Failed to initialize GAS query"); + return -1; + } + + if ((!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_DEDICATED_P2P_DEVICE) || + wpa_s->p2p_mgmt) && + wpas_p2p_init(wpa_s->global, wpa_s) < 0) { + wpa_msg(wpa_s, MSG_ERROR, "Failed to init P2P"); + return -1; + } + + if (wpa_bss_init(wpa_s) < 0) + return -1; + +#ifdef CONFIG_PMKSA_CACHE_EXTERNAL +#ifdef CONFIG_MESH + dl_list_init(&wpa_s->mesh_external_pmksa_cache); +#endif /* CONFIG_MESH */ +#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */ + + /* + * Set Wake-on-WLAN triggers, if configured. + * Note: We don't restore/remove the triggers on shutdown (it doesn't + * have effect anyway when the interface is down). + */ + if (capa_res == 0 && wpas_set_wowlan_triggers(wpa_s, &capa) < 0) + return -1; + +#ifdef CONFIG_EAP_PROXY +{ + size_t len; + wpa_s->mnc_len = eapol_sm_get_eap_proxy_imsi(wpa_s->eapol, -1, + wpa_s->imsi, &len); + if (wpa_s->mnc_len > 0) { + wpa_s->imsi[len] = '\0'; + wpa_printf(MSG_DEBUG, "eap_proxy: IMSI %s (MNC length %d)", + wpa_s->imsi, wpa_s->mnc_len); + } else { + wpa_printf(MSG_DEBUG, "eap_proxy: IMSI not available"); + } +} +#endif /* CONFIG_EAP_PROXY */ + + if (pcsc_reader_init(wpa_s) < 0) + return -1; + + if (wpas_init_ext_pw(wpa_s) < 0) + return -1; + + wpas_rrm_reset(wpa_s); + + wpas_sched_scan_plans_set(wpa_s, wpa_s->conf->sched_scan_plans); + +#ifdef CONFIG_HS20 + hs20_init(wpa_s); +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_MBO + if (!wpa_s->disable_mbo_oce && wpa_s->conf->oce) { + if ((wpa_s->conf->oce & OCE_STA) && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_OCE_STA)) + wpa_s->enable_oce = OCE_STA; + if ((wpa_s->conf->oce & OCE_STA_CFON) && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_OCE_STA_CFON)) { + /* TODO: Need to add STA-CFON support */ + wpa_printf(MSG_ERROR, + "OCE STA-CFON feature is not yet supported"); + } + } + wpas_mbo_update_non_pref_chan(wpa_s, wpa_s->conf->non_pref_chan); +#endif /* CONFIG_MBO */ + + wpa_supplicant_set_default_scan_ies(wpa_s); + + return 0; +} + + +static void wpa_supplicant_deinit_iface(struct wpa_supplicant *wpa_s, + int notify, int terminate) +{ + struct wpa_global *global = wpa_s->global; + struct wpa_supplicant *iface, *prev; + + if (wpa_s == wpa_s->parent || (wpa_s == wpa_s->p2pdev && wpa_s->p2p_mgmt)) + wpas_p2p_group_remove(wpa_s, "*"); + + iface = global->ifaces; + while (iface) { + if (iface->p2pdev == wpa_s) + iface->p2pdev = iface->parent; + if (iface == wpa_s || iface->parent != wpa_s) { + iface = iface->next; + continue; + } + wpa_printf(MSG_DEBUG, + "Remove remaining child interface %s from parent %s", + iface->ifname, wpa_s->ifname); + prev = iface; + iface = iface->next; + wpa_supplicant_remove_iface(global, prev, terminate); + } + + wpa_s->disconnected = 1; + if (wpa_s->drv_priv) { + /* + * Don't deauthenticate if WoWLAN is enable and not explicitly + * been configured to disconnect. + */ + if (!wpa_drv_get_wowlan(wpa_s) || + wpa_s->conf->wowlan_disconnect_on_deinit) { + wpa_supplicant_deauthenticate( + wpa_s, WLAN_REASON_DEAUTH_LEAVING); + + wpa_drv_set_countermeasures(wpa_s, 0); + wpa_clear_keys(wpa_s, NULL); + } else { + wpa_msg(wpa_s, MSG_INFO, + "Do not deauthenticate as part of interface deinit since WoWLAN is enabled"); + } + } + + wpa_supplicant_cleanup(wpa_s); + wpas_p2p_deinit_iface(wpa_s); + + wpas_ctrl_radio_work_flush(wpa_s); + radio_remove_interface(wpa_s); + +#ifdef CONFIG_FST + if (wpa_s->fst) { + fst_detach(wpa_s->fst); + wpa_s->fst = NULL; + } + if (wpa_s->received_mb_ies) { + wpabuf_free(wpa_s->received_mb_ies); + wpa_s->received_mb_ies = NULL; + } +#endif /* CONFIG_FST */ + + if (wpa_s->drv_priv) + wpa_drv_deinit(wpa_s); + + if (notify) + wpas_notify_iface_removed(wpa_s); + + if (terminate) + wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_TERMINATING); + + wpa_supplicant_ctrl_iface_deinit(wpa_s, wpa_s->ctrl_iface); + wpa_s->ctrl_iface = NULL; + +#ifdef CONFIG_MESH + if (wpa_s->ifmsh) { + wpa_supplicant_mesh_iface_deinit(wpa_s, wpa_s->ifmsh, true); + wpa_s->ifmsh = NULL; + } +#endif /* CONFIG_MESH */ + + if (wpa_s->conf != NULL) { + wpa_config_free(wpa_s->conf); + wpa_s->conf = NULL; + } + + os_free(wpa_s->ssids_from_scan_req); + os_free(wpa_s->last_scan_freqs); + + os_free(wpa_s); +} + + +#ifdef CONFIG_MATCH_IFACE + +/** + * wpa_supplicant_match_iface - Match an interface description to a name + * @global: Pointer to global data from wpa_supplicant_init() + * @ifname: Name of the interface to match + * Returns: Pointer to the created interface description or %NULL on failure + */ +struct wpa_interface * wpa_supplicant_match_iface(struct wpa_global *global, + const char *ifname) +{ + int i; + struct wpa_interface *iface, *miface; + + for (i = 0; i < global->params.match_iface_count; i++) { + miface = &global->params.match_ifaces[i]; + if (!miface->ifname || + fnmatch(miface->ifname, ifname, 0) == 0) { + iface = os_zalloc(sizeof(*iface)); + if (!iface) + return NULL; + *iface = *miface; + if (!miface->ifname) + iface->matched = WPA_IFACE_MATCHED_NULL; + else + iface->matched = WPA_IFACE_MATCHED; + iface->ifname = ifname; + return iface; + } + } + + return NULL; +} + + +/** + * wpa_supplicant_match_existing - Match existing interfaces + * @global: Pointer to global data from wpa_supplicant_init() + * Returns: 0 on success, -1 on failure + */ +static int wpa_supplicant_match_existing(struct wpa_global *global) +{ + struct if_nameindex *ifi, *ifp; + struct wpa_supplicant *wpa_s; + struct wpa_interface *iface; + + ifp = if_nameindex(); + if (!ifp) { + wpa_printf(MSG_ERROR, "if_nameindex: %s", strerror(errno)); + return -1; + } + + for (ifi = ifp; ifi->if_name; ifi++) { + wpa_s = wpa_supplicant_get_iface(global, ifi->if_name); + if (wpa_s) + continue; + iface = wpa_supplicant_match_iface(global, ifi->if_name); + if (iface) { + wpa_supplicant_add_iface(global, iface, NULL); + os_free(iface); + } + } + + if_freenameindex(ifp); + return 0; +} + +#endif /* CONFIG_MATCH_IFACE */ + + +/** + * wpa_supplicant_add_iface - Add a new network interface + * @global: Pointer to global data from wpa_supplicant_init() + * @iface: Interface configuration options + * @parent: Parent interface or %NULL to assign new interface as parent + * Returns: Pointer to the created interface or %NULL on failure + * + * This function is used to add new network interfaces for %wpa_supplicant. + * This can be called before wpa_supplicant_run() to add interfaces before the + * main event loop has been started. In addition, new interfaces can be added + * dynamically while %wpa_supplicant is already running. This could happen, + * e.g., when a hotplug network adapter is inserted. + */ +struct wpa_supplicant * wpa_supplicant_add_iface(struct wpa_global *global, + struct wpa_interface *iface, + struct wpa_supplicant *parent) +{ + struct wpa_supplicant *wpa_s; + struct wpa_interface t_iface; + struct wpa_ssid *ssid; + + if (global == NULL || iface == NULL) + return NULL; + + wpa_s = wpa_supplicant_alloc(parent); + if (wpa_s == NULL) + return NULL; + + wpa_s->global = global; + + t_iface = *iface; + if (global->params.override_driver) { + wpa_printf(MSG_DEBUG, "Override interface parameter: driver " + "('%s' -> '%s')", + iface->driver, global->params.override_driver); + t_iface.driver = global->params.override_driver; + } + if (global->params.override_ctrl_interface) { + wpa_printf(MSG_DEBUG, "Override interface parameter: " + "ctrl_interface ('%s' -> '%s')", + iface->ctrl_interface, + global->params.override_ctrl_interface); + t_iface.ctrl_interface = + global->params.override_ctrl_interface; + } + if (wpa_supplicant_init_iface(wpa_s, &t_iface)) { + wpa_printf(MSG_DEBUG, "Failed to add interface %s", + iface->ifname); + wpa_supplicant_deinit_iface(wpa_s, 0, 0); + return NULL; + } + + /* Notify the control interfaces about new iface */ + if (wpas_notify_iface_added(wpa_s)) { + wpa_supplicant_deinit_iface(wpa_s, 1, 0); + return NULL; + } + + /* Notify the control interfaces about new networks */ + for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) { + if (iface->p2p_mgmt == 0) { + wpas_notify_network_added(wpa_s, ssid); + } else if (ssid->ssid_len > P2P_WILDCARD_SSID_LEN + && os_strncmp((const char *) ssid->ssid, + P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN) == 0) { + wpas_notify_persistent_group_added(wpa_s, ssid); + } + } + + wpa_s->next = global->ifaces; + global->ifaces = wpa_s; + + wpa_dbg(wpa_s, MSG_DEBUG, "Added interface %s", wpa_s->ifname); + wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); + +#ifdef CONFIG_P2P + if (wpa_s->global->p2p == NULL && + !wpa_s->global->p2p_disabled && !wpa_s->conf->p2p_disabled && + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_DEDICATED_P2P_DEVICE) && + wpas_p2p_add_p2pdev_interface( + wpa_s, wpa_s->global->params.conf_p2p_dev) < 0) { + wpa_printf(MSG_INFO, + "P2P: Failed to enable P2P Device interface"); + /* Try to continue without. P2P will be disabled. */ + } +#endif /* CONFIG_P2P */ + + return wpa_s; +} + + +/** + * wpa_supplicant_remove_iface - Remove a network interface + * @global: Pointer to global data from wpa_supplicant_init() + * @wpa_s: Pointer to the network interface to be removed + * Returns: 0 if interface was removed, -1 if interface was not found + * + * This function can be used to dynamically remove network interfaces from + * %wpa_supplicant, e.g., when a hotplug network adapter is ejected. In + * addition, this function is used to remove all remaining interfaces when + * %wpa_supplicant is terminated. + */ +int wpa_supplicant_remove_iface(struct wpa_global *global, + struct wpa_supplicant *wpa_s, + int terminate) +{ + struct wpa_supplicant *prev; +#ifdef CONFIG_MESH + unsigned int mesh_if_created = wpa_s->mesh_if_created; + char *ifname = NULL; + struct wpa_supplicant *parent = wpa_s->parent; +#endif /* CONFIG_MESH */ + + /* Remove interface from the global list of interfaces */ + prev = global->ifaces; + if (prev == wpa_s) { + global->ifaces = wpa_s->next; + } else { + while (prev && prev->next != wpa_s) + prev = prev->next; + if (prev == NULL) + return -1; + prev->next = wpa_s->next; + } + + wpa_dbg(wpa_s, MSG_DEBUG, "Removing interface %s", wpa_s->ifname); + +#ifdef CONFIG_MESH + if (mesh_if_created) { + ifname = os_strdup(wpa_s->ifname); + if (ifname == NULL) { + wpa_dbg(wpa_s, MSG_ERROR, + "mesh: Failed to malloc ifname"); + return -1; + } + } +#endif /* CONFIG_MESH */ + + if (global->p2p_group_formation == wpa_s) + global->p2p_group_formation = NULL; + if (global->p2p_invite_group == wpa_s) + global->p2p_invite_group = NULL; + wpa_supplicant_deinit_iface(wpa_s, 1, terminate); + +#ifdef CONFIG_MESH + if (mesh_if_created) { + wpa_drv_if_remove(parent, WPA_IF_MESH, ifname); + os_free(ifname); + } +#endif /* CONFIG_MESH */ + + return 0; +} + + +/** + * wpa_supplicant_get_eap_mode - Get the current EAP mode + * @wpa_s: Pointer to the network interface + * Returns: Pointer to the eap mode or the string "UNKNOWN" if not found + */ +const char * wpa_supplicant_get_eap_mode(struct wpa_supplicant *wpa_s) +{ + const char *eapol_method; + + if (wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt) == 0 && + wpa_s->key_mgmt != WPA_KEY_MGMT_IEEE8021X_NO_WPA) { + return "NO-EAP"; + } + + eapol_method = eapol_sm_get_method_name(wpa_s->eapol); + if (eapol_method == NULL) + return "UNKNOWN-EAP"; + + return eapol_method; +} + + +/** + * wpa_supplicant_get_iface - Get a new network interface + * @global: Pointer to global data from wpa_supplicant_init() + * @ifname: Interface name + * Returns: Pointer to the interface or %NULL if not found + */ +struct wpa_supplicant * wpa_supplicant_get_iface(struct wpa_global *global, + const char *ifname) +{ + struct wpa_supplicant *wpa_s; + + for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) { + if (os_strcmp(wpa_s->ifname, ifname) == 0) + return wpa_s; + } + return NULL; +} + + +#ifndef CONFIG_NO_WPA_MSG +static const char * wpa_supplicant_msg_ifname_cb(void *ctx) +{ + struct wpa_supplicant *wpa_s = ctx; + if (wpa_s == NULL) + return NULL; + return wpa_s->ifname; +} +#endif /* CONFIG_NO_WPA_MSG */ + + +#ifndef WPA_SUPPLICANT_CLEANUP_INTERVAL +#define WPA_SUPPLICANT_CLEANUP_INTERVAL 10 +#endif /* WPA_SUPPLICANT_CLEANUP_INTERVAL */ + +/* Periodic cleanup tasks */ +static void wpas_periodic(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_global *global = eloop_ctx; + struct wpa_supplicant *wpa_s; + + eloop_register_timeout(WPA_SUPPLICANT_CLEANUP_INTERVAL, 0, + wpas_periodic, global, NULL); + +#ifdef CONFIG_P2P + if (global->p2p) + p2p_expire_peers(global->p2p); +#endif /* CONFIG_P2P */ + + for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) { + wpa_bss_flush_by_age(wpa_s, wpa_s->conf->bss_expiration_age); +#ifdef CONFIG_AP + ap_periodic(wpa_s); +#endif /* CONFIG_AP */ + } +} + + +/** + * wpa_supplicant_init - Initialize %wpa_supplicant + * @params: Parameters for %wpa_supplicant + * Returns: Pointer to global %wpa_supplicant data, or %NULL on failure + * + * This function is used to initialize %wpa_supplicant. After successful + * initialization, the returned data pointer can be used to add and remove + * network interfaces, and eventually, to deinitialize %wpa_supplicant. + */ +struct wpa_global * wpa_supplicant_init(struct wpa_params *params) +{ + struct wpa_global *global; + int ret, i; + + if (params == NULL) + return NULL; + +#ifdef CONFIG_DRIVER_NDIS + { + void driver_ndis_init_ops(void); + driver_ndis_init_ops(); + } +#endif /* CONFIG_DRIVER_NDIS */ + +#ifndef CONFIG_NO_WPA_MSG + wpa_msg_register_ifname_cb(wpa_supplicant_msg_ifname_cb); +#endif /* CONFIG_NO_WPA_MSG */ + + if (params->wpa_debug_file_path) + wpa_debug_open_file(params->wpa_debug_file_path); + if (!params->wpa_debug_file_path && !params->wpa_debug_syslog) + wpa_debug_setup_stdout(); + if (params->wpa_debug_syslog) + wpa_debug_open_syslog(); + if (params->wpa_debug_tracing) { + ret = wpa_debug_open_linux_tracing(); + if (ret) { + wpa_printf(MSG_ERROR, + "Failed to enable trace logging"); + return NULL; + } + } + + ret = eap_register_methods(); + if (ret) { + wpa_printf(MSG_ERROR, "Failed to register EAP methods"); + if (ret == -2) + wpa_printf(MSG_ERROR, "Two or more EAP methods used " + "the same EAP type."); + return NULL; + } + + global = os_zalloc(sizeof(*global)); + if (global == NULL) + return NULL; + dl_list_init(&global->p2p_srv_bonjour); + dl_list_init(&global->p2p_srv_upnp); + global->params.daemonize = params->daemonize; + global->params.wait_for_monitor = params->wait_for_monitor; + global->params.dbus_ctrl_interface = params->dbus_ctrl_interface; + + if (params->pid_file) { + global->params.pid_file = os_strdup(params->pid_file); + if (!global->params.pid_file) { + wpa_supplicant_deinit(global); + return NULL; + } + } + + if (params->ctrl_interface) { + global->params.ctrl_interface = + os_strdup(params->ctrl_interface); + if (!global->params.ctrl_interface) { + wpa_supplicant_deinit(global); + return NULL; + } + } + + if (params->ctrl_interface_group) { + global->params.ctrl_interface_group = + os_strdup(params->ctrl_interface_group); + if (!global->params.ctrl_interface_group) { + wpa_supplicant_deinit(global); + return NULL; + } + } + + if (params->override_driver) { + global->params.override_driver = + os_strdup(params->override_driver); + if (!global->params.override_driver) { + wpa_supplicant_deinit(global); + return NULL; + } + } + + if (params->override_ctrl_interface) { + global->params.override_ctrl_interface = + os_strdup(params->override_ctrl_interface); + if (!global->params.override_ctrl_interface) { + wpa_supplicant_deinit(global); + return NULL; + } + } + +#ifdef CONFIG_MATCH_IFACE + global->params.match_iface_count = params->match_iface_count; + if (params->match_iface_count) { + global->params.match_ifaces = + os_calloc(params->match_iface_count, + sizeof(struct wpa_interface)); + if (!global->params.match_ifaces) { + wpa_printf(MSG_ERROR, + "Failed to allocate match interfaces"); + wpa_supplicant_deinit(global); + return NULL; + } + os_memcpy(global->params.match_ifaces, + params->match_ifaces, + params->match_iface_count * + sizeof(struct wpa_interface)); + } +#endif /* CONFIG_MATCH_IFACE */ +#ifdef CONFIG_P2P + if (params->conf_p2p_dev) { + global->params.conf_p2p_dev = + os_strdup(params->conf_p2p_dev); + if (!global->params.conf_p2p_dev) { + wpa_printf(MSG_ERROR, "Failed to allocate conf p2p"); + wpa_supplicant_deinit(global); + return NULL; + } + } +#endif /* CONFIG_P2P */ + wpa_debug_level = global->params.wpa_debug_level = + params->wpa_debug_level; + wpa_debug_show_keys = global->params.wpa_debug_show_keys = + params->wpa_debug_show_keys; + wpa_debug_timestamp = global->params.wpa_debug_timestamp = + params->wpa_debug_timestamp; + + wpa_printf(MSG_DEBUG, "wpa_supplicant v%s", VERSION_STR); + + if (eloop_init()) { + wpa_printf(MSG_ERROR, "Failed to initialize event loop"); + wpa_supplicant_deinit(global); + return NULL; + } + + random_init(params->entropy_file); + + global->ctrl_iface = wpa_supplicant_global_ctrl_iface_init(global); + if (global->ctrl_iface == NULL) { + wpa_supplicant_deinit(global); + return NULL; + } + + if (wpas_notify_supplicant_initialized(global)) { + wpa_supplicant_deinit(global); + return NULL; + } + + for (i = 0; wpa_drivers[i]; i++) + global->drv_count++; + if (global->drv_count == 0) { + wpa_printf(MSG_ERROR, "No drivers enabled"); + wpa_supplicant_deinit(global); + return NULL; + } + global->drv_priv = os_calloc(global->drv_count, sizeof(void *)); + if (global->drv_priv == NULL) { + wpa_supplicant_deinit(global); + return NULL; + } + +#ifdef CONFIG_WIFI_DISPLAY + if (wifi_display_init(global) < 0) { + wpa_printf(MSG_ERROR, "Failed to initialize Wi-Fi Display"); + wpa_supplicant_deinit(global); + return NULL; + } +#endif /* CONFIG_WIFI_DISPLAY */ + + eloop_register_timeout(WPA_SUPPLICANT_CLEANUP_INTERVAL, 0, + wpas_periodic, global, NULL); + + return global; +} + + +/** + * wpa_supplicant_run - Run the %wpa_supplicant main event loop + * @global: Pointer to global data from wpa_supplicant_init() + * Returns: 0 after successful event loop run, -1 on failure + * + * This function starts the main event loop and continues running as long as + * there are any remaining events. In most cases, this function is running as + * long as the %wpa_supplicant process in still in use. + */ +int wpa_supplicant_run(struct wpa_global *global) +{ + struct wpa_supplicant *wpa_s; + + if (global->params.daemonize && + (wpa_supplicant_daemon(global->params.pid_file) || + eloop_sock_requeue())) + return -1; + +#ifdef CONFIG_MATCH_IFACE + if (wpa_supplicant_match_existing(global)) + return -1; +#endif + + if (global->params.wait_for_monitor) { + for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) + if (wpa_s->ctrl_iface && !wpa_s->p2p_mgmt) + wpa_supplicant_ctrl_iface_wait( + wpa_s->ctrl_iface); + } + +#ifdef CONFIG_AIDL + // If daemonize is enabled, initialize AIDL here. + if (global->params.daemonize) { + global->aidl = wpas_aidl_init(global); + if (!global->aidl) + return -1; + } +#endif /* CONFIG_AIDL */ + + eloop_register_signal_terminate(wpa_supplicant_terminate, global); + eloop_register_signal_reconfig(wpa_supplicant_reconfig, global); + + eloop_run(); + + return 0; +} + + +/** + * wpa_supplicant_deinit - Deinitialize %wpa_supplicant + * @global: Pointer to global data from wpa_supplicant_init() + * + * This function is called to deinitialize %wpa_supplicant and to free all + * allocated resources. Remaining network interfaces will also be removed. + */ +void wpa_supplicant_deinit(struct wpa_global *global) +{ + int i; + + if (global == NULL) + return; + + eloop_cancel_timeout(wpas_periodic, global, NULL); + +#ifdef CONFIG_WIFI_DISPLAY + wifi_display_deinit(global); +#endif /* CONFIG_WIFI_DISPLAY */ + + while (global->ifaces) + wpa_supplicant_remove_iface(global, global->ifaces, 1); + + if (global->ctrl_iface) + wpa_supplicant_global_ctrl_iface_deinit(global->ctrl_iface); + + wpas_notify_supplicant_deinitialized(global); + + eap_peer_unregister_methods(); +#ifdef CONFIG_AP + eap_server_unregister_methods(); +#endif /* CONFIG_AP */ + + for (i = 0; wpa_drivers[i] && global->drv_priv; i++) { + if (!global->drv_priv[i]) + continue; + wpa_drivers[i]->global_deinit(global->drv_priv[i]); + } + os_free(global->drv_priv); + + random_deinit(); + + eloop_destroy(); + + if (global->params.pid_file) { + os_daemonize_terminate(global->params.pid_file); + os_free(global->params.pid_file); + } + os_free(global->params.ctrl_interface); + os_free(global->params.ctrl_interface_group); + os_free(global->params.override_driver); + os_free(global->params.override_ctrl_interface); +#ifdef CONFIG_MATCH_IFACE + os_free(global->params.match_ifaces); +#endif /* CONFIG_MATCH_IFACE */ +#ifdef CONFIG_P2P + os_free(global->params.conf_p2p_dev); +#endif /* CONFIG_P2P */ + + os_free(global->p2p_disallow_freq.range); + os_free(global->p2p_go_avoid_freq.range); + os_free(global->add_psk); + + os_free(global); + wpa_debug_close_syslog(); + wpa_debug_close_file(); + wpa_debug_close_linux_tracing(); +} + + +void wpa_supplicant_update_config(struct wpa_supplicant *wpa_s) +{ + if ((wpa_s->conf->changed_parameters & CFG_CHANGED_COUNTRY) && + wpa_s->conf->country[0] && wpa_s->conf->country[1]) { + char country[3]; + country[0] = wpa_s->conf->country[0]; + country[1] = wpa_s->conf->country[1]; + country[2] = '\0'; + if (wpa_drv_set_country(wpa_s, country) < 0) { + wpa_printf(MSG_ERROR, "Failed to set country code " + "'%s'", country); + } + } + + if (wpa_s->conf->changed_parameters & CFG_CHANGED_EXT_PW_BACKEND) + wpas_init_ext_pw(wpa_s); + + if (wpa_s->conf->changed_parameters & CFG_CHANGED_SCHED_SCAN_PLANS) + wpas_sched_scan_plans_set(wpa_s, wpa_s->conf->sched_scan_plans); + + if (wpa_s->conf->changed_parameters & CFG_CHANGED_WOWLAN_TRIGGERS) { + struct wpa_driver_capa capa; + int res = wpa_drv_get_capa(wpa_s, &capa); + + if (res == 0 && wpas_set_wowlan_triggers(wpa_s, &capa) < 0) + wpa_printf(MSG_ERROR, + "Failed to update wowlan_triggers to '%s'", + wpa_s->conf->wowlan_triggers); + } + + if (wpa_s->conf->changed_parameters & CFG_CHANGED_DISABLE_BTM) + wpa_supplicant_set_default_scan_ies(wpa_s); + +#ifdef CONFIG_BGSCAN + /* + * We default to global bgscan parameters only when per-network bgscan + * parameters aren't set. Only bother resetting bgscan parameters if + * this is the case. + */ + if ((wpa_s->conf->changed_parameters & CFG_CHANGED_BGSCAN) && + wpa_s->current_ssid && !wpa_s->current_ssid->bgscan && + wpa_s->wpa_state == WPA_COMPLETED) + wpa_supplicant_reset_bgscan(wpa_s); +#endif /* CONFIG_BGSCAN */ + +#ifdef CONFIG_WPS + wpas_wps_update_config(wpa_s); +#endif /* CONFIG_WPS */ + wpas_p2p_update_config(wpa_s); + wpa_s->conf->changed_parameters = 0; +} + + +void add_freq(int *freqs, int *num_freqs, int freq) +{ + int i; + + for (i = 0; i < *num_freqs; i++) { + if (freqs[i] == freq) + return; + } + + freqs[*num_freqs] = freq; + (*num_freqs)++; +} + + +static int * get_bss_freqs_in_ess(struct wpa_supplicant *wpa_s) +{ + struct wpa_bss *bss, *cbss; + const int max_freqs = 10; + int *freqs; + int num_freqs = 0; + + freqs = os_calloc(max_freqs + 1, sizeof(int)); + if (freqs == NULL) + return NULL; + + cbss = wpa_s->current_bss; + + dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { + if (bss == cbss) + continue; + if (bss->ssid_len == cbss->ssid_len && + os_memcmp(bss->ssid, cbss->ssid, bss->ssid_len) == 0 && + !wpa_bssid_ignore_is_listed(wpa_s, bss->bssid)) { + add_freq(freqs, &num_freqs, bss->freq); + if (num_freqs == max_freqs) + break; + } + } + + if (num_freqs == 0) { + os_free(freqs); + freqs = NULL; + } + + return freqs; +} + + +void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid) +{ + int timeout; + int count; + int *freqs = NULL; + + wpas_connect_work_done(wpa_s); + + /* + * Remove possible authentication timeout since the connection failed. + */ + eloop_cancel_timeout(wpa_supplicant_timeout, wpa_s, NULL); + + /* + * There is no point in ignoring the AP temporarily if this event is + * generated based on local request to disconnect. + */ + if (wpa_s->own_disconnect_req || wpa_s->own_reconnect_req) { + wpa_s->own_disconnect_req = 0; + wpa_dbg(wpa_s, MSG_DEBUG, + "Ignore connection failure due to local request to disconnect"); + return; + } + if (wpa_s->disconnected) { + wpa_dbg(wpa_s, MSG_DEBUG, "Ignore connection failure " + "indication since interface has been put into " + "disconnected state"); + return; + } + if (wpa_s->auto_reconnect_disabled) { + wpa_dbg(wpa_s, MSG_DEBUG, "Ignore connection failure " + "indication since auto connect is disabled"); + return; + } + + /* + * Add the failed BSSID into the ignore list and speed up next scan + * attempt if there could be other APs that could accept association. + */ + count = wpa_bssid_ignore_add(wpa_s, bssid); + if (count == 1 && wpa_s->current_bss) { + /* + * This BSS was not in the ignore list before. If there is + * another BSS available for the same ESS, we should try that + * next. Otherwise, we may as well try this one once more + * before allowing other, likely worse, ESSes to be considered. + */ + freqs = get_bss_freqs_in_ess(wpa_s); + if (freqs) { + wpa_dbg(wpa_s, MSG_DEBUG, "Another BSS in this ESS " + "has been seen; try it next"); + wpa_bssid_ignore_add(wpa_s, bssid); + /* + * On the next scan, go through only the known channels + * used in this ESS based on previous scans to speed up + * common load balancing use case. + */ + os_free(wpa_s->next_scan_freqs); + wpa_s->next_scan_freqs = freqs; + } + } + + wpa_s->consecutive_conn_failures++; + + if (wpa_s->consecutive_conn_failures > 3 && wpa_s->current_ssid) { + wpa_printf(MSG_DEBUG, "Continuous association failures - " + "consider temporary network disabling"); + wpas_auth_failed(wpa_s, "CONN_FAILED", bssid); + } + /* + * Multiple consecutive connection failures mean that other APs are + * either not available or have already been tried, so we can start + * increasing the delay here to avoid constant scanning. + */ + switch (wpa_s->consecutive_conn_failures) { + case 1: + timeout = 100; + break; + case 2: + timeout = 500; + break; + case 3: + timeout = 1000; + break; + case 4: + timeout = 5000; + break; + default: + timeout = 10000; + break; + } + + wpa_dbg(wpa_s, MSG_DEBUG, + "Consecutive connection failures: %d --> request scan in %d ms", + wpa_s->consecutive_conn_failures, timeout); + + /* + * TODO: if more than one possible AP is available in scan results, + * could try the other ones before requesting a new scan. + */ + + /* speed up the connection attempt with normal scan */ + wpa_s->normal_scans = 0; + wpa_supplicant_req_scan(wpa_s, timeout / 1000, + 1000 * (timeout % 1000)); +} + + +#ifdef CONFIG_FILS + +void fils_pmksa_cache_flush(struct wpa_supplicant *wpa_s) +{ + struct wpa_ssid *ssid = wpa_s->current_ssid; + const u8 *realm, *username, *rrk; + size_t realm_len, username_len, rrk_len; + u16 next_seq_num; + + /* Clear the PMKSA cache entry if FILS authentication was rejected. + * Check for ERP keys existing to limit when this can be done since + * the rejection response is not protected and such triggers should + * really not allow internal state to be modified unless required to + * avoid significant issues in functionality. In addition, drop + * externally configure PMKSA entries even without ERP keys since it + * is possible for an external component to add PMKSA entries for FILS + * authentication without restoring previously generated ERP keys. + * + * In this case, this is needed to allow recovery from cases where the + * AP or authentication server has dropped PMKSAs and ERP keys. */ + if (!ssid || !ssid->eap.erp || !wpa_key_mgmt_fils(ssid->key_mgmt)) + return; + + if (eapol_sm_get_erp_info(wpa_s->eapol, &ssid->eap, + &username, &username_len, + &realm, &realm_len, &next_seq_num, + &rrk, &rrk_len) != 0 || + !realm) { + wpa_dbg(wpa_s, MSG_DEBUG, + "FILS: Drop external PMKSA cache entry"); + wpa_sm_aborted_external_cached(wpa_s->wpa); + wpa_sm_external_pmksa_cache_flush(wpa_s->wpa, ssid); + return; + } + + wpa_dbg(wpa_s, MSG_DEBUG, "FILS: Drop PMKSA cache entry"); + wpa_sm_aborted_cached(wpa_s->wpa); + wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid); +} + + +void fils_connection_failure(struct wpa_supplicant *wpa_s) +{ + struct wpa_ssid *ssid = wpa_s->current_ssid; + const u8 *realm, *username, *rrk; + size_t realm_len, username_len, rrk_len; + u16 next_seq_num; + + if (!ssid || !ssid->eap.erp || !wpa_key_mgmt_fils(ssid->key_mgmt) || + eapol_sm_get_erp_info(wpa_s->eapol, &ssid->eap, + &username, &username_len, + &realm, &realm_len, &next_seq_num, + &rrk, &rrk_len) != 0 || + !realm) + return; + + wpa_hexdump_ascii(MSG_DEBUG, + "FILS: Store last connection failure realm", + realm, realm_len); + os_free(wpa_s->last_con_fail_realm); + wpa_s->last_con_fail_realm = os_malloc(realm_len); + if (wpa_s->last_con_fail_realm) { + wpa_s->last_con_fail_realm_len = realm_len; + os_memcpy(wpa_s->last_con_fail_realm, realm, realm_len); + } +} +#endif /* CONFIG_FILS */ + + +int wpas_driver_bss_selection(struct wpa_supplicant *wpa_s) +{ + return wpa_s->conf->ap_scan == 2 || + (wpa_s->drv_flags & WPA_DRIVER_FLAGS_BSS_SELECTION); +} + + +#if defined(CONFIG_CTRL_IFACE) || defined(CONFIG_CTRL_IFACE_DBUS_NEW) || defined (CONFIG_CTRL_IFACE_AIDL) +int wpa_supplicant_ctrl_iface_ctrl_rsp_handle(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, + const char *field, + const char *value) +{ +#ifdef IEEE8021X_EAPOL + enum wpa_ctrl_req_type rtype; + + wpa_printf(MSG_DEBUG, "CTRL_IFACE: response handle field=%s", field); + wpa_hexdump_ascii_key(MSG_DEBUG, "CTRL_IFACE: response value", + (const u8 *) value, os_strlen(value)); + + rtype = wpa_supplicant_ctrl_req_from_string(field); + return wpa_supplicant_ctrl_rsp_handle(wpa_s, ssid, rtype, value, strlen(value)); +#else /* IEEE8021X_EAPOL */ + wpa_printf(MSG_DEBUG, "CTRL_IFACE: IEEE 802.1X not included"); + return -1; +#endif /* IEEE8021X_EAPOL */ +} + +int wpa_supplicant_ctrl_rsp_handle(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, + enum wpa_ctrl_req_type rtype, + const char *value, int value_len) +{ +#ifdef IEEE8021X_EAPOL + struct eap_peer_config *eap = &ssid->eap; + char *identity, *imsi_identity; + + switch (rtype) { + case WPA_CTRL_REQ_EAP_IDENTITY: + os_free(eap->identity); + os_free(eap->imsi_identity); + if (value == NULL) + return -1; + identity = os_strchr(value, ':'); + if (identity == NULL) { + /* plain identity */ + eap->identity = (u8 *)os_strdup(value); + eap->identity_len = os_strlen(value); + } else { + /* have both plain identity and encrypted identity */ + imsi_identity = value; + *identity++ = '\0'; + /* plain identity */ + eap->imsi_identity = (u8 *)dup_binstr(imsi_identity, strlen(imsi_identity)); + eap->imsi_identity_len = strlen(imsi_identity); + /* encrypted identity */ + eap->identity = (u8 *)dup_binstr(identity, + value_len - strlen(imsi_identity) - 1); + eap->identity_len = value_len - strlen(imsi_identity) - 1; + } + eap->pending_req_identity = 0; + if (ssid == wpa_s->current_ssid) + wpa_s->reassociate = 1; + break; + case WPA_CTRL_REQ_EAP_PASSWORD: + bin_clear_free(eap->password, eap->password_len); + eap->password = (u8 *) os_strdup(value); + if (!eap->password) + return -1; + eap->password_len = value_len; + eap->pending_req_password = 0; + if (ssid == wpa_s->current_ssid) + wpa_s->reassociate = 1; + break; + case WPA_CTRL_REQ_EAP_NEW_PASSWORD: + bin_clear_free(eap->new_password, eap->new_password_len); + eap->new_password = (u8 *) os_strdup(value); + if (!eap->new_password) + return -1; + eap->new_password_len = value_len; + eap->pending_req_new_password = 0; + if (ssid == wpa_s->current_ssid) + wpa_s->reassociate = 1; + break; + case WPA_CTRL_REQ_EAP_PIN: + str_clear_free(eap->cert.pin); + eap->cert.pin = os_strdup(value); + if (!eap->cert.pin) + return -1; + eap->pending_req_pin = 0; + if (ssid == wpa_s->current_ssid) + wpa_s->reassociate = 1; + break; + case WPA_CTRL_REQ_EAP_OTP: + bin_clear_free(eap->otp, eap->otp_len); + eap->otp = (u8 *) os_strdup(value); + if (!eap->otp) + return -1; + eap->otp_len = value_len; + os_free(eap->pending_req_otp); + eap->pending_req_otp = NULL; + eap->pending_req_otp_len = 0; + break; + case WPA_CTRL_REQ_EAP_PASSPHRASE: + str_clear_free(eap->cert.private_key_passwd); + eap->cert.private_key_passwd = os_strdup(value); + if (!eap->cert.private_key_passwd) + return -1; + eap->pending_req_passphrase = 0; + if (ssid == wpa_s->current_ssid) + wpa_s->reassociate = 1; + break; + case WPA_CTRL_REQ_SIM: + str_clear_free(eap->external_sim_resp); + eap->external_sim_resp = os_strdup(value); + if (!eap->external_sim_resp) + return -1; + eap->pending_req_sim = 0; + break; + case WPA_CTRL_REQ_PSK_PASSPHRASE: + if (wpa_config_set(ssid, "psk", value, 0) < 0) + return -1; + ssid->mem_only_psk = 1; + if (ssid->passphrase) + wpa_config_update_psk(ssid); + if (wpa_s->wpa_state == WPA_SCANNING && !wpa_s->scanning) + wpa_supplicant_req_scan(wpa_s, 0, 0); + break; + case WPA_CTRL_REQ_EXT_CERT_CHECK: + if (eap->pending_ext_cert_check != PENDING_CHECK) + return -1; + if (os_strcmp(value, "good") == 0) + eap->pending_ext_cert_check = EXT_CERT_CHECK_GOOD; + else if (os_strcmp(value, "bad") == 0) + eap->pending_ext_cert_check = EXT_CERT_CHECK_BAD; + else + return -1; + break; + default: + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Unknown type %d", rtype); + return -1; + } + + return 0; +#else /* IEEE8021X_EAPOL */ + wpa_printf(MSG_DEBUG, "CTRL_IFACE: IEEE 802.1X not included"); + return -1; +#endif /* IEEE8021X_EAPOL */ +} +#endif /* CONFIG_CTRL_IFACE || CONFIG_CTRL_IFACE_DBUS_NEW || CONFIG_CTRL_IFACE_AIDL */ + + +int wpas_network_disabled(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) +{ +#ifdef CONFIG_WEP + int i; + unsigned int drv_enc; +#endif /* CONFIG_WEP */ + + if (wpa_s->p2p_mgmt) + return 1; /* no normal network profiles on p2p_mgmt interface */ + + if (ssid == NULL) + return 1; + + if (ssid->disabled) + return 1; + +#ifdef CONFIG_WEP + if (wpa_s->drv_capa_known) + drv_enc = wpa_s->drv_enc; + else + drv_enc = (unsigned int) -1; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + size_t len = ssid->wep_key_len[i]; + if (len == 0) + continue; + if (len == 5 && (drv_enc & WPA_DRIVER_CAPA_ENC_WEP40)) + continue; + if (len == 13 && (drv_enc & WPA_DRIVER_CAPA_ENC_WEP104)) + continue; + if (len == 16 && (drv_enc & WPA_DRIVER_CAPA_ENC_WEP128)) + continue; + return 1; /* invalid WEP key */ + } +#endif /* CONFIG_WEP */ + + if (wpa_key_mgmt_wpa_psk(ssid->key_mgmt) && !ssid->psk_set && + (!ssid->passphrase || ssid->ssid_len != 0) && !ssid->ext_psk && + !(wpa_key_mgmt_sae(ssid->key_mgmt) && ssid->sae_password) && + !ssid->mem_only_psk) + return 1; + +#ifdef IEEE8021X_EAPOL +#ifdef CRYPTO_RSA_OAEP_SHA256 + if (ssid->eap.imsi_privacy_cert) { + struct crypto_rsa_key *key; + bool failed = false; + + key = crypto_rsa_key_read(ssid->eap.imsi_privacy_cert, false); + if (!key) + failed = true; + crypto_rsa_key_free(key); + if (failed) { + wpa_printf(MSG_DEBUG, + "Invalid imsi_privacy_cert (%s) - disable network", + ssid->eap.imsi_privacy_cert); + return 1; + } + } +#endif /* CRYPTO_RSA_OAEP_SHA256 */ +#endif /* IEEE8021X_EAPOL */ + + return 0; +} + + +int wpas_get_ssid_pmf(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) +{ + if (ssid == NULL || ssid->ieee80211w == MGMT_FRAME_PROTECTION_DEFAULT) { + if (wpa_s->conf->pmf == MGMT_FRAME_PROTECTION_OPTIONAL && + !(wpa_s->drv_enc & WPA_DRIVER_CAPA_ENC_BIP)) { + /* + * Driver does not support BIP -- ignore pmf=1 default + * since the connection with PMF would fail and the + * configuration does not require PMF to be enabled. + */ + return NO_MGMT_FRAME_PROTECTION; + } + + if (ssid && + (ssid->key_mgmt & + ~(WPA_KEY_MGMT_NONE | WPA_KEY_MGMT_WPS | + WPA_KEY_MGMT_IEEE8021X_NO_WPA)) == 0) { + /* + * Do not use the default PMF value for non-RSN networks + * since PMF is available only with RSN and pmf=2 + * configuration would otherwise prevent connections to + * all open networks. + */ + return NO_MGMT_FRAME_PROTECTION; + } + +#ifdef CONFIG_OCV + /* Enable PMF if OCV is being enabled */ + if (wpa_s->conf->pmf == NO_MGMT_FRAME_PROTECTION && + ssid && ssid->ocv) + return MGMT_FRAME_PROTECTION_OPTIONAL; +#endif /* CONFIG_OCV */ + + return wpa_s->conf->pmf; + } + + return ssid->ieee80211w; +} + + +#ifdef CONFIG_SAE +bool wpas_is_sae_avoided(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, + const struct wpa_ie_data *ie) +{ + return wpa_s->conf->sae_check_mfp && + (!(ie->capabilities & + (WPA_CAPABILITY_MFPC | WPA_CAPABILITY_MFPR)) || + wpas_get_ssid_pmf(wpa_s, ssid) == NO_MGMT_FRAME_PROTECTION); +} +#endif /* CONFIG_SAE */ + + +int pmf_in_use(struct wpa_supplicant *wpa_s, const u8 *addr) +{ + if (wpa_s->current_ssid == NULL || + wpa_s->wpa_state < WPA_4WAY_HANDSHAKE || + os_memcmp(addr, wpa_s->bssid, ETH_ALEN) != 0) + return 0; + return wpa_sm_pmf_enabled(wpa_s->wpa); +} + + +int wpas_is_p2p_prioritized(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->global->conc_pref == WPA_CONC_PREF_P2P) + return 1; + if (wpa_s->global->conc_pref == WPA_CONC_PREF_STA) + return 0; + return -1; +} + + +void wpas_auth_failed(struct wpa_supplicant *wpa_s, const char *reason, + const u8 *bssid) +{ + struct wpa_ssid *ssid = wpa_s->current_ssid; + int dur; + struct os_reltime now; + + if (ssid == NULL) { + wpa_printf(MSG_DEBUG, "Authentication failure but no known " + "SSID block"); + return; + } + + if (ssid->key_mgmt == WPA_KEY_MGMT_WPS) + return; + + ssid->auth_failures++; + +#ifdef CONFIG_P2P + if (ssid->p2p_group && + (wpa_s->p2p_in_provisioning || wpa_s->show_group_started)) { + /* + * Skip the wait time since there is a short timeout on the + * connection to a P2P group. + */ + return; + } +#endif /* CONFIG_P2P */ + + if (ssid->auth_failures > 50) + dur = 300; + else if (ssid->auth_failures > 10) + dur = 120; + else if (ssid->auth_failures > 5) + dur = 90; + else if (ssid->auth_failures > 3) + dur = 60; + else if (ssid->auth_failures > 2) + dur = 30; + else if (ssid->auth_failures > 1) + dur = 20; + else + dur = 10; + + if (ssid->auth_failures > 1 && + wpa_key_mgmt_wpa_ieee8021x(ssid->key_mgmt)) + dur += os_random() % (ssid->auth_failures * 10); + + os_get_reltime(&now); + if (now.sec + dur <= ssid->disabled_until.sec) + return; + + ssid->disabled_until.sec = now.sec + dur; + + wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_TEMP_DISABLED + "id=%d ssid=\"%s\" auth_failures=%u duration=%d reason=%s", + ssid->id, wpa_ssid_txt(ssid->ssid, ssid->ssid_len), + ssid->auth_failures, dur, reason); + + char *format_str = "id=%d ssid=\"%s\" auth_failures=%u duration=%d reason=%s"; + int msg_len = snprintf(NULL, 0, format_str, + ssid->id, wpa_ssid_txt(ssid->ssid, ssid->ssid_len), + ssid->auth_failures, dur, reason) + 1; + char *msg = os_malloc(msg_len); + if (!msg) + return; + snprintf(msg, msg_len, format_str, + ssid->id, wpa_ssid_txt(ssid->ssid, ssid->ssid_len), + ssid->auth_failures, dur, reason); + wpas_notify_ssid_temp_disabled(wpa_s, msg); + os_free(msg); + + if (bssid) + os_memcpy(ssid->disabled_due_to, bssid, ETH_ALEN); +} + + +void wpas_clear_temp_disabled(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid, int clear_failures) +{ + if (ssid == NULL) + return; + + if (ssid->disabled_until.sec) { + wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_REENABLED + "id=%d ssid=\"%s\"", + ssid->id, wpa_ssid_txt(ssid->ssid, ssid->ssid_len)); + } + ssid->disabled_until.sec = 0; + ssid->disabled_until.usec = 0; + if (clear_failures) { + ssid->auth_failures = 0; + } else if (!is_zero_ether_addr(ssid->disabled_due_to)) { + wpa_printf(MSG_DEBUG, "Mark BSSID " MACSTR + " ignored to allow a lower priority BSS, if any, to be tried next", + MAC2STR(ssid->disabled_due_to)); + wpa_bssid_ignore_add(wpa_s, ssid->disabled_due_to); + os_memset(ssid->disabled_due_to, 0, ETH_ALEN); + } +} + + +int disallowed_bssid(struct wpa_supplicant *wpa_s, const u8 *bssid) +{ + size_t i; + + if (wpa_s->disallow_aps_bssid == NULL) + return 0; + + for (i = 0; i < wpa_s->disallow_aps_bssid_count; i++) { + if (os_memcmp(wpa_s->disallow_aps_bssid + i * ETH_ALEN, + bssid, ETH_ALEN) == 0) + return 1; + } + + return 0; +} + + +int disallowed_ssid(struct wpa_supplicant *wpa_s, const u8 *ssid, + size_t ssid_len) +{ + size_t i; + + if (wpa_s->disallow_aps_ssid == NULL || ssid == NULL) + return 0; + + for (i = 0; i < wpa_s->disallow_aps_ssid_count; i++) { + struct wpa_ssid_value *s = &wpa_s->disallow_aps_ssid[i]; + if (ssid_len == s->ssid_len && + os_memcmp(ssid, s->ssid, ssid_len) == 0) + return 1; + } + + return 0; +} + + +/** + * wpas_request_connection - Request a new connection + * @wpa_s: Pointer to the network interface + * + * This function is used to request a new connection to be found. It will mark + * the interface to allow reassociation and request a new scan to find a + * suitable network to connect to. + */ +void wpas_request_connection(struct wpa_supplicant *wpa_s) +{ + wpa_s->normal_scans = 0; + wpa_s->scan_req = NORMAL_SCAN_REQ; + wpa_supplicant_reinit_autoscan(wpa_s); + wpa_s->disconnected = 0; + wpa_s->reassociate = 1; + wpa_s->last_owe_group = 0; + + if (wpa_supplicant_fast_associate(wpa_s) != 1) + wpa_supplicant_req_scan(wpa_s, 0, 0); + else + wpa_s->reattach = 0; +} + + +/** + * wpas_request_disconnection - Request disconnection + * @wpa_s: Pointer to the network interface + * + * This function is used to request disconnection from the currently connected + * network. This will stop any ongoing scans and initiate deauthentication. + */ +void wpas_request_disconnection(struct wpa_supplicant *wpa_s) +{ +#ifdef CONFIG_SME + wpa_s->sme.prev_bssid_set = 0; +#endif /* CONFIG_SME */ + wpa_s->reassociate = 0; + wpa_s->disconnected = 1; + wpa_supplicant_cancel_sched_scan(wpa_s); + wpa_supplicant_cancel_scan(wpa_s); + wpas_abort_ongoing_scan(wpa_s); + wpa_supplicant_deauthenticate(wpa_s, WLAN_REASON_DEAUTH_LEAVING); + eloop_cancel_timeout(wpas_network_reenabled, wpa_s, NULL); + radio_remove_works(wpa_s, "connect", 0); + radio_remove_works(wpa_s, "sme-connect", 0); + wpa_s->roam_in_progress = false; +#ifdef CONFIG_WNM + wpa_s->bss_trans_mgmt_in_progress = false; +#endif /* CONFIG_WNM */ +} + + +void dump_freq_data(struct wpa_supplicant *wpa_s, const char *title, + struct wpa_used_freq_data *freqs_data, + unsigned int len) +{ + unsigned int i; + + wpa_dbg(wpa_s, MSG_DEBUG, "Shared frequencies (len=%u): %s", + len, title); + for (i = 0; i < len; i++) { + struct wpa_used_freq_data *cur = &freqs_data[i]; + wpa_dbg(wpa_s, MSG_DEBUG, "freq[%u]: %d, flags=0x%X", + i, cur->freq, cur->flags); + } +} + + +/* + * Find the operating frequencies of any of the virtual interfaces that + * are using the same radio as the current interface, and in addition, get + * information about the interface types that are using the frequency. + */ +int get_shared_radio_freqs_data(struct wpa_supplicant *wpa_s, + struct wpa_used_freq_data *freqs_data, + unsigned int len, bool exclude_current) +{ + struct wpa_supplicant *ifs; + u8 bssid[ETH_ALEN]; + int freq; + unsigned int idx = 0, i; + + wpa_dbg(wpa_s, MSG_DEBUG, + "Determining shared radio frequencies (max len %u)", len); + os_memset(freqs_data, 0, sizeof(struct wpa_used_freq_data) * len); + + dl_list_for_each(ifs, &wpa_s->radio->ifaces, struct wpa_supplicant, + radio_list) { + if (idx == len) + break; + + if (exclude_current && ifs == wpa_s) + continue; + + if (ifs->current_ssid == NULL || ifs->assoc_freq == 0) + continue; + + if (ifs->current_ssid->mode == WPAS_MODE_AP || + ifs->current_ssid->mode == WPAS_MODE_P2P_GO || + ifs->current_ssid->mode == WPAS_MODE_MESH) + freq = ifs->current_ssid->frequency; + else if (wpa_drv_get_bssid(ifs, bssid) == 0) + freq = ifs->assoc_freq; + else + continue; + + /* Hold only distinct freqs */ + for (i = 0; i < idx; i++) + if (freqs_data[i].freq == freq) + break; + + if (i == idx) + freqs_data[idx++].freq = freq; + + if (ifs->current_ssid->mode == WPAS_MODE_INFRA) { + freqs_data[i].flags |= ifs->current_ssid->p2p_group ? + WPA_FREQ_USED_BY_P2P_CLIENT : + WPA_FREQ_USED_BY_INFRA_STATION; + } + } + + dump_freq_data(wpa_s, "completed iteration", freqs_data, idx); + return idx; +} + + +/* + * Find the operating frequencies of any of the virtual interfaces that + * are using the same radio as the current interface. + */ +int get_shared_radio_freqs(struct wpa_supplicant *wpa_s, + int *freq_array, unsigned int len, + bool exclude_current) +{ + struct wpa_used_freq_data *freqs_data; + int num, i; + + os_memset(freq_array, 0, sizeof(int) * len); + + freqs_data = os_calloc(len, sizeof(struct wpa_used_freq_data)); + if (!freqs_data) + return -1; + + num = get_shared_radio_freqs_data(wpa_s, freqs_data, len, + exclude_current); + for (i = 0; i < num; i++) + freq_array[i] = freqs_data[i].freq; + + os_free(freqs_data); + + return num; +} + + +struct wpa_supplicant * +wpas_vendor_elem(struct wpa_supplicant *wpa_s, enum wpa_vendor_elem_frame frame) +{ + switch (frame) { +#ifdef CONFIG_P2P + case VENDOR_ELEM_PROBE_REQ_P2P: + case VENDOR_ELEM_PROBE_RESP_P2P: + case VENDOR_ELEM_PROBE_RESP_P2P_GO: + case VENDOR_ELEM_BEACON_P2P_GO: + case VENDOR_ELEM_P2P_PD_REQ: + case VENDOR_ELEM_P2P_PD_RESP: + case VENDOR_ELEM_P2P_GO_NEG_REQ: + case VENDOR_ELEM_P2P_GO_NEG_RESP: + case VENDOR_ELEM_P2P_GO_NEG_CONF: + case VENDOR_ELEM_P2P_INV_REQ: + case VENDOR_ELEM_P2P_INV_RESP: + case VENDOR_ELEM_P2P_ASSOC_REQ: + case VENDOR_ELEM_P2P_ASSOC_RESP: + return wpa_s->p2pdev; +#endif /* CONFIG_P2P */ + default: + return wpa_s; + } +} + + +void wpas_vendor_elem_update(struct wpa_supplicant *wpa_s) +{ + unsigned int i; + char buf[30]; + + wpa_printf(MSG_DEBUG, "Update vendor elements"); + + for (i = 0; i < NUM_VENDOR_ELEM_FRAMES; i++) { + if (wpa_s->vendor_elem[i]) { + int res; + + res = os_snprintf(buf, sizeof(buf), "frame[%u]", i); + if (!os_snprintf_error(sizeof(buf), res)) { + wpa_hexdump_buf(MSG_DEBUG, buf, + wpa_s->vendor_elem[i]); + } + } + } + +#ifdef CONFIG_P2P + if ((wpa_s->parent == wpa_s || (wpa_s == wpa_s->p2pdev && wpa_s->p2p_mgmt)) && + wpa_s->global->p2p && + !wpa_s->global->p2p_disabled) + p2p_set_vendor_elems(wpa_s->global->p2p, wpa_s->vendor_elem); +#endif /* CONFIG_P2P */ +} + + +int wpas_vendor_elem_remove(struct wpa_supplicant *wpa_s, int frame, + const u8 *elem, size_t len) +{ + u8 *ie, *end; + + ie = wpabuf_mhead_u8(wpa_s->vendor_elem[frame]); + end = ie + wpabuf_len(wpa_s->vendor_elem[frame]); + + for (; ie + 1 < end; ie += 2 + ie[1]) { + if (ie + len > end) + break; + if (os_memcmp(ie, elem, len) != 0) + continue; + + if (wpabuf_len(wpa_s->vendor_elem[frame]) == len) { + wpabuf_free(wpa_s->vendor_elem[frame]); + wpa_s->vendor_elem[frame] = NULL; + } else { + os_memmove(ie, ie + len, end - (ie + len)); + wpa_s->vendor_elem[frame]->used -= len; + } + wpas_vendor_elem_update(wpa_s); + return 0; + } + + return -1; +} + + +struct hostapd_hw_modes * get_mode(struct hostapd_hw_modes *modes, + u16 num_modes, enum hostapd_hw_mode mode, + bool is_6ghz) +{ + u16 i; + + if (!modes) + return NULL; + + for (i = 0; i < num_modes; i++) { + if (modes[i].mode != mode || + !modes[i].num_channels || !modes[i].channels) + continue; + if ((!is_6ghz && !is_6ghz_freq(modes[i].channels[0].freq)) || + (is_6ghz && is_6ghz_freq(modes[i].channels[0].freq))) + return &modes[i]; + } + + return NULL; +} + + +struct hostapd_hw_modes * get_mode_with_freq(struct hostapd_hw_modes *modes, + u16 num_modes, int freq) +{ + int i, j; + + for (i = 0; i < num_modes; i++) { + for (j = 0; j < modes[i].num_channels; j++) { + if (freq == modes[i].channels[j].freq) + return &modes[i]; + } + } + + return NULL; +} + + +static struct +wpa_bss_tmp_disallowed * wpas_get_disallowed_bss(struct wpa_supplicant *wpa_s, + const u8 *bssid) +{ + struct wpa_bss_tmp_disallowed *bss; + + dl_list_for_each(bss, &wpa_s->bss_tmp_disallowed, + struct wpa_bss_tmp_disallowed, list) { + if (os_memcmp(bssid, bss->bssid, ETH_ALEN) == 0) + return bss; + } + + return NULL; +} + + +static int wpa_set_driver_tmp_disallow_list(struct wpa_supplicant *wpa_s) +{ + struct wpa_bss_tmp_disallowed *tmp; + unsigned int num_bssid = 0; + u8 *bssids; + int ret; + + bssids = os_malloc(dl_list_len(&wpa_s->bss_tmp_disallowed) * ETH_ALEN); + if (!bssids) + return -1; + dl_list_for_each(tmp, &wpa_s->bss_tmp_disallowed, + struct wpa_bss_tmp_disallowed, list) { + os_memcpy(&bssids[num_bssid * ETH_ALEN], tmp->bssid, + ETH_ALEN); + num_bssid++; + } + ret = wpa_drv_set_bssid_tmp_disallow(wpa_s, num_bssid, bssids); + os_free(bssids); + return ret; +} + + +static void wpa_bss_tmp_disallow_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + struct wpa_bss_tmp_disallowed *tmp, *bss = timeout_ctx; + + /* Make sure the bss is not already freed */ + dl_list_for_each(tmp, &wpa_s->bss_tmp_disallowed, + struct wpa_bss_tmp_disallowed, list) { + if (bss == tmp) { + remove_bss_tmp_disallowed_entry(wpa_s, tmp); + wpa_set_driver_tmp_disallow_list(wpa_s); + break; + } + } +} + + +void wpa_bss_tmp_disallow(struct wpa_supplicant *wpa_s, const u8 *bssid, + unsigned int sec, int rssi_threshold) +{ + struct wpa_bss_tmp_disallowed *bss; + + bss = wpas_get_disallowed_bss(wpa_s, bssid); + if (bss) { + eloop_cancel_timeout(wpa_bss_tmp_disallow_timeout, wpa_s, bss); + goto finish; + } + + bss = os_malloc(sizeof(*bss)); + if (!bss) { + wpa_printf(MSG_DEBUG, + "Failed to allocate memory for temp disallow BSS"); + return; + } + + os_memcpy(bss->bssid, bssid, ETH_ALEN); + dl_list_add(&wpa_s->bss_tmp_disallowed, &bss->list); + wpa_set_driver_tmp_disallow_list(wpa_s); + +finish: + bss->rssi_threshold = rssi_threshold; + eloop_register_timeout(sec, 0, wpa_bss_tmp_disallow_timeout, + wpa_s, bss); +} + + +int wpa_is_bss_tmp_disallowed(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss) +{ + struct wpa_bss_tmp_disallowed *disallowed = NULL, *tmp, *prev; + + dl_list_for_each_safe(tmp, prev, &wpa_s->bss_tmp_disallowed, + struct wpa_bss_tmp_disallowed, list) { + if (os_memcmp(bss->bssid, tmp->bssid, ETH_ALEN) == 0) { + disallowed = tmp; + break; + } + } + if (!disallowed) + return 0; + + if (disallowed->rssi_threshold != 0 && + bss->level > disallowed->rssi_threshold) { + remove_bss_tmp_disallowed_entry(wpa_s, disallowed); + wpa_set_driver_tmp_disallow_list(wpa_s); + return 0; + } + + return 1; +} + + +int wpas_enable_mac_addr_randomization(struct wpa_supplicant *wpa_s, + unsigned int type, const u8 *addr, + const u8 *mask) +{ + if ((addr && !mask) || (!addr && mask)) { + wpa_printf(MSG_INFO, + "MAC_ADDR_RAND_SCAN invalid addr/mask combination"); + return -1; + } + + if (addr && mask && (!(mask[0] & 0x01) || (addr[0] & 0x01))) { + wpa_printf(MSG_INFO, + "MAC_ADDR_RAND_SCAN cannot allow multicast address"); + return -1; + } + + if (type & MAC_ADDR_RAND_SCAN) { + if (wpas_mac_addr_rand_scan_set(wpa_s, MAC_ADDR_RAND_SCAN, + addr, mask)) + return -1; + } + + if (type & MAC_ADDR_RAND_SCHED_SCAN) { + if (wpas_mac_addr_rand_scan_set(wpa_s, MAC_ADDR_RAND_SCHED_SCAN, + addr, mask)) + return -1; + + if (wpa_s->sched_scanning && !wpa_s->pno) + wpas_scan_restart_sched_scan(wpa_s); + } + + if (type & MAC_ADDR_RAND_PNO) { + if (wpas_mac_addr_rand_scan_set(wpa_s, MAC_ADDR_RAND_PNO, + addr, mask)) + return -1; + + if (wpa_s->pno) { + wpas_stop_pno(wpa_s); + wpas_start_pno(wpa_s); + } + } + + return 0; +} + + +int wpas_disable_mac_addr_randomization(struct wpa_supplicant *wpa_s, + unsigned int type) +{ + wpas_mac_addr_rand_scan_clear(wpa_s, type); + if (wpa_s->pno) { + if (type & MAC_ADDR_RAND_PNO) { + wpas_stop_pno(wpa_s); + wpas_start_pno(wpa_s); + } + } else if (wpa_s->sched_scanning && (type & MAC_ADDR_RAND_SCHED_SCAN)) { + wpas_scan_restart_sched_scan(wpa_s); + } + + return 0; +} + + +int wpa_drv_signal_poll(struct wpa_supplicant *wpa_s, + struct wpa_signal_info *si) +{ + int res; + + if (!wpa_s->driver->signal_poll) + return -1; + + res = wpa_s->driver->signal_poll(wpa_s->drv_priv, si); + +#ifdef CONFIG_TESTING_OPTIONS + if (res == 0) { + struct driver_signal_override *dso; + + dl_list_for_each(dso, &wpa_s->drv_signal_override, + struct driver_signal_override, list) { + if (os_memcmp(wpa_s->bssid, dso->bssid, + ETH_ALEN) != 0) + continue; + wpa_printf(MSG_DEBUG, + "Override driver signal_poll information: current_signal: %d->%d avg_signal: %d->%d avg_beacon_signal: %d->%d current_noise: %d->%d", + si->data.signal, + dso->si_current_signal, + si->data.avg_signal, + dso->si_avg_signal, + si->data.avg_beacon_signal, + dso->si_avg_beacon_signal, + si->current_noise, + dso->si_current_noise); + si->data.signal = dso->si_current_signal; + si->data.avg_signal = dso->si_avg_signal; + si->data.avg_beacon_signal = dso->si_avg_beacon_signal; + si->current_noise = dso->si_current_noise; + break; + } + } +#endif /* CONFIG_TESTING_OPTIONS */ + + return res; +} + + +struct wpa_scan_results * +wpa_drv_get_scan_results2(struct wpa_supplicant *wpa_s) +{ + struct wpa_scan_results *scan_res; +#ifdef CONFIG_TESTING_OPTIONS + size_t idx; +#endif /* CONFIG_TESTING_OPTIONS */ + + if (!wpa_s->driver->get_scan_results2) + return NULL; + + scan_res = wpa_s->driver->get_scan_results2(wpa_s->drv_priv); + +#ifdef CONFIG_TESTING_OPTIONS + for (idx = 0; scan_res && idx < scan_res->num; idx++) { + struct driver_signal_override *dso; + struct wpa_scan_res *res = scan_res->res[idx]; + + dl_list_for_each(dso, &wpa_s->drv_signal_override, + struct driver_signal_override, list) { + if (os_memcmp(res->bssid, dso->bssid, ETH_ALEN) != 0) + continue; + wpa_printf(MSG_DEBUG, + "Override driver scan signal level %d->%d for " + MACSTR, + res->level, dso->scan_level, + MAC2STR(res->bssid)); + res->flags |= WPA_SCAN_QUAL_INVALID; + if (dso->scan_level < 0) + res->flags |= WPA_SCAN_LEVEL_DBM; + else + res->flags &= ~WPA_SCAN_LEVEL_DBM; + res->level = dso->scan_level; + break; + } + } +#endif /* CONFIG_TESTING_OPTIONS */ + + return scan_res; +} + + +static bool wpas_ap_link_address(struct wpa_supplicant *wpa_s, const u8 *addr) +{ + int i; + + if (!wpa_s->valid_links) + return false; + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + if (!(wpa_s->valid_links & BIT(i))) + continue; + + if (os_memcmp(wpa_s->links[i].bssid, addr, ETH_ALEN) == 0) + return true; + } + + return false; +} + + +int wpa_drv_send_action(struct wpa_supplicant *wpa_s, unsigned int freq, + unsigned int wait, const u8 *dst, const u8 *src, + const u8 *bssid, const u8 *data, size_t data_len, + int no_cck) +{ + if (!wpa_s->driver->send_action) + return -1; + + if (data_len > 0 && data[0] != WLAN_ACTION_PUBLIC) { + if (wpas_ap_link_address(wpa_s, dst)) + dst = wpa_s->ap_mld_addr; + + if (wpas_ap_link_address(wpa_s, bssid)) + bssid = wpa_s->ap_mld_addr; + } + + return wpa_s->driver->send_action(wpa_s->drv_priv, freq, wait, dst, src, + bssid, data, data_len, no_cck); +} + + +bool wpas_is_6ghz_supported(struct wpa_supplicant *wpa_s, bool only_enabled) +{ + struct hostapd_channel_data *chnl; + int i, j; + + for (i = 0; i < wpa_s->hw.num_modes; i++) { + if (wpa_s->hw.modes[i].mode == HOSTAPD_MODE_IEEE80211A) { + chnl = wpa_s->hw.modes[i].channels; + for (j = 0; j < wpa_s->hw.modes[i].num_channels; j++) { + if (only_enabled && + (chnl[j].flag & HOSTAPD_CHAN_DISABLED)) + continue; + if (is_6ghz_freq(chnl[j].freq)) + return true; + } + } + } + + return false; +} -- Gitee From 5c392ffa50b5de1cb622efe863a35d893468c9ca Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 11:19:19 +0800 Subject: [PATCH 02/10] modify wpa_supplicant_8 --- aosp/external/wpa_supplicant_8/Android.mk | 14 +- .../wpa_supplicant/Android.mk | 4 +- .../wpa_supplicant/aidl/init.wifi.rc | 10 + .../wpa_supplicant/aidl/sta_iface.cpp | 2 +- .../wpa_supplicant/android.config | 6 +- .../wpa_supplicant_8/wpa_supplicant/defconfig | 2 +- .../wpa_supplicant/wpa_drv_helpers.c | 522 ++++++++++++++++++ .../wpa_supplicant/wpa_drv_helpers.h | 12 + .../wpa_supplicant/wpa_supplicant.c | 28 + 9 files changed, 581 insertions(+), 19 deletions(-) diff --git a/aosp/external/wpa_supplicant_8/Android.mk b/aosp/external/wpa_supplicant_8/Android.mk index bb8326cba..426dfff1c 100644 --- a/aosp/external/wpa_supplicant_8/Android.mk +++ b/aosp/external/wpa_supplicant_8/Android.mk @@ -1,15 +1,3 @@ S_LOCAL_PATH := $(call my-dir) -ifneq ($(filter VER_0_8_X VER_2_1_DEVEL,$(WPA_SUPPLICANT_VERSION)),) -# The order of the 2 Android.mks does matter! -# TODO: Clean up the Android.mks, reset all the temporary variables at the -# end of each Android.mk, so that one Android.mk doesn't depend on variables -# set up in the other Android.mk. -include $(S_LOCAL_PATH)/hostapd/Android.mk \ - $(S_LOCAL_PATH)/wpa_supplicant/Android.mk -ifneq ($(TARGET_BUILD_VARIANT), user) -ifeq ($(shell test $(PLATFORM_VERSION_LAST_STABLE) -ge 8 ; echo $$?), 0) -include $(S_LOCAL_PATH)/hs20/client/Android.mk -endif #End of Check for platform version -endif #End of Check for target build variant -endif +include $(S_LOCAL_PATH)/wpa_supplicant/Android.mk diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/Android.mk b/aosp/external/wpa_supplicant_8/wpa_supplicant/Android.mk index 509dbbc07..7d4d81ac4 100644 --- a/aosp/external/wpa_supplicant_8/wpa_supplicant/Android.mk +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/Android.mk @@ -1719,7 +1719,7 @@ endif OBJS += src/drivers/driver_common.c -OBJS += wpa_supplicant.c events.c bssid_ignore.c wpas_glue.c scan.c +OBJS += wpa_supplicant.c events.c bssid_ignore.c wpas_glue.c scan.c wpa_drv_helpers.c OBJS_t := $(OBJS) $(OBJS_l2) eapol_test.c OBJS_t += src/radius/radius_client.c OBJS_t += src/radius/radius.c @@ -1998,6 +1998,8 @@ endif LOCAL_MODULE := wpa_supplicant_macsec endif +LOCAL_INIT_RC=aidl/init.wifi.rc + include $(BUILD_EXECUTABLE) endef diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/init.wifi.rc b/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/init.wifi.rc index e69de29bb..d58284f17 100644 --- a/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/init.wifi.rc +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/init.wifi.rc @@ -0,0 +1,10 @@ +service wpa_supplicant /vendor/bin/hw/wpa_supplicant -Dnl80211 -iwlan0 -c/system/etc/wifi/wpa_supplicant.conf -g@android:wpa_wlan0 + interface aidl android.hardware.wifi.supplicant.ISupplicant/default + socket wpa_wlan0 dgram 660 wifi wifi + group system wifi inet + oneshot + disabled + +on post-fs-data + mkdir /data/vendor/wifi 0771 wifi wifi + mkdir /data/vendor/wifi/wpa 0771 wifi wifi \ No newline at end of file diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/sta_iface.cpp b/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/sta_iface.cpp index a4c2c3649..c4ccf6bd4 100644 --- a/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/sta_iface.cpp +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/aidl/sta_iface.cpp @@ -874,7 +874,7 @@ ndk::ScopedAStatus StaIface::filsHlpFlushRequestInternal() wpas_flush_fils_hlp_req(wpa_s); return ndk::ScopedAStatus::ok(); #else /* CONFIG_FILS */ - return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN, ""); + return createStatus(SupplicantStatusCode::FAILURE_UNKNOWN); #endif /* CONFIG_FILS */ } diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/android.config b/aosp/external/wpa_supplicant_8/wpa_supplicant/android.config index 4cc380844..b7017377f 100644 --- a/aosp/external/wpa_supplicant_8/wpa_supplicant/android.config +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/android.config @@ -503,7 +503,7 @@ CONFIG_WIFI_DISPLAY=y CONFIG_MBO=y # Fast Initial Link Setup (FILS) (IEEE 802.11ai) -CONFIG_FILS=y +#CONFIG_FILS=y # EAP Re-authentication protocol CONFIG_ERP=y @@ -532,14 +532,14 @@ CONFIG_ERP=y #CONFIG_BGSCAN_LEARN=y # Opportunistic Wireless Encryption (OWE) -CONFIG_OWE=y +#CONFIG_OWE=y # Easy Connect (Device Provisioning Protocol - DPP R1 & R2) CONFIG_DPP=y CONFIG_DPP2=y # WPA3-Personal (SAE) -CONFIG_SAE=y +#CONFIG_SAE=y # PASN CONFIG_PASN=y diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/defconfig b/aosp/external/wpa_supplicant_8/wpa_supplicant/defconfig index 1ae5a9f62..f789c79b2 100644 --- a/aosp/external/wpa_supplicant_8/wpa_supplicant/defconfig +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/defconfig @@ -186,7 +186,7 @@ CONFIG_EAP_IKEV2=y #CONFIG_EAP_EKE=y # MACsec -CONFIG_MACSEC=y +#CONFIG_MACSEC=y # PKCS#12 (PFX) support (used to read private key and certificate file from # a file that usually has extension .p12 or .pfx) diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.c b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.c index e69de29bb..a827720b3 100644 --- a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.c +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.c @@ -0,0 +1,522 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved. + * Description: wpa helper function + */ + +#include +#include +#include +#include +#include +#include + +#ifndef BIT +#define BIT(x) (1U << (x)) +#endif + +#include "drivers/driver.h" +#include "wpa_supplicant_i.h" + +static int wpa_helpers_set_countermeasures(void *priv, int enabled) +{ + return 0; +} + +static int wpa_helpers_nl80211_authenticate(void *priv, struct wpa_driver_auth_params *params) +{ + return 0; +} + +static int wpa_helpers_nl80211_associate(void *priv, struct wpa_driver_associate_params *params) +{ + return 0; +} + +static int wpa_helpers_init_mesh(void *priv) +{ + return 0; +} + +static int wpa_helpers_join_mesh(void *priv, struct wpa_driver_mesh_join_params *params) +{ + return 0; +} + +static int wpa_helpers_leave_mesh(void *priv) +{ + return 0; +} + +static int wpa_helpers_probe_mesh_link(void *priv, const u8 *addr, const u8 *eth, size_t len) +{ + return 0; +} + +static int wpa_helpers_scan2(void *priv, struct wpa_driver_scan_params *params) +{ + return 0; +} + +int wpa_helpers_sched_scan(void *priv, struct wpa_driver_scan_params *params) +{ + return 0; +} + +int wpa_helpers_stop_sched_scan(void *priv) +{ + return 0; +} + +static int wpa_helpers_get_bssid(void* wpa_s, u8* bssid) +{ + return 0; +} + +static int wpa_helpers_get_ssid(void* wpa_s, u8* ssid) +{ + return 0; +} + +static int wpa_helpers_set_key(void *priv, struct wpa_driver_set_key_params *params) +{ + return 0; +} + +static int wpa_helpers_get_seqnum(const char *ifname, void *priv, const u8 *addr, + int idx, int link_id, u8 *seq) +{ + return 0; +} + +static int wpa_helpers_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr, + u16 reason, int link_id) +{ + return 0; +} + +static int wpa_helpers_deauthenticate(void *priv, const u8 *addr, u16 reason_code) +{ + return 0; +} + +static int wpa_helpers_add_pmkid(void *priv, struct wpa_pmkid_params *params) +{ + return 0; +} + +static int wpa_helpers_remove_pmkid(void *priv, struct wpa_pmkid_params *params) +{ + return 0; +} + +static int wpa_helpers_flush_pmkid(void *priv) +{ + return 0; +} + +static int wpa_helpers_get_capa(void *priv, struct wpa_driver_capa *capa) +{ + return 0; +} + +static const u8* wpa_helpers_get_mac_addr(void *priv) +{ + static const u8 mac_addr[6] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + return mac_addr; +} + +static int wpa_helpers_send_mlme(void *priv, const u8 *data, size_t data_len, + int noack, unsigned int freq, const u16 *csa_offs, + size_t csa_offs_len, int no_encrypt, + unsigned int wait, int link_id) +{ + return 0; +} + +static int wpa_helpers_update_ft_ies(void *priv, const u8 *md, const u8 *ies, size_t ies_len) +{ + return 0; +} + +static int wpa_helpers_set_ap(void *priv, struct wpa_driver_ap_params *params) +{ + return 0; +} + +static int wpa_helpers_sta_add(void *priv, struct hostapd_sta_add_params *params) +{ + return 0; +} + +static int wpa_helpers_sta_remove(void *priv, const u8 *addr) +{ + return 0; +} + +static int wpa_helpers_tx_control_port(void *priv, const u8 *dest, + u16 proto, const u8 *buf, size_t len, + int no_encrypt, int link_id) +{ + return 0; +} + +static int wpa_helpers_hapd_send_eapol(void *priv, const u8 *addr, const u8 *data, + size_t data_len, int encrypt, + const u8 *own_addr, u32 flags, int link_id) +{ + return 0; +} + +static int wpa_helpers_sta_set_flags(void *priv, const u8 *addr, unsigned int total_flags, unsigned int flags_or, + unsigned int flags_and) +{ + return 0; +} + +static int wpa_helpers_send_action(void *priv, unsigned int freq, unsigned int wait_time, const u8 *dst, + const u8 *src, const u8 *bssid, const u8 *data, size_t data_len, int no_cck) +{ + return 0; +} + +static int wpa_helpers_set_freq(void *priv, struct hostapd_freq_params *freq) +{ + return 0; +} + +static int wpa_wpa_helpers_if_add(void *priv, enum wpa_driver_if_type type, const char *ifname, const u8 *addr, + void *bss_ctx, void **drv_priv, char *force_ifname, u8 *if_addr, + const char *bridge, int use_existing, int setup_ap) +{ + return 0; +} + +static int wpa_helpers_if_remove(void *bss, enum wpa_driver_if_type type, const char *ifname) +{ + return 0; +} + +static int wpa_helpers_remain_on_channel(void *priv, unsigned int freq, unsigned int duration) +{ + return 0; +} + +static int wpa_helpers_cancel_remain_on_channel(void *priv) +{ + return 0; +} + +static int wpa_helpers_probe_req_report(void *priv, int report) +{ + return 0; +} + +static int wpa_helpers_signal_monitor(void *priv, int threshold, int hysteresis) +{ + return 0; +} + +static int wpa_helpers_signal_poll(void *priv, struct wpa_signal_info *si) +{ + return 0; +} + +static int wpa_helpers_channel_info(void *priv, struct wpa_channel_info *ci) +{ + return 0; +} + +static int wpa_helpers_read_sta_data(void *priv, struct hostap_sta_driver_data *data, const u8 *addr) +{ + return 0; +} + +static int wpa_helpers_set_ap_wps_ie(void *priv, const struct wpabuf *beacon, const struct wpabuf *proberesp, + const struct wpabuf *assocresp) +{ + return 0; +} + +static int wpa_helpers_get_noa(void *priv, u8 *buf, size_t buf_len) +{ + return 0; +} + +static int wpa_helpers_set_p2p_powersave(void *priv, int legacy_ps, int opp_ps, int ctwindow) +{ + return 0; +} + +static int wpa_helpers_send_tdls_mgmt(void *priv, const u8 *dst, u8 action_code, + u8 dialog_token, u16 status_code, u32 peer_capab, + int initiator, const u8 *buf, size_t len, + int link_id) +{ + return 0; +} + +static int wpa_helpers_tdls_oper(void *priv, enum tdls_oper oper, const u8 *peer) +{ + return 0; +} + +static int wpa_helpers_driver_cmd(void *priv, char *cmd, char *buf, size_t buf_len) +{ + return 0; +} + +static int wpa_helpers_switch_channel(void *priv, struct csa_settings *settings) +{ + return 0; +} + +static int wpa_helpers_add_ts(void *priv, u8 tsid, const u8 *addr, u8 user_priority, u16 admitted_time) +{ + return 0; +} + +static int wpa_helpers_del_ts(void *priv, u8 tsid, const u8 *addr) +{ + return 0; +} + +static int wpa_helpers_tdls_enable_channel_switch(void *priv, const u8 *addr, u8 oper_class, + const struct hostapd_freq_params *params) +{ + return 0; +} + +static int wpa_helpers_tdls_disable_channel_switch(void *priv, const u8 *addr) +{ + return 0; +} + +static int wpa_helpers_status(void *priv, char *buf, size_t buflen) +{ + return 0; +} + +static int wpa_helpers_set_qos_map(void *priv, const u8 *qos_map_set, u8 qos_map_set_len) +{ + return 0; +} + +static int wpa_helpers_get_wowlan(void *priv) +{ + return 0; +} + +static int wpa_helpers_set_wowlan(void *priv, const struct wowlan_triggers *triggers) +{ + return 0; +} + +static int wpa_helpers_vendor_cmd(void *priv, unsigned int vendor_id, + unsigned int subcmd, const u8 *data, size_t data_len, + enum nested_attr nested_attr_flag, + struct wpabuf *buf) +{ + return 0; +} + +static int wpa_helpers_roaming(void *priv, int allowed, const u8 *bssid) +{ + return 0; +} + +static int wpa_helpers_disable_fils(void *priv, int disable) +{ + return 0; +} + +static int wpa_helpers_set_mac_addr(void *priv, const u8 *addr) +{ + return 0; +} + +static int wpa_helpers_set_band(void *priv, u32 band_mask) +{ + return 0; +} + +static int wpa_helpers_get_pref_freq_list(void *priv, enum wpa_driver_if_type if_type, + unsigned int *num, + struct weighted_pcl *freq_list) +{ + return 0; +} + +static int wpa_helpers_abort_scan(void *priv, u64 scan_cookie) +{ + return 0; +} + +static int wpa_helpers_configure_data_frame_filters(void *priv, u32 filter_flags) +{ + return 0; +} + +static int wpa_helpers_get_ext_capab(void *priv, enum wpa_driver_if_type type, const u8 **ext_capa, + const u8 **ext_capa_mask, unsigned int *ext_capa_len) +{ + return 0; +} + +static int wpa_helpers_p2p_lo_start(void *priv, unsigned int freq, unsigned int period, unsigned int interval, + unsigned int count, const u8 *device_types, size_t dev_types_len, const u8 *ies, size_t ies_len) +{ + return 0; +} + +static int wpa_helpers_p2p_lo_stop(void *priv) +{ + return 0; +} + +static int wpa_helpers_set_default_scan_ies(void *priv, const u8 *ies, size_t ies_len) +{ + return 0; +} + +static int wpa_helpers_set_tdls_mode(void *priv, int tdls_external_control) +{ + return 0; +} + +static int wpa_helpers_ignore_assoc_disallow(void *priv, int ignore_disallow) +{ + return 0; +} + +static int wpa_helpers_set_bssid_blacklist(void *priv, unsigned int num_bssid, const u8 *bssid) +{ + return 0; +} + +static int wpa_helpers_update_connection_params(void *priv, struct wpa_driver_associate_params *params, + enum wpa_drv_update_connect_params_mask mask) +{ + return 0; +} + +static int wpa_helpers_send_external_auth_status(void *priv, struct external_auth *params) +{ + return 0; +} + +static int wpa_helpers_set_4addr_mode(void *priv, const char *bridge_ifname, int val) +{ + return 0; +} + +static struct wpa_driver_ops wpa_helpers_driver = { + .name = "wpa_driver", + .desc = "wpa_driver", + .init2 = NULL, + .deinit = NULL, + .set_param = NULL, + .set_countermeasures = wpa_helpers_set_countermeasures, + .authenticate = wpa_helpers_nl80211_authenticate, + .associate = wpa_helpers_nl80211_associate, + .init_mesh = wpa_helpers_init_mesh, + .join_mesh = wpa_helpers_join_mesh, + .leave_mesh = wpa_helpers_leave_mesh, + .probe_mesh_link = wpa_helpers_probe_mesh_link, + .scan2 = wpa_helpers_scan2, + .sched_scan = wpa_helpers_sched_scan, + .stop_sched_scan = wpa_helpers_stop_sched_scan, + .get_bssid = wpa_helpers_get_bssid, + .get_ssid = wpa_helpers_get_ssid, + .set_key = wpa_helpers_set_key, + .get_seqnum = wpa_helpers_get_seqnum, + .sta_deauth = wpa_helpers_sta_deauth, + .deauthenticate = wpa_helpers_deauthenticate, + .add_pmkid = wpa_helpers_add_pmkid, + .remove_pmkid = wpa_helpers_remove_pmkid, + .flush_pmkid = wpa_helpers_flush_pmkid, + .get_capa = wpa_helpers_get_capa, + .poll = NULL, + .get_ifname = NULL, + .get_radio_name = NULL, + .get_mac_addr = wpa_helpers_get_mac_addr, + .set_operstate = NULL, + .mlme_setprotection = NULL, + .get_hw_feature_data = NULL, + .set_country = NULL, + .send_mlme = wpa_helpers_send_mlme, + .update_ft_ies = wpa_helpers_update_ft_ies, + .set_ap = wpa_helpers_set_ap, + .sta_add = wpa_helpers_sta_add, + .sta_remove = wpa_helpers_sta_remove, + .tx_control_port = wpa_helpers_tx_control_port, + .hapd_send_eapol = wpa_helpers_hapd_send_eapol, + .sta_set_flags = wpa_helpers_sta_set_flags, + .set_supp_port = NULL, + .send_action = wpa_helpers_send_action, + .send_action_cancel_wait = NULL, + .set_freq = wpa_helpers_set_freq, + .if_add = wpa_wpa_helpers_if_add, + .if_remove = wpa_helpers_if_remove, + .remain_on_channel = wpa_helpers_remain_on_channel, + .cancel_remain_on_channel = wpa_helpers_cancel_remain_on_channel, + .probe_req_report = wpa_helpers_probe_req_report, + .deinit_ap = NULL, + .deinit_p2p_cli = NULL, + .suspend = NULL, + .resume = NULL, + .signal_monitor = wpa_helpers_signal_monitor, + .signal_poll = wpa_helpers_signal_poll, + .channel_info = wpa_helpers_channel_info, + .read_sta_data = wpa_helpers_read_sta_data, + .set_ap_wps_ie = wpa_helpers_set_ap_wps_ie, + .get_noa = wpa_helpers_get_noa, + .set_p2p_powersave = wpa_helpers_set_p2p_powersave, + .ampdu = NULL, // todo + .send_tdls_mgmt = wpa_helpers_send_tdls_mgmt, + .tdls_oper = wpa_helpers_tdls_oper, + .driver_cmd = wpa_helpers_driver_cmd, + .set_rekey_info = NULL, + .radio_disable = NULL, // todo + .switch_channel = wpa_helpers_switch_channel, + .add_tx_ts = wpa_helpers_add_ts, + .del_tx_ts = wpa_helpers_del_ts, + .tdls_enable_channel_switch = wpa_helpers_tdls_enable_channel_switch, + .tdls_disable_channel_switch = wpa_helpers_tdls_disable_channel_switch, + .wnm_oper = NULL, // todo + .status = wpa_helpers_status, + .set_qos_map = wpa_helpers_set_qos_map, + .get_wowlan = wpa_helpers_get_wowlan, + .set_wowlan = wpa_helpers_set_wowlan, + .vendor_cmd = wpa_helpers_vendor_cmd, + .roaming = wpa_helpers_roaming, + .disable_fils = wpa_helpers_disable_fils, + .set_mac_addr = wpa_helpers_set_mac_addr, + // CONFIG_MACSEC skip + .set_band = wpa_helpers_set_band, + .get_pref_freq_list = wpa_helpers_get_pref_freq_list, + .set_prob_oper_freq = NULL, + .abort_scan = wpa_helpers_abort_scan, + .configure_data_frame_filters = wpa_helpers_configure_data_frame_filters, + .get_ext_capab = wpa_helpers_get_ext_capab, + .p2p_lo_start = wpa_helpers_p2p_lo_start, + .p2p_lo_stop = wpa_helpers_p2p_lo_stop, + .set_default_scan_ies = wpa_helpers_set_default_scan_ies, + .set_tdls_mode = wpa_helpers_set_tdls_mode, + .get_bss_transition_status = NULL, + .ignore_assoc_disallow = wpa_helpers_ignore_assoc_disallow, + .set_bssid_tmp_disallow = wpa_helpers_set_bssid_blacklist, + .update_connect_params = wpa_helpers_update_connection_params, + .send_external_auth_status = wpa_helpers_send_external_auth_status, + .set_4addr_mode = wpa_helpers_set_4addr_mode, + .dpp_listen = NULL +}; + +struct wpa_supplicant* wpa_helpers_init() +{ + return &wpa_helpers_driver; +} + +bool wpa_helpers_enable() +{ + return true; +} \ No newline at end of file diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.h b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.h index e69de29bb..b66507be1 100644 --- a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.h +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_drv_helpers.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved. + * Description: wpa helper function + */ + +#ifndef HELPER_WPA_HELPERS_H +#define HELPER_WPA_HELPERS_H + +struct wpa_supplicant* wpa_helpers_init(); +bool wpa_helpers_enable(); + +#endif \ No newline at end of file diff --git a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.c b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.c index 6e34e8710..86b8419da 100644 --- a/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.c +++ b/aosp/external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.c @@ -70,6 +70,7 @@ #include "ap/hostapd.h" #endif /* CONFIG_MESH */ #include "aidl/aidl.h" +#include "wpa_drv_helpers.h" const char *const wpa_supplicant_version = "wpa_supplicant v" VERSION_STR "\n" @@ -6945,6 +6946,11 @@ radio_work_pending(struct wpa_supplicant *wpa_s, const char *type) static int wpas_init_driver(struct wpa_supplicant *wpa_s, const struct wpa_interface *iface) { + if (wpa_helpers_enable()) { + const char* rn = "RN"; + wpa_s->driver = wpa_helpers_init(); + goto wpa_helpers_radio; + } const char *ifname, *driver, *rn; driver = iface->driver; @@ -6989,6 +6995,8 @@ next_driver: if (rn && rn[0] == '\0') rn = NULL; +wpa_helpers_radio: + rn = "RN"; wpa_s->radio = radio_add_interface(wpa_s, rn); if (wpa_s->radio == NULL) return -1; @@ -7268,6 +7276,9 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, if (wpa_s->num_multichan_concurrent == 0) wpa_s->num_multichan_concurrent = 1; + if (wpa_helpers_enable()) { + goto wpa_helpers_ctrl_iface; + } if (wpa_supplicant_driver_init(wpa_s) < 0) return -1; @@ -7324,6 +7335,7 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, return -1; wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol); +wpa_helpers_ctrl_iface: wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s); if (wpa_s->ctrl_iface == NULL) { wpa_printf(MSG_ERROR, @@ -7338,6 +7350,9 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, return -1; } + if (wpa_helpers_enable()) { + goto wpa_helpers_bss_init; + } wpa_s->gas = gas_query_init(wpa_s); if (wpa_s->gas == NULL) { wpa_printf(MSG_ERROR, "Failed to initialize GAS query"); @@ -7351,9 +7366,13 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, return -1; } +wpa_helpers_bss_init: if (wpa_bss_init(wpa_s) < 0) return -1; + if (wpa_helpers_enable()) { + goto wpa_helpers_rrm_reset; + } #ifdef CONFIG_PMKSA_CACHE_EXTERNAL #ifdef CONFIG_MESH dl_list_init(&wpa_s->mesh_external_pmksa_cache); @@ -7389,8 +7408,13 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, if (wpas_init_ext_pw(wpa_s) < 0) return -1; +wpa_helpers_rrm_reset: wpas_rrm_reset(wpa_s); + if (wpa_helpers_enable()) { + return 0; + } + wpas_sched_scan_plans_set(wpa_s, wpa_s->conf->sched_scan_plans); #ifdef CONFIG_HS20 @@ -7977,6 +8001,9 @@ struct wpa_global * wpa_supplicant_init(struct wpa_params *params) for (i = 0; wpa_drivers[i]; i++) global->drv_count++; + if (wpa_helpers_enable()) { + goto wpa_helpers_config_wifi; + } if (global->drv_count == 0) { wpa_printf(MSG_ERROR, "No drivers enabled"); wpa_supplicant_deinit(global); @@ -7988,6 +8015,7 @@ struct wpa_global * wpa_supplicant_init(struct wpa_params *params) return NULL; } +wpa_helpers_config_wifi: #ifdef CONFIG_WIFI_DISPLAY if (wifi_display_init(global) < 0) { wpa_printf(MSG_ERROR, "Failed to initialize Wi-Fi Display"); -- Gitee From 56af1a24923d5b3873d24e26d0b75e82a859fc7b Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 11:44:08 +0800 Subject: [PATCH 03/10] frameworks-base orig files --- aosp/frameworks/base/Android.bp | 671 +++++++ .../SettingsProvider/res/values/defaults.xml | 341 ++++ .../net/wifi/nl80211/WifiNl80211Manager.java | 1596 +++++++++++++++++ 3 files changed, 2608 insertions(+) create mode 100644 aosp/frameworks/base/Android.bp create mode 100644 aosp/frameworks/base/packages/SettingsProvider/res/values/defaults.xml create mode 100644 aosp/frameworks/base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java diff --git a/aosp/frameworks/base/Android.bp b/aosp/frameworks/base/Android.bp new file mode 100644 index 000000000..8d7ab9835 --- /dev/null +++ b/aosp/frameworks/base/Android.bp @@ -0,0 +1,671 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Build the master framework library. + +// READ ME: ######################################################## +// +// When updating this list of aidl files, consider if that aidl is +// part of the SDK API. If it is, also add it to the list in Android.mk +// that is preprocessed and distributed with the SDK. This list should +// not contain any aidl files for parcelables, but the one below should +// if you intend for 3rd parties to be able to send those objects +// across process boundaries. +// +// READ ME: ######################################################## + +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +// Added automatically by a large-scale-change that took the approach of +// 'apply every license found to every target'. While this makes sure we respect +// every license restriction, it may not be entirely correct. +// +// e.g. GPL in an MIT project might only apply to the contrib/ directory. +// +// Please consider splitting the single license below into multiple licenses, +// taking care not to lose any license_kind information, and overriding the +// default license using the 'licenses: [...]' property on targets as needed. +// +// For unused files, consider creating a 'fileGroup' with "//visibility:private" +// to attach the license to, and including a comment whether the files may be +// used in the current project. +// See: http://go/android-license-faq +license { + name: "frameworks_base_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + "SPDX-license-identifier-BSD", + "SPDX-license-identifier-CC-BY", + "SPDX-license-identifier-MIT", + "SPDX-license-identifier-Unicode-DFS", + "legacy_unencumbered", + ], + license_text: [ + "NOTICE", + ], +} + +filegroup { + name: "framework-non-updatable-sources", + srcs: [ + // Java/AIDL sources under frameworks/base + ":framework-annotations", + ":ravenwood-annotations", + ":framework-blobstore-sources", + ":framework-core-sources", + ":framework-drm-sources", + ":framework-graphics-nonupdatable-sources", + ":framework-jobscheduler-sources", // jobscheduler is not a module for R + ":framework-keystore-sources", + ":framework-identity-sources", + ":framework-mca-effect-sources", + ":framework-mca-filterfw-sources", + ":framework-mca-filterpacks-sources", + ":framework-media-non-updatable-sources", + ":framework-mms-sources", + ":framework-omapi-sources", + ":framework-opengl-sources", + ":framework-rs-sources", + ":framework-sax-sources", + ":framework-telecomm-sources", + ":framework-telephony-common-sources", + ":framework-telephony-sources", + ":framework-vcn-util-sources", + ":framework-wifi-annotations", + ":framework-wifi-non-updatable-sources", + ":PacProcessor-aidl-sources", + ":ProxyHandler-aidl-sources", + ":net-utils-framework-common-srcs", + + // AIDL from frameworks/base/native/ + ":platform-compat-native-aidl", + + // AIDL sources from external directories + ":android.frameworks.location.altitude-V2-java-source", + ":android.hardware.biometrics.common-V4-java-source", + ":android.hardware.biometrics.fingerprint-V3-java-source", + ":android.hardware.biometrics.face-V4-java-source", + ":android.hardware.gnss-V2-java-source", + ":android.hardware.graphics.common-V3-java-source", + ":android.hardware.keymaster-V4-java-source", + ":android.hardware.radio-V3-java-source", + ":android.hardware.radio.data-V3-java-source", + ":android.hardware.radio.network-V3-java-source", + ":android.hardware.radio.voice-V3-java-source", + ":android.hardware.security.keymint-V3-java-source", + ":android.hardware.security.secureclock-V1-java-source", + ":android.hardware.thermal-V2-java-source", + ":android.hardware.tv.tuner-V2-java-source", + ":android.security.apc-java-source", + ":android.security.authorization-java-source", + ":android.security.legacykeystore-java-source", + ":android.security.maintenance-java-source", + ":android.security.metrics-java-source", + ":android.system.keystore2-V3-java-source", + ":android.hardware.cas-V1-java-source", + ":credstore_aidl", + ":dumpstate_aidl", + ":framework_native_aidl", + ":gatekeeper_aidl", + ":gsiservice_aidl", + ":guiconstants_aidl", + ":idmap2_aidl", + ":idmap2_core_aidl", + ":incidentcompanion_aidl", + ":inputconstants_aidl", + ":installd_aidl", + ":libaudioclient_aidl", + ":libbinder_aidl", + ":libcamera_client_aidl", + ":libcamera_client_framework_aidl", + ":libupdate_engine_aidl", + ":libupdate_engine_stable-V2-java-source", + ":logd_aidl", + ":resourcemanager_aidl", + ":storaged_aidl", + ":vold_aidl", + ":deviceproductinfoconstants_aidl", + + // For the generated R.java and Manifest.java + ":framework-res{.aapt.srcjar}", + + // Java/AIDL sources to be moved out to CrashRecovery module + ":framework-crashrecovery-sources", + + // etc. + ":framework-javastream-protos", + ":statslog-framework-java-gen", // FrameworkStatsLog.java + ":audio_policy_configuration_V7_0", + ":perfetto_trace_javastream_protos", + ], +} + +java_library { + name: "framework-all", + installable: false, + static_libs: [ + "all-framework-module-impl", + "framework-minus-apex", + ], + apex_available: ["//apex_available:platform"], + sdk_version: "core_platform", + visibility: [ + // DO NOT ADD ANY MORE ENTRIES TO THIS LIST + "//external/robolectric-shadows:__subpackages__", + //This will eventually replace the item above, and serves the + //same purpose. + "//external/robolectric:__subpackages__", + "//frameworks/layoutlib:__subpackages__", + + // This is for the same purpose as robolectric -- to build "framework.jar" for host-side + // testing. + // TODO: Once Ravenwood is stable, move the host side jar targets to this directory, + // and remove this line. + "//frameworks/base/tools/hoststubgen:__subpackages__", + ], +} + +// AIDL files under these paths are mixture of public and private ones. +// They shouldn't be exported across module boundaries. +java_defaults { + name: "framework-aidl-export-defaults", + aidl: { + export_include_dirs: [ + "core/java", + "drm/java", + "graphics/java", + "identity/java", + "keystore/java", + "media/java", + "media/mca/effect/java", + "media/mca/filterfw/java", + "media/mca/filterpacks/java", + "mms/java", + "opengl/java", + "rs/java", + "sax/java", + "telecomm/java", + + // TODO(b/147699819): remove this + "telephony/java", + ], + }, +} + +// Collection of classes that are generated from non-Java files that are not listed in +// framework_srcs. These have no or very limited dependency to the framework. +java_library { + name: "framework-internal-utils", + defaults: [ + "android.hardware.power-java_static", + ], + static_libs: [ + "apex_aidl_interface-java", + "packagemanager_aidl-java", + "framework-protos", + "updatable-driver-protos", + "ota_metadata_proto_java", + "android.hidl.base-V1.0-java", + "android.hidl.manager-V1.2-java", + "android.hardware.cas-V1-java", // AIDL + "android.hardware.cas-V1.0-java", + "android.hardware.cas-V1.1-java", + "android.hardware.cas-V1.2-java", + "android.hardware.contexthub-V1.0-java", + "android.hardware.contexthub-V1.1-java", + "android.hardware.contexthub-V1.2-java", + "android.hardware.contexthub-V3-java", + "android.hardware.gnss-V1.0-java", + "android.hardware.gnss-V2.1-java", + "android.hardware.health-V1.0-java-constants", + "android.hardware.radio-V1.6-java", + "android.hardware.radio.data-V3-java", + "android.hardware.radio.ims-V2-java", + "android.hardware.radio.messaging-V3-java", + "android.hardware.radio.modem-V3-java", + "android.hardware.radio.network-V3-java", + "android.hardware.radio.sim-V3-java", + "android.hardware.radio.voice-V3-java", + "android.hardware.thermal-V1.0-java-constants", + "android.hardware.thermal-V1.0-java", + "android.hardware.thermal-V1.1-java", + "android.hardware.thermal-V2.0-java", + "android.hardware.tv.input-V1.0-java-constants", + "android.hardware.usb-V1.0-java-constants", + "android.hardware.usb-V1.1-java-constants", + "android.hardware.usb-V1.2-java-constants", + "android.hardware.usb.gadget-V1-java", + "android.hardware.usb.gadget-V1.0-java", + "android.hardware.usb.gadget-V1.1-java", + "android.hardware.usb.gadget-V1.2-java", + "android.hardware.vibrator-V1.0-java", + "android.hardware.vibrator-V1.1-java", + "android.hardware.vibrator-V1.2-java", + "android.hardware.vibrator-V1.3-java", + "android.hardware.vibrator-V2-java", + "android.se.omapi-V1-java", + "android.system.suspend.control.internal-java", + "devicepolicyprotosnano", + "ImmutabilityAnnotation", + + "com.android.sysprop.init", + "com.android.sysprop.localization", + "PlatformProperties", + ], + sdk_version: "core_platform", + installable: false, +} + +// NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in +// documentation. Do not remove without consulting the treble/hidl teams. +filegroup { + name: "framework-jarjar-rules", + srcs: ["framework-jarjar-rules.txt"], + visibility: ["//visibility:public"], +} + +java_defaults { + name: "framework-minus-apex-defaults", + defaults: [ + "framework-aidl-export-defaults", + "latest_android_hardware_soundtrigger3_java_static", + "framework-minus-apex-aconfig-libraries", + ], + srcs: [ + ":framework-non-updatable-sources", + "core/java/**/*.logtags", + ":apex-info-list", + ], + aidl: { + generate_get_transaction_name: true, + enforce_permissions: true, + enforce_permissions_exceptions: [ + // Do not add entries to this list. + ":framework-annotations", + ":ravenwood-annotations", + ":framework-blobstore-sources", + ":framework-core-sources", + ":framework-drm-sources", + ":framework-graphics-nonupdatable-sources", + ":framework-jobscheduler-sources", + ":framework-keystore-sources", + ":framework-identity-sources", + ":framework-mca-effect-sources", + ":framework-mca-filterfw-sources", + ":framework-mca-filterpacks-sources", + ":framework-media-non-updatable-sources", + ":framework-mms-sources", + ":framework-omapi-sources", + ":framework-opengl-sources", + ":framework-rs-sources", + ":framework-sax-sources", + ":framework-telecomm-sources", + ":framework-telephony-common-sources", + ":framework-telephony-sources", + ":framework-vcn-util-sources", + ":framework-wifi-annotations", + ":framework-wifi-non-updatable-sources", + ":PacProcessor-aidl-sources", + ":ProxyHandler-aidl-sources", + ":net-utils-framework-common-srcs", + ":platform-compat-native-aidl", + ":credstore_aidl", + ":dumpstate_aidl", + ":framework_native_aidl", + ":gatekeeper_aidl", + ":gsiservice_aidl", + ":idmap2_aidl", + ":idmap2_core_aidl", + ":incidentcompanion_aidl", + ":inputconstants_aidl", + ":installd_aidl", + ":libaudioclient_aidl", + ":libbinder_aidl", + ":libcamera_client_aidl", + ":libcamera_client_framework_aidl", + ":libupdate_engine_aidl", + ":logd_aidl", + ":resourcemanager_aidl", + ":storaged_aidl", + ":vold_aidl", + ":deviceproductinfoconstants_aidl", + ], + local_include_dirs: [ + "media/aidl", + ], + include_dirs: [ + "frameworks/av/aidl", + "frameworks/native/libs/permission/aidl", + "packages/modules/Bluetooth/framework/aidl-export", + "packages/modules/Connectivity/framework/aidl-export", + "packages/modules/Media/apex/aidl/stable", + "hardware/interfaces/biometrics/common/aidl", + "hardware/interfaces/biometrics/fingerprint/aidl", + "hardware/interfaces/graphics/common/aidl", + "hardware/interfaces/keymaster/aidl", + "system/hardware/interfaces/media/aidl", + ], + }, + dxflags: [ + "--core-library", + "--multi-dex", + ], + jarjar_rules: ":framework-jarjar-rules", + javac_shard_size: 150, + plugins: [ + "view-inspector-annotation-processor", + "staledataclass-annotation-processor", + "error_prone_android_framework", + ], + // Exports needed for staledataclass-annotation-processor, see b/139342589. + javacflags: [ + "-J--add-modules=jdk.compiler", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], + required: [ + // TODO(b/120066492): remove default_television.xml when the build system + // propagates "required" properly. + "default_television.xml", + // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build + // system propagates "required" properly. + "gps_debug.conf", + "protolog.conf.json.gz", + "core.protolog.pb", + "framework-res", + // any install dependencies should go into framework-minus-apex-install-dependencies + // rather than here to avoid bloating incremental build time + ], + libs: [ + "androidx.annotation_annotation", + "app-compat-annotations", + "ext", + "framework-updatable-stubs-module_libs_api", + "unsupportedappusage", + ], + sdk_version: "core_platform", + static_libs: [ + "android.hardware.common.fmq-V1-java", + "bouncycastle-repackaged-unbundled", + "com.android.sysprop.foldlockbehavior", + "framework-internal-utils", + // If MimeMap ever becomes its own APEX, then this dependency would need to be removed + // in favor of an API stubs dependency in java_library "framework" below. + "mimemap", + "av-types-aidl-java", + "tv_tuner_resource_manager_aidl_interface-java", + "soundtrigger_middleware-aidl-java", + "modules-utils-binary-xml", + "modules-utils-build", + "modules-utils-fastxmlserializer", + "modules-utils-preconditions", + "modules-utils-statemachine", + "modules-utils-synchronous-result-receiver", + "modules-utils-os", + "modules-utils-uieventlogger-interface", + "framework-permission-aidl-java", + "spatializer-aidl-java", + "audiopolicy-aidl-java", + "sounddose-aidl-java", + "modules-utils-expresslog", + ], +} + +// Collection of non updatable unbundled jars. The list here should match +// |non_updatable_modules| variable in frameworks/base/api/api.go. +java_library { + name: "framework-non-updatable-unbundled-impl-libs", + static_libs: [ + "framework-location.impl", + ], + sdk_version: "core_platform", + installable: false, +} + +// Separated so framework-minus-apex-defaults can be used without the libs dependency +java_defaults { + name: "framework-minus-apex-with-libs-defaults", + defaults: ["framework-minus-apex-defaults"], + libs: [ + "framework-virtualization.stubs.module_lib", + "framework-non-updatable-unbundled-impl-libs", + ], +} + +java_defaults { + name: "framework-non-updatable-lint-defaults", + lint: { + extra_check_modules: ["AndroidFrameworkLintChecker"], + disabled_checks: ["ApiMightLeakAppVisibility"], + error_checks: [ + "ClearIdentityCallNotFollowedByTryFinally", + "NestedClearCallingIdentityCalls", + "NonFinalTokenOfOriginalCallingIdentity", + "RestoreIdentityCallNotInFinallyBlock", + "ResultOfClearIdentityCallNotStoredInVariable", + "UnusedTokenOfOriginalCallingIdentity", + "UseOfCallerAwareMethodsWithClearedIdentity", + ], + }, +} + +// we are unfortunately building the turbine jar twice, but more efficient and less complex +// than generating a similar set of stubs with metalava +java_library { + name: "framework-minus-apex-headers", + defaults: ["framework-minus-apex-defaults"], + installable: false, + // For backwards compatibility. + stem: "framework", + apex_available: ["//apex_available:platform"], + visibility: [ + "//frameworks/base:__subpackages__", + ], + compile_dex: false, + headers_only: true, +} + +java_library { + name: "framework-minus-apex", + defaults: [ + "framework-minus-apex-with-libs-defaults", + "framework-non-updatable-lint-defaults", + ], + installable: true, + // For backwards compatibility. + stem: "framework", + apex_available: ["//apex_available:platform"], + visibility: [ + "//frameworks/base", + "//frameworks/base/location", + // TODO(b/147128803) remove the below lines + "//frameworks/base/apex/blobstore/framework", + "//frameworks/base/apex/jobscheduler/framework", + "//frameworks/base/packages/Tethering/tests/unit", + "//packages/modules/Connectivity/Tethering/tests/unit", + ], + errorprone: { + javacflags: [ + "-Xep:AndroidFrameworkCompatChange:ERROR", + "-Xep:AndroidFrameworkUid:ERROR", + ], + }, + lint: { + baseline_filename: "lint-baseline.xml", + }, + jarjar_prefix: "com.android.internal.hidden_from_bootclasspath", +} + +java_library { + name: "framework-minus-apex-intdefs", + defaults: ["framework-minus-apex-with-libs-defaults"], + plugins: ["intdef-annotation-processor"], + + // Errorprone and android lint will already run on framework-minus-apex, don't rerun them on + // the intdefs version in order to speed up the build. + errorprone: { + enabled: false, + }, + lint: { + enabled: false, + + }, +} + +// This "framework" module is NOT installed to the device. It's +// "framework-minus-apex" that gets installed to the device. Note that +// the filename is still framework.jar (via the stem property) for +// compatibility reason. The purpose of this module is to provide +// framework APIs (both public and private) for bundled apps. +// "framework-minus-apex" can't be used for the purpose because 1) +// many apps have already hardcoded the name "framework" and +// 2) it lacks API symbols from updatable modules - as it's clear from +// its suffix "-minus-apex". +java_library { + name: "framework", + defaults: ["framework-aidl-export-defaults"], + installable: false, // this lib is a build-only library + static_libs: [ + "app-compat-annotations", + "framework-minus-apex", + "framework-non-updatable-unbundled-impl-libs", + "framework-updatable-stubs-module_libs_api", + ], + sdk_version: "core_platform", + apex_available: ["//apex_available:platform"], +} + +java_library { + name: "framework-minus-apex-install-dependencies", + required: [ + "framework-minus-apex", + "framework-platform-compat-config", + "framework-location-compat-config", + "services-platform-compat-config", + "icu4j-platform-compat-config", + "TeleService-platform-compat-config", + "documents-ui-compat-config", + "calendar-provider-compat-config", + "contacts-provider-platform-compat-config", + ], +} + +platform_compat_config { + name: "framework-platform-compat-config", + src: ":framework-minus-apex", +} + +filegroup { + name: "framework-ike-shared-srcs", + visibility: ["//packages/modules/IPsec"], + srcs: [ + "core/java/com/android/internal/util/HexDump.java", + "core/java/com/android/internal/util/WakeupMessage.java", + "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java", + "telephony/java/android/telephony/Annotation.java", + ], +} + +filegroup { + name: "framework-android-os-unit-testable-src", + srcs: [ + "core/java/android/os/DdmSyncState.java", + ], +} + +filegroup { + name: "framework-networkstack-shared-srcs", + srcs: [ + // TODO: remove these annotations as soon as we can use andoid.support.annotations.* + ":framework-annotations", + ":modules-utils-preconditions-srcs", + "core/java/android/util/IndentingPrintWriter.java", + "core/java/android/util/LocalLog.java", + "core/java/com/android/internal/util/HexDump.java", + "core/java/com/android/internal/util/IndentingPrintWriter.java", + "core/java/com/android/internal/util/MessageUtils.java", + "core/java/com/android/internal/util/RingBufferIndices.java", + "core/java/com/android/internal/util/WakeupMessage.java", + "core/java/com/android/internal/util/TokenBucket.java", + ], +} + +// Build ext.jar +// ============================================================ +java_library { + name: "ext", + installable: true, + sdk_version: "core_platform", + static_libs: [ + "libphonenumber-platform", + "tagsoup", + "rappor", + ], + dxflags: ["--core-library"], +} + +// utility classes statically linked into framework-wifi and dynamically linked +// into wifi-service +java_library { + name: "framework-wifi-util-lib", + sdk_version: "module_current", + min_sdk_version: "30", + srcs: [ + "core/java/com/android/internal/util/AsyncChannel.java", + "core/java/com/android/internal/util/AsyncService.java", + "core/java/com/android/internal/util/Protocol.java", + "telephony/java/android/telephony/Annotation.java", + ":net-utils-framework-wifi-common-srcs", + ], + libs: [ + "framework-annotations-lib", + "framework-connectivity.stubs.module_lib", + "unsupportedappusage", + ], + visibility: [ + "//frameworks/base/wifi", + "//frameworks/base/services/net", + "//packages/modules/Wifi/framework", + ], + lint: { + baseline_filename: "lint-baseline.xml", + }, + apex_available: ["com.android.wifi"], +} + +filegroup { + name: "android-non-updatable-stub-sources", + srcs: [ + ":framework-mime-sources", // mimemap builds separately but has no separate droidstubs. + ":framework-minus-apex-aconfig-srcjars", + ":framework-non-updatable-sources", + ":opt-telephony-srcs", + ":opt-net-voip-srcs", + "core/java/**/*.logtags", + "**/package.html", + ], + visibility: ["//frameworks/base/api"], +} + +build = [ + "AconfigFlags.bp", + "ProtoLibraries.bp", + "TestProtoLibraries.bp", + "Ravenwood.bp", +] diff --git a/aosp/frameworks/base/packages/SettingsProvider/res/values/defaults.xml b/aosp/frameworks/base/packages/SettingsProvider/res/values/defaults.xml new file mode 100644 index 000000000..17d9f1b87 --- /dev/null +++ b/aosp/frameworks/base/packages/SettingsProvider/res/values/defaults.xml @@ -0,0 +1,341 @@ + + + + true + 60000 + -1 + false + false + + cell,bluetooth,uwb,wifi,wimax + bluetooth,wifi + + 0 + 0 + true + true + false + + 102 + false + 100% + 100% + true + + true + false + false + + 3 + + 1 + true + true + true + false + + 2 + true + true + + false + com.android.localtransport/.LocalTransport + + + true + + true + false + true + true + + + 1 + /product/media/audio/ui/LowBattery.ogg + 0 + 0 + /product/media/audio/ui/Dock.ogg + /product/media/audio/ui/Undock.ogg + /product/media/audio/ui/Dock.ogg + /product/media/audio/ui/Undock.ogg + 1 + /product/media/audio/ui/Lock.ogg + /product/media/audio/ui/Unlock.ogg + /product/media/audio/ui/Trusted.ogg + /product/media/audio/ui/WirelessChargingStarted.ogg + /product/media/audio/ui/ChargingStarted.ogg + + + 1000 + 15000 + + false + false + 1 + + + true + + + true + + + true + + + true + + + false + + + 200% + + + false + + + true + + + 0 + + + -1 + + -1 + + + 400 + + + 300 + + + false + + + 0 + + + true + + true + + + false + + + 9 + + + false + + + 0 + + + + + + 0 + + + 1 + + + true + + + false + + + 1 + + + %1$s %2$s + + + %1$s + + + true + + + true + + + + + + false + + + 0x2 + + + false + + + + + + true + + + + + + 0 + + + + + + false + + + 1 + + + false + + + true + + + true + + + true + + + false + + + false + + + false + + + false + + + false + + + false + + + 3 + + + false + + + 1 + + + false + + + true + + + + + false + + true + + 120 + + + true + + + true + + + 4 + + + 3 + + + 1 + + + true + + + true + + + true + + + true + + + false + + + 0 + + + false + + + false + + + -1 + + + -1 + + + 1.0 + diff --git a/aosp/frameworks/base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/aosp/frameworks/base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java new file mode 100644 index 000000000..58638e8e1 --- /dev/null +++ b/aosp/frameworks/base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -0,0 +1,1596 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.AlarmManager; +import android.content.Context; +import android.net.wifi.SoftApInfo; +import android.net.wifi.WifiAnnotations; +import android.net.wifi.WifiScanner; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework - used + * to encapsulate the Wi-Fi 80211nl management interface. The + * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions. + * + * @hide + */ +@SystemApi +@SystemService(Context.WIFI_NL80211_SERVICE) +public class WifiNl80211Manager { + private static final String TAG = "WifiNl80211Manager"; + private boolean mVerboseLoggingEnabled = false; + + /** + * The {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} + * timeout, in milliseconds, after which + * {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason + * {@link #SEND_MGMT_FRAME_ERROR_TIMEOUT}. + */ + private static final int SEND_MGMT_FRAME_TIMEOUT_MS = 1000; + + private static final String TIMEOUT_ALARM_TAG = TAG + " Send Management Frame Timeout"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SCAN_TYPE_"}, + value = {SCAN_TYPE_SINGLE_SCAN, + SCAN_TYPE_PNO_SCAN}) + public @interface ScanResultType {} + + /** + * Specifies a scan type: single scan initiated by the framework. Can be used in + * {@link #getScanResults(String, int)} to specify the type of scan result to fetch. + */ + public static final int SCAN_TYPE_SINGLE_SCAN = 0; + + /** + * Specifies a scan type: PNO scan. Can be used in {@link #getScanResults(String, int)} to + * specify the type of scan result to fetch. + */ + public static final int SCAN_TYPE_PNO_SCAN = 1; + + // Extra scanning parameter used to enable 6Ghz RNR (Reduced Neighbour Support). + public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = + "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR"; + + // Extra scanning parameter used to add vendor IEs (byte[]). + public static final String EXTRA_SCANNING_PARAM_VENDOR_IES = + "android.net.wifi.nl80211.extra.SCANNING_PARAM_VENDOR_IES"; + + private AlarmManager mAlarmManager; + private Handler mEventHandler; + + // Cached wificond binder handlers. + private IWificond mWificond; + private WificondEventHandler mWificondEventHandler = new WificondEventHandler(); + private HashMap mClientInterfaces = new HashMap<>(); + private HashMap mApInterfaces = new HashMap<>(); + private HashMap mWificondScanners = new HashMap<>(); + private HashMap mScanEventHandlers = new HashMap<>(); + private HashMap mPnoScanEventHandlers = new HashMap<>(); + private HashMap mApInterfaceListeners = new HashMap<>(); + private Runnable mDeathEventHandler; + private Object mLock = new Object(); + /** + * Ensures that no more than one sendMgmtFrame operation runs concurrently. + */ + private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false); + + /** + * Interface used to listen country code event + */ + public interface CountryCodeChangedListener { + /** + * Called when country code changed. + * + * @param countryCode An ISO-3166-alpha2 country code which is 2-Character alphanumeric. + */ + void onCountryCodeChanged(@NonNull String countryCode); + } + + /** + * Interface used when waiting for scans to be completed (with results). + */ + public interface ScanEventCallback { + /** + * Called when scan results are available. Scans results should then be obtained from + * {@link #getScanResults(String, int)}. + */ + void onScanResultReady(); + + /** + * Deprecated in Android 14. Newer wificond implementation should call + * onScanRequestFailed(). + * Called when a scan has failed. + * @deprecated The usage is replaced by {@link ScanEventCallback#onScanFailed(int)} + */ + + void onScanFailed(); + /** + * Called when a scan has failed with errorCode. + */ + default void onScanFailed(int errorCode) {} + } + + /** + * Interface for a callback to provide information about PNO scan request requested with + * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. Note that the + * callback are for the status of the request - not the scan itself. The results of the scan + * are returned with {@link ScanEventCallback}. + */ + public interface PnoScanRequestCallback { + /** + * Called when a PNO scan request has been successfully submitted. + */ + void onPnoRequestSucceeded(); + + /** + * Called when a PNO scan request fails. + */ + void onPnoRequestFailed(); + } + + /** @hide */ + @VisibleForTesting + public class WificondEventHandler extends IWificondEventCallback.Stub { + private Map mCountryCodeChangedListenerHolder = + new HashMap<>(); + + /** + * Register CountryCodeChangedListener with pid. + * + * @param executor The Executor on which to execute the callbacks. + * @param listener listener for country code changed events. + */ + public void registerCountryCodeChangedListener(Executor executor, + CountryCodeChangedListener listener) { + mCountryCodeChangedListenerHolder.put(listener, executor); + } + + /** + * Unregister CountryCodeChangedListener with pid. + * + * @param listener listener which registered country code changed events. + */ + public void unregisterCountryCodeChangedListener(CountryCodeChangedListener listener) { + mCountryCodeChangedListenerHolder.remove(listener); + } + + @Override + public void OnRegDomainChanged(String countryCode) { + Log.d(TAG, "OnRegDomainChanged " + countryCode); + final long token = Binder.clearCallingIdentity(); + try { + mCountryCodeChangedListenerHolder.forEach((listener, executor) -> { + executor.execute(() -> listener.onCountryCodeChanged(countryCode)); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + private class ScanEventHandler extends IScanEvent.Stub { + private Executor mExecutor; + private ScanEventCallback mCallback; + + ScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void OnScanResultReady() { + Log.d(TAG, "Scan result ready event"); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onScanResultReady()); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void OnScanFailed() { + Log.d(TAG, "Scan failed event"); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onScanFailed()); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void OnScanRequestFailed(int errorCode) { + Log.d(TAG, "Scan failed event with error code: " + errorCode); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onScanFailed( + toFrameworkScanStatusCode(errorCode))); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + /** + * Result of a signal poll requested using {@link #signalPoll(String)}. + * + * @deprecated The usage is replaced by + * {@code com.android.server.wifi.WifiSignalPollResults}. + */ + @Deprecated + public static class SignalPollResult { + /** @hide */ + public SignalPollResult(int currentRssiDbm, int txBitrateMbps, int rxBitrateMbps, + int associationFrequencyMHz) { + this.currentRssiDbm = currentRssiDbm; + this.txBitrateMbps = txBitrateMbps; + this.rxBitrateMbps = rxBitrateMbps; + this.associationFrequencyMHz = associationFrequencyMHz; + } + + /** + * RSSI value in dBM. + */ + public final int currentRssiDbm; + + /** + * Transmission bit rate in Mbps. + */ + public final int txBitrateMbps; + + /** + * Last received packet bit rate in Mbps. + */ + public final int rxBitrateMbps; + + /** + * Association frequency in MHz. + */ + public final int associationFrequencyMHz; + } + + /** + * Transmission counters obtained using {@link #getTxPacketCounters(String)}. + */ + public static class TxPacketCounters { + /** @hide */ + public TxPacketCounters(int txPacketSucceeded, int txPacketFailed) { + this.txPacketSucceeded = txPacketSucceeded; + this.txPacketFailed = txPacketFailed; + } + + /** + * Number of successfully transmitted packets. + */ + public final int txPacketSucceeded; + + /** + * Number of packet transmission failures. + */ + public final int txPacketFailed; + } + + /** + * Callbacks for SoftAp interface registered using + * {@link #registerApCallback(String, Executor, SoftApCallback)}. + * + * @deprecated The usage is replaced by vendor HAL + * {@code android.hardware.wifi.hostapd.V1_3.IHostapdCallback}. + */ + @Deprecated + public interface SoftApCallback { + /** + * Invoked when there is a fatal failure and the SoftAp is shutdown. + */ + void onFailure(); + + /** + * Invoked when there is a change in the associated station (STA). + * @param client Information about the client whose status has changed. + * @param isConnected Indication as to whether the client is connected (true), or + * disconnected (false). + */ + void onConnectedClientsChanged(@NonNull NativeWifiClient client, boolean isConnected); + + /** + * Invoked when a channel switch event happens - i.e. the SoftAp is moved to a different + * channel. Also called on initial registration. + * @param frequencyMhz The new frequency of the SoftAp. A value of 0 is invalid and is an + * indication that the SoftAp is not enabled. + * @param bandwidth The new bandwidth of the SoftAp. + */ + void onSoftApChannelSwitched(int frequencyMhz, @WifiAnnotations.Bandwidth int bandwidth); + } + + /** + * Callback to notify the results of a + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} call. + * Note: no callbacks will be triggered if the interface dies while sending a frame. + */ + public interface SendMgmtFrameCallback { + /** + * Called when the management frame was successfully sent and ACKed by the recipient. + * @param elapsedTimeMs The elapsed time between when the management frame was sent and when + * the ACK was processed, in milliseconds, as measured by wificond. + * This includes the time that the send frame spent queuing before it + * was sent, any firmware retries, and the time the received ACK spent + * queuing before it was processed. + */ + void onAck(int elapsedTimeMs); + + /** + * Called when the send failed. + * @param reason The error code for the failure. + */ + void onFailure(@SendMgmtFrameError int reason); + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SEND_MGMT_FRAME_ERROR_"}, + value = {SEND_MGMT_FRAME_ERROR_UNKNOWN, + SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED, + SEND_MGMT_FRAME_ERROR_NO_ACK, + SEND_MGMT_FRAME_ERROR_TIMEOUT, + SEND_MGMT_FRAME_ERROR_ALREADY_STARTED}) + public @interface SendMgmtFrameError {} + + // Send management frame error codes + + /** + * Unknown error occurred during call to + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}. + */ + public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; + + /** + * Specifying the MCS rate in + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} is not + * supported by this device. + */ + public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2; + + /** + * Driver reported that no ACK was received for the frame transmitted using + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}. + */ + public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3; + + /** + * Error code for when the driver fails to report on the status of the frame sent by + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} + * after {@link #SEND_MGMT_FRAME_TIMEOUT_MS} milliseconds. + */ + public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4; + + /** + * An existing call to + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} + * is in progress. Another frame cannot be sent until the first call completes. + */ + public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5; + + /** @hide */ + public WifiNl80211Manager(Context context) { + mAlarmManager = context.getSystemService(AlarmManager.class); + mEventHandler = new Handler(context.getMainLooper()); + } + + /** + * Construct WifiNl80211Manager with giving context and binder which is an interface of + * IWificond. + * + * @param context Android context. + * @param binder a binder of IWificond. + */ + public WifiNl80211Manager(@NonNull Context context, @NonNull IBinder binder) { + this(context); + mWificond = IWificond.Stub.asInterface(binder); + if (mWificond == null) { + Log.e(TAG, "Failed to get reference to wificond"); + } + } + + /** @hide */ + @VisibleForTesting + public WifiNl80211Manager(Context context, IWificond wificond) { + this(context); + mWificond = wificond; + } + + /** @hide */ + @VisibleForTesting + public WificondEventHandler getWificondEventHandler() { + return mWificondEventHandler; + } + + private class PnoScanEventHandler extends IPnoScanEvent.Stub { + private Executor mExecutor; + private ScanEventCallback mCallback; + + PnoScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void OnPnoNetworkFound() { + Log.d(TAG, "Pno scan result event"); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onScanResultReady()); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void OnPnoScanFailed() { + Log.d(TAG, "Pno Scan failed event"); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onScanFailed()); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + /** + * Listener for AP Interface events. + */ + private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub { + private Executor mExecutor; + private SoftApCallback mSoftApListener; + + ApInterfaceEventCallback(Executor executor, SoftApCallback listener) { + mExecutor = executor; + mSoftApListener = listener; + } + + @Override + public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "onConnectedClientsChanged called with " + + client.getMacAddress() + " isConnected: " + isConnected); + } + + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mSoftApListener.onConnectedClientsChanged(client, isConnected)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onSoftApChannelSwitched(int frequency, int bandwidth) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mSoftApListener.onSoftApChannelSwitched(frequency, + toFrameworkBandwidth(bandwidth))); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private @WifiAnnotations.Bandwidth int toFrameworkBandwidth(int bandwidth) { + switch(bandwidth) { + case IApInterfaceEventCallback.BANDWIDTH_INVALID: + return SoftApInfo.CHANNEL_WIDTH_INVALID; + case IApInterfaceEventCallback.BANDWIDTH_20_NOHT: + return SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT; + case IApInterfaceEventCallback.BANDWIDTH_20: + return SoftApInfo.CHANNEL_WIDTH_20MHZ; + case IApInterfaceEventCallback.BANDWIDTH_40: + return SoftApInfo.CHANNEL_WIDTH_40MHZ; + case IApInterfaceEventCallback.BANDWIDTH_80: + return SoftApInfo.CHANNEL_WIDTH_80MHZ; + case IApInterfaceEventCallback.BANDWIDTH_80P80: + return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ; + case IApInterfaceEventCallback.BANDWIDTH_160: + return SoftApInfo.CHANNEL_WIDTH_160MHZ; + case IApInterfaceEventCallback.BANDWIDTH_320: + return SoftApInfo.CHANNEL_WIDTH_320MHZ; + default: + return SoftApInfo.CHANNEL_WIDTH_INVALID; + } + } + } + + /** + * Callback triggered by wificond. + */ + private class SendMgmtFrameEvent extends ISendMgmtFrameEvent.Stub { + private Executor mExecutor; + private SendMgmtFrameCallback mCallback; + private AlarmManager.OnAlarmListener mTimeoutCallback; + /** + * ensures that mCallback is only called once + */ + private boolean mWasCalled; + + private void runIfFirstCall(Runnable r) { + if (mWasCalled) return; + mWasCalled = true; + + mSendMgmtFrameInProgress.set(false); + r.run(); + } + + SendMgmtFrameEvent(@NonNull Executor executor, @NonNull SendMgmtFrameCallback callback) { + mExecutor = executor; + mCallback = callback; + // called in main thread + mTimeoutCallback = () -> runIfFirstCall(() -> { + if (mVerboseLoggingEnabled) { + Log.e(TAG, "Timed out waiting for ACK"); + } + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT)); + } finally { + Binder.restoreCallingIdentity(token); + } + }); + mWasCalled = false; + + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + SEND_MGMT_FRAME_TIMEOUT_MS, + TIMEOUT_ALARM_TAG, mTimeoutCallback, mEventHandler); + } + + // called in binder thread + @Override + public void OnAck(int elapsedTimeMs) { + // post to main thread + mEventHandler.post(() -> runIfFirstCall(() -> { + mAlarmManager.cancel(mTimeoutCallback); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onAck(elapsedTimeMs)); + } finally { + Binder.restoreCallingIdentity(token); + } + })); + } + + // called in binder thread + @Override + public void OnFailure(int reason) { + // post to main thread + mEventHandler.post(() -> runIfFirstCall(() -> { + mAlarmManager.cancel(mTimeoutCallback); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onFailure(reason)); + } finally { + Binder.restoreCallingIdentity(token); + } + })); + } + } + + /** + * Called by the binder subsystem upon remote object death. + * Invoke all the register death handlers and clear state. + * @hide + */ + @VisibleForTesting + public void binderDied() { + mEventHandler.post(() -> { + synchronized (mLock) { + Log.e(TAG, "Wificond died!"); + clearState(); + // Invalidate the global wificond handle on death. Will be refreshed + // on the next setup call. + mWificond = null; + if (mDeathEventHandler != null) { + mDeathEventHandler.run(); + } + } + }); + } + + /** + * Enable or disable verbose logging of the WifiNl80211Manager module. + * @param enable True to enable verbose logging. False to disable verbose logging. + */ + public void enableVerboseLogging(boolean enable) { + mVerboseLoggingEnabled = enable; + } + + /** + * Register a death notification for the WifiNl80211Manager which acts as a proxy for the + * wificond daemon (i.e. the death listener will be called when and if the wificond daemon + * dies). + * + * @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies. + */ + public void setOnServiceDeadCallback(@NonNull Runnable deathEventHandler) { + if (mDeathEventHandler != null) { + Log.e(TAG, "Death handler already present"); + } + mDeathEventHandler = deathEventHandler; + } + + /** + * Helper method to retrieve the global wificond handle and register for + * death notifications. + */ + private boolean retrieveWificondAndRegisterForDeath() { + if (mWificond != null) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Wificond handle already retrieved"); + } + // We already have a wificond handle. + return true; + } + IBinder binder = ServiceManager.getService(Context.WIFI_NL80211_SERVICE); + mWificond = IWificond.Stub.asInterface(binder); + if (mWificond == null) { + Log.e(TAG, "Failed to get reference to wificond"); + return false; + } + try { + mWificond.asBinder().linkToDeath(() -> binderDied(), 0); + mWificond.registerWificondEventCallback(mWificondEventHandler); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register death notification for wificond"); + // The remote has already died. + return false; + } + return true; + } + + /** + * Set up an interface for client (STA) mode. + * + * @param ifaceName Name of the interface to configure. + * @param executor The Executor on which to execute the callbacks. + * @param scanCallback A callback for framework initiated scans. + * @param pnoScanCallback A callback for PNO (offloaded) scans. + * @return true on success. + */ + public boolean setupInterfaceForClientMode(@NonNull String ifaceName, + @NonNull @CallbackExecutor Executor executor, + @NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) { + Log.d(TAG, "Setting up interface for client mode: " + ifaceName); + if (!retrieveWificondAndRegisterForDeath()) { + return false; + } + + if (scanCallback == null || pnoScanCallback == null || executor == null) { + Log.e(TAG, "setupInterfaceForClientMode invoked with null callbacks"); + return false; + } + + IClientInterface clientInterface = null; + try { + clientInterface = mWificond.createClientInterface(ifaceName); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to get IClientInterface due to remote exception"); + return false; + } + + if (clientInterface == null) { + Log.e(TAG, "Could not get IClientInterface instance from wificond"); + return false; + } + Binder.allowBlocking(clientInterface.asBinder()); + + // Refresh Handlers + mClientInterfaces.put(ifaceName, clientInterface); + try { + IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl(); + if (wificondScanner == null) { + Log.e(TAG, "Failed to get WificondScannerImpl"); + return false; + } + mWificondScanners.put(ifaceName, wificondScanner); + Binder.allowBlocking(wificondScanner.asBinder()); + ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback); + mScanEventHandlers.put(ifaceName, scanEventHandler); + wificondScanner.subscribeScanEvents(scanEventHandler); + PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(executor, + pnoScanCallback); + mPnoScanEventHandlers.put(ifaceName, pnoScanEventHandler); + wificondScanner.subscribePnoScanEvents(pnoScanEventHandler); + } catch (RemoteException e) { + Log.e(TAG, "Failed to refresh wificond scanner due to remote exception"); + } + + return true; + } + + /** + * Tear down a specific client (STA) interface configured using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}. + * + * @param ifaceName Name of the interface to tear down. + * @return Returns true on success, false on failure (e.g. when called before an interface was + * set up). + */ + public boolean tearDownClientInterface(@NonNull String ifaceName) { + if (getClientInterface(ifaceName) == null) { + Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName); + return false; + } + try { + IWifiScannerImpl scannerImpl = mWificondScanners.get(ifaceName); + if (scannerImpl != null) { + scannerImpl.unsubscribeScanEvents(); + scannerImpl.unsubscribePnoScanEvents(); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to unsubscribe wificond scanner due to remote exception"); + return false; + } + + if (mWificond == null) { + Log.e(TAG, "tearDownClientInterface: mWificond binder is null! Did wificond die?"); + return false; + } + + boolean success; + try { + success = mWificond.tearDownClientInterface(ifaceName); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to teardown client interface due to remote exception"); + return false; + } + if (!success) { + Log.e(TAG, "Failed to teardown client interface"); + return false; + } + + mClientInterfaces.remove(ifaceName); + mWificondScanners.remove(ifaceName); + mScanEventHandlers.remove(ifaceName); + mPnoScanEventHandlers.remove(ifaceName); + return true; + } + + /** + * Set up interface as a Soft AP. + * + * @param ifaceName Name of the interface to configure. + * @return true on success. + */ + public boolean setupInterfaceForSoftApMode(@NonNull String ifaceName) { + Log.d(TAG, "Setting up interface for soft ap mode for iface=" + ifaceName); + if (!retrieveWificondAndRegisterForDeath()) { + return false; + } + + IApInterface apInterface = null; + try { + apInterface = mWificond.createApInterface(ifaceName); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to get IApInterface due to remote exception"); + return false; + } + + if (apInterface == null) { + Log.e(TAG, "Could not get IApInterface instance from wificond"); + return false; + } + Binder.allowBlocking(apInterface.asBinder()); + + // Refresh Handlers + mApInterfaces.put(ifaceName, apInterface); + return true; + } + + /** + * Tear down a Soft AP interface configured using + * {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface to tear down. + * @return Returns true on success, false on failure (e.g. when called before an interface was + * set up). + */ + public boolean tearDownSoftApInterface(@NonNull String ifaceName) { + if (getApInterface(ifaceName) == null) { + Log.e(TAG, "No valid wificond ap interface handler for iface=" + ifaceName); + return false; + } + + if (mWificond == null) { + Log.e(TAG, "tearDownSoftApInterface: mWificond binder is null! Did wificond die?"); + return false; + } + + boolean success; + try { + success = mWificond.tearDownApInterface(ifaceName); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to teardown AP interface due to remote exception"); + return false; + } + if (!success) { + Log.e(TAG, "Failed to teardown AP interface"); + return false; + } + mApInterfaces.remove(ifaceName); + mApInterfaceListeners.remove(ifaceName); + return true; + } + + /** + * Tear down all interfaces, whether clients (STA) or Soft AP. + * + * @return Returns true on success. + */ + public boolean tearDownInterfaces() { + synchronized (mLock) { + Log.d(TAG, "tearing down interfaces in wificond"); + // Explicitly refresh the wificond handler because |tearDownInterfaces()| + // could be used to cleanup before we setup any interfaces. + if (!retrieveWificondAndRegisterForDeath()) { + return false; + } + + try { + for (Map.Entry entry : mWificondScanners.entrySet()) { + entry.getValue().unsubscribeScanEvents(); + entry.getValue().unsubscribePnoScanEvents(); + } + mWificond.tearDownInterfaces(); + clearState(); + return true; + } catch (RemoteException e) { + Log.e(TAG, "Failed to tear down interfaces due to remote exception"); + } + + return false; + } + } + + /** Helper function to look up the interface handle using name */ + private IClientInterface getClientInterface(@NonNull String ifaceName) { + return mClientInterfaces.get(ifaceName); + } + + /** + * Request signal polling. + * + * @param ifaceName Name of the interface on which to poll. The interface must have been + * already set up using + *{@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @return A {@link SignalPollResult} object containing interface statistics, or a null on + * error (e.g. the interface hasn't been set up yet). + * + * @deprecated replaced by + * {@link com.android.server.wifi.SupplicantStaIfaceHal#getSignalPollResults} + */ + @Deprecated + @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) { + IClientInterface iface = getClientInterface(ifaceName); + if (iface == null) { + Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName); + return null; + } + + int[] resultArray; + try { + resultArray = iface.signalPoll(); + if (resultArray == null || resultArray.length != 4) { + Log.e(TAG, "Invalid signal poll result from wificond"); + return null; + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to do signal polling due to remote exception"); + return null; + } + return new SignalPollResult(resultArray[0], resultArray[1], resultArray[3], resultArray[2]); + } + + /** + * Get current transmit (Tx) packet counters of the specified interface. The interface must + * have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface. + * @return {@link TxPacketCounters} of the current interface or null on error (e.g. when + * called before the interface has been set up). + */ + @Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) { + IClientInterface iface = getClientInterface(ifaceName); + if (iface == null) { + Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName); + return null; + } + + int[] resultArray; + try { + resultArray = iface.getPacketCounters(); + if (resultArray == null || resultArray.length != 2) { + Log.e(TAG, "Invalid signal poll result from wificond"); + return null; + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to do signal polling due to remote exception"); + return null; + } + return new TxPacketCounters(resultArray[0], resultArray[1]); + } + + /** Helper function to look up the scanner impl handle using name */ + private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) { + return mWificondScanners.get(ifaceName); + } + + /** + * Fetch the latest scan results of the indicated type for the specified interface. Note that + * this method fetches the latest results - it does not initiate a scan. Initiating a scan can + * be done using {@link #startScan(String, int, Set, List)} or + * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface. + * @param scanType The type of scan result to be returned, can be + * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}. + * @return Returns an array of {@link NativeScanResult} or an empty array on failure (e.g. when + * called before the interface has been set up). + */ + @NonNull public List getScanResults(@NonNull String ifaceName, + @ScanResultType int scanType) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName); + return new ArrayList<>(); + } + List results = null; + try { + if (scanType == SCAN_TYPE_SINGLE_SCAN) { + results = Arrays.asList(scannerImpl.getScanResults()); + } else { + results = Arrays.asList(scannerImpl.getPnoScanResults()); + } + } catch (RemoteException e1) { + Log.e(TAG, "Failed to create ScanDetail ArrayList"); + } + if (results == null) { + results = new ArrayList<>(); + } + if (mVerboseLoggingEnabled) { + Log.d(TAG, "get " + results.size() + " scan results from wificond"); + } + + return results; + } + + /** + * Get the max number of SSIDs that the driver supports per scan. + * + * @param ifaceName Name of the interface. + */ + public int getMaxSsidsPerScan(@NonNull String ifaceName) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName); + return 0; + } + try { + return scannerImpl.getMaxSsidsPerScan(); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to getMaxSsidsPerScan"); + } + return 0; + } + + /** + * Return scan type for the parcelable {@link SingleScanSettings} + */ + private static int getScanType(@WifiAnnotations.ScanType int scanType) { + switch (scanType) { + case WifiScanner.SCAN_TYPE_LOW_LATENCY: + return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN; + case WifiScanner.SCAN_TYPE_LOW_POWER: + return IWifiScannerImpl.SCAN_TYPE_LOW_POWER; + case WifiScanner.SCAN_TYPE_HIGH_ACCURACY: + return IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY; + default: + throw new IllegalArgumentException("Invalid scan type " + scanType); + } + } + + /** + * @deprecated replaced by {@link #startScan(String, int, Set, List, Bundle)} + **/ + @Deprecated + public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, + @Nullable Set freqs, @Nullable List hiddenNetworkSSIDs) { + return startScan(ifaceName, scanType, freqs, hiddenNetworkSSIDs, null); + } + + /** + * @deprecated replaced by {@link #startScan2(String, int, Set, List, Bundle)} + */ + @Deprecated + public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, + @SuppressLint("NullableCollection") @Nullable Set freqs, + @SuppressLint("NullableCollection") @Nullable List hiddenNetworkSSIDs, + @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName); + return false; + } + SingleScanSettings settings = createSingleScanSettings(scanType, freqs, hiddenNetworkSSIDs, + extraScanningParams); + if (settings == null) { + return false; + } + try { + return scannerImpl.scan(settings); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to request scan due to remote exception"); + } + return false; + } + + /** + * Start a scan using the specified parameters. A scan is an asynchronous operation. The + * result of the operation is returned in the {@link ScanEventCallback} registered when + * setting up an interface using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}. + * The latest scans can be obtained using {@link #getScanResults(String, int)} and using a + * {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface on which to initiate the scan. + * @param scanType Type of scan to perform, can be any of + * {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or + * {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}. + * @param freqs list of frequencies to scan for, if null scan all supported channels. + * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that + * no hidden frequencies will be scanned for. + * @param extraScanningParams bundle of extra scanning parameters. + * @return Returns one of the scan status codes defined in {@code WifiScanner#REASON_*} + */ + public int startScan2(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, + @SuppressLint("NullableCollection") @Nullable Set freqs, + @SuppressLint("NullableCollection") @Nullable List hiddenNetworkSSIDs, + @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName); + return WifiScanner.REASON_INVALID_ARGS; + } + SingleScanSettings settings = createSingleScanSettings(scanType, freqs, hiddenNetworkSSIDs, + extraScanningParams); + if (settings == null) { + return WifiScanner.REASON_INVALID_ARGS; + } + try { + int status = scannerImpl.scanRequest(settings); + return toFrameworkScanStatusCode(status); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to request scan due to remote exception"); + } + return WifiScanner.REASON_UNSPECIFIED; + } + + private SingleScanSettings createSingleScanSettings(@WifiAnnotations.ScanType int scanType, + @SuppressLint("NullableCollection") @Nullable Set freqs, + @SuppressLint("NullableCollection") @Nullable List hiddenNetworkSSIDs, + @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) { + SingleScanSettings settings = new SingleScanSettings(); + try { + settings.scanType = getScanType(scanType); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid scan type ", e); + return null; + } + settings.channelSettings = new ArrayList<>(); + settings.hiddenNetworks = new ArrayList<>(); + if (extraScanningParams != null) { + settings.enable6GhzRnr = extraScanningParams.getBoolean(SCANNING_PARAM_ENABLE_6GHZ_RNR); + settings.vendorIes = extraScanningParams.getByteArray(EXTRA_SCANNING_PARAM_VENDOR_IES); + } + + if (freqs != null) { + for (Integer freq : freqs) { + ChannelSettings channel = new ChannelSettings(); + channel.frequency = freq; + settings.channelSettings.add(channel); + } + } + if (hiddenNetworkSSIDs != null) { + for (byte[] ssid : hiddenNetworkSSIDs) { + HiddenNetwork network = new HiddenNetwork(); + network.ssid = ssid; + + // settings.hiddenNetworks is expected to be very small, so this shouldn't cause + // any performance issues. + if (!settings.hiddenNetworks.contains(network)) { + settings.hiddenNetworks.add(network); + } + } + } + + return settings; + } + + private int toFrameworkScanStatusCode(int scanStatus) { + switch(scanStatus) { + case IWifiScannerImpl.SCAN_STATUS_SUCCESS: + return WifiScanner.REASON_SUCCEEDED; + case IWifiScannerImpl.SCAN_STATUS_FAILED_BUSY: + return WifiScanner.REASON_BUSY; + case IWifiScannerImpl.SCAN_STATUS_FAILED_ABORT: + return WifiScanner.REASON_ABORT; + case IWifiScannerImpl.SCAN_STATUS_FAILED_NODEV: + return WifiScanner.REASON_NO_DEVICE; + case IWifiScannerImpl.SCAN_STATUS_FAILED_INVALID_ARGS: + return WifiScanner.REASON_INVALID_ARGS; + case IWifiScannerImpl.SCAN_STATUS_FAILED_GENERIC: + default: + return WifiScanner.REASON_UNSPECIFIED; + } + } + + /** + * Request a PNO (Preferred Network Offload). The offload request and the scans are asynchronous + * operations. The result of the request are returned in the {@code callback} parameter which + * is an {@link PnoScanRequestCallback}. The scan results are are return in the + * {@link ScanEventCallback} which is registered when setting up an interface using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}. + * The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the + * {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface on which to request a PNO. + * @param pnoSettings PNO scan configuration. + * @param executor The Executor on which to execute the callback. + * @param callback Callback for the results of the offload request. + * @return true on success, false on failure (e.g. when called before the interface has been set + * up). + */ + public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings, + @NonNull @CallbackExecutor Executor executor, + @NonNull PnoScanRequestCallback callback) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName); + return false; + } + + if (callback == null || executor == null) { + Log.e(TAG, "startPnoScan called with a null callback"); + return false; + } + + try { + boolean success = scannerImpl.startPnoScan(pnoSettings); + if (success) { + executor.execute(callback::onPnoRequestSucceeded); + } else { + executor.execute(callback::onPnoRequestFailed); + } + return success; + } catch (RemoteException e1) { + Log.e(TAG, "Failed to start pno scan due to remote exception"); + } + return false; + } + + /** + * Stop PNO scan configured with + * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface on which the PNO scan was configured. + * @return true on success, false on failure (e.g. when called before the interface has been + * set up). + */ + public boolean stopPnoScan(@NonNull String ifaceName) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName); + return false; + } + try { + return scannerImpl.stopPnoScan(); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to stop pno scan due to remote exception"); + } + return false; + } + + /** + * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. No failure + * callback, e.g. {@link ScanEventCallback#onScanFailed()}, is triggered by this operation. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. If the interface has not been set up then + * this method has no impact. + * + * @param ifaceName Name of the interface on which the scan was started. + */ + public void abortScan(@NonNull String ifaceName) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName); + return; + } + try { + scannerImpl.abortScan(); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to request abortScan due to remote exception"); + } + } + + /** + * Query the list of valid frequencies (in MHz) for the provided band. + * The result depends on the on the country code that has been set. + * + * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants. + * The following bands are supported: + * {@link WifiScanner#WIFI_BAND_24_GHZ}, + * {@link WifiScanner#WIFI_BAND_5_GHZ}, + * {@link WifiScanner#WIFI_BAND_5_GHZ_DFS_ONLY}, + * {@link WifiScanner#WIFI_BAND_6_GHZ} + * {@link WifiScanner.WIFI_BAND_60_GHZ} + * @return frequencies vector of valid frequencies (MHz), or an empty array for error. + * @throws IllegalArgumentException if band is not recognized. + */ + public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) { + if (mWificond == null) { + Log.e(TAG, "getChannelsMhzForBand: mWificond binder is null! Did wificond die?"); + return new int[0]; + } + int[] result = null; + try { + switch (band) { + case WifiScanner.WIFI_BAND_24_GHZ: + result = mWificond.getAvailable2gChannels(); + break; + case WifiScanner.WIFI_BAND_5_GHZ: + result = mWificond.getAvailable5gNonDFSChannels(); + break; + case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY: + result = mWificond.getAvailableDFSChannels(); + break; + case WifiScanner.WIFI_BAND_6_GHZ: + result = mWificond.getAvailable6gChannels(); + break; + case WifiScanner.WIFI_BAND_60_GHZ: + result = mWificond.getAvailable60gChannels(); + break; + default: + throw new IllegalArgumentException("unsupported band " + band); + } + } catch (RemoteException e1) { + Log.e(TAG, "Failed to request getChannelsForBand due to remote exception"); + } + if (result == null) { + result = new int[0]; + } + return result; + } + + /** Helper function to look up the interface handle using name */ + private IApInterface getApInterface(@NonNull String ifaceName) { + return mApInterfaces.get(ifaceName); + } + + /** + * Get the device phy capabilities for a given interface. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @return DeviceWiphyCapabilities or null on error (e.g. when called on an interface which has + * not been set up). + */ + @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) { + if (mWificond == null) { + Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! Did wificond die?"); + return null; + } + + try { + return mWificond.getDeviceWiphyCapabilities(ifaceName); + } catch (RemoteException e) { + return null; + } + } + + /** + * Register the provided listener for country code event. + * + * @param executor The Executor on which to execute the callbacks. + * @param listener listener for country code changed events. + * @return true on success, false on failure. + */ + public boolean registerCountryCodeChangedListener(@NonNull @CallbackExecutor Executor executor, + @NonNull CountryCodeChangedListener listener) { + if (!retrieveWificondAndRegisterForDeath()) { + return false; + } + Log.d(TAG, "registerCountryCodeEventListener called"); + mWificondEventHandler.registerCountryCodeChangedListener(executor, listener); + return true; + } + + + /** + * Unregister CountryCodeChangedListener with pid. + * + * @param listener listener which registered country code changed events. + */ + public void unregisterCountryCodeChangedListener(@NonNull CountryCodeChangedListener listener) { + Log.d(TAG, "unregisterCountryCodeEventListener called"); + mWificondEventHandler.unregisterCountryCodeChangedListener(listener); + } + + /** + * Notifies the wificond daemon that the WiFi framework has successfully updated the Country + * Code of the driver. The wificond daemon needs this notification if the device does not + * support the NL80211_CMD_REG_CHANGED (otherwise it will find out on its own). The wificond + * updates in internal state in response to this Country Code update. + * + * @param newCountryCode new country code. An ISO-3166-alpha2 country code which is 2-Character + * alphanumeric. + */ + public void notifyCountryCodeChanged(@Nullable String newCountryCode) { + if (mWificond == null) { + new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer(); + } + try { + mWificond.notifyCountryCodeChanged(); + Log.i(TAG, "Receive country code change to " + newCountryCode); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + } + } + + /** + * Register the provided callback handler for SoftAp events. The interface must first be created + * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until + * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration + * method is provided). + *

+ * Note that only one callback can be registered at a time - any registration overrides previous + * registrations. + * + * @param ifaceName Name of the interface on which to register the callback. + * @param executor The Executor on which to execute the callbacks. + * @param callback Callback for AP events. + * @return true on success, false on failure (e.g. when called on an interface which has not + * been set up). + * + * @deprecated The usage is replaced by vendor HAL + * {@code android.hardware.wifi.hostapd.V1_3.IHostapdCallback}. + */ + @Deprecated + public boolean registerApCallback(@NonNull String ifaceName, + @NonNull @CallbackExecutor Executor executor, + @NonNull SoftApCallback callback) { + IApInterface iface = getApInterface(ifaceName); + if (iface == null) { + Log.e(TAG, "No valid ap interface handler for iface=" + ifaceName); + return false; + } + + if (callback == null || executor == null) { + Log.e(TAG, "registerApCallback called with a null callback"); + return false; + } + + try { + IApInterfaceEventCallback wificondCallback = new ApInterfaceEventCallback(executor, + callback); + mApInterfaceListeners.put(ifaceName, wificondCallback); + boolean success = iface.registerCallback(wificondCallback); + if (!success) { + Log.e(TAG, "Failed to register ap callback."); + return false; + } + } catch (RemoteException e) { + Log.e(TAG, "Exception in registering AP callback: " + e); + return false; + } + return true; + } + + /** + * Send a management frame on the specified interface at the specified rate. Useful for probing + * the link with arbitrary frames. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName The interface on which to send the frame. + * @param frame The raw byte array of the management frame to tramit. + * @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the + * frame. Specified per IEEE 802.11. + * @param executor The Executor on which to execute the callbacks. + * @param callback A {@link SendMgmtFrameCallback} callback for results of the operation. + */ + public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame, int mcs, + @NonNull @CallbackExecutor Executor executor, + @NonNull SendMgmtFrameCallback callback) { + + if (callback == null || executor == null) { + Log.e(TAG, "callback cannot be null!"); + return; + } + + if (frame == null) { + Log.e(TAG, "frame cannot be null!"); + executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN)); + return; + } + + // TODO (b/112029045) validate mcs + IClientInterface clientInterface = getClientInterface(ifaceName); + if (clientInterface == null) { + Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName); + executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN)); + return; + } + + if (!mSendMgmtFrameInProgress.compareAndSet(false, true)) { + Log.e(TAG, "An existing management frame transmission is in progress!"); + executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_ALREADY_STARTED)); + return; + } + + SendMgmtFrameEvent sendMgmtFrameEvent = new SendMgmtFrameEvent(executor, callback); + try { + clientInterface.SendMgmtFrame(frame, sendMgmtFrameEvent, mcs); + } catch (RemoteException e) { + Log.e(TAG, "Exception while starting link probe: " + e); + // Call sendMgmtFrameEvent.OnFailure() instead of callback.onFailure() so that + // sendMgmtFrameEvent can clean up internal state, such as cancelling the timer. + sendMgmtFrameEvent.OnFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN); + } + } + + /** + * Clear all internal handles. + */ + private void clearState() { + // Refresh handlers + mClientInterfaces.clear(); + mWificondScanners.clear(); + mPnoScanEventHandlers.clear(); + mScanEventHandlers.clear(); + mApInterfaces.clear(); + mApInterfaceListeners.clear(); + mSendMgmtFrameInProgress.set(false); + } + + /** + * OEM parsed security type + */ + public static class OemSecurityType { + /** The protocol defined in {@link android.net.wifi.WifiAnnotations.Protocol}. */ + public final @WifiAnnotations.Protocol int protocol; + /** + * Supported key management types defined + * in {@link android.net.wifi.WifiAnnotations.KeyMgmt}. + */ + @NonNull public final List keyManagement; + /** + * Supported pairwise cipher types defined + * in {@link android.net.wifi.WifiAnnotations.Cipher}. + */ + @NonNull public final List pairwiseCipher; + /** The group cipher type defined in {@link android.net.wifi.WifiAnnotations.Cipher}. */ + public final @WifiAnnotations.Cipher int groupCipher; + /** + * Default constructor for OemSecurityType + * + * @param protocol The protocol defined in + * {@link android.net.wifi.WifiAnnotations.Protocol}. + * @param keyManagement Supported key management types defined + * in {@link android.net.wifi.WifiAnnotations.KeyMgmt}. + * @param pairwiseCipher Supported pairwise cipher types defined + * in {@link android.net.wifi.WifiAnnotations.Cipher}. + * @param groupCipher The group cipher type defined + * in {@link android.net.wifi.WifiAnnotations.Cipher}. + */ + public OemSecurityType( + @WifiAnnotations.Protocol int protocol, + @NonNull List keyManagement, + @NonNull List pairwiseCipher, + @WifiAnnotations.Cipher int groupCipher) { + this.protocol = protocol; + this.keyManagement = (keyManagement != null) + ? keyManagement : new ArrayList(); + this.pairwiseCipher = (pairwiseCipher != null) + ? pairwiseCipher : new ArrayList(); + this.groupCipher = groupCipher; + } + } + + /** + * OEM information element parser for security types not parsed by the framework. + * + * The OEM method should use the method inputs {@code id}, {@code idExt}, and {@code bytes} + * to perform the parsing. The method should place the results in an OemSecurityType objct. + * + * @param id The information element id. + * @param idExt The information element extension id. This is valid only when id is + * the extension id, {@code 255}. + * @param bytes The raw bytes of information element data, 'Element ID' and 'Length' are + * stripped off already. + * @return an OemSecurityType object if this IE is parsed successfully, null otherwise. + */ + @Nullable public static OemSecurityType parseOemSecurityTypeElement( + int id, + int idExt, + @NonNull byte[] bytes) { + return null; + } +} -- Gitee From fcd4e523b685559128a104fb2dfb509df1f1e9e5 Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 14:26:30 +0800 Subject: [PATCH 04/10] modify frameworks-base --- aosp/frameworks/base/Android.bp | 1 + .../SettingsProvider/res/values/defaults.xml | 2 +- .../server/net/NetworkManagementService.java | 98 ++++++++++++------- .../net/wifi/nl80211/WifiNl80211Manager.java | 13 +++ 4 files changed, 75 insertions(+), 39 deletions(-) diff --git a/aosp/frameworks/base/Android.bp b/aosp/frameworks/base/Android.bp index 8d7ab9835..666598ff7 100644 --- a/aosp/frameworks/base/Android.bp +++ b/aosp/frameworks/base/Android.bp @@ -424,6 +424,7 @@ java_defaults { "audiopolicy-aidl-java", "sounddose-aidl-java", "modules-utils-expresslog", + "wifi-helpers", ], } diff --git a/aosp/frameworks/base/packages/SettingsProvider/res/values/defaults.xml b/aosp/frameworks/base/packages/SettingsProvider/res/values/defaults.xml index 17d9f1b87..0612a855d 100644 --- a/aosp/frameworks/base/packages/SettingsProvider/res/values/defaults.xml +++ b/aosp/frameworks/base/packages/SettingsProvider/res/values/defaults.xml @@ -48,7 +48,7 @@ true true true - false + true 2 true diff --git a/aosp/frameworks/base/services/core/java/com/android/server/net/NetworkManagementService.java b/aosp/frameworks/base/services/core/java/com/android/server/net/NetworkManagementService.java index cfa100cf2..43f7e13b7 100644 --- a/aosp/frameworks/base/services/core/java/com/android/server/net/NetworkManagementService.java +++ b/aosp/frameworks/base/services/core/java/com/android/server/net/NetworkManagementService.java @@ -80,6 +80,7 @@ import com.android.net.module.util.NetdUtils; import com.android.net.module.util.PermissionUtils; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.helpers.WifiHelpers.WifiHelpers; import com.google.android.collect.Maps; @@ -702,70 +703,91 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @Override public void setInterfaceDown(String iface) { - // PermissionUtils.enforceNetworkStackPermission(mContext); - // final InterfaceConfiguration ifcg = getInterfaceConfig(iface); - // ifcg.setInterfaceDown(); - // setInterfaceConfig(iface, ifcg); + if (WifiHelpers.isWifiIface(iface)) { + return; + } + PermissionUtils.enforceNetworkStackPermission(mContext); + final InterfaceConfiguration ifcg = getInterfaceConfig(iface); + ifcg.setInterfaceDown(); + setInterfaceConfig(iface, ifcg); } @Override public void setInterfaceUp(String iface) { - // PermissionUtils.enforceNetworkStackPermission(mContext); - // final InterfaceConfiguration ifcg = getInterfaceConfig(iface); - // ifcg.setInterfaceUp(); - // setInterfaceConfig(iface, ifcg); + if (WifiHelpers.isWifiIface(iface)) { + return; + } + PermissionUtils.enforceNetworkStackPermission(mContext); + final InterfaceConfiguration ifcg = getInterfaceConfig(iface); + ifcg.setInterfaceUp(); + setInterfaceConfig(iface, ifcg); } @Override public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) { - // PermissionUtils.enforceNetworkStackPermission(mContext); - // try { - // mNetdService.interfaceSetIPv6PrivacyExtensions(iface, enable); - // } catch (RemoteException | ServiceSpecificException e) { - // throw new IllegalStateException(e); - // } + if (WifiHelpers.isWifiIface(iface)) { + return; + } + PermissionUtils.enforceNetworkStackPermission(mContext); + try { + mNetdService.interfaceSetIPv6PrivacyExtensions(iface, enable); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } } /* TODO: This is right now a IPv4 only function. Works for wifi which loses its IPv6 addresses on interface down, but we need to do full clean up here */ @Override public void clearInterfaceAddresses(String iface) { - // PermissionUtils.enforceNetworkStackPermission(mContext); - // try { - // mNetdService.interfaceClearAddrs(iface); - // } catch (RemoteException | ServiceSpecificException e) { - // throw new IllegalStateException(e); - // } + if (WifiHelpers.isWifiIface(iface)) { + return; + } + PermissionUtils.enforceNetworkStackPermission(mContext); + try { + mNetdService.interfaceClearAddrs(iface); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } } @Override public void enableIpv6(String iface) { - // PermissionUtils.enforceNetworkStackPermission(mContext); - // try { - // mNetdService.interfaceSetEnableIPv6(iface, true); - // } catch (RemoteException | ServiceSpecificException e) { - // throw new IllegalStateException(e); - // } + if (WifiHelpers.isWifiIface(iface)) { + return; + } + PermissionUtils.enforceNetworkStackPermission(mContext); + try { + mNetdService.interfaceSetEnableIPv6(iface, true); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } } @Override public void setIPv6AddrGenMode(String iface, int mode) throws ServiceSpecificException { - // PermissionUtils.enforceNetworkStackPermission(mContext); - // try { - // mNetdService.setIPv6AddrGenMode(iface, mode); - // } catch (RemoteException e) { - // throw e.rethrowAsRuntimeException(); - // } + if (WifiHelpers.isWifiIface(iface)) { + return; + } + PermissionUtils.enforceNetworkStackPermission(mContext); + try { + mNetdService.setIPv6AddrGenMode(iface, mode); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } @Override public void disableIpv6(String iface) { - // PermissionUtils.enforceNetworkStackPermission(mContext); - // try { - // mNetdService.interfaceSetEnableIPv6(iface, false); - // } catch (RemoteException | ServiceSpecificException e) { - // throw new IllegalStateException(e); - // } + if (WifiHelpers.isWifiIface(iface)) { + return; + } + PermissionUtils.enforceNetworkStackPermission(mContext); + try { + mNetdService.interfaceSetEnableIPv6(iface, false); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } } @android.annotation.EnforcePermission(android.Manifest.permission.SHUTDOWN) diff --git a/aosp/frameworks/base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/aosp/frameworks/base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index 58638e8e1..bed87d5c3 100644 --- a/aosp/frameworks/base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/aosp/frameworks/base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -38,6 +38,7 @@ import android.os.SystemClock; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -870,6 +871,10 @@ public class WifiNl80211Manager { * @return Returns true on success. */ public boolean tearDownInterfaces() { + if (WifiHelpers.enable()) { + // do not tear down wlan0 + return true; + } synchronized (mLock) { Log.d(TAG, "tearing down interfaces in wificond"); // Explicitly refresh the wificond handler because |tearDownInterfaces()| @@ -915,6 +920,14 @@ public class WifiNl80211Manager { */ @Deprecated @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) { + if (WifiHelpers.enable()) { + // mock the RSSI poll result + return new WifiNl80211Manager.SignalPollResult( + WifiHelpers.getRssi(), + WifiHelpers.getTxBitrate(), + WifiHelpers.getTxBitrate(), + WifiHelpers.getFrequency()); + } IClientInterface iface = getClientInterface(ifaceName); if (iface == null) { Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName); -- Gitee From 7431ca6a03c60fe4cef1d804290904abc01b8917 Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 14:30:30 +0800 Subject: [PATCH 05/10] frameworks-opt add orig files --- .../opt/net/wifi/libwifi_hal/hal_tool.cpp | 590 ++++++++++++++++++ .../wifi/libwifi_hal/wifi_hal_fallback.cpp | 21 + 2 files changed, 611 insertions(+) create mode 100644 aosp/frameworks/opt/net/wifi/libwifi_hal/hal_tool.cpp create mode 100644 aosp/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_fallback.cpp diff --git a/aosp/frameworks/opt/net/wifi/libwifi_hal/hal_tool.cpp b/aosp/frameworks/opt/net/wifi/libwifi_hal/hal_tool.cpp new file mode 100644 index 000000000..49652554c --- /dev/null +++ b/aosp/frameworks/opt/net/wifi/libwifi_hal/hal_tool.cpp @@ -0,0 +1,590 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wifi_hal/hal_tool.h" + +#include + +namespace android { +namespace wifi_system { +namespace { + +wifi_error wifi_initialize_stub(wifi_handle* handle) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_wait_for_driver_ready_stub(void) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +void wifi_cleanup_stub(wifi_handle handle, wifi_cleaned_up_handler handler) {} + +void wifi_event_loop_stub(wifi_handle handle) {} + +void wifi_get_error_info_stub(wifi_error err, const char** msg) { *msg = NULL; } + +wifi_error wifi_get_supported_feature_set_stub(wifi_interface_handle handle, + feature_set* set) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_get_concurrency_matrix_stub(wifi_interface_handle handle, + int max_size, feature_set* matrix, + int* size) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_set_scanning_mac_oui_stub(wifi_interface_handle handle, + unsigned char* oui_data) { + return WIFI_ERROR_UNINITIALIZED; +} + +/* List of all supported channels, including 5GHz channels */ +wifi_error wifi_get_supported_channels_stub(wifi_handle handle, int* size, + wifi_channel* list) { + return WIFI_ERROR_UNINITIALIZED; +} + +/* Enhanced power reporting */ +wifi_error wifi_is_epr_supported_stub(wifi_handle handle) { + return WIFI_ERROR_UNINITIALIZED; +} + +/* multiple interface support */ +wifi_error wifi_get_ifaces_stub(wifi_handle handle, int* num_ifaces, + wifi_interface_handle** ifaces) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_get_iface_name_stub(wifi_interface_handle iface, char* name, + size_t size) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_set_iface_event_handler_stub(wifi_request_id id, + wifi_interface_handle iface, + wifi_event_handler eh) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_reset_iface_event_handler_stub(wifi_request_id id, + wifi_interface_handle iface) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_start_gscan_stub(wifi_request_id id, + wifi_interface_handle iface, + wifi_scan_cmd_params params, + wifi_scan_result_handler handler) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_stop_gscan_stub(wifi_request_id id, + wifi_interface_handle iface) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_get_cached_gscan_results_stub(wifi_interface_handle iface, + byte flush, int max, + wifi_cached_scan_results* results, + int* num) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_set_bssid_hotlist_stub(wifi_request_id id, + wifi_interface_handle iface, + wifi_bssid_hotlist_params params, + wifi_hotlist_ap_found_handler handler) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_reset_bssid_hotlist_stub(wifi_request_id id, + wifi_interface_handle iface) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_set_significant_change_handler_stub( + wifi_request_id id, wifi_interface_handle iface, + wifi_significant_change_params params, + wifi_significant_change_handler handler) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_reset_significant_change_handler_stub( + wifi_request_id id, wifi_interface_handle iface) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_get_gscan_capabilities_stub( + wifi_interface_handle handle, wifi_gscan_capabilities* capabilities) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_set_link_stats_stub(wifi_interface_handle iface, + wifi_link_layer_params params) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_get_link_stats_stub(wifi_request_id id, + wifi_interface_handle iface, + wifi_stats_result_handler handler) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_clear_link_stats_stub(wifi_interface_handle iface, + u32 stats_clear_req_mask, + u32* stats_clear_rsp_mask, u8 stop_req, + u8* stop_rsp) { + return WIFI_ERROR_UNINITIALIZED; +} + +wifi_error wifi_get_valid_channels_stub(wifi_interface_handle handle, int band, + int max_channels, + wifi_channel* channels, + int* num_channels) { + return WIFI_ERROR_UNINITIALIZED; +} + +/* API to request RTT measurement */ +wifi_error wifi_rtt_range_request_stub(wifi_request_id id, + wifi_interface_handle iface, + unsigned num_rtt_config, + wifi_rtt_config rtt_config[], + wifi_rtt_event_handler handler) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +/* API to cancel RTT measurements */ +wifi_error wifi_rtt_range_cancel_stub(wifi_request_id id, + wifi_interface_handle iface, + unsigned num_devices, mac_addr addr[]) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +/* API to get RTT capability */ +wifi_error wifi_get_rtt_capabilities_stub(wifi_interface_handle iface, + wifi_rtt_capabilities* capabilities) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +/* API to enable RTT responder role */ +wifi_error wifi_enable_responder_stub(wifi_request_id id, + wifi_interface_handle iface, + wifi_channel_info channel_hint, + unsigned max_duration_seconds, + wifi_channel_info* channel_used) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +/* API to disable RTT responder role */ +wifi_error wifi_disable_responder_stub(wifi_request_id id, + wifi_interface_handle iface) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +/* API to get available channel for RTT responder role */ +wifi_error wifi_rtt_get_available_channel_stub(wifi_interface_handle iface, + wifi_channel_info* channel) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle iface, u32 nodfs) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_start_logging_stub(wifi_interface_handle iface, + u32 verbose_level, u32 flags, + u32 max_interval_sec, u32 min_data_size, + char* buffer_name) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_set_epno_list_stub(int id, wifi_interface_info* iface, + const wifi_epno_params* params, + wifi_epno_handler handler) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_reset_epno_list_stub(int id, wifi_interface_info* iface) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_set_country_code_stub(wifi_interface_handle iface, + const char* code) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_firmware_memory_dump_stub( + wifi_interface_handle iface, wifi_firmware_memory_dump_handler handler) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_set_log_handler_stub(wifi_request_id id, + wifi_interface_handle iface, + wifi_ring_buffer_data_handler handler) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_reset_log_handler_stub(wifi_request_id id, + wifi_interface_handle iface) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_set_alert_handler_stub(wifi_request_id id, + wifi_interface_handle iface, + wifi_alert_handler handler) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_reset_alert_handler_stub(wifi_request_id id, + wifi_interface_handle iface) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_firmware_version_stub(wifi_interface_handle iface, + char* buffer, int buffer_size) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_ring_buffers_status_stub(wifi_interface_handle iface, + u32* num_rings, + wifi_ring_buffer_status* status) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_logger_supported_feature_set_stub( + wifi_interface_handle iface, unsigned int* support) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_ring_data_stub(wifi_interface_handle iface, + char* ring_name) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_driver_version_stub(wifi_interface_handle iface, + char* buffer, int buffer_size) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_enable_tdls_stub(wifi_interface_handle iface, mac_addr addr, + wifi_tdls_params* params, + wifi_tdls_handler handler) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_disable_tdls_stub(wifi_interface_handle iface, mac_addr addr) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_tdls_status_stub(wifi_interface_handle iface, mac_addr addr, + wifi_tdls_status* status) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_tdls_capabilities_stub( + wifi_interface_handle iface, wifi_tdls_capabilities* capabilities) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_start_sending_offloaded_packet_stub( + wifi_request_id id, wifi_interface_handle iface, u16 ether_type, + u8* ip_packet, u16 ip_packet_len, u8* src_mac_addr, u8* dst_mac_addr, + u32 period_msec) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_stop_sending_offloaded_packet_stub( + wifi_request_id id, wifi_interface_handle iface) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_wake_reason_stats_stub( + wifi_interface_handle iface, + WLAN_DRIVER_WAKE_REASON_CNT* wifi_wake_reason_cnt) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_configure_nd_offload_stub(wifi_interface_handle iface, + u8 enable) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_driver_memory_dump_stub( + wifi_interface_handle iface, wifi_driver_memory_dump_callbacks callbacks) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_start_pkt_fate_monitoring_stub(wifi_interface_handle iface) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_tx_pkt_fates_stub(wifi_interface_handle handle, + wifi_tx_report* tx_report_bufs, + size_t n_requested_fates, + size_t* n_provided_fates) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_rx_pkt_fates_stub(wifi_interface_handle handle, + wifi_rx_report* rx_report_bufs, + size_t n_requested_fates, + size_t* n_provided_fates) { + return WIFI_ERROR_NOT_SUPPORTED; +} +wifi_error wifi_nan_enable_request_stub(transaction_id id, + wifi_interface_handle iface, + NanEnableRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_disable_request_stub(transaction_id id, + wifi_interface_handle iface) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_publish_request_stub(transaction_id id, + wifi_interface_handle iface, + NanPublishRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_publish_cancel_request_stub(transaction_id id, + wifi_interface_handle iface, + NanPublishCancelRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_subscribe_request_stub(transaction_id id, + wifi_interface_handle iface, + NanSubscribeRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_subscribe_cancel_request_stub( + transaction_id id, wifi_interface_handle iface, + NanSubscribeCancelRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_transmit_followup_request_stub( + transaction_id id, wifi_interface_handle iface, + NanTransmitFollowupRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_stats_request_stub(transaction_id id, + wifi_interface_handle iface, + NanStatsRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_config_request_stub(transaction_id id, + wifi_interface_handle iface, + NanConfigRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_tca_request_stub(transaction_id id, + wifi_interface_handle iface, + NanTCARequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_beacon_sdf_payload_request_stub( + transaction_id id, wifi_interface_handle iface, + NanBeaconSdfPayloadRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_register_handler_stub(wifi_interface_handle iface, + NanCallbackHandler handlers) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_get_version_stub(wifi_handle handle, NanVersion* version) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_get_capabilities_stub(transaction_id id, + wifi_interface_handle iface) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_data_interface_create_stub(transaction_id id, + wifi_interface_handle iface, + char* iface_name) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_data_interface_delete_stub(transaction_id id, + wifi_interface_handle iface, + char* iface_name) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_data_request_initiator_stub( + transaction_id id, wifi_interface_handle iface, + NanDataPathInitiatorRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_data_indication_response_stub( + transaction_id id, wifi_interface_handle iface, + NanDataPathIndicationResponse* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_nan_data_end_stub(transaction_id id, + wifi_interface_handle iface, + NanDataPathEndRequest* msg) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_get_packet_filter_capabilities_stub( + wifi_interface_handle handle, u32* version, u32* max_len) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +wifi_error wifi_set_packet_filter_stub(wifi_interface_handle handle, + const u8* program, u32 len) { + return WIFI_ERROR_NOT_SUPPORTED; +} + +bool init_wifi_stub_hal_func_table(wifi_hal_fn* hal_fn) { + if (hal_fn == NULL) { + return false; + } + hal_fn->wifi_initialize = wifi_initialize_stub; + hal_fn->wifi_wait_for_driver_ready = wifi_wait_for_driver_ready_stub; + hal_fn->wifi_cleanup = wifi_cleanup_stub; + hal_fn->wifi_event_loop = wifi_event_loop_stub; + hal_fn->wifi_get_error_info = wifi_get_error_info_stub; + hal_fn->wifi_get_supported_feature_set = wifi_get_supported_feature_set_stub; + hal_fn->wifi_get_concurrency_matrix = wifi_get_concurrency_matrix_stub; + hal_fn->wifi_set_scanning_mac_oui = wifi_set_scanning_mac_oui_stub; + hal_fn->wifi_get_supported_channels = wifi_get_supported_channels_stub; + hal_fn->wifi_is_epr_supported = wifi_is_epr_supported_stub; + hal_fn->wifi_get_ifaces = wifi_get_ifaces_stub; + hal_fn->wifi_get_iface_name = wifi_get_iface_name_stub; + hal_fn->wifi_reset_iface_event_handler = wifi_reset_iface_event_handler_stub; + hal_fn->wifi_start_gscan = wifi_start_gscan_stub; + hal_fn->wifi_stop_gscan = wifi_stop_gscan_stub; + hal_fn->wifi_get_cached_gscan_results = wifi_get_cached_gscan_results_stub; + hal_fn->wifi_set_bssid_hotlist = wifi_set_bssid_hotlist_stub; + hal_fn->wifi_reset_bssid_hotlist = wifi_reset_bssid_hotlist_stub; + hal_fn->wifi_set_significant_change_handler = + wifi_set_significant_change_handler_stub; + hal_fn->wifi_reset_significant_change_handler = + wifi_reset_significant_change_handler_stub; + hal_fn->wifi_get_gscan_capabilities = wifi_get_gscan_capabilities_stub; + hal_fn->wifi_set_link_stats = wifi_set_link_stats_stub; + hal_fn->wifi_get_link_stats = wifi_get_link_stats_stub; + hal_fn->wifi_clear_link_stats = wifi_clear_link_stats_stub; + hal_fn->wifi_get_valid_channels = wifi_get_valid_channels_stub; + hal_fn->wifi_rtt_range_request = wifi_rtt_range_request_stub; + hal_fn->wifi_rtt_range_cancel = wifi_rtt_range_cancel_stub; + hal_fn->wifi_get_rtt_capabilities = wifi_get_rtt_capabilities_stub; + hal_fn->wifi_start_logging = wifi_start_logging_stub; + hal_fn->wifi_set_epno_list = wifi_set_epno_list_stub; + hal_fn->wifi_set_country_code = wifi_set_country_code_stub; + hal_fn->wifi_enable_tdls = wifi_enable_tdls_stub; + hal_fn->wifi_disable_tdls = wifi_disable_tdls_stub; + hal_fn->wifi_get_tdls_status = wifi_get_tdls_status_stub; + hal_fn->wifi_get_tdls_capabilities = wifi_get_tdls_capabilities_stub; + hal_fn->wifi_set_nodfs_flag = wifi_set_nodfs_flag_stub; + hal_fn->wifi_get_firmware_memory_dump = wifi_get_firmware_memory_dump_stub; + hal_fn->wifi_set_log_handler = wifi_set_log_handler_stub; + hal_fn->wifi_reset_log_handler = wifi_reset_log_handler_stub; + hal_fn->wifi_set_alert_handler = wifi_set_alert_handler_stub; + hal_fn->wifi_reset_alert_handler = wifi_reset_alert_handler_stub; + hal_fn->wifi_get_firmware_version = wifi_get_firmware_version_stub; + hal_fn->wifi_get_ring_buffers_status = wifi_get_ring_buffers_status_stub; + hal_fn->wifi_get_logger_supported_feature_set = + wifi_get_logger_supported_feature_set_stub; + hal_fn->wifi_get_ring_data = wifi_get_ring_data_stub; + hal_fn->wifi_get_driver_version = wifi_get_driver_version_stub; + hal_fn->wifi_start_sending_offloaded_packet = + wifi_start_sending_offloaded_packet_stub; + hal_fn->wifi_stop_sending_offloaded_packet = + wifi_stop_sending_offloaded_packet_stub; + hal_fn->wifi_get_wake_reason_stats = wifi_get_wake_reason_stats_stub; + hal_fn->wifi_configure_nd_offload = wifi_configure_nd_offload_stub; + hal_fn->wifi_get_driver_memory_dump = wifi_get_driver_memory_dump_stub; + hal_fn->wifi_start_pkt_fate_monitoring = wifi_start_pkt_fate_monitoring_stub; + hal_fn->wifi_get_tx_pkt_fates = wifi_get_tx_pkt_fates_stub; + hal_fn->wifi_get_rx_pkt_fates = wifi_get_rx_pkt_fates_stub; + hal_fn->wifi_nan_enable_request = wifi_nan_enable_request_stub; + hal_fn->wifi_nan_disable_request = wifi_nan_disable_request_stub; + hal_fn->wifi_nan_publish_request = wifi_nan_publish_request_stub; + hal_fn->wifi_nan_publish_cancel_request = + wifi_nan_publish_cancel_request_stub; + hal_fn->wifi_nan_subscribe_request = wifi_nan_subscribe_request_stub; + hal_fn->wifi_nan_subscribe_cancel_request = + wifi_nan_subscribe_cancel_request_stub; + hal_fn->wifi_nan_transmit_followup_request = + wifi_nan_transmit_followup_request_stub; + hal_fn->wifi_nan_stats_request = wifi_nan_stats_request_stub; + hal_fn->wifi_nan_config_request = wifi_nan_config_request_stub; + hal_fn->wifi_nan_tca_request = wifi_nan_tca_request_stub; + hal_fn->wifi_nan_beacon_sdf_payload_request = + wifi_nan_beacon_sdf_payload_request_stub; + hal_fn->wifi_nan_register_handler = wifi_nan_register_handler_stub; + hal_fn->wifi_nan_get_version = wifi_nan_get_version_stub; + hal_fn->wifi_nan_get_capabilities = wifi_nan_get_capabilities_stub; + hal_fn->wifi_nan_data_interface_create = wifi_nan_data_interface_create_stub; + hal_fn->wifi_nan_data_interface_delete = wifi_nan_data_interface_delete_stub; + hal_fn->wifi_nan_data_request_initiator = + wifi_nan_data_request_initiator_stub; + hal_fn->wifi_nan_data_indication_response = + wifi_nan_data_indication_response_stub; + hal_fn->wifi_nan_data_end = wifi_nan_data_end_stub; + hal_fn->wifi_get_packet_filter_capabilities = + wifi_get_packet_filter_capabilities_stub; + hal_fn->wifi_set_packet_filter = wifi_set_packet_filter_stub; + + return true; +} + +} // namespace + +bool HalTool::InitFunctionTable(wifi_hal_fn* hal_fn) { +#ifndef WIFI_MULTIPLE_VENDOR_HALS + if (!init_wifi_stub_hal_func_table(hal_fn)) { + LOG(ERROR) << "Can not initialize the basic function pointer table"; + return false; + } + + if (init_wifi_vendor_hal_func_table(hal_fn) != WIFI_SUCCESS) { + LOG(ERROR) << "Can not initialize the vendor function pointer table"; + return false; + } + + return true; +#else + // vendor HAL library are dynamically loaded from the WIFI HAL. + LOG(ERROR) << "InitFunctionTable no longer supported."; + return false; +#endif +} + +bool HalTool::CanGetValidChannels(wifi_hal_fn* hal_fn) { + return hal_fn && + (hal_fn->wifi_get_valid_channels != wifi_get_valid_channels_stub); +} + +} // namespace wifi_system +} // namespace android diff --git a/aosp/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_fallback.cpp b/aosp/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_fallback.cpp new file mode 100644 index 000000000..fd0fff4f3 --- /dev/null +++ b/aosp/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_fallback.cpp @@ -0,0 +1,21 @@ +/* + * Copyright 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hardware_legacy/wifi_hal.h" + +wifi_error init_wifi_vendor_hal_func_table(wifi_hal_fn *fn) { + return WIFI_ERROR_NOT_SUPPORTED; +} -- Gitee From 3092444fe1deb7a664f8eaacbcd878be151996f0 Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 14:37:27 +0800 Subject: [PATCH 06/10] modify frameworks-opt --- .../opt/net/wifi/libwifi_hal/hal_tool.cpp | 154 ++++----- .../wifi/libwifi_hal/wifi_hal_fallback.cpp | 322 +++++++++++++++++- 2 files changed, 397 insertions(+), 79 deletions(-) diff --git a/aosp/frameworks/opt/net/wifi/libwifi_hal/hal_tool.cpp b/aosp/frameworks/opt/net/wifi/libwifi_hal/hal_tool.cpp index 49652554c..fbbbd437f 100644 --- a/aosp/frameworks/opt/net/wifi/libwifi_hal/hal_tool.cpp +++ b/aosp/frameworks/opt/net/wifi/libwifi_hal/hal_tool.cpp @@ -23,11 +23,11 @@ namespace wifi_system { namespace { wifi_error wifi_initialize_stub(wifi_handle* handle) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_wait_for_driver_ready_stub(void) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } void wifi_cleanup_stub(wifi_handle handle, wifi_cleaned_up_handler handler) {} @@ -38,124 +38,124 @@ void wifi_get_error_info_stub(wifi_error err, const char** msg) { *msg = NULL; } wifi_error wifi_get_supported_feature_set_stub(wifi_interface_handle handle, feature_set* set) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_get_concurrency_matrix_stub(wifi_interface_handle handle, int max_size, feature_set* matrix, int* size) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_set_scanning_mac_oui_stub(wifi_interface_handle handle, unsigned char* oui_data) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } /* List of all supported channels, including 5GHz channels */ wifi_error wifi_get_supported_channels_stub(wifi_handle handle, int* size, wifi_channel* list) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } /* Enhanced power reporting */ wifi_error wifi_is_epr_supported_stub(wifi_handle handle) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } /* multiple interface support */ wifi_error wifi_get_ifaces_stub(wifi_handle handle, int* num_ifaces, wifi_interface_handle** ifaces) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_get_iface_name_stub(wifi_interface_handle iface, char* name, size_t size) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_set_iface_event_handler_stub(wifi_request_id id, wifi_interface_handle iface, wifi_event_handler eh) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_reset_iface_event_handler_stub(wifi_request_id id, wifi_interface_handle iface) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_start_gscan_stub(wifi_request_id id, wifi_interface_handle iface, wifi_scan_cmd_params params, wifi_scan_result_handler handler) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_stop_gscan_stub(wifi_request_id id, wifi_interface_handle iface) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_get_cached_gscan_results_stub(wifi_interface_handle iface, byte flush, int max, wifi_cached_scan_results* results, int* num) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_set_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface, wifi_bssid_hotlist_params params, wifi_hotlist_ap_found_handler handler) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_reset_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_set_significant_change_handler_stub( wifi_request_id id, wifi_interface_handle iface, wifi_significant_change_params params, wifi_significant_change_handler handler) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_reset_significant_change_handler_stub( wifi_request_id id, wifi_interface_handle iface) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_get_gscan_capabilities_stub( wifi_interface_handle handle, wifi_gscan_capabilities* capabilities) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_set_link_stats_stub(wifi_interface_handle iface, wifi_link_layer_params params) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_get_link_stats_stub(wifi_request_id id, wifi_interface_handle iface, wifi_stats_result_handler handler) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_clear_link_stats_stub(wifi_interface_handle iface, u32 stats_clear_req_mask, u32* stats_clear_rsp_mask, u8 stop_req, u8* stop_rsp) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } wifi_error wifi_get_valid_channels_stub(wifi_interface_handle handle, int band, int max_channels, wifi_channel* channels, int* num_channels) { - return WIFI_ERROR_UNINITIALIZED; + return WIFI_SUCCESS; } /* API to request RTT measurement */ @@ -164,20 +164,20 @@ wifi_error wifi_rtt_range_request_stub(wifi_request_id id, unsigned num_rtt_config, wifi_rtt_config rtt_config[], wifi_rtt_event_handler handler) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } /* API to cancel RTT measurements */ wifi_error wifi_rtt_range_cancel_stub(wifi_request_id id, wifi_interface_handle iface, unsigned num_devices, mac_addr addr[]) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } /* API to get RTT capability */ wifi_error wifi_get_rtt_capabilities_stub(wifi_interface_handle iface, wifi_rtt_capabilities* capabilities) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } /* API to enable RTT responder role */ @@ -186,282 +186,282 @@ wifi_error wifi_enable_responder_stub(wifi_request_id id, wifi_channel_info channel_hint, unsigned max_duration_seconds, wifi_channel_info* channel_used) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } /* API to disable RTT responder role */ wifi_error wifi_disable_responder_stub(wifi_request_id id, wifi_interface_handle iface) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } /* API to get available channel for RTT responder role */ wifi_error wifi_rtt_get_available_channel_stub(wifi_interface_handle iface, wifi_channel_info* channel) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle iface, u32 nodfs) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_start_logging_stub(wifi_interface_handle iface, u32 verbose_level, u32 flags, u32 max_interval_sec, u32 min_data_size, char* buffer_name) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_set_epno_list_stub(int id, wifi_interface_info* iface, const wifi_epno_params* params, wifi_epno_handler handler) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_reset_epno_list_stub(int id, wifi_interface_info* iface) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_set_country_code_stub(wifi_interface_handle iface, const char* code) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_firmware_memory_dump_stub( wifi_interface_handle iface, wifi_firmware_memory_dump_handler handler) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_set_log_handler_stub(wifi_request_id id, wifi_interface_handle iface, wifi_ring_buffer_data_handler handler) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_reset_log_handler_stub(wifi_request_id id, wifi_interface_handle iface) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_set_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface, wifi_alert_handler handler) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_reset_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_firmware_version_stub(wifi_interface_handle iface, char* buffer, int buffer_size) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_ring_buffers_status_stub(wifi_interface_handle iface, u32* num_rings, wifi_ring_buffer_status* status) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_logger_supported_feature_set_stub( wifi_interface_handle iface, unsigned int* support) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_ring_data_stub(wifi_interface_handle iface, char* ring_name) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_driver_version_stub(wifi_interface_handle iface, char* buffer, int buffer_size) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_enable_tdls_stub(wifi_interface_handle iface, mac_addr addr, wifi_tdls_params* params, wifi_tdls_handler handler) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_disable_tdls_stub(wifi_interface_handle iface, mac_addr addr) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_tdls_status_stub(wifi_interface_handle iface, mac_addr addr, wifi_tdls_status* status) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_tdls_capabilities_stub( wifi_interface_handle iface, wifi_tdls_capabilities* capabilities) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_start_sending_offloaded_packet_stub( wifi_request_id id, wifi_interface_handle iface, u16 ether_type, u8* ip_packet, u16 ip_packet_len, u8* src_mac_addr, u8* dst_mac_addr, u32 period_msec) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_stop_sending_offloaded_packet_stub( wifi_request_id id, wifi_interface_handle iface) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_wake_reason_stats_stub( wifi_interface_handle iface, WLAN_DRIVER_WAKE_REASON_CNT* wifi_wake_reason_cnt) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_configure_nd_offload_stub(wifi_interface_handle iface, u8 enable) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_driver_memory_dump_stub( wifi_interface_handle iface, wifi_driver_memory_dump_callbacks callbacks) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_start_pkt_fate_monitoring_stub(wifi_interface_handle iface) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_tx_pkt_fates_stub(wifi_interface_handle handle, wifi_tx_report* tx_report_bufs, size_t n_requested_fates, size_t* n_provided_fates) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_rx_pkt_fates_stub(wifi_interface_handle handle, wifi_rx_report* rx_report_bufs, size_t n_requested_fates, size_t* n_provided_fates) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_enable_request_stub(transaction_id id, wifi_interface_handle iface, NanEnableRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_disable_request_stub(transaction_id id, wifi_interface_handle iface) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_publish_request_stub(transaction_id id, wifi_interface_handle iface, NanPublishRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_publish_cancel_request_stub(transaction_id id, wifi_interface_handle iface, NanPublishCancelRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_subscribe_request_stub(transaction_id id, wifi_interface_handle iface, NanSubscribeRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_subscribe_cancel_request_stub( transaction_id id, wifi_interface_handle iface, NanSubscribeCancelRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_transmit_followup_request_stub( transaction_id id, wifi_interface_handle iface, NanTransmitFollowupRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_stats_request_stub(transaction_id id, wifi_interface_handle iface, NanStatsRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_config_request_stub(transaction_id id, wifi_interface_handle iface, NanConfigRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_tca_request_stub(transaction_id id, wifi_interface_handle iface, NanTCARequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_beacon_sdf_payload_request_stub( transaction_id id, wifi_interface_handle iface, NanBeaconSdfPayloadRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_register_handler_stub(wifi_interface_handle iface, NanCallbackHandler handlers) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_get_version_stub(wifi_handle handle, NanVersion* version) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_get_capabilities_stub(transaction_id id, wifi_interface_handle iface) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_data_interface_create_stub(transaction_id id, wifi_interface_handle iface, char* iface_name) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_data_interface_delete_stub(transaction_id id, wifi_interface_handle iface, char* iface_name) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_data_request_initiator_stub( transaction_id id, wifi_interface_handle iface, NanDataPathInitiatorRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_data_indication_response_stub( transaction_id id, wifi_interface_handle iface, NanDataPathIndicationResponse* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_nan_data_end_stub(transaction_id id, wifi_interface_handle iface, NanDataPathEndRequest* msg) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_get_packet_filter_capabilities_stub( wifi_interface_handle handle, u32* version, u32* max_len) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } wifi_error wifi_set_packet_filter_stub(wifi_interface_handle handle, const u8* program, u32 len) { - return WIFI_ERROR_NOT_SUPPORTED; + return WIFI_SUCCESS; } bool init_wifi_stub_hal_func_table(wifi_hal_fn* hal_fn) { diff --git a/aosp/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_fallback.cpp b/aosp/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_fallback.cpp index fd0fff4f3..7b00f78d0 100644 --- a/aosp/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_fallback.cpp +++ b/aosp/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_fallback.cpp @@ -16,6 +16,324 @@ #include "hardware_legacy/wifi_hal.h" -wifi_error init_wifi_vendor_hal_func_table(wifi_hal_fn *fn) { - return WIFI_ERROR_NOT_SUPPORTED; +wifi_error wifi_initialize(wifi_handle *) +{ + return WIFI_SUCCESS; } + +wifi_error wifi_wait_for_driver_ready() +{ + return WIFI_SUCCESS; +} + +void wifi_cleanup(wifi_handle, wifi_cleaned_up_handler) +{ +} + +void wifi_event_loop(wifi_handle) +{ +} + +wifi_error wifi_get_supported_feature_set(wifi_interface_handle, feature_set *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_concurrency_matrix(wifi_interface_handle, int, feature_set set[], int *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_scanning_mac_oui(wifi_interface_handle, oui) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_ifaces(wifi_handle, int *, wifi_interface_handle **) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_iface_name(wifi_interface_handle, char *, size_t) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_start_gscan(wifi_request_id, wifi_interface_handle, wifi_scan_cmd_params, wifi_scan_result_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_stop_gscan(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_cached_gscan_results(wifi_interface_handle, byte, int, wifi_cached_scan_results *, int *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_bssid_hotlist(wifi_request_id, wifi_interface_handle, wifi_bssid_hotlist_params, + wifi_hotlist_ap_found_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_reset_bssid_hotlist(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_significant_change_handler(wifi_request_id, wifi_interface_handle, + wifi_significant_change_params, wifi_significant_change_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_reset_significant_change_handler(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_gscan_capabilities(wifi_interface_handle, wifi_gscan_capabilities *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_link_stats(wifi_request_id, wifi_interface_handle, wifi_stats_result_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_link_stats(wifi_interface_handle, wifi_link_layer_params) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_clear_link_stats(wifi_interface_handle, u32, u32 *, u8, u8 *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_valid_channels(wifi_interface_handle, int, int, wifi_channel *, int *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_rtt_range_request(wifi_request_id, wifi_interface_handle, unsigned, wifi_rtt_config rtt_config[], + wifi_rtt_event_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_rtt_range_cancel(wifi_request_id, wifi_interface_handle, unsigned, mac_addr addr[]) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_rtt_capabilities(wifi_interface_handle, wifi_rtt_capabilities *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_rtt_get_responder_info(wifi_interface_handle, wifi_rtt_responder *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_enable_responder(wifi_request_id, wifi_interface_handle, wifi_channel_info, unsigned, + wifi_rtt_responder*) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_disable_responder(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_nodfs_flag(wifi_interface_handle, u32) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_start_logging(wifi_interface_handle, u32, u32, u32, u32, char *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_epno_list(wifi_request_id, wifi_interface_handle, const wifi_epno_params *, wifi_epno_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_reset_epno_list(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_country_code(wifi_interface_handle, const char *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_firmware_memory_dump( wifi_interface_handle, wifi_firmware_memory_dump_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_log_handler(wifi_request_id, wifi_interface_handle, wifi_ring_buffer_data_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_reset_log_handler(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_alert_handler(wifi_request_id, wifi_interface_handle, wifi_alert_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_reset_alert_handler(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_firmware_version(wifi_interface_handle, char *, int) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_ring_buffers_status(wifi_interface_handle, u32 *, wifi_ring_buffer_status *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_logger_supported_feature_set(wifi_interface_handle, unsigned int *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_ring_data(wifi_interface_handle, char *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_driver_version(wifi_interface_handle, char *, int) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_start_rssi_monitoring(wifi_request_id, wifi_interface_handle, s8, s8, wifi_rssi_event_handler) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_stop_rssi_monitoring(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +static wifi_error wifi_configure_nd_offload(wifi_interface_handle, u8) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_start_sending_offloaded_packet(wifi_request_id, wifi_interface_handle, u16, u8 *, u16, u8 *, u8 *, u32) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_stop_sending_offloaded_packet(wifi_request_id, wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_start_pkt_fate_monitoring(wifi_interface_handle) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_tx_pkt_fates(wifi_interface_handle, wifi_tx_report *, size_t, size_t *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_rx_pkt_fates(wifi_interface_handle, wifi_rx_report *, size_t, size_t *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_get_packet_filter_capabilities(wifi_interface_handle, u32 *, u32 *) +{ + return WIFI_SUCCESS; +} + +wifi_error wifi_set_packet_filter(wifi_interface_handle, const u8 *, u32) +{ + return WIFI_SUCCESS; +} + +wifi_error init_wifi_vendor_hal_func_table(wifi_hal_fn *fn) +{ + if (fn == NULL) { + return WIFI_ERROR_UNKNOWN; + } + fn->wifi_initialize = wifi_initialize; + fn->wifi_wait_for_driver_ready = wifi_wait_for_driver_ready; + fn->wifi_cleanup = wifi_cleanup; + fn->wifi_event_loop = wifi_event_loop; + fn->wifi_get_supported_feature_set = wifi_get_supported_feature_set; + fn->wifi_get_concurrency_matrix = wifi_get_concurrency_matrix; + fn->wifi_set_scanning_mac_oui = wifi_set_scanning_mac_oui; + fn->wifi_get_ifaces = wifi_get_ifaces; + fn->wifi_get_iface_name = wifi_get_iface_name; + fn->wifi_start_gscan = wifi_start_gscan; + fn->wifi_stop_gscan = wifi_stop_gscan; + fn->wifi_get_cached_gscan_results = wifi_get_cached_gscan_results; + fn->wifi_set_bssid_hotlist = wifi_set_bssid_hotlist; + fn->wifi_reset_bssid_hotlist = wifi_reset_bssid_hotlist; + fn->wifi_set_significant_change_handler = wifi_set_significant_change_handler; + fn->wifi_reset_significant_change_handler = wifi_reset_significant_change_handler; + fn->wifi_get_gscan_capabilities = wifi_get_gscan_capabilities; + fn->wifi_get_link_stats = wifi_get_link_stats; + fn->wifi_set_link_stats = wifi_set_link_stats; + fn->wifi_clear_link_stats = wifi_clear_link_stats; + fn->wifi_get_valid_channels = wifi_get_valid_channels; + fn->wifi_rtt_range_request = wifi_rtt_range_request; + fn->wifi_rtt_range_cancel = wifi_rtt_range_cancel; + fn->wifi_get_rtt_capabilities = wifi_get_rtt_capabilities; + fn->wifi_rtt_get_responder_info = wifi_rtt_get_responder_info; + fn->wifi_enable_responder = wifi_enable_responder; + fn->wifi_disable_responder = wifi_disable_responder; + fn->wifi_set_nodfs_flag = wifi_set_nodfs_flag; + fn->wifi_start_logging = wifi_start_logging; + fn->wifi_set_epno_list = wifi_set_epno_list; + fn->wifi_reset_epno_list = wifi_reset_epno_list; + fn->wifi_set_country_code = wifi_set_country_code; + fn->wifi_get_firmware_memory_dump = wifi_get_firmware_memory_dump; + fn->wifi_set_log_handler = wifi_set_log_handler; + fn->wifi_reset_log_handler = wifi_reset_log_handler; + fn->wifi_set_alert_handler = wifi_set_alert_handler; + fn->wifi_reset_alert_handler = wifi_reset_alert_handler; + fn->wifi_get_firmware_version = wifi_get_firmware_version; + fn->wifi_get_ring_buffers_status = wifi_get_ring_buffers_status; + fn->wifi_get_logger_supported_feature_set = wifi_get_logger_supported_feature_set; + fn->wifi_get_ring_data = wifi_get_ring_data; + fn->wifi_get_driver_version = wifi_get_driver_version; + fn->wifi_start_rssi_monitoring = wifi_start_rssi_monitoring; + fn->wifi_stop_rssi_monitoring = wifi_stop_rssi_monitoring; + fn->wifi_configure_nd_offload = wifi_configure_nd_offload; + fn->wifi_start_sending_offloaded_packet = wifi_start_sending_offloaded_packet; + fn->wifi_stop_sending_offloaded_packet = wifi_stop_sending_offloaded_packet; + fn->wifi_start_pkt_fate_monitoring = wifi_start_pkt_fate_monitoring; + fn->wifi_get_tx_pkt_fates = wifi_get_tx_pkt_fates; + fn->wifi_get_rx_pkt_fates = wifi_get_rx_pkt_fates; + fn->wifi_get_packet_filter_capabilities = wifi_get_packet_filter_capabilities; + fn->wifi_set_packet_filter = wifi_set_packet_filter; + return WIFI_SUCCESS; +} \ No newline at end of file -- Gitee From 7977f9e5d8de07b2b876c6f689c349dfc192477c Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 15:13:52 +0800 Subject: [PATCH 07/10] packages add orig files --- aosp/packages/apps/Settings/Android.bp | 169 + .../network/NetworkProviderSettings.java | 1499 ++++ .../settings/wifi/WifiConfigController.java | 1878 +++++ .../src/android/net/ConnectivityManager.java | 6326 +++++++++++++++++ .../modules/Connectivity/service/Android.bp | 416 ++ .../server/connectivity/DnsManager.java | 499 ++ .../staticlibs/client-libs/Android.bp | 27 + .../android/net/module/util/NetdUtils.java | 316 + aosp/packages/modules/NetworkStack/Android.bp | 618 ++ .../NetworkStack/res/values/config.xml | 131 + .../src/android/net/dhcp/DhcpClient.java | 2094 ++++++ .../src/android/net/ip/IpClient.java | 3536 +++++++++ .../server/connectivity/NetworkMonitor.java | 4183 +++++++++++ aosp/packages/modules/Wifi/service/Android.bp | 182 + .../server/wifi/AvailableNetworkNotifier.java | 603 ++ .../android/server/wifi/ScanRequestProxy.java | 733 ++ .../wifi/SupplicantStaIfaceHalAidlImpl.java | 4016 +++++++++++ .../com/android/server/wifi/WifiInjector.java | 1344 ++++ .../com/android/server/wifi/WifiNative.java | 5447 ++++++++++++++ .../server/wifi/WifiNetworkSelector.java | 1769 +++++ .../wifi/scanner/WifiScanningServiceImpl.java | 3565 ++++++++++ .../wifi/scanner/WificondChannelHelper.java | 67 + .../wifi/scanner/WificondScannerImpl.java | 576 ++ .../modules/common/build/allowed_deps.txt | 1482 ++++ 24 files changed, 41476 insertions(+) create mode 100644 aosp/packages/apps/Settings/Android.bp create mode 100644 aosp/packages/apps/Settings/src/com/android/settings/network/NetworkProviderSettings.java create mode 100644 aosp/packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java create mode 100644 aosp/packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java create mode 100644 aosp/packages/modules/Connectivity/service/Android.bp create mode 100644 aosp/packages/modules/Connectivity/service/src/com/android/server/connectivity/DnsManager.java create mode 100644 aosp/packages/modules/Connectivity/staticlibs/client-libs/Android.bp create mode 100644 aosp/packages/modules/Connectivity/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java create mode 100644 aosp/packages/modules/NetworkStack/Android.bp create mode 100644 aosp/packages/modules/NetworkStack/res/values/config.xml create mode 100644 aosp/packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java create mode 100644 aosp/packages/modules/NetworkStack/src/android/net/ip/IpClient.java create mode 100755 aosp/packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java create mode 100644 aosp/packages/modules/Wifi/service/Android.bp create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/AvailableNetworkNotifier.java create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/ScanRequestProxy.java create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlImpl.java create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiInjector.java create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkSelector.java create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java create mode 100644 aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java create mode 100644 aosp/packages/modules/common/build/allowed_deps.txt diff --git a/aosp/packages/apps/Settings/Android.bp b/aosp/packages/apps/Settings/Android.bp new file mode 100644 index 000000000..b716117b0 --- /dev/null +++ b/aosp/packages/apps/Settings/Android.bp @@ -0,0 +1,169 @@ +package { + default_team: "trendy_team_android_settings_app", + default_applicable_licenses: ["packages_apps_Settings_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "packages_apps_Settings_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} + +java_library { + name: "settings-logtags", + srcs: ["src/**/*.logtags"], +} + +genrule { + name: "statslog-settings-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module settings" + + " --javaPackage com.android.settings.core.instrumentation --javaClass SettingsStatsLog", + out: ["com/android/settings/core/instrumentation/SettingsStatsLog.java"], +} + +java_library { + name: "statslog-settings", + srcs: [ + ":statslog-settings-java-gen", + ], +} + +java_library { + name: "Settings-change-ids", + srcs: ["src/com/android/settings/ChangeIds.java"], + libs: [ + "app-compat-annotations", + ], +} + +android_library { + name: "Settings-core", + defaults: [ + "SettingsLib-search-defaults", + "SettingsLintDefaults", + "SpaPrivilegedLib-defaults", + ], + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + exclude_srcs: [ + "src/com/android/settings/biometrics/fingerprint2/lib/**/*.kt", + ], + use_resource_processor: true, + resource_dirs: [ + "res", + "res-export", // for external usage + "res-product", + ], + static_libs: [ + // External dependencies + "androidx.navigation_navigation-fragment-ktx", + "androidx.window_window-java", + "gson", + "guava", + + // Android internal dependencies + "BiometricsSharedLib", + "SystemUIUnfoldLib", + "WifiTrackerLib", + "android.hardware.dumpstate-V1-java", + "android.hardware.dumpstate-V1.0-java", + "android.hardware.dumpstate-V1.1-java", + "android.view.accessibility.flags-aconfig-java", + "com_android_server_accessibility_flags_lib", + "net-utils-framework-common", + "notification_flags_lib", + "securebox", + + // Settings dependencies + "FingerprintManagerInteractor", + "MediaDrmSettingsFlagsLib", + "Settings-change-ids", + "SettingsLib", + "SettingsLibDataStore", + "SettingsLibActivityEmbedding", + "aconfig_settings_flags_lib", + "accessibility_settings_flags_lib", + "app-usage-event-protos-lite", + "battery-event-protos-lite", + "battery-usage-slot-protos-lite", + "contextualcards", + "development_settings_flag_lib", + "factory_reset_flags_lib", + "fuelgauge-log-protos-lite", + "fuelgauge-usage-state-protos-lite", + "power-anomaly-event-protos-lite", + "settings-contextual-card-protos-lite", + "settings-log-bridge-protos-lite", + "settings-logtags", + "settings-telephony-protos-lite", + "statslog-settings", + "androidx.test.rules", + "telephony_flags_core_java_lib", + "setupdesign-lottie-loading-layout", + ], + + plugins: ["androidx.room_room-compiler-plugin"], + + errorprone: { + extra_check_modules: ["//external/nullaway:nullaway_plugin"], + javacflags: [ + "-XepOpt:NullAway:AnnotatedPackages=com.android.settings", + ], + }, + + libs: [ + "telephony-common", + "ims-common", + ], +} + +platform_compat_config { + name: "settings-platform-compat-config", + src: ":Settings-change-ids", + system_ext_specific: true, +} + +// Build the Settings APK +android_app { + name: "Settings", + defaults: [ + "platform_app_defaults", + "SpaPrivilegedLib-defaults", + ], + platform_apis: true, + certificate: "platform", + system_ext_specific: true, + privileged: true, + required: [ + "privapp_whitelist_com.android.settings", + "settings-platform-compat-config", + ], + static_libs: ["Settings-core"], + uses_libs: ["org.apache.http.legacy"], + use_resource_processor: true, + resource_dirs: [], + optimize: { + proguard_flags_files: ["proguard.flags"], + }, +} + +android_library_import { + name: "contextualcards", + aars: ["libs/contextualcards.aar"], +} + +filegroup { + name: "Settings_proguard_flags", + srcs: ["proguard.flags"], +} diff --git a/aosp/packages/apps/Settings/src/com/android/settings/network/NetworkProviderSettings.java b/aosp/packages/apps/Settings/src/com/android/settings/network/NetworkProviderSettings.java new file mode 100644 index 000000000..0bc426c98 --- /dev/null +++ b/aosp/packages/apps/Settings/src/com/android/settings/network/NetworkProviderSettings.java @@ -0,0 +1,1499 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network; + +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; +import static android.os.UserManager.DISALLOW_CONFIG_WIFI; + +import android.app.Activity; +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.ActivityNotFoundException; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.location.LocationManager; +import android.net.NetworkTemplate; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.PowerManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; +import androidx.core.view.MenuProvider; +import androidx.fragment.app.Fragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settings.AirplaneModeEnabler; +import com.android.settings.R; +import com.android.settings.RestrictedSettingsFragment; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.datausage.DataUsagePreference; +import com.android.settings.datausage.DataUsageUtils; +import com.android.settings.location.WifiScanningFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.wifi.AddNetworkFragment; +import com.android.settings.wifi.AddWifiNetworkPreference; +import com.android.settings.wifi.ConfigureWifiEntryFragment; +import com.android.settings.wifi.ConnectedWifiEntryPreference; +import com.android.settings.wifi.LongPressWifiEntryPreference; +import com.android.settings.wifi.WifiConfigUiBase2; +import com.android.settings.wifi.WifiDialog2; +import com.android.settings.wifi.WifiPickerTrackerHelper; +import com.android.settings.wifi.WifiUtils; +import com.android.settings.wifi.details.WifiNetworkDetailsFragment; +import com.android.settings.wifi.dpp.WifiDppUtils; +import com.android.settingslib.HelpUtils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.search.Indexable; +import com.android.settingslib.search.SearchIndexable; +import com.android.settingslib.utils.StringUtil; +import com.android.settingslib.utils.ThreadUtils; +import com.android.settingslib.widget.FooterPreference; +import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; +import com.android.settingslib.wifi.WifiSavedConfigUtils; +import com.android.wifi.flags.Flags; +import com.android.wifitrackerlib.WifiEntry; +import com.android.wifitrackerlib.WifiEntry.ConnectCallback; +import com.android.wifitrackerlib.WifiPickerTracker; + +import java.util.List; +import java.util.Optional; + +/** + * UI for Mobile network and Wi-Fi network settings. + * + * TODO(b/167474581): Define the intent android.settings.NETWORK_PROVIDER_SETTINGS in Settings.java. + */ +@SearchIndexable +public class NetworkProviderSettings extends RestrictedSettingsFragment + implements Indexable, WifiPickerTracker.WifiPickerTrackerCallback, + WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener, + AirplaneModeEnabler.OnAirplaneModeChangedListener, InternetUpdater.InternetChangeListener { + + public static final String ACTION_NETWORK_PROVIDER_SETTINGS = + "android.settings.NETWORK_PROVIDER_SETTINGS"; + + private static final String TAG = "NetworkProviderSettings"; + // IDs of context menu + static final int MENU_ID_CONNECT = Menu.FIRST + 1; + @VisibleForTesting + static final int MENU_ID_DISCONNECT = Menu.FIRST + 2; + @VisibleForTesting + static final int MENU_ID_FORGET = Menu.FIRST + 3; + static final int MENU_ID_MODIFY = Menu.FIRST + 4; + static final int MENU_FIX_CONNECTIVITY = Menu.FIRST + 5; + static final int MENU_ID_SHARE = Menu.FIRST + 6; + + @VisibleForTesting + static final int ADD_NETWORK_REQUEST = 2; + static final int CONFIG_NETWORK_REQUEST = 3; + static final int MANAGE_SUBSCRIPTION = 4; + + private static final String PREF_KEY_AIRPLANE_MODE_MSG = "airplane_mode_message"; + private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list"; + @VisibleForTesting + static final String PREF_KEY_WIFI_TOGGLE = "main_toggle_wifi"; + // TODO(b/70983952): Rename these to use WifiEntry instead of AccessPoint. + @VisibleForTesting + static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point"; + @VisibleForTesting + static final String PREF_KEY_FIRST_ACCESS_POINTS = "first_access_points"; + private static final String PREF_KEY_ACCESS_POINTS = "access_points"; + @VisibleForTesting + static final String PREF_KEY_ADD_WIFI_NETWORK = "add_wifi_network"; + private static final String PREF_KEY_CONFIGURE_NETWORK_SETTINGS = "configure_network_settings"; + private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks"; + @VisibleForTesting + static final String PREF_KEY_DATA_USAGE = "non_carrier_data_usage"; + private static final String PREF_KEY_RESET_INTERNET = "resetting_your_internet"; + private static final String PREF_KEY_WIFI_STATUS_MESSAGE = "wifi_status_message_footer"; + + private static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0; + + public static final int WIFI_DIALOG_ID = 1; + + // Instance state keys + private static final String SAVE_DIALOG_MODE = "dialog_mode"; + private static final String SAVE_DIALOG_WIFIENTRY_KEY = "wifi_ap_key"; + + // Cache at onCreateContextMenu and use at onContextItemSelected. Don't use it in other methods. + private WifiEntry mSelectedWifiEntry; + + // Save the dialog details + private int mDialogMode; + private String mDialogWifiEntryKey; + private WifiEntry mDialogWifiEntry; + + // This boolean extra specifies whether to enable the Next button when connected. Used by + // account creation outside of setup wizard. + private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect"; + + // Enable the Next button when a Wi-Fi network is connected. + private boolean mEnableNextOnConnection; + + // This string extra specifies a network to open the connect dialog on, so the user can enter + // network credentials. This is used by quick settings for secured networks, among other + // things. + private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid"; + private String mOpenSsid; + + private boolean mIsViewLoading; + @VisibleForTesting + final Runnable mRemoveLoadingRunnable = () -> { + if (mIsViewLoading) { + setLoading(false, false); + mIsViewLoading = false; + } + }; + + @VisibleForTesting + final Runnable mUpdateWifiEntryPreferencesRunnable = () -> { + updateWifiEntryPreferences(); + View view = getView(); + if (view != null) { + view.postDelayed(mRemoveLoadingRunnable, 10); + } + }; + @VisibleForTesting + final Runnable mHideProgressBarRunnable = () -> { + setProgressBarVisible(false); + }; + + protected WifiManager mWifiManager; + private WifiManager.ActionListener mSaveListener; + + protected InternetResetHelper mInternetResetHelper; + + /** + * The state of {@link #isUiRestricted()} at {@link #onCreate(Bundle)}}. This is necessary to + * ensure that behavior is consistent if {@link #isUiRestricted()} changes. It could be changed + * by the Test DPC tool in AFW mode. + */ + protected boolean mIsRestricted; + @VisibleForTesting + boolean mIsAdmin = true; + @VisibleForTesting + boolean mIsGuest = false; + + @VisibleForTesting + AirplaneModeEnabler mAirplaneModeEnabler; + @VisibleForTesting + WifiPickerTracker mWifiPickerTracker; + private WifiPickerTrackerHelper mWifiPickerTrackerHelper; + @VisibleForTesting + InternetUpdater mInternetUpdater; + + private WifiDialog2 mDialog; + + @VisibleForTesting + PreferenceCategory mConnectedWifiEntryPreferenceCategory; + @VisibleForTesting + PreferenceCategory mFirstWifiEntryPreferenceCategory; + @VisibleForTesting + PreferenceCategory mWifiEntryPreferenceCategory; + @VisibleForTesting + AddWifiNetworkPreference mAddWifiNetworkPreference; + private WifiSwitchPreferenceController mWifiSwitchPreferenceController; + @VisibleForTesting + Preference mConfigureWifiSettingsPreference; + @VisibleForTesting + Preference mSavedNetworksPreference; + @VisibleForTesting + DataUsagePreference mDataUsagePreference; + @VisibleForTesting + Preference mAirplaneModeMsgPreference; + @VisibleForTesting + LayoutPreference mResetInternetPreference; + @VisibleForTesting + ConnectedEthernetNetworkController mConnectedEthernetNetworkController; + @VisibleForTesting + FooterPreference mWifiStatusMessagePreference; + @VisibleForTesting + MenuProvider mMenuProvider; + + /** + * Mobile networks list for provider model + */ + private static final String PREF_KEY_PROVIDER_MOBILE_NETWORK = "provider_model_mobile_network"; + private NetworkMobileProviderController mNetworkMobileProviderController; + + /** + * Tracks whether the user initiated a connection via clicking in order to autoscroll to the + * network once connected. + */ + private boolean mClickedConnect; + + public NetworkProviderSettings() { + super(DISALLOW_CONFIG_WIFI); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Activity activity = getActivity(); + if (activity == null) { + return; + } + + setPinnedHeaderView(com.android.settingslib.widget.progressbar.R.layout.progress_header); + setProgressBarVisible(false); + + if (hasWifiManager()) { + setLoading(true, false); + mIsViewLoading = true; + } + } + + private boolean hasWifiManager() { + if (mWifiManager != null) return true; + + Context context = getContext(); + if (context == null) return false; + + mWifiManager = context.getSystemService(WifiManager.class); + return (mWifiManager != null); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + mAirplaneModeEnabler = new AirplaneModeEnabler(getContext(), this); + + // TODO(b/37429702): Add animations and preference comparator back after initial screen is + // loaded (ODR). + setAnimationAllowed(false); + + addPreferences(); + + mIsRestricted = isUiRestricted(); + updateUserType(); + + mMenuProvider = new MenuProvider() { + @Override + public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { + MenuItem fixConnectivityItem = menu.add(0, MENU_FIX_CONNECTIVITY, 0, + R.string.fix_connectivity); + fixConnectivityItem.setIcon(R.drawable.ic_repair_24dp); + fixConnectivityItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } + + @Override + public boolean onMenuItemSelected(@NonNull MenuItem menuItem) { + if (menuItem.getItemId() == MENU_FIX_CONNECTIVITY) { + if (isPhoneOnCall()) { + showResetInternetDialog(); + return true; + } + fixConnectivity(); + return true; + } + return false; + } + + @Override + public void onPrepareMenu(@NonNull Menu menu) { + MenuProvider.super.onPrepareMenu(menu); + + boolean isWifiEnabled = mWifiPickerTracker != null + && mWifiPickerTracker.getWifiState() == WifiManager.WIFI_STATE_ENABLED; + boolean isAirplaneModeOn = + mAirplaneModeEnabler != null && mAirplaneModeEnabler.isAirplaneModeOn(); + MenuItem fixConnectivityItem = menu.findItem(MENU_FIX_CONNECTIVITY); + if (fixConnectivityItem == null) { + return; + } + fixConnectivityItem.setVisible(!mIsGuest && (!isAirplaneModeOn || isWifiEnabled)); + } + }; + } + + private void updateUserType() { + UserManager userManager = getSystemService(UserManager.class); + if (userManager == null) return; + mIsAdmin = userManager.isAdminUser(); + mIsGuest = userManager.isGuestUser(); + } + + private void addPreferences() { + addPreferencesFromResource(R.xml.network_provider_settings); + + mAirplaneModeMsgPreference = findPreference(PREF_KEY_AIRPLANE_MODE_MSG); + updateAirplaneModeMsgPreference(mAirplaneModeEnabler.isAirplaneModeOn() /* visible */); + mConnectedWifiEntryPreferenceCategory = findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS); + mFirstWifiEntryPreferenceCategory = findPreference(PREF_KEY_FIRST_ACCESS_POINTS); + mWifiEntryPreferenceCategory = findPreference(PREF_KEY_ACCESS_POINTS); + mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_NETWORK_SETTINGS); + mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS); + mAddWifiNetworkPreference = findPreference(PREF_KEY_ADD_WIFI_NETWORK); + // Hide mAddWifiNetworkPreference by default. updateWifiEntryPreferences() will add it back + // later when appropriate. + mWifiEntryPreferenceCategory.removePreference(mAddWifiNetworkPreference); + mDataUsagePreference = findPreference(PREF_KEY_DATA_USAGE); + mDataUsagePreference.setVisible(DataUsageUtils.hasWifiRadio(getContext())); + mDataUsagePreference.setTemplate(new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI) + .build(), 0 /*subId*/); + mResetInternetPreference = findPreference(PREF_KEY_RESET_INTERNET); + if (mResetInternetPreference != null) { + mResetInternetPreference.setVisible(false); + } + addNetworkMobileProviderController(); + addConnectedEthernetNetworkController(); + addWifiSwitchPreferenceController(); + mWifiStatusMessagePreference = findPreference(PREF_KEY_WIFI_STATUS_MESSAGE); + + checkConnectivityRecovering(); + } + + private void updateAirplaneModeMsgPreference(boolean visible) { + if (mAirplaneModeMsgPreference != null) { + mAirplaneModeMsgPreference.setVisible(visible); + } + } + + /** + * Whether to show any UI which is SIM related. + */ + @VisibleForTesting + boolean showAnySubscriptionInfo(Context context) { + return (context != null) && SubscriptionUtil.isSimHardwareVisible(context); + } + + private void addNetworkMobileProviderController() { + if (!showAnySubscriptionInfo(getContext())) { + return; + } + if (mNetworkMobileProviderController == null) { + mNetworkMobileProviderController = new NetworkMobileProviderController( + getContext(), PREF_KEY_PROVIDER_MOBILE_NETWORK); + } + mNetworkMobileProviderController.init(getSettingsLifecycle()); + mNetworkMobileProviderController.displayPreference(getPreferenceScreen()); + } + + private void addConnectedEthernetNetworkController() { + if (mConnectedEthernetNetworkController == null) { + mConnectedEthernetNetworkController = + new ConnectedEthernetNetworkController(getContext(), getSettingsLifecycle()); + } + mConnectedEthernetNetworkController.displayPreference(getPreferenceScreen()); + } + + private void addWifiSwitchPreferenceController() { + if (!hasWifiManager()) return; + if (mWifiSwitchPreferenceController == null) { + mWifiSwitchPreferenceController = + new WifiSwitchPreferenceController(getContext(), getSettingsLifecycle()); + } + mWifiSwitchPreferenceController.displayPreference(getPreferenceScreen()); + } + + private void checkConnectivityRecovering() { + mInternetResetHelper = new InternetResetHelper(getContext(), getLifecycle(), + mNetworkMobileProviderController, + findPreference(WifiSwitchPreferenceController.KEY), + mConnectedWifiEntryPreferenceCategory, + mFirstWifiEntryPreferenceCategory, + mWifiEntryPreferenceCategory, + mResetInternetPreference); + mInternetResetHelper.checkRecovering(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (hasWifiManager()) { + mWifiPickerTrackerHelper = + new WifiPickerTrackerHelper(getSettingsLifecycle(), getContext(), this); + mWifiPickerTracker = mWifiPickerTrackerHelper.getWifiPickerTracker(); + } + mInternetUpdater = new InternetUpdater(getContext(), getSettingsLifecycle(), this); + + mSaveListener = new WifiManager.ActionListener() { + @Override + public void onSuccess() { + } + + @Override + public void onFailure(int reason) { + Activity activity = getActivity(); + if (activity != null) { + Toast.makeText(activity, + R.string.wifi_failed_save_message, + Toast.LENGTH_SHORT).show(); + } + } + }; + + if (savedInstanceState != null) { + mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE); + mDialogWifiEntryKey = savedInstanceState.getString(SAVE_DIALOG_WIFIENTRY_KEY); + } + + // If we're supposed to enable/disable the Next button based on our current connection + // state, start it off in the right state. + final Intent intent = getActivity().getIntent(); + mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); + + if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) { + mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID); + } + + if (mNetworkMobileProviderController != null) { + mNetworkMobileProviderController.setWifiPickerTrackerHelper(mWifiPickerTrackerHelper); + } + + requireActivity().addMenuProvider(mMenuProvider); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + } + + @Override + public void onStart() { + super.onStart(); + if (mIsViewLoading) { + final long delayMillis = (hasWifiManager() && mWifiManager.isWifiEnabled()) + ? 1000 : 100; + getView().postDelayed(mRemoveLoadingRunnable, delayMillis); + } + if (mIsRestricted) { + restrictUi(); + return; + } + mAirplaneModeEnabler.start(); + } + + private void restrictUi() { + if (!isUiRestrictedByOnlyAdmin()) { + getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted); + } + getPreferenceScreen().removeAll(); + } + + @Override + public void onResume() { + super.onResume(); + + // Disable the animation of the preference list + final RecyclerView prefListView = getListView(); + if (prefListView != null) { + prefListView.setItemAnimator(null); + } + + // Because RestrictedSettingsFragment's onResume potentially requests authorization, + // which changes the restriction state, recalculate it. + final boolean alreadyImmutablyRestricted = mIsRestricted; + mIsRestricted = isUiRestricted(); + if (!alreadyImmutablyRestricted && mIsRestricted) { + restrictUi(); + } + + changeNextButtonState(mWifiPickerTracker != null + && mWifiPickerTracker.getConnectedWifiEntry() != null); + } + + @Override + public void onStop() { + getView().removeCallbacks(mRemoveLoadingRunnable); + getView().removeCallbacks(mUpdateWifiEntryPreferencesRunnable); + getView().removeCallbacks(mHideProgressBarRunnable); + mAirplaneModeEnabler.stop(); + super.onStop(); + } + + @Override + public void onDestroy() { + if (mAirplaneModeEnabler != null) { + mAirplaneModeEnabler.close(); + } + super.onDestroy(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (!hasWifiManager()) { + // Do nothing + } else if (requestCode == ADD_NETWORK_REQUEST) { + handleAddNetworkRequest(resultCode, data); + return; + } else if (requestCode == REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER) { + if (resultCode == Activity.RESULT_OK) { + if (mDialog != null) { + mDialog.dismiss(); + } + } + return; + } else if (requestCode == CONFIG_NETWORK_REQUEST) { + if (resultCode == Activity.RESULT_OK) { + final WifiConfiguration wifiConfiguration = data.getParcelableExtra( + ConfigureWifiEntryFragment.NETWORK_CONFIG_KEY); + if (wifiConfiguration != null) { + mWifiManager.connect(wifiConfiguration, + new WifiConnectActionListener()); + } + } + return; + } else if (requestCode == MANAGE_SUBSCRIPTION) { + //Do nothing + return; + } + + final boolean formerlyRestricted = mIsRestricted; + mIsRestricted = isUiRestricted(); + if (formerlyRestricted && !mIsRestricted + && getPreferenceScreen().getPreferenceCount() == 0) { + // De-restrict the ui + addPreferences(); + } + } + + @Override + protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { + final RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen); + adapter.setHasStableIds(true); + return adapter; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.WIFI; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + // If dialog has been shown, save its state. + if (mDialog != null) { + outState.putInt(SAVE_DIALOG_MODE, mDialogMode); + outState.putString(SAVE_DIALOG_WIFIENTRY_KEY, mDialogWifiEntryKey); + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { + Preference preference = (Preference) view.getTag(); + if (!(preference instanceof LongPressWifiEntryPreference)) { + // Do nothing. + return; + } + + // Cache the WifiEntry for onContextItemSelected. Don't use it in other methods. + mSelectedWifiEntry = ((LongPressWifiEntryPreference) preference).getWifiEntry(); + + menu.setHeaderTitle(mSelectedWifiEntry.getTitle()); + if (mSelectedWifiEntry.canConnect()) { + menu.add(Menu.NONE, MENU_ID_CONNECT, 0 /* order */, R.string.wifi_connect); + } + + if (mSelectedWifiEntry.canDisconnect()) { + if (mSelectedWifiEntry.canShare()) { + addShareMenuIfSuitable(menu); + } + menu.add(Menu.NONE, MENU_ID_DISCONNECT, 1 /* order */, + R.string.wifi_disconnect_button_text); + } + + // "forget" for normal saved network. And "disconnect" for ephemeral network because it + // could only be disconnected and be put in blocklists so it won't be used again. + if (canForgetNetwork()) { + addForgetMenuIfSuitable(menu); + } + + WifiConfiguration config = mSelectedWifiEntry.getWifiConfiguration(); + // Some configs are ineditable + if (WifiUtils.isNetworkLockedDown(getActivity(), config)) { + return; + } + + addModifyMenuIfSuitable(menu, mSelectedWifiEntry); + } + + @VisibleForTesting + void addShareMenuIfSuitable(ContextMenu menu) { + if (mIsAdmin) { + menu.add(Menu.NONE, MENU_ID_SHARE, 0 /* order */, R.string.share); + return; + } + Log.w(TAG, "Don't add the Wi-Fi share menu because the user is not an admin."); + EventLog.writeEvent(0x534e4554, "206986392", -1 /* UID */, "User is not an admin"); + } + + @VisibleForTesting + void addForgetMenuIfSuitable(ContextMenu menu) { + if (mIsAdmin) { + menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget); + } + } + + @VisibleForTesting + void addModifyMenuIfSuitable(ContextMenu menu, WifiEntry wifiEntry) { + if (mIsAdmin && wifiEntry.isSaved() + && wifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED) { + menu.add(Menu.NONE, MENU_ID_MODIFY, 0 /* order */, R.string.wifi_modify); + } + } + + private boolean canForgetNetwork() { + return mSelectedWifiEntry.canForget() && !WifiUtils.isNetworkLockedDown(getActivity(), + mSelectedWifiEntry.getWifiConfiguration()); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_ID_CONNECT: + connect(mSelectedWifiEntry, true /* editIfNoConfig */, false /* fullScreenEdit */); + return true; + case MENU_ID_DISCONNECT: + mSelectedWifiEntry.disconnect(null /* callback */); + return true; + case MENU_ID_FORGET: + forget(mSelectedWifiEntry); + return true; + case MENU_ID_SHARE: + WifiDppUtils.showLockScreen(getContext(), + () -> launchWifiDppConfiguratorActivity(mSelectedWifiEntry)); + return true; + case MENU_ID_MODIFY: + if (!mIsAdmin) { + Log.e(TAG, "Can't modify Wi-Fi because the user isn't admin."); + EventLog.writeEvent(0x534e4554, "237672190", UserHandle.myUserId(), + "User isn't admin"); + return true; + } + showDialog(mSelectedWifiEntry, WifiConfigUiBase2.MODE_MODIFY); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + // If the preference has a fragment set, open that + if (preference.getFragment() != null) { + preference.setOnPreferenceClickListener(null); + return super.onPreferenceTreeClick(preference); + } + + if (preference instanceof LongPressWifiEntryPreference) { + onSelectedWifiPreferenceClick((LongPressWifiEntryPreference) preference); + } else if (preference == mAddWifiNetworkPreference) { + onAddNetworkPressed(); + } else { + return super.onPreferenceTreeClick(preference); + } + return true; + } + + @VisibleForTesting + void onSelectedWifiPreferenceClick(LongPressWifiEntryPreference preference) { + final WifiEntry selectedEntry = preference.getWifiEntry(); + + if (selectedEntry.shouldEditBeforeConnect()) { + launchConfigNewNetworkFragment(selectedEntry); + return; + } + + if (selectedEntry.canConnect()) { + connect(selectedEntry, true /* editIfNoConfig */, true /* fullScreenEdit */); + return; + } + + if (selectedEntry.isSaved()) { + launchNetworkDetailsFragment(preference); + } + } + + private void launchWifiDppConfiguratorActivity(WifiEntry wifiEntry) { + final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(getContext(), + mWifiManager, wifiEntry); + + if (intent == null) { + Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!"); + } else { + mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, + SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_QR_CODE, + SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR, + /* key */ null, + /* value */ Integer.MIN_VALUE); + + startActivity(intent); + } + } + + private void showDialog(WifiEntry wifiEntry, int dialogMode) { + if (WifiUtils.isNetworkLockedDown(getActivity(), wifiEntry.getWifiConfiguration()) + && wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), + RestrictedLockUtilsInternal.getDeviceOwner(getActivity())); + return; + } + + if (mDialog != null) { + removeDialog(WIFI_DIALOG_ID); + mDialog = null; + } + + // Save the access point and edit mode + mDialogWifiEntry = wifiEntry; + mDialogWifiEntryKey = wifiEntry.getKey(); + mDialogMode = dialogMode; + + showDialog(WIFI_DIALOG_ID); + } + + @Override + public Dialog onCreateDialog(int dialogId) { + if (dialogId == WIFI_DIALOG_ID) { // modify network + mDialog = new WifiDialog2(requireContext(), this, mDialogWifiEntry, mDialogMode); + return mDialog; + } + return super.onCreateDialog(dialogId); + } + + @Override + public void onDialogShowing() { + super.onDialogShowing(); + setOnDismissListener(this); + } + + @Override + public void onDismiss(DialogInterface dialog) { + // We don't keep any dialog object when dialog was dismissed. + mDialog = null; + mDialogWifiEntry = null; + mDialogWifiEntryKey = null; + } + + @Override + public int getDialogMetricsCategory(int dialogId) { + switch (dialogId) { + case WIFI_DIALOG_ID: + return SettingsEnums.DIALOG_WIFI_AP_EDIT; + default: + return 0; + } + } + + @Override + public void onInternetTypeChanged(@InternetUpdater.InternetType int internetType) { + ThreadUtils.postOnMainThread(() -> { + onWifiStateChanged(); + }); + } + + /** Called when the state of Wifi has changed. */ + @Override + public void onWifiStateChanged() { + if (mIsRestricted || !hasWifiManager()) { + return; + } + final int wifiState = mWifiPickerTracker.getWifiState(); + + if (mWifiPickerTracker.isVerboseLoggingEnabled()) { + Log.i(TAG, "onWifiStateChanged called with wifi state: " + wifiState); + } + + if (isFinishingOrDestroyed()) { + Log.w(TAG, "onWifiStateChanged shouldn't run when fragment is finishing or destroyed"); + return; + } + + if (isAdded()) { + // update the menu item + requireActivity().invalidateMenu(); + } + + switch (wifiState) { + case WifiManager.WIFI_STATE_ENABLED: + setWifiScanMessage(/* isWifiEnabled */ true); + updateWifiEntryPreferences(); + break; + + case WifiManager.WIFI_STATE_ENABLING: + removeConnectedWifiEntryPreference(); + removeWifiEntryPreference(); + setProgressBarVisible(true); + break; + + case WifiManager.WIFI_STATE_DISABLING: + removeConnectedWifiEntryPreference(); + removeWifiEntryPreference(); + break; + + case WifiManager.WIFI_STATE_DISABLED: + setWifiScanMessage(/* isWifiEnabled */ false); + removeConnectedWifiEntryPreference(); + removeWifiEntryPreference(); + setAdditionalSettingsSummaries(); + setProgressBarVisible(false); + mClickedConnect = false; + break; + } + } + + @Override + public void onScanRequested() { + setProgressBarVisible(true); + } + + @VisibleForTesting + void setWifiScanMessage(boolean isWifiEnabled) { + final Context context = getContext(); + if (context == null) { + return; + } + + final LocationManager locationManager = context.getSystemService(LocationManager.class); + if (!hasWifiManager() || isWifiEnabled || !locationManager.isLocationEnabled() + || !mWifiManager.isScanAlwaysAvailable()) { + mWifiStatusMessagePreference.setVisible(false); + return; + } + if (TextUtils.isEmpty(mWifiStatusMessagePreference.getTitle())) { + mWifiStatusMessagePreference.setTitle(R.string.wifi_scan_notify_message); + mWifiStatusMessagePreference.setLearnMoreText( + context.getString(R.string.wifi_scan_change)); + mWifiStatusMessagePreference.setLearnMoreAction(v -> launchWifiScanningFragment()); + } + mWifiStatusMessagePreference.setVisible(true); + } + + private void launchWifiScanningFragment() { + new SubSettingLauncher(getContext()) + .setDestination(WifiScanningFragment.class.getName()) + .setSourceMetricsCategory(SettingsEnums.SETTINGS_NETWORK_CATEGORY) + .launch(); + } + + @Override + public void onWifiEntriesChanged(@WifiPickerTracker.WifiEntriesChangedReason int reason) { + updateWifiEntryPreferences(); + if (reason == WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS) { + setProgressBarVisible(false); + } + changeNextButtonState(mWifiPickerTracker != null + && mWifiPickerTracker.getConnectedWifiEntry() != null); + + // Edit the Wi-Fi network of specified SSID. + if (mOpenSsid != null && mWifiPickerTracker != null) { + Optional matchedWifiEntry = mWifiPickerTracker.getWifiEntries().stream() + .filter(wifiEntry -> TextUtils.equals(mOpenSsid, wifiEntry.getSsid())) + .filter(wifiEntry -> wifiEntry.getSecurity() != WifiEntry.SECURITY_NONE + && wifiEntry.getSecurity() != WifiEntry.SECURITY_OWE) + .filter(wifiEntry -> !wifiEntry.isSaved() + || isDisabledByWrongPassword(wifiEntry)) + .findFirst(); + if (matchedWifiEntry.isPresent()) { + mOpenSsid = null; + launchConfigNewNetworkFragment(matchedWifiEntry.get()); + } + } + } + + @Override + public void onNumSavedNetworksChanged() { + if (isFinishingOrDestroyed()) { + return; + } + setAdditionalSettingsSummaries(); + } + + @Override + public void onNumSavedSubscriptionsChanged() { + if (isFinishingOrDestroyed()) { + return; + } + setAdditionalSettingsSummaries(); + } + + protected void updateWifiEntryPreferences() { + // bypass the update if the activity and the view are not ready, or it's restricted UI. + if (getActivity() == null || getView() == null || mIsRestricted) { + return; + } + // in case state has changed + if (mWifiPickerTracker == null + || mWifiPickerTracker.getWifiState() != WifiManager.WIFI_STATE_ENABLED) { + return; + } + + boolean hasAvailableWifiEntries = false; + mWifiEntryPreferenceCategory.setVisible(true); + + final WifiEntry connectedEntry = mWifiPickerTracker.getConnectedWifiEntry(); + PreferenceCategory connectedWifiPreferenceCategory = getConnectedWifiPreferenceCategory(); + connectedWifiPreferenceCategory.setVisible(connectedEntry != null); + if (connectedEntry != null) { + final LongPressWifiEntryPreference connectedPref = + connectedWifiPreferenceCategory.findPreference(connectedEntry.getKey()); + if (connectedPref == null || connectedPref.getWifiEntry() != connectedEntry) { + connectedWifiPreferenceCategory.removeAll(); + final ConnectedWifiEntryPreference pref = + createConnectedWifiEntryPreference(connectedEntry); + pref.setKey(connectedEntry.getKey()); + pref.refresh(); + connectedWifiPreferenceCategory.addPreference(pref); + pref.setOnPreferenceClickListener(preference -> { + if (connectedEntry.canSignIn()) { + connectedEntry.signIn(null /* callback */); + } else { + launchNetworkDetailsFragment(pref); + } + return true; + }); + pref.setOnGearClickListener(preference -> { + launchNetworkDetailsFragment(pref); + }); + + if (mClickedConnect) { + mClickedConnect = false; + scrollToPreference(connectedWifiPreferenceCategory); + } + } + } else { + connectedWifiPreferenceCategory.removeAll(); + } + + int index = 0; + cacheRemoveAllPrefs(mWifiEntryPreferenceCategory); + List wifiEntries = mWifiPickerTracker.getWifiEntries(); + for (WifiEntry wifiEntry : wifiEntries) { + hasAvailableWifiEntries = true; + + String key = wifiEntry.getKey(); + LongPressWifiEntryPreference pref = + (LongPressWifiEntryPreference) getCachedPreference(key); + if (pref != null) { + if (pref.getWifiEntry() == wifiEntry) { + pref.setOrder(index++); + continue; + } else { + // Create a new preference if the underlying WifiEntry object has changed + removePreference(key); + } + } + + pref = createLongPressWifiEntryPreference(wifiEntry); + pref.setKey(wifiEntry.getKey()); + pref.setOrder(index++); + pref.refresh(); + + if (wifiEntry.getHelpUriString() != null) { + pref.setOnButtonClickListener(preference -> { + openSubscriptionHelpPage(wifiEntry); + }); + } + mWifiEntryPreferenceCategory.addPreference(pref); + } + removeCachedPrefs(mWifiEntryPreferenceCategory); + + if (!hasAvailableWifiEntries) { + Preference pref = new Preference(getPrefContext()); + pref.setSelectable(false); + pref.setSummary(R.string.wifi_empty_list_wifi_on); + pref.setOrder(index++); + pref.setKey(PREF_KEY_EMPTY_WIFI_LIST); + mWifiEntryPreferenceCategory.addPreference(pref); + } + + mAddWifiNetworkPreference.setOrder(index++); + mWifiEntryPreferenceCategory.addPreference(mAddWifiNetworkPreference); + setAdditionalSettingsSummaries(); + } + + @VisibleForTesting + PreferenceCategory getConnectedWifiPreferenceCategory() { + if (mInternetUpdater.getInternetType() == InternetUpdater.INTERNET_WIFI) { + mFirstWifiEntryPreferenceCategory.setVisible(false); + mFirstWifiEntryPreferenceCategory.removeAll(); + return mConnectedWifiEntryPreferenceCategory; + } + + mConnectedWifiEntryPreferenceCategory.setVisible(false); + mConnectedWifiEntryPreferenceCategory.removeAll(); + return mFirstWifiEntryPreferenceCategory; + } + + @VisibleForTesting + ConnectedWifiEntryPreference createConnectedWifiEntryPreference(WifiEntry wifiEntry) { + if (mInternetUpdater.getInternetType() == InternetUpdater.INTERNET_WIFI) { + return new ConnectedWifiEntryPreference(getPrefContext(), wifiEntry, this); + } + return new FirstWifiEntryPreference(getPrefContext(), wifiEntry, this); + } + + @VisibleForTesting + void launchNetworkDetailsFragment(LongPressWifiEntryPreference pref) { + final WifiEntry wifiEntry = pref.getWifiEntry(); + final Context context = requireContext(); + + final Bundle bundle = new Bundle(); + bundle.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY, wifiEntry.getKey()); + + new SubSettingLauncher(context) + .setTitleText(context.getText(R.string.pref_title_network_details)) + .setDestination(WifiNetworkDetailsFragment.class.getName()) + .setArguments(bundle) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + } + + @VisibleForTesting + LongPressWifiEntryPreference createLongPressWifiEntryPreference(WifiEntry wifiEntry) { + return new LongPressWifiEntryPreference(getPrefContext(), wifiEntry, this); + } + + private void launchAddNetworkFragment() { + new SubSettingLauncher(getContext()) + .setTitleRes(R.string.wifi_add_network) + .setDestination(AddNetworkFragment.class.getName()) + .setSourceMetricsCategory(getMetricsCategory()) + .setResultListener(this, ADD_NETWORK_REQUEST) + .launch(); + } + + /** Removes all preferences and hide the {@link #mConnectedWifiEntryPreferenceCategory} and + * {@link #mFirstWifiEntryPreferenceCategory}. */ + private void removeConnectedWifiEntryPreference() { + mConnectedWifiEntryPreferenceCategory.removeAll(); + mConnectedWifiEntryPreferenceCategory.setVisible(false); + mFirstWifiEntryPreferenceCategory.setVisible(false); + mFirstWifiEntryPreferenceCategory.removeAll(); + } + + private void removeWifiEntryPreference() { + mWifiEntryPreferenceCategory.removeAll(); + mWifiEntryPreferenceCategory.setVisible(false); + } + + @VisibleForTesting + void setAdditionalSettingsSummaries() { + mConfigureWifiSettingsPreference.setSummary(getString( + isWifiWakeupEnabled() + ? R.string.wifi_configure_settings_preference_summary_wakeup_on + : R.string.wifi_configure_settings_preference_summary_wakeup_off)); + + final int numSavedNetworks = mWifiPickerTracker == null ? 0 : + mWifiPickerTracker.getNumSavedNetworks(); + final int numSavedSubscriptions = mWifiPickerTracker == null ? 0 : + mWifiPickerTracker.getNumSavedSubscriptions(); + if (numSavedNetworks + numSavedSubscriptions > 0) { + mSavedNetworksPreference.setVisible(true); + mSavedNetworksPreference.setSummary( + getSavedNetworkSettingsSummaryText(numSavedNetworks, numSavedSubscriptions)); + } else { + mSavedNetworksPreference.setVisible(false); + } + } + + private String getSavedNetworkSettingsSummaryText( + int numSavedNetworks, int numSavedSubscriptions) { + if (getContext() == null) { + Log.w(TAG, "getSavedNetworkSettingsSummaryText shouldn't run if resource is not ready"); + return null; + } + + if (numSavedSubscriptions == 0) { + return StringUtil.getIcuPluralsString(getContext(), numSavedNetworks, + R.string.wifi_saved_access_points_summary); + } else if (numSavedNetworks == 0) { + return StringUtil.getIcuPluralsString(getContext(), numSavedSubscriptions, + R.string.wifi_saved_passpoint_access_points_summary); + } else { + final int numTotalEntries = numSavedNetworks + numSavedSubscriptions; + return StringUtil.getIcuPluralsString(getContext(), numTotalEntries, + R.string.wifi_saved_all_access_points_summary); + } + } + + private boolean isWifiWakeupEnabled() { + final Context context = getContext(); + final PowerManager powerManager = context.getSystemService(PowerManager.class); + final ContentResolver contentResolver = context.getContentResolver(); + return hasWifiManager() + && mWifiManager.isAutoWakeupEnabled() + && mWifiManager.isScanAlwaysAvailable() + && Settings.Global.getInt(contentResolver, + Settings.Global.AIRPLANE_MODE_ON, 0) == 0 + && !powerManager.isPowerSaveMode(); + } + + protected void setProgressBarVisible(boolean visible) { + showPinnedHeader(visible); + } + + @VisibleForTesting + void handleAddNetworkRequest(int result, Intent data) { + if (result == Activity.RESULT_OK) { + handleAddNetworkSubmitEvent(data); + } + } + + private void handleAddNetworkSubmitEvent(Intent data) { + final WifiConfiguration wifiConfiguration = data.getParcelableExtra( + AddNetworkFragment.WIFI_CONFIG_KEY); + if (wifiConfiguration != null && hasWifiManager()) { + mWifiManager.save(wifiConfiguration, mSaveListener); + } + } + + /** + * Called when "add network" button is pressed. + */ + private void onAddNetworkPressed() { + launchAddNetworkFragment(); + } + + @Override + public int getHelpResource() { + return R.string.help_url_wifi; + } + + /** + * Renames/replaces "Next" button when appropriate. "Next" button usually exists in + * Wi-Fi setup screens, not in usual wifi settings screen. + * + * @param enabled true when the device is connected to a wifi network. + */ + @VisibleForTesting + void changeNextButtonState(boolean enabled) { + if (mEnableNextOnConnection && hasNextButton()) { + getNextButton().setEnabled(enabled); + } + } + + @Override + public void onForget(WifiDialog2 dialog) { + forget(dialog.getWifiEntry()); + } + + @Override + public void onSubmit(WifiDialog2 dialog) { + if (!hasWifiManager()) return; + + final int dialogMode = dialog.getMode(); + final WifiConfiguration config = dialog.getController().getConfig(); + final WifiEntry wifiEntry = dialog.getWifiEntry(); + + if (dialogMode == WifiConfigUiBase2.MODE_MODIFY) { + if (config == null) { + Toast.makeText(getContext(), R.string.wifi_failed_save_message, + Toast.LENGTH_SHORT).show(); + } else { + mWifiManager.save(config, mSaveListener); + } + } else if (dialogMode == WifiConfigUiBase2.MODE_CONNECT + || (dialogMode == WifiConfigUiBase2.MODE_VIEW && wifiEntry.canConnect())) { + if (config == null) { + connect(wifiEntry, false /* editIfNoConfig */, + false /* fullScreenEdit*/); + } else { + mWifiManager.connect(config, new WifiConnectActionListener()); + } + } + } + + @Override + public void onScan(WifiDialog2 dialog, String ssid) { + // Launch QR code scanner to join a network. + startActivityForResult( + WifiDppUtils.getEnrolleeQrCodeScannerIntent(dialog.getContext(), ssid), + REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER); + } + + private void forget(WifiEntry wifiEntry) { + mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_FORGET); + wifiEntry.forget(null /* callback */); + } + + @VisibleForTesting + void connect(WifiEntry wifiEntry, boolean editIfNoConfig, boolean fullScreenEdit) { + mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_CONNECT, + wifiEntry.isSaved()); + + // If it's an unsaved secure WifiEntry, it will callback + // ConnectCallback#onConnectResult with ConnectCallback#CONNECT_STATUS_FAILURE_NO_CONFIG + WifiEntryConnectCallback callback = + new WifiEntryConnectCallback(wifiEntry, editIfNoConfig, fullScreenEdit); + + if (Flags.androidVWifiApi() && wifiEntry.getSecurityTypes() + .contains(WifiEntry.SECURITY_WEP)) { + WifiUtils.checkWepAllowed( + getContext(), getViewLifecycleOwner(), wifiEntry.getSsid(), () -> { + wifiEntry.connect(callback); + return null; + }); + return; + } + + wifiEntry.connect(callback); + } + + private class WifiConnectActionListener implements WifiManager.ActionListener { + @Override + public void onSuccess() { + mClickedConnect = true; + } + + @Override + public void onFailure(int reason) { + if (isFinishingOrDestroyed()) { + return; + } + Toast.makeText(getContext(), R.string.wifi_failed_connect_message, Toast.LENGTH_SHORT) + .show(); + } + }; + + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new SearchIndexProvider(R.xml.network_provider_settings); + + @VisibleForTesting + static class SearchIndexProvider extends BaseSearchIndexProvider { + + private final WifiRestriction mWifiRestriction; + + SearchIndexProvider(int xmlRes) { + super(xmlRes); + mWifiRestriction = new WifiRestriction(); + } + + @VisibleForTesting + SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction) { + super(xmlRes); + mWifiRestriction = wifiRestriction; + } + + @Override + public List getNonIndexableKeys(Context context) { + final List keys = super.getNonIndexableKeys(context); + + if (!mWifiRestriction.isChangeWifiStateAllowed(context)) { + keys.add(PREF_KEY_WIFI_TOGGLE); + } + + final WifiManager wifiManager = context.getSystemService(WifiManager.class); + if (wifiManager == null) return keys; + + if (WifiSavedConfigUtils.getAllConfigsCount(context, wifiManager) == 0) { + keys.add(PREF_KEY_SAVED_NETWORKS); + } + if (wifiManager.getWifiState() != WifiManager.WIFI_STATE_ENABLED) { + keys.add(PREF_KEY_ADD_WIFI_NETWORK); + } + + if (!DataUsageUtils.hasWifiRadio(context)) { + keys.add(PREF_KEY_DATA_USAGE); + } + return keys; + } + } + + @VisibleForTesting + static class WifiRestriction { + public boolean isChangeWifiStateAllowed(@Nullable Context context) { + if (context == null) return true; + return WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context); + } + } + + private class WifiEntryConnectCallback implements ConnectCallback { + final WifiEntry mConnectWifiEntry; + final boolean mEditIfNoConfig; + final boolean mFullScreenEdit; + + WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig, + boolean fullScreenEdit) { + mConnectWifiEntry = connectWifiEntry; + mEditIfNoConfig = editIfNoConfig; + mFullScreenEdit = fullScreenEdit; + } + + @Override + public void onConnectResult(@ConnectStatus int status) { + if (isFinishingOrDestroyed()) { + return; + } + + if (status == ConnectCallback.CONNECT_STATUS_SUCCESS) { + mClickedConnect = true; + } else if (status == ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) { + if (mEditIfNoConfig) { + // Edit an unsaved secure Wi-Fi network. + if (mFullScreenEdit) { + launchConfigNewNetworkFragment(mConnectWifiEntry); + } else { + showDialog(mConnectWifiEntry, WifiConfigUiBase2.MODE_CONNECT); + } + } + } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) { + Toast.makeText(getContext(), R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + } + } + } + + @VisibleForTesting + void launchConfigNewNetworkFragment(WifiEntry wifiEntry) { + if (mIsRestricted) { + Log.e(TAG, "Can't configure Wi-Fi because NetworkProviderSettings is restricted."); + EventLog.writeEvent(0x534e4554, "246301667", -1 /* UID */, "Fragment is restricted."); + return; + } + + final Bundle bundle = new Bundle(); + bundle.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY, + wifiEntry.getKey()); + new SubSettingLauncher(getContext()) + .setTitleText(wifiEntry.getTitle()) + .setDestination(ConfigureWifiEntryFragment.class.getName()) + .setArguments(bundle) + .setSourceMetricsCategory(getMetricsCategory()) + .setResultListener(NetworkProviderSettings.this, CONFIG_NETWORK_REQUEST) + .launch(); + } + + /** Helper method to return whether a WifiEntry is disabled due to a wrong password */ + private static boolean isDisabledByWrongPassword(WifiEntry wifiEntry) { + WifiConfiguration config = wifiEntry.getWifiConfiguration(); + if (config == null) { + return false; + } + WifiConfiguration.NetworkSelectionStatus networkStatus = + config.getNetworkSelectionStatus(); + if (networkStatus == null + || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) { + return false; + } + int reason = networkStatus.getNetworkSelectionDisableReason(); + return WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD == reason; + } + + @VisibleForTesting + void openSubscriptionHelpPage(WifiEntry wifiEntry) { + final Intent intent = getHelpIntent(getContext(), wifiEntry.getHelpUriString()); + if (intent != null) { + try { + startActivityForResult(intent, MANAGE_SUBSCRIPTION); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Activity was not found for intent, " + intent.toString()); + } + } + } + + @VisibleForTesting + Intent getHelpIntent(Context context, String helpUrlString) { + return HelpUtils.getHelpIntent(context, helpUrlString, context.getClass().getName()); + } + + @VisibleForTesting + void showResetInternetDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + DialogInterface.OnClickListener resetInternetClickListener = + new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + fixConnectivity(); + } + }; + builder.setTitle(R.string.reset_your_internet_title) + .setMessage(R.string.reset_internet_text) + .setPositiveButton(R.string.tts_reset, resetInternetClickListener) + .setNegativeButton(android.R.string.cancel, null) + .create() + .show(); + } + + @VisibleForTesting + boolean isPhoneOnCall() { + TelephonyManager mTelephonyManager = getActivity().getSystemService(TelephonyManager.class); + int state = mTelephonyManager.getCallState(); + return state != TelephonyManager.CALL_STATE_IDLE; + } + + private void fixConnectivity() { + if (mIsGuest) { + Log.e(TAG, "Can't reset network because the user is a guest."); + EventLog.writeEvent(0x534e4554, "252995826", UserHandle.myUserId(), "User is a guest"); + return; + } + mInternetResetHelper.restart(); + } + + /** + * Called when airplane mode status is changed. + * + * @param isAirplaneModeOn The airplane mode is on + */ + @Override + public void onAirplaneModeChanged(boolean isAirplaneModeOn) { + updateAirplaneModeMsgPreference(isAirplaneModeOn /* visible */); + if (isAdded()) { + // update the menu item + requireActivity().invalidateMenu(); + } + } + + /** + * A Wi-Fi preference for the connected Wi-Fi network without internet access. + * + * Override the icon color attribute by {@link ConnectedWifiEntryPreference#getIconColorAttr()} + * and show the icon color to android.R.attr.colorControlNormal for the preference. + */ + public class FirstWifiEntryPreference extends ConnectedWifiEntryPreference { + public FirstWifiEntryPreference(Context context, WifiEntry wifiEntry, + Fragment fragment) { + super(context, wifiEntry, fragment); + } + + @Override + protected int getIconColorAttr() { + return android.R.attr.colorControlNormal; + } + } +} diff --git a/aosp/packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java b/aosp/packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java new file mode 100644 index 000000000..5d45cb235 --- /dev/null +++ b/aosp/packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java @@ -0,0 +1,1878 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.wifi; + +import android.content.Context; +import android.content.res.Resources; +import android.net.InetAddresses; +import android.net.IpConfiguration; +import android.net.IpConfiguration.IpAssignment; +import android.net.IpConfiguration.ProxySettings; +import android.net.LinkAddress; +import android.net.NetworkInfo.DetailedState; +import android.net.ProxyInfo; +import android.net.StaticIpConfiguration; +import android.net.Uri; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiEnterpriseConfig; +import android.net.wifi.WifiEnterpriseConfig.Eap; +import android.net.wifi.WifiEnterpriseConfig.Phase2; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.IBinder; +import android.security.keystore.KeyProperties; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.text.Editable; +import android.text.InputType; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.AccessibilityDelegate; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import androidx.annotation.VisibleForTesting; + +import com.android.net.module.util.NetUtils; +import com.android.net.module.util.ProxyUtils; +import com.android.settings.ProxySelector; +import com.android.settings.R; +import com.android.settings.network.SubscriptionUtil; +import com.android.settings.utils.AndroidKeystoreAliasLoader; +import com.android.settings.wifi.dpp.WifiDppUtils; +import com.android.settingslib.Utils; +import com.android.settingslib.utils.ThreadUtils; +import com.android.settingslib.wifi.AccessPoint; +import com.android.wifi.flags.Flags; +import com.android.wifitrackerlib.WifiEntry; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The class for allowing UIs like {@link WifiDialog} and {@link WifiConfigUiBase} to + * share the logic for controlling buttons, text fields, etc. + * + * Migrating from Wi-Fi SettingsLib to to WifiTrackerLib, this object will be removed in the near + * future, please develop in {@link WifiConfigController2}. + */ +public class WifiConfigController implements TextWatcher, + AdapterView.OnItemSelectedListener, OnCheckedChangeListener, + TextView.OnEditorActionListener, View.OnKeyListener { + private static final String TAG = "WifiConfigController"; + + private static final String SYSTEM_CA_STORE_PATH = "/system/etc/security/cacerts"; + + private final WifiConfigUiBase mConfigUi; + private final View mView; + private final AccessPoint mAccessPoint; + + /* This value comes from "wifi_ip_settings" resource array */ + private static final int DHCP = 0; + private static final int STATIC_IP = 1; + + /* Constants used for referring to the hidden state of a network. */ + public static final int HIDDEN_NETWORK = 1; + public static final int NOT_HIDDEN_NETWORK = 0; + + /* These values come from "wifi_proxy_settings" resource array */ + public static final int PROXY_NONE = 0; + public static final int PROXY_STATIC = 1; + public static final int PROXY_PAC = 2; + + /* These values come from "wifi_eap_method" resource array */ + public static final int WIFI_EAP_METHOD_PEAP = 0; + public static final int WIFI_EAP_METHOD_TLS = 1; + public static final int WIFI_EAP_METHOD_TTLS = 2; + public static final int WIFI_EAP_METHOD_PWD = 3; + public static final int WIFI_EAP_METHOD_SIM = 4; + public static final int WIFI_EAP_METHOD_AKA = 5; + public static final int WIFI_EAP_METHOD_AKA_PRIME = 6; + + /* These values come from "wifi_peap_phase2_entries" resource array */ + public static final int WIFI_PEAP_PHASE2_MSCHAPV2 = 0; + public static final int WIFI_PEAP_PHASE2_GTC = 1; + public static final int WIFI_PEAP_PHASE2_SIM = 2; + public static final int WIFI_PEAP_PHASE2_AKA = 3; + public static final int WIFI_PEAP_PHASE2_AKA_PRIME = 4; + + /* These values come from "wifi_ttls_phase2_entries" resource array */ + public static final int WIFI_TTLS_PHASE2_PAP = 0; + public static final int WIFI_TTLS_PHASE2_MSCHAP = 1; + public static final int WIFI_TTLS_PHASE2_MSCHAPV2 = 2; + public static final int WIFI_TTLS_PHASE2_GTC = 3; + + private static final String UNDESIRED_CERTIFICATE_MACRANDSECRET = "MacRandSecret"; + private static final String UNDESIRED_CERTIFICATE_MACRANDSAPSECRET = "MacRandSapSecret"; + @VisibleForTesting + static final String[] UNDESIRED_CERTIFICATES = { + UNDESIRED_CERTIFICATE_MACRANDSECRET, + UNDESIRED_CERTIFICATE_MACRANDSAPSECRET + }; + + // Should be the same index value as wifi_privacy_entries in arrays.xml + @VisibleForTesting static final int PRIVACY_SPINNER_INDEX_RANDOMIZED_MAC = 0; + @VisibleForTesting static final int PRIVACY_SPINNER_INDEX_DEVICE_MAC = 1; + + /* Phase2 methods supported by PEAP are limited */ + private ArrayAdapter mPhase2PeapAdapter; + /* Phase2 methods supported by TTLS are limited */ + private ArrayAdapter mPhase2TtlsAdapter; + + // e.g. AccessPoint.SECURITY_NONE + @VisibleForTesting + int mAccessPointSecurity; + private TextView mPasswordView; + private ImageButton mSsidScanButton; + + private String mUnspecifiedCertString; + private String mMultipleCertSetString; + private String mUseSystemCertsString; + private String mDoNotProvideEapUserCertString; + + private Spinner mSecuritySpinner; + @VisibleForTesting Spinner mEapMethodSpinner; + @VisibleForTesting Spinner mEapSimSpinner; // For EAP-SIM, EAP-AKA and EAP-AKA-PRIME. + private Spinner mEapCaCertSpinner; + private Spinner mEapOcspSpinner; + private TextView mEapDomainView; + private Spinner mPhase2Spinner; + // Associated with mPhase2Spinner, one of mPhase2TtlsAdapter or mPhase2PeapAdapter + private ArrayAdapter mPhase2Adapter; + private Spinner mEapUserCertSpinner; + private TextView mEapIdentityView; + private TextView mEapAnonymousView; + + private Spinner mIpSettingsSpinner; + private TextView mIpAddressView; + private TextView mGatewayView; + private TextView mNetworkPrefixLengthView; + private TextView mDns1View; + private TextView mDns2View; + + private Spinner mProxySettingsSpinner; + private Spinner mMeteredSettingsSpinner; + private Spinner mHiddenSettingsSpinner; + private Spinner mPrivacySettingsSpinner; + private TextView mHiddenWarningView; + private TextView mProxyHostView; + private TextView mProxyPortView; + private TextView mProxyExclusionListView; + private TextView mProxyPacView; + private CheckBox mSharedCheckBox; + + private IpAssignment mIpAssignment = IpAssignment.UNASSIGNED; + private ProxySettings mProxySettings = ProxySettings.UNASSIGNED; + private ProxyInfo mHttpProxy = null; + private StaticIpConfiguration mStaticIpConfiguration = null; + private boolean mRequestFocus = true; + + private String[] mLevels; + private int mMode; + private TextView mSsidView; + + private Context mContext; + + @VisibleForTesting + Integer mSecurityInPosition[]; + + private final WifiManager mWifiManager; + + private final List mActiveSubscriptionInfos = new ArrayList<>(); + + public WifiConfigController(WifiConfigUiBase parent, View view, AccessPoint accessPoint, + int mode) { + this (parent, view, accessPoint, mode, true /* requestFocus */); + } + + public WifiConfigController(WifiConfigUiBase parent, View view, AccessPoint accessPoint, + int mode, boolean requestFocus) { + mConfigUi = parent; + + mView = view; + mAccessPoint = accessPoint; + mContext = mConfigUi.getContext(); + mRequestFocus = requestFocus; + + // Init Wi-Fi manager + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + initWifiConfigController(accessPoint, mode); + } + + @VisibleForTesting + public WifiConfigController(WifiConfigUiBase parent, View view, AccessPoint accessPoint, + int mode, WifiManager wifiManager) { + mConfigUi = parent; + + mView = view; + mAccessPoint = accessPoint; + mContext = mConfigUi.getContext(); + mWifiManager = wifiManager; + initWifiConfigController(accessPoint, mode); + } + + private void initWifiConfigController(AccessPoint accessPoint, int mode) { + + mAccessPointSecurity = (accessPoint == null) ? AccessPoint.SECURITY_NONE : + accessPoint.getSecurity(); + mMode = mode; + + final Resources res = mContext.getResources(); + + mLevels = res.getStringArray(R.array.wifi_signal); + if (Utils.isWifiOnly(mContext) || !mContext.getResources().getBoolean( + com.android.internal.R.bool.config_eap_sim_based_auth_supported)) { + mPhase2PeapAdapter = getSpinnerAdapter(R.array.wifi_peap_phase2_entries); + } else { + mPhase2PeapAdapter = getSpinnerAdapterWithEapMethodsTts( + R.array.wifi_peap_phase2_entries_with_sim_auth); + } + + mPhase2TtlsAdapter = getSpinnerAdapter(R.array.wifi_ttls_phase2_entries); + + mUnspecifiedCertString = mContext.getString(R.string.wifi_unspecified); + mMultipleCertSetString = mContext.getString(R.string.wifi_multiple_cert_added); + mUseSystemCertsString = mContext.getString(R.string.wifi_use_system_certs); + mDoNotProvideEapUserCertString = + mContext.getString(R.string.wifi_do_not_provide_eap_user_cert); + + if (Flags.androidVWifiApi() && mAccessPointSecurity == WifiEntry.SECURITY_WEP) { + LinearLayout wepWarningLayout = + (LinearLayout) mView.findViewById(R.id.wep_warning_layout); + wepWarningLayout.setVisibility(View.VISIBLE); + } + + mSsidScanButton = (ImageButton) mView.findViewById(R.id.ssid_scanner_button); + mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings); + mIpSettingsSpinner.setOnItemSelectedListener(this); + mProxySettingsSpinner = (Spinner) mView.findViewById(R.id.proxy_settings); + mProxySettingsSpinner.setOnItemSelectedListener(this); + mSharedCheckBox = (CheckBox) mView.findViewById(R.id.shared); + mMeteredSettingsSpinner = mView.findViewById(R.id.metered_settings); + mHiddenSettingsSpinner = mView.findViewById(R.id.hidden_settings); + mPrivacySettingsSpinner = mView.findViewById(R.id.privacy_settings); + if (mWifiManager.isConnectedMacRandomizationSupported()) { + View privacySettingsLayout = mView.findViewById(R.id.privacy_settings_fields); + privacySettingsLayout.setVisibility(View.VISIBLE); + } + mHiddenSettingsSpinner.setOnItemSelectedListener(this); + mHiddenWarningView = mView.findViewById(R.id.hidden_settings_warning); + mHiddenWarningView.setVisibility( + mHiddenSettingsSpinner.getSelectedItemPosition() == NOT_HIDDEN_NETWORK + ? View.GONE + : View.VISIBLE); + mSecurityInPosition = new Integer[AccessPoint.SECURITY_MAX_VAL]; + + if (mAccessPoint == null) { // new network + configureSecuritySpinner(); + mConfigUi.setSubmitButton(res.getString(R.string.wifi_save)); + } else { + mConfigUi.setTitle(mAccessPoint.getTitle()); + + ViewGroup group = (ViewGroup) mView.findViewById(R.id.info); + + boolean showAdvancedFields = false; + if (mAccessPoint.isSaved()) { + WifiConfiguration config = mAccessPoint.getConfig(); + mMeteredSettingsSpinner.setSelection(config.meteredOverride); + mHiddenSettingsSpinner.setSelection(config.hiddenSSID + ? HIDDEN_NETWORK + : NOT_HIDDEN_NETWORK); + + mPrivacySettingsSpinner.setSelection( + config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT + ? PRIVACY_SPINNER_INDEX_RANDOMIZED_MAC : PRIVACY_SPINNER_INDEX_DEVICE_MAC); + + if (config.getIpConfiguration().getIpAssignment() == IpAssignment.STATIC) { + mIpSettingsSpinner.setSelection(STATIC_IP); + showAdvancedFields = true; + // Display IP address. + StaticIpConfiguration staticConfig = config.getIpConfiguration() + .getStaticIpConfiguration(); + if (staticConfig != null && staticConfig.getIpAddress() != null) { + addRow(group, R.string.wifi_ip_address, + staticConfig.getIpAddress().getAddress().getHostAddress()); + } + } else { + mIpSettingsSpinner.setSelection(DHCP); + } + + mSharedCheckBox.setEnabled(config.shared); + if (!config.shared) { + showAdvancedFields = true; + } + + ProxySettings proxySettings = config.getIpConfiguration().getProxySettings(); + if (proxySettings == ProxySettings.STATIC) { + mProxySettingsSpinner.setSelection(PROXY_STATIC); + showAdvancedFields = true; + } else if (proxySettings == ProxySettings.PAC) { + mProxySettingsSpinner.setSelection(PROXY_PAC); + showAdvancedFields = true; + } else { + mProxySettingsSpinner.setSelection(PROXY_NONE); + } + if (config != null && config.isPasspoint()) { + addRow(group, R.string.passpoint_label, + String.format(mContext.getString(R.string.passpoint_content), + config.providerFriendlyName)); + } + } + + if ((!mAccessPoint.isSaved() && !mAccessPoint.isActive() + && !mAccessPoint.isPasspointConfig()) + || mMode != WifiConfigUiBase.MODE_VIEW) { + showSecurityFields(/* refreshEapMethods */ true, /* refreshCertificates */ true); + showIpConfigFields(); + showProxyFields(); + final CheckBox advancedTogglebox = + (CheckBox) mView.findViewById(R.id.wifi_advanced_togglebox); + if (!showAdvancedFields) { + // Need to show Advanced Option button. + mView.findViewById(R.id.wifi_advanced_toggle).setVisibility(View.VISIBLE); + advancedTogglebox.setOnCheckedChangeListener(this); + advancedTogglebox.setChecked(showAdvancedFields); + setAdvancedOptionAccessibilityString(); + } + mView.findViewById(R.id.wifi_advanced_fields) + .setVisibility(showAdvancedFields ? View.VISIBLE : View.GONE); + } + + if (mMode == WifiConfigUiBase.MODE_MODIFY) { + mConfigUi.setSubmitButton(res.getString(R.string.wifi_save)); + } else if (mMode == WifiConfigUiBase.MODE_CONNECT) { + mConfigUi.setSubmitButton(res.getString(R.string.wifi_connect)); + } else { + final DetailedState state = mAccessPoint.getDetailedState(); + final String signalLevel = getSignalString(); + + if ((state == null || state == DetailedState.DISCONNECTED) && signalLevel != null) { + mConfigUi.setSubmitButton(res.getString(R.string.wifi_connect)); + } else { + if (state != null) { + boolean isEphemeral = mAccessPoint.isEphemeral(); + WifiConfiguration config = mAccessPoint.getConfig(); + String providerFriendlyName = null; + if (config != null && config.isPasspoint()) { + providerFriendlyName = config.providerFriendlyName; + } + String suggestionOrSpecifierPackageName = null; + if (config != null + && (config.fromWifiNetworkSpecifier + || config.fromWifiNetworkSuggestion)) { + suggestionOrSpecifierPackageName = config.creatorName; + } + String summary = AccessPoint.getSummary( + mConfigUi.getContext(), /* ssid */ null, state, isEphemeral, + suggestionOrSpecifierPackageName); + addRow(group, R.string.wifi_status, summary); + } + + if (signalLevel != null) { + addRow(group, R.string.wifi_signal, signalLevel); + } + + WifiInfo info = mAccessPoint.getInfo(); + if (info != null && info.getTxLinkSpeedMbps() != WifiInfo.LINK_SPEED_UNKNOWN) { + addRow(group, R.string.tx_wifi_speed, String.format( + res.getString(R.string.tx_link_speed), info.getTxLinkSpeedMbps())); + } + + if (info != null && info.getRxLinkSpeedMbps() != WifiInfo.LINK_SPEED_UNKNOWN) { + addRow(group, R.string.rx_wifi_speed, String.format( + res.getString(R.string.rx_link_speed), info.getRxLinkSpeedMbps())); + } + + if (info != null && info.getFrequency() != -1) { + final int frequency = info.getFrequency(); + String band = null; + + if (frequency >= AccessPoint.LOWER_FREQ_24GHZ + && frequency < AccessPoint.HIGHER_FREQ_24GHZ) { + band = res.getString(R.string.wifi_band_24ghz); + } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ + && frequency < AccessPoint.HIGHER_FREQ_5GHZ) { + band = res.getString(R.string.wifi_band_5ghz); + } else { + Log.e(TAG, "Unexpected frequency " + frequency); + } + if (band != null) { + addRow(group, R.string.wifi_frequency, band); + } + } + + addRow(group, R.string.wifi_security, mAccessPoint.getSecurityString(false)); + mView.findViewById(R.id.ip_fields).setVisibility(View.GONE); + } + if (mAccessPoint.isSaved() || mAccessPoint.isActive() + || mAccessPoint.isPasspointConfig()) { + mConfigUi.setForgetButton(res.getString(R.string.wifi_forget)); + } + } + + mSsidScanButton.setVisibility(View.GONE); + } + mSharedCheckBox.setVisibility(View.GONE); + + mConfigUi.setCancelButton(res.getString(R.string.wifi_cancel)); + if (mConfigUi.getSubmitButton() != null) { + enableSubmitIfAppropriate(); + } + + // After done view show and hide, request focus from parameter. + if (mRequestFocus) { + mView.findViewById(R.id.l_wifidialog).requestFocus(); + } + } + + private void addRow(ViewGroup group, int name, String value) { + View row = mConfigUi.getLayoutInflater().inflate(R.layout.wifi_dialog_row, group, false); + ((TextView) row.findViewById(R.id.name)).setText(name); + ((TextView) row.findViewById(R.id.value)).setText(value); + group.addView(row); + } + + @VisibleForTesting + String getSignalString() { + if (!mAccessPoint.isReachable()) { + return null; + } + final int level = mAccessPoint.getLevel(); + + return (level > -1 && level < mLevels.length) ? mLevels[level] : null; + } + + void hideForgetButton() { + Button forget = mConfigUi.getForgetButton(); + if (forget == null) return; + + forget.setVisibility(View.GONE); + } + + void hideSubmitButton() { + Button submit = mConfigUi.getSubmitButton(); + if (submit == null) return; + + submit.setVisibility(View.GONE); + } + + /* show submit button if password, ip and proxy settings are valid */ + void enableSubmitIfAppropriate() { + Button submit = mConfigUi.getSubmitButton(); + if (submit == null) return; + + submit.setEnabled(isSubmittable()); + } + + boolean isValidPsk(String password) { + if (password.length() == 64 && password.matches("[0-9A-Fa-f]{64}")) { + return true; + } else if (password.length() >= 8 && password.length() <= 63) { + return true; + } + return false; + } + + boolean isValidSaePassword(String password) { + if (password.length() >= 1 && password.length() <= 63) { + return true; + } + return false; + } + + boolean isSubmittable() { + boolean enabled = false; + boolean passwordInvalid = false; + if (mPasswordView != null + && ((mAccessPointSecurity == AccessPoint.SECURITY_WEP + && mPasswordView.length() == 0) + || (mAccessPointSecurity == AccessPoint.SECURITY_PSK + && !isValidPsk(mPasswordView.getText().toString())) + || (mAccessPointSecurity == AccessPoint.SECURITY_SAE + && !isValidSaePassword(mPasswordView.getText().toString())))) { + passwordInvalid = true; + } + if ((mSsidView != null && mSsidView.length() == 0) + // If Accesspoint is not saved, apply passwordInvalid check + || ((mAccessPoint == null || !mAccessPoint.isSaved()) && passwordInvalid + // If AccessPoint is saved (modifying network) and password is changed, apply + // Invalid password check + || mAccessPoint != null && mAccessPoint.isSaved() && passwordInvalid + && mPasswordView.length() > 0)) { + enabled = false; + } else { + enabled = ipAndProxyFieldsAreValid(); + } + if ((mAccessPointSecurity == AccessPoint.SECURITY_EAP + || mAccessPointSecurity == AccessPoint.SECURITY_EAP_WPA3_ENTERPRISE + || mAccessPointSecurity == AccessPoint.SECURITY_EAP_SUITE_B) + && mEapCaCertSpinner != null + && mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { + String caCertSelection = (String) mEapCaCertSpinner.getSelectedItem(); + if (caCertSelection.equals(mUnspecifiedCertString)) { + // Disallow submit if the user has not selected a CA certificate for an EAP network + // configuration. + enabled = false; + } else if (mEapDomainView != null + && mView.findViewById(R.id.l_domain).getVisibility() != View.GONE + && TextUtils.isEmpty(mEapDomainView.getText().toString())) { + // Disallow submit if the user chooses to use a certificate for EAP server + // validation, but does not provide a domain. + enabled = false; + } + } + if ((mAccessPointSecurity == AccessPoint.SECURITY_EAP + || mAccessPointSecurity == AccessPoint.SECURITY_EAP_WPA3_ENTERPRISE + || mAccessPointSecurity == AccessPoint.SECURITY_EAP_SUITE_B) + && mEapUserCertSpinner != null + && mView.findViewById(R.id.l_user_cert).getVisibility() != View.GONE + && mEapUserCertSpinner.getSelectedItem().equals(mUnspecifiedCertString)) { + // Disallow submit if the user has not selected a user certificate for an EAP network + // configuration. + enabled = false; + } + return enabled; + } + + void showWarningMessagesIfAppropriate() { + mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.GONE); + mView.findViewById(R.id.no_domain_warning).setVisibility(View.GONE); + mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.GONE); + + if (mSsidView != null) { + final String ssid = mSsidView.getText().toString(); + if (WifiUtils.isSSIDTooLong(ssid)) { + mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.VISIBLE); + } + } + if (mEapCaCertSpinner != null + && mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { + if (mEapDomainView != null + && mView.findViewById(R.id.l_domain).getVisibility() != View.GONE + && TextUtils.isEmpty(mEapDomainView.getText().toString())) { + // Display warning if user chooses to use a certificate without restricting the + // server domain that these certificates can be used to validate. + mView.findViewById(R.id.no_domain_warning).setVisibility(View.VISIBLE); + } + } + + if (mAccessPointSecurity == AccessPoint.SECURITY_EAP_SUITE_B && + mEapMethodSpinner.getSelectedItemPosition() == WIFI_EAP_METHOD_TLS) { + String userCertSelection = (String) mEapUserCertSpinner.getSelectedItem(); + if (userCertSelection.equals(mUnspecifiedCertString)) { + mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.VISIBLE); + } + } + } + + public WifiConfiguration getConfig() { + if (mMode == WifiConfigUiBase.MODE_VIEW) { + return null; + } + + WifiConfiguration config = new WifiConfiguration(); + + if (mAccessPoint == null) { + config.SSID = AccessPoint.convertToQuotedString( + mSsidView.getText().toString()); + // If the user adds a network manually, assume that it is hidden. + config.hiddenSSID = mHiddenSettingsSpinner.getSelectedItemPosition() == HIDDEN_NETWORK; + } else if (!mAccessPoint.isSaved()) { + config.SSID = AccessPoint.convertToQuotedString( + mAccessPoint.getSsidStr()); + } else { + config.networkId = mAccessPoint.getConfig().networkId; + config.hiddenSSID = mAccessPoint.getConfig().hiddenSSID; + } + + config.shared = mSharedCheckBox.isChecked(); + + switch (mAccessPointSecurity) { + case AccessPoint.SECURITY_NONE: + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); + break; + + case AccessPoint.SECURITY_WEP: + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP); + if (mPasswordView.length() != 0) { + int length = mPasswordView.length(); + String password = mPasswordView.getText().toString(); + // WEP-40, WEP-104, and 256-bit WEP (WEP-232?) + if ((length == 10 || length == 26 || length == 58) + && password.matches("[0-9A-Fa-f]*")) { + config.wepKeys[0] = password; + } else { + config.wepKeys[0] = '"' + password + '"'; + } + } + break; + + case AccessPoint.SECURITY_PSK: + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); + if (mPasswordView.length() != 0) { + String password = mPasswordView.getText().toString(); + if (password.matches("[0-9A-Fa-f]{64}")) { + config.preSharedKey = password; + } else { + config.preSharedKey = '"' + password + '"'; + } + } + break; + + case AccessPoint.SECURITY_EAP: + case AccessPoint.SECURITY_EAP_WPA3_ENTERPRISE: + case AccessPoint.SECURITY_EAP_SUITE_B: + if (mEapMethodSpinner == null || mPhase2Spinner == null) { + break; + } + if (mAccessPointSecurity == AccessPoint.SECURITY_EAP_SUITE_B) { + // allowedSuiteBCiphers will be set according to certificate type + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B); + } else if (mAccessPointSecurity == AccessPoint.SECURITY_EAP_WPA3_ENTERPRISE) { + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE); + } else { + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); + } + config.enterpriseConfig = new WifiEnterpriseConfig(); + int eapMethod = mEapMethodSpinner.getSelectedItemPosition(); + int phase2Method = mPhase2Spinner.getSelectedItemPosition(); + config.enterpriseConfig.setEapMethod(eapMethod); + switch (eapMethod) { + case Eap.PEAP: + // PEAP supports limited phase2 values + // Map the index from the mPhase2PeapAdapter to the one used + // by the API which has the full list of PEAP methods. + switch(phase2Method) { + case WIFI_PEAP_PHASE2_MSCHAPV2: + config.enterpriseConfig.setPhase2Method(Phase2.MSCHAPV2); + break; + case WIFI_PEAP_PHASE2_GTC: + config.enterpriseConfig.setPhase2Method(Phase2.GTC); + break; + case WIFI_PEAP_PHASE2_SIM: + config.enterpriseConfig.setPhase2Method(Phase2.SIM); + break; + case WIFI_PEAP_PHASE2_AKA: + config.enterpriseConfig.setPhase2Method(Phase2.AKA); + break; + case WIFI_PEAP_PHASE2_AKA_PRIME: + config.enterpriseConfig.setPhase2Method(Phase2.AKA_PRIME); + break; + default: + Log.e(TAG, "Unknown phase2 method" + phase2Method); + break; + } + break; + case Eap.TTLS: + // The default index from mPhase2TtlsAdapter maps to the API + switch(phase2Method) { + case WIFI_TTLS_PHASE2_PAP: + config.enterpriseConfig.setPhase2Method(Phase2.PAP); + break; + case WIFI_TTLS_PHASE2_MSCHAP: + config.enterpriseConfig.setPhase2Method(Phase2.MSCHAP); + break; + case WIFI_TTLS_PHASE2_MSCHAPV2: + config.enterpriseConfig.setPhase2Method(Phase2.MSCHAPV2); + break; + case WIFI_TTLS_PHASE2_GTC: + config.enterpriseConfig.setPhase2Method(Phase2.GTC); + break; + default: + Log.e(TAG, "Unknown phase2 method" + phase2Method); + break; + } + break; + default: + break; + } + + String caCert = (String) mEapCaCertSpinner.getSelectedItem(); + config.enterpriseConfig.setCaCertificateAliases(null); + config.enterpriseConfig.setCaPath(null); + config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString()); + if (caCert.equals(mUnspecifiedCertString)) { + // ca_cert already set to null, so do nothing. + } else if (caCert.equals(mUseSystemCertsString)) { + config.enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH); + } else if (caCert.equals(mMultipleCertSetString)) { + if (mAccessPoint != null) { + if (!mAccessPoint.isSaved()) { + Log.e(TAG, "Multiple certs can only be set " + + "when editing saved network"); + } + config.enterpriseConfig.setCaCertificateAliases( + mAccessPoint + .getConfig() + .enterpriseConfig + .getCaCertificateAliases()); + } + } else { + config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert}); + } + + // ca_cert or ca_path should not both be non-null, since we only intend to let + // the use either their own certificate, or the system certificates, not both. + // The variable that is not used must explicitly be set to null, so that a + // previously-set value on a saved configuration will be erased on an update. + if (config.enterpriseConfig.getCaCertificateAliases() != null + && config.enterpriseConfig.getCaPath() != null) { + Log.e(TAG, "ca_cert (" + + Arrays.toString(config.enterpriseConfig.getCaCertificateAliases()) + + ") and ca_path (" + + config.enterpriseConfig.getCaPath() + + ") should not both be non-null"); + } + + // Only set OCSP option if there is a valid CA certificate. + if (caCert.equals(mUnspecifiedCertString)) { + config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE); + } else { + config.enterpriseConfig.setOcsp(mEapOcspSpinner.getSelectedItemPosition()); + } + + String clientCert = (String) mEapUserCertSpinner.getSelectedItem(); + if (clientCert.equals(mUnspecifiedCertString) + || clientCert.equals(mDoNotProvideEapUserCertString)) { + // Note: |clientCert| should not be able to take the value |unspecifiedCert|, + // since we prevent such configurations from being saved. + clientCert = ""; + } + config.enterpriseConfig.setClientCertificateAlias(clientCert); + if (eapMethod == Eap.SIM || eapMethod == Eap.AKA || eapMethod == Eap.AKA_PRIME) { + config.enterpriseConfig.setIdentity(""); + config.enterpriseConfig.setAnonymousIdentity(""); + } else if (eapMethod == Eap.PWD) { + config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString()); + config.enterpriseConfig.setAnonymousIdentity(""); + } else { + config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString()); + config.enterpriseConfig.setAnonymousIdentity( + mEapAnonymousView.getText().toString()); + } + + if (mPasswordView.isShown()) { + // For security reasons, a previous password is not displayed to user. + // Update only if it has been changed. + if (mPasswordView.length() > 0) { + config.enterpriseConfig.setPassword(mPasswordView.getText().toString()); + } + } else { + // clear password + config.enterpriseConfig.setPassword(mPasswordView.getText().toString()); + } + break; + case AccessPoint.SECURITY_SAE: + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); + if (mPasswordView.length() != 0) { + String password = mPasswordView.getText().toString(); + config.preSharedKey = '"' + password + '"'; + } + break; + + case AccessPoint.SECURITY_OWE: + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); + break; + + default: + return null; + } + + if (config.enterpriseConfig.isAuthenticationSimBased() + && mActiveSubscriptionInfos.size() > 0) { + config.carrierId = mActiveSubscriptionInfos + .get(mEapSimSpinner.getSelectedItemPosition()).getCarrierId(); + } + + final IpConfiguration ipConfig = new IpConfiguration(); + ipConfig.setIpAssignment(mIpAssignment); + ipConfig.setProxySettings(mProxySettings); + ipConfig.setStaticIpConfiguration(mStaticIpConfiguration); + ipConfig.setHttpProxy(mHttpProxy); + config.setIpConfiguration(ipConfig); + if (mMeteredSettingsSpinner != null) { + config.meteredOverride = mMeteredSettingsSpinner.getSelectedItemPosition(); + } + + if (mPrivacySettingsSpinner != null) { + config.macRandomizationSetting = mPrivacySettingsSpinner.getSelectedItemPosition() + == PRIVACY_SPINNER_INDEX_RANDOMIZED_MAC + ? WifiConfiguration.RANDOMIZATION_PERSISTENT + : WifiConfiguration.RANDOMIZATION_NONE; + } + + return config; + } + + private boolean ipAndProxyFieldsAreValid() { + mIpAssignment = + (mIpSettingsSpinner != null + && mIpSettingsSpinner.getSelectedItemPosition() == STATIC_IP) + ? IpAssignment.STATIC + : IpAssignment.DHCP; + + if (mIpAssignment == IpAssignment.STATIC) { + mStaticIpConfiguration = new StaticIpConfiguration(); + int result = validateIpConfigFields(mStaticIpConfiguration); + if (result != 0) { + return false; + } + } + + final int selectedPosition = mProxySettingsSpinner.getSelectedItemPosition(); + mProxySettings = ProxySettings.NONE; + mHttpProxy = null; + if (selectedPosition == PROXY_STATIC && mProxyHostView != null) { + mProxySettings = ProxySettings.STATIC; + String host = mProxyHostView.getText().toString(); + String portStr = mProxyPortView.getText().toString(); + String exclusionList = mProxyExclusionListView.getText().toString(); + int port = 0; + int result = 0; + try { + port = Integer.parseInt(portStr); + result = ProxySelector.validate(host, portStr, exclusionList); + } catch (NumberFormatException e) { + result = R.string.proxy_error_invalid_port; + } + if (result == 0) { + mHttpProxy = ProxyInfo.buildDirectProxy( + host, port, Arrays.asList(exclusionList.split(","))); + } else { + return false; + } + } else if (selectedPosition == PROXY_PAC && mProxyPacView != null) { + mProxySettings = ProxySettings.PAC; + CharSequence uriSequence = mProxyPacView.getText(); + if (TextUtils.isEmpty(uriSequence)) { + return false; + } + Uri uri = Uri.parse(uriSequence.toString()); + if (uri == null) { + return false; + } + mHttpProxy = ProxyInfo.buildPacProxy(uri); + } + return true; + } + + private Inet4Address getIPv4Address(String text) { + try { + return (Inet4Address) InetAddresses.parseNumericAddress(text); + } catch (IllegalArgumentException | ClassCastException e) { + return null; + } + } + + private int validateIpConfigFields(StaticIpConfiguration staticIpConfiguration) { + if (mIpAddressView == null) return 0; + + String ipAddr = mIpAddressView.getText().toString(); + if (TextUtils.isEmpty(ipAddr)) return R.string.wifi_ip_settings_invalid_ip_address; + + Inet4Address inetAddr = getIPv4Address(ipAddr); + if (inetAddr == null || inetAddr.equals(Inet4Address.ANY)) { + return R.string.wifi_ip_settings_invalid_ip_address; + } + // Copy all fields into the builder first and set desired value later with builder. + final StaticIpConfiguration.Builder staticIPBuilder = new StaticIpConfiguration.Builder() + .setDnsServers(staticIpConfiguration.getDnsServers()) + .setDomains(staticIpConfiguration.getDomains()) + .setGateway(staticIpConfiguration.getGateway()) + .setIpAddress(staticIpConfiguration.getIpAddress()); + try { + int networkPrefixLength = -1; + try { + networkPrefixLength = Integer.parseInt( + mNetworkPrefixLengthView.getText().toString()); + if (networkPrefixLength < 0 || networkPrefixLength > 32) { + return R.string.wifi_ip_settings_invalid_network_prefix_length; + } + staticIPBuilder.setIpAddress(new LinkAddress(inetAddr, networkPrefixLength)); + } catch (NumberFormatException e) { + // Set the hint as default after user types in ip address + mNetworkPrefixLengthView.setText(mConfigUi.getContext().getString( + com.android.settingslib.R.string.wifi_network_prefix_length_hint)); + } catch (IllegalArgumentException e) { + return R.string.wifi_ip_settings_invalid_ip_address; + } + + String gateway = mGatewayView.getText().toString(); + if (TextUtils.isEmpty(gateway)) { + try { + //Extract a default gateway from IP address + InetAddress netPart = NetUtils.getNetworkPart(inetAddr, networkPrefixLength); + byte[] addr = netPart.getAddress(); + addr[addr.length - 1] = 1; + mGatewayView.setText(InetAddress.getByAddress(addr).getHostAddress()); + } catch (RuntimeException ee) { + } catch (java.net.UnknownHostException u) { + } + } else { + InetAddress gatewayAddr = getIPv4Address(gateway); + if (gatewayAddr == null) { + return R.string.wifi_ip_settings_invalid_gateway; + } + if (gatewayAddr.isMulticastAddress()) { + return R.string.wifi_ip_settings_invalid_gateway; + } + staticIPBuilder.setGateway(gatewayAddr); + } + + String dns = mDns1View.getText().toString(); + InetAddress dnsAddr = null; + final ArrayList dnsServers = new ArrayList<>(); + + if (TextUtils.isEmpty(dns)) { + //If everything else is valid, provide hint as a default option + mDns1View.setText(mConfigUi.getContext().getString( + com.android.settingslib.R.string.wifi_dns1_hint)); + } else { + dnsAddr = getIPv4Address(dns); + if (dnsAddr == null) { + return R.string.wifi_ip_settings_invalid_dns; + } + dnsServers.add(dnsAddr); + } + + if (mDns2View.length() > 0) { + dns = mDns2View.getText().toString(); + dnsAddr = getIPv4Address(dns); + if (dnsAddr == null) { + return R.string.wifi_ip_settings_invalid_dns; + } + dnsServers.add(dnsAddr); + } + staticIPBuilder.setDnsServers(dnsServers); + return 0; + } finally { + // Caller of this method may rely on staticIpConfiguration, so build the final result + // at the end of the method. + staticIpConfiguration = staticIPBuilder.build(); + } + } + + private void showSecurityFields(boolean refreshEapMethods, boolean refreshCertificates) { + if (mAccessPointSecurity == AccessPoint.SECURITY_NONE || + mAccessPointSecurity == AccessPoint.SECURITY_OWE) { + mView.findViewById(R.id.security_fields).setVisibility(View.GONE); + return; + } + mView.findViewById(R.id.security_fields).setVisibility(View.VISIBLE); + + if (mPasswordView == null) { + mPasswordView = (TextView) mView.findViewById(R.id.password); + mPasswordView.addTextChangedListener(this); + mPasswordView.setOnEditorActionListener(this); + mPasswordView.setOnKeyListener(this); + ((CheckBox) mView.findViewById(R.id.show_password)) + .setOnCheckedChangeListener(this); + + if (mAccessPoint != null && mAccessPoint.isSaved()) { + mPasswordView.setHint(R.string.wifi_unchanged); + } + } + + if (mAccessPointSecurity != AccessPoint.SECURITY_EAP && + mAccessPointSecurity != AccessPoint.SECURITY_EAP_SUITE_B) { + mView.findViewById(R.id.eap).setVisibility(View.GONE); + return; + } + mView.findViewById(R.id.eap).setVisibility(View.VISIBLE); + + // TODO (b/140541213): Maybe we can remove initiateEnterpriseNetworkUi by moving code block + boolean initiateEnterpriseNetworkUi = false; + if (mEapMethodSpinner == null) { + initiateEnterpriseNetworkUi = true; + mEapMethodSpinner = (Spinner) mView.findViewById(R.id.method); + mEapMethodSpinner.setOnItemSelectedListener(this); + mEapSimSpinner = (Spinner) mView.findViewById(R.id.sim); + mPhase2Spinner = (Spinner) mView.findViewById(R.id.phase2); + mPhase2Spinner.setOnItemSelectedListener(this); + mEapCaCertSpinner = (Spinner) mView.findViewById(R.id.ca_cert); + mEapCaCertSpinner.setOnItemSelectedListener(this); + mEapOcspSpinner = (Spinner) mView.findViewById(R.id.ocsp); + mEapDomainView = (TextView) mView.findViewById(R.id.domain); + mEapDomainView.addTextChangedListener(this); + mEapUserCertSpinner = (Spinner) mView.findViewById(R.id.user_cert); + mEapUserCertSpinner.setOnItemSelectedListener(this); + mEapIdentityView = (TextView) mView.findViewById(R.id.identity); + mEapAnonymousView = (TextView) mView.findViewById(R.id.anonymous); + + setAccessibilityDelegateForSecuritySpinners(); + } + + if (refreshEapMethods) { + ArrayAdapter eapMethodSpinnerAdapter; + if (mAccessPointSecurity == AccessPoint.SECURITY_EAP_SUITE_B) { + eapMethodSpinnerAdapter = getSpinnerAdapter(R.array.wifi_eap_method); + mEapMethodSpinner.setAdapter(eapMethodSpinnerAdapter); + // WAP3-Enterprise 192-bit only allows EAP method TLS + mEapMethodSpinner.setSelection(Eap.TLS); + mEapMethodSpinner.setEnabled(false); + } else if (Utils.isWifiOnly(mContext) || !mContext.getResources().getBoolean( + com.android.internal.R.bool.config_eap_sim_based_auth_supported)) { + eapMethodSpinnerAdapter = getSpinnerAdapter(R.array.eap_method_without_sim_auth); + mEapMethodSpinner.setAdapter(eapMethodSpinnerAdapter); + mEapMethodSpinner.setEnabled(true); + } else { + eapMethodSpinnerAdapter = getSpinnerAdapterWithEapMethodsTts(R.array.wifi_eap_method); + mEapMethodSpinner.setAdapter(eapMethodSpinnerAdapter); + mEapMethodSpinner.setEnabled(true); + } + } + + if (refreshCertificates) { + loadSims(); + + final AndroidKeystoreAliasLoader androidKeystoreAliasLoader = + getAndroidKeystoreAliasLoader(); + loadCertificates( + mEapCaCertSpinner, + androidKeystoreAliasLoader.getCaCertAliases(), + null /* noCertificateString */, + false /* showMultipleCerts */, + true /* showUsePreinstalledCertOption */); + loadCertificates( + mEapUserCertSpinner, + androidKeystoreAliasLoader.getKeyCertAliases(), + mDoNotProvideEapUserCertString, + false /* showMultipleCerts */, + false /* showUsePreinstalledCertOption */); + // To avoid the user connects to a non-secure network unexpectedly, + // request using system trusted certificates by default + // unless the user explicitly chooses "Do not validate" or other + // CA certificates. + setSelection(mEapCaCertSpinner, mUseSystemCertsString); + } + + // Modifying an existing network + if (initiateEnterpriseNetworkUi && mAccessPoint != null && mAccessPoint.isSaved()) { + final WifiConfiguration wifiConfig = mAccessPoint.getConfig(); + final WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig; + final int eapMethod = enterpriseConfig.getEapMethod(); + final int phase2Method = enterpriseConfig.getPhase2Method(); + mEapMethodSpinner.setSelection(eapMethod); + showEapFieldsByMethod(eapMethod); + switch (eapMethod) { + case Eap.PEAP: + switch (phase2Method) { + case Phase2.MSCHAPV2: + mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_MSCHAPV2); + break; + case Phase2.GTC: + mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_GTC); + break; + case Phase2.SIM: + mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_SIM); + break; + case Phase2.AKA: + mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_AKA); + break; + case Phase2.AKA_PRIME: + mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_AKA_PRIME); + break; + default: + Log.e(TAG, "Invalid phase 2 method " + phase2Method); + break; + } + break; + case Eap.TTLS: + switch (phase2Method) { + case Phase2.PAP: + mPhase2Spinner.setSelection(WIFI_TTLS_PHASE2_PAP); + break; + case Phase2.MSCHAP: + mPhase2Spinner.setSelection(WIFI_TTLS_PHASE2_MSCHAP); + break; + case Phase2.MSCHAPV2: + mPhase2Spinner.setSelection(WIFI_TTLS_PHASE2_MSCHAPV2); + break; + case Phase2.GTC: + mPhase2Spinner.setSelection(WIFI_TTLS_PHASE2_GTC); + break; + default: + Log.e(TAG, "Invalid phase 2 method " + phase2Method); + break; + } + break; + default: + break; + } + + if (enterpriseConfig.isAuthenticationSimBased()) { + for (int i = 0; i < mActiveSubscriptionInfos.size(); i++) { + if (wifiConfig.carrierId == mActiveSubscriptionInfos.get(i).getCarrierId()) { + mEapSimSpinner.setSelection(i); + break; + } + } + } + + if (!TextUtils.isEmpty(enterpriseConfig.getCaPath())) { + setSelection(mEapCaCertSpinner, mUseSystemCertsString); + } else { + String[] caCerts = enterpriseConfig.getCaCertificateAliases(); + if (caCerts == null) { + setSelection(mEapCaCertSpinner, mUnspecifiedCertString); + } else if (caCerts.length == 1) { + setSelection(mEapCaCertSpinner, caCerts[0]); + } else { + final AndroidKeystoreAliasLoader androidKeystoreAliasLoader = + getAndroidKeystoreAliasLoader(); + + // Reload the cert spinner with an extra "multiple certificates added" item. + loadCertificates( + mEapCaCertSpinner, + androidKeystoreAliasLoader.getCaCertAliases(), + null /* noCertificateString */, + true /* showMultipleCerts */, + true /* showUsePreinstalledCertOption */); + setSelection(mEapCaCertSpinner, mMultipleCertSetString); + } + } + mEapOcspSpinner.setSelection(enterpriseConfig.getOcsp()); + mEapDomainView.setText(enterpriseConfig.getDomainSuffixMatch()); + String userCert = enterpriseConfig.getClientCertificateAlias(); + if (TextUtils.isEmpty(userCert)) { + setSelection(mEapUserCertSpinner, mDoNotProvideEapUserCertString); + } else { + setSelection(mEapUserCertSpinner, userCert); + } + mEapIdentityView.setText(enterpriseConfig.getIdentity()); + mEapAnonymousView.setText(enterpriseConfig.getAnonymousIdentity()); + } else { + showEapFieldsByMethod(mEapMethodSpinner.getSelectedItemPosition()); + } + } + + private void setAccessibilityDelegateForSecuritySpinners() { + final AccessibilityDelegate selectedEventBlocker = new AccessibilityDelegate() { + @Override + public void sendAccessibilityEvent(View host, int eventType) { + if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) { + // Ignore TYPE_VIEW_SELECTED or there will be multiple Spinner selected + // information for WifiController#showSecurityFields. + return; + } + super.sendAccessibilityEvent(host, eventType); + } + }; + + mEapMethodSpinner.setAccessibilityDelegate(selectedEventBlocker); + mPhase2Spinner.setAccessibilityDelegate(selectedEventBlocker); + mEapCaCertSpinner.setAccessibilityDelegate(selectedEventBlocker); + mEapOcspSpinner.setAccessibilityDelegate(selectedEventBlocker); + mEapUserCertSpinner.setAccessibilityDelegate(selectedEventBlocker); + } + + /** + * EAP-PWD valid fields include + * identity + * password + * EAP-PEAP valid fields include + * phase2: MSCHAPV2, GTC, SIM, AKA, AKA' + * ca_cert + * identity + * anonymous_identity + * password (not required for SIM, AKA, AKA') + * EAP-TLS valid fields include + * user_cert + * ca_cert + * domain + * identity + * EAP-TTLS valid fields include + * phase2: PAP, MSCHAP, MSCHAPV2, GTC + * ca_cert + * identity + * anonymous_identity + * password + */ + private void showEapFieldsByMethod(int eapMethod) { + // Common defaults + mView.findViewById(R.id.l_method).setVisibility(View.VISIBLE); + mView.findViewById(R.id.l_identity).setVisibility(View.VISIBLE); + mView.findViewById(R.id.l_domain).setVisibility(View.VISIBLE); + + // Defaults for most of the EAP methods and over-riden by + // by certain EAP methods + mView.findViewById(R.id.l_ca_cert).setVisibility(View.VISIBLE); + mView.findViewById(R.id.l_ocsp).setVisibility(View.VISIBLE); + mView.findViewById(R.id.password_layout).setVisibility(View.VISIBLE); + mView.findViewById(R.id.show_password_layout).setVisibility(View.VISIBLE); + mView.findViewById(R.id.l_sim).setVisibility(View.VISIBLE); + + Context context = mConfigUi.getContext(); + switch (eapMethod) { + case WIFI_EAP_METHOD_PWD: + setPhase2Invisible(); + setCaCertInvisible(); + setOcspInvisible(); + setDomainInvisible(); + setAnonymousIdentInvisible(); + setUserCertInvisible(); + mView.findViewById(R.id.l_sim).setVisibility(View.GONE); + break; + case WIFI_EAP_METHOD_TLS: + mView.findViewById(R.id.l_user_cert).setVisibility(View.VISIBLE); + setPhase2Invisible(); + setAnonymousIdentInvisible(); + setPasswordInvisible(); + mView.findViewById(R.id.l_sim).setVisibility(View.GONE); + break; + case WIFI_EAP_METHOD_PEAP: + // Reset adapter if needed + if (mPhase2Adapter != mPhase2PeapAdapter) { + mPhase2Adapter = mPhase2PeapAdapter; + mPhase2Spinner.setAdapter(mPhase2Adapter); + } + mView.findViewById(R.id.l_phase2).setVisibility(View.VISIBLE); + mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); + showPeapFields(); + setUserCertInvisible(); + break; + case WIFI_EAP_METHOD_TTLS: + // Reset adapter if needed + if (mPhase2Adapter != mPhase2TtlsAdapter) { + mPhase2Adapter = mPhase2TtlsAdapter; + mPhase2Spinner.setAdapter(mPhase2Adapter); + } + mView.findViewById(R.id.l_phase2).setVisibility(View.VISIBLE); + mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); + setUserCertInvisible(); + mView.findViewById(R.id.l_sim).setVisibility(View.GONE); + break; + case WIFI_EAP_METHOD_SIM: + case WIFI_EAP_METHOD_AKA: + case WIFI_EAP_METHOD_AKA_PRIME: + setPhase2Invisible(); + setAnonymousIdentInvisible(); + setCaCertInvisible(); + setOcspInvisible(); + setDomainInvisible(); + setUserCertInvisible(); + setPasswordInvisible(); + setIdentityInvisible(); + break; + } + + if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { + String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem(); + if (eapCertSelection.equals(mUnspecifiedCertString)) { + // Domain suffix matching is not relevant if the user hasn't chosen a CA + // certificate yet, or chooses not to validate the EAP server. + setDomainInvisible(); + // Ocsp is an additional validation step for a server certifidate. + // This field is not relevant if the user hasn't chosen a valid + // CA certificate yet. + setOcspInvisible(); + } + } + } + + private void showPeapFields() { + int phase2Method = mPhase2Spinner.getSelectedItemPosition(); + if (phase2Method == WIFI_PEAP_PHASE2_SIM || phase2Method == WIFI_PEAP_PHASE2_AKA + || phase2Method == WIFI_PEAP_PHASE2_AKA_PRIME) { + mEapIdentityView.setText(""); + mView.findViewById(R.id.l_identity).setVisibility(View.GONE); + setPasswordInvisible(); + mView.findViewById(R.id.l_sim).setVisibility(View.VISIBLE); + } else { + mView.findViewById(R.id.l_identity).setVisibility(View.VISIBLE); + mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); + mView.findViewById(R.id.password_layout).setVisibility(View.VISIBLE); + mView.findViewById(R.id.show_password_layout).setVisibility(View.VISIBLE); + mView.findViewById(R.id.l_sim).setVisibility(View.GONE); + } + } + + private void setIdentityInvisible() { + mView.findViewById(R.id.l_identity).setVisibility(View.GONE); + } + + private void setPhase2Invisible() { + mView.findViewById(R.id.l_phase2).setVisibility(View.GONE); + } + + private void setCaCertInvisible() { + mView.findViewById(R.id.l_ca_cert).setVisibility(View.GONE); + setSelection(mEapCaCertSpinner, mUnspecifiedCertString); + } + + private void setOcspInvisible() { + mView.findViewById(R.id.l_ocsp).setVisibility(View.GONE); + mEapOcspSpinner.setSelection(WifiEnterpriseConfig.OCSP_NONE); + } + + private void setDomainInvisible() { + mView.findViewById(R.id.l_domain).setVisibility(View.GONE); + mEapDomainView.setText(""); + } + + private void setUserCertInvisible() { + mView.findViewById(R.id.l_user_cert).setVisibility(View.GONE); + setSelection(mEapUserCertSpinner, mUnspecifiedCertString); + } + + private void setAnonymousIdentInvisible() { + mView.findViewById(R.id.l_anonymous).setVisibility(View.GONE); + mEapAnonymousView.setText(""); + } + + private void setPasswordInvisible() { + mPasswordView.setText(""); + mView.findViewById(R.id.password_layout).setVisibility(View.GONE); + mView.findViewById(R.id.show_password_layout).setVisibility(View.GONE); + } + + private void setEapMethodInvisible() { + mView.findViewById(R.id.eap).setVisibility(View.GONE); + } + + private void showIpConfigFields() { + WifiConfiguration config = null; + + mView.findViewById(R.id.ip_fields).setVisibility(View.VISIBLE); + + if (mAccessPoint != null && mAccessPoint.isSaved()) { + config = mAccessPoint.getConfig(); + } + + if (mIpSettingsSpinner.getSelectedItemPosition() == STATIC_IP) { + mView.findViewById(R.id.staticip).setVisibility(View.VISIBLE); + if (mIpAddressView == null) { + mIpAddressView = (TextView) mView.findViewById(R.id.ipaddress); + mIpAddressView.addTextChangedListener(this); + mGatewayView = (TextView) mView.findViewById(R.id.gateway); + mGatewayView.addTextChangedListener(this); + mNetworkPrefixLengthView = (TextView) mView.findViewById( + R.id.network_prefix_length); + mNetworkPrefixLengthView.addTextChangedListener(this); + mDns1View = (TextView) mView.findViewById(R.id.dns1); + mDns1View.addTextChangedListener(this); + mDns2View = (TextView) mView.findViewById(R.id.dns2); + mDns2View.addTextChangedListener(this); + } + if (config != null) { + StaticIpConfiguration staticConfig = config.getIpConfiguration() + .getStaticIpConfiguration(); + if (staticConfig != null) { + if (staticConfig.getIpAddress() != null) { + mIpAddressView.setText( + staticConfig.getIpAddress().getAddress().getHostAddress()); + mNetworkPrefixLengthView.setText(Integer.toString( + staticConfig.getIpAddress().getPrefixLength())); + } + + if (staticConfig.getGateway() != null) { + mGatewayView.setText(staticConfig.getGateway().getHostAddress()); + } + + Iterator dnsIterator = staticConfig.getDnsServers().iterator(); + if (dnsIterator.hasNext()) { + mDns1View.setText(dnsIterator.next().getHostAddress()); + } + if (dnsIterator.hasNext()) { + mDns2View.setText(dnsIterator.next().getHostAddress()); + } + } + } + } else { + mView.findViewById(R.id.staticip).setVisibility(View.GONE); + } + } + + private void showProxyFields() { + WifiConfiguration config = null; + + mView.findViewById(R.id.proxy_settings_fields).setVisibility(View.VISIBLE); + + if (mAccessPoint != null && mAccessPoint.isSaved()) { + config = mAccessPoint.getConfig(); + } + + if (mProxySettingsSpinner.getSelectedItemPosition() == PROXY_STATIC) { + setVisibility(R.id.proxy_warning_limited_support, View.VISIBLE); + setVisibility(R.id.proxy_fields, View.VISIBLE); + setVisibility(R.id.proxy_pac_field, View.GONE); + if (mProxyHostView == null) { + mProxyHostView = (TextView) mView.findViewById(R.id.proxy_hostname); + mProxyHostView.addTextChangedListener(this); + mProxyPortView = (TextView) mView.findViewById(R.id.proxy_port); + mProxyPortView.addTextChangedListener(this); + mProxyExclusionListView = (TextView) mView.findViewById(R.id.proxy_exclusionlist); + mProxyExclusionListView.addTextChangedListener(this); + } + if (config != null) { + ProxyInfo proxyProperties = config.getHttpProxy(); + if (proxyProperties != null) { + mProxyHostView.setText(proxyProperties.getHost()); + mProxyPortView.setText(Integer.toString(proxyProperties.getPort())); + mProxyExclusionListView.setText( + ProxyUtils.exclusionListAsString(proxyProperties.getExclusionList())); + } + } + } else if (mProxySettingsSpinner.getSelectedItemPosition() == PROXY_PAC) { + setVisibility(R.id.proxy_warning_limited_support, View.GONE); + setVisibility(R.id.proxy_fields, View.GONE); + setVisibility(R.id.proxy_pac_field, View.VISIBLE); + + if (mProxyPacView == null) { + mProxyPacView = (TextView) mView.findViewById(R.id.proxy_pac); + mProxyPacView.addTextChangedListener(this); + } + if (config != null) { + ProxyInfo proxyInfo = config.getHttpProxy(); + if (proxyInfo != null) { + mProxyPacView.setText(proxyInfo.getPacFileUrl().toString()); + } + } + } else { + setVisibility(R.id.proxy_warning_limited_support, View.GONE); + setVisibility(R.id.proxy_fields, View.GONE); + setVisibility(R.id.proxy_pac_field, View.GONE); + } + } + + private void setVisibility(int id, int visibility) { + final View v = mView.findViewById(id); + if (v != null) { + v.setVisibility(visibility); + } + } + + @VisibleForTesting + AndroidKeystoreAliasLoader getAndroidKeystoreAliasLoader() { + return new AndroidKeystoreAliasLoader(KeyProperties.NAMESPACE_WIFI); + } + + @VisibleForTesting + void loadSims() { + List activeSubscriptionInfos = mContext + .getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList(); + if (activeSubscriptionInfos == null) { + activeSubscriptionInfos = Collections.EMPTY_LIST; + } + mActiveSubscriptionInfos.clear(); + + // De-duplicates active subscriptions and caches in mActiveSubscriptionInfos. + for (SubscriptionInfo newInfo : activeSubscriptionInfos) { + for (SubscriptionInfo cachedInfo : mActiveSubscriptionInfos) { + if (newInfo.getCarrierId() == cachedInfo.getCarrierId()) { + continue; + } + } + mActiveSubscriptionInfos.add(newInfo); + } + + // Shows disabled 'No SIM' when there is no active subscription. + if (mActiveSubscriptionInfos.size() == 0) { + final String[] noSim = new String[]{mContext.getString(R.string.wifi_no_sim_card)}; + mEapSimSpinner.setAdapter(getSpinnerAdapter(noSim)); + mEapSimSpinner.setSelection(0 /* position */); + mEapSimSpinner.setEnabled(false); + return; + } + + // Shows display name of each active subscription. + final ArrayList displayNames = new ArrayList<>(); + for (SubscriptionInfo activeSubInfo : mActiveSubscriptionInfos) { + displayNames.add( + SubscriptionUtil.getUniqueSubscriptionDisplayName(activeSubInfo, mContext)); + } + mEapSimSpinner.setAdapter( + getSpinnerAdapter(displayNames.toArray(new String[displayNames.size()]))); + mEapSimSpinner.setSelection(0 /* position */); + if (displayNames.size() == 1) { + mEapSimSpinner.setEnabled(false); + } + } + + @VisibleForTesting + void loadCertificates( + Spinner spinner, + Collection choices, + String noCertificateString, + boolean showMultipleCerts, + boolean showUsePreinstalledCertOption) { + final Context context = mConfigUi.getContext(); + + ArrayList certs = new ArrayList(); + certs.add(mUnspecifiedCertString); + if (showMultipleCerts) { + certs.add(mMultipleCertSetString); + } + if (showUsePreinstalledCertOption) { + certs.add(mUseSystemCertsString); + } + + if (choices != null && choices.size() != 0) { + certs.addAll(choices.stream() + .filter(certificateName -> { + for (String undesired : UNDESIRED_CERTIFICATES) { + if (certificateName.startsWith(undesired)) { + return false; + } + } + return true; + }).collect(Collectors.toList())); + } + + if (!TextUtils.isEmpty(noCertificateString) + && mAccessPointSecurity != AccessPoint.SECURITY_EAP_SUITE_B) { + certs.add(noCertificateString); + } + + // If there is only mUnspecifiedCertString and one item to select, only shows the item + if (certs.size() == 2) { + certs.remove(mUnspecifiedCertString); + spinner.setEnabled(false); + } else { + spinner.setEnabled(true); + } + + final ArrayAdapter adapter = getSpinnerAdapter( + certs.toArray(new String[certs.size()])); + spinner.setAdapter(adapter); + } + + private void setSelection(Spinner spinner, String value) { + if (value != null) { + @SuppressWarnings("unchecked") + ArrayAdapter adapter = (ArrayAdapter) spinner.getAdapter(); + for (int i = adapter.getCount() - 1; i >= 0; --i) { + if (value.equals(adapter.getItem(i))) { + spinner.setSelection(i); + break; + } + } + } + } + + public int getMode() { + return mMode; + } + + @Override + public void afterTextChanged(Editable s) { + ThreadUtils.postOnMainThread(() -> { + showWarningMessagesIfAppropriate(); + enableSubmitIfAppropriate(); + }); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // work done in afterTextChanged + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // work done in afterTextChanged + } + + @Override + public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { + if (textView == mPasswordView) { + if (id == EditorInfo.IME_ACTION_DONE && isSubmittable()) { + mConfigUi.dispatchSubmit(); + return true; + } + } + return false; + } + + @Override + public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { + if (view == mPasswordView) { + if (keyCode == KeyEvent.KEYCODE_ENTER && isSubmittable()) { + mConfigUi.dispatchSubmit(); + return true; + } + } + return false; + } + + @Override + public void onCheckedChanged(CompoundButton view, boolean isChecked) { + if (view.getId() == R.id.show_password) { + int pos = mPasswordView.getSelectionEnd(); + mPasswordView.setInputType(InputType.TYPE_CLASS_TEXT + | (isChecked ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + : InputType.TYPE_TEXT_VARIATION_PASSWORD)); + if (pos >= 0) { + ((EditText) mPasswordView).setSelection(pos); + } + } else if (view.getId() == R.id.wifi_advanced_togglebox) { + // Hide the SoftKeyboard temporary to let user can see most of the expanded items. + hideSoftKeyboard(mView.getWindowToken()); + view.setVisibility(View.GONE); + mView.findViewById(R.id.wifi_advanced_fields).setVisibility(View.VISIBLE); + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (parent == mSecuritySpinner) { + // Convert menu position to actual Wi-Fi security type + mAccessPointSecurity = mSecurityInPosition[position]; + showSecurityFields(/* refreshEapMethods */ true, /* refreshCertificates */ true); + + if (WifiDppUtils.isSupportEnrolleeQrCodeScanner(mContext, mAccessPointSecurity)) { + mSsidScanButton.setVisibility(View.VISIBLE); + } else { + mSsidScanButton.setVisibility(View.GONE); + } + } else if (parent == mEapMethodSpinner) { + showSecurityFields(/* refreshEapMethods */ false, /* refreshCertificates */ true); + } else if (parent == mEapCaCertSpinner) { + showSecurityFields(/* refreshEapMethods */ false, /* refreshCertificates */ false); + } else if (parent == mPhase2Spinner + && mEapMethodSpinner.getSelectedItemPosition() == WIFI_EAP_METHOD_PEAP) { + showPeapFields(); + } else if (parent == mProxySettingsSpinner) { + showProxyFields(); + } else if (parent == mHiddenSettingsSpinner) { + mHiddenWarningView.setVisibility(position == NOT_HIDDEN_NETWORK + ? View.GONE : View.VISIBLE); + } else { + showIpConfigFields(); + } + showWarningMessagesIfAppropriate(); + enableSubmitIfAppropriate(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + // + } + + /** + * Make the characters of the password visible if show_password is checked. + */ + public void updatePassword() { + TextView passwdView = (TextView) mView.findViewById(R.id.password); + passwdView.setInputType(InputType.TYPE_CLASS_TEXT + | (((CheckBox) mView.findViewById(R.id.show_password)).isChecked() + ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + : InputType.TYPE_TEXT_VARIATION_PASSWORD)); + } + + public AccessPoint getAccessPoint() { + return mAccessPoint; + } + + private void configureSecuritySpinner() { + mConfigUi.setTitle(R.string.wifi_add_network); + + mSsidView = (TextView) mView.findViewById(R.id.ssid); + mSsidView.addTextChangedListener(this); + mSecuritySpinner = ((Spinner) mView.findViewById(R.id.security)); + mSecuritySpinner.setOnItemSelectedListener(this); + + ArrayAdapter spinnerAdapter = new ArrayAdapter(mContext, + android.R.layout.simple_spinner_item, android.R.id.text1); + spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mSecuritySpinner.setAdapter(spinnerAdapter); + int idx = 0; + + // Populate the Wi-Fi security spinner with the various supported key management types + spinnerAdapter.add(mContext.getString(com.android.settingslib.R.string.wifi_security_none)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_NONE; + if (mWifiManager.isEnhancedOpenSupported()) { + spinnerAdapter.add( + mContext.getString(com.android.settingslib.R.string.wifi_security_owe)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_OWE; + } + spinnerAdapter.add(mContext.getString(com.android.settingslib.R.string.wifi_security_wep)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_WEP; + spinnerAdapter.add( + mContext.getString(com.android.settingslib.R.string.wifi_security_wpa_wpa2)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_PSK; + if (mWifiManager.isWpa3SaeSupported()) { + spinnerAdapter.add( + mContext.getString(com.android.settingslib.R.string.wifi_security_sae)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_SAE; + spinnerAdapter.add(mContext.getString( + com.android.settingslib.R.string.wifi_security_eap_wpa_wpa2)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_EAP; + spinnerAdapter.add( + mContext.getString(com.android.settingslib.R.string.wifi_security_eap_wpa3)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_EAP_WPA3_ENTERPRISE; + } else { + spinnerAdapter.add( + mContext.getString(com.android.settingslib.R.string.wifi_security_eap)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_EAP; + } + if (mWifiManager.isWpa3SuiteBSupported()) { + spinnerAdapter.add( + mContext.getString(com.android.settingslib.R.string.wifi_security_eap_suiteb)); + mSecurityInPosition[idx++] = AccessPoint.SECURITY_EAP_SUITE_B; + } + + spinnerAdapter.notifyDataSetChanged(); + + mView.findViewById(R.id.type).setVisibility(View.VISIBLE); + + showIpConfigFields(); + showProxyFields(); + mView.findViewById(R.id.wifi_advanced_toggle).setVisibility(View.VISIBLE); + // Hidden option can be changed only when the user adds a network manually. + mView.findViewById(R.id.hidden_settings_field).setVisibility(View.VISIBLE); + ((CheckBox) mView.findViewById(R.id.wifi_advanced_togglebox)) + .setOnCheckedChangeListener(this); + // Set correct accessibility strings. + setAdvancedOptionAccessibilityString(); + } + + /** + * For each target string in {@code targetStringArray} try to find if it appears in {@code + * originalStringArray}, if found then use the corresponding string, which have the same index + * of the target string in {@code replacementStringArray}, to replace it. And finally return the + * whole new string array back to caller. + */ + @VisibleForTesting + CharSequence[] findAndReplaceTargetStrings(CharSequence originalStringArray[], + CharSequence targetStringArray[], CharSequence replacementStringArray[]) { + // The length of the targetStringArray and replacementStringArray should be the same, each + // item in the targetStringArray should have a 1:1 mapping to replacementStringArray, so + // just return the original string if the lengths are different. + if (targetStringArray.length != replacementStringArray.length) { + return originalStringArray; + } + + final CharSequence[] returnEntries = new CharSequence[originalStringArray.length]; + for (int i = 0; i < originalStringArray.length; i++) { + returnEntries[i] = originalStringArray[i]; + for (int j = 0; j < targetStringArray.length; j++) { + if (TextUtils.equals(originalStringArray[i], targetStringArray[j])) { + returnEntries[i] = replacementStringArray[j]; + } + } + } + return returnEntries; + } + + private ArrayAdapter getSpinnerAdapter( + int contentStringArrayResId) { + return getSpinnerAdapter( + mContext.getResources().getStringArray(contentStringArrayResId)); + } + + @VisibleForTesting + ArrayAdapter getSpinnerAdapter( + String[] contentStringArray) { + ArrayAdapter spinnerAdapter = new ArrayAdapter<>(mContext, + android.R.layout.simple_spinner_item, contentStringArray); + spinnerAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + return spinnerAdapter; + } + + /** + * This function is to span the TTS strings to each EAP method items in the + * spinner to have detail TTS content for the TTS engine usage. + */ + private ArrayAdapter getSpinnerAdapterWithEapMethodsTts( + int contentStringArrayResId) { + final Resources res = mContext.getResources(); + CharSequence[] sourceStrings = res.getStringArray( + contentStringArrayResId); + CharSequence[] targetStrings = res.getStringArray( + R.array.wifi_eap_method_target_strings); + CharSequence[] ttsStrings = res.getStringArray( + R.array.wifi_eap_method_tts_strings); + + // Replace the target strings with tts strings and save all in a new array. + final CharSequence[] newTtsSourceStrings = findAndReplaceTargetStrings( + sourceStrings, targetStrings, ttsStrings); + + // Build new TtsSpan text arrays for TalkBack. + final CharSequence[] accessibilityArray = createAccessibleEntries( + sourceStrings, newTtsSourceStrings); + + // Return a new ArrayAdapter with the new TalkBack array. + ArrayAdapter spinnerAdapter = new ArrayAdapter<>( + mContext, android.R.layout.simple_spinner_item, accessibilityArray); + spinnerAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + return spinnerAdapter; + } + + private SpannableString[] createAccessibleEntries(CharSequence entries[], + CharSequence[] contentDescriptions) { + final SpannableString[] accessibleEntries = new SpannableString[entries.length]; + for (int i = 0; i < entries.length; i++) { + accessibleEntries[i] = com.android.settings.Utils.createAccessibleSequence(entries[i], + contentDescriptions[i].toString()); + } + return accessibleEntries; + } + + private void hideSoftKeyboard(IBinder windowToken) { + final InputMethodManager inputMethodManager = mContext.getSystemService( + InputMethodManager.class); + inputMethodManager.hideSoftInputFromWindow(windowToken, 0 /* flags */); + } + + private void setAdvancedOptionAccessibilityString() { + final CheckBox advancedToggleBox = mView.findViewById(R.id.wifi_advanced_togglebox); + advancedToggleBox.setAccessibilityDelegate(new AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo( + View v, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(v, info); + // To let TalkBack don't pronounce checked/unchecked. + info.setCheckable(false /* checkable */); + // To let TalkBack don't pronounce CheckBox. + info.setClassName(null /* className */); + // Customize TalkBack's pronunciation which been appended to "Double-tap to". + final AccessibilityAction customClick = new AccessibilityAction( + AccessibilityNodeInfo.ACTION_CLICK, + mContext.getString(R.string.wifi_advanced_toggle_description_collapsed)); + info.addAction(customClick); + } + }); + } +} diff --git a/aosp/packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java b/aosp/packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java new file mode 100644 index 000000000..915ec52e7 --- /dev/null +++ b/aosp/packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -0,0 +1,6326 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; +import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; +import static android.net.NetworkRequest.Type.LISTEN; +import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST; +import static android.net.NetworkRequest.Type.REQUEST; +import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; +import static android.net.QosCallback.QosCallbackRegistrationException; + +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresApi; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityDiagnosticsManager.DataStallReport.DetectionMethod; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.SocketKeepalive.Callback; +import android.net.TetheringManager.StartTetheringCallback; +import android.net.TetheringManager.TetheringEventCallback; +import android.net.TetheringManager.TetheringRequest; +import android.os.Binder; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ServiceSpecificException; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Range; +import android.util.SparseIntArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.modules.utils.build.SdkLevel; + +import libcore.net.event.NetworkEventDispatcher; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; + +/** + * Class that answers queries about the state of network connectivity. It also + * notifies applications when network connectivity changes. + *

+ * The primary responsibilities of this class are to: + *

    + *
  1. Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)
  2. + *
  3. Send broadcast intents when network connectivity changes
  4. + *
  5. Attempt to "fail over" to another network when connectivity to a network + * is lost
  6. + *
  7. Provide an API that allows applications to query the coarse-grained or fine-grained + * state of the available networks
  8. + *
  9. Provide an API that allows applications to request and select networks for their data + * traffic
  10. + *
+ */ +@SystemService(Context.CONNECTIVITY_SERVICE) +public class ConnectivityManager { + private static final String TAG = "ConnectivityManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is + // available here + /** @hide */ + public static class Flags { + static final String SET_DATA_SAVER_VIA_CM = + "com.android.net.flags.set_data_saver_via_cm"; + static final String SUPPORT_IS_UID_NETWORKING_BLOCKED = + "com.android.net.flags.support_is_uid_networking_blocked"; + static final String BASIC_BACKGROUND_RESTRICTIONS_ENABLED = + "com.android.net.flags.basic_background_restrictions_enabled"; + } + + /** + * A change in network connectivity has occurred. A default connection has either + * been established or lost. The NetworkInfo for the affected network is + * sent as an extra; it should be consulted to see what kind of + * connectivity event occurred. + *

+ * Apps targeting Android 7.0 (API level 24) and higher do not receive this + * broadcast if they declare the broadcast receiver in their manifest. Apps + * will still receive broadcasts if they register their + * {@link android.content.BroadcastReceiver} with + * {@link android.content.Context#registerReceiver Context.registerReceiver()} + * and that context is still valid. + *

+ * If this is a connection that was the result of failing over from a + * disconnected network, then the FAILOVER_CONNECTION boolean extra is + * set to true. + *

+ * For a loss of connectivity, if the connectivity manager is attempting + * to connect (or has already connected) to another network, the + * NetworkInfo for the new network is also passed as an extra. This lets + * any receivers of the broadcast know that they should not necessarily + * tell the user that no data traffic will be possible. Instead, the + * receiver should expect another broadcast soon, indicating either that + * the failover attempt succeeded (and so there is still overall data + * connectivity), or that the failover attempt failed, meaning that all + * connectivity has been lost. + *

+ * For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY + * is set to {@code true} if there are no connected networks at all. + *

+ * Note that this broadcast is deprecated and generally tries to implement backwards + * compatibility with older versions of Android. As such, it may not reflect new + * capabilities of the system, like multiple networks being connected at the same + * time, the details of newer technology, or changes in tethering state. + * + * @deprecated apps should use the more versatile {@link #requestNetwork}, + * {@link #registerNetworkCallback} or {@link #registerDefaultNetworkCallback} + * functions instead for faster and more detailed updates about the network + * changes they care about. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + + /** + * The device has connected to a network that has presented a captive + * portal, which is blocking Internet connectivity. The user was presented + * with a notification that network sign in is required, + * and the user invoked the notification's action indicating they + * desire to sign in to the network. Apps handling this activity should + * facilitate signing in to the network. This action includes a + * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents + * the network presenting the captive portal; all communication with the + * captive portal must be done using this {@code Network} object. + *

+ * This activity includes a {@link CaptivePortal} extra named + * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different + * outcomes of the captive portal sign in to the system: + *

    + *
  • When the app handling this action believes the user has signed in to + * the network and the captive portal has been dismissed, the app should + * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can + * reevaluate the network. If reevaluation finds the network no longer + * subject to a captive portal, the network may become the default active + * data network.
  • + *
  • When the app handling this action believes the user explicitly wants + * to ignore the captive portal and the network, the app should call + * {@link CaptivePortal#ignoreNetwork}.
  • + *
+ */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + + /** + * The lookup key for a {@link NetworkInfo} object. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + * + * @deprecated The {@link NetworkInfo} object is deprecated, as many of its properties + * can't accurately represent modern network characteristics. + * Please obtain information about networks from the {@link NetworkCapabilities} + * or {@link LinkProperties} objects instead. + */ + @Deprecated + public static final String EXTRA_NETWORK_INFO = "networkInfo"; + + /** + * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast. + * + * @see android.content.Intent#getIntExtra(String, int) + * @deprecated The network type is not rich enough to represent the characteristics + * of modern networks. Please use {@link NetworkCapabilities} instead, + * in particular the transports. + */ + @Deprecated + public static final String EXTRA_NETWORK_TYPE = "networkType"; + + /** + * The lookup key for a boolean that indicates whether a connect event + * is for a network to which the connectivity manager was failing over + * following a disconnect on another network. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public static final String EXTRA_IS_FAILOVER = "isFailover"; + /** + * The lookup key for a {@link NetworkInfo} object. This is supplied when + * there is another network that it may be possible to connect to. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; + /** + * The lookup key for a boolean that indicates whether there is a + * complete lack of connectivity, i.e., no network is available. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + */ + public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; + /** + * The lookup key for a string that indicates why an attempt to connect + * to a network failed. The string has no particular structure. It is + * intended to be used in notifications presented to users. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_REASON = "reason"; + /** + * The lookup key for a string that provides optionally supplied + * extra information about the network state. The information + * may be passed up from the lower networking layers, and its + * meaning may be specific to a particular network type. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + * + * @deprecated See {@link NetworkInfo#getExtraInfo()}. + */ + @Deprecated + public static final String EXTRA_EXTRA_INFO = "extraInfo"; + /** + * The lookup key for an int that provides information about + * our connection to the internet at large. 0 indicates no connection, + * 100 indicates a great connection. Retrieve it with + * {@link android.content.Intent#getIntExtra(String, int)}. + * {@hide} + */ + public static final String EXTRA_INET_CONDITION = "inetCondition"; + /** + * The lookup key for a {@link CaptivePortal} object included with the + * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent. The {@code CaptivePortal} + * object can be used to either indicate to the system that the captive + * portal has been dismissed or that the user does not want to pursue + * signing in to captive portal. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; + + /** + * Key for passing a URL to the captive portal login activity. + */ + public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; + + /** + * Key for passing a {@link android.net.captiveportal.CaptivePortalProbeSpec} to the captive + * portal login activity. + * {@hide} + */ + @SystemApi + public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = + "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; + + /** + * Key for passing a user agent string to the captive portal login activity. + * {@hide} + */ + @SystemApi + public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = + "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + + /** + * Broadcast action to indicate the change of data activity status + * (idle or active) on a network in a recent period. + * The network becomes active when data transmission is started, or + * idle if there is no data transmission for a period of time. + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_ACTIVITY_CHANGE = + "android.net.conn.DATA_ACTIVITY_CHANGE"; + /** + * The lookup key for an enum that indicates the network device type on which this data activity + * change happens. + * {@hide} + */ + public static final String EXTRA_DEVICE_TYPE = "deviceType"; + /** + * The lookup key for a boolean that indicates the device is active or not. {@code true} means + * it is actively sending or receiving data and {@code false} means it is idle. + * {@hide} + */ + public static final String EXTRA_IS_ACTIVE = "isActive"; + /** + * The lookup key for a long that contains the timestamp (nanos) of the radio state change. + * {@hide} + */ + public static final String EXTRA_REALTIME_NS = "tsNanos"; + + /** + * Broadcast Action: The setting for background data usage has changed + * values. Use {@link #getBackgroundDataSetting()} to get the current value. + *

+ * If an application uses the network in the background, it should listen + * for this broadcast and stop using the background data if the value is + * {@code false}. + *

+ * + * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability + * of background data depends on several combined factors, and + * this broadcast is no longer sent. Instead, when background + * data is unavailable, {@link #getActiveNetworkInfo()} will now + * appear disconnected. During first boot after a platform + * upgrade, this broadcast will be sent once if + * {@link #getBackgroundDataSetting()} was {@code false} before + * the upgrade. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = + "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + + /** + * Broadcast Action: The network connection may not be good + * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and + * {@code ConnectivityManager.EXTRA_NETWORK_INFO} to specify + * the network and it's condition. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage + public static final String INET_CONDITION_ACTION = + "android.net.conn.INET_CONDITION_ACTION"; + + /** + * Broadcast Action: A tetherable connection has come or gone. + * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER}, + * {@code ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY}, + * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER}, and + * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate + * the current state of tethering. Each include a list of + * interface names in that state (may be empty). + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String ACTION_TETHER_STATE_CHANGED = + TetheringManager.ACTION_TETHER_STATE_CHANGED; + + /** + * @hide + * gives a String[] listing all the interfaces configured for + * tethering and currently available for tethering. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_AVAILABLE_TETHER = TetheringManager.EXTRA_AVAILABLE_TETHER; + + /** + * @hide + * gives a String[] listing all the interfaces currently in local-only + * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) + */ + public static final String EXTRA_ACTIVE_LOCAL_ONLY = TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY; + + /** + * @hide + * gives a String[] listing all the interfaces currently tethered + * (ie, has DHCPv4 support and packets potentially forwarded/NATed) + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_ACTIVE_TETHER = TetheringManager.EXTRA_ACTIVE_TETHER; + + /** + * @hide + * gives a String[] listing all the interfaces we tried to tether and + * failed. Use {@link #getLastTetherError} to find the error code + * for any interfaces listed here. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_ERRORED_TETHER = TetheringManager.EXTRA_ERRORED_TETHER; + + /** + * Broadcast Action: The captive portal tracker has finished its test. + * Sent only while running Setup Wizard, in lieu of showing a user + * notification. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CAPTIVE_PORTAL_TEST_COMPLETED = + "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED"; + /** + * The lookup key for a boolean that indicates whether a captive portal was detected. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * @hide + */ + public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal"; + + /** + * Action used to display a dialog that asks the user whether to connect to a network that is + * not validated. This intent is used to start the dialog in settings via startActivity. + * + * This action includes a {@link Network} typed extra which is called + * {@link ConnectivityManager#EXTRA_NETWORK} that represents the network which is unvalidated. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED"; + + /** + * Action used to display a dialog that asks the user whether to avoid a network that is no + * longer validated. This intent is used to start the dialog in settings via startActivity. + * + * This action includes a {@link Network} typed extra which is called + * {@link ConnectivityManager#EXTRA_NETWORK} that represents the network which is no longer + * validated. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final String ACTION_PROMPT_LOST_VALIDATION = + "android.net.action.PROMPT_LOST_VALIDATION"; + + /** + * Action used to display a dialog that asks the user whether to stay connected to a network + * that has not validated. This intent is used to start the dialog in settings via + * startActivity. + * + * This action includes a {@link Network} typed extra which is called + * {@link ConnectivityManager#EXTRA_NETWORK} that represents the network which has partial + * connectivity. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = + "android.net.action.PROMPT_PARTIAL_CONNECTIVITY"; + + /** + * Clear DNS Cache Action: This is broadcast when networks have changed and old + * DNS entries should be cleared. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE"; + + /** + * Invalid tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + public static final int TETHERING_INVALID = TetheringManager.TETHERING_INVALID; + + /** + * Wifi tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_WIFI = 0; + + /** + * USB tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_USB = 1; + + /** + * Bluetooth tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_BLUETOOTH = 2; + + /** + * Wifi P2p tethering type. + * Wifi P2p tethering is set through events automatically, and don't + * need to start from #startTethering(int, boolean, OnStartTetheringCallback). + * @hide + */ + public static final int TETHERING_WIFI_P2P = TetheringManager.TETHERING_WIFI_P2P; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering to + * enable if any. + * @hide + */ + public static final String EXTRA_ADD_TETHER_TYPE = TetheringConstants.EXTRA_ADD_TETHER_TYPE; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering for + * which to cancel provisioning. + * @hide + */ + public static final String EXTRA_REM_TETHER_TYPE = TetheringConstants.EXTRA_REM_TETHER_TYPE; + + /** + * Extra used for communicating with the TetherService. True to schedule a recheck of tether + * provisioning. + * @hide + */ + public static final String EXTRA_SET_ALARM = TetheringConstants.EXTRA_SET_ALARM; + + /** + * Tells the TetherService to run a provision check now. + * @hide + */ + public static final String EXTRA_RUN_PROVISION = TetheringConstants.EXTRA_RUN_PROVISION; + + /** + * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} + * which will receive provisioning results. Can be left empty. + * @hide + */ + public static final String EXTRA_PROVISION_CALLBACK = + TetheringConstants.EXTRA_PROVISION_CALLBACK; + + /** + * The absence of a connection type. + * @hide + */ + @SystemApi + public static final int TYPE_NONE = -1; + + /** + * A Mobile data connection. Devices may support more than one. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. See {@link NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_MOBILE = 0; + + /** + * A WIFI data connection. Devices may support more than one. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. See {@link NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_WIFI = 1; + + /** + * An MMS-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is used by applications needing to talk to the carrier's + * Multimedia Messaging Service servers. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_MMS = 2; + + /** + * A SUPL-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is used by applications needing to talk to the carrier's + * Secure User Plane Location servers for help locating the device. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_SUPL = 3; + + /** + * A DUN-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is sometimes by the system when setting up an upstream connection + * for tethering so that the carrier is aware of DUN traffic. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_DUN} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_DUN = 4; + + /** + * A High Priority Mobile data connection. This network type uses the + * same network interface as {@link #TYPE_MOBILE} but the routing setup + * is different. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. See {@link NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_MOBILE_HIPRI = 5; + + /** + * A WiMAX data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. See {@link NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_WIMAX = 6; + + /** + * A Bluetooth data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. See {@link NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_BLUETOOTH = 7; + + /** + * Fake data connection. This should not be used on shipping devices. + * @deprecated This is not used any more. + */ + @Deprecated + public static final int TYPE_DUMMY = 8; + + /** + * An Ethernet data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. See {@link NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_ETHERNET = 9; + + /** + * Over the air Administration. + * @deprecated Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_FOTA = 10; + + /** + * IP Multimedia Subsystem. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IMS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage + public static final int TYPE_MOBILE_IMS = 11; + + /** + * Carrier Branded Services. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_CBS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_CBS = 12; + + /** + * A Wi-Fi p2p connection. Only requesting processes will have access to + * the peers connected. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_WIFI_P2P} instead. + * {@hide} + */ + @Deprecated + @SystemApi + public static final int TYPE_WIFI_P2P = 13; + + /** + * The network to use for initially attaching to the network + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IA} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage + public static final int TYPE_MOBILE_IA = 14; + + /** + * Emergency PDN connection for emergency services. This + * may include IMS and MMS in emergency situations. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_EIMS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_EMERGENCY = 15; + + /** + * The network that uses proxy to achieve connectivity. + * @deprecated Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @SystemApi + public static final int TYPE_PROXY = 16; + + /** + * A virtual network using one or more native bearers. + * It may or may not be providing security services. + * @deprecated Applications should use {@link NetworkCapabilities#TRANSPORT_VPN} instead. + */ + @Deprecated + public static final int TYPE_VPN = 17; + + /** + * A network that is exclusively meant to be used for testing + * + * @deprecated Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public static final int TYPE_TEST = 18; // TODO: Remove this once NetworkTypes are unused. + + /** + * @deprecated Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_NONE, + TYPE_MOBILE, + TYPE_WIFI, + TYPE_MOBILE_MMS, + TYPE_MOBILE_SUPL, + TYPE_MOBILE_DUN, + TYPE_MOBILE_HIPRI, + TYPE_WIMAX, + TYPE_BLUETOOTH, + TYPE_DUMMY, + TYPE_ETHERNET, + TYPE_MOBILE_FOTA, + TYPE_MOBILE_IMS, + TYPE_MOBILE_CBS, + TYPE_WIFI_P2P, + TYPE_MOBILE_IA, + TYPE_MOBILE_EMERGENCY, + TYPE_PROXY, + TYPE_VPN, + TYPE_TEST + }) + public @interface LegacyNetworkType {} + + // Deprecated constants for return values of startUsingNetworkFeature. They used to live + // in com.android.internal.telephony.PhoneConstants until they were made inaccessible. + private static final int DEPRECATED_PHONE_CONSTANT_APN_ALREADY_ACTIVE = 0; + private static final int DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED = 1; + private static final int DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED = 3; + + /** {@hide} */ + public static final int MAX_RADIO_TYPE = TYPE_TEST; + + /** {@hide} */ + public static final int MAX_NETWORK_TYPE = TYPE_TEST; + + private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; + + /** + * If you want to set the default network preference,you can directly + * change the networkAttributes array in framework's config.xml. + * + * @deprecated Since we support so many more networks now, the single + * network default network preference can't really express + * the hierarchy. Instead, the default is defined by the + * networkAttributes in config.xml. You can determine + * the current value by calling {@link #getNetworkPreference()} + * from an App. + */ + @Deprecated + public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI; + + /** + * @hide + */ + public static final int REQUEST_ID_UNSET = 0; + + /** + * Static unique request used as a tombstone for NetworkCallbacks that have been unregistered. + * This allows to distinguish when unregistering NetworkCallbacks those that were never + * registered from those that were already unregistered. + * @hide + */ + private static final NetworkRequest ALREADY_UNREGISTERED = + new NetworkRequest.Builder().clearCapabilities().build(); + + /** + * A NetID indicating no Network is selected. + * Keep in sync with bionic/libc/dns/include/resolv_netid.h + * @hide + */ + public static final int NETID_UNSET = 0; + + /** + * Flag to indicate that an app is not subject to any restrictions that could result in its + * network access blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_NONE = 0; + + /** + * Flag to indicate that an app is subject to Battery saver restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0; + + /** + * Flag to indicate that an app is subject to Doze restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_DOZE = 1 << 1; + + /** + * Flag to indicate that an app is subject to App Standby restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2; + + /** + * Flag to indicate that an app is subject to Restricted mode restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3; + + /** + * Flag to indicate that an app is blocked because it is subject to an always-on VPN but the VPN + * is not currently connected. + * + * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean) + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_LOCKDOWN_VPN = 1 << 4; + + /** + * Flag to indicate that an app is subject to Low Power Standby restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 1 << 5; + + /** + * Flag to indicate that an app is subject to default background restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_APP_BACKGROUND = 1 << 6; + + /** + * Flag to indicate that an app is subject to Data saver restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16; + + /** + * Flag to indicate that an app is subject to user restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17; + + /** + * Flag to indicate that an app is subject to Device admin restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"BLOCKED_"}, value = { + BLOCKED_REASON_NONE, + BLOCKED_REASON_BATTERY_SAVER, + BLOCKED_REASON_DOZE, + BLOCKED_REASON_APP_STANDBY, + BLOCKED_REASON_RESTRICTED_MODE, + BLOCKED_REASON_LOCKDOWN_VPN, + BLOCKED_REASON_LOW_POWER_STANDBY, + BLOCKED_REASON_APP_BACKGROUND, + BLOCKED_METERED_REASON_DATA_SAVER, + BLOCKED_METERED_REASON_USER_RESTRICTED, + BLOCKED_METERED_REASON_ADMIN_DISABLED, + }) + public @interface BlockedReason {} + + /** + * Set of blocked reasons that are only applicable on metered networks. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_MASK = 0xffff0000; + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + private final IConnectivityManager mService; + + /** + * Firewall chain for device idle (doze mode). + * Allowlist of apps that have network access in device idle. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_DOZABLE = 1; + + /** + * Firewall chain used for app standby. + * Denylist of apps that do not have network access. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_STANDBY = 2; + + /** + * Firewall chain used for battery saver. + * Allowlist of apps that have network access when battery saver is on. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_POWERSAVE = 3; + + /** + * Firewall chain used for restricted networking mode. + * Allowlist of apps that have access in restricted networking mode. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_RESTRICTED = 4; + + /** + * Firewall chain used for low power standby. + * Allowlist of apps that have access in low power standby. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; + + /** + * Firewall chain used for always-on default background restrictions. + * Allowlist of apps that have access because either they are in the foreground or they are + * exempted for specific situations while in the background. + * @hide + */ + @FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_BACKGROUND = 6; + + /** + * Firewall chain used for OEM-specific application restrictions. + * + * Denylist of apps that will not have network access due to OEM-specific restrictions. If an + * app UID is placed on this chain, and the chain is enabled, the app's packets will be dropped. + * + * All the {@code FIREWALL_CHAIN_OEM_DENY_x} chains are equivalent, and each one is + * independent of the others. The chains can be enabled and disabled independently, and apps can + * be added and removed from each chain independently. + * + * @see #FIREWALL_CHAIN_OEM_DENY_2 + * @see #FIREWALL_CHAIN_OEM_DENY_3 + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7; + + /** + * Firewall chain used for OEM-specific application restrictions. + * + * Denylist of apps that will not have network access due to OEM-specific restrictions. If an + * app UID is placed on this chain, and the chain is enabled, the app's packets will be dropped. + * + * All the {@code FIREWALL_CHAIN_OEM_DENY_x} chains are equivalent, and each one is + * independent of the others. The chains can be enabled and disabled independently, and apps can + * be added and removed from each chain independently. + * + * @see #FIREWALL_CHAIN_OEM_DENY_1 + * @see #FIREWALL_CHAIN_OEM_DENY_3 + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8; + + /** + * Firewall chain used for OEM-specific application restrictions. + * + * Denylist of apps that will not have network access due to OEM-specific restrictions. If an + * app UID is placed on this chain, and the chain is enabled, the app's packets will be dropped. + * + * All the {@code FIREWALL_CHAIN_OEM_DENY_x} chains are equivalent, and each one is + * independent of the others. The chains can be enabled and disabled independently, and apps can + * be added and removed from each chain independently. + * + * @see #FIREWALL_CHAIN_OEM_DENY_1 + * @see #FIREWALL_CHAIN_OEM_DENY_2 + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = { + FIREWALL_CHAIN_DOZABLE, + FIREWALL_CHAIN_STANDBY, + FIREWALL_CHAIN_POWERSAVE, + FIREWALL_CHAIN_RESTRICTED, + FIREWALL_CHAIN_LOW_POWER_STANDBY, + FIREWALL_CHAIN_BACKGROUND, + FIREWALL_CHAIN_OEM_DENY_1, + FIREWALL_CHAIN_OEM_DENY_2, + FIREWALL_CHAIN_OEM_DENY_3 + }) + public @interface FirewallChain {} + + /** + * A firewall rule which allows or drops packets depending on existing policy. + * Used by {@link #setUidFirewallRule(int, int, int)} to follow existing policy to handle + * specific uid's packets in specific firewall chain. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_RULE_DEFAULT = 0; + + /** + * A firewall rule which allows packets. Used by {@link #setUidFirewallRule(int, int, int)} to + * allow specific uid's packets in specific firewall chain. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_RULE_ALLOW = 1; + + /** + * A firewall rule which drops packets. Used by {@link #setUidFirewallRule(int, int, int)} to + * drop specific uid's packets in specific firewall chain. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_RULE_DENY = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "FIREWALL_RULE_", value = { + FIREWALL_RULE_DEFAULT, + FIREWALL_RULE_ALLOW, + FIREWALL_RULE_DENY + }) + public @interface FirewallRule {} + + /** + * A kludge to facilitate static access where a Context pointer isn't available, like in the + * case of the static set/getProcessDefaultNetwork methods and from the Network class. + * TODO: Remove this after deprecating the static methods in favor of non-static methods or + * methods that take a Context argument. + */ + private static ConnectivityManager sInstance; + + private final Context mContext; + + @GuardedBy("mTetheringEventCallbacks") + private TetheringManager mTetheringManager; + + private TetheringManager getTetheringManager() { + synchronized (mTetheringEventCallbacks) { + if (mTetheringManager == null) { + mTetheringManager = mContext.getSystemService(TetheringManager.class); + } + return mTetheringManager; + } + } + + /** + * Tests if a given integer represents a valid network type. + * @param networkType the type to be tested + * @return {@code true} if the type is valid, else {@code false} + * @deprecated All APIs accepting a network type are deprecated. There should be no need to + * validate a network type. + */ + @Deprecated + public static boolean isNetworkTypeValid(int networkType) { + return MIN_NETWORK_TYPE <= networkType && networkType <= MAX_NETWORK_TYPE; + } + + /** + * Returns a non-localized string representing a given network type. + * ONLY used for debugging output. + * @param type the type needing naming + * @return a String for the given type, or a string version of the type ("87") + * if no name is known. + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static String getNetworkTypeName(int type) { + switch (type) { + case TYPE_NONE: + return "NONE"; + case TYPE_MOBILE: + return "MOBILE"; + case TYPE_WIFI: + return "WIFI"; + case TYPE_MOBILE_MMS: + return "MOBILE_MMS"; + case TYPE_MOBILE_SUPL: + return "MOBILE_SUPL"; + case TYPE_MOBILE_DUN: + return "MOBILE_DUN"; + case TYPE_MOBILE_HIPRI: + return "MOBILE_HIPRI"; + case TYPE_WIMAX: + return "WIMAX"; + case TYPE_BLUETOOTH: + return "BLUETOOTH"; + case TYPE_DUMMY: + return "DUMMY"; + case TYPE_ETHERNET: + return "ETHERNET"; + case TYPE_MOBILE_FOTA: + return "MOBILE_FOTA"; + case TYPE_MOBILE_IMS: + return "MOBILE_IMS"; + case TYPE_MOBILE_CBS: + return "MOBILE_CBS"; + case TYPE_WIFI_P2P: + return "WIFI_P2P"; + case TYPE_MOBILE_IA: + return "MOBILE_IA"; + case TYPE_MOBILE_EMERGENCY: + return "MOBILE_EMERGENCY"; + case TYPE_PROXY: + return "PROXY"; + case TYPE_VPN: + return "VPN"; + case TYPE_TEST: + return "TEST"; + default: + return Integer.toString(type); + } + } + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public void systemReady() { + try { + mService.systemReady(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Checks if a given type uses the cellular data connection. + * This should be replaced in the future by a network property. + * @param networkType the type to check + * @return a boolean - {@code true} if uses cellular network, else {@code false} + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static boolean isNetworkTypeMobile(int networkType) { + switch (networkType) { + case TYPE_MOBILE: + case TYPE_MOBILE_MMS: + case TYPE_MOBILE_SUPL: + case TYPE_MOBILE_DUN: + case TYPE_MOBILE_HIPRI: + case TYPE_MOBILE_FOTA: + case TYPE_MOBILE_IMS: + case TYPE_MOBILE_CBS: + case TYPE_MOBILE_IA: + case TYPE_MOBILE_EMERGENCY: + return true; + default: + return false; + } + } + + /** + * Checks if the given network type is backed by a Wi-Fi radio. + * + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public static boolean isNetworkTypeWifi(int networkType) { + switch (networkType) { + case TYPE_WIFI: + case TYPE_WIFI_P2P: + return true; + default: + return false; + } + } + + /** + * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}. + * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)} + * Specify that the traffic for this user should by follow the default rules: + * applications in the profile designated by the UserHandle behave like any + * other application and use the system default network as their default + * network. Compare other PROFILE_NETWORK_PREFERENCE_* settings. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; + + /** + * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}. + * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)} + * Specify that the traffic for this user should by default go on a network with + * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network + * if no such network is available. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; + + /** + * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}. + * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)} + * Specify that the traffic for this user should by default go on a network with + * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE} and if no such network is available + * should not have a default network at all (that is, network accesses that + * do not specify a network explicitly terminate with an error), even if there + * is a system default network available to apps outside this preference. + * The apps can still use a non-enterprise network if they request it explicitly + * provided that specific network doesn't require any specific permission they + * do not hold. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2; + + /** + * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}. + * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)} + * Specify that the traffic for this user should by default go on a network with + * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}. + * If there is no such network, the apps will have no default + * network at all, even if there are available non-enterprise networks on the + * device (that is, network accesses that do not specify a network explicitly + * terminate with an error). Additionally, the designated apps should be + * blocked from using any non-enterprise network even if they specify it + * explicitly, unless they hold specific privilege overriding this (see + * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}). + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + PROFILE_NETWORK_PREFERENCE_DEFAULT, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + }) + public @interface ProfileNetworkPreferencePolicy { + } + + /** + * Specifies the preferred network type. When the device has more + * than one type available the preferred network type will be used. + * + * @param preference the network type to prefer over all others. It is + * unspecified what happens to the old preferred network in the + * overall ordering. + * @deprecated Functionality has been removed as it no longer makes sense, + * with many more than two networks - we'd need an array to express + * preference. Instead we use dynamic network properties of + * the networks to describe their precedence. + */ + @Deprecated + public void setNetworkPreference(int preference) { + } + + /** + * Retrieves the current preferred network type. + * + * @return an integer representing the preferred network type + * + * @deprecated Functionality has been removed as it no longer makes sense, + * with many more than two networks - we'd need an array to express + * preference. Instead we use dynamic network properties of + * the networks to describe their precedence. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public int getNetworkPreference() { + return TYPE_NONE; + } + + /** + * Returns details about the currently active default data network. When + * connected, this network is the default route for outgoing connections. + * You should always check {@link NetworkInfo#isConnected()} before initiating + * network traffic. This may return {@code null} when there is no default + * network. + * Note that if the default network is a VPN, this method will return the + * NetworkInfo for one of its underlying networks instead, or null if the + * VPN agent did not specify any. Apps interested in learning about VPNs + * should use {@link #getNetworkInfo(android.net.Network)} instead. + * + * @return a {@link NetworkInfo} object for the current default network + * or {@code null} if no default network is currently active + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getActiveNetworkInfo() { + try { + return mService.getActiveNetworkInfo(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a {@link Network} object corresponding to the currently active + * default data network. In the event that the current active default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network, or when the default network is blocked. + * + * @return a {@link Network} object for the current default network or + * {@code null} if no default network is currently active or if + * the default network is blocked for the caller + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public Network getActiveNetwork() { + try { + return mService.getActiveNetwork(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a {@link Network} object corresponding to the currently active + * default data network for a specific UID. In the event that the default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network for the UID. + * + * @return a {@link Network} object for the current default network for the + * given UID or {@code null} if no default network is currently active + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + @Nullable + public Network getActiveNetworkForUid(int uid) { + return getActiveNetworkForUid(uid, false); + } + + /** {@hide} */ + public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) { + try { + return mService.getActiveNetworkForUid(uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static UidRange[] getUidRangeArray(@NonNull Collection> ranges) { + Objects.requireNonNull(ranges); + final UidRange[] rangesArray = new UidRange[ranges.size()]; + int index = 0; + for (Range range : ranges) { + rangesArray[index++] = new UidRange(range.getLower(), range.getUpper()); + } + + return rangesArray; + } + + /** + * Adds or removes a requirement for given UID ranges to use the VPN. + * + * If set to {@code true}, informs the system that the UIDs in the specified ranges must not + * have any connectivity except if a VPN is connected and applies to the UIDs, or if the UIDs + * otherwise have permission to bypass the VPN (e.g., because they have the + * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission, or when + * using a socket protected by a method such as {@link VpnService#protect(DatagramSocket)}. If + * set to {@code false}, a previously-added restriction is removed. + *

+ * Each of the UID ranges specified by this method is added and removed as is, and no processing + * is performed on the ranges to de-duplicate, merge, split, or intersect them. In order to + * remove a previously-added range, the exact range must be removed as is. + *

+ * The changes are applied asynchronously and may not have been applied by the time the method + * returns. Apps will be notified about any changes that apply to them via + * {@link NetworkCallback#onBlockedStatusChanged} callbacks called after the changes take + * effect. + *

+ * This method will block the specified UIDs from accessing non-VPN networks, but does not + * affect what the UIDs get as their default network. + * Compare {@link #setVpnDefaultForUids(String, Collection)}, which declares that the UIDs + * should only have a VPN as their default network, but does not block them from accessing other + * networks if they request them explicitly with the {@link Network} API. + *

+ * This method should be called only by the VPN code. + * + * @param ranges the UID ranges to restrict + * @param requireVpn whether the specified UID ranges must use a VPN + * + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) + public void setRequireVpnForUids(boolean requireVpn, + @NonNull Collection> ranges) { + Objects.requireNonNull(ranges); + // The Range class is not parcelable. Convert to UidRange, which is what is used internally. + // This method is not necessarily expected to be used outside the system server, so + // parceling may not be necessary, but it could be used out-of-process, e.g., by the network + // stack process, or by tests. + final UidRange[] rangesArray = getUidRangeArray(ranges); + try { + mService.setRequireVpnForUids(requireVpn, rangesArray); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Inform the system that this VPN session should manage the passed UIDs. + * + * A VPN with the specified session ID may call this method to inform the system that the UIDs + * in the specified range are subject to a VPN. + * When this is called, the system will only choose a VPN for the default network of the UIDs in + * the specified ranges. + * + * This method declares that the UIDs in the range will only have a VPN for their default + * network, but does not block the UIDs from accessing other networks (permissions allowing) by + * explicitly requesting it with the {@link Network} API. + * Compare {@link #setRequireVpnForUids(boolean, Collection)}, which does not affect what + * network the UIDs get as default, but will block them from accessing non-VPN networks. + * + * @param session The VPN session which manages the passed UIDs. + * @param ranges The uid ranges which will treat VPN as their only default network. + * + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) + public void setVpnDefaultForUids(@NonNull String session, + @NonNull Collection> ranges) { + Objects.requireNonNull(ranges); + final UidRange[] rangesArray = getUidRangeArray(ranges); + try { + mService.setVpnNetworkPreference(session, rangesArray); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Temporarily set automaticOnOff keeplaive TCP polling alarm timer to 1 second. + * + * TODO: Remove this when the TCP polling design is replaced with callback. + * @param timeMs The time of expiry, with System.currentTimeMillis() base. The value should be + * set no more than 5 minutes in the future. + * @hide + */ + public void setTestLowTcpPollingTimerForKeepalive(long timeMs) { + try { + mService.setTestLowTcpPollingTimerForKeepalive(timeMs); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by + * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12 + * but is still supported for backwards compatibility. + *

+ * This type of VPN is assumed always to use the system default network, and must always declare + * exactly one underlying network, which is the network that was the default when the VPN + * connected. + *

+ * Calling this method with {@code true} enables legacy behaviour, specifically: + *

    + *
  • Any VPN that applies to userId 0 behaves specially with respect to deprecated + * {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the + * {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN + * connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network + * underlying the VPN.
  • + *
  • Deprecated APIs that return {@link NetworkInfo} objects will have their state + * similarly replaced by the VPN network state.
  • + *
  • Information on current network interfaces passed to NetworkStatsService will not + * include any VPN interfaces.
  • + *
+ * + * @param enabled whether legacy lockdown VPN is enabled or disabled + * + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) + public void setLegacyLockdownVpnEnabled(boolean enabled) { + try { + mService.setLegacyLockdownVpnEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns details about the currently active default data network for a given uid. + * This is for privileged use only to avoid spying on other apps. + * + * @return a {@link NetworkInfo} object for the current default network + * for the given uid or {@code null} if no default network is + * available for the specified uid. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public NetworkInfo getActiveNetworkInfoForUid(int uid) { + return getActiveNetworkInfoForUid(uid, false); + } + + /** {@hide} */ + public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { + try { + return mService.getActiveNetworkInfoForUid(uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about a particular network type. + * + * @param networkType integer specifying which networkType in + * which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network type or {@code null} if the type is not + * supported by the device. If {@code networkType} is + * TYPE_VPN and a VPN is active for the calling app, + * then this method will try to return one of the + * underlying networks for the VPN or null if the + * VPN agent didn't specify any. + * + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getNetworkInfo(int networkType) { + try { + return mService.getNetworkInfo(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about a particular Network. + * + * @param network {@link Network} specifying which network + * in which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network or {@code null} if the {@code Network} + * is not valid. + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getNetworkInfo(@Nullable Network network) { + return getNetworkInfoForUid(network, Process.myUid(), false); + } + + /** {@hide} */ + public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) { + try { + return mService.getNetworkInfoForUid(network, uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about all network types supported by the device. + * + * @return an array of {@link NetworkInfo} objects. Check each + * {@link NetworkInfo#getType} for which type each applies. + * + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @NonNull + public NetworkInfo[] getAllNetworkInfo() { + try { + return mService.getAllNetworkInfo(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return a list of {@link NetworkStateSnapshot}s, one for each network that is currently + * connected. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @NonNull + public List getAllNetworkStateSnapshots() { + try { + return mService.getAllNetworkStateSnapshots(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the {@link Network} object currently serving a given type, or + * null if the given type is not connected. + * + * @hide + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + public Network getNetworkForType(int networkType) { + try { + return mService.getNetworkForType(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns an array of all {@link Network} currently tracked by the framework. + * + * @deprecated This method does not provide any notification of network state changes, forcing + * apps to call it repeatedly. This is inefficient and prone to race conditions. + * Apps should use methods such as + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} instead. + * Apps that desire to obtain information about networks that do not apply to them + * can use {@link NetworkRequest.Builder#setIncludeOtherUidNetworks}. + * + * @return an array of {@link Network} objects. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @NonNull + @Deprecated + public Network[] getAllNetworks() { + try { + return mService.getAllNetworks(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns an array of {@link NetworkCapabilities} objects, representing + * the Networks that applications run by the given user will use by default. + * @hide + */ + @UnsupportedAppUsage + public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) { + try { + return mService.getDefaultNetworkCapabilitiesForUser( + userId, mContext.getOpPackageName(), getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the IP information for the current default network. + * + * @return a {@link LinkProperties} object describing the IP info + * for the current default network, or {@code null} if there + * is no current default network. + * + * {@hide} + * @deprecated please use {@link #getLinkProperties(Network)} on the return + * value of {@link #getActiveNetwork()} instead. In particular, + * this method will return non-null LinkProperties even if the + * app is blocked by policy from using this network. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 109783091) + public LinkProperties getActiveLinkProperties() { + try { + return mService.getActiveLinkProperties(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the IP information for a given network type. + * + * @param networkType the network type of interest. + * @return a {@link LinkProperties} object describing the IP info + * for the given networkType, or {@code null} if there is + * no current default network. + * + * {@hide} + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks}, + * {@link #getNetworkInfo(android.net.Network)}, and + * {@link #getLinkProperties(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public LinkProperties getLinkProperties(int networkType) { + try { + return mService.getLinkPropertiesForType(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the {@link LinkProperties} for the given {@link Network}. This + * will return {@code null} if the network is unknown. + * + * @param network The {@link Network} object identifying the network in question. + * @return The {@link LinkProperties} for the network, or {@code null}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public LinkProperties getLinkProperties(@Nullable Network network) { + try { + return mService.getLinkProperties(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Redact {@link LinkProperties} for a given package + * + * Returns an instance of the given {@link LinkProperties} appropriately redacted to send to the + * given package, considering its permissions. + * + * @param lp A {@link LinkProperties} which will be redacted. + * @param uid The target uid. + * @param packageName The name of the package, for appops logging. + * @return A redacted {@link LinkProperties} which is appropriate to send to the given uid, + * or null if the uid lacks the ACCESS_NETWORK_STATE permission. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) + @Nullable + public LinkProperties getRedactedLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid, + @NonNull String packageName) { + try { + return mService.getRedactedLinkPropertiesForPackage( + lp, uid, packageName, getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the {@link NetworkCapabilities} for the given {@link Network}, or null. + * + * This will remove any location sensitive data in the returned {@link NetworkCapabilities}. + * Some {@link TransportInfo} instances like {@link android.net.wifi.WifiInfo} contain location + * sensitive information. To retrieve this location sensitive information (subject to + * the caller's location permissions), use a {@link NetworkCallback} with the + * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag instead. + * + * This method returns {@code null} if the network is unknown or if the |network| argument + * is null. + * + * @param network The {@link Network} object identifying the network in question. + * @return The {@link NetworkCapabilities} for the network, or {@code null}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) { + try { + return mService.getNetworkCapabilities( + network, mContext.getOpPackageName(), getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Redact {@link NetworkCapabilities} for a given package. + * + * Returns an instance of {@link NetworkCapabilities} that is appropriately redacted to send + * to the given package, considering its permissions. If the passed capabilities contain + * location-sensitive information, they will be redacted to the correct degree for the location + * permissions of the app (COARSE or FINE), and will blame the UID accordingly for retrieving + * that level of location. If the UID holds no location permission, the returned object will + * contain no location-sensitive information and the UID is not blamed. + * + * @param nc A {@link NetworkCapabilities} instance which will be redacted. + * @param uid The target uid. + * @param packageName The name of the package, for appops logging. + * @return A redacted {@link NetworkCapabilities} which is appropriate to send to the given uid, + * or null if the uid lacks the ACCESS_NETWORK_STATE permission. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) + @Nullable + public NetworkCapabilities getRedactedNetworkCapabilitiesForPackage( + @NonNull NetworkCapabilities nc, + int uid, @NonNull String packageName) { + try { + return mService.getRedactedNetworkCapabilitiesForPackage(nc, uid, packageName, + getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets a URL that can be used for resolving whether a captive portal is present. + * 1. This URL should respond with a 204 response to a GET request to indicate no captive + * portal is present. + * 2. This URL must be HTTP as redirect responses are used to find captive portal + * sign-in pages. Captive portals cannot respond to HTTPS requests with redirects. + * + * The system network validation may be using different strategies to detect captive portals, + * so this method does not necessarily return a URL used by the system. It only returns a URL + * that may be relevant for other components trying to detect captive portals. + * + * @hide + * @deprecated This API returns a URL which is not guaranteed to be one of the URLs used by the + * system. + */ + @Deprecated + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public String getCaptivePortalServerUrl() { + try { + return mService.getCaptivePortalServerUrl(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Tells the underlying networking system that the caller wants to + * begin using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature to be used + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + * + * @deprecated Deprecated in favor of the cleaner + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public int startUsingNetworkFeature(int networkType, String feature) { + checkLegacyRoutingApiAccess(); + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " + + feature); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED; + } + + NetworkRequest request = null; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) { + Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest); + renewRequestLocked(l); + if (l.currentNetwork != null) { + return DEPRECATED_PHONE_CONSTANT_APN_ALREADY_ACTIVE; + } else { + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED; + } + } + + request = requestNetworkForFeatureLocked(netCap); + } + if (request != null) { + Log.d(TAG, "starting startUsingNetworkFeature for request " + request); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED; + } else { + Log.d(TAG, " request Failed"); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED; + } + } + + /** + * Tells the underlying networking system that the caller is finished + * using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature that is no longer needed + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + * + * @deprecated Deprecated in favor of the cleaner + * {@link #unregisterNetworkCallback(NetworkCallback)} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public int stopUsingNetworkFeature(int networkType, String feature) { + checkLegacyRoutingApiAccess(); + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " + + feature); + return -1; + } + + if (removeRequestForFeature(netCap)) { + Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature); + } + return 1; + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) { + if (networkType == TYPE_MOBILE) { + switch (feature) { + case "enableCBS": + return networkCapabilitiesForType(TYPE_MOBILE_CBS); + case "enableDUN": + case "enableDUNAlways": + return networkCapabilitiesForType(TYPE_MOBILE_DUN); + case "enableFOTA": + return networkCapabilitiesForType(TYPE_MOBILE_FOTA); + case "enableHIPRI": + return networkCapabilitiesForType(TYPE_MOBILE_HIPRI); + case "enableIMS": + return networkCapabilitiesForType(TYPE_MOBILE_IMS); + case "enableMMS": + return networkCapabilitiesForType(TYPE_MOBILE_MMS); + case "enableSUPL": + return networkCapabilitiesForType(TYPE_MOBILE_SUPL); + default: + return null; + } + } else if (networkType == TYPE_WIFI && "p2p".equals(feature)) { + return networkCapabilitiesForType(TYPE_WIFI_P2P); + } + return null; + } + + private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) { + if (netCap == null) return TYPE_NONE; + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) { + return TYPE_MOBILE_CBS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) { + return TYPE_MOBILE_IMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) { + return TYPE_MOBILE_FOTA; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) { + return TYPE_MOBILE_DUN; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) { + return TYPE_MOBILE_SUPL; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) { + return TYPE_MOBILE_MMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + return TYPE_MOBILE_HIPRI; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) { + return TYPE_WIFI_P2P; + } + return TYPE_NONE; + } + + private static class LegacyRequest { + NetworkCapabilities networkCapabilities; + NetworkRequest networkRequest; + int expireSequenceNumber; + Network currentNetwork; + int delay = -1; + + private void clearDnsBinding() { + if (currentNetwork != null) { + currentNetwork = null; + setProcessDefaultNetworkForHostResolution(null); + } + } + + NetworkCallback networkCallback = new NetworkCallback() { + @Override + public void onAvailable(Network network) { + currentNetwork = network; + Log.d(TAG, "startUsingNetworkFeature got Network:" + network); + setProcessDefaultNetworkForHostResolution(network); + } + @Override + public void onLost(Network network) { + if (network.equals(currentNetwork)) clearDnsBinding(); + Log.d(TAG, "startUsingNetworkFeature lost Network:" + network); + } + }; + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private static final HashMap sLegacyRequests = + new HashMap<>(); + + private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) { + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) return l.networkRequest; + } + return null; + } + + private void renewRequestLocked(LegacyRequest l) { + l.expireSequenceNumber++; + Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber); + sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay); + } + + private void expireRequest(NetworkCapabilities netCap, int sequenceNum) { + int ourSeqNum = -1; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l == null) return; + ourSeqNum = l.expireSequenceNumber; + if (l.expireSequenceNumber == sequenceNum) removeRequestForFeature(netCap); + } + Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum); + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) { + int delay = -1; + int type = legacyTypeForNetworkCapabilities(netCap); + try { + delay = mService.getRestoreDefaultNetworkDelay(type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + LegacyRequest l = new LegacyRequest(); + l.networkCapabilities = netCap; + l.delay = delay; + l.expireSequenceNumber = 0; + l.networkRequest = sendRequestForNetwork( + netCap, l.networkCallback, 0, REQUEST, type, getDefaultHandler()); + if (l.networkRequest == null) return null; + sLegacyRequests.put(netCap, l); + sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay); + return l.networkRequest; + } + + private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) { + if (delay >= 0) { + Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay); + CallbackHandler handler = getDefaultHandler(); + Message msg = handler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap); + handler.sendMessageDelayed(msg, delay); + } + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private boolean removeRequestForFeature(NetworkCapabilities netCap) { + final LegacyRequest l; + synchronized (sLegacyRequests) { + l = sLegacyRequests.remove(netCap); + } + if (l == null) return false; + unregisterNetworkCallback(l.networkCallback); + l.clearDnsBinding(); + return true; + } + + private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray(); + static { + sLegacyTypeToTransport.put(TYPE_MOBILE, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_CBS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_DUN, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_FOTA, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_IMS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_MMS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_SUPL, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_WIFI, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_WIFI_P2P, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_BLUETOOTH, NetworkCapabilities.TRANSPORT_BLUETOOTH); + sLegacyTypeToTransport.put(TYPE_ETHERNET, NetworkCapabilities.TRANSPORT_ETHERNET); + } + + private static final SparseIntArray sLegacyTypeToCapability = new SparseIntArray(); + static { + sLegacyTypeToCapability.put(TYPE_MOBILE_CBS, NetworkCapabilities.NET_CAPABILITY_CBS); + sLegacyTypeToCapability.put(TYPE_MOBILE_DUN, NetworkCapabilities.NET_CAPABILITY_DUN); + sLegacyTypeToCapability.put(TYPE_MOBILE_FOTA, NetworkCapabilities.NET_CAPABILITY_FOTA); + sLegacyTypeToCapability.put(TYPE_MOBILE_IMS, NetworkCapabilities.NET_CAPABILITY_IMS); + sLegacyTypeToCapability.put(TYPE_MOBILE_MMS, NetworkCapabilities.NET_CAPABILITY_MMS); + sLegacyTypeToCapability.put(TYPE_MOBILE_SUPL, NetworkCapabilities.NET_CAPABILITY_SUPL); + sLegacyTypeToCapability.put(TYPE_WIFI_P2P, NetworkCapabilities.NET_CAPABILITY_WIFI_P2P); + } + + /** + * Given a legacy type (TYPE_WIFI, ...) returns a NetworkCapabilities + * instance suitable for registering a request or callback. Throws an + * IllegalArgumentException if no mapping from the legacy type to + * NetworkCapabilities is known. + * + * @deprecated Types are deprecated. Use {@link NetworkCallback} or {@link NetworkRequest} + * to find the network instead. + * @hide + */ + public static NetworkCapabilities networkCapabilitiesForType(int type) { + final NetworkCapabilities nc = new NetworkCapabilities(); + + // Map from type to transports. + final int NOT_FOUND = -1; + final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND); + if (transport == NOT_FOUND) { + throw new IllegalArgumentException("unknown legacy type: " + type); + } + nc.addTransportType(transport); + + // Map from type to capabilities. + nc.addCapability(sLegacyTypeToCapability.get( + type, NetworkCapabilities.NET_CAPABILITY_INTERNET)); + nc.maybeMarkCapabilitiesRestricted(); + return nc; + } + + /** @hide */ + public static class PacketKeepaliveCallback { + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public PacketKeepaliveCallback() { + } + /** The requested keepalive was successfully started. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onStarted() {} + /** The keepalive was resumed after being paused by the system. */ + public void onResumed() {} + /** The keepalive was successfully stopped. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onStopped() {} + /** The keepalive was paused automatically by the system. */ + public void onPaused() {} + /** An error occurred. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onError(int error) {} + } + + /** + * Allows applications to request that the system periodically send specific packets on their + * behalf, using hardware offload to save battery power. + * + * To request that the system send keepalives, call one of the methods that return a + * {@link ConnectivityManager.PacketKeepalive} object, such as {@link #startNattKeepalive}, + * passing in a non-null callback. If the callback is successfully started, the callback's + * {@code onStarted} method will be called. If an error occurs, {@code onError} will be called, + * specifying one of the {@code ERROR_*} constants in this class. + * + * To stop an existing keepalive, call {@link PacketKeepalive#stop}. The system will call + * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or + * {@link PacketKeepaliveCallback#onError} if an error occurred. + * + * @deprecated Use {@link SocketKeepalive} instead. + * + * @hide + */ + public class PacketKeepalive { + + private static final String TAG = "PacketKeepalive"; + + /** @hide */ + public static final int SUCCESS = 0; + + /** @hide */ + public static final int NO_KEEPALIVE = -1; + + /** @hide */ + public static final int BINDER_DIED = -10; + + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = -20; + /** The specified IP addresses are invalid. For example, the specified source IP address is + * not configured on the specified {@code Network}. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + /** The requested port is invalid. */ + public static final int ERROR_INVALID_PORT = -22; + /** The packet length is invalid (e.g., too long). */ + public static final int ERROR_INVALID_LENGTH = -23; + /** The packet transmission interval is invalid (e.g., too short). */ + public static final int ERROR_INVALID_INTERVAL = -24; + + /** The hardware does not support this request. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = -30; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; + + /** The NAT-T destination port for IPsec */ + public static final int NATT_PORT = 4500; + + /** The minimum interval in seconds between keepalive packet transmissions */ + public static final int MIN_INTERVAL = 10; + + private final Network mNetwork; + private final ISocketKeepaliveCallback mCallback; + private final ExecutorService mExecutor; + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void stop() { + try { + mExecutor.execute(() -> { + try { + mService.stopKeepalive(mCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } catch (RejectedExecutionException e) { + // The internal executor has already stopped due to previous event. + } + } + + private PacketKeepalive(Network network, PacketKeepaliveCallback callback) { + Objects.requireNonNull(network, "network cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); + mNetwork = network; + mExecutor = Executors.newSingleThreadExecutor(); + mCallback = new ISocketKeepaliveCallback.Stub() { + @Override + public void onStarted() { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + callback.onStarted(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onResumed() { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + callback.onResumed(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onStopped() { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + callback.onStopped(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + mExecutor.shutdown(); + } + + @Override + public void onPaused() { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + callback.onPaused(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + mExecutor.shutdown(); + } + + @Override + public void onError(int error) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + callback.onError(error); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + mExecutor.shutdown(); + } + + @Override + public void onDataReceived() { + // PacketKeepalive is only used for Nat-T keepalive and as such does not invoke + // this callback when data is received. + } + }; + } + } + + /** + * Starts an IPsec NAT-T keepalive packet with the specified parameters. + * + * @deprecated Use {@link #createSocketKeepalive} instead. + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public PacketKeepalive startNattKeepalive( + Network network, int intervalSeconds, PacketKeepaliveCallback callback, + InetAddress srcAddr, int srcPort, InetAddress dstAddr) { + final PacketKeepalive k = new PacketKeepalive(network, callback); + try { + mService.startNattKeepalive(network, intervalSeconds, k.mCallback, + srcAddr.getHostAddress(), srcPort, dstAddr.getHostAddress()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + return k; + } + + // Construct an invalid fd. + private ParcelFileDescriptor createInvalidFd() { + final int invalidFd = -1; + return ParcelFileDescriptor.adoptFd(invalidFd); + } + + /** + * Request that keepalives be started on a IPsec NAT-T socket. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + **/ + public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull UdpEncapsulationSocket socket, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + // Dup is needed here as the pfd inside the socket is owned by the IpSecService, + // which cannot be obtained by the app process. + dup = ParcelFileDescriptor.dup(socket.getFileDescriptor()); + } catch (IOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new NattSocketKeepalive(mService, network, dup, socket.getResourceId(), source, + destination, executor, callback); + } + + /** + * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called + * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}. + * + * @param network The {@link Network} the socket is on. + * @param pfd The {@link ParcelFileDescriptor} that needs to be kept alive. The provided + * {@link ParcelFileDescriptor} must be bound to a port and the keepalives will be sent + * from that port. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. The + * keepalive packets will always be sent to port 4500 of the given {@code destination}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public @NonNull SocketKeepalive createNattKeepalive(@NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + // TODO: Consider remove unnecessary dup. + dup = pfd.dup(); + } catch (IOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new NattSocketKeepalive(mService, network, dup, + -1 /* Unused */, source, destination, executor, callback); + } + + /** + * Request that keepalives be started on a TCP socket. The socket must be established. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param executor The executor on which callback will be invoked. This implementation assumes + * the provided {@link Executor} runs the callbacks in sequence with no + * concurrency. Failing this, no guarantee of correctness can be made. It is + * the responsibility of the caller to ensure the executor provides this + * guarantee. A simple way of creating such an executor is with the standard + * tool {@code Executors.newSingleThreadExecutor}. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull Socket socket, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + dup = ParcelFileDescriptor.fromSocket(socket); + } catch (UncheckedIOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new TcpSocketKeepalive(mService, network, dup, executor, callback); + } + + /** + * Get the supported keepalive count for each transport configured in resource overlays. + * + * @return An array of supported keepalive count for each transport type. + * @hide + */ + @RequiresPermission(anyOf = { android.Manifest.permission.NETWORK_SETTINGS, + // CTS 13 used QUERY_ALL_PACKAGES to get the resource value, which was implemented + // as below in KeepaliveUtils. Also allow that permission so that KeepaliveUtils can + // use this method and avoid breaking released CTS. Apps that have this permission + // can query the resource themselves anyway. + android.Manifest.permission.QUERY_ALL_PACKAGES }) + public int[] getSupportedKeepalives() { + try { + return mService.getSupportedKeepalives(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + * + * @deprecated Deprecated in favor of the + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public boolean requestRouteToHost(int networkType, int hostAddress) { + return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress)); + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + * @hide + * @deprecated Deprecated in favor of the {@link #requestNetwork} and + * {@link #bindProcessToNetwork} API. + */ + @Deprecated + @UnsupportedAppUsage + @SystemApi(client = MODULE_LIBRARIES) + public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { + checkLegacyRoutingApiAccess(); + try { + return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress(), + mContext.getOpPackageName(), getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the context's attribution tag + */ + // TODO: Remove method and replace with direct call once R code is pushed to AOSP + private @Nullable String getAttributionTag() { + return mContext.getAttributionTag(); + } + + /** + * Returns the value of the setting for background data usage. If false, + * applications should not use the network if the application is not in the + * foreground. Developers should respect this setting, and check the value + * of this before performing any background data operations. + *

+ * All applications that have background services that use the network + * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}. + *

+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of + * background data depends on several combined factors, and this method will + * always return {@code true}. Instead, when background data is unavailable, + * {@link #getActiveNetworkInfo()} will now appear disconnected. + * + * @return Whether background data usage is allowed. + */ + @Deprecated + public boolean getBackgroundDataSetting() { + // assume that background data is allowed; final authority is + // NetworkInfo which may be blocked. + return true; + } + + /** + * Sets the value of the setting for background data usage. + * + * @param allowBackgroundData Whether an application should use data while + * it is in the background. + * + * @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING + * @see #getBackgroundDataSetting() + * @hide + */ + @Deprecated + @UnsupportedAppUsage + public void setBackgroundDataSetting(boolean allowBackgroundData) { + // ignored + } + + /** + * @hide + * @deprecated Talk to TelephonyManager directly + */ + @Deprecated + @UnsupportedAppUsage + public boolean getMobileDataEnabled() { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + if (tm != null) { + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId); + boolean retVal = tm.createForSubscriptionId(subId).isDataEnabled(); + Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId + + " retVal=" + retVal); + return retVal; + } + Log.d("ConnectivityManager", "getMobileDataEnabled()- remote exception retVal=false"); + return false; + } + + /** + * Callback for use with {@link ConnectivityManager#addDefaultNetworkActiveListener} + * to find out when the system default network has gone in to a high power state. + */ + public interface OnNetworkActiveListener { + /** + * Called on the main thread of the process to report that the current data network + * has become active, and it is now a good time to perform any pending network + * operations. Note that this listener only tells you when the network becomes + * active; if at any other time you want to know whether it is active (and thus okay + * to initiate network traffic), you can retrieve its instantaneous state with + * {@link ConnectivityManager#isDefaultNetworkActive}. + */ + void onNetworkActive(); + } + + @GuardedBy("mNetworkActivityListeners") + private final ArrayMap + mNetworkActivityListeners = new ArrayMap<>(); + + /** + * Start listening to reports when the system's default data network is active, meaning it is + * a good time to perform network traffic. Use {@link #isDefaultNetworkActive()} + * to determine the current state of the system's default network after registering the + * listener. + *

+ * If the process default network has been set with + * {@link ConnectivityManager#bindProcessToNetwork} this function will not + * reflect the process's default, but the system default. + * + * @param l The listener to be told when the network is active. + */ + public void addDefaultNetworkActiveListener(final OnNetworkActiveListener l) { + final INetworkActivityListener rl = new INetworkActivityListener.Stub() { + @Override + public void onNetworkActive() throws RemoteException { + l.onNetworkActive(); + } + }; + + synchronized (mNetworkActivityListeners) { + try { + mService.registerNetworkActivityListener(rl); + mNetworkActivityListeners.put(l, rl); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Remove network active listener previously registered with + * {@link #addDefaultNetworkActiveListener}. + * + * @param l Previously registered listener. + */ + public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) { + synchronized (mNetworkActivityListeners) { + final INetworkActivityListener rl = mNetworkActivityListeners.get(l); + if (rl == null) { + throw new IllegalArgumentException("Listener was not registered."); + } + try { + mService.unregisterNetworkActivityListener(rl); + mNetworkActivityListeners.remove(l); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Return whether the data network is currently active. An active network means that + * it is currently in a high power state for performing data transmission. On some + * types of networks, it may be expensive to move and stay in such a state, so it is + * more power efficient to batch network traffic together when the radio is already in + * this state. This method tells you whether right now is currently a good time to + * initiate network traffic, as the network is already active. + */ + public boolean isDefaultNetworkActive() { + try { + return mService.isDefaultNetworkActive(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * {@hide} + */ + public ConnectivityManager(Context context, IConnectivityManager service) { + this(context, service, true /* newStatic */); + } + + private ConnectivityManager(Context context, IConnectivityManager service, boolean newStatic) { + mContext = Objects.requireNonNull(context, "missing context"); + mService = Objects.requireNonNull(service, "missing IConnectivityManager"); + // sInstance is accessed without a lock, so it may actually be reassigned several times with + // different ConnectivityManager, but that's still OK considering its usage. + if (sInstance == null && newStatic) { + final Context appContext = mContext.getApplicationContext(); + // Don't create static ConnectivityManager instance again to prevent infinite loop. + // If the application context is null, we're either in the system process or + // it's the application context very early in app initialization. In both these + // cases, the passed-in Context will not be freed, so it's safe to pass it to the + // service. http://b/27532714 . + sInstance = new ConnectivityManager(appContext != null ? appContext : context, service, + false /* newStatic */); + } + } + + /** {@hide} */ + @UnsupportedAppUsage + public static ConnectivityManager from(Context context) { + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + /** @hide */ + public NetworkRequest getDefaultRequest() { + try { + // This is not racy as the default request is final in ConnectivityService. + return mService.getDefaultRequest(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Check if the package is allowed to write settings. This also records that such an access + * happened. + * + * @return {@code true} iff the package is allowed to write settings. + */ + // TODO: Remove method and replace with direct call once R code is pushed to AOSP + private static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, + @NonNull String callingPackage, @Nullable String callingAttributionTag, + boolean throwException) { + return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage, + callingAttributionTag, throwException); + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + @Deprecated + static ConnectivityManager getInstanceOrNull() { + return sInstance; + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + @Deprecated + @UnsupportedAppUsage + private static ConnectivityManager getInstance() { + if (getInstanceOrNull() == null) { + throw new IllegalStateException("No ConnectivityManager yet constructed"); + } + return getInstanceOrNull(); + } + + /** + * Get the set of tetherable, available interfaces. This list is limited by + * device configuration and current interface existence. + * + * @return an array of 0 or more Strings of tetherable interface names. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableIfaces() { + return getTetheringManager().getTetherableIfaces(); + } + + /** + * Get the set of tethered interfaces. + * + * @return an array of 0 or more String of currently tethered interface names. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetheredIfaces() { + return getTetheringManager().getTetheredIfaces(); + } + + /** + * Get the set of interface names which attempted to tether but + * failed. Re-attempting to tether may cause them to reset to the Tethered + * state. Alternatively, causing the interface to be destroyed and recreated + * may cause them to reset to the available state. + * {@link ConnectivityManager#getLastTetherError} can be used to get more + * information on the cause of the errors. + * + * @return an array of 0 or more String indicating the interface names + * which failed to tether. + * + * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetheringErroredIfaces() { + return getTetheringManager().getTetheringErroredIfaces(); + } + + /** + * Get the set of tethered dhcp ranges. + * + * @deprecated This method is not supported. + * TODO: remove this function when all of clients are removed. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + @Deprecated + public String[] getTetheredDhcpRanges() { + throw new UnsupportedOperationException("getTetheredDhcpRanges is not supported"); + } + + /** + * Attempt to tether the named interface. This will set up a dhcp server + * on the interface, forward and NAT IP packets and forward DNS requests + * to the best active upstream network interface. Note that if no upstream + * IP network interface is available, dhcp will still run and traffic will be + * allowed between the tethered devices and this device, though upstream net + * access will of course fail until an upstream network interface becomes + * active. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

WARNING: New clients should not use this function. The only usages should be in PanService + * and WifiStateMachine which need direct access. All other clients should use + * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning + * logic.

+ * + * @param iface the interface name to tether. + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * @deprecated Use {@link TetheringManager#startTethering} instead + * + * {@hide} + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated + public int tether(String iface) { + return getTetheringManager().tether(iface); + } + + /** + * Stop tethering the named interface. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

WARNING: New clients should not use this function. The only usages should be in PanService + * and WifiStateMachine which need direct access. All other clients should use + * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning + * logic.

+ * + * @param iface the interface name to untether. + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * + * {@hide} + */ + @UnsupportedAppUsage + @Deprecated + public int untether(String iface) { + return getTetheringManager().untether(iface); + } + + /** + * Check if the device allows for tethering. It may be disabled via + * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or + * due to device configuration. + * + *

If this app does not have permission to use this API, it will always + * return false rather than throw an exception.

+ * + *

If the device has a hotspot provisioning app, the caller is required to hold the + * {@link android.Manifest.permission.TETHER_PRIVILEGED} permission.

+ * + *

Otherwise, this method requires the caller to hold the ability to modify system + * settings as determined by {@link android.provider.Settings.System#canWrite}.

+ * + * @return a boolean - {@code true} indicating Tethering is supported. + * + * @deprecated Use {@link TetheringEventCallback#onTetheringSupported(boolean)} instead. + * {@hide} + */ + @SystemApi + @RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED, + android.Manifest.permission.WRITE_SETTINGS}) + public boolean isTetheringSupported() { + return getTetheringManager().isTetheringSupported(); + } + + /** + * Callback for use with {@link #startTethering} to find out whether tethering succeeded. + * + * @deprecated Use {@link TetheringManager.StartTetheringCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + public static abstract class OnStartTetheringCallback { + /** + * Called when tethering has been successfully started. + */ + public void onTetheringStarted() {} + + /** + * Called when starting tethering failed. + */ + public void onTetheringFailed() {} + } + + /** + * Convenient overload for + * {@link #startTethering(int, boolean, OnStartTetheringCallback, Handler)} which passes a null + * handler to run on the current thread's {@link Looper}. + * + * @deprecated Use {@link TetheringManager#startTethering} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void startTethering(int type, boolean showProvisioningUi, + final OnStartTetheringCallback callback) { + startTethering(type, showProvisioningUi, callback, null); + } + + /** + * Runs tether provisioning for the given type if needed and then starts tethering if + * the check succeeds. If no carrier provisioning is required for tethering, tethering is + * enabled immediately. If provisioning fails, tethering will not be enabled. It also + * schedules tether provisioning re-checks if appropriate. + * + * @param type The type of tethering to start. Must be one of + * {@link ConnectivityManager.TETHERING_WIFI}, + * {@link ConnectivityManager.TETHERING_USB}, or + * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * @param showProvisioningUi a boolean indicating to show the provisioning app UI if there + * is one. This should be true the first time this function is called and also any time + * the user can see this UI. It gives users information from their carrier about the + * check failing and how they can sign up for tethering if possible. + * @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller + * of the result of trying to tether. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * + * @deprecated Use {@link TetheringManager#startTethering} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void startTethering(int type, boolean showProvisioningUi, + final OnStartTetheringCallback callback, Handler handler) { + Objects.requireNonNull(callback, "OnStartTetheringCallback cannot be null."); + + final Executor executor = new Executor() { + @Override + public void execute(Runnable command) { + if (handler == null) { + command.run(); + } else { + handler.post(command); + } + } + }; + + final StartTetheringCallback tetheringCallback = new StartTetheringCallback() { + @Override + public void onTetheringStarted() { + callback.onTetheringStarted(); + } + + @Override + public void onTetheringFailed(final int error) { + callback.onTetheringFailed(); + } + }; + + final TetheringRequest request = new TetheringRequest.Builder(type) + .setShouldShowEntitlementUi(showProvisioningUi).build(); + + getTetheringManager().startTethering(request, executor, tetheringCallback); + } + + /** + * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if + * applicable. + * + * @param type The type of tethering to stop. Must be one of + * {@link ConnectivityManager.TETHERING_WIFI}, + * {@link ConnectivityManager.TETHERING_USB}, or + * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * + * @deprecated Use {@link TetheringManager#stopTethering} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void stopTethering(int type) { + getTetheringManager().stopTethering(type); + } + + /** + * Callback for use with {@link registerTetheringEventCallback} to find out tethering + * upstream status. + * + * @deprecated Use {@link TetheringManager#OnTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + public abstract static class OnTetheringEventCallback { + + /** + * Called when tethering upstream changed. This can be called multiple times and can be + * called any time. + * + * @param network the {@link Network} of tethering upstream. Null means tethering doesn't + * have any upstream. + */ + public void onUpstreamChanged(@Nullable Network network) {} + } + + @GuardedBy("mTetheringEventCallbacks") + private final ArrayMap + mTetheringEventCallbacks = new ArrayMap<>(); + + /** + * Start listening to tethering change events. Any new added callback will receive the last + * tethering status right away. If callback is registered when tethering has no upstream or + * disabled, {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called + * with a null argument. The same callback object cannot be registered twice. + * + * @param executor the executor on which callback will be invoked. + * @param callback the callback to be called when tethering has change events. + * + * @deprecated Use {@link TetheringManager#registerTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void registerTetheringEventCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull final OnTetheringEventCallback callback) { + Objects.requireNonNull(callback, "OnTetheringEventCallback cannot be null."); + + final TetheringEventCallback tetherCallback = + new TetheringEventCallback() { + @Override + public void onUpstreamChanged(@Nullable Network network) { + callback.onUpstreamChanged(network); + } + }; + + synchronized (mTetheringEventCallbacks) { + mTetheringEventCallbacks.put(callback, tetherCallback); + getTetheringManager().registerTetheringEventCallback(executor, tetherCallback); + } + } + + /** + * Remove tethering event callback previously registered with + * {@link #registerTetheringEventCallback}. + * + * @param callback previously registered callback. + * + * @deprecated Use {@link TetheringManager#unregisterTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void unregisterTetheringEventCallback( + @NonNull final OnTetheringEventCallback callback) { + Objects.requireNonNull(callback, "The callback must be non-null"); + synchronized (mTetheringEventCallbacks) { + final TetheringEventCallback tetherCallback = + mTetheringEventCallbacks.remove(callback); + getTetheringManager().unregisterTetheringEventCallback(tetherCallback); + } + } + + + /** + * Get the list of regular expressions that define any tetherable + * USB network interfaces. If USB tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable usb interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableUsbRegexs() { + return getTetheringManager().getTetherableUsbRegexs(); + } + + /** + * Get the list of regular expressions that define any tetherable + * Wifi network interfaces. If Wifi tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable wifi interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableWifiRegexs() { + return getTetheringManager().getTetherableWifiRegexs(); + } + + /** + * Get the list of regular expressions that define any tetherable + * Bluetooth network interfaces. If Bluetooth tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable bluetooth interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged( + *TetheringManager.TetheringInterfaceRegexps)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableBluetoothRegexs() { + return getTetheringManager().getTetherableBluetoothRegexs(); + } + + /** + * Attempt to both alter the mode of USB and Tethering of USB. A + * utility method to deal with some of the complexity of USB - will + * attempt to switch to Rndis and subsequently tether the resulting + * interface on {@code true} or turn off tethering and switch off + * Rndis on {@code false}. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param enable a boolean - {@code true} to enable tethering + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * @deprecated Use {@link TetheringManager#startTethering} instead + * + * {@hide} + */ + @UnsupportedAppUsage + @Deprecated + public int setUsbTethering(boolean enable) { + return getTetheringManager().setUsbTethering(enable); + } + + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_NO_ERROR}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_NO_ERROR = 0; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNKNOWN_IFACE = + TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_SERVICE_UNAVAIL}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_SERVICE_UNAVAIL = + TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNSUPPORTED}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNSUPPORTED = TetheringManager.TETHER_ERROR_UNSUPPORTED; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNAVAIL_IFACE}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNAVAIL_IFACE = + TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_INTERNAL_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_MASTER_ERROR = + TetheringManager.TETHER_ERROR_INTERNAL_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_TETHER_IFACE_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_TETHER_IFACE_ERROR = + TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNTETHER_IFACE_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = + TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENABLE_FORWARDING_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_ENABLE_NAT_ERROR = + TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_DISABLE_FORWARDING_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_DISABLE_NAT_ERROR = + TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_IFACE_CFG_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_IFACE_CFG_ERROR = + TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_PROVISIONING_FAILED}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_PROVISION_FAILED = 11; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_DHCPSERVER_ERROR = + TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENTITLEMENT_UNKNOWN}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; + + /** + * Get a more detailed error code after a Tethering or Untethering + * request asynchronously failed. + * + * @param iface The name of the interface of interest + * @return error The error code of the last error tethering or untethering the named + * interface + * + * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated + public int getLastTetherError(String iface) { + int error = getTetheringManager().getLastTetherError(iface); + if (error == TetheringManager.TETHER_ERROR_UNKNOWN_TYPE) { + // TETHER_ERROR_UNKNOWN_TYPE was introduced with TetheringManager and has never been + // returned by ConnectivityManager. Convert it to the legacy TETHER_ERROR_UNKNOWN_IFACE + // instead. + error = TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; + } + return error; + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + TETHER_ERROR_NO_ERROR, + TETHER_ERROR_PROVISION_FAILED, + TETHER_ERROR_ENTITLEMENT_UNKONWN, + }) + public @interface EntitlementResultCode { + } + + /** + * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether + * entitlement succeeded. + * + * @deprecated Use {@link TetheringManager#OnTetheringEntitlementResultListener} instead. + * @hide + */ + @SystemApi + @Deprecated + public interface OnTetheringEntitlementResultListener { + /** + * Called to notify entitlement result. + * + * @param resultCode an int value of entitlement result. It may be one of + * {@link #TETHER_ERROR_NO_ERROR}, + * {@link #TETHER_ERROR_PROVISION_FAILED}, or + * {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}. + */ + void onTetheringEntitlementResult(@EntitlementResultCode int resultCode); + } + + /** + * Get the last value of the entitlement check on this downstream. If the cached value is + * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, this just returns the + * cached value. Otherwise, a UI-based entitlement check will be performed. It is not + * guaranteed that the UI-based entitlement check will complete in any specific time period + * and it may in fact never complete. Any successful entitlement check the platform performs for + * any reason will update the cached value. + * + * @param type the downstream type of tethering. Must be one of + * {@link #TETHERING_WIFI}, + * {@link #TETHERING_USB}, or + * {@link #TETHERING_BLUETOOTH}. + * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check. + * @param executor the executor on which callback will be invoked. + * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to + * notify the caller of the result of entitlement check. The listener may be called zero + * or one time. + * @deprecated Use {@link TetheringManager#requestLatestTetheringEntitlementResult} instead. + * {@hide} + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi, + @NonNull @CallbackExecutor Executor executor, + @NonNull final OnTetheringEntitlementResultListener listener) { + Objects.requireNonNull(listener, "TetheringEntitlementResultListener cannot be null."); + ResultReceiver wrappedListener = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + listener.onTetheringEntitlementResult(resultCode); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + getTetheringManager().requestLatestTetheringEntitlementResult(type, wrappedListener, + showEntitlementUi); + } + + /** + * Report network connectivity status. This is currently used only + * to alter status bar UI. + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#STATUS_BAR}. + * + * @param networkType The type of network you want to report on + * @param percentage The quality of the connection 0 is bad, 100 is good + * @deprecated Types are deprecated. Use {@link #reportNetworkConnectivity} instead. + * {@hide} + */ + public void reportInetCondition(int networkType, int percentage) { + printStackTrace(); + try { + mService.reportInetCondition(networkType, percentage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Report a problem network to the framework. This provides a hint to the system + * that there might be connectivity problems on this network and may cause + * the framework to re-evaluate network connectivity and/or switch to another + * network. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both + * working and non-working connectivity. + */ + @Deprecated + public void reportBadNetwork(@Nullable Network network) { + printStackTrace(); + try { + // One of these will be ignored because it matches system's current state. + // The other will trigger the necessary reevaluation. + mService.reportNetworkConnectivity(network, true); + mService.reportNetworkConnectivity(network, false); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Report to the framework whether a network has working connectivity. + * This provides a hint to the system that a particular network is providing + * working connectivity or not. In response the framework may re-evaluate + * the network's connectivity and might take further action thereafter. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @param hasConnectivity {@code true} if the application was able to successfully access the + * Internet using {@code network} or {@code false} if not. + */ + public void reportNetworkConnectivity(@Nullable Network network, boolean hasConnectivity) { + printStackTrace(); + try { + mService.reportNetworkConnectivity(network, hasConnectivity); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set a network-independent global HTTP proxy. + * + * This sets an HTTP proxy that applies to all networks and overrides any network-specific + * proxy. If set, HTTP libraries that are proxy-aware will use this global proxy when + * accessing any network, regardless of what the settings for that network are. + * + * Note that HTTP proxies are by nature typically network-dependent, and setting a global + * proxy is likely to break networking on multiple networks. This method is only meant + * for device policy clients looking to do general internal filtering or similar use cases. + * + * @see #getGlobalProxy + * @see LinkProperties#getHttpProxy + * + * @param p A {@link ProxyInfo} object defining the new global HTTP proxy. Calling this + * method with a {@code null} value will clear the global HTTP proxy. + * @hide + */ + // Used by Device Policy Manager to set the global proxy. + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setGlobalProxy(@Nullable final ProxyInfo p) { + try { + mService.setGlobalProxy(p); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve any network-independent global HTTP proxy. + * + * @return {@link ProxyInfo} for the current global HTTP proxy or {@code null} + * if no global HTTP proxy is set. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @Nullable + public ProxyInfo getGlobalProxy() { + try { + return mService.getGlobalProxy(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve the global HTTP proxy, or if no global HTTP proxy is set, a + * network-specific HTTP proxy. If {@code network} is null, the + * network-specific proxy returned is the proxy of the default active + * network. + * + * @return {@link ProxyInfo} for the current global HTTP proxy, or if no + * global HTTP proxy is set, {@code ProxyInfo} for {@code network}, + * or when {@code network} is {@code null}, + * the {@code ProxyInfo} for the default active network. Returns + * {@code null} when no proxy applies or the caller doesn't have + * permission to use {@code network}. + * @hide + */ + public ProxyInfo getProxyForNetwork(Network network) { + try { + return mService.getProxyForNetwork(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the current default HTTP proxy settings. If a global proxy is set it will be returned, + * otherwise if this process is bound to a {@link Network} using + * {@link #bindProcessToNetwork} then that {@code Network}'s proxy is returned, otherwise + * the default network's proxy is returned. + * + * @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no + * HTTP proxy is active. + */ + @Nullable + public ProxyInfo getDefaultProxy() { + return getProxyForNetwork(getBoundNetworkForProcess()); + } + + /** + * Returns whether the hardware supports the given network type. + * + * This doesn't indicate there is coverage or such a network is available, just whether the + * hardware supports it. For example a GSM phone without a SIM card will return {@code true} + * for mobile data, but a WiFi only tablet would return {@code false}. + * + * @param networkType The network type we'd like to check + * @return {@code true} if supported, else {@code false} + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public boolean isNetworkSupported(int networkType) { + try { + return mService.isNetworkSupported(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns if the currently active data network is metered. A network is + * classified as metered when the user is sensitive to heavy data usage on + * that connection due to monetary costs, data limitations or + * battery/performance issues. You should check this before doing large + * data transfers, and warn the user or delay the operation until another + * network is available. + * + * @return {@code true} if large transfers should be avoided, otherwise + * {@code false}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public boolean isActiveNetworkMetered() { + try { + return mService.isActiveNetworkMetered(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set sign in error notification to visible or invisible + * + * @hide + * @deprecated Doesn't properly deal with multiple connected networks of the same type. + */ + @Deprecated + public void setProvisioningNotificationVisible(boolean visible, int networkType, + String action) { + try { + mService.setProvisioningNotificationVisible(visible, networkType, action); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set the value for enabling/disabling airplane mode + * + * @param enable whether to enable airplane mode or not + * + * @hide + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_AIRPLANE_MODE, + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK}) + @SystemApi + public void setAirplaneMode(boolean enable) { + try { + mService.setAirplaneMode(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers the specified {@link NetworkProvider}. + * Each listener must only be registered once. The listener can be unregistered with + * {@link #unregisterNetworkProvider}. + * + * @param provider the provider to register + * @return the ID of the provider. This ID must be used by the provider when registering + * {@link android.net.NetworkAgent}s. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public int registerNetworkProvider(@NonNull NetworkProvider provider) { + if (provider.getProviderId() != NetworkProvider.ID_NONE) { + throw new IllegalStateException("NetworkProviders can only be registered once"); + } + + try { + int providerId = mService.registerNetworkProvider(provider.getMessenger(), + provider.getName()); + provider.setProviderId(providerId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return provider.getProviderId(); + } + + /** + * Unregisters the specified NetworkProvider. + * + * @param provider the provider to unregister + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void unregisterNetworkProvider(@NonNull NetworkProvider provider) { + try { + mService.unregisterNetworkProvider(provider.getMessenger()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + provider.setProviderId(NetworkProvider.ID_NONE); + } + + /** + * Register or update a network offer with ConnectivityService. + * + * ConnectivityService keeps track of offers made by the various providers and matches + * them to networking requests made by apps or the system. A callback identifies an offer + * uniquely, and later calls with the same callback update the offer. The provider supplies a + * score and the capabilities of the network it might be able to bring up ; these act as + * filters used by ConnectivityService to only send those requests that can be fulfilled by the + * provider. + * + * The provider is under no obligation to be able to bring up the network it offers at any + * given time. Instead, this mechanism is meant to limit requests received by providers + * to those they actually have a chance to fulfill, as providers don't have a way to compare + * the quality of the network satisfying a given request to their own offer. + * + * An offer can be updated by calling this again with the same callback object. This is + * similar to calling unofferNetwork and offerNetwork again, but will only update the + * provider with the changes caused by the changes in the offer. + * + * @param provider The provider making this offer. + * @param score The prospective score of the network. + * @param caps The prospective capabilities of the network. + * @param callback The callback to call when this offer is needed or unneeded. + * @hide exposed via the NetworkProvider class. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void offerNetwork(@NonNull final int providerId, + @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, + @NonNull final INetworkOfferCallback callback) { + try { + mService.offerNetwork(providerId, + Objects.requireNonNull(score, "null score"), + Objects.requireNonNull(caps, "null caps"), + Objects.requireNonNull(callback, "null callback")); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Withdraw a network offer made with {@link #offerNetwork}. + * + * @param callback The callback passed at registration time. This must be the same object + * that was passed to {@link #offerNetwork} + * @hide exposed via the NetworkProvider class. + */ + public void unofferNetwork(@NonNull final INetworkOfferCallback callback) { + try { + mService.unofferNetwork(Objects.requireNonNull(callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide exposed via the NetworkProvider class. */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { + try { + mService.declareNetworkRequestUnfulfillable(request); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return Network corresponding to NetworkAgent. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, + @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId) { + return registerNetworkAgent(na, ni, lp, nc, null /* localNetworkConfig */, score, config, + providerId); + } + + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return Network corresponding to NetworkAgent. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, int providerId) { + try { + return mService.registerNetworkAgent(na, ni, lp, nc, score, localNetworkConfig, config, + providerId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Base class for {@code NetworkRequest} callbacks. Used for notifications about network + * changes. Should be extended by applications wanting notifications. + * + * A {@code NetworkCallback} is registered by calling + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)}, + * or {@link #registerDefaultNetworkCallback(NetworkCallback)}. A {@code NetworkCallback} is + * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}. + * A {@code NetworkCallback} should be registered at most once at any time. + * A {@code NetworkCallback} that has been unregistered can be registered again. + */ + public static class NetworkCallback { + /** + * No flags associated with this callback. + * @hide + */ + public static final int FLAG_NONE = 0; + + /** + * Inclusion of this flag means location-sensitive redaction requests keeping location info. + * + * Some objects like {@link NetworkCapabilities} may contain location-sensitive information. + * Prior to Android 12, this information is always returned to apps holding the appropriate + * permission, possibly noting that the app has used location. + *

In Android 12 and above, by default the sent objects do not contain any location + * information, even if the app holds the necessary permissions, and the system does not + * take note of location usage by the app. Apps can request that location information is + * included, in which case the system will check location permission and the location + * toggle state, and take note of location usage by the app if any such information is + * returned. + * + * Use this flag to include any location sensitive data in {@link NetworkCapabilities} sent + * via {@link #onCapabilitiesChanged(Network, NetworkCapabilities)}. + *

+ * These include: + *

  • Some transport info instances (retrieved via + * {@link NetworkCapabilities#getTransportInfo()}) like {@link android.net.wifi.WifiInfo} + * contain location sensitive information. + *
  • OwnerUid (retrieved via {@link NetworkCapabilities#getOwnerUid()} is location + * sensitive for wifi suggestor apps (i.e using + * {@link android.net.wifi.WifiNetworkSuggestion WifiNetworkSuggestion}).
  • + *

    + *

    + * Note: + *

  • Retrieving this location sensitive information (subject to app's location + * permissions) will be noted by system.
  • + *
  • Without this flag any {@link NetworkCapabilities} provided via the callback does + * not include location sensitive information. + */ + // Note: Some existing fields which are location sensitive may still be included without + // this flag if the app targets SDK < S (to maintain backwards compatibility). + public static final int FLAG_INCLUDE_LOCATION_INFO = 1 << 0; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = "FLAG_", value = { + FLAG_NONE, + FLAG_INCLUDE_LOCATION_INFO + }) + public @interface Flag { } + + /** + * All the valid flags for error checking. + */ + private static final int VALID_FLAGS = FLAG_INCLUDE_LOCATION_INFO; + + public NetworkCallback() { + this(FLAG_NONE); + } + + public NetworkCallback(@Flag int flags) { + if ((flags & VALID_FLAGS) != flags) { + throw new IllegalArgumentException("Invalid flags"); + } + mFlags = flags; + } + + /** + * Called when the framework connects to a new network to evaluate whether it satisfies this + * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable} + * callback. There is no guarantee that this new network will satisfy any requests, or that + * the network will stay connected for longer than the time necessary to evaluate it. + *

    + * Most applications should not act on this callback, and should instead use + * {@link #onAvailable}. This callback is intended for use by applications that can assist + * the framework in properly evaluating the network — for example, an application that + * can automatically log in to a captive portal without user intervention. + * + * @param network The {@link Network} of the network that is being evaluated. + * + * @hide + */ + public void onPreCheck(@NonNull Network network) {} + + /** + * Called when the framework connects and has declared a new network ready for use. + * This callback may be called more than once if the {@link Network} that is + * satisfying the request changes. + * + * @param network The {@link Network} of the satisfying network. + * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network. + * @param linkProperties The {@link LinkProperties} of the satisfying network. + * @param localInfo The {@link LocalNetworkInfo} of the satisfying network, or null + * if this network is not a local network. + * @param blocked Whether access to the {@link Network} is blocked due to system policy. + * @hide + */ + public final void onAvailable(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, + @Nullable LocalNetworkInfo localInfo, + @BlockedReason int blocked) { + // Internally only this method is called when a new network is available, and + // it calls the callback in the same way and order that older versions used + // to call so as not to change the behavior. + onAvailable(network, networkCapabilities, linkProperties, blocked != 0); + if (null != localInfo) onLocalNetworkInfoChanged(network, localInfo); + onBlockedStatusChanged(network, blocked); + } + + /** + * Legacy variant of onAvailable that takes a boolean blocked reason. + * + * This method has never been public API, but it's not final, so there may be apps that + * implemented it and rely on it being called. Do our best not to break them. + * Note: such apps will also get a second call to onBlockedStatusChanged immediately after + * this method is called. There does not seem to be a way to avoid this. + * TODO: add a compat check to move apps off this method, and eventually stop calling it. + * + * @hide + */ + public void onAvailable(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, + boolean blocked) { + onAvailable(network); + if (!networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)) { + onNetworkSuspended(network); + } + onCapabilitiesChanged(network, networkCapabilities); + onLinkPropertiesChanged(network, linkProperties); + // No call to onBlockedStatusChanged here. That is done by the caller. + } + + /** + * Called when the framework connects and has declared a new network ready for use. + * + *

    For callbacks registered with {@link #registerNetworkCallback}, multiple networks may + * be available at the same time, and onAvailable will be called for each of these as they + * appear. + * + *

    For callbacks registered with {@link #requestNetwork} and + * {@link #registerDefaultNetworkCallback}, this means the network passed as an argument + * is the new best network for this request and is now tracked by this callback ; this + * callback will no longer receive method calls about other networks that may have been + * passed to this method previously. The previously-best network may have disconnected, or + * it may still be around and the newly-best network may simply be better. + * + *

    Starting with {@link android.os.Build.VERSION_CODES#O}, this will always immediately + * be followed by a call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} + * then by a call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call + * to {@link #onBlockedStatusChanged(Network, boolean)}. + * + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions (there is no guarantee the objects + * returned by these methods will be current). Instead, wait for a call to + * {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link #onLinkPropertiesChanged(Network, LinkProperties)} whose arguments are guaranteed + * to be well-ordered with respect to other callbacks. + * + * @param network The {@link Network} of the satisfying network. + */ + public void onAvailable(@NonNull Network network) {} + + /** + * Called when the network is about to be lost, typically because there are no outstanding + * requests left for it. This may be paired with a {@link NetworkCallback#onAvailable} call + * with the new replacement network for graceful handover. This method is not guaranteed + * to be called before {@link NetworkCallback#onLost} is called, for example in case a + * network is suddenly disconnected. + * + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions ; calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} that is about to be lost. + * @param maxMsToLive The time in milliseconds the system intends to keep the network + * connected for graceful handover; note that the network may still + * suffer a hard loss at any time. + */ + public void onLosing(@NonNull Network network, int maxMsToLive) {} + + /** + * Called when a network disconnects or otherwise no longer satisfies this request or + * callback. + * + *

    If the callback was registered with requestNetwork() or + * registerDefaultNetworkCallback(), it will only be invoked against the last network + * returned by onAvailable() when that network is lost and no other network satisfies + * the criteria of the request. + * + *

    If the callback was registered with registerNetworkCallback() it will be called for + * each network which no longer satisfies the criteria of the callback. + * + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions ; calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} lost. + */ + public void onLost(@NonNull Network network) {} + + /** + * Called if no network is found within the timeout time specified in + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the + * requested network request cannot be fulfilled (whether or not a timeout was + * specified). When this callback is invoked the associated + * {@link NetworkRequest} will have already been removed and released, as if + * {@link #unregisterNetworkCallback(NetworkCallback)} had been called. + */ + public void onUnavailable() {} + + /** + * Called when the network corresponding to this request changes capabilities but still + * satisfies the requested criteria. + * + *

    Starting with {@link android.os.Build.VERSION_CODES#O} this method is guaranteed + * to be called immediately after {@link #onAvailable}. + * + *

    Do NOT call {@link #getLinkProperties(Network)} or other synchronous + * ConnectivityManager methods in this callback as this is prone to race conditions : + * calling these methods while in a callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose capabilities have changed. + * @param networkCapabilities The new {@link NetworkCapabilities} for this + * network. + */ + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities) {} + + /** + * Called when the network corresponding to this request changes {@link LinkProperties}. + * + *

    Starting with {@link android.os.Build.VERSION_CODES#O} this method is guaranteed + * to be called immediately after {@link #onAvailable}. + * + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or other synchronous + * ConnectivityManager methods in this callback as this is prone to race conditions : + * calling these methods while in a callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose link properties have changed. + * @param linkProperties The new {@link LinkProperties} for this network. + */ + public void onLinkPropertiesChanged(@NonNull Network network, + @NonNull LinkProperties linkProperties) {} + + /** + * Called when there is a change in the {@link LocalNetworkInfo} for this network. + * + * This is only called for local networks, that is those with the + * NET_CAPABILITY_LOCAL_NETWORK network capability. + * + * @param network the {@link Network} whose local network info has changed. + * @param localNetworkInfo the new {@link LocalNetworkInfo} for this network. + * @hide + */ + public void onLocalNetworkInfoChanged(@NonNull Network network, + @NonNull LocalNetworkInfo localNetworkInfo) {} + + /** + * Called when the network the framework connected to for this request suspends data + * transmission temporarily. + * + *

    This generally means that while the TCP connections are still live temporarily + * network data fails to transfer. To give a specific example, this is used on cellular + * networks to mask temporary outages when driving through a tunnel, etc. In general this + * means read operations on sockets on this network will block once the buffers are + * drained, and write operations will block once the buffers are full. + * + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions (there is no guarantee the objects + * returned by these methods will be current). + * + * @hide + */ + public void onNetworkSuspended(@NonNull Network network) {} + + /** + * Called when the network the framework connected to for this request + * returns from a {@link NetworkInfo.State#SUSPENDED} state. This should always be + * preceded by a matching {@link NetworkCallback#onNetworkSuspended} call. + + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions : calling these methods while in a + * callback may return an outdated or even a null object. + * + * @hide + */ + public void onNetworkResumed(@NonNull Network network) {} + + /** + * Called when access to the specified network is blocked or unblocked. + * + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions : calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose blocked status has changed. + * @param blocked The blocked status of this {@link Network}. + */ + public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {} + + /** + * Called when access to the specified network is blocked or unblocked, or the reason for + * access being blocked changes. + * + * If a NetworkCallback object implements this method, + * {@link #onBlockedStatusChanged(Network, boolean)} will not be called. + * + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions : calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose blocked status has changed. + * @param blocked The blocked status of this {@link Network}. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public void onBlockedStatusChanged(@NonNull Network network, @BlockedReason int blocked) { + onBlockedStatusChanged(network, blocked != 0); + } + + private NetworkRequest networkRequest; + private final int mFlags; + } + + /** + * Constant error codes used by ConnectivityService to communicate about failures and errors + * across a Binder boundary. + * @hide + */ + public interface Errors { + int TOO_MANY_REQUESTS = 1; + } + + /** @hide */ + public static class TooManyRequestsException extends RuntimeException {} + + private static RuntimeException convertServiceException(ServiceSpecificException e) { + switch (e.errorCode) { + case Errors.TOO_MANY_REQUESTS: + return new TooManyRequestsException(); + default: + Log.w(TAG, "Unknown service error code " + e.errorCode); + return new RuntimeException(e); + } + } + + /** @hide */ + public static final int CALLBACK_PRECHECK = 1; + /** @hide */ + public static final int CALLBACK_AVAILABLE = 2; + /** @hide arg1 = TTL */ + public static final int CALLBACK_LOSING = 3; + /** @hide */ + public static final int CALLBACK_LOST = 4; + /** @hide */ + public static final int CALLBACK_UNAVAIL = 5; + /** @hide */ + public static final int CALLBACK_CAP_CHANGED = 6; + /** @hide */ + public static final int CALLBACK_IP_CHANGED = 7; + /** @hide obj = NetworkCapabilities, arg1 = seq number */ + private static final int EXPIRE_LEGACY_REQUEST = 8; + /** @hide */ + public static final int CALLBACK_SUSPENDED = 9; + /** @hide */ + public static final int CALLBACK_RESUMED = 10; + /** @hide */ + public static final int CALLBACK_BLK_CHANGED = 11; + /** @hide */ + public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12; + + /** @hide */ + public static String getCallbackName(int whichCallback) { + switch (whichCallback) { + case CALLBACK_PRECHECK: return "CALLBACK_PRECHECK"; + case CALLBACK_AVAILABLE: return "CALLBACK_AVAILABLE"; + case CALLBACK_LOSING: return "CALLBACK_LOSING"; + case CALLBACK_LOST: return "CALLBACK_LOST"; + case CALLBACK_UNAVAIL: return "CALLBACK_UNAVAIL"; + case CALLBACK_CAP_CHANGED: return "CALLBACK_CAP_CHANGED"; + case CALLBACK_IP_CHANGED: return "CALLBACK_IP_CHANGED"; + case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST"; + case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED"; + case CALLBACK_RESUMED: return "CALLBACK_RESUMED"; + case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED"; + case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: return "CALLBACK_LOCAL_NETWORK_INFO_CHANGED"; + default: + return Integer.toString(whichCallback); + } + } + + private static class CallbackHandler extends Handler { + private static final String TAG = "ConnectivityManager.CallbackHandler"; + private static final boolean DBG = false; + + CallbackHandler(Looper looper) { + super(looper); + } + + CallbackHandler(Handler handler) { + this(Objects.requireNonNull(handler, "Handler cannot be null.").getLooper()); + } + + @Override + public void handleMessage(Message message) { + if (message.what == EXPIRE_LEGACY_REQUEST) { + // the sInstance can't be null because to send this message a ConnectivityManager + // instance must have been created prior to creating the thread on which this + // Handler is running. + sInstance.expireRequest((NetworkCapabilities) message.obj, message.arg1); + return; + } + + final NetworkRequest request = getObject(message, NetworkRequest.class); + final Network network = getObject(message, Network.class); + final NetworkCallback callback; + synchronized (sCallbacks) { + callback = sCallbacks.get(request); + if (callback == null) { + Log.w(TAG, + "callback not found for " + getCallbackName(message.what) + " message"); + return; + } + if (message.what == CALLBACK_UNAVAIL) { + sCallbacks.remove(request); + callback.networkRequest = ALREADY_UNREGISTERED; + } + } + if (DBG) { + Log.d(TAG, getCallbackName(message.what) + " for network " + network); + } + + switch (message.what) { + case CALLBACK_PRECHECK: { + callback.onPreCheck(network); + break; + } + case CALLBACK_AVAILABLE: { + NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); + LinkProperties lp = getObject(message, LinkProperties.class); + LocalNetworkInfo lni = getObject(message, LocalNetworkInfo.class); + callback.onAvailable(network, cap, lp, lni, message.arg1); + break; + } + case CALLBACK_LOSING: { + callback.onLosing(network, message.arg1); + break; + } + case CALLBACK_LOST: { + callback.onLost(network); + break; + } + case CALLBACK_UNAVAIL: { + callback.onUnavailable(); + break; + } + case CALLBACK_CAP_CHANGED: { + NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); + callback.onCapabilitiesChanged(network, cap); + break; + } + case CALLBACK_IP_CHANGED: { + LinkProperties lp = getObject(message, LinkProperties.class); + callback.onLinkPropertiesChanged(network, lp); + break; + } + case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: { + final LocalNetworkInfo info = getObject(message, LocalNetworkInfo.class); + callback.onLocalNetworkInfoChanged(network, info); + break; + } + case CALLBACK_SUSPENDED: { + callback.onNetworkSuspended(network); + break; + } + case CALLBACK_RESUMED: { + callback.onNetworkResumed(network); + break; + } + case CALLBACK_BLK_CHANGED: { + callback.onBlockedStatusChanged(network, message.arg1); + } + } + } + + private T getObject(Message msg, Class c) { + return (T) msg.getData().getParcelable(c.getSimpleName()); + } + } + + private CallbackHandler getDefaultHandler() { + synchronized (sCallbacks) { + if (sCallbackHandler == null) { + sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper()); + } + return sCallbackHandler; + } + } + + private static final HashMap sCallbacks = new HashMap<>(); + private static CallbackHandler sCallbackHandler; + + private NetworkRequest sendRequestForNetwork(int asUid, NetworkCapabilities need, + NetworkCallback callback, int timeoutMs, NetworkRequest.Type reqType, int legacyType, + CallbackHandler handler) { + printStackTrace(); + checkCallbackNotNull(callback); + if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) { + throw new IllegalArgumentException("null NetworkCapabilities"); + } + final NetworkRequest request; + final String callingPackageName = mContext.getOpPackageName(); + try { + synchronized(sCallbacks) { + if (callback.networkRequest != null + && callback.networkRequest != ALREADY_UNREGISTERED) { + // TODO: throw exception instead and enforce 1:1 mapping of callbacks + // and requests (http://b/20701525). + Log.e(TAG, "NetworkCallback was already registered"); + } + Messenger messenger = new Messenger(handler); + Binder binder = new Binder(); + final int callbackFlags = callback.mFlags; + if (reqType == LISTEN) { + request = mService.listenForNetwork( + need, messenger, binder, callbackFlags, callingPackageName, + getAttributionTag()); + } else { + request = mService.requestNetwork( + asUid, need, reqType.ordinal(), messenger, timeoutMs, binder, + legacyType, callbackFlags, callingPackageName, getAttributionTag()); + } + if (request != null) { + sCallbacks.put(request, callback); + } + callback.networkRequest = request; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + return request; + } + + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, + int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType, + legacyType, handler); + } + + /** + * Helper function to request a network with a particular legacy type. + * + * This API is only for use in internal system code that requests networks with legacy type and + * relies on CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use + * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead. + * + * @param request {@link NetworkRequest} describing this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + * @param legacyType to specify the network type(#TYPE_*). + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * + * @hide + */ + @SystemApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void requestNetwork(@NonNull NetworkRequest request, + int timeoutMs, int legacyType, @NonNull Handler handler, + @NonNull NetworkCallback networkCallback) { + if (legacyType == TYPE_NONE) { + throw new IllegalArgumentException("TYPE_NONE is meaningless legacy type"); + } + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler); + } + + /** + * Request a network to satisfy a set of {@link NetworkCapabilities}. + * + *

    This method will attempt to find the best network that matches the passed + * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the + * criteria. The platform will evaluate which network is the best at its own discretion. + * Throughput, latency, cost per byte, policy, user preference and other considerations + * may be factored in the decision of what is considered the best network. + * + *

    As long as this request is outstanding, the platform will try to maintain the best network + * matching this request, while always attempting to match the request to a better network if + * possible. If a better match is found, the platform will switch this request to the now-best + * network and inform the app of the newly best network by invoking + * {@link NetworkCallback#onAvailable(Network)} on the provided callback. Note that the platform + * will not try to maintain any other network than the best one currently matching the request: + * a network not matching any network request may be disconnected at any time. + * + *

    For example, an application could use this method to obtain a connected cellular network + * even if the device currently has a data connection over Ethernet. This may cause the cellular + * radio to consume additional power. Or, an application could inform the system that it wants + * a network supporting sending MMSes and have the system let it know about the currently best + * MMS-supporting network through the provided {@link NetworkCallback}. + * + *

    The status of the request can be followed by listening to the various callbacks described + * in {@link NetworkCallback}. The {@link Network} object passed to the callback methods can be + * used to direct traffic to the network (although accessing some networks may be subject to + * holding specific permissions). Callers will learn about the specific characteristics of the + * network through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)}. The methods of the + * provided {@link NetworkCallback} will only be invoked due to changes in the best network + * matching the request at any given time; therefore when a better network matching the request + * becomes available, the {@link NetworkCallback#onAvailable(Network)} method is called + * with the new network after which no further updates are given about the previously-best + * network, unless it becomes the best again at some later time. All callbacks are invoked + * in order on the same thread, which by default is a thread created by the framework running + * in the app. + * See {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} to change where the + * callbacks are invoked. + * + *

    This{@link NetworkRequest} will live until released via + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits, at + * which point the system may let go of the network at any time. + * + *

    A version of this method which takes a timeout is + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}, that an app can use to only + * wait for a limited amount of time for the network to become unavailable. + * + *

    It is presently unsupported to request a network with mutable + * {@link NetworkCapabilities} such as + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

    This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

    + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * The callback is invoked on the default internal Handler. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback) { + requestNetwork(request, networkCallback, getDefaultHandler()); + } + + /** + * Request a network to satisfy a set of {@link NetworkCapabilities}. + * + * This method behaves identically to {@link #requestNetwork(NetworkRequest, NetworkCallback)} + * but runs all the callbacks on the passed Handler. + * + *

    This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler); + } + + /** + * Request a network to satisfy a set of {@link NetworkCapabilities}, limited + * by a timeout. + * + * This function behaves identically to the non-timed-out version + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network + * is not found within the given time (in milliseconds) the + * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be + * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does + * not have to be released if timed-out (it is automatically released). Unregistering a + * request that timed out is not an error. + * + *

    Do not use this method to poll for the existence of specific networks (e.g. with a small + * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided + * for that purpose. Calling this method will attempt to bring up the requested network. + * + *

    This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, int timeoutMs) { + checkTimeout(timeoutMs); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, TYPE_NONE, + getDefaultHandler()); + } + + /** + * Request a network to satisfy a set of {@link NetworkCapabilities}, limited + * by a timeout. + * + * This method behaves identically to + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} but runs all the callbacks + * on the passed Handler. + * + *

    This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable} is called. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) { + checkTimeout(timeoutMs); + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, TYPE_NONE, cbHandler); + } + + /** + * The lookup key for a {@link Network} object included with the intent after + * successfully finding a network for the applications request. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + *

    + * Note that if you intend to invoke {@link Network#openConnection(java.net.URL)} + * then you must get a ConnectivityManager instance before doing so. + */ + public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; + + /** + * The lookup key for a {@link NetworkRequest} object included with the intent after + * successfully finding a network for the applications request. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; + + + /** + * Request a network to satisfy a set of {@link NetworkCapabilities}. + * + * This function behaves identically to the version that takes a NetworkCallback, but instead + * of {@link NetworkCallback} a {@link PendingIntent} is used. This means + * the request may outlive the calling application and get called back when a suitable + * network is found. + *

    + * The operation is an Intent broadcast that goes to a broadcast receiver that + * you registered with {@link Context#registerReceiver} or through the + * <receiver> tag in an AndroidManifest.xml file + *

    + * The operation Intent is delivered with two extras, a {@link Network} typed + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing + * the original requests parameters. It is important to create a new, + * {@link NetworkCallback} based request before completing the processing of the + * Intent to reserve the network or it will be released shortly after the Intent + * is processed. + *

    + * If there is already a request for this Intent registered (with the equality of + * two Intents defined by {@link Intent#filterEquals}), then it will be removed and + * replaced by this one, effectively releasing the previous {@link NetworkRequest}. + *

    + * The request may be released normally by calling + * {@link #releaseNetworkRequest(android.app.PendingIntent)}. + *

    It is presently unsupported to request a network with either + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with {@link #unregisterNetworkCallback(PendingIntent)} + * or {@link #releaseNetworkRequest(PendingIntent)}. + * + *

    This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

    + * + * @param request {@link NetworkRequest} describing this request. + * @param operation Action to perform when the network is available (corresponds + * to the {@link NetworkCallback#onAvailable} call. Typically + * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.pendingRequestForNetwork( + request.networkCapabilities, operation, mContext.getOpPackageName(), + getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + } + + /** + * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} + *

    + * This method has the same behavior as + * {@link #unregisterNetworkCallback(android.app.PendingIntent)} with respect to + * releasing network resources and disconnecting. + * + * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the + * PendingIntent passed to + * {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the + * corresponding NetworkRequest you'd like to remove. Cannot be null. + */ + public void releaseNetworkRequest(@NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.releasePendingNetworkRequest(operation); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static void checkPendingIntentNotNull(PendingIntent intent) { + Objects.requireNonNull(intent, "PendingIntent cannot be null."); + } + + private static void checkCallbackNotNull(NetworkCallback callback) { + Objects.requireNonNull(callback, "null NetworkCallback"); + } + + private static void checkTimeout(int timeoutMs) { + if (timeoutMs <= 0) { + throw new IllegalArgumentException("timeoutMs must be strictly positive."); + } + } + + /** + * Registers to receive notifications about all networks which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * The callback is invoked on the default internal Handler. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback) { + registerNetworkCallback(request, networkCallback, getDefaultHandler()); + } + + /** + * Registers to receive notifications about all networks which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler); + } + + /** + * Registers a PendingIntent to be sent when a network is available which satisfies the given + * {@link NetworkRequest}. + * + * This function behaves identically to the version that takes a NetworkCallback, but instead + * of {@link NetworkCallback} a {@link PendingIntent} is used. This means + * the request may outlive the calling application and get called back when a suitable + * network is found. + *

    + * The operation is an Intent broadcast that goes to a broadcast receiver that + * you registered with {@link Context#registerReceiver} or through the + * <receiver> tag in an AndroidManifest.xml file + *

    + * The operation Intent is delivered with two extras, a {@link Network} typed + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing + * the original requests parameters. + *

    + * If there is already a request for this Intent registered (with the equality of + * two Intents defined by {@link Intent#filterEquals}), then it will be removed and + * replaced by this one, effectively releasing the previous {@link NetworkRequest}. + *

    + * The request may be released normally by calling + * {@link #unregisterNetworkCallback(android.app.PendingIntent)}. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with {@link #unregisterNetworkCallback(PendingIntent)} + * or {@link #releaseNetworkRequest(PendingIntent)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param operation Action to perform when the network is available (corresponds + * to the {@link NetworkCallback#onAvailable} call. Typically + * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.pendingListenForNetwork( + request.networkCapabilities, operation, mContext.getOpPackageName(), + getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + } + + /** + * Registers to receive notifications about changes in the application's default network. This + * may be a physical network or a virtual network, such as a VPN that applies to the + * application. The callbacks will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * application's default network changes. + * The callback is invoked on the default internal Handler. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback) { + registerDefaultNetworkCallback(networkCallback, getDefaultHandler()); + } + + /** + * Registers to receive notifications about changes in the application's default network. This + * may be a physical network or a virtual network, such as a VPN that applies to the + * application. The callbacks will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * application's default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, + @NonNull Handler handler) { + registerDefaultNetworkCallbackForUid(Process.INVALID_UID, networkCallback, handler); + } + + /** + * Registers to receive notifications about changes in the default network for the specified + * UID. This may be a physical network or a virtual network, such as a VPN that applies to the + * UID. The callbacks will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param uid the UID for which to track default network changes. + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * UID's default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint({"ExecutorRegistration", "PairedRegistration"}) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void registerDefaultNetworkCallbackForUid(int uid, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(uid, null /* need */, networkCallback, 0 /* timeoutMs */, + TRACK_DEFAULT, TYPE_NONE, cbHandler); + } + + /** + * Registers to receive notifications about changes in the system default network. The callbacks + * will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + * This method should not be used to determine networking state seen by applications, because in + * many cases, most or even all application traffic may not use the default network directly, + * and traffic from different applications may go on different networks by default. As an + * example, if a VPN is connected, traffic from all applications might be sent through the VPN + * and not onto the system default network. Applications or system components desiring to do + * determine network state as seen by applications should use other methods such as + * {@link #registerDefaultNetworkCallback(NetworkCallback, Handler)}. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * system default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint({"ExecutorRegistration", "PairedRegistration"}) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}) + public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, + @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, + TRACK_SYSTEM_DEFAULT, TYPE_NONE, cbHandler); + } + + /** + * Registers to receive notifications about the best matching network which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * {@link #registerNetworkCallback} and its variants and {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @SuppressLint("ExecutorRegistration") + public void registerBestMatchingNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + final NetworkCapabilities nc = request.networkCapabilities; + final CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(nc, networkCallback, 0, LISTEN_FOR_BEST, TYPE_NONE, cbHandler); + } + + /** + * Requests bandwidth update for a given {@link Network} and returns whether the update request + * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying + * network connection for updated bandwidth information. The caller will be notified via + * {@link ConnectivityManager.NetworkCallback} if there is an update. Notice that this + * method assumes that the caller has previously called + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} to listen for network + * changes. + * + * @param network {@link Network} specifying which network you're interested. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean requestBandwidthUpdate(@NonNull Network network) { + try { + return mService.requestBandwidthUpdate(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a {@code NetworkCallback} and possibly releases networks originating from + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls. + * If the given {@code NetworkCallback} had previously been used with {@code #requestNetwork}, + * any networks that the device brought up only to satisfy that request will be disconnected. + * + * Notifications that would have triggered that {@code NetworkCallback} will immediately stop + * triggering it as soon as this call returns. + * + * @param networkCallback The {@link NetworkCallback} used when making the request. + */ + public void unregisterNetworkCallback(@NonNull NetworkCallback networkCallback) { + printStackTrace(); + checkCallbackNotNull(networkCallback); + final List reqs = new ArrayList<>(); + // Find all requests associated to this callback and stop callback triggers immediately. + // Callback is reusable immediately. http://b/20701525, http://b/35921499. + synchronized (sCallbacks) { + if (networkCallback.networkRequest == null) { + throw new IllegalArgumentException("NetworkCallback was not registered"); + } + if (networkCallback.networkRequest == ALREADY_UNREGISTERED) { + Log.d(TAG, "NetworkCallback was already unregistered"); + return; + } + for (Map.Entry e : sCallbacks.entrySet()) { + if (e.getValue() == networkCallback) { + reqs.add(e.getKey()); + } + } + // TODO: throw exception if callback was registered more than once (http://b/20701525). + for (NetworkRequest r : reqs) { + try { + mService.releaseNetworkRequest(r); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + // Only remove mapping if rpc was successful. + sCallbacks.remove(r); + } + networkCallback.networkRequest = ALREADY_UNREGISTERED; + } + } + + /** + * Unregisters a callback previously registered via + * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}. + * + * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the + * PendingIntent passed to + * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}. + * Cannot be null. + */ + public void unregisterNetworkCallback(@NonNull PendingIntent operation) { + releaseNetworkRequest(operation); + } + + /** + * Informs the system whether it should switch to {@code network} regardless of whether it is + * validated or not. If {@code accept} is true, and the network was explicitly selected by the + * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become + * the system default network regardless of any other network that's currently connected. If + * {@code always} is true, then the choice is remembered, so that the next time the user + * connects to this network, the system will switch to it. + * + * @param network The network to accept. + * @param accept Whether to accept the network even if unvalidated. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + public void setAcceptUnvalidated(@NonNull Network network, boolean accept, boolean always) { + try { + mService.setAcceptUnvalidated(network, accept, always); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Informs the system whether it should consider the network as validated even if it only has + * partial connectivity. If {@code accept} is true, then the network will be considered as + * validated even if connectivity is only partial. If {@code always} is true, then the choice + * is remembered, so that the next time the user connects to this network, the system will + * switch to it. + * + * @param network The network to accept. + * @param accept Whether to consider the network as validated even if it has partial + * connectivity. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + public void setAcceptPartialConnectivity(@NonNull Network network, boolean accept, + boolean always) { + try { + mService.setAcceptPartialConnectivity(network, accept, always); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Informs the system to penalize {@code network}'s score when it becomes unvalidated. This is + * only meaningful if the system is configured not to penalize such networks, e.g., if the + * {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code + * NETWORK_AVOID_BAD_WIFI setting is unset}. + * + * @param network The network to accept. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + public void setAvoidUnvalidated(@NonNull Network network) { + try { + mService.setAvoidUnvalidated(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Temporarily allow bad Wi-Fi to override {@code config_networkAvoidBadWifi} configuration. + * + * @param timeMs The expired current time. The value should be set within a limited time from + * now. + * + * @hide + */ + public void setTestAllowBadWifiUntil(long timeMs) { + try { + mService.setTestAllowBadWifiUntil(timeMs); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests that the system open the captive portal app on the specified network. + * + *

    This is to be used on networks where a captive portal was detected, as per + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}. + * + * @param network The network to log into. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void startCaptivePortalApp(@NonNull Network network) { + try { + mService.startCaptivePortalApp(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests that the system open the captive portal app with the specified extras. + * + *

    This endpoint is exclusively for use by the NetworkStack and is protected by the + * corresponding permission. + * @param network Network on which the captive portal was detected. + * @param appExtras Extras to include in the app start intent. + * @hide + */ + @SystemApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void startCaptivePortalApp(@NonNull Network network, @NonNull Bundle appExtras) { + try { + mService.startCaptivePortalAppInternal(network, appExtras); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Determine whether the device is configured to avoid bad Wi-Fi. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public boolean shouldAvoidBadWifi() { + try { + return mService.shouldAvoidBadWifi(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * It is acceptable to briefly use multipath data to provide seamless connectivity for + * time-sensitive user-facing operations when the system default network is temporarily + * unresponsive. The amount of data should be limited (less than one megabyte for every call to + * this method), and the operation should be infrequent to ensure that data usage is limited. + * + * An example of such an operation might be a time-sensitive foreground activity, such as a + * voice command, that the user is performing while walking out of range of a Wi-Fi network. + */ + public static final int MULTIPATH_PREFERENCE_HANDOVER = 1 << 0; + + /** + * It is acceptable to use small amounts of multipath data on an ongoing basis to provide + * a backup channel for traffic that is primarily going over another network. + * + * An example might be maintaining backup connections to peers or servers for the purpose of + * fast fallback if the default network is temporarily unresponsive or disconnects. The traffic + * on backup paths should be negligible compared to the traffic on the main path. + */ + public static final int MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1; + + /** + * It is acceptable to use metered data to improve network latency and performance. + */ + public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2; + + /** + * Return value to use for unmetered networks. On such networks we currently set all the flags + * to true. + * @hide + */ + public static final int MULTIPATH_PREFERENCE_UNMETERED = + MULTIPATH_PREFERENCE_HANDOVER | + MULTIPATH_PREFERENCE_RELIABILITY | + MULTIPATH_PREFERENCE_PERFORMANCE; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = { + MULTIPATH_PREFERENCE_HANDOVER, + MULTIPATH_PREFERENCE_RELIABILITY, + MULTIPATH_PREFERENCE_PERFORMANCE, + }) + public @interface MultipathPreference { + } + + /** + * Provides a hint to the calling application on whether it is desirable to use the + * multinetwork APIs (e.g., {@link Network#openConnection}, {@link Network#bindSocket}, etc.) + * for multipath data transfer on this network when it is not the system default network. + * Applications desiring to use multipath network protocols should call this method before + * each such operation. + * + * @param network The network on which the application desires to use multipath data. + * If {@code null}, this method will return a preference that will generally + * apply to metered networks. + * @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public @MultipathPreference int getMultipathPreference(@Nullable Network network) { + try { + return mService.getMultipathPreference(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Resets all connectivity manager settings back to factory defaults. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + public void factoryReset() { + try { + mService.factoryReset(); + getTetheringManager().stopAllTethering(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code bindProcessToNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean bindProcessToNetwork(@Nullable Network network) { + // Forcing callers to call through non-static function ensures ConnectivityManager + // instantiated. + return setProcessDefaultNetwork(network); + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code setProcessDefaultNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @deprecated This function can throw {@link IllegalStateException}. Use + * {@link #bindProcessToNetwork} instead. {@code bindProcessToNetwork} + * is a direct replacement. + */ + @Deprecated + public static boolean setProcessDefaultNetwork(@Nullable Network network) { + int netId = (network == null) ? NETID_UNSET : network.netId; + boolean isSameNetId = (netId == NetworkUtils.getBoundNetworkForProcess()); + + if (netId != NETID_UNSET) { + netId = network.getNetIdForResolv(); + } + + if (!NetworkUtils.bindProcessToNetwork(netId)) { + return false; + } + + if (!isSameNetId) { + // Set HTTP proxy system properties to match network. + // TODO: Deprecate this static method and replace it with a non-static version. + try { + Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy()); + } catch (SecurityException e) { + // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy. + Log.e(TAG, "Can't set proxy properties", e); + } + // Must flush DNS cache as new network may have different DNS resolutions. + InetAddress.clearDnsCache(); + // Must flush socket pool as idle sockets will be bound to previous network and may + // cause subsequent fetches to be performed on old network. + NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange(); + } + + return true; + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + */ + @Nullable + public Network getBoundNetworkForProcess() { + // Forcing callers to call through non-static function ensures ConnectivityManager has been + // instantiated. + return getProcessDefaultNetwork(); + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + * @deprecated Using this function can lead to other functions throwing + * {@link IllegalStateException}. Use {@link #getBoundNetworkForProcess} instead. + * {@code getBoundNetworkForProcess} is a direct replacement. + */ + @Deprecated + @Nullable + public static Network getProcessDefaultNetwork() { + int netId = NetworkUtils.getBoundNetworkForProcess(); + if (netId == NETID_UNSET) return null; + return new Network(netId); + } + + private void unsupportedStartingFrom(int version) { + if (Process.myUid() == Process.SYSTEM_UID) { + // The getApplicationInfo() call we make below is not supported in system context. Let + // the call through here, and rely on the fact that ConnectivityService will refuse to + // allow the system to use these APIs anyway. + return; + } + + if (mContext.getApplicationInfo().targetSdkVersion >= version) { + throw new UnsupportedOperationException( + "This method is not supported in target SDK version " + version + " and above"); + } + } + + // Checks whether the calling app can use the legacy routing API (startUsingNetworkFeature, + // stopUsingNetworkFeature, requestRouteToHost), and if not throw UnsupportedOperationException. + // TODO: convert the existing system users (Tethering, GnssLocationProvider) to the new APIs and + // remove these exemptions. Note that this check is not secure, and apps can still access these + // functions by accessing ConnectivityService directly. However, it should be clear that doing + // so is unsupported and may break in the future. http://b/22728205 + private void checkLegacyRoutingApiAccess() { + unsupportedStartingFrom(VERSION_CODES.M); + } + + /** + * Binds host resolutions performed by this process to {@code network}. + * {@link #bindProcessToNetwork} takes precedence over this setting. + * + * @param network The {@link Network} to bind host resolutions from the current process to, or + * {@code null} to clear the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @hide + * @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}. + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static boolean setProcessDefaultNetworkForHostResolution(Network network) { + return NetworkUtils.bindProcessToNetworkForHostResolution( + (network == null) ? NETID_UNSET : network.getNetIdForResolv()); + } + + /** + * Device is not restricting metered network activity while application is running on + * background. + */ + public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; + + /** + * Device is restricting metered network activity while application is running on background, + * but application is allowed to bypass it. + *

    + * In this state, application should take action to mitigate metered network access. + * For example, a music streaming application should switch to a low-bandwidth bitrate. + */ + public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; + + /** + * Device is restricting metered network activity while application is running on background. + *

    + * In this state, application should not try to use the network while running on background, + * because it would be denied. + */ + public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; + + /** + * A change in the background metered network activity restriction has occurred. + *

    + * Applications should call {@link #getRestrictBackgroundStatus()} to check if the restriction + * applies to them. + *

    + * This is only sent to registered receivers, not manifest receivers. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = + "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, value = { + RESTRICT_BACKGROUND_STATUS_DISABLED, + RESTRICT_BACKGROUND_STATUS_WHITELISTED, + RESTRICT_BACKGROUND_STATUS_ENABLED, + }) + public @interface RestrictBackgroundStatus { + } + + /** + * Determines if the calling application is subject to metered network restrictions while + * running on background. + * + * @return {@link #RESTRICT_BACKGROUND_STATUS_DISABLED}, + * {@link #RESTRICT_BACKGROUND_STATUS_ENABLED}, + * or {@link #RESTRICT_BACKGROUND_STATUS_WHITELISTED} + */ + public @RestrictBackgroundStatus int getRestrictBackgroundStatus() { + try { + return mService.getRestrictBackgroundStatusByCaller(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * The network watchlist is a list of domains and IP addresses that are associated with + * potentially harmful apps. This method returns the SHA-256 of the watchlist config file + * currently used by the system for validation purposes. + * + * @return Hash of network watchlist config file. Null if config does not exist. + */ + @Nullable + public byte[] getNetworkWatchlistConfigHash() { + try { + return mService.getNetworkWatchlistConfigHash(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get watchlist config hash"); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the {@code uid} of the owner of a network connection. + * + * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and {@code + * IPPROTO_UDP} currently supported. + * @param local The local {@link InetSocketAddress} of a connection. + * @param remote The remote {@link InetSocketAddress} of a connection. + * @return {@code uid} if the connection is found and the app has permission to observe it + * (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link + * android.os.Process#INVALID_UID} if the connection is not found. + * @throws SecurityException if the caller is not the active VpnService for the current + * user. + * @throws IllegalArgumentException if an unsupported protocol is requested. + */ + public int getConnectionOwnerUid( + int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) { + ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote); + try { + return mService.getConnectionOwnerUid(connectionInfo); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void printStackTrace() { + if (DEBUG) { + final StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); + final StringBuffer sb = new StringBuffer(); + for (int i = 3; i < callStack.length; i++) { + final String stackTrace = callStack[i].toString(); + if (stackTrace == null || stackTrace.contains("android.os")) { + break; + } + sb.append(" [").append(stackTrace).append("]"); + } + Log.d(TAG, "StackLog:" + sb.toString()); + } + } + + /** @hide */ + public TestNetworkManager startOrGetTestNetworkManager() { + final IBinder tnBinder; + try { + tnBinder = mService.startOrGetTestNetworkService(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); + } + + /** @hide */ + public ConnectivityDiagnosticsManager createDiagnosticsManager() { + return new ConnectivityDiagnosticsManager(mContext, mService); + } + + /** + * Simulates a Data Stall for the specified Network. + * + *

    This method should only be used for tests. + * + *

    The caller must be the owner of the specified Network. This simulates a data stall to + * have the system behave as if it had happened, but does not actually stall connectivity. + * + * @param detectionMethod The detection method used to identify the Data Stall. + * See ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_*. + * @param timestampMillis The timestamp at which the stall 'occurred', in milliseconds, as per + * SystemClock.elapsedRealtime. + * @param network The Network for which a Data Stall is being simluated. + * @param extras The PersistableBundle of extras included in the Data Stall notification. + * @throws SecurityException if the caller is not the owner of the given network. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_TEST_NETWORKS, + android.Manifest.permission.NETWORK_STACK}) + public void simulateDataStall(@DetectionMethod int detectionMethod, long timestampMillis, + @NonNull Network network, @NonNull PersistableBundle extras) { + try { + mService.simulateDataStall(detectionMethod, timestampMillis, network, extras); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + @NonNull + private final List mQosCallbackConnections = new ArrayList<>(); + + /** + * Registers a {@link QosSocketInfo} with an associated {@link QosCallback}. The callback will + * receive available QoS events related to the {@link Network} and local ip + port + * specified within socketInfo. + *

    + * The same {@link QosCallback} must be unregistered before being registered a second time, + * otherwise {@link QosCallbackRegistrationException} is thrown. + *

    + * This API does not, in itself, require any permission if called with a network that is not + * restricted. However, the underlying implementation currently only supports the IMS network, + * which is always restricted. That means non-preinstalled callers can't possibly find this API + * useful, because they'd never be called back on networks that they would have access to. + * + * @throws SecurityException if {@link QosSocketInfo#getNetwork()} is restricted and the app is + * missing CONNECTIVITY_USE_RESTRICTED_NETWORKS permission. + * @throws QosCallback.QosCallbackRegistrationException if qosCallback is already registered. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * Exceptions after the time of registration is passed through + * {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}. + * + * @param socketInfo the socket information used to match QoS events + * @param executor The executor on which the callback will be invoked. The provided + * {@link Executor} must run callback sequentially, otherwise the order of + * callbacks cannot be guaranteed.onQosCallbackRegistered + * @param callback receives qos events that satisfy socketInfo + * + * @hide + */ + @SystemApi + public void registerQosCallback(@NonNull final QosSocketInfo socketInfo, + @CallbackExecutor @NonNull final Executor executor, + @NonNull final QosCallback callback) { + Objects.requireNonNull(socketInfo, "socketInfo must be non-null"); + Objects.requireNonNull(executor, "executor must be non-null"); + Objects.requireNonNull(callback, "callback must be non-null"); + + try { + synchronized (mQosCallbackConnections) { + if (getQosCallbackConnection(callback) == null) { + final QosCallbackConnection connection = + new QosCallbackConnection(this, callback, executor); + mQosCallbackConnections.add(connection); + mService.registerQosSocketCallback(socketInfo, connection); + } else { + Log.e(TAG, "registerQosCallback: Callback already registered"); + throw new QosCallbackRegistrationException(); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + + // The same unregister method method is called for consistency even though nothing + // will be sent to the ConnectivityService since the callback was never successfully + // registered. + unregisterQosCallback(callback); + e.rethrowFromSystemServer(); + } catch (final ServiceSpecificException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + unregisterQosCallback(callback); + throw convertServiceException(e); + } + } + + /** + * Unregisters the given {@link QosCallback}. The {@link QosCallback} will no longer receive + * events once unregistered and can be registered a second time. + *

    + * If the {@link QosCallback} does not have an active registration, it is a no-op. + * + * @param callback the callback being unregistered + * + * @hide + */ + @SystemApi + public void unregisterQosCallback(@NonNull final QosCallback callback) { + Objects.requireNonNull(callback, "The callback must be non-null"); + try { + synchronized (mQosCallbackConnections) { + final QosCallbackConnection connection = getQosCallbackConnection(callback); + if (connection != null) { + connection.stopReceivingMessages(); + mService.unregisterQosCallback(connection); + mQosCallbackConnections.remove(connection); + } else { + Log.d(TAG, "unregisterQosCallback: Callback not registered"); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "unregisterQosCallback: Error while unregistering ", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Gets the connection related to the callback. + * + * @param callback the callback to look up + * @return the related connection + */ + @Nullable + private QosCallbackConnection getQosCallbackConnection(final QosCallback callback) { + for (final QosCallbackConnection connection : mQosCallbackConnections) { + // Checking by reference here is intentional + if (connection.getCallback() == callback) { + return connection; + } + } + return null; + } + + /** + * Request a network to satisfy a set of {@link NetworkCapabilities}, but + * does not cause any networks to retain the NET_CAPABILITY_FOREGROUND capability. This can + * be used to request that the system provide a network without causing the network to be + * in the foreground. + * + *

    This method will attempt to find the best network that matches the passed + * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the + * criteria. The platform will evaluate which network is the best at its own discretion. + * Throughput, latency, cost per byte, policy, user preference and other considerations + * may be factored in the decision of what is considered the best network. + * + *

    As long as this request is outstanding, the platform will try to maintain the best network + * matching this request, while always attempting to match the request to a better network if + * possible. If a better match is found, the platform will switch this request to the now-best + * network and inform the app of the newly best network by invoking + * {@link NetworkCallback#onAvailable(Network)} on the provided callback. Note that the platform + * will not try to maintain any other network than the best one currently matching the request: + * a network not matching any network request may be disconnected at any time. + * + *

    For example, an application could use this method to obtain a connected cellular network + * even if the device currently has a data connection over Ethernet. This may cause the cellular + * radio to consume additional power. Or, an application could inform the system that it wants + * a network supporting sending MMSes and have the system let it know about the currently best + * MMS-supporting network through the provided {@link NetworkCallback}. + * + *

    The status of the request can be followed by listening to the various callbacks described + * in {@link NetworkCallback}. The {@link Network} object passed to the callback methods can be + * used to direct traffic to the network (although accessing some networks may be subject to + * holding specific permissions). Callers will learn about the specific characteristics of the + * network through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)}. The methods of the + * provided {@link NetworkCallback} will only be invoked due to changes in the best network + * matching the request at any given time; therefore when a better network matching the request + * becomes available, the {@link NetworkCallback#onAvailable(Network)} method is called + * with the new network after which no further updates are given about the previously-best + * network, unless it becomes the best again at some later time. All callbacks are invoked + * in order on the same thread, which by default is a thread created by the framework running + * in the app. + * + *

    This{@link NetworkRequest} will live until released via + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits, at + * which point the system may let go of the network at any time. + * + *

    It is presently unsupported to request a network with mutable + * {@link NetworkCapabilities} such as + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * If null, the callback is invoked on the default internal Handler. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint("ExecutorRegistration") + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void requestBackgroundNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, + @SuppressLint("ListenerLast") @NonNull Handler handler) { + final NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, + TYPE_NONE, new CallbackHandler(handler)); + } + + /** + * Used by automotive devices to set the network preferences used to direct traffic at an + * application level as per the given OemNetworkPreferences. An example use-case would be an + * automotive OEM wanting to provide connectivity for applications critical to the usage of a + * vehicle via a particular network. + * + * Calling this will overwrite the existing preference. + * + * @param preference {@link OemNetworkPreferences} The application network preference to be set. + * @param executor the executor on which listener will be invoked. + * @param listener {@link OnSetOemNetworkPreferenceListener} optional listener used to + * communicate completion of setOemNetworkPreference(). This will only be + * called once upon successful completion of setOemNetworkPreference(). + * @throws IllegalArgumentException if {@code preference} contains invalid preference values. + * @throws SecurityException if missing the appropriate permissions. + * @throws UnsupportedOperationException if called on a non-automotive device. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) + public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final Runnable listener) { + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + if (null != listener) { + Objects.requireNonNull(executor, "Executor must be non-null"); + } + final IOnCompleteListener listenerInternal = listener == null ? null : + new IOnCompleteListener.Stub() { + @Override + public void onComplete() { + executor.execute(listener::run); + } + }; + + try { + mService.setOemNetworkPreference(preference, listenerInternal); + } catch (RemoteException e) { + Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request that a user profile is put by default on a network matching a given preference. + * + * See the documentation for the individual preferences for a description of the supported + * behaviors. + * + * @param profile the profile concerned. + * @param preference the preference for this profile. + * @param executor an executor to execute the listener on. Optional if listener is null. + * @param listener an optional listener to listen for completion of the operation. + * @throws IllegalArgumentException if {@code profile} is not a valid user profile. + * @throws SecurityException if missing the appropriate permissions. + * @deprecated Use {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)} + * instead as it provides a more flexible API with more options. + * @hide + */ + // This function is for establishing per-profile default networking and can only be called by + // the device policy manager, running as the system server. It would make no sense to call it + // on a context for a user because it does not establish a setting on behalf of a user, rather + // it establishes a setting for a user on behalf of the DPM. + @SuppressLint({"UserHandle"}) + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + @Deprecated + public void setProfileNetworkPreference(@NonNull final UserHandle profile, + @ProfileNetworkPreferencePolicy final int preference, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final Runnable listener) { + + ProfileNetworkPreference.Builder preferenceBuilder = + new ProfileNetworkPreference.Builder(); + preferenceBuilder.setPreference(preference); + if (preference != PROFILE_NETWORK_PREFERENCE_DEFAULT) { + preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1); + } + setProfileNetworkPreferences(profile, + List.of(preferenceBuilder.build()), executor, listener); + } + + /** + * Set a list of default network selection policies for a user profile. + * + * Calling this API with a user handle defines the entire policy for that user handle. + * It will overwrite any setting previously set for the same user profile, + * and not affect previously set settings for other handles. + * + * Call this API with an empty list to remove settings for this user profile. + * + * See {@link ProfileNetworkPreference} for more details on each preference + * parameter. + * + * @param profile the user profile for which the preference is being set. + * @param profileNetworkPreferences the list of profile network preferences for the + * provided profile. + * @param executor an executor to execute the listener on. Optional if listener is null. + * @param listener an optional listener to listen for completion of the operation. + * @throws IllegalArgumentException if {@code profile} is not a valid user profile. + * @throws SecurityException if missing the appropriate permissions. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setProfileNetworkPreferences( + @NonNull final UserHandle profile, + @NonNull List profileNetworkPreferences, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final Runnable listener) { + if (null != listener) { + Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener"); + } + final IOnCompleteListener proxy; + if (null == listener) { + proxy = null; + } else { + proxy = new IOnCompleteListener.Stub() { + @Override + public void onComplete() { + executor.execute(listener::run); + } + }; + } + try { + mService.setProfileNetworkPreferences(profile, profileNetworkPreferences, proxy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + // The first network ID of IPSec tunnel interface. + private static final int TUN_INTF_NETID_START = 0xFC00; // 0xFC00 = 64512 + // The network ID range of IPSec tunnel interface. + private static final int TUN_INTF_NETID_RANGE = 0x0400; // 0x0400 = 1024 + + /** + * Get the network ID range reserved for IPSec tunnel interfaces. + * + * @return A Range which indicates the network ID range of IPSec tunnel interface. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @NonNull + public static Range getIpSecNetIdRange() { + return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1); + } + + /** + * Sets data saver switch. + * + *

    This API configures the bandwidth control, and filling data saver status in BpfMap, + * which is intended for internal use by the network stack to optimize performance + * when frequently checking data saver status for multiple uids without doing IPC. + * It does not directly control the global data saver mode that users manage in settings. + * To query the comprehensive data saver status for a specific UID, including allowlist + * considerations, use {@link #getRestrictBackgroundStatus}. + * + * @param enable True if enable. + * @throws IllegalStateException if failed. + * @hide + */ + @FlaggedApi(Flags.SET_DATA_SAVER_VIA_CM) + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void setDataSaverEnabled(final boolean enable) { + try { + mService.setDataSaverEnabled(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds the specified UID to the list of UIds that are allowed to use data on metered networks + * even when background data is restricted. The deny list takes precedence over the allow list. + * + * @param uid uid of target app + * @throws IllegalStateException if updating allow list failed. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void addUidToMeteredNetworkAllowList(final int uid) { + try { + mService.updateMeteredNetworkAllowList(uid, true /* add */); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes the specified UID from the list of UIDs that are allowed to use background data on + * metered networks when background data is restricted. The deny list takes precedence over + * the allow list. + * + * @param uid uid of target app + * @throws IllegalStateException if updating allow list failed. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void removeUidFromMeteredNetworkAllowList(final int uid) { + try { + mService.updateMeteredNetworkAllowList(uid, false /* remove */); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds the specified UID to the list of UIDs that are not allowed to use background data on + * metered networks. Takes precedence over {@link #addUidToMeteredNetworkAllowList}. + * + * @param uid uid of target app + * @throws IllegalStateException if updating deny list failed. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void addUidToMeteredNetworkDenyList(final int uid) { + try { + mService.updateMeteredNetworkDenyList(uid, true /* add */); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes the specified UID from the list of UIDs that can use background data on metered + * networks if background data is not restricted. The deny list takes precedence over the + * allow list. + * + * @param uid uid of target app + * @throws IllegalStateException if updating deny list failed. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void removeUidFromMeteredNetworkDenyList(final int uid) { + try { + mService.updateMeteredNetworkDenyList(uid, false /* remove */); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets a firewall rule for the specified UID on the specified chain. + * + * @param chain target chain. + * @param uid uid to allow/deny. + * @param rule firewall rule to allow/drop packets. + * @throws IllegalStateException if updating firewall rule failed. + * @throws IllegalArgumentException if {@code rule} is not a valid rule. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void setUidFirewallRule(@FirewallChain final int chain, final int uid, + @FirewallRule final int rule) { + try { + mService.setUidFirewallRule(chain, uid, rule); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get firewall rule of specified firewall chain on specified uid. + * + * @param chain target chain. + * @param uid target uid + * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + * @hide + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public int getUidFirewallRule(@FirewallChain final int chain, final int uid) { + try { + return mService.getUidFirewallRule(chain, uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Enables or disables the specified firewall chain. + * + * @param chain target chain. + * @param enable whether the chain should be enabled. + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws IllegalStateException if enabling or disabling the firewall chain failed. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void setFirewallChainEnabled(@FirewallChain final int chain, final boolean enable) { + try { + mService.setFirewallChainEnabled(chain, enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the specified firewall chain's status. + * + * @param chain target chain. + * @return {@code true} if chain is enabled, {@code false} if chain is disabled. + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + * @hide + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public boolean getFirewallChainEnabled(@FirewallChain final int chain) { + try { + return mService.getFirewallChainEnabled(chain); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Replaces the contents of the specified UID-based firewall chain. + * + * @param chain target chain to replace. + * @param uids The list of UIDs to be placed into chain. + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws IllegalArgumentException if {@code chain} is not a valid chain. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void replaceFirewallChain(@FirewallChain final int chain, @NonNull final int[] uids) { + Objects.requireNonNull(uids); + try { + mService.replaceFirewallChain(chain, uids); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return whether the network is blocked for the given uid and metered condition. + * + * Similar to {@link NetworkPolicyManager#isUidNetworkingBlocked}, but directly reads the BPF + * maps and therefore considerably faster. For use by the NetworkStack process only. + * + * @param uid The target uid. + * @param isNetworkMetered Whether the target network is metered. + * + * @return True if all networking with the given condition is blocked. Otherwise, false. + * @throws IllegalStateException if the map cannot be opened. + * @throws ServiceSpecificException if the read fails. + * @hide + */ + // This isn't protected by a standard Android permission since it can't + // afford to do IPC for performance reasons. Instead, the access control + // is provided by linux file group permission AID_NET_BW_ACCT and the + // selinux context fs_bpf_net*. + // Only the system server process and the network stack have access. + @FlaggedApi(Flags.SUPPORT_IS_UID_NETWORKING_BLOCKED) + @SystemApi(client = MODULE_LIBRARIES) + // Note b/326143935 kernel bug can trigger crash on some T device. + @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) { + if (!SdkLevel.isAtLeastU()) { + Log.wtf(TAG, "isUidNetworkingBlocked is not supported on pre-U devices"); + } + final BpfNetMapsReader reader = BpfNetMapsReader.getInstance(); + // Note that before V, the data saver status in bpf is written by ConnectivityService + // when receiving {@link #ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus, + // the status is not synchronized. + // On V+, the data saver status is set by platform code when enabling/disabling + // data saver, which is synchronized. + return reader.isUidNetworkingBlocked(uid, isNetworkMetered, reader.getDataSaverEnabled()); + } + + /** @hide */ + public IBinder getCompanionDeviceManagerProxyService() { + try { + return mService.getCompanionDeviceManagerProxyService(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static final Object sRoutingCoordinatorManagerLock = new Object(); + @GuardedBy("sRoutingCoordinatorManagerLock") + private static RoutingCoordinatorManager sRoutingCoordinatorManager = null; + /** @hide */ + @RequiresApi(Build.VERSION_CODES.S) + public RoutingCoordinatorManager getRoutingCoordinatorManager() { + try { + synchronized (sRoutingCoordinatorManagerLock) { + if (null == sRoutingCoordinatorManager) { + sRoutingCoordinatorManager = new RoutingCoordinatorManager(mContext, + IRoutingCoordinator.Stub.asInterface( + mService.getRoutingCoordinatorService())); + } + return sRoutingCoordinatorManager; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/aosp/packages/modules/Connectivity/service/Android.bp b/aosp/packages/modules/Connectivity/service/Android.bp new file mode 100644 index 000000000..c35c4f853 --- /dev/null +++ b/aosp/packages/modules/Connectivity/service/Android.bp @@ -0,0 +1,416 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_team: "trendy_team_fwk_core_networking", + // See: http://go/android-license-faq + default_applicable_licenses: ["Android-Apache-2.0"], +} + +service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar" + +// The above variables may have different values +// depending on the branch, and this comment helps +// separate them from the rest of the file to avoid merge conflicts + +aidl_interface { + name: "connectivity_native_aidl_interface", + local_include_dir: "binder", + srcs: [ + "binder/android/net/connectivity/aidl/*.aidl", + ], + backend: { + java: { + apex_available: [ + "com.android.tethering", + ], + min_sdk_version: "30", + }, + ndk: { + apex_available: [ + "com.android.tethering", + ], + min_sdk_version: "30", + }, + }, + versions: ["1"], + +} + +cc_library_static { + name: "connectivity_native_aidl_interface-lateststable-ndk", + min_sdk_version: "30", + whole_static_libs: [ + "connectivity_native_aidl_interface-V1-ndk", + ], + apex_available: [ + "com.android.tethering", + ], +} + +java_library { + name: "connectivity_native_aidl_interface-lateststable-java", + sdk_version: "system_current", + min_sdk_version: "30", + static_libs: [ + "connectivity_native_aidl_interface-V1-java", + ], + apex_available: [ + "com.android.tethering", + ], +} + +// The library name match the service-connectivity jarjar rules that put the JNI utils in the +// android.net.connectivity.com.android.net.module.util package. +cc_library_shared { + name: "libandroid_net_connectivity_com_android_net_module_util_jni", + min_sdk_version: "30", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + srcs: [ + "jni/com_android_net_module_util/onload.cpp", + ], + static_libs: [ + "libnet_utils_device_common_bpfjni", + "libnet_utils_device_common_bpfutils", + ], + shared_libs: [ + "liblog", + "libnativehelper", + ], + apex_available: [ + "com.android.tethering", + ], +} + +cc_library_shared { + name: "libservice-connectivity", + min_sdk_version: "30", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + srcs: [ + ":services.connectivity-netstats-jni-sources", + "jni/com_android_server_connectivity_ClatCoordinator.cpp", + "jni/com_android_server_ServiceManagerWrapper.cpp", + "jni/com_android_server_TestNetworkService.cpp", + "jni/onload.cpp", + ], + header_libs: [ + "bpf_connectivity_headers", + ], + static_libs: [ + "libclat", + "libip_checksum", + "libmodules-utils-build", + "libnetjniutils", + "libnet_utils_device_common_bpfjni", + "netd_aidl_interface-lateststable-ndk", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libcutils", + "libnetdutils", + "liblog", + "libnativehelper", + "libnetworkstats", + ], + apex_available: [ + "com.android.tethering", + ], +} + +java_library { + name: "service-connectivity-pre-jarjar", + sdk_version: "system_server_current", + min_sdk_version: "30", + // NetworkStackApiShimSettingsForCurrentBranch provides the latest available shims depending on + // the branch to "service-connectivity". + // There are Tethering.apk and TetheringNext.apk variants for the tethering APEX, + // which use NetworkStackApiStableShims and NetworkStackApiCurrentShims respectively. + // Note that there can be no service-connectivity-next because it would need to be configured in + // default_art_config.mk which doesn't support conditionals, hence this scheme of using a + // variable here. + defaults: ["NetworkStackApiShimSettingsForCurrentBranch"], + srcs: [ + "src/**/*.java", + ":framework-connectivity-shared-srcs", + ":services-connectivity-shared-srcs", + ":statslog-connectivity-java-gen", + ], + libs: [ + "framework-annotations-lib", + "framework-configinfrastructure", + "framework-connectivity-pre-jarjar", + // The framework-connectivity-t library is only available on T+ platforms + // so any calls to it must be protected with a check to ensure that it is + // available. The linter will detect any unprotected calls through an API + // but not direct calls to the implementation. So, this depends on the + // module lib stubs directly to ensure the linter will work correctly + // as depending on framework-connectivity-t would cause it to be compiled + // against the implementation because the two libraries are in the same + // APEX. + "framework-connectivity-t.stubs.module_lib", + // TODO: figure out why just using "framework-tethering" uses the stubs, even though both + // service-connectivity and framework-tethering are in the same APEX. + "framework-tethering.impl", + "framework-wifi", + "unsupportedappusage", + "ServiceConnectivityResources", + "framework-statsd", + "framework-permission", + "framework-permission-s", + ], + static_libs: [ + // Do not add libs here if they are already included + // in framework-connectivity + "androidx.annotation_annotation", + "connectivity-net-module-utils-bpf", + "connectivity_native_aidl_interface-lateststable-java", + "dnsresolver_aidl_interface-V14-java", + "modules-utils-shell-command-handler", + "net-utils-device-common", + "net-utils-device-common-ip", + "net-utils-device-common-netlink", + "net-utils-services-common", + "netd-client", + "networkstack-client", + "PlatformProperties", + "service-connectivity-protos", + "service-connectivity-stats-protos", + "net-utils-multicast-forwarding-structs", + ], + apex_available: [ + "com.android.tethering", + ], + lint: { + baseline_filename: "lint-baseline.xml", + + }, + visibility: [ + "//packages/modules/Connectivity/service-t", + "//packages/modules/Connectivity/tests:__subpackages__", + "//packages/modules/Connectivity/thread/service:__subpackages__", + "//packages/modules/Connectivity/thread/tests:__subpackages__", + ], +} + +java_library { + name: "service-connectivity-protos", + sdk_version: "system_current", + min_sdk_version: "30", + proto: { + type: "nano", + }, + srcs: [ + ":system-messages-proto-src", + ], + libs: ["libprotobuf-java-nano"], + apex_available: [ + "com.android.tethering", + ], + lint: { + strict_updatability_linting: true, + + }, +} + +java_defaults { + name: "service-connectivity-defaults", + sdk_version: "system_server_current", + min_sdk_version: "30", + defaults: [ + "standalone-system-server-module-optimize-defaults", + ], + // This library combines system server jars that have access to different bootclasspath jars. + // Lower SDK service jars must not depend on higher SDK jars as that would let them + // transitively depend on the wrong bootclasspath jars. Sources also cannot be added here as + // they would depend on bootclasspath jars that may not be available. + static_libs: [ + "service-connectivity-pre-jarjar", + "service-connectivity-tiramisu-pre-jarjar", + "service-nearby-pre-jarjar", + service_remoteauth_pre_jarjar_lib, + "service-thread-pre-jarjar", + ], + // The below libraries are not actually needed to build since no source is compiled + // (only combining prebuilt static_libs), but they are necessary so that R8 has the right + // references to optimize the code. Without these, there will be missing class warnings and + // code may be wrongly optimized. + // R8 runs after jarjar, so the framework-X libraries need to be the post-jarjar artifacts + // (.impl), if they are not just stubs, so that the name of jarjared classes match. + libs: [ + "androidx.annotation_annotation", + "framework-annotations-lib", + "framework-connectivity.impl", + "framework-connectivity-t.impl", + "framework-tethering.impl", + "framework-wifi", + "libprotobuf-java-nano", + "framework-permission", + "framework-permission-s", + ], + jarjar_rules: ":connectivity-jarjar-rules", + apex_available: [ + "com.android.tethering", + ], + optimize: { + proguard_flags_files: ["proguard.flags"], + }, +} + +// A special library created strictly for use by the tests as they need the +// implementation library but that is not available when building from prebuilts. +// Using a library with a different name to what is used by the prebuilts ensures +// that this will never depend on the prebuilt. +// Switching service-connectivity to a java_sdk_library would also have worked as +// that has built in support for managing this but that is too big a change at this +// point. +java_library { + name: "service-connectivity-for-tests", + defaults: ["service-connectivity-defaults"], +} + +java_library { + name: "service-connectivity", + defaults: ["service-connectivity-defaults"], + installable: true, +} + +java_library_static { + name: "service-connectivity-stats-protos", + sdk_version: "system_current", + min_sdk_version: "30", + proto: { + type: "lite", + }, + srcs: [ + "src/com/android/metrics/stats.proto", + ], + static_libs: ["ConnectivityServiceprotos"], + apex_available: ["com.android.tethering"], +} + +genrule { + name: "connectivity-jarjar-rules", + defaults: ["jarjar-rules-combine-defaults"], + srcs: [ + ":framework-connectivity-jarjar-rules", + ":service-connectivity-jarjar-gen", + ":service-nearby-jarjar-gen", + ":service-remoteauth-jarjar-gen", + ":service-thread-jarjar-gen", + ], + out: ["connectivity-jarjar-rules.txt"], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} + +// TODO: This filegroup temporary exposes for NetworkStats. It should be +// removed right after NetworkStats moves into mainline module. +filegroup { + name: "traffic-controller-utils", + srcs: ["src/com/android/server/BpfNetMaps.java"], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} + +java_genrule { + name: "service-connectivity-jarjar-gen", + tool_files: [ + ":service-connectivity-pre-jarjar{.jar}", + ":service-connectivity-tiramisu-pre-jarjar{.jar}", + "jarjar-excludes.txt", + ], + tools: [ + "jarjar-rules-generator", + ], + out: ["service_connectivity_jarjar_rules.txt"], + cmd: "$(location jarjar-rules-generator) " + + "$(location :service-connectivity-pre-jarjar{.jar}) " + + "$(location :service-connectivity-tiramisu-pre-jarjar{.jar}) " + + "--prefix android.net.connectivity " + + "--excludes $(location jarjar-excludes.txt) " + + "--output $(out)", + visibility: ["//visibility:private"], +} + +java_genrule { + name: "service-nearby-jarjar-gen", + tool_files: [ + ":service-nearby-pre-jarjar{.jar}", + "jarjar-excludes.txt", + ], + tools: [ + "jarjar-rules-generator", + ], + out: ["service_nearby_jarjar_rules.txt"], + cmd: "$(location jarjar-rules-generator) " + + "$(location :service-nearby-pre-jarjar{.jar}) " + + "--prefix com.android.server.nearby " + + "--excludes $(location jarjar-excludes.txt) " + + "--output $(out)", + visibility: ["//visibility:private"], +} + +java_genrule { + name: "service-remoteauth-jarjar-gen", + tool_files: [ + ":" + service_remoteauth_pre_jarjar_lib + "{.jar}", + "jarjar-excludes.txt", + ], + tools: [ + "jarjar-rules-generator", + ], + out: ["service_remoteauth_jarjar_rules.txt"], + cmd: "$(location jarjar-rules-generator) " + + "$(location :" + service_remoteauth_pre_jarjar_lib + "{.jar}) " + + "--prefix com.android.server.remoteauth " + + "--excludes $(location jarjar-excludes.txt) " + + "--output $(out)", + visibility: ["//visibility:private"], +} + +java_genrule { + name: "service-thread-jarjar-gen", + tool_files: [ + ":service-thread-pre-jarjar{.jar}", + "jarjar-excludes.txt", + ], + tools: [ + "jarjar-rules-generator", + ], + out: ["service_thread_jarjar_rules.txt"], + cmd: "$(location jarjar-rules-generator) " + + "$(location :service-thread-pre-jarjar{.jar}) " + + "--prefix com.android.server.thread " + + "--excludes $(location jarjar-excludes.txt) " + + "--output $(out)", + visibility: ["//visibility:private"], +} + +genrule { + name: "statslog-connectivity-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module connectivity --javaPackage com.android.server --javaClass ConnectivityStatsLog", + out: ["com/android/server/ConnectivityStatsLog.java"], +} diff --git a/aosp/packages/modules/Connectivity/service/src/com/android/server/connectivity/DnsManager.java b/aosp/packages/modules/Connectivity/service/src/com/android/server/connectivity/DnsManager.java new file mode 100644 index 000000000..8e6854ae5 --- /dev/null +++ b/aosp/packages/modules/Connectivity/service/src/com/android/server/connectivity/DnsManager.java @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_MAX_SAMPLES; +import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_MIN_SAMPLES; +import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS; +import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; + +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.ConnectivitySettingsManager; +import android.net.IDnsResolver; +import android.net.InetAddresses; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.ResolverParamsParcel; +import android.net.Uri; +import android.net.shared.PrivateDnsConfig; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Encapsulate the management of DNS settings for networks. + * + * This class it NOT designed for concurrent access. Furthermore, all non-static + * methods MUST be called from ConnectivityService's thread. However, an exceptional + * case is getPrivateDnsConfig(Network) which is exclusively for + * ConnectivityService#dumpNetworkDiagnostics() on a random binder thread. + * + * [ Private DNS ] + * The code handling Private DNS is spread across several components, but this + * seems like the least bad place to collect all the observations. + * + * Private DNS handling and updating occurs in response to several different + * events. Each is described here with its corresponding intended handling. + * + * [A] Event: A new network comes up. + * Mechanics: + * [1] ConnectivityService gets notifications from NetworkAgents. + * [2] in updateNetworkInfo(), the first time the NetworkAgent goes into + * into CONNECTED state, the Private DNS configuration is retrieved, + * programmed, and strict mode hostname resolution (if applicable) is + * enqueued in NetworkAgent's NetworkMonitor, via a call to + * handlePerNetworkPrivateDnsConfig(). + * [3] Re-resolution of strict mode hostnames that fail to return any + * IP addresses happens inside NetworkMonitor; it sends itself a + * delayed CMD_EVALUATE_PRIVATE_DNS message in a simple backoff + * schedule. + * [4] Successfully resolved hostnames are sent to ConnectivityService + * inside an EVENT_PRIVATE_DNS_CONFIG_RESOLVED message. The resolved + * IP addresses are programmed into netd via: + * + * updatePrivateDns() -> updateDnses() + * + * both of which make calls into DnsManager. + * [5] Upon a successful hostname resolution NetworkMonitor initiates a + * validation attempt in the form of a lookup for a one-time hostname + * that uses Private DNS. + * + * [B] Event: Private DNS settings are changed. + * Mechanics: + * [1] ConnectivityService gets notifications from its SettingsObserver. + * [2] handlePrivateDnsSettingsChanged() is called, which calls + * handlePerNetworkPrivateDnsConfig() and the process proceeds + * as if from A.3 above. + * + * [C] Event: An application calls ConnectivityManager#reportBadNetwork(). + * Mechanics: + * [1] NetworkMonitor is notified and initiates a reevaluation, which + * always bypasses Private DNS. + * [2] Once completed, NetworkMonitor checks if strict mode is in operation + * and if so enqueues another evaluation of Private DNS, as if from + * step A.5 above. + * + * @hide + */ +public class DnsManager { + private static final String TAG = DnsManager.class.getSimpleName(); + private static final PrivateDnsConfig PRIVATE_DNS_OFF = new PrivateDnsConfig(); + + /* Defaults for resolver parameters. */ + private static final int DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800; + private static final int DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25; + private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8; + private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64; + + /** + * Get PrivateDnsConfig. + */ + public static PrivateDnsConfig getPrivateDnsConfig(Context context) { + final int mode = ConnectivitySettingsManager.getPrivateDnsMode(context); + + final boolean useTls = mode != PRIVATE_DNS_MODE_OFF; + + if (PRIVATE_DNS_MODE_PROVIDER_HOSTNAME == mode) { + final String specifier = getStringSetting(context.getContentResolver(), + PRIVATE_DNS_SPECIFIER); + return new PrivateDnsConfig(specifier, null); + } + + return new PrivateDnsConfig(useTls); + } + + public static Uri[] getPrivateDnsSettingsUris() { + return new Uri[]{ + Settings.Global.getUriFor(PRIVATE_DNS_DEFAULT_MODE), + Settings.Global.getUriFor(PRIVATE_DNS_MODE), + Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER), + }; + } + + public static class PrivateDnsValidationUpdate { + public final int netId; + public final InetAddress ipAddress; + public final String hostname; + // Refer to IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_*. + public final int validationResult; + + public PrivateDnsValidationUpdate(int netId, InetAddress ipAddress, + String hostname, int validationResult) { + this.netId = netId; + this.ipAddress = ipAddress; + this.hostname = hostname; + this.validationResult = validationResult; + } + } + + private static class PrivateDnsValidationStatuses { + enum ValidationStatus { + IN_PROGRESS, + FAILED, + SUCCEEDED + } + + // Validation statuses of pairs for a single netId + // Caution : not thread-safe. As mentioned in the top file comment, all + // methods of this class must only be called on ConnectivityService's thread. + private Map, ValidationStatus> mValidationMap; + + private PrivateDnsValidationStatuses() { + mValidationMap = new HashMap<>(); + } + + private boolean hasValidatedServer() { + for (ValidationStatus status : mValidationMap.values()) { + if (status == ValidationStatus.SUCCEEDED) { + return true; + } + } + return false; + } + + private void updateTrackedDnses(String[] ipAddresses, String hostname) { + Set> latestDnses = new HashSet<>(); + for (String ipAddress : ipAddresses) { + try { + latestDnses.add(new Pair(hostname, + InetAddresses.parseNumericAddress(ipAddress))); + } catch (IllegalArgumentException e) {} + } + // Remove pairs that should not be tracked. + for (Iterator, ValidationStatus>> it = + mValidationMap.entrySet().iterator(); it.hasNext(); ) { + Map.Entry, ValidationStatus> entry = it.next(); + if (!latestDnses.contains(entry.getKey())) { + it.remove(); + } + } + // Add new pairs that should be tracked. + for (Pair p : latestDnses) { + if (!mValidationMap.containsKey(p)) { + mValidationMap.put(p, ValidationStatus.IN_PROGRESS); + } + } + } + + private void updateStatus(PrivateDnsValidationUpdate update) { + Pair p = new Pair(update.hostname, + update.ipAddress); + if (!mValidationMap.containsKey(p)) { + return; + } + if (update.validationResult == VALIDATION_RESULT_SUCCESS) { + mValidationMap.put(p, ValidationStatus.SUCCEEDED); + } else if (update.validationResult == VALIDATION_RESULT_FAILURE) { + mValidationMap.put(p, ValidationStatus.FAILED); + } else { + Log.e(TAG, "Unknown private dns validation operation=" + + update.validationResult); + } + } + + private LinkProperties fillInValidatedPrivateDns(LinkProperties lp) { + lp.setValidatedPrivateDnsServers(Collections.EMPTY_LIST); + mValidationMap.forEach((key, value) -> { + if (value == ValidationStatus.SUCCEEDED) { + lp.addValidatedPrivateDnsServer(key.second); + } + }); + return lp; + } + } + + private final Context mContext; + private final ContentResolver mContentResolver; + private final IDnsResolver mDnsResolver; + private final ConcurrentHashMap mPrivateDnsMap; + // TODO: Replace the Map with SparseArrays. + private final Map mPrivateDnsValidationMap; + private final Map mLinkPropertiesMap; + private final Map mNetworkCapabilitiesMap; + + private int mSampleValidity; + private int mSuccessThreshold; + private int mMinSamples; + private int mMaxSamples; + + public DnsManager(Context ctx, IDnsResolver dnsResolver) { + mContext = ctx; + mContentResolver = mContext.getContentResolver(); + mDnsResolver = dnsResolver; + mPrivateDnsMap = new ConcurrentHashMap<>(); + mPrivateDnsValidationMap = new HashMap<>(); + mLinkPropertiesMap = new HashMap<>(); + mNetworkCapabilitiesMap = new HashMap<>(); + + // TODO: Create and register ContentObservers to track every setting + // used herein, posting messages to respond to changes. + } + + public PrivateDnsConfig getPrivateDnsConfig() { + return getPrivateDnsConfig(mContext); + } + + public void removeNetwork(Network network) { + mPrivateDnsMap.remove(network.getNetId()); + mPrivateDnsValidationMap.remove(network.getNetId()); + mNetworkCapabilitiesMap.remove(network.getNetId()); + mLinkPropertiesMap.remove(network.getNetId()); + } + + // This is exclusively called by ConnectivityService#dumpNetworkDiagnostics() which + // is not on the ConnectivityService handler thread. + public PrivateDnsConfig getPrivateDnsConfig(@NonNull Network network) { + return mPrivateDnsMap.getOrDefault(network.getNetId(), PRIVATE_DNS_OFF); + } + + public PrivateDnsConfig updatePrivateDns(Network network, PrivateDnsConfig cfg) { + Log.w(TAG, "updatePrivateDns(" + network + ", " + cfg + ")"); + return (cfg != null) + ? mPrivateDnsMap.put(network.getNetId(), cfg) + : mPrivateDnsMap.remove(network.getNetId()); + } + + public void updatePrivateDnsStatus(int netId, LinkProperties lp) { + // Use the PrivateDnsConfig data pushed to this class instance + // from ConnectivityService. + final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId, + PRIVATE_DNS_OFF); + + final boolean useTls = privateDnsCfg.mode != PRIVATE_DNS_MODE_OFF; + final PrivateDnsValidationStatuses statuses = + useTls ? mPrivateDnsValidationMap.get(netId) : null; + final boolean validated = (null != statuses) && statuses.hasValidatedServer(); + final boolean strictMode = privateDnsCfg.inStrictMode(); + final String tlsHostname = strictMode ? privateDnsCfg.hostname : null; + final boolean usingPrivateDns = strictMode || validated; + + lp.setUsePrivateDns(usingPrivateDns); + lp.setPrivateDnsServerName(tlsHostname); + if (usingPrivateDns && null != statuses) { + statuses.fillInValidatedPrivateDns(lp); + } else { + lp.setValidatedPrivateDnsServers(Collections.EMPTY_LIST); + } + } + + public void updatePrivateDnsValidation(PrivateDnsValidationUpdate update) { + final PrivateDnsValidationStatuses statuses = mPrivateDnsValidationMap.get(update.netId); + if (statuses == null) return; + statuses.updateStatus(update); + } + + /** + * Update {@link NetworkCapabilities} stored in this instance. + * + * In order to ensure that the resolver has access to necessary information when other events + * occur, capabilities are always saved to a hashMap before updating the DNS configuration + * whenever a new network is created, transport types are modified, or metered capabilities are + * altered for a network. When a network is destroyed, the corresponding entry is removed from + * the hashMap. To prevent concurrency issues, the hashMap should always be accessed from the + * same thread. + */ + public void updateCapabilitiesForNetwork(int netId, @NonNull final NetworkCapabilities nc) { + mNetworkCapabilitiesMap.put(netId, nc); + sendDnsConfigurationForNetwork(netId); + } + + /** + * When {@link LinkProperties} are changed in a specific network, they are + * always saved to a hashMap before update dns config. + * When destroying network, the specific network will be removed from the hashMap. + * The hashMap is always accessed on the same thread. + */ + public void noteDnsServersForNetwork(int netId, @NonNull LinkProperties lp) { + mLinkPropertiesMap.put(netId, lp); + sendDnsConfigurationForNetwork(netId); + } + + /** + * Send dns configuration parameters to resolver for a given network. + */ + public void sendDnsConfigurationForNetwork(int netId) { + final LinkProperties lp = mLinkPropertiesMap.get(netId); + final NetworkCapabilities nc = mNetworkCapabilitiesMap.get(netId); + if (lp == null || nc == null) return; + updateParametersSettings(); + final ResolverParamsParcel paramsParcel = new ResolverParamsParcel(); + + // We only use the PrivateDnsConfig data pushed to this class instance + // from ConnectivityService because it works in coordination with + // NetworkMonitor to decide which networks need validation and runs the + // blocking calls to resolve Private DNS strict mode hostnames. + // + // At this time we do not attempt to enable Private DNS on non-Internet + // networks like IMS. + final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId, + PRIVATE_DNS_OFF); + final boolean useTls = privateDnsCfg.mode != PRIVATE_DNS_MODE_OFF; + final boolean strictMode = privateDnsCfg.inStrictMode(); + + paramsParcel.netId = netId; + paramsParcel.sampleValiditySeconds = mSampleValidity; + paramsParcel.successThreshold = mSuccessThreshold; + paramsParcel.minSamples = mMinSamples; + paramsParcel.maxSamples = mMaxSamples; + paramsParcel.servers = makeStrings(lp.getDnsServers()); + paramsParcel.domains = getDomainStrings(lp.getDomains()); + paramsParcel.tlsName = strictMode ? privateDnsCfg.hostname : ""; + paramsParcel.tlsServers = + strictMode ? makeStrings( + Arrays.stream(privateDnsCfg.ips) + .filter((ip) -> lp.isReachable(ip)) + .collect(Collectors.toList())) + : useTls ? paramsParcel.servers // Opportunistic + : new String[0]; // Off + paramsParcel.transportTypes = nc.getTransportTypes(); + paramsParcel.meteredNetwork = nc.isMetered(); + // Prepare to track the validation status of the DNS servers in the + // resolver config when private DNS is in opportunistic or strict mode. + if (useTls) { + if (!mPrivateDnsValidationMap.containsKey(netId)) { + mPrivateDnsValidationMap.put(netId, new PrivateDnsValidationStatuses()); + } + mPrivateDnsValidationMap.get(netId).updateTrackedDnses(paramsParcel.tlsServers, + paramsParcel.tlsName); + } else { + mPrivateDnsValidationMap.remove(netId); + } + + Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, " + + "%d, %d, %s, %s, %s, %b)", paramsParcel.netId, + Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains), + paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold, + paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec, + paramsParcel.retryCount, paramsParcel.tlsName, + Arrays.toString(paramsParcel.tlsServers), + Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork)); + + try { + mDnsResolver.setResolverConfiguration(paramsParcel); + } catch (RemoteException | ServiceSpecificException e) { + Log.e(TAG, "Error setting DNS configuration: " + e); + return; + } + } + + /** + * Flush DNS caches and events work before boot has completed. + */ + public void flushVmDnsCache() { + /* + * Tell the VMs to toss their DNS caches + */ + final Intent intent = new Intent(ConnectivityManager.ACTION_CLEAR_DNS_CACHE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + /* + * Connectivity events can happen before boot has completed ... + */ + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void updateParametersSettings() { + mSampleValidity = getIntSetting( + DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, + DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS); + if (mSampleValidity < 0 || mSampleValidity > 65535) { + Log.w(TAG, "Invalid sampleValidity=" + mSampleValidity + ", using default=" + + DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS); + mSampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS; + } + + mSuccessThreshold = getIntSetting( + DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, + DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT); + if (mSuccessThreshold < 0 || mSuccessThreshold > 100) { + Log.w(TAG, "Invalid successThreshold=" + mSuccessThreshold + ", using default=" + + DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT); + mSuccessThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT; + } + + mMinSamples = getIntSetting(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES); + mMaxSamples = getIntSetting(DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES); + if (mMinSamples < 0 || mMinSamples > mMaxSamples || mMaxSamples > 64) { + Log.w(TAG, "Invalid sample count (min, max)=(" + mMinSamples + ", " + mMaxSamples + + "), using default=(" + DNS_RESOLVER_DEFAULT_MIN_SAMPLES + ", " + + DNS_RESOLVER_DEFAULT_MAX_SAMPLES + ")"); + mMinSamples = DNS_RESOLVER_DEFAULT_MIN_SAMPLES; + mMaxSamples = DNS_RESOLVER_DEFAULT_MAX_SAMPLES; + } + } + + private int getIntSetting(String which, int dflt) { + return Settings.Global.getInt(mContentResolver, which, dflt); + } + + /** + * Create a string array of host addresses from a collection of InetAddresses + * + * @param addrs a Collection of InetAddresses + * @return an array of Strings containing their host addresses + */ + private String[] makeStrings(Collection addrs) { + String[] result = new String[addrs.size()]; + int i = 0; + for (InetAddress addr : addrs) { + result[i++] = addr.getHostAddress(); + } + return result; + } + + private static String getStringSetting(ContentResolver cr, String which) { + return Settings.Global.getString(cr, which); + } + + private static String[] getDomainStrings(String domains) { + return (TextUtils.isEmpty(domains)) ? new String[0] : domains.split(" "); + } +} diff --git a/aosp/packages/modules/Connectivity/staticlibs/client-libs/Android.bp b/aosp/packages/modules/Connectivity/staticlibs/client-libs/Android.bp new file mode 100644 index 000000000..f665584f8 --- /dev/null +++ b/aosp/packages/modules/Connectivity/staticlibs/client-libs/Android.bp @@ -0,0 +1,27 @@ +package { + default_team: "trendy_team_fwk_core_networking", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "netd-client", + srcs: ["netd/**/*"], + sdk_version: "system_current", + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + "com.android.wifi", + ], + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + "//frameworks/base/services:__subpackages__", + "//frameworks/base/packages:__subpackages__", + "//packages/modules/Wifi/service:__subpackages__", + ], + libs: ["androidx.annotation_annotation"], + static_libs: [ + "netd_aidl_interface-lateststable-java", + "netd_event_listener_interface-lateststable-java", + ], +} diff --git a/aosp/packages/modules/Connectivity/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/aosp/packages/modules/Connectivity/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java new file mode 100644 index 000000000..d99eedcfa --- /dev/null +++ b/aosp/packages/modules/Connectivity/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static android.net.INetd.IF_STATE_DOWN; +import static android.net.INetd.IF_STATE_UP; +import static android.net.RouteInfo.RTN_THROW; +import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.system.OsConstants.EBUSY; + +import android.annotation.SuppressLint; +import android.net.INetd; +import android.net.InterfaceConfigurationParcel; +import android.net.IpPrefix; +import android.net.RouteInfo; +import android.net.RouteInfoParcel; +import android.net.TetherConfigParcel; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.SystemClock; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Collection of utilities for netd. + */ +public class NetdUtils { + private static final String TAG = NetdUtils.class.getSimpleName(); + + /** Used to modify the specified route. */ + public enum ModifyOperation { + ADD, + REMOVE, + } + + /** + * Get InterfaceConfigurationParcel from netd. + */ + public static InterfaceConfigurationParcel getInterfaceConfigParcel(@NonNull INetd netd, + @NonNull String iface) { + try { + return netd.interfaceGetCfg(iface); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } + } + + private static void validateFlag(String flag) { + if (flag.indexOf(' ') >= 0) { + throw new IllegalArgumentException("flag contains space: " + flag); + } + } + + /** + * Check whether the InterfaceConfigurationParcel contains the target flag or not. + * + * @param config The InterfaceConfigurationParcel instance. + * @param flag Target flag string to be checked. + */ + public static boolean hasFlag(@NonNull final InterfaceConfigurationParcel config, + @NonNull final String flag) { + validateFlag(flag); + final Set flagList = new HashSet(Arrays.asList(config.flags)); + return flagList.contains(flag); + } + + @VisibleForTesting + protected static String[] removeAndAddFlags(@NonNull String[] flags, @NonNull String remove, + @NonNull String add) { + final ArrayList result = new ArrayList<>(); + try { + // Validate the add flag first, so that the for-loop can be ignore once the format of + // add flag is invalid. + validateFlag(add); + for (String flag : flags) { + // Simply ignore both of remove and add flags first, then add the add flag after + // exiting the loop to prevent adding the duplicate flag. + if (remove.equals(flag) || add.equals(flag)) continue; + result.add(flag); + } + result.add(add); + return result.toArray(new String[result.size()]); + } catch (IllegalArgumentException iae) { + throw new IllegalStateException("Invalid InterfaceConfigurationParcel", iae); + } + } + + /** + * Set interface configuration to netd by passing InterfaceConfigurationParcel. + */ + public static void setInterfaceConfig(INetd netd, InterfaceConfigurationParcel configParcel) { + try { + netd.interfaceSetCfg(configParcel); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } + } + + /** + * Set the given interface up. + */ + public static void setInterfaceUp(INetd netd, String iface) { + final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface); + configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_DOWN /* remove */, + IF_STATE_UP /* add */); + setInterfaceConfig(netd, configParcel); + } + + /** + * Set the given interface down. + */ + public static void setInterfaceDown(INetd netd, String iface) { + final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface); + configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_UP /* remove */, + IF_STATE_DOWN /* add */); + setInterfaceConfig(netd, configParcel); + } + + /** Start tethering. */ + public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy, + final String[] dhcpRange) throws RemoteException, ServiceSpecificException { + final TetherConfigParcel config = new TetherConfigParcel(); + config.usingLegacyDnsProxy = usingLegacyDnsProxy; + config.dhcpRanges = dhcpRange; + netd.tetherStartWithConfiguration(config); + } + + /** Setup interface for tethering. */ + public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest) + throws RemoteException, ServiceSpecificException { + tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */); + } + + /** Setup interface with configurable retries for tethering. */ + public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest, + int maxAttempts, int pollingIntervalMs) + throws RemoteException, ServiceSpecificException { + netd.tetherInterfaceAdd(iface); + networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs); + // Activate a route to dest and IPv6 link local. + modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, + new RouteInfo(dest, null, iface, RTN_UNICAST)); + modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, + new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST)); + } + + /** + * Retry Netd#networkAddInterface for EBUSY error code. + * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode. + * There can be a race where puts the interface into the local network but interface is still + * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet. + * See b/158269544 for detail. + */ + private static void networkAddInterface(final INetd netd, final String iface, + int maxAttempts, int pollingIntervalMs) + throws ServiceSpecificException, RemoteException { + for (int i = 1; i <= maxAttempts; i++) { + try { + netd.networkAddInterface(INetd.LOCAL_NET_ID, iface); + return; + } catch (ServiceSpecificException e) { + if (e.errorCode == EBUSY && i < maxAttempts) { + SystemClock.sleep(pollingIntervalMs); + continue; + } + + Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e); + throw e; + } + } + } + + /** Reset interface for tethering. */ + public static void untetherInterface(final INetd netd, String iface) + throws RemoteException, ServiceSpecificException { + try { + netd.tetherInterfaceRemove(iface); + } finally { + netd.networkRemoveInterface(INetd.LOCAL_NET_ID, iface); + } + } + + /** Add |routes| to local network. */ + public static void addRoutesToLocalNetwork(final INetd netd, final String iface, + final List routes) { + + for (RouteInfo route : routes) { + if (!route.isDefaultRoute()) { + modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route); + } + } + + // IPv6 link local should be activated always. + modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, + new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST)); + } + + /** Remove routes from local network. */ + public static int removeRoutesFromLocalNetwork(final INetd netd, final List routes) { + int failures = 0; + + for (RouteInfo route : routes) { + try { + modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route); + } catch (IllegalStateException e) { + failures++; + } + } + + return failures; + } + + @SuppressLint("NewApi") + private static String findNextHop(final RouteInfo route) { + final String nextHop; + switch (route.getType()) { + case RTN_UNICAST: + if (route.hasGateway()) { + nextHop = route.getGateway().getHostAddress(); + } else { + nextHop = INetd.NEXTHOP_NONE; + } + break; + case RTN_UNREACHABLE: + nextHop = INetd.NEXTHOP_UNREACHABLE; + break; + case RTN_THROW: + nextHop = INetd.NEXTHOP_THROW; + break; + default: + nextHop = INetd.NEXTHOP_NONE; + break; + } + return nextHop; + } + + /** Add or remove |route|. */ + public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId, + final RouteInfo route) { + final String ifName = route.getInterface(); + final String dst = route.getDestination().toString(); + final String nextHop = findNextHop(route); + + try { + switch(op) { + case ADD: + netd.networkAddRoute(netId, ifName, dst, nextHop); + break; + case REMOVE: + netd.networkRemoveRoute(netId, ifName, dst, nextHop); + break; + default: + throw new IllegalStateException("Unsupported modify operation:" + op); + } + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } + } + + /** + * Convert a RouteInfo into a RouteInfoParcel. + */ + public static RouteInfoParcel toRouteInfoParcel(RouteInfo route) { + final String nextHop; + + switch (route.getType()) { + case RouteInfo.RTN_UNICAST: + if (route.hasGateway()) { + nextHop = route.getGateway().getHostAddress(); + } else { + nextHop = INetd.NEXTHOP_NONE; + } + break; + case RouteInfo.RTN_UNREACHABLE: + nextHop = INetd.NEXTHOP_UNREACHABLE; + break; + case RouteInfo.RTN_THROW: + nextHop = INetd.NEXTHOP_THROW; + break; + default: + nextHop = INetd.NEXTHOP_NONE; + break; + } + + final RouteInfoParcel rip = new RouteInfoParcel(); + rip.ifName = route.getInterface(); + rip.destination = route.getDestination().toString(); + rip.nextHop = nextHop; + rip.mtu = route.getMtu(); + + return rip; + } +} diff --git a/aosp/packages/modules/NetworkStack/Android.bp b/aosp/packages/modules/NetworkStack/Android.bp new file mode 100644 index 000000000..8d61344c1 --- /dev/null +++ b/aosp/packages/modules/NetworkStack/Android.bp @@ -0,0 +1,618 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// The network stack can be compiled using system_current (non-finalized) SDK, or finalized system_X +// SDK. There is also a variant that uses system_current SDK and runs in the system process +// (InProcessNetworkStack). The following structure is used to create the build rules: +// +// NetworkStackAndroidLibraryDefaults <-- common defaults for android libs +// / \ +// +NetworkStackApiStableShims --> / \ <-- +NetworkStackApiCurrentShims +// +NetworkStackReleaseApiLevel / \ +NetworkStackDevApiLevel +// +jarjar apishim.api[latest].* / \ +// to apishim.* / \ +// / \ +// / \ +// / \ android libs w/ all code +// / <- +module src/ -> \ (also used in unit tests) +// / \ | +// NetworkStackApiStableLib NetworkStackApiCurrentLib <--* +// | | +// | <-- +NetworkStackAppDefaults --> | +// | (APK build params) | +// | | +// | <-- +NetworkStackReleaseApiLevel | <-- +NetworkStackDevApiLevel +// | | +// | | +// NetworkStackApiStable NetworkStack, InProcessNetworkStack, <-- APKs +// TestNetworkStack + +// Common defaults to define SDK level +package { + default_team: "trendy_team_fwk_core_networking", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +// In some branches, this may be module_current instead of module_34 if the SDK prebuilts are not +// yet dropped there, meaning module_34 cannot be used yet. +module_34_version = "module_34" + +// TODO: remove this default and replace with ConnectivityNextEnableDefaults. This will need to be +// done separately in each branch due to merge conflicts. +// Defaults to enable/disable java targets that depend on +// NetworkStackNext, which uses development APIs. "enabled" may +// have a different value depending on the branch. +java_defaults { + name: "NetworkStackNextEnableDefaults", + enabled: true, +} + +// This is a placeholder comment to avoid merge conflicts +// as the above target may have different "enabled" values +// depending on the branch + +java_defaults { + name: "NetworkStackDevApiLevel", + min_sdk_version: "30", + sdk_version: "module_current", + libs: [ + "framework-configinfrastructure", + "framework-connectivity", + "framework-connectivity-t", + "framework-statsd", + "framework-wifi", + ], +} + +// Common defaults for NetworkStack integration tests, root tests and coverage tests +// to keep tests always running against the same target sdk version with NetworkStack. +java_defaults { + name: "NetworkStackReleaseTargetSdk", + min_sdk_version: "30", + target_sdk_version: "34", +} + +java_defaults { + name: "NetworkStackReleaseApiLevel", + defaults: ["NetworkStackReleaseTargetSdk"], + sdk_version: module_34_version, + libs: [ + "framework-configinfrastructure", + "framework-connectivity.stubs.module_lib", + "framework-connectivity-t", + "framework-statsd", + "framework-wifi", + ], +} + +// Libraries for the API shims +java_defaults { + name: "NetworkStackShimsDefaults", + libs: [ + "androidx.annotation_annotation", + "networkstack-aidl-latest", + ], + static_libs: [ + "modules-utils-build_system", + ], + apex_available: [ + "com.android.tethering", + "//apex_available:platform", // For InProcessNetworkStack + ], + min_sdk_version: "30", +} + +// Common shim code. This includes the shim interface definitions themselves, and things like +// ShimUtils and UnsupportedApiLevelException. Compiles against system_current because ShimUtils +// needs access to all Build.VERSION_CODES.*, which by definition are only in the newest SDK. +// TODO: consider moving ShimUtils into a library (or removing it in favour of SdkLevel) and compile +// this target against the lowest-supported SDK (currently 30). +java_library { + name: "NetworkStackShimsCommon", + defaults: ["NetworkStackShimsDefaults"], + srcs: ["apishim/common/**/*.java"], + sdk_version: "system_current", + visibility: ["//visibility:private"], +} + +// Each level of the shims (29, 30, ...) is its own java_library compiled against the corresponding +// system_X SDK. this ensures that each shim can only use SDK classes that exist in its SDK level. +java_library { + name: "NetworkStackApi29Shims", + defaults: ["NetworkStackShimsDefaults"], + srcs: ["apishim/29/**/*.java"], + libs: [ + "NetworkStackShimsCommon", + ], + sdk_version: "system_29", + visibility: ["//visibility:private"], +} + +java_library { + name: "NetworkStackApi30Shims", + defaults: ["NetworkStackShimsDefaults"], + srcs: [ + "apishim/30/**/*.java", + ], + libs: [ + "NetworkStackShimsCommon", + "NetworkStackApi29Shims", + ], + sdk_version: "system_30", + visibility: ["//visibility:private"], + lint: { + strict_updatability_linting: true, + + }, +} + +java_library { + name: "NetworkStackApi31Shims", + defaults: ["NetworkStackShimsDefaults"], + srcs: [ + "apishim/31/**/*.java", + ], + static_libs: [ + // Libs building against module API can only be linked against when building against + // module API (so NetworkStackApi30Shims or NetworkStackApi29Shims can't use it, since they + // use system_30 and system_29 respectively). + "net-utils-framework-common", + ], + libs: [ + "NetworkStackShimsCommon", + "NetworkStackApi29Shims", + "NetworkStackApi30Shims", + "framework-connectivity", + ], + sdk_version: "module_31", + visibility: ["//visibility:private"], +} + +java_library { + name: "NetworkStackApi33Shims", + defaults: ["NetworkStackShimsDefaults"], + srcs: [ + "apishim/33/**/*.java", + ], + libs: [ + "NetworkStackShimsCommon", + "NetworkStackApi29Shims", + "NetworkStackApi30Shims", + "NetworkStackApi31Shims", + "framework-bluetooth", + "framework-connectivity", + "framework-connectivity-t.stubs.module_lib", + "framework-tethering", + ], + sdk_version: "module_33", + visibility: ["//visibility:private"], +} + +java_library { + name: "NetworkStackApi34Shims", + defaults: ["NetworkStackShimsDefaults"], + srcs: [ + "apishim/34/**/*.java", + ], + libs: [ + "NetworkStackShimsCommon", + "NetworkStackApi29Shims", + "NetworkStackApi30Shims", + "NetworkStackApi31Shims", + "NetworkStackApi33Shims", + "framework-bluetooth", + "framework-connectivity", + "framework-connectivity-t.stubs.module_lib", + "framework-tethering", + ], + sdk_version: module_34_version, + visibility: ["//visibility:private"], +} + +// Shims for APIs being added to the current development version of Android. These APIs are not +// stable and have no defined version number. These could be called 10000, but they use the next +// integer so if the next SDK release happens to use that integer, we don't need to rename them. +// Jarjar rules are generated based on the stable shims, which do not contain this library. As a +// result, no static_lib that needs jarjar should be used here. In general, static_libs should not +// be used in this library at all; instead they can be in one of the earlier, shim libraries which +// are part of the stable shims and scanned when generating jarjar rules. +java_library { + name: "NetworkStackApi35Shims", + defaults: [ + "NetworkStackShimsDefaults", + "ConnectivityNextEnableDefaults", + ], + srcs: [ + "apishim/35/**/*.java", + ], + libs: [ + "NetworkStackShimsCommon", + "NetworkStackApi29Shims", + "NetworkStackApi30Shims", + "NetworkStackApi31Shims", + "NetworkStackApi33Shims", + "NetworkStackApi34Shims", + "framework-bluetooth", + "framework-connectivity", + "framework-connectivity-t.stubs.module_lib", + "framework-tethering", + "android.net.ipsec.ike.stubs.module_lib", + ], + sdk_version: "module_current", + visibility: ["//visibility:private"], +} + +// API current uses the API current shims directly. +// The current (in-progress) shims are in the com.android.networkstack.apishim package and are +// called directly by the networkstack code. +java_library { + name: "NetworkStackApiCurrentShims", + defaults: [ + "NetworkStackShimsDefaults", + "NetworkStackDevApiLevel", + "ConnectivityNextEnableDefaults", + ], + static_libs: [ + "NetworkStackShimsCommon", + "NetworkStackApi29Shims", + "NetworkStackApi30Shims", + "NetworkStackApi31Shims", + "NetworkStackApi33Shims", + "NetworkStackApi34Shims", + "NetworkStackApi35Shims", + ], + visibility: [ + "//packages/modules/Connectivity/Tethering", + "//packages/modules/Connectivity/service", + "//packages/modules/Connectivity/service-t", + "//packages/modules/Connectivity/tests:__subpackages__", + ], +} + +// API stable uses jarjar to rename the latest stable apishim package from +// com.android.networkstack.apishim.apiXX to com.android.networkstack.apishim, which is called by +// the networkstack code. +java_library { + name: "NetworkStackApiStableShims", + defaults: [ + "NetworkStackShimsDefaults", + "NetworkStackReleaseApiLevel", + ], + static_libs: [ + "NetworkStackShimsCommon", + "NetworkStackApi29Shims", + "NetworkStackApi30Shims", + "NetworkStackApi31Shims", + "NetworkStackApi33Shims", + "NetworkStackApi34Shims", + ], + jarjar_rules: "apishim/jarjar-rules-compat.txt", + visibility: [ + "//packages/modules/Connectivity/Tethering", + "//packages/modules/Connectivity/service", + "//packages/modules/Connectivity/service-t", + "//packages/modules/Connectivity/tests:__subpackages__", + ], +} + +// Common defaults for android libraries containing network stack code, used to compile variants of +// the network stack in the system process and in the network_stack process +java_defaults { + name: "NetworkStackAndroidLibraryDefaults", + srcs: [ + ":framework-networkstack-shared-srcs", + ], + libs: ["unsupportedappusage"], + static_libs: [ + "androidx.annotation_annotation", + "modules-utils-build_system", + "modules-utils-preconditions", + "modules-utils-shell-command-handler", + "modules-utils-statemachine", + "netd_aidl_interface-lateststable-java", + "networkstack-client", + "net-utils-framework-common", + // See note on statsprotos when adding/updating proto build rules + "datastallprotosnano", + "statsprotos", + "captiveportal-lib", + "net-utils-device-common-ip", + "net-utils-device-common-netlink", + "net-utils-device-common-struct", + ], +} + +// The versions of the android library containing network stack code compiled for each SDK variant. +android_library { + name: "NetworkStackApiCurrentLib", + defaults: [ + "NetworkStackDevApiLevel", + "NetworkStackAndroidLibraryDefaults", + "ConnectivityNextEnableDefaults", + ], + srcs: [ + "src/**/*.java", + ":statslog-networkstack-java-gen-current", + ], + static_libs: [ + "NetworkStackApiCurrentShims", + ], + manifest: "AndroidManifestBase.xml", + visibility: [ + "//frameworks/base/tests/net/integration", + "//packages/modules/Connectivity/Tethering/tests/integration", + "//packages/modules/NetworkStack/tests/unit", + "//packages/modules/NetworkStack/tests/integration", + ], +} + +android_library { + name: "NetworkStackApiStableLib", + defaults: [ + "NetworkStackReleaseApiLevel", + "NetworkStackAndroidLibraryDefaults", + ], + srcs: [ + "src/**/*.java", + ":statslog-networkstack-java-gen-stable", + ], + static_libs: [ + "NetworkStackApiStableShims", + ], + manifest: "AndroidManifestBase.xml", + visibility: [ + "//packages/modules/Connectivity/Tethering/tests/integration", + "//packages/modules/Connectivity/tests/integration", + "//packages/modules/NetworkStack/tests/unit", + "//packages/modules/NetworkStack/tests/integration", + ], +} + +java_library { + name: "DhcpPacketLib", + defaults: ["NetworkStackReleaseApiLevel"], + srcs: [ + "src/android/net/DhcpResults.java", + "src/android/net/dhcp/Dhcp*Packet.java", + ], + libs: [ + "androidx.annotation_annotation", + "framework-annotations-lib", + ], + static_libs: [ + "modules-utils-build", + "net-utils-framework-common", + "networkstack-client", + "net-utils-device-common", + ], + // If this library is ever used outside of tests, it should not use "Dhcp*Packet", and specify + // its contents explicitly. + visibility: [ + "//packages/modules/Connectivity/Tethering/tests/integration", + "//packages/modules/Connectivity/tests/cts/net", + ], +} + +java_genrule { + name: "NetworkStackJarJarRules", + tool_files: [ + // com.android.networkstack.apishim is already in the target package that is not jarjared, + // so it does not matter whether ApiStable or ApiCurrent is used to generate the jarjar + // rules. Use ApiStable to be based on most stable release configuration and be buildable + // in all branches. + ":NetworkStackApiStableLib{.jar}", + "jarjar-excludes.txt", + ], + tools: [ + "jarjar-rules-generator", + ], + out: ["NetworkStackJarJarRules.txt"], + cmd: "$(location jarjar-rules-generator) " + + "$(location :NetworkStackApiStableLib{.jar}) " + + "--prefix com.android.networkstack " + + "--excludes $(location jarjar-excludes.txt) " + + "--output $(out)", + visibility: [ + "//packages/modules/NetworkStack/tests/unit", + "//packages/modules/NetworkStack/tests/integration", + "//packages/modules/Connectivity/tests:__subpackages__", + "//packages/modules/Connectivity/Tethering/tests/integration", + ], +} + +// Common defaults for compiling the actual APK, based on the NetworkStackApiXBase android libraries +java_defaults { + name: "NetworkStackAppDefaults", + privileged: true, + jni_libs: [ + "libnativehelper_compat_libc++", + "libnetworkstackutilsjni", + ], + // Resources already included in NetworkStackBase + resource_dirs: [], + jarjar_rules: ":NetworkStackJarJarRules", + use_embedded_native_libs: true, + optimize: { + ignore_warnings: false, + proguard_flags_files: ["proguard.flags"], + }, +} + +// Non-updatable network stack running in the system server process for devices not using the module +android_app { + name: "InProcessNetworkStack", + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackDevApiLevel", + "ConnectivityNextEnableDefaults", + ], + static_libs: ["NetworkStackApiCurrentLib"], + certificate: "platform", + manifest: "AndroidManifest_InProcess.xml", + // InProcessNetworkStack is a replacement for NetworkStack + overrides: [ + "NetworkStack", + "NetworkStackNext", + ], + // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces + // the default CaptivePortalLogin. + required: [ + "PlatformCaptivePortalLogin", + ], +} + +// Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top +android_library { + name: "NetworkStackNextManifestBase", + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackDevApiLevel", + "ConnectivityNextEnableDefaults", + ], + static_libs: ["NetworkStackApiCurrentLib"], + manifest: "AndroidManifest.xml", +} + +// NetworkStack build targeting the current API release, for testing on in-development SDK +android_app { + name: "NetworkStackNext", + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackDevApiLevel", + "ConnectivityNextEnableDefaults", + ], + static_libs: ["NetworkStackNextManifestBase"], + certificate: "networkstack", + manifest: "AndroidManifest_Next.xml", + required: [ + "privapp_whitelist_com.android.networkstack", + ], + updatable: true, +} + +// Updatable network stack for finalized API +android_app { + name: "NetworkStack", + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackReleaseApiLevel", + ], + static_libs: ["NetworkStackApiStableLib"], + certificate: "networkstack", + manifest: "AndroidManifest.xml", + required: [ + "privapp_whitelist_com.android.networkstack", + ], + updatable: true, +} + +cc_library_shared { + name: "libnetworkstackutilsjni", + srcs: [ + "jni/network_stack_utils_jni.cpp", + ], + header_libs: [ + "bpf_headers", + ], + sdk_version: "30", + min_sdk_version: "30", + shared_libs: [ + "liblog", + "libnativehelper_compat_libc++", + ], + static_libs: [ + "libnetjniutils", + ], + + // We cannot use plain "libc++" here to link libc++ dynamically because it results in: + // java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found + // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't + // build because soong complains of: + // module NetworkStack missing dependencies: libc++_shared + // + // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries + // we depend on do not dynamically link libc++. This is currently the case, because liblog is + // C-only and libnativehelper_compat_libc also uses stl: "c++_static". + // + // TODO: find a better solution for this in R. + stl: "c++_static", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], +} + +genrule { + name: "statslog-networkstack-java-gen-current", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" + + " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + + " --minApiLevel 30", + out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"], +} + +genrule { + name: "statslog-networkstack-java-gen-stable", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" + + " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + + " --minApiLevel 30", + out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"], +} + +version_code_networkstack_next = "300000000" +version_code_networkstack_test = "999999999" + +genrule { + name: "NetworkStackTestAndroidManifest", + srcs: ["AndroidManifest.xml"], + out: ["TestAndroidManifest.xml"], + cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\"" + + version_code_networkstack_test + + "\"/' $(in) > $(out)", + visibility: ["//visibility:private"], +} + +android_app { + name: "TestNetworkStack", + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackReleaseApiLevel", + ], + static_libs: ["NetworkStackApiStableLib"], + certificate: "networkstack", + manifest: ":NetworkStackTestAndroidManifest", + required: [ + "privapp_whitelist_com.android.networkstack", + ], +} + +// When adding or modifying protos, the jarjar rules and possibly proguard rules need +// to be updated: proto libraries may pull additional static libraries. +java_library_static { + name: "statsprotos", + proto: { + type: "lite", + }, + srcs: [ + "src/com/android/networkstack/metrics/stats.proto", + ], + static_libs: [ + "networkstackprotos", + ], + defaults: ["NetworkStackReleaseApiLevel"], +} diff --git a/aosp/packages/modules/NetworkStack/res/values/config.xml b/aosp/packages/modules/NetworkStack/res/values/config.xml new file mode 100644 index 000000000..aed375ce9 --- /dev/null +++ b/aosp/packages/modules/NetworkStack/res/values/config.xml @@ -0,0 +1,131 @@ + + + + + + http://connectivitycheck.gstatic.com/generate_204 + + + https://www.google.com/generate_204 + + + + + http://www.google.com/gen_204 + http://play.googleapis.com/generate_204 + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + 10 + 750 + 5 + 750 + + + false + + + 0 + 0 + + + + + + + + + + + + + + true + + + + 0x88A2 + 0x88A4 + 0x88B8 + 0x88CD + 0x88E1 + 0x88E3 + + + + false + + + false + diff --git a/aosp/packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java b/aosp/packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java new file mode 100644 index 000000000..e41569833 --- /dev/null +++ b/aosp/packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -0,0 +1,2094 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; +import static android.net.dhcp.DhcpPacket.DHCP_CAPTIVE_PORTAL; +import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; +import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; +import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_SEARCHLIST; +import static android.net.dhcp.DhcpPacket.DHCP_IPV6_ONLY_PREFERRED; +import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_MTU; +import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_ROUTER; +import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; +import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; +import static android.net.dhcp.DhcpPacket.INADDR_ANY; +import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; +import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; +import static android.net.util.SocketUtils.makePacketSocketAddress; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.ETH_P_ARP; +import static android.system.OsConstants.ETH_P_IP; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOCK_RAW; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_BROADCAST; +import static android.system.OsConstants.SO_RCVBUF; +import static android.system.OsConstants.SO_REUSEADDR; + +import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; +import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; +import static com.android.net.module.util.NetworkStackConstants.IPV4_CONFLICT_ANNOUNCE_NUM; +import static com.android.net.module.util.NetworkStackConstants.IPV4_CONFLICT_PROBE_NUM; +import static com.android.net.module.util.SocketUtils.closeSocketQuietly; +import static com.android.networkstack.util.NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION; + +import android.content.Context; +import android.net.DhcpResults; +import android.net.InetAddresses; +import android.net.Layer2PacketParcelable; +import android.net.MacAddress; +import android.net.NetworkStackIpMemoryStore; +import android.net.TrafficStats; +import android.net.ip.IIpClient; +import android.net.ip.IpClient; +import android.net.ipmemorystore.NetworkAttributes; +import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener; +import android.net.ipmemorystore.OnStatusListener; +import android.net.metrics.DhcpClientEvent; +import android.net.metrics.DhcpErrorEvent; +import android.net.metrics.IpConnectivityLog; +import android.net.networkstack.aidl.dhcp.DhcpOption; +import android.net.util.HostnameTransliterator; +import android.net.util.SocketUtils; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.stats.connectivity.DhcpFeature; +import android.system.ErrnoException; +import android.system.Os; +import android.util.EventLog; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.HexDump; +import com.android.internal.util.MessageUtils; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.WakeupMessage; +import com.android.net.module.util.DeviceConfigUtils; +import com.android.net.module.util.InterfaceParams; +import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.PacketReader; +import com.android.net.module.util.arp.ArpPacket; +import com.android.networkstack.R; +import com.android.networkstack.apishim.CaptivePortalDataShimImpl; +import com.android.networkstack.apishim.SocketUtilsShimImpl; +import com.android.networkstack.metrics.IpProvisioningMetrics; +import com.android.networkstack.util.NetworkStackUtils; + +import java.io.ByteArrayOutputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +/** + * A DHCPv4 client. + * + * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android + * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine. + * + * TODO: + * + * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour). + * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not + * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a + * given SSID), it requests the last-leased IP address on the same interface, causing a delay if + * the server NAKs or a timeout if it doesn't. + * + * Known differences from current behaviour: + * + * - Does not request the "static routes" option. + * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now. + * - Requests the "broadcast" option, but does nothing with it. + * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0). + * + * @hide + */ +public class DhcpClient extends StateMachine { + + private static final String TAG = "DhcpClient"; + private static final boolean DBG = true; + private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); + private static final boolean STATE_DBG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean MSG_DBG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean PACKET_DBG = Log.isLoggable(TAG, Log.DEBUG); + + // Metrics events: must be kept in sync with server-side aggregation code. + /** Represents transitions from DhcpInitState to DhcpBoundState */ + private static final String EVENT_INITIAL_BOUND = "InitialBoundState"; + /** Represents transitions from and to DhcpBoundState via DhcpRenewingState */ + private static final String EVENT_RENEWING_BOUND = "RenewingBoundState"; + + // Timers and timeouts. + private static final int SECONDS = 1000; + private static final int FIRST_TIMEOUT_MS = 1 * SECONDS; + private static final int MAX_TIMEOUT_MS = 512 * SECONDS; + private static final int IPMEMORYSTORE_TIMEOUT_MS = 1 * SECONDS; + private static final int DHCP_INITREBOOT_TIMEOUT_MS = 5 * SECONDS; + + // The waiting time to restart the DHCP configuration process after broadcasting a + // DHCPDECLINE message, (RFC2131 3.1.5 describes client SHOULD wait a minimum of 10 + // seconds to avoid excessive traffic, but it's too long). + @VisibleForTesting + public static final String DHCP_RESTART_CONFIG_DELAY = "dhcp_restart_configuration_delay"; + private static final int DEFAULT_DHCP_RESTART_CONFIG_DELAY_MS = 1 * SECONDS; + private static final int MAX_DHCP_CLIENT_RESTART_CONFIG_DELAY_MS = 10 * SECONDS; + + // Initial random delay before sending first ARP probe. + @VisibleForTesting + public static final String ARP_FIRST_PROBE_DELAY_MS = "arp_first_probe_delay"; + private static final int DEFAULT_ARP_FIRST_PROBE_DELAY_MS = 100; + private static final int MAX_ARP_FIRST_PROBE_DELAY_MS = 1 * SECONDS; + + // Minimum delay until retransmitting the probe. The probe will be retransmitted after a + // random number of milliseconds in the range ARP_PROBE_MIN_MS and ARP_PROBE_MAX_MS. + @VisibleForTesting + public static final String ARP_PROBE_MIN_MS = "arp_probe_min"; + private static final int DEFAULT_ARP_PROBE_MIN_MS = 100; + private static final int MAX_ARP_PROBE_MIN_MS = 1 * SECONDS; + + // Maximum delay until retransmitting the probe. + @VisibleForTesting + public static final String ARP_PROBE_MAX_MS = "arp_probe_max"; + private static final int DEFAULT_ARP_PROBE_MAX_MS = 300; + private static final int MAX_ARP_PROBE_MAX_MS = 2 * SECONDS; + + // Initial random delay before sending first ARP Announcement after completing Probe packet + // transmission. + @VisibleForTesting + public static final String ARP_FIRST_ANNOUNCE_DELAY_MS = "arp_first_announce_delay"; + private static final int DEFAULT_ARP_FIRST_ANNOUNCE_DELAY_MS = 100; + private static final int MAX_ARP_FIRST_ANNOUNCE_DELAY_MS = 2 * SECONDS; + + // Time between retransmitting ARP Announcement packets. + @VisibleForTesting + public static final String ARP_ANNOUNCE_INTERVAL_MS = "arp_announce_interval"; + private static final int DEFAULT_ARP_ANNOUNCE_INTERVAL_MS = 100; + private static final int MAX_ARP_ANNOUNCE_INTERVAL_MS = 2 * SECONDS; + + // Max conflict count before configuring interface with declined IP address anyway. + private static final int MAX_CONFLICTS_COUNT = 2; + + // This is not strictly needed, since the client is asynchronous and implements exponential + // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was + // a blocking operation with a 30-second timeout. We pick 18 seconds so we can send packets at + // t=0, t=1, t=3, t=7, t=16, allowing for 10% jitter. + private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; + + // DhcpClient uses IpClient's handler. + private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; + + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. + /* Commands from controller to start/stop DHCP */ + public static final int CMD_START_DHCP = PUBLIC_BASE + 1; + public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; + + /* Notification from DHCP state machine prior to DHCP discovery/renewal */ + public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3; + /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates + * success/failure */ + public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4; + /* Notification from DHCP state machine before quitting */ + public static final int CMD_ON_QUIT = PUBLIC_BASE + 5; + + /* Command from controller to indicate DHCP discovery/renewal can continue + * after pre DHCP action is complete */ + public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6; + + /* Command and event notification to/from IpManager requesting the setting + * (or clearing) of an IPv4 LinkAddress. + */ + public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7; + public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8; + public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9; + + // Command to IpClient starting/aborting preconnection process. + public static final int CMD_START_PRECONNECTION = PUBLIC_BASE + 10; + public static final int CMD_ABORT_PRECONNECTION = PUBLIC_BASE + 11; + + // Command to rebind the leased IPv4 address on L2 roaming happened. + public static final int CMD_REFRESH_LINKADDRESS = PUBLIC_BASE + 12; + + /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */ + public static final int DHCP_SUCCESS = 1; + public static final int DHCP_FAILURE = 2; + public static final int DHCP_IPV6_ONLY = 3; + public static final int DHCP_REFRESH_FAILURE = 4; + + // Internal messages. + private static final int PRIVATE_BASE = IpClient.DHCPCLIENT_CMD_BASE + 100; + private static final int CMD_KICK = PRIVATE_BASE + 1; + private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2; + @VisibleForTesting + public static final int CMD_TIMEOUT = PRIVATE_BASE + 3; + private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4; + private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5; + private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6; + private static final int EVENT_CONFIGURATION_TIMEOUT = PRIVATE_BASE + 7; + private static final int EVENT_CONFIGURATION_OBTAINED = PRIVATE_BASE + 8; + private static final int EVENT_CONFIGURATION_INVALID = PRIVATE_BASE + 9; + private static final int EVENT_IP_CONFLICT = PRIVATE_BASE + 10; + private static final int CMD_ARP_PROBE = PRIVATE_BASE + 11; + private static final int CMD_ARP_ANNOUNCEMENT = PRIVATE_BASE + 12; + + // constant to represent this DHCP lease has been expired. + @VisibleForTesting + public static final long EXPIRED_LEASE = 1L; + + // For message logging. + private static final Class[] sMessageClasses = { DhcpClient.class }; + private static final SparseArray sMessageNames = + MessageUtils.findMessageNames(sMessageClasses); + + // DHCP parameters that we request by default. + @VisibleForTesting + /* package */ static final byte[] DEFAULT_REQUESTED_PARAMS = new byte[] { + DHCP_SUBNET_MASK, + DHCP_ROUTER, + DHCP_DNS_SERVER, + DHCP_DOMAIN_NAME, + DHCP_MTU, + DHCP_BROADCAST_ADDRESS, // TODO: currently ignored. + DHCP_LEASE_TIME, + DHCP_RENEWAL_TIME, + DHCP_REBINDING_TIME, + DHCP_VENDOR_INFO, + }; + + @NonNull + private byte[] getRequestedParams() { + // Set an initial size large enough for all optional parameters that we might request. + // mCreatorId + the size is changed + final int numOptionalParams; + if (mConfiguration.isWifiManagedProfile) { + numOptionalParams = 3 + mConfiguration.options.size(); + } else { + numOptionalParams = 2 + mConfiguration.options.size(); + } + + final ByteArrayOutputStream params = + new ByteArrayOutputStream(DEFAULT_REQUESTED_PARAMS.length + numOptionalParams); + params.write(DEFAULT_REQUESTED_PARAMS, 0, DEFAULT_REQUESTED_PARAMS.length); + if (isCapportApiEnabled()) { + params.write(DHCP_CAPTIVE_PORTAL); + } + params.write(DHCP_IPV6_ONLY_PREFERRED); + // Customized DHCP options to be put in PRL. + for (DhcpOption option : mConfiguration.options) { + if (option.value == null) params.write(option.type); + } + // Check if the target network is managed by user. + if (mConfiguration.isWifiManagedProfile) { + params.write(DHCP_DOMAIN_SEARCHLIST); + } + return params.toByteArray(); + } + + private static boolean isCapportApiEnabled() { + return CaptivePortalDataShimImpl.isSupported(); + } + + // DHCP flag that means "yes, we support unicast." + private static final boolean DO_UNICAST = false; + + // System services / libraries we use. + private final Context mContext; + private final Random mRandom; + private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); + @NonNull + private final IpProvisioningMetrics mMetrics; + + // We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can + // be off-link as well as on-link). + private FileDescriptor mUdpSock; + + // State variables. + private final StateMachine mController; + private final WakeupMessage mKickAlarm; + private final WakeupMessage mTimeoutAlarm; + private final WakeupMessage mRenewAlarm; + private final WakeupMessage mRebindAlarm; + private final WakeupMessage mExpiryAlarm; + private final String mIfaceName; + + private boolean mRegisteredForPreDhcpNotification; + private InterfaceParams mIface; + // TODO: MacAddress-ify more of this class hierarchy. + private byte[] mHwAddr; + private SocketAddress mInterfaceBroadcastAddr; + private int mTransactionId; + private long mTransactionStartMillis; + private DhcpResults mDhcpLease; + private long mDhcpLeaseExpiry; + private long mT2; + private DhcpResults mOffer; + private Configuration mConfiguration; + private Inet4Address mLastAssignedIpv4Address; + private int mConflictCount; + private long mLastAssignedIpv4AddressExpiry; + private Dependencies mDependencies; + @Nullable + private DhcpPacketHandler mDhcpPacketHandler; + @NonNull + private final NetworkStackIpMemoryStore mIpMemoryStore; + @Nullable + private final String mHostname; + + // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. + private long mLastInitEnterTime; + private long mLastBoundExitTime; + + // 32-bit unsigned integer used to indicate the number of milliseconds the DHCP client should + // disable DHCPv4. + private long mIpv6OnlyWaitTimeMs; + + // States. + private State mStoppedState = new StoppedState(); + private State mDhcpState = new DhcpState(); + private State mDhcpInitState = new DhcpInitState(); + private State mDhcpPreconnectingState = new DhcpPreconnectingState(); + private State mDhcpSelectingState = new DhcpSelectingState(); + private State mDhcpRequestingState = new DhcpRequestingState(); + private State mDhcpHaveLeaseState = new DhcpHaveLeaseState(); + private State mConfiguringInterfaceState = new ConfiguringInterfaceState(); + private State mDhcpBoundState = new DhcpBoundState(); + private State mDhcpRenewingState = new DhcpRenewingState(); + private State mDhcpRebindingState = new DhcpRebindingState(); + private State mDhcpInitRebootState = new DhcpInitRebootState(); + private State mDhcpRebootingState = new DhcpRebootingState(); + private State mObtainingConfigurationState = new ObtainingConfigurationState(); + private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState); + private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState); + private State mWaitBeforeObtainingConfigurationState = + new WaitBeforeObtainingConfigurationState(mObtainingConfigurationState); + private State mIpAddressConflictDetectingState = new IpAddressConflictDetectingState(); + private State mDhcpDecliningState = new DhcpDecliningState(); + private State mIpv6OnlyWaitState = new Ipv6OnlyWaitState(); + private State mDhcpRefreshingAddressState = new DhcpRefreshingAddressState(); + + private WakeupMessage makeWakeupMessage(String cmdName, int cmd) { + cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName; + return new WakeupMessage(mContext, getHandler(), cmdName, cmd); + } + + /** + * Encapsulates DhcpClient depencencies that's used for unit testing and + * integration testing. + */ + public static class Dependencies { + private final NetworkStackIpMemoryStore mNetworkStackIpMemoryStore; + private final IpProvisioningMetrics mMetrics; + + public Dependencies(NetworkStackIpMemoryStore store, IpProvisioningMetrics metrics) { + mNetworkStackIpMemoryStore = store; + mMetrics = metrics; + } + + /** + * Get the configuration from RRO to check whether or not to send hostname option in + * DHCPDISCOVER/DHCPREQUEST message. + */ + public boolean getSendHostnameOverlaySetting(final Context context) { + return context.getResources().getBoolean(R.bool.config_dhcp_client_hostname); + } + + /** + * Get the device name from system settings. + */ + public String getDeviceName(final Context context) { + return Settings.Global.getString(context.getContentResolver(), + Settings.Global.DEVICE_NAME); + } + + /** + * Get a IpMemoryStore instance. + */ + public NetworkStackIpMemoryStore getIpMemoryStore() { + return mNetworkStackIpMemoryStore; + } + + /** + * Get a IpProvisioningMetrics instance. + */ + public IpProvisioningMetrics getIpProvisioningMetrics() { + return mMetrics; + } + + /** + * Return whether a feature guarded by a feature flag is enabled. + * @see DeviceConfigUtils#isNetworkStackFeatureEnabled(Context, String) + */ + public boolean isFeatureEnabled(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name); + } + + /** + * Check whether one specific feature is not disabled. + * @see DeviceConfigUtils#isNetworkStackFeatureNotChickenedOut(Context, String) + */ + public boolean isFeatureNotChickenedOut(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(context, name); + } + + /** + * Get the Integer value of relevant DeviceConfig properties of Connectivity namespace. + */ + public int getIntDeviceConfig(final String name, int minimumValue, int maximumValue, + int defaultValue) { + return DeviceConfigUtils.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + name, minimumValue, maximumValue, defaultValue); + } + + /** + * Get a new wake lock to force CPU keeping awake when transmitting packets or waiting + * for timeout. + */ + public PowerManager.WakeLock getWakeLock(final PowerManager powerManager) { + return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + } + } + + // TODO: Take an InterfaceParams instance instead of an interface name String. + private DhcpClient(Context context, StateMachine controller, String iface, + Dependencies deps) { + super(TAG, controller.getHandler()); + + mDependencies = deps; + mContext = context; + mController = controller; + mIfaceName = iface; + mIpMemoryStore = deps.getIpMemoryStore(); + mMetrics = deps.getIpProvisioningMetrics(); + + // CHECKSTYLE:OFF IndentationCheck + addState(mStoppedState); + addState(mDhcpState); + addState(mDhcpInitState, mDhcpState); + addState(mWaitBeforeStartState, mDhcpState); + addState(mWaitBeforeObtainingConfigurationState, mDhcpState); + addState(mDhcpPreconnectingState, mDhcpState); + addState(mObtainingConfigurationState, mDhcpState); + addState(mDhcpSelectingState, mDhcpState); + addState(mDhcpRequestingState, mDhcpState); + addState(mIpAddressConflictDetectingState, mDhcpState); + addState(mIpv6OnlyWaitState, mDhcpState); + addState(mDhcpHaveLeaseState, mDhcpState); + addState(mConfiguringInterfaceState, mDhcpHaveLeaseState); + addState(mDhcpBoundState, mDhcpHaveLeaseState); + addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState); + addState(mDhcpRenewingState, mDhcpHaveLeaseState); + addState(mDhcpRebindingState, mDhcpHaveLeaseState); + addState(mDhcpDecliningState, mDhcpHaveLeaseState); + addState(mDhcpRefreshingAddressState, mDhcpHaveLeaseState); + addState(mDhcpInitRebootState, mDhcpState); + addState(mDhcpRebootingState, mDhcpState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mStoppedState); + + mRandom = new Random(); + + // Used to schedule packet retransmissions. + mKickAlarm = makeWakeupMessage("KICK", CMD_KICK); + // Used to time out PacketRetransmittingStates. + mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT); + // Used to schedule DHCP reacquisition. + mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); + mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); + mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); + + mHostname = new HostnameTransliterator().transliterate(deps.getDeviceName(mContext)); + mMetrics.setHostnameTransinfo(deps.getSendHostnameOverlaySetting(context), + mHostname != null); + } + + @Nullable + private String maybeGetHostnameForSending() { + boolean sendHostname = mDependencies.getSendHostnameOverlaySetting(mContext); + if (mConfiguration != null + && mConfiguration.hostnameSetting != IIpClient.HOSTNAME_SETTING_UNSET) { + sendHostname = mConfiguration.hostnameSetting == IIpClient.HOSTNAME_SETTING_SEND; + } + return sendHostname ? mHostname : null; + } + + public void registerForPreDhcpNotification() { + mRegisteredForPreDhcpNotification = true; + } + + public static DhcpClient makeDhcpClient( + Context context, StateMachine controller, InterfaceParams ifParams, + Dependencies deps) { + DhcpClient client = new DhcpClient(context, controller, ifParams.name, deps); + client.mIface = ifParams; + client.start(); + return client; + } + + /** + * check whether or not to support DHCP Rapid Commit option. + */ + public boolean isDhcpRapidCommitEnabled() { + return mDependencies.isFeatureNotChickenedOut(mContext, DHCP_RAPID_COMMIT_VERSION); + } + + /** + * check whether or not to support IP address conflict detection and DHCPDECLINE. + */ + public boolean isDhcpIpConflictDetectEnabled() { + return mDependencies.isFeatureEnabled(mContext, DHCP_IP_CONFLICT_DETECT_VERSION); + } + + /** + * Check whether to adopt slow DHCPREQUEST retransmission approach in Renewing/Rebinding state + * suggested in RFC2131 section 4.4.5. + */ + public boolean isSlowRetransmissionEnabled() { + return mDependencies.isFeatureEnabled(mContext, DHCP_SLOW_RETRANSMISSION_VERSION); + } + + private void recordMetricEnabledFeatures() { + mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT); + if (isDhcpRapidCommitEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT); + if (isDhcpIpConflictDetectEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD); + if (mConfiguration.isPreconnectionEnabled) { + mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_FILS); + } + } + + private void confirmDhcpLease(DhcpPacket packet, DhcpResults results) { + setDhcpLeaseExpiry(packet); + acceptDhcpResults(results, "Confirmed"); + } + + private boolean initInterface() { + if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName); + if (mIface == null) { + Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName); + return false; + } + + mHwAddr = mIface.macAddr.toByteArray(); + mInterfaceBroadcastAddr = SocketUtilsShimImpl.newInstance().makePacketSocketAddress( + ETH_P_IP, mIface.index, DhcpPacket.ETHER_BROADCAST); + return true; + } + + private void startNewTransaction() { + mTransactionId = mRandom.nextInt(); + mTransactionStartMillis = SystemClock.elapsedRealtime(); + } + + private boolean initUdpSocket() { + final int oldTag = TrafficStats.getAndSetThreadStatsTag( + NetworkStackConstants.TAG_SYSTEM_DHCP); + try { + mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); + Os.bind(mUdpSock, IPV4_ADDR_ANY, DhcpPacket.DHCP_CLIENT); + } catch (SocketException | ErrnoException e) { + Log.e(TAG, "Error creating UDP socket", e); + return false; + } finally { + TrafficStats.setThreadStatsTag(oldTag); + } + return true; + } + + private boolean connectUdpSock(Inet4Address to) { + try { + Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER); + return true; + } catch (SocketException | ErrnoException e) { + Log.e(TAG, "Error connecting UDP socket", e); + return false; + } + } + + private byte[] getOptionsToSkip() { + final ByteArrayOutputStream optionsToSkip = new ByteArrayOutputStream(2); + if (!isCapportApiEnabled()) optionsToSkip.write(DHCP_CAPTIVE_PORTAL); + if (!mConfiguration.isWifiManagedProfile) { + optionsToSkip.write(DHCP_DOMAIN_SEARCHLIST); + } + return optionsToSkip.toByteArray(); + } + + private class DhcpPacketHandler extends PacketReader { + private FileDescriptor mPacketSock; + + DhcpPacketHandler(Handler handler) { + super(handler); + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + try { + final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf, length, + DhcpPacket.ENCAP_L2, getOptionsToSkip()); + if (DBG) Log.d(TAG, "Received packet: " + packet); + sendMessage(CMD_RECEIVED_PACKET, packet); + } catch (DhcpPacket.ParseException e) { + Log.e(TAG, "Can't parse packet: " + e.getMessage()); + if (PACKET_DBG) { + Log.d(TAG, HexDump.dumpHexString(recvbuf, 0, length)); + } + if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) { + final int snetTagId = 0x534e4554; + final String bugId = "31850211"; + final int uid = -1; + final String data = DhcpPacket.ParseException.class.getName(); + EventLog.writeEvent(snetTagId, bugId, uid, data); + } + mMetricsLog.log(mIfaceName, new DhcpErrorEvent(e.errorCode)); + mMetrics.addDhcpErrorCode(e.errorCode); + } + } + + @Override + protected FileDescriptor createFd() { + try { + mPacketSock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */); + NetworkStackUtils.attachDhcpFilter(mPacketSock); + final SocketAddress addr = makePacketSocketAddress(ETH_P_IP, mIface.index); + Os.bind(mPacketSock, addr); + } catch (SocketException | ErrnoException e) { + logError("Error creating packet socket", e); + if (e instanceof ErrnoException + && ((ErrnoException) e).errno == 524 /* ENOTSUPP */) { + Log.wtf(TAG, "Errno: ENOTSUPP"); + } + closeFd(mPacketSock); + mPacketSock = null; + return null; + } + return mPacketSock; + } + + @Override + protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception { + try { + return Os.read(fd, packetBuffer, 0, packetBuffer.length); + } catch (IOException | ErrnoException e) { + mMetricsLog.log(mIfaceName, new DhcpErrorEvent(DhcpErrorEvent.RECEIVE_ERROR)); + throw e; + } + } + + @Override + protected void logError(@NonNull String msg, @Nullable Exception e) { + Log.e(TAG, msg, e); + } + + public int transmitPacket(final ByteBuffer buf, final SocketAddress socketAddress) + throws ErrnoException, SocketException { + return Os.sendto(mPacketSock, buf.array(), 0 /* byteOffset */, + buf.limit() /* byteCount */, 0 /* flags */, socketAddress); + } + } + + private short getSecs() { + return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000); + } + + private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) { + try { + if (encap == DhcpPacket.ENCAP_L2) { + if (DBG) Log.d(TAG, "Broadcasting " + description); + mDhcpPacketHandler.transmitPacket(buf, mInterfaceBroadcastAddr); + } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) { + if (DBG) Log.d(TAG, "Broadcasting " + description); + // We only send L3-encapped broadcasts in DhcpRebindingState, + // where we have an IP address and an unconnected UDP socket. + // + // N.B.: We only need this codepath because DhcpRequestPacket + // hardcodes the source IP address to 0.0.0.0. We could reuse + // the packet socket if this ever changes. + Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER); + } else { + // It's safe to call getpeername here, because we only send unicast packets if we + // have an IP address, and we connect the UDP socket in DhcpBoundState#enter. + if (DBG) Log.d(TAG, String.format("Unicasting %s to %s", + description, Os.getpeername(mUdpSock))); + Os.write(mUdpSock, buf); + } + } catch (ErrnoException | IOException e) { + Log.e(TAG, "Can't send packet: ", e); + return false; + } + return true; + } + + private boolean sendDiscoverPacket() { + // When Rapid Commit option is enabled, limit only the first 3 DHCPDISCOVER packets + // taking Rapid Commit option, in order to prevent the potential interoperability issue + // and be able to rollback later. See {@link DHCP_TIMEOUT_MS} for the (re)transmission + // schedule with 10% jitter. + final boolean requestRapidCommit = isDhcpRapidCommitEnabled() && (getSecs() <= 4); + final ByteBuffer packet = DhcpPacket.buildDiscoverPacket( + DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, + DO_UNICAST, getRequestedParams(), requestRapidCommit, maybeGetHostnameForSending(), + mConfiguration.options); + mMetrics.incrementCountForDiscover(); + return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); + } + + private boolean sendRequestPacket( + final Inet4Address clientAddress, final Inet4Address requestedAddress, + final Inet4Address serverAddress, final Inet4Address to) { + // TODO: should we use the transaction ID from the server? + final int encap = INADDR_ANY.equals(clientAddress) + ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP; + + final ByteBuffer packet = DhcpPacket.buildRequestPacket( + encap, mTransactionId, getSecs(), clientAddress, DO_UNICAST, mHwAddr, + requestedAddress, serverAddress, getRequestedParams(), maybeGetHostnameForSending(), + mConfiguration.options); + String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; + String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + + " request=" + requestedAddress.getHostAddress() + + " serverid=" + serverStr; + mMetrics.incrementCountForRequest(); + return transmitPacket(packet, description, encap, to); + } + + private boolean sendDeclinePacket(final Inet4Address requestedAddress, + final Inet4Address serverIdentifier) { + // Requested IP address and Server Identifier options are mandatory for DHCPDECLINE. + final ByteBuffer packet = DhcpPacket.buildDeclinePacket(DhcpPacket.ENCAP_L2, + mTransactionId, mHwAddr, requestedAddress, serverIdentifier); + return transmitPacket(packet, "DHCPDECLINE", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); + } + + private void scheduleLeaseTimers() { + if (mDhcpLeaseExpiry == 0) { + Log.d(TAG, "Infinite lease, no timer scheduling needed"); + return; + } + + final long now = SystemClock.elapsedRealtime(); + + // TODO: consider getting the renew and rebind timers from T1 and T2. + // See also: + // https://tools.ietf.org/html/rfc2131#section-4.4.5 + // https://tools.ietf.org/html/rfc1533#section-9.9 + // https://tools.ietf.org/html/rfc1533#section-9.10 + final long remainingDelay = mDhcpLeaseExpiry - now; + final long renewDelay = remainingDelay / 2; + final long rebindDelay = remainingDelay * 7 / 8; + mT2 = now + rebindDelay; + mRenewAlarm.schedule(now + renewDelay); + mRebindAlarm.schedule(now + rebindDelay); + mExpiryAlarm.schedule(now + remainingDelay); + Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s"); + Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s"); + Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s"); + } + + private void setLeaseExpiredToIpMemoryStore() { + final String l2Key = mConfiguration.l2Key; + if (l2Key == null) return; + final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); + // TODO: clear out the address and lease instead of storing an expired lease + na.setAssignedV4AddressExpiry(EXPIRED_LEASE); + + final OnStatusListener listener = status -> { + if (!status.isSuccess()) Log.e(TAG, "Failed to set lease expiry, status: " + status); + }; + mIpMemoryStore.storeNetworkAttributes(l2Key, na.build(), listener); + } + + private void maybeSaveLeaseToIpMemoryStore() { + final String l2Key = mConfiguration.l2Key; + if (l2Key == null || mDhcpLease == null || mDhcpLease.ipAddress == null) return; + final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); + na.setAssignedV4Address((Inet4Address) mDhcpLease.ipAddress.getAddress()); + na.setAssignedV4AddressExpiry((mDhcpLease.leaseDuration == INFINITE_LEASE) + ? Long.MAX_VALUE + : mDhcpLease.leaseDuration * 1000 + System.currentTimeMillis()); + na.setDnsAddresses(mDhcpLease.dnsServers); + na.setMtu(mDhcpLease.mtu); + + final OnStatusListener listener = status -> { + if (!status.isSuccess()) Log.e(TAG, "Failed to store network attrs, status: " + status); + }; + mIpMemoryStore.storeNetworkAttributes(l2Key, na.build(), listener); + } + + private void notifySuccess() { + maybeSaveLeaseToIpMemoryStore(); + mController.sendMessage( + CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); + } + + private void notifyFailure(int arg) { + setLeaseExpiredToIpMemoryStore(); + mController.sendMessage(CMD_POST_DHCP_ACTION, arg, 0, null); + } + + private void acceptDhcpResults(DhcpResults results, String msg) { + mDhcpLease = results; + if (mDhcpLease.dnsServers.isEmpty()) { + // supplement customized dns servers + final String[] dnsServersList = + mContext.getResources().getStringArray(R.array.config_default_dns_servers); + for (final String dnsServer : dnsServersList) { + try { + mDhcpLease.dnsServers.add(InetAddresses.parseNumericAddress(dnsServer)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid default DNS server: " + dnsServer, e); + } + } + } + mOffer = null; + Log.d(TAG, msg + " lease: " + mDhcpLease); + } + + private void clearDhcpState() { + mDhcpLease = null; + mDhcpLeaseExpiry = 0; + mT2 = 0; + mOffer = null; + } + + /** + * Quit the DhcpStateMachine. + * + * @hide + */ + public void doQuit() { + Log.d(TAG, "doQuit"); + quit(); + } + + @Override + protected void onQuitting() { + Log.d(TAG, "onQuitting"); + mController.sendMessage(CMD_ON_QUIT); + } + + abstract class LoggingState extends State { + private long mEnterTimeMs; + + @Override + public void enter() { + if (STATE_DBG) Log.d(TAG, "Entering state " + getName()); + mEnterTimeMs = SystemClock.elapsedRealtime(); + } + + @Override + public void exit() { + long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs; + logState(getName(), (int) durationMs); + } + + private String messageName(int what) { + return sMessageNames.get(what, Integer.toString(what)); + } + + private String messageToString(Message message) { + long now = SystemClock.uptimeMillis(); + return new StringBuilder(" ") + .append(message.getWhen() - now) + .append(messageName(message.what)) + .append(" ").append(message.arg1) + .append(" ").append(message.arg2) + .append(" ").append(message.obj) + .toString(); + } + + @Override + public boolean processMessage(Message message) { + if (MSG_DBG) { + Log.d(TAG, getName() + messageToString(message)); + } + return NOT_HANDLED; + } + + @Override + public String getName() { + // All DhcpClient's states are inner classes with a well defined name. + // Use getSimpleName() and avoid super's getName() creating new String instances. + return getClass().getSimpleName(); + } + } + + // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with + // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState. + abstract class WaitBeforeOtherState extends LoggingState { + private final State mOtherState; + + WaitBeforeOtherState(State otherState) { + mOtherState = otherState; + } + + @Override + public void enter() { + super.enter(); + mController.sendMessage(CMD_PRE_DHCP_ACTION); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_PRE_DHCP_ACTION_COMPLETE: + transitionTo(mOtherState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + /** + * Helper method to transition to the appropriate state according to whether the pre dhcp + * action (e.g. turn off power optimization while doing DHCP) is required to execute. + * waitStateForPreDhcpAction is used to wait the pre dhcp action completed before moving to + * other state. If the pre dhcp action is unnecessary, transition to the target state directly. + */ + private void preDhcpTransitionTo(final State waitStateForPreDhcpAction, + final State targetState) { + transitionTo(mRegisteredForPreDhcpNotification ? waitStateForPreDhcpAction : targetState); + } + + /** + * This class is used to convey initial configuration to DhcpClient when starting DHCP. + */ + public static class Configuration { + // This is part of the initial configuration because it is passed in on startup and + // never updated. + // TODO: decide what to do about L2 key changes while the client is connected. + @Nullable + public final String l2Key; + public final boolean isPreconnectionEnabled; + @NonNull + public final List options; + public final boolean isWifiManagedProfile; + public final int hostnameSetting; + + public Configuration(@Nullable final String l2Key, final boolean isPreconnectionEnabled, + @NonNull final List options, + final boolean isWifiManagedProfile, + final int hostnameSetting) { + this.l2Key = l2Key; + this.isPreconnectionEnabled = isPreconnectionEnabled; + this.options = options; + this.isWifiManagedProfile = isWifiManagedProfile; + this.hostnameSetting = hostnameSetting; + } + } + + class StoppedState extends State { + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_START_DHCP: + mConfiguration = (Configuration) message.obj; + if (mConfiguration.isPreconnectionEnabled) { + transitionTo(mDhcpPreconnectingState); + } else { + startInitReboot(); + } + recordMetricEnabledFeatures(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class WaitBeforeStartState extends WaitBeforeOtherState { + WaitBeforeStartState(State otherState) { + super(otherState); + } + } + + class WaitBeforeRenewalState extends WaitBeforeOtherState { + WaitBeforeRenewalState(State otherState) { + super(otherState); + } + } + + class WaitBeforeObtainingConfigurationState extends WaitBeforeOtherState { + WaitBeforeObtainingConfigurationState(State otherState) { + super(otherState); + } + } + + class DhcpState extends State { + @Override + public void enter() { + clearDhcpState(); + mConflictCount = 0; + if (initInterface() && initUdpSocket()) { + mDhcpPacketHandler = new DhcpPacketHandler(getHandler()); + if (mDhcpPacketHandler.start()) return; + Log.e(TAG, "Fail to start DHCP Packet Handler"); + } + notifyFailure(DHCP_FAILURE); + // We cannot call transitionTo because a transition is still in progress. + // Instead, ensure that we process CMD_STOP_DHCP as soon as the transition is complete. + deferMessage(obtainMessage(CMD_STOP_DHCP)); + } + + @Override + public void exit() { + if (mDhcpPacketHandler != null) { + mDhcpPacketHandler.stop(); + if (DBG) Log.d(TAG, "DHCP Packet Handler stopped"); + } + closeSocketQuietly(mUdpSock); + clearDhcpState(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_STOP_DHCP: + transitionTo(mStoppedState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + public boolean isValidPacket(DhcpPacket packet) { + // TODO: check checksum. + int xid = packet.getTransactionId(); + if (xid != mTransactionId) { + Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId); + return false; + } + if (!Arrays.equals(packet.getClientMac(), mHwAddr)) { + Log.d(TAG, "MAC addr mismatch: got " + + HexDump.toHexString(packet.getClientMac()) + ", expected " + + HexDump.toHexString(packet.getClientMac())); + return false; + } + return true; + } + + public void setDhcpLeaseExpiry(DhcpPacket packet) { + long leaseTimeMillis = packet.getLeaseTimeMillis(); + mDhcpLeaseExpiry = + (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0; + } + + abstract class TimeoutState extends LoggingState { + protected long mTimeout = 0; + + @Override + public void enter() { + super.enter(); + maybeInitTimeout(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_TIMEOUT: + timeout(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + public void exit() { + super.exit(); + mTimeoutAlarm.cancel(); + } + + protected abstract void timeout(); + private void maybeInitTimeout() { + if (mTimeout > 0) { + long alarmTime = SystemClock.elapsedRealtime() + mTimeout; + mTimeoutAlarm.schedule(alarmTime); + } + } + } + + /** + * Retransmits packets using jittered exponential backoff with an optional timeout. Packet + * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass + * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout + * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the + * state. + * + * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a + * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET + * sent by the receive thread. They may also set mTimeout and implement timeout. + */ + abstract class PacketRetransmittingState extends TimeoutState { + private int mTimer; + + @Override + public void enter() { + super.enter(); + initTimer(); + sendMessage(CMD_KICK); + } + + @Override + public boolean processMessage(Message message) { + if (super.processMessage(message) == HANDLED) { + return HANDLED; + } + + switch (message.what) { + case CMD_KICK: + sendPacket(); + scheduleKick(); + return HANDLED; + case CMD_RECEIVED_PACKET: + receivePacket((DhcpPacket) message.obj); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + public void exit() { + super.exit(); + mKickAlarm.cancel(); + } + + protected abstract boolean sendPacket(); + protected abstract void receivePacket(DhcpPacket packet); + protected void timeout() {} + + protected void initTimer() { + mTimer = FIRST_TIMEOUT_MS; + } + + protected int jitterTimer(int baseTimer) { + int maxJitter = baseTimer / 10; + int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter; + return baseTimer + jitter; + } + + protected void scheduleFastKick() { + long now = SystemClock.elapsedRealtime(); + long timeout = jitterTimer(mTimer); + long alarmTime = now + timeout; + mKickAlarm.schedule(alarmTime); + mTimer *= 2; + if (mTimer > MAX_TIMEOUT_MS) { + mTimer = MAX_TIMEOUT_MS; + } + } + + protected void scheduleKick() { + // Always adopt the fast kick schedule by default unless this method is overrided + // by subclasses. + scheduleFastKick(); + } + } + + class ObtainingConfigurationState extends LoggingState { + @Override + public void enter() { + super.enter(); + + // Set a timeout for retrieving network attributes operation + sendMessageDelayed(EVENT_CONFIGURATION_TIMEOUT, IPMEMORYSTORE_TIMEOUT_MS); + + final OnNetworkAttributesRetrievedListener listener = (status, l2Key, attributes) -> { + if (null == attributes || null == attributes.assignedV4Address) { + if (!status.isSuccess()) { + Log.e(TAG, "Error retrieving network attributes: " + status); + } + sendMessage(EVENT_CONFIGURATION_INVALID); + return; + } + sendMessage(EVENT_CONFIGURATION_OBTAINED, attributes); + }; + mIpMemoryStore.retrieveNetworkAttributes(mConfiguration.l2Key, listener); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case EVENT_CONFIGURATION_INVALID: + case EVENT_CONFIGURATION_TIMEOUT: + transitionTo(mDhcpInitState); + return HANDLED; + + case EVENT_CONFIGURATION_OBTAINED: + final long currentTime = System.currentTimeMillis(); + NetworkAttributes attributes = (NetworkAttributes) message.obj; + if (DBG) { + Log.d(TAG, "l2key: " + mConfiguration.l2Key + + " lease address: " + attributes.assignedV4Address + + " lease expiry: " + attributes.assignedV4AddressExpiry + + " current time: " + currentTime); + } + if (currentTime >= attributes.assignedV4AddressExpiry) { + // Lease has expired. + transitionTo(mDhcpInitState); + return HANDLED; + } + mLastAssignedIpv4Address = attributes.assignedV4Address; + mLastAssignedIpv4AddressExpiry = attributes.assignedV4AddressExpiry; + transitionTo(mDhcpInitRebootState); + return HANDLED; + + default: + deferMessage(message); + return HANDLED; + } + } + + @Override + public void exit() { + super.exit(); + removeMessages(EVENT_CONFIGURATION_INVALID); + removeMessages(EVENT_CONFIGURATION_TIMEOUT); + removeMessages(EVENT_CONFIGURATION_OBTAINED); + } + } + + private boolean maybeTransitionToIpv6OnlyWaitState(@NonNull final DhcpPacket packet) { + if (packet.getIpv6OnlyWaitTimeMillis() == DhcpPacket.V6ONLY_PREFERRED_ABSENCE) return false; + + mIpv6OnlyWaitTimeMs = packet.getIpv6OnlyWaitTimeMillis(); + transitionTo(mIpv6OnlyWaitState); + return true; + } + + private void receiveOfferOrAckPacket(final DhcpPacket packet, final boolean acceptRapidCommit) { + if (!isValidPacket(packet)) return; + + // 1. received the DHCPOFFER packet, process it by following RFC2131. + // 2. received the DHCPACK packet from DHCP Servers that support Rapid + // Commit option, process it by following RFC4039. + if (packet instanceof DhcpOfferPacket) { + if (maybeTransitionToIpv6OnlyWaitState(packet)) { + return; + } + mOffer = packet.toDhcpResults(); + if (mOffer != null) { + Log.d(TAG, "Got pending lease: " + mOffer); + transitionTo(mDhcpRequestingState); + } + } else if (packet instanceof DhcpAckPacket) { + // If rapid commit is not enabled in DhcpInitState, or enablePreconnection is + // not enabled in DhcpPreconnectingState, ignore DHCPACK packet. Only DHCPACK + // with the rapid commit option are valid. + if (!acceptRapidCommit || !packet.mRapidCommit) return; + + final DhcpResults results = packet.toDhcpResults(); + if (results != null) { + confirmDhcpLease(packet, results); + transitionTo(isDhcpIpConflictDetectEnabled() + ? mIpAddressConflictDetectingState : mConfiguringInterfaceState); + } + } + } + + class DhcpInitState extends PacketRetransmittingState { + public DhcpInitState() { + super(); + } + + @Override + public void enter() { + super.enter(); + startNewTransaction(); + mLastInitEnterTime = SystemClock.elapsedRealtime(); + } + + protected boolean sendPacket() { + return sendDiscoverPacket(); + } + + protected void receivePacket(DhcpPacket packet) { + receiveOfferOrAckPacket(packet, isDhcpRapidCommitEnabled()); + } + } + + private void startInitReboot() { + preDhcpTransitionTo(mWaitBeforeObtainingConfigurationState, mObtainingConfigurationState); + } + + class DhcpPreconnectingState extends TimeoutState { + // This state is used to support Fast Initial Link Setup (FILS) IP Address Setup + // procedure defined in the IEEE802.11ai (2016) currently. However, this state could + // be extended to support other intended useage as well in the future, e.g. pre-actions + // should be completed in advance before the normal DHCP solicit process starts. + DhcpPreconnectingState() { + mTimeout = FIRST_TIMEOUT_MS; + } + + @Override + public void enter() { + super.enter(); + startNewTransaction(); + mLastInitEnterTime = SystemClock.elapsedRealtime(); + sendPreconnectionPacket(); + } + + @Override + public boolean processMessage(Message message) { + if (super.processMessage(message) == HANDLED) { + return HANDLED; + } + + switch (message.what) { + case CMD_RECEIVED_PACKET: + receiveOfferOrAckPacket((DhcpPacket) message.obj, + mConfiguration.isPreconnectionEnabled); + return HANDLED; + case CMD_ABORT_PRECONNECTION: + startInitReboot(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + // This timeout is necessary and cannot just be replaced with a notification that + // preconnection is complete. This is because: + // - The preconnection complete notification could arrive before the ACK with rapid + // commit arrives. In this case we would go back to init state, pick a new transaction + // ID, and when the ACK with rapid commit arrives, we would ignore it because the + // transaction ID doesn't match. + // - We cannot just wait in this state until the ACK with rapid commit arrives, because + // if that ACK never arrives (e.g., dropped by the network), we'll never go back to init + // and send a DISCOVER. + @Override + public void timeout() { + startInitReboot(); + } + + private void sendPreconnectionPacket() { + final Layer2PacketParcelable l2Packet = new Layer2PacketParcelable(); + final ByteBuffer packet = DhcpPacket.buildDiscoverPacket( + DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, + DO_UNICAST, getRequestedParams(), true /* rapid commit */, + maybeGetHostnameForSending(), + mConfiguration.options); + + l2Packet.dstMacAddress = MacAddress.fromBytes(DhcpPacket.ETHER_BROADCAST); + l2Packet.payload = Arrays.copyOf(packet.array(), packet.limit()); + mController.sendMessage(CMD_START_PRECONNECTION, l2Packet); + } + } + + // Not implemented. We request the first offer we receive. + class DhcpSelectingState extends LoggingState { + } + + class DhcpRequestingState extends PacketRetransmittingState { + public DhcpRequestingState() { + mTimeout = DHCP_TIMEOUT_MS / 2; + } + + protected boolean sendPacket() { + return sendRequestPacket( + INADDR_ANY, // ciaddr + (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP + (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER + INADDR_BROADCAST); // packet destination address + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if ((packet instanceof DhcpAckPacket)) { + if (maybeTransitionToIpv6OnlyWaitState(packet)) { + return; + } + final DhcpResults results = packet.toDhcpResults(); + if (results != null) { + confirmDhcpLease(packet, results); + transitionTo(isDhcpIpConflictDetectEnabled() + ? mIpAddressConflictDetectingState : mConfiguringInterfaceState); + } + } else if (packet instanceof DhcpNakPacket) { + // TODO: Wait a while before returning into INIT state. + Log.d(TAG, "Received NAK, returning to INIT"); + mOffer = null; + transitionTo(mDhcpInitState); + } + } + + @Override + protected void timeout() { + // After sending REQUESTs unsuccessfully for a while, go back to init. + transitionTo(mDhcpInitState); + } + } + + class DhcpHaveLeaseState extends State { + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_EXPIRE_DHCP: + Log.d(TAG, "Lease expired!"); + notifyFailure(DHCP_FAILURE); + transitionTo(mStoppedState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + public void exit() { + // Clear any extant alarms. + mRenewAlarm.cancel(); + mRebindAlarm.cancel(); + mExpiryAlarm.cancel(); + clearDhcpState(); + // Tell IpManager to clear the IPv4 address. There is no need to + // wait for confirmation since any subsequent packets are sent from + // INADDR_ANY anyway (DISCOVER, REQUEST). + mController.sendMessage(CMD_CLEAR_LINKADDRESS); + } + } + + class ConfiguringInterfaceState extends LoggingState { + @Override + public void enter() { + super.enter(); + // We must call notifySuccess to apply the rest of the DHCP configuration (e.g., DNS + // servers) before adding the IP address to the interface. Otherwise, as soon as + // IpClient sees the IP address appear, it will enter provisioned state without any + // configuration information from DHCP. http://b/146850745. + notifySuccess(); + mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.leaseDuration, 0, + mDhcpLease.ipAddress); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case EVENT_LINKADDRESS_CONFIGURED: + transitionTo(mDhcpBoundState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + private class IpConflictDetector extends PacketReader { + private FileDescriptor mArpSock; + private final Inet4Address mTargetIp; + + IpConflictDetector(@NonNull Handler handler, @NonNull Inet4Address ipAddress) { + super(handler); + mTargetIp = ipAddress; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + try { + final ArpPacket packet = ArpPacket.parseArpPacket(recvbuf, length); + if (hasIpAddressConflict(packet, mTargetIp)) { + mMetrics.incrementCountForIpConflict(); + sendMessage(EVENT_IP_CONFLICT); + } + } catch (ArpPacket.ParseException e) { + logError("Can't parse ARP packet", e); + } + } + + @Override + protected FileDescriptor createFd() { + try { + mArpSock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */); + SocketAddress addr = makePacketSocketAddress(ETH_P_ARP, mIface.index); + Os.bind(mArpSock, addr); + return mArpSock; + } catch (SocketException | ErrnoException e) { + logError("Error creating ARP socket", e); + closeFd(mArpSock); + mArpSock = null; + return null; + } + } + + @Override + protected void logError(@NonNull String msg, @NonNull Exception e) { + Log.e(TAG, msg, e); + } + + public boolean transmitPacket(@NonNull Inet4Address targetIp, + @NonNull Inet4Address senderIp, final byte[] hwAddr, + @NonNull SocketAddress sockAddr) { + // RFC5227 3. describes both ARP Probes and Announcements use ARP Request packet. + final ByteBuffer packet = ArpPacket.buildArpPacket(DhcpPacket.ETHER_BROADCAST, hwAddr, + targetIp.getAddress(), new byte[ETHER_ADDR_LEN], senderIp.getAddress(), + (short) ARP_REQUEST); + try { + Os.sendto(mArpSock, packet.array(), 0 /* byteOffset */, + packet.limit() /* byteCount */, 0 /* flags */, sockAddr); + return true; + } catch (ErrnoException | SocketException e) { + logError("Can't send ARP packet", e); + return false; + } + } + } + + private boolean isArpProbe(@NonNull ArpPacket packet) { + return (packet.opCode == ARP_REQUEST && packet.senderIp.equals(INADDR_ANY) + && !packet.targetIp.equals(INADDR_ANY)); + } + + // RFC5227 2.1.1 says, during probing period: + // 1. the host receives any ARP packet (Request *or* Reply) on the interface where the + // probe is being performed, where the packet's 'sender IP address' is the address + // being probed for, then the host MUST treat this address as conflict. + // 2. the host receives any ARP Probe where the packet's 'target IP address' is the + // address being probed for, and the packet's 'sender hardware address' is not the + // hardware address of any of the host's interfaces, then the host SHOULD similarly + // treat this as an address conflict. + private boolean packetHasIpAddressConflict(@NonNull ArpPacket packet, + @NonNull Inet4Address targetIp) { + return (((!packet.senderIp.equals(INADDR_ANY) && packet.senderIp.equals(targetIp)) + || (isArpProbe(packet) && packet.targetIp.equals(targetIp))) + && !Arrays.equals(packet.senderHwAddress.toByteArray(), mHwAddr)); + } + + private boolean hasIpAddressConflict(@NonNull ArpPacket packet, + @NonNull Inet4Address targetIp) { + if (!packetHasIpAddressConflict(packet, targetIp)) return false; + if (DBG) { + final String senderIpString = packet.senderIp.getHostAddress(); + final String targetIpString = packet.targetIp.getHostAddress(); + final MacAddress senderMacAddress = packet.senderHwAddress; + final MacAddress hostMacAddress = MacAddress.fromBytes(mHwAddr); + Log.d(TAG, "IP address conflict detected:" + + (packet.opCode == ARP_REQUEST ? "ARP Request" : "ARP Reply") + + " ARP sender MAC: " + senderMacAddress.toString() + + " host MAC: " + hostMacAddress.toString() + + " ARP sender IP: " + senderIpString + + " ARP target IP: " + targetIpString + + " host target IP: " + targetIp.getHostAddress()); + } + return true; + } + + class IpAddressConflictDetectingState extends LoggingState { + private int mArpProbeCount; + private int mArpAnnounceCount; + private Inet4Address mTargetIp; + private IpConflictDetector mIpConflictDetector; + private PowerManager.WakeLock mTimeoutWakeLock; + + private int mArpFirstProbeDelayMs; + private int mArpProbeMaxDelayMs; + private int mArpProbeMinDelayMs; + private int mArpFirstAnnounceDelayMs; + private int mArpAnnounceIntervalMs; + + @Override + public void enter() { + super.enter(); + + mArpProbeCount = 0; + mArpAnnounceCount = 0; + + // IP address conflict detection occurs after receiving DHCPACK + // message every time, i.e. we already get an available lease from + // DHCP server, that ensures mDhcpLease should be NonNull, see + // {@link DhcpRequestingState#receivePacket} for details. + mTargetIp = (Inet4Address) mDhcpLease.ipAddress.getAddress(); + mIpConflictDetector = new IpConflictDetector(getHandler(), mTargetIp); + + // IpConflictDetector might fail to create the raw socket. + if (!mIpConflictDetector.start()) { + Log.e(TAG, "Fail to start IP Conflict Detector"); + transitionTo(mConfiguringInterfaceState); + return; + } + + // Read the customized parameters from DeviceConfig. + readIpConflictParametersFromDeviceConfig(); + if (VDBG) { + Log.d(TAG, "ARP First Probe delay: " + mArpFirstProbeDelayMs + + " ARP Probe Max delay: " + mArpProbeMaxDelayMs + + " ARP Probe Min delay: " + mArpProbeMinDelayMs + + " ARP First Announce delay: " + mArpFirstAnnounceDelayMs + + " ARP Announce interval: " + mArpAnnounceIntervalMs); + } + + // Note that when we get here, we're still processing the WakeupMessage that caused + // us to transition into this state, and thus the AlarmManager is still holding its + // wakelock. That wakelock might expire as soon as this method returns. + final PowerManager powerManager = mContext.getSystemService(PowerManager.class); + mTimeoutWakeLock = mDependencies.getWakeLock(powerManager); + mTimeoutWakeLock.acquire(); + + // RFC5227 2.1.1 describes waiting for a random time interval between 0 and + // PROBE_WAIT seconds before sending probe packets PROBE_NUM times, this delay + // helps avoid hosts send initial probe packet simultaneously upon power on. + // Probe packet transmission interval spaces randomly and uniformly between + // PROBE_MIN and PROBE_MAX. + sendMessageDelayed(CMD_ARP_PROBE, mRandom.nextInt(mArpFirstProbeDelayMs)); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_ARP_PROBE: + // According to RFC5227, wait ANNOUNCE_WAIT seconds after + // the last ARP Probe, and no conflicting ARP Reply or ARP + // Probe has been received within this period, then host can + // determine the desired IP address may be used safely. + sendArpProbe(); + if (++mArpProbeCount < IPV4_CONFLICT_PROBE_NUM) { + scheduleProbe(); + } else { + scheduleAnnounce(mArpFirstAnnounceDelayMs); + } + return HANDLED; + case CMD_ARP_ANNOUNCEMENT: + sendArpAnnounce(); + if (++mArpAnnounceCount < IPV4_CONFLICT_ANNOUNCE_NUM) { + scheduleAnnounce(mArpAnnounceIntervalMs); + } else { + transitionTo(mConfiguringInterfaceState); + } + return HANDLED; + case EVENT_IP_CONFLICT: + transitionTo(mDhcpDecliningState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + // Because the timing parameters used in IP Address detection mechanism are in + // milliseconds, WakeupMessage would be too imprecise for small timeouts. + private void scheduleProbe() { + long timeout = mRandom.nextInt(mArpProbeMaxDelayMs - mArpProbeMinDelayMs) + + mArpProbeMinDelayMs; + sendMessageDelayed(CMD_ARP_PROBE, timeout); + } + + private void scheduleAnnounce(final int timeout) { + sendMessageDelayed(CMD_ARP_ANNOUNCEMENT, timeout); + } + + @Override + public void exit() { + super.exit(); + mTimeoutWakeLock.release(); + mIpConflictDetector.stop(); + if (DBG) Log.d(TAG, "IP Conflict Detector stopped"); + removeMessages(CMD_ARP_PROBE); + removeMessages(CMD_ARP_ANNOUNCEMENT); + removeMessages(EVENT_IP_CONFLICT); + } + + // The following timing parameters are defined in RFC5227 as fixed constants, which + // are too long to adopt in the mobile network scenario, however more appropriate to + // reference these fixed value as maximumValue argument to restrict the upper bound, + // the minimum values of 10/20ms are used to avoid tight loops due to misconfiguration. + private void readIpConflictParametersFromDeviceConfig() { + // PROBE_WAIT + mArpFirstProbeDelayMs = mDependencies.getIntDeviceConfig(ARP_FIRST_PROBE_DELAY_MS, + 10, MAX_ARP_FIRST_PROBE_DELAY_MS, DEFAULT_ARP_FIRST_PROBE_DELAY_MS); + + // PROBE_MIN + mArpProbeMinDelayMs = mDependencies.getIntDeviceConfig(ARP_PROBE_MIN_MS, 10, + MAX_ARP_PROBE_MIN_MS, DEFAULT_ARP_PROBE_MIN_MS); + + // PROBE_MAX + mArpProbeMaxDelayMs = Math.max(mArpProbeMinDelayMs + 1, + mDependencies.getIntDeviceConfig(ARP_PROBE_MAX_MS, 20, MAX_ARP_PROBE_MAX_MS, + DEFAULT_ARP_PROBE_MAX_MS)); + + // ANNOUNCE_WAIT + mArpFirstAnnounceDelayMs = mDependencies.getIntDeviceConfig(ARP_FIRST_ANNOUNCE_DELAY_MS, + 20, MAX_ARP_FIRST_ANNOUNCE_DELAY_MS, DEFAULT_ARP_FIRST_ANNOUNCE_DELAY_MS); + + // ANNOUNCE_INTERVAL + mArpAnnounceIntervalMs = mDependencies.getIntDeviceConfig(ARP_ANNOUNCE_INTERVAL_MS, 20, + MAX_ARP_ANNOUNCE_INTERVAL_MS, DEFAULT_ARP_ANNOUNCE_INTERVAL_MS); + } + + private boolean sendArpProbe() { + return mIpConflictDetector.transmitPacket(mTargetIp /* target IP */, + INADDR_ANY /* sender IP */, mHwAddr, mInterfaceBroadcastAddr); + } + + private boolean sendArpAnnounce() { + return mIpConflictDetector.transmitPacket(mTargetIp /* target IP */, + mTargetIp /* sender IP */, mHwAddr, mInterfaceBroadcastAddr); + } + } + + class DhcpBoundState extends LoggingState { + @Override + public void enter() { + super.enter(); + if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) { + // There's likely no point in going into DhcpInitState here, we'll probably + // just repeat the transaction, get the same IP address as before, and fail. + // + // NOTE: It is observed that connectUdpSock() basically never fails, due to + // SO_BINDTODEVICE. Examining the local socket address shows it will happily + // return an IPv4 address from another interface, or even return "0.0.0.0". + // + // TODO: Consider deleting this check, following testing on several kernels. + notifyFailure(DHCP_FAILURE); + transitionTo(mStoppedState); + } + + scheduleLeaseTimers(); + logTimeToBoundState(); + } + + @Override + public void exit() { + super.exit(); + mLastBoundExitTime = SystemClock.elapsedRealtime(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_RENEW_DHCP: + preDhcpTransitionTo(mWaitBeforeRenewalState, mDhcpRenewingState); + return HANDLED; + case CMD_REFRESH_LINKADDRESS: + transitionTo(mDhcpRefreshingAddressState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + private void logTimeToBoundState() { + long now = SystemClock.elapsedRealtime(); + if (mLastBoundExitTime > mLastInitEnterTime) { + logState(EVENT_RENEWING_BOUND, (int) (now - mLastBoundExitTime)); + } else { + logState(EVENT_INITIAL_BOUND, (int) (now - mLastInitEnterTime)); + } + } + } + + abstract class DhcpReacquiringState extends PacketRetransmittingState { + protected String mLeaseMsg; + + @Override + public void enter() { + super.enter(); + startNewTransaction(); + } + + protected abstract Inet4Address packetDestination(); + + // Check whether DhcpClient should notify provisioning failure when receiving DHCPNAK + // in renew/rebind state or just restart reconfiguration from StoppedState. + protected abstract boolean shouldRestartOnNak(); + + // Schedule alarm for the next DHCPREQUEST tranmission. Per RFC2131 if the client + // receives no response to its DHCPREQUEST message, the client should wait one-half + // of the remaining time until T2 in RENEWING state, and one-half of the remaining + // lease time in REBINDING state, down to a minimum of 60 seconds before transmitting + // DHCPREQUEST. + private static final long MIN_DELAY_BEFORE_NEXT_REQUEST = 60_000L; + protected void scheduleSlowKick(final long target) { + final long now = SystemClock.elapsedRealtime(); + long remainingDelay = (target - now) / 2; + if (remainingDelay < MIN_DELAY_BEFORE_NEXT_REQUEST) { + remainingDelay = MIN_DELAY_BEFORE_NEXT_REQUEST; + } + mKickAlarm.schedule(now + remainingDelay); + } + + protected boolean sendPacket() { + return sendRequestPacket( + (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr + INADDR_ANY, // DHCP_REQUESTED_IP + null, // DHCP_SERVER_IDENTIFIER + packetDestination()); // packet destination address + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if ((packet instanceof DhcpAckPacket)) { + if (maybeTransitionToIpv6OnlyWaitState(packet)) { + return; + } + final DhcpResults results = packet.toDhcpResults(); + if (results != null) { + if (!mDhcpLease.ipAddress.equals(results.ipAddress)) { + Log.d(TAG, "Renewed lease not for our current IP address!"); + notifyFailure(DHCP_FAILURE); + transitionTo(mStoppedState); + return; + } + setDhcpLeaseExpiry(packet); + // Updating our notion of DhcpResults here only causes the + // DNS servers and routes to be updated in LinkProperties + // in IpManager and by any overridden relevant handlers of + // the registered IpManager.Callback. IP address changes + // are not supported here. + acceptDhcpResults(results, mLeaseMsg); + notifySuccess(); + transitionTo(mDhcpBoundState); + } + } else if (packet instanceof DhcpNakPacket) { + Log.d(TAG, "Received NAK, returning to StoppedState"); + notifyFailure(shouldRestartOnNak() ? DHCP_REFRESH_FAILURE : DHCP_FAILURE); + transitionTo(mStoppedState); + } + } + } + + class DhcpRenewingState extends DhcpReacquiringState { + public DhcpRenewingState() { + mLeaseMsg = "Renewed"; + } + + @Override + public boolean processMessage(Message message) { + if (super.processMessage(message) == HANDLED) { + return HANDLED; + } + + switch (message.what) { + case CMD_REBIND_DHCP: + transitionTo(mDhcpRebindingState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + protected Inet4Address packetDestination() { + // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but... + // http://b/25343517 . Try to make things work anyway by using broadcast renews. + return (mDhcpLease.serverAddress != null) ? + mDhcpLease.serverAddress : INADDR_BROADCAST; + } + + @Override + protected boolean shouldRestartOnNak() { + return false; + } + + @Override + protected void scheduleKick() { + if (isSlowRetransmissionEnabled()) { + scheduleSlowKick(mT2); + } else { + scheduleFastKick(); + } + } + } + + class DhcpRebindingBaseState extends DhcpReacquiringState { + @Override + public void enter() { + super.enter(); + + // We need to broadcast and possibly reconnect the socket to a + // completely different server. + closeSocketQuietly(mUdpSock); + if (!initUdpSocket()) { + Log.e(TAG, "Failed to recreate UDP socket"); + transitionTo(mDhcpInitState); + } + } + + @Override + protected Inet4Address packetDestination() { + return INADDR_BROADCAST; + } + + @Override + protected boolean shouldRestartOnNak() { + return false; + } + } + + class DhcpRebindingState extends DhcpRebindingBaseState { + DhcpRebindingState() { + mLeaseMsg = "Rebound"; + } + + @Override + protected void scheduleKick() { + if (isSlowRetransmissionEnabled()) { + scheduleSlowKick(mDhcpLeaseExpiry); + } else { + scheduleFastKick(); + } + } + } + + // The slow retransmission approach complied with RFC2131 should only be applied + // for Renewing and Rebinding state. For this state it's expected to refresh IPv4 + // link address after roam as soon as possible, obviously it should not adopt the + // slow retransmission algorithm. Create a base DhcpRebindingBaseState state and + // have both of DhcpRebindingState and DhcpRefreshingAddressState extend from it, + // then override the scheduleKick method in DhcpRebindingState to comply with slow + // schedule and keep DhcpRefreshingAddressState as-is to use the fast schedule. + class DhcpRefreshingAddressState extends DhcpRebindingBaseState { + DhcpRefreshingAddressState() { + mLeaseMsg = "Refreshing address"; + } + + @Override + protected boolean shouldRestartOnNak() { + return true; + } + } + + class DhcpInitRebootState extends DhcpRequestingState { + @Override + public void enter() { + mTimeout = DHCP_INITREBOOT_TIMEOUT_MS; + super.enter(); + startNewTransaction(); + } + + // RFC 2131 4.3.2 describes generated DHCPREQUEST message during + // INIT-REBOOT state: + // 'server identifier' MUST NOT be filled in, 'requested IP address' + // option MUST be filled in with client's notion of its previously + // assigned address. 'ciaddr' MUST be zero. The client is seeking to + // verify a previously allocated, cached configuration. Server SHOULD + // send a DHCPNAK message to the client if the 'requested IP address' + // is incorrect, or is on the wrong network. + @Override + protected boolean sendPacket() { + return sendRequestPacket( + INADDR_ANY, // ciaddr + mLastAssignedIpv4Address, // DHCP_REQUESTED_IP + null, // DHCP_SERVER_IDENTIFIER + INADDR_BROADCAST); // packet destination address + } + + @Override + public void exit() { + mLastAssignedIpv4Address = null; + mLastAssignedIpv4AddressExpiry = 0; + } + } + + class DhcpRebootingState extends LoggingState { + } + + class DhcpDecliningState extends TimeoutState { + @Override + public void enter() { + // If the host experiences MAX_CONFLICTS or more address conflicts on the + // interface, configure interface with this IP address anyway. + if (++mConflictCount > MAX_CONFLICTS_COUNT) { + transitionTo(mConfiguringInterfaceState); + return; + } + + mTimeout = mDependencies.getIntDeviceConfig(DHCP_RESTART_CONFIG_DELAY, 100, + MAX_DHCP_CLIENT_RESTART_CONFIG_DELAY_MS, DEFAULT_DHCP_RESTART_CONFIG_DELAY_MS); + super.enter(); + sendPacket(); + } + + // No need to override processMessage here since this state is + // functionally identical to its superclass TimeoutState. + protected void timeout() { + transitionTo(mDhcpInitState); + } + + private boolean sendPacket() { + return sendDeclinePacket( + (Inet4Address) mDhcpLease.ipAddress.getAddress(), // requested IP + (Inet4Address) mDhcpLease.serverAddress); // serverIdentifier + } + } + + // This state is used for IPv6-only preferred mode defined in the draft-ietf-dhc-v6only. + // For IPv6-only capable host, it will forgo obtaining an IPv4 address for V6ONLY_WAIT + // period if the network indicates that it can provide IPv6 connectivity by replying + // with a valid IPv6-only preferred option in the DHCPOFFER or DHCPACK. + class Ipv6OnlyWaitState extends TimeoutState { + @Override + public void enter() { + mTimeout = mIpv6OnlyWaitTimeMs; + super.enter(); + + // Restore power save and suspend optimization if it was disabled before. + if (mRegisteredForPreDhcpNotification) { + mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_IPV6_ONLY, 0, null); + } + } + + @Override + public void exit() { + mIpv6OnlyWaitTimeMs = 0; + } + + protected void timeout() { + startInitReboot(); + } + } + + private void logState(String name, int durationMs) { + final DhcpClientEvent event = new DhcpClientEvent.Builder() + .setMsg(name) + .setDurationMs(durationMs) + .build(); + mMetricsLog.log(mIfaceName, event); + } +} diff --git a/aosp/packages/modules/NetworkStack/src/android/net/ip/IpClient.java b/aosp/packages/modules/NetworkStack/src/android/net/ip/IpClient.java new file mode 100644 index 000000000..c1d18ae07 --- /dev/null +++ b/aosp/packages/modules/NetworkStack/src/android/net/ip/IpClient.java @@ -0,0 +1,3536 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ip; + +import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; +import static android.net.ip.IIpClient.PROV_IPV4_DISABLED; +import static android.net.ip.IIpClient.PROV_IPV6_DISABLED; +import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL; +import static android.net.ip.IIpClient.PROV_IPV6_SLAAC; +import static android.net.ip.IIpClientCallbacks.DTIM_MULTIPLIER_RESET; +import static android.net.ip.IpReachabilityMonitor.INVALID_REACHABILITY_LOSS_TYPE; +import static android.net.ip.IpReachabilityMonitor.nudEventTypeToInt; +import static android.net.util.SocketUtils.makePacketSocketAddress; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.ETH_P_ARP; +import static android.system.OsConstants.ETH_P_IPV6; +import static android.system.OsConstants.IFA_F_NODAD; +import static android.system.OsConstants.RT_SCOPE_UNIVERSE; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOCK_RAW; + +import static com.android.net.module.util.LinkPropertiesUtils.CompareResult; +import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; +import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST; +import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; +import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID; +import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR; +import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE; +import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE; +import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64; +import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64; +import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission; + +import android.annotation.SuppressLint; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.DhcpResults; +import android.net.INetd; +import android.net.IpPrefix; +import android.net.Layer2InformationParcelable; +import android.net.Layer2PacketParcelable; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.MacAddress; +import android.net.NattKeepalivePacketDataParcelable; +import android.net.NetworkStackIpMemoryStore; +import android.net.ProvisioningConfigurationParcelable; +import android.net.ProxyInfo; +import android.net.RouteInfo; +import android.net.TcpKeepalivePacketDataParcelable; +import android.net.Uri; +import android.net.apf.AndroidPacketFilter; +import android.net.apf.ApfCapabilities; +import android.net.apf.ApfFilter; +import android.net.apf.LegacyApfFilter; +import android.net.dhcp.DhcpClient; +import android.net.dhcp.DhcpPacket; +import android.net.dhcp6.Dhcp6Client; +import android.net.metrics.IpConnectivityLog; +import android.net.metrics.IpManagerEvent; +import android.net.networkstack.aidl.dhcp.DhcpOption; +import android.net.networkstack.aidl.ip.ReachabilityLossInfoParcelable; +import android.net.networkstack.aidl.ip.ReachabilityLossReason; +import android.net.shared.InitialConfiguration; +import android.net.shared.Layer2Information; +import android.net.shared.ProvisioningConfiguration; +import android.net.shared.ProvisioningConfiguration.ScanResultInfo; +import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement; +import android.os.Build; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.stats.connectivity.DisconnectCode; +import android.stats.connectivity.NetworkQuirkEvent; +import android.stats.connectivity.NudEventType; +import android.system.ErrnoException; +import android.system.Os; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.LocalLog; +import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.HexDump; +import com.android.internal.util.IState; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.MessageUtils; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.WakeupMessage; +import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.ConnectivityUtils; +import com.android.net.module.util.DeviceConfigUtils; +import com.android.net.module.util.InterfaceParams; +import com.android.net.module.util.LinkPropertiesUtils; +import com.android.net.module.util.SharedLog; +import com.android.net.module.util.SocketUtils; +import com.android.net.module.util.arp.ArpPacket; +import com.android.net.module.util.ip.InterfaceController; +import com.android.net.module.util.netlink.NetlinkUtils; +import com.android.net.module.util.structs.IaPrefixOption; +import com.android.networkstack.R; +import com.android.networkstack.apishim.NetworkInformationShimImpl; +import com.android.networkstack.apishim.SocketUtilsShimImpl; +import com.android.networkstack.apishim.common.NetworkInformationShim; +import com.android.networkstack.apishim.common.ShimUtils; +import com.android.networkstack.metrics.IpProvisioningMetrics; +import com.android.networkstack.metrics.NetworkQuirkMetrics; +import com.android.networkstack.packets.NeighborAdvertisement; +import com.android.networkstack.packets.NeighborSolicitation; +import com.android.networkstack.util.NetworkStackUtils; +import com.android.server.NetworkObserverRegistry; +import com.android.server.NetworkStackService.NetworkStackServiceManager; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.URL; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * IpClient + * + * This class provides the interface to IP-layer provisioning and maintenance + * functionality that can be used by transport layers like Wi-Fi, Ethernet, + * et cetera. + * + * [ Lifetime ] + * IpClient is designed to be instantiated as soon as the interface name is + * known and can be as long-lived as the class containing it (i.e. declaring + * it "private final" is okay). + * + * @hide + */ +public class IpClient extends StateMachine { + private static final String TAG = IpClient.class.getSimpleName(); + private static final boolean DBG = false; + + // For message logging. + private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class }; + private static final SparseArray sWhatToString = + MessageUtils.findMessageNames(sMessageClasses); + // Two static concurrent hashmaps of interface name to logging classes. + // One holds StateMachine logs and the other connectivity packet logs. + private static final ConcurrentHashMap sSmLogs = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap sPktLogs = new ConcurrentHashMap<>(); + private final NetworkStackIpMemoryStore mIpMemoryStore; + private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance(); + private final IpProvisioningMetrics mIpProvisioningMetrics = new IpProvisioningMetrics(); + private final NetworkQuirkMetrics mNetworkQuirkMetrics; + + /** + * Dump all state machine and connectivity packet logs to the specified writer. + * @param skippedIfaces Interfaces for which logs should not be dumped. + */ + public static void dumpAllLogs(PrintWriter writer, Set skippedIfaces) { + for (String ifname : sSmLogs.keySet()) { + if (skippedIfaces.contains(ifname)) continue; + + writer.println(String.format("--- BEGIN %s ---", ifname)); + + final SharedLog smLog = sSmLogs.get(ifname); + if (smLog != null) { + writer.println("State machine log:"); + smLog.dump(null, writer, null); + } + + writer.println(""); + + final LocalLog pktLog = sPktLogs.get(ifname); + if (pktLog != null) { + writer.println("Connectivity packet log:"); + pktLog.readOnlyLocalLog().dump(null, writer, null); + } + + writer.println(String.format("--- END %s ---", ifname)); + } + } + + // Use a wrapper class to log in order to ensure complete and detailed + // logging. This method is lighter weight than annotations/reflection + // and has the following benefits: + // + // - No invoked method can be forgotten. + // Any new method added to IpClient.Callback must be overridden + // here or it will never be called. + // + // - No invoking call site can be forgotten. + // Centralized logging in this way means call sites don't need to + // remember to log, and therefore no call site can be forgotten. + // + // - No variation in log format among call sites. + // Encourages logging of any available arguments, and all call sites + // are necessarily logged identically. + // + // NOTE: Log first because passed objects may or may not be thread-safe and + // once passed on to the callback they may be modified by another thread. + // + // TODO: Find an lighter weight approach. + public static class IpClientCallbacksWrapper { + private static final String PREFIX = "INVOKE "; + private final IIpClientCallbacks mCallback; + private final SharedLog mLog; + @NonNull + private final NetworkInformationShim mShim; + + @VisibleForTesting + protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log, + @NonNull NetworkInformationShim shim) { + mCallback = callback; + mLog = log; + mShim = shim; + } + + private void log(String msg) { + mLog.log(PREFIX + msg); + } + + private void log(String msg, Throwable e) { + mLog.e(PREFIX + msg, e); + } + + /** + * Callback called prior to DHCP discovery/renewal only if the pre DHCP action + * is enabled. + */ + public void onPreDhcpAction() { + log("onPreDhcpAction()"); + try { + mCallback.onPreDhcpAction(); + } catch (RemoteException e) { + log("Failed to call onPreDhcpAction", e); + } + } + + /** + * Callback called after DHCP discovery/renewal only if the pre DHCP action + * is enabled. + */ + public void onPostDhcpAction() { + log("onPostDhcpAction()"); + try { + mCallback.onPostDhcpAction(); + } catch (RemoteException e) { + log("Failed to call onPostDhcpAction", e); + } + } + + /** + * Callback called when new DHCP results are available. + */ + public void onNewDhcpResults(DhcpResults dhcpResults) { + log("onNewDhcpResults({" + dhcpResults + "})"); + try { + mCallback.onNewDhcpResults(toStableParcelable(dhcpResults)); + } catch (RemoteException e) { + log("Failed to call onNewDhcpResults", e); + } + } + + /** + * Indicates that provisioning was successful. + */ + public void onProvisioningSuccess(LinkProperties newLp) { + log("onProvisioningSuccess({" + newLp + "})"); + try { + mCallback.onProvisioningSuccess(mShim.makeSensitiveFieldsParcelingCopy(newLp)); + } catch (RemoteException e) { + log("Failed to call onProvisioningSuccess", e); + } + } + + /** + * Indicates that provisioning failed. + */ + public void onProvisioningFailure(LinkProperties newLp) { + log("onProvisioningFailure({" + newLp + "})"); + try { + mCallback.onProvisioningFailure(mShim.makeSensitiveFieldsParcelingCopy(newLp)); + } catch (RemoteException e) { + log("Failed to call onProvisioningFailure", e); + } + } + + /** + * Invoked on LinkProperties changes. + */ + public void onLinkPropertiesChange(LinkProperties newLp) { + log("onLinkPropertiesChange({" + newLp + "})"); + try { + mCallback.onLinkPropertiesChange(mShim.makeSensitiveFieldsParcelingCopy(newLp)); + } catch (RemoteException e) { + log("Failed to call onLinkPropertiesChange", e); + } + } + + /** + * Called when the internal IpReachabilityMonitor (if enabled) has detected the loss of + * required neighbors (e.g. on-link default gw or dns servers) due to NUD_FAILED. + * + * Note this method is only supported on networkstack-aidl-interfaces-v12 or below. + * For above aidl versions, the caller should call {@link onReachabilityFailure} instead. + * For callbacks extending IpClientCallbacks, this method will be called iff the callback + * does not implement onReachabilityFailure. + */ + public void onReachabilityLost(String logMsg) { + log("onReachabilityLost(" + logMsg + ")"); + try { + mCallback.onReachabilityLost(logMsg); + } catch (RemoteException e) { + log("Failed to call onReachabilityLost", e); + } + } + + /** + * Called when the IpClient state machine terminates. + */ + public void onQuit() { + log("onQuit()"); + try { + mCallback.onQuit(); + } catch (RemoteException e) { + log("Failed to call onQuit", e); + } + } + + /** + * Called to indicate that a new APF program must be installed to filter incoming packets. + */ + public boolean installPacketFilter(byte[] filter) { + log("installPacketFilter(byte[" + filter.length + "])"); + try { + mCallback.installPacketFilter(filter); + } catch (RemoteException e) { + log("Failed to call installPacketFilter", e); + return false; + } + return true; + } + + /** + * Called to indicate that the APF Program & data buffer must be read asynchronously from + * the wifi driver. + */ + public void startReadPacketFilter() { + log("startReadPacketFilter()"); + try { + mCallback.startReadPacketFilter(); + } catch (RemoteException e) { + log("Failed to call startReadPacketFilter", e); + } + } + + /** + * If multicast filtering cannot be accomplished with APF, this function will be called to + * actuate multicast filtering using another means. + */ + public void setFallbackMulticastFilter(boolean enabled) { + log("setFallbackMulticastFilter(" + enabled + ")"); + try { + mCallback.setFallbackMulticastFilter(enabled); + } catch (RemoteException e) { + log("Failed to call setFallbackMulticastFilter", e); + } + } + + /** + * Enabled/disable Neighbor Discover offload functionality. This is called, for example, + * whenever 464xlat is being started or stopped. + */ + public void setNeighborDiscoveryOffload(boolean enable) { + log("setNeighborDiscoveryOffload(" + enable + ")"); + try { + mCallback.setNeighborDiscoveryOffload(enable); + } catch (RemoteException e) { + log("Failed to call setNeighborDiscoveryOffload", e); + } + } + + /** + * Invoked on starting preconnection process. + */ + public void onPreconnectionStart(List packets) { + log("onPreconnectionStart(Layer2Packets[" + packets.size() + "])"); + try { + mCallback.onPreconnectionStart(packets); + } catch (RemoteException e) { + log("Failed to call onPreconnectionStart", e); + } + } + + /** + * Called when Neighbor Unreachability Detection fails, that might be caused by the organic + * probe or probeAll from IpReachabilityMonitor (if enabled). + */ + public void onReachabilityFailure(ReachabilityLossInfoParcelable lossInfo) { + log("onReachabilityFailure(" + lossInfo.message + ", loss reason: " + + reachabilityLossReasonToString(lossInfo.reason) + ")"); + try { + mCallback.onReachabilityFailure(lossInfo); + } catch (RemoteException e) { + log("Failed to call onReachabilityFailure", e); + } + } + + /** + * Set maximum acceptable DTIM multiplier to hardware driver. + */ + public void setMaxDtimMultiplier(int multiplier) { + try { + // {@link IWifiStaIface#setDtimMultiplier} has been implemented since U, calling + // this method on U- platforms does nothing actually. + if (!SdkLevel.isAtLeastU()) { + log("SDK level is lower than U, do not call setMaxDtimMultiplier method"); + return; + } + log("setMaxDtimMultiplier(" + multiplier + ")"); + mCallback.setMaxDtimMultiplier(multiplier); + } catch (RemoteException e) { + log("Failed to call setMaxDtimMultiplier", e); + } + } + + /** + * Get the version of the IIpClientCallbacks AIDL interface. + */ + public int getInterfaceVersion() { + log("getInterfaceVersion"); + try { + return mCallback.getInterfaceVersion(); + } catch (RemoteException e) { + // This can never happen for callers in the system server, because if the + // system server crashes, then the networkstack will crash as well. But it can + // happen for other callers such as bluetooth or telephony (if it starts to use + // IpClient). 0 will generally work but will assume an old client and disable + // all new features. + log("Failed to call getInterfaceVersion", e); + return 0; + } + } + } + + public static final String DUMP_ARG_CONFIRM = "confirm"; + + // Sysctl parameter strings. + private static final String ACCEPT_RA = "accept_ra"; + private static final String ACCEPT_RA_DEFRTR = "accept_ra_defrtr"; + @VisibleForTesting + static final String ACCEPT_RA_MIN_LFT = "accept_ra_min_lft"; + private static final String DAD_TRANSMITS = "dad_transmits"; + + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. + private static final int CMD_TERMINATE_AFTER_STOP = 1; + private static final int CMD_STOP = 2; + private static final int CMD_START = 3; + private static final int CMD_CONFIRM = 4; + private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; + // Triggered by IpClientLinkObserver to communicate netlink events. + private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; + private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; + private static final int CMD_UPDATE_HTTP_PROXY = 8; + private static final int CMD_SET_MULTICAST_FILTER = 9; + private static final int EVENT_PROVISIONING_TIMEOUT = 10; + private static final int EVENT_DHCPACTION_TIMEOUT = 11; + private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; + private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13; + private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14; + private static final int CMD_UPDATE_L2KEY_CLUSTER = 15; + private static final int CMD_COMPLETE_PRECONNECTION = 16; + private static final int CMD_UPDATE_L2INFORMATION = 17; + private static final int CMD_SET_DTIM_MULTIPLIER_AFTER_DELAY = 18; + private static final int CMD_UPDATE_APF_CAPABILITIES = 19; + private static final int EVENT_IPV6_AUTOCONF_TIMEOUT = 20; + private static final int CMD_UPDATE_APF_DATA_SNAPSHOT = 21; + + private static final int ARG_LINKPROP_CHANGED_LINKSTATE_DOWN = 0; + private static final int ARG_LINKPROP_CHANGED_LINKSTATE_UP = 1; + + // Internal commands to use instead of trying to call transitionTo() inside + // a given State's enter() method. Calling transitionTo() from enter/exit + // encounters a Log.wtf() that can cause trouble on eng builds. + private static final int CMD_ADDRESSES_CLEARED = 100; + private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101; + private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102; + + // IpClient shares a handler with DhcpClient: commands must not overlap + public static final int DHCPCLIENT_CMD_BASE = 1000; + + // IpClient shares a handler with Dhcp6Client: commands must not overlap + public static final int DHCP6CLIENT_CMD_BASE = 2000; + private static final int DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS = + IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE | IFA_F_NODAD; + + // Settings and default values. + private static final int MAX_LOG_RECORDS = 500; + private static final int MAX_PACKET_RECORDS = 100; + + @VisibleForTesting + static final String CONFIG_MIN_RDNSS_LIFETIME = "ipclient_min_rdnss_lifetime"; + private static final int DEFAULT_MIN_RDNSS_LIFETIME = + ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q) ? 120 : 0; + + @VisibleForTesting + static final String CONFIG_ACCEPT_RA_MIN_LFT = "ipclient_accept_ra_min_lft"; + @VisibleForTesting + static final int DEFAULT_ACCEPT_RA_MIN_LFT = 180; + + @VisibleForTesting + static final String CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS = + "ipclient_apf_counter_polling_interval_secs"; + @VisibleForTesting + static final int DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS = 300; + + // Used to wait for the provisioning to complete eventually and then decide the target + // network type, which gives the accurate hint to set DTIM multiplier. Per current IPv6 + // provisioning connection latency metrics, the latency of 95% can go up to 16s, so pick + // ProvisioningConfiguration.DEFAULT_TIMEOUT_MS value for this delay. + @VisibleForTesting + static final String CONFIG_INITIAL_PROVISIONING_DTIM_DELAY_MS = + "ipclient_initial_provisioning_dtim_delay"; + private static final int DEFAULT_INITIAL_PROVISIONING_DTIM_DELAY_MS = 18000; + + @VisibleForTesting + static final String CONFIG_MULTICAST_LOCK_MAX_DTIM_MULTIPLIER = + "ipclient_multicast_lock_max_dtim_multiplier"; + @VisibleForTesting + static final int DEFAULT_MULTICAST_LOCK_MAX_DTIM_MULTIPLIER = 1; + + @VisibleForTesting + static final String CONFIG_IPV6_ONLY_NETWORK_MAX_DTIM_MULTIPLIER = + "ipclient_ipv6_only_max_dtim_multiplier"; + @VisibleForTesting + static final int DEFAULT_IPV6_ONLY_NETWORK_MAX_DTIM_MULTIPLIER = 2; + + @VisibleForTesting + static final String CONFIG_IPV4_ONLY_NETWORK_MAX_DTIM_MULTIPLIER = + "ipclient_ipv4_only_max_dtim_multiplier"; + @VisibleForTesting + static final int DEFAULT_IPV4_ONLY_NETWORK_MAX_DTIM_MULTIPLIER = 9; + + @VisibleForTesting + static final String CONFIG_DUAL_STACK_MAX_DTIM_MULTIPLIER = + "ipclient_dual_stack_max_dtim_multiplier"; + // The default value for dual-stack networks is the min of maximum DTIM multiplier to use for + // IPv4-only and IPv6-only networks. + @VisibleForTesting + static final int DEFAULT_DUAL_STACK_MAX_DTIM_MULTIPLIER = 2; + + @VisibleForTesting + static final String CONFIG_BEFORE_IPV6_PROV_MAX_DTIM_MULTIPLIER = + "ipclient_before_ipv6_prov_max_dtim_multiplier"; + @VisibleForTesting + static final int DEFAULT_BEFORE_IPV6_PROV_MAX_DTIM_MULTIPLIER = 1; + + // Timeout to wait for IPv6 autoconf via SLAAC to complete before starting DHCPv6 + // prefix delegation. + @VisibleForTesting + static final String CONFIG_IPV6_AUTOCONF_TIMEOUT = "ipclient_ipv6_autoconf_timeout"; + private static final int DEFAULT_IPV6_AUTOCONF_TIMEOUT_MS = 5000; + + private static final boolean NO_CALLBACKS = false; + private static final boolean SEND_CALLBACKS = true; + + private static final int IMMEDIATE_FAILURE_DURATION = 0; + + private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1; + private static final int PROV_CHANGE_LOST_PROVISIONING = 2; + private static final int PROV_CHANGE_GAINED_PROVISIONING = 3; + private static final int PROV_CHANGE_STILL_PROVISIONED = 4; + + // onReachabilityFailure callback is added since networkstack-aidl-interfaces-v13. + @VisibleForTesting + static final int VERSION_ADDED_REACHABILITY_FAILURE = 13; + + // Specific vendor OUI(3 bytes)/vendor specific type(1 byte) pattern for upstream hotspot + // device detection. Add new byte array pattern below in turn. + private static final List METERED_IE_PATTERN_LIST = Collections.singletonList( + new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xf2, (byte) 0x06 } + ); + + // Allows Wi-Fi to pass in DHCP options when particular vendor-specific IEs are present. + // Maps each DHCP option code to a list of IEs, any of which will allow that option. + private static final Map> DHCP_OPTIONS_ALLOWED = Map.of( + (byte) 60, Collections.singletonList( + // KT OUI: 00:17:C3, type: 33(0x21). See b/236745261. + new byte[]{ (byte) 0x00, (byte) 0x17, (byte) 0xc3, (byte) 0x21 }), + (byte) 77, Collections.singletonList( + // KT OUI: 00:17:C3, type: 33(0x21). See b/236745261. + new byte[]{ (byte) 0x00, (byte) 0x17, (byte) 0xc3, (byte) 0x21 }) + ); + + // Initialize configurable particular SSID set supporting DHCP Roaming feature. See + // b/131797393 for more details. + private static final Set DHCP_ROAMING_SSID_SET = new HashSet<>( + Arrays.asList( + "0001docomo", "ollehWiFi", "olleh GiGa WiFi", "KT WiFi", + "KT GiGA WiFi", "marente" + )); + + private final State mStoppedState = new StoppedState(); + private final State mStoppingState = new StoppingState(); + private final State mClearingIpAddressesState = new ClearingIpAddressesState(); + private final State mStartedState = new StartedState(); + private final State mRunningState = new RunningState(); + private final State mPreconnectingState = new PreconnectingState(); + + private final String mTag; + private final Context mContext; + private final String mInterfaceName; + @VisibleForTesting + protected final IpClientCallbacksWrapper mCallback; + private final Dependencies mDependencies; + private final CountDownLatch mShutdownLatch; + private final ConnectivityManager mCm; + private final INetd mNetd; + private final NetworkObserverRegistry mObserverRegistry; + private final IpClientLinkObserver mLinkObserver; + private final WakeupMessage mProvisioningTimeoutAlarm; + private final WakeupMessage mDhcpActionTimeoutAlarm; + private final SharedLog mLog; + private final LocalLog mConnectivityPacketLog; + private final MessageHandlingLogger mMsgStateLogger; + private final IpConnectivityLog mMetricsLog; + private final InterfaceController mInterfaceCtrl; + // Set of IPv6 addresses for which unsolicited gratuitous NA packets have been sent. + private final Set mGratuitousNaTargetAddresses = new HashSet<>(); + // Set of IPv6 addresses from which multicast NS packets have been sent. + private final Set mMulticastNsSourceAddresses = new HashSet<>(); + // Set of delegated prefixes. + private final Set mDelegatedPrefixes = new HashSet<>(); + @Nullable + private final DevicePolicyManager mDevicePolicyManager; + + // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled. + private final int mMinRdnssLifetimeSec; + + // Ignore any nonzero RA section with lifetime below this value. + private final int mAcceptRaMinLft; + + // Polling interval to update APF data snapshot + private final long mApfCounterPollingIntervalMs; + + // Experiment flag read from device config. + private final boolean mDhcp6PrefixDelegationEnabled; + private final boolean mUseNewApfFilter; + private final boolean mEnableIpClientIgnoreLowRaLifetime; + private final boolean mApfShouldHandleLightDoze; + private final boolean mEnableApfPollingCounters; + private final boolean mPopulateLinkAddressLifetime; + + private InterfaceParams mInterfaceParams; + + /** + * Non-final member variables accessed only from within our StateMachine. + */ + private LinkProperties mLinkProperties; + private android.net.shared.ProvisioningConfiguration mConfiguration; + private IpReachabilityMonitor mIpReachabilityMonitor; + private DhcpClient mDhcpClient; + private Dhcp6Client mDhcp6Client; + private DhcpResults mDhcpResults; + private String mTcpBufferSizes; + private ProxyInfo mHttpProxy; + private AndroidPacketFilter mApfFilter; + private String mL2Key; // The L2 key for this network, for writing into the memory store + private String mCluster; // The cluster for this network, for writing into the memory store + private int mCreatorUid; // Uid of app creating the wifi configuration + private boolean mMulticastFiltering; + private long mStartTimeMillis; + private long mIPv6ProvisioningDtimGracePeriodMillis; + private MacAddress mCurrentBssid; + private boolean mHasDisabledAcceptRaDefrtrOnProvLoss; + private Integer mDadTransmits = null; + private int mMaxDtimMultiplier = DTIM_MULTIPLIER_RESET; + private ApfCapabilities mCurrentApfCapabilities; + private WakeupMessage mIpv6AutoconfTimeoutAlarm = null; + + /** + * Reading the snapshot is an asynchronous operation initiated by invoking + * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an + * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable + * signals when a new snapshot is ready. + */ + private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable(); + + public static class Dependencies { + /** + * Get interface parameters for the specified interface. + */ + public InterfaceParams getInterfaceParams(String ifname) { + return InterfaceParams.getByName(ifname); + } + + /** + * Get a INetd connector. + */ + public INetd getNetd(Context context) { + return INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)); + } + + /** + * Get a IpMemoryStore instance. + */ + public NetworkStackIpMemoryStore getIpMemoryStore(Context context, + NetworkStackServiceManager nssManager) { + return new NetworkStackIpMemoryStore(context, nssManager.getIpMemoryStoreService()); + } + + /** + * Get a DhcpClient instance. + */ + public DhcpClient makeDhcpClient(Context context, StateMachine controller, + InterfaceParams ifParams, DhcpClient.Dependencies deps) { + return DhcpClient.makeDhcpClient(context, controller, ifParams, deps); + } + + /** + * Get a Dhcp6Client instance. + */ + public Dhcp6Client makeDhcp6Client(Context context, StateMachine controller, + InterfaceParams ifParams, Dhcp6Client.Dependencies deps) { + return Dhcp6Client.makeDhcp6Client(context, controller, ifParams, deps); + } + + /** + * Get a DhcpClient Dependencies instance. + */ + public DhcpClient.Dependencies getDhcpClientDependencies( + NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) { + return new DhcpClient.Dependencies(ipMemoryStore, metrics); + } + + /** + * Get a Dhcp6Client Dependencies instance. + */ + public Dhcp6Client.Dependencies getDhcp6ClientDependencies() { + return new Dhcp6Client.Dependencies(); + } + + /** + * Read an integer DeviceConfig property. + */ + public int getDeviceConfigPropertyInt(String name, int defaultValue) { + return DeviceConfigUtils.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, name, + defaultValue); + } + + /** + * Get a IpConnectivityLog instance. + */ + public IpConnectivityLog getIpConnectivityLog() { + return new IpConnectivityLog(); + } + + /** + * Get a NetworkQuirkMetrics instance. + */ + public NetworkQuirkMetrics getNetworkQuirkMetrics() { + return new NetworkQuirkMetrics(); + } + + /** + * Get a IpReachabilityMonitor instance. + */ + public IpReachabilityMonitor getIpReachabilityMonitor(Context context, + InterfaceParams ifParams, Handler h, SharedLog log, + IpReachabilityMonitor.Callback callback, boolean usingMultinetworkPolicyTracker, + IpReachabilityMonitor.Dependencies deps, final INetd netd) { + return new IpReachabilityMonitor(context, ifParams, h, log, callback, + usingMultinetworkPolicyTracker, deps, netd); + } + + /** + * Get a IpReachabilityMonitor dependencies instance. + */ + public IpReachabilityMonitor.Dependencies getIpReachabilityMonitorDeps(Context context, + String name) { + return IpReachabilityMonitor.Dependencies.makeDefault(context, name); + } + + /** + * Return whether a feature guarded by a feature flag is enabled. + * @see DeviceConfigUtils#isNetworkStackFeatureEnabled(Context, String) + */ + public boolean isFeatureEnabled(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name); + } + + /** + * Check whether one specific feature is not disabled. + * @see DeviceConfigUtils#isNetworkStackFeatureNotChickenedOut(Context, String) + */ + public boolean isFeatureNotChickenedOut(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(context, name); + } + + /** + * Create an APF filter if apfCapabilities indicates support for packet filtering using + * APF programs. + * @see ApfFilter#maybeCreate + */ + public AndroidPacketFilter maybeCreateApfFilter(Context context, + ApfFilter.ApfConfiguration config, InterfaceParams ifParams, + IpClientCallbacksWrapper cb, NetworkQuirkMetrics networkQuirkMetrics, + boolean useNewApfFilter) { + if (useNewApfFilter) { + return ApfFilter.maybeCreate(context, config, ifParams, cb, networkQuirkMetrics); + } else { + return LegacyApfFilter.maybeCreate(context, config, ifParams, cb, + networkQuirkMetrics); + } + } + + /** + * Check if a specific IPv6 sysctl file exists or not. + */ + public boolean hasIpv6Sysctl(final String ifname, final String name) { + final String path = "/proc/sys/net/ipv6/conf/" + ifname + "/" + name; + final File sysctl = new File(path); + return sysctl.exists(); + } + /** + * Get the configuration from RRO to check whether or not to send domain search list + * option in DHCPDISCOVER/DHCPREQUEST message. + */ + public boolean getSendDomainSearchListOption(final Context context) { + return context.getResources().getBoolean(R.bool.config_dhcp_client_domain_search_list); + } + + } + + public IpClient(Context context, String ifName, IIpClientCallbacks callback, + NetworkObserverRegistry observerRegistry, NetworkStackServiceManager nssManager) { + this(context, ifName, callback, observerRegistry, nssManager, new Dependencies()); + } + + @VisibleForTesting + public IpClient(Context context, String ifName, IIpClientCallbacks callback, + NetworkObserverRegistry observerRegistry, NetworkStackServiceManager nssManager, + Dependencies deps) { + super(IpClient.class.getSimpleName() + "." + ifName); + Objects.requireNonNull(ifName); + Objects.requireNonNull(callback); + + mTag = getName(); + + mDevicePolicyManager = (DevicePolicyManager) + context.getSystemService(Context.DEVICE_POLICY_SERVICE); + mContext = context; + mInterfaceName = ifName; + mDependencies = deps; + mMetricsLog = deps.getIpConnectivityLog(); + mNetworkQuirkMetrics = deps.getNetworkQuirkMetrics(); + mShutdownLatch = new CountDownLatch(1); + mCm = mContext.getSystemService(ConnectivityManager.class); + mObserverRegistry = observerRegistry; + mIpMemoryStore = deps.getIpMemoryStore(context, nssManager); + + sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag)); + mLog = sSmLogs.get(mInterfaceName); + sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS)); + mConnectivityPacketLog = sPktLogs.get(mInterfaceName); + mMsgStateLogger = new MessageHandlingLogger(); + mCallback = new IpClientCallbacksWrapper(callback, mLog, mShim); + + // TODO: Consider creating, constructing, and passing in some kind of + // InterfaceController.Dependencies class. + mNetd = deps.getNetd(mContext); + mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog); + + mDhcp6PrefixDelegationEnabled = mDependencies.isFeatureEnabled(mContext, + IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION); + + mMinRdnssLifetimeSec = mDependencies.getDeviceConfigPropertyInt( + CONFIG_MIN_RDNSS_LIFETIME, DEFAULT_MIN_RDNSS_LIFETIME); + mAcceptRaMinLft = mDependencies.getDeviceConfigPropertyInt(CONFIG_ACCEPT_RA_MIN_LFT, + DEFAULT_ACCEPT_RA_MIN_LFT); + mApfCounterPollingIntervalMs = mDependencies.getDeviceConfigPropertyInt( + CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, + DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS; + mUseNewApfFilter = SdkLevel.isAtLeastV() || mDependencies.isFeatureEnabled(context, + APF_NEW_RA_FILTER_VERSION); + mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context, + APF_POLLING_COUNTERS_VERSION); + mEnableIpClientIgnoreLowRaLifetime = + SdkLevel.isAtLeastV() || mDependencies.isFeatureEnabled(context, + IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION); + // Light doze mode status checking API is only available at T or later releases. + mApfShouldHandleLightDoze = SdkLevel.isAtLeastT() && mDependencies.isFeatureNotChickenedOut( + mContext, APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE); + mPopulateLinkAddressLifetime = mDependencies.isFeatureEnabled(context, + IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION); + + IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration( + mMinRdnssLifetimeSec, mPopulateLinkAddressLifetime); + + mLinkObserver = new IpClientLinkObserver( + mContext, getHandler(), + mInterfaceName, + new IpClientLinkObserver.Callback() { + @Override + public void update(boolean linkState) { + sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED, linkState + ? ARG_LINKPROP_CHANGED_LINKSTATE_UP + : ARG_LINKPROP_CHANGED_LINKSTATE_DOWN); + } + + @Override + public void onIpv6AddressRemoved(final Inet6Address address) { + // The update of Gratuitous NA target addresses set or unsolicited + // multicast NS source addresses set should be only accessed from the + // handler thread of IpClient StateMachine, keeping the behaviour + // consistent with relying on the non-blocking NetworkObserver callbacks, + // see {@link registerObserverForNonblockingCallback}. This can be done + // by either sending a message to StateMachine or posting a handler. + if (address.isLinkLocalAddress()) return; + getHandler().post(() -> { + mLog.log("Remove IPv6 GUA " + address + + " from both Gratuituous NA and Multicast NS sets"); + mGratuitousNaTargetAddresses.remove(address); + mMulticastNsSourceAddresses.remove(address); + }); + } + + @Override + public void onClatInterfaceStateUpdate(boolean add) { + // TODO: when clat interface was removed, consider sending a message to + // the IpClient main StateMachine thread, in case "NDO enabled" state + // becomes tied to more things that 464xlat operation. + getHandler().post(() -> { + mCallback.setNeighborDiscoveryOffload(add ? false : true); + }); + } + }, + config, mLog, mDependencies + ); + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + + mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); + mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); + + // Anything the StateMachine may access must have been instantiated + // before this point. + configureAndStartStateMachine(); + + // Anything that may send messages to the StateMachine must only be + // configured to do so after the StateMachine has started (above). + startStateMachineUpdaters(); + } + + /** + * Make a IIpClient connector to communicate with this IpClient. + */ + public IIpClient makeConnector() { + return new IpClientConnector(); + } + + class IpClientConnector extends IIpClient.Stub { + @Override + public void completedPreDhcpAction() { + enforceNetworkStackCallingPermission(); + IpClient.this.completedPreDhcpAction(); + } + @Override + public void confirmConfiguration() { + enforceNetworkStackCallingPermission(); + IpClient.this.confirmConfiguration(); + } + @Override + public void readPacketFilterComplete(byte[] data) { + enforceNetworkStackCallingPermission(); + IpClient.this.readPacketFilterComplete(data); + } + @Override + public void shutdown() { + enforceNetworkStackCallingPermission(); + IpClient.this.shutdown(); + } + @Override + public void startProvisioning(ProvisioningConfigurationParcelable req) { + enforceNetworkStackCallingPermission(); + IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req, + mCallback.getInterfaceVersion())); + } + @Override + public void stop() { + enforceNetworkStackCallingPermission(); + IpClient.this.stop(); + } + @Override + public void setL2KeyAndGroupHint(String l2Key, String cluster) { + enforceNetworkStackCallingPermission(); + IpClient.this.setL2KeyAndCluster(l2Key, cluster); + } + @Override + public void setTcpBufferSizes(String tcpBufferSizes) { + enforceNetworkStackCallingPermission(); + IpClient.this.setTcpBufferSizes(tcpBufferSizes); + } + @Override + public void setHttpProxy(ProxyInfo proxyInfo) { + enforceNetworkStackCallingPermission(); + IpClient.this.setHttpProxy(proxyInfo); + } + @Override + public void setMulticastFilter(boolean enabled) { + enforceNetworkStackCallingPermission(); + IpClient.this.setMulticastFilter(enabled); + } + @Override + public void addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) { + enforceNetworkStackCallingPermission(); + IpClient.this.addKeepalivePacketFilter(slot, pkt); + } + @Override + public void addNattKeepalivePacketFilter(int slot, NattKeepalivePacketDataParcelable pkt) { + enforceNetworkStackCallingPermission(); + IpClient.this.addNattKeepalivePacketFilter(slot, pkt); + } + @Override + public void removeKeepalivePacketFilter(int slot) { + enforceNetworkStackCallingPermission(); + IpClient.this.removeKeepalivePacketFilter(slot); + } + @Override + public void notifyPreconnectionComplete(boolean success) { + enforceNetworkStackCallingPermission(); + IpClient.this.notifyPreconnectionComplete(success); + } + @Override + public void updateLayer2Information(Layer2InformationParcelable info) { + enforceNetworkStackCallingPermission(); + IpClient.this.updateLayer2Information(info); + } + @Override + public void updateApfCapabilities(ApfCapabilities apfCapabilities) { + enforceNetworkStackCallingPermission(); + IpClient.this.updateApfCapabilities(apfCapabilities); + } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } + } + + public String getInterfaceName() { + return mInterfaceName; + } + + private void configureAndStartStateMachine() { + // CHECKSTYLE:OFF IndentationCheck + addState(mStoppedState); + addState(mStartedState); + addState(mPreconnectingState, mStartedState); + addState(mClearingIpAddressesState, mStartedState); + addState(mRunningState, mStartedState); + addState(mStoppingState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mStoppedState); + + super.start(); + } + + private void startStateMachineUpdaters() { + mObserverRegistry.registerObserverForNonblockingCallback(mLinkObserver); + } + + private void stopStateMachineUpdaters() { + mObserverRegistry.unregisterObserver(mLinkObserver); + mLinkObserver.clearInterfaceParams(); + mLinkObserver.shutdown(); + } + + private boolean isGratuitousNaEnabled() { + return mDependencies.isFeatureNotChickenedOut(mContext, IPCLIENT_GRATUITOUS_NA_VERSION); + } + + private boolean isGratuitousArpNaRoamingEnabled() { + return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION); + } + + @VisibleForTesting + static MacAddress getInitialBssid(final Layer2Information layer2Info, + final ScanResultInfo scanResultInfo, boolean isAtLeastS) { + MacAddress bssid = null; + // http://b/185202634 + // ScanResultInfo is not populated in some situations. + // On S and above, prefer getting the BSSID from the Layer2Info. + // On R and below, get the BSSID from the ScanResultInfo and fall back to + // getting it from the Layer2Info. This ensures no regressions if any R + // devices pass in a null or meaningless BSSID in the Layer2Info. + if (!isAtLeastS && scanResultInfo != null) { + try { + bssid = MacAddress.fromString(scanResultInfo.getBssid()); + } catch (IllegalArgumentException e) { + Log.wtf(TAG, "Invalid BSSID: " + scanResultInfo.getBssid() + + " in provisioning configuration", e); + } + } + if (bssid == null && layer2Info != null) { + bssid = layer2Info.mBssid; + } + return bssid; + } + + @Override + protected void onQuitting() { + mCallback.onQuit(); + mShutdownLatch.countDown(); + } + + /** + * Shut down this IpClient instance altogether. + */ + public void shutdown() { + stop(); + sendMessage(CMD_TERMINATE_AFTER_STOP); + } + + /** + * Start provisioning with the provided parameters. + */ + public void startProvisioning(ProvisioningConfiguration req) { + if (!req.isValid()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + return; + } + + mCurrentBssid = getInitialBssid(req.mLayer2Info, req.mScanResultInfo, + ShimUtils.isAtLeastS()); + mCurrentApfCapabilities = req.mApfCapabilities; + mCreatorUid = req.mCreatorUid; + if (req.mLayer2Info != null) { + mL2Key = req.mLayer2Info.mL2Key; + mCluster = req.mLayer2Info.mCluster; + } + sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req)); + } + + /** + * Stop this IpClient. + * + *

    This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}. + * The message "arg1" parameter is used to record the disconnect code metrics. + * Usually this method is called by the peer (e.g. wifi) intentionally to stop IpClient, + * consider that's the normal user termination. + */ + public void stop() { + sendMessage(CMD_STOP, DisconnectCode.DC_NORMAL_TERMINATION.getNumber()); + } + + /** + * Confirm the provisioning configuration. + */ + public void confirmConfiguration() { + sendMessage(CMD_CONFIRM); + } + + /** + * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be + * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to + * proceed. + */ + public void completedPreDhcpAction() { + sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); + } + + /** + * Indicate that packet filter read is complete. + */ + public void readPacketFilterComplete(byte[] data) { + sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data); + } + + /** + * Set the TCP buffer sizes to use. + * + * This may be called, repeatedly, at any time before or after a call to + * #startProvisioning(). The setting is cleared upon calling #stop(). + */ + public void setTcpBufferSizes(String tcpBufferSizes) { + sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes); + } + + /** + * Set the L2 key and cluster for storing info into the memory store. + * + * This method is only supported on Q devices. For R or above releases, + * caller should call #updateLayer2Information() instead. + */ + public void setL2KeyAndCluster(String l2Key, String cluster) { + if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { + sendMessage(CMD_UPDATE_L2KEY_CLUSTER, new Pair<>(l2Key, cluster)); + } + } + + /** + * Set the HTTP Proxy configuration to use. + * + * This may be called, repeatedly, at any time before or after a call to + * #startProvisioning(). The setting is cleared upon calling #stop(). + */ + public void setHttpProxy(ProxyInfo proxyInfo) { + sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo); + } + + /** + * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering, + * if not, Callback.setFallbackMulticastFilter() is called. + */ + public void setMulticastFilter(boolean enabled) { + sendMessage(CMD_SET_MULTICAST_FILTER, enabled); + } + + /** + * Called by WifiStateMachine to add TCP keepalive packet filter before setting up + * keepalive offload. + */ + public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) { + sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt); + } + + /** + * Called by WifiStateMachine to add NATT keepalive packet filter before setting up + * keepalive offload. + */ + public void addNattKeepalivePacketFilter(int slot, + @NonNull NattKeepalivePacketDataParcelable pkt) { + sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */ , pkt); + } + + /** + * Called by WifiStateMachine to remove keepalive packet filter after stopping keepalive + * offload. + */ + public void removeKeepalivePacketFilter(int slot) { + sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF, slot, 0 /* Unused */); + } + + /** + * Notify IpClient that preconnection is complete and that the link is ready for use. + * The success parameter indicates whether the packets passed in by onPreconnectionStart were + * successfully sent to the network or not. + */ + public void notifyPreconnectionComplete(boolean success) { + sendMessage(CMD_COMPLETE_PRECONNECTION, success ? 1 : 0); + } + + /** + * Update the network bssid, L2Key and cluster on L2 roaming happened. + */ + public void updateLayer2Information(@NonNull Layer2InformationParcelable info) { + sendMessage(CMD_UPDATE_L2INFORMATION, info); + } + + /** + * Update the APF capabilities. + * + * This method will update the APF capabilities used in IpClient and decide if a new APF + * program should be installed to filter the incoming packets based on that. So far this + * method only allows for the APF capabilities to go from null to non-null, and no other + * changes are allowed. One use case is when WiFi interface switches from secondary to + * primary in STA+STA mode. + */ + public void updateApfCapabilities(@NonNull ApfCapabilities apfCapabilities) { + sendMessage(CMD_UPDATE_APF_CAPABILITIES, apfCapabilities); + } + + /** + * Dump logs of this IpClient. + */ + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) { + // Execute confirmConfiguration() and take no further action. + confirmConfiguration(); + return; + } + + // Thread-unsafe access to mApfFilter but just used for debugging. + final AndroidPacketFilter apfFilter = mApfFilter; + final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration; + final ApfCapabilities apfCapabilities = (provisioningConfig != null) + ? provisioningConfig.mApfCapabilities : null; + + IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println(mTag + " APF dump:"); + pw.increaseIndent(); + if (apfFilter != null && apfCapabilities != null + && apfCapabilities.apfVersionSupported > 0) { + if (apfCapabilities.hasDataAccess()) { + // Request a new snapshot, then wait for it. + mApfDataSnapshotComplete.close(); + mCallback.startReadPacketFilter(); + if (!mApfDataSnapshotComplete.block(1000)) { + pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT"); + } + } + apfFilter.dump(pw); + + } else { + pw.print("No active ApfFilter; "); + if (provisioningConfig == null) { + pw.println("IpClient not yet started."); + } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) { + pw.println("Hardware does not support APF."); + } else { + pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities); + } + } + pw.decreaseIndent(); + pw.println(); + pw.println(mTag + " current ProvisioningConfiguration:"); + pw.increaseIndent(); + pw.println(Objects.toString(provisioningConfig, "N/A")); + pw.decreaseIndent(); + + final IpReachabilityMonitor iprm = mIpReachabilityMonitor; + if (iprm != null) { + pw.println(); + pw.println(mTag + " current IpReachabilityMonitor state:"); + pw.increaseIndent(); + iprm.dump(pw); + pw.decreaseIndent(); + } + + pw.println(); + pw.println(mTag + " StateMachine dump:"); + pw.increaseIndent(); + mLog.dump(fd, pw, args); + pw.decreaseIndent(); + + pw.println(); + pw.println(mTag + " connectivity packet log:"); + pw.println(); + pw.println("Debug with python and scapy via:"); + pw.println("shell$ python"); + pw.println(">>> from scapy import all as scapy"); + pw.println(">>> scapy.Ether(\"\".decode(\"hex\")).show2()"); + pw.println(); + + pw.increaseIndent(); + mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); + pw.decreaseIndent(); + } + + + /** + * Internals. + */ + + @Override + protected String getWhatToString(int what) { + return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what)); + } + + @Override + protected String getLogRecString(Message msg) { + final String logLine = String.format( + "%s/%d %d %d %s [%s]", + mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index, + msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger); + + final String richerLogLine = getWhatToString(msg.what) + " " + logLine; + mLog.log(richerLogLine); + if (DBG) { + Log.d(mTag, richerLogLine); + } + + mMsgStateLogger.reset(); + return logLine; + } + + @Override + protected boolean recordLogRec(Message msg) { + // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy, + // and we already log any LinkProperties change that results in an + // invocation of IpClient.Callback#onLinkPropertiesChange(). + final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED); + if (!shouldLog) { + mMsgStateLogger.reset(); + } + return shouldLog; + } + + private void logError(String fmt, Throwable e, Object... args) { + mLog.e(String.format(fmt, args), e); + } + + private void logError(String fmt, Object... args) { + logError(fmt, null, args); + } + + // This needs to be called with care to ensure that our LinkProperties + // are in sync with the actual LinkProperties of the interface. For example, + // we should only call this if we know for sure that there are no IP addresses + // assigned to the interface, etc. + private void resetLinkProperties() { + mLinkObserver.clearLinkProperties(); + mConfiguration = null; + mDhcpResults = null; + mTcpBufferSizes = ""; + mHttpProxy = null; + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + } + + private void recordMetric(final int type) { + // We may record error metrics prior to starting. + // Map this to IMMEDIATE_FAILURE_DURATION. + final long duration = (mStartTimeMillis > 0) + ? (SystemClock.elapsedRealtime() - mStartTimeMillis) + : IMMEDIATE_FAILURE_DURATION; + mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration)); + } + + // Record the DisconnectCode and transition to StoppingState. + private void transitionToStoppingState(final DisconnectCode code) { + mIpProvisioningMetrics.setDisconnectCode(code); + transitionTo(mStoppingState); + } + + // Convert reachability loss reason enum to a string. + private static String reachabilityLossReasonToString(int reason) { + switch (reason) { + case ReachabilityLossReason.ROAM: + return "reachability_loss_after_roam"; + case ReachabilityLossReason.CONFIRM: + return "reachability_loss_after_confirm"; + case ReachabilityLossReason.ORGANIC: + return "reachability_loss_organic"; + default: + return "unknown"; + } + } + + private static boolean hasIpv6LinkLocalInterfaceRoute(final LinkProperties lp) { + for (RouteInfo r : lp.getRoutes()) { + if (r.getDestination().equals(new IpPrefix("fe80::/64")) + && r.getGateway().isAnyLocalAddress()) { + return true; + } + } + return false; + } + + private static boolean hasIpv6LinkLocalAddress(final LinkProperties lp) { + for (LinkAddress address : lp.getLinkAddresses()) { + if (address.isIpv6() && address.getAddress().isLinkLocalAddress()) { + return true; + } + } + return false; + } + + // LinkProperties has a link-local (fe80::xxx) IPv6 address and route to fe80::/64 destination. + private boolean isIpv6LinkLocalProvisioned(final LinkProperties lp) { + if (mConfiguration == null + || mConfiguration.mIPv6ProvisioningMode != PROV_IPV6_LINKLOCAL) return false; + if (hasIpv6LinkLocalAddress(lp) && hasIpv6LinkLocalInterfaceRoute(lp)) return true; + return false; + } + + // For now: use WifiStateMachine's historical notion of provisioned. + @VisibleForTesting + boolean isProvisioned(final LinkProperties lp, final InitialConfiguration config) { + // For historical reasons, we should connect even if all we have is an IPv4 + // address and nothing else. If IPv6 link-local only mode is enabled and + // it's provisioned without IPv4, then still connecting once IPv6 link-local + // address is ready to use and route to fe80::/64 destination is up. + if (lp.hasIpv4Address() || lp.isProvisioned() || isIpv6LinkLocalProvisioned(lp)) { + return true; + } + if (config == null) { + return false; + } + + // When an InitialConfiguration is specified, ignore any difference with previous + // properties and instead check if properties observed match the desired properties. + return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); + } + + // Set "/proc/sys/net/ipv6/conf/${iface}/${name}" with the given specific value. + private void setIpv6Sysctl(@NonNull final String name, int value) { + try { + mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceName, + name, Integer.toString(value)); + } catch (Exception e) { + Log.e(mTag, "Failed to set " + name + " to " + value + ": " + e); + } + } + + // Read "/proc/sys/net/ipv6/conf/${iface}/${name}". + private Integer getIpv6Sysctl(@NonNull final String name) { + try { + return Integer.parseInt(mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, + mInterfaceName, name)); + } catch (RemoteException | ServiceSpecificException e) { + logError("Couldn't read " + name + " on " + mInterfaceName, e); + return null; + } + } + + // TODO: Investigate folding all this into the existing static function + // LinkProperties.compareProvisioning() or some other single function that + // takes two LinkProperties objects and returns a ProvisioningChange + // object that is a correct and complete assessment of what changed, taking + // account of the asymmetries described in the comments in this function. + // Then switch to using it everywhere (IpReachabilityMonitor, etc.). + private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { + int delta; + InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; + final boolean wasProvisioned = isProvisioned(oldLp, config); + final boolean isProvisioned = isProvisioned(newLp, config); + + if (!wasProvisioned && isProvisioned) { + delta = PROV_CHANGE_GAINED_PROVISIONING; + } else if (wasProvisioned && isProvisioned) { + delta = PROV_CHANGE_STILL_PROVISIONED; + } else if (!wasProvisioned && !isProvisioned) { + delta = PROV_CHANGE_STILL_NOT_PROVISIONED; + } else { + // (wasProvisioned && !isProvisioned) + // + // Note that this is true even if we lose a configuration element + // (e.g., a default gateway) that would not be required to advance + // into provisioned state. This is intended: if we have a default + // router and we lose it, that's a sure sign of a problem, but if + // we connect to a network with no IPv4 DNS servers, we consider + // that to be a network without DNS servers and connect anyway. + // + // See the comment below. + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + final boolean lostIPv6 = oldLp.isIpv6Provisioned() && !newLp.isIpv6Provisioned(); + final boolean lostIPv4Address = oldLp.hasIpv4Address() && !newLp.hasIpv4Address(); + final boolean lostIPv6Router = oldLp.hasIpv6DefaultRoute() && !newLp.hasIpv6DefaultRoute(); + + // If bad wifi avoidance is disabled, then ignore IPv6 loss of + // provisioning. Otherwise, when a hotspot that loses Internet + // access sends out a 0-lifetime RA to its clients, the clients + // will disconnect and then reconnect, avoiding the bad hotspot, + // instead of getting stuck on the bad hotspot. http://b/31827713 . + // + // This is incorrect because if the hotspot then regains Internet + // access with a different prefix, TCP connections on the + // deprecated addresses will remain stuck. + // + // Note that we can still be disconnected by IpReachabilityMonitor + // if the IPv6 default gateway (but not the IPv6 DNS servers; see + // accompanying code in IpReachabilityMonitor) is unreachable. + final boolean ignoreIPv6ProvisioningLoss = mHasDisabledAcceptRaDefrtrOnProvLoss + || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker + && !mCm.shouldAvoidBadWifi()); + + // Additionally: + // + // Partial configurations (e.g., only an IPv4 address with no DNS + // servers and no default route) are accepted as long as DHCPv4 + // succeeds. On such a network, isProvisioned() will always return + // false, because the configuration is not complete, but we want to + // connect anyway. It might be a disconnected network such as a + // Chromecast or a wireless printer, for example. + // + // Because on such a network isProvisioned() will always return false, + // delta will never be LOST_PROVISIONING. So check for loss of + // provisioning here too. + if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + // Additionally: + // + // If the previous link properties had a global IPv6 address and an + // IPv6 default route then also consider the loss of that default route + // to be a loss of provisioning. See b/27962810. + if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { + // Although link properties have lost IPv6 default route in this case, if IPv4 is still + // working with appropriate routes and DNS servers, we can keep the current connection + // without disconnecting from the network, just disable accept_ra_defrtr sysctl on that + // given network until to the next provisioning. + // + // Disabling IPv6 stack will result in all IPv6 connectivity torn down and all IPv6 + // sockets being closed, the non-routable IPv6 DNS servers will be stripped out, so + // applications will be able to reconnect immediately over IPv4. See b/131781810. + // + // Sometimes disabling IPv6 stack can cause other problems(see b/179222860), conversely, + // disabling accept_ra_defrtr can still keep the interface IPv6 capable, but no longer + // learns the default router from incoming RA, partial IPv6 connectivity will remain on + // the interface, through which applications can still communicate locally. + if (newLp.isIpv4Provisioned()) { + // Restart ipv6 with accept_ra_defrtr set to 0. + mInterfaceCtrl.disableIPv6(); + startIPv6(0 /* accept_ra_defrtr */); + + mNetworkQuirkMetrics.setEvent(NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST); + mNetworkQuirkMetrics.statsWrite(); + mHasDisabledAcceptRaDefrtrOnProvLoss = true; + delta = PROV_CHANGE_STILL_PROVISIONED; + mLog.log("Disabled accept_ra_defrtr sysctl on loss of IPv6 default router"); + } else { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + } + + return delta; + } + + private void dispatchCallback(int delta, LinkProperties newLp) { + switch (delta) { + case PROV_CHANGE_GAINED_PROVISIONING: + if (DBG) { + Log.d(mTag, "onProvisioningSuccess()"); + } + recordMetric(IpManagerEvent.PROVISIONING_OK); + mCallback.onProvisioningSuccess(newLp); + break; + + case PROV_CHANGE_LOST_PROVISIONING: + if (DBG) { + Log.d(mTag, "onProvisioningFailure()"); + } + recordMetric(IpManagerEvent.PROVISIONING_FAIL); + mCallback.onProvisioningFailure(newLp); + break; + + default: + if (DBG) { + Log.d(mTag, "onLinkPropertiesChange()"); + } + mCallback.onLinkPropertiesChange(newLp); + break; + } + } + + // Updates all IpClient-related state concerned with LinkProperties. + // Returns a ProvisioningChange for possibly notifying other interested + // parties that are not fronted by IpClient. + private int setLinkProperties(LinkProperties newLp) { + if (mApfFilter != null) { + mApfFilter.setLinkProperties(newLp); + } + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.updateLinkProperties(newLp); + } + + int delta = compareProvisioning(mLinkProperties, newLp); + mLinkProperties = new LinkProperties(newLp); + + if (delta == PROV_CHANGE_GAINED_PROVISIONING) { + // TODO: Add a proper ProvisionedState and cancel the alarm in + // its enter() method. + mProvisioningTimeoutAlarm.cancel(); + } + + return delta; + } + + private LinkProperties assembleLinkProperties() { + // [1] Create a new LinkProperties object to populate. + LinkProperties newLp = new LinkProperties(); + newLp.setInterfaceName(mInterfaceName); + + // [2] Pull in data from netlink: + // - IPv4 addresses + // - IPv6 addresses + // - IPv6 routes + // - IPv6 DNS servers + // + // N.B.: this is fundamentally race-prone and should be fixed by + // changing IpClientLinkObserver from a hybrid edge/level model to an + // edge-only model, or by giving IpClient its own netlink socket(s) + // so as to track all required information directly. + LinkProperties netlinkLinkProperties = mLinkObserver.getLinkProperties(); + newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); + for (RouteInfo route : netlinkLinkProperties.getRoutes()) { + newLp.addRoute(route); + } + addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); + mShim.setNat64Prefix(newLp, mShim.getNat64Prefix(netlinkLinkProperties)); + + // Check if any link address update from netlink. + final CompareResult results = + LinkPropertiesUtils.compareAddresses(mLinkProperties, newLp); + // In the case that there are multiple netlink update events about a global IPv6 address + // derived from the delegated prefix, a flag-only change event(e.g. due to the duplicate + // address detection) will cause an identical IP address to be put into both Added and + // Removed list based on the CompareResult implementation. To prevent a prefix from being + // mistakenly removed from the delegate prefix list, it is better to always check the + // removed list before checking the added list(e.g. anyway we can add the removed prefix + // back again). + for (LinkAddress la : results.removed) { + if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) { + final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH); + mDelegatedPrefixes.remove(prefix); + } + // TODO: remove onIpv6AddressRemoved callback. + } + + for (LinkAddress la : results.added) { + if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) { + final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH); + mDelegatedPrefixes.add(prefix); + } + } + + // [3] Add in data from DHCPv4, if available. + // + // mDhcpResults is never shared with any other owner so we don't have + // to worry about concurrent modification. + if (mDhcpResults != null) { + final List routes = + mDhcpResults.toStaticIpConfiguration().getRoutes(mInterfaceName); + for (RouteInfo route : routes) { + newLp.addRoute(route); + } + addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); + if (mDhcpResults.dmnsrchList.size() == 0) { + newLp.setDomains(mDhcpResults.domains); + } else { + final String domainsString = mDhcpResults.appendDomainsSearchList(); + newLp.setDomains(TextUtils.isEmpty(domainsString) ? null : domainsString); + } + + if (mDhcpResults.mtu != 0) { + newLp.setMtu(mDhcpResults.mtu); + } + + if (mDhcpResults.serverAddress != null) { + mShim.setDhcpServerAddress(newLp, mDhcpResults.serverAddress); + } + + final String capportUrl = mDhcpResults.captivePortalApiUrl; + // Uri.parse does no syntax check; do a simple check to eliminate garbage. + // If the URL is still incorrect data fetching will fail later, which is fine. + if (isParseableUrl(capportUrl)) { + NetworkInformationShimImpl.newInstance() + .setCaptivePortalApiUrl(newLp, Uri.parse(capportUrl)); + } + // TODO: also look at the IPv6 RA (netlink) for captive portal URL + } + + // [4] Add route with delegated prefix according to the global address update. + if (mDhcp6PrefixDelegationEnabled) { + for (IpPrefix destination : mDelegatedPrefixes) { + // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to + // this route based on the delegated prefix. To prevent the traffic loop + // between host and upstream delegated router. Because we specify the + // IFA_F_NOPREFIXROUTE when adding the IPv6 address, the kernel does not + // create a delegated prefix route, as a result, the user space won't + // receive any RTM_NEWROUTE message about the delegated prefix, we still + // need to install an unreachable route for the delegated prefix manually + // in LinkProperties to notify the caller this update. + // TODO: support RTN_BLACKHOLE in netd and use that on newer Android + // versions. + final RouteInfo route = new RouteInfo(destination, + null /* gateway */, mInterfaceName, RTN_UNREACHABLE); + newLp.addRoute(route); + } + } + + // [5] Add in TCP buffer sizes and HTTP Proxy config, if available. + if (!TextUtils.isEmpty(mTcpBufferSizes)) { + newLp.setTcpBufferSizes(mTcpBufferSizes); + } + if (mHttpProxy != null) { + newLp.setHttpProxy(mHttpProxy); + } + + // [6] Add data from InitialConfiguration + if (mConfiguration != null && mConfiguration.mInitialConfig != null) { + InitialConfiguration config = mConfiguration.mInitialConfig; + // Add InitialConfiguration routes and dns server addresses once all addresses + // specified in the InitialConfiguration have been observed with Netlink. + if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { + for (IpPrefix prefix : config.directlyConnectedRoutes) { + newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName, RTN_UNICAST)); + } + } + addAllReachableDnsServers(newLp, config.dnsServers); + } + final LinkProperties oldLp = mLinkProperties; + if (DBG) { + Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", + netlinkLinkProperties, newLp, oldLp)); + } + + // TODO: also learn via netlink routes specified by an InitialConfiguration and specified + // from a static IP v4 config instead of manually patching them in in steps [3] and [5]. + return newLp; + } + + private static boolean isParseableUrl(String url) { + // Verify that a URL has a reasonable format that can be parsed as per the URL constructor. + // This does not use Patterns.WEB_URL as that pattern excludes URLs without TLDs, such as on + // localhost. + if (url == null) return false; + try { + new URL(url); + return true; + } catch (MalformedURLException e) { + return false; + } + } + + private static void addAllReachableDnsServers( + LinkProperties lp, Iterable dnses) { + // TODO: Investigate deleting this reachability check. We should be + // able to pass everything down to netd and let netd do evaluation + // and RFC6724-style sorting. + for (InetAddress dns : dnses) { + if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { + lp.addDnsServer(dns); + } + } + } + + private void transmitPacket(final ByteBuffer packet, final SocketAddress sockAddress, + final String msg) { + FileDescriptor sock = null; + try { + sock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */); + Os.sendto(sock, packet.array(), 0 /* byteOffset */, packet.limit() /* byteCount */, + 0 /* flags */, sockAddress); + } catch (SocketException | ErrnoException e) { + logError(msg, e); + } finally { + SocketUtils.closeSocketQuietly(sock); + } + } + + private void sendGratuitousNA(final Inet6Address srcIp, final Inet6Address targetIp) { + final int flags = 0; // R=0, S=0, O=0 + final Inet6Address dstIp = IPV6_ADDR_ALL_ROUTERS_MULTICAST; + // Ethernet multicast destination address: 33:33:00:00:00:02. + final MacAddress dstMac = NetworkStackUtils.ipv6MulticastToEthernetMulticast(dstIp); + final ByteBuffer packet = NeighborAdvertisement.build(mInterfaceParams.macAddr, dstMac, + srcIp, dstIp, flags, targetIp); + final SocketAddress sockAddress = + SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6, + mInterfaceParams.index, dstMac.toByteArray()); + + transmitPacket(packet, sockAddress, "Failed to send Gratuitous Neighbor Advertisement"); + } + + private void sendGratuitousARP(final Inet4Address srcIp) { + final ByteBuffer packet = ArpPacket.buildArpPacket(ETHER_BROADCAST /* dstMac */, + mInterfaceParams.macAddr.toByteArray() /* srcMac */, + srcIp.getAddress() /* targetIp */, + ETHER_BROADCAST /* targetHwAddress */, + srcIp.getAddress() /* senderIp */, (short) ARP_REPLY); + final SocketAddress sockAddress = + makePacketSocketAddress(ETH_P_ARP, mInterfaceParams.index); + + transmitPacket(packet, sockAddress, "Failed to send GARP"); + } + + private void sendMulticastNs(final Inet6Address srcIp, final Inet6Address dstIp, + final Inet6Address targetIp) { + final MacAddress dstMac = NetworkStackUtils.ipv6MulticastToEthernetMulticast(dstIp); + final ByteBuffer packet = NeighborSolicitation.build(mInterfaceParams.macAddr, dstMac, + srcIp, dstIp, targetIp); + final SocketAddress sockAddress = + SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6, + mInterfaceParams.index, dstMac.toByteArray()); + + if (DBG) { + mLog.log("send multicast NS from " + srcIp.getHostAddress() + " to " + + dstIp.getHostAddress() + " , target IP: " + targetIp.getHostAddress()); + } + transmitPacket(packet, sockAddress, "Failed to send multicast Neighbor Solicitation"); + } + + @Nullable + private static Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) { + for (LinkAddress la : newLp.getLinkAddresses()) { + if (!la.isIpv6()) continue; + final Inet6Address ip = (Inet6Address) la.getAddress(); + if (ip.isLinkLocalAddress()) return ip; + } + return null; + } + + private void maybeSendGratuitousNAs(final LinkProperties lp, boolean afterRoaming) { + if (!lp.hasGlobalIpv6Address()) return; + + final Inet6Address srcIp = getIpv6LinkLocalAddress(lp); + if (srcIp == null) return; + + // TODO: add experiment with sending only one gratuitous NA packet instead of one + // packet per address. + for (LinkAddress la : lp.getLinkAddresses()) { + if (!NetworkStackUtils.isIPv6GUA(la)) continue; + final Inet6Address targetIp = (Inet6Address) la.getAddress(); + // Already sent gratuitous NA with this target global IPv6 address. But for + // the L2 roaming case, device should always (re)transmit Gratuitous NA for + // each IPv6 global unicast address respectively after roaming. + if (!afterRoaming && mGratuitousNaTargetAddresses.contains(targetIp)) continue; + if (DBG) { + mLog.log("send Gratuitous NA from " + srcIp.getHostAddress() + " for " + + targetIp.getHostAddress() + (afterRoaming ? " after roaming" : "")); + } + sendGratuitousNA(srcIp, targetIp); + if (!afterRoaming) { + mGratuitousNaTargetAddresses.add(targetIp); + } + } + } + + private void maybeSendGratuitousARP(final LinkProperties lp) { + for (LinkAddress address : lp.getLinkAddresses()) { + if (address.getAddress() instanceof Inet4Address) { + final Inet4Address srcIp = (Inet4Address) address.getAddress(); + if (DBG) { + mLog.log("send GARP for " + srcIp.getHostAddress() + " HW address: " + + mInterfaceParams.macAddr); + } + sendGratuitousARP(srcIp); + } + } + } + + @Nullable + private static Inet6Address getIPv6DefaultGateway(final LinkProperties lp) { + for (RouteInfo r : lp.getRoutes()) { + // TODO: call {@link RouteInfo#isIPv6Default} directly after core networking modules + // are consolidated. + if (r.getType() == RTN_UNICAST && r.getDestination().getPrefixLength() == 0 + && r.getDestination().getAddress() instanceof Inet6Address) { + // Check if it's IPv6 default route, if yes, return the gateway address + // (i.e. default router's IPv6 link-local address) + return (Inet6Address) r.getGateway(); + } + } + return null; + } + + private void maybeSendMulticastNSes(final LinkProperties lp) { + if (!(lp.hasGlobalIpv6Address() && lp.hasIpv6DefaultRoute())) return; + + // Get the default router's IPv6 link-local address. + final Inet6Address targetIp = getIPv6DefaultGateway(lp); + if (targetIp == null) return; + final Inet6Address dstIp = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(targetIp); + if (dstIp == null) return; + + for (LinkAddress la : lp.getLinkAddresses()) { + if (!NetworkStackUtils.isIPv6GUA(la)) continue; + final Inet6Address srcIp = (Inet6Address) la.getAddress(); + if (mMulticastNsSourceAddresses.contains(srcIp)) continue; + sendMulticastNs(srcIp, dstIp, targetIp); + mMulticastNsSourceAddresses.add(srcIp); + } + } + + private static boolean hasFlag(@NonNull final LinkAddress la, final int flags) { + return (la.getFlags() & flags) == flags; + + } + + // Check whether a global IPv6 stable address is derived from DHCPv6 prefix delegation. + // Address derived from delegated prefix should be: + // - unicast global routable address + // - with prefix length of 64 + // - has IFA_F_MANAGETEMPADDR, IFA_F_NOPREFIXROUTE and IFA_F_NODAD flags + private static boolean isIpv6StableDelegatedAddress(@NonNull final LinkAddress la) { + return la.isIpv6() + && !ConnectivityUtils.isIPv6ULA(la.getAddress()) + && (la.getPrefixLength() == RFC7421_PREFIX_LENGTH) + && (la.getScope() == (byte) RT_SCOPE_UNIVERSE) + && hasFlag(la, DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS); + } + + // Returns false if we have lost provisioning, true otherwise. + private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { + final LinkProperties newLp = assembleLinkProperties(); + if (Objects.equals(newLp, mLinkProperties)) { + return true; + } + + // Set an alarm to wait for IPv6 autoconf via SLAAC to succeed after receiving an RA, + // if we don't see global IPv6 address within timeout then start DHCPv6 Prefix Delegation + // for provisioning. We cannot just check if there is an available on-link IPv6 DNS server + // in the LinkProperties, because on-link IPv6 DNS server won't be updated to LP until + // we have a global IPv6 address via PD. Instead, we have to check if the IPv6 default + // route exists and start DHCPv6 Prefix Delegation process if IPv6 provisioning still + // doesn't complete with success after timeout. This check also handles IPv6-only link + // local mode case, since there will be no IPv6 default route in that mode even with Prefix + // Delegation experiment flag enabled. + if (mDhcp6PrefixDelegationEnabled + && newLp.hasIpv6DefaultRoute() + && mIpv6AutoconfTimeoutAlarm == null) { + mIpv6AutoconfTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_IPV6_AUTOCONF_TIMEOUT", EVENT_IPV6_AUTOCONF_TIMEOUT); + final long alarmTime = SystemClock.elapsedRealtime() + + mDependencies.getDeviceConfigPropertyInt(CONFIG_IPV6_AUTOCONF_TIMEOUT, + DEFAULT_IPV6_AUTOCONF_TIMEOUT_MS); + mIpv6AutoconfTimeoutAlarm.schedule(alarmTime); + } + + // Check if new assigned IPv6 GUA is available in the LinkProperties now. If so, initiate + // gratuitous multicast unsolicited Neighbor Advertisements as soon as possible to inform + // first-hop routers that the new GUA host is goning to use. + if (isGratuitousNaEnabled()) { + maybeSendGratuitousNAs(newLp, false /* isGratuitousNaAfterRoaming */); + } + + // Sending multicast NS from each new assigned IPv6 GUAs to the solicited-node multicast + // address based on the default router's IPv6 link-local address should trigger default + // router response with NA, and update the neighbor cache entry immediately, that would + // help speed up the connection to an IPv6-only network. + // + // TODO: stop sending this multicast NS after deployment of RFC9131 in the field, leverage + // the gratuitous NA to update the first-hop router's neighbor cache entry. + maybeSendMulticastNSes(newLp); + + // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update, + // wait for the provisioning completion and record the latency. + mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned()); + mIpProvisioningMetrics.setIPv6ProvisionedLatencyOnFirstTime(newLp.isIpv6Provisioned()); + + final int delta = setLinkProperties(newLp); + // Most of the attributes stored in the memory store are deduced from + // the link properties, therefore when the properties update the memory + // store record should be updated too. + maybeSaveNetworkToIpMemoryStore(); + if (sendCallbacks) { + dispatchCallback(delta, newLp); + // We cannot do this along with onProvisioningSuccess callback, because the network + // can become dual-stack after a success IPv6 provisioning, and the multiplier also + // needs to be updated upon the loss of IPv4 and/or IPv6 provisioning. The multiplier + // has been initialized with DTIM_MULTIPLIER_RESET before starting provisioning, it + // gets updated on the first LinkProperties update (which usually happens when the + // IPv6 link-local address appears). + updateMaxDtimMultiplier(); + } + return (delta != PROV_CHANGE_LOST_PROVISIONING); + } + + @VisibleForTesting + static String removeDoubleQuotes(@NonNull String ssid) { + final int length = ssid.length(); + if ((length > 1) && (ssid.charAt(0) == '"') && (ssid.charAt(length - 1) == '"')) { + return ssid.substring(1, length - 1); + } + return ssid; + } + + private static List getVendorSpecificIEs(@NonNull ScanResultInfo scanResultInfo) { + ArrayList vendorSpecificPayloadList = new ArrayList<>(); + for (InformationElement ie : scanResultInfo.getInformationElements()) { + if (ie.getId() == VENDOR_SPECIFIC_IE_ID) { + vendorSpecificPayloadList.add(ie.getPayload()); + } + } + return vendorSpecificPayloadList; + } + + private boolean checkIfOuiAndTypeMatched(@NonNull ScanResultInfo scanResultInfo, + @NonNull List patternList) { + final List vendorSpecificPayloadList = getVendorSpecificIEs(scanResultInfo); + + for (ByteBuffer payload : vendorSpecificPayloadList) { + byte[] ouiAndType = new byte[4]; + try { + payload.get(ouiAndType); + } catch (BufferUnderflowException e) { + Log.e(mTag, "Couldn't parse vendor specific IE, buffer underflow"); + return false; + } + for (byte[] pattern : patternList) { + if (Arrays.equals(pattern, ouiAndType)) { + if (DBG) { + Log.d(mTag, "match pattern: " + HexDump.toHexString(ouiAndType)); + } + return true; + } + } + } + return false; + } + + private boolean detectUpstreamHotspotFromVendorIe() { + final ScanResultInfo scanResultInfo = mConfiguration.mScanResultInfo; + if (scanResultInfo == null) return false; + final String ssid = scanResultInfo.getSsid(); + + if (mConfiguration.mDisplayName == null + || !removeDoubleQuotes(mConfiguration.mDisplayName).equals(ssid)) { + return false; + } + return checkIfOuiAndTypeMatched(scanResultInfo, METERED_IE_PATTERN_LIST); + } + + private void handleIPv4Success(DhcpResults dhcpResults) { + mDhcpResults = new DhcpResults(dhcpResults); + final LinkProperties newLp = assembleLinkProperties(); + final int delta = setLinkProperties(newLp); + + if (mDhcpResults.vendorInfo == null && detectUpstreamHotspotFromVendorIe()) { + mDhcpResults.vendorInfo = DhcpPacket.VENDOR_INFO_ANDROID_METERED; + } + + if (DBG) { + Log.d(mTag, "onNewDhcpResults(" + Objects.toString(mDhcpResults) + ")"); + Log.d(mTag, "handleIPv4Success newLp{" + newLp + "}"); + } + mCallback.onNewDhcpResults(mDhcpResults); + maybeSaveNetworkToIpMemoryStore(); + + dispatchCallback(delta, newLp); + } + + private void handleIPv4Failure() { + // TODO: Investigate deleting this clearIPv4Address() call. + // + // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances + // that could trigger a call to this function. If we missed handling + // that message in StartedState for some reason we would still clear + // any addresses upon entry to StoppedState. + mInterfaceCtrl.clearIPv4Address(); + mDhcpResults = null; + if (DBG) { + Log.d(mTag, "onNewDhcpResults(null)"); + } + mCallback.onNewDhcpResults(null); + + handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_FAIL); + } + + private void handleProvisioningFailure(final DisconnectCode code) { + final LinkProperties newLp = assembleLinkProperties(); + int delta = setLinkProperties(newLp); + // If we've gotten here and we're still not provisioned treat that as + // a total loss of provisioning. + // + // Either (a) static IP configuration failed or (b) DHCPv4 failed AND + // there was no usable IPv6 obtained before a non-zero provisioning + // timeout expired. + // + // Regardless: GAME OVER. + if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + dispatchCallback(delta, newLp); + if (delta == PROV_CHANGE_LOST_PROVISIONING) { + transitionToStoppingState(code); + } + } + + private void doImmediateProvisioningFailure(int failureType) { + logError("onProvisioningFailure(): %s", failureType); + recordMetric(failureType); + mCallback.onProvisioningFailure(mLinkProperties); + } + + @SuppressLint("NewApi") // TODO: b/193460475 remove once fixed + private boolean startIPv4() { + // If we have a StaticIpConfiguration attempt to apply it and + // handle the result accordingly. + if (mConfiguration.mStaticIpConfig != null) { + if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.getIpAddress())) { + handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); + } else { + return false; + } + } else { + if (mDhcpClient != null) { + Log.wtf(mTag, "DhcpClient should never be non-null in startIPv4()"); + } + startDhcpClient(); + } + + return true; + } + + private boolean shouldDisableDad() { + return mConfiguration.mUniqueEui64AddressesOnly + && mConfiguration.mIPv6ProvisioningMode == PROV_IPV6_LINKLOCAL + && mConfiguration.mIPv6AddrGenMode + == ProvisioningConfiguration.IPV6_ADDR_GEN_MODE_EUI64; + } + + private boolean startIPv6(int acceptRaDefrtr) { + setIpv6Sysctl(ACCEPT_RA, + mConfiguration.mIPv6ProvisioningMode == PROV_IPV6_LINKLOCAL ? 0 : 2); + setIpv6Sysctl(ACCEPT_RA_DEFRTR, acceptRaDefrtr); + if (shouldDisableDad()) { + final Integer dadTransmits = getIpv6Sysctl(DAD_TRANSMITS); + if (dadTransmits != null) { + mDadTransmits = dadTransmits; + setIpv6Sysctl(DAD_TRANSMITS, 0 /* dad_transmits */); + } + } + return mInterfaceCtrl.setIPv6PrivacyExtensions(true) + && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) + && mInterfaceCtrl.enableIPv6(); + } + + private void startDhcp6PrefixDelegation() { + if (!mDhcp6PrefixDelegationEnabled) return; + if (mDhcp6Client != null) { + Log.wtf(mTag, "Dhcp6Client should never be non-null in startDhcp6PrefixDelegation"); + return; + } + mDhcp6Client = mDependencies.makeDhcp6Client(mContext, IpClient.this, mInterfaceParams, + mDependencies.getDhcp6ClientDependencies()); + mDhcp6Client.sendMessage(Dhcp6Client.CMD_START_DHCP6); + } + + private boolean applyInitialConfig(InitialConfiguration config) { + // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. + for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIpv6)) { + if (!mInterfaceCtrl.addAddress(addr)) return false; + } + + return true; + } + + private boolean startIpReachabilityMonitor() { + try { + mIpReachabilityMonitor = mDependencies.getIpReachabilityMonitor( + mContext, + mInterfaceParams, + getHandler(), + mLog, + new IpReachabilityMonitor.Callback() { + @Override + public void notifyLost(InetAddress ip, String logMsg, NudEventType type) { + final int version = mCallback.getInterfaceVersion(); + if (version >= VERSION_ADDED_REACHABILITY_FAILURE) { + final int reason = nudEventTypeToInt(type); + if (reason == INVALID_REACHABILITY_LOSS_TYPE) return; + final ReachabilityLossInfoParcelable lossInfo = + new ReachabilityLossInfoParcelable(logMsg, reason); + mCallback.onReachabilityFailure(lossInfo); + } else { + mCallback.onReachabilityLost(logMsg); + } + } + }, + mConfiguration.mUsingMultinetworkPolicyTracker, + mDependencies.getIpReachabilityMonitorDeps(mContext, mInterfaceParams.name), + mNetd); + } catch (IllegalArgumentException iae) { + // Failed to start IpReachabilityMonitor. Log it and call + // onProvisioningFailure() immediately. + // + // See http://b/31038971. + logError("IpReachabilityMonitor failure: %s", iae); + mIpReachabilityMonitor = null; + } + + return (mIpReachabilityMonitor != null); + } + + private void stopAllIP() { + // We don't need to worry about routes, just addresses, because: + // - disableIpv6() will clear autoconf IPv6 routes as well, and + // - we don't get IPv4 routes from netlink + // so we neither react to nor need to wait for changes in either. + mInterfaceCtrl.disableIPv6(); + mInterfaceCtrl.clearAllAddresses(); + + // Reset IPv6 sysctls to their initial state. It's better to restore + // sysctls after IPv6 stack is disabled, which prevents a potential + // race where receiving an RA between restoring accept_ra and disabling + // IPv6 stack, although it's unlikely. + setIpv6Sysctl(ACCEPT_RA, 2); + setIpv6Sysctl(ACCEPT_RA_DEFRTR, 1); + maybeRestoreDadTransmits(); + if (mUseNewApfFilter && mEnableIpClientIgnoreLowRaLifetime + && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) { + setIpv6Sysctl(ACCEPT_RA_MIN_LFT, 0 /* sysctl default */); + } + } + + private void maybeSaveNetworkToIpMemoryStore() { + // TODO : implement this + } + + private void maybeRestoreInterfaceMtu() { + InterfaceParams params = mDependencies.getInterfaceParams(mInterfaceName); + if (params == null) { + Log.w(mTag, "interface: " + mInterfaceName + " is gone"); + return; + } + + // Check whether "mInterfaceParams" is null or not to prevent the potential NPE + // introduced if the interface was initially not found, but came back before this + // method was called. See b/162808916 for more details. TODO: query the new interface + // parameters by the interface index instead and check that the index has not changed. + if (mInterfaceParams == null || params.index != mInterfaceParams.index) { + Log.w(mTag, "interface: " + mInterfaceName + " has a different index: " + params.index); + return; + } + + if (params.defaultMtu == mInterfaceParams.defaultMtu) return; + + try { + mNetd.interfaceSetMtu(mInterfaceName, mInterfaceParams.defaultMtu); + } catch (RemoteException | ServiceSpecificException e) { + logError("Couldn't reset MTU on " + mInterfaceName + " from " + + params.defaultMtu + " to " + mInterfaceParams.defaultMtu, e); + } + } + + private void maybeRestoreDadTransmits() { + if (mDadTransmits == null) return; + + setIpv6Sysctl(DAD_TRANSMITS, mDadTransmits); + mDadTransmits = null; + } + + private void handleUpdateL2Information(@NonNull Layer2InformationParcelable info) { + mL2Key = info.l2Key; + mCluster = info.cluster; + + // Sometimes the wifi code passes in a null BSSID. Don't use Log.wtf in R because + // it's a known bug that will not be fixed in R. + if (info.bssid == null || mCurrentBssid == null) { + final String msg = "bssid in the parcelable: " + info.bssid + " or " + + "current tracked bssid: " + mCurrentBssid + " is null"; + if (ShimUtils.isAtLeastS()) { + Log.wtf(mTag, msg); + } else { + Log.w(mTag, msg); + } + return; + } + + // If the BSSID has not changed, there is nothing to do. + if (info.bssid.equals(mCurrentBssid)) return; + + // Before trigger probing to the critical neighbors, send Gratuitous ARP + // and Neighbor Advertisment in advance to propgate host's IPv4/v6 addresses. + if (isGratuitousArpNaRoamingEnabled()) { + maybeSendGratuitousARP(mLinkProperties); + maybeSendGratuitousNAs(mLinkProperties, true /* isGratuitousNaAfterRoaming */); + } + + // Check whether attempting to refresh previous IP lease on specific networks or need to + // probe the critical neighbors proactively on L2 roaming happened. The NUD probe on the + // specific networks is cancelled because otherwise the probe will happen in parallel with + // DHCP refresh, it will be difficult to understand what happened exactly and error-prone + // to introduce race condition. + final String ssid = removeDoubleQuotes(mConfiguration.mDisplayName); + if (DHCP_ROAMING_SSID_SET.contains(ssid) && mDhcpClient != null) { + if (DBG) { + Log.d(mTag, "L2 roaming happened from " + mCurrentBssid + + " to " + info.bssid + + " , SSID: " + ssid + + " , starting refresh leased IP address"); + } + mDhcpClient.sendMessage(DhcpClient.CMD_REFRESH_LINKADDRESS); + } else if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.probeAll(true /* dueToRoam */); + } + mCurrentBssid = info.bssid; + } + + @Nullable + private AndroidPacketFilter maybeCreateApfFilter(final ApfCapabilities apfCapabilities) { + ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration(); + apfConfig.apfCapabilities = apfCapabilities; + apfConfig.multicastFilter = mMulticastFiltering; + // Get the Configuration for ApfFilter from Context + // Resource settings were moved from ApfCapabilities APIs to NetworkStack resources in S + if (ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) { + final Resources res = mContext.getResources(); + apfConfig.ieee802_3Filter = res.getBoolean(R.bool.config_apfDrop802_3Frames); + apfConfig.ethTypeBlackList = res.getIntArray(R.array.config_apfEthTypeDenyList); + } else { + apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames(); + apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList(); + } + + apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec; + // Check the feature flag first before reading IPv6 sysctl, which can prevent from + // triggering a potential kernel bug about the sysctl. + // TODO: add unit test to check if the setIpv6Sysctl() is called or not. + if (mEnableIpClientIgnoreLowRaLifetime && mUseNewApfFilter + && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) { + setIpv6Sysctl(ACCEPT_RA_MIN_LFT, mAcceptRaMinLft); + final Integer acceptRaMinLft = getIpv6Sysctl(ACCEPT_RA_MIN_LFT); + apfConfig.acceptRaMinLft = acceptRaMinLft == null ? 0 : acceptRaMinLft; + } else { + apfConfig.acceptRaMinLft = 0; + } + apfConfig.shouldHandleLightDoze = mApfShouldHandleLightDoze; + apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs; + return mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams, + mCallback, mNetworkQuirkMetrics, mUseNewApfFilter); + } + + private boolean handleUpdateApfCapabilities(@NonNull final ApfCapabilities apfCapabilities) { + // For the use case where the wifi interface switches from secondary to primary, the + // secondary interface does not support APF by default see the overlay config about + // {@link config_wifiEnableApfOnNonPrimarySta}. so we should see empty ApfCapabilities + // in {@link ProvisioningConfiguration} when wifi starts provisioning on the secondary + // interface. For other cases, we should not accept the updateApfCapabilities call. + if (mCurrentApfCapabilities != null || apfCapabilities == null) { + Log.wtf(mTag, "current ApfCapabilities " + mCurrentApfCapabilities + + " is not null or new ApfCapabilities " + apfCapabilities + " is null"); + return false; + } + if (mApfFilter != null) { + mApfFilter.shutdown(); + } + mCurrentApfCapabilities = apfCapabilities; + return apfCapabilities != null; + } + + class StoppedState extends State { + @Override + public void enter() { + stopAllIP(); + mHasDisabledAcceptRaDefrtrOnProvLoss = false; + mGratuitousNaTargetAddresses.clear(); + mMulticastNsSourceAddresses.clear(); + mDelegatedPrefixes.clear(); + + resetLinkProperties(); + if (mStartTimeMillis > 0) { + // Completed a life-cycle; send a final empty LinkProperties + // (cleared in resetLinkProperties() above) and record an event. + mCallback.onLinkPropertiesChange(mLinkProperties); + recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE); + mStartTimeMillis = 0; + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_TERMINATE_AFTER_STOP: + stopStateMachineUpdaters(); + quit(); + break; + + case CMD_STOP: + break; + + case CMD_START: + mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj; + transitionTo(mClearingIpAddressesState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_TCP_BUFFER_SIZES: + mTcpBufferSizes = (String) msg.obj; + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_HTTP_PROXY: + mHttpProxy = (ProxyInfo) msg.obj; + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_L2KEY_CLUSTER: { + final Pair args = (Pair) msg.obj; + mL2Key = args.first; + mCluster = args.second; + break; + } + + case CMD_SET_MULTICAST_FILTER: + mMulticastFiltering = (boolean) msg.obj; + break; + + case DhcpClient.CMD_ON_QUIT: + case Dhcp6Client.CMD_ON_QUIT: + // Everything is already stopped. + logError("Unexpected CMD_ON_QUIT (already stopped)."); + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + class StoppingState extends State { + @Override + public void enter() { + if (mDhcpClient == null && mDhcp6Client == null) { + // There's no DHCPv4 as well as DHCPv6 for which to wait; proceed to stopped + deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED)); + } else { + if (mDhcpClient != null) { + mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); + mDhcpClient.doQuit(); + } + if (mDhcp6Client != null) { + mDhcp6Client.sendMessage(Dhcp6Client.CMD_STOP_DHCP6); + mDhcp6Client.doQuit(); + } + } + + // Restore the interface MTU to initial value if it has changed. + maybeRestoreInterfaceMtu(); + // Reset DTIM multiplier to default value if changed. + if (mMaxDtimMultiplier != DTIM_MULTIPLIER_RESET) { + mCallback.setMaxDtimMultiplier(DTIM_MULTIPLIER_RESET); + mMaxDtimMultiplier = DTIM_MULTIPLIER_RESET; + mIPv6ProvisioningDtimGracePeriodMillis = 0; + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_JUMP_STOPPING_TO_STOPPED: + transitionTo(mStoppedState); + break; + + case CMD_STOP: + break; + + case DhcpClient.CMD_CLEAR_LINKADDRESS: + mInterfaceCtrl.clearIPv4Address(); + break; + + case DhcpClient.CMD_ON_QUIT: + mDhcpClient = null; + // DhcpClient always starts no matter of target network type, however, we have + // to make sure both of DHCPv4 and DHCPv6 client have quit from state machine + // before transition to StoppedState, otherwise, we may miss CMD_ON_QUIT cmd + // that arrives later and transit to StoppedState before that. + if (mDhcp6Client == null) { + transitionTo(mStoppedState); + } + break; + + case Dhcp6Client.CMD_ON_QUIT: + mDhcp6Client = null; + if (mDhcpClient == null) { + transitionTo(mStoppedState); + } + break; + + default: + deferMessage(msg); + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + private boolean isUsingPreconnection() { + return mConfiguration.mEnablePreconnection && mConfiguration.mStaticIpConfig == null; + } + + /** + * Check if the customized DHCP client options passed from Wi-Fi are allowed to be put + * in PRL or in the DHCP packet. + */ + private List maybeFilterCustomizedDhcpOptions() { + final List options = new ArrayList(); + if (mConfiguration.mDhcpOptions == null + || mConfiguration.mScanResultInfo == null) return options; // empty DhcpOption list + + for (DhcpOption option : mConfiguration.mDhcpOptions) { + final List patternList = DHCP_OPTIONS_ALLOWED.get(option.type); + // requested option won't be added if no vendor-specific IE oui/type allows this option. + if (patternList == null) continue; + if (checkIfOuiAndTypeMatched(mConfiguration.mScanResultInfo, patternList)) { + options.add(option); + } + } + Collections.sort(options, (o1, o2) -> + Integer.compare(Byte.toUnsignedInt(o1.type), Byte.toUnsignedInt(o2.type))); + return options; + } + + private void startDhcpClient() { + // Start DHCPv4. + mDhcpClient = mDependencies.makeDhcpClient(mContext, IpClient.this, mInterfaceParams, + mDependencies.getDhcpClientDependencies(mIpMemoryStore, mIpProvisioningMetrics)); + + // Check if the vendor-specific IE oui/type matches and filters the customized DHCP options. + final List options = maybeFilterCustomizedDhcpOptions(); + + // If preconnection is enabled, there is no need to ask Wi-Fi to disable powersaving + // during DHCP, because the DHCP handshake will happen during association. In order to + // ensure that future renews still do the DHCP action (if configured), + // registerForPreDhcpNotification is called later when processing the CMD_*_PRECONNECTION + // messages. + if (!isUsingPreconnection()) mDhcpClient.registerForPreDhcpNotification(); + boolean isManagedWifiProfile = false; + if (mDependencies.getSendDomainSearchListOption(mContext) + && (mCreatorUid > 0) && (isDeviceOwnerNetwork(mCreatorUid) + || isProfileOwner(mCreatorUid))) { + isManagedWifiProfile = true; + } + mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP, new DhcpClient.Configuration(mL2Key, + isUsingPreconnection(), options, isManagedWifiProfile, + mConfiguration.mHostnameSetting)); + } + + private boolean hasPermission(String permissionName) { + return (mContext.checkCallingOrSelfPermission(permissionName) + == PackageManager.PERMISSION_GRANTED); + } + + private boolean isDeviceOwnerNetwork(int creatorUid) { + if (mDevicePolicyManager == null) return false; + if (!hasPermission(android.Manifest.permission.MANAGE_USERS)) return false; + final ComponentName devicecmpName = mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser(); + if (devicecmpName == null) return false; + final String deviceOwnerPackageName = devicecmpName.getPackageName(); + if (deviceOwnerPackageName == null) return false; + + final String[] packages = mContext.getPackageManager().getPackagesForUid(creatorUid); + + for (String pkg : packages) { + if (pkg.equals(deviceOwnerPackageName)) { + return true; + } + } + return false; + } + + @Nullable + private Context createPackageContextAsUser(int uid) { + Context userContext = null; + try { + userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0, + UserHandle.getUserHandleForUid(uid)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unknown package name"); + return null; + } + return userContext; + } + + /** + * Returns the DevicePolicyManager from context + */ + private DevicePolicyManager retrieveDevicePolicyManagerFromContext(Context context) { + DevicePolicyManager devicePolicyManager = + context.getSystemService(DevicePolicyManager.class); + if (devicePolicyManager == null + && context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_DEVICE_ADMIN)) { + Log.wtf(TAG, "Error retrieving DPM service"); + } + return devicePolicyManager; + } + + private DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(int uid) { + Context userContext = createPackageContextAsUser(uid); + if (userContext == null) return null; + return retrieveDevicePolicyManagerFromContext(userContext); + } + + /** + * Returns {@code true} if the calling {@code uid} is the profile owner + * + */ + + private boolean isProfileOwner(int uid) { + DevicePolicyManager devicePolicyManager = retrieveDevicePolicyManagerFromUserContext(uid); + if (devicePolicyManager == null) return false; + String[] packages = mContext.getPackageManager().getPackagesForUid(uid); + if (packages == null) { + Log.w(TAG, "isProfileOwner: could not find packages for uid=" + + uid); + return false; + } + for (String packageName : packages) { + if (devicePolicyManager.isProfileOwnerApp(packageName)) { + return true; + } + } + return false; + } + + class ClearingIpAddressesState extends State { + @Override + public void enter() { + // Ensure that interface parameters are fetched on the handler thread so they are + // properly ordered with other events, such as restoring the interface MTU on teardown. + mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName); + if (mInterfaceParams == null) { + logError("Failed to find InterfaceParams for " + mInterfaceName); + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND); + deferMessage(obtainMessage(CMD_STOP, + DisconnectCode.DC_INTERFACE_NOT_FOUND.getNumber())); + return; + } + + mLinkObserver.setInterfaceParams(mInterfaceParams); + + if (readyToProceed()) { + deferMessage(obtainMessage(CMD_ADDRESSES_CLEARED)); + } else { + // Clear all IPv4 and IPv6 before proceeding to RunningState. + // Clean up any leftover state from an abnormal exit from + // tethering or during an IpClient restart. + stopAllIP(); + } + + mCallback.setNeighborDiscoveryOffload(true); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_ADDRESSES_CLEARED: + transitionTo(isUsingPreconnection() ? mPreconnectingState : mRunningState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + if (readyToProceed()) { + transitionTo(isUsingPreconnection() ? mPreconnectingState : mRunningState); + } + break; + + case CMD_STOP: + case EVENT_PROVISIONING_TIMEOUT: + // Fall through to StartedState. + return NOT_HANDLED; + + default: + // It's safe to process messages out of order because the + // only message that can both + // a) be received at this time and + // b) affect provisioning state + // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). + deferMessage(msg); + } + return HANDLED; + } + + private boolean readyToProceed() { + return !mLinkProperties.hasIpv4Address() && !mLinkProperties.hasGlobalIpv6Address(); + } + } + + class PreconnectingState extends State { + @Override + public void enter() { + startDhcpClient(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_COMPLETE_PRECONNECTION: + boolean success = (msg.arg1 == 1); + mDhcpClient.registerForPreDhcpNotification(); + if (!success) { + mDhcpClient.sendMessage(DhcpClient.CMD_ABORT_PRECONNECTION); + } + // The link is ready for use. Advance to running state, start IPv6, etc. + transitionTo(mRunningState); + break; + + case DhcpClient.CMD_START_PRECONNECTION: + final Layer2PacketParcelable l2Packet = (Layer2PacketParcelable) msg.obj; + mCallback.onPreconnectionStart(Collections.singletonList(l2Packet)); + break; + + case CMD_STOP: + case EVENT_PROVISIONING_TIMEOUT: + // Fall through to StartedState. + return NOT_HANDLED; + + default: + deferMessage(msg); + } + return HANDLED; + } + } + + class StartedState extends State { + @Override + public void enter() { + mIpProvisioningMetrics.reset(); + mStartTimeMillis = SystemClock.elapsedRealtime(); + + if (mConfiguration.mProvisioningTimeoutMs > 0) { + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mProvisioningTimeoutMs; + mProvisioningTimeoutAlarm.schedule(alarmTime); + } + + // There is no need to temporarlily lower the DTIM multiplier in IPv6 link-local + // only mode or when IPv6 is disabled. + if (mConfiguration.mIPv6ProvisioningMode == PROV_IPV6_SLAAC) { + // Send a delay message to wait for IP provisioning to complete eventually and + // set the specific DTIM multiplier by checking the target network type. + final int delay = mDependencies.getDeviceConfigPropertyInt( + CONFIG_INITIAL_PROVISIONING_DTIM_DELAY_MS, + DEFAULT_INITIAL_PROVISIONING_DTIM_DELAY_MS); + mIPv6ProvisioningDtimGracePeriodMillis = mStartTimeMillis + delay; + sendMessageDelayed(CMD_SET_DTIM_MULTIPLIER_AFTER_DELAY, delay); + } + } + + @Override + public void exit() { + mProvisioningTimeoutAlarm.cancel(); + mCurrentApfCapabilities = null; + + // Record metrics information once this provisioning has completed due to certain + // reason (normal termination, provisioning timeout, lost provisioning and etc). + mIpProvisioningMetrics.statsWrite(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_STOP: + transitionToStoppingState(DisconnectCode.forNumber(msg.arg1)); + break; + + case CMD_UPDATE_L2KEY_CLUSTER: { + final Pair args = (Pair) msg.obj; + mL2Key = args.first; + mCluster = args.second; + // TODO : attributes should be saved to the memory store with + // these new values if they differ from the previous ones. + // If the state machine is in pure StartedState, then the values to input + // are not known yet and should be updated when the LinkProperties are updated. + // If the state machine is in RunningState (which is a child of StartedState) + // then the next NUD check should be used to store the new values to avoid + // inputting current values for what may be a different L3 network. + break; + } + + case CMD_UPDATE_L2INFORMATION: + handleUpdateL2Information((Layer2InformationParcelable) msg.obj); + break; + + // Only update the current ApfCapabilities but do not create and start APF + // filter until transition to RunningState, actually we should always do that + // in RunningState. + case CMD_UPDATE_APF_CAPABILITIES: + handleUpdateApfCapabilities((ApfCapabilities) msg.obj); + break; + + case EVENT_PROVISIONING_TIMEOUT: + handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_TIMEOUT); + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + private boolean isIpv6Enabled() { + return mConfiguration.mIPv6ProvisioningMode != PROV_IPV6_DISABLED; + } + + private boolean isIpv4Enabled() { + return mConfiguration.mIPv4ProvisioningMode != PROV_IPV4_DISABLED; + } + + class RunningState extends State { + private ConnectivityPacketTracker mPacketTracker; + private boolean mDhcpActionInFlight; + + @Override + public void enter() { + mApfFilter = maybeCreateApfFilter(mCurrentApfCapabilities); + // TODO: investigate the effects of any multicast filtering racing/interfering with the + // rest of this IP configuration startup. + if (mApfFilter == null) { + mCallback.setFallbackMulticastFilter(mMulticastFiltering); + } + if (mEnableApfPollingCounters) { + sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs); + } + + mPacketTracker = createPacketTracker(); + if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); + + if (isIpv6Enabled() && !startIPv6(1 /* acceptRaDefrtr */)) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); + enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV6); + return; + } + + if (isIpv4Enabled() && !isUsingPreconnection() && !startIPv4()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); + enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV4); + return; + } + + final InitialConfiguration config = mConfiguration.mInitialConfig; + if ((config != null) && !applyInitialConfig(config)) { + // TODO introduce a new IpManagerEvent constant to distinguish this error case. + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + enqueueJumpToStoppingState(DisconnectCode.DC_INVALID_PROVISIONING); + return; + } + + if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { + doImmediateProvisioningFailure( + IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); + enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPREACHABILITYMONITOR); + return; + } + } + + @Override + public void exit() { + stopDhcpAction(); + + if (mIpv6AutoconfTimeoutAlarm != null) { + mIpv6AutoconfTimeoutAlarm.cancel(); + mIpv6AutoconfTimeoutAlarm = null; + } + + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.stop(); + mIpReachabilityMonitor = null; + } + + if (mPacketTracker != null) { + mPacketTracker.stop(); + mPacketTracker = null; + } + + if (mApfFilter != null) { + mApfFilter.shutdown(); + mApfFilter = null; + } + + resetLinkProperties(); + + removeMessages(CMD_UPDATE_APF_DATA_SNAPSHOT); + } + + private void enqueueJumpToStoppingState(final DisconnectCode code) { + deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING, code.getNumber())); + } + + private ConnectivityPacketTracker createPacketTracker() { + try { + return new ConnectivityPacketTracker( + getHandler(), mInterfaceParams, mConnectivityPacketLog); + } catch (IllegalArgumentException e) { + return null; + } + } + + private void ensureDhcpAction() { + if (!mDhcpActionInFlight) { + mCallback.onPreDhcpAction(); + mDhcpActionInFlight = true; + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mRequestedPreDhcpActionMs; + mDhcpActionTimeoutAlarm.schedule(alarmTime); + } + } + + private void stopDhcpAction() { + mDhcpActionTimeoutAlarm.cancel(); + if (mDhcpActionInFlight) { + mCallback.onPostDhcpAction(); + mDhcpActionInFlight = false; + } + } + + private void deleteIpv6PrefixDelegationAddresses(final IpPrefix prefix) { + for (LinkAddress la : mLinkProperties.getLinkAddresses()) { + final InetAddress address = la.getAddress(); + if (prefix.contains(address)) { + if (!NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index, + (Inet6Address) address, (short) la.getPrefixLength())) { + Log.e(TAG, "Failed to delete IPv6 address " + address.getHostAddress()); + } + } + } + } + + private void addInterfaceAddress(@Nullable final Inet6Address address, + @NonNull final IaPrefixOption ipo) { + final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD; + final long now = SystemClock.elapsedRealtime(); + // Per RFC8415 section 21.22 the preferred/valid lifetime in IA Prefix option + // expressed in units of seconds. + final long deprecationTime = now + ipo.preferred * 1000; + final long expirationTime = now + ipo.valid * 1000; + final LinkAddress la; + try { + la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags, + RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid IPv6 link address " + e); + return; + } + if (!la.isGlobalPreferred()) { + Log.w(TAG, la + " is not a global IPv6 address"); + return; + } + if (!NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index, address, + (short) RFC7421_PREFIX_LENGTH, + flags, (byte) RT_SCOPE_UNIVERSE /* scope */, + ipo.preferred, ipo.valid)) { + Log.e(TAG, "Failed to set IPv6 address on " + address.getHostAddress() + + "%" + mInterfaceParams.index); + } + } + + private void updateDelegatedAddresses(@NonNull final List valid) { + if (valid.isEmpty()) return; + final List zeroLifetimePrefixList = new ArrayList<>(); + for (IaPrefixOption ipo : valid) { + final IpPrefix prefix = ipo.getIpPrefix(); + // The prefix with preferred/valid lifetime of 0 is considered as a valid prefix, + // and can be passed to IpClient from Dhcp6Client, but client should stop using + // the global addresses derived from this prefix asap. Deleting the associated + // global IPv6 addresses immediately before adding another IPv6 address may result + // in a race where the device throws the provisioning failure callback due to the + // loss of all valid IPv6 addresses, however, IPv6 provisioning will soon complete + // successfully when the user space sees the new IPv6 address update. To avoid this + // race, temporarily store all prefix(es) with 0 preferred/valid lifetime and then + // delete them after iterating through all valid IA prefix options. + if (ipo.withZeroLifetimes()) { + zeroLifetimePrefixList.add(prefix); + continue; + } + // Otherwise, configure IPv6 addresses derived from the delegated prefix(es) on + // the interface. We've checked that delegated prefix is valid upon receiving the + // response from DHCPv6 server, and the server may assign a prefix with length less + // than 64. So for SLAAC use case we always set the prefix length to 64 even if the + // delegated prefix length is less than 64. + final Inet6Address address = createInet6AddressFromEui64(prefix, + macAddressToEui64(mInterfaceParams.macAddr)); + addInterfaceAddress(address, ipo); + } + + // Delete global IPv6 addresses derived from prefix with 0 preferred/valid lifetime. + if (!zeroLifetimePrefixList.isEmpty()) { + for (IpPrefix prefix : zeroLifetimePrefixList) { + Log.d(TAG, "Delete IPv6 address derived from prefix " + prefix + + " with 0 preferred/valid lifetime"); + deleteIpv6PrefixDelegationAddresses(prefix); + } + } + } + + private void removeExpiredDelegatedAddresses(@NonNull final List expired) { + if (expired.isEmpty()) return; + for (IaPrefixOption ipo : expired) { + final IpPrefix prefix = ipo.getIpPrefix(); + Log.d(TAG, "Delete IPv6 address derived from expired prefix " + prefix); + deleteIpv6PrefixDelegationAddresses(prefix); + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_JUMP_RUNNING_TO_STOPPING: + case CMD_STOP: + transitionToStoppingState(DisconnectCode.forNumber(msg.arg1)); + break; + + case CMD_START: + logError("ALERT: START received in StartedState. Please fix caller."); + break; + + case CMD_CONFIRM: + // TODO: Possibly introduce a second type of confirmation + // that both probes (a) on-link neighbors and (b) does + // a DHCPv4 RENEW. We used to do this on Wi-Fi framework + // roams. + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.probeAll(false /* dueToRoam */); + } + break; + + case EVENT_PRE_DHCP_ACTION_COMPLETE: + // It's possible to reach here if, for example, someone + // calls completedPreDhcpAction() after provisioning with + // a static IP configuration. + if (mDhcpClient != null) { + mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); + } + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + // EVENT_NETLINK_LINKPROPERTIES_CHANGED message will be received in both of + // provisioning loss and normal user termination cases (e.g. turn off wifi or + // switch to another wifi ssid), hence, checking the current interface link + // state (down or up) helps distinguish the two cases: if the link state is + // down, provisioning is only lost because the link is being torn down (for + // example when turning off wifi), so treat it as a normal termination. + if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) { + final boolean linkStateUp = (msg.arg1 == ARG_LINKPROP_CHANGED_LINKSTATE_UP); + transitionToStoppingState(linkStateUp ? DisconnectCode.DC_PROVISIONING_FAIL + : DisconnectCode.DC_NORMAL_TERMINATION); + } + break; + + case CMD_UPDATE_TCP_BUFFER_SIZES: + mTcpBufferSizes = (String) msg.obj; + // This cannot possibly change provisioning state. + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case CMD_UPDATE_HTTP_PROXY: + mHttpProxy = (ProxyInfo) msg.obj; + // This cannot possibly change provisioning state. + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case CMD_SET_MULTICAST_FILTER: { + mMulticastFiltering = (boolean) msg.obj; + if (mApfFilter != null) { + mApfFilter.setMulticastFilter(mMulticastFiltering); + } else { + mCallback.setFallbackMulticastFilter(mMulticastFiltering); + } + updateMaxDtimMultiplier(); + break; + } + + case EVENT_READ_PACKET_FILTER_COMPLETE: { + if (mApfFilter != null) { + mApfFilter.setDataSnapshot((byte[]) msg.obj); + } + mApfDataSnapshotComplete.open(); + break; + } + + case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: { + final int slot = msg.arg1; + + if (mApfFilter != null) { + if (msg.obj instanceof NattKeepalivePacketDataParcelable) { + mApfFilter.addNattKeepalivePacketFilter(slot, + (NattKeepalivePacketDataParcelable) msg.obj); + } else if (msg.obj instanceof TcpKeepalivePacketDataParcelable) { + mApfFilter.addTcpKeepalivePacketFilter(slot, + (TcpKeepalivePacketDataParcelable) msg.obj); + } + } + break; + } + + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF: { + final int slot = msg.arg1; + if (mApfFilter != null) { + mApfFilter.removeKeepalivePacketFilter(slot); + } + break; + } + + case EVENT_DHCPACTION_TIMEOUT: + stopDhcpAction(); + break; + + case EVENT_IPV6_AUTOCONF_TIMEOUT: + // Only enable DHCPv6 PD on networks that support IPv6 but not autoconf. The + // right way to do it is to use the P flag, once it's defined. For now, assume + // that the network doesn't support autoconf if it provides an IPv6 default + // route but no addresses via an RA. + // TODO: leverage the P flag in RA to determine if starting DHCPv6 PD or not, + // which is more clear and straightforward. + if (!hasIpv6Address(mLinkProperties) + && mLinkProperties.hasIpv6DefaultRoute()) { + Log.d(TAG, "Network supports IPv6 but not autoconf, starting DHCPv6 PD"); + startDhcp6PrefixDelegation(); + } + break; + + case DhcpClient.CMD_PRE_DHCP_ACTION: + if (mConfiguration.mRequestedPreDhcpActionMs > 0) { + ensureDhcpAction(); + } else { + sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); + } + break; + + case DhcpClient.CMD_CLEAR_LINKADDRESS: + mInterfaceCtrl.clearIPv4Address(); + break; + + case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { + final LinkAddress ipAddress = (LinkAddress) msg.obj; + final boolean success; + if (mPopulateLinkAddressLifetime) { + // For IPv4 link addresses, there is no concept of preferred/valid + // lifetimes. Populate the ifa_cacheinfo attribute in the netlink + // message with the DHCP lease duration, which is used by the kernel + // to maintain the validity of the IP addresses. + final int leaseDuration = msg.arg1; + success = NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index, + ipAddress.getAddress(), + (short) ipAddress.getPrefixLength(), + 0 /* flags */, + (byte) RT_SCOPE_UNIVERSE /* scope */, + leaseDuration /* preferred */, + leaseDuration /* valid */); + } else { + success = mInterfaceCtrl.setIPv4Address(ipAddress); + } + if (success) { + // Although it's impossible to happen that DHCP client becomes null in + // RunningState and then NPE is thrown when it attempts to send a message + // on an null object, sometimes it's found during stress tests. If this + // issue does happen, log the terrible failure, that would be helpful to + // see how often this case occurs on fields and the log trace would be + // also useful for debugging(see b/203174383). + if (mDhcpClient == null) { + Log.wtf(mTag, "DhcpClient should never be null in RunningState."); + } + mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); + } else { + logError("Failed to set IPv4 address."); + dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, mLinkProperties); + transitionToStoppingState(DisconnectCode.DC_PROVISIONING_FAIL); + } + break; + } + + // This message is only received when: + // + // a) initial address acquisition succeeds, + // b) renew succeeds or is NAK'd, + // c) rebind succeeds or is NAK'd, or + // d) the lease expires, or + // e) the IPv6-only preferred option is enabled and entering Ipv6OnlyWaitState. + // + // but never when initial address acquisition fails. The latter + // condition is now governed by the provisioning timeout. + case DhcpClient.CMD_POST_DHCP_ACTION: + stopDhcpAction(); + + switch (msg.arg1) { + case DhcpClient.DHCP_SUCCESS: + handleIPv4Success((DhcpResults) msg.obj); + break; + case DhcpClient.DHCP_FAILURE: + handleIPv4Failure(); + break; + case DhcpClient.DHCP_IPV6_ONLY: + break; + case DhcpClient.DHCP_REFRESH_FAILURE: + // This case should only happen on the receipt of DHCPNAK when + // refreshing IP address post L2 roaming on some specific networks. + // WiFi should try to restart a new provisioning immediately without + // disconnecting L2 when it receives DHCP roaming failure event. IPv4 + // link address still will be cleared when DhcpClient transits to + // StoppedState from RefreshingAddress State, although it will result + // in a following onProvisioningFailure then, WiFi should ignore this + // failure and start a new DHCP reconfiguration from INIT state. + final ReachabilityLossInfoParcelable lossInfo = + new ReachabilityLossInfoParcelable("DHCP refresh failure", + ReachabilityLossReason.ROAM); + mCallback.onReachabilityFailure(lossInfo); + break; + default: + logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); + } + break; + + case Dhcp6Client.CMD_DHCP6_RESULT: + switch(msg.arg1) { + case Dhcp6Client.DHCP6_PD_SUCCESS: + final List toBeUpdated = (List) msg.obj; + updateDelegatedAddresses(toBeUpdated); + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case Dhcp6Client.DHCP6_PD_PREFIX_EXPIRED: + final List toBeRemoved = (List) msg.obj; + removeExpiredDelegatedAddresses(toBeRemoved); + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + default: + logError("Unknown CMD_DHCP6_RESULT status: %s", msg.arg1); + } + break; + + case DhcpClient.CMD_ON_QUIT: + // DHCPv4 quit early for some reason. + logError("Unexpected CMD_ON_QUIT from DHCPv4."); + mDhcpClient = null; + break; + + case Dhcp6Client.CMD_ON_QUIT: + // DHCPv6 quit early for some reason. + logError("Unexpected CMD_ON_QUIT from DHCPv6."); + mDhcp6Client = null; + break; + + case CMD_SET_DTIM_MULTIPLIER_AFTER_DELAY: + updateMaxDtimMultiplier(); + break; + + case CMD_UPDATE_APF_CAPABILITIES: + final ApfCapabilities apfCapabilities = (ApfCapabilities) msg.obj; + if (handleUpdateApfCapabilities(apfCapabilities)) { + mApfFilter = maybeCreateApfFilter(apfCapabilities); + } + break; + + case CMD_UPDATE_APF_DATA_SNAPSHOT: + mCallback.startReadPacketFilter(); + sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs); + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + /** + * Set the maximum DTIM multiplier to hardware driver per network condition. Any multiplier + * larger than the maximum value must not be accepted, it will cause packet loss higher than + * what the system can accept, which will cause unexpected behavior for apps, and may interrupt + * the network connection. + * + * When Wifi STA is in the power saving mode and the system is suspended, the wakeup interval + * will be set to: + * 1) multiplier * AP's DTIM period if multiplier > 0. + * 2) the driver default value if multiplier <= 0. + * Some implementations may apply an additional cap to wakeup interval in the case of 1). + */ + private void updateMaxDtimMultiplier() { + int multiplier = deriveDtimMultiplier(); + if (mMaxDtimMultiplier == multiplier) return; + + mMaxDtimMultiplier = multiplier; + log("set max DTIM multiplier to " + multiplier); + mCallback.setMaxDtimMultiplier(multiplier); + } + + /** + * Check if current LinkProperties has either global IPv6 address or ULA (i.e. non IPv6 + * link-local addres). + * + * This function can be used to derive the DTIM multiplier per current network situation or + * decide if we should start DHCPv6 Prefix Delegation when no IPv6 addresses are available + * after autoconf timeout(5s). + */ + private static boolean hasIpv6Address(@NonNull final LinkProperties lp) { + return CollectionUtils.any(lp.getLinkAddresses(), + la -> { + final InetAddress address = la.getAddress(); + return (address instanceof Inet6Address) && !address.isLinkLocalAddress(); + }); + } + + private int deriveDtimMultiplier() { + final boolean hasIpv4Addr = mLinkProperties.hasIpv4Address(); + // For a host in the network that has only ULA and link-local but no GUA, consider + // that it also has IPv6 connectivity. LinkProperties#isIpv6Provisioned only returns + // true when it has a GUA, so we cannot use it for IPv6-only network case. + final boolean hasIpv6Addr = hasIpv6Address(mLinkProperties); + + final int multiplier; + if (!mMulticastFiltering) { + multiplier = mDependencies.getDeviceConfigPropertyInt( + CONFIG_MULTICAST_LOCK_MAX_DTIM_MULTIPLIER, + DEFAULT_MULTICAST_LOCK_MAX_DTIM_MULTIPLIER); + } else if (!hasIpv6Addr + && (SystemClock.elapsedRealtime() < mIPv6ProvisioningDtimGracePeriodMillis)) { + // IPv6 provisioning may or may not complete soon in the future, we don't know when + // it will complete, however, setting multiplier to a high value will cause higher + // RA packet loss, that increases the overall IPv6 provisioning latency. So just set + // multiplier to 1 before device gains the IPv6 provisioning, make sure device won't + // miss any RA packet later. + multiplier = mDependencies.getDeviceConfigPropertyInt( + CONFIG_BEFORE_IPV6_PROV_MAX_DTIM_MULTIPLIER, + DEFAULT_BEFORE_IPV6_PROV_MAX_DTIM_MULTIPLIER); + } else if (hasIpv6Addr && !hasIpv4Addr) { + multiplier = mDependencies.getDeviceConfigPropertyInt( + CONFIG_IPV6_ONLY_NETWORK_MAX_DTIM_MULTIPLIER, + DEFAULT_IPV6_ONLY_NETWORK_MAX_DTIM_MULTIPLIER); + } else if (hasIpv4Addr && !hasIpv6Addr) { + multiplier = mDependencies.getDeviceConfigPropertyInt( + CONFIG_IPV4_ONLY_NETWORK_MAX_DTIM_MULTIPLIER, + DEFAULT_IPV4_ONLY_NETWORK_MAX_DTIM_MULTIPLIER); + } else if (hasIpv6Addr && hasIpv4Addr) { + multiplier = mDependencies.getDeviceConfigPropertyInt( + CONFIG_DUAL_STACK_MAX_DTIM_MULTIPLIER, + DEFAULT_DUAL_STACK_MAX_DTIM_MULTIPLIER); + } else { + multiplier = DTIM_MULTIPLIER_RESET; + } + return multiplier; + } + + private static class MessageHandlingLogger { + public String processedInState; + public String receivedInState; + + public void reset() { + processedInState = null; + receivedInState = null; + } + + public void handled(State processedIn, IState receivedIn) { + processedInState = processedIn.getClass().getSimpleName(); + receivedInState = receivedIn.getName(); + } + + public String toString() { + return String.format("rcvd_in=%s, proc_in=%s", + receivedInState, processedInState); + } + } + + // TODO: extract out into CollectionUtils. + static boolean any(Iterable coll, Predicate fn) { + for (T t : coll) { + if (fn.test(t)) { + return true; + } + } + return false; + } + + static boolean all(Iterable coll, Predicate fn) { + return !any(coll, not(fn)); + } + + static Predicate not(Predicate fn) { + return (t) -> !fn.test(t); + } + + static String join(String delimiter, Collection coll) { + return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); + } + + static T find(Iterable coll, Predicate fn) { + for (T t: coll) { + if (fn.test(t)) { + return t; + } + } + return null; + } + + static List findAll(Collection coll, Predicate fn) { + return coll.stream().filter(fn).collect(Collectors.toList()); + } +} diff --git a/aosp/packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/aosp/packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java new file mode 100755 index 000000000..c62fb9087 --- /dev/null +++ b/aosp/packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -0,0 +1,4183 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; +import static android.net.CaptivePortal.APP_RETURN_DISMISSED; +import static android.net.CaptivePortal.APP_RETURN_UNWANTED; +import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; +import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; +import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL; +import static android.net.DnsResolver.FLAG_EMPTY; +import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP; +import static android.net.DnsResolver.TYPE_A; +import static android.net.DnsResolver.TYPE_AAAA; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs; +import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE; +import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS; +import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK; +import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS; +import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD; +import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE; +import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL; +import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_TCP_POLLING_INTERVAL; +import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD; +import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS; +import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_NONE; +import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP; +import static android.net.util.DataStallUtils.DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD; +import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES; +import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS; +import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS; +import static android.net.util.DataStallUtils.DEFAULT_DNS_LOG_SIZE; +import static android.net.util.DataStallUtils.DEFAULT_TCP_POLLING_INTERVAL_MS; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; + +import static com.android.modules.utils.build.SdkLevel.isAtLeastU; +import static com.android.net.module.util.CollectionUtils.isEmpty; +import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA; +import static com.android.net.module.util.DeviceConfigUtils.getResBooleanConfig; +import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTPS_URL; +import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL; +import static com.android.net.module.util.NetworkStackConstants.TEST_URL_EXPIRATION_TIME; +import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_DNS_EVENTS; +import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_TCP_METRICS; +import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED; +import static com.android.networkstack.apishim.ConstantsShim.TRANSPORT_TEST; +import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX; +import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_URL; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTPS_URL; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTP_URL; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_IGNORE; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_HTTPS_URLS; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_HTTP_URLS; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT; +import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS; +import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT; +import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS; +import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS; +import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_HTTP_URLS; +import static com.android.networkstack.util.NetworkStackUtils.DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.DataStallReportParcelable; +import android.net.DnsResolver; +import android.net.INetworkMonitorCallbacks; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkTestResultParcelable; +import android.net.ProxyInfo; +import android.net.TrafficStats; +import android.net.Uri; +import android.net.captiveportal.CapportApiProbeResult; +import android.net.captiveportal.CaptivePortalProbeResult; +import android.net.captiveportal.CaptivePortalProbeSpec; +import android.net.metrics.IpConnectivityLog; +import android.net.metrics.NetworkEvent; +import android.net.metrics.ValidationProbeEvent; +import android.net.networkstack.aidl.NetworkMonitorParameters; +import android.net.shared.PrivateDnsConfig; +import android.net.util.DataStallUtils.EvaluationType; +import android.net.util.Stopwatch; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.provider.Settings; +import android.stats.connectivity.ProbeResult; +import android.stats.connectivity.ProbeType; +import android.telephony.CellIdentityNr; +import android.telephony.CellInfo; +import android.telephony.CellInfoGsm; +import android.telephony.CellInfoLte; +import android.telephony.CellInfoNr; +import android.telephony.CellInfoTdscdma; +import android.telephony.CellInfoWcdma; +import android.telephony.CellSignalStrength; +import android.telephony.SignalStrength; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; + +import androidx.annotation.ArrayRes; +import androidx.annotation.IntegerRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.VisibleForTesting; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.RingBufferIndices; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.DeviceConfigUtils; +import com.android.net.module.util.NetworkMonitorUtils; +import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.SharedLog; +import com.android.networkstack.NetworkStackNotifier; +import com.android.networkstack.R; +import com.android.networkstack.apishim.CaptivePortalDataShimImpl; +import com.android.networkstack.apishim.NetworkAgentConfigShimImpl; +import com.android.networkstack.apishim.NetworkInformationShimImpl; +import com.android.networkstack.apishim.api29.ConstantsShim; +import com.android.networkstack.apishim.common.CaptivePortalDataShim; +import com.android.networkstack.apishim.common.NetworkAgentConfigShim; +import com.android.networkstack.apishim.common.NetworkInformationShim; +import com.android.networkstack.apishim.common.ShimUtils; +import com.android.networkstack.apishim.common.UnsupportedApiLevelException; +import com.android.networkstack.metrics.DataStallDetectionStats; +import com.android.networkstack.metrics.DataStallStatsUtils; +import com.android.networkstack.metrics.NetworkValidationMetrics; +import com.android.networkstack.netlink.TcpSocketTracker; +import com.android.networkstack.util.DnsUtils; +import com.android.networkstack.util.NetworkStackUtils; +import com.android.server.NetworkStackService.NetworkStackServiceManager; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.InterruptedIOException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.StringJoiner; +import java.util.UUID; +import java.util.concurrent.CompletionService; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * {@hide} + */ +public class NetworkMonitor extends StateMachine { + private static final String TAG = NetworkMonitor.class.getSimpleName(); + private static final boolean DBG = true; + private static final boolean VDBG = false; + private static final boolean DDBG_STALL = false; + private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG); + private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) " + + "AppleWebKit/537.36 (KHTML, like Gecko) " + + "Chrome/60.0.3112.32 Safari/537.36"; + + @VisibleForTesting + static final String CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT = + "captive_portal_dns_probe_timeout"; + @VisibleForTesting + static final String CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS = + "async_privdns_probe_timeout_ms"; + private static final int DEFAULT_PRIVDNS_PROBE_TIMEOUT_MS = 10_000; + + private static final int SOCKET_TIMEOUT_MS = 10000; + private static final int PROBE_TIMEOUT_MS = 3000; + private static final long TEST_URL_EXPIRATION_MS = TimeUnit.MINUTES.toMillis(10); + + private static final int UNSET_MCC_OR_MNC = -1; + + private static final int CAPPORT_API_MAX_JSON_LENGTH = 4096; + private static final String ACCEPT_HEADER = "Accept"; + private static final String CONTENT_TYPE_HEADER = "Content-Type"; + private static final String CAPPORT_API_CONTENT_TYPE = "application/captive+json"; + + enum EvaluationResult { + VALIDATED(true), + CAPTIVE_PORTAL(false); + final boolean mIsValidated; + EvaluationResult(boolean isValidated) { + this.mIsValidated = isValidated; + } + } + + enum ValidationStage { + FIRST_VALIDATION(true), + REVALIDATION(false); + final boolean mIsFirstValidation; + ValidationStage(boolean isFirstValidation) { + this.mIsFirstValidation = isFirstValidation; + } + } + + @VisibleForTesting + protected static final class MccMncOverrideInfo { + public final int mcc; + public final int mnc; + MccMncOverrideInfo(int mcc, int mnc) { + this.mcc = mcc; + this.mnc = mnc; + } + } + + @VisibleForTesting + protected static final SparseArray sCarrierIdToMccMnc = new SparseArray<>(); + + static { + // CTC + // This is a wrong config, but it may need to be here for a while since the + // carrier_list.textpb in OEM side may still wrong. + // TODO: Remove this wrong config when the carrier_list.textpb is corrected everywhere. + sCarrierIdToMccMnc.put(1854, new MccMncOverrideInfo(460, 03)); + // China telecom. + sCarrierIdToMccMnc.put(2237, new MccMncOverrideInfo(460, 03)); + } + + /** + * ConnectivityService has sent a notification to indicate that network has connected. + * Initiates Network Validation. + */ + private static final int CMD_NETWORK_CONNECTED = 1; + + /** + * Message to self indicating it's time to evaluate a network's connectivity. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_REEVALUATE = 6; + + /** + * ConnectivityService has sent a notification to indicate that network has disconnected. + */ + private static final int CMD_NETWORK_DISCONNECTED = 7; + + /** + * Force evaluation even if it has succeeded in the past. + * arg1 = UID responsible for requesting this reeval. Will be billed for data. + */ + private static final int CMD_FORCE_REEVALUATION = 8; + + /** + * Message to self indicating captive portal app finished. + * arg1 = one of: APP_RETURN_DISMISSED, + * APP_RETURN_UNWANTED, + * APP_RETURN_WANTED_AS_IS + * obj = mCaptivePortalLoggedInResponseToken as String + */ + private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9; + + /** + * Message indicating sign-in app should be launched. + * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the + * user touches the sign in notification, or sent by + * ConnectivityService when the user touches the "sign into + * network" button in the wifi access point detail page. + */ + private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11; + + /** + * Retest network to see if captive portal is still in place. + * arg1 = UID responsible for requesting this reeval. Will be billed for data. + * 0 indicates self-initiated, so nobody to blame. + */ + private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12; + + /** + * ConnectivityService notifies NetworkMonitor of settings changes to + * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in + * strict mode, then an event is sent back to ConnectivityService with the + * result of the resolution attempt. + * + * A separate message is used to trigger (re)evaluation of the Private DNS + * configuration, so that the message can be handled as needed in different + * states, including being ignored until after an ongoing captive portal + * validation phase is completed. + */ + private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13; + private static final int CMD_EVALUATE_PRIVATE_DNS = 15; + + /** + * Message to self indicating captive portal detection is completed. + * obj = CaptivePortalProbeResult for detection result; + */ + private static final int CMD_PROBE_COMPLETE = 16; + + /** + * ConnectivityService notifies NetworkMonitor of DNS query responses event. + * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query. + */ + private static final int EVENT_DNS_NOTIFICATION = 17; + + /** + * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and + * NetworkMonitor should ignore the https probe. + */ + private static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18; + + /** + * ConnectivityService notifies NetworkMonitor of changed LinkProperties. + * obj = new LinkProperties. + */ + private static final int EVENT_LINK_PROPERTIES_CHANGED = 19; + + /** + * ConnectivityService notifies NetworkMonitor of changed NetworkCapabilities. + * obj = new NetworkCapabilities. + */ + private static final int EVENT_NETWORK_CAPABILITIES_CHANGED = 20; + + /** + * Message to self to poll current tcp status from kernel. + */ + private static final int EVENT_POLL_TCPINFO = 21; + + /** + * Message to self to do the bandwidth check in EvaluatingBandwidthState. + */ + private static final int CMD_EVALUATE_BANDWIDTH = 22; + + /** + * Message to self to know the bandwidth check is completed. + */ + private static final int CMD_BANDWIDTH_CHECK_COMPLETE = 23; + + /** + * Message to self to know the bandwidth check has timed out. + */ + private static final int CMD_BANDWIDTH_CHECK_TIMEOUT = 24; + + /** + * Message to self to notify resource configuration is changed. + */ + private static final int EVENT_RESOURCE_CONFIG_CHANGED = 25; + + /** + * Message to self to notify that private DNS strict mode hostname resolution has finished. + * + *

    arg2 = Last DNS rcode. + * obj = Pair<List<InetAddress>, DnsCallback>: query results and DnsCallback used. + */ + private static final int CMD_STRICT_MODE_RESOLUTION_COMPLETED = 26; + + /** + * Message to self to notify that the private DNS probe has finished. + * + *

    arg2 = Last DNS rcode. + * obj = Pair<List<InetAddress>, DnsCallback>: query results and DnsCallback used. + */ + private static final int CMD_PRIVATE_DNS_PROBE_COMPLETED = 27; + + /** + * Message to self to notify that private DNS hostname resolution or probing has failed. + */ + private static final int CMD_PRIVATE_DNS_EVALUATION_FAILED = 28; + + /** + * Message to self to notify that a DNS query has timed out. + */ + private static final int CMD_DNS_TIMEOUT = 29; + + // Start mReevaluateDelayMs at this value and double. + @VisibleForTesting + static final int INITIAL_REEVALUATE_DELAY_MS = 1000; + private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000; + // Default timeout of evaluating network bandwidth. + private static final int DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS = 10_000; + // Before network has been evaluated this many times, ignore repeated reevaluate requests. + private static final int IGNORE_REEVALUATE_ATTEMPTS = 5; + private int mReevaluateToken = 0; + private static final int NO_UID = 0; + private static final int INVALID_UID = -1; + private int mUidResponsibleForReeval = INVALID_UID; + // Stop blaming UID that requested re-evaluation after this many attempts. + private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5; + // Delay between reevaluations once a captive portal has been found. + private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000; + private static final int NETWORK_VALIDATION_RESULT_INVALID = 0; + // Max thread pool size for parallel probing. Fixed thread pool size to control the thread + // number used for either HTTP or HTTPS probing. + @VisibleForTesting + static final int MAX_PROBE_THREAD_POOL_SIZE = 5; + private String mPrivateDnsProviderHostname = ""; + + private final Context mContext; + private final INetworkMonitorCallbacks mCallback; + private final int mCallbackVersion; + private final Network mCleartextDnsNetwork; + @NonNull + private final Network mNetwork; + private final TelephonyManager mTelephonyManager; + private final WifiManager mWifiManager; + private final ConnectivityManager mCm; + @Nullable + private final NetworkStackNotifier mNotifier; + private final IpConnectivityLog mMetricsLog; + private final Dependencies mDependencies; + private final TcpSocketTracker mTcpTracker; + // Configuration values for captive portal detection probes. + private final String mCaptivePortalUserAgent; + // Configuration values in setting providers for captive portal detection probes + private final String mCaptivePortalHttpsUrlFromSetting; + private final String mCaptivePortalHttpUrlFromSetting; + @Nullable + private final URL mTestCaptivePortalHttpsUrl; + @Nullable + private final URL mTestCaptivePortalHttpUrl; + @Nullable + private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs; + + // The probing URLs may be updated after constructor if system notifies configuration changed. + // Thus, these probing URLs should only be accessed in the StateMachine thread. + @NonNull + private URL[] mCaptivePortalFallbackUrls; + @NonNull + private URL[] mCaptivePortalHttpUrls; + @NonNull + private URL[] mCaptivePortalHttpsUrls; + + // Configuration values for network bandwidth check. + @Nullable + private final String mEvaluatingBandwidthUrl; + private final int mMaxRetryTimerMs; + private final int mEvaluatingBandwidthTimeoutMs; + private final AtomicInteger mNextEvaluatingBandwidthThreadId = new AtomicInteger(1); + + @NonNull + private NetworkAgentConfigShim mNetworkAgentConfig; + @NonNull + private NetworkCapabilities mNetworkCapabilities; + @NonNull + private LinkProperties mLinkProperties; + + private final boolean mIsCaptivePortalCheckEnabled; + + private boolean mUseHttps; + /** + * The total number of completed validation attempts (network validated or a captive portal was + * detected) for this NetworkMonitor instance. + * This does not include attempts that were interrupted, retried or finished with a result that + * is not success or portal. See {@code mValidationIndex} in {@link NetworkValidationMetrics} + * for a count of all attempts. + * TODO: remove when removing legacy metrics. + */ + private int mValidations = 0; + + // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. + private boolean mUserDoesNotWant = false; + // Avoids surfacing "Sign in to network" notification. + private boolean mDontDisplaySigninNotification = false; + // Set to true once the evaluating network bandwidth is passed or the captive portal respond + // APP_RETURN_WANTED_AS_IS which means the user wants to use this network anyway. + @VisibleForTesting + protected boolean mIsBandwidthCheckPassedOrIgnored = false; + + private final State mDefaultState = new DefaultState(); + private final State mValidatedState = new ValidatedState(); + private final State mMaybeNotifyState = new MaybeNotifyState(); + private final State mEvaluatingState = new EvaluatingState(); + private final State mCaptivePortalState = new CaptivePortalState(); + private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState(); + private final State mStartingPrivateDnsEvaluation = new StartingPrivateDnsEvaluation(); + private final State mResolvingPrivateDnsState = new ResolvingPrivateDnsState(); + private final State mProbingForPrivateDnsState = new ProbingForPrivateDnsState(); + + private final State mProbingState = new ProbingState(); + private final State mWaitingForNextProbeState = new WaitingForNextProbeState(); + private final State mEvaluatingBandwidthState = new EvaluatingBandwidthState(); + + private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; + + private final SharedLog mValidationLogs; + + private final Stopwatch mEvaluationTimer = new Stopwatch(); + + // This variable is set before transitioning to the mCaptivePortalState. + private CaptivePortalProbeResult mLastPortalProbeResult = + CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); + + // Random generator to select fallback URL index + private final Random mRandom; + private int mNextFallbackUrlIndex = 0; + + + private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; + private int mEvaluateAttempts = 0; + private volatile int mProbeToken = 0; + private final int mConsecutiveDnsTimeoutThreshold; + private final int mDataStallMinEvaluateTime; + private final int mDataStallValidDnsTimeThreshold; + private final int mDataStallEvaluationType; + @Nullable + private final DnsStallDetector mDnsStallDetector; + private long mLastProbeTime; + // A bitmask of signals causing a data stall to be suspected. Reset to + // {@link DataStallUtils#DATA_STALL_EVALUATION_TYPE_NONE} after metrics are sent to statsd. + private @EvaluationType int mDataStallTypeToCollect; + private boolean mAcceptPartialConnectivity = false; + private final EvaluationState mEvaluationState = new EvaluationState(); + @NonNull + private final BroadcastReceiver mConfigurationReceiver; + private final boolean mPrivateIpNoInternetEnabled; + + private final boolean mMetricsEnabled; + private final boolean mReevaluateWhenResumeEnabled; + private final boolean mAsyncPrivdnsResolutionEnabled; + + @NonNull + private final NetworkInformationShim mInfoShim = NetworkInformationShimImpl.newInstance(); + + // The validation metrics are accessed by individual probe threads, and by the StateMachine + // thread. All accesses must be synchronized to make sure the StateMachine thread can see + // reports from all probes. + // TODO: as that most usage is in the StateMachine thread and probes only add their probe + // events, consider having probes return their stats to the StateMachine, and only access this + // member on the StateMachine thread without synchronization. + @GuardedBy("mNetworkValidationMetrics") + private final NetworkValidationMetrics mNetworkValidationMetrics = + new NetworkValidationMetrics(); + + private int getCallbackVersion(INetworkMonitorCallbacks cb) { + int version; + try { + version = cb.getInterfaceVersion(); + } catch (RemoteException e) { + version = 0; + } + // The AIDL was freezed from Q beta 5 but it's unfreezing from R before releasing. In order + // to distinguish the behavior between R and Q beta 5 and before Q beta 5, add SDK and + // CODENAME check here. Basically, it's only expected to return 0 for Q beta 4 and below + // because the test result has changed. + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q + && Build.VERSION.CODENAME.equals("REL") + && version == Build.VERSION_CODES.CUR_DEVELOPMENT) version = 0; + return version; + } + + public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, + SharedLog validationLog, @NonNull NetworkStackServiceManager serviceManager) { + this(context, cb, network, new IpConnectivityLog(), validationLog, serviceManager, + Dependencies.DEFAULT, getTcpSocketTrackerOrNull(context, network, + Dependencies.DEFAULT)); + } + + @VisibleForTesting + public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, + IpConnectivityLog logger, SharedLog validationLogs, + @NonNull NetworkStackServiceManager serviceManager, Dependencies deps, + @Nullable TcpSocketTracker tst) { + // Add suffix indicating which NetworkMonitor we're talking about. + super(TAG + "/" + network.toString()); + + // Logs with a tag of the form given just above, e.g. + // 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ... + setDbg(VDBG); + + mContext = context; + mMetricsLog = logger; + mValidationLogs = validationLogs; + mCallback = cb; + mCallbackVersion = getCallbackVersion(cb); + mDependencies = deps; + mNetwork = network; + mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network); + mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mNotifier = serviceManager.getNotifier(); + + // CHECKSTYLE:OFF IndentationCheck + addState(mDefaultState); + addState(mMaybeNotifyState, mDefaultState); + addState(mEvaluatingState, mMaybeNotifyState); + addState(mProbingState, mEvaluatingState); + addState(mWaitingForNextProbeState, mEvaluatingState); + addState(mCaptivePortalState, mMaybeNotifyState); + addState(mEvaluatingPrivateDnsState, mDefaultState); + addState(mStartingPrivateDnsEvaluation, mEvaluatingPrivateDnsState); + addState(mResolvingPrivateDnsState, mEvaluatingPrivateDnsState); + addState(mProbingForPrivateDnsState, mEvaluatingPrivateDnsState); + addState(mEvaluatingBandwidthState, mDefaultState); + addState(mValidatedState, mDefaultState); + setInitialState(mDefaultState); + // CHECKSTYLE:ON IndentationCheck + + mCaptivePortalHttpsUrlFromSetting = + mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTPS_URL, null); + mCaptivePortalHttpUrlFromSetting = + mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTP_URL, null); + mTestCaptivePortalHttpsUrl = + getTestUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL, validationLogs, deps); + mTestCaptivePortalHttpUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL, validationLogs, deps); + mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(context, deps); + mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); + mMetricsEnabled = deps.isFeatureNotChickenedOut(context, + NetworkStackUtils.VALIDATION_METRICS_VERSION); + mReevaluateWhenResumeEnabled = deps.isFeatureEnabled( + context, NetworkStackUtils.REEVALUATE_WHEN_RESUME); + mAsyncPrivdnsResolutionEnabled = deps.isFeatureEnabled(context, + NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION); + mUseHttps = getUseHttpsValidation(); + mCaptivePortalUserAgent = getCaptivePortalUserAgent(); + mCaptivePortalFallbackSpecs = + makeCaptivePortalFallbackProbeSpecs(getCustomizedContextOrDefault()); + mRandom = deps.getRandom(); + // TODO: Evaluate to move data stall configuration to a specific class. + mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold(); + mDataStallMinEvaluateTime = getDataStallMinEvaluateTime(); + mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold(); + mDataStallEvaluationType = getDataStallEvaluationType(); + mDnsStallDetector = initDnsStallDetectorIfRequired(mIsCaptivePortalCheckEnabled, + mDataStallEvaluationType, mConsecutiveDnsTimeoutThreshold); + mTcpTracker = tst; + // Read the configurations of evaluating network bandwidth. + mEvaluatingBandwidthUrl = getResStringConfig(mContext, + R.string.config_evaluating_bandwidth_url, null); + mMaxRetryTimerMs = getResIntConfig(mContext, + R.integer.config_evaluating_bandwidth_max_retry_timer_ms, + MAX_REEVALUATE_DELAY_MS); + mEvaluatingBandwidthTimeoutMs = getResIntConfig(mContext, + R.integer.config_evaluating_bandwidth_timeout_ms, + DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS); + mConfigurationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + sendMessage(EVENT_RESOURCE_CONFIG_CHANGED); + } + } + }; + // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null, + // even before notifyNetworkConnected. + mLinkProperties = new LinkProperties(); + mNetworkCapabilities = new NetworkCapabilities(null); + mNetworkAgentConfig = NetworkAgentConfigShimImpl.newInstance(null); + } + + /** + * ConnectivityService notifies NetworkMonitor that the user already accepted partial + * connectivity previously, so NetworkMonitor can validate the network even if it has partial + * connectivity. + */ + public void setAcceptPartialConnectivity() { + sendMessage(EVENT_ACCEPT_PARTIAL_CONNECTIVITY); + } + + /** + * Request the NetworkMonitor to reevaluate the network. + * + * TODO : refactor reevaluation to introduce rate limiting. If the system finds a network is + * validated but some app can't access their server, or the network is behind a captive portal + * that only lets the validation URL through, apps may be calling reportNetworkConnectivity + * often, causing many revalidation attempts. Meanwhile, reevaluation attempts that result + * from actions that may affect the validation status (e.g. the user just logged in through + * the captive portal app) should never be skipped because of the rate limitation. + */ + public void forceReevaluation(int responsibleUid) { + sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0); + } + + /** + * Send a notification to NetworkMonitor indicating that there was a DNS query response event. + * @param returnCode the DNS return code of the response. + */ + public void notifyDnsResponse(int returnCode) { + sendMessage(EVENT_DNS_NOTIFICATION, returnCode); + } + + /** + * Send a notification to NetworkMonitor indicating that private DNS settings have changed. + * @param newCfg The new private DNS configuration. + */ + public void notifyPrivateDnsSettingsChanged(@NonNull PrivateDnsConfig newCfg) { + // Cancel any outstanding resolutions. + removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED); + // Send the update to the proper thread. + sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg); + } + + /** + * Send a notification to NetworkMonitor indicating that the network is now connected. + * @Deprecated use notifyNetworkConnectedParcel. This method is called on S-. + */ + public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) { + final NetworkMonitorParameters params = new NetworkMonitorParameters(); + params.linkProperties = lp; + params.networkCapabilities = nc; + notifyNetworkConnectedParcel(params); + } + + /** + * Send a notification to NetworkMonitor indicating that the network is now connected. + * Called in S when the Connectivity module is recent enough, or in T+ in all cases. + */ + public void notifyNetworkConnectedParcel(NetworkMonitorParameters params) { + sendMessage(CMD_NETWORK_CONNECTED, params); + } + + private void updateConnectedNetworkAttributes(Message connectedMsg) { + final NetworkMonitorParameters params = (NetworkMonitorParameters) connectedMsg.obj; + mNetworkAgentConfig = NetworkAgentConfigShimImpl.newInstance(params.networkAgentConfig); + mLinkProperties = params.linkProperties; + mNetworkCapabilities = params.networkCapabilities; + suppressNotificationIfNetworkRestricted(); + } + + /** + * Send a notification to NetworkMonitor indicating that the network is now disconnected. + */ + public void notifyNetworkDisconnected() { + sendMessage(CMD_NETWORK_DISCONNECTED); + } + + /** + * Send a notification to NetworkMonitor indicating that link properties have changed. + */ + public void notifyLinkPropertiesChanged(final LinkProperties lp) { + sendMessage(EVENT_LINK_PROPERTIES_CHANGED, new LinkProperties(lp)); + } + + /** + * Send a notification to NetworkMonitor indicating that network capabilities have changed. + */ + public void notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc) { + sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(nc)); + } + + /** + * Request the captive portal application to be launched. + */ + public void launchCaptivePortalApp() { + sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP); + } + + /** + * Notify that the captive portal app was closed with the provided response code. + */ + public void notifyCaptivePortalAppFinished(int response) { + sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); + } + + @Override + protected void log(String s) { + if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s); + } + + private void validationLog(int probeType, Object url, String msg) { + String probeName = ValidationProbeEvent.getProbeName(probeType); + validationLog(String.format("%s %s %s", probeName, url, msg)); + } + + private void validationLog(String s) { + if (DBG) log(s); + mValidationLogs.log(s); + } + + private ValidationStage validationStage() { + return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION; + } + + private boolean isValidationRequired() { + final boolean dunValidationRequired = isAtLeastU() + || mContext.getResources().getBoolean(R.bool.config_validate_dun_networks); + return NetworkMonitorUtils.isValidationRequired(dunValidationRequired, + mNetworkAgentConfig.isVpnValidationRequired(), mNetworkCapabilities); + } + + private boolean isDataStallDetectionRequired() { + return mIsCaptivePortalCheckEnabled && isValidationRequired(); + } + + private boolean isPrivateDnsValidationRequired() { + return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities); + } + + private void suppressNotificationIfNetworkRestricted() { + if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) { + mDontDisplaySigninNotification = true; + } + } + + private void notifyNetworkTested(NetworkTestResultParcelable result) { + try { + if (mCallbackVersion <= 5) { + mCallback.notifyNetworkTested( + getLegacyTestResult(result.result, result.probesSucceeded), + result.redirectUrl); + } else { + mCallback.notifyNetworkTestedWithExtras(result); + } + } catch (RemoteException e) { + Log.e(TAG, "Error sending network test result", e); + } + } + + /** + * Get the test result that was used as an int up to interface version 5. + * + *

    For callback version < 3 (only used in Q beta preview builds), the int represented one of + * the NETWORK_TEST_RESULT_* constants. + * + *

    Q released with version 3, which used a single int for both the evaluation result bitmask, + * and the probesSucceeded bitmask. + */ + protected int getLegacyTestResult(int evaluationResult, int probesSucceeded) { + if (mCallbackVersion < 3) { + if ((evaluationResult & NETWORK_VALIDATION_RESULT_VALID) != 0) { + return NETWORK_TEST_RESULT_VALID; + } + if ((evaluationResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0) { + return NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; + } + return NETWORK_TEST_RESULT_INVALID; + } + + return evaluationResult | probesSucceeded; + } + + private void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) { + try { + mCallback.notifyProbeStatusChanged(probesCompleted, probesSucceeded); + } catch (RemoteException e) { + Log.e(TAG, "Error sending probe status", e); + } + } + + private void showProvisioningNotification(String action) { + try { + mCallback.showProvisioningNotification(action, mContext.getPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error showing provisioning notification", e); + } + } + + private void hideProvisioningNotification() { + try { + mCallback.hideProvisioningNotification(); + } catch (RemoteException e) { + Log.e(TAG, "Error hiding provisioning notification", e); + } + } + + private void notifyDataStallSuspected(@NonNull DataStallReportParcelable p) { + try { + mCallback.notifyDataStallSuspected(p); + } catch (RemoteException e) { + Log.e(TAG, "Error sending notification for suspected data stall", e); + } + } + + private void startMetricsCollection() { + if (!mMetricsEnabled) return; + try { + synchronized (mNetworkValidationMetrics) { + mNetworkValidationMetrics.startCollection(mNetworkCapabilities); + } + } catch (Exception e) { + Log.wtf(TAG, "Error resetting validation metrics", e); + } + } + + private void recordProbeEventMetrics(ProbeType type, long latencyMicros, ProbeResult result, + CaptivePortalDataShim capportData) { + if (!mMetricsEnabled) return; + try { + synchronized (mNetworkValidationMetrics) { + mNetworkValidationMetrics.addProbeEvent(type, latencyMicros, result, capportData); + } + } catch (Exception e) { + Log.wtf(TAG, "Error recording probe event", e); + } + } + + private void recordValidationResult(int result, String redirectUrl) { + if (!mMetricsEnabled) return; + try { + synchronized (mNetworkValidationMetrics) { + mNetworkValidationMetrics.setValidationResult(result, redirectUrl); + } + } catch (Exception e) { + Log.wtf(TAG, "Error recording validation result", e); + } + } + + private void maybeStopCollectionAndSendMetrics() { + if (!mMetricsEnabled) return; + try { + synchronized (mNetworkValidationMetrics) { + mNetworkValidationMetrics.maybeStopCollectionAndSend(); + } + } catch (Exception e) { + Log.wtf(TAG, "Error sending validation stats", e); + } + } + + // DefaultState is the parent of all States. It exists only to handle CMD_* messages but + // does not entail any real state (hence no enter() or exit() routines). + private class DefaultState extends State { + @Override + public void enter() { + // Register configuration broadcast here instead of constructor to prevent start() was + // not called yet when the broadcast is received and cause crash. + mContext.registerReceiver(mConfigurationReceiver, + new IntentFilter(ACTION_CONFIGURATION_CHANGED)); + checkAndRenewResourceConfig(); + final TcpSocketTracker tst = getTcpSocketTracker(); + if (tst != null) { + // Initialization. + tst.setOpportunisticMode(false); + tst.setLinkProperties(mLinkProperties); + tst.setNetworkCapabilities(mNetworkCapabilities); + } + Log.d(TAG, "Starting on network " + mNetwork + + " with capport HTTPS URL " + Arrays.toString(mCaptivePortalHttpsUrls) + + " and HTTP URL " + Arrays.toString(mCaptivePortalHttpUrls)); + } + + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_NETWORK_CONNECTED: + updateConnectedNetworkAttributes(message); + logNetworkEvent(NetworkEvent.NETWORK_CONNECTED); + transitionTo(mEvaluatingState); + return HANDLED; + case CMD_NETWORK_DISCONNECTED: + maybeStopCollectionAndSendMetrics(); + logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED); + if (mTcpTracker != null) { + mTcpTracker.quit(); + } + quit(); + return HANDLED; + case CMD_FORCE_REEVALUATION: + case CMD_CAPTIVE_PORTAL_RECHECK: + if (getCurrentState() == mDefaultState) { + // Before receiving CMD_NETWORK_CONNECTED (when still in mDefaultState), + // requests to reevaluate are not valid: drop them. + return HANDLED; + } + String msg = "Forcing reevaluation for UID " + message.arg1; + final DnsStallDetector dsd = getDnsStallDetector(); + if (dsd != null) { + msg += ". Dns signal count: " + dsd.getConsecutiveTimeoutCount(); + } + validationLog(msg); + mUidResponsibleForReeval = message.arg1; + transitionTo(mEvaluatingState); + return HANDLED; + case CMD_CAPTIVE_PORTAL_APP_FINISHED: + log("CaptivePortal App responded with " + message.arg1); + + // If the user has seen and acted on a captive portal notification, and the + // captive portal app is now closed, disable HTTPS probes. This avoids the + // following pathological situation: + // + // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out. + // 2. User opens the app and logs into the captive portal. + // 3. HTTP starts working, but HTTPS still doesn't work for some other reason - + // perhaps due to the network blocking HTTPS? + // + // In this case, we'll fail to validate the network even after the app is + // dismissed. There is now no way to use this network, because the app is now + // gone, so the user cannot select "Use this network as is". + mUseHttps = false; + + switch (message.arg1) { + case APP_RETURN_DISMISSED: + sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0); + break; + case APP_RETURN_WANTED_AS_IS: + mDontDisplaySigninNotification = true; + // If the user wants to use this network anyway, there is no need to + // perform the bandwidth check even if configured. + mIsBandwidthCheckPassedOrIgnored = true; + // If the user wants to use this network anyway, it should always + // be reported as validated, but other checks still need to be + // done. For example, it should still validate strict private DNS and + // show a notification if not available, because the network will + // be unusable for this additional reason. + mEvaluationState.setCaptivePortalWantedAsIs(); + // A successful evaluation result should be reported immediately, so + // that the network stack may immediately use the validation in ranking + // without waiting for a possibly long private DNS or bandwidth eval + // step. + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_VALID, + null); + // TODO: Distinguish this from a network that actually validates. + // Displaying the "x" on the system UI icon may still be a good idea. + transitionTo(mEvaluatingPrivateDnsState); + break; + case APP_RETURN_UNWANTED: + mDontDisplaySigninNotification = true; + mUserDoesNotWant = true; + mEvaluationState.reportEvaluationResult( + NETWORK_VALIDATION_RESULT_INVALID, null); + + mUidResponsibleForReeval = 0; + transitionTo(mEvaluatingState); + break; + } + return HANDLED; + case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { + final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj; + final TcpSocketTracker tst = getTcpSocketTracker(); + if (!isPrivateDnsValidationRequired() || !cfg.inStrictMode()) { + // No DNS resolution required. + // + // We don't force any validation in opportunistic mode + // here. Opportunistic mode nameservers are validated + // separately within netd. + // + // Reset Private DNS settings state. + mPrivateDnsProviderHostname = ""; + if (tst != null) { + tst.setOpportunisticMode(cfg.inOpportunisticMode()); + } + if (mAsyncPrivdnsResolutionEnabled) { + // When using async privdns validation, reevaluate on any change of + // configuration (even if turning it off), as this will handle + // cancelling current attempts and transitioning to validated state. + removeMessages(CMD_EVALUATE_PRIVATE_DNS); + sendMessage(CMD_EVALUATE_PRIVATE_DNS); + } + break; + } + + mPrivateDnsProviderHostname = cfg.hostname; + if (tst != null) { + tst.setOpportunisticMode(false); + } + + // DNS resolutions via Private DNS strict mode block for a + // few seconds (~4.2) checking for any IP addresses to + // arrive and validate. Initiating a (re)evaluation now + // should not significantly alter the validation outcome. + // + // No matter what: enqueue a validation request; one of + // three things can happen with this request: + // [1] ignored (EvaluatingState or CaptivePortalState) + // [2] transition to EvaluatingPrivateDnsState + // (DefaultState and ValidatedState) + // [3] handled (EvaluatingPrivateDnsState) + // + // The Private DNS configuration to be evaluated will: + // [1] be skipped (not in strict mode), or + // [2] validate (huzzah), or + // [3] encounter some problem (invalid hostname, + // no resolved IP addresses, IPs unreachable, + // port 853 unreachable, port 853 is not running a + // DNS-over-TLS server, et cetera). + // Cancel any outstanding CMD_EVALUATE_PRIVATE_DNS. + removeMessages(CMD_EVALUATE_PRIVATE_DNS); + sendMessage(CMD_EVALUATE_PRIVATE_DNS); + break; + } + case EVENT_DNS_NOTIFICATION: + final DnsStallDetector detector = getDnsStallDetector(); + if (detector != null) { + detector.accumulateConsecutiveDnsTimeoutCount(message.arg1); + } + break; + // Set mAcceptPartialConnectivity to true and if network start evaluating or + // re-evaluating and get the result of partial connectivity, ProbingState will + // disable HTTPS probe and transition to EvaluatingPrivateDnsState. + case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: + maybeDisableHttpsProbing(true /* acceptPartial */); + break; + case EVENT_LINK_PROPERTIES_CHANGED: + final Uri oldCapportUrl = getCaptivePortalApiUrl(mLinkProperties); + mLinkProperties = (LinkProperties) message.obj; + final Uri newCapportUrl = getCaptivePortalApiUrl(mLinkProperties); + if (!Objects.equals(oldCapportUrl, newCapportUrl)) { + sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0); + } + final TcpSocketTracker tst = getTcpSocketTracker(); + if (tst != null) { + tst.setLinkProperties(mLinkProperties); + } + break; + case EVENT_NETWORK_CAPABILITIES_CHANGED: + handleCapabilitiesChanged((NetworkCapabilities) message.obj, + true /* reevaluateOnResume */); + break; + case EVENT_RESOURCE_CONFIG_CHANGED: + // RRO generation does not happen during package installation and instead after + // the OMS receives the PACKAGE_ADDED event, there is a delay where the old + // idmap is used with the new target package resulting in the incorrect overlay + // is used. Renew the resource if a configuration change is received. + // TODO: Remove it once design to generate the idmaps during package + // installation in overlay manager and package manager is ready. + if (checkAndRenewResourceConfig()) { + sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 1 /* forceAccept */); + } + break; + default: + break; + } + return HANDLED; + } + + @Override + public void exit() { + mContext.unregisterReceiver(mConfigurationReceiver); + } + } + + private void handleCapabilitiesChanged(@NonNull final NetworkCapabilities newCap, + boolean reevaluateOnResume) { + // Go to EvaluatingState to reset the network re-evaluation timer when + // the network resumes from suspended. + // This is because the network is expected to be down + // when the device is suspended, and if the delay timer falls back to + // the maximum interval, re-evaluation will be triggered slowly after + // the network resumes. + // Suppress re-evaluation in validated state, if the network has been validated, + // then it's in the expected state. + // TODO(b/287183389): Evaluate once but do not re-evaluate when suspended, to make + // exclamation mark visible by user but doesn't cause too much network traffic. + if (mReevaluateWhenResumeEnabled && reevaluateOnResume + && !mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && newCap.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)) { + // Interrupt if waiting for next probe. + sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 1 /* forceAccept */); + } else if (isVpnUnderlyingNetworkChangeReevaluationRequired(newCap, mNetworkCapabilities)) { + // If no re-evaluation is needed from the previous check, fall-through for lower + // priority checks. + // Reevaluate network if underlying network changes on the validation required + // VPN. + sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0 /* forceAccept */); + } + final TcpSocketTracker tst = getTcpSocketTracker(); + if (tst != null) { + tst.setNetworkCapabilities(newCap); + } + + mNetworkCapabilities = newCap; + suppressNotificationIfNetworkRestricted(); + } + + private boolean isVpnUnderlyingNetworkChangeReevaluationRequired( + final NetworkCapabilities newCap, final NetworkCapabilities oldCap) { + return !newCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + && isValidationRequired() + && !Objects.equals(mInfoShim.getUnderlyingNetworks(newCap), + mInfoShim.getUnderlyingNetworks(oldCap)); + } + + // Being in the ValidatedState State indicates a Network is: + // - Successfully validated, or + // - Wanted "as is" by the user, or + // - Does not satisfy the default NetworkRequest and so validation has been skipped. + private class ValidatedState extends State { + @Override + public void enter() { + maybeLogEvaluationResult( + networkEventType(validationStage(), EvaluationResult.VALIDATED)); + // If the user has accepted partial connectivity and HTTPS probing is disabled, then + // mark the network as validated and partial so that settings can keep informing the + // user that the connection is limited. + int result = NETWORK_VALIDATION_RESULT_VALID; + if (!mUseHttps && mAcceptPartialConnectivity) { + result |= NETWORK_VALIDATION_RESULT_PARTIAL; + } + mEvaluationState.reportEvaluationResult(result, null /* redirectUrl */); + mValidations++; + initSocketTrackingIfRequired(); + // start periodical polling. + sendTcpPollingEvent(); + maybeStopCollectionAndSendMetrics(); + } + + private void initSocketTrackingIfRequired() { + if (!isDataStallDetectionRequired()) return; + + final TcpSocketTracker tst = getTcpSocketTracker(); + if (tst != null) { + tst.pollSocketsInfo(); + } + } + + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_NETWORK_CONNECTED: + updateConnectedNetworkAttributes(message); + transitionTo(mValidatedState); + break; + case CMD_EVALUATE_PRIVATE_DNS: + // TODO: this causes reevaluation of a single probe that is not counted in + // metrics. Add support for such reevaluation probes in metrics, and log them + // separately. + transitionTo(mEvaluatingPrivateDnsState); + break; + case EVENT_DNS_NOTIFICATION: + final DnsStallDetector dsd = getDnsStallDetector(); + if (dsd == null) break; + + dsd.accumulateConsecutiveDnsTimeoutCount(message.arg1); + if (evaluateDataStall()) { + transitionTo(mEvaluatingState); + } + break; + case EVENT_POLL_TCPINFO: + final TcpSocketTracker tst = getTcpSocketTracker(); + if (tst == null) break; + // Transit if retrieve socket info is succeeded and suspected as a stall. + if (tst.pollSocketsInfo() && evaluateDataStall()) { + transitionTo(mEvaluatingState); + } else { + sendTcpPollingEvent(); + } + break; + case EVENT_NETWORK_CAPABILITIES_CHANGED: + // The timer does not need to reset, and it won't need to re-evaluate if + // the network is already validated when resumes. + handleCapabilitiesChanged((NetworkCapabilities) message.obj, + false /* reevaluateOnResume */); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + boolean evaluateDataStall() { + if (isDataStall()) { + validationLog("Suspecting data stall, reevaluate"); + return true; + } + return false; + } + + @Override + public void exit() { + // Not useful for non-ValidatedState. + removeMessages(EVENT_POLL_TCPINFO); + } + } + + @VisibleForTesting + void sendTcpPollingEvent() { + if (isDataStallDetectionRequired()) { + sendMessageDelayed(EVENT_POLL_TCPINFO, getTcpPollingInterval()); + } + } + + private void maybeWriteDataStallStats(@NonNull final CaptivePortalProbeResult result) { + if (mDataStallTypeToCollect == DATA_STALL_EVALUATION_TYPE_NONE) return; + /* + * Collect data stall detection level information for each transport type. Collect type + * specific information for cellular and wifi only currently. Generate + * DataStallDetectionStats for each transport type. E.g., if a network supports both + * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated. + */ + final int[] transports = mNetworkCapabilities.getTransportTypes(); + for (int i = 0; i < transports.length; i++) { + final DataStallDetectionStats stats = + buildDataStallDetectionStats(transports[i], mDataStallTypeToCollect); + mDependencies.writeDataStallDetectionStats(stats, result); + } + mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_NONE; + } + + @VisibleForTesting + protected DataStallDetectionStats buildDataStallDetectionStats(int transport, + @EvaluationType int evaluationType) { + final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder(); + if (VDBG_STALL) { + log("collectDataStallMetrics: type=" + transport + ", evaluation=" + evaluationType); + } + stats.setEvaluationType(evaluationType); + stats.setNetworkType(transport); + switch (transport) { + case NetworkCapabilities.TRANSPORT_WIFI: + // TODO: Update it if status query in dual wifi is supported. + final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + stats.setWiFiData(wifiInfo); + break; + case NetworkCapabilities.TRANSPORT_CELLULAR: + final boolean isRoaming = !mNetworkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); + final SignalStrength ss = mTelephonyManager.getSignalStrength(); + // TODO(b/120452078): Support multi-sim. + stats.setCellData( + mTelephonyManager.getDataNetworkType(), + isRoaming, + mTelephonyManager.getNetworkOperator(), + mTelephonyManager.getSimOperator(), + (ss != null) + ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + break; + default: + // No transport type specific information for the other types. + break; + } + + addDnsEvents(stats); + addTcpStats(stats); + + return stats.build(); + } + + private void addTcpStats(@NonNull final DataStallDetectionStats.Builder stats) { + final TcpSocketTracker tst = getTcpSocketTracker(); + if (tst == null) return; + + stats.setTcpSentSinceLastRecv(tst.getSentSinceLastRecv()); + stats.setTcpFailRate(tst.getLatestPacketFailPercentage()); + } + + @VisibleForTesting + protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) { + final DnsStallDetector dsd = getDnsStallDetector(); + if (dsd == null) return; + + final int size = dsd.mResultIndices.size(); + for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) { + final int index = dsd.mResultIndices.indexOf(size - i); + stats.addDnsEvent(dsd.mDnsEvents[index].mReturnCode, dsd.mDnsEvents[index].mTimeStamp); + } + } + + + // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in + // is required. This State takes care to clear the notification upon exit from the State. + private class MaybeNotifyState extends State { + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_LAUNCH_CAPTIVE_PORTAL_APP: + final Bundle appExtras = new Bundle(); + // OneAddressPerFamilyNetwork is not parcelable across processes. + final Network network = new Network(mCleartextDnsNetwork); + appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network); + final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; + // Use redirect URL from AP if exists. + final String portalUrl = + (useRedirectUrlForPortal() && makeURL(probeRes.redirectUrl) != null) + ? probeRes.redirectUrl : probeRes.detectUrl; + appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, portalUrl); + if (probeRes.probeSpec != null) { + final String encodedSpec = probeRes.probeSpec.getEncodedSpec(); + appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec); + } + appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, + mCaptivePortalUserAgent); + if (mNotifier != null) { + mNotifier.notifyCaptivePortalValidationPending(network); + } + mCm.startCaptivePortalApp(network, appExtras); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + private boolean useRedirectUrlForPortal() { + // It must match the conditions in CaptivePortalLogin in which the redirect URL is not + // used to validate that the portal is gone. + return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); + } + + @Override + public void exit() { + if (mLaunchCaptivePortalAppBroadcastReceiver != null) { + mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver); + mLaunchCaptivePortalAppBroadcastReceiver = null; + } + hideProvisioningNotification(); + } + } + + // Being in the EvaluatingState State indicates the Network is being evaluated for internet + // connectivity, or that the user has indicated that this network is unwanted. + private class EvaluatingState extends State { + private Uri mEvaluatingCapportUrl; + + @Override + public void enter() { + // If we have already started to track time spent in EvaluatingState + // don't reset the timer due simply to, say, commands or events that + // cause us to exit and re-enter EvaluatingState. + if (!mEvaluationTimer.isStarted()) { + mEvaluationTimer.start(); + } + + // Check if the network is captive with Terms & Conditions page. The first network + // evaluation for captive networks with T&Cs returns early but NetworkMonitor will then + // keep checking for connectivity to determine when the T&Cs are cleared. + if (isTermsAndConditionsCaptive(mInfoShim.getCaptivePortalData(mLinkProperties)) + && mValidations == 0) { + mLastPortalProbeResult = new CaptivePortalProbeResult( + CaptivePortalProbeResult.PORTAL_CODE, + mLinkProperties.getCaptivePortalData().getUserPortalUrl() + .toString(), null, + CaptivePortalProbeResult.PROBE_UNKNOWN); + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + mLastPortalProbeResult.redirectUrl); + transitionTo(mCaptivePortalState); + return; + } + sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + if (mUidResponsibleForReeval != INVALID_UID) { + TrafficStats.setThreadStatsUid(mUidResponsibleForReeval); + mUidResponsibleForReeval = INVALID_UID; + } + mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; + mEvaluateAttempts = 0; + mEvaluatingCapportUrl = getCaptivePortalApiUrl(mLinkProperties); + // Reset all current probe results to zero, but retain current validation state until + // validation succeeds or fails. + mEvaluationState.clearProbeResults(); + } + + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_REEVALUATE: + if (message.arg1 != mReevaluateToken || mUserDoesNotWant) { + return HANDLED; + } + // Don't bother validating networks that don't satisfy the default request. + // This includes: + // - VPNs which can be considered explicitly desired by the user and the + // user's desire trumps whether the network validates. + // - Networks that don't provide Internet access. It's unclear how to + // validate such networks. + // - Untrusted networks. It's unsafe to prompt the user to sign-in to + // such networks and the user didn't express interest in connecting to + // such networks (an app did) so the user may be unhappily surprised when + // asked to sign-in to a network they didn't want to connect to in the + // first place. Validation could be done to adjust the network scores + // however these networks are app-requested and may not be intended for + // general usage, in which case general validation may not be an accurate + // measure of the network's quality. Only the app knows how to evaluate + // the network so don't bother validating here. Furthermore sending HTTP + // packets over the network may be undesirable, for example an extremely + // expensive metered network, or unwanted leaking of the User Agent string. + // Also don't bother validating networks that the user already said they + // wanted as-is. + // + // On networks that need to support private DNS in strict mode (e.g., VPNs, but + // not networks that don't provide Internet access), we still need to perform + // private DNS server resolution. + if (mEvaluationState.isCaptivePortalWantedAsIs() + && isPrivateDnsValidationRequired()) { + // Captive portals can only be detected on networks that validate both + // validation and private DNS validation. + validationLog("Captive portal is used as is, resolving private DNS"); + transitionTo(mEvaluatingPrivateDnsState); + return HANDLED; + } else if (!isValidationRequired()) { + if (isPrivateDnsValidationRequired()) { + validationLog("Network would not satisfy default request, " + + "resolving private DNS"); + transitionTo(mEvaluatingPrivateDnsState); + } else { + validationLog("Network would not satisfy default request, " + + "not validating"); + transitionTo(mValidatedState); + } + return HANDLED; + } + mEvaluateAttempts++; + + transitionTo(mProbingState); + return HANDLED; + case CMD_FORCE_REEVALUATION: + // The evaluation process restarts via EvaluatingState#enter. + final boolean forceAccept = (message.arg2 != 0); + return forceAccept || shouldAcceptForceRevalidation() + ? NOT_HANDLED : HANDLED; + // Disable HTTPS probe and transition to EvaluatingPrivateDnsState because: + // 1. Network is connected and finish the network validation. + // 2. NetworkMonitor detects network is partial connectivity and user accepts it. + case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: + maybeDisableHttpsProbing(true /* acceptPartial */); + transitionTo(mEvaluatingPrivateDnsState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + private boolean shouldAcceptForceRevalidation() { + // If the captive portal URL has changed since the last evaluation attempt, always + // revalidate. Otherwise, ignore any re-evaluation requests before + // IGNORE_REEVALUATE_ATTEMPTS are made. + return mEvaluateAttempts >= IGNORE_REEVALUATE_ATTEMPTS + || !Objects.equals( + mEvaluatingCapportUrl, getCaptivePortalApiUrl(mLinkProperties)); + } + + @Override + public void exit() { + TrafficStats.clearThreadStatsUid(); + } + } + + // BroadcastReceiver that waits for a particular Intent and then posts a message. + private class CustomIntentReceiver extends BroadcastReceiver { + private final int mToken; + private final int mWhat; + private final String mAction; + CustomIntentReceiver(String action, int token, int what) { + mToken = token; + mWhat = what; + mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token; + final int flags = SdkLevel.isAtLeastT() ? RECEIVER_NOT_EXPORTED : 0; + mContext.registerReceiver(this, new IntentFilter(mAction), flags); + } + public PendingIntent getPendingIntent() { + final Intent intent = new Intent(mAction); + intent.setPackage(mContext.getPackageName()); + return PendingIntent.getBroadcast(mContext, 0, intent, 0); + } + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken)); + } + } + + // Being in the CaptivePortalState State indicates a captive portal was detected and the user + // has been shown a notification to sign-in. + private class CaptivePortalState extends State { + private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP = + "android.net.netmon.launchCaptivePortalApp"; + + @Override + public void enter() { + maybeLogEvaluationResult( + networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL)); + // Don't annoy user with sign-in notifications. + if (mDontDisplaySigninNotification) return; + // Create a CustomIntentReceiver that sends us a + // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user + // touches the notification. + if (mLaunchCaptivePortalAppBroadcastReceiver == null) { + // Wait for result. + mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver( + ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(), + CMD_LAUNCH_CAPTIVE_PORTAL_APP); + // Display the sign in notification. + // Only do this once for every time we enter MaybeNotifyState. b/122164725 + showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction); + } + // Retest for captive portal occasionally. + sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, + CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); + mValidations++; + maybeStopCollectionAndSendMetrics(); + } + + @Override + public void exit() { + removeMessages(CMD_CAPTIVE_PORTAL_RECHECK); + } + } + + private class EvaluatingPrivateDnsState extends State { + private int mPrivateDnsReevalDelayMs; + private PrivateDnsConfig mPrivateDnsConfig; + + @Override + public void enter() { + mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS; + mPrivateDnsConfig = null; + sendMessage(CMD_EVALUATE_PRIVATE_DNS); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_EVALUATE_PRIVATE_DNS: { + if (mAsyncPrivdnsResolutionEnabled) { + // Cancel any previously scheduled retry attempt + removeMessages(CMD_EVALUATE_PRIVATE_DNS); + + if (inStrictMode()) { + // Note this may happen even in the case where the current state is + // resolve or probe: private DNS evaluation would then restart. + transitionTo(mStartingPrivateDnsEvaluation); + } else { + mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS); + transitionToPrivateDnsEvaluationSuccessState(); + } + break; + } + + if (inStrictMode()) { + if (!isStrictModeHostnameResolved(mPrivateDnsConfig)) { + resolveStrictModeHostname(); + + if (isStrictModeHostnameResolved(mPrivateDnsConfig)) { + notifyPrivateDnsConfigResolved(mPrivateDnsConfig); + } else { + handlePrivateDnsEvaluationFailure(); + // The private DNS probe fails-fast if the server hostname cannot + // be resolved. Record it as a failure with zero latency. + // TODO: refactor this together with the probe recorded in + // sendPrivateDnsProbe, so logging is symmetric / easier to follow. + recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */, + ProbeResult.PR_FAILURE, null /* capportData */); + break; + } + } + + // Look up a one-time hostname, to bypass caching. + // + // Note that this will race with ConnectivityService + // code programming the DNS-over-TLS server IP addresses + // into netd (if invoked, above). If netd doesn't know + // the IP addresses yet, or if the connections to the IP + // addresses haven't yet been validated, netd will block + // for up to a few seconds before failing the lookup. + if (!sendPrivateDnsProbe()) { + handlePrivateDnsEvaluationFailure(); + break; + } + mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, + true /* succeeded */); + } else { + mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS); + } + transitionToPrivateDnsEvaluationSuccessState(); + break; + } + case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { + // When settings change the reevaluation timer must be reset. + mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS; + // Let the message bubble up and be handled by parent states as usual. + return NOT_HANDLED; + } + // Only used with mAsyncPrivdnsResolutionEnabled + case CMD_PRIVATE_DNS_EVALUATION_FAILED: { + reschedulePrivateDnsEvaluation(); + } + default: + return NOT_HANDLED; + } + return HANDLED; + } + + private boolean inStrictMode() { + return !TextUtils.isEmpty(mPrivateDnsProviderHostname); + } + + private void resolveStrictModeHostname() { + try { + // Do a blocking DNS resolution using the network-assigned nameservers. + final InetAddress[] ips = DnsUtils.getAllByName(mDependencies.getDnsResolver(), + mCleartextDnsNetwork, mPrivateDnsProviderHostname, getDnsProbeTimeout(), + str -> validationLog("Strict mode hostname resolution " + str)); + mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips); + } catch (UnknownHostException uhe) { + mPrivateDnsConfig = null; + } + } + + private void handlePrivateDnsEvaluationFailure() { + mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, + false /* succeeded */); + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + null /* redirectUrl */); + reschedulePrivateDnsEvaluation(); + } + + private void reschedulePrivateDnsEvaluation() { + // Queue up a re-evaluation with backoff. + // + // TODO: Consider abandoning this state after a few attempts and + // transitioning back to EvaluatingState, to perhaps give ourselves + // the opportunity to (re)detect a captive portal or something. + // + sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs); + mPrivateDnsReevalDelayMs *= 2; + if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) { + mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS; + } + } + + private boolean sendPrivateDnsProbe() { + final String host = UUID.randomUUID().toString().substring(0, 8) + + PRIVATE_DNS_PROBE_HOST_SUFFIX; + final Stopwatch watch = new Stopwatch().start(); + boolean success = false; + long time; + try { + final InetAddress[] ips = mNetwork.getAllByName(host); + time = watch.stop(); + final String strIps = Arrays.toString(ips); + success = (ips != null && ips.length > 0); + validationLog(PROBE_PRIVDNS, host, String.format("%dus: %s", time, strIps)); + } catch (UnknownHostException uhe) { + time = watch.stop(); + validationLog(PROBE_PRIVDNS, host, + String.format("%dus - Error: %s", time, uhe.getMessage())); + } + recordProbeEventMetrics(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS : + ProbeResult.PR_FAILURE, null /* capportData */); + logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); + return success; + } + } + + private void transitionToPrivateDnsEvaluationSuccessState() { + if (needEvaluatingBandwidth()) { + transitionTo(mEvaluatingBandwidthState); + } else { + // All good! + transitionTo(mValidatedState); + } + } + + private class StartingPrivateDnsEvaluation extends State { + @Override + public void enter() { + transitionTo(mResolvingPrivateDnsState); + } + } + + private class DnsCallback implements DnsResolver.Callback> { + private final int mReplyMessage; + final CancellationSignal mCancellationSignal; + final boolean mHighPriorityResults; + + DnsCallback(int replyMessage, boolean highPriorityResults) { + mReplyMessage = replyMessage; + mCancellationSignal = new CancellationSignal(); + mHighPriorityResults = highPriorityResults; + } + + @Override + public void onAnswer(List answer, int rcode) { + sendMessage(mReplyMessage, 0, rcode, new Pair<>(answer, this)); + } + + @Override + public void onError(DnsResolver.DnsException error) { + sendMessage(mReplyMessage, 0, error.code, new Pair<>(null, this)); + } + } + + /** + * Base class for a state that is sending a DNS query, cancelled if the state is exited. + */ + private abstract class DnsQueryState extends State { + private static final int ERROR_TIMEOUT = -1; + private final int mCompletedCommand; + private final ArraySet mPendingQueries = new ArraySet<>(2); + private final List mResults = new ArrayList<>(); + private String mQueryName; + private long mStartTime; + + private DnsQueryState(int completedCommand) { + mCompletedCommand = completedCommand; + } + + @Override + public void enter() { + mPendingQueries.clear(); + mResults.clear(); + mStartTime = SystemClock.elapsedRealtimeNanos(); + + mQueryName = getQueryName(); + if (TextUtils.isEmpty(mQueryName)) { + // No query necessary (in particular not in strict mode): skip DNS query states + mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS); + transitionToPrivateDnsEvaluationSuccessState(); + return; + } + + final DnsResolver resolver = mDependencies.getDnsResolver(); + mPendingQueries.addAll(sendQueries(mQueryName, resolver)); + sendMessageDelayed(CMD_DNS_TIMEOUT, getTimeoutMs()); + } + + @Override + public void exit() { + removeMessages(CMD_DNS_TIMEOUT); + cancelAllQueries(); + } + + @Override + public boolean processMessage(Message msg) { + if (msg.what == mCompletedCommand) { + final Pair, DnsCallback> result = + (Pair, DnsCallback>) msg.obj; + if (!mPendingQueries.remove(result.second)) { + // Ignore previous queries if the state was exited and re-entered. This state + // calls cancelAllQueries on exit, but this may still happen if results were + // already posted when the querier processed the cancel request. + return HANDLED; + } + + if (result.first != null) { + if (result.second.mHighPriorityResults) { + mResults.addAll(0, result.first); + } else { + mResults.addAll(result.first); + } + } + + if (mPendingQueries.isEmpty()) { + removeMessages(CMD_DNS_TIMEOUT); + final long time = SystemClock.elapsedRealtimeNanos() - mStartTime; + onQueryDone(mQueryName, mResults, msg.arg2 /* lastRCode */, time); + } + return HANDLED; + } else if (msg.what == CMD_DNS_TIMEOUT) { + cancelAllQueries(); + // If some queries were successful, onQueryDone will still proceed, even if + // lastRCode is not a success code. + onQueryDone(mQueryName, mResults, ERROR_TIMEOUT /* lastRCode */, + SystemClock.elapsedRealtimeNanos() - mStartTime); + return HANDLED; + } + return NOT_HANDLED; + } + + private void cancelAllQueries() { + for (int i = 0; i < mPendingQueries.size(); i++) { + mPendingQueries.valueAt(i).mCancellationSignal.cancel(); + } + mPendingQueries.clear(); + } + + abstract void onQueryDone(@NonNull String queryName, @NonNull List answer, + int lastRCode, long elapsedNanos); + + @NonNull + abstract String getQueryName(); + + abstract List sendQueries(@NonNull String queryName, + @NonNull DnsResolver resolver); + + abstract long getTimeoutMs(); + } + + private class ResolvingPrivateDnsState extends DnsQueryState { + private ResolvingPrivateDnsState() { + super(CMD_STRICT_MODE_RESOLUTION_COMPLETED); + } + + @Override + List sendQueries(@NonNull String queryName, @NonNull DnsResolver resolver) { + // Follow legacy behavior that sent AAAA and A queries synchronously in sequence: AAAA + // is marked as highPriorityResults, so they are placed first in the resulting list. + final DnsCallback v6Cb = new DnsCallback(CMD_STRICT_MODE_RESOLUTION_COMPLETED, + true /* highPriorityResults */); + final DnsCallback v4Cb = new DnsCallback(CMD_STRICT_MODE_RESOLUTION_COMPLETED, + false /* highPriorityResults */); + + resolver.query(mCleartextDnsNetwork, queryName, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP, + Runnable::run, v6Cb.mCancellationSignal, v6Cb); + resolver.query(mCleartextDnsNetwork, queryName, TYPE_A, FLAG_NO_CACHE_LOOKUP, + Runnable::run, v4Cb.mCancellationSignal, v4Cb); + + return List.of(v6Cb, v4Cb); + } + + @Override + void onQueryDone(@NonNull String queryName, @NonNull List answer, + int lastRCode, long elapsedNanos) { + if (!Objects.equals(queryName, mPrivateDnsProviderHostname)) { + validationLog("Ignoring stale private DNS resolve answers for " + queryName + + " (now \"" + mPrivateDnsProviderHostname + "\"): " + answer); + // This may happen if mPrivateDnsProviderHostname was changed, in which case + // reevaluation must have been queued (CMD_EVALUATE_PRIVATE_DNS), but results for + // the first evaluation are received before the reevaluation command gets processed. + // Just ignore the results and wait for reevaluation to be processed. + // More generally, reevaluation is scheduled every time the hostname changes, so + // IP addresses matching the hostname are eventually received, but intermediate + // results should be ignored to avoid reporting a PrivateDnsConfig with IP addresses + // that don't match mPrivateDnsProviderHostname. + return; + } + + if (!answer.isEmpty()) { + final InetAddress[] ips = answer.toArray(new InetAddress[0]); + final PrivateDnsConfig config = + new PrivateDnsConfig(mPrivateDnsProviderHostname, ips); + notifyPrivateDnsConfigResolved(config); + + validationLog("Strict mode hostname resolution " + elapsedNanos + "ns OK " + + answer + " for " + mPrivateDnsProviderHostname); + transitionTo(mProbingForPrivateDnsState); + } else { + mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, + false /* succeeded */); + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + null /* redirectUrl */); + + validationLog("Strict mode hostname resolution " + elapsedNanos + "ns FAIL " + + "lastRCode " + lastRCode + " for " + mPrivateDnsProviderHostname); + sendMessage(CMD_PRIVATE_DNS_EVALUATION_FAILED); + + // The private DNS probe fails-fast if the server hostname cannot + // be resolved. Record it as a failure with zero latency. + recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */, + ProbeResult.PR_FAILURE, null /* capportData */); + } + } + + @NonNull + @Override + String getQueryName() { + return mPrivateDnsProviderHostname; + } + + @Override + long getTimeoutMs() { + return getDnsProbeTimeout(); + } + } + + private class ProbingForPrivateDnsState extends DnsQueryState { + private ProbingForPrivateDnsState() { + super(CMD_PRIVATE_DNS_PROBE_COMPLETED); + } + + @Override + public void enter() { + super.enter(); + } + + @Override + List sendQueries(@NonNull String queryName, @NonNull DnsResolver resolver) { + final DnsCallback cb = new DnsCallback(CMD_PRIVATE_DNS_PROBE_COMPLETED, + false /* highPriorityResults */); + resolver.query(mNetwork, queryName, FLAG_EMPTY, Runnable::run, cb.mCancellationSignal, + cb); + return Collections.singletonList(cb); + } + + @Override + void onQueryDone(@NonNull String queryName, @NonNull List answer, + int lastRCode, long elapsedNanos) { + final boolean success = !answer.isEmpty(); + recordProbeEventMetrics(ProbeType.PT_PRIVDNS, elapsedNanos, + success ? ProbeResult.PR_SUCCESS : + ProbeResult.PR_FAILURE, null /* capportData */); + logValidationProbe(elapsedNanos, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); + + final String strIps = Objects.toString(answer); + validationLog(PROBE_PRIVDNS, queryName, + String.format("%dus: %s", elapsedNanos / 1000, strIps)); + + mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, success); + if (success) { + transitionToPrivateDnsEvaluationSuccessState(); + } else { + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + null /* redirectUrl */); + sendMessage(CMD_PRIVATE_DNS_EVALUATION_FAILED); + } + } + + @Override + long getTimeoutMs() { + return getAsyncPrivateDnsProbeTimeout(); + } + + @NonNull + @Override + String getQueryName() { + return UUID.randomUUID().toString().substring(0, 8) + PRIVATE_DNS_PROBE_HOST_SUFFIX; + } + } + + private boolean isStrictModeHostnameResolved(PrivateDnsConfig config) { + return (config != null) + && config.hostname.equals(mPrivateDnsProviderHostname) + && (config.ips.length > 0); + } + + private void notifyPrivateDnsConfigResolved(@NonNull PrivateDnsConfig config) { + try { + mCallback.notifyPrivateDnsConfigResolved(config.toParcel()); + } catch (RemoteException e) { + Log.e(TAG, "Error sending private DNS config resolved notification", e); + } + } + + private class ProbingState extends State { + private Thread mThread; + + @Override + public void enter() { + // When starting a full probe cycle here, record any pending stats (for example if + // CMD_FORCE_REEVALUATE was called before evaluation finished, as can happen in + // EvaluatingPrivateDnsState). + maybeStopCollectionAndSendMetrics(); + // Restart the metrics collection timers. Metrics will be stopped and sent when the + // validation attempt finishes (as success, failure or portal), or if it is interrupted + // (by being restarted or if NetworkMonitor stops). + startMetricsCollection(); + if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { + //Don't continue to blame UID forever. + TrafficStats.clearThreadStatsUid(); + } + + final int token = ++mProbeToken; + final ValidationProperties deps = new ValidationProperties(mNetworkCapabilities); + final URL fallbackUrl = nextFallbackUrl(); + final URL[] httpsUrls = Arrays.copyOf( + mCaptivePortalHttpsUrls, mCaptivePortalHttpsUrls.length); + final URL[] httpUrls = Arrays.copyOf( + mCaptivePortalHttpUrls, mCaptivePortalHttpUrls.length); + mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0, + isCaptivePortal(deps, httpsUrls, httpUrls, fallbackUrl)))); + mThread.start(); + mDependencies.onThreadCreated(mThread); + } + + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_PROBE_COMPLETE: + // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored. + if (message.arg1 != mProbeToken) { + return HANDLED; + } + + final CaptivePortalProbeResult probeResult = + (CaptivePortalProbeResult) message.obj; + mLastProbeTime = SystemClock.elapsedRealtime(); + + maybeWriteDataStallStats(probeResult); + + if (probeResult.isSuccessful()) { + // Transit EvaluatingPrivateDnsState to get to Validated + // state (even if no Private DNS validation required). + transitionTo(mEvaluatingPrivateDnsState); + } else if (isTermsAndConditionsCaptive( + mInfoShim.getCaptivePortalData(mLinkProperties))) { + mLastPortalProbeResult = new CaptivePortalProbeResult( + CaptivePortalProbeResult.PORTAL_CODE, + mLinkProperties.getCaptivePortalData().getUserPortalUrl() + .toString(), null, + CaptivePortalProbeResult.PROBE_UNKNOWN); + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + mLastPortalProbeResult.redirectUrl); + transitionTo(mCaptivePortalState); + } else if (probeResult.isPortal()) { + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + probeResult.redirectUrl); + mLastPortalProbeResult = probeResult; + transitionTo(mCaptivePortalState); + } else if (probeResult.isPartialConnectivity()) { + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL, + null /* redirectUrl */); + maybeDisableHttpsProbing(mAcceptPartialConnectivity); + if (mAcceptPartialConnectivity) { + transitionTo(mEvaluatingPrivateDnsState); + } else { + transitionTo(mWaitingForNextProbeState); + } + } else { + logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + null /* redirectUrl */); + transitionTo(mWaitingForNextProbeState); + } + return HANDLED; + case EVENT_DNS_NOTIFICATION: + case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: + // Leave the event to DefaultState. + return NOT_HANDLED; + default: + // Wait for probe result and defer events to next state by default. + deferMessage(message); + return HANDLED; + } + } + + @Override + public void exit() { + if (mThread.isAlive()) { + mThread.interrupt(); + } + mThread = null; + } + } + + // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is + // transited from ProbingState. This ensures that the state machine is only in ProbingState + // while a probe is in progress, not while waiting to perform the next probe. That allows + // ProbingState to defer most messages until the probe is complete, which keeps the code simple + // and matches the pre-Q behaviour where probes were a blocking operation performed on the state + // machine thread. + private class WaitingForNextProbeState extends State { + @Override + public void enter() { + // Send metrics for this evaluation attempt. Metrics collection (and its timers) will be + // restarted when the next probe starts. + maybeStopCollectionAndSendMetrics(); + scheduleNextProbe(); + } + + private void scheduleNextProbe() { + final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + sendMessageDelayed(msg, mReevaluateDelayMs); + mReevaluateDelayMs *= 2; + if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { + mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; + } + } + + @Override + public boolean processMessage(Message message) { + return NOT_HANDLED; + } + } + + private final class EvaluatingBandwidthThread extends Thread { + final int mThreadId; + + EvaluatingBandwidthThread(int id) { + mThreadId = id; + } + + @Override + public void run() { + HttpURLConnection urlConnection = null; + try { + final URL url = makeURL(mEvaluatingBandwidthUrl); + urlConnection = makeProbeConnection(url, true /* followRedirects */); + // In order to exclude the time of DNS lookup, send the delay message of timeout + // here. + sendMessageDelayed(CMD_BANDWIDTH_CHECK_TIMEOUT, mEvaluatingBandwidthTimeoutMs); + readContentFromDownloadUrl(urlConnection); + } catch (InterruptedIOException e) { + // There is a timing issue that someone triggers the forcing reevaluation when + // executing the getInputStream(). The InterruptedIOException is thrown by + // Timeout#throwIfReached, it will reset the interrupt flag of Thread. So just + // return and wait for the bandwidth reevaluation, otherwise the + // CMD_BANDWIDTH_CHECK_COMPLETE will be sent. + validationLog("The thread is interrupted when executing the getInputStream()," + + " return and wait for the bandwidth reevaluation"); + return; + } catch (IOException e) { + validationLog("Evaluating bandwidth failed: " + e + ", if the thread is not" + + " interrupted, transition to validated state directly to make sure user" + + " can use wifi normally."); + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + // Don't send CMD_BANDWIDTH_CHECK_COMPLETE if the IO is interrupted or timeout. + // Only send CMD_BANDWIDTH_CHECK_COMPLETE when the download is finished normally. + // Add a serial number for CMD_BANDWIDTH_CHECK_COMPLETE to prevent handling the obsolete + // CMD_BANDWIDTH_CHECK_COMPLETE. + if (!isInterrupted()) sendMessage(CMD_BANDWIDTH_CHECK_COMPLETE, mThreadId); + } + + private void readContentFromDownloadUrl(@NonNull final HttpURLConnection conn) + throws IOException { + final byte[] buffer = new byte[1000]; + final InputStream is = conn.getInputStream(); + while (!isInterrupted() && is.read(buffer) > 0) { /* read again */ } + } + } + + private class EvaluatingBandwidthState extends State { + private EvaluatingBandwidthThread mEvaluatingBandwidthThread; + private int mRetryBandwidthDelayMs; + private int mCurrentThreadId; + + @Override + public void enter() { + mRetryBandwidthDelayMs = getResIntConfig(mContext, + R.integer.config_evaluating_bandwidth_min_retry_timer_ms, + INITIAL_REEVALUATE_DELAY_MS); + sendMessage(CMD_EVALUATE_BANDWIDTH); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_EVALUATE_BANDWIDTH: + mCurrentThreadId = mNextEvaluatingBandwidthThreadId.getAndIncrement(); + mEvaluatingBandwidthThread = new EvaluatingBandwidthThread(mCurrentThreadId); + mEvaluatingBandwidthThread.start(); + break; + case CMD_BANDWIDTH_CHECK_COMPLETE: + // Only handle the CMD_BANDWIDTH_CHECK_COMPLETE which is sent by the newest + // EvaluatingBandwidthThread. + if (mCurrentThreadId == msg.arg1) { + mIsBandwidthCheckPassedOrIgnored = true; + transitionTo(mValidatedState); + } + break; + case CMD_BANDWIDTH_CHECK_TIMEOUT: + validationLog("Evaluating bandwidth timeout!"); + mEvaluatingBandwidthThread.interrupt(); + scheduleReevaluatingBandwidth(); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + private void scheduleReevaluatingBandwidth() { + sendMessageDelayed(obtainMessage(CMD_EVALUATE_BANDWIDTH), mRetryBandwidthDelayMs); + mRetryBandwidthDelayMs *= 2; + if (mRetryBandwidthDelayMs > mMaxRetryTimerMs) { + mRetryBandwidthDelayMs = mMaxRetryTimerMs; + } + } + + @Override + public void exit() { + mEvaluatingBandwidthThread.interrupt(); + removeMessages(CMD_EVALUATE_BANDWIDTH); + removeMessages(CMD_BANDWIDTH_CHECK_TIMEOUT); + } + } + + // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at + // most one per address family. This ensures we only wait up to 20 seconds for TCP connections + // to complete, regardless of how many IP addresses a host has. + private static class OneAddressPerFamilyNetwork extends Network { + OneAddressPerFamilyNetwork(Network network) { + // Always bypass Private DNS. + super(network.getPrivateDnsBypassingCopy()); + } + + @Override + public InetAddress[] getAllByName(String host) throws UnknownHostException { + final List addrs = Arrays.asList(super.getAllByName(host)); + + // Ensure the address family of the first address is tried first. + LinkedHashMap addressByFamily = new LinkedHashMap<>(); + addressByFamily.put(addrs.get(0).getClass(), addrs.get(0)); + Collections.shuffle(addrs); + + for (InetAddress addr : addrs) { + addressByFamily.put(addr.getClass(), addr); + } + + return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]); + } + } + + @VisibleForTesting + boolean onlyWifiTransport() { + int[] transportTypes = mNetworkCapabilities.getTransportTypes(); + return transportTypes.length == 1 + && transportTypes[0] == NetworkCapabilities.TRANSPORT_WIFI; + } + + @VisibleForTesting + boolean needEvaluatingBandwidth() { + if (mIsBandwidthCheckPassedOrIgnored + || TextUtils.isEmpty(mEvaluatingBandwidthUrl) + || !mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) + || !onlyWifiTransport()) { + return false; + } + + return true; + } + + private static boolean getIsCaptivePortalCheckEnabled(@NonNull Context context, + @NonNull Dependencies dependencies) { + String symbol = CAPTIVE_PORTAL_MODE; + int defaultValue = CAPTIVE_PORTAL_MODE_PROMPT; + int mode = dependencies.getSetting(context, symbol, defaultValue); + return mode != CAPTIVE_PORTAL_MODE_IGNORE; + } + + private boolean getIsPrivateIpNoInternetEnabled() { + return mDependencies.isFeatureEnabled(mContext, DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION) + || mContext.getResources().getBoolean( + R.bool.config_force_dns_probe_private_ip_no_internet); + } + + private boolean getUseHttpsValidation() { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; + } + + @Nullable + private String getMccFromCellInfo(final CellInfo cell) { + if (cell instanceof CellInfoGsm) { + return ((CellInfoGsm) cell).getCellIdentity().getMccString(); + } else if (cell instanceof CellInfoLte) { + return ((CellInfoLte) cell).getCellIdentity().getMccString(); + } else if (cell instanceof CellInfoWcdma) { + return ((CellInfoWcdma) cell).getCellIdentity().getMccString(); + } else if (cell instanceof CellInfoTdscdma) { + return ((CellInfoTdscdma) cell).getCellIdentity().getMccString(); + } else if (cell instanceof CellInfoNr) { + return ((CellIdentityNr) ((CellInfoNr) cell).getCellIdentity()).getMccString(); + } else { + return null; + } + } + + /** + * Return location mcc. + */ + @VisibleForTesting + @Nullable + protected String getLocationMcc() { + // Adding this check is because the new permission won't be granted by mainline update, + // the new permission only be granted by OTA for current design. Tracking: b/145774617. + if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, + Process.myPid(), Process.myUid()) + == PackageManager.PERMISSION_DENIED) { + log("getLocationMcc : NetworkStack does not hold ACCESS_FINE_LOCATION"); + return null; + } + try { + final List cells = mTelephonyManager.getAllCellInfo(); + if (cells == null) { + log("CellInfo is null"); + return null; + } + final Map countryCodeMap = new HashMap<>(); + int maxCount = 0; + for (final CellInfo cell : cells) { + final String mcc = getMccFromCellInfo(cell); + if (mcc != null) { + final int count = countryCodeMap.getOrDefault(mcc, 0) + 1; + countryCodeMap.put(mcc, count); + } + } + // Return the MCC which occurs most. + if (countryCodeMap.size() <= 0) return null; + return Collections.max(countryCodeMap.entrySet(), + (e1, e2) -> e1.getValue().compareTo(e2.getValue())).getKey(); + } catch (SecurityException e) { + log("Permission is not granted:" + e); + return null; + } + } + + /** + * Return a matched MccMncOverrideInfo if carrier id and sim mccmnc are matching a record in + * sCarrierIdToMccMnc. + */ + @VisibleForTesting + @Nullable + MccMncOverrideInfo getMccMncOverrideInfo() { + final int carrierId = mTelephonyManager.getSimCarrierId(); + return sCarrierIdToMccMnc.get(carrierId); + } + + private Context getContextByMccMnc(final int mcc, final int mnc) { + final Configuration config = mContext.getResources().getConfiguration(); + if (mcc != UNSET_MCC_OR_MNC) config.mcc = mcc; + if (mnc != UNSET_MCC_OR_MNC) config.mnc = mnc; + return mContext.createConfigurationContext(config); + } + + @VisibleForTesting + protected Context getCustomizedContextOrDefault() { + // Return customized context if carrier id can match a record in sCarrierIdToMccMnc. + final MccMncOverrideInfo overrideInfo = getMccMncOverrideInfo(); + if (overrideInfo != null) { + log("Return customized context by MccMncOverrideInfo."); + return getContextByMccMnc(overrideInfo.mcc, overrideInfo.mnc); + } + + // Use neighbor mcc feature only works when the config_no_sim_card_uses_neighbor_mcc is + // true and there is no sim card inserted. + final boolean useNeighborResource = + getResBooleanConfig(mContext, R.bool.config_no_sim_card_uses_neighbor_mcc, false); + if (!useNeighborResource + || TelephonyManager.SIM_STATE_READY == mTelephonyManager.getSimState()) { + if (useNeighborResource) log("Sim state is ready, return original context."); + return mContext; + } + + final String mcc = getLocationMcc(); + if (TextUtils.isEmpty(mcc)) { + log("Return original context due to getting mcc failed."); + return mContext; + } + + return getContextByMccMnc(Integer.parseInt(mcc), UNSET_MCC_OR_MNC); + } + + @Nullable + private static URL getTestUrl(@NonNull String key, @NonNull SharedLog log, + @NonNull Dependencies deps) { + final String strExpiration = deps.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, + TEST_URL_EXPIRATION_TIME, null); + if (strExpiration == null) return null; + + final long expTime; + try { + expTime = Long.parseUnsignedLong(strExpiration); + } catch (NumberFormatException e) { + log.e("Invalid test URL expiration time format", e); + return null; + } + + final long now = System.currentTimeMillis(); + if (expTime < now || (expTime - now) > TEST_URL_EXPIRATION_MS) { + log.w("Skipping test URL with expiration " + expTime + ", now " + now); + return null; + } + + final String strUrl = deps.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, + key, null /* defaultValue */); + if (!isValidTestUrl(strUrl)) { + log.w("Skipping invalid test URL " + strUrl); + return null; + } + return makeURL(strUrl, log); + } + + private String getCaptivePortalServerHttpsUrl(@NonNull Context context) { + return getSettingFromResource(context, + R.string.config_captive_portal_https_url, mCaptivePortalHttpsUrlFromSetting, + context.getResources().getString( + R.string.default_captive_portal_https_url)); + } + + private static boolean isValidTestUrl(@Nullable String url) { + if (TextUtils.isEmpty(url)) return false; + + try { + // Only accept test URLs on localhost + return Uri.parse(url).getHost().equals("localhost"); + } catch (Throwable e) { + Log.wtf(TAG, "Error parsing test URL", e); + return false; + } + } + + private int getDnsProbeTimeout() { + return getIntSetting(mContext, R.integer.config_captive_portal_dns_probe_timeout, + CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT, DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT); + } + + private int getAsyncPrivateDnsProbeTimeout() { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS, DEFAULT_PRIVDNS_PROBE_TIMEOUT_MS); + } + + /** + * Gets an integer setting from resources or device config + * + * configResource is used if set, followed by device config if set, followed by defaultValue. + * If none of these are set then an exception is thrown. + * + * TODO: move to a common location such as a ConfigUtils class. + * TODO(b/130324939): test that the resources can be overlayed by an RRO package. + */ + @VisibleForTesting + int getIntSetting(@NonNull final Context context, @StringRes int configResource, + @NonNull String symbol, int defaultValue) { + final Resources res = context.getResources(); + try { + return res.getInteger(configResource); + } catch (Resources.NotFoundException e) { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + symbol, defaultValue); + } + } + + /** + * Gets integer config from resources. + */ + @VisibleForTesting + int getResIntConfig(@NonNull final Context context, + @IntegerRes final int configResource, final int defaultValue) { + final Resources res = context.getResources(); + try { + return res.getInteger(configResource); + } catch (Resources.NotFoundException e) { + return defaultValue; + } + } + + /** + * Gets string config from resources. + */ + @VisibleForTesting + String getResStringConfig(@NonNull final Context context, + @StringRes final int configResource, @Nullable final String defaultValue) { + final Resources res = context.getResources(); + try { + return res.getString(configResource); + } catch (Resources.NotFoundException e) { + return defaultValue; + } + } + + /** + * Get the captive portal server HTTP URL that is configured on the device. + * + * NetworkMonitor does not use {@link ConnectivityManager#getCaptivePortalServerUrl()} as + * it has its own updatable strategies to detect captive portals. The framework only advises + * on one URL that can be used, while NetworkMonitor may implement more complex logic. + */ + public String getCaptivePortalServerHttpUrl(@NonNull Context context) { + return getSettingFromResource(context, + R.string.config_captive_portal_http_url, mCaptivePortalHttpUrlFromSetting, + context.getResources().getString( + R.string.default_captive_portal_http_url)); + } + + private int getConsecutiveDnsTimeoutThreshold() { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD, + DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD); + } + + private int getDataStallMinEvaluateTime() { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL, + DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS); + } + + private int getDataStallValidDnsTimeThreshold() { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD, + DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS); + } + + @VisibleForTesting + int getDataStallEvaluationType() { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + CONFIG_DATA_STALL_EVALUATION_TYPE, + DEFAULT_DATA_STALL_EVALUATION_TYPES); + } + + private int getTcpPollingInterval() { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + CONFIG_DATA_STALL_TCP_POLLING_INTERVAL, + DEFAULT_TCP_POLLING_INTERVAL_MS); + } + + @VisibleForTesting + URL[] makeCaptivePortalFallbackUrls(@NonNull Context context) { + try { + final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL, + null); + final URL[] settingProviderUrls = + combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_FALLBACK_URLS); + return getProbeUrlArrayConfig(context, settingProviderUrls, + R.array.config_captive_portal_fallback_urls, + R.array.default_captive_portal_fallback_urls, + this::makeURL); + } catch (Exception e) { + // Don't let a misconfiguration bootloop the system. + Log.e(TAG, "Error parsing configured fallback URLs", e); + return new URL[0]; + } + } + + private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs(@NonNull Context context) { + try { + final String settingsValue = mDependencies.getDeviceConfigProperty( + NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null); + + final CaptivePortalProbeSpec[] emptySpecs = new CaptivePortalProbeSpec[0]; + final CaptivePortalProbeSpec[] providerValue = TextUtils.isEmpty(settingsValue) + ? emptySpecs + : parseCaptivePortalProbeSpecs(settingsValue).toArray(emptySpecs); + + return getProbeUrlArrayConfig(context, providerValue, + R.array.config_captive_portal_fallback_probe_specs, + DEFAULT_CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, + CaptivePortalProbeSpec::parseSpecOrNull); + } catch (Exception e) { + // Don't let a misconfiguration bootloop the system. + Log.e(TAG, "Error parsing configured fallback probe specs", e); + return null; + } + } + + private URL[] makeCaptivePortalHttpsUrls(@NonNull Context context) { + if (mTestCaptivePortalHttpsUrl != null) return new URL[] { mTestCaptivePortalHttpsUrl }; + + final String firstUrl = getCaptivePortalServerHttpsUrl(context); + try { + final URL[] settingProviderUrls = + combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTPS_URLS); + // firstUrl will at least be default configuration, so default value in + // getProbeUrlArrayConfig is actually never used. + return getProbeUrlArrayConfig(context, settingProviderUrls, + R.array.config_captive_portal_https_urls, + DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS, this::makeURL); + } catch (Exception e) { + // Don't let a misconfiguration bootloop the system. + Log.e(TAG, "Error parsing configured https URLs", e); + // Ensure URL aligned with legacy configuration. + return new URL[]{makeURL(firstUrl)}; + } + } + + private URL[] makeCaptivePortalHttpUrls(@NonNull Context context) { + if (mTestCaptivePortalHttpUrl != null) return new URL[] { mTestCaptivePortalHttpUrl }; + + final String firstUrl = getCaptivePortalServerHttpUrl(context); + try { + final URL[] settingProviderUrls = + combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTP_URLS); + // firstUrl will at least be default configuration, so default value in + // getProbeUrlArrayConfig is actually never used. + return getProbeUrlArrayConfig(context, settingProviderUrls, + R.array.config_captive_portal_http_urls, + DEFAULT_CAPTIVE_PORTAL_HTTP_URLS, this::makeURL); + } catch (Exception e) { + // Don't let a misconfiguration bootloop the system. + Log.e(TAG, "Error parsing configured http URLs", e); + // Ensure URL aligned with legacy configuration. + return new URL[]{makeURL(firstUrl)}; + } + } + + private URL[] combineCaptivePortalUrls(final String firstUrl, final String propertyName) { + if (TextUtils.isEmpty(firstUrl)) return new URL[0]; + + final String otherUrls = mDependencies.getDeviceConfigProperty( + NAMESPACE_CONNECTIVITY, propertyName, ""); + // otherUrls may be empty, but .split() ignores trailing empty strings + final String separator = ","; + final String[] urls = (firstUrl + separator + otherUrls).split(separator); + return convertStrings(urls, this::makeURL, new URL[0]); + } + + /** + * Read a setting from a resource or the settings provider. + * + *

    The configuration resource is prioritized, then the provider value. + * @param context The context + * @param configResource The resource id for the configuration parameter + * @param settingValue The value in the settings provider + * @param defaultValue The default value + * @return The best available value + */ + @Nullable + private String getSettingFromResource(@NonNull final Context context, + @StringRes int configResource, @NonNull String settingValue, + @NonNull String defaultValue) { + final Resources res = context.getResources(); + String setting = res.getString(configResource); + + if (!TextUtils.isEmpty(setting)) return setting; + + if (!TextUtils.isEmpty(settingValue)) return settingValue; + + return defaultValue; + } + + /** + * Get an array configuration from resources or the settings provider. + * + *

    The configuration resource is prioritized, then the provider values, then the default + * resource values. + * + * @param context The Context + * @param providerValue Values obtained from the setting provider. + * @param configResId ID of the configuration resource. + * @param defaultResId ID of the default resource. + * @param resourceConverter Converter from the resource strings to stored setting class. Null + * return values are ignored. + */ + private T[] getProbeUrlArrayConfig(@NonNull Context context, @NonNull T[] providerValue, + @ArrayRes int configResId, @ArrayRes int defaultResId, + @NonNull Function resourceConverter) { + final Resources res = context.getResources(); + return getProbeUrlArrayConfig(context, providerValue, configResId, + res.getStringArray(defaultResId), resourceConverter); + } + + /** + * Get an array configuration from resources or the settings provider. + * + *

    The configuration resource is prioritized, then the provider values, then the default + * resource values. + * + * @param context The Context + * @param providerValue Values obtained from the setting provider. + * @param configResId ID of the configuration resource. + * @param defaultConfig Values of default configuration. + * @param resourceConverter Converter from the resource strings to stored setting class. Null + * return values are ignored. + */ + private T[] getProbeUrlArrayConfig(@NonNull Context context, @NonNull T[] providerValue, + @ArrayRes int configResId, String[] defaultConfig, + @NonNull Function resourceConverter) { + final Resources res = context.getResources(); + String[] configValue = res.getStringArray(configResId); + + if (configValue.length == 0) { + if (providerValue.length > 0) { + return providerValue; + } + + configValue = defaultConfig; + } + + return convertStrings(configValue, resourceConverter, Arrays.copyOf(providerValue, 0)); + } + + /** + * Convert a String array to an array of some other type using the specified converter. + * + *

    Any null value, or value for which the converter throws a {@link RuntimeException}, will + * not be added to the output array, so the output array may be smaller than the input. + */ + private T[] convertStrings( + @NonNull String[] strings, Function converter, T[] emptyArray) { + final ArrayList convertedValues = new ArrayList<>(strings.length); + for (String configString : strings) { + T convertedValue = null; + try { + convertedValue = converter.apply(configString); + } catch (Exception e) { + Log.e(TAG, "Error parsing configuration", e); + // Fall through + } + if (convertedValue != null) { + convertedValues.add(convertedValue); + } + } + return convertedValues.toArray(emptyArray); + } + + private String getCaptivePortalUserAgent() { + return mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, + CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT); + } + + private URL nextFallbackUrl() { + if (mCaptivePortalFallbackUrls.length == 0) { + return null; + } + int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length; + mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory. + return mCaptivePortalFallbackUrls[idx]; + } + + private CaptivePortalProbeSpec nextFallbackSpec() { + if (isEmpty(mCaptivePortalFallbackSpecs)) { + return null; + } + // Randomly change spec without memory. Also randomize the first attempt. + final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length; + return mCaptivePortalFallbackSpecs[idx]; + } + + /** + * Validation properties that can be accessed by the evaluation thread in a thread-safe way. + * + * Parameters such as LinkProperties and NetworkCapabilities cannot be accessed by the + * evaluation thread directly, as they are managed in the state machine thread and not + * synchronized. This class provides a copy of the required data that is not modified and can be + * used safely by the evaluation thread. + */ + private static class ValidationProperties { + // TODO: add other properties that are needed for evaluation and currently extracted in a + // non-thread-safe way from LinkProperties, NetworkCapabilities, etc. + private final boolean mIsTestNetwork; + + ValidationProperties(NetworkCapabilities nc) { + this.mIsTestNetwork = nc.hasTransport(TRANSPORT_TEST); + } + } + + private CaptivePortalProbeResult isCaptivePortal(ValidationProperties properties, + URL[] httpsUrls, URL[] httpUrls, URL fallbackUrl) { + if (!mIsCaptivePortalCheckEnabled) { + validationLog("Validation disabled."); + return CaptivePortalProbeResult.success(CaptivePortalProbeResult.PROBE_UNKNOWN); + } + + URL pacUrl = null; + + // On networks with a PAC instead of fetching a URL that should result in a 204 + // response, we instead simply fetch the PAC script. This is done for a few reasons: + // 1. At present our PAC code does not yet handle multiple PACs on multiple networks + // until something like https://android-review.googlesource.com/#/c/115180/ lands. + // Network.openConnection() will ignore network-specific PACs and instead fetch + // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with + // NO_PROXY is the fetch of the PAC itself. + // 2. To proxy the generate_204 fetch through a PAC would require a number of things + // happen before the fetch can commence, namely: + // a) the PAC script be fetched + // b) a PAC script resolver service be fired up and resolve the captive portal + // server. + // Network validation could be delayed until these prerequisities are satisifed or + // could simply be left to race them. Neither is an optimal solution. + // 3. PAC scripts are sometimes used to block or restrict Internet access and may in + // fact block fetching of the generate_204 URL which would lead to false negative + // results for network validation. + final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy(); + if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { + pacUrl = makeURL(proxyInfo.getPacFileUrl().toString()); + if (pacUrl == null) { + return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); + } + } + + if ((pacUrl == null) && (httpUrls.length == 0 || httpsUrls.length == 0 + || httpUrls[0] == null || httpsUrls[0] == null)) { + return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); + } + + long startTime = SystemClock.elapsedRealtime(); + + final CaptivePortalProbeResult result; + if (pacUrl != null) { + result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC); + reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); + } else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) { + // Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes. + result = sendHttpAndHttpsParallelWithFallbackProbes(properties, proxyInfo, + httpsUrls[0], httpUrls[0], fallbackUrl); + } else if (mUseHttps) { + // Support result aggregation from multiple Urls. + result = sendMultiParallelHttpAndHttpsProbes(properties, proxyInfo, httpsUrls, + httpUrls); + } else { + result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP); + reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); + } + + long endTime = SystemClock.elapsedRealtime(); + + log("isCaptivePortal: isSuccessful()=" + result.isSuccessful() + + " isPortal()=" + result.isPortal() + + " RedirectUrl=" + result.redirectUrl + + " isPartialConnectivity()=" + result.isPartialConnectivity() + + " Time=" + (endTime - startTime) + "ms"); + + return result; + } + + /** + * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect. + * @return a CaptivePortalProbeResult inferred from the HTTP response. + */ + private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) { + // Pre-resolve the captive portal server host so we can log it. + // Only do this if HttpURLConnection is about to, to avoid any potentially + // unnecessary resolution. + final String host = (proxy != null) ? proxy.getHost() : url.getHost(); + // This method cannot safely report probe results because it might not be running on the + // state machine thread. Reporting results here would cause races and potentially send + // information to callers that does not make sense because the state machine has already + // changed state. + final InetAddress[] resolvedAddr = sendDnsProbe(host); + // The private IP logic only applies to captive portal detection (the HTTP probe), not + // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe. + if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP + && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) { + recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType), + 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */); + return CaptivePortalProbeResult.PRIVATE_IP; + } + return sendHttpProbe(url, probeType, null); + } + + /** Do a DNS lookup for the given server, or throw UnknownHostException after timeoutMs */ + @VisibleForTesting + protected InetAddress[] sendDnsProbeWithTimeout(String host, int timeoutMs) + throws UnknownHostException { + return DnsUtils.getAllByName(mDependencies.getDnsResolver(), mCleartextDnsNetwork, host, + TYPE_ADDRCONFIG, FLAG_EMPTY, timeoutMs, + str -> validationLog(ValidationProbeEvent.PROBE_DNS, host, str)); + } + + /** Do a DNS resolution of the given server. */ + private InetAddress[] sendDnsProbe(String host) { + if (TextUtils.isEmpty(host)) { + return null; + } + + final Stopwatch watch = new Stopwatch().start(); + int result; + InetAddress[] addresses; + try { + addresses = sendDnsProbeWithTimeout(host, getDnsProbeTimeout()); + result = ValidationProbeEvent.DNS_SUCCESS; + } catch (UnknownHostException e) { + addresses = null; + result = ValidationProbeEvent.DNS_FAILURE; + } + final long latency = watch.stop(); + recordProbeEventMetrics(ProbeType.PT_DNS, latency, + (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS : + ProbeResult.PR_FAILURE, null /* capportData */); + logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); + return addresses; + } + + /** + * Check if any of the provided IP addresses include a private IP. + * @return true if an IP address is private. + */ + private static boolean hasPrivateIpAddress(@Nullable InetAddress[] addresses) { + if (addresses == null) { + return false; + } + for (InetAddress address : addresses) { + if (address.isLinkLocalAddress() || address.isSiteLocalAddress() + || isIPv6ULA(address)) { + return true; + } + } + return false; + } + + /** + * Do a URL fetch on a known web server to see if we get the data we expect. + * @return a CaptivePortalProbeResult inferred from the HTTP response. + */ + @VisibleForTesting + protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType, + @Nullable CaptivePortalProbeSpec probeSpec) { + HttpURLConnection urlConnection = null; + int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; + String redirectUrl = null; + final Stopwatch probeTimer = new Stopwatch().start(); + final int oldTag = TrafficStats.getAndSetThreadStatsTag( + NetworkStackConstants.TAG_SYSTEM_PROBE); + try { + // Follow redirects for PAC probes as such probes verify connectivity by fetching the + // PAC proxy file, which may be configured behind a redirect. + final boolean followRedirect = probeType == ValidationProbeEvent.PROBE_PAC; + urlConnection = makeProbeConnection(url, followRedirect); + // cannot read request header after connection + String requestHeader = urlConnection.getRequestProperties().toString(); + + // Time how long it takes to get a response to our request + long requestTimestamp = SystemClock.elapsedRealtime(); + + httpResponseCode = urlConnection.getResponseCode(); + redirectUrl = urlConnection.getHeaderField("location"); + + // Time how long it takes to get a response to our request + long responseTimestamp = SystemClock.elapsedRealtime(); + + validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms" + + " ret=" + httpResponseCode + + " request=" + requestHeader + + " headers=" + urlConnection.getHeaderFields()); + // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive + // portal. The only example of this seen so far was a captive portal. For + // the time being go with prior behavior of assuming it's not a captive + // portal. If it is considered a captive portal, a different sign-in URL + // is needed (i.e. can't browse a 204). This could be the result of an HTTP + // proxy server. + if (httpResponseCode == 200) { + long contentLength = urlConnection.getContentLengthLong(); + if (probeType == ValidationProbeEvent.PROBE_PAC) { + validationLog( + probeType, url, "PAC fetch 200 response interpreted as 204 response."); + httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE; + } else if (contentLength == -1) { + // When no Content-length (default value == -1), attempt to read a byte + // from the response. Do not use available() as it is unreliable. + // See http://b/33498325. + if (urlConnection.getInputStream().read() == -1) { + validationLog(probeType, url, + "Empty 200 response interpreted as failed response."); + httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; + } + } else if (matchesHttpContentLength(contentLength)) { + final InputStream is = new BufferedInputStream(urlConnection.getInputStream()); + final String content = readAsString(is, (int) contentLength, + extractCharset(urlConnection.getContentType())); + if (matchesHttpContent(content, + R.string.config_network_validation_failed_content_regexp)) { + httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; + } else if (matchesHttpContent(content, + R.string.config_network_validation_success_content_regexp)) { + httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE; + } + + if (httpResponseCode != 200) { + validationLog(probeType, url, "200 response with Content-length =" + + contentLength + ", content matches custom regexp, interpreted" + + " as " + httpResponseCode + + " response."); + } + } else if (contentLength <= 4) { + // Consider 200 response with "Content-length <= 4" to not be a captive + // portal. There's no point in considering this a captive portal as the + // user cannot sign-in to an empty page. Probably the result of a broken + // transparent proxy. See http://b/9972012 and http://b/122999481. + validationLog(probeType, url, "200 response with Content-length <= 4" + + " interpreted as failed response."); + httpResponseCode = CaptivePortalProbeResult.FAILED_CODE; + } + } + } catch (IOException e) { + validationLog(probeType, url, "Probe failed with exception " + e); + if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) { + // TODO: Ping gateway and DNS server and log results. + } + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + TrafficStats.setThreadStatsTag(oldTag); + } + logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); + + final CaptivePortalProbeResult probeResult; + if (probeSpec == null) { + if (CaptivePortalProbeResult.isPortalCode(httpResponseCode) + && TextUtils.isEmpty(redirectUrl) + && ShimUtils.isAtLeastS()) { + // If a portal is a non-redirect portal (often portals that return HTTP 200 with a + // login page for all HTTP requests), report the probe URL as the login URL starting + // from S (b/172048052). This avoids breaking assumptions that + // [is a portal] is equivalent to [there is a login URL]. + redirectUrl = url.toString(); + } + probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl, + url.toString(), 1 << probeType); + } else { + probeResult = probeSpec.getResult(httpResponseCode, redirectUrl); + } + recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType), + probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult), + null /* capportData */); + return probeResult; + } + + @VisibleForTesting + boolean matchesHttpContent(final String content, @StringRes final int configResource) { + final String resString = getResStringConfig(mContext, configResource, ""); + try { + return content.matches(resString); + } catch (PatternSyntaxException e) { + Log.e(TAG, "Pattern syntax exception occurs when matching the resource=" + resString, + e); + return false; + } + } + + @VisibleForTesting + boolean matchesHttpContentLength(final long contentLength) { + // Consider that the Resources#getInteger() is returning an integer, so if the contentLength + // is lower or equal to 0 or higher than Integer.MAX_VALUE, then it's an invalid value. + if (contentLength <= 0) return false; + if (contentLength > Integer.MAX_VALUE) { + logw("matchesHttpContentLength : Get invalid contentLength = " + contentLength); + return false; + } + return (contentLength > getResIntConfig(mContext, + R.integer.config_min_matches_http_content_length, Integer.MAX_VALUE) + && + contentLength < getResIntConfig(mContext, + R.integer.config_max_matches_http_content_length, 0)); + } + + private HttpURLConnection makeProbeConnection(URL url, boolean followRedirects) + throws IOException { + final HttpURLConnection conn = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url); + conn.setInstanceFollowRedirects(followRedirects); + conn.setConnectTimeout(SOCKET_TIMEOUT_MS); + conn.setReadTimeout(SOCKET_TIMEOUT_MS); + conn.setRequestProperty("Connection", "close"); + conn.setUseCaches(false); + if (mCaptivePortalUserAgent != null) { + conn.setRequestProperty("User-Agent", mCaptivePortalUserAgent); + } + return conn; + } + + @VisibleForTesting + @NonNull + protected static String readAsString(InputStream is, int maxLength, Charset charset) + throws IOException { + final InputStreamReader reader = new InputStreamReader(is, charset); + final char[] buffer = new char[1000]; + final StringBuilder builder = new StringBuilder(); + int totalReadLength = 0; + while (totalReadLength < maxLength) { + final int availableLength = Math.min(maxLength - totalReadLength, buffer.length); + final int currentLength = reader.read(buffer, 0, availableLength); + if (currentLength < 0) break; // EOF + + totalReadLength += currentLength; + builder.append(buffer, 0, currentLength); + } + return builder.toString(); + } + + /** + * Attempt to extract the {@link Charset} of the response from its Content-Type header. + * + *

    If the {@link Charset} cannot be extracted, UTF-8 is returned by default. + */ + @VisibleForTesting + @NonNull + protected static Charset extractCharset(@Nullable String contentTypeHeader) { + if (contentTypeHeader == null) return StandardCharsets.UTF_8; + // See format in https://tools.ietf.org/html/rfc7231#section-3.1.1.1 + final Pattern charsetPattern = Pattern.compile("; *charset=\"?([^ ;\"]+)\"?", + Pattern.CASE_INSENSITIVE); + final Matcher matcher = charsetPattern.matcher(contentTypeHeader); + if (!matcher.find()) return StandardCharsets.UTF_8; + + try { + return Charset.forName(matcher.group(1)); + } catch (IllegalArgumentException e) { + return StandardCharsets.UTF_8; + } + } + + private class ProbeThread extends Thread { + private final CountDownLatch mLatch; + private final Probe mProbe; + + ProbeThread(CountDownLatch latch, ValidationProperties properties, ProxyInfo proxy, URL url, + int probeType, Uri captivePortalApiUrl) { + mLatch = latch; + mProbe = (probeType == ValidationProbeEvent.PROBE_HTTPS) + ? new HttpsProbe(properties, proxy, url, captivePortalApiUrl) + : new HttpProbe(properties, proxy, url, captivePortalApiUrl); + mResult = CaptivePortalProbeResult.failed(probeType); + mDependencies.onThreadCreated(this); + } + + private volatile CaptivePortalProbeResult mResult; + + public CaptivePortalProbeResult result() { + return mResult; + } + + @Override + public void run() { + mResult = mProbe.sendProbe(); + if (isConclusiveResult(mResult, mProbe.mCaptivePortalApiUrl)) { + // Stop waiting immediately if any probe is conclusive. + while (mLatch.getCount() > 0) { + mLatch.countDown(); + } + } + // Signal this probe has completed. + mLatch.countDown(); + } + } + + private abstract static class Probe { + protected final ValidationProperties mProperties; + protected final ProxyInfo mProxy; + protected final URL mUrl; + protected final Uri mCaptivePortalApiUrl; + + protected Probe(ValidationProperties properties, ProxyInfo proxy, URL url, + Uri captivePortalApiUrl) { + mProperties = properties; + mProxy = proxy; + mUrl = url; + mCaptivePortalApiUrl = captivePortalApiUrl; + } + // sendProbe() is synchronous and blocks until it has the result. + protected abstract CaptivePortalProbeResult sendProbe(); + } + + final class HttpsProbe extends Probe { + HttpsProbe(ValidationProperties properties, ProxyInfo proxy, URL url, + Uri captivePortalApiUrl) { + super(properties, proxy, url, captivePortalApiUrl); + } + + @Override + protected CaptivePortalProbeResult sendProbe() { + return sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTPS); + } + } + + final class HttpProbe extends Probe { + HttpProbe(ValidationProperties properties, ProxyInfo proxy, URL url, + Uri captivePortalApiUrl) { + super(properties, proxy, url, captivePortalApiUrl); + } + + private CaptivePortalDataShim sendCapportApiProbe() { + // TODO: consider adding metrics counters for each case returning null in this method + // (cases where the API is not implemented properly). + validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl); + + final String apiContent; + try { + final URL url = new URL(mCaptivePortalApiUrl.toString()); + // Protocol must be HTTPS + // (as per https://www.ietf.org/id/draft-ietf-capport-api-07.txt, #4). + // Only allow HTTP on localhost, for testing. + final boolean isTestLocalhostHttp = mProperties.mIsTestNetwork + && "localhost".equals(url.getHost()) && "http".equals(url.getProtocol()); + if (!"https".equals(url.getProtocol()) && !isTestLocalhostHttp) { + validationLog("Invalid captive portal API protocol: " + url.getProtocol()); + return null; + } + + final HttpURLConnection conn = makeProbeConnection( + url, true /* followRedirects */); + conn.setRequestProperty(ACCEPT_HEADER, CAPPORT_API_CONTENT_TYPE); + final int responseCode = conn.getResponseCode(); + if (responseCode != 200) { + validationLog("Non-200 API response code: " + conn.getResponseCode()); + return null; + } + final Charset charset = extractCharset(conn.getHeaderField(CONTENT_TYPE_HEADER)); + if (charset != StandardCharsets.UTF_8) { + validationLog("Invalid charset for capport API: " + charset); + return null; + } + + apiContent = readAsString(conn.getInputStream(), + CAPPORT_API_MAX_JSON_LENGTH, charset); + } catch (IOException e) { + validationLog("I/O error reading capport data: " + e.getMessage()); + return null; + } + + try { + final JSONObject info = new JSONObject(apiContent); + final CaptivePortalDataShim capportData = CaptivePortalDataShimImpl.fromJson(info); + if (capportData != null && capportData.isCaptive() + && capportData.getUserPortalUrl() == null) { + validationLog("Missing user-portal-url from capport response"); + return null; + } + return capportData; + } catch (JSONException e) { + validationLog("Could not parse capport API JSON: " + e.getMessage()); + return null; + } catch (UnsupportedApiLevelException e) { + // This should never happen because LinkProperties would not have a capport URL + // before R. + validationLog("Platform API too low to support capport API"); + return null; + } + } + + private CaptivePortalDataShim tryCapportApiProbe() { + if (mCaptivePortalApiUrl == null) return null; + final Stopwatch capportApiWatch = new Stopwatch().start(); + final CaptivePortalDataShim capportData = sendCapportApiProbe(); + recordProbeEventMetrics(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(), + capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS, + capportData); + return capportData; + } + + @Override + protected CaptivePortalProbeResult sendProbe() { + final CaptivePortalDataShim capportData = tryCapportApiProbe(); + if (capportData != null && capportData.isCaptive()) { + final String loginUrlString = capportData.getUserPortalUrl().toString(); + // Starting from R (where CaptivePortalData was introduced), the captive portal app + // delegates to NetworkMonitor for verifying when the network validates instead of + // probing the detectUrl. So pass the detectUrl to have the portal open on that, + // page; CaptivePortalLogin will not use it for probing. + return new CapportApiProbeResult( + CaptivePortalProbeResult.PORTAL_CODE, + loginUrlString /* redirectUrl */, + loginUrlString /* detectUrl */, + capportData, + 1 << ValidationProbeEvent.PROBE_HTTP); + } + + // If the API says it's not captive, still check for HTTP connectivity. This helps + // with partial connectivity detection, and a broken API saying that there is no + // redirect when there is one. + final CaptivePortalProbeResult res = + sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP); + return mCaptivePortalApiUrl == null ? res : new CapportApiProbeResult(res, capportData); + } + } + + private static boolean isConclusiveResult(@NonNull CaptivePortalProbeResult result, + @Nullable Uri captivePortalApiUrl) { + // isPortal() is not expected on the HTTPS probe, but treat the network as portal would make + // sense if the probe reports portal. In case the capport API is available, the API is + // authoritative on whether there is a portal, so the HTTPS probe is not enough to conclude + // there is connectivity, and a determination will be made once the capport API probe + // returns. Note that the API can only force the system to detect a portal even if the HTTPS + // probe succeeds. It cannot force the system to detect no portal if the HTTPS probe fails. + return result.isPortal() + || (result.isConcludedFromHttps() && result.isSuccessful() + && captivePortalApiUrl == null); + } + + private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes( + @NonNull ValidationProperties properties, @Nullable ProxyInfo proxy, + @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls) { + // If multiple URLs are required to ensure the correctness of validation, send parallel + // probes to explore the result in separate probe threads and aggregate those results into + // one as the final result for either HTTP or HTTPS. + + // Number of probes to wait for. + final int num = httpsUrls.length + httpUrls.length; + // Fixed pool to prevent configuring too many urls to exhaust system resource. + final ExecutorService executor = Executors.newFixedThreadPool( + Math.min(num, MAX_PROBE_THREAD_POOL_SIZE)); + mDependencies.onExecutorServiceCreated(executor); + final CompletionService ecs = + new ExecutorCompletionService(executor); + final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties); + final List> futures = new ArrayList<>(); + + try { + // Queue https and http probe. + + // Each of these HTTP probes will start with probing capport API if present. So if + // multiple HTTP URLs are configured, AP will send multiple identical accesses to the + // capport URL. Thus, send capport API probing with one of the HTTP probe is enough. + // Probe capport API with the first HTTP probe. + // TODO: Have the capport probe as a different probe for cleanliness. + final URL urlMaybeWithCapport = httpUrls[0]; + for (final URL url : httpUrls) { + futures.add(ecs.submit(() -> new HttpProbe(properties, proxy, url, + url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe())); + } + + for (final URL url : httpsUrls) { + futures.add(ecs.submit(() -> new HttpsProbe(properties, proxy, url, capportApiUrl) + .sendProbe())); + } + + final ArrayList completedProbes = new ArrayList<>(); + for (int i = 0; i < num; i++) { + completedProbes.add(ecs.take().get()); + final CaptivePortalProbeResult res = evaluateCapportResult( + completedProbes, httpsUrls.length, capportApiUrl != null /* hasCapport */); + if (res != null) { + reportProbeResult(res); + return res; + } + } + } catch (ExecutionException e) { + Log.e(TAG, "Error sending probes.", e); + } catch (InterruptedException e) { + // Ignore interrupted probe result because result is not important to conclude the + // result. + } finally { + // Interrupt ongoing probes since we have already gotten result from one of them. + futures.forEach(future -> future.cancel(true)); + executor.shutdownNow(); + } + + return CaptivePortalProbeResult.failed(ValidationProbeEvent.PROBE_HTTPS); + } + + @Nullable + private CaptivePortalProbeResult evaluateCapportResult( + List probes, int numHttps, boolean hasCapport) { + CaptivePortalProbeResult capportResult = null; + CaptivePortalProbeResult httpPortalResult = null; + int httpSuccesses = 0; + int httpsSuccesses = 0; + int httpsFailures = 0; + + for (CaptivePortalProbeResult probe : probes) { + if (probe instanceof CapportApiProbeResult) { + capportResult = probe; + } else if (probe.isConcludedFromHttps()) { + if (probe.isSuccessful()) httpsSuccesses++; + else httpsFailures++; + } else { // http probes + if (probe.isPortal()) { + // Unlike https probe, http probe may have redirect url information kept in the + // probe result. Thus, the result can not be newly created with response code + // only. If the captive portal behavior will be varied because of different + // probe URLs, this means that if the portal returns different redirect URLs for + // different probes and has a different behavior depending on the URL, then the + // behavior of the login page may differ depending on the order in which the + // probes terminate. However, NetworkMonitor does have to choose one of the + // redirect URLs and right now there is no clue at all which of the probe has + // the better redirect URL, so there is no telling which is best to use. + // Therefore the current code just uses whichever happens to be the last one to + // complete. + httpPortalResult = probe; + } else if (probe.isSuccessful()) { + httpSuccesses++; + } + } + } + // If there is Capport url configured but the result is not available yet, wait for it. + if (hasCapport && capportResult == null) return null; + // Capport API saying it's a portal is authoritative. + if (capportResult != null && capportResult.isPortal()) return capportResult; + // Any HTTP probes saying probe portal is conclusive. + if (httpPortalResult != null) return httpPortalResult; + // Any HTTPS probes works then the network validates. + if (httpsSuccesses > 0) { + return CaptivePortalProbeResult.success(1 << ValidationProbeEvent.PROBE_HTTPS); + } + // All HTTPS failed and at least one HTTP succeeded, then it's partial. + if (httpsFailures == numHttps && httpSuccesses > 0) { + return CaptivePortalProbeResult.PARTIAL; + } + // Otherwise, the result is unknown yet. + return null; + } + + private void reportProbeResult(@NonNull CaptivePortalProbeResult res) { + if (res instanceof CapportApiProbeResult) { + maybeReportCaptivePortalData(((CapportApiProbeResult) res).getCaptivePortalData()); + } + + // This is not a if-else case since partial connectivity will concluded from both HTTP and + // HTTPS probe. Both HTTP and HTTPS result should be reported. + if (res.isConcludedFromHttps()) { + reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, res); + } + + if (res.isConcludedFromHttp()) { + reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, res); + } + } + + private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes( + ValidationProperties properties, ProxyInfo proxy, URL httpsUrl, URL httpUrl, + URL fallbackUrl) { + // Number of probes to wait for. If a probe completes with a conclusive answer + // it shortcuts the latch immediately by forcing the count to 0. + final CountDownLatch latch = new CountDownLatch(2); + + final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties); + final ProbeThread httpsProbe = new ProbeThread(latch, properties, proxy, httpsUrl, + ValidationProbeEvent.PROBE_HTTPS, capportApiUrl); + final ProbeThread httpProbe = new ProbeThread(latch, properties, proxy, httpUrl, + ValidationProbeEvent.PROBE_HTTP, capportApiUrl); + + try { + httpsProbe.start(); + httpProbe.start(); + latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + validationLog("Error: probes wait interrupted!"); + return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); + } + + final CaptivePortalProbeResult httpsResult = httpsProbe.result(); + final CaptivePortalProbeResult httpResult = httpProbe.result(); + + // Look for a conclusive probe result first. + if (isConclusiveResult(httpResult, capportApiUrl)) { + reportProbeResult(httpProbe.result()); + return httpResult; + } + + if (isConclusiveResult(httpsResult, capportApiUrl)) { + reportProbeResult(httpsProbe.result()); + return httpsResult; + } + // Consider a DNS response with a private IP address on the HTTP probe as an indication that + // the network is not connected to the Internet, and have the whole evaluation fail in that + // case, instead of potentially detecting a captive portal. This logic only affects portal + // detection, not network validation. + // This only applies if the DNS probe completed within PROBE_TIMEOUT_MS, as the fallback + // probe should not be delayed by this check. + if (mPrivateIpNoInternetEnabled && (httpResult.isDnsPrivateIpResponse())) { + validationLog("DNS response to the URL is private IP"); + return CaptivePortalProbeResult.failed(1 << ValidationProbeEvent.PROBE_HTTP); + } + // If a fallback method exists, use it to retry portal detection. + // If we have new-style probe specs, use those. Otherwise, use the fallback URLs. + final CaptivePortalProbeSpec probeSpec = nextFallbackSpec(); + final URL fallback = (probeSpec != null) ? probeSpec.getUrl() : fallbackUrl; + CaptivePortalProbeResult fallbackProbeResult = null; + if (fallback != null) { + fallbackProbeResult = sendHttpProbe(fallback, PROBE_FALLBACK, probeSpec); + reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult); + if (fallbackProbeResult.isPortal()) { + return fallbackProbeResult; + } + } + // Otherwise wait until http and https probes completes and use their results. + try { + httpProbe.join(); + reportProbeResult(httpProbe.result()); + + if (httpProbe.result().isPortal()) { + return httpProbe.result(); + } + + httpsProbe.join(); + reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsProbe.result()); + + if (httpsProbe.result().isFailed() && httpProbe.result().isSuccessful()) { + return CaptivePortalProbeResult.PARTIAL; + } + return httpsProbe.result(); + } catch (InterruptedException e) { + validationLog("Error: http or https probe wait interrupted!"); + return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); + } + } + + @Nullable + private URL makeURL(@Nullable String url) { + return makeURL(url, mValidationLogs); + } + + @Nullable + private static URL makeURL(@Nullable String url, @NonNull SharedLog log) { + if (url != null) { + try { + return new URL(url); + } catch (MalformedURLException e) { + log.w("Bad URL: " + url); + } + } + return null; + } + + private void logNetworkEvent(int evtype) { + int[] transports = mNetworkCapabilities.getTransportTypes(); + mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype)); + } + + private int networkEventType(ValidationStage s, EvaluationResult r) { + if (s.mIsFirstValidation) { + if (r.mIsValidated) { + return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS; + } else { + return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND; + } + } else { + if (r.mIsValidated) { + return NetworkEvent.NETWORK_REVALIDATION_SUCCESS; + } else { + return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND; + } + } + } + + private void maybeLogEvaluationResult(int evtype) { + if (mEvaluationTimer.isRunning()) { + int[] transports = mNetworkCapabilities.getTransportTypes(); + mMetricsLog.log(mCleartextDnsNetwork, transports, + new NetworkEvent(evtype, mEvaluationTimer.stop() / 1000)); + mEvaluationTimer.reset(); + } + } + + private void logValidationProbe(long durationUs, int probeType, int probeResult) { + int[] transports = mNetworkCapabilities.getTransportTypes(); + boolean isFirstValidation = validationStage().mIsFirstValidation; + ValidationProbeEvent ev = new ValidationProbeEvent.Builder() + .setProbeType(probeType, isFirstValidation) + .setReturnCode(probeResult) + .setDurationMs(durationUs / 1000) + .build(); + mMetricsLog.log(mCleartextDnsNetwork, transports, ev); + } + + @VisibleForTesting + public static class Dependencies { + public Network getPrivateDnsBypassNetwork(Network network) { + return new OneAddressPerFamilyNetwork(network); + } + + public DnsResolver getDnsResolver() { + return DnsResolver.getInstance(); + } + + public Random getRandom() { + return new Random(); + } + + /** + * Get the value of a global integer setting. + * @param symbol Name of the setting + * @param defaultValue Value to return if the setting is not defined. + */ + public int getSetting(Context context, String symbol, int defaultValue) { + return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue); + } + + /** + * Get the value of a global String setting. + * @param symbol Name of the setting + * @param defaultValue Value to return if the setting is not defined. + */ + public String getSetting(Context context, String symbol, String defaultValue) { + final String value = Settings.Global.getString(context.getContentResolver(), symbol); + return value != null ? value : defaultValue; + } + + /** + * Look up the value of a property in DeviceConfig. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or has no non-null + * value. + * @return the corresponding value, or defaultValue if none exists. + */ + @Nullable + public String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name, + @Nullable String defaultValue) { + return DeviceConfigUtils.getDeviceConfigProperty(namespace, name, defaultValue); + } + + /** + * Look up the value of a property in DeviceConfig. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or has no non-null + * value. + * @return the corresponding value, or defaultValue if none exists. + */ + public int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name, + int defaultValue) { + return DeviceConfigUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue); + } + + /** + * Check whether or not one experimental feature in the connectivity namespace is + * enabled. + * @param name Flag name of the experiment in the connectivity namespace. + * @see DeviceConfigUtils#isNetworkStackFeatureEnabled(Context, String) + */ + public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { + return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name); + } + + /** + * Check whether one specific feature is not disabled. + * @param name Flag name of the experiment in the connectivity namespace. + * @see DeviceConfigUtils#isNetworkStackFeatureNotChickenedOut(Context, String) + */ + public boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name) { + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(context, name); + } + + /** + * Collect data stall detection level information for each transport type. Write metrics + * data to statsd pipeline. + * @param stats a {@link DataStallDetectionStats} that contains the detection level + * information. + * @param result the network reevaluation result. + */ + public void writeDataStallDetectionStats(@NonNull final DataStallDetectionStats stats, + @NonNull final CaptivePortalProbeResult result) { + DataStallStatsUtils.write(stats, result); + } + + /** + * Callback to be called when a probing thread instance is created. + * + * This method is designed for overriding in test classes to collect + * created threads and waits for the termination. + */ + public void onThreadCreated(@NonNull Thread thread) { + } + + /** + * Callback to be called when a ExecutorService instance is created. + * + * This method is designed for overriding in test classes to collect + * created threads and waits for the termination. + */ + public void onExecutorServiceCreated(@NonNull ExecutorService ecs) { + } + + public static final Dependencies DEFAULT = new Dependencies(); + } + + /** + * Methods in this class perform no locking because all accesses are performed on the state + * machine's thread. Need to consider the thread safety if it ever could be accessed outside the + * state machine. + */ + @VisibleForTesting + protected class DnsStallDetector { + private int mConsecutiveTimeoutCount = 0; + private int mSize; + final DnsResult[] mDnsEvents; + final RingBufferIndices mResultIndices; + + DnsStallDetector(int size) { + mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size); + mDnsEvents = new DnsResult[mSize]; + mResultIndices = new RingBufferIndices(mSize); + } + + @VisibleForTesting + protected void accumulateConsecutiveDnsTimeoutCount(int code) { + final DnsResult result = new DnsResult(code); + mDnsEvents[mResultIndices.add()] = result; + if (result.isTimeout()) { + mConsecutiveTimeoutCount++; + } else { + // Keep the event in mDnsEvents without clearing it so that there are logs to do the + // simulation and analysis. + mConsecutiveTimeoutCount = 0; + } + } + + private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) { + if (timeoutCountThreshold <= 0) { + Log.wtf(TAG, "Timeout count threshold should be larger than 0."); + return false; + } + + // Check if the consecutive timeout count reach the threshold or not. + if (mConsecutiveTimeoutCount < timeoutCountThreshold) { + return false; + } + + // Check if the target dns event index is valid or not. + final int firstConsecutiveTimeoutIndex = + mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold); + + // If the dns timeout events happened long time ago, the events are meaningless for + // data stall evaluation. Thus, check if the first consecutive timeout dns event + // considered in the evaluation happened in defined threshold time. + final long now = SystemClock.elapsedRealtime(); + final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp; + if (DDBG_STALL) { + Log.d(TAG, "DSD.isDataStallSuspected, first=" + + firstTimeoutTime + ", valid=" + validTime); + } + return (firstTimeoutTime < validTime); + } + + int getConsecutiveTimeoutCount() { + return mConsecutiveTimeoutCount; + } + } + + private static class DnsResult { + // TODO: Need to move the DNS return code definition to a specific class once unify DNS + // response code is done. + private static final int RETURN_CODE_DNS_TIMEOUT = 255; + + private final long mTimeStamp; + private final int mReturnCode; + + DnsResult(int code) { + mTimeStamp = SystemClock.elapsedRealtime(); + mReturnCode = code; + } + + private boolean isTimeout() { + return mReturnCode == RETURN_CODE_DNS_TIMEOUT; + } + } + + @VisibleForTesting + @Nullable + protected DnsStallDetector getDnsStallDetector() { + return mDnsStallDetector; + } + + @Nullable + private TcpSocketTracker getTcpSocketTracker() { + return mTcpTracker; + } + + private boolean dataStallEvaluateTypeEnabled(int type) { + return (mDataStallEvaluationType & type) != 0; + } + + @VisibleForTesting + protected long getLastProbeTime() { + return mLastProbeTime; + } + + @VisibleForTesting + public int getReevaluationDelayMs() { + return mReevaluateDelayMs; + } + + @VisibleForTesting + protected boolean isDataStall() { + if (!isDataStallDetectionRequired()) { + return false; + } + + int typeToCollect = 0; + final int notStall = -1; + final StringJoiner msg = (DBG || VDBG_STALL || DDBG_STALL) ? new StringJoiner(", ") : null; + // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the + // possible traffic cost in metered network. + final long currentTime = SystemClock.elapsedRealtime(); + if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) + && (currentTime - getLastProbeTime() < mDataStallMinEvaluateTime)) { + if (DDBG_STALL) { + Log.d(TAG, "isDataStall: false, currentTime=" + currentTime + + ", lastProbeTime=" + getLastProbeTime() + + ", MinEvaluateTime=" + mDataStallMinEvaluateTime); + } + return false; + } + // Check TCP signal. Suspect it may be a data stall if : + // 1. TCP connection fail rate(lost+retrans) is higher than threshold. + // 2. Accumulate enough packets count. + final TcpSocketTracker tst = getTcpSocketTracker(); + if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_TCP) && tst != null) { + if (tst.getLatestReceivedCount() > 0) { + typeToCollect = notStall; + } else if (tst.isDataStallSuspected()) { + typeToCollect |= DATA_STALL_EVALUATION_TYPE_TCP; + } + if (DBG || VDBG_STALL || DDBG_STALL) { + msg.add("tcp packets received=" + tst.getLatestReceivedCount()) + .add("latest tcp fail rate=" + tst.getLatestPacketFailPercentage()); + } + } + + // Check dns signal. Suspect it may be a data stall if both : + // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold. + // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms. + final DnsStallDetector dsd = getDnsStallDetector(); + if ((typeToCollect != notStall) && (dsd != null) + && dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) { + if (dsd.isDataStallSuspected( + mConsecutiveDnsTimeoutThreshold, mDataStallValidDnsTimeThreshold)) { + typeToCollect |= DATA_STALL_EVALUATION_TYPE_DNS; + logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND); + } + if (DBG || VDBG_STALL || DDBG_STALL) { + msg.add("consecutive dns timeout count=" + dsd.getConsecutiveTimeoutCount()); + } + } + + if (typeToCollect > 0) { + mDataStallTypeToCollect = typeToCollect; + final DataStallReportParcelable p = new DataStallReportParcelable(); + int detectionMethod = 0; + p.timestampMillis = SystemClock.elapsedRealtime(); + if (isDataStallTypeDetected(typeToCollect, DATA_STALL_EVALUATION_TYPE_DNS)) { + detectionMethod |= DETECTION_METHOD_DNS_EVENTS; + p.dnsConsecutiveTimeouts = mDnsStallDetector.getConsecutiveTimeoutCount(); + } + + if (isDataStallTypeDetected(typeToCollect, DATA_STALL_EVALUATION_TYPE_TCP)) { + detectionMethod |= DETECTION_METHOD_TCP_METRICS; + p.tcpPacketFailRate = tst.getLatestPacketFailPercentage(); + p.tcpMetricsCollectionPeriodMillis = getTcpPollingInterval(); + } + p.detectionMethod = detectionMethod; + notifyDataStallSuspected(p); + } + + // log only data stall suspected. + if ((DBG && (typeToCollect > 0)) || VDBG_STALL || DDBG_STALL) { + log("isDataStall: result=" + typeToCollect + ", " + msg); + } + + return typeToCollect > 0; + } + + private static boolean isDataStallTypeDetected(int typeToCollect, int evaluationType) { + return (typeToCollect & evaluationType) != 0; + } + // Class to keep state of evaluation results and probe results. + // + // The main purpose was to ensure NetworkMonitor can notify ConnectivityService of probe results + // as soon as they happen, without triggering any other changes. This requires keeping state on + // the most recent evaluation result. Calling noteProbeResult will ensure that the results + // reported to ConnectivityService contain the previous evaluation result, and thus won't + // trigger a validation or partial connectivity state change. + // + // Note that this class is not currently being used for this purpose. The reason is that some + // of the system behaviour triggered by reporting network validation - notably, NetworkAgent + // behaviour - depends not only on the value passed by notifyNetworkTested, but also on the + // fact that notifyNetworkTested was called. For example, telephony triggers network recovery + // any time it is told that validation failed, i.e., if the result does not contain + // NETWORK_VALIDATION_RESULT_VALID. But with this scheme, the first two or three validation + // reports are all failures, because they are "HTTP succeeded but validation not yet passed", + // "HTTP and HTTPS succeeded but validation not yet passed", etc. + // TODO : rename EvaluationState to not contain "State" in the name, as it makes this class + // sound like one of the states of the state machine, which it's not. + @VisibleForTesting + protected class EvaluationState { + // The latest validation result for this network. This is a bitmask of + // INetworkMonitor.NETWORK_VALIDATION_RESULT_* constants. + private int mEvaluationResult = NETWORK_VALIDATION_RESULT_INVALID; + + + // Set when the captive portal app said this network should be used as is as a result + // of user interaction. The valid bit represents the user's decision to override automatic + // determination of whether the network has access to Internet, so in this case the + // network is always reported as validated. + // TODO : Make ConnectivityService aware of this state, so that it can use the network as + // the default without setting the VALIDATED bit, as it's a bit of a lie. This can't be + // done on Android <= R where CS can't be updated, but it is doable on S+. + private boolean mCaptivePortalWantedAsIs = false; + // Indicates which probes have succeeded since clearProbeResults was called. + // This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants. + private int mProbeResults = 0; + // A bitmask to record which probes are completed. + private int mProbeCompleted = 0; + + protected void clearProbeResults() { + mProbeResults = 0; + mProbeCompleted = 0; + } + + private void maybeNotifyProbeResults(@NonNull final Runnable modif) { + final int oldCompleted = mProbeCompleted; + final int oldResults = mProbeResults; + modif.run(); + if (oldCompleted != mProbeCompleted || oldResults != mProbeResults) { + notifyProbeStatusChanged(mProbeCompleted, mProbeResults); + } + } + + protected void removeProbeResult(final int probeResult) { + maybeNotifyProbeResults(() -> { + mProbeCompleted &= ~probeResult; + mProbeResults &= ~probeResult; + }); + } + + protected void noteProbeResult(final int probeResult, final boolean succeeded) { + maybeNotifyProbeResults(() -> { + mProbeCompleted |= probeResult; + if (succeeded) { + mProbeResults |= probeResult; + } else { + mProbeResults &= ~probeResult; + } + }); + } + + protected void setCaptivePortalWantedAsIs() { + mCaptivePortalWantedAsIs = true; + } + + protected boolean isCaptivePortalWantedAsIs() { + return mCaptivePortalWantedAsIs; + } + + protected void reportEvaluationResult(int result, @Nullable String redirectUrl) { + if (mCaptivePortalWantedAsIs) { + result = NETWORK_VALIDATION_RESULT_VALID; + } else if (!isValidationRequired() && mProbeCompleted == 0 && mCallbackVersion >= 11) { + // If validation is not required AND no probes were attempted, the validation was + // skipped. Report this to ConnectivityService for ConnectivityDiagnostics, but only + // if the platform has callback version 11+, as ConnectivityService must also know + // how to understand this bit. + result |= NETWORK_VALIDATION_RESULT_SKIPPED; + } + + mEvaluationResult = result; + final NetworkTestResultParcelable p = new NetworkTestResultParcelable(); + p.result = result; + p.probesSucceeded = mProbeResults; + p.probesAttempted = mProbeCompleted; + p.redirectUrl = redirectUrl; + p.timestampMillis = SystemClock.elapsedRealtime(); + notifyNetworkTested(p); + recordValidationResult(result, redirectUrl); + } + + @VisibleForTesting + protected int getEvaluationResult() { + return mEvaluationResult; + } + + @VisibleForTesting + protected int getProbeResults() { + return mProbeResults; + } + + @VisibleForTesting + protected int getProbeCompletedResult() { + return mProbeCompleted; + } + } + + @VisibleForTesting + protected EvaluationState getEvaluationState() { + return mEvaluationState; + } + + private void maybeDisableHttpsProbing(boolean acceptPartial) { + mAcceptPartialConnectivity = acceptPartial; + // Ignore https probe in next validation if user accept partial connectivity on a partial + // connectivity network. + if (((mEvaluationState.getEvaluationResult() & NETWORK_VALIDATION_RESULT_PARTIAL) != 0) + && mAcceptPartialConnectivity) { + mUseHttps = false; + } + } + + // Report HTTP, HTTP or FALLBACK probe result. + @VisibleForTesting + protected void reportHttpProbeResult(int probeResult, + @NonNull final CaptivePortalProbeResult result) { + boolean succeeded = result.isSuccessful(); + // The success of a HTTP probe does not tell us whether the DNS probe succeeded. + // The DNS and HTTP probes run one after the other in sendDnsAndHttpProbes, and that + // method cannot report the result of the DNS probe because that it could be running + // on a different thread which is racing with the main state machine thread. So, if + // an HTTP or HTTPS probe succeeded, assume that the DNS probe succeeded. But if an + // HTTP or HTTPS probe failed, don't assume that DNS is not working. + // TODO: fix this. + if (succeeded) { + probeResult |= NETWORK_VALIDATION_PROBE_DNS; + } + mEvaluationState.noteProbeResult(probeResult, succeeded); + } + + private void maybeReportCaptivePortalData(@Nullable CaptivePortalDataShim data) { + // Do not clear data even if it is null: access points should not stop serving the API, so + // if the API disappears this is treated as a temporary failure, and previous data should + // remain valid. + if (data == null) return; + try { + data.notifyChanged(mCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error notifying ConnectivityService of new capport data", e); + } + } + + /** + * Interface for logging dns results. + */ + public interface DnsLogFunc { + /** + * Log function. + */ + void log(String s); + } + + @Nullable + private static TcpSocketTracker getTcpSocketTrackerOrNull(Context context, Network network, + Dependencies deps) { + return (getIsCaptivePortalCheckEnabled(context, deps) + && (deps.getDeviceConfigPropertyInt( + NAMESPACE_CONNECTIVITY, + CONFIG_DATA_STALL_EVALUATION_TYPE, + DEFAULT_DATA_STALL_EVALUATION_TYPES) + & DATA_STALL_EVALUATION_TYPE_TCP) != 0) + ? new TcpSocketTracker(new TcpSocketTracker.Dependencies(context), network) + : null; + } + + @Nullable + private DnsStallDetector initDnsStallDetectorIfRequired(boolean isCaptivePortalCheckEnabled, + int type, int threshold) { + return (isCaptivePortalCheckEnabled && (type & DATA_STALL_EVALUATION_TYPE_DNS) != 0) + ? new DnsStallDetector(threshold) : null; + } + + private static Uri getCaptivePortalApiUrl(LinkProperties lp) { + return NetworkInformationShimImpl.newInstance().getCaptivePortalApiUrl(lp); + } + + /** + * Check if the network is captive with terms and conditions page + * @return true if network is captive with T&C page, false otherwise + */ + private boolean isTermsAndConditionsCaptive(CaptivePortalDataShim captivePortalDataShim) { + return captivePortalDataShim != null + && captivePortalDataShim.getUserPortalUrl() != null + && !TextUtils.isEmpty(captivePortalDataShim.getUserPortalUrl().toString()) + && captivePortalDataShim.isCaptive() + && captivePortalDataShim.getUserPortalUrlSource() + == ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT; + } + + private boolean checkAndRenewResourceConfig() { + boolean reevaluationNeeded = false; + + final Context customizedContext = getCustomizedContextOrDefault(); + final URL[] captivePortalHttpsUrls = makeCaptivePortalHttpsUrls(customizedContext); + if (!Arrays.equals(mCaptivePortalHttpsUrls, captivePortalHttpsUrls)) { + mCaptivePortalHttpsUrls = captivePortalHttpsUrls; + reevaluationNeeded = true; + log("checkAndRenewResourceConfig: update captive portal https urls to " + + Arrays.toString(mCaptivePortalHttpsUrls)); + } + + final URL[] captivePortalHttpUrls = makeCaptivePortalHttpUrls(customizedContext); + if (!Arrays.equals(mCaptivePortalHttpUrls, captivePortalHttpUrls)) { + mCaptivePortalHttpUrls = captivePortalHttpUrls; + reevaluationNeeded = true; + log("checkAndRenewResourceConfig: update captive portal http urls to " + + Arrays.toString(mCaptivePortalHttpUrls)); + } + + final URL[] captivePortalFallbackUrls = makeCaptivePortalFallbackUrls(customizedContext); + if (!Arrays.equals(mCaptivePortalFallbackUrls, captivePortalFallbackUrls)) { + mCaptivePortalFallbackUrls = captivePortalFallbackUrls; + // Reset the index since the array is changed. + mNextFallbackUrlIndex = 0; + reevaluationNeeded = true; + log("checkAndRenewResourceConfig: update captive portal fallback urls to" + + Arrays.toString(mCaptivePortalFallbackUrls)); + } + + return reevaluationNeeded; + } +} diff --git a/aosp/packages/modules/Wifi/service/Android.bp b/aosp/packages/modules/Wifi/service/Android.bp new file mode 100644 index 000000000..220d9fb74 --- /dev/null +++ b/aosp/packages/modules/Wifi/service/Android.bp @@ -0,0 +1,182 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package { + default_team: "trendy_team_fwk_wifi_hal", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_defaults { + name: "wifi-service-common", + defaults: ["wifi-module-sdk-version-defaults"], + errorprone: { + javacflags: ["-Xep:CheckReturnValue:ERROR"], + }, +} + +filegroup { + name: "wifi-service-srcs", + srcs: [ + "java/**/*.java", + "java/**/*.logtags", + ":framework-wifi-service-shared-srcs", + ":net-utils-wifi-service-common-srcs", + ":statslog-wifi-java-gen", + ":coex-table-parser", + ], +} + +// pre-jarjar version of wifi-service that builds against pre-jarjar version of framework-wifi +java_library { + name: "wifi-service-pre-jarjar", + installable: false, + defaults: ["wifi-service-common"], + srcs: [":wifi-service-srcs"], + + sdk_version: "system_server_current", + lint: { + baseline_filename: "lint-baseline.xml", + }, + libs: [ + "androidx.annotation_annotation", + "error_prone_annotations", + "jsr305", + "framework-annotations-lib", + // load the resources from the resources APK. + "ServiceWifiResources", + // need pre-jarjar symbols so that wifi-service can reference the original class names at + // compile time + "framework-wifi-pre-jarjar", + "framework-bluetooth", + "framework-configinfrastructure", + "framework-connectivity.stubs.module_lib", + "framework-connectivity-t.stubs.module_lib", + "framework-location.stubs.module_lib", + "framework-statsd.stubs.module_lib", + "framework-tethering.stubs.module_lib", + "unsupportedappusage", + "app-compat-annotations", + "auto_value_annotations", + ], + + plugins: [ + "auto_value_plugin", + ], + + static_libs: [ + // Types-only package shared across the HALs + "android.hardware.wifi.common-V1-java", + // AIDL vendor hal implementation + "android.hardware.wifi-V2-java", + // HIDL vendor hal implementation + "android.hardware.wifi-V1.0-java", + "android.hardware.wifi-V1.1-java", + "android.hardware.wifi-V1.2-java", + "android.hardware.wifi-V1.3-java", + "android.hardware.wifi-V1.4-java", + "android.hardware.wifi-V1.5-java", + "android.hardware.wifi-V1.6-java", + // AIDL hostapd implementation + "android.hardware.wifi.hostapd-V2-java", + // HIDL hostapd implementation + "android.hardware.wifi.hostapd-V1.0-java", + "android.hardware.wifi.hostapd-V1.1-java", + "android.hardware.wifi.hostapd-V1.2-java", + "android.hardware.wifi.hostapd-V1.3-java", + // AIDL supplicant implementation + "android.hardware.wifi.supplicant-V3-java", + // HIDL supplicant implementation + "android.hardware.wifi.supplicant-V1.0-java", + "android.hardware.wifi.supplicant-V1.1-java", + "android.hardware.wifi.supplicant-V1.2-java", + "android.hardware.wifi.supplicant-V1.3-java", + "android.hardware.wifi.supplicant-V1.4-java", + "android.hidl.manager-V1.2-java", + "bouncycastle-unbundled", + "ksoap2", + // Note: libprotobuf-java-lite uses a few core platform APIs which + // does show up as @hide API usage. But, this can be safely ignored + // since the library uses reflection to ensure that the OS does provide + // the necessary core platform APIs. + "libprotobuf-java-lite", + "libnanohttpd", + "modules-utils-backgroundthread", + "modules-utils-fastxmlserializer", + "modules-utils-locallog", + "netd-client", + "networkstack-client", + "net-utils-services-common", + "service-entitlement", + "wifi-lite-protos", + "wifi-nano-protos", + "android.net.wifi.flags-aconfig-java", + ], + apex_available: ["com.android.wifi"], +} + +// wifi-service static library +// ============================================================ +java_library { + name: "service-wifi", + defaults: [ + "wifi-service-common", + "standalone-system-server-module-optimize-defaults", + ], + installable: true, + static_libs: ["wifi-service-pre-jarjar"], + + // need to include `libs` so that Soong doesn't complain about missing classes after jarjaring + libs: [ + "framework-wifi.impl", + "auto_value_annotations", + ], + + plugins: [ + "auto_value_plugin", + ], + + sdk_version: "system_server_current", + + jarjar_rules: ":wifi-jarjar-rules", + + visibility: [ + "//frameworks/opt/net/wifi/service/apex", + "//frameworks/opt/net/wifi/tests/wifitests/apex", + "//packages/modules/Wifi/apex", + "//packages/modules/Wifi/service/tests/wifitests/apex", + ], + apex_available: [ + "com.android.wifi", + "test_com.android.wifi", + ], +} + +// Statsd auto-generated code +// ============================================================ +genrule { + name: "statslog-wifi-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module wifi " + + " --javaPackage com.android.server.wifi.proto --javaClass WifiStatsLog" + + " --minApiLevel 30", + out: ["com/android/server/wifi/proto/WifiStatsLog.java"], +} + +// Prebuilt for the wifi.rc file. +prebuilt_etc { + name: "wifi.rc", + src: "wifi.rc", + sub_dir: "init", +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/AvailableNetworkNotifier.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/AvailableNetworkNotifier.java new file mode 100644 index 000000000..03d6cf567 --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/AvailableNetworkNotifier.java @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK; +import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK; +import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE; +import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_USER_DISMISSED_NOTIFICATION; +import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.AVAILABLE_NETWORK_NOTIFIER_TAG; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.wifi.IActionListener; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiContext; +import android.net.wifi.util.ScanResultUtil; +import android.os.Handler; +import android.os.Looper; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; +import com.android.server.wifi.proto.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount; +import com.android.server.wifi.util.ActionListenerWrapper; +import com.android.server.wifi.util.WifiPermissionsUtil; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Set; + +/** + * Base class for all network notifiers (e.g. OpenNetworkNotifier). + * + * NOTE: These API's are not thread safe and should only be used from WifiCoreThread. + */ +public class AvailableNetworkNotifier { + + /** Time in milliseconds to display the Connecting notification. */ + private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000; + + /** Time in milliseconds to display the Connected notification. */ + private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000; + + /** Time in milliseconds to display the Failed To Connect notification. */ + private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000; + + /** The state of the notification */ + @IntDef({ + STATE_NO_NOTIFICATION, + STATE_SHOWING_RECOMMENDATION_NOTIFICATION, + STATE_CONNECTING_IN_NOTIFICATION, + STATE_CONNECTED_NOTIFICATION, + STATE_CONNECT_FAILED_NOTIFICATION + }) + @Retention(RetentionPolicy.SOURCE) + private @interface State {} + + /** No recommendation is made and no notifications are shown. */ + private static final int STATE_NO_NOTIFICATION = 0; + /** The initial notification recommending a network to connect to is shown. */ + @VisibleForTesting + static final int STATE_SHOWING_RECOMMENDATION_NOTIFICATION = 1; + /** The notification of status of connecting to the recommended network is shown. */ + private static final int STATE_CONNECTING_IN_NOTIFICATION = 2; + /** The notification that the connection to the recommended network was successful is shown. */ + private static final int STATE_CONNECTED_NOTIFICATION = 3; + /** The notification to show that connection to the recommended network failed is shown. */ + private static final int STATE_CONNECT_FAILED_NOTIFICATION = 4; + + /** Current state of the notification. */ + @VisibleForTesting + @State int mState = STATE_NO_NOTIFICATION; + + /** + * The {@link Clock#getWallClockMillis()} must be at least this value for us + * to show the notification again. + */ + private long mNotificationRepeatTime; + /** + * When a notification is shown, we wait this amount before possibly showing it again. + */ + private final long mNotificationRepeatDelay; + /** Default repeat delay in seconds. */ + @VisibleForTesting + static final int DEFAULT_REPEAT_DELAY_SEC = 900; + + /** Whether the user has set the setting to show the 'available networks' notification. */ + private boolean mSettingEnabled; + /** Whether the screen is on or not. */ + private boolean mScreenOn; + + /** List of SSIDs blocklisted from recommendation. */ + private final Set mBlocklistedSsids = new ArraySet<>(); + + private final WifiContext mContext; + private final Handler mHandler; + private final FrameworkFacade mFrameworkFacade; + private final WifiMetrics mWifiMetrics; + private final Clock mClock; + private final WifiConfigManager mConfigManager; + private final ConnectHelper mConnectHelper; + private final ConnectToNetworkNotificationBuilder mNotificationBuilder; + private final MakeBeforeBreakManager mMakeBeforeBreakManager; + private final WifiNotificationManager mWifiNotificationManager; + private final WifiPermissionsUtil mWifiPermissionsUtil; + + @VisibleForTesting + ScanResult mRecommendedNetwork; + + /** Tag used for logs and metrics */ + private final String mTag; + /** Identifier of the {@link SsidSetStoreData}. */ + private final String mStoreDataIdentifier; + /** Identifier for the settings toggle, used for registering ContentObserver */ + private final String mToggleSettingsName; + + /** System wide identifier for notification in Notification Manager */ + private final int mSystemMessageNotificationId; + + /** + * The nominator id for this class, from + * {@link com.android.server.wifi.proto.nano.WifiMetricsProto.ConnectionEvent. + * ConnectionNominator} + */ + private final int mNominatorId; + + public AvailableNetworkNotifier( + String tag, + String storeDataIdentifier, + String toggleSettingsName, + int notificationIdentifier, + int nominatorId, + WifiContext context, + Looper looper, + FrameworkFacade framework, + Clock clock, + WifiMetrics wifiMetrics, + WifiConfigManager wifiConfigManager, + WifiConfigStore wifiConfigStore, + ConnectHelper connectHelper, + ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder, + MakeBeforeBreakManager makeBeforeBreakManager, + WifiNotificationManager wifiNotificationManager, + WifiPermissionsUtil wifiPermissionsUtil) { + mTag = tag; + mStoreDataIdentifier = storeDataIdentifier; + mToggleSettingsName = toggleSettingsName; + mSystemMessageNotificationId = notificationIdentifier; + mNominatorId = nominatorId; + mContext = context; + mHandler = new Handler(looper); + mFrameworkFacade = framework; + mWifiMetrics = wifiMetrics; + mClock = clock; + mConfigManager = wifiConfigManager; + mConnectHelper = connectHelper; + mNotificationBuilder = connectToNetworkNotificationBuilder; + mMakeBeforeBreakManager = makeBeforeBreakManager; + mWifiNotificationManager = wifiNotificationManager; + mWifiPermissionsUtil = wifiPermissionsUtil; + mScreenOn = false; + wifiConfigStore.registerStoreData(new SsidSetStoreData(mStoreDataIdentifier, + new AvailableNetworkNotifierStoreData())); + + // Setting is in seconds + mNotificationRepeatDelay = mFrameworkFacade.getIntegerSetting(context, + Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, + DEFAULT_REPEAT_DELAY_SEC) * 1000L; + NotificationEnabledSettingObserver settingObserver = new NotificationEnabledSettingObserver( + mHandler); + settingObserver.register(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_USER_DISMISSED_NOTIFICATION); + filter.addAction(ACTION_CONNECT_TO_NETWORK); + filter.addAction(ACTION_PICK_WIFI_NETWORK); + filter.addAction(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE); + mContext.registerReceiver( + mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler); + } + + private final BroadcastReceiver mBroadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (!TextUtils.equals(mTag, + intent.getStringExtra(AVAILABLE_NETWORK_NOTIFIER_TAG))) { + return; + } + switch (intent.getAction()) { + case ACTION_USER_DISMISSED_NOTIFICATION: + handleUserDismissedAction(); + break; + case ACTION_CONNECT_TO_NETWORK: + handleConnectToNetworkAction(); + break; + case ACTION_PICK_WIFI_NETWORK: + handleSeeAllNetworksAction(); + break; + case ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE: + handlePickWifiNetworkAfterConnectFailure(); + break; + default: + Log.e(mTag, "Unknown action " + intent.getAction()); + } + } + }; + + private final class ConnectActionListener extends IActionListener.Stub { + @Override + public void onSuccess() { + // Success here means that an attempt to connect to the network has been initiated. + // Successful connection updates are received via the + // WifiConnectivityManager#handleConnectionStateChanged() callback. + } + + @Override + public void onFailure(int reason) { + handleConnectionAttemptFailedToSend(); + } + } + + /** + * Clears the pending notification. This is called by {@link WifiConnectivityManager} on stop. + * + * @param resetRepeatTime resets the time delay for repeated notification if true. + */ + public void clearPendingNotification(boolean resetRepeatTime) { + if (resetRepeatTime) { + mNotificationRepeatTime = 0; + } + + if (mState != STATE_NO_NOTIFICATION) { + mWifiNotificationManager.cancel(mSystemMessageNotificationId); + Log.i(mTag, "notification canceled"); + + if (mRecommendedNetwork != null) { + Log.d(mTag, "Notification with state=" + + mState + + " was cleared for recommended network: " + + "\"" + mRecommendedNetwork.SSID + "\""); + } + mState = STATE_NO_NOTIFICATION; + mRecommendedNetwork = null; + } + } + + public boolean isSettingEnabled() { + return mSettingEnabled; + } + + private boolean isControllerEnabled() { + UserManager userManager = mContext.getSystemService(UserManager.class); + UserHandle currentUser = UserHandle.of(mWifiPermissionsUtil.getCurrentUser()); + return mSettingEnabled + && !userManager.hasUserRestrictionForUser( + UserManager.DISALLOW_CONFIG_WIFI, currentUser) + && !(SdkLevel.isAtLeastT() && userManager.hasUserRestrictionForUser( + UserManager.DISALLOW_ADD_WIFI_CONFIG, currentUser)); + } + + /** + * If there are available networks, attempt to post a network notification. + * + * @param availableNetworks Available networks to choose from and possibly show notification + */ + public void handleScanResults(@NonNull List availableNetworks) { + if (!isControllerEnabled()) { + clearPendingNotification(true /* resetRepeatTime */); + return; + } + if (availableNetworks.isEmpty() && mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) { + clearPendingNotification(false /* resetRepeatTime */); + return; + } + + // Not enough time has passed to show a recommendation notification again + if (mState == STATE_NO_NOTIFICATION + && mClock.getWallClockMillis() < mNotificationRepeatTime) { + return; + } + + // Do nothing when the screen is off and no notification is showing. + if (mState == STATE_NO_NOTIFICATION && !mScreenOn) { + return; + } + + // Only show a new or update an existing recommendation notification. + if (mState == STATE_NO_NOTIFICATION + || mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) { + ScanResult recommendation = + recommendNetwork(availableNetworks); + + if (recommendation != null) { + postInitialNotification(recommendation); + } else { + clearPendingNotification(false /* resetRepeatTime */); + } + } + } + + /** + * Recommends a network to connect to from a list of available networks, while ignoring the + * SSIDs in the blocklist. + * + * @param networks List of networks to select from + */ + public ScanResult recommendNetwork(@NonNull List networks) { + ScanResult result = null; + int highestRssi = Integer.MIN_VALUE; + for (ScanDetail scanDetail : networks) { + ScanResult scanResult = scanDetail.getScanResult(); + + if (scanResult.level > highestRssi) { + result = scanResult; + highestRssi = scanResult.level; + } + } + + if (result != null && mBlocklistedSsids.contains(result.SSID)) { + result = null; + } + return result; + } + + /** Handles screen state changes. */ + public void handleScreenStateChanged(boolean screenOn) { + mScreenOn = screenOn; + } + + /** + * Called by {@link WifiConnectivityManager} when Wi-Fi is connected. If the notification + * was in the connecting state, update the notification to show that it has connected to the + * recommended network. + * + * @param ssid The connected network's ssid + */ + public void handleWifiConnected(String ssid) { + removeNetworkFromBlocklist(ssid); + if (mState != STATE_CONNECTING_IN_NOTIFICATION) { + clearPendingNotification(true /* resetRepeatTime */); + return; + } + + postNotification(mNotificationBuilder.createNetworkConnectedNotification(mTag, + mRecommendedNetwork)); + + Log.d(mTag, "User connected to recommended network: " + + "\"" + mRecommendedNetwork.SSID + "\""); + mWifiMetrics.incrementConnectToNetworkNotification(mTag, + ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK); + mState = STATE_CONNECTED_NOTIFICATION; + mHandler.postDelayed( + () -> { + if (mState == STATE_CONNECTED_NOTIFICATION) { + clearPendingNotification(true /* resetRepeatTime */); + } + }, + TIME_TO_SHOW_CONNECTED_MILLIS); + } + + /** + * Handles when a Wi-Fi connection attempt failed. + */ + public void handleConnectionFailure() { + if (mState != STATE_CONNECTING_IN_NOTIFICATION) { + return; + } + postNotification(mNotificationBuilder.createNetworkFailedNotification(mTag)); + + Log.d(mTag, "User failed to connect to recommended network: " + + "\"" + mRecommendedNetwork.SSID + "\""); + mWifiMetrics.incrementConnectToNetworkNotification(mTag, + ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT); + mState = STATE_CONNECT_FAILED_NOTIFICATION; + mHandler.postDelayed( + () -> { + if (mState == STATE_CONNECT_FAILED_NOTIFICATION) { + clearPendingNotification(false /* resetRepeatTime */); + } + }, + TIME_TO_SHOW_FAILED_MILLIS); + } + + private void postInitialNotification(ScanResult recommendedNetwork) { + if (mRecommendedNetwork != null + && TextUtils.equals(mRecommendedNetwork.SSID, recommendedNetwork.SSID)) { + return; + } + + postNotification(mNotificationBuilder.createConnectToAvailableNetworkNotification(mTag, + recommendedNetwork)); + + if (mState == STATE_NO_NOTIFICATION) { + mWifiMetrics.incrementConnectToNetworkNotification(mTag, + ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK); + } else { + mWifiMetrics.incrementNumNetworkRecommendationUpdates(mTag); + } + mState = STATE_SHOWING_RECOMMENDATION_NOTIFICATION; + mRecommendedNetwork = recommendedNetwork; + mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay; + } + + private void postNotification(Notification notification) { + mWifiNotificationManager.notify(mSystemMessageNotificationId, notification); + Log.i(mTag, "notification send"); + } + + private void handleConnectToNetworkAction() { + mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState, + ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK); + if (mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) { + return; + } + postNotification(mNotificationBuilder.createNetworkConnectingNotification(mTag, + mRecommendedNetwork)); + mWifiMetrics.incrementConnectToNetworkNotification(mTag, + ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK); + + Log.d(mTag, + "User initiated connection to recommended network: " + + "\"" + mRecommendedNetwork.SSID + "\""); + WifiConfiguration network = createRecommendedNetworkConfig(mRecommendedNetwork); + if (null == network) { + Log.e(mTag, "Cannot create the network from the scan result."); + return; + } + + NetworkUpdateResult result = mConfigManager.addOrUpdateNetwork(network, Process.WIFI_UID); + if (result.isSuccess()) { + mWifiMetrics.setNominatorForNetwork(result.getNetworkId(), mNominatorId); + ConnectActionListener listener = new ConnectActionListener(); + mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() -> + mConnectHelper.connectToNetwork( + // only keep netId, discard other fields + new NetworkUpdateResult(result.getNetworkId()), + new ActionListenerWrapper(listener), + Process.SYSTEM_UID, mContext.getOpPackageName(), null)); + addNetworkToBlocklist(mRecommendedNetwork.SSID); + } + + mState = STATE_CONNECTING_IN_NOTIFICATION; + mHandler.postDelayed( + () -> { + if (mState == STATE_CONNECTING_IN_NOTIFICATION) { + handleConnectionFailure(); + } + }, + TIME_TO_SHOW_CONNECTING_MILLIS); + } + + private void addNetworkToBlocklist(String ssid) { + mBlocklistedSsids.add(ssid); + mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlocklistedSsids.size()); + mConfigManager.saveToStore(false /* forceWrite */); + Log.d(mTag, "Network is added to the network notification blocklist: " + + "\"" + ssid + "\""); + } + + private void removeNetworkFromBlocklist(String ssid) { + if (ssid == null) { + return; + } + if (!mBlocklistedSsids.remove(ssid)) { + return; + } + mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlocklistedSsids.size()); + mConfigManager.saveToStore(false /* forceWrite */); + Log.d(mTag, "Network is removed from the network notification blocklist: " + + "\"" + ssid + "\""); + } + + @Nullable WifiConfiguration createRecommendedNetworkConfig(ScanResult recommendedNetwork) { + return ScanResultUtil.createNetworkFromScanResult(recommendedNetwork); + } + + private void handleSeeAllNetworksAction() { + mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState, + ConnectToNetworkNotificationAndActionCount.ACTION_PICK_WIFI_NETWORK); + startWifiSettings(); + } + + private void startWifiSettings() { + // Close notification drawer before opening the picker. + mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + mContext.startActivityAsUser( + new Intent(Settings.ACTION_WIFI_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), + UserHandle.CURRENT); + clearPendingNotification(false /* resetRepeatTime */); + } + + private void handleConnectionAttemptFailedToSend() { + handleConnectionFailure(); + mWifiMetrics.incrementNumNetworkConnectMessageFailedToSend(mTag); + } + + private void handlePickWifiNetworkAfterConnectFailure() { + mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState, + ConnectToNetworkNotificationAndActionCount + .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE); + startWifiSettings(); + } + + private void handleUserDismissedAction() { + Log.d(mTag, "User dismissed notification with state=" + mState); + mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState, + ConnectToNetworkNotificationAndActionCount.ACTION_USER_DISMISSED_NOTIFICATION); + if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) { + // blocklist dismissed network + addNetworkToBlocklist(mRecommendedNetwork.SSID); + } + resetStateAndDelayNotification(); + } + + private void resetStateAndDelayNotification() { + mState = STATE_NO_NOTIFICATION; + mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelay; + mRecommendedNetwork = null; + } + + /** Dump this network notifier's state. */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(mTag + ": "); + pw.println("mSettingEnabled " + mSettingEnabled); + pw.println("currentTime: " + mClock.getWallClockMillis()); + pw.println("mNotificationRepeatTime: " + mNotificationRepeatTime); + pw.println("mState: " + mState); + pw.println("mBlocklistedSsids: " + mBlocklistedSsids.toString()); + } + + private class AvailableNetworkNotifierStoreData implements SsidSetStoreData.DataSource { + @Override + public Set getSsids() { + return new ArraySet<>(mBlocklistedSsids); + } + + @Override + public void setSsids(Set ssidList) { + mBlocklistedSsids.addAll(ssidList); + mWifiMetrics.setNetworkRecommenderBlocklistSize(mTag, mBlocklistedSsids.size()); + } + } + + private class NotificationEnabledSettingObserver extends ContentObserver { + NotificationEnabledSettingObserver(Handler handler) { + super(handler); + } + + public void register() { + mFrameworkFacade.registerContentObserver(mContext, + Settings.Global.getUriFor(mToggleSettingsName), true, this); + mSettingEnabled = getValue(); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + mSettingEnabled = getValue(); + clearPendingNotification(true /* resetRepeatTime */); + } + + private boolean getValue() { + boolean enabled = + mFrameworkFacade.getIntegerSetting(mContext, mToggleSettingsName, 1) == 1; + mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(mTag, enabled); + Log.d(mTag, "Settings toggle enabled=" + enabled); + return enabled; + } + } +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/ScanRequestProxy.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/ScanRequestProxy.java new file mode 100644 index 000000000..c5af30eff --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/ScanRequestProxy.java @@ -0,0 +1,733 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_SCAN_THROTTLE_ENABLED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.BroadcastOptions; +import android.content.Context; +import android.content.Intent; +import android.net.wifi.IScanResultsCallback; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiScanner; +import android.net.wifi.util.ScanResultUtil; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.WorkSource; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; +import android.util.LruCache; +import android.util.Pair; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; +import com.android.server.wifi.scanner.WifiScannerInternal; +import com.android.server.wifi.util.WifiPermissionsUtil; +import com.android.wifi.resources.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.annotation.concurrent.NotThreadSafe; + +/** + * This class manages all scan requests originating from external apps using the + * {@link WifiManager#startScan()}. + * + * This class is responsible for: + * a) Enable/Disable scanning based on the request from {@link ActiveModeWarden}. + * a) Forwarding scan requests from {@link WifiManager#startScan()} to + * {@link WifiScanner#startScan(WifiScanner.ScanSettings, WifiScanner.ScanListener)}. + * Will essentially proxy scan requests from WifiService to WifiScanningService. + * b) Cache the results of these scan requests and return them when + * {@link WifiManager#getScanResults()} is invoked. + * c) Will send out the {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} broadcast when new + * scan results are available. + * d) Throttle scan requests from non-setting apps: + * a) Each foreground app can request a max of + * {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} scan every + * {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS}. + * b) Background apps combined can request 1 scan every + * {@link #SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS}. + * Note: This class is not thread-safe. It needs to be invoked from the main Wifi thread only. + */ +@NotThreadSafe +public class ScanRequestProxy { + private static final String TAG = "WifiScanRequestProxy"; + + @VisibleForTesting + public static final int SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS = 120 * 1000; + @VisibleForTesting + public static final int SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS = 4; + @VisibleForTesting + public static final int SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS = 30 * 60 * 1000; + + public static final int PARTIAL_SCAN_CACHE_SIZE = 200; + + private final Context mContext; + private final Handler mHandler; + private final AppOpsManager mAppOps; + private final ActivityManager mActivityManager; + private final WifiInjector mWifiInjector; + private final WifiConfigManager mWifiConfigManager; + private final WifiPermissionsUtil mWifiPermissionsUtil; + private final WifiMetrics mWifiMetrics; + private final Clock mClock; + private final WifiSettingsConfigStore mSettingsConfigStore; + private WifiScannerInternal mWifiScanner; + + // Verbose logging flag. + private boolean mVerboseLoggingEnabled = false; + private final Object mThrottleEnabledLock = new Object(); + @GuardedBy("mThrottleEnabledLock") + private boolean mThrottleEnabled = true; + // Flag to decide if we need to scan or not. + private boolean mScanningEnabled = false; + // Flag to decide if we need to scan for hidden networks or not. + private boolean mScanningForHiddenNetworksEnabled = false; + // Timestamps for the last scan requested by any background app. + private long mLastScanTimestampForBgApps = 0; + // Timestamps for the list of last few scan requests by each foreground app. + // Keys in the map = Pair of the app. + // Values in the map = List of the last few scan request timestamps from the app. + private final ArrayMap, LinkedList> mLastScanTimestampsForFgApps = + new ArrayMap(); + // Full scan results cached from the last full single scan request. + // Stored as a map of bssid -> ScanResult to allow other clients to perform ScanResult lookup + // for bssid more efficiently. + private final Map mFullScanCache = new HashMap<>(); + // Partial scan results cached since the last full single scan request. + private final LruCache mPartialScanCache = + new LruCache<>(PARTIAL_SCAN_CACHE_SIZE); + // external ScanResultCallback tracker + private final RemoteCallbackList mRegisteredScanResultsCallbacks; + private class GlobalScanListener implements WifiScanner.ScanListener { + @Override + public void onSuccess() { + // Ignore. These will be processed from the scan request listener. + } + + @Override + public void onFailure(int reason, String description) { + // Ignore. These will be processed from the scan request listener. + } + + @Override + public void onResults(WifiScanner.ScanData[] scanDatas) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Scan results received"); + } + // For single scans, the array size should always be 1. + if (scanDatas.length != 1) { + Log.wtf(TAG, "Found more than 1 batch of scan results, Failing..."); + sendScanResultBroadcast(false); + return; + } + WifiScanner.ScanData scanData = scanDatas[0]; + ScanResult[] scanResults = scanData.getResults(); + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Received " + scanResults.length + " scan results"); + } + // Only process full band scan results. + boolean isFullBandScan = WifiScanner.isFullBandScan( + scanData.getScannedBandsInternal(), false); + if (isFullBandScan) { + // If is full scan, clear the cache so only the latest data is available + mFullScanCache.clear(); + mPartialScanCache.evictAll(); + } + for (ScanResult s : scanResults) { + ScanResult scanResult = mFullScanCache.get(s.BSSID); + if (isFullBandScan && scanResult == null) { + mFullScanCache.put(s.BSSID, s); + continue; + } + // If a hidden network is configured, wificond may report two scan results for + // the same BSS, ie. One with the SSID and another one without SSID. So avoid + // overwriting the scan result of the same BSS with Hidden SSID scan result + if (scanResult != null) { + if (TextUtils.isEmpty(scanResult.SSID) || !TextUtils.isEmpty(s.SSID)) { + mFullScanCache.put(s.BSSID, s); + } + continue; + } + scanResult = mPartialScanCache.get(s.BSSID); + if (scanResult == null + || TextUtils.isEmpty(scanResult.SSID) || !TextUtils.isEmpty(s.SSID)) { + mPartialScanCache.put(s.BSSID, s); + } + } + if (isFullBandScan) { + // Only trigger broadcasts for full scans + sendScanResultBroadcast(true); + sendScanResultsAvailableToCallbacks(); + } + } + + @Override + public void onFullResult(ScanResult fullScanResult) { + // Ignore for single scans. + } + + @Override + public void onPeriodChanged(int periodInMs) { + // Ignore for single scans. + } + }; + + // Common scan listener for scan requests initiated by this class. + private class ScanRequestProxyScanListener implements WifiScanner.ScanListener { + @Override + public void onSuccess() { + // Scan request succeeded, wait for results to report to external clients. + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Scan request succeeded"); + } + } + + @Override + public void onFailure(int reason, String description) { + Log.e(TAG, "Scan failure received. reason: " + reason + ",description: " + description); + sendScanResultBroadcast(false); + } + + @Override + public void onResults(WifiScanner.ScanData[] scanDatas) { + // Ignore. These will be processed from the global listener. + } + + @Override + public void onFullResult(ScanResult fullScanResult) { + // Ignore for single scans. + } + + @Override + public void onPeriodChanged(int periodInMs) { + // Ignore for single scans. + } + }; + + ScanRequestProxy(Context context, AppOpsManager appOpsManager, ActivityManager activityManager, + WifiInjector wifiInjector, WifiConfigManager configManager, + WifiPermissionsUtil wifiPermissionUtil, WifiMetrics wifiMetrics, Clock clock, + Handler handler, WifiSettingsConfigStore settingsConfigStore) { + mContext = context; + mHandler = handler; + mAppOps = appOpsManager; + mActivityManager = activityManager; + mWifiInjector = wifiInjector; + mWifiConfigManager = configManager; + mWifiPermissionsUtil = wifiPermissionUtil; + mWifiMetrics = wifiMetrics; + mClock = clock; + mSettingsConfigStore = settingsConfigStore; + mRegisteredScanResultsCallbacks = new RemoteCallbackList<>(); + } + + /** + * Enable verbose logging. + */ + public void enableVerboseLogging(boolean verboseEnabled) { + mVerboseLoggingEnabled = verboseEnabled; + } + + private void updateThrottleEnabled() { + synchronized (mThrottleEnabledLock) { + // Start listening for throttle settings change after we retrieve scanner instance. + mThrottleEnabled = mSettingsConfigStore.get(WIFI_SCAN_THROTTLE_ENABLED); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Scan throttle enabled " + mThrottleEnabled); + } + } + } + + /** + * Helper method to populate WifiScanner handle. This is done lazily because + * WifiScanningService is started after WifiService. + */ + private boolean retrieveWifiScannerIfNecessary() { + if (mWifiScanner == null) { + mWifiScanner = WifiLocalServices.getService(WifiScannerInternal.class); + updateThrottleEnabled(); + // Register the global scan listener. + if (mWifiScanner != null) { + mWifiScanner.registerScanListener( + new WifiScannerInternal.ScanListener(new GlobalScanListener(), mHandler)); + } + } + return mWifiScanner != null; + } + + /** + * Method that lets public apps know that scans are available. + * + * @param context Context to use for the notification + * @param available boolean indicating if scanning is available + */ + private void sendScanAvailableBroadcast(Context context, boolean available) { + Log.d(TAG, "Sending scan available broadcast: " + available); + final Intent intent = new Intent(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, available); + context.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + + private void enableScanningInternal(boolean enable) { + if (!retrieveWifiScannerIfNecessary()) { + Log.e(TAG, "Failed to retrieve wifiscanner"); + return; + } + mWifiScanner.setScanningEnabled(enable); + sendScanAvailableBroadcast(mContext, enable); + if (!enable) clearScanResults(); + Log.i(TAG, "Scanning is " + (enable ? "enabled" : "disabled")); + } + + /** + * Enable/disable scanning. + * + * @param enable true to enable, false to disable. + * @param enableScanningForHiddenNetworks true to enable scanning for hidden networks, + * false to disable. + */ + public void enableScanning(boolean enable, boolean enableScanningForHiddenNetworks) { + if (enable) { + enableScanningInternal(true); + mScanningForHiddenNetworksEnabled = enableScanningForHiddenNetworks; + Log.i(TAG, "Scanning for hidden networks is " + + (enableScanningForHiddenNetworks ? "enabled" : "disabled")); + } else { + enableScanningInternal(false); + } + mScanningEnabled = enable; + } + + + /** + * Helper method to send the scan request status broadcast. + */ + private void sendScanResultBroadcast(boolean scanSucceeded) { + Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, + createBroadcastOptionsForScanResultsAvailable(scanSucceeded)); + } + + /** + * Helper method to send the scan request failure broadcast to specified package. + */ + private void sendScanResultFailureBroadcastToPackage(String packageName) { + final boolean scanSucceeded = false; + Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded); + intent.setPackage(packageName); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, + createBroadcastOptionsForScanResultsAvailable(scanSucceeded)); + } + + static Bundle createBroadcastOptionsForScanResultsAvailable(boolean scanSucceeded) { + if (!SdkLevel.isAtLeastU()) return null; + + // Delay delivering the broadcast to apps in the Cached state and apply policy such + // that when a new SCAN_RESULTS_AVAILABLE broadcast is sent, any older pending + // broadcasts with the same 'scanSucceeded' extra value will be discarded. + return BroadcastOptions.makeBasic() + .setDeliveryGroupMatchingKey(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, + String.valueOf(scanSucceeded)) + .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) + .toBundle(); + } + + private void trimPastScanRequestTimesForForegroundApp( + List scanRequestTimestamps, long currentTimeMillis) { + Iterator timestampsIter = scanRequestTimestamps.iterator(); + while (timestampsIter.hasNext()) { + Long scanRequestTimeMillis = timestampsIter.next(); + if ((currentTimeMillis - scanRequestTimeMillis) + > SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS) { + timestampsIter.remove(); + } else { + // This list is sorted by timestamps, so we can skip any more checks + break; + } + } + } + + private LinkedList getOrCreateScanRequestTimestampsForForegroundApp( + int callingUid, String packageName) { + Pair uidAndPackageNamePair = Pair.create(callingUid, packageName); + synchronized (mThrottleEnabledLock) { + LinkedList scanRequestTimestamps = + mLastScanTimestampsForFgApps.get(uidAndPackageNamePair); + if (scanRequestTimestamps == null) { + scanRequestTimestamps = new LinkedList<>(); + mLastScanTimestampsForFgApps.put(uidAndPackageNamePair, scanRequestTimestamps); + } + return scanRequestTimestamps; + } + } + + /** + * Checks if the scan request from the app (specified by packageName) needs + * to be throttled. + * The throttle limit allows a max of {@link #SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS} + * in {@link #SCAN_REQUEST_THROTTLE_TIME_WINDOW_FG_APPS_MS} window. + */ + private boolean shouldScanRequestBeThrottledForForegroundApp( + int callingUid, String packageName) { + if (isPackageNameInExceptionList(packageName, true)) { + return false; + } + LinkedList scanRequestTimestamps = + getOrCreateScanRequestTimestampsForForegroundApp(callingUid, packageName); + long currentTimeMillis = mClock.getElapsedSinceBootMillis(); + // First evict old entries from the list. + trimPastScanRequestTimesForForegroundApp(scanRequestTimestamps, currentTimeMillis); + if (scanRequestTimestamps.size() >= SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS) { + return true; + } + // Proceed with the scan request and record the time. + scanRequestTimestamps.addLast(currentTimeMillis); + return false; + } + + private boolean isPackageNameInExceptionList(String packageName, boolean isForeground) { + if (packageName == null) { + return false; + } + String[] exceptionList = mContext.getResources().getStringArray(isForeground + ? R.array.config_wifiForegroundScanThrottleExceptionList + : R.array.config_wifiBackgroundScanThrottleExceptionList); + if (exceptionList == null) { + return false; + } + for (String name : exceptionList) { + if (TextUtils.equals(packageName, name)) { + return true; + } + } + return false; + } + + /** + * Checks if the scan request from a background app needs to be throttled. + */ + private boolean shouldScanRequestBeThrottledForBackgroundApp(String packageName) { + if (isPackageNameInExceptionList(packageName, false)) { + return false; + } + synchronized (mThrottleEnabledLock) { + long lastScanMs = mLastScanTimestampForBgApps; + long elapsedRealtime = mClock.getElapsedSinceBootMillis(); + if (lastScanMs != 0 + && (elapsedRealtime - lastScanMs) < SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS) { + return true; + } + // Proceed with the scan request and record the time. + mLastScanTimestampForBgApps = elapsedRealtime; + return false; + } + } + + /** + * Safely retrieve package importance. + */ + private int getPackageImportance(int callingUid, String packageName) { + mAppOps.checkPackage(callingUid, packageName); + try { + return mActivityManager.getPackageImportance(packageName); + } catch (SecurityException e) { + Log.e(TAG, "Failed to check the app state", e); + return ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; + } + } + + /** + * Checks if the scan request from the app (specified by callingUid & packageName) needs + * to be throttled. + */ + private boolean shouldScanRequestBeThrottledForApp(int callingUid, String packageName, + int packageImportance) { + boolean isThrottled; + if (packageImportance + > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) { + isThrottled = shouldScanRequestBeThrottledForBackgroundApp(packageName); + if (isThrottled) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Background scan app request [" + callingUid + ", " + + packageName + "]"); + } + mWifiMetrics.incrementExternalBackgroundAppOneshotScanRequestsThrottledCount(); + } + } else { + isThrottled = shouldScanRequestBeThrottledForForegroundApp(callingUid, packageName); + if (isThrottled) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Foreground scan app request [" + callingUid + ", " + + packageName + "]"); + } + mWifiMetrics.incrementExternalForegroundAppOneshotScanRequestsThrottledCount(); + } + } + mWifiMetrics.incrementExternalAppOneshotScanRequestsCount(); + return isThrottled; + } + + /** + * Initiate a wifi scan. + * + * @param callingUid The uid initiating the wifi scan. Blame will be given to this uid. + * @return true if the scan request was placed or a scan is already ongoing, false otherwise. + */ + public boolean startScan(int callingUid, String packageName) { + if (!mScanningEnabled || !retrieveWifiScannerIfNecessary()) { + Log.e(TAG, "Failed to retrieve wifiscanner"); + sendScanResultFailureBroadcastToPackage(packageName); + return false; + } + boolean fromSettingsOrSetupWizard = + mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid) + || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(callingUid); + // Check and throttle scan request unless, + // a) App has either NETWORK_SETTINGS or NETWORK_SETUP_WIZARD permission. + // b) Throttling has been disabled by user. + int packageImportance = getPackageImportance(callingUid, packageName); + if (!fromSettingsOrSetupWizard && isScanThrottleEnabled() + && shouldScanRequestBeThrottledForApp(callingUid, packageName, + packageImportance)) { + Log.i(TAG, "Scan request from " + packageName + " throttled"); + sendScanResultFailureBroadcastToPackage(packageName); + return false; + } + // Create a worksource using the caller's UID. + WorkSource workSource = new WorkSource(callingUid, packageName); + mWifiMetrics.getScanMetrics().setWorkSource(workSource); + mWifiMetrics.getScanMetrics().setImportance(packageImportance); + + // Create the scan settings. + WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings(); + // Scan requests from apps with network settings will be of high accuracy type. + if (fromSettingsOrSetupWizard) { + settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY; + } else { + if (SdkLevel.isAtLeastS()) { + // since the scan request is from a normal app, do not scan all 6Ghz channels. + settings.set6GhzPscOnlyEnabled(true); + } + } + settings.band = WifiScanner.WIFI_BAND_ALL; + settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN + | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; + if (mScanningForHiddenNetworksEnabled) { + settings.hiddenNetworks.clear(); + // retrieve the list of hidden network SSIDs from saved network to scan if enabled. + settings.hiddenNetworks.addAll(mWifiConfigManager.retrieveHiddenNetworkList(false)); + // retrieve the list of hidden network SSIDs from Network suggestion to scan for. + settings.hiddenNetworks.addAll(mWifiInjector.getWifiNetworkSuggestionsManager() + .retrieveHiddenNetworkList(false)); + } + mWifiScanner.startScan(settings, + new WifiScannerInternal.ScanListener(new ScanRequestProxyScanListener(), mHandler), + workSource); + return true; + } + + /** + * Return the results of the most recent access point scan, in the form of + * a list of {@link ScanResult} objects. + * @return the list of results + */ + public List getScanResults() { + // return a copy to prevent external modification + return new ArrayList<>(combineScanResultsCache().values()); + } + + /** + * Return the ScanResult from the most recent access point scan for the provided bssid. + * + * @param bssid BSSID as string {@link ScanResult#BSSID}. + * @return ScanResult for the corresponding bssid if found, null otherwise. + */ + public @Nullable ScanResult getScanResult(@Nullable String bssid) { + if (bssid == null) return null; + ScanResult scanResult = mFullScanCache.get(bssid); + if (scanResult == null) { + scanResult = mPartialScanCache.get(bssid); + if (scanResult == null) return null; + } + // return a copy to prevent external modification + return new ScanResult(scanResult); + } + + + /** + * Clear the stored scan results. + */ + private void clearScanResults() { + synchronized (mThrottleEnabledLock) { + mFullScanCache.clear(); + mPartialScanCache.evictAll(); + mLastScanTimestampForBgApps = 0; + mLastScanTimestampsForFgApps.clear(); + } + } + + /** + * Clear any scan timestamps being stored for the app. + * + * @param uid Uid of the package. + * @param packageName Name of the package. + */ + public void clearScanRequestTimestampsForApp(@NonNull String packageName, int uid) { + synchronized (mThrottleEnabledLock) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Clearing scan request timestamps for uid=" + uid + ", packageName=" + + packageName); + } + mLastScanTimestampsForFgApps.remove(Pair.create(uid, packageName)); + } + } + + private void sendScanResultsAvailableToCallbacks() { + int itemCount = mRegisteredScanResultsCallbacks.beginBroadcast(); + for (int i = 0; i < itemCount; i++) { + try { + mRegisteredScanResultsCallbacks.getBroadcastItem(i).onScanResultsAvailable(); + } catch (RemoteException e) { + Log.e(TAG, "onScanResultsAvailable: remote exception -- " + e); + } + } + mRegisteredScanResultsCallbacks.finishBroadcast(); + } + + /** Combine the full and partial scan results */ + private Map combineScanResultsCache() { + Map combinedCache = new HashMap<>(); + combinedCache.putAll(mFullScanCache); + combinedCache.putAll(mPartialScanCache.snapshot()); + return combinedCache; + } + + /** + * Register a callback on scan event + * @param callback IScanResultListener instance to add. + * @return true if succeed otherwise false. + */ + public boolean registerScanResultsCallback(IScanResultsCallback callback) { + return mRegisteredScanResultsCallbacks.register(callback); + } + + /** + * Unregister a callback on scan event + * @param callback IScanResultListener instance to add. + */ + public void unregisterScanResultsCallback(IScanResultsCallback callback) { + mRegisteredScanResultsCallbacks.unregister(callback); + } + + /** + * Enable/disable wifi scan throttling from 3rd party apps. + */ + public void setScanThrottleEnabled(boolean enable) { + synchronized (mThrottleEnabledLock) { + mThrottleEnabled = enable; + mSettingsConfigStore.put(WIFI_SCAN_THROTTLE_ENABLED, enable); + if (mVerboseLoggingEnabled) { + Log.i(TAG, "Scan throttle enabled " + mThrottleEnabled); + } + // reset internal counters when enabling/disabling throttling + mLastScanTimestampsForFgApps.clear(); + mLastScanTimestampForBgApps = 0; + } + } + + /** + * Get the persisted Wi-Fi scan throttle state, set by + * {@link #setScanThrottleEnabled(boolean)}. + */ + public boolean isScanThrottleEnabled() { + synchronized (mThrottleEnabledLock) { + return mThrottleEnabled; + } + } + + /** Indicate whether there are WPA2 personal only networks. */ + public boolean isWpa2PersonalOnlyNetworkInRange(String ssid) { + return combineScanResultsCache().values().stream().anyMatch(r -> + TextUtils.equals(ssid, r.getWifiSsid().toString()) + && ScanResultUtil.isScanResultForPskOnlyNetwork(r)); + } + + /** Indicate whether there are WPA3 only networks. */ + public boolean isWpa3PersonalOnlyNetworkInRange(String ssid) { + return combineScanResultsCache().values().stream().anyMatch(r -> + TextUtils.equals(ssid, r.getWifiSsid().toString()) + && ScanResultUtil.isScanResultForSaeOnlyNetwork(r)); + } + + /** Indicate whether there are WPA2/WPA3 transition mode networks. */ + public boolean isWpa2Wpa3PersonalTransitionNetworkInRange(String ssid) { + return combineScanResultsCache().values().stream().anyMatch(r -> + TextUtils.equals(ssid, ScanResultUtil.createQuotedSsid(r.SSID)) + && ScanResultUtil.isScanResultForPskSaeTransitionNetwork(r)); + } + + /** Indicate whether there are OPEN only networks. */ + public boolean isOpenOnlyNetworkInRange(String ssid) { + return combineScanResultsCache().values().stream().anyMatch(r -> + TextUtils.equals(ssid, r.getWifiSsid().toString()) + && ScanResultUtil.isScanResultForOpenOnlyNetwork(r)); + } + + /** Indicate whether there are OWE only networks. */ + public boolean isOweOnlyNetworkInRange(String ssid) { + return combineScanResultsCache().values().stream().anyMatch(r -> + TextUtils.equals(ssid, r.getWifiSsid().toString()) + && ScanResultUtil.isScanResultForOweOnlyNetwork(r)); + } + + /** Indicate whether there are WPA2 Enterprise only networks. */ + public boolean isWpa2EnterpriseOnlyNetworkInRange(String ssid) { + return combineScanResultsCache().values().stream().anyMatch(r -> + TextUtils.equals(ssid, r.getWifiSsid().toString()) + && ScanResultUtil.isScanResultForWpa2EnterpriseOnlyNetwork(r)); + } + + /** Indicate whether there are WPA3 Enterprise only networks. */ + public boolean isWpa3EnterpriseOnlyNetworkInRange(String ssid) { + return combineScanResultsCache().values().stream().anyMatch(r -> + TextUtils.equals(ssid, r.getWifiSsid().toString()) + && ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(r)); + } +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlImpl.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlImpl.java new file mode 100644 index 000000000..7722e4acc --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlImpl.java @@ -0,0 +1,4016 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import static android.net.wifi.WifiManager.WIFI_FEATURE_DECORATED_IDENTITY; +import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP; +import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP_ENROLLEE_RESPONDER; +import static android.net.wifi.WifiManager.WIFI_FEATURE_FILS_SHA256; +import static android.net.wifi.WifiManager.WIFI_FEATURE_FILS_SHA384; +import static android.net.wifi.WifiManager.WIFI_FEATURE_MBO; +import static android.net.wifi.WifiManager.WIFI_FEATURE_OCE; +import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE; +import static android.net.wifi.WifiManager.WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS; +import static android.net.wifi.WifiManager.WIFI_FEATURE_SAE_PK; +import static android.net.wifi.WifiManager.WIFI_FEATURE_SET_TLS_MINIMUM_VERSION; +import static android.net.wifi.WifiManager.WIFI_FEATURE_TLS_V1_3; +import static android.net.wifi.WifiManager.WIFI_FEATURE_TRUST_ON_FIRST_USE; +import static android.net.wifi.WifiManager.WIFI_FEATURE_WAPI; +import static android.net.wifi.WifiManager.WIFI_FEATURE_WFD_R2; +import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE; +import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SUITE_B; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.wifi.WifiChannelWidthInMhz; +import android.hardware.wifi.supplicant.BtCoexistenceMode; +import android.hardware.wifi.supplicant.ConnectionCapabilities; +import android.hardware.wifi.supplicant.DebugLevel; +import android.hardware.wifi.supplicant.DppAkm; +import android.hardware.wifi.supplicant.DppCurve; +import android.hardware.wifi.supplicant.DppNetRole; +import android.hardware.wifi.supplicant.DppResponderBootstrapInfo; +import android.hardware.wifi.supplicant.INonStandardCertCallback; +import android.hardware.wifi.supplicant.ISupplicant; +import android.hardware.wifi.supplicant.ISupplicantStaIface; +import android.hardware.wifi.supplicant.ISupplicantStaIfaceCallback; +import android.hardware.wifi.supplicant.ISupplicantStaNetwork; +import android.hardware.wifi.supplicant.IfaceInfo; +import android.hardware.wifi.supplicant.IfaceType; +import android.hardware.wifi.supplicant.IpVersion; +import android.hardware.wifi.supplicant.KeyMgmtMask; +import android.hardware.wifi.supplicant.LegacyMode; +import android.hardware.wifi.supplicant.MloLinksInfo; +import android.hardware.wifi.supplicant.MscsParams.FrameClassifierFields; +import android.hardware.wifi.supplicant.MsduDeliveryInfo; +import android.hardware.wifi.supplicant.MsduDeliveryInfo.DeliveryRatio; +import android.hardware.wifi.supplicant.PortRange; +import android.hardware.wifi.supplicant.QosCharacteristics; +import android.hardware.wifi.supplicant.QosCharacteristics.QosCharacteristicsMask; +import android.hardware.wifi.supplicant.QosPolicyClassifierParams; +import android.hardware.wifi.supplicant.QosPolicyClassifierParamsMask; +import android.hardware.wifi.supplicant.QosPolicyData; +import android.hardware.wifi.supplicant.QosPolicyRequestType; +import android.hardware.wifi.supplicant.QosPolicyScsData; +import android.hardware.wifi.supplicant.QosPolicyScsData.LinkDirection; +import android.hardware.wifi.supplicant.QosPolicyScsRequestStatus; +import android.hardware.wifi.supplicant.QosPolicyScsRequestStatusCode; +import android.hardware.wifi.supplicant.QosPolicyStatus; +import android.hardware.wifi.supplicant.QosPolicyStatusCode; +import android.hardware.wifi.supplicant.RxFilterType; +import android.hardware.wifi.supplicant.SignalPollResult; +import android.hardware.wifi.supplicant.SupplicantStatusCode; +import android.hardware.wifi.supplicant.WifiTechnology; +import android.hardware.wifi.supplicant.WpaDriverCapabilitiesMask; +import android.hardware.wifi.supplicant.WpsConfigMethods; +import android.net.DscpPolicy; +import android.net.MacAddress; +import android.net.NetworkAgent; +import android.net.wifi.MscsParams; +import android.net.wifi.QosPolicyParams; +import android.net.wifi.ScanResult; +import android.net.wifi.SecurityParams; +import android.net.wifi.WifiAnnotations; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiKeystore; +import android.net.wifi.WifiSsid; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; +import com.android.server.wifi.mockwifi.MockWifiServiceUtil; +import com.android.server.wifi.util.HalAidlUtil; +import com.android.server.wifi.util.NativeUtil; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HAL calls to set up/tear down the supplicant daemon and make requests + * related to station mode. Uses the AIDL supplicant interface. + * To maintain thread-safety, the locking protocol is that every non-static method (regardless of + * access level) acquires mLock. + */ +public class SupplicantStaIfaceHalAidlImpl implements ISupplicantStaIfaceHal { + private static final String TAG = "SupplicantStaIfaceHalAidlImpl"; + private static final String ISUPPLICANTSTAIFACE = "ISupplicantStaIface"; + @VisibleForTesting + private static final String HAL_INSTANCE_NAME = ISupplicant.DESCRIPTOR + "/default"; + @VisibleForTesting + public static final long WAIT_FOR_DEATH_TIMEOUT_MS = 50L; + + /** + * Regex pattern for extracting the wps device type bytes. + * Matches a strings like the following: "--"; + */ + private static final Pattern WPS_DEVICE_TYPE_PATTERN = + Pattern.compile("^(\\d{1,2})-([0-9a-fA-F]{8})-(\\d{1,2})$"); + + private final Object mLock = new Object(); + private boolean mVerboseLoggingEnabled = false; + private boolean mVerboseHalLoggingEnabled = false; + private boolean mServiceDeclared = false; + private int mServiceVersion = -1; + + // Supplicant HAL interface objects + private ISupplicant mISupplicant = null; + private Map mISupplicantStaIfaces = new HashMap<>(); + private Map + mISupplicantStaIfaceCallbacks = new HashMap<>(); + private Map + mCurrentNetworkRemoteHandles = new HashMap<>(); + private Map mCurrentNetworkLocalConfigs = new HashMap<>(); + private Map mCurrentNetworkFallbackSsids = new HashMap<>(); + private Map>> + mLinkedNetworkLocalAndRemoteConfigs = new HashMap<>(); + @VisibleForTesting + PmkCacheManager mPmkCacheManager; + private WifiNative.SupplicantDeathEventHandler mDeathEventHandler; + private SupplicantDeathRecipient mSupplicantDeathRecipient; + private final Context mContext; + private final WifiMonitor mWifiMonitor; + private final Handler mEventHandler; + private WifiNative.DppEventCallback mDppCallback = null; + private final Clock mClock; + private final WifiMetrics mWifiMetrics; + private final WifiGlobals mWifiGlobals; + private final SsidTranslator mSsidTranslator; + private final WifiInjector mWifiInjector; + private CountDownLatch mWaitForDeathLatch; + private INonStandardCertCallback mNonStandardCertCallback; + private SupplicantStaIfaceHal.QosScsResponseCallback mQosScsResponseCallback; + + private class SupplicantDeathRecipient implements DeathRecipient { + @Override + public void binderDied() { + } + + @Override + public void binderDied(@NonNull IBinder who) { + synchronized (mLock) { + IBinder supplicantBinder = getServiceBinderMockable(); + Log.w(TAG, "ISupplicant binder died. who=" + who + ", service=" + + supplicantBinder); + if (supplicantBinder == null) { + Log.w(TAG, "Supplicant Death EventHandler called" + + " when ISupplicant/binder service is already cleared"); + } else if (supplicantBinder != who) { + Log.w(TAG, "Ignoring stale death recipient notification"); + return; + } + if (mWaitForDeathLatch != null) { + mWaitForDeathLatch.countDown(); + } + Log.w(TAG, "Handle supplicant death"); + supplicantServiceDiedHandler(); + } + } + } + + public SupplicantStaIfaceHalAidlImpl(Context context, WifiMonitor monitor, Handler handler, + Clock clock, WifiMetrics wifiMetrics, WifiGlobals wifiGlobals, + @NonNull SsidTranslator ssidTranslator, WifiInjector wifiInjector) { + mContext = context; + mWifiMonitor = monitor; + mEventHandler = handler; + mClock = clock; + mWifiMetrics = wifiMetrics; + mWifiGlobals = wifiGlobals; + mSsidTranslator = ssidTranslator; + mSupplicantDeathRecipient = new SupplicantDeathRecipient(); + mPmkCacheManager = new PmkCacheManager(mClock, mEventHandler); + mWifiInjector = wifiInjector; + } + + /** + * Enable/Disable verbose logging. + * + */ + public void enableVerboseLogging(boolean verboseEnabled, boolean halVerboseEnabled) { + synchronized (mLock) { + mVerboseLoggingEnabled = verboseEnabled; + mVerboseHalLoggingEnabled = halVerboseEnabled; + setLogLevel(mVerboseHalLoggingEnabled); + } + } + + protected boolean isVerboseLoggingEnabled() { + synchronized (mLock) { + return mVerboseLoggingEnabled; + } + } + + /** + * Checks whether the ISupplicant service is declared, and therefore should be available. + * + * @return true if the ISupplicant service is declared + */ + public boolean initialize() { + synchronized (mLock) { + if (mISupplicant != null) { + Log.i(TAG, "Service is already initialized, skipping initialize method"); + return true; + } + if (mVerboseLoggingEnabled) { + Log.i(TAG, "Checking for ISupplicant service."); + } + mISupplicantStaIfaces.clear(); + mServiceDeclared = serviceDeclared(); + return mServiceDeclared; + } + } + + protected int getCurrentNetworkId(@NonNull String ifaceName) { + synchronized (mLock) { + WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName); + if (currentConfig == null) { + return WifiConfiguration.INVALID_NETWORK_ID; + } + return currentConfig.networkId; + } + } + + /** + * Setup a STA interface for the specified iface name. + * + * @param ifaceName Name of the interface. + * @return true on success, false otherwise. + */ + public boolean setupIface(@NonNull String ifaceName) { + synchronized (mLock) { + if (getStaIface(ifaceName) != null) { + Log.e(TAG, "Iface " + ifaceName + " already exists."); + return false; + } + + ISupplicantStaIface iface = addIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Unable to add iface " + ifaceName); + return false; + } + + ISupplicantStaIfaceCallback callback = new SupplicantStaIfaceCallbackAidlImpl( + SupplicantStaIfaceHalAidlImpl.this, ifaceName, + new Object(), mContext, mWifiMonitor, mSsidTranslator); + if (registerCallback(iface, callback)) { + mISupplicantStaIfaces.put(ifaceName, iface); + // Keep callback in a store to avoid recycling by garbage collector + mISupplicantStaIfaceCallbacks.put(ifaceName, callback); + return true; + } else { + Log.e(TAG, "Unable to register callback for iface " + ifaceName); + return false; + } + } + } + + /** + * Create a STA interface for the specified iface name. + * + * @param ifaceName Name of the interface. + * @return ISupplicantStaIface object on success, null otherwise. + */ + private ISupplicantStaIface addIface(@NonNull String ifaceName) { + synchronized (mLock) { + String methodStr = "addIface"; + if (!checkSupplicantAndLogFailure(methodStr)) { + return null; + } + try { + return mISupplicant.addStaInterface(ifaceName); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } catch (NoSuchElementException | IllegalArgumentException e) { + Log.e(TAG, "Encountered exception at addIface: ", e); + } + return null; + } + } + + /** + * Teardown a STA interface for the specified iface name. + * + * @param ifaceName Name of the interface. + * @return true on success, false otherwise. + */ + public boolean teardownIface(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "teardownIface"; + if (checkStaIfaceAndLogFailure(ifaceName, methodStr) == null) { + return false; + } + if (!checkSupplicantAndLogFailure(methodStr)) { + return false; + } + + try { + IfaceInfo ifaceInfo = new IfaceInfo(); + ifaceInfo.name = ifaceName; + ifaceInfo.type = IfaceType.STA; + mISupplicant.removeInterface(ifaceInfo); + mISupplicantStaIfaces.remove(ifaceName); + mISupplicantStaIfaceCallbacks.remove(ifaceName); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } catch (NoSuchElementException e) { + Log.e(TAG, "Encountered exception at teardownIface: ", e); + } + return false; + } + } + + /** + * Registers a death notification for supplicant. + * @return Returns true on success. + */ + public boolean registerDeathHandler(@NonNull WifiNative.SupplicantDeathEventHandler handler) { + synchronized (mLock) { + if (mDeathEventHandler != null) { + Log.e(TAG, "Death handler already present"); + } + mDeathEventHandler = handler; + return true; + } + } + + /** + * Deregisters a death notification for supplicant. + * @return Returns true on success. + */ + public boolean deregisterDeathHandler() { + synchronized (mLock) { + if (mDeathEventHandler == null) { + Log.e(TAG, "No Death handler present"); + } + mDeathEventHandler = null; + return true; + } + } + + /** + * Signals whether initialization started successfully. + */ + public boolean isInitializationStarted() { + synchronized (mLock) { + return mServiceDeclared; + } + } + + /** + * Signals whether initialization completed successfully. + */ + public boolean isInitializationComplete() { + synchronized (mLock) { + return mISupplicant != null; + } + } + + /** + * Indicates whether the AIDL service is declared + */ + public static boolean serviceDeclared() { + // Service Manager API ServiceManager#isDeclared supported after T. + if (!SdkLevel.isAtLeastT()) { + return false; + } + return ServiceManager.isDeclared(HAL_INSTANCE_NAME); + } + + /** + * Check that the service is running at least the expected version. + * Use to avoid the case where the framework is using a newer + * interface version than the service. + */ + protected boolean isServiceVersionAtLeast(int expectedVersion) { + return expectedVersion <= mServiceVersion; + } + + private void clearState() { + synchronized (mLock) { + Log.i(TAG, "Clearing internal state"); + mISupplicant = null; + mISupplicantStaIfaces.clear(); + mCurrentNetworkLocalConfigs.clear(); + mCurrentNetworkRemoteHandles.clear(); + mLinkedNetworkLocalAndRemoteConfigs.clear(); + mNonStandardCertCallback = null; + } + } + + private void supplicantServiceDiedHandler() { + synchronized (mLock) { + clearState(); + if (mDeathEventHandler != null) { + mDeathEventHandler.onDeath(); + } + } + } + + /** + * Start the supplicant daemon. + * + * @return true on success, false otherwise. + */ + public boolean startDaemon() { + synchronized (mLock) { + final String methodStr = "startDaemon"; + if (mISupplicant != null) { + Log.i(TAG, "Service is already initialized, skipping " + methodStr); + return true; + } + + clearState(); + mISupplicant = getSupplicantMockable(); + if (mISupplicant == null) { + Log.e(TAG, "Unable to obtain ISupplicant binder."); + return false; + } + Log.i(TAG, "Obtained ISupplicant binder."); + Log.i(TAG, "Local Version: " + ISupplicant.VERSION); + + try { + getServiceVersion(); + Log.i(TAG, "Remote Version: " + mServiceVersion); + IBinder serviceBinder = getServiceBinderMockable(); + if (serviceBinder == null) { + return false; + } + mWaitForDeathLatch = null; + serviceBinder.linkToDeath(mSupplicantDeathRecipient, /* flags= */ 0); + setLogLevel(mVerboseHalLoggingEnabled); + registerNonStandardCertCallback(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + return false; + } + } + } + + private void getServiceVersion() throws RemoteException { + synchronized (mLock) { + if (mISupplicant == null) return; + if (mServiceVersion == -1) { + int serviceVersion = mISupplicant.getInterfaceVersion(); + mWifiInjector.getSettingsConfigStore().put( + WifiSettingsConfigStore.SUPPLICANT_HAL_AIDL_SERVICE_VERSION, + serviceVersion); + mServiceVersion = serviceVersion; + Log.i(TAG, "Remote service version was cached"); + } + } + } + + /** + * Terminate the supplicant daemon & wait for its death. + */ + public void terminate() { + synchronized (mLock) { + final String methodStr = "terminate"; + if (!checkSupplicantAndLogFailure(methodStr)) { + return; + } + Log.i(TAG, "Terminate supplicant service"); + try { + mWaitForDeathLatch = new CountDownLatch(1); + mISupplicant.terminate(); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } + } + + // Wait for death recipient to confirm the service death. + try { + if (!mWaitForDeathLatch.await(WAIT_FOR_DEATH_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + Log.w(TAG, "Timed out waiting for confirmation of supplicant death"); + } else { + Log.d(TAG, "Got service death confirmation"); + } + } catch (InterruptedException e) { + Log.w(TAG, "Failed to wait for supplicant death"); + } + } + + /** + * Wrapper functions to access HAL objects, created to be mockable in unit tests + */ + @VisibleForTesting + protected ISupplicant getSupplicantMockable() { + synchronized (mLock) { + try { + if (SdkLevel.isAtLeastT()) { + return ISupplicant.Stub.asInterface( + ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME)); + } else { + return null; + } + } catch (Exception e) { + Log.e(TAG, "Unable to get ISupplicant service, " + e); + return null; + } + } + } + + @VisibleForTesting + protected IBinder getServiceBinderMockable() { + synchronized (mLock) { + if (mISupplicant == null) { + return null; + } + return mISupplicant.asBinder(); + } + } + + /** + * Helper method to look up the specified iface. + */ + private ISupplicantStaIface getStaIface(@NonNull String ifaceName) { + synchronized (mLock) { + return mISupplicantStaIfaces.get(ifaceName); + } + } + + /** + * Helper method to look up the network object for the specified iface. + */ + private SupplicantStaNetworkHalAidlImpl getCurrentNetworkRemoteHandle( + @NonNull String ifaceName) { + synchronized (mLock) { + return mCurrentNetworkRemoteHandles.get(ifaceName); + } + } + + /** + * Helper method to look up the network config for the specified iface. + */ + protected WifiConfiguration getCurrentNetworkLocalConfig(@NonNull String ifaceName) { + synchronized (mLock) { + return mCurrentNetworkLocalConfigs.get(ifaceName); + } + } + + /** + * Add a network configuration to wpa_supplicant. + * + * @param config Config corresponding to the network. + * @return a Pair object including SupplicantStaNetworkHal and WifiConfiguration objects + * for the current network. + */ + private Pair + addNetworkAndSaveConfig(@NonNull String ifaceName, WifiConfiguration config) { + synchronized (mLock) { + if (config == null) { + Log.e(TAG, "Cannot add null network."); + return null; + } + SupplicantStaNetworkHalAidlImpl network = addNetwork(ifaceName); + if (network == null) { + Log.e(TAG, "Failed to add network."); + return null; + } + boolean saveSuccess = false; + try { + saveSuccess = network.saveWifiConfiguration(config); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Exception while saving config params: " + config, e); + } + if (!saveSuccess) { + Log.e(TAG, "Failed to save variables for: " + config.getProfileKey()); + if (!removeAllNetworks(ifaceName)) { + Log.e(TAG, "Failed to remove all networks on failure."); + } + return null; + } + return new Pair(network, new WifiConfiguration(config)); + } + } + + /** + * Add the provided network configuration to wpa_supplicant and initiate connection to it. + * This method does the following: + * 1. If |config| is different to the current supplicant network, removes all supplicant + * networks and saves |config|. + * 2. Select the new network in wpa_supplicant. + * + * @param ifaceName Name of the interface. + * @param config WifiConfiguration parameters for the provided network. + * @return true if it succeeds, false otherwise + */ + public boolean connectToNetwork(@NonNull String ifaceName, @NonNull WifiConfiguration config) { + return connectToNetwork(ifaceName, config, null); + } + + /** + * Connects to the fallback SSID (if any) of the current network upon a network not found + * notification. + */ + public boolean connectToFallbackSsid(@NonNull String ifaceName) { + synchronized (mLock) { + WifiSsid fallbackSsid = mCurrentNetworkFallbackSsids.remove(ifaceName); + if (fallbackSsid == null) { + return false; + } + Log.d(TAG, "connectToFallbackSsid " + fallbackSsid); + return connectToNetwork( + ifaceName, getCurrentNetworkLocalConfig(ifaceName), fallbackSsid); + } + } + + /** + * Add the provided network configuration to wpa_supplicant and initiate connection to it. + * This method does the following: + * 1. If |config| is different to the current supplicant network, removes all supplicant + * networks and saves |config|. + * 2. Selects an SSID from the 2 possible original SSIDs derived from config.SSID to pass to + * wpa_supplicant, and stores the unused one as fallback if the first one is not found. + * 3. Select the new network in wpa_supplicant. + * + * @param ifaceName Name of the interface. + * @param config WifiConfiguration parameters for the provided network. + * @param actualSsid The actual, untranslated SSID to send to supplicant. If this is null, then + * we will connect to either of the 2 possible original SSIDs based on the + * config's network selection BSSID or network selection candidate. If that + * SSID is not found, then we will immediately connect to the other one. + * @return true if it succeeds, false otherwise + */ + private boolean connectToNetwork(@NonNull String ifaceName, @NonNull WifiConfiguration config, + WifiSsid actualSsid) { + synchronized (mLock) { + Log.d(TAG, "connectToNetwork " + config.getProfileKey() + ", actualSsid=" + actualSsid); + WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName); + if (actualSsid == null && WifiConfigurationUtil.isSameNetwork(config, currentConfig)) { + String networkSelectionBSSID = config.getNetworkSelectionStatus() + .getNetworkSelectionBSSID(); + String networkSelectionBSSIDCurrent = currentConfig.getNetworkSelectionStatus() + .getNetworkSelectionBSSID(); + if (Objects.equals(networkSelectionBSSID, networkSelectionBSSIDCurrent)) { + Log.d(TAG, "Network is already saved, will not trigger remove and add."); + } else { + Log.d(TAG, "Network is already saved, but need to update BSSID."); + if (!setCurrentNetworkBssid( + ifaceName, + config.getNetworkSelectionStatus().getNetworkSelectionBSSID())) { + Log.e(TAG, "Failed to set current network BSSID."); + return false; + } + mCurrentNetworkLocalConfigs.put(ifaceName, new WifiConfiguration(config)); + } + } else { + mCurrentNetworkRemoteHandles.remove(ifaceName); + mCurrentNetworkLocalConfigs.remove(ifaceName); + mLinkedNetworkLocalAndRemoteConfigs.remove(ifaceName); + mCurrentNetworkFallbackSsids.remove(ifaceName); + if (!removeAllNetworks(ifaceName)) { + Log.e(TAG, "Failed to remove existing networks"); + return false; + } + WifiConfiguration supplicantConfig = new WifiConfiguration(config); + if (actualSsid != null) { + supplicantConfig.SSID = actualSsid.toString(); + } else { + if (config.SSID != null) { + // No actual SSID supplied, so select from the network selection BSSID + // or the latest candidate BSSID. + WifiSsid configSsid = WifiSsid.fromString(config.SSID); + WifiSsid supplicantSsid = mSsidTranslator.getOriginalSsid(config); + if (supplicantSsid != null) { + supplicantConfig.SSID = supplicantSsid.toString(); + List allPossibleSsids = mSsidTranslator + .getAllPossibleOriginalSsids(configSsid); + WifiSsid selectedSsid = mSsidTranslator.getOriginalSsid(config); + allPossibleSsids.remove(selectedSsid); + if (!allPossibleSsids.isEmpty()) { + // Store the unused SSID to fallback on in + // connectToFallbackSsid(String) if the chosen SSID isn't found. + mCurrentNetworkFallbackSsids.put( + ifaceName, allPossibleSsids.get(0)); + } + Log.d(TAG, "Selecting supplicant SSID " + supplicantSsid); + supplicantConfig.SSID = supplicantSsid.toString(); + } + // Set the actual translation of the original SSID in case the untranslated + // SSID has an ambiguous encoding. + mSsidTranslator.setTranslatedSsidForStaIface(configSsid, ifaceName); + } + } + Pair pair = + addNetworkAndSaveConfig(ifaceName, supplicantConfig); + if (pair == null) { + Log.e(TAG, "Failed to add/save network configuration: " + config + .getProfileKey()); + return false; + } + mCurrentNetworkRemoteHandles.put(ifaceName, pair.first); + mCurrentNetworkLocalConfigs.put(ifaceName, pair.second); + } + + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure(ifaceName, "connectToNetwork"); + if (networkHandle == null) { + Log.e(TAG, "No valid remote network handle for network configuration: " + + config.getProfileKey()); + return false; + } + + SecurityParams params = config.getNetworkSelectionStatus() + .getCandidateSecurityParams(); + if (params != null && !(params.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK) + || params.isSecurityType(WifiConfiguration.SECURITY_TYPE_DPP))) { + List> pmkDataList = mPmkCacheManager.get(config.networkId); + if (pmkDataList != null) { + Log.i(TAG, "Set PMK cache for config id " + config.networkId); + pmkDataList.forEach(pmkData -> { + if (networkHandle.setPmkCache(NativeUtil.byteArrayFromArrayList(pmkData))) { + mWifiMetrics.setConnectionPmkCache(ifaceName, true); + } + }); + } + } + + if (!networkHandle.select()) { + Log.e(TAG, "Failed to select network configuration: " + config.getProfileKey()); + return false; + } + return true; + } + } + + /** + * Initiates roaming to the already configured network in wpa_supplicant. If the network + * configuration provided does not match the already configured network, then this triggers + * a new connection attempt (instead of roam). + * 1. First check if we're attempting to connect to a linked network, and select the existing + * supplicant network if there is one. + * 2. Set the new bssid for the network in wpa_supplicant. + * 3. Trigger reassociate command to wpa_supplicant. + * + * @param ifaceName Name of the interface. + * @param config WifiConfiguration parameters for the provided network. + * @return {@code true} if it succeeds, {@code false} otherwise + */ + public boolean roamToNetwork(@NonNull String ifaceName, WifiConfiguration config) { + synchronized (mLock) { + if (updateOnLinkedNetworkRoaming(ifaceName, config.networkId, true)) { + SupplicantStaNetworkHalAidlImpl networkHandle = + getCurrentNetworkRemoteHandle(ifaceName); + if (networkHandle == null) { + Log.e(TAG, "Roaming config matches a linked config, " + + "but a linked network handle was not found."); + return false; + } + return networkHandle.select(); + } + if (getCurrentNetworkId(ifaceName) != config.networkId) { + Log.w(TAG, "Cannot roam to a different network, initiate new connection. " + + "Current network ID: " + getCurrentNetworkId(ifaceName)); + return connectToNetwork(ifaceName, config); + } + String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID(); + Log.d(TAG, "roamToNetwork" + config.getProfileKey() + " (bssid " + bssid + ")"); + + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure(ifaceName, "roamToNetwork"); + if (networkHandle == null || !networkHandle.setBssid(bssid)) { + Log.e(TAG, "Failed to set new bssid on network: " + config.getProfileKey()); + return false; + } + if (!reassociate(ifaceName)) { + Log.e(TAG, "Failed to trigger reassociate"); + return false; + } + return true; + } + } + + /** + * Clean HAL cached data for |networkId| in the framework. + * + * @param networkId network id of the network to be removed from supplicant. + */ + public void removeNetworkCachedData(int networkId) { + synchronized (mLock) { + Log.d(TAG, "Remove cached HAL data for config id " + networkId); + removePmkCacheEntry(networkId); + } + } + + + /** + * Clear HAL cached data if MAC address is changed. + * + * @param networkId network id of the network to be checked. + * @param curMacAddress current MAC address + */ + public void removeNetworkCachedDataIfNeeded(int networkId, MacAddress curMacAddress) { + synchronized (mLock) { + mPmkCacheManager.remove(networkId, curMacAddress); + } + } + + /** + * Remove all networks from supplicant + * + * @param ifaceName Name of the interface. + */ + public boolean removeAllNetworks(@NonNull String ifaceName) { + synchronized (mLock) { + int[] networks = listNetworks(ifaceName); + if (networks == null) { + Log.e(TAG, "removeAllNetworks failed, got null networks"); + return false; + } + for (int id : networks) { + if (!removeNetwork(ifaceName, id)) { + Log.e(TAG, "removeAllNetworks failed to remove network: " + id); + return false; + } + } + // Reset current network info. + mCurrentNetworkRemoteHandles.remove(ifaceName); + mCurrentNetworkLocalConfigs.remove(ifaceName); + mLinkedNetworkLocalAndRemoteConfigs.remove(ifaceName); + return true; + } + } + + /** + * Disable the current network in supplicant + * + * @param ifaceName Name of the interface. + */ + public boolean disableCurrentNetwork(@NonNull String ifaceName) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure(ifaceName, "disableCurrentNetwork"); + if (networkHandle == null) { + return false; + } + return networkHandle.disable(); + } + } + + /** + * Set the currently configured network's bssid. + * + * @param ifaceName Name of the interface. + * @param bssidStr Bssid to set in the form of "XX:XX:XX:XX:XX:XX" + * @return true if succeeds, false otherwise. + */ + public boolean setCurrentNetworkBssid(@NonNull String ifaceName, String bssidStr) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure(ifaceName, "setCurrentNetworkBssid"); + if (networkHandle == null) { + return false; + } + return networkHandle.setBssid(bssidStr); + } + } + + /** + * Get the currently configured network's WPS NFC token. + * + * @param ifaceName Name of the interface. + * @return Hex string corresponding to the WPS NFC token. + */ + public String getCurrentNetworkWpsNfcConfigurationToken(@NonNull String ifaceName) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure( + ifaceName, "getCurrentNetworkWpsNfcConfigurationToken"); + if (networkHandle == null) { + return null; + } + return networkHandle.getWpsNfcConfigurationToken(); + } + } + + /** + * Get the eap anonymous identity for the currently configured network. + * + * @param ifaceName Name of the interface. + * @return anonymous identity string if succeeds, null otherwise. + */ + public String getCurrentNetworkEapAnonymousIdentity(@NonNull String ifaceName) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure( + ifaceName, "getCurrentNetworkEapAnonymousIdentity"); + if (networkHandle == null) { + return null; + } + return networkHandle.fetchEapAnonymousIdentity(); + } + } + + /** + * Send the eap identity response for the currently configured network. + * + * @param ifaceName Name of the interface. + * @param identity identity used for EAP-Identity + * @param encryptedIdentity encrypted identity used for EAP-AKA/EAP-SIM + * @return true if succeeds, false otherwise. + */ + public boolean sendCurrentNetworkEapIdentityResponse( + @NonNull String ifaceName, @NonNull String identity, String encryptedIdentity) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure( + ifaceName, "sendCurrentNetworkEapIdentityResponse"); + if (networkHandle == null) { + return false; + } + return networkHandle.sendNetworkEapIdentityResponse(identity, encryptedIdentity); + } + } + + /** + * Send the eap sim gsm auth response for the currently configured network. + * + * @param ifaceName Name of the interface. + * @param paramsStr String to send. + * @return true if succeeds, false otherwise. + */ + public boolean sendCurrentNetworkEapSimGsmAuthResponse( + @NonNull String ifaceName, String paramsStr) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure( + ifaceName, "sendCurrentNetworkEapSimGsmAuthResponse"); + if (networkHandle == null) { + return false; + } + return networkHandle.sendNetworkEapSimGsmAuthResponse(paramsStr); + } + } + + /** + * Send the eap sim gsm auth failure for the currently configured network. + * + * @param ifaceName Name of the interface. + * @return true if succeeds, false otherwise. + */ + public boolean sendCurrentNetworkEapSimGsmAuthFailure(@NonNull String ifaceName) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure( + ifaceName, "sendCurrentNetworkEapSimGsmAuthFailure"); + if (networkHandle == null) { + return false; + } + return networkHandle.sendNetworkEapSimGsmAuthFailure(); + } + } + + /** + * Send the eap sim umts auth response for the currently configured network. + * + * @param ifaceName Name of the interface. + * @param paramsStr String to send. + * @return true if succeeds, false otherwise. + */ + public boolean sendCurrentNetworkEapSimUmtsAuthResponse( + @NonNull String ifaceName, String paramsStr) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure( + ifaceName, "sendCurrentNetworkEapSimUmtsAuthResponse"); + if (networkHandle == null) { + return false; + } + return networkHandle.sendNetworkEapSimUmtsAuthResponse(paramsStr); + } + } + + /** + * Send the eap sim umts auts response for the currently configured network. + * + * @param ifaceName Name of the interface. + * @param paramsStr String to send. + * @return true if succeeds, false otherwise. + */ + public boolean sendCurrentNetworkEapSimUmtsAutsResponse( + @NonNull String ifaceName, String paramsStr) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure( + ifaceName, "sendCurrentNetworkEapSimUmtsAutsResponse"); + if (networkHandle == null) { + return false; + } + return networkHandle.sendNetworkEapSimUmtsAutsResponse(paramsStr); + } + } + + /** + * Send the eap sim umts auth failure for the currently configured network. + * + * @param ifaceName Name of the interface. + * @return true if succeeds, false otherwise. + */ + public boolean sendCurrentNetworkEapSimUmtsAuthFailure(@NonNull String ifaceName) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure( + ifaceName, "sendCurrentNetworkEapSimUmtsAuthFailure"); + if (networkHandle == null) { + return false; + } + return networkHandle.sendNetworkEapSimUmtsAuthFailure(); + } + } + + /** + * Adds a new network. + * + * @return SupplicantStaNetworkHalAidlImpl object for the new network, or null if the call fails + */ + private SupplicantStaNetworkHalAidlImpl addNetwork(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "addNetwork"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return null; + } + try { + ISupplicantStaNetwork network = iface.addNetwork(); + // Get framework wrapper around the AIDL network object + return getStaNetworkHalMockable(ifaceName, network); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return null; + } + } + + /** + * Remove network with specified network Id from supplicant. + * + * @return true if request is sent successfully, false otherwise. + */ + private boolean removeNetwork(@NonNull String ifaceName, int id) { + synchronized (mLock) { + final String methodStr = "removeNetwork"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.removeNetwork(id); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Creates a SupplicantStaNetworkHal wrapper around an AIDL ISupplicantStaNetwork object. + * Declared mockable for use in unit tests. + * + * @param ifaceName Name of the interface. + * @param network ISupplicantStaNetwork instance retrieved from AIDL. + * @return SupplicantStaNetworkHal object for the given network, or null if + * the call fails + */ + protected SupplicantStaNetworkHalAidlImpl getStaNetworkHalMockable( + @NonNull String ifaceName, ISupplicantStaNetwork network) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkWrapper = + new SupplicantStaNetworkHalAidlImpl(mServiceVersion, + network, ifaceName, mContext, + mWifiMonitor, mWifiGlobals, + getAdvancedCapabilities(ifaceName), + getWpaDriverFeatureSet(ifaceName)); + if (networkWrapper != null) { + networkWrapper.enableVerboseLogging( + mVerboseLoggingEnabled, mVerboseHalLoggingEnabled); + } + return networkWrapper; + } + } + + private boolean registerCallback( + ISupplicantStaIface iface, ISupplicantStaIfaceCallback callback) { + synchronized (mLock) { + String methodStr = "registerCallback"; + if (iface == null) { + return false; + } + try { + iface.registerCallback(callback); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Get list of id's of all networks controlled by supplicant. + * + * @return list of network id's, null if failed + */ + private int[] listNetworks(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "listNetworks"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return null; + } + try { + return iface.listNetworks(); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return null; + } + } + + /** + * Set WPS device name. + * + * @param ifaceName Name of the interface. + * @param name String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setWpsDeviceName(@NonNull String ifaceName, String name) { + synchronized (mLock) { + final String methodStr = "setWpsDeviceName"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setWpsDeviceName(name); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set WPS device type. + * + * @param ifaceName Name of the interface. + * @param typeStr Type specified as a string. Used format: -- + * @return true if request is sent successfully, false otherwise. + */ + public boolean setWpsDeviceType(@NonNull String ifaceName, String typeStr) { + synchronized (mLock) { + try { + Matcher match = WPS_DEVICE_TYPE_PATTERN.matcher(typeStr); + if (!match.find() || match.groupCount() != 3) { + Log.e(TAG, "Malformed WPS device type " + typeStr); + return false; + } + short categ = Short.parseShort(match.group(1)); + byte[] oui = NativeUtil.hexStringToByteArray(match.group(2)); + short subCateg = Short.parseShort(match.group(3)); + + byte[] bytes = new byte[8]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + byteBuffer.putShort(categ); + byteBuffer.put(oui); + byteBuffer.putShort(subCateg); + return setWpsDeviceType(ifaceName, bytes); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + typeStr, e); + return false; + } + } + } + + private boolean setWpsDeviceType(@NonNull String ifaceName, byte[/* 8 */] type) { + synchronized (mLock) { + final String methodStr = "setWpsDeviceType"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setWpsDeviceType(type); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set WPS manufacturer. + * + * @param ifaceName Name of the interface. + * @param manufacturer String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setWpsManufacturer(@NonNull String ifaceName, String manufacturer) { + synchronized (mLock) { + final String methodStr = "setWpsManufacturer"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setWpsManufacturer(manufacturer); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set WPS model name. + * + * @param ifaceName Name of the interface. + * @param modelName String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setWpsModelName(@NonNull String ifaceName, String modelName) { + synchronized (mLock) { + final String methodStr = "setWpsModelName"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setWpsModelName(modelName); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set WPS model number. + * + * @param ifaceName Name of the interface. + * @param modelNumber String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setWpsModelNumber(@NonNull String ifaceName, String modelNumber) { + synchronized (mLock) { + final String methodStr = "setWpsModelNumber"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setWpsModelNumber(modelNumber); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set WPS serial number. + * + * @param ifaceName Name of the interface. + * @param serialNumber String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setWpsSerialNumber(@NonNull String ifaceName, String serialNumber) { + synchronized (mLock) { + final String methodStr = "setWpsSerialNumber"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setWpsSerialNumber(serialNumber); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set WPS config methods + * + * @param ifaceName Name of the interface. + * @param configMethodsStr List of config methods. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setWpsConfigMethods(@NonNull String ifaceName, String configMethodsStr) { + synchronized (mLock) { + int configMethodsMask = 0; + String[] configMethodsStrArr = configMethodsStr.split("\\s+"); + for (int i = 0; i < configMethodsStrArr.length; i++) { + configMethodsMask |= stringToWpsConfigMethod(configMethodsStrArr[i]); + } + return setWpsConfigMethods(ifaceName, configMethodsMask); + } + } + + private boolean setWpsConfigMethods(@NonNull String ifaceName, int configMethods) { + synchronized (mLock) { + final String methodStr = "setWpsConfigMethods"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setWpsConfigMethods(configMethods); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Trigger a reassociation even if the iface is currently connected. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean reassociate(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "reassociate"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.reassociate(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Trigger a reconnection if the iface is disconnected. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean reconnect(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "reconnect"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.reconnect(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + + /** + * Trigger a disconnection from the currently connected network. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean disconnect(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "disconnect"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.disconnect(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Enable or disable power save mode. + * + * @param ifaceName Name of the interface. + * @param enable true to enable, false to disable. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setPowerSave(@NonNull String ifaceName, boolean enable) { + synchronized (mLock) { + final String methodStr = "setPowerSave"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setPowerSave(enable); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Initiate TDLS discover with the specified AP. + * + * @param ifaceName Name of the interface. + * @param macAddress MAC Address of the AP. + * @return true if request is sent successfully, false otherwise. + */ + public boolean initiateTdlsDiscover(@NonNull String ifaceName, String macAddress) { + synchronized (mLock) { + try { + return initiateTdlsDiscover( + ifaceName, NativeUtil.macAddressToByteArray(macAddress)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + macAddress, e); + return false; + } + } + } + + private boolean initiateTdlsDiscover(@NonNull String ifaceName, byte[/* 6 */] macAddress) { + synchronized (mLock) { + final String methodStr = "initiateTdlsDiscover"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.initiateTdlsDiscover(macAddress); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Initiate TDLS setup with the specified AP. + * + * @param ifaceName Name of the interface. + * @param macAddress MAC Address of the AP. + * @return true if request is sent successfully, false otherwise. + */ + public boolean initiateTdlsSetup(@NonNull String ifaceName, String macAddress) { + synchronized (mLock) { + try { + return initiateTdlsSetup(ifaceName, NativeUtil.macAddressToByteArray(macAddress)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + macAddress, e); + return false; + } + } + } + + private boolean initiateTdlsSetup(@NonNull String ifaceName, byte[/* 6 */] macAddress) { + synchronized (mLock) { + final String methodStr = "initiateTdlsSetup"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.initiateTdlsSetup(macAddress); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Initiate TDLS teardown with the specified AP. + * + * @param ifaceName Name of the interface. + * @param macAddress MAC Address of the AP. + * @return true if request is sent successfully, false otherwise. + */ + public boolean initiateTdlsTeardown(@NonNull String ifaceName, String macAddress) { + synchronized (mLock) { + try { + return initiateTdlsTeardown( + ifaceName, NativeUtil.macAddressToByteArray(macAddress)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + macAddress, e); + return false; + } + } + } + + private boolean initiateTdlsTeardown(@NonNull String ifaceName, byte[/* 6 */] macAddress) { + synchronized (mLock) { + final String methodStr = "initiateTdlsTeardown"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.initiateTdlsTeardown(macAddress); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Request the specified ANQP elements |elements| from the specified AP |bssid|. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the AP + * @param infoElements ANQP elements to be queried. Refer to ISupplicantStaIface.AnqpInfoId. + * @param hs20SubTypes HS subtypes to be queried. Refer to ISupplicantStaIface.Hs20AnqpSubTypes. + * @return true if request is sent successfully, false otherwise. + */ + public boolean initiateAnqpQuery(@NonNull String ifaceName, String bssid, + ArrayList infoElements, + ArrayList hs20SubTypes) { + synchronized (mLock) { + try { + int[] infoElementsCast = new int[infoElements.size()]; + int[] hs20SubTypesCast = new int[hs20SubTypes.size()]; + for (int i = 0; i < infoElements.size(); i++) { + infoElementsCast[i] = infoElements.get(i); + } + for (int i = 0; i < hs20SubTypes.size(); i++) { + hs20SubTypesCast[i] = hs20SubTypes.get(i); + } + return initiateAnqpQuery( + ifaceName, + NativeUtil.macAddressToByteArray(bssid), + infoElementsCast, hs20SubTypesCast); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + bssid, e); + return false; + } + } + } + + private boolean initiateAnqpQuery(@NonNull String ifaceName, byte[/* 6 */] macAddress, + int[] infoElements, int[] subTypes) { + synchronized (mLock) { + final String methodStr = "initiateAnqpQuery"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.initiateAnqpQuery(macAddress, infoElements, subTypes); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Request Venue URL ANQP element from the specified AP |bssid|. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the AP + * @return true if request is sent successfully, false otherwise. + */ + public boolean initiateVenueUrlAnqpQuery(@NonNull String ifaceName, String bssid) { + synchronized (mLock) { + try { + return initiateVenueUrlAnqpQuery( + ifaceName, NativeUtil.macAddressToByteArray(bssid)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + bssid, e); + return false; + } + } + } + + private boolean initiateVenueUrlAnqpQuery(@NonNull String ifaceName, byte[/* 6 */] macAddress) { + synchronized (mLock) { + final String methodStr = "initiateVenueUrlAnqpQuery"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.initiateVenueUrlAnqpQuery(macAddress); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Request the specified ANQP ICON from the specified AP |bssid|. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the AP + * @param fileName Name of the file to request. + * @return true if request is sent successfully, false otherwise. + */ + public boolean initiateHs20IconQuery(@NonNull String ifaceName, String bssid, String fileName) { + synchronized (mLock) { + try { + return initiateHs20IconQuery( + ifaceName, NativeUtil.macAddressToByteArray(bssid), fileName); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + bssid, e); + return false; + } + } + } + + private boolean initiateHs20IconQuery(@NonNull String ifaceName, + byte[/* 6 */] macAddress, String fileName) { + synchronized (mLock) { + final String methodStr = "initiateHs20IconQuery"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.initiateHs20IconQuery(macAddress, fileName); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Gets MAC Address from the supplicant. + * + * @param ifaceName Name of the interface. + * @return string containing the MAC address, or null on a failed call + */ + public String getMacAddress(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "getMacAddress"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return null; + } + try { + byte[] macAddr = iface.getMacAddress(); + return NativeUtil.macAddressFromByteArray(macAddr); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid MAC address value", e); + } + return null; + } + } + + /** + * Start using the added RX filters. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean startRxFilter(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "startRxFilter"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.startRxFilter(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Stop using the added RX filters. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean stopRxFilter(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "stopRxFilter"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.stopRxFilter(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Add an RX filter. + * + * @param ifaceName Name of the interface. + * @param type one of {@link WifiNative#RX_FILTER_TYPE_V4_MULTICAST} + * {@link WifiNative#RX_FILTER_TYPE_V6_MULTICAST} values. + * @return true if request is sent successfully, false otherwise. + */ + public boolean addRxFilter(@NonNull String ifaceName, int type) { + synchronized (mLock) { + byte halType; + switch (type) { + case WifiNative.RX_FILTER_TYPE_V4_MULTICAST: + halType = RxFilterType.V4_MULTICAST; + break; + case WifiNative.RX_FILTER_TYPE_V6_MULTICAST: + halType = RxFilterType.V6_MULTICAST; + break; + default: + Log.e(TAG, "Invalid Rx Filter type: " + type); + return false; + } + return addRxFilter(ifaceName, halType); + } + } + + private boolean addRxFilter(@NonNull String ifaceName, byte type) { + synchronized (mLock) { + final String methodStr = "addRxFilter"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.addRxFilter(type); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Remove an RX filter. + * + * @param ifaceName Name of the interface. + * @param type one of {@link WifiNative#RX_FILTER_TYPE_V4_MULTICAST} + * {@link WifiNative#RX_FILTER_TYPE_V6_MULTICAST} values. + * @return true if request is sent successfully, false otherwise. + */ + public boolean removeRxFilter(@NonNull String ifaceName, int type) { + synchronized (mLock) { + byte halType; + switch (type) { + case WifiNative.RX_FILTER_TYPE_V4_MULTICAST: + halType = RxFilterType.V4_MULTICAST; + break; + case WifiNative.RX_FILTER_TYPE_V6_MULTICAST: + halType = RxFilterType.V6_MULTICAST; + break; + default: + Log.e(TAG, "Invalid Rx Filter type: " + type); + return false; + } + return removeRxFilter(ifaceName, halType); + } + } + + private boolean removeRxFilter(@NonNull String ifaceName, byte type) { + synchronized (mLock) { + final String methodStr = "removeRxFilter"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.removeRxFilter(type); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set Bt co existense mode. + * + * @param ifaceName Name of the interface. + * @param mode one of the above {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_DISABLED}, + * {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_ENABLED} or + * {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_SENSE}. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setBtCoexistenceMode(@NonNull String ifaceName, int mode) { + synchronized (mLock) { + byte halMode; + switch (mode) { + case WifiNative.BLUETOOTH_COEXISTENCE_MODE_ENABLED: + halMode = BtCoexistenceMode.ENABLED; + break; + case WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED: + halMode = BtCoexistenceMode.DISABLED; + break; + case WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE: + halMode = BtCoexistenceMode.SENSE; + break; + default: + Log.e(TAG, "Invalid Bt Coex mode: " + mode); + return false; + } + return setBtCoexistenceMode(ifaceName, halMode); + } + } + + private boolean setBtCoexistenceMode(@NonNull String ifaceName, byte mode) { + synchronized (mLock) { + final String methodStr = "setBtCoexistenceMode"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setBtCoexistenceMode(mode); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** Enable or disable BT coexistence mode. + * + * @param ifaceName Name of the interface. + * @param enable true to enable, false to disable. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setBtCoexistenceScanModeEnabled(@NonNull String ifaceName, boolean enable) { + synchronized (mLock) { + final String methodStr = "setBtCoexistenceScanModeEnabled"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setBtCoexistenceScanModeEnabled(enable); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Enable or disable suspend mode optimizations. + * + * @param ifaceName Name of the interface. + * @param enable true to enable, false otherwise. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setSuspendModeEnabled(@NonNull String ifaceName, boolean enable) { + synchronized (mLock) { + final String methodStr = "setSuspendModeEnabled"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setSuspendModeEnabled(enable); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set country code. + * + * @param ifaceName Name of the interface. + * @param codeStr 2 byte ASCII string. For ex: US, CA. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setCountryCode(@NonNull String ifaceName, String codeStr) { + synchronized (mLock) { + if (TextUtils.isEmpty(codeStr)) { + return false; + } + byte[] countryCodeBytes = NativeUtil.stringToByteArray(codeStr); + if (countryCodeBytes.length != 2) { + return false; + } + return setCountryCode(ifaceName, countryCodeBytes); + } + } + + private boolean setCountryCode(@NonNull String ifaceName, byte[/* 2 */] code) { + synchronized (mLock) { + final String methodStr = "setCountryCode"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setCountryCode(code); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Flush all previously configured HLPs. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean flushAllHlp(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "filsHlpFlushRequest"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.filsHlpFlushRequest(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set FILS HLP packet. + * + * @param ifaceName Name of the interface. + * @param dst Destination MAC address. + * @param hlpPacket Hlp Packet data in hex. + * @return true if request is sent successfully, false otherwise. + */ + public boolean addHlpReq(@NonNull String ifaceName, byte[] dst, byte[] hlpPacket) { + synchronized (mLock) { + final String methodStr = "filsHlpAddRequest"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.filsHlpAddRequest(dst, hlpPacket); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Start WPS pin registrar operation with the specified peer and pin. + * + * @param ifaceName Name of the interface. + * @param bssidStr BSSID of the peer. + * @param pin Pin to be used. + * @return true if request is sent successfully, false otherwise. + */ + public boolean startWpsRegistrar(@NonNull String ifaceName, String bssidStr, String pin) { + synchronized (mLock) { + if (TextUtils.isEmpty(bssidStr) || TextUtils.isEmpty(pin)) { + return false; + } + try { + return startWpsRegistrar( + ifaceName, NativeUtil.macAddressToByteArray(bssidStr), pin); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + bssidStr, e); + return false; + } + } + } + + private boolean startWpsRegistrar(@NonNull String ifaceName, byte[/* 6 */] bssid, String pin) { + synchronized (mLock) { + final String methodStr = "startWpsRegistrar"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.startWpsRegistrar(bssid, pin); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Start WPS pin display operation with the specified peer. + * + * @param ifaceName Name of the interface. + * @param bssidStr BSSID of the peer. Use empty bssid to indicate wildcard. + * @return true if request is sent successfully, false otherwise. + */ + public boolean startWpsPbc(@NonNull String ifaceName, String bssidStr) { + synchronized (mLock) { + try { + return startWpsPbc(ifaceName, NativeUtil.macAddressToByteArray(bssidStr)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + bssidStr, e); + return false; + } + } + } + + private boolean startWpsPbc(@NonNull String ifaceName, byte[/* 6 */] bssid) { + synchronized (mLock) { + final String methodStr = "startWpsPbc"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.startWpsPbc(bssid); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Start WPS pin keypad operation with the specified pin. + * + * @param ifaceName Name of the interface. + * @param pin Pin to be used. + * @return true if request is sent successfully, false otherwise. + */ + public boolean startWpsPinKeypad(@NonNull String ifaceName, String pin) { + if (TextUtils.isEmpty(pin)) { + return false; + } + synchronized (mLock) { + final String methodStr = "startWpsPinKeypad"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.startWpsPinKeypad(pin); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Start WPS pin display operation with the specified peer. + * + * @param ifaceName Name of the interface. + * @param bssidStr BSSID of the peer. Use empty bssid to indicate wildcard. + * @return new pin generated on success, null otherwise. + */ + public String startWpsPinDisplay(@NonNull String ifaceName, String bssidStr) { + synchronized (mLock) { + try { + return startWpsPinDisplay(ifaceName, NativeUtil.macAddressToByteArray(bssidStr)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + bssidStr, e); + return null; + } + } + } + + private String startWpsPinDisplay(@NonNull String ifaceName, byte[/* 6 */] bssid) { + synchronized (mLock) { + final String methodStr = "startWpsPinDisplay"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return null; + } + try { + return iface.startWpsPinDisplay(bssid); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return null; + } + } + + /** + * Cancels any ongoing WPS requests. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean cancelWps(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "cancelWps"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.cancelWps(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Sets whether to use external sim for SIM/USIM processing. + * + * @param ifaceName Name of the interface. + * @param useExternalSim true to enable, false otherwise. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setExternalSim(@NonNull String ifaceName, boolean useExternalSim) { + synchronized (mLock) { + final String methodStr = "setExternalSim"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setExternalSim(useExternalSim); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Enable/Disable auto reconnect to networks. + * Use this to prevent wpa_supplicant from trying to connect to networks + * on its own. + * + * @param enable true to enable, false to disable. + * @return true if no exceptions occurred, false otherwise + */ + public boolean enableAutoReconnect(@NonNull String ifaceName, boolean enable) { + synchronized (mLock) { + final String methodStr = "enableAutoReconnect"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.enableAutoReconnect(enable); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set the debug log level for wpa_supplicant + * + * @param turnOnVerbose Whether to turn on verbose logging or not. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setLogLevel(boolean turnOnVerbose) { + synchronized (mLock) { + int logLevel = turnOnVerbose + ? DebugLevel.DEBUG + : DebugLevel.INFO; + return setDebugParams(logLevel, false, + turnOnVerbose && mWifiGlobals.getShowKeyVerboseLoggingModeEnabled()); + } + } + + /** + * Set debug parameters for the ISupplicant service. + * + * @param level Debug logging level for the supplicant. + * (one of |DebugLevel| values). + * @param showTimestamp Determines whether to show timestamps in logs or not. + * @param showKeys Determines whether to show keys in debug logs or not. + * CAUTION: Do not set this param in production code! + * @return true if no exceptions occurred, false otherwise + */ + private boolean setDebugParams(int level, boolean showTimestamp, boolean showKeys) { + synchronized (mLock) { + final String methodStr = "setDebugParams"; + if (!checkSupplicantAndLogFailure(methodStr)) { + return false; + } + try { + mISupplicant.setDebugParams(level, showTimestamp, showKeys); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set concurrency priority between P2P & STA operations. + * + * @param isStaHigherPriority Set to true to prefer STA over P2P during concurrency operations, + * false otherwise. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setConcurrencyPriority(boolean isStaHigherPriority) { + synchronized (mLock) { + if (isStaHigherPriority) { + return setConcurrencyPriority(IfaceType.STA); + } else { + return setConcurrencyPriority(IfaceType.P2P); + } + } + } + + private boolean setConcurrencyPriority(int type) { + synchronized (mLock) { + final String methodStr = "setConcurrencyPriority"; + if (!checkSupplicantAndLogFailure(methodStr)) { + return false; + } + try { + mISupplicant.setConcurrencyPriority(type); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Returns false if mISupplicant is null and logs failure message + */ + private boolean checkSupplicantAndLogFailure(final String methodStr) { + synchronized (mLock) { + if (mISupplicant == null) { + Log.e(TAG, "Can't call " + methodStr + ", ISupplicant is null"); + return false; + } + return true; + } + } + + /** + * Returns specified STA iface if it exists. Otherwise, logs error and returns null. + */ + private ISupplicantStaIface checkStaIfaceAndLogFailure( + @NonNull String ifaceName, final String methodStr) { + synchronized (mLock) { + ISupplicantStaIface iface = getStaIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaIface is null for " + + "iface=" + ifaceName); + return null; + } + return iface; + } + } + + /** + * Returns network belonging to the specified STA iface if it exists. + * Otherwise, logs error and returns null. + */ + private SupplicantStaNetworkHalAidlImpl checkStaNetworkAndLogFailure( + @NonNull String ifaceName, final String methodStr) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + getCurrentNetworkRemoteHandle(ifaceName); + if (networkHandle == null) { + Log.e(TAG, "Can't call " + methodStr + ", SupplicantStaNetwork for iface=" + + ifaceName + " is null."); + return null; + } + return networkHandle; + } + } + + /** + * Helper function to log callback events + */ + protected void logCallback(final String methodStr) { + synchronized (mLock) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "ISupplicantStaIfaceCallback." + methodStr + " received"); + } + } + } + + private void handleRemoteException(RemoteException e, String methodStr) { + synchronized (mLock) { + clearState(); + Log.e(TAG, + "ISupplicantStaIface." + methodStr + " failed with remote exception: ", e); + } + } + + private void handleServiceSpecificException(ServiceSpecificException e, String methodStr) { + synchronized (mLock) { + Log.e(TAG, "ISupplicantStaIface." + methodStr + " failed with " + + "service specific exception: ", e); + } + } + + /** + * Converts the Wps config method string to the equivalent enum value. + */ + private static int stringToWpsConfigMethod(String configMethod) { + switch (configMethod) { + case "usba": + return WpsConfigMethods.USBA; + case "ethernet": + return WpsConfigMethods.ETHERNET; + case "label": + return WpsConfigMethods.LABEL; + case "display": + return WpsConfigMethods.DISPLAY; + case "int_nfc_token": + return WpsConfigMethods.INT_NFC_TOKEN; + case "ext_nfc_token": + return WpsConfigMethods.EXT_NFC_TOKEN; + case "nfc_interface": + return WpsConfigMethods.NFC_INTERFACE; + case "push_button": + return WpsConfigMethods.PUSHBUTTON; + case "keypad": + return WpsConfigMethods.KEYPAD; + case "virtual_push_button": + return WpsConfigMethods.VIRT_PUSHBUTTON; + case "physical_push_button": + return WpsConfigMethods.PHY_PUSHBUTTON; + case "p2ps": + return WpsConfigMethods.P2PS; + case "virtual_display": + return WpsConfigMethods.VIRT_DISPLAY; + case "physical_display": + return WpsConfigMethods.PHY_DISPLAY; + default: + throw new IllegalArgumentException( + "Invalid WPS config method: " + configMethod); + } + } + + protected void addPmkCacheEntry(String ifaceName, int networkId, byte[/* 6 */] bssid, + long expirationTimeInSec, ArrayList serializedEntry) { + synchronized (mLock) { + String macAddressStr = getMacAddress(ifaceName); + try { + MacAddress bssAddr = bssid != null ? MacAddress.fromBytes(bssid) : null; + if (!mPmkCacheManager.add(MacAddress.fromString(macAddressStr), networkId, + bssAddr, expirationTimeInSec, serializedEntry)) { + Log.w(TAG, "Cannot add PMK cache for " + ifaceName); + } + } catch (IllegalArgumentException ex) { + Log.w(TAG, "Cannot add PMK cache: " + ex); + } + } + } + + protected void removePmkCacheEntry(int networkId) { + synchronized (mLock) { + mPmkCacheManager.remove(networkId); + } + } + + /** + * Returns a bitmask of advanced capabilities: WPA3 SAE/SUITE B and OWE + * Bitmask used is: + * - WIFI_FEATURE_WPA3_SAE + * - WIFI_FEATURE_WPA3_SUITE_B + * - WIFI_FEATURE_OWE + * + * @return true if successful, false otherwise. + */ + public long getAdvancedCapabilities(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "getAdvancedCapabilities"; + long advancedCapabilities = 0; + int keyMgmtCapabilities = getKeyMgmtCapabilities(ifaceName); + + advancedCapabilities |= WIFI_FEATURE_PASSPOINT_TERMS_AND_CONDITIONS + | WIFI_FEATURE_DECORATED_IDENTITY; + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": Passpoint T&C supported"); + Log.v(TAG, methodStr + ": RFC 7542 decorated identity supported"); + } + + if ((keyMgmtCapabilities & KeyMgmtMask.SAE) != 0) { + advancedCapabilities |= WIFI_FEATURE_WPA3_SAE; + + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": SAE supported"); + } + } + + if ((keyMgmtCapabilities & KeyMgmtMask.SUITE_B_192) != 0) { + advancedCapabilities |= WIFI_FEATURE_WPA3_SUITE_B; + + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": SUITE_B supported"); + } + } + + if ((keyMgmtCapabilities & KeyMgmtMask.OWE) != 0) { + advancedCapabilities |= WIFI_FEATURE_OWE; + + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": OWE supported"); + } + } + + if ((keyMgmtCapabilities & KeyMgmtMask.DPP) != 0) { + advancedCapabilities |= WIFI_FEATURE_DPP + | WIFI_FEATURE_DPP_ENROLLEE_RESPONDER; + + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": DPP supported"); + Log.v(TAG, methodStr + ": DPP ENROLLEE RESPONDER supported"); + } + } + + if ((keyMgmtCapabilities & KeyMgmtMask.WAPI_PSK) != 0) { + advancedCapabilities |= WIFI_FEATURE_WAPI; + + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": WAPI supported"); + } + } + + if ((keyMgmtCapabilities & KeyMgmtMask.FILS_SHA256) != 0) { + advancedCapabilities |= WIFI_FEATURE_FILS_SHA256; + + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": FILS_SHA256 supported"); + } + } + + if ((keyMgmtCapabilities & KeyMgmtMask.FILS_SHA384) != 0) { + advancedCapabilities |= WIFI_FEATURE_FILS_SHA384; + + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": FILS_SHA384 supported"); + } + } + + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": Capability flags = " + keyMgmtCapabilities); + } + + return advancedCapabilities; + } + } + + /** + * Get the bitmask of supplicant/driver supported key management capabilities in + * AIDL KeyMgmtMask format. + */ + private int getKeyMgmtCapabilities(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "getKeyMgmtCapabilities"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return 0; + } + try { + return iface.getKeyMgmtCapabilities(); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return 0; + } + } + + private long aidlWpaDrvFeatureSetToFrameworkV2(int drvCapabilitiesMask) { + if (!isServiceVersionAtLeast(2)) return 0; + + final String methodStr = "getWpaDriverFeatureSetV2"; + long featureSet = 0; + + if ((drvCapabilitiesMask & WpaDriverCapabilitiesMask.SET_TLS_MINIMUM_VERSION) != 0) { + featureSet |= WIFI_FEATURE_SET_TLS_MINIMUM_VERSION; + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": EAP-TLS minimum version supported"); + } + } + + if ((drvCapabilitiesMask & WpaDriverCapabilitiesMask.TLS_V1_3) != 0) { + featureSet |= WIFI_FEATURE_TLS_V1_3; + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": EAP-TLS v1.3 supported"); + } + } + return featureSet; + } + + /** + * Get the driver supported features through supplicant. + * + * @param ifaceName Name of the interface. + * @return bitmask defined by WifiManager.WIFI_FEATURE_*. + */ + public long getWpaDriverFeatureSet(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "getWpaDriverFeatureSet"; + int drvCapabilitiesMask = getWpaDriverCapabilities(ifaceName); + long featureSet = 0; + + if ((drvCapabilitiesMask & WpaDriverCapabilitiesMask.MBO) != 0) { + featureSet |= WIFI_FEATURE_MBO; + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": MBO supported"); + } + if ((drvCapabilitiesMask & WpaDriverCapabilitiesMask.OCE) != 0) { + featureSet |= WIFI_FEATURE_OCE; + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": OCE supported"); + } + } + } + + if ((drvCapabilitiesMask & WpaDriverCapabilitiesMask.SAE_PK) != 0) { + featureSet |= WIFI_FEATURE_SAE_PK; + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": SAE-PK supported"); + } + } + + if ((drvCapabilitiesMask & WpaDriverCapabilitiesMask.WFD_R2) != 0) { + featureSet |= WIFI_FEATURE_WFD_R2; + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": WFD-R2 supported"); + } + } + + if ((drvCapabilitiesMask + & WpaDriverCapabilitiesMask.TRUST_ON_FIRST_USE) != 0) { + featureSet |= WIFI_FEATURE_TRUST_ON_FIRST_USE; + if (mVerboseLoggingEnabled) { + Log.v(TAG, methodStr + ": Trust-On-First-Use supported"); + } + } + + featureSet |= aidlWpaDrvFeatureSetToFrameworkV2(drvCapabilitiesMask); + + return featureSet; + } + } + + /** + * Get the bitmask of supplicant/driver supported features in + * AIDL WpaDriverCapabilitiesMask format. + */ + private int getWpaDriverCapabilities(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "getWpaDriverCapabilities"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return 0; + } + try { + return iface.getWpaDriverCapabilities(); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return 0; + } + } + + private @WifiAnnotations.WifiStandard int getWifiStandard(int technology) { + switch(technology) { + case WifiTechnology.EHT: + return ScanResult.WIFI_STANDARD_11BE; + case WifiTechnology.HE: + return ScanResult.WIFI_STANDARD_11AX; + case WifiTechnology.VHT: + return ScanResult.WIFI_STANDARD_11AC; + case WifiTechnology.HT: + return ScanResult.WIFI_STANDARD_11N; + case WifiTechnology.LEGACY: + return ScanResult.WIFI_STANDARD_LEGACY; + default: + return ScanResult.WIFI_STANDARD_UNKNOWN; + } + } + + private int getChannelBandwidth(int channelBandwidth) { + switch(channelBandwidth) { + case WifiChannelWidthInMhz.WIDTH_20: + return ScanResult.CHANNEL_WIDTH_20MHZ; + case WifiChannelWidthInMhz.WIDTH_40: + return ScanResult.CHANNEL_WIDTH_40MHZ; + case WifiChannelWidthInMhz.WIDTH_80: + return ScanResult.CHANNEL_WIDTH_80MHZ; + case WifiChannelWidthInMhz.WIDTH_160: + return ScanResult.CHANNEL_WIDTH_160MHZ; + case WifiChannelWidthInMhz.WIDTH_80P80: + return ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ; + case WifiChannelWidthInMhz.WIDTH_320: + return ScanResult.CHANNEL_WIDTH_320MHZ; + default: + return ScanResult.CHANNEL_WIDTH_20MHZ; + } + } + + private int frameworkToAidlDppAkm(int dppAkm) { + switch(dppAkm) { + case SupplicantStaIfaceHal.DppAkm.PSK: + return DppAkm.PSK; + case SupplicantStaIfaceHal.DppAkm.PSK_SAE: + return DppAkm.PSK_SAE; + case SupplicantStaIfaceHal.DppAkm.SAE: + return DppAkm.SAE; + case SupplicantStaIfaceHal.DppAkm.DPP: + return DppAkm.DPP; + default: + Log.e(TAG, "Invalid DppAkm received"); + return -1; + } + } + + private int frameworkToAidlDppCurve(int dppCurve) { + switch(dppCurve) { + case SupplicantStaIfaceHal.DppCurve.PRIME256V1: + return DppCurve.PRIME256V1; + case SupplicantStaIfaceHal.DppCurve.SECP384R1: + return DppCurve.SECP384R1; + case SupplicantStaIfaceHal.DppCurve.SECP521R1: + return DppCurve.SECP521R1; + case SupplicantStaIfaceHal.DppCurve.BRAINPOOLP256R1: + return DppCurve.BRAINPOOLP256R1; + case SupplicantStaIfaceHal.DppCurve.BRAINPOOLP384R1: + return DppCurve.BRAINPOOLP384R1; + case SupplicantStaIfaceHal.DppCurve.BRAINPOOLP512R1: + return DppCurve.BRAINPOOLP512R1; + default: + Log.e(TAG, "Invalid DppCurve received"); + return -1; + } + } + + private int frameworkToAidlDppNetRole(int dppNetRole) { + switch(dppNetRole) { + case SupplicantStaIfaceHal.DppNetRole.STA: + return DppNetRole.STA; + case SupplicantStaIfaceHal.DppNetRole.AP: + return DppNetRole.AP; + default: + Log.e(TAG, "Invalid DppNetRole received"); + return -1; + } + } + + protected byte dscpPolicyToAidlQosPolicyStatusCode(int status) { + switch (status) { + case NetworkAgent.DSCP_POLICY_STATUS_SUCCESS: + case NetworkAgent.DSCP_POLICY_STATUS_DELETED: + return QosPolicyStatusCode.QOS_POLICY_SUCCESS; + case NetworkAgent.DSCP_POLICY_STATUS_REQUEST_DECLINED: + return QosPolicyStatusCode.QOS_POLICY_REQUEST_DECLINED; + case NetworkAgent.DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED: + return QosPolicyStatusCode.QOS_POLICY_CLASSIFIER_NOT_SUPPORTED; + case NetworkAgent.DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES: + return QosPolicyStatusCode.QOS_POLICY_INSUFFICIENT_RESOURCES; + default: + Log.e(TAG, "Invalid DSCP policy failure code received: " + status); + return QosPolicyStatusCode.QOS_POLICY_REQUEST_DECLINED; + } + } + + protected static int halToFrameworkQosPolicyRequestType(byte requestType) { + switch (requestType) { + case QosPolicyRequestType.QOS_POLICY_ADD: + return SupplicantStaIfaceHal.QOS_POLICY_REQUEST_ADD; + case QosPolicyRequestType.QOS_POLICY_REMOVE: + return SupplicantStaIfaceHal.QOS_POLICY_REQUEST_REMOVE; + default: + Log.e(TAG, "Invalid QosPolicyRequestType received: " + requestType); + return -1; + } + } + + private static boolean qosClassifierParamHasValue(int classifierParamMask, int paramBit) { + return (classifierParamMask & paramBit) != 0; + } + + /** + * Convert from a HAL QosPolicyData object to a framework QosPolicy object. + */ + public static SupplicantStaIfaceHal.QosPolicyRequest halToFrameworkQosPolicy( + QosPolicyData halQosPolicy) { + QosPolicyClassifierParams classifierParams = halQosPolicy.classifierParams; + int classifierParamMask = classifierParams.classifierParamMask; + + byte[] srcIp = null; + byte[] dstIp = null; + int srcPort = DscpPolicy.SOURCE_PORT_ANY; + int[] dstPortRange = null; + int protocol = DscpPolicy.PROTOCOL_ANY; + boolean hasSrcIp = false; + boolean hasDstIp = false; + + if (qosClassifierParamHasValue(classifierParamMask, QosPolicyClassifierParamsMask.SRC_IP)) { + hasSrcIp = true; + srcIp = classifierParams.srcIp; + } + if (qosClassifierParamHasValue(classifierParamMask, QosPolicyClassifierParamsMask.DST_IP)) { + hasDstIp = true; + dstIp = classifierParams.dstIp; + } + if (qosClassifierParamHasValue(classifierParamMask, + QosPolicyClassifierParamsMask.SRC_PORT)) { + srcPort = classifierParams.srcPort; + } + if (qosClassifierParamHasValue(classifierParamMask, + QosPolicyClassifierParamsMask.DST_PORT_RANGE)) { + dstPortRange = new int[2]; + dstPortRange[0] = classifierParams.dstPortRange.startPort; + dstPortRange[1] = classifierParams.dstPortRange.endPort; + } + if (qosClassifierParamHasValue(classifierParamMask, + QosPolicyClassifierParamsMask.PROTOCOL_NEXT_HEADER)) { + protocol = classifierParams.protocolNextHdr; + } + + return new SupplicantStaIfaceHal.QosPolicyRequest(halQosPolicy.policyId, + halToFrameworkQosPolicyRequestType(halQosPolicy.requestType), halQosPolicy.dscp, + new SupplicantStaIfaceHal.QosPolicyClassifierParams( + hasSrcIp, srcIp, hasDstIp, dstIp, srcPort, dstPortRange, protocol)); + } + + @VisibleForTesting + protected static byte frameworkToHalDeliveryRatio( + @android.net.wifi.QosCharacteristics.DeliveryRatio int frameworkRatio) { + switch (frameworkRatio) { + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_95: + return DeliveryRatio.RATIO_95; + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_96: + return DeliveryRatio.RATIO_96; + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_97: + return DeliveryRatio.RATIO_97; + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_98: + return DeliveryRatio.RATIO_98; + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_99: + return DeliveryRatio.RATIO_99; + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_99_9: + return DeliveryRatio.RATIO_99_9; + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_99_99: + return DeliveryRatio.RATIO_99_99; + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_99_999: + return DeliveryRatio.RATIO_99_999; + case android.net.wifi.QosCharacteristics.DELIVERY_RATIO_99_9999: + return DeliveryRatio.RATIO_99_9999; + default: + Log.e(TAG, "Invalid delivery ratio received: " + frameworkRatio); + return DeliveryRatio.RATIO_95; + } + } + + @VisibleForTesting + protected static byte frameworkToHalPolicyDirection( + @QosPolicyParams.Direction int frameworkDirection) { + switch (frameworkDirection) { + case QosPolicyParams.DIRECTION_UPLINK: + return LinkDirection.UPLINK; + case QosPolicyParams.DIRECTION_DOWNLINK: + return LinkDirection.DOWNLINK; + default: + Log.e(TAG, "Invalid direction received: " + frameworkDirection); + return LinkDirection.DOWNLINK; + } + } + + /** + * Convert from a framework QosCharacteristics to its HAL equivalent. + */ + @VisibleForTesting + protected static QosCharacteristics frameworkToHalQosCharacteristics( + android.net.wifi.QosCharacteristics frameworkChars) { + QosCharacteristics halChars = new QosCharacteristics(); + halChars.minServiceIntervalUs = frameworkChars.getMinServiceIntervalMicros(); + halChars.maxServiceIntervalUs = frameworkChars.getMaxServiceIntervalMicros(); + halChars.minDataRateKbps = frameworkChars.getMinDataRateKbps(); + halChars.delayBoundUs = frameworkChars.getDelayBoundMicros(); + + int optionalFieldMask = 0; + if (frameworkChars.containsOptionalField( + android.net.wifi.QosCharacteristics.MAX_MSDU_SIZE)) { + optionalFieldMask |= QosCharacteristicsMask.MAX_MSDU_SIZE; + halChars.maxMsduSizeOctets = (char) frameworkChars.getMaxMsduSizeOctets(); + } + if (frameworkChars.containsOptionalField( + android.net.wifi.QosCharacteristics.SERVICE_START_TIME)) { + optionalFieldMask |= QosCharacteristicsMask.SERVICE_START_TIME; + optionalFieldMask |= QosCharacteristicsMask.SERVICE_START_TIME_LINK_ID; + halChars.serviceStartTimeUs = frameworkChars.getServiceStartTimeMicros(); + halChars.serviceStartTimeLinkId = (byte) frameworkChars.getServiceStartTimeLinkId(); + } + if (frameworkChars.containsOptionalField( + android.net.wifi.QosCharacteristics.MEAN_DATA_RATE)) { + optionalFieldMask |= QosCharacteristicsMask.MEAN_DATA_RATE; + halChars.meanDataRateKbps = frameworkChars.getMeanDataRateKbps(); + } + if (frameworkChars.containsOptionalField( + android.net.wifi.QosCharacteristics.BURST_SIZE)) { + optionalFieldMask |= QosCharacteristicsMask.BURST_SIZE; + halChars.burstSizeOctets = frameworkChars.getBurstSizeOctets(); + } + if (frameworkChars.containsOptionalField( + android.net.wifi.QosCharacteristics.MSDU_LIFETIME)) { + optionalFieldMask |= QosCharacteristicsMask.MSDU_LIFETIME; + halChars.msduLifetimeMs = (char) frameworkChars.getMsduLifetimeMillis(); + } + if (frameworkChars.containsOptionalField( + android.net.wifi.QosCharacteristics.MSDU_DELIVERY_INFO)) { + optionalFieldMask |= QosCharacteristicsMask.MSDU_DELIVERY_INFO; + MsduDeliveryInfo deliveryInfo = new MsduDeliveryInfo(); + deliveryInfo.deliveryRatio = + frameworkToHalDeliveryRatio(frameworkChars.getDeliveryRatio()); + deliveryInfo.countExponent = (byte) frameworkChars.getCountExponent(); + halChars.msduDeliveryInfo = deliveryInfo; + } + + halChars.optionalFieldMask = optionalFieldMask; + return halChars; + } + + /** + * Convert from a framework {@link QosPolicyParams} to a HAL QosPolicyScsData object. + */ + @VisibleForTesting + protected QosPolicyScsData frameworkToHalQosPolicyScsData(QosPolicyParams params) { + QosPolicyScsData halData = new QosPolicyScsData(); + halData.policyId = (byte) params.getTranslatedPolicyId(); + halData.userPriority = (byte) params.getUserPriority(); + QosPolicyClassifierParams classifierParams = new QosPolicyClassifierParams(); + int paramsMask = 0; + + classifierParams.srcIp = new byte[0]; + classifierParams.dstIp = new byte[0]; + classifierParams.dstPortRange = new PortRange(); + classifierParams.flowLabelIpv6 = new byte[0]; + classifierParams.domainName = ""; + classifierParams.ipVersion = params.getIpVersion() == QosPolicyParams.IP_VERSION_4 + ? IpVersion.VERSION_4 : IpVersion.VERSION_6; + + if (params.getSourceAddress() != null) { + paramsMask |= QosPolicyClassifierParamsMask.SRC_IP; + classifierParams.srcIp = params.getSourceAddress().getAddress(); + } + if (params.getDestinationAddress() != null) { + paramsMask |= QosPolicyClassifierParamsMask.DST_IP; + classifierParams.dstIp = params.getDestinationAddress().getAddress(); + } + if (params.getSourcePort() != DscpPolicy.SOURCE_PORT_ANY) { + paramsMask |= QosPolicyClassifierParamsMask.SRC_PORT; + classifierParams.srcPort = params.getSourcePort(); + } + if (params.getDestinationPortRange() != null) { + paramsMask |= QosPolicyClassifierParamsMask.DST_PORT_RANGE; + classifierParams.dstPortRange.startPort = params.getDestinationPortRange()[0]; + classifierParams.dstPortRange.endPort = params.getDestinationPortRange()[1]; + } + if (params.getProtocol() != QosPolicyParams.PROTOCOL_ANY) { + paramsMask |= QosPolicyClassifierParamsMask.PROTOCOL_NEXT_HEADER; + classifierParams.protocolNextHdr = (byte) params.getProtocol(); + } + if (params.getDscp() != QosPolicyParams.DSCP_ANY) { + paramsMask |= QosPolicyClassifierParamsMask.DSCP; + classifierParams.dscp = (byte) params.getDscp(); + } + if (params.getFlowLabel() != null) { + paramsMask |= QosPolicyClassifierParamsMask.FLOW_LABEL; + classifierParams.flowLabelIpv6 = params.getFlowLabel(); + } + if (SdkLevel.isAtLeastV() && isServiceVersionAtLeast(3)) { + halData.direction = frameworkToHalPolicyDirection(params.getDirection()); + if (params.getQosCharacteristics() != null) { + halData.QosCharacteristics = + frameworkToHalQosCharacteristics(params.getQosCharacteristics()); + } + } + + classifierParams.classifierParamMask = paramsMask; + halData.classifierParams = classifierParams; + return halData; + } + + private QosPolicyScsData[] frameworkToHalQosPolicyScsDataList( + List frameworkPolicies) { + QosPolicyScsData[] halDataList = new QosPolicyScsData[frameworkPolicies.size()]; + int index = 0; + for (QosPolicyParams policy : frameworkPolicies) { + halDataList[index] = frameworkToHalQosPolicyScsData(policy); + index++; + } + return halDataList; + } + + private static @SupplicantStaIfaceHal.QosPolicyScsRequestStatusCode int + halToFrameworkQosPolicyScsRequestStatusCode(int statusCode) { + switch (statusCode) { + case QosPolicyScsRequestStatusCode.SENT: + return SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_SENT; + case QosPolicyScsRequestStatusCode.ALREADY_ACTIVE: + return SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_ALREADY_ACTIVE; + case QosPolicyScsRequestStatusCode.NOT_EXIST: + return SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_NOT_EXIST; + case QosPolicyScsRequestStatusCode.INVALID: + return SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_INVALID; + default: + Log.wtf(TAG, "Invalid QosPolicyScsRequestStatusCode: " + statusCode); + return SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_ERROR_UNKNOWN; + } + } + + private static List + halToFrameworkQosPolicyScsRequestStatusList(QosPolicyScsRequestStatus[] halStatusList) { + List frameworkStatusList = new ArrayList<>(); + for (QosPolicyScsRequestStatus halStatus : halStatusList) { + frameworkStatusList.add(new SupplicantStaIfaceHal.QosPolicyStatus( + halStatus.policyId, + halToFrameworkQosPolicyScsRequestStatusCode( + halStatus.qosPolicyScsRequestStatusCode))); + } + return frameworkStatusList; + } + + /** + * Returns connection capabilities of the current network + * + * @param ifaceName Name of the interface. + * @return connection capabilities of the current network + */ + public WifiNative.ConnectionCapabilities getConnectionCapabilities(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "getConnectionCapabilities"; + WifiNative.ConnectionCapabilities capOut = new WifiNative.ConnectionCapabilities(); + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return capOut; + } + try { + ConnectionCapabilities cap = iface.getConnectionCapabilities(); + capOut.wifiStandard = getWifiStandard(cap.technology); + capOut.channelBandwidth = getChannelBandwidth(cap.channelBandwidth); + capOut.is11bMode = (cap.legacyMode == LegacyMode.B_MODE); + capOut.maxNumberTxSpatialStreams = cap.maxNumberTxSpatialStreams; + capOut.maxNumberRxSpatialStreams = cap.maxNumberRxSpatialStreams; + capOut.apTidToLinkMapNegotiationSupported = cap.apTidToLinkMapNegotiationSupported; + if (isServiceVersionAtLeast(3) && cap.vendorData != null) { + capOut.vendorData = HalAidlUtil.halToFrameworkOuiKeyedDataList(cap.vendorData); + } + return capOut; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return capOut; + } + } + + /** + * Returns signal poll results for all Wi-Fi links of the interface. Need service version at + * least 2 or higher. + * + * @param ifaceName Name of the interface. + * @return Signal poll results or null if error. + */ + public WifiSignalPollResults getSignalPollResults(@NonNull String ifaceName) { + if (!isServiceVersionAtLeast(2)) return null; + synchronized (mLock) { + final String methodStr = "getSignalPollResult"; + ISupplicantStaIface iface; + if (mWifiInjector.getMockWifiServiceUtil() != null + && mWifiInjector.getMockWifiServiceUtil().isMethodConfigured( + MockWifiServiceUtil.MOCK_SUPPLICANT_SERVICE, ISUPPLICANTSTAIFACE + + MockWifiServiceUtil.AIDL_METHOD_IDENTIFIER + + "getSignalPollResults")) { + iface = mWifiInjector.getMockWifiServiceUtil().getMockSupplicantManager() + .getMockSupplicantStaIface(ifaceName); + } else { + iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + } + if (iface == null) { + return null; + } + try { + SignalPollResult[] halSignalPollResults = iface.getSignalPollResults(); + if (halSignalPollResults == null) { + return null; + } + WifiSignalPollResults nativeSignalPollResults = + new WifiSignalPollResults(); + for (SignalPollResult r : halSignalPollResults) { + nativeSignalPollResults.addEntry(r.linkId, r.currentRssiDbm, r.txBitrateMbps, + r.rxBitrateMbps, r.frequencyMhz); + } + return nativeSignalPollResults; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return null; + } + } + + /** + * Returns connection MLO links info + * + * @param ifaceName Name of the interface. + * @return connection MLO links info + */ + public WifiNative.ConnectionMloLinksInfo getConnectionMloLinksInfo(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "getConnectionMloLinksInfo"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return null; + } + try { + MloLinksInfo halInfo = iface.getConnectionMloLinksInfo(); + if (halInfo == null) { + return null; + } + + WifiNative.ConnectionMloLinksInfo nativeInfo = + new WifiNative.ConnectionMloLinksInfo(); + + // The parameter 'apMldMacAddress' can come as null. + if (halInfo.apMldMacAddress != null) { + nativeInfo.apMldMacAddress = MacAddress.fromBytes(halInfo.apMldMacAddress); + } + nativeInfo.apMloLinkId = halInfo.apMloLinkId; + nativeInfo.links = new WifiNative.ConnectionMloLink[halInfo.links.length]; + + for (int i = 0; i < halInfo.links.length; i++) { + // The parameter 'apLinkMacAddress' can come as null. + nativeInfo.links[i] = new WifiNative.ConnectionMloLink( + halInfo.links[i].linkId, + MacAddress.fromBytes(halInfo.links[i].staLinkMacAddress), + (halInfo.links[i].apLinkMacAddress != null) ? MacAddress.fromBytes( + halInfo.links[i].apLinkMacAddress) : null, + halInfo.links[i].tidsUplinkMap, halInfo.links[i].tidsDownlinkMap, + halInfo.links[i].frequencyMHz); + } + return nativeInfo; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid STA Mac Address received from HAL"); + return null; + } + + return null; + } + } + + /** + * Adds a DPP peer URI to the URI list. + * + * Returns an ID to be used later to refer to this URI (>0). + * On error, -1 is returned. + */ + public int addDppPeerUri(@NonNull String ifaceName, @NonNull String uri) { + synchronized (mLock) { + final String methodStr = "addDppPeerUri"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return -1; + } + try { + return iface.addDppPeerUri(uri); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return -1; + } + } + + /** + * Removes a DPP URI to the URI list given an ID. + * + * Returns true when operation is successful + * On error, false is returned. + */ + public boolean removeDppUri(@NonNull String ifaceName, int bootstrapId) { + synchronized (mLock) { + final String methodStr = "removeDppUri"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.removeDppUri(bootstrapId); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Stops/aborts DPP Initiator request + * + * Returns true when operation is successful + * On error, false is returned. + */ + public boolean stopDppInitiator(@NonNull String ifaceName) { + synchronized (mLock) { + final String methodStr = "stopDppInitiator"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.stopDppInitiator(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Starts DPP Configurator-Initiator request + * + * Returns true when operation is successful + * On error, false is returned. + */ + public boolean startDppConfiguratorInitiator(@NonNull String ifaceName, int peerBootstrapId, + int ownBootstrapId, @NonNull String ssid, String password, String psk, + int netRole, int securityAkm, byte[] privEcKey) { + synchronized (mLock) { + final String methodStr = "startDppConfiguratorInitiator"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + byte[] key = iface.startDppConfiguratorInitiator(peerBootstrapId, ownBootstrapId, + ssid, password != null ? password : "", psk != null ? psk : "", + frameworkToAidlDppNetRole(netRole), frameworkToAidlDppAkm(securityAkm), + privEcKey != null ? privEcKey : new byte[] {}); + if (key != null && key.length > 0 && mDppCallback != null) { + mDppCallback.onDppConfiguratorKeyUpdate(key); + } + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Starts DPP Enrollee-Initiator request + * + * Returns true when operation is successful + * On error, false is returned. + */ + public boolean startDppEnrolleeInitiator(@NonNull String ifaceName, int peerBootstrapId, + int ownBootstrapId) { + synchronized (mLock) { + final String methodStr = "startDppEnrolleeInitiator"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.startDppEnrolleeInitiator(peerBootstrapId, ownBootstrapId); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Generate a DPP QR code based boot strap info + * + * Returns DppResponderBootstrapInfo; + */ + public WifiNative.DppBootstrapQrCodeInfo generateDppBootstrapInfoForResponder( + @NonNull String ifaceName, String macAddress, @NonNull String deviceInfo, + int dppCurve) { + synchronized (mLock) { + final String methodStr = "generateDppBootstrapInfoForResponder"; + WifiNative.DppBootstrapQrCodeInfo bootstrapInfoOut = + new WifiNative.DppBootstrapQrCodeInfo(); + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return bootstrapInfoOut; + } + try { + DppResponderBootstrapInfo info = iface.generateDppBootstrapInfoForResponder( + NativeUtil.macAddressToByteArray(macAddress), deviceInfo, + frameworkToAidlDppCurve(dppCurve)); + bootstrapInfoOut.bootstrapId = info.bootstrapId; + bootstrapInfoOut.listenChannel = info.listenChannel; + bootstrapInfoOut.uri = info.uri; + return bootstrapInfoOut; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return bootstrapInfoOut; + } + } + + /** + * Starts DPP Enrollee-Responder request + * + * Returns true when operation is successful + * On error, false is returned. + */ + public boolean startDppEnrolleeResponder(@NonNull String ifaceName, int listenChannel) { + synchronized (mLock) { + final String methodStr = "startDppEnrolleeResponder"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.startDppEnrolleeResponder(listenChannel); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Stops/aborts DPP Responder request. + * + * Returns true when operation is successful + * On error, false is returned. + */ + public boolean stopDppResponder(@NonNull String ifaceName, int ownBootstrapId) { + synchronized (mLock) { + final String methodStr = "stopDppResponder"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.stopDppResponder(ownBootstrapId); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Register callbacks for DPP events. + * + * @param dppCallback DPP callback object. + */ + public void registerDppCallback(WifiNative.DppEventCallback dppCallback) { + synchronized (mLock) { + mDppCallback = dppCallback; + } + } + + protected WifiNative.DppEventCallback getDppCallback() { + synchronized (mLock) { + return mDppCallback; + } + } + + /** + * Set MBO cellular data availability. + * + * @param ifaceName Name of the interface. + * @param available true means cellular data available, false otherwise. + * @return true is operation is successful, false otherwise. + */ + public boolean setMboCellularDataStatus(@NonNull String ifaceName, boolean available) { + synchronized (mLock) { + final String methodStr = "setMboCellularDataStatus"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setMboCellularDataStatus(available); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set whether the network-centric QoS policy feature is enabled or not for this interface. + * + * @param ifaceName name of the interface. + * @param isEnabled true if feature is enabled, false otherwise. + * @return true if operation is successful, false otherwise. + */ + public boolean setNetworkCentricQosPolicyFeatureEnabled(@NonNull String ifaceName, + boolean isEnabled) { + synchronized (mLock) { + String methodStr = "setNetworkCentricQosPolicyFeatureEnabled"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.setQosPolicyFeatureEnabled(isEnabled); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Check if we've roamed to a linked network and make the linked network the current network + * if we have. + * + * @param ifaceName Name of the interface. + * @param newNetworkId Network id of the new network we've roamed to. If fromFramework is + * {@code true}, this will be a framework network id. Otherwise, this will + * be a remote network id. + * @param fromFramework {@code true} if the network id is a framework network id, {@code false} + if the network id is a remote network id. + * @return true if we've roamed to a linked network, false if not. + */ + public boolean updateOnLinkedNetworkRoaming( + @NonNull String ifaceName, int newNetworkId, boolean fromFramework) { + synchronized (mLock) { + List> linkedNetworkHandles = + mLinkedNetworkLocalAndRemoteConfigs.get(ifaceName); + SupplicantStaNetworkHalAidlImpl currentHandle = + getCurrentNetworkRemoteHandle(ifaceName); + WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName); + if (linkedNetworkHandles == null || currentHandle == null || currentConfig == null) { + return false; + } + if (fromFramework ? currentConfig.networkId == newNetworkId + : currentHandle.getNetworkId() == newNetworkId) { + return false; + } + for (Pair pair + : linkedNetworkHandles) { + if (fromFramework ? pair.second.networkId == newNetworkId + : pair.first.getNetworkId() == newNetworkId) { + Log.i(TAG, "Roamed to linked network, make linked network as current network"); + mCurrentNetworkRemoteHandles.put(ifaceName, pair.first); + mCurrentNetworkLocalConfigs.put(ifaceName, pair.second); + return true; + } + } + return false; + } + } + + /** + * Updates the linked networks for the current network and sends them to the supplicant. + * + * @param ifaceName Name of the interface. + * @param networkId Network id of the network to link the configurations to. + * @param linkedConfigurations Map of config profile key to config for linking. + * @return true if networks were successfully linked, false otherwise. + */ + public boolean updateLinkedNetworks(@NonNull String ifaceName, int networkId, + Map linkedConfigurations) { + synchronized (mLock) { + WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName); + SupplicantStaNetworkHalAidlImpl currentHandle = + getCurrentNetworkRemoteHandle(ifaceName); + + if (currentConfig == null || currentHandle == null) { + Log.e(TAG, "current network not configured yet."); + return false; + } + + if (networkId != currentConfig.networkId) { + Log.e(TAG, "current config network id is not matching"); + return false; + } + + final int remoteNetworkId = currentHandle.getNetworkId(); + if (remoteNetworkId == -1) { + Log.e(TAG, "current handle getNetworkId failed"); + return false; + } + + if (!removeAllNetworksExcept(ifaceName, remoteNetworkId)) { + Log.e(TAG, "couldn't remove non-current supplicant networks"); + return false; + } + + mLinkedNetworkLocalAndRemoteConfigs.remove(ifaceName); + + if (linkedConfigurations == null || linkedConfigurations.size() == 0) { + Log.i(TAG, "cleared linked networks"); + return true; + } + + List> linkedNetworkHandles = + new ArrayList<>(); + linkedNetworkHandles.add(new Pair(currentHandle, currentConfig)); + for (String linkedNetwork : linkedConfigurations.keySet()) { + Log.i(TAG, "add linked network: " + linkedNetwork); + Pair pair = + addNetworkAndSaveConfig(ifaceName, linkedConfigurations.get(linkedNetwork)); + if (pair == null) { + Log.e(TAG, "failed to add/save linked network: " + linkedNetwork); + return false; + } + pair.first.enable(true); + linkedNetworkHandles.add(pair); + } + + mLinkedNetworkLocalAndRemoteConfigs.put(ifaceName, linkedNetworkHandles); + + return true; + } + } + + /** + * Remove all networks except the supplied network ID from supplicant + * + * @param ifaceName Name of the interface + * @param networkId network id to keep + */ + private boolean removeAllNetworksExcept(@NonNull String ifaceName, int networkId) { + synchronized (mLock) { + int[] networks = listNetworks(ifaceName); + if (networks == null) { + Log.e(TAG, "removeAllNetworksExcept failed, got null networks"); + return false; + } + for (int id : networks) { + if (networkId == id) { + continue; + } + if (!removeNetwork(ifaceName, id)) { + Log.e(TAG, "removeAllNetworksExcept failed to remove network: " + id); + return false; + } + } + return true; + } + } + + /** + * Gets the security params of the current network associated with this interface + * + * @param ifaceName Name of the interface + * @return Security params of the current network associated with the interface + */ + public SecurityParams getCurrentNetworkSecurityParams(@NonNull String ifaceName) { + WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName); + + if (currentConfig == null) { + return null; + } + + return currentConfig.getNetworkSelectionStatus().getCandidateSecurityParams(); + } + + /** + * Sends a QoS policy response. + * + * @param ifaceName Name of the interface. + * @param qosPolicyRequestId Dialog token to identify the request. + * @param morePolicies Flag to indicate more QoS policies can be accommodated. + * @param qosPolicyStatusList List of framework QosPolicyStatus objects. + * @return true if response is sent successfully, false otherwise. + */ + public boolean sendQosPolicyResponse(String ifaceName, int qosPolicyRequestId, + boolean morePolicies, + @NonNull List qosPolicyStatusList) { + synchronized (mLock) { + final String methodStr = "sendQosPolicyResponse"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + + int index = 0; + QosPolicyStatus[] halPolicyStatusList = new QosPolicyStatus[qosPolicyStatusList.size()]; + for (SupplicantStaIfaceHal.QosPolicyStatus frameworkPolicyStatus + : qosPolicyStatusList) { + if (frameworkPolicyStatus == null) { + return false; + } + QosPolicyStatus halPolicyStatus = new QosPolicyStatus(); + halPolicyStatus.policyId = (byte) frameworkPolicyStatus.policyId; + halPolicyStatus.status = dscpPolicyToAidlQosPolicyStatusCode( + frameworkPolicyStatus.statusCode); + halPolicyStatusList[index] = halPolicyStatus; + index++; + } + + try { + iface.sendQosPolicyResponse(qosPolicyRequestId, morePolicies, halPolicyStatusList); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Indicates the removal of all active QoS policies configured by the AP. + * + * @param ifaceName Name of the interface. + */ + public boolean removeAllQosPolicies(String ifaceName) { + final String methodStr = "removeAllQosPolicies"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + + try { + iface.removeAllQosPolicies(); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + + /** + * See comments for {@link ISupplicantStaIfaceHal#addQosPolicyRequestForScs(String, List)} + */ + public List addQosPolicyRequestForScs( + @NonNull String ifaceName, @NonNull List policies) { + synchronized (mLock) { + final String methodStr = "addQosPolicyRequestForScs"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return null; + } + try { + QosPolicyScsData[] halPolicies = frameworkToHalQosPolicyScsDataList(policies); + QosPolicyScsRequestStatus[] halStatusList = + iface.addQosPolicyRequestForScs(halPolicies); + return halToFrameworkQosPolicyScsRequestStatusList(halStatusList); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return null; + } + } + + /** + * See comments for {@link ISupplicantStaIfaceHal#removeQosPolicyForScs(String, List)} + */ + public List removeQosPolicyForScs( + @NonNull String ifaceName, @NonNull List policyIds) { + synchronized (mLock) { + final String methodStr = "removeQosPolicyForScs"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return null; + } + try { + byte[] halPolicyIds = new byte[policyIds.size()]; + for (int i = 0; i < policyIds.size(); i++) { + halPolicyIds[i] = policyIds.get(i); + } + QosPolicyScsRequestStatus[] halStatusList = + iface.removeQosPolicyForScs(halPolicyIds); + return halToFrameworkQosPolicyScsRequestStatusList(halStatusList); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return null; + } + } + + /** + * See comments for {@link ISupplicantStaIfaceHal#registerQosScsResponseCallback( + * SupplicantStaIfaceHal.QosScsResponseCallback)} + */ + public void registerQosScsResponseCallback( + @NonNull SupplicantStaIfaceHal.QosScsResponseCallback callback) { + synchronized (mLock) { + if (callback == null) { + Log.e(TAG, "QosScsResponseCallback should not be null"); + return; + } else if (mQosScsResponseCallback != null) { + Log.e(TAG, "mQosScsResponseCallback has already been assigned"); + return; + } + mQosScsResponseCallback = callback; + } + } + + protected SupplicantStaIfaceHal.QosScsResponseCallback getQosScsResponseCallback() { + return mQosScsResponseCallback; + } + + /** + * Generate DPP credential for network access + * + * @param ifaceName Name of the interface. + * @param ssid ssid of the network + * @param privEcKey Private EC Key for DPP Configurator + * Returns true when operation is successful. On error, false is returned. + */ + public boolean generateSelfDppConfiguration(@NonNull String ifaceName, @NonNull String ssid, + byte[] privEcKey) { + synchronized (mLock) { + final String methodStr = "generateSelfDppConfiguration"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return false; + } + try { + iface.generateSelfDppConfiguration( + NativeUtil.removeEnclosingQuotes(ssid), privEcKey); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + } + + /** + * Set the currently configured network's anonymous identity. + * + * @param ifaceName Name of the interface. + * @param anonymousIdentity the anonymouns identity. + * @param updateToNativeService write the data to the native service. + * @return true if succeeds, false otherwise. + */ + public boolean setEapAnonymousIdentity(@NonNull String ifaceName, String anonymousIdentity, + boolean updateToNativeService) { + synchronized (mLock) { + SupplicantStaNetworkHalAidlImpl networkHandle = + checkStaNetworkAndLogFailure(ifaceName, "setEapAnonymousIdentity"); + if (networkHandle == null) return false; + if (anonymousIdentity == null) return false; + WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName); + if (currentConfig == null) return false; + if (!currentConfig.isEnterprise()) return false; + + if (updateToNativeService) { + if (!networkHandle.setEapAnonymousIdentity(anonymousIdentity.getBytes())) { + Log.w(TAG, "Cannot set EAP anonymous identity."); + return false; + } + } + + // Update cached config after setting native data successfully. + currentConfig.enterpriseConfig.setAnonymousIdentity(anonymousIdentity); + return true; + } + } + + private static byte frameworkToHalFrameClassifierMask(int frameworkBitmap) { + byte halBitmap = 0; + if ((frameworkBitmap & MscsParams.FRAME_CLASSIFIER_IP_VERSION) != 0) { + halBitmap |= FrameClassifierFields.IP_VERSION; + } + if ((frameworkBitmap & MscsParams.FRAME_CLASSIFIER_SRC_IP_ADDR) != 0) { + halBitmap |= FrameClassifierFields.SRC_IP_ADDR; + } + if ((frameworkBitmap & MscsParams.FRAME_CLASSIFIER_DST_IP_ADDR) != 0) { + halBitmap |= FrameClassifierFields.DST_IP_ADDR; + } + if ((frameworkBitmap & MscsParams.FRAME_CLASSIFIER_SRC_PORT) != 0) { + halBitmap |= FrameClassifierFields.SRC_PORT; + } + if ((frameworkBitmap & MscsParams.FRAME_CLASSIFIER_DST_PORT) != 0) { + halBitmap |= FrameClassifierFields.DST_PORT; + } + if ((frameworkBitmap & MscsParams.FRAME_CLASSIFIER_DSCP) != 0) { + halBitmap |= FrameClassifierFields.DSCP; + } + if ((frameworkBitmap & MscsParams.FRAME_CLASSIFIER_PROTOCOL_NEXT_HDR) != 0) { + halBitmap |= FrameClassifierFields.PROTOCOL_NEXT_HDR; + } + if ((frameworkBitmap & MscsParams.FRAME_CLASSIFIER_FLOW_LABEL) != 0) { + halBitmap |= FrameClassifierFields.FLOW_LABEL; + } + return halBitmap; + } + + private static android.hardware.wifi.supplicant.MscsParams frameworkToHalMscsParams( + MscsParams frameworkParams) { + android.hardware.wifi.supplicant.MscsParams halParams = + new android.hardware.wifi.supplicant.MscsParams(); + halParams.upBitmap = (byte) frameworkParams.getUserPriorityBitmap(); + halParams.upLimit = (byte) frameworkParams.getUserPriorityLimit(); + halParams.streamTimeoutUs = frameworkParams.getStreamTimeoutUs(); + halParams.frameClassifierMask = + frameworkToHalFrameClassifierMask(frameworkParams.getFrameClassifierFields()); + return halParams; + } + + /** + * See comments for {@link ISupplicantStaIfaceHal#enableMscs(MscsParams, String)} + */ + @Override + public void enableMscs(@NonNull MscsParams mscsParams, String ifaceName) { + synchronized (mLock) { + if (!isServiceVersionAtLeast(3)) { + return; + } + String methodStr = "enableMscs"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return; + } + try { + android.hardware.wifi.supplicant.MscsParams halParams = + frameworkToHalMscsParams(mscsParams); + iface.configureMscs(halParams); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + } + } + + /** + * See comments for {@link ISupplicantStaIface#disableMscs()} + */ + @Override + public void disableMscs(String ifaceName) { + if (!isServiceVersionAtLeast(3)) { + return; + } + String methodStr = "disableMscs"; + ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); + if (iface == null) { + return; + } + try { + iface.disableMscs(); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + } + + private class NonStandardCertCallback extends INonStandardCertCallback.Stub { + @Override + public byte[] getBlob(String alias) { + byte[] blob = null; + if (SdkLevel.isAtLeastU()) { + Log.i(TAG, "Non-standard certificate requested"); + blob = WifiKeystore.get(alias); + } + if (blob != null) { + return blob; + } else { + Log.e(TAG, "Unable to retrieve the blob"); + throw new ServiceSpecificException(SupplicantStatusCode.FAILURE_UNKNOWN); + } + } + + @Override + public String[] listAliases(String prefix) { + Log.i(TAG, "Alias list was requested"); + return SdkLevel.isAtLeastU() ? WifiKeystore.list(prefix) : null; + } + + @Override + public String getInterfaceHash() { + return INonStandardCertCallback.HASH; + } + + @Override + public int getInterfaceVersion() { + return INonStandardCertCallback.VERSION; + } + } + + private void registerNonStandardCertCallback() { + synchronized (mLock) { + final String methodStr = "registerNonStandardCertCallback"; + if (!checkSupplicantAndLogFailure(methodStr) || !isServiceVersionAtLeast(2)) { + return; + } else if (mNonStandardCertCallback != null) { + Log.i(TAG, "Non-standard cert callback has already been registered"); + return; + } + + try { + INonStandardCertCallback tempCallback = new NonStandardCertCallback(); + mISupplicant.registerNonStandardCertCallback(tempCallback); + mNonStandardCertCallback = tempCallback; + Log.i(TAG, "Non-standard cert callback was registered"); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + } + } +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiInjector.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiInjector.java new file mode 100644 index 000000000..ff699bbc5 --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiInjector.java @@ -0,0 +1,1344 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.AppOpsManager; +import android.app.BroadcastOptions; +import android.app.StatsManager; +import android.content.Context; +import android.net.IpMemoryStore; +import android.net.LinkProperties; +import android.net.MatchAllNetworkSpecifier; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkProvider; +import android.net.wifi.WifiContext; +import android.net.wifi.WifiScanner; +import android.net.wifi.WifiTwtSession; +import android.net.wifi.nl80211.WifiNl80211Manager; +import android.os.BatteryManager; +import android.os.BatteryStatsManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; +import android.os.UserManager; +import android.os.WorkSource; +import android.provider.Settings.Secure; +import android.security.keystore.AndroidKeyStoreProvider; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.LocalLog; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.BackgroundThread; +import com.android.modules.utils.build.SdkLevel; +import com.android.server.wifi.aware.WifiAwareMetrics; +import com.android.server.wifi.b2b.WifiRoamingModeManager; +import com.android.server.wifi.coex.CoexManager; +import com.android.server.wifi.hotspot2.PasspointManager; +import com.android.server.wifi.hotspot2.PasspointNetworkNominateHelper; +import com.android.server.wifi.hotspot2.PasspointObjectFactory; +import com.android.server.wifi.mockwifi.MockWifiServiceUtil; +import com.android.server.wifi.p2p.SupplicantP2pIfaceHal; +import com.android.server.wifi.p2p.WifiP2pMetrics; +import com.android.server.wifi.p2p.WifiP2pMonitor; +import com.android.server.wifi.p2p.WifiP2pNative; +import com.android.server.wifi.rtt.RttMetrics; +import com.android.server.wifi.util.KeystoreWrapper; +import com.android.server.wifi.util.LastCallerInfoManager; +import com.android.server.wifi.util.LruConnectionTracker; +import com.android.server.wifi.util.NetdWrapper; +import com.android.server.wifi.util.SettingsMigrationDataHolder; +import com.android.server.wifi.util.WifiPermissionsUtil; +import com.android.server.wifi.util.WifiPermissionsWrapper; +import com.android.server.wifi.util.WorkSourceHelper; +import com.android.wifi.flags.FeatureFlags; +import com.android.wifi.resources.R; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchProviderException; +import java.util.Random; + +/** + * WiFi dependency injector. To be used for accessing various WiFi class instances and as a + * handle for mock injection. + * + * Some WiFi class instances currently depend on having a Looper from a HandlerThread that has + * been started. To accommodate this, we have a two-phased approach to initialize and retrieve + * an instance of the WifiInjector. + */ +public class WifiInjector { + private static final String TAG = "WifiInjector"; + private static final String BOOT_DEFAULT_WIFI_COUNTRY_CODE = "ro.boot.wificountrycode"; + /** + * Maximum number in-memory store network connection order; + */ + private static final int MAX_RECENTLY_CONNECTED_NETWORK = 100; + + private final WifiDeviceStateChangeManager mWifiDeviceStateChangeManager; + private final PasspointNetworkNominateHelper mNominateHelper; + private final AlarmManager mAlarmManager; + + private static NetworkCapabilities.Builder makeBaseNetworkCapatibilitiesFilterBuilder() { + NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .setLinkUpstreamBandwidthKbps(1024 * 1024) + .setLinkDownstreamBandwidthKbps(1024 * 1024) + .setNetworkSpecifier(new MatchAllNetworkSpecifier()); + if (SdkLevel.isAtLeastS()) { + builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); + } + return builder; + } + + @VisibleForTesting + public static final NetworkCapabilities REGULAR_NETWORK_CAPABILITIES_FILTER = + makeBaseNetworkCapatibilitiesFilterBuilder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .build(); + + private static NetworkCapabilities makeOemNetworkCapatibilitiesFilter() { + NetworkCapabilities.Builder builder = + makeBaseNetworkCapatibilitiesFilterBuilder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID); + if (SdkLevel.isAtLeastS()) { + // OEM_PRIVATE capability was only added in Android S. + builder.addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE); + } + return builder.build(); + } + + private static final NetworkCapabilities OEM_NETWORK_CAPABILITIES_FILTER = + makeOemNetworkCapatibilitiesFilter(); + + private static final NetworkCapabilities RESTRICTED_NETWORK_CAPABILITIES_FILTER = + makeBaseNetworkCapatibilitiesFilterBuilder() + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .build(); + + + static WifiInjector sWifiInjector = null; + + private final WifiContext mContext; + private final BatteryStatsManager mBatteryStats; + private final FrameworkFacade mFrameworkFacade = new FrameworkFacade(); + private final DeviceConfigFacade mDeviceConfigFacade; + private final FeatureFlags mFeatureFlags; + private final UserManager mUserManager; + private final HandlerThread mWifiHandlerThread; + private final HandlerThread mWifiP2pServiceHandlerThread; + private final HandlerThread mPasspointProvisionerHandlerThread; + private final HandlerThread mWifiDiagnosticsHandlerThread; + private final WifiTrafficPoller mWifiTrafficPoller; + private final WifiCountryCode mCountryCode; + private final BackupManagerProxy mBackupManagerProxy = new BackupManagerProxy(); + private final WifiApConfigStore mWifiApConfigStore; + private final WifiNative mWifiNative; + private final WifiMonitor mWifiMonitor; + private final WifiP2pNative mWifiP2pNative; + private final WifiP2pMonitor mWifiP2pMonitor; + private final SupplicantStaIfaceHal mSupplicantStaIfaceHal; + private final SupplicantP2pIfaceHal mSupplicantP2pIfaceHal; + private final HostapdHal mHostapdHal; + private final WifiVendorHal mWifiVendorHal; + private final ScoringParams mScoringParams; + private final ActiveModeWarden mActiveModeWarden; + private final WifiSettingsStore mSettingsStore; + private final OpenNetworkNotifier mOpenNetworkNotifier; + private final WifiLockManager mLockManager; + private final WifiNl80211Manager mWifiCondManager; + private final Clock mClock = new Clock(); + private final WifiMetrics mWifiMetrics; + private final WifiP2pMetrics mWifiP2pMetrics; + private final WifiLastResortWatchdog mWifiLastResortWatchdog; + private final PropertyService mPropertyService = new SystemPropertyService(); + private final BuildProperties mBuildProperties = new SystemBuildProperties(); + private final WifiBackupRestore mWifiBackupRestore; + private final BackupRestoreController mBackupRestoreController; + // This will only be null if SdkLevel is not at least S + @Nullable private final CoexManager mCoexManager; + private final SoftApBackupRestore mSoftApBackupRestore; + private final WifiSettingsBackupRestore mWifiSettingsBackupRestore; + private final WifiMulticastLockManager mWifiMulticastLockManager; + private final WifiConfigStore mWifiConfigStore; + private final WifiKeyStore mWifiKeyStore; + private final WifiConfigManager mWifiConfigManager; + private final WifiConnectivityHelper mWifiConnectivityHelper; + private final LocalLog mConnectivityLocalLog; + private final LocalLog mWifiHandlerLocalLog; + private final ThroughputScorer mThroughputScorer; + private final WifiNetworkSelector mWifiNetworkSelector; + private final SavedNetworkNominator mSavedNetworkNominator; + private final NetworkSuggestionNominator mNetworkSuggestionNominator; + private final ClientModeManagerBroadcastQueue mBroadcastQueue; + private WifiScanner mWifiScanner; + private MockWifiServiceUtil mMockWifiModem; + private final WifiPermissionsWrapper mWifiPermissionsWrapper; + private final WifiPermissionsUtil mWifiPermissionsUtil; + private final PasspointManager mPasspointManager; + private HandlerThread mWifiAwareHandlerThread; + private HandlerThread mRttHandlerThread; + private final HalDeviceManager mHalDeviceManager; + private final WifiStateTracker mWifiStateTracker; + private final SelfRecovery mSelfRecovery; + private final WakeupController mWakeupController; + private final ScanRequestProxy mScanRequestProxy; + private final SarManager mSarManager; + private final WifiDiagnostics mWifiDiagnostics; + private final WifiDataStall mWifiDataStall; + private final WifiScoreCard mWifiScoreCard; + private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager; + private final DppMetrics mDppMetrics; + private final DppManager mDppManager; + private final WifiPulledAtomLogger mWifiPulledAtomLogger; + private final LinkProbeManager mLinkProbeManager; + private IpMemoryStore mIpMemoryStore; + private final WifiThreadRunner mWifiThreadRunner; + private final WifiBlocklistMonitor mWifiBlocklistMonitor; + private final MacAddressUtil mMacAddressUtil = new MacAddressUtil(new KeystoreWrapper()); + private final MboOceController mMboOceController; + private final WifiPseudonymManager mWifiPseudonymManager; + private final WifiCarrierInfoManager mWifiCarrierInfoManager; + private final WifiChannelUtilization mWifiChannelUtilizationScan; + private final KeyStore mKeyStore; + private final ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder; + private final ThroughputPredictor mThroughputPredictor; + private NetdWrapper mNetdWrapper; + private final WifiHealthMonitor mWifiHealthMonitor; + private final WifiSettingsConfigStore mSettingsConfigStore; + private final WifiScanAlwaysAvailableSettingsCompatibility + mWifiScanAlwaysAvailableSettingsCompatibility; + private final SettingsMigrationDataHolder mSettingsMigrationDataHolder; + private final LruConnectionTracker mLruConnectionTracker; + private final WifiConnectivityManager mWifiConnectivityManager; + private final ExternalPnoScanRequestManager mExternalPnoScanRequestManager; + private final ConnectHelper mConnectHelper; + private final ConnectionFailureNotifier mConnectionFailureNotifier; + private final WifiNetworkFactory mWifiNetworkFactory; + private final UntrustedWifiNetworkFactory mUntrustedWifiNetworkFactory; + private final OemWifiNetworkFactory mOemWifiNetworkFactory; + private final RestrictedWifiNetworkFactory mRestrictedWifiNetworkFactory; + private final MultiInternetWifiNetworkFactory mMultiInternetWifiNetworkFactory; + private final WifiP2pConnection mWifiP2pConnection; + private final WifiGlobals mWifiGlobals; + private final SimRequiredNotifier mSimRequiredNotifier; + private final DefaultClientModeManager mDefaultClientModeManager; + private final AdaptiveConnectivityEnabledSettingObserver + mAdaptiveConnectivityEnabledSettingObserver; + private final MakeBeforeBreakManager mMakeBeforeBreakManager; + private final MultiInternetManager mMultiInternetManager; + private final ClientModeImplMonitor mCmiMonitor = new ClientModeImplMonitor(); + private final ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy; + private final WifiNotificationManager mWifiNotificationManager; + private final LastCallerInfoManager mLastCallerInfoManager; + private final InterfaceConflictManager mInterfaceConflictManager; + private final AfcManager mAfcManager; + private final WifiContext mContextWithAttributionTag; + private final AfcLocationUtil mAfcLocationUtil; + private final AfcClient mAfcClient; + @NonNull private final WifiDialogManager mWifiDialogManager; + @NonNull private final SsidTranslator mSsidTranslator; + @NonNull private final ApplicationQosPolicyRequestHandler mApplicationQosPolicyRequestHandler; + private final WifiRoamingModeManager mWifiRoamingModeManager; + private final TwtManager mTwtManager; + + public WifiInjector(WifiContext context) { + if (context == null) { + throw new IllegalStateException( + "WifiInjector should not be initialized with a null Context."); + } + + if (sWifiInjector != null) { + throw new IllegalStateException( + "WifiInjector was already created, use getInstance instead."); + } + + sWifiInjector = this; + mLastCallerInfoManager = new LastCallerInfoManager(); + mContext = context; + mAlarmManager = mContext.getSystemService(AlarmManager.class); + + // Now create and start handler threads + mWifiHandlerThread = new HandlerThread("WifiHandlerThread"); + mWifiHandlerThread.start(); + Looper wifiLooper = mWifiHandlerThread.getLooper(); + mWifiHandlerLocalLog = new LocalLog(1024); + WifiAwareMetrics awareMetrics = new WifiAwareMetrics(mClock); + RttMetrics rttMetrics = new RttMetrics(mClock); + mDppMetrics = new DppMetrics(); + mWifiMonitor = new WifiMonitor(); + mBatteryStats = context.getSystemService(BatteryStatsManager.class); + mWifiP2pMetrics = new WifiP2pMetrics(mClock, mContext); + RunnerHandler wifiHandler = new RunnerHandler(wifiLooper, context.getResources().getInteger( + R.integer.config_wifiConfigurationWifiRunnerThresholdInMs), + mWifiHandlerLocalLog); + mWifiDeviceStateChangeManager = new WifiDeviceStateChangeManager(context, wifiHandler); + mWifiMetrics = new WifiMetrics(mContext, mFrameworkFacade, mClock, wifiLooper, + awareMetrics, rttMetrics, new WifiPowerMetrics(mBatteryStats), mWifiP2pMetrics, + mDppMetrics, mWifiMonitor, mWifiDeviceStateChangeManager); + + mWifiDiagnosticsHandlerThread = new HandlerThread("WifiDiagnostics"); + mWifiDiagnosticsHandlerThread.start(); + + + mWifiNotificationManager = new WifiNotificationManager(mContext); + mWifiGlobals = new WifiGlobals(mContext); + mScoringParams = new ScoringParams(mContext); + mWifiChannelUtilizationScan = new WifiChannelUtilization(mClock, mContext); + mSettingsMigrationDataHolder = new SettingsMigrationDataHolder(mContext); + mConnectionFailureNotificationBuilder = new ConnectionFailureNotificationBuilder( + mContext, mFrameworkFacade); + + mWifiPermissionsWrapper = new WifiPermissionsWrapper(mContext); + mUserManager = mContext.getSystemService(UserManager.class); + mWifiPermissionsUtil = new WifiPermissionsUtil(mWifiPermissionsWrapper, mContext, + mUserManager, this); + mWifiBackupRestore = new WifiBackupRestore(mWifiPermissionsUtil); + mSoftApBackupRestore = new SoftApBackupRestore(mContext, mSettingsMigrationDataHolder); + mWifiStateTracker = new WifiStateTracker(mBatteryStats); + mWifiThreadRunner = new WifiThreadRunner(wifiHandler); + mWifiDialogManager = new WifiDialogManager(mContext, mWifiThreadRunner, mFrameworkFacade, + this); + mSsidTranslator = new SsidTranslator(mContext, wifiHandler); + mWifiP2pServiceHandlerThread = new HandlerThread("WifiP2pService"); + mWifiP2pServiceHandlerThread.start(); + mPasspointProvisionerHandlerThread = + new HandlerThread("PasspointProvisionerHandlerThread"); + mPasspointProvisionerHandlerThread.start(); + mDeviceConfigFacade = new DeviceConfigFacade(mContext, wifiHandler, mWifiMetrics); + mFeatureFlags = mDeviceConfigFacade.getFeatureFlags(); + mAdaptiveConnectivityEnabledSettingObserver = + new AdaptiveConnectivityEnabledSettingObserver(wifiHandler, mWifiMetrics, + mFrameworkFacade, mContext); + // Modules interacting with Native. + mHalDeviceManager = new HalDeviceManager(mContext, mClock, this, wifiHandler); + mInterfaceConflictManager = new InterfaceConflictManager(this, mContext, mFrameworkFacade, + mHalDeviceManager, mWifiThreadRunner, mWifiDialogManager, new LocalLog( + mContext.getSystemService(ActivityManager.class).isLowRamDevice() ? 128 : 256)); + mWifiVendorHal = new WifiVendorHal(mContext, mHalDeviceManager, wifiHandler, mWifiGlobals, + mSsidTranslator); + mSupplicantStaIfaceHal = new SupplicantStaIfaceHal( + mContext, mWifiMonitor, mFrameworkFacade, wifiHandler, mClock, mWifiMetrics, + mWifiGlobals, mSsidTranslator, this); + mHostapdHal = new HostapdHal(mContext, wifiHandler); + mWifiCondManager = (WifiNl80211Manager) mContext.getSystemService( + Context.WIFI_NL80211_SERVICE); + mWifiNative = new WifiNative( + mWifiVendorHal, mSupplicantStaIfaceHal, mHostapdHal, mWifiCondManager, + mWifiMonitor, mPropertyService, mWifiMetrics, + wifiHandler, new Random(), mBuildProperties, this); + mWifiP2pMonitor = new WifiP2pMonitor(); + mSupplicantP2pIfaceHal = new SupplicantP2pIfaceHal(mWifiP2pMonitor, mWifiGlobals, this); + mWifiP2pNative = new WifiP2pNative(mWifiCondManager, mWifiNative, mWifiMetrics, + mWifiVendorHal, mSupplicantP2pIfaceHal, mHalDeviceManager, mPropertyService, this); + SubscriptionManager subscriptionManager = + mContext.getSystemService(SubscriptionManager.class); + if (SdkLevel.isAtLeastS()) { + mCoexManager = new CoexManager(mContext, mWifiNative, makeTelephonyManager(), + subscriptionManager, mContext.getSystemService(CarrierConfigManager.class), + wifiHandler); + } else { + mCoexManager = null; + } + + // Now get instances of all the objects that depend on the HandlerThreads + mWifiTrafficPoller = new WifiTrafficPoller(mContext); + // WifiConfigManager/Store objects and their dependencies. + KeyStore keyStore = null; + try { + keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(Process.WIFI_UID); + } catch (KeyStoreException | NoSuchProviderException e) { + Log.wtf(TAG, "Failed to load keystore", e); + } + mKeyStore = keyStore; + mWifiKeyStore = new WifiKeyStore(mContext, mKeyStore, mFrameworkFacade); + // New config store + mWifiConfigStore = new WifiConfigStore(mContext, wifiHandler, mClock, mWifiMetrics, + WifiConfigStore.createSharedFiles(mFrameworkFacade.isNiapModeOn(mContext))); + mWifiPseudonymManager = + new WifiPseudonymManager( + mContext, + this, + mClock, + mAlarmManager, + wifiLooper); + mWifiCarrierInfoManager = new WifiCarrierInfoManager(makeTelephonyManager(), + subscriptionManager, this, mFrameworkFacade, mContext, + mWifiConfigStore, wifiHandler, mWifiMetrics, mClock, mWifiPseudonymManager); + String l2KeySeed = Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID); + mWifiScoreCard = new WifiScoreCard(mClock, l2KeySeed, mDeviceConfigFacade, + mContext, mWifiGlobals); + mWifiMetrics.setWifiScoreCard(mWifiScoreCard); + mLruConnectionTracker = new LruConnectionTracker(MAX_RECENTLY_CONNECTED_NETWORK, + mContext); + mWifiConnectivityHelper = new WifiConnectivityHelper(this); + int maxLinesLowRam = mContext.getResources().getInteger( + R.integer.config_wifiConnectivityLocalLogMaxLinesLowRam); + int maxLinesHighRam = mContext.getResources().getInteger( + R.integer.config_wifiConnectivityLocalLogMaxLinesHighRam); + mConnectivityLocalLog = new LocalLog( + mContext.getSystemService(ActivityManager.class).isLowRamDevice() ? maxLinesLowRam + : maxLinesHighRam); + mWifiDiagnostics = new WifiDiagnostics( + mContext, this, mWifiNative, mBuildProperties, + new LastMileLogger(this), mClock, mWifiDiagnosticsHandlerThread.getLooper()); + mWifiLastResortWatchdog = new WifiLastResortWatchdog(this, mContext, mClock, + mWifiMetrics, mWifiDiagnostics, wifiLooper, + mDeviceConfigFacade, mWifiThreadRunner, mWifiMonitor); + mWifiBlocklistMonitor = + new WifiBlocklistMonitor( + mContext, + mWifiConnectivityHelper, + mWifiLastResortWatchdog, + mClock, + new LocalLog( + mContext.getSystemService(ActivityManager.class).isLowRamDevice() + ? 128 + : 256), + mWifiScoreCard, + mScoringParams, + mWifiMetrics, + mWifiPermissionsUtil, + mWifiGlobals); + mWifiMetrics.setWifiBlocklistMonitor(mWifiBlocklistMonitor); + // Config Manager + mWifiConfigManager = + new WifiConfigManager( + mContext, + mWifiKeyStore, + mWifiConfigStore, + new NetworkListSharedStoreData(mContext), + new NetworkListUserStoreData(mContext), + new RandomizedMacStoreData(), + mLruConnectionTracker, + this, + wifiHandler); + mSettingsConfigStore = new WifiSettingsConfigStore(context, wifiHandler, + mSettingsMigrationDataHolder, mWifiConfigManager, mWifiConfigStore); + mWifiSettingsBackupRestore = new WifiSettingsBackupRestore(mSettingsConfigStore); + mSettingsStore = new WifiSettingsStore(mContext, mSettingsConfigStore, mWifiThreadRunner, + mFrameworkFacade, mWifiNotificationManager, mDeviceConfigFacade, + mWifiMetrics, mClock); + mWifiMetrics.setWifiConfigManager(mWifiConfigManager); + mWifiMetrics.setWifiSettingsStore(mSettingsStore); + + mWifiMetrics.setScoringParams(mScoringParams); + mThroughputPredictor = new ThroughputPredictor(mContext); + mScanRequestProxy = new ScanRequestProxy(mContext, + mContext.getSystemService(AppOpsManager.class), + mContext.getSystemService(ActivityManager.class), + this, mWifiConfigManager, + mWifiPermissionsUtil, mWifiMetrics, mClock, wifiHandler, mSettingsConfigStore); + mWifiBlocklistMonitor.setScanRequestProxy(mScanRequestProxy); + mSarManager = new SarManager(mContext, makeTelephonyManager(), wifiLooper, + mWifiNative, mWifiDeviceStateChangeManager); + mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiScoreCard, mScoringParams, + mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiMetrics, this, + mThroughputPredictor, mWifiChannelUtilizationScan, mWifiGlobals, + mScanRequestProxy, mWifiNative); + CompatibilityScorer compatibilityScorer = new CompatibilityScorer(mScoringParams); + mWifiNetworkSelector.registerCandidateScorer(compatibilityScorer); + ScoreCardBasedScorer scoreCardBasedScorer = new ScoreCardBasedScorer(mScoringParams); + mWifiNetworkSelector.registerCandidateScorer(scoreCardBasedScorer); + BubbleFunScorer bubbleFunScorer = new BubbleFunScorer(mScoringParams); + mWifiNetworkSelector.registerCandidateScorer(bubbleFunScorer); + mThroughputScorer = new ThroughputScorer(mContext, mScoringParams); + mWifiNetworkSelector.registerCandidateScorer(mThroughputScorer); + mWifiMetrics.setWifiNetworkSelector(mWifiNetworkSelector); + mWifiNetworkSuggestionsManager = new WifiNetworkSuggestionsManager(mContext, wifiHandler, + this, mWifiPermissionsUtil, mWifiConfigManager, mWifiConfigStore, mWifiMetrics, + mWifiCarrierInfoManager, mWifiKeyStore, mLruConnectionTracker, + mClock); + mPasspointManager = new PasspointManager(mContext, this, + wifiHandler, mWifiNative, mWifiKeyStore, mClock, new PasspointObjectFactory(), + mWifiConfigManager, mWifiConfigStore, mSettingsStore, mWifiMetrics, + mWifiCarrierInfoManager, mMacAddressUtil, mWifiPermissionsUtil); + mNominateHelper = + new PasspointNetworkNominateHelper(mPasspointManager, mWifiConfigManager, + mConnectivityLocalLog, mWifiCarrierInfoManager, mContext.getResources(), + mClock); + mPasspointManager.setPasspointNetworkNominateHelper(mNominateHelper); + mSavedNetworkNominator = new SavedNetworkNominator( + mWifiConfigManager, mConnectivityLocalLog, mWifiCarrierInfoManager, + mWifiPseudonymManager, mWifiPermissionsUtil, mWifiNetworkSuggestionsManager); + mNetworkSuggestionNominator = new NetworkSuggestionNominator(mWifiNetworkSuggestionsManager, + mWifiConfigManager, mConnectivityLocalLog, mWifiCarrierInfoManager, + mWifiPseudonymManager, mWifiMetrics); + + mWifiMetrics.setPasspointManager(mPasspointManager); + WifiChannelUtilization wifiChannelUtilizationConnected = + new WifiChannelUtilization(mClock, mContext); + mWifiMetrics.setWifiChannelUtilization(wifiChannelUtilizationConnected); + mLinkProbeManager = new LinkProbeManager(mClock, mWifiNative, mWifiMetrics, + mFrameworkFacade, wifiHandler, mContext); + mDefaultClientModeManager = new DefaultClientModeManager(); + mExternalScoreUpdateObserverProxy = + new ExternalScoreUpdateObserverProxy(mWifiThreadRunner); + mDppManager = new DppManager(this, wifiHandler, mWifiNative, + mWifiConfigManager, mContext, mDppMetrics, mScanRequestProxy, mWifiPermissionsUtil); + mActiveModeWarden = new ActiveModeWarden(this, wifiLooper, + mWifiNative, mDefaultClientModeManager, mBatteryStats, mWifiDiagnostics, + mContext, mSettingsStore, mFrameworkFacade, mWifiPermissionsUtil, mWifiMetrics, + mExternalScoreUpdateObserverProxy, mDppManager, mWifiGlobals); + mWifiMetrics.setActiveModeWarden(mActiveModeWarden); + mWifiHealthMonitor = new WifiHealthMonitor(mContext, this, mClock, mWifiConfigManager, + mWifiScoreCard, wifiHandler, mWifiNative, l2KeySeed, mDeviceConfigFacade, + mActiveModeWarden); + mWifiDataStall = new WifiDataStall(mWifiMetrics, mContext, + mDeviceConfigFacade, wifiChannelUtilizationConnected, mClock, wifiHandler, + mThroughputPredictor, mActiveModeWarden, mCmiMonitor, mWifiGlobals); + mWifiMetrics.setWifiDataStall(mWifiDataStall); + mWifiMetrics.setWifiHealthMonitor(mWifiHealthMonitor); + mWifiP2pConnection = new WifiP2pConnection(mContext, wifiLooper, mActiveModeWarden); + mConnectHelper = new ConnectHelper(mActiveModeWarden, mWifiConfigManager); + mBroadcastQueue = new ClientModeManagerBroadcastQueue(mActiveModeWarden, mContext); + mMakeBeforeBreakManager = new MakeBeforeBreakManager(mActiveModeWarden, mFrameworkFacade, + mContext, mCmiMonitor, mBroadcastQueue, mWifiMetrics); + mOpenNetworkNotifier = new OpenNetworkNotifier(mContext, + wifiLooper, mFrameworkFacade, mClock, mWifiMetrics, + mWifiConfigManager, mWifiConfigStore, mConnectHelper, + new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade), + mMakeBeforeBreakManager, mWifiNotificationManager, mWifiPermissionsUtil); + mMultiInternetManager = new MultiInternetManager(mActiveModeWarden, mFrameworkFacade, + mContext, mCmiMonitor, mSettingsStore, wifiHandler, mClock); + mExternalPnoScanRequestManager = new ExternalPnoScanRequestManager(wifiHandler, mContext); + mCountryCode = new WifiCountryCode(mContext, mActiveModeWarden, mWifiP2pMetrics, + mCmiMonitor, mWifiNative, mSettingsConfigStore, mClock, mWifiPermissionsUtil); + mWifiConnectivityManager = new WifiConnectivityManager( + mContext, mScoringParams, mWifiConfigManager, + mWifiNetworkSuggestionsManager, mWifiNetworkSelector, + mWifiConnectivityHelper, mWifiLastResortWatchdog, mOpenNetworkNotifier, + mWifiMetrics, wifiHandler, + mClock, mConnectivityLocalLog, mWifiScoreCard, mWifiBlocklistMonitor, + mWifiChannelUtilizationScan, mPasspointManager, mMultiInternetManager, + mDeviceConfigFacade, mActiveModeWarden, mFrameworkFacade, mWifiGlobals, + mExternalPnoScanRequestManager, mSsidTranslator, mWifiPermissionsUtil, + mWifiCarrierInfoManager, mCountryCode, mWifiDialogManager, + mWifiDeviceStateChangeManager); + mMboOceController = new MboOceController(makeTelephonyManager(), mActiveModeWarden, + mWifiThreadRunner); + mConnectionFailureNotifier = new ConnectionFailureNotifier( + mContext, mFrameworkFacade, mWifiConfigManager, + mWifiConnectivityManager, wifiHandler, + mWifiNotificationManager, mConnectionFailureNotificationBuilder, + mWifiDialogManager); + mWifiNetworkFactory = new WifiNetworkFactory( + wifiLooper, mContext, REGULAR_NETWORK_CAPABILITIES_FILTER, + (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE), + (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE), + (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE), + mClock, this, mWifiConnectivityManager, mWifiConfigManager, + mWifiConfigStore, mWifiPermissionsUtil, mWifiMetrics, mWifiNative, + mActiveModeWarden, mConnectHelper, mCmiMonitor, mFrameworkFacade, + mMultiInternetManager); + // We can't filter untrusted network in the capabilities filter because a trusted + // network would still satisfy a request that accepts untrusted ones. + // We need a second network factory for untrusted network requests because we need a + // different score filter for these requests. + mUntrustedWifiNetworkFactory = new UntrustedWifiNetworkFactory( + wifiLooper, mContext, REGULAR_NETWORK_CAPABILITIES_FILTER, + mWifiConnectivityManager); + mOemWifiNetworkFactory = new OemWifiNetworkFactory( + wifiLooper, mContext, OEM_NETWORK_CAPABILITIES_FILTER, + mWifiConnectivityManager); + mRestrictedWifiNetworkFactory = new RestrictedWifiNetworkFactory( + wifiLooper, mContext, RESTRICTED_NETWORK_CAPABILITIES_FILTER, + mWifiConnectivityManager); + mMultiInternetWifiNetworkFactory = new MultiInternetWifiNetworkFactory( + wifiLooper, mContext, REGULAR_NETWORK_CAPABILITIES_FILTER, + mFrameworkFacade, mAlarmManager, + mWifiPermissionsUtil, mMultiInternetManager, mWifiConnectivityManager, + mConnectivityLocalLog); + mWifiScanAlwaysAvailableSettingsCompatibility = + new WifiScanAlwaysAvailableSettingsCompatibility(mContext, wifiHandler, + mSettingsStore, mActiveModeWarden, mFrameworkFacade); + mWifiApConfigStore = new WifiApConfigStore( + mContext, this, wifiHandler, mBackupManagerProxy, + mWifiConfigStore, mWifiConfigManager, mActiveModeWarden, mWifiMetrics); + WakeupNotificationFactory wakeupNotificationFactory = + new WakeupNotificationFactory(mContext, mFrameworkFacade); + WakeupOnboarding wakeupOnboarding = new WakeupOnboarding(mContext, mWifiConfigManager, + wifiHandler, mFrameworkFacade, wakeupNotificationFactory, mWifiNotificationManager); + mWakeupController = new WakeupController(mContext, wifiHandler, + new WakeupLock(mWifiConfigManager, mWifiMetrics.getWakeupMetrics(), mClock), + new WakeupEvaluator(mScoringParams), wakeupOnboarding, mWifiConfigManager, + mWifiConfigStore, mWifiNetworkSuggestionsManager, mWifiMetrics.getWakeupMetrics(), + this, mFrameworkFacade, mClock, mActiveModeWarden); + mLockManager = new WifiLockManager(mContext, mBatteryStats, mActiveModeWarden, + mFrameworkFacade, wifiHandler, mClock, mWifiMetrics, mDeviceConfigFacade, + mWifiPermissionsUtil, mWifiDeviceStateChangeManager); + mSelfRecovery = new SelfRecovery(mContext, mActiveModeWarden, mClock, mWifiNative, + mWifiGlobals); + mWifiMulticastLockManager = new WifiMulticastLockManager(mActiveModeWarden, mBatteryStats, + wifiLooper); + mApplicationQosPolicyRequestHandler = new ApplicationQosPolicyRequestHandler( + mActiveModeWarden, mWifiNative, mWifiHandlerThread, mDeviceConfigFacade, mContext); + + // Register the various network Nominators with the network selector. + mWifiNetworkSelector.registerNetworkNominator(mSavedNetworkNominator); + mWifiNetworkSelector.registerNetworkNominator(mNetworkSuggestionNominator); + + mSimRequiredNotifier = new SimRequiredNotifier(mContext, mFrameworkFacade, + mWifiNotificationManager); + mWifiPulledAtomLogger = new WifiPulledAtomLogger( + mContext.getSystemService(StatsManager.class), wifiHandler, + mContext, this); + mAfcLocationUtil = new AfcLocationUtil(); + mAfcClient = new AfcClient(BackgroundThread.getHandler()); + mContextWithAttributionTag = new WifiContext( + mContext.createAttributionContext("WifiService")); + // The context needs to have an Attribution Tag set to get the current location using + // {@link LocationManager#getCurrentLocation}, so we need to pass mContextWithAttributionTag + // instead of mContext to the AfcManager. + mAfcManager = new AfcManager(mContextWithAttributionTag, this); + mWifiRoamingModeManager = new WifiRoamingModeManager(mWifiNative, + mActiveModeWarden, new WifiRoamingConfigStore(mWifiConfigManager, + mWifiConfigStore)); + + mTwtManager = new TwtManager(this, mCmiMonitor, mWifiNative, wifiHandler, mClock, + WifiTwtSession.MAX_TWT_SESSIONS, 1); + mBackupRestoreController = new BackupRestoreController(mWifiSettingsBackupRestore, mClock); + } + + /** + * Obtain an instance of the WifiInjector class. + * + * This is the generic method to get an instance of the class. The first instance should be + * retrieved using the getInstanceWithContext method. + */ + public static WifiInjector getInstance() { + if (sWifiInjector == null) { + throw new IllegalStateException( + "Attempted to retrieve a WifiInjector instance before constructor was called."); + } + return sWifiInjector; + } + + /** + * Enable verbose logging in Injector objects. Called from the WifiServiceImpl (based on + * binder call). + */ + public void enableVerboseLogging(boolean verboseEnabled, boolean halVerboseEnabled) { + Log.i(TAG, "enableVerboseLogging " + verboseEnabled + " hal " + halVerboseEnabled); + mWifiLastResortWatchdog.enableVerboseLogging(verboseEnabled); + mWifiBackupRestore.enableVerboseLogging(verboseEnabled); + mHalDeviceManager.enableVerboseLogging(verboseEnabled); + mScanRequestProxy.enableVerboseLogging(verboseEnabled); + mInterfaceConflictManager.enableVerboseLogging(verboseEnabled); + mWakeupController.enableVerboseLogging(verboseEnabled); + mWifiNetworkSuggestionsManager.enableVerboseLogging(verboseEnabled); + LogcatLog.enableVerboseLogging(verboseEnabled); + mDppManager.enableVerboseLogging(verboseEnabled); + mWifiCarrierInfoManager.enableVerboseLogging(verboseEnabled); + mWifiPseudonymManager.enableVerboseLogging(verboseEnabled); + mCountryCode.enableVerboseLogging(verboseEnabled); + mWifiDiagnostics.enableVerboseLogging(verboseEnabled, halVerboseEnabled); + mWifiMonitor.enableVerboseLogging(verboseEnabled); + mWifiNative.enableVerboseLogging(verboseEnabled, halVerboseEnabled); + mWifiConfigManager.enableVerboseLogging(verboseEnabled); + mPasspointManager.enableVerboseLogging(verboseEnabled); + mWifiNetworkFactory.enableVerboseLogging(verboseEnabled); + mLinkProbeManager.enableVerboseLogging(verboseEnabled); + mMboOceController.enableVerboseLogging(verboseEnabled); + mWifiScoreCard.enableVerboseLogging(verboseEnabled); + mWifiHealthMonitor.enableVerboseLogging(verboseEnabled); + mThroughputPredictor.enableVerboseLogging(verboseEnabled); + mWifiDataStall.enableVerboseLogging(verboseEnabled); + mWifiConnectivityManager.enableVerboseLogging(verboseEnabled); + mThroughputScorer.enableVerboseLogging(verboseEnabled); + mWifiNetworkSelector.enableVerboseLogging(verboseEnabled); + mMakeBeforeBreakManager.setVerboseLoggingEnabled(verboseEnabled); + mMultiInternetManager.setVerboseLoggingEnabled(verboseEnabled); + mBroadcastQueue.setVerboseLoggingEnabled(verboseEnabled); + if (SdkLevel.isAtLeastS()) { + mCoexManager.enableVerboseLogging(verboseEnabled); + } + mWifiPermissionsUtil.enableVerboseLogging(verboseEnabled); + mWifiDialogManager.enableVerboseLogging(verboseEnabled); + mExternalPnoScanRequestManager.enableVerboseLogging(verboseEnabled); + mMultiInternetWifiNetworkFactory.enableVerboseLogging(verboseEnabled); + mWifiRoamingModeManager.enableVerboseLogging(verboseEnabled); + } + + public UserManager getUserManager() { + return mUserManager; + } + + public WifiMetrics getWifiMetrics() { + return mWifiMetrics; + } + + public WifiP2pMetrics getWifiP2pMetrics() { + return mWifiP2pMetrics; + } + + public SupplicantStaIfaceHal getSupplicantStaIfaceHal() { + return mSupplicantStaIfaceHal; + } + + public BackupManagerProxy getBackupManagerProxy() { + return mBackupManagerProxy; + } + + public FrameworkFacade getFrameworkFacade() { + return mFrameworkFacade; + } + + public HandlerThread getWifiP2pServiceHandlerThread() { + if (mFeatureFlags.singleWifiThread()) { + return mWifiHandlerThread; + } + return mWifiP2pServiceHandlerThread; + } + + public HandlerThread getPasspointProvisionerHandlerThread() { + return mPasspointProvisionerHandlerThread; + } + + public HandlerThread getWifiHandlerThread() { + return mWifiHandlerThread; + } + + public MockWifiServiceUtil getMockWifiServiceUtil() { + return mMockWifiModem; + } + + public void setMockWifiServiceUtil(MockWifiServiceUtil mockWifiServiceUtil) { + mMockWifiModem = mockWifiServiceUtil; + } + + /** + * Wrapper method for getting the current native Java Thread ID of the current thread. + */ + public long getCurrentThreadId() { + return Thread.currentThread().getId(); + } + + public WifiTrafficPoller getWifiTrafficPoller() { + return mWifiTrafficPoller; + } + + public WifiCountryCode getWifiCountryCode() { + return mCountryCode; + } + + public WifiApConfigStore getWifiApConfigStore() { + return mWifiApConfigStore; + } + + public SarManager getSarManager() { + return mSarManager; + } + + public ActiveModeWarden getActiveModeWarden() { + return mActiveModeWarden; + } + + public WifiSettingsStore getWifiSettingsStore() { + return mSettingsStore; + } + + public WifiLockManager getWifiLockManager() { + return mLockManager; + } + + public WifiLastResortWatchdog getWifiLastResortWatchdog() { + return mWifiLastResortWatchdog; + } + + public Clock getClock() { + return mClock; + } + + public WifiBackupRestore getWifiBackupRestore() { + return mWifiBackupRestore; + } + + public SoftApBackupRestore getSoftApBackupRestore() { + return mSoftApBackupRestore; + } + + public WifiMulticastLockManager getWifiMulticastLockManager() { + return mWifiMulticastLockManager; + } + + public WifiConfigManager getWifiConfigManager() { + return mWifiConfigManager; + } + + public PasspointManager getPasspointManager() { + return mPasspointManager; + } + + public WakeupController getWakeupController() { + return mWakeupController; + } + + public ScoringParams getScoringParams() { + return mScoringParams; + } + + public WifiScoreCard getWifiScoreCard() { + return mWifiScoreCard; + } + + public TelephonyManager makeTelephonyManager() { + return mContext.getSystemService(TelephonyManager.class); + } + + /** + * Returns BatteryManager service + */ + public BatteryManager makeBatteryManager() { + return mContext.getSystemService(BatteryManager.class); + } + + public WifiCarrierInfoManager getWifiCarrierInfoManager() { + return mWifiCarrierInfoManager; + } + + public WifiPseudonymManager getWifiPseudonymManager() { + return mWifiPseudonymManager; + } + + public DppManager getDppManager() { + return mDppManager; + } + + /** + * Create a WifiShellCommand. + * + * @param wifiService WifiServiceImpl object shell commands get sent to. + * @return an instance of WifiShellCommand + */ + public WifiShellCommand makeWifiShellCommand(WifiServiceImpl wifiService) { + return new WifiShellCommand(this, wifiService, mContext, + mWifiGlobals, mWifiThreadRunner); + } + + /** + * Create a SoftApManager. + * + * @param config SoftApModeConfiguration object holding the config and mode + * @return an instance of SoftApManager + */ + public SoftApManager makeSoftApManager( + @NonNull ActiveModeManager.Listener listener, + @NonNull WifiServiceImpl.SoftApCallbackInternal callback, + @NonNull SoftApModeConfiguration config, + @NonNull WorkSource requestorWs, + @NonNull ActiveModeManager.SoftApRole role, + boolean verboseLoggingEnabled) { + return new SoftApManager(mContext, mWifiHandlerThread.getLooper(), mFrameworkFacade, + mWifiNative, this, mCoexManager, mInterfaceConflictManager, + listener, callback, mWifiApConfigStore, + config, mWifiMetrics, mSarManager, mWifiDiagnostics, + new SoftApNotifier(mContext, mFrameworkFacade, mWifiNotificationManager), + mCmiMonitor, mActiveModeWarden, mClock.getElapsedSinceBootMillis(), + requestorWs, role, verboseLoggingEnabled); + } + + /** + * Create a ClientModeImpl + * @param ifaceName interface name for the ClientModeImpl + * @param clientModeManager ClientModeManager that will own the ClientModeImpl + */ + public ClientModeImpl makeClientModeImpl( + @NonNull String ifaceName, + @NonNull ConcreteClientModeManager clientModeManager, + boolean verboseLoggingEnabled) { + ExtendedWifiInfo wifiInfo = new ExtendedWifiInfo(mWifiGlobals, ifaceName); + SupplicantStateTracker supplicantStateTracker = new SupplicantStateTracker( + mContext, mWifiConfigManager, mBatteryStats, mWifiHandlerThread.getLooper(), + mWifiMonitor, ifaceName, clientModeManager, mBroadcastQueue); + supplicantStateTracker.enableVerboseLogging(verboseLoggingEnabled); + return new ClientModeImpl(mContext, mWifiMetrics, mClock, + mWifiScoreCard, mWifiStateTracker, mWifiPermissionsUtil, mWifiConfigManager, + mPasspointManager, mWifiMonitor, mWifiDiagnostics, + mWifiDataStall, mScoringParams, mWifiThreadRunner, + mWifiNetworkSuggestionsManager, mWifiHealthMonitor, mThroughputPredictor, + mDeviceConfigFacade, mScanRequestProxy, wifiInfo, mWifiConnectivityManager, + mWifiBlocklistMonitor, mConnectionFailureNotifier, + REGULAR_NETWORK_CAPABILITIES_FILTER, mWifiNetworkFactory, + mUntrustedWifiNetworkFactory, mOemWifiNetworkFactory, mRestrictedWifiNetworkFactory, + mMultiInternetManager, mWifiLastResortWatchdog, mWakeupController, + mLockManager, mFrameworkFacade, mWifiHandlerThread.getLooper(), + mWifiNative, new WrongPasswordNotifier(mContext, mFrameworkFacade, + mWifiNotificationManager), + mWifiTrafficPoller, mLinkProbeManager, mClock.getElapsedSinceBootMillis(), + mBatteryStats, supplicantStateTracker, mMboOceController, mWifiCarrierInfoManager, + mWifiPseudonymManager, + new EapFailureNotifier(mContext, mFrameworkFacade, mWifiCarrierInfoManager, + mWifiNotificationManager, mWifiGlobals), + mSimRequiredNotifier, + new WifiScoreReport(mScoringParams, mClock, mWifiMetrics, wifiInfo, + mWifiNative, mWifiBlocklistMonitor, mWifiThreadRunner, mWifiScoreCard, + mDeviceConfigFacade, mContext, mAdaptiveConnectivityEnabledSettingObserver, + ifaceName, mExternalScoreUpdateObserverProxy, mSettingsStore, mWifiGlobals, + mActiveModeWarden, mWifiConnectivityManager, mWifiConfigManager), + mWifiP2pConnection, mWifiGlobals, ifaceName, clientModeManager, + mCmiMonitor, mBroadcastQueue, mWifiNetworkSelector, makeTelephonyManager(), + this, mSettingsConfigStore, verboseLoggingEnabled, mWifiNotificationManager, + mWifiConnectivityHelper); + } + + public WifiNetworkAgent makeWifiNetworkAgent( + @NonNull NetworkCapabilities nc, + @NonNull LinkProperties linkProperties, + @NonNull NetworkAgentConfig naConfig, + @Nullable NetworkProvider provider, + @NonNull WifiNetworkAgent.Callback callback) { + return new WifiNetworkAgent(mContext, mWifiHandlerThread.getLooper(), + nc, linkProperties, naConfig, provider, callback); + } + + /** + * Create a ClientModeManager + * + * @param listener listener for ClientModeManager state changes + * @return a new instance of ClientModeManager + */ + public ConcreteClientModeManager makeClientModeManager( + @NonNull ClientModeManager.Listener listener, + @NonNull WorkSource requestorWs, + @NonNull ActiveModeManager.ClientRole role, + boolean verboseLoggingEnabled) { + return new ConcreteClientModeManager( + mContext, mWifiHandlerThread.getLooper(), mClock, + mWifiNative, listener, mWifiMetrics, mWakeupController, + this, mSelfRecovery, mWifiGlobals, mDefaultClientModeManager, + mClock.getElapsedSinceBootMillis(), requestorWs, role, mBroadcastQueue, + verboseLoggingEnabled); + } + + public ScanOnlyModeImpl makeScanOnlyModeImpl(@NonNull String ifaceName) { + return new ScanOnlyModeImpl(mClock.getElapsedSinceBootMillis(), mWifiNative, ifaceName); + } + + /** + * Create a WifiLog instance. + * + * @param tag module name to include in all log messages + */ + public WifiLog makeLog(String tag) { + return new LogcatLog(tag); + } + + /** + * Obtain an instance of WifiScanner. + * If it was not already created, then obtain an instance. Note, this must be done lazily since + * WifiScannerService is separate and created later. + */ + public synchronized WifiScanner getWifiScanner() { + if (mWifiScanner == null) { + mWifiScanner = mContext.getSystemService(WifiScanner.class); + } + return mWifiScanner; + } + + /** + * Construct an instance of {@link NetworkRequestStoreData}. + */ + public NetworkRequestStoreData makeNetworkRequestStoreData( + NetworkRequestStoreData.DataSource dataSource) { + return new NetworkRequestStoreData(dataSource); + } + + /** + * Construct an instance of {@link NetworkSuggestionStoreData}. + */ + public NetworkSuggestionStoreData makeNetworkSuggestionStoreData( + NetworkSuggestionStoreData.DataSource dataSource) { + return new NetworkSuggestionStoreData(dataSource); + } + + /** + * Construct an instance of {@link WifiCarrierInfoStoreManagerData} + */ + public WifiCarrierInfoStoreManagerData makeWifiCarrierInfoStoreManagerData( + WifiCarrierInfoStoreManagerData.DataSource dataSource) { + return new WifiCarrierInfoStoreManagerData(dataSource); + } + + /** + * Construct an instance of {@link ImsiPrivacyProtectionExemptionStoreData} + */ + public ImsiPrivacyProtectionExemptionStoreData makeImsiPrivacyProtectionExemptionStoreData( + ImsiPrivacyProtectionExemptionStoreData.DataSource dataSource) { + return new ImsiPrivacyProtectionExemptionStoreData(dataSource); + } + + /** + * Construct an instance of {@link SoftApStoreData}. + */ + public SoftApStoreData makeSoftApStoreData( + SoftApStoreData.DataSource dataSource) { + return new SoftApStoreData(mContext, mSettingsMigrationDataHolder, dataSource); + } + + public WifiPermissionsUtil getWifiPermissionsUtil() { + return mWifiPermissionsUtil; + } + + public WifiPermissionsWrapper getWifiPermissionsWrapper() { + return mWifiPermissionsWrapper; + } + + /** + * Returns a singleton instance of a HandlerThread for injection. Uses lazy initialization. + * + * TODO: share worker thread with other Wi-Fi handlers (b/27924886) + */ + public HandlerThread getWifiAwareHandlerThread() { + if (mFeatureFlags.singleWifiThread()) { + return mWifiHandlerThread; + } + if (mWifiAwareHandlerThread == null) { // lazy initialization + mWifiAwareHandlerThread = new HandlerThread("wifiAwareService"); + mWifiAwareHandlerThread.start(); + } + return mWifiAwareHandlerThread; + } + + /** + * Returns a singleton instance of a HandlerThread for injection. Uses lazy initialization. + * + * TODO: share worker thread with other Wi-Fi handlers (b/27924886) + */ + public HandlerThread getRttHandlerThread() { + if (mFeatureFlags.singleWifiThread()) { + return mWifiHandlerThread; + } + if (mRttHandlerThread == null) { // lazy initialization + mRttHandlerThread = new HandlerThread("wifiRttService"); + mRttHandlerThread.start(); + } + return mRttHandlerThread; + } + + public MacAddressUtil getMacAddressUtil() { + return mMacAddressUtil; + } + + /** + * Returns a single instance of HalDeviceManager for injection. + */ + public HalDeviceManager getHalDeviceManager() { + return mHalDeviceManager; + } + + public WifiNative getWifiNative() { + return mWifiNative; + } + + public WifiMonitor getWifiMonitor() { + return mWifiMonitor; + } + + public WifiP2pNative getWifiP2pNative() { + return mWifiP2pNative; + } + + /** + * Returns a single instance of CoexManager for injection. + * This will be null if SdkLevel is not at least S. + */ + @Nullable public CoexManager getCoexManager() { + return mCoexManager; + } + + public WifiP2pMonitor getWifiP2pMonitor() { + return mWifiP2pMonitor; + } + + public SelfRecovery getSelfRecovery() { + return mSelfRecovery; + } + + public ScanRequestProxy getScanRequestProxy() { + return mScanRequestProxy; + } + + public Runtime getJavaRuntime() { + return Runtime.getRuntime(); + } + + public WifiDataStall getWifiDataStall() { + return mWifiDataStall; + } + + public WifiPulledAtomLogger getWifiPulledAtomLogger() { + return mWifiPulledAtomLogger; + } + + public WifiNetworkSuggestionsManager getWifiNetworkSuggestionsManager() { + return mWifiNetworkSuggestionsManager; + } + + public IpMemoryStore getIpMemoryStore() { + if (mIpMemoryStore == null) { + mIpMemoryStore = IpMemoryStore.getMemoryStore(mContext); + } + return mIpMemoryStore; + } + + public WifiBlocklistMonitor getWifiBlocklistMonitor() { + return mWifiBlocklistMonitor; + } + + public HostapdHal getHostapdHal() { + return mHostapdHal; + } + + public WifiThreadRunner getWifiThreadRunner() { + return mWifiThreadRunner; + } + + public NetdWrapper makeNetdWrapper() { + if (mNetdWrapper == null) { + mNetdWrapper = new NetdWrapper(mContext, new Handler(mWifiHandlerThread.getLooper())); + } + return mNetdWrapper; + } + + public WifiNl80211Manager getWifiCondManager() { + return mWifiCondManager; + } + + public WifiHealthMonitor getWifiHealthMonitor() { + return mWifiHealthMonitor; + } + + public WifiSettingsConfigStore getSettingsConfigStore() { + return mSettingsConfigStore; + } + + @NonNull + public WifiSettingsBackupRestore getWifiSettingsBackupRestore() { + return mWifiSettingsBackupRestore; + } + + public WifiScanAlwaysAvailableSettingsCompatibility + getWifiScanAlwaysAvailableSettingsCompatibility() { + return mWifiScanAlwaysAvailableSettingsCompatibility; + } + + public DeviceConfigFacade getDeviceConfigFacade() { + return mDeviceConfigFacade; + } + + public WifiConnectivityManager getWifiConnectivityManager() { + return mWifiConnectivityManager; + } + + public ConnectHelper getConnectHelper() { + return mConnectHelper; + } + + public WifiNetworkFactory getWifiNetworkFactory() { + return mWifiNetworkFactory; + } + + public UntrustedWifiNetworkFactory getUntrustedWifiNetworkFactory() { + return mUntrustedWifiNetworkFactory; + } + + public OemWifiNetworkFactory getOemWifiNetworkFactory() { + return mOemWifiNetworkFactory; + } + + public RestrictedWifiNetworkFactory getRestrictedWifiNetworkFactory() { + return mRestrictedWifiNetworkFactory; + } + + public MultiInternetWifiNetworkFactory getMultiInternetWifiNetworkFactory() { + return mMultiInternetWifiNetworkFactory; + } + + public WifiDiagnostics getWifiDiagnostics() { + return mWifiDiagnostics; + } + + public WifiP2pConnection getWifiP2pConnection() { + return mWifiP2pConnection; + } + + public WifiGlobals getWifiGlobals() { + return mWifiGlobals; + } + + public SimRequiredNotifier getSimRequiredNotifier() { + return mSimRequiredNotifier; + } + + /** + * Useful for mocking {@link WorkSourceHelper} instance in {@link HalDeviceManager} unit tests. + */ + public WorkSourceHelper makeWsHelper(@NonNull WorkSource ws) { + return new WorkSourceHelper(ws, mWifiPermissionsUtil, + mContext.getSystemService(ActivityManager.class), mContext.getPackageManager(), + mContext.getResources()); + } + + public AdaptiveConnectivityEnabledSettingObserver + getAdaptiveConnectivityEnabledSettingObserver() { + return mAdaptiveConnectivityEnabledSettingObserver; + } + + /** + * Creates a BroadcastOptions. + */ + // TODO(b/193460475): Remove when tooling supports SystemApi to public API. + @SuppressLint("NewApi") + public BroadcastOptions makeBroadcastOptions() { + return BroadcastOptions.makeBasic(); + } + + public MakeBeforeBreakManager getMakeBeforeBreakManager() { + return mMakeBeforeBreakManager; + } + + public OpenNetworkNotifier getOpenNetworkNotifier() { + return mOpenNetworkNotifier; + } + + public WifiNotificationManager getWifiNotificationManager() { + return mWifiNotificationManager; + } + + public InterfaceConflictManager getInterfaceConflictManager() { + return mInterfaceConflictManager; + } + + public LastCallerInfoManager getLastCallerInfoManager() { + return mLastCallerInfoManager; + } + + @NonNull + public WifiDialogManager getWifiDialogManager() { + return mWifiDialogManager; + } + + @NonNull + public SsidTranslator getSsidTranslator() { + return mSsidTranslator; + } + + public BuildProperties getBuildProperties() { + return mBuildProperties; + } + + public DefaultClientModeManager getDefaultClientModeManager() { + return mDefaultClientModeManager; + } + + public LinkProbeManager getLinkProbeManager() { + return mLinkProbeManager; + } + + public MultiInternetManager getMultiInternetManager() { + return mMultiInternetManager; + } + + public AfcManager getAfcManager() { + return mAfcManager; + } + + public WifiContext getContext() { + return mContext; + } + + public WifiContext getContextWithAttributionTag() { + return mContextWithAttributionTag; + } + + public AfcLocationUtil getAfcLocationUtil() { + return mAfcLocationUtil; + } + + public AfcClient getAfcClient() { + return mAfcClient; + } + + /** + * Creates a BufferedReader for the given filename. Useful for unit tests that depend on IO. + */ + @NonNull + public BufferedReader createBufferedReader(String filename) throws FileNotFoundException { + return new BufferedReader(new FileReader(filename)); + } + + @NonNull + public LocalLog getWifiHandlerLocalLog() { + return mWifiHandlerLocalLog; + } + + @NonNull + public WifiKeyStore getWifiKeyStore() { + return mWifiKeyStore; + } + + @NonNull + public ApplicationQosPolicyRequestHandler getApplicationQosPolicyRequestHandler() { + return mApplicationQosPolicyRequestHandler; + } + + @NonNull + public WifiDeviceStateChangeManager getWifiDeviceStateChangeManager() { + return mWifiDeviceStateChangeManager; + } + + @NonNull + public PasspointNetworkNominateHelper getPasspointNetworkNominateHelper() { + return mNominateHelper; + } + + @NonNull + public AlarmManager getAlarmManager() { + return mAlarmManager; + } + + public WifiRoamingModeManager getWifiRoamingModeManager() { + return mWifiRoamingModeManager; + } + + public TwtManager getTwtManager() { + return mTwtManager; + } + + @NonNull + public BackupRestoreController getBackupRestoreController() { + return mBackupRestoreController; + } +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java new file mode 100644 index 000000000..103d7982a --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java @@ -0,0 +1,5447 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE; + +import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP; +import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP_BRIDGE; +import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_STA; +import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_P2P; +import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_NATIVE_SUPPORTED_FEATURES; +import static com.android.server.wifi.p2p.WifiP2pNative.P2P_IFACE_NAME; +import static com.android.server.wifi.p2p.WifiP2pNative.P2P_INTERFACE_PROPERTY; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.hardware.wifi.WifiStatusCode; +import android.net.MacAddress; +import android.net.TrafficStats; +import android.net.apf.ApfCapabilities; +import android.net.wifi.CoexUnsafeChannel; +import android.net.wifi.MscsParams; +import android.net.wifi.OuiKeyedData; +import android.net.wifi.QosPolicyParams; +import android.net.wifi.ScanResult; +import android.net.wifi.SecurityParams; +import android.net.wifi.SoftApConfiguration; +import android.net.wifi.WifiAnnotations; +import android.net.wifi.WifiAvailableChannel; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiContext; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiManager.RoamingMode; +import android.net.wifi.WifiScanner; +import android.net.wifi.WifiScanner.ScanData; +import android.net.wifi.WifiSsid; +import android.net.wifi.nl80211.DeviceWiphyCapabilities; +import android.net.wifi.nl80211.NativeScanResult; +import android.net.wifi.nl80211.NativeWifiClient; +import android.net.wifi.nl80211.RadioChainInfo; +import android.net.wifi.nl80211.WifiNl80211Manager; +import android.net.wifi.twt.TwtRequest; +import android.net.wifi.twt.TwtSessionCallback; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.SystemClock; +import android.os.WorkSource; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import com.android.internal.annotations.Immutable; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.HexDump; +import com.android.modules.utils.build.SdkLevel; +import com.android.server.wifi.SupplicantStaIfaceHal.QosPolicyStatus; +import com.android.server.wifi.hal.WifiChip; +import com.android.server.wifi.hotspot2.NetworkDetail; +import com.android.server.wifi.mockwifi.MockWifiServiceUtil; +import com.android.server.wifi.proto.WifiStatsLog; +import com.android.server.wifi.util.FrameParser; +import com.android.server.wifi.util.InformationElementUtil; +import com.android.server.wifi.util.NativeUtil; +import com.android.server.wifi.util.NetdWrapper; +import com.android.server.wifi.util.NetdWrapper.NetdEventObserver; +import com.android.wifi.resources.R; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.TimeZone; + +/** + * Native calls for bring up/shut down of the supplicant daemon and for + * sending requests to the supplicant daemon + * + * {@hide} + */ +public class WifiNative { + private static final String TAG = "WifiNative"; + + private final SupplicantStaIfaceHal mSupplicantStaIfaceHal; + private final HostapdHal mHostapdHal; + private final WifiVendorHal mWifiVendorHal; + private final WifiNl80211Manager mWifiCondManager; + private final WifiMonitor mWifiMonitor; + private final PropertyService mPropertyService; + private final WifiMetrics mWifiMetrics; + private final Handler mHandler; + private final Random mRandom; + private final BuildProperties mBuildProperties; + private final WifiInjector mWifiInjector; + private final WifiContext mContext; + private NetdWrapper mNetdWrapper; + private boolean mVerboseLoggingEnabled = false; + private boolean mIsEnhancedOpenSupported = false; + private final List mCachedCoexUnsafeChannels = new ArrayList<>(); + private int mCachedCoexRestrictions; + private CountryCodeChangeListenerInternal mCountryCodeChangeListener; + private boolean mUseFakeScanDetails; + private final ArrayList mFakeScanDetails = new ArrayList<>(); + private long mCachedFeatureSet; + private boolean mQosPolicyFeatureEnabled = false; + private final Map mWifiCondIfacesForBridgedAp = new ArrayMap<>(); + private MockWifiServiceUtil mMockWifiModem = null; + private InterfaceObserverInternal mInterfaceObserver; + private InterfaceEventCallback mInterfaceListener; + private @WifiManager.MloMode int mCachedMloMode = WifiManager.MLO_MODE_DEFAULT; + private boolean mIsLocationModeEnabled = false; + private long mLastLocationModeEnabledTimeMs = 0; + private Map mCachedTwtCapabilities = new ArrayMap<>(); + /** + * Mapping of unknown AKMs configured in overlay config item + * config_wifiUnknownAkmToKnownAkmMapping to ScanResult security key management scheme + * (ScanResult.KEY_MGMT_XX) + */ + @VisibleForTesting @Nullable SparseIntArray mUnknownAkmMap; + + public WifiNative(WifiVendorHal vendorHal, + SupplicantStaIfaceHal staIfaceHal, HostapdHal hostapdHal, + WifiNl80211Manager condManager, WifiMonitor wifiMonitor, + PropertyService propertyService, WifiMetrics wifiMetrics, + Handler handler, Random random, BuildProperties buildProperties, + WifiInjector wifiInjector) { + mWifiVendorHal = vendorHal; + mSupplicantStaIfaceHal = staIfaceHal; + mHostapdHal = hostapdHal; + mWifiCondManager = condManager; + mWifiMonitor = wifiMonitor; + mPropertyService = propertyService; + mWifiMetrics = wifiMetrics; + mHandler = handler; + mRandom = random; + mBuildProperties = buildProperties; + mWifiInjector = wifiInjector; + mContext = wifiInjector.getContext(); + initializeUnknownAkmMapping(); + } + + private void initializeUnknownAkmMapping() { + String[] unknownAkmMapping = + mContext.getResources() + .getStringArray(R.array.config_wifiUnknownAkmToKnownAkmMapping); + if (unknownAkmMapping == null) { + return; + } + for (String line : unknownAkmMapping) { + if (line == null) { + continue; + } + String[] items = line.split(","); + if (items.length != 2) { + Log.e( + TAG, + "Failed to parse config_wifiUnknownAkmToKnownAkmMapping line=" + + line + + ". Should contain only two values separated by comma"); + continue; + } + try { + int unknownAkm = Integer.parseInt(items[0].trim()); + int knownAkm = Integer.parseInt(items[1].trim()); + // Convert the OEM configured known AKM suite selector to + // ScanResult security key management scheme(ScanResult.KEY_MGMT_XX)*/ + int keyMgmtScheme = + InformationElementUtil.Capabilities.akmToScanResultKeyManagementScheme( + knownAkm); + if (keyMgmtScheme != ScanResult.KEY_MGMT_UNKNOWN) { + if (mUnknownAkmMap == null) { + mUnknownAkmMap = new SparseIntArray(); + } + mUnknownAkmMap.put(unknownAkm, keyMgmtScheme); + Log.d( + TAG, + "unknown AKM = " + + unknownAkm + + " - converted keyMgmtScheme: " + + keyMgmtScheme); + } else { + Log.e( + TAG, + "Known AKM: " + + knownAkm + + " is not defined in the framework." + + " Hence Failed to add AKM: " + + unknownAkm + + " in UnknownAkmMap." + + " Parsed config from overlay: " + + line); + } + } catch (Exception e) { + // failure to parse. Something is wrong with the configuration. + Log.e( + TAG, + "Parsing config_wifiUnknownAkmToKnownAkmMapping line=" + + line + + ". Exception occurred:" + + e); + } + } + } + + /** + * Enable verbose logging for all sub modules. + */ + public void enableVerboseLogging(boolean verboseEnabled, boolean halVerboseEnabled) { + Log.d(TAG, "enableVerboseLogging " + verboseEnabled + " hal " + halVerboseEnabled); + mVerboseLoggingEnabled = verboseEnabled; + mWifiCondManager.enableVerboseLogging(verboseEnabled); + mSupplicantStaIfaceHal.enableVerboseLogging(verboseEnabled, halVerboseEnabled); + mHostapdHal.enableVerboseLogging(verboseEnabled, halVerboseEnabled); + mWifiVendorHal.enableVerboseLogging(verboseEnabled, halVerboseEnabled); + mIfaceMgr.enableVerboseLogging(verboseEnabled); + } + + /** + * Get TWT capabilities for the interface + */ + public Bundle getTwtCapabilities(String interfaceName) { + return mCachedTwtCapabilities.get(interfaceName); + } + + /** + * Callbacks for SoftAp interface. + */ + public class SoftApHalCallbackFromWificond implements WifiNl80211Manager.SoftApCallback { + // placeholder for now - provide a shell so that clients don't use a + // WifiNl80211Manager-specific API. + private String mIfaceName; + private SoftApHalCallback mSoftApHalCallback; + + SoftApHalCallbackFromWificond(String ifaceName, + SoftApHalCallback softApHalCallback) { + mIfaceName = ifaceName; + mSoftApHalCallback = softApHalCallback; + } + + @Override + public void onFailure() { + mSoftApHalCallback.onFailure(); + } + + @Override + public void onSoftApChannelSwitched(int frequency, int bandwidth) { + mSoftApHalCallback.onInfoChanged(mIfaceName, frequency, bandwidth, + ScanResult.WIFI_STANDARD_UNKNOWN, null, Collections.emptyList()); + } + + @Override + public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) { + mSoftApHalCallback.onConnectedClientsChanged(mIfaceName, + client.getMacAddress(), isConnected); + } + } + + @SuppressLint("NewApi") + private static class CountryCodeChangeListenerInternal implements + WifiNl80211Manager.CountryCodeChangedListener { + private WifiCountryCode.ChangeListener mListener; + + public void setChangeListener(@NonNull WifiCountryCode.ChangeListener listener) { + mListener = listener; + } + + public void onSetCountryCodeSucceeded(String country) { + Log.d(TAG, "onSetCountryCodeSucceeded: " + country); + if (mListener != null) { + mListener.onSetCountryCodeSucceeded(country); + } + } + + @Override + public void onCountryCodeChanged(String country) { + Log.d(TAG, "onCountryCodeChanged: " + country); + if (mListener != null) { + mListener.onDriverCountryCodeChanged(country); + } + } + } + + /** + * Callbacks for SoftAp instance. + */ + public interface SoftApHalCallback { + /** + * Invoked when there is a fatal failure and the SoftAp is shutdown. + */ + void onFailure(); + + /** + * Invoked when there is a fatal happen in specific instance only. + */ + default void onInstanceFailure(String instanceName) {} + + /** + * Invoked when a channel switch event happens - i.e. the SoftAp is moved to a different + * channel. Also called on initial registration. + * + * @param apIfaceInstance The identity of the ap instance. + * @param frequency The new frequency of the SoftAp. A value of 0 is invalid and is an + * indication that the SoftAp is not enabled. + * @param bandwidth The new bandwidth of the SoftAp. + * @param generation The new generation of the SoftAp. + * @param vendorData List of {@link OuiKeyedData} containing vendor-specific configuration + * data, or empty list if not provided. + */ + void onInfoChanged(String apIfaceInstance, int frequency, int bandwidth, + int generation, MacAddress apIfaceInstanceMacAddress, + @NonNull List vendorData); + /** + * Invoked when there is a change in the associated station (STA). + * + * @param apIfaceInstance The identity of the ap instance. + * @param clientAddress Macaddress of the client. + * @param isConnected Indication as to whether the client is connected (true), or + * disconnected (false). + */ + void onConnectedClientsChanged(String apIfaceInstance, MacAddress clientAddress, + boolean isConnected); + } + + /******************************************************** + * Interface management related methods. + ********************************************************/ + /** + * Meta-info about every iface that is active. + */ + public static class Iface { + /** Type of ifaces possible */ + public static final int IFACE_TYPE_AP = 0; + public static final int IFACE_TYPE_STA_FOR_CONNECTIVITY = 1; + public static final int IFACE_TYPE_STA_FOR_SCAN = 2; + public static final int IFACE_TYPE_P2P = 3; + + @IntDef({IFACE_TYPE_AP, IFACE_TYPE_STA_FOR_CONNECTIVITY, IFACE_TYPE_STA_FOR_SCAN, + IFACE_TYPE_P2P}) + @Retention(RetentionPolicy.SOURCE) + public @interface IfaceType{} + + /** Identifier allocated for the interface */ + public final int id; + /** Type of the iface: STA (for Connectivity or Scan) or AP */ + public @IfaceType int type; + /** Name of the interface */ + public String name; + /** Is the interface up? This is used to mask up/down notifications to external clients. */ + public boolean isUp; + /** External iface destroyed listener for the iface */ + public InterfaceCallback externalListener; + /** Network observer registered for this interface */ + public NetworkObserverInternal networkObserver; + /** Interface feature set / capabilities */ + public long featureSet; + public int bandsSupported; + public DeviceWiphyCapabilities phyCapabilities; + + Iface(int id, @Iface.IfaceType int type) { + this.id = id; + this.type = type; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + String typeString; + switch(type) { + case IFACE_TYPE_STA_FOR_CONNECTIVITY: + typeString = "STA_CONNECTIVITY"; + break; + case IFACE_TYPE_STA_FOR_SCAN: + typeString = "STA_SCAN"; + break; + case IFACE_TYPE_AP: + typeString = "AP"; + break; + default: + typeString = ""; + break; + } + sb.append("Iface:") + .append("{") + .append("Name=").append(name) + .append(",") + .append("Id=").append(id) + .append(",") + .append("Type=").append(typeString) + .append("}"); + return sb.toString(); + } + } + + /** + * Iface Management entity. This class maintains list of all the active ifaces. + */ + private static class IfaceManager { + /** Integer to allocate for the next iface being created */ + private int mNextId; + /** Map of the id to the iface structure */ + private HashMap mIfaces = new HashMap<>(); + private boolean mVerboseLoggingEnabled = false; + + public void enableVerboseLogging(boolean enable) { + mVerboseLoggingEnabled = enable; + } + + /** Allocate a new iface for the given type */ + private Iface allocateIface(@Iface.IfaceType int type) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "IfaceManager#allocateIface: type=" + type + ", pre-map=" + mIfaces); + } + Iface iface = new Iface(mNextId, type); + mIfaces.put(mNextId, iface); + mNextId++; + return iface; + } + + /** Remove the iface using the provided id */ + private Iface removeIface(int id) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "IfaceManager#removeIface: id=" + id + ", pre-map=" + mIfaces); + } + return mIfaces.remove(id); + } + + /** Lookup the iface using the provided id */ + private Iface getIface(int id) { + return mIfaces.get(id); + } + + /** Lookup the iface using the provided name */ + private Iface getIface(@NonNull String ifaceName) { + for (Iface iface : mIfaces.values()) { + if (TextUtils.equals(iface.name, ifaceName)) { + return iface; + } + } + return null; + } + + /** Iterator to use for deleting all the ifaces while performing teardown on each of them */ + private Iterator getIfaceIdIter() { + return mIfaces.keySet().iterator(); + } + + /** Checks if there are any iface active. */ + private boolean hasAnyIface() { + return !mIfaces.isEmpty(); + } + + /** Checks if there are any iface of the given type active. */ + private boolean hasAnyIfaceOfType(@Iface.IfaceType int type) { + for (Iface iface : mIfaces.values()) { + if (iface.type == type) { + return true; + } + } + return false; + } + + /** Checks if there are any P2P iface active. */ + private boolean hasAnyP2pIface() { + return hasAnyIfaceOfType(Iface.IFACE_TYPE_P2P); + } + + /** Checks if there are any STA (for connectivity) iface active. */ + private boolean hasAnyStaIfaceForConnectivity() { + return hasAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY); + } + + /** Checks if there are any STA (for scan) iface active. */ + private boolean hasAnyStaIfaceForScan() { + return hasAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_SCAN); + } + + /** Checks if there are any AP iface active. */ + private boolean hasAnyApIface() { + return hasAnyIfaceOfType(Iface.IFACE_TYPE_AP); + } + + private @NonNull Set findAllStaIfaceNames() { + Set ifaceNames = new ArraySet<>(); + for (Iface iface : mIfaces.values()) { + if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY + || iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { + ifaceNames.add(iface.name); + } + } + return ifaceNames; + } + + private @NonNull Set findAllApIfaceNames() { + Set ifaceNames = new ArraySet<>(); + for (Iface iface : mIfaces.values()) { + if (iface.type == Iface.IFACE_TYPE_AP) { + ifaceNames.add(iface.name); + } + } + return ifaceNames; + } + + /** Removes the existing iface that does not match the provided id. */ + public Iface removeExistingIface(int newIfaceId) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "IfaceManager#removeExistingIface: newIfaceId=" + newIfaceId + + ", pre-map=" + mIfaces); + } + Iface removedIface = null; + // The number of ifaces in the database could be 1 existing & 1 new at the max. + if (mIfaces.size() > 2) { + Log.wtf(TAG, "More than 1 existing interface found"); + } + Iterator> iter = mIfaces.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + if (entry.getKey() != newIfaceId) { + removedIface = entry.getValue(); + iter.remove(); + } + } + return removedIface; + } + + @Override + public String toString() { + return mIfaces.toString(); + } + } + + private class NormalScanEventCallback implements WifiNl80211Manager.ScanEventCallback { + private String mIfaceName; + + NormalScanEventCallback(String ifaceName) { + mIfaceName = ifaceName; + } + + @Override + public void onScanResultReady() { + Log.d(TAG, "Scan result ready event"); + mWifiMonitor.broadcastScanResultEvent(mIfaceName); + } + + @Override + public void onScanFailed() { + Log.d(TAG, "Scan failed event"); + mWifiMonitor.broadcastScanFailedEvent(mIfaceName, WifiScanner.REASON_UNSPECIFIED); + } + + @Override + public void onScanFailed(int errorCode) { + Log.d(TAG, "Scan failed event: errorCode: " + errorCode); + mWifiMonitor.broadcastScanFailedEvent(mIfaceName, errorCode); + } + } + + private class PnoScanEventCallback implements WifiNl80211Manager.ScanEventCallback { + private String mIfaceName; + + PnoScanEventCallback(String ifaceName) { + mIfaceName = ifaceName; + } + + @Override + public void onScanResultReady() { + Log.d(TAG, "Pno scan result event"); + mWifiMonitor.broadcastPnoScanResultEvent(mIfaceName); + mWifiMetrics.incrementPnoFoundNetworkEventCount(); + } + + @Override + public void onScanFailed() { + Log.d(TAG, "Pno Scan failed event"); + WifiStatsLog.write(WifiStatsLog.PNO_SCAN_STOPPED, + WifiStatsLog.PNO_SCAN_STOPPED__STOP_REASON__SCAN_FAILED, + 0, false, false, false, false, // default values + WifiStatsLog.PNO_SCAN_STOPPED__FAILURE_CODE__WIFICOND_SCAN_FAILURE); + } + } + + private final Object mLock = new Object(); + private final IfaceManager mIfaceMgr = new IfaceManager(); + private HashSet mStatusListeners = new HashSet<>(); + + /** Helper method invoked to start supplicant if there were no ifaces */ + private boolean startHal() { + synchronized (mLock) { + if (!mIfaceMgr.hasAnyIface()) { + if (mWifiVendorHal.isVendorHalSupported()) { + if (!mWifiVendorHal.startVendorHal()) { + Log.e(TAG, "Failed to start vendor HAL"); + return false; + } + if (SdkLevel.isAtLeastS()) { + mWifiVendorHal.setCoexUnsafeChannels( + mCachedCoexUnsafeChannels, mCachedCoexRestrictions); + } + } else { + Log.i(TAG, "Vendor Hal not supported, ignoring start."); + } + } + registerWificondListenerIfNecessary(); + return true; + } + } + + /** Helper method invoked to stop HAL if there are no more ifaces */ + private void stopHalAndWificondIfNecessary() { + synchronized (mLock) { + if (!mIfaceMgr.hasAnyIface()) { + if (!mWifiCondManager.tearDownInterfaces()) { + Log.e(TAG, "Failed to teardown ifaces from wificond"); + } + if (mWifiVendorHal.isVendorHalSupported()) { + mWifiVendorHal.stopVendorHal(); + } else { + Log.i(TAG, "Vendor Hal not supported, ignoring stop."); + } + } + } + } + + /** + * Helper method invoked to setup wificond related callback/listener. + */ + private void registerWificondListenerIfNecessary() { + if (mCountryCodeChangeListener == null && SdkLevel.isAtLeastS()) { + // The country code listener is a new API in S. + mCountryCodeChangeListener = new CountryCodeChangeListenerInternal(); + mWifiCondManager.registerCountryCodeChangedListener(Runnable::run, + mCountryCodeChangeListener); + } + } + + private static final int CONNECT_TO_SUPPLICANT_RETRY_INTERVAL_MS = 100; + private static final int CONNECT_TO_SUPPLICANT_RETRY_TIMES = 50; + /** + * This method is called to wait for establishing connection to wpa_supplicant. + * + * @return true if connection is established, false otherwise. + */ + private boolean startAndWaitForSupplicantConnection() { + // Start initialization if not already started. + if (!mSupplicantStaIfaceHal.isInitializationStarted() + && !mSupplicantStaIfaceHal.initialize()) { + return false; + } + if (!mSupplicantStaIfaceHal.startDaemon()) { + Log.e(TAG, "Failed to startup supplicant"); + return false; + } + boolean connected = false; + int connectTries = 0; + while (!connected && connectTries++ < CONNECT_TO_SUPPLICANT_RETRY_TIMES) { + // Check if the initialization is complete. + connected = mSupplicantStaIfaceHal.isInitializationComplete(); + if (connected) { + break; + } + try { + Thread.sleep(CONNECT_TO_SUPPLICANT_RETRY_INTERVAL_MS); + } catch (InterruptedException ignore) { + } + } + return connected; + } + + /** Helper method invoked to start supplicant if there were no STA ifaces */ + private boolean startSupplicant() { + synchronized (mLock) { + if (!mIfaceMgr.hasAnyStaIfaceForConnectivity()) { + if (!startAndWaitForSupplicantConnection()) { + Log.e(TAG, "Failed to connect to supplicant"); + return false; + } + if (!mSupplicantStaIfaceHal.registerDeathHandler( + new SupplicantDeathHandlerInternal())) { + Log.e(TAG, "Failed to register supplicant death handler"); + return false; + } + } + return true; + } + } + + /** Helper method invoked to stop supplicant if there are no more STA ifaces */ + private void stopSupplicantIfNecessary() { + synchronized (mLock) { + if (!mIfaceMgr.hasAnyStaIfaceForConnectivity()) { + if (mSupplicantStaIfaceHal.isInitializationStarted()) { + if (!mSupplicantStaIfaceHal.deregisterDeathHandler()) { + Log.e(TAG, "Failed to deregister supplicant death handler"); + } + + } + if (!mIfaceMgr.hasAnyP2pIface()) { + if (mSupplicantStaIfaceHal.isInitializationStarted()) { + mSupplicantStaIfaceHal.terminate(); + } else { + mWifiInjector.getWifiP2pNative().stopP2pSupplicantIfNecessary(); + } + } + } + } + } + + /** Helper method invoked to start hostapd if there were no AP ifaces */ + private boolean startHostapd() { + synchronized (mLock) { + if (!mIfaceMgr.hasAnyApIface()) { + if (!startAndWaitForHostapdConnection()) { + Log.e(TAG, "Failed to connect to hostapd"); + return false; + } + if (!mHostapdHal.registerDeathHandler( + new HostapdDeathHandlerInternal())) { + Log.e(TAG, "Failed to register hostapd death handler"); + return false; + } + } + return true; + } + } + + /** Helper method invoked to stop hostapd if there are no more AP ifaces */ + private void stopHostapdIfNecessary() { + synchronized (mLock) { + if (!mIfaceMgr.hasAnyApIface()) { + if (!mHostapdHal.deregisterDeathHandler()) { + Log.e(TAG, "Failed to deregister hostapd death handler"); + } + mHostapdHal.terminate(); + } + } + } + + /** + * Helper method to register a new {@link InterfaceObserverInternal}, if there is no previous + * observer in place and {@link WifiGlobals#isWifiInterfaceAddedSelfRecoveryEnabled()} is + * enabled. + */ + private void registerInterfaceObserver() { + if (!mWifiInjector.getWifiGlobals().isWifiInterfaceAddedSelfRecoveryEnabled()) { + return; + } + if (mInterfaceObserver != null) { + Log.d(TAG, "Interface observer has previously been registered."); + return; + } + mInterfaceObserver = new InterfaceObserverInternal(); + mNetdWrapper.registerObserver(mInterfaceObserver); + Log.d(TAG, "Registered new interface observer."); + } + + /** Helper method to register a network observer and return it */ + private boolean registerNetworkObserver(NetworkObserverInternal observer) { + if (observer == null) return false; + mNetdWrapper.registerObserver(observer); + return true; + } + + /** Helper method to unregister a network observer */ + private boolean unregisterNetworkObserver(NetworkObserverInternal observer) { + if (observer == null) return false; + mNetdWrapper.unregisterObserver(observer); + return true; + } + + /** + * Helper method invoked to teardown client iface (for connectivity) and perform + * necessary cleanup + */ + private void onClientInterfaceForConnectivityDestroyed(@NonNull Iface iface) { + synchronized (mLock) { + mWifiMonitor.stopMonitoring(iface.name); + if (!unregisterNetworkObserver(iface.networkObserver)) { + Log.e(TAG, "Failed to unregister network observer on " + iface); + } + if (!mSupplicantStaIfaceHal.teardownIface(iface.name)) { + Log.e(TAG, "Failed to teardown iface in supplicant on " + iface); + } + if (!mWifiCondManager.tearDownClientInterface(iface.name)) { + Log.e(TAG, "Failed to teardown iface in wificond on " + iface); + } + stopSupplicantIfNecessary(); + stopHalAndWificondIfNecessary(); + } + } + + /** Helper method invoked to teardown client iface (for scan) and perform necessary cleanup */ + private void onClientInterfaceForScanDestroyed(@NonNull Iface iface) { + synchronized (mLock) { + mWifiMonitor.stopMonitoring(iface.name); + if (!unregisterNetworkObserver(iface.networkObserver)) { + Log.e(TAG, "Failed to unregister network observer on " + iface); + } + if (!mWifiCondManager.tearDownClientInterface(iface.name)) { + Log.e(TAG, "Failed to teardown iface in wificond on " + iface); + } + stopHalAndWificondIfNecessary(); + } + } + + /** Helper method invoked to teardown softAp iface and perform necessary cleanup */ + private void onSoftApInterfaceDestroyed(@NonNull Iface iface) { + synchronized (mLock) { + if (!unregisterNetworkObserver(iface.networkObserver)) { + Log.e(TAG, "Failed to unregister network observer on " + iface); + } + if (!mHostapdHal.removeAccessPoint(iface.name)) { + Log.e(TAG, "Failed to remove access point on " + iface); + } + String wificondIface = iface.name; + String bridgedApInstance = mWifiCondIfacesForBridgedAp.remove(iface.name); + if (bridgedApInstance != null) { + wificondIface = bridgedApInstance; + } + if (!mWifiCondManager.tearDownSoftApInterface(wificondIface)) { + Log.e(TAG, "Failed to teardown iface in wificond on " + iface); + } + stopHostapdIfNecessary(); + stopHalAndWificondIfNecessary(); + } + } + + /** Helper method invoked to teardown iface and perform necessary cleanup */ + private void onInterfaceDestroyed(@NonNull Iface iface) { + synchronized (mLock) { + if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY) { + onClientInterfaceForConnectivityDestroyed(iface); + } else if (iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { + onClientInterfaceForScanDestroyed(iface); + } else if (iface.type == Iface.IFACE_TYPE_AP) { + onSoftApInterfaceDestroyed(iface); + } + // Invoke the external callback only if the iface was not destroyed because of vendor + // HAL crash. In case of vendor HAL crash, let the crash recovery destroy the mode + // managers. + if (mWifiVendorHal.isVendorHalReady()) { + iface.externalListener.onDestroyed(iface.name); + } + } + } + + /** + * Callback to be invoked by HalDeviceManager when an interface is destroyed. + */ + private class InterfaceDestoyedListenerInternal + implements HalDeviceManager.InterfaceDestroyedListener { + /** Identifier allocated for the interface */ + private final int mInterfaceId; + + InterfaceDestoyedListenerInternal(int ifaceId) { + mInterfaceId = ifaceId; + } + + @Override + public void onDestroyed(@NonNull String ifaceName) { + synchronized (mLock) { + final Iface iface = mIfaceMgr.removeIface(mInterfaceId); + if (iface == null) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Received iface destroyed notification on an invalid iface=" + + ifaceName); + } + return; + } + onInterfaceDestroyed(iface); + Log.i(TAG, "Successfully torn down " + iface); + } + } + } + + /** + * Helper method invoked to trigger the status changed callback after one of the native + * daemon's death. + */ + private void onNativeDaemonDeath() { + synchronized (mLock) { + for (StatusListener listener : mStatusListeners) { + listener.onStatusChanged(false); + } + for (StatusListener listener : mStatusListeners) { + listener.onStatusChanged(true); + } + } + } + + /** + * Death handler for the Vendor HAL daemon. + */ + private class VendorHalDeathHandlerInternal implements VendorHalDeathEventHandler { + @Override + public void onDeath() { + synchronized (mLock) { + Log.i(TAG, "Vendor HAL died. Cleaning up internal state."); + onNativeDaemonDeath(); + mWifiMetrics.incrementNumHalCrashes(); + } + } + } + + /** + * Death handler for the wificond daemon. + */ + private class WificondDeathHandlerInternal implements Runnable { + @Override + public void run() { + synchronized (mLock) { + Log.i(TAG, "wificond died. Cleaning up internal state."); + onNativeDaemonDeath(); + mWifiMetrics.incrementNumWificondCrashes(); + } + } + } + + /** + * Death handler for the supplicant daemon. + */ + private class SupplicantDeathHandlerInternal implements SupplicantDeathEventHandler { + @Override + public void onDeath() { + mHandler.post(() -> { + synchronized (mLock) { + Log.i(TAG, "wpa_supplicant died. Cleaning up internal state."); + onNativeDaemonDeath(); + mWifiMetrics.incrementNumSupplicantCrashes(); + } + }); + } + } + + /** + * Death handler for the hostapd daemon. + */ + private class HostapdDeathHandlerInternal implements HostapdDeathEventHandler { + @Override + public void onDeath() { + synchronized (mLock) { + Log.i(TAG, "hostapd died. Cleaning up internal state."); + onNativeDaemonDeath(); + mWifiMetrics.incrementNumHostapdCrashes(); + } + } + } + + /** Helper method invoked to handle interface change. */ + private void onInterfaceStateChanged(Iface iface, boolean isUp) { + synchronized (mLock) { + // Mask multiple notifications with the same state. + if (isUp == iface.isUp) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Interface status unchanged on " + iface + " from " + isUp + + ", Ignoring..."); + } + return; + } + Log.i(TAG, "Interface state changed on " + iface + ", isUp=" + isUp); + if (isUp) { + iface.externalListener.onUp(iface.name); + } else { + iface.externalListener.onDown(iface.name); + if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY + || iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { + mWifiMetrics.incrementNumClientInterfaceDown(); + } else if (iface.type == Iface.IFACE_TYPE_AP) { + mWifiMetrics.incrementNumSoftApInterfaceDown(); + } + } + iface.isUp = isUp; + } + } + + /** + * Listener for wifi interface events. + */ + public interface InterfaceEventCallback { + + /** + * Interface physical-layer link state has changed. + * + * @param ifaceName The interface. + * @param isLinkUp True if the physical link-layer connection signal is valid. + */ + void onInterfaceLinkStateChanged(String ifaceName, boolean isLinkUp); + + /** + * Interface has been added. + * + * @param ifaceName Name of the interface. + */ + void onInterfaceAdded(String ifaceName); + } + + /** + * Register a listener for wifi interface events. + * + * @param ifaceEventCallback Listener object. + */ + public void setWifiNativeInterfaceEventCallback(InterfaceEventCallback ifaceEventCallback) { + mInterfaceListener = ifaceEventCallback; + Log.d(TAG, "setWifiNativeInterfaceEventCallback"); + } + + private class InterfaceObserverInternal implements NetdEventObserver { + private static final String TAG = "InterfaceObserverInternal"; + private final String mSelfRecoveryInterfaceName = mContext.getResources().getString( + R.string.config_wifiSelfRecoveryInterfaceName); + + @Override + public void interfaceLinkStateChanged(String ifaceName, boolean isLinkUp) { + if (!ifaceName.equals(mSelfRecoveryInterfaceName)) { + return; + } + Log.d(TAG, "Received interfaceLinkStateChanged, iface=" + ifaceName + " up=" + + isLinkUp); + if (mInterfaceListener != null) { + mInterfaceListener.onInterfaceLinkStateChanged(ifaceName, isLinkUp); + } else { + Log.e(TAG, "Received interfaceLinkStateChanged, interfaceListener=null"); + } + } + + @Override + public void interfaceStatusChanged(String iface, boolean up) { + // unused. + } + + @Override + public void interfaceAdded(String ifaceName) { + if (!ifaceName.equals(mSelfRecoveryInterfaceName)) { + return; + } + Log.d(TAG, "Received interfaceAdded, iface=" + ifaceName); + if (mInterfaceListener != null) { + mInterfaceListener.onInterfaceAdded(ifaceName); + } else { + Log.e(TAG, "Received interfaceAdded, interfaceListener=null"); + } + } + + } + + /** + * Network observer to use for all interface up/down notifications. + */ + private class NetworkObserverInternal implements NetdEventObserver { + /** Identifier allocated for the interface */ + private final int mInterfaceId; + + NetworkObserverInternal(int id) { + mInterfaceId = id; + } + + /** + * Note: We should ideally listen to + * {@link NetdEventObserver#interfaceStatusChanged(String, boolean)} here. But, that + * callback is not working currently (broken in netd). So, instead listen to link state + * change callbacks as triggers to query the real interface state. We should get rid of + * this workaround if we get the |interfaceStatusChanged| callback to work in netd. + * Also, this workaround will not detect an interface up event, if the link state is + * still down. + */ + @Override + public void interfaceLinkStateChanged(String ifaceName, boolean unusedIsLinkUp) { + // This is invoked from the main system_server thread. Post to our handler. + mHandler.post(() -> { + synchronized (mLock) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "interfaceLinkStateChanged: ifaceName=" + ifaceName + + ", mInterfaceId = " + mInterfaceId + + ", mIfaceMgr=" + mIfaceMgr.toString()); + } + final Iface ifaceWithId = mIfaceMgr.getIface(mInterfaceId); + if (ifaceWithId == null) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Received iface link up/down notification on an invalid" + + " iface=" + mInterfaceId); + } + return; + } + final Iface ifaceWithName = mIfaceMgr.getIface(ifaceName); + if (ifaceWithName == null || ifaceWithName != ifaceWithId) { + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Received iface link up/down notification on an invalid" + + " iface=" + ifaceName); + } + return; + } + onInterfaceStateChanged(ifaceWithName, isInterfaceUp(ifaceName)); + } + }); + } + + @Override + public void interfaceStatusChanged(String ifaceName, boolean unusedIsLinkUp) { + // unused currently. Look at note above. + } + + @Override + public void interfaceAdded(String iface){ + // unused currently. + } + } + + /** + * Radio mode change handler for the Vendor HAL daemon. + */ + private class VendorHalRadioModeChangeHandlerInternal + implements VendorHalRadioModeChangeEventHandler { + @Override + public void onMcc(int band) { + synchronized (mLock) { + Log.i(TAG, "Device is in MCC mode now"); + mWifiMetrics.incrementNumRadioModeChangeToMcc(); + } + } + @Override + public void onScc(int band) { + synchronized (mLock) { + Log.i(TAG, "Device is in SCC mode now"); + mWifiMetrics.incrementNumRadioModeChangeToScc(); + } + } + @Override + public void onSbs(int band) { + synchronized (mLock) { + Log.i(TAG, "Device is in SBS mode now"); + mWifiMetrics.incrementNumRadioModeChangeToSbs(); + } + } + @Override + public void onDbs() { + synchronized (mLock) { + Log.i(TAG, "Device is in DBS mode now"); + mWifiMetrics.incrementNumRadioModeChangeToDbs(); + } + } + } + + // For devices that don't support the vendor HAL, we will not support any concurrency. + // So simulate the HalDeviceManager behavior by triggering the destroy listener for + // any active interface. + private String handleIfaceCreationWhenVendorHalNotSupported(@NonNull Iface newIface) { + synchronized (mLock) { + Iface existingIface = mIfaceMgr.removeExistingIface(newIface.id); + if (existingIface != null) { + onInterfaceDestroyed(existingIface); + Log.i(TAG, "Successfully torn down " + existingIface); + } + // Return the interface name directly from the system property. + return mPropertyService.getString("wifi.interface", "wlan0"); + } + } + + /** + * Helper function to handle creation of STA iface. + * For devices which do not the support the HAL, this will bypass HalDeviceManager & + * teardown any existing iface. + */ + private String createStaIface(@NonNull Iface iface, @NonNull WorkSource requestorWs, + @NonNull ConcreteClientModeManager concreteClientModeManager) { + synchronized (mLock) { + if (mWifiVendorHal.isVendorHalSupported()) { + return mWifiVendorHal.createStaIface( + new InterfaceDestoyedListenerInternal(iface.id), requestorWs, + concreteClientModeManager); + } else { + Log.i(TAG, "Vendor Hal not supported, ignoring createStaIface."); + return handleIfaceCreationWhenVendorHalNotSupported(iface); + } + } + } + + /** + * Helper function to handle creation of AP iface. + * For devices which do not the support the HAL, this will bypass HalDeviceManager & + * teardown any existing iface. + */ + private String createApIface(@NonNull Iface iface, @NonNull WorkSource requestorWs, + @SoftApConfiguration.BandType int band, boolean isBridged, + @NonNull SoftApManager softApManager, @NonNull List vendorData) { + synchronized (mLock) { + if (mWifiVendorHal.isVendorHalSupported()) { + return mWifiVendorHal.createApIface( + new InterfaceDestoyedListenerInternal(iface.id), requestorWs, + band, isBridged, softApManager, vendorData); + } else { + Log.i(TAG, "Vendor Hal not supported, ignoring createApIface."); + return handleIfaceCreationWhenVendorHalNotSupported(iface); + } + } + } + + private String createP2pIfaceFromHalOrGetNameFromProperty( + HalDeviceManager.InterfaceDestroyedListener p2pInterfaceDestroyedListener, + Handler handler, WorkSource requestorWs) { + synchronized (mLock) { + if (mWifiVendorHal.isVendorHalSupported()) { + return mWifiInjector.getHalDeviceManager().createP2pIface( + p2pInterfaceDestroyedListener, handler, requestorWs); + } else { + Log.i(TAG, "Vendor Hal not supported, ignoring createStaIface."); + return mPropertyService.getString(P2P_INTERFACE_PROPERTY, P2P_IFACE_NAME); + } + } + } + + /** + * Helper function to handle creation of P2P iface. + * For devices which do not the support the HAL, this will bypass HalDeviceManager & + * teardown any existing iface. + */ + public Iface createP2pIface( + HalDeviceManager.InterfaceDestroyedListener p2pInterfaceDestroyedListener, + Handler handler, WorkSource requestorWs) { + synchronized (mLock) { + // Make sure HAL is started for p2p + if (!startHal()) { + Log.e(TAG, "Failed to start Hal"); + mWifiMetrics.incrementNumSetupP2pInterfaceFailureDueToHal(); + return null; + } + // maintain iface status in WifiNative + Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_P2P); + if (iface == null) { + Log.e(TAG, "Failed to allocate new P2P iface"); + stopHalAndWificondIfNecessary(); + return null; + } + iface.name = createP2pIfaceFromHalOrGetNameFromProperty( + p2pInterfaceDestroyedListener, handler, requestorWs); + if (TextUtils.isEmpty(iface.name)) { + Log.e(TAG, "Failed to create P2p iface in HalDeviceManager"); + mIfaceMgr.removeIface(iface.id); + mWifiMetrics.incrementNumSetupP2pInterfaceFailureDueToHal(); + stopHalAndWificondIfNecessary(); + return null; + } + return iface; + } + } + + /** + * Teardown P2p iface with input interface Id which was returned by createP2pIface. + * + * @param interfaceId the interface identify which was gerenated when creating P2p iface. + */ + public void teardownP2pIface(int interfaceId) { + synchronized (mLock) { + mIfaceMgr.removeIface(interfaceId); + stopHalAndWificondIfNecessary(); + stopSupplicantIfNecessary(); + } + } + + /** + * Get list of instance name from this bridged AP iface. + * + * @param ifaceName Name of the bridged interface. + * @return list of instance name when succeed, otherwise null. + */ + @Nullable + public List getBridgedApInstances(@NonNull String ifaceName) { + synchronized (mLock) { + if (mWifiVendorHal.isVendorHalSupported()) { + return mWifiVendorHal.getBridgedApInstances(ifaceName); + } else { + Log.i(TAG, "Vendor Hal not supported, ignoring getBridgedApInstances."); + return null; + } + } + } + + // For devices that don't support the vendor HAL, we will not support any concurrency. + // So simulate the HalDeviceManager behavior by triggering the destroy listener for + // the interface. + private boolean handleIfaceRemovalWhenVendorHalNotSupported(@NonNull Iface iface) { + synchronized (mLock) { + mIfaceMgr.removeIface(iface.id); + onInterfaceDestroyed(iface); + Log.i(TAG, "Successfully torn down " + iface); + return true; + } + } + + /** + * Helper function to handle removal of STA iface. + * For devices which do not the support the HAL, this will bypass HalDeviceManager & + * teardown any existing iface. + */ + private boolean removeStaIface(@NonNull Iface iface) { + synchronized (mLock) { + if (mWifiVendorHal.isVendorHalSupported()) { + return mWifiVendorHal.removeStaIface(iface.name); + } else { + Log.i(TAG, "Vendor Hal not supported, ignoring removeStaIface."); + return handleIfaceRemovalWhenVendorHalNotSupported(iface); + } + } + } + + /** + * Helper function to handle removal of STA iface. + */ + private boolean removeApIface(@NonNull Iface iface) { + synchronized (mLock) { + if (mWifiVendorHal.isVendorHalSupported()) { + return mWifiVendorHal.removeApIface(iface.name); + } else { + Log.i(TAG, "Vendor Hal not supported, ignoring removeApIface."); + return handleIfaceRemovalWhenVendorHalNotSupported(iface); + } + } + } + + /** + * Helper function to remove specific instance in bridged AP iface. + * + * @param ifaceName Name of the iface. + * @param apIfaceInstance The identity of the ap instance. + * @return true if the operation succeeded, false if there is an error in Hal. + */ + public boolean removeIfaceInstanceFromBridgedApIface(@NonNull String ifaceName, + @NonNull String apIfaceInstance) { + synchronized (mLock) { + if (mWifiVendorHal.isVendorHalSupported()) { + return mWifiVendorHal.removeIfaceInstanceFromBridgedApIface(ifaceName, + apIfaceInstance); + } else { + return false; + } + } + } + + /** + * Register listener for subsystem restart event + * + * @param listener SubsystemRestartListener listener object. + */ + public void registerSubsystemRestartListener( + HalDeviceManager.SubsystemRestartListener listener) { + if (listener != null) { + mWifiVendorHal.registerSubsystemRestartListener(listener); + } + } + + /** + * Initialize the native modules. + * + * @return true on success, false otherwise. + */ + public boolean initialize() { + synchronized (mLock) { + if (!mWifiVendorHal.initialize(new VendorHalDeathHandlerInternal())) { + Log.e(TAG, "Failed to initialize vendor HAL"); + return false; + } + mWifiCondManager.setOnServiceDeadCallback(new WificondDeathHandlerInternal()); + mWifiCondManager.tearDownInterfaces(); + mWifiVendorHal.registerRadioModeChangeHandler( + new VendorHalRadioModeChangeHandlerInternal()); + mNetdWrapper = mWifiInjector.makeNetdWrapper(); + return true; + } + } + + /** + * Callback to notify when the status of one of the native daemons + * (wificond, wpa_supplicant & vendor HAL) changes. + */ + public interface StatusListener { + /** + * @param allReady Indicates if all the native daemons are ready for operation or not. + */ + void onStatusChanged(boolean allReady); + } + + /** + * Register a StatusListener to get notified about any status changes from the native daemons. + * + * It is safe to re-register the same callback object - duplicates are detected and only a + * single copy kept. + * + * @param listener StatusListener listener object. + */ + public void registerStatusListener(@NonNull StatusListener listener) { + synchronized (mLock) { + mStatusListeners.add(listener); + } + } + + /** + * Callback to notify when the associated interface is destroyed, up or down. + */ + public interface InterfaceCallback { + /** + * Interface destroyed by HalDeviceManager. + * + * @param ifaceName Name of the iface. + */ + void onDestroyed(String ifaceName); + + /** + * Interface is up. + * + * @param ifaceName Name of the iface. + */ + void onUp(String ifaceName); + + /** + * Interface is down. + * + * @param ifaceName Name of the iface. + */ + void onDown(String ifaceName); + } + + private void takeBugReportInterfaceFailureIfNeeded(String bugTitle, String bugDetail) { + if (mWifiInjector.getDeviceConfigFacade().isInterfaceFailureBugreportEnabled()) { + mWifiInjector.getWifiDiagnostics().takeBugReport(bugTitle, bugDetail); + } + } + + /** + * Setup an interface for client mode (for scan) operations. + * + * This method configures an interface in STA mode in the native daemons + * (wificond, vendor HAL). + * + * @param interfaceCallback Associated callback for notifying status changes for the iface. + * @param requestorWs Requestor worksource. + * @param concreteClientModeManager ConcreteClientModeManager requesting the interface. + * @return Returns the name of the allocated interface, will be null on failure. + */ + public String setupInterfaceForClientInScanMode( + @NonNull InterfaceCallback interfaceCallback, @NonNull WorkSource requestorWs, + @NonNull ConcreteClientModeManager concreteClientModeManager) { + synchronized (mLock) { + if (!startHal()) { + Log.e(TAG, "Failed to start Hal"); + mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal(); + return null; + } + Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_STA_FOR_SCAN); + if (iface == null) { + Log.e(TAG, "Failed to allocate new STA iface"); + return null; + } + iface.externalListener = interfaceCallback; + iface.name = createStaIface(iface, requestorWs, concreteClientModeManager); + if (TextUtils.isEmpty(iface.name)) { + Log.e(TAG, "Failed to create iface in vendor HAL"); + mIfaceMgr.removeIface(iface.id); + mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal(); + return null; + } + if (!mWifiCondManager.setupInterfaceForClientMode(iface.name, Runnable::run, + new NormalScanEventCallback(iface.name), + new PnoScanEventCallback(iface.name))) { + Log.e(TAG, "Failed to setup iface in wificond=" + iface.name); + teardownInterface(iface.name); + mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToWificond(); + return null; + } + registerInterfaceObserver(); + iface.networkObserver = new NetworkObserverInternal(iface.id); + if (!registerNetworkObserver(iface.networkObserver)) { + Log.e(TAG, "Failed to register network observer for iface=" + iface.name); + teardownInterface(iface.name); + return null; + } + mWifiMonitor.startMonitoring(iface.name); + // Just to avoid any race conditions with interface state change callbacks, + // update the interface state before we exit. + onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); + mWifiVendorHal.enableLinkLayerStats(iface.name); + Log.i(TAG, "Successfully setup " + iface); + + iface.featureSet = getSupportedFeatureSetInternal(iface.name); + updateSupportedBandForStaInternal(iface); + + mWifiVendorHal.enableStaChannelForPeerNetwork(mContext.getResources().getBoolean( + R.bool.config_wifiEnableStaIndoorChannelForPeerNetwork), + mContext.getResources().getBoolean( + R.bool.config_wifiEnableStaDfsChannelForPeerNetwork)); + return iface.name; + } + } + + /** + * Setup an interface for Soft AP mode operations. + * + * This method configures an interface in AP mode in all the native daemons + * (wificond, wpa_supplicant & vendor HAL). + * + * @param interfaceCallback Associated callback for notifying status changes for the iface. + * @param requestorWs Requestor worksource. + * @param isBridged Whether or not AP interface is a bridge interface. + * @param softApManager SoftApManager of the request. + * @param vendorData List of {@link OuiKeyedData} containing vendor-provided + * configuration data. Empty list indicates no vendor data. + * @return Returns the name of the allocated interface, will be null on failure. + */ + public String setupInterfaceForSoftApMode( + @NonNull InterfaceCallback interfaceCallback, @NonNull WorkSource requestorWs, + @SoftApConfiguration.BandType int band, boolean isBridged, + @NonNull SoftApManager softApManager, @NonNull List vendorData) { + synchronized (mLock) { + String bugTitle = "Wi-Fi BugReport (softAp interface failure)"; + String errorMsg = ""; + if (!startHal()) { + errorMsg = "Failed to start softAp Hal"; + Log.e(TAG, errorMsg); + mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal(); + takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); + softApManager.writeSoftApStartedEvent(SoftApManager.START_RESULT_FAILURE_START_HAL); + return null; + } + if (!startHostapd()) { + errorMsg = "Failed to start softAp hostapd"; + Log.e(TAG, errorMsg); + mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd(); + takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); + softApManager.writeSoftApStartedEvent( + SoftApManager.START_RESULT_FAILURE_START_HOSTAPD); + return null; + } + Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_AP); + if (iface == null) { + Log.e(TAG, "Failed to allocate new AP iface"); + return null; + } + iface.externalListener = interfaceCallback; + iface.name = createApIface(iface, requestorWs, band, isBridged, softApManager, + vendorData); + if (TextUtils.isEmpty(iface.name)) { + errorMsg = "Failed to create softAp iface in vendor HAL"; + Log.e(TAG, errorMsg); + mIfaceMgr.removeIface(iface.id); + mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal(); + takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); + return null; + } + String ifaceInstanceName = iface.name; + if (isBridged) { + List instances = getBridgedApInstances(iface.name); + if (instances == null || instances.size() == 0) { + errorMsg = "Failed to get bridged AP instances" + iface.name; + Log.e(TAG, errorMsg); + teardownInterface(iface.name); + mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal(); + takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); + return null; + } + // Always select first instance as wificond interface. + ifaceInstanceName = instances.get(0); + mWifiCondIfacesForBridgedAp.put(iface.name, ifaceInstanceName); + } + if (!mWifiCondManager.setupInterfaceForSoftApMode(ifaceInstanceName)) { + errorMsg = "Failed to setup softAp iface in wifiCond manager on " + iface; + Log.e(TAG, errorMsg); + teardownInterface(iface.name); + mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToWificond(); + takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); + return null; + } + iface.networkObserver = new NetworkObserverInternal(iface.id); + if (!registerNetworkObserver(iface.networkObserver)) { + Log.e(TAG, "Failed to register network observer on " + iface); + teardownInterface(iface.name); + return null; + } + // Just to avoid any race conditions with interface state change callbacks, + // update the interface state before we exit. + onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); + Log.i(TAG, "Successfully setup " + iface); + + iface.featureSet = getSupportedFeatureSetInternal(iface.name); + updateSupportedBandForStaInternal(iface); + return iface.name; + } + } + + /** + * Switches an existing Client mode interface from connectivity + * {@link Iface#IFACE_TYPE_STA_FOR_CONNECTIVITY} to scan mode + * {@link Iface#IFACE_TYPE_STA_FOR_SCAN}. + * + * @param ifaceName Name of the interface. + * @param requestorWs Requestor worksource. + * @return true if the operation succeeded, false if there is an error or the iface is already + * in scan mode. + */ + public boolean switchClientInterfaceToScanMode(@NonNull String ifaceName, + @NonNull WorkSource requestorWs) { + synchronized (mLock) { + final Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Trying to switch to scan mode on an invalid iface=" + ifaceName); + return false; + } + if (iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { + Log.e(TAG, "Already in scan mode on iface=" + ifaceName); + return true; + } + if (mWifiVendorHal.isVendorHalSupported() + && !mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, requestorWs)) { + Log.e(TAG, "Failed to replace requestor ws on " + iface); + teardownInterface(iface.name); + return false; + } + if (!mSupplicantStaIfaceHal.teardownIface(iface.name)) { + Log.e(TAG, "Failed to teardown iface in supplicant on " + iface); + teardownInterface(iface.name); + return false; + } + iface.type = Iface.IFACE_TYPE_STA_FOR_SCAN; + stopSupplicantIfNecessary(); + iface.featureSet = getSupportedFeatureSetInternal(iface.name); + updateSupportedBandForStaInternal(iface); + iface.phyCapabilities = null; + Log.i(TAG, "Successfully switched to scan mode on iface=" + iface); + return true; + } + } + + /** + * Switches an existing Client mode interface from scan mode + * {@link Iface#IFACE_TYPE_STA_FOR_SCAN} to connectivity mode + * {@link Iface#IFACE_TYPE_STA_FOR_CONNECTIVITY}. + * + * @param ifaceName Name of the interface. + * @param requestorWs Requestor worksource. + * @return true if the operation succeeded, false if there is an error or the iface is already + * in scan mode. + */ + public boolean switchClientInterfaceToConnectivityMode(@NonNull String ifaceName, + @NonNull WorkSource requestorWs) { + synchronized (mLock) { + final Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Trying to switch to connectivity mode on an invalid iface=" + + ifaceName); + return false; + } + if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY) { + Log.e(TAG, "Already in connectivity mode on iface=" + ifaceName); + return true; + } + if (mWifiVendorHal.isVendorHalSupported() + && !mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, requestorWs)) { + Log.e(TAG, "Failed to replace requestor ws on " + iface); + teardownInterface(iface.name); + return false; + } + if (!startSupplicant()) { + Log.e(TAG, "Failed to start supplicant"); + teardownInterface(iface.name); + mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant(); + return false; + } + if (!mSupplicantStaIfaceHal.setupIface(iface.name)) { + Log.e(TAG, "Failed to setup iface in supplicant on " + iface); + teardownInterface(iface.name); + mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant(); + return false; + } + if (mContext.getResources().getBoolean( + R.bool.config_wifiNetworkCentricQosPolicyFeatureEnabled) + && isSupplicantUsingAidlService()) { + mQosPolicyFeatureEnabled = mSupplicantStaIfaceHal + .setNetworkCentricQosPolicyFeatureEnabled(iface.name, true); + if (!mQosPolicyFeatureEnabled) { + Log.e(TAG, "Failed to enable QoS policy feature for iface " + iface.name); + } + } + iface.type = Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY; + iface.featureSet = getSupportedFeatureSetInternal(iface.name); + saveCompleteFeatureSetInConfigStoreIfNecessary(iface.featureSet); + updateSupportedBandForStaInternal(iface); + mIsEnhancedOpenSupported = (iface.featureSet & WIFI_FEATURE_OWE) != 0; + Log.i(TAG, "Successfully switched to connectivity mode on iface=" + iface); + return true; + } + } + + /** + * Change the requestor WorkSource for a given STA iface. + * @return true if the operation succeeded, false otherwise. + */ + public boolean replaceStaIfaceRequestorWs(@NonNull String ifaceName, WorkSource newWorkSource) { + final Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Called replaceStaIfaceRequestorWs() on an invalid iface=" + ifaceName); + return false; + } + if (!mWifiVendorHal.isVendorHalSupported()) { + // if vendor HAL isn't supported, return true since there's nothing to do. + return true; + } + if (!mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, newWorkSource)) { + Log.e(TAG, "Failed to replace requestor ws on " + iface); + teardownInterface(iface.name); + return false; + } + return true; + } + + /** + * + * Check if the interface is up or down. + * + * @param ifaceName Name of the interface. + * @return true if iface is up, false if it's down or on error. + */ + public boolean isInterfaceUp(@NonNull String ifaceName) { + synchronized (mLock) { + final Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Trying to get iface state on invalid iface=" + ifaceName); + return false; + } + try { + return mNetdWrapper.isInterfaceUp(ifaceName); + } catch (IllegalStateException e) { + Log.e(TAG, "Unable to get interface config", e); + return false; + } + } + } + + /** + * Teardown an interface in Client/AP mode. + * + * This method tears down the associated interface from all the native daemons + * (wificond, wpa_supplicant & vendor HAL). + * Also, brings down the HAL, supplicant or hostapd as necessary. + * + * @param ifaceName Name of the interface. + */ + public void teardownInterface(@NonNull String ifaceName) { + synchronized (mLock) { + final Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Trying to teardown an invalid iface=" + ifaceName); + return; + } + // Trigger the iface removal from HAL. The rest of the cleanup will be triggered + // from the interface destroyed callback. + if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY + || iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { + if (!removeStaIface(iface)) { + Log.e(TAG, "Failed to remove iface in vendor HAL=" + ifaceName); + return; + } + } else if (iface.type == Iface.IFACE_TYPE_AP) { + if (!removeApIface(iface)) { + Log.e(TAG, "Failed to remove iface in vendor HAL=" + ifaceName); + return; + } + } + Log.i(TAG, "Successfully initiated teardown for iface=" + ifaceName); + } + } + + /** + * Teardown all the active interfaces. + * + * This method tears down the associated interfaces from all the native daemons + * (wificond, wpa_supplicant & vendor HAL). + * Also, brings down the HAL, supplicant or hostapd as necessary. + */ + public void teardownAllInterfaces() { + synchronized (mLock) { + Iterator ifaceIdIter = mIfaceMgr.getIfaceIdIter(); + while (ifaceIdIter.hasNext()) { + Iface iface = mIfaceMgr.getIface(ifaceIdIter.next()); + ifaceIdIter.remove(); + onInterfaceDestroyed(iface); + Log.i(TAG, "Successfully torn down " + iface); + } + Log.i(TAG, "Successfully torn down all ifaces"); + } + } + + /** + * Get names of all the client interfaces. + * + * @return List of interface name of all active client interfaces. + */ + public Set getClientInterfaceNames() { + synchronized (mLock) { + return mIfaceMgr.findAllStaIfaceNames(); + } + } + + /** + * Get names of all the client interfaces. + * + * @return List of interface name of all active client interfaces. + */ + public Set getSoftApInterfaceNames() { + synchronized (mLock) { + return mIfaceMgr.findAllApIfaceNames(); + } + } + + /******************************************************** + * Wificond operations + ********************************************************/ + + /** + * Query the list of valid frequencies for the provided band. + * The result depends on the on the country code that has been set. + * + * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants. + * The following bands are supported {@link WifiAnnotations.WifiBandBasic}: + * WifiScanner.WIFI_BAND_24_GHZ + * WifiScanner.WIFI_BAND_5_GHZ + * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY + * WifiScanner.WIFI_BAND_6_GHZ + * WifiScanner.WIFI_BAND_60_GHZ + * @return frequencies vector of valid frequencies (MHz), or null for error. + * @throws IllegalArgumentException if band is not recognized. + */ + public int [] getChannelsForBand(@WifiAnnotations.WifiBandBasic int band) { + if (!SdkLevel.isAtLeastS() && band == WifiScanner.WIFI_BAND_60_GHZ) { + // 60 GHz band is new in Android S, return empty array on older SDK versions + return new int[0]; + } + return mWifiCondManager.getChannelsMhzForBand(band); + } + + /** + * Start a scan using wificond for the given parameters. + * @param ifaceName Name of the interface. + * @param scanType Type of scan to perform. One of {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}, + * {@link WifiScanner#SCAN_TYPE_LOW_POWER} or {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}. + * @param freqs list of frequencies to scan for, if null scan all supported channels. + * @param hiddenNetworkSSIDs List of hidden networks to be scanned for. + * @param enable6GhzRnr whether Reduced Neighbor Report should be enabled for 6Ghz scanning. + * @param vendorIes Byte array of vendor IEs + * @return Returns true on success. + */ + public int scan( + @NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, Set freqs, + List hiddenNetworkSSIDs, boolean enable6GhzRnr, byte[] vendorIes) { + int scanRequestStatus = WifiScanner.REASON_SUCCEEDED; + boolean scanStatus = true; + List hiddenNetworkSsidsArrays = new ArrayList<>(); + for (String hiddenNetworkSsid : hiddenNetworkSSIDs) { + try { + hiddenNetworkSsidsArrays.add( + NativeUtil.byteArrayFromArrayList( + NativeUtil.decodeSsid(hiddenNetworkSsid))); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + hiddenNetworkSsid, e); + continue; + } + } + if (SdkLevel.isAtLeastS()) { + // enable6GhzRnr is a new parameter first introduced in Android S. + Bundle extraScanningParams = new Bundle(); + extraScanningParams.putBoolean(WifiNl80211Manager.SCANNING_PARAM_ENABLE_6GHZ_RNR, + enable6GhzRnr); + if (SdkLevel.isAtLeastU()) { + extraScanningParams.putByteArray(WifiNl80211Manager.EXTRA_SCANNING_PARAM_VENDOR_IES, + vendorIes); + scanRequestStatus = mWifiCondManager.startScan2(ifaceName, scanType, freqs, + hiddenNetworkSsidsArrays, extraScanningParams); + } else { + scanStatus = mWifiCondManager.startScan(ifaceName, scanType, freqs, + hiddenNetworkSsidsArrays, + extraScanningParams); + scanRequestStatus = scanStatus + ? WifiScanner.REASON_SUCCEEDED : WifiScanner.REASON_UNSPECIFIED; + + } + } else { + scanStatus = mWifiCondManager.startScan(ifaceName, scanType, freqs, + hiddenNetworkSsidsArrays); + scanRequestStatus = scanStatus + ? WifiScanner.REASON_SUCCEEDED : WifiScanner.REASON_UNSPECIFIED; + } + + return scanRequestStatus; + } + + /** + * Fetch the latest scan result from kernel via wificond. + * @param ifaceName Name of the interface. + * @return Returns an ArrayList of ScanDetail. + * Returns an empty ArrayList on failure. + */ + public ArrayList getScanResults(@NonNull String ifaceName) { + if (mUseFakeScanDetails) { + synchronized (mFakeScanDetails) { + ArrayList copyList = new ArrayList<>(); + for (ScanDetail sd: mFakeScanDetails) { + ScanDetail copy = new ScanDetail(sd); + copy.getScanResult().ifaceName = ifaceName; + // otherwise the fake will be too old + copy.getScanResult().timestamp = SystemClock.elapsedRealtime() * 1000; + copyList.add(copy); + } + return copyList; + } + } + if (mMockWifiModem != null + && mMockWifiModem.isMethodConfigured( + MockWifiServiceUtil.MOCK_NL80211_SERVICE, "getScanResults")) { + Log.i(TAG, "getScanResults was called from mock wificond"); + return convertNativeScanResults(ifaceName, mMockWifiModem.getWifiNl80211Manager() + .getScanResults(ifaceName, WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN)); + } + return convertNativeScanResults(ifaceName, mWifiCondManager.getScanResults( + ifaceName, WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN)); + } + + /** + * Start faking scan results - using information provided via + * {@link #addFakeScanDetail(ScanDetail)}. Stop with {@link #stopFakingScanDetails()}. + */ + public void startFakingScanDetails() { + if (mBuildProperties.isUserBuild()) { + Log.wtf(TAG, "Can't fake scan results in a user build!"); + return; + } + Log.d(TAG, "Starting faking scan results - " + mFakeScanDetails); + mUseFakeScanDetails = true; + } + + /** + * Add fake scan result. Fakes are not used until activated via + * {@link #startFakingScanDetails()}. + * @param fakeScanDetail + */ + public void addFakeScanDetail(@NonNull ScanDetail fakeScanDetail) { + synchronized (mFakeScanDetails) { + mFakeScanDetails.add(fakeScanDetail); + } + } + + /** + * Reset the fake scan result list updated via {@link #addFakeScanDetail(ScanDetail)} .} + */ + public void resetFakeScanDetails() { + synchronized (mFakeScanDetails) { + mFakeScanDetails.clear(); + } + } + + /** + * Stop faking scan results. Started with {@link #startFakingScanDetails()}. + */ + public void stopFakingScanDetails() { + mUseFakeScanDetails = false; + } + + /** + * Fetch the latest scan result from kernel via wificond. + * @param ifaceName Name of the interface. + * @return Returns an ArrayList of ScanDetail. + * Returns an empty ArrayList on failure. + */ + public ArrayList getPnoScanResults(@NonNull String ifaceName) { + if (mMockWifiModem != null + && mMockWifiModem.isMethodConfigured( + MockWifiServiceUtil.MOCK_NL80211_SERVICE, "getPnoScanResults")) { + Log.i(TAG, "getPnoScanResults was called from mock wificond"); + return convertNativeScanResults(ifaceName, mMockWifiModem.getWifiNl80211Manager() + .getScanResults(ifaceName, WifiNl80211Manager.SCAN_TYPE_PNO_SCAN)); + } + return convertNativeScanResults(ifaceName, mWifiCondManager.getScanResults(ifaceName, + WifiNl80211Manager.SCAN_TYPE_PNO_SCAN)); + } + + /** + * Get the max number of SSIDs that the driver supports per scan. + * @param ifaceName Name of the interface. + */ + public int getMaxSsidsPerScan(@NonNull String ifaceName) { + if (SdkLevel.isAtLeastT()) { + return mWifiCondManager.getMaxSsidsPerScan(ifaceName); + } else { + return -1; + } + } + + private ArrayList convertNativeScanResults(@NonNull String ifaceName, + List nativeResults) { + ArrayList results = new ArrayList<>(); + for (NativeScanResult result : nativeResults) { + if (result.getSsid().length > 32) { + Log.e(TAG, "Invalid SSID length (> 32 bytes): " + + Arrays.toString(result.getSsid())); + continue; + } + WifiSsid originalSsid = WifiSsid.fromBytes(result.getSsid()); + MacAddress bssidMac = result.getBssid(); + if (bssidMac == null) { + Log.e(TAG, "Invalid MAC (BSSID) for SSID " + originalSsid); + continue; + } + String bssid = bssidMac.toString(); + ScanResult.InformationElement[] ies = + InformationElementUtil.parseInformationElements(result.getInformationElements()); + InformationElementUtil.Capabilities capabilities = + new InformationElementUtil.Capabilities(); + capabilities.from( + ies, + result.getCapabilities(), + mIsEnhancedOpenSupported, + result.getFrequencyMhz(), + mUnknownAkmMap); + String flags = capabilities.generateCapabilitiesString(); + NetworkDetail networkDetail; + try { + networkDetail = new NetworkDetail(bssid, ies, null, result.getFrequencyMhz()); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument for scan result with bssid: " + bssid, e); + continue; + } + + WifiSsid translatedSsid = mWifiInjector.getSsidTranslator() + .getTranslatedSsidAndRecordBssidCharset(originalSsid, bssidMac); + ScanDetail scanDetail = new ScanDetail(networkDetail, translatedSsid, bssid, flags, + result.getSignalMbm() / 100, result.getFrequencyMhz(), result.getTsf(), ies, + null, result.getInformationElements()); + ScanResult scanResult = scanDetail.getScanResult(); + scanResult.setWifiStandard(wifiModeToWifiStandard(networkDetail.getWifiMode())); + scanResult.ifaceName = ifaceName; + + // Fill up the radio chain info. + scanResult.radioChainInfos = + new ScanResult.RadioChainInfo[result.getRadioChainInfos().size()]; + int idx = 0; + for (RadioChainInfo nativeRadioChainInfo : result.getRadioChainInfos()) { + scanResult.radioChainInfos[idx] = new ScanResult.RadioChainInfo(); + scanResult.radioChainInfos[idx].id = nativeRadioChainInfo.getChainId(); + scanResult.radioChainInfos[idx].level = nativeRadioChainInfo.getLevelDbm(); + idx++; + } + + // Fill MLO Attributes + scanResult.setApMldMacAddress(networkDetail.getMldMacAddress()); + scanResult.setApMloLinkId(networkDetail.getMloLinkId()); + scanResult.setAffiliatedMloLinks(networkDetail.getAffiliatedMloLinks()); + + results.add(scanDetail); + } + if (mVerboseLoggingEnabled) { + Log.d(TAG, "get " + results.size() + " scan results from wificond"); + } + + return results; + } + + @WifiAnnotations.WifiStandard + private static int wifiModeToWifiStandard(int wifiMode) { + switch (wifiMode) { + case InformationElementUtil.WifiMode.MODE_11A: + case InformationElementUtil.WifiMode.MODE_11B: + case InformationElementUtil.WifiMode.MODE_11G: + return ScanResult.WIFI_STANDARD_LEGACY; + case InformationElementUtil.WifiMode.MODE_11N: + return ScanResult.WIFI_STANDARD_11N; + case InformationElementUtil.WifiMode.MODE_11AC: + return ScanResult.WIFI_STANDARD_11AC; + case InformationElementUtil.WifiMode.MODE_11AX: + return ScanResult.WIFI_STANDARD_11AX; + case InformationElementUtil.WifiMode.MODE_11BE: + return ScanResult.WIFI_STANDARD_11BE; + case InformationElementUtil.WifiMode.MODE_UNDEFINED: + default: + return ScanResult.WIFI_STANDARD_UNKNOWN; + } + } + + /** + * Start PNO scan. + * @param ifaceName Name of the interface. + * @param pnoSettings Pno scan configuration. + * @return true on success. + */ + public boolean startPnoScan(@NonNull String ifaceName, PnoSettings pnoSettings) { + if (mMockWifiModem != null + && mMockWifiModem.isMethodConfigured( + MockWifiServiceUtil.MOCK_NL80211_SERVICE, "startPnoScan")) { + Log.i(TAG, "startPnoScan was called from mock wificond"); + return mMockWifiModem.getWifiNl80211Manager() + .startPnoScan(ifaceName, pnoSettings.toNativePnoSettings(), + Runnable::run, + new WifiNl80211Manager.PnoScanRequestCallback() { + @Override + public void onPnoRequestSucceeded() { + } + + @Override + public void onPnoRequestFailed() { + } + }); + } + return mWifiCondManager.startPnoScan(ifaceName, pnoSettings.toNativePnoSettings(), + Runnable::run, + new WifiNl80211Manager.PnoScanRequestCallback() { + @Override + public void onPnoRequestSucceeded() { + mWifiMetrics.incrementPnoScanStartAttemptCount(); + } + + @Override + public void onPnoRequestFailed() { + WifiStatsLog.write(WifiStatsLog.PNO_SCAN_STOPPED, + WifiStatsLog.PNO_SCAN_STOPPED__STOP_REASON__SCAN_FAILED, + 0, false, false, false, false, // default values + WifiStatsLog + .PNO_SCAN_STOPPED__FAILURE_CODE__WIFICOND_REQUEST_FAILURE); + } + }); + } + + /** + * Stop PNO scan. + * @param ifaceName Name of the interface. + * @return true on success. + */ + public boolean stopPnoScan(@NonNull String ifaceName) { + return mWifiCondManager.stopPnoScan(ifaceName); + } + + /** + * Sends an arbitrary 802.11 management frame on the current channel. + * + * @param ifaceName Name of the interface. + * @param frame Bytes of the 802.11 management frame to be sent, including the header, but not + * including the frame check sequence (FCS). + * @param callback A callback triggered when the transmitted frame is ACKed or the transmission + * fails. + * @param mcs The MCS index that the frame will be sent at. If mcs < 0, the driver will select + * the rate automatically. If the device does not support sending the frame at a + * specified MCS rate, the transmission will be aborted and + * {@link WifiNl80211Manager.SendMgmtFrameCallback#onFailure(int)} will be called + * with reason {@link WifiNl80211Manager#SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED}. + */ + public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame, + @NonNull WifiNl80211Manager.SendMgmtFrameCallback callback, int mcs) { + mWifiCondManager.sendMgmtFrame(ifaceName, frame, mcs, Runnable::run, callback); + } + + /** + * Sends a probe request to the AP and waits for a response in order to determine whether + * there is connectivity between the device and AP. + * + * @param ifaceName Name of the interface. + * @param receiverMac the MAC address of the AP that the probe request will be sent to. + * @param callback callback triggered when the probe was ACKed by the AP, or when + * an error occurs after the link probe was started. + * @param mcs The MCS index that this probe will be sent at. If mcs < 0, the driver will select + * the rate automatically. If the device does not support sending the frame at a + * specified MCS rate, the transmission will be aborted and + * {@link WifiNl80211Manager.SendMgmtFrameCallback#onFailure(int)} will be called + * with reason {@link WifiNl80211Manager#SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED}. + */ + public void probeLink(@NonNull String ifaceName, @NonNull MacAddress receiverMac, + @NonNull WifiNl80211Manager.SendMgmtFrameCallback callback, int mcs) { + if (callback == null) { + Log.e(TAG, "callback cannot be null!"); + return; + } + + if (receiverMac == null) { + Log.e(TAG, "Receiver MAC address cannot be null!"); + callback.onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + return; + } + + String senderMacStr = getMacAddress(ifaceName); + if (senderMacStr == null) { + Log.e(TAG, "Failed to get this device's MAC Address"); + callback.onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + return; + } + + byte[] frame = buildProbeRequestFrame( + receiverMac.toByteArray(), + NativeUtil.macAddressToByteArray(senderMacStr)); + sendMgmtFrame(ifaceName, frame, callback, mcs); + } + + // header = 24 bytes, minimal body = 2 bytes, no FCS (will be added by driver) + private static final int BASIC_PROBE_REQUEST_FRAME_SIZE = 24 + 2; + + private byte[] buildProbeRequestFrame(byte[] receiverMac, byte[] transmitterMac) { + ByteBuffer frame = ByteBuffer.allocate(BASIC_PROBE_REQUEST_FRAME_SIZE); + // ByteBuffer is big endian by default, switch to little endian + frame.order(ByteOrder.LITTLE_ENDIAN); + + // Protocol version = 0, Type = management, Subtype = Probe Request + frame.put((byte) 0x40); + + // no flags set + frame.put((byte) 0x00); + + // duration = 60 microseconds. Note: this is little endian + // Note: driver should calculate the duration and replace it before sending, putting a + // reasonable default value here just in case. + frame.putShort((short) 0x3c); + + // receiver/destination MAC address byte array + frame.put(receiverMac); + // sender MAC address byte array + frame.put(transmitterMac); + // BSSID (same as receiver address since we are sending to the AP) + frame.put(receiverMac); + + // Generate random sequence number, fragment number = 0 + // Note: driver should replace the sequence number with the correct number that is + // incremented from the last used sequence number. Putting a random sequence number as a + // default here just in case. + // bit 0 is least significant bit, bit 15 is most significant bit + // bits [0, 7] go in byte 0 + // bits [8, 15] go in byte 1 + // bits [0, 3] represent the fragment number (which is 0) + // bits [4, 15] represent the sequence number (which is random) + // clear bits [0, 3] to set fragment number = 0 + short sequenceAndFragmentNumber = (short) (mRandom.nextInt() & 0xfff0); + frame.putShort(sequenceAndFragmentNumber); + + // NL80211 rejects frames with an empty body, so we just need to put a placeholder + // information element. + // Tag for SSID + frame.put((byte) 0x00); + // Represents broadcast SSID. Not accurate, but works as placeholder. + frame.put((byte) 0x00); + + return frame.array(); + } + + private static final int CONNECT_TO_HOSTAPD_RETRY_INTERVAL_MS = 100; + private static final int CONNECT_TO_HOSTAPD_RETRY_TIMES = 50; + /** + * This method is called to wait for establishing connection to hostapd. + * + * @return true if connection is established, false otherwise. + */ + private boolean startAndWaitForHostapdConnection() { + // Start initialization if not already started. + if (!mHostapdHal.isInitializationStarted() + && !mHostapdHal.initialize()) { + return false; + } + if (!mHostapdHal.startDaemon()) { + Log.e(TAG, "Failed to startup hostapd"); + return false; + } + boolean connected = false; + int connectTries = 0; + while (!connected && connectTries++ < CONNECT_TO_HOSTAPD_RETRY_TIMES) { + // Check if the initialization is complete. + connected = mHostapdHal.isInitializationComplete(); + if (connected) { + break; + } + try { + Thread.sleep(CONNECT_TO_HOSTAPD_RETRY_INTERVAL_MS); + } catch (InterruptedException ignore) { + } + } + return connected; + } + + /** + * Start Soft AP operation using the provided configuration. + * + * @param ifaceName Name of the interface. + * @param config Configuration to use for the soft ap created. + * @param isMetered Indicates the network is metered or not. + * @param callback Callback for AP events. + * @return one of {@link SoftApManager.StartResult} + */ + public @SoftApManager.StartResult int startSoftAp( + @NonNull String ifaceName, SoftApConfiguration config, boolean isMetered, + SoftApHalCallback callback) { + if (mHostapdHal.isApInfoCallbackSupported()) { + if (!mHostapdHal.registerApCallback(ifaceName, callback)) { + Log.e(TAG, "Failed to register ap hal event callback"); + return SoftApManager.START_RESULT_FAILURE_REGISTER_AP_CALLBACK_HOSTAPD; + } + } else { + SoftApHalCallbackFromWificond softApHalCallbackFromWificond = + new SoftApHalCallbackFromWificond(ifaceName, callback); + if (!mWifiCondManager.registerApCallback(ifaceName, + Runnable::run, softApHalCallbackFromWificond)) { + Log.e(TAG, "Failed to register ap hal event callback from wificond"); + return SoftApManager.START_RESULT_FAILURE_REGISTER_AP_CALLBACK_WIFICOND; + } + } + + if (!mHostapdHal.addAccessPoint(ifaceName, config, isMetered, callback::onFailure)) { + String errorMsg = "Failed to add softAp"; + Log.e(TAG, errorMsg); + mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd(); + takeBugReportInterfaceFailureIfNeeded("Wi-Fi BugReport (softap interface failure)", + errorMsg); + return SoftApManager.START_RESULT_FAILURE_ADD_AP_HOSTAPD; + } + + return SoftApManager.START_RESULT_SUCCESS; + } + + /** + * Force a softap client disconnect with specific reason code. + * + * @param ifaceName Name of the interface. + * @param client Mac address to force disconnect in clients of the SoftAp. + * @param reasonCode One of disconnect reason code which defined in {@link ApConfigUtil}. + * @return true on success, false otherwise. + */ + public boolean forceClientDisconnect(@NonNull String ifaceName, + @NonNull MacAddress client, int reasonCode) { + return mHostapdHal.forceClientDisconnect(ifaceName, client, reasonCode); + } + + /** + * Set MAC address of the given interface + * @param interfaceName Name of the interface + * @param mac Mac address to change into + * @return true on success + */ + public boolean setStaMacAddress(String interfaceName, MacAddress mac) { + // TODO(b/72459123): Suppress interface down/up events from this call + // Trigger an explicit disconnect to avoid losing the disconnect event reason (if currently + // connected) from supplicant if the interface is brought down for MAC address change. + disconnect(interfaceName); + return mWifiVendorHal.setStaMacAddress(interfaceName, mac); + } + + /** + * Set MAC address of the given interface + * @param interfaceName Name of the interface + * @param mac Mac address to change into + * @return true on success + */ + public boolean setApMacAddress(String interfaceName, MacAddress mac) { + return mWifiVendorHal.setApMacAddress(interfaceName, mac); + } + + /** + * Returns true if Hal version supports setMacAddress, otherwise false. + * + * @param interfaceName Name of the interface + */ + public boolean isApSetMacAddressSupported(@NonNull String interfaceName) { + return mWifiVendorHal.isApSetMacAddressSupported(interfaceName); + } + + /** + * Get the factory MAC address of the given interface + * @param interfaceName Name of the interface. + * @return factory MAC address, or null on a failed call or if feature is unavailable. + */ + public MacAddress getStaFactoryMacAddress(@NonNull String interfaceName) { + return mWifiVendorHal.getStaFactoryMacAddress(interfaceName); + } + + /** + * Get the factory MAC address of the given interface + * @param interfaceName Name of the interface. + * @return factory MAC address, or null on a failed call or if feature is unavailable. + */ + public MacAddress getApFactoryMacAddress(@NonNull String interfaceName) { + return mWifiVendorHal.getApFactoryMacAddress(interfaceName); + } + + /** + * Reset MAC address to factory MAC address on the given interface + * + * @param interfaceName Name of the interface + * @return true for success + */ + public boolean resetApMacToFactoryMacAddress(@NonNull String interfaceName) { + return mWifiVendorHal.resetApMacToFactoryMacAddress(interfaceName); + } + + /** + * Set the unsafe channels and restrictions to avoid for coex. + * @param unsafeChannels List of {@link CoexUnsafeChannel} to avoid + * @param restrictions Bitmask of WifiManager.COEX_RESTRICTION_ flags + */ + public void setCoexUnsafeChannels( + @NonNull List unsafeChannels, int restrictions) { + mCachedCoexUnsafeChannels.clear(); + mCachedCoexUnsafeChannels.addAll(unsafeChannels); + mCachedCoexRestrictions = restrictions; + mWifiVendorHal.setCoexUnsafeChannels(mCachedCoexUnsafeChannels, mCachedCoexRestrictions); + } + + /******************************************************** + * Hostapd operations + ********************************************************/ + + /** + * Callback to notify hostapd death. + */ + public interface HostapdDeathEventHandler { + /** + * Invoked when the supplicant dies. + */ + void onDeath(); + } + + /******************************************************** + * Supplicant operations + ********************************************************/ + + /** + * Callback to notify supplicant death. + */ + public interface SupplicantDeathEventHandler { + /** + * Invoked when the supplicant dies. + */ + void onDeath(); + } + + /** + * Set supplicant log level + * + * @param turnOnVerbose Whether to turn on verbose logging or not. + */ + public void setSupplicantLogLevel(boolean turnOnVerbose) { + mSupplicantStaIfaceHal.setLogLevel(turnOnVerbose); + } + + /** + * Trigger a reconnection if the iface is disconnected. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean reconnect(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.reconnect(ifaceName); + } + + /** + * Trigger a reassociation even if the iface is currently connected. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean reassociate(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.reassociate(ifaceName); + } + + /** + * Trigger a disconnection from the currently connected network. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean disconnect(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.disconnect(ifaceName); + } + + /** + * Makes a callback to HIDL to getMacAddress from supplicant + * + * @param ifaceName Name of the interface. + * @return string containing the MAC address, or null on a failed call + */ + public String getMacAddress(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.getMacAddress(ifaceName); + } + + public static final int RX_FILTER_TYPE_V4_MULTICAST = 0; + public static final int RX_FILTER_TYPE_V6_MULTICAST = 1; + /** + * Start filtering out Multicast V4 packets + * @param ifaceName Name of the interface. + * @return {@code true} if the operation succeeded, {@code false} otherwise + * + * Multicast filtering rules work as follows: + * + * The driver can filter multicast (v4 and/or v6) and broadcast packets when in + * a power optimized mode (typically when screen goes off). + * + * In order to prevent the driver from filtering the multicast/broadcast packets, we have to + * add a DRIVER RXFILTER-ADD rule followed by DRIVER RXFILTER-START to make the rule effective + * + * DRIVER RXFILTER-ADD Num + * where Num = 0 - Unicast, 1 - Broadcast, 2 - Mutil4 or 3 - Multi6 + * + * and DRIVER RXFILTER-START + * In order to stop the usage of these rules, we do + * + * DRIVER RXFILTER-STOP + * DRIVER RXFILTER-REMOVE Num + * where Num is as described for RXFILTER-ADD + * + * The SETSUSPENDOPT driver command overrides the filtering rules + */ + public boolean startFilteringMulticastV4Packets(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.stopRxFilter(ifaceName) + && mSupplicantStaIfaceHal.removeRxFilter( + ifaceName, RX_FILTER_TYPE_V4_MULTICAST) + && mSupplicantStaIfaceHal.startRxFilter(ifaceName); + } + + /** + * Stop filtering out Multicast V4 packets. + * @param ifaceName Name of the interface. + * @return {@code true} if the operation succeeded, {@code false} otherwise + */ + public boolean stopFilteringMulticastV4Packets(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.stopRxFilter(ifaceName) + && mSupplicantStaIfaceHal.addRxFilter( + ifaceName, RX_FILTER_TYPE_V4_MULTICAST) + && mSupplicantStaIfaceHal.startRxFilter(ifaceName); + } + + /** + * Start filtering out Multicast V6 packets + * @param ifaceName Name of the interface. + * @return {@code true} if the operation succeeded, {@code false} otherwise + */ + public boolean startFilteringMulticastV6Packets(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.stopRxFilter(ifaceName) + && mSupplicantStaIfaceHal.removeRxFilter( + ifaceName, RX_FILTER_TYPE_V6_MULTICAST) + && mSupplicantStaIfaceHal.startRxFilter(ifaceName); + } + + /** + * Stop filtering out Multicast V6 packets. + * @param ifaceName Name of the interface. + * @return {@code true} if the operation succeeded, {@code false} otherwise + */ + public boolean stopFilteringMulticastV6Packets(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.stopRxFilter(ifaceName) + && mSupplicantStaIfaceHal.addRxFilter( + ifaceName, RX_FILTER_TYPE_V6_MULTICAST) + && mSupplicantStaIfaceHal.startRxFilter(ifaceName); + } + + public static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED = 0; + public static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED = 1; + public static final int BLUETOOTH_COEXISTENCE_MODE_SENSE = 2; + /** + * Sets the bluetooth coexistence mode. + * + * @param ifaceName Name of the interface. + * @param mode One of {@link #BLUETOOTH_COEXISTENCE_MODE_DISABLED}, + * {@link #BLUETOOTH_COEXISTENCE_MODE_ENABLED}, or + * {@link #BLUETOOTH_COEXISTENCE_MODE_SENSE}. + * @return Whether the mode was successfully set. + */ + public boolean setBluetoothCoexistenceMode(@NonNull String ifaceName, int mode) { + return mSupplicantStaIfaceHal.setBtCoexistenceMode(ifaceName, mode); + } + + /** + * Enable or disable Bluetooth coexistence scan mode. When this mode is on, + * some of the low-level scan parameters used by the driver are changed to + * reduce interference with A2DP streaming. + * + * @param ifaceName Name of the interface. + * @param setCoexScanMode whether to enable or disable this mode + * @return {@code true} if the command succeeded, {@code false} otherwise. + */ + public boolean setBluetoothCoexistenceScanMode( + @NonNull String ifaceName, boolean setCoexScanMode) { + return mSupplicantStaIfaceHal.setBtCoexistenceScanModeEnabled( + ifaceName, setCoexScanMode); + } + + /** + * Enable or disable suspend mode optimizations. + * + * @param ifaceName Name of the interface. + * @param enabled true to enable, false otherwise. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setSuspendOptimizations(@NonNull String ifaceName, boolean enabled) { + return mSupplicantStaIfaceHal.setSuspendModeEnabled(ifaceName, enabled); + } + + /** + * Set country code for STA interface + * + * @param ifaceName Name of the STA interface. + * @param countryCode 2 byte ASCII string. For ex: US, CA. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setStaCountryCode(@NonNull String ifaceName, String countryCode) { + if (mSupplicantStaIfaceHal.setCountryCode(ifaceName, countryCode)) { + if (mCountryCodeChangeListener != null) { + mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode); + } + return true; + } + return false; + } + + /** + * Flush all previously configured HLPs. + * + * @return true if request is sent successfully, false otherwise. + */ + public boolean flushAllHlp(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.flushAllHlp(ifaceName); + } + + /** + * Set FILS HLP packet. + * + * @param dst Destination MAC address. + * @param hlpPacket Hlp Packet data in hex. + * @return true if request is sent successfully, false otherwise. + */ + public boolean addHlpReq(@NonNull String ifaceName, MacAddress dst, byte [] hlpPacket) { + return mSupplicantStaIfaceHal.addHlpReq(ifaceName, dst.toByteArray(), hlpPacket); + } + + /** + * Initiate TDLS discover and setup or teardown with the specified peer. + * + * @param ifaceName Name of the interface. + * @param macAddr MAC Address of the peer. + * @param enable true to start discovery and setup, false to teardown. + * @return true if request is sent successfully, false otherwise. + */ + public boolean startTdls(@NonNull String ifaceName, String macAddr, boolean enable) { + boolean ret = true; + if (enable) { + mSupplicantStaIfaceHal.initiateTdlsDiscover(ifaceName, macAddr); + ret = mSupplicantStaIfaceHal.initiateTdlsSetup(ifaceName, macAddr); + } else { + ret = mSupplicantStaIfaceHal.initiateTdlsTeardown(ifaceName, macAddr); + } + return ret; + } + + /** + * Start WPS pin display operation with the specified peer. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the peer. + * @return true if request is sent successfully, false otherwise. + */ + public boolean startWpsPbc(@NonNull String ifaceName, String bssid) { + return mSupplicantStaIfaceHal.startWpsPbc(ifaceName, bssid); + } + + /** + * Start WPS pin keypad operation with the specified pin. + * + * @param ifaceName Name of the interface. + * @param pin Pin to be used. + * @return true if request is sent successfully, false otherwise. + */ + public boolean startWpsPinKeypad(@NonNull String ifaceName, String pin) { + return mSupplicantStaIfaceHal.startWpsPinKeypad(ifaceName, pin); + } + + /** + * Start WPS pin display operation with the specified peer. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the peer. + * @return new pin generated on success, null otherwise. + */ + public String startWpsPinDisplay(@NonNull String ifaceName, String bssid) { + return mSupplicantStaIfaceHal.startWpsPinDisplay(ifaceName, bssid); + } + + /** + * Sets whether to use external sim for SIM/USIM processing. + * + * @param ifaceName Name of the interface. + * @param external true to enable, false otherwise. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setExternalSim(@NonNull String ifaceName, boolean external) { + return mSupplicantStaIfaceHal.setExternalSim(ifaceName, external); + } + + /** + * Sim auth response types. + */ + public static final String SIM_AUTH_RESP_TYPE_GSM_AUTH = "GSM-AUTH"; + public static final String SIM_AUTH_RESP_TYPE_UMTS_AUTH = "UMTS-AUTH"; + public static final String SIM_AUTH_RESP_TYPE_UMTS_AUTS = "UMTS-AUTS"; + + /** + * EAP-SIM Error Codes + */ + public static final int EAP_SIM_NOT_SUBSCRIBED = 1031; + public static final int EAP_SIM_VENDOR_SPECIFIC_CERT_EXPIRED = 16385; + + /** + * Send the sim auth response for the currently configured network. + * + * @param ifaceName Name of the interface. + * @param type |GSM-AUTH|, |UMTS-AUTH| or |UMTS-AUTS|. + * @param response Response params. + * @return true if succeeds, false otherwise. + */ + public boolean simAuthResponse( + @NonNull String ifaceName, String type, String response) { + if (SIM_AUTH_RESP_TYPE_GSM_AUTH.equals(type)) { + return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthResponse( + ifaceName, response); + } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTH.equals(type)) { + return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthResponse( + ifaceName, response); + } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTS.equals(type)) { + return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAutsResponse( + ifaceName, response); + } else { + return false; + } + } + + /** + * Send the eap sim gsm auth failure for the currently configured network. + * + * @param ifaceName Name of the interface. + * @return true if succeeds, false otherwise. + */ + public boolean simAuthFailedResponse(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthFailure(ifaceName); + } + + /** + * Send the eap sim umts auth failure for the currently configured network. + * + * @param ifaceName Name of the interface. + * @return true if succeeds, false otherwise. + */ + public boolean umtsAuthFailedResponse(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthFailure(ifaceName); + } + + /** + * Send the eap identity response for the currently configured network. + * + * @param ifaceName Name of the interface. + * @param unencryptedResponse String to send. + * @param encryptedResponse String to send. + * @return true if succeeds, false otherwise. + */ + public boolean simIdentityResponse(@NonNull String ifaceName, String unencryptedResponse, + String encryptedResponse) { + return mSupplicantStaIfaceHal.sendCurrentNetworkEapIdentityResponse(ifaceName, + unencryptedResponse, encryptedResponse); + } + + /** + * This get anonymous identity from supplicant and returns it as a string. + * + * @param ifaceName Name of the interface. + * @return anonymous identity string if succeeds, null otherwise. + */ + public String getEapAnonymousIdentity(@NonNull String ifaceName) { + String anonymousIdentity = mSupplicantStaIfaceHal + .getCurrentNetworkEapAnonymousIdentity(ifaceName); + + if (TextUtils.isEmpty(anonymousIdentity)) { + return anonymousIdentity; + } + + int indexOfDecoration = anonymousIdentity.lastIndexOf('!'); + if (indexOfDecoration >= 0) { + if (anonymousIdentity.substring(indexOfDecoration).length() < 2) { + // Invalid identity, shouldn't happen + Log.e(TAG, "Unexpected anonymous identity: " + anonymousIdentity); + return null; + } + // Truncate RFC 7542 decorated prefix, if exists. Keep only the anonymous identity or + // pseudonym. + anonymousIdentity = anonymousIdentity.substring(indexOfDecoration + 1); + } + + return anonymousIdentity; + } + + /** + * Start WPS pin registrar operation with the specified peer and pin. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the peer. + * @param pin Pin to be used. + * @return true if request is sent successfully, false otherwise. + */ + public boolean startWpsRegistrar(@NonNull String ifaceName, String bssid, String pin) { + return mSupplicantStaIfaceHal.startWpsRegistrar(ifaceName, bssid, pin); + } + + /** + * Cancels any ongoing WPS requests. + * + * @param ifaceName Name of the interface. + * @return true if request is sent successfully, false otherwise. + */ + public boolean cancelWps(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.cancelWps(ifaceName); + } + + /** + * Set WPS device name. + * + * @param ifaceName Name of the interface. + * @param name String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setDeviceName(@NonNull String ifaceName, String name) { + return mSupplicantStaIfaceHal.setWpsDeviceName(ifaceName, name); + } + + /** + * Set WPS device type. + * + * @param ifaceName Name of the interface. + * @param type Type specified as a string. Used format: -- + * @return true if request is sent successfully, false otherwise. + */ + public boolean setDeviceType(@NonNull String ifaceName, String type) { + return mSupplicantStaIfaceHal.setWpsDeviceType(ifaceName, type); + } + + /** + * Set WPS config methods + * + * @param cfg List of config methods. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setConfigMethods(@NonNull String ifaceName, String cfg) { + return mSupplicantStaIfaceHal.setWpsConfigMethods(ifaceName, cfg); + } + + /** + * Set WPS manufacturer. + * + * @param ifaceName Name of the interface. + * @param value String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setManufacturer(@NonNull String ifaceName, String value) { + return mSupplicantStaIfaceHal.setWpsManufacturer(ifaceName, value); + } + + /** + * Set WPS model name. + * + * @param ifaceName Name of the interface. + * @param value String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setModelName(@NonNull String ifaceName, String value) { + return mSupplicantStaIfaceHal.setWpsModelName(ifaceName, value); + } + + /** + * Set WPS model number. + * + * @param ifaceName Name of the interface. + * @param value String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setModelNumber(@NonNull String ifaceName, String value) { + return mSupplicantStaIfaceHal.setWpsModelNumber(ifaceName, value); + } + + /** + * Set WPS serial number. + * + * @param ifaceName Name of the interface. + * @param value String to be set. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setSerialNumber(@NonNull String ifaceName, String value) { + return mSupplicantStaIfaceHal.setWpsSerialNumber(ifaceName, value); + } + + /** + * Enable or disable power save mode. + * + * @param ifaceName Name of the interface. + * @param enabled true to enable, false to disable. + */ + public void setPowerSave(@NonNull String ifaceName, boolean enabled) { + mSupplicantStaIfaceHal.setPowerSave(ifaceName, enabled); + } + + /** + * Enable or disable low latency mode. + * + * @param enabled true to enable, false to disable. + * @return true on success, false on failure + */ + public boolean setLowLatencyMode(boolean enabled) { + return mWifiVendorHal.setLowLatencyMode(enabled); + } + + /** + * Set concurrency priority between P2P & STA operations. + * + * @param isStaHigherPriority Set to true to prefer STA over P2P during concurrency operations, + * false otherwise. + * @return true if request is sent successfully, false otherwise. + */ + public boolean setConcurrencyPriority(boolean isStaHigherPriority) { + return mSupplicantStaIfaceHal.setConcurrencyPriority(isStaHigherPriority); + } + + /** + * Enable/Disable auto reconnect functionality in wpa_supplicant. + * + * @param ifaceName Name of the interface. + * @param enable true to enable auto reconnecting, false to disable. + * @return true if request is sent successfully, false otherwise. + */ + public boolean enableStaAutoReconnect(@NonNull String ifaceName, boolean enable) { + return mSupplicantStaIfaceHal.enableAutoReconnect(ifaceName, enable); + } + + /** + * Add the provided network configuration to wpa_supplicant and initiate connection to it. + * This method does the following: + * 1. Abort any ongoing scan to unblock the connection request. + * 2. Remove any existing network in wpa_supplicant(This implicitly triggers disconnect). + * 3. Add a new network to wpa_supplicant. + * 4. Save the provided configuration to wpa_supplicant. + * 5. Select the new network in wpa_supplicant. + * 6. Triggers reconnect command to wpa_supplicant. + * + * @param ifaceName Name of the interface. + * @param configuration WifiConfiguration parameters for the provided network. + * @return {@code true} if it succeeds, {@code false} otherwise + */ + public boolean connectToNetwork(@NonNull String ifaceName, WifiConfiguration configuration) { + // Abort ongoing scan before connect() to unblock connection request. + mWifiCondManager.abortScan(ifaceName); + return mSupplicantStaIfaceHal.connectToNetwork(ifaceName, configuration); + } + + /** + * Initiates roaming to the already configured network in wpa_supplicant. If the network + * configuration provided does not match the already configured network, then this triggers + * a new connection attempt (instead of roam). + * 1. Abort any ongoing scan to unblock the roam request. + * 2. First check if we're attempting to connect to the same network as we currently have + * configured. + * 3. Set the new bssid for the network in wpa_supplicant. + * 4. Triggers reassociate command to wpa_supplicant. + * + * @param ifaceName Name of the interface. + * @param configuration WifiConfiguration parameters for the provided network. + * @return {@code true} if it succeeds, {@code false} otherwise + */ + public boolean roamToNetwork(@NonNull String ifaceName, WifiConfiguration configuration) { + // Abort ongoing scan before connect() to unblock roaming request. + mWifiCondManager.abortScan(ifaceName); + return mSupplicantStaIfaceHal.roamToNetwork(ifaceName, configuration); + } + + /** + * Remove all the networks. + * + * @param ifaceName Name of the interface. + * @return {@code true} if it succeeds, {@code false} otherwise + */ + public boolean removeAllNetworks(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.removeAllNetworks(ifaceName); + } + + /** + * Disable the currently configured network in supplicant + * + * @param ifaceName Name of the interface. + */ + public boolean disableNetwork(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.disableCurrentNetwork(ifaceName); + } + + /** + * Set the BSSID for the currently configured network in wpa_supplicant. + * + * @param ifaceName Name of the interface. + * @return true if successful, false otherwise. + */ + public boolean setNetworkBSSID(@NonNull String ifaceName, String bssid) { + return mSupplicantStaIfaceHal.setCurrentNetworkBssid(ifaceName, bssid); + } + + /** + * Initiate ANQP query. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the AP to be queried + * @param anqpIds Set of anqp IDs. + * @param hs20Subtypes Set of HS20 subtypes. + * @return true on success, false otherwise. + */ + public boolean requestAnqp( + @NonNull String ifaceName, String bssid, Set anqpIds, + Set hs20Subtypes) { + if (bssid == null || ((anqpIds == null || anqpIds.isEmpty()) + && (hs20Subtypes == null || hs20Subtypes.isEmpty()))) { + Log.e(TAG, "Invalid arguments for ANQP request."); + return false; + } + ArrayList anqpIdList = new ArrayList<>(); + for (Integer anqpId : anqpIds) { + anqpIdList.add(anqpId.shortValue()); + } + ArrayList hs20SubtypeList = new ArrayList<>(); + hs20SubtypeList.addAll(hs20Subtypes); + return mSupplicantStaIfaceHal.initiateAnqpQuery( + ifaceName, bssid, anqpIdList, hs20SubtypeList); + } + + /** + * Request a passpoint icon file |filename| from the specified AP |bssid|. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the AP + * @param fileName name of the icon file + * @return true if request is sent successfully, false otherwise + */ + public boolean requestIcon(@NonNull String ifaceName, String bssid, String fileName) { + if (bssid == null || fileName == null) { + Log.e(TAG, "Invalid arguments for Icon request."); + return false; + } + return mSupplicantStaIfaceHal.initiateHs20IconQuery(ifaceName, bssid, fileName); + } + + /** + * Initiate Venue URL ANQP query. + * + * @param ifaceName Name of the interface. + * @param bssid BSSID of the AP to be queried + * @return true on success, false otherwise. + */ + public boolean requestVenueUrlAnqp( + @NonNull String ifaceName, String bssid) { + if (bssid == null) { + Log.e(TAG, "Invalid arguments for Venue URL ANQP request."); + return false; + } + return mSupplicantStaIfaceHal.initiateVenueUrlAnqpQuery(ifaceName, bssid); + } + + /** + * Get the currently configured network's WPS NFC token. + * + * @param ifaceName Name of the interface. + * @return Hex string corresponding to the WPS NFC token. + */ + public String getCurrentNetworkWpsNfcConfigurationToken(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.getCurrentNetworkWpsNfcConfigurationToken(ifaceName); + } + + /** + * Clean HAL cached data for |networkId|. + * + * @param networkId network id of the network to be removed from supplicant. + */ + public void removeNetworkCachedData(int networkId) { + mSupplicantStaIfaceHal.removeNetworkCachedData(networkId); + } + + /** Clear HAL cached data for |networkId| if MAC address is changed. + * + * @param networkId network id of the network to be checked. + * @param curMacAddress current MAC address + */ + public void removeNetworkCachedDataIfNeeded(int networkId, MacAddress curMacAddress) { + mSupplicantStaIfaceHal.removeNetworkCachedDataIfNeeded(networkId, curMacAddress); + } + + /* + * DPP + */ + + /** + * Adds a DPP peer URI to the URI list. + * + * @param ifaceName Interface name + * @param uri Bootstrap (URI) string (e.g. DPP:....) + * @return ID, or -1 for failure + */ + public int addDppPeerUri(@NonNull String ifaceName, @NonNull String uri) { + return mSupplicantStaIfaceHal.addDppPeerUri(ifaceName, uri); + } + + /** + * Removes a DPP URI to the URI list given an ID. + * + * @param ifaceName Interface name + * @param bootstrapId Bootstrap (URI) ID + * @return true when operation is successful, or false for failure + */ + public boolean removeDppUri(@NonNull String ifaceName, int bootstrapId) { + return mSupplicantStaIfaceHal.removeDppUri(ifaceName, bootstrapId); + } + + /** + * Stops/aborts DPP Initiator request + * + * @param ifaceName Interface name + * @return true when operation is successful, or false for failure + */ + public boolean stopDppInitiator(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.stopDppInitiator(ifaceName); + } + + /** + * Starts DPP Configurator-Initiator request + * + * @param ifaceName Interface name + * @param peerBootstrapId Peer's bootstrap (URI) ID + * @param ownBootstrapId Own bootstrap (URI) ID - Optional, 0 for none + * @param ssid SSID of the selected network + * @param password Password of the selected network, or + * @param psk PSK of the selected network in hexadecimal representation + * @param netRole The network role of the enrollee (STA or AP) + * @param securityAkm Security AKM to use: PSK, SAE + * @return true when operation is successful, or false for failure + */ + public boolean startDppConfiguratorInitiator(@NonNull String ifaceName, int peerBootstrapId, + int ownBootstrapId, @NonNull String ssid, String password, String psk, + int netRole, int securityAkm, byte[] privEcKey) { + return mSupplicantStaIfaceHal.startDppConfiguratorInitiator(ifaceName, peerBootstrapId, + ownBootstrapId, ssid, password, psk, netRole, securityAkm, privEcKey); + } + + /** + * Starts DPP Enrollee-Initiator request + * + * @param ifaceName Interface name + * @param peerBootstrapId Peer's bootstrap (URI) ID + * @param ownBootstrapId Own bootstrap (URI) ID - Optional, 0 for none + * @return true when operation is successful, or false for failure + */ + public boolean startDppEnrolleeInitiator(@NonNull String ifaceName, int peerBootstrapId, + int ownBootstrapId) { + return mSupplicantStaIfaceHal.startDppEnrolleeInitiator(ifaceName, peerBootstrapId, + ownBootstrapId); + } + + /** + * Callback to notify about DPP success, failure and progress events. + */ + public interface DppEventCallback { + /** + * Called when local DPP Enrollee successfully receives a new Wi-Fi configuration from the + * peer DPP configurator. + * + * @param newWifiConfiguration New Wi-Fi configuration received from the configurator + * @param connStatusRequested Flag to indicate that the configurator requested + * connection status + */ + void onSuccessConfigReceived(WifiConfiguration newWifiConfiguration, + boolean connStatusRequested); + + /** + * DPP Success event. + * + * @param dppStatusCode Status code of the success event. + */ + void onSuccess(int dppStatusCode); + + /** + * DPP Progress event. + * + * @param dppStatusCode Status code of the progress event. + */ + void onProgress(int dppStatusCode); + + /** + * DPP Failure event. + * + * @param dppStatusCode Status code of the failure event. + * @param ssid SSID of the network the Enrollee tried to connect to. + * @param channelList List of channels the Enrollee scanned for the network. + * @param bandList List of bands the Enrollee supports. + */ + void onFailure(int dppStatusCode, String ssid, String channelList, int[] bandList); + + /** + * DPP Configurator Private keys update. + * + * @param key Configurator's private EC key. + */ + void onDppConfiguratorKeyUpdate(byte[] key); + + /** + * Indicates that DPP connection status result frame is sent + * + * @param result DPP Status value indicating the result of a connection attempt. + */ + void onConnectionStatusResultSent(int result); + } + + /** + * Class to get generated bootstrap info for DPP responder operation. + */ + public static class DppBootstrapQrCodeInfo { + public int bootstrapId; + public int listenChannel; + public String uri = new String(); + DppBootstrapQrCodeInfo() { + bootstrapId = -1; + listenChannel = -1; + } + } + + /** + * Generate DPP bootstrap Information:Bootstrap ID, DPP URI and the listen channel. + * + * @param ifaceName Interface name + * @param deviceInfo Device specific info to attach in DPP URI. + * @param dppCurve Elliptic curve cryptography type used to generate DPP + * public/private key pair. + * @return ID, or -1 for failure + */ + public DppBootstrapQrCodeInfo generateDppBootstrapInfoForResponder(@NonNull String ifaceName, + String deviceInfo, int dppCurve) { + return mSupplicantStaIfaceHal.generateDppBootstrapInfoForResponder(ifaceName, + getMacAddress(ifaceName), deviceInfo, dppCurve); + } + + /** + * start DPP Enrollee responder mode. + * + * @param ifaceName Interface name + * @param listenChannel Listen channel to wait for DPP authentication request. + * @return ID, or -1 for failure + */ + public boolean startDppEnrolleeResponder(@NonNull String ifaceName, int listenChannel) { + return mSupplicantStaIfaceHal.startDppEnrolleeResponder(ifaceName, listenChannel); + } + + /** + * Stops/aborts DPP Responder request + * + * @param ifaceName Interface name + * @param ownBootstrapId Bootstrap (URI) ID + * @return true when operation is successful, or false for failure + */ + public boolean stopDppResponder(@NonNull String ifaceName, int ownBootstrapId) { + return mSupplicantStaIfaceHal.stopDppResponder(ifaceName, ownBootstrapId); + } + + + /** + * Registers DPP event callbacks. + * + * @param dppEventCallback Callback object. + */ + public void registerDppEventCallback(DppEventCallback dppEventCallback) { + mSupplicantStaIfaceHal.registerDppCallback(dppEventCallback); + } + + /** + * Check whether Supplicant is using the AIDL HAL service. + * + * @return true if the Supplicant is using the AIDL service, false otherwise. + */ + public boolean isSupplicantUsingAidlService() { + return mSupplicantStaIfaceHal.isAidlService(); + } + + /** + * Check whether the Supplicant AIDL service is running at least the expected version. + * + * @param expectedVersion Version number to check. + * @return true if the AIDL service is available and >= the expected version, false otherwise. + */ + public boolean isSupplicantAidlServiceVersionAtLeast(int expectedVersion) { + return mSupplicantStaIfaceHal.isAidlServiceVersionAtLeast(expectedVersion); + } + + /******************************************************** + * Vendor HAL operations + ********************************************************/ + /** + * Callback to notify vendor HAL death. + */ + public interface VendorHalDeathEventHandler { + /** + * Invoked when the vendor HAL dies. + */ + void onDeath(); + } + + /** + * Callback to notify when vendor HAL detects that a change in radio mode. + */ + public interface VendorHalRadioModeChangeEventHandler { + /** + * Invoked when the vendor HAL detects a change to MCC mode. + * MCC (Multi channel concurrency) = Multiple interfaces are active on the same band, + * different channels, same radios. + * + * @param band Band on which MCC is detected (specified by one of the + * WifiScanner.WIFI_BAND_* constants) + */ + void onMcc(int band); + /** + * Invoked when the vendor HAL detects a change to SCC mode. + * SCC (Single channel concurrency) = Multiple interfaces are active on the same band, same + * channels, same radios. + * + * @param band Band on which SCC is detected (specified by one of the + * WifiScanner.WIFI_BAND_* constants) + */ + void onScc(int band); + /** + * Invoked when the vendor HAL detects a change to SBS mode. + * SBS (Single Band Simultaneous) = Multiple interfaces are active on the same band, + * different channels, different radios. + * + * @param band Band on which SBS is detected (specified by one of the + * WifiScanner.WIFI_BAND_* constants) + */ + void onSbs(int band); + /** + * Invoked when the vendor HAL detects a change to DBS mode. + * DBS (Dual Band Simultaneous) = Multiple interfaces are active on the different bands, + * different channels, different radios. + */ + void onDbs(); + } + + /** + * Tests whether the HAL is running or not + */ + public boolean isHalStarted() { + return mWifiVendorHal.isHalStarted(); + } + + /** + * Tests whether the HAL is supported or not + */ + public boolean isHalSupported() { + return mWifiVendorHal.isVendorHalSupported(); + } + + // TODO: Change variable names to camel style. + public static class ScanCapabilities { + public int max_scan_cache_size; + public int max_scan_buckets; + public int max_ap_cache_per_scan; + public int max_rssi_sample_size; + public int max_scan_reporting_threshold; + } + + /** + * Gets the scan capabilities + * + * @param ifaceName Name of the interface. + * @param capabilities object to be filled in + * @return true for success. false for failure + */ + public boolean getBgScanCapabilities( + @NonNull String ifaceName, ScanCapabilities capabilities) { + return mWifiVendorHal.getBgScanCapabilities(ifaceName, capabilities); + } + + public static class ChannelSettings { + public int frequency; + public int dwell_time_ms; + public boolean passive; + } + + public static class BucketSettings { + public int bucket; + public int band; + public int period_ms; + public int max_period_ms; + public int step_count; + public int report_events; + public int num_channels; + public ChannelSettings[] channels; + } + + /** + * Network parameters for hidden networks to be scanned for. + */ + public static class HiddenNetwork { + public String ssid; + + @Override + public boolean equals(Object otherObj) { + if (this == otherObj) { + return true; + } else if (otherObj == null || getClass() != otherObj.getClass()) { + return false; + } + HiddenNetwork other = (HiddenNetwork) otherObj; + return Objects.equals(ssid, other.ssid); + } + + @Override + public int hashCode() { + return Objects.hash(ssid); + } + } + + public static class ScanSettings { + /** + * Type of scan to perform. One of {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}, + * {@link WifiScanner#SCAN_TYPE_LOW_POWER} or {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}. + */ + @WifiAnnotations.ScanType + public int scanType; + public int base_period_ms; + public int max_ap_per_scan; + public int report_threshold_percent; + public int report_threshold_num_scans; + public int num_buckets; + public boolean enable6GhzRnr; + /* Not used for bg scans. Only works for single scans. */ + public HiddenNetwork[] hiddenNetworks; + public BucketSettings[] buckets; + public byte[] vendorIes; + } + + /** + * Network parameters to start PNO scan. + */ + public static class PnoNetwork { + public String ssid; + public byte flags; + public byte auth_bit_field; + public int[] frequencies; + + @Override + public boolean equals(Object otherObj) { + if (this == otherObj) { + return true; + } else if (otherObj == null || getClass() != otherObj.getClass()) { + return false; + } + PnoNetwork other = (PnoNetwork) otherObj; + return ((Objects.equals(ssid, other.ssid)) && (flags == other.flags) + && (auth_bit_field == other.auth_bit_field)) + && Arrays.equals(frequencies, other.frequencies); + } + + @Override + public int hashCode() { + return Objects.hash(ssid, flags, auth_bit_field, Arrays.hashCode(frequencies)); + } + + android.net.wifi.nl80211.PnoNetwork toNativePnoNetwork() { + android.net.wifi.nl80211.PnoNetwork nativePnoNetwork = + new android.net.wifi.nl80211.PnoNetwork(); + nativePnoNetwork.setHidden( + (flags & WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN) != 0); + try { + nativePnoNetwork.setSsid( + NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(ssid))); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + ssid, e); + return null; + } + nativePnoNetwork.setFrequenciesMhz(frequencies); + return nativePnoNetwork; + } + } + + /** + * Parameters to start PNO scan. This holds the list of networks which are going to used for + * PNO scan. + */ + public static class PnoSettings { + public int min5GHzRssi; + public int min24GHzRssi; + public int min6GHzRssi; + public int periodInMs; + public int scanIterations; + public int scanIntervalMultiplier; + public boolean isConnected; + public PnoNetwork[] networkList; + + android.net.wifi.nl80211.PnoSettings toNativePnoSettings() { + android.net.wifi.nl80211.PnoSettings nativePnoSettings = + new android.net.wifi.nl80211.PnoSettings(); + nativePnoSettings.setIntervalMillis(periodInMs); + nativePnoSettings.setMin2gRssiDbm(min24GHzRssi); + nativePnoSettings.setMin5gRssiDbm(min5GHzRssi); + nativePnoSettings.setMin6gRssiDbm(min6GHzRssi); + if (SdkLevel.isAtLeastU()) { + nativePnoSettings.setScanIterations(scanIterations); + nativePnoSettings.setScanIntervalMultiplier(scanIntervalMultiplier); + } + + List pnoNetworks = new ArrayList<>(); + if (networkList != null) { + for (PnoNetwork network : networkList) { + android.net.wifi.nl80211.PnoNetwork nativeNetwork = + network.toNativePnoNetwork(); + if (nativeNetwork != null) { + pnoNetworks.add(nativeNetwork); + } + } + } + nativePnoSettings.setPnoNetworks(pnoNetworks); + return nativePnoSettings; + } + } + + public static interface ScanEventHandler { + /** + * Called for each AP as it is found with the entire contents of the beacon/probe response. + * Only called when WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT is specified. + */ + void onFullScanResult(ScanResult fullScanResult, int bucketsScanned); + /** + * Callback on an event during a gscan scan. + * See WifiNative.WIFI_SCAN_* for possible values. + */ + void onScanStatus(int event); + /** + * Called with the current cached scan results when gscan is paused. + */ + void onScanPaused(WifiScanner.ScanData[] data); + /** + * Called with the current cached scan results when gscan is resumed. + */ + void onScanRestarted(); + /** + * Callback to notify when the scan request fails. + * See WifiScanner.REASON_* for possible values. + */ + void onScanRequestFailed(int errorCode); + } + + /** + * Handler to notify the occurrence of various events during PNO scan. + */ + public interface PnoEventHandler { + /** + * Callback to notify when one of the shortlisted networks is found during PNO scan. + * @param results List of Scan results received. + */ + void onPnoNetworkFound(ScanResult[] results); + + /** + * Callback to notify when the PNO scan schedule fails. + */ + void onPnoScanFailed(); + } + + public static final int WIFI_SCAN_RESULTS_AVAILABLE = 0; + public static final int WIFI_SCAN_THRESHOLD_NUM_SCANS = 1; + public static final int WIFI_SCAN_THRESHOLD_PERCENT = 2; + public static final int WIFI_SCAN_FAILED = 3; + + /** + * Starts a background scan. + * Any ongoing scan will be stopped first + * + * @param ifaceName Name of the interface. + * @param settings to control the scan + * @param eventHandler to call with the results + * @return true for success + */ + public boolean startBgScan( + @NonNull String ifaceName, ScanSettings settings, ScanEventHandler eventHandler) { + return mWifiVendorHal.startBgScan(ifaceName, settings, eventHandler); + } + + /** + * Stops any ongoing backgound scan + * @param ifaceName Name of the interface. + */ + public void stopBgScan(@NonNull String ifaceName) { + mWifiVendorHal.stopBgScan(ifaceName); + } + + /** + * Pauses an ongoing backgound scan + * @param ifaceName Name of the interface. + */ + public void pauseBgScan(@NonNull String ifaceName) { + mWifiVendorHal.pauseBgScan(ifaceName); + } + + /** + * Restarts a paused scan + * @param ifaceName Name of the interface. + */ + public void restartBgScan(@NonNull String ifaceName) { + mWifiVendorHal.restartBgScan(ifaceName); + } + + /** + * Gets the latest scan results received. + * @param ifaceName Name of the interface. + */ + public WifiScanner.ScanData[] getBgScanResults(@NonNull String ifaceName) { + return mWifiVendorHal.getBgScanResults(ifaceName); + } + + /** + * Sets whether global location mode is enabled. + */ + public void setLocationModeEnabled(boolean enabled) { + if (!mIsLocationModeEnabled && enabled) { + mLastLocationModeEnabledTimeMs = SystemClock.elapsedRealtime(); + } + Log.d(TAG, "mIsLocationModeEnabled " + enabled + + " mLastLocationModeEnabledTimeMs " + mLastLocationModeEnabledTimeMs); + mIsLocationModeEnabled = enabled; + } + + @NonNull + private ScanResult[] getCachedScanResultsFilteredByLocationModeEnabled( + @NonNull ScanResult[] scanResults) { + List resultList = new ArrayList(); + for (ScanResult scanResult : scanResults) { + if (mIsLocationModeEnabled + && scanResult.timestamp >= mLastLocationModeEnabledTimeMs * 1000) { + resultList.add(scanResult); + } + } + return resultList.toArray(new ScanResult[0]); + } + + /** + * Gets the cached scan data from the given client interface + */ + @Nullable + ScanData getCachedScanResults(String ifaceName) { + ScanData scanData = mWifiVendorHal.getCachedScanData(ifaceName); + if (scanData == null || scanData.getResults() == null) { + return null; + } + ScanResult[] results = getCachedScanResultsFilteredByLocationModeEnabled( + scanData.getResults()); + return new ScanData(0, 0, 0, scanData.getScannedBands(), results); + } + + /** + * Gets the cached scan data from all client interfaces + */ + @NonNull + public ScanData getCachedScanResultsFromAllClientIfaces() { + ScanData consolidatedScanData = new ScanData(); + Set ifaceNames = getClientInterfaceNames(); + for (String ifaceName : ifaceNames) { + ScanData scanData = getCachedScanResults(ifaceName); + if (scanData == null) { + continue; + } + consolidatedScanData.addResults(scanData.getResults()); + } + return consolidatedScanData; + } + + /** + * Gets the latest link layer stats + * @param ifaceName Name of the interface. + */ + public WifiLinkLayerStats getWifiLinkLayerStats(@NonNull String ifaceName) { + WifiLinkLayerStats stats = mWifiVendorHal.getWifiLinkLayerStats(ifaceName); + if (stats != null) { + stats.aggregateLinkLayerStats(); + } + return stats; + } + + /** + * Gets the usable channels + * @param band one of the {@code WifiScanner#WIFI_BAND_*} constants. + * @param mode bitmask of {@code WifiAvailablechannel#OP_MODE_*} constants. + * @param filter bitmask of filters (regulatory, coex, concurrency). + * + * @return list of channels + */ + public List getUsableChannels( + @WifiScanner.WifiBand int band, + @WifiAvailableChannel.OpMode int mode, + @WifiAvailableChannel.Filter int filter) { + return mWifiVendorHal.getUsableChannels(band, mode, filter); + } + /** + * Returns whether the device supports the requested + * {@link HalDeviceManager.HdmIfaceTypeForCreation} combo. + */ + public boolean canDeviceSupportCreateTypeCombo(SparseArray combo) { + synchronized (mLock) { + return mWifiVendorHal.canDeviceSupportCreateTypeCombo(combo); + } + } + + /** + * Returns whether STA + AP concurrency is supported or not. + */ + public boolean isStaApConcurrencySupported() { + synchronized (mLock) { + return mWifiVendorHal.canDeviceSupportCreateTypeCombo( + new SparseArray() {{ + put(HDM_CREATE_IFACE_STA, 1); + put(HDM_CREATE_IFACE_AP, 1); + }}); + } + } + + /** + * Returns whether STA + STA concurrency is supported or not. + */ + public boolean isStaStaConcurrencySupported() { + synchronized (mLock) { + return mWifiVendorHal.canDeviceSupportCreateTypeCombo( + new SparseArray() {{ + put(HDM_CREATE_IFACE_STA, 2); + }}); + } + } + + /** + * Returns whether P2p + STA concurrency is supported or not. + */ + public boolean isP2pStaConcurrencySupported() { + synchronized (mLock) { + return mWifiVendorHal.canDeviceSupportCreateTypeCombo( + new SparseArray() {{ + put(HDM_CREATE_IFACE_STA, 1); + put(HDM_CREATE_IFACE_P2P, 1); + }}); + } + } + + /** + * Returns whether a new AP iface can be created or not. + */ + public boolean isItPossibleToCreateApIface(@NonNull WorkSource requestorWs) { + synchronized (mLock) { + if (!isHalStarted()) { + return canDeviceSupportCreateTypeCombo( + new SparseArray() {{ + put(HDM_CREATE_IFACE_AP, 1); + }}); + } + return mWifiVendorHal.isItPossibleToCreateApIface(requestorWs); + } + } + + /** + * Returns whether a new AP iface can be created or not. + */ + public boolean isItPossibleToCreateBridgedApIface(@NonNull WorkSource requestorWs) { + synchronized (mLock) { + if (!isHalStarted()) { + return canDeviceSupportCreateTypeCombo( + new SparseArray() {{ + put(HDM_CREATE_IFACE_AP_BRIDGE, 1); + }}); + } + return mWifiVendorHal.isItPossibleToCreateBridgedApIface(requestorWs); + } + } + + /** + * Returns whether creating a single AP does not require destroying an existing iface, but + * creating a bridged AP does. + */ + public boolean shouldDowngradeToSingleApForConcurrency(@NonNull WorkSource requestorWs) { + synchronized (mLock) { + if (!mWifiVendorHal.isHalStarted()) { + return false; + } + return !mWifiVendorHal.canDeviceSupportAdditionalIface(HDM_CREATE_IFACE_AP_BRIDGE, + requestorWs) + && mWifiVendorHal.canDeviceSupportAdditionalIface(HDM_CREATE_IFACE_AP, + requestorWs); + } + } + + /** + * Returns whether a new STA iface can be created or not. + */ + public boolean isItPossibleToCreateStaIface(@NonNull WorkSource requestorWs) { + synchronized (mLock) { + if (!isHalStarted()) { + return canDeviceSupportCreateTypeCombo( + new SparseArray() {{ + put(HDM_CREATE_IFACE_STA, 1); + }}); + } + return mWifiVendorHal.isItPossibleToCreateStaIface(requestorWs); + } + } + + /** + * Set primary connection when multiple STA ifaces are active. + * + * @param ifaceName Name of the interface. + * @return true for success + */ + public boolean setMultiStaPrimaryConnection(@NonNull String ifaceName) { + synchronized (mLock) { + return mWifiVendorHal.setMultiStaPrimaryConnection(ifaceName); + } + } + + /** + * Multi STA use case flags. + */ + public static final int DUAL_STA_TRANSIENT_PREFER_PRIMARY = 0; + public static final int DUAL_STA_NON_TRANSIENT_UNBIASED = 1; + + @IntDef({DUAL_STA_TRANSIENT_PREFER_PRIMARY, DUAL_STA_NON_TRANSIENT_UNBIASED}) + @Retention(RetentionPolicy.SOURCE) + public @interface MultiStaUseCase{} + + /** + * Set use-case when multiple STA ifaces are active. + * + * @param useCase one of the use cases. + * @return true for success + */ + public boolean setMultiStaUseCase(@MultiStaUseCase int useCase) { + synchronized (mLock) { + return mWifiVendorHal.setMultiStaUseCase(useCase); + } + } + + /** + * Get the supported features + * + * @param ifaceName Name of the interface. + * @return bitmask defined by WifiManager.WIFI_FEATURE_* + */ + public long getSupportedFeatureSet(String ifaceName) { + synchronized (mLock) { + long featureSet = 0; + // First get the complete feature set stored in config store when supplicant was + // started + featureSet = getCompleteFeatureSetFromConfigStore(); + // Include the feature set saved in interface class. This is to make sure that + // framework is returning the feature set for SoftAp only products and multi-chip + // products. + if (ifaceName != null) { + Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface != null) { + featureSet |= iface.featureSet; + } + } + return featureSet; + } + } + + /** + * Get the supported bands for STA mode. + * @return supported bands + */ + public @WifiScanner.WifiBand int getSupportedBandsForSta(String ifaceName) { + synchronized (mLock) { + if (ifaceName != null) { + Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface != null) { + return iface.bandsSupported; + } + } + return WifiScanner.WIFI_BAND_UNSPECIFIED; + } + } + + /** + * Get the supported features + * + * @param ifaceName Name of the interface. + * @return bitmask defined by WifiManager.WIFI_FEATURE_* + */ + private long getSupportedFeatureSetInternal(@NonNull String ifaceName) { + long featureSet = mSupplicantStaIfaceHal.getAdvancedCapabilities(ifaceName) + | mWifiVendorHal.getSupportedFeatureSet(ifaceName) + | mSupplicantStaIfaceHal.getWpaDriverFeatureSet(ifaceName); + if (SdkLevel.isAtLeastT()) { + if (((featureSet & WifiManager.WIFI_FEATURE_DPP) != 0) + && mContext.getResources().getBoolean(R.bool.config_wifiDppAkmSupported)) { + // Set if DPP is filled by supplicant and DPP AKM is enabled by overlay. + featureSet |= WifiManager.WIFI_FEATURE_DPP_AKM; + Log.v(TAG, ": DPP AKM supported"); + } + } + Bundle twtCapabilities = mWifiVendorHal.getTwtCapabilities(ifaceName); + if (twtCapabilities != null) mCachedTwtCapabilities.put(ifaceName, twtCapabilities); + return featureSet; + } + + private void updateSupportedBandForStaInternal(Iface iface) { + List usableChannelList = + mWifiVendorHal.getUsableChannels(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ, + WifiAvailableChannel.OP_MODE_STA, + WifiAvailableChannel.FILTER_REGULATORY); + int bands = 0; + if (usableChannelList == null) { + // If HAL doesn't support getUsableChannels then check wificond + if (getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ).length > 0) { + bands |= WifiScanner.WIFI_BAND_24_GHZ; + } + if (getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ).length > 0) { + bands |= WifiScanner.WIFI_BAND_5_GHZ; + } + if (getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ).length > 0) { + bands |= WifiScanner.WIFI_BAND_6_GHZ; + } + if (getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ).length > 0) { + bands |= WifiScanner.WIFI_BAND_60_GHZ; + } + } else { + for (int i = 0; i < usableChannelList.size(); i++) { + int frequency = usableChannelList.get(i).getFrequencyMhz(); + if (ScanResult.is24GHz(frequency)) { + bands |= WifiScanner.WIFI_BAND_24_GHZ; + } else if (ScanResult.is5GHz(frequency)) { + bands |= WifiScanner.WIFI_BAND_5_GHZ; + } else if (ScanResult.is6GHz(frequency)) { + bands |= WifiScanner.WIFI_BAND_6_GHZ; + } else if (ScanResult.is60GHz(frequency)) { + bands |= WifiScanner.WIFI_BAND_60_GHZ; + } + } + } + if (mVerboseLoggingEnabled) { + Log.i(TAG, "updateSupportedBandForStaInternal " + iface.name + " : 0x" + + Integer.toHexString(bands)); + } + iface.bandsSupported = bands; + } + + /** + * Class to retrieve connection capability parameters after association + */ + public static class ConnectionCapabilities { + public @WifiAnnotations.WifiStandard int wifiStandard; + public int channelBandwidth; + public int maxNumberTxSpatialStreams; + public int maxNumberRxSpatialStreams; + public boolean is11bMode; + /** Indicates the AP support for TID-to-link mapping negotiation. */ + public boolean apTidToLinkMapNegotiationSupported; + public @NonNull List vendorData; + ConnectionCapabilities() { + wifiStandard = ScanResult.WIFI_STANDARD_UNKNOWN; + channelBandwidth = ScanResult.CHANNEL_WIDTH_20MHZ; + maxNumberTxSpatialStreams = 1; + maxNumberRxSpatialStreams = 1; + is11bMode = false; + vendorData = Collections.emptyList(); + } + } + + /** + * Returns connection capabilities of the current network + * + * @param ifaceName Name of the interface. + * @return connection capabilities of the current network + */ + public ConnectionCapabilities getConnectionCapabilities(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.getConnectionCapabilities(ifaceName); + } + + /** + * Request signal polling to supplicant. + * + * @param ifaceName Name of the interface. + * Returns an array of SignalPollResult objects. + * Returns null on failure. + */ + @Nullable + public WifiSignalPollResults signalPoll(@NonNull String ifaceName) { + if (mMockWifiModem != null + && mMockWifiModem.isMethodConfigured( + MockWifiServiceUtil.MOCK_NL80211_SERVICE, "signalPoll")) { + Log.i(TAG, "signalPoll was called from mock wificond"); + WifiNl80211Manager.SignalPollResult result = + mMockWifiModem.getWifiNl80211Manager().signalPoll(ifaceName); + if (result != null) { + // Convert WifiNl80211Manager#SignalPollResult to WifiSignalPollResults. + // Assume single link and linkId = 0. + WifiSignalPollResults results = new WifiSignalPollResults(); + results.addEntry(0, result.currentRssiDbm, result.txBitrateMbps, + result.rxBitrateMbps, result.associationFrequencyMHz); + return results; + } + } + // Query supplicant. + WifiSignalPollResults results = mSupplicantStaIfaceHal.getSignalPollResults( + ifaceName); + if (results == null) { + // Fallback to WifiCond. + WifiNl80211Manager.SignalPollResult result = mWifiCondManager.signalPoll(ifaceName); + if (result != null) { + // Convert WifiNl80211Manager#SignalPollResult to WifiSignalPollResults. + // Assume single link and linkId = 0. + results = new WifiSignalPollResults(); + results.addEntry(0, result.currentRssiDbm, result.txBitrateMbps, + result.rxBitrateMbps, result.associationFrequencyMHz); + } + } + return results; + } + + /** + * Class to represent a connection MLO Link + */ + public static class ConnectionMloLink { + private final int mLinkId; + private final MacAddress mStaMacAddress; + private final BitSet mTidsUplinkMap; + private final BitSet mTidsDownlinkMap; + private final MacAddress mApMacAddress; + private final int mFrequencyMHz; + + ConnectionMloLink(int id, MacAddress staMacAddress, MacAddress apMacAddress, + byte tidsUplink, byte tidsDownlink, int frequencyMHz) { + mLinkId = id; + mStaMacAddress = staMacAddress; + mApMacAddress = apMacAddress; + mTidsDownlinkMap = BitSet.valueOf(new byte[] { tidsDownlink }); + mTidsUplinkMap = BitSet.valueOf(new byte[] { tidsUplink }); + mFrequencyMHz = frequencyMHz; + }; + + /** + * Check if there is any TID mapped to this link in uplink of downlink direction. + * + * @return true if there is any TID mapped to this link, otherwise false. + */ + public boolean isAnyTidMapped() { + if (mTidsDownlinkMap.isEmpty() && mTidsUplinkMap.isEmpty()) { + return false; + } + return true; + } + + /** + * Check if a TID is mapped to this link in uplink direction. + * + * @param tid TID value. + * @return true if the TID is mapped in uplink direction. Otherwise, false. + */ + public boolean isTidMappedToUplink(byte tid) { + if (tid < mTidsUplinkMap.length()) { + return mTidsUplinkMap.get(tid); + } + return false; + } + + /** + * Check if a TID is mapped to this link in downlink direction. Otherwise, false. + * + * @param tid TID value + * @return true if the TID is mapped in downlink direction. Otherwise, false. + */ + public boolean isTidMappedtoDownlink(byte tid) { + if (tid < mTidsDownlinkMap.length()) { + return mTidsDownlinkMap.get(tid); + } + return false; + } + + /** + * Get link id for the link. + * + * @return link id. + */ + public int getLinkId() { + return mLinkId; + } + + /** + * Get link STA MAC address. + * + * @return link mac address. + */ + public MacAddress getStaMacAddress() { + return mStaMacAddress; + } + + /** + * Get link AP MAC address. + * + * @return MAC address. + */ + public MacAddress getApMacAddress() { + return mApMacAddress; + } + + /** + * Get link frequency in MHz. + * + * @return frequency in Mhz. + */ + public int getFrequencyMHz() { + return mFrequencyMHz; + } + } + + /** + * Class to represent the MLO links info for a connection that is collected after association + */ + public static class ConnectionMloLinksInfo { + public ConnectionMloLink[] links; + public MacAddress apMldMacAddress; + public int apMloLinkId; + ConnectionMloLinksInfo() { + // Nothing for now + } + } + + /** + * Returns connection MLO Links Info. + * + * @param ifaceName Name of the interface. + * @return connection MLO Links Info + */ + public ConnectionMloLinksInfo getConnectionMloLinksInfo(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.getConnectionMloLinksInfo(ifaceName); + } + + /** + * Get the APF (Android Packet Filter) capabilities of the device + * @param ifaceName Name of the interface. + */ + public ApfCapabilities getApfCapabilities(@NonNull String ifaceName) { + return mWifiVendorHal.getApfCapabilities(ifaceName); + } + + /** + * Installs an APF program on this iface, replacing any existing program. + * + * @param ifaceName Name of the interface + * @param filter is the android packet filter program + * @return true for success + */ + public boolean installPacketFilter(@NonNull String ifaceName, byte[] filter) { + return mWifiVendorHal.installPacketFilter(ifaceName, filter); + } + + /** + * Reads the APF program and data buffer for this iface. + * + * @param ifaceName Name of the interface + * @return the buffer returned by the driver, or null in case of an error + */ + public byte[] readPacketFilter(@NonNull String ifaceName) { + return mWifiVendorHal.readPacketFilter(ifaceName); + } + + /** + * Set country code for this AP iface. + * @param ifaceName Name of the AP interface. + * @param countryCode - two-letter country code (as ISO 3166) + * @return true for success + */ + public boolean setApCountryCode(@NonNull String ifaceName, String countryCode) { + if (mWifiVendorHal.setApCountryCode(ifaceName, countryCode)) { + if (mCountryCodeChangeListener != null) { + mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode); + } + return true; + } + return false; + } + + /** + * Set country code for this chip + * @param countryCode - two-letter country code (as ISO 3166) + * @return true for success + */ + public boolean setChipCountryCode(String countryCode) { + if (mWifiVendorHal.setChipCountryCode(countryCode)) { + if (mCountryCodeChangeListener != null) { + mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode); + } + return true; + } + return false; + } + + //--------------------------------------------------------------------------------- + /* Wifi Logger commands/events */ + public static interface WifiLoggerEventHandler { + void onRingBufferData(RingBufferStatus status, byte[] buffer); + void onWifiAlert(int errorCode, byte[] buffer); + } + + /** + * Registers the logger callback and enables alerts. + * Ring buffer data collection is only triggered when |startLoggingRingBuffer| is invoked. + * + * @param handler Callback to be invoked. + * @return true on success, false otherwise. + */ + public boolean setLoggingEventHandler(WifiLoggerEventHandler handler) { + return mWifiVendorHal.setLoggingEventHandler(handler); + } + + /** + * Control debug data collection + * + * @param verboseLevel 0 to 3, inclusive. 0 stops logging. + * @param flags Ignored. + * @param maxInterval Maximum interval between reports; ignore if 0. + * @param minDataSize Minimum data size in buffer for report; ignore if 0. + * @param ringName Name of the ring for which data collection is to start. + * @return true for success, false otherwise. + */ + public boolean startLoggingRingBuffer(int verboseLevel, int flags, int maxInterval, + int minDataSize, String ringName){ + return mWifiVendorHal.startLoggingRingBuffer( + verboseLevel, flags, maxInterval, minDataSize, ringName); + } + + /** + * Logger features exposed. + * This is a no-op now, will always return -1. + * + * @return true on success, false otherwise. + */ + public int getSupportedLoggerFeatureSet() { + return mWifiVendorHal.getSupportedLoggerFeatureSet(); + } + + /** + * Stops all logging and resets the logger callback. + * This stops both the alerts and ring buffer data collection. + * @return true on success, false otherwise. + */ + public boolean resetLogHandler() { + return mWifiVendorHal.resetLogHandler(); + } + + /** + * Vendor-provided wifi driver version string + * + * @return String returned from the HAL. + */ + public String getDriverVersion() { + return mWifiVendorHal.getDriverVersion(); + } + + /** + * Vendor-provided wifi firmware version string + * + * @return String returned from the HAL. + */ + public String getFirmwareVersion() { + return mWifiVendorHal.getFirmwareVersion(); + } + + public static class RingBufferStatus{ + public String name; + public int flag; + public int ringBufferId; + public int ringBufferByteSize; + public int verboseLevel; + int writtenBytes; + int readBytes; + int writtenRecords; + + // Bit masks for interpreting |flag| + public static final int HAS_BINARY_ENTRIES = (1 << 0); + public static final int HAS_ASCII_ENTRIES = (1 << 1); + public static final int HAS_PER_PACKET_ENTRIES = (1 << 2); + + @Override + public String toString() { + return "name: " + name + " flag: " + flag + " ringBufferId: " + ringBufferId + + " ringBufferByteSize: " +ringBufferByteSize + " verboseLevel: " +verboseLevel + + " writtenBytes: " + writtenBytes + " readBytes: " + readBytes + + " writtenRecords: " + writtenRecords; + } + } + + /** + * API to get the status of all ring buffers supported by driver + */ + public RingBufferStatus[] getRingBufferStatus() { + return mWifiVendorHal.getRingBufferStatus(); + } + + /** + * Indicates to driver that all the data has to be uploaded urgently + * + * @param ringName Name of the ring buffer requested. + * @return true on success, false otherwise. + */ + public boolean getRingBufferData(String ringName) { + return mWifiVendorHal.getRingBufferData(ringName); + } + + /** + * Request hal to flush ring buffers to files + * + * @return true on success, false otherwise. + */ + public boolean flushRingBufferData() { + return mWifiVendorHal.flushRingBufferData(); + } + + /** + * Request vendor debug info from the firmware + * + * @return Raw data obtained from the HAL. + */ + public byte[] getFwMemoryDump() { + return mWifiVendorHal.getFwMemoryDump(); + } + + /** + * Request vendor debug info from the driver + * + * @return Raw data obtained from the HAL. + */ + public byte[] getDriverStateDump() { + return mWifiVendorHal.getDriverStateDump(); + } + + /** + * Dump information about the internal state + * + * @param pw PrintWriter to write dump to + */ + protected void dump(PrintWriter pw) { + pw.println("Dump of " + TAG); + pw.println("mIsLocationModeEnabled: " + mIsLocationModeEnabled); + pw.println("mLastLocationModeEnabledTimeMs: " + mLastLocationModeEnabledTimeMs); + mHostapdHal.dump(pw); + } + + //--------------------------------------------------------------------------------- + /* Packet fate API */ + + @Immutable + public abstract static class FateReport { + final static int USEC_PER_MSEC = 1000; + // The driver timestamp is a 32-bit counter, in microseconds. This field holds the + // maximal value of a driver timestamp in milliseconds. + final static int MAX_DRIVER_TIMESTAMP_MSEC = (int) (0xffffffffL / 1000); + final static SimpleDateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); + + public final byte mFate; + public final long mDriverTimestampUSec; + public final byte mFrameType; + public final byte[] mFrameBytes; + public final long mEstimatedWallclockMSec; + + FateReport(byte fate, long driverTimestampUSec, byte frameType, byte[] frameBytes) { + mFate = fate; + mDriverTimestampUSec = driverTimestampUSec; + mEstimatedWallclockMSec = + convertDriverTimestampUSecToWallclockMSec(mDriverTimestampUSec); + mFrameType = frameType; + mFrameBytes = frameBytes; + } + + public String toTableRowString() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + FrameParser parser = new FrameParser(mFrameType, mFrameBytes); + dateFormatter.setTimeZone(TimeZone.getDefault()); + pw.format("%-15s %12s %-9s %-32s %-12s %-23s %s\n", + mDriverTimestampUSec, + dateFormatter.format(new Date(mEstimatedWallclockMSec)), + directionToString(), fateToString(), parser.mMostSpecificProtocolString, + parser.mTypeString, parser.mResultString); + return sw.toString(); + } + + public String toVerboseStringWithPiiAllowed() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + FrameParser parser = new FrameParser(mFrameType, mFrameBytes); + pw.format("Frame direction: %s\n", directionToString()); + pw.format("Frame timestamp: %d\n", mDriverTimestampUSec); + pw.format("Frame fate: %s\n", fateToString()); + pw.format("Frame type: %s\n", frameTypeToString(mFrameType)); + pw.format("Frame protocol: %s\n", parser.mMostSpecificProtocolString); + pw.format("Frame protocol type: %s\n", parser.mTypeString); + pw.format("Frame length: %d\n", mFrameBytes.length); + pw.append("Frame bytes"); + pw.append(HexDump.dumpHexString(mFrameBytes)); // potentially contains PII + pw.append("\n"); + return sw.toString(); + } + + /* Returns a header to match the output of toTableRowString(). */ + public static String getTableHeader() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.format("\n%-15s %-12s %-9s %-32s %-12s %-23s %s\n", + "Time usec", "Walltime", "Direction", "Fate", "Protocol", "Type", "Result"); + pw.format("%-15s %-12s %-9s %-32s %-12s %-23s %s\n", + "---------", "--------", "---------", "----", "--------", "----", "------"); + return sw.toString(); + } + + protected abstract String directionToString(); + + protected abstract String fateToString(); + + private static String frameTypeToString(byte frameType) { + switch (frameType) { + case WifiLoggerHal.FRAME_TYPE_UNKNOWN: + return "unknown"; + case WifiLoggerHal.FRAME_TYPE_ETHERNET_II: + return "data"; + case WifiLoggerHal.FRAME_TYPE_80211_MGMT: + return "802.11 management"; + default: + return Byte.toString(frameType); + } + } + + /** + * Converts a driver timestamp to a wallclock time, based on the current + * BOOTTIME to wallclock mapping. The driver timestamp is a 32-bit counter of + * microseconds, with the same base as BOOTTIME. + */ + private static long convertDriverTimestampUSecToWallclockMSec(long driverTimestampUSec) { + final long wallclockMillisNow = System.currentTimeMillis(); + final long boottimeMillisNow = SystemClock.elapsedRealtime(); + final long driverTimestampMillis = driverTimestampUSec / USEC_PER_MSEC; + + long boottimeTimestampMillis = boottimeMillisNow % MAX_DRIVER_TIMESTAMP_MSEC; + if (boottimeTimestampMillis < driverTimestampMillis) { + // The 32-bit microsecond count has wrapped between the time that the driver + // recorded the packet, and the call to this function. Adjust the BOOTTIME + // timestamp, to compensate. + // + // Note that overflow is not a concern here, since the result is less than + // 2 * MAX_DRIVER_TIMESTAMP_MSEC. (Given the modulus operation above, + // boottimeTimestampMillis must be less than MAX_DRIVER_TIMESTAMP_MSEC.) And, since + // MAX_DRIVER_TIMESTAMP_MSEC is an int, 2 * MAX_DRIVER_TIMESTAMP_MSEC must fit + // within a long. + boottimeTimestampMillis += MAX_DRIVER_TIMESTAMP_MSEC; + } + + final long millisSincePacketTimestamp = boottimeTimestampMillis - driverTimestampMillis; + return wallclockMillisNow - millisSincePacketTimestamp; + } + } + + /** + * Represents the fate information for one outbound packet. + */ + @Immutable + public static final class TxFateReport extends FateReport { + public TxFateReport(byte fate, long driverTimestampUSec, byte frameType, + byte[] frameBytes) { + super(fate, driverTimestampUSec, frameType, frameBytes); + } + + @Override + protected String directionToString() { + return "TX"; + } + + @Override + protected String fateToString() { + switch (mFate) { + case WifiLoggerHal.TX_PKT_FATE_ACKED: + return "acked"; + case WifiLoggerHal.TX_PKT_FATE_SENT: + return "sent"; + case WifiLoggerHal.TX_PKT_FATE_FW_QUEUED: + return "firmware queued"; + case WifiLoggerHal.TX_PKT_FATE_FW_DROP_INVALID: + return "firmware dropped (invalid frame)"; + case WifiLoggerHal.TX_PKT_FATE_FW_DROP_NOBUFS: + return "firmware dropped (no bufs)"; + case WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER: + return "firmware dropped (other)"; + case WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED: + return "driver queued"; + case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_INVALID: + return "driver dropped (invalid frame)"; + case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_NOBUFS: + return "driver dropped (no bufs)"; + case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_OTHER: + return "driver dropped (other)"; + default: + return Byte.toString(mFate); + } + } + } + + /** + * Represents the fate information for one inbound packet. + */ + @Immutable + public static final class RxFateReport extends FateReport { + public RxFateReport(byte fate, long driverTimestampUSec, byte frameType, + byte[] frameBytes) { + super(fate, driverTimestampUSec, frameType, frameBytes); + } + + @Override + protected String directionToString() { + return "RX"; + } + + @Override + protected String fateToString() { + switch (mFate) { + case WifiLoggerHal.RX_PKT_FATE_SUCCESS: + return "success"; + case WifiLoggerHal.RX_PKT_FATE_FW_QUEUED: + return "firmware queued"; + case WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER: + return "firmware dropped (filter)"; + case WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID: + return "firmware dropped (invalid frame)"; + case WifiLoggerHal.RX_PKT_FATE_FW_DROP_NOBUFS: + return "firmware dropped (no bufs)"; + case WifiLoggerHal.RX_PKT_FATE_FW_DROP_OTHER: + return "firmware dropped (other)"; + case WifiLoggerHal.RX_PKT_FATE_DRV_QUEUED: + return "driver queued"; + case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_FILTER: + return "driver dropped (filter)"; + case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_INVALID: + return "driver dropped (invalid frame)"; + case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_NOBUFS: + return "driver dropped (no bufs)"; + case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_OTHER: + return "driver dropped (other)"; + default: + return Byte.toString(mFate); + } + } + } + + /** + * Ask the HAL to enable packet fate monitoring. Fails unless HAL is started. + * + * @param ifaceName Name of the interface. + * @return true for success, false otherwise. + */ + public boolean startPktFateMonitoring(@NonNull String ifaceName) { + return mWifiVendorHal.startPktFateMonitoring(ifaceName); + } + + /** + * Fetch the most recent TX packet fates from the HAL. Fails unless HAL is started. + * + * @param ifaceName Name of the interface. + * @return TxFateReport list on success, empty list on failure. Never returns null. + */ + @NonNull + public List getTxPktFates(@NonNull String ifaceName) { + return mWifiVendorHal.getTxPktFates(ifaceName); + } + + /** + * Fetch the most recent RX packet fates from the HAL. Fails unless HAL is started. + * @param ifaceName Name of the interface. + * @return RxFateReport list on success, empty list on failure. Never returns null. + */ + @NonNull + public List getRxPktFates(@NonNull String ifaceName) { + return mWifiVendorHal.getRxPktFates(ifaceName); + } + + /** + * Get the tx packet counts for the interface. + * + * @param ifaceName Name of the interface. + * @return tx packet counts + */ + public long getTxPackets(@NonNull String ifaceName) { + return TrafficStats.getTxPackets(ifaceName); + } + + /** + * Get the rx packet counts for the interface. + * + * @param ifaceName Name of the interface + * @return rx packet counts + */ + public long getRxPackets(@NonNull String ifaceName) { + return TrafficStats.getRxPackets(ifaceName); + } + + /** + * Start sending the specified keep alive packets periodically. + * + * @param ifaceName Name of the interface. + * @param slot Integer used to identify each request. + * @param dstMac Destination MAC Address + * @param packet Raw packet contents to send. + * @param protocol The ethernet protocol type + * @param period Period to use for sending these packets. + * @return 0 for success, -1 for error + */ + public int startSendingOffloadedPacket(@NonNull String ifaceName, int slot, + byte[] dstMac, byte[] packet, int protocol, int period) { + byte[] srcMac = NativeUtil.macAddressToByteArray(getMacAddress(ifaceName)); + return mWifiVendorHal.startSendingOffloadedPacket( + ifaceName, slot, srcMac, dstMac, packet, protocol, period); + } + + /** + * Stop sending the specified keep alive packets. + * + * @param ifaceName Name of the interface. + * @param slot id - same as startSendingOffloadedPacket call. + * @return 0 for success, -1 for error + */ + public int stopSendingOffloadedPacket(@NonNull String ifaceName, int slot) { + return mWifiVendorHal.stopSendingOffloadedPacket(ifaceName, slot); + } + + public static interface WifiRssiEventHandler { + void onRssiThresholdBreached(byte curRssi); + } + + /** + * Start RSSI monitoring on the currently connected access point. + * + * @param ifaceName Name of the interface. + * @param maxRssi Maximum RSSI threshold. + * @param minRssi Minimum RSSI threshold. + * @param rssiEventHandler Called when RSSI goes above maxRssi or below minRssi + * @return 0 for success, -1 for failure + */ + public int startRssiMonitoring( + @NonNull String ifaceName, byte maxRssi, byte minRssi, + WifiRssiEventHandler rssiEventHandler) { + return mWifiVendorHal.startRssiMonitoring( + ifaceName, maxRssi, minRssi, rssiEventHandler); + } + + /** + * Stop RSSI monitoring on the currently connected access point. + * + * @param ifaceName Name of the interface. + * @return 0 for success, -1 for failure + */ + public int stopRssiMonitoring(@NonNull String ifaceName) { + return mWifiVendorHal.stopRssiMonitoring(ifaceName); + } + + /** + * Fetch the host wakeup reasons stats from wlan driver. + * + * @return the |WlanWakeReasonAndCounts| object retrieved from the wlan driver. + */ + public WlanWakeReasonAndCounts getWlanWakeReasonCount() { + return mWifiVendorHal.getWlanWakeReasonCount(); + } + + /** + * Enable/Disable Neighbour discovery offload functionality in the firmware. + * + * @param ifaceName Name of the interface. + * @param enabled true to enable, false to disable. + * @return true for success, false otherwise. + */ + public boolean configureNeighborDiscoveryOffload(@NonNull String ifaceName, boolean enabled) { + return mWifiVendorHal.configureNeighborDiscoveryOffload(ifaceName, enabled); + } + + // Firmware roaming control. + + /** + * Class to retrieve firmware roaming capability parameters. + */ + public static class RoamingCapabilities { + public int maxBlocklistSize; + public int maxAllowlistSize; + } + + /** + * Query the firmware roaming capabilities. + * @param ifaceName Name of the interface. + * @return capabilities object on success, null otherwise. + */ + @Nullable + public RoamingCapabilities getRoamingCapabilities(@NonNull String ifaceName) { + return mWifiVendorHal.getRoamingCapabilities(ifaceName); + } + + /** + * Macros for controlling firmware roaming. + */ + public static final int DISABLE_FIRMWARE_ROAMING = 0; + public static final int ENABLE_FIRMWARE_ROAMING = 1; + + @IntDef({ENABLE_FIRMWARE_ROAMING, DISABLE_FIRMWARE_ROAMING}) + @Retention(RetentionPolicy.SOURCE) + public @interface RoamingEnableState {} + + /** + * Indicates success for enableFirmwareRoaming + */ + public static final int SET_FIRMWARE_ROAMING_SUCCESS = 0; + + /** + * Indicates failure for enableFirmwareRoaming + */ + public static final int SET_FIRMWARE_ROAMING_FAILURE = 1; + + /** + * Indicates temporary failure for enableFirmwareRoaming - try again later + */ + public static final int SET_FIRMWARE_ROAMING_BUSY = 2; + + @IntDef({SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE, SET_FIRMWARE_ROAMING_BUSY}) + @Retention(RetentionPolicy.SOURCE) + public @interface RoamingEnableStatus {} + + /** + * Enable/disable firmware roaming. + * + * @param ifaceName Name of the interface. + * @return SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE, + * or SET_FIRMWARE_ROAMING_BUSY + */ + public @RoamingEnableStatus int enableFirmwareRoaming(@NonNull String ifaceName, + @RoamingEnableState int state) { + return mWifiVendorHal.enableFirmwareRoaming(ifaceName, state); + } + + /** + * Class for specifying the roaming configurations. + */ + public static class RoamingConfig { + public ArrayList blocklistBssids; + public ArrayList allowlistSsids; + } + + /** + * Set firmware roaming configurations. + * @param ifaceName Name of the interface. + */ + public boolean configureRoaming(@NonNull String ifaceName, RoamingConfig config) { + return mWifiVendorHal.configureRoaming(ifaceName, config); + } + + /** + * Reset firmware roaming configuration. + * @param ifaceName Name of the interface. + */ + public boolean resetRoamingConfiguration(@NonNull String ifaceName) { + // Pass in an empty RoamingConfig object which translates to zero size + // blacklist and whitelist to reset the firmware roaming configuration. + return mWifiVendorHal.configureRoaming(ifaceName, new RoamingConfig()); + } + + /** + * Select one of the pre-configured transmit power level scenarios or reset it back to normal. + * Primarily used for meeting SAR requirements. + * + * @param sarInfo The collection of inputs used to select the SAR scenario. + * @return true for success; false for failure or if the HAL version does not support this API. + */ + public boolean selectTxPowerScenario(SarInfo sarInfo) { + return mWifiVendorHal.selectTxPowerScenario(sarInfo); + } + + /** + * Set MBO cellular data status + * + * @param ifaceName Name of the interface. + * @param available cellular data status, + * true means cellular data available, false otherwise. + */ + public void setMboCellularDataStatus(@NonNull String ifaceName, boolean available) { + mSupplicantStaIfaceHal.setMboCellularDataStatus(ifaceName, available); + } + + /** + * Query of support of Wi-Fi standard + * + * @param ifaceName name of the interface to check support on + * @param standard the wifi standard to check on + * @return true if the wifi standard is supported on this interface, false otherwise. + */ + public boolean isWifiStandardSupported(@NonNull String ifaceName, + @WifiAnnotations.WifiStandard int standard) { + synchronized (mLock) { + Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface == null || iface.phyCapabilities == null) { + return false; + } + return iface.phyCapabilities.isWifiStandardSupported(standard); + } + } + + /** + * Get the Wiphy capabilities of a device for a given interface + * If the interface is not associated with one, + * it will be read from the device through wificond + * + * @param ifaceName name of the interface + * @return the device capabilities for this interface + */ + public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) { + synchronized (mLock) { + Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Failed to get device capabilities, interface not found: " + ifaceName); + return null; + } + if (iface.phyCapabilities == null) { + iface.phyCapabilities = mWifiCondManager.getDeviceWiphyCapabilities(ifaceName); + } + if (iface.phyCapabilities != null + && iface.phyCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11BE) + != mWifiInjector.getSettingsConfigStore() + .get(WifiSettingsConfigStore.WIFI_WIPHY_11BE_SUPPORTED)) { + mWifiInjector.getSettingsConfigStore().put( + WifiSettingsConfigStore.WIFI_WIPHY_11BE_SUPPORTED, + iface.phyCapabilities.isWifiStandardSupported( + ScanResult.WIFI_STANDARD_11BE)); + } + return iface.phyCapabilities; + } + } + + /** + * Set the Wiphy capabilities of a device for a given interface + * + * @param ifaceName name of the interface + * @param capabilities the wiphy capabilities to set for this interface + */ + public void setDeviceWiphyCapabilities(@NonNull String ifaceName, + DeviceWiphyCapabilities capabilities) { + synchronized (mLock) { + Iface iface = mIfaceMgr.getIface(ifaceName); + if (iface == null) { + Log.e(TAG, "Failed to set device capabilities, interface not found: " + ifaceName); + return; + } + iface.phyCapabilities = capabilities; + } + } + + /** + * Notify scan mode state to driver to save power in scan-only mode. + * + * @param ifaceName Name of the interface. + * @param enable whether is in scan-only mode + * @return true for success + */ + public boolean setScanMode(String ifaceName, boolean enable) { + return mWifiVendorHal.setScanMode(ifaceName, enable); + } + + /** updates linked networks of the |networkId| in supplicant if it's the current network, + * if the current configured network matches |networkId|. + * + * @param ifaceName Name of the interface. + * @param networkId network id of the network to be updated from supplicant. + * @param linkedNetworks Map of config profile key and config for linking. + */ + public boolean updateLinkedNetworks(@NonNull String ifaceName, int networkId, + Map linkedNetworks) { + return mSupplicantStaIfaceHal.updateLinkedNetworks(ifaceName, networkId, linkedNetworks); + } + + /** + * Start Subsystem Restart + * @return true on success + */ + public boolean startSubsystemRestart() { + return mWifiVendorHal.startSubsystemRestart(); + } + + /** + * Register the provided listener for country code event. + * + * @param listener listener for country code changed events. + */ + public void registerCountryCodeEventListener(WifiCountryCode.ChangeListener listener) { + registerWificondListenerIfNecessary(); + if (mCountryCodeChangeListener != null) { + mCountryCodeChangeListener.setChangeListener(listener); + } + } + + /** + * Gets the security params of the current network associated with this interface + * + * @param ifaceName Name of the interface + * @return Security params of the current network associated with the interface + */ + public SecurityParams getCurrentNetworkSecurityParams(@NonNull String ifaceName) { + return mSupplicantStaIfaceHal.getCurrentNetworkSecurityParams(ifaceName); + } + + /** + * Check if the network-centric QoS policy feature was successfully enabled. + */ + public boolean isQosPolicyFeatureEnabled() { + return mQosPolicyFeatureEnabled; + } + + /** + * Sends a QoS policy response. + * + * @param ifaceName Name of the interface. + * @param qosPolicyRequestId Dialog token to identify the request. + * @param morePolicies Flag to indicate more QoS policies can be accommodated. + * @param qosPolicyStatusList List of framework QosPolicyStatus objects. + * @return true if response is sent successfully, false otherwise. + */ + public boolean sendQosPolicyResponse(String ifaceName, int qosPolicyRequestId, + boolean morePolicies, @NonNull List qosPolicyStatusList) { + if (!mQosPolicyFeatureEnabled) { + Log.e(TAG, "Unable to send QoS policy response, feature is not enabled"); + return false; + } + return mSupplicantStaIfaceHal.sendQosPolicyResponse(ifaceName, qosPolicyRequestId, + morePolicies, qosPolicyStatusList); + } + + /** + * Indicates the removal of all active QoS policies configured by the AP. + * + * @param ifaceName Name of the interface. + */ + public boolean removeAllQosPolicies(String ifaceName) { + if (!mQosPolicyFeatureEnabled) { + Log.e(TAG, "Unable to remove all QoS policies, feature is not enabled"); + return false; + } + return mSupplicantStaIfaceHal.removeAllQosPolicies(ifaceName); + } + + /** + * Send a set of QoS SCS policy add requests to the AP. + * + * Immediate response will indicate which policies were sent to the AP, and which were + * rejected immediately by the supplicant. If any requests were sent to the AP, the AP's + * response will arrive later in the onQosPolicyResponseForScs callback. + * + * @param ifaceName Name of the interface. + * @param policies List of policies that the caller is requesting to add. + * @return List of responses for each policy in the request, or null if an error occurred. + * Status code will be one of + * {@link SupplicantStaIfaceHal.QosPolicyScsRequestStatusCode}. + */ + List addQosPolicyRequestForScs( + @NonNull String ifaceName, @NonNull List policies) { + return mSupplicantStaIfaceHal.addQosPolicyRequestForScs(ifaceName, policies); + } + + /** + * Request the removal of specific QoS policies for SCS. + * + * Immediate response will indicate which policies were sent to the AP, and which were + * rejected immediately by the supplicant. If any requests were sent to the AP, the AP's + * response will arrive later in the onQosPolicyResponseForScs callback. + * + * @param ifaceName Name of the interface. + * @param policyIds List of policy IDs for policies that should be removed. + * @return List of responses for each policy in the request, or null if an error occurred. + * Status code will be one of + * {@link SupplicantStaIfaceHal.QosPolicyScsRequestStatusCode}. + */ + List removeQosPolicyForScs( + @NonNull String ifaceName, @NonNull List policyIds) { + return mSupplicantStaIfaceHal.removeQosPolicyForScs(ifaceName, policyIds); + } + + /** + * Register a callback to receive notifications for QoS SCS transactions. + * Callback should only be registered once. + * + * @param callback {@link SupplicantStaIfaceHal.QosScsResponseCallback} to register. + */ + public void registerQosScsResponseCallback( + @NonNull SupplicantStaIfaceHal.QosScsResponseCallback callback) { + mSupplicantStaIfaceHal.registerQosScsResponseCallback(callback); + } + + /** + * Generate DPP credential for network access + * + * @param ifaceName Name of the interface. + * @param ssid ssid of the network + * @param privEcKey Private EC Key for DPP Configurator + * Returns true when operation is successful. On error, false is returned. + */ + public boolean generateSelfDppConfiguration(@NonNull String ifaceName, @NonNull String ssid, + byte[] privEcKey) { + return mSupplicantStaIfaceHal.generateSelfDppConfiguration(ifaceName, ssid, privEcKey); + } + + /** + * This set anonymous identity to supplicant. + * + * @param ifaceName Name of the interface. + * @param anonymousIdentity the anonymouns identity. + * @param updateToNativeService write the data to the native service. + * @return true if succeeds, false otherwise. + */ + public boolean setEapAnonymousIdentity(@NonNull String ifaceName, String anonymousIdentity, + boolean updateToNativeService) { + if (null == anonymousIdentity) { + Log.e(TAG, "Cannot set null anonymous identity."); + return false; + } + return mSupplicantStaIfaceHal.setEapAnonymousIdentity(ifaceName, anonymousIdentity, + updateToNativeService); + } + + /** + * Notify wificond daemon of country code have changed. + */ + public void countryCodeChanged(String countryCode) { + if (SdkLevel.isAtLeastT()) { + try { + mWifiCondManager.notifyCountryCodeChanged(countryCode); + } catch (RuntimeException re) { + Log.e(TAG, "Fail to notify wificond country code changed to " + countryCode + + "because exception happened:" + re); + } + } + } + + /** + * Return the maximum number of concurrent TDLS sessions supported by the device. + * @return -1 if the information is not available on the device + */ + public int getMaxSupportedConcurrentTdlsSessions(@NonNull String ifaceName) { + return mWifiVendorHal.getMaxSupportedConcurrentTdlsSessions(ifaceName); + } + + /** + * Save the complete list of features retrieved from WiFi HAL and Supplicant HAL in + * config store. + */ + private void saveCompleteFeatureSetInConfigStoreIfNecessary(long featureSet) { + long cachedFeatureSet = getCompleteFeatureSetFromConfigStore(); + if (cachedFeatureSet != featureSet) { + mCachedFeatureSet = featureSet; + mWifiInjector.getSettingsConfigStore() + .put(WIFI_NATIVE_SUPPORTED_FEATURES, mCachedFeatureSet); + Log.i(TAG, "Supported features is updated in config store: " + mCachedFeatureSet); + } + } + + /** + * Get the feature set from cache/config store + */ + private long getCompleteFeatureSetFromConfigStore() { + if (mCachedFeatureSet == 0) { + mCachedFeatureSet = mWifiInjector.getSettingsConfigStore() + .get(WIFI_NATIVE_SUPPORTED_FEATURES); + } + return mCachedFeatureSet; + } + + /** + * Returns whether or not the hostapd HAL supports reporting single instance died event. + */ + public boolean isSoftApInstanceDiedHandlerSupported() { + return mHostapdHal.isSoftApInstanceDiedHandlerSupported(); + } + + /** Checks if there are any STA (for connectivity) iface active. */ + @VisibleForTesting + boolean hasAnyStaIfaceForConnectivity() { + return mIfaceMgr.hasAnyStaIfaceForConnectivity(); + } + + /** Checks if there are any STA (for scan) iface active. */ + @VisibleForTesting + boolean hasAnyStaIfaceForScan() { + return mIfaceMgr.hasAnyStaIfaceForScan(); + } + + /** Checks if there are any AP iface active. */ + @VisibleForTesting + boolean hasAnyApIface() { + return mIfaceMgr.hasAnyApIface(); + } + + /** Checks if there are any iface active. */ + @VisibleForTesting + boolean hasAnyIface() { + return mIfaceMgr.hasAnyIface(); + } + + /** Checks if there are any P2P iface active. */ + @VisibleForTesting + boolean hasAnyP2pIface() { + return mIfaceMgr.hasAnyP2pIface(); + } + + /** + * Sets or clean mock wifi service + * + * @param serviceName the service name of mock wifi service. When service name is empty, the + * framework will clean mock wifi service. + */ + public void setMockWifiService(String serviceName) { + Log.d(TAG, "set MockWifiModemService to " + serviceName); + if (TextUtils.isEmpty(serviceName)) { + mMockWifiModem.unbindMockModemService(); + mMockWifiModem = null; + mWifiInjector.setMockWifiServiceUtil(null); + return; + } + mMockWifiModem = new MockWifiServiceUtil(mContext, serviceName, mWifiMonitor); + mWifiInjector.setMockWifiServiceUtil(mMockWifiModem); + if (mMockWifiModem == null) { + Log.e(TAG, "MockWifiServiceUtil creation failed."); + return; + } + + // mock wifi modem service is set, try to bind all supported mock HAL services + mMockWifiModem.bindAllMockModemService(); + for (int service = MockWifiServiceUtil.MIN_SERVICE_IDX; + service < MockWifiServiceUtil.NUM_SERVICES; service++) { + int retryCount = 0; + IBinder binder; + do { + binder = mMockWifiModem.getServiceBinder(service); + retryCount++; + if (binder == null) { + Log.d(TAG, "Retry(" + retryCount + ") for " + + mMockWifiModem.getModuleName(service)); + try { + Thread.sleep(MockWifiServiceUtil.BINDER_RETRY_MILLIS); + } catch (InterruptedException e) { + } + } + } while ((binder == null) && (retryCount < MockWifiServiceUtil.BINDER_MAX_RETRY)); + + if (binder == null) { + Log.e(TAG, "Mock " + mMockWifiModem.getModuleName(service) + " bind fail"); + } + } + } + + /** + * Returns mock wifi service name. + */ + public String getMockWifiServiceName() { + String serviceName = mMockWifiModem != null ? mMockWifiModem.getServiceName() : null; + Log.d(TAG, "getMockWifiServiceName - service name is " + serviceName); + return serviceName; + } + + /** + * Sets mocked methods which like to be called. + * + * @param methods the methods string with formats HAL name - method name, ... + */ + public boolean setMockWifiMethods(String methods) { + if (mMockWifiModem == null || methods == null) { + return false; + } + return mMockWifiModem.setMockedMethods(methods); + } + + /** + * Set maximum acceptable DTIM multiplier to hardware driver. Any multiplier larger than the + * maximum value must not be accepted, it will cause packet loss higher than what the system + * can accept, which will cause unexpected behavior for apps, and may interrupt the network + * connection. + * + * @param ifaceName Name of the interface. + * @param multiplier integer maximum DTIM multiplier value to set. + * @return true for success + */ + public boolean setDtimMultiplier(String ifaceName, int multiplier) { + return mWifiVendorHal.setDtimMultiplier(ifaceName, multiplier); + } + + /** + * Set Multi-Link Operation mode. + * + * @param mode Multi-Link Operation mode {@link android.net.wifi.WifiManager.MloMode}. + * @return {@link WifiStatusCode#SUCCESS} if success, otherwise error code. + */ + public @WifiStatusCode int setMloMode(@WifiManager.MloMode int mode) { + @WifiStatusCode int errorCode = mWifiVendorHal.setMloMode(mode); + // If set is success, cache it. + if (errorCode == WifiStatusCode.SUCCESS) mCachedMloMode = mode; + return errorCode; + } + + /** + * Get Multi-Link Operation mode. + * + * @return Current Multi-Link Operation mode {@link android.net.wifi.WifiManager.MloMode}. + */ + public @WifiManager.MloMode int getMloMode() { + return mCachedMloMode; + } + + /** + * Get the maximum number of links supported by the chip for MLO association. + * + * e.g. if the chip supports eMLSR (Enhanced Multi-Link Single Radio) and STR (Simultaneous + * Transmit and Receive) with following capabilities, + * - Maximum MLO association link count = 3 + * - Maximum MLO STR link count = 2 See {@link WifiNative#getMaxMloStrLinkCount(String)} + * One of the possible configuration is - STR (2.4 , eMLSR(5, 6)), provided the radio + * combination of the chip supports it. + * + * Note: This is an input to MLO aware network scoring logic to predict maximum multi-link + * throughput. + * + * @param ifaceName Name of the interface. + * @return maximum number of association links or -1 if error or not available. + */ + public int getMaxMloAssociationLinkCount(@NonNull String ifaceName) { + return mWifiVendorHal.getMaxMloAssociationLinkCount(ifaceName); + } + + /** + * Get the maximum number of STR links used in Multi-Link Operation. The maximum number of STR + * links used for MLO can be different from the number of radios supported by the chip. + * + * e.g. if the chip supports eMLSR (Enhanced Multi-Link Single Radio) and STR (Simultaneous + * Transmit and Receive) with following capabilities, + * - Maximum MLO association link count = 3 + * See {@link WifiNative#getMaxMloAssociationLinkCount(String)} + * - Maximum MLO STR link count = 2 + * One of the possible configuration is - STR (2.4, eMLSR(5, 6)), provided the radio + * combination of the chip supports it. + * + * Note: This is an input to MLO aware network scoring logic to predict maximum multi-link + * throughput. + * + * @param ifaceName Name of the interface. + * @return maximum number of MLO STR links or -1 if error or not available. + */ + public int getMaxMloStrLinkCount(@NonNull String ifaceName) { + return mWifiVendorHal.getMaxMloStrLinkCount(ifaceName); + } + + /** + * Check the given band combination is supported simultaneously by the Wi-Fi chip. + * + * Note: This method is for checking simultaneous band operations and not for multichannel + * concurrent operation (MCC). + * + * @param ifaceName Name of the interface. + * @param bands A list of bands in the combination. See {@link WifiScanner.WifiBand} + * for the band enums. List of bands can be in any order. + * @return true if the provided band combination is supported by the chip, otherwise false. + */ + public boolean isBandCombinationSupported(@NonNull String ifaceName, List bands) { + return mWifiVendorHal.isBandCombinationSupported(ifaceName, bands); + } + + /** + * Get the set of band combinations supported simultaneously by the Wi-Fi Chip. + * + * Note: This method returns simultaneous band operation combination and not multichannel + * concurrent operation (MCC) combination. + * + * @param ifaceName Name of the interface. + * @return An unmodifiable set of supported band combinations. + */ + public Set> getSupportedBandCombinations(@NonNull String ifaceName) { + return mWifiVendorHal.getSupportedBandCombinations(ifaceName); + } + + /** + * Sends the AFC allowed channels and frequencies to the driver. + * + * @param afcChannelAllowance the allowed frequencies and channels received from + * querying the AFC server. + * @return whether the channel allowance was set successfully. + */ + public boolean setAfcChannelAllowance(WifiChip.AfcChannelAllowance afcChannelAllowance) { + return mWifiVendorHal.setAfcChannelAllowance(afcChannelAllowance); + } + + /** + * Enable Mirrored Stream Classification Service (MSCS) and configure using + * the provided configuration values. + * + * @param mscsParams {@link MscsParams} object containing the configuration parameters. + * @param ifaceName Name of the interface. + */ + public void enableMscs(@NonNull MscsParams mscsParams, String ifaceName) { + mSupplicantStaIfaceHal.enableMscs(mscsParams, ifaceName); + } + + /** + * Disable Mirrored Stream Classification Service (MSCS). + * + * @param ifaceName Name of the interface. + */ + public void disableMscs(String ifaceName) { + mSupplicantStaIfaceHal.disableMscs(ifaceName); + } + + /** + * Set the roaming mode value. + * + * @param ifaceName Name of the interface. + * @param roamingMode {@link android.net.wifi.WifiManager.RoamingMode}. + * @return {@link WifiStatusCode#SUCCESS} if success, otherwise error code. + */ + public @WifiStatusCode int setRoamingMode(@NonNull String ifaceName, + @RoamingMode int roamingMode) { + return mWifiVendorHal.setRoamingMode(ifaceName, roamingMode); + } + + /* + * TWT callback events + */ + public interface WifiTwtEvents { + /** + * Called when a TWT operation fails + * + * @param cmdId Unique command id. + * @param twtErrorCode Error code + */ + void onTwtFailure(int cmdId, @TwtSessionCallback.TwtErrorCode int twtErrorCode); + + /** + * Called when {@link #setupTwtSession(int, String, TwtRequest)} succeeds. + * + * @param cmdId Unique command id used in {@link #setupTwtSession(int, String, TwtRequest)} + * @param wakeDurationUs TWT wake duration for the session in microseconds + * @param wakeIntervalUs TWT wake interval for the session in microseconds + * @param linkId Multi link operation link id + * @param sessionId TWT session id + */ + void onTwtSessionCreate(int cmdId, int wakeDurationUs, long wakeIntervalUs, int linkId, + int sessionId); + /** + * Called when TWT session is torn down by {@link #tearDownTwtSession(int, String, int)}. + * Can also be called unsolicitedly by the vendor software with proper reason code. + * + * @param cmdId Unique command id used in {@link #tearDownTwtSession(int, String, int)} + * @param twtSessionId TWT session Id + * @param twtReasonCode Reason code for teardown + */ + void onTwtSessionTeardown(int cmdId, int twtSessionId, + @TwtSessionCallback.TwtReasonCode int twtReasonCode); + + /** + * Called as a response to {@link #getStatsTwtSession(int, String, int)} + * + * @param cmdId Unique command id used in {@link #getStatsTwtSession(int, String, int)} + * @param twtSessionId TWT session Id + * @param twtStats TWT stats object + */ + void onTwtSessionStats(int cmdId, int twtSessionId, Bundle twtStats); + } + + + /** + * Sets up a TWT session for the interface + * + * @param commandId A unique command id to identify this command + * @param interfaceName Interface name + * @param twtRequest TWT request parameters + * @return true if successful, otherwise false + */ + public boolean setupTwtSession(int commandId, String interfaceName, TwtRequest twtRequest) { + return mWifiVendorHal.setupTwtSession(commandId, interfaceName, twtRequest); + } + + /** + * Registers TWT callbacks + * + * @param wifiTwtCallback TWT callbacks + */ + public void registerTwtCallbacks(WifiTwtEvents wifiTwtCallback) { + mWifiVendorHal.registerTwtCallbacks(wifiTwtCallback); + } + + /** + * Teardown the TWT session + * + * @param commandId A unique command id to identify this command + * @param interfaceName Interface name + * @param sessionId TWT session id + * @return true if successful, otherwise false + */ + public boolean tearDownTwtSession(int commandId, String interfaceName, int sessionId) { + return mWifiVendorHal.tearDownTwtSession(commandId, interfaceName, sessionId); + } + + /** + * Gets stats of the TWT session + * + * @param commandId A unique command id to identify this command + * @param interfaceName Interface name + * @param sessionId TWT session id + * @return true if successful, otherwise false + */ + public boolean getStatsTwtSession(int commandId, String interfaceName, int sessionId) { + return mWifiVendorHal.getStatsTwtSession(commandId, interfaceName, sessionId); + } +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkSelector.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkSelector.java new file mode 100644 index 000000000..621d75099 --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkSelector.java @@ -0,0 +1,1769 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE; +import static android.net.wifi.WifiNetworkSelectionConfig.ASSOCIATED_NETWORK_SELECTION_OVERRIDE_DISABLED; +import static android.net.wifi.WifiNetworkSelectionConfig.ASSOCIATED_NETWORK_SELECTION_OVERRIDE_ENABLED; +import static android.net.wifi.WifiNetworkSelectionConfig.ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.admin.DevicePolicyManager; +import android.app.admin.WifiSsidPolicy; +import android.content.Context; +import android.net.MacAddress; +import android.net.wifi.ScanResult; +import android.net.wifi.SecurityParams; +import android.net.wifi.SupplicantState; +import android.net.wifi.WifiAnnotations; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiNetworkSelectionConfig.AssociatedNetworkSelectionOverride; +import android.net.wifi.WifiSsid; +import android.net.wifi.util.ScanResultUtil; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.LocalLog; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; +import com.android.modules.utils.build.SdkLevel; +import com.android.server.wifi.hotspot2.NetworkDetail; +import com.android.server.wifi.proto.nano.WifiMetricsProto; +import com.android.server.wifi.util.InformationElementUtil.BssLoad; +import com.android.server.wifi.util.WifiPermissionsUtil; +import com.android.wifi.resources.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * WifiNetworkSelector looks at all the connectivity scan results and + * runs all the nominators to find or create matching configurations. + * Then it makes a final selection from among the resulting candidates. + */ +public class WifiNetworkSelector { + private static final String TAG = "WifiNetworkSelector"; + + private static final long INVALID_TIME_STAMP = Long.MIN_VALUE; + + /** + * Minimum time gap between last successful network selection and a + * new selection attempt. + */ + @VisibleForTesting + public static final int MINIMUM_NETWORK_SELECTION_INTERVAL_MS = 10 * 1000; + + /** + * Connected score value used to decide whether a still-connected wifi should be treated + * as unconnected when filtering scan results. + */ + @VisibleForTesting + public static final int WIFI_POOR_SCORE = ConnectedScore.WIFI_TRANSITION_SCORE - 10; + + /** + * The identifier string of the CandidateScorer to use (in the absence of overrides). + */ + public static final String PRESET_CANDIDATE_SCORER_NAME = "ThroughputScorer"; + + /** + * Experiment ID for the legacy scorer. + */ + public static final int LEGACY_CANDIDATE_SCORER_EXP_ID = 0; + + private final Context mContext; + private final WifiConfigManager mWifiConfigManager; + private final Clock mClock; + private final LocalLog mLocalLog; + private boolean mVerboseLoggingEnabled = false; + private final WifiMetrics mWifiMetrics; + private long mLastNetworkSelectionTimeStamp = INVALID_TIME_STAMP; + // Buffer of filtered scan results (Scan results considered by network selection) & associated + // WifiConfiguration (if any). + private final List> mConnectableNetworks = + new ArrayList<>(); + private List mFilteredNetworks = new ArrayList<>(); + private final WifiScoreCard mWifiScoreCard; + private final ScoringParams mScoringParams; + private final WifiInjector mWifiInjector; + private final ThroughputPredictor mThroughputPredictor; + private final WifiChannelUtilization mWifiChannelUtilization; + private final WifiGlobals mWifiGlobals; + private final ScanRequestProxy mScanRequestProxy; + + private final Map mCandidateScorers = new ArrayMap<>(); + private boolean mIsEnhancedOpenSupportedInitialized = false; + private boolean mIsEnhancedOpenSupported; + private boolean mSufficiencyCheckEnabledWhenScreenOff = true; + private boolean mSufficiencyCheckEnabledWhenScreenOn = true; + private boolean mUserConnectChoiceOverrideEnabled = true; + private boolean mLastSelectionWeightEnabled = true; + private @AssociatedNetworkSelectionOverride int mAssociatedNetworkSelectionOverride = + ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE; + private boolean mScreenOn = false; + private final WifiNative mWifiNative; + private final DevicePolicyManager mDevicePolicyManager; + + /** + * Interface for WiFi Network Nominator + * + * A network nominator examines the scan results reports the + * connectable candidates in its category for further consideration. + */ + public interface NetworkNominator { + /** Type of nominators */ + int NOMINATOR_ID_SAVED = 0; + int NOMINATOR_ID_SUGGESTION = 1; + int NOMINATOR_ID_SCORED = 4; + int NOMINATOR_ID_CURRENT = 5; // Should always be last + + @IntDef(prefix = {"NOMINATOR_ID_"}, value = { + NOMINATOR_ID_SAVED, + NOMINATOR_ID_SUGGESTION, + NOMINATOR_ID_SCORED, + NOMINATOR_ID_CURRENT}) + @Retention(RetentionPolicy.SOURCE) + public @interface NominatorId { + } + + /** + * Get the nominator type. + */ + @NominatorId + int getId(); + + /** + * Get the nominator name. + */ + String getName(); + + /** + * Update the nominator. + * + * Certain nominators have to be updated with the new scan results. For example + * the ScoredNetworkNominator needs to refresh its Score Cache. + * + * @param scanDetails a list of scan details constructed from the scan results + */ + void update(List scanDetails); + + /** + * Evaluate all the networks from the scan results. + * + * @param scanDetails a list of scan details constructed from the scan + * results + * @param untrustedNetworkAllowed a flag to indicate if untrusted networks are allowed + * @param oemPaidNetworkAllowed a flag to indicate if oem paid networks are allowed + * @param oemPrivateNetworkAllowed a flag to indicate if oem private networks are + * allowed + * @param restrictedNetworkAllowedUids a set of Uids are allowed for restricted network + * @param onConnectableListener callback to record all of the connectable networks + */ + void nominateNetworks(@NonNull List scanDetails, + @NonNull List> passpointCandidates, + boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed, + boolean oemPrivateNetworkAllowed, + @NonNull Set restrictedNetworkAllowedUids, + @NonNull OnConnectableListener onConnectableListener); + + /** + * Callback for recording connectable candidates + */ + public interface OnConnectableListener { + /** + * Notes that an access point is an eligible connection candidate + * + * @param scanDetail describes the specific access point + * @param config is the WifiConfiguration for the network + */ + void onConnectable(ScanDetail scanDetail, WifiConfiguration config); + } + } + + private final List mNominators = new ArrayList<>(3); + + // A helper to log debugging information in the local log buffer, which can + // be retrieved in bugreport. It is also used to print the log in the console. + private void localLog(String log) { + mLocalLog.log(log); + if (mVerboseLoggingEnabled) Log.d(TAG, log, null); + } + + /** + * Enable verbose logging in the console + */ + public void enableVerboseLogging(boolean verbose) { + mVerboseLoggingEnabled = verbose; + } + + /** + * Check if current network has sufficient RSSI + * + * @param wifiInfo info of currently connected network + * @return true if current link quality is sufficient, false otherwise. + */ + public boolean hasSufficientLinkQuality(WifiInfo wifiInfo) { + int currentRssi = wifiInfo.getRssi(); + return currentRssi >= mScoringParams.getSufficientRssi(wifiInfo.getFrequency()); + } + + /** + * Check if current network has active Tx or Rx traffic + * + * @param wifiInfo info of currently connected network + * @return true if it has active Tx or Rx traffic, false otherwise. + */ + public boolean hasActiveStream(WifiInfo wifiInfo) { + return wifiInfo.getSuccessfulTxPacketsPerSecond() + > mScoringParams.getActiveTrafficPacketsPerSecond() + || wifiInfo.getSuccessfulRxPacketsPerSecond() + > mScoringParams.getActiveTrafficPacketsPerSecond(); + } + + /** + * Check if current network has internet or is expected to not have internet + */ + public boolean hasInternetOrExpectNoInternet(WifiInfo wifiInfo) { + WifiConfiguration network = + mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId()); + if (network == null) { + return false; + } + return !network.hasNoInternetAccess() || network.isNoInternetAccessExpected(); + } + /** + * Determines whether the currently connected network is sufficient. + * + * If the network is good enough, or if switching to a new network is likely to + * be disruptive, we should avoid doing a network selection. + * + * @param wifiInfo info of currently connected network + * @return true if the network is sufficient + */ + public boolean isNetworkSufficient(WifiInfo wifiInfo) { + // Currently connected? + if (wifiInfo.getSupplicantState() != SupplicantState.COMPLETED) { + return false; + } + + localLog("Current connected network: " + wifiInfo.getNetworkId()); + + WifiConfiguration network = + mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId()); + + if (network == null) { + localLog("Current network was removed"); + return false; + } + + // Skip autojoin for the first few seconds of a user-initiated connection. + // This delays network selection during the time that connectivity service may be posting + // a dialog about a no-internet network. + if (mWifiConfigManager.getLastSelectedNetwork() == network.networkId + && (mClock.getElapsedSinceBootMillis() + - mWifiConfigManager.getLastSelectedTimeStamp()) + <= mContext.getResources().getInteger( + R.integer.config_wifiSufficientDurationAfterUserSelectionMilliseconds)) { + localLog("Current network is recently user-selected"); + return true; + } + + // Set OSU (Online Sign Up) network for Passpoint Release 2 to sufficient + // so that network select selection is skipped and OSU process can complete. + if (network.osu) { + localLog("Current connection is OSU"); + return true; + } + + // Current network is set as unusable by the external scorer. + if (!wifiInfo.isUsable()) { + localLog("Wifi is unusable according to external scorer."); + return false; + } + + // External scorer is not being used, and the current network's score is below the + // sufficient score threshold configured for the AOSP scorer. + if (!mWifiGlobals.isUsingExternalScorer() && wifiInfo.getScore() + < mWifiGlobals.getWifiLowConnectedScoreThresholdToTriggerScanForMbb()) { + if (!SdkLevel.isAtLeastS()) { + // Return false to prevent build issues since WifiInfo#isPrimary is only supported + // on S and above. + return false; + } + // Only return false to trigger network selection on the primary, since the secondary + // STA is not scored. + if (wifiInfo.isPrimary()) { + return false; + } + } + + // OEM paid/private networks are only available to system apps, so this is never sufficient. + if (network.oemPaid || network.oemPrivate) { + localLog("Current network is oem paid/private"); + return false; + } + + // Metered networks costs the user data, so this is insufficient. + if (WifiConfiguration.isMetered(network, wifiInfo)) { + localLog("Current network is metered"); + return false; + } + + // Network without internet access is not sufficient, unless expected + if (!hasInternetOrExpectNoInternet(wifiInfo)) { + localLog("Current network has [" + network.numNoInternetAccessReports + + "] no-internet access reports"); + return false; + } + + if (!isSufficiencyCheckEnabled()) { + localLog("Current network assumed as insufficient because sufficiency check is " + + "disabled. mScreenOn=" + mScreenOn); + return false; + } + + if (network.isIpProvisioningTimedOut()) { + localLog("Current network has no IPv4 provisioning and therefore insufficient"); + return false; + } + + if (!hasSufficientLinkQuality(wifiInfo) && !hasActiveStream(wifiInfo)) { + localLog("Current network link quality is not sufficient and has low ongoing traffic"); + return false; + } + + return true; + } + + /** + * Get whether associated network selection is enabled. + * @return + */ + public boolean isAssociatedNetworkSelectionEnabled() { + if (mAssociatedNetworkSelectionOverride == ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE) { + return mContext.getResources().getBoolean( + R.bool.config_wifi_framework_enable_associated_network_selection); + } + return mAssociatedNetworkSelectionOverride == ASSOCIATED_NETWORK_SELECTION_OVERRIDE_ENABLED; + } + + /** + * Check if network selection is needed on a CMM. + * @return True if network selection is needed. False if not needed. + */ + public boolean isNetworkSelectionNeededForCmm(@NonNull ClientModeManagerState cmmState) { + if (cmmState.connected) { + // Is roaming allowed? + if (!isAssociatedNetworkSelectionEnabled()) { + localLog(cmmState.ifaceName + ": Switching networks in connected state is not " + + "allowed. Skip network selection."); + return false; + } + + // Has it been at least the minimum interval since last network selection? + if (mLastNetworkSelectionTimeStamp != INVALID_TIME_STAMP) { + long gap = mClock.getElapsedSinceBootMillis() + - mLastNetworkSelectionTimeStamp; + if (gap < MINIMUM_NETWORK_SELECTION_INTERVAL_MS) { + localLog(cmmState.ifaceName + ": Too short since last network selection: " + + gap + " ms. Skip network selection."); + return false; + } + } + // Please note other scans (e.g., location scan or app scan) may also trigger network + // selection and these scans may or may not run sufficiency check. + // So it is better to run sufficiency check here before network selection. + if (isNetworkSufficient(cmmState.wifiInfo)) { + localLog(cmmState.ifaceName + + ": Current connected network already sufficient." + + " Skip network selection."); + return false; + } else { + localLog(cmmState.ifaceName + ": Current connected network is not sufficient."); + return true; + } + } else if (cmmState.disconnected || cmmState.ipProvisioningTimedOut) { + return true; + } else { + // No network selection if ClientModeImpl is in a state other than + // connected or disconnected (i.e connecting). + localLog(cmmState.ifaceName + ": ClientModeImpl is in neither CONNECTED nor " + + "DISCONNECTED state. Skip network selection."); + return false; + } + + } + + private boolean isNetworkSelectionNeeded(@NonNull List scanDetails, + @NonNull List cmmStates) { + if (scanDetails.size() == 0) { + localLog("Empty connectivity scan results. Skip network selection."); + return false; + } + for (ClientModeManagerState cmmState : cmmStates) { + // network selection needed by this CMM instance, perform network selection + if (isNetworkSelectionNeededForCmm(cmmState)) { + return true; + } + } + // none of the CMM instances need network selection, skip network selection. + return false; + } + + /** + * Format the given ScanResult as a scan ID for logging. + */ + public static String toScanId(@Nullable ScanResult scanResult) { + return scanResult == null ? "NULL" + : scanResult.SSID + ":" + scanResult.BSSID; + } + + /** + * Format the given WifiConfiguration as a SSID:netId string + */ + public static String toNetworkString(WifiConfiguration network) { + if (network == null) { + return null; + } + + return (network.SSID + ":" + network.networkId); + } + + /** + * Compares ScanResult level against the minimum threshold for its band, returns true if lower + */ + public boolean isSignalTooWeak(ScanResult scanResult) { + return (scanResult.level < mScoringParams.getEntryRssi(scanResult.frequency)); + } + + @SuppressLint("NewApi") + private List filterScanResults(List scanDetails, + Set bssidBlocklist, List cmmStates) { + List validScanDetails = new ArrayList<>(); + StringBuffer noValidSsid = new StringBuffer(); + StringBuffer blockedBssid = new StringBuffer(); + StringBuffer lowRssi = new StringBuffer(); + StringBuffer mboAssociationDisallowedBssid = new StringBuffer(); + StringBuffer adminRestrictedSsid = new StringBuffer(); + StringJoiner deprecatedSecurityTypeSsid = new StringJoiner(" / "); + List currentBssids = cmmStates.stream() + .map(cmmState -> cmmState.wifiInfo.getBSSID()) + .collect(Collectors.toList()); + Set scanResultPresentForCurrentBssids = new ArraySet<>(); + + int adminMinimumSecurityLevel = 0; + boolean adminSsidRestrictionSet = false; + Set adminSsidAllowlist = new ArraySet<>(); + Set admindSsidDenylist = new ArraySet<>(); + + int numBssidFiltered = 0; + + if (mDevicePolicyManager != null && SdkLevel.isAtLeastT()) { + adminMinimumSecurityLevel = + mDevicePolicyManager.getMinimumRequiredWifiSecurityLevel(); + WifiSsidPolicy policy = mDevicePolicyManager.getWifiSsidPolicy(); + if (policy != null) { + adminSsidRestrictionSet = true; + if (policy.getPolicyType() == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) { + adminSsidAllowlist = policy.getSsids(); + } else { + admindSsidDenylist = policy.getSsids(); + } + } + } + + for (ScanDetail scanDetail : scanDetails) { + ScanResult scanResult = scanDetail.getScanResult(); + + if (TextUtils.isEmpty(scanResult.SSID)) { + noValidSsid.append(scanResult.BSSID).append(" / "); + continue; + } + + // Check if the scan results contain the currently connected BSSID's + if (currentBssids.contains(scanResult.BSSID)) { + scanResultPresentForCurrentBssids.add(scanResult.BSSID); + validScanDetails.add(scanDetail); + continue; + } + + final String scanId = toScanId(scanResult); + + if (bssidBlocklist.contains(scanResult.BSSID)) { + blockedBssid.append(scanId).append(" / "); + numBssidFiltered++; + continue; + } + + // Skip network with too weak signals. + if (isSignalTooWeak(scanResult)) { + lowRssi.append(scanId); + if (scanResult.is24GHz()) { + lowRssi.append("(2.4GHz)"); + } else if (scanResult.is5GHz()) { + lowRssi.append("(5GHz)"); + } else if (scanResult.is6GHz()) { + lowRssi.append("(6GHz)"); + } + lowRssi.append(scanResult.level).append(" / "); + continue; + } + + // Skip BSS which is not accepting new connections. + NetworkDetail networkDetail = scanDetail.getNetworkDetail(); + if (networkDetail != null) { + if (networkDetail.getMboAssociationDisallowedReasonCode() + != MboOceConstants.MBO_OCE_ATTRIBUTE_NOT_PRESENT) { + mWifiMetrics + .incrementNetworkSelectionFilteredBssidCountDueToMboAssocDisallowInd(); + mboAssociationDisallowedBssid.append(scanId).append("(") + .append(networkDetail.getMboAssociationDisallowedReasonCode()) + .append(")").append(" / "); + continue; + } + } + + // Skip network that does not meet the admin set SSID restriction + if (adminSsidRestrictionSet) { + WifiSsid ssid = scanResult.getWifiSsid(); + // Allowlist policy set but network is not present in the list + if (!adminSsidAllowlist.isEmpty() && !adminSsidAllowlist.contains(ssid)) { + adminRestrictedSsid.append(scanId).append(" / "); + continue; + } + // Denylist policy set but network is present in the list + if (!admindSsidDenylist.isEmpty() && admindSsidDenylist.contains(ssid)) { + adminRestrictedSsid.append(scanId).append(" / "); + continue; + } + } + + // Skip network that does not meet the admin set minimum security level restriction + if (adminMinimumSecurityLevel != 0) { + boolean securityRestrictionPassed = false; + @WifiAnnotations.SecurityType int[] securityTypes = scanResult.getSecurityTypes(); + for (int type : securityTypes) { + int securityLevel = WifiInfo.convertSecurityTypeToDpmWifiSecurity(type); + + // Skip unknown security type since security level cannot be determined. + // If all the security types are unknown when the minimum security level + // restriction is set, the scan result is ignored. + if (securityLevel == WifiInfo.DPM_SECURITY_TYPE_UNKNOWN) continue; + + if (adminMinimumSecurityLevel <= securityLevel) { + securityRestrictionPassed = true; + break; + } + } + if (!securityRestrictionPassed) { + adminRestrictedSsid.append(scanId).append(" / "); + continue; + } + } + + // Skip network that has deprecated security type + if (mWifiGlobals.isWpaPersonalDeprecated() || mWifiGlobals.isWepDeprecated()) { + boolean securityTypeDeprecated = false; + @WifiAnnotations.SecurityType int[] securityTypes = scanResult.getSecurityTypes(); + for (int type : securityTypes) { + if (mWifiGlobals.isWepDeprecated() && type == WifiInfo.SECURITY_TYPE_WEP) { + securityTypeDeprecated = true; + break; + } + if (mWifiGlobals.isWpaPersonalDeprecated() && type == WifiInfo.SECURITY_TYPE_PSK + && ScanResultUtil.isScanResultForWpaPersonalOnlyNetwork(scanResult)) { + securityTypeDeprecated = true; + break; + } + } + if (securityTypeDeprecated) { + deprecatedSecurityTypeSsid.add(scanId); + continue; + } + } + + validScanDetails.add(scanDetail); + } + mWifiMetrics.incrementNetworkSelectionFilteredBssidCount(numBssidFiltered); + + // WNS listens to all single scan results. Some scan requests may not include + // the channel of the currently connected network, so the currently connected + // network won't show up in the scan results. We don't act on these scan results + // to avoid aggressive network switching which might trigger disconnection. + // TODO(b/147751334) this may no longer be needed + for (ClientModeManagerState cmmState : cmmStates) { + // TODO (b/169413079): Disable network selection on corresponding CMM instead. + if (cmmState.connected && cmmState.wifiInfo.getScore() >= WIFI_POOR_SCORE + && !scanResultPresentForCurrentBssids.contains(cmmState.wifiInfo.getBSSID())) { + if (isSufficiencyCheckEnabled()) { + localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID() + + " is not in the scan results. Skip network selection."); + validScanDetails.clear(); + return validScanDetails; + } else { + localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID() + + " is not in the scan results. But continue network selection because" + + " sufficiency check is disabled."); + } + } + } + + if (noValidSsid.length() != 0) { + localLog("Networks filtered out due to invalid SSID: " + noValidSsid); + } + + if (blockedBssid.length() != 0) { + localLog("Networks filtered out due to blocklist: " + blockedBssid); + } + + if (lowRssi.length() != 0) { + localLog("Networks filtered out due to low signal strength: " + lowRssi); + } + + if (mboAssociationDisallowedBssid.length() != 0) { + localLog("Networks filtered out due to mbo association disallowed indication: " + + mboAssociationDisallowedBssid); + } + + if (adminRestrictedSsid.length() != 0) { + localLog("Networks filtered out due to admin restrictions: " + adminRestrictedSsid); + } + + if (deprecatedSecurityTypeSsid.length() != 0) { + localLog("Networks filtered out due to deprecated security type: " + + deprecatedSecurityTypeSsid); + } + + return validScanDetails; + } + + private ScanDetail findScanDetailForBssid(List scanDetails, + String currentBssid) { + for (ScanDetail scanDetail : scanDetails) { + ScanResult scanResult = scanDetail.getScanResult(); + if (scanResult.BSSID.equals(currentBssid)) { + return scanDetail; + } + } + return null; + } + + private boolean isEnhancedOpenSupported() { + if (mIsEnhancedOpenSupportedInitialized) { + return mIsEnhancedOpenSupported; + } + + mIsEnhancedOpenSupportedInitialized = true; + ClientModeManager primaryManager = + mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager(); + mIsEnhancedOpenSupported = (primaryManager.getSupportedFeatures() & WIFI_FEATURE_OWE) != 0; + return mIsEnhancedOpenSupported; + } + + /** + * This returns a list of ScanDetails that were filtered in the process of network selection. + * The list is further filtered for only open unsaved networks. + * + * @return the list of ScanDetails for open unsaved networks that do not have invalid SSIDS, + * blocked BSSIDS, or low signal strength. This will return an empty list when there are + * no open unsaved networks, or when network selection has not been run. + */ + public List getFilteredScanDetailsForOpenUnsavedNetworks() { + List openUnsavedNetworks = new ArrayList<>(); + boolean enhancedOpenSupported = isEnhancedOpenSupported(); + for (ScanDetail scanDetail : mFilteredNetworks) { + ScanResult scanResult = scanDetail.getScanResult(); + + if (!ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { + continue; + } + + // Filter out Enhanced Open networks on devices that do not support it + if (ScanResultUtil.isScanResultForOweNetwork(scanResult) + && !enhancedOpenSupported) { + continue; + } + + // Skip saved networks + if (mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail) != null) { + continue; + } + + openUnsavedNetworks.add(scanDetail); + } + return openUnsavedNetworks; + } + + /** + * @return the list of ScanDetails scored as potential candidates by the last run of + * selectNetwork, this will be empty if Network selector determined no selection was + * needed on last run. This includes scan details of sufficient signal strength, and + * had an associated WifiConfiguration. + */ + public List> getConnectableScanDetails() { + return mConnectableNetworks; + } + + /** + * Iterate thru the list of configured networks (includes all saved network configurations + + * any ephemeral network configurations created for passpoint networks, suggestions, carrier + * networks, etc) and do the following: + * a) Try to re-enable any temporarily enabled networks (if the blocklist duration has expired). + * b) Clear the {@link WifiConfiguration.NetworkSelectionStatus#getCandidate()} field for all + * of them to identify networks that are present in the current scan result. + * c) Log any disabled networks. + */ + private void updateConfiguredNetworks() { + List configuredNetworks = mWifiConfigManager.getConfiguredNetworks(); + if (configuredNetworks.size() == 0) { + localLog("No configured networks."); + return; + } + + StringBuffer sbuf = new StringBuffer(); + for (WifiConfiguration network : configuredNetworks) { + // If a configuration is temporarily disabled, re-enable it before trying + // to connect to it. + mWifiConfigManager.tryEnableNetwork(network.networkId); + // Clear the cached candidate, score and seen. + mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId); + + // Log disabled network. + WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus(); + if (!status.isNetworkEnabled()) { + sbuf.append(" ").append(toNetworkString(network)).append(" "); + for (int index = WifiConfiguration.NetworkSelectionStatus + .NETWORK_SELECTION_DISABLED_STARTING_INDEX; + index < WifiConfiguration.NetworkSelectionStatus + .NETWORK_SELECTION_DISABLED_MAX; + index++) { + int count = status.getDisableReasonCounter(index); + // Here we log the reason as long as its count is greater than zero. The + // network may not be disabled because of this particular reason. Logging + // this information anyway to help understand what happened to the network. + if (count > 0) { + sbuf.append("reason=") + .append(WifiConfiguration.NetworkSelectionStatus + .getNetworkSelectionDisableReasonString(index)) + .append(", count=").append(count).append("; "); + } + } + sbuf.append("\n"); + } + } + + if (sbuf.length() > 0) { + localLog("Disabled configured networks:"); + localLog(sbuf.toString()); + } + } + + /** + * Overrides the {@code candidate} chosen by the {@link #mNominators} with the user chosen + * {@link WifiConfiguration} if one exists. + * + * @return the user chosen {@link WifiConfiguration} if one exists, {@code candidate} otherwise + */ + private WifiConfiguration overrideCandidateWithUserConnectChoice( + @NonNull WifiConfiguration candidate) { + WifiConfiguration tempConfig = Preconditions.checkNotNull(candidate); + WifiConfiguration originalCandidate = candidate; + ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate(); + + Set seenNetworks = new HashSet<>(); + seenNetworks.add(candidate.getProfileKey()); + + while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) { + String key = tempConfig.getNetworkSelectionStatus().getConnectChoice(); + int userSelectedRssi = tempConfig.getNetworkSelectionStatus().getConnectChoiceRssi(); + tempConfig = mWifiConfigManager.getConfiguredNetwork(key); + + if (tempConfig != null) { + if (seenNetworks.contains(tempConfig.getProfileKey())) { + Log.wtf(TAG, "user connected network is a loop, use candidate:" + + candidate); + mWifiConfigManager.setLegacyUserConnectChoice(candidate, + candidate.getNetworkSelectionStatus().getCandidate().level); + break; + } + seenNetworks.add(tempConfig.getProfileKey()); + WifiConfiguration.NetworkSelectionStatus tempStatus = + tempConfig.getNetworkSelectionStatus(); + boolean noInternetButInternetIsExpected = !tempConfig.isNoInternetAccessExpected() + && tempConfig.hasNoInternetAccess(); + if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled() + && !noInternetButInternetIsExpected + && isUserChoiceRssiCloseToOrGreaterThanExpectedValue( + tempStatus.getCandidate().level, userSelectedRssi)) { + scanResultCandidate = tempStatus.getCandidate(); + candidate = tempConfig; + } + } else { + localLog("Connect choice: " + key + " has no corresponding saved config."); + break; + } + } + + if (candidate != originalCandidate) { + localLog("After user selection adjustment, the final candidate is:" + + WifiNetworkSelector.toNetworkString(candidate) + " : " + + scanResultCandidate.BSSID); + mWifiMetrics.setNominatorForNetwork(candidate.networkId, + WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE); + } + return candidate; + } + + private boolean isUserChoiceRssiCloseToOrGreaterThanExpectedValue(int observedRssi, + int expectedRssi) { + // The expectedRssi may be 0 for newly upgraded devices which do not have this information, + // pass the test for those devices to avoid regression. + if (expectedRssi == 0) { + return true; + } + return observedRssi >= expectedRssi - mScoringParams.getEstimateRssiErrorMargin(); + } + + + /** + * Indicates whether we have ever seen the network to be metered since wifi was enabled. + * + * This is sticky to prevent continuous flip-flopping between networks, when the metered + * status is learned after association. + */ + private boolean isEverMetered(@NonNull WifiConfiguration config, @Nullable WifiInfo info, + @NonNull ScanDetail scanDetail) { + // If info does not match config, don't use it. + if (info != null && info.getNetworkId() != config.networkId) info = null; + boolean metered = WifiConfiguration.isMetered(config, info); + NetworkDetail networkDetail = scanDetail.getNetworkDetail(); + if (networkDetail != null + && networkDetail.getAnt() + == NetworkDetail.Ant.ChargeablePublic) { + metered = true; + } + mWifiMetrics.addMeteredStat(config, metered); + if (config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE) { + // User override is in effect; we should trust it + if (mKnownMeteredNetworkIds.remove(config.networkId)) { + localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds); + } + metered = config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED; + } else if (mKnownMeteredNetworkIds.contains(config.networkId)) { + // Use the saved information + metered = true; + } else if (metered) { + // Update the saved information + mKnownMeteredNetworkIds.add(config.networkId); + localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds); + } + return metered; + } + + /** + * Returns the set of known metered network ids (for tests. dumpsys, and metrics). + */ + public Set getKnownMeteredNetworkIds() { + return new ArraySet<>(mKnownMeteredNetworkIds); + } + + private final ArraySet mKnownMeteredNetworkIds = new ArraySet<>(); + + + /** + * Cleans up state that should go away when wifi is disabled. + */ + public void resetOnDisable() { + mWifiConfigManager.clearLastSelectedNetwork(); + mKnownMeteredNetworkIds.clear(); + } + + /** + * Container class for passing the ClientModeManager state for each instance that is managed by + * WifiConnectivityManager, i.e all {@link ClientModeManager#getRole()} equals + * {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} or + * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}. + */ + public static class ClientModeManagerState { + /** Iface Name corresponding to iface (if known) */ + public final String ifaceName; + /** True if the device is connected */ + public final boolean connected; + /** True if the device is disconnected */ + public final boolean disconnected; + /** True if the device is connected in local-only mode due to ip provisioning timeout**/ + public final boolean ipProvisioningTimedOut; + /** Currently connected network */ + public final WifiInfo wifiInfo; + + ClientModeManagerState(@NonNull ClientModeManager clientModeManager) { + ifaceName = clientModeManager.getInterfaceName(); + connected = clientModeManager.isConnected(); + disconnected = clientModeManager.isDisconnected(); + ipProvisioningTimedOut = clientModeManager.isIpProvisioningTimedOut(); + wifiInfo = clientModeManager.getConnectionInfo(); + } + + ClientModeManagerState() { + ifaceName = "unknown"; + connected = false; + disconnected = true; + wifiInfo = new WifiInfo(); + ipProvisioningTimedOut = false; + } + + @VisibleForTesting + ClientModeManagerState(@NonNull String ifaceName, boolean connected, boolean disconnected, + @NonNull WifiInfo wifiInfo, boolean ipProvisioningTimedOut) { + this.ifaceName = ifaceName; + this.connected = connected; + this.disconnected = disconnected; + this.wifiInfo = wifiInfo; + this.ipProvisioningTimedOut = ipProvisioningTimedOut; + } + + @Override + public boolean equals(Object that) { + if (this == that) return true; + if (!(that instanceof ClientModeManagerState)) return false; + ClientModeManagerState thatCmmState = (ClientModeManagerState) that; + return Objects.equals(ifaceName, thatCmmState.ifaceName) + && connected == thatCmmState.connected + && disconnected == thatCmmState.disconnected + // Since wifiinfo does not have equals currently. + && Objects.equals(wifiInfo.getSSID(), thatCmmState.wifiInfo.getSSID()) + && Objects.equals(wifiInfo.getBSSID(), thatCmmState.wifiInfo.getBSSID()); + } + + @Override + public int hashCode() { + return Objects.hash(ifaceName, connected, disconnected, + wifiInfo.getSSID(), wifiInfo.getBSSID()); + } + + @Override + public String toString() { + return "ClientModeManagerState: " + ifaceName + + ", connection state: " + + (connected ? " connected" : (disconnected ? " disconnected" : "unknown")) + + ", WifiInfo: " + wifiInfo; + } + } + + /** + * Sets the screen state. + */ + public void setScreenState(boolean screenOn) { + mScreenOn = screenOn; + } + + /** + * Sets the associated network selection override. + */ + public boolean setAssociatedNetworkSelectionOverride( + @AssociatedNetworkSelectionOverride int value) { + if (value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE + && value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_ENABLED + && value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_DISABLED) { + localLog("Error in setting associated network selection override. Invalid value=" + + value); + return false; + } + mAssociatedNetworkSelectionOverride = value; + return true; + } + + /** + * Get whether sufficiency check is enabled based on the current screen state. + */ + public boolean isSufficiencyCheckEnabled() { + return mScreenOn ? mSufficiencyCheckEnabledWhenScreenOn + : mSufficiencyCheckEnabledWhenScreenOff; + } + + /** + * Enable or disable sufficiency check. + */ + public void setSufficiencyCheckEnabled(boolean enabledWhileScreenOff, + boolean enabledWhileScreenOn) { + mSufficiencyCheckEnabledWhenScreenOff = enabledWhileScreenOff; + mSufficiencyCheckEnabledWhenScreenOn = enabledWhileScreenOn; + } + + /** + * Enable or disable candidate override with user connect choice. + */ + public void setUserConnectChoiceOverrideEnabled(boolean enabled) { + mUserConnectChoiceOverrideEnabled = enabled; + } + + /** + * Enable or disable last selection weight. + */ + public void setLastSelectionWeightEnabled(boolean enabled) { + mLastSelectionWeightEnabled = enabled; + } + + /** + * Returns the list of Candidates from networks in range. + * + * @param scanDetails List of ScanDetail for all the APs in range + * @param bssidBlocklist Blocked BSSIDs + * @param cmmStates State of all long lived client mode manager instances - + * {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} & + * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}. + * @param untrustedNetworkAllowed True if untrusted networks are allowed for connection + * @param oemPaidNetworkAllowed True if oem paid networks are allowed for connection + * @param oemPrivateNetworkAllowed True if oem private networks are allowed for connection + * @param restrictedNetworkAllowedUids a set of Uids are allowed for restricted network + * @param multiInternetNetworkAllowed True if multi internet networks are allowed for + * connection. + * @return list of valid Candidate(s) + */ + public List getCandidatesFromScan( + @NonNull List scanDetails, @NonNull Set bssidBlocklist, + @NonNull List cmmStates, boolean untrustedNetworkAllowed, + boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed, + Set restrictedNetworkAllowedUids, boolean multiInternetNetworkAllowed) { + mFilteredNetworks.clear(); + mConnectableNetworks.clear(); + if (scanDetails.size() == 0) { + localLog("Empty connectivity scan result"); + return null; + } + + // Update the scan detail cache at the start, even if we skip network selection + updateScanDetailCache(scanDetails); + + // Update the registered network nominators. + for (NetworkNominator registeredNominator : mNominators) { + registeredNominator.update(scanDetails); + } + // Update the matching profiles into WifiConfigManager, help displaying Passpoint networks + // in Wifi Picker + mWifiInjector.getPasspointNetworkNominateHelper().updatePasspointConfig(scanDetails); + + // Shall we start network selection at all? + if (!multiInternetNetworkAllowed && !isNetworkSelectionNeeded(scanDetails, cmmStates)) { + return null; + } + + // Filter out unwanted networks. + mFilteredNetworks = filterScanResults(scanDetails, bssidBlocklist, cmmStates); + if (mFilteredNetworks.size() == 0) { + return null; + } + + WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext); + for (ClientModeManagerState cmmState : cmmStates) { + // Always get the current BSSID from WifiInfo in case that firmware initiated + // roaming happened. + String currentBssid = cmmState.wifiInfo.getBSSID(); + WifiConfiguration currentNetwork = + mWifiConfigManager.getConfiguredNetwork(cmmState.wifiInfo.getNetworkId()); + if (currentNetwork != null) { + wifiCandidates.setCurrent(currentNetwork.networkId, currentBssid); + // We always want the current network to be a candidate so that it can participate. + // It may also get re-added by a nominator, in which case this fallback + // will be replaced. + MacAddress bssid = MacAddress.fromString(currentBssid); + SecurityParams params = currentNetwork.getNetworkSelectionStatus() + .getLastUsedSecurityParams(); + if (null == params) { + localLog("No known candidate security params for current network."); + continue; + } + WifiCandidates.Key key = new WifiCandidates.Key( + ScanResultMatchInfo.fromWifiConfiguration(currentNetwork), + bssid, currentNetwork.networkId, + params.getSecurityType()); + ScanDetail scanDetail = findScanDetailForBssid(mFilteredNetworks, currentBssid); + int predictedTputMbps = (scanDetail == null) ? 0 : predictThroughput(scanDetail); + wifiCandidates.add(key, currentNetwork, + NetworkNominator.NOMINATOR_ID_CURRENT, + cmmState.wifiInfo.getRssi(), + cmmState.wifiInfo.getFrequency(), + ScanResult.CHANNEL_WIDTH_20MHZ, // channel width not available in WifiInfo + calculateLastSelectionWeight(currentNetwork.networkId, + WifiConfiguration.isMetered(currentNetwork, cmmState.wifiInfo)), + WifiConfiguration.isMetered(currentNetwork, cmmState.wifiInfo), + isFromCarrierOrPrivilegedApp(currentNetwork), + predictedTputMbps, + (scanDetail != null) ? scanDetail.getScanResult().getApMldMacAddress() + : null); + } + } + + // Update all configured networks before initiating network selection. + updateConfiguredNetworks(); + + List> passpointCandidates = mWifiInjector + .getPasspointNetworkNominateHelper() + .getPasspointNetworkCandidates(new ArrayList<>(mFilteredNetworks)); + for (NetworkNominator registeredNominator : mNominators) { + localLog("About to run " + registeredNominator.getName() + " :"); + registeredNominator.nominateNetworks( + new ArrayList<>(mFilteredNetworks), passpointCandidates, + untrustedNetworkAllowed, oemPaidNetworkAllowed, oemPrivateNetworkAllowed, + restrictedNetworkAllowedUids, (scanDetail, config) -> { + WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig( + scanDetail, config); + if (key != null) { + boolean metered = false; + for (ClientModeManagerState cmmState : cmmStates) { + if (isEverMetered(config, cmmState.wifiInfo, scanDetail)) { + metered = true; + break; + } + } + // TODO(b/151981920) Saved passpoint candidates are marked ephemeral + boolean added = wifiCandidates.add(key, config, + registeredNominator.getId(), + scanDetail.getScanResult().level, + scanDetail.getScanResult().frequency, + scanDetail.getScanResult().channelWidth, + calculateLastSelectionWeight(config.networkId, metered), + metered, + isFromCarrierOrPrivilegedApp(config), + predictThroughput(scanDetail), + scanDetail.getScanResult().getApMldMacAddress()); + if (added) { + mConnectableNetworks.add(Pair.create(scanDetail, config)); + mWifiConfigManager.updateScanDetailForNetwork( + config.networkId, scanDetail); + mWifiMetrics.setNominatorForNetwork(config.networkId, + toProtoNominatorId(registeredNominator.getId())); + } + } + }); + } + if (mConnectableNetworks.size() != wifiCandidates.size()) { + localLog("Connectable: " + mConnectableNetworks.size() + + " Candidates: " + wifiCandidates.size()); + } + + // Update multi link candidate throughput before network selection. + updateMultiLinkCandidatesThroughput(wifiCandidates); + + return wifiCandidates.getCandidates(); + } + + /** + * Check Wi-Fi7 is enabled for all candidates. + */ + private boolean isWifi7Enabled(List candidates) { + for (WifiCandidates.Candidate candidate : candidates) { + if (!mWifiConfigManager.isWifi7Enabled(candidate.getNetworkConfigId())) return false; + } + return true; + } + + /** + * Update multi link candidate's throughput which is used in network selection by + * {@link ThroughputScorer} + * + * Algorithm: + * {@link WifiNative#getSupportedBandCombinations(String)} returns a list of band combinations + * supported by the chip. e.g. { {2.4}, {5}, {6}, {2.4, 5}, {2.4, 6}, {5, 6} }. + * + * During the creation of candidate list, members which have same MLD AP MAC address are grouped + * together. Let's say we have the following multi link candidates in one group {C_2.4, C_5, + * C_6}. First intersect this list with allowed combination to get a collection like this, + * { {C_2.4}, {C_5}, {C_6}, {C_2.4, C_5}, {C_2.4, C_6}, {C_5, C_6} }. For each of the sub-group, + * predicted single link throughputs are added and each candidate in the subgroup get an + * updated multi link throughput if the saved value is less. This calculation takes care of + * eMLSR and STR. + * + * If the chip can't support all the radios for multi-link operation at the same time for STR + * operation, we can't use the higher-order radio combinations. + * + * Above algorithm is extendable to multiple links with any number of bands and link + * restriction. + * + * @param wifiCandidates A list of WifiCandidates + */ + private void updateMultiLinkCandidatesThroughput(WifiCandidates wifiCandidates) { + ClientModeManager primaryManager = + mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager(); + if (primaryManager == null) return; + String interfaceName = primaryManager.getInterfaceName(); + if (interfaceName == null) return; + + // Check if the chip has more than one MLO STR link support. + int maxMloStrLinkCount = mWifiNative.getMaxMloStrLinkCount(interfaceName); + if (maxMloStrLinkCount <= 1) return; + + Set> simultaneousBandCombinations = mWifiNative.getSupportedBandCombinations( + interfaceName); + if (simultaneousBandCombinations == null) return; + + for (List mlCandidates : + wifiCandidates.getMultiLinkCandidates()) { + if (!isWifi7Enabled(mlCandidates)) continue; + for (List bands : simultaneousBandCombinations) { + // Limit the radios/bands to maximum STR link supported in multi link operation. + if (bands.size() > maxMloStrLinkCount) break; + List strBandsToIntersect = new ArrayList<>(bands); + List strMlCandidates = intersectMlCandidatesWithStrBands( + mlCandidates, strBandsToIntersect); + if (strMlCandidates != null) { + aggregateStrMultiLinkThroughput(strMlCandidates); + } + } + } + } + + /** + * Return the intersection of STR band combinations and best Multi-Link Wi-Fi candidates. + */ + private List intersectMlCandidatesWithStrBands( + @NonNull List candidates, @NonNull List bands) { + // Sorting is needed here to make the best candidates first in the list. + List intersectedCandidates = candidates.stream() + .sorted(Comparator.comparingInt( + WifiCandidates.Candidate::getPredictedThroughputMbps).reversed()) + .filter(k -> { + int band = Integer.valueOf(ScanResult.toBand(k.getFrequency())); + if (bands.contains(band)) { + // Remove first occurrence as it is counted already. + bands.remove(bands.indexOf(band)); + return true; + } + return false; + }) + .collect(Collectors.toList()); + // Make sure all bands are intersected. + return (bands.isEmpty()) ? intersectedCandidates : null; + } + + /** + * Aggregate the throughput of STR multi-link candidates. + */ + private void aggregateStrMultiLinkThroughput( + @NonNull List candidates) { + // Add all throughputs. + int predictedMlThroughput = candidates.stream() + .mapToInt(c -> c.getPredictedThroughputMbps()) + .sum(); + // Check if an update needed for multi link throughput. + candidates.stream() + .filter(c -> c.getPredictedMultiLinkThroughputMbps() < predictedMlThroughput) + .forEach(c -> c.setPredictedMultiLinkThroughputMbps(predictedMlThroughput)); + } + + /** + * Add all results as candidates for the user selected network and let network selection + * chooses the proper one for the user selected network. + * @param config The configuration for the user selected network. + * @param scanDetails List of ScanDetail for the user selected network. + * @return list of valid Candidate(s) + */ + public List getCandidatesForUserSelection( + WifiConfiguration config, @NonNull List scanDetails) { + if (scanDetails.size() == 0) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "No scan result for the user selected network."); + return null; + } + } + + mConnectableNetworks.clear(); + WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext); + for (ScanDetail scanDetail: scanDetails) { + WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig( + scanDetail, config); + if (null == key) continue; + + boolean added = wifiCandidates.add(key, config, + WifiNetworkSelector.NetworkNominator.NOMINATOR_ID_CURRENT, + scanDetail.getScanResult().level, + scanDetail.getScanResult().frequency, + scanDetail.getScanResult().channelWidth, + 0.0 /* lastSelectionWeightBetweenZeroAndOne */, + false /* isMetered */, + WifiNetworkSelector.isFromCarrierOrPrivilegedApp(config), + predictThroughput(scanDetail), scanDetail.getScanResult().getApMldMacAddress()); + if (!added) continue; + + mConnectableNetworks.add(Pair.create(scanDetail, config)); + mWifiConfigManager.updateScanDetailForNetwork( + config.networkId, scanDetail); + } + return wifiCandidates.getCandidates(); + } + + /** + * For transition networks with only legacy networks, + * remove auto-upgrade type to use the legacy type to + * avoid roaming issues between two types. + */ + private void removeAutoUpgradeSecurityParamsIfNecessary( + WifiConfiguration config, + List scanResultParamsList, + @WifiConfiguration.SecurityType int baseSecurityType, + @WifiConfiguration.SecurityType int upgradableSecurityType, + boolean isLegacyNetworkInRange, + boolean isUpgradableTypeOnlyInRange, + boolean isAutoUpgradeEnabled) { + localLog("removeAutoUpgradeSecurityParamsIfNecessary:" + + " SSID: " + config.SSID + + " baseSecurityType: " + baseSecurityType + + " upgradableSecurityType: " + upgradableSecurityType + + " isLegacyNetworkInRange: " + isLegacyNetworkInRange + + " isUpgradableTypeOnlyInRange: " + isUpgradableTypeOnlyInRange + + " isAutoUpgradeEnabled: " + isAutoUpgradeEnabled); + if (isAutoUpgradeEnabled) { + // Consider removing the auto-upgraded type if legacy networks are in range. + if (!isLegacyNetworkInRange) return; + // If base params is disabled or removed, keep the auto-upgrade params. + SecurityParams baseParams = config.getSecurityParams(baseSecurityType); + if (null == baseParams || !baseParams.isEnabled()) return; + // If there are APs with standalone-upgradeable security type is in range, + // do not consider removing the auto-upgraded type. + if (isUpgradableTypeOnlyInRange) return; + } + + SecurityParams upgradableParams = config.getSecurityParams(upgradableSecurityType); + if (null == upgradableParams) return; + if (!upgradableParams.isAddedByAutoUpgrade()) return; + localLog("Remove upgradable security type " + upgradableSecurityType + " for the network."); + scanResultParamsList.removeIf(p -> p.isSecurityType(upgradableSecurityType)); + } + + /** Helper function to place all conditions which need to remove auto-upgrade types. */ + private void removeSecurityParamsIfNecessary( + WifiConfiguration config, + List scanResultParamsList) { + // When offload is supported, both types are passed down. + if (!mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()) { + removeAutoUpgradeSecurityParamsIfNecessary( + config, scanResultParamsList, + WifiConfiguration.SECURITY_TYPE_PSK, + WifiConfiguration.SECURITY_TYPE_SAE, + mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(config.SSID), + mScanRequestProxy.isWpa3PersonalOnlyNetworkInRange(config.SSID), + mWifiGlobals.isWpa3SaeUpgradeEnabled()); + } + removeAutoUpgradeSecurityParamsIfNecessary( + config, scanResultParamsList, + WifiConfiguration.SECURITY_TYPE_OPEN, + WifiConfiguration.SECURITY_TYPE_OWE, + mScanRequestProxy.isOpenOnlyNetworkInRange(config.SSID), + mScanRequestProxy.isOweOnlyNetworkInRange(config.SSID), + mWifiGlobals.isOweUpgradeEnabled()); + removeAutoUpgradeSecurityParamsIfNecessary( + config, scanResultParamsList, + WifiConfiguration.SECURITY_TYPE_EAP, + WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE, + mScanRequestProxy.isWpa2EnterpriseOnlyNetworkInRange(config.SSID), + mScanRequestProxy.isWpa3EnterpriseOnlyNetworkInRange(config.SSID), + true); + // When using WPA3 (SAE), all passwords in all lengths are strings, but when using WPA2, + // there is a distinction between 8-63 octets that go through BDKDF2 function, and + // 64-octets that are assumed to be the output of it. BDKDF2 is not applicable to SAE + // and to prevent interop issues with APs when 64-octet Hex PSK is configured, update + // the configuration to use WPA2 only. + WifiConfiguration configWithPassword = mWifiConfigManager + .getConfiguredNetworkWithPassword(config.networkId); + if (configWithPassword.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK) + && configWithPassword.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE) + && !configWithPassword.preSharedKey.startsWith("\"") + && configWithPassword.preSharedKey.length() == 64 + && configWithPassword.preSharedKey.matches("[0-9A-Fa-f]{64}")) { + localLog("Remove SAE type for " + configWithPassword.SSID + " with 64-octet Hex PSK."); + scanResultParamsList + .removeIf(p -> p.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)); + } + } + + /** + * For the transition mode, MFPC should be true, and MFPR should be false, + * see WPA3 SAE specification section 2.3 and 3.3. + */ + private void updateSecurityParamsForTransitionModeIfNecessary( + ScanResult scanResult, SecurityParams params) { + if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE) + && ScanResultUtil.isScanResultForPskSaeTransitionNetwork(scanResult)) { + params.setRequirePmf(false); + } else if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE) + && ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) { + params.setRequirePmf(false); + } + } + + /** + * Update the candidate security params against a scan detail. + * + * @param network the target network. + * @param scanDetail the target scan detail. + */ + private void updateNetworkCandidateSecurityParams( + WifiConfiguration network, ScanDetail scanDetail) { + if (network == null) return; + if (scanDetail == null) return; + + ScanResult scanResult = scanDetail.getScanResult(); + List scanResultParamsList = ScanResultUtil + .generateSecurityParamsListFromScanResult(scanResult); + if (scanResultParamsList == null) return; + // Under some conditions, the legacy type is preferred to have better + // connectivity behaviors, and the auto-upgrade type should be removed. + removeSecurityParamsIfNecessary(network, scanResultParamsList); + SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams( + network, + scanResultParamsList); + if (params == null) return; + updateSecurityParamsForTransitionModeIfNecessary(scanResult, params); + mWifiConfigManager.setNetworkCandidateScanResult( + network.networkId, scanResult, 0, params); + } + + /** + * Using the registered Scorers, choose the best network from the list of Candidate(s). + * The ScanDetailCache is also updated here. + * @param candidates - Candidates to perferm network selection on. + * @return WifiConfiguration - the selected network, or null. + */ + @Nullable + public WifiConfiguration selectNetwork(@NonNull List candidates) { + return selectNetwork(candidates, true); + } + + /** + * Using the registered Scorers, choose the best network from the list of Candidate(s). + * The ScanDetailCache is also updated here. + * @param candidates - Candidates to perferm network selection on. + * @param overrideEnabled If it is allowed to override candidate with User Connect Choice. + * @return WifiConfiguration - the selected network, or null. + */ + @Nullable + public WifiConfiguration selectNetwork(@NonNull List candidates, + boolean overrideEnabled) { + if (candidates == null || candidates.size() == 0) { + return null; + } + WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext, candidates); + final WifiCandidates.CandidateScorer activeScorer = getActiveCandidateScorer(); + // Update the NetworkSelectionStatus in the configs for the current candidates + // This is needed for the legacy user connect choice, at least + Collection> groupedCandidates = + wifiCandidates.getGroupedCandidates(); + for (Collection group : groupedCandidates) { + WifiCandidates.ScoredCandidate choice = activeScorer.scoreCandidates(group); + if (choice == null) continue; + ScanDetail scanDetail = getScanDetailForCandidateKey(choice.candidateKey); + if (scanDetail == null) continue; + WifiConfiguration config = mWifiConfigManager + .getConfiguredNetwork(choice.candidateKey.networkId); + if (config == null) continue; + updateNetworkCandidateSecurityParams(config, scanDetail); + } + + for (Collection group : groupedCandidates) { + for (WifiCandidates.Candidate candidate : group.stream() + .sorted((a, b) -> (b.getScanRssi() - a.getScanRssi())) // decreasing rssi + .collect(Collectors.toList())) { + localLog(candidate.toString()); + } + } + + ArrayMap experimentNetworkSelections = new ArrayMap<>(); // for metrics + + int selectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID; + + // Run all the CandidateScorers + boolean legacyOverrideWanted = true; + for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) { + WifiCandidates.ScoredCandidate choice; + try { + choice = wifiCandidates.choose(candidateScorer); + } catch (RuntimeException e) { + Log.wtf(TAG, "Exception running a CandidateScorer", e); + continue; + } + int networkId = choice.candidateKey == null + ? WifiConfiguration.INVALID_NETWORK_ID + : choice.candidateKey.networkId; + String chooses = " would choose "; + if (candidateScorer == activeScorer) { + chooses = " chooses "; + legacyOverrideWanted = choice.userConnectChoiceOverride; + selectedNetworkId = networkId; + updateChosenPasspointNetwork(choice); + } + String id = candidateScorer.getIdentifier(); + int expid = experimentIdFromIdentifier(id); + localLog(id + chooses + networkId + + " score " + choice.value + "+/-" + choice.err + + " expid " + expid); + experimentNetworkSelections.put(expid, networkId); + } + + // Update metrics about differences in the selections made by various methods + final int activeExperimentId = experimentIdFromIdentifier(activeScorer.getIdentifier()); + for (Map.Entry entry : + experimentNetworkSelections.entrySet()) { + int experimentId = entry.getKey(); + if (experimentId == activeExperimentId) continue; + int thisSelectedNetworkId = entry.getValue(); + mWifiMetrics.logNetworkSelectionDecision(experimentId, activeExperimentId, + selectedNetworkId == thisSelectedNetworkId, + groupedCandidates.size()); + } + + // Get a fresh copy of WifiConfiguration reflecting any scan result updates + WifiConfiguration selectedNetwork = + mWifiConfigManager.getConfiguredNetwork(selectedNetworkId); + if (selectedNetwork != null && legacyOverrideWanted && overrideEnabled + && mUserConnectChoiceOverrideEnabled) { + selectedNetwork = overrideCandidateWithUserConnectChoice(selectedNetwork); + } + if (selectedNetwork != null) { + mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis(); + } + return selectedNetwork; + } + + /** + * Returns the ScanDetail given the candidate key, using the saved list of connectible networks. + */ + private ScanDetail getScanDetailForCandidateKey(WifiCandidates.Key candidateKey) { + if (candidateKey == null) return null; + String bssid = candidateKey.bssid.toString(); + for (Pair pair : mConnectableNetworks) { + if (candidateKey.networkId == pair.second.networkId + && bssid.equals(pair.first.getBSSIDString())) { + return pair.first; + } + } + return null; + } + + private void updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice) { + if (choice.candidateKey == null) { + return; + } + WifiConfiguration config = + mWifiConfigManager.getConfiguredNetwork(choice.candidateKey.networkId); + if (config == null) { + return; + } + if (config.isPasspoint()) { + config.SSID = choice.candidateKey.matchInfo.networkSsid; + mWifiConfigManager.addOrUpdateNetwork(config, config.creatorUid, config.creatorName, + false); + } + } + + private void updateScanDetailCache(List scanDetails) { + for (ScanDetail scanDetail : scanDetails) { + mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail); + } + } + + private static int toProtoNominatorId(@NetworkNominator.NominatorId int nominatorId) { + switch (nominatorId) { + case NetworkNominator.NOMINATOR_ID_SAVED: + return WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED; + case NetworkNominator.NOMINATOR_ID_SUGGESTION: + return WifiMetricsProto.ConnectionEvent.NOMINATOR_SUGGESTION; + case NetworkNominator.NOMINATOR_ID_SCORED: + return WifiMetricsProto.ConnectionEvent.NOMINATOR_EXTERNAL_SCORED; + case NetworkNominator.NOMINATOR_ID_CURRENT: + Log.e(TAG, "Unexpected NOMINATOR_ID_CURRENT", new RuntimeException()); + return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN; + default: + Log.e(TAG, "UnrecognizedNominatorId" + nominatorId); + return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN; + } + } + + private double calculateLastSelectionWeight(int networkId, boolean isMetered) { + if (!mLastSelectionWeightEnabled + || networkId != mWifiConfigManager.getLastSelectedNetwork()) { + return 0.0; + } + double timeDifference = mClock.getElapsedSinceBootMillis() + - mWifiConfigManager.getLastSelectedTimeStamp(); + long millis = TimeUnit.MINUTES.toMillis(isMetered + ? mScoringParams.getLastMeteredSelectionMinutes() + : mScoringParams.getLastUnmeteredSelectionMinutes()); + if (timeDifference >= millis) return 0.0; + double unclipped = 1.0 - (timeDifference / millis); + return Math.min(Math.max(unclipped, 0.0), 1.0); + } + + private WifiCandidates.CandidateScorer getActiveCandidateScorer() { + WifiCandidates.CandidateScorer ans = mCandidateScorers.get(PRESET_CANDIDATE_SCORER_NAME); + int overrideExperimentId = mScoringParams.getExperimentIdentifier(); + if (overrideExperimentId >= MIN_SCORER_EXP_ID) { + for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) { + int expId = experimentIdFromIdentifier(candidateScorer.getIdentifier()); + if (expId == overrideExperimentId) { + ans = candidateScorer; + break; + } + } + } + if (ans == null && PRESET_CANDIDATE_SCORER_NAME != null) { + Log.wtf(TAG, PRESET_CANDIDATE_SCORER_NAME + " is not registered!"); + } + mWifiMetrics.setNetworkSelectorExperimentId(ans == null + ? LEGACY_CANDIDATE_SCORER_EXP_ID + : experimentIdFromIdentifier(ans.getIdentifier())); + return ans; + } + + private int predictThroughput(@NonNull ScanDetail scanDetail) { + if (scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) { + return 0; + } + int channelUtilizationLinkLayerStats = BssLoad.INVALID; + if (mWifiChannelUtilization != null) { + channelUtilizationLinkLayerStats = + mWifiChannelUtilization.getUtilizationRatio( + scanDetail.getScanResult().frequency); + } + ClientModeManager primaryManager = + mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager(); + return mThroughputPredictor.predictThroughput( + primaryManager.getDeviceWiphyCapabilities(), + scanDetail.getScanResult().getWifiStandard(), + scanDetail.getScanResult().channelWidth, + scanDetail.getScanResult().level, + scanDetail.getScanResult().frequency, + scanDetail.getNetworkDetail().getMaxNumberSpatialStreams(), + scanDetail.getNetworkDetail().getChannelUtilization(), + channelUtilizationLinkLayerStats, + mWifiGlobals.isBluetoothConnected(), + scanDetail.getNetworkDetail().getDisabledSubchannelBitmap()); + } + + /** + * Register a network nominator + * + * @param nominator the network nominator to be registered + */ + public void registerNetworkNominator(@NonNull NetworkNominator nominator) { + mNominators.add(Preconditions.checkNotNull(nominator)); + } + + /** + * Register a candidate scorer. + * + * Replaces any existing scorer having the same identifier. + */ + public void registerCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) { + String name = Preconditions.checkNotNull(candidateScorer).getIdentifier(); + if (name != null) { + mCandidateScorers.put(name, candidateScorer); + } + } + + /** + * Unregister a candidate scorer. + */ + public void unregisterCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) { + String name = Preconditions.checkNotNull(candidateScorer).getIdentifier(); + if (name != null) { + mCandidateScorers.remove(name); + } + } + + /** + * Indicate whether or not a configuration is from carrier or privileged app. + * + * @param config The network configuration + * @return true if this configuration is from carrier or privileged app; false otherwise. + */ + public static boolean isFromCarrierOrPrivilegedApp(WifiConfiguration config) { + if (config.fromWifiNetworkSuggestion + && config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { + // Privileged carrier suggestion + return true; + } + if (config.isEphemeral() + && !config.fromWifiNetworkSpecifier + && !config.fromWifiNetworkSuggestion) { + // From ScoredNetworkNominator + return true; + } + return false; + } + + /** + * Derives a numeric experiment identifier from a CandidateScorer's identifier. + * + * @returns a positive number that starts with the decimal digits ID_PREFIX + */ + public static int experimentIdFromIdentifier(String id) { + final int digits = (int) (((long) id.hashCode()) & Integer.MAX_VALUE) % ID_SUFFIX_MOD; + return ID_PREFIX * ID_SUFFIX_MOD + digits; + } + + private static final int ID_SUFFIX_MOD = 1_000_000; + private static final int ID_PREFIX = 42; + private static final int MIN_SCORER_EXP_ID = ID_PREFIX * ID_SUFFIX_MOD; + + WifiNetworkSelector( + Context context, + WifiScoreCard wifiScoreCard, + ScoringParams scoringParams, + WifiConfigManager configManager, + Clock clock, + LocalLog localLog, + WifiMetrics wifiMetrics, + WifiInjector wifiInjector, + ThroughputPredictor throughputPredictor, + WifiChannelUtilization wifiChannelUtilization, + WifiGlobals wifiGlobals, + ScanRequestProxy scanRequestProxy, + WifiNative wifiNative) { + mContext = context; + mWifiScoreCard = wifiScoreCard; + mScoringParams = scoringParams; + mWifiConfigManager = configManager; + mClock = clock; + mLocalLog = localLog; + mWifiMetrics = wifiMetrics; + mWifiInjector = wifiInjector; + mThroughputPredictor = throughputPredictor; + mWifiChannelUtilization = wifiChannelUtilization; + mWifiGlobals = wifiGlobals; + mScanRequestProxy = scanRequestProxy; + mWifiNative = wifiNative; + mDevicePolicyManager = WifiPermissionsUtil.retrieveDevicePolicyManagerFromContext(mContext); + } +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java new file mode 100644 index 000000000..77383f643 --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java @@ -0,0 +1,3565 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi.scanner; + +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.wifi.IScanDataListener; +import android.net.wifi.IWifiScanner; +import android.net.wifi.IWifiScannerListener; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiAnnotations; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiScanner; +import android.net.wifi.WifiScanner.ChannelSpec; +import android.net.wifi.WifiScanner.PnoSettings; +import android.net.wifi.WifiScanner.ScanData; +import android.net.wifi.WifiScanner.ScanSettings; +import android.net.wifi.WifiScanner.WifiBand; +import android.net.wifi.util.ScanResultUtil; +import android.os.BatteryStatsManager; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.WorkSource; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.LocalLog; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.modules.utils.build.SdkLevel; +import com.android.server.wifi.ClientModeImpl; +import com.android.server.wifi.Clock; +import com.android.server.wifi.DeviceConfigFacade; +import com.android.server.wifi.WifiGlobals; +import com.android.server.wifi.WifiInjector; +import com.android.server.wifi.WifiLocalServices; +import com.android.server.wifi.WifiLog; +import com.android.server.wifi.WifiMetrics; +import com.android.server.wifi.WifiNative; +import com.android.server.wifi.WifiThreadRunner; +import com.android.server.wifi.proto.WifiStatsLog; +import com.android.server.wifi.proto.nano.WifiMetricsProto; +import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; +import com.android.server.wifi.util.ArrayUtils; +import com.android.server.wifi.util.LastCallerInfoManager; +import com.android.server.wifi.util.WifiPermissionsUtil; +import com.android.server.wifi.util.WorkSourceUtil; +import com.android.wifi.resources.R; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +public class WifiScanningServiceImpl extends IWifiScanner.Stub { + + private static final String TAG = WifiScanningService.TAG; + private static final boolean DBG = false; + + private static final int UNKNOWN_PID = -1; + + private final LocalLog mLocalLog = new LocalLog(512); + + private WifiLog mLog; + + private void localLog(String message) { + mLocalLog.log(message); + if (isVerboseLoggingEnabled()) { + Log.i(TAG, message, null); + } + } + + private void logw(String message) { + Log.w(TAG, message, null); + mLocalLog.log(message); + } + + private void loge(String message) { + Log.e(TAG, message, null); + mLocalLog.log(message); + } + + private void notifyFailure(IWifiScannerListener listener, int reasonCode, String reason) { + try { + listener.onFailure(reasonCode, reason); + } catch (RemoteException e) { + loge(e + "failed to notify listener for failure"); + } + } + + private boolean isPlatformOrTargetSdkLessThanU(String packageName, int uid) { + if (!SdkLevel.isAtLeastU()) { + return true; + } + return mWifiPermissionsUtil.isTargetSdkLessThan(packageName, + Build.VERSION_CODES.UPSIDE_DOWN_CAKE, uid); + } + + @Override + public Bundle getAvailableChannels(@WifiBand int band, String packageName, + @Nullable String attributionTag, Bundle extras) { + int uid = Binder.getCallingUid(); + if (isPlatformOrTargetSdkLessThanU(packageName, uid)) { + long ident = Binder.clearCallingIdentity(); + try { + enforcePermission(uid, packageName, attributionTag, false, false, false); + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + mWifiPermissionsUtil.enforceNearbyDevicesPermission( + extras.getParcelable(WifiManager.EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE), + true, TAG + " getAvailableChannels"); + } + ChannelSpec[][] channelSpecs = mWifiThreadRunner.call(() -> { + if (mChannelHelper == null) return new ChannelSpec[0][0]; + mChannelHelper.updateChannels(); + return mChannelHelper.getAvailableScanChannels(band); + }, new ChannelSpec[0][0]); + if (channelSpecs == null) { + channelSpecs = new ChannelSpec[0][0]; + } + + ArrayList list = new ArrayList<>(); + for (int i = 0; i < channelSpecs.length; i++) { + for (ChannelSpec channelSpec : channelSpecs[i]) { + list.add(channelSpec.frequency); + } + } + Bundle b = new Bundle(); + b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list); + mLog.trace("getAvailableChannels uid=%").c(Binder.getCallingUid()).flush(); + return b; + } + + /** + * See {@link WifiScanner#isScanning()} + * + * @return true if in ScanningState. + */ + @Override + public boolean isScanning() { + int uid = Binder.getCallingUid(); + if (!mWifiPermissionsUtil.checkCallersHardwareLocationPermission(uid)) { + throw new SecurityException("UID " + uid + + " does not have hardware Location permission"); + } + return mIsScanning; + } + + @Override + public boolean setScanningEnabled(boolean enable, int tid, String packageName) { + int uid = Binder.getCallingUid(); + int msgWhat = enable ? WifiScanner.CMD_ENABLE : WifiScanner.CMD_DISABLE; + try { + enforcePermission(uid, packageName, null, isPrivilegedMessage(msgWhat), + false, false); + } catch (SecurityException e) { + localLog("setScanningEnabled: failed to authorize app: " + packageName + " uid " + + uid); + return false; + } + localLog("enable scan: package " + packageName + " tid " + tid + " enable " + enable); + mWifiThreadRunner.post(() -> { + if (enable) { + Log.i(TAG, + "Received a request to enable scanning, UID = " + Binder.getCallingUid()); + setupScannerImpls(); + } else { + Log.i(TAG, "Received a request to disable scanning, UID = " + uid); + teardownScannerImpls(); + } + Message msg = Message.obtain(); + msg.what = msgWhat; + mBackgroundScanStateMachine.sendMessage(Message.obtain(msg)); + mSingleScanStateMachine.sendMessage(Message.obtain(msg)); + mPnoScanStateMachine.sendMessage(Message.obtain(msg)); + mLastCallerInfoManager.put(WifiManager.API_SCANNING_ENABLED, tid, + Binder.getCallingUid(), Binder.getCallingPid(), packageName, enable); + }); + return true; + } + + @Override + public void registerScanListener(IWifiScannerListener listener, String packageName, + String featureId) { + final int uid = Binder.getCallingUid(); + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_REGISTER_SCAN_LISTENER), + false, false); + } catch (SecurityException e) { + localLog("registerScanListener: failed to authorize app: " + packageName + " uid " + + uid + " AttributionTag " + featureId); + notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + mWifiThreadRunner.post(() -> { + if (mClients.get(listener) != null) { + logw("duplicate client connection: " + uid + ", listener=" + listener + + " AttributionTag " + featureId); + return; + } + final ExternalClientInfo client = new ExternalClientInfo(uid, packageName, + listener); + client.register(); + localLog("register scan listener: " + client + " AttributionTag " + featureId); + logScanRequest("registerScanListener", client, null, null, null); + mSingleScanListeners.addRequest(client, null, null); + client.replySucceeded(); + }); + } + + @Override + public void unregisterScanListener(IWifiScannerListener listener, String packageName, + String featureId) { + int uid = Binder.getCallingUid(); + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_DEREGISTER_SCAN_LISTENER), + true, false); + } catch (SecurityException e) { + localLog("unregisterScanListener: failed to authorize app: " + packageName + " uid " + + uid + " AttributionTag " + featureId); + notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); + if (client == null) { + logw("no client registered: " + uid + ", listener=" + listener + + " AttributionTag " + featureId); + return; + } + mWifiThreadRunner.post(() -> { + logScanRequest("deregisterScanListener", client, null, null, null); + mSingleScanListeners.removeRequest(client); + client.cleanup(); + }); + } + + @Override + public void startBackgroundScan(IWifiScannerListener listener, + WifiScanner.ScanSettings settings, + WorkSource workSource, String packageName, String featureId) { + final int uid = Binder.getCallingUid(); + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_START_BACKGROUND_SCAN), + false, false); + } catch (SecurityException e) { + localLog("startBackgroundScan: failed to authorize app: " + packageName + " uid " + + uid); + notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + mWifiThreadRunner.post(() -> { + ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); + if (client == null) { + client = new ExternalClientInfo(uid, packageName, listener); + client.register(); + } + localLog("start background scan: " + client + " package " + packageName); + Message msg = Message.obtain(); + msg.what = WifiScanner.CMD_START_BACKGROUND_SCAN; + msg.obj = new ScanParams(listener, settings, workSource); + msg.sendingUid = uid; + mBackgroundScanStateMachine.sendMessage(msg); + }); + } + + @Override + public void stopBackgroundScan(IWifiScannerListener listener, String packageName, + String featureId) { + final int uid = Binder.getCallingUid(); + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_STOP_BACKGROUND_SCAN), + true, false); + } catch (SecurityException e) { + localLog("stopBackgroundScan: failed to authorize app: " + packageName + " uid " + + uid); + notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); + if (client == null) { + Log.e(TAG, "listener not found " + listener); + return; + } + localLog("stop background scan: " + client); + Message msg = Message.obtain(); + msg.what = WifiScanner.CMD_STOP_BACKGROUND_SCAN; + msg.obj = new ScanParams(listener, null, null); + msg.sendingUid = uid; + mBackgroundScanStateMachine.sendMessage(msg); + } + + @Override + public boolean getScanResults(String packageName, String featureId) { + final int uid = Binder.getCallingUid(); + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_GET_SCAN_RESULTS), + false, false); + } catch (SecurityException e) { + localLog("getScanResults: failed to authorize app: " + packageName + " uid " + + uid + " AttributionTag " + featureId); + return false; + } + localLog("get scan result: " + packageName + " AttributionTag " + featureId); + mBackgroundScanStateMachine.sendMessage(WifiScanner.CMD_GET_SCAN_RESULTS); + return true; + } + + private static class ScanParams { + public IWifiScannerListener listener; + public WifiScanner.ScanSettings settings; + public WifiScanner.PnoSettings pnoSettings; + public WorkSource workSource; + public String packageName; + public String featureId; + + ScanParams(IWifiScannerListener listener, WifiScanner.ScanSettings settings, + WorkSource workSource) { + this(listener, settings, null, workSource, null, null); + } + + ScanParams(IWifiScannerListener listener, WifiScanner.ScanSettings settings, + WifiScanner.PnoSettings pnoSettings, WorkSource workSource, String packageName, + String featureId) { + this.listener = listener; + this.settings = settings; + this.pnoSettings = pnoSettings; + this.workSource = workSource; + this.packageName = packageName; + this.featureId = featureId; + } + } + + @Override + public void startScan(IWifiScannerListener listener, WifiScanner.ScanSettings settings, + WorkSource workSource, String packageName, String featureId) { + final int uid = Binder.getCallingUid(); + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_START_SINGLE_SCAN), + shouldIgnoreLocationSettingsForSingleScan(settings), + shouldHideFromAppsForSingleScan(settings)); + } catch (SecurityException e) { + localLog("startScan: failed to authorize app: " + packageName + " uid " + + uid + " AttributionTag " + featureId); + notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + mLastCallerInfoManager.put(WifiManager.API_WIFI_SCANNER_START_SCAN, Process.myTid(), + uid, Binder.getCallingPid(), packageName, true); + mWifiThreadRunner.post(() -> { + ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); + if (client == null) { + client = new ExternalClientInfo(uid, packageName, listener); + client.register(); + } + localLog("start scan: " + client + " package " + packageName + " AttributionTag " + + featureId); + Message msg = Message.obtain(); + msg.what = WifiScanner.CMD_START_SINGLE_SCAN; + msg.obj = new ScanParams(listener, settings, workSource); + msg.sendingUid = uid; + mSingleScanStateMachine.sendMessage(msg); + }); + } + + @Override + public void stopScan(IWifiScannerListener listener, String packageName, String featureId) { + int uid = Binder.getCallingUid(); + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_STOP_SINGLE_SCAN), + true, false); + } catch (SecurityException e) { + localLog("stopScan: failed to authorize app: " + packageName + " uid " + + uid + " AttributionTag " + featureId); + notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + mWifiThreadRunner.post(() -> { + ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); + if (client == null) { + Log.e(TAG, "listener not found " + listener); + return; + } + localLog("stop scan: " + client + " AttributionTag " + featureId); + Message msg = Message.obtain(); + msg.what = WifiScanner.CMD_STOP_SINGLE_SCAN; + msg.obj = new ScanParams(listener, null, null); + msg.sendingUid = uid; + mSingleScanStateMachine.sendMessage(msg); + }); + } + + @Override + public List getSingleScanResults(String packageName, String featureId) { + localLog("get single scan result: package " + packageName + + " AttributionTag " + featureId); + final int uid = Binder.getCallingUid(); + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_GET_SCAN_RESULTS), + false, false); + } catch (SecurityException e) { + localLog("getSingleScanResults: failed to authorize app: " + packageName + " uid " + + uid + " AttributionTag " + featureId); + return new ArrayList<>(); + } + return mWifiThreadRunner.call(() -> mSingleScanStateMachine.filterCachedScanResultsByAge(), + new ArrayList()); + } + + + /** + * See {@link WifiScanner#getCachedScanData(Executor, Consumer)}. + */ + @Override + public void getCachedScanData(String packageName, String featureId, + IScanDataListener listener) { + localLog("get single scan result: package " + packageName + + " AttributionTag " + featureId); + final int uid = Binder.getCallingUid(); + Objects.requireNonNull(listener, "listener cannot be null"); + enforcePermission(uid, packageName, featureId, false, false, false); + + mWifiThreadRunner.post(() -> { + try { + listener.onResult(mWifiNative.getCachedScanResultsFromAllClientIfaces()); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage(), e); + } + }); + } + + @Override + public void startPnoScan(IWifiScannerListener listener, WifiScanner.ScanSettings scanSettings, + WifiScanner.PnoSettings pnoSettings, String packageName, String featureId) { + final int uid = Binder.getCallingUid(); + if (listener == null) { + Log.e(TAG, "listener is null"); + return; + } + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_START_PNO_SCAN), + false, false); + } catch (SecurityException e) { + localLog("startPnoScan: failed to authorize app: " + packageName + " uid " + + uid + " AttributionTag " + featureId); + notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + mWifiThreadRunner.post(() -> { + String clientInfoLog = "ClientInfo[uid=" + uid + ", package=" + packageName + ", " + + listener + "]"; + localLog("start pno scan: " + clientInfoLog + " AttributionTag " + featureId); + Message msg = Message.obtain(); + msg.what = WifiScanner.CMD_START_PNO_SCAN; + msg.obj = new ScanParams(listener, scanSettings, pnoSettings, null, packageName, null); + msg.sendingUid = uid; + mPnoScanStateMachine.sendMessage(msg); + }); + } + + @Override + public void stopPnoScan(IWifiScannerListener listener, String packageName, String featureId) { + final int uid = Binder.getCallingUid(); + if (listener == null) { + Log.e(TAG, "listener is null"); + return; + } + try { + enforcePermission(uid, packageName, featureId, + isPrivilegedMessage(WifiScanner.CMD_STOP_PNO_SCAN), + true, false); + } catch (SecurityException e) { + localLog("stopPnoScan: failed to authorize app: " + packageName + " uid " + + uid + " AttributionTag " + featureId); + notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + mWifiThreadRunner.post(() -> { + ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); + if (client == null) { + Log.e(TAG, "listener not found " + listener); + return; + } + localLog("stop pno scan: " + client + " AttributionTag " + featureId); + Message msg = Message.obtain(); + msg.what = WifiScanner.CMD_STOP_PNO_SCAN; + msg.obj = new ScanParams(listener, null, null); + msg.sendingUid = uid; + mPnoScanStateMachine.sendMessage(msg); + }); + } + + @Override + public void enableVerboseLogging(boolean enabled) { + if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) { + return; + } + mVerboseLoggingEnabled.set(enabled); + localLog("enableVerboseLogging: uid=" + Binder.getCallingUid() + " enabled=" + enabled); + } + + private boolean isVerboseLoggingEnabled() { + return mVerboseLoggingEnabled.get(); + } + + private void enforceNetworkStack(int uid) { + mContext.enforcePermission( + Manifest.permission.NETWORK_STACK, + UNKNOWN_PID, uid, + "NetworkStack"); + } + + // Helper method to check if the incoming message is for a privileged request. + private boolean isPrivilegedMessage(int msgWhat) { + boolean isPrivileged = (msgWhat == WifiScanner.CMD_ENABLE + || msgWhat == WifiScanner.CMD_DISABLE + || msgWhat == WifiScanner.CMD_START_PNO_SCAN + || msgWhat == WifiScanner.CMD_STOP_PNO_SCAN); + if (!SdkLevel.isAtLeastT()) { + isPrivileged = isPrivileged || msgWhat == WifiScanner.CMD_REGISTER_SCAN_LISTENER; + } + return isPrivileged; + } + + // Check if we should ignore location settings if this is a single scan request. + private boolean shouldIgnoreLocationSettingsForSingleScan(ScanSettings scanSettings) { + if (scanSettings == null) return false; + return scanSettings.ignoreLocationSettings; + } + + // Check if we should hide this request from app-ops if this is a single scan request. + private boolean shouldHideFromAppsForSingleScan(ScanSettings scanSettings) { + if (scanSettings == null) return false; + return scanSettings.hideFromAppOps; + } + + /** + * Get merged vendor IE byte array from List + */ + public static byte[] getVendorIesBytesFromVendorIesList( + List vendorIesList) { + if (vendorIesList.size() == 0) return null; + + int len = 0; + for (ScanResult.InformationElement ie : vendorIesList) { + if ((len + WifiScanner.WIFI_IE_HEAD_LEN + ie.bytes.length) + > WifiScanner.WIFI_SCANNER_SETTINGS_VENDOR_ELEMENTS_MAX_LEN) { + Log.w(TAG, "Total vendor IE len is larger than max len. Current len:" + len); + break; + } + len += WifiScanner.WIFI_IE_HEAD_LEN + ie.bytes.length; + } + + byte[] vendorIes = new byte[len]; + int index = 0; + for (ScanResult.InformationElement ie : vendorIesList) { + if ((index + WifiScanner.WIFI_IE_HEAD_LEN + ie.bytes.length) + > WifiScanner.WIFI_SCANNER_SETTINGS_VENDOR_ELEMENTS_MAX_LEN) { + break; + } + vendorIes[index] = (byte) ie.id; + vendorIes[index + 1] = (byte) ie.bytes.length; + System.arraycopy(ie.bytes, 0, vendorIes, index + WifiScanner.WIFI_IE_HEAD_LEN, + ie.bytes.length); + index += WifiScanner.WIFI_IE_HEAD_LEN + ie.bytes.length; + } + return vendorIes; + } + + /** + * Enforce the necessary client permissions for WifiScanner. + * If the client has NETWORK_STACK permission, then it can "always" send "any" request. + * If the client has only LOCATION_HARDWARE permission, then it can + * a) Only make scan related requests when location is turned on. + * b) Can never make one of the privileged requests. + * + * @param uid uid of the client + * @param packageName package name of the client + * @param attributionTag The feature in the package of the client + * @param isPrivilegedRequest whether we are checking for a privileged request + * @param shouldIgnoreLocationSettings override to ignore location settings + * @param shouldHideFromApps override to hide request from AppOps + */ + private void enforcePermission(int uid, String packageName, @Nullable String attributionTag, + boolean isPrivilegedRequest, boolean shouldIgnoreLocationSettings, + boolean shouldHideFromApps) { + try { + /** Wifi stack issued requests.*/ + enforceNetworkStack(uid); + } catch (SecurityException e) { + // System-app issued requests + if (isPrivilegedRequest) { + // Privileged message, only requests from clients with NETWORK_STACK allowed! + throw e; + } + mWifiPermissionsUtil.enforceCanAccessScanResultsForWifiScanner(packageName, + attributionTag, uid, shouldIgnoreLocationSettings, shouldHideFromApps); + } + } + + private static final int BASE = Protocol.BASE_WIFI_SCANNER_SERVICE; + + private static final int CMD_SCAN_RESULTS_AVAILABLE = BASE + 0; + private static final int CMD_FULL_SCAN_RESULTS = BASE + 1; + private static final int CMD_SCAN_PAUSED = BASE + 8; + private static final int CMD_SCAN_RESTARTED = BASE + 9; + private static final int CMD_SCAN_FAILED = BASE + 10; + private static final int CMD_PNO_NETWORK_FOUND = BASE + 11; + private static final int CMD_PNO_SCAN_FAILED = BASE + 12; + private static final int CMD_SW_PNO_SCAN = BASE + 14; + + + private final Context mContext; + private final Looper mLooper; + private final WifiThreadRunner mWifiThreadRunner; + private final WifiScannerImpl.WifiScannerImplFactory mScannerImplFactory; + private final ArrayMap mClients; + private final Map mScannerImpls; + + + private final RequestList mSingleScanListeners = new RequestList<>(); + + private ChannelHelper mChannelHelper; + private BackgroundScanScheduler mBackgroundScheduler; + private WifiNative.ScanSettings mPreviousSchedule; + private boolean mIsScanning = false; + + private WifiBackgroundScanStateMachine mBackgroundScanStateMachine; + private WifiSingleScanStateMachine mSingleScanStateMachine; + private WifiPnoScanStateMachine mPnoScanStateMachine; + private final BatteryStatsManager mBatteryStats; + private final AlarmManager mAlarmManager; + private final WifiMetrics mWifiMetrics; + private final Clock mClock; + private final WifiPermissionsUtil mWifiPermissionsUtil; + private final WifiNative mWifiNative; + private final WifiManager mWifiManager; + private final LastCallerInfoManager mLastCallerInfoManager; + private final DeviceConfigFacade mDeviceConfigFacade; + private final WifiGlobals mWifiGlobals; + + private AtomicBoolean mVerboseLoggingEnabled = new AtomicBoolean(false); + + WifiScanningServiceImpl(Context context, Looper looper, + WifiScannerImpl.WifiScannerImplFactory scannerImplFactory, + BatteryStatsManager batteryStats, WifiInjector wifiInjector) { + mContext = context; + mLooper = looper; + mWifiThreadRunner = new WifiThreadRunner(new Handler(looper)); + mScannerImplFactory = scannerImplFactory; + mBatteryStats = batteryStats; + mClients = new ArrayMap<>(); + mScannerImpls = new ArrayMap<>(); + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mWifiMetrics = wifiInjector.getWifiMetrics(); + mClock = wifiInjector.getClock(); + mLog = wifiInjector.makeLog(TAG); + mWifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil(); + mWifiNative = wifiInjector.getWifiNative(); + mDeviceConfigFacade = wifiInjector.getDeviceConfigFacade(); + mWifiGlobals = wifiInjector.getWifiGlobals(); + // Wifi service is always started before other wifi services. So, there is no problem + // obtaining WifiManager in the constructor here. + mWifiManager = mContext.getSystemService(WifiManager.class); + mPreviousSchedule = null; + mLastCallerInfoManager = wifiInjector.getLastCallerInfoManager(); + } + + public void startService() { + mWifiThreadRunner.post(() -> { + WifiLocalServices.addService(WifiScannerInternal.class, new LocalService()); + mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper); + mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper); + mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper); + + mBackgroundScanStateMachine.start(); + mSingleScanStateMachine.start(); + mPnoScanStateMachine.start(); + }); + } + + /** + * Checks if all the channels provided by the new impl is already satisfied by an existing impl. + * + * Note: This only handles the cases where the 2 ifaces are on different chips with + * distinctly different bands supported on both. If there are cases where + * the 2 ifaces support overlapping bands, then we probably need to rework this. + * For example: wlan0 supports 2.4G only, wlan1 supports 2.4G + 5G + DFS. + * In the above example, we should teardown wlan0 impl when wlan1 impl is created + * because wlan1 impl can already handle all the supported bands. + * Ignoring this for now since we don't foresee this requirement in the near future. + */ + private boolean doesAnyExistingImplSatisfy(WifiScannerImpl newImpl) { + for (WifiScannerImpl existingImpl : mScannerImpls.values()) { + if (existingImpl.getChannelHelper().satisfies(newImpl.getChannelHelper())) { + return true; + } + } + return false; + } + + private void setupScannerImpls() { + Set ifaceNames = mWifiNative.getClientInterfaceNames(); + if (ArrayUtils.isEmpty(ifaceNames)) { + loge("Failed to retrieve client interface names"); + return; + } + Set ifaceNamesOfImplsAlreadySetup = mScannerImpls.keySet(); + if (ifaceNames.equals(ifaceNamesOfImplsAlreadySetup)) { + // Scanner Impls already exist for all ifaces (back to back CMD_ENABLE sent?). + Log.i(TAG, "scanner impls already exists"); + return; + } + // set of impls to teardown. + Set ifaceNamesOfImplsToTeardown = new ArraySet<>(ifaceNamesOfImplsAlreadySetup); + ifaceNamesOfImplsToTeardown.removeAll(ifaceNames); + // set of impls to be considered for setup. + Set ifaceNamesOfImplsToSetup = new ArraySet<>(ifaceNames); + ifaceNamesOfImplsToSetup.removeAll(ifaceNamesOfImplsAlreadySetup); + + for (String ifaceName : ifaceNamesOfImplsToTeardown) { + WifiScannerImpl impl = mScannerImpls.remove(ifaceName); + if (impl == null) continue; // should never happen + impl.cleanup(); + Log.i(TAG, "Removed an impl for " + ifaceName); + } + for (String ifaceName : ifaceNamesOfImplsToSetup) { + WifiScannerImpl impl = mScannerImplFactory.create(mContext, mLooper, mClock, ifaceName); + if (impl == null) { + loge("Failed to create scanner impl for " + ifaceName); + continue; + } + // If this new scanner impl does not offer any new bands to scan, then we should + // ignore it. + if (!doesAnyExistingImplSatisfy(impl)) { + mScannerImpls.put(ifaceName, impl); + Log.i(TAG, "Created a new impl for " + ifaceName); + } else { + Log.i(TAG, "All the channels on the new impl for iface " + ifaceName + + " are already satisfied by an existing impl. Skipping.."); + impl.cleanup(); // cleanup the impl before discarding. + } + } + } + + private void teardownScannerImpls() { + for (Map.Entry entry : mScannerImpls.entrySet()) { + WifiScannerImpl impl = entry.getValue(); + String ifaceName = entry.getKey(); + if (impl == null) continue; // should never happen + impl.cleanup(); + Log.i(TAG, "Removed an impl for " + ifaceName); + } + mScannerImpls.clear(); + } + + private WorkSource computeWorkSource(ClientInfo ci, WorkSource requestedWorkSource) { + if (requestedWorkSource != null && !requestedWorkSource.isEmpty()) { + return requestedWorkSource.withoutNames(); + } + + if (ci.getUid() > 0) { + return new WorkSource(ci.getUid()); + } + + // We can't construct a sensible WorkSource because the one supplied to us was empty and + // we don't have a valid UID for the given client. + loge("Unable to compute workSource for client: " + ci + ", requested: " + + requestedWorkSource); + return new WorkSource(); + } + + private class RequestInfo { + final ClientInfo clientInfo; + final WorkSource workSource; + final T settings; + + RequestInfo(ClientInfo clientInfo, WorkSource requestedWorkSource, T settings) { + this.clientInfo = clientInfo; + this.settings = settings; + this.workSource = computeWorkSource(clientInfo, requestedWorkSource); + } + } + + private class RequestList extends ArrayList> { + void addRequest(ClientInfo ci, WorkSource reqworkSource, T settings) { + add(new RequestInfo(ci, reqworkSource, settings)); + } + + T removeRequest(ClientInfo ci) { + T removed = null; + Iterator> iter = iterator(); + while (iter.hasNext()) { + RequestInfo entry = iter.next(); + if (entry.clientInfo == ci) { + removed = entry.settings; + iter.remove(); + } + } + return removed; + } + + Collection getAllSettings() { + ArrayList settingsList = new ArrayList<>(); + Iterator> iter = iterator(); + while (iter.hasNext()) { + RequestInfo entry = iter.next(); + settingsList.add(entry.settings); + } + return settingsList; + } + + Collection getAllSettingsForClient(ClientInfo ci) { + ArrayList settingsList = new ArrayList<>(); + Iterator> iter = iterator(); + while (iter.hasNext()) { + RequestInfo entry = iter.next(); + if (entry.clientInfo == ci) { + settingsList.add(entry.settings); + } + } + return settingsList; + } + + void removeAllForClient(ClientInfo ci) { + Iterator> iter = iterator(); + while (iter.hasNext()) { + RequestInfo entry = iter.next(); + if (entry.clientInfo == ci) { + iter.remove(); + } + } + } + + WorkSource createMergedWorkSource() { + WorkSource mergedSource = new WorkSource(); + for (RequestInfo entry : this) { + mergedSource.add(entry.workSource); + } + return mergedSource; + } + } + + /** + * State machine that holds the state of single scans. Scans should only be active in the + * ScanningState. The pending scans and active scans maps are swapped when entering + * ScanningState. Any requests queued while scanning will be placed in the pending queue and + * executed after transitioning back to IdleState. + */ + class WifiSingleScanStateMachine extends StateMachine { + /** + * Maximum age of results that we return from our cache via + * {@link WifiScanner#getScanResults()}. + * This is currently set to 3 minutes to restore parity with the wpa_supplicant's scan + * result cache expiration policy. (See b/62253332 for details) + */ + @VisibleForTesting + public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 180 * 1000; + /** + * Alarm Tag to use for the delayed indication of emergency scan end. + */ + @VisibleForTesting + public static final String EMERGENCY_SCAN_END_INDICATION_ALARM_TAG = + TAG + "EmergencyScanEnd"; + /** + * Alarm timeout to use for the delayed indication of emergency scan end. + */ + private static final int EMERGENCY_SCAN_END_INDICATION_DELAY_MILLIS = 15_000; + /** + * Alarm listener to use for the delayed indication of emergency scan end. + */ + private final AlarmManager.OnAlarmListener mEmergencyScanEndIndicationListener = + () -> mWifiManager.setEmergencyScanRequestInProgress(false); + + private final DefaultState mDefaultState = new DefaultState(); + private final DriverStartedState mDriverStartedState = new DriverStartedState(); + private final IdleState mIdleState = new IdleState(); + private final ScanningState mScanningState = new ScanningState(); + + private WifiNative.ScanSettings mActiveScanSettings = null; + private RequestList mActiveScans = new RequestList<>(); + private RequestList mPendingScans = new RequestList<>(); + + // Scan results cached from the last full single scan request. + private final List mCachedScanResults = new ArrayList<>(); + + // Tracks scan requests across multiple scanner impls. + private final ScannerImplsTracker mScannerImplsTracker; + + WifiSingleScanStateMachine(Looper looper) { + super("WifiSingleScanStateMachine", looper); + + mScannerImplsTracker = new ScannerImplsTracker(); + + setLogRecSize(128); + setLogOnlyTransitions(false); + + // CHECKSTYLE:OFF IndentationCheck + addState(mDefaultState); + addState(mDriverStartedState, mDefaultState); + addState(mIdleState, mDriverStartedState); + addState(mScanningState, mDriverStartedState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mDefaultState); + } + + /** + * Tracks a single scan request across all the available scanner impls. + * + * a) Initiates the scan using the same ScanSettings across all the available impls. + * b) Waits for all the impls to report the status of the scan request (success or failure). + * c) Calculates a consolidated scan status and sends the results if successful. + * Note: If there are failures on some of the scanner impls, we ignore them since we will + * get some scan results from the other successful impls. We don't declare total scan + * failures, unless all the scanner impls fail. + */ + private final class ScannerImplsTracker { + private final class ScanEventHandler implements WifiNative.ScanEventHandler { + private final String mImplIfaceName; + ScanEventHandler(@NonNull String implIfaceName) { + mImplIfaceName = implIfaceName; + } + + /** + * Called to indicate a change in state for the current scan. + * Will dispatch a corresponding event to the state machine + */ + @Override + public void onScanStatus(int event) { + if (DBG) { + localLog("onScanStatus event received, event=" + event + + ", iface=" + mImplIfaceName); + } + switch (event) { + case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: + case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: + case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: + reportScanStatusForImpl(mImplIfaceName, STATUS_SUCCEEDED, + WifiScanner.REASON_SUCCEEDED); + break; + case WifiNative.WIFI_SCAN_FAILED: + reportScanStatusForImpl(mImplIfaceName, STATUS_FAILED, + WifiScanner.REASON_UNSPECIFIED); + break; + default: + Log.e(TAG, "Unknown scan status event: " + event); + break; + } + } + + /** + * Called for each full scan result if requested + */ + @Override + public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { + if (DBG) localLog("onFullScanResult received on iface " + mImplIfaceName); + reportFullScanResultForImpl(mImplIfaceName, fullScanResult, bucketsScanned); + } + + @Override + public void onScanPaused(ScanData[] scanData) { + // should not happen for single scan + Log.e(TAG, "Got scan paused for single scan"); + } + + @Override + public void onScanRestarted() { + // should not happen for single scan + Log.e(TAG, "Got scan restarted for single scan"); + } + + /** + * Called to indicate a scan failure + */ + @Override + public void onScanRequestFailed(int errorCode) { + reportScanStatusForImpl(mImplIfaceName, STATUS_FAILED, errorCode); + } + } + + private static final int STATUS_PENDING = 0; + private static final int STATUS_SUCCEEDED = 1; + private static final int STATUS_FAILED = 2; + + // Tracks scan status per impl. + Map mStatusPerImpl = new ArrayMap<>(); + + /** + * Triggers a new scan on all the available scanner impls. + * @return true if the scan succeeded on any of the impl, false otherwise. + */ + public boolean startSingleScan(WifiNative.ScanSettings scanSettings) { + mStatusPerImpl.clear(); + boolean anySuccess = false; + for (Map.Entry entry : mScannerImpls.entrySet()) { + String ifaceName = entry.getKey(); + WifiScannerImpl impl = entry.getValue(); + boolean success = impl.startSingleScan( + scanSettings, new ScanEventHandler(ifaceName)); + if (!success) { + Log.e(TAG, "Failed to start single scan on " + ifaceName); + mStatusPerImpl.put(ifaceName, STATUS_FAILED); + continue; + } + mStatusPerImpl.put(ifaceName, STATUS_PENDING); + anySuccess = true; + } + return anySuccess; + } + + /** + * Returns the latest scan results from all the available scanner impls. + * @return Consolidated list of scan results from all the impl. + */ + public @Nullable ScanData getLatestSingleScanResults() { + ScanData consolidatedScanData = null; + for (WifiScannerImpl impl : mScannerImpls.values()) { + Integer ifaceStatus = mStatusPerImpl.get(impl.getIfaceName()); + if (ifaceStatus == null || ifaceStatus != STATUS_SUCCEEDED) { + continue; + } + ScanData scanData = impl.getLatestSingleScanResults(); + if (consolidatedScanData == null) { + consolidatedScanData = new ScanData(scanData); + } else { + consolidatedScanData.addResults(scanData.getResults()); + } + } + return consolidatedScanData; + } + + private void reportFullScanResultForImpl(@NonNull String implIfaceName, + ScanResult fullScanResult, int bucketsScanned) { + Integer status = mStatusPerImpl.get(implIfaceName); + if (status != null && status == STATUS_PENDING) { + sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); + } + } + + private int getConsolidatedStatus() { + boolean anyPending = mStatusPerImpl.values().stream() + .anyMatch(status -> status == STATUS_PENDING); + // at-least one impl status is still pending. + if (anyPending) return STATUS_PENDING; + + boolean anySuccess = mStatusPerImpl.values().stream() + .anyMatch(status -> status == STATUS_SUCCEEDED); + // one success is good enough to declare consolidated success. + if (anySuccess) { + return STATUS_SUCCEEDED; + } else { + // all failed. + return STATUS_FAILED; + } + } + + private void reportScanStatusForImpl(@NonNull String implIfaceName, int newStatus, + int statusCode) { + Integer currentStatus = mStatusPerImpl.get(implIfaceName); + if (currentStatus != null && currentStatus == STATUS_PENDING) { + mStatusPerImpl.put(implIfaceName, newStatus); + } + // Now check if all the scanner impls scan status is available. + int consolidatedStatus = getConsolidatedStatus(); + if (consolidatedStatus == STATUS_SUCCEEDED) { + sendMessage(CMD_SCAN_RESULTS_AVAILABLE); + } else if (consolidatedStatus == STATUS_FAILED) { + sendMessage(CMD_SCAN_FAILED, statusCode); + } + } + } + + /** + * Helper method to handle the scan start message. + */ + private void handleScanStartMessage(ClientInfo ci, ScanParams scanParams) { + if (ci == null) { + logCallback("singleScanInvalidRequest", ci, "null params"); + return; + } + ScanSettings scanSettings = scanParams.settings; + WorkSource workSource = scanParams.workSource; + if (validateScanRequest(ci, scanSettings)) { + if (getCurrentState() == mDefaultState && !scanSettings.ignoreLocationSettings) { + // Reject regular scan requests if scanning is disabled. + ci.replyFailed(WifiScanner.REASON_UNSPECIFIED, "not available"); + ci.cleanup(); + return; + } + mWifiMetrics.incrementOneshotScanCount(); + if ((scanSettings.band & WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY) != 0) { + mWifiMetrics.incrementOneshotScanWithDfsCount(); + } + logScanRequest("addSingleScanRequest", ci, workSource, + scanSettings, null); + ci.replySucceeded(); + + if (scanSettings.ignoreLocationSettings) { + // Inform wifi manager that an emergency scan is in progress (regardless of + // whether scanning is currently enabled or not). This ensures that + // the wifi chip remains on for the duration of this scan. + mWifiManager.setEmergencyScanRequestInProgress(true); + } + + if (getCurrentState() == mScanningState) { + // If there is an active scan that will fulfill the scan request then + // mark this request as an active scan, otherwise mark it pending. + if (activeScanSatisfies(scanSettings)) { + mActiveScans.addRequest(ci, workSource, scanSettings); + } else { + mPendingScans.addRequest(ci, workSource, scanSettings); + } + } else if (getCurrentState() == mIdleState) { + // If were not currently scanning then try to start a scan. Otherwise + // this scan will be scheduled when transitioning back to IdleState + // after finishing the current scan. + mPendingScans.addRequest(ci, workSource, scanSettings); + tryToStartNewScan(); + } else if (getCurrentState() == mDefaultState) { + // If scanning is disabled and the request is for emergency purposes + // (checked above), add to pending list. this scan will be scheduled when + // transitioning to IdleState when wifi manager enables scanning as a part of + // processing WifiManager.setEmergencyScanRequestInProgress(true) + mPendingScans.addRequest(ci, workSource, scanSettings); + } + } else { + logCallback("singleScanInvalidRequest", ci, "bad request"); + ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad request"); + ci.cleanup(); + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1); + } + } + + class DefaultState extends State { + @Override + public void enter() { + mActiveScans.clear(); + mPendingScans.clear(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_ENABLE: + if (mScannerImpls.isEmpty()) { + loge("Failed to start single scan state machine because scanner impl" + + " is null"); + return HANDLED; + } + transitionTo(mIdleState); + return HANDLED; + case WifiScanner.CMD_DISABLE: + transitionTo(mDefaultState); + return HANDLED; + case WifiScanner.CMD_START_SINGLE_SCAN: + ScanParams scanParams = (ScanParams) msg.obj; + if (scanParams != null) { + ClientInfo ci = mClients.get(scanParams.listener); + handleScanStartMessage(ci, scanParams); + } + return HANDLED; + case WifiScanner.CMD_STOP_SINGLE_SCAN: + scanParams = (ScanParams) msg.obj; + if (scanParams != null) { + ClientInfo ci = mClients.get(scanParams.listener); + removeSingleScanRequests(ci); + } + return HANDLED; + case CMD_SCAN_RESULTS_AVAILABLE: + if (DBG) localLog("ignored scan results available event"); + return HANDLED; + case CMD_FULL_SCAN_RESULTS: + if (DBG) localLog("ignored full scan result event"); + return HANDLED; + case WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS: + // Should not handled here. + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + /** + * State representing when the driver is running. This state is not meant to be transitioned + * directly, but is instead intended as a parent state of ScanningState and IdleState + * to hold common functionality and handle cleaning up scans when the driver is shut down. + */ + class DriverStartedState extends State { + @Override + public void exit() { + // clear scan results when scan mode is not active + mCachedScanResults.clear(); + + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED, + mPendingScans.size()); + sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, + "Scan was interrupted"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_ENABLE: + // Ignore if we're already in driver loaded state. + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class IdleState extends State { + @Override + public void enter() { + tryToStartNewScan(); + } + + @Override + public boolean processMessage(Message msg) { + return NOT_HANDLED; + } + } + + class ScanningState extends State { + private WorkSource mScanWorkSource; + + @Override + public void enter() { + mScanWorkSource = mActiveScans.createMergedWorkSource(); + mBatteryStats.reportWifiScanStartedFromSource(mScanWorkSource); + Pair uidsAndTags = + WorkSourceUtil.getUidsAndTagsForWs(mScanWorkSource); + WifiStatsLog.write(WifiStatsLog.WIFI_SCAN_STATE_CHANGED, + uidsAndTags.first, uidsAndTags.second, + WifiStatsLog.WIFI_SCAN_STATE_CHANGED__STATE__ON); + mIsScanning = true; + } + + @Override + public void exit() { + mActiveScanSettings = null; + mBatteryStats.reportWifiScanStoppedFromSource(mScanWorkSource); + Pair uidsAndTags = + WorkSourceUtil.getUidsAndTagsForWs(mScanWorkSource); + WifiStatsLog.write(WifiStatsLog.WIFI_SCAN_STATE_CHANGED, + uidsAndTags.first, uidsAndTags.second, + WifiStatsLog.WIFI_SCAN_STATE_CHANGED__STATE__OFF); + mIsScanning = false; + + // if any scans are still active (never got results available then indicate failure) + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_UNKNOWN, + mActiveScans.size()); + sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, + "Scan was interrupted"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_SCAN_RESULTS_AVAILABLE: + ScanData latestScanResults = + mScannerImplsTracker.getLatestSingleScanResults(); + if (latestScanResults != null) { + handleScanResults(latestScanResults); + } else { + Log.e(TAG, "latest scan results null unexpectedly"); + } + transitionTo(mIdleState); + return HANDLED; + case CMD_FULL_SCAN_RESULTS: + reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); + return HANDLED; + case CMD_SCAN_FAILED: + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size()); + mWifiMetrics.getScanMetrics().logScanFailed( + WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE); + sendOpFailedToAllAndClear(mActiveScans, msg.arg1, + scanErrorCodeToDescriptionString(msg.arg1)); + transitionTo(mIdleState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + boolean validateScanType(@WifiAnnotations.ScanType int type) { + return (type == WifiScanner.SCAN_TYPE_LOW_LATENCY + || type == WifiScanner.SCAN_TYPE_LOW_POWER + || type == WifiScanner.SCAN_TYPE_HIGH_ACCURACY); + } + + boolean validateScanRequest(ClientInfo ci, ScanSettings settings) { + if (ci == null) { + Log.d(TAG, "Failing single scan request ClientInfo not found " + ci); + return false; + } + if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) { + if (settings.channels == null || settings.channels.length == 0) { + Log.d(TAG, "Failing single scan because channel list was empty"); + return false; + } + } + if (!validateScanType(settings.type)) { + Log.e(TAG, "Invalid scan type " + settings.type); + return false; + } + if (mContext.checkPermission( + Manifest.permission.NETWORK_STACK, UNKNOWN_PID, ci.getUid()) + == PERMISSION_DENIED) { + if (!ArrayUtils.isEmpty(settings.hiddenNetworks)) { + Log.e(TAG, "Failing single scan because app " + ci.getUid() + + " does not have permission to set hidden networks"); + return false; + } + if (settings.type != WifiScanner.SCAN_TYPE_LOW_LATENCY) { + Log.e(TAG, "Failing single scan because app " + ci.getUid() + + " does not have permission to set type"); + return false; + } + } + return true; + } + + // We can coalesce a LOW_POWER/LOW_LATENCY scan request into an ongoing HIGH_ACCURACY + // scan request. But, we can't coalesce a HIGH_ACCURACY scan request into an ongoing + // LOW_POWER/LOW_LATENCY scan request. + boolean activeScanTypeSatisfies(int requestScanType) { + switch(mActiveScanSettings.scanType) { + case WifiScanner.SCAN_TYPE_LOW_LATENCY: + case WifiScanner.SCAN_TYPE_LOW_POWER: + return requestScanType != WifiScanner.SCAN_TYPE_HIGH_ACCURACY; + case WifiScanner.SCAN_TYPE_HIGH_ACCURACY: + return true; + default: + // This should never happen because we've validated the incoming type in + // |validateScanType|. + throw new IllegalArgumentException("Invalid scan type " + + mActiveScanSettings.scanType); + } + } + + // If there is a HIGH_ACCURACY scan request among the requests being merged, the merged + // scan type should be HIGH_ACCURACY. + int mergeScanTypes(int existingScanType, int newScanType) { + switch(existingScanType) { + case WifiScanner.SCAN_TYPE_LOW_LATENCY: + case WifiScanner.SCAN_TYPE_LOW_POWER: + return newScanType; + case WifiScanner.SCAN_TYPE_HIGH_ACCURACY: + return existingScanType; + default: + // This should never happen because we've validated the incoming type in + // |validateScanType|. + throw new IllegalArgumentException("Invalid scan type " + existingScanType); + } + } + + private boolean mergeRnrSetting(boolean enable6GhzRnr, ScanSettings scanSettings) { + if (!SdkLevel.isAtLeastS()) { + return false; + } + if (enable6GhzRnr) { + return true; + } + int rnrSetting = scanSettings.getRnrSetting(); + if (rnrSetting == WifiScanner.WIFI_RNR_ENABLED) { + return true; + } + if (rnrSetting == WifiScanner.WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED) { + return ChannelHelper.is6GhzBandIncluded(scanSettings.band); + } + return false; + } + + private void mergeVendorIes(List existingVendorIes, + ScanSettings scanSettings) { + if (!SdkLevel.isAtLeastU()) { + return; + } + for (ScanResult.InformationElement ie : scanSettings.getVendorIes()) { + if (!existingVendorIes.contains(ie)) { + existingVendorIes.add(ie); + } + } + } + + boolean activeScanSatisfies(ScanSettings settings) { + if (mActiveScanSettings == null) { + return false; + } + + if (!activeScanTypeSatisfies(settings.type)) { + return false; + } + + // there is always one bucket for a single scan + WifiNative.BucketSettings activeBucket = mActiveScanSettings.buckets[0]; + + // validate that all requested channels are being scanned + ChannelCollection activeChannels = mChannelHelper.createChannelCollection(); + activeChannels.addChannels(activeBucket); + if (!activeChannels.containsSettings(settings)) { + return false; + } + + // if the request is for a full scan, but there is no ongoing full scan + if ((settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0 + && (activeBucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) + == 0) { + return false; + } + + if (!ArrayUtils.isEmpty(settings.hiddenNetworks)) { + if (ArrayUtils.isEmpty(mActiveScanSettings.hiddenNetworks)) { + return false; + } + List activeHiddenNetworks = new ArrayList<>(); + for (WifiNative.HiddenNetwork hiddenNetwork : mActiveScanSettings.hiddenNetworks) { + activeHiddenNetworks.add(hiddenNetwork); + } + for (ScanSettings.HiddenNetwork hiddenNetwork : settings.hiddenNetworks) { + WifiNative.HiddenNetwork nativeHiddenNetwork = new WifiNative.HiddenNetwork(); + nativeHiddenNetwork.ssid = hiddenNetwork.ssid; + if (!activeHiddenNetworks.contains(nativeHiddenNetwork)) { + return false; + } + } + } + + return true; + } + + void removeSingleScanRequests(ClientInfo ci) { + if (ci != null) { + logScanRequest("removeSingleScanRequests", ci, null, null, null); + mPendingScans.removeAllForClient(ci); + mActiveScans.removeAllForClient(ci); + } + } + + void tryToStartNewScan() { + if (mPendingScans.size() == 0) { // no pending requests + return; + } + mChannelHelper.updateChannels(); + // TODO move merging logic to a scheduler + WifiNative.ScanSettings settings = new WifiNative.ScanSettings(); + settings.num_buckets = 1; + WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings(); + bucketSettings.bucket = 0; + bucketSettings.period_ms = 0; + bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; + + ChannelCollection channels = mChannelHelper.createChannelCollection(); + WifiScanner.ChannelSpec[][] available6GhzChannels = + mChannelHelper.getAvailableScanChannels(WifiScanner.WIFI_BAND_6_GHZ); + boolean are6GhzChannelsAvailable = available6GhzChannels.length > 0 + && available6GhzChannels[0].length > 0; + List hiddenNetworkList = new ArrayList<>(); + List vendorIesList = new ArrayList<>(); + for (RequestInfo entry : mPendingScans) { + settings.scanType = mergeScanTypes(settings.scanType, entry.settings.type); + if (are6GhzChannelsAvailable) { + settings.enable6GhzRnr = mergeRnrSetting( + settings.enable6GhzRnr, entry.settings); + } else { + settings.enable6GhzRnr = false; + } + channels.addChannels(entry.settings); + for (ScanSettings.HiddenNetwork srcNetwork : entry.settings.hiddenNetworks) { + WifiNative.HiddenNetwork hiddenNetwork = new WifiNative.HiddenNetwork(); + hiddenNetwork.ssid = srcNetwork.ssid; + hiddenNetworkList.add(hiddenNetwork); + } + mergeVendorIes(vendorIesList, entry.settings); + if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) + != 0) { + bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; + } + + if (entry.clientInfo != null) { + mWifiMetrics.getScanMetrics().setClientUid(entry.clientInfo.mUid); + } + mWifiMetrics.getScanMetrics().setWorkSource(entry.workSource); + } + + if (hiddenNetworkList.size() > 0) { + settings.hiddenNetworks = new WifiNative.HiddenNetwork[hiddenNetworkList.size()]; + int numHiddenNetworks = 0; + for (WifiNative.HiddenNetwork hiddenNetwork : hiddenNetworkList) { + settings.hiddenNetworks[numHiddenNetworks++] = hiddenNetwork; + } + } + settings.vendorIes = getVendorIesBytesFromVendorIesList(vendorIesList); + + channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE); + settings.buckets = new WifiNative.BucketSettings[] {bucketSettings}; + + if (mScannerImplsTracker.startSingleScan(settings)) { + mWifiMetrics.getScanMetrics().logScanStarted( + WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE); + + // store the active scan settings + mActiveScanSettings = settings; + // swap pending and active scan requests + RequestList tmp = mActiveScans; + mActiveScans = mPendingScans; + mPendingScans = tmp; + // make sure that the pending list is clear + mPendingScans.clear(); + transitionTo(mScanningState); + } else { + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mPendingScans.size()); + mWifiMetrics.getScanMetrics().logScanFailedToStart( + WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE); + + // notify and cancel failed scans + sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, + "Failed to start single scan"); + } + } + + void sendOpFailedToAllAndClear(RequestList clientHandlers, int reason, + String description) { + for (RequestInfo entry : clientHandlers) { + logCallback("singleScanFailed", entry.clientInfo, + "reason=" + reason + ", " + description); + try { + entry.clientInfo.mListener.onFailure(reason, description); + } catch (Exception e) { + loge("Failed to call onFailure: " + entry.clientInfo); + } + entry.clientInfo.unregister(); + } + clientHandlers.clear(); + } + + void reportFullScanResult(@NonNull ScanResult result, int bucketsScanned) { + for (RequestInfo entry : mActiveScans) { + if (ScanScheduleUtil.shouldReportFullScanResultForSettings(mChannelHelper, + result, bucketsScanned, entry.settings, -1)) { + entry.clientInfo.reportEvent((listener) -> { + try { + listener.onFullResult(result); + } catch (RemoteException e) { + loge("Failed to call onFullResult: " + entry.clientInfo); + } + }); + } + } + + for (RequestInfo entry : mSingleScanListeners) { + entry.clientInfo.reportEvent((listener) -> { + try { + listener.onFullResult(result); + } catch (RemoteException e) { + loge("Failed to call onFullResult: " + entry.clientInfo); + } + }); + } + } + + void reportScanResults(@NonNull ScanData results) { + if (results != null && results.getResults() != null) { + if (results.getResults().length > 0) { + mWifiMetrics.incrementNonEmptyScanResultCount(); + } else { + mWifiMetrics.incrementEmptyScanResultCount(); + } + } + ScanData[] allResults = new ScanData[] {results}; + for (RequestInfo entry : mActiveScans) { + ScanData[] resultsToDeliver = ScanScheduleUtil.filterResultsForSettings( + mChannelHelper, allResults, entry.settings, -1); + logCallback("singleScanResults", entry.clientInfo, + describeForLog(resultsToDeliver)); + entry.clientInfo.reportEvent((listener) -> { + try { + listener.onResults(resultsToDeliver); + // make sure the handler is removed + listener.onSingleScanCompleted(); + } catch (RemoteException e) { + loge("Failed to call onResult: " + entry.clientInfo); + } + }); + } + + for (RequestInfo entry : mSingleScanListeners) { + logCallback("singleScanResults", entry.clientInfo, + describeForLog(allResults)); + entry.clientInfo.reportEvent((listener) -> { + try { + listener.onResults(allResults); + } catch (RemoteException e) { + loge("Failed to call onResult: " + entry.clientInfo); + } + }); + } + } + + void handleScanResults(@NonNull ScanData results) { + mWifiMetrics.getScanMetrics().logScanSucceeded( + WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, results.getResults().length); + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_SUCCESS, mActiveScans.size()); + reportScanResults(results); + // Cache full band (with DFS or not) scan results. + if (WifiScanner.isFullBandScan(results.getScannedBandsInternal(), true)) { + mCachedScanResults.clear(); + mCachedScanResults.addAll(Arrays.asList(results.getResults())); + } + if (mActiveScans.stream().anyMatch(rI -> rI.settings.ignoreLocationSettings)) { + // We were processing an emergency scan, post an alarm to inform WifiManager the + // end of that scan processing. If another scan is processed before the alarm fires, + // this timer is restarted (AlarmManager.set() using the same listener resets the + // timer). This delayed indication of emergency scan end prevents + // quick wifi toggle on/off if there is a burst of emergency scans when wifi is off. + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mClock.getElapsedSinceBootMillis() + + EMERGENCY_SCAN_END_INDICATION_DELAY_MILLIS, + EMERGENCY_SCAN_END_INDICATION_ALARM_TAG, + mEmergencyScanEndIndicationListener, getHandler()); + } + for (RequestInfo entry : mActiveScans) { + entry.clientInfo.unregister(); + } + mActiveScans.clear(); + } + + List getCachedScanResultsAsList() { + return mCachedScanResults; + } + + /** + * Filter out any scan results that are older than + * {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}. + * + * @return Filtered list of scan results. + */ + public List filterCachedScanResultsByAge() { + // Using ScanResult.timestamp here to ensure that we use the same fields as + // WificondScannerImpl for filtering stale results. + long currentTimeInMillis = mClock.getElapsedSinceBootMillis(); + return mCachedScanResults.stream() + .filter(scanResult + -> ((currentTimeInMillis - (scanResult.timestamp / 1000)) + < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS)).collect(Collectors.toList()); + } + } + + // TODO(b/71855918): Remove this bg scan state machine and its dependencies. + // Note: bgscan will not support multiple scanner impls (will pick any). + class WifiBackgroundScanStateMachine extends StateMachine { + + private final DefaultState mDefaultState = new DefaultState(); + private final StartedState mStartedState = new StartedState(); + private final PausedState mPausedState = new PausedState(); + + private final RequestList mActiveBackgroundScans = new RequestList<>(); + + private WifiScannerImpl mScannerImpl; + + WifiBackgroundScanStateMachine(Looper looper) { + super("WifiBackgroundScanStateMachine", looper); + + setLogRecSize(512); + setLogOnlyTransitions(false); + + // CHECKSTYLE:OFF IndentationCheck + addState(mDefaultState); + addState(mStartedState, mDefaultState); + addState(mPausedState, mDefaultState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mDefaultState); + } + + public Collection getBackgroundScanSettings(ClientInfo ci) { + return mActiveBackgroundScans.getAllSettingsForClient(ci); + } + + public void removeBackgroundScanSettings(ClientInfo ci) { + mActiveBackgroundScans.removeAllForClient(ci); + updateSchedule(); + } + + private final class ScanEventHandler implements WifiNative.ScanEventHandler { + private final String mImplIfaceName; + + ScanEventHandler(@NonNull String implIfaceName) { + mImplIfaceName = implIfaceName; + } + + @Override + public void onScanStatus(int event) { + if (DBG) localLog("onScanStatus event received, event=" + event); + switch (event) { + case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: + case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: + case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: + sendMessage(CMD_SCAN_RESULTS_AVAILABLE); + break; + case WifiNative.WIFI_SCAN_FAILED: + sendMessage(CMD_SCAN_FAILED, WifiScanner.REASON_UNSPECIFIED); + break; + default: + Log.e(TAG, "Unknown scan status event: " + event); + break; + } + } + + @Override + public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { + if (DBG) localLog("onFullScanResult received"); + sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); + } + + @Override + public void onScanPaused(ScanData[] scanData) { + if (DBG) localLog("onScanPaused received"); + sendMessage(CMD_SCAN_PAUSED, scanData); + } + + @Override + public void onScanRestarted() { + if (DBG) localLog("onScanRestarted received"); + sendMessage(CMD_SCAN_RESTARTED); + } + + /** + * Called to indicate a scan failure + */ + @Override + public void onScanRequestFailed(int errorCode) { + sendMessage(CMD_SCAN_FAILED, errorCode); + } + } + + class DefaultState extends State { + @Override + public void enter() { + if (DBG) localLog("DefaultState"); + mActiveBackgroundScans.clear(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_ENABLE: + if (mScannerImpls.isEmpty()) { + loge("Failed to start bgscan scan state machine because scanner impl" + + " is null"); + return HANDLED; + } + // Pick any impl available and stick to it until disable. + mScannerImpl = mScannerImpls.entrySet().iterator().next().getValue(); + mChannelHelper = mScannerImpl.getChannelHelper(); + + mBackgroundScheduler = new BackgroundScanScheduler(mChannelHelper); + + WifiNative.ScanCapabilities capabilities = + new WifiNative.ScanCapabilities(); + if (!mScannerImpl.getScanCapabilities(capabilities)) { + loge("could not get scan capabilities"); + return HANDLED; + } + if (capabilities.max_scan_buckets <= 0) { + loge("invalid max buckets in scan capabilities " + + capabilities.max_scan_buckets); + return HANDLED; + } + mBackgroundScheduler.setMaxBuckets(capabilities.max_scan_buckets); + mBackgroundScheduler.setMaxApPerScan(capabilities.max_ap_cache_per_scan); + + Log.i(TAG, "wifi driver loaded with scan capabilities: " + + "max buckets=" + capabilities.max_scan_buckets); + + transitionTo(mStartedState); + return HANDLED; + case WifiScanner.CMD_DISABLE: + Log.i(TAG, "wifi driver unloaded"); + transitionTo(mDefaultState); + break; + case WifiScanner.CMD_START_BACKGROUND_SCAN: + case WifiScanner.CMD_STOP_BACKGROUND_SCAN: + case WifiScanner.CMD_START_SINGLE_SCAN: + case WifiScanner.CMD_STOP_SINGLE_SCAN: + case WifiScanner.CMD_GET_SCAN_RESULTS: + ScanParams scanParams = (ScanParams) msg.obj; + ClientInfo ci = mClients.get(scanParams.listener); + if (ci == null) { + loge("ClientInfo is null"); + break; + } + ci.replyFailed(WifiScanner.REASON_UNSPECIFIED, "not available"); + break; + + case CMD_SCAN_RESULTS_AVAILABLE: + if (DBG) localLog("ignored scan results available event"); + break; + + case CMD_FULL_SCAN_RESULTS: + if (DBG) localLog("ignored full scan result event"); + break; + + default: + break; + } + + return HANDLED; + } + } + + class StartedState extends State { + + @Override + public void enter() { + if (DBG) localLog("StartedState"); + if (mScannerImpl == null) { + // should never happen + Log.wtf(TAG, "Scanner impl unexpectedly null"); + transitionTo(mDefaultState); + } + } + + @Override + public void exit() { + sendBackgroundScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); + mScannerImpl = null; // reset impl + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_ENABLE: + Log.e(TAG, "wifi driver loaded received while already loaded"); + // Ignore if we're already in driver loaded state. + return HANDLED; + case WifiScanner.CMD_DISABLE: + return NOT_HANDLED; + case WifiScanner.CMD_START_BACKGROUND_SCAN: { + ScanParams scanParams = (ScanParams) msg.obj; + mWifiMetrics.incrementBackgroundScanCount(); + ClientInfo ci = mClients.get(scanParams.listener); + if (ci == null) { + loge("ClientInfo is null"); + return HANDLED; + } + if (scanParams.settings == null) { + loge("params null"); + return HANDLED; + } + if (addBackgroundScanRequest(ci, msg.arg2, scanParams.settings, + scanParams.workSource)) { + ci.replySucceeded(); + } else { + ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad request"); + } + break; + } + case WifiScanner.CMD_STOP_BACKGROUND_SCAN: + ScanParams scanParams = (ScanParams) msg.obj; + ClientInfo ci = mClients.get(scanParams.listener); + removeBackgroundScanRequest(ci); + break; + case WifiScanner.CMD_GET_SCAN_RESULTS: + reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); + break; + case CMD_SCAN_RESULTS_AVAILABLE: + WifiScanner.ScanData[] results = mScannerImpl.getLatestBatchedScanResults( + true); + mWifiMetrics.getScanMetrics().logScanSucceeded( + WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND, + results != null ? results.length : 0); + reportScanResults(results); + break; + case CMD_FULL_SCAN_RESULTS: + reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); + break; + case CMD_SCAN_PAUSED: + reportScanResults((ScanData[]) msg.obj); + transitionTo(mPausedState); + break; + case CMD_SCAN_FAILED: + mWifiMetrics.getScanMetrics().logScanFailed( + WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND); + Log.e(TAG, "WifiScanner background scan gave CMD_SCAN_FAILED"); + sendBackgroundScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "Background Scan failed"); + break; + default: + return NOT_HANDLED; + } + + return HANDLED; + } + } + + class PausedState extends State { + @Override + public void enter() { + if (DBG) localLog("PausedState"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_SCAN_RESTARTED: + transitionTo(mStartedState); + break; + default: + deferMessage(msg); + break; + } + return HANDLED; + } + } + + private boolean addBackgroundScanRequest(ClientInfo ci, int handler, + ScanSettings settings, WorkSource workSource) { + if (ci == null) { + Log.d(TAG, "Failing scan request ClientInfo not found " + handler); + return false; + } + + if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) { + loge("Failing scan request because periodInMs is " + settings.periodInMs + + ", min scan period is: " + WifiScanner.MIN_SCAN_PERIOD_MS); + return false; + } + + if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED && settings.channels == null) { + loge("Channels was null with unspecified band"); + return false; + } + + if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED + && settings.channels.length == 0) { + loge("No channels specified"); + return false; + } + + int minSupportedPeriodMs = mChannelHelper.estimateScanDuration(settings); + if (settings.periodInMs < minSupportedPeriodMs) { + loge("Failing scan request because minSupportedPeriodMs is " + + minSupportedPeriodMs + " but the request wants " + settings.periodInMs); + return false; + } + + // check truncated binary exponential back off scan settings + if (settings.maxPeriodInMs != 0 && settings.maxPeriodInMs != settings.periodInMs) { + if (settings.maxPeriodInMs < settings.periodInMs) { + loge("Failing scan request because maxPeriodInMs is " + settings.maxPeriodInMs + + " but less than periodInMs " + settings.periodInMs); + return false; + } + if (settings.maxPeriodInMs > WifiScanner.MAX_SCAN_PERIOD_MS) { + loge("Failing scan request because maxSupportedPeriodMs is " + + WifiScanner.MAX_SCAN_PERIOD_MS + " but the request wants " + + settings.maxPeriodInMs); + return false; + } + if (settings.stepCount < 1) { + loge("Failing scan request because stepCount is " + settings.stepCount + + " which is less than 1"); + return false; + } + } + + logScanRequest("addBackgroundScanRequest", ci, null, settings, null); + mWifiMetrics.getScanMetrics().setClientUid(ci.mUid); + mWifiMetrics.getScanMetrics().setWorkSource(workSource); + mActiveBackgroundScans.addRequest(ci, workSource, settings); + + if (updateSchedule()) { + return true; + } else { + mActiveBackgroundScans.removeRequest(ci); + localLog("Failing scan request because failed to reset scan"); + return false; + } + } + + private boolean updateSchedule() { + if (mChannelHelper == null || mBackgroundScheduler == null || mScannerImpl == null) { + loge("Failed to update schedule because WifiScanningService is not initialized"); + return false; + } + mChannelHelper.updateChannels(); + Collection settings = mActiveBackgroundScans.getAllSettings(); + + mBackgroundScheduler.updateSchedule(settings); + WifiNative.ScanSettings schedule = mBackgroundScheduler.getSchedule(); + + if (ScanScheduleUtil.scheduleEquals(mPreviousSchedule, schedule)) { + if (DBG) Log.d(TAG, "schedule updated with no change"); + return true; + } + + mPreviousSchedule = schedule; + + if (schedule.num_buckets == 0) { + mScannerImpl.stopBatchedScan(); + if (DBG) Log.d(TAG, "scan stopped"); + return true; + } else { + localLog("starting scan: " + + "base period=" + schedule.base_period_ms + + ", max ap per scan=" + schedule.max_ap_per_scan + + ", batched scans=" + schedule.report_threshold_num_scans); + for (int b = 0; b < schedule.num_buckets; b++) { + WifiNative.BucketSettings bucket = schedule.buckets[b]; + localLog("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" + + "[" + bucket.report_events + "]: " + + ChannelHelper.toString(bucket)); + } + + if (mScannerImpl.startBatchedScan(schedule, + new ScanEventHandler(mScannerImpl.getIfaceName()))) { + if (DBG) { + Log.d(TAG, "scan restarted with " + schedule.num_buckets + + " bucket(s) and base period: " + schedule.base_period_ms); + } + mWifiMetrics.getScanMetrics().logScanStarted( + WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND); + return true; + } else { + mPreviousSchedule = null; + loge("error starting scan: " + + "base period=" + schedule.base_period_ms + + ", max ap per scan=" + schedule.max_ap_per_scan + + ", batched scans=" + schedule.report_threshold_num_scans); + for (int b = 0; b < schedule.num_buckets; b++) { + WifiNative.BucketSettings bucket = schedule.buckets[b]; + loge("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" + + "[" + bucket.report_events + "]: " + + ChannelHelper.toString(bucket)); + } + mWifiMetrics.getScanMetrics().logScanFailedToStart( + WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND); + return false; + } + } + } + + private void removeBackgroundScanRequest(ClientInfo ci) { + if (ci != null) { + ScanSettings settings = mActiveBackgroundScans.removeRequest(ci); + logScanRequest("removeBackgroundScanRequest", ci, null, settings, null); + updateSchedule(); + } + } + + private void reportFullScanResult(ScanResult result, int bucketsScanned) { + for (RequestInfo entry : mActiveBackgroundScans) { + ClientInfo ci = entry.clientInfo; + ScanSettings settings = entry.settings; + if (mBackgroundScheduler.shouldReportFullScanResultForSettings( + result, bucketsScanned, settings)) { + ScanResult newResult = new ScanResult(result); + if (result.informationElements != null) { + newResult.informationElements = result.informationElements.clone(); + } + else { + newResult.informationElements = null; + } + entry.clientInfo.reportEvent((listener) -> { + try { + listener.onFullResult(newResult); + } catch (RemoteException e) { + loge("Failed to call onFullResult: " + ci); + } + }); + } + } + } + + private void reportScanResults(ScanData[] results) { + if (results == null) { + Log.d(TAG,"The results is null, nothing to report."); + return; + } + for (ScanData result : results) { + if (result != null && result.getResults() != null) { + if (result.getResults().length > 0) { + mWifiMetrics.incrementNonEmptyScanResultCount(); + } else { + mWifiMetrics.incrementEmptyScanResultCount(); + } + } + } + for (RequestInfo entry : mActiveBackgroundScans) { + ClientInfo ci = entry.clientInfo; + ScanSettings settings = entry.settings; + ScanData[] resultsToDeliver = + mBackgroundScheduler.filterResultsForSettings(results, settings); + if (resultsToDeliver != null) { + logCallback("backgroundScanResults", ci, + describeForLog(resultsToDeliver)); + entry.clientInfo.reportEvent((listener) -> { + try { + listener.onResults(resultsToDeliver); + } catch (RemoteException e) { + loge("Failed to call onFullResult: " + ci); + } + }); + } + } + } + + private void sendBackgroundScanFailedToAllAndClear(int reason, String description) { + for (RequestInfo entry : mActiveBackgroundScans) { + ClientInfo ci = entry.clientInfo; + entry.clientInfo.reportEvent((listener) -> { + try { + listener.onFailure(reason, description); + } catch (RemoteException e) { + loge("Failed to call onFullResult: " + ci); + } + }); + } + mActiveBackgroundScans.clear(); + } + } + + /** + * PNO scan state machine has 5 states: + * -Default State + * -Started State + * -Hw Pno Scan state + * -Single Scan state + * + * These are the main state transitions: + * 1. Start at |Default State| + * 2. Move to |Started State| when we get the |WIFI_SCAN_AVAILABLE| broadcast from WifiManager. + * 3. When a new PNO scan request comes in: + * a.1. Switch to |Hw Pno Scan state| when the device supports HW PNO + * (This could either be HAL based ePNO or wificond based PNO). + * a.2. In |Hw Pno Scan state| when PNO scan results are received, check if the result + * contains IE (information elements). If yes, send the results to the client, else + * switch to |Single Scan state| and send the result to the client when the scan result + * is obtained. + * + * Note: PNO scans only work for a single client today. We don't have support in HW to support + * multiple requests at the same time, so will need non-trivial changes to support (if at all + * possible) in WifiScanningService. + */ + class WifiPnoScanStateMachine extends StateMachine { + + private final DefaultState mDefaultState = new DefaultState(); + private final StartedState mStartedState = new StartedState(); + private final HwPnoScanState mHwPnoScanState = new HwPnoScanState(); + private final SwPnoScanState mSwPnoScanState = new SwPnoScanState(); + private final SingleScanState mSingleScanState = new SingleScanState(); + private InternalClientInfo mInternalClientInfo; + + private final RequestList> mActivePnoScans = + new RequestList<>(); + // Tracks scan requests across multiple scanner impls. + private final ScannerImplsTracker mScannerImplsTracker; + + WifiPnoScanStateMachine(Looper looper) { + super("WifiPnoScanStateMachine", looper); + + mScannerImplsTracker = new ScannerImplsTracker(); + + setLogRecSize(256); + setLogOnlyTransitions(false); + + // CHECKSTYLE:OFF IndentationCheck + addState(mDefaultState); + addState(mStartedState, mDefaultState); + addState(mHwPnoScanState, mStartedState); + addState(mSingleScanState, mHwPnoScanState); + addState(mSwPnoScanState, mStartedState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mDefaultState); + } + + public void removePnoSettings(ClientInfo ci) { + mActivePnoScans.removeAllForClient(ci); + } + + /** + * Tracks a PNO scan request across all the available scanner impls. + * + * Note: If there are failures on some of the scanner impls, we ignore them since we can + * get a PNO match from the other successful impls. We don't declare total scan + * failures, unless all the scanner impls fail. + */ + private final class ScannerImplsTracker { + private final class PnoEventHandler implements WifiNative.PnoEventHandler { + private final String mImplIfaceName; + + PnoEventHandler(@NonNull String implIfaceName) { + mImplIfaceName = implIfaceName; + } + + @Override + public void onPnoNetworkFound(ScanResult[] results) { + if (DBG) localLog("onWifiPnoNetworkFound event received"); + reportPnoNetworkFoundForImpl(mImplIfaceName, results); + } + + @Override + public void onPnoScanFailed() { + if (DBG) localLog("onWifiPnoScanFailed event received"); + reportPnoScanFailedForImpl(mImplIfaceName); + } + } + + private static final int STATUS_PENDING = 0; + private static final int STATUS_FAILED = 2; + + // Tracks scan status per impl. + Map mStatusPerImpl = new ArrayMap<>(); + + /** + * Triggers a new PNO with the specified settings on all the available scanner impls. + * @return true if the PNO succeeded on any of the impl, false otherwise. + */ + public boolean setHwPnoList(WifiNative.PnoSettings pnoSettings) { + mStatusPerImpl.clear(); + boolean anySuccess = false; + for (Map.Entry entry : mScannerImpls.entrySet()) { + String ifaceName = entry.getKey(); + WifiScannerImpl impl = entry.getValue(); + boolean success = impl.setHwPnoList( + pnoSettings, new PnoEventHandler(ifaceName)); + if (!success) { + Log.e(TAG, "Failed to start pno on " + ifaceName); + continue; + } + mStatusPerImpl.put(ifaceName, STATUS_PENDING); + anySuccess = true; + } + return anySuccess; + } + + /** + * Resets any ongoing PNO on all the available scanner impls. + * @return true if the PNO stop succeeded on all of the impl, false otherwise. + */ + public boolean resetHwPnoList() { + boolean allSuccess = true; + for (String ifaceName : mStatusPerImpl.keySet()) { + WifiScannerImpl impl = mScannerImpls.get(ifaceName); + if (impl == null) continue; + boolean success = impl.resetHwPnoList(); + if (!success) { + Log.e(TAG, "Failed to stop pno on " + ifaceName); + allSuccess = false; + } + } + mStatusPerImpl.clear(); + return allSuccess; + } + + /** + * @return true if HW PNO is supported on all the available scanner impls, + * false otherwise. + */ + public boolean isHwPnoSupported(boolean isConnected) { + for (WifiScannerImpl impl : mScannerImpls.values()) { + if (!impl.isHwPnoSupported(isConnected)) { + return false; + } + } + return true; + } + + private void reportPnoNetworkFoundForImpl(@NonNull String implIfaceName, + ScanResult[] results) { + Integer status = mStatusPerImpl.get(implIfaceName); + if (status != null && status == STATUS_PENDING) { + sendMessage(CMD_PNO_NETWORK_FOUND, 0, 0, results); + } + } + + private int getConsolidatedStatus() { + boolean anyPending = mStatusPerImpl.values().stream() + .anyMatch(status -> status == STATUS_PENDING); + // at-least one impl status is still pending. + if (anyPending) { + return STATUS_PENDING; + } else { + // all failed. + return STATUS_FAILED; + } + } + + private void reportPnoScanFailedForImpl(@NonNull String implIfaceName) { + Integer currentStatus = mStatusPerImpl.get(implIfaceName); + if (currentStatus != null && currentStatus == STATUS_PENDING) { + mStatusPerImpl.put(implIfaceName, STATUS_FAILED); + } + // Now check if all the scanner impls scan status is available. + int consolidatedStatus = getConsolidatedStatus(); + if (consolidatedStatus == STATUS_FAILED) { + sendMessage(CMD_PNO_SCAN_FAILED); + } + } + } + + class DefaultState extends State { + @Override + public void enter() { + if (DBG) localLog("DefaultState"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_ENABLE: + if (mScannerImpls.isEmpty()) { + loge("Failed to start pno scan state machine because scanner impl" + + " is null"); + return HANDLED; + } + transitionTo(mStartedState); + break; + case WifiScanner.CMD_DISABLE: + transitionTo(mDefaultState); + break; + case WifiScanner.CMD_START_PNO_SCAN: { + ScanParams scanParams = (ScanParams) msg.obj; + try { + scanParams.listener.onFailure(WifiScanner.REASON_UNSPECIFIED, + "not available"); + } catch (RemoteException e) { + // not much we can do if message can't be sent. + } + break; + } + case WifiScanner.CMD_STOP_PNO_SCAN: { + ScanParams scanParams = (ScanParams) msg.obj; + ClientInfo ci = mClients.get(scanParams.listener); + if (ci == null) { + localLog("Pno ClientInfo is null in DefaultState"); + break; + } + ci.replyFailed(WifiScanner.REASON_UNSPECIFIED, "not available"); + break; + } + case CMD_PNO_NETWORK_FOUND: + case CMD_PNO_SCAN_FAILED: + case WifiScanner.CMD_SCAN_RESULT: + case WifiScanner.CMD_OP_FAILED: + loge("Unexpected message " + msg.what); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class StartedState extends State { + @Override + public void enter() { + if (DBG) localLog("StartedState"); + } + + @Override + public void exit() { + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_ENABLE: + // Ignore if we're already in driver loaded state. + return HANDLED; + case WifiScanner.CMD_START_PNO_SCAN: + ScanParams scanParams = (ScanParams) msg.obj; + if (scanParams == null) { + loge("scan params null"); + return HANDLED; + } + ClientInfo ci = mClients.get(scanParams.listener); + if (ci == null) { + ci = new ExternalClientInfo(msg.sendingUid, scanParams.packageName, + scanParams.listener); + ci.register(); + } + if (scanParams.pnoSettings == null || scanParams.settings == null) { + Log.e(TAG, "Failed to get parcelable params"); + ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad parcel params"); + return HANDLED; + } + if (mScannerImplsTracker.isHwPnoSupported( + scanParams.pnoSettings.isConnected)) { + deferMessage(msg); + transitionTo(mHwPnoScanState); + } else if (mWifiGlobals.isSwPnoEnabled() + && mDeviceConfigFacade.isSoftwarePnoEnabled()) { + deferMessage(msg); + transitionTo(mSwPnoScanState); + } else { + Log.w(TAG, "PNO is not available"); + ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "not supported"); + } + break; + case WifiScanner.CMD_STOP_PNO_SCAN: + scanParams = (ScanParams) msg.obj; + ci = mClients.get(scanParams.listener); + if (ci != null) { + ci.cleanup(); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class HwPnoScanState extends State { + @Override + public void enter() { + if (DBG) localLog("HwPnoScanState"); + } + + @Override + public void exit() { + // Reset PNO scan in ScannerImpl before we exit. + mScannerImplsTracker.resetHwPnoList(); + removeInternalClient(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_START_PNO_SCAN: + ScanParams scanParams = (ScanParams) msg.obj; + if (scanParams == null) { + loge("params null"); + return HANDLED; + } + ClientInfo ci = mClients.get(scanParams.listener); + if (ci == null) { + ci = new ExternalClientInfo(msg.sendingUid, scanParams.packageName, + scanParams.listener); + ci.register(); + } + if (scanParams.pnoSettings == null || scanParams.settings == null) { + Log.e(TAG, "Failed to get parcelable params"); + ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad parcel params"); + return HANDLED; + } + + if (addHwPnoScanRequest(ci, scanParams.settings, + scanParams.pnoSettings)) { + mWifiMetrics.getScanMetrics().logPnoScanEvent( + WifiMetrics.ScanMetrics.PNO_SCAN_STATE_STARTED); + ci.replySucceeded(); + } else { + mWifiMetrics.getScanMetrics().logPnoScanEvent( + WifiMetrics.ScanMetrics.PNO_SCAN_STATE_FAILED_TO_START); + ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad request"); + ci.cleanup(); + transitionTo(mStartedState); + } + break; + case WifiScanner.CMD_STOP_PNO_SCAN: + scanParams = (ScanParams) msg.obj; + ci = mClients.get(scanParams.listener); + removeHwPnoScanRequest(ci); + transitionTo(mStartedState); + break; + case CMD_PNO_NETWORK_FOUND: + ScanResult[] scanResults = ((ScanResult[]) msg.obj); + mWifiMetrics.getScanMetrics().logPnoScanEvent( + WifiMetrics.ScanMetrics.PNO_SCAN_STATE_COMPLETED_NETWORK_FOUND); + + if (isSingleScanNeeded(scanResults)) { + ScanSettings activeScanSettings = getScanSettings(); + if (activeScanSettings == null) { + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, + "couldn't retrieve setting"); + transitionTo(mStartedState); + } else { + addSingleScanRequest(activeScanSettings); + transitionTo(mSingleScanState); + } + } else { + reportPnoNetworkFound((ScanResult[]) msg.obj); + } + break; + case CMD_PNO_SCAN_FAILED: + mWifiMetrics.getScanMetrics().logPnoScanEvent( + WifiMetrics.ScanMetrics.PNO_SCAN_STATE_FAILED); + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "pno scan failed"); + transitionTo(mStartedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class SwPnoScanState extends State { + private final AlarmManager mSwPnoAlarmManager; + private final SwPnoAlarmReceiver mSwPnoAlarmReceiver = new SwPnoAlarmReceiver(); + @VisibleForTesting + public static final String SW_PNO_ALARM_INTENT_ACTION = + "com.android.server.wifi.scanner.WifiPnoScanStateMachine.SwPnoScanState" + + ".SW_PNO_ALARM"; + PendingIntent mPendingIntentSwPno; + private final int mSwPnoTimerMarginMs; + @VisibleForTesting + public static final String SW_PNO_UPPER_BOUND_ALARM_INTENT_ACTION = + "com.android.server.wifi.scanner.WifiPnoScanStateMachine.SwPnoScanState" + + ".SW_PNO_UPPERBOUND_ALARM"; + PendingIntent mPendingIntentSwPnoUpperBound; + private SwPnoScheduler mSwPnoScheduler = null; + private ScanParams mScanParams = null; + private ClientInfo mClientInfo = null; + + + private final class SwPnoScheduler { + + private final class SwPnoScheduleInfo { + /** + * The timer is initially scheduled with an interval equal to mTimerBaseMs. + * If mBackoff is true, at each iteration the interval will increase + * proportionally to the elapsed iterations. + * The schedule is repeated up to mMaxIterations iterations. + */ + private final int mMaxIterations; + private final int mTimerBaseMs; + private final boolean mBackoff; + /** + * Whether the alarm should be exact or not. + * Inexact alarms are delivered when the system thinks it is most efficient. + */ + private final boolean mExact; + + + SwPnoScheduleInfo(int maxIterations, boolean exact, int timerBaseMs, + boolean backoff) { + if (maxIterations < 1 || timerBaseMs < 1) { + Log.wtf(TAG, "Invalid Sw PNO Schedule Info."); + throw new IllegalArgumentException("Invalid Sw PNO Schedule Info"); + } + this.mMaxIterations = maxIterations; + this.mExact = exact; + this.mTimerBaseMs = timerBaseMs; + this.mBackoff = backoff; + } + } + private List mSwPnoScheduleInfos = new ArrayList<>(); + SwPnoScheduleInfo mCurrentSchedule; + Iterator mScheduleIterator; + int mIteration = 0; + + /** + * Append a new schedule info at the end of the schedule queue. + * @param maxIterations Number of times the current schedule must be repeated + * @param exact Whether the alarms are scheduled exactly or not + * @param timerBaseMs Initial alarm interval + * @param backoff Whether the interval should increase at each iteration or not + */ + void addSchedule(int maxIterations, boolean exact, int timerBaseMs, + boolean backoff) { + mSwPnoScheduleInfos.add(new SwPnoScheduleInfo(maxIterations, exact, timerBaseMs, + backoff)); + } + + boolean start() { + if (mSwPnoScheduleInfos.isEmpty()) { + Log.wtf(TAG, "No SwPno Schedule Found"); + return false; + } + mScheduleIterator = mSwPnoScheduleInfos.iterator(); + mCurrentSchedule = mScheduleIterator.next(); + return true; + } + + boolean next() { + if (mCurrentSchedule.mMaxIterations > mIteration) { + mIteration++; + return true; + } else if (mScheduleIterator.hasNext()) { + mCurrentSchedule = mScheduleIterator.next(); + mIteration = 1; + return true; + } + return false; + } + + int getInterval() { + int multiplier = mCurrentSchedule.mBackoff ? mIteration : 1; + return mCurrentSchedule.mTimerBaseMs * multiplier; + } + + boolean isExact() { + return mCurrentSchedule.mExact; + } + } + + private class SwPnoAlarmReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(SW_PNO_UPPER_BOUND_ALARM_INTENT_ACTION)) { + mSwPnoAlarmManager.cancel(mPendingIntentSwPno); + } else { + mSwPnoAlarmManager.cancel(mPendingIntentSwPnoUpperBound); + } + Message msg = obtainMessage(); + msg.what = CMD_SW_PNO_SCAN; + sendMessage(msg); + } + } + + SwPnoScanState() { + Intent alarmIntent = new Intent(SW_PNO_ALARM_INTENT_ACTION).setPackage( + mContext.getPackageName()); + Intent alarmIntentUpperBound = new Intent( + SW_PNO_UPPER_BOUND_ALARM_INTENT_ACTION).setPackage( + mContext.getPackageName()); + mSwPnoAlarmManager = mContext.getSystemService(AlarmManager.class); + mPendingIntentSwPno = PendingIntent.getBroadcast(mContext, /* requestCode */ 0, + alarmIntent, PendingIntent.FLAG_IMMUTABLE); + mPendingIntentSwPnoUpperBound = PendingIntent.getBroadcast(mContext, + /* requestCode */ 1, alarmIntentUpperBound, PendingIntent.FLAG_IMMUTABLE); + mSwPnoTimerMarginMs = mContext.getResources().getInteger( + R.integer.config_wifiSwPnoSlowTimerMargin); + } + + @Override + public void enter() { + if (DBG) localLog("SwPnoScanState"); + IntentFilter filter = new IntentFilter(SW_PNO_ALARM_INTENT_ACTION); + filter.addAction(SW_PNO_UPPER_BOUND_ALARM_INTENT_ACTION); + mContext.registerReceiver(mSwPnoAlarmReceiver, filter, null, + getHandler()); + } + + @Override + public void exit() { + removeInternalClient(); + mSwPnoAlarmManager.cancel(mPendingIntentSwPno); + mSwPnoAlarmManager.cancel(mPendingIntentSwPnoUpperBound); + mContext.unregisterReceiver(mSwPnoAlarmReceiver); + mScanParams = null; + mClientInfo = null; + } + + boolean initializeSwPnoScheduleInfos(int mobilityIntervalMs) { + final int swPnoDefaultTimerFastMs = mContext.getResources().getInteger( + R.integer.config_wifiSwPnoFastTimerMs); + final int swPnoDefaultTimerSlowMs = mContext.getResources().getInteger( + R.integer.config_wifiSwPnoSlowTimerMs); + final int swPnoMobilityIterations = mContext.getResources().getInteger( + R.integer.config_wifiSwPnoMobilityStateTimerIterations); + final int swPnoFastIterations = mContext.getResources().getInteger( + R.integer.config_wifiSwPnoFastTimerIterations); + final int swPnoSlowIterations = mContext.getResources().getInteger( + R.integer.config_wifiSwPnoSlowTimerIterations); + + mSwPnoScheduler = new SwPnoScheduler(); + try { + mSwPnoScheduler.addSchedule(swPnoMobilityIterations, + /* exact */ true, mobilityIntervalMs, /* backoff */ true); + mSwPnoScheduler.addSchedule(swPnoFastIterations, + /* exact */ true, swPnoDefaultTimerFastMs, /* backoff */ false); + mSwPnoScheduler.addSchedule(swPnoSlowIterations, + /* exact */ false, swPnoDefaultTimerSlowMs, /* backoff */ false); + } catch (IllegalArgumentException e) { + return false; + } + return mSwPnoScheduler.start(); + } + + private void addSwPnoScanRequest(ClientInfo ci, + ScanSettings scanSettings, PnoSettings pnoSettings) { + scanSettings.reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT + | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; + addPnoScanRequest(ci, scanSettings, pnoSettings); + } + + private void removeSwPnoScanRequest(ClientInfo ci) { + if (ci != null) { + Pair settings = removePnoScanRequest(ci); + ci.cleanup(); + if (settings != null) { + logScanRequest("removeSwPnoScanRequest", ci, null, + settings.second, settings.first); + } + } + } + + private void schedulePnoTimer(boolean exact, int timeoutMs) { + Log.i(TAG, "Next SwPno scan in: " + timeoutMs); + if (exact) { + mSwPnoAlarmManager.setExactAndAllowWhileIdle( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + mClock.getElapsedSinceBootMillis() + timeoutMs, + mPendingIntentSwPno); + } else { + mSwPnoAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME, + mClock.getElapsedSinceBootMillis() + timeoutMs, + mSwPnoTimerMarginMs, + mPendingIntentSwPno); + + mSwPnoAlarmManager.setExactAndAllowWhileIdle( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + mClock.getElapsedSinceBootMillis() + timeoutMs + + mSwPnoTimerMarginMs, mPendingIntentSwPnoUpperBound); + } + } + + private void handleSwPnoScan() { + if (mScanParams != null && mScanParams.settings != null && mClientInfo != null) { + // The Internal ClientInfo is unregistered by + // WifiSingleScanStateMachine#handleScanResults after each scan. We have + // therefore to re-create or at least re-register the client before each scan. + // For the first scan this is redundant. + removeInternalClient(); + addInternalClient(mClientInfo); + addSingleScanRequest(mScanParams.settings); + } + } + + private void handleSwPnoSchedule() { + if (mSwPnoScheduler.next()) { + schedulePnoTimer(mSwPnoScheduler.isExact(), + mSwPnoScheduler.getInterval()); + } else { + // Nothing more to schedule, stopping SW PNO + Message msg = obtainMessage(); + msg.what = WifiScanner.CMD_STOP_PNO_SCAN; + sendMessage(msg); + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_START_PNO_SCAN: { + Log.i(TAG, "Starting Software PNO"); + ScanParams scanParams = (ScanParams) msg.obj; + if (scanParams == null) { + Log.wtf(TAG, "Received Start PNO request without parameters"); + transitionTo(mStartedState); + return HANDLED; + } + + ClientInfo clientInfo = mClients.get(scanParams.listener); + if (clientInfo == null) { + clientInfo = new ExternalClientInfo(msg.sendingUid, + scanParams.packageName, scanParams.listener); + clientInfo.register(); + } + + if (!mActivePnoScans.isEmpty()) { + loge("Dropping scan request because there is already an active scan"); + clientInfo.replyFailed(WifiScanner.REASON_DUPLICATE_REQEUST, + "Failed to add a SW Pno Scan Request"); + return HANDLED; + } + + if (scanParams.pnoSettings == null || scanParams.settings == null) { + Log.e(TAG, "SwPno Invalid Scan Parameters"); + clientInfo.replyFailed(WifiScanner.REASON_INVALID_REQUEST, + "invalid settings"); + transitionTo(mStartedState); + return HANDLED; + } + + if (!initializeSwPnoScheduleInfos(scanParams.settings.periodInMs)) { + clientInfo.replyFailed(WifiScanner.REASON_INVALID_REQUEST, + "Failed to initialize the Sw PNO Scheduler"); + transitionTo(mStartedState); + return HANDLED; + } + + addSwPnoScanRequest(clientInfo, scanParams.settings, + scanParams.pnoSettings); + clientInfo.replySucceeded(); + mClientInfo = clientInfo; + mScanParams = scanParams; + + handleSwPnoScan(); + handleSwPnoSchedule(); + break; + } + case CMD_SW_PNO_SCAN: + // The internal client is registered to mClients when the PNO scan is + // started, and is deregistered when the scan is over. By verifying that + // the internal client is not registered in mClients can be sure that no + // other pno scans are in progress + if (mClients.get(mInternalClientInfo.mListener) == null) { + handleSwPnoScan(); + handleSwPnoSchedule(); + } + break; + case WifiScanner.CMD_STOP_PNO_SCAN: { + Log.i(TAG, "Stopping Software PNO"); + if (mClientInfo != null) { + removeSwPnoScanRequest(mClientInfo); + transitionTo(mStartedState); + } + break; + } + case WifiScanner.CMD_OP_FAILED: + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "scan failed"); + transitionTo(mStartedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class SingleScanState extends State { + @Override + public void enter() { + if (DBG) localLog("SingleScanState"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case WifiScanner.CMD_SCAN_RESULT: + WifiScanner.ParcelableScanData parcelableScanData = + (WifiScanner.ParcelableScanData) msg.obj; + ScanData[] scanDatas = parcelableScanData.getResults(); + ScanData lastScanData = scanDatas[scanDatas.length - 1]; + reportPnoNetworkFound(lastScanData.getResults()); + transitionTo(mHwPnoScanState); + break; + case WifiScanner.CMD_OP_FAILED: + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "single scan failed"); + transitionTo(mStartedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private WifiNative.PnoSettings convertToWifiNativePnoSettings(ScanSettings scanSettings, + PnoSettings pnoSettings) { + WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings(); + nativePnoSetting.periodInMs = scanSettings.periodInMs; + nativePnoSetting.min5GHzRssi = pnoSettings.min5GHzRssi; + nativePnoSetting.min24GHzRssi = pnoSettings.min24GHzRssi; + nativePnoSetting.min6GHzRssi = pnoSettings.min6GHzRssi; + nativePnoSetting.scanIterations = pnoSettings.scanIterations; + nativePnoSetting.scanIntervalMultiplier = pnoSettings.scanIntervalMultiplier; + nativePnoSetting.isConnected = pnoSettings.isConnected; + nativePnoSetting.networkList = + new WifiNative.PnoNetwork[pnoSettings.networkList.length]; + for (int i = 0; i < pnoSettings.networkList.length; i++) { + nativePnoSetting.networkList[i] = new WifiNative.PnoNetwork(); + nativePnoSetting.networkList[i].ssid = pnoSettings.networkList[i].ssid; + nativePnoSetting.networkList[i].flags = pnoSettings.networkList[i].flags; + nativePnoSetting.networkList[i].auth_bit_field = + pnoSettings.networkList[i].authBitField; + nativePnoSetting.networkList[i].frequencies = + pnoSettings.networkList[i].frequencies; + } + return nativePnoSetting; + } + + // Retrieve the only active scan settings. + private ScanSettings getScanSettings() { + for (Pair settingsPair : mActivePnoScans.getAllSettings()) { + return settingsPair.second; + } + return null; + } + + private void removeInternalClient() { + if (mInternalClientInfo != null) { + mInternalClientInfo.cleanup(); + mInternalClientInfo = null; + } else { + Log.w(TAG, "No Internal client for PNO"); + } + } + + private void addInternalClient(ClientInfo ci) { + if (mInternalClientInfo == null) { + mInternalClientInfo = new InternalClientInfo(ci.getUid(), "internal", + new InternalListener()); + mInternalClientInfo.register(); + } else { + Log.w(TAG, "Internal client for PNO already exists"); + } + } + + private void addPnoScanRequest(ClientInfo ci, ScanSettings scanSettings, + PnoSettings pnoSettings) { + mActivePnoScans.addRequest(ci, ClientModeImpl.WIFI_WORK_SOURCE, + Pair.create(pnoSettings, scanSettings)); + addInternalClient(ci); + } + + private Pair removePnoScanRequest(ClientInfo ci) { + Pair settings = mActivePnoScans.removeRequest(ci); + return settings; + } + + private boolean addHwPnoScanRequest(ClientInfo ci, ScanSettings scanSettings, + PnoSettings pnoSettings) { + if (ci == null) { + Log.d(TAG, "Failing scan request ClientInfo not found "); + return false; + } + if (!mActivePnoScans.isEmpty()) { + loge("Failing scan request because there is already an active scan"); + return false; + } + WifiNative.PnoSettings nativePnoSettings = + convertToWifiNativePnoSettings(scanSettings, pnoSettings); + if (!mScannerImplsTracker.setHwPnoList(nativePnoSettings)) { + return false; + } + logScanRequest("addHwPnoScanRequest", ci, null, scanSettings, pnoSettings); + addPnoScanRequest(ci, scanSettings, pnoSettings); + + return true; + } + + private void removeHwPnoScanRequest(ClientInfo ci) { + if (ci != null) { + Pair settings = removePnoScanRequest(ci); + ci.cleanup(); + if (settings != null) { + logScanRequest("removeHwPnoScanRequest", ci, null, + settings.second, settings.first); + } + } + } + + private void reportPnoNetworkFound(ScanResult[] results) { + for (RequestInfo> entry : mActivePnoScans) { + ClientInfo ci = entry.clientInfo; + logCallback("pnoNetworkFound", ci, describeForLog(results)); + ci.reportEvent((listener) -> { + try { + listener.onPnoNetworkFound(results); + } catch (RemoteException e) { + loge("Failed to call onFullResult: " + ci); + } + }); + } + } + + private void sendPnoScanFailedToAllAndClear(int reason, String description) { + for (RequestInfo> entry : mActivePnoScans) { + ClientInfo ci = entry.clientInfo; + ci.reportEvent((listener) -> { + try { + listener.onFailure(reason, description); + } catch (RemoteException e) { + loge("Failed to call onFullResult: " + ci); + } + }); + } + mActivePnoScans.clear(); + } + + private void addSingleScanRequest(ScanSettings settings) { + if (DBG) localLog("Starting single scan"); + if (mInternalClientInfo != null) { + Message msg = Message.obtain(); + msg.what = WifiScanner.CMD_START_SINGLE_SCAN; + msg.obj = new ScanParams(mInternalClientInfo.mListener, settings, + ClientModeImpl.WIFI_WORK_SOURCE); + mSingleScanStateMachine.sendMessage(msg); + } + mWifiMetrics.getScanMetrics().setWorkSource(ClientModeImpl.WIFI_WORK_SOURCE); + } + + /** + * Checks if IE are present in scan data, if no single scan is needed to report event to + * client + */ + private boolean isSingleScanNeeded(ScanResult[] scanResults) { + for (ScanResult scanResult : scanResults) { + if (scanResult.informationElements != null + && scanResult.informationElements.length > 0) { + return false; + } + } + return true; + } + } + + @FunctionalInterface + private interface ListenerCallback { + void callListener(IWifiScannerListener listener); + } + + private abstract class ClientInfo { + private final int mUid; + private final String mPackageName; + private final WorkSource mWorkSource; + private boolean mScanWorkReported = false; + protected final IWifiScannerListener mListener; + protected DeathRecipient mDeathRecipient = new DeathRecipient() { + @Override + public void binderDied() { + mWifiThreadRunner.post(() -> { + if (DBG) localLog("binder died: client listener: " + mListener); + if (isVerboseLoggingEnabled()) { + Log.i(TAG, "binder died: client listener: " + mListener); + } + cleanup(); + }); + } + }; + + ClientInfo(int uid, String packageName, IWifiScannerListener listener) { + mUid = uid; + mPackageName = packageName; + mListener = listener; + mWorkSource = new WorkSource(uid); + } + + /** + * Register this client to main client map. + */ + public void register() { + if (isVerboseLoggingEnabled()) { + Log.i(TAG, "Registering listener= " + mListener + " uid=" + mUid + + " packageName=" + mPackageName + " workSource=" + mWorkSource); + } + mClients.put(mListener, this); + } + + /** + * Unregister this client from main client map. + */ + private void unregister() { + if (isVerboseLoggingEnabled()) { + Log.i(TAG, "Unregistering listener= " + mListener + " uid=" + mUid + + " packageName=" + mPackageName + " workSource=" + mWorkSource); + } + try { + mListener.asBinder().unlinkToDeath(mDeathRecipient, 0); + } catch (Exception e) { + Log.e(TAG, "Failed to unregister death recipient! " + mListener); + } + + mClients.remove(mListener); + } + + public void cleanup() { + mSingleScanListeners.removeAllForClient(this); + mSingleScanStateMachine.removeSingleScanRequests(this); + mBackgroundScanStateMachine.removeBackgroundScanSettings(this); + unregister(); + localLog("Successfully stopped all requests for client " + this); + } + + public int getUid() { + return mUid; + } + + // This has to be implemented by subclasses to report events back to clients. + public abstract void reportEvent(ListenerCallback cb); + + // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ? + private void reportBatchedScanStart() { + if (mUid == 0) + return; + + int csph = getCsph(); + + mBatteryStats.reportWifiBatchedScanStartedFromSource(mWorkSource, csph); + } + + // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ? + private void reportBatchedScanStop() { + if (mUid == 0) + return; + + mBatteryStats.reportWifiBatchedScanStoppedFromSource(mWorkSource); + } + + // TODO migrate batterystats to accept scan duration per hour instead of csph + private int getCsph() { + int totalScanDurationPerHour = 0; + Collection settingsList = + mBackgroundScanStateMachine.getBackgroundScanSettings(this); + for (ScanSettings settings : settingsList) { + int scanDurationMs = mChannelHelper.estimateScanDuration(settings); + int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) / + settings.periodInMs; + totalScanDurationPerHour += scanDurationMs * scans_per_Hour; + } + + return totalScanDurationPerHour / ChannelHelper.SCAN_PERIOD_PER_CHANNEL_MS; + } + + // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ? + private void reportScanWorkUpdate() { + if (mScanWorkReported) { + reportBatchedScanStop(); + mScanWorkReported = false; + } + if (mBackgroundScanStateMachine.getBackgroundScanSettings(this).isEmpty()) { + reportBatchedScanStart(); + mScanWorkReported = true; + } + } + + void replySucceeded() { + if (mListener != null) { + try { + mListener.onSuccess(); + mLog.trace("onSuccess").flush(); + } catch (Exception e) { + // There's not much we can do if reply can't be sent! + } + } else { + // locally generated message; doesn't need a reply! + } + } + + void replyFailed(int reason, String description) { + if (mListener != null) { + try { + mListener.onFailure(reason, description); + mLog.trace("onFailure reason=% description=%") + .c(reason) + .c(description) + .flush(); + } catch (Exception e) { + // There's not much we can do if reply can't be sent! + } + } else { + // locally generated message; doesn't need a reply! + } + } + + @Override + public String toString() { + return "ClientInfo[uid=" + mUid + ", package=" + mPackageName + ", " + mListener + + "]"; + } + } + + /** + * This class is used to represent external clients to the WifiScanning Service. + */ + private class ExternalClientInfo extends ClientInfo { + /** + * Indicates if the client is still connected + * If the client is no longer connected then messages to it will be silently dropped + */ + private boolean mDisconnected = false; + + ExternalClientInfo(int uid, String packageName, IWifiScannerListener listener) { + super(uid, packageName, listener); + if (DBG) localLog("New client, listener: " + listener); + try { + listener.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + Log.e(TAG, "can't register death recipient! " + listener); + } + } + + public void reportEvent(ListenerCallback cb) { + if (!mDisconnected) { + cb.callListener(mListener); + } + } + + @Override + public void cleanup() { + mDisconnected = true; + mPnoScanStateMachine.removePnoSettings(this); + super.cleanup(); + } + } + + /** + * This class is used to represent internal clients to the WifiScanning Service. This is needed + * for communicating between State Machines. + * This leaves the onReportEvent method unimplemented, so that the clients have the freedom + * to handle the events as they need. + */ + private class InternalClientInfo extends ClientInfo { + /** + * The UID here is used to proxy the original external requester UID. + */ + InternalClientInfo(int requesterUid, String packageName, IWifiScannerListener listener) { + super(requesterUid, packageName, listener); + } + + @Override + public void reportEvent(ListenerCallback cb) { + cb.callListener(mListener); + } + + @Override + public String toString() { + return "InternalClientInfo[]"; + } + } + + private static class InternalListener extends IWifiScannerListener.Stub { + InternalListener() { + } + + @Override + public void onSuccess() { + } + + @Override + public void onFailure(int reason, String description) { + } + + @Override + public void onResults(WifiScanner.ScanData[] results) { + } + + @Override + public void onFullResult(ScanResult fullScanResult) { + } + + @Override + public void onSingleScanCompleted() { + } + + @Override + public void onPnoNetworkFound(ScanResult[] results) { + } + } + + private class LocalService extends WifiScannerInternal { + @Override + public void setScanningEnabled(boolean enable) { + WifiScanningServiceImpl.this.setScanningEnabled(enable, Process.myTid(), + mContext.getOpPackageName()); + } + + @Override + public void registerScanListener(@NonNull WifiScannerInternal.ScanListener listener) { + WifiScanningServiceImpl.this.registerScanListener(listener, + mContext.getOpPackageName(), mContext.getAttributionTag()); + } + + @Override + public void startScan(WifiScanner.ScanSettings settings, + WifiScannerInternal.ScanListener listener, + @Nullable WorkSource workSource) { + WifiScanningServiceImpl.this.startScan(listener, settings, workSource, + workSource.getPackageName(0), mContext.getAttributionTag()); + } + + @Override + public void stopScan(WifiScannerInternal.ScanListener listener) { + WifiScanningServiceImpl.this.stopScan(listener, + mContext.getOpPackageName(), mContext.getAttributionTag()); + } + + @Override + public void startPnoScan(WifiScanner.ScanSettings scanSettings, + WifiScanner.PnoSettings pnoSettings, + WifiScannerInternal.ScanListener listener) { + WifiScanningServiceImpl.this.startPnoScan(listener, + scanSettings, pnoSettings, mContext.getOpPackageName(), + mContext.getAttributionTag()); + } + + @Override + public void stopPnoScan(WifiScannerInternal.ScanListener listener) { + WifiScanningServiceImpl.this.stopPnoScan(listener, mContext.getOpPackageName(), + mContext.getAttributionTag()); + } + + @Override + public List getSingleScanResults() { + return mSingleScanStateMachine.filterCachedScanResultsByAge(); + } + } + + private static String toString(int uid, ScanSettings settings) { + StringBuilder sb = new StringBuilder(); + sb.append("ScanSettings[uid=").append(uid); + sb.append(", period=").append(settings.periodInMs); + sb.append(", report=").append(settings.reportEvents); + if (settings.reportEvents == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL + && settings.numBssidsPerScan > 0 + && settings.maxScansToCache > 1) { + sb.append(", batch=").append(settings.maxScansToCache); + sb.append(", numAP=").append(settings.numBssidsPerScan); + } + sb.append(", ").append(ChannelHelper.toString(settings)); + sb.append("]"); + + return sb.toString(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump WifiScanner from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " without permission " + + android.Manifest.permission.DUMP); + return; + } + pw.println("WifiScanningService - Log Begin ----"); + mLocalLog.dump(fd, pw, args); + pw.println("WifiScanningService - Log End ----"); + pw.println(); + pw.println("clients:"); + for (ClientInfo client : mClients.values()) { + pw.println(" " + client); + } + pw.println("listeners:"); + for (ClientInfo client : mClients.values()) { + Collection settingsList = + mBackgroundScanStateMachine.getBackgroundScanSettings(client); + for (ScanSettings settings : settingsList) { + pw.println(" " + toString(client.mUid, settings)); + } + } + if (mBackgroundScheduler != null) { + WifiNative.ScanSettings schedule = mBackgroundScheduler.getSchedule(); + if (schedule != null) { + pw.println("schedule:"); + pw.println(" base period: " + schedule.base_period_ms); + pw.println(" max ap per scan: " + schedule.max_ap_per_scan); + pw.println(" batched scans: " + schedule.report_threshold_num_scans); + pw.println(" buckets:"); + for (int b = 0; b < schedule.num_buckets; b++) { + WifiNative.BucketSettings bucket = schedule.buckets[b]; + pw.println(" bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)[" + + bucket.report_events + "]: " + + ChannelHelper.toString(bucket)); + } + } + } + if (mPnoScanStateMachine != null) { + mPnoScanStateMachine.dump(fd, pw, args); + } + pw.println(); + + if (mChannelHelper != null) { + mChannelHelper.dump(fd, pw, args); + pw.println(); + } + + if (mSingleScanStateMachine != null) { + mSingleScanStateMachine.dump(fd, pw, args); + pw.println(); + List scanResults = mSingleScanStateMachine.getCachedScanResultsAsList(); + long nowMs = mClock.getElapsedSinceBootMillis(); + Log.d(TAG, "Latest scan results nowMs = " + nowMs); + pw.println("Latest scan results:"); + ScanResultUtil.dumpScanResults(pw, scanResults, nowMs); + pw.println(); + } + for (WifiScannerImpl impl : mScannerImpls.values()) { + impl.dump(fd, pw, args); + } + } + + void logScanRequest(String request, ClientInfo ci, WorkSource workSource, + ScanSettings settings, PnoSettings pnoSettings) { + StringBuilder sb = new StringBuilder(); + sb.append(request) + .append(": ") + .append((ci == null) ? "ClientInfo[unknown]" : ci.toString()); + if (workSource != null) { + sb.append(",").append(workSource); + } + if (settings != null) { + sb.append(", "); + describeTo(sb, settings); + } + if (pnoSettings != null) { + sb.append(", "); + describeTo(sb, pnoSettings); + } + localLog(sb.toString()); + } + + void logCallback(String callback, ClientInfo ci, String extra) { + StringBuilder sb = new StringBuilder(); + sb.append(callback) + .append(": ") + .append((ci == null) ? "ClientInfo[unknown]" : ci.toString()); + if (extra != null) { + sb.append(",").append(extra); + } + localLog(sb.toString()); + } + + static String describeForLog(ScanData[] results) { + StringBuilder sb = new StringBuilder(); + sb.append("results="); + for (int i = 0; i < results.length; ++i) { + if (i > 0) sb.append(";"); + sb.append(results[i].getResults().length); + } + return sb.toString(); + } + + static String describeForLog(ScanResult[] results) { + return "results=" + results.length; + } + + static String getScanTypeString(int type) { + switch(type) { + case WifiScanner.SCAN_TYPE_LOW_LATENCY: + return "LOW LATENCY"; + case WifiScanner.SCAN_TYPE_LOW_POWER: + return "LOW POWER"; + case WifiScanner.SCAN_TYPE_HIGH_ACCURACY: + return "HIGH ACCURACY"; + default: + // This should never happen because we've validated the incoming type in + // |validateScanType|. + throw new IllegalArgumentException("Invalid scan type " + type); + } + } + + /** + * Convert Wi-Fi standard error to string + */ + private static String scanErrorCodeToDescriptionString(int errorCode) { + switch(errorCode) { + case WifiScanner.REASON_BUSY: + return "Scan failed - Device or resource busy"; + case WifiScanner.REASON_ABORT: + return "Scan aborted"; + case WifiScanner.REASON_NO_DEVICE: + return "Scan failed - No such device"; + case WifiScanner.REASON_INVALID_ARGS: + return "Scan failed - invalid argument"; + case WifiScanner.REASON_TIMEOUT: + return "Scan failed - Timeout"; + case WifiScanner.REASON_UNSPECIFIED: + default: + return "Scan failed - unspecified reason"; + } + } + + static String describeTo(StringBuilder sb, ScanSettings scanSettings) { + sb.append("ScanSettings { ") + .append(" type:").append(getScanTypeString(scanSettings.type)) + .append(" band:").append(ChannelHelper.bandToString(scanSettings.band)) + .append(" ignoreLocationSettings:").append(scanSettings.ignoreLocationSettings) + .append(" period:").append(scanSettings.periodInMs) + .append(" reportEvents:").append(scanSettings.reportEvents) + .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan) + .append(" maxScansToCache:").append(scanSettings.maxScansToCache) + .append(" rnrSetting:").append( + SdkLevel.isAtLeastS() ? scanSettings.getRnrSetting() : "Not supported") + .append(" 6GhzPscOnlyEnabled:").append( + SdkLevel.isAtLeastS() ? scanSettings.is6GhzPscOnlyEnabled() + : "Not supported") + .append(" channels:[ "); + if (scanSettings.channels != null) { + for (int i = 0; i < scanSettings.channels.length; i++) { + sb.append(scanSettings.channels[i].frequency).append(" "); + } + } + sb.append(" ] ").append(" } "); + return sb.toString(); + } + + static String describeTo(StringBuilder sb, PnoSettings pnoSettings) { + sb.append("PnoSettings { ") + .append(" min5GhzRssi:").append(pnoSettings.min5GHzRssi) + .append(" min24GhzRssi:").append(pnoSettings.min24GHzRssi) + .append(" min6GhzRssi:").append(pnoSettings.min6GHzRssi) + .append(" scanIterations:").append(pnoSettings.scanIterations) + .append(" scanIntervalMultiplier:").append(pnoSettings.scanIntervalMultiplier) + .append(" isConnected:").append(pnoSettings.isConnected) + .append(" networks:[ "); + if (pnoSettings.networkList != null) { + for (int i = 0; i < pnoSettings.networkList.length; i++) { + sb.append(pnoSettings.networkList[i].ssid).append(","); + } + } + sb.append(" ] ") + .append(" } "); + return sb.toString(); + } +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java new file mode 100644 index 000000000..927d04e01 --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi.scanner; + +import android.net.wifi.WifiScanner; +import android.util.Log; + +import com.android.server.wifi.WifiNative; + +/** + * KnownBandsChannelHelper that uses band to channel mappings retrieved from wificond. + * Also supporting updating the channel list from the wificond on demand. + */ +public class WificondChannelHelper extends KnownBandsChannelHelper { + private static final String TAG = "WificondChannelHelper"; + + private final WifiNative mWifiNative; + + public WificondChannelHelper(WifiNative wifiNative) { + mWifiNative = wifiNative; + final int[] emptyFreqList = new int[0]; + setBandChannels(emptyFreqList, emptyFreqList, emptyFreqList, emptyFreqList, emptyFreqList); + updateChannels(); + } + + @Override + public void updateChannels() { + int[] channels24G = + mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ); + if (channels24G == null) Log.e(TAG, "Failed to get channels for 2.4GHz band"); + int[] channels5G = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ); + if (channels5G == null) Log.e(TAG, "Failed to get channels for 5GHz band"); + int[] channelsDfs = + mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY); + if (channelsDfs == null) Log.e(TAG, "Failed to get channels for 5GHz DFS only band"); + int[] channels6G = + mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ); + if (channels6G == null) Log.e(TAG, "Failed to get channels for 6GHz band"); + int[] channels60G = + mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ); + if (channels60G == null) Log.e(TAG, "Failed to get channels for 60GHz band"); + + if (channels24G == null || channels5G == null || channelsDfs == null + || channels6G == null || channels60G == null) { + Log.e(TAG, "Failed to get all channels for band, not updating band channel lists"); + } else if (channels24G.length > 0 || channels5G.length > 0 || channelsDfs.length > 0 + || channels6G.length > 0 || channels60G.length > 0) { + setBandChannels(channels24G, channels5G, channelsDfs, channels6G, channels60G); + } else { + Log.e(TAG, "Got zero length for all channel lists"); + } + } +} diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java new file mode 100644 index 000000000..1fec13539 --- /dev/null +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi.scanner; + +import android.app.AlarmManager; +import android.content.Context; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiScanner; +import android.net.wifi.WifiScanner.WifiBandIndex; +import android.net.wifi.util.ScanResultUtil; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.android.server.wifi.Clock; +import com.android.server.wifi.ScanDetail; +import com.android.server.wifi.WifiGlobals; +import com.android.server.wifi.WifiMonitor; +import com.android.server.wifi.WifiNative; +import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; +import com.android.server.wifi.util.NativeUtil; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.concurrent.GuardedBy; + +/** + * Implementation of the WifiScanner HAL API that uses wificond to perform all scans + * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method. + */ +public class WificondScannerImpl extends WifiScannerImpl implements Handler.Callback { + private static final String TAG = "WificondScannerImpl"; + private static final boolean DBG = false; + + public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout"; + // Default number of networks that can be specified to wificond per scan request + public static final int DEFAULT_NUM_HIDDEN_NETWORK_IDS_PER_SCAN = 16; + + private static final int SCAN_BUFFER_CAPACITY = 10; + private static final int MAX_APS_PER_SCAN = 32; + private static final int MAX_SCAN_BUCKETS = 16; + + private final Context mContext; + private final WifiGlobals mWifiGlobals; + private final WifiNative mWifiNative; + private final WifiMonitor mWifiMonitor; + private final AlarmManager mAlarmManager; + private final Handler mEventHandler; + private final ChannelHelper mChannelHelper; + private final Clock mClock; + + private final Object mSettingsLock = new Object(); + + private ArrayList mNativeScanResults; + private ArrayList mNativePnoScanResults; + private WifiScanner.ScanData mLatestSingleScanResult = + new WifiScanner.ScanData(0, 0, new ScanResult[0]); + private int mMaxNumScanSsids = -1; + private int mNextHiddenNetworkScanId = 0; + + // Settings for the currently running single scan, null if no scan active + private LastScanSettings mLastScanSettings = null; + // Settings for the currently running pno scan, null if no scan active + private LastPnoScanSettings mLastPnoScanSettings = null; + + /** + * Duration to wait before timing out a scan. + * + * The expected behavior is that the hardware will return a failed scan if it does not + * complete, but timeout just in case it does not. + */ + private static final long SCAN_TIMEOUT_MS = 15000; + + @GuardedBy("mSettingsLock") + private AlarmManager.OnAlarmListener mScanTimeoutListener; + + public WificondScannerImpl(Context context, String ifaceName, WifiGlobals wifiGlobals, + WifiNative wifiNative, WifiMonitor wifiMonitor, + ChannelHelper channelHelper, Looper looper, Clock clock) { + super(ifaceName); + mContext = context; + mWifiGlobals = wifiGlobals; + mWifiNative = wifiNative; + mWifiMonitor = wifiMonitor; + mChannelHelper = channelHelper; + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mEventHandler = new Handler(looper, this); + mClock = clock; + + wifiMonitor.registerHandler(getIfaceName(), + WifiMonitor.SCAN_FAILED_EVENT, mEventHandler); + wifiMonitor.registerHandler(getIfaceName(), + WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler); + wifiMonitor.registerHandler(getIfaceName(), + WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler); + } + + @Override + public void cleanup() { + synchronized (mSettingsLock) { + cancelScanTimeout(); + reportScanFailure(WifiScanner.REASON_UNSPECIFIED); + stopHwPnoScan(); + mMaxNumScanSsids = -1; + mNextHiddenNetworkScanId = 0; + mLastScanSettings = null; // finally clear any active scan + mLastPnoScanSettings = null; // finally clear any active scan + mWifiMonitor.deregisterHandler(getIfaceName(), + WifiMonitor.SCAN_FAILED_EVENT, mEventHandler); + mWifiMonitor.deregisterHandler(getIfaceName(), + WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler); + mWifiMonitor.deregisterHandler(getIfaceName(), + WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler); + } + } + + @Override + public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) { + capabilities.max_scan_cache_size = Integer.MAX_VALUE; + capabilities.max_scan_buckets = MAX_SCAN_BUCKETS; + capabilities.max_ap_cache_per_scan = MAX_APS_PER_SCAN; + capabilities.max_rssi_sample_size = 8; + capabilities.max_scan_reporting_threshold = SCAN_BUFFER_CAPACITY; + return true; + } + + @Override + public ChannelHelper getChannelHelper() { + return mChannelHelper; + } + + @Override + public boolean startSingleScan(WifiNative.ScanSettings settings, + WifiNative.ScanEventHandler eventHandler) { + if (eventHandler == null || settings == null) { + Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings + + ",eventHandler=" + eventHandler); + return false; + } + synchronized (mSettingsLock) { + if (mLastScanSettings != null) { + Log.w(TAG, "A single scan is already running"); + return false; + } + + ChannelCollection allFreqs = mChannelHelper.createChannelCollection(); + boolean reportFullResults = false; + + for (int i = 0; i < settings.num_buckets; ++i) { + WifiNative.BucketSettings bucketSettings = settings.buckets[i]; + if ((bucketSettings.report_events + & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) { + reportFullResults = true; + } + allFreqs.addChannels(bucketSettings); + } + + List hiddenNetworkSSIDSet = new ArrayList<>(); + if (settings.hiddenNetworks != null) { + boolean executeRoundRobin = true; + int maxNumScanSsids = mMaxNumScanSsids; + if (maxNumScanSsids <= 0) { + // Subtract 1 to account for the wildcard/broadcast probe request that + // wificond adds to the scan set. + mMaxNumScanSsids = mWifiNative.getMaxSsidsPerScan(getIfaceName()) - 1; + if (mMaxNumScanSsids > 0) { + maxNumScanSsids = mMaxNumScanSsids; + } else { + maxNumScanSsids = DEFAULT_NUM_HIDDEN_NETWORK_IDS_PER_SCAN; + executeRoundRobin = false; + } + } + int numHiddenNetworksPerScan = + Math.min(settings.hiddenNetworks.length, maxNumScanSsids); + if (numHiddenNetworksPerScan == settings.hiddenNetworks.length + || mNextHiddenNetworkScanId >= settings.hiddenNetworks.length + || !executeRoundRobin) { + mNextHiddenNetworkScanId = 0; + } + if (DBG) { + Log.d(TAG, "Scanning for " + numHiddenNetworksPerScan + " out of " + + settings.hiddenNetworks.length + " total hidden networks"); + Log.d(TAG, "Scan hidden networks starting at id=" + mNextHiddenNetworkScanId); + } + + int id = mNextHiddenNetworkScanId; + for (int i = 0; i < numHiddenNetworksPerScan; i++, id++) { + hiddenNetworkSSIDSet.add( + settings.hiddenNetworks[id % settings.hiddenNetworks.length].ssid); + } + mNextHiddenNetworkScanId = id % settings.hiddenNetworks.length; + } + mLastScanSettings = new LastScanSettings( + mClock.getElapsedSinceBootNanos(), + reportFullResults, allFreqs, eventHandler); + + int scanStatus = WifiScanner.REASON_UNSPECIFIED; + Set freqs = Collections.emptySet(); + if (!allFreqs.isEmpty()) { + freqs = allFreqs.getScanFreqs(); + scanStatus = mWifiNative.scan( + getIfaceName(), settings.scanType, freqs, hiddenNetworkSSIDSet, + settings.enable6GhzRnr, settings.vendorIes); + if (scanStatus != WifiScanner.REASON_SUCCEEDED) { + Log.e(TAG, "Failed to start scan, freqs=" + freqs + " status: " + + scanStatus); + } + } else { + // There is a scan request but no available channels could be scanned for. + // We regard it as a scan failure in this case. + Log.e(TAG, "Failed to start scan because there is no available channel to scan"); + } + if (scanStatus == WifiScanner.REASON_SUCCEEDED) { + if (DBG) { + Log.d(TAG, "Starting wifi scan for freqs=" + freqs + + " on iface " + getIfaceName()); + } + + mScanTimeoutListener = new AlarmManager.OnAlarmListener() { + @Override public void onAlarm() { + handleScanTimeout(); + } + }; + + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS, + TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler); + } else { + // indicate scan failure async + int finalScanStatus = scanStatus; + mEventHandler.post(() -> reportScanFailure(finalScanStatus)); + } + + return true; + } + } + + @Override + public WifiScanner.ScanData getLatestSingleScanResults() { + return mLatestSingleScanResult; + } + + @Override + public boolean startBatchedScan(WifiNative.ScanSettings settings, + WifiNative.ScanEventHandler eventHandler) { + Log.w(TAG, "startBatchedScan() is not supported"); + return false; + } + + @Override + public void stopBatchedScan() { + Log.w(TAG, "stopBatchedScan() is not supported"); + } + + @Override + public void pauseBatchedScan() { + Log.w(TAG, "pauseBatchedScan() is not supported"); + } + + @Override + public void restartBatchedScan() { + Log.w(TAG, "restartBatchedScan() is not supported"); + } + + private void handleScanTimeout() { + synchronized (mSettingsLock) { + Log.e(TAG, "Timed out waiting for scan result from wificond"); + reportScanFailure(WifiScanner.REASON_TIMEOUT); + mScanTimeoutListener = null; + } + } + + @Override + public boolean handleMessage(Message msg) { + switch(msg.what) { + case WifiMonitor.SCAN_FAILED_EVENT: + Log.w(TAG, "Scan failed: error code: " + msg.arg1); + cancelScanTimeout(); + reportScanFailure(msg.arg1); + break; + case WifiMonitor.PNO_SCAN_RESULTS_EVENT: + pollLatestScanDataForPno(); + break; + case WifiMonitor.SCAN_RESULTS_EVENT: + cancelScanTimeout(); + pollLatestScanData(); + break; + default: + // ignore unknown event + } + return true; + } + + private void cancelScanTimeout() { + synchronized (mSettingsLock) { + if (mScanTimeoutListener != null) { + mAlarmManager.cancel(mScanTimeoutListener); + mScanTimeoutListener = null; + } + } + } + + private void reportScanFailure(int errorCode) { + synchronized (mSettingsLock) { + if (mLastScanSettings != null) { + if (mLastScanSettings.singleScanEventHandler != null) { + mLastScanSettings.singleScanEventHandler + .onScanRequestFailed(errorCode); + } + mLastScanSettings = null; + } + } + } + + private void reportPnoScanFailure() { + synchronized (mSettingsLock) { + if (mLastPnoScanSettings != null) { + if (mLastPnoScanSettings.pnoScanEventHandler != null) { + mLastPnoScanSettings.pnoScanEventHandler.onPnoScanFailed(); + } + // Clean up PNO state, we don't want to continue PNO scanning. + mLastPnoScanSettings = null; + } + } + } + + private void pollLatestScanDataForPno() { + synchronized (mSettingsLock) { + if (mLastPnoScanSettings == null) { + // got a scan before we started scanning or after scan was canceled + return; + } + mNativePnoScanResults = mWifiNative.getPnoScanResults(getIfaceName()); + List hwPnoScanResults = new ArrayList<>(); + int numFilteredScanResults = 0; + for (int i = 0; i < mNativePnoScanResults.size(); ++i) { + ScanResult result = mNativePnoScanResults.get(i).getScanResult(); + // nanoseconds -> microseconds + if (result.timestamp >= mLastPnoScanSettings.startTimeNanos / 1_000) { + hwPnoScanResults.add(result); + } else { + numFilteredScanResults++; + } + } + + if (numFilteredScanResults != 0) { + Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results."); + } + + if (mLastPnoScanSettings.pnoScanEventHandler != null) { + ScanResult[] pnoScanResultsArray = + hwPnoScanResults.toArray(new ScanResult[hwPnoScanResults.size()]); + mLastPnoScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray); + } + } + } + + /** + * Return one of the WIFI_BAND_# values that was scanned for in this scan. + */ + private static int getScannedBandsInternal(ChannelCollection channelCollection) { + int bandsScanned = WifiScanner.WIFI_BAND_UNSPECIFIED; + + for (@WifiBandIndex int i = 0; i < WifiScanner.WIFI_BAND_COUNT; i++) { + if (channelCollection.containsBand(1 << i)) { + bandsScanned |= 1 << i; + } + } + return bandsScanned; + } + + private void pollLatestScanData() { + synchronized (mSettingsLock) { + if (mLastScanSettings == null) { + // got a scan before we started scanning or after scan was canceled + return; + } + + mNativeScanResults = mWifiNative.getScanResults(getIfaceName()); + List singleScanResults = new ArrayList<>(); + int numFilteredScanResults = 0; + for (int i = 0; i < mNativeScanResults.size(); ++i) { + ScanResult result = mNativeScanResults.get(i).getScanResult(); + // nanoseconds -> microseconds + if (result.timestamp >= mLastScanSettings.startTimeNanos / 1_000) { + // Allow even not explicitly requested 6Ghz results because they could be found + // via 6Ghz RNR. + if (mLastScanSettings.singleScanFreqs.containsChannel( + result.frequency) || ScanResult.is6GHz(result.frequency)) { + singleScanResults.add(result); + } else { + numFilteredScanResults++; + } + } else { + numFilteredScanResults++; + } + } + if (numFilteredScanResults != 0) { + Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results."); + } + + if (mLastScanSettings.singleScanEventHandler != null) { + if (mLastScanSettings.reportSingleScanFullResults) { + for (ScanResult scanResult : singleScanResults) { + // ignore buckets scanned since there is only one bucket for a single scan + mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult, + /* bucketsScanned */ 0); + } + } + Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR); + mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0, + getScannedBandsInternal(mLastScanSettings.singleScanFreqs), + singleScanResults.toArray(new ScanResult[singleScanResults.size()])); + mLastScanSettings.singleScanEventHandler + .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE); + } + + mLastScanSettings = null; + } + } + + + @Override + public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) { + return null; + } + + private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) { + return mWifiNative.startPnoScan(getIfaceName(), pnoSettings); + } + + private void stopHwPnoScan() { + mWifiNative.stopPnoScan(getIfaceName()); + } + + /** + * Hw Pno Scan is required only for disconnected PNO when the device supports it. + * @param isConnectedPno Whether this is connected PNO vs disconnected PNO. + * @return true if HW PNO scan is required, false otherwise. + */ + private boolean isHwPnoScanRequired(boolean isConnectedPno) { + return (!isConnectedPno + && mWifiGlobals.isBackgroundScanSupported()); + } + + @Override + public boolean setHwPnoList(WifiNative.PnoSettings settings, + WifiNative.PnoEventHandler eventHandler) { + synchronized (mSettingsLock) { + if (mLastPnoScanSettings != null) { + Log.w(TAG, "Already running a PNO scan"); + return false; + } + if (!isHwPnoScanRequired(settings.isConnected)) { + return false; + } + + mLastPnoScanSettings = new LastPnoScanSettings( + mClock.getElapsedSinceBootNanos(), + settings.networkList, eventHandler); + + if (!startHwPnoScan(settings)) { + Log.e(TAG, "Failed to start PNO scan"); + reportPnoScanFailure(); + } + return true; + } + } + + @Override + public boolean resetHwPnoList() { + synchronized (mSettingsLock) { + if (mLastPnoScanSettings == null) { + Log.w(TAG, "No PNO scan running"); + return false; + } + mLastPnoScanSettings = null; + // For wificond based PNO, we stop the scan immediately when we reset pno list. + stopHwPnoScan(); + return true; + } + } + + @Override + public boolean isHwPnoSupported(boolean isConnectedPno) { + // Hw Pno Scan is supported only for disconnected PNO when the device supports it. + return isHwPnoScanRequired(isConnectedPno); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (mSettingsLock) { + long nowMs = mClock.getElapsedSinceBootMillis(); + Log.d(TAG, "Latest native scan results nowMs = " + nowMs); + pw.println("Latest native scan results:"); + if (mNativeScanResults != null) { + List scanResults = mNativeScanResults.stream().map(r -> { + return r.getScanResult(); + }).collect(Collectors.toList()); + ScanResultUtil.dumpScanResults(pw, scanResults, nowMs); + } + pw.println("Latest native pno scan results:"); + if (mNativePnoScanResults != null) { + List pnoScanResults = mNativePnoScanResults.stream().map(r -> { + return r.getScanResult(); + }).collect(Collectors.toList()); + ScanResultUtil.dumpScanResults(pw, pnoScanResults, nowMs); + } + pw.println("Latest native scan results IEs:"); + if (mNativeScanResults != null) { + for (ScanDetail detail : mNativeScanResults) { + if (detail.getInformationElementRawData() != null) { + pw.println(NativeUtil.hexStringFromByteArray( + detail.getInformationElementRawData())); + } + } + } + pw.println(""); + } + } + + private static class LastScanSettings { + LastScanSettings(long startTimeNanos, + boolean reportSingleScanFullResults, + ChannelCollection singleScanFreqs, + WifiNative.ScanEventHandler singleScanEventHandler) { + this.startTimeNanos = startTimeNanos; + this.reportSingleScanFullResults = reportSingleScanFullResults; + this.singleScanFreqs = singleScanFreqs; + this.singleScanEventHandler = singleScanEventHandler; + } + + public long startTimeNanos; + public boolean reportSingleScanFullResults; + public ChannelCollection singleScanFreqs; + public WifiNative.ScanEventHandler singleScanEventHandler; + + } + + private static class LastPnoScanSettings { + LastPnoScanSettings(long startTimeNanos, + WifiNative.PnoNetwork[] pnoNetworkList, + WifiNative.PnoEventHandler pnoScanEventHandler) { + this.startTimeNanos = startTimeNanos; + this.pnoNetworkList = pnoNetworkList; + this.pnoScanEventHandler = pnoScanEventHandler; + } + + public long startTimeNanos; + public WifiNative.PnoNetwork[] pnoNetworkList; + public WifiNative.PnoEventHandler pnoScanEventHandler; + + } + +} diff --git a/aosp/packages/modules/common/build/allowed_deps.txt b/aosp/packages/modules/common/build/allowed_deps.txt new file mode 100644 index 000000000..0efebb57b --- /dev/null +++ b/aosp/packages/modules/common/build/allowed_deps.txt @@ -0,0 +1,1482 @@ +# A list of allowed dependencies for all updatable modules. +# +# The list tracks all direct and transitive dependencies that end up within any +# of the updatable binaries; specifically excluding external dependencies +# required to compile those binaries. This prevents potential regressions in +# case a new dependency is not aware of the different functional and +# non-functional requirements being part of an updatable module, for example +# setting correct min_sdk_version. +# +# To update the list, run: +# repo-root$ packages/modules/common/build/update-apex-allowed-deps.sh +# +# See go/apex-allowed-deps-error for more details. +# TODO(b/157465465): introduce automated quality signals and remove this list. + +aconfig_mediacodec_flags_c_lib(minSdkVersion:30) +android.hardware.audio.common-V1-ndk(minSdkVersion:31) +android.hardware.audio.common-V2-ndk(minSdkVersion:31) +android.hardware.audio.common-V3-ndk(minSdkVersion:31) +android.hardware.audio.common@5.0(minSdkVersion:30) +android.hardware.bluetooth-V1-ndk(minSdkVersion:33) +android.hardware.bluetooth.a2dp@1.0(minSdkVersion:30) +android.hardware.bluetooth.audio-V1-ndk(minSdkVersion:31) +android.hardware.bluetooth.audio-V2-ndk(minSdkVersion:31) +android.hardware.bluetooth.audio-V3-ndk(minSdkVersion:31) +android.hardware.bluetooth.audio-V4-ndk(minSdkVersion:31) +android.hardware.bluetooth.audio@2.0(minSdkVersion:30) +android.hardware.bluetooth.audio@2.1(minSdkVersion:30) +android.hardware.bluetooth@1.0(minSdkVersion:30) +android.hardware.bluetooth@1.1(minSdkVersion:30) +android.hardware.cas.native@1.0(minSdkVersion:29) +android.hardware.cas@1.0(minSdkVersion:29) +android.hardware.common-ndk_platform(minSdkVersion:29) +android.hardware.common-V2-ndk(minSdkVersion:29) +android.hardware.common-V2-ndk_platform(minSdkVersion:29) +android.hardware.common.fmq-V1-ndk(minSdkVersion:29) +android.hardware.graphics.allocator-V1-ndk(minSdkVersion:29) +android.hardware.graphics.allocator-V2-ndk(minSdkVersion:29) +android.hardware.graphics.allocator@2.0(minSdkVersion:29) +android.hardware.graphics.allocator@3.0(minSdkVersion:29) +android.hardware.graphics.allocator@4.0(minSdkVersion:29) +android.hardware.graphics.bufferqueue@1.0(minSdkVersion:29) +android.hardware.graphics.bufferqueue@2.0(minSdkVersion:29) +android.hardware.graphics.common-ndk_platform(minSdkVersion:29) +android.hardware.graphics.common-V2-ndk(minSdkVersion:29) +android.hardware.graphics.common-V2-ndk_platform(minSdkVersion:29) +android.hardware.graphics.common-V3-ndk(minSdkVersion:29) +android.hardware.graphics.common-V4-ndk(minSdkVersion:29) +android.hardware.graphics.common-V5-ndk(minSdkVersion:29) +android.hardware.graphics.common@1.0(minSdkVersion:29) +android.hardware.graphics.common@1.1(minSdkVersion:29) +android.hardware.graphics.common@1.2(minSdkVersion:29) +android.hardware.graphics.mapper@2.0(minSdkVersion:29) +android.hardware.graphics.mapper@2.1(minSdkVersion:29) +android.hardware.graphics.mapper@3.0(minSdkVersion:29) +android.hardware.graphics.mapper@4.0(minSdkVersion:29) +android.hardware.media.bufferpool2-V1-ndk(minSdkVersion:29) +android.hardware.media.bufferpool2-V2-ndk(minSdkVersion:29) +android.hardware.media.bufferpool@2.0(minSdkVersion:29) +android.hardware.media.c2-V1-ndk(minSdkVersion:30) +android.hardware.media.c2@1.0(minSdkVersion:29) +android.hardware.media.c2@1.1(minSdkVersion:29) +android.hardware.media.c2@1.2(minSdkVersion:29) +android.hardware.media.omx@1.0(minSdkVersion:29) +android.hardware.media@1.0(minSdkVersion:29) +android.hardware.neuralnetworks-V1-ndk(minSdkVersion:30) +android.hardware.neuralnetworks-V1-ndk_platform(minSdkVersion:30) +android.hardware.neuralnetworks-V2-ndk(minSdkVersion:30) +android.hardware.neuralnetworks-V3-ndk(minSdkVersion:30) +android.hardware.neuralnetworks-V4-ndk(minSdkVersion:30) +android.hardware.neuralnetworks@1.0(minSdkVersion:30) +android.hardware.neuralnetworks@1.1(minSdkVersion:30) +android.hardware.neuralnetworks@1.2(minSdkVersion:30) +android.hardware.neuralnetworks@1.3(minSdkVersion:30) +android.hardware.radio-V1.0-java(minSdkVersion:current) +android.hardware.radio.sap-V1-java(minSdkVersion:33) +android.hardware.security.rkp-V3-java(minSdkVersion:33) +android.hardware.tetheroffload.config-V1.0-java(minSdkVersion:current) +android.hardware.tetheroffload.control-V1.0-java(minSdkVersion:current) +android.hardware.tetheroffload.control-V1.1-java(minSdkVersion:current) +android.hardware.threadnetwork-V1-ndk(minSdkVersion:30) +android.hardware.uwb-V1-ndk(minSdkVersion:Tiramisu) +android.hardware.uwb-V1-rust(minSdkVersion:Tiramisu) +android.hardware.wifi-V1-java(minSdkVersion:30) +android.hardware.wifi-V1.0-java(minSdkVersion:30) +android.hardware.wifi-V1.0-java-constants(minSdkVersion:30) +android.hardware.wifi-V1.1-java(minSdkVersion:30) +android.hardware.wifi-V1.2-java(minSdkVersion:30) +android.hardware.wifi-V1.3-java(minSdkVersion:30) +android.hardware.wifi-V1.4-java(minSdkVersion:30) +android.hardware.wifi-V1.5-java(minSdkVersion:30) +android.hardware.wifi-V1.6-java(minSdkVersion:30) +android.hardware.wifi-V2-java(minSdkVersion:30) +android.hardware.wifi.common-V1-java(minSdkVersion:30) +android.hardware.wifi.hostapd-V1-java(minSdkVersion:30) +android.hardware.wifi.hostapd-V1.0-java(minSdkVersion:30) +android.hardware.wifi.hostapd-V1.1-java(minSdkVersion:30) +android.hardware.wifi.hostapd-V1.2-java(minSdkVersion:30) +android.hardware.wifi.hostapd-V1.3-java(minSdkVersion:30) +android.hardware.wifi.hostapd-V2-java(minSdkVersion:30) +android.hardware.wifi.supplicant-V1-java(minSdkVersion:30) +android.hardware.wifi.supplicant-V1.0-java(minSdkVersion:30) +android.hardware.wifi.supplicant-V1.1-java(minSdkVersion:30) +android.hardware.wifi.supplicant-V1.2-java(minSdkVersion:30) +android.hardware.wifi.supplicant-V1.3-java(minSdkVersion:30) +android.hardware.wifi.supplicant-V1.4-java(minSdkVersion:30) +android.hardware.wifi.supplicant-V2-java(minSdkVersion:30) +android.hardware.wifi.supplicant-V3-java(minSdkVersion:30) +android.hidl.allocator@1.0(minSdkVersion:29) +android.hidl.base-V1.0-java(minSdkVersion:current) +android.hidl.manager-V1.0-java(minSdkVersion:30) +android.hidl.manager-V1.1-java(minSdkVersion:30) +android.hidl.manager-V1.2-java(minSdkVersion:30) +android.hidl.memory.token@1.0(minSdkVersion:29) +android.hidl.memory@1.0(minSdkVersion:29) +android.hidl.safe_union@1.0(minSdkVersion:29) +android.hidl.token@1.0(minSdkVersion:29) +android.hidl.token@1.0-utils(minSdkVersion:29) +android.media.audio.common.types-V1-cpp(minSdkVersion:29) +android.media.audio.common.types-V1-ndk(minSdkVersion:29) +android.media.audio.common.types-V2-cpp(minSdkVersion:29) +android.media.audio.common.types-V2-ndk(minSdkVersion:29) +android.media.audio.common.types-V3-cpp(minSdkVersion:29) +android.media.audio.common.types-V3-ndk(minSdkVersion:29) +android.net.ipsec.ike.xml(minSdkVersion:(no version)) +android.net.wifi.flags-aconfig-java(minSdkVersion:30) +android.permission.flags-aconfig-java(minSdkVersion:30) +android.security.rkpd-ndk(minSdkVersion:33) +android.security.rkpd-rust(minSdkVersion:33) +android.service.notification.flags-aconfig-export-java(minSdkVersion:30) +android.system.suspend-V1-ndk(minSdkVersion:30) +android.system.suspend-V1-ndk(minSdkVersion:Tiramisu) +android.system.suspend.control-V1-ndk(minSdkVersion:30) +android_checker_annotation_stubs(minSdkVersion:current) +android_downloader_lib(minSdkVersion:30) +androidx-constraintlayout_constraintlayout(minSdkVersion:19) +androidx-constraintlayout_constraintlayout-core(minSdkVersion:24) +androidx-constraintlayout_constraintlayout-solver(minSdkVersion:24) +androidx.activity_activity(minSdkVersion:19) +androidx.activity_activity-compose(minSdkVersion:21) +androidx.activity_activity-ktx(minSdkVersion:19) +androidx.annotation_annotation(minSdkVersion:24) +androidx.annotation_annotation-experimental(minSdkVersion:19) +androidx.annotation_annotation-jvm(minSdkVersion:24) +androidx.appcompat_appcompat(minSdkVersion:19) +androidx.appcompat_appcompat-resources(minSdkVersion:19) +androidx.appsearch_appsearch(minSdkVersion:19) +androidx.appsearch_appsearch-platform-storage(minSdkVersion:19) +androidx.arch.core_core-common(minSdkVersion:24) +androidx.arch.core_core-runtime(minSdkVersion:19) +androidx.asynclayoutinflater_asynclayoutinflater(minSdkVersion:19) +androidx.autofill_autofill(minSdkVersion:19) +androidx.cardview_cardview(minSdkVersion:19) +androidx.collection_collection(minSdkVersion:24) +androidx.collection_collection-jvm(minSdkVersion:24) +androidx.collection_collection-ktx(minSdkVersion:24) +androidx.compose.animation_animation(minSdkVersion:21) +androidx.compose.animation_animation-android(minSdkVersion:21) +androidx.compose.animation_animation-core(minSdkVersion:21) +androidx.compose.animation_animation-core-android(minSdkVersion:21) +androidx.compose.foundation_foundation(minSdkVersion:21) +androidx.compose.foundation_foundation-android(minSdkVersion:21) +androidx.compose.foundation_foundation-layout(minSdkVersion:21) +androidx.compose.foundation_foundation-layout-android(minSdkVersion:21) +androidx.compose.material3_material3(minSdkVersion:21) +androidx.compose.material3_material3-android(minSdkVersion:21) +androidx.compose.material_material-icons-core(minSdkVersion:21) +androidx.compose.material_material-icons-core-android(minSdkVersion:21) +androidx.compose.material_material-ripple(minSdkVersion:21) +androidx.compose.material_material-ripple-android(minSdkVersion:21) +androidx.compose.runtime_runtime(minSdkVersion:21) +androidx.compose.runtime_runtime-android(minSdkVersion:21) +androidx.compose.runtime_runtime-livedata(minSdkVersion:21) +androidx.compose.runtime_runtime-saveable(minSdkVersion:21) +androidx.compose.runtime_runtime-saveable-android(minSdkVersion:21) +androidx.compose.ui_ui(minSdkVersion:21) +androidx.compose.ui_ui-android(minSdkVersion:21) +androidx.compose.ui_ui-geometry(minSdkVersion:21) +androidx.compose.ui_ui-geometry-android(minSdkVersion:21) +androidx.compose.ui_ui-graphics(minSdkVersion:21) +androidx.compose.ui_ui-graphics-android(minSdkVersion:21) +androidx.compose.ui_ui-text(minSdkVersion:21) +androidx.compose.ui_ui-text-android(minSdkVersion:21) +androidx.compose.ui_ui-unit(minSdkVersion:21) +androidx.compose.ui_ui-unit-android(minSdkVersion:21) +androidx.compose.ui_ui-util(minSdkVersion:21) +androidx.compose.ui_ui-util-android(minSdkVersion:21) +androidx.concurrent_concurrent-futures(minSdkVersion:24) +androidx.constraintlayout_constraintlayout-core(minSdkVersion:24) +androidx.coordinatorlayout_coordinatorlayout(minSdkVersion:19) +androidx.core.uwb.backend.aidl_interface-V1-java(minSdkVersion:30) +androidx.core.uwb.backend.aidl_interface-V2-java(minSdkVersion:30) +androidx.core_core(minSdkVersion:19) +androidx.core_core-ktx(minSdkVersion:19) +androidx.cursoradapter_cursoradapter(minSdkVersion:19) +androidx.customview_customview(minSdkVersion:19) +androidx.customview_customview-poolingcontainer(minSdkVersion:19) +androidx.documentfile_documentfile(minSdkVersion:19) +androidx.drawerlayout_drawerlayout(minSdkVersion:19) +androidx.dynamicanimation_dynamicanimation(minSdkVersion:19) +androidx.emoji2_emoji2(minSdkVersion:19) +androidx.emoji2_emoji2-views-helper(minSdkVersion:19) +androidx.exifinterface_exifinterface(minSdkVersion:19) +androidx.fragment_fragment(minSdkVersion:19) +androidx.fragment_fragment-ktx(minSdkVersion:19) +androidx.graphics_graphics-path(minSdkVersion:21) +androidx.interpolator_interpolator(minSdkVersion:19) +androidx.javascriptengine_javascriptengine(minSdkVersion:26) +androidx.leanback_leanback(minSdkVersion:19) +androidx.leanback_leanback-grid(minSdkVersion:19) +androidx.leanback_leanback-preference(minSdkVersion:21) +androidx.legacy_legacy-preference-v14(minSdkVersion:19) +androidx.legacy_legacy-support-core-ui(minSdkVersion:19) +androidx.legacy_legacy-support-core-utils(minSdkVersion:19) +androidx.legacy_legacy-support-v13(minSdkVersion:19) +androidx.legacy_legacy-support-v4(minSdkVersion:19) +androidx.lifecycle_lifecycle-common(minSdkVersion:24) +androidx.lifecycle_lifecycle-common-java8(minSdkVersion:24) +androidx.lifecycle_lifecycle-extensions(minSdkVersion:19) +androidx.lifecycle_lifecycle-livedata(minSdkVersion:19) +androidx.lifecycle_lifecycle-livedata-core(minSdkVersion:19) +androidx.lifecycle_lifecycle-livedata-core-ktx(minSdkVersion:19) +androidx.lifecycle_lifecycle-process(minSdkVersion:19) +androidx.lifecycle_lifecycle-runtime(minSdkVersion:19) +androidx.lifecycle_lifecycle-runtime-compose(minSdkVersion:21) +androidx.lifecycle_lifecycle-runtime-ktx(minSdkVersion:19) +androidx.lifecycle_lifecycle-service(minSdkVersion:19) +androidx.lifecycle_lifecycle-viewmodel(minSdkVersion:19) +androidx.lifecycle_lifecycle-viewmodel-compose(minSdkVersion:21) +androidx.lifecycle_lifecycle-viewmodel-ktx(minSdkVersion:19) +androidx.lifecycle_lifecycle-viewmodel-savedstate(minSdkVersion:19) +androidx.loader_loader(minSdkVersion:19) +androidx.localbroadcastmanager_localbroadcastmanager(minSdkVersion:19) +androidx.media_media(minSdkVersion:19) +androidx.navigation_navigation-common(minSdkVersion:19) +androidx.navigation_navigation-common-ktx(minSdkVersion:19) +androidx.navigation_navigation-compose(minSdkVersion:21) +androidx.navigation_navigation-fragment(minSdkVersion:19) +androidx.navigation_navigation-fragment-ktx(minSdkVersion:19) +androidx.navigation_navigation-runtime(minSdkVersion:19) +androidx.navigation_navigation-runtime-ktx(minSdkVersion:19) +androidx.navigation_navigation-ui(minSdkVersion:19) +androidx.navigation_navigation-ui-ktx(minSdkVersion:19) +androidx.preference_preference(minSdkVersion:19) +androidx.print_print(minSdkVersion:19) +androidx.profileinstaller_profileinstaller(minSdkVersion:19) +androidx.recyclerview_recyclerview(minSdkVersion:19) +androidx.recyclerview_recyclerview-selection(minSdkVersion:19) +androidx.resourceinspection_resourceinspection-annotation(minSdkVersion:24) +androidx.room_room-common(minSdkVersion:24) +androidx.room_room-ktx(minSdkVersion:19) +androidx.room_room-runtime(minSdkVersion:19) +androidx.savedstate_savedstate(minSdkVersion:19) +androidx.savedstate_savedstate-ktx(minSdkVersion:19) +androidx.slidingpanelayout_slidingpanelayout(minSdkVersion:19) +androidx.sqlite_sqlite(minSdkVersion:19) +androidx.sqlite_sqlite-framework(minSdkVersion:19) +androidx.startup_startup-runtime(minSdkVersion:19) +androidx.swiperefreshlayout_swiperefreshlayout(minSdkVersion:19) +androidx.tracing_tracing(minSdkVersion:19) +androidx.transition_transition(minSdkVersion:19) +androidx.vectordrawable_vectordrawable(minSdkVersion:19) +androidx.vectordrawable_vectordrawable-animated(minSdkVersion:19) +androidx.versionedparcelable_versionedparcelable(minSdkVersion:19) +androidx.viewpager2_viewpager2(minSdkVersion:19) +androidx.viewpager_viewpager(minSdkVersion:19) +androidx.wear.compose_compose-foundation(minSdkVersion:25) +androidx.wear.compose_compose-material(minSdkVersion:25) +androidx.wear.compose_compose-material-core(minSdkVersion:25) +androidx.wear.compose_compose-navigation(minSdkVersion:25) +androidx.wear_wear(minSdkVersion:23) +androidx.webkit_webkit(minSdkVersion:19) +androidx.window.extensions.core_core(minSdkVersion:19) +androidx.window.extensions.core_core(minSdkVersion:21) +androidx.window_window(minSdkVersion:19) +androidx.window_window(minSdkVersion:21) +androidx.work_work-runtime(minSdkVersion:19) +apache-commons-compress(minSdkVersion:29) +apache-commons-compress(minSdkVersion:current) +apache-commons-io(minSdkVersion:33) +apache-commons-lang(minSdkVersion:33) +apache-velocity-engine-core(minSdkVersion:33) +apache-xml(minSdkVersion:31) +audioclient-types-aidl-cpp(minSdkVersion:29) +audioflinger-aidl-cpp(minSdkVersion:29) +audiopolicy-aidl-cpp(minSdkVersion:29) +auto_value_annotations(minSdkVersion:19) +av-headers(minSdkVersion:29) +av-types-aidl-cpp(minSdkVersion:29) +avrcp_headers(minSdkVersion:30) +bcm_object(minSdkVersion:29) +bionic_libc_platform_headers(minSdkVersion:29) +boringssl_self_test(minSdkVersion:29) +bouncycastle(minSdkVersion:31) +bouncycastle-unbundled(minSdkVersion:30) +bpf_connectivity_headers(minSdkVersion:30) +bpf_headers(minSdkVersion:30) +bpf_syscall_wrappers(minSdkVersion:30) +brotli-java(minSdkVersion:29) +brotli-java(minSdkVersion:current) +captiveportal-lib(minSdkVersion:29) +captiveportal-lib(minSdkVersion:30) +car-rotary-lib(minSdkVersion:28) +car-rotary-lib-overlayable-resources(minSdkVersion:28) +car-rotary-lib-resources(minSdkVersion:28) +car-ui-lib(minSdkVersion:28) +car-ui-lib-overlayable-resources(minSdkVersion:28) +car-ui-lib-resources(minSdkVersion:28) +car-ui-lib-source(minSdkVersion:28) +car-ui-lib-source-no-overlayable(minSdkVersion:28) +cbor-java(minSdkVersion:30) +cellbroadcast-java-proto-lite(minSdkVersion:current) +CellBroadcastCommon(minSdkVersion:30) +census(minSdkVersion:30) +clatd(minSdkVersion:30) +codecs_g711dec(minSdkVersion:29) +com.android.media.audio-aconfig-cc(minSdkVersion:29) +com.android.media.audioserver-aconfig-cc(minSdkVersion:29) +com.android.vcard(minSdkVersion:9) +com.google.android.material_material(minSdkVersion:19) +com.uwb.support.aliro(minSdkVersion:30) +com.uwb.support.base(minSdkVersion:30) +com.uwb.support.ccc(minSdkVersion:30) +com.uwb.support.dltdoa(minSdkVersion:30) +com.uwb.support.fira(minSdkVersion:30) +com.uwb.support.generic(minSdkVersion:30) +com.uwb.support.multichip(minSdkVersion:30) +com.uwb.support.oemextension(minSdkVersion:30) +com.uwb.support.profile(minSdkVersion:30) +com.uwb.support.radar(minSdkVersion:30) +connectivity_native_aidl_interface-V1-java(minSdkVersion:30) +conscrypt(minSdkVersion:29) +core-libart(minSdkVersion:31) +core-oj(minSdkVersion:31) +crt_pad_segment(minSdkVersion:16) +crt_pad_segment(minSdkVersion:30) +crt_pad_segment(minSdkVersion:VanillaIceCream) +crtbegin_dynamic(minSdkVersion:16) +crtbegin_dynamic(minSdkVersion:apex_inherit) +crtbegin_dynamic1(minSdkVersion:apex_inherit) +crtbegin_so(minSdkVersion:16) +crtbegin_so(minSdkVersion:29) +crtbegin_so(minSdkVersion:30) +crtbegin_so(minSdkVersion:apex_inherit) +crtbegin_so(minSdkVersion:current) +crtbegin_so(minSdkVersion:VanillaIceCream) +crtbegin_so1(minSdkVersion:apex_inherit) +crtbrand(minSdkVersion:16) +crtbrand(minSdkVersion:29) +crtbrand(minSdkVersion:30) +crtbrand(minSdkVersion:apex_inherit) +crtbrand(minSdkVersion:current) +crtbrand(minSdkVersion:VanillaIceCream) +crtend_android(minSdkVersion:16) +crtend_android(minSdkVersion:apex_inherit) +crtend_so(minSdkVersion:16) +crtend_so(minSdkVersion:29) +crtend_so(minSdkVersion:30) +crtend_so(minSdkVersion:apex_inherit) +crtend_so(minSdkVersion:current) +crtend_so(minSdkVersion:VanillaIceCream) +dagger2(minSdkVersion:current) +datastallprotosnano(minSdkVersion:29) +dlmalloc(minSdkVersion:apex_inherit) +dnsproxyd_protocol_headers(minSdkVersion:29) +dnsproxyd_protocol_headers(minSdkVersion:30) +DocumentsUI-lib(minSdkVersion:29) +DocumentsUIManifestLib(minSdkVersion:29) +ethtool(minSdkVersion:30) +exoplayer-annotation_stubs(minSdkVersion:19) +exoplayer-media_apex(minSdkVersion:19) +exoplayer-mediaprovider-core(minSdkVersion:19) +exoplayer-mediaprovider-ui(minSdkVersion:19) +expresslog-catalog(minSdkVersion:30) +ExtServices(minSdkVersion:30) +ExtServices(minSdkVersion:current) +ExtServices-core(minSdkVersion:30) +ExtServices-core(minSdkVersion:current) +ExtServices-tplus(minSdkVersion:30) +ExtServices-tplus(minSdkVersion:current) +flatbuffer_headers(minSdkVersion:(no version)) +flatbuffer_headers(minSdkVersion:apex_inherit) +flatbuffers-java(minSdkVersion:30) +fmtlib(minSdkVersion:29) +fmtlib_headers(minSdkVersion:29) +fmtlib_ndk(minSdkVersion:29) +fp16_headers(minSdkVersion:30) +framework-bluetooth(minSdkVersion:33) +framework-mediaprovider(minSdkVersion:30) +framework-permission(minSdkVersion:30) +framework-permission(minSdkVersion:current) +framework-profiling(minSdkVersion:34) +framework-statsd(minSdkVersion:30) +framework-statsd(minSdkVersion:current) +framework-tethering(minSdkVersion:30) +framework-uwb(minSdkVersion:33) +framework-uwb(minSdkVersion:Tiramisu) +framework-wifi(minSdkVersion:30) +framework-wifi-util-lib(minSdkVersion:30) +gemmlowp_headers(minSdkVersion:(no version)) +gemmlowp_headers(minSdkVersion:apex_inherit) +geotz_common(minSdkVersion:31) +geotz_lookup(minSdkVersion:31) +geotz_s2storage_ro(minSdkVersion:31) +GoogleCellBroadcastApp(minSdkVersion:29) +GoogleCellBroadcastServiceModule(minSdkVersion:29) +GoogleExtServices(minSdkVersion:30) +GooglePermissionController(minSdkVersion:28) +GooglePermissionController(minSdkVersion:30) +gpr_base(minSdkVersion:30) +grpc-java-annotation-stubs(minSdkVersion:30) +grpc-java-api(minSdkVersion:30) +grpc-java-context(minSdkVersion:30) +grpc-java-core(minSdkVersion:30) +grpc-java-core-android(minSdkVersion:30) +grpc-java-core-internal(minSdkVersion:30) +grpc-java-core-util(minSdkVersion:30) +grpc-java-okhttp(minSdkVersion:30) +grpc-java-protobuf-lite(minSdkVersion:30) +grpc-java-stub(minSdkVersion:30) +guava(minSdkVersion:current) +guava-android-annotation-stubs(minSdkVersion:30) +gwp_asan_headers(minSdkVersion:S) +hilt_android(minSdkVersion:19) +hilt_core(minSdkVersion:current) +icing-java-proto-lite(minSdkVersion:current) +iconloader(minSdkVersion:21) +iconloader(minSdkVersion:26) +iconloader_sc_mainline_prod(minSdkVersion:26) +internal_include_headers(minSdkVersion:30) +ipmemorystore-aidl-interfaces-java(minSdkVersion:29) +ipmemorystore-aidl-interfaces-V10-java(minSdkVersion:29) +ipmemorystore-aidl-interfaces-V10-java(minSdkVersion:30) +jacocoagent(minSdkVersion:9) +jni_headers(minSdkVersion:29) +jni_platform_headers(minSdkVersion:S) +jsr305(minSdkVersion:14) +jsr330(minSdkVersion:current) +kotlinx-coroutines-android(minSdkVersion:28) +kotlinx-coroutines-android(minSdkVersion:current) +kotlinx-coroutines-core(minSdkVersion:current) +kotlinx-coroutines-core-jvm(minSdkVersion:28) +kotlinx_atomicfu(minSdkVersion:current) +kotlinx_coroutines(minSdkVersion:28) +kotlinx_coroutines_android(minSdkVersion:28) +ksoap2(minSdkVersion:30) +libaacextractor(minSdkVersion:29) +libaconfig_java_proto_lite(minSdkVersion:34) +libaconfig_java_proto_lite(minSdkVersion:UpsideDownCake) +libadbconnection_server(minSdkVersion:(no version)) +libadbconnection_server(minSdkVersion:30) +libadbd_core(minSdkVersion:(no version)) +libadbd_core(minSdkVersion:30) +libadbd_services(minSdkVersion:(no version)) +libadbd_services(minSdkVersion:30) +libaddress_sorting(minSdkVersion:30) +libaidlcommonsupport(minSdkVersion:29) +liballoc.rust_sysroot(minSdkVersion:29) +libalts_frame_protector(minSdkVersion:30) +libalts_util(minSdkVersion:30) +libamrextractor(minSdkVersion:29) +libandroid_log_sys(minSdkVersion:29) +libandroid_logger(minSdkVersion:29) +libanyhow(minSdkVersion:29) +libaom(minSdkVersion:29) +libapp_processes_protos_lite(minSdkVersion:(no version)) +libapp_processes_protos_lite(minSdkVersion:30) +libarect(minSdkVersion:29) +libarect_headers(minSdkVersion:29) +libasync_safe(minSdkVersion:apex_inherit) +libasyncio(minSdkVersion:(no version)) +libasyncio(minSdkVersion:apex_inherit) +libatomic(minSdkVersion:(no version)) +libaudio-a2dp-hw-utils(minSdkVersion:29) +libaudio_aidl_conversion_common_util_cpp(minSdkVersion:29) +libaudio_system_headers(minSdkVersion:29) +libaudioclient_headers(minSdkVersion:29) +libaudiofoundation_headers(minSdkVersion:29) +libaudioutils(minSdkVersion:29) +libaudioutils_fastmath(minSdkVersion:29) +libaudioutils_fixedfft(minSdkVersion:29) +libavcdec(minSdkVersion:29) +libavcenc(minSdkVersion:29) +libavservices_minijail(minSdkVersion:29) +libbacktrace(minSdkVersion:apex_inherit) +libbacktrace_headers(minSdkVersion:apex_inherit) +libbacktrace_rs.rust_sysroot(minSdkVersion:29) +libbacktrace_sys.rust_sysroot(minSdkVersion:29) +libbase(minSdkVersion:29) +libbase64_rust(minSdkVersion:29) +libbase_headers(minSdkVersion:29) +libbase_ndk(minSdkVersion:29) +libbinder_headers(minSdkVersion:29) +libbinder_headers_base(minSdkVersion:29) +libbinder_headers_platform_shared(minSdkVersion:29) +libbinder_ndk_bindgen(minSdkVersion:Tiramisu) +libbinder_ndk_sys(minSdkVersion:Tiramisu) +libbinder_rs(minSdkVersion:Tiramisu) +libbinder_tokio_rs(minSdkVersion:Tiramisu) +libbinderthreadstateutils(minSdkVersion:29) +libbitflags(minSdkVersion:29) +libbitflags-1.3.2(minSdkVersion:29) +libbluetooth-types(minSdkVersion:29) +libbluetooth-types-header(minSdkVersion:29) +libbluetooth_headers(minSdkVersion:30) +libbrotli(minSdkVersion:(no version)) +libbrotli(minSdkVersion:apex_inherit) +libbt-platform-protos-lite(minSdkVersion:30) +libbt_keystore_cc(minSdkVersion:30) +libbt_keystore_cc_headers(minSdkVersion:30) +libbtcore_headers(minSdkVersion:30) +libbuildversion(minSdkVersion:(no version)) +libbuildversion(minSdkVersion:26) +libbytes(minSdkVersion:29) +libc++(minSdkVersion:apex_inherit) +libc++_static(minSdkVersion:apex_inherit) +libc++abi(minSdkVersion:apex_inherit) +libc++demangle(minSdkVersion:apex_inherit) +libc++fs(minSdkVersion:apex_inherit) +libc_headers(minSdkVersion:apex_inherit) +libc_headers_arch(minSdkVersion:apex_inherit) +libc_llndk_headers(minSdkVersion:apex_inherit) +libc_scudo(minSdkVersion:apex_inherit) +libcap(minSdkVersion:29) +libcesu8(minSdkVersion:29) +libcfg_if(minSdkVersion:29) +libcfg_if.rust_sysroot(minSdkVersion:29) +libchrome(minSdkVersion:30) +libcodec2(minSdkVersion:29) +libcodec2_aidl(minSdkVersion:30) +libcodec2_hal_common(minSdkVersion:29) +libcodec2_hal_selection_static(minSdkVersion:29) +libcodec2_headers(minSdkVersion:29) +libcodec2_hidl@1.0(minSdkVersion:29) +libcodec2_hidl@1.1(minSdkVersion:29) +libcodec2_hidl@1.2(minSdkVersion:29) +libcodec2_internal(minSdkVersion:29) +libcodec2_soft_aacdec(minSdkVersion:29) +libcodec2_soft_aacenc(minSdkVersion:29) +libcodec2_soft_amrnbdec(minSdkVersion:29) +libcodec2_soft_amrnbenc(minSdkVersion:29) +libcodec2_soft_amrwbdec(minSdkVersion:29) +libcodec2_soft_amrwbenc(minSdkVersion:29) +libcodec2_soft_av1dec_dav1d(minSdkVersion:29) +libcodec2_soft_av1dec_gav1(minSdkVersion:29) +libcodec2_soft_av1enc(minSdkVersion:29) +libcodec2_soft_avcdec(minSdkVersion:29) +libcodec2_soft_avcenc(minSdkVersion:29) +libcodec2_soft_common(minSdkVersion:29) +libcodec2_soft_flacdec(minSdkVersion:29) +libcodec2_soft_flacenc(minSdkVersion:29) +libcodec2_soft_g711alawdec(minSdkVersion:29) +libcodec2_soft_g711mlawdec(minSdkVersion:29) +libcodec2_soft_gsmdec(minSdkVersion:29) +libcodec2_soft_h263dec(minSdkVersion:29) +libcodec2_soft_h263enc(minSdkVersion:29) +libcodec2_soft_hevcdec(minSdkVersion:29) +libcodec2_soft_hevcenc(minSdkVersion:29) +libcodec2_soft_mp3dec(minSdkVersion:29) +libcodec2_soft_mpeg2dec(minSdkVersion:29) +libcodec2_soft_mpeg4dec(minSdkVersion:29) +libcodec2_soft_mpeg4enc(minSdkVersion:29) +libcodec2_soft_opusdec(minSdkVersion:29) +libcodec2_soft_opusenc(minSdkVersion:29) +libcodec2_soft_rawdec(minSdkVersion:29) +libcodec2_soft_vorbisdec(minSdkVersion:29) +libcodec2_soft_vp8dec(minSdkVersion:29) +libcodec2_soft_vp8enc(minSdkVersion:29) +libcodec2_soft_vp9dec(minSdkVersion:29) +libcodec2_soft_vp9enc(minSdkVersion:29) +libcodec2_vndk(minSdkVersion:29) +libcom_android_networkstack_tethering_util_jni(minSdkVersion:30) +libcombine(minSdkVersion:29) +libcompiler_builtins.rust_sysroot(minSdkVersion:29) +libcore.rust_sysroot(minSdkVersion:29) +libcrypto(minSdkVersion:29) +libcrypto_static(minSdkVersion:(no version)) +libcrypto_static(minSdkVersion:29) +libcrypto_utils(minSdkVersion:(no version)) +libcrypto_utils(minSdkVersion:apex_inherit) +libcutils(minSdkVersion:29) +libcutils_bindgen(minSdkVersion:29) +libcutils_headers(minSdkVersion:29) +libcutils_sockets(minSdkVersion:29) +libcxx(minSdkVersion:29) +libcxxbridge05(minSdkVersion:29) +libdav1d(minSdkVersion:29) +libdav1d_16bit(minSdkVersion:29) +libdav1d_8bit(minSdkVersion:29) +libdexfile_external_headers(minSdkVersion:31) +libdexfile_support(minSdkVersion:31) +libdiagnose_usb(minSdkVersion:(no version)) +libdiagnose_usb(minSdkVersion:apex_inherit) +libdmabufheap(minSdkVersion:29) +libdmabufinfo(minSdkVersion:S) +libdoh_ffi(minSdkVersion:29) +libdoh_ffi(minSdkVersion:30) +libdowncast_rs(minSdkVersion:29) +libeigen(minSdkVersion:(no version)) +libeigen(minSdkVersion:apex_inherit) +libenv_logger(minSdkVersion:29) +liberror_headers(minSdkVersion:29) +libevent(minSdkVersion:30) +libexpat(minSdkVersion:apex_inherit) +libexpectedutils_headers(minSdkVersion:29) +libexpresslog_jni(minSdkVersion:30) +libextservices(minSdkVersion:30) +libextservices_jni(minSdkVersion:30) +libfft2d(minSdkVersion:30) +libfifo(minSdkVersion:29) +libFLAC(minSdkVersion:29) +libFLAC-config(minSdkVersion:29) +libFLAC-headers(minSdkVersion:29) +libflacextractor(minSdkVersion:29) +libflags_rust(minSdkVersion:33) +libflags_rust_cpp_bridge(minSdkVersion:33) +libflatbuffers-cpp(minSdkVersion:30) +libfmq(minSdkVersion:29) +libfmq-base(minSdkVersion:29) +libform_urlencoded(minSdkVersion:29) +libFraunhoferAAC(minSdkVersion:29) +libfstab(minSdkVersion:31) +libft2(minSdkVersion:30) +libfuse(minSdkVersion:30) +libfuse_jni(minSdkVersion:30) +libfutures(minSdkVersion:29) +libfutures_channel(minSdkVersion:29) +libfutures_core(minSdkVersion:29) +libfutures_executor(minSdkVersion:29) +libfutures_io(minSdkVersion:29) +libfutures_sink(minSdkVersion:29) +libfutures_task(minSdkVersion:29) +libfutures_util(minSdkVersion:29) +libgav1(minSdkVersion:29) +libgcc_stripped(minSdkVersion:(no version)) +libgetopts(minSdkVersion:29) +libgralloctypes(minSdkVersion:29) +libgrallocusage(minSdkVersion:29) +libgrpc(minSdkVersion:30) +libgrpc++(minSdkVersion:30) +libgrpc++_base(minSdkVersion:30) +libgrpc++_codegen_base_src(minSdkVersion:30) +libgrpc_base(minSdkVersion:30) +libgrpc_base_c(minSdkVersion:30) +libgrpc_bindgen(minSdkVersion:29) +libgrpc_census(minSdkVersion:30) +libgrpc_client_authority_filter(minSdkVersion:30) +libgrpc_client_channel(minSdkVersion:30) +libgrpc_deadline_filter(minSdkVersion:30) +libgrpc_http_filters(minSdkVersion:30) +libgrpc_lb_policy_grpclb_secure(minSdkVersion:30) +libgrpc_lb_policy_pick_first(minSdkVersion:30) +libgrpc_lb_policy_round_robin(minSdkVersion:30) +libgrpc_lb_policy_xds_secure(minSdkVersion:30) +libgrpc_max_age_filter(minSdkVersion:30) +libgrpc_message_size_filter(minSdkVersion:30) +libgrpc_resolver_dns_ares(minSdkVersion:30) +libgrpc_resolver_dns_native(minSdkVersion:30) +libgrpc_resolver_fake(minSdkVersion:30) +libgrpc_resolver_sockaddr(minSdkVersion:30) +libgrpc_secure(minSdkVersion:30) +libgrpc_server_backward_compatibility(minSdkVersion:30) +libgrpc_trace(minSdkVersion:30) +libgrpc_transport_chttp2(minSdkVersion:30) +libgrpc_transport_chttp2_alpn(minSdkVersion:30) +libgrpc_transport_chttp2_client_connector(minSdkVersion:30) +libgrpc_transport_chttp2_client_insecure(minSdkVersion:30) +libgrpc_transport_chttp2_client_secure(minSdkVersion:30) +libgrpc_transport_chttp2_server_insecure(minSdkVersion:30) +libgrpc_transport_chttp2_server_secure(minSdkVersion:30) +libgrpc_transport_inproc(minSdkVersion:30) +libgrpc_workaround_cronet_compression_filter(minSdkVersion:30) +libgrpc_wrap(minSdkVersion:29) +libgrpchealth_proto(minSdkVersion:30) +libgrpcio(minSdkVersion:29) +libgrpcio_sys(minSdkVersion:29) +libgrpclb_proto(minSdkVersion:30) +libgsi_headers(minSdkVersion:31) +libgsm(minSdkVersion:apex_inherit) +libgtest_prod(minSdkVersion:apex_inherit) +libgtest_prod_headers(minSdkVersion:apex_inherit) +libgui_bufferqueue_static(minSdkVersion:29) +libgui_headers(minSdkVersion:29) +libguiflags(minSdkVersion:29) +libhardware(minSdkVersion:29) +libhardware_headers(minSdkVersion:29) +libhashbrown.rust_sysroot(minSdkVersion:29) +libhevcdec(minSdkVersion:29) +libhevcenc(minSdkVersion:29) +libhidlbase(minSdkVersion:29) +libhidlmemory(minSdkVersion:29) +libhwbinder-impl-internal(minSdkVersion:29) +libhwbinder_headers(minSdkVersion:29) +libidna(minSdkVersion:29) +libimapper_providerutils(minSdkVersion:29) +libimapper_stablec(minSdkVersion:29) +libion(minSdkVersion:29) +libip_checksum(minSdkVersion:30) +libjni(minSdkVersion:29) +libjni_sys(minSdkVersion:29) +libjpeg(minSdkVersion:apex_inherit) +libjsoncpp(minSdkVersion:29) +libkll(minSdkVersion:30) +libkll-encoder(minSdkVersion:30) +libkll-protos(minSdkVersion:30) +liblazy_static(minSdkVersion:29) +libldacBT_abr(minSdkVersion:Tiramisu) +libldacBT_enc(minSdkVersion:Tiramisu) +liblibc(minSdkVersion:29) +liblibc.rust_sysroot(minSdkVersion:29) +libLibGuiProperties(minSdkVersion:29) +liblibm(minSdkVersion:29) +liblibz_sys(minSdkVersion:29) +liblock_api(minSdkVersion:29) +liblog_headers(minSdkVersion:29) +liblog_rust(minSdkVersion:29) +liblogger(minSdkVersion:29) +liblua(minSdkVersion:(no version)) +liblua(minSdkVersion:30) +liblz4(minSdkVersion:(no version)) +liblz4(minSdkVersion:apex_inherit) +liblzma(minSdkVersion:apex_inherit) +libmatches(minSdkVersion:29) +libmath(minSdkVersion:29) +libmath_headers(minSdkVersion:apex_inherit) +libmdnssd(minSdkVersion:(no version)) +libmdnssd(minSdkVersion:apex_inherit) +libmedia_codecserviceregistrant(minSdkVersion:29) +libmedia_datasource_headers(minSdkVersion:29) +libmedia_headers(minSdkVersion:29) +libmedia_helper(minSdkVersion:29) +libmedia_helper_headers(minSdkVersion:29) +libmedia_midiiowrapper(minSdkVersion:29) +libmediaformatshaper(minSdkVersion:29) +libmemchr(minSdkVersion:29) +libmeminfo(minSdkVersion:S) +libmemoffset(minSdkVersion:29) +libminijail(minSdkVersion:29) +libminijail_gen_constants(minSdkVersion:(no version)) +libminijail_gen_constants_obj(minSdkVersion:29) +libminijail_gen_syscall(minSdkVersion:(no version)) +libminijail_gen_syscall_obj(minSdkVersion:29) +libminijail_generated(minSdkVersion:29) +libmio(minSdkVersion:29) +libmkvextractor(minSdkVersion:29) +libmodpb64(minSdkVersion:30) +libmodules-utils-build(minSdkVersion:29) +libmp3extractor(minSdkVersion:29) +libmp4extractor(minSdkVersion:29) +libmpeg2dec(minSdkVersion:29) +libnanohttpd(minSdkVersion:30) +libnativebase_headers(minSdkVersion:29) +libnativebridge-headers(minSdkVersion:31) +libnativehelper_compat_libc++(minSdkVersion:(no version)) +libnativehelper_compat_libc++(minSdkVersion:29) +libnativehelper_header_only(minSdkVersion:29) +libnativeloader-headers(minSdkVersion:31) +libnativewindow_headers(minSdkVersion:29) +libnet_utils_device_common_bpfjni(minSdkVersion:30) +libnet_utils_device_common_bpfutils(minSdkVersion:30) +libnetdbinder_utils_headers(minSdkVersion:29) +libnetdutils(minSdkVersion:29) +libnetdutils(minSdkVersion:30) +libnetjniutils(minSdkVersion:29) +libnetjniutils(minSdkVersion:30) +libnetworkstackutilsjni(minSdkVersion:29) +libnetworkstackutilsjni(minSdkVersion:30) +libneuralnetworks_common(minSdkVersion:(no version)) +libneuralnetworks_common(minSdkVersion:30) +libneuralnetworks_headers(minSdkVersion:(no version)) +libneuralnetworks_headers(minSdkVersion:30) +libneuralnetworks_shim_static(minSdkVersion:30) +libnix(minSdkVersion:29) +libnum_cpus(minSdkVersion:29) +libnum_traits(minSdkVersion:29) +liboctets(minSdkVersion:29) +liboggextractor(minSdkVersion:29) +libonce_cell(minSdkVersion:29) +libopenjdkjvmti_headers(minSdkVersion:31) +libopus(minSdkVersion:29) +libpanic_abort.rust_sysroot(minSdkVersion:29) +libparking_lot(minSdkVersion:29) +libparking_lot_core(minSdkVersion:29) +libpdfium(minSdkVersion:apex_inherit) +libpdfium-agg(minSdkVersion:apex_inherit) +libpdfium-cmaps(minSdkVersion:apex_inherit) +libpdfium-constants(minSdkVersion:apex_inherit) +libpdfium-edit(minSdkVersion:apex_inherit) +libpdfium-fdrm(minSdkVersion:apex_inherit) +libpdfium-font(minSdkVersion:apex_inherit) +libpdfium-formfiller(minSdkVersion:apex_inherit) +libpdfium-fpdfdoc(minSdkVersion:apex_inherit) +libpdfium-fpdfsdk(minSdkVersion:apex_inherit) +libpdfium-fpdftext(minSdkVersion:apex_inherit) +libpdfium-fxcodec(minSdkVersion:apex_inherit) +libpdfium-fxcrt(minSdkVersion:apex_inherit) +libpdfium-fxge(minSdkVersion:apex_inherit) +libpdfium-fxjs(minSdkVersion:apex_inherit) +libpdfium-lcms2(minSdkVersion:apex_inherit) +libpdfium-libopenjpeg2(minSdkVersion:apex_inherit) +libpdfium-page(minSdkVersion:apex_inherit) +libpdfium-parser(minSdkVersion:apex_inherit) +libpdfium-pwl(minSdkVersion:apex_inherit) +libpdfium-render(minSdkVersion:apex_inherit) +libpdfium-skia_shared(minSdkVersion:apex_inherit) +libpdfium-third_party-base(minSdkVersion:apex_inherit) +libpdl_runtime(minSdkVersion:33) +libpercent_encoding(minSdkVersion:29) +libperfetto_client_experimental(minSdkVersion:30) +libperfetto_client_experimental(minSdkVersion:S) +libpin_project_lite(minSdkVersion:29) +libpin_utils(minSdkVersion:29) +libPlatformProperties(minSdkVersion:S) +libpng(minSdkVersion:30) +libpower(minSdkVersion:Tiramisu) +libproc_macro_nested(minSdkVersion:29) +libprocessgroup(minSdkVersion:29) +libprocessgroup_headers(minSdkVersion:29) +libprocinfo(minSdkVersion:apex_inherit) +libprocpartition(minSdkVersion:(no version)) +libprocpartition(minSdkVersion:30) +libprofile-clang-extras(minSdkVersion:(no version)) +libprofile-clang-extras_cfi_support(minSdkVersion:(no version)) +libprofile-clang-extras_ndk(minSdkVersion:(no version)) +libprofile-extras(minSdkVersion:(no version)) +libprofile-extras_ndk(minSdkVersion:(no version)) +libprofiler_builtins.rust_sysroot(minSdkVersion:29) +libprotobuf(minSdkVersion:29) +libprotobuf-c-nano(minSdkVersion:30) +libprotobuf-cpp-full(minSdkVersion:29) +libprotobuf-cpp-lite(minSdkVersion:29) +libprotobuf-cpp-lite-ndk(minSdkVersion:(no version)) +libprotobuf-cpp-lite-ndk(minSdkVersion:30) +libprotobuf-cpp-lite-ndk(minSdkVersion:33) +libprotobuf-java-lite(minSdkVersion:current) +libprotobuf-java-micro(minSdkVersion:8) +libprotobuf-java-nano(minSdkVersion:9) +libprotobuf_deprecated(minSdkVersion:29) +libprotoutil(minSdkVersion:(no version)) +libprotoutil(minSdkVersion:30) +libqemu_pipe(minSdkVersion:(no version)) +libquiche(minSdkVersion:29) +libring(minSdkVersion:29) +libring-core(minSdkVersion:29) +libring-test(minSdkVersion:29) +librkpd(minSdkVersion:33) +librustc_demangle(minSdkVersion:S) +librustc_demangle.rust_sysroot(minSdkVersion:29) +librustc_demangle_static(minSdkVersion:S) +librustutils(minSdkVersion:29) +libruy_static(minSdkVersion:30) +libscopeguard(minSdkVersion:29) +libserde(minSdkVersion:29) +libsfplugin_ccodec_utils(minSdkVersion:29) +libslab(minSdkVersion:29) +libsmallvec(minSdkVersion:29) +libsocket2(minSdkVersion:29) +libsonivoxwithoutjet(minSdkVersion:29) +libspeexresampler(minSdkVersion:29) +libspin(minSdkVersion:29) +libsqlite3_android_noicu(minSdkVersion:apex_inherit) +libsqlite_static_noicu(minSdkVersion:apex_inherit) +libssl(minSdkVersion:29) +libstagefright_aidl_bufferpool2(minSdkVersion:29) +libstagefright_amrnb_common(minSdkVersion:29) +libstagefright_amrnbdec(minSdkVersion:29) +libstagefright_amrnbenc(minSdkVersion:29) +libstagefright_amrwbdec(minSdkVersion:29) +libstagefright_amrwbenc(minSdkVersion:29) +libstagefright_bufferpool@2.0.1(minSdkVersion:29) +libstagefright_bufferqueue_helper(minSdkVersion:29) +libstagefright_bufferqueue_helper_novndk(minSdkVersion:29) +libstagefright_enc_common(minSdkVersion:29) +libstagefright_esds(minSdkVersion:29) +libstagefright_flacdec(minSdkVersion:29) +libstagefright_foundation(minSdkVersion:29) +libstagefright_foundation_colorutils_ndk(minSdkVersion:29) +libstagefright_foundation_headers(minSdkVersion:29) +libstagefright_foundation_without_imemory(minSdkVersion:29) +libstagefright_headers(minSdkVersion:29) +libstagefright_id3(minSdkVersion:29) +libstagefright_m4vh263dec(minSdkVersion:29) +libstagefright_m4vh263enc(minSdkVersion:29) +libstagefright_metadatautils(minSdkVersion:29) +libstagefright_mp3dec(minSdkVersion:29) +libstagefright_mp3dec_headers(minSdkVersion:29) +libstagefright_mpeg2extractor(minSdkVersion:29) +libstagefright_mpeg2support(minSdkVersion:29) +libstatic_assertions(minSdkVersion:29) +libstatspull_bindgen(minSdkVersion:apex_inherit) +libstatssocket_headers(minSdkVersion:29) +libstd(minSdkVersion:29) +libstd_detect.rust_sysroot(minSdkVersion:29) +libsync(minSdkVersion:(no version)) +libsystem_headers(minSdkVersion:apex_inherit) +libsystem_properties_bindgen(minSdkVersion:29) +libsystem_properties_bindgen_sys(minSdkVersion:29) +libsysutils(minSdkVersion:apex_inherit) +libtcutils(minSdkVersion:30) +libterm(minSdkVersion:29) +libtest(minSdkVersion:29) +libtextclassifier(minSdkVersion:(no version)) +libtextclassifier(minSdkVersion:30) +libtextclassifier-java(minSdkVersion:28) +libtextclassifier-java(minSdkVersion:current) +libtextclassifier_abseil(minSdkVersion:30) +libtextclassifier_bert_tokenizer(minSdkVersion:30) +libtextclassifier_flatbuffer_headers(minSdkVersion:30) +libtextclassifier_hash_headers(minSdkVersion:(no version)) +libtextclassifier_hash_headers(minSdkVersion:apex_inherit) +libtextclassifier_hash_static(minSdkVersion:(no version)) +libtextclassifier_hash_static(minSdkVersion:apex_inherit) +libtflite_kernel_utils(minSdkVersion:(no version)) +libtflite_kernel_utils(minSdkVersion:apex_inherit) +libtflite_static(minSdkVersion:(no version)) +libtflite_static(minSdkVersion:30) +libthiserror(minSdkVersion:29) +libtinyvec(minSdkVersion:29) +libtinyvec_macros(minSdkVersion:29) +libtinyxml2(minSdkVersion:S) +libtokio(minSdkVersion:29) +libtokio_stream(minSdkVersion:29) +libtsi(minSdkVersion:30) +libtsi_interface(minSdkVersion:30) +libui(minSdkVersion:29) +libui-types(minSdkVersion:apex_inherit) +libui_headers(minSdkVersion:29) +libunicode_bidi(minSdkVersion:29) +libunicode_normalization(minSdkVersion:29) +libunicode_width.rust_sysroot(minSdkVersion:29) +libuntrusted(minSdkVersion:29) +libunwind.rust_sysroot(minSdkVersion:29) +libunwind_llvm(minSdkVersion:apex_inherit) +libunwindstack(minSdkVersion:29) +liburl(minSdkVersion:29) +libutf(minSdkVersion:(no version)) +libutf(minSdkVersion:14) +libutils(minSdkVersion:29) +libutils(minSdkVersion:30) +libutils(minSdkVersion:apex_inherit) +libutils_binder(minSdkVersion:29) +libutils_binder(minSdkVersion:30) +libutils_binder(minSdkVersion:apex_inherit) +libutils_headers(minSdkVersion:29) +libutils_headers(minSdkVersion:30) +libutils_headers(minSdkVersion:apex_inherit) +libuwb_aconfig_flags_rust(minSdkVersion:33) +libuwb_uci_packets(minSdkVersion:Tiramisu) +libvendorsupport_llndk_headers(minSdkVersion:apex_inherit) +libvorbisidec(minSdkVersion:29) +libvpx(minSdkVersion:29) +libwavextractor(minSdkVersion:29) +libwebm(minSdkVersion:29) +libwebm_mkvparser(minSdkVersion:29) +libxml2(minSdkVersion:apex_inherit) +libyuv(minSdkVersion:29) +libyuv_static(minSdkVersion:29) +libz_static(minSdkVersion:apex_inherit) +libzeroize(minSdkVersion:Tiramisu) +libziparchive(minSdkVersion:apex_inherit) +libzstd(minSdkVersion:(no version)) +libzstd(minSdkVersion:apex_inherit) +lottie(minSdkVersion:19) +marisa-trie(minSdkVersion:30) +mdns_aidl_interface-V1-java(minSdkVersion:30) +media_ndk_headers(minSdkVersion:29) +media_plugin_headers(minSdkVersion:29) +MediaProvider(minSdkVersion:30) +MediaProviderGoogle(minSdkVersion:30) +mediaswcodec(minSdkVersion:29) +metrics-constants-protos(minSdkVersion:29) +metrics-constants-protos(minSdkVersion:current) +mobile-data-download-java-proto-lite(minSdkVersion:30) +mobile-data-download-populator-java-proto-lite(minSdkVersion:30) +mobile_data_downloader_lib(minSdkVersion:30) +modules-utils-backgroundthread(minSdkVersion:29) +modules-utils-binary-xml(minSdkVersion:29) +modules-utils-binary-xml(minSdkVersion:31) +modules-utils-build(minSdkVersion:29) +modules-utils-build_system(minSdkVersion:29) +modules-utils-bytesmatcher(minSdkVersion:29) +modules-utils-expresslog(minSdkVersion:30) +modules-utils-fastxmlserializer(minSdkVersion:29) +modules-utils-handlerexecutor(minSdkVersion:29) +modules-utils-list-slice(minSdkVersion:30) +modules-utils-locallog(minSdkVersion:30) +modules-utils-os(minSdkVersion:30) +modules-utils-package-state(minSdkVersion:31) +modules-utils-preconditions(minSdkVersion:29) +modules-utils-shell-command-handler(minSdkVersion:29) +modules-utils-statemachine(minSdkVersion:29) +modules-utils-synchronous-result-receiver(minSdkVersion:29) +modules-utils-uieventlogger-interface(minSdkVersion:29) +ndk_crtbegin_so.21(minSdkVersion:(no version)) +ndk_crtbegin_so.27(minSdkVersion:(no version)) +ndk_crtend_so.21(minSdkVersion:(no version)) +ndk_crtend_so.27(minSdkVersion:(no version)) +ndk_libc++_static(minSdkVersion:(no version)) +ndk_libc++_static(minSdkVersion:16) +ndk_libc++abi(minSdkVersion:(no version)) +ndk_libc++abi(minSdkVersion:16) +ndk_libunwind(minSdkVersion:16) +ndk_system(minSdkVersion:(no version)) +net-utils-device-common(minSdkVersion:29) +net-utils-device-common(minSdkVersion:30) +net-utils-device-common-bpf(minSdkVersion:29) +net-utils-device-common-bpf(minSdkVersion:30) +net-utils-device-common-ip(minSdkVersion:29) +net-utils-device-common-ip(minSdkVersion:30) +net-utils-device-common-netlink(minSdkVersion:29) +net-utils-device-common-netlink(minSdkVersion:30) +net-utils-device-common-struct(minSdkVersion:29) +net-utils-device-common-struct(minSdkVersion:30) +net-utils-framework-common(minSdkVersion:29) +net-utils-framework-common(minSdkVersion:30) +net-utils-framework-common(minSdkVersion:current) +net-utils-multicast-forwarding-structs(minSdkVersion:30) +net-utils-services-common(minSdkVersion:30) +netbpfload(minSdkVersion:30) +netd-client(minSdkVersion:29) +netd-client(minSdkVersion:30) +netd_aidl_interface-java(minSdkVersion:29) +netd_aidl_interface-lateststable-java(minSdkVersion:29) +netd_aidl_interface-lateststable-java(minSdkVersion:30) +netd_aidl_interface-lateststable-ndk(minSdkVersion:29) +netd_aidl_interface-V10-java(minSdkVersion:29) +netd_aidl_interface-V10-ndk(minSdkVersion:29) +netd_aidl_interface-V11-java(minSdkVersion:29) +netd_aidl_interface-V11-ndk(minSdkVersion:29) +netd_aidl_interface-V12-java(minSdkVersion:29) +netd_aidl_interface-V12-ndk(minSdkVersion:29) +netd_aidl_interface-V13-java(minSdkVersion:29) +netd_aidl_interface-V13-java(minSdkVersion:30) +netd_aidl_interface-V13-ndk(minSdkVersion:29) +netd_aidl_interface-V13-ndk(minSdkVersion:30) +netd_aidl_interface-V14-java(minSdkVersion:30) +netd_aidl_interface-V14-ndk(minSdkVersion:30) +netd_aidl_interface-V3-java(minSdkVersion:29) +netd_aidl_interface-V5-java(minSdkVersion:29) +netd_aidl_interface-V6-java(minSdkVersion:29) +netd_aidl_interface-V7-java(minSdkVersion:29) +netd_aidl_interface-V8-java(minSdkVersion:29) +netd_aidl_interface-V8-ndk(minSdkVersion:29) +netd_aidl_interface-V9-java(minSdkVersion:29) +netd_aidl_interface-V9-ndk(minSdkVersion:29) +netd_event_listener_interface-java(minSdkVersion:29) +netd_event_listener_interface-lateststable-java(minSdkVersion:29) +netd_event_listener_interface-lateststable-java(minSdkVersion:30) +netd_event_listener_interface-ndk_platform(minSdkVersion:29) +netd_event_listener_interface-V1-java(minSdkVersion:29) +netd_event_listener_interface-V1-java(minSdkVersion:30) +netd_event_listener_interface-V1-ndk(minSdkVersion:29) +netd_event_listener_interface-V1-ndk(minSdkVersion:30) +netd_event_listener_interface-V1-ndk_platform(minSdkVersion:29) +netd_mainline_headers(minSdkVersion:29) +netlink-client(minSdkVersion:29) +networkstack-aidl-interfaces-V10-java(minSdkVersion:29) +networkstack-aidl-interfaces-V11-java(minSdkVersion:29) +networkstack-aidl-interfaces-V12-java(minSdkVersion:29) +networkstack-aidl-interfaces-V13-java(minSdkVersion:29) +networkstack-aidl-interfaces-V14-java(minSdkVersion:29) +networkstack-aidl-interfaces-V15-java(minSdkVersion:29) +networkstack-aidl-interfaces-V16-java(minSdkVersion:29) +networkstack-aidl-interfaces-V17-java(minSdkVersion:29) +networkstack-aidl-interfaces-V18-java(minSdkVersion:29) +networkstack-aidl-interfaces-V18-java(minSdkVersion:30) +networkstack-aidl-interfaces-V19-java(minSdkVersion:30) +networkstack-aidl-interfaces-V20-java(minSdkVersion:30) +networkstack-aidl-interfaces-V21-java(minSdkVersion:30) +networkstack-aidl-latest(minSdkVersion:29) +networkstack-aidl-latest(minSdkVersion:30) +networkstack-client(minSdkVersion:29) +networkstack-client(minSdkVersion:30) +NetworkStackApi29Shims(minSdkVersion:29) +NetworkStackApi29Shims(minSdkVersion:30) +NetworkStackApi30Shims(minSdkVersion:29) +NetworkStackApi30Shims(minSdkVersion:30) +NetworkStackApi31Shims(minSdkVersion:29) +NetworkStackApi31Shims(minSdkVersion:30) +NetworkStackApi33Shims(minSdkVersion:29) +NetworkStackApi33Shims(minSdkVersion:30) +NetworkStackApi34Shims(minSdkVersion:29) +NetworkStackApi34Shims(minSdkVersion:30) +NetworkStackApi35Shims(minSdkVersion:29) +NetworkStackApi35Shims(minSdkVersion:30) +NetworkStackApiCurrentLib(minSdkVersion:29) +NetworkStackApiCurrentLib(minSdkVersion:30) +NetworkStackApiCurrentShims(minSdkVersion:29) +NetworkStackApiCurrentShims(minSdkVersion:30) +NetworkStackApiStableLib(minSdkVersion:29) +NetworkStackApiStableLib(minSdkVersion:30) +NetworkStackApiStableShims(minSdkVersion:29) +NetworkStackApiStableShims(minSdkVersion:30) +NetworkStackNextManifestBase(minSdkVersion:29) +NetworkStackNextManifestBase(minSdkVersion:30) +networkstackprotos(minSdkVersion:29) +NetworkStackShimsCommon(minSdkVersion:29) +NetworkStackShimsCommon(minSdkVersion:30) +neuralnetworks_supportlibrary_loader(minSdkVersion:29) +neuralnetworks_types(minSdkVersion:30) +neuralnetworks_utils_hal_1_0(minSdkVersion:30) +neuralnetworks_utils_hal_1_1(minSdkVersion:30) +neuralnetworks_utils_hal_1_2(minSdkVersion:30) +neuralnetworks_utils_hal_1_3(minSdkVersion:30) +neuralnetworks_utils_hal_aidl(minSdkVersion:30) +neuralnetworks_utils_hal_common(minSdkVersion:30) +neuralnetworks_utils_hal_service(minSdkVersion:30) +note_memtag_heap_async(minSdkVersion:16) +note_memtag_heap_sync(minSdkVersion:16) +offlinelocationtimezoneprovider(minSdkVersion:31) +okhttp(minSdkVersion:31) +okhttp-norepackage(minSdkVersion:30) +okio-lib(minSdkVersion:30) +opencensus-java-api(minSdkVersion:33) +opencensus-java-api(minSdkVersion:current) +opencensus-java-contrib-grpc-metrics(minSdkVersion:33) +opencensus-java-contrib-grpc-metrics(minSdkVersion:current) +OsuLoginGoogle(minSdkVersion:30) +owasp-java-encoder(minSdkVersion:33) +pdfium-headers(minSdkVersion:apex_inherit) +pdfium-third-party-headers(minSdkVersion:apex_inherit) +perfetto_trace_protos(minSdkVersion:S) +perfmark(minSdkVersion:30) +perfmark-impl(minSdkVersion:30) +perfmark-link(minSdkVersion:30) +perfmark-stringfunction(minSdkVersion:30) +perfmark-tag(minSdkVersion:30) +PermissionController(minSdkVersion:28) +PermissionController(minSdkVersion:30) +permissioncontroller-protos(minSdkVersion:30) +permissioncontroller-statsd(minSdkVersion:current) +philox_random(minSdkVersion:(no version)) +philox_random(minSdkVersion:30) +philox_random_headers(minSdkVersion:(no version)) +philox_random_headers(minSdkVersion:30) +PhotopickerGoogle(minSdkVersion:30) +PhotopickerLib(minSdkVersion:30) +PlatformProperties(minSdkVersion:current) +prebuilt_androidx-constraintlayout_constraintlayout-core-nodeps(minSdkVersion:29) +prebuilt_androidx-constraintlayout_constraintlayout-nodeps(minSdkVersion:(no version)) +prebuilt_androidx-constraintlayout_constraintlayout-nodeps(minSdkVersion:19) +prebuilt_androidx-constraintlayout_constraintlayout-solver-nodeps(minSdkVersion:24) +prebuilt_androidx-constraintlayout_constraintlayout-solver-nodeps(minSdkVersion:current) +prebuilt_androidx.activity_activity-compose-nodeps(minSdkVersion:21) +prebuilt_androidx.activity_activity-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.activity_activity-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.activity_activity-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.activity_activity-nodeps(minSdkVersion:19) +prebuilt_androidx.annotation_annotation-experimental-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.annotation_annotation-experimental-nodeps(minSdkVersion:19) +prebuilt_androidx.annotation_annotation-jvm-nodeps(minSdkVersion:24) +prebuilt_androidx.annotation_annotation-nodeps(minSdkVersion:24) +prebuilt_androidx.annotation_annotation-nodeps(minSdkVersion:30) +prebuilt_androidx.annotation_annotation-nodeps(minSdkVersion:current) +prebuilt_androidx.appcompat_appcompat-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.appcompat_appcompat-nodeps(minSdkVersion:19) +prebuilt_androidx.appcompat_appcompat-resources-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.appcompat_appcompat-resources-nodeps(minSdkVersion:19) +prebuilt_androidx.appsearch_appsearch-nodeps(minSdkVersion:19) +prebuilt_androidx.appsearch_appsearch-platform-storage-nodeps(minSdkVersion:19) +prebuilt_androidx.arch.core_core-common-nodeps(minSdkVersion:24) +prebuilt_androidx.arch.core_core-common-nodeps(minSdkVersion:30) +prebuilt_androidx.arch.core_core-common-nodeps(minSdkVersion:current) +prebuilt_androidx.arch.core_core-runtime-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.arch.core_core-runtime-nodeps(minSdkVersion:19) +prebuilt_androidx.asynclayoutinflater_asynclayoutinflater-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.asynclayoutinflater_asynclayoutinflater-nodeps(minSdkVersion:19) +prebuilt_androidx.autofill_autofill-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.autofill_autofill-nodeps(minSdkVersion:19) +prebuilt_androidx.cardview_cardview-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.cardview_cardview-nodeps(minSdkVersion:19) +prebuilt_androidx.collection_collection-jvm-nodeps(minSdkVersion:24) +prebuilt_androidx.collection_collection-ktx-nodeps(minSdkVersion:24) +prebuilt_androidx.collection_collection-ktx-nodeps(minSdkVersion:30) +prebuilt_androidx.collection_collection-ktx-nodeps(minSdkVersion:current) +prebuilt_androidx.collection_collection-nodeps(minSdkVersion:24) +prebuilt_androidx.collection_collection-nodeps(minSdkVersion:30) +prebuilt_androidx.collection_collection-nodeps(minSdkVersion:current) +prebuilt_androidx.compose.animation_animation-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.animation_animation-core-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.animation_animation-core-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.animation_animation-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.foundation_foundation-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.foundation_foundation-layout-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.foundation_foundation-layout-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.foundation_foundation-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.material3_material3-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.material3_material3-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.material_material-icons-core-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.material_material-icons-core-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.material_material-ripple-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.material_material-ripple-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.runtime_runtime-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.runtime_runtime-livedata-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.runtime_runtime-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.runtime_runtime-saveable-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.runtime_runtime-saveable-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-geometry-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-geometry-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-graphics-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-graphics-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-text-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-text-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-unit-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-unit-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-util-android-nodeps(minSdkVersion:21) +prebuilt_androidx.compose.ui_ui-util-nodeps(minSdkVersion:21) +prebuilt_androidx.concurrent_concurrent-futures-nodeps(minSdkVersion:24) +prebuilt_androidx.constraintlayout_constraintlayout-core-nodeps(minSdkVersion:24) +prebuilt_androidx.coordinatorlayout_coordinatorlayout-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.coordinatorlayout_coordinatorlayout-nodeps(minSdkVersion:19) +prebuilt_androidx.core_core-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.core_core-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.core_core-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.core_core-nodeps(minSdkVersion:19) +prebuilt_androidx.cursoradapter_cursoradapter-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.cursoradapter_cursoradapter-nodeps(minSdkVersion:19) +prebuilt_androidx.customview_customview-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.customview_customview-nodeps(minSdkVersion:19) +prebuilt_androidx.customview_customview-poolingcontainer-nodeps(minSdkVersion:19) +prebuilt_androidx.documentfile_documentfile-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.documentfile_documentfile-nodeps(minSdkVersion:19) +prebuilt_androidx.drawerlayout_drawerlayout-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.drawerlayout_drawerlayout-nodeps(minSdkVersion:19) +prebuilt_androidx.dynamicanimation_dynamicanimation-nodeps(minSdkVersion:19) +prebuilt_androidx.emoji2_emoji-nodeps(minSdkVersion:19) +prebuilt_androidx.emoji2_emoji2-nodeps(minSdkVersion:19) +prebuilt_androidx.emoji2_emoji2-views-helpe-nodeps(minSdkVersion:19) +prebuilt_androidx.emoji2_emoji2-views-helper-nodeps(minSdkVersion:19) +prebuilt_androidx.exifinterface_exifinterface-nodeps(minSdkVersion:19) +prebuilt_androidx.fragment_fragment-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.fragment_fragment-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.fragment_fragment-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.fragment_fragment-nodeps(minSdkVersion:19) +prebuilt_androidx.graphics_graphics-path-nodeps(minSdkVersion:21) +prebuilt_androidx.interpolator_interpolator-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.interpolator_interpolator-nodeps(minSdkVersion:19) +prebuilt_androidx.javascriptengine_javascriptengine-nodeps(minSdkVersion:26) +prebuilt_androidx.leanback_leanback-grid-nodeps(minSdkVersion:19) +prebuilt_androidx.leanback_leanback-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.leanback_leanback-nodeps(minSdkVersion:19) +prebuilt_androidx.leanback_leanback-preference-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.leanback_leanback-preference-nodeps(minSdkVersion:21) +prebuilt_androidx.legacy_legacy-support-core-ui-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.legacy_legacy-support-core-ui-nodeps(minSdkVersion:19) +prebuilt_androidx.legacy_legacy-support-core-utils-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.legacy_legacy-support-core-utils-nodeps(minSdkVersion:19) +prebuilt_androidx.legacy_legacy-support-v13-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.legacy_legacy-support-v13-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-common-java8-nodeps(minSdkVersion:24) +prebuilt_androidx.lifecycle_lifecycle-common-java8-nodeps(minSdkVersion:30) +prebuilt_androidx.lifecycle_lifecycle-common-java8-nodeps(minSdkVersion:current) +prebuilt_androidx.lifecycle_lifecycle-common-nodeps(minSdkVersion:24) +prebuilt_androidx.lifecycle_lifecycle-common-nodeps(minSdkVersion:30) +prebuilt_androidx.lifecycle_lifecycle-common-nodeps(minSdkVersion:current) +prebuilt_androidx.lifecycle_lifecycle-extensions-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-extensions-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-livedata-core-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-livedata-core-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-livedata-core-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-livedata-core-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-livedata-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-livedata-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-process-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-process-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-runtime-compose-nodeps(minSdkVersion:21) +prebuilt_androidx.lifecycle_lifecycle-runtime-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-runtime-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-runtime-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-runtime-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-service-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-service-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-viewmodel-compose-nodeps(minSdkVersion:21) +prebuilt_androidx.lifecycle_lifecycle-viewmodel-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-viewmodel-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-viewmodel-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-viewmodel-nodeps(minSdkVersion:19) +prebuilt_androidx.lifecycle_lifecycle-viewmodel-savedstate-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.lifecycle_lifecycle-viewmodel-savedstate-nodeps(minSdkVersion:19) +prebuilt_androidx.loader_loader-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.loader_loader-nodeps(minSdkVersion:19) +prebuilt_androidx.localbroadcastmanager_localbroadcastmanager-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.localbroadcastmanager_localbroadcastmanager-nodeps(minSdkVersion:19) +prebuilt_androidx.media_media-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.media_media-nodeps(minSdkVersion:19) +prebuilt_androidx.navigation_navigation-common-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.navigation_navigation-common-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.navigation_navigation-common-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.navigation_navigation-common-nodeps(minSdkVersion:19) +prebuilt_androidx.navigation_navigation-compose-nodeps(minSdkVersion:21) +prebuilt_androidx.navigation_navigation-fragment-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.navigation_navigation-fragment-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.navigation_navigation-fragment-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.navigation_navigation-fragment-nodeps(minSdkVersion:19) +prebuilt_androidx.navigation_navigation-runtime-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.navigation_navigation-runtime-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.navigation_navigation-runtime-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.navigation_navigation-runtime-nodeps(minSdkVersion:19) +prebuilt_androidx.navigation_navigation-ui-ktx-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.navigation_navigation-ui-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.navigation_navigation-ui-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.navigation_navigation-ui-nodeps(minSdkVersion:19) +prebuilt_androidx.preference_preference-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.preference_preference-nodeps(minSdkVersion:19) +prebuilt_androidx.print_print-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.print_print-nodeps(minSdkVersion:19) +prebuilt_androidx.profileinstaller_profileinstaller-nodeps(minSdkVersion:19) +prebuilt_androidx.recyclerview_recyclerview-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.recyclerview_recyclerview-nodeps(minSdkVersion:19) +prebuilt_androidx.recyclerview_recyclerview-selection-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.recyclerview_recyclerview-selection-nodeps(minSdkVersion:19) +prebuilt_androidx.resourceinspection_resourceinspection-annotation-nodeps(minSdkVersion:24) +prebuilt_androidx.room_room-common-nodeps(minSdkVersion:24) +prebuilt_androidx.room_room-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.room_room-runtime-nodeps(minSdkVersion:19) +prebuilt_androidx.savedstate_savedstate-ktx-nodeps(minSdkVersion:19) +prebuilt_androidx.savedstate_savedstate-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.savedstate_savedstate-nodeps(minSdkVersion:19) +prebuilt_androidx.slidingpanelayout_slidingpanelayout-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.slidingpanelayout_slidingpanelayout-nodeps(minSdkVersion:19) +prebuilt_androidx.sqlite_sqlite-framework-nodeps(minSdkVersion:19) +prebuilt_androidx.sqlite_sqlite-nodeps(minSdkVersion:19) +prebuilt_androidx.startup_startup-runtime-nodeps(minSdkVersion:19) +prebuilt_androidx.swiperefreshlayout_swiperefreshlayout-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.swiperefreshlayout_swiperefreshlayout-nodeps(minSdkVersion:19) +prebuilt_androidx.tracing_tracing-nodeps(minSdkVersion:19) +prebuilt_androidx.transition_transition-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.transition_transition-nodeps(minSdkVersion:19) +prebuilt_androidx.vectordrawable_vectordrawable-animated-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.vectordrawable_vectordrawable-animated-nodeps(minSdkVersion:19) +prebuilt_androidx.vectordrawable_vectordrawable-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.vectordrawable_vectordrawable-nodeps(minSdkVersion:19) +prebuilt_androidx.versionedparcelable_versionedparcelable-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.versionedparcelable_versionedparcelable-nodeps(minSdkVersion:19) +prebuilt_androidx.viewpager2_viewpager2-nodeps(minSdkVersion:19) +prebuilt_androidx.viewpager_viewpager-nodeps(minSdkVersion:(no version)) +prebuilt_androidx.viewpager_viewpager-nodeps(minSdkVersion:19) +prebuilt_androidx.wear.compose_compose-foundation-nodeps(minSdkVersion:25) +prebuilt_androidx.wear.compose_compose-material-core-nodeps(minSdkVersion:25) +prebuilt_androidx.wear.compose_compose-material-nodeps(minSdkVersion:25) +prebuilt_androidx.wear.compose_compose-navigation-nodeps(minSdkVersion:25) +prebuilt_androidx.wear_wear-nodeps(minSdkVersion:23) +prebuilt_androidx.webkit_webkit-nodeps(minSdkVersion:19) +prebuilt_androidx.window.extensions.core_core-nodeps(minSdkVersion:19) +prebuilt_androidx.window.extensions.core_core-nodeps(minSdkVersion:21) +prebuilt_androidx.window_window-nodeps(minSdkVersion:19) +prebuilt_androidx.window_window-nodeps(minSdkVersion:21) +prebuilt_androidx.work_work-runtime-nodeps(minSdkVersion:19) +prebuilt_asm-7.0(minSdkVersion:current) +prebuilt_asm-9.2(minSdkVersion:current) +prebuilt_asm-commons-7.0(minSdkVersion:current) +prebuilt_asm-commons-9.2(minSdkVersion:current) +prebuilt_asm-tree-7.0(minSdkVersion:current) +prebuilt_asm-tree-9.2(minSdkVersion:current) +prebuilt_bionic_libc_platform_headers(minSdkVersion:(no version)) +prebuilt_car-ui-lib-nodeps(minSdkVersion:28) +prebuilt_car-ui-lib-source(minSdkVersion:28) +prebuilt_com.google.android.material_material-nodeps(minSdkVersion:(no version)) +prebuilt_com.google.android.material_material-nodeps(minSdkVersion:19) +prebuilt_crt_pad_segment(minSdkVersion:(no version)) +prebuilt_crtbegin_dynamic(minSdkVersion:(no version)) +prebuilt_crtbegin_so(minSdkVersion:(no version)) +prebuilt_crtend_android(minSdkVersion:(no version)) +prebuilt_crtend_so(minSdkVersion:(no version)) +prebuilt_error_prone_annotations(minSdkVersion:(no version)) +prebuilt_error_prone_annotations(minSdkVersion:29) +prebuilt_error_prone_annotations(minSdkVersion:current) +prebuilt_firebase-common-aar(minSdkVersion:14) +prebuilt_firebase-components-aar(minSdkVersion:14) +prebuilt_firebase-datatransport-aar(minSdkVersion:14) +prebuilt_firebase-encoders-jar(minSdkVersion:33) +prebuilt_firebase-encoders-json-aar(minSdkVersion:14) +prebuilt_firebase-iid-aar(minSdkVersion:14) +prebuilt_firebase-iid-interop-aar(minSdkVersion:14) +prebuilt_firebase-installations-aar(minSdkVersion:14) +prebuilt_firebase-installations-interop-aar(minSdkVersion:14) +prebuilt_firebase-measurement-connector-aar(minSdkVersion:14) +prebuilt_firebase-messaging-aar(minSdkVersion:14) +prebuilt_glide-annotation-and-compiler-prebuilt(minSdkVersion:14) +prebuilt_glide-disklrucache-prebuilt(minSdkVersion:14) +prebuilt_glide-gifdecoder-prebuilt(minSdkVersion:14) +prebuilt_glide-integration-recyclerview-prebuilt(minSdkVersion:14) +prebuilt_glide-integration-webpdecoder-prebuilt(minSdkVersion:14) +prebuilt_glide-prebuilt(minSdkVersion:14) +prebuilt_guava-listenablefuture-prebuilt-jar(minSdkVersion:29) +prebuilt_guava-listenablefuture-prebuilt-jar(minSdkVersion:current) +prebuilt_jni_headers(minSdkVersion:(no version)) +prebuilt_kotlin-stdlib(minSdkVersion:current) +prebuilt_kotlin-stdlib-jdk8(minSdkVersion:current) +prebuilt_kotlinx-coroutines-android-nodeps(minSdkVersion:(no version)) +prebuilt_kotlinx-coroutines-android-nodeps(minSdkVersion:current) +prebuilt_kotlinx-coroutines-core-nodeps(minSdkVersion:(no version)) +prebuilt_kotlinx-coroutines-core-nodeps(minSdkVersion:current) +prebuilt_libasync_safe(minSdkVersion:(no version)) +prebuilt_libc_headers(minSdkVersion:(no version)) +prebuilt_libcap(minSdkVersion:(no version)) +prebuilt_libclang_rt.builtins(minSdkVersion:(no version)) +prebuilt_libclang_rt.ubsan_minimal(minSdkVersion:(no version)) +prebuilt_libnativehelper_compat_libc++(minSdkVersion:(no version)) +prebuilt_libnativehelper_header_only(minSdkVersion:(no version)) +prebuilt_libperfetto_client_experimental(minSdkVersion:(no version)) +prebuilt_libunwind(minSdkVersion:(no version)) +prebuilt_perfetto_trace_protos(minSdkVersion:(no version)) +prebuilt_play-services-basement-aar(minSdkVersion:14) +prebuilt_play-services-cloud-messaging-aar(minSdkVersion:14) +prebuilt_play-services-stats-aar(minSdkVersion:14) +prebuilt_play-services-tasks-aar(minSdkVersion:14) +prebuilt_test_framework-sdkextensions(minSdkVersion:(no version)) +prebuilt_transport-api-aar(minSdkVersion:14) +prebuilt_transport-backend-cct-aar(minSdkVersion:14) +prebuilt_transport-runtime-aar(minSdkVersion:14) +resourceobserver_aidl_interface-V1-ndk(minSdkVersion:29) +resourceobserver_aidl_interface-V1-ndk_platform(minSdkVersion:29) +rkpd(minSdkVersion:33) +s2-geometry-library-java(minSdkVersion:30) +s2storage_ro(minSdkVersion:31) +sap-api-java-static(minSdkVersion:30) +server_configurable_flags(minSdkVersion:29) +service-entitlement(minSdkVersion:29) +service-entitlement-api(minSdkVersion:29) +service-entitlement-data(minSdkVersion:29) +service-entitlement-impl(minSdkVersion:29) +ServiceWifiResourcesGoogle(minSdkVersion:30) +SettingsLibActionBarShadow(minSdkVersion:21) +SettingsLibActionBarShadow(minSdkVersion:28) +SettingsLibActivityEmbedding(minSdkVersion:21) +SettingsLibAppPreference(minSdkVersion:21) +SettingsLibBarChartPreference(minSdkVersion:21) +SettingsLibCollapsingToolbarBaseActivity(minSdkVersion:29) +SettingsLibColor(minSdkVersion:28) +SettingsLibFooterPreference(minSdkVersion:21) +SettingsLibHelpUtils(minSdkVersion:21) +SettingsLibIllustrationPreference(minSdkVersion:28) +SettingsLibLayoutPreference(minSdkVersion:21) +SettingsLibMainSwitchPreference(minSdkVersion:28) +SettingsLibProfileSelector(minSdkVersion:23) +SettingsLibProgressBar(minSdkVersion:21) +SettingsLibRadioButtonPreference(minSdkVersion:21) +SettingsLibRestrictedLockUtils(minSdkVersion:21) +SettingsLibSearchWidget(minSdkVersion:21) +SettingsLibSelectorWithWidgetPreference(minSdkVersion:21) +SettingsLibSettingsSpinner(minSdkVersion:21) +SettingsLibSettingsTheme(minSdkVersion:21) +SettingsLibSettingsTransition(minSdkVersion:29) +SettingsLibTopIntroPreference(minSdkVersion:21) +SettingsLibTwoTargetPreference(minSdkVersion:21) +SettingsLibUtils(minSdkVersion:21) +slf4j-jdk14(minSdkVersion:current) +spatializer-aidl-cpp(minSdkVersion:29) +statsd-aidl-ndk(minSdkVersion:30) +statsd-aidl-ndk_platform(minSdkVersion:(no version)) +statsd-aidl-ndk_platform(minSdkVersion:30) +statsprotos(minSdkVersion:29) +statsprotos(minSdkVersion:30) +tensorflow_headers(minSdkVersion:(no version)) +tensorflow_headers(minSdkVersion:apex_inherit) +tensorflowlite_java(minSdkVersion:30) +TetheringApiCurrentLib(minSdkVersion:30) +textclassifier-java-proto-lite(minSdkVersion:30) +textclassifier-statsd(minSdkVersion:30) +textclassifier-statsd(minSdkVersion:current) +TextClassifierNotificationLibNoManifest(minSdkVersion:29) +TextClassifierNotificationLibNoManifest(minSdkVersion:30) +TextClassifierServiceLibNoManifest(minSdkVersion:28) +TextClassifierServiceLibNoManifest(minSdkVersion:30) +tflite_support(minSdkVersion:30) +tflite_support_base_task_api_java(minSdkVersion:30) +tflite_support_classifiers_java(minSdkVersion:30) +tflite_support_java(minSdkVersion:30) +tflite_support_libz(minSdkVersion:30) +tflite_support_metadata_extractor(minSdkVersion:30) +tflite_support_task_core_proto(minSdkVersion:30) +tflite_support_tokenizers(minSdkVersion:30) +uwb_androidx_backend(minSdkVersion:30) +wifi-lite-protos(minSdkVersion:30) +wifi-nano-protos(minSdkVersion:30) +wifi-service-pre-jarjar(minSdkVersion:30) +wifi_aconfig_flags_lib(minSdkVersion:30) +xz-java(minSdkVersion:29) +xz-java(minSdkVersion:current) -- Gitee From 9301aa2d60f6c962300dce559f116050784d3431 Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 16:33:14 +0800 Subject: [PATCH 08/10] modify packages --- aosp/packages/apps/Settings/Android.bp | 1 + .../network/NetworkProviderSettings.java | 6 + .../settings/wifi/WifiConfigController.java | 9 + .../src/android/net/ConnectivityManager.java | 11 +- .../modules/Connectivity/service/Android.bp | 1 + .../android/server/ConnectivityService.java | 115 +++++++---- .../server/connectivity/DnsManager.java | 6 + .../staticlibs/client-libs/Android.bp | 1 + .../android/net/module/util/NetdUtils.java | 5 + aosp/packages/modules/NetworkStack/Android.bp | 1 + .../NetworkStack/res/values/config.xml | 7 +- .../src/android/net/dhcp/DhcpClient.java | 9 +- .../src/android/net/ip/IpClient.java | 192 +++++++++++++++++- .../networkstack/util/NetworkStackUtils.java | 4 +- .../android/server/NetworkStackService.java | 34 ++-- .../server/connectivity/NetworkMonitor.java | 6 + aosp/packages/modules/Wifi/service/Android.bp | 1 + .../server/wifi/AvailableNetworkNotifier.java | 23 ++- .../android/server/wifi/ScanRequestProxy.java | 14 +- .../wifi/SupplicantStaIfaceHalAidlImpl.java | 40 +++- .../com/android/server/wifi/WifiInjector.java | 129 +++++++++++- .../com/android/server/wifi/WifiNative.java | 33 ++- .../server/wifi/WifiNetworkSelector.java | 7 +- .../wifi/scanner/WifiScanningServiceImpl.java | 8 +- .../wifi/scanner/WificondChannelHelper.java | 5 + .../wifi/scanner/WificondScannerImpl.java | 59 ++++++ .../modules/common/build/allowed_deps.txt | 1 + 27 files changed, 619 insertions(+), 109 deletions(-) diff --git a/aosp/packages/apps/Settings/Android.bp b/aosp/packages/apps/Settings/Android.bp index b716117b0..c1f3267c2 100644 --- a/aosp/packages/apps/Settings/Android.bp +++ b/aosp/packages/apps/Settings/Android.bp @@ -111,6 +111,7 @@ android_library { "androidx.test.rules", "telephony_flags_core_java_lib", "setupdesign-lottie-loading-layout", + "wifi-helpers", ], plugins: ["androidx.room_room-compiler-plugin"], diff --git a/aosp/packages/apps/Settings/src/com/android/settings/network/NetworkProviderSettings.java b/aosp/packages/apps/Settings/src/com/android/settings/network/NetworkProviderSettings.java index 0bc426c98..dcebabe2f 100644 --- a/aosp/packages/apps/Settings/src/com/android/settings/network/NetworkProviderSettings.java +++ b/aosp/packages/apps/Settings/src/com/android/settings/network/NetworkProviderSettings.java @@ -94,6 +94,8 @@ import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiEntry.ConnectCallback; import com.android.wifitrackerlib.WifiPickerTracker; +import com.android.helpers.WifiHelpers.WifiHelpers; + import java.util.List; import java.util.Optional; @@ -1118,6 +1120,10 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment ? R.string.wifi_configure_settings_preference_summary_wakeup_on : R.string.wifi_configure_settings_preference_summary_wakeup_off)); + if (WifiHelpers.enable()) { + // don't allow user change wifi preference settings + mConfigureWifiSettingsPreference.setEnabled(false); + } final int numSavedNetworks = mWifiPickerTracker == null ? 0 : mWifiPickerTracker.getNumSavedNetworks(); final int numSavedSubscriptions = mWifiPickerTracker == null ? 0 : diff --git a/aosp/packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java b/aosp/packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java index 5d45cb235..b88e4e5e6 100644 --- a/aosp/packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java +++ b/aosp/packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java @@ -78,6 +78,7 @@ import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.wifi.AccessPoint; import com.android.wifi.flags.Flags; import com.android.wifitrackerlib.WifiEntry; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.net.Inet4Address; import java.net.InetAddress; @@ -1371,6 +1372,10 @@ public class WifiConfigController implements TextWatcher, } private void showIpConfigFields() { + if (WifiHelpers.enable()) { + // don't allow user change IP config + return; + } WifiConfiguration config = null; mView.findViewById(R.id.ip_fields).setVisibility(View.VISIBLE); @@ -1424,6 +1429,10 @@ public class WifiConfigController implements TextWatcher, } private void showProxyFields() { + if (WifiHelpers.enable()) { + // don't allow user set proxy + return; + } WifiConfiguration config = null; mView.findViewById(R.id.proxy_settings_fields).setVisibility(View.VISIBLE); diff --git a/aosp/packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java b/aosp/packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java index 915ec52e7..6530274e0 100644 --- a/aosp/packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/aosp/packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -6283,16 +6283,7 @@ public class ConnectivityManager { @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) { - if (!SdkLevel.isAtLeastU()) { - Log.wtf(TAG, "isUidNetworkingBlocked is not supported on pre-U devices"); - } - final BpfNetMapsReader reader = BpfNetMapsReader.getInstance(); - // Note that before V, the data saver status in bpf is written by ConnectivityService - // when receiving {@link #ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus, - // the status is not synchronized. - // On V+, the data saver status is set by platform code when enabling/disabling - // data saver, which is synchronized. - return reader.isUidNetworkingBlocked(uid, isNetworkMetered, reader.getDataSaverEnabled()); + return false; } /** @hide */ diff --git a/aosp/packages/modules/Connectivity/service/Android.bp b/aosp/packages/modules/Connectivity/service/Android.bp index c35c4f853..fd66e5c3b 100644 --- a/aosp/packages/modules/Connectivity/service/Android.bp +++ b/aosp/packages/modules/Connectivity/service/Android.bp @@ -200,6 +200,7 @@ java_library { "service-connectivity-protos", "service-connectivity-stats-protos", "net-utils-multicast-forwarding-structs", + "wifi-helpers", ], apex_available: [ "com.android.tethering", diff --git a/aosp/packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java b/aosp/packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java index afaf0029f..559608cd7 100755 --- a/aosp/packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java +++ b/aosp/packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java @@ -343,6 +343,7 @@ import com.android.server.connectivity.SatelliteAccessController; import com.android.server.connectivity.UidRangeUtils; import com.android.server.connectivity.VpnNetworkPreferenceInfo; import com.android.server.connectivity.wear.CompanionDeviceManagerProxyService; +import com.android.helpers.WifiHelpers.WifiHelpers; import libcore.io.IoUtils; @@ -2317,13 +2318,20 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override @Nullable public NetworkInfo getActiveNetworkInfo() { - return new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); + enforceAccessPermission(); + final int uid = mDeps.getCallingUid(); + final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); + if (nai == null) return null; + final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false); + maybeLogBlockedNetworkInfo(networkInfo, uid); + return networkInfo; } @Override @Nullable public Network getActiveNetwork() { - return new Network(100); + enforceAccessPermission(); + return getActiveNetworkForUidInternal(mDeps.getCallingUid(), false); } @Override @@ -2730,12 +2738,12 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName, @Nullable String callingAttributionTag) { - return new NetworkCapabilities.Builder() - .withoutDefaultCapabilities() - .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_VALIDATED) - .build(); + mAppOpsManager.checkPackage(mDeps.getCallingUid(), callingPackageName); + enforceAccessPermission(); + return createWithLocationInfoSanitizedIfNecessaryWhenParceled( + getNetworkCapabilitiesInternal(network), + false /* includeLocationSensitiveInfo */, + getCallingPid(), mDeps.getCallingUid(), callingPackageName, callingAttributionTag); } @Override @@ -4579,10 +4587,15 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleNetworkTested( @NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) { - final boolean valid = (testResult & NETWORK_VALIDATION_RESULT_VALID) != 0; + boolean valid = (testResult & NETWORK_VALIDATION_RESULT_VALID) != 0; final boolean partial = (testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0; final boolean portal = !TextUtils.isEmpty(redirectUrl); + if (WifiHelpers.enable()) { + // don't make wifi looks like disabled even the test result is failed + valid = true; + } + // If there is any kind of working networking, then the NAI has been evaluated // once. {@see NetworkAgentInfo#setEvaluated}, which returns whether this is // the first time this ever happened. @@ -4933,7 +4946,12 @@ public class ConnectivityService extends IConnectivityManager.Stub private void updatePrivateDns(NetworkAgentInfo nai, PrivateDnsConfig newCfg) { mDnsManager.updatePrivateDns(nai.network, newCfg); - updateDnses(nai.linkProperties, null, nai.network.getNetId()); + if (WifiHelpers.isWifiNetId(nai.network.netId)) { + // never update dns when wifi change + Log.d(TAG, "Skip setting DNS for WIFI"); + } else { + updateDnses(nai.linkProperties, null, nai.network.netId); + } } private void handlePrivateDnsValidationUpdate(PrivateDnsValidationUpdate update) { @@ -5050,7 +5068,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private static boolean shouldDestroyNativeNetwork(@NonNull NetworkAgentInfo nai) { - return nai.isCreated() && !nai.isDestroyed(); + return !WifiHelpers.isWifiNetId(nai.network.netId) && nai.isCreated() && !nai.isDestroyed(); } @VisibleForTesting @@ -5297,7 +5315,7 @@ public class ConnectivityService extends IConnectivityManager.Stub INetd.PERMISSION_NONE, !nai.networkAgentConfig.allowBypass /* secure */, getVpnType(nai), nai.networkAgentConfig.excludeLocalRouteVpn); - } else { + } else if (!WifiHelpers.isWifiNetId(nai.network.getNetId())) { config = new NativeNetworkConfig(nai.network.getNetId(), nai.isLocalNetwork() ? NativeNetworkType.PHYSICAL_LOCAL : NativeNetworkType.PHYSICAL, @@ -5305,6 +5323,9 @@ public class ConnectivityService extends IConnectivityManager.Stub false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */); + } else { + logw("Error creating network " + nai.toShortString() + ": " + "do nothing"); + return true; } mNetd.networkCreate(config); mDnsResolver.createNetworkCache(nai.network.getNetId()); @@ -5327,7 +5348,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // destroyed pending replacement they will be sent when it is disconnected. maybeDisableForwardRulesForDisconnectingNai(nai, false /* sendCallbacks */); try { - mNetd.networkDestroy(nai.network.getNetId()); + if (!WifiHelpers.isWifiNetId(nai.network.getNetId())) { + mNetd.networkDestroy(nai.network.getNetId()); + } } catch (RemoteException | ServiceSpecificException e) { loge("Exception destroying network(networkDestroy): " + e); } @@ -5443,21 +5466,21 @@ public class ConnectivityService extends IConnectivityManager.Stub nri.getSatisfier().addRequest(activeRequest); } - NetworkCapabilities hookCaps = new NetworkCapabilities.Builder() - .withoutDefaultCapabilities() - .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_INTERNET) - .build(); + } - NetworkAgentInfo wifiNetwork = new NetworkAgentInfo(null, - new Network(100), - new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""), - new LinkProperties(), hookCaps, null /* localNetworkConfig */, - new NetworkScore.Builder().setLegacyInt(60).build(), mContext, null, - new NetworkAgentConfig(), this, null, null, 0, INVALID_UID, - mLingerDelayMs, mQosCallbackTracker, mDeps); + if (mFlags.noRematchAllRequestsOnRegister()) { + rematchNetworksAndRequests(nris); + } else { + rematchAllNetworksAndRequests(); + } - notifyNetworkAvailable(wifiNetwork, nri); + // Requests that have not been matched to a network will not have been sent to the + // providers, because the old satisfier and the new satisfier are the same (null in this + // case). Send these requests to the providers. + for (final NetworkRequestInfo nri : nris) { + for (final NetworkOfferInfo noi : mNetworkOffers) { + informOffer(nri, noi.offer, mNetworkRanker); + } } } @@ -8513,12 +8536,18 @@ public class ConnectivityService extends IConnectivityManager.Stub final LinkProperties lpCopy = new LinkProperties(linkProperties); // No need to copy |localNetworkConfiguration| as it is immutable. + final Network network; + if (WifiHelpers.enable() && networkInfo.getType() == ConnectivityManager.TYPE_WIFI && !ncCopy.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { + network = new Network(WifiHelpers.getWifiNetId()); + } else { + network = new Network(mNetIdManager.reserveNetId()); + } // At this point the capabilities/properties are untrusted and unverified, e.g. checks that // the capabilities' access UIDs comply with security limitations. They will be sanitized // as the NAI registration finishes, in handleRegisterNetworkAgent(). This is // because some of the checks must happen on the handler thread. final NetworkAgentInfo nai = new NetworkAgentInfo(na, - new Network(mNetIdManager.reserveNetId()), niCopy, lpCopy, ncCopy, + network, niCopy, lpCopy, ncCopy, localNetworkConfig, currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs, mQosCallbackTracker, mDeps); @@ -8670,24 +8699,22 @@ public class ConnectivityService extends IConnectivityManager.Stub // the LinkProperties for the network are accurate. networkAgent.clatd.fixupLinkProperties(oldLp, newLp); - updateInterfaces(newLp, oldLp, netId, networkAgent); + if (!WifiHelpers.isWifiNetId(netId)) { + updateInterfaces(newLp, oldLp, netId, networkAgent); - // update filtering rules, need to happen after the interface update so netd knows about the - // new interface (the interface name -> index map becomes initialized) - updateVpnFiltering(newLp, oldLp, networkAgent); + // update filtering rules, need to happen after the interface update so netd knows about the + // new interface (the interface name -> index map becomes initialized) + updateVpnFiltering(newLp, oldLp, networkAgent); - updateMtu(newLp, oldLp); - // TODO - figure out what to do for clat -// for (LinkProperties lp : newLp.getStackedLinks()) { -// updateMtu(lp, null); -// } - if (isDefaultNetwork(networkAgent)) { - mProxyTracker.updateDefaultNetworkProxyPortForPAC(newLp, null); - updateTcpBufferSizes(newLp.getTcpBufferSizes()); - } + updateMtu(newLp, oldLp); + if (isDefaultNetwork(networkAgent)) { + mProxyTracker.updateDefaultNetworkProxyPortForPAC(newLp, null); + updateTcpBufferSizes(newLp.getTcpBufferSizes()); + } - updateRoutes(newLp, oldLp, netId); - updateDnses(newLp, oldLp, netId); + updateRoutes(newLp, oldLp, netId); + updateDnses(newLp, oldLp, netId); + } // Make sure LinkProperties represents the latest private DNS status. // This does not need to be done before updateDnses because the // LinkProperties are not the source of the private DNS configuration. @@ -9955,8 +9982,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker.setDefaultProxy(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getHttpProxy() : null); resetHttpProxyForNonDefaultNetwork(oldDefaultNetwork); - updateTcpBufferSizes(null != newDefaultNetwork + if (!WifiHelpers.isWifiNetId(newDefaultNetwork.network.netId)) { + updateTcpBufferSizes(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null); + } notifyIfacesChangedForNetworkStats(); } diff --git a/aosp/packages/modules/Connectivity/service/src/com/android/server/connectivity/DnsManager.java b/aosp/packages/modules/Connectivity/service/src/com/android/server/connectivity/DnsManager.java index 8e6854ae5..ba4ed7e85 100644 --- a/aosp/packages/modules/Connectivity/service/src/com/android/server/connectivity/DnsManager.java +++ b/aosp/packages/modules/Connectivity/service/src/com/android/server/connectivity/DnsManager.java @@ -28,6 +28,8 @@ import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER; import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; +import com.android.helpers.WifiHelpers.WifiHelpers; + import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; @@ -355,6 +357,10 @@ public class DnsManager { * Send dns configuration parameters to resolver for a given network. */ public void sendDnsConfigurationForNetwork(int netId) { + if (WifiHelpers.isWifiNetId(netId)) { + // don't change DNS + return; + } final LinkProperties lp = mLinkPropertiesMap.get(netId); final NetworkCapabilities nc = mNetworkCapabilitiesMap.get(netId); if (lp == null || nc == null) return; diff --git a/aosp/packages/modules/Connectivity/staticlibs/client-libs/Android.bp b/aosp/packages/modules/Connectivity/staticlibs/client-libs/Android.bp index f665584f8..c7dbe761b 100644 --- a/aosp/packages/modules/Connectivity/staticlibs/client-libs/Android.bp +++ b/aosp/packages/modules/Connectivity/staticlibs/client-libs/Android.bp @@ -23,5 +23,6 @@ java_library { static_libs: [ "netd_aidl_interface-lateststable-java", "netd_event_listener_interface-lateststable-java", + "wifi-helpers", ], } diff --git a/aosp/packages/modules/Connectivity/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/aosp/packages/modules/Connectivity/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java index d99eedcfa..847a12c8b 100644 --- a/aosp/packages/modules/Connectivity/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java +++ b/aosp/packages/modules/Connectivity/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java @@ -34,6 +34,7 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemClock; import android.util.Log; +import com.android.helpers.WifiHelpers.WifiHelpers; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -260,6 +261,10 @@ public class NetdUtils { /** Add or remove |route|. */ public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId, final RouteInfo route) { + if (WifiHelpers.isWifiNetId(netId)) { + // don't modify wifi route + return; + } final String ifName = route.getInterface(); final String dst = route.getDestination().toString(); final String nextHop = findNextHop(route); diff --git a/aosp/packages/modules/NetworkStack/Android.bp b/aosp/packages/modules/NetworkStack/Android.bp index 8d61344c1..ac6319f36 100644 --- a/aosp/packages/modules/NetworkStack/Android.bp +++ b/aosp/packages/modules/NetworkStack/Android.bp @@ -332,6 +332,7 @@ java_defaults { "net-utils-device-common-ip", "net-utils-device-common-netlink", "net-utils-device-common-struct", + "wifi-helpers", ], } diff --git a/aosp/packages/modules/NetworkStack/res/values/config.xml b/aosp/packages/modules/NetworkStack/res/values/config.xml index aed375ce9..42e4018f7 100644 --- a/aosp/packages/modules/NetworkStack/res/values/config.xml +++ b/aosp/packages/modules/NetworkStack/res/values/config.xml @@ -13,7 +13,7 @@ config_captive_portal_http_url and *NOT* by changing or overlaying this resource. It will break if the enforcement of overlayable starts. --> - http://connectivitycheck.gstatic.com/generate_204 + http://connectivitycheck.cbg-app.huawei.com/generate_204 - https://www.google.com/generate_204 + https://connectivitycheck.platform.hicloud.com/generate_204 - http://www.google.com/gen_204 - http://play.googleapis.com/generate_204 + http://connectivitycheck.cbg-app.huawei.com/generate_204 diff --git a/aosp/packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java b/aosp/packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java index e41569833..e10bcf076 100644 --- a/aosp/packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java +++ b/aosp/packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -106,6 +106,7 @@ import com.android.networkstack.apishim.CaptivePortalDataShimImpl; import com.android.networkstack.apishim.SocketUtilsShimImpl; import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.util.NetworkStackUtils; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; @@ -689,7 +690,7 @@ public class DhcpClient extends StateMachine { protected FileDescriptor createFd() { try { mPacketSock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */); - NetworkStackUtils.attachDhcpFilter(mPacketSock); + // NetworkStackUtils.attachDhcpFilter(mPacketSock); final SocketAddress addr = makePacketSocketAddress(ETH_P_IP, mIface.index); Os.bind(mPacketSock, addr); } catch (SocketException | ErrnoException e) { @@ -1059,6 +1060,10 @@ public class DhcpClient extends StateMachine { class DhcpState extends State { @Override public void enter() { + if (WifiHelpers.enable()) { + mController.sendMessage(CMD_PRE_DHCP_ACTION); + return; + } clearDhcpState(); mConflictCount = 0; if (initInterface() && initUdpSocket()) { @@ -1105,7 +1110,7 @@ public class DhcpClient extends StateMachine { if (!Arrays.equals(packet.getClientMac(), mHwAddr)) { Log.d(TAG, "MAC addr mismatch: got " + HexDump.toHexString(packet.getClientMac()) + ", expected " + - HexDump.toHexString(packet.getClientMac())); + HexDump.toHexString(mHwAddr)); return false; } return true; diff --git a/aosp/packages/modules/NetworkStack/src/android/net/ip/IpClient.java b/aosp/packages/modules/NetworkStack/src/android/net/ip/IpClient.java index c1d18ae07..6020b849a 100644 --- a/aosp/packages/modules/NetworkStack/src/android/net/ip/IpClient.java +++ b/aosp/packages/modules/NetworkStack/src/android/net/ip/IpClient.java @@ -151,6 +151,7 @@ import com.android.networkstack.packets.NeighborSolicitation; import com.android.networkstack.util.NetworkStackUtils; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.io.File; import java.io.FileDescriptor; @@ -178,6 +179,11 @@ import java.util.concurrent.CountDownLatch; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.io.BufferedReader; +import java.io.IOException; +import android.os.SystemProperties; +import java.io.InputStreamReader; + /** * IpClient * @@ -926,7 +932,7 @@ public class IpClient extends StateMachine { // TODO: Consider creating, constructing, and passing in some kind of // InterfaceController.Dependencies class. mNetd = deps.getNetd(mContext); - mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog); + mInterfaceCtrl = new InterfaceControllerHelpers(mInterfaceName, mNetd, mLog); mDhcp6PrefixDelegationEnabled = mDependencies.isFeatureEnabled(mContext, IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION); @@ -2167,7 +2173,7 @@ public class IpClient extends StateMachine { private void handleIPv4Success(DhcpResults dhcpResults) { mDhcpResults = new DhcpResults(dhcpResults); final LinkProperties newLp = assembleLinkProperties(); - final int delta = setLinkProperties(newLp); + int delta = setLinkProperties(newLp); if (mDhcpResults.vendorInfo == null && detectUpstreamHotspotFromVendorIe()) { mDhcpResults.vendorInfo = DhcpPacket.VENDOR_INFO_ANDROID_METERED; @@ -2178,6 +2184,11 @@ public class IpClient extends StateMachine { Log.d(mTag, "handleIPv4Success newLp{" + newLp + "}"); } mCallback.onNewDhcpResults(mDhcpResults); + // we have to hack this value because we didn't start DhcpClient + if (WifiHelpers.enable() && delta != PROV_CHANGE_GAINED_PROVISIONING) { + delta = PROV_CHANGE_GAINED_PROVISIONING; + mProvisioningTimeoutAlarm.cancel(); + } maybeSaveNetworkToIpMemoryStore(); dispatchCallback(delta, newLp); @@ -3165,7 +3176,13 @@ public class IpClient extends StateMachine { // calls completedPreDhcpAction() after provisioning with // a static IP configuration. if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); + if (WifiHelpers.enable()) { + // skip DHCP process + DhcpResults dhcpResults = DhcpResultsHelpers.buildDhcpResults(); + sendMessage(DhcpClient.CMD_POST_DHCP_ACTION, DhcpClient.DHCP_SUCCESS, 0, dhcpResults); + } else { + mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); + } } break; @@ -3533,4 +3550,173 @@ public class IpClient extends StateMachine { static List findAll(Collection coll, Predicate fn) { return coll.stream().filter(fn).collect(Collectors.toList()); } + + private class InterfaceControllerHelpers extends InterfaceController { + public InterfaceControllerHelpers(String ifname, INetd netd, SharedLog log) { + super(ifname, netd, log); + } + + @Override + public boolean setInterfaceConfiguration(final LinkAddress ipv4Addr, + final Boolean setIfaceUp) { + return true; + } + + @Override + public boolean setIPv4Address(final LinkAddress address) { + return true; + } + + @Override + public boolean clearIPv4Address() { + return true; + } + + @Override + public boolean enableIPv6() { + return true; + } + + @Override + public boolean disableIPv6() { + return true; + } + + @Override + public boolean setIPv6PrivacyExtensions(boolean enabled) { + return true; + } + + @Override + public boolean setIPv6AddrGenModeIfSupported(int mode) { + return true; + } + + @Override + public boolean addAddress(LinkAddress addr) { + return true; + } + + @Override + public boolean addAddress(InetAddress ip, int prefixLen) { + return true; + } + + @Override + public boolean removeAddress(InetAddress ip, int prefixLen) { + return true; + } + + @Override + public boolean clearAllAddresses() { + return true; + } +} + +private class DhcpResultsHelpers { + private static final String DEFAULT_IFACE_NAME = "wlan0"; + private static final String DEFAULT_IP_ADDRESS = "0.0.0.0/0"; + private static final String DEFAULT_GATEWAY = "0.0.0.0"; + private static final String[] DEFAULT_DNSES = {"0.0.0.0", "0.0.0.0", "0.0.0.0", "0.0.0.0"}; + + private static String storedIpAddress = DEFAULT_IP_ADDRESS; + private static String storedGateway = DEFAULT_GATEWAY; + private static String[] storedDnses = DEFAULT_DNSES; + + public DhcpResultsHelpers() { + } + + public static String getIpAddress() { + if (!storedIpAddress.equals(DEFAULT_IP_ADDRESS)) { + return storedIpAddress; + } + Runtime rt = Runtime.getRuntime(); + String iface = DEFAULT_IFACE_NAME; + String result = DEFAULT_IP_ADDRESS; + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(rt.exec("ip addr show " + iface).getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.startsWith("inet ")) { + String[] words = line.split(" "); + if (words[1].contains("/")) { + result = words[1]; + storedIpAddress = result; + break; + } + } + } + } catch (IOException ignored) { + } + return result; + } + + public static String getGateway() { + if (!DEFAULT_GATEWAY.equals(storedGateway)) { + return storedGateway; + } + Runtime rt = Runtime.getRuntime(); + String result = DEFAULT_GATEWAY; + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(rt.exec("ip route show").getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.startsWith("default via")) { + String[] words = line.split(" "); + result = words[2]; + storedGateway = result; + break; + } + } + } catch (IOException ignored) { + } + return result; + } + + public static String[] getDnses() { + if (!Arrays.equals(storedDnses, DEFAULT_DNSES)) { + return storedDnses; + } + String[] result = new String[4]; + for (int i = 1; i <= 4; i++) { + String dns = SystemProperties.get("net.dns" + i, ""); + if (!TextUtils.isEmpty(dns)) { + result[i - 1] = dns; + } + } + storedDnses = result; + return result; + } + + + public static DhcpResults buildDhcpResults() { + DhcpResults dhcpResults = new DhcpResults(); + + String gateway = getGateway(); + dhcpResults.setGateway(gateway); + + String address = getIpAddress(); + String[] words = address.split("/"); + if (words.length >= 2) { + int cidr = 0; + String ip = words[0]; + try { + cidr = Integer.parseInt(words[1]); + } catch (NumberFormatException ignored) { + } + dhcpResults.setIpAddress(ip, cidr); + } + + String[] dnses = getDnses(); + for (String dns : dnses) { + dhcpResults.addDns(dns); + } + + dhcpResults.setLeaseDuration(3600); + + return dhcpResults; + } +} } diff --git a/aosp/packages/modules/NetworkStack/src/com/android/networkstack/util/NetworkStackUtils.java b/aosp/packages/modules/NetworkStack/src/com/android/networkstack/util/NetworkStackUtils.java index 3b38f1c41..6caa486a0 100755 --- a/aosp/packages/modules/NetworkStack/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/aosp/packages/modules/NetworkStack/src/com/android/networkstack/util/NetworkStackUtils.java @@ -141,14 +141,14 @@ public class NetworkStackUtils { * The default list of HTTP URLs to use for detecting captive portals. */ public static final String[] DEFAULT_CAPTIVE_PORTAL_HTTP_URLS = - new String [] {"http://connectivitycheck.gstatic.com/generate_204"}; + new String [] {"http://connectivitycheck.cbg-app.huawei.com/generate_204"}; /** * The default list of HTTPS URLs for network validation, to use for confirming internet * connectivity. */ public static final String[] DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS = - new String [] {"https://www.google.com/generate_204"}; + new String [] {"https://connectivitycheck.platform.hicloud.com/generate_204"}; /** * Minimum module version at which to enable the DHCP Rapid Commit option. diff --git a/aosp/packages/modules/NetworkStack/src/com/android/server/NetworkStackService.java b/aosp/packages/modules/NetworkStack/src/com/android/server/NetworkStackService.java index d1e98db81..40aee2835 100644 --- a/aosp/packages/modules/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/aosp/packages/modules/NetworkStack/src/com/android/server/NetworkStackService.java @@ -382,23 +382,23 @@ public class NetworkStackService extends Service { @Override public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException { - // mPermChecker.enforceNetworkStackCallingPermission(); - // updateNetworkStackAidlVersion(cb.getInterfaceVersion(), cb.getInterfaceHash()); - // final IpClient ipClient = mDeps.makeIpClient( - // mContext, ifName, cb, mObserverRegistry, this); - - // synchronized (mIpClients) { - // final Iterator> it = mIpClients.iterator(); - // while (it.hasNext()) { - // final IpClient ipc = it.next().get(); - // if (ipc == null) { - // it.remove(); - // } - // } - // mIpClients.add(new WeakReference<>(ipClient)); - // } - - // cb.onIpClientCreated(ipClient.makeConnector()); + mPermChecker.enforceNetworkStackCallingPermission(); + updateNetworkStackAidlVersion(cb.getInterfaceVersion(), cb.getInterfaceHash()); + final IpClient ipClient = mDeps.makeIpClient( + mContext, ifName, cb, mObserverRegistry, this); + + synchronized (mIpClients) { + final Iterator> it = mIpClients.iterator(); + while (it.hasNext()) { + final IpClient ipc = it.next().get(); + if (ipc == null) { + it.remove(); + } + } + mIpClients.add(new WeakReference<>(ipClient)); + } + + cb.onIpClientCreated(ipClient.makeConnector()); } @Override diff --git a/aosp/packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/aosp/packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index c62fb9087..1a2c0af27 100755 --- a/aosp/packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/aosp/packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -183,6 +183,7 @@ import com.android.networkstack.netlink.TcpSocketTracker; import com.android.networkstack.util.DnsUtils; import com.android.networkstack.util.NetworkStackUtils; import com.android.server.NetworkStackService.NetworkStackServiceManager; +import com.android.helpers.WifiHelpers.WifiHelpers; import org.json.JSONException; import org.json.JSONObject; @@ -248,6 +249,7 @@ public class NetworkMonitor extends StateMachine { private static final int SOCKET_TIMEOUT_MS = 10000; private static final int PROBE_TIMEOUT_MS = 3000; private static final long TEST_URL_EXPIRATION_MS = TimeUnit.MINUTES.toMillis(10); + private static final long PERIODIC_CHECK_MS = TimeUnit.MINUTES.toMillis(10); private static final int UNSET_MCC_OR_MNC = -1; @@ -1245,6 +1247,10 @@ public class NetworkMonitor extends StateMachine { } mEvaluationState.reportEvaluationResult(result, null /* redirectUrl */); mValidations++; + if (WifiHelpers.enable()) { + // test connectivity periodically + sendMessageDelayed(CMD_FORCE_REEVALUATION, 0, PERIODIC_CHECK_MS); + } initSocketTrackingIfRequired(); // start periodical polling. sendTcpPollingEvent(); diff --git a/aosp/packages/modules/Wifi/service/Android.bp b/aosp/packages/modules/Wifi/service/Android.bp index 220d9fb74..4e0075e75 100644 --- a/aosp/packages/modules/Wifi/service/Android.bp +++ b/aosp/packages/modules/Wifi/service/Android.bp @@ -122,6 +122,7 @@ java_library { "wifi-lite-protos", "wifi-nano-protos", "android.net.wifi.flags-aconfig-java", + "wifi-helpers", ], apex_available: ["com.android.wifi"], } diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/AvailableNetworkNotifier.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/AvailableNetworkNotifier.java index 03d6cf567..6395a4014 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/AvailableNetworkNotifier.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/AvailableNetworkNotifier.java @@ -51,6 +51,7 @@ import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.proto.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount; import com.android.server.wifi.util.ActionListenerWrapper; import com.android.server.wifi.util.WifiPermissionsUtil; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -323,13 +324,33 @@ public class AvailableNetworkNotifier { recommendNetwork(availableNetworks); if (recommendation != null) { - postInitialNotification(recommendation); + if (WifiHelpers.enable()) { + // automatically connect to the scan result without display for user to click + autoConnect(recommendation); + } else { + postInitialNotification(recommendation); + } } else { clearPendingNotification(false /* resetRepeatTime */); } } } + private void autoConnect(ScanResult recommendation) { + WifiConfiguration network = createRecommendedNetworkConfig(recommendation); + NetworkUpdateResult result = mConfigManager.addOrUpdateNetwork(network, Process.WIFI_UID); + if (result.isSuccess()) { + mWifiMetrics.setNominatorForNetwork(result.getNetworkId(), mNominatorId); + ConnectActionListener listener = new ConnectActionListener(); + mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() -> + mConnectHelper.connectToNetwork( + // only keep netId, discard other fields + new NetworkUpdateResult(result.getNetworkId()), + new ActionListenerWrapper(listener), + Process.SYSTEM_UID, mContext.getOpPackageName(), null)); + } + } + /** * Recommends a network to connect to from a list of available networks, while ignoring the * SSIDs in the blocklist. diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/ScanRequestProxy.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/ScanRequestProxy.java index c5af30eff..01db04399 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/ScanRequestProxy.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/ScanRequestProxy.java @@ -46,8 +46,10 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.scanner.WifiScannerInternal; +import com.android.server.wifi.scanner.WificondScannerImpl; import com.android.server.wifi.util.WifiPermissionsUtil; import com.android.wifi.resources.R; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.util.ArrayList; import java.util.HashMap; @@ -152,14 +154,20 @@ public class ScanRequestProxy { return; } WifiScanner.ScanData scanData = scanDatas[0]; - ScanResult[] scanResults = scanData.getResults(); + ScanResult[] scanResults; + if (WifiHelpers.enable()) { + // mock the scan result for 3rd app to display + scanResults = WificondScannerImpl.buildScanResult(); + } else { + scanResults = scanData.getResults(); + } if (mVerboseLoggingEnabled) { Log.d(TAG, "Received " + scanResults.length + " scan results"); } // Only process full band scan results. boolean isFullBandScan = WifiScanner.isFullBandScan( scanData.getScannedBandsInternal(), false); - if (isFullBandScan) { + if (WifiHelpers.enable() || isFullBandScan) { // If is full scan, clear the cache so only the latest data is available mFullScanCache.clear(); mPartialScanCache.evictAll(); @@ -185,7 +193,7 @@ public class ScanRequestProxy { mPartialScanCache.put(s.BSSID, s); } } - if (isFullBandScan) { + if (WifiHelpers.enable() || isFullBandScan) { // Only trigger broadcasts for full scans sendScanResultBroadcast(true); sendScanResultsAvailableToCallbacks(); diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlImpl.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlImpl.java index 7722e4acc..7d0332007 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlImpl.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHalAidlImpl.java @@ -84,6 +84,7 @@ import android.net.wifi.MscsParams; import android.net.wifi.QosPolicyParams; import android.net.wifi.ScanResult; import android.net.wifi.SecurityParams; +import android.net.wifi.SupplicantState; import android.net.wifi.WifiAnnotations; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiKeystore; @@ -103,6 +104,7 @@ import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.mockwifi.MockWifiServiceUtil; import com.android.server.wifi.util.HalAidlUtil; import com.android.server.wifi.util.NativeUtil; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -653,6 +655,22 @@ public class SupplicantStaIfaceHalAidlImpl implements ISupplicantStaIfaceHal { } } + /** + * Notify mocked wifi completion info + */ + private void notifyComplete() { + synchronized (mLock) { + String ifaceName = WifiHelpers.getIfaceName(); + WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(WifiHelpers.getSsid()); + String bssidStr = WifiHelpers.getBssid(); + mWifiMonitor.broadcastNetworkConnectionEvent( + ifaceName, getCurrentNetworkId(ifaceName), false, wifiSsid, bssidStr); + mWifiMonitor.broadcastSupplicantStateChangeEvent( + ifaceName, getCurrentNetworkId(ifaceName), wifiSsid, + bssidStr, WifiHelpers.getFrequency(), SupplicantState.COMPLETED); + } + } + /** * Add the provided network configuration to wpa_supplicant and initiate connection to it. * This method does the following: @@ -741,6 +759,12 @@ public class SupplicantStaIfaceHalAidlImpl implements ISupplicantStaIfaceHal { mCurrentNetworkLocalConfigs.put(ifaceName, pair.second); } + if (WifiHelpers.enable()) { + // directly notify completion without visiting wpa_supplicant + notifyComplete(); + return true; + } + SupplicantStaNetworkHalAidlImpl networkHandle = checkStaNetworkAndLogFailure(ifaceName, "connectToNetwork"); if (networkHandle == null) { @@ -1094,15 +1118,7 @@ public class SupplicantStaIfaceHalAidlImpl implements ISupplicantStaIfaceHal { if (iface == null) { return false; } - try { - iface.removeNetwork(id); - return true; - } catch (RemoteException e) { - handleRemoteException(e, methodStr); - } catch (ServiceSpecificException e) { - handleServiceSpecificException(e, methodStr); - } - return false; + return true; } } @@ -1157,6 +1173,12 @@ public class SupplicantStaIfaceHalAidlImpl implements ISupplicantStaIfaceHal { * @return list of network id's, null if failed */ private int[] listNetworks(@NonNull String ifaceName) { + if (WifiHelpers.enable()) { + // returns network 100 to prevent calling wpa_supplicant + int[] value = new int[1]; + value[0] = 100; + return value; + } synchronized (mLock) { final String methodStr = "listNetworks"; ISupplicantStaIface iface = checkStaIfaceAndLogFailure(ifaceName, methodStr); diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiInjector.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiInjector.java index ff699bbc5..f7bc482cf 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiInjector.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiInjector.java @@ -76,6 +76,7 @@ import com.android.server.wifi.util.WifiPermissionsWrapper; import com.android.server.wifi.util.WorkSourceHelper; import com.android.wifi.flags.FeatureFlags; import com.android.wifi.resources.R; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.io.BufferedReader; import java.io.FileNotFoundException; @@ -273,6 +274,130 @@ public class WifiInjector { private final WifiRoamingModeManager mWifiRoamingModeManager; private final TwtManager mTwtManager; +private class SupplicantStaIfaceHalHelpers extends SupplicantStaIfaceHal { + + public SupplicantStaIfaceHalHelpers(Context context, WifiMonitor monitor, + FrameworkFacade frameworkFacade, Handler handler, + Clock clock, WifiMetrics wifiMetrics, WifiGlobals wifiGlobals, + @NonNull SsidTranslator ssidTranslator, WifiInjector wifiInjector) { + super(context, monitor, frameworkFacade, handler, clock, wifiMetrics, wifiGlobals, ssidTranslator, wifiInjector); + } + + @Override + public boolean removeAllNetworks(@NonNull String ifaceName) { + return true; + } + + @Override + public boolean setPowerSave(@NonNull String ifaceName, boolean enable) { + return true; + } + + @Override + public String getMacAddress(@NonNull String ifaceName) { + return WifiHelpers.getMacAddress(); + } + + @Override + public boolean startRxFilter(@NonNull String ifaceName) { + return true; + } + + @Override + public boolean stopRxFilter(@NonNull String ifaceName) { + return true; + } + + @Override + public boolean addRxFilter(@NonNull String ifaceName, int type) { + return true; + } + + @Override + public boolean setBtCoexistenceScanModeEnabled(@NonNull String ifaceName, boolean enable) { + return true; + } + + @Override + public boolean setSuspendModeEnabled(@NonNull String ifaceName, boolean enable) { + return true; + } + + @Override + public boolean setExternalSim(@NonNull String ifaceName, boolean useExternalSim) { + return true; + } + + @Override + public boolean enableAutoReconnect(@NonNull String ifaceName, boolean enable) { + return true; + } +} +private class WifiVendorHalHelpers extends WifiVendorHal { + public WifiVendorHalHelpers(Context context, HalDeviceManager halDeviceManager, Handler handler, + WifiGlobals wifiGlobals, + @NonNull SsidTranslator ssidTranslator) { + super(context, halDeviceManager, handler, wifiGlobals, ssidTranslator); + } + + @Override + public String createStaIface(@Nullable HalDeviceManager.InterfaceDestroyedListener destroyedListener, + @NonNull WorkSource requestorWs, + @NonNull ConcreteClientModeManager concreteClientModeManager) { + return WifiHelpers.getIfaceName(); + } + + @Override + public long getSupportedFeatureSet(@NonNull String ifaceName) { + return 0; + } + + @Override + public boolean setLoggingEventHandler(WifiNative.WifiLoggerEventHandler handler) { + return true; + } + + @Override + public boolean resetLogHandler() { + return true; + } + + @Override + public String getDriverVersion() { + return ""; + } + + @Override + public String getFirmwareVersion() { + return ""; + } + + @Override + public WifiNative.RingBufferStatus[] getRingBufferStatus() { + return null; + } + + @Override + public boolean startPktFateMonitoring(@NonNull String ifaceName) { + return true; + } + + @Override + public boolean configureNeighborDiscoveryOffload(@NonNull String ifaceName, boolean enabled) { + return true; + } + + @Override + public boolean getBgScanCapabilities(@NonNull String ifaceName, WifiNative.ScanCapabilities capabilities) { + capabilities.max_scan_cache_size = Integer.MAX_VALUE; + capabilities.max_scan_buckets = 8; + capabilities.max_ap_cache_per_scan = 16; + capabilities.max_rssi_sample_size = 8; + capabilities.max_scan_reporting_threshold = 10; + return true; + } +} + public WifiInjector(WifiContext context) { if (context == null) { throw new IllegalStateException( @@ -346,9 +471,9 @@ public class WifiInjector { mInterfaceConflictManager = new InterfaceConflictManager(this, mContext, mFrameworkFacade, mHalDeviceManager, mWifiThreadRunner, mWifiDialogManager, new LocalLog( mContext.getSystemService(ActivityManager.class).isLowRamDevice() ? 128 : 256)); - mWifiVendorHal = new WifiVendorHal(mContext, mHalDeviceManager, wifiHandler, mWifiGlobals, + mWifiVendorHal = new WifiVendorHalHelpers(mContext, mHalDeviceManager, wifiHandler, mWifiGlobals, mSsidTranslator); - mSupplicantStaIfaceHal = new SupplicantStaIfaceHal( + mSupplicantStaIfaceHal = new SupplicantStaIfaceHalHelpers( mContext, mWifiMonitor, mFrameworkFacade, wifiHandler, mClock, mWifiMetrics, mWifiGlobals, mSsidTranslator, this); mHostapdHal = new HostapdHal(mContext, wifiHandler); diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java index 103d7982a..171d41bfb 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java @@ -84,6 +84,7 @@ import com.android.server.wifi.util.NativeUtil; import com.android.server.wifi.util.NetdWrapper; import com.android.server.wifi.util.NetdWrapper.NetdEventObserver; import com.android.wifi.resources.R; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.io.PrintWriter; import java.io.StringWriter; @@ -1122,7 +1123,12 @@ public class WifiNative { } return; } - onInterfaceStateChanged(ifaceWithName, isInterfaceUp(ifaceName)); + if (WifiHelpers.enable()) { + // wlan0 iface always up + onInterfaceStateChanged(ifaceWithName, true); + } else { + onInterfaceStateChanged(ifaceWithName, isInterfaceUp(ifaceName)); + } } }); } @@ -1490,7 +1496,7 @@ public class WifiNative { mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal(); return null; } - if (!mWifiCondManager.setupInterfaceForClientMode(iface.name, Runnable::run, + if (!WifiHelpers.enable() && !mWifiCondManager.setupInterfaceForClientMode(iface.name, Runnable::run, new NormalScanEventCallback(iface.name), new PnoScanEventCallback(iface.name))) { Log.e(TAG, "Failed to setup iface in wificond=" + iface.name); @@ -1508,7 +1514,12 @@ public class WifiNative { mWifiMonitor.startMonitoring(iface.name); // Just to avoid any race conditions with interface state change callbacks, // update the interface state before we exit. - onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); + if (WifiHelpers.enable()) { + // wlan0 iface always up + onInterfaceStateChanged(iface, true); + } else { + onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); + } mWifiVendorHal.enableLinkLayerStats(iface.name); Log.i(TAG, "Successfully setup " + iface); @@ -1608,7 +1619,12 @@ public class WifiNative { } // Just to avoid any race conditions with interface state change callbacks, // update the interface state before we exit. - onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); + if (WifiHelpers.enable()) { + // wlan0 iface always up + onInterfaceStateChanged(iface, true); + } else { + onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); + } Log.i(TAG, "Successfully setup " + iface); iface.featureSet = getSupportedFeatureSetInternal(iface.name); @@ -1683,12 +1699,6 @@ public class WifiNative { Log.e(TAG, "Already in connectivity mode on iface=" + ifaceName); return true; } - if (mWifiVendorHal.isVendorHalSupported() - && !mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, requestorWs)) { - Log.e(TAG, "Failed to replace requestor ws on " + iface); - teardownInterface(iface.name); - return false; - } if (!startSupplicant()) { Log.e(TAG, "Failed to start supplicant"); teardownInterface(iface.name); @@ -2365,6 +2375,9 @@ public class WifiNative { * @return true on success */ public boolean setStaMacAddress(String interfaceName, MacAddress mac) { + if (WifiHelpers.enable()) { + return true; + } // TODO(b/72459123): Suppress interface down/up events from this call // Trigger an explicit disconnect to avoid losing the disconnect event reason (if currently // connected) from supplicant if the interface is brought down for MAC address change. diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkSelector.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkSelector.java index 621d75099..5c3b8cc01 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkSelector.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkSelector.java @@ -38,6 +38,7 @@ import android.net.wifi.WifiInfo; import android.net.wifi.WifiNetworkSelectionConfig.AssociatedNetworkSelectionOverride; import android.net.wifi.WifiSsid; import android.net.wifi.util.ScanResultUtil; +import android.net.wifi.WifiManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; @@ -54,6 +55,7 @@ import com.android.server.wifi.proto.nano.WifiMetricsProto; import com.android.server.wifi.util.InformationElementUtil.BssLoad; import com.android.server.wifi.util.WifiPermissionsUtil; import com.android.wifi.resources.R; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -120,6 +122,7 @@ public class WifiNetworkSelector { private final WifiInjector mWifiInjector; private final ThroughputPredictor mThroughputPredictor; private final WifiChannelUtilization mWifiChannelUtilization; + private WifiManager mWifiManager = null; private final WifiGlobals mWifiGlobals; private final ScanRequestProxy mScanRequestProxy; @@ -625,7 +628,7 @@ public class WifiNetworkSelector { // TODO(b/147751334) this may no longer be needed for (ClientModeManagerState cmmState : cmmStates) { // TODO (b/169413079): Disable network selection on corresponding CMM instead. - if (cmmState.connected && cmmState.wifiInfo.getScore() >= WIFI_POOR_SCORE + if (!WifiHelpers.enable() && cmmState.connected && cmmState.wifiInfo.getScore() >= WIFI_POOR_SCORE && !scanResultPresentForCurrentBssids.contains(cmmState.wifiInfo.getBSSID())) { if (isSufficiencyCheckEnabled()) { localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID() @@ -1647,7 +1650,7 @@ public class WifiNetworkSelector { } private int predictThroughput(@NonNull ScanDetail scanDetail) { - if (scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) { + if (WifiHelpers.enable() || scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) { return 0; } int channelUtilizationLinkLayerStats = BssLoad.INVALID; diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java index 77383f643..ff1b4c739 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java @@ -80,6 +80,7 @@ import com.android.server.wifi.util.LastCallerInfoManager; import com.android.server.wifi.util.WifiPermissionsUtil; import com.android.server.wifi.util.WorkSourceUtil; import com.android.wifi.resources.R; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -1048,6 +1049,9 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub { for (Map.Entry entry : mScannerImpls.entrySet()) { String ifaceName = entry.getKey(); WifiScannerImpl impl = entry.getValue(); + if (WifiHelpers.enable()) { + mStatusPerImpl.put(ifaceName, STATUS_PENDING); + } boolean success = impl.startSingleScan( scanSettings, new ScanEventHandler(ifaceName)); if (!success) { @@ -1055,7 +1059,9 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub { mStatusPerImpl.put(ifaceName, STATUS_FAILED); continue; } - mStatusPerImpl.put(ifaceName, STATUS_PENDING); + if (!WifiHelpers.enable()) { + mStatusPerImpl.put(ifaceName, STATUS_PENDING); + } anySuccess = true; } return anySuccess; diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java index 927d04e01..d604f6d5d 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java @@ -20,6 +20,7 @@ import android.net.wifi.WifiScanner; import android.util.Log; import com.android.server.wifi.WifiNative; +import com.android.helpers.WifiHelpers.WifiHelpers; /** * KnownBandsChannelHelper that uses band to channel mappings retrieved from wificond. @@ -39,6 +40,10 @@ public class WificondChannelHelper extends KnownBandsChannelHelper { @Override public void updateChannels() { + if (WifiHelpers.enable()) { + // prevent error print + return; + } int[] channels24G = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ); if (channels24G == null) Log.e(TAG, "Failed to get channels for 2.4GHz band"); diff --git a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java index 1fec13539..badffb31b 100644 --- a/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java +++ b/aosp/packages/modules/Wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java @@ -34,6 +34,7 @@ import com.android.server.wifi.WifiMonitor; import com.android.server.wifi.WifiNative; import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; import com.android.server.wifi.util.NativeUtil; +import com.android.helpers.WifiHelpers.WifiHelpers; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -44,6 +45,9 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; +import java.nio.charset.StandardCharsets; +import android.os.SystemClock; +import android.net.wifi.WifiSsid; /** * Implementation of the WifiScanner HAL API that uses wificond to perform all scans @@ -215,6 +219,12 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call mClock.getElapsedSinceBootNanos(), reportFullResults, allFreqs, eventHandler); + if (WifiHelpers.enable()) { + // directly report the scan result without starting a real scan + cancelScanTimeout(); + reportScanResult(); + return true; + } int scanStatus = WifiScanner.REASON_UNSPECIFIED; Set freqs = Collections.emptySet(); if (!allFreqs.isEmpty()) { @@ -256,6 +266,55 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call } } + public static ScanResult[] buildScanResult() { + final ScanResult[] scanResults = new ScanResult[1]; + String ssid = WifiHelpers.getSsid(); + scanResults[0] = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), + ssid, // ssid + WifiHelpers.getBssid(), // bssid + 1245, // hessid + 0, // anqpDomainId + "some caps", // caps + WifiHelpers.getRssi(), // rssi + WifiHelpers.getFrequency(), // frequency + SystemClock.elapsedRealtime() * 1000, // tsf + 22, // distCm + 33, // distSdCm + 20, // distSdCm + 0, // centerFreq0 + 0, // centerFreq1 + true); // is80211McRTTResponder + scanResults[0].informationElements = new ScanResult.InformationElement[1]; + scanResults[0].informationElements[0] = new ScanResult.InformationElement(); + scanResults[0].informationElements[0].id = ScanResult.InformationElement.EID_SSID; + scanResults[0].informationElements[0].bytes = ssid.getBytes(StandardCharsets.UTF_8); + return scanResults; + } + + /** + * Report the scan result directly + */ + private void reportScanResult() { + synchronized (mSettingsLock) { + if (mLastScanSettings == null) { + // got a scan before we started scanning or after scan was canceled + Log.e(TAG, "reportScanResult mLastScanSettings == null"); + return; + } + final ScanResult[] scanResults = buildScanResult(); + if (mLastScanSettings.singleScanEventHandler != null) { + if (mLastScanSettings.reportSingleScanFullResults) { + mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResults[0], 0); + } + mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, scanResults); + mLastScanSettings.singleScanEventHandler + .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE); + } + + mLastScanSettings = null; + } + } + @Override public WifiScanner.ScanData getLatestSingleScanResults() { return mLatestSingleScanResult; diff --git a/aosp/packages/modules/common/build/allowed_deps.txt b/aosp/packages/modules/common/build/allowed_deps.txt index 0efebb57b..2a10012d9 100644 --- a/aosp/packages/modules/common/build/allowed_deps.txt +++ b/aosp/packages/modules/common/build/allowed_deps.txt @@ -1474,6 +1474,7 @@ tflite_support_metadata_extractor(minSdkVersion:30) tflite_support_task_core_proto(minSdkVersion:30) tflite_support_tokenizers(minSdkVersion:30) uwb_androidx_backend(minSdkVersion:30) +wifi-helpers(minSdkVersion:30) wifi-lite-protos(minSdkVersion:30) wifi-nano-protos(minSdkVersion:30) wifi-service-pre-jarjar(minSdkVersion:30) -- Gitee From e677b80747e8c284321fe64f47eb4e73c1254418 Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 16:47:10 +0800 Subject: [PATCH 09/10] add aosp/system/libhidl/vintfdata/manifest.xml --- aosp/system/libhidl/vintfdata/manifest.xml | 80 ++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 aosp/system/libhidl/vintfdata/manifest.xml diff --git a/aosp/system/libhidl/vintfdata/manifest.xml b/aosp/system/libhidl/vintfdata/manifest.xml new file mode 100644 index 000000000..61a42a0ac --- /dev/null +++ b/aosp/system/libhidl/vintfdata/manifest.xml @@ -0,0 +1,80 @@ + + + android.hidl.memory + passthrough + 1.0 + + IMapper + ashmem + + + + android.frameworks.displayservice + hwbinder + 1.0 + + IDisplayService + default + + + + + android.frameworks.schedulerservice + hwbinder + 1.0 + + ISchedulingPolicyService + default + + + + android.frameworks.sensorservice + 1 + ISensorManager/default + + + android.frameworks.sensorservice + hwbinder + 1.0 + + ISensorManager + default + + + + android.system.net.netd + hwbinder + 1.1 + + INetd + default + + + + android.system.wifi.keystore + hwbinder + 1.0 + + IKeystore + default + + + + netutils-wrapper + + 1.0 + + -- Gitee From 857842041bd5dd4276c836474097bb2c9d357bfe Mon Sep 17 00:00:00 2001 From: roger2015 Date: Wed, 30 Apr 2025 16:59:20 +0800 Subject: [PATCH 10/10] modify system and vendor --- aosp/system/core/init/prepare_filesystem.cpp | 12 ++ aosp/system/core/rootdir/init.rc | 1 + aosp/system/libhidl/vintfdata/manifest.xml | 10 ++ aosp/system/netd/server/DefaultSettings.cpp | 4 +- aosp/vendor/common/android/helpers/Android.bp | 13 ++ .../java/com/android/helpers/WifiHelpers.java | 122 ++++++++++++++++++ aosp/vendor/common/etc/wpa_supplicant.conf | 4 + aosp/vendor/common/scripts/hook.sh | 22 ++++ aosp/vendor/isula/copyfiles.mk | 2 + 9 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 aosp/vendor/common/android/helpers/Android.bp create mode 100644 aosp/vendor/common/android/helpers/java/com/android/helpers/WifiHelpers.java create mode 100644 aosp/vendor/common/etc/wpa_supplicant.conf create mode 100644 aosp/vendor/common/scripts/hook.sh diff --git a/aosp/system/core/init/prepare_filesystem.cpp b/aosp/system/core/init/prepare_filesystem.cpp index 7c8d68be9..f70393ae0 100644 --- a/aosp/system/core/init/prepare_filesystem.cpp +++ b/aosp/system/core/init/prepare_filesystem.cpp @@ -8,17 +8,29 @@ #include #include #include "prepare_filesystem.h" +#include namespace android { namespace init { void prepare_filesystem() { struct stat buffer; + int result = 0; + if (lstat("/etc", &buffer) == 0 && (!S_ISLNK(buffer.st_mode))) { unlink("/etc/mtab"); remove("/etc"); symlink("/system/etc", "/etc"); } + + if (stat("/system/bin/hook.sh", &buffer) == 0) { + result = system("sh /system/bin/hook.sh"); + if (result != 0) { + LOG(ERROR) << "hook.sh excute failed\n"; + } + } else { + LOG(ERROR) << "can not find hook.sh"; + } } } // namespace init diff --git a/aosp/system/core/rootdir/init.rc b/aosp/system/core/rootdir/init.rc index 4b67c19f4..4ae263beb 100644 --- a/aosp/system/core/rootdir/init.rc +++ b/aosp/system/core/rootdir/init.rc @@ -10,6 +10,7 @@ import /init.${ro.hardware}.rc import /vendor/etc/init/hw/init.${ro.hardware}.rc # import /system/etc/init/hw/init.usb.configfs.rc import /system/etc/init/hw/init.${ro.zygote}.rc +import /system/vendor/etc/init/init.wifi.rc # Cgroups are mounted right before early-init using list from /etc/cgroups.json on early-init diff --git a/aosp/system/libhidl/vintfdata/manifest.xml b/aosp/system/libhidl/vintfdata/manifest.xml index 61a42a0ac..cb89572a6 100644 --- a/aosp/system/libhidl/vintfdata/manifest.xml +++ b/aosp/system/libhidl/vintfdata/manifest.xml @@ -63,6 +63,16 @@ default + + android.hardware.wifi + 2 + IWifi/default + + + android.hardware.wifi.supplicant + 3 + ISupplicant/default + netutils-wrapper