ʻO Wi-Fi a me nā mea pōkole ʻē aʻe. Pehea e loaʻa ai ka ʻikepili e pili ana i nā nodes Wi-Fi i kahi noi Android me ka ʻole o ka pehu

I kekahi lā pono wau e nānā i nā pūnaewele Wi-Fi mai nā polokalamu Android a loaʻa ka ʻikepili kikoʻī e pili ana i nā wahi komo.

Ma ʻaneʻi ua loaʻa iā mākou kekahi mau pilikia: off.Android palapala ua pau ka nui o nā papa i wehewehe ʻia (API level > 26), ʻaʻole i ʻike ʻia i loko; ʻo ka wehewehe ʻana i kekahi mau mea i loko o ka palapala he mea liʻiliʻi (no ka laʻana, ke kahua hiki o ka papa Huli ʻImi i ka manawa kākau, aneane ʻaʻohe mea i wehewehe ʻia, ʻoiai aia ka nui o nā ʻikepili koʻikoʻi). Aia paha ke kolu o ka paʻakikī ma ka ʻoiaʻiʻo i ka wā e hoʻokokoke mua ai ʻoe i ka Wi-Fi, ʻokoʻa ka heluhelu ʻana i ke kumumanaʻo a me ka hoʻonohonoho ʻana i ke alalai ma o localhost, pono ʻoe e hoʻoponopono i kekahi mau pōkole i ʻike ʻia i kēlā me kēia. Akā, ʻaʻole maopopo ke ʻano o ka pili ʻana a me ke kūkulu ʻana iā lākou (he manaʻo ka hoʻoholo a pili i ka ʻike mua).

Kūkākūkā kēia ʻatikala pehea e loaʻa ai ka ʻikepili piha e pili ana i ke kaiapuni Wi-Fi mai ka Android code me ka ʻole o NDK, hacks, akā hoʻohana wale i ka Android API a hoʻomaopopo i ka wehewehe ʻana.

Mai hoʻokaʻulua a hoʻomaka kākou e kākau code.

1. Hana i kahi papahana

Kuhi ʻia kēia memo no ka poʻe i hana i kahi papahana Android ma mua o hoʻokahi manawa, no laila e haʻalele mākou i nā kikoʻī o kēia mea. E hōʻike ʻia ke code ma lalo nei ma Kotlin, minSdkVersion=23.

2. Nā ʻae komo

No ka hana ʻana me Wi-Fi mai ka noi, pono ʻoe e kiʻi i nā ʻae mai ka mea hoʻohana. I kulike ai me palapala, no ka nānā ʻana i ka pūnaewele ma nā polokalamu me nā mana OS ma hope o 8.0, ma kahi o ke komo ʻana i ka nānā ʻana i ke kūlana o ke kaiapuni pūnaewele, pono ʻoe e komo e hoʻololi i ke kūlana o ka module Wi-Fi o ka hāmeʻa, a i ʻole ke komo ʻana i nā hoʻonohonoho (kokoke. a pololei paha). E hoʻomaka ana me ka mana 9.0, pono ʻoe e koi i ka mea hoʻohana no nā mea ʻelua, a noi pū aku i ka mea hoʻohana e hoʻā i nā lawelawe wahi. Mai poina e wehewehe pono i ka mea hoʻohana ʻo ia ka makemake o Google, ʻaʻole ko mākou makemake e kiu iā ia :)

No laila, ma AndroidManifest.xml mākou e hoʻohui:

    <uses-permission android_name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android_name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android_name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android_name="android.permission.ACCESS_FINE_LOCATION"/>

A ma ke code i loaʻa kahi loulou i ka hana o kēia manawa:

import android.app.Activity
import android.content.Context
import android.location.LocationManager
import androidx.core.app.ActivityCompat

....

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            ActivityCompat.requestPermissions(
                activity,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CHANGE_WIFI_STATE),
                1
            )
            makeEnableLocationServices(activity.applicationContext)
        } else {
            ActivityCompat.requestPermissions(
                activity,
                arrayOf(Manifest.permission.CHANGE_WIFI_STATE),
                1
            )
        }

    /* включает экран включения службы по определению местоположения */
    fun makeEnableLocationServices(context: Context) {
        // TODO: перед вызовом этой функции надо рассказать пользователю, зачем Вам доступ к местоположению
        val lm: LocationManager =
            context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager

        val gpsEnabled: Boolean = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
        val networkEnabled: Boolean = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

        if (!gpsEnabled && !networkEnabled) {
            context.startActivity(Intent(ACTION_LOCATION_SOURCE_SETTINGS));
        }
    }

3. Hana i kahi BroadcastReceiver a kau inoa i nā hanana hōʻano hou e pili ana i ka nānā ʻana i ke kaiapuni pūnaewele Wi-Fi

val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager

val wifiScanReceiver = object : BroadcastReceiver() {

  override fun onReceive(context: Context, intent: Intent) {
    val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
    if (success) {
      scanSuccess()
    } 
  }
}

val intentFilter = IntentFilter()
/* подписываемся на сообщения о получении новых результатов сканирования */
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
context.registerReceiver(wifiScanReceiver, intentFilter)

val success = wifiManager.startScan()
if (!success) {
  /* что-то не получилось при запуске сканирования, проверьте выданые разрешения */
}

....

private fun scanSuccess() {
 /* вот они, результаты сканирования */
  val results: List<ScanResult> = wifiManager.scanResults
}

ʻO ke ala WiFiManager.startScan i loko o ka palapala i hōʻailona ʻia ʻo ia ka mea i hoʻopau ʻia mai ka API version 28, akā ua hala. alakai manaʻo e hoʻohana.

I ka huina, ua loaʻa iā mākou kahi papa inoa o nā mea Huli ʻImi.

4. E nānā iā ScanResult a hoʻomaopopo i nā huaʻōlelo

E nānā kākou i kekahi mau māhele o kēia papa a wehewehe i ke ʻano o ia mau mea:

SSID — ʻO ka Service Set Identifier ka inoa o ka pūnaewele

Nā BSSID - Mea hoʻonohonoho hoʻonohonoho maʻamau - helu MAC o ka mea hoʻopili pūnaewele (wahi Wi-Fi)

ilikai — Loaʻa ka hōʻailona hōʻailona ikaika [dBm (Russian dBm) - Decibel, mana kuhikuhi 1 mW.] - He hōʻailona o ka ikaika hōʻailona i loaʻa. Lawe i kahi waiwai mai 0 a -100, ʻoi aku ka nui mai ka 0, ʻoi aku ka nui o ka mana hōʻailona i nalowale ma ke ala mai ka lae Wi-Fi i kāu hāmeʻa. Hiki ke loaʻa nā kikoʻī hou aku, no ka laʻana, ma ʻO Wikipedia. Eia wau e haʻi aku iā ʻoe e hoʻohana ana i ka papa Android WifiManager hiki iā ʻoe ke calibrate i ka pae hōʻailona ma kahi pālākiō mai ka maikaʻi a i ka weliweli i ka pae āu e koho ai:

        val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
        val numberOfLevels = 5
        val level = WifiManager.calculateSignalLevel(level, numberOfLevels)

alapine (frequency) — ka alapine hana o ke kiko Wi-Fi [Hz]. Ma waho aʻe o ke alapine ponoʻī, makemake paha ʻoe i ke kahawai i kapa ʻia. Loaʻa i kēlā me kēia wahi kona maʻemaʻe hana ponoʻī. I ka manawa e kākau ai, ʻo 2.4 GHz ka laulā kaulana loa o nā wahi Wi-Fi. Akā, no ka ʻoi aku ka pololei, hoʻouna ke kiko i ka ʻike i kāu kelepona ma kahi alapine helu kokoke i ka mea i kapa ʻia. Ka helu o nā kaha a me nā alapine kūpono hoʻohālikelike ʻia. Hana ʻia kēia i mea e hana ai nā wahi kokoke i nā alapine like ʻole, no laila ʻaʻole keʻakeʻa kekahi i kekahi a ʻaʻole hoʻi e hōʻemi i ka wikiwiki a me ka maikaʻi o ka hoʻouna ʻana. I kēia hihia, ʻaʻole e hana nā helu i hoʻokahi alapine, akā ma luna o kahi ākea o nā alapine (parameter channelWidth), i kapa ʻia ka laula o ke kahawai. ʻO ia hoʻi, nā kiko e hana ana ma kahi kokoke (a ʻaʻole wale nō e pili ana, akā ʻo 3 mai iā lākou iho) e hoʻopilikia kekahi i kekahi. Hiki iā ʻoe ke ʻike i kēia code maʻalahi, e hiki ai iā ʻoe ke helu i ka helu channel mai ka helu alapine no nā helu me ke alapine o 2.4 a me 5 Ghz:


    /* по частоте определяем номер канала */
    val channel: Int
        get() {
            return if (frequency in 2412..2484) {
                (frequency - 2412) / 5 + 1
            } else if (frequency in 5170..5825) {
                (frequency - 5170) / 5 + 34
            } else {
                -1
            }
        }

nā mea hiki - ke kahua hoihoi loa no ka nānā ʻana, hana me ka mea e pono ai ka manawa nui. Maanei ua kākau ʻia nā "hiki" o ke kiko ma ka laina. I kēia hihia, ʻaʻole pono ʻoe e ʻimi i nā kikoʻī o ka wehewehe ʻana i ke kaula i ka palapala. Eia kekahi mau laʻana o ka mea i loko o kēia laina:

[WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][WPS][ESS]
[WPA2-PSK-CCMP][ESS]
[WPA2-PSK-CCMP+TKIP][ESS]
[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS]
[ESS][WPS]

5. Ka hoʻomaopopo ʻana i nā pōkole a me nā hiki ke koho

Pono e ʻōlelo ʻia e hoʻohana ʻia nā papa o ka android.net.wifi.* ma lalo o ka puʻupuʻu e kahi pono Linux. wpa_supplicant a ʻo ka hopena o ka hoʻopuka ʻana i ke kahua hiki ke kope o ka māla hae i ka wā e nānā ana.

E hana mau mākou. E noʻonoʻo mua i ka hoʻopuka ʻana o kahi ʻano i hoʻokaʻawale ʻia nā mea i loko o nā pale e kahi hōʻailona "-":

[WPA-PSK-TKIP+CCMP]
[WPA2-PSK-CCMP]

ʻO ka manaʻo mua e wehewehe i ka mea i kapa ʻia. ʻano hōʻoia. ʻO ia hoʻi, he aha ke ʻano o nā hana e pono ai ka mea hana a me ka wahi hiki ke hana i mea e hiki ai i ka wahi hiki ke ʻae iā ia iho e hoʻohana ʻia a pehea e hoʻopili ai i ka uku uku. I ka manawa e kākau ai i kēia pou, ʻo nā koho maʻamau ʻo WPA a me WPA2, kahi i pili pono ai kēlā me kēia mea pili a i ʻole ma o ka mea i kapa ʻia. Hāʻawi ke kikowaena RADIUS (WPA-Enterprice) i ka ʻōlelo huna ma luna o kahi kahawai i hoʻopili ʻia. ʻO ka mea maʻamau, ʻo ka wahi komo i kou home e hāʻawi i kahi pilina e like me kēia hoʻolālā. ʻO ka ʻokoʻa ma waena o ka ʻaoʻao ʻelua a me ka mea mua, ʻoi aku ka ikaika o ka cipher: AES me ka TKIP paʻa ʻole. ʻO ka WPA3, ʻoi aku ka paʻakikī a me ka holomua, ke hoʻokomo lohi ʻia nei. ʻO ka manaʻo, aia paha kahi koho me ka hoʻonā enterprice CCKM (Cisco Centralized Key Management), akā ʻaʻole au i ʻike.

Ua hoʻonohonoho ʻia paha ka wahi komo e hōʻoia e ka helu MAC. A i ʻole, inā hāʻawi ka wahi komo i ka ʻikepili me ka hoʻohana ʻana i ka algorithm WEP kahiko, a laila ʻaʻohe hōʻoia ʻoiaʻiʻo (ʻo ke kī huna ma ʻaneʻi ke kī hoʻopunipuni). Hoʻokaʻawale mākou i nā koho e like me OTHER.
Aia kekahi ala kaulana i ka lehulehu wi-fi me ka huna Captive Portal Detection - kahi noi hōʻoia ma o ka polokalamu kele pūnaewele. Hōʻike ʻia kēlā mau wahi komo i ka scanner ma ke ʻano he hāmama (ʻo ia mai ka ʻike o ka pili kino). No laila, hoʻokaʻawale mākou iā lākou he OPEN.

Hiki ke kuhikuhi ʻia ka waiwai ʻelua algorithm hooponopono kī. He palena kēia o ke ʻano hōʻoia i hōʻike ʻia ma luna. Kūkākūkā e pili ana i ka hoʻololi ʻana i nā kī hoʻopunipuni. E noʻonoʻo kākou i nā koho hiki. EAP - hoʻohana ʻia i ka WPA-Enterprice i ʻōlelo ʻia, hoʻohana i kahi waihona e hōʻoia i ka ʻikepili hōʻoia i hoʻokomo ʻia. SAE - hoʻohana ʻia i ka WPA3 kiʻekiʻe, ʻoi aku ke kūpaʻa i ka ikaika. ʻO PSK - ke koho maʻamau, pili i ke komo ʻana i kahi ʻōlelo huna a hoʻouna iā ia ma ke ʻano i hoʻopili ʻia. IEEE8021X - e like me ke kūlana honua (ʻokoʻa i ka mea i kākoʻo ʻia e ka ʻohana WPA). ʻO OWE (Opportunistic Wireless Encryption) he hoʻonui ia o ka maʻamau IEEE 802.11 no nā helu a mākou i helu ʻia ʻo OPEN. Mālama ʻo OWE i ka palekana o ka ʻikepili i hoʻouna ʻia ma luna o kahi pūnaewele paʻa ʻole ma o ka hoʻopili ʻana iā ia. Hiki nō hoʻi ke koho inā ʻaʻohe kī komo, e kāhea kākou i kēia koho NONE.

ʻO ke kolu o ka parameter ka mea i kapa ʻia. nā papahana hoʻopunipuni - pehea e hoʻohana pono ʻia ai ka cipher no ka pale ʻana i ka ʻikepili i hoʻouna ʻia. E papa inoa i nā koho. WEP - hoʻohana i ka RC4 stream cipher, ʻo ke kī huna ke kī hoʻopunipuni, i manaʻo ʻia ʻaʻole i ʻae ʻia i ka honua o ka cryptography hou. TKIP - hoʻohana ʻia ma WPA, CKIP - ma WPA2. TKIP+CKIP - hiki ke kuhikuhi ʻia i nā kiko i hiki iā WPA a me WPA2 no ka hoʻokō ʻana i hope.

Ma kahi o ʻekolu mau mea, hiki iā ʻoe ke loaʻa kahi māka WEP mehameha:

[WEP]

E like me kā mākou i kūkākūkā ai ma luna, ua lawa kēia ʻaʻole e wehewehe i ka algorithm no ka hoʻohana ʻana i nā kī, ʻaʻole i loaʻa, a me ke ʻano hoʻopunipuni, ʻo ia ka mea like me ka paʻamau.

I kēia manawa e noʻonoʻo i kēia bracket:

[ESS]

keia ʻO ke ʻano hana Wi-Fi ai ole ia, Topology pūnaewele Wi-Fi. Hiki iā ʻoe ke hālāwai me ke ʻano BSS (Basic Service Set) - ke loaʻa hoʻokahi wahi komo e kamaʻilio ai nā mea pili. Hiki ke loaʻa ma nā pūnaewele kūloko. Ma ke ʻano maʻamau, pono nā wahi komo e hoʻopili ai i nā polokalamu mai nā pūnaewele kūloko like ʻole, no laila he ʻāpana lākou o Extended Service Sets - ESS. Hōʻike ke ʻano IBSS (Independent Basic Service Sets) he ʻāpana ka mea hana o kahi pūnaewele Peer-to-Peer.

Hiki iā ʻoe ke ʻike i ka hae WPS:

[WPS]

ʻO WPS (Wi-Fi Protected Setup) kahi protocol no ka hoʻomaka semi-akomi o kahi pūnaewele Wi-Fi. No ka hoʻomaka ʻana, hoʻokomo ka mea hoʻohana i kahi huaʻōlelo 8-ʻano a i ʻole kaomi i kahi pihi ma ke alalai. Inā ʻo ka ʻano mua kāu wahi e komo ai a ʻike ʻia kēia pahu pahu ma ka ʻaoʻao o ka inoa o kāu wahi komo, pono ʻoe e hele i ka ʻaoʻao admin a hoʻopau i ke komo WPS. ʻO ka ʻoiaʻiʻo, hiki ke ʻike pinepine ʻia ka PIN 8-helu e ka helu MAC, a i ʻole hiki ke hoʻokaʻawale ʻia i ka manawa i ʻike ʻia, hiki i kekahi ke hoʻohana pono ʻole.

6. E hana i kahi hoʻohālike a me ka hana parsing

Ma muli o ka mea a mākou i ʻike ai ma luna, e wehewehe mākou i ka mea i hana me ka hoʻohana ʻana i nā papa ʻikepili:

/* схема аутентификации */
enum class AuthMethod {
    WPA3,
    WPA2,
    WPA, // Wi-Fi Protected Access
    OTHER, // включает в себя Shared Key Authentication и др. использующие mac-address-based и WEP
    CCKM, // Cisco
    OPEN // Open Authentication. Может быть со скрытым Captive Portal Detection - запрос аутентификации через браузер
}

/* алгоритм ввода ключей */
enum class KeyManagementAlgorithm {
    IEEE8021X, // по стандарту
    EAP, // Extensible Authentication Protocol, расширяемый протокол аутентификации
    PSK, // Pre-Shared Key — каждый узел вводит пароль для доступа к сети
    WEP, // в WEP пароль является ключом шифрования (No auth key)
    SAE, // Simultaneous Authentication of Equals - может быть в WPA3
    OWE, // Opportunistic Wireless Encryption - в роутерах новых поколений, публичных сетях типа OPEN
    NONE // может быть без шифрования в OPEN, OTHER
}

/* метод шифрования */
enum class CipherMethod {
    WEP, // Wired Equivalent Privacy, Аналог шифрования трафика в проводных сетях
    TKIP, // Temporal Key Integrity Protocol
    CCMP, // Counter Mode with Cipher Block Chaining Message Authentication Code Protocol,
    // протокол блочного шифрования с кодом аутентичности сообщения и режимом сцепления блоков и счетчика
    // на основе AES
    NONE // может быть без шифрования в OPEN, OTHER
}

/* набор методов шифрования и протоколов, по которым может работать точка */
data class Capability(
    var authScheme: AuthMethod? = null,
    var keyManagementAlgorithm: KeyManagementAlgorithm? = null,
    var cipherMethod: CipherMethod? = null
)

/* Режим работы WiFi (или топология сетей WiFi) */
enum class TopologyMode {
    IBSS, // Эпизодическая сеть (Ad-Hoc или IBSS – Independent Basic Service Set).
    BSS, // Основная зона обслуживания Basic Service Set (BSS) или Infrastructure Mode.
    ESS // Расширенная зона обслуживания ESS – Extended Service Set.
}

I kēia manawa, e kākau kāua i kahi hana e hoʻokaʻawale i ke kahua kaha:


private fun parseCapabilities(capabilitiesString: String): List < Capability > {
    val capabilities: List < Capability > = capabilitiesString
        .splitByBrackets()
        .filter {
            !it.isTopology() && !it.isWps()
        }
        .flatMap {
            parseCapability(it)
        }
    return
        if (!capabilities.isEmpty()) {
            capabilities
        } else {
            listOf(Capability(AuthMethod.OPEN, KeyManagementAlgorithm.NONE, CipherMethod.NONE))
        }
}

private fun parseCapability(part: String): List < Capability > {
    if (part.contains("WEP")) {
        return listOf(Capability(
            AuthMethod.OTHER,
            KeyManagementAlgorithm.WEP,
            CipherMethod.WEP
        ))
    }

    val authScheme = when {
        part.contains("WPA3") - > AuthMethod.WPA3
        part.contains("WPA2") - > AuthMethod.WPA2
        part.contains("WPA") - > AuthMethod.WPA
        else - > null
    }

    val keyManagementAlgorithm = when {
        part.contains("OWE") - > KeyManagementAlgorithm.OWE
        part.contains("SAE") - > KeyManagementAlgorithm.SAE
        part.contains("IEEE802.1X") - > KeyManagementAlgorithm.IEEE8021X
        part.contains("EAP") - > KeyManagementAlgorithm.EAP
        part.contains("PSK") - > KeyManagementAlgorithm.PSK
        else - > null
    }

    val capabilities = ArrayList < Capability > ()
    if (part.contains("TKIP") || part.contains("CCMP")) {
        if (part.contains("TKIP")) {
            capabilities.add(Capability(
                authScheme ? : AuthMethod.OPEN,
                keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE,
                CipherMethod.TKIP
            ))
        }
        if (part.contains("CCMP")) {
            capabilities.add(Capability(
                authScheme ? : AuthMethod.OPEN,
                keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE,
                CipherMethod.CCMP
            ))
        }
    } else if (authScheme != null || keyManagementAlgorithm != null) {
        capabilities.add(Capability(
            authScheme ? : AuthMethod.OPEN,
            keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE,
            CipherMethod.NONE
        ))
    }

    return capabilities
}

private fun parseTopologyMode(capabilitiesString: String): TopologyMode ? {
    return capabilitiesString
        .splitByBrackets()
        .mapNotNull {
            when {
                it.contains("ESS") - > TopologyMode.ESS
                it.contains("BSS") - > TopologyMode.BSS
                it.contains("IBSS") - > TopologyMode.IBSS
                else - > null
            }
        }
        .firstOrNull()
}

private fun parseWPSAvailable(capabilitiesString: String): Boolean {
    return capabilitiesString
        .splitByBrackets()
        .any {
            it.isWps()
        }
}

private fun String.splitByBrackets(): List < String > {
    val m = Pattern.compile("[(.*?)]").matcher(this)
    val parts = ArrayList < String > ()
    while (m.find()) {
        parts.add(m.group().replace("[", "").replace("]", ""))
    }
    return parts
}

private fun String.isTopology(): Boolean {
    return TopologyMode.values().any {
        this == it.name
    }
}

private fun String.isWps(): Boolean {
    return this == "WPS"
}

8. E nana i ka hopena

E nānā au i ka pūnaewele a hōʻike iā ʻoe i kaʻu mea i loaʻa ai. Hōʻike ʻia nā hopena o ka hoʻopuka maʻalahi ma o Log.d:

Capability of Home-Home [WPA2-PSK-CCMP][ESS][WPS]
...
capabilities=[Capability(authScheme=WPA2, keyManagementAlgorithm=PSK, cipherMethod=CCMP)], topologyMode=ESS, availableWps=true

ʻAʻole i noʻonoʻo ʻia ka pilikia o ka hoʻopili ʻana i ka pūnaewele mai ke code noi. E ʻōlelo wale wau no ka heluhelu ʻana i nā huaʻōlelo i mālama ʻia mai ka OS o kahi polokalamu kelepona, pono ʻoe i nā kuleana aʻa a me ka makemake e ʻimi i ka ʻōnaehana faila e heluhelu ai wpa_supplicant.conf. Inā makemake ka loiloi noi e hoʻokomo i kahi ʻōlelo huna mai waho mai, hiki ke hana ʻia ka pilina ma o ka papa android.net.wifi.WifiManager.

Спасибо Egor Ponomarev no nā mea hoʻohui waiwai.

Inā manaʻo ʻoe he pono e hoʻohui a hoʻoponopono ʻia paha, e kākau i loko o nā manaʻo :)

Source: www.habr.com

Pākuʻi i ka manaʻo hoʻopuka