|
|
|
package org.getmonero.i2p.zero;
|
|
|
|
|
|
|
|
import net.i2p.data.router.RouterAddress;
|
|
|
|
import net.i2p.data.router.RouterInfo;
|
|
|
|
import net.i2p.router.CommSystemFacade;
|
|
|
|
import net.i2p.router.Router;
|
|
|
|
import net.i2p.router.RouterContext;
|
|
|
|
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
|
|
|
import net.i2p.router.transport.FIFOBandwidthRefiller;
|
|
|
|
import net.i2p.router.transport.TransportUtil;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum NetworkState {
|
|
|
|
HIDDEN,
|
|
|
|
TESTING,
|
|
|
|
FIREWALLED,
|
|
|
|
RUNNING,
|
|
|
|
WARN,
|
|
|
|
ERROR,
|
|
|
|
CLOCKSKEW,
|
|
|
|
VMCOMM;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class NetworkStateMessage {
|
|
|
|
private NetworkState state;
|
|
|
|
private String msg;
|
|
|
|
|
|
|
|
NetworkStateMessage(NetworkState state, String msg) {
|
|
|
|
setMessage(state, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMessage(NetworkState state, String msg) {
|
|
|
|
this.state = state;
|
|
|
|
this.msg = msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
public NetworkState getState() {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getMessage() {
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "(" + state + "; " + msg + ')';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public String _t(String s) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
|
|
|
|
final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
|
|
|
|
|
|
|
|
public NetworkStateMessage getReachability() {
|
|
|
|
RouterContext _context = router.getContext();
|
|
|
|
if (_context.commSystem().isDummy())
|
|
|
|
return new NetworkStateMessage(NetworkState.VMCOMM, "VM Comm System");
|
|
|
|
if (_context.router().getUptime() > 60*1000 && (!_context.router().gracefulShutdownInProgress()) &&
|
|
|
|
!_context.clientManager().isAlive())
|
|
|
|
return new NetworkStateMessage(NetworkState.ERROR, _t("ERR-Client Manager I2CP Error - check logs")); // not a router problem but the user should know
|
|
|
|
// Warn based on actual skew from peers, not update status, so if we successfully offset
|
|
|
|
// the clock, we don't complain.
|
|
|
|
//if (!_context.clock().getUpdatedSuccessfully())
|
|
|
|
long skew = _context.commSystem().getFramedAveragePeerClockSkew(33);
|
|
|
|
// Display the actual skew, not the offset
|
|
|
|
if (Math.abs(skew) > 30*1000)
|
|
|
|
return new NetworkStateMessage(NetworkState.CLOCKSKEW, _t("ERR-Clock Skew"));
|
|
|
|
if (_context.router().isHidden())
|
|
|
|
return new NetworkStateMessage(NetworkState.HIDDEN, _t("Hidden"));
|
|
|
|
RouterInfo routerInfo = _context.router().getRouterInfo();
|
|
|
|
if (routerInfo == null)
|
|
|
|
return new NetworkStateMessage(NetworkState.TESTING, _t("Testing"));
|
|
|
|
|
|
|
|
CommSystemFacade.Status status = _context.commSystem().getStatus();
|
|
|
|
NetworkState state = NetworkState.RUNNING;
|
|
|
|
switch (status) {
|
|
|
|
case OK:
|
|
|
|
case IPV4_OK_IPV6_UNKNOWN:
|
|
|
|
case IPV4_OK_IPV6_FIREWALLED:
|
|
|
|
case IPV4_UNKNOWN_IPV6_OK:
|
|
|
|
case IPV4_DISABLED_IPV6_OK:
|
|
|
|
case IPV4_SNAT_IPV6_OK:
|
|
|
|
RouterAddress ra = routerInfo.getTargetAddress("NTCP");
|
|
|
|
if (ra == null)
|
|
|
|
return new NetworkStateMessage(NetworkState.RUNNING, _t(status.toStatusString()));
|
|
|
|
byte[] ip = ra.getIP();
|
|
|
|
if (ip == null)
|
|
|
|
return new NetworkStateMessage(NetworkState.ERROR, _t("ERR-Unresolved TCP Address"));
|
|
|
|
// TODO set IPv6 arg based on configuration?
|
|
|
|
if (TransportUtil.isPubliclyRoutable(ip, true))
|
|
|
|
return new NetworkStateMessage(NetworkState.RUNNING, _t(status.toStatusString()));
|
|
|
|
return new NetworkStateMessage(NetworkState.ERROR, _t("ERR-Private TCP Address"));
|
|
|
|
|
|
|
|
case IPV4_SNAT_IPV6_UNKNOWN:
|
|
|
|
case DIFFERENT:
|
|
|
|
return new NetworkStateMessage(NetworkState.ERROR, _t("ERR-SymmetricNAT"));
|
|
|
|
|
|
|
|
case REJECT_UNSOLICITED:
|
|
|
|
state = NetworkState.FIREWALLED;
|
|
|
|
case IPV4_DISABLED_IPV6_FIREWALLED:
|
|
|
|
if (routerInfo.getTargetAddress("NTCP") != null)
|
|
|
|
return new NetworkStateMessage(NetworkState.WARN, _t("WARN-Firewalled with Inbound TCP Enabled"));
|
|
|
|
// fall through...
|
|
|
|
case IPV4_FIREWALLED_IPV6_OK:
|
|
|
|
case IPV4_FIREWALLED_IPV6_UNKNOWN:
|
|
|
|
if (((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled())
|
|
|
|
return new NetworkStateMessage(NetworkState.WARN, _t("WARN-Firewalled and Floodfill"));
|
|
|
|
//if (_context.router().getRouterInfo().getCapabilities().indexOf('O') >= 0)
|
|
|
|
// return new NetworkStateMessage(NetworkState.WARN, _t("WARN-Firewalled and Fast"));
|
|
|
|
return new NetworkStateMessage(state, _t(status.toStatusString()));
|
|
|
|
|
|
|
|
case DISCONNECTED:
|
|
|
|
return new NetworkStateMessage(NetworkState.TESTING, _t("Disconnected - check network connection"));
|
|
|
|
|
|
|
|
case HOSED:
|
|
|
|
return new NetworkStateMessage(NetworkState.ERROR, _t("ERR-UDP Port In Use - Set i2np.udp.internalPort=xxxx in advanced config and restart"));
|
|
|
|
|
|
|
|
case UNKNOWN:
|
|
|
|
state = NetworkState.TESTING;
|
|
|
|
case IPV4_UNKNOWN_IPV6_FIREWALLED:
|
|
|
|
case IPV4_DISABLED_IPV6_UNKNOWN:
|
|
|
|
default:
|
|
|
|
ra = routerInfo.getTargetAddress("SSU");
|
|
|
|
if (ra == null && _context.router().getUptime() > 5*60*1000) {
|
|
|
|
if (_context.commSystem().countActivePeers() <= 0)
|
|
|
|
return new NetworkStateMessage(NetworkState.ERROR, _t("ERR-No Active Peers, Check Network Connection and Firewall"));
|
|
|
|
else if (_context.getProperty(PROP_I2NP_NTCP_HOSTNAME) == null ||
|
|
|
|
_context.getProperty(PROP_I2NP_NTCP_PORT) == null)
|
|
|
|
return new NetworkStateMessage(NetworkState.ERROR, _t("ERR-UDP Disabled and Inbound TCP host/port not set"));
|
|
|
|
else
|
|
|
|
return new NetworkStateMessage(NetworkState.WARN, _t("WARN-Firewalled with UDP Disabled"));
|
|
|
|
}
|
|
|
|
return new NetworkStateMessage(state, _t(status.toStatusString()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|