func withDetailsMiddleware(id protocol.DeviceID, h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Syncthing-Version", Version) w.Header().Set("X-Syncthing-ID", id.String()) h.ServeHTTP(w, r) }) }
func NewDeviceStatisticsReference(ldb *leveldb.DB, device protocol.DeviceID) *DeviceStatisticsReference { prefix := string(db.KeyTypeDeviceStatistic) + device.String() return &DeviceStatisticsReference{ ns: db.NewNamespacedKV(ldb, prefix), device: device, } }
// Close removes the peer from the model and closes the underlying connection if possible. // Implements the protocol.Model interface. func (m *Model) Close(device protocol.DeviceID, err error) { l.Infof("Connection to %s closed: %v", device, err) events.Default.Log(events.DeviceDisconnected, map[string]string{ "id": device.String(), "error": err.Error(), }) m.pmut.Lock() m.fmut.RLock() for _, folder := range m.deviceFolders[device] { m.folderFiles[folder].Replace(device, nil) } m.fmut.RUnlock() conn, ok := m.rawConn[device] if ok { if conn, ok := conn.(*tls.Conn); ok { // If the underlying connection is a *tls.Conn, Close() does more // than it says on the tin. Specifically, it sends a TLS alert // message, which might block forever if the connection is dead // and we don't have a deadline site. conn.SetWriteDeadline(time.Now().Add(250 * time.Millisecond)) } conn.Close() } delete(m.protoConn, device) delete(m.rawConn, device) delete(m.deviceVer, device) m.pmut.Unlock() }
// Lookup returns the list of addresses where the given device is available; // direct, and via relays. func (c *globalClient) Lookup(device protocol.DeviceID) (direct []string, relays []Relay, err error) { qURL, err := url.Parse(c.server) if err != nil { return nil, nil, err } q := qURL.Query() q.Set("device", device.String()) qURL.RawQuery = q.Encode() resp, err := c.queryClient.Get(qURL.String()) if err != nil { if debug { l.Debugln("globalClient.Lookup", qURL.String(), err) } return nil, nil, err } if resp.StatusCode != 200 { resp.Body.Close() if debug { l.Debugln("globalClient.Lookup", qURL.String(), resp.Status) } return nil, nil, errors.New(resp.Status) } // TODO: Handle 429 and Retry-After? var ann announcement err = json.NewDecoder(resp.Body).Decode(&ann) resp.Body.Close() return ann.Direct, ann.Relays, err }
func (d *Discoverer) Hint(device string, addrs []string) { resAddrs := resolveAddrs(addrs) var id protocol.DeviceID id.UnmarshalText([]byte(device)) d.registerDevice(nil, Device{ Addresses: resAddrs, ID: id[:], }) }
// IndexUpdate is called for incremental updates to connected devices' indexes. // Implements the protocol.Model interface. func (m *Model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo, flags uint32, options []protocol.Option) { if flags != 0 { l.Warnln("protocol error: unknown flags 0x%x in IndexUpdate message", flags) return } if debug { l.Debugf("%v IDXUP(in): %s / %q: %d files", m, deviceID, folder, len(fs)) } if !m.folderSharedWith(folder, deviceID) { l.Infof("Update for unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder, deviceID) return } m.fmut.RLock() files := m.folderFiles[folder] runner, ok := m.folderRunners[folder] m.fmut.RUnlock() if !ok { l.Fatalf("IndexUpdate for nonexistant folder %q", folder) } for i := 0; i < len(fs); { if fs[i].Flags&^protocol.FlagsAll != 0 { if debug { l.Debugln("dropping update for file with unknown bits set", fs[i]) } fs[i] = fs[len(fs)-1] fs = fs[:len(fs)-1] } else if symlinkInvalid(folder, fs[i]) { if debug { l.Debugln("dropping update for unsupported symlink", fs[i]) } fs[i] = fs[len(fs)-1] fs = fs[:len(fs)-1] } else { i++ } } files.Update(deviceID, fs) events.Default.Log(events.RemoteIndexUpdated, map[string]interface{}{ "device": deviceID.String(), "folder": folder, "items": len(fs), "version": files.LocalVersion(deviceID), }) runner.IndexUpdated() }
func (s *querysrv) updateAddress(tx *sql.Tx, device protocol.DeviceID, uri string) error { res, err := tx.Stmt(s.prep["updateAddress"]).Exec(device.String(), uri) if err != nil { return err } if rows, _ := res.RowsAffected(); rows == 0 { _, err := tx.Stmt(s.prep["insertAddress"]).Exec(device.String(), uri) if err != nil { return err } } return nil }
func handleAnnounceV2(db *leveldb.DB, addr *net.UDPAddr, buf []byte) error { var pkt discover.Announce err := pkt.UnmarshalXDR(buf) if err != nil && err != io.EOF { return err } if debug { log.Printf("<- %v %#v", addr, pkt) } lock.Lock() announces++ lock.Unlock() ip := addr.IP.To4() if ip == nil { ip = addr.IP.To16() } var addrs []address now := time.Now().Unix() for _, addr := range pkt.This.Addresses { tip := addr.IP if len(tip) == 0 { tip = ip } addrs = append(addrs, address{ ip: tip, port: addr.Port, seen: now, }) } var id protocol.DeviceID if len(pkt.This.ID) == 32 { // Raw node ID copy(id[:], pkt.This.ID) } else { err = id.UnmarshalText(pkt.This.ID) if err != nil { return err } } update(db, id, addrs) return nil }
// Index is called when a new device is connected and we receive their full index. // Implements the protocol.Model interface. func (m *Model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) { if debug { l.Debugf("IDX(in): %s %q: %d files", deviceID, folder, len(fs)) } if !m.folderSharedWith(folder, deviceID) { events.Default.Log(events.FolderRejected, map[string]string{ "folder": folder, "device": deviceID.String(), }) l.Infof("Unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder, deviceID) return } m.fmut.RLock() files, ok := m.folderFiles[folder] m.fmut.RUnlock() if !ok { l.Fatalf("Index for nonexistant folder %q", folder) } for i := 0; i < len(fs); { lamport.Default.Tick(fs[i].Version) if symlinkInvalid(fs[i].IsSymlink()) { if debug { l.Debugln("dropping update for unsupported symlink", fs[i]) } fs[i] = fs[len(fs)-1] fs = fs[:len(fs)-1] } else { i++ } } files.Replace(deviceID, fs) events.Default.Log(events.RemoteIndexUpdated, map[string]interface{}{ "device": deviceID.String(), "folder": folder, "items": len(fs), "version": files.LocalVersion(deviceID), }) }
func (s *querysrv) getAddresses(device protocol.DeviceID) ([]string, error) { rows, err := s.prep["selectAddress"].Query(device.String()) if err != nil { return nil, err } var res []string for rows.Next() { var addr string err := rows.Scan(&addr) if err != nil { log.Println("Scan:", err) continue } res = append(res, addr) } return res, nil }
// NewModel creates and starts a new model. The model starts in read-only mode, // where it sends index information to connected peers and responds to requests // for file data without altering the local folder in any way. func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName, clientVersion string, ldb *leveldb.DB) *Model { m := &Model{ Supervisor: suture.New("model", suture.Spec{ Log: func(line string) { if debug { l.Debugln(line) } }, }), cfg: cfg, db: ldb, finder: db.NewBlockFinder(ldb, cfg), progressEmitter: NewProgressEmitter(cfg), id: id, shortID: id.Short(), deviceName: deviceName, clientName: clientName, clientVersion: clientVersion, folderCfgs: make(map[string]config.FolderConfiguration), folderFiles: make(map[string]*db.FileSet), folderDevices: make(map[string][]protocol.DeviceID), deviceFolders: make(map[protocol.DeviceID][]string), deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference), folderIgnores: make(map[string]*ignore.Matcher), folderRunners: make(map[string]service), folderStatRefs: make(map[string]*stats.FolderStatisticsReference), protoConn: make(map[protocol.DeviceID]protocol.Connection), rawConn: make(map[protocol.DeviceID]io.Closer), deviceVer: make(map[protocol.DeviceID]string), reqValidationCache: make(map[string]time.Time), fmut: sync.NewRWMutex(), pmut: sync.NewRWMutex(), rvmut: sync.NewRWMutex(), } if cfg.Options().ProgressUpdateIntervalS > -1 { go m.progressEmitter.Serve() } return m }
func (s *querysrv) getRelays(device protocol.DeviceID) ([]discover.Relay, error) { rows, err := s.prep["selectRelay"].Query(device.String()) if err != nil { return nil, err } var res []discover.Relay for rows.Next() { var addr string var latency int32 err := rows.Scan(&addr, &latency) if err != nil { log.Println("Scan:", err) continue } res = append(res, discover.Relay{ Address: addr, Latency: latency, }) } return res, nil }
func (s *querysrv) handleAnnounce(addr *net.UDPAddr, buf []byte) error { var pkt discover.Announce err := pkt.UnmarshalXDR(buf) if err != nil && err != io.EOF { return err } var id protocol.DeviceID copy(id[:], pkt.This.ID) if id == protocol.LocalDeviceID { return fmt.Errorf("Rejecting announce for local device ID from %v", addr) } tx, err := s.db.Begin() if err != nil { return err } for _, annAddr := range pkt.This.Addresses { uri, err := url.Parse(annAddr) if err != nil { continue } host, port, err := net.SplitHostPort(uri.Host) if err != nil { continue } if len(host) == 0 { uri.Host = net.JoinHostPort(addr.IP.String(), port) } if err := s.updateAddress(tx, id, uri.String()); err != nil { tx.Rollback() return err } } _, err = tx.Stmt(s.prep["deleteRelay"]).Exec(id.String()) if err != nil { tx.Rollback() return err } for _, relay := range pkt.This.Relays { uri, err := url.Parse(relay.Address) if err != nil { continue } _, err = tx.Stmt(s.prep["insertRelay"]).Exec(id.String(), uri, relay.Latency) if err != nil { tx.Rollback() return err } } if err := s.updateDevice(tx, id); err != nil { tx.Rollback() return err } return tx.Commit() }
func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterConfigMessage) { m.pmut.Lock() if cm.ClientName == "syncthing" { m.deviceVer[deviceID] = cm.ClientVersion } else { m.deviceVer[deviceID] = cm.ClientName + " " + cm.ClientVersion } event := map[string]string{ "id": deviceID.String(), "clientName": cm.ClientName, "clientVersion": cm.ClientVersion, } if conn, ok := m.rawConn[deviceID].(*tls.Conn); ok { event["addr"] = conn.RemoteAddr().String() } m.pmut.Unlock() events.Default.Log(events.DeviceConnected, event) l.Infof(`Device %s client is "%s %s"`, deviceID, cm.ClientName, cm.ClientVersion) var changed bool if name := cm.GetOption("name"); name != "" { l.Infof("Device %s name is %q", deviceID, name) device, ok := m.cfg.Devices()[deviceID] if ok && device.Name == "" { device.Name = name m.cfg.SetDevice(device) changed = true } } if m.cfg.Devices()[deviceID].Introducer { // This device is an introducer. Go through the announced lists of folders // and devices and add what we are missing. for _, folder := range cm.Folders { // If we don't have this folder yet, skip it. Ideally, we'd // offer up something in the GUI to create the folder, but for the // moment we only handle folders that we already have. if _, ok := m.folderDevices[folder.ID]; !ok { continue } nextDevice: for _, device := range folder.Devices { var id protocol.DeviceID copy(id[:], device.ID) if _, ok := m.cfg.Devices()[id]; !ok { // The device is currently unknown. Add it to the config. l.Infof("Adding device %v to config (vouched for by introducer %v)", id, deviceID) newDeviceCfg := config.DeviceConfiguration{ DeviceID: id, Compression: m.cfg.Devices()[deviceID].Compression, Addresses: []string{"dynamic"}, } // The introducers' introducers are also our introducers. if device.Flags&protocol.FlagIntroducer != 0 { l.Infof("Device %v is now also an introducer", id) newDeviceCfg.Introducer = true } m.cfg.SetDevice(newDeviceCfg) changed = true } for _, er := range m.deviceFolders[id] { if er == folder.ID { // We already share the folder with this device, so // nothing to do. continue nextDevice } } // We don't yet share this folder with this device. Add the device // to sharing list of the folder. l.Infof("Adding device %v to share %q (vouched for by introducer %v)", id, folder.ID, deviceID) m.deviceFolders[id] = append(m.deviceFolders[id], folder.ID) m.folderDevices[folder.ID] = append(m.folderDevices[folder.ID], id) folderCfg := m.cfg.Folders()[folder.ID] folderCfg.Devices = append(folderCfg.Devices, config.FolderDeviceConfiguration{ DeviceID: id, }) m.cfg.SetFolder(folderCfg) changed = true } } } if changed { m.cfg.Save() } }
func handleQueryV2(db *leveldb.DB, conn *net.UDPConn, addr *net.UDPAddr, buf []byte) error { var pkt discover.Query err := pkt.UnmarshalXDR(buf) if err != nil { return err } if debug { log.Printf("<- %v %#v", addr, pkt) } var id protocol.DeviceID if len(pkt.DeviceID) == 32 { // Raw node ID copy(id[:], pkt.DeviceID) } else { err = id.UnmarshalText(pkt.DeviceID) if err != nil { return err } } lock.Lock() queries++ lock.Unlock() addrs := get(db, id) now := time.Now().Unix() if len(addrs) > 0 { ann := discover.Announce{ Magic: discover.AnnouncementMagic, This: discover.Device{ ID: pkt.DeviceID, }, } for _, addr := range addrs { if now-addr.seen > cacheLimitSeconds { continue } ann.This.Addresses = append(ann.This.Addresses, discover.Address{IP: addr.ip, Port: addr.port}) } if debug { log.Printf("-> %v %#v", addr, pkt) } if len(ann.This.Addresses) == 0 { return nil } tb, err := ann.MarshalXDR() if err != nil { log.Println("QueryV2 response marshal:", err) return nil } _, err = conn.WriteToUDP(tb, addr) if err != nil { log.Println("QueryV2 response write:", err) return nil } lock.Lock() answered++ lock.Unlock() } return nil }