From be38c2111f07daa2e3e8f29d821f16cbbc487365 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Tue, 23 Aug 2016 08:43:27 +0200 Subject: [PATCH] cmd/strelaysrv: Add uPNP support, ability to set listen protocol (fixes #3503, fixes #3505, fixes #3506) --- cmd/strelaysrv/listener.go | 14 +++++--- cmd/strelaysrv/main.go | 74 ++++++++++++++++++++++++++++++++++---- cmd/strelaysrv/pool.go | 9 +++-- 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/cmd/strelaysrv/listener.go b/cmd/strelaysrv/listener.go index 322bd09b6..db7ef1921 100644 --- a/cmd/strelaysrv/listener.go +++ b/cmd/strelaysrv/listener.go @@ -23,7 +23,7 @@ var ( numConnections int64 ) -func listener(addr string, config *tls.Config) { +func listener(proto, addr string, config *tls.Config) { tcpListener, err := net.Listen("tcp", addr) if err != nil { log.Fatalln(err) @@ -167,10 +167,16 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) { continue } - peerOutbox <- serverInvitation + select { + case peerOutbox <- serverInvitation: + if debug { + log.Println("Sent invitation from", id, "to", requestedPeer) + } + default: + if debug { + log.Println("Could not send invitation from", id, "to", requestedPeer, "as peer disconnected") + } - if debug { - log.Println("Sent invitation from", id, "to", requestedPeer) } conn.Close() diff --git a/cmd/strelaysrv/main.go b/cmd/strelaysrv/main.go index 392712570..d1aea7221 100644 --- a/cmd/strelaysrv/main.go +++ b/cmd/strelaysrv/main.go @@ -24,6 +24,11 @@ import ( "github.com/syncthing/syncthing/lib/relay/protocol" "github.com/syncthing/syncthing/lib/tlsutil" + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/nat" + _ "github.com/syncthing/syncthing/lib/pmp" + _ "github.com/syncthing/syncthing/lib/upnp" + syncthingprotocol "github.com/syncthing/syncthing/lib/protocol" ) @@ -48,6 +53,7 @@ func init() { var ( listen string debug bool + proto string sessionAddress []byte sessionPort uint16 @@ -70,12 +76,17 @@ var ( pools []string providedBy string defaultPoolAddrs = "https://relays.syncthing.net/endpoint" + + natEnabled bool + natLease int + natRenewal int + natTimeout int ) func main() { log.SetFlags(log.Lshortfile | log.LstdFlags) - var dir, extAddress string + var dir, extAddress, proto string flag.StringVar(&listen, "listen", ":22067", "Protocol listen address") flag.StringVar(&dir, "keys", ".", "Directory where cert.pem and key.pem is stored") @@ -89,14 +100,22 @@ func main() { flag.StringVar(&poolAddrs, "pools", defaultPoolAddrs, "Comma separated list of relay pool addresses to join") flag.StringVar(&providedBy, "provided-by", "", "An optional description about who provides the relay") flag.StringVar(&extAddress, "ext-address", "", "An optional address to advertise as being available on.\n\tAllows listening on an unprivileged port with port forwarding from e.g. 443, and be connected to on port 443.") - + flag.StringVar(&proto, "protocol", "tcp", "Protocol used for listening. 'tcp' for IPv4 and IPv6, 'tcp4' for IPv4, 'tcp6' for IPv6") + flag.BoolVar(&natEnabled, "nat", false, "Use UPnP/NAT-PMP to acquire external port mapping") + flag.IntVar(&natLease, "nat-lease", 60, "NAT lease length in minutes") + flag.IntVar(&natRenewal, "nat-renewal", 30, "NAT renewal frequency in minutes") + flag.IntVar(&natTimeout, "nat-timeout", 10, "NAT discovery timeout in seconds") flag.Parse() if extAddress == "" { extAddress = listen } - addr, err := net.ResolveTCPAddr("tcp", extAddress) + if len(providedBy) > 30 { + log.Fatal("Provided-by cannot be longer than 30 characters") + } + + addr, err := net.ResolveTCPAddr(proto, extAddress) if err != nil { log.Fatal(err) } @@ -149,6 +168,37 @@ func main() { log.Println("ID:", id) } + wrapper := config.Wrap("config", config.New(id)) + wrapper.SetOptions(config.OptionsConfiguration{ + NATLeaseM: natLease, + NATRenewalM: natRenewal, + NATTimeoutS: natTimeout, + }) + natSvc := nat.NewService(id, wrapper) + mapping := mapping{natSvc.NewMapping(nat.TCP, addr.IP, addr.Port)} + + if natEnabled { + go natSvc.Serve() + found := make(chan struct{}) + mapping.OnChanged(func(_ *nat.Mapping, _, _ []nat.Address) { + select { + case found <- struct{}{}: + default: + } + }) + + // Need to wait a few extra seconds, since NAT library waits exactly natTimeout seconds on all interfaces. + timeout := time.Duration(natTimeout+2) * time.Second + log.Printf("Waiting %s to acquire NAT mapping", timeout) + + select { + case <-found: + log.Printf("Found NAT mapping: %s", mapping.ExternalAddresses()) + case <-time.After(timeout): + log.Println("Timeout out waiting for NAT mapping.") + } + } + if sessionLimitBps > 0 { sessionLimiter = ratelimit.NewBucketWithRate(float64(sessionLimitBps), int64(2*sessionLimitBps)) } @@ -160,7 +210,7 @@ func main() { go statusService(statusAddr) } - uri, err := url.Parse(fmt.Sprintf("relay://%s/?id=%s&pingInterval=%s&networkTimeout=%s&sessionLimitBps=%d&globalLimitBps=%d&statusAddr=%s&providedBy=%s", extAddress, id, pingInterval, networkTimeout, sessionLimitBps, globalLimitBps, statusAddr, providedBy)) + uri, err := url.Parse(fmt.Sprintf("relay://%s/?id=%s&pingInterval=%s&networkTimeout=%s&sessionLimitBps=%d&globalLimitBps=%d&statusAddr=%s&providedBy=%s", mapping.Address(), id, pingInterval, networkTimeout, sessionLimitBps, globalLimitBps, statusAddr, providedBy)) if err != nil { log.Fatalln("Failed to construct URI", err) } @@ -178,11 +228,11 @@ func main() { for _, pool := range pools { pool = strings.TrimSpace(pool) if len(pool) > 0 { - go poolHandler(pool, uri) + go poolHandler(pool, uri, mapping) } } - go listener(listen, tlsCfg) + go listener(proto, listen, tlsCfg) sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) @@ -222,3 +272,15 @@ func monitorLimits() { limitCheckTimer.Reset(time.Minute) } } + +type mapping struct { + *nat.Mapping +} + +func (m *mapping) Address() nat.Address { + ext := m.ExternalAddresses() + if len(ext) > 0 { + return ext[0] + } + return m.Mapping.Address() +} diff --git a/cmd/strelaysrv/pool.go b/cmd/strelaysrv/pool.go index 5a00bd24f..4a92ef347 100644 --- a/cmd/strelaysrv/pool.go +++ b/cmd/strelaysrv/pool.go @@ -12,16 +12,19 @@ import ( "time" ) -func poolHandler(pool string, uri *url.URL) { +func poolHandler(pool string, uri *url.URL, mapping mapping) { if debug { log.Println("Joining", pool) } for { + uriCopy := *uri + uriCopy.Host = mapping.Address().String() + var b bytes.Buffer json.NewEncoder(&b).Encode(struct { URL string `json:"url"` }{ - uri.String(), + uriCopy.String(), }) resp, err := http.Post(pool, "application/json", &b) @@ -39,7 +42,7 @@ func poolHandler(pool string, uri *url.URL) { log.Println(pool, "under load, will retry in a minute") time.Sleep(time.Minute) continue - } else if resp.StatusCode == 403 { + } else if resp.StatusCode == 401 { log.Println(pool, "failed to join due to IP address not matching external address. Aborting") return } else if resp.StatusCode == 200 {