UPnP: IPv6 part 4

WIP - disabled by default
Store local IPv6 address at startup so UPnP can attempt to forward it
Request forwarding of ports to IPv6 addresses
Update status on successful IPv6 forward
Fix IP mismatch test for IPv6
Log tweaks
This commit is contained in:
zzz
2021-02-28 10:00:11 -05:00
parent 851752a57a
commit 9177459db6
5 changed files with 113 additions and 22 deletions

View File

@@ -58,6 +58,18 @@ public interface Transport {
*/
public List<RouterAddress> getCurrentAddresses();
/**
* What address are we currently listening to?
* Replaces getCurrentAddress()
*
* Note: An address without a host is considered IPv4.
*
* @param ipv6 true for IPv6 only; false for IPv4 only
* @return first matching address or null
* @since 0.9.50 lifted from TransportImpl
*/
public RouterAddress getCurrentAddress(boolean ipv6);
/**
* Do we have any current address?
* @since IPv6

View File

@@ -30,6 +30,7 @@ import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.crypto.SigType;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterIdentity;
@@ -85,6 +86,8 @@ public class TransportManager implements TransportEventListener {
public final static String PROP_ENABLE_NTCP = "i2np.ntcp.enable";
/** default true */
public final static String PROP_ENABLE_UPNP = "i2np.upnp.enable";
/** default false for now */
public final static String PROP_ENABLE_UPNP_IPV6 = "i2np.upnp.ipv6.enable";
private static final String PROP_JAVA_PROXY1 = "socksProxyHost";
private static final String PROP_JAVA_PROXY2 = "java.net.useSystemProxies";
private static final String PROP_JAVA_PROXY3 = "http.proxyHost";
@@ -743,25 +746,43 @@ public class TransportManager implements TransportEventListener {
static class Port {
public final String style;
public final int port;
public final boolean isIPv6;
public final String ip;
/**
* IPv4 only
*/
public Port(String style, int port) {
this.style = style;
this.port = port;
isIPv6 = false;
ip = null;
}
/**
* IPv6 only
* @since 0.9.50
*/
public Port(String style, String host, int port) {
this.style = style;
this.port = port;
isIPv6 = true;
ip = host;
}
@Override
public int hashCode() {
return style.hashCode() ^ port;
return style.hashCode() ^ port ^ DataHelper.hashCode(ip);
}
@Override
public boolean equals(Object o) {
if (o == null)
return false;
if (o == this)
return true;
if (! (o instanceof Port))
return false;
Port p = (Port) o;
return port == p.port && style.equals(p.style);
return port == p.port && style.equals(p.style) && DataHelper.eq(ip, p.ip);
}
}
@@ -780,8 +801,24 @@ public class TransportManager implements TransportEventListener {
if (udp != null)
port = udp.getRequestedPort();
}
if (port > 0)
if (port > 0) {
// ipv4
rv.add(new Port(t.getStyle(), port));
// ipv6
if (_context.getBooleanProperty(PROP_ENABLE_UPNP_IPV6)) {
RouterAddress ra = t.getCurrentAddress(true);
if (ra == null) {
if (t.getStyle().equals(UDPTransport.STYLE)) {
UDPTransport udp = (UDPTransport) t;
ra = udp.getCurrentExternalAddress(true);
}
}
if (ra != null) {
String host = ra.getHost();
rv.add(new Port(t.getStyle(), host, port));
}
}
}
}
return rv;
}

View File

@@ -267,7 +267,7 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
}
}
}
Set<String> myAddresses = Addresses.getAddresses(true, false); // yes local, no IPv6
Set<String> myAddresses = Addresses.getAddresses(true, true); // yes local, yes IPv6
if (!ignore && !ALLOW_SAME_HOST && ip != null && myAddresses.contains(ip)) {
ignore = true;
if (_log.shouldWarn())
@@ -282,7 +282,8 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
if (!stringEquals(ip, pktIP)) {
ignore = true;
if (_log.shouldWarn())
_log.warn("Ignoring UPnP with IP mismatch: " + name + " UDN: " + udn);
_log.warn("Ignoring UPnP with IP mismatch: " + name + " UDN: " + udn +
" dev IP " + ip + " pkt IP: " + pktIP);
}
}
}
@@ -1452,6 +1453,8 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
} catch (URISyntaxException use) {}
}
}
if (rv != null && rv.startsWith("[") && rv.endsWith("]"))
rv = rv.substring(1, rv.length() - 1);
return rv;
}
@@ -1556,11 +1559,12 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
portsForwarded.remove(fp);
}
if (!noLog && _log.shouldWarn()) {
int level = retval ? Log.INFO : Log.WARN;
if (!noLog && _log.shouldLog(level)) {
if (retval)
_log.warn("UPnP: Removed IPv4 mapping for "+fp.name+" "+port+" / "+protocol);
_log.log(level, "UPnP: Removed IPv4 mapping for "+fp.name+" "+port+" / "+protocol);
else
_log.warn("UPnP: Failed to remove IPv4 mapping for "+fp.name+" "+port+" / "+protocol);
_log.log(level, "UPnP: Failed to remove IPv4 mapping for "+fp.name+" "+port+" / "+protocol);
}
return retval;
}
@@ -1593,12 +1597,23 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
synchronized(lock) {
portsForwarded.remove(fp);
}
if (!noLog && _log.shouldWarn()) {
int level = retval ? Log.INFO : Log.WARN;
if (!noLog && _log.shouldLog(level)) {
String ip = fp.getIP();
if (retval)
_log.warn("UPnP: Removed IPv6 mapping for " + fp.name + ' ' + ip + ' ' + port + " / " + protocol);
else
_log.warn("UPnP: Failed to remove IPv6 mapping for " + fp.name + ' ' + ip + ' ' + port + " / " + protocol);
if (retval) {
_log.log(level, "UPnP: Removed IPv6 mapping for " + fp.name + ' ' + ip + ' ' + port + " / " + protocol);
} else {
StringBuilder buf = new StringBuilder();
buf.append("UPnP: Failed to remove IPv6 mapping for ").append(fp.getIP()).append(" port ").append(fp.portNumber).append(" / ").append(protocol);
UPnPStatus status = remove.getStatus();
if (status != null)
buf.append(" Status: ").append(status.getCode()).append(' ').append(status.getDescription());
status = remove.getControlStatus();
if (status != null)
buf.append(" ControlStatus: ").append(status.getCode()).append(' ').append(status.getDescription());
_log.log(level, buf.toString());
}
}
return retval;
}

View File

@@ -287,7 +287,11 @@ class UPnPManager {
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Adding: " + style + " " + port);
ForwardPort fp = new ForwardPort(name, false, protocol, port);
ForwardPort fp;
if (entry.isIPv6)
fp = new UPnP.IPv6ForwardPort(name, protocol, port, entry.ip);
else
fp = new ForwardPort(name, false, protocol, port);
forwards.add(fp);
}
// non-blocking
@@ -352,6 +356,7 @@ class UPnPManager {
ForwardPortStatus fps = entry.getValue();
if (_log.shouldDebug())
_log.debug("FPS: " + fp.name + ' ' + fp.protocol + ' ' + fp.portNumber +
(fp.isIP6 ? " IPv6" : " IPv4") +
" status: " + fps.status + " reason: " + fps.reasonString + " ext port: " + fps.externalPort);
String style;
if (fp.protocol == ForwardPort.PROTOCOL_UDP_IPV4) {
@@ -364,8 +369,18 @@ class UPnPManager {
continue;
}
boolean success = fps.status >= ForwardPortStatus.MAYBE_SUCCESS;
byte[] fwdip;
if (fp.isIP6) {
UPnP.IPv6ForwardPort v6fp = (UPnP.IPv6ForwardPort) fp;
String sip = v6fp.getIP();
fwdip = Addresses.getIP(sip);
if (fwdip == null)
continue;
} else {
fwdip = ipaddr;
}
// deadlock path 2
_manager.forwardPortStatus(style, ipaddr, fp.portNumber, fps.externalPort, success, fps.reasonString);
_manager.forwardPortStatus(style, fwdip, fp.portNumber, fps.externalPort, success, fps.reasonString);
}
}
}

View File

@@ -633,6 +633,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
hasv6 = true;
if (isIPv6Firewalled() || _context.getBooleanProperty(PROP_IPV6_FIREWALLED)) {
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_FIREWALLED, true);
// save the external address but don't publish it
// save it where UPnP can get it and try to forward it
OrderedProperties localOpts = new OrderedProperties();
localOpts.setProperty(UDPAddress.PROP_PORT, String.valueOf(newPort));
localOpts.setProperty(UDPAddress.PROP_HOST, newIP);
RouterAddress local = new RouterAddress(STYLE, localOpts, DEFAULT_COST);
replaceCurrentExternalAddress(local, true);
} else {
_lastInboundIPv6 = _context.clock().now();
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
@@ -1013,9 +1020,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
else
_log.warn("UPnP has failed to open the SSU port: " + port + " reason: " + reason);
}
if (success && ip != null && getExternalIP() != null) {
if (!isIPv4Firewalled())
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
if (success && ip != null) {
if (ip.length == 4) {
if (getExternalIP() != null && !isIPv4Firewalled())
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
} else if (ip.length == 16) {
if (!isIPv6Firewalled())
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
}
}
}
@@ -2637,9 +2649,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
* we don't put them in the real, published RouterAddress anymore
* if we are firewalled.
*
* @since 0.9.18, pkg private for PacketBuilder since 0.9.50
* @since 0.9.18, public for PacketBuilder and TransportManager since 0.9.50
*/
RouterAddress getCurrentExternalAddress(boolean isIPv6) {
public RouterAddress getCurrentExternalAddress(boolean isIPv6) {
// deadlock thru here ticket #1699
synchronized (_rebuildLock) {
return isIPv6 ? _currentOurV6Address : _currentOurV4Address;