| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- // Copyright (C) 2014 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 discover
- import (
- "bytes"
- "encoding/hex"
- "errors"
- "fmt"
- "io"
- "net"
- "net/url"
- "strconv"
- "time"
- "github.com/syncthing/protocol"
- "github.com/syncthing/syncthing/lib/beacon"
- "github.com/syncthing/syncthing/lib/events"
- "github.com/thejerf/suture"
- )
- type localClient struct {
- *suture.Supervisor
- myID protocol.DeviceID
- addrList AddressLister
- relayStat RelayStatusProvider
- name string
- beacon beacon.Interface
- localBcastStart time.Time
- localBcastTick <-chan time.Time
- forcedBcastTick chan time.Time
- *cache
- }
- const (
- BroadcastInterval = 30 * time.Second
- CacheLifeTime = 3 * BroadcastInterval
- )
- var (
- ErrIncorrectMagic = errors.New("incorrect magic number")
- )
- func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, relayStat RelayStatusProvider) (FinderService, error) {
- c := &localClient{
- Supervisor: suture.NewSimple("local"),
- myID: id,
- addrList: addrList,
- relayStat: relayStat,
- localBcastTick: time.Tick(BroadcastInterval),
- forcedBcastTick: make(chan time.Time),
- localBcastStart: time.Now(),
- cache: newCache(),
- }
- host, port, err := net.SplitHostPort(addr)
- if err != nil {
- return nil, err
- }
- if len(host) == 0 {
- // A broadcast client
- c.name = "IPv4 local"
- bcPort, err := strconv.Atoi(port)
- if err != nil {
- return nil, err
- }
- c.startLocalIPv4Broadcasts(bcPort)
- } else {
- // A multicast client
- c.name = "IPv6 local"
- c.startLocalIPv6Multicasts(addr)
- }
- go c.sendLocalAnnouncements()
- return c, nil
- }
- func (c *localClient) startLocalIPv4Broadcasts(localPort int) {
- c.beacon = beacon.NewBroadcast(localPort)
- c.Add(c.beacon)
- go c.recvAnnouncements(c.beacon)
- }
- func (c *localClient) startLocalIPv6Multicasts(localMCAddr string) {
- c.beacon = beacon.NewMulticast(localMCAddr)
- c.Add(c.beacon)
- go c.recvAnnouncements(c.beacon)
- }
- // Lookup returns a list of addresses the device is available at. Local
- // discovery never returns relays.
- func (c *localClient) Lookup(device protocol.DeviceID) (direct []string, relays []Relay, err error) {
- if cache, ok := c.Get(device); ok {
- if time.Since(cache.when) < CacheLifeTime {
- direct = cache.Direct
- relays = cache.Relays
- }
- }
- return
- }
- func (c *localClient) String() string {
- return c.name
- }
- func (c *localClient) Error() error {
- return c.beacon.Error()
- }
- func (c *localClient) announcementPkt() Announce {
- addrs := c.addrList.AllAddresses()
- var relays []Relay
- for _, relay := range c.relayStat.Relays() {
- latency, ok := c.relayStat.RelayStatus(relay)
- if ok {
- relays = append(relays, Relay{
- URL: relay,
- Latency: int32(latency / time.Millisecond),
- })
- }
- }
- return Announce{
- Magic: AnnouncementMagic,
- This: Device{
- ID: c.myID[:],
- Addresses: addrs,
- Relays: relays,
- },
- }
- }
- func (c *localClient) sendLocalAnnouncements() {
- var pkt = c.announcementPkt()
- msg := pkt.MustMarshalXDR()
- for {
- c.beacon.Send(msg)
- select {
- case <-c.localBcastTick:
- case <-c.forcedBcastTick:
- }
- }
- }
- func (c *localClient) recvAnnouncements(b beacon.Interface) {
- for {
- buf, addr := b.Recv()
- var pkt Announce
- err := pkt.UnmarshalXDR(buf)
- if err != nil && err != io.EOF {
- if debug {
- l.Debugf("discover: Failed to unmarshal local announcement from %s:\n%s", addr, hex.Dump(buf))
- }
- continue
- }
- if debug {
- l.Debugf("discover: Received local announcement from %s for %s", addr, protocol.DeviceIDFromBytes(pkt.This.ID))
- }
- var newDevice bool
- if bytes.Compare(pkt.This.ID, c.myID[:]) != 0 {
- newDevice = c.registerDevice(addr, pkt.This)
- }
- if newDevice {
- select {
- case c.forcedBcastTick <- time.Now():
- }
- }
- }
- }
- func (c *localClient) registerDevice(src net.Addr, device Device) bool {
- var id protocol.DeviceID
- copy(id[:], device.ID)
- // Remember whether we already had a valid cache entry for this device.
- ce, existsAlready := c.Get(id)
- isNewDevice := !existsAlready || time.Since(ce.when) > CacheLifeTime
- // Any empty or unspecified addresses should be set to the source address
- // of the announcement. We also skip any addresses we can't parse.
- var validAddresses []string
- for _, addr := range device.Addresses {
- u, err := url.Parse(addr)
- if err != nil {
- continue
- }
- tcpAddr, err := net.ResolveTCPAddr("tcp", u.Host)
- if err != nil {
- continue
- }
- if len(tcpAddr.IP) == 0 || tcpAddr.IP.IsUnspecified() {
- host, _, err := net.SplitHostPort(src.String())
- if err != nil {
- continue
- }
- u.Host = fmt.Sprintf("%s:%d", host, tcpAddr.Port)
- validAddresses = append(validAddresses, u.String())
- } else {
- validAddresses = append(validAddresses, addr)
- }
- }
- c.Set(id, CacheEntry{
- Direct: validAddresses,
- Relays: device.Relays,
- when: time.Now(),
- found: true,
- })
- if isNewDevice {
- events.Default.Log(events.DeviceDiscovered, map[string]interface{}{
- "device": id.String(),
- "addrs": device.Addresses,
- "relays": device.Relays,
- })
- }
- return isNewDevice
- }
- func addrToAddr(addr *net.TCPAddr) string {
- if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
- return fmt.Sprintf(":%c", addr.Port)
- } else if bs := addr.IP.To4(); bs != nil {
- return fmt.Sprintf("%s:%c", bs.String(), addr.Port)
- } else if bs := addr.IP.To16(); bs != nil {
- return fmt.Sprintf("[%s]:%c", bs.String(), addr.Port)
- }
- return ""
- }
- func resolveAddrs(addrs []string) []string {
- var raddrs []string
- for _, addrStr := range addrs {
- uri, err := url.Parse(addrStr)
- if err != nil {
- continue
- }
- addrRes, err := net.ResolveTCPAddr("tcp", uri.Host)
- if err != nil {
- continue
- }
- addr := addrToAddr(addrRes)
- if len(addr) > 0 {
- uri.Host = addr
- raddrs = append(raddrs, uri.String())
- }
- }
- return raddrs
- }
|