github.com/kayoticsully/syncthing@v0.8.9-0.20140724133906-c45a2fdc03f8/cmd/syncthing/main.go (about)

     1  // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
     2  // All rights reserved. Use of this source code is governed by an MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"crypto/sha1"
     9  	"crypto/tls"
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"log"
    14  	"math/rand"
    15  	"net"
    16  	"net/http"
    17  	_ "net/http/pprof"
    18  	"os"
    19  	"os/exec"
    20  	"path/filepath"
    21  	"regexp"
    22  	"runtime"
    23  	"runtime/debug"
    24  	"runtime/pprof"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/calmh/syncthing/config"
    30  	"github.com/calmh/syncthing/discover"
    31  	"github.com/calmh/syncthing/events"
    32  	"github.com/calmh/syncthing/logger"
    33  	"github.com/calmh/syncthing/model"
    34  	"github.com/calmh/syncthing/osutil"
    35  	"github.com/calmh/syncthing/protocol"
    36  	"github.com/calmh/syncthing/upnp"
    37  	"github.com/juju/ratelimit"
    38  	"github.com/syndtr/goleveldb/leveldb"
    39  )
    40  
    41  var (
    42  	Version     = "unknown-dev"
    43  	BuildEnv    = "default"
    44  	BuildStamp  = "0"
    45  	BuildDate   time.Time
    46  	BuildHost   = "unknown"
    47  	BuildUser   = "unknown"
    48  	LongVersion string
    49  )
    50  
    51  var l = logger.DefaultLogger
    52  
    53  func init() {
    54  	if Version != "unknown-dev" {
    55  		// If not a generic dev build, version string should come from git describe
    56  		exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-beta\d+)?(-\d+-g[0-9a-f]+)?(-dirty)?$`)
    57  		if !exp.MatchString(Version) {
    58  			l.Fatalf("Invalid version string %q;\n\tdoes not match regexp %v", Version, exp)
    59  		}
    60  	}
    61  
    62  	stamp, _ := strconv.Atoi(BuildStamp)
    63  	BuildDate = time.Unix(int64(stamp), 0)
    64  
    65  	date := BuildDate.UTC().Format("2006-01-02 15:04:05 MST")
    66  	LongVersion = fmt.Sprintf("syncthing %s (%s %s-%s %s) %s@%s %s", Version, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildEnv, BuildUser, BuildHost, date)
    67  
    68  	if os.Getenv("STTRACE") != "" {
    69  		logFlags = log.Ltime | log.Ldate | log.Lmicroseconds | log.Lshortfile
    70  	}
    71  }
    72  
    73  var (
    74  	cfg        config.Configuration
    75  	myID       protocol.NodeID
    76  	confDir    string
    77  	logFlags   int = log.Ltime
    78  	rateBucket *ratelimit.Bucket
    79  	stop       = make(chan bool)
    80  	discoverer *discover.Discoverer
    81  )
    82  
    83  const (
    84  	usage      = "syncthing [options]"
    85  	extraUsage = `The value for the -logflags option is a sum of the following:
    86  
    87     1  Date
    88     2  Time
    89     4  Microsecond time
    90     8  Long filename
    91    16  Short filename
    92  
    93  I.e. to prefix each log line with date and time, set -logflags=3 (1 + 2 from
    94  above). The value 0 is used to disable all of the above. The default is to
    95  show time only (2).
    96  
    97  The following enviroment variables are interpreted by syncthing:
    98  
    99   STNORESTART   Do not attempt to restart when requested to, instead just exit.
   100                 Set this variable when running under a service manager such as
   101                 runit, launchd, etc.
   102  
   103   STPROFILER    Set to a listen address such as "127.0.0.1:9090" to start the
   104                 profiler with HTTP access.
   105  
   106   STTRACE       A comma separated string of facilities to trace. The valid
   107                 facility strings:
   108                 - "beacon"   (the beacon package)
   109                 - "discover" (the discover package)
   110                 - "files"    (the files package)
   111                 - "net"      (the main package; connections & network messages)
   112                 - "model"    (the model package)
   113                 - "scanner"  (the scanner package)
   114                 - "upnp"     (the upnp package)
   115                 - "xdr"      (the xdr package)
   116                 - "all"      (all of the above)
   117  
   118   STCPUPROFILE  Write CPU profile to the specified file.
   119  
   120   STGUIASSETS   Directory to load GUI assets from. Overrides compiled in assets.
   121  
   122   STDEADLOCKTIMEOUT  Alter deadlock detection timeout (seconds; default 1200).`
   123  )
   124  
   125  func init() {
   126  	rand.Seed(time.Now().UnixNano())
   127  }
   128  
   129  func main() {
   130  	var reset bool
   131  	var showVersion bool
   132  	var doUpgrade bool
   133  	flag.StringVar(&confDir, "home", getDefaultConfDir(), "Set configuration directory")
   134  	flag.BoolVar(&reset, "reset", false, "Prepare to resync from cluster")
   135  	flag.BoolVar(&showVersion, "version", false, "Show version")
   136  	flag.BoolVar(&doUpgrade, "upgrade", false, "Perform upgrade")
   137  	flag.IntVar(&logFlags, "logflags", logFlags, "Set log flags")
   138  	flag.Usage = usageFor(flag.CommandLine, usage, extraUsage)
   139  	flag.Parse()
   140  
   141  	if showVersion {
   142  		fmt.Println(LongVersion)
   143  		return
   144  	}
   145  
   146  	l.SetFlags(logFlags)
   147  
   148  	if doUpgrade {
   149  		err := upgrade()
   150  		if err != nil {
   151  			l.Fatalln(err)
   152  		}
   153  		return
   154  	}
   155  
   156  	if len(os.Getenv("GOGC")) == 0 {
   157  		debug.SetGCPercent(25)
   158  	}
   159  
   160  	if len(os.Getenv("GOMAXPROCS")) == 0 {
   161  		runtime.GOMAXPROCS(runtime.NumCPU())
   162  	}
   163  
   164  	confDir = expandTilde(confDir)
   165  
   166  	events.Default.Log(events.Starting, map[string]string{"home": confDir})
   167  
   168  	if _, err := os.Stat(confDir); err != nil && confDir == getDefaultConfDir() {
   169  		// We are supposed to use the default configuration directory. It
   170  		// doesn't exist. In the past our default has been ~/.syncthing, so if
   171  		// that directory exists we move it to the new default location and
   172  		// continue. We don't much care if this fails at this point, we will
   173  		// be checking that later.
   174  
   175  		var oldDefault string
   176  		if runtime.GOOS == "windows" {
   177  			oldDefault = filepath.Join(os.Getenv("AppData"), "Syncthing")
   178  		} else {
   179  			oldDefault = expandTilde("~/.syncthing")
   180  		}
   181  		if _, err := os.Stat(oldDefault); err == nil {
   182  			os.MkdirAll(filepath.Dir(confDir), 0700)
   183  			if err := os.Rename(oldDefault, confDir); err == nil {
   184  				l.Infoln("Moved config dir", oldDefault, "to", confDir)
   185  			}
   186  		}
   187  	}
   188  
   189  	// Ensure that our home directory exists and that we have a certificate and key.
   190  
   191  	ensureDir(confDir, 0700)
   192  	cert, err := loadCert(confDir, "")
   193  	if err != nil {
   194  		newCertificate(confDir, "")
   195  		cert, err = loadCert(confDir, "")
   196  		l.FatalErr(err)
   197  	}
   198  
   199  	myID = protocol.NewNodeID(cert.Certificate[0])
   200  	l.SetPrefix(fmt.Sprintf("[%s] ", myID.String()[:5]))
   201  
   202  	l.Infoln(LongVersion)
   203  	l.Infoln("My ID:", myID)
   204  
   205  	// Prepare to be able to save configuration
   206  
   207  	cfgFile := filepath.Join(confDir, "config.xml")
   208  	go saveConfigLoop(cfgFile)
   209  
   210  	// Load the configuration file, if it exists.
   211  	// If it does not, create a template.
   212  
   213  	cf, err := os.Open(cfgFile)
   214  	if err == nil {
   215  		// Read config.xml
   216  		cfg, err = config.Load(cf, myID)
   217  		if err != nil {
   218  			l.Fatalln(err)
   219  		}
   220  		cf.Close()
   221  	} else {
   222  		l.Infoln("No config file; starting with empty defaults")
   223  		name, _ := os.Hostname()
   224  		defaultRepo := filepath.Join(getHomeDir(), "Sync")
   225  		ensureDir(defaultRepo, 0755)
   226  
   227  		cfg, err = config.Load(nil, myID)
   228  		cfg.Repositories = []config.RepositoryConfiguration{
   229  			{
   230  				ID:        "default",
   231  				Directory: defaultRepo,
   232  				Nodes:     []config.NodeConfiguration{{NodeID: myID}},
   233  			},
   234  		}
   235  		cfg.Nodes = []config.NodeConfiguration{
   236  			{
   237  				NodeID:    myID,
   238  				Addresses: []string{"dynamic"},
   239  				Name:      name,
   240  			},
   241  		}
   242  
   243  		port, err := getFreePort("127.0.0.1", 8080)
   244  		l.FatalErr(err)
   245  		cfg.GUI.Address = fmt.Sprintf("127.0.0.1:%d", port)
   246  
   247  		port, err = getFreePort("0.0.0.0", 22000)
   248  		l.FatalErr(err)
   249  		cfg.Options.ListenAddress = []string{fmt.Sprintf("0.0.0.0:%d", port)}
   250  
   251  		saveConfig()
   252  		l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
   253  	}
   254  
   255  	if reset {
   256  		resetRepositories()
   257  		return
   258  	}
   259  
   260  	if profiler := os.Getenv("STPROFILER"); len(profiler) > 0 {
   261  		go func() {
   262  			l.Debugln("Starting profiler on", profiler)
   263  			runtime.SetBlockProfileRate(1)
   264  			err := http.ListenAndServe(profiler, nil)
   265  			if err != nil {
   266  				l.Fatalln(err)
   267  			}
   268  		}()
   269  	}
   270  
   271  	if len(os.Getenv("STRESTART")) > 0 {
   272  		waitForParentExit()
   273  	}
   274  
   275  	// The TLS configuration is used for both the listening socket and outgoing
   276  	// connections.
   277  
   278  	tlsCfg := &tls.Config{
   279  		Certificates:           []tls.Certificate{cert},
   280  		NextProtos:             []string{"bep/1.0"},
   281  		ServerName:             myID.String(),
   282  		ClientAuth:             tls.RequestClientCert,
   283  		SessionTicketsDisabled: true,
   284  		InsecureSkipVerify:     true,
   285  		MinVersion:             tls.VersionTLS12,
   286  	}
   287  
   288  	// If the write rate should be limited, set up a rate limiter for it.
   289  	// This will be used on connections created in the connect and listen routines.
   290  
   291  	if cfg.Options.MaxSendKbps > 0 {
   292  		rateBucket = ratelimit.NewBucketWithRate(float64(1000*cfg.Options.MaxSendKbps), int64(5*1000*cfg.Options.MaxSendKbps))
   293  	}
   294  
   295  	removeLegacyIndexes()
   296  	db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), nil)
   297  	if err != nil {
   298  		l.Fatalln("leveldb.OpenFile():", err)
   299  	}
   300  	m := model.NewModel(confDir, &cfg, "syncthing", Version, db)
   301  
   302  nextRepo:
   303  	for i, repo := range cfg.Repositories {
   304  		if repo.Invalid != "" {
   305  			continue
   306  		}
   307  
   308  		repo.Directory = expandTilde(repo.Directory)
   309  
   310  		// Safety check. If the cached index contains files but the repository
   311  		// doesn't exist, we have a problem. We would assume that all files
   312  		// have been deleted which might not be the case, so abort instead.
   313  
   314  		id := fmt.Sprintf("%x", sha1.Sum([]byte(repo.Directory)))
   315  		idxFile := filepath.Join(confDir, id+".idx.gz")
   316  		if _, err := os.Stat(idxFile); err == nil {
   317  			if fi, err := os.Stat(repo.Directory); err != nil || !fi.IsDir() {
   318  				cfg.Repositories[i].Invalid = "repo directory missing"
   319  				continue nextRepo
   320  			}
   321  		}
   322  
   323  		ensureDir(repo.Directory, -1)
   324  		m.AddRepo(repo)
   325  	}
   326  
   327  	// GUI
   328  	if cfg.GUI.Enabled && cfg.GUI.Address != "" {
   329  		addr, err := net.ResolveTCPAddr("tcp", cfg.GUI.Address)
   330  		if err != nil {
   331  			l.Fatalf("Cannot start GUI on %q: %v", cfg.GUI.Address, err)
   332  		} else {
   333  			var hostOpen, hostShow string
   334  			switch {
   335  			case addr.IP == nil:
   336  				hostOpen = "localhost"
   337  				hostShow = "0.0.0.0"
   338  			case addr.IP.IsUnspecified():
   339  				hostOpen = "localhost"
   340  				hostShow = addr.IP.String()
   341  			default:
   342  				hostOpen = addr.IP.String()
   343  				hostShow = hostOpen
   344  			}
   345  
   346  			var proto = "http"
   347  			if cfg.GUI.UseTLS {
   348  				proto = "https"
   349  			}
   350  
   351  			l.Infof("Starting web GUI on %s://%s:%d/", proto, hostShow, addr.Port)
   352  			err := startGUI(cfg.GUI, os.Getenv("STGUIASSETS"), m)
   353  			if err != nil {
   354  				l.Fatalln("Cannot start GUI:", err)
   355  			}
   356  			if cfg.Options.StartBrowser && len(os.Getenv("STRESTART")) == 0 {
   357  				openURL(fmt.Sprintf("%s://%s:%d", proto, hostOpen, addr.Port))
   358  			}
   359  		}
   360  	}
   361  
   362  	// Walk the repository and update the local model before establishing any
   363  	// connections to other nodes.
   364  
   365  	m.CleanRepos()
   366  	l.Infoln("Performing initial repository scan")
   367  	m.ScanRepos()
   368  
   369  	// Remove all .idx* files that don't belong to an active repo.
   370  
   371  	validIndexes := make(map[string]bool)
   372  	for _, repo := range cfg.Repositories {
   373  		dir := expandTilde(repo.Directory)
   374  		id := fmt.Sprintf("%x", sha1.Sum([]byte(dir)))
   375  		validIndexes[id] = true
   376  	}
   377  
   378  	allIndexes, err := filepath.Glob(filepath.Join(confDir, "*.idx*"))
   379  	if err == nil {
   380  		for _, idx := range allIndexes {
   381  			bn := filepath.Base(idx)
   382  			fs := strings.Split(bn, ".")
   383  			if len(fs) > 1 {
   384  				if _, ok := validIndexes[fs[0]]; !ok {
   385  					l.Infoln("Removing old index", bn)
   386  					os.Remove(idx)
   387  				}
   388  			}
   389  		}
   390  	}
   391  
   392  	// UPnP
   393  
   394  	var externalPort = 0
   395  	if cfg.Options.UPnPEnabled {
   396  		// We seed the random number generator with the node ID to get a
   397  		// repeatable sequence of random external ports.
   398  		externalPort = setupUPnP(rand.NewSource(certSeed(cert.Certificate[0])))
   399  	}
   400  
   401  	// Routine to connect out to configured nodes
   402  	discoverer = discovery(externalPort)
   403  	go listenConnect(myID, m, tlsCfg)
   404  
   405  	for _, repo := range cfg.Repositories {
   406  		if repo.Invalid != "" {
   407  			continue
   408  		}
   409  
   410  		// Routine to pull blocks from other nodes to synchronize the local
   411  		// repository. Does not run when we are in read only (publish only) mode.
   412  		if repo.ReadOnly {
   413  			l.Okf("Ready to synchronize %s (read only; no external updates accepted)", repo.ID)
   414  			m.StartRepoRO(repo.ID)
   415  		} else {
   416  			l.Okf("Ready to synchronize %s (read-write)", repo.ID)
   417  			m.StartRepoRW(repo.ID, cfg.Options.ParallelRequests)
   418  		}
   419  	}
   420  
   421  	if cpuprof := os.Getenv("STCPUPROFILE"); len(cpuprof) > 0 {
   422  		f, err := os.Create(cpuprof)
   423  		if err != nil {
   424  			log.Fatal(err)
   425  		}
   426  		pprof.StartCPUProfile(f)
   427  		defer pprof.StopCPUProfile()
   428  	}
   429  
   430  	for _, node := range cfg.Nodes {
   431  		if len(node.Name) > 0 {
   432  			l.Infof("Node %s is %q at %v", node.NodeID, node.Name, node.Addresses)
   433  		}
   434  	}
   435  
   436  	if cfg.Options.URAccepted > 0 && cfg.Options.URAccepted < usageReportVersion {
   437  		l.Infoln("Anonymous usage report has changed; revoking acceptance")
   438  		cfg.Options.URAccepted = 0
   439  	}
   440  	if cfg.Options.URAccepted >= usageReportVersion {
   441  		go usageReportingLoop(m)
   442  		go func() {
   443  			time.Sleep(10 * time.Minute)
   444  			err := sendUsageReport(m)
   445  			if err != nil {
   446  				l.Infoln("Usage report:", err)
   447  			}
   448  		}()
   449  	}
   450  
   451  	events.Default.Log(events.StartupComplete, nil)
   452  	go generateEvents()
   453  
   454  	<-stop
   455  
   456  	l.Okln("Exiting")
   457  }
   458  
   459  func generateEvents() {
   460  	for {
   461  		time.Sleep(300 * time.Second)
   462  		events.Default.Log(events.Ping, nil)
   463  	}
   464  }
   465  
   466  func waitForParentExit() {
   467  	l.Infoln("Waiting for parent to exit...")
   468  	// Wait for the listen address to become free, indicating that the parent has exited.
   469  	for {
   470  		ln, err := net.Listen("tcp", cfg.Options.ListenAddress[0])
   471  		if err == nil {
   472  			ln.Close()
   473  			break
   474  		}
   475  		time.Sleep(250 * time.Millisecond)
   476  	}
   477  	l.Okln("Continuing")
   478  }
   479  
   480  func setupUPnP(r rand.Source) int {
   481  	var externalPort = 0
   482  	if len(cfg.Options.ListenAddress) == 1 {
   483  		_, portStr, err := net.SplitHostPort(cfg.Options.ListenAddress[0])
   484  		if err != nil {
   485  			l.Warnln(err)
   486  		} else {
   487  			// Set up incoming port forwarding, if necessary and possible
   488  			port, _ := strconv.Atoi(portStr)
   489  			igd, err := upnp.Discover()
   490  			if err == nil {
   491  				for i := 0; i < 10; i++ {
   492  					r := 1024 + int(r.Int63()%(65535-1024))
   493  					err := igd.AddPortMapping(upnp.TCP, r, port, "syncthing", 0)
   494  					if err == nil {
   495  						externalPort = r
   496  						l.Infoln("Created UPnP port mapping - external port", externalPort)
   497  						break
   498  					}
   499  				}
   500  				if externalPort == 0 {
   501  					l.Warnln("Failed to create UPnP port mapping")
   502  				}
   503  			} else {
   504  				l.Infof("No UPnP gateway detected")
   505  				if debugNet {
   506  					l.Debugf("UPnP: %v", err)
   507  				}
   508  			}
   509  		}
   510  	} else {
   511  		l.Warnln("Multiple listening addresses; not attempting UPnP port mapping")
   512  	}
   513  	return externalPort
   514  }
   515  
   516  func resetRepositories() {
   517  	suffix := fmt.Sprintf(".syncthing-reset-%d", time.Now().UnixNano())
   518  	for _, repo := range cfg.Repositories {
   519  		if _, err := os.Stat(repo.Directory); err == nil {
   520  			l.Infof("Reset: Moving %s -> %s", repo.Directory, repo.Directory+suffix)
   521  			os.Rename(repo.Directory, repo.Directory+suffix)
   522  		}
   523  	}
   524  
   525  	idx := filepath.Join(confDir, "index")
   526  	os.RemoveAll(idx)
   527  }
   528  
   529  func removeLegacyIndexes() {
   530  	pat := filepath.Join(confDir, "*.idx.gz*")
   531  	idxs, err := filepath.Glob(pat)
   532  	if err == nil {
   533  		for _, idx := range idxs {
   534  			l.Infof("Reset: Removing %s", idx)
   535  			os.Remove(idx)
   536  		}
   537  	}
   538  }
   539  
   540  func restart() {
   541  	l.Infoln("Restarting")
   542  	if os.Getenv("SMF_FMRI") != "" || os.Getenv("STNORESTART") != "" {
   543  		// Solaris SMF
   544  		l.Infoln("Service manager detected; exit instead of restart")
   545  		stop <- true
   546  		return
   547  	}
   548  
   549  	env := os.Environ()
   550  	if len(os.Getenv("STRESTART")) == 0 {
   551  		env = append(env, "STRESTART=1")
   552  	}
   553  	pgm, err := exec.LookPath(os.Args[0])
   554  	if err != nil {
   555  		l.Warnln(err)
   556  		return
   557  	}
   558  	proc, err := os.StartProcess(pgm, os.Args, &os.ProcAttr{
   559  		Env:   env,
   560  		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
   561  	})
   562  	if err != nil {
   563  		l.Fatalln(err)
   564  	}
   565  	proc.Release()
   566  	stop <- true
   567  }
   568  
   569  func shutdown() {
   570  	stop <- true
   571  }
   572  
   573  var saveConfigCh = make(chan struct{})
   574  
   575  func saveConfigLoop(cfgFile string) {
   576  	for _ = range saveConfigCh {
   577  		fd, err := os.Create(cfgFile + ".tmp")
   578  		if err != nil {
   579  			l.Warnln(err)
   580  			continue
   581  		}
   582  
   583  		err = config.Save(fd, cfg)
   584  		if err != nil {
   585  			l.Warnln(err)
   586  			fd.Close()
   587  			continue
   588  		}
   589  
   590  		err = fd.Close()
   591  		if err != nil {
   592  			l.Warnln(err)
   593  			continue
   594  		}
   595  
   596  		err = osutil.Rename(cfgFile+".tmp", cfgFile)
   597  		if err != nil {
   598  			l.Warnln(err)
   599  		}
   600  	}
   601  }
   602  
   603  func saveConfig() {
   604  	saveConfigCh <- struct{}{}
   605  }
   606  
   607  func listenConnect(myID protocol.NodeID, m *model.Model, tlsCfg *tls.Config) {
   608  	var conns = make(chan *tls.Conn)
   609  
   610  	// Listen
   611  	for _, addr := range cfg.Options.ListenAddress {
   612  		go listenTLS(conns, addr, tlsCfg)
   613  	}
   614  
   615  	// Connect
   616  	go dialTLS(m, conns, tlsCfg)
   617  
   618  next:
   619  	for conn := range conns {
   620  		certs := conn.ConnectionState().PeerCertificates
   621  		if cl := len(certs); cl != 1 {
   622  			l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, conn.RemoteAddr())
   623  			conn.Close()
   624  			continue
   625  		}
   626  		remoteID := protocol.NewNodeID(certs[0].Raw)
   627  
   628  		if remoteID == myID {
   629  			l.Infof("Connected to myself (%s) - should not happen", remoteID)
   630  			conn.Close()
   631  			continue
   632  		}
   633  
   634  		if m.ConnectedTo(remoteID) {
   635  			l.Infof("Connected to already connected node (%s)", remoteID)
   636  			conn.Close()
   637  			continue
   638  		}
   639  
   640  		for _, nodeCfg := range cfg.Nodes {
   641  			if nodeCfg.NodeID == remoteID {
   642  				var wr io.Writer = conn
   643  				if rateBucket != nil {
   644  					wr = &limitedWriter{conn, rateBucket}
   645  				}
   646  				name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr())
   647  				protoConn := protocol.NewConnection(remoteID, conn, wr, m, name)
   648  
   649  				l.Infof("Established secure connection to %s at %s", remoteID, name)
   650  				if debugNet {
   651  					l.Debugf("cipher suite %04X", conn.ConnectionState().CipherSuite)
   652  				}
   653  				events.Default.Log(events.NodeConnected, map[string]string{
   654  					"id":   remoteID.String(),
   655  					"addr": conn.RemoteAddr().String(),
   656  				})
   657  
   658  				m.AddConnection(conn, protoConn)
   659  				continue next
   660  			}
   661  		}
   662  
   663  		l.Infof("Connection from %s with unknown node ID %s; ignoring", conn.RemoteAddr(), remoteID)
   664  		conn.Close()
   665  	}
   666  }
   667  
   668  func listenTLS(conns chan *tls.Conn, addr string, tlsCfg *tls.Config) {
   669  	if debugNet {
   670  		l.Debugln("listening on", addr)
   671  	}
   672  
   673  	tcaddr, err := net.ResolveTCPAddr("tcp", addr)
   674  	l.FatalErr(err)
   675  	listener, err := net.ListenTCP("tcp", tcaddr)
   676  	l.FatalErr(err)
   677  
   678  	for {
   679  		conn, err := listener.Accept()
   680  		if err != nil {
   681  			l.Warnln(err)
   682  			continue
   683  		}
   684  
   685  		if debugNet {
   686  			l.Debugln("connect from", conn.RemoteAddr())
   687  		}
   688  
   689  		tcpConn := conn.(*net.TCPConn)
   690  		setTCPOptions(tcpConn)
   691  
   692  		tc := tls.Server(conn, tlsCfg)
   693  		err = tc.Handshake()
   694  		if err != nil {
   695  			l.Warnln(err)
   696  			tc.Close()
   697  			continue
   698  		}
   699  
   700  		conns <- tc
   701  	}
   702  
   703  }
   704  
   705  func dialTLS(m *model.Model, conns chan *tls.Conn, tlsCfg *tls.Config) {
   706  	var delay time.Duration = 1 * time.Second
   707  	for {
   708  	nextNode:
   709  		for _, nodeCfg := range cfg.Nodes {
   710  			if nodeCfg.NodeID == myID {
   711  				continue
   712  			}
   713  
   714  			if m.ConnectedTo(nodeCfg.NodeID) {
   715  				continue
   716  			}
   717  
   718  			var addrs []string
   719  			for _, addr := range nodeCfg.Addresses {
   720  				if addr == "dynamic" {
   721  					if discoverer != nil {
   722  						t := discoverer.Lookup(nodeCfg.NodeID)
   723  						if len(t) == 0 {
   724  							continue
   725  						}
   726  						addrs = append(addrs, t...)
   727  					}
   728  				} else {
   729  					addrs = append(addrs, addr)
   730  				}
   731  			}
   732  
   733  			for _, addr := range addrs {
   734  				host, port, err := net.SplitHostPort(addr)
   735  				if err != nil && strings.HasPrefix(err.Error(), "missing port") {
   736  					// addr is on the form "1.2.3.4"
   737  					addr = net.JoinHostPort(addr, "22000")
   738  				} else if err == nil && port == "" {
   739  					// addr is on the form "1.2.3.4:"
   740  					addr = net.JoinHostPort(host, "22000")
   741  				}
   742  				if debugNet {
   743  					l.Debugln("dial", nodeCfg.NodeID, addr)
   744  				}
   745  
   746  				raddr, err := net.ResolveTCPAddr("tcp", addr)
   747  				if err != nil {
   748  					if debugNet {
   749  						l.Debugln(err)
   750  					}
   751  					continue
   752  				}
   753  
   754  				conn, err := net.DialTCP("tcp", nil, raddr)
   755  				if err != nil {
   756  					if debugNet {
   757  						l.Debugln(err)
   758  					}
   759  					continue
   760  				}
   761  
   762  				setTCPOptions(conn)
   763  
   764  				tc := tls.Client(conn, tlsCfg)
   765  				err = tc.Handshake()
   766  				if err != nil {
   767  					l.Warnln(err)
   768  					tc.Close()
   769  					continue
   770  				}
   771  
   772  				conns <- tc
   773  				continue nextNode
   774  			}
   775  		}
   776  
   777  		time.Sleep(delay)
   778  		delay *= 2
   779  		if maxD := time.Duration(cfg.Options.ReconnectIntervalS) * time.Second; delay > maxD {
   780  			delay = maxD
   781  		}
   782  	}
   783  }
   784  
   785  func setTCPOptions(conn *net.TCPConn) {
   786  	var err error
   787  	if err = conn.SetLinger(0); err != nil {
   788  		l.Infoln(err)
   789  	}
   790  	if err = conn.SetNoDelay(false); err != nil {
   791  		l.Infoln(err)
   792  	}
   793  	if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
   794  		l.Infoln(err)
   795  	}
   796  	if err = conn.SetKeepAlive(true); err != nil {
   797  		l.Infoln(err)
   798  	}
   799  }
   800  
   801  func discovery(extPort int) *discover.Discoverer {
   802  	disc, err := discover.NewDiscoverer(myID, cfg.Options.ListenAddress, cfg.Options.LocalAnnPort)
   803  	if err != nil {
   804  		l.Warnf("No discovery possible (%v)", err)
   805  		return nil
   806  	}
   807  
   808  	if cfg.Options.LocalAnnEnabled {
   809  		l.Infoln("Sending local discovery announcements")
   810  		disc.StartLocal()
   811  	}
   812  
   813  	if cfg.Options.GlobalAnnEnabled {
   814  		l.Infoln("Sending global discovery announcements")
   815  		disc.StartGlobal(cfg.Options.GlobalAnnServer, uint16(extPort))
   816  	}
   817  
   818  	return disc
   819  }
   820  
   821  func ensureDir(dir string, mode int) {
   822  	fi, err := os.Stat(dir)
   823  	if os.IsNotExist(err) {
   824  		err := os.MkdirAll(dir, 0700)
   825  		l.FatalErr(err)
   826  	} else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
   827  		err := os.Chmod(dir, os.FileMode(mode))
   828  		l.FatalErr(err)
   829  	}
   830  }
   831  
   832  func getDefaultConfDir() string {
   833  	switch runtime.GOOS {
   834  	case "windows":
   835  		return filepath.Join(os.Getenv("LocalAppData"), "Syncthing")
   836  
   837  	case "darwin":
   838  		return expandTilde("~/Library/Application Support/Syncthing")
   839  
   840  	default:
   841  		if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
   842  			return filepath.Join(xdgCfg, "syncthing")
   843  		} else {
   844  			return expandTilde("~/.config/syncthing")
   845  		}
   846  	}
   847  }
   848  
   849  func expandTilde(p string) string {
   850  	if p == "~" {
   851  		return getHomeDir()
   852  	}
   853  
   854  	p = filepath.FromSlash(p)
   855  	if !strings.HasPrefix(p, fmt.Sprintf("~%c", os.PathSeparator)) {
   856  		return p
   857  	}
   858  
   859  	return filepath.Join(getHomeDir(), p[2:])
   860  }
   861  
   862  func getHomeDir() string {
   863  	var home string
   864  
   865  	switch runtime.GOOS {
   866  	case "windows":
   867  		home = filepath.Join(os.Getenv("HomeDrive"), os.Getenv("HomePath"))
   868  		if home == "" {
   869  			home = os.Getenv("UserProfile")
   870  		}
   871  	default:
   872  		home = os.Getenv("HOME")
   873  	}
   874  
   875  	if home == "" {
   876  		l.Fatalln("No home directory found - set $HOME (or the platform equivalent).")
   877  	}
   878  
   879  	return home
   880  }
   881  
   882  // getFreePort returns a free TCP port fort listening on. The ports given are
   883  // tried in succession and the first to succeed is returned. If none succeed,
   884  // a random high port is returned.
   885  func getFreePort(host string, ports ...int) (int, error) {
   886  	for _, port := range ports {
   887  		c, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
   888  		if err == nil {
   889  			c.Close()
   890  			return port, nil
   891  		}
   892  	}
   893  
   894  	c, err := net.Listen("tcp", host+":0")
   895  	if err != nil {
   896  		return 0, err
   897  	}
   898  	addr := c.Addr().String()
   899  	c.Close()
   900  
   901  	_, portstr, err := net.SplitHostPort(addr)
   902  	if err != nil {
   903  		return 0, err
   904  	}
   905  
   906  	port, err := strconv.Atoi(portstr)
   907  	if err != nil {
   908  		return 0, err
   909  	}
   910  
   911  	return port, nil
   912  }