diff options
Diffstat (limited to 'utils')
-rw-r--r-- | utils/.gitignore | 1 | ||||
-rw-r--r-- | utils/Makefile | 18 | ||||
-rw-r--r-- | utils/go.mod | 5 | ||||
-rw-r--r-- | utils/go.sum | 4 | ||||
-rw-r--r-- | utils/ipam/ipam.go | 183 | ||||
-rw-r--r-- | utils/ipam/yaml_model/yaml_model.go | 36 | ||||
-rw-r--r-- | utils/main.go | 50 | ||||
-rw-r--r-- | utils/networks/networks.go | 55 | ||||
-rw-r--r-- | utils/routers/routers.go | 51 |
9 files changed, 403 insertions, 0 deletions
diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000..9487075 --- /dev/null +++ b/utils/.gitignore @@ -0,0 +1 @@ +utils diff --git a/utils/Makefile b/utils/Makefile new file mode 100644 index 0000000..409ef2d --- /dev/null +++ b/utils/Makefile @@ -0,0 +1,18 @@ +IPAM=../ansible/group_vars/all/ipam.yml + +all: utils + +networks: ../ansible/group_vars/all/networks.tmp.png +routers: ../ansible/group_vars/all/routers.tmp.png + +%.png:%.dot + dot -Tpng $< > $@ + +../ansible/group_vars/all/networks.tmp.dot: $(IPAM) + go run main.go networks $< > $@ + +../ansible/group_vars/all/routers.tmp.dot: $(IPAM) + go run main.go routers $< > $@ + +utils: + go build diff --git a/utils/go.mod b/utils/go.mod new file mode 100644 index 0000000..0090185 --- /dev/null +++ b/utils/go.mod @@ -0,0 +1,5 @@ +module utils + +go 1.23 + +require gopkg.in/yaml.v3 v3.0.1 diff --git a/utils/go.sum b/utils/go.sum new file mode 100644 index 0000000..a62c313 --- /dev/null +++ b/utils/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/utils/ipam/ipam.go b/utils/ipam/ipam.go new file mode 100644 index 0000000..d61fa3e --- /dev/null +++ b/utils/ipam/ipam.go @@ -0,0 +1,183 @@ +package ipam + +import ( + "cmp" + "fmt" + "log" + "net/netip" + "slices" + "utils/ipam/yaml_model" +) + +func LoadIpam(bs []byte) (*Ipam, error) { + parsed, err := yaml_model.Parse(bs) + if err != nil { + return nil, err + } + + ipam := Ipam{} + err = processNetworks(parsed.Ipam6.Networks, &ipam) + if err != nil { + return nil, err + } + + err = processRouters(parsed.Routers, &ipam) + if err != nil { + return nil, err + } + + ipam.ResolveParents() + + return &ipam, nil +} + +type Ipam struct { + Networks []*Network + Routers []*Router +} + +func contains(a, b netip.Prefix) bool { + return b.Bits() > a.Bits() && b.Overlaps(a) +} + +func (ipam *Ipam) ResolveParents() { + for _, n := range ipam.Networks { + log.Printf("network %s/%s", n.Name, n.Prefix) + for _, p := range ipam.Networks { + if n == p { + continue + } + log.Printf(" candidate %s/%s", p.Name, p.Prefix) + if contains(p.Prefix, n.Prefix) { + if n.Parent == nil { + log.Printf(" found parent %s/%s", p.Name, p.Prefix) + n.Parent = p + } else { + if n.Parent.Prefix.Bits() < p.Prefix.Bits() { + log.Printf(" found better parent %s/%s", p.Name, p.Prefix) + n.Parent = p + } + } + } + } + } + + return +} + +func (ipam *Ipam) FindRoots() []*Network { + var ns []*Network + + for _, n := range ipam.Networks { + if n.Parent == nil { + ns = append(ns, n) + } + } + + return ns +} + +type Network struct { + Name string + Parent *Network + Prefix netip.Prefix + hosts []networkHost +} + +func CompareNetwork(a, b *Network) int { + return cmp.Compare(a.Name, b.Name) +} + +func (n *Network) Compare(other *Network) int { + return cmp.Compare(n.Name, other.Name) +} + +type networkHost struct { + name string + address netip.Addr +} + +func processNetworks(networks map[string]yaml_model.Network6Yaml, ipam *Ipam) error { + var ns []*Network + + for name, net := range networks { + log.Printf("Processing net %v\n", name) + prefix, err := netip.ParsePrefix(net.Range) + if err != nil { + return fmt.Errorf("error parsing net range: %v", err) + } + log.Printf("prefix: %s", prefix.String()) + + n := Network{Name: name, Prefix: prefix} + for hostname, a := range net.Hosts { + addr, err := netip.ParseAddr(a) + if err != nil { + return fmt.Errorf("network: %s, unable to parse host address %s", n.Name, a) + } + + n.hosts = append(n.hosts, networkHost{ + name: hostname, + address: addr, + }) + } + + ns = append(ns, &n) + } + + ipam.Networks = ns + return nil +} + +type Router struct { + Name string + As string + Peers []Peer +} + +func (r *Router) Compare(other *Router) int { + return cmp.Compare(r.Name, other.Name) +} + +type Peer struct { + Name string + /// Router is set if this is a known peer + Router *Router + + As string +} + +func processRouters(routers map[string]yaml_model.Router, ipam *Ipam) error { + var rs []*Router + rsIndex := make(map[string]*Router) + for name, router := range routers { + r := &Router{ + Name: name, + As: router.As, + } + + rs = append(rs, r) + rsIndex[name] = r + } + + for _, r := range rs { + router := routers[r.Name] + + for name, peer := range router.Peers { + remote := rsIndex[name] + + r.Peers = append(r.Peers, Peer{ + Name: name, + Router: remote, + As: peer.As, + }) + } + } + + compare := func(a *Router, b *Router) int { + return a.Compare(b) + } + + ipam.Routers = slices.SortedStableFunc(slices.Values(rs), compare) + + return nil +} diff --git a/utils/ipam/yaml_model/yaml_model.go b/utils/ipam/yaml_model/yaml_model.go new file mode 100644 index 0000000..4109e12 --- /dev/null +++ b/utils/ipam/yaml_model/yaml_model.go @@ -0,0 +1,36 @@ +package yaml_model + +import "gopkg.in/yaml.v3" + +type Ipam struct { + Ipam6 Ipam6 `yaml:"ipam6,omitempty"` + Routers map[string]Router `yaml:"routers,omitempty"` +} + +type Ipam6 struct { + Networks map[string]Network6Yaml `yaml:"networks"` +} + +type Network6Yaml struct { + Range string `yaml:"range"` + Hosts map[string]string `yaml:"hosts"` +} + +type Router struct { + As string `yaml:"as,omitempty"` + Peers map[string]Peer `yaml:"peers,omitempty"` +} + +type Peer struct { + As string `yaml:"as"` +} + +func Parse(bs []byte) (Ipam, error) { + var ipam Ipam + err := yaml.Unmarshal(bs, &ipam) + if err != nil { + return Ipam{}, err + } + + return ipam, nil +} diff --git a/utils/main.go b/utils/main.go new file mode 100644 index 0000000..32f9682 --- /dev/null +++ b/utils/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "os" + Ipam "utils/ipam" + "utils/networks" + "utils/routers" +) + +func main() { + if len(os.Args) != 3 { + _, _ = fmt.Fprintf(os.Stderr, "Usage: %s IPAM-FILE PICTURE\n", os.Args[0]) + os.Exit(1) + } + + ipam, err := loadIpam() + if err != nil { + return + } + + if os.Args[1] == "networks" { + err = networks.RunNetworks(ipam) + } else if os.Args[1] == "routers" { + err = routers.RunRouters(ipam) + } else { + _, _ = fmt.Fprintf(os.Stderr, "Unknown command: %s\n", os.Args[2]) + os.Exit(1) + } + + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +func loadIpam() (*Ipam.Ipam, error) { + ipamYaml := os.Args[2] + bs, err := os.ReadFile(ipamYaml) + if err != nil { + return nil, err + } + + ipam, err := Ipam.LoadIpam(bs) + if err != nil { + return nil, err + } + + return ipam, nil +} diff --git a/utils/networks/networks.go b/utils/networks/networks.go new file mode 100644 index 0000000..9ecf256 --- /dev/null +++ b/utils/networks/networks.go @@ -0,0 +1,55 @@ +package networks + +import ( + "fmt" + "slices" + Ipam "utils/ipam" +) + +func RunNetworks(ipam *Ipam.Ipam) error { + networks := slices.SortedFunc(slices.Values(ipam.Networks), Ipam.CompareNetwork) + roots := slices.SortedFunc(slices.Values(ipam.FindRoots()), Ipam.CompareNetwork) + + fmt.Printf("digraph {\n") + //fmt.Printf(" layout=dot\n") + //fmt.Printf(" layout=twopi\n") + fmt.Printf(" layout=fdp\n") + //fmt.Printf(" layout=circo\n") + //fmt.Printf(" layout=neato\n") + //fmt.Printf(" ranksep=3\n") + //fmt.Printf(" ratio=auto\n") + //fmt.Printf(" rankdir=LR\n") + //fmt.Printf(" rankdir=RL\n") + fmt.Printf(" rankdir=TB\n") + //fmt.Printf(" sep=3\n") + //fmt.Printf(" overlap_scaling=-10\n") + fmt.Printf("node [len=10];") + fmt.Printf("node [shape=box];") + fmt.Printf("overlap=false") + + fmt.Printf("\n") + fmt.Printf(" # Nodes\n") + for _, n := range networks { + fmt.Printf(" %s [ label = \"%s\\n%s\"];\n", n.Name, n.Name, n.Prefix) + } + + fmt.Printf("\n") + fmt.Printf(" # Roots\n") + for _, n := range roots { + fmt.Printf(" %s [ root = true ];\n", n.Name) + } + + fmt.Printf("\n") + fmt.Printf(" # Relationships\n") + for _, n := range networks { + for _, c := range ipam.Networks { + if c.Parent == n { + fmt.Printf(" %s -> %s", n.Name, c.Name) + fmt.Printf(";\n") + } + } + } + fmt.Printf("}\n") + + return nil +} diff --git a/utils/routers/routers.go b/utils/routers/routers.go new file mode 100644 index 0000000..24d25e3 --- /dev/null +++ b/utils/routers/routers.go @@ -0,0 +1,51 @@ +package routers + +import ( + "fmt" + Ipam "utils/ipam" +) + +func RunRouters(ipam *Ipam.Ipam) error { + routers := ipam.Routers + + fmt.Printf("digraph {\n") + //fmt.Printf(" layout=dot\n") + //fmt.Printf(" layout=twopi\n") + fmt.Printf(" layout=fdp\n") + //fmt.Printf(" layout=circo\n") + //fmt.Printf(" layout=neato\n") + //fmt.Printf(" ranksep=3\n") + //fmt.Printf(" ratio=auto\n") + //fmt.Printf(" rankdir=LR\n") + //fmt.Printf(" rankdir=RL\n") + fmt.Printf(" rankdir=TB\n") + //fmt.Printf(" sep=3\n") + //fmt.Printf(" overlap_scaling=-10\n") + fmt.Printf("node [len=10];") + fmt.Printf("node [shape=box];") + fmt.Printf("overlap=false") + + fmt.Printf("\n") + fmt.Printf(" # Nodes\n") + for _, r := range routers { + fmt.Printf(" %s [ label = \"%s\"];\n", r.Name, r.Name) + } + + fmt.Printf("\n") + fmt.Printf(" # Connections\n") + for _, n := range routers { + fmt.Printf(" %s [ root = true ];\n", n.Name) + } + + fmt.Printf("\n") + fmt.Printf(" # Peers\n") + for _, r := range routers { + for _, p := range r.Peers { + fmt.Printf(" %s -> %s", r.Name, p.Name) + fmt.Printf(";\n") + } + } + fmt.Printf("}\n") + + return nil +} |