Ver Fonte

lib/connections: Refactor address listing into connection service

Audrius Butkevicius há 9 anos atrás
pai
commit
29913dd1e4

+ 0 - 127
cmd/syncthing/addresslister.go

@@ -1,127 +0,0 @@
-// Copyright (C) 2015 The Syncthing Authors.
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this file,
-// You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package main
-
-import (
-	"fmt"
-	"net"
-	"net/url"
-
-	"github.com/syncthing/syncthing/lib/config"
-)
-
-type addressLister struct {
-	upnpService *upnpService
-	cfg         *config.Wrapper
-}
-
-func newAddressLister(upnpService *upnpService, cfg *config.Wrapper) *addressLister {
-	return &addressLister{
-		upnpService: upnpService,
-		cfg:         cfg,
-	}
-}
-
-// ExternalAddresses returns a list of addresses that are our best guess for
-// where we are reachable from the outside. As a special case, we may return
-// one or more addresses with an empty IP address (0.0.0.0 or ::) and just
-// port number - this means that the outside address of a NAT gateway should
-// be substituted.
-func (e *addressLister) ExternalAddresses() []string {
-	return e.addresses(false)
-}
-
-// AllAddresses returns a list of addresses that are our best guess for where
-// we are reachable from the local network. Same conditions as
-// ExternalAddresses, but private IPv4 addresses are included.
-func (e *addressLister) AllAddresses() []string {
-	return e.addresses(true)
-}
-
-func (e *addressLister) addresses(includePrivateIPV4 bool) []string {
-	var addrs []string
-
-	// Grab our listen addresses from the config. Unspecified ones are passed
-	// on verbatim (to be interpreted by a global discovery server or local
-	// discovery peer). Public addresses are passed on verbatim. Private
-	// addresses are filtered.
-	for _, addrStr := range e.cfg.Options().ListenAddress {
-		addrURL, err := url.Parse(addrStr)
-		if err != nil {
-			l.Infoln("Listen address", addrStr, "is invalid:", err)
-			continue
-		}
-		addr, err := net.ResolveTCPAddr("tcp", addrURL.Host)
-		if err != nil {
-			l.Infoln("Listen address", addrStr, "is invalid:", err)
-			continue
-		}
-
-		if addr.IP == nil || addr.IP.IsUnspecified() {
-			// Address like 0.0.0.0:22000 or [::]:22000 or :22000; include as is.
-			addrs = append(addrs, tcpAddr(addr.String()))
-		} else if isPublicIPv4(addr.IP) || isPublicIPv6(addr.IP) {
-			// A public address; include as is.
-			addrs = append(addrs, tcpAddr(addr.String()))
-		} else if includePrivateIPV4 && addr.IP.To4().IsGlobalUnicast() {
-			// A private IPv4 address.
-			addrs = append(addrs, tcpAddr(addr.String()))
-		}
-	}
-
-	// Get an external port mapping from the upnpService, if it has one. If so,
-	// add it as another unspecified address.
-	if e.upnpService != nil {
-		if port := e.upnpService.ExternalPort(); port != 0 {
-			addrs = append(addrs, fmt.Sprintf("tcp://:%d", port))
-		}
-	}
-
-	return addrs
-}
-
-func isPublicIPv4(ip net.IP) bool {
-	ip = ip.To4()
-	if ip == nil {
-		// Not an IPv4 address (IPv6)
-		return false
-	}
-
-	// IsGlobalUnicast below only checks that it's not link local or
-	// multicast, and we want to exclude private (NAT:ed) addresses as well.
-	rfc1918 := []net.IPNet{
-		{IP: net.IP{10, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}},
-		{IP: net.IP{172, 16, 0, 0}, Mask: net.IPMask{255, 240, 0, 0}},
-		{IP: net.IP{192, 168, 0, 0}, Mask: net.IPMask{255, 255, 0, 0}},
-	}
-	for _, n := range rfc1918 {
-		if n.Contains(ip) {
-			return false
-		}
-	}
-
-	return ip.IsGlobalUnicast()
-}
-
-func isPublicIPv6(ip net.IP) bool {
-	if ip.To4() != nil {
-		// Not an IPv6 address (IPv4)
-		// (To16() returns a v6 mapped v4 address so can't be used to check
-		// that it's an actual v6 address)
-		return false
-	}
-
-	return ip.IsGlobalUnicast()
-}
-
-func tcpAddr(host string) string {
-	u := url.URL{
-		Scheme: "tcp",
-		Host:   host,
-	}
-	return u.String()
-}

+ 2 - 1
cmd/syncthing/gui.go

@@ -39,6 +39,7 @@ import (
 	"github.com/syncthing/syncthing/lib/sync"
 	"github.com/syncthing/syncthing/lib/tlsutil"
 	"github.com/syncthing/syncthing/lib/upgrade"
+	"github.com/syncthing/syncthing/lib/util"
 	"github.com/vitrun/qart/qr"
 	"golang.org/x/crypto/bcrypt"
 )
@@ -733,7 +734,7 @@ func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
 	if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
 		// UR was enabled
 		to.Options.URAccepted = usageReportVersion
-		to.Options.URUniqueID = randomString(8)
+		to.Options.URUniqueID = util.RandomString(8)
 	} else if to.Options.URAccepted < curAcc {
 		// UR was disabled
 		to.Options.URAccepted = -1

+ 2 - 1
cmd/syncthing/gui_auth.go

@@ -17,6 +17,7 @@ import (
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/sync"
+	"github.com/syncthing/syncthing/lib/util"
 	"golang.org/x/crypto/bcrypt"
 )
 
@@ -90,7 +91,7 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
 			return
 		}
 
-		sessionid := randomString(32)
+		sessionid := util.RandomString(32)
 		sessionsMut.Lock()
 		sessions[sessionid] = true
 		sessionsMut.Unlock()

+ 2 - 1
cmd/syncthing/gui_csrf.go

@@ -16,6 +16,7 @@ import (
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/osutil"
 	"github.com/syncthing/syncthing/lib/sync"
+	"github.com/syncthing/syncthing/lib/util"
 )
 
 // csrfTokens is a list of valid tokens. It is sorted so that the most
@@ -97,7 +98,7 @@ func validCsrfToken(token string) bool {
 }
 
 func newCsrfToken() string {
-	token := randomString(32)
+	token := util.RandomString(32)
 
 	csrfMut.Lock()
 	csrfTokens = append([]string{token}, csrfTokens...)

+ 14 - 22
cmd/syncthing/main.go

@@ -44,6 +44,8 @@ import (
 	"github.com/syncthing/syncthing/lib/symlinks"
 	"github.com/syncthing/syncthing/lib/tlsutil"
 	"github.com/syncthing/syncthing/lib/upgrade"
+	"github.com/syncthing/syncthing/lib/upnp"
+	"github.com/syncthing/syncthing/lib/util"
 
 	"github.com/thejerf/suture"
 )
@@ -558,7 +560,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 
 	// We reinitialize the predictable RNG with our device ID, to get a
 	// sequence that is always the same but unique to this syncthing instance.
-	predictableRandom.Seed(seedFromBytes(cert.Certificate[0]))
+	util.PredictableRandom.Seed(util.SeedFromBytes(cert.Certificate[0]))
 
 	myID = protocol.NewDeviceID(cert.Certificate[0])
 	l.SetPrefix(fmt.Sprintf("[%s] ", myID.String()[:5]))
@@ -720,21 +722,11 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 		l.Fatalln("Bad listen address:", err)
 	}
 
-	// The externalAddr tracks our external addresses for discovery purposes.
-
-	var addrList *addressLister
-
 	// Start UPnP
-
+	var upnpService *upnp.Service
 	if opts.UPnPEnabled {
-		upnpService := newUPnPService(cfg, addr.Port)
+		upnpService = upnp.NewUPnPService(cfg, addr.Port)
 		mainService.Add(upnpService)
-
-		// The external address tracker needs to know about the UPnP service
-		// so it can check for an external mapped port.
-		addrList = newAddressLister(upnpService, cfg)
-	} else {
-		addrList = newAddressLister(nil, cfg)
 	}
 
 	// Start relay management
@@ -750,10 +742,15 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 	cachedDiscovery := discover.NewCachingMux()
 	mainService.Add(cachedDiscovery)
 
+	// Start connection management
+
+	connectionService := connections.NewConnectionService(cfg, myID, m, tlsCfg, cachedDiscovery, upnpService, relayService, bepProtocolName, tlsDefaultCommonName, lans)
+	mainService.Add(connectionService)
+
 	if cfg.Options().GlobalAnnEnabled {
 		for _, srv := range cfg.GlobalDiscoveryServers() {
 			l.Infoln("Using discovery server", srv)
-			gd, err := discover.NewGlobal(srv, cert, addrList, relayService)
+			gd, err := discover.NewGlobal(srv, cert, connectionService, relayService)
 			if err != nil {
 				l.Warnln("Global discovery:", err)
 				continue
@@ -768,14 +765,14 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 
 	if cfg.Options().LocalAnnEnabled {
 		// v4 broadcasts
-		bcd, err := discover.NewLocal(myID, fmt.Sprintf(":%d", cfg.Options().LocalAnnPort), addrList, relayService)
+		bcd, err := discover.NewLocal(myID, fmt.Sprintf(":%d", cfg.Options().LocalAnnPort), connectionService, relayService)
 		if err != nil {
 			l.Warnln("IPv4 local discovery:", err)
 		} else {
 			cachedDiscovery.Add(bcd, 0, 0, ipv4LocalDiscoveryPriority)
 		}
 		// v6 multicasts
-		mcd, err := discover.NewLocal(myID, cfg.Options().LocalAnnMCAddr, addrList, relayService)
+		mcd, err := discover.NewLocal(myID, cfg.Options().LocalAnnMCAddr, connectionService, relayService)
 		if err != nil {
 			l.Warnln("IPv6 local discovery:", err)
 		} else {
@@ -787,11 +784,6 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 
 	setupGUI(mainService, cfg, m, apiSub, cachedDiscovery, relayService, errors, systemLog, runtimeOptions)
 
-	// Start connection management
-
-	connectionService := connections.NewConnectionService(cfg, myID, m, tlsCfg, cachedDiscovery, relayService, bepProtocolName, tlsDefaultCommonName, lans)
-	mainService.Add(connectionService)
-
 	if runtimeOptions.cpuProfile {
 		f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
 		if err != nil {
@@ -816,7 +808,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
 		if opts.URUniqueID == "" {
 			// Previously the ID was generated from the node ID. We now need
 			// to generate a new one.
-			opts.URUniqueID = randomString(8)
+			opts.URUniqueID = util.RandomString(8)
 			cfg.SetOptions(opts)
 			cfg.Save()
 		}

+ 120 - 17
lib/connections/connections.go

@@ -23,6 +23,7 @@ import (
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/relay"
 	"github.com/syncthing/syncthing/lib/relay/client"
+	"github.com/syncthing/syncthing/lib/upnp"
 
 	"github.com/thejerf/suture"
 )
@@ -42,9 +43,9 @@ type Model interface {
 	IsPaused(remoteID protocol.DeviceID) bool
 }
 
-// The connection connectionService listens on TLS and dials configured unconnected
-// devices. Successful connections are handed to the model.
-type connectionService struct {
+// Service listens on TLS and dials configured unconnected devices. Successful
+// connections are handed to the model.
+type Service struct {
 	*suture.Supervisor
 	cfg                  *config.Wrapper
 	myID                 protocol.DeviceID
@@ -52,6 +53,7 @@ type connectionService struct {
 	tlsCfg               *tls.Config
 	discoverer           discover.Finder
 	conns                chan model.IntermediateConnection
+	upnpService          *upnp.Service
 	relayService         relay.Service
 	bepProtocolName      string
 	tlsDefaultCommonName string
@@ -66,15 +68,16 @@ type connectionService struct {
 	relaysEnabled bool
 }
 
-func NewConnectionService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, relayService relay.Service,
-	bepProtocolName string, tlsDefaultCommonName string, lans []*net.IPNet) suture.Service {
-	service := &connectionService{
-		Supervisor:           suture.NewSimple("connectionService"),
+func NewConnectionService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, upnpService *upnp.Service,
+	relayService relay.Service, bepProtocolName string, tlsDefaultCommonName string, lans []*net.IPNet) *Service {
+	service := &Service{
+		Supervisor:           suture.NewSimple("connections.Service"),
 		cfg:                  cfg,
 		myID:                 myID,
 		model:                mdl,
 		tlsCfg:               tlsCfg,
 		discoverer:           discoverer,
+		upnpService:          upnpService,
 		relayService:         relayService,
 		conns:                make(chan model.IntermediateConnection),
 		bepProtocolName:      bepProtocolName,
@@ -100,7 +103,7 @@ func NewConnectionService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model
 	// to handle incoming connections, one routine to periodically attempt
 	// outgoing connections, one routine to the the common handling
 	// regardless of whether the connection was incoming or outgoing.
-	// Furthermore, a relay connectionService which handles incoming requests to connect
+	// Furthermore, a relay service which handles incoming requests to connect
 	// via the relays.
 	//
 	// TODO: Clean shutdown, and/or handling config changes on the fly. We
@@ -137,7 +140,7 @@ func NewConnectionService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model
 	return service
 }
 
-func (s *connectionService) handle() {
+func (s *Service) handle() {
 next:
 	for c := range s.conns {
 		cs := c.Conn.ConnectionState()
@@ -259,7 +262,7 @@ next:
 	}
 }
 
-func (s *connectionService) connect() {
+func (s *Service) connect() {
 	delay := time.Second
 	for {
 		l.Debugln("Reconnect loop")
@@ -342,7 +345,7 @@ func (s *connectionService) connect() {
 	}
 }
 
-func (s *connectionService) resolveAddresses(deviceID protocol.DeviceID, inAddrs []string) (addrs []string, relays []discover.Relay) {
+func (s *Service) resolveAddresses(deviceID protocol.DeviceID, inAddrs []string) (addrs []string, relays []discover.Relay) {
 	for _, addr := range inAddrs {
 		if addr == "dynamic" {
 			if s.discoverer != nil {
@@ -358,7 +361,7 @@ func (s *connectionService) resolveAddresses(deviceID protocol.DeviceID, inAddrs
 	return
 }
 
-func (s *connectionService) connectDirect(deviceID protocol.DeviceID, addr string) *tls.Conn {
+func (s *Service) connectDirect(deviceID protocol.DeviceID, addr string) *tls.Conn {
 	uri, err := url.Parse(addr)
 	if err != nil {
 		l.Infoln("Failed to parse connection url:", addr, err)
@@ -381,7 +384,7 @@ func (s *connectionService) connectDirect(deviceID protocol.DeviceID, addr strin
 	return conn
 }
 
-func (s *connectionService) connectViaRelay(deviceID protocol.DeviceID, addr discover.Relay) *tls.Conn {
+func (s *Service) connectViaRelay(deviceID protocol.DeviceID, addr discover.Relay) *tls.Conn {
 	uri, err := url.Parse(addr.URL)
 	if err != nil {
 		l.Infoln("Failed to parse relay connection url:", addr, err)
@@ -420,7 +423,7 @@ func (s *connectionService) connectViaRelay(deviceID protocol.DeviceID, addr dis
 	return tc
 }
 
-func (s *connectionService) acceptRelayConns() {
+func (s *Service) acceptRelayConns() {
 	for {
 		conn := s.relayService.Accept()
 		s.conns <- model.IntermediateConnection{
@@ -430,7 +433,7 @@ func (s *connectionService) acceptRelayConns() {
 	}
 }
 
-func (s *connectionService) shouldLimit(addr net.Addr) bool {
+func (s *Service) shouldLimit(addr net.Addr) bool {
 	if s.cfg.Options().LimitBandwidthInLan {
 		return true
 	}
@@ -447,11 +450,11 @@ func (s *connectionService) shouldLimit(addr net.Addr) bool {
 	return !tcpaddr.IP.IsLoopback()
 }
 
-func (s *connectionService) VerifyConfiguration(from, to config.Configuration) error {
+func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
 	return nil
 }
 
-func (s *connectionService) CommitConfiguration(from, to config.Configuration) bool {
+func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
 	s.mut.Lock()
 	s.relaysEnabled = to.Options.RelaysEnabled
 	s.mut.Unlock()
@@ -472,6 +475,106 @@ func (s *connectionService) CommitConfiguration(from, to config.Configuration) b
 	return true
 }
 
+// ExternalAddresses returns a list of addresses that are our best guess for
+// where we are reachable from the outside. As a special case, we may return
+// one or more addresses with an empty IP address (0.0.0.0 or ::) and just
+// port number - this means that the outside address of a NAT gateway should
+// be substituted.
+func (s *Service) ExternalAddresses() []string {
+	return s.addresses(false)
+}
+
+// AllAddresses returns a list of addresses that are our best guess for where
+// we are reachable from the local network. Same conditions as
+// ExternalAddresses, but private IPv4 addresses are included.
+func (s *Service) AllAddresses() []string {
+	return s.addresses(true)
+}
+
+func (s *Service) addresses(includePrivateIPV4 bool) []string {
+	var addrs []string
+
+	// Grab our listen addresses from the config. Unspecified ones are passed
+	// on verbatim (to be interpreted by a global discovery server or local
+	// discovery peer). Public addresses are passed on verbatim. Private
+	// addresses are filtered.
+	for _, addrStr := range s.cfg.Options().ListenAddress {
+		addrURL, err := url.Parse(addrStr)
+		if err != nil {
+			l.Infoln("Listen address", addrStr, "is invalid:", err)
+			continue
+		}
+		addr, err := net.ResolveTCPAddr("tcp", addrURL.Host)
+		if err != nil {
+			l.Infoln("Listen address", addrStr, "is invalid:", err)
+			continue
+		}
+
+		if addr.IP == nil || addr.IP.IsUnspecified() {
+			// Address like 0.0.0.0:22000 or [::]:22000 or :22000; include as is.
+			addrs = append(addrs, tcpAddr(addr.String()))
+		} else if isPublicIPv4(addr.IP) || isPublicIPv6(addr.IP) {
+			// A public address; include as is.
+			addrs = append(addrs, tcpAddr(addr.String()))
+		} else if includePrivateIPV4 && addr.IP.To4().IsGlobalUnicast() {
+			// A private IPv4 address.
+			addrs = append(addrs, tcpAddr(addr.String()))
+		}
+	}
+
+	// Get an external port mapping from the upnpService, if it has one. If so,
+	// add it as another unspecified address.
+	if s.upnpService != nil {
+		if port := s.upnpService.ExternalPort(); port != 0 {
+			addrs = append(addrs, fmt.Sprintf("tcp://:%d", port))
+		}
+	}
+
+	return addrs
+}
+
+func isPublicIPv4(ip net.IP) bool {
+	ip = ip.To4()
+	if ip == nil {
+		// Not an IPv4 address (IPv6)
+		return false
+	}
+
+	// IsGlobalUnicast below only checks that it's not link local or
+	// multicast, and we want to exclude private (NAT:ed) addresses as well.
+	rfc1918 := []net.IPNet{
+		{IP: net.IP{10, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}},
+		{IP: net.IP{172, 16, 0, 0}, Mask: net.IPMask{255, 240, 0, 0}},
+		{IP: net.IP{192, 168, 0, 0}, Mask: net.IPMask{255, 255, 0, 0}},
+	}
+	for _, n := range rfc1918 {
+		if n.Contains(ip) {
+			return false
+		}
+	}
+
+	return ip.IsGlobalUnicast()
+}
+
+func isPublicIPv6(ip net.IP) bool {
+	if ip.To4() != nil {
+		// Not an IPv6 address (IPv4)
+		// (To16() returns a v6 mapped v4 address so can't be used to check
+		// that it's an actual v6 address)
+		return false
+	}
+
+	return ip.IsGlobalUnicast()
+}
+
+func tcpAddr(host string) string {
+	u := url.URL{
+		Scheme: "tcp",
+		Host:   host,
+	}
+	return u.String()
+}
+
 // serviceFunc wraps a function to create a suture.Service without stop
 // functionality.
 type serviceFunc func()

+ 16 - 16
cmd/syncthing/upnpservice.go → lib/upnp/service.go

@@ -4,7 +4,7 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this file,
 // You can obtain one at http://mozilla.org/MPL/2.0/.
 
-package main
+package upnp
 
 import (
 	"fmt"
@@ -13,12 +13,12 @@ import (
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/events"
 	"github.com/syncthing/syncthing/lib/sync"
-	"github.com/syncthing/syncthing/lib/upnp"
+	"github.com/syncthing/syncthing/lib/util"
 )
 
-// The UPnP service runs a loop for discovery of IGDs (Internet Gateway
-// Devices) and setup/renewal of a port mapping.
-type upnpService struct {
+// Service runs a loop for discovery of IGDs (Internet Gateway Devices) and
+// setup/renewal of a port mapping.
+type Service struct {
 	cfg        *config.Wrapper
 	localPort  int
 	extPort    int
@@ -26,20 +26,20 @@ type upnpService struct {
 	stop       chan struct{}
 }
 
-func newUPnPService(cfg *config.Wrapper, localPort int) *upnpService {
-	return &upnpService{
+func NewUPnPService(cfg *config.Wrapper, localPort int) *Service {
+	return &Service{
 		cfg:        cfg,
 		localPort:  localPort,
 		extPortMut: sync.NewMutex(),
 	}
 }
 
-func (s *upnpService) Serve() {
+func (s *Service) Serve() {
 	foundIGD := true
 	s.stop = make(chan struct{})
 
 	for {
-		igds := upnp.Discover(time.Duration(s.cfg.Options().UPnPTimeoutS) * time.Second)
+		igds := Discover(time.Duration(s.cfg.Options().UPnPTimeoutS) * time.Second)
 		if len(igds) > 0 {
 			foundIGD = true
 			s.extPortMut.Lock()
@@ -72,18 +72,18 @@ func (s *upnpService) Serve() {
 	}
 }
 
-func (s *upnpService) Stop() {
+func (s *Service) Stop() {
 	close(s.stop)
 }
 
-func (s *upnpService) ExternalPort() int {
+func (s *Service) ExternalPort() int {
 	s.extPortMut.Lock()
 	port := s.extPort
 	s.extPortMut.Unlock()
 	return port
 }
 
-func (s *upnpService) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
+func (s *Service) tryIGDs(igds []IGD, prevExtPort int) int {
 	// Lets try all the IGDs we found and use the first one that works.
 	// TODO: Use all of them, and sort out the resulting mess to the
 	// discovery announcement code...
@@ -105,14 +105,14 @@ func (s *upnpService) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
 	return 0
 }
 
-func (s *upnpService) tryIGD(igd upnp.IGD, suggestedPort int) (int, error) {
+func (s *Service) tryIGD(igd IGD, suggestedPort int) (int, error) {
 	var err error
 	leaseTime := s.cfg.Options().UPnPLeaseM * 60
 
 	if suggestedPort != 0 {
 		// First try renewing our existing mapping.
 		name := fmt.Sprintf("syncthing-%d", suggestedPort)
-		err = igd.AddPortMapping(upnp.TCP, suggestedPort, s.localPort, name, leaseTime)
+		err = igd.AddPortMapping(TCP, suggestedPort, s.localPort, name, leaseTime)
 		if err == nil {
 			return suggestedPort, nil
 		}
@@ -120,9 +120,9 @@ func (s *upnpService) tryIGD(igd upnp.IGD, suggestedPort int) (int, error) {
 
 	for i := 0; i < 10; i++ {
 		// Then try up to ten random ports.
-		extPort := 1024 + predictableRandom.Intn(65535-1024)
+		extPort := 1024 + util.PredictableRandom.Intn(65535-1024)
 		name := fmt.Sprintf("syncthing-%d", extPort)
-		err = igd.AddPortMapping(upnp.TCP, extPort, s.localPort, name, leaseTime)
+		err = igd.AddPortMapping(TCP, extPort, s.localPort, name, leaseTime)
 		if err == nil {
 			return extPort, nil
 		}

+ 11 - 11
cmd/syncthing/random.go → lib/util/random.go

@@ -4,7 +4,7 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this file,
 // You can obtain one at http://mozilla.org/MPL/2.0/.
 
-package main
+package util
 
 import (
 	"crypto/md5"
@@ -17,19 +17,19 @@ import (
 // randomCharset contains the characters that can make up a randomString().
 const randomCharset = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
 
-// predictableRandom is an RNG that will always have the same sequence. It
+// PredictableRandom is an RNG that will always have the same sequence. It
 // will be seeded with the device ID during startup, so that the sequence is
 // predictable but varies between instances.
-var predictableRandom = mathRand.New(mathRand.NewSource(42))
+var PredictableRandom = mathRand.New(mathRand.NewSource(42))
 
 func init() {
 	// The default RNG should be seeded with something good.
-	mathRand.Seed(randomInt64())
+	mathRand.Seed(RandomInt64())
 }
 
-// randomString returns a string of random characters (taken from
+// RandomString returns a string of random characters (taken from
 // randomCharset) of the specified length.
-func randomString(l int) string {
+func RandomString(l int) string {
 	bs := make([]byte, l)
 	for i := range bs {
 		bs[i] = randomCharset[mathRand.Intn(len(randomCharset))]
@@ -37,19 +37,19 @@ func randomString(l int) string {
 	return string(bs)
 }
 
-// randomInt64 returns a strongly random int64, slowly
-func randomInt64() int64 {
+// RandomInt64 returns a strongly random int64, slowly
+func RandomInt64() int64 {
 	var bs [8]byte
 	_, err := io.ReadFull(cryptoRand.Reader, bs[:])
 	if err != nil {
 		panic("randomness failure: " + err.Error())
 	}
-	return seedFromBytes(bs[:])
+	return SeedFromBytes(bs[:])
 }
 
-// seedFromBytes calculates a weak 64 bit hash from the given byte slice,
+// SeedFromBytes calculates a weak 64 bit hash from the given byte slice,
 // suitable for use a predictable random seed.
-func seedFromBytes(bs []byte) int64 {
+func SeedFromBytes(bs []byte) int64 {
 	h := md5.New()
 	h.Write(bs)
 	s := h.Sum(nil)

+ 6 - 6
cmd/syncthing/random_test.go → lib/util/random_test.go

@@ -4,7 +4,7 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this file,
 // You can obtain one at http://mozilla.org/MPL/2.0/.
 
-package main
+package util
 
 import (
 	"runtime"
@@ -21,7 +21,7 @@ func TestPredictableRandom(t *testing.T) {
 	predictableRandomTest.Do(func() {
 		// predictable random sequence is predictable
 		e := int64(3440579354231278675)
-		if v := int64(predictableRandom.Int()); v != e {
+		if v := int64(PredictableRandom.Int()); v != e {
 			t.Errorf("Unexpected random value %d != %d", v, e)
 		}
 	})
@@ -38,7 +38,7 @@ func TestSeedFromBytes(t *testing.T) {
 	}
 
 	for _, tc := range tcs {
-		if v := seedFromBytes(tc.bs); v != tc.v {
+		if v := SeedFromBytes(tc.bs); v != tc.v {
 			t.Errorf("Unexpected seed value %d != %d", v, tc.v)
 		}
 	}
@@ -46,7 +46,7 @@ func TestSeedFromBytes(t *testing.T) {
 
 func TestRandomString(t *testing.T) {
 	for _, l := range []int{0, 1, 2, 3, 4, 8, 42} {
-		s := randomString(l)
+		s := RandomString(l)
 		if len(s) != l {
 			t.Errorf("Incorrect length %d != %d", len(s), l)
 		}
@@ -54,7 +54,7 @@ func TestRandomString(t *testing.T) {
 
 	strings := make([]string, 1000)
 	for i := range strings {
-		strings[i] = randomString(8)
+		strings[i] = RandomString(8)
 		for j := range strings {
 			if i == j {
 				continue
@@ -69,7 +69,7 @@ func TestRandomString(t *testing.T) {
 func TestRandomInt64(t *testing.T) {
 	ints := make([]int64, 1000)
 	for i := range ints {
-		ints[i] = randomInt64()
+		ints[i] = RandomInt64()
 		for j := range ints {
 			if i == j {
 				continue