Skip to content

Commit b91d153

Browse files
committed
New commandline programs export-netvis and print-path
These tools output information as JSON to be used in a graph visualization. export-netvis creates a list of all networks and routers of the given topology with information useful for visualizing the topology. print-path analyzes the given topology and creates a list of names of network nodes (routers and networks) between source and destination passed via command line. To create the list, print-path uses the same path-walk algorithm as used in netspoc main program to create ruleset for devices.
1 parent 2f59082 commit b91d153

File tree

9 files changed

+767
-0
lines changed

9 files changed

+767
-0
lines changed

bin/export-netvis

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../go/cmd/export-netvis/export-netvis

bin/print-path

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../go/cmd/print-path/print-path

go/cmd/export-netvis/main.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package main
2+
3+
import (
4+
"github.com/hknutzen/Netspoc/go/pkg/oslink"
5+
"github.com/hknutzen/Netspoc/go/pkg/pass1"
6+
"os"
7+
)
8+
9+
func main() {
10+
os.Exit(pass1.ExportNetvisMain(oslink.Get()))
11+
}

go/cmd/print-path/main.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package main
2+
3+
import (
4+
"github.com/hknutzen/Netspoc/go/pkg/oslink"
5+
"github.com/hknutzen/Netspoc/go/pkg/pass1"
6+
"os"
7+
)
8+
9+
func main() {
10+
os.Exit(pass1.PrintPathMain(oslink.Get()))
11+
}

go/pkg/pass1/export-netvis.go

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package pass1
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"github.com/hknutzen/Netspoc/go/pkg/conf"
8+
"github.com/hknutzen/Netspoc/go/pkg/oslink"
9+
"github.com/spf13/pflag"
10+
"io"
11+
)
12+
13+
func ExportNetvisMain(d oslink.Data) int {
14+
fs := pflag.NewFlagSet(d.Args[0], pflag.ContinueOnError)
15+
16+
// Setup custom usage function.
17+
fs.Usage = func() {
18+
fmt.Fprintf(d.Stderr,
19+
"Usage: %s [options] FILE|DIR\n%s",
20+
d.Args[0], fs.FlagUsages())
21+
}
22+
23+
// Command line flags
24+
quiet := fs.BoolP("quiet", "q", false, "Don't print progress messages")
25+
if err := fs.Parse(d.Args[1:]); err != nil {
26+
if errors.Is(err, pflag.ErrHelp) {
27+
return 1
28+
}
29+
fmt.Fprintf(d.Stderr, "Error: %s\n", err)
30+
fs.Usage()
31+
return 1
32+
}
33+
34+
// Argument processing
35+
args := fs.Args()
36+
if len(args) <= 0 || len(args) > 1 {
37+
fs.Usage()
38+
return 1
39+
}
40+
path := args[0]
41+
42+
dummyArgs := []string{
43+
fmt.Sprintf("--quiet=%v", *quiet),
44+
}
45+
cnf := conf.ConfigFromArgsAndFile(dummyArgs, path)
46+
47+
return toplevelSpoc(d, cnf, func(c *spoc) {
48+
c.exportNetvis(d.Stdout, path)
49+
})
50+
}
51+
52+
type visBase struct {
53+
Id string `json:"id"`
54+
Type string `json:"type"`
55+
InArea string `json:"in_area,omitempty"`
56+
Address string `json:"address,omitempty"`
57+
Neighbors []visNeighbor `json:"neighbors"`
58+
}
59+
60+
type visNeighbor struct {
61+
Id string `json:"id"`
62+
NeighborCount int `json:"neighbor_count"`
63+
IsTunnel bool `json:"is_tunnel,omitempty"`
64+
}
65+
type visNetwork struct {
66+
visBase
67+
Hosts []string `json:"hosts"`
68+
}
69+
70+
type visRouter = visBase
71+
72+
func (c *spoc) exportNetvis(stdout io.Writer, path string) {
73+
c.readNetspoc(path)
74+
c.setZone()
75+
networks := make(map[string]visNetwork)
76+
routers := make(map[string]visRouter)
77+
78+
for _, n := range c.symTable.network {
79+
getVisNetwork(n, networks)
80+
}
81+
for _, r := range c.symTable.router {
82+
getVisRouter(r, routers)
83+
}
84+
data := struct {
85+
Network map[string]visNetwork `json:"network"`
86+
Router map[string]visRouter `json:"router"`
87+
}{
88+
networks, routers,
89+
}
90+
out, _ := json.Marshal(data)
91+
fmt.Fprintln(stdout, string(out))
92+
93+
}
94+
95+
func getVisNetwork(net *network, networks map[string]visNetwork) {
96+
var node visNetwork
97+
node.Id = net.name
98+
node.Type = "network"
99+
if net.ipType != unnumberedIP {
100+
node.Address = net.ipp.String()
101+
}
102+
103+
if a := net.zone.inArea; a != nil {
104+
node.InArea = a.name
105+
}
106+
107+
getVisNeigh := func(intf *routerIntf) visNeighbor {
108+
r := intf.router
109+
if r.origRouter != nil {
110+
r = r.origRouter
111+
}
112+
return visNeighbor{Id: r.name, NeighborCount: len(r.interfaces)}
113+
}
114+
115+
for _, in := range net.interfaces {
116+
node.Neighbors = append(node.Neighbors, getVisNeigh(in))
117+
}
118+
for _, h := range net.hosts {
119+
node.Hosts = append(node.Hosts, h.name)
120+
}
121+
networks[net.name] = node
122+
}
123+
124+
func getVisRouter(r *router, routers map[string]visRouter) {
125+
var node visRouter
126+
node.Id = r.name
127+
128+
intfs := r.origIntfs
129+
if intfs == nil {
130+
intfs = r.interfaces
131+
}
132+
routerArea := intfs[0].network.zone.inArea
133+
134+
var typ = "router"
135+
if r.managed == "" {
136+
if r.routingOnly {
137+
typ += ": routing_only"
138+
}
139+
} else {
140+
typ += ": " + r.managed
141+
}
142+
143+
node.Type = typ
144+
145+
seen := make(map[*area]bool)
146+
for _, intf := range intfs {
147+
if intf.network.ipType == tunnelIP {
148+
for _, intf2 := range intf.network.interfaces {
149+
if intf2.router != r {
150+
node.Neighbors = append(node.Neighbors,
151+
visNeighbor{Id: intf2.router.name, NeighborCount: len(intf2.router.interfaces), IsTunnel: true})
152+
}
153+
}
154+
} else {
155+
node.Neighbors = append(node.Neighbors,
156+
visNeighbor{Id: intf.network.name, NeighborCount: len(intf.network.interfaces)})
157+
a := intf.network.zone.inArea
158+
if a != routerArea {
159+
routerArea = nil
160+
}
161+
if a != nil && !seen[a] && r.managed != "" {
162+
node.Neighbors = append(node.Neighbors,
163+
visNeighbor{Id: a.name, NeighborCount: len(a.border) + len(a.inclusiveBorder)})
164+
seen[a] = true
165+
}
166+
}
167+
}
168+
if routerArea != nil {
169+
node.InArea = routerArea.name
170+
}
171+
routers[r.name] = node
172+
}

go/pkg/pass1/print-path.go

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package pass1
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"github.com/hknutzen/Netspoc/go/pkg/conf"
8+
"github.com/hknutzen/Netspoc/go/pkg/oslink"
9+
"github.com/hknutzen/Netspoc/go/pkg/parser"
10+
"github.com/spf13/pflag"
11+
"io"
12+
"slices"
13+
"strings"
14+
)
15+
16+
func PrintPathMain(d oslink.Data) int {
17+
fs := pflag.NewFlagSet(d.Args[0], pflag.ContinueOnError)
18+
19+
// Setup custom usage function.
20+
fs.Usage = func() {
21+
fmt.Fprintf(d.Stderr,
22+
"Usage: %s [options] FILE|DIR SOURCE DESTINATION\n%s",
23+
d.Args[0], fs.FlagUsages())
24+
}
25+
26+
// Command line flags
27+
quiet := fs.BoolP("quiet", "q", false, "Don't print progress messages")
28+
if err := fs.Parse(d.Args[1:]); err != nil {
29+
if errors.Is(err, pflag.ErrHelp) {
30+
return 1
31+
}
32+
fmt.Fprintf(d.Stderr, "Error: %s\n", err)
33+
fs.Usage()
34+
return 1
35+
}
36+
37+
// Argument processing
38+
args := fs.Args()
39+
if len(args) != 3 {
40+
fs.Usage()
41+
return 1
42+
}
43+
path := args[0]
44+
params := args[1:]
45+
46+
dummyArgs := []string{
47+
fmt.Sprintf("--quiet=%v", *quiet),
48+
}
49+
cnf := conf.ConfigFromArgsAndFile(dummyArgs, path)
50+
51+
return toplevelSpoc(d, cnf, func(c *spoc) {
52+
c.printPath(d.Stdout, path, params)
53+
})
54+
}
55+
56+
func (c *spoc) printPath(stdout io.Writer, path string, params []string) {
57+
c.readNetspoc(path)
58+
c.setZone()
59+
c.setPath()
60+
61+
var l [2]*network
62+
for i, obj := range params {
63+
parsed, err := parser.ParseUnion([]byte(obj))
64+
if err != nil {
65+
c.abort("%v", err)
66+
}
67+
elements := c.expandGroup(parsed, "print-path", false)
68+
c.stopOnErr()
69+
if len(elements) != 1 {
70+
c.abort("Only one element allowed in %s", elements)
71+
}
72+
switch el := elements[0].(type) {
73+
case *network:
74+
l[i] = el
75+
case *host:
76+
l[i] = el.network
77+
case *routerIntf:
78+
l[i] = el.network
79+
default:
80+
c.abort("Unsupported element: %v", elements[0])
81+
}
82+
}
83+
84+
znl := make(map[*zone]netList)
85+
isUsed := make(map[string]bool)
86+
isUsed[l[0].name] = true
87+
isUsed[l[1].name] = true
88+
znl[l[0].zone] = append(znl[l[0].zone], l[0])
89+
znl[l[1].zone] = append(znl[l[1].zone], l[1])
90+
c.singlePathWalk(l[0], l[1], func(r *groupedRule, i, o *routerIntf) {
91+
isUsed[i.router.name] = true
92+
isUsed[i.network.name] = true
93+
isUsed[o.network.name] = true
94+
znl[i.zone] = append(znl[i.zone], i.network)
95+
znl[o.zone] = append(znl[o.zone], o.network)
96+
}, "Router")
97+
c.stopOnErr()
98+
99+
var markPathInZone func(netList)
100+
markPathInZone = func(list netList) {
101+
if len(list) <= 1 {
102+
return
103+
}
104+
markUnconnectedPair(list[0], list[1], isUsed)
105+
markPathInZone(list[1:])
106+
}
107+
for _, list := range znl {
108+
markPathInZone(list)
109+
}
110+
111+
var used []string
112+
for e := range isUsed {
113+
if !strings.HasPrefix(e, "interface") {
114+
used = append(used, e)
115+
}
116+
}
117+
slices.Sort(used)
118+
used = slices.Compact(used)
119+
out, _ := json.Marshal(used)
120+
fmt.Fprintln(stdout, string(out))
121+
}

go/test/netspoc_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ var tests = []test{
6161
{"api", stdoutT, modifyRun, stdoutCheck},
6262
{"cut-netspoc", stdoutT, pass1.CutNetspocMain, stdoutCheck},
6363
{"export-netspoc-syntax", stdoutT, exportsyntax.Main, jsonCheck},
64+
{"export-netvis", stdoutT, pass1.ExportNetvisMain, jsonCheck},
65+
{"print-path", stdoutT, pass1.PrintPathMain, jsonCheck},
6466
{"print-group", stdoutT, pass1.PrintGroupMain, stdoutCheck},
6567
{"print-service", stdoutT, pass1.PrintServiceMain, stdoutCheck},
6668
{"check-acl", outDirStdoutT, checkACLRun, stdoutCheck},

0 commit comments

Comments
 (0)