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 * https://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.io.IOUtils;
24 import org.apache.commons.net.ntp.NtpUtils;
25 import org.apache.commons.net.ntp.NtpV3Impl;
26 import org.apache.commons.net.ntp.NtpV3Packet;
27 import org.apache.commons.net.ntp.TimeStamp;
28
29 /**
30 * 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
31 * 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
32 * using the current clock time. To be used for debugging or testing.
33 *
34 * To prevent this from interfering with the actual NTP service it can be run from any local port.
35 */
36 public class SimpleNTPServer implements Runnable {
37
38 public static void main(final String[] args) {
39 int port = NtpV3Packet.NTP_PORT;
40 if (args.length != 0) {
41 try {
42 port = Integer.parseInt(args[0]);
43 } catch (final NumberFormatException nfe) {
44 nfe.printStackTrace();
45 }
46 }
47 final SimpleNTPServer timeServer = new SimpleNTPServer(port);
48 try {
49 timeServer.start();
50 } catch (final IOException e) {
51 e.printStackTrace();
52 }
53 }
54
55 private int port;
56 private volatile boolean running;
57 private boolean started;
58
59 private DatagramSocket socket;
60
61 /**
62 * Creates SimpleNTPServer listening on default NTP port.
63 */
64 public SimpleNTPServer() {
65 this(NtpV3Packet.NTP_PORT);
66 }
67
68 /**
69 * Creates SimpleNTPServer.
70 *
71 * @param port the local port the server socket is bound to, or {@code zero} for a system selected free port.
72 * @throws IllegalArgumentException if port number less than 0
73 */
74 public SimpleNTPServer(final int port) {
75 if (port < 0) {
76 throw new IllegalArgumentException();
77 }
78 this.port = port;
79 }
80
81 /**
82 * Connects to server socket and listen for client connections.
83 *
84 * @throws IOException if an I/O error occurs when creating the socket.
85 */
86 public void connect() throws IOException {
87 if (socket == null) {
88 socket = new DatagramSocket(port);
89 // port = 0 is bound to available free port
90 if (port == 0) {
91 port = socket.getLocalPort();
92 }
93 System.out.println("Running NTP service on port " + port + "/UDP");
94 }
95 }
96
97 public int getPort() {
98 return port;
99 }
100
101 /**
102 * Handles incoming packet. If NTP packet is client-mode then respond to that host with a NTP response packet otherwise ignore.
103 *
104 * @param request incoming DatagramPacket
105 * @param rcvTime time packet received
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 // force closing of the socket
202 IOUtils.closeQuietly(socket);
203 socket = null;
204 started = false;
205 }
206
207 }