Add VLAN device binding

IPMI net channel supports the VLAN. This patch will bind the service
to VLAN device if the VLANID is set in net channel.

Tested:
In all the steps, use following commands to check if ipmi overlan is
still working and lan channel info is correct.
ipmitool -I lanplus ... mc info
ipmitool -I lanplus ... lan print 1

1. Start the phosphor-ipmi-net@eth0.service and this service binds to
   eth0 device
   # Command to get the binding device
   journalctl -u phosphor-ipmi-net@eth0.service -o verbose | grep \
   INTERFACE
    INTERFACE=eth0

2. Set the VLANID (123) for channel 1 (eth0) and restart the service.
   The service is binded to eth0.123
   # Command to set the channel 1 VLANID
    ipmitool -I lanplus ... lan set 1 vlan id 123

   # Command to restart
   systemctl restart  phosphor-ipmi-net@eth0.service

   # Command to check the binding
   journalctl -u phosphor-ipmi-net@eth0.service -o verbose | grep \
   INTERFACE
    INTERFACE=eth0.123

3. Disable the VLANID for channel 0 and restart the service.
   The service is binded to eth0
   # Command to disable the channel 1 VLANID
   ipmitool -I lanplus ... lan set 1 vlan id off

   # Command to restart
   systemctl restart  phosphor-ipmi-net@eth0.service

   # Command to check the binding
   journalctl -u phosphor-ipmi-net@eth0.service -o verbose | grep \
   INTERFACE
    INTERFACE=eth0

Limitation: Need to restart this service when the VLANID is changed.
            This should be done in phosphor-host-ipmid.

Change-Id: I6c05aacf6b18cb1fa0d1cabe6ad36f0d683948d1
Signed-off-by: Alvin Wang <alvinwang@msn.com>
diff --git a/sd_event_loop.cpp b/sd_event_loop.cpp
index 7a647cc..2ad5d2e 100644
--- a/sd_event_loop.cpp
+++ b/sd_event_loop.cpp
@@ -11,6 +11,7 @@
 #include <boost/asio/io_context.hpp>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/asio/sd_event.hpp>
+#include <user_channel/channel_layer.hpp>
 
 namespace eventloop
 {
@@ -46,13 +47,117 @@
                           });
 }
 
-int EventLoop::setupSocket(std::shared_ptr<sdbusplus::asio::connection>& bus,
-                           std::string iface, uint16_t reqPort)
+int EventLoop::getVLANID(const std::string channel)
 {
-    static constexpr const char* unboundIface = "rmcpp";
-    if (iface == "")
+    int vlanid = 0;
+    if (channel.empty())
     {
-        iface = unboundIface;
+        return 0;
+    }
+
+    sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
+    // Enumerate all VLAN + ETHERNET interfaces
+    auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
+                                   "GetSubTree");
+    req.append(PATH_ROOT, 0,
+               std::vector<std::string>{INTF_VLAN, INTF_ETHERNET});
+    ObjectTree objs;
+    try
+    {
+        auto reply = bus.call(req);
+        reply.read(objs);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("getVLANID: failed to execute/read GetSubTree");
+        return 0;
+    }
+
+    std::string ifService, logicalPath;
+    for (const auto& [path, impls] : objs)
+    {
+        if (path.find(channel) == path.npos)
+        {
+            continue;
+        }
+        for (const auto& [service, intfs] : impls)
+        {
+            bool vlan = false;
+            bool ethernet = false;
+            for (const auto& intf : intfs)
+            {
+                if (intf == INTF_VLAN)
+                {
+                    vlan = true;
+                }
+                else if (intf == INTF_ETHERNET)
+                {
+                    ethernet = true;
+                }
+            }
+            if (ifService.empty() && (vlan || ethernet))
+            {
+                ifService = service;
+            }
+            if (logicalPath.empty() && vlan)
+            {
+                logicalPath = path;
+            }
+        }
+    }
+
+    // VLAN devices will always have a separate logical object
+    if (logicalPath.empty())
+    {
+        return 0;
+    }
+
+    Value value;
+    auto method = bus.new_method_call(ifService.c_str(), logicalPath.c_str(),
+                                      PROP_INTF, METHOD_GET);
+    method.append(INTF_VLAN, "Id");
+    try
+    {
+        auto method_reply = bus.call(method);
+        method_reply.read(value);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("getVLANID: failed to execute/read VLAN Id");
+        return 0;
+    }
+
+    vlanid = std::get<uint32_t>(value);
+    if ((vlanid & VLAN_VALUE_MASK) != vlanid)
+    {
+        log<level::ERR>("networkd returned an invalid vlan",
+                        entry("VLAN=%", vlanid));
+        return 0;
+    }
+
+    return vlanid;
+}
+
+int EventLoop::setupSocket(std::shared_ptr<sdbusplus::asio::connection>& bus,
+                           std::string channel, uint16_t reqPort)
+{
+    std::string iface = channel;
+    static constexpr const char* unboundIface = "rmcpp";
+    if (channel == "")
+    {
+        iface = channel = unboundIface;
+    }
+    else
+    {
+        // If VLANID of this channel is set, bind the socket to this
+        // VLAN logic device
+        auto vlanid = getVLANID(channel);
+        if (vlanid)
+        {
+            iface = iface + "." + std::to_string(vlanid);
+            log<level::DEBUG>("This channel has VLAN id",
+                              entry("VLANID=%d", vlanid));
+        }
     }
     // Create our own socket if SysD did not supply one.
     int listensFdCount = sd_listen_fds(0);
@@ -104,6 +209,8 @@
                             entry("ERROR=%s", strerror(errno)));
             return EXIT_FAILURE;
         }
+        log<level::INFO>("Bind to interfae",
+                         entry("INTERFACE=%s", iface.c_str()));
     }
     // cannot be constexpr because it gets passed by address
     const int option_enabled = 1;
@@ -114,7 +221,7 @@
                  &option_enabled, sizeof(option_enabled));
 
     // set the dbus name
-    std::string busName = "xyz.openbmc_project.Ipmi.Channel." + iface;
+    std::string busName = "xyz.openbmc_project.Ipmi.Channel." + channel;
     try
     {
         bus->request_name(busName.c_str());
diff --git a/sd_event_loop.hpp b/sd_event_loop.hpp
index 54b2946..9729078 100644
--- a/sd_event_loop.hpp
+++ b/sd_event_loop.hpp
@@ -19,6 +19,23 @@
 
 namespace eventloop
 {
+using DbusObjectPath = std::string;
+using DbusService = std::string;
+using DbusInterface = std::string;
+using ObjectTree =
+    std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
+using Value = std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
+                           int64_t, uint64_t, double, std::string>;
+// VLANs are a 12-bit value
+constexpr uint16_t VLAN_VALUE_MASK = 0x0fff;
+constexpr auto MAPPER_BUS_NAME = "xyz.openbmc_project.ObjectMapper";
+constexpr auto MAPPER_OBJ = "/xyz/openbmc_project/object_mapper";
+constexpr auto MAPPER_INTF = "xyz.openbmc_project.ObjectMapper";
+constexpr auto PATH_ROOT = "/xyz/openbmc_project/network";
+constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN";
+constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface";
+constexpr auto METHOD_GET = "Get";
+constexpr auto PROP_INTF = "org.freedesktop.DBus.Properties";
 
 class EventLoop
 {
@@ -54,6 +71,9 @@
     /** @brief register the async handler for incoming udp packets */
     void startRmcpReceive();
 
+    /** @brief get vlanid  */
+    int getVLANID(const std::string channel);
+
     /** @brief boost::asio io context to run with
      */
     std::shared_ptr<boost::asio::io_context> io;