TRENDSHIFT 딥다이브 · 일간 #24 · 2026-06-18

iroh 딥다이브
— 공개키로 전화 걸듯 P2P를 잇는 Rust 네트워킹 스택

n0-computer/iroh"상대의 공개키만 알면, 그 기기가 인터넷 어디에 있든 가장 빠른 직접 연결을 알아서 찾아 유지해 주는" P2P 라이브러리다. IP 주소가 아니라 신원(공개키)으로 연결을 건다. NAT·방화벽에 막히면 릴레이 서버를 거쳐 일단 잇고, 곧바로 홀펀칭으로 직접 연결을 뚫은 뒤 릴레이에서 빠진다. 표어는 "less net work for networks(네트워크 작업을 덜어 주는 네트워크)". (저장소: n0-computer/iroh · 언어 Rust(edition 2024 · MSRV 1.91) · 버전 v1.0.0 · 라이선스 MIT OR Apache-2.0 · 제작 number 0(n0) · 자체 QUIC 엔진 noq · WASM/브라우저 지원 · 포스트양자 키교환)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어/시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

한 문장으로 이 프로젝트가 뭘 하는지부터.

iroh = "공개키로 다이얼하는 P2P QUIC 연결 라이브러리." 보통 네트워크 프로그램은 상대의 IP 주소:포트를 알아야 연결한다. 그런데 휴대폰·노트북은 와이파이를 옮기고 NAT 뒤에 숨고 IP가 수시로 바뀐다 — 주소는 끊임없이 변한다. iroh는 발상을 뒤집는다. 변하지 않는 것, 즉 그 기기의 공개키(=신원)로 연결을 건다. "저 폰에 연결해 줘"라고 공개키만 건네면, iroh가 알아서 상대의 현재 주소를 찾고, NAT를 뚫고, 가장 빠른 경로로 암호화된 QUIC 연결을 만들어 유지한다.

📞 한 줄 비유

"전화번호로 거는 전화" vs "이름으로 거는 전화"

전화번호(IP 주소)는 통신사·지역·기기를 바꾸면 달라진다. 번호로만 연결하는 세상에선, 상대가 이사할 때마다 새 번호를 알아내야 한다. NAT·방화벽은 "이 번호는 외부에서 못 건다"는 사설 교환대까지 끼워 일을 더 어렵게 만든다.

iroh는 번호가 아니라 "이름(공개키)"으로 거는 전화다. 이름은 절대 안 바뀐다. 상대가 어디로 이사하든, "그 이름에게 연결해 줘"라고만 하면 iroh의 안내 시스템(릴레이 + 디스커버리)이 지금 어디 있는지 찾아 연결해 준다. 게다가 이름 자체가 암호키라서, 연결이 곧 상대 본인임을 증명한다 — 잘못 걸 수가 없다.

용어
P2P (Peer-to-Peer · 피어 투 피어)
중앙 서버를 거치지 않고 두 기기가 직접 데이터를 주고받는 방식. 카톡 같은 중앙형은 모든 메시지가 회사 서버를 지나지만, P2P는 내 폰 ↔ 네 폰을 곧장 잇는다. 빠르고 사생활 보호에 유리하지만, "상대를 어떻게 찾고, NAT를 어떻게 뚫느냐"가 늘 난제였다 — iroh가 바로 그 난제를 풀어 주는 도구다.
용어
QUIC (퀵)
구글이 만들고 HTTP/3의 바탕이 된 차세대 전송 프로토콜. UDP 위에서 동작하면서 TCP의 신뢰성 + TLS 암호화 + 다중 스트림을 한데 묶었다. 연결이 빠르게 맺어지고(0-RTT), 한 연결 안에 여러 스트림을 깔아도 서로 막히지 않는다(head-of-line blocking 없음). iroh의 모든 연결은 QUIC이며, 암호화·신원확인이 프로토콜에 내장돼 있다.
용어
홀펀칭 (Hole Punching · 구멍 뚫기) · NAT
NAT는 공유기가 여러 기기를 하나의 공인 IP로 묶는 장치 — 덕분에 외부에서 내 기기로 직접 들어오기가 막힌다. 홀펀칭은 양쪽이 동시에 서로에게 패킷을 쏘아 NAT에 "임시 구멍"을 내고, 그 구멍으로 직접 연결을 트는 기술이다. iroh는 릴레이의 도움을 받아 양쪽 타이밍을 맞춰 이 구멍을 뚫는다.

정리하면 iroh의 핵심 약속은 세 가지다. ① 주소가 아니라 공개키로 연결한다(EndpointId). ② 처음엔 릴레이로 무조건 잇고, 가능해지면 홀펀칭으로 직접 연결로 갈아탄 뒤 릴레이를 뺀다. ③ 모든 연결은 QUIC + TLS로 암호화되며, 공개키가 곧 신원이라 중간자 공격이 원천 차단된다. 이 위에 파일 전송(iroh-blobs), 펍섭(iroh-gossip), 동기화 DB(iroh-docs) 같은 프로토콜을 얹어 "로컬 퍼스트(local-first)" 앱을 만든다.

2왜 주목받는가

TrendShift 일간 24위 — "공개키로 다이얼"이라는 단순한 약속과, 1.0 도달이 핵심.

분산·로컬 퍼스트 앱이 늘면서 "중앙 서버 없이 두 기기를 직접 잇는 일"의 수요가 커졌다. 하지만 NAT 통과·디스커버리·암호화·릴레이를 직접 구현하는 건 악명 높게 어렵다. iroh는 그 모든 걸 한 줄짜리 API 뒤로 숨겨 준다. 최근 주목도가 오른 이유를 다섯 가지로 본다:

① "주소가 아니라 공개키로 연결" — 발상이 단순하고 강력하다

iroh의 모든 매력은 한 문장에서 나온다. connect(EndpointId, ALPN) — 상대의 32바이트 공개키와 프로토콜 이름만 주면 끝이다. IP·포트·NAT·릴레이는 라이브러리가 알아서 처리한다. "어디에 있는가"가 아니라 "누구인가"로 연결을 거는 이 모델은, 기기가 이동해도 연결 식별자가 안 변한다는 결정적 장점을 준다.

② 자체 QUIC 엔진 noq로 갈아탔다

iroh는 오랫동안 Rust의 대표 QUIC 구현인 quinn 위에서 돌았는데, 1.0에 오며 자체 QUIC 엔진 noq(n0가 직접 개발)로 교체했다. 멀티패스(여러 경로 동시 사용)·커스텀 전송·홀펀칭 같은 iroh 특유의 요구를 QUIC 계층에서 직접 제어하려는 결정이다. 인증 암호화, 스트림 우선순위, 데이터그램 전송을 기본 제공한다.

③ Tailscale 'DERP' 계보의 릴레이 + 홀펀칭

iroh의 릴레이 프로토콜과 NAT 탐지는 Tailscale의 검증된 설계(DERP 릴레이, netcheck)에서 갈라져 나왔다(코드 주석에 출처 명시). 거기에 STUN 대신 QAD(QUIC Address Discovery)라는 최신 방식으로 자기 주소를 알아낸다. "그 회사가 수년간 프로덕션에서 검증한 NAT 통과 노하우"를 오픈소스 라이브러리로 쓸 수 있다는 점이 크다.

④ 1.0 도달 + 브라우저(WASM) + 포스트양자

이번에 v1.0.0에 도달하며 공개 API가 안정화됐다(외부 노출 타입을 cargo_check_external_types로 엄격히 관리). 게다가 wasm32 타깃을 1급으로 지원해 브라우저 안에서도 iroh 연결이 가능하고, 포스트양자 키교환 예제(pq-only-key-exchange)까지 들어 있다. "지금 쓸 수 있고, 미래도 대비했다"는 신호.

⑤ 라이브러리 + 생태계 + 무료 공용 인프라

iroh는 연결만 주고 끝이 아니다. 그 위에 얹는 iroh-blobs(BLAKE3 콘텐츠 주소 파일전송), iroh-gossip(펍섭 오버레이), iroh-docs(동기화 KV 저장소)가 함께 큰다. 게다가 n0가 전 세계 4곳에 공용 릴레이(북미 동/서·EU·아태)와 디스커버리 서버(dns.iroh.link)를 무료로 운영해, 별도 인프라 없이 바로 시작할 수 있다.

접근 방식두 기기를 어떻게 잇나iroh와의 차이
직접 소켓(TCP/UDP)상대 IP:포트를 알아야 직접 연결NAT 뒤 기기엔 직접 못 닿음. iroh는 릴레이+홀펀칭으로 NAT를 뚫는다.
중앙 서버 경유모든 트래픽이 회사 서버를 통과느리고 비싸고 사생활 노출. iroh는 직접 연결로 갈아타고 릴레이는 암호문만 중계.
WebRTC브라우저 P2P(STUN/TURN/ICE)브라우저 중심·시그널링이 복잡. iroh는 QUIC 기반·공개키 신원에 네이티브+WASM 모두.
iroh공개키로 다이얼 → 릴레이 → 홀펀칭 직접연결주소가 아닌 신원으로 연결, 암호화 내장, 한 줄 API, 무료 공용 릴레이.
강점
"어려운 P2P"를 한 줄 API로 — 그리고 직접 연결로 빠르다

NAT 통과·디스커버리·암호화·릴레이라는 네 가지 난제를 Endpoint::bind + connect 뒤로 숨겼다. 처음엔 릴레이로 안정적으로 잇고, 가능해지는 즉시 직접 연결로 승격해 릴레이를 빼므로 빠르면서도 끊김이 없다. 신원이 곧 암호키라 보안도 공짜로 따라온다.

냉정하게 보기
Rust 전제 · 1.0 직후의 대규모 개명 · 릴레이 신뢰 가정

iroh는 Rust 라이브러리다(다른 언어는 iroh-ffi 바인딩 필요). 1.0에서 핵심 용어가 대거 바뀌어(Node→Endpoint, NodeId→EndpointId) 옛 블로그·README 예제는 그대로 안 돌아갈 수 있다 — 반드시 1.0 기준 문서를 봐야 한다. 또 디스커버리·릴레이를 n0 공용 서버에 의존하면 가용성이 그쪽에 묶이고, 완전 자립하려면 릴레이·DNS 서버를 직접 운영해야 한다(가능은 하다). 마지막으로, 모든 NAT가 홀펀칭에 뚫리는 건 아니라 일부는 계속 릴레이를 거친다.

3기술 스택 전체 지도

루트 Cargo.toml workspace · 각 crate의 Cargo.toml · 실제 소스 트리에서 확인한 구성.

마음의 준비부터. iroh는 "백엔드+프론트엔드" 웹 스택이 아니라 네트워킹 라이브러리 스택이다. 전부 Rust(edition 2024, MSRV 1.91)이고, 여러 crate를 묶은 workspace(모노레포)로 되어 있다. 계층은 크게 ⓐ 핵심 라이브러리, ⓑ QUIC·암호, ⓒ 디스커버리, ⓓ NAT 통과, ⓔ 런타임·생태계로 나뉜다.

ⓐ workspace의 crate들 — 무엇이 무엇인가

crate무엇역할
iroh핵심 라이브러리 ("p2p quic connections dialed by public key")Endpoint·연결관리·홀펀칭·net_report·디스커버리·TLS. 가장 큰 덩어리.
iroh-base공용 기본 타입EndpointId·PublicKey/SecretKey·EndpointAddr·RelayUrl 같은 순수 타입.
iroh-relay릴레이 클라이언트 + 서버n0가 프로덕션에서 돌리는 바로 그 릴레이 구현. 직접 띄울 수도 있다.
iroh-dnsDNS 기반 디스커버리 클라이언트EndpointId를 DNS(pkarr)로 조회. hickory-resolver 사용.
iroh-dns-serverpkarr 릴레이 + DNS 서버dns.iroh.link에서 도는 서버. axum·redb·mainline(BitTorrent DHT).

ⓑ QUIC · 암호 계층 — 연결의 본체

구성요소무엇역할
noq / noq-proto / noq-udpn0 자체 QUIC 구현endpoint 간 QUIC 연결을 수립. quinn을 대체. 멀티패스·데이터그램 지원.
ed25519-dalek / curve25519-dalek타원곡선 서명·키교환각 endpoint의 SecretKey/PublicKey. 공개키가 곧 EndpointId.
rustls + ring / aws-lc-rsRust TLS + 암호 백엔드QUIC의 TLS. tls-ring(기본) 또는 tls-aws-lc-rs(포스트양자용) 선택.
raw public key TLS인증서 없는 TLS (RFC 7250류)CA 인증서 체인 대신 endpoint 키쌍으로 서로를 인증. iroh/src/tls.rs.

ⓒ 디스커버리(주소 찾기) — "공개키 → 현재 주소"

구성요소무엇역할
PkarrPublic Key Addressable Resource Records공개키로 서명한 DNS 레코드를 발행/조회. "내 EndpointId는 이 릴레이에 있다"를 공표.
DNS (hickory)표준 DNS 조회/서버DnsAddressLookup가 dns.iroh.link에서 endpoint 주소를 조회. DNS-over-HTTPS 지원.
mainline DHTBitTorrent 메인라인 DHT중앙 서버 없는 분산 조회 경로(별도 crate). pkarr 레코드를 DHT에도 보관.
redb / governor임베디드 DB · 레이트리밋dns-server가 pkarr 패킷을 저장하고 남용을 막는 부품.

ⓓ NAT 통과 · 경로 선택 — 직접 연결을 뚫는 부품

iroh의 마법 대부분이 여기 있다. net_report(iroh/src/net_report.rs)는 "내가 IPv4/IPv6로 인터넷에 닿는지, NAT 상황은 어떤지, 릴레이까지 지연은 얼마인지"를 점검한다 — Tailscale의 netcheck에서 갈라져 나왔고, 자기 공인주소 파악에 STUN 대신 QAD(QUIC Address Discovery)를 쓴다. portmapper는 공유기에 UPnP·PCP·NAT-PMP로 포트 매핑을 요청해 직접 연결 가능성을 높인다. magic socket(socket.rs)은 직접IP·릴레이·커스텀 전송을 한데 묶고 RTT가 가장 낮은 경로를 골라 보낸다(biased_rtt_path_selector).

ⓔ 런타임 · 생태계 — 주변 부품과 위에 얹는 프로토콜

비동기 런타임은 tokio다. n0가 함께 만드는 보조 crate들 — n0-future(future 유틸), n0-watcher(값 변화를 구독하는 Watcher), n0-error(스택트레이스 친화 에러), netwatch(네트워크 변화 감지) — 가 곳곳에 쓰인다. WASM 타깃에선 ws_stream_wasm·wasm-bindgen-futures 등으로 브라우저 환경을 지원한다. 그리고 응용 계층에 iroh-blobs / iroh-gossip / iroh-docs를 얹어 실제 앱을 만든다(아래 §4·§6에서 상술).

4아키텍처 심화 분석

먼저 전체 그림을 한 장으로 본 뒤, 대표 흐름 '공개키로 연결이 맺히는 과정'을 끝까지 따라간다.

iroh의 일은 "두 endpoint 사이에 가장 빠른 암호화 QUIC 경로를 만들고 유지하는 것"이다. 큰 그림은 이렇다. (1) 각 endpoint는 시작할 때 가장 가까운 릴레이를 home relay로 잡는다. (2) A가 B에 연결하려 하면 먼저 B의 home relay를 거쳐 잇는다. (3) 그 사이 양쪽은 서로의 주소를 교환하고 홀펀칭으로 직접 경로를 시도한다. (4) 직접 경로가 뚫리면 릴레이를 빼고 직접 연결만 쓴다. 부품 이름을 외우기 전에 숲부터 보자.

독수리 시점 — 전체 구조 한 장

Endpoint A Endpoint B (SecretKey a) (SecretKey b) │ connect(EndpointId_b, ALPN) ▲ ▼ │ ┌──────────────┐ ① 주소 모름 → 디스커버리 ┌──────────────┐ │ AddressLookup│◀───────────────────────────────▶│ AddressLookup│ │ (Pkarr · DNS)│ "EndpointId_b 는 어느 릴레이?" │ + 발행자 │ └──────┬───────┘ └──────┬───────┘ │ ② home relay 경유로 일단 연결 │ ▼ ▼ ┌─────────────────── RELAY (DERP 계보) ───────────────────┐ │ 암호문만 중계 · EndpointId 로 라우팅 · 홀펀칭 보조(QAD) │ └───────────────────────────┬───────────────────────────────┘ │ ③ 서로 주소 교환 + 홀펀칭 ▼ ════════ ④ 직접 QUIC 연결(noq) — 릴레이 빠짐 ════════ magic socket 이 RTT 최저 경로 선택 [공통 토대] net_report(QAD로 NAT 파악) · portmapper(UPnP/PCP) · raw-public-key TLS(인증서 없이 키로 인증)
용어
EndpointId · ALPN — 연결에 꼭 필요한 두 가지
EndpointId는 그 endpoint의 PublicKey 그 자체(32바이트 Ed25519). 신원이자 주소록 키이자 암호 검증 대상이다. ALPN(Application-Layer Protocol Negotiation)은 "이 연결에서 무슨 프로토콜로 말할지" 정하는 이름표(예: b"iroh-example/echo/0"). 연결하려면 ①상대 EndpointId ②주소 힌트(보통 RelayUrl) ③ALPN, 이 셋이 필요하다.

흐름 ① — "공개키만으로 연결이 맺히기까지" (해피패스)

endpoint.connect(addr, ALPN) 한 줄 뒤에서 벌어지는 일을 순서대로 풀면:

// A가 B의 EndpointId(공개키)만 들고 connect 했을 때
1. 주소 찾기   AddressLookup(Pkarr/DNS)로 "EndpointId_b 의 RelayUrl" 조회
2. 릴레이 경유   B의 home relay 에 접속 → 암호문을 EndpointId 로 라우팅
3. 경로 탐색   net_report 가 내 NAT/주소 파악(QAD), 양쪽이 후보 주소 교환
4. 홀펀칭      양쪽이 동시에 패킷을 쏘아 NAT 에 구멍 → 직접 경로 시도
5. 승격      직접 QUIC(noq) 연결 성공 → magic socket 이 릴레이→직접 전환
6. 암호화      raw-public-key TLS 로 "정말 EndpointId_b 인지" 검증 완료

핵심 관례 하나 — 릴레이와 직접 주소를 동시에 쓸 수 있다. 처음엔 릴레이로 0초 지연 없이 잇고, 직접 경로가 준비되면 자연스럽게 갈아탄다. 사용자 코드는 이 전환을 신경 쓸 필요가 없다. 또 하나, 상대가 이미 직접 닿을 수 있으면 릴레이 없이도 연결되고, 반대로 홀펀칭이 끝내 안 되면 계속 릴레이를 통해 (느리지만) 끊김 없이 통신한다.

흐름 ② — 앱을 얹는 법: Router + ProtocolHandler + ALPN

받는 쪽은 "ALPN마다 다른 처리기"를 등록한다. Router가 들어오는 연결의 ALPN을 보고 알맞은 ProtocolHandler로 보낸다 — 한 endpoint가 여러 프로토콜을 동시에 서빙할 수 있다.

// 받는 쪽: ALPN "iroh-example/echo/0" 에 Echo 처리기 등록
let endpoint = Endpoint::bind(presets::N0).await?;
let router = Router::builder(endpoint)
    .accept(ALPN, Echo)   // ALPN → 처리기 매핑
    .spawn();

// 보내는 쪽: 같은 ALPN 으로 다이얼 후 양방향 스트림
let conn = endpoint.connect(addr, ALPN).await?;
let (mut send, mut recv) = conn.open_bi().await?;
send.write_all(b"Hello, world!").await?;
send.finish()?;                          // 이 스트림은 더 안 보냄
let echo = recv.read_to_end(1000).await?;
초보자 함정
스트림은 "게으르다" — open_bi 만으론 상대가 모른다

QUIC 스트림은 비용을 아끼려고 게으르게(lazy) 생성된다. conn.open_bi()를 불러도, 실제로 데이터를 한 번 보내기 전까지 상대의 accept_bi()는 반환되지 않는다. "연결은 됐는데 상대가 스트림을 못 받는다"면 십중팔구 이 함정이다 — 여는 쪽이 write_all로 첫 바이트를 흘려보내야 받는 쪽이 깨어난다.

이 레포에서 배울 핵심 설계 패턴 둘. 첫째, magic socket(socket.rs, 108KB) — 직접IP·릴레이·커스텀을 하나의 소켓처럼 추상화하고 경로별 RTT·상태를 추적해 최적 경로로 자동 전환한다. 둘째, 릴레이는 "암호문 우체부" — 릴레이는 EndpointId만 보고 패킷을 전달할 뿐, 내용은 양쪽 키로만 풀리므로 릴레이 운영자도 트래픽을 못 읽는다. "중앙 서버를 쓰되 신뢰는 최소화"하는 모범 설계다.

5디렉토리 구조 해부

workspace 멤버 crate들과, 간판 crate iroh/src/의 내부. 큰 덩어리만 본다.

루트는 여러 crate를 묶은 workspace다. 복잡성의 대부분은 iroh/src/ 안 두 거대 파일 — endpoint.rs(158KB)와 socket.rs(108KB) — 에 모여 있다. 전자는 공개 API(Endpoint·Connection), 후자는 그 마법을 떠받치는 멀티패스 소켓이다.

iroh/ ← workspace 루트 ├── Cargo.toml [workspace] members 정의 ├── iroh-base/ 공용 타입: EndpointId · RelayUrl · 키 ├── iroh-relay/ 릴레이 클라이언트 + 서버(DERP 계보, 바이너리 iroh-relay) │ └── src/protos/relay.rs 릴레이 와이어 프로토콜(Ping/Pong·Datagram 프레임) ├── iroh-dns/ DNS(pkarr) 디스커버리 클라이언트 ├── iroh-dns-server/ dns.iroh.link 서버(axum·redb·mainline DHT) ├── docker/ 릴레이/DNS 서버 도커 자산 └── iroh/ ★ 간판 라이브러리 ├── examples/ echo · connect · transfer · 0rtt · pq-key-exchange … └── src/ ├── lib.rs 아키텍처 설명이 담긴 최상위 문서(꼭 읽을 것) ├── endpoint.rs (158KB) ★ Endpoint·Builder·Connection·스트림 API │ └── endpoint/{presets, connection, quic, hooks, bind}.rs ├── socket.rs (108KB) ★ magic socket: 멀티패스·경로선택 │ └── socket/{biased_rtt_path_selector, remote_map, transports/}.rs ├── net_report.rs NAT/네트워크 상태 파악(QAD, netcheck 계보) ├── address_lookup.rs 디스커버리: dns.rs · pkarr.rs · memory.rs ├── protocol.rs Router + ProtocolHandler(ALPN 라우팅) ├── tls.rs raw-public-key TLS(인증서 없는 인증) ├── portmapper.rs UPnP/PCP/NAT-PMP 포트 매핑 └── defaults.rs n0 공용 릴레이/DNS 기본값
위치한 줄 역할
iroh/src/lib.rs시스템 전체를 설명하는 최상위 문서 주석. iroh를 이해하려면 가장 먼저 읽을 한 장.
iroh/examples/echo.rs두 endpoint를 잇는 최소 완전 예제. 여기서 시작.
iroh/src/endpoint.rs공개 API의 본체 — Endpoint·Builder·Connection·open_bi/accept_bi.
iroh/src/socket.rsmagic socket. 직접/릴레이/커스텀 경로 멀티플렉싱 + RTT 기반 경로 선택.
iroh/src/net_report.rsNAT·도달성 진단. STUN 대신 QAD. Tailscale netcheck에서 파생.
iroh/src/address_lookup.rs디스커버리(옛 discovery). Pkarr 발행·DNS 조회·메모리 등록.
iroh-relay/릴레이 클라이언트와 서버. n0 프로덕션과 같은 코드 — 직접 운영 가능.
iroh-dns-server/pkarr 릴레이 + DNS 서버. 공개키 서명 레코드를 DNS로 서빙.

6학습 포인트 (기술별)

이 레포 한 채로 배울 수 있는 네트워킹 기술과, 각각의 실습 아이디어.

iroh는 "현대 P2P 네트워킹에서 어려운 것"이 골고루 한 코드베이스에 모여 있어 공부 소재로 훌륭하다. 기술별로 무엇을 건질지 짚는다.

① QUIC 전송 프로토콜

UDP 위의 신뢰성 + 암호화 + 다중 스트림

iroh의 모든 연결은 QUIC(자체 엔진 noq)이다. 한 연결 안에 양방향/단방향 스트림과 데이터그램을 깔 수 있고, 스트림끼리 head-of-line blocking이 없다. examples/0rtt.rs로 0-RTT 재연결도 볼 수 있다. "TCP+TLS와 무엇이 다른가"를 코드로 체감하기 좋다.

실습: open_bi(양방향)와 open_uni(단방향), 그리고 데이터그램(listen-unreliable.rs)을 각각 써 보고 차이를 정리.

② NAT 통과 · 홀펀칭

공유기 뒤 기기에 어떻게 직접 닿는가

net_report가 NAT 종류와 공인주소를 파악하고(STUN 대신 QAD), portmapper가 UPnP/PCP로 포트를 열고, 릴레이가 양쪽 타이밍을 맞춰 홀펀칭을 돕는다. 네트워크 엔지니어링의 정수가 한 곳에 있다.

실습: RUST_LOG를 켜고 echo 예제를 다른 네트워크의 두 기기에서 돌려, "릴레이 경유 → 직접 연결 승격" 로그가 찍히는 순간을 관찰.

③ 공개키 신원 · raw-public-key TLS

인증서(CA) 없이 키만으로 서로를 증명

iroh는 CA 인증서 체인이 없다. 각 endpoint의 SecretKey로 TLS를 수행하고, 연결 상대가 정확히 그 EndpointId(공개키)인지 검증한다(src/tls.rs). "신원 = 암호키"라는 모델이 어떻게 중간자 공격을 원천 차단하는지 배운다.

실습: SecretKey를 생성·저장해 재시작해도 같은 EndpointId를 갖게 만들기. 잘못된 EndpointId로 connect하면 어떻게 거부되는지 확인.

④ 릴레이 프로토콜 (DERP 계보)

"암호문만 중계하는 우체부" 설계

iroh-relay는 Tailscale의 DERP를 개정한 프로토콜이다(protos/relay.rs). Ping/Pong, Datagram 프레임, 클라이언트 큐, Happy Eyeballs(IPv4/IPv6 경주), WebSocket 업그레이드, ACME TLS까지 — 프로덕션급 중계 서버를 통째로 읽을 수 있다.

실습: iroh-relay 바이너리를 로컬에 띄우고 RelayMode::Custom으로 내 릴레이만 쓰게 설정해 두 프로세스를 연결.

⑤ 디스커버리 (Pkarr · DNS · DHT)

"공개키 → 현재 주소"를 분산적으로 푸는 법

각 endpoint는 자기 주소를 Pkarr(공개키 서명 DNS 레코드)로 발행하고, 상대는 DNS나 DHT로 조회한다. 중앙 주소록 없이 "이름으로 사람 찾기"를 구현하는 실전 예다(iroh-dns·iroh-dns-server).

실습: PkarrPublisher::n0_dns() + DnsAddressLookup::n0_dns()만으로(직접 주소 없이) 두 기기를 연결해 보고, 발행된 DNS 레코드를 조회로 확인.

⑥ 프로토콜 설계 (Router/ALPN) + 생태계

한 endpoint에 여러 프로토콜 얹기

Router가 ALPN별로 ProtocolHandler를 라우팅한다. 직접 짜는 대신 iroh-blobs(파일전송)·iroh-gossip(펍섭)·iroh-docs(동기화 KV)를 얹으면 곧장 실전 앱이 된다. "전송 계층 위에 앱 프로토콜을 어떻게 구조화하나"의 모범.

실습: 나만의 ALPN으로 간단한 요청-응답 프로토콜(예: 핑퐁/채팅)을 ProtocolHandler로 구현해 Router에 등록.

7하드웨어/시스템 요구사항

GPU·고사양 불필요. "Rust 툴체인"과 "약간의 네트워크 권한"이면 충분하다.

iroh는 가벼운 라이브러리다 — gossip 같은 프로토콜도 "평범한 휴대폰이 감당할 자원"으로 돈다고 README가 명시한다. 추론 모델이 아니라 네트워킹 코드라 GPU는 전혀 필요 없다. 대신 최신 Rust 툴체인과, 직접 연결을 위한 UDP 통신 권한이 중요하다.

항목요구사항
Rust 툴체인rustc 1.91 이상(MSRV), edition 2024. cargo add iroh로 의존성 추가.
비동기 런타임tokio 기반. #[tokio::main] 환경에서 동작.
네트워크UDP 송수신 가능해야 함(홀펀칭). 막히면 자동으로 릴레이 폴백. 기본 릴레이 QUIC 포트 7842.
공용 인프라(선택)n0가 운영하는 릴레이 4곳(NA 동/서·EU·아태)과 dns.iroh.link가 기본값. 무료, 별도 설정 불필요.
자립 운영(선택)완전 자체 운영 시 iroh-relay·iroh-dns-server를 직접 띄움(도커 자산 제공).
브라우저(WASM)wasm32-unknown-unknown 타깃 지원. 브라우저에선 릴레이 경유(WebSocket) 중심.
포스트양자(선택)PQ 키교환 예제는 tls-aws-lc-rs 백엔드 필요(ring 기본에선 미지원).
GPU불필요.

가장 빠른 출발점은 레포를 클론해 cargo run --example echo를 돌리는 것이다. 한 프로세스 안에서 두 endpoint가 서로 echo를 주고받으므로, 외부 네트워크 설정 없이도 iroh의 연결·스트림·Router 흐름을 즉시 눈으로 확인할 수 있다.

8직접 해볼 수 있는 실습 과제

"echo 돌리기"는 5분, "직접 프로토콜·릴레이"는 한나절. 난이도 순으로.

실습 1 난이도 ★☆☆ 입문

echo 예제 실행하고 로그 읽기

레포를 클론해 cargo run --example echo를 돌린다. 한 프로세스 안에서 두 endpoint가 연결·스트림·Router를 거쳐 "Hello, world!"를 echo한다. RUST_LOG=iroh=debug를 붙여 어떤 단계가 찍히는지 관찰한다.

목표: Endpoint·connect·open_bi·ProtocolHandler의 한 사이클을 눈으로 확인. iroh의 "최소 골격"을 손에 익히기.
실습 2 난이도 ★☆☆ 입문

EndpointId를 출력하고 두 프로세스로 분리

받는 쪽 프로그램이 endpoint.id()(EndpointId)와 endpoint.addr()를 콘솔에 찍게 하고, 보내는 쪽은 그 값을 인자로 받아 connect한다. examples/listen.rs·connect.rs가 그대로 본보기다.

목표: "공개키 = 주소록 키"를 체감. 같은 PC의 두 터미널로 P2P 연결을 직접 만들어 보기.
실습 3 난이도 ★★☆ 중급

직접 주소 없이 '디스커버리로만' 연결

IP·포트를 전혀 주지 않고 EndpointId만으로 연결해 본다. 받는 쪽은 PkarrPublisher::n0_dns()로 주소를 발행하고, 보내는 쪽은 DnsAddressLookup::n0_dns()로 조회한다. 진짜 "이름으로 전화 걸기"를 구현하는 단계.

목표: 디스커버리(Pkarr+DNS)가 "공개키 → 현재 주소"를 어떻게 푸는지 체감. 발행된 DNS 레코드도 조회로 확인.
실습 4 난이도 ★★☆ 중급

나만의 ALPN 프로토콜 만들기

ProtocolHandler를 구현해 간단한 요청-응답(예: 시간 묻기, 핑퐁, 한 줄 채팅) 프로토콜을 만든다. 고유 ALPN을 붙여 Router::builder(endpoint).accept(MY_ALPN, handler).spawn()으로 등록하고, 같은 ALPN으로 다이얼한다.

목표: "전송 계층 위에 앱 프로토콜 얹기"를 직접. ALPN이 어떻게 여러 프로토콜을 한 endpoint에서 분기하는지 이해.
실습 5 난이도 ★★★ 고급

내 릴레이 서버 띄우고 그것만 쓰게 하기

iroh-relay 바이너리를 로컬/서버에 띄우고, 클라이언트는 RelayMode::Custom(RelayMap)으로 n0 공용 릴레이 대신 내 릴레이만 쓰게 설정한다. 두 프로세스를 그 릴레이로 연결해 본다.

목표: "자립형 P2P 인프라"의 첫걸음. 릴레이가 암호문만 중계함을 로그/패킷으로 확인.
실습 6 난이도 ★★★ 고급

iroh-blobs / iroh-gossip로 실전 앱 만들기

전송 계층 위에 생태계 프로토콜을 얹는다. iroh-blobs로 두 기기 간 파일을 콘텐츠 주소(BLAKE3)로 전송하거나, iroh-gossip으로 여러 노드가 메시지를 펍섭하는 작은 채팅방을 만든다.

목표: "연결 라이브러리"가 어떻게 "분산 앱 토대"가 되는지 체감. 로컬 퍼스트 앱의 구조 감 잡기.

9관련 기술 심화 학습 로드맵

iroh를 100% 소화하기 위한 6주 코스(주당 5~7시간 가정).

주차주제학습 자료 / 할 일
1주차P2P·NAT·QUIC 기초NAT/홀펀칭 개념 + QUIC가 TCP+TLS와 뭐가 다른지 정리. iroh/src/lib.rs 정독. 실습 1.
2주차iroh 기본 APIEndpoint·Connection·open_bi/accept_bi·게으른 스트림 함정. 실습 2.
3주차디스커버리(Pkarr·DNS)"공개키 → 주소" 원리, address_lookup 읽기. 직접주소 없이 연결. 실습 3.
4주차프로토콜 설계(Router·ALPN)protocol.rs + 예제. 나만의 ALPN 프로토콜 구현. 실습 4.
5주차릴레이·홀펀칭 내부net_report(QAD)·iroh-relay(DERP)·magic socket 읽기. 내 릴레이 운영. 실습 5.
6주차생태계 + WASMiroh-blobs/gossip/docs로 앱 만들기, 브라우저(WASM) 빌드 맛보기. 실습 6.

10핵심 키워드 사전

이 문서와 레포에 등장한 용어를 한자리에.

용어의미
P2P중앙 서버 없이 두 기기가 직접 통신하는 방식. iroh가 풀어 주는 핵심 문제.
QUICUDP 위의 차세대 전송 프로토콜. 신뢰성+TLS 암호화+다중 스트림 내장. HTTP/3의 바탕.
noqn0가 자체 개발한 QUIC 구현. iroh 1.0에서 quinn을 대체. 멀티패스·데이터그램 지원.
EndpointIdendpoint의 공개키(32바이트 Ed25519) 그 자체. 신원·주소록 키·암호 검증 대상.
Endpointiroh의 연결 단위(옛 Node). connect·bind·id·addr를 제공.
ALPNApplication-Layer Protocol Negotiation. 연결에서 쓸 프로토콜 이름표. Router 라우팅 키.
홀펀칭 / NATNAT(공유기)에 임시 구멍을 내 직접 연결을 트는 기술. iroh가 릴레이 도움으로 수행.
릴레이(relay)NAT를 못 뚫을 때 암호문을 중계하는 서버. EndpointId로만 라우팅, 내용은 못 읽음.
DERPTailscale의 릴레이 프로토콜(Designated Encrypted Relay for Packets). iroh 릴레이의 계보.
home relayendpoint가 시작 시 잡는 "가장 가까운 릴레이". 다른 endpoint가 나에게 닿는 첫 통로.
PkarrPublic Key Addressable Resource Records. 공개키로 서명한 DNS 레코드로 주소를 발행/조회.
디스커버리(AddressLookup)"공개키 → 현재 주소"를 찾는 시스템. DNS·Pkarr·mDNS·DHT 경로 제공.
QADQUIC Address Discovery. STUN을 대체해 자기 공인주소를 알아내는 최신 방식.
net_reportNAT 종류·IPv4/6 도달성·릴레이 지연을 진단하는 모듈. Tailscale netcheck 계보.
magic socket직접IP·릴레이·커스텀 경로를 하나로 묶고 RTT 최저 경로를 자동 선택하는 소켓 추상화.
raw public key TLSCA 인증서 없이 endpoint 키쌍으로 서로를 인증하는 TLS(RFC 7250류).
Router / ProtocolHandler들어오는 연결을 ALPN별 처리기로 라우팅하는 구조. 한 endpoint에 여러 프로토콜.
RelayMode / RelayMap릴레이 사용 방식 설정. Default(n0 공용)·Custom(내 릴레이)·Disable.
portmapper공유기에 UPnP/PCP/NAT-PMP로 포트 매핑을 요청해 직접 연결 가능성을 높이는 모듈.
iroh-blobs / gossip / docsiroh 위에 얹는 프로토콜: 파일전송(BLAKE3)·펍섭 오버레이·동기화 KV 저장소.
n0 (number 0)iroh를 만든 팀/회사. 공용 릴레이·DNS 인프라도 운영.

11참고 링크

원문·문서·관련 기술 더 파기.