사전 RFC 5905
표준

RFC 5905

gabury1고친 사람 github-actions[bot]

RFC 5905 는 네트워크로 이어진 컴퓨터들이 시계를 같은 시각에 맞추는 방법을 하나로 정해 줍니다. 시간 서버에 시각을 묻는 패킷의 모양부터 받은 답으로 시계를 고치는 계산까지 이 문서 한 편에 들어 있습니다. 오늘 널리 쓰는 NTP 버전 4가 이 문서를 따릅니다.

쉽고 빠른 이해

RFC 5905 는 컴퓨터가 시간 서버에 몇 시냐고 물어 자기 시계를 맞추는 규칙을 적은 문서입니다. 여러 서버의 로그에 찍힌 시각을 나란히 놓고 비교할 수 있는 것은 서버마다 이 규칙대로 시계를 맞췄기 때문입니다.

이런 문서가 없으면 프로그램마다 묻는 방식과 계산이 달라집니다. 같은 서버에 물어도 컴퓨터마다 다른 시각을 가리키게 됩니다.

어떻게 도나:

  1. 컴퓨터가 서버에 묻고, 서버는 요청을 받은 시각과 답한 시각을 실어 돌려줍니다
  2. 컴퓨터는 오간 시각 넷으로 왕복 시간과 내 시계가 어긋난 정도를 구합니다
  3. 여러 서버의 답을 비교해 틀린 답을 버리고, 남은 답으로 시계를 조금씩 고칩니다

대가도 있습니다. 시각을 32비트 초 필드에 적어서 2036년에 숫자가 한 바퀴 돌아 0 이 됩니다. 처음 문서에 실린 인증 방식은 오래된 해시를 써서, 뒤에 나온 문서들이 보안을 따로 덧붙였습니다.

모든 시계 맞추기에 쓰지는 않습니다. 한 건물 안 장비끼리 마이크로초보다 촘촘하게 맞춰야 하는 망은 정밀 시각 프로토콜을 따로 씁니다.

상세

이 절은 먼저 이 표준 문서의 번호와 상태, 담긴 내용을 봅니다. 이어서 받은 답이 시계를 고치기까지의 단계와 패킷의 모드 번호를 봅니다. 그다음 시각을 적는 형식, 2036년에 한 바퀴 도는 초 필드, 서버가 패킷 헤더로 보내는 윤초·계층·거절 신호를 차례로 봅니다. 마지막으로 이 문서가 대신한 문서와 이 문서를 고친 문서를 봅니다.

문서 번호와 상태

RFC(Request for Comments)는 인터넷 기술의 규칙을 번호를 붙여 펴내는 문서 묶음입니다. IETF(Internet Engineering Task Force, 인터넷 기술 태스크포스)가 이 문서들을 만듭니다. 5905 는 그중 한 편의 번호입니다.

한 번 나온 RFC 는 고쳐 쓰지 않습니다. 내용을 바꾸려면 새 번호로 새 문서를 냅니다. 새 문서는 옛 문서를 대신하거나 고친다고 밝힙니다. 그래서 RFC 하나를 읽을 때는 그 문서를 뒤에서 누가 고쳤는지도 함께 봐야 합니다.

RFC 5905 는 2010년 6월에 나왔습니다. 상태는 제안 표준(Proposed Standard)입니다. 표준으로 가는 RFC 가 처음 받는 등급입니다. 널리 쓰이는 문서도 이 등급에 오래 머무는 일이 많습니다.

NTP 가 하는 일

NTP(Network Time Protocol, 네트워크 시간 프로토콜)는 컴퓨터가 시간 서버에 시각을 물어 자기 시계를 맞추는 프로토콜입니다. 컴퓨터 시계는 혼자 두면 조금씩 빨라지거나 늦어집니다. 그래서 더 믿을 만한 시계에 물어 주기적으로 고쳐야 합니다.

묻고 답하는 메시지는 UDP(User Datagram Protocol, 사용자 데이터그램 프로토콜) 123번 포트로 오갑니다. 요청 한 번과 응답 한 번이 오가면 시각 넷이 모입니다. 아래 표는 그 넷에 이름을 붙여 찍히는 순서대로 늘어놓은 것입니다.

이름 찍는 쪽 찍는 때
시각 1 클라이언트 요청을 보낸 때
시각 2 서버 요청을 받은 때
시각 3 서버 답을 보낸 때
시각 4 클라이언트 답을 받은 때

시각 1 과 시각 4 는 클라이언트 시계로 잽니다. 시각 2 와 시각 3 은 서버 시계로 잽니다. 이 넷으로 왕복 시간과 두 시계의 차이를 구합니다. 차이 계산은 가는 길과 오는 길이 똑같이 걸렸다고 보고 셈합니다.

패킷 헤더에는 버전 번호를 적는 필드가 있습니다. RFC 5905 를 따르는 패킷은 그 필드에 4 를 적습니다. 이 문서를 흔히 NTPv4 라고 부르는 까닭입니다.

모든 시계 맞추기가 이 문서를 따르지는 않습니다. 한 건물 안의 장비끼리 마이크로초보다 촘촘하게 맞춰야 하면 PTP(Precision Time Protocol, 정밀 시각 프로토콜)를 씁니다. 경로 위의 스위치가 패킷이 지나간 시각을 직접 찍어 주는 방식이라 그런 장비를 갖춘 망에서만 돌아갑니다.

한 문서에 담긴 세 덩어리

RFC 5905 는 네트워크로 오가는 것과 각 컴퓨터 안에서 계산하는 것을 한 문서에 함께 적습니다. 덩어리는 셋입니다.

덩어리 담는 것
패킷 형식 패킷 헤더의 필드와 뜻 · 모드 번호 · 인증 코드 필드
시각 표현 시각을 적는 세 가지 형식 · 셈을 시작하는 기준 시점
알고리즘 받은 답을 거르고 고르고 합쳐서 시계를 고치는 절차

표의 셋째 줄이 문서 분량의 큰 몫을 차지합니다. 패킷 모양만 같으면 컴퓨터끼리 시각을 주고받을 수는 있습니다. 그런데 받은 답을 어떻게 쓰는지가 프로그램마다 다르면, 같은 서버에 물은 두 컴퓨터가 다른 시각을 가리킵니다.

그래서 이 문서는 계산 절차까지 적습니다. 부록에는 그 절차를 C 코드 뼈대로 옮긴 예제 프로그램을 붙였습니다.

받은 답이 시계에 닿기까지

서버에서 온 답 하나가 시계 조정으로 이어지기까지 다섯 단계를 거칩니다. 앞의 네 단계는 믿을 만한 값을 추려 냅니다. 마지막 단계가 그 값으로 시계를 움직입니다.

flowchart TD
    A["서버마다 도착한 답"] --> F["시계 필터 · 서버마다 최근 답 여덟 개 중 하나"]
    F --> S["선택 · 다수와 어긋난 서버를 버린다"]
    S --> C["클러스터 · 흔들림이 큰 서버를 더 뺀다"]
    C --> M["결합 · 남은 값에 가중치를 달리해 합친다"]
    M --> D["시계 보정 · 시계 속도와 시각을 고친다"]

시계 필터는 서버 하나에서 온 최근 답 여덟 개를 들고 있다가 왕복 시간이 가장 짧았던 답을 고릅니다. 왕복 시간이 길어진 것은 대개 패킷이 네트워크 어딘가에서 기다렸기 때문입니다. 그 기다림이 가는 길과 오는 길 가운데 한쪽에만 몰리면 두 길이 같다는 가정이 깨집니다. 왕복이 짧으면 기다린 시간이 적어서 차이 계산이 틀어질 여지도 작습니다.

서버의 답에는 저마다 오차 범위가 붙습니다. 그래서 답 하나는 시각 한 점이 아니라 그 앞뒤로 폭을 가진 시간 구간이 됩니다.

선택 단계는 여러 서버의 구간을 비교해 다수가 함께 겹치는 구간을 찾습니다. 그 구간에 들지 못한 서버는 틀린 시각을 내는 서버로 보고 버립니다. 살아남은 쪽을 truechimer, 버린 쪽을 falseticker 라고 부릅니다.

클러스터 단계는 살아남은 서버 가운데 답이 들쭉날쭉한 서버를 하나씩 더 뺍니다. 같은 서버에 되풀이해 물었을 때 값이 흔들리는 정도를 지터라고 부릅니다.

결합 단계는 남은 서버들의 값을 하나로 합칩니다. 기준 시계에 가깝고 오차 범위가 좁은 서버일수록 가중치를 더 줍니다.

시계 보정(clock discipline)은 합친 값을 받아 컴퓨터 시계를 고칩니다. 어긋남이 작으면 시계가 가는 속도를 살짝 바꿔 천천히 따라잡습니다. 어긋남이 크면 시각을 한 번에 옮깁니다. 매번 남은 오차를 보고 다음 조정을 정하는 피드백 고리입니다.

패킷의 모드 번호

패킷 헤더에는 보낸 쪽이 어떤 관계로 말을 거는지 적는 3비트 필드가 있습니다. 이 필드를 모드라고 부릅니다. 값은 여덟 가지입니다.

값 뜻
0 예약
1 대칭 능동 · 먼저 피어 관계를 맺자고 거는 쪽
2 대칭 수동 · 그 요청을 받아 피어가 된 쪽
3 클라이언트
4 서버
5 브로드캐스트
6 NTP 제어 메시지
7 개별 용도로 남겨 둠

가장 흔히 보는 값은 3 과 4 입니다. 클라이언트가 3 을 적어 묻고, 서버가 4 를 적어 답합니다.

1 과 2 는 서버 둘이 서로 시각을 주고받는 피어 관계입니다. 한쪽이 기준 시계를 잃어도 다른 쪽에서 시각을 이어받을 수 있습니다. 5 는 서버가 묻지 않은 컴퓨터들에게 시각을 뿌리는 방식입니다. 6 은 운영자가 서버 상태를 조회할 때 씁니다.

시각을 적는 세 가지 형식

NTP 의 시각은 기준 시점부터 흐른 초를 셉니다. 셈을 시작하는 시점을 에포크라고 부릅니다. NTP 의 에포크는 1900년 1월 1일 0시 UTC(Coordinated Universal Time, 협정 세계시)입니다. 1970년부터 세는 유닉스 시간과는 출발점이 70년 다릅니다.

시각을 적는 형식은 크기가 다른 셋입니다. 표의 시대 번호는 32비트 초 필드가 지금 몇 번째 바퀴를 도는지 셉니다. 한 바퀴는 약 136년입니다. 자세한 것은 아래 2036년 절에서 봅니다.

형식 크기 필드 구성 쓰임
짧은 형식 32비트 초 16 · 초 아래 16 기준 시계까지의 지연처럼 작은 값
타임스탬프 형식 64비트 초 32 · 초 아래 32 패킷에 싣는 시각
날짜 형식 128비트 시대 번호 32 · 시대 안의 초 32 · 초 아래 64 저장 공간이 넉넉한 계산

패킷에 가장 많이 실리는 것은 64비트 타임스탬프 형식입니다. 앞 32비트가 초입니다. 뒤 32비트는 1초를 2의 32제곱 조각으로 나눈 것 가운데 몇 조각인지를 적습니다.

packet-beta
0-31: "초 · 1900년 1월 1일부터"
32-63: "초 아래 · 1초의 2^32 분의 1 단위"

한 조각은 약 232피코초입니다. 1피코초는 1조분의 1초라서, 초 아래를 네트워크가 잴 수 있는 것보다 훨씬 잘게 적을 수 있습니다.

유닉스 시간을 NTP 의 초로 바꾸려면 두 기준 시점 사이의 70년을 초로 세어 더합니다.

unix = 0                  // 1970년 1월 1일
ntp  = unix + 2208988800  // 2208988800

더하는 값 2208988800 은 1900년부터 1969년까지 70년의 날수 25567 에 하루의 초 86400 을 곱한 값입니다. 날수에는 그 사이 윤년 열일곱 번의 하루씩이 들어 있습니다.

2036년에 한 바퀴 도는 초 필드

32비트 초 필드에 담을 수 있는 가장 큰 수는 4294967295 입니다. 여기에 1초를 더한 2의 32제곱 초는 약 136년입니다. 1900년부터 이만큼이 흐르는 때가 2036년 2월 7일 6시 28분 16초 UTC 입니다. 그 순간 초 필드는 다시 0 이 됩니다.

이 한 바퀴를 시대(era)라고 부릅니다. 1900년부터 2036년까지가 시대 0 이고, 그 뒤가 시대 1 입니다. 128비트 날짜 형식의 맨 앞 필드가 지금 몇 번째 시대인지를 담습니다.

패킷에 실리는 64비트 형식에는 시대 번호가 없습니다. 그래도 시계 맞추기는 대개 깨지지 않습니다. NTP 의 계산은 시각 하나가 아니라 두 시각의 차이를 쓰기 때문입니다.

두 값의 차이는 부호 있는 32비트 정수로 읽습니다. 두 시각이 서로 68년 안에 있으면, 초 필드가 한 바퀴 돌았어도 맞는 차이가 나옵니다. 2의 31제곱 초가 약 68년입니다.

한 바퀴 직전과 직후의 두 값으로 셈해 보면 이렇습니다. 앞 값은 초 필드가 0 이 되기 6초 전이고, 뒤 값은 0 이 된 지 4초 뒤입니다.

old  = 4294967290   // 0 되기 6초 전
new  = 4            // 0 된 지 4초 뒤
diff = new - old    // 10

그냥 빼면 -4294967286 이 나옵니다. 32비트 안에서 셈하면 여기에 2의 32제곱을 한 번 더한 것과 같아져 10 이 됩니다. 실제로 흐른 10초와 맞습니다.

어려운 것은 시계를 처음 켤 때입니다. 시계가 전혀 안 맞은 상태에서는 받은 64비트 값이 몇 번째 시대인지 가릴 단서가 없습니다. 그래서 구현은 대략 맞는 기준 날짜를 따로 들고 있다가 그 날짜에 가장 가까운 시대를 고릅니다.

유닉스 시간을 부호 있는 32비트 정수에 담은 프로그램이 2038년에 넘치는 2038 문제와 뿌리가 같습니다. NTP 는 세기 시작한 해가 70년 이릅니다. 대신 부호 없이 32비트를 다 써서 약 136년을 셉니다. 유닉스 시간은 부호 비트를 빼고 약 68년만 세므로, NTP 가 2년 먼저 한 바퀴를 돕니다.

윤초를 알리는 두 비트

패킷 헤더 맨 앞 두 비트는 Leap Indicator입니다. 윤초는 지구 자전과 원자시계의 어긋남을 메우려고 UTC 에 1초를 끼우거나 빼는 조정입니다. 서버는 그달 마지막 날의 마지막 분에 윤초가 있으면 이 두 비트로 미리 알립니다.

값 뜻
0 알릴 것 없음
1 마지막 분이 61초다
2 마지막 분이 59초다
3 알 수 없음 · 이 서버의 시계가 아직 맞지 않았다

값 3 은 윤초와 다른 뜻을 겸합니다. 이 서버가 아직 시계를 맞추지 못했으니 여기서 온 시각을 쓰지 말라는 신호입니다.

계층 번호와 거절 신호

패킷 헤더에는 이 서버가 기준 시계에서 몇 단계 떨어져 있는지를 적는 필드도 있습니다. 이 단계를 계층이라고 부릅니다. 원자시계나 위성 수신기처럼 시각의 원본이 되는 장비에서 한 다리 건널 때마다 숫자가 하나씩 커집니다.

값 뜻
0 지정 안 됨 · 거절 신호에 쓴다
1 기준 장비에 바로 붙은 1차 서버
2~15 NTP 로 윗단 서버에 맞춘 2차 서버
16 시계가 맞지 않음

패킷 헤더에는 참조 식별자라는 32비트 필드도 있습니다. 평소에는 이 서버가 시각을 받아 오는 출처를 적습니다. 1차 서버는 기준 장비의 종류를 영문 네 글자 안으로 적습니다. 2차 서버는 윗단 서버의 주소를 적습니다.

계층 필드가 0 인 패킷은 시각을 알리는 답이 아닙니다. 서버가 클라이언트를 물리려고 보내는 신호입니다. 이 패킷을 Kiss-o'-Death 패킷이라고 부릅니다.

이 패킷의 참조 식별자 필드에는 출처 대신 무슨 뜻인지를 영문 네 글자로 적습니다. 자주 보는 코드는 DENY(Deny, 거부) · RSTR(Restricted, 제한) · RATE(Rate, 빈도) 셋입니다.

아래 설명에서 괄호 안의 MUST 는 반드시 지켜야 하는 요구라는 뜻입니다. 이 낱말의 뜻을 모아 둔 문서가 RFC 2119 입니다.

코드 뜻 클라이언트가 할 일
DENY 접근 거부 그 서버 몫으로 들고 있던 기록(최근 답 등)을 지우고 더는 보내지 않는다(MUST)
RSTR 서버 정책으로 접근 제한 DENY 와 같다(MUST)
RATE 너무 자주 물었다 묻는 간격을 곧바로 늘린다(MUST)

공개 시간 서버 하나에는 셀 수 없이 많은 클라이언트가 붙습니다. 서버가 과부하를 피하려면 클라이언트를 물러서게 할 수단이 있어야 합니다. 표의 MUST 는 그 신호를 받은 클라이언트가 무시하지 못하게 못박은 것입니다.

이 문서가 대신한 문서

RFC 5905 는 두 문서를 대신합니다. RFC 1305 는 NTP 버전 3 입니다. RFC 4330 은 간이 버전인 SNTP(Simple Network Time Protocol, 간이 네트워크 시간 프로토콜)를 적은 문서입니다.

SNTP 는 서버 여럿을 비교하는 절차 없이 서버 하나의 답을 바로 쓰는 방식입니다. 계산 여력이 적은 작은 장비가 씁니다. RFC 5905 에서는 이 간이 버전의 규칙이 한 절로 들어왔습니다.

버전 4는 버전 3과 주고받을 수 있게 만들었습니다. 그래서 버전 3 서버와 버전 4 클라이언트가 섞여 있어도 시계를 맞춥니다. 버전 3에서 달라진 것은 넷입니다.

달라진 것 내용
IPv6(Internet Protocol version 6, 인터넷 프로토콜 버전 6) 패킷 헤더를 고쳐 IPv6 주소를 쓰는 서버도 다룬다
알고리즘 답을 거르고 시계를 고치는 절차를 손봤다
서버 자동 탐색 서버 목록을 미리 적지 않아도 네트워크에서 서버를 찾아 붙는다
Autokey 공개 키로 서버를 확인하는 인증 방식을 더했다

IPv6 는 주소가 128비트라 참조 식별자의 32비트 필드에 들어가지 않습니다. MD5(Message-Digest algorithm 5)는 입력을 128비트 값으로 줄이는 해시 함수입니다. 윗단 서버가 IPv6 주소이면 참조 식별자에는 그 주소를 MD5 로 줄인 값의 앞 4바이트를 적습니다.

Autokey 절차는 RFC 5905 본문이 아니라 같은 달에 나온 RFC 5906 이 따로 담습니다.

이 문서를 고친 문서

RFC 5905 에 실린 기본 인증은 메시지 인증 코드입니다. 서버와 클라이언트는 같은 키를 미리 나눠 갖습니다. 패킷 끝에는 키 번호 32비트와 MD5 로 만든 확인 값을 붙입니다. 받는 쪽은 같은 키로 값을 다시 만들어 비교합니다.

MD5 는 같은 값을 내는 두 입력을 일부러 만드는 공격이 알려진 해시입니다. 그래서 뒤에 나온 문서가 이 대목을 고쳤습니다. RFC 5905 를 고친 문서는 아래와 같습니다.

문서 고친 내용
RFC 7822 기본 헤더 뒤에 덧붙여 인증 정보 같은 추가 데이터를 싣는 확장 필드의 길이와 배치 규칙
RFC 8573 인증 코드 계산에서 MD5 를 물리고 AES-CMAC(Advanced Encryption Standard - Cipher-based Message Authentication Code)을 쓰게 했다
RFC 9109 클라이언트가 보내는 출발 포트를 무작위로 고르게 했다

AES-CMAC 은 블록 암호 AES 로 만드는 메시지 인증 코드입니다.

출발 포트를 무작위로 고르는 까닭은 가짜 응답을 막으려는 것입니다. 클라이언트는 자기가 보낸 출발 포트로 돌아온 응답만 받아들입니다. 그래서 가짜 응답도 그 포트를 맞혀야 받아들여집니다. 포트가 늘 같으면 밖에서 이 포트를 쉽게 맞힙니다.

RFC 9748 과 RFC 9769 도 RFC 목록에 이 문서를 고친 문서로 올라 있습니다. 고친 내용은 미확인입니다.

키를 미리 나눌 필요 없이 서버를 확인하는 NTS(Network Time Security, 네트워크 시각 보안)는 이 문서를 고치지 않고 별도 문서 RFC 8915 로 나왔습니다. 암호화 연결로 키를 먼저 맞춘 뒤 그 키로 NTP 패킷을 보호합니다.

관련 항목

RFC 5905 가 정하는 프로토콜과 나란히 서는 시각 프로토콜

NTP · SNTP · NTS · PTP · Autokey · 브로드캐스트

RFC 5905 앞뒤로 NTP 를 규정한 표준 문서

RFC 1305 · RFC 4330 · RFC 5906 · RFC 7822 · RFC 8573 · RFC 9109 · RFC 9748 · RFC 9769 · RFC 8915

RFC 5905 가 기대는 표준화 체계

RFC · IETF · RFC 2119 · 제안 표준 · 표준 트랙 · IANA

RFC 5905 패킷 헤더에 담기는 필드

Leap Indicator · stratum · 참조 식별자 · NTP 타임스탬프 · 루트 지연 · 루트 분산 · 폴링 간격 · Kiss-o'-Death · 확장 필드

RFC 5905 알고리즘이 답을 거르고 시계를 고치는 단계

시계 필터 · Marzullo 알고리즘 · 클러스터 알고리즘 · 시계 보정 · 위상 고정 루프 · 주파수 고정 루프 · 지터 · 오프셋 · 왕복 시간

RFC 5905 가 시각을 셀 때 쓰는 기준

UTC · 에포크 · 타임스케일 · 유닉스 시간 · 윤초 · 윤년 · NTP 시대 · 2038 문제

NTP 패킷을 지키는 인증 수단

메시지 인증 코드 · MD5 · AES-CMAC · 대칭 키 · 포트 무작위화

NTP 를 실어 나르는 네트워크 프로토콜

UDP · 포트 · IPv4 · IPv6 · 패킷

RFC 5905 를 구현한 프로그램

ntpd · chrony · systemd-timesyncd · OpenNTPD

RFC 5905 가 속하는 상위 분류

시간과 시계 · 시계 동기화 · 프로토콜 · 네트워크 · 분산 시스템

다른 이름: NTPv4 · NTP 4판 · NTP 버전 4 · Network Time Protocol Version 4