Cleanup UPnP API

This commit addresses most of the issues identified in #432:

* Support UPnP IGDs with both WANIPConnection and WANPPPConnection services

  IGDs that offer both WANIPConnection and WANPPPConnection services should
  now have port forwarding correctly configured for all services.

* Support multiple UPnP WANDevice and WANConnection descriptions

  Per Figure 1 of the InternetGatewayDevice specification
  (http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf),
  an IGD may have multiple WAN devices, each with multiple WANConnection
  services

* Support for discovery of UPnP InternetGatewayDevice version 2 devices

* Support for discovery of multiple UPnP IGDs

  Consumers that cannot yet properly process multiple IGDs can simply use
  the first IGD listed in the discovery results

* Logging refinements such as friendly UPnP IGD identifiers in log messages.
This commit is contained in:
Caleb Callaway
2014-09-25 21:28:53 -07:00
parent 598ce4bb5f
commit c17507b216
2 changed files with 362 additions and 137 deletions

View File

@@ -103,6 +103,7 @@ var (
stop = make(chan int)
discoverer *discover.Discoverer
externalPort int
igd *upnp.IGD
cert tls.Certificate
)
@@ -474,6 +475,7 @@ func syncthingMain() {
externalPort = addr.Port
// UPnP
igd = nil
if opts.UPnPEnabled {
setupUPnP()
@@ -689,20 +691,20 @@ func setupUPnP() {
} else {
// Set up incoming port forwarding, if necessary and possible
port, _ := strconv.Atoi(portStr)
igd, err := upnp.Discover()
if err == nil {
igds := upnp.Discover()
if len(igds) > 0 {
// Configure the first discovered IGD only. This is a work-around until we have a better mechanism
// for handling multiple IGDs, which will require changes to the global discovery service
igd = igds[0]
externalPort = setupExternalPort(igd, port)
if externalPort == 0 {
l.Warnln("Failed to create UPnP port mapping")
} else {
l.Infoln("Created UPnP port mapping - external port", externalPort)
}
} else {
l.Infof("No UPnP gateway detected")
if debugNet {
l.Debugf("UPnP: %v", err)
l.Infof("Created UPnP port mapping for external port %d on UPnP device %s.", externalPort, igd.FriendlyIdentifier())
}
}
if opts.UPnPRenewal > 0 {
go renewUPnP(port)
}
@@ -713,7 +715,11 @@ func setupUPnP() {
}
func setupExternalPort(igd *upnp.IGD, port int) int {
// We seed the random number generator with the device ID to get a
if igd == nil {
return 0
}
// We seed the random number generator with the node ID to get a
// repeatable sequence of random external ports.
rnd := rand.NewSource(certSeed(cert.Certificate[0]))
for i := 0; i < 10; i++ {
@@ -731,32 +737,46 @@ func renewUPnP(port int) {
opts := cfg.Options()
time.Sleep(time.Duration(opts.UPnPRenewal) * time.Minute)
igd, err := upnp.Discover()
if err != nil {
continue
// Make sure our IGD reference isn't nil
if igd == nil {
l.Infoln("Undefined IGD during UPnP port renewal. Re-discovering...")
igds := upnp.Discover()
if len(igds) > 0 {
// Configure the first discovered IGD only. This is a work-around until we have a better mechanism
// for handling multiple IGDs, which will require changes to the global discovery service
igd = igds[0]
} else {
l.Infof("Failed to re-discover IGD during UPnP port mapping renewal.")
continue
}
}
// Just renew the same port that we already have
if externalPort != 0 {
err = igd.AddPortMapping(upnp.TCP, externalPort, port, "syncthing", opts.UPnPLease*60)
err := igd.AddPortMapping(upnp.TCP, externalPort, port, "syncthing", opts.UPnPLease*60)
if err == nil {
l.Infoln("Renewed UPnP port mapping - external port", externalPort)
continue
l.Infof("Renewed UPnP port mapping for external port %d on device %s.", externalPort, igd.FriendlyIdentifier())
} else {
l.Warnf("Error renewing UPnP port mapping for external port %d on device %s: %s", externalPort, igd.FriendlyIdentifier(), err.Error())
}
continue
}
// Something strange has happened. We didn't have an external port before?
// Or perhaps the gateway has changed?
// Retry the same port sequence from the beginning.
l.Infoln("No UPnP port mapping defined, updating...")
r := setupExternalPort(igd, port)
if r != 0 {
externalPort = r
l.Infoln("Updated UPnP port mapping - external port", externalPort)
l.Infof("Updated UPnP port mapping for external port %d on device %s.", externalPort, igd.FriendlyIdentifier())
discoverer.StopGlobal()
discoverer.StartGlobal(opts.GlobalAnnServer, uint16(r))
continue
} else {
l.Warnf("Failed to update UPnP port mapping for external port on device " + igd.FriendlyIdentifier() + ".")
}
l.Warnln("Failed to update UPnP port mapping - external port", externalPort)
}
}