본문 바로가기
카테고리 없음

Chromium Based Browser의 DNS 방식

by marklim 2023. 4. 4.

 

Browser가 DNS Query를 직접 한다는 것을 알고 계셨을까요?

엄밀히 말하면 글 제목에서 알 수 있듯이 Chromium 기반의 Browser만 DNS Query를 직접 수행합니다.

* Chromium Based Browser는 Chrome, Edge, Opera, etc...입니다.

 

Bing에게 문의해 본 결과, 직접 한다는 것을 확인해 주었는데요. 🤔

실제로 검증하고자 아래와 같이 테스트해 보았습니다.

 

1. 제 컴퓨터의 DNS Cache 초기화 (DNS Flush)

2. Process Monitor 4개의 Browser(Edge, Opera, Chrome, Firefox)에 대한 DNS Query 주체를 확인해 봤습니다.

Process Monitor로 아래와 같이 Filtering 하여 확인해 본 결과,

- Path contains {Browser product}

- Path ends with :53 (DNS uses both TCP and UDP port 53)

 

Firefox = DNS Query OS단에서 수행했습니다.

(Firefox Filtering 한 결과 데이터를 확인할 수 없었습니다.)

, FireFox Browser는 OS 단(svchost.exe)에서 수행함을 알 수 있었습니다.

 

반면에 Chromium Based Browser(Chrome, Edge, Opera)아래와 같이 DNS Query를 직접 수행하는 것을 검증할 수 있었습니다.

그렇다면 왜 DNS Query를 직접 수행하는 것일까요?

정확한 답은 찾을 수는 없었지만, 개인적인 생각으로는 아래와 같이 보다 나은 보안 환경을 제공하고자 직접 수행한다고 생각합니다.

 

Unencrypted DNS

 

DNS-over-HTTPS(DoH)

출처: Chromium Blog: A safer and more private browsing experience with Secure DNS

 

위 테스트를 통해 Chromium Based Browser는 DNS Query를 직접 수행한다는 것을 확인할 수 있었습니다.

(Bing이 나름 똑똑하긴 하네요.😎)

 

번외로 DNS는 TCP와 UDP 모두 사용할 수 있다는 것은 알고 계셨을 텐데요.

출처: TCP/UDP의 포트 목록 - 위키백과, 우리 모두의 백과사전 (wikipedia.org)

DNS는 UDP와 TCP 모두 사용할 수 있지만 주로 UDP를 사용합니다.

그렇다면 어느 조건에 UDP에서 TCP로 전환될까요?

 

Chormium Open source를 확인해 본 결과 여러 조건이 있습니다.

(Open Source를 공부하며 개인적으로 도식화해 봤습니다.)

(Chromium) DNS Query 순서도 요약

 

모든 내용을 설명하기엔 방대한 양이기에 low_entropy에 한해서 정리하겠습니다.

보다 자세한 내용을 확인하시고 싶으시면 아래 오픈소스 링크에서 확인 가능합니다.

- dns_udp_tracker.cc - Chromium Code Search 

 

위 순서도에서 확인할 수 있듯이, low_entropy()가 True로 변경되면 DNS를 UDP에서 TCP로 시도하게 됩니다.

= Code Level에서 low_entropy()가 Ture로 되는 조건을 확인하면 UDP에서 TCP로 전환되는 조건을 알 수 있습니다.

 

실제 Code상으로는 Reference를 확인한 결과 아래와 같이 총 4가지 조건이 있습니다.

 

1. 재사용된 포트의 수가 3회 이상인 경우

//dns_udp_tracker.cc
void DnsUdpTracker::RecordQuery(uint16_t port, uint16_t query_id) {
  PurgeOldRecords();

  int reused_port_count = base::checked_cast<int>(
      base::ranges::count(recent_queries_, port, &QueryData::port));

  // 재사용된 Client의 Port 수가 포트재사용임계값(3) 이상인 경우
  if (reused_port_count >= kPortReuseThreshold && !low_entropy_) {
    low_entropy_ = true;
    RecordLowEntropyUma(LowEntropyReason::kPortReuse);
  }

  SaveQuery({port, query_id, tick_clock_->NowTicks()});
}

//dns_udp_tracker.h
static constexpr int kPortReuseThreshold = 3;

Windows에서 Dynamic port Range 49,152 ~ 65,536이며 사용가능한 port의 수는16,384입니다.잘 아시겠지만, 해당 dynamic port Range Udp만 사용하는 것은 아닙니다.)

 

즉 UDP에서 TCP로 전환될 확률(3회 이상 재사용될 확률)은3.92566 e-05 = 0.0000392566입니다.입니다
( /entropy_threshold_calculator.cc · Gerrit Code Review (googlesource.com))

  

2. INSUFFICIENT_RESOURCES (Server 측의 자원 부족을 의미합니다.)

//dns_udp_tracker.cc
void DnsUdpTracker::RecordConnectionError(int connection_error) {
  if (!low_entropy_ && connection_error == ERR_INSUFFICIENT_RESOURCES) {
    // On UDP connection, this error signifies that the process is using an
    // unreasonably large number of UDP sockets, potentially a deliberate
    // attack to reduce DNS port entropy.
    low_entropy_ = true;
    RecordLowEntropyUma(LowEntropyReason::kSocketLimitExhaustion);
  }
}

//net_error_list.h
// There were not enough resources to complete the operation.
NET_ERROR(INSUFFICIENT_RESOURCES, -12)

즉, DNS 운영 측 Server의 자원이 부족할 경우 UDP에서 TCP로 전환됩니다.

 

3. 인지된 ID 불일치

- 인지된 ID 불일치 수가 127 회인 경우

 

4. 미 인지된 ID 불일치

- 미 인지된 ID 불일치 수가 7 회 인 경우

//dns_udp_tracker.cc
void DnsUdpTracker::SaveIdMismatch(uint16_t id) {
  // No need to track mismatches if already flagged for low entropy.
  if (low_entropy_)
    return;

  base::TimeTicks now = tick_clock_->NowTicks();
  base::TimeTicks time_cutoff = now - kMaxRecognizedIdAge;
  bool is_recognized =
      base::ranges::any_of(recent_queries_, [&](const auto& recent_query) {
        return recent_query.query_id == id && recent_query.time >= time_cutoff;
      });

  if (is_recognized) {
    DCHECK_LT(recent_recognized_id_hits_.size(),
              kRecognizedIdMismatchThreshold);
    if (recent_recognized_id_hits_.size() ==
        kRecognizedIdMismatchThreshold - 1) {
      low_entropy_ = true;
      RecordLowEntropyUma(LowEntropyReason::kRecognizedIdMismatch);
      return;
    }

    DCHECK(recent_recognized_id_hits_.empty() ||
           now >= recent_recognized_id_hits_.back());
    recent_recognized_id_hits_.push_back(now);
  } else {
    DCHECK_LT(recent_unrecognized_id_hits_.size(),
              kUnrecognizedIdMismatchThreshold);
    if (recent_unrecognized_id_hits_.size() ==
        kUnrecognizedIdMismatchThreshold - 1) {
      low_entropy_ = true;
      RecordLowEntropyUma(LowEntropyReason::kUnrecognizedIdMismatch);
      return;
    }

    DCHECK(recent_unrecognized_id_hits_.empty() ||
           now >= recent_unrecognized_id_hits_.back());
    recent_unrecognized_id_hits_.push_back(now);
  }
}

//dns_udp_tracker.h
// Numbers of ID mismatches required to set the |low_entropy_| flag. Also
// serves as the max number of mismatches to be recorded, as no more entries
// are recorded after setting the flag.
static constexpr size_t kUnrecognizedIdMismatchThreshold = 8;
static constexpr size_t kRecognizedIdMismatchThreshold = 128;

이외에 더 조건이 있지만 공유드리는 취지와 맞지 않아서 생략하겠습니다.

 

위 내용을 다시 요약해 보자면,

1. Chromium Based Browser는 DNS Query를 직접 수행합니다.

2. DnsUdpTracker가 Client, Server 측을 체크하며 특정 임계치를 넘으면 UDP에서 TCP로 DNS Query를 수행합니다. 

 

읽어주셔서 감사합니다.🫡

해당 내용에 관련하여 피드백은 환영입니다.

댓글