github.com/coreos/mantle@v0.13.0/network/ntp/server.go (about) 1 // Copyright 2015 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ntp 16 17 import ( 18 "net" 19 "sync" 20 "time" 21 22 "github.com/coreos/pkg/capnslog" 23 24 "github.com/coreos/mantle/network/neterror" 25 ) 26 27 var plog = capnslog.NewPackageLogger("github.com/coreos/mantle", "network/ntp") 28 29 // BUG(marineam): Since our clock source is UTC instead of TAI or some type of 30 // monotonic clock, trying to use this server during a real leap second will 31 // lead to incorrect results. Timekeeping sucks. 32 33 // A simple NTP server intended for testing. It can serve time at some offset 34 // from the real time and adjust for a single leap second. 35 type Server struct { 36 net.PacketConn 37 mu sync.Mutex // protects offset, leapTime, and leapType. 38 offset time.Duration // see SetTime 39 leapTime time.Time // see SetLeapSecond 40 leapType LeapIndicator 41 } 42 43 type ServerReq struct { 44 Client net.Addr 45 Received time.Time 46 Packet []byte 47 } 48 49 // Create a NTP server that listens on the given address. 50 func NewServer(addr string) (*Server, error) { 51 l, err := net.ListenPacket("udp", addr) 52 if err != nil { 53 return nil, err 54 } 55 56 return &Server{PacketConn: l}, nil 57 } 58 59 // Adjust the internal time offset to begin serving based on the given time. 60 func (s *Server) SetTime(now time.Time) { 61 s.mu.Lock() 62 defer s.mu.Unlock() 63 if now.IsZero() { 64 s.offset = time.Duration(0) 65 } else { 66 s.offset = -time.Since(now) 67 } 68 } 69 70 // Must be exactly midnight on the first day of the month. This is the first 71 // time that is always valid after the leap has occurred for both adding and 72 // removing a second. This is the same way leap seconds are officially listed. 73 // https://www.ietf.org/timezones/data/leap-seconds.list 74 func (s *Server) SetLeapSecond(second time.Time, direction LeapIndicator) { 75 second = second.UTC() 76 if (second.IsZero() && direction != LEAP_NONE) || 77 (second.Truncate(24*time.Hour) != second) || 78 (second.Day() != 1) { 79 panic("Invalid leap second.") 80 } 81 s.mu.Lock() 82 defer s.mu.Unlock() 83 s.leapTime = second 84 s.leapType = direction 85 } 86 87 // Get the current offset between real time and the server's time, adjusting 88 // for a leap second as needed. now is real time, not server time. 89 func (s *Server) UpdateOffset(now time.Time) (time.Duration, LeapIndicator) { 90 s.mu.Lock() 91 defer s.mu.Unlock() 92 93 if s.leapTime.IsZero() || s.leapType == LEAP_NONE { 94 return s.offset, LEAP_NONE 95 } 96 97 now = now.Add(s.offset) 98 if now.Add(24 * time.Hour).Before(s.leapTime) { 99 return s.offset, LEAP_NONE 100 } 101 102 if s.leapType == LEAP_ADD && !now.Before(s.leapTime) { 103 plog.Infof("Inserting leap second at %s", s.leapTime) 104 s.offset -= time.Second 105 s.leapTime = time.Time{} 106 s.leapType = LEAP_NONE 107 108 } else if s.leapType == LEAP_SUB && 109 !now.Before(s.leapTime.Add(-time.Second)) { 110 111 plog.Infof("Skipping leap second at %s", s.leapTime) 112 s.offset += time.Second 113 s.leapTime = time.Time{} 114 s.leapType = LEAP_NONE 115 } 116 117 return s.offset, s.leapType 118 } 119 120 // Serve NTP requests forever. 121 func (s *Server) Serve() { 122 plog.Infof("Started NTP server on %s", s.LocalAddr()) 123 124 for { 125 req, err := s.Accept() 126 if neterror.IsClosed(err) { 127 // gracefully quit 128 return 129 } else if err != nil { 130 plog.Errorf("NTP server failed: %v", err) 131 return 132 } 133 go s.Respond(req) 134 } 135 } 136 137 // Accept a single NTP request. 138 func (s *Server) Accept() (*ServerReq, error) { 139 pkt := make([]byte, 1024) 140 n, client, err := s.ReadFrom(pkt) 141 if err != nil { 142 return nil, err 143 } 144 return &ServerReq{ 145 Client: client, 146 Received: time.Now(), 147 Packet: pkt[:n], 148 }, nil 149 } 150 151 // Respond to a single NTP request. 152 func (s *Server) Respond(r *ServerReq) { 153 if len(r.Packet) == cap(r.Packet) { 154 plog.Errorf("Ignoring huge NTP packet from %s", r.Client) 155 return 156 } 157 158 recv := Header{} 159 if err := recv.UnmarshalBinary(r.Packet); err != nil { 160 plog.Errorf("Invalid NTP packet from %s: %v", r.Client, err) 161 return 162 } 163 164 if recv.VersionNumber != NTPv4 { 165 plog.Errorf("Invalid NTP version from %s: %d", r.Client, recv.VersionNumber) 166 return 167 } 168 169 if recv.Mode != MODE_CLIENT { 170 plog.Errorf("Invalid NTP mode from %s: %d", r.Client, recv.Mode) 171 return 172 } 173 174 plog.Infof("Recieved NTP request from %s", r.Client) 175 176 // BUG(marineam): We doesn't account for the possibility of 177 // UpdateOffset behaving differently for the transmit time instead of 178 // the received time. No idea what the correct behavior is. 179 offset, leap := s.UpdateOffset(r.Received) 180 received := NewTimestamp(r.Received.Add(offset)) 181 transmit := NewTimestamp(time.Now().Add(offset)) 182 183 resp := Header{ 184 LeapIndicator: leap, 185 VersionNumber: NTPv4, 186 Mode: MODE_SERVER, 187 Poll: 6, // 64s, arbitrary... 188 Stratum: 7, // Anything within [2,14] will work 189 Precision: Precision(), 190 ReferenceTimestamp: received, 191 OriginTimestamp: recv.TransmitTimestamp, 192 ReceiveTimestamp: received, 193 TransmitTimestamp: transmit, 194 } 195 196 pkt, err := resp.MarshalBinary() 197 if err != nil { 198 plog.Errorf("Creating NTP packet failed: %v", err) 199 return 200 } 201 202 _, err = s.WriteTo(pkt, r.Client) 203 if err != nil { 204 plog.Errorf("Error sending NTP packet to %s: %v", r.Client, err) 205 return 206 } 207 }