Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
131 lines
4.5 KiB
Go
131 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gosnmp/gosnmp"
|
|
|
|
"github.com/v10networks/nmon/internal/fixturegen"
|
|
"github.com/v10networks/nmon/internal/profile"
|
|
"github.com/v10networks/nmon/internal/snmprec"
|
|
)
|
|
|
|
// demoDevice is one simulated device of `devtools seed-demo`.
|
|
type demoDevice struct {
|
|
name, site, role string
|
|
kind string // counters, big, router, ups, v1, down: what the gate and screenshots look for
|
|
rec *snmprec.Recording
|
|
community string
|
|
version string // v2c unless set
|
|
hc bool // 64-bit counters, so history can be back-filled
|
|
}
|
|
|
|
// counterBase is added to every simulated 64-bit octet counter so that the
|
|
// back-filled history (about 3e13 bytes for 31 days at 100 Mbit/s) fits below
|
|
// the live counter and the join shows no reset.
|
|
const counterBase = 100_000_000_000_000
|
|
|
|
const (
|
|
oidHCIn = "1.3.6.1.2.1.31.1.1.1.6."
|
|
oidHCOut = "1.3.6.1.2.1.31.1.1.1.10."
|
|
oidUp = "1.3.6.1.2.1.1.3.0"
|
|
)
|
|
|
|
// animate returns a copy of rec whose 64-bit octet counters advance at a steady
|
|
// per-port rate from counterBase and whose uptime ticks, so successive polls see
|
|
// traffic. Counters that already advance keep their rate and only gain the base.
|
|
func animate(rec *snmprec.Recording) *snmprec.Recording {
|
|
recs := append([]snmprec.Record(nil), rec.Records()...)
|
|
for i := range recs {
|
|
r := &recs[i]
|
|
switch {
|
|
case r.Type == gosnmp.Counter64 && (strings.HasPrefix(r.OID, oidHCIn) || strings.HasPrefix(r.OID, oidHCOut)):
|
|
recs[i] = animateCounter(*r)
|
|
case r.OID == oidUp && r.Dyn == nil:
|
|
if v, ok := r.Value.(uint32); ok {
|
|
r.Dyn, r.Value = &snmprec.Dynamic{Rate: 100, Initial: uint64(v)}, nil
|
|
}
|
|
}
|
|
}
|
|
return snmprec.NewRecording(recs)
|
|
}
|
|
|
|
func animateCounter(r snmprec.Record) snmprec.Record {
|
|
if r.Dyn != nil {
|
|
d := *r.Dyn
|
|
d.Initial += counterBase
|
|
r.Dyn = &d
|
|
return r
|
|
}
|
|
k, _ := strconv.Atoi(r.OID[strings.LastIndexByte(r.OID, '.')+1:])
|
|
dir := 1
|
|
if strings.HasPrefix(r.OID, oidHCOut) {
|
|
dir = 2
|
|
}
|
|
mbps := 1 + (k*(31+7*dir))%90
|
|
r.Dyn = &snmprec.Dynamic{Rate: float64(mbps) * 125_000, Initial: counterBase + uint64(k)*1_000_000_000}
|
|
r.Value = nil
|
|
return r
|
|
}
|
|
|
|
// profileDevice builds a device that a shipped profile matches, with traffic.
|
|
func profileDevice(set *profile.Set, prof, name, site, role, kind string, ports int) (demoDevice, error) {
|
|
spec, ok := fixturegen.SpecFor(prof)
|
|
if !ok {
|
|
return demoDevice{}, fmt.Errorf("no fixture spec for profile %q", prof)
|
|
}
|
|
res, ok := set.Lookup(prof)
|
|
if !ok {
|
|
return demoDevice{}, fmt.Errorf("no profile %q", prof)
|
|
}
|
|
spec.SysName = name
|
|
if ports > 0 {
|
|
spec.Ports = ports
|
|
}
|
|
rec, err := fixturegen.FromProfile(res, spec)
|
|
if err != nil {
|
|
return demoDevice{}, fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
return demoDevice{name: name, site: site, role: role, kind: kind, rec: animate(rec), community: "public", hc: true}, nil
|
|
}
|
|
|
|
// buildFleet is the small network used for live screenshots: a 48-port switch,
|
|
// routers, a firewall, a UPS, one 32-bit device, the counters fixture and one
|
|
// device whose agent is stopped after seeding, so it reads as down.
|
|
func buildFleet(set *profile.Set, countersRec string) ([]demoDevice, error) {
|
|
counters, err := snmprec.Load(countersRec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fleet := []demoDevice{{
|
|
name: "yyz1-edge1", site: "yyz1", role: "edge", kind: "counters", rec: animate(counters), community: "public", hc: true,
|
|
}}
|
|
for _, p := range []struct {
|
|
prof, name, site, role, kind string
|
|
ports int
|
|
}{
|
|
{"cisco-ios", "yyz1-acc01", "yyz1", "access", "big", 48},
|
|
{"cisco-ios", "yyz1-acc02", "yyz1", "access", "router", 24},
|
|
{"cisco-ios", "ott1-acc02", "ott1", "access", "down", 24}, // its agent is stopped once seeding is done
|
|
{"juniper-junos", "yyz1-core1", "yyz1", "core", "router", 0},
|
|
{"arista-eos", "yul1-core1", "yul1", "core", "router", 0},
|
|
{"mikrotik-routeros", "ott1-gw1", "ott1", "gateway", "router", 0},
|
|
{"fortigate", "yyz1-fw1", "yyz1", "firewall", "router", 0},
|
|
{"apc-ups", "yyz1-ups1", "yyz1", "power", "ups", 0},
|
|
} {
|
|
d, err := profileDevice(set, p.prof, p.name, p.site, p.role, p.kind, p.ports)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fleet = append(fleet, d)
|
|
}
|
|
// 32-bit counters on 1 Gbit/s ports: the API flags them unreliable and the UI never graphs them.
|
|
v1 := fixturegen.Generic(fixturegen.Options{Ports: 12, SysName: "yul1-acc03", Counters: true, NamePattern: "Fa0/%d"})
|
|
fleet = append(fleet, demoDevice{
|
|
name: "yul1-acc03", site: "yul1", role: "access", kind: "v1", rec: v1, community: "nmon-v1", version: "v1",
|
|
})
|
|
return fleet, nil
|
|
}
|