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 }