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  }