mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-23 11:28:59 +00:00
280 lines
5.9 KiB
Go
280 lines
5.9 KiB
Go
// Copyright (C) 2015 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 relay
|
|
|
|
import (
|
|
"crypto/tls"
|
|
|
|
"net/url"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/syncthing/syncthing/lib/config"
|
|
"github.com/syncthing/syncthing/lib/events"
|
|
"github.com/syncthing/syncthing/lib/relay/client"
|
|
"github.com/syncthing/syncthing/lib/relay/protocol"
|
|
"github.com/syncthing/syncthing/lib/sync"
|
|
|
|
"github.com/thejerf/suture"
|
|
)
|
|
|
|
const (
|
|
eventBroadcasterCheckInterval = 10 * time.Second
|
|
)
|
|
|
|
type Service struct {
|
|
*suture.Supervisor
|
|
cfg *config.Wrapper
|
|
tlsCfg *tls.Config
|
|
|
|
tokens map[string]suture.ServiceToken
|
|
clients map[string]client.RelayClient
|
|
mut sync.RWMutex
|
|
invitations chan protocol.SessionInvitation
|
|
conns chan *tls.Conn
|
|
}
|
|
|
|
func NewService(cfg *config.Wrapper, tlsCfg *tls.Config) *Service {
|
|
conns := make(chan *tls.Conn)
|
|
|
|
service := &Service{
|
|
Supervisor: suture.New("Service", suture.Spec{
|
|
Log: func(log string) {
|
|
l.Debugln(log)
|
|
},
|
|
FailureBackoff: 5 * time.Minute,
|
|
FailureDecay: float64((10 * time.Minute) / time.Second),
|
|
FailureThreshold: 5,
|
|
}),
|
|
cfg: cfg,
|
|
tlsCfg: tlsCfg,
|
|
|
|
tokens: make(map[string]suture.ServiceToken),
|
|
clients: make(map[string]client.RelayClient),
|
|
mut: sync.NewRWMutex(),
|
|
invitations: make(chan protocol.SessionInvitation),
|
|
conns: conns,
|
|
}
|
|
|
|
rcfg := cfg.Raw()
|
|
service.CommitConfiguration(rcfg, rcfg)
|
|
cfg.Subscribe(service)
|
|
|
|
receiver := &invitationReceiver{
|
|
tlsCfg: tlsCfg,
|
|
conns: conns,
|
|
invitations: service.invitations,
|
|
stop: make(chan struct{}),
|
|
}
|
|
|
|
eventBc := &eventBroadcaster{
|
|
Service: service,
|
|
stop: make(chan struct{}),
|
|
}
|
|
|
|
service.Add(receiver)
|
|
service.Add(eventBc)
|
|
|
|
return service
|
|
}
|
|
|
|
func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
|
|
for _, addr := range to.Options.RelayServers {
|
|
_, err := url.Parse(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
|
|
existing := make(map[string]*url.URL, len(to.Options.RelayServers))
|
|
|
|
for _, addr := range to.Options.RelayServers {
|
|
uri, err := url.Parse(addr)
|
|
if err != nil {
|
|
l.Debugln("Failed to parse relay address", addr, err)
|
|
continue
|
|
}
|
|
existing[uri.String()] = uri
|
|
}
|
|
|
|
s.mut.Lock()
|
|
|
|
for key, uri := range existing {
|
|
_, ok := s.tokens[key]
|
|
if !ok {
|
|
l.Debugln("Connecting to relay", uri)
|
|
c, err := client.NewClient(uri, s.tlsCfg.Certificates, s.invitations, 10*time.Second)
|
|
if err != nil {
|
|
l.Infoln("Failed to connect to relay", uri, err)
|
|
continue
|
|
}
|
|
s.tokens[key] = s.Add(c)
|
|
s.clients[key] = c
|
|
}
|
|
}
|
|
|
|
for key, token := range s.tokens {
|
|
_, ok := existing[key]
|
|
if !ok {
|
|
err := s.Remove(token)
|
|
delete(s.tokens, key)
|
|
delete(s.clients, key)
|
|
l.Debugln("Disconnecting from relay", key, err)
|
|
}
|
|
}
|
|
|
|
s.mut.Unlock()
|
|
|
|
return true
|
|
}
|
|
|
|
type Status struct {
|
|
URL string
|
|
OK bool
|
|
Latency int
|
|
}
|
|
|
|
// Relays return the list of relays that currently have an OK status.
|
|
func (s *Service) Relays() []string {
|
|
if s == nil {
|
|
// A nil client does not have a status, really. Yet we may be called
|
|
// this way, for raisins...
|
|
return nil
|
|
}
|
|
|
|
s.mut.RLock()
|
|
relays := make([]string, 0, len(s.clients))
|
|
for _, client := range s.clients {
|
|
relays = append(relays, client.URI().String())
|
|
}
|
|
s.mut.RUnlock()
|
|
|
|
sort.Strings(relays)
|
|
|
|
return relays
|
|
}
|
|
|
|
// RelayStatus returns the latency and OK status for a given relay.
|
|
func (s *Service) RelayStatus(uri string) (time.Duration, bool) {
|
|
if s == nil {
|
|
// A nil client does not have a status, really. Yet we may be called
|
|
// this way, for raisins...
|
|
return time.Hour, false
|
|
}
|
|
|
|
s.mut.RLock()
|
|
defer s.mut.RUnlock()
|
|
|
|
for _, client := range s.clients {
|
|
if client.URI().String() == uri {
|
|
return client.Latency(), client.StatusOK()
|
|
}
|
|
}
|
|
|
|
return time.Hour, false
|
|
}
|
|
|
|
// Accept returns a new *tls.Conn. The connection is already handshaken.
|
|
func (s *Service) Accept() *tls.Conn {
|
|
return <-s.conns
|
|
}
|
|
|
|
type invitationReceiver struct {
|
|
invitations chan protocol.SessionInvitation
|
|
tlsCfg *tls.Config
|
|
conns chan<- *tls.Conn
|
|
stop chan struct{}
|
|
}
|
|
|
|
func (r *invitationReceiver) Serve() {
|
|
for {
|
|
select {
|
|
case inv := <-r.invitations:
|
|
l.Debugln("Received relay invitation", inv)
|
|
conn, err := client.JoinSession(inv)
|
|
if err != nil {
|
|
l.Debugf("Failed to join relay session %s: %v", inv, err)
|
|
continue
|
|
}
|
|
|
|
var tc *tls.Conn
|
|
|
|
if inv.ServerSocket {
|
|
tc = tls.Server(conn, r.tlsCfg)
|
|
} else {
|
|
tc = tls.Client(conn, r.tlsCfg)
|
|
}
|
|
err = tc.Handshake()
|
|
if err != nil {
|
|
l.Infof("TLS handshake (BEP/relay %s): %v", inv, err)
|
|
tc.Close()
|
|
continue
|
|
}
|
|
r.conns <- tc
|
|
|
|
case <-r.stop:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *invitationReceiver) Stop() {
|
|
close(r.stop)
|
|
}
|
|
|
|
// The eventBroadcaster sends a RelayStateChanged event when the relay status
|
|
// changes. We need this somewhat ugly polling mechanism as there's currently
|
|
// no way to get the event feed directly from the relay lib. This may be
|
|
// something to revisit later, possibly.
|
|
type eventBroadcaster struct {
|
|
Service *Service
|
|
stop chan struct{}
|
|
}
|
|
|
|
func (e *eventBroadcaster) Serve() {
|
|
timer := time.NewTicker(eventBroadcasterCheckInterval)
|
|
defer timer.Stop()
|
|
|
|
var prevOKRelays []string
|
|
|
|
for {
|
|
select {
|
|
case <-timer.C:
|
|
curOKRelays := e.Service.Relays()
|
|
|
|
changed := len(curOKRelays) != len(prevOKRelays)
|
|
if !changed {
|
|
for i := range curOKRelays {
|
|
if curOKRelays[i] != prevOKRelays[i] {
|
|
changed = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if changed {
|
|
events.Default.Log(events.RelayStateChanged, map[string][]string{
|
|
"old": prevOKRelays,
|
|
"new": curOKRelays,
|
|
})
|
|
}
|
|
|
|
prevOKRelays = curOKRelays
|
|
|
|
case <-e.stop:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *eventBroadcaster) Stop() {
|
|
close(e.stop)
|
|
}
|