5
0
mirror of https://github.com/cwinfo/yggdrasil-go.git synced 2024-11-25 23:01:38 +00:00

Merge pull request #587 from yggdrasil-network/develop

Version 0.3.11
This commit is contained in:
Neil Alexander 2019-10-25 09:37:50 +01:00 committed by GitHub
commit 1fbab17b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 450 additions and 153 deletions

View File

@ -5,7 +5,7 @@ version: 2.1
jobs:
build-linux:
docker:
- image: circleci/golang:1.12.7
- image: circleci/golang:1.13.3
steps:
- checkout
@ -48,6 +48,7 @@ jobs:
PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel;
PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips;
PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf;
PKGARCH=armel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armel;
PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64;
mv *.deb /tmp/upload/
@ -105,11 +106,11 @@ jobs:
echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- run:
name: Install Go 1.12.7
name: Install Go 1.13.3
command: |
cd /tmp
curl -LO https://dl.google.com/go/go1.12.7.darwin-amd64.pkg
sudo installer -pkg /tmp/go1.12.7.darwin-amd64.pkg -target /
curl -LO https://dl.google.com/go/go1.13.3.darwin-amd64.pkg
sudo installer -pkg /tmp/go1.13.3.darwin-amd64.pkg -target /
#- run:
# name: Install Gomobile
@ -145,7 +146,7 @@ jobs:
build-other:
docker:
- image: circleci/golang:1.12.7
- image: circleci/golang:1.13.3
steps:
- checkout

View File

@ -25,6 +25,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities.
-->
## [0.3.11] - 2019-10-25
### Added
- Support for TLS listeners and peers has been added, allowing the use of `tls://host:port` in `Peers`, `InterfacePeers` and `Listen` configuration settings - this allows hiding Yggdrasil peerings inside regular TLS connections
### Changed
- Go 1.13 or later is now required for building Yggdrasil
- Some exported API functions have been updated to work with standard Go interfaces:
- `net.Conn` instead of `yggdrasil.Conn`
- `net.Dialer` (the interface it would satisfy if it wasn't a concrete type) instead of `yggdrasil.Dialer`
- `net.Listener` instead of `yggdrasil.Listener`
- Session metadata is now updated correctly when a search completes for a node to which we already have an open session
- Multicast module reloading behaviour has been improved
### Fixed
- An incorrectly held mutex in the crypto-key routing code has been fixed
- Multicast module no longer opens a listener socket if no multicast interfaces are configured
## [0.3.10] - 2019-10-10
### Added
- The core library now includes several unit tests for peering and `yggdrasil.Conn` connections

View File

@ -49,7 +49,7 @@ You may also find other platform-specific wrappers, scripts or tools in the
If you want to build from source, as opposed to installing one of the pre-built
packages:
1. Install [Go](https://golang.org) (requires Go 1.12 or later)
1. Install [Go](https://golang.org) (requires Go 1.13 or later)
2. Clone this repository
2. Run `./build`

View File

@ -23,6 +23,7 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/module"
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
@ -32,9 +33,9 @@ import (
type node struct {
core yggdrasil.Core
state *config.NodeState
tuntap tuntap.TunAdapter
multicast multicast.Multicast
admin admin.AdminSocket
tuntap module.Module // tuntap.TunAdapter
multicast module.Module // multicast.Multicast
admin module.Module // admin.AdminSocket
}
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig {
@ -231,25 +232,30 @@ func main() {
}
// Register the session firewall gatekeeper function
n.core.SetSessionGatekeeper(n.sessionFirewall)
// Allocate our modules
n.admin = &admin.AdminSocket{}
n.multicast = &multicast.Multicast{}
n.tuntap = &tuntap.TunAdapter{}
// Start the admin socket
n.admin.Init(&n.core, n.state, logger, nil)
if err := n.admin.Start(); err != nil {
logger.Errorln("An error occurred starting admin socket:", err)
}
n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
// Start the multicast interface
n.multicast.Init(&n.core, n.state, logger, nil)
if err := n.multicast.Start(); err != nil {
logger.Errorln("An error occurred starting multicast:", err)
}
n.multicast.SetupAdminHandlers(&n.admin)
n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
// Start the TUN/TAP interface
if listener, err := n.core.ConnListen(); err == nil {
if dialer, err := n.core.ConnDialer(); err == nil {
n.tuntap.Init(n.state, logger, listener, dialer)
n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{Listener: listener, Dialer: dialer})
if err := n.tuntap.Start(); err != nil {
logger.Errorln("An error occurred starting TUN/TAP:", err)
}
n.tuntap.SetupAdminHandlers(&n.admin)
n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
} else {
logger.Errorln("Unable to get Dialer:", err)
}

View File

@ -9,7 +9,7 @@ ProtectHome=true
ProtectSystem=true
SyslogIdentifier=yggdrasil
CapabilityBoundSet=CAP_NET_ADMIN
ExecStartPre=+/sbin/modprobe tun
ExecStartPre=+-/sbin/modprobe tun
ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \
then umask 077; \
yggdrasil -genconf > /etc/yggdrasil.conf; \

10
go.mod
View File

@ -1,7 +1,9 @@
module github.com/yggdrasil-network/yggdrasil-go
go 1.13
require (
github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0
github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible
@ -12,8 +14,8 @@ require (
github.com/vishvananda/netlink v1.0.0
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/net v0.0.0-20191021144547-ec77196f6094
golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb
golang.org/x/text v0.3.2
)

19
go.sum
View File

@ -1,5 +1,5 @@
github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 h1:IOFsvAMFkgnKfSQHxXTeqb1+ODFeR5px1HCHU86KF30=
github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2FnnIvY/C0N1aqJo2kbEsdAzU+Tnv48=
github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
@ -20,16 +20,17 @@ github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHS
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 h1:YY9Pg2BEp0jeUVU60svTOaDr+fs1ySC9RbdC1Qc6wOw=
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb h1:ZxSglHghKPYD8WDeRUzRJrUJtDF0PxsTUSxyqr9/5BI=
golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -2,7 +2,11 @@
// Of particular importance are the functions used to derive addresses or subnets from a NodeID, or to get the NodeID and bitmask of the bits visible from an address, which is needed for DHT searches.
package address
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
import (
"fmt"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
// Address represents an IPv6 address in the yggdrasil address range.
type Address [16]byte
@ -128,6 +132,13 @@ func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) {
return &nid, &mask
}
// GetNodeIDLengthString returns a string representation of the known bits of the NodeID, along with the number of known bits, for use with yggdrasil.Dialer's Dial and DialContext functions.
func (a *Address) GetNodeIDLengthString() string {
nid, mask := a.GetNodeIDandMask()
l := mask.PrefixLength()
return fmt.Sprintf("%s/%d", nid.String(), l)
}
// GetNodeIDandMask returns two *NodeID.
// The first is a NodeID with all the bits known from the Subnet set to their correct values.
// The second is a bitmask with 1 bit set for each bit that was known from the Subnet.
@ -156,3 +167,10 @@ func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) {
}
return &nid, &mask
}
// GetNodeIDLengthString returns a string representation of the known bits of the NodeID, along with the number of known bits, for use with yggdrasil.Dialer's Dial and DialContext functions.
func (s *Subnet) GetNodeIDLengthString() string {
nid, mask := s.GetNodeIDandMask()
l := mask.PrefixLength()
return fmt.Sprintf("%s/%d", nid.String(), l)
}

View File

@ -25,12 +25,12 @@ import (
// TODO: Add authentication
type AdminSocket struct {
core *yggdrasil.Core
log *log.Logger
reconfigure chan chan error
listenaddr string
listener net.Listener
handlers map[string]handler
core *yggdrasil.Core
log *log.Logger
listenaddr string
listener net.Listener
handlers map[string]handler
started bool
}
// Info refers to information that is returned to the admin socket handler.
@ -54,23 +54,10 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(In
}
// init runs the initial admin setup.
func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) {
func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
a.core = c
a.log = log
a.reconfigure = make(chan chan error, 1)
a.handlers = make(map[string]handler)
go func() {
for {
e := <-a.reconfigure
current, previous := state.GetCurrent(), state.GetPrevious()
if current.AdminListen != previous.AdminListen {
a.listenaddr = current.AdminListen
a.Stop()
a.Start()
}
e <- nil
}
}()
current := state.GetCurrent()
a.listenaddr = current.AdminListen
a.AddHandler("list", []string{}, func(in Info) (Info, error) {
@ -80,16 +67,31 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.
}
return Info{"list": handlers}, nil
})
return nil
}
func (a *AdminSocket) UpdateConfig(config *config.NodeConfig) {
a.log.Debugln("Reloading admin configuration...")
if a.listenaddr != config.AdminListen {
a.listenaddr = config.AdminListen
if a.IsStarted() {
a.Stop()
}
a.Start()
}
}
func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) {
a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) {
ip := c.Address().String()
subnet := c.Subnet()
ip := a.core.Address().String()
subnet := a.core.Subnet()
return Info{
"self": Info{
ip: Info{
"box_pub_key": c.EncryptionPublicKey(),
"box_pub_key": a.core.EncryptionPublicKey(),
"build_name": version.BuildName(),
"build_version": version.BuildVersion(),
"coords": fmt.Sprintf("%v", c.Coords()),
"coords": fmt.Sprintf("%v", a.core.Coords()),
"subnet": subnet.String(),
},
},
@ -312,17 +314,24 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.
})
}
// start runs the admin API socket to listen for / respond to admin API calls.
// Start runs the admin API socket to listen for / respond to admin API calls.
func (a *AdminSocket) Start() error {
if a.listenaddr != "none" && a.listenaddr != "" {
go a.listen()
a.started = true
}
return nil
}
// cleans up when stopping
// IsStarted returns true if the module has been started.
func (a *AdminSocket) IsStarted() bool {
return a.started
}
// Stop will stop the admin API and close the socket.
func (a *AdminSocket) Stop() error {
if a.listener != nil {
a.started = false
return a.listener.Close()
} else {
return nil

20
src/module/module.go Normal file
View File

@ -0,0 +1,20 @@
package module
import (
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
// Module is an interface that defines which functions must be supported by a
// given Yggdrasil module.
type Module interface {
Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error
Start() error
Stop() error
UpdateConfig(config *config.NodeConfig)
SetupAdminHandlers(a *admin.AdminSocket)
IsStarted() bool
}

View File

@ -55,6 +55,22 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log
// listen for multicast beacons from other hosts and will advertise multicast
// beacons out to the network.
func (m *Multicast) Start() error {
var err error
phony.Block(m, func() {
err = m._start()
})
m.log.Debugln("Started multicast module")
return err
}
func (m *Multicast) _start() error {
if m.isOpen {
return fmt.Errorf("multicast module is already started")
}
if len(m.config.GetCurrent().MulticastInterfaces) == 0 {
return nil
}
m.log.Infoln("Starting multicast module")
addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
if err != nil {
return err
@ -80,8 +96,27 @@ func (m *Multicast) Start() error {
return nil
}
// Stop is not implemented for multicast yet.
// IsStarted returns true if the module has been started.
func (m *Multicast) IsStarted() bool {
var isOpen bool
phony.Block(m, func() {
isOpen = m.isOpen
})
return isOpen
}
// Stop stops the multicast module.
func (m *Multicast) Stop() error {
var err error
phony.Block(m, func() {
err = m._stop()
})
m.log.Debugln("Stopped multicast module")
return nil
}
func (m *Multicast) _stop() error {
m.log.Infoln("Stopping multicast module")
m.isOpen = false
if m.announcer != nil {
m.announcer.Stop()
@ -97,8 +132,26 @@ func (m *Multicast) Stop() error {
// and then signals the various module goroutines to reconfigure themselves if
// needed.
func (m *Multicast) UpdateConfig(config *config.NodeConfig) {
m.log.Debugln("Reloading multicast configuration...")
m.Act(m, func() { m._updateConfig(config) })
}
func (m *Multicast) _updateConfig(config *config.NodeConfig) {
m.log.Infoln("Reloading multicast configuration...")
if m.isOpen {
if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort {
if err := m._stop(); err != nil {
m.log.Errorln("Error stopping multicast module:", err)
}
}
}
m.config.Replace(*config)
m.listenPort = config.LinkLocalTCPPort
if !m.isOpen && len(config.MulticastInterfaces) > 0 {
if err := m._start(); err != nil {
m.log.Errorln("Error starting multicast module:", err)
}
}
m.log.Debugln("Reloaded multicast configuration successfully")
}
// GetInterfaces returns the currently known/enabled multicast interfaces. It is
@ -271,7 +324,7 @@ func (m *Multicast) listen() {
for {
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil {
if !m.isOpen {
if !m.IsStarted() {
return
}
panic(err)

View File

@ -263,7 +263,6 @@ func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error {
// length specified in bytes) from the crypto-key routing table. An error is
// returned if the address is not suitable or no route was found.
func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) {
c.mutexcaches.RLock()
// Check if the address is a valid Yggdrasil address - if so it
// is exempt from all CKR checking
@ -285,11 +284,11 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
}
// Check if there's a cache entry for this addr
c.mutexcaches.RLock()
if route, ok := (*routingcache)[addr]; ok {
c.mutexcaches.RUnlock()
return route.destination, nil
}
c.mutexcaches.RUnlock()
c.mutexremotes.RLock()

View File

@ -93,7 +93,7 @@ func (s *tunConn) _read(bs []byte) (err error) {
skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil {
srcNodeID := crypto.GetNodeID(&key)
if s.conn.RemoteAddr() == *srcNodeID {
if *s.conn.RemoteAddr().(*crypto.NodeID) == *srcNodeID {
// This is the one allowed CKR case, where source and destination addresses are both good
} else {
// The CKR key associated with this address doesn't match the sender's NodeID
@ -170,7 +170,7 @@ func (s *tunConn) _write(bs []byte) (err error) {
skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
dstNodeID := crypto.GetNodeID(&key)
if s.conn.RemoteAddr() == *dstNodeID {
if *s.conn.RemoteAddr().(*crypto.NodeID) == *dstNodeID {
// This is the one allowed CKR case, where source and destination addresses are both good
} else {
// The CKR key associated with this address doesn't match the sender's NodeID

View File

@ -9,6 +9,7 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
"github.com/Arceliar/phony"
)
@ -225,7 +226,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
return
}
// Do we have an active connection for this node address?
var dstNodeID, dstNodeIDMask *crypto.NodeID
var dstString string
session, isIn := tun.addrToConn[dstAddr]
if !isIn || session == nil {
session, isIn = tun.subnetToConn[dstSnet]
@ -233,9 +234,9 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
// Neither an address nor a subnet mapping matched, therefore populate
// the node ID and mask to commence a search
if dstAddr.IsValid() {
dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask()
dstString = dstAddr.GetNodeIDLengthString()
} else {
dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask()
dstString = dstSnet.GetNodeIDLengthString()
}
}
}
@ -243,27 +244,27 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
if !isIn || session == nil {
// Check we haven't been given empty node ID, really this shouldn't ever
// happen but just to be sure...
if dstNodeID == nil || dstNodeIDMask == nil {
panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen")
if dstString == "" {
panic("Given empty dstString - this shouldn't happen")
}
_, known := tun.dials[*dstNodeID]
tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs)
for len(tun.dials[*dstNodeID]) > 32 {
util.PutBytes(tun.dials[*dstNodeID][0])
tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:]
_, known := tun.dials[dstString]
tun.dials[dstString] = append(tun.dials[dstString], bs)
for len(tun.dials[dstString]) > 32 {
util.PutBytes(tun.dials[dstString][0])
tun.dials[dstString] = tun.dials[dstString][1:]
}
if !known {
go func() {
conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask)
conn, err := tun.dialer.Dial("nodeid", dstString)
tun.Act(nil, func() {
packets := tun.dials[*dstNodeID]
delete(tun.dials, *dstNodeID)
packets := tun.dials[dstString]
delete(tun.dials, dstString)
if err != nil {
return
}
// We've been given a connection so prepare the session wrapper
var tc *tunConn
if tc, err = tun._wrap(conn); err != nil {
if tc, err = tun._wrap(conn.(*yggdrasil.Conn)); err != nil {
// Something went wrong when storing the connection, typically that
// something already exists for this address or subnet
tun.log.Debugln("TUN/TAP iface wrap:", err)

View File

@ -52,10 +52,15 @@ type TunAdapter struct {
//mutex sync.RWMutex // Protects the below
addrToConn map[address.Address]*tunConn
subnetToConn map[address.Subnet]*tunConn
dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes
dials map[string][][]byte // Buffer of packets to send after dialing finishes
isOpen bool
}
type TunOptions struct {
Listener *yggdrasil.Listener
Dialer *yggdrasil.Dialer
}
// Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults().
func getSupportedMTU(mtu int) int {
@ -110,16 +115,21 @@ func MaximumMTU() int {
// Init initialises the TUN/TAP module. You must have acquired a Listener from
// the Yggdrasil core before this point and it must not be in use elsewhere.
func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) {
func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error {
tunoptions, ok := options.(TunOptions)
if !ok {
return fmt.Errorf("invalid options supplied to TunAdapter module")
}
tun.config = config
tun.log = log
tun.listener = listener
tun.dialer = dialer
tun.listener = tunoptions.Listener
tun.dialer = tunoptions.Dialer
tun.addrToConn = make(map[address.Address]*tunConn)
tun.subnetToConn = make(map[address.Subnet]*tunConn)
tun.dials = make(map[crypto.NodeID][][]byte)
tun.dials = make(map[string][][]byte)
tun.writer.tun = tun
tun.reader.tun = tun
return nil
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
@ -133,9 +143,12 @@ func (tun *TunAdapter) Start() error {
}
func (tun *TunAdapter) _start() error {
if tun.isOpen {
return errors.New("TUN/TAP module is already started")
}
current := tun.config.GetCurrent()
if tun.config == nil || tun.listener == nil || tun.dialer == nil {
return errors.New("No configuration available to TUN/TAP")
return errors.New("no configuration available to TUN/TAP")
}
var boxPub crypto.BoxPubKey
boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey)
@ -160,13 +173,6 @@ func (tun *TunAdapter) _start() error {
return nil
}
tun.isOpen = true
tun.reconfigure = make(chan chan error)
go func() {
for {
e := <-tun.reconfigure
e <- nil
}
}()
go tun.handler()
tun.reader.Act(nil, tun.reader._read) // Start the reader
tun.icmpv6.Init(tun)
@ -177,6 +183,15 @@ func (tun *TunAdapter) _start() error {
return nil
}
// IsStarted returns true if the module has been started.
func (tun *TunAdapter) IsStarted() bool {
var isOpen bool
phony.Block(tun, func() {
isOpen = tun.isOpen
})
return isOpen
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Stop() error {
@ -219,7 +234,7 @@ func (tun *TunAdapter) handler() error {
return err
}
phony.Block(tun, func() {
if _, err := tun._wrap(conn); err != nil {
if _, err := tun._wrap(conn.(*yggdrasil.Conn)); err != nil {
// Something went wrong when storing the connection, typically that
// something already exists for this address or subnet
tun.log.Debugln("TUN/TAP handler wrap:", err)
@ -237,9 +252,9 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
}
c = &s
// Get the remote address and subnet of the other side
remoteNodeID := conn.RemoteAddr()
s.addr = *address.AddrForNodeID(&remoteNodeID)
s.snet = *address.SubnetForNodeID(&remoteNodeID)
remoteNodeID := conn.RemoteAddr().(*crypto.NodeID)
s.addr = *address.AddrForNodeID(remoteNodeID)
s.snet = *address.SubnetForNodeID(remoteNodeID)
// Work out if this is already a destination we already know about
atc, aok := tun.addrToConn[s.addr]
stc, sok := tun.subnetToConn[s.snet]

View File

@ -280,7 +280,14 @@ func (c *Core) ConnDialer() (*Dialer, error) {
// "Listen" configuration item, e.g.
// tcp://a.b.c.d:e
func (c *Core) ListenTCP(uri string) (*TcpListener, error) {
return c.link.tcp.listen(uri)
return c.link.tcp.listen(uri, nil)
}
// ListenTLS starts a new TLS listener. The input URI should match that of the
// "Listen" configuration item, e.g.
// tls://a.b.c.d:e
func (c *Core) ListenTLS(uri string) (*TcpListener, error) {
return c.link.tcp.listen(uri, c.link.tcp.tls.forListener)
}
// NodeID gets the node ID. This is derived from your router encryption keys.

View File

@ -3,6 +3,7 @@ package yggdrasil
import (
"errors"
"fmt"
"net"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
@ -348,14 +349,14 @@ func (c *Conn) Close() (err error) {
// LocalAddr returns the complete node ID of the local side of the connection.
// This is always going to return your own node's node ID.
func (c *Conn) LocalAddr() crypto.NodeID {
return *crypto.GetNodeID(&c.core.boxPub)
func (c *Conn) LocalAddr() net.Addr {
return crypto.GetNodeID(&c.core.boxPub)
}
// RemoteAddr returns the complete node ID of the remote side of the connection.
func (c *Conn) RemoteAddr() crypto.NodeID {
func (c *Conn) RemoteAddr() net.Addr {
// RemoteAddr is set during the dial or accept, and isn't changed, so it's safe to access directly
return *c.nodeID
return c.nodeID
}
// SetDeadline is equivalent to calling both SetReadDeadline and

View File

@ -1,8 +1,10 @@
package yggdrasil
import (
"context"
"encoding/hex"
"errors"
"net"
"strconv"
"strings"
"time"
@ -15,12 +17,15 @@ type Dialer struct {
core *Core
}
// TODO DialContext that allows timeouts/cancellation, Dial should just call this with no timeout set in the context
// Dial opens a session to the given node. The first paramter should be "nodeid"
// and the second parameter should contain a hexadecimal representation of the
// target node ID.
func (d *Dialer) Dial(network, address string) (*Conn, error) {
// target node ID. It uses DialContext internally.
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(nil, network, address)
}
// DialContext is used internally by Dial, and should only be used with a context that includes a timeout. It uses DialByNodeIDandMask internally.
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
var nodeID crypto.NodeID
var nodeMask crypto.NodeID
// Process
@ -28,7 +33,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) {
case "nodeid":
// A node ID was provided - we don't need to do anything special with it
if tokens := strings.Split(address, "/"); len(tokens) == 2 {
len, err := strconv.Atoi(tokens[1])
l, err := strconv.Atoi(tokens[1])
if err != nil {
return nil, err
}
@ -37,7 +42,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) {
return nil, err
}
copy(nodeID[:], dest)
for idx := 0; idx < len; idx++ {
for idx := 0; idx < l; idx++ {
nodeMask[idx/8] |= 0x80 >> byte(idx%8)
}
} else {
@ -50,7 +55,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) {
nodeMask[i] = 0xFF
}
}
return d.DialByNodeIDandMask(&nodeID, &nodeMask)
return d.DialByNodeIDandMask(ctx, &nodeID, &nodeMask)
default:
// An unexpected address type was given, so give up
return nil, errors.New("unexpected address type")
@ -58,20 +63,25 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) {
}
// DialByNodeIDandMask opens a session to the given node based on raw
// NodeID parameters.
func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) {
// NodeID parameters. If ctx is nil or has no timeout, then a default timeout of 6 seconds will apply, beginning *after* the search finishes.
func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) {
conn := newConn(d.core, nodeID, nodeMask, nil)
if err := conn.search(); err != nil {
// TODO: make searches take a context, so they can be cancelled early
conn.Close()
return nil, err
}
conn.session.setConn(nil, conn)
t := time.NewTimer(6 * time.Second) // TODO use a context instead
defer t.Stop()
var cancel context.CancelFunc
if ctx == nil {
ctx = context.Background()
}
ctx, cancel = context.WithTimeout(ctx, 6*time.Second)
defer cancel()
select {
case <-conn.session.init:
return conn, nil
case <-t.C:
case <-ctx.Done():
conn.Close()
return nil, errors.New("session handshake timeout")
}

View File

@ -93,9 +93,11 @@ func (l *link) call(uri string, sintf string) error {
pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/")
switch u.Scheme {
case "tcp":
l.tcp.call(u.Host, nil, sintf)
l.tcp.call(u.Host, nil, sintf, nil)
case "socks":
l.tcp.call(pathtokens[0], u.Host, sintf)
l.tcp.call(pathtokens[0], u.Host, sintf, nil)
case "tls":
l.tcp.call(u.Host, nil, sintf, l.tcp.tls.forDialer)
default:
return errors.New("unknown call scheme: " + u.Scheme)
}
@ -109,7 +111,10 @@ func (l *link) listen(uri string) error {
}
switch u.Scheme {
case "tcp":
_, err := l.tcp.listen(u.Host)
_, err := l.tcp.listen(u.Host, nil)
return err
case "tls":
_, err := l.tcp.listen(u.Host, l.tcp.tls.forListener)
return err
default:
return errors.New("unknown listen scheme: " + u.Scheme)

View File

@ -13,7 +13,7 @@ type Listener struct {
}
// Accept blocks until a new incoming session is received
func (l *Listener) Accept() (*Conn, error) {
func (l *Listener) Accept() (net.Conn, error) {
select {
case c, ok := <-l.conn:
if !ok {

View File

@ -189,32 +189,34 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
if themMasked != destMasked {
return false
}
finishSearch := func(sess *sessionInfo, err error) {
if sess != nil {
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
sess.coords = res.Coords
sess.ping(sinfo.searches.router)
}
if err != nil {
sinfo.callback(nil, err)
} else {
sinfo.callback(sess, nil)
}
// Cleanup
delete(sinfo.searches.searches, res.Dest)
}
// They match, so create a session and send a sessionRequest
var err error
sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key)
if !isIn {
// Don't already have a session
sess = sinfo.searches.router.sessions.createSession(&res.Key)
if sess == nil {
// nil if the DHT search finished but the session wasn't allowed
sinfo.callback(nil, errors.New("session not allowed"))
// Cleanup
delete(sinfo.searches.searches, res.Dest)
return true
}
_, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key)
if !isIn {
err = errors.New("session not allowed")
} else if _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key); !isIn {
panic("This should never happen")
}
} else {
sinfo.callback(nil, errors.New("session already exists"))
// Cleanup
delete(sinfo.searches.searches, res.Dest)
return true
err = errors.New("session already exists")
}
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
sess.coords = res.Coords
sess.ping(sinfo.searches.router)
sinfo.callback(sess, nil)
// Cleanup
delete(sinfo.searches.searches, res.Dest)
finishSearch(sess, err)
return true
}

View File

@ -39,6 +39,7 @@ type tcp struct {
listeners map[string]*TcpListener
calls map[string]struct{}
conns map[linkInfo](chan struct{})
tls tcptls
}
// TcpListener is a stoppable TCP listener interface. These are typically
@ -47,9 +48,15 @@ type tcp struct {
// multicast interfaces.
type TcpListener struct {
Listener net.Listener
upgrade *TcpUpgrade
stop chan struct{}
}
type TcpUpgrade struct {
upgrade func(c net.Conn) (net.Conn, error)
name string
}
func (l *TcpListener) Stop() {
defer func() { recover() }()
close(l.stop)
@ -81,6 +88,7 @@ func (t *tcp) getAddr() *net.TCPAddr {
// Initializes the struct.
func (t *tcp) init(l *link) error {
t.link = l
t.tls.init(t)
t.mutex.Lock()
t.calls = make(map[string]struct{})
t.conns = make(map[linkInfo](chan struct{}))
@ -90,12 +98,17 @@ func (t *tcp) init(l *link) error {
t.link.core.config.Mutex.RLock()
defer t.link.core.config.Mutex.RUnlock()
for _, listenaddr := range t.link.core.config.Current.Listen {
if listenaddr[:6] != "tcp://" {
switch listenaddr[:6] {
case "tcp://":
if _, err := t.listen(listenaddr[6:], nil); err != nil {
return err
}
case "tls://":
if _, err := t.listen(listenaddr[6:], t.tls.forListener); err != nil {
return err
}
default:
t.link.core.log.Errorln("Failed to add listener: listener", listenaddr, "is not correctly formatted, ignoring")
continue
}
if _, err := t.listen(listenaddr[6:]); err != nil {
return err
}
}
@ -119,18 +132,21 @@ func (t *tcp) reconfigure() {
t.link.core.config.Mutex.RUnlock()
if len(added) > 0 || len(deleted) > 0 {
for _, a := range added {
if a[:6] != "tcp://" {
switch a[:6] {
case "tcp://":
if _, err := t.listen(a[6:], nil); err != nil {
t.link.core.log.Errorln("Error adding TCP", a[6:], "listener:", err)
}
case "tls://":
if _, err := t.listen(a[6:], t.tls.forListener); err != nil {
t.link.core.log.Errorln("Error adding TLS", a[6:], "listener:", err)
}
default:
t.link.core.log.Errorln("Failed to add listener: listener", a, "is not correctly formatted, ignoring")
continue
}
if _, err := t.listen(a[6:]); err != nil {
t.link.core.log.Errorln("Error adding TCP", a[6:], "listener:", err)
} else {
t.link.core.log.Infoln("Started TCP listener:", a[6:])
}
}
for _, d := range deleted {
if d[:6] != "tcp://" {
if d[:6] != "tcp://" && d[:6] != "tls://" {
t.link.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring")
continue
}
@ -146,7 +162,7 @@ func (t *tcp) reconfigure() {
}
}
func (t *tcp) listen(listenaddr string) (*TcpListener, error) {
func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) {
var err error
ctx := context.Background()
@ -157,6 +173,7 @@ func (t *tcp) listen(listenaddr string) (*TcpListener, error) {
if err == nil {
l := TcpListener{
Listener: listener,
upgrade: upgrade,
stop: make(chan struct{}),
}
t.waitgroup.Add(1)
@ -204,7 +221,7 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) {
return
}
t.waitgroup.Add(1)
go t.handler(sock, true, nil)
go t.handler(sock, true, nil, l.upgrade)
}
}
@ -222,11 +239,15 @@ func (t *tcp) startCalling(saddr string) bool {
// If the dial is successful, it launches the handler.
// When finished, it removes the outgoing call, so reconnection attempts can be made later.
// This all happens in a separate goroutine that it spawns.
func (t *tcp) call(saddr string, options interface{}, sintf string) {
func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *TcpUpgrade) {
go func() {
callname := saddr
callproto := "TCP"
if upgrade != nil {
callproto = strings.ToUpper(upgrade.name)
}
if sintf != "" {
callname = fmt.Sprintf("%s/%s", saddr, sintf)
callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf)
}
if !t.startCalling(callname) {
return
@ -261,7 +282,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) {
return
}
t.waitgroup.Add(1)
t.handler(conn, false, saddr)
t.handler(conn, false, saddr, nil)
} else {
dst, err := net.ResolveTCPAddr("tcp", saddr)
if err != nil {
@ -322,19 +343,29 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) {
}
conn, err = dialer.Dial("tcp", dst.String())
if err != nil {
t.link.core.log.Debugln("Failed to dial TCP:", err)
t.link.core.log.Debugf("Failed to dial %s: %s", callproto, err)
return
}
t.waitgroup.Add(1)
t.handler(conn, false, nil)
t.handler(conn, false, nil, upgrade)
}
}()
}
func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) {
func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade *TcpUpgrade) {
defer t.waitgroup.Done() // Happens after sock.close
defer sock.Close()
t.setExtraOptions(sock)
var upgraded bool
if upgrade != nil {
var err error
if sock, err = upgrade.upgrade(sock); err != nil {
t.link.core.log.Errorln("TCP handler upgrade failed:", err)
return
} else {
upgraded = true
}
}
stream := stream{}
stream.init(sock)
var name, proto, local, remote string
@ -344,8 +375,13 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) {
local, _, _ = net.SplitHostPort(sock.LocalAddr().String())
remote, _, _ = net.SplitHostPort(socksaddr)
} else {
name = "tcp://" + sock.RemoteAddr().String()
proto = "tcp"
if upgraded {
proto = upgrade.name
name = proto + "://" + sock.RemoteAddr().String()
} else {
proto = "tcp"
name = proto + "://" + sock.RemoteAddr().String()
}
local, _, _ = net.SplitHostPort(sock.LocalAddr().String())
remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String())
}

93
src/yggdrasil/tls.go Normal file
View File

@ -0,0 +1,93 @@
package yggdrasil
import (
"bytes"
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"log"
"math/big"
"net"
"time"
)
type tcptls struct {
tcp *tcp
config *tls.Config
forDialer *TcpUpgrade
forListener *TcpUpgrade
}
func (t *tcptls) init(tcp *tcp) {
t.tcp = tcp
t.forDialer = &TcpUpgrade{
upgrade: t.upgradeDialer,
name: "tls",
}
t.forListener = &TcpUpgrade{
upgrade: t.upgradeListener,
name: "tls",
}
edpriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
copy(edpriv[:], tcp.link.core.sigPriv[:])
certBuf := &bytes.Buffer{}
// TODO: because NotAfter is finite, we should add some mechanism to regenerate the certificate and restart the listeners periodically for nodes with very high uptimes. Perhaps regenerate certs and restart listeners every few months or so.
pubtemp := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: hex.EncodeToString(tcp.link.core.sigPub[:]),
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derbytes, err := x509.CreateCertificate(rand.Reader, &pubtemp, &pubtemp, edpriv.Public(), edpriv)
if err != nil {
log.Fatalf("Failed to create certificate: %s", err)
}
if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derbytes}); err != nil {
panic("failed to encode certificate into PEM")
}
cpool := x509.NewCertPool()
cpool.AppendCertsFromPEM(derbytes)
t.config = &tls.Config{
RootCAs: cpool,
Certificates: []tls.Certificate{
{
Certificate: [][]byte{derbytes},
PrivateKey: edpriv,
},
},
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS13,
}
}
func (t *tcptls) upgradeListener(c net.Conn) (net.Conn, error) {
conn := tls.Server(c, t.config)
if err := conn.Handshake(); err != nil {
return c, err
}
return conn, nil
}
func (t *tcptls) upgradeDialer(c net.Conn) (net.Conn, error) {
conn := tls.Client(c, t.config)
if err := conn.Handshake(); err != nil {
return c, err
}
return conn, nil
}