Fix authentication issues, store address

This commit is contained in:
Manfred Karrer 2015-11-12 02:00:04 +01:00
parent f8adaaed62
commit 85427d7bde
22 changed files with 553 additions and 598 deletions

View file

@ -78,6 +78,15 @@ public class Storage<T extends Serializable> {
this.dir = dir; this.dir = dir;
} }
@Nullable
public T initAndGetPersisted(String fileName) {
this.fileName = fileName;
storageFile = new File(dir, fileName);
fileManager = new FileManager<>(dir, storageFile, 600, TimeUnit.MILLISECONDS);
return getPersisted();
}
@Nullable @Nullable
public T initAndGetPersisted(T serializable) { public T initAndGetPersisted(T serializable) {
return initAndGetPersisted(serializable, serializable.getClass().getSimpleName()); return initAndGetPersisted(serializable, serializable.getClass().getSimpleName());
@ -90,15 +99,23 @@ public class Storage<T extends Serializable> {
storageFile = new File(dir, fileName); storageFile = new File(dir, fileName);
fileManager = new FileManager<>(dir, storageFile, 600, TimeUnit.MILLISECONDS); fileManager = new FileManager<>(dir, storageFile, 600, TimeUnit.MILLISECONDS);
return getPersisted(serializable); return getPersisted();
}
public void queueUpForSave() {
queueUpForSave(serializable);
} }
// Save delayed and on a background thread // Save delayed and on a background thread
public void queueUpForSave() { public void queueUpForSave(T serializable) {
if (serializable != null) {
log.trace("save " + fileName); log.trace("save " + fileName);
checkNotNull(storageFile, "storageFile = null. Call setupFileStorage before using read/write."); checkNotNull(storageFile, "storageFile = null. Call setupFileStorage before using read/write.");
fileManager.saveLater(serializable); fileManager.saveLater(serializable);
} else {
log.trace("queueUpForSave called but no serializable set");
}
} }
public void remove(String fileName) { public void remove(String fileName) {
@ -113,17 +130,17 @@ public class Storage<T extends Serializable> {
// We do the file read on the UI thread to avoid problems from multi threading. // We do the file read on the UI thread to avoid problems from multi threading.
// Data are small and read is done only at startup, so it is no performance issue. // Data are small and read is done only at startup, so it is no performance issue.
@Nullable @Nullable
private T getPersisted(T serializable) { private T getPersisted() {
if (storageFile.exists()) { if (storageFile.exists()) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
try { try {
T persistedObject = fileManager.read(storageFile); T persistedObject = fileManager.read(storageFile);
log.trace("Read {} completed in {}msec", serializable.getClass().getSimpleName(), System.currentTimeMillis() - now); log.trace("Read {} completed in {}msec", storageFile, System.currentTimeMillis() - now);
// If we did not get any exception we can be sure the data are consistent so we make a backup // If we did not get any exception we can be sure the data are consistent so we make a backup
now = System.currentTimeMillis(); now = System.currentTimeMillis();
fileManager.backupFile(fileName); fileManager.backupFile(fileName);
log.trace("Backup {} completed in {}msec", serializable.getClass().getSimpleName(), System.currentTimeMillis() - now); log.trace("Backup {} completed in {}msec", storageFile, System.currentTimeMillis() - now);
return persistedObject; return persistedObject;
} catch (ClassCastException | IOException e) { } catch (ClassCastException | IOException e) {

View file

@ -44,6 +44,7 @@ import org.reactfx.util.FxTimer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Named; import javax.inject.Named;
import java.io.File; import java.io.File;
import java.time.Duration; import java.time.Duration;
@ -176,7 +177,7 @@ public class OpenOfferManager {
shutDown(null); shutDown(null);
} }
public void shutDown(Runnable completeHandler) { public void shutDown(@Nullable Runnable completeHandler) {
if (timer != null) if (timer != null)
timer.cancel(); timer.cancel();
@ -188,6 +189,7 @@ public class OpenOfferManager {
offerBookService.removeOfferAtShutDown(openOffer.getOffer()); offerBookService.removeOfferAtShutDown(openOffer.getOffer());
} }
if (completeHandler != null)
FxTimer.runLater(Duration.ofMillis(500), completeHandler::run); FxTimer.runLater(Duration.ofMillis(500), completeHandler::run);
} }
} }

View file

@ -183,21 +183,23 @@ class MainViewModel implements ViewModel {
private BooleanProperty initP2PNetwork() { private BooleanProperty initP2PNetwork() {
final BooleanProperty initialDataReady = new SimpleBooleanProperty(); final BooleanProperty initialDataReady = new SimpleBooleanProperty();
splashP2PNetworkInfo.set("Connecting to Tor network..."); splashP2PNetworkInfo.set("Connecting to Tor network...");
p2PService.start(new P2PServiceListener() { p2PService.start(new P2PServiceListener() {
@Override @Override
public void onTorNodeReady() { public void onTorNodeReady() {
splashP2PNetworkInfo.set("Publishing Tor Hidden Service..."); splashP2PNetworkInfo.set("Publishing Tor Hidden Service...");
p2PNetworkInfo.set(splashP2PNetworkInfo.get());
p2PNetworkIconId.set("image-connection-tor"); p2PNetworkIconId.set("image-connection-tor");
} }
@Override @Override
public void onHiddenServicePublished() { public void onHiddenServicePublished() {
splashP2PNetworkInfo.set("Authenticating to a seed node..."); splashP2PNetworkInfo.set("Authenticating to a seed node...");
p2PNetworkInfo.set(splashP2PNetworkInfo.get());
} }
@Override @Override
public void onRequestingDataCompleted() { public void onRequestingDataCompleted() {
p2PNetworkInfo.set("Publishing Tor Hidden Service...");
initialDataReady.set(true); initialDataReady.set(true);
} }
@ -340,14 +342,14 @@ class MainViewModel implements ViewModel {
// update nr of peers in footer // update nr of peers in footer
p2PService.numAuthenticatedPeers.addListener((observable, oldValue, newValue) -> updateP2pNetworkInfo()); p2PService.getNumAuthenticatedPeers().addListener((observable, oldValue, newValue) -> updateP2pNetworkInfo());
// now show app // now show app
showAppScreen.set(true); showAppScreen.set(true);
} }
private void updateP2pNetworkInfo() { private void updateP2pNetworkInfo() {
p2PNetworkInfo.set("Nr. of authenticated connections: " + p2PService.numAuthenticatedPeers.get()); p2PNetworkInfo.set("Nr. of authenticated connections: " + p2PService.getNumAuthenticatedPeers().get());
} }
private void displayAlertIfPresent(Alert alert) { private void displayAlertIfPresent(Alert alert) {

View file

@ -17,6 +17,7 @@
package io.bitsquare.gui.main.market; package io.bitsquare.gui.main.market;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple2;
import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.FxmlView;
@ -134,7 +135,8 @@ public class MarketView extends ActivatableViewAndModel<TabPane, MarketViewModel
newValue -> { newValue -> {
String code = newValue.getCode(); String code = newValue.getCode();
areaChart.setTitle("Offer book for " + newValue.getName()); areaChart.setTitle("Offer book for " + newValue.getName());
xAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(xAxis, "", " " + code + "/BTC")); xAxis.setLabel(priceColumnLabel.get());
xAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(xAxis, "", ""));
priceColumnLabel.set("Price (" + code + "/BTC)"); priceColumnLabel.set("Price (" + code + "/BTC)");
volumeColumnLabel.set("Volume (" + code + ")"); volumeColumnLabel.set("Volume (" + code + ")");
}); });
@ -156,7 +158,7 @@ public class MarketView extends ActivatableViewAndModel<TabPane, MarketViewModel
TableView<Offer> tableView = new TableView(); TableView<Offer> tableView = new TableView();
// price // price
TableColumn<Offer, Offer> priceColumn = new TableColumn<>("Price (EUR/BTC)"); TableColumn<Offer, Offer> priceColumn = new TableColumn<>();
priceColumn.textProperty().bind(priceColumnLabel); priceColumn.textProperty().bind(priceColumnLabel);
priceColumn.setMinWidth(120); priceColumn.setMinWidth(120);
priceColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); priceColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
@ -237,7 +239,7 @@ public class MarketView extends ActivatableViewAndModel<TabPane, MarketViewModel
Label titleLabel = new Label(direction.equals(Offer.Direction.BUY) ? "Offers for buy bitcoin (bid)" : "Offers for sell bitcoin (ask)"); Label titleLabel = new Label(direction.equals(Offer.Direction.BUY) ? "Offers for buy bitcoin (bid)" : "Offers for sell bitcoin (ask)");
titleLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 16; -fx-alignment: center"); titleLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 16; -fx-alignment: center");
titleLabel.prefWidthProperty().bind(tableView.widthProperty()); UserThread.execute(() -> titleLabel.prefWidthProperty().bind(tableView.widthProperty()));
VBox vBox = new VBox(); VBox vBox = new VBox();
vBox.setSpacing(10); vBox.setSpacing(10);
@ -252,13 +254,13 @@ public class MarketView extends ActivatableViewAndModel<TabPane, MarketViewModel
xAxis = new NumberAxis(); xAxis = new NumberAxis();
xAxis.setForceZeroInRange(false); xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(true); xAxis.setAutoRanging(true);
xAxis.setLabel("Price"); xAxis.setLabel(priceColumnLabel.get());
yAxis = new NumberAxis(); yAxis = new NumberAxis();
yAxis.setForceZeroInRange(false); yAxis.setForceZeroInRange(false);
yAxis.setAutoRanging(true); yAxis.setAutoRanging(true);
yAxis.setLabel("Amount"); yAxis.setLabel("Amount in BTC");
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, "", " BTC")); yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, "", ""));
seriesBuy = new XYChart.Series(); seriesBuy = new XYChart.Series();
seriesBuy.setName("Offers for buy bitcoin "); seriesBuy.setName("Offers for buy bitcoin ");

View file

@ -127,7 +127,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
} }
numAuthenticatedPeersChangeListener = (observable, oldValue, newValue) -> updateAuthenticatedPeersTextArea(); numAuthenticatedPeersChangeListener = (observable, oldValue, newValue) -> updateAuthenticatedPeersTextArea();
p2PService.numAuthenticatedPeers.addListener(numAuthenticatedPeersChangeListener); p2PService.getNumAuthenticatedPeers().addListener(numAuthenticatedPeersChangeListener);
updateAuthenticatedPeersTextArea(); updateAuthenticatedPeersTextArea();
} }
@ -136,7 +136,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
if (p2PServiceListener != null) if (p2PServiceListener != null)
p2PService.removeP2PServiceListener(p2PServiceListener); p2PService.removeP2PServiceListener(p2PServiceListener);
if (numAuthenticatedPeersChangeListener != null) if (numAuthenticatedPeersChangeListener != null)
p2PService.numAuthenticatedPeers.removeListener(numAuthenticatedPeersChangeListener); p2PService.getNumAuthenticatedPeers().removeListener(numAuthenticatedPeersChangeListener);
} }
private void updateAuthenticatedPeersTextArea() { private void updateAuthenticatedPeersTextArea() {

View file

@ -33,7 +33,7 @@
<logger name="org.bitcoinj.core.BitcoinSerializer" level="WARN"/> <logger name="org.bitcoinj.core.BitcoinSerializer" level="WARN"/>
<logger name="org.bitcoinj.core.Peer" level="WARN"/> <logger name="org.bitcoinj.core.Peer" level="WARN"/>
<logger name="org.bitcoinj.core.HeadersMessage" level="WARN"/> <logger name="org.bitcoinj.core.HeadersMessage" level="WARN"/>
<logger name="org.bitcoinj.core.AbstractBlockChain" level="WARN"/> <logger name="org.bitcoinj.core.AbstractBlockChain" level="ERROR"/>
<!-- <!--
<logger name="io.netty" level="OFF"/> <logger name="io.netty" level="OFF"/>

View file

@ -13,11 +13,11 @@ public final class SealedAndSignedMessage implements MailboxMessage {
private final int networkId = Version.NETWORK_ID; private final int networkId = Version.NETWORK_ID;
public final SealedAndSigned sealedAndSigned; public final SealedAndSigned sealedAndSigned;
public final byte[] blurredAddressHash; public final byte[] addressPrefixHash;
public SealedAndSignedMessage(SealedAndSigned sealedAndSigned, byte[] blurredAddressHash) { public SealedAndSignedMessage(SealedAndSigned sealedAndSigned, byte[] addressPrefixHash) {
this.sealedAndSigned = sealedAndSigned; this.sealedAndSigned = sealedAndSigned;
this.blurredAddressHash = blurredAddressHash; this.addressPrefixHash = addressPrefixHash;
} }
@Override @Override
@ -35,7 +35,7 @@ public final class SealedAndSignedMessage implements MailboxMessage {
return "SealedAndSignedMessage{" + return "SealedAndSignedMessage{" +
"networkId=" + networkId + "networkId=" + networkId +
", sealedAndSigned=" + sealedAndSigned + ", sealedAndSigned=" + sealedAndSigned +
", receiverAddressMaskHash.hashCode()=" + Arrays.toString(blurredAddressHash).hashCode() + ", receiverAddressMaskHash.hashCode()=" + Arrays.toString(addressPrefixHash).hashCode() +
'}'; '}';
} }
} }

View file

@ -8,7 +8,7 @@ import java.util.regex.Pattern;
public class Address implements Serializable { public class Address implements Serializable {
public final String hostName; public final String hostName;
public final int port; public final int port;
transient private byte[] blurredAddress; transient private byte[] addressPrefixHash;
public Address(String hostName, int port) { public Address(String hostName, int port) {
this.hostName = hostName; this.hostName = hostName;
@ -26,10 +26,10 @@ public class Address implements Serializable {
} }
// We use just a few chars form or address to blur the potential receiver for sent messages // We use just a few chars form or address to blur the potential receiver for sent messages
public byte[] getBlurredAddress() { public byte[] getAddressPrefixHash() {
if (blurredAddress == null) if (addressPrefixHash == null)
blurredAddress = Hash.getHash(getFullAddress().substring(0, 2)); addressPrefixHash = Hash.getHash(getFullAddress().substring(0, 2));
return blurredAddress; return addressPrefixHash;
} }
@Override @Override

View file

@ -16,9 +16,7 @@ import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.crypto.SealedAndSignedMessage; import io.bitsquare.crypto.SealedAndSignedMessage;
import io.bitsquare.p2p.messaging.*; import io.bitsquare.p2p.messaging.*;
import io.bitsquare.p2p.network.*; import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.peers.Peer;
import io.bitsquare.p2p.peers.PeerGroup; import io.bitsquare.p2p.peers.PeerGroup;
import io.bitsquare.p2p.peers.PeerListener;
import io.bitsquare.p2p.seed.SeedNodesRepository; import io.bitsquare.p2p.seed.SeedNodesRepository;
import io.bitsquare.p2p.storage.HashMapChangedListener; import io.bitsquare.p2p.storage.HashMapChangedListener;
import io.bitsquare.p2p.storage.ProtectedExpirableDataStorage; import io.bitsquare.p2p.storage.ProtectedExpirableDataStorage;
@ -26,13 +24,10 @@ import io.bitsquare.p2p.storage.data.ExpirableMailboxPayload;
import io.bitsquare.p2p.storage.data.ExpirablePayload; import io.bitsquare.p2p.storage.data.ExpirablePayload;
import io.bitsquare.p2p.storage.data.ProtectedData; import io.bitsquare.p2p.storage.data.ProtectedData;
import io.bitsquare.p2p.storage.data.ProtectedMailboxData; import io.bitsquare.p2p.storage.data.ProtectedMailboxData;
import io.bitsquare.p2p.storage.messages.DataExchangeMessage;
import io.bitsquare.p2p.storage.messages.GetDataRequest; import io.bitsquare.p2p.storage.messages.GetDataRequest;
import io.bitsquare.p2p.storage.messages.GetDataResponse; import io.bitsquare.p2p.storage.messages.GetDataResponse;
import javafx.beans.property.BooleanProperty; import io.bitsquare.storage.Storage;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.*;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.monadic.MonadicBinding; import org.fxmisc.easybind.monadic.MonadicBinding;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -52,11 +47,9 @@ import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* Represents our node in the P2P network * Represents our node in the P2P network
*/ */
public class P2PService implements SetupListener, MessageListener, ConnectionListener, PeerListener { public class P2PService implements SetupListener, MessageListener, ConnectionListener {
private static final Logger log = LoggerFactory.getLogger(P2PService.class); private static final Logger log = LoggerFactory.getLogger(P2PService.class);
private static final int RETRY_GET_DATA = 10 * 1000;
private final SeedNodesRepository seedNodesRepository; private final SeedNodesRepository seedNodesRepository;
private final int port; private final int port;
private final File torDir; private final File torDir;
@ -64,25 +57,29 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
@Nullable @Nullable
private final EncryptionService encryptionService; private final EncryptionService encryptionService;
private final KeyRing keyRing; private final KeyRing keyRing;
private final File storageDir;
// set in init
private NetworkNode networkNode; private NetworkNode networkNode;
private PeerGroup peerGroup; private PeerGroup peerGroup;
private ProtectedExpirableDataStorage dataStorage; private ProtectedExpirableDataStorage dataStorage;
private final CopyOnWriteArraySet<DecryptedMailListener> decryptedMailListeners = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<DecryptedMailListener> decryptedMailListeners = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<DecryptedMailboxListener> decryptedMailboxListeners = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<DecryptedMailboxListener> decryptedMailboxListeners = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<P2PServiceListener> p2pServiceListeners = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<P2PServiceListener> p2pServiceListeners = new CopyOnWriteArraySet<>();
private final Map<DecryptedMsgWithPubKey, ProtectedMailboxData> mailboxMap = new HashMap<>(); private final Map<DecryptedMsgWithPubKey, ProtectedMailboxData> mailboxMap = new HashMap<>();
private volatile boolean shutDownInProgress;
private Address connectedSeedNode;
private final Set<Address> authenticatedPeerAddresses = new HashSet<>(); private final Set<Address> authenticatedPeerAddresses = new HashSet<>();
private boolean shutDownComplete;
private final CopyOnWriteArraySet<Runnable> shutDownResultHandlers = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<Runnable> shutDownResultHandlers = new CopyOnWriteArraySet<>();
private final BooleanProperty hiddenServicePublished = new SimpleBooleanProperty(); private final BooleanProperty hiddenServicePublished = new SimpleBooleanProperty();
private final BooleanProperty requestingDataCompleted = new SimpleBooleanProperty(); private final BooleanProperty requestingDataCompleted = new SimpleBooleanProperty();
private final BooleanProperty authenticated = new SimpleBooleanProperty(); private final BooleanProperty authenticated = new SimpleBooleanProperty();
private final IntegerProperty numAuthenticatedPeers = new SimpleIntegerProperty(0);
private Address connectedSeedNode;
private volatile boolean shutDownInProgress;
private boolean shutDownComplete;
private MonadicBinding<Boolean> readyForAuthentication; private MonadicBinding<Boolean> readyForAuthentication;
public final IntegerProperty numAuthenticatedPeers = new SimpleIntegerProperty(0); private final Storage<Address> dbStorage;
private Address myOnionAddress;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -105,40 +102,42 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
this.useLocalhost = useLocalhost; this.useLocalhost = useLocalhost;
this.encryptionService = encryptionService; this.encryptionService = encryptionService;
this.keyRing = keyRing; this.keyRing = keyRing;
this.storageDir = storageDir; dbStorage = new Storage<>(storageDir);
init(networkId); init(networkId, storageDir);
} }
private void init(int networkId) { private void init(int networkId, File storageDir) {
Log.traceCall(); Log.traceCall();
Address persisted = dbStorage.initAndGetPersisted("myOnionAddress");
if (persisted != null)
this.myOnionAddress = persisted;
// network // network
networkNode = useLocalhost ? new LocalhostNetworkNode(port) : new TorNetworkNode(port, torDir); networkNode = useLocalhost ? new LocalhostNetworkNode(port) : new TorNetworkNode(port, torDir);
Set<Address> seedNodeAddresses = seedNodesRepository.geSeedNodeAddresses(useLocalhost, networkId); Set<Address> seedNodeAddresses = seedNodesRepository.geSeedNodeAddresses(useLocalhost, networkId);
// peer group // peer group
peerGroup = new PeerGroup(networkNode, seedNodeAddresses); peerGroup = new PeerGroup(networkNode, seedNodeAddresses);
if (useLocalhost) PeerGroup.setSimulateAuthTorNode(2 * 100); if (useLocalhost)
PeerGroup.setSimulateAuthTorNode(400);
// storage // P2P network storage
dataStorage = new ProtectedExpirableDataStorage(peerGroup, storageDir); dataStorage = new ProtectedExpirableDataStorage(peerGroup, storageDir);
networkNode.addConnectionListener(this); networkNode.addConnectionListener(this);
networkNode.addMessageListener(this); networkNode.addMessageListener(this);
peerGroup.addPeerListener(this);
dataStorage.addHashMapChangedListener(new HashMapChangedListener() { dataStorage.addHashMapChangedListener(new HashMapChangedListener() {
@Override @Override
public void onAdded(ProtectedData entry) { public void onAdded(ProtectedData entry) {
Log.traceCall();
if (entry instanceof ProtectedMailboxData) if (entry instanceof ProtectedMailboxData)
tryDecryptMailboxData((ProtectedMailboxData) entry); processProtectedMailboxData((ProtectedMailboxData) entry);
} }
@Override @Override
public void onRemoved(ProtectedData entry) { public void onRemoved(ProtectedData entry) {
Log.traceCall();
} }
}); });
@ -148,7 +147,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
// we need to have both the initial data delivered and the hidden service published before we // we need to have both the initial data delivered and the hidden service published before we
// bootstrap and authenticate to other nodes. // bootstrap and authenticate to other nodes.
if (newValue) if (newValue)
tryAuthenticateSeedNode(); authenticateSeedNode();
}); });
requestingDataCompleted.addListener((observable, oldValue, newValue) -> { requestingDataCompleted.addListener((observable, oldValue, newValue) -> {
@ -173,21 +172,22 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
// we keep that connection open as the bootstrapping peer will use that for the authentication // we keep that connection open as the bootstrapping peer will use that for the authentication
// as we are not authenticated yet the data adding will not be broadcasted // as we are not authenticated yet the data adding will not be broadcasted
set.stream().forEach(e -> dataStorage.add(e, connection.getPeerAddress())); set.stream().forEach(e -> dataStorage.add(e, connection.getPeerAddress()));
setRequestingDataCompleted(); onRequestingDataComplete();
} else if (message instanceof DataExchangeMessage) {
Log.traceCall(message.toString());
DataExchangeMessage dataExchangeMessage = (DataExchangeMessage) message;
HashSet<ProtectedData> set = dataExchangeMessage.set;
set.stream().forEach(e -> dataStorage.add(e, connection.getPeerAddress()));
setRequestingDataCompleted();
} else if (message instanceof SealedAndSignedMessage) { } else if (message instanceof SealedAndSignedMessage) {
Log.traceCall(message.toString()); Log.traceCall(message.toString());
// Seed nodes don't have set the encryptionService
if (encryptionService != null) { if (encryptionService != null) {
try { try {
SealedAndSignedMessage sealedAndSignedMessage = (SealedAndSignedMessage) message; SealedAndSignedMessage sealedAndSignedMessage = (SealedAndSignedMessage) message;
if (verifyBlurredAddressHash(sealedAndSignedMessage)) { if (verifyAddressPrefixHash(sealedAndSignedMessage)) {
DecryptedMsgWithPubKey decryptedMsgWithPubKey = encryptionService.decryptAndVerify( DecryptedMsgWithPubKey decryptedMsgWithPubKey = encryptionService.decryptAndVerify(
sealedAndSignedMessage.sealedAndSigned); sealedAndSignedMessage.sealedAndSigned);
// We set connectionType to that connection to avoid that is get closed when
// we get too many connection attempts.
// That is used as protection against eclipse attacks.
connection.setConnectionType(ConnectionsType.DIRECT_MSG);
log.info("Received SealedAndSignedMessage and decrypted it: " + decryptedMsgWithPubKey); log.info("Received SealedAndSignedMessage and decrypted it: " + decryptedMsgWithPubKey);
decryptedMailListeners.stream().forEach( decryptedMailListeners.stream().forEach(
e -> e.onMailMessage(decryptedMsgWithPubKey, connection.getPeerAddress())); e -> e.onMailMessage(decryptedMsgWithPubKey, connection.getPeerAddress()));
@ -202,12 +202,6 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
} }
} }
private boolean verifyBlurredAddressHash(SealedAndSignedMessage sealedAndSignedMessage) {
byte[] blurredAddressHash = getAddress().getBlurredAddress();
return blurredAddressHash != null &&
Arrays.equals(blurredAddressHash, sealedAndSignedMessage.blurredAddressHash);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// ConnectionListener implementation // ConnectionListener implementation
@ -223,9 +217,12 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
checkArgument(peerAddress.equals(connection.getPeerAddress()), checkArgument(peerAddress.equals(connection.getPeerAddress()),
"peerAddress must match connection.getPeerAddress()"); "peerAddress must match connection.getPeerAddress()");
authenticatedPeerAddresses.add(peerAddress); authenticatedPeerAddresses.add(peerAddress);
authenticated.set(true);
if (!authenticated.get()) {
authenticated.set(true);
sendGetDataRequestAfterAuthentication(peerAddress, connection);
p2pServiceListeners.stream().forEach(e -> e.onFirstPeerAuthenticated()); p2pServiceListeners.stream().forEach(e -> e.onFirstPeerAuthenticated());
}
numAuthenticatedPeers.set(authenticatedPeerAddresses.size()); numAuthenticatedPeers.set(authenticatedPeerAddresses.size());
} }
@ -244,31 +241,6 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
} }
///////////////////////////////////////////////////////////////////////////////////////////
// PeerListener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onFirstAuthenticatePeer(Peer peer) {
Log.traceCall();
log.trace("onFirstAuthenticatePeer " + peer);
sendGetAllDataMessageAfterAuthentication(peer);
}
@Override
public void onPeerAdded(Peer peer) {
}
@Override
public void onPeerRemoved(Address address) {
}
@Override
public void onConnectionAuthenticated(Connection connection) {
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// SetupListener implementation // SetupListener implementation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -283,10 +255,60 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
sendGetDataRequest(peerGroup.getSeedNodeAddresses()); sendGetDataRequest(peerGroup.getSeedNodeAddresses());
} }
private void sendGetDataRequest(Collection<Address> seedNodeAddresses) {
Log.traceCall(seedNodeAddresses.toString());
if (!seedNodeAddresses.isEmpty()) {
List<Address> remainingSeedNodeAddresses = new ArrayList<>(seedNodeAddresses);
Collections.shuffle(remainingSeedNodeAddresses);
Address candidate = remainingSeedNodeAddresses.remove(0);
log.info("We try to send a GetAllDataMessage request to a random seed node. " + candidate);
SettableFuture<Connection> future = networkNode.sendMessage(candidate, new GetDataRequest());
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.info("Send GetAllDataMessage to " + candidate + " succeeded.");
checkArgument(connectedSeedNode == null, "We have already a connectedSeedNode. That should not happen.");
connectedSeedNode = candidate;
// In case we get called from a retry we check if we need to authenticate
if (!authenticated.get() && hiddenServicePublished.get())
authenticateSeedNode();
else
log.debug("No connected seedNode available.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("Send GetAllDataMessage to " + candidate + " failed. " +
"That is expected if other seed nodes are offline. " +
"Exception:" + throwable.getMessage());
if (!remainingSeedNodeAddresses.isEmpty())
log.trace("We try to connect another random seed node. " + remainingSeedNodeAddresses);
sendGetDataRequest(remainingSeedNodeAddresses);
}
});
} else {
log.info("There is no seed node available for requesting data. " +
"That is expected if no seed node is online.\n" +
"We will try again after a bit ");
onRequestingDataComplete();
UserThread.runAfterRandomDelay(() -> sendGetDataRequest(peerGroup.getSeedNodeAddresses()),
20, 30, TimeUnit.SECONDS);
}
}
@Override @Override
public void onHiddenServicePublished() { public void onHiddenServicePublished() {
Log.traceCall(); Log.traceCall();
checkArgument(networkNode.getAddress() != null, "Address must be set when we have the hidden service ready"); checkArgument(networkNode.getAddress() != null, "Address must be set when we have the hidden service ready");
if (myOnionAddress != null)
checkArgument(networkNode.getAddress() == myOnionAddress, "networkNode.getAddress() must be same as myOnionAddress");
myOnionAddress = networkNode.getAddress();
dbStorage.queueUpForSave(myOnionAddress);
p2pServiceListeners.stream().forEach(e -> e.onHiddenServicePublished()); p2pServiceListeners.stream().forEach(e -> e.onHiddenServicePublished());
@ -300,83 +322,37 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
p2pServiceListeners.stream().forEach(e -> e.onSetupFailed(throwable)); p2pServiceListeners.stream().forEach(e -> e.onSetupFailed(throwable));
} }
private void sendGetDataRequest(Collection<Address> seedNodeAddresses) { private void onRequestingDataComplete() {
Log.traceCall();
if (!seedNodeAddresses.isEmpty()) {
List<Address> remainingSeedNodeAddresses = new ArrayList<>(seedNodeAddresses);
Collections.shuffle(remainingSeedNodeAddresses);
Address candidate = remainingSeedNodeAddresses.remove(0);
log.info("We try to send a GetAllDataMessage request to a random seed node. " + candidate);
SettableFuture<Connection> future = networkNode.sendMessage(candidate, new GetDataRequest());
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.info("Send GetAllDataMessage to " + candidate + " succeeded.");
connectedSeedNode = candidate;
// In case we get called by a retry we check if we need authenticate as well
if (hiddenServicePublished.get() && !authenticated.get()) {
peerGroup.authenticateSeedNode(connectedSeedNode);
} else {
log.debug("No connected seedNode available.");
}
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("Send GetAllDataMessage to " + candidate + " failed. " +
"That is expected if other seed nodes are offline." +
"\nException:" + throwable.getMessage());
if (!remainingSeedNodeAddresses.isEmpty())
log.trace("We try to connect another random seed node. " + remainingSeedNodeAddresses);
sendGetDataRequest(remainingSeedNodeAddresses);
}
});
} else {
log.info("There is no seed node available for requesting data. " +
"That is expected if no seed node is available.\n" +
"We will try again after {} ms", RETRY_GET_DATA);
setRequestingDataCompleted();
UserThread.runAfter(() -> sendGetDataRequest(peerGroup.getSeedNodeAddresses()),
RETRY_GET_DATA, TimeUnit.MILLISECONDS);
}
}
private void setRequestingDataCompleted() {
Log.traceCall(); Log.traceCall();
// 2. (or 3.) Step: We got all data loaded (or no seed node available - should not happen in real operation) // 2. (or 3.) Step: We got all data loaded (or no seed node available - should not happen in real operation)
requestingDataCompleted.set(true); requestingDataCompleted.set(true);
} }
// 4. Step: hiddenServicePublished and allDataLoaded. We start authenticate to the connected seed node. // 4. Step: hiddenServicePublished and allDataLoaded. We start authenticate to the connected seed node.
private void tryAuthenticateSeedNode() { private void authenticateSeedNode() {
Log.traceCall(); Log.traceCall();
if (connectedSeedNode != null) { checkNotNull(connectedSeedNode != null, "connectedSeedNode must not be null");
if (connectedSeedNode != null)
peerGroup.authenticateSeedNode(connectedSeedNode); peerGroup.authenticateSeedNode(connectedSeedNode);
} else {
log.debug("No connected seedNode available.");
}
} }
// 5. Step: // 5. Step:
private void sendGetAllDataMessageAfterAuthentication(final Peer peer) { private void sendGetDataRequestAfterAuthentication(Address peerAddress, Connection connection) {
Log.traceCall(peer.toString()); Log.traceCall(peerAddress.toString());
// We have to exchange the data again as we might have missed pushed data in the meantime // We have to exchange the data again as we might have missed pushed data in the meantime
// After authentication we send our data set to the other peer. // After authentication we send our data set to the other peer.
// As he will do the same we will get his actual data set. // As he will do the same we will get his actual data set.
SettableFuture<Connection> future = networkNode.sendMessage(peer.connection, new DataExchangeMessage(getDataSet())); SettableFuture<Connection> future = networkNode.sendMessage(connection, new GetDataRequest());
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(@Nullable Connection connection) { public void onSuccess(@Nullable Connection connection) {
log.info("sendGetAllDataMessageAfterAuthentication Send DataExchangeMessage to " + peer.address + " succeeded."); log.trace("sendGetDataRequestAfterAuthentication: Send GetDataRequest to " + peerAddress + " succeeded.");
} }
@Override @Override
public void onFailure(@NotNull Throwable throwable) { public void onFailure(@NotNull Throwable throwable) {
log.warn("sendGetAllDataMessageAfterAuthentication Send DataExchangeMessage to " + peer.address + " failed. " + //TODO how to deal with that case?
log.warn("sendGetDataRequestAfterAuthentication: Send GetDataRequest to " + peerAddress + " failed. " +
"Exception:" + throwable.getMessage()); "Exception:" + throwable.getMessage());
} }
}); });
@ -429,13 +405,13 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
shutDownCompleteHandler.run(); shutDownCompleteHandler.run();
else else
shutDownResultHandlers.add(shutDownCompleteHandler); shutDownResultHandlers.add(shutDownCompleteHandler);
log.warn("shutDown already in progress");
}
}
log.debug("shutDown already in progress");
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Messaging // MailMessages
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void sendEncryptedMailMessage(Address peerAddress, PubKeyRing pubKeyRing, MailMessage message, public void sendEncryptedMailMessage(Address peerAddress, PubKeyRing pubKeyRing, MailMessage message,
@ -445,7 +421,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
checkAuthentication(); checkAuthentication();
if (!authenticatedPeerAddresses.contains(peerAddress)) if (!authenticatedPeerAddresses.contains(peerAddress))
peerGroup.authenticateToPeer(peerAddress, peerGroup.authenticateToDirectMessagePeer(peerAddress,
() -> doSendEncryptedMailMessage(peerAddress, pubKeyRing, message, sendMailMessageListener), () -> doSendEncryptedMailMessage(peerAddress, pubKeyRing, message, sendMailMessageListener),
() -> sendMailMessageListener.onFault()); () -> sendMailMessageListener.onFault());
else else
@ -458,7 +434,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
if (encryptionService != null) { if (encryptionService != null) {
try { try {
SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage( SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage(
encryptionService.encryptAndSign(pubKeyRing, message), peerAddress.getBlurredAddress()); encryptionService.encryptAndSign(pubKeyRing, message), peerAddress.getAddressPrefixHash());
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, sealedAndSignedMessage); SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, sealedAndSignedMessage);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
@ -479,6 +455,47 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
} }
} }
///////////////////////////////////////////////////////////////////////////////////////////
// MailboxMessages
///////////////////////////////////////////////////////////////////////////////////////////
private void processProtectedMailboxData(ProtectedMailboxData mailboxData) {
if (encryptionService != null) {
Log.traceCall();
ExpirablePayload expirablePayload = mailboxData.expirablePayload;
if (expirablePayload instanceof ExpirableMailboxPayload) {
ExpirableMailboxPayload expirableMailboxPayload = (ExpirableMailboxPayload) expirablePayload;
SealedAndSignedMessage sealedAndSignedMessage = expirableMailboxPayload.sealedAndSignedMessage;
if (verifyAddressPrefixHash(sealedAndSignedMessage)) {
try {
DecryptedMsgWithPubKey decryptedMsgWithPubKey = encryptionService.decryptAndVerify(
sealedAndSignedMessage.sealedAndSigned);
if (decryptedMsgWithPubKey.message instanceof MailboxMessage) {
MailboxMessage mailboxMessage = (MailboxMessage) decryptedMsgWithPubKey.message;
Address senderAddress = mailboxMessage.getSenderAddress();
checkNotNull(senderAddress, "senderAddress must not be null for mailbox messages");
mailboxMap.put(decryptedMsgWithPubKey, mailboxData);
log.trace("Decryption of SealedAndSignedMessage succeeded. senderAddress="
+ senderAddress + " / my address=" + getAddress());
decryptedMailboxListeners.stream().forEach(
e -> e.onMailboxMessageAdded(decryptedMsgWithPubKey, senderAddress));
} else {
log.warn("tryDecryptMailboxData: Expected MailboxMessage but got other type. " +
"decryptedMsgWithPubKey.message=", decryptedMsgWithPubKey.message);
}
} catch (CryptoException e) {
log.trace("Decryption of SealedAndSignedMessage failed. " +
"That is expected if the message is not intended for us. " + e.getMessage());
}
} else {
log.info("Wrong blurredAddressHash. The message is not intended for us.");
}
}
}
}
public void sendEncryptedMailboxMessage(Address peerAddress, PubKeyRing peersPubKeyRing, public void sendEncryptedMailboxMessage(Address peerAddress, PubKeyRing peersPubKeyRing,
MailboxMessage message, SendMailboxMessageListener sendMailboxMessageListener) { MailboxMessage message, SendMailboxMessageListener sendMailboxMessageListener) {
Log.traceCall(); Log.traceCall();
@ -489,7 +506,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
if (authenticatedPeerAddresses.contains(peerAddress)) { if (authenticatedPeerAddresses.contains(peerAddress)) {
trySendEncryptedMailboxMessage(peerAddress, peersPubKeyRing, message, sendMailboxMessageListener); trySendEncryptedMailboxMessage(peerAddress, peersPubKeyRing, message, sendMailboxMessageListener);
} else { } else {
peerGroup.authenticateToPeer(peerAddress, peerGroup.authenticateToDirectMessagePeer(peerAddress,
() -> trySendEncryptedMailboxMessage(peerAddress, peersPubKeyRing, message, sendMailboxMessageListener), () -> trySendEncryptedMailboxMessage(peerAddress, peersPubKeyRing, message, sendMailboxMessageListener),
() -> { () -> {
log.info("We cannot authenticate to peer. Peer might be offline. We will store message in mailbox."); log.info("We cannot authenticate to peer. Peer might be offline. We will store message in mailbox.");
@ -498,13 +515,14 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
} }
} }
// send message and if it fails (peer offline) we store the data to the network
private void trySendEncryptedMailboxMessage(Address peerAddress, PubKeyRing peersPubKeyRing, private void trySendEncryptedMailboxMessage(Address peerAddress, PubKeyRing peersPubKeyRing,
MailboxMessage message, SendMailboxMessageListener sendMailboxMessageListener) { MailboxMessage message, SendMailboxMessageListener sendMailboxMessageListener) {
Log.traceCall(); Log.traceCall();
if (encryptionService != null) { if (encryptionService != null) {
try { try {
SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage( SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage(
encryptionService.encryptAndSign(peersPubKeyRing, message), peerAddress.getBlurredAddress()); encryptionService.encryptAndSign(peersPubKeyRing, message), peerAddress.getAddressPrefixHash());
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, sealedAndSignedMessage); SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, sealedAndSignedMessage);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
@ -535,7 +553,6 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Data storage // Data storage
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -545,8 +562,8 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
checkAuthentication(); checkAuthentication();
try { try {
return dataStorage.add(dataStorage.getDataWithSignedSeqNr(expirablePayload, ProtectedData protectedData = dataStorage.getDataWithSignedSeqNr(expirablePayload, keyRing.getSignatureKeyPair());
keyRing.getSignatureKeyPair()), networkNode.getAddress()); return dataStorage.add(protectedData, networkNode.getAddress());
} catch (CryptoException e) { } catch (CryptoException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen."); log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
return false; return false;
@ -558,8 +575,11 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
checkAuthentication(); checkAuthentication();
try { try {
dataStorage.add(dataStorage.getMailboxDataWithSignedSeqNr(expirableMailboxPayload, ProtectedMailboxData protectedMailboxData = dataStorage.getMailboxDataWithSignedSeqNr(
keyRing.getSignatureKeyPair(), receiversPublicKey), networkNode.getAddress()); expirableMailboxPayload,
keyRing.getSignatureKeyPair(),
receiversPublicKey);
dataStorage.add(protectedMailboxData, networkNode.getAddress());
} catch (CryptoException e) { } catch (CryptoException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen."); log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
} }
@ -570,8 +590,8 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
checkAuthentication(); checkAuthentication();
try { try {
return dataStorage.remove(dataStorage.getDataWithSignedSeqNr(expirablePayload, ProtectedData protectedData = dataStorage.getDataWithSignedSeqNr(expirablePayload, keyRing.getSignatureKeyPair());
keyRing.getSignatureKeyPair()), networkNode.getAddress()); return dataStorage.remove(protectedData, networkNode.getAddress());
} catch (CryptoException e) { } catch (CryptoException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen."); log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
return false; return false;
@ -585,12 +605,22 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
if (mailboxMap.containsKey(decryptedMsgWithPubKey)) { if (mailboxMap.containsKey(decryptedMsgWithPubKey)) {
ProtectedMailboxData mailboxData = mailboxMap.get(decryptedMsgWithPubKey); ProtectedMailboxData mailboxData = mailboxMap.get(decryptedMsgWithPubKey);
if (mailboxData != null && mailboxData.expirablePayload instanceof ExpirableMailboxPayload) { if (mailboxData != null && mailboxData.expirablePayload instanceof ExpirableMailboxPayload) {
checkArgument(mailboxData.receiversPubKey.equals(keyRing.getSignatureKeyPair().getPublic()), ExpirableMailboxPayload expirableMailboxPayload = (ExpirableMailboxPayload) mailboxData.expirablePayload;
"mailboxData.receiversPubKey is not matching with our key. That must not happen."); PublicKey receiversPubKey = mailboxData.receiversPubKey;
removeMailboxData((ExpirableMailboxPayload) mailboxData.expirablePayload, mailboxData.receiversPubKey); checkArgument(receiversPubKey.equals(keyRing.getSignatureKeyPair().getPublic()),
mailboxMap.remove(decryptedMsgWithPubKey); "receiversPubKey is not matching with our key. That must not happen.");
log.trace("Removed successfully protectedExpirableData."); try {
ProtectedMailboxData protectedMailboxData = dataStorage.getMailboxDataWithSignedSeqNr(
expirableMailboxPayload,
keyRing.getSignatureKeyPair(),
receiversPubKey);
dataStorage.removeMailboxData(protectedMailboxData, networkNode.getAddress());
} catch (CryptoException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
}
mailboxMap.remove(decryptedMsgWithPubKey);
log.trace("Removed successfully decryptedMsgWithPubKey.");
} }
} else { } else {
log.warn("decryptedMsgWithPubKey not found in mailboxMap. That should never happen." + log.warn("decryptedMsgWithPubKey not found in mailboxMap. That should never happen." +
@ -598,20 +628,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
} }
} }
private void removeMailboxData(ExpirableMailboxPayload expirableMailboxPayload, PublicKey receiversPublicKey) {
Log.traceCall();
checkAuthentication();
try {
dataStorage.removeMailboxData(dataStorage.getMailboxDataWithSignedSeqNr(expirableMailboxPayload,
keyRing.getSignatureKeyPair(), receiversPublicKey), networkNode.getAddress());
} catch (CryptoException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
}
}
public Map<ByteArray, ProtectedData> getDataMap() { public Map<ByteArray, ProtectedData> getDataMap() {
Log.traceCall();
return dataStorage.getMap(); return dataStorage.getMap();
} }
@ -621,37 +638,30 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void addDecryptedMailListener(DecryptedMailListener listener) { public void addDecryptedMailListener(DecryptedMailListener listener) {
Log.traceCall();
decryptedMailListeners.add(listener); decryptedMailListeners.add(listener);
} }
public void removeDecryptedMailListener(DecryptedMailListener listener) { public void removeDecryptedMailListener(DecryptedMailListener listener) {
Log.traceCall();
decryptedMailListeners.remove(listener); decryptedMailListeners.remove(listener);
} }
public void addDecryptedMailboxListener(DecryptedMailboxListener listener) { public void addDecryptedMailboxListener(DecryptedMailboxListener listener) {
Log.traceCall();
decryptedMailboxListeners.add(listener); decryptedMailboxListeners.add(listener);
} }
public void removeDecryptedMailboxListener(DecryptedMailboxListener listener) { public void removeDecryptedMailboxListener(DecryptedMailboxListener listener) {
Log.traceCall();
decryptedMailboxListeners.remove(listener); decryptedMailboxListeners.remove(listener);
} }
public void addP2PServiceListener(P2PServiceListener listener) { public void addP2PServiceListener(P2PServiceListener listener) {
Log.traceCall();
p2pServiceListeners.add(listener); p2pServiceListeners.add(listener);
} }
public void removeP2PServiceListener(P2PServiceListener listener) { public void removeP2PServiceListener(P2PServiceListener listener) {
Log.traceCall();
p2pServiceListeners.remove(listener); p2pServiceListeners.remove(listener);
} }
public void addHashSetChangedListener(HashMapChangedListener hashMapChangedListener) { public void addHashSetChangedListener(HashMapChangedListener hashMapChangedListener) {
Log.traceCall();
dataStorage.addHashMapChangedListener(hashMapChangedListener); dataStorage.addHashMapChangedListener(hashMapChangedListener);
} }
@ -680,49 +690,27 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
return authenticatedPeerAddresses; return authenticatedPeerAddresses;
} }
public ReadOnlyIntegerProperty getNumAuthenticatedPeers() {
return numAuthenticatedPeers;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Private // Private
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private HashSet<ProtectedData> getDataSet() { private HashSet<ProtectedData> getDataSet() {
// Log.traceCall(); return new HashSet<>(getDataMap().values());
return new HashSet<>(dataStorage.getMap().values());
} }
private void tryDecryptMailboxData(ProtectedMailboxData mailboxData) { private boolean verifyAddressPrefixHash(SealedAndSignedMessage sealedAndSignedMessage) {
Log.traceCall(); if (myOnionAddress != null) {
if (encryptionService != null) { byte[] blurredAddressHash = myOnionAddress.getAddressPrefixHash();
ExpirablePayload expirablePayload = mailboxData.expirablePayload; return blurredAddressHash != null &&
if (expirablePayload instanceof ExpirableMailboxPayload) { Arrays.equals(blurredAddressHash, sealedAndSignedMessage.addressPrefixHash);
ExpirableMailboxPayload expirableMailboxPayload = (ExpirableMailboxPayload) expirablePayload;
SealedAndSignedMessage sealedAndSignedMessage = expirableMailboxPayload.sealedAndSignedMessage;
if (verifyBlurredAddressHash(sealedAndSignedMessage)) {
try {
DecryptedMsgWithPubKey decryptedMsgWithPubKey = encryptionService.decryptAndVerify(
sealedAndSignedMessage.sealedAndSigned);
if (decryptedMsgWithPubKey.message instanceof MailboxMessage) {
MailboxMessage mailboxMessage = (MailboxMessage) decryptedMsgWithPubKey.message;
Address senderAddress = mailboxMessage.getSenderAddress();
checkNotNull(senderAddress, "senderAddress must not be null for mailbox messages");
mailboxMap.put(decryptedMsgWithPubKey, mailboxData);
log.trace("Decryption of SealedAndSignedMessage succeeded. senderAddress="
+ senderAddress + " / my address=" + getAddress());
decryptedMailboxListeners.stream().forEach(
e -> e.onMailboxMessageAdded(decryptedMsgWithPubKey, senderAddress));
} else { } else {
log.warn("tryDecryptMailboxData: Expected MailboxMessage but got other type. " + log.warn("myOnionAddress must not be null at verifyAddressPrefixHash");
"decryptedMsgWithPubKey.message=", decryptedMsgWithPubKey.message); return false;
}
} catch (CryptoException e) {
log.trace("Decryption of SealedAndSignedMessage failed. " +
"That is expected if the message is not intended for us. " + e.getMessage());
}
} else {
log.info("Wrong blurredAddressHash. The message is not intended for us.");
}
}
} }
} }

View file

@ -31,6 +31,7 @@ public class Connection implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(Connection.class); private static final Logger log = LoggerFactory.getLogger(Connection.class);
private static final int MAX_MSG_SIZE = 5 * 1024 * 1024; // 5 MB of compressed data private static final int MAX_MSG_SIZE = 5 * 1024 * 1024; // 5 MB of compressed data
private static final int SOCKET_TIMEOUT = 30 * 60 * 1000; // 30 min. private static final int SOCKET_TIMEOUT = 30 * 60 * 1000; // 30 min.
private ConnectionsType connectionType;
public static int getMaxMsgSize() { public static int getMaxMsgSize() {
return MAX_MSG_SIZE; return MAX_MSG_SIZE;
@ -122,6 +123,10 @@ public class Connection implements MessageListener {
connectionListener.onPeerAddressAuthenticated(peerAddress, connection); connectionListener.onPeerAddressAuthenticated(peerAddress, connection);
} }
public void setConnectionType(ConnectionsType connectionType) {
this.connectionType = connectionType;
}
// Called form various threads // Called form various threads
public void sendMessage(Message message) { public void sendMessage(Message message) {
Log.traceCall(); Log.traceCall();
@ -205,6 +210,9 @@ public class Connection implements MessageListener {
return stopped; return stopped;
} }
public ConnectionsType getConnectionType() {
return connectionType;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// ShutDown // ShutDown
@ -324,6 +332,7 @@ public class Connection implements MessageListener {
", isAuthenticated=" + isAuthenticated + ", isAuthenticated=" + isAuthenticated +
", stopped=" + stopped + ", stopped=" + stopped +
", stopped=" + stopped + ", stopped=" + stopped +
", connectionType=" + connectionType +
", useCompression=" + useCompression + ", useCompression=" + useCompression +
'}'; '}';
} }

View file

@ -0,0 +1,8 @@
package io.bitsquare.p2p.network;
public enum ConnectionsType {
PASSIVE, // for connections initiated by other peer
ACTIVE, // for connections initiated by us
DIRECT_MSG, // for connections used for direct messaging
AUTH_REQUEST // for connections used for starting the authentication
}

View file

@ -27,8 +27,8 @@ import java.util.function.Consumer;
public class LocalhostNetworkNode extends NetworkNode { public class LocalhostNetworkNode extends NetworkNode {
private static final Logger log = LoggerFactory.getLogger(LocalhostNetworkNode.class); private static final Logger log = LoggerFactory.getLogger(LocalhostNetworkNode.class);
private static volatile int simulateTorDelayTorNode = 2 * 100; private static volatile int simulateTorDelayTorNode = 600;
private static volatile int simulateTorDelayHiddenService = 2 * 100; private static volatile int simulateTorDelayHiddenService = 3000;
private Address address; private Address address;
public static void setSimulateTorDelayTorNode(int simulateTorDelayTorNode) { public static void setSimulateTorDelayTorNode(int simulateTorDelayTorNode) {

View file

@ -87,17 +87,20 @@ public abstract class NetworkNode implements MessageListener, ConnectionListener
if (connection != null) { if (connection != null) {
return sendMessage(connection, message); return sendMessage(connection, message);
} else { } else {
log.debug("inBoundConnections " + inBoundConnections.toString());
log.debug("outBoundConnections " + outBoundConnections.toString());
log.trace("We have not found any connection for that peerAddress. " + log.trace("We have not found any connection for that peerAddress. " +
"We will create a new outbound connection."); "We will create a new outbound connection.");
final SettableFuture<Connection> resultFuture = SettableFuture.create(); final SettableFuture<Connection> resultFuture = SettableFuture.create();
final boolean[] timeoutOccurred = new boolean[1];
timeoutOccurred[0] = false;
ListenableFuture<Connection> future = executorService.submit(() -> { ListenableFuture<Connection> future = executorService.submit(() -> {
Thread.currentThread().setName("NetworkNode:SendMessage-to-" + peerAddress); Thread.currentThread().setName("NetworkNode:SendMessage-to-" + peerAddress);
try { try {
Socket socket = createSocket(peerAddress); // can take a while when using tor Socket socket = createSocket(peerAddress); // can take a while when using tor
if (timeoutOccurred[0])
throw new TimeoutException("Timeout occurred when tried to create Socket to peer: " + peerAddress);
Connection newConnection = new Connection(socket, NetworkNode.this, NetworkNode.this); Connection newConnection = new Connection(socket, NetworkNode.this, NetworkNode.this);
newConnection.setPeerAddress(peerAddress); newConnection.setPeerAddress(peerAddress);
outBoundConnections.add(newConnection); outBoundConnections.add(newConnection);
@ -125,10 +128,11 @@ public abstract class NetworkNode implements MessageListener, ConnectionListener
@Override @Override
public void run() { public void run() {
Thread.currentThread().setName("TimerTask-" + new Random().nextInt(10000)); Thread.currentThread().setName("TimerTask-" + new Random().nextInt(10000));
timeoutOccurred[0] = true;
future.cancel(true); future.cancel(true);
String message = "Timeout occurred when tried to create Socket to peer: " + peerAddress; String message = "Timeout occurred when tried to create Socket to peer: " + peerAddress;
log.info(message); log.info(message);
resultFuture.setException(new TimeoutException(message)); UserThread.execute(() -> resultFuture.setException(new TimeoutException(message)));
} }
}, CREATE_SOCKET_TIMEOUT); }, CREATE_SOCKET_TIMEOUT);
@ -335,15 +339,13 @@ public abstract class NetworkNode implements MessageListener, ConnectionListener
} }
private Optional<Connection> lookupOutboundConnection(Address peerAddress) { private Optional<Connection> lookupOutboundConnection(Address peerAddress) {
Log.traceCall(peerAddress.toString()); Log.traceCall("search for " + peerAddress.toString() + " / outBoundConnections " + outBoundConnections);
log.debug("outBoundConnections " + outBoundConnections);
return outBoundConnections.stream() return outBoundConnections.stream()
.filter(e -> peerAddress.equals(e.getPeerAddress())).findAny(); .filter(e -> peerAddress.equals(e.getPeerAddress())).findAny();
} }
private Optional<Connection> lookupInboundConnection(Address peerAddress) { private Optional<Connection> lookupInboundConnection(Address peerAddress) {
Log.traceCall(peerAddress.toString()); Log.traceCall("search for " + peerAddress.toString() + " / inBoundConnections " + inBoundConnections);
log.debug("inBoundConnections " + inBoundConnections);
return inBoundConnections.stream() return inBoundConnections.stream()
.filter(e -> peerAddress.equals(e.getPeerAddress())).findAny(); .filter(e -> peerAddress.equals(e.getPeerAddress())).findAny();
} }

View file

@ -4,20 +4,23 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Address; import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.network.Connection; import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.ConnectionsType;
import io.bitsquare.p2p.network.MessageListener; import io.bitsquare.p2p.network.MessageListener;
import io.bitsquare.p2p.network.NetworkNode; import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.messages.auth.*; import io.bitsquare.p2p.peers.messages.auth.*;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.HashSet; import java.util.HashSet;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
// authentication example: // authentication example:
@ -51,8 +54,6 @@ public class AuthenticationHandshake implements MessageListener {
this.networkNode = networkNode; this.networkNode = networkNode;
this.peerGroup = peerGroup; this.peerGroup = peerGroup;
this.myAddress = myAddress; this.myAddress = myAddress;
networkNode.addMessageListener(this);
} }
@ -62,74 +63,85 @@ public class AuthenticationHandshake implements MessageListener {
@Override @Override
public void onMessage(Message message, Connection connection) { public void onMessage(Message message, Connection connection) {
checkArgument(!stopped);
if (message instanceof AuthenticationMessage) { if (message instanceof AuthenticationMessage) {
Log.traceCall(message.toString()); Log.traceCall(message.toString());
if (message instanceof AuthenticationResponse) { if (message instanceof AuthenticationResponse) {
// Requesting peer // Requesting peer
// We use the active connectionType if we started the authentication request to another peer
// That is used for protecting eclipse attacks
connection.setConnectionType(ConnectionsType.ACTIVE);
AuthenticationResponse authenticationResponse = (AuthenticationResponse) message; AuthenticationResponse authenticationResponse = (AuthenticationResponse) message;
connection.setPeerAddress(authenticationResponse.address);
Address peerAddress = authenticationResponse.address; Address peerAddress = authenticationResponse.address;
log.trace("ChallengeMessage from " + peerAddress + " at " + myAddress); connection.setPeerAddress(peerAddress);
log.trace("Received authenticationResponse from " + peerAddress);
boolean verified = nonce != 0 && nonce == authenticationResponse.requesterNonce; boolean verified = nonce != 0 && nonce == authenticationResponse.requesterNonce;
if (verified) { if (verified) {
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, GetPeersAuthRequest getPeersAuthRequest = new GetPeersAuthRequest(myAddress,
new GetPeersAuthRequest(myAddress, authenticationResponse.responderNonce, new HashSet<>(peerGroup.getAllPeerAddresses()))); authenticationResponse.responderNonce,
new HashSet<>(peerGroup.getAllPeerAddresses()));
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, getPeersAuthRequest);
log.trace("Sent GetPeersAuthRequest {} to {}", getPeersAuthRequest, peerAddress);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(Connection connection) { public void onSuccess(Connection connection) {
log.trace("GetPeersAuthRequest sent successfully from " + myAddress + " to " + peerAddress); log.trace("Successfully sent GetPeersAuthRequest {} to {}", getPeersAuthRequest, peerAddress);
connection.setPeerAddress(peerAddress);
} }
@Override @Override
public void onFailure(@NotNull Throwable throwable) { public void onFailure(@NotNull Throwable throwable) {
log.info("GetPeersAuthRequest sending failed " + throwable.getMessage()); log.info("GetPeersAuthRequest sending failed " + throwable.getMessage());
onFault(throwable); failed(throwable);
} }
}); });
// We could set already the authenticated flag here already, but as we need the reported peers we need
// to wait for the GetPeersAuthResponse before we are completed.
} else { } else {
log.warn("verify nonce failed. challengeMessage=" + authenticationResponse + " / nonce=" + nonce); log.warn("verify nonce failed. AuthenticationResponse=" + authenticationResponse + " / nonce=" + nonce);
onFault(new Exception("Verify nonce failed. challengeMessage=" + authenticationResponse + " / nonceMap=" + nonce)); failed(new Exception("Verify nonce failed. AuthenticationResponse=" + authenticationResponse + " / nonceMap=" + nonce));
} }
} else if (message instanceof GetPeersAuthRequest) { } else if (message instanceof GetPeersAuthRequest) {
// Responding peer // Responding peer
GetPeersAuthRequest getPeersAuthRequest = (GetPeersAuthRequest) message; GetPeersAuthRequest getPeersAuthRequest = (GetPeersAuthRequest) message;
Address peerAddress = getPeersAuthRequest.address; Address peerAddress = getPeersAuthRequest.address;
log.trace("GetPeersMessage from " + peerAddress + " at " + myAddress); log.trace("GetPeersAuthRequest from " + peerAddress + " at " + myAddress);
boolean verified = nonce != 0 && nonce == getPeersAuthRequest.responderNonce; boolean verified = nonce != 0 && nonce == getPeersAuthRequest.responderNonce;
if (verified) { if (verified) {
// we create the msg with our already collected peer addresses (before adding the new ones) // we create the msg with our already collected peer addresses (before adding the new ones)
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, GetPeersAuthResponse getPeersAuthResponse = new GetPeersAuthResponse(myAddress,
new GetPeersAuthResponse(myAddress, new HashSet<>(peerGroup.getAllPeerAddresses()))); new HashSet<>(peerGroup.getAllPeerAddresses()));
log.trace("sent GetPeersAuthResponse to " + peerAddress + " from " + myAddress SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, getPeersAuthResponse);
+ " with allPeers=" + peerGroup.getAllPeerAddresses()); log.trace("Sent GetPeersAuthResponse {} to {}", getPeersAuthResponse, peerAddress);
// now we add the reported peers to our own set // now we add the reported peers to our own set
HashSet<Address> peerAddresses = getPeersAuthRequest.peerAddresses; HashSet<Address> peerAddresses = getPeersAuthRequest.peerAddresses;
log.trace("Received peers: " + peerAddresses); log.trace("Received reported peers: " + peerAddresses);
peerGroup.addToReportedPeers(peerAddresses, connection); peerGroup.addToReportedPeers(peerAddresses, connection);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(Connection connection) { public void onSuccess(Connection connection) {
log.trace("GetPeersAuthResponse sent successfully from " + myAddress + " to " + peerAddress); log.trace("Successfully sent GetPeersAuthResponse {} to {}", getPeersAuthResponse, peerAddress);
connection.setPeerAddress(peerAddress);
log.info("AuthenticationComplete: Peer with address " + peerAddress log.info("AuthenticationComplete: Peer with address " + peerAddress
+ " authenticated (" + connection.getUid() + "). Took " + " authenticated (" + connection.getUid() + "). Took "
+ (System.currentTimeMillis() - startAuthTs) + " ms."); + (System.currentTimeMillis() - startAuthTs) + " ms.");
AuthenticationHandshake.this.onSuccess(connection); completed(connection);
} }
@Override @Override
public void onFailure(@NotNull Throwable throwable) { public void onFailure(@NotNull Throwable throwable) {
log.info("GetPeersAuthResponse sending failed " + throwable.getMessage()); log.info("GetPeersAuthResponse sending failed " + throwable.getMessage());
onFault(throwable); failed(throwable);
} }
}); });
} else { } else {
log.warn("verify nonce failed. getPeersMessage=" + getPeersAuthRequest + " / nonce=" + nonce); log.warn("verify nonce failed. getPeersMessage=" + getPeersAuthRequest + " / nonce=" + nonce);
onFault(new Exception("Verify nonce failed. getPeersMessage=" + getPeersAuthRequest + " / nonce=" + nonce)); failed(new Exception("Verify nonce failed. getPeersMessage=" + getPeersAuthRequest + " / nonce=" + nonce));
} }
} else if (message instanceof GetPeersAuthResponse) { } else if (message instanceof GetPeersAuthResponse) {
// Requesting peer // Requesting peer
@ -137,130 +149,97 @@ public class AuthenticationHandshake implements MessageListener {
Address peerAddress = getPeersAuthResponse.address; Address peerAddress = getPeersAuthResponse.address;
log.trace("GetPeersAuthResponse from " + peerAddress + " at " + myAddress); log.trace("GetPeersAuthResponse from " + peerAddress + " at " + myAddress);
HashSet<Address> peerAddresses = getPeersAuthResponse.peerAddresses; HashSet<Address> peerAddresses = getPeersAuthResponse.peerAddresses;
log.trace("Received peers: " + peerAddresses); log.trace("Received reported peers: " + peerAddresses);
peerGroup.addToReportedPeers(peerAddresses, connection); peerGroup.addToReportedPeers(peerAddresses, connection);
// we wait until the handshake is completed before setting the authenticate flag
// authentication at both sides of the connection
log.info("AuthenticationComplete: Peer with address " + peerAddress log.info("AuthenticationComplete: Peer with address " + peerAddress
+ " authenticated (" + connection.getUid() + "). Took " + " authenticated (" + connection.getUid() + "). Took "
+ (System.currentTimeMillis() - startAuthTs) + " ms."); + (System.currentTimeMillis() - startAuthTs) + " ms.");
onSuccess(connection); completed(connection);
} }
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// API // Authentication initiated by requesting peer
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public SettableFuture<Connection> requestAuthenticationToPeer(Address peerAddress) { public SettableFuture<Connection> requestAuthentication(Address peerAddress) {
Log.traceCall(); Log.traceCall();
// Requesting peer // Requesting peer
resultFuture = SettableFuture.create();
startAuthTs = System.currentTimeMillis();
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new AuthenticationRequest(myAddress, getAndSetNonce()));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.trace("send RequestAuthenticationMessage to " + peerAddress + " succeeded.");
}
@Override init();
public void onFailure(@NotNull Throwable throwable) {
log.info("Send RequestAuthenticationMessage to " + peerAddress + " failed." +
"\nException:" + throwable.getMessage());
onFault(throwable);
}
});
return resultFuture; AuthenticationRequest authenticationRequest = new AuthenticationRequest(myAddress, getAndSetNonce());
} SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, authenticationRequest);
public SettableFuture<Connection> requestAuthentication(Set<Address> remainingAddresses, Address peerAddress) {
Log.traceCall(peerAddress.getFullAddress());
// Requesting peer
resultFuture = SettableFuture.create();
startAuthTs = System.currentTimeMillis();
remainingAddresses.remove(peerAddress);
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new AuthenticationRequest(myAddress, getAndSetNonce()));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.trace("send RequestAuthenticationMessage to " + peerAddress + " succeeded.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.info("Send RequestAuthenticationMessage to " + peerAddress + " failed." +
"\nThat is expected if seed nodes are offline." +
"\nException:" + throwable.getMessage());
onFault(throwable);
}
});
return resultFuture;
}
public SettableFuture<Connection> processAuthenticationRequest(AuthenticationRequest authenticationRequest, Connection connection) {
Log.traceCall();
// Responding peer
resultFuture = SettableFuture.create();
startAuthTs = System.currentTimeMillis();
Address peerAddress = authenticationRequest.address;
log.trace("RequestAuthenticationMessage from " + peerAddress + " at " + myAddress);
log.info("We shut down inbound connection from peer {} to establish a new " +
"connection with his reported address.", peerAddress);
//TODO check if causes problems without delay
connection.shutDown(() -> {
Log.traceCall();
if (!stopped) {
// we delay a bit as listeners for connection.onDisconnect are on other threads and might lead to
// inconsistent state (removal of connection from NetworkNode.authenticatedConnections)
log.trace("processAuthenticationMessage: connection.shutDown complete. RequestAuthenticationMessage from " + peerAddress + " at " + myAddress);
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new AuthenticationResponse(myAddress, authenticationRequest.requesterNonce, getAndSetNonce()));
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(Connection connection) { public void onSuccess(Connection connection) {
log.trace("onSuccess sending ChallengeMessage"); log.trace("send AuthenticationRequest to " + peerAddress + " succeeded.");
connection.setPeerAddress(peerAddress);
// We protect that connection from getting closed by maintenance cleanup...
connection.setConnectionType(ConnectionsType.AUTH_REQUEST);
} }
@Override @Override
public void onFailure(@NotNull Throwable throwable) { public void onFailure(@NotNull Throwable throwable) {
log.warn("onFailure sending ChallengeMessage."); log.info("Send AuthenticationRequest to " + peerAddress + " failed." +
onFault(throwable); "\nException:" + throwable.getMessage());
failed(throwable);
} }
}); });
return resultFuture;
} }
});
/* connection.shutDown(() -> UserThread.runAfter(() -> { Log.traceCall();
///////////////////////////////////////////////////////////////////////////////////////////
// Responding to authentication request
///////////////////////////////////////////////////////////////////////////////////////////
public SettableFuture<Connection> respondToAuthenticationRequest(AuthenticationRequest authenticationRequest, Connection connection) {
Log.traceCall();
// Responding peer
init();
Address peerAddress = authenticationRequest.address;
log.trace("AuthenticationRequest from " + peerAddress + " at " + myAddress);
log.info("We shut down inbound connection from peer {} to establish a new " +
"connection with his reported address.", peerAddress);
connection.shutDown(() -> {
UserThread.runAfter(() -> {
if (!stopped) { if (!stopped) {
// we delay a bit as listeners for connection.onDisconnect are on other threads and might lead to // we delay a bit as listeners for connection.onDisconnect are on other threads and might lead to
// inconsistent state (removal of connection from NetworkNode.authenticatedConnections) // inconsistent state (removal of connection from NetworkNode.authenticatedConnections)
log.trace("processAuthenticationMessage: connection.shutDown complete. RequestAuthenticationMessage from " + peerAddress + " at " + myAddress); log.trace("processAuthenticationMessage: connection.shutDown complete. AuthenticationRequest from " + peerAddress + " at " + myAddress);
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new AuthenticationResponse(myAddress, authenticationRequest.nonce, getAndSetNonce())); AuthenticationResponse authenticationResponse = new AuthenticationResponse(myAddress,
authenticationRequest.requesterNonce,
getAndSetNonce());
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, authenticationResponse);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(Connection connection) { Log.traceCall(); public void onSuccess(Connection connection) {
log.trace("onSuccess sending ChallengeMessage"); log.trace("onSuccess sending AuthenticationResponse");
connection.setPeerAddress(peerAddress);
// We use passive connectionType for connections created from received authentication requests from other peers
// That is used for protecting eclipse attacks
connection.setConnectionType(ConnectionsType.PASSIVE);
} }
@Override @Override
public void onFailure(@NotNull Throwable throwable) { Log.traceCall(); public void onFailure(@NotNull Throwable throwable) {
log.warn("onFailure sending ChallengeMessage."); log.warn("onFailure sending AuthenticationResponse.");
onFault(throwable); failed(throwable);
} }
}); });
} }
}, }, 200, TimeUnit.MILLISECONDS);
100 + PeerGroup.simulateAuthTorNode, });
TimeUnit.MILLISECONDS));*/
return resultFuture; return resultFuture;
} }
@ -269,6 +248,12 @@ public class AuthenticationHandshake implements MessageListener {
// Private // Private
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void init() {
networkNode.addMessageListener(this);
resultFuture = SettableFuture.create();
startAuthTs = System.currentTimeMillis();
}
private long getAndSetNonce() { private long getAndSetNonce() {
Log.traceCall(); Log.traceCall();
nonce = new Random().nextLong(); nonce = new Random().nextLong();
@ -278,21 +263,21 @@ public class AuthenticationHandshake implements MessageListener {
return nonce; return nonce;
} }
private void onFault(@NotNull Throwable throwable) { private void failed(@NotNull Throwable throwable) {
Log.traceCall(); Log.traceCall();
cleanup(); shutDown();
resultFuture.setException(throwable); resultFuture.setException(throwable);
} }
private void onSuccess(Connection connection) { private void completed(Connection connection) {
Log.traceCall(); Log.traceCall();
cleanup(); shutDown();
resultFuture.set(connection); resultFuture.set(connection);
} }
private void cleanup() { private void shutDown() {
Log.traceCall(); Log.traceCall();
stopped = true;
networkNode.removeMessageListener(this); networkNode.removeMessageListener(this);
stopped = true;
} }
} }

View file

@ -1,18 +0,0 @@
package io.bitsquare.p2p.peers;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.network.Connection;
//TODO used only in unittests yet
public abstract class AuthenticationListener implements PeerListener {
public void onFirstAuthenticatePeer(Peer peer) {
}
public void onPeerAdded(Peer peer) {
}
public void onPeerRemoved(Address address) {
}
abstract public void onConnectionAuthenticated(Connection connection);
}

View file

@ -8,10 +8,7 @@ import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple2;
import io.bitsquare.p2p.Address; import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.network.Connection; import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.network.ConnectionListener;
import io.bitsquare.p2p.network.MessageListener;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.messages.auth.AuthenticationRequest; import io.bitsquare.p2p.peers.messages.auth.AuthenticationRequest;
import io.bitsquare.p2p.peers.messages.maintenance.*; import io.bitsquare.p2p.peers.messages.maintenance.*;
import io.bitsquare.p2p.storage.messages.DataBroadcastMessage; import io.bitsquare.p2p.storage.messages.DataBroadcastMessage;
@ -21,7 +18,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -37,10 +33,12 @@ public class PeerGroup implements MessageListener, ConnectionListener {
PeerGroup.simulateAuthTorNode = simulateAuthTorNode; PeerGroup.simulateAuthTorNode = simulateAuthTorNode;
} }
private static int MAX_CONNECTIONS = 8; private static int MAX_CONNECTIONS_LOW_PRIO = 8;
private static int MAX_CONNECTIONS_NORMAL_PRIO = MAX_CONNECTIONS_LOW_PRIO + 4;
private static int MAX_CONNECTIONS_HIGH_PRIO = MAX_CONNECTIONS_NORMAL_PRIO + 4;
public static void setMaxConnections(int maxConnections) { public static void setMaxConnectionsLowPrio(int maxConnectionsLowPrio) {
MAX_CONNECTIONS = maxConnections; MAX_CONNECTIONS_LOW_PRIO = maxConnectionsLowPrio;
} }
private static final int PING_AFTER_CONNECTION_INACTIVITY = 30 * 1000; private static final int PING_AFTER_CONNECTION_INACTIVITY = 30 * 1000;
@ -49,16 +47,14 @@ public class PeerGroup implements MessageListener, ConnectionListener {
private final NetworkNode networkNode; private final NetworkNode networkNode;
private final Set<Address> seedNodeAddresses; private final Set<Address> seedNodeAddresses;
private final Set<PeerListener> peerListeners = new HashSet<>();
private final Map<Address, Peer> authenticatedPeers = new HashMap<>(); private final Map<Address, Peer> authenticatedPeers = new HashMap<>();
private final Set<Address> reportedPeerAddresses = new HashSet<>(); private final Set<Address> reportedPeerAddresses = new HashSet<>();
private final Map<Address, AuthenticationHandshake> authenticationHandshakes = new ConcurrentHashMap<>(); private final Map<Address, AuthenticationHandshake> authenticationHandshakes = new HashMap<>();
private Timer sendPingTimer = new Timer(); private Timer sendPingTimer = new Timer();
private Timer getPeersTimer = new Timer(); private Timer getPeersTimer = new Timer();
private boolean shutDownInProgress; private boolean shutDownInProgress;
private boolean firstPeerAdded = false;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -164,16 +160,18 @@ public class PeerGroup implements MessageListener, ConnectionListener {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Authentication to seed node // Process incoming authentication request
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void processAuthenticationRequest(NetworkNode networkNode, AuthenticationRequest message, final Connection connection) { private void processAuthenticationRequest(NetworkNode networkNode, AuthenticationRequest message, final Connection connection) {
Log.traceCall(message.toString()); Log.traceCall(message.toString());
Address peerAddress = message.address; Address peerAddress = message.address;
if (!authenticationHandshakes.containsKey(peerAddress)) { if (!authenticationHandshakes.containsKey(peerAddress)) {
// We protect that connection from getting closed by maintenance cleanup...
connection.setConnectionType(ConnectionsType.AUTH_REQUEST);
AuthenticationHandshake authenticationHandshake = new AuthenticationHandshake(networkNode, PeerGroup.this, getMyAddress()); AuthenticationHandshake authenticationHandshake = new AuthenticationHandshake(networkNode, PeerGroup.this, getMyAddress());
authenticationHandshakes.put(peerAddress, authenticationHandshake); authenticationHandshakes.put(peerAddress, authenticationHandshake);
SettableFuture<Connection> future = authenticationHandshake.processAuthenticationRequest(message, connection); SettableFuture<Connection> future = authenticationHandshake.respondToAuthenticationRequest(message, connection);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(@Nullable Connection connection) { public void onSuccess(@Nullable Connection connection) {
@ -198,29 +196,38 @@ public class PeerGroup implements MessageListener, ConnectionListener {
} }
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Authentication to seed node
///////////////////////////////////////////////////////////////////////////////////////////
// After HS is published or after a retry from a successful GetDataRequest if no seed nodes have been available initially
public void authenticateSeedNode(Address peerAddress) { public void authenticateSeedNode(Address peerAddress) {
Log.traceCall(); Log.traceCall();
authenticateToSeedNode(new HashSet<>(seedNodeAddresses), peerAddress, true); authenticateToSeedNode(new HashSet<>(seedNodeAddresses), peerAddress, true);
} }
// First we try to connect to 1 seed node. If we fail we try to connect to any reported peer. // First we try to connect to 1 seed node.
// If we fail we try to connect to one of the remaining seed nodes.
// If that fails as well we use the reported peers if available.
// If there are also no reported peers we retry after a random pause of a few minutes.
//
// After connection is authenticated, we try to connect to any reported peer as long we have not // After connection is authenticated, we try to connect to any reported peer as long we have not
// reached our max connection size. // reached our max connection size.
private void authenticateToSeedNode(Set<Address> remainingAddresses, Address peerAddress, boolean continueOnSuccess) { private void authenticateToSeedNode(Set<Address> remainingAddresses, Address peerAddress, boolean connectToReportedAfterSuccess) {
Log.traceCall(peerAddress.getFullAddress()); Log.traceCall(peerAddress.getFullAddress());
checkArgument(!authenticatedPeers.containsKey(peerAddress), checkArgument(!authenticatedPeers.containsKey(peerAddress),
"We have that peer already authenticated. That must never happen."); "We have that peer already authenticated. That must never happen.");
if (!authenticationHandshakes.containsKey(peerAddress)) { if (!authenticationHandshakes.containsKey(peerAddress)) {
AuthenticationHandshake authenticationHandshake = new AuthenticationHandshake(networkNode, this, getMyAddress()); AuthenticationHandshake authenticationHandshake = new AuthenticationHandshake(networkNode, this, getMyAddress());
authenticationHandshakes.put(peerAddress, authenticationHandshake); authenticationHandshakes.put(peerAddress, authenticationHandshake);
SettableFuture<Connection> future = authenticationHandshake.requestAuthentication(remainingAddresses, peerAddress); SettableFuture<Connection> future = authenticationHandshake.requestAuthentication(peerAddress);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(@Nullable Connection connection) { public void onSuccess(Connection connection) {
if (connection != null) {
setAuthenticated(connection, peerAddress); setAuthenticated(connection, peerAddress);
if (continueOnSuccess) { if (connectToReportedAfterSuccess) {
if (getAuthenticatedPeers().size() <= MAX_CONNECTIONS) { if (getAuthenticatedPeers().size() < MAX_CONNECTIONS_LOW_PRIO) {
log.info("We still don't have enough connections. Lets try the reported peers."); log.info("We still don't have enough connections. Lets try the reported peers.");
authenticateToRemainingReportedPeers(true); authenticateToRemainingReportedPeers(true);
} else { } else {
@ -233,7 +240,6 @@ public class PeerGroup implements MessageListener, ConnectionListener {
1, 2, TimeUnit.MINUTES); 1, 2, TimeUnit.MINUTES);
} }
} }
}
@Override @Override
public void onFailure(@NotNull Throwable throwable) { public void onFailure(@NotNull Throwable throwable) {
@ -242,12 +248,12 @@ public class PeerGroup implements MessageListener, ConnectionListener {
"\nException:" + throwable.getMessage()); "\nException:" + throwable.getMessage());
removePeer(peerAddress); removePeer(peerAddress);
// If we fail we try again with the remaining set // If we fail we try again with the remaining set excluding the failed one
remainingAddresses.remove(peerAddress); remainingAddresses.remove(peerAddress);
log.trace("We try to authenticate to another random seed nodes of that list: " + remainingAddresses); log.trace("We try to authenticate to another random seed nodes of that list: " + remainingAddresses);
Optional<Tuple2<Address, Set<Address>>> tupleOptional = getRandomItemAndRemainingSet(remainingAddresses); Optional<Tuple2<Address, Set<Address>>> tupleOptional = getRandomNotAuthPeerAndRemainingSet(remainingAddresses);
if (tupleOptional.isPresent()) { if (tupleOptional.isPresent()) {
log.info("We try to authenticate to a seed node. " + tupleOptional.get().first); log.info("We try to authenticate to a seed node. " + tupleOptional.get().first);
authenticateToSeedNode(tupleOptional.get().second, tupleOptional.get().first, true); authenticateToSeedNode(tupleOptional.get().second, tupleOptional.get().first, true);
@ -255,8 +261,8 @@ public class PeerGroup implements MessageListener, ConnectionListener {
log.info("We don't have any more seed nodes for connecting. Lets try the reported peers."); log.info("We don't have any more seed nodes for connecting. Lets try the reported peers.");
authenticateToRemainingReportedPeers(true); authenticateToRemainingReportedPeers(true);
} else { } else {
log.info("We don't have any more seed nodes or reported nodes for connecting. " + log.info("We don't have any more seed nodes nor reported nodes for connecting. " +
"We stop bootstrapping now, but will repeat after an while."); "We stop authentication attempts now, but will repeat after a few minutes.");
UserThread.runAfterRandomDelay(() -> authenticateToRemainingReportedPeers(true), UserThread.runAfterRandomDelay(() -> authenticateToRemainingReportedPeers(true),
1, 2, TimeUnit.MINUTES); 1, 2, TimeUnit.MINUTES);
} }
@ -267,13 +273,32 @@ public class PeerGroup implements MessageListener, ConnectionListener {
} }
} }
private void authenticateToRemainingReportedPeers(boolean calledFromSeedNodeMethod) { private void authenticateToRemainingSeedNodes() {
Log.traceCall(); Log.traceCall();
Optional<Tuple2<Address, Set<Address>>> tupleOptional = getRandomItemAndRemainingSet(reportedPeerAddresses); Optional<Tuple2<Address, Set<Address>>> tupleOptional = getRandomNotAuthPeerAndRemainingSet(seedNodeAddresses);
if (tupleOptional.isPresent()) {
log.info("We try to authenticate to a random seed node. " + tupleOptional.get().first);
authenticateToSeedNode(tupleOptional.get().second, tupleOptional.get().first, true);
} else {
log.info("We don't have any more seed nodes for connecting. " +
"We stop authentication attempts now, but will repeat after an while.");
UserThread.runAfterRandomDelay(() -> authenticateToRemainingReportedPeers(false),
1, 2, TimeUnit.MINUTES);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Authentication to reported peers
///////////////////////////////////////////////////////////////////////////////////////////
private void authenticateToRemainingReportedPeers(boolean calledFromAuthenticateToSeedNode) {
Log.traceCall();
Optional<Tuple2<Address, Set<Address>>> tupleOptional = getRandomNotAuthPeerAndRemainingSet(reportedPeerAddresses);
if (tupleOptional.isPresent()) { if (tupleOptional.isPresent()) {
log.info("We try to authenticate to a random peer. " + tupleOptional.get().first); log.info("We try to authenticate to a random peer. " + tupleOptional.get().first);
authenticateToReportedPeer(tupleOptional.get().second, tupleOptional.get().first); authenticateToReportedPeer(tupleOptional.get().first);
} else if (calledFromSeedNodeMethod) { } else if (calledFromAuthenticateToSeedNode) {
log.info("We don't have any reported peers for connecting. " + log.info("We don't have any reported peers for connecting. " +
"As we tried recently the seed nodes we will wait a bit before repeating."); "As we tried recently the seed nodes we will wait a bit before repeating.");
UserThread.runAfterRandomDelay(() -> authenticateToRemainingSeedNodes(), UserThread.runAfterRandomDelay(() -> authenticateToRemainingSeedNodes(),
@ -285,27 +310,26 @@ public class PeerGroup implements MessageListener, ConnectionListener {
} }
// We try to connect to a reported peer. If we fail we repeat after the failed peer has been removed. // We try to connect to a reported peer. If we fail we repeat after the failed peer has been removed.
// If we succeed we repeat until we are ut of addresses. // If we succeed we repeat until we are out of addresses.
private void authenticateToReportedPeer(Set<Address> remainingAddresses, Address peerAddress) { private void authenticateToReportedPeer(Address peerAddress) {
Log.traceCall(peerAddress.getFullAddress()); Log.traceCall(peerAddress.getFullAddress());
checkArgument(!authenticatedPeers.containsKey(peerAddress), checkArgument(!authenticatedPeers.containsKey(peerAddress),
"We have that peer already authenticated. That must never happen."); "We have that peer already authenticated. That must never happen.");
if (!authenticationHandshakes.containsKey(peerAddress)) { if (!authenticationHandshakes.containsKey(peerAddress)) {
AuthenticationHandshake authenticationHandshake = new AuthenticationHandshake(networkNode, this, getMyAddress()); AuthenticationHandshake authenticationHandshake = new AuthenticationHandshake(networkNode, this, getMyAddress());
authenticationHandshakes.put(peerAddress, authenticationHandshake); authenticationHandshakes.put(peerAddress, authenticationHandshake);
SettableFuture<Connection> future = authenticationHandshake.requestAuthentication(remainingAddresses, peerAddress); SettableFuture<Connection> future = authenticationHandshake.requestAuthentication(peerAddress);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(@Nullable Connection connection) { public void onSuccess(Connection connection) {
if (connection != null) {
setAuthenticated(connection, peerAddress); setAuthenticated(connection, peerAddress);
if (getAuthenticatedPeers().size() <= MAX_CONNECTIONS) { if (getAuthenticatedPeers().size() < MAX_CONNECTIONS_LOW_PRIO) {
if (reportedPeerAddresses.size() > 0) { if (reportedPeerAddresses.size() > 0) {
log.info("We still don't have enough connections. " + log.info("We still don't have enough connections. " +
"Lets try the remaining reported peer addresses."); "Lets try the remaining reported peer addresses.");
authenticateToRemainingReportedPeers(false); authenticateToRemainingReportedPeers(false);
} else { } else {
log.info("We still don't have enough connections. " + log.info("We don't have more reported peers and still don't have enough connections. " +
"Lets wait a bit and then try the remaining seed nodes."); "Lets wait a bit and then try the remaining seed nodes.");
UserThread.runAfterRandomDelay(() -> authenticateToRemainingSeedNodes(), UserThread.runAfterRandomDelay(() -> authenticateToRemainingSeedNodes(),
1, 2, TimeUnit.MINUTES); 1, 2, TimeUnit.MINUTES);
@ -314,16 +338,14 @@ public class PeerGroup implements MessageListener, ConnectionListener {
log.info("We have already enough connections."); log.info("We have already enough connections.");
} }
} }
}
@Override @Override
public void onFailure(@NotNull Throwable throwable) { public void onFailure(@NotNull Throwable throwable) {
log.error("AuthenticationHandshake failed. " + throwable.getMessage()); log.info("Send RequestAuthenticationMessage to a reported peer with address " + peerAddress + " failed." +
throwable.printStackTrace(); "\nThat is expected if the nodes was offline." +
"\nException:" + throwable.getMessage());
removePeer(peerAddress); removePeer(peerAddress);
authenticateToRemainingReportedPeers(false);
if (reportedPeerAddresses.size() > 0) { if (reportedPeerAddresses.size() > 0) {
log.info("Authentication failed. Lets try again with the remaining reported peer addresses."); log.info("Authentication failed. Lets try again with the remaining reported peer addresses.");
authenticateToRemainingReportedPeers(false); authenticateToRemainingReportedPeers(false);
@ -340,40 +362,29 @@ public class PeerGroup implements MessageListener, ConnectionListener {
} }
} }
private void authenticateToRemainingSeedNodes() {
Log.traceCall();
Optional<Tuple2<Address, Set<Address>>> tupleOptional = getRandomItemAndRemainingSet(seedNodeAddresses);
if (tupleOptional.isPresent()) {
log.info("We try to authenticate to a random seed node. " + tupleOptional.get().first);
authenticateToSeedNode(tupleOptional.get().second, tupleOptional.get().first, false);
} else {
log.info("We don't have any more seed nodes for connecting. " +
"We stop bootstrapping now, but will repeat after an while.");
UserThread.runAfterRandomDelay(() -> authenticateToRemainingReportedPeers(true),
1, 2, TimeUnit.MINUTES);
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Authentication to non-seed node peer // Authentication to peer used for direct messaging
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void authenticateToPeer(Address peerAddress, @Nullable Runnable authenticationCompleteHandler, @Nullable Runnable faultHandler) { // Priority is set when we receive a decrypted mail message as those are used for direct messages
public void authenticateToDirectMessagePeer(Address peerAddress,
@Nullable Runnable completeHandler,
@Nullable Runnable faultHandler) {
Log.traceCall(peerAddress.getFullAddress()); Log.traceCall(peerAddress.getFullAddress());
checkArgument(!authenticatedPeers.containsKey(peerAddress), checkArgument(!authenticatedPeers.containsKey(peerAddress),
"We have that seed node already authenticated. That must never happen."); "We have that seed node already authenticated. That must never happen.");
if (!authenticationHandshakes.containsKey(peerAddress)) { if (!authenticationHandshakes.containsKey(peerAddress)) {
AuthenticationHandshake authenticationHandshake = new AuthenticationHandshake(networkNode, this, getMyAddress()); AuthenticationHandshake authenticationHandshake = new AuthenticationHandshake(networkNode, this, getMyAddress());
authenticationHandshakes.put(peerAddress, authenticationHandshake); authenticationHandshakes.put(peerAddress, authenticationHandshake);
SettableFuture<Connection> future = authenticationHandshake.requestAuthenticationToPeer(peerAddress); SettableFuture<Connection> future = authenticationHandshake.requestAuthentication(peerAddress);
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(@Nullable Connection connection) { public void onSuccess(@Nullable Connection connection) {
if (connection != null) { if (connection != null) {
setAuthenticated(connection, peerAddress); setAuthenticated(connection, peerAddress);
if (authenticationCompleteHandler != null) if (completeHandler != null)
authenticationCompleteHandler.run(); completeHandler.run();
} }
} }
@ -405,20 +416,13 @@ public class PeerGroup implements MessageListener, ConnectionListener {
connection.setAuthenticated(peerAddress, connection); connection.setAuthenticated(peerAddress, connection);
addAuthenticatedPeer(new Peer(connection)); addAuthenticatedPeer(new Peer(connection));
peerListeners.stream().forEach(e -> e.onConnectionAuthenticated(connection));
} }
private void addAuthenticatedPeer(Peer peer) { private void addAuthenticatedPeer(Peer peer) {
Log.traceCall(peer.toString()); Log.traceCall(peer.toString());
authenticatedPeers.put(peer.address, peer); Address peerAddress = peer.address;
reportedPeerAddresses.remove(peer.address); authenticatedPeers.put(peerAddress, peer);
firstPeerAdded = !firstPeerAdded && authenticatedPeers.size() == 1; reportedPeerAddresses.remove(peerAddress);
peerListeners.stream().forEach(e -> e.onPeerAdded(peer));
if (firstPeerAdded)
peerListeners.stream().forEach(e -> e.onFirstAuthenticatePeer(peer));
if (!checkIfConnectedPeersExceeds()) if (!checkIfConnectedPeersExceeds())
printAuthenticatedPeers(); printAuthenticatedPeers();
@ -451,13 +455,44 @@ public class PeerGroup implements MessageListener, ConnectionListener {
}, 1, 2, TimeUnit.MINUTES); }, 1, 2, TimeUnit.MINUTES);
} }
// TODO needs unit tests
private boolean checkIfConnectedPeersExceeds() { private boolean checkIfConnectedPeersExceeds() {
Log.traceCall(); Log.traceCall();
if (authenticatedPeers.size() > MAX_CONNECTIONS) { int size = authenticatedPeers.size();
log.trace("We have too many connections open. Lets remove the one which was not active recently."); if (size > MAX_CONNECTIONS_LOW_PRIO) {
List<Connection> authenticatedConnections = networkNode.getAllConnections().stream() Set<Connection> allConnections = networkNode.getAllConnections();
log.info("We have {} connections open. Lets remove the passive connections" +
" which have not been active recently.", allConnections.size());
if (size != allConnections.size())
log.warn("authenticatedPeers.size()!=allConnections.size(). There is some inconsistency.");
List<Connection> authenticatedConnections = allConnections.stream()
.filter(e -> e.isAuthenticated())
.filter(e -> e.getConnectionType() == ConnectionsType.PASSIVE)
.collect(Collectors.toList());
if (authenticatedConnections.size() == 0) {
log.debug("There are no passive connections for closing. We check if we are exceeding " +
"MAX_CONNECTIONS_NORMAL ({}) ", MAX_CONNECTIONS_NORMAL_PRIO);
if (size > MAX_CONNECTIONS_NORMAL_PRIO) {
authenticatedConnections = allConnections.stream()
.filter(e -> e.isAuthenticated())
.filter(e -> e.getConnectionType() == ConnectionsType.PASSIVE || e.getConnectionType() == ConnectionsType.ACTIVE)
.collect(Collectors.toList());
if (authenticatedConnections.size() == 0) {
log.debug("There are no passive or active connections for closing. We check if we are exceeding " +
"MAX_CONNECTIONS_HIGH ({}) ", MAX_CONNECTIONS_HIGH_PRIO);
if (size > MAX_CONNECTIONS_HIGH_PRIO) {
authenticatedConnections = allConnections.stream()
.filter(e -> e.isAuthenticated()) .filter(e -> e.isAuthenticated())
.collect(Collectors.toList()); .collect(Collectors.toList());
}
}
}
}
if (authenticatedConnections.size() > 0) {
authenticatedConnections.sort((o1, o2) -> o1.getLastActivityDate().compareTo(o2.getLastActivityDate())); authenticatedConnections.sort((o1, o2) -> o1.getLastActivityDate().compareTo(o2.getLastActivityDate()));
log.info("Number of connections exceeds MAX_CONNECTIONS. Current size=" + authenticatedConnections.size()); log.info("Number of connections exceeds MAX_CONNECTIONS. Current size=" + authenticatedConnections.size());
Connection connection = authenticatedConnections.remove(0); Connection connection = authenticatedConnections.remove(0);
@ -466,14 +501,19 @@ public class PeerGroup implements MessageListener, ConnectionListener {
connection.shutDown(() -> UserThread.runAfterRandomDelay(() -> checkIfConnectedPeersExceeds(), 100, 500, TimeUnit.MILLISECONDS)); connection.shutDown(() -> UserThread.runAfterRandomDelay(() -> checkIfConnectedPeersExceeds(), 100, 500, TimeUnit.MILLISECONDS));
return true; return true;
} else { } else {
log.trace("We don't have too many connections open."); log.warn("That code path should never be reached. (checkIfConnectedPeersExceeds)");
return false;
}
} else {
log.trace("We only have {} connections open and don't need to close any.", size);
return false; return false;
} }
} }
private void pingPeers() { private void pingPeers() {
Log.traceCall();
Set<Peer> connectedPeersList = new HashSet<>(authenticatedPeers.values()); Set<Peer> connectedPeersList = new HashSet<>(authenticatedPeers.values());
if (!connectedPeersList.isEmpty()) {
Log.traceCall();
connectedPeersList.stream() connectedPeersList.stream()
.filter(e -> (new Date().getTime() - e.connection.getLastActivityDate().getTime()) > PING_AFTER_CONNECTION_INACTIVITY) .filter(e -> (new Date().getTime() - e.connection.getLastActivityDate().getTime()) > PING_AFTER_CONNECTION_INACTIVITY)
.forEach(e -> UserThread.runAfterRandomDelay(() -> { .forEach(e -> UserThread.runAfterRandomDelay(() -> {
@ -492,11 +532,12 @@ public class PeerGroup implements MessageListener, ConnectionListener {
}); });
}, 1, 10)); }, 1, 10));
} }
}
private void trySendGetPeersRequest() { private void trySendGetPeersRequest() {
Log.traceCall();
Collection<Peer> peers = authenticatedPeers.values(); Collection<Peer> peers = authenticatedPeers.values();
if (!peers.isEmpty()) { if (!peers.isEmpty()) {
Log.traceCall();
Set<Peer> connectedPeersList = new HashSet<>(peers); Set<Peer> connectedPeersList = new HashSet<>(peers);
connectedPeersList.stream() connectedPeersList.stream()
.forEach(e -> UserThread.runAfterRandomDelay(() -> { .forEach(e -> UserThread.runAfterRandomDelay(() -> {
@ -515,8 +556,6 @@ public class PeerGroup implements MessageListener, ConnectionListener {
} }
}); });
}, 5, 10)); }, 5, 10));
} else {
log.info("No peers available for requesting data.");
} }
} }
@ -576,21 +615,6 @@ public class PeerGroup implements MessageListener, ConnectionListener {
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public void addPeerListener(PeerListener peerListener) {
Log.traceCall();
peerListeners.add(peerListener);
}
public void removePeerListener(PeerListener peerListener) {
Log.traceCall();
peerListeners.remove(peerListener);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getters // Getters
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -642,7 +666,7 @@ public class PeerGroup implements MessageListener, ConnectionListener {
int diff = size - MAX_REPORTED_PEERS; int diff = size - MAX_REPORTED_PEERS;
List<Address> list = new LinkedList<>(getReportedNotConnectedPeerAddresses()); List<Address> list = new LinkedList<>(getReportedNotConnectedPeerAddresses());
for (int i = 0; i < diff; i++) { for (int i = 0; i < diff; i++) {
Address toRemove = getAndRemoveRandomItem(list); Address toRemove = getAndRemoveRandomAddress(list);
reportedPeerAddresses.remove(toRemove); reportedPeerAddresses.remove(toRemove);
} }
} else { } else {
@ -668,12 +692,9 @@ public class PeerGroup implements MessageListener, ConnectionListener {
if (authenticationHandshakes.containsKey(peerAddress)) if (authenticationHandshakes.containsKey(peerAddress))
authenticationHandshakes.remove(peerAddress); authenticationHandshakes.remove(peerAddress);
boolean contained = reportedPeerAddresses.remove(peerAddress); boolean wasInReportedPeers = reportedPeerAddresses.remove(peerAddress);
Peer disconnectedPeer = authenticatedPeers.remove(peerAddress); Peer disconnectedPeer = authenticatedPeers.remove(peerAddress);
if (disconnectedPeer != null) if (wasInReportedPeers || disconnectedPeer != null)
peerListeners.stream().forEach(e -> e.onPeerRemoved(peerAddress));
if (contained || disconnectedPeer != null)
printAllPeers(); printAllPeers();
} }
} }
@ -688,23 +709,24 @@ public class PeerGroup implements MessageListener, ConnectionListener {
// Utils // Utils
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private Address getAndRemoveRandomItem(List<Address> list) { private Optional<Tuple2<Address, Set<Address>>> getRandomNotAuthPeerAndRemainingSet(Set<Address> remainingAddresses) {
Log.traceCall();
return list.remove(new Random().nextInt(list.size()));
}
private Optional<Tuple2<Address, Set<Address>>> getRandomItemAndRemainingSet(Set<Address> remainingAddresses) {
Log.traceCall(); Log.traceCall();
List<Address> list = new ArrayList<>(remainingAddresses); List<Address> list = new ArrayList<>(remainingAddresses);
authenticatedPeers.values().stream().forEach(e -> list.remove(e.address)); authenticatedPeers.values().stream().forEach(e -> list.remove(e.address));
if (!list.isEmpty()) { if (!list.isEmpty()) {
Address item = getAndRemoveRandomItem(list); Address item = getAndRemoveRandomAddress(list);
return Optional.of(new Tuple2<>(item, new HashSet<>(list))); return Optional.of(new Tuple2<>(item, new HashSet<>(list)));
} else { } else {
return Optional.empty(); return Optional.empty();
} }
} }
private Address getAndRemoveRandomAddress(List<Address> list) {
Log.traceCall();
return list.remove(new Random().nextInt(list.size()));
}
public void printAllPeers() { public void printAllPeers() {
printAuthenticatedPeers(); printAuthenticatedPeers();
printReportedPeers(); printReportedPeers();

View file

@ -1,16 +0,0 @@
package io.bitsquare.p2p.peers;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.network.Connection;
public interface PeerListener {
void onFirstAuthenticatePeer(Peer peer);
// TODO never used
void onPeerAdded(Peer peer);
// TODO never used
void onPeerRemoved(Address address);
void onConnectionAuthenticated(Connection connection);
}

View file

@ -65,10 +65,10 @@ public class SeedNode {
String arg2 = args[2]; String arg2 = args[2];
int maxConnections = Integer.parseInt(arg2); int maxConnections = Integer.parseInt(arg2);
checkArgument(maxConnections < 1000, "maxConnections seems to be a bit too high..."); checkArgument(maxConnections < 1000, "maxConnections seems to be a bit too high...");
PeerGroup.setMaxConnections(maxConnections); PeerGroup.setMaxConnectionsLowPrio(maxConnections);
} else { } else {
// we keep default a higher connection size for seed nodes // we keep default a higher connection size for seed nodes
PeerGroup.setMaxConnections(50); PeerGroup.setMaxConnectionsLowPrio(50);
} }
if (args.length > 3) { if (args.length > 3) {
String arg3 = args[3]; String arg3 = args[3];

View file

@ -65,7 +65,7 @@ public class ProtectedExpirableDataStorage implements MessageListener {
private void init() { private void init() {
Log.traceCall(); Log.traceCall();
HashMap<ByteArray, Integer> persisted = storage.initAndGetPersisted(sequenceNumberMap, "sequenceNumberMap"); HashMap<ByteArray, Integer> persisted = storage.initAndGetPersisted(sequenceNumberMap, "SequenceNumberMap");
if (persisted != null) { if (persisted != null) {
sequenceNumberMap = persisted; sequenceNumberMap = persisted;
} }

View file

@ -1,48 +0,0 @@
package io.bitsquare.p2p.storage.messages;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.storage.data.ProtectedData;
import java.util.HashSet;
public final class DataExchangeMessage implements Message {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
private final int networkId = Version.NETWORK_ID;
public final HashSet<ProtectedData> set;
public DataExchangeMessage(HashSet<ProtectedData> set) {
this.set = set;
}
@Override
public int networkId() {
return networkId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DataExchangeMessage)) return false;
DataExchangeMessage that = (DataExchangeMessage) o;
return !(set != null ? !set.equals(that.set) : that.set != null);
}
@Override
public int hashCode() {
return set != null ? set.hashCode() : 0;
}
@Override
public String toString() {
return "GetDataResponse{" +
"networkId=" + networkId +
", set=" + set +
'}';
}
}

View file

@ -59,7 +59,7 @@ public class P2PServiceTest {
LocalhostNetworkNode.setSimulateTorDelayTorNode(10); LocalhostNetworkNode.setSimulateTorDelayTorNode(10);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(100); LocalhostNetworkNode.setSimulateTorDelayHiddenService(100);
PeerGroup.setMaxConnections(8); PeerGroup.setMaxConnectionsLowPrio(8);
keyRing1 = new KeyRing(new KeyStorage(dir1)); keyRing1 = new KeyRing(new KeyStorage(dir1));
keyRing2 = new KeyRing(new KeyStorage(dir2)); keyRing2 = new KeyRing(new KeyStorage(dir2));

View file

@ -1,12 +1,9 @@
package io.bitsquare.p2p.routing; package io.bitsquare.p2p.routing;
import io.bitsquare.common.util.Profiler;
import io.bitsquare.p2p.Address; import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.P2PService; import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener; import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.LocalhostNetworkNode; import io.bitsquare.p2p.network.LocalhostNetworkNode;
import io.bitsquare.p2p.peers.AuthenticationListener;
import io.bitsquare.p2p.peers.PeerGroup; import io.bitsquare.p2p.peers.PeerGroup;
import io.bitsquare.p2p.seed.SeedNode; import io.bitsquare.p2p.seed.SeedNode;
import org.junit.*; import org.junit.*;
@ -36,7 +33,7 @@ public class PeerGroupTest {
public void setup() throws InterruptedException { public void setup() throws InterruptedException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(50); LocalhostNetworkNode.setSimulateTorDelayTorNode(50);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(8); LocalhostNetworkNode.setSimulateTorDelayHiddenService(8);
PeerGroup.setMaxConnections(100); PeerGroup.setMaxConnectionsLowPrio(100);
seedNodes = new HashSet<>(); seedNodes = new HashSet<>();
if (useLocalhost) { if (useLocalhost) {
@ -213,7 +210,8 @@ public class PeerGroupTest {
// node1 -> node2 PeersMessage // node1 -> node2 PeersMessage
// first authentication from seedNode2 to seedNode1, then from seedNode1 to seedNode2 // first authentication from seedNode2 to seedNode1, then from seedNode1 to seedNode2
CountDownLatch latch1 = new CountDownLatch(2); //TODO
/* CountDownLatch latch1 = new CountDownLatch(2);
AuthenticationListener routingListener1 = new AuthenticationListener() { AuthenticationListener routingListener1 = new AuthenticationListener() {
@Override @Override
public void onConnectionAuthenticated(Connection connection) { public void onConnectionAuthenticated(Connection connection) {
@ -274,12 +272,13 @@ public class PeerGroupTest {
seedNode1.shutDown(() -> shutDownLatch.countDown()); seedNode1.shutDown(() -> shutDownLatch.countDown());
seedNode2.shutDown(() -> shutDownLatch.countDown()); seedNode2.shutDown(() -> shutDownLatch.countDown());
seedNode3.shutDown(() -> shutDownLatch.countDown()); seedNode3.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await(); shutDownLatch.await();*/
} }
//@Test //@Test
public void testAuthenticationWithDisconnect() throws InterruptedException { public void testAuthenticationWithDisconnect() throws InterruptedException {
LocalhostNetworkNode.setSimulateTorDelayTorNode(0); //TODO
/* LocalhostNetworkNode.setSimulateTorDelayTorNode(0);
LocalhostNetworkNode.setSimulateTorDelayHiddenService(0); LocalhostNetworkNode.setSimulateTorDelayHiddenService(0);
SeedNode seedNode1 = getAndStartSeedNode(8001); SeedNode seedNode1 = getAndStartSeedNode(8001);
SeedNode seedNode2 = getAndStartSeedNode(8002); SeedNode seedNode2 = getAndStartSeedNode(8002);
@ -340,12 +339,13 @@ public class PeerGroupTest {
CountDownLatch shutDownLatch = new CountDownLatch(2); CountDownLatch shutDownLatch = new CountDownLatch(2);
seedNode1.shutDown(() -> shutDownLatch.countDown()); seedNode1.shutDown(() -> shutDownLatch.countDown());
seedNode2.shutDown(() -> shutDownLatch.countDown()); seedNode2.shutDown(() -> shutDownLatch.countDown());
shutDownLatch.await(); shutDownLatch.await();*/
} }
//@Test //@Test
public void testAuthenticationWithManyNodes() throws InterruptedException { public void testAuthenticationWithManyNodes() throws InterruptedException {
int authentications = 0; //TODO
/* int authentications = 0;
int length = 3; int length = 3;
SeedNode[] nodes = new SeedNode[length]; SeedNode[] nodes = new SeedNode[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
@ -379,7 +379,7 @@ public class PeerGroupTest {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
nodes[i].shutDown(() -> shutDownLatch.countDown()); nodes[i].shutDown(() -> shutDownLatch.countDown());
} }
shutDownLatch.await(); shutDownLatch.await();*/
} }
private SeedNode getAndStartSeedNode(int port) throws InterruptedException { private SeedNode getAndStartSeedNode(int port) throws InterruptedException {