forked from I2P_Developers/i2p.i2p
UPnP: IPv6 part 3
WIP - more to follow Add support for WANIPv6FirewallControl service Add class extending PortForward to request forwarding to a specific IP, and to store UID Bind POST socket to local IP for POST to WANIPv6FirewallControl service Reduce max forward attempts Don't sleep after last forward attempt fails Log tweaks
This commit is contained in:
@@ -103,6 +103,7 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
|
||||
private Device _router;
|
||||
private Service _service;
|
||||
private Service _service6;
|
||||
// UDN -> device
|
||||
private final Map<String, Device> _otherUDNs;
|
||||
private final Map<String, String> _eventVars;
|
||||
@@ -169,6 +170,7 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
synchronized(lock) {
|
||||
_router = null;
|
||||
_service = null;
|
||||
_service6 = null;
|
||||
_serviceLacksAPM = false;
|
||||
_permanentLeasesOnly = false;
|
||||
}
|
||||
@@ -286,14 +288,16 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
}
|
||||
|
||||
// Find valid service
|
||||
List<Service> services = null;
|
||||
Service service = null;
|
||||
String extIP = null;
|
||||
boolean subscriptionFailed = false;
|
||||
if (!ignore) {
|
||||
service = discoverService(dev);
|
||||
if (service == null) {
|
||||
services = discoverService(dev);
|
||||
if (services == null) {
|
||||
ignore = true;
|
||||
} else {
|
||||
service = services.get(0);
|
||||
// does it have an external IP?
|
||||
extIP = getNATAddress(service);
|
||||
if (extIP == null) {
|
||||
@@ -394,6 +398,8 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
_eventVars.clear();
|
||||
_router = dev;
|
||||
_service = service;
|
||||
if (services.size() > 1)
|
||||
_service6 = services.get(1);
|
||||
_permanentLeasesOnly = false;
|
||||
}
|
||||
if (fpc != null)
|
||||
@@ -427,10 +433,12 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
|
||||
/**
|
||||
* Traverses the structure of the router device looking for the port mapping service.
|
||||
* The first Service will be the IPv4 Service.
|
||||
* The second Service, if present, will be the IPv6 Service.
|
||||
*
|
||||
* @return the service or null
|
||||
* @return the list of services, non-empty, one or two entries, or null
|
||||
*/
|
||||
private Service discoverService(Device router) {
|
||||
private List<Service> discoverService(Device router) {
|
||||
for (Device current : router.getDeviceList()) {
|
||||
String type = current.getDeviceType();
|
||||
if (!(WAN_DEVICE.equals(type) || WAN_DEVICE_2.equals(type)))
|
||||
@@ -454,13 +462,16 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldInfo()) {
|
||||
if (service != null) {
|
||||
Service svc2 = current2.getService(WAN_IPV6_CONNECTION);
|
||||
if (svc2 != null)
|
||||
_log.info(_router.getFriendlyName() + " supports WANIPv6Connection, but we don't");
|
||||
if (svc2 != null) {
|
||||
List<Service> rv = new ArrayList<Service>(2);
|
||||
rv.add(service);
|
||||
rv.add(svc2);
|
||||
return rv;
|
||||
}
|
||||
return Collections.singletonList(service);
|
||||
}
|
||||
if (service != null)
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,15 +479,18 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
}
|
||||
|
||||
private boolean tryAddMapping(String protocol, int port, String description, ForwardPort fp) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Registering a port mapping for " + port + "/" + protocol);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Registering a port mapping for " + port + "/" + protocol + " IPv" + (fp.isIP6 ? '6' : '4'));
|
||||
int nbOfTries = 0;
|
||||
final int maxTries = fp.isIP6 ? 1 : 3;
|
||||
boolean isPortForwarded = false;
|
||||
while ((!_serviceLacksAPM) && nbOfTries++ < 5) {
|
||||
while ((!_serviceLacksAPM) && nbOfTries++ < maxTries) {
|
||||
//isPortForwarded = addMapping(protocol, port, "I2P " + description, fp);
|
||||
isPortForwarded = addMapping(protocol, port, description, fp);
|
||||
if(isPortForwarded || _serviceLacksAPM)
|
||||
break;
|
||||
if (++nbOfTries >= maxTries)
|
||||
break;
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {}
|
||||
@@ -523,6 +537,7 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
runSearch = true;
|
||||
_router = null;
|
||||
_service = null;
|
||||
_service6 = null;
|
||||
_eventVars.clear();
|
||||
_serviceLacksAPM = false;
|
||||
_permanentLeasesOnly = false;
|
||||
@@ -617,11 +632,13 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
boolean isIGD = (ROUTER_DEVICE.equals(type) || ROUTER_DEVICE_2.equals(type)) && dev.isRootDevice();
|
||||
if (!isIGD)
|
||||
continue;
|
||||
Service service = discoverService(dev);
|
||||
if (service == null)
|
||||
List<Service> services = discoverService(dev);
|
||||
if (services == null)
|
||||
continue;
|
||||
if (sid.equals(service.getSID()))
|
||||
return dev;
|
||||
for (Service service : services) {
|
||||
if (sid.equals(service.getSID()))
|
||||
return dev;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -1117,6 +1134,7 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
|
||||
Device router;
|
||||
Service service;
|
||||
Service service6;
|
||||
synchronized(lock) {
|
||||
if (!_otherUDNs.isEmpty()) {
|
||||
sb.append("<b>");
|
||||
@@ -1159,6 +1177,7 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
}
|
||||
router = _router;
|
||||
service = _service;
|
||||
service6 = _service6;
|
||||
}
|
||||
listSubDev(null, router, sb);
|
||||
String addr = getNATAddress(service);
|
||||
@@ -1176,6 +1195,7 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
synchronized(lock) {
|
||||
for(ForwardPort port : portsToForward) {
|
||||
sb.append("<br>");
|
||||
sb.append(port.isIP6 ? "IPv6: " : "IPv4: ");
|
||||
if(portsForwarded.contains(port))
|
||||
// {0} is TCP or UDP
|
||||
// {1,number,#####} prevents 12345 from being output as 12,345 in the English locale.
|
||||
@@ -1211,6 +1231,7 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
/**
|
||||
* This always requests that the external port == the internal port, for now.
|
||||
* Blocking!
|
||||
* @return success
|
||||
*/
|
||||
private boolean addMapping(String protocol, int port, String description, ForwardPort fp) {
|
||||
Service service;
|
||||
@@ -1219,13 +1240,27 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
_log.error("Can't addMapping: " + isNATPresent() + " " + _router);
|
||||
return false;
|
||||
}
|
||||
service = _service;
|
||||
service = fp.isIP6 ? _service6 : _service;
|
||||
}
|
||||
|
||||
// Just in case...
|
||||
// this confuses my linksys? - zzz
|
||||
//removeMapping(protocol, port, fp, true);
|
||||
|
||||
if (service == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("No service for IPv" + (fp.isIP6 ? '6' : '4'));
|
||||
return false;
|
||||
}
|
||||
if (fp.isIP6)
|
||||
return addMappingV6(service, port, (IPv6ForwardPort) fp);
|
||||
else
|
||||
return addMappingV4(service, protocol, port, description, fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* This always requests that the external port == the internal port, for now.
|
||||
* Blocking!
|
||||
*
|
||||
* @return success
|
||||
* @since 0.9.50 split out from above
|
||||
*/
|
||||
private boolean addMappingV4(Service service, String protocol, int port, String description, ForwardPort fp) {
|
||||
Action add = service.getAction("AddPortMapping");
|
||||
if(add == null) {
|
||||
if (_serviceLacksAPM) {
|
||||
@@ -1306,6 +1341,92 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* This always requests that the external port == the internal port, for now.
|
||||
* Blocking!
|
||||
*
|
||||
* @param service WANIPv6FirewallControl
|
||||
* @return success
|
||||
* @since 0.9.50
|
||||
*/
|
||||
private boolean addMappingV6(Service service, int port, IPv6ForwardPort fp) {
|
||||
Action add = service.getAction("AddPinhole");
|
||||
if (add == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("UPnP device does not support pinholing");
|
||||
return false;
|
||||
}
|
||||
String ip = fp.getIP();
|
||||
add.setArgumentValue("RemoteHost", ip);
|
||||
add.setArgumentValue("RemotePort", port);
|
||||
add.setArgumentValue("InternalClient", ip);
|
||||
add.setArgumentValue("InternalPort", port);
|
||||
add.setArgumentValue("Protocol", fp.protocol);
|
||||
// permanent leases aren't supported by miniupnpd anyway
|
||||
int leaseTime = 3*60*60;
|
||||
add.setArgumentValue("LeaseTime", leaseTime);
|
||||
int uid = fp.getUID();
|
||||
if (uid < 0) {
|
||||
uid = getNewUID();
|
||||
fp.setUID(uid);
|
||||
}
|
||||
add.setArgumentValue("UniqueID", uid);
|
||||
|
||||
// I2P - bind the POST socket to the given IP if we're sending to IPv6
|
||||
boolean rv;
|
||||
String hisIP = service.getRootDevice().getLocation(true);
|
||||
if (hisIP != null && hisIP.contains(":")) {
|
||||
rv = add.postControlAction(ip);
|
||||
} else {
|
||||
// this probably won't work if security is enabled
|
||||
rv = add.postControlAction();
|
||||
}
|
||||
if (rv) {
|
||||
synchronized(lock) {
|
||||
portsForwarded.add(fp);
|
||||
}
|
||||
}
|
||||
int level = rv ? Log.INFO : Log.WARN;
|
||||
if (_log.shouldLog(level)) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("AddPinhole result for ").append(ip).append(' ').append(fp.protocol).append(" port ").append(port);
|
||||
UPnPStatus status = add.getStatus();
|
||||
if (status != null)
|
||||
buf.append(" Status: ").append(status.getCode()).append(' ').append(status.getDescription());
|
||||
status = add.getControlStatus();
|
||||
if (status != null)
|
||||
buf.append(" ControlStatus: ").append(status.getCode()).append(' ').append(status.getDescription());
|
||||
_log.log(level, buf.toString());
|
||||
}
|
||||
|
||||
// 606 Action not authorized
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 65536 isn't a lot, so check for dups
|
||||
*
|
||||
* @return 0 - 65535
|
||||
* @since 0.9.50
|
||||
*/
|
||||
private int getNewUID() {
|
||||
synchronized(lock) {
|
||||
while(true) {
|
||||
int rv = _context.random().nextInt(65536);
|
||||
boolean dup = false;
|
||||
for (ForwardPort fp : portsToForward) {
|
||||
if (fp.isIP6 && ((IPv6ForwardPort) fp).getUID() == rv) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup)
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dev non-null
|
||||
@@ -1389,7 +1510,10 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** blocking */
|
||||
/**
|
||||
* Blocking
|
||||
* @return success
|
||||
*/
|
||||
private boolean removeMapping(String protocol, int port, ForwardPort fp, boolean noLog) {
|
||||
Service service;
|
||||
synchronized(lock) {
|
||||
@@ -1397,9 +1521,25 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
_log.error("Can't removeMapping: " + isNATPresent() + " " + _router);
|
||||
return false;
|
||||
}
|
||||
service = _service;
|
||||
service = fp.isIP6 ? _service6 : _service;
|
||||
}
|
||||
if (service == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("No service for IPv" + (fp.isIP6 ? '6' : '4'));
|
||||
return false;
|
||||
}
|
||||
if (fp.isIP6)
|
||||
return removeMappingV6(service, protocol, port, (IPv6ForwardPort) fp, noLog);
|
||||
else
|
||||
return removeMappingV4(service, protocol, port, fp, noLog);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @since 0.9.50 split out from above
|
||||
* @return success
|
||||
*/
|
||||
private boolean removeMappingV4(Service service, String protocol, int port, ForwardPort fp, boolean noLog) {
|
||||
Action remove = service.getAction("DeletePortMapping");
|
||||
if(remove == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -1416,8 +1556,50 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
portsForwarded.remove(fp);
|
||||
}
|
||||
|
||||
if(_log.shouldLog(Log.WARN) && !noLog)
|
||||
_log.warn("UPnP: Removed mapping for "+fp.name+" "+port+" / "+protocol);
|
||||
if (!noLog && _log.shouldWarn()) {
|
||||
if (retval)
|
||||
_log.warn("UPnP: Removed IPv4 mapping for "+fp.name+" "+port+" / "+protocol);
|
||||
else
|
||||
_log.warn("UPnP: Failed to remove IPv4 mapping for "+fp.name+" "+port+" / "+protocol);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @since 0.9.50
|
||||
* @return success
|
||||
*/
|
||||
private boolean removeMappingV6(Service service, String protocol, int port, IPv6ForwardPort fp, boolean noLog) {
|
||||
int uid = fp.getUID();
|
||||
if (uid < 0)
|
||||
return false;
|
||||
Action remove = service.getAction("DeletePinhole");
|
||||
if (remove == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Couldn't find DeletePinhole action!");
|
||||
return false;
|
||||
}
|
||||
remove.setArgumentValue("UniqueID", uid);
|
||||
// I2P - bind the POST socket to the given IP if we're sending to IPv6
|
||||
boolean retval;
|
||||
String hisIP = service.getRootDevice().getLocation(true);
|
||||
if (hisIP != null && hisIP.contains(":")) {
|
||||
String ip = fp.getIP();
|
||||
retval = remove.postControlAction(ip);
|
||||
} else {
|
||||
retval = remove.postControlAction();
|
||||
}
|
||||
synchronized(lock) {
|
||||
portsForwarded.remove(fp);
|
||||
}
|
||||
if (!noLog && _log.shouldWarn()) {
|
||||
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);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -1468,6 +1650,8 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
if(ports.contains(port)) {
|
||||
// Should be forwarded, has been forwarded, cool.
|
||||
} else {
|
||||
// TODO don't dump old ipv6 immediately if temporary
|
||||
|
||||
// Needs dropping
|
||||
if(portsToDumpNow == null) portsToDumpNow = new HashSet<ForwardPort>();
|
||||
portsToDumpNow.add(port);
|
||||
@@ -1584,6 +1768,51 @@ public class UPnP extends ControlPoint implements DeviceChangeListener, EventLis
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended to store the requested IP to be forwarded.
|
||||
* @since 0.9.50
|
||||
*/
|
||||
static class IPv6ForwardPort extends ForwardPort {
|
||||
private final String _ip;
|
||||
private int _uid = -1;
|
||||
|
||||
/**
|
||||
* @param ip the IPv6 address being forwarded
|
||||
*/
|
||||
public IPv6ForwardPort(String name, int protocol, int port, String ip) {
|
||||
super(name, true, protocol, port);
|
||||
_ip = ip;
|
||||
}
|
||||
|
||||
public String getIP() { return _ip; }
|
||||
|
||||
/**
|
||||
* @return 0-65535 or -1 if unset
|
||||
*/
|
||||
public synchronized int getUID() { return _uid; }
|
||||
|
||||
/**
|
||||
* @param uid 0-65535
|
||||
*/
|
||||
public synchronized void setUID(int uid) { _uid = uid; }
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return _ip.hashCode() ^ super.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignores UID
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof IPv6ForwardPort)) return false;
|
||||
IPv6ForwardPort f = (IPv6ForwardPort) o;
|
||||
return _ip.equals(f.getIP()) && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps out device info in semi-HTML format
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user