func dbOpts(cfg *config.Wrapper) *opt.Options { // Calculate a suitable database block cache capacity. // Default is 8 MiB. blockCacheCapacity := 8 << 20 // Increase block cache up to this maximum: const maxCapacity = 64 << 20 // ... which we reach when the box has this much RAM: const maxAtRAM = 8 << 30 if v := cfg.Options().DatabaseBlockCacheMiB; v != 0 { // Use the value from the config, if it's set. blockCacheCapacity = v << 20 } else if bytes, err := memorySize(); err == nil { // We start at the default of 8 MiB and use larger values for machines // with more memory. if bytes > maxAtRAM { // Cap the cache at maxCapacity when we reach maxAtRam amount of memory blockCacheCapacity = maxCapacity } else if bytes > maxAtRAM/maxCapacity*int64(blockCacheCapacity) { // Grow from the default to maxCapacity at maxAtRam amount of memory blockCacheCapacity = int(bytes * maxCapacity / maxAtRAM) } l.Infoln("Database block cache capacity", blockCacheCapacity/1024, "KiB") } return &opt.Options{ OpenFilesCacheCapacity: 100, BlockCacheCapacity: blockCacheCapacity, WriteBuffer: 4 << 20, } }
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Model, tlsCfg *tls.Config, discoverer discover.Finder, relaySvc *relay.Svc) *connectionSvc { svc := &connectionSvc{ Supervisor: suture.NewSimple("connectionSvc"), cfg: cfg, myID: myID, model: mdl, tlsCfg: tlsCfg, discoverer: discoverer, relaySvc: relaySvc, conns: make(chan model.IntermediateConnection), connType: make(map[protocol.DeviceID]model.ConnectionType), relaysEnabled: cfg.Options().RelaysEnabled, lastRelayCheck: make(map[protocol.DeviceID]time.Time), } cfg.Subscribe(svc) // There are several moving parts here; one routine per listening address // 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 service which handles incoming requests to connect // via the relays. // // TODO: Clean shutdown, and/or handling config changes on the fly. We // partly do this now - new devices and addresses will be picked up, but // not new listen addresses and we don't support disconnecting devices // that are removed and so on... svc.Add(serviceFunc(svc.connect)) for _, addr := range svc.cfg.Options().ListenAddress { uri, err := url.Parse(addr) if err != nil { l.Infoln("Failed to parse listen address:", addr, err) continue } listener, ok := listeners[uri.Scheme] if !ok { l.Infoln("Unknown listen address scheme:", uri.String()) continue } if debugNet { l.Debugln("listening on", uri.String()) } svc.Add(serviceFunc(func() { listener(uri, svc.tlsCfg, svc.conns) })) } svc.Add(serviceFunc(svc.handle)) if svc.relaySvc != nil { svc.Add(serviceFunc(svc.acceptRelayConns)) } return svc }
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) { guiCfg := cfg.GUI() if !guiCfg.Enabled { return } if guiCfg.Address == "" { return } addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address) if err != nil { l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err) } else { var hostOpen, hostShow string switch { case addr.IP == nil: hostOpen = "localhost" hostShow = "0.0.0.0" case addr.IP.IsUnspecified(): hostOpen = "localhost" hostShow = addr.IP.String() default: hostOpen = addr.IP.String() hostShow = hostOpen } var proto = "http" if guiCfg.UseTLS { proto = "https" } urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port))) l.Infoln("Starting web GUI on", urlShow) api, err := newAPISvc(myID, cfg, guiAssets, m, apiSub, discoverer, relaySvc, errors, systemLog) if err != nil { l.Fatalln("Cannot start GUI:", err) } cfg.Subscribe(api) mainSvc.Add(api) if cfg.Options().StartBrowser && !noBrowser && !stRestarting { urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port))) // Can potentially block if the utility we are invoking doesn't // fork, and just execs, hence keep it in it's own routine. go openURL(urlOpen) } } }
func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) { guiCfg := cfg.GUI() if !guiCfg.Enabled { return } api, err := newAPISvc(myID, cfg, guiAssets, m, apiSub, discoverer, relaySvc, errors, systemLog) if err != nil { l.Fatalln("Cannot start GUI:", err) } cfg.Subscribe(api) mainSvc.Add(api) if cfg.Options().StartBrowser && !noBrowser && !stRestarting { // Can potentially block if the utility we are invoking doesn't // fork, and just execs, hence keep it in it's own routine. go openURL(guiCfg.URL()) } }
func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService *connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) { guiCfg := cfg.GUI() if !guiCfg.Enabled { return } if guiCfg.InsecureAdminAccess { l.Warnln("Insecure admin access is enabled.") } api := newAPIService(myID, cfg, locations[locHTTPSCertFile], locations[locHTTPSKeyFile], runtimeOptions.assetDir, m, apiSub, discoverer, connectionsService, errors, systemLog) cfg.Subscribe(api) mainService.Add(api) if cfg.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting { // Can potentially block if the utility we are invoking doesn't // fork, and just execs, hence keep it in it's own routine. go openURL(guiCfg.URL()) } }
func autoUpgrade(cfg *config.Wrapper) { timer := time.NewTimer(0) sub := events.Default.Subscribe(events.DeviceConnected) for { select { case event := <-sub.C(): data, ok := event.Data.(map[string]string) if !ok || data["clientName"] != "syncthing" || upgrade.CompareVersions(data["clientVersion"], Version) != upgrade.Newer { continue } l.Infof("Connected to device %s with a newer version (current %q < remote %q). Checking for upgrades.", data["id"], Version, data["clientVersion"]) case <-timer.C: } rel, err := upgrade.LatestRelease(cfg.Options().ReleasesURL, Version) if err == upgrade.ErrUpgradeUnsupported { events.Default.Unsubscribe(sub) return } if err != nil { // Don't complain too loudly here; we might simply not have // internet connectivity, or the upgrade server might be down. l.Infoln("Automatic upgrade:", err) timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour) continue } if upgrade.CompareVersions(rel.Tag, Version) != upgrade.Newer { // Skip equal, older or majorly newer (incompatible) versions timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour) continue } l.Infof("Automatic upgrade (current %q < latest %q)", Version, rel.Tag) err = upgrade.To(rel) if err != nil { l.Warnln("Automatic upgrade:", err) timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour) continue } events.Default.Unsubscribe(sub) l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag) time.Sleep(time.Minute) stop <- exitUpgrading return } }
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, tlsDefaultCommonName: tlsDefaultCommonName, lans: lans, connType: make(map[protocol.DeviceID]model.ConnectionType), relaysEnabled: cfg.Options().RelaysEnabled, lastRelayCheck: make(map[protocol.DeviceID]time.Time), } cfg.Subscribe(service) // The rate variables are in KiB/s in the UI (despite the camel casing // of the name). We multiply by 1024 here to get B/s. if service.cfg.Options().MaxSendKbps > 0 { service.writeRateLimit = ratelimit.NewBucketWithRate(float64(1024*service.cfg.Options().MaxSendKbps), int64(5*1024*service.cfg.Options().MaxSendKbps)) } if service.cfg.Options().MaxRecvKbps > 0 { service.readRateLimit = ratelimit.NewBucketWithRate(float64(1024*service.cfg.Options().MaxRecvKbps), int64(5*1024*service.cfg.Options().MaxRecvKbps)) } // There are several moving parts here; one routine per listening address // 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 service which handles incoming requests to connect // via the relays. // // TODO: Clean shutdown, and/or handling config changes on the fly. We // partly do this now - new devices and addresses will be picked up, but // not new listen addresses and we don't support disconnecting devices // that are removed and so on... service.Add(serviceFunc(service.connect)) for _, addr := range service.cfg.Options().ListenAddress { uri, err := url.Parse(addr) if err != nil { l.Infoln("Failed to parse listen address:", addr, err) continue } listener, ok := listeners[uri.Scheme] if !ok { l.Infoln("Unknown listen address scheme:", uri.String()) continue } l.Debugln("listening on", uri) service.Add(serviceFunc(func() { listener(uri, service.tlsCfg, service.conns) })) } service.Add(serviceFunc(service.handle)) if service.relayService != nil { service.Add(serviceFunc(service.acceptRelayConns)) } return service }
// reportData returns the data to be sent in a usage report. It's used in // various places, so not part of the usageReportingManager object. func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} { res := make(map[string]interface{}) res["urVersion"] = usageReportVersion res["uniqueID"] = cfg.Options().URUniqueID res["version"] = Version res["longVersion"] = LongVersion res["platform"] = runtime.GOOS + "-" + runtime.GOARCH res["numFolders"] = len(cfg.Folders()) res["numDevices"] = len(cfg.Devices()) var totFiles, maxFiles int var totBytes, maxBytes int64 for folderID := range cfg.Folders() { files, _, bytes := m.GlobalSize(folderID) totFiles += files totBytes += bytes if files > maxFiles { maxFiles = files } if bytes > maxBytes { maxBytes = bytes } } res["totFiles"] = totFiles res["folderMaxFiles"] = maxFiles res["totMiB"] = totBytes / 1024 / 1024 res["folderMaxMiB"] = maxBytes / 1024 / 1024 var mem runtime.MemStats runtime.ReadMemStats(&mem) res["memoryUsageMiB"] = (mem.Sys - mem.HeapReleased) / 1024 / 1024 res["sha256Perf"] = cpuBench(5, 125*time.Millisecond) bytes, err := memorySize() if err == nil { res["memorySize"] = bytes / 1024 / 1024 } res["numCPU"] = runtime.NumCPU() var rescanIntvs []int folderUses := map[string]int{ "readonly": 0, "ignorePerms": 0, "ignoreDelete": 0, "autoNormalize": 0, "simpleVersioning": 0, "externalVersioning": 0, "staggeredVersioning": 0, "trashcanVersioning": 0, } for _, cfg := range cfg.Folders() { rescanIntvs = append(rescanIntvs, cfg.RescanIntervalS) if cfg.ReadOnly { folderUses["readonly"]++ } if cfg.IgnorePerms { folderUses["ignorePerms"]++ } if cfg.IgnoreDelete { folderUses["ignoreDelete"]++ } if cfg.AutoNormalize { folderUses["autoNormalize"]++ } if cfg.Versioning.Type != "" { folderUses[cfg.Versioning.Type+"Versioning"]++ } } sort.Ints(rescanIntvs) res["rescanIntvs"] = rescanIntvs res["folderUses"] = folderUses deviceUses := map[string]int{ "introducer": 0, "customCertName": 0, "compressAlways": 0, "compressMetadata": 0, "compressNever": 0, "dynamicAddr": 0, "staticAddr": 0, } for _, cfg := range cfg.Devices() { if cfg.Introducer { deviceUses["introducer"]++ } if cfg.CertName != "" && cfg.CertName != "syncthing" { deviceUses["customCertName"]++ } if cfg.Compression == protocol.CompressAlways { deviceUses["compressAlways"]++ } else if cfg.Compression == protocol.CompressMetadata { deviceUses["compressMetadata"]++ } else if cfg.Compression == protocol.CompressNever { deviceUses["compressNever"]++ } for _, addr := range cfg.Addresses { if addr == "dynamic" { deviceUses["dynamicAddr"]++ } else { deviceUses["staticAddr"]++ } } } res["deviceUses"] = deviceUses defaultAnnounceServersDNS, defaultAnnounceServersIP, otherAnnounceServers := 0, 0, 0 for _, addr := range cfg.Options().GlobalAnnServers { if addr == "default" || addr == "default-v4" || addr == "default-v6" { defaultAnnounceServersDNS++ } else { otherAnnounceServers++ } } res["announce"] = map[string]interface{}{ "globalEnabled": cfg.Options().GlobalAnnEnabled, "localEnabled": cfg.Options().LocalAnnEnabled, "defaultServersDNS": defaultAnnounceServersDNS, "defaultServersIP": defaultAnnounceServersIP, "otherServers": otherAnnounceServers, } defaultRelayServers, otherRelayServers := 0, 0 for _, addr := range cfg.Options().RelayServers { switch addr { case "dynamic+https://relays.syncthing.net/endpoint": defaultRelayServers++ default: otherRelayServers++ } } res["relays"] = map[string]interface{}{ "enabled": cfg.Options().RelaysEnabled, "defaultServers": defaultRelayServers, "otherServers": otherRelayServers, } res["usesRateLimit"] = cfg.Options().MaxRecvKbps > 0 || cfg.Options().MaxSendKbps > 0 res["upgradeAllowedManual"] = !(upgrade.DisabledByCompilation || noUpgrade) res["upgradeAllowedAuto"] = !(upgrade.DisabledByCompilation || noUpgrade) && cfg.Options().AutoUpgradeIntervalH > 0 return res }