Files
KLALB/src/org/kne/cloud/network/ntp/NTPv4Protocol.java
T
2026-07-07 00:22:32 +08:00

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); }
*/
}
}