Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
136 lines
3.8 KiB
Go
136 lines
3.8 KiB
Go
package simagent
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/v10networks/nmon/internal/snmp"
|
|
)
|
|
|
|
// AgentFile is the sidecar `<name>.agent.yaml` that says how a fixture's
|
|
// recording is served.
|
|
//
|
|
// recording: renumber.a # file stem in the same directory; default: the sidecar's own stem
|
|
// community: public
|
|
// version: v2c # v1, v2c or v3
|
|
// v3: {user: nmon, auth_proto: SHA256, auth_pass: ..., priv_proto: AES, priv_pass: ...}
|
|
// latency: 3s
|
|
// drop_every: 3
|
|
// toobig_above: 20
|
|
type AgentFile struct {
|
|
Recording string `yaml:"recording,omitempty"`
|
|
Community string `yaml:"community,omitempty"`
|
|
Version string `yaml:"version,omitempty"`
|
|
V3 *V3Config `yaml:"v3,omitempty"`
|
|
Latency string `yaml:"latency,omitempty"`
|
|
DropEvery int `yaml:"drop_every,omitempty"`
|
|
TooBigAbove int `yaml:"toobig_above,omitempty"`
|
|
|
|
// Dir and Stem say where the sidecar was read from.
|
|
Dir string `yaml:"-"`
|
|
Stem string `yaml:"-"`
|
|
}
|
|
|
|
// V3Config is a USM user in a sidecar.
|
|
type V3Config struct {
|
|
User string `yaml:"user,omitempty"`
|
|
AuthProto string `yaml:"auth_proto,omitempty"`
|
|
AuthPass string `yaml:"auth_pass,omitempty"`
|
|
PrivProto string `yaml:"priv_proto,omitempty"`
|
|
PrivPass string `yaml:"priv_pass,omitempty"`
|
|
}
|
|
|
|
// LoadAgentFile reads path. A missing file yields the defaults for the stem
|
|
// (v2c, public, no faults), with ok false.
|
|
func LoadAgentFile(path string) (af *AgentFile, ok bool, err error) {
|
|
af = &AgentFile{Dir: filepath.Dir(path)}
|
|
af.Stem = strings.TrimSuffix(filepath.Base(path), ".agent.yaml")
|
|
b, err := os.ReadFile(path)
|
|
if os.IsNotExist(err) {
|
|
return af.defaults(), false, nil
|
|
}
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
dec := yaml.NewDecoder(strings.NewReader(string(b)))
|
|
dec.KnownFields(true)
|
|
if err := dec.Decode(af); err != nil {
|
|
return nil, false, fmt.Errorf("%s: %w", path, err)
|
|
}
|
|
return af.defaults(), true, nil
|
|
}
|
|
|
|
func (a *AgentFile) defaults() *AgentFile {
|
|
if a.Recording == "" {
|
|
a.Recording = a.Stem
|
|
}
|
|
if a.Community == "" {
|
|
a.Community = "public"
|
|
}
|
|
if a.Version == "" {
|
|
a.Version = "v2c"
|
|
if a.V3 != nil {
|
|
a.Version = "v3"
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// RecordingPath is the .snmprec the sidecar serves.
|
|
func (a *AgentFile) RecordingPath() string { return filepath.Join(a.Dir, a.Recording+".snmprec") }
|
|
|
|
// Config builds the agent configuration; Addr is left for the caller.
|
|
func (a *AgentFile) Config() (Config, error) {
|
|
rec, err := LoadRecording(a.RecordingPath())
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
return a.ConfigFor(rec)
|
|
}
|
|
|
|
// ConfigFor is Config with an explicit recording.
|
|
func (a *AgentFile) ConfigFor(rec *Recording) (Config, error) {
|
|
cfg := Config{Recording: rec, Community: a.Community, Behaviour: Behaviour{DropEvery: a.DropEvery, TooBigAbove: a.TooBigAbove}}
|
|
if a.Latency != "" {
|
|
d, err := time.ParseDuration(a.Latency)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("latency: %w", err)
|
|
}
|
|
cfg.Behaviour.Latency = d
|
|
}
|
|
if a.V3 != nil {
|
|
ap, err := snmp.ParseAuthProto(orStr(a.V3.AuthProto, "SHA256"))
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
pp, err := snmp.ParsePrivProto(orStr(a.V3.PrivProto, "AES"))
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.V3 = []V3User{{Name: a.V3.User, AuthProto: ap, AuthPass: a.V3.AuthPass, PrivProto: pp, PrivPass: a.V3.PrivPass}}
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// Credential is what a client needs to talk to the agent.
|
|
func (a *AgentFile) Credential() snmp.Credential {
|
|
c := snmp.Credential{Version: strings.TrimPrefix(a.Version, "v"), Community: a.Community}
|
|
if a.V3 != nil {
|
|
c.V3User, c.V3AuthProto, c.V3AuthPass = a.V3.User, orStr(a.V3.AuthProto, "SHA256"), a.V3.AuthPass
|
|
c.V3PrivProto, c.V3PrivPass = orStr(a.V3.PrivProto, "AES"), a.V3.PrivPass
|
|
}
|
|
return c
|
|
}
|
|
|
|
func orStr(s, d string) string {
|
|
if s == "" {
|
|
return d
|
|
}
|
|
return s
|
|
}
|