Merge remote-tracking branch 'syncthing/pr/1995'

* syncthing/pr/1995:
  Add switch to disable relays
  Do not start relay service unless explicitly asked for, or global announcement server is running
  Add dynamic relay lookup (DDoS relays.syncthing.net!)
  Discovery clients now take an announcer, global discovery is delayed
  Expose connection type and relay status in the UI
  Add dependencies (fixes #1364)
  Check relays for available devices
  Add incoming connection relay service
  Add unsubscribe to config
  Connections have types
  Large refactoring/feature commit
This commit is contained in:
Jakob Borg 2015-08-20 09:13:37 +02:00
commit c87faace6b
37 changed files with 2493 additions and 457 deletions

8
Godeps/Godeps.json generated
View File

@ -41,6 +41,14 @@
"ImportPath": "github.com/syncthing/protocol", "ImportPath": "github.com/syncthing/protocol",
"Rev": "ebcdea63c07327a342f65415bbadc497462b8f1f" "Rev": "ebcdea63c07327a342f65415bbadc497462b8f1f"
}, },
{
"ImportPath": "github.com/syncthing/relaysrv/client",
"Rev": "7c6a31017968e7c1a69148db1ca3dea71eba8236"
},
{
"ImportPath": "github.com/syncthing/relaysrv/protocol",
"Rev": "7c6a31017968e7c1a69148db1ca3dea71eba8236"
},
{ {
"ImportPath": "github.com/syndtr/goleveldb/leveldb", "ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "b743d92d3215f11c9b5ce8830fafe1f16786adf4" "Rev": "b743d92d3215f11c9b5ce8830fafe1f16786adf4"

View File

@ -0,0 +1,280 @@
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
package client
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/url"
"time"
syncthingprotocol "github.com/syncthing/protocol"
"github.com/syncthing/relaysrv/protocol"
"github.com/syncthing/syncthing/lib/sync"
)
type ProtocolClient struct {
URI *url.URL
Invitations chan protocol.SessionInvitation
closeInvitationsOnFinish bool
config *tls.Config
timeout time.Duration
stop chan struct{}
stopped chan struct{}
conn *tls.Conn
mut sync.RWMutex
connected bool
}
func NewProtocolClient(uri *url.URL, certs []tls.Certificate, invitations chan protocol.SessionInvitation) *ProtocolClient {
closeInvitationsOnFinish := false
if invitations == nil {
closeInvitationsOnFinish = true
invitations = make(chan protocol.SessionInvitation)
}
return &ProtocolClient{
URI: uri,
Invitations: invitations,
closeInvitationsOnFinish: closeInvitationsOnFinish,
config: configForCerts(certs),
timeout: time.Minute * 2,
stop: make(chan struct{}),
stopped: make(chan struct{}),
mut: sync.NewRWMutex(),
connected: false,
}
}
func (c *ProtocolClient) Serve() {
c.stop = make(chan struct{})
c.stopped = make(chan struct{})
defer close(c.stopped)
if err := c.connect(); err != nil {
if debug {
l.Debugln("Relay connect:", err)
}
return
}
if debug {
l.Debugln(c, "connected", c.conn.RemoteAddr())
}
if err := c.join(); err != nil {
c.conn.Close()
l.Infoln("Relay join:", err)
return
}
if err := c.conn.SetDeadline(time.Time{}); err != nil {
l.Infoln("Relay set deadline:", err)
return
}
if debug {
l.Debugln(c, "joined", c.conn.RemoteAddr(), "via", c.conn.LocalAddr())
}
defer c.cleanup()
c.mut.Lock()
c.connected = true
c.mut.Unlock()
messages := make(chan interface{})
errors := make(chan error, 1)
go messageReader(c.conn, messages, errors)
timeout := time.NewTimer(c.timeout)
for {
select {
case message := <-messages:
timeout.Reset(c.timeout)
if debug {
log.Printf("%s received message %T", c, message)
}
switch msg := message.(type) {
case protocol.Ping:
if err := protocol.WriteMessage(c.conn, protocol.Pong{}); err != nil {
l.Infoln("Relay write:", err)
return
}
if debug {
l.Debugln(c, "sent pong")
}
case protocol.SessionInvitation:
ip := net.IP(msg.Address)
if len(ip) == 0 || ip.IsUnspecified() {
msg.Address = c.conn.RemoteAddr().(*net.TCPAddr).IP[:]
}
c.Invitations <- msg
default:
l.Infoln("Relay: protocol error: unexpected message %v", msg)
return
}
case <-c.stop:
if debug {
l.Debugln(c, "stopping")
}
return
case err := <-errors:
l.Infoln("Relay received:", err)
return
case <-timeout.C:
if debug {
l.Debugln(c, "timed out")
}
return
}
}
}
func (c *ProtocolClient) Stop() {
if c.stop == nil {
return
}
close(c.stop)
<-c.stopped
}
func (c *ProtocolClient) StatusOK() bool {
c.mut.RLock()
con := c.connected
c.mut.RUnlock()
return con
}
func (c *ProtocolClient) String() string {
return fmt.Sprintf("ProtocolClient@%p", c)
}
func (c *ProtocolClient) connect() error {
if c.URI.Scheme != "relay" {
return fmt.Errorf("Unsupported relay schema:", c.URI.Scheme)
}
conn, err := tls.Dial("tcp", c.URI.Host, c.config)
if err != nil {
return err
}
if err := conn.SetDeadline(time.Now().Add(10 * time.Second)); err != nil {
conn.Close()
return err
}
if err := performHandshakeAndValidation(conn, c.URI); err != nil {
conn.Close()
return err
}
c.conn = conn
return nil
}
func (c *ProtocolClient) cleanup() {
if c.closeInvitationsOnFinish {
close(c.Invitations)
c.Invitations = make(chan protocol.SessionInvitation)
}
if debug {
l.Debugln(c, "cleaning up")
}
c.mut.Lock()
c.connected = false
c.mut.Unlock()
c.conn.Close()
}
func (c *ProtocolClient) join() error {
if err := protocol.WriteMessage(c.conn, protocol.JoinRelayRequest{}); err != nil {
return err
}
message, err := protocol.ReadMessage(c.conn)
if err != nil {
return err
}
switch msg := message.(type) {
case protocol.Response:
if msg.Code != 0 {
return fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message)
}
default:
return fmt.Errorf("protocol error: expecting response got %v", msg)
}
return nil
}
func performHandshakeAndValidation(conn *tls.Conn, uri *url.URL) error {
if err := conn.Handshake(); err != nil {
return err
}
cs := conn.ConnectionState()
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != protocol.ProtocolName {
return fmt.Errorf("protocol negotiation error")
}
q := uri.Query()
relayIDs := q.Get("id")
if relayIDs != "" {
relayID, err := syncthingprotocol.DeviceIDFromString(relayIDs)
if err != nil {
return fmt.Errorf("relay address contains invalid verification id: %s", err)
}
certs := cs.PeerCertificates
if cl := len(certs); cl != 1 {
return fmt.Errorf("unexpected certificate count: %d", cl)
}
remoteID := syncthingprotocol.NewDeviceID(certs[0].Raw)
if remoteID != relayID {
return fmt.Errorf("relay id does not match. Expected %v got %v", relayID, remoteID)
}
}
return nil
}
func messageReader(conn net.Conn, messages chan<- interface{}, errors chan<- error) {
for {
msg, err := protocol.ReadMessage(conn)
if err != nil {
errors <- err
return
}
messages <- msg
}
}

View File

@ -0,0 +1,15 @@
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
package client
import (
"os"
"strings"
"github.com/calmh/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "relay") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
)

View File

@ -0,0 +1,117 @@
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
package client
import (
"crypto/tls"
"fmt"
"net"
"net/url"
"strconv"
"time"
syncthingprotocol "github.com/syncthing/protocol"
"github.com/syncthing/relaysrv/protocol"
)
func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs []tls.Certificate) (protocol.SessionInvitation, error) {
if uri.Scheme != "relay" {
return protocol.SessionInvitation{}, fmt.Errorf("Unsupported relay scheme:", uri.Scheme)
}
conn, err := tls.Dial("tcp", uri.Host, configForCerts(certs))
conn.SetDeadline(time.Now().Add(10 * time.Second))
if err != nil {
return protocol.SessionInvitation{}, err
}
if err := performHandshakeAndValidation(conn, uri); err != nil {
return protocol.SessionInvitation{}, err
}
defer conn.Close()
request := protocol.ConnectRequest{
ID: id[:],
}
if err := protocol.WriteMessage(conn, request); err != nil {
return protocol.SessionInvitation{}, err
}
message, err := protocol.ReadMessage(conn)
if err != nil {
return protocol.SessionInvitation{}, err
}
switch msg := message.(type) {
case protocol.Response:
return protocol.SessionInvitation{}, fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message)
case protocol.SessionInvitation:
if debug {
l.Debugln("Received invitation", msg, "via", conn.LocalAddr())
}
ip := net.IP(msg.Address)
if len(ip) == 0 || ip.IsUnspecified() {
msg.Address = conn.RemoteAddr().(*net.TCPAddr).IP[:]
}
return msg, nil
default:
return protocol.SessionInvitation{}, fmt.Errorf("protocol error: unexpected message %v", msg)
}
}
func JoinSession(invitation protocol.SessionInvitation) (net.Conn, error) {
addr := net.JoinHostPort(net.IP(invitation.Address).String(), strconv.Itoa(int(invitation.Port)))
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
request := protocol.JoinSessionRequest{
Key: invitation.Key,
}
conn.SetDeadline(time.Now().Add(10 * time.Second))
err = protocol.WriteMessage(conn, request)
if err != nil {
return nil, err
}
message, err := protocol.ReadMessage(conn)
if err != nil {
return nil, err
}
conn.SetDeadline(time.Time{})
switch msg := message.(type) {
case protocol.Response:
if msg.Code != 0 {
return nil, fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message)
}
return conn, nil
default:
return nil, fmt.Errorf("protocol error: expecting response got %v", msg)
}
}
func configForCerts(certs []tls.Certificate) *tls.Config {
return &tls.Config{
Certificates: certs,
NextProtos: []string{protocol.ProtocolName},
ClientAuth: tls.RequestClientCert,
SessionTicketsDisabled: true,
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
},
}
}

View File

@ -0,0 +1,65 @@
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
//go:generate -command genxdr go run ../../syncthing/Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
//go:generate genxdr -o packets_xdr.go packets.go
package protocol
import (
"fmt"
syncthingprotocol "github.com/syncthing/protocol"
"net"
)
const (
messageTypePing int32 = iota
messageTypePong
messageTypeJoinRelayRequest
messageTypeJoinSessionRequest
messageTypeResponse
messageTypeConnectRequest
messageTypeSessionInvitation
)
type header struct {
magic uint32
messageType int32
messageLength int32
}
type Ping struct{}
type Pong struct{}
type JoinRelayRequest struct{}
type JoinSessionRequest struct {
Key []byte // max:32
}
type Response struct {
Code int32
Message string
}
type ConnectRequest struct {
ID []byte // max:32
}
type SessionInvitation struct {
From []byte // max:32
Key []byte // max:32
Address []byte // max:32
Port uint16
ServerSocket bool
}
func (i SessionInvitation) String() string {
return fmt.Sprintf("%s@%s", syncthingprotocol.DeviceIDFromBytes(i.From), i.AddressString())
}
func (i SessionInvitation) GoString() string {
return i.String()
}
func (i SessionInvitation) AddressString() string {
return fmt.Sprintf("%s:%d", net.IP(i.Address), i.Port)
}

View File

@ -0,0 +1,567 @@
// ************************************************************
// This file is automatically generated by genxdr. Do not edit.
// ************************************************************
package protocol
import (
"bytes"
"io"
"github.com/calmh/xdr"
)
/*
header Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| magic |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| message Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct header {
unsigned int magic;
int messageType;
int messageLength;
}
*/
func (o header) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o header) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o header) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o header) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o header) EncodeXDRInto(xw *xdr.Writer) (int, error) {
xw.WriteUint32(o.magic)
xw.WriteUint32(uint32(o.messageType))
xw.WriteUint32(uint32(o.messageLength))
return xw.Tot(), xw.Error()
}
func (o *header) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *header) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *header) DecodeXDRFrom(xr *xdr.Reader) error {
o.magic = xr.ReadUint32()
o.messageType = int32(xr.ReadUint32())
o.messageLength = int32(xr.ReadUint32())
return xr.Error()
}
/*
Ping Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Ping {
}
*/
func (o Ping) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Ping) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Ping) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Ping) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Ping) EncodeXDRInto(xw *xdr.Writer) (int, error) {
return xw.Tot(), xw.Error()
}
func (o *Ping) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Ping) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Ping) DecodeXDRFrom(xr *xdr.Reader) error {
return xr.Error()
}
/*
Pong Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Pong {
}
*/
func (o Pong) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Pong) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Pong) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Pong) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Pong) EncodeXDRInto(xw *xdr.Writer) (int, error) {
return xw.Tot(), xw.Error()
}
func (o *Pong) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Pong) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Pong) DecodeXDRFrom(xr *xdr.Reader) error {
return xr.Error()
}
/*
JoinRelayRequest Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct JoinRelayRequest {
}
*/
func (o JoinRelayRequest) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o JoinRelayRequest) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o JoinRelayRequest) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o JoinRelayRequest) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o JoinRelayRequest) EncodeXDRInto(xw *xdr.Writer) (int, error) {
return xw.Tot(), xw.Error()
}
func (o *JoinRelayRequest) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *JoinRelayRequest) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *JoinRelayRequest) DecodeXDRFrom(xr *xdr.Reader) error {
return xr.Error()
}
/*
JoinSessionRequest Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Key |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Key (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct JoinSessionRequest {
opaque Key<32>;
}
*/
func (o JoinSessionRequest) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o JoinSessionRequest) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o JoinSessionRequest) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o JoinSessionRequest) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o JoinSessionRequest) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.Key); l > 32 {
return xw.Tot(), xdr.ElementSizeExceeded("Key", l, 32)
}
xw.WriteBytes(o.Key)
return xw.Tot(), xw.Error()
}
func (o *JoinSessionRequest) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *JoinSessionRequest) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *JoinSessionRequest) DecodeXDRFrom(xr *xdr.Reader) error {
o.Key = xr.ReadBytesMax(32)
return xr.Error()
}
/*
Response Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Code |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Message |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Message (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Response {
int Code;
string Message<>;
}
*/
func (o Response) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Response) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Response) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Response) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Response) EncodeXDRInto(xw *xdr.Writer) (int, error) {
xw.WriteUint32(uint32(o.Code))
xw.WriteString(o.Message)
return xw.Tot(), xw.Error()
}
func (o *Response) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Response) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Response) DecodeXDRFrom(xr *xdr.Reader) error {
o.Code = int32(xr.ReadUint32())
o.Message = xr.ReadString()
return xr.Error()
}
/*
ConnectRequest Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ ID (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct ConnectRequest {
opaque ID<32>;
}
*/
func (o ConnectRequest) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o ConnectRequest) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o ConnectRequest) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o ConnectRequest) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o ConnectRequest) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.ID); l > 32 {
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
}
xw.WriteBytes(o.ID)
return xw.Tot(), xw.Error()
}
func (o *ConnectRequest) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *ConnectRequest) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *ConnectRequest) DecodeXDRFrom(xr *xdr.Reader) error {
o.ID = xr.ReadBytesMax(32)
return xr.Error()
}
/*
SessionInvitation Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of From |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ From (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Key |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Key (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Address (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0000 | Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Server Socket (V=0 or 1) |V|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct SessionInvitation {
opaque From<32>;
opaque Key<32>;
opaque Address<32>;
unsigned int Port;
bool ServerSocket;
}
*/
func (o SessionInvitation) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o SessionInvitation) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o SessionInvitation) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o SessionInvitation) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o SessionInvitation) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.From); l > 32 {
return xw.Tot(), xdr.ElementSizeExceeded("From", l, 32)
}
xw.WriteBytes(o.From)
if l := len(o.Key); l > 32 {
return xw.Tot(), xdr.ElementSizeExceeded("Key", l, 32)
}
xw.WriteBytes(o.Key)
if l := len(o.Address); l > 32 {
return xw.Tot(), xdr.ElementSizeExceeded("Address", l, 32)
}
xw.WriteBytes(o.Address)
xw.WriteUint16(o.Port)
xw.WriteBool(o.ServerSocket)
return xw.Tot(), xw.Error()
}
func (o *SessionInvitation) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *SessionInvitation) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *SessionInvitation) DecodeXDRFrom(xr *xdr.Reader) error {
o.From = xr.ReadBytesMax(32)
o.Key = xr.ReadBytesMax(32)
o.Address = xr.ReadBytesMax(32)
o.Port = xr.ReadUint16()
o.ServerSocket = xr.ReadBool()
return xr.Error()
}

View File

@ -0,0 +1,114 @@
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
package protocol
import (
"fmt"
"io"
)
const (
magic = 0x9E79BC40
ProtocolName = "bep-relay"
)
var (
ResponseSuccess = Response{0, "success"}
ResponseNotFound = Response{1, "not found"}
ResponseAlreadyConnected = Response{2, "already connected"}
ResponseInternalError = Response{99, "internal error"}
ResponseUnexpectedMessage = Response{100, "unexpected message"}
)
func WriteMessage(w io.Writer, message interface{}) error {
header := header{
magic: magic,
}
var payload []byte
var err error
switch msg := message.(type) {
case Ping:
payload, err = msg.MarshalXDR()
header.messageType = messageTypePing
case Pong:
payload, err = msg.MarshalXDR()
header.messageType = messageTypePong
case JoinRelayRequest:
payload, err = msg.MarshalXDR()
header.messageType = messageTypeJoinRelayRequest
case JoinSessionRequest:
payload, err = msg.MarshalXDR()
header.messageType = messageTypeJoinSessionRequest
case Response:
payload, err = msg.MarshalXDR()
header.messageType = messageTypeResponse
case ConnectRequest:
payload, err = msg.MarshalXDR()
header.messageType = messageTypeConnectRequest
case SessionInvitation:
payload, err = msg.MarshalXDR()
header.messageType = messageTypeSessionInvitation
default:
err = fmt.Errorf("Unknown message type")
}
if err != nil {
return err
}
header.messageLength = int32(len(payload))
headerpayload, err := header.MarshalXDR()
if err != nil {
return err
}
_, err = w.Write(append(headerpayload, payload...))
return err
}
func ReadMessage(r io.Reader) (interface{}, error) {
var header header
if err := header.DecodeXDR(r); err != nil {
return nil, err
}
if header.magic != magic {
return nil, fmt.Errorf("magic mismatch")
}
switch header.messageType {
case messageTypePing:
var msg Ping
err := msg.DecodeXDR(r)
return msg, err
case messageTypePong:
var msg Pong
err := msg.DecodeXDR(r)
return msg, err
case messageTypeJoinRelayRequest:
var msg JoinRelayRequest
err := msg.DecodeXDR(r)
return msg, err
case messageTypeJoinSessionRequest:
var msg JoinSessionRequest
err := msg.DecodeXDR(r)
return msg, err
case messageTypeResponse:
var msg Response
err := msg.DecodeXDR(r)
return msg, err
case messageTypeConnectRequest:
var msg ConnectRequest
err := msg.DecodeXDR(r)
return msg, err
case messageTypeSessionInvitation:
var msg SessionInvitation
err := msg.DecodeXDR(r)
return msg, err
}
return nil, fmt.Errorf("Unknown message type")
}

View File

@ -21,11 +21,11 @@ func main() {
var server string var server string
flag.StringVar(&server, "server", "udp4://announce.syncthing.net:22026", "Announce server") flag.StringVar(&server, "server", "udp4://announce.syncthing.net:22027", "Announce server")
flag.Parse() flag.Parse()
if len(flag.Args()) != 1 || server == "" { if len(flag.Args()) != 1 || server == "" {
log.Printf("Usage: %s [-server=\"udp4://announce.syncthing.net:22026\"] <device>", os.Args[0]) log.Printf("Usage: %s [-server=\"udp4://announce.syncthing.net:22027\"] <device>", os.Args[0])
os.Exit(64) os.Exit(64)
} }
@ -35,9 +35,13 @@ func main() {
os.Exit(1) os.Exit(1)
} }
discoverer := discover.NewDiscoverer(protocol.LocalDeviceID, nil) discoverer := discover.NewDiscoverer(protocol.LocalDeviceID, nil, nil)
discoverer.StartGlobal([]string{server}, 1) discoverer.StartGlobal([]string{server}, 1)
for _, addr := range discoverer.Lookup(id) { addresses, relays := discoverer.Lookup(id)
log.Println(addr) for _, addr := range addresses {
log.Println("address:", addr)
}
for _, addr := range relays {
log.Println("relay:", addr)
} }
} }

View File

@ -11,16 +11,28 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strings" "net/url"
"sync"
"time" "time"
"github.com/syncthing/protocol" "github.com/syncthing/protocol"
"github.com/syncthing/relaysrv/client"
"github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/thejerf/suture" "github.com/thejerf/suture"
) )
type DialerFactory func(*url.URL, *tls.Config) (*tls.Conn, error)
type ListenerFactory func(*url.URL, *tls.Config, chan<- model.IntermediateConnection)
var (
dialers = make(map[string]DialerFactory, 0)
listeners = make(map[string]ListenerFactory, 0)
)
// The connection service listens on TLS and dials configured unconnected // The connection service listens on TLS and dials configured unconnected
// devices. Successful connections are handed to the model. // devices. Successful connections are handed to the model.
type connectionSvc struct { type connectionSvc struct {
@ -29,48 +41,36 @@ type connectionSvc struct {
myID protocol.DeviceID myID protocol.DeviceID
model *model.Model model *model.Model
tlsCfg *tls.Config tlsCfg *tls.Config
conns chan *tls.Conn conns chan model.IntermediateConnection
lastRelayCheck map[protocol.DeviceID]time.Time
mut sync.RWMutex
connType map[protocol.DeviceID]model.ConnectionType
relaysEnabled bool
} }
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.Model, tlsCfg *tls.Config) *connectionSvc { func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Model, tlsCfg *tls.Config) *connectionSvc {
svc := &connectionSvc{ svc := &connectionSvc{
Supervisor: suture.NewSimple("connectionSvc"), Supervisor: suture.NewSimple("connectionSvc"),
cfg: cfg, cfg: cfg,
myID: myID, myID: myID,
model: model, model: mdl,
tlsCfg: tlsCfg, tlsCfg: tlsCfg,
conns: make(chan *tls.Conn), 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 // There are several moving parts here; one routine per listening address
// to handle incoming connections, one routine to periodically attempt // to handle incoming connections, one routine to periodically attempt
// outgoing connections, and lastly one routine to the the common handling // outgoing connections, one routine to the the common handling
// regardless of whether the connection was incoming or outgoing. It ends // regardless of whether the connection was incoming or outgoing.
// up as in the diagram below. We embed a Supervisor to manage the // Furthermore, a relay service which handles incoming requests to connect
// routines (i.e. log and restart if they crash or exit, etc). // via the relays.
//
// +-----------------+
// Incoming | +---------------+-+ +-----------------+
// Connections | | | | | Outgoing
// -------------->| | svc.listen | | | Connections
// | | (1 per listen | | svc.connect |-------------->
// | | address) | | |
// +-+ | | |
// +-----------------+ +-----------------+
// v v
// | |
// | |
// +------------+-----------+
// |
// | svc.conns
// v
// +-----------------+
// | |
// | |
// | svc.handle |------> model.AddConnection()
// | |
// | |
// +-----------------+
// //
// TODO: Clean shutdown, and/or handling config changes on the fly. We // 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 // partly do this now - new devices and addresses will be picked up, but
@ -79,11 +79,25 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.
svc.Add(serviceFunc(svc.connect)) svc.Add(serviceFunc(svc.connect))
for _, addr := range svc.cfg.Options().ListenAddress { for _, addr := range svc.cfg.Options().ListenAddress {
addr := addr uri, err := url.Parse(addr)
listener := serviceFunc(func() { if err != nil {
svc.listen(addr) l.Infoln("Failed to parse listen address:", addr, err)
}) continue
svc.Add(listener) }
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)) svc.Add(serviceFunc(svc.handle))
@ -92,15 +106,15 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, model *model.
func (s *connectionSvc) handle() { func (s *connectionSvc) handle() {
next: next:
for conn := range s.conns { for c := range s.conns {
cs := conn.ConnectionState() cs := c.Conn.ConnectionState()
// We should have negotiated the next level protocol "bep/1.0" as part // We should have negotiated the next level protocol "bep/1.0" as part
// of the TLS handshake. Unfortunately this can't be a hard error, // of the TLS handshake. Unfortunately this can't be a hard error,
// because there are implementations out there that don't support // because there are implementations out there that don't support
// protocol negotiation (iOS for one...). // protocol negotiation (iOS for one...).
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != bepProtocolName { if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != bepProtocolName {
l.Infof("Peer %s did not negotiate bep/1.0", conn.RemoteAddr()) l.Infof("Peer %s did not negotiate bep/1.0", c.Conn.RemoteAddr())
} }
// We should have received exactly one certificate from the other // We should have received exactly one certificate from the other
@ -108,8 +122,8 @@ next:
// connection. // connection.
certs := cs.PeerCertificates certs := cs.PeerCertificates
if cl := len(certs); cl != 1 { if cl := len(certs); cl != 1 {
l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, conn.RemoteAddr()) l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, c.Conn.RemoteAddr())
conn.Close() c.Conn.Close()
continue continue
} }
remoteCert := certs[0] remoteCert := certs[0]
@ -120,19 +134,29 @@ next:
// clients between the same NAT gateway, and global discovery. // clients between the same NAT gateway, and global discovery.
if remoteID == myID { if remoteID == myID {
l.Infof("Connected to myself (%s) - should not happen", remoteID) l.Infof("Connected to myself (%s) - should not happen", remoteID)
conn.Close() c.Conn.Close()
continue continue
} }
// We should not already be connected to the other party. TODO: This // If we have a relay connection, and the new incoming connection is
// could use some better handling. If the old connection is dead but // not a relay connection, we should drop that, and prefer the this one.
// hasn't timed out yet we may want to drop *that* connection and keep s.mut.RLock()
// this one. But in case we are two devices connecting to each other ct, ok := s.connType[remoteID]
// in parallel we don't want to do that or we end up with no s.mut.RUnlock()
// connections still established... if ok && !ct.IsDirect() && c.ConnType.IsDirect() {
if s.model.ConnectedTo(remoteID) { if debugNet {
l.Debugln("Switching connections", remoteID)
}
s.model.Close(remoteID, fmt.Errorf("switching connections"))
} else if s.model.ConnectedTo(remoteID) {
// We should not already be connected to the other party. TODO: This
// could use some better handling. If the old connection is dead but
// hasn't timed out yet we may want to drop *that* connection and keep
// this one. But in case we are two devices connecting to each other
// in parallel we don't want to do that or we end up with no
// connections still established...
l.Infof("Connected to already connected device (%s)", remoteID) l.Infof("Connected to already connected device (%s)", remoteID)
conn.Close() c.Conn.Close()
continue continue
} }
@ -150,35 +174,42 @@ next:
// Incorrect certificate name is something the user most // Incorrect certificate name is something the user most
// likely wants to know about, since it's an advanced // likely wants to know about, since it's an advanced
// config. Warn instead of Info. // config. Warn instead of Info.
l.Warnf("Bad certificate from %s (%v): %v", remoteID, conn.RemoteAddr(), err) l.Warnf("Bad certificate from %s (%v): %v", remoteID, c.Conn.RemoteAddr(), err)
conn.Close() c.Conn.Close()
continue next continue next
} }
// If rate limiting is set, and based on the address we should // If rate limiting is set, and based on the address we should
// limit the connection, then we wrap it in a limiter. // limit the connection, then we wrap it in a limiter.
limit := s.shouldLimit(conn.RemoteAddr()) limit := s.shouldLimit(c.Conn.RemoteAddr())
wr := io.Writer(conn) wr := io.Writer(c.Conn)
if limit && writeRateLimit != nil { if limit && writeRateLimit != nil {
wr = &limitedWriter{conn, writeRateLimit} wr = &limitedWriter{c.Conn, writeRateLimit}
} }
rd := io.Reader(conn) rd := io.Reader(c.Conn)
if limit && readRateLimit != nil { if limit && readRateLimit != nil {
rd = &limitedReader{conn, readRateLimit} rd = &limitedReader{c.Conn, readRateLimit}
} }
name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr()) name := fmt.Sprintf("%s-%s (%s)", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), c.ConnType)
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, name, deviceCfg.Compression) protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, name, deviceCfg.Compression)
l.Infof("Established secure connection to %s at %s", remoteID, name) l.Infof("Established secure connection to %s at %s", remoteID, name)
if debugNet { if debugNet {
l.Debugf("cipher suite: %04X in lan: %t", conn.ConnectionState().CipherSuite, !limit) l.Debugf("cipher suite: %04X in lan: %t", c.Conn.ConnectionState().CipherSuite, !limit)
} }
s.model.AddConnection(conn, protoConn) s.model.AddConnection(model.Connection{
c.Conn,
protoConn,
c.ConnType,
})
s.mut.Lock()
s.connType[remoteID] = c.ConnType
s.mut.Unlock()
continue next continue next
} }
} }
@ -186,54 +217,14 @@ next:
if !s.cfg.IgnoredDevice(remoteID) { if !s.cfg.IgnoredDevice(remoteID) {
events.Default.Log(events.DeviceRejected, map[string]string{ events.Default.Log(events.DeviceRejected, map[string]string{
"device": remoteID.String(), "device": remoteID.String(),
"address": conn.RemoteAddr().String(), "address": c.Conn.RemoteAddr().String(),
}) })
l.Infof("Connection from %s with unknown device ID %s", conn.RemoteAddr(), remoteID) l.Infof("Connection from %s (%s) with unknown device ID %s", c.Conn.RemoteAddr(), c.ConnType, remoteID)
} else { } else {
l.Infof("Connection from %s with ignored device ID %s", conn.RemoteAddr(), remoteID) l.Infof("Connection from %s (%s) with ignored device ID %s", c.Conn.RemoteAddr(), c.ConnType, remoteID)
} }
conn.Close() c.Conn.Close()
}
}
func (s *connectionSvc) listen(addr string) {
if debugNet {
l.Debugln("listening on", addr)
}
tcaddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
listener, err := net.ListenTCP("tcp", tcaddr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
for {
conn, err := listener.Accept()
if err != nil {
l.Warnln("Accepting connection:", err)
continue
}
if debugNet {
l.Debugln("connect from", conn.RemoteAddr())
}
tcpConn := conn.(*net.TCPConn)
s.setTCPOptions(tcpConn)
tc := tls.Server(conn, s.tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
s.conns <- tc
} }
} }
@ -246,19 +237,24 @@ func (s *connectionSvc) connect() {
continue continue
} }
if s.model.ConnectedTo(deviceID) { connected := s.model.ConnectedTo(deviceID)
s.mut.RLock()
ct, ok := s.connType[deviceID]
relaysEnabled := s.relaysEnabled
s.mut.RUnlock()
if connected && ok && ct.IsDirect() {
continue continue
} }
var addrs []string var addrs []string
var relays []string
for _, addr := range deviceCfg.Addresses { for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" { if addr == "dynamic" {
if discoverer != nil { if discoverer != nil {
t := discoverer.Lookup(deviceID) t, r := discoverer.Lookup(deviceID)
if len(t) == 0 {
continue
}
addrs = append(addrs, t...) addrs = append(addrs, t...)
relays = append(relays, r...)
} }
} else { } else {
addrs = append(addrs, addr) addrs = append(addrs, addr)
@ -266,45 +262,108 @@ func (s *connectionSvc) connect() {
} }
for _, addr := range addrs { for _, addr := range addrs {
host, port, err := net.SplitHostPort(addr) uri, err := url.Parse(addr)
if err != nil && strings.HasPrefix(err.Error(), "missing port") { if err != nil {
// addr is on the form "1.2.3.4" l.Infoln("Failed to parse connection url:", addr, err)
addr = net.JoinHostPort(addr, "22000") continue
} else if err == nil && port == "" {
// addr is on the form "1.2.3.4:"
addr = net.JoinHostPort(host, "22000")
} }
dialer, ok := dialers[uri.Scheme]
if !ok {
l.Infoln("Unknown address schema", uri.String())
continue
}
if debugNet { if debugNet {
l.Debugln("dial", deviceCfg.DeviceID, addr) l.Debugln("dial", deviceCfg.DeviceID, uri.String())
} }
conn, err := dialer(uri, s.tlsCfg)
raddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil { if err != nil {
if debugNet { if debugNet {
l.Debugln(err) l.Debugln("dial failed", deviceCfg.DeviceID, uri.String(), err)
} }
continue continue
} }
conn, err := net.DialTCP("tcp", nil, raddr) if connected {
s.model.Close(deviceID, fmt.Errorf("switching connections"))
}
s.conns <- model.IntermediateConnection{
conn, model.ConnectionTypeBasicDial,
}
continue nextDevice
}
// Only connect via relays if not already connected
// Also, do not set lastRelayCheck time if we have no relays,
// as otherwise when we do discover relays, we might have to
// wait up to RelayReconnectIntervalM to connect again.
// Also, do not try relays if we are explicitly told not to.
if connected || len(relays) == 0 || !relaysEnabled {
continue nextDevice
}
reconIntv := time.Duration(s.cfg.Options().RelayReconnectIntervalM) * time.Minute
if last, ok := s.lastRelayCheck[deviceID]; ok && time.Since(last) < reconIntv {
if debugNet {
l.Debugln("Skipping connecting via relay to", deviceID, "last checked at", last)
}
continue nextDevice
} else if debugNet {
l.Debugln("Trying relay connections to", deviceID, relays)
}
s.lastRelayCheck[deviceID] = time.Now()
for _, addr := range relays {
uri, err := url.Parse(addr)
if err != nil { if err != nil {
if debugNet { l.Infoln("Failed to parse relay connection url:", addr, err)
l.Debugln(err)
}
continue continue
} }
s.setTCPOptions(conn) inv, err := client.GetInvitationFromRelay(uri, deviceID, s.tlsCfg.Certificates)
if err != nil {
if debugNet {
l.Debugf("Failed to get invitation for %s from %s: %v", deviceID, uri, err)
}
continue
} else if debugNet {
l.Debugln("Succesfully retrieved relay invitation", inv, "from", uri)
}
tc := tls.Client(conn, s.tlsCfg) conn, err := client.JoinSession(inv)
if err != nil {
if debugNet {
l.Debugf("Failed to join relay session %s: %v", inv, err)
}
continue
} else if debugNet {
l.Debugln("Sucessfully joined relay session", inv)
}
err = osutil.SetTCPOptions(conn.(*net.TCPConn))
if err != nil {
l.Infoln(err)
}
var tc *tls.Conn
if inv.ServerSocket {
tc = tls.Server(conn, s.tlsCfg)
} else {
tc = tls.Client(conn, s.tlsCfg)
}
err = tc.Handshake() err = tc.Handshake()
if err != nil { if err != nil {
l.Infoln("TLS handshake:", err) l.Infof("TLS handshake (BEP/relay %s): %v", inv, err)
tc.Close() tc.Close()
continue continue
} }
s.conns <- model.IntermediateConnection{
s.conns <- tc tc, model.ConnectionTypeRelayDial,
}
continue nextDevice continue nextDevice
} }
} }
@ -317,22 +376,6 @@ func (s *connectionSvc) connect() {
} }
} }
func (*connectionSvc) setTCPOptions(conn *net.TCPConn) {
var err error
if err = conn.SetLinger(0); err != nil {
l.Infoln(err)
}
if err = conn.SetNoDelay(false); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlive(true); err != nil {
l.Infoln(err)
}
}
func (s *connectionSvc) shouldLimit(addr net.Addr) bool { func (s *connectionSvc) shouldLimit(addr net.Addr) bool {
if s.cfg.Options().LimitBandwidthInLan { if s.cfg.Options().LimitBandwidthInLan {
return true return true
@ -355,6 +398,10 @@ func (s *connectionSvc) VerifyConfiguration(from, to config.Configuration) error
} }
func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool { func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool {
s.mut.Lock()
s.relaysEnabled = to.Options.RelaysEnabled
s.mut.Unlock()
// We require a restart if a device as been removed. // We require a restart if a device as been removed.
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices)) newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))

View File

@ -0,0 +1,105 @@
// 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 main
import (
"crypto/tls"
"net"
"net/url"
"strings"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
)
func init() {
dialers["tcp"] = tcpDialer
listeners["tcp"] = tcpListener
}
func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) {
host, port, err := net.SplitHostPort(uri.Host)
if err != nil && strings.HasPrefix(err.Error(), "missing port") {
// addr is on the form "1.2.3.4"
uri.Host = net.JoinHostPort(uri.Host, "22000")
} else if err == nil && port == "" {
// addr is on the form "1.2.3.4:"
uri.Host = net.JoinHostPort(host, "22000")
}
raddr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
if debugNet {
l.Debugln(err)
}
return nil, err
}
conn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
if debugNet {
l.Debugln(err)
}
return nil, err
}
err = osutil.SetTCPOptions(conn)
if err != nil {
l.Infoln(err)
}
tc := tls.Client(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
tc.Close()
return nil, err
}
return tc, nil
}
func tcpListener(uri *url.URL, tlsCfg *tls.Config, conns chan<- model.IntermediateConnection) {
tcaddr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
l.Fatalln("listen (BEP/tcp):", err)
return
}
listener, err := net.ListenTCP("tcp", tcaddr)
if err != nil {
l.Fatalln("listen (BEP/tcp):", err)
return
}
for {
conn, err := listener.Accept()
if err != nil {
l.Warnln("Accepting connection (BEP/tcp):", err)
continue
}
if debugNet {
l.Debugln("connect from", conn.RemoteAddr())
}
err = osutil.SetTCPOptions(conn.(*net.TCPConn))
if err != nil {
l.Infoln(err)
}
tc := tls.Server(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake (BEP/tcp):", err)
tc.Close()
continue
}
conns <- model.IntermediateConnection{
tc, model.ConnectionTypeBasicAccept,
}
}
}

View File

@ -628,6 +628,9 @@ func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
if cfg.Options().GlobalAnnEnabled && discoverer != nil { if cfg.Options().GlobalAnnEnabled && discoverer != nil {
res["extAnnounceOK"] = discoverer.ExtAnnounceOK() res["extAnnounceOK"] = discoverer.ExtAnnounceOK()
} }
if relaySvc != nil {
res["relayClientStatus"] = relaySvc.ClientStatus()
}
cpuUsageLock.RLock() cpuUsageLock.RLock()
var cpusum float64 var cpusum float64
for _, p := range cpuUsagePercent { for _, p := range cpuUsagePercent {

View File

@ -34,8 +34,10 @@ import (
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil" "github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/symlinks" "github.com/syncthing/syncthing/lib/symlinks"
"github.com/syncthing/syncthing/lib/upgrade" "github.com/syncthing/syncthing/lib/upgrade"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/opt"
@ -110,6 +112,7 @@ var (
readRateLimit *ratelimit.Bucket readRateLimit *ratelimit.Bucket
stop = make(chan int) stop = make(chan int)
discoverer *discover.Discoverer discoverer *discover.Discoverer
relaySvc *relay.Svc
cert tls.Certificate cert tls.Certificate
lans []*net.IPNet lans []*net.IPNet
) )
@ -627,7 +630,7 @@ func syncthingMain() {
m.StartDeadlockDetector(time.Duration(it) * time.Second) m.StartDeadlockDetector(time.Duration(it) * time.Second)
} }
} else if !IsRelease || IsBeta { } else if !IsRelease || IsBeta {
m.StartDeadlockDetector(20 * 60 * time.Second) m.StartDeadlockDetector(20 * time.Minute)
} }
// Clear out old indexes for other devices. Otherwise we'll start up and // Clear out old indexes for other devices. Otherwise we'll start up and
@ -658,15 +661,30 @@ func syncthingMain() {
// The default port we announce, possibly modified by setupUPnP next. // The default port we announce, possibly modified by setupUPnP next.
addr, err := net.ResolveTCPAddr("tcp", opts.ListenAddress[0]) uri, err := url.Parse(opts.ListenAddress[0])
if err != nil {
l.Fatalf("Failed to parse listen address %s: %v", opts.ListenAddress[0], err)
}
addr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil { if err != nil {
l.Fatalln("Bad listen address:", err) l.Fatalln("Bad listen address:", err)
} }
// Start the relevant services
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg)
mainSvc.Add(connectionSvc)
if opts.RelaysEnabled && (opts.GlobalAnnEnabled || opts.RelayWithoutGlobalAnn) {
relaySvc = relay.NewSvc(cfg, tlsCfg, connectionSvc.conns)
connectionSvc.Add(relaySvc)
}
// Start discovery // Start discovery
localPort := addr.Port localPort := addr.Port
discoverer = discovery(localPort) discoverer = discovery(localPort, relaySvc)
// Start UPnP. The UPnP service will restart global discovery if the // Start UPnP. The UPnP service will restart global discovery if the
// external port changes. // external port changes.
@ -676,10 +694,6 @@ func syncthingMain() {
mainSvc.Add(upnpSvc) mainSvc.Add(upnpSvc)
} }
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg)
cfg.Subscribe(connectionSvc)
mainSvc.Add(connectionSvc)
if cpuProfile { if cpuProfile {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid())) f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil { if err != nil {
@ -900,18 +914,23 @@ func shutdown() {
stop <- exitSuccess stop <- exitSuccess
} }
func discovery(extPort int) *discover.Discoverer { func discovery(extPort int, relaySvc *relay.Svc) *discover.Discoverer {
opts := cfg.Options() opts := cfg.Options()
disc := discover.NewDiscoverer(myID, opts.ListenAddress) disc := discover.NewDiscoverer(myID, opts.ListenAddress, relaySvc)
if opts.LocalAnnEnabled { if opts.LocalAnnEnabled {
l.Infoln("Starting local discovery announcements") l.Infoln("Starting local discovery announcements")
disc.StartLocal(opts.LocalAnnPort, opts.LocalAnnMCAddr) disc.StartLocal(opts.LocalAnnPort, opts.LocalAnnMCAddr)
} }
if opts.GlobalAnnEnabled { if opts.GlobalAnnEnabled {
l.Infoln("Starting global discovery announcements") go func() {
disc.StartGlobal(opts.GlobalAnnServers, uint16(extPort)) // Defer starting global announce server, giving time to connect
// to relay servers.
time.Sleep(5 * time.Second)
l.Infoln("Starting global discovery announcements")
disc.StartGlobal(opts.GlobalAnnServers, uint16(extPort))
}()
} }
return disc return disc

View File

@ -74,7 +74,7 @@ func (s *verboseSvc) formatEvent(ev events.Event) string {
return fmt.Sprintf("Discovered device %v at %v", data["device"], data["addrs"]) return fmt.Sprintf("Discovered device %v at %v", data["device"], data["addrs"])
case events.DeviceConnected: case events.DeviceConnected:
data := ev.Data.(map[string]string) data := ev.Data.(map[string]string)
return fmt.Sprintf("Connected to device %v at %v", data["id"], data["addr"]) return fmt.Sprintf("Connected to device %v at %v (type %s)", data["id"], data["addr"], data["type"])
case events.DeviceDisconnected: case events.DeviceDisconnected:
data := ev.Data.(map[string]string) data := ev.Data.(map[string]string)
return fmt.Sprintf("Disconnected from device %v", data["id"]) return fmt.Sprintf("Disconnected from device %v", data["id"])

View File

@ -49,7 +49,7 @@
"Edit Folder": "Edit Folder", "Edit Folder": "Edit Folder",
"Editing": "Editing", "Editing": "Editing",
"Enable UPnP": "Enable UPnP", "Enable UPnP": "Enable UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.", "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.", "Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
"Error": "Error", "Error": "Error",
"External File Versioning": "External File Versioning", "External File Versioning": "External File Versioning",
@ -120,6 +120,8 @@
"Quick guide to supported patterns": "Quick guide to supported patterns", "Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilization", "RAM Utilization": "RAM Utilization",
"Random": "Random", "Random": "Random",
"Relayed via": "Relayed via",
"Relays": "Relays",
"Release Notes": "Release Notes", "Release Notes": "Release Notes",
"Remove": "Remove", "Remove": "Remove",
"Rescan": "Rescan", "Rescan": "Rescan",

View File

@ -385,6 +385,19 @@
</span> </span>
</td> </td>
</tr> </tr>
<tr ng-if="system.relayClientStatus != undefined && relayClientsTotal > 0">
<th><span class="fa fa-fw fa-sitemap"></span>&nbsp;<span translate>Relays</span></th>
<td class="text-right">
<span ng-if="relayClientsFailed.length == 0" class="data text-success">
<span>OK</span>
</span>
<span ng-if="relayClientsFailed.length != 0" class="data" ng-class="{'text-danger': relayClientsFailed.length == relayClientsTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-content="{{relayClientsFailed.join('\n')}}">
{{relayClientsTotal-relayClientsFailed.length}}/{{relayClientsTotal}}
</span>
</span>
</td>
</tr>
<tr> <tr>
<th><span class="fa fa-fw fa-clock-o"></span>&nbsp;<span translate>Uptime</span></th> <th><span class="fa fa-fw fa-clock-o"></span>&nbsp;<span translate>Uptime</span></th>
<td class="text-right">{{system.uptime | duration:"m"}}</td> <td class="text-right">{{system.uptime | duration:"m"}}</td>
@ -430,7 +443,11 @@
<td class="text-right">{{connections[deviceCfg.deviceID].outbps | binary}}B/s ({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)</td> <td class="text-right">{{connections[deviceCfg.deviceID].outbps | binary}}B/s ({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)</td>
</tr> </tr>
<tr> <tr>
<th><span class="fa fa-fw fa-link"></span>&nbsp;<span translate>Address</span></th> <th>
<span class="fa fa-fw fa-link"></span>
<span translate ng-if="connections[deviceCfg.deviceID].type.indexOf('basic') == 0" >Address</span>
<span translate ng-if="connections[deviceCfg.deviceID].type.indexOf('relay') == 0" >Relayed via</span>
</th>
<td class="text-right">{{deviceAddr(deviceCfg)}}</td> <td class="text-right">{{deviceAddr(deviceCfg)}}</td>
</tr> </tr>
<tr ng-if="deviceCfg.compression != 'metadata'"> <tr ng-if="deviceCfg.compression != 'metadata'">

View File

@ -176,6 +176,7 @@ angular.module('syncthing.core')
outbps: 0, outbps: 0,
inBytesTotal: 0, inBytesTotal: 0,
outBytesTotal: 0, outBytesTotal: 0,
type: arg.data.type,
address: arg.data.addr address: arg.data.addr
}; };
$scope.completion[arg.data.id] = { $scope.completion[arg.data.id] = {
@ -346,14 +347,24 @@ angular.module('syncthing.core')
$http.get(urlbase + '/system/status').success(function (data) { $http.get(urlbase + '/system/status').success(function (data) {
$scope.myID = data.myID; $scope.myID = data.myID;
$scope.system = data; $scope.system = data;
$scope.announceServersTotal = data.extAnnounceOK ? Object.keys(data.extAnnounceOK).length : 0; $scope.announceServersTotal = data.extAnnounceOK ? Object.keys(data.extAnnounceOK).length : 0;
var failed = []; var failedAnnounce = [];
for (var server in data.extAnnounceOK) { for (var server in data.extAnnounceOK) {
if (!data.extAnnounceOK[server]) { if (!data.extAnnounceOK[server]) {
failed.push(server); failedAnnounce.push(server);
} }
} }
$scope.announceServersFailed = failed; $scope.announceServersFailed = failedAnnounce;
$scope.relayClientsTotal = data.relayClientStatus ? Object.keys(data.relayClientStatus).length : 0;
var failedRelays = [];
for (var relay in data.relayClientStatus) {
if (!data.relayClientStatus[relay]) {
failedRelays.push(relay);
}
}
$scope.relayClientsFailed = failedRelays;
console.log("refreshSystem", data); console.log("refreshSystem", data);

View File

@ -32,7 +32,7 @@
<div class="form-group"> <div class="form-group">
<label translate for="addresses">Addresses</label> <label translate for="addresses">Addresses</label>
<input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice._addressesStr"></input> <input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice._addressesStr"></input>
<p translate class="help-block">Enter comma separated "ip:port" addresses or "dynamic" to perform automatic discovery of the address.</p> <p translate class="help-block">Enter comma separated ("tcp://ip:port", "tcp://host:port") addresses or "dynamic" to perform automatic discovery of the address.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label translate>Compression</label> <label translate>Compression</label>

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@ package config
import ( import (
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"math/rand" "math/rand"
"os" "os"
@ -26,7 +27,7 @@ import (
const ( const (
OldestHandledVersion = 5 OldestHandledVersion = 5
CurrentVersion = 11 CurrentVersion = 12
MaxRescanIntervalS = 365 * 24 * 60 * 60 MaxRescanIntervalS = 365 * 24 * 60 * 60
) )
@ -212,15 +213,19 @@ type FolderDeviceConfiguration struct {
} }
type OptionsConfiguration struct { type OptionsConfiguration struct {
ListenAddress []string `xml:"listenAddress" json:"listenAddress" default:"0.0.0.0:22000"` ListenAddress []string `xml:"listenAddress" json:"listenAddress" default:"tcp://0.0.0.0:22000"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026, udp6://announce-v6.syncthing.net:22026"` GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22027, udp6://announce-v6.syncthing.net:22027"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"` GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"` LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21025"` LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21025"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff32::5222]:21026"` LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
RelayServers []string `xml:"relayServer" json:"relayServers" default:"dynamic+https://relays.syncthing.net"`
MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"` MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"` MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"` ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
RelaysEnabled bool `xml:"relaysEnabled" json:"relaysEnabled" default:"true"`
RelayReconnectIntervalM int `xml:"relayReconnectIntervalM" json:"relayReconnectIntervalM" default:"10"`
RelayWithoutGlobalAnn bool `xml:"relayWithoutGlobalAnn" json:"relayWithoutGlobalAnn" default:"false"`
StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"` StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
UPnPEnabled bool `xml:"upnpEnabled" json:"upnpEnabled" default:"true"` UPnPEnabled bool `xml:"upnpEnabled" json:"upnpEnabled" default:"true"`
UPnPLeaseM int `xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"60"` UPnPLeaseM int `xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"60"`
@ -346,6 +351,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
} }
} }
cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
cfg.Options.GlobalAnnServers = uniqueStrings(cfg.Options.GlobalAnnServers)
if cfg.Version < OldestHandledVersion { if cfg.Version < OldestHandledVersion {
l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version) l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
} }
@ -369,6 +377,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
if cfg.Version == 10 { if cfg.Version == 10 {
convertV10V11(cfg) convertV10V11(cfg)
} }
if cfg.Version == 11 {
convertV11V12(cfg)
}
// Hash old cleartext passwords // Hash old cleartext passwords
if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' { if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' {
@ -420,9 +431,6 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
cfg.Options.ReconnectIntervalS = 5 cfg.Options.ReconnectIntervalS = 5
} }
cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
cfg.Options.GlobalAnnServers = uniqueStrings(cfg.Options.GlobalAnnServers)
if cfg.GUI.APIKey == "" { if cfg.GUI.APIKey == "" {
cfg.GUI.APIKey = randomString(32) cfg.GUI.APIKey = randomString(32)
} }
@ -467,6 +475,38 @@ func convertV10V11(cfg *Configuration) {
cfg.Version = 11 cfg.Version = 11
} }
func convertV11V12(cfg *Configuration) {
// Change listen address schema
for i, addr := range cfg.Options.ListenAddress {
if len(addr) > 0 && !strings.HasPrefix(addr, "tcp://") {
cfg.Options.ListenAddress[i] = fmt.Sprintf("tcp://%s", addr)
}
}
for i, device := range cfg.Devices {
for j, addr := range device.Addresses {
if addr != "dynamic" && addr != "" {
cfg.Devices[i].Addresses[j] = fmt.Sprintf("tcp://%s", addr)
}
}
}
// Use new discovery server
for i, addr := range cfg.Options.GlobalAnnServers {
if addr == "udp4://announce.syncthing.net:22026" {
cfg.Options.GlobalAnnServers[i] = "udp4://announce.syncthing.net:22027"
} else if addr == "udp6://announce-v6.syncthing.net:22026" {
cfg.Options.GlobalAnnServers[i] = "udp6://announce-v6.syncthing.net:22027"
} else if addr == "udp4://194.126.249.5:22026" {
cfg.Options.GlobalAnnServers[i] = "udp4://194.126.249.5:22027"
} else if addr == "udp6://[2001:470:28:4d6::5]:22026" {
cfg.Options.GlobalAnnServers[i] = "udp6://[2001:470:28:4d6::5]:22027"
}
}
cfg.Version = 12
}
func convertV9V10(cfg *Configuration) { func convertV9V10(cfg *Configuration) {
// Enable auto normalization on existing folders. // Enable auto normalization on existing folders.
for i := range cfg.Folders { for i := range cfg.Folders {

View File

@ -31,15 +31,19 @@ func init() {
func TestDefaultValues(t *testing.T) { func TestDefaultValues(t *testing.T) {
expected := OptionsConfiguration{ expected := OptionsConfiguration{
ListenAddress: []string{"0.0.0.0:22000"}, ListenAddress: []string{"tcp://0.0.0.0:22000"},
GlobalAnnServers: []string{"udp4://announce.syncthing.net:22026", "udp6://announce-v6.syncthing.net:22026"}, GlobalAnnServers: []string{"udp4://announce.syncthing.net:22027", "udp6://announce-v6.syncthing.net:22027"},
GlobalAnnEnabled: true, GlobalAnnEnabled: true,
LocalAnnEnabled: true, LocalAnnEnabled: true,
LocalAnnPort: 21025, LocalAnnPort: 21025,
LocalAnnMCAddr: "[ff32::5222]:21026", LocalAnnMCAddr: "[ff32::5222]:21026",
RelayServers: []string{"dynamic+https://relays.syncthing.net"},
MaxSendKbps: 0, MaxSendKbps: 0,
MaxRecvKbps: 0, MaxRecvKbps: 0,
ReconnectIntervalS: 60, ReconnectIntervalS: 60,
RelaysEnabled: true,
RelayReconnectIntervalM: 10,
RelayWithoutGlobalAnn: false,
StartBrowser: true, StartBrowser: true,
UPnPEnabled: true, UPnPEnabled: true,
UPnPLeaseM: 60, UPnPLeaseM: 60,
@ -100,13 +104,13 @@ func TestDeviceConfig(t *testing.T) {
{ {
DeviceID: device1, DeviceID: device1,
Name: "node one", Name: "node one",
Addresses: []string{"a"}, Addresses: []string{"tcp://a"},
Compression: protocol.CompressMetadata, Compression: protocol.CompressMetadata,
}, },
{ {
DeviceID: device4, DeviceID: device4,
Name: "node two", Name: "node two",
Addresses: []string{"b"}, Addresses: []string{"tcp://b"},
Compression: protocol.CompressMetadata, Compression: protocol.CompressMetadata,
}, },
} }
@ -142,15 +146,19 @@ func TestNoListenAddress(t *testing.T) {
func TestOverriddenValues(t *testing.T) { func TestOverriddenValues(t *testing.T) {
expected := OptionsConfiguration{ expected := OptionsConfiguration{
ListenAddress: []string{":23000"}, ListenAddress: []string{"tcp://:23000"},
GlobalAnnServers: []string{"udp4://syncthing.nym.se:22026"}, GlobalAnnServers: []string{"udp4://syncthing.nym.se:22026"},
GlobalAnnEnabled: false, GlobalAnnEnabled: false,
LocalAnnEnabled: false, LocalAnnEnabled: false,
LocalAnnPort: 42123, LocalAnnPort: 42123,
LocalAnnMCAddr: "quux:3232", LocalAnnMCAddr: "quux:3232",
RelayServers: []string{"relay://123.123.123.123:1234", "relay://125.125.125.125:1255"},
MaxSendKbps: 1234, MaxSendKbps: 1234,
MaxRecvKbps: 2341, MaxRecvKbps: 2341,
ReconnectIntervalS: 6000, ReconnectIntervalS: 6000,
RelaysEnabled: false,
RelayReconnectIntervalM: 20,
RelayWithoutGlobalAnn: true,
StartBrowser: false, StartBrowser: false,
UPnPEnabled: false, UPnPEnabled: false,
UPnPLeaseM: 90, UPnPLeaseM: 90,
@ -255,15 +263,15 @@ func TestDeviceAddressesStatic(t *testing.T) {
expected := map[protocol.DeviceID]DeviceConfiguration{ expected := map[protocol.DeviceID]DeviceConfiguration{
device1: { device1: {
DeviceID: device1, DeviceID: device1,
Addresses: []string{"192.0.2.1", "192.0.2.2"}, Addresses: []string{"tcp://192.0.2.1", "tcp://192.0.2.2"},
}, },
device2: { device2: {
DeviceID: device2, DeviceID: device2,
Addresses: []string{"192.0.2.3:6070", "[2001:db8::42]:4242"}, Addresses: []string{"tcp://192.0.2.3:6070", "tcp://[2001:db8::42]:4242"},
}, },
device3: { device3: {
DeviceID: device3, DeviceID: device3,
Addresses: []string{"[2001:db8::44]:4444", "192.0.2.4:6090"}, Addresses: []string{"tcp://[2001:db8::44]:4444", "tcp://192.0.2.4:6090"},
}, },
device4: { device4: {
DeviceID: device4, DeviceID: device4,
@ -330,12 +338,12 @@ func TestIssue1750(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if cfg.Options().ListenAddress[0] != ":23000" { if cfg.Options().ListenAddress[0] != "tcp://:23000" {
t.Errorf("%q != %q", cfg.Options().ListenAddress[0], ":23000") t.Errorf("%q != %q", cfg.Options().ListenAddress[0], "tcp://:23000")
} }
if cfg.Options().ListenAddress[1] != ":23001" { if cfg.Options().ListenAddress[1] != "tcp://:23001" {
t.Errorf("%q != %q", cfg.Options().ListenAddress[1], ":23001") t.Errorf("%q != %q", cfg.Options().ListenAddress[1], "tcp://:23001")
} }
if cfg.Options().GlobalAnnServers[0] != "udp4://syncthing.nym.se:22026" { if cfg.Options().GlobalAnnServers[0] != "udp4://syncthing.nym.se:22026" {

View File

@ -7,10 +7,15 @@
<localAnnounceEnabled>false</localAnnounceEnabled> <localAnnounceEnabled>false</localAnnounceEnabled>
<localAnnouncePort>42123</localAnnouncePort> <localAnnouncePort>42123</localAnnouncePort>
<localAnnounceMCAddr>quux:3232</localAnnounceMCAddr> <localAnnounceMCAddr>quux:3232</localAnnounceMCAddr>
<relayServer>relay://123.123.123.123:1234</relayServer>
<relayServer>relay://125.125.125.125:1255</relayServer>
<parallelRequests>32</parallelRequests> <parallelRequests>32</parallelRequests>
<maxSendKbps>1234</maxSendKbps> <maxSendKbps>1234</maxSendKbps>
<maxRecvKbps>2341</maxRecvKbps> <maxRecvKbps>2341</maxRecvKbps>
<reconnectionIntervalS>6000</reconnectionIntervalS> <reconnectionIntervalS>6000</reconnectionIntervalS>
<relaysEnabled>false</relaysEnabled>
<relayReconnectIntervalM>20</relayReconnectIntervalM>
<relayWithoutGlobalAnn>true</relayWithoutGlobalAnn>
<startBrowser>false</startBrowser> <startBrowser>false</startBrowser>
<upnpEnabled>false</upnpEnabled> <upnpEnabled>false</upnpEnabled>
<upnpLeaseMinutes>90</upnpLeaseMinutes> <upnpLeaseMinutes>90</upnpLeaseMinutes>

13
lib/config/testdata/v12.xml vendored Normal file
View File

@ -0,0 +1,13 @@
<configuration version="12">
<folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600" autoNormalize="true">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
<minDiskFreePct>1</minDiskFreePct>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
<address>tcp://a</address>
</device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
<address>tcp://b</address>
</device>
</configuration>

View File

@ -114,6 +114,21 @@ func (w *Wrapper) Subscribe(c Committer) {
w.sMut.Unlock() w.sMut.Unlock()
} }
// Unsubscribe de-registers the given handler from any future calls to
// configuration changes
func (w *Wrapper) Unsubscribe(c Committer) {
w.sMut.Lock()
for i := range w.subs {
if w.subs[i] == c {
copy(w.subs[i:], w.subs[i+1:])
w.subs[len(w.subs)-1] = nil
w.subs = w.subs[:len(w.subs)-1]
break
}
}
w.sMut.Unlock()
}
// Raw returns the currently wrapped Configuration object. // Raw returns the currently wrapped Configuration object.
func (w *Wrapper) Raw() Configuration { func (w *Wrapper) Raw() Configuration {
return w.cfg return w.cfg

View File

@ -14,7 +14,11 @@ import (
"github.com/syncthing/protocol" "github.com/syncthing/protocol"
) )
type Factory func(*url.URL, *Announce) (Client, error) type Announcer interface {
Announcement() Announce
}
type Factory func(*url.URL, Announcer) (Client, error)
var ( var (
factories = make(map[string]Factory) factories = make(map[string]Factory)
@ -26,7 +30,7 @@ func Register(proto string, factory Factory) {
factories[proto] = factory factories[proto] = factory
} }
func New(addr string, pkt *Announce) (Client, error) { func New(addr string, announcer Announcer) (Client, error) {
uri, err := url.Parse(addr) uri, err := url.Parse(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -35,7 +39,7 @@ func New(addr string, pkt *Announce) (Client, error) {
if !ok { if !ok {
return nil, fmt.Errorf("Unsupported scheme: %s", uri.Scheme) return nil, fmt.Errorf("Unsupported scheme: %s", uri.Scheme)
} }
client, err := factory(uri, pkt) client, err := factory(uri, announcer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -43,7 +47,7 @@ func New(addr string, pkt *Announce) (Client, error) {
} }
type Client interface { type Client interface {
Lookup(device protocol.DeviceID) []string Lookup(device protocol.DeviceID) (Announce, error)
StatusOK() bool StatusOK() bool
Address() string Address() string
Stop() Stop()

View File

@ -24,6 +24,14 @@ func init() {
device, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2") device, _ = protocol.DeviceIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2")
} }
type FakeAnnouncer struct {
pkt Announce
}
func (f *FakeAnnouncer) Announcement() Announce {
return f.pkt
}
func TestUDP4Success(t *testing.T) { func TestUDP4Success(t *testing.T) {
conn, err := net.ListenUDP("udp4", nil) conn, err := net.ListenUDP("udp4", nil)
if err != nil { if err != nil {
@ -33,18 +41,19 @@ func TestUDP4Success(t *testing.T) {
port := conn.LocalAddr().(*net.UDPAddr).Port port := conn.LocalAddr().(*net.UDPAddr).Port
address := fmt.Sprintf("udp4://127.0.0.1:%d", port) address := fmt.Sprintf("udp4://127.0.0.1:%d", port)
pkt := &Announce{ pkt := Announce{
Magic: AnnouncementMagic, Magic: AnnouncementMagic,
This: Device{ This: Device{
device[:], device[:],
[]Address{{ []string{"tcp://123.123.123.123:1234"},
IP: net.IPv4(123, 123, 123, 123), nil,
Port: 1234,
}},
}, },
} }
ann := &FakeAnnouncer{
pkt: pkt,
}
client, err := New(address, pkt) client, err := New(address, ann)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -101,7 +110,12 @@ func TestUDP4Success(t *testing.T) {
wg := sync.NewWaitGroup() wg := sync.NewWaitGroup()
wg.Add(1) wg.Add(1)
go func() { go func() {
addrs = client.Lookup(device) pkt, err := client.Lookup(device)
if err == nil {
for _, addr := range pkt.This.Addresses {
addrs = append(addrs, addr)
}
}
wg.Done() wg.Done()
}() }()
@ -117,7 +131,7 @@ func TestUDP4Success(t *testing.T) {
// Wait for the lookup to arrive, verify that the number of answers is correct // Wait for the lookup to arrive, verify that the number of answers is correct
wg.Wait() wg.Wait()
if len(addrs) != 1 || addrs[0] != "123.123.123.123:1234" { if len(addrs) != 1 || addrs[0] != "tcp://123.123.123.123:1234" {
t.Fatal("Wrong number of answers") t.Fatal("Wrong number of answers")
} }
@ -134,18 +148,19 @@ func TestUDP4Failure(t *testing.T) {
address := fmt.Sprintf("udp4://127.0.0.1:%d/?listenaddress=127.0.0.1&retry=5", port) address := fmt.Sprintf("udp4://127.0.0.1:%d/?listenaddress=127.0.0.1&retry=5", port)
pkt := &Announce{ pkt := Announce{
Magic: AnnouncementMagic, Magic: AnnouncementMagic,
This: Device{ This: Device{
device[:], device[:],
[]Address{{ []string{"tcp://123.123.123.123:1234"},
IP: net.IPv4(123, 123, 123, 123), nil,
Port: 1234,
}},
}, },
} }
ann := &FakeAnnouncer{
pkt: pkt,
}
client, err := New(address, pkt) client, err := New(address, ann)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -197,7 +212,12 @@ func TestUDP4Failure(t *testing.T) {
wg := sync.NewWaitGroup() wg := sync.NewWaitGroup()
wg.Add(1) wg.Add(1)
go func() { go func() {
addrs = client.Lookup(device) pkt, err := client.Lookup(device)
if err == nil {
for _, addr := range pkt.This.Addresses {
addrs = append(addrs, addr)
}
}
wg.Done() wg.Done()
}() }()

View File

@ -20,12 +20,13 @@ import (
func init() { func init() {
for _, proto := range []string{"udp", "udp4", "udp6"} { for _, proto := range []string{"udp", "udp4", "udp6"} {
Register(proto, func(uri *url.URL, pkt *Announce) (Client, error) { Register(proto, func(uri *url.URL, announcer Announcer) (Client, error) {
c := &UDPClient{ c := &UDPClient{
wg: sync.NewWaitGroup(), announcer: announcer,
mut: sync.NewRWMutex(), wg: sync.NewWaitGroup(),
mut: sync.NewRWMutex(),
} }
err := c.Start(uri, pkt) err := c.Start(uri)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,22 +38,20 @@ func init() {
type UDPClient struct { type UDPClient struct {
url *url.URL url *url.URL
id protocol.DeviceID
stop chan struct{} stop chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
listenAddress *net.UDPAddr listenAddress *net.UDPAddr
globalBroadcastInterval time.Duration globalBroadcastInterval time.Duration
errorRetryInterval time.Duration errorRetryInterval time.Duration
announcer Announcer
status bool status bool
mut sync.RWMutex mut sync.RWMutex
} }
func (d *UDPClient) Start(uri *url.URL, pkt *Announce) error { func (d *UDPClient) Start(uri *url.URL) error {
d.url = uri d.url = uri
d.id = protocol.DeviceIDFromBytes(pkt.This.ID)
d.stop = make(chan struct{}) d.stop = make(chan struct{})
params := uri.Query() params := uri.Query()
@ -79,11 +78,11 @@ func (d *UDPClient) Start(uri *url.URL, pkt *Announce) error {
} }
d.wg.Add(1) d.wg.Add(1)
go d.broadcast(pkt.MustMarshalXDR()) go d.broadcast()
return nil return nil
} }
func (d *UDPClient) broadcast(pkt []byte) { func (d *UDPClient) broadcast() {
defer d.wg.Done() defer d.wg.Done()
conn, err := net.ListenUDP(d.url.Scheme, d.listenAddress) conn, err := net.ListenUDP(d.url.Scheme, d.listenAddress)
@ -126,7 +125,14 @@ func (d *UDPClient) broadcast(pkt []byte) {
l.Debugf("discover %s: broadcast: Sending self announcement to %v", d.url, remote) l.Debugf("discover %s: broadcast: Sending self announcement to %v", d.url, remote)
} }
_, err := conn.WriteTo(pkt, remote) ann := d.announcer.Announcement()
pkt, err := ann.MarshalXDR()
if err != nil {
timer.Reset(d.errorRetryInterval)
continue
}
_, err = conn.WriteTo(pkt, remote)
if err != nil { if err != nil {
if debug { if debug {
l.Debugf("discover %s: broadcast: Failed to send self announcement: %s", d.url, err) l.Debugf("discover %s: broadcast: Failed to send self announcement: %s", d.url, err)
@ -137,11 +143,13 @@ func (d *UDPClient) broadcast(pkt []byte) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
res := d.Lookup(d.id) pkt, err := d.Lookup(protocol.DeviceIDFromBytes(ann.This.ID))
if debug { if err != nil && debug {
l.Debugf("discover %s: broadcast: Self-lookup returned: %v", d.url, res) l.Debugf("discover %s: broadcast: Self-lookup failed: %v", d.url, err)
} else if debug {
l.Debugf("discover %s: broadcast: Self-lookup returned: %v", d.url, pkt.This.Addresses)
} }
ok = len(res) > 0 ok = len(pkt.This.Addresses) > 0
} }
d.mut.Lock() d.mut.Lock()
@ -157,13 +165,13 @@ func (d *UDPClient) broadcast(pkt []byte) {
} }
} }
func (d *UDPClient) Lookup(device protocol.DeviceID) []string { func (d *UDPClient) Lookup(device protocol.DeviceID) (Announce, error) {
extIP, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host) extIP, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host)
if err != nil { if err != nil {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
conn, err := net.DialUDP(d.url.Scheme, d.listenAddress, extIP) conn, err := net.DialUDP(d.url.Scheme, d.listenAddress, extIP)
@ -171,7 +179,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
defer conn.Close() defer conn.Close()
@ -180,7 +188,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
buf := Query{QueryMagic, device[:]}.MustMarshalXDR() buf := Query{QueryMagic, device[:]}.MustMarshalXDR()
@ -189,7 +197,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
buf = make([]byte, 2048) buf = make([]byte, 2048)
@ -197,12 +205,12 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if err != nil { if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() { if err, ok := err.(net.Error); ok && err.Timeout() {
// Expected if the server doesn't know about requested device ID // Expected if the server doesn't know about requested device ID
return nil return Announce{}, err
} }
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err) l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
} }
return nil return Announce{}, err
} }
var pkt Announce var pkt Announce
@ -211,18 +219,13 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
if debug { if debug {
l.Debugf("discover %s: Lookup(%s): %s\n%s", d.url, device, err, hex.Dump(buf[:n])) l.Debugf("discover %s: Lookup(%s): %s\n%s", d.url, device, err, hex.Dump(buf[:n]))
} }
return nil return Announce{}, err
} }
var addrs []string
for _, a := range pkt.This.Addresses {
deviceAddr := net.JoinHostPort(net.IP(a.IP).String(), strconv.Itoa(int(a.Port)))
addrs = append(addrs, deviceAddr)
}
if debug { if debug {
l.Debugf("discover %s: Lookup(%s) result: %v", d.url, device, addrs) l.Debugf("discover %s: Lookup(%s) result: %v relays: %v", d.url, device, pkt.This.Addresses, pkt.This.Relays)
} }
return addrs return pkt, nil
} }
func (d *UDPClient) Stop() { func (d *UDPClient) Stop() {

View File

@ -10,21 +10,26 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"io" "io"
"net" "net"
"net/url"
"runtime" "runtime"
"strconv" "sort"
"time" "time"
"github.com/syncthing/protocol" "github.com/syncthing/protocol"
"github.com/syncthing/syncthing/lib/beacon" "github.com/syncthing/syncthing/lib/beacon"
"github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/sync"
) )
type Discoverer struct { type Discoverer struct {
myID protocol.DeviceID myID protocol.DeviceID
listenAddrs []string listenAddrs []string
relaySvc *relay.Svc
localBcastIntv time.Duration localBcastIntv time.Duration
localBcastStart time.Time localBcastStart time.Time
cacheLifetime time.Duration cacheLifetime time.Duration
@ -34,9 +39,10 @@ type Discoverer struct {
localBcastTick <-chan time.Time localBcastTick <-chan time.Time
forcedBcastTick chan time.Time forcedBcastTick chan time.Time
registryLock sync.RWMutex registryLock sync.RWMutex
registry map[protocol.DeviceID][]CacheEntry addressRegistry map[protocol.DeviceID][]CacheEntry
lastLookup map[protocol.DeviceID]time.Time relayRegistry map[protocol.DeviceID][]CacheEntry
lastLookup map[protocol.DeviceID]time.Time
clients []Client clients []Client
mut sync.RWMutex mut sync.RWMutex
@ -51,17 +57,19 @@ var (
ErrIncorrectMagic = errors.New("incorrect magic number") ErrIncorrectMagic = errors.New("incorrect magic number")
) )
func NewDiscoverer(id protocol.DeviceID, addresses []string) *Discoverer { func NewDiscoverer(id protocol.DeviceID, addresses []string, relaySvc *relay.Svc) *Discoverer {
return &Discoverer{ return &Discoverer{
myID: id, myID: id,
listenAddrs: addresses, listenAddrs: addresses,
localBcastIntv: 30 * time.Second, relaySvc: relaySvc,
cacheLifetime: 5 * time.Minute, localBcastIntv: 30 * time.Second,
negCacheCutoff: 3 * time.Minute, cacheLifetime: 5 * time.Minute,
registry: make(map[protocol.DeviceID][]CacheEntry), negCacheCutoff: 3 * time.Minute,
lastLookup: make(map[protocol.DeviceID]time.Time), addressRegistry: make(map[protocol.DeviceID][]CacheEntry),
registryLock: sync.NewRWMutex(), relayRegistry: make(map[protocol.DeviceID][]CacheEntry),
mut: sync.NewRWMutex(), lastLookup: make(map[protocol.DeviceID]time.Time),
registryLock: sync.NewRWMutex(),
mut: sync.NewRWMutex(),
} }
} }
@ -136,14 +144,13 @@ func (d *Discoverer) StartGlobal(servers []string, extPort uint16) {
} }
d.extPort = extPort d.extPort = extPort
pkt := d.announcementPkt()
wg := sync.NewWaitGroup() wg := sync.NewWaitGroup()
clients := make(chan Client, len(servers)) clients := make(chan Client, len(servers))
for _, address := range servers { for _, address := range servers {
wg.Add(1) wg.Add(1)
go func(addr string) { go func(addr string) {
defer wg.Done() defer wg.Done()
client, err := New(addr, pkt) client, err := New(addr, d)
if err != nil { if err != nil {
l.Infoln("Error creating discovery client", addr, err) l.Infoln("Error creating discovery client", addr, err)
return return
@ -184,75 +191,108 @@ func (d *Discoverer) ExtAnnounceOK() map[string]bool {
return ret return ret
} }
func (d *Discoverer) Lookup(device protocol.DeviceID) []string { // Lookup returns a list of addresses the device is available at, as well as
// a list of relays the device is supposed to be available on sorted by the
// sum of latencies between this device, and the device in question.
func (d *Discoverer) Lookup(device protocol.DeviceID) ([]string, []string) {
d.registryLock.RLock() d.registryLock.RLock()
cached := d.filterCached(d.registry[device]) cachedAddresses := d.filterCached(d.addressRegistry[device])
cachedRelays := d.filterCached(d.relayRegistry[device])
lastLookup := d.lastLookup[device] lastLookup := d.lastLookup[device]
d.registryLock.RUnlock() d.registryLock.RUnlock()
d.mut.RLock() d.mut.RLock()
defer d.mut.RUnlock() defer d.mut.RUnlock()
if len(cached) > 0 { relays := make([]string, len(cachedRelays))
for i := range cachedRelays {
relays[i] = cachedRelays[i].Address
}
if len(cachedAddresses) > 0 {
// There are cached address entries. // There are cached address entries.
addrs := make([]string, len(cached)) addrs := make([]string, len(cachedAddresses))
for i := range cached { for i := range cachedAddresses {
addrs[i] = cached[i].Address addrs[i] = cachedAddresses[i].Address
} }
return addrs return addrs, relays
} }
if time.Since(lastLookup) < d.negCacheCutoff { if time.Since(lastLookup) < d.negCacheCutoff {
// We have recently tried to lookup this address and failed. Lets // We have recently tried to lookup this address and failed. Lets
// chill for a while. // chill for a while.
return nil return nil, relays
} }
if len(d.clients) != 0 && time.Since(d.localBcastStart) > d.localBcastIntv { if len(d.clients) != 0 && time.Since(d.localBcastStart) > d.localBcastIntv {
// Only perform external lookups if we have at least one external // Only perform external lookups if we have at least one external
// server client and one local announcement interval has passed. This is // server client and one local announcement interval has passed. This is
// to avoid finding local peers on their remote address at startup. // to avoid finding local peers on their remote address at startup.
results := make(chan []string, len(d.clients)) results := make(chan Announce, len(d.clients))
wg := sync.NewWaitGroup() wg := sync.NewWaitGroup()
for _, client := range d.clients { for _, client := range d.clients {
wg.Add(1) wg.Add(1)
go func(c Client) { go func(c Client) {
defer wg.Done() defer wg.Done()
results <- c.Lookup(device) ann, err := c.Lookup(device)
if err == nil {
results <- ann
}
}(client) }(client)
} }
wg.Wait() wg.Wait()
close(results) close(results)
cached := []CacheEntry{} cachedAddresses := []CacheEntry{}
seen := make(map[string]struct{}) availableRelays := []Relay{}
seenAddresses := make(map[string]struct{})
seenRelays := make(map[string]struct{})
now := time.Now() now := time.Now()
var addrs []string var addrs []string
for result := range results { for result := range results {
for _, addr := range result { for _, addr := range result.This.Addresses {
_, ok := seen[addr] _, ok := seenAddresses[addr]
if !ok { if !ok {
cached = append(cached, CacheEntry{ cachedAddresses = append(cachedAddresses, CacheEntry{
Address: addr, Address: addr,
Seen: now, Seen: now,
}) })
seen[addr] = struct{}{} seenAddresses[addr] = struct{}{}
addrs = append(addrs, addr) addrs = append(addrs, addr)
} }
} }
for _, relay := range result.This.Relays {
_, ok := seenRelays[relay.Address]
if !ok {
availableRelays = append(availableRelays, relay)
seenRelays[relay.Address] = struct{}{}
}
}
}
relays = addressesSortedByLatency(availableRelays)
cachedRelays := make([]CacheEntry, len(relays))
for i := range relays {
cachedRelays[i] = CacheEntry{
Address: relays[i],
Seen: now,
}
} }
d.registryLock.Lock() d.registryLock.Lock()
d.registry[device] = cached d.addressRegistry[device] = cachedAddresses
d.relayRegistry[device] = cachedRelays
d.lastLookup[device] = time.Now() d.lastLookup[device] = time.Now()
d.registryLock.Unlock() d.registryLock.Unlock()
return addrs return addrs, relays
} }
return nil return nil, relays
} }
func (d *Discoverer) Hint(device string, addrs []string) { func (d *Discoverer) Hint(device string, addrs []string) {
@ -267,8 +307,8 @@ func (d *Discoverer) Hint(device string, addrs []string) {
func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry { func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry {
d.registryLock.RLock() d.registryLock.RLock()
devices := make(map[protocol.DeviceID][]CacheEntry, len(d.registry)) devices := make(map[protocol.DeviceID][]CacheEntry, len(d.addressRegistry))
for device, addrs := range d.registry { for device, addrs := range d.addressRegistry {
addrsCopy := make([]CacheEntry, len(addrs)) addrsCopy := make([]CacheEntry, len(addrs))
copy(addrsCopy, addrs) copy(addrsCopy, addrs)
devices[device] = addrsCopy devices[device] = addrsCopy
@ -277,41 +317,36 @@ func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry {
return devices return devices
} }
func (d *Discoverer) announcementPkt() *Announce { func (d *Discoverer) Announcement() Announce {
var addrs []Address return d.announcementPkt(true)
if d.extPort != 0 { }
addrs = []Address{{Port: d.extPort}}
func (d *Discoverer) announcementPkt(allowExternal bool) Announce {
var addrs []string
if d.extPort != 0 && allowExternal {
addrs = []string{fmt.Sprintf("tcp://:%d", d.extPort)}
} else { } else {
for _, astr := range d.listenAddrs { addrs = resolveAddrs(d.listenAddrs)
addr, err := net.ResolveTCPAddr("tcp", astr) }
if err != nil {
l.Warnln("discover: %v: not announcing %s", err, astr) var relayAddrs []string
continue if d.relaySvc != nil {
} else if debug { status := d.relaySvc.ClientStatus()
l.Debugf("discover: resolved %s as %#v", astr, addr) for uri, ok := range status {
} if ok {
if len(addr.IP) == 0 || addr.IP.IsUnspecified() { relayAddrs = append(relayAddrs, uri)
addrs = append(addrs, Address{Port: uint16(addr.Port)})
} else if bs := addr.IP.To4(); bs != nil {
addrs = append(addrs, Address{IP: bs, Port: uint16(addr.Port)})
} else if bs := addr.IP.To16(); bs != nil {
addrs = append(addrs, Address{IP: bs, Port: uint16(addr.Port)})
} }
} }
} }
return &Announce{
return Announce{
Magic: AnnouncementMagic, Magic: AnnouncementMagic,
This: Device{d.myID[:], addrs}, This: Device{d.myID[:], addrs, measureLatency(relayAddrs)},
} }
} }
func (d *Discoverer) sendLocalAnnouncements() { func (d *Discoverer) sendLocalAnnouncements() {
var addrs = resolveAddrs(d.listenAddrs) var pkt = d.announcementPkt(false)
var pkt = Announce{
Magic: AnnouncementMagic,
This: Device{d.myID[:], addrs},
}
msg := pkt.MustMarshalXDR() msg := pkt.MustMarshalXDR()
for { for {
@ -363,19 +398,32 @@ func (d *Discoverer) registerDevice(addr net.Addr, device Device) bool {
d.registryLock.Lock() d.registryLock.Lock()
defer d.registryLock.Unlock() defer d.registryLock.Unlock()
current := d.filterCached(d.registry[id]) current := d.filterCached(d.addressRegistry[id])
orig := current orig := current
for _, a := range device.Addresses { for _, deviceAddr := range device.Addresses {
var deviceAddr string uri, err := url.Parse(deviceAddr)
if len(a.IP) > 0 { if err != nil {
deviceAddr = net.JoinHostPort(net.IP(a.IP).String(), strconv.Itoa(int(a.Port))) if debug {
} else if addr != nil { l.Debugf("discover: Failed to parse address %s: %s", deviceAddr, err)
ua := addr.(*net.UDPAddr) }
ua.Port = int(a.Port) continue
deviceAddr = ua.String()
} }
host, port, err := net.SplitHostPort(uri.Host)
if err != nil {
if debug {
l.Debugf("discover: Failed to split address host %s: %s", deviceAddr, err)
}
continue
}
if host == "" {
uri.Host = net.JoinHostPort(addr.(*net.UDPAddr).IP.String(), port)
deviceAddr = uri.String()
}
for i := range current { for i := range current {
if current[i].Address == deviceAddr { if current[i].Address == deviceAddr {
current[i].Seen = time.Now() current[i].Seen = time.Now()
@ -393,7 +441,7 @@ func (d *Discoverer) registerDevice(addr net.Addr, device Device) bool {
l.Debugf("discover: Caching %s addresses: %v", id, current) l.Debugf("discover: Caching %s addresses: %v", id, current)
} }
d.registry[id] = current d.addressRegistry[id] = current
if len(current) > len(orig) { if len(current) > len(orig) {
addrs := make([]string, len(current)) addrs := make([]string, len(current))
@ -413,7 +461,7 @@ func (d *Discoverer) filterCached(c []CacheEntry) []CacheEntry {
for i := 0; i < len(c); { for i := 0; i < len(c); {
if ago := time.Since(c[i].Seen); ago > d.cacheLifetime { if ago := time.Since(c[i].Seen); ago > d.cacheLifetime {
if debug { if debug {
l.Debugf("discover: Removing cached address %s - seen %v ago", c[i].Address, ago) l.Debugf("discover: Removing cached entry %s - seen %v ago", c[i].Address, ago)
} }
c[i] = c[len(c)-1] c[i] = c[len(c)-1]
c = c[:len(c)-1] c = c[:len(c)-1]
@ -424,30 +472,99 @@ func (d *Discoverer) filterCached(c []CacheEntry) []CacheEntry {
return c return c
} }
func addrToAddr(addr *net.TCPAddr) Address { func addrToAddr(addr *net.TCPAddr) string {
if len(addr.IP) == 0 || addr.IP.IsUnspecified() { if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
return Address{Port: uint16(addr.Port)} return fmt.Sprintf(":%d", addr.Port)
} else if bs := addr.IP.To4(); bs != nil { } else if bs := addr.IP.To4(); bs != nil {
return Address{IP: bs, Port: uint16(addr.Port)} return fmt.Sprintf("%s:%d", bs.String(), addr.Port)
} else if bs := addr.IP.To16(); bs != nil { } else if bs := addr.IP.To16(); bs != nil {
return Address{IP: bs, Port: uint16(addr.Port)} return fmt.Sprintf("[%s]:%d", bs.String(), addr.Port)
} }
return Address{} return ""
} }
func resolveAddrs(addrs []string) []Address { func resolveAddrs(addrs []string) []string {
var raddrs []Address var raddrs []string
for _, addrStr := range addrs { for _, addrStr := range addrs {
addrRes, err := net.ResolveTCPAddr("tcp", addrStr) uri, err := url.Parse(addrStr)
if err != nil {
continue
}
addrRes, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil { if err != nil {
continue continue
} }
addr := addrToAddr(addrRes) addr := addrToAddr(addrRes)
if len(addr.IP) > 0 { if len(addr) > 0 {
raddrs = append(raddrs, addr) uri.Host = addr
} else { raddrs = append(raddrs, uri.String())
raddrs = append(raddrs, Address{Port: addr.Port})
} }
} }
return raddrs return raddrs
} }
func measureLatency(relayAdresses []string) []Relay {
relays := make([]Relay, 0, len(relayAdresses))
for i, addr := range relayAdresses {
relay := Relay{
Address: addr,
Latency: int32(time.Hour / time.Millisecond),
}
relays = append(relays, relay)
if latency, err := getLatencyForURL(addr); err == nil {
if debug {
l.Debugf("Relay %s latency %s", addr, latency)
}
relays[i].Latency = int32(latency / time.Millisecond)
} else {
l.Debugf("Failed to get relay %s latency %s", addr, err)
}
}
return relays
}
// addressesSortedByLatency adds local latency to the relay, and sorts them
// by sum latency, and returns the addresses.
func addressesSortedByLatency(input []Relay) []string {
relays := make([]Relay, len(input))
copy(relays, input)
for i, relay := range relays {
if latency, err := getLatencyForURL(relay.Address); err == nil {
relays[i].Latency += int32(latency / time.Millisecond)
} else {
relays[i].Latency += int32(time.Hour / time.Millisecond)
}
}
sort.Sort(relayList(relays))
addresses := make([]string, 0, len(relays))
for _, relay := range relays {
addresses = append(addresses, relay.Address)
}
return addresses
}
func getLatencyForURL(addr string) (time.Duration, error) {
uri, err := url.Parse(addr)
if err != nil {
return 0, err
}
return osutil.TCPPing(uri.Host)
}
type relayList []Relay
func (l relayList) Len() int {
return len(l)
}
func (l relayList) Less(a, b int) bool {
return l[a].Latency < l[b].Latency
}
func (l relayList) Swap(a, b int) {
l[a], l[b] = l[b], l[a]
}

View File

@ -18,15 +18,15 @@ import (
type DummyClient struct { type DummyClient struct {
url *url.URL url *url.URL
lookups []protocol.DeviceID lookups []protocol.DeviceID
lookupRet []string lookupRet Announce
stops int stops int
statusRet bool statusRet bool
statusChecks int statusChecks int
} }
func (c *DummyClient) Lookup(device protocol.DeviceID) []string { func (c *DummyClient) Lookup(device protocol.DeviceID) (Announce, error) {
c.lookups = append(c.lookups, device) c.lookups = append(c.lookups, device)
return c.lookupRet return c.lookupRet, nil
} }
func (c *DummyClient) StatusOK() bool { func (c *DummyClient) StatusOK() bool {
@ -45,34 +45,58 @@ func (c *DummyClient) Address() string {
func TestGlobalDiscovery(t *testing.T) { func TestGlobalDiscovery(t *testing.T) {
c1 := &DummyClient{ c1 := &DummyClient{
statusRet: false, statusRet: false,
lookupRet: []string{"test.com:1234"}, lookupRet: Announce{
Magic: AnnouncementMagic,
This: Device{
ID: protocol.LocalDeviceID[:],
Addresses: []string{"test.com:1234"},
Relays: nil,
},
Extra: nil,
},
} }
c2 := &DummyClient{ c2 := &DummyClient{
statusRet: true, statusRet: true,
lookupRet: []string{}, lookupRet: Announce{
Magic: AnnouncementMagic,
This: Device{
ID: protocol.LocalDeviceID[:],
Addresses: nil,
Relays: nil,
},
Extra: nil,
},
} }
c3 := &DummyClient{ c3 := &DummyClient{
statusRet: true, statusRet: true,
lookupRet: []string{"best.com:2345"}, lookupRet: Announce{
Magic: AnnouncementMagic,
This: Device{
ID: protocol.LocalDeviceID[:],
Addresses: []string{"best.com:2345"},
Relays: nil,
},
Extra: nil,
},
} }
clients := []*DummyClient{c1, c2} clients := []*DummyClient{c1, c2}
Register("test1", func(uri *url.URL, pkt *Announce) (Client, error) { Register("test1", func(uri *url.URL, ann Announcer) (Client, error) {
c := clients[0] c := clients[0]
clients = clients[1:] clients = clients[1:]
c.url = uri c.url = uri
return c, nil return c, nil
}) })
Register("test2", func(uri *url.URL, pkt *Announce) (Client, error) { Register("test2", func(uri *url.URL, ann Announcer) (Client, error) {
c3.url = uri c3.url = uri
return c3, nil return c3, nil
}) })
d := NewDiscoverer(device, []string{}) d := NewDiscoverer(device, []string{}, nil)
d.localBcastStart = time.Time{} d.localBcastStart = time.Time{}
servers := []string{ servers := []string{
"test1://123.123.123.123:1234", "test1://123.123.123.123:1234",
@ -93,7 +117,7 @@ func TestGlobalDiscovery(t *testing.T) {
} }
} }
addrs := d.Lookup(device) addrs, _ := d.Lookup(device)
if len(addrs) != 2 { if len(addrs) != 2 {
t.Fatal("Wrong number of addresses", addrs) t.Fatal("Wrong number of addresses", addrs)
} }
@ -117,7 +141,7 @@ func TestGlobalDiscovery(t *testing.T) {
} }
} }
addrs = d.Lookup(device) addrs, _ = d.Lookup(device)
if len(addrs) != 2 { if len(addrs) != 2 {
t.Fatal("Wrong number of addresses", addrs) t.Fatal("Wrong number of addresses", addrs)
} }

View File

@ -10,8 +10,8 @@
package discover package discover
const ( const (
AnnouncementMagic = 0x9D79BC39 AnnouncementMagic = 0x9D79BC40
QueryMagic = 0x2CA856F5 QueryMagic = 0x2CA856F6
) )
type Query struct { type Query struct {
@ -25,12 +25,13 @@ type Announce struct {
Extra []Device // max:16 Extra []Device // max:16
} }
type Device struct { type Relay struct {
ID []byte // max:32 Address string // max:256
Addresses []Address // max:16 Latency int32
} }
type Address struct { type Device struct {
IP []byte // max:16 ID []byte // max:32
Port uint16 Addresses []string // max:16
Relays []Relay // max:16
} }

View File

@ -187,6 +187,80 @@ func (o *Announce) DecodeXDRFrom(xr *xdr.Reader) error {
/* /*
Relay Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Address (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Latency |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Relay {
string Address<256>;
int Latency;
}
*/
func (o Relay) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Relay) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Relay) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Relay) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Relay) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.Address); l > 256 {
return xw.Tot(), xdr.ElementSizeExceeded("Address", l, 256)
}
xw.WriteString(o.Address)
xw.WriteUint32(uint32(o.Latency))
return xw.Tot(), xw.Error()
}
func (o *Relay) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Relay) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Relay) DecodeXDRFrom(xr *xdr.Reader) error {
o.Address = xr.ReadStringMax(256)
o.Latency = int32(xr.ReadUint32())
return xr.Error()
}
/*
Device Structure: Device Structure:
0 1 2 3 0 1 2 3
@ -200,15 +274,24 @@ Device Structure:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Addresses | | Number of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ / / /
\ Zero or more Address Structures \ \ Addresses (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Relays |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Zero or more Relay Structures \
/ / / /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Device { struct Device {
opaque ID<32>; opaque ID<32>;
Address Addresses<16>; string Addresses<16>;
Relay Relays<16>;
} }
*/ */
@ -247,7 +330,14 @@ func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
} }
xw.WriteUint32(uint32(len(o.Addresses))) xw.WriteUint32(uint32(len(o.Addresses)))
for i := range o.Addresses { for i := range o.Addresses {
_, err := o.Addresses[i].EncodeXDRInto(xw) xw.WriteString(o.Addresses[i])
}
if l := len(o.Relays); l > 16 {
return xw.Tot(), xdr.ElementSizeExceeded("Relays", l, 16)
}
xw.WriteUint32(uint32(len(o.Relays)))
for i := range o.Relays {
_, err := o.Relays[i].EncodeXDRInto(xw)
if err != nil { if err != nil {
return xw.Tot(), err return xw.Tot(), err
} }
@ -275,83 +365,20 @@ func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
if _AddressesSize > 16 { if _AddressesSize > 16 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16) return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
} }
o.Addresses = make([]Address, _AddressesSize) o.Addresses = make([]string, _AddressesSize)
for i := range o.Addresses { for i := range o.Addresses {
(&o.Addresses[i]).DecodeXDRFrom(xr) o.Addresses[i] = xr.ReadString()
}
_RelaysSize := int(xr.ReadUint32())
if _RelaysSize < 0 {
return xdr.ElementSizeExceeded("Relays", _RelaysSize, 16)
}
if _RelaysSize > 16 {
return xdr.ElementSizeExceeded("Relays", _RelaysSize, 16)
}
o.Relays = make([]Relay, _RelaysSize)
for i := range o.Relays {
(&o.Relays[i]).DecodeXDRFrom(xr)
} }
return xr.Error() return xr.Error()
} }
/*
Address Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of IP |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ IP (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0000 | Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Address {
opaque IP<16>;
unsigned int Port;
}
*/
func (o Address) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
}
func (o Address) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o Address) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o Address) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o Address) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.IP); l > 16 {
return xw.Tot(), xdr.ElementSizeExceeded("IP", l, 16)
}
xw.WriteBytes(o.IP)
xw.WriteUint16(o.Port)
return xw.Tot(), xw.Error()
}
func (o *Address) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
}
func (o *Address) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
}
func (o *Address) DecodeXDRFrom(xr *xdr.Reader) error {
o.IP = xr.ReadBytesMax(16)
o.Port = xr.ReadUint16()
return xr.Error()
}

52
lib/model/connection.go Normal file
View File

@ -0,0 +1,52 @@
// 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 model
import (
"crypto/tls"
"net"
"github.com/syncthing/protocol"
)
type IntermediateConnection struct {
Conn *tls.Conn
ConnType ConnectionType
}
type Connection struct {
net.Conn
protocol.Connection
Type ConnectionType
}
const (
ConnectionTypeBasicAccept ConnectionType = iota
ConnectionTypeBasicDial
ConnectionTypeRelayAccept
ConnectionTypeRelayDial
)
type ConnectionType int
func (t ConnectionType) String() string {
switch t {
case ConnectionTypeBasicAccept:
return "basic-accept"
case ConnectionTypeBasicDial:
return "basic-dial"
case ConnectionTypeRelayAccept:
return "relay-accept"
case ConnectionTypeRelayDial:
return "relay-dial"
}
return "unknown"
}
func (t ConnectionType) IsDirect() bool {
return t == ConnectionTypeBasicAccept || t == ConnectionTypeBasicDial
}

View File

@ -87,10 +87,9 @@ type Model struct {
folderStatRefs map[string]*stats.FolderStatisticsReference // folder -> statsRef folderStatRefs map[string]*stats.FolderStatisticsReference // folder -> statsRef
fmut sync.RWMutex // protects the above fmut sync.RWMutex // protects the above
protoConn map[protocol.DeviceID]protocol.Connection conn map[protocol.DeviceID]Connection
rawConn map[protocol.DeviceID]io.Closer
deviceVer map[protocol.DeviceID]string deviceVer map[protocol.DeviceID]string
pmut sync.RWMutex // protects protoConn and rawConn pmut sync.RWMutex // protects conn and deviceVer
reqValidationCache map[string]time.Time // folder / file name => time when confirmed to exist reqValidationCache map[string]time.Time // folder / file name => time when confirmed to exist
rvmut sync.RWMutex // protects reqValidationCache rvmut sync.RWMutex // protects reqValidationCache
@ -130,8 +129,7 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
folderIgnores: make(map[string]*ignore.Matcher), folderIgnores: make(map[string]*ignore.Matcher),
folderRunners: make(map[string]service), folderRunners: make(map[string]service),
folderStatRefs: make(map[string]*stats.FolderStatisticsReference), folderStatRefs: make(map[string]*stats.FolderStatisticsReference),
protoConn: make(map[protocol.DeviceID]protocol.Connection), conn: make(map[protocol.DeviceID]Connection),
rawConn: make(map[protocol.DeviceID]io.Closer),
deviceVer: make(map[protocol.DeviceID]string), deviceVer: make(map[protocol.DeviceID]string),
reqValidationCache: make(map[string]time.Time), reqValidationCache: make(map[string]time.Time),
@ -221,6 +219,7 @@ type ConnectionInfo struct {
protocol.Statistics protocol.Statistics
Address string Address string
ClientVersion string ClientVersion string
Type ConnectionType
} }
func (info ConnectionInfo) MarshalJSON() ([]byte, error) { func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
@ -229,6 +228,7 @@ func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
"inBytesTotal": info.InBytesTotal, "inBytesTotal": info.InBytesTotal,
"outBytesTotal": info.OutBytesTotal, "outBytesTotal": info.OutBytesTotal,
"address": info.Address, "address": info.Address,
"type": info.Type.String(),
"clientVersion": info.ClientVersion, "clientVersion": info.ClientVersion,
}) })
} }
@ -243,14 +243,15 @@ func (m *Model) ConnectionStats() map[string]interface{} {
m.fmut.RLock() m.fmut.RLock()
var res = make(map[string]interface{}) var res = make(map[string]interface{})
conns := make(map[string]ConnectionInfo, len(m.protoConn)) conns := make(map[string]ConnectionInfo, len(m.conn))
for device, conn := range m.protoConn { for device, conn := range m.conn {
ci := ConnectionInfo{ ci := ConnectionInfo{
Statistics: conn.Statistics(), Statistics: conn.Statistics(),
ClientVersion: m.deviceVer[device], ClientVersion: m.deviceVer[device],
} }
if nc, ok := m.rawConn[device].(remoteAddrer); ok { if addr := m.conn[device].RemoteAddr(); addr != nil {
ci.Address = nc.RemoteAddr().String() ci.Address = addr.String()
ci.Type = conn.Type
} }
conns[device.String()] = ci conns[device.String()] = ci
@ -586,8 +587,12 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
"clientVersion": cm.ClientVersion, "clientVersion": cm.ClientVersion,
} }
if conn, ok := m.rawConn[deviceID].(*tls.Conn); ok { if conn, ok := m.conn[deviceID]; ok {
event["addr"] = conn.RemoteAddr().String() event["type"] = conn.Type.String()
addr := conn.RemoteAddr()
if addr != nil {
event["addr"] = addr.String()
}
} }
m.pmut.Unlock() m.pmut.Unlock()
@ -693,12 +698,11 @@ func (m *Model) Close(device protocol.DeviceID, err error) {
} }
m.fmut.RUnlock() m.fmut.RUnlock()
conn, ok := m.rawConn[device] conn, ok := m.conn[device]
if ok { if ok {
closeRawConn(conn) closeRawConn(conn)
} }
delete(m.protoConn, device) delete(m.conn, device)
delete(m.rawConn, device)
delete(m.deviceVer, device) delete(m.deviceVer, device)
m.pmut.Unlock() m.pmut.Unlock()
} }
@ -860,7 +864,7 @@ func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) {
// ConnectedTo returns true if we are connected to the named device. // ConnectedTo returns true if we are connected to the named device.
func (m *Model) ConnectedTo(deviceID protocol.DeviceID) bool { func (m *Model) ConnectedTo(deviceID protocol.DeviceID) bool {
m.pmut.RLock() m.pmut.RLock()
_, ok := m.protoConn[deviceID] _, ok := m.conn[deviceID]
m.pmut.RUnlock() m.pmut.RUnlock()
if ok { if ok {
m.deviceWasSeen(deviceID) m.deviceWasSeen(deviceID)
@ -927,28 +931,24 @@ func (m *Model) SetIgnores(folder string, content []string) error {
// AddConnection adds a new peer connection to the model. An initial index will // AddConnection adds a new peer connection to the model. An initial index will
// be sent to the connected peer, thereafter index updates whenever the local // be sent to the connected peer, thereafter index updates whenever the local
// folder changes. // folder changes.
func (m *Model) AddConnection(rawConn io.Closer, protoConn protocol.Connection) { func (m *Model) AddConnection(conn Connection) {
deviceID := protoConn.ID() deviceID := conn.ID()
m.pmut.Lock() m.pmut.Lock()
if _, ok := m.protoConn[deviceID]; ok { if _, ok := m.conn[deviceID]; ok {
panic("add existing device") panic("add existing device")
} }
m.protoConn[deviceID] = protoConn m.conn[deviceID] = conn
if _, ok := m.rawConn[deviceID]; ok {
panic("add existing device")
}
m.rawConn[deviceID] = rawConn
protoConn.Start() conn.Start()
cm := m.clusterConfig(deviceID) cm := m.clusterConfig(deviceID)
protoConn.ClusterConfig(cm) conn.ClusterConfig(cm)
m.fmut.RLock() m.fmut.RLock()
for _, folder := range m.deviceFolders[deviceID] { for _, folder := range m.deviceFolders[deviceID] {
fs := m.folderFiles[folder] fs := m.folderFiles[folder]
go sendIndexes(protoConn, folder, fs, m.folderIgnores[folder]) go sendIndexes(conn, folder, fs, m.folderIgnores[folder])
} }
m.fmut.RUnlock() m.fmut.RUnlock()
m.pmut.Unlock() m.pmut.Unlock()
@ -1114,7 +1114,7 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, flags uint32, options []protocol.Option) ([]byte, error) { func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, flags uint32, options []protocol.Option) ([]byte, error) {
m.pmut.RLock() m.pmut.RLock()
nc, ok := m.protoConn[deviceID] nc, ok := m.conn[deviceID]
m.pmut.RUnlock() m.pmut.RUnlock()
if !ok { if !ok {
@ -1640,7 +1640,7 @@ func (m *Model) Availability(folder, file string) []protocol.DeviceID {
availableDevices := []protocol.DeviceID{} availableDevices := []protocol.DeviceID{}
for _, device := range fs.Availability(file) { for _, device := range fs.Availability(file) {
_, ok := m.protoConn[device] _, ok := m.conn[device]
if ok { if ok {
availableDevices = append(availableDevices, device) availableDevices = append(availableDevices, device)
} }
@ -1764,7 +1764,7 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
// folder. // folder.
m.pmut.Lock() m.pmut.Lock()
for _, dev := range cfg.DeviceIDs() { for _, dev := range cfg.DeviceIDs() {
if conn, ok := m.rawConn[dev]; ok { if conn, ok := m.conn[dev]; ok {
closeRawConn(conn) closeRawConn(conn)
} }
} }
@ -1812,7 +1812,7 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
// disconnect it so that we start sharing the folder with it. // disconnect it so that we start sharing the folder with it.
// We close the underlying connection and let the normal error // We close the underlying connection and let the normal error
// handling kick in to clean up and reconnect. // handling kick in to clean up and reconnect.
if conn, ok := m.rawConn[dev]; ok { if conn, ok := m.conn[dev]; ok {
closeRawConn(conn) closeRawConn(conn)
} }

View File

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"net"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -281,7 +282,11 @@ func BenchmarkRequest(b *testing.B) {
id: device1, id: device1,
requestData: []byte("some data to return"), requestData: []byte("some data to return"),
} }
m.AddConnection(fc, fc) m.AddConnection(Connection{
&net.TCPConn{},
fc,
ConnectionTypeBasicAccept,
})
m.Index(device1, "default", files, 0, nil) m.Index(device1, "default", files, 0, nil)
b.ResetTimer() b.ResetTimer()
@ -314,6 +319,18 @@ func TestDeviceRename(t *testing.T) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil) db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db) m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
fc := FakeConnection{
id: device1,
requestData: []byte("some data to return"),
}
m.AddConnection(Connection{
&net.TCPConn{},
fc,
ConnectionTypeBasicAccept,
})
m.ServeBackground() m.ServeBackground()
if cfg.Devices()[device1].Name != "" { if cfg.Devices()[device1].Name != "" {
t.Errorf("Device already has a name") t.Errorf("Device already has a name")

View File

@ -11,10 +11,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"time"
"github.com/calmh/du" "github.com/calmh/du"
"github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/sync"
@ -221,3 +223,21 @@ func DiskFreePercentage(path string) (freePct float64, err error) {
u, err := du.Get(path) u, err := du.Get(path)
return (float64(u.FreeBytes) / float64(u.TotalBytes)) * 100, err return (float64(u.FreeBytes) / float64(u.TotalBytes)) * 100, err
} }
// SetTCPOptions sets syncthings default TCP options on a TCP connection
func SetTCPOptions(conn *net.TCPConn) error {
var err error
if err = conn.SetLinger(0); err != nil {
return err
}
if err = conn.SetNoDelay(false); err != nil {
return err
}
if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
return err
}
if err = conn.SetKeepAlive(true); err != nil {
return err
}
return nil
}

27
lib/osutil/ping.go Normal file
View File

@ -0,0 +1,27 @@
// 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 osutil
import (
"net"
"time"
)
// TCPPing returns the duration required to establish a TCP connection
// to the given host. ICMP packets require root priviledges, hence why we use
// tcp.
func TCPPing(address string) (time.Duration, error) {
dialer := net.Dialer{
Deadline: time.Now().Add(time.Second),
}
start := time.Now()
conn, err := dialer.Dial("tcp", address)
if conn != nil {
conn.Close()
}
return time.Since(start), err
}

19
lib/relay/debug.go Normal file
View File

@ -0,0 +1,19 @@
// 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 (
"os"
"strings"
"github.com/calmh/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "relay") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
)

250
lib/relay/relay.go Normal file
View File

@ -0,0 +1,250 @@
// 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"
"encoding/json"
"net"
"net/http"
"net/url"
"time"
"github.com/syncthing/relaysrv/client"
"github.com/syncthing/relaysrv/protocol"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture"
)
func NewSvc(cfg *config.Wrapper, tlsCfg *tls.Config, conns chan<- model.IntermediateConnection) *Svc {
svc := &Svc{
Supervisor: suture.New("Svc", suture.Spec{
Log: func(log string) {
if debug {
l.Infoln(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.ProtocolClient),
mut: sync.NewRWMutex(),
invitations: make(chan protocol.SessionInvitation),
}
rcfg := cfg.Raw()
svc.CommitConfiguration(rcfg, rcfg)
cfg.Subscribe(svc)
receiver := &invitationReceiver{
tlsCfg: tlsCfg,
conns: conns,
invitations: svc.invitations,
}
svc.receiverToken = svc.Add(receiver)
return svc
}
type Svc struct {
*suture.Supervisor
cfg *config.Wrapper
tlsCfg *tls.Config
receiverToken suture.ServiceToken
tokens map[string]suture.ServiceToken
clients map[string]*client.ProtocolClient
mut sync.RWMutex
invitations chan protocol.SessionInvitation
}
func (s *Svc) 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 *Svc) 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 {
if debug {
l.Debugln("Failed to parse relay address", addr, err)
}
continue
}
existing[uri.String()] = uri
}
// Expand dynamic addresses into a set of relays
for key, uri := range existing {
if uri.Scheme != "dynamic+http" && uri.Scheme != "dynamic+https" {
continue
}
delete(existing, key)
uri.Scheme = uri.Scheme[8:]
data, err := http.Get(uri.String())
if err != nil {
if debug {
l.Debugln("Failed to lookup dynamic relays", err)
}
continue
}
var ann dynamicAnnouncement
err = json.NewDecoder(data.Body).Decode(&ann)
data.Body.Close()
if err != nil {
if debug {
l.Debugln("Failed to lookup dynamic relays", err)
}
continue
}
for _, relayAnn := range ann.Relays {
ruri, err := url.Parse(relayAnn.URL)
if err != nil {
if debug {
l.Debugln("Failed to parse dynamic relay address", relayAnn.URL, err)
}
continue
}
if debug {
l.Debugln("Found", ruri, "via", uri)
}
existing[ruri.String()] = ruri
}
}
for key, uri := range existing {
_, ok := s.tokens[key]
if !ok {
if debug {
l.Debugln("Connecting to relay", uri)
}
c := client.NewProtocolClient(uri, s.tlsCfg.Certificates, s.invitations)
s.tokens[key] = s.Add(c)
s.mut.Lock()
s.clients[key] = c
s.mut.Unlock()
}
}
for key, token := range s.tokens {
_, ok := existing[key]
if !ok {
err := s.Remove(token)
delete(s.tokens, key)
s.mut.Lock()
delete(s.clients, key)
s.mut.Unlock()
if debug {
l.Debugln("Disconnecting from relay", key, err)
}
}
}
return true
}
func (s *Svc) ClientStatus() map[string]bool {
s.mut.RLock()
status := make(map[string]bool, len(s.clients))
for uri, client := range s.clients {
status[uri] = client.StatusOK()
}
s.mut.RUnlock()
return status
}
type invitationReceiver struct {
invitations chan protocol.SessionInvitation
tlsCfg *tls.Config
conns chan<- model.IntermediateConnection
stop chan struct{}
}
func (r *invitationReceiver) Serve() {
if r.stop != nil {
return
}
r.stop = make(chan struct{})
for {
select {
case inv := <-r.invitations:
if debug {
l.Debugln("Received relay invitation", inv)
}
conn, err := client.JoinSession(inv)
if err != nil {
if debug {
l.Debugf("Failed to join relay session %s: %v", inv, err)
}
continue
}
err = osutil.SetTCPOptions(conn.(*net.TCPConn))
if err != nil {
l.Infoln(err)
}
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 <- model.IntermediateConnection{
tc, model.ConnectionTypeRelayAccept,
}
case <-r.stop:
return
}
}
}
func (r *invitationReceiver) Stop() {
if r.stop == nil {
return
}
r.stop <- struct{}{}
r.stop = nil
}
type dynamicAnnouncement struct {
Relays []relayAnnouncement `json:"relays"`
}
type relayAnnouncement struct {
URL string `json:"url"`
}