github.com/NebulousLabs/Sia@v1.3.7/modules/gateway/upnp.go (about)

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