Fetch the kernel routing table

It would be used to get the gateway for
a specific network.
It would also be used to get the default
gateway for the system.

Change-Id: I1f7f81e1d6ea6c3f4385f454ad229d4074341c44
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/routing_table.cpp b/routing_table.cpp
new file mode 100644
index 0000000..bd8f9e0
--- /dev/null
+++ b/routing_table.cpp
@@ -0,0 +1,228 @@
+#include "routing_table.hpp"
+#include "util.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+
+#include <netinet/in.h>
+#include <net/if.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/rtnetlink.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <stdexcept>
+
+namespace phosphor
+{
+namespace network
+{
+namespace route
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+Table::Table()
+{
+    try
+    {
+        getRoutes();
+    }
+    catch (InternalFailure& e)
+    {
+        commit<InternalFailure>();
+    }
+
+}
+
+int Table::readNetLinkSock(int sockFd, std::array<char,BUFSIZE>& buf)
+{
+    struct nlmsghdr* nlHdr = nullptr;
+    int readLen {};
+    int msgLen {};
+    uint8_t seqNum = 1;
+    uint8_t pID = getpid();
+    char* bufPtr = buf.data();
+
+    do
+    {
+        // Recieve response from the kernel
+        if ((readLen = recv(sockFd, bufPtr, BUFSIZE - msgLen, 0)) < 0)
+        {
+            auto error = errno;
+            log<level::ERR>("Socket recv failed:",
+                             entry("ERROR=%s", strerror(error)));
+            elog<InternalFailure>();
+
+        }
+
+        nlHdr = reinterpret_cast<nlmsghdr*>(bufPtr);
+
+        // Check if the header is valid
+
+        if ((NLMSG_OK(nlHdr, readLen) == 0)
+            || (nlHdr->nlmsg_type == NLMSG_ERROR))
+        {
+
+            auto error = errno;
+            log<level::ERR>("Error validating header",
+                             entry("NLMSGTYPE=%d", nlHdr->nlmsg_type),
+                             entry("ERROR=%s", strerror(error)));
+            elog<InternalFailure>();
+        }
+
+        // Check if the its the last message
+        if (nlHdr->nlmsg_type == NLMSG_DONE)
+        {
+            break;
+        }
+        else
+        {
+            // Else move the pointer to buffer appropriately
+            bufPtr += readLen;
+            msgLen += readLen;
+        }
+
+        // Check if its a multi part message
+        if ((nlHdr->nlmsg_flags & NLM_F_MULTI) == 0)
+        {
+            break;
+        }
+    }
+    while ((nlHdr->nlmsg_seq != seqNum) || (nlHdr->nlmsg_pid != pID));
+    return msgLen;
+}
+
+void Table::parseRoutes(const nlmsghdr* nlHdr)
+{
+    rtmsg* rtMsg = nullptr;
+    rtattr* rtAttr = nullptr;
+    int rtLen {};
+    in_addr dstAddr {};
+    in_addr gateWayAddr {};
+    char ifName[IF_NAMESIZE] = {};
+
+    rtMsg = reinterpret_cast<rtmsg*>(NLMSG_DATA(nlHdr));
+
+    // If the route is not for AF_INET or does not belong to main routing table
+    // then return.
+    if ((rtMsg->rtm_family != AF_INET) || (rtMsg->rtm_table != RT_TABLE_MAIN))
+    {
+        return;
+    }
+
+    // get the rtattr field
+    rtAttr = reinterpret_cast<rtattr*>(RTM_RTA(rtMsg));
+
+    rtLen = RTM_PAYLOAD(nlHdr);
+
+    for (; RTA_OK(rtAttr, rtLen); rtAttr = RTA_NEXT(rtAttr, rtLen))
+    {
+        switch (rtAttr->rta_type)
+        {
+            case RTA_OIF:
+                if_indextoname(*reinterpret_cast<int*>(RTA_DATA(rtAttr)), ifName);
+                break;
+            case RTA_GATEWAY:
+                gateWayAddr.s_addr = *reinterpret_cast<u_int*>(RTA_DATA(rtAttr));
+                break;
+            case RTA_DST:
+                dstAddr.s_addr = *reinterpret_cast<u_int*>(RTA_DATA(rtAttr));
+                break;
+        }
+    }
+
+    std::string dstStr;
+    std::string gatewayStr;
+
+    if (dstAddr.s_addr == 0)
+    {
+        defaultGateway = reinterpret_cast<char*>(inet_ntoa(gateWayAddr));
+    }
+
+    dstStr = inet_ntoa(dstAddr);
+    gatewayStr = inet_ntoa(gateWayAddr);
+
+    Entry route(dstStr, gatewayStr, ifName);
+    routeList.emplace(std::make_pair(dstStr, std::move(route)));
+}
+
+Map Table::getRoutes()
+{
+    nlmsghdr* nlMsg = nullptr;
+    std::array<char, BUFSIZE> msgBuf = {0};
+
+    int sock = -1;
+    int len {0};
+
+    uint8_t msgSeq {0};
+
+    // Create Socket
+    if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0)
+    {
+        auto error = errno;
+        log<level::ERR>("Error occured during socket creation",
+                        entry("ERRNO=%s", strerror(error)));
+        elog<InternalFailure>();
+    }
+
+    phosphor::Descriptor smartSock(sock);
+    sock = -1;
+
+    // point the header and the msg structure pointers into the buffer.
+    nlMsg = reinterpret_cast<nlmsghdr*>(msgBuf.data());
+    // Length of message
+    nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(rtmsg));
+    // Get the routes from kernel routing table
+    nlMsg->nlmsg_type =  RTM_GETROUTE;
+    // The message is a request for dump
+    nlMsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+
+    nlMsg->nlmsg_seq = msgSeq;
+    nlMsg->nlmsg_pid = getpid();
+
+    // Send the request
+    if (send(smartSock(), nlMsg, nlMsg->nlmsg_len, 0) < 0)
+    {
+        auto error = errno;
+        log<level::ERR>("Error occurred during send on netlink socket",
+                        entry("ERRNO=%s", strerror(error)));
+        elog<InternalFailure>();
+    }
+
+    // Read the response
+    len = readNetLinkSock(smartSock(), msgBuf);
+
+    // Parse and print the response
+    for (; NLMSG_OK(nlMsg, len); nlMsg = NLMSG_NEXT(nlMsg, len))
+    {
+        parseRoutes(nlMsg);
+    }
+    return routeList;
+}
+
+std::string Table::getGateway(int addressFamily,
+                              const std::string& ipaddress,
+                              uint8_t prefix) const
+{
+    std::string gateway;
+    std::string network = getNetworkID(addressFamily, ipaddress, prefix);
+    auto it = routeList.find(network);
+    if (it != routeList.end())
+    {
+        gateway = it->second.gateway;
+    }
+
+    return gateway;
+}
+
+}// namespace route
+}// namespace network
+}// namespace phosphor