요약

  • RFC 1464는 DNS TXT 레코드 안에 파싱 가능한name=value규약을 제안하여, 속성 이름의 전역적 의미나 등록을 정의하지 않고도 기존 서버와의 호환성을 확보했습니다.
  • 따라서 반환된 TXT 문자열은 특정 이름과 시점에서의 존재만을 입증할 뿐이며, 애플리케이션적 의미, 게시 권한, DNSSEC 검증, 신선도, 파서 동작 및 다운스트림 결과는 별도의 증거를 필요로 했습니다.

RFC 1464의 매력은 등호 자체가 아니었습니다. 그 주변의 거의 모든 것이 바뀌지 않아도 된다는 약속이었습니다.

1993년 5월, 도메인 네임 시스템에는 이미 호스트 주소나 메일 익스체인저 같은 형식화된 객체가 실려 있었습니다. 새로운 종류의 데이터를 추가하는 일은 보통 새로운 리소스 레코드 타입을 정의하고 소프트웨어가 이를 이해할 때까지 기다리는 것을 의미했습니다. RFC 1464는 더 좁은 경로를 제안했습니다. 기존 TXT 레코드가<속성 이름>=<속성 값>형태의 인쇄 가능한 ASCII를 담을 수 있다는 것입니다. 대부분의 DNS 서버는 속성이 무엇을 의미하는지 알지 못한 채 그 텍스트를 저장하고 반환할 수 있었습니다.

이는 배포 비용의 실질적인 절감이었습니다. 동시에, 이후 수십 년간 DNS 설계가 드러내게 되는 경계의 시작이기도 했습니다. 즉, 전송 계층이 범용으로 남을 수 있는 유일한 방법은 해석을 다른 곳으로 옮기는 것이라는 경계였습니다.

하나의 문자열이 두 필드를 갖게 되다

제안된 외부 형식은 단순했습니다. 소유자 이름, 클래스, TTL, TXT 타입, 그리고 따옴표로 묶인 문자열이었습니다. 문자열 안에서 첫 번째로 따옴표에 묶이지 않은 등호가 속성 이름과 값 사이를 나누었습니다. 이후의 등호는 모두 값에 속했습니다. 이름은 대소문자를 구분하지 않았습니다. 이름 쪽의 앞뒤 공백과 탭은 따옴표로 묶이지 않는 한 무시되었습니다. 이름 안에서 악센트 부호(백틱)는 등호나 다른 백틱을 인용할 수 있었습니다.

값은 다른 규칙을 따랐습니다. 인쇄 가능한 ASCII가 허용되었고, 이후의 등호는 일반 데이터로 취급되었으며, 모든 공백 문자는 요청자에게 그대로 반환되었습니다. 그 공백이 의미를 갖는지 여부는 애플리케이션의 결정이었습니다. 검증된 정오표 5193은 나중에 값 안에서 백틱을 잘못 이중화한 예제를 수정했습니다. 구분자 뒤에서는 백틱이 더 이상 특별한 인용 역할을 하지 않았습니다. 보류 중인 정오표 5194는 메커니즘 자체를 바꾸지 않고 다른 예제의 정렬만을 바로잡았습니다.

이러한 세부 사항은 단지 컴팩트한 문법을 만든 데 그치지 않았습니다. 결정론적인 파서 경계를 만든 것입니다. 조회 결과에서 따옴표로 묶이지 않은 등호가 없거나 속성 이름이 빈 문자열인 TXT 문자열은 무시할 수 있었습니다.gethostbyname과 유사한 헬퍼는 이름 쪽의 인용 문자를 제거하고 하나 이상의 속성을 반환할 수 있었습니다. 따라서 동일한 바이트가 어떤 파서에서는 수용되고, RFC 1464를 구현하지 않은 다른 파서에서는 무시되거나, 서로 다른 애플리케이션 프로파일 하에서 해석될 수 있었습니다.

DNS 응답은 특정 애플리케이션에 대해 이 동작들 중 어느 것이 옳은지를 정해 주지 않았습니다. DNS는 레코드 집합을 제공했고, 계약은 소비 측이 공급했습니다.

등록되지 않은 레지스트리는 각주가 아니었다

RFC 1464는 상호운용성을 개선하고 충돌을 줄이기 위해 잘 알려진 속성 이름의 등록이 필요할 수 있음을 인정했습니다. 주기적으로 갱신되는 목록이나, 공개된 객체 식별자 같은 다른 명명 메커니즘을 제안하기도 했습니다. 그리고 거기서 멈췄습니다. 이 문서는 속성 이름 등록을 명시적으로 정의하지 않았습니다.

이 누락은 제안의 초기 최소 면적을 보전했습니다. 서버는 새로운 타입 테이블이 필요 없었고, 실험은 이름을 사용하기 전에 허가가 필요 없었습니다. 그러나 비용은 사라지지 않았습니다. 비용은 두 게시자 또는 두 프로그램이 어떤 이름의 의미에 대해 합의해야 하는 모든 지점으로 옮겨졌습니다.

어떤 레코드에color=blue가 들어 있다고 가정해 봅시다. RFC 1464는 파서에게 이름이 어디서 끝나는지,COLOR가color와 일치하는지, 값이 남은 네 문자라는 것까지는 알려 줄 수 있습니다. 그러나color가 프린터를 지칭하는지, 사용자 선호 설정인지, 케이블인지, 상태 램프인지, 행정적 분류인지는 알려 줄 수 없습니다. 누가 그 속성을 주장할 자격이 있는지도 알 수 없습니다.blue가 통제된 어휘의 토큰인지 자유 서술인지도 말해 줄 수 없습니다. 소비 애플리케이션이 어떤 동작을 취해야 하는지 증명하지도 못합니다.

문법은 속성을 읽을 수 있게 만들었습니다. 스키마가 있었다면 속성은 상호운용 가능해졌을 것입니다.

TXT는 공용 컨테이너였지, 전용 네임스페이스가 아니었다

RFC 1035는 TXT RDATA를 하나 이상의 문자열로 정의하고, 그 의미론은 해당 레코드가 발견되는 도메인에 의존한다고 명시합니다. RFC 1464는 그 범용 운반체 안에 또 하나의 미니어처 타입 시스템을 넣었습니다. 속성 이름이 선택자 역할을 했지만, DNS 쿼리는 여전히 소유자, 클래스, 리소스 레코드 타입으로 선택했습니다. TXT를 요청한 클라이언트는 전체 TXT RRset을 받은 뒤 그 내용물 안을 검색할 수 있었습니다.

IAB의 RFC 5507은 나중에 이 접근의 구조적 약점을 서술했습니다. 하위 타입 체계는 클라이언트가 원하는 레코드를 선택하기 전에 완전한 RRset을 먼저 받아야 합니다. DNSSEC 서명이 완전한 RRset을 대상으로 하기 때문에, 한 애플리케이션의 변경이 공유된 집합 전체의 재서명을 요구할 수도 있습니다. TXT에 대해서는 문서가 더 직설적이었습니다. RFC 1464는 레코드 타입에 표준화된 선택자 필드가 없는 곳에서 선택자를 만들려고 시도했고, 그 시도는 성공이 아니었다고 말합니다.

이 판단은 아무도 RFC 1464를 구현하지 않았다는 뜻이 아닙니다. 범용 규약이 보편적인 의미 계층이 되지 못한 이유를 짚어 줄 뿐입니다. 하나의 소유자와 하나의 TXT 타입을 공유하는 서로 다른 애플리케이션은 페이로드 공간, 파서 가정, 운영상 한계에서 충돌할 수 있습니다. 한 소비자에게 무관해 보이는 문자열이 다른 소비자에게는 필수일 수 있습니다. 한 규약을 위해 작성된 파서는 어느 규약에도 속하지 않는 텍스트와 마주칠 수 있습니다.

캐시 항목은 시계가 달린 증거였다

속성을 DNS에 넣는 일은 DNS의 시간 개념도 함께 들여왔습니다. 권한 있는 존, 재귀 리졸버, 애플리케이션 캐시는 각각 레코드 생애의 서로 다른 시점을 노출할 수 있었습니다. 긍정 응답은 사람이 배후의 의도를 변경한 이후에도 TTL과 리졸버 동작에 따라 계속 재사용 가능했습니다.

따라서 응답에status=closed가 존재한다는 사실만으로는, 게시자가 언제 존을 변경했는지, 권한 있는 서비스가 언제 이를 서빙하기 시작했는지, 리졸버가 언제 가져왔는지, TTL이 얼마 남았는지, 애플리케이션이 언제 동작했는지 알 수 없습니다. 이후의 쿼리가 다른 바이트를 반환하더라도 이전 관찰이 사기였다는 뜻은 아닙니다. 반대로 오래된 캐시 항목은 프로토콜상으로 완벽히 유효하면서도, 빠르게 변하는 결정에는 운영적으로 낡은 것일 수 있습니다.

이것이 바로 “DNS가 그렇다고 했다”는 불완전한 감사 진술인 이유입니다. 유용한 수증에는 쿼리 이름, 클래스, 타입, 반환된 완전한 RRset, 관찰 시각, 남은 TTL, 리졸버 경로, 그리고 하나의 문자열을 선택한 파서 규칙이 필요합니다. 이 필드들이 없으면 그 한 문장은 여러 개의 시계를 시간 없는 주장 하나로 압축해 버립니다.

DNSSEC는 출처 경로를 보호하지, 그 주장 자체를 보호하지 않는다

RFC 1464는 보안 문제를 다루지 않는다고 밝혔습니다. 이후의 DNSSEC 아키텍처는 신뢰 사슬 하에서 출처를 인증하고 무결성을 보호하는 방법을 DNS에 제공했습니다. RFC 4033 역시 한계를 동등하게 명확히 밝힙니다. 이 확장은 기밀성을 제공하지 않습니다.

검증은 중요한 사실 하나를 추가합니다. 검증 결과는 RRset이 검증자가 수용하는 인증된 DNS 위임 경로를 따르며, 그 경로에서 탐지 불가능하게 변경되지 않았음을 보여 줄 수 있습니다. 그러나 존 운영자가 비즈니스, 안전 또는 신원 주장을 할 권한이 있었다는 증명은 되지 않습니다. 속성을 정의하지도 않습니다. 값이 물리적 세계와 일치함을 보여 주지도 않습니다. 운영적 의미가 바뀐 뒤에 캐시된 진술을 현재 것으로 만들지도 않습니다.

따라서 보안이 확보된 결과가 어떤 애플리케이션에는 문법적으로 유효하지 않을 수 있습니다. 문법적으로 유효하고 안전하게 검증된 결과도, 애플리케이션이 관심을 두는 세계에서는 거짓일 수 있습니다. 그리고 서명되지 않은 결과도, 자신의 더 약한 증거 지위를 기록하는 로컬 정책 하에서는 여전히 유용할 수 있습니다. 이것들은 하나의 보편적 진실 척도 위의 등급이 아니라 서로 다른 결정입니다.

이후 설계는 맥락을 좁혔다

DNS는 애플리케이션 데이터를 실는 것을 멈추지 않았습니다. 대신 그 데이터가 어디서, 어떻게 해석되는지를 제한하는 법을 배웠습니다.

RFC 6763은 DNS 기반 서비스 디스커버리(DNS-Based Service Discovery)를 위해 TXT 키/값 쌍을 사용하지만, 명명된 서비스 타입에 대한 PTR, SRV, TXT 규약 내부에서만 사용합니다. 각 키/값 쌍은 자신만의 구성 문자열을 차지합니다. 키는 대소문자를 구분하지 않고 서비스 프로파일 안에서 정의됩니다. 알려지지 않은 키는 무시됩니다. 중복 키에는 명시적인 첫 출현 규칙이 있습니다. 호스트와 포트는 TXT에 중복되지 않고 SRV에 남습니다. 이 문서는 애플리케이션 프로토콜이 기능을 직접 협상할 수 있을 때 TXT 정보를 최적화로 취급하기까지 합니다.

이것은 RFC 1464가 조용히 DNS-SD가 된 것입니다. 유사한 문장부호 주위에 더 작은 의미 세계를 정의한 이후의 애플리케이션입니다.

RFC 6950은 더 넓은 역사를 서술합니다. TXT는 애플리케이션이 새 RR 타입을 등록하지 않고 데이터를 추가할 수 있게 했지만, 한 애플리케이션의 레코드를 다른 애플리케이션의 레코드와 구분하는 일은 어려웠습니다. 특수화된 소유자 이름 구조는 TXT 페이로드가 해석되어야 하는 범위를 제한함으로써 모호성을 줄였습니다.

RFC 8552는 그 방향을 전역 범위의 밑줄(_) 노드 이름에 대해 정식화했습니다._domainkey.example같은 이름은 부모 아래에 구분 가능한 분기를 만듭니다. 밑줄 노드 이름과 RR 타입의 조합이 등록되므로, 직접 조회가 구분되지 않는 대량 대신 관련 RRset을 반환할 수 있습니다. 그런 경우에도 그 위치는 그곳에 나타나는 것에 대한 모든 규칙을 정의하지 않습니다. 그 일은 여전히 애플리케이션 명세가 합니다.

역사적 진행은 “비구조화”에서 “자기 설명적”으로 향한 것이 아닙니다. 범용 운반체에서 시작해 명시적인 범위, 레지스트리, 프로파일, 파서 동작의 계층으로 나아간 것입니다.

실행 코드만이 문장부호가 프로토콜이 되는지를 결정한다

RFC 1464는 실험적(Experimental) 문서였습니다. 네 페이지 분량의 이 문서는 등호를 포함하는 모든 TXT 문자열이 이제 인터넷 속성이 되었다고 선언하지 않았습니다. 규약과 가능한 라이브러리 인터페이스를 제공했을 뿐입니다. 실제 상호운용성은 양측이 호환되는 파싱을 구현하고 요청된 이름에 대한 정의를 공유해야만 성립했습니다.

프로토콜 역사를 읽을 때 이 구분은 중요합니다. 출판은 제안이 문서적 기록에 들어갔다는 증명입니다. 샘플 파서는 특정 입력이 변환될 수 있다는 증명입니다. 배포 증거는 어떤 프로그램이 어떤 속성을 어떤 이름에서 어떤 기간 동안 사용했는지 보여 줄 것입니다. 애플리케이션 수증은 파싱된 값 하나가 실제 결정에 영향을 주었다는 것을 보여 줄 것입니다. 어느 것도 다른 것을 대신할 수 없습니다.

따라서 RFC 1464의 지속적 가치는 채택 주장이 아닙니다. 운반체 계층에서의 호환성을, 운반체가 검증할 수 없는 계층에 의미론을 맡기는 대가로 구매한 그 명료한 거래의 명료성입니다.