Happy Eyeballs v2: 듀얼 스택 클라이언트는 IPv6와 IPv4를 어떻게 경주시킬까

DNS가 IPv6와 IPv4 주소를 함께 돌려줄 때, 클라이언트가 AAAA·A 조회와 주소 정렬, 시차를 둔 연결 시도로 먼저 성공한 연결을 고르는 RFC 8305의 절차와 권장 기본값의 의미를 정리합니다.

네트워크IPv6DNSTCP

DNS 조회 과정 글은 A와 AAAA 레코드로 주소를 받은 뒤 어느 주소로 먼저 연결할지는 클라이언트가 정한다는 이야기로 끝났습니다. 이 글은 그다음 단계를 다룹니다. 호스트 이름 하나가 IPv6 주소와 IPv4 주소를 여러 개 돌려줄 때, 듀얼 스택 클라이언트가 어떤 순서로 시도하고 언제 다음 주소로 넘어가는지를 정한 것이 Happy Eyeballs입니다. 현재 기준 문서는 RFC 8305(Happy Eyeballs Version 2)입니다.

주소를 하나씩 기다리면 생기는 문제

RFC 6724는 getaddrinfo() 같은 API가 주소 목록을 정렬해 돌려주면, 애플리케이션은 보통 첫 주소부터 연결을 시도하고 동작하는 주소를 찾을 때까지 목록을 차례로 돈다고 설명합니다. 이 방식에서는 앞쪽 주소가 응답하지 않으면 그 시도가 실패로 끝날 때까지 다음 주소로 넘어가지 못합니다.

RFC 6555는 2012년 당시 상황을 이렇게 적었습니다. IPv6 연결이 손상된 환경에서 IPv6를 지원하는 애플리케이션은 IPv4로 넘어가기 전에 몇 초씩 지연을 겪었고, 그 때문에 단말에서 IPv6를 통째로 끄는 일이 잦았다는 것입니다. DNS에 AAAA 레코드가 있다고 해서 그 IPv6 경로가 실제로 동작한다는 보장은 없습니다.

RFC 6724는 첫 주소만 써 보고 실패하면 포기하는 방식은 피해야 한다고(SHOULD NOT) 하면서, 애플리케이션에 따라서는 작은 지연을 두고 여러 주소를 병렬로 시도해 먼저 성공한 것을 쓰는 방식이 적절할 수 있다고 언급합니다. Happy Eyeballs는 이 생각을 구체적인 절차로 정리한 것입니다.

RFC 8305가 정하는 것과 정하지 않는 것

RFC 8305는 2017년 12월에 나온 Standards Track 문서이고, 처음 알고리즘을 기술한 RFC 6555(2012년 4월)를 폐기(obsolete)합니다. 문서가 스스로 밝히는 내용은 두 가지입니다. 사용자가 느끼는 지연을 줄이는 알고리즘이 지켜야 할 요구 사항, 그리고 예시 알고리즘입니다.

그래서 이 글에 나오는 50 ms, 250 ms 같은 숫자는 RFC가 권장하는 설정 가능한 기본값이지, 모든 브라우저나 운영체제가 그대로 쓰는 값이 아닙니다. RFC 8305 8절은 이 지연값들이 매우 많은 운영 기기에서 연결 타이밍을 측정해 정한 것이고 시간이 지나면 바뀔 것으로 예상한다고 쓰며, 구현자가 사양을 바꾸지 않고 다른 값을 써도 된다고 말합니다. 특정 클라이언트가 실제로 어떤 값을 쓰는지는 그 구현의 문서나 코드를 따로 확인해야 합니다.

RFC 8305는 호스트가 IPv6를 IPv4보다 선호하는 정책을 전제로 쓰였고, 정책이 다르면 권고를 그에 맞게 바꾸면 된다고 적고 있습니다. 절차는 네 단계로 나뉩니다.

  1. 비동기 DNS 조회 시작 (3절)
  2. 받은 목적지 주소 정렬 (4절)
  3. 비동기 연결 시도 시작 (5절)
  4. 연결 하나가 성립하면 나머지 시도 취소 (5절)

1단계: AAAA와 A를 함께 보내고, 둘 다 기다리지 않기

IPv4와 IPv6 연결이 모두 있는 클라이언트는 AAAA와 A 조회를 둘 다 보내야 합니다. RFC는 두 조회를 가능한 한 붙여서 보내되, AAAA를 먼저 보내고 곧바로 A를 보내라고 권고합니다(SHOULD).

또 두 응답이 모두 올 때까지 기다린 뒤 연결을 시작해서는 안 되며(SHOULD NOT), DNS 조회를 비동기로 다루라고 합니다(SHOULD). 한쪽 응답이 오지 않거나 크게 늦으면 다른 쪽 연결까지 늦어지기 때문입니다. 플랫폼에 비동기 DNS API가 없다면 주소 체계마다 스레드를 따로 두고 동기 조회를 하나씩 실행해 같은 동작을 흉내 낼 수 있다고 덧붙입니다.

응답 순서에 따라 동작이 갈립니다.

  • 유효한 AAAA 레코드가 하나 이상 있는 응답이 먼저 오면 첫 IPv6 연결 시도를 바로 시작합니다.
  • 유효한 A 응답이 먼저 오면 IPv6에 우선권을 주기 위해 AAAA 응답을 잠깐 기다립니다(SHOULD). 이 대기를 Resolution Delay라 하고 권장값은 50 ms입니다. RFC는 AAAA 응답이 A 응답보다 몇 밀리초 늦게 오는 일이 흔하다고 설명합니다.
  • 그 대기 시간 안에 유효한 AAAA 응답이 오면 바로 IPv6 시도를 시작합니다. 데이터가 없는 AAAA 응답(no error, no data)이 오거나 대기 시간이 끝날 때까지 응답이 없으면, 그때까지 받은 IPv4 주소로 정렬과 연결 시도를 진행합니다(SHOULD).
  • 연결 시도가 진행 중이고 아직 어떤 연결도 성립하지 않았을 때 AAAA 응답이 오면, 새 IPv6 주소를 후보 목록에 넣고 시도를 이어 갑니다.

즉 50 ms는 “A가 먼저 왔을 때 AAAA를 얼마나 기다릴지”에 대한 값이지, DNS 조회 전체에 붙는 대기 시간이 아닙니다.

2단계: RFC 6724로 정렬한 뒤 주소 체계를 번갈아 배치하기

연결을 시작하기 전에 클라이언트는 시도 순서를 정합니다. 이 목록에는 그 시점까지 받은 두 주소 체계의 주소가 모두 들어가야 합니다. 그래야 첫 IPv6 주소와 첫 IPv4 주소만이 아니라 목록 전체에 경주 효과가 생깁니다.

먼저 RFC 6724 6절의 목적지 주소 선택(Destination Address Selection)으로 정렬해야 합니다(MUST). 이 알고리즘은 IPv6와 IPv4 주소를 한 목록에서 함께 정렬하며, 두 주소를 비교하는 규칙 10개를 순서대로 적용합니다. 도달할 수 없는 목적지를 피하는 규칙, 정책 테이블의 우선순위(precedence)를 비교하는 규칙 등이 여기에 들어 있습니다. 클라이언트가 경로별 RTT 기록을 갖고 있다면 RTT가 낮은 주소를 선호하는 규칙을 RFC 6724의 규칙 8과 9 사이에 추가하라고(SHOULD) RFC 8305가 덧붙입니다. 이런 과거 기록은 서로 다른 네트워크 인터페이스 사이에서 써서는 안 됩니다(MUST NOT).

다음으로 주소 체계가 번갈아 나오도록 목록을 고칩니다(SHOULD). 정렬 결과 첫 주소가 IPv6라면 첫 IPv4 주소를 두 번째 자리로 끌어올리는 식입니다. 한 주소 체계가 통째로 막혀 있을 때 그 체계의 긴 목록을 모두 기다리지 않기 위해서입니다. 다른 체계로 넘어가기 전에 첫 주소 체계의 주소를 몇 개 연달아 시도할지를 First Address Family Count라 하며, 권장값은 1이고 특정 체계를 더 강하게 선호하려면 2를 쓸 수 있습니다.

3단계: 시차를 두고 시작하되, 시도는 겹쳐서 진행

연결 시도는 동시에 시작하지 않습니다(SHOULD NOT). 네트워크에 과도한 부하를 주지 않도록 목록의 첫 주소로 시도를 하나 시작하고, 나머지를 한 번에 하나씩 뒤이어 시작합니다. 다만 새 시도를 시작한다고 이전 시도가 멈추지는 않으므로 여러 시도가 동시에 진행 중일 수 있습니다. 전부 한꺼번에 보내는 것이 아니라, 간격을 두고 하나씩 추가하면서 앞선 시도도 계속 기다리는 방식입니다.

다음 시도까지의 간격을 Connection Attempt Delay라고 합니다.

  • RTT 데이터가 없을 때의 권장 기본값은 250 ms입니다. 더 정교한 구현이라면 이전 시도가 두 번째 TCP SYN을 보내는 시점, 즉 첫 SYN 재전송 시점에 맞추라고 RFC는 설명합니다.
  • 하한은 반드시 있어야 하며(MUST), 이전 시도 후 10 ms 안에 다음 연결을 시작해서는 안 됩니다(MUST NOT). 권장 최솟값(Minimum Connection Attempt Delay)은 100 ms입니다.
  • 상한도 두어야 하며(SHOULD), 권장 최댓값(Maximum Connection Attempt Delay)은 2초입니다.

정리하면 반드시 지켜야 하는 것은 하한을 둘 것과 10 ms보다 짧게 붙이지 않을 것이고, 50 ms, 1개, 250 ms, 100 ms, 2초는 권장값입니다.

4단계: 먼저 성공한 연결을 쓴다

시도 하나가 성공하면(대개 TCP 핸드셰이크가 끝난 시점) 아직 성공하지 않은 다른 시도는 모두 취소하고(SHOULD), 아직 시도하지 않은 주소는 무시합니다(SHOULD). 이때 진행 중인 비동기 DNS 조회는 취소해도 됩니다(MAY). 다만 DNS 리졸버는 그 뒤에도 짧은 시간(권장 1초) 동안 네트워크에서 오는 DNS 응답을 계속 처리해야 합니다(SHOULD). 그 응답이 DNS 캐시를 채워 다음 연결에 쓰일 수 있기 때문입니다.

권장 기본값으로 그려 본 가상 타임라인

아래는 RFC 8305의 권장 기본값(Resolution Delay 50 ms, First Address Family Count 1, Connection Attempt Delay 250 ms)을 그대로 쓴다고 가정한 가상의 예입니다. 측정값이 아니며, 주소는 문서용 예시 주소입니다. RFC 6724 정렬 결과 IPv6 주소가 앞에 왔다고 가정합니다.

(가상 예시, RFC 8305 권장 기본값 기준)
  0 ms  AAAA 조회 전송, 곧바로 A 조회 전송
 20 ms  A 응답 도착: 192.0.2.10
        → AAAA를 최대 50 ms(70 ms 시점까지) 기다림
 35 ms  AAAA 응답 도착: 2001:db8::10, 2001:db8::20
        → 정렬 후 주소 체계 교차:
          [2001:db8::10, 192.0.2.10, 2001:db8::20]
        → 시도 1 시작: 2001:db8::10 (응답 없음)
285 ms  시도 1이 아직 성립하지 않음 → 시도 2 시작: 192.0.2.10
        (시도 1은 취소하지 않고 계속 진행)
325 ms  시도 2의 TCP 핸드셰이크 완료 → 이 연결을 사용
        → 시도 1 취소, 2001:db8::20은 시도하지 않음

만약 70 ms까지 AAAA 응답이 오지 않았다면 클라이언트는 그 시점에 IPv4 주소만으로 정렬과 연결 시도를 시작하고, 아직 연결이 성립하기 전에 AAAA가 도착하면 그 IPv6 주소를 아직 시도하지 않은 주소 목록에 정렬해 넣습니다.

v1(RFC 6555)과 달라진 점

RFC 8305 부록 A에 따르면 RFC 6555는 AAAA 레코드와 A 레코드를 가진 호스트에 연결을 어떻게 시차를 두고 시도할지에 주로 집중했습니다. RFC 8305는 여기에 DNS 조회 방법, 주소 체계별로 주소가 여러 개일 때의 처리, 경주 중 DNS 응답이 바뀔 때의 처리, 과거 기록의 활용, NAT64/DNS64를 쓰는 IPv6 전용 네트워크 지원을 더했습니다. RFC 8305의 알고리즘을 단순하게 구현해도 이전 사양인 RFC 6555에 여전히 부합한다고 적고 있습니다.

진단할 때 기억할 점

  • Happy Eyeballs는 TCP/IP 계층의 초기 연결 실패를 다룹니다. 그 밖의 실패나 성능 문제는 선택된 연결에 여전히 영향을 줄 수 있고, 초기 핸드셰이크 동안에만 동작하므로 MTU 관련 문제가 드러나지 않을 수 있습니다(9절, 9.1절).
  • DNS가 서로 다른 애플리케이션 서버의 주소를 돌려줄 때, 애플리케이션 자체가 그 모든 서버에서 정상 동작한다는 보장은 없습니다. RFC는 TLS와 HTTP를 흔한 예로 듭니다(9.2절).
  • IPv6가 늘 실패하고 IPv4는 동작하는 설정 오류가 있어도, Happy Eyeballs 때문에 운영자가 문제를 알아차리기 어려울 수 있습니다. RFC는 네트워크 운영자가 모든 주소 체계를 외부 수단으로 따로 모니터링하라고 권합니다(9.3절). 연결이 잘 된다고 해서 IPv6가 정상이라는 뜻은 아닙니다.
  • 같은 호스트 이름으로 맺는 연결이 매번 같은 IP 주소로 간다고 가정하면 안 됩니다. RFC 8305는 Happy Eyeballs로 인해 이어지는 연결이 다른 IP 주소를 쓸 가능성이 커질 수 있다고 적고, 보안 속성을 안정된 이름-주소 대응에 기대지 말라고 합니다(10절).

출처

확인일: 2026-10-11. 이 글은 RFC 8305를 기준으로 설명합니다. 같은 날 IETF Datatracker에서 RFC 8305가 Proposed Standard이고, 이를 폐기한 RFC가 표시되어 있지 않음을 확인했습니다.

블로그로