SimpleNTPServer.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.net.examples.ntp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import org.apache.commons.net.ntp.NtpUtils;
import org.apache.commons.net.ntp.NtpV3Impl;
import org.apache.commons.net.ntp.NtpV3Packet;
import org.apache.commons.net.ntp.TimeStamp;
/**
* 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
* 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
* using the current clock time. To be used for debugging or testing.
*
* To prevent this from interfering with the actual NTP service it can be run from any local port.
*/
public class SimpleNTPServer implements Runnable {
public static void main(final String[] args) {
int port = NtpV3Packet.NTP_PORT;
if (args.length != 0) {
try {
port = Integer.parseInt(args[0]);
} catch (final NumberFormatException nfe) {
nfe.printStackTrace();
}
}
final SimpleNTPServer timeServer = new SimpleNTPServer(port);
try {
timeServer.start();
} catch (final IOException e) {
e.printStackTrace();
}
}
private int port;
private volatile boolean running;
private boolean started;
private DatagramSocket socket;
/**
* Creates SimpleNTPServer listening on default NTP port.
*/
public SimpleNTPServer() {
this(NtpV3Packet.NTP_PORT);
}
/**
* Creates SimpleNTPServer.
*
* @param port the local port the server socket is bound to, or <code>zero</code> for a system selected free port.
* @throws IllegalArgumentException if port number less than 0
*/
public SimpleNTPServer(final int port) {
if (port < 0) {
throw new IllegalArgumentException();
}
this.port = port;
}
/**
* Connects to server socket and listen for client connections.
*
* @throws IOException if an I/O error occurs when creating the socket.
*/
public void connect() throws IOException {
if (socket == null) {
socket = new DatagramSocket(port);
// port = 0 is bound to available free port
if (port == 0) {
port = socket.getLocalPort();
}
System.out.println("Running NTP service on port " + port + "/UDP");
}
}
public int getPort() {
return port;
}
/**
* Handles incoming packet. If NTP packet is client-mode then respond to that host with a NTP response packet otherwise ignore.
*
* @param request incoming DatagramPacket
* @param rcvTime time packet received
*
* @throws IOException if an I/O error occurs.
*/
protected void handlePacket(final DatagramPacket request, final long rcvTime) throws IOException {
final NtpV3Packet message = new NtpV3Impl();
message.setDatagramPacket(request);
System.out.printf("NTP packet from %s mode=%s%n", request.getAddress().getHostAddress(), NtpUtils.getModeName(message.getMode()));
if (message.getMode() == NtpV3Packet.MODE_CLIENT) {
final NtpV3Packet response = new NtpV3Impl();
response.setStratum(1);
response.setMode(NtpV3Packet.MODE_SERVER);
response.setVersion(NtpV3Packet.VERSION_3);
response.setPrecision(-20);
response.setPoll(0);
response.setRootDelay(62);
response.setRootDispersion((int) (16.51 * 65.536));
// originate time as defined in RFC-1305 (t1)
response.setOriginateTimeStamp(message.getTransmitTimeStamp());
// Receive Time is time request received by server (t2)
response.setReceiveTimeStamp(TimeStamp.getNtpTime(rcvTime));
response.setReferenceTime(response.getReceiveTimeStamp());
response.setReferenceId(0x4C434C00); // LCL (Undisciplined Local Clock)
// Transmit time is time reply sent by server (t3)
response.setTransmitTime(TimeStamp.getNtpTime(System.currentTimeMillis()));
final DatagramPacket dp = response.getDatagramPacket();
dp.setPort(request.getPort());
dp.setAddress(request.getAddress());
socket.send(dp);
}
// otherwise if received packet is other than CLIENT mode then ignore it
}
/**
* Returns state of whether time service is running.
*
* @return true if time service is running
*/
public boolean isRunning() {
return running;
}
/**
* Returns state of whether time service is running.
*
* @return true if time service is running
*/
public boolean isStarted() {
return started;
}
/**
* Main method to service client connections.
*/
@Override
public void run() {
running = true;
final byte[] buffer = new byte[48];
final DatagramPacket request = new DatagramPacket(buffer, buffer.length);
do {
try {
socket.receive(request);
final long rcvTime = System.currentTimeMillis();
handlePacket(request, rcvTime);
} catch (final IOException e) {
if (running) {
e.printStackTrace();
}
// otherwise socket thrown exception during shutdown
}
} while (running);
}
/**
* Starts time service and provide time to client connections.
*
* @throws IOException if an I/O error occurs when creating the socket.
*/
public void start() throws IOException {
if (socket == null) {
connect();
}
if (!started) {
started = true;
new Thread(this).start();
}
}
/**
* Closes server socket and stop listening.
*/
public void stop() {
running = false;
if (socket != null) {
socket.close(); // force closing of the socket
socket = null;
}
started = false;
}
}