mirror of
https://github.com/cwinfo/yggdrasil-go.git
synced 2024-11-10 12:10:27 +00:00
Merge pull request #419 from yggdrasil-network/modular
Modular Yggdrasil
This commit is contained in:
commit
6b6266bfdd
7
build
7
build
@ -8,7 +8,7 @@ PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
|
|||||||
|
|
||||||
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
|
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
|
||||||
|
|
||||||
while getopts "udaitc:l:" option
|
while getopts "udaitc:l:r" option
|
||||||
do
|
do
|
||||||
case "${option}"
|
case "${option}"
|
||||||
in
|
in
|
||||||
@ -19,6 +19,7 @@ do
|
|||||||
t) TABLES=true;;
|
t) TABLES=true;;
|
||||||
c) GCFLAGS="$GCFLAGS $OPTARG";;
|
c) GCFLAGS="$GCFLAGS $OPTARG";;
|
||||||
l) LDFLAGS="$LDFLAGS $OPTARG";;
|
l) LDFLAGS="$LDFLAGS $OPTARG";;
|
||||||
|
r) RACE="-race";;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -43,9 +44,9 @@ else
|
|||||||
echo "Building: $CMD"
|
echo "Building: $CMD"
|
||||||
|
|
||||||
if [ $DEBUG ]; then
|
if [ $DEBUG ]; then
|
||||||
go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD
|
go build $RACE -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD
|
||||||
else
|
else
|
||||||
go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD
|
go build $RACE -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD
|
||||||
fi
|
fi
|
||||||
if [ $UPX ]; then
|
if [ $UPX ]; then
|
||||||
upx --brute $CMD
|
upx --brute $CMD
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/kardianos/minwinsvc"
|
"github.com/kardianos/minwinsvc"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
||||||
@ -31,6 +32,7 @@ type node struct {
|
|||||||
core Core
|
core Core
|
||||||
tuntap tuntap.TunAdapter
|
tuntap tuntap.TunAdapter
|
||||||
multicast multicast.Multicast
|
multicast multicast.Multicast
|
||||||
|
admin admin.AdminSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig {
|
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig {
|
||||||
@ -76,77 +78,6 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
json.Unmarshal(confJson, &cfg)
|
json.Unmarshal(confJson, &cfg)
|
||||||
// For now we will do a little bit to help the user adjust their
|
|
||||||
// configuration to match the new configuration format, as some of the key
|
|
||||||
// names have changed recently.
|
|
||||||
changes := map[string]string{
|
|
||||||
"Multicast": "",
|
|
||||||
"ReadTimeout": "",
|
|
||||||
"LinkLocal": "MulticastInterfaces",
|
|
||||||
"BoxPub": "EncryptionPublicKey",
|
|
||||||
"BoxPriv": "EncryptionPrivateKey",
|
|
||||||
"SigPub": "SigningPublicKey",
|
|
||||||
"SigPriv": "SigningPrivateKey",
|
|
||||||
"AllowedBoxPubs": "AllowedEncryptionPublicKeys",
|
|
||||||
}
|
|
||||||
// Loop over the mappings aove and see if we have anything to fix.
|
|
||||||
for from, to := range changes {
|
|
||||||
if _, ok := dat[from]; ok {
|
|
||||||
if to == "" {
|
|
||||||
if !*normaliseconf {
|
|
||||||
log.Println("Warning: Config option", from, "is deprecated")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !*normaliseconf {
|
|
||||||
log.Println("Warning: Config option", from, "has been renamed - please change to", to)
|
|
||||||
}
|
|
||||||
// If the configuration file doesn't already contain a line with the
|
|
||||||
// new name then set it to the old value. This makes sure that we
|
|
||||||
// don't overwrite something that was put there intentionally.
|
|
||||||
if _, ok := dat[to]; !ok {
|
|
||||||
dat[to] = dat[from]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check to see if the peers are in a parsable format, if not then default
|
|
||||||
// them to the TCP scheme
|
|
||||||
if peers, ok := dat["Peers"].([]interface{}); ok {
|
|
||||||
for index, peer := range peers {
|
|
||||||
uri := peer.(string)
|
|
||||||
if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(uri, "tcp:") {
|
|
||||||
uri = uri[4:]
|
|
||||||
}
|
|
||||||
(dat["Peers"].([]interface{}))[index] = "tcp://" + uri
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Now do the same with the interface peers
|
|
||||||
if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok {
|
|
||||||
for intf, peers := range interfacepeers {
|
|
||||||
for index, peer := range peers.([]interface{}) {
|
|
||||||
uri := peer.(string)
|
|
||||||
if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(uri, "tcp:") {
|
|
||||||
uri = uri[4:]
|
|
||||||
}
|
|
||||||
((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Do a quick check for old-format Listen statement so that mapstructure
|
|
||||||
// doesn't fail and crash
|
|
||||||
if listen, ok := dat["Listen"].(string); ok {
|
|
||||||
if strings.HasPrefix(listen, "tcp://") {
|
|
||||||
dat["Listen"] = []string{listen}
|
|
||||||
} else {
|
|
||||||
dat["Listen"] = []string{"tcp://" + listen}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Overlay our newly mapped configuration onto the autoconf node config that
|
// Overlay our newly mapped configuration onto the autoconf node config that
|
||||||
// we generated above.
|
// we generated above.
|
||||||
if err = mapstructure.Decode(dat, &cfg); err != nil {
|
if err = mapstructure.Decode(dat, &cfg); err != nil {
|
||||||
@ -248,8 +179,6 @@ func main() {
|
|||||||
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
|
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
|
||||||
// don't need to create this manually.
|
// don't need to create this manually.
|
||||||
n := node{}
|
n := node{}
|
||||||
// Before we start the node, set the TUN/TAP to be our router adapter
|
|
||||||
n.core.SetRouterAdapter(&n.tuntap)
|
|
||||||
// Now start Yggdrasil - this starts the DHT, router, switch and other core
|
// Now start Yggdrasil - this starts the DHT, router, switch and other core
|
||||||
// components needed for Yggdrasil to operate
|
// components needed for Yggdrasil to operate
|
||||||
state, err := n.core.Start(cfg, logger)
|
state, err := n.core.Start(cfg, logger)
|
||||||
@ -257,11 +186,31 @@ func main() {
|
|||||||
logger.Errorln("An error occurred during startup")
|
logger.Errorln("An error occurred during startup")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
// Start the admin socket
|
||||||
|
n.admin.Init(&n.core, state, logger, nil)
|
||||||
|
if err := n.admin.Start(); err != nil {
|
||||||
|
logger.Errorln("An error occurred starting admin socket:", err)
|
||||||
|
}
|
||||||
// Start the multicast interface
|
// Start the multicast interface
|
||||||
n.multicast.Init(&n.core, state, logger, nil)
|
n.multicast.Init(&n.core, state, logger, nil)
|
||||||
if err := n.multicast.Start(); err != nil {
|
if err := n.multicast.Start(); err != nil {
|
||||||
logger.Errorln("An error occurred starting multicast:", err)
|
logger.Errorln("An error occurred starting multicast:", err)
|
||||||
}
|
}
|
||||||
|
n.multicast.SetupAdminHandlers(&n.admin)
|
||||||
|
// Start the TUN/TAP interface
|
||||||
|
if listener, err := n.core.ConnListen(); err == nil {
|
||||||
|
if dialer, err := n.core.ConnDialer(); err == nil {
|
||||||
|
n.tuntap.Init(state, logger, listener, dialer)
|
||||||
|
if err := n.tuntap.Start(); err != nil {
|
||||||
|
logger.Errorln("An error occurred starting TUN/TAP:", err)
|
||||||
|
}
|
||||||
|
n.tuntap.SetupAdminHandlers(&n.admin)
|
||||||
|
} else {
|
||||||
|
logger.Errorln("Unable to get Dialer:", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Errorln("Unable to get Listener:", err)
|
||||||
|
}
|
||||||
// The Stop function ensures that the TUN/TAP adapter is correctly shut down
|
// The Stop function ensures that the TUN/TAP adapter is correctly shut down
|
||||||
// before the program exits.
|
// before the program exits.
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -291,6 +240,8 @@ func main() {
|
|||||||
if *useconffile != "" {
|
if *useconffile != "" {
|
||||||
cfg = readConfig(useconf, useconffile, normaliseconf)
|
cfg = readConfig(useconf, useconffile, normaliseconf)
|
||||||
n.core.UpdateConfig(cfg)
|
n.core.UpdateConfig(cfg)
|
||||||
|
n.tuntap.UpdateConfig(cfg)
|
||||||
|
n.multicast.UpdateConfig(cfg)
|
||||||
} else {
|
} else {
|
||||||
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
|
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ func main() {
|
|||||||
if !keysOrdered {
|
if !keysOrdered {
|
||||||
for k := range slv.(map[string]interface{}) {
|
for k := range slv.(map[string]interface{}) {
|
||||||
if !*verbose {
|
if !*verbose {
|
||||||
if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" {
|
if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,6 +277,9 @@ func main() {
|
|||||||
fmt.Println("Coords:", coords)
|
fmt.Println("Coords:", coords)
|
||||||
}
|
}
|
||||||
if *verbose {
|
if *verbose {
|
||||||
|
if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok {
|
||||||
|
fmt.Println("Node ID:", nodeID)
|
||||||
|
}
|
||||||
if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok {
|
if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok {
|
||||||
fmt.Println("Public encryption key:", boxPubKey)
|
fmt.Println("Public encryption key:", boxPubKey)
|
||||||
}
|
}
|
||||||
|
591
src/admin/admin.go
Normal file
591
src/admin/admin.go
Normal file
@ -0,0 +1,591 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gologme/log"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info refers to information that is returned to the admin socket handler.
|
||||||
|
type Info map[string]interface{}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
args []string // List of human-readable argument names
|
||||||
|
handler func(Info) (Info, error) // First is input map, second is output
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHandler is called for each admin function to add the handler and help documentation to the API.
|
||||||
|
func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(Info) (Info, error)) error {
|
||||||
|
if _, ok := a.handlers[strings.ToLower(name)]; ok {
|
||||||
|
return errors.New("handler already exists")
|
||||||
|
}
|
||||||
|
a.handlers[strings.ToLower(name)] = handler{
|
||||||
|
args: args,
|
||||||
|
handler: handlerfunc,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// init runs the initial admin setup.
|
||||||
|
func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) {
|
||||||
|
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.Get()
|
||||||
|
if current.AdminListen != previous.AdminListen {
|
||||||
|
a.listenaddr = current.AdminListen
|
||||||
|
a.Stop()
|
||||||
|
a.Start()
|
||||||
|
}
|
||||||
|
e <- nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
current, _ := state.Get()
|
||||||
|
a.listenaddr = current.AdminListen
|
||||||
|
a.AddHandler("list", []string{}, func(in Info) (Info, error) {
|
||||||
|
handlers := make(map[string]interface{})
|
||||||
|
for handlername, handler := range a.handlers {
|
||||||
|
handlers[handlername] = Info{"fields": handler.args}
|
||||||
|
}
|
||||||
|
return Info{"list": handlers}, nil
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
a.AddHandler("dot", []string{}, func(in Info) (Info, error) {
|
||||||
|
return Info{"dot": string(a.getResponse_dot())}, nil
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) {
|
||||||
|
ip := c.Address().String()
|
||||||
|
return Info{
|
||||||
|
"self": Info{
|
||||||
|
ip: Info{
|
||||||
|
"box_pub_key": c.BoxPubKey(),
|
||||||
|
"build_name": yggdrasil.BuildName(),
|
||||||
|
"build_version": yggdrasil.BuildVersion(),
|
||||||
|
"coords": fmt.Sprintf("%v", c.Coords()),
|
||||||
|
"subnet": c.Subnet().String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) {
|
||||||
|
peers := make(Info)
|
||||||
|
for _, p := range a.core.GetPeers() {
|
||||||
|
addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey))
|
||||||
|
so := net.IP(addr[:]).String()
|
||||||
|
peers[so] = Info{
|
||||||
|
"ip": so,
|
||||||
|
"port": p.Port,
|
||||||
|
"uptime": p.Uptime.Seconds(),
|
||||||
|
"bytes_sent": p.BytesSent,
|
||||||
|
"bytes_recvd": p.BytesRecvd,
|
||||||
|
"proto": p.Protocol,
|
||||||
|
"endpoint": p.Endpoint,
|
||||||
|
"box_pub_key": p.PublicKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Info{"peers": peers}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) {
|
||||||
|
switchpeers := make(Info)
|
||||||
|
for _, s := range a.core.GetSwitchPeers() {
|
||||||
|
addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
|
||||||
|
so := fmt.Sprint(s.Port)
|
||||||
|
switchpeers[so] = Info{
|
||||||
|
"ip": net.IP(addr[:]).String(),
|
||||||
|
"coords": fmt.Sprintf("%v", s.Coords),
|
||||||
|
"port": s.Port,
|
||||||
|
"bytes_sent": s.BytesSent,
|
||||||
|
"bytes_recvd": s.BytesRecvd,
|
||||||
|
"proto": s.Protocol,
|
||||||
|
"endpoint": s.Endpoint,
|
||||||
|
"box_pub_key": s.PublicKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Info{"switchpeers": switchpeers}, nil
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
a.AddHandler("getSwitchQueues", []string{}, func(in Info) (Info, error) {
|
||||||
|
queues := a.core.GetSwitchQueues()
|
||||||
|
return Info{"switchqueues": queues.asMap()}, nil
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) {
|
||||||
|
dht := make(Info)
|
||||||
|
for _, d := range a.core.GetDHT() {
|
||||||
|
addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey))
|
||||||
|
so := net.IP(addr[:]).String()
|
||||||
|
dht[so] = Info{
|
||||||
|
"coords": fmt.Sprintf("%v", d.Coords),
|
||||||
|
"last_seen": d.LastSeen.Seconds(),
|
||||||
|
"box_pub_key": d.PublicKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Info{"dht": dht}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) {
|
||||||
|
sessions := make(Info)
|
||||||
|
for _, s := range a.core.GetSessions() {
|
||||||
|
addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
|
||||||
|
so := net.IP(addr[:]).String()
|
||||||
|
sessions[so] = Info{
|
||||||
|
"coords": fmt.Sprintf("%v", s.Coords),
|
||||||
|
"bytes_sent": s.BytesSent,
|
||||||
|
"bytes_recvd": s.BytesRecvd,
|
||||||
|
"mtu": s.MTU,
|
||||||
|
"uptime": s.Uptime.Seconds(),
|
||||||
|
"was_mtu_fixed": s.WasMTUFixed,
|
||||||
|
"box_pub_key": s.PublicKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Info{"sessions": sessions}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) {
|
||||||
|
// Set sane defaults
|
||||||
|
intf := ""
|
||||||
|
// Has interface been specified?
|
||||||
|
if itf, ok := in["interface"]; ok {
|
||||||
|
intf = itf.(string)
|
||||||
|
}
|
||||||
|
if a.core.AddPeer(in["uri"].(string), intf) == nil {
|
||||||
|
return Info{
|
||||||
|
"added": []string{
|
||||||
|
in["uri"].(string),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return Info{
|
||||||
|
"not_added": []string{
|
||||||
|
in["uri"].(string),
|
||||||
|
},
|
||||||
|
}, errors.New("Failed to add peer")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) {
|
||||||
|
port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
if a.core.DisconnectPeer(uint64(port)) == nil {
|
||||||
|
return Info{
|
||||||
|
"removed": []string{
|
||||||
|
fmt.Sprint(port),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return Info{
|
||||||
|
"not_removed": []string{
|
||||||
|
fmt.Sprint(port),
|
||||||
|
},
|
||||||
|
}, errors.New("Failed to remove peer")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) {
|
||||||
|
return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
|
||||||
|
if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
|
||||||
|
return Info{
|
||||||
|
"added": []string{
|
||||||
|
in["box_pub_key"].(string),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return Info{
|
||||||
|
"not_added": []string{
|
||||||
|
in["box_pub_key"].(string),
|
||||||
|
},
|
||||||
|
}, errors.New("Failed to add allowed key")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
|
||||||
|
if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
|
||||||
|
return Info{
|
||||||
|
"removed": []string{
|
||||||
|
in["box_pub_key"].(string),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return Info{
|
||||||
|
"not_removed": []string{
|
||||||
|
in["box_pub_key"].(string),
|
||||||
|
},
|
||||||
|
}, errors.New("Failed to remove allowed key")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) {
|
||||||
|
if in["target"] == nil {
|
||||||
|
in["target"] = "none"
|
||||||
|
}
|
||||||
|
result, err := a.core.DHTPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string))
|
||||||
|
if err == nil {
|
||||||
|
infos := make(map[string]map[string]string, len(result.Infos))
|
||||||
|
for _, dinfo := range result.Infos {
|
||||||
|
info := map[string]string{
|
||||||
|
"box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]),
|
||||||
|
"coords": fmt.Sprintf("%v", dinfo.Coords),
|
||||||
|
}
|
||||||
|
addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String()
|
||||||
|
infos[addr] = info
|
||||||
|
}
|
||||||
|
return Info{"nodes": infos}, nil
|
||||||
|
} else {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) {
|
||||||
|
var nocache bool
|
||||||
|
if in["nocache"] != nil {
|
||||||
|
nocache = in["nocache"].(string) == "true"
|
||||||
|
}
|
||||||
|
var box_pub_key, coords string
|
||||||
|
if in["box_pub_key"] == nil && in["coords"] == nil {
|
||||||
|
nodeinfo := a.core.MyNodeInfo()
|
||||||
|
var jsoninfo interface{}
|
||||||
|
if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
} else {
|
||||||
|
return Info{"nodeinfo": jsoninfo}, nil
|
||||||
|
}
|
||||||
|
} else if in["box_pub_key"] == nil || in["coords"] == nil {
|
||||||
|
return Info{}, errors.New("Expecting both box_pub_key and coords")
|
||||||
|
} else {
|
||||||
|
box_pub_key = in["box_pub_key"].(string)
|
||||||
|
coords = in["coords"].(string)
|
||||||
|
}
|
||||||
|
result, err := a.core.GetNodeInfo(box_pub_key, coords, nocache)
|
||||||
|
if err == nil {
|
||||||
|
var m map[string]interface{}
|
||||||
|
if err = json.Unmarshal(result, &m); err == nil {
|
||||||
|
return Info{"nodeinfo": m}, nil
|
||||||
|
} else {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleans up when stopping
|
||||||
|
func (a *AdminSocket) Stop() error {
|
||||||
|
if a.listener != nil {
|
||||||
|
return a.listener.Close()
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen is run by start and manages API connections.
|
||||||
|
func (a *AdminSocket) listen() {
|
||||||
|
u, err := url.Parse(a.listenaddr)
|
||||||
|
if err == nil {
|
||||||
|
switch strings.ToLower(u.Scheme) {
|
||||||
|
case "unix":
|
||||||
|
if _, err := os.Stat(a.listenaddr[7:]); err == nil {
|
||||||
|
a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up")
|
||||||
|
if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
|
||||||
|
a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process")
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
if err := os.Remove(a.listenaddr[7:]); err == nil {
|
||||||
|
a.log.Debugln(a.listenaddr[7:], "was cleaned up")
|
||||||
|
} else {
|
||||||
|
a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.listener, err = net.Listen("unix", a.listenaddr[7:])
|
||||||
|
if err == nil {
|
||||||
|
switch a.listenaddr[7:8] {
|
||||||
|
case "@": // maybe abstract namespace
|
||||||
|
default:
|
||||||
|
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
|
||||||
|
a.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "tcp":
|
||||||
|
a.listener, err = net.Listen("tcp", u.Host)
|
||||||
|
default:
|
||||||
|
// err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme))
|
||||||
|
a.listener, err = net.Listen("tcp", a.listenaddr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a.listener, err = net.Listen("tcp", a.listenaddr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
a.log.Errorf("Admin socket failed to listen: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
a.log.Infof("%s admin socket listening on %s",
|
||||||
|
strings.ToUpper(a.listener.Addr().Network()),
|
||||||
|
a.listener.Addr().String())
|
||||||
|
defer a.listener.Close()
|
||||||
|
for {
|
||||||
|
conn, err := a.listener.Accept()
|
||||||
|
if err == nil {
|
||||||
|
go a.handleRequest(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRequest calls the request handler for each request sent to the admin API.
|
||||||
|
func (a *AdminSocket) handleRequest(conn net.Conn) {
|
||||||
|
decoder := json.NewDecoder(conn)
|
||||||
|
encoder := json.NewEncoder(conn)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
recv := make(Info)
|
||||||
|
send := make(Info)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
r := recover()
|
||||||
|
if r != nil {
|
||||||
|
send = Info{
|
||||||
|
"status": "error",
|
||||||
|
"error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax",
|
||||||
|
}
|
||||||
|
a.log.Errorln("Admin socket error:", r)
|
||||||
|
if err := encoder.Encode(&send); err != nil {
|
||||||
|
a.log.Errorln("Admin socket JSON encode error:", err)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Start with a clean slate on each request
|
||||||
|
recv = Info{}
|
||||||
|
send = Info{}
|
||||||
|
|
||||||
|
// Decode the input
|
||||||
|
if err := decoder.Decode(&recv); err != nil {
|
||||||
|
a.log.Debugln("Admin socket JSON decode error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the request back with the response, and default to "error"
|
||||||
|
// unless the status is changed below by one of the handlers
|
||||||
|
send["request"] = recv
|
||||||
|
send["status"] = "error"
|
||||||
|
|
||||||
|
if _, ok := recv["request"]; !ok {
|
||||||
|
send["error"] = "No request sent"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
n := strings.ToLower(recv["request"].(string))
|
||||||
|
if h, ok := a.handlers[strings.ToLower(n)]; ok {
|
||||||
|
// Check that we have all the required arguments
|
||||||
|
for _, arg := range h.args {
|
||||||
|
// An argument in [square brackets] is optional and not required,
|
||||||
|
// so we can safely ignore those
|
||||||
|
if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the field is missing
|
||||||
|
if _, ok := recv[arg]; !ok {
|
||||||
|
send = Info{
|
||||||
|
"status": "error",
|
||||||
|
"error": "Expected field missing: " + arg,
|
||||||
|
"expecting": arg,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By this point we should have all the fields we need, so call
|
||||||
|
// the handler
|
||||||
|
response, err := h.handler(recv)
|
||||||
|
if err != nil {
|
||||||
|
send["error"] = err.Error()
|
||||||
|
if response != nil {
|
||||||
|
send["response"] = response
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send["status"] = "success"
|
||||||
|
if response != nil {
|
||||||
|
send["response"] = response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the response back
|
||||||
|
if err := encoder.Encode(&send); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If "keepalive" isn't true then close the connection
|
||||||
|
if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getResponse_dot returns a response for a graphviz dot formatted
|
||||||
|
// representation of the known parts of the network. This is color-coded and
|
||||||
|
// labeled, and includes the self node, switch peers, nodes known to the DHT,
|
||||||
|
// and nodes with open sessions. The graph is structured as a tree with directed
|
||||||
|
// links leading away from the root.
|
||||||
|
/*
|
||||||
|
func (a *AdminSocket) getResponse_dot() []byte {
|
||||||
|
//self := a.getData_getSelf()
|
||||||
|
peers := a.core.GetSwitchPeers()
|
||||||
|
dht := a.core.GetDHT()
|
||||||
|
sessions := a.core.GetSessions()
|
||||||
|
// Start building a tree from all known nodes
|
||||||
|
type nodeInfo struct {
|
||||||
|
name string
|
||||||
|
key string
|
||||||
|
parent string
|
||||||
|
port uint64
|
||||||
|
options string
|
||||||
|
}
|
||||||
|
infos := make(map[string]nodeInfo)
|
||||||
|
// Get coords as a slice of strings, FIXME? this looks very fragile
|
||||||
|
coordSlice := func(coords string) []string {
|
||||||
|
tmp := strings.Replace(coords, "[", "", -1)
|
||||||
|
tmp = strings.Replace(tmp, "]", "", -1)
|
||||||
|
return strings.Split(tmp, " ")
|
||||||
|
}
|
||||||
|
// First fill the tree with all known nodes, no parents
|
||||||
|
addInfo := func(nodes []admin_nodeInfo, options string, tag string) {
|
||||||
|
for _, node := range nodes {
|
||||||
|
n := node.asMap()
|
||||||
|
info := nodeInfo{
|
||||||
|
key: n["coords"].(string),
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
if len(tag) > 0 {
|
||||||
|
info.name = fmt.Sprintf("%s\n%s", n["ip"].(string), tag)
|
||||||
|
} else {
|
||||||
|
info.name = n["ip"].(string)
|
||||||
|
}
|
||||||
|
coordsSplit := coordSlice(info.key)
|
||||||
|
if len(coordsSplit) != 0 {
|
||||||
|
portStr := coordsSplit[len(coordsSplit)-1]
|
||||||
|
portUint, err := strconv.ParseUint(portStr, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
info.port = portUint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
infos[info.key] = info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addInfo(dht, "fillcolor=\"#ffffff\" style=filled fontname=\"sans serif\"", "Known in DHT") // white
|
||||||
|
addInfo(sessions, "fillcolor=\"#acf3fd\" style=filled fontname=\"sans serif\"", "Open session") // blue
|
||||||
|
addInfo(peers, "fillcolor=\"#ffffb5\" style=filled fontname=\"sans serif\"", "Connected peer") // yellow
|
||||||
|
addInfo(append([]admin_nodeInfo(nil), *self), "fillcolor=\"#a5ff8a\" style=filled fontname=\"sans serif\"", "This node") // green
|
||||||
|
// Now go through and create placeholders for any missing nodes
|
||||||
|
for _, info := range infos {
|
||||||
|
// This is ugly string manipulation
|
||||||
|
coordsSplit := coordSlice(info.key)
|
||||||
|
for idx := range coordsSplit {
|
||||||
|
key := fmt.Sprintf("[%v]", strings.Join(coordsSplit[:idx], " "))
|
||||||
|
newInfo, isIn := infos[key]
|
||||||
|
if isIn {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newInfo.name = "?"
|
||||||
|
newInfo.key = key
|
||||||
|
newInfo.options = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
|
||||||
|
|
||||||
|
coordsSplit := coordSlice(newInfo.key)
|
||||||
|
if len(coordsSplit) != 0 {
|
||||||
|
portStr := coordsSplit[len(coordsSplit)-1]
|
||||||
|
portUint, err := strconv.ParseUint(portStr, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
newInfo.port = portUint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
infos[key] = newInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now go through and attach parents
|
||||||
|
for _, info := range infos {
|
||||||
|
pSplit := coordSlice(info.key)
|
||||||
|
if len(pSplit) > 0 {
|
||||||
|
pSplit = pSplit[:len(pSplit)-1]
|
||||||
|
}
|
||||||
|
info.parent = fmt.Sprintf("[%v]", strings.Join(pSplit, " "))
|
||||||
|
infos[info.key] = info
|
||||||
|
}
|
||||||
|
// Finally, get a sorted list of keys, which we use to organize the output
|
||||||
|
var keys []string
|
||||||
|
for _, info := range infos {
|
||||||
|
keys = append(keys, info.key)
|
||||||
|
}
|
||||||
|
// sort
|
||||||
|
sort.SliceStable(keys, func(i, j int) bool {
|
||||||
|
return keys[i] < keys[j]
|
||||||
|
})
|
||||||
|
sort.SliceStable(keys, func(i, j int) bool {
|
||||||
|
return infos[keys[i]].port < infos[keys[j]].port
|
||||||
|
})
|
||||||
|
// Now print it all out
|
||||||
|
var out []byte
|
||||||
|
put := func(s string) {
|
||||||
|
out = append(out, []byte(s)...)
|
||||||
|
}
|
||||||
|
put("digraph {\n")
|
||||||
|
// First set the labels
|
||||||
|
for _, key := range keys {
|
||||||
|
info := infos[key]
|
||||||
|
put(fmt.Sprintf("\"%v\" [ label = \"%v\" %v ];\n", info.key, info.name, info.options))
|
||||||
|
}
|
||||||
|
// Then print the tree structure
|
||||||
|
for _, key := range keys {
|
||||||
|
info := infos[key]
|
||||||
|
if info.key == info.parent {
|
||||||
|
continue
|
||||||
|
} // happens for the root, skip it
|
||||||
|
port := fmt.Sprint(info.port)
|
||||||
|
style := "fontname=\"sans serif\""
|
||||||
|
if infos[info.parent].name == "?" || infos[info.key].name == "?" {
|
||||||
|
style = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
|
||||||
|
}
|
||||||
|
put(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" %s ];\n", info.parent, info.key, port, style))
|
||||||
|
}
|
||||||
|
put("}\n")
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
*/
|
@ -13,6 +13,7 @@ It also defines NodeID and TreeID as hashes of keys, and wraps hash functions
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"golang.org/x/crypto/nacl/box"
|
"golang.org/x/crypto/nacl/box"
|
||||||
@ -32,6 +33,41 @@ type NodeID [NodeIDLen]byte
|
|||||||
type TreeID [TreeIDLen]byte
|
type TreeID [TreeIDLen]byte
|
||||||
type Handle [handleLen]byte
|
type Handle [handleLen]byte
|
||||||
|
|
||||||
|
func (n *NodeID) String() string {
|
||||||
|
return hex.EncodeToString(n[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network returns "nodeid" nearly always right now.
|
||||||
|
func (n *NodeID) Network() string {
|
||||||
|
return "nodeid"
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrefixLength returns the number of bits set in a masked NodeID.
|
||||||
|
func (n *NodeID) PrefixLength() int {
|
||||||
|
var len int
|
||||||
|
for i, v := range *n {
|
||||||
|
_, _ = i, v
|
||||||
|
if v == 0xff {
|
||||||
|
len += 8
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for v&0x80 != 0 {
|
||||||
|
len++
|
||||||
|
v <<= 1
|
||||||
|
}
|
||||||
|
if v != 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
for i++; i < NodeIDLen; i++ {
|
||||||
|
if n[i] != 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return len
|
||||||
|
}
|
||||||
|
|
||||||
func GetNodeID(pub *BoxPubKey) *NodeID {
|
func GetNodeID(pub *BoxPubKey) *NodeID {
|
||||||
h := sha512.Sum512(pub[:])
|
h := sha512.Sum512(pub[:])
|
||||||
return (*NodeID)(&h)
|
return (*NodeID)(&h)
|
||||||
|
13
src/multicast/admin.go
Normal file
13
src/multicast/admin.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package multicast
|
||||||
|
|
||||||
|
import "github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
|
|
||||||
|
func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) {
|
||||||
|
a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
var intfs []string
|
||||||
|
for _, v := range m.interfaces() {
|
||||||
|
intfs = append(intfs, v.Name)
|
||||||
|
}
|
||||||
|
return admin.Info{"multicast_interfaces": intfs}, nil
|
||||||
|
})
|
||||||
|
}
|
@ -41,6 +41,10 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
e := <-m.reconfigure
|
e := <-m.reconfigure
|
||||||
|
// There's nothing particularly to do here because the multicast module
|
||||||
|
// already consults the config.NodeState when enumerating multicast
|
||||||
|
// interfaces on each pass. We just need to return nil so that the
|
||||||
|
// reconfiguration doesn't block indefinitely
|
||||||
e <- nil
|
e <- nil
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -56,7 +60,8 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log
|
|||||||
// listen for multicast beacons from other hosts and will advertise multicast
|
// listen for multicast beacons from other hosts and will advertise multicast
|
||||||
// beacons out to the network.
|
// beacons out to the network.
|
||||||
func (m *Multicast) Start() error {
|
func (m *Multicast) Start() error {
|
||||||
if len(m.interfaces()) == 0 {
|
current, _ := m.config.Get()
|
||||||
|
if len(current.MulticastInterfaces) == 0 {
|
||||||
m.log.Infoln("Multicast discovery is disabled")
|
m.log.Infoln("Multicast discovery is disabled")
|
||||||
} else {
|
} else {
|
||||||
m.log.Infoln("Multicast discovery is enabled")
|
m.log.Infoln("Multicast discovery is enabled")
|
||||||
@ -89,6 +94,36 @@ func (m *Multicast) Stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateConfig updates the multicast module with the provided config.NodeConfig
|
||||||
|
// 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.config.Replace(*config)
|
||||||
|
|
||||||
|
errors := 0
|
||||||
|
|
||||||
|
components := []chan chan error{
|
||||||
|
m.reconfigure,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, component := range components {
|
||||||
|
response := make(chan error)
|
||||||
|
component <- response
|
||||||
|
if err := <-response; err != nil {
|
||||||
|
m.log.Errorln(err)
|
||||||
|
errors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors > 0 {
|
||||||
|
m.log.Warnln(errors, "multicast module(s) reported errors during configuration reload")
|
||||||
|
} else {
|
||||||
|
m.log.Infoln("Multicast configuration reloaded successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Multicast) interfaces() map[string]net.Interface {
|
func (m *Multicast) interfaces() map[string]net.Interface {
|
||||||
// Get interface expressions from config
|
// Get interface expressions from config
|
||||||
current, _ := m.config.Get()
|
current, _ := m.config.Get()
|
||||||
|
119
src/tuntap/admin.go
Normal file
119
src/tuntap/admin.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package tuntap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
|
||||||
|
a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
r = admin.Info{"none": admin.Info{}}
|
||||||
|
e = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return admin.Info{
|
||||||
|
t.iface.Name(): admin.Info{
|
||||||
|
"tap_mode": t.iface.IsTAP(),
|
||||||
|
"mtu": t.mtu,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
// TODO: rewrite this as I'm fairly sure it doesn't work right on many
|
||||||
|
// platforms anyway, but it may require changes to Water
|
||||||
|
a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) {
|
||||||
|
// Set sane defaults
|
||||||
|
iftapmode := defaults.GetDefaults().DefaultIfTAPMode
|
||||||
|
ifmtu := defaults.GetDefaults().DefaultIfMTU
|
||||||
|
// Has TAP mode been specified?
|
||||||
|
if tap, ok := in["tap_mode"]; ok {
|
||||||
|
iftapmode = tap.(bool)
|
||||||
|
}
|
||||||
|
// Check we have enough params for MTU
|
||||||
|
if mtu, ok := in["mtu"]; ok {
|
||||||
|
if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU {
|
||||||
|
ifmtu = int(in["mtu"].(float64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Start the TUN adapter
|
||||||
|
if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil {
|
||||||
|
return Info{}, errors.New("Failed to configure adapter")
|
||||||
|
} else {
|
||||||
|
return Info{
|
||||||
|
a.core.router.tun.iface.Name(): Info{
|
||||||
|
"tap_mode": a.core.router.tun.iface.IsTAP(),
|
||||||
|
"mtu": ifmtu,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
a.AddHandler("getTunnelRouting", []string{}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
return admin.Info{"enabled": t.ckr.isEnabled()}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
enabled := false
|
||||||
|
if e, ok := in["enabled"].(bool); ok {
|
||||||
|
enabled = e
|
||||||
|
}
|
||||||
|
t.ckr.setEnabled(enabled)
|
||||||
|
return admin.Info{"enabled": enabled}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
if err := t.ckr.addSourceSubnet(in["subnet"].(string)); err == nil {
|
||||||
|
return admin.Info{"added": []string{in["subnet"].(string)}}, nil
|
||||||
|
} else {
|
||||||
|
return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
if err := t.ckr.addRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil {
|
||||||
|
return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
|
||||||
|
} else {
|
||||||
|
return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
var subnets []string
|
||||||
|
getSourceSubnets := func(snets []net.IPNet) {
|
||||||
|
for _, subnet := range snets {
|
||||||
|
subnets = append(subnets, subnet.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getSourceSubnets(t.ckr.ipv4sources)
|
||||||
|
getSourceSubnets(t.ckr.ipv6sources)
|
||||||
|
return admin.Info{"source_subnets": subnets}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
routes := make(admin.Info)
|
||||||
|
getRoutes := func(ckrs []cryptokey_route) {
|
||||||
|
for _, ckr := range ckrs {
|
||||||
|
routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getRoutes(t.ckr.ipv4routes)
|
||||||
|
getRoutes(t.ckr.ipv6routes)
|
||||||
|
return admin.Info{"routes": routes}, nil
|
||||||
|
})
|
||||||
|
a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
if err := t.ckr.removeSourceSubnet(in["subnet"].(string)); err == nil {
|
||||||
|
return admin.Info{"removed": []string{in["subnet"].(string)}}, nil
|
||||||
|
} else {
|
||||||
|
return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) {
|
||||||
|
if err := t.ckr.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil {
|
||||||
|
return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
|
||||||
|
} else {
|
||||||
|
return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package yggdrasil
|
package tuntap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -7,6 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
@ -16,15 +18,18 @@ import (
|
|||||||
// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil.
|
// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil.
|
||||||
|
|
||||||
type cryptokey struct {
|
type cryptokey struct {
|
||||||
core *Core
|
tun *TunAdapter
|
||||||
enabled bool
|
enabled atomic.Value // bool
|
||||||
reconfigure chan chan error
|
reconfigure chan chan error
|
||||||
ipv4routes []cryptokey_route
|
ipv4routes []cryptokey_route
|
||||||
ipv6routes []cryptokey_route
|
ipv6routes []cryptokey_route
|
||||||
ipv4cache map[address.Address]cryptokey_route
|
ipv4cache map[address.Address]cryptokey_route
|
||||||
ipv6cache map[address.Address]cryptokey_route
|
ipv6cache map[address.Address]cryptokey_route
|
||||||
ipv4sources []net.IPNet
|
ipv4sources []net.IPNet
|
||||||
ipv6sources []net.IPNet
|
ipv6sources []net.IPNet
|
||||||
|
mutexroutes sync.RWMutex
|
||||||
|
mutexcaches sync.RWMutex
|
||||||
|
mutexsources sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type cryptokey_route struct {
|
type cryptokey_route struct {
|
||||||
@ -33,58 +38,59 @@ type cryptokey_route struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialise crypto-key routing. This must be done before any other CKR calls.
|
// Initialise crypto-key routing. This must be done before any other CKR calls.
|
||||||
func (c *cryptokey) init(core *Core) {
|
func (c *cryptokey) init(tun *TunAdapter) {
|
||||||
c.core = core
|
c.tun = tun
|
||||||
c.reconfigure = make(chan chan error, 1)
|
c.reconfigure = make(chan chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
e := <-c.reconfigure
|
e := <-c.reconfigure
|
||||||
var err error
|
e <- nil
|
||||||
c.core.router.doAdmin(func() {
|
|
||||||
err = c.core.router.cryptokey.configure()
|
|
||||||
})
|
|
||||||
e <- err
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := c.configure(); err != nil {
|
if err := c.configure(); err != nil {
|
||||||
c.core.log.Errorln("CKR configuration failed:", err)
|
c.tun.log.Errorln("CKR configuration failed:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the CKR routes - this must only ever be called from the router
|
// Configure the CKR routes - this must only ever be called from the router
|
||||||
// goroutine, e.g. through router.doAdmin
|
// goroutine, e.g. through router.doAdmin
|
||||||
func (c *cryptokey) configure() error {
|
func (c *cryptokey) configure() error {
|
||||||
current, _ := c.core.config.Get()
|
c.tun.config.Mutex.RLock()
|
||||||
|
defer c.tun.config.Mutex.RUnlock()
|
||||||
|
|
||||||
// Set enabled/disabled state
|
// Set enabled/disabled state
|
||||||
c.setEnabled(current.TunnelRouting.Enable)
|
c.setEnabled(c.tun.config.Current.TunnelRouting.Enable)
|
||||||
|
|
||||||
// Clear out existing routes
|
// Clear out existing routes
|
||||||
|
c.mutexroutes.Lock()
|
||||||
c.ipv6routes = make([]cryptokey_route, 0)
|
c.ipv6routes = make([]cryptokey_route, 0)
|
||||||
c.ipv4routes = make([]cryptokey_route, 0)
|
c.ipv4routes = make([]cryptokey_route, 0)
|
||||||
|
c.mutexroutes.Unlock()
|
||||||
|
|
||||||
// Add IPv6 routes
|
// Add IPv6 routes
|
||||||
for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations {
|
for ipv6, pubkey := range c.tun.config.Current.TunnelRouting.IPv6Destinations {
|
||||||
if err := c.addRoute(ipv6, pubkey); err != nil {
|
if err := c.addRoute(ipv6, pubkey); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add IPv4 routes
|
// Add IPv4 routes
|
||||||
for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations {
|
for ipv4, pubkey := range c.tun.config.Current.TunnelRouting.IPv4Destinations {
|
||||||
if err := c.addRoute(ipv4, pubkey); err != nil {
|
if err := c.addRoute(ipv4, pubkey); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear out existing sources
|
// Clear out existing sources
|
||||||
|
c.mutexsources.Lock()
|
||||||
c.ipv6sources = make([]net.IPNet, 0)
|
c.ipv6sources = make([]net.IPNet, 0)
|
||||||
c.ipv4sources = make([]net.IPNet, 0)
|
c.ipv4sources = make([]net.IPNet, 0)
|
||||||
|
c.mutexsources.Unlock()
|
||||||
|
|
||||||
// Add IPv6 sources
|
// Add IPv6 sources
|
||||||
c.ipv6sources = make([]net.IPNet, 0)
|
c.ipv6sources = make([]net.IPNet, 0)
|
||||||
for _, source := range current.TunnelRouting.IPv6Sources {
|
for _, source := range c.tun.config.Current.TunnelRouting.IPv6Sources {
|
||||||
if err := c.addSourceSubnet(source); err != nil {
|
if err := c.addSourceSubnet(source); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -92,43 +98,48 @@ func (c *cryptokey) configure() error {
|
|||||||
|
|
||||||
// Add IPv4 sources
|
// Add IPv4 sources
|
||||||
c.ipv4sources = make([]net.IPNet, 0)
|
c.ipv4sources = make([]net.IPNet, 0)
|
||||||
for _, source := range current.TunnelRouting.IPv4Sources {
|
for _, source := range c.tun.config.Current.TunnelRouting.IPv4Sources {
|
||||||
if err := c.addSourceSubnet(source); err != nil {
|
if err := c.addSourceSubnet(source); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wipe the caches
|
// Wipe the caches
|
||||||
|
c.mutexcaches.Lock()
|
||||||
c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
|
c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
|
||||||
c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
|
c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
|
||||||
|
c.mutexcaches.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable or disable crypto-key routing.
|
// Enable or disable crypto-key routing.
|
||||||
func (c *cryptokey) setEnabled(enabled bool) {
|
func (c *cryptokey) setEnabled(enabled bool) {
|
||||||
c.enabled = enabled
|
c.enabled.Store(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if crypto-key routing is enabled.
|
// Check if crypto-key routing is enabled.
|
||||||
func (c *cryptokey) isEnabled() bool {
|
func (c *cryptokey) isEnabled() bool {
|
||||||
return c.enabled
|
return c.enabled.Load().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the given address (with the address length specified in bytes)
|
// Check whether the given address (with the address length specified in bytes)
|
||||||
// matches either the current node's address, the node's routed subnet or the
|
// matches either the current node's address, the node's routed subnet or the
|
||||||
// list of subnets specified in IPv4Sources/IPv6Sources.
|
// list of subnets specified in IPv4Sources/IPv6Sources.
|
||||||
func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
|
func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
|
||||||
|
c.mutexsources.RLock()
|
||||||
|
defer c.mutexsources.RUnlock()
|
||||||
|
|
||||||
ip := net.IP(addr[:addrlen])
|
ip := net.IP(addr[:addrlen])
|
||||||
|
|
||||||
if addrlen == net.IPv6len {
|
if addrlen == net.IPv6len {
|
||||||
// Does this match our node's address?
|
// Does this match our node's address?
|
||||||
if bytes.Equal(addr[:16], c.core.router.addr[:16]) {
|
if bytes.Equal(addr[:16], c.tun.addr[:16]) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does this match our node's subnet?
|
// Does this match our node's subnet?
|
||||||
if bytes.Equal(addr[:8], c.core.router.subnet[:8]) {
|
if bytes.Equal(addr[:8], c.tun.subnet[:8]) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,6 +172,9 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
|
|||||||
// Adds a source subnet, which allows traffic with these source addresses to
|
// Adds a source subnet, which allows traffic with these source addresses to
|
||||||
// be tunnelled using crypto-key routing.
|
// be tunnelled using crypto-key routing.
|
||||||
func (c *cryptokey) addSourceSubnet(cidr string) error {
|
func (c *cryptokey) addSourceSubnet(cidr string) error {
|
||||||
|
c.mutexsources.Lock()
|
||||||
|
defer c.mutexsources.Unlock()
|
||||||
|
|
||||||
// Is the CIDR we've been given valid?
|
// Is the CIDR we've been given valid?
|
||||||
_, ipnet, err := net.ParseCIDR(cidr)
|
_, ipnet, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -191,13 +205,18 @@ func (c *cryptokey) addSourceSubnet(cidr string) error {
|
|||||||
|
|
||||||
// Add the source subnet
|
// Add the source subnet
|
||||||
*routingsources = append(*routingsources, *ipnet)
|
*routingsources = append(*routingsources, *ipnet)
|
||||||
c.core.log.Infoln("Added CKR source subnet", cidr)
|
c.tun.log.Infoln("Added CKR source subnet", cidr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a destination route for the given CIDR to be tunnelled to the node
|
// Adds a destination route for the given CIDR to be tunnelled to the node
|
||||||
// with the given BoxPubKey.
|
// with the given BoxPubKey.
|
||||||
func (c *cryptokey) addRoute(cidr string, dest string) error {
|
func (c *cryptokey) addRoute(cidr string, dest string) error {
|
||||||
|
c.mutexroutes.Lock()
|
||||||
|
c.mutexcaches.Lock()
|
||||||
|
defer c.mutexroutes.Unlock()
|
||||||
|
defer c.mutexcaches.Unlock()
|
||||||
|
|
||||||
// Is the CIDR we've been given valid?
|
// Is the CIDR we've been given valid?
|
||||||
ipaddr, ipnet, err := net.ParseCIDR(cidr)
|
ipaddr, ipnet, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -263,7 +282,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
|
|||||||
delete(*routingcache, k)
|
delete(*routingcache, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.core.log.Infoln("Added CKR destination subnet", cidr)
|
c.tun.log.Infoln("Added CKR destination subnet", cidr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,6 +291,8 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
|
|||||||
// length specified in bytes) from the crypto-key routing table. An error is
|
// 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.
|
// returned if the address is not suitable or no route was found.
|
||||||
func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) {
|
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
|
// Check if the address is a valid Yggdrasil address - if so it
|
||||||
// is exempt from all CKR checking
|
// is exempt from all CKR checking
|
||||||
if addr.IsValid() {
|
if addr.IsValid() {
|
||||||
@ -284,10 +305,8 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
|
|||||||
|
|
||||||
// Check if the prefix is IPv4 or IPv6
|
// Check if the prefix is IPv4 or IPv6
|
||||||
if addrlen == net.IPv6len {
|
if addrlen == net.IPv6len {
|
||||||
routingtable = &c.ipv6routes
|
|
||||||
routingcache = &c.ipv6cache
|
routingcache = &c.ipv6cache
|
||||||
} else if addrlen == net.IPv4len {
|
} else if addrlen == net.IPv4len {
|
||||||
routingtable = &c.ipv4routes
|
|
||||||
routingcache = &c.ipv4cache
|
routingcache = &c.ipv4cache
|
||||||
} else {
|
} else {
|
||||||
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
|
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
|
||||||
@ -295,9 +314,24 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
|
|||||||
|
|
||||||
// Check if there's a cache entry for this addr
|
// Check if there's a cache entry for this addr
|
||||||
if route, ok := (*routingcache)[addr]; ok {
|
if route, ok := (*routingcache)[addr]; ok {
|
||||||
|
c.mutexcaches.RUnlock()
|
||||||
return route.destination, nil
|
return route.destination, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.mutexcaches.RUnlock()
|
||||||
|
|
||||||
|
c.mutexroutes.RLock()
|
||||||
|
defer c.mutexroutes.RUnlock()
|
||||||
|
|
||||||
|
// Check if the prefix is IPv4 or IPv6
|
||||||
|
if addrlen == net.IPv6len {
|
||||||
|
routingtable = &c.ipv6routes
|
||||||
|
} else if addrlen == net.IPv4len {
|
||||||
|
routingtable = &c.ipv4routes
|
||||||
|
} else {
|
||||||
|
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
|
||||||
|
}
|
||||||
|
|
||||||
// No cache was found - start by converting the address into a net.IP
|
// No cache was found - start by converting the address into a net.IP
|
||||||
ip := make(net.IP, addrlen)
|
ip := make(net.IP, addrlen)
|
||||||
copy(ip[:addrlen], addr[:])
|
copy(ip[:addrlen], addr[:])
|
||||||
@ -307,6 +341,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
|
|||||||
for _, route := range *routingtable {
|
for _, route := range *routingtable {
|
||||||
// Does this subnet match the given IP?
|
// Does this subnet match the given IP?
|
||||||
if route.subnet.Contains(ip) {
|
if route.subnet.Contains(ip) {
|
||||||
|
c.mutexcaches.Lock()
|
||||||
|
defer c.mutexcaches.Unlock()
|
||||||
|
|
||||||
// Check if the routing cache is above a certain size, if it is evict
|
// Check if the routing cache is above a certain size, if it is evict
|
||||||
// a random entry so we can make room for this one. We take advantage
|
// a random entry so we can make room for this one. We take advantage
|
||||||
// of the fact that the iteration order is random here
|
// of the fact that the iteration order is random here
|
||||||
@ -332,6 +369,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
|
|||||||
// Removes a source subnet, which allows traffic with these source addresses to
|
// Removes a source subnet, which allows traffic with these source addresses to
|
||||||
// be tunnelled using crypto-key routing.
|
// be tunnelled using crypto-key routing.
|
||||||
func (c *cryptokey) removeSourceSubnet(cidr string) error {
|
func (c *cryptokey) removeSourceSubnet(cidr string) error {
|
||||||
|
c.mutexsources.Lock()
|
||||||
|
defer c.mutexsources.Unlock()
|
||||||
|
|
||||||
// Is the CIDR we've been given valid?
|
// Is the CIDR we've been given valid?
|
||||||
_, ipnet, err := net.ParseCIDR(cidr)
|
_, ipnet, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -357,7 +397,7 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
|
|||||||
for idx, subnet := range *routingsources {
|
for idx, subnet := range *routingsources {
|
||||||
if subnet.String() == ipnet.String() {
|
if subnet.String() == ipnet.String() {
|
||||||
*routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...)
|
*routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...)
|
||||||
c.core.log.Infoln("Removed CKR source subnet", cidr)
|
c.tun.log.Infoln("Removed CKR source subnet", cidr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,6 +407,11 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
|
|||||||
// Removes a destination route for the given CIDR to be tunnelled to the node
|
// Removes a destination route for the given CIDR to be tunnelled to the node
|
||||||
// with the given BoxPubKey.
|
// with the given BoxPubKey.
|
||||||
func (c *cryptokey) removeRoute(cidr string, dest string) error {
|
func (c *cryptokey) removeRoute(cidr string, dest string) error {
|
||||||
|
c.mutexroutes.Lock()
|
||||||
|
c.mutexcaches.Lock()
|
||||||
|
defer c.mutexroutes.Unlock()
|
||||||
|
defer c.mutexcaches.Unlock()
|
||||||
|
|
||||||
// Is the CIDR we've been given valid?
|
// Is the CIDR we've been given valid?
|
||||||
_, ipnet, err := net.ParseCIDR(cidr)
|
_, ipnet, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -406,7 +451,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
|
|||||||
for k := range *routingcache {
|
for k := range *routingcache {
|
||||||
delete(*routingcache, k)
|
delete(*routingcache, k)
|
||||||
}
|
}
|
||||||
c.core.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest)
|
c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
132
src/tuntap/conn.go
Normal file
132
src/tuntap/conn.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package tuntap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/util"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tunConn struct {
|
||||||
|
tun *TunAdapter
|
||||||
|
conn *yggdrasil.Conn
|
||||||
|
addr address.Address
|
||||||
|
snet address.Subnet
|
||||||
|
send chan []byte
|
||||||
|
stop chan struct{}
|
||||||
|
alive chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tunConn) close() {
|
||||||
|
s.tun.mutex.Lock()
|
||||||
|
defer s.tun.mutex.Unlock()
|
||||||
|
s._close_nomutex()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tunConn) _close_nomutex() {
|
||||||
|
s.conn.Close()
|
||||||
|
delete(s.tun.addrToConn, s.addr)
|
||||||
|
delete(s.tun.subnetToConn, s.snet)
|
||||||
|
func() {
|
||||||
|
defer func() { recover() }()
|
||||||
|
close(s.stop) // Closes reader/writer goroutines
|
||||||
|
}()
|
||||||
|
func() {
|
||||||
|
defer func() { recover() }()
|
||||||
|
close(s.alive) // Closes timeout goroutine
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tunConn) reader() error {
|
||||||
|
select {
|
||||||
|
case _, ok := <-s.stop:
|
||||||
|
if !ok {
|
||||||
|
return errors.New("session was already closed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
read := make(chan bool)
|
||||||
|
b := make([]byte, 65535)
|
||||||
|
for {
|
||||||
|
go func() {
|
||||||
|
// TODO don't start a new goroutine for every packet read, this is probably a big part of the slowdowns we saw when refactoring
|
||||||
|
if n, err = s.conn.Read(b); err != nil {
|
||||||
|
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
read <- true
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-read:
|
||||||
|
if n > 0 {
|
||||||
|
bs := append(util.GetBytes(), b[:n]...)
|
||||||
|
select {
|
||||||
|
case s.tun.send <- bs:
|
||||||
|
default:
|
||||||
|
util.PutBytes(bs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.stillAlive() // TODO? Only stay alive if we read >0 bytes?
|
||||||
|
case <-s.stop:
|
||||||
|
s.tun.log.Debugln("Stopping conn reader for", s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tunConn) writer() error {
|
||||||
|
select {
|
||||||
|
case _, ok := <-s.stop:
|
||||||
|
if !ok {
|
||||||
|
return errors.New("session was already closed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.stop:
|
||||||
|
s.tun.log.Debugln("Stopping conn writer for", s)
|
||||||
|
return nil
|
||||||
|
case b, ok := <-s.send:
|
||||||
|
if !ok {
|
||||||
|
return errors.New("send closed")
|
||||||
|
}
|
||||||
|
// TODO write timeout and close
|
||||||
|
if _, err := s.conn.Write(b); err != nil {
|
||||||
|
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err)
|
||||||
|
}
|
||||||
|
util.PutBytes(b)
|
||||||
|
s.stillAlive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tunConn) stillAlive() {
|
||||||
|
select {
|
||||||
|
case s.alive <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tunConn) checkForTimeouts() error {
|
||||||
|
const timeout = 2 * time.Minute
|
||||||
|
timer := time.NewTimer(timeout)
|
||||||
|
defer util.TimerStop(timer)
|
||||||
|
defer s.close()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-s.alive:
|
||||||
|
if !ok {
|
||||||
|
return errors.New("connection closed")
|
||||||
|
}
|
||||||
|
util.TimerStop(timer)
|
||||||
|
timer.Reset(timeout)
|
||||||
|
case <-timer.C:
|
||||||
|
return errors.New("timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -169,7 +169,6 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send it back
|
// Send it back
|
||||||
return responsePacket, nil
|
return responsePacket, nil
|
||||||
} else {
|
} else {
|
||||||
@ -186,7 +185,7 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error)
|
|||||||
copy(addr[:], ipv6Header.Src[:])
|
copy(addr[:], ipv6Header.Src[:])
|
||||||
copy(target[:], datain[48:64])
|
copy(target[:], datain[48:64])
|
||||||
copy(mac[:], (*datamac)[:])
|
copy(mac[:], (*datamac)[:])
|
||||||
// i.tun.core.log.Printf("Learning peer MAC %x for %x\n", mac, target)
|
// fmt.Printf("Learning peer MAC %x for %x\n", mac, target)
|
||||||
neighbor := i.peermacs[target]
|
neighbor := i.peermacs[target]
|
||||||
neighbor.mac = mac
|
neighbor.mac = mac
|
||||||
neighbor.learned = true
|
neighbor.learned = true
|
||||||
|
254
src/tuntap/iface.go
Normal file
254
src/tuntap/iface.go
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
package tuntap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/songgao/packets/ethernet"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tun *TunAdapter) writer() error {
|
||||||
|
var w int
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
b := <-tun.send
|
||||||
|
n := len(b)
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tun.iface.IsTAP() {
|
||||||
|
var dstAddr address.Address
|
||||||
|
if b[0]&0xf0 == 0x60 {
|
||||||
|
if len(b) < 40 {
|
||||||
|
//panic("Tried to send a packet shorter than an IPv6 header...")
|
||||||
|
util.PutBytes(b)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
copy(dstAddr[:16], b[24:])
|
||||||
|
} else if b[0]&0xf0 == 0x40 {
|
||||||
|
if len(b) < 20 {
|
||||||
|
//panic("Tried to send a packet shorter than an IPv4 header...")
|
||||||
|
util.PutBytes(b)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
copy(dstAddr[:4], b[16:])
|
||||||
|
} else {
|
||||||
|
return errors.New("Invalid address family")
|
||||||
|
}
|
||||||
|
sendndp := func(dstAddr address.Address) {
|
||||||
|
neigh, known := tun.icmpv6.peermacs[dstAddr]
|
||||||
|
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
|
||||||
|
if !known {
|
||||||
|
request, err := tun.icmpv6.CreateNDPL2(dstAddr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if _, err := tun.iface.Write(request); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
tun.icmpv6.peermacs[dstAddr] = neighbor{
|
||||||
|
lastsolicitation: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var peermac macAddress
|
||||||
|
var peerknown bool
|
||||||
|
if b[0]&0xf0 == 0x40 {
|
||||||
|
dstAddr = tun.addr
|
||||||
|
} else if b[0]&0xf0 == 0x60 {
|
||||||
|
if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) {
|
||||||
|
dstAddr = tun.addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned {
|
||||||
|
peermac = neighbor.mac
|
||||||
|
peerknown = true
|
||||||
|
} else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned {
|
||||||
|
peermac = neighbor.mac
|
||||||
|
peerknown = true
|
||||||
|
sendndp(dstAddr)
|
||||||
|
} else {
|
||||||
|
sendndp(tun.addr)
|
||||||
|
}
|
||||||
|
if peerknown {
|
||||||
|
var proto ethernet.Ethertype
|
||||||
|
switch {
|
||||||
|
case b[0]&0xf0 == 0x60:
|
||||||
|
proto = ethernet.IPv6
|
||||||
|
case b[0]&0xf0 == 0x40:
|
||||||
|
proto = ethernet.IPv4
|
||||||
|
}
|
||||||
|
var frame ethernet.Frame
|
||||||
|
frame.Prepare(
|
||||||
|
peermac[:6], // Destination MAC address
|
||||||
|
tun.icmpv6.mymac[:6], // Source MAC address
|
||||||
|
ethernet.NotTagged, // VLAN tagging
|
||||||
|
proto, // Ethertype
|
||||||
|
len(b)) // Payload length
|
||||||
|
copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n])
|
||||||
|
n += tun_ETHER_HEADER_LENGTH
|
||||||
|
w, err = tun.iface.Write(frame[:n])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w, err = tun.iface.Write(b[:n])
|
||||||
|
util.PutBytes(b)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
tun.log.Errorln("TUN/TAP iface write error:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if w != n {
|
||||||
|
tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *TunAdapter) reader() error {
|
||||||
|
bs := make([]byte, 65535)
|
||||||
|
for {
|
||||||
|
// Wait for a packet to be delivered to us through the TUN/TAP adapter
|
||||||
|
n, err := tun.iface.Read(bs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If it's a TAP adapter, update the buffer slice so that we no longer
|
||||||
|
// include the ethernet headers
|
||||||
|
offset := 0
|
||||||
|
if tun.iface.IsTAP() {
|
||||||
|
// Set our offset to beyond the ethernet headers
|
||||||
|
offset = tun_ETHER_HEADER_LENGTH
|
||||||
|
// If we detect an ICMP packet then hand it to the ICMPv6 module
|
||||||
|
if bs[offset+6] == 58 {
|
||||||
|
// Found an ICMPv6 packet
|
||||||
|
b := make([]byte, n)
|
||||||
|
copy(b, bs)
|
||||||
|
go tun.icmpv6.ParsePacket(b)
|
||||||
|
}
|
||||||
|
// Then offset the buffer so that we can now just treat it as an IP
|
||||||
|
// packet from now on
|
||||||
|
bs = bs[offset:]
|
||||||
|
}
|
||||||
|
// From the IP header, work out what our source and destination addresses
|
||||||
|
// and node IDs are. We will need these in order to work out where to send
|
||||||
|
// the packet
|
||||||
|
var srcAddr address.Address
|
||||||
|
var dstAddr address.Address
|
||||||
|
var dstNodeID *crypto.NodeID
|
||||||
|
var dstNodeIDMask *crypto.NodeID
|
||||||
|
var dstSnet address.Subnet
|
||||||
|
var addrlen int
|
||||||
|
// Check the IP protocol - if it doesn't match then we drop the packet and
|
||||||
|
// do nothing with it
|
||||||
|
if bs[0]&0xf0 == 0x60 {
|
||||||
|
// Check if we have a fully-sized IPv6 header
|
||||||
|
if len(bs) < 40 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check the packet size
|
||||||
|
if n != 256*int(bs[4])+int(bs[5])+offset+tun_IPv6_HEADER_LENGTH {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// IPv6 address
|
||||||
|
addrlen = 16
|
||||||
|
copy(srcAddr[:addrlen], bs[8:])
|
||||||
|
copy(dstAddr[:addrlen], bs[24:])
|
||||||
|
copy(dstSnet[:addrlen/2], bs[24:])
|
||||||
|
} else if bs[0]&0xf0 == 0x40 {
|
||||||
|
// Check if we have a fully-sized IPv4 header
|
||||||
|
if len(bs) < 20 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check the packet size
|
||||||
|
if n != 256*int(bs[2])+int(bs[3])+offset {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// IPv4 address
|
||||||
|
addrlen = 4
|
||||||
|
copy(srcAddr[:addrlen], bs[12:])
|
||||||
|
copy(dstAddr[:addrlen], bs[16:])
|
||||||
|
} else {
|
||||||
|
// Unknown address length or protocol, so drop the packet and ignore it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !tun.ckr.isValidSource(srcAddr, addrlen) {
|
||||||
|
// The packet had a source address that doesn't belong to us or our
|
||||||
|
// configured crypto-key routing source subnets
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !dstAddr.IsValid() && !dstSnet.IsValid() {
|
||||||
|
if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
|
||||||
|
// A public key was found, get the node ID for the search
|
||||||
|
dstNodeID = crypto.GetNodeID(&key)
|
||||||
|
// Do a quick check to ensure that the node ID refers to a vaild
|
||||||
|
// Yggdrasil address or subnet - this might be superfluous
|
||||||
|
addr := *address.AddrForNodeID(dstNodeID)
|
||||||
|
copy(dstAddr[:], addr[:])
|
||||||
|
copy(dstSnet[:], addr[:])
|
||||||
|
// Are we certain we looked up a valid node?
|
||||||
|
if !dstAddr.IsValid() && !dstSnet.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No public key was found in the CKR table so we've exhausted our options
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do we have an active connection for this node address?
|
||||||
|
tun.mutex.RLock()
|
||||||
|
session, isIn := tun.addrToConn[dstAddr]
|
||||||
|
if !isIn || session == nil {
|
||||||
|
session, isIn = tun.subnetToConn[dstSnet]
|
||||||
|
if !isIn || session == nil {
|
||||||
|
// 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()
|
||||||
|
} else {
|
||||||
|
dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tun.mutex.RUnlock()
|
||||||
|
// If we don't have a connection then we should open one
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
// Dial to the remote node
|
||||||
|
if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil {
|
||||||
|
// We've been given a connection so prepare the session wrapper
|
||||||
|
if s, err := tun.wrap(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)
|
||||||
|
} else {
|
||||||
|
// Update our reference to the connection
|
||||||
|
session, isIn = s, true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We weren't able to dial for some reason so there's no point in
|
||||||
|
// continuing this iteration - skip to the next one
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we have a connection now, try writing to it
|
||||||
|
if isIn && session != nil {
|
||||||
|
packet := append(util.GetBytes(), bs[:n]...)
|
||||||
|
select {
|
||||||
|
case session.send <- packet:
|
||||||
|
default:
|
||||||
|
util.PutBytes(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,14 @@ package tuntap
|
|||||||
|
|
||||||
// This manages the tun driver to send/recv packets to/from applications
|
// This manages the tun driver to send/recv packets to/from applications
|
||||||
|
|
||||||
|
// TODO: Crypto-key routing support
|
||||||
|
// TODO: Set MTU of session properly
|
||||||
|
// TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery
|
||||||
|
// TODO: Connection timeouts (call Conn.Close() when we want to time out)
|
||||||
|
// TODO: Don't block in reader on writes that are pending searches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -11,16 +17,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
"golang.org/x/net/icmp"
|
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
|
|
||||||
"github.com/songgao/packets/ethernet"
|
|
||||||
"github.com/yggdrasil-network/water"
|
"github.com/yggdrasil-network/water"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/util"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
|
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,14 +34,22 @@ const tun_ETHER_HEADER_LENGTH = 14
|
|||||||
// you should pass this object to the yggdrasil.SetRouterAdapter() function
|
// you should pass this object to the yggdrasil.SetRouterAdapter() function
|
||||||
// before calling yggdrasil.Start().
|
// before calling yggdrasil.Start().
|
||||||
type TunAdapter struct {
|
type TunAdapter struct {
|
||||||
yggdrasil.Adapter
|
config *config.NodeState
|
||||||
addr address.Address
|
log *log.Logger
|
||||||
subnet address.Subnet
|
reconfigure chan chan error
|
||||||
icmpv6 ICMPv6
|
listener *yggdrasil.Listener
|
||||||
mtu int
|
dialer *yggdrasil.Dialer
|
||||||
iface *water.Interface
|
addr address.Address
|
||||||
mutex sync.RWMutex // Protects the below
|
subnet address.Subnet
|
||||||
isOpen bool
|
ckr cryptokey
|
||||||
|
icmpv6 ICMPv6
|
||||||
|
mtu int
|
||||||
|
iface *water.Interface
|
||||||
|
send chan []byte
|
||||||
|
mutex sync.RWMutex // Protects the below
|
||||||
|
addrToConn map[address.Address]*tunConn
|
||||||
|
subnetToConn map[address.Subnet]*tunConn
|
||||||
|
isOpen bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the maximum supported MTU for the platform based on the defaults in
|
// Gets the maximum supported MTU for the platform based on the defaults in
|
||||||
@ -94,62 +104,52 @@ func MaximumMTU() int {
|
|||||||
return defaults.GetDefaults().MaximumIfMTU
|
return defaults.GetDefaults().MaximumIfMTU
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialises the TUN/TAP adapter.
|
// Init initialises the TUN/TAP module. You must have acquired a Listener from
|
||||||
func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) {
|
// the Yggdrasil core before this point and it must not be in use elsewhere.
|
||||||
tun.Adapter.Init(config, log, send, recv, reject)
|
func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) {
|
||||||
tun.icmpv6.Init(tun)
|
tun.config = config
|
||||||
go func() {
|
tun.log = log
|
||||||
for {
|
tun.listener = listener
|
||||||
e := <-tun.Reconfigure
|
tun.dialer = dialer
|
||||||
tun.Config.Mutex.RLock()
|
tun.addrToConn = make(map[address.Address]*tunConn)
|
||||||
updated := tun.Config.Current.IfName != tun.Config.Previous.IfName ||
|
tun.subnetToConn = make(map[address.Subnet]*tunConn)
|
||||||
tun.Config.Current.IfTAPMode != tun.Config.Previous.IfTAPMode ||
|
|
||||||
tun.Config.Current.IfMTU != tun.Config.Previous.IfMTU
|
|
||||||
tun.Config.Mutex.RUnlock()
|
|
||||||
if updated {
|
|
||||||
tun.Log.Warnln("Reconfiguring TUN/TAP is not supported yet")
|
|
||||||
e <- nil
|
|
||||||
} else {
|
|
||||||
e <- nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the setup process for the TUN/TAP adapter. If successful, starts the
|
// Start the setup process for the TUN/TAP adapter. If successful, starts the
|
||||||
// read/write goroutines to handle packets on that interface.
|
// read/write goroutines to handle packets on that interface.
|
||||||
func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error {
|
func (tun *TunAdapter) Start() error {
|
||||||
tun.addr = a
|
tun.config.Mutex.RLock()
|
||||||
tun.subnet = s
|
defer tun.config.Mutex.RUnlock()
|
||||||
if tun.Config == nil {
|
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")
|
||||||
}
|
}
|
||||||
tun.Config.Mutex.RLock()
|
var boxPub crypto.BoxPubKey
|
||||||
ifname := tun.Config.Current.IfName
|
boxPubHex, err := hex.DecodeString(tun.config.Current.EncryptionPublicKey)
|
||||||
iftapmode := tun.Config.Current.IfTAPMode
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy(boxPub[:], boxPubHex)
|
||||||
|
nodeID := crypto.GetNodeID(&boxPub)
|
||||||
|
tun.addr = *address.AddrForNodeID(nodeID)
|
||||||
|
tun.subnet = *address.SubnetForNodeID(nodeID)
|
||||||
|
tun.mtu = tun.config.Current.IfMTU
|
||||||
|
ifname := tun.config.Current.IfName
|
||||||
|
iftapmode := tun.config.Current.IfTAPMode
|
||||||
addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1)
|
addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1)
|
||||||
mtu := tun.Config.Current.IfMTU
|
|
||||||
tun.Config.Mutex.RUnlock()
|
|
||||||
if ifname != "none" {
|
if ifname != "none" {
|
||||||
if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil {
|
if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ifname == "none" || ifname == "dummy" {
|
if ifname == "none" || ifname == "dummy" {
|
||||||
tun.Log.Debugln("Not starting TUN/TAP as ifname is none or dummy")
|
tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tun.mutex.Lock()
|
tun.mutex.Lock()
|
||||||
tun.isOpen = true
|
tun.isOpen = true
|
||||||
|
tun.send = make(chan []byte, 32) // TODO: is this a sensible value?
|
||||||
|
tun.reconfigure = make(chan chan error)
|
||||||
tun.mutex.Unlock()
|
tun.mutex.Unlock()
|
||||||
go func() {
|
|
||||||
tun.Log.Debugln("Starting TUN/TAP reader goroutine")
|
|
||||||
tun.Log.Errorln("WARNING: tun.read() exited with error:", tun.read())
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
tun.Log.Debugln("Starting TUN/TAP writer goroutine")
|
|
||||||
tun.Log.Errorln("WARNING: tun.write() exited with error:", tun.write())
|
|
||||||
}()
|
|
||||||
if iftapmode {
|
if iftapmode {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
@ -160,202 +160,107 @@ func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if _, err := tun.iface.Write(request); err != nil {
|
tun.send <- request
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
e := <-tun.reconfigure
|
||||||
|
e <- nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go tun.handler()
|
||||||
|
go tun.reader()
|
||||||
|
go tun.writer()
|
||||||
|
tun.icmpv6.Init(tun)
|
||||||
|
tun.ckr.init(tun)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP
|
// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig
|
||||||
// mode then additional ethernet encapsulation is added for the benefit of the
|
// and then signals the various module goroutines to reconfigure themselves if
|
||||||
// host operating system.
|
// needed.
|
||||||
func (tun *TunAdapter) write() error {
|
func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) {
|
||||||
for {
|
tun.log.Debugln("Reloading TUN/TAP configuration...")
|
||||||
select {
|
|
||||||
case reject := <-tun.Reject:
|
|
||||||
switch reject.Reason {
|
|
||||||
case yggdrasil.PacketTooBig:
|
|
||||||
if mtu, ok := reject.Detail.(int); ok {
|
|
||||||
// Create the Packet Too Big response
|
|
||||||
ptb := &icmp.PacketTooBig{
|
|
||||||
MTU: int(mtu),
|
|
||||||
Data: reject.Packet,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the ICMPv6 response from it
|
tun.config.Replace(*config)
|
||||||
icmpv6Buf, err := CreateICMPv6(
|
|
||||||
reject.Packet[8:24], reject.Packet[24:40],
|
|
||||||
ipv6.ICMPTypePacketTooBig, 0, ptb)
|
|
||||||
|
|
||||||
// Send the ICMPv6 response back to the TUN/TAP adapter
|
errors := 0
|
||||||
if err == nil {
|
|
||||||
tun.iface.Write(icmpv6Buf)
|
components := []chan chan error{
|
||||||
}
|
tun.reconfigure,
|
||||||
}
|
tun.ckr.reconfigure,
|
||||||
fallthrough
|
}
|
||||||
default:
|
|
||||||
continue
|
for _, component := range components {
|
||||||
}
|
response := make(chan error)
|
||||||
case data := <-tun.Recv:
|
component <- response
|
||||||
if tun.iface == nil {
|
if err := <-response; err != nil {
|
||||||
continue
|
tun.log.Errorln(err)
|
||||||
}
|
errors++
|
||||||
if tun.iface.IsTAP() {
|
|
||||||
var destAddr address.Address
|
|
||||||
if data[0]&0xf0 == 0x60 {
|
|
||||||
if len(data) < 40 {
|
|
||||||
//panic("Tried to send a packet shorter than an IPv6 header...")
|
|
||||||
util.PutBytes(data)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
copy(destAddr[:16], data[24:])
|
|
||||||
} else if data[0]&0xf0 == 0x40 {
|
|
||||||
if len(data) < 20 {
|
|
||||||
//panic("Tried to send a packet shorter than an IPv4 header...")
|
|
||||||
util.PutBytes(data)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
copy(destAddr[:4], data[16:])
|
|
||||||
} else {
|
|
||||||
return errors.New("Invalid address family")
|
|
||||||
}
|
|
||||||
sendndp := func(destAddr address.Address) {
|
|
||||||
neigh, known := tun.icmpv6.peermacs[destAddr]
|
|
||||||
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
|
|
||||||
if !known {
|
|
||||||
request, err := tun.icmpv6.CreateNDPL2(destAddr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if _, err := tun.iface.Write(request); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
tun.icmpv6.peermacs[destAddr] = neighbor{
|
|
||||||
lastsolicitation: time.Now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var peermac macAddress
|
|
||||||
var peerknown bool
|
|
||||||
if data[0]&0xf0 == 0x40 {
|
|
||||||
destAddr = tun.addr
|
|
||||||
} else if data[0]&0xf0 == 0x60 {
|
|
||||||
if !bytes.Equal(tun.addr[:16], destAddr[:16]) && !bytes.Equal(tun.subnet[:8], destAddr[:8]) {
|
|
||||||
destAddr = tun.addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned {
|
|
||||||
peermac = neighbor.mac
|
|
||||||
peerknown = true
|
|
||||||
} else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned {
|
|
||||||
peermac = neighbor.mac
|
|
||||||
peerknown = true
|
|
||||||
sendndp(destAddr)
|
|
||||||
} else {
|
|
||||||
sendndp(tun.addr)
|
|
||||||
}
|
|
||||||
if peerknown {
|
|
||||||
var proto ethernet.Ethertype
|
|
||||||
switch {
|
|
||||||
case data[0]&0xf0 == 0x60:
|
|
||||||
proto = ethernet.IPv6
|
|
||||||
case data[0]&0xf0 == 0x40:
|
|
||||||
proto = ethernet.IPv4
|
|
||||||
}
|
|
||||||
var frame ethernet.Frame
|
|
||||||
frame.Prepare(
|
|
||||||
peermac[:6], // Destination MAC address
|
|
||||||
tun.icmpv6.mymac[:6], // Source MAC address
|
|
||||||
ethernet.NotTagged, // VLAN tagging
|
|
||||||
proto, // Ethertype
|
|
||||||
len(data)) // Payload length
|
|
||||||
copy(frame[tun_ETHER_HEADER_LENGTH:], data[:])
|
|
||||||
if _, err := tun.iface.Write(frame); err != nil {
|
|
||||||
tun.mutex.RLock()
|
|
||||||
open := tun.isOpen
|
|
||||||
tun.mutex.RUnlock()
|
|
||||||
if !open {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if _, err := tun.iface.Write(data); err != nil {
|
|
||||||
tun.mutex.RLock()
|
|
||||||
open := tun.isOpen
|
|
||||||
tun.mutex.RUnlock()
|
|
||||||
if !open {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
util.PutBytes(data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors > 0 {
|
||||||
|
tun.log.Warnln(errors, "TUN/TAP module(s) reported errors during configuration reload")
|
||||||
|
} else {
|
||||||
|
tun.log.Infoln("TUN/TAP configuration reloaded successfully")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads any packets that are waiting on the TUN/TAP adapter. If the adapter
|
func (tun *TunAdapter) handler() error {
|
||||||
// is running in TAP mode then the ethernet headers will automatically be
|
|
||||||
// processed and stripped if necessary. If an ICMPv6 packet is found, then
|
|
||||||
// the relevant helper functions in icmpv6.go are called.
|
|
||||||
func (tun *TunAdapter) read() error {
|
|
||||||
mtu := tun.mtu
|
|
||||||
if tun.iface.IsTAP() {
|
|
||||||
mtu += tun_ETHER_HEADER_LENGTH
|
|
||||||
}
|
|
||||||
buf := make([]byte, mtu)
|
|
||||||
for {
|
for {
|
||||||
n, err := tun.iface.Read(buf)
|
// Accept the incoming connection
|
||||||
|
conn, err := tun.listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.mutex.RLock()
|
tun.log.Errorln("TUN/TAP connection accept error:", err)
|
||||||
open := tun.isOpen
|
return err
|
||||||
tun.mutex.RUnlock()
|
|
||||||
if !open {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
o := 0
|
if _, err := tun.wrap(conn); err != nil {
|
||||||
if tun.iface.IsTAP() {
|
// Something went wrong when storing the connection, typically that
|
||||||
o = tun_ETHER_HEADER_LENGTH
|
// something already exists for this address or subnet
|
||||||
|
tun.log.Debugln("TUN/TAP handler wrap:", err)
|
||||||
}
|
}
|
||||||
switch {
|
|
||||||
case buf[o]&0xf0 == 0x60 && n == 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o:
|
|
||||||
case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o:
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if buf[o+6] == 58 {
|
|
||||||
if tun.iface.IsTAP() {
|
|
||||||
// Found an ICMPv6 packet
|
|
||||||
b := make([]byte, n)
|
|
||||||
copy(b, buf)
|
|
||||||
go tun.icmpv6.ParsePacket(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packet := append(util.GetBytes(), buf[o:n]...)
|
|
||||||
tun.Send <- packet
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil
|
func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
|
||||||
// process stops. Typically this operation will happen quickly, but on macOS
|
// Prepare a session wrapper for the given connection
|
||||||
// it can block until a read operation is completed.
|
s := tunConn{
|
||||||
func (tun *TunAdapter) Close() error {
|
tun: tun,
|
||||||
tun.mutex.Lock()
|
conn: conn,
|
||||||
tun.isOpen = false
|
send: make(chan []byte, 32), // TODO: is this a sensible value?
|
||||||
tun.mutex.Unlock()
|
stop: make(chan struct{}),
|
||||||
if tun.iface == nil {
|
alive: make(chan struct{}, 1),
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return tun.iface.Close()
|
// Get the remote address and subnet of the other side
|
||||||
|
remoteNodeID := conn.RemoteAddr()
|
||||||
|
s.addr = *address.AddrForNodeID(&remoteNodeID)
|
||||||
|
s.snet = *address.SubnetForNodeID(&remoteNodeID)
|
||||||
|
// Work out if this is already a destination we already know about
|
||||||
|
tun.mutex.Lock()
|
||||||
|
defer tun.mutex.Unlock()
|
||||||
|
atc, aok := tun.addrToConn[s.addr]
|
||||||
|
stc, sok := tun.subnetToConn[s.snet]
|
||||||
|
// If we know about a connection for this destination already then assume it
|
||||||
|
// is no longer valid and close it
|
||||||
|
if aok {
|
||||||
|
atc._close_nomutex()
|
||||||
|
err = errors.New("replaced connection for address")
|
||||||
|
} else if sok {
|
||||||
|
stc._close_nomutex()
|
||||||
|
err = errors.New("replaced connection for subnet")
|
||||||
|
}
|
||||||
|
// Save the session wrapper so that we can look it up quickly next time
|
||||||
|
// we receive a packet through the interface for this address
|
||||||
|
tun.addrToConn[s.addr] = &s
|
||||||
|
tun.subnetToConn[s.snet] = &s
|
||||||
|
// Start the connection goroutines
|
||||||
|
go s.reader()
|
||||||
|
go s.writer()
|
||||||
|
go s.checkForTimeouts()
|
||||||
|
// Return
|
||||||
|
return c, err
|
||||||
}
|
}
|
||||||
|
@ -109,14 +109,14 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
|||||||
|
|
||||||
// Create system socket
|
// Create system socket
|
||||||
if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil {
|
if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil {
|
||||||
tun.Log.Printf("Create AF_INET socket failed: %v.", err)
|
tun.log.Printf("Create AF_INET socket failed: %v.", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Friendly output
|
// Friendly output
|
||||||
tun.Log.Infof("Interface name: %s", tun.iface.Name())
|
tun.log.Infof("Interface name: %s", tun.iface.Name())
|
||||||
tun.Log.Infof("Interface IPv6: %s", addr)
|
tun.log.Infof("Interface IPv6: %s", addr)
|
||||||
tun.Log.Infof("Interface MTU: %d", tun.mtu)
|
tun.log.Infof("Interface MTU: %d", tun.mtu)
|
||||||
|
|
||||||
// Create the MTU request
|
// Create the MTU request
|
||||||
var ir in6_ifreq_mtu
|
var ir in6_ifreq_mtu
|
||||||
@ -126,15 +126,15 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
|||||||
// Set the MTU
|
// Set the MTU
|
||||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
|
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
|
||||||
err = errno
|
err = errno
|
||||||
tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno)
|
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
|
||||||
|
|
||||||
// Fall back to ifconfig to set the MTU
|
// Fall back to ifconfig to set the MTU
|
||||||
cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu))
|
cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu))
|
||||||
tun.Log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
|
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.Log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
|
tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
|
||||||
tun.Log.Traceln(string(output))
|
tun.log.Traceln(string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,15 +155,15 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
|||||||
// Set the interface address
|
// Set the interface address
|
||||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
|
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
|
||||||
err = errno
|
err = errno
|
||||||
tun.Log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
|
tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
|
||||||
|
|
||||||
// Fall back to ifconfig to set the address
|
// Fall back to ifconfig to set the address
|
||||||
cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr)
|
cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr)
|
||||||
tun.Log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
|
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.Log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
|
tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
|
||||||
tun.Log.Traceln(string(output))
|
tun.log.Traceln(string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
// Configures the "utun" adapter with the correct IPv6 address and MTU.
|
// Configures the "utun" adapter with the correct IPv6 address and MTU.
|
||||||
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
|
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
|
||||||
if iftapmode {
|
if iftapmode {
|
||||||
tun.Log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
|
tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
|
||||||
}
|
}
|
||||||
config := water.Config{DeviceType: water.TUN}
|
config := water.Config{DeviceType: water.TUN}
|
||||||
iface, err := water.New(config)
|
iface, err := water.New(config)
|
||||||
@ -69,7 +69,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
|
if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
|
||||||
tun.Log.Printf("Create AF_SYSTEM socket failed: %v.", err)
|
tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,19 +98,19 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
|||||||
copy(ir.ifr_name[:], tun.iface.Name())
|
copy(ir.ifr_name[:], tun.iface.Name())
|
||||||
ir.ifru_mtu = uint32(tun.mtu)
|
ir.ifru_mtu = uint32(tun.mtu)
|
||||||
|
|
||||||
tun.Log.Infof("Interface name: %s", ar.ifra_name)
|
tun.log.Infof("Interface name: %s", ar.ifra_name)
|
||||||
tun.Log.Infof("Interface IPv6: %s", addr)
|
tun.log.Infof("Interface IPv6: %s", addr)
|
||||||
tun.Log.Infof("Interface MTU: %d", ir.ifru_mtu)
|
tun.log.Infof("Interface MTU: %d", ir.ifru_mtu)
|
||||||
|
|
||||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
|
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
|
||||||
err = errno
|
err = errno
|
||||||
tun.Log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
|
tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
|
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
|
||||||
err = errno
|
err = errno
|
||||||
tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno)
|
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +40,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Friendly output
|
// Friendly output
|
||||||
tun.Log.Infof("Interface name: %s", tun.iface.Name())
|
tun.log.Infof("Interface name: %s", tun.iface.Name())
|
||||||
tun.Log.Infof("Interface IPv6: %s", addr)
|
tun.log.Infof("Interface IPv6: %s", addr)
|
||||||
tun.Log.Infof("Interface MTU: %d", tun.mtu)
|
tun.log.Infof("Interface MTU: %d", tun.mtu)
|
||||||
return tun.setupAddress(addr)
|
return tun.setupAddress(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,6 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
|
|||||||
// We don't know how to set the IPv6 address on an unknown platform, therefore
|
// We don't know how to set the IPv6 address on an unknown platform, therefore
|
||||||
// write about it to stdout and don't try to do anything further.
|
// write about it to stdout and don't try to do anything further.
|
||||||
func (tun *TunAdapter) setupAddress(addr string) error {
|
func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
tun.Log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
|
tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
// delegate the hard work to "netsh".
|
// delegate the hard work to "netsh".
|
||||||
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
|
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
|
||||||
if !iftapmode {
|
if !iftapmode {
|
||||||
tun.Log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
|
tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
|
||||||
}
|
}
|
||||||
config := water.Config{DeviceType: water.TAP}
|
config := water.Config{DeviceType: water.TAP}
|
||||||
config.PlatformSpecificParams.ComponentID = "tap0901"
|
config.PlatformSpecificParams.ComponentID = "tap0901"
|
||||||
@ -31,19 +31,19 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
|
|||||||
}
|
}
|
||||||
// Disable/enable the interface to resets its configuration (invalidating iface)
|
// Disable/enable the interface to resets its configuration (invalidating iface)
|
||||||
cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
|
cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
|
||||||
tun.Log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
|
tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.Log.Errorf("Windows netsh failed: %v.", err)
|
tun.log.Errorf("Windows netsh failed: %v.", err)
|
||||||
tun.Log.Traceln(string(output))
|
tun.log.Traceln(string(output))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED")
|
cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED")
|
||||||
tun.Log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
|
tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
|
||||||
output, err = cmd.CombinedOutput()
|
output, err = cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.Log.Errorf("Windows netsh failed: %v.", err)
|
tun.log.Errorf("Windows netsh failed: %v.", err)
|
||||||
tun.Log.Traceln(string(output))
|
tun.log.Traceln(string(output))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Get a new iface
|
// Get a new iface
|
||||||
@ -58,9 +58,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
// Friendly output
|
// Friendly output
|
||||||
tun.Log.Infof("Interface name: %s", tun.iface.Name())
|
tun.log.Infof("Interface name: %s", tun.iface.Name())
|
||||||
tun.Log.Infof("Interface IPv6: %s", addr)
|
tun.log.Infof("Interface IPv6: %s", addr)
|
||||||
tun.Log.Infof("Interface MTU: %d", tun.mtu)
|
tun.log.Infof("Interface MTU: %d", tun.mtu)
|
||||||
return tun.setupAddress(addr)
|
return tun.setupAddress(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,11 +71,11 @@ func (tun *TunAdapter) setupMTU(mtu int) error {
|
|||||||
fmt.Sprintf("interface=%s", tun.iface.Name()),
|
fmt.Sprintf("interface=%s", tun.iface.Name()),
|
||||||
fmt.Sprintf("mtu=%d", mtu),
|
fmt.Sprintf("mtu=%d", mtu),
|
||||||
"store=active")
|
"store=active")
|
||||||
tun.Log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
|
tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.Log.Errorf("Windows netsh failed: %v.", err)
|
tun.log.Errorf("Windows netsh failed: %v.", err)
|
||||||
tun.Log.Traceln(string(output))
|
tun.log.Traceln(string(output))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -88,11 +88,11 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
|||||||
fmt.Sprintf("interface=%s", tun.iface.Name()),
|
fmt.Sprintf("interface=%s", tun.iface.Name()),
|
||||||
fmt.Sprintf("addr=%s", addr),
|
fmt.Sprintf("addr=%s", addr),
|
||||||
"store=active")
|
"store=active")
|
||||||
tun.Log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
|
tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.Log.Errorf("Windows netsh failed: %v.", err)
|
tun.log.Errorf("Windows netsh failed: %v.", err)
|
||||||
tun.Log.Traceln(string(output))
|
tun.log.Traceln(string(output))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -48,13 +48,12 @@ func PutBytes(bs []byte) {
|
|||||||
|
|
||||||
// This is a workaround to go's broken timer implementation
|
// This is a workaround to go's broken timer implementation
|
||||||
func TimerStop(t *time.Timer) bool {
|
func TimerStop(t *time.Timer) bool {
|
||||||
if !t.Stop() {
|
stopped := t.Stop()
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
default:
|
default:
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return stopped
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run a blocking function with a timeout.
|
// Run a blocking function with a timeout.
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package yggdrasil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gologme/log"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Adapter defines the minimum required struct members for an adapter type. This
|
|
||||||
// is now the base type for adapters like tun.go. When implementing a new
|
|
||||||
// adapter type, you should extend the adapter struct with this one and should
|
|
||||||
// call the Adapter.Init() function when initialising.
|
|
||||||
type Adapter struct {
|
|
||||||
adapterImplementation
|
|
||||||
Core *Core
|
|
||||||
Config *config.NodeState
|
|
||||||
Log *log.Logger
|
|
||||||
Send chan<- []byte
|
|
||||||
Recv <-chan []byte
|
|
||||||
Reject <-chan RejectedPacket
|
|
||||||
Reconfigure chan chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the minimum required functions for an adapter type. Note that the
|
|
||||||
// implementation of Init() should call Adapter.Init(). This is not exported
|
|
||||||
// because doing so breaks the gomobile bindings for iOS/Android.
|
|
||||||
type adapterImplementation interface {
|
|
||||||
Init(*config.NodeState, *log.Logger, chan<- []byte, <-chan []byte, <-chan RejectedPacket)
|
|
||||||
Name() string
|
|
||||||
MTU() int
|
|
||||||
IsTAP() bool
|
|
||||||
Start(address.Address, address.Subnet) error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initialises the adapter with the necessary channels to operate from the
|
|
||||||
// router. When defining a new Adapter type, the Adapter should call this
|
|
||||||
// function from within it's own Init function to set up the channels. It is
|
|
||||||
// otherwise not expected for you to call this function directly.
|
|
||||||
func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan RejectedPacket) {
|
|
||||||
adapter.Config = config
|
|
||||||
adapter.Log = log
|
|
||||||
adapter.Send = send
|
|
||||||
adapter.Recv = recv
|
|
||||||
adapter.Reject = reject
|
|
||||||
adapter.Reconfigure = make(chan chan error, 1)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
536
src/yggdrasil/api.go
Normal file
536
src/yggdrasil/api.go
Normal file
@ -0,0 +1,536 @@
|
|||||||
|
package yggdrasil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gologme/log"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Peer represents a single peer object. This contains information from the
|
||||||
|
// preferred switch port for this peer, although there may be more than one in
|
||||||
|
// reality.
|
||||||
|
type Peer struct {
|
||||||
|
PublicKey crypto.BoxPubKey
|
||||||
|
Endpoint string
|
||||||
|
BytesSent uint64
|
||||||
|
BytesRecvd uint64
|
||||||
|
Protocol string
|
||||||
|
Port uint64
|
||||||
|
Uptime time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchPeer represents a switch connection to a peer. Note that there may be
|
||||||
|
// multiple switch peers per actual peer, e.g. if there are multiple connections
|
||||||
|
// to a given node.
|
||||||
|
type SwitchPeer struct {
|
||||||
|
PublicKey crypto.BoxPubKey
|
||||||
|
Coords []byte
|
||||||
|
BytesSent uint64
|
||||||
|
BytesRecvd uint64
|
||||||
|
Port uint64
|
||||||
|
Protocol string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DHTEntry represents a single DHT entry that has been learned or cached from
|
||||||
|
// DHT searches.
|
||||||
|
type DHTEntry struct {
|
||||||
|
PublicKey crypto.BoxPubKey
|
||||||
|
Coords []byte
|
||||||
|
LastSeen time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DHTRes represents a DHT response, as returned by DHTPing.
|
||||||
|
type DHTRes struct {
|
||||||
|
PublicKey crypto.BoxPubKey // key of the sender
|
||||||
|
Coords []byte // coords of the sender
|
||||||
|
Dest crypto.NodeID // the destination node ID
|
||||||
|
Infos []DHTEntry // response
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeInfoPayload represents a RequestNodeInfo response, in bytes.
|
||||||
|
type NodeInfoPayload nodeinfoPayload
|
||||||
|
|
||||||
|
// SwitchQueues represents information from the switch related to link
|
||||||
|
// congestion and a list of switch queues created in response to congestion on a
|
||||||
|
// given link.
|
||||||
|
type SwitchQueues struct {
|
||||||
|
Queues []SwitchQueue
|
||||||
|
Count uint64
|
||||||
|
Size uint64
|
||||||
|
HighestCount uint64
|
||||||
|
HighestSize uint64
|
||||||
|
MaximumSize uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchQueue represents a single switch queue, which is created in response
|
||||||
|
// to congestion on a given link.
|
||||||
|
type SwitchQueue struct {
|
||||||
|
ID string
|
||||||
|
Size uint64
|
||||||
|
Packets uint64
|
||||||
|
Port uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session represents an open session with another node.
|
||||||
|
type Session struct {
|
||||||
|
PublicKey crypto.BoxPubKey
|
||||||
|
Coords []byte
|
||||||
|
BytesSent uint64
|
||||||
|
BytesRecvd uint64
|
||||||
|
MTU uint16
|
||||||
|
Uptime time.Duration
|
||||||
|
WasMTUFixed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeers returns one or more Peer objects containing information about active
|
||||||
|
// peerings with other Yggdrasil nodes, where one of the responses always
|
||||||
|
// includes information about the current node (with a port number of 0). If
|
||||||
|
// there is exactly one entry then this node is not connected to any other nodes
|
||||||
|
// and is therefore isolated.
|
||||||
|
func (c *Core) GetPeers() []Peer {
|
||||||
|
ports := c.peers.ports.Load().(map[switchPort]*peer)
|
||||||
|
var peers []Peer
|
||||||
|
var ps []switchPort
|
||||||
|
for port := range ports {
|
||||||
|
ps = append(ps, port)
|
||||||
|
}
|
||||||
|
sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] })
|
||||||
|
for _, port := range ps {
|
||||||
|
p := ports[port]
|
||||||
|
info := Peer{
|
||||||
|
Endpoint: p.intf.name,
|
||||||
|
BytesSent: atomic.LoadUint64(&p.bytesSent),
|
||||||
|
BytesRecvd: atomic.LoadUint64(&p.bytesRecvd),
|
||||||
|
Protocol: p.intf.info.linkType,
|
||||||
|
Port: uint64(port),
|
||||||
|
Uptime: time.Since(p.firstSeen),
|
||||||
|
}
|
||||||
|
copy(info.PublicKey[:], p.box[:])
|
||||||
|
peers = append(peers, info)
|
||||||
|
}
|
||||||
|
return peers
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSwitchPeers returns zero or more SwitchPeer objects containing information
|
||||||
|
// about switch port connections with other Yggdrasil nodes. Note that, unlike
|
||||||
|
// GetPeers, GetSwitchPeers does not include information about the current node,
|
||||||
|
// therefore it is possible for this to return zero elements if the node is
|
||||||
|
// isolated or not connected to any peers.
|
||||||
|
func (c *Core) GetSwitchPeers() []SwitchPeer {
|
||||||
|
var switchpeers []SwitchPeer
|
||||||
|
table := c.switchTable.table.Load().(lookupTable)
|
||||||
|
peers := c.peers.ports.Load().(map[switchPort]*peer)
|
||||||
|
for _, elem := range table.elems {
|
||||||
|
peer, isIn := peers[elem.port]
|
||||||
|
if !isIn {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
coords := elem.locator.getCoords()
|
||||||
|
info := SwitchPeer{
|
||||||
|
Coords: append([]byte{}, coords...),
|
||||||
|
BytesSent: atomic.LoadUint64(&peer.bytesSent),
|
||||||
|
BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd),
|
||||||
|
Port: uint64(elem.port),
|
||||||
|
Protocol: peer.intf.info.linkType,
|
||||||
|
Endpoint: peer.intf.info.remote,
|
||||||
|
}
|
||||||
|
copy(info.PublicKey[:], peer.box[:])
|
||||||
|
switchpeers = append(switchpeers, info)
|
||||||
|
}
|
||||||
|
return switchpeers
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDHT returns zero or more entries as stored in the DHT, cached primarily
|
||||||
|
// from searches that have already taken place.
|
||||||
|
func (c *Core) GetDHT() []DHTEntry {
|
||||||
|
var dhtentries []DHTEntry
|
||||||
|
getDHT := func() {
|
||||||
|
now := time.Now()
|
||||||
|
var dhtentry []*dhtInfo
|
||||||
|
for _, v := range c.dht.table {
|
||||||
|
dhtentry = append(dhtentry, v)
|
||||||
|
}
|
||||||
|
sort.SliceStable(dhtentry, func(i, j int) bool {
|
||||||
|
return dht_ordered(&c.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID())
|
||||||
|
})
|
||||||
|
for _, v := range dhtentry {
|
||||||
|
info := DHTEntry{
|
||||||
|
Coords: append([]byte{}, v.coords...),
|
||||||
|
LastSeen: now.Sub(v.recv),
|
||||||
|
}
|
||||||
|
copy(info.PublicKey[:], v.key[:])
|
||||||
|
dhtentries = append(dhtentries, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.router.doAdmin(getDHT)
|
||||||
|
return dhtentries
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSwitchQueues returns information about the switch queues that are
|
||||||
|
// currently in effect. These values can change within an instant.
|
||||||
|
func (c *Core) GetSwitchQueues() SwitchQueues {
|
||||||
|
var switchqueues SwitchQueues
|
||||||
|
switchTable := &c.switchTable
|
||||||
|
getSwitchQueues := func() {
|
||||||
|
switchqueues = SwitchQueues{
|
||||||
|
Count: uint64(len(switchTable.queues.bufs)),
|
||||||
|
Size: switchTable.queues.size,
|
||||||
|
HighestCount: uint64(switchTable.queues.maxbufs),
|
||||||
|
HighestSize: switchTable.queues.maxsize,
|
||||||
|
MaximumSize: switchTable.queueTotalMaxSize,
|
||||||
|
}
|
||||||
|
for k, v := range switchTable.queues.bufs {
|
||||||
|
nexthop := switchTable.bestPortForCoords([]byte(k))
|
||||||
|
queue := SwitchQueue{
|
||||||
|
ID: k,
|
||||||
|
Size: v.size,
|
||||||
|
Packets: uint64(len(v.packets)),
|
||||||
|
Port: uint64(nexthop),
|
||||||
|
}
|
||||||
|
switchqueues.Queues = append(switchqueues.Queues, queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
c.switchTable.doAdmin(getSwitchQueues)
|
||||||
|
return switchqueues
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSessions returns a list of open sessions from this node to other nodes.
|
||||||
|
func (c *Core) GetSessions() []Session {
|
||||||
|
var sessions []Session
|
||||||
|
getSessions := func() {
|
||||||
|
for _, sinfo := range c.sessions.sinfos {
|
||||||
|
// TODO? skipped known but timed out sessions?
|
||||||
|
session := Session{
|
||||||
|
Coords: append([]byte{}, sinfo.coords...),
|
||||||
|
MTU: sinfo.getMTU(),
|
||||||
|
BytesSent: sinfo.bytesSent,
|
||||||
|
BytesRecvd: sinfo.bytesRecvd,
|
||||||
|
Uptime: time.Now().Sub(sinfo.timeOpened),
|
||||||
|
WasMTUFixed: sinfo.wasMTUFixed,
|
||||||
|
}
|
||||||
|
copy(session.PublicKey[:], sinfo.theirPermPub[:])
|
||||||
|
sessions = append(sessions, session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.router.doAdmin(getSessions)
|
||||||
|
return sessions
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildName gets the current build name. This is usually injected if built
|
||||||
|
// from git, or returns "unknown" otherwise.
|
||||||
|
func BuildName() string {
|
||||||
|
if buildName == "" {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return buildName
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildVersion gets the current build version. This is usually injected if
|
||||||
|
// built from git, or returns "unknown" otherwise.
|
||||||
|
func BuildVersion() string {
|
||||||
|
if buildVersion == "" {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return buildVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenConn returns a listener for Yggdrasil session connections.
|
||||||
|
func (c *Core) ConnListen() (*Listener, error) {
|
||||||
|
c.sessions.listenerMutex.Lock()
|
||||||
|
defer c.sessions.listenerMutex.Unlock()
|
||||||
|
if c.sessions.listener != nil {
|
||||||
|
return nil, errors.New("a listener already exists")
|
||||||
|
}
|
||||||
|
c.sessions.listener = &Listener{
|
||||||
|
core: c,
|
||||||
|
conn: make(chan *Conn),
|
||||||
|
close: make(chan interface{}),
|
||||||
|
}
|
||||||
|
return c.sessions.listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnDialer returns a dialer for Yggdrasil session connections.
|
||||||
|
func (c *Core) ConnDialer() (*Dialer, error) {
|
||||||
|
return &Dialer{
|
||||||
|
core: c,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenTCP starts a new TCP listener. The input URI should match that of the
|
||||||
|
// "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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncryptionKeys generates a new encryption keypair. The encryption keys are
|
||||||
|
// used to encrypt traffic and to derive the IPv6 address/subnet of the node.
|
||||||
|
func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) {
|
||||||
|
return crypto.NewBoxKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSigningKeys generates a new signing keypair. The signing keys are used to
|
||||||
|
// derive the structure of the spanning tree.
|
||||||
|
func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) {
|
||||||
|
return crypto.NewSigKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID gets the node ID.
|
||||||
|
func (c *Core) NodeID() *crypto.NodeID {
|
||||||
|
return crypto.GetNodeID(&c.boxPub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TreeID gets the tree ID.
|
||||||
|
func (c *Core) TreeID() *crypto.TreeID {
|
||||||
|
return crypto.GetTreeID(&c.sigPub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigPubKey gets the node's signing public key.
|
||||||
|
func (c *Core) SigPubKey() string {
|
||||||
|
return hex.EncodeToString(c.sigPub[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoxPubKey gets the node's encryption public key.
|
||||||
|
func (c *Core) BoxPubKey() string {
|
||||||
|
return hex.EncodeToString(c.boxPub[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coords returns the current coordinates of the node.
|
||||||
|
func (c *Core) Coords() []byte {
|
||||||
|
table := c.switchTable.table.Load().(lookupTable)
|
||||||
|
return table.self.getCoords()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
|
||||||
|
// address.
|
||||||
|
func (c *Core) Address() *net.IP {
|
||||||
|
address := net.IP(address.AddrForNodeID(c.NodeID())[:])
|
||||||
|
return &address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a
|
||||||
|
// /64 subnet.
|
||||||
|
func (c *Core) Subnet() *net.IPNet {
|
||||||
|
subnet := address.SubnetForNodeID(c.NodeID())[:]
|
||||||
|
subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||||
|
return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouterAddresses returns the raw address and subnet types as used by the
|
||||||
|
// router
|
||||||
|
func (c *Core) RouterAddresses() (address.Address, address.Subnet) {
|
||||||
|
return c.router.addr, c.router.subnet
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeInfo gets the currently configured nodeinfo.
|
||||||
|
func (c *Core) MyNodeInfo() nodeinfoPayload {
|
||||||
|
return c.router.nodeinfo.getNodeInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct,
|
||||||
|
// it will be serialised into JSON automatically.
|
||||||
|
func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
|
||||||
|
c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeInfo requests nodeinfo from a remote node, as specified by the public
|
||||||
|
// key and coordinates specified. The third parameter specifies whether a cached
|
||||||
|
// result is acceptable - this results in less traffic being generated than is
|
||||||
|
// necessary when, e.g. crawling the network.
|
||||||
|
func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInfoPayload, error) {
|
||||||
|
var key crypto.BoxPubKey
|
||||||
|
if keyBytes, err := hex.DecodeString(keyString); err != nil {
|
||||||
|
return NodeInfoPayload{}, err
|
||||||
|
} else {
|
||||||
|
copy(key[:], keyBytes)
|
||||||
|
}
|
||||||
|
if !nocache {
|
||||||
|
if response, err := c.router.nodeinfo.getCachedNodeInfo(key); err == nil {
|
||||||
|
return NodeInfoPayload(response), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var coords []byte
|
||||||
|
for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
|
||||||
|
if cstr == "" {
|
||||||
|
// Special case, happens if trimmed is the empty string, e.g. this is the root
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
|
||||||
|
return NodeInfoPayload{}, err
|
||||||
|
} else {
|
||||||
|
coords = append(coords, uint8(u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response := make(chan *nodeinfoPayload, 1)
|
||||||
|
sendNodeInfoRequest := func() {
|
||||||
|
c.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) {
|
||||||
|
defer func() { recover() }()
|
||||||
|
select {
|
||||||
|
case response <- nodeinfo:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
})
|
||||||
|
c.router.nodeinfo.sendNodeInfo(key, coords, false)
|
||||||
|
}
|
||||||
|
c.router.doAdmin(sendNodeInfoRequest)
|
||||||
|
go func() {
|
||||||
|
time.Sleep(6 * time.Second)
|
||||||
|
close(response)
|
||||||
|
}()
|
||||||
|
for res := range response {
|
||||||
|
return NodeInfoPayload(*res), nil
|
||||||
|
}
|
||||||
|
return NodeInfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger sets the output logger of the Yggdrasil node after startup. This
|
||||||
|
// may be useful if you want to redirect the output later.
|
||||||
|
func (c *Core) SetLogger(log *log.Logger) {
|
||||||
|
c.log = log
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPeer adds a peer. This should be specified in the peer URI format, e.g.:
|
||||||
|
// tcp://a.b.c.d:e
|
||||||
|
// socks://a.b.c.d:e/f.g.h.i:j
|
||||||
|
// This adds the peer to the peer list, so that they will be called again if the
|
||||||
|
// connection drops.
|
||||||
|
func (c *Core) AddPeer(addr string, sintf string) error {
|
||||||
|
if err := c.CallPeer(addr, sintf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.config.Mutex.Lock()
|
||||||
|
if sintf == "" {
|
||||||
|
c.config.Current.Peers = append(c.config.Current.Peers, addr)
|
||||||
|
} else {
|
||||||
|
c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr)
|
||||||
|
}
|
||||||
|
c.config.Mutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePeer is not implemented yet.
|
||||||
|
func (c *Core) RemovePeer(addr string, sintf string) error {
|
||||||
|
// TODO: Implement a reverse of AddPeer, where we look up the port number
|
||||||
|
// based on the addr and sintf, disconnect it and then remove it from the
|
||||||
|
// peers list so we don't reconnect to it later
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallPeer calls a peer once. This should be specified in the peer URI format,
|
||||||
|
// e.g.:
|
||||||
|
// tcp://a.b.c.d:e
|
||||||
|
// socks://a.b.c.d:e/f.g.h.i:j
|
||||||
|
// This does not add the peer to the peer list, so if the connection drops, the
|
||||||
|
// peer will not be called again automatically.
|
||||||
|
func (c *Core) CallPeer(addr string, sintf string) error {
|
||||||
|
return c.link.call(addr, sintf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisconnectPeer disconnects a peer once. This should be specified as a port
|
||||||
|
// number.
|
||||||
|
func (c *Core) DisconnectPeer(port uint64) error {
|
||||||
|
c.peers.removePeer(switchPort(port))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming
|
||||||
|
// peer connections.
|
||||||
|
func (c *Core) GetAllowedEncryptionPublicKeys() []string {
|
||||||
|
return c.peers.getAllowedEncryptionPublicKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections.
|
||||||
|
func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) {
|
||||||
|
c.peers.addAllowedEncryptionPublicKey(bstr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllowedEncryptionPublicKey removes a key from the whitelist for
|
||||||
|
// incoming peer connections. If none are set, an empty list permits all
|
||||||
|
// incoming connections.
|
||||||
|
func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) {
|
||||||
|
c.peers.removeAllowedEncryptionPublicKey(bstr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID.
|
||||||
|
func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, error) {
|
||||||
|
var key crypto.BoxPubKey
|
||||||
|
if keyBytes, err := hex.DecodeString(keyString); err != nil {
|
||||||
|
return DHTRes{}, err
|
||||||
|
} else {
|
||||||
|
copy(key[:], keyBytes)
|
||||||
|
}
|
||||||
|
var coords []byte
|
||||||
|
for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
|
||||||
|
if cstr == "" {
|
||||||
|
// Special case, happens if trimmed is the empty string, e.g. this is the root
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
|
||||||
|
return DHTRes{}, err
|
||||||
|
} else {
|
||||||
|
coords = append(coords, uint8(u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resCh := make(chan *dhtRes, 1)
|
||||||
|
info := dhtInfo{
|
||||||
|
key: key,
|
||||||
|
coords: coords,
|
||||||
|
}
|
||||||
|
target := *info.getNodeID()
|
||||||
|
if targetString == "none" {
|
||||||
|
// Leave the default target in place
|
||||||
|
} else if targetBytes, err := hex.DecodeString(targetString); err != nil {
|
||||||
|
return DHTRes{}, err
|
||||||
|
} else if len(targetBytes) != len(target) {
|
||||||
|
return DHTRes{}, errors.New("Incorrect target NodeID length")
|
||||||
|
} else {
|
||||||
|
var target crypto.NodeID
|
||||||
|
copy(target[:], targetBytes)
|
||||||
|
}
|
||||||
|
rq := dhtReqKey{info.key, target}
|
||||||
|
sendPing := func() {
|
||||||
|
c.dht.addCallback(&rq, func(res *dhtRes) {
|
||||||
|
defer func() { recover() }()
|
||||||
|
select {
|
||||||
|
case resCh <- res:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
})
|
||||||
|
c.dht.ping(&info, &target)
|
||||||
|
}
|
||||||
|
c.router.doAdmin(sendPing)
|
||||||
|
go func() {
|
||||||
|
time.Sleep(6 * time.Second)
|
||||||
|
close(resCh)
|
||||||
|
}()
|
||||||
|
// TODO: do something better than the below...
|
||||||
|
for res := range resCh {
|
||||||
|
r := DHTRes{
|
||||||
|
Coords: append([]byte{}, res.Coords...),
|
||||||
|
}
|
||||||
|
copy(r.PublicKey[:], res.Key[:])
|
||||||
|
for _, i := range res.Infos {
|
||||||
|
e := DHTEntry{
|
||||||
|
Coords: append([]byte{}, i.coords...),
|
||||||
|
}
|
||||||
|
copy(e.PublicKey[:], i.key[:])
|
||||||
|
r.Infos = append(r.Infos, e)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
return DHTRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString))
|
||||||
|
}
|
332
src/yggdrasil/conn.go
Normal file
332
src/yggdrasil/conn.go
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
package yggdrasil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error implements the net.Error interface
|
||||||
|
type ConnError struct {
|
||||||
|
error
|
||||||
|
timeout bool
|
||||||
|
temporary bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ConnError) Timeout() bool {
|
||||||
|
return e.timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ConnError) Temporary() bool {
|
||||||
|
return e.temporary
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
core *Core
|
||||||
|
nodeID *crypto.NodeID
|
||||||
|
nodeMask *crypto.NodeID
|
||||||
|
mutex sync.RWMutex
|
||||||
|
closed bool
|
||||||
|
session *sessionInfo
|
||||||
|
readDeadline atomic.Value // time.Time // TODO timer
|
||||||
|
writeDeadline atomic.Value // time.Time // TODO timer
|
||||||
|
searching atomic.Value // bool
|
||||||
|
searchwait chan struct{} // Never reset this, it's only used for the initial search
|
||||||
|
writebuf [][]byte // Packets to be sent if/when the search finishes
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO func NewConn() that initializes additional fields as needed
|
||||||
|
func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn {
|
||||||
|
conn := Conn{
|
||||||
|
core: core,
|
||||||
|
nodeID: nodeID,
|
||||||
|
nodeMask: nodeMask,
|
||||||
|
session: session,
|
||||||
|
searchwait: make(chan struct{}),
|
||||||
|
}
|
||||||
|
conn.searching.Store(false)
|
||||||
|
return &conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) String() string {
|
||||||
|
return fmt.Sprintf("conn=%p", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method should only be called from the router goroutine
|
||||||
|
func (c *Conn) startSearch() {
|
||||||
|
// The searchCompleted callback is given to the search
|
||||||
|
searchCompleted := func(sinfo *sessionInfo, err error) {
|
||||||
|
defer c.searching.Store(false)
|
||||||
|
// If the search failed for some reason, e.g. it hit a dead end or timed
|
||||||
|
// out, then do nothing
|
||||||
|
if err != nil {
|
||||||
|
c.core.log.Debugln(c.String(), "DHT search failed:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Take the connection mutex
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
// Were we successfully given a sessionInfo pointer?
|
||||||
|
if sinfo != nil {
|
||||||
|
// Store it, and update the nodeID and nodeMask (which may have been
|
||||||
|
// wildcarded before now) with their complete counterparts
|
||||||
|
c.core.log.Debugln(c.String(), "DHT search completed")
|
||||||
|
c.session = sinfo
|
||||||
|
c.nodeID = crypto.GetNodeID(&sinfo.theirPermPub)
|
||||||
|
for i := range c.nodeMask {
|
||||||
|
c.nodeMask[i] = 0xFF
|
||||||
|
}
|
||||||
|
// Make sure that any blocks on read/write operations are lifted
|
||||||
|
defer func() { recover() }() // So duplicate searches don't panic
|
||||||
|
close(c.searchwait)
|
||||||
|
} else {
|
||||||
|
// No session was returned - this shouldn't really happen because we
|
||||||
|
// should always return an error reason if we don't return a session
|
||||||
|
panic("DHT search didn't return an error or a sessionInfo")
|
||||||
|
}
|
||||||
|
if c.closed {
|
||||||
|
// Things were closed before the search returned
|
||||||
|
// Go ahead and close it again to make sure the session is cleaned up
|
||||||
|
go c.Close()
|
||||||
|
} else {
|
||||||
|
// Send any messages we may have buffered
|
||||||
|
var msgs [][]byte
|
||||||
|
msgs, c.writebuf = c.writebuf, nil
|
||||||
|
go func() {
|
||||||
|
for _, msg := range msgs {
|
||||||
|
c.Write(msg)
|
||||||
|
util.PutBytes(msg)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// doSearch will be called below in response to one or more conditions
|
||||||
|
doSearch := func() {
|
||||||
|
c.searching.Store(true)
|
||||||
|
// Check to see if there is a search already matching the destination
|
||||||
|
sinfo, isIn := c.core.searches.searches[*c.nodeID]
|
||||||
|
if !isIn {
|
||||||
|
// Nothing was found, so create a new search
|
||||||
|
sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
|
||||||
|
c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo)
|
||||||
|
}
|
||||||
|
// Continue the search
|
||||||
|
c.core.searches.continueSearch(sinfo)
|
||||||
|
}
|
||||||
|
// Take a copy of the session object, in case it changes later
|
||||||
|
c.mutex.RLock()
|
||||||
|
sinfo := c.session
|
||||||
|
c.mutex.RUnlock()
|
||||||
|
if c.session == nil {
|
||||||
|
// No session object is present so previous searches, if we ran any, have
|
||||||
|
// not yielded a useful result (dead end, remote host not found)
|
||||||
|
doSearch()
|
||||||
|
} else {
|
||||||
|
sinfo.worker <- func() {
|
||||||
|
switch {
|
||||||
|
case !sinfo.init:
|
||||||
|
doSearch()
|
||||||
|
case time.Since(sinfo.time) > 6*time.Second:
|
||||||
|
if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second {
|
||||||
|
// TODO double check that the above condition is correct
|
||||||
|
doSearch()
|
||||||
|
} else {
|
||||||
|
c.core.sessions.ping(sinfo)
|
||||||
|
}
|
||||||
|
default: // Don't do anything, to keep traffic throttled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDeadlineTimer(value *atomic.Value) *time.Timer {
|
||||||
|
timer := time.NewTimer(24 * 365 * time.Hour) // FIXME for some reason setting this to 0 doesn't always let it stop and drain the channel correctly
|
||||||
|
util.TimerStop(timer)
|
||||||
|
if deadline, ok := value.Load().(time.Time); ok {
|
||||||
|
timer.Reset(time.Until(deadline))
|
||||||
|
}
|
||||||
|
return timer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Read(b []byte) (int, error) {
|
||||||
|
// Take a copy of the session object
|
||||||
|
c.mutex.RLock()
|
||||||
|
sinfo := c.session
|
||||||
|
c.mutex.RUnlock()
|
||||||
|
timer := getDeadlineTimer(&c.readDeadline)
|
||||||
|
defer util.TimerStop(timer)
|
||||||
|
// If there is a search in progress then wait for the result
|
||||||
|
if sinfo == nil {
|
||||||
|
// Wait for the search to complete
|
||||||
|
select {
|
||||||
|
case <-c.searchwait:
|
||||||
|
case <-timer.C:
|
||||||
|
return 0, ConnError{errors.New("Timeout"), true, false}
|
||||||
|
}
|
||||||
|
// Retrieve our session info again
|
||||||
|
c.mutex.RLock()
|
||||||
|
sinfo = c.session
|
||||||
|
c.mutex.RUnlock()
|
||||||
|
// If sinfo is still nil at this point then the search failed and the
|
||||||
|
// searchwait channel has been recreated, so might as well give up and
|
||||||
|
// return an error code
|
||||||
|
if sinfo == nil {
|
||||||
|
return 0, errors.New("search failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wait for some traffic to come through from the session
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
return 0, ConnError{errors.New("Timeout"), true, false}
|
||||||
|
case p, ok := <-sinfo.recv:
|
||||||
|
// If the session is closed then do nothing
|
||||||
|
if !ok {
|
||||||
|
return 0, errors.New("session is closed")
|
||||||
|
}
|
||||||
|
defer util.PutBytes(p.Payload)
|
||||||
|
var err error
|
||||||
|
done := make(chan struct{})
|
||||||
|
workerFunc := func() {
|
||||||
|
defer close(done)
|
||||||
|
// If the nonce is bad then drop the packet and return an error
|
||||||
|
if !sinfo.nonceIsOK(&p.Nonce) {
|
||||||
|
err = errors.New("packet dropped due to invalid nonce")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Decrypt the packet
|
||||||
|
bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
|
||||||
|
defer util.PutBytes(bs) // FIXME commenting this out leads to illegal buffer reuse, this implies there's a memory error somewhere and that this is just flooding things out of the finite pool of old slices that get reused
|
||||||
|
// Check if we were unable to decrypt the packet for some reason and
|
||||||
|
// return an error if we couldn't
|
||||||
|
if !isOK {
|
||||||
|
err = errors.New("packet dropped due to decryption failure")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Return the newly decrypted buffer back to the slice we were given
|
||||||
|
copy(b, bs)
|
||||||
|
// Trim the slice down to size based on the data we received
|
||||||
|
if len(bs) < len(b) {
|
||||||
|
b = b[:len(bs)]
|
||||||
|
}
|
||||||
|
// Update the session
|
||||||
|
sinfo.updateNonce(&p.Nonce)
|
||||||
|
sinfo.time = time.Now()
|
||||||
|
sinfo.bytesRecvd += uint64(len(b))
|
||||||
|
}
|
||||||
|
// Hand over to the session worker
|
||||||
|
select { // Send to worker
|
||||||
|
case sinfo.worker <- workerFunc:
|
||||||
|
case <-timer.C:
|
||||||
|
return 0, ConnError{errors.New("Timeout"), true, false}
|
||||||
|
}
|
||||||
|
<-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff)
|
||||||
|
// Something went wrong in the session worker so abort
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// If we've reached this point then everything went to plan, return the
|
||||||
|
// number of bytes we populated back into the given slice
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Write(b []byte) (bytesWritten int, err error) {
|
||||||
|
c.mutex.RLock()
|
||||||
|
sinfo := c.session
|
||||||
|
c.mutex.RUnlock()
|
||||||
|
// If the session doesn't exist, or isn't initialised (which probably means
|
||||||
|
// that the search didn't complete successfully) then we may need to wait for
|
||||||
|
// the search to complete or start the search again
|
||||||
|
if sinfo == nil || !sinfo.init {
|
||||||
|
// Is a search already taking place?
|
||||||
|
if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) {
|
||||||
|
// No search was already taking place so start a new one
|
||||||
|
c.core.router.doAdmin(c.startSearch)
|
||||||
|
}
|
||||||
|
// Buffer the packet to be sent if/when the search is finished
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
c.writebuf = append(c.writebuf, append(util.GetBytes(), b...))
|
||||||
|
for len(c.writebuf) > 32 {
|
||||||
|
util.PutBytes(c.writebuf[0])
|
||||||
|
c.writebuf = c.writebuf[1:]
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
var packet []byte
|
||||||
|
done := make(chan struct{})
|
||||||
|
workerFunc := func() {
|
||||||
|
defer close(done)
|
||||||
|
// Encrypt the packet
|
||||||
|
payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce)
|
||||||
|
defer util.PutBytes(payload)
|
||||||
|
// Construct the wire packet to send to the router
|
||||||
|
p := wire_trafficPacket{
|
||||||
|
Coords: sinfo.coords,
|
||||||
|
Handle: sinfo.theirHandle,
|
||||||
|
Nonce: *nonce,
|
||||||
|
Payload: payload,
|
||||||
|
}
|
||||||
|
packet = p.encode()
|
||||||
|
sinfo.bytesSent += uint64(len(b))
|
||||||
|
}
|
||||||
|
// Set up a timer so this doesn't block forever
|
||||||
|
timer := getDeadlineTimer(&c.writeDeadline)
|
||||||
|
defer util.TimerStop(timer)
|
||||||
|
// Hand over to the session worker
|
||||||
|
select { // Send to worker
|
||||||
|
case sinfo.worker <- workerFunc:
|
||||||
|
case <-timer.C:
|
||||||
|
return 0, ConnError{errors.New("Timeout"), true, false}
|
||||||
|
}
|
||||||
|
// Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff)
|
||||||
|
<-done
|
||||||
|
// Give the packet to the router
|
||||||
|
sinfo.core.router.out(packet)
|
||||||
|
// Finally return the number of bytes we wrote
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
if c.session != nil {
|
||||||
|
// Close the session, if it hasn't been closed already
|
||||||
|
c.session.close()
|
||||||
|
c.session = nil
|
||||||
|
}
|
||||||
|
// This can't fail yet - TODO?
|
||||||
|
c.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) LocalAddr() crypto.NodeID {
|
||||||
|
return *crypto.GetNodeID(&c.session.core.boxPub)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) RemoteAddr() crypto.NodeID {
|
||||||
|
c.mutex.RLock()
|
||||||
|
defer c.mutex.RUnlock()
|
||||||
|
return *c.nodeID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SetDeadline(t time.Time) error {
|
||||||
|
c.SetReadDeadline(t)
|
||||||
|
c.SetWriteDeadline(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||||
|
c.readDeadline.Store(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||||
|
c.writeDeadline.Store(t)
|
||||||
|
return nil
|
||||||
|
}
|
@ -2,14 +2,11 @@ package yggdrasil
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
)
|
)
|
||||||
@ -33,7 +30,6 @@ type Core struct {
|
|||||||
sessions sessions
|
sessions sessions
|
||||||
router router
|
router router
|
||||||
dht dht
|
dht dht
|
||||||
admin admin
|
|
||||||
searches searches
|
searches searches
|
||||||
link link
|
link link
|
||||||
log *log.Logger
|
log *log.Logger
|
||||||
@ -72,11 +68,9 @@ func (c *Core) init() error {
|
|||||||
copy(c.sigPub[:], sigPubHex)
|
copy(c.sigPub[:], sigPubHex)
|
||||||
copy(c.sigPriv[:], sigPrivHex)
|
copy(c.sigPriv[:], sigPrivHex)
|
||||||
|
|
||||||
c.admin.init(c)
|
|
||||||
c.searches.init(c)
|
c.searches.init(c)
|
||||||
c.dht.init(c)
|
c.dht.init(c)
|
||||||
c.sessions.init(c)
|
c.sessions.init(c)
|
||||||
//c.multicast.init(c)
|
|
||||||
c.peers.init(c)
|
c.peers.init(c)
|
||||||
c.router.init(c)
|
c.router.init(c)
|
||||||
c.switchTable.init(c) // TODO move before peers? before router?
|
c.switchTable.init(c) // TODO move before peers? before router?
|
||||||
@ -115,20 +109,18 @@ func (c *Core) addPeerLoop() {
|
|||||||
// config.NodeConfig and then signals the various module goroutines to
|
// config.NodeConfig and then signals the various module goroutines to
|
||||||
// reconfigure themselves if needed.
|
// reconfigure themselves if needed.
|
||||||
func (c *Core) UpdateConfig(config *config.NodeConfig) {
|
func (c *Core) UpdateConfig(config *config.NodeConfig) {
|
||||||
c.log.Infoln("Reloading configuration...")
|
c.log.Debugln("Reloading node configuration...")
|
||||||
|
|
||||||
c.config.Replace(*config)
|
c.config.Replace(*config)
|
||||||
|
|
||||||
errors := 0
|
errors := 0
|
||||||
|
|
||||||
components := []chan chan error{
|
components := []chan chan error{
|
||||||
c.admin.reconfigure,
|
|
||||||
c.searches.reconfigure,
|
c.searches.reconfigure,
|
||||||
c.dht.reconfigure,
|
c.dht.reconfigure,
|
||||||
c.sessions.reconfigure,
|
c.sessions.reconfigure,
|
||||||
c.peers.reconfigure,
|
c.peers.reconfigure,
|
||||||
c.router.reconfigure,
|
c.router.reconfigure,
|
||||||
c.router.cryptokey.reconfigure,
|
|
||||||
c.switchTable.reconfigure,
|
c.switchTable.reconfigure,
|
||||||
c.link.reconfigure,
|
c.link.reconfigure,
|
||||||
}
|
}
|
||||||
@ -143,45 +135,12 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if errors > 0 {
|
if errors > 0 {
|
||||||
c.log.Warnln(errors, "modules reported errors during configuration reload")
|
c.log.Warnln(errors, "node module(s) reported errors during configuration reload")
|
||||||
} else {
|
} else {
|
||||||
c.log.Infoln("Configuration reloaded successfully")
|
c.log.Infoln("Node configuration reloaded successfully")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildName gets the current build name. This is usually injected if built
|
|
||||||
// from git, or returns "unknown" otherwise.
|
|
||||||
func BuildName() string {
|
|
||||||
if buildName == "" {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
return buildName
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildVersion gets the current build version. This is usually injected if
|
|
||||||
// built from git, or returns "unknown" otherwise.
|
|
||||||
func BuildVersion() string {
|
|
||||||
if buildVersion == "" {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
return buildVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRouterAdapter instructs Yggdrasil to use the given adapter when starting
|
|
||||||
// the router. The adapter must implement the standard
|
|
||||||
// adapter.adapterImplementation interface and should extend the adapter.Adapter
|
|
||||||
// struct.
|
|
||||||
func (c *Core) SetRouterAdapter(adapter interface{}) error {
|
|
||||||
// We do this because adapterImplementation is not a valid type for the
|
|
||||||
// gomobile bindings so we just ask for a generic interface and try to cast it
|
|
||||||
// to adapterImplementation instead
|
|
||||||
if a, ok := adapter.(adapterImplementation); ok {
|
|
||||||
c.router.adapter = a
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("unsuitable adapter")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs
|
// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs
|
||||||
// debug logging through the provided log.Logger. The started stack will include
|
// debug logging through the provided log.Logger. The started stack will include
|
||||||
// TCP and UDP sockets, a multicast discovery socket, an admin socket, router,
|
// TCP and UDP sockets, a multicast discovery socket, an admin socket, router,
|
||||||
@ -227,18 +186,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.admin.start(); err != nil {
|
|
||||||
c.log.Errorln("Failed to start admin socket")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.router.adapter != nil {
|
|
||||||
if err := c.router.adapter.Start(c.router.addr, c.router.subnet); err != nil {
|
|
||||||
c.log.Errorln("Failed to start TUN/TAP")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go c.addPeerLoop()
|
go c.addPeerLoop()
|
||||||
|
|
||||||
c.log.Infoln("Startup complete")
|
c.log.Infoln("Startup complete")
|
||||||
@ -248,120 +195,4 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState,
|
|||||||
// Stop shuts down the Yggdrasil node.
|
// Stop shuts down the Yggdrasil node.
|
||||||
func (c *Core) Stop() {
|
func (c *Core) Stop() {
|
||||||
c.log.Infoln("Stopping...")
|
c.log.Infoln("Stopping...")
|
||||||
if c.router.adapter != nil {
|
|
||||||
c.router.adapter.Close()
|
|
||||||
}
|
|
||||||
c.admin.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenTCP starts a new TCP listener. The input URI should match that of the
|
|
||||||
// "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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncryptionKeys generates a new encryption keypair. The encryption keys are
|
|
||||||
// used to encrypt traffic and to derive the IPv6 address/subnet of the node.
|
|
||||||
func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) {
|
|
||||||
return crypto.NewBoxKeys()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSigningKeys generates a new signing keypair. The signing keys are used to
|
|
||||||
// derive the structure of the spanning tree.
|
|
||||||
func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) {
|
|
||||||
return crypto.NewSigKeys()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeID gets the node ID.
|
|
||||||
func (c *Core) NodeID() *crypto.NodeID {
|
|
||||||
return crypto.GetNodeID(&c.boxPub)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TreeID gets the tree ID.
|
|
||||||
func (c *Core) TreeID() *crypto.TreeID {
|
|
||||||
return crypto.GetTreeID(&c.sigPub)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigPubKey gets the node's signing public key.
|
|
||||||
func (c *Core) SigPubKey() string {
|
|
||||||
return hex.EncodeToString(c.sigPub[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoxPubKey gets the node's encryption public key.
|
|
||||||
func (c *Core) BoxPubKey() string {
|
|
||||||
return hex.EncodeToString(c.boxPub[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
|
|
||||||
// address.
|
|
||||||
func (c *Core) Address() *net.IP {
|
|
||||||
address := net.IP(address.AddrForNodeID(c.NodeID())[:])
|
|
||||||
return &address
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a
|
|
||||||
// /64 subnet.
|
|
||||||
func (c *Core) Subnet() *net.IPNet {
|
|
||||||
subnet := address.SubnetForNodeID(c.NodeID())[:]
|
|
||||||
subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
||||||
return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RouterAddresses returns the raw address and subnet types as used by the
|
|
||||||
// router
|
|
||||||
func (c *Core) RouterAddresses() (address.Address, address.Subnet) {
|
|
||||||
return c.router.addr, c.router.subnet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeInfo gets the currently configured nodeinfo.
|
|
||||||
func (c *Core) NodeInfo() nodeinfoPayload {
|
|
||||||
return c.router.nodeinfo.getNodeInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct,
|
|
||||||
// it will be serialised into JSON automatically.
|
|
||||||
func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
|
|
||||||
c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLogger sets the output logger of the Yggdrasil node after startup. This
|
|
||||||
// may be useful if you want to redirect the output later.
|
|
||||||
func (c *Core) SetLogger(log *log.Logger) {
|
|
||||||
c.log = log
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPeer adds a peer. This should be specified in the peer URI format, e.g.:
|
|
||||||
// tcp://a.b.c.d:e
|
|
||||||
// socks://a.b.c.d:e/f.g.h.i:j
|
|
||||||
// This adds the peer to the peer list, so that they will be called again if the
|
|
||||||
// connection drops.
|
|
||||||
func (c *Core) AddPeer(addr string, sintf string) error {
|
|
||||||
if err := c.CallPeer(addr, sintf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.config.Mutex.Lock()
|
|
||||||
if sintf == "" {
|
|
||||||
c.config.Current.Peers = append(c.config.Current.Peers, addr)
|
|
||||||
} else {
|
|
||||||
c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr)
|
|
||||||
}
|
|
||||||
c.config.Mutex.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallPeer calls a peer once. This should be specified in the peer URI format,
|
|
||||||
// e.g.:
|
|
||||||
// tcp://a.b.c.d:e
|
|
||||||
// socks://a.b.c.d:e/f.g.h.i:j
|
|
||||||
// This does not add the peer to the peer list, so if the connection drops, the
|
|
||||||
// peer will not be called again automatically.
|
|
||||||
func (c *Core) CallPeer(addr string, sintf string) error {
|
|
||||||
return c.link.call(addr, sintf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings
|
|
||||||
// to be restricted only to keys that you have selected.
|
|
||||||
func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error {
|
|
||||||
return c.admin.addAllowedEncryptionPublicKey(boxStr)
|
|
||||||
}
|
}
|
||||||
|
@ -59,13 +59,17 @@ func (c *Core) Init() {
|
|||||||
hbpriv := hex.EncodeToString(bpriv[:])
|
hbpriv := hex.EncodeToString(bpriv[:])
|
||||||
hspub := hex.EncodeToString(spub[:])
|
hspub := hex.EncodeToString(spub[:])
|
||||||
hspriv := hex.EncodeToString(spriv[:])
|
hspriv := hex.EncodeToString(spriv[:])
|
||||||
c.config = config.NodeConfig{
|
cfg := config.NodeConfig{
|
||||||
EncryptionPublicKey: hbpub,
|
EncryptionPublicKey: hbpub,
|
||||||
EncryptionPrivateKey: hbpriv,
|
EncryptionPrivateKey: hbpriv,
|
||||||
SigningPublicKey: hspub,
|
SigningPublicKey: hspub,
|
||||||
SigningPrivateKey: hspriv,
|
SigningPrivateKey: hspriv,
|
||||||
}
|
}
|
||||||
c.init( /*bpub, bpriv, spub, spriv*/ )
|
c.config = config.NodeState{
|
||||||
|
Current: cfg,
|
||||||
|
Previous: cfg,
|
||||||
|
}
|
||||||
|
c.init()
|
||||||
c.switchTable.start()
|
c.switchTable.start()
|
||||||
c.router.start()
|
c.router.start()
|
||||||
}
|
}
|
||||||
@ -82,6 +86,7 @@ func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey {
|
|||||||
return (crypto.BoxPubKey)(c.boxPub)
|
return (crypto.BoxPubKey)(c.boxPub)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (c *Core) DEBUG_getSend() chan<- []byte {
|
func (c *Core) DEBUG_getSend() chan<- []byte {
|
||||||
return c.router.tun.send
|
return c.router.tun.send
|
||||||
}
|
}
|
||||||
@ -89,6 +94,7 @@ func (c *Core) DEBUG_getSend() chan<- []byte {
|
|||||||
func (c *Core) DEBUG_getRecv() <-chan []byte {
|
func (c *Core) DEBUG_getRecv() <-chan []byte {
|
||||||
return c.router.tun.recv
|
return c.router.tun.recv
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Peer
|
// Peer
|
||||||
|
|
||||||
@ -317,6 +323,7 @@ func (c *Core) DEBUG_getAddr() *address.Address {
|
|||||||
return address.AddrForNodeID(&c.dht.nodeID)
|
return address.AddrForNodeID(&c.dht.nodeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
|
func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
|
||||||
c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280)
|
c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280)
|
||||||
}
|
}
|
||||||
@ -338,6 +345,7 @@ func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) {
|
|||||||
func (c *Core) DEBUG_stopTun() {
|
func (c *Core) DEBUG_stopTun() {
|
||||||
c.router.tun.close()
|
c.router.tun.close()
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@ -382,13 +390,17 @@ func (c *Core) DEBUG_init(bpub []byte,
|
|||||||
hbpriv := hex.EncodeToString(bpriv[:])
|
hbpriv := hex.EncodeToString(bpriv[:])
|
||||||
hspub := hex.EncodeToString(spub[:])
|
hspub := hex.EncodeToString(spub[:])
|
||||||
hspriv := hex.EncodeToString(spriv[:])
|
hspriv := hex.EncodeToString(spriv[:])
|
||||||
c.config = config.NodeConfig{
|
cfg := config.NodeConfig{
|
||||||
EncryptionPublicKey: hbpub,
|
EncryptionPublicKey: hbpub,
|
||||||
EncryptionPrivateKey: hbpriv,
|
EncryptionPrivateKey: hbpriv,
|
||||||
SigningPublicKey: hspub,
|
SigningPublicKey: hspub,
|
||||||
SigningPrivateKey: hspriv,
|
SigningPrivateKey: hspriv,
|
||||||
}
|
}
|
||||||
c.init( /*bpub, bpriv, spub, spriv*/ )
|
c.config = config.NodeState{
|
||||||
|
Current: cfg,
|
||||||
|
Previous: cfg,
|
||||||
|
}
|
||||||
|
c.init()
|
||||||
|
|
||||||
if err := c.router.start(); err != nil {
|
if err := c.router.start(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -455,7 +467,7 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//*
|
/*
|
||||||
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
|
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
|
||||||
c.config.Listen = []string{addrport}
|
c.config.Listen = []string{addrport}
|
||||||
if err := c.link.init(c); err != nil {
|
if err := c.link.init(c); err != nil {
|
||||||
@ -503,10 +515,11 @@ func (c *Core) DEBUG_addKCPConn(saddr string) {
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) {
|
func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) {
|
||||||
a := admin{}
|
a := admin{}
|
||||||
c.config.AdminListen = addrport
|
c.config.AdminListen = addrport
|
||||||
a.init(c /*, addrport*/)
|
a.init()
|
||||||
c.admin = a
|
c.admin = a
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,6 +529,7 @@ func (c *Core) DEBUG_setupAndStartMulticastInterface() {
|
|||||||
c.multicast = m
|
c.multicast = m
|
||||||
m.start()
|
m.start()
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@ -579,9 +593,11 @@ func DEBUG_simLinkPeers(p, q *peer) {
|
|||||||
q.core.switchTable.idleIn <- q.port
|
q.core.switchTable.idleIn <- q.port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (c *Core) DEBUG_simFixMTU() {
|
func (c *Core) DEBUG_simFixMTU() {
|
||||||
c.router.tun.mtu = 65535
|
c.router.tun.mtu = 65535
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
package yggdrasil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wrappedConn implements net.Conn
|
|
||||||
type wrappedConn struct {
|
|
||||||
c net.Conn
|
|
||||||
raddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrappedAddr implements net.Addr
|
|
||||||
type wrappedAddr struct {
|
|
||||||
network string
|
|
||||||
addr string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *wrappedAddr) Network() string {
|
|
||||||
return a.network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *wrappedAddr) String() string {
|
|
||||||
return a.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrappedConn) Write(data []byte) (int, error) {
|
|
||||||
return c.c.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrappedConn) Read(data []byte) (int, error) {
|
|
||||||
return c.c.Read(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrappedConn) SetDeadline(t time.Time) error {
|
|
||||||
return c.c.SetDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrappedConn) SetReadDeadline(t time.Time) error {
|
|
||||||
return c.c.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return c.c.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrappedConn) Close() error {
|
|
||||||
return c.c.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrappedConn) LocalAddr() net.Addr {
|
|
||||||
return c.c.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrappedConn) RemoteAddr() net.Addr {
|
|
||||||
return c.raddr
|
|
||||||
}
|
|
62
src/yggdrasil/dialer.go
Normal file
62
src/yggdrasil/dialer.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package yggdrasil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dialer represents an Yggdrasil connection dialer.
|
||||||
|
type Dialer struct {
|
||||||
|
core *Core
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var nodeID crypto.NodeID
|
||||||
|
var nodeMask crypto.NodeID
|
||||||
|
// Process
|
||||||
|
switch network {
|
||||||
|
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])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dest, err := hex.DecodeString(tokens[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
copy(nodeID[:], dest)
|
||||||
|
for idx := 0; idx < len; idx++ {
|
||||||
|
nodeMask[idx/8] |= 0x80 >> byte(idx%8)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dest, err := hex.DecodeString(tokens[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
copy(nodeID[:], dest)
|
||||||
|
for i := range nodeMask {
|
||||||
|
nodeMask[i] = 0xFF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d.DialByNodeIDandMask(&nodeID, &nodeMask)
|
||||||
|
default:
|
||||||
|
// An unexpected address type was given, so give up
|
||||||
|
return nil, errors.New("unexpected address type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialByNodeIDandMask opens a session to the given node based on raw
|
||||||
|
// NodeID parameters.
|
||||||
|
func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) {
|
||||||
|
conn := newConn(d.core, nodeID, nodeMask, nil)
|
||||||
|
return conn, nil
|
||||||
|
}
|
45
src/yggdrasil/listener.go
Normal file
45
src/yggdrasil/listener.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package yggdrasil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Listener waits for incoming sessions
|
||||||
|
type Listener struct {
|
||||||
|
core *Core
|
||||||
|
conn chan *Conn
|
||||||
|
close chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept blocks until a new incoming session is received
|
||||||
|
func (l *Listener) Accept() (*Conn, error) {
|
||||||
|
select {
|
||||||
|
case c, ok := <-l.conn:
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("listener closed")
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
case <-l.close:
|
||||||
|
return nil, errors.New("listener closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close will stop the listener
|
||||||
|
func (l *Listener) Close() (err error) {
|
||||||
|
defer func() {
|
||||||
|
recover()
|
||||||
|
err = errors.New("already closed")
|
||||||
|
}()
|
||||||
|
if l.core.sessions.listener == l {
|
||||||
|
l.core.sessions.listener = nil
|
||||||
|
}
|
||||||
|
close(l.close)
|
||||||
|
close(l.conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr is not implemented for this type yet
|
||||||
|
func (l *Listener) Addr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
@ -23,7 +23,8 @@ package yggdrasil
|
|||||||
// The router then runs some sanity checks before passing it to the adapter
|
// The router then runs some sanity checks before passing it to the adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
//"bytes"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
@ -38,46 +39,13 @@ type router struct {
|
|||||||
reconfigure chan chan error
|
reconfigure chan chan error
|
||||||
addr address.Address
|
addr address.Address
|
||||||
subnet address.Subnet
|
subnet address.Subnet
|
||||||
in <-chan []byte // packets we received from the network, link to peer's "out"
|
in <-chan []byte // packets we received from the network, link to peer's "out"
|
||||||
out func([]byte) // packets we're sending to the network, link to peer's "in"
|
out func([]byte) // packets we're sending to the network, link to peer's "in"
|
||||||
toRecv chan router_recvPacket // packets to handle via recvPacket()
|
reset chan struct{} // signal that coords changed (re-init sessions/dht)
|
||||||
adapter adapterImplementation // TUN/TAP adapter
|
admin chan func() // pass a lambda for the admin socket to query stuff
|
||||||
recv chan<- []byte // place where the adapter pulls received packets from
|
|
||||||
send <-chan []byte // place where the adapter puts outgoing packets
|
|
||||||
reject chan<- RejectedPacket // place where we send error packets back to adapter
|
|
||||||
reset chan struct{} // signal that coords changed (re-init sessions/dht)
|
|
||||||
admin chan func() // pass a lambda for the admin socket to query stuff
|
|
||||||
cryptokey cryptokey
|
|
||||||
nodeinfo nodeinfo
|
nodeinfo nodeinfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the adapter.
|
|
||||||
type router_recvPacket struct {
|
|
||||||
bs []byte
|
|
||||||
sinfo *sessionInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// RejectedPacketReason is the type code used to represent the reason that a
|
|
||||||
// packet was rejected.
|
|
||||||
type RejectedPacketReason int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// The router rejected the packet because it exceeds the session MTU for the
|
|
||||||
// given destination. In TUN/TAP, this results in the generation of an ICMPv6
|
|
||||||
// Packet Too Big message.
|
|
||||||
PacketTooBig = 1 + iota
|
|
||||||
)
|
|
||||||
|
|
||||||
// RejectedPacket represents a rejected packet from the router. This is passed
|
|
||||||
// back to the adapter so that the adapter can respond appropriately, e.g. in
|
|
||||||
// the case of TUN/TAP, a "PacketTooBig" reason can be used to generate an
|
|
||||||
// ICMPv6 Packet Too Big response.
|
|
||||||
type RejectedPacket struct {
|
|
||||||
Reason RejectedPacketReason
|
|
||||||
Packet []byte
|
|
||||||
Detail interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initializes the router struct, which includes setting up channels to/from the adapter.
|
// Initializes the router struct, which includes setting up channels to/from the adapter.
|
||||||
func (r *router) init(core *Core) {
|
func (r *router) init(core *Core) {
|
||||||
r.core = core
|
r.core = core
|
||||||
@ -122,23 +90,12 @@ func (r *router) init(core *Core) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
r.out = func(packet []byte) { out2 <- packet }
|
r.out = func(packet []byte) { out2 <- packet }
|
||||||
r.toRecv = make(chan router_recvPacket, 32)
|
|
||||||
recv := make(chan []byte, 32)
|
|
||||||
send := make(chan []byte, 32)
|
|
||||||
reject := make(chan RejectedPacket, 32)
|
|
||||||
r.recv = recv
|
|
||||||
r.send = send
|
|
||||||
r.reject = reject
|
|
||||||
r.reset = make(chan struct{}, 1)
|
r.reset = make(chan struct{}, 1)
|
||||||
r.admin = make(chan func(), 32)
|
r.admin = make(chan func(), 32)
|
||||||
r.nodeinfo.init(r.core)
|
r.nodeinfo.init(r.core)
|
||||||
r.core.config.Mutex.RLock()
|
r.core.config.Mutex.RLock()
|
||||||
r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy)
|
r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy)
|
||||||
r.core.config.Mutex.RUnlock()
|
r.core.config.Mutex.RUnlock()
|
||||||
r.cryptokey.init(r.core)
|
|
||||||
if r.adapter != nil {
|
|
||||||
r.adapter.Init(&r.core.config, r.core.log, send, recv, reject)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts the mainLoop goroutine.
|
// Starts the mainLoop goroutine.
|
||||||
@ -157,12 +114,8 @@ func (r *router) mainLoop() {
|
|||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case rp := <-r.toRecv:
|
|
||||||
r.recvPacket(rp.bs, rp.sinfo)
|
|
||||||
case p := <-r.in:
|
case p := <-r.in:
|
||||||
r.handleIn(p)
|
r.handleIn(p)
|
||||||
case p := <-r.send:
|
|
||||||
r.sendPacket(p)
|
|
||||||
case info := <-r.core.dht.peers:
|
case info := <-r.core.dht.peers:
|
||||||
r.core.dht.insertPeer(info)
|
r.core.dht.insertPeer(info)
|
||||||
case <-r.reset:
|
case <-r.reset:
|
||||||
@ -185,6 +138,7 @@ func (r *router) mainLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// Checks a packet's to/from address to make sure it's in the allowed range.
|
// Checks a packet's to/from address to make sure it's in the allowed range.
|
||||||
// If a session to the destination exists, gets the session and passes the packet to it.
|
// If a session to the destination exists, gets the session and passes the packet to it.
|
||||||
// If no session exists, it triggers (or continues) a search.
|
// If no session exists, it triggers (or continues) a search.
|
||||||
@ -245,6 +199,12 @@ func (r *router) sendPacket(bs []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
searchCompleted := func(sinfo *sessionInfo, err error) {
|
||||||
|
if err != nil {
|
||||||
|
r.core.log.Debugln("DHT search failed:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
doSearch := func(packet []byte) {
|
doSearch := func(packet []byte) {
|
||||||
var nodeID, mask *crypto.NodeID
|
var nodeID, mask *crypto.NodeID
|
||||||
switch {
|
switch {
|
||||||
@ -270,7 +230,7 @@ func (r *router) sendPacket(bs []byte) {
|
|||||||
}
|
}
|
||||||
sinfo, isIn := r.core.searches.searches[*nodeID]
|
sinfo, isIn := r.core.searches.searches[*nodeID]
|
||||||
if !isIn {
|
if !isIn {
|
||||||
sinfo = r.core.searches.newIterSearch(nodeID, mask)
|
sinfo = r.core.searches.newIterSearch(nodeID, mask, searchCompleted)
|
||||||
}
|
}
|
||||||
if packet != nil {
|
if packet != nil {
|
||||||
sinfo.packet = packet
|
sinfo.packet = packet
|
||||||
@ -285,12 +245,15 @@ func (r *router) sendPacket(bs []byte) {
|
|||||||
if destSnet.IsValid() {
|
if destSnet.IsValid() {
|
||||||
sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet)
|
sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet)
|
||||||
}
|
}
|
||||||
|
sTime := sinfo.time.Load().(time.Time)
|
||||||
|
pingTime := sinfo.pingTime.Load().(time.Time)
|
||||||
|
pingSend := sinfo.pingSend.Load().(time.Time)
|
||||||
switch {
|
switch {
|
||||||
case !isIn || !sinfo.init:
|
case !isIn || !sinfo.init.Load().(bool):
|
||||||
// No or unintiialized session, so we need to search first
|
// No or unintiialized session, so we need to search first
|
||||||
doSearch(bs)
|
doSearch(bs)
|
||||||
case time.Since(sinfo.time) > 6*time.Second:
|
case time.Since(sTime) > 6*time.Second:
|
||||||
if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second {
|
if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second {
|
||||||
// We haven't heard from the dest in a while
|
// We haven't heard from the dest in a while
|
||||||
// We tried pinging but didn't get a response
|
// We tried pinging but didn't get a response
|
||||||
// They may have changed coords
|
// They may have changed coords
|
||||||
@ -300,13 +263,14 @@ func (r *router) sendPacket(bs []byte) {
|
|||||||
} else {
|
} else {
|
||||||
// We haven't heard about the dest in a while
|
// We haven't heard about the dest in a while
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if !sinfo.time.Before(sinfo.pingTime) {
|
|
||||||
|
if !sTime.Before(pingTime) {
|
||||||
// Update pingTime to start the clock for searches (above)
|
// Update pingTime to start the clock for searches (above)
|
||||||
sinfo.pingTime = now
|
sinfo.pingTime.Store(now)
|
||||||
}
|
}
|
||||||
if time.Since(sinfo.pingSend) > time.Second {
|
if time.Since(pingSend) > time.Second {
|
||||||
// Send at most 1 ping per second
|
// Send at most 1 ping per second
|
||||||
sinfo.pingSend = now
|
sinfo.pingSend.Store(now)
|
||||||
r.core.sessions.sendPingPong(sinfo, false)
|
r.core.sessions.sendPingPong(sinfo, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,54 +311,7 @@ func (r *router) sendPacket(bs []byte) {
|
|||||||
sinfo.send <- bs
|
sinfo.send <- bs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// Called for incoming traffic by the session worker for that connection.
|
|
||||||
// Checks that the IP address is correct (matches the session) and passes the packet to the adapter.
|
|
||||||
func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) {
|
|
||||||
// Note: called directly by the session worker, not the router goroutine
|
|
||||||
if len(bs) < 24 {
|
|
||||||
util.PutBytes(bs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var sourceAddr address.Address
|
|
||||||
var dest address.Address
|
|
||||||
var snet address.Subnet
|
|
||||||
var addrlen int
|
|
||||||
if bs[0]&0xf0 == 0x60 {
|
|
||||||
// IPv6 address
|
|
||||||
addrlen = 16
|
|
||||||
copy(sourceAddr[:addrlen], bs[8:])
|
|
||||||
copy(dest[:addrlen], bs[24:])
|
|
||||||
copy(snet[:addrlen/2], bs[8:])
|
|
||||||
} else if bs[0]&0xf0 == 0x40 {
|
|
||||||
// IPv4 address
|
|
||||||
addrlen = 4
|
|
||||||
copy(sourceAddr[:addrlen], bs[12:])
|
|
||||||
copy(dest[:addrlen], bs[16:])
|
|
||||||
} else {
|
|
||||||
// Unknown address length
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check that the packet is destined for either our Yggdrasil address or
|
|
||||||
// subnet, or that it matches one of the crypto-key routing source routes
|
|
||||||
if !r.cryptokey.isValidSource(dest, addrlen) {
|
|
||||||
util.PutBytes(bs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// See whether the packet they sent should have originated from this session
|
|
||||||
switch {
|
|
||||||
case sourceAddr.IsValid() && sourceAddr == sinfo.theirAddr:
|
|
||||||
case snet.IsValid() && snet == sinfo.theirSubnet:
|
|
||||||
default:
|
|
||||||
key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen)
|
|
||||||
if err != nil || key != sinfo.theirPermPub {
|
|
||||||
util.PutBytes(bs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//go func() { r.recv<-bs }()
|
|
||||||
r.recv <- bs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks incoming traffic type and passes it to the appropriate handler.
|
// Checks incoming traffic type and passes it to the appropriate handler.
|
||||||
func (r *router) handleIn(packet []byte) {
|
func (r *router) handleIn(packet []byte) {
|
||||||
@ -423,7 +340,11 @@ func (r *router) handleTraffic(packet []byte) {
|
|||||||
if !isIn {
|
if !isIn {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sinfo.recv <- &p
|
select {
|
||||||
|
case sinfo.recv <- &p: // FIXME ideally this should be front drop
|
||||||
|
default:
|
||||||
|
util.PutBytes(p.Payload)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type.
|
// Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type.
|
||||||
|
@ -15,6 +15,7 @@ package yggdrasil
|
|||||||
// Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there?
|
// Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there?
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -32,12 +33,13 @@ const search_RETRY_TIME = time.Second
|
|||||||
// Information about an ongoing search.
|
// Information about an ongoing search.
|
||||||
// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
|
// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
|
||||||
type searchInfo struct {
|
type searchInfo struct {
|
||||||
dest crypto.NodeID
|
dest crypto.NodeID
|
||||||
mask crypto.NodeID
|
mask crypto.NodeID
|
||||||
time time.Time
|
time time.Time
|
||||||
packet []byte
|
packet []byte
|
||||||
toVisit []*dhtInfo
|
toVisit []*dhtInfo
|
||||||
visited map[crypto.NodeID]bool
|
visited map[crypto.NodeID]bool
|
||||||
|
callback func(*sessionInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This stores a map of active searches.
|
// This stores a map of active searches.
|
||||||
@ -61,7 +63,7 @@ func (s *searches) init(core *Core) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new search info, adds it to the searches struct, and returns a pointer to the info.
|
// Creates a new search info, adds it to the searches struct, and returns a pointer to the info.
|
||||||
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
|
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
for dest, sinfo := range s.searches {
|
for dest, sinfo := range s.searches {
|
||||||
if now.Sub(sinfo.time) > time.Minute {
|
if now.Sub(sinfo.time) > time.Minute {
|
||||||
@ -69,9 +71,10 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
info := searchInfo{
|
info := searchInfo{
|
||||||
dest: *dest,
|
dest: *dest,
|
||||||
mask: *mask,
|
mask: *mask,
|
||||||
time: now.Add(-time.Second),
|
time: now.Add(-time.Second),
|
||||||
|
callback: callback,
|
||||||
}
|
}
|
||||||
s.searches[*dest] = &info
|
s.searches[*dest] = &info
|
||||||
return &info
|
return &info
|
||||||
@ -87,11 +90,10 @@ func (s *searches) handleDHTRes(res *dhtRes) {
|
|||||||
if !isIn || s.checkDHTRes(sinfo, res) {
|
if !isIn || s.checkDHTRes(sinfo, res) {
|
||||||
// Either we don't recognize this search, or we just finished it
|
// Either we don't recognize this search, or we just finished it
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
// Add to the search and continue
|
|
||||||
s.addToSearch(sinfo, res)
|
|
||||||
s.doSearchStep(sinfo)
|
|
||||||
}
|
}
|
||||||
|
// Add to the search and continue
|
||||||
|
s.addToSearch(sinfo, res)
|
||||||
|
s.doSearchStep(sinfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds the information from a dhtRes to an ongoing search.
|
// Adds the information from a dhtRes to an ongoing search.
|
||||||
@ -137,15 +139,15 @@ func (s *searches) doSearchStep(sinfo *searchInfo) {
|
|||||||
if len(sinfo.toVisit) == 0 {
|
if len(sinfo.toVisit) == 0 {
|
||||||
// Dead end, do cleanup
|
// Dead end, do cleanup
|
||||||
delete(s.searches, sinfo.dest)
|
delete(s.searches, sinfo.dest)
|
||||||
|
go sinfo.callback(nil, errors.New("search reached dead end"))
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
// Send to the next search target
|
|
||||||
var next *dhtInfo
|
|
||||||
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
|
|
||||||
rq := dhtReqKey{next.key, sinfo.dest}
|
|
||||||
s.core.dht.addCallback(&rq, s.handleDHTRes)
|
|
||||||
s.core.dht.ping(next, &sinfo.dest)
|
|
||||||
}
|
}
|
||||||
|
// Send to the next search target
|
||||||
|
var next *dhtInfo
|
||||||
|
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
|
||||||
|
rq := dhtReqKey{next.key, sinfo.dest}
|
||||||
|
s.core.dht.addCallback(&rq, s.handleDHTRes)
|
||||||
|
s.core.dht.ping(next, &sinfo.dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've recenty sent a ping for this search, do nothing.
|
// If we've recenty sent a ping for this search, do nothing.
|
||||||
@ -173,8 +175,8 @@ func (s *searches) continueSearch(sinfo *searchInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calls create search, and initializes the iterative search parts of the struct before returning it.
|
// Calls create search, and initializes the iterative search parts of the struct before returning it.
|
||||||
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
|
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
|
||||||
sinfo := s.createSearch(dest, mask)
|
sinfo := s.createSearch(dest, mask, callback)
|
||||||
sinfo.toVisit = s.core.dht.lookup(dest, true)
|
sinfo.toVisit = s.core.dht.lookup(dest, true)
|
||||||
sinfo.visited = make(map[crypto.NodeID]bool)
|
sinfo.visited = make(map[crypto.NodeID]bool)
|
||||||
return sinfo
|
return sinfo
|
||||||
@ -200,6 +202,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool {
|
|||||||
sinfo = s.core.sessions.createSession(&res.Key)
|
sinfo = s.core.sessions.createSession(&res.Key)
|
||||||
if sinfo == nil {
|
if sinfo == nil {
|
||||||
// nil if the DHT search finished but the session wasn't allowed
|
// nil if the DHT search finished but the session wasn't allowed
|
||||||
|
go info.callback(nil, errors.New("session not allowed"))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
_, isIn := s.core.sessions.getByTheirPerm(&res.Key)
|
_, isIn := s.core.sessions.getByTheirPerm(&res.Key)
|
||||||
@ -211,6 +214,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool {
|
|||||||
sinfo.coords = res.Coords
|
sinfo.coords = res.Coords
|
||||||
sinfo.packet = info.packet
|
sinfo.packet = info.packet
|
||||||
s.core.sessions.ping(sinfo)
|
s.core.sessions.ping(sinfo)
|
||||||
|
go info.callback(sinfo, nil)
|
||||||
// Cleanup
|
// Cleanup
|
||||||
delete(s.searches, res.Dest)
|
delete(s.searches, res.Dest)
|
||||||
return true
|
return true
|
||||||
|
@ -7,45 +7,61 @@ package yggdrasil
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// All the information we know about an active session.
|
// All the information we know about an active session.
|
||||||
// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API.
|
// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API.
|
||||||
type sessionInfo struct {
|
type sessionInfo struct {
|
||||||
core *Core
|
core *Core //
|
||||||
reconfigure chan chan error
|
reconfigure chan chan error //
|
||||||
theirAddr address.Address
|
theirAddr address.Address //
|
||||||
theirSubnet address.Subnet
|
theirSubnet address.Subnet //
|
||||||
theirPermPub crypto.BoxPubKey
|
theirPermPub crypto.BoxPubKey //
|
||||||
theirSesPub crypto.BoxPubKey
|
theirSesPub crypto.BoxPubKey //
|
||||||
mySesPub crypto.BoxPubKey
|
mySesPub crypto.BoxPubKey //
|
||||||
mySesPriv crypto.BoxPrivKey
|
mySesPriv crypto.BoxPrivKey //
|
||||||
sharedSesKey crypto.BoxSharedKey // derived from session keys
|
sharedSesKey crypto.BoxSharedKey // derived from session keys
|
||||||
theirHandle crypto.Handle
|
theirHandle crypto.Handle //
|
||||||
myHandle crypto.Handle
|
myHandle crypto.Handle //
|
||||||
theirNonce crypto.BoxNonce
|
theirNonce crypto.BoxNonce //
|
||||||
myNonce crypto.BoxNonce
|
theirNonceMask uint64 //
|
||||||
theirMTU uint16
|
myNonce crypto.BoxNonce //
|
||||||
myMTU uint16
|
theirMTU uint16 //
|
||||||
wasMTUFixed bool // Was the MTU fixed by a receive error?
|
myMTU uint16 //
|
||||||
time time.Time // Time we last received a packet
|
wasMTUFixed bool // Was the MTU fixed by a receive error?
|
||||||
coords []byte // coords of destination
|
timeOpened time.Time // Time the sessino was opened
|
||||||
packet []byte // a buffered packet, sent immediately on ping/pong
|
time time.Time // Time we last received a packet
|
||||||
init bool // Reset if coords change
|
mtuTime time.Time // time myMTU was last changed
|
||||||
send chan []byte
|
pingTime time.Time // time the first ping was sent since the last received packet
|
||||||
recv chan *wire_trafficPacket
|
pingSend time.Time // time the last ping was sent
|
||||||
nonceMask uint64
|
coords []byte // coords of destination
|
||||||
tstamp int64 // tstamp from their last session ping, replay attack mitigation
|
packet []byte // a buffered packet, sent immediately on ping/pong
|
||||||
mtuTime time.Time // time myMTU was last changed
|
init bool // Reset if coords change
|
||||||
pingTime time.Time // time the first ping was sent since the last received packet
|
tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation
|
||||||
pingSend time.Time // time the last ping was sent
|
bytesSent uint64 // Bytes of real traffic sent in this session
|
||||||
bytesSent uint64 // Bytes of real traffic sent in this session
|
bytesRecvd uint64 // Bytes of real traffic received in this session
|
||||||
bytesRecvd uint64 // Bytes of real traffic received in this session
|
worker chan func() // Channel to send work to the session worker
|
||||||
|
recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sinfo *sessionInfo) doWorker(f func()) {
|
||||||
|
done := make(chan struct{})
|
||||||
|
sinfo.worker <- func() {
|
||||||
|
f()
|
||||||
|
close(done)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sinfo *sessionInfo) workerMain() {
|
||||||
|
for f := range sinfo.worker {
|
||||||
|
f()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU.
|
// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU.
|
||||||
@ -53,10 +69,10 @@ type sessionPing struct {
|
|||||||
SendPermPub crypto.BoxPubKey // Sender's permanent key
|
SendPermPub crypto.BoxPubKey // Sender's permanent key
|
||||||
Handle crypto.Handle // Random number to ID session
|
Handle crypto.Handle // Random number to ID session
|
||||||
SendSesPub crypto.BoxPubKey // Session key to use
|
SendSesPub crypto.BoxPubKey // Session key to use
|
||||||
Coords []byte
|
Coords []byte //
|
||||||
Tstamp int64 // unix time, but the only real requirement is that it increases
|
Tstamp int64 // unix time, but the only real requirement is that it increases
|
||||||
IsPong bool
|
IsPong bool //
|
||||||
MTU uint16
|
MTU uint16 //
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates session info in response to a ping, after checking that the ping is OK.
|
// Updates session info in response to a ping, after checking that the ping is OK.
|
||||||
@ -76,7 +92,7 @@ func (s *sessionInfo) update(p *sessionPing) bool {
|
|||||||
s.theirHandle = p.Handle
|
s.theirHandle = p.Handle
|
||||||
s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub)
|
s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub)
|
||||||
s.theirNonce = crypto.BoxNonce{}
|
s.theirNonce = crypto.BoxNonce{}
|
||||||
s.nonceMask = 0
|
s.theirNonceMask = 0
|
||||||
}
|
}
|
||||||
if p.MTU >= 1280 || p.MTU == 0 {
|
if p.MTU >= 1280 || p.MTU == 0 {
|
||||||
s.theirMTU = p.MTU
|
s.theirMTU = p.MTU
|
||||||
@ -85,35 +101,28 @@ func (s *sessionInfo) update(p *sessionPing) bool {
|
|||||||
// allocate enough space for additional coords
|
// allocate enough space for additional coords
|
||||||
s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...)
|
s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...)
|
||||||
}
|
}
|
||||||
now := time.Now()
|
s.time = time.Now()
|
||||||
s.time = now
|
|
||||||
s.tstamp = p.Tstamp
|
s.tstamp = p.Tstamp
|
||||||
s.init = true
|
s.init = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the session has been idle for longer than the allowed timeout.
|
|
||||||
func (s *sessionInfo) timedout() bool {
|
|
||||||
return time.Since(s.time) > time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
// Struct of all active sessions.
|
// Struct of all active sessions.
|
||||||
// Sessions are indexed by handle.
|
// Sessions are indexed by handle.
|
||||||
// Additionally, stores maps of address/subnet onto keys, and keys onto handles.
|
// Additionally, stores maps of address/subnet onto keys, and keys onto handles.
|
||||||
type sessions struct {
|
type sessions struct {
|
||||||
core *Core
|
core *Core
|
||||||
reconfigure chan chan error
|
listener *Listener
|
||||||
lastCleanup time.Time
|
listenerMutex sync.Mutex
|
||||||
// Maps known permanent keys to their shared key, used by DHT a lot
|
reconfigure chan chan error
|
||||||
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey
|
lastCleanup time.Time
|
||||||
// Maps (secret) handle onto session info
|
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot
|
||||||
sinfos map[crypto.Handle]*sessionInfo
|
sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info
|
||||||
// Maps mySesPub onto handle
|
conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections
|
||||||
byMySes map[crypto.BoxPubKey]*crypto.Handle
|
byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle
|
||||||
// Maps theirPermPub onto handle
|
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle
|
||||||
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle
|
addrToPerm map[address.Address]*crypto.BoxPubKey
|
||||||
addrToPerm map[address.Address]*crypto.BoxPubKey
|
subnetToPerm map[address.Subnet]*crypto.BoxPubKey
|
||||||
subnetToPerm map[address.Subnet]*crypto.BoxPubKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializes the session struct.
|
// Initializes the session struct.
|
||||||
@ -215,10 +224,6 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b
|
|||||||
// Gets the session corresponding to a given handle.
|
// Gets the session corresponding to a given handle.
|
||||||
func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) {
|
func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) {
|
||||||
sinfo, isIn := ss.sinfos[*handle]
|
sinfo, isIn := ss.sinfos[*handle]
|
||||||
if isIn && sinfo.timedout() {
|
|
||||||
// We have a session, but it has timed out
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return sinfo, isIn
|
return sinfo, isIn
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,8 +267,9 @@ func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool)
|
|||||||
return sinfo, isIn
|
return sinfo, isIn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new session and lazily cleans up old/timedout existing sessions.
|
// Creates a new session and lazily cleans up old existing sessions. This
|
||||||
// This includse initializing session info to sane defaults (e.g. lowest supported MTU).
|
// includse initializing session info to sane defaults (e.g. lowest supported
|
||||||
|
// MTU).
|
||||||
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
|
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
|
||||||
if !ss.isSessionAllowed(theirPermKey, true) {
|
if !ss.isSessionAllowed(theirPermKey, true) {
|
||||||
return nil
|
return nil
|
||||||
@ -277,10 +283,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
|
|||||||
sinfo.mySesPriv = *priv
|
sinfo.mySesPriv = *priv
|
||||||
sinfo.myNonce = *crypto.NewBoxNonce()
|
sinfo.myNonce = *crypto.NewBoxNonce()
|
||||||
sinfo.theirMTU = 1280
|
sinfo.theirMTU = 1280
|
||||||
if ss.core.router.adapter != nil {
|
sinfo.myMTU = 1280
|
||||||
sinfo.myMTU = uint16(ss.core.router.adapter.MTU())
|
|
||||||
}
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
sinfo.timeOpened = now
|
||||||
sinfo.time = now
|
sinfo.time = now
|
||||||
sinfo.mtuTime = now
|
sinfo.mtuTime = now
|
||||||
sinfo.pingTime = now
|
sinfo.pingTime = now
|
||||||
@ -304,14 +309,14 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
|
|||||||
sinfo.myHandle = *crypto.NewHandle()
|
sinfo.myHandle = *crypto.NewHandle()
|
||||||
sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
|
sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
|
||||||
sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
|
sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
|
||||||
sinfo.send = make(chan []byte, 32)
|
sinfo.worker = make(chan func(), 1)
|
||||||
sinfo.recv = make(chan *wire_trafficPacket, 32)
|
sinfo.recv = make(chan *wire_trafficPacket, 32)
|
||||||
go sinfo.doWorker()
|
|
||||||
ss.sinfos[sinfo.myHandle] = &sinfo
|
ss.sinfos[sinfo.myHandle] = &sinfo
|
||||||
ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle
|
ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle
|
||||||
ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle
|
ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle
|
||||||
ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub
|
ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub
|
||||||
ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub
|
ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub
|
||||||
|
go sinfo.workerMain()
|
||||||
return &sinfo
|
return &sinfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,11 +330,6 @@ func (ss *sessions) cleanup() {
|
|||||||
if time.Since(ss.lastCleanup) < time.Minute {
|
if time.Since(ss.lastCleanup) < time.Minute {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, s := range ss.sinfos {
|
|
||||||
if s.timedout() {
|
|
||||||
s.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared))
|
permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared))
|
||||||
for k, v := range ss.permShared {
|
for k, v := range ss.permShared {
|
||||||
permShared[k] = v
|
permShared[k] = v
|
||||||
@ -370,8 +370,7 @@ func (sinfo *sessionInfo) close() {
|
|||||||
delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
|
delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
|
||||||
delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr)
|
delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr)
|
||||||
delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet)
|
delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet)
|
||||||
close(sinfo.send)
|
close(sinfo.worker)
|
||||||
close(sinfo.recv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a session ping appropriate for the given session info.
|
// Returns a session ping appropriate for the given session info.
|
||||||
@ -449,29 +448,42 @@ func (ss *sessions) handlePing(ping *sessionPing) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !isIn || sinfo.timedout() {
|
if !isIn {
|
||||||
if isIn {
|
|
||||||
sinfo.close()
|
|
||||||
}
|
|
||||||
ss.createSession(&ping.SendPermPub)
|
ss.createSession(&ping.SendPermPub)
|
||||||
sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub)
|
sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub)
|
||||||
if !isIn {
|
if !isIn {
|
||||||
panic("This should not happen")
|
panic("This should not happen")
|
||||||
}
|
}
|
||||||
|
ss.listenerMutex.Lock()
|
||||||
|
// Check and see if there's a Listener waiting to accept connections
|
||||||
|
// TODO: this should not block if nothing is accepting
|
||||||
|
if !ping.IsPong && ss.listener != nil {
|
||||||
|
conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo)
|
||||||
|
for i := range conn.nodeMask {
|
||||||
|
conn.nodeMask[i] = 0xFF
|
||||||
|
}
|
||||||
|
ss.listener.conn <- conn
|
||||||
|
}
|
||||||
|
ss.listenerMutex.Unlock()
|
||||||
}
|
}
|
||||||
// Update the session
|
sinfo.doWorker(func() {
|
||||||
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
|
// Update the session
|
||||||
return
|
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
|
||||||
}
|
return
|
||||||
if !ping.IsPong {
|
}
|
||||||
ss.sendPingPong(sinfo, true)
|
if !ping.IsPong {
|
||||||
}
|
ss.sendPingPong(sinfo, true)
|
||||||
if sinfo.packet != nil {
|
}
|
||||||
// send
|
if sinfo.packet != nil {
|
||||||
var bs []byte
|
/* FIXME this needs to live in the net.Conn or something, needs work in Write
|
||||||
bs, sinfo.packet = sinfo.packet, nil
|
// send
|
||||||
ss.core.router.sendPacket(bs)
|
var bs []byte
|
||||||
}
|
bs, sinfo.packet = sinfo.packet, nil
|
||||||
|
ss.core.router.sendPacket(bs) // FIXME this needs to live in the net.Conn or something, needs work in Write
|
||||||
|
*/
|
||||||
|
sinfo.packet = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the MTU of the session.
|
// Get the MTU of the session.
|
||||||
@ -494,7 +506,7 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool {
|
|||||||
if diff > 0 {
|
if diff > 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return ^sinfo.nonceMask&(0x01<<uint64(-diff)) != 0
|
return ^sinfo.theirNonceMask&(0x01<<uint64(-diff)) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce
|
// Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce
|
||||||
@ -504,12 +516,12 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) {
|
|||||||
diff := theirNonce.Minus(&sinfo.theirNonce)
|
diff := theirNonce.Minus(&sinfo.theirNonce)
|
||||||
if diff > 0 {
|
if diff > 0 {
|
||||||
// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
|
// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
|
||||||
sinfo.nonceMask <<= uint64(diff)
|
sinfo.theirNonceMask <<= uint64(diff)
|
||||||
sinfo.nonceMask &= 0x01
|
sinfo.theirNonceMask &= 0x01
|
||||||
sinfo.theirNonce = *theirNonce
|
sinfo.theirNonce = *theirNonce
|
||||||
} else {
|
} else {
|
||||||
// This nonce is older, so set the bit but do not shift the window.
|
// This nonce is older, so set the bit but do not shift the window.
|
||||||
sinfo.nonceMask &= 0x01 << uint64(-diff)
|
sinfo.theirNonceMask &= 0x01 << uint64(-diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,134 +529,8 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) {
|
|||||||
// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change.
|
// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change.
|
||||||
func (ss *sessions) resetInits() {
|
func (ss *sessions) resetInits() {
|
||||||
for _, sinfo := range ss.sinfos {
|
for _, sinfo := range ss.sinfos {
|
||||||
sinfo.init = false
|
sinfo.doWorker(func() {
|
||||||
|
sinfo.init = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// This is for a per-session worker.
|
|
||||||
// It handles calling the relatively expensive crypto operations.
|
|
||||||
// It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK.
|
|
||||||
func (sinfo *sessionInfo) doWorker() {
|
|
||||||
send := make(chan []byte, 32)
|
|
||||||
defer close(send)
|
|
||||||
go func() {
|
|
||||||
for bs := range send {
|
|
||||||
sinfo.doSend(bs)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
recv := make(chan *wire_trafficPacket, 32)
|
|
||||||
defer close(recv)
|
|
||||||
go func() {
|
|
||||||
for p := range recv {
|
|
||||||
sinfo.doRecv(p)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case p, ok := <-sinfo.recv:
|
|
||||||
if ok {
|
|
||||||
select {
|
|
||||||
case recv <- p:
|
|
||||||
default:
|
|
||||||
// We need something to not block, and it's best to drop it before we decrypt
|
|
||||||
util.PutBytes(p.Payload)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bs, ok := <-sinfo.send:
|
|
||||||
if ok {
|
|
||||||
send <- bs
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case e := <-sinfo.reconfigure:
|
|
||||||
e <- nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This encrypts a packet, creates a trafficPacket struct, encodes it, and sends it to router.out to pass it to the switch layer.
|
|
||||||
func (sinfo *sessionInfo) doSend(bs []byte) {
|
|
||||||
defer util.PutBytes(bs)
|
|
||||||
if !sinfo.init {
|
|
||||||
// To prevent using empty session keys
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// code isn't multithreaded so appending to this is safe
|
|
||||||
coords := sinfo.coords
|
|
||||||
// Work out the flowkey - this is used to determine which switch queue
|
|
||||||
// traffic will be pushed to in the event of congestion
|
|
||||||
var flowkey uint64
|
|
||||||
// Get the IP protocol version from the packet
|
|
||||||
switch bs[0] & 0xf0 {
|
|
||||||
case 0x40: // IPv4 packet
|
|
||||||
// Check the packet meets minimum UDP packet length
|
|
||||||
if len(bs) >= 24 {
|
|
||||||
// Is the protocol TCP, UDP or SCTP?
|
|
||||||
if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 {
|
|
||||||
ihl := bs[0] & 0x0f * 4 // Header length
|
|
||||||
flowkey = uint64(bs[9])<<32 /* proto */ |
|
|
||||||
uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ |
|
|
||||||
uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 0x60: // IPv6 packet
|
|
||||||
// Check if the flowlabel was specified in the packet header
|
|
||||||
flowkey = uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3])
|
|
||||||
// If the flowlabel isn't present, make protokey from proto | sport | dport
|
|
||||||
// if the packet meets minimum UDP packet length
|
|
||||||
if flowkey == 0 && len(bs) >= 48 {
|
|
||||||
// Is the protocol TCP, UDP or SCTP?
|
|
||||||
if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 {
|
|
||||||
flowkey = uint64(bs[6])<<32 /* proto */ |
|
|
||||||
uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
|
|
||||||
uint64(bs[42])<<8 | uint64(bs[43]) /* dport */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we have a flowkey, either through the IPv6 flowlabel field or through
|
|
||||||
// known TCP/UDP/SCTP proto-sport-dport triplet, then append it to the coords.
|
|
||||||
// Appending extra coords after a 0 ensures that we still target the local router
|
|
||||||
// but lets us send extra data (which is otherwise ignored) to help separate
|
|
||||||
// traffic streams into independent queues
|
|
||||||
if flowkey != 0 {
|
|
||||||
coords = append(coords, 0) // First target the local switchport
|
|
||||||
coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey
|
|
||||||
}
|
|
||||||
// Prepare the payload
|
|
||||||
payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce)
|
|
||||||
defer util.PutBytes(payload)
|
|
||||||
p := wire_trafficPacket{
|
|
||||||
Coords: coords,
|
|
||||||
Handle: sinfo.theirHandle,
|
|
||||||
Nonce: *nonce,
|
|
||||||
Payload: payload,
|
|
||||||
}
|
|
||||||
packet := p.encode()
|
|
||||||
sinfo.bytesSent += uint64(len(bs))
|
|
||||||
sinfo.core.router.out(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This takes a trafficPacket and checks the nonce.
|
|
||||||
// If the nonce is OK, it decrypts the packet.
|
|
||||||
// If the decrypted packet is OK, it calls router.recvPacket to pass the packet to the tun/tap.
|
|
||||||
// If a packet does not decrypt successfully, it assumes the packet was truncated, and updates the MTU accordingly.
|
|
||||||
// TODO? remove the MTU updating part? That should never happen with TCP peers, and the old UDP code that caused it was removed (and if replaced, should be replaced with something that can reliably send messages with an arbitrary size).
|
|
||||||
func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
|
|
||||||
defer util.PutBytes(p.Payload)
|
|
||||||
if !sinfo.nonceIsOK(&p.Nonce) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
|
|
||||||
if !isOK {
|
|
||||||
util.PutBytes(bs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sinfo.updateNonce(&p.Nonce)
|
|
||||||
sinfo.time = time.Now()
|
|
||||||
sinfo.bytesRecvd += uint64(len(bs))
|
|
||||||
sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo}
|
|
||||||
}
|
|
||||||
|
@ -577,23 +577,28 @@ func (t *switchTable) start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type closerInfo struct {
|
||||||
|
port switchPort
|
||||||
|
dist int
|
||||||
|
}
|
||||||
|
|
||||||
// Return a map of ports onto distance, keeping only ports closer to the destination than this node
|
// Return a map of ports onto distance, keeping only ports closer to the destination than this node
|
||||||
// If the map is empty (or nil), then no peer is closer
|
// If the map is empty (or nil), then no peer is closer
|
||||||
func (t *switchTable) getCloser(dest []byte) map[switchPort]int {
|
func (t *switchTable) getCloser(dest []byte) []closerInfo {
|
||||||
table := t.getTable()
|
table := t.getTable()
|
||||||
myDist := table.self.dist(dest)
|
myDist := table.self.dist(dest)
|
||||||
if myDist == 0 {
|
if myDist == 0 {
|
||||||
// Skip the iteration step if it's impossible to be closer
|
// Skip the iteration step if it's impossible to be closer
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
closer := make(map[switchPort]int, len(table.elems))
|
t.queues.closer = t.queues.closer[:0]
|
||||||
for _, info := range table.elems {
|
for _, info := range table.elems {
|
||||||
dist := info.locator.dist(dest)
|
dist := info.locator.dist(dest)
|
||||||
if dist < myDist {
|
if dist < myDist {
|
||||||
closer[info.port] = dist
|
t.queues.closer = append(t.queues.closer, closerInfo{info.port, dist})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return closer
|
return t.queues.closer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the peer is closer to the destination than ourself
|
// Returns true if the peer is closer to the destination than ourself
|
||||||
@ -656,9 +661,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
|
|||||||
var bestDist int
|
var bestDist int
|
||||||
var bestTime time.Time
|
var bestTime time.Time
|
||||||
ports := t.core.peers.getPorts()
|
ports := t.core.peers.getPorts()
|
||||||
for port, dist := range closer {
|
for _, cinfo := range closer {
|
||||||
to := ports[port]
|
to := ports[cinfo.port]
|
||||||
thisTime, isIdle := idle[port]
|
thisTime, isIdle := idle[cinfo.port]
|
||||||
var update bool
|
var update bool
|
||||||
switch {
|
switch {
|
||||||
case to == nil:
|
case to == nil:
|
||||||
@ -667,9 +672,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
|
|||||||
//nothing
|
//nothing
|
||||||
case best == nil:
|
case best == nil:
|
||||||
update = true
|
update = true
|
||||||
case dist < bestDist:
|
case cinfo.dist < bestDist:
|
||||||
update = true
|
update = true
|
||||||
case dist > bestDist:
|
case cinfo.dist > bestDist:
|
||||||
//nothing
|
//nothing
|
||||||
case thisTime.Before(bestTime):
|
case thisTime.Before(bestTime):
|
||||||
update = true
|
update = true
|
||||||
@ -678,7 +683,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
|
|||||||
}
|
}
|
||||||
if update {
|
if update {
|
||||||
best = to
|
best = to
|
||||||
bestDist = dist
|
bestDist = cinfo.dist
|
||||||
bestTime = thisTime
|
bestTime = thisTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -711,6 +716,7 @@ type switch_buffers struct {
|
|||||||
size uint64 // Total size of all buffers, in bytes
|
size uint64 // Total size of all buffers, in bytes
|
||||||
maxbufs int
|
maxbufs int
|
||||||
maxsize uint64
|
maxsize uint64
|
||||||
|
closer []closerInfo // Scratch space
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *switch_buffers) cleanup(t *switchTable) {
|
func (b *switch_buffers) cleanup(t *switchTable) {
|
||||||
|
@ -255,13 +255,6 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn = &wrappedConn{
|
|
||||||
c: conn,
|
|
||||||
raddr: &wrappedAddr{
|
|
||||||
network: "tcp",
|
|
||||||
addr: saddr,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
t.handler(conn, false, dialerdst.String())
|
t.handler(conn, false, dialerdst.String())
|
||||||
} else {
|
} else {
|
||||||
dst, err := net.ResolveTCPAddr("tcp", saddr)
|
dst, err := net.ResolveTCPAddr("tcp", saddr)
|
||||||
|
Loading…
Reference in New Issue
Block a user