mirror of https://github.com/i2p-zero/i2p-zero
parent
72de15f401
commit
7374e9455c
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="org.getmonero.i2p.zero" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/javafx-sdk-11.0.2/lib/javafx-swt.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/javafx-sdk-11.0.2/lib/javafx.base.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/javafx-sdk-11.0.2/lib/javafx.controls.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/javafx-sdk-11.0.2/lib/javafx.fxml.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/javafx-sdk-11.0.2/lib/javafx.graphics.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/javafx-sdk-11.0.2/lib/javafx.media.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/javafx-sdk-11.0.2/lib/javafx.swing.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../import/javafx-sdk-11.0.2/lib/javafx.web.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,8 @@
|
||||
module org.getmonero.i2p.zero.gui {
|
||||
requires org.getmonero.i2p.zero;
|
||||
requires java.desktop;
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
exports org.getmonero.i2p.zero.gui;
|
||||
opens org.getmonero.i2p.zero.gui;
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package org.getmonero.i2p.zero.gui;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.Pane;
|
||||
import org.getmonero.i2p.zero.TunnelControl;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import static org.getmonero.i2p.zero.TunnelControl.Tunnel;
|
||||
|
||||
public class AddTunnelController {
|
||||
|
||||
@FXML Pane clientTunnelConfigPane;
|
||||
@FXML Pane serverTunnelConfigPane;
|
||||
@FXML Pane socksProxyConfigPane;
|
||||
@FXML Button addButton;
|
||||
@FXML Button cancelButton;
|
||||
@FXML ToggleGroup tunnelType;
|
||||
@FXML RadioButton clientTunnelRadioButton;
|
||||
@FXML RadioButton serverTunnelRadioButton;
|
||||
@FXML RadioButton socksProxyRadioButton;
|
||||
@FXML TextField clientDestAddrField;
|
||||
@FXML TextField clientPortField;
|
||||
@FXML TextField serverHostField;
|
||||
@FXML TextField serverPortField;
|
||||
@FXML TextField serverKeyField;
|
||||
@FXML TextField serverAddrField;
|
||||
@FXML TextField socksPortField;
|
||||
|
||||
|
||||
private void updateAddButtonState() {
|
||||
if(tunnelType.getSelectedToggle().equals(clientTunnelRadioButton)) {
|
||||
addButton.setDisable(Stream.of(clientDestAddrField, clientPortField).anyMatch(f->f.getText().isBlank()));
|
||||
}
|
||||
else if(tunnelType.getSelectedToggle().equals(serverTunnelRadioButton)) {
|
||||
addButton.setDisable(Stream.of(serverHostField, serverPortField, serverKeyField, serverAddrField).anyMatch(f->f.getText().isBlank()));
|
||||
}
|
||||
else if(tunnelType.getSelectedToggle().equals(socksProxyRadioButton)) {
|
||||
addButton.setDisable(Stream.of(socksPortField).anyMatch(f->f.getText().isBlank()));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
|
||||
serverTunnelConfigPane.setVisible(false);
|
||||
socksProxyConfigPane.setVisible(false);
|
||||
|
||||
addButton.setOnAction(ev->{
|
||||
try {
|
||||
var controller = Gui.instance.getController();
|
||||
var tunnelControl = controller.getRouterWrapper().getTunnelControl();
|
||||
var tunnels = tunnelControl.getTunnels();
|
||||
if (tunnelType.getSelectedToggle().equals(clientTunnelRadioButton)) {
|
||||
Tunnel t = new TunnelControl.ClientTunnel(clientDestAddrField.getText(), Integer.parseInt(clientPortField.getText()));
|
||||
tunnels.add(t);
|
||||
controller.tunnelTableList.add(t);
|
||||
} else if (tunnelType.getSelectedToggle().equals(serverTunnelRadioButton)) {
|
||||
Tunnel t = new TunnelControl.ServerTunnel(serverHostField.getText(), Integer.parseInt(serverPortField.getText()), new TunnelControl.KeyPair(serverKeyField.getText()), tunnelControl.getTunnelControlTempDir());
|
||||
tunnels.add(t);
|
||||
controller.tunnelTableList.add(t);
|
||||
} else if (tunnelType.getSelectedToggle().equals(socksProxyRadioButton)) {
|
||||
Tunnel t = new TunnelControl.SocksTunnel(Integer.parseInt(socksPortField.getText()));
|
||||
tunnels.add(t);
|
||||
controller.tunnelTableList.add(t);
|
||||
}
|
||||
clientTunnelConfigPane.getScene().getWindow().hide();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
TextField[] allTextFields = new TextField[] {clientDestAddrField, clientPortField, serverHostField, serverPortField, serverKeyField, serverAddrField, socksPortField};
|
||||
for(TextField f : allTextFields) {
|
||||
f.textProperty().addListener((observable, oldValue, newValue) -> updateAddButtonState());
|
||||
}
|
||||
|
||||
|
||||
cancelButton.setOnAction(e->{
|
||||
clientTunnelConfigPane.getScene().getWindow().hide();
|
||||
});
|
||||
|
||||
tunnelType.selectedToggleProperty().addListener((ov, oldToggle, newToggle)-> {
|
||||
try {
|
||||
var tunnelControl = Gui.instance.getController().getRouterWrapper().getTunnelControl();
|
||||
clientTunnelConfigPane.setVisible(false);
|
||||
serverTunnelConfigPane.setVisible(false);
|
||||
socksProxyConfigPane.setVisible(false);
|
||||
if (newToggle.equals(clientTunnelRadioButton)) clientTunnelConfigPane.setVisible(true);
|
||||
if (newToggle.equals(serverTunnelRadioButton)) {
|
||||
var keyPair = tunnelControl.genKeyPair();
|
||||
serverKeyField.setText(keyPair.seckey + "," + keyPair.pubkey);
|
||||
serverAddrField.setText(keyPair.b32Dest);
|
||||
serverTunnelConfigPane.setVisible(true);
|
||||
}
|
||||
if (newToggle.equals(socksProxyRadioButton)) socksProxyConfigPane.setVisible(true);
|
||||
updateAddButtonState();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
serverAddrField.setEditable(false);
|
||||
|
||||
serverKeyField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
serverAddrField.setText("");
|
||||
String key = newValue;
|
||||
if(key!=null && !key.isEmpty()) {
|
||||
try {
|
||||
TunnelControl.KeyPair keyPair = new TunnelControl.KeyPair(key);
|
||||
serverAddrField.setText(keyPair.b32Dest);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// ignore exception. user may be part way through entering string
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
package org.getmonero.i2p.zero.gui;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.getmonero.i2p.zero.RouterWrapper;
|
||||
import static org.getmonero.i2p.zero.TunnelControl.Tunnel;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Properties;
|
||||
|
||||
public class Controller {
|
||||
|
||||
private RouterWrapper routerWrapper;
|
||||
|
||||
@FXML
|
||||
private BorderPane rootBorderPane;
|
||||
@FXML private Slider bandwidthSlider;
|
||||
@FXML private Label maxBandwidthLabel;
|
||||
@FXML private ImageView masterToggle;
|
||||
@FXML private AnchorPane bandwidthDisabledOverlay;
|
||||
@FXML private Tab bandwidthTab;
|
||||
@FXML private Tab tunnelsTab;
|
||||
@FXML private Tab helpTab;
|
||||
@FXML private Label statusLabel;
|
||||
@FXML private Button tunnelAddButton;
|
||||
@FXML private Button tunnelRemoveButton;
|
||||
@FXML private TableView<Tunnel> tunnelsTableView;
|
||||
@FXML private TableColumn typeCol;
|
||||
@FXML private TableColumn hostCol;
|
||||
@FXML private TableColumn portCol;
|
||||
@FXML private TableColumn i2PCol;
|
||||
@FXML private Label bandwidthIn1s;
|
||||
@FXML private Label bandwidthIn5m;
|
||||
@FXML private Label bandwidthInAll;
|
||||
@FXML private Label totalTransferredIn;
|
||||
@FXML private Label bandwidthOut1s;
|
||||
@FXML private Label bandwidthOut5m;
|
||||
@FXML private Label bandwidthOutAll;
|
||||
@FXML private Label totalTransferredOut;
|
||||
|
||||
DecimalFormat format2dp = new DecimalFormat("0.00");
|
||||
private boolean masterState = true;
|
||||
public final ObservableList<Tunnel> tunnelTableList = FXCollections.observableArrayList();
|
||||
|
||||
private Stage getStage() {
|
||||
return (Stage) rootBorderPane.getScene().getWindow();
|
||||
}
|
||||
|
||||
@FXML private void initialize() {
|
||||
|
||||
typeCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("type"));
|
||||
hostCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("host"));
|
||||
portCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("port"));
|
||||
i2PCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("I2P"));
|
||||
|
||||
DoubleBinding usedWidth = typeCol.widthProperty().add(hostCol.widthProperty()).add(portCol.widthProperty());
|
||||
i2PCol.prefWidthProperty().bind(tunnelsTableView.widthProperty().subtract(usedWidth).subtract(2));
|
||||
|
||||
tunnelsTableView.setItems(tunnelTableList);
|
||||
|
||||
tunnelsTableView.getSelectionModel().selectedItemProperty().addListener((observableValue, oldSelection, newSelection) -> {
|
||||
if (newSelection == null) {
|
||||
tunnelRemoveButton.setDisable(true);
|
||||
}
|
||||
else {
|
||||
tunnelRemoveButton.setDisable(false);
|
||||
}
|
||||
});
|
||||
|
||||
tunnelRemoveButton.setOnAction(e->{
|
||||
Tunnel t = tunnelsTableView.getSelectionModel().getSelectedItem();
|
||||
getRouterWrapper().getTunnelControl().getTunnels().remove(t);
|
||||
tunnelTableList.remove(t);
|
||||
t.destroy();
|
||||
tunnelRemoveButton.setDisable(true);
|
||||
});
|
||||
|
||||
tunnelAddButton.setOnAction(event->{
|
||||
try {
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.WINDOW_MODAL);
|
||||
dialogStage.initOwner(getStage());
|
||||
dialogStage.setResizable(false);
|
||||
dialogStage.setTitle("New tunnel");
|
||||
Scene dialogScene = new Scene(FXMLLoader.load(getClass().getResource("addTunnel.fxml")));
|
||||
dialogScene.getStylesheets().add("org/getmonero/i2p/zero/gui/gui.css");
|
||||
dialogStage.setScene(dialogScene);
|
||||
dialogStage.show();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
EventHandler<Event> tabSelectionEventHandler = e->{
|
||||
Stage stage = getStage();
|
||||
if(bandwidthTab.isSelected()) {
|
||||
stage.setWidth(360);
|
||||
stage.setHeight(370);
|
||||
}
|
||||
else if(tunnelsTab.isSelected()) {
|
||||
stage.setWidth(700);
|
||||
}
|
||||
};
|
||||
bandwidthTab.setOnSelectionChanged(tabSelectionEventHandler);
|
||||
tunnelsTab.setOnSelectionChanged(tabSelectionEventHandler);
|
||||
helpTab.setOnSelectionChanged(tabSelectionEventHandler);
|
||||
|
||||
bandwidthSlider.valueProperty().addListener((observableValue, oldValue, newValue)-> {
|
||||
maxBandwidthLabel.setText(String.format("%.1f", newValue.floatValue()) + " Mbps");
|
||||
routerWrapper.debouncedUpdateBandwidthLimitKBPerSec((int) Math.round(1024d*newValue.doubleValue()/8d));
|
||||
});
|
||||
|
||||
masterToggle.setOnMouseClicked(e->{
|
||||
masterState = !masterState;
|
||||
bandwidthDisabledOverlay.setVisible(!masterState);
|
||||
|
||||
if(masterState) {
|
||||
masterToggle.setImage(new Image("org/getmonero/i2p/zero/gui/toggle-on.png"));
|
||||
statusLabel.setVisible(true);
|
||||
routerWrapper.start();
|
||||
tunnelAddButton.setDisable(false);
|
||||
}
|
||||
else {
|
||||
masterToggle.setImage(new Image("org/getmonero/i2p/zero/gui/toggle-off.png"));
|
||||
statusLabel.setVisible(false);
|
||||
routerWrapper.stop();
|
||||
tunnelTableList.clear();
|
||||
tunnelAddButton.setDisable(true);
|
||||
}
|
||||
});
|
||||
|
||||
startRouter();
|
||||
|
||||
var bandwidthUpdateThread = new Thread(()->{
|
||||
while(!Gui.instance.isStopping()) {
|
||||
if (routerWrapper.isStarted()) {
|
||||
Platform.runLater(() -> {
|
||||
bandwidthIn1s.setText(format2dp.format(routerWrapper.get1sRateInKBps()) + " KBps");
|
||||
bandwidthIn5m.setText(format2dp.format(routerWrapper.get5mRateInKBps()) + " KBps");
|
||||
bandwidthInAll.setText(format2dp.format(routerWrapper.getAvgRateInKBps()) + " KBps");
|
||||
totalTransferredIn.setText(format2dp.format(routerWrapper.getTotalInMB()) + " MB ");
|
||||
bandwidthOut1s.setText(format2dp.format(routerWrapper.get1sRateOutKBps()) + " KBps");
|
||||
bandwidthOut5m.setText(format2dp.format(routerWrapper.get5mRateOutKBps()) + " KBps");
|
||||
bandwidthOutAll.setText(format2dp.format(routerWrapper.getAvgRateOutKBps()) + " KBps");
|
||||
totalTransferredOut.setText(format2dp.format(routerWrapper.getTotalOutMB()) + " MB ");
|
||||
});
|
||||
}
|
||||
try { Thread.sleep(1000); } catch (InterruptedException e) {}
|
||||
}
|
||||
});
|
||||
bandwidthUpdateThread.start();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(()->bandwidthUpdateThread.interrupt()));
|
||||
|
||||
}
|
||||
|
||||
public RouterWrapper getRouterWrapper() {
|
||||
return routerWrapper;
|
||||
}
|
||||
|
||||
private int getBandwidthLimitKBPerSec() {
|
||||
return (int)Math.round(bandwidthSlider.getValue()*1024d/8d);
|
||||
}
|
||||
|
||||
private void startRouter() {
|
||||
|
||||
// need to launch Gui with parameters: --i2p.dir.base= and --i2p.dir.config=
|
||||
|
||||
var params = Gui.instance.getParameters().getNamed();
|
||||
|
||||
Properties routerProperties = new Properties();
|
||||
routerProperties.put("i2p.dir.base", params.get("i2p.dir.base"));
|
||||
routerProperties.put("i2p.dir.config", params.get("i2p.dir.config"));
|
||||
routerProperties.put("i2np.inboundKBytesPerSecond", getBandwidthLimitKBPerSec());
|
||||
routerProperties.put("i2np.outboundKBytesPerSecond", getBandwidthLimitKBPerSec());
|
||||
routerProperties.put("router.sharePercentage", 80);
|
||||
routerWrapper = new RouterWrapper(routerProperties);
|
||||
routerWrapper.start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package org.getmonero.i2p.zero.gui;
|
||||
|
||||
import java.awt.Taskbar;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
public class Gui extends Application {
|
||||
|
||||
|
||||
public static Gui instance;
|
||||
private Controller controller;
|
||||
private boolean isStopping = false;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception{
|
||||
|
||||
instance = this;
|
||||
String osName = System.getProperty("os.name");
|
||||
if(osName.startsWith("Mac")) {
|
||||
Taskbar taskbar = Taskbar.getTaskbar();
|
||||
BufferedImage image = ImageIO.read(getClass().getResource("icon.png"));
|
||||
taskbar.setIconImage(image);
|
||||
}
|
||||
else primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("gui.fxml"));
|
||||
Parent root = loader.load();
|
||||
controller = loader.getController();
|
||||
|
||||
primaryStage.setTitle("I2P-zero");
|
||||
primaryStage.setMinWidth(360);
|
||||
primaryStage.setMinHeight(370);
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add("org/getmonero/i2p/zero/gui/gui.css");
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
isStopping = true;
|
||||
}
|
||||
|
||||
public boolean isStopping() {
|
||||
return isStopping;
|
||||
}
|
||||
|
||||
public Controller getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
Binary file not shown.
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.RadioButton?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.control.ToggleGroup?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
|
||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="241.0" prefWidth="770.0" stylesheets="@gui.css" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.getmonero.i2p.zero.gui.AddTunnelController">
|
||||
<children>
|
||||
<Label layoutX="18.0" layoutY="9.0" prefHeight="27.0" prefWidth="120.0" text="Add a tunnel">
|
||||
</Label>
|
||||
<RadioButton fx:id="clientTunnelRadioButton" layoutX="19.0" layoutY="48.0" mnemonicParsing="false" selected="true" style="" text="Client tunnel - create a local port that forwards requests to a specified I2P destination address">
|
||||
<toggleGroup>
|
||||
<ToggleGroup fx:id="tunnelType" />
|
||||
</toggleGroup>
|
||||
</RadioButton>
|
||||
<RadioButton fx:id="serverTunnelRadioButton" layoutX="19.0" layoutY="73.0" mnemonicParsing="false" text="Server tunnel - create an I2P destination address that will forward requests to the specified host and port" toggleGroup="$tunnelType" />
|
||||
<RadioButton fx:id="socksProxyRadioButton" layoutX="19.0" layoutY="97.0" mnemonicParsing="false" text="SOCKS proxy - applications (like web browsers) that support SOCKS can use your proxy to connect to I2P destinations" toggleGroup="$tunnelType" />
|
||||
<Button fx:id="addButton" defaultButton="true" disable="true" layoutX="712.0" layoutY="197.0" mnemonicParsing="false" text="Add" />
|
||||
<Button fx:id="cancelButton" cancelButton="true" layoutX="647.0" layoutY="197.0" mnemonicParsing="false" text="Cancel" />
|
||||
<Pane fx:id="clientTunnelConfigPane" layoutX="27.0" layoutY="133.0" prefHeight="67.0" prefWidth="523.0">
|
||||
<children>
|
||||
<TextField fx:id="clientDestAddrField" layoutX="165.0" prefHeight="27.0" prefWidth="353.0" />
|
||||
<Label layoutX="1.0" layoutY="4.0" text="I2P destination address" />
|
||||
<TextField fx:id="clientPortField" layoutX="165.0" layoutY="33.0" prefHeight="28.0" prefWidth="84.0" />
|
||||
<Label layoutX="1.0" layoutY="37.0" text="Local port" />
|
||||
</children>
|
||||
</Pane>
|
||||
<Pane fx:id="serverTunnelConfigPane" layoutX="27.0" layoutY="132.0" prefHeight="97.0" prefWidth="523.0" visible="false">
|
||||
<children>
|
||||
<TextField fx:id="serverHostField" layoutX="135.0" />
|
||||
<Label layoutX="1.0" layoutY="5.0" text="Destination host" />
|
||||
<TextField fx:id="serverPortField" layoutX="478.0" prefHeight="27.0" prefWidth="77.0" />
|
||||
<Label layoutX="366.0" layoutY="5.0" text="Destination port" />
|
||||
<TextField fx:id="serverKeyField" layoutX="135.0" layoutY="33.0" prefHeight="28.0" prefWidth="420.0" />
|
||||
<Label layoutX="1.0" layoutY="38.0" text="Server secret key" />
|
||||
<TextField fx:id="serverAddrField" layoutX="135.0" layoutY="66.0" prefHeight="28.0" prefWidth="420.0" />
|
||||
<Label layoutX="1.0" layoutY="71.0" text="Server public address" />
|
||||
</children>
|
||||
</Pane>
|
||||
<Pane fx:id="socksProxyConfigPane" layoutX="27.0" layoutY="132.0" prefHeight="39.0" prefWidth="523.0" visible="false">
|
||||
<children>
|
||||
<TextField fx:id="socksPortField" layoutX="124.0" prefHeight="27.0" prefWidth="77.0" />
|
||||
<Label layoutX="1.0" layoutY="5.0" text="Local SOCKS port" />
|
||||
</children>
|
||||
</Pane>
|
||||
</children>
|
||||
</AnchorPane>
|
@ -0,0 +1,44 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans Regular';
|
||||
src: url('OpenSans-Regular.ttf');
|
||||
}
|
||||
|
||||
.tab .tab-label {
|
||||
-fx-font-size: 12px;
|
||||
-fx-font-family: 'Open Sans Regular';
|
||||
}
|
||||
|
||||
.text-area {
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-family: 'Open Sans Regular';
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-family: 'Open Sans Regular';
|
||||
}
|
||||
|
||||
.label {
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-family: 'Open Sans Regular';
|
||||
}
|
||||
|
||||
.button {
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-family: 'Open Sans Regular';
|
||||
}
|
||||
|
||||
.text-field {
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-family: 'Open Sans Regular';
|
||||
}
|
||||
|
||||
.slider {
|
||||
-fx-font-size: 12px;
|
||||
-fx-font-family: 'Open Sans Regular';
|
||||
}
|
||||
|
||||
.table-view {
|
||||
-fx-font-size: 13px;
|
||||
-fx-font-family: 'Open Sans Regular';
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Slider?>
|
||||
<?import javafx.scene.control.Tab?>
|
||||
<?import javafx.scene.control.TabPane?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
|
||||
<BorderPane fx:id="rootBorderPane" minHeight="340.0" minWidth="360.0" prefHeight="340.0" prefWidth="360.0" stylesheets="@gui.css" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.getmonero.i2p.zero.gui.Controller">
|
||||
<center>
|
||||
<TabPane>
|
||||
<tabs>
|
||||
<Tab fx:id="bandwidthTab" closable="false" text="Bandwidth">
|
||||
<content>
|
||||
<BorderPane minHeight="-Infinity" minWidth="-Infinity">
|
||||
<center>
|
||||
<StackPane BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<AnchorPane maxHeight="230.0" maxWidth="340.0" minHeight="211.0" minWidth="340.0" prefHeight="216.0" prefWidth="340.0">
|
||||
<children>
|
||||
<Slider fx:id="bandwidthSlider" blockIncrement="0.5" layoutX="19.0" layoutY="165.0" majorTickUnit="5.0" max="20.0" min="0.5" minorTickCount="0" prefHeight="33.0" prefWidth="295.0" showTickLabels="true" value="0.5" />
|
||||
<Label layoutX="22.0" layoutY="134.0" style="-fx-font-size: 16;" styleClass="bandwidthLabel" text="Max. Bandwidth">
|
||||
</Label>
|
||||
<Label fx:id="maxBandwidthLabel" layoutX="166.0" layoutY="134.0" prefHeight="22.0" prefWidth="149.0" style="-fx-font-size: 16;" text="0.5 Mbps" textAlignment="right">
|
||||
</Label>
|
||||
<Label layoutX="22.0" layoutY="3.0" style="-fx-font-size: 16;" text="Bandwidth usage">
|
||||
</Label>
|
||||
<Label layoutX="22.0" layoutY="34.0" styleClass="bandwidth" text="1 second average">
|
||||
</Label>
|
||||
<Label layoutX="22.0" layoutY="54.0" styleClass="bandwidth" text="5 minute averge">
|
||||
</Label>
|
||||
<Label layoutX="22.0" layoutY="74.0" text="All-time average">
|
||||
</Label>
|
||||
<Label layoutX="22.0" layoutY="94.0" text="Total transferred">
|
||||
</Label>
|
||||
<Label fx:id="bandwidthIn1s" alignment="CENTER_RIGHT" layoutX="140.0" layoutY="34.0" prefHeight="18.0" prefWidth="80.0" text="0.00 KBps">
|
||||
</Label>
|
||||
<Label fx:id="bandwidthIn5m" alignment="CENTER_RIGHT" layoutX="140.0" layoutY="54.0" prefHeight="18.0" prefWidth="80.0" text="0.00 KBps">
|
||||
</Label>
|
||||
<Label fx:id="bandwidthInAll" alignment="CENTER_RIGHT" layoutX="140.0" layoutY="74.0" prefHeight="18.0" prefWidth="80.0" text="0.00 KBps">
|
||||
</Label>
|
||||
<Label fx:id="totalTransferredIn" alignment="CENTER_RIGHT" layoutX="140.0" layoutY="94.0" prefHeight="18.0" prefWidth="80.0" text="0.00 MB">
|
||||
</Label>
|
||||
<Label alignment="CENTER_RIGHT" layoutX="140.0" layoutY="14.0" prefHeight="18.0" prefWidth="80.0" text="In">
|
||||
</Label>
|
||||
<Label fx:id="bandwidthOut1s" alignment="CENTER_RIGHT" layoutX="231.0" layoutY="34.0" prefHeight="18.0" prefWidth="80.0" text="0.00 KBps">
|
||||
</Label>
|
||||
<Label fx:id="bandwidthOut5m" alignment="CENTER_RIGHT" layoutX="231.0" layoutY="54.0" prefHeight="18.0" prefWidth="80.0" text="0.00 KBps">
|
||||
</Label>
|
||||
<Label fx:id="bandwidthOutAll" alignment="CENTER_RIGHT" layoutX="231.0" layoutY="74.0" prefHeight="18.0" prefWidth="80.0" text="0.00 KBps">
|
||||
</Label>
|
||||
<Label fx:id="totalTransferredOut" alignment="CENTER_RIGHT" layoutX="231.0" layoutY="94.0" prefHeight="18.0" prefWidth="80.0" text="0.00 MB">
|
||||
</Label>
|
||||
<Label alignment="CENTER_RIGHT" layoutX="231.0" layoutY="14.0" prefHeight="18.0" prefWidth="80.0" text="Out">
|
||||
</Label>
|
||||
</children>
|
||||
<StackPane.margin>
|
||||
<Insets top="16.0" />
|
||||
</StackPane.margin>
|
||||
</AnchorPane>
|
||||
<AnchorPane fx:id="bandwidthDisabledOverlay" opacity="0.61" prefHeight="79.0" prefWidth="143.0" style="-fx-background-color: #fff;" visible="false" />
|
||||
</children>
|
||||
</StackPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab fx:id="tunnelsTab" closable="false" text="Tunnels">
|
||||
<content>
|
||||
|
||||
<BorderPane layoutX="9.0" layoutY="56.0" prefWidth="355.0">
|
||||
<center>
|
||||
<TableView fx:id="tunnelsTableView" prefHeight="139.0" prefWidth="318.0">
|
||||
<columns>
|
||||
<TableColumn fx:id="typeCol" prefWidth="93.0" text="Type" />
|
||||
<TableColumn fx:id="hostCol" prefWidth="186.0" text="Host" />
|
||||
<TableColumn fx:id="portCol" minWidth="0.0" prefWidth="70.0" text="Port" />
|
||||
<TableColumn fx:id="i2PCol" prefWidth="280.0" text="I2P address" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</center>
|
||||
<bottom>
|
||||
<BorderPane>
|
||||
<right>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="32.0" prefWidth="339.0" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
|
||||
<Button fx:id="tunnelAddButton" layoutX="301.0" layoutY="7.0" mnemonicParsing="false" text="Add">
|
||||
</Button>
|
||||
<Button fx:id="tunnelRemoveButton" disable="true" layoutX="231.0" layoutY="7.0" mnemonicParsing="false" text="Remove">
|
||||
</Button>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
</bottom>
|
||||
<padding>
|
||||
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
|
||||
</padding>
|
||||
|
||||
</BorderPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab fx:id="helpTab" closable="false" text="Help">
|
||||
<content>
|
||||
<BorderPane>
|
||||
<center>
|
||||
<TextArea fx:id="helpTextArea" editable="false" text="I2P hides your IP address when you connect to other I2P destinations. For example, if you are using a Monero wallet: When your Monero wallet needs to announce a transaction, none of the other Monero I2P nodes that it announces the transaction to will be able to know your IP address. You can create your own I2P destination addresses using the Tunnels tab. Connections received to these I2P addresses will be forwarded to the destination of your choice (such as a local web server). You can also create your own client tunnels, which will allow existing software to easily communicate with remote I2P destinations. I2P achives privacy by routing your traffic through a series of other I2P nodes. Each node between you and your final destination will not be able to tell whether the next node in the chain is your final destination. The nodes you connect to will not know whether you are originating a tunnel, or whether you're simply acting as a link in the chain of somebody else's tunnel. I2P automatically encrypts all traffic, so no node will be able to know what kind of traffic it is forwarding on behalf of others. For more information, visit https://geti2p.net" wrapText="true" BorderPane.alignment="CENTER">
|
||||
<BorderPane.margin>
|
||||
<Insets />
|
||||
</BorderPane.margin>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets></TextArea>
|
||||
</center>
|
||||
</BorderPane>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
</center>
|
||||
<top>
|
||||
<BorderPane prefHeight="76.0" prefWidth="400.0" style="-fx-background-color: #fff;" BorderPane.alignment="CENTER">
|
||||
<left>
|
||||
<Pane prefHeight="71.0" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<ImageView fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@i2p-zero.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</children>
|
||||
</Pane>
|
||||
</left>
|
||||
<right>
|
||||
<Pane prefHeight="76.0" prefWidth="155.0" style="-fx-background-color: #fff;" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<ImageView fx:id="masterToggle" fitHeight="34.0" fitWidth="101.0" layoutX="23.0" layoutY="10.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@toggle-on.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
<Label fx:id="statusLabel" layoutY="44.0" prefHeight="18.0" prefWidth="148.0" style="-fx-alignment: center;" text="Status: Firewalled">
|
||||
</Label>
|
||||
</children>
|
||||
</Pane>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</top>
|
||||
</BorderPane>
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../target/modules/i2p.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../target/modules/i2ptunnel.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../target/modules/mstreaming.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../target/modules/router.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../target/modules/sam.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../target/modules/streaming.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,165 @@
|
||||
package org.getmonero.i2p.zero;
|
||||
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.transport.FIFOBandwidthRefiller;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
public class RouterWrapper {
|
||||
|
||||
private Router router;
|
||||
private boolean started = false;
|
||||
private Properties routerProperties;
|
||||
private TunnelControl tunnelControl;
|
||||
|
||||
public RouterWrapper(Properties p) {
|
||||
this.routerProperties = p;
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return started;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
|
||||
new Thread(()-> {
|
||||
if(started) return;
|
||||
started = true;
|
||||
|
||||
router = new Router(routerProperties);
|
||||
|
||||
new Thread(()->{
|
||||
router.setKillVMOnEnd(false);
|
||||
router.runRouter();
|
||||
}).start();
|
||||
|
||||
new Thread(()->{
|
||||
try {
|
||||
while(true) {
|
||||
if(router.isAlive()) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
Thread.sleep(1000);
|
||||
System.out.println("Waiting for I2P router to start...");
|
||||
}
|
||||
}
|
||||
|
||||
tunnelControl = new TunnelControl(router, new File(new File(routerProperties.getProperty("i2p.dir.config")), "tunnel"));
|
||||
new Thread(tunnelControl).start();
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(()->stop()));
|
||||
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if(!started) return;
|
||||
started = false;
|
||||
tunnelControl.stop();
|
||||
System.out.println("I2P router will shut down gracefully");
|
||||
router.shutdownGracefully();
|
||||
}
|
||||
|
||||
long lastTriggerTimestamp = 0;
|
||||
public void debouncedUpdateBandwidthLimitKBPerSec(int n) {
|
||||
long triggerTime = new Date().getTime();
|
||||
lastTriggerTimestamp = triggerTime;
|
||||
new Thread(()->{
|
||||
try { Thread.sleep(2000); } catch(InterruptedException e) {}
|
||||
if(lastTriggerTimestamp==triggerTime) {
|
||||
// nothing happened after we were triggered, so proceed
|
||||
updateBandwidthLimitKBPerSec(n);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void updateBandwidthLimitKBPerSec(int n) {
|
||||
routerProperties.put("i2np.inboundKBytesPerSecond", n);
|
||||
routerProperties.put("i2np.outboundKBytesPerSecond", n);
|
||||
|
||||
var changes = new HashMap<String, String>();
|
||||
|
||||
final int DEF_BURST_PCT = 10;
|
||||
final int DEF_BURST_TIME = 20;
|
||||
|
||||
int inboundRate = n;
|
||||
int outboundRate = n;
|
||||
|
||||
{
|
||||
float rate = inboundRate / 1.024f;
|
||||
float kb = DEF_BURST_TIME * rate;
|
||||
changes.put(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, Integer.toString(Math.round(rate)));
|
||||
changes.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH_PEAK, Integer.toString(Math.round(kb)));
|
||||
rate -= Math.min(rate * DEF_BURST_PCT / 100, 50);
|
||||
changes.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, Integer.toString(Math.round(rate)));
|
||||
}
|
||||
{
|
||||
float rate = outboundRate / 1.024f;
|
||||
float kb = DEF_BURST_TIME * rate;
|
||||
changes.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, Integer.toString(Math.round(rate)));
|
||||
changes.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH_PEAK, Integer.toString(Math.round(kb)));
|
||||
rate -= Math.min(rate * DEF_BURST_PCT / 100, 50);
|
||||
changes.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, Integer.toString(Math.round(rate)));
|
||||
}
|
||||
|
||||
boolean saved = router.saveConfig(changes, null);
|
||||
// this has to be after the save
|
||||
router.getContext().bandwidthLimiter().reinitialize();
|
||||
if(!saved) throw new RuntimeException("Error saving the new bandwidth limit");
|
||||
|
||||
}
|
||||
|
||||
public double get1sRateInKBps() {
|
||||
try { return router.getContext().bandwidthLimiter().getReceiveBps()/1024d; } catch (Exception e) { return 0; }
|
||||
}
|
||||
public double get1sRateOutKBps() {
|
||||
try { return router.getContext().bandwidthLimiter().getSendBps()/1024d; } catch (Exception e) { return 0; }
|
||||
}
|
||||
public double get5mRateInKBps() {
|
||||
try {
|
||||
return router.getContext().statManager().getRate("bw.recvRate").getRate(5*60*1000).getAverageValue()/1024d;
|
||||
}
|
||||
catch (Exception e) { return 0; }
|
||||
}
|
||||
public double get5mRateOutKBps() {
|
||||
try {
|
||||
return router.getContext().statManager().getRate("bw.sendRate").getRate(5*60*1000).getAverageValue()/1024d;
|
||||
}
|
||||
catch (Exception e) { return 0; }
|
||||
}
|
||||
public double getAvgRateInKBps() {
|
||||
try {
|
||||
return router.getContext().statManager().getRate("bw.recvRate").getLifetimeAverageValue()/1024d;
|
||||
}
|
||||
catch (Exception e) { return 0; }
|
||||
}
|
||||
public double getAvgRateOutKBps() {
|
||||
try {
|
||||
return router.getContext().statManager().getRate("bw.sendRate").getLifetimeAverageValue()/1024d;
|
||||
}
|
||||
catch (Exception e) { return 0; }
|
||||
}
|
||||
public double getTotalInMB() {
|
||||
try { return router.getContext().bandwidthLimiter().getTotalAllocatedInboundBytes()/1048576d; } catch (Exception e) { return 0; }
|
||||
}
|
||||
public double getTotalOutMB() {
|
||||
try { return router.getContext().bandwidthLimiter().getTotalAllocatedOutboundBytes()/1048576d; } catch (Exception e) { return 0; }
|
||||
}
|
||||
|
||||
public TunnelControl getTunnelControl() {
|
||||
return tunnelControl;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,277 @@
|
||||
package org.getmonero.i2p.zero;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.ClientAppManagerImpl;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.sam.SAMBridge;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class TunnelControl implements Runnable {
|
||||
|
||||
private List<Tunnel> tunnels = new ArrayList<>();
|
||||
private int clientPortSeq = 30000;
|
||||
private Router router;
|
||||
private boolean stopping = false;
|
||||
private ServerSocket controlServerSocket;
|
||||
private File tunnelControlTempDir;
|
||||
|
||||
|
||||
public TunnelControl(Router router, File tunnelControlTempDir) {
|
||||
this.router = router;
|
||||
tunnelControlTempDir.delete();
|
||||
tunnelControlTempDir.mkdir();
|
||||
this.tunnelControlTempDir = tunnelControlTempDir;
|
||||
}
|
||||
|
||||
public interface Tunnel {
|
||||
public String getType();
|
||||
public String getHost();
|
||||
public String getPort();
|
||||
public String getI2P();
|
||||
public void destroy();
|
||||
}
|
||||
|
||||
public static class ClientTunnel implements Tunnel {
|
||||
public String dest;
|
||||
public int port;
|
||||
public I2PTunnel tunnel;
|
||||
public ClientTunnel(String dest, int port) {
|
||||
this.dest = dest;
|
||||
this.port = port;
|
||||
new Thread(()->{
|
||||
tunnel = new I2PTunnel(new String[]{"-die", "-nocli", "-e", "config localhost 7654", "-e", "client " + port + " " + dest});
|
||||
}).start();
|
||||
}
|
||||
public void destroy() {
|
||||
new Thread(()->{
|
||||
tunnel.runClose(new String[]{"forced", "all"}, tunnel);
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override public String getType() { return "client"; }
|
||||
@Override public String getHost() { return "localhost"; }
|
||||
@Override public String getPort() { return port+""; }
|
||||
@Override public String getI2P() { return dest; }
|
||||
}
|
||||
public static class ServerTunnel implements Tunnel {
|
||||
public String dest;
|
||||
public String host;
|
||||
public int port;
|
||||
public I2PTunnel tunnel;
|
||||
public KeyPair keyPair;
|
||||
public ServerTunnel(String host, int port, KeyPair keyPair, File tunnelControlTempDir) throws Exception {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.keyPair = keyPair;
|
||||
this.dest = keyPair.b32Dest;
|
||||
|
||||
String uuid = new BigInteger(128, new Random()).toString(16);
|
||||
String seckeyPath = tunnelControlTempDir.getAbsolutePath() + File.separator + "seckey."+uuid+".dat";
|
||||
|
||||
Files.write(Path.of(seckeyPath), Base64.decode(keyPair.seckey));
|
||||
|
||||
// listen using the I2P server keypair, and forward incoming connections to a destination and port
|
||||
new Thread(()->{
|
||||
tunnel = new I2PTunnel(new String[]{"-die", "-nocli", "-e", "server "+host+" "+port+" " + seckeyPath});
|
||||
}).start();
|
||||
}
|
||||
public void destroy() {
|
||||
new Thread(()->{
|
||||
tunnel.runClose(new String[]{"forced", "all"}, tunnel);
|
||||
}).start();
|
||||
}
|
||||
@Override public String getType() { return "server"; }
|
||||
@Override public String getHost() { return host; }
|
||||
@Override public String getPort() { return port+""; }
|
||||
@Override public String getI2P() { return dest; }
|
||||
|
||||
}
|
||||
public static class SocksTunnel implements Tunnel {
|
||||
public int port;
|
||||
public I2PTunnel tunnel;
|
||||
public SocksTunnel(int port) {
|
||||
this.port = port;
|
||||
new Thread(()->{
|
||||
tunnel = new I2PTunnel(new String[]{"-die", "-nocli", "-e", "sockstunnel " + port});
|
||||
}).start();
|
||||
}
|
||||
public void destroy() {
|
||||
new Thread(()->{
|
||||
tunnel.runClose(new String[]{"forced", "all"}, tunnel);
|
||||
}).start();
|
||||
}
|
||||
@Override public String getType() { return "socks"; }
|
||||
@Override public String getHost() { return "localhost"; }
|
||||
@Override public String getPort() { return port+""; }
|
||||
@Override public String getI2P() { return "n/a"; }
|
||||
}
|
||||
|
||||
public List<Tunnel> getTunnels() {
|
||||
return tunnels;
|
||||
}
|
||||
|
||||
public KeyPair genKeyPair() throws Exception {
|
||||
ByteArrayOutputStream seckey = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Destination d = client.createDestination(seckey);
|
||||
d.writeBytes(pubkey);
|
||||
String b32Dest = d.toBase32();
|
||||
return new KeyPair(Base64.encode(seckey.toByteArray()), Base64.encode(pubkey.toByteArray()), b32Dest);
|
||||
}
|
||||
|
||||
public static class KeyPair {
|
||||
public String seckey;
|
||||
public String pubkey;
|
||||
public String b32Dest;
|
||||
public KeyPair(String seckey, String pubkey, String b32Dest) {
|
||||
this.seckey = seckey;
|
||||
this.pubkey = pubkey;
|
||||
this.b32Dest = b32Dest;
|
||||
}
|
||||
public KeyPair(String base64EncodedCommaDelimitedPair) throws Exception {
|
||||
String[] a = base64EncodedCommaDelimitedPair.split(",");
|
||||
this.seckey = a[0];
|
||||
this.pubkey = a[1];
|
||||
Destination d = new Destination();
|
||||
d.readBytes(new ByteArrayInputStream(Base64.decode(this.seckey)));
|
||||
this.b32Dest = d.toBase32();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// listen for socket connections to the tunnel controller.
|
||||
// listen for the commands on port 30000:
|
||||
// server.create <host> <port> // returns a newly created destination public key, which will listen for i2p connections and forward them to the specified host and port
|
||||
// server.destroy <i2p destination public key> // closes the tunnel listening for connections on the specified destination public key, and returns OK
|
||||
// client.create <i2p destination public key> // returns a newly created localhost port number, where connections will be sent over I2P to the destination public key
|
||||
// client.destroy <port> // closes the tunnel listening for connections on the specified port, and returns OK
|
||||
// socks.create <port> // creates a socks proxy listening on the specified port
|
||||
// socks.destroy <port> // closes the socks proxy listening 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 {
|
||||
controlServerSocket = new ServerSocket(clientPortSeq++, 0, InetAddress.getLoopbackAddress());
|
||||
while (!stopping) {
|
||||
try (var socket = controlServerSocket.accept()) {
|
||||
var out = new PrintWriter(socket.getOutputStream(), true);
|
||||
var in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
||||
var args = in.readLine().split(" ");
|
||||
|
||||
switch(args[0]) {
|
||||
|
||||
case "server.create":
|
||||
String destHost = args[1];
|
||||
int destPort = Integer.parseInt(args[2]);
|
||||
var tunnel = new ServerTunnel(destHost, destPort, genKeyPair(), getTunnelControlTempDir());
|
||||
tunnels.add(tunnel);
|
||||
out.println(tunnel.dest);
|
||||
break;
|
||||
|
||||
case "server.destroy":
|
||||
String dest = args[1];
|
||||
new ArrayList<>(tunnels).stream().filter(t->t.getType().equals("server") && ((ServerTunnel) t).dest.equals(dest)).forEach(t->{
|
||||
t.destroy();
|
||||
tunnels.remove(t);
|
||||
});
|
||||
out.println("OK");
|
||||
break;
|
||||
|
||||
case "client.create":
|
||||
String destPubKey = args[1];
|
||||
var clientTunnel = new ClientTunnel(destPubKey, clientPortSeq++);
|
||||
tunnels.add(clientTunnel);
|
||||
out.println(clientTunnel.port);
|
||||
break;
|
||||
|
||||
case "client.destroy": {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
new ArrayList<>(tunnels).stream().filter(t->t.getType().equals("client") && ((ClientTunnel) t).port == port).forEach(t->{
|
||||
t.destroy();
|
||||
tunnels.remove(t);
|
||||
});
|
||||
out.println("OK");
|
||||
break;
|
||||
}
|
||||
|
||||
case "socks.create": {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
tunnels.add(new SocksTunnel(port));
|
||||
out.println("OK");
|
||||
break;
|
||||
}
|
||||
|
||||
case "socks.destroy":
|
||||
int port = Integer.parseInt(args[1]);
|
||||
new ArrayList<>(tunnels).stream().filter(t->t.getType().equals("socks") && ((SocksTunnel) t).port == port).forEach(t->{
|
||||
t.destroy();
|
||||
tunnels.remove(t);
|
||||
});
|
||||
out.println("OK");
|
||||
break;
|
||||
|
||||
case "sam.create":
|
||||
String[] samArgs = new String[]{"sam.keys", "127.0.0.1", "7656", "i2cp.tcp.host=127.0.0.1", "i2cp.tcp.port=7654"};
|
||||
I2PAppContext context = router.getContext();
|
||||
ClientAppManager mgr = new ClientAppManagerImpl(context);
|
||||
SAMBridge samBridge = new SAMBridge(context, mgr, samArgs);
|
||||
samBridge.startup();
|
||||
out.println("OK");
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
if(!e.getMessage().contains("Socket closed")) e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
if(controlServerSocket!=null) try {
|
||||
controlServerSocket.close();
|
||||
}
|
||||
catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
stopping = true;
|
||||
try {
|
||||
controlServerSocket.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public File getTunnelControlTempDir() {
|
||||
return tunnelControlTempDir;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
set DIR=%~dp0
|
||||
"%DIR%\java" -cp %DIR%\..\i2p.base\jbigi.jar -m org.getmonero.i2p.zero.gui --i2p.dir.base=%DIR%\..\i2p.base --i2p.dir.config=%DIR%\..\i2p.config %*
|
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $(uname -s) = Darwin ]; then
|
||||
basedir=$(dirname $(cd "$(dirname "$0")"; pwd -P))
|
||||
else
|
||||
basedir=$(dirname $(dirname $(readlink -fm $0)))
|
||||
fi
|
||||
|
||||
$basedir/bin/java -cp $basedir/i2p.base/jbigi.jar -m org.getmonero.i2p.zero.gui --i2p.dir.base=$basedir/i2p.base --i2p.dir.config=$basedir/i2p.config
|
@ -1,145 +0,0 @@
|
||||
package org.getmonero.i2p.zero;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.ClientAppManagerImpl;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.sam.SAMBridge;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.FileInputStream;
|
||||
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<Integer, I2PTunnel> clientTunnels = new HashMap<>();
|
||||
private Map<String, I2PTunnel> serverTunnels = new HashMap<>();
|
||||
private Map<Integer, I2PTunnel> socksTunnels = new HashMap<>();
|
||||
private int clientPortSeq = 30000;
|
||||
private int serverSeq = 0;
|
||||
private String tunnelConfigDirPrefix;
|
||||
private Router router;
|
||||
public TunnelControl(Router router, File tunnelConfigDir) {
|
||||
this.router = router;
|
||||
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 <host> <port> // returns a newly created destination public key, which will listen for i2p connections and forward them to the specified host and port
|
||||
// server.destroy <i2p destination public key> // closes the tunnel listening for connections on the specified destination public key, and returns OK
|
||||
// client.create <i2p destination public key> // returns a newly created localhost port number, where connections will be sent over I2P to the destination public key
|
||||
// client.destroy <port> // closes the tunnel listening for connections on the specified port, and returns OK
|
||||
// socks.create <port> // creates a socks proxy listening on the specified port
|
||||
// socks.destroy <port> // closes the socks proxy listening 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(()->{
|
||||
I2PTunnel t = new I2PTunnel(new String[]{"-die", "-nocli", "-e", "server "+destHost+" "+destPort+" " + seckeyPath});
|
||||
serverTunnels.put(destPubKey, t);
|
||||
}).start();
|
||||
try (var fis = new FileInputStream(seckeyPath)) {
|
||||
Destination d = new Destination();
|
||||
d.readBytes(fis);
|
||||
out.println(d.toBase32());
|
||||
}
|
||||
}
|
||||
else if(args[0].equals("server.destroy")) {
|
||||
String destPubKey = args[1];
|
||||
new Thread(()->{
|
||||
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(()->{
|
||||
var t = new I2PTunnel(new String[]{"-die", "-nocli", "-e", "config localhost 7654", "-e", "client " + port + " " + destPubKey});
|
||||
clientTunnels.put(port, t);
|
||||
}).start();
|
||||
out.println(port);
|
||||
}
|
||||
else if(args[0].equals("client.destroy")) {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
new Thread(()->{
|
||||
var t = clientTunnels.get(port);
|
||||
clientTunnels.remove(port);
|
||||
t.runClose(new String[]{"forced", "all"}, t);
|
||||
}).start();
|
||||
out.println("OK");
|
||||
}
|
||||
else if(args[0].equals("socks.create")) {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
new Thread(()->{
|
||||
var t = new I2PTunnel(new String[]{"-die", "-nocli", "-e", "sockstunnel " + port});
|
||||
socksTunnels.put(port, t);
|
||||
}).start();
|
||||
out.println("OK");
|
||||
}
|
||||
else if(args[0].equals("socks.destroy")) {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
new Thread(()->{
|
||||
var t = socksTunnels.get(port);
|
||||
socksTunnels.remove(port);
|
||||
t.runClose(new String[]{"forced", "all"}, t);
|
||||
}).start();
|
||||
out.println("OK");
|
||||
}
|
||||
else if(args[0].equals("sam.create")) {
|
||||
String[] samArgs = new String[]{"sam.keys", "127.0.0.1", "7656", "i2cp.tcp.host=127.0.0.1", "i2cp.tcp.port=7654"};
|
||||
I2PAppContext context = router.getContext();
|
||||
ClientAppManager mgr = new ClientAppManagerImpl(context);
|
||||
SAMBridge samBridge = new SAMBridge(context, mgr, samArgs);
|
||||
samBridge.startup();
|
||||
out.println("OK");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in new issue