View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.net.examples.ntp;
18  
19  import java.io.IOException;
20  import java.net.DatagramPacket;
21  import java.net.DatagramSocket;
22  
23  import org.apache.commons.net.ntp.NtpUtils;
24  import org.apache.commons.net.ntp.NtpV3Impl;
25  import org.apache.commons.net.ntp.NtpV3Packet;
26  import org.apache.commons.net.ntp.TimeStamp;
27  
28  /**
29   * The SimpleNTPServer class is a UDP implementation of a server for the Network Time Protocol (NTP) version 3 as described in RFC 1305. It is a minimal NTP
30   * server that doesn't actually adjust the time but only responds to NTP datagram requests with response sent back to originating host with info filled out
31   * using the current clock time. To be used for debugging or testing.
32   *
33   * To prevent this from interfering with the actual NTP service it can be run from any local port.
34   */
35  public class SimpleNTPServer implements Runnable {
36  
37      public static void main(final String[] args) {
38          int port = NtpV3Packet.NTP_PORT;
39          if (args.length != 0) {
40              try {
41                  port = Integer.parseInt(args[0]);
42              } catch (final NumberFormatException nfe) {
43                  nfe.printStackTrace();
44              }
45          }
46          final SimpleNTPServer timeServer = new SimpleNTPServer(port);
47          try {
48              timeServer.start();
49          } catch (final IOException e) {
50              e.printStackTrace();
51          }
52      }
53  
54      private int port;
55      private volatile boolean running;
56      private boolean started;
57  
58      private DatagramSocket socket;
59  
60      /**
61       * Creates SimpleNTPServer listening on default NTP port.
62       */
63      public SimpleNTPServer() {
64          this(NtpV3Packet.NTP_PORT);
65      }
66  
67      /**
68       * Creates SimpleNTPServer.
69       *
70       * @param port the local port the server socket is bound to, or <code>zero</code> for a system selected free port.
71       * @throws IllegalArgumentException if port number less than 0
72       */
73      public SimpleNTPServer(final int port) {
74          if (port < 0) {
75              throw new IllegalArgumentException();
76          }
77          this.port = port;
78      }
79  
80      /**
81       * Connects to server socket and listen for client connections.
82       *
83       * @throws IOException if an I/O error occurs when creating the socket.
84       */
85      public void connect() throws IOException {
86          if (socket == null) {
87              socket = new DatagramSocket(port);
88              // port = 0 is bound to available free port
89              if (port == 0) {
90                  port = socket.getLocalPort();
91              }
92              System.out.println("Running NTP service on port " + port + "/UDP");
93          }
94      }
95  
96      public int getPort() {
97          return port;
98      }
99  
100     /**
101      * Handles incoming packet. If NTP packet is client-mode then respond to that host with a NTP response packet otherwise ignore.
102      *
103      * @param request incoming DatagramPacket
104      * @param rcvTime time packet received
105      *
106      * @throws IOException if an I/O error occurs.
107      */
108     protected void handlePacket(final DatagramPacket request, final long rcvTime) throws IOException {
109         final NtpV3Packet message = new NtpV3Impl();
110         message.setDatagramPacket(request);
111         System.out.printf("NTP packet from %s mode=%s%n", request.getAddress().getHostAddress(), NtpUtils.getModeName(message.getMode()));
112         if (message.getMode() == NtpV3Packet.MODE_CLIENT) {
113             final NtpV3Packet response = new NtpV3Impl();
114 
115             response.setStratum(1);
116             response.setMode(NtpV3Packet.MODE_SERVER);
117             response.setVersion(NtpV3Packet.VERSION_3);
118             response.setPrecision(-20);
119             response.setPoll(0);
120             response.setRootDelay(62);
121             response.setRootDispersion((int) (16.51 * 65.536));
122 
123             // originate time as defined in RFC-1305 (t1)
124             response.setOriginateTimeStamp(message.getTransmitTimeStamp());
125             // Receive Time is time request received by server (t2)
126             response.setReceiveTimeStamp(TimeStamp.getNtpTime(rcvTime));
127             response.setReferenceTime(response.getReceiveTimeStamp());
128             response.setReferenceId(0x4C434C00); // LCL (Undisciplined Local Clock)
129 
130             // Transmit time is time reply sent by server (t3)
131             response.setTransmitTime(TimeStamp.getNtpTime(System.currentTimeMillis()));
132 
133             final DatagramPacket dp = response.getDatagramPacket();
134             dp.setPort(request.getPort());
135             dp.setAddress(request.getAddress());
136             socket.send(dp);
137         }
138         // otherwise if received packet is other than CLIENT mode then ignore it
139     }
140 
141     /**
142      * Returns state of whether time service is running.
143      *
144      * @return true if time service is running
145      */
146     public boolean isRunning() {
147         return running;
148     }
149 
150     /**
151      * Returns state of whether time service is running.
152      *
153      * @return true if time service is running
154      */
155     public boolean isStarted() {
156         return started;
157     }
158 
159     /**
160      * Main method to service client connections.
161      */
162     @Override
163     public void run() {
164         running = true;
165         final byte[] buffer = new byte[48];
166         final DatagramPacket request = new DatagramPacket(buffer, buffer.length);
167         do {
168             try {
169                 socket.receive(request);
170                 final long rcvTime = System.currentTimeMillis();
171                 handlePacket(request, rcvTime);
172             } catch (final IOException e) {
173                 if (running) {
174                     e.printStackTrace();
175                 }
176                 // otherwise socket thrown exception during shutdown
177             }
178         } while (running);
179     }
180 
181     /**
182      * Starts time service and provide time to client connections.
183      *
184      * @throws IOException if an I/O error occurs when creating the socket.
185      */
186     public void start() throws IOException {
187         if (socket == null) {
188             connect();
189         }
190         if (!started) {
191             started = true;
192             new Thread(this).start();
193         }
194     }
195 
196     /**
197      * Closes server socket and stop listening.
198      */
199     public void stop() {
200         running = false;
201         if (socket != null) {
202             socket.close(); // force closing of the socket
203             socket = null;
204         }
205         started = false;
206     }
207 
208 }