안드로이드의 DNS-over-HTTP/3

@onseok· May 04, 2025 · 12 min read

dns over http3 header

2022년 7월, Google은 안드로이드에 중요한 보안 업데이트를 발표했습니다. 바로 DNS-over-HTTP/3(DoH3) 지원인데요. 이는 사용자의 개인정보를 보호하면서 동시에 네트워크 성능을 개선하는 혁신적인 기술입니다.

DNS 개인정보 문제

네트워크 연결의 대부분은 DNS Lookup으로 시작됩니다. 우리가 웹사이트에 접속할 때 HTTPS를 통해 콘텐츠를 안전하게 전송받지만, 정작 그 웹사이트의 주소를 찾는 DNS 조회는 전통적으로 암호화되지 않은 UDP 프로토콜을 통해 이루어져 왔습니다.

이는 마치 편지의 내용은 봉투에 넣어 보호하면서도, 받는 사람의 주소는 누구나 볼 수 있게 해놓은 것과 같습니다. 누군가 우리가 어떤 웹사이트를 방문하려 하는지 엿볼 수 있는 것입니다.

안드로이드의 해결 방법

안드로이드 9.0(Pie)부터 도입된 Private DNS 기능DNS-over-TLS(DoT)를 통해 이 문제를 해결하려 했습니다. 하지만 DoT에는 몇 가지 성능상의 한계가 있었습니다.

참고로 DoT는 RFC 8310 표준문서에서 확인하실 수 있습니다.

첫 번째로 헤드 오브 라인 블로킹(HOL Blocking)이라는 TCP의 고질적인 문제입니다. 하나의 연결에서 여러 DNS 조회를 처리할 때, 앞선 요청이 지연되면 그 뒤의 모든 요청들이 순서대로 기다려야 합니다. 이는 은행에서 한 명의 고객이 복잡한 업무를 처리하면, 뒤에 기다리는 모든 고객들이 영향을 받는 것과 비슷합니다.

두 번째로는 실제로도 제 경험상 회사에서 오래된 안드로이드 기기로 테스트를 하다보면 자주 접하는 문제인데, 모바일 환경의 특성상 네트워크가 자주 변경될 수 있고, WiFi에서 셀룰러로, 또는 4G에서 5G로 전환될 때마다 TCP 연결을 처음부터 다시 협상해야 합니다. 이는 상당한 시간과 리소스가 소모되는 과정입니다.

이러한 문제들로 인해 DoT는 실제 환경에서 개별 연결이 대부분 매우 짧은 수명을 가졌고, 연결 재시작에 따른 오버헤드가 상당해졌습니다. 결국 이는 암호화된 DNS의 장점을 충분히 살리지 못하게 만들게 되었습니다.

HTTP/3와 QUIC

이러한 한계를 극복하기 위해 Google은 DNS-over-HTTP/3(DoH)라는 새로운 접근 방식을 도입했습니다. 여기서 핵심이 되는 기술이 바로 HTTP/3에서 사용하는 QUIC 프로토콜입니다.

QUICUDP를 기반으로 구축되었습니다. 네트워크를 공부해보았다면 UDP는 신뢰성이 낮다고 알고 계실 것입니다. 실제로 기존의 UDP는 그랬지만, QUIC는 단순히 UDP 위에 올라탄 것이 아니라, UDP를 캔버스처럼 활용하여 완전히 새로운 전송 프로토콜을 설계했습니다.

가장 큰 혁신은 멀티플렉싱의 재설계입니다. QUIC에서는 여러 개의 데이터 스트림이 독립적으로 존재합니다. 앞서 언급한 은행의 예시로 돌아가면, 이제는 각 카운터마다 별도의 줄이 있어서 한 카운터에서 문제가 생겨도 다른 카운터의 업무 처리에는 전혀 영향을 주지 않습니다. 즉, 하나의 DNS 조회가 지연되어도 다른 DNS 조회들은 그대로 진행됩니다.

또한 QUIC는 연결 ID라는 개념을 도입했는데, 이는 IP 주소나 포트 번호가 변경되어도 기존 연결 상태를 유지할 수 있게 해줍니다. 모바일 기기가 WiFi에서 LTE로 전환될 때, 기존에는 모든 연결이 끊어지고 다시 맺어야 했지만, QUIC는 마치 사람이 집에서 사무실로 이동해도 계속 통화를 이어가듯이 연결을 유지합니다.

zero rtt connection establishment

RTT(Round Trip Time) 최적화도 중요한 특징입니다. TCP는 연결을 시작할 때 HandShake를 나누는 데 최소 2번의 통신이 필요했다면, QUIC는 초기 연결 시에도 1번의 통신으로 충분하며, 특히 재연결 시에는 0-RTT로 즉시 데이터 전송이 가능합니다. 이전에 연결했던 적이 있다면, 인증과 데이터 전송을 동시에 진행할 수 있어 매우 빠릅니다.

QUIC에 대한 더 자세한 내용은 RFC 9000 표준문서에서 확인하실 수 있습니다.

DoH3의 실제 성능

doh3 measurements

Google에서 실제로 테스트한 결과, DoH3는 DNS 조회 응답 시간을 약 24% 단축시켰습니다. 특히 네트워크 상태가 좋지 않을 때 DoH3의 성능 향상이 두드러졌는데, 95번째 백분위수 기준으로 응답 시간을 44%나 개선했습니다. 이는 QUIC의 개선된 흐름 제어와 패킷 손실 감지 메커니즘 덕분입니다. 신뢰성 측면에서도 DoH3는 안정적인 성능을 보였습니다. DoT와 거의 동일한 97%의 성공률을 유지합니다. (기존 UDP 방식은 83% 성공률)

Rust로 만든 안전한 구현

Google은 DNS-over-HTTP/3 구현에 Rust를 선택했습니다. 가끔 안드로이드 크래시 로그를 분석하다 보면, AOSP의 Rust코드들도 확인할 수 있는데 DNS-over-HTTP/3 구현이 안드로이드 플랫폼에서 메인라인 모듈로는 처음으로 Rust를 사용한 사례라고 합니다.

DNS 리졸버는 외부 입력을 직접 처리하는 핵심 컴포넌트이기 때문에 보안이 특히 중요하고, Rust의 메모리 안전성 보장과 엄격한 소유권 모델은 이러한 요구사항을 충족시키는 데 매우 적합했기 때문에 Rust를 선택하지 않았나 싶습니다. 실제로 DoH3 구현체는 불과 1,640줄의 코드로 완성되었으며, 단일 스레드만을 사용했습니다. 이는 기존 DoT 구현과 비교했을 때 더 간결하면서도 안전한 코드베이스를 제공하였고, 또한 Tokio라는 비동기 런타임 프레임워크를 활용하여 효율적인 I/O 처리를 구현했습니다. 중요한 것은 Cloudflare가 개발한 quiche라는 HTTP/3 라이브러리를 사용했다는 점입니다. 이 라이브러리는 메모리 안전성을 보장하면서도 C++와 원활하게 연동되어 기존 안드로이드 코드베이스와 통합하기에 적합했습니다. 이러한 기술적 선택들이 모여 안드로이드 플랫폼에서는 처음으로 Rust로 구현된 메인라인 모듈이 탄생하게 되었고, Mainline(Google Play 시스템 업데이트 메커니즘)을 통해 이러한 개선사항들이 더 많은 Android 사용자들에게 더 빨리 전달될 수 있도록 하였습니다.

Android 10부터 도입된 Mainline에 대한 더 자세한 내용은 AOSP - Mainline 문서에서 확인하실 수 있습니다.

마치며

rust

안드로이드 플랫폼에 점차 Rust가 도입됨에 따라, 최근에 개인적으로도 틈틈히 (취미로?) Rust를 공부하고 있습니다. (물론 회사일이 바쁘다는 핑계로 많은 시간을 투자하고 있지는 않습니다..)

아직 Rust 코드 구현 레벨까지 완벽하게 이해하지는 못하였지만, DNS-over-HTTP/3는 개인정보 보호와 성능 향상이라는 두 마리 토끼를 동시에 잡은 기술이라고 생각합니다.

무엇보다 보안과 성능이 반드시 트레이드오프 관계일 필요가 없다는 것을 알게 되었고, RFC 문서들을 보면서 modern network protocol의 설계 철학도 엿볼 수 있었는데, 단순히 기존 프로토콜의 단점을 보완하는 것이 아니라, 모바일 환경의 특성(네트워크 전환, 패킷 손실 등)을 고려한 근본적인 재설계가 이루어졌다는 점이 인상적이었던 것 같습니다.

또한 Mainline을 통한 모듈식 업데이트 시스템은 안드로이드 플랫폼의 fragmentation 문제를 우회하는 동시에, 새로운 기술의 빠른 확산을 가능하게 하는데, 안드로이드 개발을 하면서 개발/QA 단계에서 항상 고민해야 했던 호환성 문제들이 이런 방식으로 점차 해결되어가고 있다는 새로운 사실도 알게 되었습니다.

참고 자료

  1. Google Security Blog: DNS-over-HTTP/3 in Android
  2. Android Developer Guide: Private DNS
  3. HTTP/3 & QUIC RFC 9000
  4. Cloudflare quiche GitHub Repository
@onseok
배움을 배포하기