From 7b6a21c15a5382a6be7e9bc1bfa93ebe61acfa57 Mon Sep 17 00:00:00 2001 From: knaccc Date: Sun, 20 Jan 2019 15:41:04 +0000 Subject: [PATCH] Tunnel control now lets you easily create client and server tunnels --- README.md | 20 +++ bin/build-all.sh | 5 +- bin/build-launcher.sh | 9 +- bin/build-original-i2p.sh | 2 +- resources/tunnel-control.sh | 3 + src/module-info.java | 1 + src/org/getmonero/i2p/embedded/Main.java | 3 + .../getmonero/i2p/embedded/TunnelControl.java | 114 ++++++++++++++++++ 8 files changed, 150 insertions(+), 7 deletions(-) create mode 100755 resources/tunnel-control.sh create mode 100644 src/org/getmonero/i2p/embedded/TunnelControl.java diff --git a/README.md b/README.md index 0ce1598..af16173 100755 --- a/README.md +++ b/README.md @@ -63,3 +63,23 @@ Press Ctrl-C to gracefully shut down the router (or send the SIGINT signal to th ## Check that the I2P router is running and that it is listening for SAM connections `fuser 7656/tcp` + + +## Tunnel control + +Listen for i2p connections and forward them to the specified host and port. Returns a newly created destination public key. +`dist/linux/router/bin/tunnel-control.sh server.create ` + +// Close the tunnel listening for connections on the specified port. Returns "OK". +`dist/linux/router/bin/tunnel-control.sh server.destroy ` + +Create a tunnel that listens for connections on localhost and forwards connections over I2P to the specified destination public key. Returns a newly created localhost port number. +`dist/linux/router/bin/tunnel-control.sh client.create ` + +Close the tunnel listening for connections on the specified port. Returns "OK". +`dist/linux/router/bin/tunnel-control.sh client.destroy ` + + +## Watch the I2P log for messages + +`tail -f dist/linux/router/i2p.config/wrapper.log` diff --git a/bin/build-all.sh b/bin/build-all.sh index cd20a38..a5e5c7c 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -8,6 +8,9 @@ $basedir/bin/import-packages.sh # build the i2p project retrieved from the I2P repository $basedir/bin/build-original-i2p.sh -# convert the imported JARs to modules, compile the Java source code in this project, and then use the jlink tool +# convert the jar files from an existing I2P build into modules suitable for use with jlink +$basedir/bin/convert-jars-to-modules.sh + +# compile the Java source code in this project, and then use the jlink tool # to build a zero-dependency platform-specific launcher $basedir/bin/build-launcher.sh diff --git a/bin/build-launcher.sh b/bin/build-launcher.sh index b6741ed..b17beae 100755 --- a/bin/build-launcher.sh +++ b/bin/build-launcher.sh @@ -4,9 +4,6 @@ basedir=$(dirname $(dirname $(readlink -fm $0))) source $basedir/bin/java-config.sh -# convert the jar files from an existing I2P build into modules suitable for use with jlink -$basedir/bin/convert-jars-to-modules.sh - # compile the Main class that starts the I2P router and SAM listener echo "*** Compiling Main class" $JAVA_HOME/bin/javac --module-path import/lib -d target/classes $(find src -name '*.java') @@ -29,8 +26,10 @@ echo "*** Performing jlink (Windows)" $JAVA_HOME/bin/jlink --module-path $basedir/import/jdks/win/jdk-${JDK_VERSION}/jmods:target/modules:target/org.getmonero.i2p.embedded.jar --add-modules org.getmonero.i2p.embedded --output dist/win/router --strip-debug --compress 2 --no-header-files --no-man-pages -cp $basedir/resources/launch.sh $basedir/dist/linux/router/bin/ -cp $basedir/resources/launch.sh $basedir/dist/mac/router/bin/ +for i in linux mac; do + cp $basedir/resources/launch.sh $basedir/dist/$i/router/bin/ + cp $basedir/resources/tunnel-control.sh $basedir/dist/$i/router/bin/ +done cp $basedir/resources/launch.bat $basedir/dist/win/router/bin/ for i in linux mac win; do cp -r $basedir/import/i2p.base $basedir/dist/$i/router/; done diff --git a/bin/build-original-i2p.sh b/bin/build-original-i2p.sh index 402acac..eafcbd3 100755 --- a/bin/build-original-i2p.sh +++ b/bin/build-original-i2p.sh @@ -16,7 +16,7 @@ cd .. # copy the jars that we're going to modularize rm -fr $basedir/import/lib mkdir -p $basedir/import/lib -for i in i2p.jar mstreaming.jar router.jar sam.jar streaming.jar; do cp $basedir/import/i2p.i2p/build/$i $basedir/import/lib/; done +for i in i2ptunnel.jar i2p.jar mstreaming.jar router.jar sam.jar streaming.jar; do cp $basedir/import/i2p.i2p/build/$i $basedir/import/lib/; done # build a minimal i2p.base dir rm -fr $basedir/import/i2p.base diff --git a/resources/tunnel-control.sh b/resources/tunnel-control.sh new file mode 100755 index 0000000..24ad902 --- /dev/null +++ b/resources/tunnel-control.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +exec 3<>/dev/tcp/localhost/30000; echo "$1 $2 $3" >&3; cat <&3 \ No newline at end of file diff --git a/src/module-info.java b/src/module-info.java index b325f19..3cb892e 100644 --- a/src/module-info.java +++ b/src/module-info.java @@ -4,5 +4,6 @@ module org.getmonero.i2p.embedded { requires streaming; requires router; requires sam; + requires i2ptunnel; requires jdk.crypto.ec; } \ No newline at end of file diff --git a/src/org/getmonero/i2p/embedded/Main.java b/src/org/getmonero/i2p/embedded/Main.java index 67f4562..d4f53c2 100644 --- a/src/org/getmonero/i2p/embedded/Main.java +++ b/src/org/getmonero/i2p/embedded/Main.java @@ -71,6 +71,9 @@ public class Main { ClientAppManager mgr = new ClientAppManagerImpl(context); SAMBridge samBridge = new SAMBridge(context, mgr, args); samBridge.startup(); + + new Thread(new TunnelControl(new File(new File(p.getProperty("i2p.dir.config")), "tunnel"))).start(); + } catch (Exception e) { e.printStackTrace(); diff --git a/src/org/getmonero/i2p/embedded/TunnelControl.java b/src/org/getmonero/i2p/embedded/TunnelControl.java new file mode 100644 index 0000000..7de35ce --- /dev/null +++ b/src/org/getmonero/i2p/embedded/TunnelControl.java @@ -0,0 +1,114 @@ +package org.getmonero.i2p.embedded; + +import net.i2p.data.Base64; +import net.i2p.i2ptunnel.I2PTunnel; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; + +public class TunnelControl implements Runnable { + + private Map clientTunnels = new HashMap<>(); + private Map serverTunnels = new HashMap<>(); + private int clientPortSeq = 30000; + private int serverSeq = 0; + private String tunnelConfigDirPrefix; + public TunnelControl(File tunnelConfigDir) { + tunnelConfigDir.delete(); + tunnelConfigDir.mkdir(); + this.tunnelConfigDirPrefix = tunnelConfigDir.getAbsolutePath() + File.separator; + } + + @Override + public void run() { + + // listen for socket connections to the tunnel controller. + // listen for the commands on port 30000: + // server.create // returns a newly created destination public key, which will listen for i2p connections and forward them to the specified host and port + // server.destroy // closes the tunnel listening for connections on the specified port, and returns OK + // client.create // returns a newly created localhost port number, where connections will be sent over I2P to the destination public key + // client.destroy // closes the tunnel listening for connections on the specified port, and returns OK + // + // send a command with bash: exec 3<>/dev/tcp/localhost/30000; echo "server.create localhost 80" >&3; cat <&3 + // + try (var listener = new ServerSocket(clientPortSeq++, 0, InetAddress.getLoopbackAddress())) { + while (true) { + try (var socket = listener.accept()) { + var out = new PrintWriter(socket.getOutputStream(), true); + var in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + var args = in.readLine().split(" "); + if(args[0].equals("server.create")) { + String destHost = args[1]; + String destPort = args[2]; + String seckeyPath = tunnelConfigDirPrefix + "seckey."+serverSeq+".dat"; + String pubkeyPath = tunnelConfigDirPrefix + "pubkey."+serverSeq+".dat"; + serverSeq++; + new I2PTunnel(new String[]{"-die", "-nocli", "-e", "genkeys " + seckeyPath + " " + pubkeyPath}); + String destPubKey = Base64.encode(Files.readAllBytes(new File(pubkeyPath).toPath())); + + // listen using the I2P server keypair, and forward incoming connections to a destination and port + new Thread() { + @Override + public void run() { + I2PTunnel t = new I2PTunnel(new String[]{"-die", "-nocli", "-e", "server "+destHost+" "+destPort+" " + seckeyPath}); + serverTunnels.put(destPubKey, t); + } + }.start(); + out.println(destPubKey); + } + else if(args[0].equals("server.destroy")) { + String destPubKey = args[1]; + new Thread() { + @Override + public void run() { + var t = serverTunnels.get(destPubKey); + serverTunnels.remove(destPubKey); + t.runClose(new String[]{"forced", "all"}, t); + } + }.start(); + out.println("OK"); + } + else if(args[0].equals("client.create")) { + String destPubKey = args[1]; + int port = clientPortSeq++; + new Thread() { + @Override + public void run() { + var t = new I2PTunnel(new String[]{"-die", "-nocli", "-e", "config localhost 7654", "-e", "client " + port + " " + destPubKey}); + clientTunnels.put(port, t); + } + }.start(); + out.println(port); + } + if(args[0].equals("client.destroy")) { + int port = Integer.parseInt(args[1]); + new Thread() { + @Override + public void run() { + var t = clientTunnels.get(port); + clientTunnels.remove(port); + t.runClose(new String[]{"forced", "all"}, t); + } + }.start(); + out.println("OK"); + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + + } +}