Full Network Commissioning Cluster w/ Linux Implementation (#12931)

* Linux network commissioning

* Rename network-commissioning -> network-commissioning-old in existing apps

* Update python test script

* Enable NetworkCommissioningCluster on Endpoint 0 and Endpoint 1

* Run Codegen
diff --git a/src/platform/Linux/BUILD.gn b/src/platform/Linux/BUILD.gn
index 8803940..f5bdc54 100644
--- a/src/platform/Linux/BUILD.gn
+++ b/src/platform/Linux/BUILD.gn
@@ -61,6 +61,9 @@
     "KeyValueStoreManagerImpl.cpp",
     "KeyValueStoreManagerImpl.h",
     "Logging.cpp",
+    "NetworkCommissioningDriver.h",
+    "NetworkCommissioningThreadDriver.cpp",
+    "NetworkCommissioningWiFiDriver.cpp",
     "PlatformManagerImpl.cpp",
     "PlatformManagerImpl.h",
     "PosixConfig.cpp",
@@ -81,6 +84,7 @@
   deps = [ "${chip_root}/src/setup_payload" ]
 
   public_deps = [
+    "${chip_root}/src/app/common:cluster-objects",
     "${chip_root}/src/platform:platform_base",
     "${chip_root}/third_party/inipp",
   ]
diff --git a/src/platform/Linux/ConnectivityManagerImpl.cpp b/src/platform/Linux/ConnectivityManagerImpl.cpp
index 8cc3fc1..1496f04 100644
--- a/src/platform/Linux/ConnectivityManagerImpl.cpp
+++ b/src/platform/Linux/ConnectivityManagerImpl.cpp
@@ -22,11 +22,15 @@
 #include <platform/DiagnosticDataProvider.h>
 #include <platform/Linux/ConnectivityUtils.h>
 #include <platform/Linux/DiagnosticDataProviderImpl.h>
+#include <platform/Linux/NetworkCommissioningDriver.h>
 #include <platform/Linux/WirelessDefs.h>
 #include <platform/internal/BLEManager.h>
 
 #include <cstdlib>
 #include <new>
+#include <string>
+#include <utility>
+#include <vector>
 
 #include <ifaddrs.h>
 #include <stdio.h>
@@ -60,6 +64,8 @@
 using namespace ::chip::app::Clusters::GeneralDiagnostics;
 using namespace ::chip::app::Clusters::WiFiNetworkDiagnostics;
 
+using namespace ::chip::DeviceLayer::NetworkCommissioning;
+
 namespace chip {
 namespace DeviceLayer {
 
@@ -69,12 +75,17 @@
 char ConnectivityManagerImpl::sWiFiIfName[];
 #endif
 
+WiFiDriver::ScanCallback * ConnectivityManagerImpl::mpScanCallback;
+NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * ConnectivityManagerImpl::mpConnectCallback;
+
 CHIP_ERROR ConnectivityManagerImpl::_Init()
 {
 #if CHIP_DEVICE_CONFIG_ENABLE_WPA
     mWiFiStationMode              = kWiFiStationMode_Disabled;
     mWiFiStationReconnectInterval = System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_WIFI_STATION_RECONNECT_INTERVAL);
 #endif
+    mpConnectCallback = nullptr;
+    mpScanCallback    = nullptr;
 
     if (ConnectivityUtils::GetEthInterfaceName(mEthIfName, IFNAMSIZ) == CHIP_NO_ERROR)
     {
@@ -427,6 +438,7 @@
         ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant interface proxy");
 
         g_signal_connect(mWpaSupplicant.iface, "properties-changed", G_CALLBACK(_OnWpaPropertiesChanged), NULL);
+        g_signal_connect(mWpaSupplicant.iface, "scan-done", G_CALLBACK(_OnWpaInterfaceScanDone), NULL);
     }
     else
     {
@@ -632,6 +644,17 @@
         mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NOT_CONNECTED;
     }
 
+    // We need to stop auto scan or it will block our network scan.
+    DeviceLayer::SystemLayer().ScheduleLambda([]() {
+        std::lock_guard<std::mutex> innerLock(mWpaSupplicantMutex);
+        ChipLogDetail(DeviceLayer, "Disabling auto scan");
+        CHIP_ERROR errInner = StopAutoScan();
+        if (errInner != CHIP_NO_ERROR)
+        {
+            ChipLogError(DeviceLayer, "Failed to stop auto scan");
+        }
+    });
+
     if (err != nullptr)
         g_error_free(err);
 }
@@ -864,6 +887,126 @@
     sInstance.DriveAPState();
 }
 
+CHIP_ERROR
+ConnectivityManagerImpl::ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials,
+                                                 NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * apCallback)
+{
+    CHIP_ERROR ret  = CHIP_NO_ERROR;
+    GError * err    = nullptr;
+    GVariant * args = nullptr;
+    GVariantBuilder builder;
+    gboolean result;
+    char ssidStr[kMaxWiFiSSIDLength] = { 0 };
+    char keyStr[kMaxWiFiKeyLength]   = { 0 };
+    // There is another ongoing connect request, reject the new one.
+    VerifyOrReturnError(mpConnectCallback == nullptr, CHIP_ERROR_INCORRECT_STATE);
+
+    // Clean up current network if exists
+    if (mWpaSupplicant.networkPath)
+    {
+        GError * error = nullptr;
+
+        result = wpa_fi_w1_wpa_supplicant1_interface_call_remove_network_sync(mWpaSupplicant.iface, mWpaSupplicant.networkPath,
+                                                                              nullptr, &error);
+
+        if (result)
+        {
+            ChipLogProgress(DeviceLayer, "wpa_supplicant: removed network: %s", mWpaSupplicant.networkPath);
+            g_free(mWpaSupplicant.networkPath);
+            mWpaSupplicant.networkPath = nullptr;
+        }
+        else
+        {
+            ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to stop AP mode with error: %s",
+                            error ? error->message : "unknown error");
+            ret = CHIP_ERROR_INTERNAL;
+        }
+
+        if (error != nullptr)
+            g_error_free(error);
+
+        SuccessOrExit(ret);
+    }
+
+    g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+    memcpy(ssidStr, ssid.data(), ssid.size());
+    memcpy(keyStr, credentials.data(), credentials.size());
+    g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssidStr));
+    g_variant_builder_add(&builder, "{sv}", "psk", g_variant_new_string(keyStr));
+    g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("WPA-PSK"));
+    args = g_variant_builder_end(&builder);
+
+    result = wpa_fi_w1_wpa_supplicant1_interface_call_add_network_sync(mWpaSupplicant.iface, args, &mWpaSupplicant.networkPath,
+                                                                       nullptr, &err);
+
+    if (result)
+    {
+        // Note: wpa_supplicant will return immediately if the network is already connected, but it will still try reconnect in the
+        // background. The client still need to wait for a few seconds for this reconnect operation. So we always disconnect from
+        // the network we are connected and ignore any errors.
+        wpa_fi_w1_wpa_supplicant1_interface_call_disconnect_sync(mWpaSupplicant.iface, nullptr, nullptr);
+        ChipLogProgress(DeviceLayer, "wpa_supplicant: added network: %s", mWpaSupplicant.networkPath);
+
+        wpa_fi_w1_wpa_supplicant1_interface_call_select_network(mWpaSupplicant.iface, mWpaSupplicant.networkPath, nullptr,
+                                                                _ConnectWiFiNetworkAsyncCallback, this);
+        mpConnectCallback = apCallback;
+    }
+    else
+    {
+        ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to add network: %s", err ? err->message : "unknown error");
+
+        if (mWpaSupplicant.networkPath)
+        {
+            g_object_unref(mWpaSupplicant.networkPath);
+            mWpaSupplicant.networkPath = nullptr;
+        }
+
+        ret = CHIP_ERROR_INTERNAL;
+    }
+
+exit:
+    if (err != nullptr)
+        g_error_free(err);
+
+    return ret;
+}
+
+void ConnectivityManagerImpl::_ConnectWiFiNetworkAsyncCallback(GObject * source_object, GAsyncResult * res, gpointer user_data)
+{
+    ConnectivityManagerImpl * this_ = reinterpret_cast<ConnectivityManagerImpl *>(user_data);
+    std::unique_ptr<GVariant, GVariantDeleter> attachRes;
+    std::unique_ptr<GError, GErrorDeleter> err;
+    {
+        gboolean result = wpa_fi_w1_wpa_supplicant1_interface_call_select_network_finish(mWpaSupplicant.iface, res,
+                                                                                         &MakeUniquePointerReceiver(err).Get());
+        if (!result)
+        {
+            ChipLogError(DeviceLayer, "Failed to perform connect network: %s", err == nullptr ? "unknown error" : err->message);
+            DeviceLayer::SystemLayer().ScheduleLambda([this_]() {
+                if (mpConnectCallback != nullptr)
+                {
+                    // TODO: Replace this with actual thread attach result.
+                    this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), 0);
+                    this_->mpConnectCallback = nullptr;
+                }
+                mpConnectCallback = nullptr;
+            });
+        }
+        else
+        {
+            DeviceLayer::SystemLayer().ScheduleLambda([this_]() {
+                if (this_->mpConnectCallback != nullptr)
+                {
+                    // TODO: Replace this with actual thread attach result.
+                    this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0);
+                    this_->mpConnectCallback = nullptr;
+                }
+                this_->PostNetworkConnect();
+            });
+        }
+    }
+}
+
 CHIP_ERROR ConnectivityManagerImpl::ProvisionWiFiNetwork(const char * ssid, const char * key)
 {
     CHIP_ERROR ret  = CHIP_NO_ERROR;
@@ -937,45 +1080,7 @@
             if (gerror != nullptr)
                 g_error_free(gerror);
 
-            // Iterate on the network interface to see if we already have beed assigned addresses.
-            // The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session.
-            // This should be removed or find a better place once we depercate the rendezvous session.
-            for (chip::Inet::InterfaceAddressIterator it; it.HasCurrent(); it.Next())
-            {
-                char ifName[chip::Inet::InterfaceId::kMaxIfNameLength];
-                if (it.IsUp() && CHIP_NO_ERROR == it.GetInterfaceName(ifName, sizeof(ifName)) &&
-                    strncmp(ifName, sWiFiIfName, sizeof(ifName)) == 0)
-                {
-                    chip::Inet::IPAddress addr;
-                    if ((it.GetAddress(addr) == CHIP_NO_ERROR) && addr.IsIPv4())
-                    {
-                        ChipDeviceEvent event;
-                        event.Type                            = DeviceEventType::kInternetConnectivityChange;
-                        event.InternetConnectivityChange.IPv4 = kConnectivity_Established;
-                        event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange;
-                        addr.ToString(event.InternetConnectivityChange.address);
-
-                        ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", ifName,
-                                      event.InternetConnectivityChange.address);
-
-                        PlatformMgr().PostEventOrDie(&event);
-                    }
-                }
-            }
-
-            // Run dhclient for IP on WiFi.
-            // TODO: The wifi can be managed by networkmanager on linux so we don't have to care about this.
-            char cmdBuffer[128];
-            sprintf(cmdBuffer, CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD, sWiFiIfName);
-            int dhclientSystemRet = system(cmdBuffer);
-            if (dhclientSystemRet != 0)
-            {
-                ChipLogError(DeviceLayer, "Failed to run dhclient, system() returns %d", dhclientSystemRet);
-            }
-            else
-            {
-                ChipLogProgress(DeviceLayer, "dhclient is running on the %s interface.", sWiFiIfName);
-            }
+            PostNetworkConnect();
 
             // Return success as long as the device is connected to the network
             ret = CHIP_NO_ERROR;
@@ -1011,6 +1116,69 @@
     return ret;
 }
 
+void ConnectivityManagerImpl::PostNetworkConnect()
+{
+    // Iterate on the network interface to see if we already have beed assigned addresses.
+    // The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session.
+    // This should be removed or find a better place once we depercate the rendezvous session.
+    for (chip::Inet::InterfaceAddressIterator it; it.HasCurrent(); it.Next())
+    {
+        char ifName[chip::Inet::InterfaceId::kMaxIfNameLength];
+        if (it.IsUp() && CHIP_NO_ERROR == it.GetInterfaceName(ifName, sizeof(ifName)) &&
+            strncmp(ifName, sWiFiIfName, sizeof(ifName)) == 0)
+        {
+            chip::Inet::IPAddress addr;
+            if ((it.GetAddress(addr) == CHIP_NO_ERROR) && addr.IsIPv4())
+            {
+                ChipDeviceEvent event;
+                event.Type                            = DeviceEventType::kInternetConnectivityChange;
+                event.InternetConnectivityChange.IPv4 = kConnectivity_Established;
+                event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange;
+                addr.ToString(event.InternetConnectivityChange.address);
+
+                ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", ifName,
+                              event.InternetConnectivityChange.address);
+
+                PlatformMgr().PostEventOrDie(&event);
+            }
+        }
+    }
+
+    // Run dhclient for IP on WiFi.
+    // TODO: The wifi can be managed by networkmanager on linux so we don't have to care about this.
+    char cmdBuffer[128];
+    sprintf(cmdBuffer, CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD, sWiFiIfName);
+    int dhclientSystemRet = system(cmdBuffer);
+    if (dhclientSystemRet != 0)
+    {
+        ChipLogError(DeviceLayer, "Failed to run dhclient, system() returns %d", dhclientSystemRet);
+    }
+    else
+    {
+        ChipLogProgress(DeviceLayer, "dhclient is running on the %s interface.", sWiFiIfName);
+    }
+}
+
+CHIP_ERROR ConnectivityManagerImpl::CommitConfig()
+{
+    gboolean result;
+    std::unique_ptr<GError, GErrorDeleter> err;
+
+    ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to network");
+
+    result = wpa_fi_w1_wpa_supplicant1_interface_call_save_config_sync(mWpaSupplicant.iface, nullptr,
+                                                                       &MakeUniquePointerReceiver(err).Get());
+
+    if (!result)
+    {
+        ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to save config: %s", err ? err->message : "unknown error");
+        return CHIP_ERROR_INTERNAL;
+    }
+
+    ChipLogProgress(DeviceLayer, "wpa_supplicant: save config succeeded!");
+    return CHIP_NO_ERROR;
+}
+
 CHIP_ERROR ConnectivityManagerImpl::GetWiFiBssId(ByteSpan & value)
 {
     CHIP_ERROR err          = CHIP_ERROR_READ_FAILED;
@@ -1110,6 +1278,368 @@
     return CHIP_NO_ERROR;
 }
 
+CHIP_ERROR ConnectivityManagerImpl::GetConnectedNetwork(NetworkCommissioning::Network & network)
+{
+    std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
+    std::unique_ptr<GError, GErrorDeleter> err;
+
+    const gchar * networkPath = wpa_fi_w1_wpa_supplicant1_interface_get_current_network(mWpaSupplicant.iface);
+
+    std::unique_ptr<WpaFiW1Wpa_supplicant1Network, GObjectDeleter> networkInfo(
+        wpa_fi_w1_wpa_supplicant1_network_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE,
+                                                                 kWpaSupplicantServiceName, networkPath, nullptr,
+                                                                 &MakeUniquePointerReceiver(err).Get()));
+    if (networkInfo == nullptr)
+    {
+        return CHIP_ERROR_INTERNAL;
+    }
+
+    network.connected     = wpa_fi_w1_wpa_supplicant1_network_get_enabled(networkInfo.get());
+    GVariant * properties = wpa_fi_w1_wpa_supplicant1_network_get_properties(networkInfo.get());
+    GVariant * ssid       = g_variant_lookup_value(properties, "ssid", nullptr);
+    gsize length;
+    const gchar * ssidStr = g_variant_get_string(ssid, &length);
+    if (length > sizeof(network.networkID))
+    {
+        return CHIP_ERROR_INTERNAL;
+    }
+    // TODO: wpa_supplicant will return ssid with quotes! We should have a better way to get the actual ssid in bytes.
+    ChipLogDetail(DeviceLayer, "Current connected network: %s", ssidStr);
+    memcpy(network.networkID, ssidStr + 1, length - 2);
+    network.networkIDLen = length - 2;
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ConnectivityManagerImpl::StopAutoScan()
+{
+    std::unique_ptr<GError, GErrorDeleter> err;
+
+    gboolean result;
+    result = wpa_fi_w1_wpa_supplicant1_interface_call_auto_scan_sync(
+        mWpaSupplicant.iface, "" /* empty string means disabling auto scan */, nullptr, &MakeUniquePointerReceiver(err).Get());
+    if (!result)
+    {
+        ChipLogError(DeviceLayer, "wpa_supplicant: Failed to stop auto network scan: %s", err ? err->message : "unknown");
+        return CHIP_ERROR_INTERNAL;
+    }
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ConnectivityManagerImpl::StartWiFiScan(ByteSpan ssid, WiFiDriver::ScanCallback * callback)
+{
+    std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
+    VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE);
+    // There is another ongoing scan request, reject the new one.
+    VerifyOrReturnError(mpScanCallback == nullptr, CHIP_ERROR_INCORRECT_STATE);
+
+    CHIP_ERROR ret  = CHIP_NO_ERROR;
+    GError * err    = nullptr;
+    GVariant * args = nullptr;
+    GVariantBuilder builder;
+    gboolean result;
+
+    g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+    g_variant_builder_add(&builder, "{sv}", "Type", g_variant_new_string("active"));
+    args = g_variant_builder_end(&builder);
+
+    result = wpa_fi_w1_wpa_supplicant1_interface_call_scan_sync(mWpaSupplicant.iface, args, nullptr, &err);
+
+    if (result)
+    {
+        ChipLogProgress(DeviceLayer, "wpa_supplicant: initialized network scan.");
+        mpScanCallback = callback;
+    }
+    else
+    {
+        ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to start network scan: %s", err ? err->message : "unknown error");
+        ret = CHIP_ERROR_INTERNAL;
+    }
+
+    if (err != nullptr)
+    {
+        g_error_free(err);
+    }
+    return ret;
+}
+
+namespace {
+// wpa_supplicant's scan results don't contains the channel infomation, so we need this lookup table for resolving the band and
+// channel infomation.
+std::pair<WiFiBand, uint16_t> GetBandAndChannelFromFrequency(uint32_t freq)
+{
+    std::pair<WiFiBand, uint16_t> ret = std::make_pair(WiFiBand::k2g4, 0);
+    if (freq <= 2472)
+    {
+        ret.second = static_cast<uint16_t>((freq - 2412) / 5 + 1);
+    }
+    else if (freq == 2484)
+    {
+        ret.second = 14;
+    }
+    else if (freq >= 3600 && freq <= 3700)
+    {
+        // Note: There are not many devices supports this band, and this band contains rational frequency in MHz, need to figure out
+        // the behavior of wpa_supplicant in this case.
+        ret.first = WiFiBand::k3g65;
+    }
+    else if (freq >= 5035 && freq <= 5945)
+    {
+        ret.first  = WiFiBand::k5g;
+        ret.second = static_cast<uint16_t>((freq - 5000) / 5);
+    }
+    else if (freq == 5960 || freq == 5980)
+    {
+        ret.first  = WiFiBand::k5g;
+        ret.second = static_cast<uint16_t>((freq - 5000) / 5);
+    }
+    else if (freq >= 5955)
+    {
+        ret.first  = WiFiBand::k6g;
+        ret.second = static_cast<uint16_t>((freq - 5950) / 5);
+    }
+    else if (freq >= 58000)
+    {
+        ret.first = WiFiBand::k60g;
+        // Note: Some channel has the same center frequency but different bandwidth. Should figure out wpa_supplicant's behavior in
+        // this case. Also, wpa_supplicant's frequency property is uint16 infact.
+        switch (freq)
+        {
+        case 58'320:
+            ret.second = 1;
+            break;
+        case 60'480:
+            ret.second = 2;
+            break;
+        case 62'640:
+            ret.second = 3;
+            break;
+        case 64'800:
+            ret.second = 4;
+            break;
+        case 66'960:
+            ret.second = 5;
+            break;
+        case 69'120:
+            ret.second = 6;
+            break;
+        case 59'400:
+            ret.second = 9;
+            break;
+        case 61'560:
+            ret.second = 10;
+            break;
+        case 63'720:
+            ret.second = 11;
+            break;
+        case 65'880:
+            ret.second = 12;
+            break;
+        case 68'040:
+            ret.second = 13;
+            break;
+        }
+    }
+    return ret;
+}
+} // namespace
+
+bool ConnectivityManagerImpl::_GetBssInfo(const gchar * bssPath, NetworkCommissioning::WiFiScanResponse & result)
+{
+    std::unique_ptr<GError, GErrorDeleter> err;
+    std::unique_ptr<WpaFiW1Wpa_supplicant1BSS, GObjectDeleter> bss(
+        wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName,
+                                                             bssPath, nullptr, &MakeUniquePointerReceiver(err).Get()));
+
+    if (bss == nullptr)
+    {
+        return false;
+    }
+
+    WpaFiW1Wpa_supplicant1BSSProxy * bssProxy = WPA_FI_W1_WPA_SUPPLICANT1_BSS_PROXY(bss.get());
+
+    std::unique_ptr<GVariant, GVariantDeleter> ssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "SSID"));
+    std::unique_ptr<GVariant, GVariantDeleter> bssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "BSSID"));
+
+    const guchar * ssidStr       = nullptr;
+    const guchar * bssidBuf      = nullptr;
+    char bssidStr[2 * 6 + 5 + 1] = { 0 };
+    gsize ssidLen                = 0;
+    gsize bssidLen               = 0;
+    gint16 signal                = wpa_fi_w1_wpa_supplicant1_bss_get_signal(bss.get());
+    guint16 frequency            = wpa_fi_w1_wpa_supplicant1_bss_get_frequency(bss.get());
+
+    ssidStr  = reinterpret_cast<const guchar *>(g_variant_get_fixed_array(ssid.get(), &ssidLen, sizeof(guchar)));
+    bssidBuf = reinterpret_cast<const guchar *>(g_variant_get_fixed_array(bssid.get(), &bssidLen, sizeof(guchar)));
+
+    if (bssidLen == 6)
+    {
+        snprintf(bssidStr, sizeof(bssidStr), "%02x:%02x:%02x:%02x:%02x:%02x", bssidBuf[0], bssidBuf[1], bssidBuf[2], bssidBuf[3],
+                 bssidBuf[4], bssidBuf[5]);
+    }
+    else
+    {
+        bssidLen = 0;
+        ChipLogError(DeviceLayer, "Got a network with bssid not equals to 6");
+    }
+    ChipLogDetail(DeviceLayer, "Network Found: %.*s (%s) Signal:%" PRId16, int(ssidLen), ssidStr, bssidStr, signal);
+
+    // A flag for enterprise encryption option to avoid returning open for these networks by mistake
+    // TODO: The following code will mistakenly recognize WEP encryption as OPEN network, this should be fixed by reading
+    // IEs (information elements) field instead of reading cooked data.
+
+    static constexpr uint8_t kEAP = (1 << 7);
+
+    auto IsNetworkWPAPSK = [](GVariant * wpa) -> uint8_t {
+        if (wpa == nullptr)
+        {
+            return 0;
+        }
+
+        GVariant * keyMgmt = g_variant_lookup_value(wpa, "KeyMgmt", nullptr);
+        if (keyMgmt == nullptr)
+        {
+            return 0;
+        }
+        const gchar ** keyMgmts        = g_variant_get_strv(keyMgmt, nullptr);
+        const gchar ** keyMgmtsForFree = keyMgmts;
+        uint8_t res                    = 0;
+        for (const gchar * keyMgmtVal = (keyMgmts != nullptr ? *keyMgmts : nullptr); keyMgmtVal != nullptr;
+             keyMgmtVal               = *(++keyMgmts))
+        {
+            if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-none") == 0)
+            {
+                res |= (1 << 2); // SecurityType::WPA_PERSONAL
+            }
+            else if (g_strcasecmp(keyMgmtVal, "wpa-eap"))
+            {
+                res |= (kEAP);
+            }
+        }
+        g_variant_unref(keyMgmt);
+        g_free(keyMgmtsForFree);
+        return res;
+    };
+    auto IsNetworkWPA2PSK = [](GVariant * rsn) -> uint8_t {
+        if (rsn == nullptr)
+        {
+            return 0;
+        }
+        GVariant * keyMgmt = g_variant_lookup_value(rsn, "KeyMgmt", nullptr);
+        if (keyMgmt == nullptr)
+        {
+            return 0;
+        }
+        const gchar ** keyMgmts        = g_variant_get_strv(keyMgmt, nullptr);
+        const gchar ** keyMgmtsForFree = keyMgmts;
+        uint8_t res                    = 0;
+        for (const gchar * keyMgmtVal = (keyMgmts != nullptr ? *keyMgmts : nullptr); keyMgmtVal != nullptr;
+             keyMgmtVal               = *(++keyMgmts))
+        {
+            if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-psk-sha256") == 0 ||
+                g_strcasecmp(keyMgmtVal, "wpa-ft-psk") == 0)
+            {
+                res |= (1 << 3); // SecurityType::WPA2_PERSONAL
+            }
+            else if (g_strcasecmp(keyMgmtVal, "wpa-eap") == 0 || g_strcasecmp(keyMgmtVal, "wpa-eap-sha256") == 0 ||
+                     g_strcasecmp(keyMgmtVal, "wpa-ft-eap") == 0)
+            {
+                res |= kEAP;
+            }
+            else if (g_strcasecmp(keyMgmtVal, "sae") == 0)
+            {
+                // wpa_supplicant will include "sae" in KeyMgmt field for WPA3 WiFi, this is not included in the wpa_supplicant
+                // document.
+                res |= (1 << 4); // SecurityType::WPA3_PERSONAL
+            }
+        }
+        g_variant_unref(keyMgmt);
+        g_free(keyMgmtsForFree);
+        return res;
+    };
+    auto GetNetworkSecurityType = [IsNetworkWPAPSK, IsNetworkWPA2PSK](WpaFiW1Wpa_supplicant1BSSProxy * proxy) -> uint8_t {
+        std::unique_ptr<GVariant, GVariantDeleter> wpa(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "WPA"));
+        std::unique_ptr<GVariant, GVariantDeleter> rsn(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "RSN"));
+
+        uint8_t res = IsNetworkWPAPSK(wpa.get()) | IsNetworkWPA2PSK(rsn.get());
+        if (res == 0)
+        {
+            res = 1; // Open
+        }
+        return res & (0x7F);
+    };
+
+    // Drop the network if its SSID or BSSID is illegal.
+    VerifyOrReturnError(ssidLen <= kMaxWiFiSSIDLength, false);
+    VerifyOrReturnError(bssidLen == kWiFiBSSIDLength, false);
+    memcpy(result.ssid, ssidStr, ssidLen);
+    memcpy(result.bssid, bssidBuf, bssidLen);
+    result.ssidLen = ssidLen;
+    if (signal < INT8_MIN)
+    {
+        result.rssi = INT8_MIN;
+    }
+    else if (signal > INT8_MAX)
+    {
+        result.rssi = INT8_MAX;
+    }
+    else
+    {
+        result.rssi = static_cast<uint8_t>(signal);
+    }
+
+    auto bandInfo   = GetBandAndChannelFromFrequency(frequency);
+    result.wiFiBand = bandInfo.first;
+    result.channel  = bandInfo.second;
+    result.security = GetNetworkSecurityType(bssProxy);
+
+    return true;
+}
+
+void ConnectivityManagerImpl::_OnWpaInterfaceScanDone(GObject * source_object, GAsyncResult * res, gpointer user_data)
+{
+    ChipLogProgress(DeviceLayer, "wpa_supplicant: network scan done");
+    gchar ** bsss    = wpa_fi_w1_wpa_supplicant1_interface_dup_bsss(mWpaSupplicant.iface);
+    gchar ** oldBsss = bsss;
+    if (bsss == nullptr)
+    {
+        ChipLogProgress(DeviceLayer, "wpa_supplicant: no network found");
+        DeviceLayer::SystemLayer().ScheduleLambda([]() {
+            if (mpScanCallback != nullptr)
+            {
+                mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), nullptr);
+                mpScanCallback = nullptr;
+            }
+        });
+        return;
+    }
+
+    std::vector<WiFiScanResponse> * networkScanned = new std::vector<WiFiScanResponse>();
+    for (const gchar * bssPath = (bsss != nullptr ? *bsss : nullptr); bssPath != nullptr; bssPath = *(++bsss))
+    {
+        WiFiScanResponse network;
+        if (_GetBssInfo(bssPath, network))
+        {
+            networkScanned->push_back(network);
+        }
+    }
+
+    DeviceLayer::SystemLayer().ScheduleLambda([networkScanned]() {
+        // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of
+        // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this
+        // lambda.
+        if (mpScanCallback != nullptr)
+        {
+            LinuxScanResponseIterator<WiFiScanResponse> iter(const_cast<std::vector<WiFiScanResponse> *>(networkScanned));
+            mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter);
+            mpScanCallback = nullptr;
+        }
+
+        delete const_cast<std::vector<WiFiScanResponse> *>(networkScanned);
+    });
+
+    g_strfreev(oldBsss);
+}
+
 #endif // CHIP_DEVICE_CONFIG_ENABLE_WPA
 
 } // namespace DeviceLayer
diff --git a/src/platform/Linux/ConnectivityManagerImpl.h b/src/platform/Linux/ConnectivityManagerImpl.h
index 39b7aec..555aa2a 100644
--- a/src/platform/Linux/ConnectivityManagerImpl.h
+++ b/src/platform/Linux/ConnectivityManagerImpl.h
@@ -45,6 +45,10 @@
 #include <mutex>
 #endif
 
+#include <platform/Linux/NetworkCommissioningDriver.h>
+#include <platform/NetworkCommissioning.h>
+#include <vector>
+
 namespace chip {
 namespace Inet {
 class IPAddress;
@@ -110,11 +114,19 @@
 public:
 #if CHIP_DEVICE_CONFIG_ENABLE_WPA
     CHIP_ERROR ProvisionWiFiNetwork(const char * ssid, const char * key);
+    CHIP_ERROR ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials,
+                                       NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback);
+    void PostNetworkConnect();
+    static void _ConnectWiFiNetworkAsyncCallback(GObject * source_object, GAsyncResult * res, gpointer user_data);
+    CHIP_ERROR CommitConfig();
+
     void StartWiFiManagement();
     bool IsWiFiManagementStarted();
     CHIP_ERROR GetWiFiBssId(ByteSpan & value);
     CHIP_ERROR GetWiFiSecurityType(uint8_t & securityType);
     CHIP_ERROR GetWiFiVersion(uint8_t & wiFiVersion);
+    CHIP_ERROR GetConnectedNetwork(NetworkCommissioning::Network & network);
+    CHIP_ERROR StartWiFiScan(ByteSpan ssid, NetworkCommissioning::WiFiDriver::ScanCallback * callback);
 #endif
 
     const char * GetEthernetIfName() { return (mEthIfName[0] == '\0') ? nullptr : mEthIfName; }
@@ -126,6 +138,18 @@
 private:
     // ===== Members that implement the ConnectivityManager abstract interface.
 
+    struct WiFiNetworkScanned
+    {
+        // The fields matches WiFiInterfaceScanResult::Type.
+        uint8_t ssid[Internal::kMaxWiFiSSIDLength];
+        uint8_t ssidLen;
+        uint8_t bssid[6];
+        int8_t rssi;
+        uint16_t frequencyBand;
+        uint8_t channel;
+        uint8_t security;
+    };
+
     CHIP_ERROR _Init();
     void _OnPlatformEvent(const ChipDeviceEvent * event);
 
@@ -150,6 +174,7 @@
     void _MaintainOnDemandWiFiAP();
     System::Clock::Timeout _GetWiFiAPIdleTimeout();
     void _SetWiFiAPIdleTimeout(System::Clock::Timeout val);
+    static CHIP_ERROR StopAutoScan();
 
     static void _OnWpaProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data);
     static void _OnWpaInterfaceRemoved(WpaFiW1Wpa_supplicant1 * proxy, const gchar * path, GVariant * properties,
@@ -160,6 +185,9 @@
     static void _OnWpaInterfaceReady(GObject * source_object, GAsyncResult * res, gpointer user_data);
     static void _OnWpaInterfaceProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data);
     static void _OnWpaBssProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data);
+    static void _OnWpaInterfaceScanDone(GObject * source_object, GAsyncResult * res, gpointer user_data);
+
+    static bool _GetBssInfo(const gchar * bssPath, NetworkCommissioning::WiFiScanResponse & result);
 
     static bool mAssociattionStarted;
     static BitFlags<ConnectivityFlags> mConnectivityFlag;
@@ -199,6 +227,9 @@
 #if CHIP_DEVICE_CONFIG_ENABLE_WIFI
     static char sWiFiIfName[IFNAMSIZ];
 #endif
+
+    static NetworkCommissioning::WiFiDriver::ScanCallback * mpScanCallback;
+    static NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback;
 };
 
 #if CHIP_DEVICE_CONFIG_ENABLE_WPA
diff --git a/src/platform/Linux/NetworkCommissioningDriver.h b/src/platform/Linux/NetworkCommissioningDriver.h
new file mode 100644
index 0000000..41daa19
--- /dev/null
+++ b/src/platform/Linux/NetworkCommissioningDriver.h
@@ -0,0 +1,159 @@
+/*
+ *
+ *    Copyright (c) 2021 Project CHIP Authors
+ *
+ *    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.
+ */
+
+#pragma once
+
+#include <platform/NetworkCommissioning.h>
+#include <vector>
+
+namespace chip {
+namespace DeviceLayer {
+namespace NetworkCommissioning {
+
+template <typename T>
+class LinuxScanResponseIterator : public Iterator<T>
+{
+public:
+    LinuxScanResponseIterator(std::vector<T> * apScanResponse) : mpScanResponse(apScanResponse) {}
+    size_t Count() override { return mpScanResponse != nullptr ? mpScanResponse->size() : 0; }
+    bool Next(T & item) override
+    {
+        if (mpScanResponse == nullptr || currentIterating >= mpScanResponse->size())
+        {
+            return false;
+        }
+        item = (*mpScanResponse)[currentIterating];
+        currentIterating++;
+        return true;
+    }
+    void Release() override
+    { /* nothing to do, we don't hold the ownership of the vector, and users is not expected to hold the ownership in OnFinished for
+         scan. */
+    }
+
+private:
+    size_t currentIterating = 0;
+    // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable.
+    std::vector<T> * mpScanResponse;
+};
+
+#if CHIP_DEVICE_CONFIG_ENABLE_WPA
+class LinuxWiFiDriver final : public WiFiDriver
+{
+public:
+    class WiFiNetworkIterator final : public NetworkIterator
+    {
+    public:
+        WiFiNetworkIterator(LinuxWiFiDriver * aDriver) : driver(aDriver) {}
+        size_t Count() override;
+        bool Next(Network & item) override;
+        void Release() override { delete this; }
+        ~WiFiNetworkIterator() = default;
+
+    private:
+        LinuxWiFiDriver * driver;
+        bool exhausted = false;
+    };
+
+    struct WiFiNetwork
+    {
+        uint8_t ssid[DeviceLayer::Internal::kMaxWiFiSSIDLength];
+        uint8_t ssidLen = 0;
+        uint8_t credentials[DeviceLayer::Internal::kMaxWiFiKeyLength];
+        uint8_t credentialsLen = 0;
+    };
+
+    // BaseDriver
+    NetworkIterator * GetNetworks() override { return new WiFiNetworkIterator(this); }
+    CHIP_ERROR Init() override;
+    CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } // Nothing to do on linux for shutdown.
+
+    // WirelessDriver
+    uint8_t GetMaxNetworks() override { return 1; }
+    uint8_t GetScanNetworkTimeoutSeconds() override { return 10; }
+    uint8_t GetConnectNetworkTimeoutSeconds() override { return 20; }
+
+    CHIP_ERROR CommitConfiguration() override;
+    CHIP_ERROR RevertConfiguration() override;
+
+    Status RemoveNetwork(ByteSpan networkId) override;
+    Status ReorderNetwork(ByteSpan networkId, uint8_t index) override;
+    void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override;
+
+    // WiFiDriver
+    Status AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials) override;
+    void ScanNetworks(ByteSpan ssid, ScanCallback * callback) override;
+
+private:
+    bool NetworkMatch(const WiFiNetwork & network, ByteSpan networkId);
+
+    WiFiNetworkIterator mWiFiIterator = WiFiNetworkIterator(this);
+    WiFiNetwork mSavedNetwork;
+    WiFiNetwork mStagingNetwork;
+};
+#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA
+
+#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
+class LinuxThreadDriver final : public ThreadDriver
+{
+public:
+    class ThreadNetworkIterator final : public NetworkIterator
+    {
+    public:
+        ThreadNetworkIterator(LinuxThreadDriver * aDriver) : driver(aDriver) {}
+        size_t Count() override;
+        bool Next(Network & item) override;
+        void Release() override { delete this; }
+        ~ThreadNetworkIterator() = default;
+
+    private:
+        LinuxThreadDriver * driver;
+        bool exhausted = false;
+    };
+
+    // BaseDriver
+    NetworkIterator * GetNetworks() override { return new ThreadNetworkIterator(this); }
+    CHIP_ERROR Init() override;
+    CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } // Nothing to do on linux for shutdown.
+
+    // WirelessDriver
+    uint8_t GetMaxNetworks() override { return 1; }
+    uint8_t GetScanNetworkTimeoutSeconds() override { return 10; }
+    uint8_t GetConnectNetworkTimeoutSeconds() override { return 20; }
+
+    CHIP_ERROR CommitConfiguration() override;
+    CHIP_ERROR RevertConfiguration() override;
+
+    Status RemoveNetwork(ByteSpan networkId) override;
+    Status ReorderNetwork(ByteSpan networkId, uint8_t index) override;
+    void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override;
+
+    // ThreadDriver
+    Status AddOrUpdateNetwork(ByteSpan operationalDataset) override;
+    void ScanNetworks(ScanCallback * callback) override;
+
+private:
+    ThreadNetworkIterator mThreadIterator = ThreadNetworkIterator(this);
+    Thread::OperationalDataset mSavedNetwork;
+    Thread::OperationalDataset mStagingNetwork;
+};
+
+#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD
+
+} // namespace NetworkCommissioning
+} // namespace DeviceLayer
+} // namespace chip
diff --git a/src/platform/Linux/NetworkCommissioningThreadDriver.cpp b/src/platform/Linux/NetworkCommissioningThreadDriver.cpp
new file mode 100644
index 0000000..abbeeaa
--- /dev/null
+++ b/src/platform/Linux/NetworkCommissioningThreadDriver.cpp
@@ -0,0 +1,196 @@
+/*
+ *
+ *    Copyright (c) 2021 Project CHIP Authors
+ *
+ *    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 <lib/support/CodeUtils.h>
+#include <lib/support/SafeInt.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <platform/Linux/NetworkCommissioningDriver.h>
+#include <platform/Linux/ThreadStackManagerImpl.h>
+#include <platform/ThreadStackManager.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+using namespace chip;
+using namespace chip::Thread;
+
+namespace chip {
+namespace DeviceLayer {
+namespace NetworkCommissioning {
+
+#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
+
+// NOTE: For ThreadDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will
+// load the network config from otbr-agent, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks,
+// all changed are made on the staging network.
+// TODO: The otbr-posix does not actually maintains its own networking states, it will always persist the last network connected.
+// This should not be an issue for most cases, but we should implement the code for maintaining the states by ourselves.
+
+CHIP_ERROR LinuxThreadDriver::Init()
+{
+    ByteSpan currentProvision;
+    VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), CHIP_NO_ERROR);
+    VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(currentProvision) == CHIP_NO_ERROR, CHIP_NO_ERROR);
+
+    mSavedNetwork.Init(currentProvision);
+    mStagingNetwork.Init(currentProvision);
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LinuxThreadDriver::CommitConfiguration()
+{
+    // Note: otbr-agent will persist the networks by their own, we don't have much to do for saving the networks (see Init() above,
+    // we just loads the saved dataset from otbr-agent.)
+    mSavedNetwork = mStagingNetwork;
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LinuxThreadDriver::RevertConfiguration()
+{
+    mStagingNetwork = mSavedNetwork;
+    return CHIP_NO_ERROR;
+}
+
+Status LinuxThreadDriver::AddOrUpdateNetwork(ByteSpan operationalDataset)
+{
+    uint8_t extpanid[kSizeExtendedPanId];
+    uint8_t newExtpanid[kSizeExtendedPanId];
+    Thread::OperationalDataset newDataset;
+
+    newDataset.Init(operationalDataset);
+    VerifyOrReturnError(newDataset.IsCommissioned(), Status::kOutOfRange);
+
+    VerifyOrReturnError(!mStagingNetwork.IsCommissioned() || memcmp(extpanid, newExtpanid, kSizeExtendedPanId) == 0,
+                        Status::kBoundsExceeded);
+
+    mStagingNetwork = newDataset;
+    return Status::kSuccess;
+}
+
+Status LinuxThreadDriver::RemoveNetwork(ByteSpan networkId)
+{
+    uint8_t extpanid[kSizeExtendedPanId];
+    if (!mStagingNetwork.IsCommissioned())
+    {
+        return Status::kNetworkNotFound;
+    }
+    else if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR)
+    {
+        return Status::kUnknownError;
+    }
+
+    VerifyOrReturnError(networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0,
+                        Status::kNetworkNotFound);
+    mStagingNetwork.Clear();
+    return Status::kSuccess;
+}
+
+Status LinuxThreadDriver::ReorderNetwork(ByteSpan networkId, uint8_t index)
+{
+    uint8_t extpanid[kSizeExtendedPanId];
+    if (!mStagingNetwork.IsCommissioned())
+    {
+        return Status::kNetworkNotFound;
+    }
+    else if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR)
+    {
+        return Status::kUnknownError;
+    }
+
+    VerifyOrReturnError(networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0,
+                        Status::kNetworkNotFound);
+
+    return Status::kSuccess;
+}
+
+void LinuxThreadDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback)
+{
+    NetworkCommissioning::Status status = Status::kSuccess;
+    uint8_t extpanid[kSizeExtendedPanId];
+    if (!mStagingNetwork.IsCommissioned())
+    {
+        ExitNow(status = Status::kNetworkNotFound);
+    }
+    else if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR)
+    {
+        ExitNow(status = Status::kUnknownError);
+    }
+
+    VerifyOrExit((networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0),
+                 status = Status::kNetworkNotFound);
+
+    VerifyOrExit(DeviceLayer::ThreadStackMgrImpl().AttachToThreadNetwork(mStagingNetwork.AsByteSpan(), callback) == CHIP_NO_ERROR,
+                 status = Status::kUnknownError);
+
+exit:
+    if (status != Status::kSuccess)
+    {
+        callback->OnResult(status, CharSpan(), 0);
+    }
+}
+
+void LinuxThreadDriver::ScanNetworks(ThreadDriver::ScanCallback * callback)
+{
+    CHIP_ERROR err = DeviceLayer::ThreadStackMgrImpl().StartThreadScan(callback);
+    if (err != CHIP_NO_ERROR)
+    {
+        callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr);
+    }
+}
+
+size_t LinuxThreadDriver::ThreadNetworkIterator::Count()
+{
+    return driver->mStagingNetwork.IsCommissioned() ? 1 : 0;
+}
+
+bool LinuxThreadDriver::ThreadNetworkIterator::Next(Network & item)
+{
+    if (exhausted || !driver->mStagingNetwork.IsCommissioned())
+    {
+        return false;
+    }
+    uint8_t extpanid[kSizeExtendedPanId];
+    VerifyOrReturnError(driver->mStagingNetwork.GetExtendedPanId(extpanid) == CHIP_NO_ERROR, false);
+    memcpy(item.networkID, extpanid, kSizeExtendedPanId);
+    item.networkIDLen = kSizeExtendedPanId;
+    item.connected    = false;
+    exhausted         = true;
+
+    ByteSpan currentProvision;
+    Thread::OperationalDataset currentDataset;
+    uint8_t enabledExtPanId[Thread::kSizeExtendedPanId];
+
+    // The Thread network is not actually enabled.
+    VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), true);
+    VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(currentProvision) == CHIP_NO_ERROR, true);
+    VerifyOrReturnError(currentDataset.Init(currentProvision) == CHIP_NO_ERROR, true);
+    // The Thread network is not enabled, but has a different extended pan id.
+    VerifyOrReturnError(currentDataset.GetExtendedPanId(enabledExtPanId) == CHIP_NO_ERROR, true);
+    VerifyOrReturnError(memcmp(extpanid, enabledExtPanId, kSizeExtendedPanId) == 0, true);
+    // The Thread network is enabled and has the same extended pan id as the one in our record.
+    item.connected = true;
+
+    return true;
+}
+
+#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD
+
+} // namespace NetworkCommissioning
+} // namespace DeviceLayer
+} // namespace chip
diff --git a/src/platform/Linux/NetworkCommissioningWiFiDriver.cpp b/src/platform/Linux/NetworkCommissioningWiFiDriver.cpp
new file mode 100644
index 0000000..5483715
--- /dev/null
+++ b/src/platform/Linux/NetworkCommissioningWiFiDriver.cpp
@@ -0,0 +1,207 @@
+/*
+ *
+ *    Copyright (c) 2021 Project CHIP Authors
+ *
+ *    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 <lib/support/CodeUtils.h>
+#include <lib/support/SafeInt.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <platform/Linux/NetworkCommissioningDriver.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+using namespace chip;
+using namespace chip::Thread;
+
+namespace chip {
+namespace DeviceLayer {
+namespace NetworkCommissioning {
+
+#if CHIP_DEVICE_CONFIG_ENABLE_WPA
+// TODO: Here, most interfaces are just calling ConnectivityManager interfaces, this is because the ConnectivityProvides some
+// bootstrap code for the wpa_supplicant. However, we can wrap the wpa_supplicant dbus api directly (and remove the related code in
+// ConnectivityManagerImpl).
+namespace {
+constexpr char kWiFiSSIDKeyName[]        = "wifi-ssid";
+constexpr char kWiFiCredentialsKeyName[] = "wifi-pass";
+} // namespace
+
+// NOTE: For WiFiDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will
+// load the network config from k-v storage, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks,
+// all changed are made on the staging network, and when the network is committed, it will update the mSavedNetwork to
+// mStagingNetwork and persist the changes.
+
+// NOTE: LinuxWiFiDriver uses network config with empty ssid (ssidLen = 0) for empty network config.
+
+// NOTE: For now, the LinuxWiFiDriver only supports one network, this can be fixed by using the wpa_supplicant API directly (then
+// wpa_supplicant will manage the networks for us.)
+
+CHIP_ERROR LinuxWiFiDriver::Init()
+{
+    CHIP_ERROR err;
+    size_t ssidLen        = 0;
+    size_t credentialsLen = 0;
+
+    err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiCredentialsKeyName, mSavedNetwork.credentials,
+                                                   sizeof(mSavedNetwork.credentials), &credentialsLen);
+    if (err == CHIP_ERROR_KEY_NOT_FOUND)
+    {
+        return CHIP_NO_ERROR;
+    }
+
+    err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiSSIDKeyName, mSavedNetwork.ssid, sizeof(mSavedNetwork.ssid), &ssidLen);
+    if (err == CHIP_ERROR_KEY_NOT_FOUND)
+    {
+        return CHIP_NO_ERROR;
+    }
+
+    mSavedNetwork.credentialsLen = credentialsLen;
+    mSavedNetwork.ssidLen        = ssidLen;
+
+    mStagingNetwork = mSavedNetwork;
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LinuxWiFiDriver::CommitConfiguration()
+{
+    ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kWiFiSSIDKeyName, mStagingNetwork.ssid, mStagingNetwork.ssidLen));
+    ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kWiFiCredentialsKeyName, mStagingNetwork.credentials,
+                                                                  mStagingNetwork.credentialsLen));
+    ReturnErrorOnFailure(ConnectivityMgrImpl().CommitConfig());
+    mSavedNetwork = mStagingNetwork;
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LinuxWiFiDriver::RevertConfiguration()
+{
+    mStagingNetwork = mSavedNetwork;
+    return CHIP_NO_ERROR;
+}
+
+bool LinuxWiFiDriver::NetworkMatch(const WiFiNetwork & network, ByteSpan networkId)
+{
+    return networkId.size() == network.ssidLen && memcmp(networkId.data(), network.ssid, network.ssidLen) == 0;
+}
+
+Status LinuxWiFiDriver::AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials)
+{
+    VerifyOrReturnError(mStagingNetwork.ssidLen == 0 || NetworkMatch(mStagingNetwork, ssid), Status::kBoundsExceeded);
+
+    static_assert(sizeof(WiFiNetwork::ssid) <= std::numeric_limits<decltype(WiFiNetwork::ssidLen)>::max(),
+                  "Max length of WiFi ssid exceeds the limit of ssidLen field");
+    static_assert(sizeof(WiFiNetwork::credentials) <= std::numeric_limits<decltype(WiFiNetwork::credentialsLen)>::max(),
+                  "Max length of WiFi credentials exceeds the limit of credentialsLen field");
+
+    // Do the check before setting the values, so the data is not updated on error.
+    VerifyOrReturnError(credentials.size() <= sizeof(mStagingNetwork.credentials), Status::kOutOfRange);
+    VerifyOrReturnError(ssid.size() <= sizeof(mStagingNetwork.ssid), Status::kOutOfRange);
+
+    memcpy(mStagingNetwork.credentials, credentials.data(), credentials.size());
+    mStagingNetwork.credentialsLen = static_cast<decltype(mStagingNetwork.credentialsLen)>(credentials.size());
+
+    memcpy(mStagingNetwork.ssid, ssid.data(), ssid.size());
+    mStagingNetwork.ssidLen = static_cast<decltype(mStagingNetwork.ssidLen)>(ssid.size());
+
+    return Status::kSuccess;
+}
+
+Status LinuxWiFiDriver::RemoveNetwork(ByteSpan networkId)
+{
+    VerifyOrReturnError(NetworkMatch(mStagingNetwork, networkId), Status::kNetworkIDNotFound);
+
+    // Use empty ssid for representing invalid network
+    mStagingNetwork.ssidLen = 0;
+    return Status::kSuccess;
+}
+
+Status LinuxWiFiDriver::ReorderNetwork(ByteSpan networkId, uint8_t index)
+{
+    VerifyOrReturnError(NetworkMatch(mStagingNetwork, networkId), Status::kNetworkIDNotFound);
+    // We only support one network, so reorder is actually no-op.
+
+    return Status::kSuccess;
+}
+
+void LinuxWiFiDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback)
+{
+    CHIP_ERROR err          = CHIP_NO_ERROR;
+    Status networkingStatus = Status::kSuccess;
+
+    VerifyOrExit(NetworkMatch(mStagingNetwork, networkId), networkingStatus = Status::kNetworkIDNotFound);
+
+    ChipLogProgress(NetworkProvisioning, "LinuxNetworkCommissioningDelegate: SSID: %s", networkId.data());
+
+    err = ConnectivityMgrImpl().ConnectWiFiNetworkAsync(ByteSpan(mStagingNetwork.ssid, mStagingNetwork.ssidLen),
+                                                        ByteSpan(mStagingNetwork.credentials, mStagingNetwork.credentialsLen),
+                                                        callback);
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        networkingStatus = Status::kUnknownError;
+    }
+
+    if (networkingStatus != Status::kSuccess)
+    {
+        ChipLogError(NetworkProvisioning, "Failed to connect to WiFi network: %s", chip::ErrorStr(err));
+        callback->OnResult(networkingStatus, CharSpan(), 0);
+    }
+}
+
+void LinuxWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * callback)
+{
+    CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().StartWiFiScan(ssid, callback);
+    if (err != CHIP_NO_ERROR)
+    {
+        callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr);
+    }
+}
+
+size_t LinuxWiFiDriver::WiFiNetworkIterator::Count()
+{
+    return driver->mStagingNetwork.ssidLen == 0 ? 0 : 1;
+}
+
+bool LinuxWiFiDriver::WiFiNetworkIterator::Next(Network & item)
+{
+    if (exhausted || driver->mStagingNetwork.ssidLen == 0)
+    {
+        return false;
+    }
+    memcpy(item.networkID, driver->mStagingNetwork.ssid, driver->mStagingNetwork.ssidLen);
+    item.networkIDLen = driver->mStagingNetwork.ssidLen;
+    item.connected    = false;
+    exhausted         = true;
+
+    Network connectedNetwork;
+    CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().GetConnectedNetwork(connectedNetwork);
+    if (err == CHIP_NO_ERROR)
+    {
+        if (connectedNetwork.networkIDLen == item.networkIDLen &&
+            memcmp(connectedNetwork.networkID, item.networkID, item.networkIDLen) == 0)
+        {
+            item.connected = true;
+        }
+    }
+
+    return true;
+}
+
+#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA
+
+} // namespace NetworkCommissioning
+} // namespace DeviceLayer
+} // namespace chip
diff --git a/src/platform/Linux/ThreadStackManagerImpl.cpp b/src/platform/Linux/ThreadStackManagerImpl.cpp
index 2fc6614..d50aed7 100644
--- a/src/platform/Linux/ThreadStackManagerImpl.cpp
+++ b/src/platform/Linux/ThreadStackManagerImpl.cpp
@@ -21,11 +21,17 @@
 #include <app/AttributeAccessInterface.h>
 #include <lib/support/CodeUtils.h>
 #include <lib/support/logging/CHIPLogging.h>
+#include <platform/Linux/NetworkCommissioningDriver.h>
 #include <platform/PlatformManager.h>
 #include <platform/ThreadStackManager.h>
 
+#include <nlbyteorder.hpp>
+#include <nlio-byteorder.hpp>
+#include <nlio.hpp>
+
 using namespace ::chip::app;
 using namespace ::chip::app::Clusters;
+using namespace chip::DeviceLayer::NetworkCommissioning;
 
 namespace chip {
 namespace DeviceLayer {
@@ -232,8 +238,15 @@
     VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE);
 
     {
+        // TODO: The following code does not works actually, since otbr-posix does not emit signals for properties changes. Which is
+        // required for gdbus to caching properties.
         std::unique_ptr<GVariant, GVariantDeleter> value(
             openthread_io_openthread_border_router_dup_active_dataset_tlvs(mProxy.get()));
+        if (value == nullptr)
+        {
+            netInfo = ByteSpan();
+            return CHIP_ERROR_KEY_NOT_FOUND;
+        }
         GBytes * bytes = g_variant_get_data_as_bytes(value.get());
         gsize size;
         const uint8_t * data = reinterpret_cast<const uint8_t *>(g_bytes_get_data(bytes, &size));
@@ -276,20 +289,7 @@
     VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE);
     if (val)
     {
-        std::unique_ptr<GError, GErrorDeleter> err;
-        gboolean result =
-            openthread_io_openthread_border_router_call_attach_sync(mProxy.get(), nullptr, &MakeUniquePointerReceiver(err).Get());
-        if (err)
-        {
-            ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Attach", err->message);
-            return CHIP_ERROR_INTERNAL;
-        }
-
-        if (!result)
-        {
-            ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Attach", "return false");
-            return CHIP_ERROR_INTERNAL;
-        }
+        openthread_io_openthread_border_router_call_attach(mProxy.get(), nullptr, _OnThreadAttachFinished, this);
     }
     else
     {
@@ -311,6 +311,41 @@
     return CHIP_NO_ERROR;
 }
 
+void ThreadStackManagerImpl::_OnThreadAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data)
+{
+    ThreadStackManagerImpl * this_ = reinterpret_cast<ThreadStackManagerImpl *>(user_data);
+    std::unique_ptr<GVariant, GVariantDeleter> attachRes;
+    std::unique_ptr<GError, GErrorDeleter> err;
+    {
+        gboolean result = openthread_io_openthread_border_router_call_attach_finish(this_->mProxy.get(), res,
+                                                                                    &MakeUniquePointerReceiver(err).Get());
+        if (!result)
+        {
+            ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s",
+                         err == nullptr ? "unknown error" : err->message);
+            DeviceLayer::SystemLayer().ScheduleLambda([this_]() {
+                if (this_->mpConnectCallback != nullptr)
+                {
+                    // TODO: Replace this with actual thread attach result.
+                    this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), 0);
+                    this_->mpConnectCallback = nullptr;
+                }
+            });
+        }
+        else
+        {
+            DeviceLayer::SystemLayer().ScheduleLambda([this_]() {
+                if (this_->mpConnectCallback != nullptr)
+                {
+                    // TODO: Replace this with actual thread attach result.
+                    this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0);
+                    this_->mpConnectCallback = nullptr;
+                }
+            });
+        }
+    }
+}
+
 ConnectivityManager::ThreadDeviceType ThreadStackManagerImpl::_GetThreadDeviceType()
 {
     ConnectivityManager::ThreadDeviceType type = ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported;
@@ -472,6 +507,109 @@
     return CHIP_ERROR_NOT_IMPLEMENTED;
 }
 
+CHIP_ERROR ThreadStackManagerImpl::StartThreadScan(ThreadDriver::ScanCallback * callback)
+{
+    // There is another ongoing scan request, reject the new one.
+    VerifyOrReturnError(mpScanCallback == nullptr, CHIP_ERROR_INCORRECT_STATE);
+    mpScanCallback = callback;
+    openthread_io_openthread_border_router_call_scan(mProxy.get(), nullptr, _OnNetworkScanFinished, this);
+    return CHIP_NO_ERROR;
+}
+
+void ThreadStackManagerImpl::_OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data)
+{
+    ThreadStackManagerImpl * this_ = reinterpret_cast<ThreadStackManagerImpl *>(user_data);
+    this_->_OnNetworkScanFinished(res);
+}
+
+void ThreadStackManagerImpl::_OnNetworkScanFinished(GAsyncResult * res)
+{
+    std::unique_ptr<GVariant, GVariantDeleter> scan_result;
+    std::unique_ptr<GError, GErrorDeleter> err;
+    {
+        gboolean result = openthread_io_openthread_border_router_call_scan_finish(
+            mProxy.get(), &MakeUniquePointerReceiver(scan_result).Get(), res, &MakeUniquePointerReceiver(err).Get());
+        if (!result)
+        {
+            ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s",
+                         err == nullptr ? "unknown error" : err->message);
+            DeviceLayer::SystemLayer().ScheduleLambda([this]() {
+                if (mpScanCallback != nullptr)
+                {
+                    LinuxScanResponseIterator<ThreadScanResponse> iter(nullptr);
+                    mpScanCallback->OnFinished(Status::kUnknownError, CharSpan(), &iter);
+                }
+                mpScanCallback = nullptr;
+            });
+        }
+    }
+
+    std::vector<NetworkCommissioning::ThreadScanResponse> * scanResult =
+        new std::vector<NetworkCommissioning::ThreadScanResponse>();
+
+    if (g_variant_n_children(scan_result.get()) > 0)
+    {
+        std::unique_ptr<GVariantIter, GVariantIterDeleter> iter;
+        g_variant_get(scan_result.get(), "a(tstayqqyyyybb)", &MakeUniquePointerReceiver(iter).Get());
+        if (!iter)
+            return;
+
+        guint64 ext_address;
+        const gchar * network_name;
+        guint64 ext_panid;
+        const gchar * steering_data;
+        guint16 panid;
+        guint16 joiner_udp_port;
+        guint8 channel;
+        guint8 rssi;
+        guint8 lqi;
+        guint8 version;
+        gboolean is_native;
+        gboolean is_joinable;
+
+        while (g_variant_iter_loop(iter.get(), "(tstayqqyyyybb)", &ext_address, &network_name, &ext_panid, &steering_data, &panid,
+                                   &joiner_udp_port, &channel, &rssi, &lqi, &version, &is_native, &is_joinable))
+        {
+            ChipLogProgress(DeviceLayer,
+                            "Thread Network: %s (%016" PRIx64 ") ExtPanId(%016" PRIx64 ") RSSI %" PRIu16 " LQI %" PRIu8
+                            " Version %" PRIu8,
+                            network_name, ext_address, ext_panid, rssi, lqi, version);
+            NetworkCommissioning::ThreadScanResponse networkScanned;
+            networkScanned.panId         = panid;
+            networkScanned.extendedPanId = ext_panid;
+            size_t networkNameLen        = strlen(network_name);
+            if (networkNameLen > 16)
+            {
+                ChipLogProgress(DeviceLayer, "Network name is too long, ignore it.");
+                continue;
+            }
+            networkScanned.networkNameLen = static_cast<uint8_t>(networkNameLen);
+            memcpy(networkScanned.networkName, network_name, networkNameLen);
+            networkScanned.channel         = channel;
+            networkScanned.version         = version;
+            networkScanned.extendedAddress = 0;
+            networkScanned.rssi            = rssi;
+            networkScanned.lqi             = lqi;
+
+            scanResult->push_back(networkScanned);
+        }
+    }
+
+    DeviceLayer::SystemLayer().ScheduleLambda([this, scanResult]() {
+        // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of
+        // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this
+        // lambda.
+        if (mpScanCallback != nullptr)
+        {
+            LinuxScanResponseIterator<NetworkCommissioning::ThreadScanResponse> iter(
+                const_cast<std::vector<ThreadScanResponse> *>(scanResult));
+            mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter);
+            mpScanCallback = nullptr;
+        }
+        delete const_cast<std::vector<ThreadScanResponse> *>(scanResult);
+    });
+}
+
 void ThreadStackManagerImpl::_ResetThreadNetworkDiagnosticsCounts() {}
 
 CHIP_ERROR ThreadStackManagerImpl::_WriteThreadNetworkDiagnosticAttributeToTlv(AttributeId attributeId,
@@ -498,6 +636,19 @@
     return err;
 }
 
+CHIP_ERROR
+ThreadStackManagerImpl::AttachToThreadNetwork(ByteSpan netInfo,
+                                              NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback)
+{
+    // There is another ongoing connect request, reject the new one.
+    VerifyOrReturnError(mpConnectCallback == nullptr, CHIP_ERROR_INCORRECT_STATE);
+    ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(false));
+    ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadProvision(netInfo));
+    ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(true));
+    mpConnectCallback = callback;
+    return CHIP_NO_ERROR;
+}
+
 ThreadStackManager & ThreadStackMgr()
 {
     return chip::DeviceLayer::ThreadStackManagerImpl::sInstance;
diff --git a/src/platform/Linux/ThreadStackManagerImpl.h b/src/platform/Linux/ThreadStackManagerImpl.h
index 3bbd298..caac537 100644
--- a/src/platform/Linux/ThreadStackManagerImpl.h
+++ b/src/platform/Linux/ThreadStackManagerImpl.h
@@ -18,11 +18,13 @@
 #pragma once
 
 #include <memory>
+#include <vector>
 
 #include <app/AttributeAccessInterface.h>
 #include <lib/support/ThreadOperationalDataset.h>
 #include <platform/Linux/GlibTypeDeleter.h>
 #include <platform/Linux/dbus/openthread/introspect.h>
+#include <platform/NetworkCommissioning.h>
 #include <platform/internal/CHIPDeviceLayerInternal.h>
 #include <platform/internal/DeviceNetworkInfo.h>
 
@@ -50,6 +52,11 @@
 
     CHIP_ERROR _SetThreadProvision(ByteSpan netInfo);
 
+    void _OnNetworkScanFinished(GAsyncResult * res);
+    static void _OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data);
+
+    CHIP_ERROR GetExtendedPanId(uint8_t extPanId[Thread::kSizeExtendedPanId]);
+
     void _ErasePersistentInfo();
 
     bool _IsThreadProvisioned();
@@ -58,7 +65,10 @@
 
     bool _IsThreadAttached();
 
+    CHIP_ERROR AttachToThreadNetwork(ByteSpan netInfo, NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback);
+
     CHIP_ERROR _SetThreadEnabled(bool val);
+    static void _OnThreadAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data);
 
     ConnectivityManager::ThreadDeviceType _GetThreadDeviceType();
 
@@ -90,6 +100,8 @@
 
     CHIP_ERROR _WriteThreadNetworkDiagnosticAttributeToTlv(AttributeId attributeId, app::AttributeValueEncoder & encoder);
 
+    CHIP_ERROR StartThreadScan(NetworkCommissioning::ThreadDriver::ScanCallback * callback);
+
     ~ThreadStackManagerImpl() = default;
 
     static ThreadStackManagerImpl sInstance;
@@ -106,6 +118,19 @@
 
     static constexpr char kPropertyDeviceRole[] = "DeviceRole";
 
+    struct ThreadNetworkScanned
+    {
+        uint16_t panId;
+        uint64_t extendedPanId;
+        uint8_t networkName[16];
+        uint8_t networkNameLen;
+        uint16_t channel;
+        uint8_t version;
+        uint64_t extendedAddress;
+        int8_t rssi;
+        uint8_t lqi;
+    };
+
     std::unique_ptr<OpenthreadIoOpenthreadBorderRouter, GObjectDeleter> mProxy;
 
     static void OnDbusPropertiesChanged(OpenthreadIoOpenthreadBorderRouter * proxy, GVariant * changed_properties,
@@ -114,6 +139,9 @@
 
     Thread::OperationalDataset mDataset = {};
 
+    NetworkCommissioning::ThreadDriver::ScanCallback * mpScanCallback;
+    NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback;
+
     bool mAttached;
 };
 
diff --git a/src/platform/Linux/dbus/openthread/introspect.xml b/src/platform/Linux/dbus/openthread/introspect.xml
index e92b3ac..e583697 100644
--- a/src/platform/Linux/dbus/openthread/introspect.xml
+++ b/src/platform/Linux/dbus/openthread/introspect.xml
@@ -30,8 +30,8 @@
           uint8[] steering_data
           uint16 panid
           uint16 joiner_udp_port
-          uint16 channel
-          uint16 rssi
+          uint8 channel
+          uint8 rssi
           uint8 lqi
           uint8 version
           bool is_native
@@ -40,7 +40,7 @@
       </literallayout>
     -->
     <method name="Scan">
-      <arg name="scan_result" type="a(tstayqqqqyybb)" direction="out"/> 
+      <arg name="scan_result" type="a(tstayqqyyyybb)" direction="out"/> 
     </method>
 
     <!-- Attach: Attach the current device to the Thread network using the current active network dataset. -->
diff --git a/src/platform/Linux/dbus/wpa/DBusWpaBss.xml b/src/platform/Linux/dbus/wpa/DBusWpaBss.xml
index b6e8fee..8ec5c4a 100644
--- a/src/platform/Linux/dbus/wpa/DBusWpaBss.xml
+++ b/src/platform/Linux/dbus/wpa/DBusWpaBss.xml
@@ -2,8 +2,18 @@
 <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "https://raw.githubusercontent.com/freedesktop/dbus/master/doc/introspect.dtd">
 <node>
     <interface name="fi.w1.wpa_supplicant1.BSS">
-        <property name="SSID" type="ay" access="read" />
-        <property name="BSSID" type="ay" access="read" />
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+        <signal name="PropertiesChanged">
+            <arg name="properties" type="a{sv}" />
+        </signal>
+
+        <property name="SSID" type="ay" access="read">
+            <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+        </property>
+        <property name="BSSID" type="ay" access="read">
+            <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+        </property>
+        <property name="WPA" type="a{sv}" access="read" />
         <property name="Signal" type="n" access="read" />
         <property name="Frequency" type="q" access="read" />
     </interface>