1. 시작하기 전에
스레드 보더 라우터 (TBR) Codelab에서는 Raspberry Pi를 기반으로 스레드 보더 라우터를 빌드하는 방법을 보여줍니다. 이 Codelab에서는
- 스레드와 Wi-Fi/이더넷 네트워크 간에 양방향 IP 연결을 설정합니다.
- mDNS (Wi-Fi/이더넷 링크) 및 SRP (스레드 네트워크)를 통해 양방향 서비스 검색을 제공합니다.
이 Codelab은 이전 Codelab을 기반으로 하며, 자체 보더 라우터와 앱이 Google API와 상호작용하여 단일 스레드 네트워크를 만드는 방법을 다룹니다. 스레드 크리덴셜을 수렴하는 것은 네트워크 견고성을 강화하고 스레드를 사용하는 애플리케이션과의 사용자 상호작용을 간소화하기 때문에 중요합니다.
기본 요건
- OTBR Codelab 완료
- Linux, Android/Kotlin, 스레드 네트워킹에 관한 기본 지식
학습할 내용
- Thread Sharing API를 사용하여 사용자 인증 정보 세트를 가져오고 설정하는 방법
- Google 네트워크와 동일한 사용자 인증 정보로 나만의 OpenThread 보더 라우터를 설정하는 방법
필요한 항목
- Raspberry Pi 4 보드 또는 OTBR (Open Thread Border Router)을 실행하는 다른 Linux 기반 보드
- 무선 공동 프로세서 (RCP)로 IEEE 802.15.4 연결을 제공하는 보드입니다. 다양한 SoC 공급업체의 저장소 목록과 안내는 OpenThread GitHub 페이지에서 확인하세요.
2. HTTP 서비스 설정
가장 먼저 필요한 것은 활성 크리덴셜을 읽고 OTBR에 대기 중인 크리덴셜을 쓸 수 있게 해주는 인터페이스입니다. TBR을 구축할 때는 여기에 나온 두 가지 예와 같이 자체적인 독점 메커니즘을 사용하세요. 첫 번째 옵션은 DBUS를 통해 로컬에서 OTBR 에이전트와 인터페이스하는 방법을 보여주며, 두 번째 옵션은 OTBR에서 빌드할 수 있는 REST API를 활용합니다.
두 방법 모두 안전하지 않으므로 프로덕션 환경에서 그대로 사용해서는 안 됩니다. 그러나 공급업체는 이 두 가지 방법 중 하나를 기반으로 암호화를 구축하여 프로덕션 환경에서 사용하거나, 사용자가 자체 모니터 서비스를 확장하여 루프백 HTTP 또는 본질적으로 로컬 DBUS 호출을 발행할 수 있습니다.
옵션 1: Python 스크립트의 DBUS 및 HTTP API
이 단계에서는 두 엔드포인트를 노출하여 크리덴셜을 읽고 설정하고, 최종적으로 DBUS 명령을 호출하는 기본 HTTP 서비스를 생성합니다.
OTBR로 사용될 RPi에 Python 3 종속 항목을 설치합니다.
$ pip install dbus-python shlex json
스크립트를 다음과 같이 실행합니다.
$ sudo python credentials_server.py 8081 serving at port 8081
이 샘플은 포트 8081에 HTTP 서버를 설정하고, 루트 경로에서 스레드 사용자 인증 정보를 검색하기 위한 GET 요청이나 스레드 사용자 인증 정보를 설정하기 위한 POST 요청을 수신 대기합니다. 페이로드는 항상 TLV를 사용한 JSON 구조입니다.
다음 PUT 요청은 /node/dataset/pending
경로를 통해 OTBR에 새로운 대기 중인 스레드 사용자 인증 정보를 설정합니다. 이 경우 대기 중인 사용자 인증 정보가 10초 후에 적용됩니다.
PUT /node/dataset/pending Host: <IP>:8081 ContentType: "application/json" acceptMimeType: "application/json" ... { "ActiveDataset": "<TLV encoded new Thread Dataset>" "PendingTimestamp": { "Seconds": <Unix timestamp in seconds>, "Ticks": 0, "Authoritative": false }, "Delay": 10000 // in milliseconds }
/node/dataset/active
에 대한 GET 요청은 현재 활성 사용자 인증 정보를 가져옵니다.
GET /node/dataset/active Host: <IP>:8081 ContentType = "application/json" acceptMimeType = "text/plain" ... <TLV encoded Thread Dataset>
스크립트가 버스 경로 io.openthread.BorderRouter.wpan0
, 객체 경로 /io/openthread/BorderRouter/wpan0
로 DBUS R/W 명령어를 호출합니다.
# D-BUS interface def call_dbus_method(interface, method_name, *arguments): bus = dbus.SystemBus() obj = bus.get_object('io.openthread.BorderRouter.wpan0', '/io/openthread/BorderRouter/wpan0') iface = dbus.Interface(obj, interface) method = getattr(iface, method_name) res = method(*arguments) return res def get_dbus_property(property_name): return call_dbus_method('org.freedesktop.DBus.Properties', 'Get', 'io.openthread.BorderRouter', property_name) def set_dbus_property(property_name, property_value): return call_dbus_method('org.freedesktop.DBus.Properties', 'Set', 'io.openthread.BorderRouter', property_name, property_value)
DBUS는 보안 기능의 점검을 허용합니다. 다음과 같이 할 수 있습니다.
$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \ --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \ org.freedesktop.DBus.Introspectable.Introspect
여기에 설명된 지원 기능을 확인할 수도 있습니다.
옵션 2: OTBR 에이전트 네이티브 HTTP REST API
OpenThread Border Router가 기본적으로 REST_API=1
플래그로 빌드되어 REST API를 사용 설정합니다. 이전 Codelab의 빌드에서 REST API를 사용 설정하지 않은 경우 해당 플래그를 사용하여 RPi에 OTBR을 빌드해야 합니다.
$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup
다음을 실행하여 OTBR 에이전트를 다시 시작할 수 있습니다.
$ sudo systemctl restart otbr-agent.service
에이전트는 포트 8081에서 HTTP 서버를 시작합니다. 이 서버를 통해 사용자 또는 모니터 프로그램이 OTBR에서 많은 작업을 수행할 수 있습니다 (여기에 설명되어 있음). 브라우저, curl
또는 wget
를 사용하여 콘텐츠를 검사할 수 있습니다. 지원되는 여러 경로 중에는 위에서 설명한 사용 사례가 있으며 /node/dataset/active
에 GET
동사가 있고 /node/dataset/pending
에 PUT
동사가 사용됩니다.
3. Android에서 사용자 인증 정보 프레임워크 설정
선호되는 사용자 인증 정보
Android의 Google Play 서비스는 네트워크 내 모든 TBR의 사용자 인증 정보 등록을 허용하고 예상합니다. 각각은 경계 라우터 에이전트 ID (BAID)로 식별됩니다. ThreadNetworkClient
인터페이스의 addCredentials()
메서드를 사용하여 이 작업을 실행합니다. Google Play 서비스 저장소에 추가되는 첫 번째 TBR에 따라 이 휴대기기의 기본 사용자 인증 정보가 결정됩니다.
BAID에 스레드 네트워크 사용자 인증 정보 집합을 추가하는 앱이 사용자 인증 정보의 소유자가 되며 사용자 인증 정보에 액세스할 수 있는 전체 권한을 가집니다. 다른 앱에서 추가한 사용자 인증 정보에 액세스하려고 하면 PERMISSION_DENIED 오류가 표시됩니다. 하지만 사용자 동의 시 모든 앱에서 기본 사용자 인증 정보를 언제든지 사용할 수 있습니다. 스레드 보더 라우터 네트워크가 업데이트되면 Google Play 서비스에 저장된 사용자 인증 정보를 최신 상태로 유지하는 것이 좋습니다. 이 정보는 현재 사용되지 않지만 향후 더욱 개선된 여정을 제공할 수 있습니다.
첫 번째 TBR이 나중에 제외되더라도, 기본 사용자 인증 정보는 Android 기기에 유지됩니다. 설정되면 스레드 사용자 인증 정보를 관리하는 다른 앱이 getPreferredCredentials()
호출에서 사용자 인증 정보를 가져올 수 있습니다.
Google TBR 동기화
Android 기기는 Google TBR과 자동으로 동기화됩니다. Android에 사용자 인증 정보가 존재하지 않는 경우 기기가 네트워크의 Google TBR에서 사용자 인증 정보를 추출하고 해당 사용자 인증 정보가 기본 사용자 인증 정보가 됩니다. TBR과 Android 기기 간의 동기화는 TBR이 단일 사용자와 페어링되었거나 동일한 스마트 홈 ( 구조)에 있는 두 사용자와 페어링된 경우에만 발생합니다.
사용자가 동일한 구조에 있을 때 다른 Google 사용자가 Android용 GHA 또는 iOS용 GHA를 사용하는 경우에도 이 프로세스가 발생합니다. iOS용 GHA의 경우 기본 사용자 인증 정보가 없는 경우 iOS 저장소에 기본 사용자 인증 정보가 설정됩니다.
두 개의 Android 기기 (또는 Android + iGHA)가 서로 다른 기본 사용자 인증 정보 세트를 사용하여 동일한 네트워크에 존재하는 경우, TBR을 원래 구성한 기기가 TBR에서 우선합니다.
서드 파티 TBR 온보딩
사용자 인증 정보의 저장소는 현재 사용자의 스마트 홈 ( 구조)으로 범위가 지정되지 않습니다. 각 Android 기기에는 BAID 저장소가 있지만, 네트워크에 Google TBR이 있게 되면 iOS용 Google Home 앱을 실행하는 다른 Android 기기 및 iOS 기기가 해당 TBR과 동기화되고 휴대전화 저장소에 로컬 사용자 인증 정보를 설정하려고 시도합니다.
새 OOB TBR이 네트워크를 생성하기 전에, 기본 네트워크가 Android의 저장소에 이미 존재하는지 확인하는 것이 중요합니다.
- 선호하는 네트워크가 있는 경우, 공급업체는 이를 사용해야 합니다. 이렇게 하면 가능한 경우 스레드 기기가 단일 스레드 네트워크에 연결됩니다.
- 기본 네트워크가 없는 경우 새 사용자 인증 정보 세트를 만들어 Google Play 서비스의 TBR에 할당합니다. Android는 이러한 사용자 인증 정보를 모든 Google 기반 TBR에 설정된 표준 사용자 인증 정보로 간주하며, 다른 공급업체는 추가 기기를 통해 메시 도달범위와 견고성을 개선할 수 있습니다.
4. Android 앱 클론 및 수정
Thread API의 가능한 주요 호출을 보여주는 Android 앱을 만들었습니다. 앱에서 이러한 패턴을 사용할 수 있습니다. 이 Codelab에서는 GitHub에서 Matter용 Google Home 샘플 앱을 클론합니다.
여기에 표시된 모든 소스 코드는 이미 샘플 앱에 코딩되어 있습니다. 필요에 맞게 수정하도록 초대를 받았지만 간단히 앱을 클론하거나 사전 빌드된 바이너리를 실행하여 기능을 검사하면 됩니다.
- 다음을 사용하여 클론합니다.
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- Android 스튜디오를 다운로드하여 엽니다.
- 파일 >을 클릭합니다. 클론한 저장소를 열고 가리킵니다.
- Android 휴대전화에서 개발자 모드를 사용 설정합니다.
- USB 케이블을 통해 컴퓨터에 연결합니다.
- <Cmd+R>을 통해 Android 스튜디오에서 앱 실행 (OS X) 또는 <Ctrl+R> (Win, Linux)
- 휠로 이동 -> 개발자 유틸리티 -> 스레드 네트워크
- 사용 가능한 다양한 옵션을 사용합니다. 아래 섹션에서는 모든 버튼에서 실행되는 코드를 압축해제합니다.
기본 사용자 인증 정보가 있나요?
TBR 제조업체가 Google에 물어봐야 할 첫 번째 질문은 선호하는 사용자 인증 정보 세트가 기기에 이미 존재하는지 여부입니다. 이는 흐름의 시작점이어야 합니다. 아래 코드는 사용자 인증 정보가 있는 경우 GPS를 쿼리합니다. 사용자 인증 정보가 공유되지 않으므로 사용자 동의 메시지가 표시되지 않습니다.
/** * Prompts whether credentials exist in storage or not. Consent from user is not necessary */ fun doGPSPreferredCredsExist(activity: FragmentActivity) { try { // Uses the ThreadNetwork interface for the preferred credentials, adding // a listener that will receive an intentSenderResult. If that is NULL, // preferred credentials don't exist. If that isn't NULL, they exist. // In this case we'll not use it. ThreadNetwork.getClient(activity).preferredCredentials.addOnSuccessListener { intentSenderResult -> intentSenderResult.intentSender?.let { intentSender -> ToastTimber.d("threadClient: preferred credentials exist", activity) // don't post the intent on `threadClientIntentSender` as we do when // we really want to know which are the credentials. That will prompt a // user consent. In this case we just want to know whether they exist } ?: ToastTimber.d( "threadClient: no preferred credentials found, or no thread module found", activity ) }.addOnFailureListener { e: Exception -> Timber.d("ERROR: [${e}]") } } catch (e: Exception) { ToastTimber.e("Error $e", activity) } }
GPS 기본 사용자 인증 정보 가져오기
사용자 인증 정보가 있는 경우 해당 사용자 인증 정보를 읽는 것이 좋습니다. 이전 코드와의 유일한 차이점은 intentSenderResult
를 수신한 후에 발신자의 결과를 사용하여 인텐트를 빌드하고 실행하려고 한다는 것입니다.
이 코드에서는 조직/아키텍처 목적으로 MutableLiveData<IntentSender?>
를 사용합니다. 원본 코드는 ViewModel (ThreadViewModel.kt)에 있고 인텐트 관찰자가 활동 프래그먼트 ( ThreadFragment.kt)에 있기 때문입니다. 따라서 인텐트 발신자결과가 라이브 데이터에 게시되면 우리는 이 관찰자의 콘텐츠를 실행할 것입니다.
viewModel.threadClientIntentSender.observe(viewLifecycleOwner) { sender -> Timber.d( "threadClient: intent observe is called with [${intentSenderToString(sender)}]" ) if (sender != null) { Timber.d("threadClient: Launch GPS activity to get ThreadClient") threadClientLauncher.launch(IntentSenderRequest.Builder(sender).build()) viewModel.consumeThreadClientIntentSender() } }
사용자 인증 정보를 공유하여 사용자 동의를 트리거하며, 승인되면 다음을 통해 콘텐츠가 반환됩니다.
threadClientLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> if (result.resultCode == RESULT_OK) { val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!) viewModel.threadPreferredCredentialsOperationalDataset.postValue( threadNetworkCredentials ) } else { val error = "User denied request." Timber.d(error) updateThreadInfo(null, "") } }
MutableLiveData<ThreadNetworkCredentials?>
에 사용자 인증 정보를 게시하는 방법은 다음과 같습니다.
GPS 사용자 인증 정보 설정
TBR의 존재 여부와 관계없이 TBR을 Google Play 서비스에 등록해야 합니다. 개발자님의 앱에서만 TBR의 국경 대리인 ID와 관련된 사용자 인증 정보를 읽을 수 있지만, TBR이 가장 먼저 등록되는 경우 해당 사용자 인증 정보가 기본 사용자 인증 정보 세트에 복사됩니다. 이 정보는 사용자가 승인하는 한 휴대전화의 모든 앱에서 액세스할 수 있습니다.
/** * Last step in setting the GPS thread credentials of a TBR */ private fun associateGPSThreadCredentialsToThreadBorderRouterAgent( credentials: ThreadNetworkCredentials?, activity: FragmentActivity, threadBorderAgent: ThreadBorderAgent, ) { credentials?.let { ThreadNetwork.getClient(activity).addCredentials(threadBorderAgent, credentials) .addOnSuccessListener { ToastTimber.d("threadClient: Credentials added", activity) }.addOnFailureListener { e: Exception -> ToastTimber.e("threadClient: Error adding the new credentials: $e", activity) } } }
TBR 제품에 대한 사용자 인증 정보 설정
이 부분은 각 공급업체의 독점 항목이며, 이 Codelab에서는 DBUS+Python HTTP REST 서버 또는 OTBR의 네이티브 HTTP REST 서버를 통해 이를 구현합니다.
/** * Creates credentials in the format used by the OTBR HTTP server. See its documentation in * https://github.com/openthread/ot-br-posix/blob/main/src/rest/openapi.yaml#L215 */ fun createJsonCredentialsObject(newCredentials: ThreadNetworkCredentials): JSONObject { val jsonTimestamp = JSONObject() jsonTimestamp.put("Seconds", System.currentTimeMillis() / 1000) jsonTimestamp.put("Ticks", 0) jsonTimestamp.put("Authoritative", false) val jsonQuery = JSONObject() jsonQuery.put( "ActiveDataset", BaseEncoding.base16().encode(newCredentials.activeOperationalDataset) ) jsonQuery.put("PendingTimestamp", jsonTimestamp) // delay of committing the pending set into active set: 10000ms jsonQuery.put("Delay", 10000) Timber.d(jsonQuery.toString()) return jsonQuery } //(...) var response = OtbrHttpClient.createJsonHttpRequest( URL("http://$ipAddress:$otbrPort$otbrDatasetPendingEndpoint"), activity, OtbrHttpClient.Verbs.PUT, jsonQuery.toString() )
TBR 제품에서 사용자 인증 정보 가져오기
앞에서 살펴본 것처럼 GET HTTP 동사를 사용하여 TBR에서 인증 정보를 가져옵니다. 샘플 Python 스크립트를 참조하세요.
빌드 및 가져오기
Android 앱을 만들 때 Google Play 서비스 스레드 모듈을 지원하도록 매니페스트, 빌드, 가져오기를 변경해야 합니다. 다음 3개의 스니펫은 추가된 내용을 대부분 요약합니다.
샘플 앱은 주로 Matter 커미셔닝용으로 빌드되었습니다. 따라서 매니페스트와 Gradle 파일은 스레드 사용자 인증 정보를 사용하는 데 필요한 추가 파일보다 더 복잡합니다.
매니페스트 변경사항
<manifest xmlns:android="http://schemas.android.com/apk/res/android" (...) <!-- usesCleartextTraffic needed for OTBR local unencrypted communication --> <!-- Not needed for Thread Module, only used for HTTP --> <uses-feature (...) android:usesCleartextTraffic="true"> <application> (...) <!-- GPS automatically downloads scanner module when app is installed --> <!-- Not needed for Thread Module, only used for scanning QR Codes --> <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="barcode_ui"/> </application> </manifest>
Build.gradle
// Thread Network implementation 'com.google.android.gms:play-services-threadnetwork:16.0.0' // Thread QR Code Scanning implementation 'com.google.android.gms:play-services-code-scanner:16.0.0' // Thread QR Code Generation implementation 'com.journeyapps:zxing-android-embedded:4.1.0' // Needed for using BaseEncoding class implementation 'com.google.guava:guava:31.1-jre'
관련 가져오기
// Thread Network Module import com.google.android.gms.threadnetwork.ThreadNetworkCredentials import com.google.android.gms.threadnetwork.ThreadBorderAgent import com.google.android.gms.threadnetwork.ThreadNetwork // Conversion of credentials to/fro Base16 (hex) import com.google.common.io.BaseEncoding // HTTP import java.io.BufferedInputStream import java.io.InputStream import java.net.HttpURLConnection import java.net.URL import java.nio.charset.StandardCharsets // Co-routines for HTTP calls import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch // JSON import org.json.JSONObject // Logs import timber.log.Timber // mDNS/SD import android.net.nsd.NsdServiceInfo // QR Code reader / writer import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions import com.google.mlkit.vision.codescanner.GmsBarcodeScanning import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter import com.journeyapps.barcodescanner.BarcodeEncoder
5. mDNS/SD 검색
샘플 앱은 mDNS/SD 검색을 사용하여 네트워크에서 사용 가능한 스레드 보더 라우터의 목록과 각각의 BAID를 생성합니다.
이는 TBR의 정보를 GPS 자격 증명 저장소에 입력할 때 매우 유용합니다. 그러나 이 Codelab에서는 이와 관련된 사용법을 다루지 않습니다. 저희는 Android 서비스 검색 라이브러리 NSDManager를 사용하며, 전체 소스 코드는 ServiceDiscovery.kt
의 샘플 앱에서 확인할 수 있습니다.
6. 통합 정리
이러한 호출을 구현하거나 샘플 앱을 사용하고 나면 RPi OTBR을 완전히 온보딩할 수 있습니다. 샘플 앱에서는 버튼 8개를 노출합니다.
TBR 온보딩을 위해 가능한 순서는 다음과 같습니다.
- 우선 사용자 인증 정보가 있는지 쿼리 (파란색, 첫 번째 행)
- 답변에 따라
- GPS 기본 사용자 인증 정보 가져오기 (파란색, 두 번째 행)
- GPS에 TBR 사용자 인증 정보 설정 (파란색, 세 번째 행) -> TBR 선택 -> 랜덤 만들기 -> 네트워크 이름 입력 -> 확인
- 이제 선호하는 사용자 인증 정보가 있으므로 RPi OTBR 사용자 인증 정보 설정을 사용하여 해당 사용자 인증 정보를 OTBR로 설정합니다. 그러면 해당 사용자 인증 정보가 대기 중인 세트에 적용됩니다.
샘플 앱의 기본값은 10초 지연을 사용하는 것입니다. 따라서 이 기간이 지나면 RPi TBR (및 네트워크에 존재할 수 있는 기타 노드)의 사용자 인증 정보가 새 데이터 세트로 이전됩니다.
7. 결론
이 Codelab에서는 샘플 Android 앱을 클론하고 Google Play 서비스의 Google Play 서비스 SDK를 활용하는 Thread Storage API입니다. 이러한 API를 사용하여 공급업체의 TBR을 보여주는 RPi TBR에 온보딩할 수 있는 공통 데이터 세트를 만들었습니다.
사용자의 모든 TBR이 동일한 네트워크에 있으면 스레드 네트워크의 복원력과 도달범위가 향상됩니다. 또한 앱이 사용자 인증 정보에 액세스할 수 없어서 스레드 기기를 온보딩할 수 없는 결함이 있는 사용자 여정을 방지합니다.
이 Codelab과 샘플 앱이 자체 앱과 스레드 보더 라우터 제품을 설계하고 개발하는 데 도움이 되기를 바랍니다.
8. 참조
RCP 보조 프로세서
DBUS