From 80c83be436e7aece754a9ae7de4ac063be1a4068 Mon Sep 17 00:00:00 2001 From: Patrick Cullen Date: Mon, 22 Jan 2024 08:59:47 -0800 Subject: [PATCH] add support for new command type Summary: This adds support for `sourcename` requests. The intent is to to record the names of upstream peers without making DNS queries. This uses [REQ_NTP_SOURCE_NAME](https://github.com/mlichvar/chrony/blob/e11b518a1ffa704986fb1f1835c425844ba248ef/candm.h#L105) in chronyc / chronyd protocol. Reviewed By: abulimov, pmazzini Differential Revision: D52958625 fbshipit-source-id: e80ee596833b6b39aa6550ee1d60e66481b40b79 --- ntp/chrony/packet.go | 89 +++++++++++++++++++++++++++++++-------- ntp/chrony/packet_test.go | 80 +++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 17 deletions(-) diff --git a/ntp/chrony/packet.go b/ntp/chrony/packet.go index 7e990841..9c224a66 100644 --- a/ntp/chrony/packet.go +++ b/ntp/chrony/packet.go @@ -70,26 +70,28 @@ func (t PacketType) String() string { // request types. Only those we support, there are more const ( - reqNSources CommandType = 14 - reqSourceData CommandType = 15 - reqTracking CommandType = 33 - reqSourceStats CommandType = 34 - reqActivity CommandType = 44 - reqServerStats CommandType = 54 - reqNTPData CommandType = 57 + reqNSources CommandType = 14 + reqSourceData CommandType = 15 + reqTracking CommandType = 33 + reqSourceStats CommandType = 34 + reqActivity CommandType = 44 + reqServerStats CommandType = 54 + reqNTPData CommandType = 57 + reqNTPSourceName CommandType = 65 ) // reply types const ( - rpyNSources ReplyType = 2 - rpySourceData ReplyType = 3 - rpyTracking ReplyType = 5 - rpySourceStats ReplyType = 6 - rpyActivity ReplyType = 12 - rpyServerStats ReplyType = 14 - rpyNTPData ReplyType = 16 - rpyServerStats2 ReplyType = 22 - rpyServerStats3 ReplyType = 24 + rpyNSources ReplyType = 2 + rpySourceData ReplyType = 3 + rpyTracking ReplyType = 5 + rpySourceStats ReplyType = 6 + rpyActivity ReplyType = 12 + rpyServerStats ReplyType = 14 + rpyNTPData ReplyType = 16 + rpyNTPSourceName ReplyType = 19 + rpyServerStats2 ReplyType = 22 + rpyServerStats3 ReplyType = 24 ) // source modes @@ -273,6 +275,15 @@ type RequestNTPData struct { data [maxDataLen - 16]uint8 } +// RequestNTPSourceName - packet to request source name for peer IP. +type RequestNTPSourceName struct { + RequestHead + IPAddr ipAddr + EOR int32 + // we pass at max ipv6 addr - 16 bytes + data [maxDataLen - 16]uint8 +} + // RequestServerStats - packet to request server stats type RequestServerStats struct { RequestHead @@ -590,12 +601,33 @@ func newNTPData(r *replyNTPDataContent) *NTPData { } } -// ReplyNTPData is a what end user will get for of 'ntp data' response +// ReplyNTPData is a what end user will get in 'ntp data' response type ReplyNTPData struct { ReplyHead NTPData } +type replyNTPSourceNameContent struct { + Name [256]uint8 +} + +// NTPSourceName contains parsed version of 'sourcename' reply +type NTPSourceName struct { + Name [256]uint8 +} + +func newNTPSourceName(r *replyNTPSourceNameContent) *NTPSourceName { + return &NTPSourceName{ + Name: r.Name, + } +} + +// ReplyNTPSourceName is a what end user will get in 'sourcename' response +type ReplyNTPSourceName struct { + ReplyHead + NTPSourceName +} + // Activity contains parsed version of 'activity' reply type Activity struct { Online int32 @@ -730,6 +762,19 @@ func NewNTPDataPacket(ip net.IP) *RequestNTPData { } } +// NewNTPSourceNamePacket creates new packet to request 'source name' information for given peer IP +func NewNTPSourceNamePacket(ip net.IP) *RequestNTPSourceName { + return &RequestNTPSourceName{ + RequestHead: RequestHead{ + Version: protoVersionNumber, + PKTType: pktTypeCmdRequest, + Command: reqNTPSourceName, + }, + IPAddr: *newIPAddr(ip), + data: [maxDataLen - 16]uint8{}, + } +} + // NewServerStatsPacket creates new packet to request 'serverstats' information func NewServerStatsPacket() *RequestServerStats { return &RequestServerStats{ @@ -837,6 +882,16 @@ func decodePacket(response []byte) (ResponsePacket, error) { ReplyHead: *head, NTPData: *newNTPData(data), }, nil + case rpyNTPSourceName: + data := new(replyNTPSourceNameContent) + if err = binary.Read(r, binary.BigEndian, data); err != nil { + return nil, err + } + log.Debugf("response data: %+v", data) + return &ReplyNTPSourceName{ + ReplyHead: *head, + NTPSourceName: *newNTPSourceName(data), + }, nil case rpyServerStats2: data := new(ServerStats2) if err = binary.Read(r, binary.BigEndian, data); err != nil { diff --git a/ntp/chrony/packet_test.go b/ntp/chrony/packet_test.go index c86abaf4..7a466fa1 100644 --- a/ntp/chrony/packet_test.go +++ b/ntp/chrony/packet_test.go @@ -327,6 +327,86 @@ func TestDecodeNTPData(t *testing.T) { require.Equal(t, want, packet) } +func TestDecodeNTPSourceName(t *testing.T) { + raw := []uint8{ + 0x06, 0x02, 0x00, 0x00, 0x00, 0x41, 0x00, 0x13, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x6d, 0x2e, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x74, + 0x70, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x30, 0x30, 0x31, 0x2e, + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, 0x61, 0x63, + 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x63, 0x6f, 0x6d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + packet, err := decodePacket(raw) + require.Nil(t, err) + want := &ReplyNTPSourceName{ + ReplyHead: ReplyHead{ + Version: protoVersionNumber, + PKTType: pktTypeCmdReply, + Res1: 0, + Res2: 0, + Command: reqNTPSourceName, + Reply: rpyNTPSourceName, + Status: sttSuccess, + Sequence: 3295489555, + }, + NTPSourceName: NTPSourceName{ + Name: [256]uint8{ + // ntp_peer001.sample.facebook.com + 0x6e, 0x74, 0x70, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x30, 0x30, + 0x31, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x66, + 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, + } + require.Equal(t, want, packet) +} + func TestDecodeActivity(t *testing.T) { raw := []uint8{ 0x06, 0x02, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x0c, 0x00, 0x00,