001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.net.examples.ntp;
018
019import java.io.IOException;
020import java.net.DatagramPacket;
021import java.net.DatagramSocket;
022
023import org.apache.commons.net.ntp.NtpUtils;
024import org.apache.commons.net.ntp.NtpV3Impl;
025import org.apache.commons.net.ntp.NtpV3Packet;
026import org.apache.commons.net.ntp.TimeStamp;
027
028/**
029 * 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
030 * 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
031 * using the current clock time. To be used for debugging or testing.
032 *
033 * To prevent this from interfering with the actual NTP service it can be run from any local port.
034 */
035public class SimpleNTPServer implements Runnable {
036
037    public static void main(final String[] args) {
038        int port = NtpV3Packet.NTP_PORT;
039        if (args.length != 0) {
040            try {
041                port = Integer.parseInt(args[0]);
042            } catch (final NumberFormatException nfe) {
043                nfe.printStackTrace();
044            }
045        }
046        final SimpleNTPServer timeServer = new SimpleNTPServer(port);
047        try {
048            timeServer.start();
049        } catch (final IOException e) {
050            e.printStackTrace();
051        }
052    }
053
054    private int port;
055    private volatile boolean running;
056    private boolean started;
057
058    private DatagramSocket socket;
059
060    /**
061     * Creates SimpleNTPServer listening on default NTP port.
062     */
063    public SimpleNTPServer() {
064        this(NtpV3Packet.NTP_PORT);
065    }
066
067    /**
068     * Creates SimpleNTPServer.
069     *
070     * @param port the local port the server socket is bound to, or <code>zero</code> for a system selected free port.
071     * @throws IllegalArgumentException if port number less than 0
072     */
073    public SimpleNTPServer(final int port) {
074        if (port < 0) {
075            throw new IllegalArgumentException();
076        }
077        this.port = port;
078    }
079
080    /**
081     * Connects to server socket and listen for client connections.
082     *
083     * @throws IOException if an I/O error occurs when creating the socket.
084     */
085    public void connect() throws IOException {
086        if (socket == null) {
087            socket = new DatagramSocket(port);
088            // port = 0 is bound to available free port
089            if (port == 0) {
090                port = socket.getLocalPort();
091            }
092            System.out.println("Running NTP service on port " + port + "/UDP");
093        }
094    }
095
096    public int getPort() {
097        return port;
098    }
099
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}