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 examples.ntp;
018
019import org.apache.commons.net.ntp.NtpUtils;
020import org.apache.commons.net.ntp.NtpV3Impl;
021import org.apache.commons.net.ntp.NtpV3Packet;
022import org.apache.commons.net.ntp.TimeStamp;
023
024import java.io.IOException;
025import java.net.DatagramPacket;
026import java.net.DatagramSocket;
027
028/**
029 * The SimpleNTPServer class is a UDP implementation of a server for the
030 * Network Time Protocol (NTP) version 3 as described in RFC 1305.
031 * It is a minimal NTP server that doesn't actually adjust the time but
032 * only responds to NTP datagram requests with response sent back to
033 * originating host with info filled out using the current clock time.
034 * To be used for debugging or testing.
035 *
036 * To prevent this from interfering with the actual NTP service it can be
037 * run from any local port.
038 */
039public class SimpleNTPServer implements Runnable {
040
041    private int port;
042
043    private volatile boolean running;
044    private boolean started;
045
046    private DatagramSocket socket;
047
048    /**
049     * Create SimpleNTPServer listening on default NTP port.
050     */
051    public SimpleNTPServer()
052    {
053        this(NtpV3Packet.NTP_PORT);
054    }
055
056    /**
057     * Create SimpleNTPServer.
058     *
059     * @param port the local port the server socket is bound to, or
060     *             <code>zero</code> for a system selected free port.
061     * @throws IllegalArgumentException if port number less than 0
062     */
063    public SimpleNTPServer(int port)
064    {
065        if (port < 0) {
066            throw new IllegalArgumentException();
067        }
068        this.port = port;
069    }
070
071    public int getPort()
072    {
073        return port;
074    }
075
076    /**
077     * Return state of whether time service is running.
078     *
079     * @return true if time service is running
080     */
081    public boolean isRunning()
082    {
083        return running;
084    }
085
086    /**
087     * Return state of whether time service is running.
088     *
089     * @return true if time service is running
090     */
091    public boolean isStarted()
092    {
093        return started;
094    }
095
096    /**
097     * Connect to server socket and listen for client connections.
098     *
099     * @throws IOException if an I/O error occurs when creating the socket.
100     */
101    public void connect() throws IOException
102    {
103        if (socket == null)
104        {
105            socket = new DatagramSocket(port);
106            // port = 0 is bound to available free port
107            if (port == 0) {
108                port = socket.getLocalPort();
109            }
110            System.out.println("Running NTP service on port " + port + "/UDP");
111        }
112    }
113
114    /**
115     * Start time service and provide time to client connections.
116     *
117     * @throws java.io.IOException if an I/O error occurs when creating the socket.
118     */
119    public void start() throws IOException
120    {
121        if (socket == null)
122        {
123            connect();
124        }
125        if (!started)
126        {
127            started = true;
128            new Thread(this).start();
129        }
130    }
131
132    /**
133     * main thread to service client connections.
134     */
135    @Override
136    public void run()
137    {
138        running = true;
139        byte buffer[] = new byte[48];
140        final DatagramPacket request = new DatagramPacket(buffer, buffer.length);
141        do {
142            try {
143                socket.receive(request);
144                final long rcvTime = System.currentTimeMillis();
145                handlePacket(request, rcvTime);
146            } catch (IOException e) {
147                if (running)
148                {
149                    e.printStackTrace();
150                }
151                // otherwise socket thrown exception during shutdown
152            }
153        } while (running);
154    }
155
156    /**
157     * Handle incoming packet. If NTP packet is client-mode then respond
158     * to that host with a NTP response packet otherwise ignore.
159     *
160     * @param request incoming DatagramPacket
161     * @param rcvTime time packet received
162     *
163     * @throws IOException  if an I/O error occurs.
164     */
165    protected void handlePacket(DatagramPacket request, long rcvTime) throws IOException
166    {
167        NtpV3Packet message = new NtpV3Impl();
168        message.setDatagramPacket(request);
169        System.out.printf("NTP packet from %s mode=%s%n", request.getAddress().getHostAddress(),
170                NtpUtils.getModeName(message.getMode()));
171        if (message.getMode() == NtpV3Packet.MODE_CLIENT) {
172            NtpV3Packet response = new NtpV3Impl();
173
174            response.setStratum(1);
175            response.setMode(NtpV3Packet.MODE_SERVER);
176            response.setVersion(NtpV3Packet.VERSION_3);
177            response.setPrecision(-20);
178            response.setPoll(0);
179            response.setRootDelay(62);
180            response.setRootDispersion((int) (16.51 * 65.536));
181
182            // originate time as defined in RFC-1305 (t1)
183            response.setOriginateTimeStamp(message.getTransmitTimeStamp());
184            // Receive Time is time request received by server (t2)
185            response.setReceiveTimeStamp(TimeStamp.getNtpTime(rcvTime));
186            response.setReferenceTime(response.getReceiveTimeStamp());
187            response.setReferenceId(0x4C434C00); // LCL (Undisciplined Local Clock)
188
189            // Transmit time is time reply sent by server (t3)
190            response.setTransmitTime(TimeStamp.getNtpTime(System.currentTimeMillis()));
191
192            DatagramPacket dp = response.getDatagramPacket();
193            dp.setPort(request.getPort());
194            dp.setAddress(request.getAddress());
195            socket.send(dp);
196        }
197        // otherwise if received packet is other than CLIENT mode then ignore it
198    }
199
200    /**
201     * Close server socket and stop listening.
202     */
203    public void stop()
204    {
205        running = false;
206        if (socket != null)
207        {
208            socket.close();  // force closing of the socket
209            socket = null;
210        }
211        started = false;
212    }
213
214    public static void main(String[] args)
215    {
216        int port = NtpV3Packet.NTP_PORT;
217        if (args.length != 0)
218        {
219            try {
220                port = Integer.parseInt(args[0]);
221            } catch (NumberFormatException nfe) {
222                nfe.printStackTrace();
223            }
224        }
225        SimpleNTPServer timeServer = new SimpleNTPServer(port);
226        try {
227            timeServer.start();
228        } catch (IOException e) {
229            e.printStackTrace();
230        }
231    }
232
233}