forked from KNEMC/KLALB
410 lines
12 KiB
Java
410 lines
12 KiB
Java
package org.kne.cloud.network.ntp;
|
|
|
|
import java.io.Closeable;
|
|
import java.io.IOException;
|
|
import java.math.BigInteger;
|
|
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.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
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.MultipurposeSocketAddress;
|
|
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 MultipurposeSocketAddress 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(MultipurposeSocketAddress address, int requestMode) {
|
|
this(address, requestMode, false);
|
|
}
|
|
|
|
protected NTPPeer(MultipurposeSocketAddress 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 MultipurposeSocketAddress(hostport), requestMode);
|
|
}
|
|
|
|
public MultipurposeSocketAddress 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<NTPPeer> peers = Collections.synchronizedSet(new HashSet<>());
|
|
private Set<NTPPeer> ephemeralPeers = Collections.synchronizedSet(new HashSet<>());
|
|
|
|
private ReentrantLock checkLock = new ReentrantLock();
|
|
private List<CheckListItem> checkList = new ArrayList<>();
|
|
|
|
private NTPContext context;
|
|
private DatagramSocket dgs;
|
|
private MultipurposeSocketAddress bind;
|
|
|
|
public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException {
|
|
this(context, new MultipurposeSocketAddress("UDP", "::0", NTP_DEFAULT_PORT));
|
|
}
|
|
|
|
public NTPv4Protocol(NTPContext context, MultipurposeSocketAddress bind) throws UnknownHostException, IOException {
|
|
this.bind = bind;
|
|
this.context = context;
|
|
dgs = bind.listenDatagramSocket();
|
|
context.registerIO(this);
|
|
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 {
|
|
removeTimeoutTimestamp();
|
|
checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer));
|
|
} finally {
|
|
checkLock.unlock();
|
|
}
|
|
}
|
|
|
|
private void removeTimeoutTimestamp() {
|
|
for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext();) {
|
|
CheckListItem bigInteger = (CheckListItem) iterator.next();
|
|
if (bigInteger.checkTimeout()) {
|
|
if (showPacket)
|
|
System.out.println("timeout:" + bigInteger.timeStamp);
|
|
iterator.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
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<CheckListItem> 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(MultipurposeSocketAddress 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<InetSocketAddress> isa = new AtomicReference<>();
|
|
NTPv4Packet nv4 = receiveNTPPacket(isa);
|
|
MultipurposeSocketAddress mpsafrom = new MultipurposeSocketAddress(bind.getType(), 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<NTPPeer> sends = getPeersWillSend();
|
|
for (NTPPeer npr : sends) {
|
|
request(npr);
|
|
}
|
|
|
|
}
|
|
|
|
protected List<NTPPeer> getPeersWillSend() {
|
|
List<NTPPeer> 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<InetSocketAddress> 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<NTPPeer> 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 MultipurposeSocketAddress("{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); }
|
|
*/
|
|
}
|
|
}
|