diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go new file mode 100644 index 0000000..b527810 --- /dev/null +++ b/src/yggdrasil/api.go @@ -0,0 +1,341 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "net" + "sort" + "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 +} + +type DHTEntry struct { + PublicKey crypto.BoxPubKey + Coords []byte + LastSeen time.Duration +} + +type SwitchQueue struct{} +type Session struct{} + +// 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: coords, + BytesSent: atomic.LoadUint64(&peer.bytesSent), + BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), + Port: uint64(elem.port), + Protocol: peer.intf.info.linkType, + } + copy(info.PublicKey[:], peer.box[:]) + switchpeers = append(switchpeers, info) + } + return switchpeers +} + +func (c *Core) GetDHT() []DHTEntry { + /* + var infos []admin_nodeInfo + getDHT := func() { + now := time.Now() + var dhtInfos []*dhtInfo + for _, v := range a.core.dht.table { + dhtInfos = append(dhtInfos, v) + } + sort.SliceStable(dhtInfos, func(i, j int) bool { + return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) + }) + for _, v := range dhtInfos { + addr := *address.AddrForNodeID(v.getNodeID()) + info := admin_nodeInfo{ + {"ip", net.IP(addr[:]).String()}, + {"coords", fmt.Sprint(v.coords)}, + {"last_seen", int(now.Sub(v.recv).Seconds())}, + {"box_pub_key", hex.EncodeToString(v.key[:])}, + } + infos = append(infos, info) + } + } + a.core.router.doAdmin(getDHT) + return infos + */ + return []DHTEntry{} +} + +func (c *Core) GetSwitchQueues() []SwitchQueue { + /* + var peerInfos admin_nodeInfo + switchTable := &a.core.switchTable + getSwitchQueues := func() { + queues := make([]map[string]interface{}, 0) + for k, v := range switchTable.queues.bufs { + nexthop := switchTable.bestPortForCoords([]byte(k)) + queue := map[string]interface{}{ + "queue_id": k, + "queue_size": v.size, + "queue_packets": len(v.packets), + "queue_port": nexthop, + } + queues = append(queues, queue) + } + peerInfos = admin_nodeInfo{ + {"queues", queues}, + {"queues_count", len(switchTable.queues.bufs)}, + {"queues_size", switchTable.queues.size}, + {"highest_queues_count", switchTable.queues.maxbufs}, + {"highest_queues_size", switchTable.queues.maxsize}, + {"maximum_queues_size", switchTable.queueTotalMaxSize}, + } + } + a.core.switchTable.doAdmin(getSwitchQueues) + return peerInfos + */ + return []SwitchQueue{} +} + +func (c *Core) GetSessions() []Session { + /* + 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)}, + {"mtu", sinfo.getMTU()}, + {"was_mtu_fixed", sinfo.wasMTUFixed}, + {"bytes_sent", sinfo.bytesSent}, + {"bytes_recvd", sinfo.bytesRecvd}, + {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])}, + } + infos = append(infos, info) + } + } + a.core.router.doAdmin(getSessions) + return infos + */ + return []Session{} +} + +// 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[:]) +} + +// 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) +} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 63d33d9..41bb11f 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -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" ) @@ -147,24 +144,6 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { } } -// 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 -} - // 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, @@ -226,137 +205,3 @@ func (c *Core) Stop() { c.log.Infoln("Stopping...") c.admin.close() } - -// 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[:]) -} - -// 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) -}