From 2dd8152a0c1d08153574060cddf512b6d9d35a43 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 29 Jan 2018 18:48:14 -0600 Subject: [PATCH] reorganize and add more to the admin --- src/yggdrasil/admin.go | 420 ++++++++++++++++++++++++++--------------- 1 file changed, 264 insertions(+), 156 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 58840cf6..419d7f1e 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -5,6 +5,7 @@ import "os" import "bytes" import "fmt" import "sort" +import "strings" // TODO? Make all of this JSON // TODO: Add authentication @@ -12,11 +13,45 @@ import "sort" type admin struct { core *Core listenaddr string + handlers []admin_handlerInfo +} + +type admin_handlerInfo struct { + name string // Checked against the first word of the api call + args []string // List of human-readable argument names + handler func(*[]byte, ...string) // First arg is pointer to the out slice, rest is args +} + +func (a *admin) addHandler(name string, args []string, handler func(*[]byte, ...string)) { + a.handlers = append(a.handlers, admin_handlerInfo{name, args, handler}) } func (a *admin) init(c *Core, listenaddr string) { a.core = c a.listenaddr = listenaddr + a.addHandler("help", nil, func(out *[]byte, _ ...string) { + for _, handler := range a.handlers { + tmp := append([]string{handler.name}, handler.args...) + *out = append(*out, []byte(strings.Join(tmp, " "))...) + *out = append(*out, "\n"...) + } + }) + // TODO? have other parts of the program call to add their own handlers + a.addHandler("dot", nil, func(out *[]byte, _ ...string) { + *out = a.getResponse_dot() + }) + a.addHandler("getSelf", nil, func(out *[]byte, _ ...string) { + *out = []byte(a.printInfos(a.getData_getPeers())) + }) + a.addHandler("getPeers", nil, func(out *[]byte, _ ...string) { + *out = []byte(a.printInfos(a.getData_getPeers())) + }) + a.addHandler("getDHT", nil, func(out *[]byte, _ ...string) { + *out = []byte(a.printInfos(a.getData_getDHT())) + }) + a.addHandler("getSessions", nil, func(out *[]byte, _ ...string) { + *out = []byte(a.printInfos(a.getData_getSessions())) + }) go a.listen() } @@ -44,166 +79,239 @@ func (a *admin) handleRequest(conn net.Conn) { conn.Close() return } + var out []byte buf = bytes.Trim(buf, "\x00\r\n\t") - switch string(buf) { - case "dot": - const mDepth = 1024 - m := make(map[[mDepth]switchPort]string) - table := a.core.switchTable.table.Load().(lookupTable) - peers := a.core.peers.ports.Load().(map[switchPort]*peer) - - // Add my own entry - peerID := address_addrForNodeID(getNodeID(&peers[0].box)) - myAddr := net.IP(peerID[:]).String() - var index [mDepth]switchPort - copy(index[:mDepth], table.self.coords[:]) - m[index] = myAddr - - // Connect switch table entries to peer entries - for _, tableentry := range table.elems { - for _, peerentry := range peers { - if peerentry.port == tableentry.port { - peerID := address_addrForNodeID(getNodeID(&peerentry.box)) - addr := net.IP(peerID[:]).String() - var index [mDepth]switchPort - copy(index[:], tableentry.locator.coords) - m[index] = addr - } - } - } - - getPorts := func(coords []byte) []switchPort { - var ports []switchPort - for offset := 0; ; { - coord, length := wire_decode_uint64(coords[offset:]) - if length == 0 { - break - } - ports = append(ports, switchPort(coord)) - offset += length - } - return ports - } - - // Look up everything we know from DHT - getDHT := func() { - for i := 0; i < a.core.dht.nBuckets(); i++ { - b := a.core.dht.getBucket(i) - for _, v := range b.infos { - destPorts := getPorts(v.coords) - addr := net.IP(address_addrForNodeID(v.nodeID_hidden)[:]).String() - var index [mDepth]switchPort - copy(index[:], destPorts) - m[index] = addr - } - } - } - a.core.router.doAdmin(getDHT) - - // Look up everything we know from active sessions - getSessions := func() { - for _, sinfo := range a.core.sessions.sinfos { - destPorts := getPorts(sinfo.coords) - var index [mDepth]switchPort - copy(index[:], destPorts) - m[index] = net.IP(sinfo.theirAddr[:]).String() - } - } - a.core.router.doAdmin(getSessions) - - // Start building a tree from all known nodes - type nodeInfo struct { - name string - key [mDepth]switchPort - parent [mDepth]switchPort - } - infos := make(map[[mDepth]switchPort]nodeInfo) - // First fill the tree with all known nodes, no parents - for k, n := range m { - infos[k] = nodeInfo{ - name: n, - key: k, - } - } - // Now go through and create placeholders for any missing nodes - for _, info := range infos { - for idx, port := range info.key { - if port == 0 { - break - } - var key [mDepth]switchPort - copy(key[:idx], info.key[:]) - newInfo, isIn := infos[key] - if isIn { - continue - } - newInfo.name = "?" - newInfo.key = key - infos[key] = newInfo - } - } - // Now go through and attach parents - for _, info := range infos { - info.parent = info.key - for idx := len(info.parent) - 1; idx >= 0; idx-- { - if info.parent[idx] != 0 { - info.parent[idx] = 0 - break - } - } - infos[info.key] = info - } - // Finally, get a sorted list of keys, which we use to organize the output - var keys [][mDepth]switchPort - for _, info := range infos { - keys = append(keys, info.key) - } - less := func(i, j int) bool { - for idx := range keys[i] { - if keys[i][idx] < keys[j][idx] { - return true - } - if keys[i][idx] > keys[j][idx] { - return false - } - } - return false - } - sort.Slice(keys, less) - // Now print it all out - conn.Write([]byte(fmt.Sprintf("digraph {\n"))) - // First set the labels - for _, key := range keys { - info := infos[key] - if info.name == myAddr { - conn.Write([]byte(fmt.Sprintf("\"%v\" [ style = \"filled\", label = \"%v\" ];\n", info.key, info.name))) - } else { - conn.Write([]byte(fmt.Sprintf("\"%v\" [ label = \"%v\" ];\n", info.key, info.name))) - } - } - // Then print the tree structure - for _, key := range keys { - info := infos[key] - if info.key == info.parent { - continue - } // happens for the root, skip it - for idx := len(info.key) - 1; idx >= 0; idx-- { - port := info.key[idx] - if port == 0 { - continue - } - conn.Write([]byte(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" ];\n", info.parent, info.key, port))) - break - } - } - conn.Write([]byte(fmt.Sprintf("}\n"))) - break - - default: - conn.Write([]byte("I didn't understand that!\n")) + call := strings.Split(string(buf), " ") + var cmd string + var args []string + if len(call) > 0 { + cmd = call[0] + args = call[1:] } + done := false + for _, handler := range a.handlers { + if cmd == handler.name { + handler.handler(&out, args...) + done = true + break + } + } + if !done { + out = []byte("I didn't understand that!\n") + } + _, err = conn.Write(out) if err != nil { a.core.log.Printf("Admin socket error: %v", err) } conn.Close() } + +// Maps things like "IP", "port", "bucket", or "coords" onto strings +type admin_pair struct { + key string + val string +} +type admin_nodeInfo []admin_pair + +func (n *admin_nodeInfo) asMap() map[string]string { + m := make(map[string]string, len(*n)) + for _, p := range *n { + m[p.key] = p.val + } + return m +} + +func (n *admin_nodeInfo) toString() string { + // TODO return something nicer looking than this + var out []string + for _, p := range *n { + out = append(out, fmt.Sprintf("%v: %v", p.key, p.val)) + } + return strings.Join(out, ", ") + return fmt.Sprint(*n) +} + +func (a *admin) printInfos(infos []admin_nodeInfo) string { + var out []string + for _, info := range infos { + out = append(out, info.toString()) + } + out = append(out, "") // To add a trailing "\n" in the join + return strings.Join(out, "\n") +} + +func (a *admin) getData_getSelf() *admin_nodeInfo { + table := a.core.switchTable.table.Load().(lookupTable) + addr := a.core.router.addr + coords := table.self.getCoords() + self := admin_nodeInfo{ + {"IP", net.IP(addr[:]).String()}, + {"coords", fmt.Sprint(coords)}, + } + return &self +} + +func (a *admin) getData_getPeers() []admin_nodeInfo { + var peerInfos []admin_nodeInfo + table := a.core.switchTable.table.Load().(lookupTable) + peers := a.core.peers.ports.Load().(map[switchPort]*peer) + for _, elem := range table.elems { + peer, isIn := peers[elem.port] + if !isIn { + continue + } + addr := *address_addrForNodeID(getNodeID(&peer.box)) + coords := elem.locator.getCoords() + info := admin_nodeInfo{ + {"IP", net.IP(addr[:]).String()}, + {"coords", fmt.Sprint(coords)}, + {"port", fmt.Sprint(elem.port)}, + } + peerInfos = append(peerInfos, info) + } + return peerInfos +} + +func (a *admin) getData_getDHT() []admin_nodeInfo { + var infos []admin_nodeInfo + getDHT := func() { + for i := 0; i < a.core.dht.nBuckets(); i++ { + b := a.core.dht.getBucket(i) + for _, v := range b.infos { + addr := *address_addrForNodeID(v.getNodeID()) + info := admin_nodeInfo{ + {"IP", net.IP(addr[:]).String()}, + {"coords", fmt.Sprint(v.coords)}, + {"bucket", fmt.Sprint(i)}, + } + infos = append(infos, info) + } + } + } + a.core.router.doAdmin(getDHT) + return infos +} + +func (a *admin) getData_getSessions() []admin_nodeInfo { + var infos []admin_nodeInfo + getSessions := func() { + for _, sinfo := range a.core.sessions.sinfos { + // TODO? skipped known but timed out sessions? + info := admin_nodeInfo{ + {"IP", net.IP(sinfo.theirAddr[:]).String()}, + {"coords", fmt.Sprint(sinfo.coords)}, + } + infos = append(infos, info) + } + } + a.core.router.doAdmin(getSessions) + return infos +} + +func (a *admin) getResponse_dot() []byte { + self := a.getData_getSelf().asMap() + myAddr := self["IP"] + peers := a.getData_getPeers() + dht := a.getData_getDHT() + sessions := a.getData_getSessions() + // Map of coords onto IP + m := make(map[string]string) + m[self["coords"]] = self["IP"] + for _, peer := range peers { + p := peer.asMap() + m[p["coords"]] = p["IP"] + } + for _, node := range dht { + n := node.asMap() + m[n["coords"]] = n["IP"] + } + for _, node := range sessions { + n := node.asMap() + m[n["coords"]] = n["IP"] + } + + // Start building a tree from all known nodes + type nodeInfo struct { + name string + key string + parent string + } + infos := make(map[string]nodeInfo) + // First fill the tree with all known nodes, no parents + for k, n := range m { + infos[k] = nodeInfo{ + name: n, + key: k, + } + } + // 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, " ") + } + // 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 + 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) + } + // TODO sort + less := func(i, j int) bool { + return keys[i] < keys[j] + } + sort.Slice(keys, less) + // 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] + if info.name == myAddr { + put(fmt.Sprintf("\"%v\" [ style = \"filled\", label = \"%v\" ];\n", info.key, info.name)) + } else { + put(fmt.Sprintf("\"%v\" [ label = \"%v\" ];\n", info.key, info.name)) + } + } + // Then print the tree structure + for _, key := range keys { + info := infos[key] + if info.key == info.parent { + continue + } // happens for the root, skip it + coordsSplit := coordSlice(key) + if len(coordsSplit) == 0 { + continue + } + port := coordsSplit[len(coordsSplit)-1] + put(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" ];\n", info.parent, info.key, port)) + } + put("}\n") + return out +}