package org.kne.cloud.network.ntp; import java.io.Closeable; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import org.kne.cloud.clock.HighAccuracyClock; import org.kne.cloud.clock.NTPTimestamps; import org.kne.cloud.network.MultiProtocolSocketAddress; import org.kne.cloud.network.ThreadTool; import org.kne.io.KNEChannels; import org.kne.math.Long128; public class NTPv4Protocol implements Closeable, AutoCloseable { private static final boolean showPacket = false; public static final int NTP_DEFAULT_PORT = 123; private static final long DEFAULT_POLL_INTERVAL = 1000000000L; private static final long REQUEST_TIMEOUT = 10000000000L; private static final long EPHEMERAL_TIMEOUT = 60000000000L; public static class NTPPeer { private MultiProtocolSocketAddress address; private boolean isEphemeral;// ephemeral private volatile long ephemeralUpdateTime = System.nanoTime(); private volatile long pollInterval = DEFAULT_POLL_INTERVAL; private volatile long pollUpdateTime = System.nanoTime() - pollInterval; private volatile int requestMode; private volatile long peerPollInterval = pollInterval; private boolean checkPollInterval() { long curr = System.nanoTime(); if (curr - pollUpdateTime > Math.min(peerPollInterval, pollInterval)) { pollUpdateTime = curr; return true; } else { return false; } } private boolean checkEphemeralTimeout() { if (!isEphemeral) { return false; } long curr = System.nanoTime(); return curr - ephemeralUpdateTime > EPHEMERAL_TIMEOUT; } private void resetEphemeralTimeout() { ephemeralUpdateTime = System.nanoTime(); } public long getPollInterval() { return pollInterval; } public void setPollInterval(long pollInterval) { this.pollInterval = pollInterval; } public NTPPeer(MultiProtocolSocketAddress address, int requestMode) { this(address, requestMode, false); } protected NTPPeer(MultiProtocolSocketAddress address, int requestMode, boolean isEphemeral) { super(); checkRequestMode(requestMode); this.address = address; this.isEphemeral = isEphemeral; this.requestMode = requestMode; } private void checkRequestMode(int requestMode2) { if (requestMode2 != NTPv4Packet.NTP_CLIENT && requestMode2 != NTPv4Packet.NTP_SYMMETRIC_ACTIVE) { throw new IllegalArgumentException( "request mode must be NTPv4Packet.NTP_CLIEN or NTPv4Packet.NTP_SYMMETRIC_ACTIVE"); } } public NTPPeer(String hostport, int requestMode) { this(new MultiProtocolSocketAddress(hostport), requestMode); } public MultiProtocolSocketAddress getAddress() { return address; } private boolean isEphemeral() { return isEphemeral; } @Override public String toString() { return "NTPPeer [address=" + address + ", isEphemeral=" + isEphemeral + ", ephemeralUpdateTime=" + ephemeralUpdateTime + "]"; } @Override public int hashCode() { return Objects.hash(address); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; NTPPeer other = (NTPPeer) obj; return Objects.equals(address, other.address); } private void setPeerPollInterval(long peerPollInterval) { this.peerPollInterval = peerPollInterval; } } private static class CheckListItem { private NTPPeer peer; private Long128 timeStamp; private long createTime = System.nanoTime(); private CheckListItem(Long128 timeStamp, NTPPeer peer) { this.timeStamp = timeStamp; this.peer = peer; } private boolean checkTimeout() { return System.nanoTime() - createTime > REQUEST_TIMEOUT; } } private Set peers = Collections.synchronizedSet(new HashSet<>()); private Set ephemeralPeers = Collections.synchronizedSet(new HashSet<>()); private ReentrantLock checkLock = new ReentrantLock(); private List checkList = new ArrayList<>(); private NTPContext context; private DatagramSocket dgs; private MultiProtocolSocketAddress bind; public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException { this(context, new MultiProtocolSocketAddress("UDP", "::0", NTP_DEFAULT_PORT)); } public NTPv4Protocol(NTPContext context, MultiProtocolSocketAddress bind) throws UnknownHostException, IOException { this.bind = bind; this.context = context; dgs = bind.listenDatagramSocket(); context.registerIO(this); Thread t4= ThreadTool.makeVThread("NTPv4 Packet Cleaner",()->{ while(isClosed()){ removeTimeoutTimestamp(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t4.start(); Thread tr = new Thread(recv); tr.setName("NTPv4 Receive Thread"); tr.start(); } private void setNTPInformation(NTPv4Packet nv4, NTPPeer peer) { if (peer != null) { long interval = peer.getPollInterval(); int pi = (int) log(interval / 1000000000L, 2); nv4.setPollInterval(pi); } else { long interval = DEFAULT_POLL_INTERVAL; int pi = (int) log(interval / 1000000000L, 2); nv4.setPollInterval(pi); } nv4.setVersionNumber(NTPv4Packet.NTP_VERSION_NUMBER); nv4.setLeapIndicator(context.getLeapIndicator()); int stratum = context.getStratum(); if (stratum == 16) { stratum = 0; } nv4.setStratum(stratum); Long128 precision = context.getLocalPrecision(); byte b = (byte) Math.ceil(log(precision.doubleValue() / (Math.pow(2, 64)), 2)); nv4.setPrecision(b); nv4.setRootDelay((int) context.getRootDelay()); nv4.setRootDispersion((int) context.getCurrentRootDispersion()); nv4.setReferenceTimestamp64(context.getReferenceTimestamp64()); nv4.setTransmitTimestamp64(context.getClock().getCurrentTimeNTP64()); } private void setNTPInformation(NTPv4Packet nv4, NTPv4Packet request, NTPPeer peer) { nv4.setOriginateTimestamp(request.getTransmitTimestamp()); nv4.setReceiveTimestamp64(NTPTimestamps.ntp128To64(request.getDestinationTimestamp128())); setNTPInformation(nv4, peer); } private static double log(double value, double base) { return Math.log(value) / Math.log(base); } public void request(NTPPeer peer) throws IOException { NTPv4Packet nv4 = new NTPv4Packet(context.getClock()); nv4.setMode(peer.requestMode); setNTPInformation(nv4, peer); putOriginTimestamp(nv4, peer); peer.address.getInetAddress(); sendNTPPacket(nv4, peer.address.getSocketAddress()); } private void putOriginTimestamp(NTPv4Packet nv4, NTPPeer peer) { checkLock.lock(); try { checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer)); } finally { checkLock.unlock(); } } private void removeTimeoutTimestamp() { checkLock.lock(); try { for (Iterator iterator = checkList.iterator(); iterator.hasNext(); ) { CheckListItem bigInteger = (CheckListItem) iterator.next(); if (bigInteger.checkTimeout()) { if (showPacket) System.out.println("timeout:" + bigInteger.timeStamp); iterator.remove(); } } }finally{ checkLock.unlock(); } } private void sendNTPPacket(NTPv4Packet nv4, SocketAddress target) throws IOException { ByteBuffer bbf = ByteBuffer.allocate(65535); nv4.writeToChannel(KNEChannels.newWritableChannel(bbf)); bbf.flip(); DatagramPacket dp = new DatagramPacket(bbf.array(), bbf.limit()); dp.setSocketAddress(target); dgs.send(dp); if (showPacket) System.out.println("NTPv4 TX:" + target + " " + nv4); } private NTPPeer checkOriginTimestamp(NTPv4Packet nv4) { checkLock.lock(); try { Long128 ori = nv4.getOriginateTimestamp128(); removeTimeoutTimestamp(); for (Iterator iterator = checkList.iterator(); iterator.hasNext();) { CheckListItem item = (CheckListItem) iterator.next(); if (item.timeStamp.equals(ori)) { iterator.remove(); return item.peer; } } } finally { checkLock.unlock(); } return null; } NTPPeer findPeer(MultiProtocolSocketAddress addr) { Object[] objs = peers.toArray(); for (int i = 0; i < objs.length; i++) { NTPPeer np = (NTPPeer) objs[i]; if (np.getAddress().equals2(addr)) { return np; } } objs = ephemeralPeers.toArray(); for (int i = 0; i < objs.length; i++) { NTPPeer np = (NTPPeer) objs[i]; if (np.getAddress().equals2(addr)) { return np; } } return null; } private Runnable recv = new Runnable() { @Override public void run() { try { while (!dgs.isClosed()) { AtomicReference isa = new AtomicReference<>(); NTPv4Packet nv4 = receiveNTPPacket(isa); MultiProtocolSocketAddress mpsafrom = new MultiProtocolSocketAddress(bind.getProtocol(), isa.get()); switch (nv4.getMode()) { case NTPv4Packet.NTP_SYMMETRIC_ACTIVE: NTPv4Packet nv4r = new NTPv4Packet(context.getClock()); nv4r.setMode(NTPv4Packet.NTP_SYMMETRIC_PASSIVE); setNTPInformation(nv4r, nv4, findPeer(mpsafrom)); sendNTPPacket(nv4r, isa.get()); ephemeralPeers.add(new NTPPeer(mpsafrom, NTPv4Packet.NTP_SYMMETRIC_ACTIVE, true)); break; case NTPv4Packet.NTP_SYMMETRIC_PASSIVE: NTPPeer npr = checkOriginTimestamp(nv4); if (npr != null) { npr.resetEphemeralTimeout(); npr.setPeerPollInterval((1L << nv4.getPollInterval()) * 1000000000); context.putPacket(nv4, mpsafrom); } else { } break; case NTPv4Packet.NTP_CLIENT: NTPv4Packet nv4s = new NTPv4Packet(context.getClock()); nv4s.setMode(NTPv4Packet.NTP_SERVER); setNTPInformation(nv4s, nv4, findPeer(mpsafrom)); sendNTPPacket(nv4s, isa.get()); break; case NTPv4Packet.NTP_SERVER: NTPPeer nprx = checkOriginTimestamp(nv4); if (nprx != null) { nprx.resetEphemeralTimeout(); nprx.setPeerPollInterval((1L << nv4.getPollInterval()) * 1000000000); context.putPacket(nv4, mpsafrom); } else { } break; } } } catch (IOException e) { e.printStackTrace(); } } }; protected void sendReqPackets() throws IOException { List sends = getPeersWillSend(); for (NTPPeer npr : sends) { request(npr); } } protected List getPeersWillSend() { List sends = new ArrayList<>(); peers.forEach((pr) -> { if (pr.checkPollInterval()) { sends.add(pr); } }); ephemeralPeers.removeIf((pr) -> { return pr.checkEphemeralTimeout(); }); ephemeralPeers.forEach((pr) -> { if (pr.checkPollInterval()) { if (!peers.contains(pr)) { sends.add(pr); } } }); return sends; } private NTPv4Packet receiveNTPPacket(AtomicReference isa) throws IOException { ByteBuffer bbf = ByteBuffer.allocate(65535); DatagramPacket dp = new DatagramPacket(bbf.array(), bbf.limit()); dgs.receive(dp); isa.set((InetSocketAddress) dp.getSocketAddress()); NTPv4Packet nv4 = new NTPv4Packet(context.getClock()); nv4.readFromChannel(KNEChannels.newReadableChannel(bbf), dp.getLength()); if (showPacket) System.out.println("NTPv4 RX:" + isa.get() + " " + nv4); return nv4; } public Set getPeers() { return peers; } @Override public void close() throws IOException { dgs.close(); context.unregisterIO(this); } public boolean isClosed() { return dgs.isClosed(); } public static void main(String[] args) throws IOException, InterruptedException { HighAccuracyClock hac = new HighAccuracyClock(); NTPContext context = new NTPContext(hac); System.out.println(context); NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199 nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT)); nvc.getPeers().add(new NTPPeer("{UDP}time.windows.com:123", NTPv4Packet.NTP_CLIENT)); nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE)); /* * for (int i = 0; i < 10000; i++) { Thread.sleep(1000); * System.out.println(context.getCurrentSelfDispersion128()); * System.out.println(context); } */ } }