mirror of
https://github.com/cwinfo/yggdrasil-go.git
synced 2024-11-22 02:30:31 +00:00
reorganize and add more to the admin
This commit is contained in:
parent
fe8a78f966
commit
2dd8152a0c
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user