summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey V.Kosteltsev <AKosteltsev@IBS.RU>2022-07-15 10:36:51 +0300
committerAndrey V.Kosteltsev <AKosteltsev@IBS.RU>2022-07-15 10:36:51 +0300
commit0f4556fc2343dc0ade0bb1e0d1fc6f85770d77af (patch)
treee26b6cb64f8ac4625c45baa40834fe51dc25c4f8
downloadobmc-sila-snmp-0f4556fc2343dc0ade0bb1e0d1fc6f85770d77af.tar.xz
First commit: Sila SNMP Sub Agent and configuration manager
-rw-r--r--.clang-format84
-rw-r--r--.gitignore3
-rw-r--r--LICENSE201
-rw-r--r--Makefile.am11
-rw-r--r--README.md230
-rw-r--r--agent/Makefile.am18
-rw-r--r--agent/data/enums.hpp57
-rw-r--r--agent/data/scalar.hpp150
-rw-r--r--agent/data/table.hpp348
-rw-r--r--agent/data/table/item.hpp157
-rw-r--r--agent/main.cpp180
-rw-r--r--agent/sila-snmp-agent.service.in15
-rw-r--r--agent/sila/inventory.cpp249
-rw-r--r--agent/sila/inventory.hpp32
-rw-r--r--agent/sila/powerstate.cpp140
-rw-r--r--agent/sila/powerstate.hpp38
-rw-r--r--agent/sila/sensors.cpp385
-rw-r--r--agent/sila/sensors.hpp33
-rw-r--r--agent/sila/sila_oid.hpp27
-rw-r--r--agent/sila/software.cpp225
-rw-r--r--agent/sila/software.hpp32
-rw-r--r--agent/snmp.cpp140
-rw-r--r--agent/snmp.hpp26
-rw-r--r--agent/snmp_oid.hpp83
-rw-r--r--agent/snmptrap.hpp164
-rw-r--r--agent/snmpvars.hpp121
-rw-r--r--agent/tracing.hpp30
-rwxr-xr-xbootstrap.sh23
-rw-r--r--configure.ac99
-rw-r--r--mibs/SILA-MIB.txt947
-rw-r--r--sdbusplus/helper.hpp168
-rw-r--r--snmpcfg/Makefile.am41
-rw-r--r--snmpcfg/sila-snmp-cfg-manager.service.in13
-rw-r--r--snmpcfg/snmpcfg-server.cpp291
-rw-r--r--snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml13
-rwxr-xr-xtests/emit-added-MEMBUF1.sh29
-rwxr-xr-xtests/emit-added-ambient.sh29
-rwxr-xr-xtests/emit-added-bmc.sh26
-rwxr-xr-xtests/emit-added-opfw.sh26
-rwxr-xr-xtests/emit-added-system-chassis.sh14
-rwxr-xr-xtests/emit-change-ambient-warn.sh9
-rwxr-xr-xtests/emit-change-ambient.sh8
-rwxr-xr-xtests/emit-change-power-state.sh7
-rwxr-xr-xtests/emit-removed-ambient.sh12
44 files changed, 4934 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..37469de
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,84 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: true
+PointerAlignment: Left
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IndentCaseLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Right
+ReflowComments: true
+SortIncludes: false
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
+...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..25a2537
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+ycm_extra_conf.py
+cscope.files
+cscope.out
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..7226319
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,11 @@
+SUBDIRS = .
+
+if WANT_AGENT
+SUBDIRS += agent
+endif
+if WANT_CFG_MANAGER
+SUBDIRS += snmpcfg
+endif
+
+mibsdir = $(datarootdir)/snmp/mibs
+mibs_DATA = mibs/SILA-MIB.txt
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e950a2a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,230 @@
+# sila-snmp
+
+This project containt two subprojects:
+- [sila-snmp-agent](#sila-snmp-agent)
+- [snmpcfg - DBus service for manage snmp configuration](#snmpcfg)
+
+## sila-snmp-agent
+
+This is a subagent for the original net-snmp daemon.
+It queries DBus for values of available sensors, inventory items and some
+other stuff, and exports their actual values over SNMP.
+
+### SILA-MIB.txt
+
+File SILA-MIB.txt describe a struc of exported data.
+This file can be found at OpenBMC hosts in folder `/usr/share/snmp/mibs` and may be fetched from OpenBMC host over http.
+```shell
+$ wget https://<IP-addres-of-BMC-host>/mibs/SILA-MIB.txt
+```
+
+### Basic SNMP
+
+This module export a host power state field and tree tables of sensors values.
+```shell
+$ snmptranslate -Tp -IR SILA-MIB::sila
++--sila(49769)
+ |
+ +--silaNotifications(0)
+ | |
+ | +--silaHostPowerStateNotification(1)
+ | +--silaTempSensorStateNotification(2)
+ | +--silaVoltSensorStateNotification(3)
+ | +--silaTachSensorStateNotification(4)
+ |
+ +--silaSensors(1)
+ | |
+ | +-- -R-- EnumVal silaHostPowerState(1)
+ | | Values: unknown(-1), off(0), on(1)
+ | |
+ | +--silaTempSensorsTable(2)
+ | | |
+ | | +--silaTempSensorsEntry(1)
+ | | | Index: silaTempSensorName
+ | | |
+ | | +-- ---- String silaTempSensorName(1)
+ | | | Size: 1..32
+ | | +-- -R-- Integer32 silaTempSensorValue(2)
+ | | | Textual Convention: Degrees
+ | | +-- -R-- Integer32 silaTempSensorWarnLow(3)
+ | | | Textual Convention: Degrees
+ | | +-- -R-- Integer32 silaTempSensorWarnHigh(4)
+ | | | Textual Convention: Degrees
+ | | +-- -R-- Integer32 silaTempSensorCritLow(5)
+ | | | Textual Convention: Degrees
+ | | +-- -R-- Integer32 silaTempSensorCritHigh(6)
+ | | | Textual Convention: Degrees
+ | | +-- -R-- EnumVal silaTempSensorState(7)
+ | | Textual Convention: SensorState
+ | | Values: disabled(0), normal(1), warningLow(2), warningHigh(3), criticalLow(4), criticalHigh(5)
+ | |
+ | +--silaVoltSensorsTable(3)
+ | | |
+ | | +--silaVoltSensorsEntry(1)
+ | | | Index: silaVoltSensorName
+ | | |
+ | | +-- ---- String silaVoltSensorName(1)
+ | | | Size: 1..32
+ | | +-- -R-- Integer32 silaVoltSensorValue(2)
+ | | | Textual Convention: Voltage
+ | | +-- -R-- Integer32 silaVoltSensorWarnLow(3)
+ | | | Textual Convention: Voltage
+ | | +-- -R-- Integer32 silaVoltSensorWarnHigh(4)
+ | | | Textual Convention: Voltage
+ | | +-- -R-- Integer32 silaVoltSensorCritLow(5)
+ | | | Textual Convention: Voltage
+ | | +-- -R-- Integer32 silaVoltSensorCritHigh(6)
+ | | | Textual Convention: Voltage
+ | | +-- -R-- EnumVal silaVoltSensorState(7)
+ | | Textual Convention: SensorState
+ | | Values: disabled(0), normal(1), warningLow(2), warningHigh(3), criticalLow(4), criticalHigh(5)
+ | |
+ | +--silaTachSensorsTable(4)
+ | |
+ | +--silaTachSensorsEntry(1)
+ | | Index: silaTachSensorName
+ | |
+ | +-- ---- String silaTachSensorName(1)
+ | | Size: 1..32
+ | +-- -R-- Integer32 silaTachSensorValue(2)
+ | | Textual Convention: RPMS
+ | +-- -R-- Integer32 silaTachSensorWarnLow(3)
+ | | Textual Convention: RPMS
+ | +-- -R-- Integer32 silaTachSensorWarnHigh(4)
+ | | Textual Convention: RPMS
+ | +-- -R-- Integer32 silaTachSensorCritLow(5)
+ | | Textual Convention: RPMS
+ | +-- -R-- Integer32 silaTachSensorCritHigh(6)
+ | | Textual Convention: RPMS
+ | +-- -R-- EnumVal silaTachSensorState(7)
+ | Textual Convention: SensorState
+ | Values: disabled(0), normal(1), warningLow(2), warningHigh(3), criticalLow(4), criticalHigh(5)
+ |
+ +--silaConformance(2)
+ |
+ +--silaCompliances(1)
+ | |
+ | +--silaCommpliance(1)
+ |
+ +--silaGroups(2)
+ |
+ +--silaScalarFieldsGroup(1)
+ +--silaTempSensorsTableGroup(2)
+ +--silaVoltSensorsTableGroup(3)
+ +--silaTachSensorsTableGroup(4)
+ +--silaNotificationsGroup(10)
+```
+
+Content of tables may be viewed by command `snmptable`:
+```shell
+$ snmptable -v2c -cpublic -Ci manzoni.dev.sila.com SILA-MIB::silaTempSensorsTable
+SNMP table: SILA-MIB::silaTempSensorsTable
+
+ index silaTempSensorValue silaTempSensorWarnLow silaTempSensorWarnHigh silaTempSensorCritLow silaTempSensorCritHigh silaTempSensorState
+ "ambient" 27.000 °C .000 °C 35.000 °C .000 °C 40.000 °C normal
+ "RTC_temp1" 21.000 °C .000 °C 35.000 °C .000 °C 40.000 °C normal
+ "INlet_Temp1" 21.000 °C .000 °C 35.000 °C .000 °C 40.000 °C normal
+ "INlet_Temp2" 20.500 °C .000 °C 35.000 °C .000 °C 40.000 °C normal
+"OUTlet_Temp1" 21.500 °C .000 °C 35.000 °C .000 °C 40.000 °C normal
+"OUTlet_Temp2" 22.000 °C .000 °C 35.000 °C .000 °C 40.000 °C normal
+...
+```
+
+All exporting data may be viewed by command `snmpwalk`:
+```shell
+$ snmpwalk -v2c -cpublic manzoni.dev.sila.com SILA-MIB::silaTempSensorsTable
+SILA-MIB::silaTempSensorValue."ambient" = INTEGER: 26.750 °C
+SILA-MIB::silaTempSensorValue."RTC_temp1" = INTEGER: 20.750 °C
+SILA-MIB::silaTempSensorValue."INlet_Temp1" = INTEGER: 21.500 °C
+SILA-MIB::silaTempSensorValue."INlet_Temp2" = INTEGER: 20.500 °C
+SILA-MIB::silaTempSensorValue."OUTlet_Temp1" = INTEGER: 22.000 °C
+SILA-MIB::silaTempSensorValue."OUTlet_Temp2" = INTEGER: 22.000 °C
+SILA-MIB::silaTempSensorWarnLow."ambient" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorWarnLow."RTC_temp1" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorWarnLow."INlet_Temp1" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorWarnLow."INlet_Temp2" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorWarnLow."OUTlet_Temp1" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorWarnLow."OUTlet_Temp2" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorWarnHigh."ambient" = INTEGER: 35.000 °C
+SILA-MIB::silaTempSensorWarnHigh."RTC_temp1" = INTEGER: 35.000 °C
+SILA-MIB::silaTempSensorWarnHigh."INlet_Temp1" = INTEGER: 35.000 °C
+SILA-MIB::silaTempSensorWarnHigh."INlet_Temp2" = INTEGER: 35.000 °C
+SILA-MIB::silaTempSensorWarnHigh."OUTlet_Temp1" = INTEGER: 35.000 °C
+SILA-MIB::silaTempSensorWarnHigh."OUTlet_Temp2" = INTEGER: 35.000 °C
+SILA-MIB::silaTempSensorCritLow."ambient" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorCritLow."RTC_temp1" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorCritLow."INlet_Temp1" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorCritLow."INlet_Temp2" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorCritLow."OUTlet_Temp1" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorCritLow."OUTlet_Temp2" = INTEGER: .000 °C
+SILA-MIB::silaTempSensorCritHigh."ambient" = INTEGER: 40.000 °C
+SILA-MIB::silaTempSensorCritHigh."RTC_temp1" = INTEGER: 40.000 °C
+SILA-MIB::silaTempSensorCritHigh."INlet_Temp1" = INTEGER: 40.000 °C
+SILA-MIB::silaTempSensorCritHigh."INlet_Temp2" = INTEGER: 40.000 °C
+SILA-MIB::silaTempSensorCritHigh."OUTlet_Temp1" = INTEGER: 40.000 °C
+SILA-MIB::silaTempSensorCritHigh."OUTlet_Temp2" = INTEGER: 40.000 °C
+SILA-MIB::silaTempSensorState."ambient" = INTEGER: normal(1)
+SILA-MIB::silaTempSensorState."RTC_temp1" = INTEGER: normal(1)
+SILA-MIB::silaTempSensorState."INlet_Temp1" = INTEGER: normal(1)
+SILA-MIB::silaTempSensorState."INlet_Temp2" = INTEGER: normal(1)
+SILA-MIB::silaTempSensorState."OUTlet_Temp1" = INTEGER: normal(1)
+SILA-MIB::silaTempSensorState."OUTlet_Temp2" = INTEGER: normal(1)
+...
+```
+
+Get current value of a specific sensor:
+```
+$ snmpget -v2c -cpublic malevich.dev.sila.com SILA-MIB::silaTempSensorValue.\"OUTlet_Temp2\"
+
+SILA-MIB::silaTempSensorValue."OUTlet_Temp2" = INTEGER: 29.500 °C
+```
+
+### SNMPv3 support
+
+For manage SNMPv3 access in runtime required `snmpusm` and `snmpvacm` tools from `net-snmp` package.
+
+I did create the snmp user `root` with password `0penBmcAA` for control snmpd.
+But by default him have access only from localhost.
+For allowing access from other hosts, you should add/modify access rule in snmpd.conf:
+```shell
+...
+com2sec readwrite <Your host> private
+...
+```
+
+New user can be created over 3 steps:
+1. Create copy of existen user
+```shell
+$ snmpusm -v3 -uroot -a0penBmcAA -x0penBmcAA -lauthPriv <OpenBMC-Host> create user01
+```
+
+2. Change password for new user:
+```shell
+$ snmpusm -v3 -uroot -a0penBmcAA -x0penBmcAA -lauthPriv <OpenBMC-Host> passwd 0penBmcAA NewPassword
+```
+
+3. Allow for new user read our data over snmp (add user to group MyRoGroup):
+```shell
+$ snmpvacm -v3 -uroot -a0penBmcAA -x0penBmc -lauthPriv <OpenBMC-Host> createSec2Group 3 user01 MyRoGroup
+```
+
+All users present in `SNMP-USER-BASED-SM-MIB::usmUserTable`.
+
+### SNMP traps
+
+For receive SNMP traps you should add receivers to snmpd.conf
+```shell
+trap2sink <receiver-host> <receiver-community>
+```
+
+## snmpcfg
+
+This is a DBus service with interface `xyz.openbmc_project.SNMPCfg`
+and object path `/xyz/openbmc_project/snmpcfg` for manage snmpd.conf content.
+
+There has three methods:
+- GetConfig - return body of actual snmpd.conf
+- SetConfig - replace body of snmpd.conf with specified data and restart snmpd.
+- ResetConfig - restore default version of snmpd.conf and restart snmpd.
+
+This subproject will be moved to separate project in fututre.
diff --git a/agent/Makefile.am b/agent/Makefile.am
new file mode 100644
index 0000000..851c3a1
--- /dev/null
+++ b/agent/Makefile.am
@@ -0,0 +1,18 @@
+AM_CPPFLAGS = -iquote $(top_srcdir)
+
+bin_PROGRAMS = sila-snmp-agent
+
+sila_snmp_agent_SOURCES = \
+ snmp.cpp \
+ sila/powerstate.cpp \
+ sila/sensors.cpp \
+ sila/software.cpp \
+ sila/inventory.cpp \
+ main.cpp
+
+sila_snmp_agent_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(SDEVENTPLUS_CFLAGS) $(NETSNMP_CFLAGS)
+sila_snmp_agent_LDADD = $(SDBUSPLUS_LIBS) $(SDEVENTPLUS_LIBS) $(NETSNMP_AGENT_LIBS)
+
+if HAVE_SYSTEMD
+systemdsystemunit_DATA = sila-snmp-agent.service
+endif
diff --git a/agent/data/enums.hpp b/agent/data/enums.hpp
new file mode 100644
index 0000000..bd6eb94
--- /dev/null
+++ b/agent/data/enums.hpp
@@ -0,0 +1,57 @@
+/**
+ * @brief DBus enums to base type converter.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include <string>
+#include <map>
+
+namespace phosphor
+{
+namespace snmp
+{
+namespace data
+{
+
+template <typename T> struct DBusEnum
+{
+ std::string base;
+ std::map<std::string, T> values;
+ T wrongValue;
+
+ T get(const std::string& str) const
+ {
+ const auto len = base.length();
+ if (0 == str.compare(0, len, base))
+ {
+ const auto& it = values.find(str.substr(len + 1));
+ if (it != values.end())
+ {
+ return it->second;
+ }
+ }
+
+ return wrongValue;
+ }
+};
+
+} // namespace data
+} // namespace snmp
+} // namespace phosphor
diff --git a/agent/data/scalar.hpp b/agent/data/scalar.hpp
new file mode 100644
index 0000000..2fc4a24
--- /dev/null
+++ b/agent/data/scalar.hpp
@@ -0,0 +1,150 @@
+/**
+ * @brief Export DBus property to scalar MIB value
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include "sdbusplus/helper.hpp"
+#include <memory>
+
+namespace phosphor
+{
+namespace snmp
+{
+namespace data
+{
+template <typename T> class Scalar
+{
+ public:
+ using value_t = std::variant<T>;
+
+ /* Define all of the basic class operations:
+ * Not allowed:
+ * - Default constructor to avoid nullptrs.
+ * - Copy operations due to internal unique_ptr.
+ * Allowed:
+ * - Move operations.
+ * - Destructor.
+ */
+ Scalar() = delete;
+ Scalar(const Scalar&) = delete;
+ Scalar& operator=(const Scalar&) = delete;
+ Scalar(Scalar&&) = default;
+ Scalar& operator=(Scalar&&) = default;
+ ~Scalar() = default;
+
+ /**
+ * @brief Object constructor
+ */
+ Scalar(const std::string& path, const std::string& iface,
+ const std::string& prop, const T& initValue) :
+ _value(initValue),
+ _path(path), _iface(iface), _prop(prop),
+ _onChangedMatch(sdbusplus::helper::helper::getBus(),
+ sdbusplus::bus::match::rules::propertiesChanged(
+ path.c_str(), iface.c_str()),
+ std::bind(&Scalar<T>::onPropertyChanged, this,
+ std::placeholders::_1))
+ {
+ }
+
+ /**
+ * @brief Sent request to DBus object and store new value of property
+ */
+ void update()
+ {
+ try
+ {
+ auto service = sdbusplus::helper::helper::getService(_path, _iface);
+ auto r = sdbusplus::helper::helper::callMethod(
+ service, _path, sdbusplus::helper::PROPERTIES_IFACE, "Get",
+ _iface, _prop);
+
+ value_t var;
+ r.read(var);
+ setValue(var);
+ }
+ catch (const sdbusplus::exception::SdBusError&)
+ {
+ // Corresponding service is not started yet.
+ // When service is started, we'll receive data with
+ // `propertiesChanged` signal.
+ // So, this catch block is just for silencing the exception.
+ }
+ }
+
+ const T& getValue() const
+ {
+ return _value;
+ }
+
+ const std::string& getPath() const
+ {
+ return _path;
+ }
+
+ const std::string& getInterface() const
+ {
+ return _iface;
+ }
+
+ const std::string& getProperty() const
+ {
+ return _prop;
+ }
+
+ protected:
+ /**
+ * @brief DBus signal `PropertiesChanged` handler
+ */
+ void onPropertyChanged(sdbusplus::message::message& m)
+ {
+ std::string iface;
+ std::map<std::string, value_t> data;
+ std::vector<std::string> v;
+
+ m.read(iface, data, v);
+
+ if (data.find(_prop) != data.end())
+ {
+ setValue(data[_prop]);
+ }
+ }
+
+ /**
+ * @brief Setter for actual value
+ */
+ virtual void setValue(value_t& var)
+ {
+ auto newValue = std::get<T>(var);
+ std::swap(_value, newValue);
+ }
+
+ private:
+ T _value;
+ std::string _path;
+ std::string _iface;
+ std::string _prop;
+
+ sdbusplus::bus::match::match _onChangedMatch;
+};
+
+} // namespace data
+} // namespace snmp
+} // namespace phosphor
diff --git a/agent/data/table.hpp b/agent/data/table.hpp
new file mode 100644
index 0000000..ab891c4
--- /dev/null
+++ b/agent/data/table.hpp
@@ -0,0 +1,348 @@
+/**
+ * @brief Export DBus objects tree to MIB table
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include "sdbusplus/helper.hpp"
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+
+namespace phosphor
+{
+namespace snmp
+{
+namespace data
+{
+
+/**
+ * @brief MIB Table implementation.
+ */
+template <typename ItemType> class Table
+{
+ public:
+ using interfaces_t = std::vector<std::string>;
+
+ /* Define all of the basic class operations:
+ * Not allowed:
+ * - Default constructor to avoid nullptrs.
+ * - Copy operations due to internal unique_ptr.
+ * Allowed:
+ * - Move operations.
+ * - Destructor.
+ */
+ Table() = delete;
+ Table(const Table&) = delete;
+ Table& operator=(const Table&) = delete;
+ Table(Table&&) = default;
+ Table& operator=(Table&&) = default;
+ ~Table() = default;
+
+ /**
+ * @brief Object constructor
+ *
+ * @param folder - DBus folder where required objects exist.
+ * @param interfaces - List of required DBus properties interfaces
+ */
+ Table(const std::string& folder, const interfaces_t interfaces = {}) :
+ _path(folder), _interfaces(interfaces)
+ {
+ _matches.emplace_back(sdbusplus::helper::helper::getBus(),
+ sdbusplus::bus::match::rules::interfacesAdded(),
+ std::bind(&Table<ItemType>::onInterfacesAdded,
+ this, std::placeholders::_1));
+ _matches.emplace_back(sdbusplus::helper::helper::getBus(),
+ sdbusplus::bus::match::rules::interfacesRemoved(),
+ std::bind(&Table<ItemType>::onInterfacesRemoved,
+ this, std::placeholders::_1));
+ }
+
+ /**
+ * @brief Force update table items
+ */
+ void update()
+ {
+ auto data = sdbusplus::helper::helper::getSubTree(_path, _interfaces);
+
+ // Drop sensors if it not present in answer
+ auto path = _path + "/";
+ for (auto it = _items.begin(); it != _items.end();)
+ {
+ if (data.find(path + (*it)->name) == data.end())
+ {
+ it = dropItem(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ // Update existing and create new items.
+ using fields_map_t = typename ItemType::fields_map_t;
+ for (const auto& pi : data)
+ {
+ for (const auto& bi : pi.second)
+ {
+ auto fields =
+ sdbusplus::helper::helper::callMethodAndRead<fields_map_t>(
+ bi.first, pi.first, sdbusplus::helper::PROPERTIES_IFACE,
+ "GetAll", "");
+ getItem(pi.first).setFields(fields);
+ }
+ }
+ }
+
+ /**
+ * @brief Register MIB handlers
+ *
+ * @param name - Name of table in MIB
+ * @param table_oid - OID of table
+ * @param table_oid_len - Table OID length
+ * @param min_column - Minimum columns number
+ * @param max_column - Maximum columns number
+ */
+ void init_mib(const char* name, const oid* table_oid, size_t table_oid_len,
+ size_t min_column, size_t max_column)
+ {
+ netsnmp_handler_registration* reg = netsnmp_create_handler_registration(
+ name, Table<ItemType>::snmp_handler, table_oid, table_oid_len,
+ HANDLER_CAN_RONLY);
+
+ netsnmp_table_registration_info* table_info =
+ SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
+ netsnmp_table_helper_add_indexes(table_info, ASN_OCTET_STR, 0);
+ table_info->min_column = min_column;
+ table_info->max_column = max_column;
+
+ netsnmp_iterator_info* iinfo =
+ SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
+ iinfo->get_first_data_point = Table<ItemType>::get_first_data_point;
+ iinfo->get_next_data_point = Table<ItemType>::get_next_data_point;
+ iinfo->table_reginfo = table_info;
+ iinfo->myvoid = this;
+
+ netsnmp_register_table_iterator(reg, iinfo);
+ }
+
+ protected:
+ using ItemPtr = std::unique_ptr<ItemType>;
+ using Items = std::vector<ItemPtr>;
+
+ /**
+ * @brief DBus signal `InterfacesAdded` handler.
+ */
+ void onInterfacesAdded(sdbusplus::message::message& m)
+ {
+ using Data = std::map<std::string, typename ItemType::fields_map_t>;
+
+ sdbusplus::message::object_path path;
+ Data data;
+ m.read(path, data);
+
+ if (0 == path.str.compare(0, _path.length(), _path))
+ {
+ // Skip unnecessary objects
+ bool isOwned = _interfaces.empty();
+ if (!isOwned)
+ {
+ auto it = std::find_first_of(
+ _interfaces.begin(), _interfaces.end(), data.begin(),
+ data.end(),
+ [](const std::string& iface,
+ const typename Data::value_type& item) {
+ return iface == item.first;
+ });
+ isOwned = (it != _interfaces.end());
+ }
+
+ if (isOwned)
+ {
+ auto& item = getItem(path.str);
+
+ for (const auto& [iface, fields] : data)
+ {
+ item.setFields(fields);
+ }
+ }
+ }
+ }
+
+ /**
+ * @brief DBus signal `InterfacesRemoved` handler.
+ */
+ void onInterfacesRemoved(sdbusplus::message::message& m)
+ {
+ sdbusplus::message::object_path path;
+ std::vector<std::string> data;
+ try
+ {
+ m.read(path, data);
+ }
+ catch (const sdbusplus::exception::SdBusError& e)
+ {
+ TRACE_ERROR("data/table: Failed to parse signal data. "
+ "ERROR='%s', REPLY_SIG='%s', PATH='%s', "
+ "IFACE='%s', MEMBER='%s', object path='%s'\n",
+ e.what(), m.get_signature(), m.get_path(),
+ m.get_interface(), m.get_member(), path.str.c_str());
+ }
+
+ if (0 == path.str.compare(0, _path.length(), _path))
+ {
+ bool isOwned = _interfaces.empty();
+ if (!isOwned)
+ {
+ auto it =
+ std::find_first_of(_interfaces.begin(), _interfaces.end(),
+ data.begin(), data.end());
+ isOwned = (it != _interfaces.end());
+ }
+
+ if (isOwned)
+ {
+ dropItem(path.str);
+ }
+ }
+ }
+
+ /**
+ * @brief Create new item if does not exist and return reference.
+ */
+ ItemType& getItem(const std::string& path)
+ {
+ auto name = path.substr(_path.length() + 1); // Skip following '/'
+ auto it = std::lower_bound(_items.begin(), _items.end(), name);
+ if (it != _items.end() && (*it)->name == name)
+ {
+ return *(*it);
+ }
+
+ it = _items.emplace(it, std::make_unique<ItemType>(_path, name));
+ (*it)->onCreate();
+ return *(*it);
+ }
+
+ /**
+ * @brief Drop item specified by iterator.
+ *
+ * @param it - Items iterator
+ *
+ * @return Iterator pointing to the new location of the item that followed
+ * the item erased.
+ */
+ typename Items::iterator dropItem(typename Items::iterator it)
+ {
+ if (it != _items.end())
+ {
+ (*it)->onDestroy();
+ it = _items.erase(it);
+ }
+ return it;
+ }
+
+ /**
+ * @brief Drop item.
+ */
+ void dropItem(const std::string& path)
+ {
+ auto name = path.substr(_path.length() + 1); // Skip following '/'
+ auto it = std::lower_bound(_items.begin(), _items.end(), name);
+ if (it != _items.end() && (*it)->name == name)
+ {
+ dropItem(it);
+ }
+ }
+
+ /**
+ * @brief Iterator hook routines
+ */
+ static netsnmp_variable_list*
+ get_first_data_point(void** loop_ctx, void** data_ctx,
+ netsnmp_variable_list* idx_data,
+ netsnmp_iterator_info* data)
+ {
+ *loop_ctx = reinterpret_cast<void*>(0);
+ return Table<ItemType>::get_next_data_point(loop_ctx, data_ctx,
+ idx_data, data);
+ }
+ static netsnmp_variable_list*
+ get_next_data_point(void** loop_ctx, void** data_ctx,
+ netsnmp_variable_list* idx_data,
+ netsnmp_iterator_info* data)
+ {
+ auto index = reinterpret_cast<size_t>(*loop_ctx);
+ auto& table = *reinterpret_cast<Table<ItemType>*>(data->myvoid);
+
+ if (index < table._items.size())
+ {
+ auto& item = table._items[index];
+
+ snmp_set_var_value(idx_data, item->name.c_str(),
+ item->name.length());
+
+ *data_ctx = item.get();
+ *loop_ctx = reinterpret_cast<void*>(index + 1);
+ return idx_data;
+ }
+
+ return nullptr;
+ }
+
+ /**
+ * @brief handles snmp requests for the table data.
+ */
+ static int snmp_handler(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+ {
+ switch (reqinfo->mode)
+ {
+ case MODE_GET:
+ for (auto request = requests; request; request = request->next)
+ {
+ auto entry = reinterpret_cast<ItemType*>(
+ netsnmp_extract_iterator_context(request));
+
+ if (!entry)
+ {
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_NOSUCHINSTANCE);
+ continue;
+ }
+
+ entry->get_snmp_reply(reqinfo, request);
+ }
+ break;
+ }
+
+ return SNMP_ERR_NOERROR;
+ }
+
+ std::string _path;
+ interfaces_t _interfaces;
+ std::vector<sdbusplus::bus::match::match> _matches;
+ Items _items;
+};
+
+} // namespace data
+} // namespace snmp
+} // namespace phosphor
diff --git a/agent/data/table/item.hpp b/agent/data/table/item.hpp
new file mode 100644
index 0000000..7c4c0eb
--- /dev/null
+++ b/agent/data/table/item.hpp
@@ -0,0 +1,157 @@
+/**
+ * @brief MIB tables item definition.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include "sdbusplus/helper.hpp"
+
+namespace phosphor
+{
+namespace snmp
+{
+namespace data
+{
+namespace table
+{
+
+/**
+ * @brief MIB Table row implementation.
+ */
+template <typename... T> struct Item
+{
+ /*
+ * The `std::variant` allows to keep duplicates of types,
+ * but `std::get<>()` and `std::holds_alternative<>()` is ill-formed
+ * in this case.
+ *
+ * So we can't use here:
+ * using variant_t = std::variant<T...>;
+ * and we should specify all possible types.
+ */
+ using variant_t = std::variant<int64_t, std::string, bool, uint8_t, double>;
+
+ using values_t = std::tuple<T...>;
+ using fields_map_t = std::map<std::string, variant_t>;
+
+ /* Define all of the basic class operations:
+ * Not allowed:
+ * - Default constructor to avoid nullptrs.
+ * - Copy operations due to internal unique_ptr.
+ * Allowed:
+ * - Move operations.
+ * - Destructor.
+ */
+ Item() = delete;
+ Item(const Item&) = delete;
+ Item& operator=(const Item&) = delete;
+ Item(Item&&) = default;
+ Item& operator=(Item&&) = default;
+ ~Item() = default;
+
+ /**
+ * @brief Object constructor
+ *
+ * @param folder - Base folder for DBus object path
+ * @param name - DBus object path relative by folder
+ * @param args - Default values for each fields
+ */
+ Item(const std::string& folder, const std::string& name, T&&... args) :
+ name(name), data(std::forward<T>(args)...),
+ changedMatch(sdbusplus::helper::helper::getBus(),
+ sdbusplus::bus::match::rules::propertiesChanged(
+ folder + "/" + name),
+ std::bind(&Item<T...>::onPropertiesChanged, this,
+ std::placeholders::_1))
+ {
+ }
+
+ /**
+ * @brief PropertiesChanged signal handler
+ */
+ virtual void onPropertiesChanged(sdbusplus::message::message& m)
+ {
+ std::string iface;
+ fields_map_t data;
+ std::vector<std::string> v;
+ m.read(iface, data, v);
+
+ setFields(data);
+ }
+
+ /**
+ * @brief Store fields values recieved from DBus
+ */
+ virtual void setFields(const fields_map_t& fields) = 0;
+
+ /**
+ * @brief Called after object has been created.
+ */
+ virtual void onCreate()
+ {
+ }
+
+ /**
+ * @brief Called before object will be destroyed.
+ */
+ virtual void onDestroy()
+ {
+ }
+
+ /**
+ * @brief String fields vlaues helper
+ */
+ template <size_t Index>
+ void setField(const fields_map_t& fieldsMap, const char* propertyName)
+ {
+ using FieldType = typename std::tuple_element<Index, values_t>::type;
+ if (fieldsMap.find(propertyName) != fieldsMap.end() &&
+ std::holds_alternative<FieldType>(fieldsMap.at(propertyName)))
+ {
+ std::get<Index>(data) =
+ std::get<FieldType>(fieldsMap.at(propertyName));
+ }
+ }
+
+ /**
+ * @brief Fill snmp reply with fields values
+ */
+ virtual void get_snmp_reply(netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* request) const = 0;
+
+ std::string name;
+ values_t data;
+
+ private:
+ sdbusplus::bus::match::match changedMatch;
+};
+
+/**
+ * @brief Used for std::lower_bound throw vector of smartpointers.
+ */
+template <typename ItemType>
+inline bool operator<(const std::unique_ptr<ItemType>& o, const std::string& s)
+{
+ return o->name < s;
+}
+
+} // namespace table
+} // namespace data
+} // namespace snmp
+} // namespace phosphor
diff --git a/agent/main.cpp b/agent/main.cpp
new file mode 100644
index 0000000..033696e
--- /dev/null
+++ b/agent/main.cpp
@@ -0,0 +1,180 @@
+/**
+ * @brief SNMP Agent entry point
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "config.h"
+#include "tracing.hpp"
+#include "sdbusplus/helper.hpp"
+#include "snmp.hpp"
+
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/signal.hpp>
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+
+#include <csignal>
+
+#include "sila/powerstate.hpp"
+#include "sila/sensors.hpp"
+#include "sila/software.hpp"
+#include "sila/inventory.hpp"
+
+void print_usage()
+{
+ fprintf(stderr, "Usage: %s [OPTIONS]\n\n", PACKAGE_NAME);
+ fprintf(stderr, " Version: %s\n\nOPTIONS:\n", PACKAGE_VERSION);
+ fprintf(stderr, " -h,--help\t\tdisplay this help message\n");
+ fprintf(stderr, " -d\t\t\tdump sent and received SNMP packets\n");
+ fprintf(
+ stderr,
+ " -D[TOKEN[,...]]\tturn on debugging output for the given TOKEN(s)\n"
+ "\t\t\t (try ALL for extremely verbose output)\n");
+ fprintf(stderr,
+ " -L <LOGOPTS>\t\ttoggle options controlling where to log to\n");
+ snmp_log_options_usage("\t\t\t ", stderr);
+ fflush(stderr);
+}
+
+// parse_args error codes
+enum
+{
+ EC_SHOW_USAGE = 1,
+ EC_ERROR = -1,
+ EC_SUCCESS = 0,
+};
+
+int parse_args(int argc, char** argv)
+{
+ constexpr auto Opts = "dD:L:h";
+
+ optind = 1;
+ int arg;
+ int rc = EC_SUCCESS;
+
+ while (EC_SUCCESS == rc && (arg = getopt(argc, argv, Opts)) != EOF)
+ {
+ DEBUGMSGTL(("parse_args", "handling (#%d): %c\n", optind, arg));
+ switch (arg)
+ {
+ case 'D':
+ debug_register_tokens(optarg);
+ snmp_set_do_debugging(1);
+ break;
+
+ case 'd':
+ netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
+ NETSNMP_DS_LIB_DUMP_PACKET, 1);
+ break;
+
+ case 'L':
+ rc = snmp_log_options(optarg, argc, argv);
+ break;
+
+ case 'h':
+ rc = EC_SHOW_USAGE;
+ break;
+
+ case '?':
+ default:
+ rc = EC_ERROR;
+ break;
+ }
+ }
+ DEBUGMSGTL(("parse_args", "finished: %d/%d\n", optind, argc));
+ return rc;
+}
+
+static void clean_exit(sdeventplus::source::Signal& source,
+ const struct signalfd_siginfo*)
+{
+ TRACE_INFO("Signal %d received, terminating...\n", source.get_signal());
+ source.get_event().exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = parse_args(argc, argv);
+ if (rc < EC_SUCCESS)
+ {
+ return EXIT_FAILURE;
+ }
+ else if (rc > EC_SUCCESS)
+ {
+ print_usage();
+ return EXIT_SUCCESS;
+ }
+
+ auto evt = sdeventplus::Event::get_default();
+ sdbusplus::helper::helper::getBus().attach_event(evt.get(),
+ SD_EVENT_PRIORITY_NORMAL);
+
+ sigset_t ss;
+ if (sigemptyset(&ss) < 0 || sigaddset(&ss, SIGTERM) < 0 ||
+ sigaddset(&ss, SIGINT) < 0)
+ {
+ TRACE_ERROR("Failed to setup signal hanlders.\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Block SIGTERM first, so than the event loop can handle it */
+ if (sigprocmask(SIG_BLOCK, &ss, NULL) < 0)
+ {
+ TRACE_ERROR("Failed to block SIGTERM.\n");
+ return EXIT_FAILURE;
+ }
+
+ sdeventplus::source::Signal sigterm(evt, SIGTERM, clean_exit);
+ sdeventplus::source::Signal sigint(evt, SIGINT, clean_exit);
+
+ snmpagent_init(evt);
+
+ // Initialize DBus and MIB objects
+
+ sila::host::power::state::init();
+ sila::sensors::init();
+ sila::software::init();
+ sila::inventory::init();
+
+ // main loop
+
+ TRACE_INFO("%s is up and running.\n", PACKAGE_STRING);
+
+ rc = evt.loop();
+
+ TRACE_INFO("%s shuting down.\n", PACKAGE_STRING);
+
+ // Release DBus and MIB objects resources
+
+ sila::inventory::destroy();
+ sila::software::destroy();
+ sila::sensors::destroy();
+ sila::host::power::state::destroy();
+
+ snmpagent_destroy();
+
+ if (rc < 0)
+ {
+ TRACE_ERROR("Event loop returned error %d, %s\n", rc, strerror(-rc));
+ return -rc;
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/agent/sila-snmp-agent.service.in b/agent/sila-snmp-agent.service.in
new file mode 100644
index 0000000..9658558
--- /dev/null
+++ b/agent/sila-snmp-agent.service.in
@@ -0,0 +1,15 @@
+[Unit]
+Description=Sila SNMP Sub Agent
+PartOf=snmpd.service
+After=snmpd.service
+After=obmc-mapper.target
+
+[Service]
+Restart=always
+Environment=OPTIONS="-Ls0-6d"
+EnvironmentFile=-@sysconfdir@/default/sila-snmp-agent
+ExecStart=@bindir@/sila-snmp-agent $OPTIONS
+SyslogIdentifier=sila-snmp
+
+[Install]
+WantedBy=snmpd.service
diff --git a/agent/sila/inventory.cpp b/agent/sila/inventory.cpp
new file mode 100644
index 0000000..233b8f8
--- /dev/null
+++ b/agent/sila/inventory.cpp
@@ -0,0 +1,249 @@
+/**
+ * @brief SILA inventory table implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "tracing.hpp"
+#include "data/table.hpp"
+#include "data/table/item.hpp"
+#include "sila/sila_oid.hpp"
+#include "snmptrap.hpp"
+
+namespace sila
+{
+namespace inventory
+{
+using OID = phosphor::snmp::agent::OID;
+static const OID NOTIFY_OID = SILA_OID(0, 7);
+
+struct InventoryItem : public phosphor::snmp::data::table::Item<
+ std::string, std::string, std::string, std::string,
+ std::string, std::string, std::string, bool, bool>
+{
+ // Indexes of fields in tuble
+ enum Fields
+ {
+ FIELD_INVENTORY_PRETTY_NAME = 0,
+ FIELD_INVENTORY_MANUFACTURER,
+ FIELD_INVENTORY_BUILD_DATE,
+ FIELD_INVENTORY_MODEL,
+ FIELD_INVENTORY_PART_NUMBER,
+ FIELD_INVENTORY_SERIAL_NUMBER,
+ FIELD_INVENTORY_VERSION,
+ FIELD_INVENTORY_PRESENT,
+ FIELD_INVENTORY_FUNCTIONAL,
+ };
+
+ enum Columns
+ {
+ COLUMN_SILAINVENTORY_PATH = 1,
+ COLUMN_SILAINVENTORY_NAME,
+ COLUMN_SILAINVENTORY_MANUFACTURER,
+ COLUMN_SILAINVENTORY_BUILD_DATE,
+ COLUMN_SILAINVENTORY_MODEL,
+ COLUMN_SILAINVENTORY_PART_NUMBER,
+ COLUMN_SILAINVENTORY_SERIAL_NUMBER,
+ COLUMN_SILAINVENTORY_VERSION,
+ COLUMN_SILAINVENTORY_PRESENT,
+ COLUMN_SILAINVENTORY_FUNCTIONAL,
+ };
+
+ InventoryItem(const std::string& folder, const std::string& name) :
+ phosphor::snmp::data::table::Item<std::string, std::string, std::string,
+ std::string, std::string, std::string,
+ std::string, bool, bool>(
+ folder, name,
+ "", // Pretty Name
+ "", // Manufacturer
+ "", // Build date
+ "", // Model
+ "", // Part number
+ "", // Serial number
+ "", // Version
+ false, // Present
+ false) // Functional
+ {
+ phosphor::snmp::agent::make_oid(
+ _presentOid, ".1.3.6.1.4.1.49769.4.1.%lu.\"%s\"",
+ COLUMN_SILAINVENTORY_PRESENT, name.c_str());
+ phosphor::snmp::agent::make_oid(
+ _functionalOid, ".1.3.6.1.4.1.49769.4.1.%lu.\"%s\"",
+ COLUMN_SILAINVENTORY_FUNCTIONAL, name.c_str());
+ }
+
+ void setFields(const fields_map_t& fields) override
+ {
+ bool isPresent = std::get<FIELD_INVENTORY_PRESENT>(data);
+ bool isFunctional = std::get<FIELD_INVENTORY_FUNCTIONAL>(data);
+
+ setField<FIELD_INVENTORY_PRETTY_NAME>(fields, "PrettyName");
+ setField<FIELD_INVENTORY_MANUFACTURER>(fields, "Manufacturer");
+ setField<FIELD_INVENTORY_BUILD_DATE>(fields, "BuildDate");
+ setField<FIELD_INVENTORY_MODEL>(fields, "Model");
+ setField<FIELD_INVENTORY_PART_NUMBER>(fields, "PartNumber");
+ setField<FIELD_INVENTORY_SERIAL_NUMBER>(fields, "SerialNumber");
+ setField<FIELD_INVENTORY_VERSION>(fields, "Version");
+ setField<FIELD_INVENTORY_PRESENT>(fields, "Present");
+ setField<FIELD_INVENTORY_FUNCTIONAL>(fields, "Functional");
+
+ if (isPresent != std::get<FIELD_INVENTORY_PRESENT>(data) ||
+ isFunctional != std::get<FIELD_INVENTORY_FUNCTIONAL>(data))
+ {
+ DEBUGMSGTL(("sila:inventory",
+ "Inventory item '%s' at '%s': "
+ "present %d -> %d, function %d -> %d\n",
+ std::get<FIELD_INVENTORY_PRETTY_NAME>(data).c_str(),
+ name.c_str(), isPresent,
+ std::get<FIELD_INVENTORY_PRESENT>(data), isFunctional,
+ std::get<FIELD_INVENTORY_FUNCTIONAL>(data)));
+
+ phosphor::snmp::agent::Trap trap(NOTIFY_OID);
+ trap.add_field(_presentOid,
+ std::get<FIELD_INVENTORY_PRESENT>(data));
+ trap.add_field(_functionalOid,
+ std::get<FIELD_INVENTORY_FUNCTIONAL>(data));
+ trap.send();
+ }
+ }
+
+ void get_snmp_reply(netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* request) const override
+ {
+ using namespace phosphor::snmp::agent;
+
+ netsnmp_table_request_info* tinfo = netsnmp_extract_table_info(request);
+
+ switch (tinfo->colnum)
+ {
+ case COLUMN_SILAINVENTORY_PATH:
+ VariableList::set(request->requestvb, name);
+ break;
+
+ case COLUMN_SILAINVENTORY_NAME:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_INVENTORY_PRETTY_NAME>(data));
+ break;
+
+ case COLUMN_SILAINVENTORY_MANUFACTURER:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_INVENTORY_MANUFACTURER>(data));
+ break;
+
+ case COLUMN_SILAINVENTORY_BUILD_DATE:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_INVENTORY_BUILD_DATE>(data));
+ break;
+
+ case COLUMN_SILAINVENTORY_MODEL:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_INVENTORY_MODEL>(data));
+ break;
+
+ case COLUMN_SILAINVENTORY_PART_NUMBER:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_INVENTORY_PART_NUMBER>(data));
+ break;
+
+ case COLUMN_SILAINVENTORY_SERIAL_NUMBER:
+ VariableList::set(
+ request->requestvb,
+ std::get<FIELD_INVENTORY_SERIAL_NUMBER>(data));
+ break;
+
+ case COLUMN_SILAINVENTORY_VERSION:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_INVENTORY_VERSION>(data));
+ break;
+
+ case COLUMN_SILAINVENTORY_PRESENT:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_INVENTORY_PRESENT>(data));
+ break;
+
+ case COLUMN_SILAINVENTORY_FUNCTIONAL:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_INVENTORY_FUNCTIONAL>(data));
+ break;
+
+ default:
+ netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
+ break;
+ }
+ }
+
+ void onCreate() override
+ {
+ DEBUGMSGTL(
+ ("sila:inventory", "Inventory item '%s' added.\n", name.c_str()));
+ }
+
+ void onDestroy() override
+ {
+ DEBUGMSGTL(("sila:inventory", "Inventory item '%s' removed.\n",
+ name.c_str()));
+ if (std::get<FIELD_INVENTORY_PRESENT>(data) ||
+ std::get<FIELD_INVENTORY_FUNCTIONAL>(data))
+ {
+ phosphor::snmp::agent::Trap trap(NOTIFY_OID);
+ trap.add_field(_presentOid, false);
+ trap.add_field(_functionalOid, false);
+ trap.send();
+ }
+ }
+
+ OID _presentOid;
+ OID _functionalOid;
+};
+
+static phosphor::snmp::agent::OID inventoryTableOid = SILA_OID(4);
+
+static phosphor::snmp::data::Table<InventoryItem>
+ inventoryTable("/xyz/openbmc_project/inventory",
+ {
+ "xyz.openbmc_project.Inventory.Item",
+ "xyz.openbmc_project.Inventory.Decorator.Asset",
+ "xyz.openbmc_project.Inventory.Decorator.Revision",
+ "xyz.openbmc_project.State.Decorator.OperationalStatus",
+ });
+
+/**
+ * @brief Initialize inventory table
+ */
+void init()
+{
+ DEBUGMSGTL(("sila:init", "Initialize silaInventoryTable\n"));
+
+ inventoryTable.update();
+ inventoryTable.init_mib("silaInventoryTable", inventoryTableOid.data(),
+ inventoryTableOid.size(),
+ InventoryItem::COLUMN_SILAINVENTORY_PATH,
+ InventoryItem::COLUMN_SILAINVENTORY_FUNCTIONAL);
+}
+
+/**
+ * @brief Deinitialize inventory table
+ */
+void destroy()
+{
+ DEBUGMSGTL(("sila:shutdown", "Deinitialize silaInventoryTable\n"));
+ unregister_mib(inventoryTableOid.data(), inventoryTableOid.size());
+}
+
+} // namespace inventory
+} // namespace sila
diff --git a/agent/sila/inventory.hpp b/agent/sila/inventory.hpp
new file mode 100644
index 0000000..ad8a3cc
--- /dev/null
+++ b/agent/sila/inventory.hpp
@@ -0,0 +1,32 @@
+/**
+ * @brief SILA inventory table implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+namespace sila
+{
+namespace inventory
+{
+
+void init();
+void destroy();
+
+} // namespace inventory
+} // namespace sila
diff --git a/agent/sila/powerstate.cpp b/agent/sila/powerstate.cpp
new file mode 100644
index 0000000..d82ad23
--- /dev/null
+++ b/agent/sila/powerstate.cpp
@@ -0,0 +1,140 @@
+/**
+ * @brief SILA host power state implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include "data/scalar.hpp"
+#include "sila/sila_oid.hpp"
+#include "tracing.hpp"
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+
+#include "snmptrap.hpp"
+
+namespace sila
+{
+namespace host
+{
+namespace power
+{
+namespace state
+{
+using OID = phosphor::snmp::agent::OID;
+
+static const OID state_oid = SILA_OID(1, 1);
+static const OID notify_oid = SILA_OID(0, 1);
+
+// Values specified in the MIB file.
+constexpr int UNKNOWN = -1;
+constexpr int OFF = 0;
+constexpr int ON = 1;
+
+struct State : public phosphor::snmp::data::Scalar<std::string>
+{
+ static constexpr auto IFACE = "xyz.openbmc_project.State.Host";
+ static constexpr auto PATH = "/xyz/openbmc_project/state/host0";
+
+ State() :
+ phosphor::snmp::data::Scalar<std::string>(PATH, IFACE,
+ "CurrentHostState", "")
+ {
+ }
+
+ void setValue(value_t& var)
+ {
+ auto prev = getValue();
+ phosphor::snmp::data::Scalar<std::string>::setValue(var);
+
+ auto curr = getValue();
+ if (prev != curr)
+ {
+ DEBUGMSGTL(("sila:powerstate",
+ "Host power state changed: '%s' -> '%s'\n",
+ prev.c_str(), curr.c_str()));
+
+ phosphor::snmp::agent::Trap trap(notify_oid);
+ trap.add_field(state_oid, toSNMPValue());
+ trap.send();
+ }
+ }
+
+ int toSNMPValue() const
+ {
+ auto value = getValue();
+ if (value == "xyz.openbmc_project.State.Host.HostState.Running")
+ {
+ return ON;
+ }
+ if (value == "xyz.openbmc_project.State.Host.HostState.Off")
+ {
+ return OFF;
+ }
+
+ return UNKNOWN;
+ }
+};
+
+static State state;
+
+/** @brief Handler for snmp requests */
+static int State_snmp_handler(netsnmp_mib_handler* /*handler*/,
+ netsnmp_handler_registration* /*reginfo*/,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ DEBUGMSGTL(("sila:handle",
+ "Processing request (%d) for silaHostPowerState\n",
+ reqinfo->mode));
+
+ switch (reqinfo->mode)
+ {
+ case MODE_GET:
+ for (netsnmp_request_info* request = requests; request;
+ request = request->next)
+ {
+ phosphor::snmp::agent::VariableList::set(request->requestvb,
+ state.toSNMPValue());
+ }
+ break;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+void init()
+{
+ DEBUGMSGTL(("sila:init", "Initialize silaHostPowerState\n"));
+
+ state.update();
+
+ netsnmp_register_read_only_instance(netsnmp_create_handler_registration(
+ "silaHostPowerState", State_snmp_handler, state_oid.data(),
+ state_oid.size(), HANDLER_CAN_RONLY));
+}
+void destroy()
+{
+ DEBUGMSGTL(("sila:shutdown", "destroy silaHostPowerState\n"));
+ unregister_mib(const_cast<oid*>(state_oid.data()), state_oid.size());
+}
+
+} // namespace state
+} // namespace power
+} // namespace host
+} // namespace sila
diff --git a/agent/sila/powerstate.hpp b/agent/sila/powerstate.hpp
new file mode 100644
index 0000000..937786d
--- /dev/null
+++ b/agent/sila/powerstate.hpp
@@ -0,0 +1,38 @@
+/**
+ * @brief SILA host power state implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+namespace sila
+{
+namespace host
+{
+namespace power
+{
+namespace state
+{
+
+void init();
+void destroy();
+
+} // namespace state
+} // namespace power
+} // namespace host
+} // namespace sila
diff --git a/agent/sila/sensors.cpp b/agent/sila/sensors.cpp
new file mode 100644
index 0000000..5cb8392
--- /dev/null
+++ b/agent/sila/sensors.cpp
@@ -0,0 +1,385 @@
+/**
+ * @brief SILA sensors tables implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "tracing.hpp"
+#include "data/table.hpp"
+#include "data/table/item.hpp"
+#include "sila/sila_oid.hpp"
+#include "snmptrap.hpp"
+
+#include <cmath>
+
+namespace sila
+{
+namespace sensors
+{
+/**
+ * @brief Sensor implementation.
+ */
+struct Sensor
+ : public phosphor::snmp::data::table::Item<double, double, bool, double,
+ bool, double, bool, double, bool>
+{
+ /**
+ * @brief State codes like in MIB file.
+ */
+ enum state_t
+ {
+ E_DISABLED = 0,
+ E_NORMAL,
+ E_WARNING_LOW,
+ E_WARNING_HIGH,
+ E_CRITICAL_LOW,
+ E_CRITICAL_HIGH,
+ };
+
+ // SNMP table columns
+ enum Columns
+ {
+ COLUMN_SILASENSOR_NAME = 1,
+ COLUMN_SILASENSOR_VALUE,
+ COLUMN_SILASENSOR_WARNLOW,
+ COLUMN_SILASENSOR_WARNHIGH,
+ COLUMN_SILASENSOR_CRITLOW,
+ COLUMN_SILASENSOR_CRITHIGH,
+ COLUMN_SILASENSOR_STATE,
+ };
+
+ // Indexes of fields in tuple
+ enum Fields
+ {
+ FIELD_SENSOR_VALUE = 0,
+ FIELD_SENSOR_WARNLOW,
+ FIELD_SENSOR_WARNLOW_ALARM,
+ FIELD_SENSOR_WARNHI,
+ FIELD_SENSOR_WARNHI_ALARM,
+ FIELD_SENSOR_CRITLOW,
+ FIELD_SENSOR_CRITLOW_ALARM,
+ FIELD_SENSOR_CRITHI,
+ FIELD_SENSOR_CRITHI_ALARM,
+ };
+
+ // Sensor types (first letter in sensors folder name)
+ enum Types
+ {
+ ST_TEMPERATURE = 't', // temperature
+ ST_VOLTAGE = 'v', // voltage
+ ST_TACHOMETER = 'f', // fan_tach
+ ST_CURRENT = 'c', // current
+ ST_POWER = 'p', // power
+ };
+
+ /**
+ * @brief Object contructor.
+ */
+ Sensor(const std::string& folder, const std::string& name) :
+ phosphor::snmp::data::table::Item<double, double, bool, double, bool,
+ double, bool, double, bool>(
+ folder, name,
+ .0, // Value
+ .0, false, // WarningLow
+ .0, false, // WarningHigh
+ .0, false, // CriticalLow
+ .0, false) // CriticalHigh
+ {
+ auto n = folder.rfind('/');
+ if (n != std::string::npos)
+ {
+ // Prepare for send traps
+ switch (folder[n + 1])
+ {
+ case ST_TEMPERATURE:
+ _notifyOid.assign(SILA_OID(0, 2));
+ break;
+
+ case ST_VOLTAGE:
+ _notifyOid.assign(SILA_OID(0, 3));
+ break;
+
+ case ST_TACHOMETER:
+ _notifyOid.assign(SILA_OID(0, 4));
+ break;
+
+ case ST_CURRENT:
+ _notifyOid.assign(SILA_OID(0, 5));
+ break;
+
+ case ST_POWER:
+ _notifyOid.assign(SILA_OID(0, 6));
+ break;
+ }
+
+ // Correct scale power
+ // Required for TEXTUAL-CONVENTION in SILA-MIB.txt
+ switch (folder[n + 1])
+ {
+ case ST_TACHOMETER:
+ _power = 0;
+ break;
+ }
+ }
+
+ if (!_notifyOid.empty())
+ {
+ phosphor::snmp::agent::make_oid(
+ _stateOid, ".1.3.6.1.4.1.49769.1.%lu.1.7.\"%s\"",
+ _notifyOid.back(), name.c_str());
+ }
+ }
+
+ /**
+ * @brief Update fields with new values recieved from DBus.
+ */
+ void setFields(const fields_map_t& fields) override
+ {
+ auto prevValue = getValue<FIELD_SENSOR_VALUE>();
+ auto prevState = getState();
+
+ setField<FIELD_SENSOR_VALUE>(fields, "Value");
+ setField<FIELD_SENSOR_WARNLOW>(fields, "WarningLow");
+ setField<FIELD_SENSOR_WARNLOW_ALARM>(fields, "WarningAlarmLow");
+ setField<FIELD_SENSOR_WARNHI>(fields, "WarningHigh");
+ setField<FIELD_SENSOR_WARNHI_ALARM>(fields, "WarningAlarmHigh");
+ setField<FIELD_SENSOR_CRITLOW>(fields, "CriticalLow");
+ setField<FIELD_SENSOR_CRITLOW_ALARM>(fields, "CriticalAlarmLow");
+ setField<FIELD_SENSOR_CRITHI>(fields, "CriticalHigh");
+ setField<FIELD_SENSOR_CRITHI_ALARM>(fields, "CriticalAlarmHigh");
+
+ auto lastState = getState();
+
+ if (prevValue != getValue<FIELD_SENSOR_VALUE>() ||
+ prevState != lastState)
+ {
+ DEBUGMSGTL(("sila:sensors",
+ "Sensor '%s' changed: %d -> %d, state: %d -> %d\n",
+ name.c_str(), prevValue, getValue<FIELD_SENSOR_VALUE>(),
+ prevState, lastState));
+ }
+
+ if (prevState != lastState)
+ {
+ send_notify(lastState);
+ }
+ }
+
+ /**
+ * @brief Called when sensor added to table.
+ */
+ void onCreate() override
+ {
+ DEBUGMSGTL(("sila:sensors", "Sensor '%s' added, state=%d\n",
+ name.c_str(), getState()));
+ send_notify(E_NORMAL);
+ }
+
+ /**
+ * @brief Called when sensor removed from table.
+ */
+ void onDestroy() override
+ {
+ DEBUGMSGTL(("sila:sensors", "Sensor '%s' removed, state=%d\n",
+ name.c_str(), getState()));
+ send_notify(E_DISABLED);
+ }
+
+ /**
+ * @brief Send snmptrap about changed state.
+ *
+ * @param state - state value for sent with trap.
+ */
+ void send_notify(state_t state)
+ {
+ if (!_notifyOid.empty() && !_stateOid.empty())
+ {
+ phosphor::snmp::agent::Trap trap(_notifyOid);
+ trap.add_field(_stateOid.data(), _stateOid.size(), state);
+ trap.send();
+ }
+ else
+ {
+ TRACE_ERROR("Notify is unsupported for sensor '%s'\n",
+ name.c_str());
+ }
+ }
+
+ /**
+ * @brief Get current state.
+ */
+ state_t getState() const
+ {
+ if (std::get<FIELD_SENSOR_CRITHI_ALARM>(data))
+ {
+ return E_CRITICAL_HIGH;
+ }
+ else if (std::get<FIELD_SENSOR_WARNHI_ALARM>(data))
+ {
+ return E_WARNING_HIGH;
+ }
+ else if (std::get<FIELD_SENSOR_WARNLOW_ALARM>(data))
+ {
+ return E_WARNING_LOW;
+ }
+ else if (std::get<FIELD_SENSOR_CRITLOW_ALARM>(data))
+ {
+ return E_CRITICAL_LOW;
+ }
+
+ return E_NORMAL;
+ }
+
+ /**
+ * @brief snmp request handler.
+ */
+ void get_snmp_reply(netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* request) const override
+ {
+ using namespace phosphor::snmp::agent;
+
+ netsnmp_table_request_info* tinfo = netsnmp_extract_table_info(request);
+
+ switch (tinfo->colnum)
+ {
+ case COLUMN_SILASENSOR_NAME:
+ VariableList::set(request->requestvb, name);
+ break;
+
+ case COLUMN_SILASENSOR_VALUE:
+ VariableList::set(request->requestvb,
+ getValue<FIELD_SENSOR_VALUE>());
+ break;
+
+ case COLUMN_SILASENSOR_WARNLOW:
+ VariableList::set(request->requestvb,
+ getValue<FIELD_SENSOR_WARNLOW>());
+ break;
+
+ case COLUMN_SILASENSOR_WARNHIGH:
+ VariableList::set(request->requestvb,
+ getValue<FIELD_SENSOR_WARNHI>());
+ break;
+
+ case COLUMN_SILASENSOR_CRITLOW:
+ VariableList::set(request->requestvb,
+ getValue<FIELD_SENSOR_CRITLOW>());
+ break;
+
+ case COLUMN_SILASENSOR_CRITHIGH:
+ VariableList::set(request->requestvb,
+ getValue<FIELD_SENSOR_CRITHI>());
+ break;
+
+ case COLUMN_SILASENSOR_STATE:
+ VariableList::set(request->requestvb, getState());
+ break;
+
+ default:
+ netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
+ break;
+ }
+ }
+
+ /**
+ * @brief Scale and round sensors value.
+ */
+ template <size_t Idx> int getValue() const
+ {
+ return static_cast<int>(
+ std::round(std::get<Idx>(data) * powf(10.f, _power)));
+ }
+
+ std::vector<oid> _notifyOid;
+ std::vector<oid> _stateOid;
+ int _power = 3;
+};
+
+struct SensorsTable : public phosphor::snmp::data::Table<Sensor>
+{
+ using OID = std::vector<oid>;
+
+ SensorsTable(const std::string& folder, const std::string& tableName,
+ const OID& tableOID) :
+ phosphor::snmp::data::Table<Sensor>(
+ "/xyz/openbmc_project/sensors/" + folder,
+ {
+ "xyz.openbmc_project.Sensor.Value",
+ "xyz.openbmc_project.Sensor.Threshold.Warning",
+ "xyz.openbmc_project.Sensor.Threshold.Critical",
+ "xyz.openbmc_project.Sensor.Threshold.Fatal",
+ }),
+ tableName(tableName), tableOID(tableOID)
+
+ {
+ }
+
+ std::string tableName;
+ OID tableOID;
+};
+
+static std::array<SensorsTable, 5> sensors = {
+ SensorsTable{"temperature", "silaTempSensorsTable", SILA_OID(1, 2)},
+ SensorsTable{"voltage", "silaVoltSensorsTable", SILA_OID(1, 3)},
+ SensorsTable{"fan_tach", "silaTachSensorsTable", SILA_OID(1, 4)},
+ SensorsTable{"current", "silaCurrSensorsTable", SILA_OID(1, 5)},
+ SensorsTable{"power", "silaPowerSensorsTable", SILA_OID(1, 6)},
+};
+
+/**
+ * @brief Update all sensors.
+ */
+void update()
+{
+ for (auto& s : sensors)
+ {
+ s.update();
+ }
+}
+
+/**
+ * @brief Initialize sensors.
+ */
+void init()
+{
+ DEBUGMSGTL(("sila:init", "Initialize silaSensors\n"));
+
+ for (auto& s : sensors)
+ {
+ s.init_mib(s.tableName.c_str(), s.tableOID.data(), s.tableOID.size(),
+ Sensor::COLUMN_SILASENSOR_NAME,
+ Sensor::COLUMN_SILASENSOR_STATE);
+ s.update();
+ }
+}
+
+/**
+ * @brief Deinitialize sensors.
+ */
+void destroy()
+{
+ DEBUGMSGTL(("sila:shutdown", "Destroy silaSensors\n"));
+
+ for (auto& s : sensors)
+ {
+ unregister_mib(s.tableOID.data(), s.tableOID.size());
+ }
+}
+
+} // namespace sensors
+} // namespace sila
diff --git a/agent/sila/sensors.hpp b/agent/sila/sensors.hpp
new file mode 100644
index 0000000..6ce123c
--- /dev/null
+++ b/agent/sila/sensors.hpp
@@ -0,0 +1,33 @@
+/**
+ * @brief SILA sensors tables implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+namespace sila
+{
+namespace sensors
+{
+
+void init();
+void update();
+void destroy();
+
+} // namespace sensors
+} // namespace sila
diff --git a/agent/sila/sila_oid.hpp b/agent/sila/sila_oid.hpp
new file mode 100644
index 0000000..033a7e7
--- /dev/null
+++ b/agent/sila/sila_oid.hpp
@@ -0,0 +1,27 @@
+/**
+ * @brief SILA OID helper
+ *
+ * This file is part of yadro-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#pragma once
+
+#define SILA_OID(args...) \
+ { \
+ 1, 3, 6, 1, 4, 1, 49769, ##args \
+ }
diff --git a/agent/sila/software.cpp b/agent/sila/software.cpp
new file mode 100644
index 0000000..bed1130
--- /dev/null
+++ b/agent/sila/software.cpp
@@ -0,0 +1,225 @@
+/**
+ * @brief SILA software table implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "tracing.hpp"
+#include "data/table.hpp"
+#include "data/table/item.hpp"
+#include "data/enums.hpp"
+#include "sila/sila_oid.hpp"
+#include "snmpvars.hpp"
+
+#define INVALID_ENUM_VALUE 0xFF
+
+namespace sila
+{
+namespace software
+{
+
+/**
+ * @brief DBus enum to byte converters.
+ */
+const phosphor::snmp::data::DBusEnum<uint8_t>
+ purpose = {"xyz.openbmc_project.Software.Version.VersionPurpose",
+ {
+ {"Unknown", 0},
+ {"Other", 1},
+ {"System", 2},
+ {"BMC", 3},
+ {"Host", 4},
+ },
+ INVALID_ENUM_VALUE},
+
+ activation = {"xyz.openbmc_project.Software.Activation.Activations",
+ {
+ {"NotReady", 0},
+ {"Invalid", 1},
+ {"Ready", 2},
+ {"Activating", 3},
+ {"Active", 4},
+ {"Failed", 5},
+ },
+ INVALID_ENUM_VALUE};
+
+/**
+ * @brief Software item implementation.
+ */
+struct Software : public phosphor::snmp::data::table::Item<std::string, uint8_t,
+ uint8_t, uint8_t>
+{
+ // Indexes of fields in tuple
+ enum Fields
+ {
+ FIELD_SOFTWARE_VERSION = 0,
+ FIELD_SOFTWARE_PURPOSE,
+ FIELD_SOFTWARE_ACTIVATION,
+ FIELD_SOFTWARE_PRIORITY,
+ };
+
+ /**
+ * @brief Object constructor.
+ */
+ Software(const std::string& folder, const std::string& name) :
+ phosphor::snmp::data::table::Item<std::string, uint8_t, uint8_t,
+ uint8_t>(
+ folder, name,
+ "", // Version
+ INVALID_ENUM_VALUE, // Purpose
+ INVALID_ENUM_VALUE, // Activation
+ INVALID_ENUM_VALUE) // Priority
+ {
+ }
+
+ /**
+ * @brief Set field value with DBus enum converter.
+ *
+ * @tparam Idx - Index of field
+ * @param fields - DBus fields map
+ * @param field - Name of field in DBus
+ * @param enumcvt - Enum converter
+ */
+ template <size_t Idx>
+ void setFieldEnum(const fields_map_t& fields, const char* field,
+ const phosphor::snmp::data::DBusEnum<uint8_t>& enumcvt)
+ {
+ if (fields.find(field) != fields.end() &&
+ std::holds_alternative<std::string>(fields.at(field)))
+ {
+ std::get<Idx>(data) =
+ enumcvt.get(std::get<std::string>(fields.at(field)));
+ }
+ }
+
+ /**
+ * @brief Update fields with new values recieved from DBus.
+ */
+ void setFields(const fields_map_t& fields) override
+ {
+ uint8_t prevActivation = std::get<FIELD_SOFTWARE_ACTIVATION>(data),
+ prevPriority = std::get<FIELD_SOFTWARE_PRIORITY>(data);
+
+ setField<FIELD_SOFTWARE_VERSION>(fields, "Version");
+ setFieldEnum<FIELD_SOFTWARE_PURPOSE>(fields, "Purpose", purpose);
+ setFieldEnum<FIELD_SOFTWARE_ACTIVATION>(fields, "Activation",
+ activation);
+ setField<FIELD_SOFTWARE_PRIORITY>(fields, "Priority");
+
+ if (prevActivation != std::get<FIELD_SOFTWARE_ACTIVATION>(data) ||
+ prevPriority != std::get<FIELD_SOFTWARE_PRIORITY>(data))
+ {
+ DEBUGMSGTL(("sila:software",
+ "Software '%s' version='%s', purpose=%d changed: "
+ "activation %d -> %d, priority %d -> %d\n",
+ name.c_str(),
+ std::get<FIELD_SOFTWARE_VERSION>(data).c_str(),
+ std::get<FIELD_SOFTWARE_PURPOSE>(data), prevActivation,
+ std::get<FIELD_SOFTWARE_ACTIVATION>(data), prevPriority,
+ std::get<FIELD_SOFTWARE_PRIORITY>(data)));
+ }
+ }
+
+ enum Columns
+ {
+ COLUMN_SILASOFTWARE_HASH = 1,
+ COLUMN_SILASOFTWARE_VERSION = 2,
+ COLUMN_SILASOFTWARE_PURPOSE = 3,
+ COLUMN_SILASOFTWARE_ACTIVATION = 4,
+ COLUMN_SILASOFTWARE_PRIORITY = 5,
+ };
+
+ /**
+ * @brief snmp request handler.
+ */
+ void get_snmp_reply(netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* request) const override
+ {
+ using namespace phosphor::snmp::agent;
+
+ netsnmp_table_request_info* tinfo = netsnmp_extract_table_info(request);
+
+ switch (tinfo->colnum)
+ {
+ case COLUMN_SILASOFTWARE_HASH:
+ VariableList::set(request->requestvb, name);
+ break;
+
+ case COLUMN_SILASOFTWARE_VERSION:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_SOFTWARE_VERSION>(data));
+ break;
+
+ case COLUMN_SILASOFTWARE_PURPOSE:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_SOFTWARE_PURPOSE>(data));
+ break;
+
+ case COLUMN_SILASOFTWARE_ACTIVATION:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_SOFTWARE_ACTIVATION>(data));
+ break;
+
+ case COLUMN_SILASOFTWARE_PRIORITY:
+ VariableList::set(request->requestvb,
+ std::get<FIELD_SOFTWARE_PRIORITY>(data));
+ break;
+
+ default:
+ netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
+ break;
+ }
+ }
+};
+
+constexpr oid softwareOid[] = SILA_OID(5);
+constexpr auto SOFTWARE_FOLDER = "/xyz/openbmc_project/software";
+
+static phosphor::snmp::data::Table<Software>
+ softwareTable("/xyz/openbmc_project/software",
+ {
+ "xyz.openbmc_project.Software.Version",
+ "xyz.openbmc_project.Software.Activation",
+ "xyz.openbmc_project.Software.RedundancyPriority",
+ });
+
+/**
+ * @brief Initialize software table
+ */
+void init()
+{
+ DEBUGMSGTL(("sila:init", "Initialize silaSoftwareTable\n"));
+
+ softwareTable.update();
+ softwareTable.init_mib("silaSoftwareTable", softwareOid,
+ OID_LENGTH(softwareOid),
+ Software::COLUMN_SILASOFTWARE_HASH,
+ Software::COLUMN_SILASOFTWARE_PRIORITY);
+}
+
+/**
+ * @brief Deinitialize software table
+ */
+void destroy()
+{
+ DEBUGMSGTL(("sila:shutdown", "Deinitialize silaSoftwareTable\n"));
+ unregister_mib(const_cast<oid*>(softwareOid), OID_LENGTH(softwareOid));
+}
+
+} // namespace software
+} // namespace sila
diff --git a/agent/sila/software.hpp b/agent/sila/software.hpp
new file mode 100644
index 0000000..3cd1d87
--- /dev/null
+++ b/agent/sila/software.hpp
@@ -0,0 +1,32 @@
+/**
+ * @brief SILA software tables implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+namespace sila
+{
+namespace software
+{
+
+void init();
+void destroy();
+
+} // namespace software
+} // namespace sila
diff --git a/agent/snmp.cpp b/agent/snmp.cpp
new file mode 100644
index 0000000..b6a2881
--- /dev/null
+++ b/agent/snmp.cpp
@@ -0,0 +1,140 @@
+/**
+ * @brief SNMP Agent implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include "config.h"
+#include "tracing.hpp"
+
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/event.hpp>
+#include <sdeventplus/source/io.hpp>
+#include <sdeventplus/source/time.hpp>
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+
+#include <map>
+
+constexpr auto clockId = sdeventplus::ClockId::Monotonic;
+using Clock = sdeventplus::Clock<clockId>;
+using Time = sdeventplus::source::Time<clockId>;
+
+// list of snmp file descriptors attached to sd_event loop.
+static std::map<int, sdeventplus::source::IO> snmp_fds;
+
+/** @brief Called when snmp file descriptors have data for reading. */
+static void sdevent_snmp_read(sdeventplus::source::IO& /*source*/, int fd,
+ uint32_t /*revents*/)
+{
+ DEBUGMSGTL(("snmpagent:handle", "Handle fd=%d\n", fd));
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(fd, &fdset);
+ snmp_read(&fdset);
+}
+
+/** @brief Refresh list of snmp file descriptors. */
+static void sdevent_snmp_update(const sdeventplus::Event& event)
+{
+ netsnmp_check_outstanding_agent_requests();
+
+ int maxfd = 0;
+ int is_blocked = 1;
+ fd_set fdset;
+ timeval timeout;
+
+ FD_ZERO(&fdset);
+ snmp_select_info(&maxfd, &fdset, &timeout, &is_blocked);
+
+ static Time snmpTimer(event, {}, std::chrono::microseconds{1},
+ [](Time&, Time::TimePoint) {
+ DEBUGMSGTL(("snmpagent:handle", "Time out\n"));
+ snmp_timeout();
+ run_alarms();
+ });
+
+ if (timeout.tv_sec || timeout.tv_usec)
+ {
+ snmpTimer.set_time(Clock(event).now() +
+ std::chrono::seconds{timeout.tv_sec} +
+ std::chrono::microseconds{timeout.tv_usec});
+ snmpTimer.set_enabled(sdeventplus::source::Enabled::OneShot);
+ }
+
+ // We need to untrack any event whose FD is not in `fdset` anymore.
+ for (auto it = snmp_fds.begin(); it != snmp_fds.end();)
+ {
+ if (it->first >= maxfd || (!FD_ISSET(it->first, &fdset)))
+ {
+ DEBUGMSGTL(
+ ("snmpagent:handle", "Remove fd=%d from set.\n", it->first));
+ it->second.set_enabled(sdeventplus::source::Enabled::Off);
+ it = snmp_fds.erase(it);
+ }
+ else
+ {
+ FD_CLR(it->first, &fdset);
+ ++it;
+ }
+ }
+
+ // Invariant: FD in `fdset` are not in `snmp_fds`
+ for (int fd = 0; fd < maxfd; ++fd)
+ {
+ if (FD_ISSET(fd, &fdset))
+ {
+ DEBUGMSGTL(("snmpagent:handle", "Add fd=%d to set.\n", fd));
+ snmp_fds.emplace(fd, sdeventplus::source::IO(event, fd, EPOLLIN,
+ sdevent_snmp_read));
+ }
+ }
+}
+
+/** @brief Initialize snmp agent */
+void snmpagent_init(const sdeventplus::Event& event)
+{
+ // make us a agentx client
+ netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1);
+
+ // initialize tcpip, if necessary
+ SOCK_STARTUP;
+
+ // initialize the agent library
+ init_agent(PACKAGE_NAME);
+
+ // We will be used to read <PACKAGE_NAME>.conf files.
+ init_snmp(PACKAGE_NAME);
+
+ // This is run before the event loop will sleep and wait for new events.
+ static sdeventplus::source::Post post(
+ event, [](sdeventplus::source::EventBase& source) {
+ sdevent_snmp_update(source.get_event());
+ });
+
+ sdevent_snmp_update(event);
+}
+
+/** @brief Deinitialize snmp agen */
+void snmpagent_destroy()
+{
+ snmp_shutdown(PACKAGE_NAME);
+ SOCK_CLEANUP;
+}
diff --git a/agent/snmp.hpp b/agent/snmp.hpp
new file mode 100644
index 0000000..8332850
--- /dev/null
+++ b/agent/snmp.hpp
@@ -0,0 +1,26 @@
+/**
+ * @brief SNMP Agent implementation.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include <sdeventplus/event.hpp>
+
+void snmpagent_init(const sdeventplus::Event& event);
+void snmpagent_destroy();
diff --git a/agent/snmp_oid.hpp b/agent/snmp_oid.hpp
new file mode 100644
index 0000000..5faf623
--- /dev/null
+++ b/agent/snmp_oid.hpp
@@ -0,0 +1,83 @@
+/**
+ * @brief SNMP OID manipulation helper.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include <net-snmp/net-snmp-includes.h>
+#include <vector>
+#include <cstdarg>
+
+namespace phosphor
+{
+namespace snmp
+{
+namespace agent
+{
+
+using OID = std::vector<oid>;
+
+/**
+ * @brief Parse OID present as string
+ *
+ * @param oid_value - Container for parsed oid
+ * @param fmt - Format string for oid presence
+ * @param ... - Additional args for oid string
+ */
+inline void make_oid(OID& oid_value, const char* fmt, ...)
+{
+ va_list ap;
+ std::vector<char> oid_str;
+
+ // Calculate required size of oid_strfer
+ va_start(ap, fmt);
+ int n = vsnprintf(nullptr, 0, fmt, ap);
+ va_end(ap);
+
+ if (n > 0)
+ {
+ oid_str.resize(n + 1); // for terminating '\0'
+
+ // Create string oid
+ va_start(ap, fmt);
+ n = vsnprintf(oid_str.data(), oid_str.size(), fmt, ap);
+ va_end(ap);
+ }
+
+ if (n < 0)
+ {
+ oid_str.clear();
+ }
+
+ // Parse oid string
+ size_t oid_len = MAX_OID_LEN;
+ oid_value.resize(oid_len);
+ if (read_objid(oid_str.data(), oid_value.data(), &oid_len))
+ {
+ oid_value.resize(oid_len);
+ }
+ else
+ {
+ oid_value.clear();
+ }
+}
+
+} // namespace agent
+} // namespace snmp
+} // namespace phosphor
diff --git a/agent/snmptrap.hpp b/agent/snmptrap.hpp
new file mode 100644
index 0000000..59ef74b
--- /dev/null
+++ b/agent/snmptrap.hpp
@@ -0,0 +1,164 @@
+/**
+ * @brief SNMP Traps implementation
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include <net-snmp/net-snmp-includes.h>
+#include <array>
+#include "snmp_oid.hpp"
+#include "snmpvars.hpp"
+
+namespace phosphor
+{
+namespace snmp
+{
+namespace agent
+{
+
+namespace details
+{
+
+/**
+ * @brief unique_ptr functor to release an variable list reference.
+ */
+struct VariableListDeleter
+{
+ void operator()(netsnmp_variable_list* ptr) const
+ {
+ deleter(ptr);
+ }
+
+ decltype(&snmp_free_varbind) deleter = snmp_free_varbind;
+};
+
+/**
+ * @brief Alias 'VariableList' to a unique_ptr type for auto-release.
+ */
+using VariableList =
+ std::unique_ptr<netsnmp_variable_list, VariableListDeleter>;
+
+} // namespace details
+
+/*
+ * In the notification, we have to assign our notification OID to
+ * the snmpTrapOID.0 object. Here is it's defintion.
+ */
+constexpr std::array<oid, 11> SNMPTRAP_OID = {1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0};
+
+class Trap
+{
+ public:
+ /* Define all of the basic class operations:
+ * Not allowed:
+ * - Default constructor to avoid nullptrs.
+ * - Copy operations due to internal unique_ptr.
+ * Allowed:
+ * - Move operations.
+ * - Destructor.
+ */
+ Trap() = delete;
+ Trap(const Trap&) = delete;
+ Trap& operator=(const Trap&) = delete;
+ Trap(Trap&&) = default;
+ Trap& operator=(Trap&&) = default;
+ ~Trap() = default;
+
+ explicit Trap(const OID& trap_oid)
+ {
+ create_variable_list(trap_oid.data(), trap_oid.size());
+ }
+
+ Trap(const oid* trap_oid, size_t trap_oid_len)
+ {
+ create_variable_list(trap_oid, trap_oid_len);
+ }
+
+ template <typename T> void add_field(const OID& field_oid, T&& field_value)
+ {
+ add_field(field_oid.data(), field_oid.size(),
+ std::forward<T>(field_value));
+ }
+
+ void add_field(const oid* field_oid, size_t field_oid_len,
+ const std::string field_value)
+ {
+ DEBUGMSGTL(("snmpagent:trap", " Field OID: "));
+ DEBUGMSGOID(("snmpagent:trap", field_oid, field_oid_len));
+ DEBUGMSG(
+ ("snmpagent:trap", ", Value: STRING(%s)\n", field_value.c_str()));
+
+ VariableList::add(_vars.get(), field_oid, field_oid_len, field_value);
+ }
+
+ void add_field(const oid* field_oid, size_t field_oid_len, bool field_value)
+ {
+ DEBUGMSGTL(("snmpagent:trap", " Field OID: "));
+ DEBUGMSGOID(("snmpagent:trap", field_oid, field_oid_len));
+ DEBUGMSG(("snmpagent:trap", ", Value: BOOLEAN(%s)\n",
+ field_value ? "True" : "False"));
+
+ VariableList::add(_vars.get(), field_oid, field_oid_len, field_value);
+ }
+
+ template <typename T>
+ void add_field(const oid* field_oid, size_t field_oid_len, T&& field_value)
+ {
+ DEBUGMSGTL(("snmpagent:trap", " Field OID: "));
+ DEBUGMSGOID(("snmpagent:trap", field_oid, field_oid_len));
+ DEBUGMSG(("snmpagent:trap", ", Value: INTEGER(%d)\n", field_value));
+
+ VariableList::add(_vars.get(), field_oid, field_oid_len,
+ std::forward<T>(field_value));
+ }
+
+ void send() const
+ {
+ DEBUGMSGTL(("snmpagent:trap", "send trap\n"));
+ send_v2trap(_vars.get());
+ }
+
+ protected:
+ void create_variable_list(const oid* trap_oid, size_t trap_oid_len)
+ {
+ DEBUGMSGTL(("snmpagent:trap", "Trap OID: "));
+ DEBUGMSGOID(("snmpagent:trap", trap_oid, trap_oid_len));
+ DEBUGMSG(("snmpagent:trap", "\n"));
+
+ netsnmp_variable_list* vars = nullptr;
+
+ // add in the trap definition object
+ snmp_varlist_add_variable(&vars,
+ /* the snmpTrapOID.0 variable */
+ SNMPTRAP_OID.data(), SNMPTRAP_OID.size(),
+ /* value type is an OID */
+ ASN_OBJECT_ID,
+ /* value contents is our notification OID */
+ reinterpret_cast<const u_char*>(trap_oid),
+ /* size of notification OID in bytes */
+ trap_oid_len * sizeof(oid));
+ _vars.reset(vars);
+ }
+
+ details::VariableList _vars;
+};
+
+} // namespace agent
+} // namespace snmp
+} // namespace phosphor
diff --git a/agent/snmpvars.hpp b/agent/snmpvars.hpp
new file mode 100644
index 0000000..67cfa17
--- /dev/null
+++ b/agent/snmpvars.hpp
@@ -0,0 +1,121 @@
+/**
+ * @brief netsnmp_variable_list C++ wrapper.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+namespace phosphor
+{
+namespace snmp
+{
+namespace agent
+{
+
+/**
+ * @brief SNMP representation of boolean type.
+ */
+enum class TruthValue : int
+{
+ True = 1,
+ False = 2,
+};
+
+#define SNMPBOOL(value) \
+ static_cast<int>(value ? TruthValue::True : TruthValue::False)
+
+struct VariableList
+{
+ /**
+ * @brief Fill snmp field with string value.
+ */
+ static void set(netsnmp_variable_list* var, const std::string& value)
+ {
+ snmp_set_var_typed_value(var, ASN_OCTET_STR, value.c_str(),
+ value.length());
+ }
+
+ /**
+ * @brief Fill snmp field with boolean value.
+ */
+ static void set(netsnmp_variable_list* var, bool value)
+ {
+ snmp_set_var_typed_integer(var, ASN_INTEGER, SNMPBOOL(value));
+ }
+
+ /**
+ * @brief Fill snmp field with integral value.
+ */
+ template <typename T> static void set(netsnmp_variable_list* var, T&& value)
+ {
+ snmp_set_var_typed_integer(var, ASN_INTEGER, value);
+ }
+
+ /**
+ * @brief Add string as field into snmp variables list.
+ *
+ * @param vars - Pointer to snmp variables list
+ * @param field_oid - field OID
+ * @param field_oid_len - length of field OID
+ * @param field_value - field value
+ */
+ static void add(netsnmp_variable_list* vars, const oid* field_oid,
+ size_t field_oid_len, const std::string& field_value)
+ {
+ snmp_varlist_add_variable(
+ &vars, field_oid, field_oid_len, ASN_OCTET_STR,
+ reinterpret_cast<const u_char*>(field_value.data()),
+ field_value.length());
+ }
+
+ /**
+ * @brief Add boolean as field into snmp variables list.
+ *
+ * @param vars - Pointer to snmp variables list
+ * @param field_oid - field OID
+ * @param field_oid_len - length of field OID
+ * @param field_value - field value
+ */
+ static void add(netsnmp_variable_list* vars, const oid* field_oid,
+ size_t field_oid_len, bool field_value)
+ {
+ auto v = SNMPBOOL(field_value);
+ snmp_varlist_add_variable(&vars, field_oid, field_oid_len, ASN_INTEGER,
+ &v, sizeof(v));
+ }
+ /**
+ * @brief Add value of integral type as field into snmp variables list.
+ *
+ * @param vars - Pointer to snmp variables list
+ * @param field_oid - field OID
+ * @param field_oid_len - length of field OID
+ * @param field_value - field value
+ */
+ template <typename T>
+ static void add(netsnmp_variable_list* vars, const oid* field_oid,
+ size_t field_oid_len, T&& field_value)
+ {
+ snmp_varlist_add_variable(&vars, field_oid, field_oid_len, ASN_INTEGER,
+ reinterpret_cast<const u_char*>(&field_value),
+ sizeof(field_value));
+ }
+};
+
+} // namespace agent
+} // namespace snmp
+} // namespace phosphor
diff --git a/agent/tracing.hpp b/agent/tracing.hpp
new file mode 100644
index 0000000..cc9e1c8
--- /dev/null
+++ b/agent/tracing.hpp
@@ -0,0 +1,30 @@
+/**
+ * @brief TRACE_* macroses definitions.
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+
+#define TRACE_ERROR(fmt, ...) snmp_log(LOG_ERR, fmt, ##__VA_ARGS__)
+#define TRACE_WARNING(fmt, ...) snmp_log(LOG_WARNING, fmt, ##__VA_ARGS__)
+#define TRACE_NOTICE(fmt, ...) snmp_log(LOG_NOTICE, fmt, ##__VA_ARGS__)
+#define TRACE_INFO(fmt, ...) snmp_log(LOG_INFO, fmt, ##__VA_ARGS__)
+#define TRACE_DEBUG(fmt, ...) snmp_log(LOG_DEBUG, fmt, ##__VA_ARGS__)
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..5e9e7e5
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+PREFIX=$(readlink -f "${0%/*}")
+AUTOCONF_FILES="aclocal.m4 autom4te.cache compile \
+ config.guess config.sub configure depcomp \
+ install-sh ltmain.sh Makefile.in missing \
+ ar-lib config.h.in"
+
+TARGET=${PREFIX}/sila/.libs/sila.so
+
+case $1 in
+ clean)
+ cd ${PREFIX}
+ test -f Makefile && make maintainer-clean
+ for file in ${AUTOCONF_FILES}; do
+ find -name "$file" | xargs -r rm -rf
+ done
+ exit 0
+ ;;
+esac
+
+autoreconf --install ${PREFIX}
+echo 'Run: "./configure ${CONFIGURE_FLAGS} && make"'
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..c20c2e5
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,99 @@
+AC_PREREQ([2.69])
+AC_INIT([sila-snmp],
+ [m4_esyscmd_s([git describe --dirty --long --always])],
+ [https://github.com/SILA/obmc-sila-snmp])
+AC_LANG([C++])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign])
+AM_SILENT_RULES([yes])
+
+# Checks for programs
+AC_PROG_CXX
+AM_PROG_AR
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+AC_PROG_MKDIR_P
+
+# Checks for typedefs, structures and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_17([noext])
+AX_APPEND_COMPILE_FLAGS([-fpic -Wall -Werror], [CXXFLAGS])
+
+# Checks for libraries.
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus])
+AC_SUBST(SDBUSPLUS_CFLAGS)
+AC_SUBST(SDBUSPLUS_LIBS)
+
+PKG_CHECK_MODULES([SDEVENTPLUS], [sdeventplus])
+AC_SUBST(SDEVENTPLUS_CLFAGS)
+AC_SUBST(SDEVENTPLUS_LIBS)
+
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging])
+PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces])
+
+# Check for sdbus++
+AC_PATH_PROG([SDBUSPLUSPLUS], [sdbus++])
+AS_IF([test "x$SDBUSPLUSPLUS" == "x"],
+ AC_MSG_ERROR(["Requires sdbus++"]))
+
+AC_CHECK_HEADERS([net-snmp/net-snmp-config.h],,\
+ AC_MSG_ERROR(["Requires net-snmp headers"]))
+
+# Check for net-snmp-config
+AC_PATH_PROG([NETSNMP_CONFIG], [net-snmp-config])
+AS_IF([test "x$NETSNMP_CONFIG" == "x"],
+ AC_MSG_ERROR(["Requires net-snmp-config"]))
+
+NETSNMP_CFLAGS=`$NETSNMP_CONFIG --base-cflags`
+NETSNMP_AGENT_LIBS=`$NETSNMP_CONFIG --agent-libs`
+AC_SUBST([NETSNMP_CFLAGS])
+AC_SUBST([NETSNMP_AGENT_LIBS])
+
+# Checks for library functions
+LT_INIT # Required for systemd linking
+
+PKG_PROG_PKG_CONFIG
+AC_ARG_WITH([systemdsystemunitdir],
+ [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],
+ [],
+ [with_systemdsystemunitdir=auto]
+)
+AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"],
+ [def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
+ AS_IF([test "x$def_systemdsystemunitdir" = "x"],
+ [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
+ [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]
+ )
+ with_systemdsystemunitdir=no],
+ [with_systemdsystemunitdir="$def_systemdsystemunitdir"]
+ )]
+)
+AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
+ [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]
+)
+AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
+
+AC_ARG_ENABLE([agent],
+ AS_HELP_STRING([--disable-agent], [Disable sila-snmp-agent.]))
+AC_ARG_ENABLE([cfg-manager],
+ AS_HELP_STRING([--disable-cfg-manager], [Disable sila-snmp-cfg-manager.]))
+
+AM_CONDITIONAL([WANT_AGENT], [test "x$enable_agent" != "xno"])
+AM_CONDITIONAL([WANT_CFG_MANAGER], [test "x$enable_cfg_manager" != "xno"])
+
+AC_ARG_VAR(SYSTEMD_TARGET, "Target for starting this service")
+AS_IF([test "x$SYSTEMD_TARGET" = "x"], [SYSTEMD_TARGET="multi-user.target"])
+
+# Package specific checks.
+AS_IF([test "x$enable_agent" != "xno"], [
+ AC_CONFIG_FILES([agent/Makefile])
+ AC_CONFIG_FILES([agent/sila-snmp-agent.service])
+])
+AS_IF([test "x$enable_cfg_manager" != "xno"], [
+ AC_CONFIG_FILES([snmpcfg/Makefile])
+ AC_CONFIG_FILES([snmpcfg/sila-snmp-cfg-manager.service])
+])
+
+# Create configured output
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
+
diff --git a/mibs/SILA-MIB.txt b/mibs/SILA-MIB.txt
new file mode 100644
index 0000000..8b77ae1
--- /dev/null
+++ b/mibs/SILA-MIB.txt
@@ -0,0 +1,947 @@
+SILA-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE,
+ Integer32, enterprises
+ FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION, TruthValue
+ FROM SNMPv2-TC
+ MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP
+ FROM SNMPv2-CONF
+;
+
+sila MODULE-IDENTITY
+ LAST-UPDATED "202110010000Z"
+ ORGANIZATION "Sila"
+ CONTACT-INFO
+ "Primary Contact: SNMP support team
+ email: snmp@sila.ru"
+ DESCRIPTION
+ "Made table indexes read-only fields"
+ REVISION "202110010000Z"
+ DESCRIPTION
+ "Added SILA products OIDs"
+ REVISION "202106170000Z"
+ DESCRIPTION
+ "Added node for products manufactored by SILA LLC"
+ REVISION "201909260000Z"
+ DESCRIPTION
+ "Added inventory table"
+ REVISION "201806190000Z"
+ DESCRIPTION
+ "Added software items table"
+ REVISION "201805110000Z"
+ DESCRIPTION
+ "Added current and power sensor objects"
+ REVISION "201802080000Z"
+ DESCRIPTION
+ "This MIB module defines objects for OpenBMC sensors derived data."
+ REVISION "201712080000Z"
+ DESCRIPTION
+ "Add sensors state fields"
+ REVISION "201711150000Z"
+ DESCRIPTION
+ "Add format for sensors values"
+ REVISION "201711031000Z"
+ DESCRIPTION
+ "First draft"
+ ::= { enterprises 49769 }
+
+silaNotifications OBJECT IDENTIFIER ::= { sila 0 }
+silaSensors OBJECT IDENTIFIER ::= { sila 1 }
+silaConformance OBJECT IDENTIFIER ::= { sila 2 }
+silaCompliances OBJECT IDENTIFIER ::= { silaConformance 1 }
+silaGroups OBJECT IDENTIFIER ::= { silaConformance 2 }
+silaProducts OBJECT IDENTIFIER ::= { sila 3 }
+-- silaInventoryTable takes { sila 4 }
+-- silaSoftwareTable takes { sila 5 }
+
+-- OIDs for SNMPv2-MIB::sysObjectID field
+vesninBmc OBJECT IDENTIFIER ::= { silaProducts 1 }
+vegmanBmc OBJECT IDENTIFIER ::= { silaProducts 20 }
+tatlinArchiveSBmc OBJECT IDENTIFIER ::= { silaProducts 21 }
+
+--
+
+silaHostPowerState OBJECT-TYPE
+ SYNTAX INTEGER { unknown(-1), off(0), on(1) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Power state of the host"
+ DEFVAL { 0 }
+ ::= { silaSensors 1 }
+
+--
+
+Degrees ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "d-3"
+ STATUS current
+ DESCRIPTION "Degrees Celsius"
+ SYNTAX Integer32
+
+Voltage ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "d-3"
+ STATUS current
+ DESCRIPTION "Volt"
+ SYNTAX Integer32
+
+RPMS ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "d-0"
+ STATUS current
+ DESCRIPTION "RPM"
+ SYNTAX Integer32
+
+Current ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "d-3"
+ STATUS current
+ DESCRIPTION "Ampere"
+ SYNTAX Integer32
+
+Power ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "d-3"
+ STATUS current
+ DESCRIPTION "Watt"
+ SYNTAX Integer32
+
+SensorState ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION "Actual state of sensor"
+ SYNTAX INTEGER {
+ disabled(0),
+ normal(1),
+ warningLow(2),
+ warningHigh(3),
+ criticalLow(4),
+ criticalHigh(5)
+ }
+
+--
+
+silaTempSensorsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF SILATempSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of temperature sensors and their values."
+ ::= { silaSensors 2 }
+
+silaTempSensorsEntry OBJECT-TYPE
+ SYNTAX SILATempSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry containing a device and its statistics."
+ INDEX { silaTempSensorName }
+ ::= { silaTempSensorsTable 1 }
+
+SILATempSensorsEntry ::= SEQUENCE {
+ silaTempSensorName OCTET STRING,
+ silaTempSensorValue Degrees,
+ silaTempSensorWarnLow Degrees,
+ silaTempSensorWarnHigh Degrees,
+ silaTempSensorCritLow Degrees,
+ silaTempSensorCritHigh Degrees,
+ silaTempSensorState SensorState
+}
+
+silaTempSensorName OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(1..32))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the temperature sensor."
+ ::= { silaTempSensorsEntry 1 }
+
+silaTempSensorValue OBJECT-TYPE
+ SYNTAX Degrees
+ UNITS "°C"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current temperature from sensor."
+ ::= { silaTempSensorsEntry 2 }
+
+silaTempSensorWarnLow OBJECT-TYPE
+ SYNTAX Degrees
+ UNITS "°C"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning low value of sensor."
+ ::= { silaTempSensorsEntry 3 }
+
+silaTempSensorWarnHigh OBJECT-TYPE
+ SYNTAX Degrees
+ UNITS "°C"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning high value of sensor."
+ ::= { silaTempSensorsEntry 4 }
+
+silaTempSensorCritLow OBJECT-TYPE
+ SYNTAX Degrees
+ UNITS "°C"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical low value of sensor."
+ ::= { silaTempSensorsEntry 5 }
+
+silaTempSensorCritHigh OBJECT-TYPE
+ SYNTAX Degrees
+ UNITS "°C"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical high value of sensor."
+ ::= { silaTempSensorsEntry 6 }
+
+silaTempSensorState OBJECT-TYPE
+ SYNTAX SensorState
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Current state of sensor"
+ ::= { silaTempSensorsEntry 7 }
+
+--
+
+silaVoltSensorsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF SILAVoltSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of voltage sensors and their values."
+ ::= { silaSensors 3 }
+
+silaVoltSensorsEntry OBJECT-TYPE
+ SYNTAX SILAVoltSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry containing a device and its statistics."
+ INDEX { silaVoltSensorName }
+ ::= { silaVoltSensorsTable 1 }
+
+SILAVoltSensorsEntry ::= SEQUENCE {
+ silaVoltSensorName OCTET STRING,
+ silaVoltSensorValue Voltage,
+ silaVoltSensorWarnLow Voltage,
+ silaVoltSensorWarnHigh Voltage,
+ silaVoltSensorCritLow Voltage,
+ silaVoltSensorCritHigh Voltage,
+ silaVoltSensorState SensorState
+}
+
+silaVoltSensorName OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(1..32))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the voltage sensor."
+ ::= { silaVoltSensorsEntry 1 }
+
+silaVoltSensorValue OBJECT-TYPE
+ SYNTAX Voltage
+ UNITS "V"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current voltage from sensor."
+ ::= { silaVoltSensorsEntry 2 }
+
+silaVoltSensorWarnLow OBJECT-TYPE
+ SYNTAX Voltage
+ UNITS "V"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning low value of sensor."
+ ::= { silaVoltSensorsEntry 3 }
+
+silaVoltSensorWarnHigh OBJECT-TYPE
+ SYNTAX Voltage
+ UNITS "V"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning high value of sensor."
+ ::= { silaVoltSensorsEntry 4 }
+
+silaVoltSensorCritLow OBJECT-TYPE
+ SYNTAX Voltage
+ UNITS "V"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical low value of sensor."
+ ::= { silaVoltSensorsEntry 5 }
+
+silaVoltSensorCritHigh OBJECT-TYPE
+ SYNTAX Voltage
+ UNITS "V"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical high value of sensor."
+ ::= { silaVoltSensorsEntry 6 }
+
+silaVoltSensorState OBJECT-TYPE
+ SYNTAX SensorState
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Current state of sensor"
+ ::= { silaVoltSensorsEntry 7 }
+
+--
+
+silaTachSensorsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF SILATachSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of tachometer sensors and their values."
+ ::= { silaSensors 4 }
+
+silaTachSensorsEntry OBJECT-TYPE
+ SYNTAX SILATachSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry containing a device and its statistics."
+ INDEX { silaTachSensorName }
+ ::= { silaTachSensorsTable 1 }
+
+SILATachSensorsEntry ::= SEQUENCE {
+ silaTachSensorName OCTET STRING,
+ silaTachSensorValue RPMS,
+ silaTachSensorWarnLow RPMS,
+ silaTachSensorWarnHigh RPMS,
+ silaTachSensorCritLow RPMS,
+ silaTachSensorCritHigh RPMS,
+ silaTachSensorState SensorState
+}
+
+silaTachSensorName OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(1..32))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the tachometer sensor."
+ ::= { silaTachSensorsEntry 1 }
+
+silaTachSensorValue OBJECT-TYPE
+ SYNTAX RPMS
+ UNITS "RPM"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current tachometer from sensor."
+ ::= { silaTachSensorsEntry 2 }
+
+silaTachSensorWarnLow OBJECT-TYPE
+ SYNTAX RPMS
+ UNITS "RPM"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning low value of sensor."
+ ::= { silaTachSensorsEntry 3 }
+
+silaTachSensorWarnHigh OBJECT-TYPE
+ SYNTAX RPMS
+ UNITS "RPM"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning high value of sensor."
+ ::= { silaTachSensorsEntry 4 }
+
+silaTachSensorCritLow OBJECT-TYPE
+ SYNTAX RPMS
+ UNITS "RPM"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical low value of sensor."
+ ::= { silaTachSensorsEntry 5 }
+
+silaTachSensorCritHigh OBJECT-TYPE
+ SYNTAX RPMS
+ UNITS "RPM"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical high value of sensor."
+ ::= { silaTachSensorsEntry 6 }
+
+silaTachSensorState OBJECT-TYPE
+ SYNTAX SensorState
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Current state of sensor"
+ ::= { silaTachSensorsEntry 7 }
+
+--
+
+silaCurrSensorsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF SILACurrSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of current sensors and their values."
+ ::= { silaSensors 5 }
+
+silaCurrSensorsEntry OBJECT-TYPE
+ SYNTAX SILACurrSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry containing a device and its statistics."
+ INDEX { silaCurrSensorName }
+ ::= { silaCurrSensorsTable 1 }
+
+SILACurrSensorsEntry ::= SEQUENCE {
+ silaCurrSensorName OCTET STRING,
+ silaCurrSensorValue Current,
+ silaCurrSensorWarnLow Current,
+ silaCurrSensorWarnHigh Current,
+ silaCurrSensorCritLow Current,
+ silaCurrSensorCritHigh Current,
+ silaCurrSensorState SensorState
+}
+
+silaCurrSensorName OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(1..32))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the current sensor."
+ ::= { silaCurrSensorsEntry 1 }
+
+silaCurrSensorValue OBJECT-TYPE
+ SYNTAX Current
+ UNITS "A"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current amperage from sensor."
+ ::= { silaCurrSensorsEntry 2 }
+
+silaCurrSensorWarnLow OBJECT-TYPE
+ SYNTAX Current
+ UNITS "A"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning low value of sensor."
+ ::= { silaCurrSensorsEntry 3 }
+
+silaCurrSensorWarnHigh OBJECT-TYPE
+ SYNTAX Current
+ UNITS "A"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning high value of sensor."
+ ::= { silaCurrSensorsEntry 4 }
+
+silaCurrSensorCritLow OBJECT-TYPE
+ SYNTAX Current
+ UNITS "A"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical low value of sensor."
+ ::= { silaCurrSensorsEntry 5 }
+
+silaCurrSensorCritHigh OBJECT-TYPE
+ SYNTAX Current
+ UNITS "A"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical high value of sensor."
+ ::= { silaCurrSensorsEntry 6 }
+
+silaCurrSensorState OBJECT-TYPE
+ SYNTAX SensorState
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Current state of sensor"
+ ::= { silaCurrSensorsEntry 7 }
+
+--
+
+silaPowerSensorsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF SILAPowerSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of power sensors and their values."
+ ::= { silaSensors 6 }
+
+silaPowerSensorsEntry OBJECT-TYPE
+ SYNTAX SILAPowerSensorsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry containing a device and its statistics."
+ INDEX { silaPowerSensorName }
+ ::= { silaPowerSensorsTable 1 }
+
+SILAPowerSensorsEntry ::= SEQUENCE {
+ silaPowerSensorName OCTET STRING,
+ silaPowerSensorValue Power,
+ silaPowerSensorWarnLow Power,
+ silaPowerSensorWarnHigh Power,
+ silaPowerSensorCritLow Power,
+ silaPowerSensorCritHigh Power,
+ silaPowerSensorState SensorState
+}
+
+silaPowerSensorName OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(1..32))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the power sensor."
+ ::= { silaPowerSensorsEntry 1 }
+
+silaPowerSensorValue OBJECT-TYPE
+ SYNTAX Power
+ UNITS "W"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current amperage from sensor."
+ ::= { silaPowerSensorsEntry 2 }
+
+silaPowerSensorWarnLow OBJECT-TYPE
+ SYNTAX Power
+ UNITS "W"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning low value of sensor."
+ ::= { silaPowerSensorsEntry 3 }
+
+silaPowerSensorWarnHigh OBJECT-TYPE
+ SYNTAX Power
+ UNITS "W"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The warning high value of sensor."
+ ::= { silaPowerSensorsEntry 4 }
+
+silaPowerSensorCritLow OBJECT-TYPE
+ SYNTAX Power
+ UNITS "W"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical low value of sensor."
+ ::= { silaPowerSensorsEntry 5 }
+
+silaPowerSensorCritHigh OBJECT-TYPE
+ SYNTAX Power
+ UNITS "W"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The critical high value of sensor."
+ ::= { silaPowerSensorsEntry 6 }
+
+silaPowerSensorState OBJECT-TYPE
+ SYNTAX SensorState
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Current state of sensor"
+ ::= { silaPowerSensorsEntry 7 }
+
+--
+silaSoftwareTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF SILASoftwareEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of existing firmware images."
+ ::= { sila 5 }
+
+silaSoftwareEntry OBJECT-TYPE
+ SYNTAX SILASoftwareEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry containing a firmware images details."
+ INDEX { silaSoftwareHash }
+ ::= { silaSoftwareTable 1 }
+
+SILASoftwareEntry ::= SEQUENCE {
+ silaSoftwareHash OCTET STRING,
+ silaSoftwareVersion OCTET STRING,
+ silaSoftwarePurpose Integer32,
+ silaSoftwareActivation Integer32,
+ silaSoftwarePriority Integer32
+}
+
+silaSoftwareHash OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(1..8))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The software hash represent as string."
+ ::= { silaSoftwareEntry 1 }
+
+silaSoftwareVersion OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The software version."
+ ::= { silaSoftwareEntry 2 }
+
+silaSoftwarePurpose OBJECT-TYPE
+ SYNTAX INTEGER { unknown(0), other(1), system(2), bmc(3), host(4) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The software version purpose."
+ ::= { silaSoftwareEntry 3 }
+
+silaSoftwareActivation OBJECT-TYPE
+ SYNTAX INTEGER { notReady(0), invalid(1), ready(2),
+ activating(3), active(4), failed(5) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current activation state of the software."
+ ::= { silaSoftwareEntry 4 }
+
+silaSoftwarePriority OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The piority, for redundancy purposes, of the associated software."
+ ::= { silaSoftwareEntry 5 }
+
+--
+
+silaInventoryTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF SILAInventoryEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of inventory items."
+ ::= { sila 4 }
+
+silaInventoryEntry OBJECT-TYPE
+ SYNTAX SILAInventoryEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry containing a inventory item details."
+ INDEX { silaInventoryPath }
+ ::= { silaInventoryTable 1 }
+
+SILAInventoryEntry ::= SEQUENCE {
+ silaInventoryPath OCTET STRING,
+ silaInventoryName OCTET STRING,
+ silaInventoryManufacturer OCTET STRING,
+ silaInventoryBuildDate OCTET STRING,
+ silaInventoryModel OCTET STRING,
+ silaInventoryPartNumber OCTET STRING,
+ silaInventorySerialNumber OCTET STRING,
+ silaInventoryVersion OCTET STRING,
+ silaInventoryPresent TruthValue,
+ silaInventoryFunctional TruthValue
+}
+
+silaInventoryPath OBJECT-TYPE
+ SYNTAX OCTET STRING(SIZE(1..117))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The DBus path of the inventory item based on inventory folder."
+ ::= { silaInventoryEntry 1 }
+
+silaInventoryName OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The human readable name of the inventory item."
+ ::= { silaInventoryEntry 2 }
+
+silaInventoryManufacturer OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The inventory item manufacturer."
+ ::= { silaInventoryEntry 3 }
+
+silaInventoryBuildDate OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The date of inventory item manufacture in YYYYMMDD format."
+ ::= { silaInventoryEntry 4 }
+
+silaInventoryModel OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The model of the inventory item."
+ ::= { silaInventoryEntry 5 }
+
+silaInventoryPartNumber OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The inventory item part number, typically a stocking number."
+ ::= { silaInventoryEntry 6 }
+
+silaInventorySerialNumber OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The inventory item serial number."
+ ::= { silaInventoryEntry 7 }
+
+silaInventoryVersion OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The version of the inventory item."
+ ::= { silaInventoryEntry 8 }
+
+silaInventoryPresent OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Whether or not the inventory item is present."
+ ::= { silaInventoryEntry 9 }
+
+silaInventoryFunctional OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "the inventory item is functional or not."
+ ::= { silaInventoryEntry 10 }
+
+--
+
+silaHostPowerStateNotification NOTIFICATION-TYPE
+ OBJECTS { silaHostPowerState }
+ STATUS current
+ DESCRIPTION
+ "Notification about changed power state of host"
+ ::= { silaNotifications 1 }
+
+silaTachSensorStateNotification NOTIFICATION-TYPE
+ OBJECTS { silaTachSensorState }
+ STATUS current
+ DESCRIPTION
+ "Notification about changed state of tachometer sensor"
+ ::= { silaNotifications 4 }
+
+silaTempSensorStateNotification NOTIFICATION-TYPE
+ OBJECTS { silaTempSensorState }
+ STATUS current
+ DESCRIPTION
+ "Notification about changed state of temperature sensor"
+ ::= { silaNotifications 2 }
+
+silaVoltSensorStateNotification NOTIFICATION-TYPE
+ OBJECTS { silaVoltSensorState }
+ STATUS current
+ DESCRIPTION
+ "Notification about changed state of voltage sensor"
+ ::= { silaNotifications 3 }
+
+silaCurrSensorStateNotification NOTIFICATION-TYPE
+ OBJECTS { silaCurrSensorState }
+ STATUS current
+ DESCRIPTION
+ "Notification about changed state of current sensor"
+ ::= { silaNotifications 5 }
+
+silaPwrSensorStateNotification NOTIFICATION-TYPE
+ OBJECTS { silaPowerSensorState }
+ STATUS current
+ DESCRIPTION
+ "Notification about changed state of current sensor"
+ ::= { silaNotifications 6 }
+
+silaInventoryItemNotification NOTIFICATION-TYPE
+ OBJECTS { silaInventoryPresent,
+ silaInventoryFunctional }
+ STATUS current
+ DESCRIPTION
+ "Notification about changed presence or functional
+ of the inventory item."
+ ::= { silaNotifications 7 }
+
+--
+
+silaCommpliance MODULE-COMPLIANCE
+ STATUS current
+ DESCRIPTION
+ "The compliance statement for sensors"
+ MODULE -- this module
+ MANDATORY-GROUPS { silaNotificationsGroup,
+ silaScalarFieldsGroup,
+ silaTempSensorsTableGroup,
+ silaVoltSensorsTableGroup,
+ silaTachSensorsTableGroup,
+ silaCurrSensorsTableGroup,
+ silaPowerSensorsTableGroup,
+ silaSoftwareTableGroup,
+ silaInventoryTableGroup
+ }
+ ::= { silaCompliances 1 }
+
+silaNotificationsGroup NOTIFICATION-GROUP
+ NOTIFICATIONS { silaHostPowerStateNotification,
+ silaTachSensorStateNotification,
+ silaTempSensorStateNotification,
+ silaVoltSensorStateNotification,
+ silaCurrSensorStateNotification,
+ silaPwrSensorStateNotification,
+ silaInventoryItemNotification
+ }
+ STATUS current
+ DESCRIPTION
+ "A collectopn of notifications"
+ ::= { silaGroups 10 }
+
+silaScalarFieldsGroup OBJECT-GROUP
+ OBJECTS { silaHostPowerState }
+ STATUS current
+ DESCRIPTION
+ "A collection of scalar fields"
+ ::= { silaGroups 1 }
+
+silaTempSensorsTableGroup OBJECT-GROUP
+ OBJECTS { silaTempSensorName,
+ silaTempSensorValue,
+ silaTempSensorWarnLow,
+ silaTempSensorWarnHigh,
+ silaTempSensorCritLow,
+ silaTempSensorCritHigh,
+ silaTempSensorState
+ }
+ STATUS current
+ DESCRIPTION
+ "A collection of objects providing information
+ about temperature sensors."
+ ::= { silaGroups 2 }
+
+silaVoltSensorsTableGroup OBJECT-GROUP
+ OBJECTS { silaVoltSensorName,
+ silaVoltSensorValue,
+ silaVoltSensorWarnLow,
+ silaVoltSensorWarnHigh,
+ silaVoltSensorCritLow,
+ silaVoltSensorCritHigh,
+ silaVoltSensorState
+ }
+ STATUS current
+ DESCRIPTION
+ "A collection of objects providing information
+ about voltage sensors."
+ ::= { silaGroups 3 }
+
+silaTachSensorsTableGroup OBJECT-GROUP
+ OBJECTS { silaTachSensorName,
+ silaTachSensorValue,
+ silaTachSensorWarnLow,
+ silaTachSensorWarnHigh,
+ silaTachSensorCritLow,
+ silaTachSensorCritHigh,
+ silaTachSensorState
+ }
+ STATUS current
+ DESCRIPTION
+ "A collection of objects providing information
+ about tachometer sensors."
+ ::= { silaGroups 4 }
+
+silaCurrSensorsTableGroup OBJECT-GROUP
+ OBJECTS { silaCurrSensorName,
+ silaCurrSensorValue,
+ silaCurrSensorWarnLow,
+ silaCurrSensorWarnHigh,
+ silaCurrSensorCritLow,
+ silaCurrSensorCritHigh,
+ silaCurrSensorState
+ }
+ STATUS current
+ DESCRIPTION
+ "A collection of objects providing information
+ about current sensors."
+ ::= { silaGroups 5 }
+
+silaPowerSensorsTableGroup OBJECT-GROUP
+ OBJECTS { silaPowerSensorName,
+ silaPowerSensorValue,
+ silaPowerSensorWarnLow,
+ silaPowerSensorWarnHigh,
+ silaPowerSensorCritLow,
+ silaPowerSensorCritHigh,
+ silaPowerSensorState
+ }
+ STATUS current
+ DESCRIPTION
+ "A collection of objects providing information
+ about power sensors."
+ ::= { silaGroups 6 }
+
+silaSoftwareTableGroup OBJECT-GROUP
+ OBJECTS { silaSoftwareHash,
+ silaSoftwareVersion,
+ silaSoftwarePurpose,
+ silaSoftwareActivation,
+ silaSoftwarePriority
+ }
+ STATUS current
+ DESCRIPTION
+ "A collection for objects providing information
+ about existing software."
+ ::= { silaGroups 7 }
+
+silaInventoryTableGroup OBJECT-GROUP
+ OBJECTS { silaInventoryPath,
+ silaInventoryName,
+ silaInventoryManufacturer,
+ silaInventoryBuildDate,
+ silaInventoryPartNumber,
+ silaInventorySerialNumber,
+ silaInventoryVersion,
+ silaInventoryModel,
+ silaInventoryPresent,
+ silaInventoryFunctional
+ }
+ STATUS current
+ DESCRIPTION
+ "A collection of objects providing information
+ about existing itventory items."
+ ::= { silaGroups 8 }
+
+END
diff --git a/sdbusplus/helper.hpp b/sdbusplus/helper.hpp
new file mode 100644
index 0000000..512eada
--- /dev/null
+++ b/sdbusplus/helper.hpp
@@ -0,0 +1,168 @@
+/**
+ * @brief sdbusplus library helper
+ *
+ * This file is part of sila-snmp-agent project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/exception.hpp>
+
+namespace sdbusplus
+{
+namespace helper
+{
+
+constexpr auto OBJECT_MAPPER_IFACE = "xyz.openbmc_project.ObjectMapper";
+constexpr auto OBJECT_MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
+constexpr auto PROPERTIES_IFACE = "org.freedesktop.DBus.Properties";
+
+struct helper
+{
+ static auto& getBus()
+ {
+ static auto bus = sdbusplus::bus::new_system();
+ return bus;
+ }
+
+ /** @brief Invoke a method. */
+ template <typename... Args>
+ static auto callMethod(const std::string& busName, const std::string& path,
+ const std::string& interface,
+ const std::string& method, Args&&... args)
+ {
+ auto reqMsg = getBus().new_method_call(
+ busName.c_str(), path.c_str(), interface.c_str(), method.c_str());
+ reqMsg.append(std::forward<Args>(args)...);
+ return getBus().call(reqMsg);
+ }
+
+ /** @brief Invoke a method and read the response. */
+ template <typename Ret, typename... Args>
+ static Ret callMethodAndRead(const std::string& busName,
+ const std::string& path,
+ const std::string& interface,
+ const std::string& method, Args&&... args)
+ {
+ Ret resp;
+ try
+ {
+ sdbusplus::message::message respMsg = callMethod<Args...>(
+ busName, path, interface, method, std::forward<Args>(args)...);
+ respMsg.read(resp);
+ }
+ catch (const sdbusplus::exception::SdBusError&)
+ {
+ }
+ return resp;
+ }
+
+ using Path = std::string;
+ using Interface = std::string;
+ using Service = std::string;
+ using Interfaces = std::vector<Interface>;
+ using Services = std::map<Service, Interfaces>;
+ using Objects = std::map<Path, Services>;
+
+ /** @brief Get subtree from the mapper. */
+ static Objects getSubTree(const std::string& path, const Interfaces& ifaces,
+ int32_t depth = 0)
+ {
+ using namespace std::literals::string_literals;
+
+ return callMethodAndRead<Objects>(
+ OBJECT_MAPPER_IFACE, OBJECT_MAPPER_PATH, OBJECT_MAPPER_IFACE,
+ "GetSubTree", path, depth, ifaces);
+ }
+
+ /** @brief Get subtree paths from mapper. */
+ static Interfaces getSubTreePaths(const std::string& path,
+ const Interfaces& ifaces,
+ int32_t depth = 0)
+ {
+ return callMethodAndRead<Interfaces>(
+ OBJECT_MAPPER_IFACE, OBJECT_MAPPER_PATH, OBJECT_MAPPER_IFACE,
+ "GetSubTreePaths", path, depth, ifaces);
+ }
+
+ /** @brief Get service provides specified object */
+ static Service getService(const Path& path, const Interface& iface)
+ {
+ Interfaces ifaces = {iface};
+ auto services = callMethodAndRead<Services>(
+ OBJECT_MAPPER_IFACE, OBJECT_MAPPER_PATH, OBJECT_MAPPER_IFACE,
+ "GetObject", path, ifaces);
+
+ if (!services.empty())
+ {
+ return std::move(services.begin()->first);
+ }
+
+ return {};
+ }
+
+ /** @brief Get a property. */
+ template <typename Property>
+ static Property
+ getProperty(const std::string& busName, const std::string& path,
+ const std::string& interface, const std::string& property)
+ {
+ auto reqMsg = callMethod(busName, path, PROPERTIES_IFACE, "Get",
+ interface, property);
+ std::variant<Property> value;
+ reqMsg.read(value);
+ return value.template get<Property>();
+ }
+
+ /** @brief Get all properties. */
+ template <typename... Types>
+ static auto getAllProperties(const std::string& busName,
+ const std::string& path,
+ const std::string& interface)
+ {
+ using Value = std::variant<Types...>;
+ using Values = std::map<std::string, Value>;
+
+ auto reqMsg =
+ callMethod(busName, path, PROPERTIES_IFACE, "GetAll", interface);
+
+ Values values;
+ reqMsg.read(values);
+ return values;
+ }
+};
+
+} // namespace helper
+
+namespace bus
+{
+namespace match
+{
+namespace rules
+{
+inline auto propertiesChanged(const std::string& p)
+{
+ return type::signal() + path(p) + member("PropertiesChanged") +
+ interface(sdbusplus::helper::PROPERTIES_IFACE);
+}
+} // namespace rules
+} // namespace match
+} // namespace bus
+} // namespace sdbusplus
diff --git a/snmpcfg/Makefile.am b/snmpcfg/Makefile.am
new file mode 100644
index 0000000..5741fd0
--- /dev/null
+++ b/snmpcfg/Makefile.am
@@ -0,0 +1,41 @@
+bin_PROGRAMS = sila-snmpcfg
+
+nobase_nodist_include_HEADERS = \
+ xyz/openbmc_project/SNMPCfg/server.hpp
+
+sila_snmpcfg_SOURCES = \
+ snmpcfg-server.cpp \
+ xyz/openbmc_project/SNMPCfg/server.cpp
+
+sila_snmpcfg_CXXFLAGS = \
+ $(SDBUSPLUS_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS)
+sila_snmpcfg_LDADD = \
+ $(SDBUSPLUS_LIBS) \
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS)
+
+# Be sure to build needed files before compiling
+BUILT_SOURCES = \
+ xyz/openbmc_project/SNMPCfg/server.cpp \
+ xyz/openbmc_project/SNMPCfg/server.hpp
+
+CLEANFILES=${BUILT_SOURCES}
+
+xyz/openbmc_project/SNMPCfg/server.cpp: \
+xyz/openbmc_project/SNMPCfg.interface.yaml \
+xyz/openbmc_project/SNMPCfg/server.hpp
+ @mkdir -p $(@D)
+ $(SDBUSPLUSPLUS) -r $(srcdir) interface server-cpp \
+xyz.openbmc_project.SNMPCfg > $@
+
+xyz/openbmc_project/SNMPCfg/server.hpp: \
+xyz/openbmc_project/SNMPCfg.interface.yaml
+ @mkdir -p $(@D)
+ $(SDBUSPLUSPLUS) -r $(srcdir) interface server-header \
+xyz.openbmc_project.SNMPCfg > $@
+
+if HAVE_SYSTEMD
+systemdsystemunit_DATA = sila-snmp-cfg-manager.service
+endif
diff --git a/snmpcfg/sila-snmp-cfg-manager.service.in b/snmpcfg/sila-snmp-cfg-manager.service.in
new file mode 100644
index 0000000..d80b9a6
--- /dev/null
+++ b/snmpcfg/sila-snmp-cfg-manager.service.in
@@ -0,0 +1,13 @@
+[Unit]
+Description=Sila SNMP configuration manager
+After=snmpd.service
+
+[Service]
+ExecStart=@bindir@/sila-snmpcfg
+SyslogIdentifier=sila-snmpcfg
+Restart=always
+Type=dbus
+BusName=xyz.openbmc_project.SNMPCfg
+
+[Install]
+WantedBy=@SYSTEMD_TARGET@
diff --git a/snmpcfg/snmpcfg-server.cpp b/snmpcfg/snmpcfg-server.cpp
new file mode 100644
index 0000000..da76555
--- /dev/null
+++ b/snmpcfg/snmpcfg-server.cpp
@@ -0,0 +1,291 @@
+/**
+ * @brief SNMP Configuration manager
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <string>
+#include <fstream>
+#include <streambuf>
+
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/SNMPCfg/server.hpp>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+using namespace phosphor::logging;
+using SNMPCfg_inherit = sdbusplus::server::object_t<
+ sdbusplus::xyz::openbmc_project::server::SNMPCfg>;
+
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using InvalidArgument =
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
+using Argument = xyz::openbmc_project::Common::InvalidArgument;
+
+static constexpr auto snmpdConf = "/etc/snmp/snmpd.conf";
+static constexpr auto whitespace = " \t";
+
+/**
+ * @brief Check if the string contains the token at the position or after
+ * several spaces.
+ *
+ * @param[in] str - string to be checked
+ * @param[in] token - expected token
+ * @param[in/out] pos - start position for checking, will keep the last
+ * checked position.
+ *
+ * @return - true if token found.
+ */
+static bool getToken(const std::string& str, const std::string& token,
+ size_t& pos)
+{
+ pos = str.find_first_not_of(whitespace, pos);
+ if (pos != std::string::npos && 0 == str.compare(pos, token.size(), token))
+ {
+ auto endPos = str.find_first_of(whitespace, pos);
+ if (endPos == std::string::npos || pos + token.size() == endPos)
+ {
+ pos = endPos;
+ return true;
+ }
+ }
+ return false;
+}
+
+class Configurator : public SNMPCfg_inherit
+{
+ public:
+ /**
+ * @brief Configurator object constructor
+ *
+ * @param bus - DBus connection object reference
+ * @param path - DBus object path
+ */
+ Configurator(sdbusplus::bus::bus& bus, const char* path) :
+ SNMPCfg_inherit(bus, path), bus(bus)
+ {
+ readConfig();
+ }
+
+ std::string community(std::string value) override
+ {
+ static constexpr auto communityMaxLen = 256;
+ static constexpr auto communityMinLen = 1;
+
+ auto communityLen = value.length();
+ if (communityLen < communityMinLen || communityLen > communityMaxLen)
+ {
+ log<level::ERR>("Invalid community name length");
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("Community"),
+ Argument::ARGUMENT_VALUE(value.c_str()));
+ }
+
+ SNMPCfg_inherit::community(value);
+ writeConfig();
+ return value;
+ }
+
+ private:
+ /**
+ * @brief Parse line and get community name
+ *
+ * @param line - the line from configuration file
+ *
+ * @return community name if corresponding statement found
+ * and nullopt otherwise
+ */
+ std::optional<std::string> getCommunityName(std::string line) const
+ {
+ // Required line should be in format:
+ // 'com2sec readonly default <communityName>'
+ size_t pos = 0;
+
+ for (const auto& token : {"com2sec", "readonly", "default"})
+ {
+ if (!getToken(line, token, pos) || pos == std::string::npos)
+ {
+ return std::nullopt;
+ }
+ }
+
+ pos = line.find_first_not_of(whitespace, pos);
+ if (pos == std::string::npos)
+ {
+ return std::nullopt;
+ }
+
+ auto endPos = line.find_last_not_of(whitespace);
+ if (pos < endPos && line[pos] == '"' && line[endPos] == '"')
+ {
+ pos++;
+ endPos--;
+ }
+
+ return (pos <= endPos ? line.substr(pos, endPos - pos + 1)
+ : std::string{});
+ }
+
+ /**
+ * @brief Read actual settings from configuration file
+ */
+ void readConfig()
+ {
+ std::ifstream fileToRead(snmpdConf, std::ios::in);
+ if (!fileToRead.is_open())
+ {
+ log<level::ERR>("Failed to open SNMP daemon configuration file",
+ entry("FILE_NAME=%s", snmpdConf));
+ return;
+ }
+
+ std::string line;
+ while (std::getline(fileToRead, line))
+ {
+ auto communityName = getCommunityName(line);
+ if (communityName)
+ {
+ SNMPCfg_inherit::community(*communityName);
+ return;
+ }
+ }
+ log<level::ERR>("Community not found",
+ entry("FILE_NAME=%s", snmpdConf));
+ }
+
+ /**
+ * @brief Write actual settings to the configuration file.
+ */
+ void writeConfig()
+ {
+ std::string tmpFileName{snmpdConf};
+ tmpFileName += ".tmp";
+
+ std::ifstream fileToRead(snmpdConf, std::ios::in);
+ std::ofstream fileToWrite(tmpFileName, std::ios::out);
+ if (!fileToRead.is_open())
+ {
+ log<level::ERR>("Failed to open SNMP daemon configuration file",
+ entry("FILE_NAME=%s", snmpdConf));
+ return;
+ }
+
+ if (!fileToWrite.is_open())
+ {
+ log<level::ERR>("Failed to create new configuration file",
+ entry("FILE_NAME=%s", tmpFileName.c_str()));
+ return;
+ }
+
+ auto value = SNMPCfg_inherit::community();
+ bool isQuoteRequired =
+ (value.find_first_of(whitespace) != std::string::npos);
+ bool updated = false;
+ std::string line;
+ while (std::getline(fileToRead, line))
+ {
+ auto communityName = getCommunityName(line);
+ if (communityName && *communityName != value)
+ {
+ fileToWrite << "com2sec readonly default\t "
+ << (isQuoteRequired ? "\"" : "") << value
+ << (isQuoteRequired ? "\"" : "") << std::endl;
+ updated = true;
+ }
+ else
+ {
+ fileToWrite << line << std::endl;
+ }
+ }
+ fileToWrite.close();
+ fileToRead.close();
+
+ if (updated)
+ {
+ if (0 != std::rename(tmpFileName.c_str(), snmpdConf))
+ {
+ int error = errno;
+ log<level::ERR>("Failed to update SNMP daemon configuarion",
+ entry("WHAT=%s", strerror(error)),
+ entry("FILE_NAME=%s", snmpdConf));
+ elog<InternalFailure>();
+ }
+
+ reloadSnmpDaemon();
+ }
+ else
+ {
+ if (0 != std::remove(tmpFileName.c_str()))
+ {
+ int error = errno;
+ log<level::ERR>("Failed to remove temporary file",
+ entry("WHAT=%s", strerror(error)),
+ entry("FILE_NAME=%s", tmpFileName.c_str()));
+ }
+ }
+ }
+
+ sdbusplus::bus::bus& bus;
+
+ /**
+ * @brief Ask SNMP daemon to reread configuration.
+ */
+ void reloadSnmpDaemon()
+ {
+ auto m = bus.new_method_call(
+ "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager", "ReloadUnit");
+ m.append("snmpd.service", "replace");
+ try
+ {
+ bus.call_noreply(m);
+ }
+ catch (const sdbusplus::exception::SdBusError& e)
+ {
+ log<level::ERR>("Failed to reload SNMP daemon",
+ entry("WHATE=%s", e.what()));
+ elog<InternalFailure>();
+ }
+ }
+};
+
+/**
+ * @brief Application entry point
+ *
+ * @return exit status
+ */
+int main()
+{
+ constexpr auto path = "/xyz/openbmc_project/snmpcfg";
+
+ auto b = sdbusplus::bus::new_default();
+ sdbusplus::server::manager_t m{b, path};
+
+ b.request_name("xyz.openbmc_project.SNMPCfg");
+ Configurator cfg{b, path};
+
+ while (1)
+ {
+ b.process_discard();
+ b.wait();
+ }
+
+ return 0;
+}
diff --git a/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml b/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml
new file mode 100644
index 0000000..5017f7b
--- /dev/null
+++ b/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml
@@ -0,0 +1,13 @@
+description: >
+ SNMP daemon configuration manager.
+
+properties:
+ - name: Community
+ type: string
+ description: >
+ SNMPv1 and SNMPv2 community that allows read-only (GET and GETNEXT) access
+ to the data providing by SNMP daemon.
+ errors:
+ - xyz.openbmc_project.Common.Error.InternalFailure
+ - xyz.openbmc_project.Common.Error.InvalidArgument
+
diff --git a/tests/emit-added-MEMBUF1.sh b/tests/emit-added-MEMBUF1.sh
new file mode 100755
index 0000000..e40690d
--- /dev/null
+++ b/tests/emit-added-MEMBUF1.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+gdbus emit --system --object-path '/xyz/openbmc_project/sensors' \
+ --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \
+ 'objectpath "/xyz/openbmc_project/sensors/temperature/MEMBUF1"' \
+ "{ \
+ 'org.freedesktop.DBus.Peer': @a{sv} {}, \
+ 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \
+ 'org.freedesktop.DBus.Properties': @a{sv} {}, \
+ 'org.freedesktop.DBus.ObjectManager': @a{sv} {}, \
+ 'xyz.openbmc_project.Sensor.Threshold.Critical': { \
+ 'CriticalHigh':<int64 83000>, \
+ 'CriticalLow':<int64 0>, \
+ 'CriticalAlarmHigh':<false>, \
+ 'CriticalAlarmLow':<false> \
+ }, \
+ 'xyz.openbmc_project.Sensor.Threshold.Warning': { \
+ 'WarningHigh':<int64 78000>, \
+ 'WarningLow':<int64 0>, \
+ 'WarningAlarmHigh':<false>, \
+ 'WarningAlarmLow':<false> \
+ }, \
+ 'xyz.openbmc_project.Sensor.Value': { \
+ 'Value':<int64 48000>, \
+ 'Unit':<'xyz.openbmc_project.Sensor.Value.Unit.DegreesC'>, \
+ 'Scale':<int64 -3> \
+ } \
+ }"
+
diff --git a/tests/emit-added-ambient.sh b/tests/emit-added-ambient.sh
new file mode 100755
index 0000000..b0a023f
--- /dev/null
+++ b/tests/emit-added-ambient.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+gdbus emit --system --object-path '/xyz/openbmc_project/sensors' \
+ --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \
+ 'objectpath "/xyz/openbmc_project/sensors/temperature/ambient"' \
+ "{ \
+ 'org.freedesktop.DBus.Peer': @a{sv} {}, \
+ 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \
+ 'org.freedesktop.DBus.Properties': @a{sv} {}, \
+ 'org.freedesktop.DBus.ObjectManager': @a{sv} {}, \
+ 'xyz.openbmc_project.Sensor.Threshold.Critical': { \
+ 'CriticalHigh':<int64 83000>, \
+ 'CriticalLow':<int64 0>, \
+ 'CriticalAlarmHigh':<false>, \
+ 'CriticalAlarmLow':<false> \
+ }, \
+ 'xyz.openbmc_project.Sensor.Threshold.Warning': { \
+ 'WarningHigh':<int64 78000>, \
+ 'WarningLow':<int64 0>, \
+ 'WarningAlarmHigh':<false>, \
+ 'WarningAlarmLow':<false> \
+ }, \
+ 'xyz.openbmc_project.Sensor.Value': { \
+ 'Value':<int64 48000>, \
+ 'Unit':<'xyz.openbmc_project.Sensor.Value.Unit.DegreesC'>, \
+ 'Scale':<int64 -3> \
+ } \
+ }"
+
diff --git a/tests/emit-added-bmc.sh b/tests/emit-added-bmc.sh
new file mode 100755
index 0000000..35b1d92
--- /dev/null
+++ b/tests/emit-added-bmc.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+gdbus emit --system --object-path '/xyz/openbmc_project/software' \
+ --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \
+ 'objectpath "/xyz/openbmc_project/software/1f6027c9"' \
+ "{ \
+ 'org.freedesktop.DBus.Peer': @a{sv} {}, \
+ 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \
+ 'org.freedesktop.DBus.Properties': @a{sv} {}, \
+ 'xyz.openbmc_project.Common.FilePath': { \
+ 'Path':<'/tmp/images/1f6027c9'> \
+ }, \
+ 'xyz.openbmc_project.Software.Version': { \
+ 'Version':<'v2.2-441-gc04c198-dirty'>, \
+ 'Purpose':<'xyz.openbmc_project.Software.Version.VersionPurpose.BMC'> \
+ }, \
+ 'xyz.openbmc_project.Software.Activation': { \
+ 'Activation':<'xyz.openbmc_project.Software.Activation.Activations.Ready'>, \
+ 'RequestedActivation':<'xyz.openbmc_project.Software.Activation.RequestedActivations.None'> \
+ }
+ }"
+
+
+# 'org.openbmc.Associations': { \
+# 'associations':<struct {'inventory', 'activation', ''} > \
+# } \
diff --git a/tests/emit-added-opfw.sh b/tests/emit-added-opfw.sh
new file mode 100755
index 0000000..b2ef3dd
--- /dev/null
+++ b/tests/emit-added-opfw.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+gdbus emit --system --object-path '/xyz/openbmc_project/inventory' \
+ --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \
+ 'objectpath "/xyz/openbmc_project/inventory/system/chassis/motherboard/opfw"' \
+ "{ \
+ 'org.freedesktop.DBus.Peer': @a{sv} {}, \
+ 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \
+ 'org.freedesktop.DBus.Properties': @a{sv} {}, \
+ 'xyz.openbmc_project.Inventory.Opfw': { \
+ 'BuildrootVersion':<'buildroot-2018.02.2-7-gcb36c6d'>, \
+ 'SkibootVersion': <'skiboot-v6.0.1-27-g34e9c3c1edb3-p13e1584'>, \
+ 'HostbootVersion': <'hostboot-p8-d3025f5-pe7d78e0'>, \
+ 'LinuxVersion': <'occ-p8-28f2cec-pf0b771d'>, \
+ 'PetitbootVersion':<'linux-4.16.13-openpower1-p908fb4b'>, \
+ 'MachinexmlVersion':<'petitboot-1.8.0'> \
+ }, \
+ 'xyz.openbmc_project.Inventory.Item': { \
+ 'PrettyName':<'OpenPOWER Firmware'>, \
+ 'Present':<true> \
+ }, \
+ 'xyz.openbmc_project.Inventory.Decorator.Revision': { \
+ 'Version':<'open-power-vesnin-v2.0-45-g16f9312'> \
+ } \
+ }"
+
diff --git a/tests/emit-added-system-chassis.sh b/tests/emit-added-system-chassis.sh
new file mode 100755
index 0000000..3f5407f
--- /dev/null
+++ b/tests/emit-added-system-chassis.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+gdbus emit --system --object-path '/xyz/openbmc_project/inventory' \
+ --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \
+ 'objectpath "/xyz/openbmc_project/inventory/system/chassis"' \
+ "{ \
+ 'org.freedesktop.DBus.Peer': @a{sv} {}, \
+ 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \
+ 'org.freedesktop.DBus.Properties': @a{sv} {}, \
+ 'xyz.openbmc_project.Inventory.Decorator.CoolingType': { \
+ 'AirCooled': <true>, 'WaterCooled': <false> \
+ } \
+ }"
+
diff --git a/tests/emit-change-ambient-warn.sh b/tests/emit-change-ambient-warn.sh
new file mode 100755
index 0000000..0860fcf
--- /dev/null
+++ b/tests/emit-change-ambient-warn.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+gdbus emit --system \
+ --object-path '/xyz/openbmc_project/sensors/temperature/ambient' \
+ --signal 'org.freedesktop.DBus.Properties.PropertiesChanged' \
+ 'xyz.openbmc_project.Sensor.Threshold.Warning' \
+ "{'WarningAlarmHigh':<boolean ${1:-false}>}" \
+ '@as []'
+
diff --git a/tests/emit-change-ambient.sh b/tests/emit-change-ambient.sh
new file mode 100755
index 0000000..b74fa46
--- /dev/null
+++ b/tests/emit-change-ambient.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+gdbus emit --system \
+ --object-path '/xyz/openbmc_project/sensors/temperature/ambient' \
+ --signal 'org.freedesktop.DBus.Properties.PropertiesChanged' \
+ 'xyz.openbmc_project.Sensor.Value' "{'Value':<int64 34250>}" \
+ '@as []'
+
diff --git a/tests/emit-change-power-state.sh b/tests/emit-change-power-state.sh
new file mode 100755
index 0000000..0d46cca
--- /dev/null
+++ b/tests/emit-change-power-state.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+gdbus emit --system \
+ --object-path '/org/openbmc/control/power0' \
+ --signal 'org.freedesktop.DBus.Properties.PropertiesChanged' \
+ 'org.openbmc.control.Power' "{'state':<int32 ${1:-0}>}" '@as []'
+
diff --git a/tests/emit-removed-ambient.sh b/tests/emit-removed-ambient.sh
new file mode 100755
index 0000000..a01dd0e
--- /dev/null
+++ b/tests/emit-removed-ambient.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+gdbus emit --system --object-path '/xyz/openbmc_project/sensors' \
+ --signal org.freedesktop.DBus.ObjectManager.InterfacesRemoved \
+ 'objectpath "/xyz/openbmc_project/sensors/temperature/ambient"' \
+ "[ \
+ 'org.freedesktop.DBus.Peer', \
+ 'org.freedesktop.DBus.Introspectable', \
+ 'org.freedesktop.DBus.Properties', \
+ 'xyz.openbmc_project.Sensor.Value' \
+ ]"
+