package net.i2p.router.web; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import net.i2p.data.DataHelper; import net.i2p.router.Router; import net.i2p.router.transport.FIFOBandwidthRefiller; import net.i2p.router.transport.TransportManager; import net.i2p.router.transport.TransportUtil; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.router.web.ConfigServiceHandler; import net.i2p.util.Addresses; /** * Handler to deal with form submissions from the main config form and act * upon the values. * * Used for both /config and /confignet */ public class ConfigNetHandler extends FormHandler { private String _hostname; private boolean _saveRequested; private boolean _recheckReachabilityRequested; private boolean _requireIntroductions; private boolean _hiddenMode; private boolean _dynamicKeys; private String _ntcpHostname; private String _ntcpPort; private String _tcpPort; private String _udpHost1; private String _udpHost2; private String _udpPort; private String _udpAutoIP; private String _ntcpAutoIP; private boolean _ntcpAutoPort; private boolean _upnp; private boolean _laptop; private String _inboundRate; private String _inboundBurstRate; private String _inboundBurst; private String _outboundRate; private String _outboundBurstRate; private String _outboundBurst; private String _sharePct; private boolean _ratesOnly; private boolean _udpDisabled; private String _ipv6Mode; private boolean _ipv4Firewalled; private boolean _ipv6Firewalled; private final Map<String, String> changes = new HashMap<String, String>(); private static final String PROP_HIDDEN = Router.PROP_HIDDEN_HIDDEN; // see Router for other choice @Override protected void processForm() { if (_saveRequested || ( (_action != null) && (_t("Save changes").equals(_action)) )) { saveChanges(); //} else if (_recheckReachabilityRequested) { // recheckReachability(); } else { // noop } } public void setSave(String moo) { _saveRequested = true; } public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; } public void setRequireIntroductions(String moo) { _requireIntroductions = true; } public void setDynamicKeys(String moo) { _dynamicKeys = true; } public void setEnableloadtesting(String moo) { } public void setUdpAutoIP(String mode) { _udpAutoIP = mode; _hiddenMode = "hidden".equals(mode); } public void setNtcpAutoIP(String mode) { _ntcpAutoIP = mode; } public void setNtcpAutoPort(String mode) { _ntcpAutoPort = mode.equals("2"); } public void setUpnp(String moo) { _upnp = true; } public void setLaptop(String moo) { _laptop = true; } /** @since 0.9.20 */ public void setIPv4Firewalled(String moo) { _ipv4Firewalled = true; } /** @since 0.9.28 */ public void setIPv6Firewalled(String moo) { _ipv6Firewalled = true; } public void setHostname(String hostname) { _hostname = (hostname != null ? hostname.trim() : null); } public void setTcpPort(String port) { _tcpPort = (port != null ? port.trim() : null); } public void setNtcphost(String host) { _ntcpHostname = (host != null ? host.trim() : null); } public void setNtcpport(String port) { _ntcpPort = (port != null ? port.trim() : null); } public void setUdpHost1(String host) { _udpHost1 = (host != null ? host.trim() : null); } public void setUdpPort(String port) { _udpPort = (port != null ? port.trim() : null); } public void setInboundrate(String rate) { _inboundRate = (rate != null ? rate.trim() : null); } public void setInboundburstrate(String rate) { _inboundBurstRate = (rate != null ? rate.trim() : null); } public void setInboundburstfactor(String factor) { _inboundBurst = (factor != null ? factor.trim() : null); } public void setOutboundrate(String rate) { _outboundRate = (rate != null ? rate.trim() : null); } public void setOutboundburstrate(String rate) { _outboundBurstRate = (rate != null ? rate.trim() : null); } public void setOutboundburstfactor(String factor) { _outboundBurst = (factor != null ? factor.trim() : null); } public void setSharePercentage(String pct) { _sharePct = (pct != null ? pct.trim() : null); } /** @since 0.8.12 */ public void setRatesOnly(String foo) { _ratesOnly = true; } /** @since 0.8.13 */ public void setDisableUDP(String foo) { _udpDisabled = true; } /** @since IPv6 */ public void setIpv6(String mode) { _ipv6Mode = mode; } /**** private void recheckReachability() { _context.commSystem().recheckReachability(); addFormNotice(_t("Rechecking router reachability...")); } ****/ /** * The user made changes to the network config and wants to save them, so * lets go ahead and do so. * */ private void saveChanges() { boolean restartRequired = false; boolean error = false; List<String> removes = new ArrayList<String>(); if (!_ratesOnly) { // IP Settings String oldUdp = _context.getProperty(UDPTransport.PROP_SOURCES, _context.router().isHidden() ? "hidden" : UDPTransport.DEFAULT_SOURCES); String oldUHost = _context.getProperty(UDPTransport.PROP_EXTERNAL_HOST, ""); // force change to fixed if user enters a host name/IP if (_udpHost1 != null && _udpHost1.length() > 0) _udpAutoIP = "fixed"; if (_udpAutoIP != null) { String uhost = ""; if (_udpAutoIP.equals("fixed")) { if (_settings == null) _settings = Collections.EMPTY_MAP; Set<String> addrs = new TreeSet<String>(); for (Object o : _settings.keySet()) { String k = (String) o; if (k.startsWith("addr_")) { String v = DataHelper.stripHTML(k.substring(5)); if (v.length() > 0) addrs.add(v); } } if (_udpHost1 != null && _udpHost1.length() > 0) { if (verifyAddress(_udpHost1)) { addrs.add(_udpHost1); } else { // verifyAddress() outputs form error error = true; } } int tot = addrs.size(); int i = 0; if (tot > 0) { StringBuilder buf = new StringBuilder(128); for (String addr : addrs) { buf.append(addr); if (++i < tot) buf.append(','); } uhost = buf.toString(); changes.put(UDPTransport.PROP_EXTERNAL_HOST, uhost); } else { _udpAutoIP = UDPTransport.DEFAULT_SOURCES; removes.add(UDPTransport.PROP_EXTERNAL_HOST); } } else { // not fixed if (oldUHost.length() > 0) removes.add(UDPTransport.PROP_EXTERNAL_HOST); } changes.put(UDPTransport.PROP_SOURCES, _udpAutoIP); if ((!oldUdp.equals(_udpAutoIP)) || (!oldUHost.equals(uhost))) { addFormNotice(_t("Updating IP address")); restartRequired = true; } } if (_ipv6Mode != null) { // take care not to set default, as it will change String tcp6 = _context.getProperty(TransportUtil.NTCP_IPV6_CONFIG); if (tcp6 == null) tcp6 = TransportUtil.DEFAULT_IPV6_CONFIG.toConfigString(); String udp6 = _context.getProperty(TransportUtil.SSU_IPV6_CONFIG); if (udp6 == null) udp6 = TransportUtil.DEFAULT_IPV6_CONFIG.toConfigString(); boolean ch = false; if (!_ipv6Mode.equals(tcp6)) { changes.put(TransportUtil.NTCP_IPV6_CONFIG, _ipv6Mode); ch = true; } if (!_ipv6Mode.equals(udp6)) { changes.put(TransportUtil.SSU_IPV6_CONFIG, _ipv6Mode); ch = true; } if (ch) addFormNotice(_t("Updating IPv6 setting")); } // NTCP Settings // Normalize some things to make the following code a little easier... String oldNHost = _context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME, ""); String oldNPort = _context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_PORT, ""); String oldAutoHost = _context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP, "true"); String sAutoPort = _context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT, "true"); boolean oldAutoPort = Boolean.parseBoolean(sAutoPort); if (_ntcpHostname == null) _ntcpHostname = ""; if (_ntcpPort == null) _ntcpPort = ""; if (_ntcpAutoIP == null) _ntcpAutoIP = "true"; if ((!oldAutoHost.equals(_ntcpAutoIP)) || ! oldNHost.equalsIgnoreCase(_ntcpHostname)) { boolean valid = true; if ("disabled".equals(_ntcpAutoIP)) { addFormNotice(_t("Disabling TCP completely")); } else if ("false".equals(_ntcpAutoIP) && _ntcpHostname.length() > 0) { valid = verifyAddress(_ntcpHostname); if (valid) { changes.put(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME, _ntcpHostname); addFormNotice(_t("Updating TCP address to {0}", _ntcpHostname)); } else { error = true; } } else { removes.add(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME); if ("false".equals(_ntcpAutoIP)) addFormNotice(_t("Disabling inbound TCP")); else addFormNotice(_t("Updating inbound TCP address to auto")); // true or always } if (valid) { changes.put(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP, _ntcpAutoIP); changes.put(TransportManager.PROP_ENABLE_NTCP, "" + !"disabled".equals(_ntcpAutoIP)); restartRequired = true; } } if (oldAutoPort != _ntcpAutoPort || ! oldNPort.equals(_ntcpPort)) { if (_ntcpPort.length() > 0 && !_ntcpAutoPort) { int port = Addresses.getPort(_ntcpPort); if (port != 0) { changes.put(ConfigNetHelper.PROP_I2NP_NTCP_PORT, _ntcpPort); addFormNotice(_t("Updating TCP port to {0}", _ntcpPort)); if (port < 1024) { addFormError(_t("Warning - ports less than 1024 are not recommended")); error = true; } } else { addFormError(_t("Invalid port") + ": " + _ntcpPort); error = true; } } else { removes.add(ConfigNetHelper.PROP_I2NP_NTCP_PORT); addFormNotice(_t("Updating inbound TCP port to auto")); } changes.put(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT, "" + _ntcpAutoPort); restartRequired = true; } // UDP Settings if ( (_udpPort != null) && (_udpPort.length() > 0) ) { String oldPort = _context.getProperty(UDPTransport.PROP_INTERNAL_PORT, "unset"); if (!oldPort.equals(_udpPort)) { int port = Addresses.getPort(_udpPort); if (port != 0) { changes.put(UDPTransport.PROP_INTERNAL_PORT, _udpPort); changes.put(UDPTransport.PROP_EXTERNAL_PORT, _udpPort); addFormNotice(_t("Updating UDP port to {0}", _udpPort)); if (port < 1024) { addFormError(_t("Warning - ports less than 1024 are not recommended")); error = true; } else { restartRequired = true; } } else { addFormError(_t("Invalid port") + ": " + _udpPort); error = true; } } } } boolean ratesUpdated = updateRates(); boolean switchRequired = false; if (!_ratesOnly) { // If hidden mode value changes, restart is required switchRequired = _hiddenMode != _context.router().isHidden(); if (switchRequired) { changes.put(PROP_HIDDEN, "" + _hiddenMode); if (_hiddenMode) addFormError(_t("Gracefully restarting into Hidden Router Mode")); else addFormError(_t("Gracefully restarting to exit Hidden Router Mode")); } changes.put(Router.PROP_DYNAMIC_KEYS, "" + _dynamicKeys); if (_context.getBooleanPropertyDefaultTrue(TransportManager.PROP_ENABLE_UPNP) != _upnp) { // This is minor, don't set restartRequired if (_upnp) addFormNotice(_t("Enabling UPnP")); else addFormNotice(_t("Disabling UPnP")); addFormNotice(_t("Restart required to take effect")); } changes.put(TransportManager.PROP_ENABLE_UPNP, "" + _upnp); if (Boolean.parseBoolean(_context.getProperty(UDPTransport.PROP_LAPTOP_MODE)) != _laptop) { // This is minor, don't set restartRequired if (_laptop) addFormNotice(_t("Enabling laptop mode")); else addFormNotice(_t("Disabling laptop mode")); } changes.put(UDPTransport.PROP_LAPTOP_MODE, "" + _laptop); if (Boolean.parseBoolean(_context.getProperty(TransportUtil.PROP_IPV4_FIREWALLED)) != _ipv4Firewalled) { if (_ipv4Firewalled) addFormNotice(_t("Disabling inbound IPv4")); else addFormNotice(_t("Enabling inbound IPv4")); restartRequired = true; } changes.put(TransportUtil.PROP_IPV4_FIREWALLED, "" + _ipv4Firewalled); if (Boolean.parseBoolean(_context.getProperty(TransportUtil.PROP_IPV6_FIREWALLED)) != _ipv6Firewalled) { if (_ipv6Firewalled) addFormNotice(_t("Disabling inbound IPv6")); else addFormNotice(_t("Enabling inbound IPv6")); restartRequired = true; } changes.put(TransportUtil.PROP_IPV6_FIREWALLED, "" + _ipv6Firewalled); if (_context.getBooleanPropertyDefaultTrue(TransportManager.PROP_ENABLE_UDP) != !_udpDisabled) { if (_udpDisabled) addFormNotice(_t("Disabling UDP")); else addFormNotice(_t("Enabling UDP")); restartRequired = true; } changes.put(TransportManager.PROP_ENABLE_UDP, "" + (!_udpDisabled)); if (_requireIntroductions) { changes.put(UDPTransport.PROP_FORCE_INTRODUCERS, "true"); addFormNotice(_t("Requiring SSU introducers")); } else { removes.add(UDPTransport.PROP_FORCE_INTRODUCERS); } // Hidden in the GUI //LoadTestManager.setEnableLoadTesting(_context, _enableLoadTesting); } boolean saved = _context.router().saveConfig(changes, removes); if (saved) addFormNotice(_t("Configuration saved successfully")); else addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs")); // this has to be after the save if (ratesUpdated) _context.bandwidthLimiter().reinitialize(); if (saved && !error) { if (switchRequired) { hiddenSwitch(); } else if (restartRequired) { //if (_context.hasWrapper()) { // Wow this dumps all conns immediately and really isn't nice addFormNotice("Performing a soft restart"); _context.router().restart(); // restart() returns immediately now //addFormNotice("Soft restart complete"); // Most of the time we aren't changing addresses, just enabling or disabling // things, so let's try just a new routerInfo and see how that works. // Maybe we should restart if we change addresses though? // No, this doesn't work well, really need to call SSU Transport externalAddressReceived(), // but that's hard to get to, and doesn't handle port changes, etc. // So don't do this... //_context.router().rebuildRouterInfo(); //addFormNotice("Router Info rebuilt"); //} else { // There's a few changes that don't really require restart (e.g. enabling inbound TCP) // But it would be hard to get right, so just do a restart. //addFormError(_t("Gracefully restarting I2P to change published router address")); //_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); //} } } } /** * Do basic verification of address here to prevent problems later * @return valid * @since 0.8.9 */ private boolean verifyAddress(String addr) { if (addr == null || addr.length() <= 0) return false; byte[] iab = Addresses.getIP(addr); if (iab == null) { addFormError(_t("Invalid address") + ": " + addr); return false; } // TODO set IPv6 arg based on configuration? boolean rv = TransportUtil.isPubliclyRoutable(iab, true); if (!rv) addFormError(_t("The hostname or IP {0} is not publicly routable", addr)); return rv; } private void hiddenSwitch() { // Full restart required to generate new keys // FIXME don't call wrapper if not present, only rekey ConfigServiceHandler.registerWrapperNotifier(_context, Router.EXIT_GRACEFUL_RESTART, false); _context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); } private static final int DEF_BURST_PCT = 10; private static final int DEF_BURST_TIME = 20; /** * @return changed */ private boolean updateRates() { boolean updated = false; boolean bwUpdated = false; if (_sharePct != null) { String old = _context.router().getConfigSetting(Router.PROP_BANDWIDTH_SHARE_PERCENTAGE); if ( (old == null) || (!old.equals(_sharePct)) ) { changes.put(Router.PROP_BANDWIDTH_SHARE_PERCENTAGE, _sharePct); addFormNotice(_t("Updating bandwidth share percentage")); updated = true; } } // Since burst is now hidden in the gui, set burst to +10% for 20 seconds if ( (_inboundRate != null) && (_inboundRate.length() > 0) && !_inboundRate.equals(_context.getProperty(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, "" + FIFOBandwidthRefiller.DEFAULT_INBOUND_BANDWIDTH))) { changes.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, _inboundRate); try { int rate = Integer.parseInt(_inboundRate) * (100 + DEF_BURST_PCT) / 100; int kb = DEF_BURST_TIME * rate; changes.put(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, "" + rate); changes.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH_PEAK, "" + kb); } catch (NumberFormatException nfe) {} bwUpdated = true; } if ( (_outboundRate != null) && (_outboundRate.length() > 0) && !_outboundRate.equals(_context.getProperty(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, "" + FIFOBandwidthRefiller.DEFAULT_OUTBOUND_BANDWIDTH))) { changes.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, _outboundRate); try { int rate = Integer.parseInt(_outboundRate) * (100 + DEF_BURST_PCT) / 100; int kb = DEF_BURST_TIME * rate; changes.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, "" + rate); changes.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH_PEAK, "" + kb); } catch (NumberFormatException nfe) {} bwUpdated = true; } if (bwUpdated) { addFormNotice(_t("Updated bandwidth limits")); updated = true; } /******* These aren't in the GUI for now if ( (_inboundBurstRate != null) && (_inboundBurstRate.length() > 0) && !_inboundBurstRate.equals(_context.getProperty(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, "" + FIFOBandwidthRefiller.DEFAULT_INBOUND_BURST_BANDWIDTH))) { changes.put(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, _inboundBurstRate); updated = true; } if ( (_outboundBurstRate != null) && (_outboundBurstRate.length() > 0) && !_outboundBurstRate.equals(_context.getProperty(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, "" + FIFOBandwidthRefiller.DEFAULT_OUTBOUND_BURST_BANDWIDTH))) { changes.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, _outboundBurstRate); updated = true; } String inBurstRate = _context.router().getConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH); if (_inboundBurst != null) { int rateKBps = 0; int burstSeconds = 0; try { rateKBps = Integer.parseInt(inBurstRate); burstSeconds = Integer.parseInt(_inboundBurst); } catch (NumberFormatException nfe) { // ignore } if ( (rateKBps > 0) && (burstSeconds > 0) ) { int kb = rateKBps * burstSeconds; changes.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH_PEAK, "" + kb); updated = true; } } String outBurstRate = _context.router().getConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH); if (_outboundBurst != null) { int rateKBps = 0; int burstSeconds = 0; try { rateKBps = Integer.parseInt(outBurstRate); burstSeconds = Integer.parseInt(_outboundBurst); } catch (NumberFormatException nfe) { // ignore } if ( (rateKBps > 0) && (burstSeconds > 0) ) { int kb = rateKBps * burstSeconds; changes.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH_PEAK, "" + kb); updated = true; } } ***********/ return updated; } }