273 lines
6.1 KiB
Go
273 lines
6.1 KiB
Go
package tendrils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/fvbommel/sortorder"
|
|
"go.jetify.com/typeid"
|
|
)
|
|
|
|
func newTypeID(prefix string) string {
|
|
tid, _ := typeid.WithPrefix(prefix)
|
|
return tid.String()
|
|
}
|
|
|
|
type MAC string
|
|
|
|
func (m MAC) Parse() net.HardwareAddr {
|
|
mac, _ := net.ParseMAC(string(m))
|
|
return mac
|
|
}
|
|
|
|
func MACFrom(mac net.HardwareAddr) MAC {
|
|
if mac == nil {
|
|
return ""
|
|
}
|
|
return MAC(mac.String())
|
|
}
|
|
|
|
type IPSet map[string]bool
|
|
|
|
func (s IPSet) MarshalJSON() ([]byte, error) {
|
|
ips := make([]string, 0, len(s))
|
|
for ip := range s {
|
|
ips = append(ips, ip)
|
|
}
|
|
sort.Strings(ips)
|
|
return json.Marshal(ips)
|
|
}
|
|
|
|
func (s IPSet) Add(ip net.IP) {
|
|
s[ip.String()] = true
|
|
}
|
|
|
|
func (s IPSet) Has(ip string) bool {
|
|
return s[ip]
|
|
}
|
|
|
|
func (s IPSet) Slice() []string {
|
|
ips := make([]string, 0, len(s))
|
|
for ip := range s {
|
|
ips = append(ips, ip)
|
|
}
|
|
sort.Strings(ips)
|
|
return ips
|
|
}
|
|
|
|
type NameSet map[string]bool
|
|
|
|
func sortNamesByLength(names []string) {
|
|
sort.Slice(names, func(i, j int) bool {
|
|
if len(names[i]) != len(names[j]) {
|
|
return len(names[i]) < len(names[j])
|
|
}
|
|
return names[i] < names[j]
|
|
})
|
|
}
|
|
|
|
func (s NameSet) MarshalJSON() ([]byte, error) {
|
|
names := make([]string, 0, len(s))
|
|
for name := range s {
|
|
names = append(names, name)
|
|
}
|
|
sortNamesByLength(names)
|
|
return json.Marshal(names)
|
|
}
|
|
|
|
func (s NameSet) Add(name string) {
|
|
s[name] = true
|
|
}
|
|
|
|
func (s NameSet) Has(name string) bool {
|
|
return s[name]
|
|
}
|
|
|
|
type InterfaceMap map[string]*Interface
|
|
|
|
func (m InterfaceMap) MarshalJSON() ([]byte, error) {
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
return sortorder.NaturalLess(keys[i], keys[j])
|
|
})
|
|
ifaces := make([]*Interface, 0, len(m))
|
|
for _, k := range keys {
|
|
ifaces = append(ifaces, m[k])
|
|
}
|
|
return json.Marshal(ifaces)
|
|
}
|
|
|
|
type Interface struct {
|
|
Name string `json:"name,omitempty"`
|
|
MAC MAC `json:"mac"`
|
|
IPs IPSet `json:"ips"`
|
|
Stats *InterfaceStats `json:"stats,omitempty"`
|
|
}
|
|
|
|
type InterfaceStats struct {
|
|
Speed uint64 `json:"speed,omitempty"`
|
|
InErrors uint64 `json:"in_errors,omitempty"`
|
|
OutErrors uint64 `json:"out_errors,omitempty"`
|
|
InPktsRate float64 `json:"in_pkts_rate,omitempty"`
|
|
OutPktsRate float64 `json:"out_pkts_rate,omitempty"`
|
|
InBytesRate float64 `json:"in_bytes_rate,omitempty"`
|
|
OutBytesRate float64 `json:"out_bytes_rate,omitempty"`
|
|
PoE *PoEStats `json:"poe,omitempty"`
|
|
}
|
|
|
|
type PoEStats struct {
|
|
Power float64 `json:"power"`
|
|
MaxPower float64 `json:"max_power"`
|
|
}
|
|
|
|
type PoEBudget struct {
|
|
Power float64 `json:"power"`
|
|
MaxPower float64 `json:"max_power"`
|
|
}
|
|
|
|
type Node struct {
|
|
TypeID string `json:"typeid"`
|
|
Names NameSet `json:"names"`
|
|
Interfaces InterfaceMap `json:"interfaces"`
|
|
MACTable map[string]string `json:"-"`
|
|
MACTableSize int `json:"mac_table_size,omitempty"`
|
|
PoEBudget *PoEBudget `json:"poe_budget,omitempty"`
|
|
IsDanteClockMaster bool `json:"is_dante_clock_master,omitempty"`
|
|
DanteTxChannels string `json:"dante_tx_channels,omitempty"`
|
|
Missing bool `json:"missing,omitempty"`
|
|
pollTrigger chan struct{}
|
|
}
|
|
|
|
func (n *Node) WithInterface(ifaceKey string) *Node {
|
|
if ifaceKey == "" {
|
|
return n
|
|
}
|
|
iface, exists := n.Interfaces[ifaceKey]
|
|
if !exists {
|
|
return n
|
|
}
|
|
return &Node{
|
|
TypeID: n.TypeID,
|
|
Names: n.Names,
|
|
Interfaces: InterfaceMap{ifaceKey: iface},
|
|
MACTableSize: n.MACTableSize,
|
|
PoEBudget: n.PoEBudget,
|
|
IsDanteClockMaster: n.IsDanteClockMaster,
|
|
DanteTxChannels: n.DanteTxChannels,
|
|
Missing: n.Missing,
|
|
}
|
|
}
|
|
|
|
func (i *Interface) String() string {
|
|
var parts []string
|
|
parts = append(parts, string(i.MAC))
|
|
if i.Name != "" {
|
|
parts = append(parts, fmt.Sprintf("(%s)", i.Name))
|
|
}
|
|
if len(i.IPs) > 0 {
|
|
parts = append(parts, fmt.Sprintf("%v", i.IPs.Slice()))
|
|
}
|
|
if i.Stats != nil {
|
|
parts = append(parts, i.Stats.String())
|
|
}
|
|
|
|
result := parts[0]
|
|
for _, p := range parts[1:] {
|
|
result += " " + p
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (s *InterfaceStats) String() string {
|
|
var parts []string
|
|
|
|
if s.Speed > 0 {
|
|
if s.Speed >= 1000000000 {
|
|
parts = append(parts, fmt.Sprintf("%dG", s.Speed/1000000000))
|
|
} else if s.Speed >= 1000000 {
|
|
parts = append(parts, fmt.Sprintf("%dM", s.Speed/1000000))
|
|
} else {
|
|
parts = append(parts, fmt.Sprintf("%d", s.Speed))
|
|
}
|
|
}
|
|
|
|
if s.InErrors > 0 || s.OutErrors > 0 {
|
|
parts = append(parts, fmt.Sprintf("err:%d/%d", s.InErrors, s.OutErrors))
|
|
}
|
|
|
|
if s.InBytesRate > 0 || s.OutBytesRate > 0 {
|
|
parts = append(parts, fmt.Sprintf("%.0f/%.0fB/s", s.InBytesRate, s.OutBytesRate))
|
|
}
|
|
|
|
if s.PoE != nil {
|
|
if s.PoE.MaxPower > 0 {
|
|
parts = append(parts, fmt.Sprintf("poe:%.1f/%.1fW", s.PoE.Power, s.PoE.MaxPower))
|
|
} else {
|
|
parts = append(parts, fmt.Sprintf("poe:%.1fW", s.PoE.Power))
|
|
}
|
|
}
|
|
|
|
return "[" + strings.Join(parts, " ") + "]"
|
|
}
|
|
|
|
func (n *Node) String() string {
|
|
name := n.DisplayName()
|
|
if name == "" {
|
|
name = "??"
|
|
}
|
|
|
|
var parts []string
|
|
parts = append(parts, name)
|
|
|
|
if n.PoEBudget != nil {
|
|
parts = append(parts, fmt.Sprintf("[poe:%.0f/%.0fW]", n.PoEBudget.Power, n.PoEBudget.MaxPower))
|
|
}
|
|
|
|
var ifaces []string
|
|
for _, iface := range n.Interfaces {
|
|
ifaces = append(ifaces, iface.String())
|
|
}
|
|
sort.Slice(ifaces, func(i, j int) bool { return sortorder.NaturalLess(ifaces[i], ifaces[j]) })
|
|
|
|
parts = append(parts, fmt.Sprintf("{%v}", ifaces))
|
|
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
func (n *Node) DisplayName() string {
|
|
if len(n.Names) > 0 {
|
|
var names []string
|
|
for name := range n.Names {
|
|
names = append(names, name)
|
|
}
|
|
sortNamesByLength(names)
|
|
return strings.Join(names, "/")
|
|
}
|
|
for _, iface := range n.Interfaces {
|
|
for ip := range iface.IPs {
|
|
return ip
|
|
}
|
|
}
|
|
for _, iface := range n.Interfaces {
|
|
if iface.MAC != "" {
|
|
return string(iface.MAC)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (n *Node) FirstMAC() string {
|
|
for _, iface := range n.Interfaces {
|
|
if iface.MAC != "" {
|
|
return string(iface.MAC)
|
|
}
|
|
}
|
|
return "??"
|
|
}
|