Skip to content

Commit 4c986cd

Browse files
pingesmacfarla
authored andcommitted
Peering - Find and remove peers from the peer table that share the same IP and TCP port with different discovery ports (besu-eth#7089)
Find and remove peers from the peer table that share the same IP and TCP port with different discovery ports --------- Signed-off-by: stefan.pingel@consensys.net <stefan.pingel@consensys.net> Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com> Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com> Signed-off-by: Justin Florentine <justin+github@florentine.us>
1 parent 61115d4 commit 4c986cd

File tree

7 files changed

+158
-23
lines changed

7 files changed

+158
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
- Remove deprecated Goerli testnet [#7049](https://github.com/hyperledger/besu/pull/7049)
5151
- Default bonsai to use full-flat db and code-storage-by-code-hash [#6984](https://github.com/hyperledger/besu/pull/6894)
5252
- New RPC methods miner_setExtraData and miner_getExtraData [#7078](https://github.com/hyperledger/besu/pull/7078)
53+
- Disconnect peers that have multiple discovery ports since they give us bad neighbours [#7089](https://github.com/hyperledger/besu/pull/7089)
5354

5455
### Bug fixes
5556
- Fix txpool dump/restore race condition [#6665](https://github.com/hyperledger/besu/pull/6665)

ethereum/p2p/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ dependencies {
5656
implementation 'io.tmio:tuweni-io'
5757
implementation 'io.tmio:tuweni-rlp'
5858
implementation 'io.tmio:tuweni-units'
59+
implementation 'org.apache.commons:commons-collections4'
5960
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
6061
implementation 'org.owasp.encoder:encoder'
6162
implementation 'org.xerial.snappy:snappy-java'

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryController.java

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) {
310310
}
311311

312312
final DiscoveryPeer peer = resolvePeer(sender);
313+
if (peer == null) {
314+
return;
315+
}
313316
final Bytes peerId = peer.getId();
314317
switch (packet.getType()) {
315318
case PING:
@@ -399,30 +402,33 @@ private List<DiscoveryPeer> getPeersFromNeighborsPacket(final Packet packet) {
399402
}
400403

401404
private boolean addToPeerTable(final DiscoveryPeer peer) {
402-
// Reset the last seen timestamp.
403-
final long now = System.currentTimeMillis();
404-
if (peer.getFirstDiscovered() == 0) {
405-
peer.setFirstDiscovered(now);
406-
}
407-
peer.setLastSeen(now);
405+
final PeerTable.AddResult result = peerTable.tryAdd(peer);
406+
if (result.getOutcome() != PeerTable.AddResult.AddOutcome.INVALID) {
408407

409-
if (peer.getStatus() != PeerDiscoveryStatus.BONDED) {
410-
peer.setStatus(PeerDiscoveryStatus.BONDED);
411-
connectOnRlpxLayer(peer);
412-
}
408+
// Reset the last seen timestamp.
409+
final long now = System.currentTimeMillis();
410+
if (peer.getFirstDiscovered() == 0) {
411+
peer.setFirstDiscovered(now);
412+
}
413+
peer.setLastSeen(now);
413414

414-
final PeerTable.AddResult result = peerTable.tryAdd(peer);
415+
if (peer.getStatus() != PeerDiscoveryStatus.BONDED) {
416+
peer.setStatus(PeerDiscoveryStatus.BONDED);
417+
connectOnRlpxLayer(peer);
418+
}
415419

416-
if (result.getOutcome() == PeerTable.AddResult.AddOutcome.ALREADY_EXISTED) {
417-
// Bump peer.
418-
peerTable.tryEvict(peer);
419-
peerTable.tryAdd(peer);
420-
} else if (result.getOutcome() == PeerTable.AddResult.AddOutcome.BUCKET_FULL) {
421-
peerTable.tryEvict(result.getEvictionCandidate());
422-
peerTable.tryAdd(peer);
423-
}
420+
if (result.getOutcome() == PeerTable.AddResult.AddOutcome.ALREADY_EXISTED) {
421+
// Bump peer.
422+
peerTable.tryEvict(peer);
423+
peerTable.tryAdd(peer);
424+
} else if (result.getOutcome() == PeerTable.AddResult.AddOutcome.BUCKET_FULL) {
425+
peerTable.tryEvict(result.getEvictionCandidate());
426+
peerTable.tryAdd(peer);
427+
}
424428

425-
return true;
429+
return true;
430+
}
431+
return false;
426432
}
427433

428434
void connectOnRlpxLayer(final DiscoveryPeer peer) {
@@ -688,7 +694,9 @@ public void setRetryDelayFunction(final RetryDelayFunction retryDelayFunction) {
688694

689695
public void handleBondingRequest(final DiscoveryPeer peer) {
690696
final DiscoveryPeer peerToBond = resolvePeer(peer);
691-
697+
if (peerToBond == null) {
698+
return;
699+
}
692700
if (peerPermissions.allowOutboundBonding(peerToBond)
693701
&& PeerDiscoveryStatus.KNOWN.equals(peerToBond.getStatus())) {
694702
bond(peerToBond);
@@ -697,6 +705,9 @@ public void handleBondingRequest(final DiscoveryPeer peer) {
697705

698706
// Load the peer first from the table, then from bonding cache or use the instance that comes in.
699707
private DiscoveryPeer resolvePeer(final DiscoveryPeer peer) {
708+
if (peerTable.ipAddressIsInvalid(peer.getEndpoint())) {
709+
return null;
710+
}
700711
final Optional<DiscoveryPeer> maybeKnownPeer =
701712
peerTable.get(peer).filter(known -> known.discoveryEndpointMatches(peer));
702713
DiscoveryPeer resolvedPeer = maybeKnownPeer.orElse(peer);

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTable.java

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919

2020
import org.hyperledger.besu.crypto.Hash;
2121
import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer;
22+
import org.hyperledger.besu.ethereum.p2p.discovery.Endpoint;
2223
import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryStatus;
2324
import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable.AddResult.AddOutcome;
2425
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
2526
import org.hyperledger.besu.ethereum.p2p.peers.PeerId;
2627

2728
import java.util.Arrays;
29+
import java.util.LinkedHashMap;
2830
import java.util.List;
2931
import java.util.Map;
3032
import java.util.Optional;
@@ -33,6 +35,7 @@
3335
import java.util.stream.Stream;
3436

3537
import com.google.common.hash.BloomFilter;
38+
import org.apache.commons.collections4.queue.CircularFifoQueue;
3639
import org.apache.tuweni.bytes.Bytes;
3740

3841
/**
@@ -51,6 +54,10 @@ public class PeerTable {
5154
private final Map<Bytes, Integer> distanceCache;
5255
private BloomFilter<Bytes> idBloom;
5356
private int evictionCnt = 0;
57+
private final LinkedHashMapWithMaximumSize<String, Integer> ipAddressCheckMap =
58+
new LinkedHashMapWithMaximumSize<>(DEFAULT_BUCKET_SIZE * N_BUCKETS);
59+
private final CircularFifoQueue<String> invalidIPs =
60+
new CircularFifoQueue<>(DEFAULT_BUCKET_SIZE * N_BUCKETS);
5461

5562
/**
5663
* Builds a new peer table, where distance is calculated using the provided nodeId as a baseline.
@@ -97,13 +104,17 @@ public Optional<DiscoveryPeer> get(final PeerId peer) {
97104
* <li>the operation failed because the k-bucket was full, in which case a candidate is proposed
98105
* for eviction.
99106
* <li>the operation failed because the peer already existed.
107+
* <li>the operation failed because the IP address is invalid.
100108
* </ul>
101109
*
102110
* @param peer The peer to add.
103111
* @return An object indicating the outcome of the operation.
104112
* @see AddOutcome
105113
*/
106114
public AddResult tryAdd(final DiscoveryPeer peer) {
115+
if (ipAddressIsInvalid(peer.getEndpoint())) {
116+
return AddResult.invalid();
117+
}
107118
final Bytes id = peer.getId();
108119
final int distance = distanceFrom(peer);
109120

@@ -129,6 +140,7 @@ public AddResult tryAdd(final DiscoveryPeer peer) {
129140
if (!res.isPresent()) {
130141
idBloom.put(id);
131142
distanceCache.put(id, distance);
143+
ipAddressCheckMap.put(getKey(peer.getEndpoint()), peer.getEndpoint().getUdpPort());
132144
return AddResult.added();
133145
}
134146

@@ -200,6 +212,34 @@ public Stream<DiscoveryPeer> streamAllPeers() {
200212
return Arrays.stream(table).flatMap(e -> e.getPeers().stream());
201213
}
202214

215+
boolean ipAddressIsInvalid(final Endpoint endpoint) {
216+
final String key = getKey(endpoint);
217+
if (invalidIPs.contains(key)) {
218+
return true;
219+
}
220+
if (ipAddressCheckMap.containsKey(key) && ipAddressCheckMap.get(key) != endpoint.getUdpPort()) {
221+
// This peer has multiple discovery services on the same IP address + TCP port.
222+
invalidIPs.add(key);
223+
for (final Bucket bucket : table) {
224+
bucket.getPeers().stream()
225+
.filter(p -> p.getEndpoint().getHost().equals(endpoint.getHost()))
226+
.forEach(p -> evictAndStore(p, bucket, key));
227+
}
228+
return true;
229+
} else {
230+
return false;
231+
}
232+
}
233+
234+
private void evictAndStore(final DiscoveryPeer peer, final Bucket bucket, final String key) {
235+
bucket.evict(peer);
236+
invalidIPs.add(key);
237+
}
238+
239+
private static String getKey(final Endpoint endpoint) {
240+
return endpoint.getHost() + endpoint.getFunctionalTcpPort();
241+
}
242+
203243
/**
204244
* Calculates the XOR distance between the keccak-256 hashes of our node ID and the provided
205245
* {@link DiscoveryPeer}.
@@ -216,6 +256,7 @@ private int distanceFrom(final PeerId peer) {
216256

217257
/** A class that encapsulates the result of a peer addition to the table. */
218258
public static class AddResult {
259+
219260
/** The outcome of the operation. */
220261
public enum AddOutcome {
221262

@@ -229,7 +270,10 @@ public enum AddOutcome {
229270
ALREADY_EXISTED,
230271

231272
/** The caller requested to add ourselves. */
232-
SELF
273+
SELF,
274+
275+
/** The peer was not added because the IP address is invalid. */
276+
INVALID
233277
}
234278

235279
private final AddOutcome outcome;
@@ -256,6 +300,10 @@ static AddResult self() {
256300
return new AddResult(AddOutcome.SELF, null);
257301
}
258302

303+
public static AddResult invalid() {
304+
return new AddResult((AddOutcome.INVALID), null);
305+
}
306+
259307
public AddOutcome getOutcome() {
260308
return outcome;
261309
}
@@ -265,6 +313,20 @@ public Peer getEvictionCandidate() {
265313
}
266314
}
267315

316+
private static class LinkedHashMapWithMaximumSize<K, V> extends LinkedHashMap<K, V> {
317+
private final int maxSize;
318+
319+
public LinkedHashMapWithMaximumSize(final int maxSize) {
320+
super(maxSize, 0.75f, false);
321+
this.maxSize = maxSize;
322+
}
323+
324+
@Override
325+
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
326+
return size() > maxSize;
327+
}
328+
}
329+
268330
static class EvictResult {
269331
public enum EvictOutcome {
270332
EVICTED,

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshState.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ private void neighboursCancelOutstandingRequests() {
185185
private boolean satisfiesMapAdditionCriteria(final DiscoveryPeer discoPeer) {
186186
return !oneTrueMap.containsKey(discoPeer.getId())
187187
&& (initialPeers.contains(discoPeer) || !peerTable.get(discoPeer).isPresent())
188-
&& !discoPeer.getId().equals(localPeer.getId());
188+
&& !discoPeer.getId().equals(localPeer.getId())
189+
&& !peerTable.ipAddressIsInvalid(discoPeer.getEndpoint());
189190
}
190191

191192
void onNeighboursReceived(final DiscoveryPeer peer, final List<DiscoveryPeer> peers) {

ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTableTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,62 @@ public void evictSelfPeerShouldReturnSelfOutcome() {
184184
final EvictResult evictResult = table.tryEvict(peer);
185185
assertThat(evictResult.getOutcome()).isEqualTo(EvictOutcome.SELF);
186186
}
187+
188+
@Test
189+
public void ipAddressIsInvalidReturnsTrue() {
190+
final Endpoint endpoint1 = new Endpoint("1.1.1.1", 2, Optional.of(Integer.valueOf(1)));
191+
final Endpoint endpoint2 = new Endpoint("1.1.1.1", 3, Optional.of(Integer.valueOf(1)));
192+
final DiscoveryPeer peer1 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint1);
193+
final DiscoveryPeer peer2 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint2);
194+
final PeerTable table = new PeerTable(Bytes.random(64));
195+
196+
final PeerTable.AddResult addResult1 = table.tryAdd(peer1);
197+
assertThat(addResult1.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome());
198+
199+
assertThat(table.ipAddressIsInvalid(peer2.getEndpoint())).isEqualTo(true);
200+
}
201+
202+
@Test
203+
public void ipAddressIsInvalidReturnsFalse() {
204+
final Endpoint endpoint1 = new Endpoint("1.1.1.1", 2, Optional.of(Integer.valueOf(1)));
205+
final Endpoint endpoint2 = new Endpoint("1.1.1.1", 3, Optional.of(Integer.valueOf(2)));
206+
final DiscoveryPeer peer1 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint1);
207+
final DiscoveryPeer peer2 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint2);
208+
final PeerTable table = new PeerTable(Bytes.random(64));
209+
210+
final PeerTable.AddResult addResult1 = table.tryAdd(peer1);
211+
assertThat(addResult1.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome());
212+
213+
assertThat(table.ipAddressIsInvalid(peer2.getEndpoint())).isEqualTo(false);
214+
}
215+
216+
@Test
217+
public void invalidIPAddressNotAdded() {
218+
final Endpoint endpoint1 = new Endpoint("1.1.1.1", 2, Optional.of(Integer.valueOf(1)));
219+
final Endpoint endpoint2 = new Endpoint("1.1.1.1", 3, Optional.of(Integer.valueOf(1)));
220+
final DiscoveryPeer peer1 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint1);
221+
final DiscoveryPeer peer2 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint2);
222+
final PeerTable table = new PeerTable(Bytes.random(64));
223+
224+
final PeerTable.AddResult addResult1 = table.tryAdd(peer1);
225+
assertThat(addResult1.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome());
226+
227+
final PeerTable.AddResult addResult2 = table.tryAdd(peer2);
228+
assertThat(addResult2.getOutcome()).isEqualTo(PeerTable.AddResult.invalid().getOutcome());
229+
}
230+
231+
@Test
232+
public void validIPAddressAdded() {
233+
final Endpoint endpoint1 = new Endpoint("1.1.1.1", 2, Optional.of(Integer.valueOf(1)));
234+
final Endpoint endpoint2 = new Endpoint("1.1.1.1", 3, Optional.of(Integer.valueOf(2)));
235+
final DiscoveryPeer peer1 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint1);
236+
final DiscoveryPeer peer2 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint2);
237+
final PeerTable table = new PeerTable(Bytes.random(64));
238+
239+
final PeerTable.AddResult addResult1 = table.tryAdd(peer1);
240+
assertThat(addResult1.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome());
241+
242+
final PeerTable.AddResult addResult2 = table.tryAdd(peer2);
243+
assertThat(addResult2.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome());
244+
}
187245
}

gradle/versions.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ dependencyManagement {
134134
dependency 'org.apache.commons:commons-compress:1.26.0'
135135
dependency 'org.apache.commons:commons-lang3:3.14.0'
136136
dependency 'org.apache.commons:commons-text:1.11.0'
137+
dependency 'org.apache.commons:commons-collections4:4.4'
137138

138139
dependencySet(group: 'org.apache.logging.log4j', version: '2.22.1') {
139140
entry 'log4j-api'

0 commit comments

Comments
 (0)