크라우니텔레 ⑥통화 — 방기반 RTC 시그널링 + coturn turncred
개요
마스터플랜(
/Users/ef/crowny-tele/docs/프로덕션화-마스터플랜.md §⑥) 스펙대로 텔레서버.한선(단독, 순수 한선씨)에
방(room) 기반 WebRTC 시그널링 3라우트와 coturn REST-auth 시간제한 credential 발급 라우트를 추가했다.
기존 lid 기반(
/tmp/tele_rtc_*) 레거시 경로는 전혀 건드리지 않고 그대로 유지(호환).
무엇을 했는지
1. 방기반 RTC 시그널링 (offer/answer/candidate/end)
POST /api/rtc/call {token, room, type, to?, sdp|cand} — _처리RTC콜()
세션·방존재·멤버십 검증(기존 _처리메시지전송과 동일 패턴 재사용: _세션조회/_방찾기/_멤버인가).
to 생략 시 dm 방은 _DM상대()로 상대를 자동 추론(2인 통화 MVP; 그룹콜은 to 명시 필요).
payload는 type=="candidate"면 cand 필드, 그 외(offer/answer)는 sdp 필드에서 _JSON값빠름()으로 추출
(가드레일: 대형 JSON 필드는
_JSON값빠름 — SDP가 커질 수 있음). base64 형식 검증은 기존
_b64유효() 재사용.
end 타입은 payload 없이 허용(행업 신호).
저장: data/rtc_<room>_<to>.psv (수신자별 인박스, 마스터플랜 문구 그대로) — 방로그_<room>.psv(채팅 히스토리)는
건드리지 않는다. ICE candidate가 통화당 여러 번 append되는데 그걸 채팅 히스토리에 섞으면 회전(50000B)·목록 조회를
오염시키기 때문. seq는 그 인박스 파일 자체의 줄 수 기준 로컬 카운터(전역 방 seq와 무관 — 통화 신호는 poll 후
즉시 소비되는 휘발성 큐라 방 채팅 seq 커서와 얽을 필요가 없음).
저장 직후 _notify기록(room, "call", seq) 직접 호출 → 실시간프로토콜.한선이 이미 문서화해둔 SSE
event: call 스키마(§②)가 그대로 흡수(notify.log 포맷 불변이라 사이드카 수정 불필요).
GET /api/rtc/poll {token, room} — _처리RTC폴()
인증·멤버십 확인 후 data/rtc_<room>_<me>.psv(자기 인박스)를 읽어 JSON 배열로 반환하고, 반환 즉시
파일을 비운다(
쓰기(큐파일, "")) — 단일스레드 accept-루프라 read-then-clear 사이 경합 없음, 1회성 pull 계약.
응답: {"ok":true,"items":[{"seq":,"ts":,"from":,"type":,"data":<base64>}]}2. coturn REST-auth turncred
GET /api/rtc/turncred?token= — _처리RTC턴크레드()
username = "<만료epoch>:<userId>"(ttl=3600), credential = base64(HMAC-SHA256(secret, username)).
VM 내장 SIGN(opcode 509)은 진짜 HMAC이 아님(해시+정수 뒤섞기 "단순화" — crownyc.c 실측 확인)이라
coturn이 openssl로 검증하는 byte-perfect HMAC-SHA256을 낼 수 없다 → 가드레일 원칙(byte표준 암호는
체계()+openssl 외부위임)대로
_turn_hmac_b64()가
printf '%s' 'msg' | openssl dgst -sha256 -hmac 'secret' -binary | openssl base64 -A를
체계()로 실행해 계산. 격리 테스트에서 동일 공식을 셸에서 독립 재계산해 서버 응답과
바이트 단위 일치 확인(아래 검증 참조).
시크릿: data/turn_secret.txt — 없으면 부팅 시 무작위(0,16) 니블 64회로 64자리 hex 생성 후 저장,
있으면 그대로 로드(전역 캐시, 요청마다 파일읽기 안 함).
응답: {"ok":true,"username":"...","credential":"...","ttl":3600,"urls":["turn:tele.crowny.org:3479"]}3. coturn conf 변경 제안 (오케스트레이터 적용 필요 — 이 임무는 turnserver.conf를 건드리지 않음)
현재
/Users/ef/crowny-tele/turn/turnserver.conf는
lt-cred-mech + 고정
user=crowny:crownyturn2026(장기 자격증명).
turncred 발급이 실제로 통하려면 use-auth-secret 방식으로 전환 필요:
# 제거
lt-cred-mech
user=crowny:crownyturn2026
# 추가
use-auth-secret
static-auth-secret=<data/turn_secret.txt 파일 내용 그대로>
- realm은 현재 conf가
tele.crowny.org로 이미 설정돼 있고 서버 코드도 이 값을 urls에 그대로 씀(마스터플랜 초안 문구는
realm=crowny.org였으나 기존 conf와의 정합을 위해
tele.crowny.org 유지 — 와일드카드 인증서라 어느 쪽이든 TLS는 문제없음).
secret이 바뀌면(재생성) conf의 static-auth-secret도 같이 갱신해야 하므로, 배포 스크립트가 매 배포 시
data/turn_secret.txt를 읽어 conf에 주입하는 걸 권장.
- 참고(범위 밖, 관찰만):
listening-ip=127.0.0.1/relay-ip=127.0.0.1는 루프백 전용이라 실제 NAT 뒤 2대 릴레이 테스트(AC의
"서로 다른 네트워크 2대")에는
external-ip/실 공인 relay-ip 설정이 추가로 필요 — 이건 ops 프로비저닝 영역.
검증
STRICT 컴파일: CROWNY_STRICT=1 hanseonc_high 텔레서버.한선 → EXIT=0, 경고 0.
격리 실행: TELE_ROOT=/tmp/rtc-test TELE_PORT=9629(data 사본)로 기동.
기존 회귀 하네스(docs/운영하네스/시나리오.sh) 24/24 PASS(수정 전후 동일 — 회귀 없음).
/api/rtc/call(offer→answer→candidate×2→end) → 각 타입별 seq 정상 증가, notify.log에
<epoch>|<room>|call|<seq> 줄 확인.
/api/rtc/poll로 상대 인박스 조회 성공 후 재조회 시 빈 배열(소비 확인).
에러 경로: 잘못된 type→400 type, 존재 안 하는 room→404 room, sdp 누락→400 sdp, 잘못된 token→401 auth — 전부 확인.
레거시 lid 기반 /api/rtc/join·/api/rtc/listeners 회귀 없음(그대로 동작).
turncred: 응답 username/credential을 외부에서 동일 시크릿으로 독립 재계산한 HMAC-SHA256 base64와
바이트 단위 일치(HMAC MATCH 확인) — coturn이 검증할 수 있는 진짜 규격임을 실측.
종료: lsof -ti:9629 | xargs kill 확인.
크라우니코드: lookup MISS 2건(rtc방기반콜인박스, coturn_turncred_hmac발급) → 생성 후 learn.sh add 2건 학습 완료.관련 파일
/Users/ef/crowny-tele/텔레서버.한선 — _RTC타입유효/_처리RTC콜/_처리RTC폴/_TURN시크릿초기화/_turn_hmac_b64/_처리RTC턴크레드 추가,
요청처리 디스패치에
/api/rtc/call·
/api/rtc/poll·
/api/rtc/turncred 3라우트 배선, 부팅부에
_TURN_SECRET 로드 1줄 추가.
/Users/ef/crowny-tele/실시간프로토콜.한선 — 참조만(수정 없음), notify.log/SSE event:call 스키마 SSOT.
/Users/ef/crowny-tele/turn/turnserver.conf — 읽기만(수정 없음), 위 제안 반영은 오케스트레이터 몫.
/Users/ef/crowny-tele/docs/운영하네스/시나리오.sh — 격리 회귀 24/24 확인용(수정 없음).
잔여 이슈 (오케스트레이터/후속 임무)
- coturn conf 전환 + 재기동 — 위 use-auth-secret/static-auth-secret 반영, 실 서비스 재시작.
- 텔레서버.한선 재컴파일 + 라이브 배포 —
hanseonc_high 텔레서버.한선 > 텔레서버.toau 후 org.crowny.tele LaunchAgent 재기동(이 임무는 라이브 .toau/plist 무접촉).
- NAT 실측 — 서로 다른 네트워크 2대 실기기로 음성통화 연결 검증(현재 conf가 loopback relay라 이 임무 범위의 로컬 검증으로는 불가, external-ip 설정은 ops 영역).
- 클라이언트(voice-link.js + RTCPeerConnection) — 이 임무는 crownyc 서버만 대상(mobile-app.html 등 병렬 클라 금지 지시), 클라 배선은 별도 임무.
- 통화기록 kind:sys 메시지(
✓ 통화 3:12) — 마스터플랜 문구상 통화 "종료 후" 요약 1줄은 클라(또는 후속 서버 임무)가 /api/msg/send로 kind=sys 남기는 방식 제안(방로그 오염 없이 요약만 1줄 — 이번 임무에서는 미구현, 스코프 밖으로 판단해 제외).