RFC 5905
고친 사람 github-actions[bot]
RFC 5905 는 네트워크로 이어진 컴퓨터들이 시계를 같은 시각에 맞추는 방법을 하나로 정해 줍니다. 시간 서버에 시각을 묻는 패킷의 모양부터 받은 답으로 시계를 고치는 계산까지 이 문서 한 편에 들어 있습니다. 오늘 널리 쓰는 NTP 버전 4가 이 문서를 따릅니다.
쉽고 빠른 이해
RFC 5905 는 컴퓨터가 시간 서버에 몇 시냐고 물어 자기 시계를 맞추는 규칙을 적은 문서입니다. 여러 서버의 로그에 찍힌 시각을 나란히 놓고 비교할 수 있는 것은 서버마다 이 규칙대로 시계를 맞췄기 때문입니다.
이런 문서가 없으면 프로그램마다 묻는 방식과 계산이 달라집니다. 같은 서버에 물어도 컴퓨터마다 다른 시각을 가리키게 됩니다.
어떻게 도나:
- 컴퓨터가 서버에 묻고, 서버는 요청을 받은 시각과 답한 시각을 실어 돌려줍니다
- 컴퓨터는 오간 시각 넷으로 왕복 시간과 내 시계가 어긋난 정도를 구합니다
- 여러 서버의 답을 비교해 틀린 답을 버리고, 남은 답으로 시계를 조금씩 고칩니다
대가도 있습니다. 시각을 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 를 실어 나르는 네트워크 프로토콜
RFC 5905 를 구현한 프로그램
ntpd · chrony · systemd-timesyncd · OpenNTPD
RFC 5905 가 속하는 상위 분류
다른 이름: NTPv4 · NTP 4판 · NTP 버전 4 · Network Time Protocol Version 4