* lib/tlsutil: Enable TLS 1.3 when available, on test builds (fixes #5065) This enables TLS 1.3 negotiation on Go 1.12 by setting the GODEBUG variable. For now, this just gets enabled on test versions (those with a dash in the version number). Users wishing to enable this on production builds can set GODEBUG manually. The string representation of connections now includes the TLS version and cipher suite. This becomes part of the log output on connections. That is, when talking to an old client: Established secure connection .../TLS1.2-TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 and now potentially: Established secure connection .../TLS1.3-TLS_AES_128_GCM_SHA256 (The cipher suite was there previously in the log output, but not the TLS version.) I also added this info as a new Crypto() method on the connection, and propagate this out to the API and GUI, where it can be seen in the connection address hover (although with bad word wrapping sometimes). * wip * wip
214 lines
5.2 KiB
Go
214 lines
5.2 KiB
Go
// Copyright (C) 2016 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 https://mozilla.org/MPL/2.0/.
|
|
|
|
package connections
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/syncthing/syncthing/lib/config"
|
|
"github.com/syncthing/syncthing/lib/nat"
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
)
|
|
|
|
// Connection is what we expose to the outside. It is a protocol.Connection
|
|
// that can be closed and has some metadata.
|
|
type Connection interface {
|
|
protocol.Connection
|
|
Type() string
|
|
Transport() string
|
|
RemoteAddr() net.Addr
|
|
Priority() int
|
|
String() string
|
|
Crypto() string
|
|
}
|
|
|
|
// completeConn is the aggregation of an internalConn and the
|
|
// protocol.Connection running on top of it. It implements the Connection
|
|
// interface.
|
|
type completeConn struct {
|
|
internalConn
|
|
protocol.Connection
|
|
}
|
|
|
|
func (c completeConn) Close(err error) {
|
|
c.Connection.Close(err)
|
|
c.internalConn.Close()
|
|
}
|
|
|
|
// internalConn is the raw TLS connection plus some metadata on where it
|
|
// came from (type, priority).
|
|
type internalConn struct {
|
|
*tls.Conn
|
|
connType connType
|
|
priority int
|
|
}
|
|
|
|
type connType int
|
|
|
|
const (
|
|
connTypeRelayClient connType = iota
|
|
connTypeRelayServer
|
|
connTypeTCPClient
|
|
connTypeTCPServer
|
|
)
|
|
|
|
func (t connType) String() string {
|
|
switch t {
|
|
case connTypeRelayClient:
|
|
return "relay-client"
|
|
case connTypeRelayServer:
|
|
return "relay-server"
|
|
case connTypeTCPClient:
|
|
return "tcp-client"
|
|
case connTypeTCPServer:
|
|
return "tcp-server"
|
|
default:
|
|
return "unknown-type"
|
|
}
|
|
}
|
|
|
|
func (t connType) Transport() string {
|
|
switch t {
|
|
case connTypeRelayClient, connTypeRelayServer:
|
|
return "relay"
|
|
case connTypeTCPClient, connTypeTCPServer:
|
|
return "tcp"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func (c internalConn) Close() {
|
|
// *tls.Conn.Close() does more than it says on the tin. Specifically, it
|
|
// sends a TLS alert message, which might block forever if the
|
|
// connection is dead and we don't have a deadline set.
|
|
c.SetWriteDeadline(time.Now().Add(250 * time.Millisecond))
|
|
c.Conn.Close()
|
|
}
|
|
|
|
func (c internalConn) Type() string {
|
|
return c.connType.String()
|
|
}
|
|
|
|
func (c internalConn) Priority() int {
|
|
return c.priority
|
|
}
|
|
|
|
func (c internalConn) Crypto() string {
|
|
cs := c.ConnectionState()
|
|
return fmt.Sprintf("%s-%s", tlsVersionNames[cs.Version], tlsCipherSuiteNames[cs.CipherSuite])
|
|
}
|
|
|
|
func (c internalConn) Transport() string {
|
|
transport := c.connType.Transport()
|
|
host, _, err := net.SplitHostPort(c.LocalAddr().String())
|
|
if err != nil {
|
|
return transport
|
|
}
|
|
ip := net.ParseIP(host)
|
|
if ip == nil {
|
|
return transport
|
|
}
|
|
if ip.To4() != nil {
|
|
return transport + "4"
|
|
}
|
|
return transport + "6"
|
|
}
|
|
|
|
func (c internalConn) String() string {
|
|
return fmt.Sprintf("%s-%s/%s/%s", c.LocalAddr(), c.RemoteAddr(), c.Type(), c.Crypto())
|
|
}
|
|
|
|
type dialerFactory interface {
|
|
New(config.Wrapper, *tls.Config) genericDialer
|
|
Priority() int
|
|
AlwaysWAN() bool
|
|
Valid(config.Configuration) error
|
|
String() string
|
|
}
|
|
|
|
type genericDialer interface {
|
|
Dial(protocol.DeviceID, *url.URL) (internalConn, error)
|
|
RedialFrequency() time.Duration
|
|
}
|
|
|
|
type listenerFactory interface {
|
|
New(*url.URL, config.Wrapper, *tls.Config, chan internalConn, *nat.Service) genericListener
|
|
Valid(config.Configuration) error
|
|
}
|
|
|
|
type genericListener interface {
|
|
Serve()
|
|
Stop()
|
|
URI() *url.URL
|
|
// A given address can potentially be mutated by the listener.
|
|
// For example we bind to tcp://0.0.0.0, but that for example might return
|
|
// tcp://gateway1.ip and tcp://gateway2.ip as WAN addresses due to there
|
|
// being multiple gateways, and us managing to get a UPnP mapping on both
|
|
// and tcp://192.168.0.1 and tcp://10.0.0.1 due to there being multiple
|
|
// network interfaces. (The later case for LAN addresses is made up just
|
|
// to provide an example)
|
|
WANAddresses() []*url.URL
|
|
LANAddresses() []*url.URL
|
|
Error() error
|
|
OnAddressesChanged(func(genericListener))
|
|
String() string
|
|
Factory() listenerFactory
|
|
NATType() string
|
|
}
|
|
|
|
type Model interface {
|
|
protocol.Model
|
|
AddConnection(conn Connection, hello protocol.HelloResult)
|
|
Connection(remoteID protocol.DeviceID) (Connection, bool)
|
|
OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error
|
|
GetHello(protocol.DeviceID) protocol.HelloIntf
|
|
}
|
|
|
|
// serviceFunc wraps a function to create a suture.Service without stop
|
|
// functionality.
|
|
type serviceFunc func()
|
|
|
|
func (f serviceFunc) Serve() { f() }
|
|
func (f serviceFunc) Stop() {}
|
|
|
|
type onAddressesChangedNotifier struct {
|
|
callbacks []func(genericListener)
|
|
}
|
|
|
|
func (o *onAddressesChangedNotifier) OnAddressesChanged(callback func(genericListener)) {
|
|
o.callbacks = append(o.callbacks, callback)
|
|
}
|
|
|
|
func (o *onAddressesChangedNotifier) notifyAddressesChanged(l genericListener) {
|
|
for _, callback := range o.callbacks {
|
|
callback(l)
|
|
}
|
|
}
|
|
|
|
type dialTarget struct {
|
|
dialer genericDialer
|
|
priority int
|
|
uri *url.URL
|
|
deviceID protocol.DeviceID
|
|
}
|
|
|
|
func (t dialTarget) Dial() (internalConn, error) {
|
|
l.Debugln("dialing", t.deviceID, t.uri, "prio", t.priority)
|
|
conn, err := t.dialer.Dial(t.deviceID, t.uri)
|
|
if err != nil {
|
|
l.Debugln("dialing", t.deviceID, t.uri, "error:", err)
|
|
} else {
|
|
l.Debugln("dialing", t.deviceID, t.uri, "success:", conn)
|
|
}
|
|
return conn, err
|
|
}
|