Skip to content

Commit

Permalink
Merge pull request #170 from alkum/i2p-preserve-default-session-manager
Browse files Browse the repository at this point in the history
Preserve I2P manager for default session when one peer times out
  • Loading branch information
chimp1984 authored Mar 29, 2022
2 parents 3192ea7 + 196781c commit 6e4c572
Showing 1 changed file with 53 additions and 45 deletions.
98 changes: 53 additions & 45 deletions i2p/src/main/java/bisq/i2p/I2pClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
public class I2pClient {
public final static String DEFAULT_HOST = "127.0.0.1";
public final static int DEFAULT_PORT = 7656;
public final static long DEFAULT_SOCKET_TIMEOUT = TimeUnit.MINUTES.toMillis(3);
// Sockets expected to be created after the router is operational, so no need to have a large value that accommodates for router startup time
public final static long DEFAULT_SOCKET_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
private final static Map<String, I2pClient> I2P_CLIENT_BY_APP = new ConcurrentHashMap<>();

private Router router;
Expand Down Expand Up @@ -260,54 +261,58 @@ private I2PSocketManager startEmbeddedRouter(File privKeyFile) throws IOExceptio
}

private I2PSocketManager maybeCreateServerSession(String sessionId, int port) throws IOException {
if (!sessionMap.containsKey(sessionId)) {
long ts = System.currentTimeMillis();
log.info("Start to create server socket manager for session {} using port {}", sessionId, port);
// There is one manager per sessionId
// Creating the manager is a blocking call, so we synchronize this on sessionId to avoid creating it multiple times during bootstrap
synchronized (sessionId) {
if (!sessionMap.containsKey(sessionId)) {
long ts = System.currentTimeMillis();
log.info("Start to create server socket manager for session {} using port {}", sessionId, port);

String fileName = getFileName(sessionId);
String privKeyFileName = fileName + ".priv_key";
File privKeyFile = new File(privKeyFileName);
PrivateKeyFile pkf = new PrivateKeyFile(privKeyFile);
try {
// Persist priv key to disk
pkf.createIfAbsent();
} catch (I2PException e) {
throw new IOException("Could not persist priv key to disk", e);
}

String fileName = getFileName(sessionId);
String privKeyFileName = fileName + ".priv_key";
File privKeyFile = new File(privKeyFileName);
PrivateKeyFile pkf = new PrivateKeyFile(privKeyFile);
try {
// Persist priv key to disk
pkf.createIfAbsent();
} catch (I2PException e) {
throw new IOException("Could not persist priv key to disk", e);
}
// Create a I2PSocketManager based on the locally persisted private key
// This allows the server to preserve its identity and be reachable at the same destination
I2PSocketManager manager;
try(FileInputStream privKeyInputStream = new FileInputStream(privKeyFile)) {
manager = I2PSocketManagerFactory.createManager(privKeyInputStream); // Blocking while router builds tunnels
}

// Create a I2PSocketManager based on the locally persisted private key
// This allows the server to preserve its identity and be reachable at the same destination
I2PSocketManager manager;
try(FileInputStream privKeyInputStream = new FileInputStream(privKeyFile)) {
manager = I2PSocketManagerFactory.createManager(privKeyInputStream);
}
if (manager == null) {
log.info("No I2P router found, initializing embedded one ...");
manager = startEmbeddedRouter(privKeyFile);
}

if (manager == null) {
log.info("No I2P router found, initializing embedded one ...");
manager = startEmbeddedRouter(privKeyFile);
}
// Set port (which is embedded in the generated destination)
I2PSocketOptions i2PSocketOptions = manager.getDefaultOptions();
i2PSocketOptions.setLocalPort(port);
i2PSocketOptions.setConnectTimeout(Math.toIntExact(socketTimeout));
manager.setDefaultOptions(i2PSocketOptions);

// Persist destination to disk
String destinationBase64 = manager.getSession().getMyDestination().toBase64();
log.info("My destination: {}", destinationBase64);
String destinationFileName = fileName + ".destination";
File destinationFile = new File(destinationFileName);
if (!destinationFile.exists()) {
FileUtils.write(destinationFileName, destinationBase64);
}

// Set port (which is embedded in the generated destination)
I2PSocketOptions i2PSocketOptions = manager.getDefaultOptions();
i2PSocketOptions.setLocalPort(port);
i2PSocketOptions.setConnectTimeout(Math.toIntExact(socketTimeout));
manager.setDefaultOptions(i2PSocketOptions);

// Persist destination to disk
String destinationBase64 = manager.getSession().getMyDestination().toBase64();
log.info("My destination: {}", destinationBase64);
String destinationFileName = fileName + ".destination";
File destinationFile = new File(destinationFileName);
if (!destinationFile.exists()) {
FileUtils.write(destinationFileName, destinationBase64);
// Takes 10-30 sec
log.info("Server socket manager ready for session {}. Took {} ms.", sessionId, System.currentTimeMillis() - ts);
sessionMap.put(sessionId, manager);
}

// Takes 10-30 sec
log.info("Server socket manager ready for session {}. Took {} ms.", sessionId, System.currentTimeMillis() - ts);
sessionMap.put(sessionId, manager);
return sessionMap.get(sessionId);
}

return sessionMap.get(sessionId);
}

private Destination getDestinationFor(String peer) throws IOException {
Expand All @@ -322,11 +327,14 @@ private Destination getDestinationFor(String peer) throws IOException {
}

protected void handleIOException(IOException e, String sessionId) {
e.printStackTrace();
log.error("IO Exception for session " + sessionId + ": " + e.getMessage(), e);

// Only destroy the session manager if the IO exception closed its last connected socket
// The session manager, especially for the default session, handles multiple sockets (one per peer)
I2PSocketManager manager = sessionMap.get(sessionId);
if (manager != null) {
if (manager != null && manager.listSockets().size() == 0) {
manager.destroySocketManager();
sessionMap.remove(sessionId);
}
sessionMap.remove(sessionId);
}
}

0 comments on commit 6e4c572

Please sign in to comment.