From f007f9a86d6457b748dd1c4f0fe788d1c94443f1 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 3 Jan 2022 19:05:10 +0000 Subject: [PATCH] Added optional "senderPeerAddress" string to HELLO messages, to allow external IP changes to be detected without using a centralized service. --- .../java/org/qortal/network/Handshake.java | 6 +- src/main/java/org/qortal/network/Network.java | 63 ++++++++++++++++++- .../qortal/network/message/HelloMessage.java | 22 +++++-- 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/qortal/network/Handshake.java b/src/main/java/org/qortal/network/Handshake.java index 78b181ce..d88654cf 100644 --- a/src/main/java/org/qortal/network/Handshake.java +++ b/src/main/java/org/qortal/network/Handshake.java @@ -48,6 +48,9 @@ public enum Handshake { return null; } + // Make a note of the senderPeerAddress, as this should be our public IP + Network.getInstance().ourPeerAddressUpdated(helloMessage.getSenderPeerAddress()); + String versionString = helloMessage.getVersionString(); Matcher matcher = peer.VERSION_PATTERN.matcher(versionString); @@ -87,8 +90,9 @@ public enum Handshake { public void action(Peer peer) { String versionString = Controller.getInstance().getVersionString(); long timestamp = NTP.getTime(); + String senderPeerAddress = peer.getPeerData().getAddress().toString(); - Message helloMessage = new HelloMessage(timestamp, versionString); + Message helloMessage = new HelloMessage(timestamp, versionString, senderPeerAddress); if (!peer.sendMessage(helloMessage)) peer.disconnect("failed to send HELLO"); } diff --git a/src/main/java/org/qortal/network/Network.java b/src/main/java/org/qortal/network/Network.java index c9843ce4..40dde099 100644 --- a/src/main/java/org/qortal/network/Network.java +++ b/src/main/java/org/qortal/network/Network.java @@ -7,7 +7,6 @@ import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters; import org.qortal.block.BlockChain; import org.qortal.controller.Controller; import org.qortal.controller.arbitrary.ArbitraryDataFileManager; -import org.qortal.controller.arbitrary.ArbitraryDataManager; import org.qortal.crypto.Crypto; import org.qortal.data.block.BlockData; import org.qortal.data.network.PeerData; @@ -117,6 +116,9 @@ public class Network { private final Lock mergePeersLock = new ReentrantLock(); + private List ourExternalIpAddressHistory = new ArrayList<>(); + private String ourExternalIpAddress = null; + // Constructors private Network() { @@ -1102,6 +1104,65 @@ public class Network { return new GetUnconfirmedTransactionsMessage(); } + + // External IP / peerAddress tracking + + public void ourPeerAddressUpdated(String peerAddress) { + if (peerAddress == null) { + return; + } + + String[] parts = peerAddress.split(":"); + if (parts.length != 2) { + return; + } + String host = parts[0]; + try { + InetAddress addr = InetAddress.getByName(host); + if (addr.isAnyLocalAddress() || addr.isSiteLocalAddress()) { + // Ignore local addresses + return; + } + } catch (UnknownHostException e) { + return; + } + + this.ourExternalIpAddressHistory.add(host); + + // Limit to 10 entries + while (this.ourExternalIpAddressHistory.size() > 10) { + this.ourExternalIpAddressHistory.remove(0); + } + + // If we've had 3 consecutive matching addresses, and they're different from + // our stored IP address value, treat it as updated. + + int size = this.ourExternalIpAddressHistory.size(); + if (size < 3) { + // Need at least 3 readings + return; + } + + String ip1 = this.ourExternalIpAddressHistory.get(size - 1); + String ip2 = this.ourExternalIpAddressHistory.get(size - 2); + String ip3 = this.ourExternalIpAddressHistory.get(size - 3); + + if (!Objects.equals(ip1, this.ourExternalIpAddress)) { + // Latest reading doesn't match our known value + if (Objects.equals(ip1, ip2) && Objects.equals(ip1, ip3)) { + // Last 3 readings were the same - i.e. more than one peer agreed on the new IP address + this.ourExternalIpAddress = ip1; + this.onExternalIpUpdate(ip1); + } + } + } + + public void onExternalIpUpdate(String ipAddress) { + LOGGER.info("External IP address updated to {}", ipAddress); + + } + + // Peer-management calls public void noteToSelf(Peer peer) { diff --git a/src/main/java/org/qortal/network/message/HelloMessage.java b/src/main/java/org/qortal/network/message/HelloMessage.java index 537daf48..1b6de17d 100644 --- a/src/main/java/org/qortal/network/message/HelloMessage.java +++ b/src/main/java/org/qortal/network/message/HelloMessage.java @@ -13,16 +13,18 @@ public class HelloMessage extends Message { private final long timestamp; private final String versionString; + private final String senderPeerAddress; - private HelloMessage(int id, long timestamp, String versionString) { + private HelloMessage(int id, long timestamp, String versionString, String senderPeerAddress) { super(id, MessageType.HELLO); this.timestamp = timestamp; this.versionString = versionString; + this.senderPeerAddress = senderPeerAddress; } - public HelloMessage(long timestamp, String versionString) { - this(-1, timestamp, versionString); + public HelloMessage(long timestamp, String versionString, String senderPeerAddress) { + this(-1, timestamp, versionString, senderPeerAddress); } public long getTimestamp() { @@ -33,12 +35,22 @@ public class HelloMessage extends Message { return this.versionString; } + public String getSenderPeerAddress() { + return this.senderPeerAddress; + } + public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws TransformationException { long timestamp = byteBuffer.getLong(); String versionString = Serialization.deserializeSizedString(byteBuffer, 255); - return new HelloMessage(id, timestamp, versionString); + // Sender peer address added in v3.0, so is an optional field. Older versions won't send it. + String senderPeerAddress = null; + if (byteBuffer.hasRemaining()) { + senderPeerAddress = Serialization.deserializeSizedString(byteBuffer, 255); + } + + return new HelloMessage(id, timestamp, versionString, senderPeerAddress); } @Override @@ -49,6 +61,8 @@ public class HelloMessage extends Message { Serialization.serializeSizedString(bytes, this.versionString); + Serialization.serializeSizedString(bytes, this.senderPeerAddress); + return bytes.toByteArray(); }