Expose connection type and relay status in the UI

This commit is contained in:
Audrius Butkevicius 2015-07-17 21:22:07 +01:00
parent 2c0f8dc546
commit 8f2db99c86
15 changed files with 183 additions and 123 deletions

View File

@ -16,17 +16,17 @@ import (
"time" "time"
"github.com/syncthing/protocol" "github.com/syncthing/protocol"
"github.com/syncthing/relaysrv/client" "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 DialerFactory func(*url.URL, *tls.Config) (*tls.Conn, error)
type ListenerFactory func(*url.URL, *tls.Config, chan<- intermediateConnection) type ListenerFactory func(*url.URL, *tls.Config, chan<- model.IntermediateConnection)
var ( var (
dialers = make(map[string]DialerFactory, 0) dialers = make(map[string]DialerFactory, 0)
@ -41,7 +41,7 @@ type connectionSvc struct {
myID protocol.DeviceID myID protocol.DeviceID
model *model.Model model *model.Model
tlsCfg *tls.Config tlsCfg *tls.Config
conns chan intermediateConnection conns chan model.IntermediateConnection
lastRelayCheck map[protocol.DeviceID]time.Time lastRelayCheck map[protocol.DeviceID]time.Time
@ -49,11 +49,6 @@ type connectionSvc struct {
connType map[protocol.DeviceID]model.ConnectionType connType map[protocol.DeviceID]model.ConnectionType
} }
type intermediateConnection struct {
conn *tls.Conn
connType model.ConnectionType
}
func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *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"),
@ -61,7 +56,7 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Mo
myID: myID, myID: myID,
model: mdl, model: mdl,
tlsCfg: tlsCfg, tlsCfg: tlsCfg,
conns: make(chan intermediateConnection), conns: make(chan model.IntermediateConnection),
connType: make(map[protocol.DeviceID]model.ConnectionType), connType: make(map[protocol.DeviceID]model.ConnectionType),
lastRelayCheck: make(map[protocol.DeviceID]time.Time), lastRelayCheck: make(map[protocol.DeviceID]time.Time),
@ -110,14 +105,14 @@ func newConnectionSvc(cfg *config.Wrapper, myID protocol.DeviceID, mdl *model.Mo
func (s *connectionSvc) handle() { func (s *connectionSvc) handle() {
next: next:
for c := range s.conns { for c := range s.conns {
cs := c.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", c.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
@ -125,8 +120,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, c.conn.RemoteAddr()) l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, c.Conn.RemoteAddr())
c.conn.Close() c.Conn.Close()
continue continue
} }
remoteCert := certs[0] remoteCert := certs[0]
@ -137,7 +132,7 @@ 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)
c.conn.Close() c.Conn.Close()
continue continue
} }
@ -146,7 +141,7 @@ next:
s.mut.RLock() s.mut.RLock()
ct, ok := s.connType[remoteID] ct, ok := s.connType[remoteID]
s.mut.RUnlock() s.mut.RUnlock()
if ok && !ct.IsDirect() && c.connType.IsDirect() { if ok && !ct.IsDirect() && c.ConnType.IsDirect() {
if debugNet { if debugNet {
l.Debugln("Switching connections", remoteID) l.Debugln("Switching connections", remoteID)
} }
@ -159,7 +154,7 @@ next:
// in parallel we don't want to do that or we end up with no // in parallel we don't want to do that or we end up with no
// connections still established... // connections still established...
l.Infof("Connected to already connected device (%s)", remoteID) l.Infof("Connected to already connected device (%s)", remoteID)
c.conn.Close() c.Conn.Close()
continue continue
} }
@ -177,41 +172,41 @@ 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, c.conn.RemoteAddr(), err) l.Warnf("Bad certificate from %s (%v): %v", remoteID, c.Conn.RemoteAddr(), err)
c.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(c.conn.RemoteAddr()) limit := s.shouldLimit(c.Conn.RemoteAddr())
wr := io.Writer(c.conn) wr := io.Writer(c.Conn)
if limit && writeRateLimit != nil { if limit && writeRateLimit != nil {
wr = &limitedWriter{c.conn, writeRateLimit} wr = &limitedWriter{c.Conn, writeRateLimit}
} }
rd := io.Reader(c.conn) rd := io.Reader(c.Conn)
if limit && readRateLimit != nil { if limit && readRateLimit != nil {
rd = &limitedReader{c.conn, readRateLimit} rd = &limitedReader{c.Conn, readRateLimit}
} }
name := fmt.Sprintf("%s-%s (%s)", c.conn.LocalAddr(), c.conn.RemoteAddr(), c.connType) 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", c.conn.ConnectionState().CipherSuite, !limit) l.Debugf("cipher suite: %04X in lan: %t", c.Conn.ConnectionState().CipherSuite, !limit)
} }
s.model.AddConnection(model.Connection{ s.model.AddConnection(model.Connection{
c.conn, c.Conn,
protoConn, protoConn,
c.connType, c.ConnType,
}) })
s.mut.Lock() s.mut.Lock()
s.connType[remoteID] = c.connType s.connType[remoteID] = c.ConnType
s.mut.Unlock() s.mut.Unlock()
continue next continue next
} }
@ -220,14 +215,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": c.conn.RemoteAddr().String(), "address": c.Conn.RemoteAddr().String(),
}) })
l.Infof("Connection from %s (%s) with unknown device ID %s", c.conn.RemoteAddr(), c.connType, 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 (%s) with ignored device ID %s", c.conn.RemoteAddr(), c.connType, remoteID) l.Infof("Connection from %s (%s) with ignored device ID %s", c.Conn.RemoteAddr(), c.ConnType, remoteID)
} }
c.conn.Close() c.Conn.Close()
} }
} }
@ -294,7 +289,7 @@ func (s *connectionSvc) connect() {
s.model.Close(deviceID, fmt.Errorf("switching connections")) s.model.Close(deviceID, fmt.Errorf("switching connections"))
} }
s.conns <- intermediateConnection{ s.conns <- model.IntermediateConnection{
conn, model.ConnectionTypeBasicDial, conn, model.ConnectionTypeBasicDial,
} }
continue nextDevice continue nextDevice
@ -347,7 +342,10 @@ func (s *connectionSvc) connect() {
l.Debugln("Sucessfully joined relay session", inv) l.Debugln("Sucessfully joined relay session", inv)
} }
setTCPOptions(conn.(*net.TCPConn)) err = osutil.SetTCPOptions(conn.(*net.TCPConn))
if err != nil {
l.Infoln(err)
}
var tc *tls.Conn var tc *tls.Conn
@ -362,7 +360,7 @@ func (s *connectionSvc) connect() {
tc.Close() tc.Close()
continue continue
} }
s.conns <- intermediateConnection{ s.conns <- model.IntermediateConnection{
tc, model.ConnectionTypeRelayDial, tc, model.ConnectionTypeRelayDial,
} }
continue nextDevice continue nextDevice
@ -414,19 +412,3 @@ func (s *connectionSvc) CommitConfiguration(from, to config.Configuration) bool
return true return true
} }
func 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)
}
}

View File

@ -13,6 +13,7 @@ import (
"strings" "strings"
"github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
) )
func init() { func init() {
@ -46,7 +47,10 @@ func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) {
return nil, err return nil, err
} }
setTCPOptions(conn) err = osutil.SetTCPOptions(conn)
if err != nil {
l.Infoln(err)
}
tc := tls.Client(conn, tlsCfg) tc := tls.Client(conn, tlsCfg)
err = tc.Handshake() err = tc.Handshake()
@ -58,7 +62,7 @@ func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) {
return tc, nil return tc, nil
} }
func tcpListener(uri *url.URL, tlsCfg *tls.Config, conns chan<- intermediateConnection) { func tcpListener(uri *url.URL, tlsCfg *tls.Config, conns chan<- model.IntermediateConnection) {
tcaddr, err := net.ResolveTCPAddr("tcp", uri.Host) tcaddr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil { if err != nil {
l.Fatalln("listen (BEP/tcp):", err) l.Fatalln("listen (BEP/tcp):", err)
@ -81,8 +85,10 @@ func tcpListener(uri *url.URL, tlsCfg *tls.Config, conns chan<- intermediateConn
l.Debugln("connect from", conn.RemoteAddr()) l.Debugln("connect from", conn.RemoteAddr())
} }
tcpConn := conn.(*net.TCPConn) err = osutil.SetTCPOptions(conn.(*net.TCPConn))
setTCPOptions(tcpConn) if err != nil {
l.Infoln(err)
}
tc := tls.Server(conn, tlsCfg) tc := tls.Server(conn, tlsCfg)
err = tc.Handshake() err = tc.Handshake()
@ -92,7 +98,7 @@ func tcpListener(uri *url.URL, tlsCfg *tls.Config, conns chan<- intermediateConn
continue continue
} }
conns <- intermediateConnection{ conns <- model.IntermediateConnection{
tc, model.ConnectionTypeBasicAccept, 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
) )
@ -671,14 +674,14 @@ func syncthingMain() {
// Start the relevant services // Start the relevant services
connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg) connectionSvc := newConnectionSvc(cfg, myID, m, tlsCfg)
relaySvc := newRelaySvc(cfg, tlsCfg, connectionSvc.conns) relaySvc = relay.NewSvc(cfg, tlsCfg, connectionSvc.conns)
connectionSvc.Add(relaySvc) connectionSvc.Add(relaySvc)
mainSvc.Add(connectionSvc) mainSvc.Add(connectionSvc)
// 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.
@ -908,10 +911,9 @@ 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, opts.RelayServers) 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)

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);

File diff suppressed because one or more lines are too long

View File

@ -22,13 +22,14 @@ import (
"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/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
relays []Relay relaySvc *relay.Svc
localBcastIntv time.Duration localBcastIntv time.Duration
localBcastStart time.Time localBcastStart time.Time
cacheLifetime time.Duration cacheLifetime time.Duration
@ -56,11 +57,11 @@ var (
ErrIncorrectMagic = errors.New("incorrect magic number") ErrIncorrectMagic = errors.New("incorrect magic number")
) )
func NewDiscoverer(id protocol.DeviceID, addresses []string, relayAdresses []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,
relays: measureLatency(relayAdresses), relaySvc: relaySvc,
localBcastIntv: 30 * time.Second, localBcastIntv: 30 * time.Second,
cacheLifetime: 5 * time.Minute, cacheLifetime: 5 * time.Minute,
negCacheCutoff: 3 * time.Minute, negCacheCutoff: 3 * time.Minute,
@ -143,7 +144,7 @@ func (d *Discoverer) StartGlobal(servers []string, extPort uint16) {
} }
d.extPort = extPort d.extPort = extPort
pkt := d.announcementPkt() pkt := d.announcementPkt(true)
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 {
@ -317,49 +318,32 @@ func (d *Discoverer) All() map[protocol.DeviceID][]CacheEntry {
return devices return devices
} }
func (d *Discoverer) announcementPkt() *Announce { func (d *Discoverer) announcementPkt(allowExternal bool) *Announce {
var addrs []string var addrs []string
if d.extPort != 0 { if d.extPort != 0 && allowExternal {
addrs = []string{fmt.Sprintf("tcp://:%d", d.extPort)} addrs = []string{fmt.Sprintf("tcp://:%d", d.extPort)}
} else { } else {
for _, aurl := range d.listenAddrs { addrs = resolveAddrs(d.listenAddrs)
uri, err := url.Parse(aurl) }
if err != nil {
if debug { relayAddrs := make([]string, 0)
l.Debugf("discovery: failed to parse listen address %s: %s", aurl, err) if d.relaySvc != nil {
} status := d.relaySvc.ClientStatus()
continue for uri, ok := range status {
if ok {
relayAddrs = append(relayAddrs, uri)
} }
addr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
l.Warnln("discover: %v: not announcing %s", err, aurl)
continue
} else if debug {
l.Debugf("discover: resolved %s as %#v", aurl, uri.Host)
}
if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
uri.Host = fmt.Sprintf(":%d", addr.Port)
} else if bs := addr.IP.To4(); bs != nil {
uri.Host = fmt.Sprintf("%s:%d", bs.String(), addr.Port)
} else if bs := addr.IP.To16(); bs != nil {
uri.Host = fmt.Sprintf("[%s]:%d", bs.String(), addr.Port)
}
addrs = append(addrs, uri.String())
} }
} }
return &Announce{ return &Announce{
Magic: AnnouncementMagic, Magic: AnnouncementMagic,
This: Device{d.myID[:], addrs, d.relays}, 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, d.relays},
}
msg := pkt.MustMarshalXDR() msg := pkt.MustMarshalXDR()
for { for {

View File

@ -7,11 +7,17 @@
package model package model
import ( import (
"crypto/tls"
"net" "net"
"github.com/syncthing/protocol" "github.com/syncthing/protocol"
) )
type IntermediateConnection struct {
Conn *tls.Conn
ConnType ConnectionType
}
type Connection struct { type Connection struct {
net.Conn net.Conn
protocol.Connection protocol.Connection

View File

@ -219,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) {
@ -227,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,
}) })
} }
@ -249,6 +251,7 @@ func (m *Model) ConnectionStats() map[string]interface{} {
} }
if addr := m.conn[device].RemoteAddr(); addr != nil { if addr := m.conn[device].RemoteAddr(); addr != nil {
ci.Address = addr.String() ci.Address = addr.String()
ci.Type = conn.Type
} }
conns[device.String()] = ci conns[device.String()] = ci
@ -585,6 +588,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
} }
if conn, ok := m.conn[deviceID]; ok { if conn, ok := m.conn[deviceID]; ok {
event["type"] = conn.Type.String()
addr := conn.RemoteAddr() addr := conn.RemoteAddr()
if addr != nil { if addr != nil {
event["addr"] = addr.String() event["addr"] = addr.String()

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
}

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
)

View File

@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // 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/. // You can obtain one at http://mozilla.org/MPL/2.0/.
package main package relay
import ( import (
"crypto/tls" "crypto/tls"
@ -16,16 +16,17 @@ import (
"github.com/syncthing/relaysrv/protocol" "github.com/syncthing/relaysrv/protocol"
"github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture" "github.com/thejerf/suture"
) )
func newRelaySvc(cfg *config.Wrapper, tlsCfg *tls.Config, conns chan<- intermediateConnection) *relaySvc { func NewSvc(cfg *config.Wrapper, tlsCfg *tls.Config, conns chan<- model.IntermediateConnection) *Svc {
svc := &relaySvc{ svc := &Svc{
Supervisor: suture.New("relaySvc", suture.Spec{ Supervisor: suture.New("Svc", suture.Spec{
Log: func(log string) { Log: func(log string) {
if debugNet { if debug {
l.Infoln(log) l.Infoln(log)
} }
}, },
@ -58,7 +59,7 @@ func newRelaySvc(cfg *config.Wrapper, tlsCfg *tls.Config, conns chan<- intermedi
return svc return svc
} }
type relaySvc struct { type Svc struct {
*suture.Supervisor *suture.Supervisor
cfg *config.Wrapper cfg *config.Wrapper
tlsCfg *tls.Config tlsCfg *tls.Config
@ -70,7 +71,7 @@ type relaySvc struct {
invitations chan protocol.SessionInvitation invitations chan protocol.SessionInvitation
} }
func (s *relaySvc) VerifyConfiguration(from, to config.Configuration) error { func (s *Svc) VerifyConfiguration(from, to config.Configuration) error {
for _, addr := range to.Options.RelayServers { for _, addr := range to.Options.RelayServers {
_, err := url.Parse(addr) _, err := url.Parse(addr)
if err != nil { if err != nil {
@ -80,12 +81,12 @@ func (s *relaySvc) VerifyConfiguration(from, to config.Configuration) error {
return nil return nil
} }
func (s *relaySvc) CommitConfiguration(from, to config.Configuration) bool { func (s *Svc) CommitConfiguration(from, to config.Configuration) bool {
existing := make(map[string]struct{}, len(to.Options.RelayServers)) existing := make(map[string]struct{}, len(to.Options.RelayServers))
for _, addr := range to.Options.RelayServers { for _, addr := range to.Options.RelayServers {
uri, err := url.Parse(addr) uri, err := url.Parse(addr)
if err != nil { if err != nil {
if debugNet { if debug {
l.Debugln("Failed to parse relay address", addr, err) l.Debugln("Failed to parse relay address", addr, err)
} }
continue continue
@ -95,7 +96,7 @@ func (s *relaySvc) CommitConfiguration(from, to config.Configuration) bool {
_, ok := s.tokens[uri.String()] _, ok := s.tokens[uri.String()]
if !ok { if !ok {
if debugNet { if debug {
l.Debugln("Connecting to relay", uri) l.Debugln("Connecting to relay", uri)
} }
c := client.NewProtocolClient(uri, s.tlsCfg.Certificates, s.invitations) c := client.NewProtocolClient(uri, s.tlsCfg.Certificates, s.invitations)
@ -114,7 +115,7 @@ func (s *relaySvc) CommitConfiguration(from, to config.Configuration) bool {
s.mut.Lock() s.mut.Lock()
delete(s.clients, uri) delete(s.clients, uri)
s.mut.Unlock() s.mut.Unlock()
if debugNet { if debug {
l.Debugln("Disconnecting from relay", uri, err) l.Debugln("Disconnecting from relay", uri, err)
} }
} }
@ -123,7 +124,7 @@ func (s *relaySvc) CommitConfiguration(from, to config.Configuration) bool {
return true return true
} }
func (s *relaySvc) ClientStatus() map[string]bool { func (s *Svc) ClientStatus() map[string]bool {
s.mut.RLock() s.mut.RLock()
status := make(map[string]bool, len(s.clients)) status := make(map[string]bool, len(s.clients))
for uri, client := range s.clients { for uri, client := range s.clients {
@ -136,7 +137,7 @@ func (s *relaySvc) ClientStatus() map[string]bool {
type invitationReceiver struct { type invitationReceiver struct {
invitations chan protocol.SessionInvitation invitations chan protocol.SessionInvitation
tlsCfg *tls.Config tlsCfg *tls.Config
conns chan<- intermediateConnection conns chan<- model.IntermediateConnection
stop chan struct{} stop chan struct{}
} }
@ -149,18 +150,21 @@ func (r *invitationReceiver) Serve() {
for { for {
select { select {
case inv := <-r.invitations: case inv := <-r.invitations:
if debugNet { if debug {
l.Debugln("Received relay invitation", inv) l.Debugln("Received relay invitation", inv)
} }
conn, err := client.JoinSession(inv) conn, err := client.JoinSession(inv)
if err != nil { if err != nil {
if debugNet { if debug {
l.Debugf("Failed to join relay session %s: %v", inv, err) l.Debugf("Failed to join relay session %s: %v", inv, err)
} }
continue continue
} }
setTCPOptions(conn.(*net.TCPConn)) err = osutil.SetTCPOptions(conn.(*net.TCPConn))
if err != nil {
l.Infoln(err)
}
var tc *tls.Conn var tc *tls.Conn
@ -175,7 +179,7 @@ func (r *invitationReceiver) Serve() {
tc.Close() tc.Close()
continue continue
} }
r.conns <- intermediateConnection{ r.conns <- model.IntermediateConnection{
tc, model.ConnectionTypeRelayAccept, tc, model.ConnectionTypeRelayAccept,
} }
case <-r.stop: case <-r.stop: