Add external address tracker object

This commit is contained in:
Jakob Borg
2015-09-12 21:59:15 +02:00
parent 8779f93746
commit e694c664e5
10 changed files with 248 additions and 92 deletions

View File

@@ -18,6 +18,7 @@ import (
"github.com/syncthing/protocol"
"github.com/syncthing/relaysrv/client"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
@@ -37,11 +38,12 @@ var (
// devices. Successful connections are handed to the model.
type connectionSvc struct {
*suture.Supervisor
cfg *config.Wrapper
myID protocol.DeviceID
model *model.Model
tlsCfg *tls.Config
conns chan model.IntermediateConnection
cfg *config.Wrapper
myID protocol.DeviceID
model *model.Model
tlsCfg *tls.Config
discoverer *discover.Discoverer
conns chan model.IntermediateConnection
lastRelayCheck map[protocol.DeviceID]time.Time
@@ -50,13 +52,14 @@ type connectionSvc struct {
relaysEnabled bool
}
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Model, tlsCfg *tls.Config) *connectionSvc {
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Model, tlsCfg *tls.Config, discoverer *discover.Discoverer) *connectionSvc {
svc := &connectionSvc{
Supervisor: suture.NewSimple("connectionSvc"),
cfg: cfg,
myID: myID,
model: mdl,
tlsCfg: tlsCfg,
discoverer: discoverer,
conns: make(chan model.IntermediateConnection),
connType: make(map[protocol.DeviceID]model.ConnectionType),
@@ -257,8 +260,8 @@ func (s *connectionSvc) connect() {
var relays []string
for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" {
if discoverer != nil {
t, r := discoverer.Lookup(deviceID)
if s.discoverer != nil {
t, r := s.discoverer.Lookup(deviceID)
addrs = append(addrs, t...)
relays = append(relays, r...)
}

View File

@@ -0,0 +1,107 @@
// 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 externalAddr struct {
upnpSvc *upnpSvc
cfg *config.Wrapper
}
func newExternalAddr(upnpSvc *upnpSvc, cfg *config.Wrapper) *externalAddr {
return &externalAddr{
upnpSvc: upnpSvc,
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 *externalAddr) ExternalAddresses() []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, "tcp://"+addr.String())
} else if isPublicIPv4(addr.IP) || isPublicIPv6(addr.IP) {
// A public address; include as is.
addrs = append(addrs, "tcp://"+addr.String())
}
}
// Get an external port mapping from the upnpSvc, if it has one. If so,
// add it as another unspecified address.
if e.upnpSvc != nil {
if port := e.upnpSvc.ExternalPort(); port != 0 {
addrs = append(addrs, fmt.Sprintf("tcp://:%d", port))
}
}
l.Infoln("External addresses:", addrs)
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()
}

View File

@@ -57,21 +57,23 @@ type apiSvc struct {
cfg config.GUIConfiguration
assetDir string
model *model.Model
eventSub *events.BufferedSubscription
discoverer *discover.Discoverer
listener net.Listener
fss *folderSummarySvc
stop chan struct{}
systemConfigMut sync.Mutex
eventSub *events.BufferedSubscription
}
func newAPISvc(id protocol.DeviceID, cfg config.GUIConfiguration, assetDir string, m *model.Model, eventSub *events.BufferedSubscription) (*apiSvc, error) {
func newAPISvc(id protocol.DeviceID, cfg config.GUIConfiguration, assetDir string, m *model.Model, eventSub *events.BufferedSubscription, discoverer *discover.Discoverer) (*apiSvc, error) {
svc := &apiSvc{
id: id,
cfg: cfg,
assetDir: assetDir,
model: m,
systemConfigMut: sync.NewMutex(),
eventSub: eventSub,
discoverer: discoverer,
systemConfigMut: sync.NewMutex(),
}
var err error
@@ -628,8 +630,8 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
res["alloc"] = m.Alloc
res["sys"] = m.Sys - m.HeapReleased
res["tilde"] = tilde
if cfg.Options().GlobalAnnEnabled && discoverer != nil {
res["extAnnounceOK"] = discoverer.ExtAnnounceOK()
if cfg.Options().GlobalAnnEnabled && s.discoverer != nil {
res["extAnnounceOK"] = s.discoverer.ExtAnnounceOK()
}
if relaySvc != nil {
res["relayClientStatus"] = relaySvc.ClientStatus()
@@ -681,8 +683,8 @@ func (s *apiSvc) postSystemDiscovery(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var device = qs.Get("device")
var addr = qs.Get("addr")
if len(device) != 0 && len(addr) != 0 && discoverer != nil {
discoverer.Hint(device, []string{addr})
if len(device) != 0 && len(addr) != 0 && s.discoverer != nil {
s.discoverer.Hint(device, []string{addr})
}
}
@@ -690,11 +692,11 @@ func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
devices := map[string][]discover.CacheEntry{}
if discoverer != nil {
if s.discoverer != nil {
// Device ids can't be marshalled as keys so we need to manually
// rebuild this map using strings. Discoverer may be nil if discovery
// has not started yet.
for device, entries := range discoverer.All() {
for device, entries := range s.discoverer.All() {
devices[device.String()] = entries
}
}

View File

@@ -114,7 +114,6 @@ var (
writeRateLimit *ratelimit.Bucket
readRateLimit *ratelimit.Bucket
stop = make(chan int)
discoverer *discover.Discoverer
relaySvc *relay.Svc
cert tls.Certificate
lans []*net.IPNet
@@ -674,10 +673,6 @@ func syncthingMain() {
mainSvc.Add(m)
// GUI
setupGUI(mainSvc, cfg, m, apiSub)
// The default port we announce, possibly modified by setupUPnP next.
uri, err := url.Parse(opts.ListenAddress[0])
@@ -690,27 +685,40 @@ func syncthingMain() {
l.Fatalln("Bad listen address:", err)
}
// Start the relevant services
// The externalAddr tracks our external addresses for discovery purposes.
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg)
mainSvc.Add(connectionSvc)
if opts.RelaysEnabled && (opts.GlobalAnnEnabled || opts.RelayWithoutGlobalAnn) {
relaySvc = relay.NewSvc(cfg, tlsCfg, connectionSvc.conns)
connectionSvc.Add(relaySvc)
}
// Start discovery
localPort := addr.Port
discoverer = discovery(localPort, relaySvc)
var extAddr *externalAddr
// Start UPnP. The UPnP service will restart global discovery if the
// external port changes.
if opts.UPnPEnabled {
upnpSvc := newUPnPSvc(cfg, localPort)
upnpSvc := newUPnPSvc(cfg, addr.Port)
mainSvc.Add(upnpSvc)
// The external address tracker needs to know about the UPnP service
// so it can check for an external mapped port.
extAddr = newExternalAddr(upnpSvc, cfg)
} else {
extAddr = newExternalAddr(nil, cfg)
}
// Start discovery
discoverer := discovery(extAddr, relaySvc)
// GUI
setupGUI(mainSvc, cfg, m, apiSub, discoverer)
// Start connection management
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg, discoverer)
mainSvc.Add(connectionSvc)
if opts.RelaysEnabled && (opts.GlobalAnnEnabled || opts.RelayWithoutGlobalAnn) {
relaySvc = relay.NewSvc(cfg, tlsCfg, connectionSvc.conns)
connectionSvc.Add(relaySvc)
}
if cpuProfile {
@@ -833,7 +841,7 @@ func startAuditing(mainSvc *suture.Supervisor) {
l.Infoln("Audit log in", auditFile)
}
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription) {
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.Discoverer) {
opts := cfg.Options()
guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey)
@@ -862,7 +870,7 @@ func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, a
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
l.Infoln("Starting web GUI on", urlShow)
api, err := newAPISvc(myID, guiCfg, guiAssets, m, apiSub)
api, err := newAPISvc(myID, guiCfg, guiAssets, m, apiSub, discoverer)
if err != nil {
l.Fatalln("Cannot start GUI:", err)
}
@@ -933,7 +941,7 @@ func shutdown() {
stop <- exitSuccess
}
func discovery(extPort int, relaySvc *relay.Svc) *discover.Discoverer {
func discovery(extAddr *externalAddr, relaySvc *relay.Svc) *discover.Discoverer {
opts := cfg.Options()
disc := discover.NewDiscoverer(myID, opts.ListenAddress, relaySvc)
if opts.LocalAnnEnabled {
@@ -947,7 +955,7 @@ func discovery(extPort int, relaySvc *relay.Svc) *discover.Discoverer {
// to relay servers.
time.Sleep(5 * time.Second)
l.Infoln("Starting global discovery announcements")
disc.StartGlobal(opts.GlobalAnnServers, uint16(extPort))
disc.StartGlobal(opts.GlobalAnnServers, extAddr)
}()
}

View File

@@ -11,26 +11,30 @@ import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/upnp"
)
// The UPnP service runs a loop for discovery of IGDs (Internet Gateway
// Devices) and setup/renewal of a port mapping.
type upnpSvc struct {
cfg *config.Wrapper
localPort int
stop chan struct{}
cfg *config.Wrapper
localPort int
extPort int
extPortMut sync.Mutex
stop chan struct{}
}
func newUPnPSvc(cfg *config.Wrapper, localPort int) *upnpSvc {
return &upnpSvc{
cfg: cfg,
localPort: localPort,
cfg: cfg,
localPort: localPort,
extPortMut: sync.NewMutex(),
}
}
func (s *upnpSvc) Serve() {
extPort := 0
foundIGD := true
s.stop = make(chan struct{})
@@ -38,7 +42,15 @@ func (s *upnpSvc) Serve() {
igds := upnp.Discover(time.Duration(s.cfg.Options().UPnPTimeoutS) * time.Second)
if len(igds) > 0 {
foundIGD = true
extPort = s.tryIGDs(igds, extPort)
s.extPortMut.Lock()
oldExtPort := s.extPort
s.extPortMut.Unlock()
newExtPort := s.tryIGDs(igds, oldExtPort)
s.extPortMut.Lock()
s.extPort = newExtPort
s.extPortMut.Unlock()
} else if foundIGD {
// Only print a notice if we've previously found an IGD or this is
// the first time around.
@@ -64,6 +76,13 @@ func (s *upnpSvc) Stop() {
close(s.stop)
}
func (s *upnpSvc) ExternalPort() int {
s.extPortMut.Lock()
port := s.extPort
s.extPortMut.Unlock()
return port
}
func (s *upnpSvc) tryIGDs(igds []upnp.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
@@ -76,13 +95,8 @@ func (s *upnpSvc) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
}
if extPort != prevExtPort {
// External port changed; refresh the discovery announcement.
// TODO: Don't reach out to some magic global here?
l.Infof("New UPnP port mapping: external port %d to local port %d.", extPort, s.localPort)
if s.cfg.Options().GlobalAnnEnabled {
discoverer.StopGlobal()
discoverer.StartGlobal(s.cfg.Options().GlobalAnnServers, uint16(extPort))
}
events.Default.Log(events.ExternalPortMappingChanged, map[string]int{"port": extPort})
}
if debugNet {
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())