diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/admin/admin.go b/src/admin/admin.go index 2b73764c..9789bca2 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -17,6 +17,7 @@ import ( "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/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -78,11 +79,6 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. } 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() subnet := c.Subnet() @@ -110,7 +106,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "bytes_recvd": p.BytesRecvd, "proto": p.Protocol, "endpoint": p.Endpoint, - "box_pub_key": p.PublicKey, + "box_pub_key": hex.EncodeToString(p.PublicKey[:]), } } return Info{"peers": peers}, nil @@ -128,7 +124,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "bytes_recvd": s.BytesRecvd, "proto": s.Protocol, "endpoint": s.Endpoint, - "box_pub_key": s.PublicKey, + "box_pub_key": hex.EncodeToString(s.PublicKey[:]), } } return Info{"switchpeers": switchpeers}, nil @@ -147,7 +143,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. dht[so] = Info{ "coords": fmt.Sprintf("%v", d.Coords), "last_seen": d.LastSeen.Seconds(), - "box_pub_key": d.PublicKey, + "box_pub_key": hex.EncodeToString(d.PublicKey[:]), } } return Info{"dht": dht}, nil @@ -164,7 +160,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "mtu": s.MTU, "uptime": s.Uptime.Seconds(), "was_mtu_fixed": s.WasMTUFixed, - "box_pub_key": s.PublicKey, + "box_pub_key": hex.EncodeToString(s.PublicKey[:]), } } return Info{"sessions": sessions}, nil @@ -243,31 +239,46 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. } }) a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { + var reserr error + var result yggdrasil.DHTRes 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 + coords := util.DecodeCoordString(in["coords"].(string)) + var boxPubKey crypto.BoxPubKey + if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { + copy(boxPubKey[:], b[:]) + if n, err := hex.DecodeString(in["target"].(string)); err == nil { + var targetNodeID crypto.NodeID + copy(targetNodeID[:], n[:]) + result, reserr = a.core.DHTPing(boxPubKey, coords, &targetNodeID) + } else { + result, reserr = a.core.DHTPing(boxPubKey, coords, nil) } - return Info{"nodes": infos}, nil } else { return Info{}, err } + if reserr != nil { + return Info{}, reserr + } + 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 }) 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 + var boxPubKey crypto.BoxPubKey + var coords []uint64 if in["box_pub_key"] == nil && in["coords"] == nil { nodeinfo := a.core.MyNodeInfo() var jsoninfo interface{} @@ -279,10 +290,14 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. } 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) + if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { + copy(boxPubKey[:], b[:]) + } else { + return Info{}, err + } + coords = util.DecodeCoordString(in["coords"].(string)) } - result, err := a.core.GetNodeInfo(box_pub_key, coords, nocache) + result, err := a.core.GetNodeInfo(boxPubKey, coords, nocache) if err == nil { var m map[string]interface{} if err = json.Unmarshal(result, &m); err == nil { @@ -472,133 +487,3 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { } } } - -// 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 -} -*/ diff --git a/src/util/util.go b/src/util/util.go index 4596474e..6fb515c8 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -2,9 +2,13 @@ package util // These are misc. utility functions that didn't really fit anywhere else -import "runtime" -import "sync" -import "time" +import ( + "runtime" + "strconv" + "strings" + "sync" + "time" +) // A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere. func Yield() { @@ -91,3 +95,16 @@ func Difference(a, b []string) []string { } return ab } + +// DecodeCoordString decodes a string representing coordinates in [1 2 3] format +// and returns a []uint64. +func DecodeCoordString(in string) (out []uint64) { + s := strings.Trim(in, "[]") + t := strings.Split(s, " ") + for _, a := range t { + if u, err := strconv.ParseUint(a, 0, 64); err == nil { + out = append(out, u) + } + } + return out +} diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 014c370f..98f90130 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -6,8 +6,6 @@ import ( "fmt" "net" "sort" - "strconv" - "strings" "sync/atomic" "time" @@ -34,7 +32,7 @@ type Peer struct { // to a given node. type SwitchPeer struct { PublicKey crypto.BoxPubKey - Coords []byte + Coords []uint64 BytesSent uint64 BytesRecvd uint64 Port uint64 @@ -46,14 +44,14 @@ type SwitchPeer struct { // DHT searches. type DHTEntry struct { PublicKey crypto.BoxPubKey - Coords []byte + Coords []uint64 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 + Coords []uint64 // coords of the sender Dest crypto.NodeID // the destination node ID Infos []DHTEntry // response } @@ -85,7 +83,7 @@ type SwitchQueue struct { // Session represents an open session with another node. type Session struct { PublicKey crypto.BoxPubKey - Coords []byte + Coords []uint64 BytesSent uint64 BytesRecvd uint64 MTU uint16 @@ -138,7 +136,7 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { } coords := elem.locator.getCoords() info := SwitchPeer{ - Coords: append([]byte{}, coords...), + Coords: append([]uint64{}, wire_coordsBytestoUint64s(coords)...), BytesSent: atomic.LoadUint64(&peer.bytesSent), BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), Port: uint64(elem.port), @@ -166,7 +164,7 @@ func (c *Core) GetDHT() []DHTEntry { }) for _, v := range dhtentry { info := DHTEntry{ - Coords: append([]byte{}, v.coords...), + Coords: append([]uint64{}, wire_coordsBytestoUint64s(v.coords)...), LastSeen: now.Sub(v.recv), } copy(info.PublicKey[:], v.key[:]) @@ -214,7 +212,7 @@ func (c *Core) GetSessions() []Session { var session Session workerFunc := func() { session = Session{ - Coords: append([]byte{}, sinfo.coords...), + Coords: append([]uint64{}, wire_coordsBytestoUint64s(sinfo.coords)...), MTU: sinfo.getMTU(), BytesSent: sinfo.bytesSent, BytesRecvd: sinfo.bytesRecvd, @@ -311,9 +309,9 @@ func (c *Core) EncryptionPublicKey() string { } // Coords returns the current coordinates of the node. -func (c *Core) Coords() []byte { +func (c *Core) Coords() []uint64 { table := c.switchTable.table.Load().(lookupTable) - return table.self.getCoords() + return wire_coordsBytestoUint64s(table.self.getCoords()) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 @@ -336,8 +334,8 @@ 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. +// SetNodeInfo sets the local 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) } @@ -346,30 +344,7 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { // 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 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)) - } - } +func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) (NodeInfoPayload, error) { response := make(chan *NodeInfoPayload, 1) sendNodeInfoRequest := func() { c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) { @@ -379,7 +354,7 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf default: } }) - c.router.nodeinfo.sendNodeInfo(key, coords, false) + c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) } c.router.doAdmin(sendNodeInfoRequest) go func() { @@ -389,7 +364,7 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf for res := range response { return *res, nil } - return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", keyString) + return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", hex.EncodeToString(key[:])) } // SetSessionGatekeeper allows you to configure a handler function for deciding @@ -477,64 +452,38 @@ func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) { // DHTPing sends 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)) - } - } +func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.NodeID) (DHTRes, error) { resCh := make(chan *dhtRes, 1) info := dhtInfo{ key: key, - coords: coords, + coords: wire_coordsUint64stoBytes(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) + if target == nil { + target = info.getNodeID() } - rq := dhtReqKey{info.key, target} + rq := dhtReqKey{info.key, *target} sendPing := func() { c.dht.addCallback(&rq, func(res *dhtRes) { resCh <- res }) - c.dht.ping(&info, &target) + c.dht.ping(&info, &rq.dest) } c.router.doAdmin(sendPing) // TODO: do something better than the below... res := <-resCh if res != nil { r := DHTRes{ - Coords: append([]byte{}, res.Coords...), + Coords: append([]uint64{}, wire_coordsBytestoUint64s(res.Coords)...), } copy(r.PublicKey[:], res.Key[:]) for _, i := range res.Infos { e := DHTEntry{ - Coords: append([]byte{}, i.coords...), + Coords: append([]uint64{}, wire_coordsBytestoUint64s(i.coords)...), } copy(e.PublicKey[:], i.key[:]) r.Infos = append(r.Infos, e) } return r, nil } - return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", keyString) + return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", hex.EncodeToString(key[:])) } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 5aa354dc..4ff19e2d 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -115,6 +115,29 @@ func wire_decode_coords(packet []byte) ([]byte, int) { return packet[coordBegin:coordEnd], coordEnd } +// Converts a []uint64 set of coords to a []byte set of coords. +func wire_coordsUint64stoBytes(in []uint64) (out []byte) { + for _, coord := range in { + c := wire_encode_uint64(coord) + out = append(out, c...) + } + return out +} + +// Converts a []byte set of coords to a []uint64 set of coords. +func wire_coordsBytestoUint64s(in []byte) (out []uint64) { + offset := 0 + for { + coord, length := wire_decode_uint64(in[offset:]) + if length == 0 { + break + } + out = append(out, coord) + offset += length + } + return out +} + //////////////////////////////////////////////////////////////////////////////// // Encodes a swtichMsg into its wire format.