github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/gateway/upnp.go (about)

     1  package gateway
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/ioutil"
     7  	"net"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/NebulousLabs/go-upnp"
    14  
    15  	"github.com/Synthesix/Sia/build"
    16  	"github.com/Synthesix/Sia/modules"
    17  )
    18  
    19  // myExternalIP discovers the gateway's external IP by querying a centralized
    20  // service, http://myexternalip.com.
    21  func myExternalIP() (string, error) {
    22  	// timeout after 10 seconds
    23  	client := http.Client{Timeout: time.Duration(10 * time.Second)}
    24  	resp, err := client.Get("http://myexternalip.com/raw")
    25  	if err != nil {
    26  		return "", err
    27  	}
    28  	defer resp.Body.Close()
    29  	if resp.StatusCode != http.StatusOK {
    30  		errResp, _ := ioutil.ReadAll(resp.Body)
    31  		return "", errors.New(string(errResp))
    32  	}
    33  	buf, err := ioutil.ReadAll(io.LimitReader(resp.Body, 64))
    34  	if err != nil {
    35  		return "", err
    36  	}
    37  	if len(buf) == 0 {
    38  		return "", errors.New("myexternalip.com returned a 0 length IP address")
    39  	}
    40  	// trim newline
    41  	return strings.TrimSpace(string(buf)), nil
    42  }
    43  
    44  // threadedLearnHostname discovers the external IP of the Gateway regularly.
    45  func (g *Gateway) threadedLearnHostname() {
    46  	if err := g.threads.Add(); err != nil {
    47  		return
    48  	}
    49  	defer g.threads.Done()
    50  
    51  	if build.Release == "testing" {
    52  		return
    53  	}
    54  
    55  	for {
    56  		// try UPnP first, then fallback to myexternalip.com and peer-to-peer
    57  		// discovery.
    58  		var host string
    59  		d, err := upnp.Discover()
    60  		if err == nil {
    61  			host, err = d.ExternalIP()
    62  		}
    63  		if !build.DEBUG && err != nil {
    64  			host, err = myExternalIP()
    65  		}
    66  		if err != nil {
    67  			host, err = g.managedIPFromPeers()
    68  		}
    69  		if err != nil {
    70  			g.log.Println("WARN: failed to discover external IP:", err)
    71  		}
    72  		// If we were unable to discover our IP we try again later.
    73  		if err != nil {
    74  			if !g.managedSleep(rediscoverIPIntervalFailure) {
    75  				return // shutdown interrupted sleep
    76  			}
    77  			continue
    78  		}
    79  
    80  		g.mu.RLock()
    81  		addr := modules.NetAddress(net.JoinHostPort(host, g.port))
    82  		g.mu.RUnlock()
    83  		if err := addr.IsValid(); err != nil {
    84  			g.log.Printf("WARN: discovered hostname %q is invalid: %v", addr, err)
    85  			if err != nil {
    86  				if !g.managedSleep(rediscoverIPIntervalFailure) {
    87  					return // shutdown interrupted sleep
    88  				}
    89  				continue
    90  			}
    91  		}
    92  
    93  		g.mu.Lock()
    94  		g.myAddr = addr
    95  		g.mu.Unlock()
    96  
    97  		g.log.Println("INFO: our address is", addr)
    98  
    99  		// Rediscover the IP later in case it changed.
   100  		if !g.managedSleep(rediscoverIPIntervalSuccess) {
   101  			return // shutdown interrupted sleep
   102  		}
   103  	}
   104  }
   105  
   106  // threadedForwardPort adds a port mapping to the router.
   107  func (g *Gateway) threadedForwardPort(port string) {
   108  	if err := g.threads.Add(); err != nil {
   109  		return
   110  	}
   111  	defer g.threads.Done()
   112  
   113  	if build.Release == "testing" {
   114  		return
   115  	}
   116  
   117  	d, err := upnp.Discover()
   118  	if err != nil {
   119  		g.log.Printf("WARN: could not automatically forward port %s: no UPnP-enabled devices found: %v", port, err)
   120  		return
   121  	}
   122  
   123  	portInt, _ := strconv.Atoi(port)
   124  	err = d.Forward(uint16(portInt), "Sia RPC")
   125  	if err != nil {
   126  		g.log.Printf("WARN: could not automatically forward port %s: %v", port, err)
   127  		return
   128  	}
   129  
   130  	g.log.Println("INFO: successfully forwarded port", port)
   131  
   132  	// Establish port-clearing at shutdown.
   133  	g.threads.AfterStop(func() {
   134  		g.managedClearPort(port)
   135  	})
   136  }
   137  
   138  // managedClearPort removes a port mapping from the router.
   139  func (g *Gateway) managedClearPort(port string) {
   140  	if build.Release == "testing" {
   141  		return
   142  	}
   143  
   144  	d, err := upnp.Discover()
   145  	if err != nil {
   146  		return
   147  	}
   148  
   149  	portInt, _ := strconv.Atoi(port)
   150  	err = d.Clear(uint16(portInt))
   151  	if err != nil {
   152  		g.log.Printf("WARN: could not automatically unforward port %s: %v", port, err)
   153  		return
   154  	}
   155  
   156  	g.log.Println("INFO: successfully unforwarded port", port)
   157  }