diff --git a/cmd/snmpprobe/main.go b/cmd/snmpprobe/main.go index 3107f52..80fedd2 100644 --- a/cmd/snmpprobe/main.go +++ b/cmd/snmpprobe/main.go @@ -18,16 +18,27 @@ func init() { func snmpprobe(client mibs.Client, ids ...mibs.ID) error { if len(ids) == 0 { + var mibList []*mibs.MIB + mibs.WalkMIBs(func(mib *mibs.MIB) { - ids = append(ids, mib.ID) + mibList = append(mibList, mib) }) - } - if probed, err := client.Probe(ids); err != nil { - return err + if probed, err := client.ProbeMIBs(mibList); err != nil { + return err + } else { + for i, ok := range probed { + fmt.Printf("%v = %v\n", mibList[i], ok) + } + } + } else { - for i, ok := range probed { - fmt.Printf("%v = %v\n", ids[i], ok) + if probed, err := client.Probe(ids); err != nil { + return err + } else { + for i, ok := range probed { + fmt.Printf("%v = %v\n", ids[i], ok) + } } } diff --git a/mibs/client.go b/mibs/client.go index 22532ba..94fe18f 100644 --- a/mibs/client.go +++ b/mibs/client.go @@ -13,7 +13,35 @@ type Client struct { *client.Client } -// Probe the MIB at id +// Probe for the existence of given MIBs +func (client Client) ProbeMIBs(mibs []*MIB) ([]bool, error) { + var probed = make([]bool, len(mibs)) + var oids []snmp.OID + var mibIndex []int + + for i, mib := range mibs { + for _, oid := range mib.OIDs { + oids = append(oids, oid) + mibIndex = append(mibIndex, i) + } + } + + if varBinds, err := client.WalkScalars(oids); err != nil { + return probed, err + } else { + for i, varBind := range varBinds { + if err := varBind.ErrorValue(); err != nil { + // not supported + } else { + probed[mibIndex[i]] = true + } + } + } + + return probed, nil +} + +// Probe for the existence of arbitrary OIDs func (client Client) Probe(ids []ID) ([]bool, error) { var oids = make([]snmp.OID, len(ids)) var probed = make([]bool, len(ids)) diff --git a/mibs/config.go b/mibs/config.go index fca3335..5732486 100644 --- a/mibs/config.go +++ b/mibs/config.go @@ -24,27 +24,39 @@ func (config ConfigID) resolve(mib *MIB) (ID, error) { type MIBConfig struct { OID string + OIDs []string Name string Objects []ObjectConfig Tables []TableConfig - - oid snmp.OID } -func (config MIBConfig) build() (MIB, error) { +func (config MIBConfig) makeMIB() (MIB, error) { if oid, err := snmp.ParseOID(config.OID); err != nil { return MIB{}, fmt.Errorf("Invalid OID for MIB %v: %v", config.Name, err) } else { return makeMIB(config.Name, oid), nil } +} +func (config MIBConfig) loadOIDs(mib *MIB) error { + for _, o := range config.OIDs { + if oid, err := snmp.ParseOID(o); err != nil { + return fmt.Errorf("Invalid OID %v for MIB %v: %v", o, config.Name, err) + } else { + mib.OIDs.Add(oid) + } + } + + return nil } func (config MIBConfig) loadMIB() (*MIB, error) { - if buildMIB, err := config.build(); err != nil { + if mib, err := config.makeMIB(); err != nil { + return nil, err + } else if err := config.loadOIDs(&mib); err != nil { return nil, err } else { - return registerMIB(buildMIB), nil + return registerMIB(mib), nil } } @@ -52,6 +64,8 @@ func (config MIBConfig) loadObjects(mib *MIB) error { for _, objectConfig := range config.Objects { if object, err := objectConfig.build(mib); err != nil { return fmt.Errorf("Invalid Object %v: %v", objectConfig.Name, err) + } else if mib.OIDs.Get(object.OID) == nil { + return fmt.Errorf("Unknown OID %v for Object %v: outside of MIB OIDs %v", object.OID, objectConfig.Name, mib.OIDs) } else { mib.registerObject(object) } @@ -69,8 +83,12 @@ func (config MIBConfig) loadTables(mib *MIB, loadContext loadContext) error { for _, tableConfig := range config.Tables { if table, err := tableConfig.build(mib); err != nil { return fmt.Errorf("Invalid Table %v: %v", tableConfig.Name, err) + } else if mib.OIDs.Get(table.OID) == nil { + return fmt.Errorf("Unknown OID %v for Table %v: outside of MIB OIDs %v", table.OID, tableConfig.Name, mib.OIDs) } else { - loadContext.entryMap[mib.Name+"::"+tableConfig.EntryName] = mib.registerTable(table) + var table = mib.registerTable(table) + + loadContext.entryMap[mib.Name+"::"+tableConfig.EntryName] = table loadContext.augmentsMap[mib.Name+"::"+tableConfig.EntryName] = tableConfig.AugmentsEntry } } diff --git a/mibs/config_test.go b/mibs/config_test.go index 626237d..c3cc2c7 100644 --- a/mibs/config_test.go +++ b/mibs/config_test.go @@ -21,6 +21,14 @@ func TestConfigResolveMIB(t *testing.T) { } } +func TestConfigMIBOIDs(t *testing.T) { + if resolveMIB, err := ResolveMIB("TEST2-MIB"); err != nil { + t.Errorf("ResolveMIB TEST2-MIB: %v", err) + } else { + assert.Equal(t, "{.1.0.2 .1.1.5}", resolveMIB.OIDs.String()) + } +} + func TestConfigResolve(t *testing.T) { if id, err := Resolve("TEST2-MIB"); err != nil { t.Errorf("Resolve TEST2-MIB: %v", err) @@ -133,6 +141,11 @@ func TestConfigLookupObject(t *testing.T) { } } +func TestConfigFormatObject(t *testing.T) { + assert.Equal(t, "TEST2-MIB::test", FormatOID(snmp.OID{1, 0, 2, 1, 1})) + assert.Equal(t, "TEST2-MIB::test.0", FormatOID(snmp.OID{1, 0, 2, 1, 1, 0})) +} + func TestConfigLookupObjectExt(t *testing.T) { if object := LookupObject(snmp.OID{1, 1, 5, 1}); object == nil { t.Errorf("LookupObject .1.1.5.1: %v", nil) @@ -141,6 +154,11 @@ func TestConfigLookupObjectExt(t *testing.T) { } } +func TestConfigFormatObjectExt(t *testing.T) { + assert.Equal(t, "TEST2-MIB::extObject", FormatOID(snmp.OID{1, 1, 5, 1})) + assert.Equal(t, "TEST2-MIB::extObject.0", FormatOID(snmp.OID{1, 1, 5, 1, 0})) +} + func TestConfigObjectUnknownSyntax(t *testing.T) { if object, err := ResolveObject("TEST2-MIB::testUnknownSyntax"); err != nil { t.Errorf("ResolveObject TEST2-MIB::testUnknownSyntax: %v", err) diff --git a/mibs/id.go b/mibs/id.go index 00a8be1..fb8ef83 100644 --- a/mibs/id.go +++ b/mibs/id.go @@ -27,10 +27,6 @@ func (id ID) String() string { } } -func (id ID) MakeID(name string, ids ...int) ID { - return ID{id.MIB, name, id.OID.Extend(ids...)} -} - func (id ID) FormatOID(oid snmp.OID) string { if index := id.OID.Index(oid); index == nil { return oid.String() diff --git a/mibs/mib.go b/mibs/mib.go index 7554f9e..4341f9c 100644 --- a/mibs/mib.go +++ b/mibs/mib.go @@ -7,9 +7,20 @@ import ( ) func makeMIB(name string, oid snmp.OID) MIB { + var oids snmp.OIDSet + + if oid != nil { + oids = snmp.MakeOIDSet(oid) + } else { + // MIB without any OID has an empty OID set + oids = snmp.MakeOIDSet() + } + return MIB{ - ID: ID{OID: oid}, - Name: name, + Name: name, + OID: oid, + OIDs: oids, + registry: makeRegistry(), objects: make(map[IDKey]*Object), tables: make(map[IDKey]*Table), @@ -17,8 +28,9 @@ func makeMIB(name string, oid snmp.OID) MIB { } type MIB struct { - ID - Name string // shadows ID.Name, which is empty + Name string + OID snmp.OID + OIDs snmp.OIDSet registry objects map[IDKey]*Object @@ -29,10 +41,6 @@ func (mib *MIB) String() string { return mib.Name } -func (mib *MIB) MakeID(name string, ids ...int) ID { - return ID{mib, name, mib.OID.Extend(ids...)} -} - func (mib *MIB) registerObject(object Object) *Object { mibRegistry.registerOID(object.ID) mib.registry.register(object.ID) @@ -41,12 +49,6 @@ func (mib *MIB) registerObject(object Object) *Object { return &object } -func (mib *MIB) RegisterObject(id ID, object Object) *Object { - object.ID = id - - return mib.registerObject(object) -} - func (mib *MIB) registerTable(table Table) *Table { mibRegistry.registerOID(table.ID) mib.registry.register(table.ID) @@ -55,12 +57,6 @@ func (mib *MIB) registerTable(table Table) *Table { return &table } -func (mib *MIB) RegisterTable(id ID, table Table) *Table { - table.ID = id - - return mib.registerTable(table) -} - /* Resolve MIB-relative ID by human-readable name: ".1.0" "sysDescr" @@ -69,7 +65,7 @@ func (mib *MIB) RegisterTable(id ID, table Table) *Table { var mibResolveRegexp = regexp.MustCompile("^([^.]+?)?([.][0-9.]+)?$") func (mib *MIB) Resolve(name string) (ID, error) { - var id = ID{OID: mib.OID} + var id = ID{MIB: mib, OID: mib.OID} var nameID, nameOID string if matches := mibResolveRegexp.FindStringSubmatch(name); matches == nil { diff --git a/mibs/mib_test.go b/mibs/mib_test.go index 23c7d8c..ac6e073 100644 --- a/mibs/mib_test.go +++ b/mibs/mib_test.go @@ -1,14 +1,17 @@ package mibs import ( + "github.com/qmsk/snmpbot/snmp" "github.com/stretchr/testify/assert" + "testing" ) var ( - TestMIB = RegisterMIB("TEST-MIB", 1, 0, 1) + TestMIB = registerMIB(makeMIB("TEST-MIB", snmp.OID{1, 0, 1})) - TestObject = TestMIB.RegisterObject(TestMIB.MakeID("test", 1, 1), Object{ + TestObject = TestMIB.registerObject(Object{ + ID: ID{MIB: TestMIB, Name: "test", OID: snmp.OID{1, 0, 1, 1, 1}}, Syntax: DisplayStringSyntax{}, }) ) diff --git a/mibs/mibs.go b/mibs/mibs.go index 097e606..971af86 100644 --- a/mibs/mibs.go +++ b/mibs/mibs.go @@ -9,21 +9,15 @@ import ( var mibRegistry = makeRegistry() func registerMIB(mib MIB) *MIB { - mib.ID.MIB = &mib - - mibRegistry.registerName(mib.ID, mib.Name) + mibRegistry.registerName(ID{MIB: &mib, OID: mib.OID}, mib.Name) if mib.OID != nil { - mibRegistry.registerOID(mib.ID) + mibRegistry.registerOID(ID{MIB: &mib, OID: mib.OID}) } return &mib } -func RegisterMIB(name string, oid ...int) *MIB { - return registerMIB(makeMIB(name, snmp.OID(oid))) -} - func ResolveMIB(name string) (*MIB, error) { if id, ok := mibRegistry.getName(name); !ok { return nil, fmt.Errorf("MIB not found: %v", name) @@ -34,11 +28,6 @@ func ResolveMIB(name string) (*MIB, error) { func WalkMIBs(f func(mib *MIB)) { mibRegistry.walk(func(id ID) { - if id.OID == nil { - // skip MIBs without a top-level OID - return - } - f(id.MIB) }) } @@ -69,8 +58,7 @@ func Resolve(name string) (ID, error) { } else if mib, err := ResolveMIB(nameMIB); err != nil { return id, err } else { - id = mib.ID - id.Name = "" // fixup MIB.ID re-use of Name + id = ID{MIB: mib, OID: mib.OID} } if nameID == "" { diff --git a/mibs/test/TEST2-MIB.json b/mibs/test/TEST2-MIB.json index e06c735..146afd2 100644 --- a/mibs/test/TEST2-MIB.json +++ b/mibs/test/TEST2-MIB.json @@ -1,6 +1,9 @@ { "Name": "TEST2-MIB", "OID": ".1.0.2", + "OIDS": [ + ".1.1.5" + ], "Objects": [ { "Name": "test", diff --git a/server/host.go b/server/host.go index ddf7628..425f37a 100644 --- a/server/host.go +++ b/server/host.go @@ -83,17 +83,23 @@ func (host *Host) init(engine *Engine, config HostConfig) error { func (host *Host) probe(probeMIBs MIBs) error { var client = mibs.MakeClient(host.client) - var ids = probeMIBs.ListIDs() + var mibList []*mibs.MIB var mibs = make(MIBs) host.log.Infof("Probing MIBs: %v", probeMIBs) - if probed, err := client.Probe(ids); err != nil { + for _, mib := range probeMIBs { + mibList = append(mibList, mib) + } + + if probed, err := client.ProbeMIBs(mibList); err != nil { return err } else { for i, ok := range probed { + host.log.Debugf("probe MIB %v => %v", mibList[i], ok) + if ok { - mibs.Add(ids[i].MIB) + mibs.Add(mibList[i]) } } } diff --git a/server/mibs.go b/server/mibs.go index daa54df..a6fe033 100644 --- a/server/mibs.go +++ b/server/mibs.go @@ -18,16 +18,6 @@ func AllMIBs() MIBs { return mibMap } -func (mibMap MIBs) ListIDs() []mibs.ID { - var list = make([]mibs.ID, 0, len(mibMap)) - - for _, mib := range mibMap { - list = append(list, mib.ID) - } - - return list -} - func (mibMap MIBs) Add(mib *mibs.MIB) { mibMap[mib.Name] = mib } diff --git a/snmp/oid_set.go b/snmp/oid_set.go new file mode 100644 index 0000000..01c8b55 --- /dev/null +++ b/snmp/oid_set.go @@ -0,0 +1,68 @@ +package snmp + +import ( + "fmt" + "sort" + "strings" +) + +func MakeOIDSet(oids ...OID) OIDSet { + // minimize + var oidSet = make(OIDSet) + + for _, oid := range oids { + oidSet.Add(oid) + } + + return oidSet +} + +type OIDSet map[string]OID + +func (oidSet OIDSet) String() string { + var strs []string + + for _, oid := range oidSet { + strs = append(strs, oid.String()) + } + + sort.Strings(strs) + + return "{" + strings.Join(strs, " ") + "}" +} + +func (oidSet OIDSet) Get(oid OID) OID { + var key = "" + + if matchOID, ok := oidSet["."]; ok { + return matchOID + } + + for _, x := range oid { + key += fmt.Sprintf(".%d", x) + + if matchOID, ok := oidSet[key]; ok { + return matchOID + } + } + + return nil +} + +func (oidSet OIDSet) Add(oid OID) { + if oid == nil { + panic("add nil oid to set") + } + + for oidKey, o := range oidSet { + if idx := o.Index(oid); idx != nil { + // set already contains OID covering this OID + return + } else if idx := oid.Index(o); idx != nil { + // delete OID from set covered by this OID + delete(oidSet, oidKey) + } + } + + oidSet[oid.String()] = oid +} diff --git a/snmp/oid_set_test.go b/snmp/oid_set_test.go new file mode 100644 index 0000000..765b13b --- /dev/null +++ b/snmp/oid_set_test.go @@ -0,0 +1,48 @@ +package snmp + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestOIDSetGet(t *testing.T) { + assert.Equal(t, OID(nil), MakeOIDSet().Get(OID{1, 2, 1})) + assert.Equal(t, OID{}, MakeOIDSet(OID{}).Get(OID{1, 2, 1})) + assert.Equal(t, OID{1}, MakeOIDSet(OID{1}).Get(OID{1, 2, 1})) + assert.Equal(t, OID(nil), MakeOIDSet(OID{1, 1}).Get(OID{1, 2, 1})) +} + +func TestOIDSetAddEmpty(t *testing.T) { + var oidSet = MakeOIDSet() + + oidSet.Add(MustParseOID(".1")) + + assert.Equal(t, "{.1}", oidSet.String()) +} + +func TestOIDSetAddOther(t *testing.T) { + var oidSet = MakeOIDSet() + + oidSet.Add(MustParseOID(".1")) + oidSet.Add(MustParseOID(".2")) + + assert.Equal(t, "{.1 .2}", oidSet.String()) +} + +func TestOIDSetAddSub(t *testing.T) { + var oidSet = MakeOIDSet() + + oidSet.Add(MustParseOID(".1")) + oidSet.Add(MustParseOID(".1.2.1")) + + assert.Equal(t, "{.1}", oidSet.String()) +} + +func TestOIDSetAddSup(t *testing.T) { + var oidSet = MakeOIDSet() + + oidSet.Add(MustParseOID(".1.2.1")) + oidSet.Add(MustParseOID(".1")) + + assert.Equal(t, "{.1}", oidSet.String()) +}