NTP
고친 사람 github-actions[bot]
NTP 는 네트워크로 이어진 컴퓨터들의 시계를 같은 시각에 맞춰 줍니다. 시간 서버에 지금 몇 시냐고 묻고, 답이 오가는 데 걸린 시간을 걷어 내 내 시계가 얼마나 어긋났는지 알아냅니다. 알아낸 어긋남은 대개 시계가 가는 빠르기를 조금 바꿔 서서히 메웁니다.
쉽고 빠른 이해
NTP 는 컴퓨터 시계를 시간 서버의 시각에 맞추는 규칙입니다. 위성 수신기를 단 서버에서 시작한 시각이 서버를 몇 다리 건너 내 컴퓨터까지 내려옵니다.
컴퓨터 시계는 혼자 두면 조금씩 빨라지거나 늦어집니다. 가장 크게 벌어지는 경우로 잡으면 하루에 1초 넘게 벌어집니다. 암호 키의 유효 기간처럼 시계가 맞아야 판단할 수 있는 일이 많습니다.
어떻게 도는가:
- 서버에 한 번 묻고 답을 받습니다. 내가 보낸 시각, 서버가 받은 시각, 서버가 답한 시각, 내가 받은 시각 넷이 모입니다
- 네 시각을 더하고 빼서 왕복에 걸린 시간과 내 시계의 어긋남을 구합니다
- 서버 여럿의 답을 견주어 혼자 튀는 답을 버리고 나머지를 합칩니다
- 어긋남이 작으면 조금씩 서서히 메우고, 크면 시각을 한 번에 옮깁니다
대가도 있습니다. 가는 길과 오는 길이 똑같이 걸린다고 가정해서, 한쪽만 느리면 그만큼 틀립니다. 기본 형태는 답한 서버가 진짜인지 확인하지 않습니다.
상세
NTP(Network Time Protocol, 네트워크 시간 프로토콜)는 시간 서버들이 시각 정보를 나눠 각 컴퓨터의 시계를 UTC(Coordinated Universal Time, 협정 세계시)에 맞추는 프로토콜입니다. 목표는 둘입니다. 시스템 시계와 UTC 사이의 시각 차이를 줄이고, 시계가 가는 빠르기의 차이도 줄입니다. 두 차이가 허용 범위 아래로 내려오면 그 시계가 UTC 에 동기화됐다고 말합니다.
아래 순서로 봅니다. 시계가 왜 혼자 벌어지나, 메시지가 무엇을 타고 오가나, 시각이 어떤 사슬을 따라 내려오나, 시각을 어떤 꼴로 담나, 한 번 재면 어떤 값이 나오나, 그 값이 시계에 닿기까지 어떤 공정을 거치나입니다. 끝에 기능을 덜어 낸 갈래 SNTP(Simple Network Time Protocol, 간이 네트워크 시간 프로토콜)를 짧게 짚습니다.
시계가 혼자 벌어지는 까닭
시스템 시계는 하드웨어와 운영체제가 유지합니다. 그 바탕인 발진기의 주파수가 조금씩 어긋나 있어서, 시계는 아무 일이 없어도 UTC 보다 빨라지거나 늦어집니다. 이 빠르기의 어긋남을 주파수 오프셋이라고 부릅니다.
NTP 는 시각을 받아 오던 서버를 모두 잃고 혼자 가는 시계가 가장 나쁘게 벌어지는 빠르기를 주파수 허용 오차 15ppm(parts per million, 백만분율)으로 잡습니다. 1초에 15 마이크로초씩 벌어지는 빠르기입니다. 하루로 치면 1.3초 남짓입니다.
시계가 이만큼 흔들리면 곤란한 일이 생깁니다. 암호 키처럼 정해진 기간에만 유효한 값은 시계가 믿을 만해야 그 기간을 지킬 수 있습니다. 거꾸로 시계를 맞추려면 시각을 보내 주는 쪽을 믿어야 합니다. 시각과 인증이 서로에게 기대는 셈입니다.
UDP 123 번 포트로 오가는 메시지
NTP 패킷은 UDP(User Datagram Protocol, 사용자 데이터그램 프로토콜) 데이터그램 하나에 실립니다. 서버는 123 번 포트에서 기다립니다.
NTP 의 주고받기는 잰 시각 차이에만 기대고, 메시지가 반드시 닿기를 요구하지 않습니다. TCP(Transmission Control Protocol, 전송 제어 프로토콜)처럼 재시도하는 전송을 쓰면 재시도가 지연 값과 그 밖의 오차를 키웁니다. 도착한 패킷이 오히려 덜 믿을 만해질 수 있습니다.
흐름 제어도 재전송도 두지 않습니다. 패킷이 사라지거나 두 번 와도 견디도록 짜여 있습니다. 데이터가 깨졌는지는 IP(Internet Protocol, 인터넷 프로토콜)와 UDP 의 체크섬이 봅니다.
클라이언트가 보내는 쪽 포트는 다릅니다. 클라이언트의 출발 포트는 무작위로 골라야 합니다(SHOULD). 포트 번호를 짐작할 수 있으면 경로 밖의 공격자가 위조 패킷을 맞춰 보내기 쉬워지기 때문입니다.
기준 시계에서 내려오는 계층
시각의 뿌리는 UTC 로 거슬러 올라갈 수 있는 기준 시계입니다. GPS(Global Positioning System, 위성 위치 확인 시스템)나 갈릴레오 같은 위성 항법 수신기가 그런 기준 시계입니다. 기준 시계에 직접 맞춘 서버를 주 서버라고 부릅니다.
NTP 를 쓰는 기계는 셋 중 하나로 일합니다.
| 역할 | 시각을 받는 곳 | 시각을 나눠 주는 곳 |
|---|---|---|
| 주 서버 | 기준 시계 | 2차 서버 · 클라이언트 |
| 2차 서버 | 위쪽 서버 하나 이상 | 아래쪽 서버나 클라이언트 하나 이상 |
| 클라이언트 | 위쪽 서버 하나 이상 | 없다 |
위쪽에서 시각을 받아 오는 서버를 원천이라고도 부릅니다. 뒤에서 「원천 넷」이라고 적은 것은 서버 넷을 뜻합니다.
시각은 주 서버에서 2차 서버를 거쳐 클라이언트로 흐릅니다. 몇 다리 내려왔는지를 계층(stratum) 번호로 적습니다. 프로토콜을 쌓는 네트워크 계층과는 다른 뜻입니다.
주 서버가 계층 1 입니다. 기준 시계 자신은 대개 계층 0 으로 나타납니다.
flowchart TD
S0["계층 0 · 기준 시계 · GPS 수신기"]
S1["계층 1 · 주 서버"]
S2["계층 2 · 2차 서버"]
S3["계층 3 · 2차 서버 · 클라이언트"]
S0 --> S1
S1 --> S2
S2 --> S3
한 단계 내려갈 때마다 번호가 하나씩 커집니다. 번호가 커질수록 경로와 시계 사정에 따라 정확도가 떨어집니다. 평균 오차는 대략 계층 번호와 왕복 지연에 비례해 커집니다.
패킷의 Stratum 칸에 실리는 값은 이렇게 갈립니다.
| 값 | 뜻 |
|---|---|
| 0 | 정해지지 않았거나 무효 |
| 1 | 주 서버 |
| 2~15 | 2차 서버 |
| 16 | 동기화 안 됨 |
| 17~255 | 예약 |
받은 패킷의 0 을 안에서는 16 으로 바꿔 읽고, 내보낼 때는 16 이상을 0 으로 바꿔 싣는 것이 관례입니다. 그래서 아직 시계를 못 맞춘 서버는 안에서는 계층 16 이지만 패킷에는 0 을 싣습니다.
사슬의 모양은 미리 정해 두지 않습니다. 주 서버를 뿌리로 삼고 거기까지 가장 짧은 길만 남긴 나무를 계산합니다. 그 나무를 최단 경로 신장 트리라 부릅니다. 시각은 이 나무를 따라 내려갑니다.
계산은 라우팅에 쓰는 벨만-포드 알고리즘을 고쳐 쓴 것입니다. 중간 서버가 죽어도 나무가 저절로 다시 짜입니다.
NTP 가 내는 정밀도는 대개 이 정도입니다. 아래의 폴 간격은 클라이언트가 서버에 한 번 묻고 다시 묻기까지 기다리는 시간입니다.
| 어디서 | 대개 맞추는 범위 |
|---|---|
| 요즘 기계로 돌리는 주 서버 | 수십 마이크로초 |
| 고속 근거리 망 위의 2차 서버와 클라이언트, 폴 간격 1024초까지 | 수백 마이크로초 |
| 폴 간격을 36시간까지 늘린 서버와 클라이언트 | 수십 밀리초 |
시각을 담는 64비트 타임스탬프
패킷에 실리는 시각은 64비트 타임스탬프입니다. 앞 32비트가 초, 뒤 32비트가 초 아래 소수입니다.
초 칸은 1900년 1월 1일 0시 UTC 를 0 으로 세기 시작합니다. 유닉스 시간이 0 으로 세는 1970년 1월 1일이 이 눈금으로 2,208,988,800 초입니다. 32비트 초 칸은 136년을 담습니다. 소수 칸은 232 피코초까지 가릅니다.
136년을 다 채우면 초 칸이 0 으로 돌아갑니다. 그날이 2036년 2월 8일입니다. 1900년부터 그날까지를 시대(era) 0, 그 뒤를 시대 1 이라고 부릅니다. 타임스탬프 값 0 은 따로 「모르거나 동기화 안 된 시각」을 뜻합니다.
block-beta columns 4 axis["초 칸이 세는 시간 축 — 왼쪽이 이른 시각"]:4 e0["시대 0"]:3 e1["시대 1"]:1 m0["1900-01-01 · 0초"] m1["1970-01-01 · 2,208,988,800초"] m2["2036-02-08 · 초 칸이 0 으로 돌아간다"] m3["다시 0 부터 센다"]
시대 번호는 패킷에 안 실립니다. 두 타임스탬프의 차이만 계산에 쓰기 때문에, 시작 전에 클라이언트 시계가 서버와 68년 안에 맞춰져 있으면 이웃한 시대에 걸쳐도 바른 차이 값이 나옵니다. 그 차이를 다시 더하고 빼는 오프셋 계산에서는 이 범위가 더 좁아집니다. 좁아진 범위는 「실패」의 마지막 소절에서 봅니다.
한 번 잴 때 나오는 네 값
클라이언트가 서버와 한 번 주고받을 때마다 통계 넷이 새로 나옵니다.
| 이름 | 기호 | 무엇인가 |
|---|---|---|
| 오프셋(offset) | θ | 서버 시계가 내 시스템 시계보다 얼마나 앞서 있나. 가장 그럴듯한 추정값이다 |
| 지연(delay) | δ | 클라이언트와 서버 사이의 왕복 지연 |
| 분산(dispersion) | ε | 그 측정에 들어 있을 수 있는 최대 오차. 시간이 지나면 주파수 허용 오차의 빠르기로 커진다 |
| 지터(jitter) | ψ | 최근 오프셋 차이들의 RMS(root-mean-square, 제곱평균제곱근). 오프셋을 추정할 때의 보통 오차다 |
분산은 통계에서 말하는 분산과 다른 뜻입니다. 오차의 상한을 가리킵니다. 15ppm 으로 잡으면 하루에 1.3초쯤 불어납니다.
지연과 분산은 기준 시계에서부터 계층마다 쌓입니다. 쌓인 값을 루트 지연(root delay)과 루트 분산(root dispersion)이라 부르고, 패킷에 실어 아래로 넘깁니다. 이 둘로 동기화 거리(synchronization distance)를 냅니다.
동기화 거리 = 루트 분산 + (루트 지연 / 2)
동기화 거리는 모든 원인을 합친 최대 오차를 나타냅니다. 서버를 고르고 버리는 판단이 이 값을 씁니다.
받은 시각이 시계에 닿는 공정
서버에 묻는 일을 폴(poll)이라 합니다. 서버 하나마다 프로세스가 둘 붙습니다. 묻는 쪽이 폴 프로세스, 답을 받는 쪽이 피어 프로세스입니다. 둘이 함께 쓰는 서버별 상태 묶음을 연결(association)이라 부릅니다. 연결은 설정으로 미리 띄워 두기도 하고, 맞는 연결이 없는 패킷이 왔을 때 그때 띄우기도 합니다.
서버들에게서 받은 값은 아래 공정을 거쳐 시스템 시계에 닿습니다.
flowchart TD
A["피어 프로세스 · 서버마다 오프셋과 지연을 잰다"]
B["시계 필터 · 최근 표본 8개에서 고른다"]
C["선택 알고리즘 · 거짓 시계를 버린다"]
D["군집 알고리즘 · 튀는 후보를 덜어 낸다"]
E["결합 알고리즘 · 남은 값을 가중 평균한다"]
F["시계 보정 알고리즘 · 시각과 빠르기를 고친다"]
A --> B
B --> C
C --> D
D --> E
E --> F
시계 필터(clock filter)는 서버마다 최근 표본 8개를 시프트 레지스터에 둡니다. 표본 하나는 오프셋·지연·분산·도착 시각 묶음입니다. 표본을 지연이 작은 순으로 늘어놓고 정확할 가능성이 가장 큰 것을 고릅니다.
선택 알고리즘(selection algorithm)은 서버들을 견주어 거짓 시계(falseticker)를 버립니다. 거짓 시계는 오해를 부르거나 앞뒤가 안 맞는 시각을 내는 시계입니다. 남는 것은 정직한 시계(truechimer), 곧 믿을 만한 기준에 맞게 시각을 지키는 시계입니다.
군집 알고리즘(cluster algorithm)은 남은 후보 가운데 통계적으로 중심에서 가장 먼 것을 한 라운드에 하나씩 덜어 냅니다. 남은 수가 최소 생존 수 3 이하가 되거나, 더 덜어도 나아질 게 없으면 멈춥니다. 맨 앞에 남은 후보가 시스템 피어(system peer)가 됩니다. 시스템 피어는 이 기계가 아래로 넘길 계층 번호와 루트 지연 같은 값을 물려받는 대표 서버입니다.
결합 알고리즘(combine algorithm)은 남은 후보의 오프셋을 가중 평균해 최종 오프셋 하나를 냅니다. 무게는 각 서버의 동기화 거리의 역수입니다. 동기화 거리가 짧은 서버일수록 무겁습니다.
시계 보정 알고리즘(clock discipline algorithm)은 이 최종 오프셋으로 시스템 시계의 시각과 빠르기를 함께 고치는 되먹임 제어입니다. 1초마다 도는 시계 조정 프로세스가 계산된 보정량을 조금씩 넣습니다. 보정량이 문턱 아래면 adjtime() 처럼 시각을 조금씩 앞당기거나 늦추는 함수를 쓰고, 문턱 위면 settimeofday() 처럼 시각을 바로 정하는 함수를 씁니다.
단순한 갈래 SNTP
SNTP 는 NTP 의 부분집합입니다. 위에서 본 선택·군집·결합 같은 알고리즘을 갖추지 않아도 됩니다.
기준 시계 하나만 단 주 서버, 또는 위쪽 서버 하나에만 묻고 아래로 나눠 주지 않는 클라이언트를 위한 것입니다. 이 차이 말고는 NTP 와 섞여 돌 수 있습니다. 가장 단순한 SNTP 클라이언트는 서버가 답을 보낸 시각 하나만 쓰고 나머지 칸은 버립니다.
교환 순서
여기서 다루는 것은 클라이언트와 서버 사이의 한 왕복입니다. 요청 한 통과 응답 한 통이 오가면 타임스탬프 넷이 모입니다. 그 넷으로 오프셋과 지연이 나옵니다. 어느 칸에 어느 시각이 언제 채워지나를 차례로 봅니다.
클라이언트가 보내는 요청은 Mode 칸이 3(client)입니다. 서버가 돌려주는 응답은 4(server)입니다. 서버는 클라이언트마다 상태를 남기지 않습니다. 받은 패킷의 주소와 포트를 맞바꾸고, 몇 칸을 고쳐 써서 곧바로 돌려줍니다.
타임스탬프 칸 셋과 도착 시각
패킷 머리에 타임스탬프 칸이 셋 있고, 넷째 시각은 받는 쪽이 따로 찍습니다.
| 칸 | 응답에서 담는 시각 |
|---|---|
| Origin Timestamp | 요청이 클라이언트를 떠난 시각 · T1 |
| Receive Timestamp | 요청이 서버에 닿은 시각 · T2 |
| Transmit Timestamp | 응답이 서버를 떠난 시각 · T3 |
| Destination Timestamp | 응답이 클라이언트에 닿은 시각 · T4. 머리 칸이 아니라 도착할 때 클라이언트가 찍어 둔다 |
클라이언트는 연결마다 상태 변수 셋을 들고 있습니다. 클라이언트가 마지막으로 보낸 시각 xmt, 마지막으로 받은 응답의 Transmit Timestamp 인 org, 그 응답이 닿은 시각 rec 입니다. 아래 검사가 이 변수들을 씁니다.
한 왕복의 차례
- 클라이언트가 mode 3 요청을 보냅니다. 보내는 순간 시스템 시계에서 T1 을 찍어 Transmit Timestamp 칸에 싣고, 같은 값을 xmt 에 적어 둡니다
- 서버가 T2 에 요청을 받습니다. 맺어 둔 연결이 없는 mode 3 패킷이라 상태를 남기지 않고 바로 응답을 만듭니다
- 서버가 T3 에 mode 4 응답을 보냅니다. 요청의 Transmit Timestamp(T1)를 Origin Timestamp 칸으로, 요청이 닿은 시각 T2 를 Receive Timestamp 칸으로 옮깁니다. 보내는 순간의 시각 T3 은 Transmit Timestamp 칸에 찍습니다
- 클라이언트가 T4 에 응답을 받습니다. 타임스탬프 검사 둘을 통과하면 T1~T4 로 오프셋과 지연을 계산합니다
sequenceDiagram
participant 클라이언트
participant 서버
Note over 클라이언트: T1 을 찍고 xmt 에 적어 둔다
클라이언트->>서버: mode 3 · Transmit Timestamp = T1
Note over 서버: T2 에 받는다 · 상태를 안 남긴다
서버-->>클라이언트: mode 4 · Origin = T1 · Receive = T2 · Transmit = T3
Note over 클라이언트: T4 에 받는다 · 검사 둘 · 오프셋과 지연 계산
왕복은 한 번입니다. 요청 한 통과 응답 한 통으로 측정 하나가 끝납니다.
오프셋과 지연을 내는 식
오프셋과 지연은 T1~T4 네 값만으로 나옵니다. 식은 둘입니다.
theta = 1/2 * [(T2-T1) + (T3-T4)]
delta = (T4-T1) - (T3-T2)
지연 δ(delta) 는 전체 왕복 시간 T4−T1 에서 서버가 붙들고 있던 시간 T3−T2 를 뺀 값입니다. 오프셋 θ(theta) 는 가는 길에서 잰 차이 T2−T1 과 오는 길에서 잰 차이 T3−T4 의 평균입니다. θ 가 양수면 서버 시계가 클라이언트 시계보다 앞서 있다는 뜻입니다.
빠르기가 크게 어긋나고 전파 시간이 짧으면 지연이 음수로 나올 수 있습니다. 빠르기 차이가 100ppm 이고 T4−T1 이 64초면 겉보기 지연이 −6.4 밀리초입니다. 음수는 뒤 계산을 헷갈리게 하므로, 지연을 시스템 정밀도 아래로 내려가지 않게 묶어 둡니다(should).
받은 패킷을 걸러 내는 일곱 검사
받은 패킷은 일곱 가지 검사를 거칩니다. 중복된 패킷, 가짜 패킷, 누가 다시 틀어 준 패킷을 막기 위해서입니다.
| 번호 | 검사 | 걸리는 패킷 |
|---|---|---|
| 1 | 중복(duplicate) | 오래된 복사본이거나 누가 다시 틀어 준 것 |
| 2 | 가짜(bogus) | 내가 보낸 요청에 대한 답이 아닌 것 |
| 3 | 무효(invalid) | 타임스탬프 칸 하나 이상이 무효한 것 |
| 4 | 접근 거부(access denied) | 접근 통제가 막아 둔 상대에게서 온 것 |
| 5 | 인증 실패(authentication failure) | 암호 다이제스트가 패킷에 붙은 인증 코드와 맞지 않는 것 |
| 6 | 미동기(unsynchronized) | 서버가 유효한 원천에 맞춰져 있지 않은 것 |
| 7 | 머리 칸 오류(bad header data) | 머리 칸 하나 이상이 무효한 것 |
앞의 둘은 받은 타임스탬프를 클라이언트가 들고 있는 상태 변수와 맞춰 가립니다. 상태 변수를 고치기 전에 봅니다.
| 검사 | 걸리는 조건 | 뜻 |
|---|---|---|
| 중복(duplicate) | 응답의 Transmit Timestamp 가 org 에 적어 둔 값과 같다 | 같은 응답이 두 번 왔거나 누가 다시 틀었다 |
| 가짜(bogus) | 응답의 Origin Timestamp 가 xmt 에 적어 둔 T1 과 다르다 | 내 요청에 대한 답이 아니다 |
둘 중 하나라도 걸리면 상태 변수만 고치고 패킷은 버립니다. 가짜 검사를 통과하면 xmt 를 곧바로 0 으로 지웁니다. 그래야 마지막 응답을 누가 다시 틀어도 걸립니다.
이 짜임은 서버 응답을 다시 트는 공격은 막지만 클라이언트 요청을 다시 트는 공격은 못 막습니다. 다시 틀린 요청을 받은 서버는 새 T2·T3 을 담아 답합니다. 그러면 오프셋과 지연이 틀어집니다. xmt 를 지우는 시점을 오프셋과 지연을 계산한 뒤로 미루면 이 공격도 피할 수 있습니다.
먼저 보내는 쪽을 가르는 모드
Mode 칸은 3비트이고 값마다 뜻이 정해져 있습니다.
| 값 | 뜻 |
|---|---|
| 0 | 예약 |
| 1 | symmetric active · 대칭 능동 |
| 2 | symmetric passive · 대칭 수동 |
| 3 | client |
| 4 | server |
| 5 | broadcast |
| 6 | NTP 제어 메시지 |
| 7 | 사설 용도로 예약 |
주고받는 방식은 세 갈래입니다. 갈래마다 누가 먼저 보내나가 다릅니다.
| 갈래 | 먼저 보내는 쪽 | 받는 쪽 |
|---|---|---|
| 클라이언트/서버 | 클라이언트가 mode 3 을 보내 시각을 당겨 온다 | 서버가 mode 4 로 답한다. 클라이언트의 시각은 받아들이지 않는다 |
| 대칭 | 미리 설정한 쪽이 mode 1 을 보낸다 | 맞는 연결이 없으면 그때 연결을 새로 띄우고 mode 2 로 답한다. 둘이 서로 밀고 당긴다 |
| 브로드캐스트 | 서버가 mode 5 를 주기적으로 뿌린다 | 클라이언트는 듣기만 한다 |
브로드캐스트 클라이언트는 처음에 짧게 mode 3 으로 주고받아 전파 지연을 재고 나서 듣기로 돌아서야 합니다(SHOULD).
서버를 미리 몰라도 되는 manycast 도 있습니다. 클라이언트가 멀티캐스트 그룹 주소로 mode 3 을 보내고, 범위 안의 서버가 클라이언트의 유니캐스트 주소로 mode 4 를 답합니다. 클라이언트는 패킷의 수명 값을 1 부터 하나씩 늘려 가며 필요한 수의 서버를 찾습니다.
폴 간격과 연달아 묻기
클라이언트는 2^τ 초마다 서버에 묻습니다. 이 간격이 폴 간격입니다. τ 는 폴 지수라 부릅니다. 값은 4(16초)에서 17(36시간) 사이에서 움직입니다. 기본 하한과 상한으로는 6(64초)과 10(1024초)을 권합니다.
τ 는 시계 보정 알고리즘이 정합니다. 시계가 안정되면 간격을 늘리고, 흔들리면 줄입니다. 보통은 상한 근처에 머물다가, 온도가 갑자기 바뀌어 빠르기가 출렁이면 빠르게 내려옵니다. 서버가 패킷의 Poll 칸으로 알린 값과 클라이언트 자신의 값 가운데 작은 쪽을 씁니다. 더 자주 물을 수는 있어도 덜 자주 묻지는 않습니다.
한 번에 여러 통을 보내기도 합니다. burst 를 켜면 서버가 닿고 쓸 만한 동기화 원천이 있을 때 묻는 차례마다 패킷 8통을 2초 간격으로 보냅니다. 간격이 길 때 지터를 정확히 재려는 것입니다. iburst 를 켜면 닿지 않던 서버에 처음 보낼 때 이렇게 몰아 보냅니다. 동기화 거리를 빨리 문턱 아래로 내려 시계를 일찍 맞추려는 것입니다.
예시
아래에서 읽는 것은 NTP 구현 가운데 하나인 chrony 가 chronyc ntpdata 명령으로 보여 주는 측정 한 건입니다. 서버 응답 한 통에 담겼던 칸들과 그것으로 잰 값이 나옵니다. 먼저 그 칸들이 패킷 머리 어디에 놓이는지 보고, 출력의 값을 칸에 맞춰 봅니다.
패킷 머리 48바이트의 배치
패킷 머리는 32비트 낱말 12개, 모두 48바이트입니다. 숫자는 비트 자리입니다.
packet-beta 0-1: "LI" 2-4: "VN" 5-7: "Mode" 8-15: "Stratum" 16-23: "Poll" 24-31: "Precision" 32-63: "Root Delay" 64-95: "Root Dispersion" 96-127: "Reference ID" 128-191: "Reference Timestamp" 192-255: "Origin Timestamp" 256-319: "Receive Timestamp" 320-383: "Transmit Timestamp"
맨 앞 LI(Leap Indicator, 윤초 표시)가 2비트, VN(Version Number, 판 번호)과 Mode 가 3비트씩입니다. Stratum · Poll · Precision 은 8비트씩입니다. Poll 과 Precision 은 2를 밑으로 한 로그 초 값이라, Precision 이 −18 이면 약 1 마이크로초입니다. Root Delay 와 Root Dispersion 은 16비트 초와 16비트 소수로 된 32비트 짧은 형식입니다. 머리 뒤에는 확장 필드와 인증 코드가 붙을 수 있습니다.
Reference Timestamp 는 그 서버가 시스템 시계를 마지막으로 맞추거나 고친 시각입니다. 아래 출력의 Reference time 줄이 이 칸의 값입니다.
LI 는 이달 마지막 날의 마지막 분에 윤초가 끼거나 빠지는지 미리 알립니다.
| LI 값 | 뜻 |
|---|---|
| 0 | 예고 없음 |
| 1 | 그날 마지막 분이 61초 |
| 2 | 그날 마지막 분이 59초 |
| 3 | 알 수 없음, 시계가 동기화 안 됨 |
chronyc ntpdata 가 보여 준 응답 한 통
Remote address : 203.0.113.15 (CB00710F)
Remote port : 123
Leap status : Normal
Version : 4
Mode : Server
Stratum : 1
Poll interval : 10 (1024 seconds)
Precision : -24 (0.000000060 seconds)
Root delay : 0.000015 seconds
Root dispersion : 0.000015 seconds
Reference ID : 47505300 (GPS)
Reference time : Fri Nov 25 15:22:12 2016
Offset : -0.000060878 seconds
Peer delay : 0.000175634 seconds
Peer dispersion : 0.000000681 seconds
Response time : 0.000053050 seconds
NTP tests : 111 111 1111
Total TX : 24
Total RX : 24
Total valid RX : 24
Total good RX : 22
출력 가운데 이 절에서 읽지 않는 줄 몇 개는 덜어 냈습니다. Leap status 부터 Reference time 까지 열 줄은 마지막 유효 응답의 머리 칸 값입니다. Offset · Peer delay · Peer dispersion 셋은 클라이언트가 잰 값입니다. 줄마다 맞춰 보면 이렇습니다.
| 출력 줄 | 값 | 머리 칸과 뜻 |
|---|---|---|
| Remote address | 203.0.113.15 (CB00710F) | 서버의 IP 주소. 괄호 안은 그 주소를 Reference ID 꼴로 적은 값이다. 이 패킷의 Reference ID 는 아래 줄에 따로 있다 |
| Remote port | 123 | 서버의 NTP 포트 |
| Leap status | Normal | LI 칸. 윤초 예고가 없다 |
| Version | 4 | VN 칸. NTP 4판 |
| Mode | Server | Mode 칸. 서버의 응답이다 |
| Stratum | 1 | 주 서버 |
| Poll interval | 10 (1024 seconds) | 폴 지수 10, 곧 1024초 간격 |
| Precision | -24 | 2^−24 초, 약 60 나노초 |
| Root delay | 0.000015 seconds | 기준 시계까지 쌓인 왕복 지연 |
| Root dispersion | 0.000015 seconds | 기준 시계까지 쌓인 분산 |
| Reference ID | 47505300 (GPS) | 계층 1 이라 기준 시계 이름이 들어 있다 |
| Reference time | Fri Nov 25 15:22:12 2016 | 서버 시계를 마지막으로 맞춘 시각 |
| Offset | -0.000060878 seconds | 잰 오프셋, 약 −61 마이크로초 |
| Peer delay | 0.000175634 seconds | 잰 왕복 지연, 약 176 마이크로초 |
| Peer dispersion | 0.000000681 seconds | 잰 분산 |
| Response time | 0.000053050 seconds | 서버가 요청을 처리하고 답을 보내기까지 쓴 시간 |
Reference ID 의 47505300 은 4바이트를 16진수로 적은 것입니다. 0x47 · 0x50 · 0x53 이 ASCII(American Standard Code for Information Interchange) 글자 G · P · S 입니다. 남은 한 바이트는 0 으로 채웠습니다. 이 서버는 GPS 수신기를 기준 시계로 쓰는 계층 1 주 서버입니다.
chrony 가 Offset 의 부호를 어느 쪽 기준으로 적는지와 네 타임스탬프의 원래 값은 이 출력에 없습니다(미확인).
NTP tests 의 111 111 1111 은 이 서버에서 온 패킷이 받은 검사의 결과입니다. 검사는 세 묶음으로 나뉘어 있고, 자릿수도 3 · 3 · 4 로 끊겨 있습니다. 묶인 검사는 「교환 순서」에서 본 패킷 검사 1~3 과 5~7, 그리고 chrony 자신의 검사 넷(최대 지연 · 지연 비율 · 지연 편차 비율(지연 분위) · 동기화 고리)입니다. 일곱 가운데 4번 접근 거부만 빠져 있습니다. ntpdata 는 마지막 유효 측정 한 건을 보여 주는 명령입니다. 이 열 자리도 그 한 건의 결과입니다. 어느 검사가 어느 묶음에 들어가는지, 열 자리가 어느 검사에 차례로 대응하는지, 숫자 1 이 통과를 뜻하는지는 매뉴얼이 밝히지 않습니다(미확인).
아래 넷은 이 서버와 주고받은 누적 횟수입니다. 위의 열 자리와 재는 대상이 다릅니다. 열 자리는 마지막 한 통의 결과입니다. 이 넷은 24통을 센 값입니다. 24통을 보내 24통을 받았습니다. 24통 모두 앞의 두 묶음을 통과했습니다. 그중 22통이 세 묶음을 다 통과해 측정으로 받아들여졌습니다. 뒤의 두 수는 매뉴얼이 Total valid RX 를 「앞의 두 묶음을 통과한 패킷 수」로, Total good RX 를 「세 묶음을 다 통과한 패킷 수, 곧 측정이 받아들여진 수」로 적어 둔 값입니다. 위의 열 자리를 읽어서 센 것이 아닙니다.
Reference ID 를 읽는 법
Reference ID 칸 4바이트가 무엇을 담는지는 Stratum 값이 정합니다.
| Stratum | Reference ID 에 담기는 것 |
|---|---|
| 0 | kiss code. 4글자 ASCII 로 된 상태 알림이다 |
| 1 | 기준 시계를 가리키는 4글자 ASCII. 왼쪽부터 채우고 남은 바이트는 0 이다. GPS · PPS · DCF 따위 |
| 2 이상, IPv4 | 위쪽 서버의 IPv4(Internet Protocol version 4) 주소 4바이트 |
| 2 이상, IPv6 | 위쪽 서버 IPv6(Internet Protocol version 6) 주소를 MD5(Message-Digest algorithm 5)로 해시한 값의 앞 4바이트 |
2 이상에서 이 값은 시계가 고리를 이루는지 찾는 데 쓰입니다. 위쪽 서버가 다시 나에게 맞추고 있으면 그 서버의 Reference ID 가 내 주소와 같아집니다.
실패
클라이언트가 시각을 못 받거나, 받은 시각을 믿을 수 없게 되는 경우를 모았습니다. 대상은 클라이언트 한 대와 그가 묻는 서버들입니다. 조건마다 그때 무엇이 일어나는지를 짝지어 적습니다.
| 조건 | 일어나는 일 |
|---|---|
| 서버가 답을 안 한다 | 분산이 커져 그 서버가 동기화 후보에서 빠진다. 계속되면 폴 간격을 두 배씩 늘린다 |
| Kiss-o'-Death 를 받는다 | DENY · RSTR 이면 그 서버와 끊는다. RATE 면 묻는 빈도를 낮춘다 |
| 오프셋이 0.125초를 넘는다 | 그 상태가 900초 넘게 이어지면 시각을 한 번에 옮기고 연결을 모두 처음부터 다시 맺는다 |
| 오프셋이 1000초를 넘는다 | 맞추기를 그만두고 프로그램이 끝난다 |
| 서버가 아직 동기화 안 됐다 | 그 서버의 응답을 동기화에 쓰지 않는다 |
| 정직한 다수를 못 찾는다 | 이번에는 시계를 고치지 않고 넘어간다 |
| 시작 전 시계가 서버와 34년 넘게 어긋났다 | 오프셋과 지연이 모호해진다 |
서버가 답을 안 할 때
서버마다 8비트 도달 레지스터(reach register)가 있습니다. 패킷을 보낼 때 왼쪽으로 한 칸 밀고 맨 오른쪽을 0 으로 둡니다. 유효한 응답이 오면 맨 오른쪽을 1 로 바꿉니다. 0 이 아닌 비트가 하나라도 있으면 닿는 서버로 봅니다.
block-beta columns 8 L["왼쪽 끝은 밀려 떨어져 나간다"]:4 R["보낼 때마다 오른쪽에 0 이 들어온다"]:4 b7["비트 7"] b6["비트 6"] b5["비트 5"] b4["비트 4"] b3["비트 3"] b2["비트 2"] b1["비트 1"] b0["비트 0"] space:5 low["아래 세 비트 — 최근 세 차례"]:3
아래 세 비트가 모두 0 이면 세 번 연달아 응답이 없었다는 뜻입니다. 그러면 시계 필터에 분산이 최대인 더미 표본(dummy tuple)을 밀어 넣어 분산을 키웁니다.
분산이 커지면 동기화 거리도 커집니다. 거리 문턱 MAXDIST(1초)를 넘으면 그 서버는 동기화에 쓸 수 없다고 봅니다. 레지스터가 통째로 0 이 되면 후보에서 아예 빠집니다.
닿지 않는 동안에는 묻는 차례마다 미도달 카운터를 하나씩 올립니다. 카운터가 문턱(UNREACH, 24회. 참고 구현 예시는 12회)에 닿으면 그 뒤로는 차례마다 폴 지수를 하나씩 올려 간격을 두 배로 늘립니다. 상한은 36시간입니다. 서버가 다시 닿으면 카운터를 0 으로 되돌리고, 폴 지수도 시계 보정 알고리즘이 정한 값으로 되돌립니다.
NTP 4판은 3판까지와 달리 응답이 끊긴 서버에 계층 16 · LI 3 을 매기지 않습니다. 마지막으로 받은 값을 그대로 두고, 동기화 거리가 커져 문턱을 넘기를 기다립니다.
Kiss-o'-Death 를 받을 때
Kiss-o'-Death(KoD)는 Stratum 이 0 인 응답입니다. Reference ID 칸에 kiss code 라는 4글자 ASCII 를 실어 상태를 알리거나 접근을 막습니다. 접근 통제를 어긴 클라이언트에게 그만 보내라고 알리던 쓰임에서 이름이 왔습니다.
받은 쪽은 kiss code 를 반드시 살펴봐야 하고(MUST), 몇 가지에는 정해진 대로 움직여야 합니다.
| kiss code | 뜻 | 클라이언트가 할 일 |
|---|---|---|
DENY |
서버가 접근을 거부했다 | 그 서버와 맺은 연결을 모두 내리고 보내기를 멈춘다(MUST) |
RSTR |
서버 쪽 정책으로 접근을 거부했다 | DENY 와 같다(MUST) |
RATE |
빈도를 넘어서 서버가 잠시 접근을 막았다 | 그 서버에 묻는 빈도를 곧바로 낮추고, RATE 를 받을 때마다 더 낮춘다(MUST) |
| X 로 시작하는 코드 | 등록 안 된 실험용 | 모르는 것이면 무시한다(MUST) |
그 밖 · INIT · STEP · AUTH 따위 |
상태 알림 | 살펴본 뒤 버린다 |
빈도를 낮춘다는 것은 폴 간격 값을 키운다는 뜻입니다. 처음 규칙은 「폴링 간격을 줄인다(reduce its polling interval)」라고 적어 거꾸로 읽히기 쉽습니다. 줄이는 것은 묻는 빈도이고 키우는 것은 폴 간격 값이라고, 뒤에 나온 운영 권고가 풉니다.
그 값은 13(두 시간)을 넘지 않게 둡니다(should not). 얼마나 키울지는 정해져 있지 않습니다. 서버가 RATE 패킷에 실어 보낸 간격 값을 쓰더라도 아무 값이나 받아들이면 안 됩니다(MUST NOT). 큰 값을 받아들이면 클라이언트가 묻기를 멈추게 만드는 서비스 거부 공격의 통로가 됩니다.
KoD 패킷의 Receive Timestamp 와 Transmit Timestamp 는 뜻이 없습니다. 믿지 말고 버려야 합니다(MUST NOT · MUST). 클라이언트는 Origin Timestamp 가 맞는 KoD 만 받아들여야 합니다(MUST). KoD 는 암호 인증 없이도 흔히 받아들여져서, 높은 간격 값을 담은 RATE 하나만 보여도 공격의 신호일 수 있습니다.
NTS(Network Time Security, 네트워크 시각 보안)를 쓰는 연결에는 NTSN 이라는 kiss code 가 더 있습니다. NTS 쪽의 부정 응답입니다. NTS 는 아래 「보장과 가정」에서 다룹니다.
오프셋이 클 때
시계 보정 알고리즘은 최종 오프셋의 크기로 세 갈래를 나눕니다. 문턱은 셋입니다.
| 문턱 | 값 | 뜻 |
|---|---|---|
STEPT · 스텝 임계값 |
0.125초 | 넘으면 시각을 조금씩 조정하는 대신 한 번에 옮기는 쪽으로 간다 |
WATCH · 스텝아웃 임계값 |
900초 | 스텝 임계값을 넘는 오프셋이 이만큼 이어져야 옮긴다 |
PANICT · 패닉 임계값 |
1000초 | 넘으면 맞추기를 그만둔다 |
flowchart TD
A["최종 오프셋이 나왔다"] --> B{"1000초를 넘나"}
B -->|넘는다| P["맞추기를 그만두고 끝난다"]
B -->|아니다| C{"0.125초를 넘나"}
C -->|아니다| D["시각과 빠르기를 조금씩 조정한다"]
C -->|넘는다| E{"900초 넘게 이어졌나"}
E -->|아니다| F["튀는 값으로 보고 버틴다"]
E -->|그렇다| G["시각을 한 번에 옮기고 연결을 다시 맺는다"]
평소 동기화된 상태에서는 그림대로 갑니다. 오프셋이 0.125초 아래면 시각과 빠르기를 조금씩 조정합니다. 0.125초를 넘어도 900초가 지나기 전에는 튀는 값으로 보고 옮기지 않습니다. 네트워크가 심하게 막혀 오프셋이 잠깐 튀는 것을 버티려는 장치입니다.
막 켜진 직후의 첫 갱신은 그림과 다릅니다. 저장해 둔 빠르기 기록이 있든 없든, 0.125초를 넘으면 기다리지 않고 바로 옮깁니다.
시각을 옮기고 나면 서버들과 주고받아 모은 값이 전부 낡은 것이 됩니다. 그래서 연결을 모두 초기화하고 막 켜진 것처럼 다시 시작해야 합니다(MUST). 옮기는 방향이 뒤일 수도 있습니다. 참고 구현은 스텝 임계값을 바꿔 뒤로 옮겨질 가능성을 줄일 수 있게 해 두었지만, 그렇게 바꾸면 심각한 결과가 따를 수 있습니다.
오프셋이 1000초를 넘으면 무언가 크게 잘못됐다고 봅니다. 프로그램이 시스템 로그에 진단 메시지를 남기고 끝나야 하고(SHOULD), 운영자가 시계를 손으로 그 범위 안에 맞춥니다.
패닉 임계값에는 약점이 있습니다. 두 조건이 함께 서면 막아 주지 못합니다. 하나는 운영체제가 끝난 NTP 클라이언트를 자동으로 다시 띄우는 것입니다. systemd 같은 프로세스 관리자가 그렇게 설정될 수 있습니다. 다른 하나는 다시 뜰 때마다 패닉 임계값을 무시하도록 설정한 것입니다.
그러면 공격자가 1000초 넘는 오프셋을 보내 클라이언트를 끝냅니다. 다시 뜬 클라이언트는 임계값을 무시하고 그 큰 오프셋을 받아들입니다. 충분한 감시가 없다면 콜드 스타트, 곧 새로 뜨는 모든 경우에 패닉 임계값을 무시하도록 두지 않아야 합니다(SHOULD NOT). 구현은 NTP 데몬이 컴파일된 날짜보다 앞으로 시계를 옮기지 못하게 막아야 합니다(SHOULD).
서버가 아직 동기화 안 됐을 때
NTP 가 막 켜지면 윤초 표시는 3(동기화 안 됨), 계층은 16 에서 시작합니다. 패킷에는 LI 3 과 Stratum 0 으로 실립니다.
받는 쪽은 이런 응답을 걸러 냅니다. 「교환 순서」에서 본 패킷 검사 일곱 가운데 여섯째가 미동기 검사입니다. 걸리면 패킷을 버립니다. 참고 구현 예시도 LI 가 3 이거나 계층이 16 이상인 서버를 동기화에 쓸 수 없다고 판정합니다.
막 켜진 서버가 Reference ID 에 INIT 을 싣는 일이 있습니다. 구현은 막 켜질 때 INIT 을 싣지 않도록 막아야 합니다(SHOULD). 공격자가 데몬이 언제 다시 떴는지 알아채기 쉬워지기 때문입니다.
정직한 다수를 못 찾을 때
선택 알고리즘은 거짓 시계 수를 0 으로 가정하고 시작합니다. 다수가 겹치는 구간을 못 찾으면 거짓 시계 수를 하나 늘려 다시 찾습니다. 거짓 시계 수가 전체의 절반에 닿을 때까지도 못 찾으면 실패입니다.
실패하면 시계를 고칠 만한 후보가 없다고 보고, 이번에는 시계를 고치지 않고 넘어갑니다. 정직한 시계가 최소 후보 수(CMIN)보다 적을 때도 같습니다. CMIN 의 기본값은 역사적 이유로 1 입니다. 조심스러운 운영자는 이 값을 올려 여러 서버가 있어야만 시계를 고치게 둡니다.
시계가 처음부터 크게 어긋나 있을 때
오프셋과 지연은 64비트 타임스탬프의 차이를 다시 더하고 빼서 냅니다. 64비트 정수로만 계산하면 과거 34년에서 미래 34년 사이만 모호하지 않게 담깁니다. 그래서 서비스를 시작하기 전에 클라이언트 시계가 서버와 34년 안에 맞춰져 있어야 합니다.
차이를 배정밀도 부동소수점으로 바꿔 계산하면 이 범위가 68년으로 넓어집니다.
보장과 가정
NTP 가 클라이언트에게 약속하는 것들을 각 약속이 기대는 가정과 짝지어 봅니다. 대상은 클라이언트 한 대가 쓰는 서버들입니다. 가정이 깨지면 어느 약속이 무너지는지도 적습니다.
| 보장 | 가정 | 가정이 깨지면 |
|---|---|---|
| 대칭이 깨져 생기는 오프셋 오차는 왕복 지연의 절반 안에 든다 | 가는 길과 오는 길의 지연이 대략 같다 | 두 길 차이의 절반만큼 오프셋이 틀린다. 패킷 내용은 멀쩡해서 암호로도 못 막는다 |
| 거짓 시계를 걸러 낸다 | 쓰는 서버의 다수가 정직하다 | 거짓 쪽이 다수면 거짓 시각을 고른다 |
| 시각이 뛰지 않고 조금씩 맞춰진다 | 오프셋이 스텝 임계값 0.125초 아래에 머문다 | 시각이 한 번에 옮겨지고, 뒤로 갈 수도 있다 |
| 윤초를 미리 알고 그달 마지막 날에 넣는다 | 윤초를 알려 주는 서버에 맞춰져 있고, 쓰는 서버들이 윤초를 같은 방식으로 다룬다 | 서버들이 둘로 갈려 정확한 원천을 못 고른다 |
| 없다. 기본 형태는 답한 서버가 진짜인지 확인하지 않는다 | 경로 위에 공격자가 없다 | 중간자가 패킷을 버리거나 다시 틀거나 고쳐 거짓 시각을 넣는다 |
대칭 경로
오프셋 식은 왕복 지연을 반으로 나눠 가는 길과 오는 길에 똑같이 나눠 줍니다. 두 길이 대략 같아야 이 계산이 맞습니다. 가는 길만 느리면 두 길 차이의 절반이 오프셋에 섞여 듭니다.
공격자는 이 가정을 노릴 수 있습니다. 중간자가 패킷 내용은 건드리지 않고 한쪽 방향만 늦추는 지연 공격입니다. 내용을 바꾸거나 순서를 뒤섞지 않으니 암호 인증으로는 막을 방법이 없습니다.
그래도 넣을 수 있는 오차에는 상한이 있습니다. 왕복 지연의 절반입니다. 클라이언트는 거리 문턱 MAXDIST 를 넘는 서버를 쓰지 않습니다.
이 문턱은 바로 앞 서버와의 왕복뿐 아니라 기준 시계까지 쌓인 루트 지연까지 함께 잽니다. 이 값이 표준대로 1초면 지연 공격이 넣을 수 있는 오차는 0.5초까지입니다. 시각 원천이나 경로를 여럿 쓰면, 공격자가 그중 일부만 쥐고 있을 때 피해를 줄이는 데 도움이 될 수 있습니다.
정직한 다수
선택 알고리즘은 서버마다 오프셋 θ 를 가운데 두고 양쪽으로 동기화 거리 λ 만큼 벌린 구간을 그립니다. 정직한 시계라면 참 시각이 그 구간 안에 있어야 합니다. 다수의 구간이 함께 겹치는 범위를 찾고, 거기서 벗어난 서버를 거짓 시계로 버립니다.
block-beta columns 8 axis["시각 축 — 왼쪽이 이른 시각, 오른쪽이 늦은 시각"]:8 A["서버 A · θ−λ 에서 θ+λ"]:4 space:4 space B["서버 B 의 구간"]:4 space:3 space:2 C["서버 C 의 구간"]:4 space:2 space:6 D["서버 D 의 구간"]:2 space:2 X["셋이 겹치는 범위"]:2 space:4
그림은 서버 넷 가운데 셋의 구간이 겹치는 모양입니다. 그 겹침에서 벗어난 서버 D 가 거짓 시계로 버려집니다. 이 방법은 Marzullo 가 처음 내놓은 비잔틴 합의 원리를 정확도가 높아지도록 고친 것입니다. 비잔틴 합의는 참가자 일부가 틀린 값을 내놓아도 나머지가 한 값에 이르게 하는 방법입니다.
판정이 기대는 가정은 쓰는 서버의 다수가 정직하다는 것입니다. 일부가 부정확해도 다수가 정직하면 됩니다. 공격자가 네트워크를 쥐고 있으면 모든 서버에서 오는 시각이 오염될 수 있습니다.
서버 수에 따라 버틸 수 있는 것이 달라집니다.
| 원천 수 | 어떻게 되나 |
|---|---|
| 1 | 그 원천이 틀리면 그대로 클라이언트에 넘어온다 |
| 2 | 둘이 다르면 프로토콜 안에서는 어느 쪽이 맞는지 알기 어렵다. 하나가 죽으면 1 이 된다 |
| 3 | 하나를 잃어도 버틴다. 잃고 나면 2 가 된다 |
| 4 이상 | 원천이 서로 다양하면 하나에 문제가 생겨도 셋이 남는다 |
정확한 시각이 중요하면 독립적이고 다양한 원천을 넷 이상 쓰기를 권합니다(SHOULD). 넷을 못 구하면 더 적게 써도 되지만(MAY), 위 표의 위험을 떠안습니다.
애니캐스트 주소 하나로 NTP 서버를 붙이면 이 가정이 깨집니다. 주소는 하나입니다. 라우터가 가장 가까운 서버로 보내므로 클라이언트는 대개 원천 하나만 씁니다. 여러 원천을 견주는 판정이 일어나지 않습니다. 애니캐스트 풀을 여럿 따로 두면 설정의 편함을 지키면서 원천도 여럿 쓸 수 있습니다.
조금씩 맞추기와 스텝 임계값
오프셋이 스텝 임계값 0.125초 아래면 시계 보정 알고리즘은 시각을 작은 폭으로 앞당기거나 늦춥니다. 시각이 끊기지 않고 이어지고, 앞뒤로 뛰지 않습니다.
이 약속은 오프셋이 0.125초 아래에 머문다는 가정 위에 섭니다. 넘는 상태가 이어지면 시각을 한 번에 옮기고, 그 방향이 뒤일 수도 있습니다. 자세한 갈림은 「실패」의 오프셋이 클 때에 있습니다.
윤초와 스미어
NTP 는 UTC 를 따르므로 윤초도 나릅니다. LI 칸이 미리 알리고, 윤초는 그달 마지막 날에만 들어갑니다. 1972년부터 들어간 윤초는 모두 1초를 더하는 쪽이었습니다. 1초를 빼는 쪽도 지원합니다.
1분이 61초가 되면 탈이 나는 애플리케이션과 운영체제가 있습니다. 그런 곳에서는 윤초 스미어(leap smearing)를 씁니다. 윤초를 한 번에 넣지 않고, 그 앞뒤 두 시간에서 스물네 시간쯤의 창에 걸쳐 시각을 아주 조금씩 옮기는 방법입니다. 그 창 동안 클라이언트 시각은 UTC 와 최대 1초까지 어긋날 수 있지만, 클라이언트끼리는 대개 같은 시각을 가리킵니다.
이 방법이 기대는 가정은 쓰는 서버들이 모두 같은 처리를 한다는 것입니다. 2015년 6월 윤초 앞뒤 몇 시간 동안, 원천 넷 가운데 둘은 스미어한 시각을 주고 둘은 그렇지 않은 시각을 준 클라이언트가 많았습니다. 그 클라이언트들은 정확한 원천을 고르지 못했습니다.
한 클라이언트에 스미어한 서버와 안 한 서버를 섞어 쓰면 안 됩니다(MUST NOT). 공개 NTP 서버에는 스미어를 쓰면 안 됩니다(MUST NOT). 법이나 다른 강한 이유로 UTC 를 따라야 하는 운영자는 스미어를 쓰지 않아야 합니다(SHOULD NOT).
인증 없음과 그 메우기
기본 설정에서 NTP 패킷은 아무 보호 없이 오갑니다. 주고받기의 짜임 덕분에 위조 · 유실 · 다시 틀기(replay)에는 본디 강합니다. 그러나 답한 서버가 누구인지 확인하고 인증하지는 못합니다. 중간자가 되면 패킷을 버리거나 다시 틀거나 고쳐서 거짓 시각을 넣을 수 있습니다.
인증을 붙인다고 시각이 맞다는 뜻은 아닙니다. 인증은 패킷을 누가 보냈는지만 확인합니다. 인증된 서버 여럿을 두고도 앞의 선택 알고리즘으로 정직한 시계를 골라냅니다.
메우는 수단은 넷입니다.
| 수단 | 어떻게 | 조건과 권고 |
|---|---|---|
| 미리 나눈 대칭 키 | 연결마다 키를 정하고, 패킷 끝에 키 식별자와 다이제스트로 된 MAC(Message Authentication Code, 메시지 인증 코드)을 붙인다 | 키는 NTP 밖에서 안전하게 나누고 새지 않게 지켜야 한다(MUST). MD5 는 이제 너무 약하다고 보고, 쓸 수 있으면 AES-CMAC(Advanced Encryption Standard 기반 Cipher-based Message Authentication Code)을 쓴다(SHOULD) |
| Autokey | 공개 키로 키 관리와 서버 인증을 자동화한다 | 취약점이 드러나 쓰지 않는다(SHOULD NOT) |
| NTS | TLS(Transport Layer Security, 전송 계층 보안)로 키를 나눈 뒤 NTP 확장 필드로 패킷을 인증한다 | 클라이언트/서버 갈래에만 정의돼 있다 |
| IPsec · MACsec | NTP 밖의 계층에서 패킷을 감싼다 | 시각 동기화 성능이 떨어질 수 있다 |
NTS 는 두 단계로 갑니다. 먼저 NTS-KE(NTS Key Establishment, NTS 키 수립) 서버와 TCP 4460 번 포트에서 TLS 핸드셰이크를 하고, 키와 쿠키 여럿과 쓸 NTP 서버 주소를 받습니다. 이 단계는 한 왕복으로 끝납니다. 서버는 상태를 남기지 않습니다. 그다음 NTP 요청마다 쿠키와 인증 태그를 확장 필드에 실어 보내고, 응답에서 새 쿠키를 받습니다.
쿠키는 서버가 키를 되찾는 데 쓰는 암호화된 값입니다. 서버가 클라이언트마다 상태를 들고 있지 않아도 되도록, 그 상태를 클라이언트가 쿠키에 담아 요청마다 들고 옵니다.
NTS 를 벗기는 공격도 있습니다. 중간자가 핸드셰이크를 실패시켜 클라이언트가 보호 없는 NTP 로 돌아가게 만듭니다. 사용자가 명시하지 않는 한 NTS 에서 보호 없는 NTP 로 되돌아가지 않아야 합니다(SHOULD NOT). 어느 수단도 앞에서 본 지연 공격은 막지 못합니다.
NTP 는 남을 공격하는 도구로도 쓰입니다. UDP 위의 프로토콜이라 연결을 맺는 프로토콜보다 대개 출발지 주소를 속이기 쉽습니다. mode 6 제어 메시지는 작은 질의에 큰 응답을 돌려줘, 분산 서비스 거부 공격에서 트래픽을 부풀리는 데 쓰였습니다. 그래서 공개 NTP 서버는 조직 밖에서 오는 제어 메시지 질의를 막아야 합니다(RECOMMENDED).
믿는 상대에게만 여는 갈래도 있습니다. 대칭 수동 연결은 아무나 시작할 수 있어서, 믿는 상대와만 맺고 각각 암호 인증을 요구하기를 권합니다(SHOULD). 브로드캐스트는 모든 클라이언트가 같은 키를 나눠 가지므로, 믿는 네트워크 안에서만 돌리기를 권합니다(SHOULD).
관련 항목
NTP 패킷 머리에 담기는 필드
Leap Indicator · stratum · 폴링 간격 · 루트 지연 · 루트 분산 · 참조 식별자 · NTP 타임스탬프 · 정밀도 · Origin Timestamp · Receive Timestamp · Transmit Timestamp · NTP 확장 필드
NTP 를 실어 나르는 전송·네트워크 프로토콜
UDP · IP · 포트 번호 · 데이터그램 · 브로드캐스트 · 멀티캐스트 · TCP · 애니캐스트 · 임시 포트
NTP 가 오차를 재고 시계를 고치는 동작
오프셋 · 왕복 시간 · 지터 · 슬루 · 스텝 · 시계 보정 · 주파수 오차 · 동기화 거리 · 스텝 임계값 · 패닉 임계값 · 비대칭 경로
NTP 가 시각을 고르고 다듬는 알고리즘
시계 필터 · 선택 알고리즘 · 군집 알고리즘 · 결합 알고리즘 · Marzullo 알고리즘 · 비잔틴 장애 · 위상 고정 루프 · 벨만-포드 알고리즘 · 신장 트리
NTP 가 서버와 연결을 맺고 묻는 방식
대칭 모드 · 브로드캐스트 모드 · manycast · 도달 레지스터 · iburst
NTP 가 다루는 시각과 시계 개념
UTC · 에포크 · 벽시계 · 단조 시계 · 시스템 클록 · 타임스탬프 · 클록 드리프트 · 클록 스큐 · 윤초 · 수정 진동자 · 시간대 · 유닉스 시간 · NTP 시대 · 2036년 문제
NTP 를 구현한 프로그램
ntpd · chrony · systemd-timesyncd · OpenNTPD · w32time · chronyc · ntpq
시각을 맞추는 데 NTP 대신 쓰는 프로토콜
SNTP · PTP · Roughtime
NTP 시각의 뿌리가 되는 기준 시계
원자시계 · 전파 시계 · GPS 수신기 · 갈릴레오 · DCF77 · PPS
NTP 시각을 지키거나 노리는 보안 수단과 공격
NTS · NTS-KE · TLS · 메시지 인증 코드 · AES-CMAC · MD5 · Autokey · Kiss-o'-Death · NTP 증폭 공격 · UDP 반사 공격 · 중간자 공격 · 지연 공격 · 리플레이 공격 · NTS 스트리핑 · IPsec · MACsec · BCP 38 · 방화벽
NTP 를 규정하는 표준 문서
RFC 5905 · RFC 4330 · RFC 8915 · RFC 8633 · RFC 9109 · RFC 5906 · RFC 1305 · IETF · IANA
정확한 시각에 기대는 시스템 기능
인증서 · 스노우플레이크 · 분산 추적 · Kerberos · 캐시 · JWT · 로그 타임스탬프
NTP 서버를 굴리는 서비스와 운영 방식
NTP Pool · 윤초 스미어 · 시간 서버 · 계층 1 서버 · 시간 동기화 · 애니캐스트 NTP
NTP 가 속하는 상위 분류
다른 이름: Network Time Protocol · 네트워크 시간 프로토콜