Send the trap to the configured SNMP managers

Get the snmp manager details from the D-bus service and send the
trap to all the configured snmp managers.

Resolves openbmc/openbmc#3058

Change-Id: I9c59ae866ab194e92a85254ca646ecc299803f26
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 7167b1d..68c7ab7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,7 +9,8 @@
 
 noinst_HEADERS = \
 		snmp_client.hpp \
-		snmp_conf_manager.hpp
+		snmp_conf_manager.hpp \
+		snmp_util.hpp
 
 phosphor_network_snmpconf_SOURCES = \
 		snmp_main.cpp \
@@ -51,7 +52,8 @@
 
 libsnmp_LTLIBRARIES = libsnmp.la
 libsnmp_la_SOURCES = \
-		snmp_notification.cpp
+		snmp_notification.cpp \
+		snmp_util.cpp
 
 libsnmp_la_LDFLAGS = \
 		$(SDBUSPLUS_LIBS) \
diff --git a/snmp_main.cpp b/snmp_main.cpp
index 932b9f4..d11ed1e 100644
--- a/snmp_main.cpp
+++ b/snmp_main.cpp
@@ -10,7 +10,7 @@
 /* Need a custom deleter for freeing up sd_event */
 struct EventDeleter
 {
-    void operator()(sd_event* event) const
+    void operator()(sd_event *event) const
     {
         event = sd_event_unref(event);
     }
@@ -18,13 +18,13 @@
 
 using EventPtr = std::unique_ptr<sd_event, EventDeleter>;
 
-int main(int argc, char* argv[])
+int main(int argc, char *argv[])
 {
     using namespace phosphor::logging;
 
     auto bus = sdbusplus::bus::new_default();
 
-    sd_event* event = nullptr;
+    sd_event *event = nullptr;
     auto r = sd_event_default(&event);
     if (r < 0)
     {
diff --git a/snmp_notification.cpp b/snmp_notification.cpp
index 1763602..b0df691 100644
--- a/snmp_notification.cpp
+++ b/snmp_notification.cpp
@@ -1,4 +1,5 @@
 #include "snmp_notification.hpp"
+#include "snmp_util.hpp"
 
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
@@ -59,7 +60,6 @@
 void Notification::sendTrap()
 {
     constexpr auto comm = "public";
-    constexpr auto localHost = "127.0.0.1";
     netsnmp_session session{0};
 
     snmp_sess_init(&session);
@@ -73,63 +73,67 @@
     session.callback = nullptr;
     session.callback_magic = nullptr;
 
-    // TODO:- get it from settings D-bus object.
-    session.peername = const_cast<char*>(localHost);
+    auto mgrs = getManagers();
 
-    // create the session
-    auto ss = snmp_add(
-        &session, netsnmp_transport_open_client("snmptrap", session.peername),
-        nullptr, nullptr);
-    if (!ss)
+    for (auto& mgr : mgrs)
     {
-        log<level::ERR>("Unable to get the snmp session.",
-                        entry("SNMPMANAGER=%s", session.peername));
-        elog<InternalFailure>();
-    }
-
-    // Wrap the raw pointer in RAII
-    snmpSessionPtr sessionPtr(ss, &::snmp_close);
-
-    ss = nullptr;
-
-    auto pdu = snmp_pdu_create(SNMP_MSG_TRAP2);
-    if (!pdu)
-    {
-        log<level::ERR>("Failed to create notification PDU");
-        elog<InternalFailure>();
-    }
-
-    pdu->trap_type = SNMP_TRAP_ENTERPRISESPECIFIC;
-
-    auto trapInfo = getTrapOID();
-
-    if (!snmp_pdu_add_variable(
-            pdu, SNMPTrapOID, sizeof(SNMPTrapOID) / sizeof(oid), ASN_OBJECT_ID,
-            trapInfo.first.data(), trapInfo.second * sizeof(oid)))
-    {
-        log<level::ERR>("Failed to add the SNMP var(trapID)");
-        snmp_free_pdu(pdu);
-        elog<InternalFailure>();
-    }
-
-    auto objectList = getFieldOIDList();
-
-    for (const auto& object : objectList)
-    {
-        if (!addPDUVar(*pdu, std::get<0>(object), std::get<1>(object),
-                       std::get<2>(object), std::get<3>(object)))
+        session.peername = const_cast<char*>(mgr.c_str());
+        // create the session
+        auto ss = snmp_add(
+            &session,
+            netsnmp_transport_open_client("snmptrap", session.peername),
+            nullptr, nullptr);
+        if (!ss)
         {
-            log<level::ERR>("Failed to add the SNMP var");
+            log<level::ERR>("Unable to get the snmp session.",
+                            entry("SNMPMANAGER=%s", mgr.c_str()));
+            elog<InternalFailure>();
+        }
+
+        // Wrap the raw pointer in RAII
+        snmpSessionPtr sessionPtr(ss, &::snmp_close);
+
+        ss = nullptr;
+
+        auto pdu = snmp_pdu_create(SNMP_MSG_TRAP2);
+        if (!pdu)
+        {
+            log<level::ERR>("Failed to create notification PDU");
+            elog<InternalFailure>();
+        }
+
+        pdu->trap_type = SNMP_TRAP_ENTERPRISESPECIFIC;
+
+        auto trapInfo = getTrapOID();
+
+        if (!snmp_pdu_add_variable(pdu, SNMPTrapOID,
+                                   sizeof(SNMPTrapOID) / sizeof(oid),
+                                   ASN_OBJECT_ID, trapInfo.first.data(),
+                                   trapInfo.second * sizeof(oid)))
+        {
+            log<level::ERR>("Failed to add the SNMP var(trapID)");
             snmp_free_pdu(pdu);
             elog<InternalFailure>();
         }
-    }
 
-    // pdu is freed by snmp_send
-    if (!snmp_send(sessionPtr.get(), pdu))
-    {
-        log<level::ERR>("Failed to send the snmp trap.");
-        elog<InternalFailure>();
+        auto objectList = getFieldOIDList();
+
+        for (const auto& object : objectList)
+        {
+            if (!addPDUVar(*pdu, std::get<0>(object), std::get<1>(object),
+                           std::get<2>(object), std::get<3>(object)))
+            {
+                log<level::ERR>("Failed to add the SNMP var");
+                snmp_free_pdu(pdu);
+                elog<InternalFailure>();
+            }
+        }
+        // pdu is freed by snmp_send
+        if (!snmp_send(sessionPtr.get(), pdu))
+        {
+            log<level::ERR>("Failed to send the snmp trap.");
+            elog<InternalFailure>();
+        }
     }
 
     log<level::DEBUG>("Sent SNMP Trap");
diff --git a/snmp_util.cpp b/snmp_util.cpp
new file mode 100644
index 0000000..a8a1ff8
--- /dev/null
+++ b/snmp_util.cpp
@@ -0,0 +1,124 @@
+#include "snmp_util.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <string>
+
+namespace phosphor
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+ObjectValueTree getManagedObjects(sdbusplus::bus::bus& bus,
+                                  const std::string& service,
+                                  const std::string& objPath)
+{
+    ObjectValueTree interfaces;
+
+    auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                      "org.freedesktop.DBus.ObjectManager",
+                                      "GetManagedObjects");
+
+    auto reply = bus.call(method);
+
+    if (reply.is_method_error())
+    {
+        log<level::ERR>("Failed to get managed objects",
+                        entry("PATH=%s", objPath.c_str()));
+        elog<InternalFailure>();
+    }
+
+    reply.read(interfaces);
+    return interfaces;
+}
+
+namespace network
+{
+
+std::string resolveAddress(const std::string& address)
+{
+    addrinfo hints{0};
+    addrinfo* addr = nullptr;
+
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags |= AI_CANONNAME;
+
+    auto result = getaddrinfo(address.c_str(), NULL, &hints, &addr);
+    if (result)
+    {
+        log<level::ERR>("getaddrinfo failed",
+                        entry("ADDRESS=%s", address.c_str()));
+        elog<InternalFailure>();
+    }
+
+    AddrPtr addrPtr{addr};
+    addr = nullptr;
+
+    char ipaddress[INET6_ADDRSTRLEN]{0};
+    result = getnameinfo(addrPtr->ai_addr, addrPtr->ai_addrlen, ipaddress,
+                         sizeof(ipaddress), NULL, 0, NI_NUMERICHOST);
+    if (result)
+    {
+        log<level::ERR>("getnameinfo failed",
+                        entry("ADDRESS=%s", address.c_str()));
+        elog<InternalFailure>();
+    }
+
+    return ipaddress;
+}
+
+namespace snmp
+{
+
+static constexpr auto busName = "xyz.openbmc_project.Network.SNMP";
+static constexpr auto root = "/xyz/openbmc_project/network/snmp/manager";
+static constexpr auto clientIntf = "xyz.openbmc_project.Network.Client";
+
+/** @brief Gets the sdbus object for this process.
+ *  @return the bus object.
+ */
+static auto& getBus()
+{
+    static auto bus = sdbusplus::bus::new_default();
+    return bus;
+}
+
+std::vector<std::string> getManagers()
+{
+    std::vector<std::string> managers;
+    auto& bus = getBus();
+    auto objTree = phosphor::getManagedObjects(bus, busName, root);
+    for (const auto& objIter : objTree)
+    {
+        try
+        {
+            auto& intfMap = objIter.second;
+            auto& snmpClientProps = intfMap.at(clientIntf);
+            auto& address = snmpClientProps.at("Address").get<std::string>();
+            auto& port = snmpClientProps.at("Port").get<uint16_t>();
+            auto ipaddress = phosphor::network::resolveAddress(address);
+            auto mgr = std::move(ipaddress);
+            if (port > 0)
+            {
+                mgr += ":";
+                mgr += std::to_string(port);
+            }
+            managers.push_back(mgr);
+        }
+        catch (const std::exception& e)
+        {
+            log<level::ERR>(e.what());
+        }
+    }
+    return managers;
+}
+
+} // namespace snmp
+} // namespace network
+} // namespace phosphor
diff --git a/snmp_util.hpp b/snmp_util.hpp
new file mode 100644
index 0000000..7a38745
--- /dev/null
+++ b/snmp_util.hpp
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <map>
+#include <string>
+
+#include <sdbusplus/server.hpp>
+
+namespace phosphor
+{
+
+/* Need a custom deleter for freeing up addrinfo */
+struct AddrDeleter
+{
+    void operator()(addrinfo* addrPtr) const
+    {
+        freeaddrinfo(addrPtr);
+    }
+};
+
+using AddrPtr = std::unique_ptr<addrinfo, AddrDeleter>;
+
+using DbusInterface = std::string;
+using DbusProperty = std::string;
+
+using Value =
+    sdbusplus::message::variant<bool, uint8_t, int16_t, uint16_t, int32_t,
+                                uint32_t, int64_t, uint64_t, std::string>;
+
+using PropertyMap = std::map<DbusProperty, Value>;
+
+using DbusInterfaceMap = std::map<DbusInterface, PropertyMap>;
+
+using ObjectValueTree =
+    std::map<sdbusplus::message::object_path, DbusInterfaceMap>;
+
+/** @brief Gets all managed objects associated with the given object
+ *         path and service.
+ *  @param[in] bus - D-Bus Bus Object.
+ *  @param[in] service - D-Bus service name.
+ *  @param[in] objPath - D-Bus object path.
+ *  @return On success returns the map of name value pair.
+ */
+ObjectValueTree getManagedObjects(sdbusplus::bus::bus& bus,
+                                  const std::string& service,
+                                  const std::string& objPath);
+
+namespace network
+{
+
+/** @brief Resolves the given address to IP address.
+ *         Given address could be hostname or IP address.
+ *         if given address is not valid then it throws an exception.
+ *  @param[in] address - address which needs to be converted into IP address.
+ *  @return the IP address.
+ */
+std::string resolveAddress(const std::string& address);
+
+namespace snmp
+{
+
+/** @brief Gets all the snmp manager info.
+ *  @return the list of manager info in the format
+ *          of ipaddress:port
+ */
+std::vector<std::string> getManagers();
+
+} // namespace snmp
+} // namespace network
+
+} // namespace phosphor