ntp/control/packet.go (264 lines of code) (raw):

/* Copyright (c) Facebook, Inc. and its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package control import ( "strings" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // Mode is NTP Control operation mode code const Mode = 6 // Supported operation codes const ( OpReadStatus = 1 OpReadVariables = 2 ) // NormalizeData turns bytes that contain kv ASCII string info a map[string]string func NormalizeData(data []byte) (map[string]string, error) { result := map[string]string{} pairs := strings.Split(string(data), ",") for _, pair := range pairs { split := strings.Split(pair, "=") if len(split) != 2 { log.Debugf("WARNING: Malformed packet, bad k=v pair '%s'", pair) continue } k := strings.TrimSpace(split[0]) v := strings.TrimSpace(strings.Trim(split[1], `"`)) result[k] = v } if len(result) == 0 { return result, errors.Errorf("Malformed packet, no k=v pairs decoded") } return result, nil } // NTPControlMsgHead structure is described in NTPv3 RFC1305 Appendix B. NTP Control Messages. // We don't have Data defined here as data size is variable and binary package // simply doesn't support reading or writing structs with non-fixed fields. type NTPControlMsgHead struct { // 0: 00 Version(3bit) Mode(3bit) VnMode uint8 // 1: Response Error More Operation(5bit) REMOp uint8 // 2-3: Sequence (16bit) Sequence uint16 // 4-5: Status (16bit) Status uint16 // 6-7: Association ID (16bit) AssociationID uint16 // 8-9: Offset (16bit) Offset uint16 // 10-11: Count (16bit) Count uint16 // 12+: Data (up to 468 bits) // then goes [468]uint8 of data that we have in NTPControlMsg } // NTPControlMsg is just a NTPControlMsgHead with data. type NTPControlMsg struct { NTPControlMsgHead Data []uint8 } // LeapDesc stores human-readable descriptions of LI (leap indicator) field var LeapDesc = [4]string{"none", "add_sec", "del_sec", "alarm"} // ClockSourceDesc stores human-readable descriptions of ClockSource field var ClockSourceDesc = [10]string{ "unspec", // 00 "pps", // 01 "lf_radio", // 02 "hf_radio", // 03 "uhf_radio", // 04 "local", // 05 "ntp", // 06 "other", // 07 "wristwatch", // 08 "telephone", // 09 } // SystemEventDesc stores human-readable descriptions of SystemEvent field var SystemEventDesc = [17]string{ "unspecified", // 00 "freq_not_set", // 01 "freq_set", // 02 "spike_detect", // 03 "freq_mode", // 04 "clock_sync", // 05 "restart", // 06 "panic_stop", // 07 "no_system_peer", // 08 "leap_armed", // 09 "leap_disarmed", // 0a "leap_event", // 0b "clock_step", // 0c "kern", // 0d "TAI...", // 0e "stale leapsecond values", // 0f "clockhop", // 10 } // FlashDescMap maps bit mask with corresponding flash status var FlashDescMap = map[uint16]string{ 0x0001: "pkt_dup", 0x0002: "pkt_bogus", 0x0004: "pkt_unsync", 0x0008: "pkt_denied", 0x0010: "pkt_auth", 0x0020: "pkt_stratum", 0x0040: "pkt_header", 0x0080: "pkt_autokey", 0x0100: "pkt_crypto", 0x0200: "peer_stratum", 0x0400: "peer_dist", 0x0800: "peer_loop", 0x1000: "peer_unreach", } // ReadFlashStatusWord returns list of flashers (as strings) decoded from flash status word func ReadFlashStatusWord(flash uint16) []string { flashers := []string{} for mask, message := range FlashDescMap { if flash&mask > 0 { flashers = append(flashers, message) } } return flashers } // SystemStatusWord stores parsed SystemStatus 16bit word. type SystemStatusWord struct { LI uint8 ClockSource uint8 SystemEventCounter uint8 SystemEventCode uint8 } // Word encodes SystemStatusWord as uint16 word func (ssw *SystemStatusWord) Word() uint16 { var out uint16 out |= uint16(ssw.LI) << 14 out |= uint16(ssw.ClockSource) << 8 out |= uint16(ssw.SystemEventCounter) << 4 out |= uint16(ssw.SystemEventCode) return out } // ReadSystemStatusWord transforms SystemStatus 16bit word into usable struct. func ReadSystemStatusWord(b uint16) *SystemStatusWord { return &SystemStatusWord{ LI: uint8((b & 0xc000) >> 14), // first 2 bits ClockSource: uint8((b & 0x3f00) >> 8), // next 6 bits SystemEventCounter: uint8((b & 0xf0) >> 4), // 4 bits SystemEventCode: uint8((b & 0xf)), // last 4 bits } } // PeerStatus word decoded. Sadly values used by ntpd are different from RFC for v2 and v3 of NTP. // Actual values are from http://doc.ntp.org/4.2.6/decode.html#peer type PeerStatus struct { Broadcast bool Reachable bool AuthEnabled bool AuthOK bool Configured bool } // Byte encodes PeerStatus as uint8 func (ps *PeerStatus) Byte() uint8 { var out uint8 if ps.Configured { out |= 0x10 } if ps.AuthOK { out |= 0x8 } if ps.AuthEnabled { out |= 0x4 } if ps.Reachable { out |= 0x2 } if ps.Broadcast { out |= 0x1 } return out } // Here go peer selection statuses, as described in http://doc.ntp.org/current-stable/decode.html#peer const ( // SelReject means peer is discarded as not valid (TEST10-TEST13) SelReject uint8 = 0 // SelFalseTick means peer is discarded by intersection algorithm SelFalseTick uint8 = 1 // SelExcess means peer is discarded by table overflow (not used) SelExcess uint8 = 2 // SelOutlier means peer is discarded by the cluster algorithm SelOutlier uint8 = 3 // SelCandidate means peer is included by the combine algorithm SelCandidate uint8 = 4 // SelBackup means peer is a backup (more than tos maxclock sources) SelBackup uint8 = 5 // SelSYSPeer means peer is a system peer (main synchronization source) SelSYSPeer uint8 = 6 // SelPPSPeer means peer is a PPS peer (when the prefer peer is valid) SelPPSPeer uint8 = 7 ) // PeerSelect maps PeerSelection uint8 to human-readable string taken from http://doc.ntp.org/4.2.6/decode.html#peer var PeerSelect = [8]string{"reject", "falsetick", "excess", "outlyer", "candidate", "backup", "sys.peer", "pps.peer"} // ReadPeerStatus transforms PeerStatus 8bit flag into usable struct func ReadPeerStatus(b uint8) PeerStatus { // 5 bit code with bits assigned different meanings return PeerStatus{ Configured: b&0x10 != 0, AuthOK: b&0x8 != 0, AuthEnabled: b&0x4 != 0, Reachable: b&0x2 != 0, Broadcast: b&0x1 != 0, } } // PeerStatusWord stores parsed PeerStatus 16bit word. type PeerStatusWord struct { PeerStatus PeerStatus PeerSelection uint8 PeerEventCounter uint8 PeerEventCode uint8 } // Word encodes PeerStatusWord as uint16 word func (psw *PeerStatusWord) Word() uint16 { var out uint16 psByte := uint16(psw.PeerStatus.Byte()) out |= psByte << 11 out |= uint16(psw.PeerSelection) << 8 out |= uint16(psw.PeerEventCounter) << 4 out |= uint16(psw.PeerEventCode) return out } // ReadPeerStatusWord transforms PeerStatus 16bit word into usable struct func ReadPeerStatusWord(b uint16) *PeerStatusWord { status := uint8((b & 0xf800) >> 11) // first 5 bits return &PeerStatusWord{ PeerStatus: ReadPeerStatus(status), PeerSelection: uint8((b & 0x700) >> 8), // 3 bits PeerEventCounter: uint8((b & 0xf0) >> 4), // 4 bits PeerEventCode: uint8((b & 0xf)), // last 4 bits } } // MakeVnMode composes uint8 with version and mode bits set func MakeVnMode(version int, mode int) uint8 { var out uint8 out |= uint8(version) << 3 out |= uint8(mode) return out } // MakeREMOp composes uint8 with response error and more bits set, combined with operation code func MakeREMOp(response, err, more bool, op int) uint8 { var out uint8 if response { out |= 0x80 } if err { out |= 0x40 } if more { out |= 0x20 } out |= uint8(op) return out } // GetVersion gets int version from Version+Mode 8bit word func (n NTPControlMsgHead) GetVersion() int { return int((n.VnMode & 0x38) >> 3) // get 3 bits offset by 3 bits } // GetMode gets int mode from Version+Mode 8bit word func (n NTPControlMsgHead) GetMode() int { return int(n.VnMode & 0x7) // get last 3 bits } // IsResponse returns true if packet is a response func (n NTPControlMsgHead) IsResponse() bool { return n.REMOp&0x80 != 0 // response, bit 7 } // HasError returns true if packet has error flag set func (n NTPControlMsgHead) HasError() bool { return n.REMOp&0x40 != 0 // error flag, bit 6 } // HasMore returns true if packet has More flag set func (n NTPControlMsgHead) HasMore() bool { return n.REMOp&0x20 != 0 // more flag, bit 5 } // GetOperation returns int operation extracted from REMOp 8bit word func (n NTPControlMsgHead) GetOperation() uint8 { return uint8(n.REMOp & 0x1f) // last 5 bits } // GetSystemStatus returns parsed SystemStatusWord struct if present func (n NTPControlMsg) GetSystemStatus() (*SystemStatusWord, error) { if n.GetOperation() != OpReadStatus { return nil, errors.Errorf("no System Status Word supported for operation=%d", n.GetOperation()) } return ReadSystemStatusWord(n.Status), nil } // GetPeerStatus returns parsed PeerStatusWord struct if present func (n NTPControlMsg) GetPeerStatus() (*PeerStatusWord, error) { if n.GetOperation() != OpReadVariables { return nil, errors.Errorf("no Peer Status Word supported for operation=%d", n.GetOperation()) } return ReadPeerStatusWord(n.Status), nil } // GetAssociations returns map of PeerStatusWord, basically peer information. func (n NTPControlMsg) GetAssociations() (map[uint16]*PeerStatusWord, error) { result := map[uint16]*PeerStatusWord{} if n.GetOperation() != OpReadStatus { return result, errors.Errorf("no peer list supported for operation=%d", n.GetOperation()) } for i := 0; i < int(n.Count/4); i++ { assoc := n.Data[i*4 : i*4+4] // 2 uint16 encoded as 4 bytes id := uint16(assoc[0])<<8 | uint16(assoc[1]) // uint16 from 2 uint8 peerStatus := uint16(assoc[2])<<8 | uint16(assoc[3]) // ditto result[id] = ReadPeerStatusWord(peerStatus) } return result, nil } // GetAssociationInfo returns parsed normalized variables if present func (n NTPControlMsg) GetAssociationInfo() (map[string]string, error) { result := map[string]string{} if n.GetOperation() != OpReadVariables { return result, errors.Errorf("no variables supported for operation=%d", n.GetOperation()) } data, err := NormalizeData(n.Data) if err != nil { return result, err } return data, nil }