케이블 하나로 맥이 안드로이드 폰의 인터넷을 쓰게 만드는 절차·스크립트·실측 근거. Redmi 14C(HyperOS / Android 15)에서 2026-08-18 완주했다.
핵심은 두 줄이다. macOS 에는 RNDIS 드라이버가 없으니 USB 가젯을 CDC-NCM 으로 바꿔야 하고,
그 전환은 settings put global tether_force_usb_functions 1 + 재부팅 1회로 해야 한다.
이 절차를 안내와 상태 점검으로 감싼 메뉴바 앱이 있다 — adb 를 번들해서 Android SDK 설치가 필요 없고, 6단계 중 어디서 막혔는지와 무엇을 해야 하는지를 보여준다. 소스는 비공개 레포이고 배포본은 릴리스에서 받는다.
./install.sh # 스킬 + adb 설치
# 폰 연결 → USB 디버깅 허용 → 폰 잠금해제(UI 탭이 필요하다)
~/.claude/skills/android-usb-tethering/scripts/ncm_tether.sh # 세팅 + 검증설정이 아직 반영되지 않았으면 스크립트가 exit 2 로 멈추고 재부팅을 요구한다.
AUTO_REBOOT=1 을 붙여 실행하면 알아서 재부팅한다. 재부팅은 최초 1회뿐이다.
ncm_tether.sh status # 현재 상태만
ncm_tether.sh off # 테더링 끄고 USB 기능 원복
ncm_tether.sh --serial <SN> # 기기가 여러 대일 때
USB_LABEL="USB tethering" ncm_tether.sh # 영문 UI 기기설치 후에는 Claude Code 에서 "안드로이드 USB 테더링 해줘" 라고만 해도 스킬이 잡힌다
(~/.claude/skills/ 전역 스킬이라 프로젝트와 무관하다).
안드로이드 USB 테더링의 기본 가젯은 RNDIS 다. macOS 에는 그 드라이버가 없고,
폰 쪽 rndis0 가 UP 이어도 맥 networksetup -listallhardwareports 에는 인터페이스가 생기지도 않는다.
HoRNDIS·EasyTether 는 kext 라 SIP 완화가 필요하다. CDC-NCM 은 macOS 네이티브 지원이라 설치할 드라이버가 없다.
svc usb setFunctions ncm |
settings put global tether_force_usb_functions 1 |
|
|---|---|---|
| 지속성 | 비영구 — HyperOS 가 수 초 내 adb 로 되돌린다 |
영구 |
| 필요한 곡예 | 폴링·재적용, 실패 시 상태 오염 | 없음 |
| 대가 | — | 최초 1회 재부팅 |
tether_force_usb_functions 는 부팅 시 한 번만 읽힌다. 설정 직후에는 반영되지 않는다.
usb0 가 NCM 버킷으로 잘못 분류돼, 상태머신은 TetheredState 로 가고
dumpsys 는 LinkAddresses: [10.x.x.x/24] 를 보고하는데 커널에는 주소가 없다.
맥은 IP 를 들고 있지만(=stale lease) 통신은 되지 않는다. 겉보기 초록의 대표적인 형태다.
맥에 IP 가 붙은 것으로 판정하면 안 된다. 죽은 lease 가 그대로 남기 때문이다. 세 가지를 본다.
# 1. 설정 반영 — [rndis\d] 가 아니라 [usb\d] 여야 한다
adb shell dumpsys tethering | grep tetherableUsbRegexs
# 2. 테더링 상태머신 — 이것이 "토글이 켜졌나" 의 참값이다
adb shell dumpsys tethering | grep -E '^ *usb[0-9] - ' # → usb0 - TetheredState
# 3. 실제 USB 가젯 — 재부팅 전에는 설정이 1 이어도 여기가 rndis 다
adb shell getprop sys.usb.state # → ncm,adb
# 4. 외부 통신 (ping 아님 — ICMP 가 차단돼 항상 100% 손실로 나온다)
curl -sS -o /dev/null -w '%{http_code}\n' --interface <enN> http://captive.apple.com/hotspot-detect.htmladb shell ip -4 addr show usb0 로는 판정할 수 없다. root 아닌 adb shell 은 인터페이스
주소를 다 읽지 못한다. 실측에서 테더링이 멀쩡히 붙어 있는데도(usb0 - TetheredState,
맥 10.171.42.230, http 200) ip -4 addr 는 lo 와 v4-ccmni1 만 보여줬다 — wlan0 조차
안 나온다. 이 명령의 빈 출력은 "주소가 없다"가 아니라 "볼 권한이 없다"이다. 위 2번을 쓴다.
맥 인터페이스를 이름으로 찾으면 속는다. macOS 는 한 번 꽂혔던 기기의 네트워크 서비스를
이름째 남겨둔다. networksetup -listnetworkserviceorder 에 Redmi 14C 가 en21 로 잡히는데
ifconfig en21 은 죽어 있는 상태가 실제로 나왔다. 이름 · 활성(status: active) ·
링크로컬(169.254)이 아닌 주소, 셋을 모두 만족해야 인정한다. 아이폰·아이패드 테더링이
같이 붙어 있으면 하드웨어 포트 이름으로 걸러낸다.
붙고 나서 겪는 함정이다. 국내 통신사 회선이 IPv6 전용 + NAT64 로 오면 도메인은 되는데
IPv4 주소를 직접 쓰는 것은 전부 실패한다. store.example.com 은 200 인데
ssh 140.245.71.178 은 No route to host 로 죽고, dig @1.1.1.1 도 타임아웃이다.
판별과 우회:
curl -s -o /dev/null -w '%{remote_ip}\n' https://example.com # 64:ff9b:: 로 시작하면 NAT6464:ff9b::/96 은 NAT64 표준 프리픽스로, 뒤 32비트가 원래 IPv4 다
(64:ff9b::8cf5:47b2 = 0x8c.0xf5.0x47.0xb2 = 140.245.71.178).
SSH config 에 IP 리터럴을 박아두면 이 회선에서 못 붙는다 — DNS64 가 변환할 이름이 없기
때문이다. HostName 을 도메인으로 바꾸거나, 급하면 위 방식으로 계산한 IPv6 주소로 직접 붙는다.
| 증상 | 원인 | 확인 |
|---|---|---|
| 토글을 눌러도 꺼진 상태로 되돌아온다 | 누른 것이 "이더넷을 통해 공유" 다 | 720x1640 기준 USB 테더링 (624,830) / 이더넷 공유 (624,1138) |
| 맥에 IP 는 있는데 통신이 안 된다 | stale lease | ipconfig getpacket en15 의 server_identifier 가 현재 폰 주소와 다르면 죽은 lease. arp -an -i en15 가 (incomplete) |
dumpsys 는 주소를 보고하는데 안 된다 |
재부팅 전 상태 | ip -4 addr show usb0 로 대조 |
| 맥에 인터페이스가 아예 안 뜬다 | RNDIS 로 붙어 있다 | tetherableUsbRegexs 가 [rndis\d] |
| 여러 번 갈아끼운 뒤 계속 꼬인다 | USB 가젯 상태 오염 | svc usb resetUsbGadget 으로는 복구되지 않는다 — 재부팅이 유일 |
cmd tethering 이 없다 |
미구현 | cmd connectivity·cmd ethernet 에도 없다. UI 탭이 유일한 경로다 |
그 밖에 알아둘 것:
- 테더링이 켜진 채로 NCM 전환하면 꼬인다. 반드시 끄고 전환한다.
adb devices와 테더링은 별개 층이다. adb 는 USB 디버깅 인터페이스, 테더링은 네트워크 클래스다.- 폰에서
ip route가 비어 보이는 건 정상이다. 안드로이드는 정책 라우팅 테이블을 쓴다. - 좌표는 하드코딩하지 않는다. 스크립트가
uiautomator dump로 라벨 옆 스위치를 찾는다. 행 전체를 덮는 Switch 노드(폭 > 300px)를 걸러야 실제 위젯이 잡힌다.
세 경로는 실측으로 막힌 것이 확인됐다. 다시 시도할 이유가 없다.
- RNDIS 로 macOS 붙이기 — 맥에 인터페이스가 생기지 않는다.
- "이더넷을 통해 공유" 토글 — NCM 으로
usb0가 이더넷 포트로 등록돼도 (dumpsys ethernet에EthernetPort(usb0...)로 보인다)startTethering(TETHERING_ETHERNET)이Tethered interface requests: 0에서 무시된다. - root 경유 수동 IP 부여 — production build 에서
adb root가 거부된다(adbd cannot run as root).
| 항목 | 값 |
|---|---|
폰 usb0 |
10.55.97.19/24 (커널 실주소) |
| 맥 포트 | Redmi 14C = en15 = 10.55.97.115 |
| 기본 라우트 | 10.55.97.19 / en15 |
| RTT | 게이트웨이 1.23ms · 8.8.8.8 72ms |
generate_204 |
204 / 0.26s |
| USB 상태 | sys.usb.config = ncm,adb · mUsbTetheringFunction = NCM |
| 폰 | HyperOS / Android 15 / SDK 35 / 720x1640 |
| 맥 | macOS 26.5.2 / arm64 · adb 1.0.41 |
아이폰 USB 테더링(en11)이 함께 붙어 있었고, 아이폰을 뽑자 기본 라우트가 en15 로 자동으로 넘어갔다.
수동 조작은 필요 없었다.
| 경로 | 내용 |
|---|---|
HANDOFF.txt |
원인 규명·실행 순서·함정 10개·폐기 경로 전체 |
install.sh |
새 맥에 스킬 설치 + adb 확인/설치 |
skill/SKILL.md |
Claude Code 전역 스킬 본문 |
skill/scripts/ncm_tether.sh |
실행 스크립트 — up(기본)/status/off/--serial |
EVIDENCE-2026-08-18.txt |
성공 시점 원본 실측 덤프 |
reference/00-* |
선행 세션 핸드오프(미해결 상태). 계보 보존용 |
reference/01-*, 02-* |
메모리 노트와 인덱스 한 줄 |
스크립트의 stale lease 감지 → sudo ipconfig DHCP 갱신 분기는 실행된 적이 없다.
맥이 매번 올바른 대역을 자동으로 받았기 때문이다. 이 분기만 실전 검증이 남았다.