From f0915d35b728de394b38ec170181da46b7985ece Mon Sep 17 00:00:00 2001
From: Simon Ruderich <simon@ruderich.org>
Date: Fri, 1 Nov 2024 08:43:55 +0100
Subject: [PATCH] Add support to generate dot files of the network

The resulting image (after running `dot`) isn't perfect but good enough
to get a quick overview of the network.
---
 dot.go  | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 main.go | 12 ++++++++--
 2 files changed, 78 insertions(+), 2 deletions(-)
 create mode 100644 dot.go

diff --git a/dot.go b/dot.go
new file mode 100644
index 0000000..c49c3bb
--- /dev/null
+++ b/dot.go
@@ -0,0 +1,68 @@
+// Write dot file showing the network architecture.
+
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright (C) 2024  Simon Ruderich
+
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"html"
+	"os"
+	"strings"
+)
+
+func writeDot(cfg *Config, path string) error {
+	f, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	w := bufio.NewWriter(f)
+
+	fmt.Fprintf(w, "graph {\n")
+	fmt.Fprintf(w, "graph [nodesep=3]\n")
+
+	for _, node := range cfg.Nodes {
+		var as []string
+		as = append(as, html.EscapeString(node.Name)+"<br/>")
+		for _, x := range node.Loopbacks {
+			as = append(as, fmt.Sprintf("&nbsp;<font point-size=\"10\">%s</font>&nbsp;<br/>",
+				html.EscapeString(x.String())))
+		}
+		fmt.Fprintf(w, "%s [label=<%s>]\n",
+			node.Name, strings.Join(as, ""))
+	}
+
+	for _, link := range cfg.Links {
+		var aas []string
+		for _, x := range link.A.Addrs {
+			aas = append(aas, fmt.Sprintf("&nbsp;%s&nbsp;<br/>",
+				html.EscapeString(x.Addr().String())))
+		}
+		var abs []string
+		for _, x := range link.B.Addrs {
+			abs = append(abs, fmt.Sprintf("&nbsp;%s&nbsp;<br/>",
+				html.EscapeString(x.Addr().String())))
+		}
+		fmt.Fprintf(w, "%s -- %s [taillabel=<%s>,headlabel=<%s>,labelfontsize=10]\n",
+			link.A.Node.Name, link.B.Node.Name,
+			strings.Join(aas, ""), strings.Join(abs, ""),
+		)
+	}
+
+	fmt.Fprintf(w, "}\n")
+
+	err = w.Flush()
+	if err != nil {
+		return err
+	}
+	err = f.Sync()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// vi: set noet ts=4 sw=4 sts=4:
diff --git a/main.go b/main.go
index a935b5c..5c1e9cc 100644
--- a/main.go
+++ b/main.go
@@ -18,9 +18,9 @@ import (
 )
 
 func main() {
-	if len(os.Args) != 2 {
+	if len(os.Args) != 2 && len(os.Args) != 3 {
 		log.SetFlags(0)
-		log.Fatalf("usage: %s <config>", os.Args[0])
+		log.Fatalf("usage: %s <config> [<dot-output>]", os.Args[0])
 	}
 
 	cfg, err := LoadConfig(os.Args[1])
@@ -28,6 +28,14 @@ func main() {
 		log.Fatalf("config %q: %v", os.Args[1], err)
 	}
 
+	// Write dot file showing the network architecture
+	if len(os.Args) == 3 {
+		err := writeDot(cfg, os.Args[2])
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+
 	for _, node := range cfg.Nodes {
 		log.Printf("Setting up node %q ...", node.Name)
 
-- 
2.49.0