Merge pull request #419 from yggdrasil-network/modular

Modular Yggdrasil
This commit is contained in:
Neil Alexander 2019-05-29 18:19:13 +01:00 committed by GitHub
commit 6b6266bfdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2655 additions and 2082 deletions

7
build
View File

@ -8,7 +8,7 @@ PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
while getopts "udaitc:l:" option
while getopts "udaitc:l:r" option
do
case "${option}"
in
@ -19,6 +19,7 @@ do
t) TABLES=true;;
c) GCFLAGS="$GCFLAGS $OPTARG";;
l) LDFLAGS="$LDFLAGS $OPTARG";;
r) RACE="-race";;
esac
done
@ -43,9 +44,9 @@ else
echo "Building: $CMD"
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
go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD
go build $RACE -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD
fi
if [ $UPX ]; then
upx --brute $CMD

View File

@ -18,6 +18,7 @@ import (
"github.com/kardianos/minwinsvc"
"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/multicast"
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
@ -31,6 +32,7 @@ type node struct {
core Core
tuntap tuntap.TunAdapter
multicast multicast.Multicast
admin admin.AdminSocket
}
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig {
@ -76,77 +78,6 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo
panic(err)
}
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
// we generated above.
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
// don't need to create this manually.
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
// components needed for Yggdrasil to operate
state, err := n.core.Start(cfg, logger)
@ -257,11 +186,31 @@ func main() {
logger.Errorln("An error occurred during startup")
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
n.multicast.Init(&n.core, state, logger, nil)
if err := n.multicast.Start(); err != nil {
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
// before the program exits.
defer func() {
@ -291,6 +240,8 @@ func main() {
if *useconffile != "" {
cfg = readConfig(useconf, useconffile, normaliseconf)
n.core.UpdateConfig(cfg)
n.tuntap.UpdateConfig(cfg)
n.multicast.UpdateConfig(cfg)
} else {
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
}

View File

@ -200,7 +200,7 @@ func main() {
if !keysOrdered {
for k := range slv.(map[string]interface{}) {
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
}
}
@ -277,6 +277,9 @@ func main() {
fmt.Println("Coords:", coords)
}
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 {
fmt.Println("Public encryption key:", boxPubKey)
}

591
src/admin/admin.go Normal file
View 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
}
*/

View File

@ -13,6 +13,7 @@ It also defines NodeID and TreeID as hashes of keys, and wraps hash functions
import (
"crypto/rand"
"crypto/sha512"
"encoding/hex"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
@ -32,6 +33,41 @@ type NodeID [NodeIDLen]byte
type TreeID [TreeIDLen]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 {
h := sha512.Sum512(pub[:])
return (*NodeID)(&h)

13
src/multicast/admin.go Normal file
View 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
})
}

View File

@ -41,6 +41,10 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log
go func() {
for {
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
}
}()
@ -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
// beacons out to the network.
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")
} else {
m.log.Infoln("Multicast discovery is enabled")
@ -89,6 +94,36 @@ func (m *Multicast) Stop() error {
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 {
// Get interface expressions from config
current, _ := m.config.Get()

119
src/tuntap/admin.go Normal file
View 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")
}
})
}

View File

@ -1,4 +1,4 @@
package yggdrasil
package tuntap
import (
"bytes"
@ -7,6 +7,8 @@ import (
"fmt"
"net"
"sort"
"sync"
"sync/atomic"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
@ -16,15 +18,18 @@ import (
// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil.
type cryptokey struct {
core *Core
enabled bool
reconfigure chan chan error
ipv4routes []cryptokey_route
ipv6routes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route
ipv6cache map[address.Address]cryptokey_route
ipv4sources []net.IPNet
ipv6sources []net.IPNet
tun *TunAdapter
enabled atomic.Value // bool
reconfigure chan chan error
ipv4routes []cryptokey_route
ipv6routes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route
ipv6cache map[address.Address]cryptokey_route
ipv4sources []net.IPNet
ipv6sources []net.IPNet
mutexroutes sync.RWMutex
mutexcaches sync.RWMutex
mutexsources sync.RWMutex
}
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.
func (c *cryptokey) init(core *Core) {
c.core = core
func (c *cryptokey) init(tun *TunAdapter) {
c.tun = tun
c.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-c.reconfigure
var err error
c.core.router.doAdmin(func() {
err = c.core.router.cryptokey.configure()
})
e <- err
e <- 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
// goroutine, e.g. through router.doAdmin
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
c.setEnabled(current.TunnelRouting.Enable)
c.setEnabled(c.tun.config.Current.TunnelRouting.Enable)
// Clear out existing routes
c.mutexroutes.Lock()
c.ipv6routes = make([]cryptokey_route, 0)
c.ipv4routes = make([]cryptokey_route, 0)
c.mutexroutes.Unlock()
// 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 {
return err
}
}
// 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 {
return err
}
}
// Clear out existing sources
c.mutexsources.Lock()
c.ipv6sources = make([]net.IPNet, 0)
c.ipv4sources = make([]net.IPNet, 0)
c.mutexsources.Unlock()
// Add IPv6 sources
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 {
return err
}
@ -92,43 +98,48 @@ func (c *cryptokey) configure() error {
// Add IPv4 sources
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 {
return err
}
}
// Wipe the caches
c.mutexcaches.Lock()
c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
c.mutexcaches.Unlock()
return nil
}
// Enable or disable crypto-key routing.
func (c *cryptokey) setEnabled(enabled bool) {
c.enabled = enabled
c.enabled.Store(true)
}
// Check if crypto-key routing is enabled.
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)
// matches either the current node's address, the node's routed subnet or the
// list of subnets specified in IPv4Sources/IPv6Sources.
func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
c.mutexsources.RLock()
defer c.mutexsources.RUnlock()
ip := net.IP(addr[:addrlen])
if addrlen == net.IPv6len {
// 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
}
// 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
}
}
@ -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
// be tunnelled using crypto-key routing.
func (c *cryptokey) addSourceSubnet(cidr string) error {
c.mutexsources.Lock()
defer c.mutexsources.Unlock()
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@ -191,13 +205,18 @@ func (c *cryptokey) addSourceSubnet(cidr string) error {
// Add the source subnet
*routingsources = append(*routingsources, *ipnet)
c.core.log.Infoln("Added CKR source subnet", cidr)
c.tun.log.Infoln("Added CKR source subnet", cidr)
return nil
}
// Adds a destination route for the given CIDR to be tunnelled to the node
// with the given BoxPubKey.
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?
ipaddr, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@ -263,7 +282,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
delete(*routingcache, k)
}
c.core.log.Infoln("Added CKR destination subnet", cidr)
c.tun.log.Infoln("Added CKR destination subnet", cidr)
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
// returned if the address is not suitable or no route was found.
func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) {
c.mutexcaches.RLock()
// Check if the address is a valid Yggdrasil address - if so it
// is exempt from all CKR checking
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
if addrlen == net.IPv6len {
routingtable = &c.ipv6routes
routingcache = &c.ipv6cache
} else if addrlen == net.IPv4len {
routingtable = &c.ipv4routes
routingcache = &c.ipv4cache
} else {
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
if route, ok := (*routingcache)[addr]; ok {
c.mutexcaches.RUnlock()
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
ip := make(net.IP, addrlen)
copy(ip[:addrlen], addr[:])
@ -307,6 +341,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
for _, route := range *routingtable {
// Does this subnet match the given 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
// a random entry so we can make room for this one. We take advantage
// 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
// be tunnelled using crypto-key routing.
func (c *cryptokey) removeSourceSubnet(cidr string) error {
c.mutexsources.Lock()
defer c.mutexsources.Unlock()
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@ -357,7 +397,7 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
for idx, subnet := range *routingsources {
if subnet.String() == ipnet.String() {
*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
}
}
@ -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
// with the given BoxPubKey.
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?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@ -406,7 +451,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
for k := range *routingcache {
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
}
}

132
src/tuntap/conn.go Normal file
View 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")
}
}
}

View File

@ -169,7 +169,6 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error)
if err != nil {
return nil, err
}
// Send it back
return responsePacket, nil
} else {
@ -186,7 +185,7 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error)
copy(addr[:], ipv6Header.Src[:])
copy(target[:], datain[48:64])
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.mac = mac
neighbor.learned = true

254
src/tuntap/iface.go Normal file
View 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)
}
}
}
}

View File

@ -2,8 +2,14 @@ package tuntap
// 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 (
"bytes"
"encoding/hex"
"errors"
"fmt"
"net"
@ -11,16 +17,12 @@ import (
"time"
"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/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/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
"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
// before calling yggdrasil.Start().
type TunAdapter struct {
yggdrasil.Adapter
addr address.Address
subnet address.Subnet
icmpv6 ICMPv6
mtu int
iface *water.Interface
mutex sync.RWMutex // Protects the below
isOpen bool
config *config.NodeState
log *log.Logger
reconfigure chan chan error
listener *yggdrasil.Listener
dialer *yggdrasil.Dialer
addr address.Address
subnet address.Subnet
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
@ -94,62 +104,52 @@ func MaximumMTU() int {
return defaults.GetDefaults().MaximumIfMTU
}
// Init initialises the TUN/TAP adapter.
func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) {
tun.Adapter.Init(config, log, send, recv, reject)
tun.icmpv6.Init(tun)
go func() {
for {
e := <-tun.Reconfigure
tun.Config.Mutex.RLock()
updated := tun.Config.Current.IfName != tun.Config.Previous.IfName ||
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
}
}
}()
// Init initialises the TUN/TAP module. You must have acquired a Listener from
// the Yggdrasil core before this point and it must not be in use elsewhere.
func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) {
tun.config = config
tun.log = log
tun.listener = listener
tun.dialer = dialer
tun.addrToConn = make(map[address.Address]*tunConn)
tun.subnetToConn = make(map[address.Subnet]*tunConn)
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error {
tun.addr = a
tun.subnet = s
if tun.Config == nil {
func (tun *TunAdapter) Start() error {
tun.config.Mutex.RLock()
defer tun.config.Mutex.RUnlock()
if tun.config == nil || tun.listener == nil || tun.dialer == nil {
return errors.New("No configuration available to TUN/TAP")
}
tun.Config.Mutex.RLock()
ifname := tun.Config.Current.IfName
iftapmode := tun.Config.Current.IfTAPMode
var boxPub crypto.BoxPubKey
boxPubHex, err := hex.DecodeString(tun.config.Current.EncryptionPublicKey)
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)
mtu := tun.Config.Current.IfMTU
tun.Config.Mutex.RUnlock()
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
}
}
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
}
tun.mutex.Lock()
tun.isOpen = true
tun.send = make(chan []byte, 32) // TODO: is this a sensible value?
tun.reconfigure = make(chan chan error)
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 {
go func() {
for {
@ -160,202 +160,107 @@ func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error {
if err != nil {
panic(err)
}
if _, err := tun.iface.Write(request); err != nil {
panic(err)
}
tun.send <- request
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
}
// Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP
// mode then additional ethernet encapsulation is added for the benefit of the
// host operating system.
func (tun *TunAdapter) write() error {
for {
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,
}
// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig
// and then signals the various module goroutines to reconfigure themselves if
// needed.
func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) {
tun.log.Debugln("Reloading TUN/TAP configuration...")
// Create the ICMPv6 response from it
icmpv6Buf, err := CreateICMPv6(
reject.Packet[8:24], reject.Packet[24:40],
ipv6.ICMPTypePacketTooBig, 0, ptb)
tun.config.Replace(*config)
// Send the ICMPv6 response back to the TUN/TAP adapter
if err == nil {
tun.iface.Write(icmpv6Buf)
}
}
fallthrough
default:
continue
}
case data := <-tun.Recv:
if tun.iface == nil {
continue
}
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)
errors := 0
components := []chan chan error{
tun.reconfigure,
tun.ckr.reconfigure,
}
for _, component := range components {
response := make(chan error)
component <- response
if err := <-response; err != nil {
tun.log.Errorln(err)
errors++
}
}
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
// 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)
func (tun *TunAdapter) handler() error {
for {
n, err := tun.iface.Read(buf)
// Accept the incoming connection
conn, err := tun.listener.Accept()
if err != nil {
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
return err
}
tun.log.Errorln("TUN/TAP connection accept error:", err)
return err
}
o := 0
if tun.iface.IsTAP() {
o = tun_ETHER_HEADER_LENGTH
if _, 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 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
// process stops. Typically this operation will happen quickly, but on macOS
// it can block until a read operation is completed.
func (tun *TunAdapter) Close() error {
tun.mutex.Lock()
tun.isOpen = false
tun.mutex.Unlock()
if tun.iface == nil {
return nil
func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
// Prepare a session wrapper for the given connection
s := tunConn{
tun: tun,
conn: conn,
send: make(chan []byte, 32), // TODO: is this a sensible value?
stop: make(chan struct{}),
alive: make(chan struct{}, 1),
}
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
}

View File

@ -109,14 +109,14 @@ func (tun *TunAdapter) setupAddress(addr string) error {
// Create system socket
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
}
// Friendly output
tun.Log.Infof("Interface name: %s", tun.iface.Name())
tun.Log.Infof("Interface IPv6: %s", addr)
tun.Log.Infof("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
// Create the MTU request
var ir in6_ifreq_mtu
@ -126,15 +126,15 @@ func (tun *TunAdapter) setupAddress(addr string) error {
// Set the MTU
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
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
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()
if err != nil {
tun.Log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
tun.Log.Traceln(string(output))
tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
tun.log.Traceln(string(output))
}
}
@ -155,15 +155,15 @@ func (tun *TunAdapter) setupAddress(addr string) error {
// Set the interface address
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
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
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()
if err != nil {
tun.Log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
tun.Log.Traceln(string(output))
tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
tun.log.Traceln(string(output))
}
}

View File

@ -18,7 +18,7 @@ import (
// Configures the "utun" adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
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}
iface, err := water.New(config)
@ -69,7 +69,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
var err error
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
}
@ -98,19 +98,19 @@ func (tun *TunAdapter) setupAddress(addr string) error {
copy(ir.ifr_name[:], tun.iface.Name())
ir.ifru_mtu = uint32(tun.mtu)
tun.Log.Infof("Interface name: %s", ar.ifra_name)
tun.Log.Infof("Interface IPv6: %s", addr)
tun.Log.Infof("Interface MTU: %d", ir.ifru_mtu)
tun.log.Infof("Interface name: %s", ar.ifra_name)
tun.log.Infof("Interface IPv6: %s", addr)
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 {
err = errno
tun.Log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
return err
}
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno
tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno)
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
return err
}

View File

@ -40,9 +40,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
}
}
// Friendly output
tun.Log.Infof("Interface name: %s", tun.iface.Name())
tun.Log.Infof("Interface IPv6: %s", addr)
tun.Log.Infof("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
}

View File

@ -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
// write about it to stdout and don't try to do anything further.
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
}

View File

@ -15,7 +15,7 @@ import (
// delegate the hard work to "netsh".
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
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.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)
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()
if err != nil {
tun.Log.Errorf("Windows netsh failed: %v.", err)
tun.Log.Traceln(string(output))
tun.log.Errorf("Windows netsh failed: %v.", err)
tun.log.Traceln(string(output))
return err
}
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()
if err != nil {
tun.Log.Errorf("Windows netsh failed: %v.", err)
tun.Log.Traceln(string(output))
tun.log.Errorf("Windows netsh failed: %v.", err)
tun.log.Traceln(string(output))
return err
}
// Get a new iface
@ -58,9 +58,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
panic(err)
}
// Friendly output
tun.Log.Infof("Interface name: %s", tun.iface.Name())
tun.Log.Infof("Interface IPv6: %s", addr)
tun.Log.Infof("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
}
@ -71,11 +71,11 @@ func (tun *TunAdapter) setupMTU(mtu int) error {
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("mtu=%d", mtu),
"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()
if err != nil {
tun.Log.Errorf("Windows netsh failed: %v.", err)
tun.Log.Traceln(string(output))
tun.log.Errorf("Windows netsh failed: %v.", err)
tun.log.Traceln(string(output))
return err
}
return nil
@ -88,11 +88,11 @@ func (tun *TunAdapter) setupAddress(addr string) error {
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("addr=%s", addr),
"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()
if err != nil {
tun.Log.Errorf("Windows netsh failed: %v.", err)
tun.Log.Traceln(string(output))
tun.log.Errorf("Windows netsh failed: %v.", err)
tun.log.Traceln(string(output))
return err
}
return nil

View File

@ -48,13 +48,12 @@ func PutBytes(bs []byte) {
// This is a workaround to go's broken timer implementation
func TimerStop(t *time.Timer) bool {
if !t.Stop() {
select {
case <-t.C:
default:
}
stopped := t.Stop()
select {
case <-t.C:
default:
}
return true
return stopped
}
// Run a blocking function with a timeout.

View File

@ -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
View 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
View 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
}

View File

@ -2,14 +2,11 @@ package yggdrasil
import (
"encoding/hex"
"errors"
"io/ioutil"
"net"
"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"
)
@ -33,7 +30,6 @@ type Core struct {
sessions sessions
router router
dht dht
admin admin
searches searches
link link
log *log.Logger
@ -72,11 +68,9 @@ func (c *Core) init() error {
copy(c.sigPub[:], sigPubHex)
copy(c.sigPriv[:], sigPrivHex)
c.admin.init(c)
c.searches.init(c)
c.dht.init(c)
c.sessions.init(c)
//c.multicast.init(c)
c.peers.init(c)
c.router.init(c)
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
// reconfigure themselves if needed.
func (c *Core) UpdateConfig(config *config.NodeConfig) {
c.log.Infoln("Reloading configuration...")
c.log.Debugln("Reloading node configuration...")
c.config.Replace(*config)
errors := 0
components := []chan chan error{
c.admin.reconfigure,
c.searches.reconfigure,
c.dht.reconfigure,
c.sessions.reconfigure,
c.peers.reconfigure,
c.router.reconfigure,
c.router.cryptokey.reconfigure,
c.switchTable.reconfigure,
c.link.reconfigure,
}
@ -143,45 +135,12 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) {
}
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 {
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
// debug logging through the provided log.Logger. The started stack will include
// 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
}
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()
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.
func (c *Core) Stop() {
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)
}

View File

@ -59,13 +59,17 @@ func (c *Core) Init() {
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
c.config = config.NodeConfig{
cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.init( /*bpub, bpriv, spub, spriv*/ )
c.config = config.NodeState{
Current: cfg,
Previous: cfg,
}
c.init()
c.switchTable.start()
c.router.start()
}
@ -82,6 +86,7 @@ func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey {
return (crypto.BoxPubKey)(c.boxPub)
}
/*
func (c *Core) DEBUG_getSend() chan<- []byte {
return c.router.tun.send
}
@ -89,6 +94,7 @@ func (c *Core) DEBUG_getSend() chan<- []byte {
func (c *Core) DEBUG_getRecv() <-chan []byte {
return c.router.tun.recv
}
*/
// Peer
@ -317,6 +323,7 @@ func (c *Core) DEBUG_getAddr() *address.Address {
return address.AddrForNodeID(&c.dht.nodeID)
}
/*
func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
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() {
c.router.tun.close()
}
*/
////////////////////////////////////////////////////////////////////////////////
@ -382,13 +390,17 @@ func (c *Core) DEBUG_init(bpub []byte,
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
c.config = config.NodeConfig{
cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
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 {
panic(err)
@ -455,7 +467,7 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
}
*/
//*
/*
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
c.config.Listen = []string{addrport}
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) {
a := admin{}
c.config.AdminListen = addrport
a.init(c /*, addrport*/)
a.init()
c.admin = a
}
@ -516,6 +529,7 @@ func (c *Core) DEBUG_setupAndStartMulticastInterface() {
c.multicast = m
m.start()
}
*/
////////////////////////////////////////////////////////////////////////////////
@ -579,9 +593,11 @@ func DEBUG_simLinkPeers(p, q *peer) {
q.core.switchTable.idleIn <- q.port
}
/*
func (c *Core) DEBUG_simFixMTU() {
c.router.tun.mtu = 65535
}
*/
////////////////////////////////////////////////////////////////////////////////

View File

@ -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
View 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
View 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
}

View File

@ -23,7 +23,8 @@ package yggdrasil
// The router then runs some sanity checks before passing it to the adapter
import (
"bytes"
//"bytes"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
@ -38,46 +39,13 @@ type router struct {
reconfigure chan chan error
addr address.Address
subnet address.Subnet
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"
toRecv chan router_recvPacket // packets to handle via recvPacket()
adapter adapterImplementation // TUN/TAP adapter
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
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"
reset chan struct{} // signal that coords changed (re-init sessions/dht)
admin chan func() // pass a lambda for the admin socket to query stuff
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.
func (r *router) init(core *Core) {
r.core = core
@ -122,23 +90,12 @@ func (r *router) init(core *Core) {
}
}()
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.admin = make(chan func(), 32)
r.nodeinfo.init(r.core)
r.core.config.Mutex.RLock()
r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy)
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.
@ -157,12 +114,8 @@ func (r *router) mainLoop() {
defer ticker.Stop()
for {
select {
case rp := <-r.toRecv:
r.recvPacket(rp.bs, rp.sinfo)
case p := <-r.in:
r.handleIn(p)
case p := <-r.send:
r.sendPacket(p)
case info := <-r.core.dht.peers:
r.core.dht.insertPeer(info)
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.
// 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.
@ -245,6 +199,12 @@ func (r *router) sendPacket(bs []byte) {
return
}
}
searchCompleted := func(sinfo *sessionInfo, err error) {
if err != nil {
r.core.log.Debugln("DHT search failed:", err)
return
}
}
doSearch := func(packet []byte) {
var nodeID, mask *crypto.NodeID
switch {
@ -270,7 +230,7 @@ func (r *router) sendPacket(bs []byte) {
}
sinfo, isIn := r.core.searches.searches[*nodeID]
if !isIn {
sinfo = r.core.searches.newIterSearch(nodeID, mask)
sinfo = r.core.searches.newIterSearch(nodeID, mask, searchCompleted)
}
if packet != nil {
sinfo.packet = packet
@ -285,12 +245,15 @@ func (r *router) sendPacket(bs []byte) {
if destSnet.IsValid() {
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 {
case !isIn || !sinfo.init:
case !isIn || !sinfo.init.Load().(bool):
// No or unintiialized session, so we need to search first
doSearch(bs)
case time.Since(sinfo.time) > 6*time.Second:
if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second {
case time.Since(sTime) > 6*time.Second:
if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second {
// We haven't heard from the dest in a while
// We tried pinging but didn't get a response
// They may have changed coords
@ -300,13 +263,14 @@ func (r *router) sendPacket(bs []byte) {
} else {
// We haven't heard about the dest in a while
now := time.Now()
if !sinfo.time.Before(sinfo.pingTime) {
if !sTime.Before(pingTime) {
// 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
sinfo.pingSend = now
sinfo.pingSend.Store(now)
r.core.sessions.sendPingPong(sinfo, false)
}
}
@ -347,54 +311,7 @@ func (r *router) sendPacket(bs []byte) {
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.
func (r *router) handleIn(packet []byte) {
@ -423,7 +340,11 @@ func (r *router) handleTraffic(packet []byte) {
if !isIn {
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.

View File

@ -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?
import (
"errors"
"sort"
"time"
@ -32,12 +33,13 @@ const search_RETRY_TIME = time.Second
// 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.
type searchInfo struct {
dest crypto.NodeID
mask crypto.NodeID
time time.Time
packet []byte
toVisit []*dhtInfo
visited map[crypto.NodeID]bool
dest crypto.NodeID
mask crypto.NodeID
time time.Time
packet []byte
toVisit []*dhtInfo
visited map[crypto.NodeID]bool
callback func(*sessionInfo, error)
}
// 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.
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()
for dest, sinfo := range s.searches {
if now.Sub(sinfo.time) > time.Minute {
@ -69,9 +71,10 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searc
}
}
info := searchInfo{
dest: *dest,
mask: *mask,
time: now.Add(-time.Second),
dest: *dest,
mask: *mask,
time: now.Add(-time.Second),
callback: callback,
}
s.searches[*dest] = &info
return &info
@ -87,11 +90,10 @@ func (s *searches) handleDHTRes(res *dhtRes) {
if !isIn || s.checkDHTRes(sinfo, res) {
// Either we don't recognize this search, or we just finished it
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.
@ -137,15 +139,15 @@ func (s *searches) doSearchStep(sinfo *searchInfo) {
if len(sinfo.toVisit) == 0 {
// Dead end, do cleanup
delete(s.searches, sinfo.dest)
go sinfo.callback(nil, errors.New("search reached dead end"))
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.
@ -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.
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
sinfo := s.createSearch(dest, mask)
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
sinfo := s.createSearch(dest, mask, callback)
sinfo.toVisit = s.core.dht.lookup(dest, true)
sinfo.visited = make(map[crypto.NodeID]bool)
return sinfo
@ -200,6 +202,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool {
sinfo = s.core.sessions.createSession(&res.Key)
if sinfo == nil {
// nil if the DHT search finished but the session wasn't allowed
go info.callback(nil, errors.New("session not allowed"))
return true
}
_, 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.packet = info.packet
s.core.sessions.ping(sinfo)
go info.callback(sinfo, nil)
// Cleanup
delete(s.searches, res.Dest)
return true

View File

@ -7,45 +7,61 @@ package yggdrasil
import (
"bytes"
"encoding/hex"
"sync"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"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.
// 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 {
core *Core
reconfigure chan chan error
theirAddr address.Address
theirSubnet address.Subnet
theirPermPub crypto.BoxPubKey
theirSesPub crypto.BoxPubKey
mySesPub crypto.BoxPubKey
mySesPriv crypto.BoxPrivKey
sharedSesKey crypto.BoxSharedKey // derived from session keys
theirHandle crypto.Handle
myHandle crypto.Handle
theirNonce crypto.BoxNonce
myNonce crypto.BoxNonce
theirMTU uint16
myMTU uint16
wasMTUFixed bool // Was the MTU fixed by a receive error?
time time.Time // Time we last received a packet
coords []byte // coords of destination
packet []byte // a buffered packet, sent immediately on ping/pong
init bool // Reset if coords change
send chan []byte
recv chan *wire_trafficPacket
nonceMask uint64
tstamp int64 // tstamp from their last session ping, replay attack mitigation
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
bytesSent uint64 // Bytes of real traffic sent in this session
bytesRecvd uint64 // Bytes of real traffic received in this session
core *Core //
reconfigure chan chan error //
theirAddr address.Address //
theirSubnet address.Subnet //
theirPermPub crypto.BoxPubKey //
theirSesPub crypto.BoxPubKey //
mySesPub crypto.BoxPubKey //
mySesPriv crypto.BoxPrivKey //
sharedSesKey crypto.BoxSharedKey // derived from session keys
theirHandle crypto.Handle //
myHandle crypto.Handle //
theirNonce crypto.BoxNonce //
theirNonceMask uint64 //
myNonce crypto.BoxNonce //
theirMTU uint16 //
myMTU uint16 //
wasMTUFixed bool // Was the MTU fixed by a receive error?
timeOpened time.Time // Time the sessino was opened
time time.Time // Time we last received a packet
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
coords []byte // coords of destination
packet []byte // a buffered packet, sent immediately on ping/pong
init bool // Reset if coords change
tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation
bytesSent uint64 // Bytes of real traffic sent 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.
@ -53,10 +69,10 @@ type sessionPing struct {
SendPermPub crypto.BoxPubKey // Sender's permanent key
Handle crypto.Handle // Random number to ID session
SendSesPub crypto.BoxPubKey // Session key to use
Coords []byte
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool
MTU uint16
Coords []byte //
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool //
MTU uint16 //
}
// 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.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub)
s.theirNonce = crypto.BoxNonce{}
s.nonceMask = 0
s.theirNonceMask = 0
}
if p.MTU >= 1280 || p.MTU == 0 {
s.theirMTU = p.MTU
@ -85,35 +101,28 @@ func (s *sessionInfo) update(p *sessionPing) bool {
// allocate enough space for additional coords
s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...)
}
now := time.Now()
s.time = now
s.time = time.Now()
s.tstamp = p.Tstamp
s.init = 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.
// Sessions are indexed by handle.
// Additionally, stores maps of address/subnet onto keys, and keys onto handles.
type sessions struct {
core *Core
reconfigure chan chan error
lastCleanup time.Time
// Maps known permanent keys to their shared key, used by DHT a lot
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey
// Maps (secret) handle onto session info
sinfos map[crypto.Handle]*sessionInfo
// Maps mySesPub onto handle
byMySes map[crypto.BoxPubKey]*crypto.Handle
// Maps theirPermPub onto handle
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle
addrToPerm map[address.Address]*crypto.BoxPubKey
subnetToPerm map[address.Subnet]*crypto.BoxPubKey
core *Core
listener *Listener
listenerMutex sync.Mutex
reconfigure chan chan error
lastCleanup time.Time
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot
sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info
conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections
byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle
addrToPerm map[address.Address]*crypto.BoxPubKey
subnetToPerm map[address.Subnet]*crypto.BoxPubKey
}
// 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.
func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) {
sinfo, isIn := ss.sinfos[*handle]
if isIn && sinfo.timedout() {
// We have a session, but it has timed out
return nil, false
}
return sinfo, isIn
}
@ -262,8 +267,9 @@ func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool)
return sinfo, isIn
}
// Creates a new session and lazily cleans up old/timedout existing sessions.
// This includse initializing session info to sane defaults (e.g. lowest supported MTU).
// Creates a new session and lazily cleans up old existing sessions. This
// includse initializing session info to sane defaults (e.g. lowest supported
// MTU).
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
if !ss.isSessionAllowed(theirPermKey, true) {
return nil
@ -277,10 +283,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.mySesPriv = *priv
sinfo.myNonce = *crypto.NewBoxNonce()
sinfo.theirMTU = 1280
if ss.core.router.adapter != nil {
sinfo.myMTU = uint16(ss.core.router.adapter.MTU())
}
sinfo.myMTU = 1280
now := time.Now()
sinfo.timeOpened = now
sinfo.time = now
sinfo.mtuTime = now
sinfo.pingTime = now
@ -304,14 +309,14 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.myHandle = *crypto.NewHandle()
sinfo.theirAddr = *address.AddrForNodeID(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)
go sinfo.doWorker()
ss.sinfos[sinfo.myHandle] = &sinfo
ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle
ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle
ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub
ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub
go sinfo.workerMain()
return &sinfo
}
@ -325,11 +330,6 @@ func (ss *sessions) cleanup() {
if time.Since(ss.lastCleanup) < time.Minute {
return
}
for _, s := range ss.sinfos {
if s.timedout() {
s.close()
}
}
permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared))
for k, v := range ss.permShared {
permShared[k] = v
@ -370,8 +370,7 @@ func (sinfo *sessionInfo) close() {
delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr)
delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet)
close(sinfo.send)
close(sinfo.recv)
close(sinfo.worker)
}
// Returns a session ping appropriate for the given session info.
@ -449,29 +448,42 @@ func (ss *sessions) handlePing(ping *sessionPing) {
return
}
}
if !isIn || sinfo.timedout() {
if isIn {
sinfo.close()
}
if !isIn {
ss.createSession(&ping.SendPermPub)
sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub)
if !isIn {
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
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
return
}
if !ping.IsPong {
ss.sendPingPong(sinfo, true)
}
if sinfo.packet != nil {
// send
var bs []byte
bs, sinfo.packet = sinfo.packet, nil
ss.core.router.sendPacket(bs)
}
sinfo.doWorker(func() {
// Update the session
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
return
}
if !ping.IsPong {
ss.sendPingPong(sinfo, true)
}
if sinfo.packet != nil {
/* FIXME this needs to live in the net.Conn or something, needs work in Write
// send
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.
@ -494,7 +506,7 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool {
if diff > 0 {
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
@ -504,12 +516,12 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) {
diff := theirNonce.Minus(&sinfo.theirNonce)
if diff > 0 {
// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
sinfo.nonceMask <<= uint64(diff)
sinfo.nonceMask &= 0x01
sinfo.theirNonceMask <<= uint64(diff)
sinfo.theirNonceMask &= 0x01
sinfo.theirNonce = *theirNonce
} else {
// 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.
func (ss *sessions) resetInits() {
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}
}

View File

@ -577,23 +577,28 @@ func (t *switchTable) start() error {
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
// 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()
myDist := table.self.dist(dest)
if myDist == 0 {
// Skip the iteration step if it's impossible to be closer
return nil
}
closer := make(map[switchPort]int, len(table.elems))
t.queues.closer = t.queues.closer[:0]
for _, info := range table.elems {
dist := info.locator.dist(dest)
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
@ -656,9 +661,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
var bestDist int
var bestTime time.Time
ports := t.core.peers.getPorts()
for port, dist := range closer {
to := ports[port]
thisTime, isIdle := idle[port]
for _, cinfo := range closer {
to := ports[cinfo.port]
thisTime, isIdle := idle[cinfo.port]
var update bool
switch {
case to == nil:
@ -667,9 +672,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
//nothing
case best == nil:
update = true
case dist < bestDist:
case cinfo.dist < bestDist:
update = true
case dist > bestDist:
case cinfo.dist > bestDist:
//nothing
case thisTime.Before(bestTime):
update = true
@ -678,7 +683,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
}
if update {
best = to
bestDist = dist
bestDist = cinfo.dist
bestTime = thisTime
}
}
@ -711,6 +716,7 @@ type switch_buffers struct {
size uint64 // Total size of all buffers, in bytes
maxbufs int
maxsize uint64
closer []closerInfo // Scratch space
}
func (b *switch_buffers) cleanup(t *switchTable) {

View File

@ -255,13 +255,6 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) {
if err != nil {
return
}
conn = &wrappedConn{
c: conn,
raddr: &wrappedAddr{
network: "tcp",
addr: saddr,
},
}
t.handler(conn, false, dialerdst.String())
} else {
dst, err := net.ResolveTCPAddr("tcp", saddr)