summaryrefslogtreecommitdiff
path: root/agent
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 /agent
downloadobmc-sila-snmp-0f4556fc2343dc0ade0bb1e0d1fc6f85770d77af.tar.xz
First commit: Sila SNMP Sub Agent and configuration manager
Diffstat (limited to 'agent')
-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
22 files changed, 2650 insertions, 0 deletions
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__)