Add support for vlan(set lan command)

Resolves openbmc/openbmc#1899

Change-Id: I3c436045676bc96e5d91fd9420509bc991549a13
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/apphandler.cpp b/apphandler.cpp
index 7b30edd..2251e8c 100644
--- a/apphandler.cpp
+++ b/apphandler.cpp
@@ -17,7 +17,6 @@
 #include <phosphor-logging/elog-errors.hpp>
 #include "xyz/openbmc_project/Common/error.hpp"
 
-
 extern sd_bus *bus;
 
 constexpr auto app_obj = "/org/openbmc/NetworkManager/Interface";
@@ -535,6 +534,7 @@
     std::string ipaddress;
     std::string gateway;
     uint8_t prefix {};
+    uint32_t vlanID {};
 
     // Todo: parse the request data if needed.
 
@@ -574,9 +574,21 @@
             ipaddress = channelConfig.ipaddr;
             prefix = ipmi::network::toPrefix(AF_INET, channelConfig.netmask);
             gateway = channelConfig.gateway;
+
+            if (channelConfig.vlanID != ipmi::network::VLAN_ID_MASK)
+            {
+                //get the first twelve bits which is vlan id
+                //not interested in rest of the bits.
+                vlanID = channelConfig.vlanID & ipmi::network::VLAN_ID_MASK;
+                channelConfig.vlanID = le32toh(channelConfig.vlanID);
+            }
+
         }
         else
         {
+            // We have partial filled cache so get the remaning
+            // info from the system.
+
             // gets the network data from the system as user has
             // not given all the data then use the data fetched from the
             // system but it is implementation dependent,IPMI spec doesn't
@@ -585,7 +597,6 @@
             // if system is not having any ip object don't throw error,
             try
             {
-
                 auto ipObjectInfo = ipmi::getDbusObject(bus,
                         ipmi::network::IP_INTERFACE,
                         ipmi::network::ROOT,
@@ -605,6 +616,18 @@
                     ipmi::network::toPrefix(AF_INET,
                                             channelConfig.netmask);
 
+                if (channelConfig.vlanID != ipmi::network::VLAN_ID_MASK)
+                {
+                    //get the first twelve bits which is vlan id
+                    //not interested in rest of the bits.
+                    vlanID = channelConfig.vlanID & ipmi::network::VLAN_ID_MASK;
+                    channelConfig.vlanID = le32toh(channelConfig.vlanID);
+                }
+                else
+                {
+                    vlanID = ipmi::network::getVLAN(ipObjectInfo.first);
+                }
+
             }
             catch (InternalFailure& e)
             {
@@ -626,7 +649,7 @@
         }
 
         // Currently network manager doesn't support purging of all the
-        // ip addresses from the parent interface,
+        // ip addresses and the vlan interfaces from the parent interface,
         // TODO once the support is there, will make the change here.
         // https://github.com/openbmc/openbmc/issues/2141.
 
@@ -634,11 +657,24 @@
         // Multiple interface through
         // https://github.com/openbmc/openbmc/issues/2138
 
-        //delete all the ipv4  addresses
-
+        //delete all the vlan interfaces
+        //delete all the ipv4 addresses
+        ipmi::deleteAllDbusObjects(bus, ipmi::network::ROOT,
+                                   ipmi::network::VLAN_INTERFACE);
         ipmi::deleteAllDbusObjects(bus, ipmi::network::ROOT,
                                    ipmi::network::IP_INTERFACE,
                                    ipmi::network::IP_TYPE);
+        if (vlanID)
+        {
+            ipmi::network::createVLAN(bus, ipmi::network::SERVICE,
+                                      ipmi::network::ROOT,
+                                      ipmi::network::INTERFACE, vlanID);
+
+            networkInterfaceObject = ipmi::getDbusObject(
+                    bus,
+                    ipmi::network::VLAN_INTERFACE,
+                    ipmi::network::ROOT);
+        }
 
         ipmi::network::createIP(bus, networkInterfaceObject.second,
                                 networkInterfaceObject.first,
@@ -664,6 +700,7 @@
     return rc;
 }
 
+
 // ATTENTION: This ipmi function is very hardcoded on purpose
 // OpenBMC does not fully support IPMI.  This command is useful
 // to have around because it enables testing of interfaces with
diff --git a/transporthandler.cpp b/transporthandler.cpp
index 4de9400..30526b3 100644
--- a/transporthandler.cpp
+++ b/transporthandler.cpp
@@ -49,6 +49,7 @@
 {
     ipmi_ret_t rc = IPMI_CC_OK;
     sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
+
     try
     {
         switch (lan_param)
@@ -180,6 +181,37 @@
             }
             break;
 
+            case LAN_PARM_VLAN:
+            {
+                if (lan_set_in_progress == SET_COMPLETE)
+                {
+                    auto ipObjectInfo = ipmi::getDbusObject(
+                                            bus,
+                                            ipmi::network::IP_INTERFACE,
+                                            ipmi::network::ROOT,
+                                            ipmi::network::IP_TYPE);
+
+                    auto vlanID = static_cast<uint16_t>(
+                            ipmi::network::getVLAN(ipObjectInfo.first));
+
+                    vlanID = htole16(vlanID);
+
+                    if (vlanID)
+                    {
+                        //Enable the 16th bit
+                        vlanID |= htole16(ipmi::network::VLAN_ENABLE_MASK);
+                    }
+
+                    memcpy(data, &vlanID, ipmi::network::VLAN_SIZE_BYTE);
+                }
+                else if (lan_set_in_progress == SET_IN_PROGRESS)
+                {
+                    memcpy(data, &(channelConfig.vlanID),
+                           ipmi::network::VLAN_SIZE_BYTE);
+                }
+            }
+            break;
+
             default:
                 rc = IPMI_CC_PARM_OUT_OF_RANGE;
         }
@@ -290,6 +322,18 @@
         }
         break;
 
+        case LAN_PARM_VLAN:
+        {
+            uint16_t vlan {};
+            memcpy(&vlan, reqptr->data, ipmi::network::VLAN_SIZE_BYTE);
+            // We are not storing the enable bit
+            // We assume that ipmitool always send enable
+            // bit as 1.
+            vlan = le16toh(vlan);
+            channelConfig.vlanID = vlan;
+        }
+        break;
+
         case LAN_PARM_INPROGRESS:
         {
             if (reqptr->data[0] == SET_COMPLETE)
@@ -299,7 +343,8 @@
                 log<level::INFO>("Network data from Cache",
                                  entry("PREFIX=%s", channelConfig.netmask.c_str()),
                                  entry("ADDRESS=%s", channelConfig.ipaddr.c_str()),
-                                 entry("GATEWAY=%s", channelConfig.gateway.c_str()));
+                                 entry("GATEWAY=%s", channelConfig.gateway.c_str()),
+                                 entry("VLAN=%d", channelConfig.vlanID));
 
                 log<level::INFO>("Use Set Channel Access command to apply");
 
@@ -314,8 +359,6 @@
 
         default:
         {
-            log<level::ERR>("Unsupported parameter",
-                            entry("PARAMETER=0x%x", reqptr->parameter));
             rc = IPMI_CC_PARM_NOT_SUPPORTED;
         }
 
@@ -398,6 +441,18 @@
             rc = IPMI_CC_UNSPECIFIED_ERROR;
         }
     }
+    else if (reqptr->parameter == LAN_PARM_VLAN)
+    {
+        uint8_t buf[ipmi::network::VLAN_SIZE_BYTE + 1];
+
+        *data_len = sizeof(current_revision);
+        memcpy(buf, &current_revision, *data_len);
+        if (getNetworkData(reqptr->parameter, &buf[1]) == IPMI_CC_OK)
+        {
+            *data_len = sizeof(buf);
+            memcpy(response, &buf, *data_len);
+        }
+    }
     else
     {
         log<level::ERR>("Unsupported parameter",
diff --git a/transporthandler.hpp b/transporthandler.hpp
index f1fd410..24ee8be 100644
--- a/transporthandler.hpp
+++ b/transporthandler.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
+#include "types.hpp"
 #include <string>
-
 // IPMI commands for Transport net functions.
 enum ipmi_netfn_storage_cmds
 {
@@ -24,6 +24,7 @@
 static const int LAN_PARM_MAC         = 5;
 static const int LAN_PARM_SUBNET      = 6;
 static const int LAN_PARM_GATEWAY     = 12;
+static const int LAN_PARM_VLAN        = 20;
 
 struct ChannelConfig_t
 {
@@ -31,11 +32,17 @@
     std::string netmask;
     std::string gateway;
     std::string macAddress;
+    // IPMI stores the vlan info in 16 bits,32 bits is to aligned
+    // with phosphor-dbus interfaces.
+    // vlan id is in 12 bits and the 16th bit is for enable mask.
+    uint32_t vlanID = ipmi::network::VLAN_ID_MASK;
+
     void clear()
     {
         ipaddr.clear();
         netmask.clear();
         gateway.clear();
         macAddress.clear();
+        vlanID = ipmi::network::VLAN_ID_MASK;
     }
 };
diff --git a/types.hpp b/types.hpp
index 5b39c38..d3c8dc6 100644
--- a/types.hpp
+++ b/types.hpp
@@ -108,8 +108,11 @@
 constexpr auto DEFAULT_ADDRESS = "0.0.0.0";
 
 constexpr auto MAC_ADDRESS_SIZE_BYTE = 6;
+constexpr auto VLAN_SIZE_BYTE = 2;
 constexpr auto BITS_32 = 32;
 constexpr auto MASK_32_BIT = 0xFFFFFFFF;
+constexpr auto VLAN_ID_MASK = 0x00000FFF;
+constexpr auto VLAN_ENABLE_MASK = 0x8000;
 
 }//namespace network
 }//namespace ipmi
diff --git a/utils.cpp b/utils.cpp
index 11dc2d4..2e6c5e7 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -339,6 +339,32 @@
 
 }
 
+void createVLAN(sdbusplus::bus::bus& bus,
+                const std::string& service,
+                const std::string& objPath,
+                const std::string& interfaceName,
+                uint32_t vlanID)
+{
+    auto busMethod = bus.new_method_call(
+                         service.c_str(),
+                         objPath.c_str(),
+                         VLAN_CREATE_INTERFACE,
+                         "VLAN");
+
+    busMethod.append(interfaceName, vlanID);
+
+    auto reply = bus.call(busMethod);
+
+    if (reply.is_method_error())
+    {
+        log<level::ERR>("Failed to excute method",
+                        entry("METHOD=%s", "VLAN"),
+                        entry("PATH=%s", objPath.c_str()));
+        elog<InternalFailure>();
+    }
+
+}
+
 uint8_t toPrefix(int addressFamily, const std::string& subnetMask)
 {
     if (addressFamily == AF_INET6)
@@ -370,7 +396,34 @@
     }
 }
 
+uint32_t getVLAN(const std::string& path)
+{
+    // Path would be look like
+    // /xyz/openbmc_project/network/eth0_443/ipv4
+
+    uint32_t vlanID = 0;
+    try
+    {
+        auto intfObjectPath = path.substr(0,
+                path.find(IP_TYPE) - 1);
+
+        auto intfName = intfObjectPath.substr(intfObjectPath.rfind("/") + 1);
+
+        auto index = intfName.find("_");
+        if (index != std::string::npos)
+        {
+            auto str = intfName.substr(index + 1);
+            vlanID = std::stoul(str);
+        }
+    }
+    catch (std::exception & e)
+    {
+        log<level::ERR>("Exception occured during getVLAN",
+                        entry("PATH=%s",path.c_str()),
+                        entry("EXCEPTIOn=%s", e.what()));
+    }
+    return vlanID;
+}
+
 } // namespace network
 } // namespace ipmi
-
-
diff --git a/utils.hpp b/utils.hpp
index cb64a25..a172e22 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -135,7 +135,7 @@
 {
 
 constexpr auto ROOT = "/xyz/openbmc_project/network";
-constexpr auto NETWORK_SERVICE = "xyz.openbmc_project.Network";
+constexpr auto SERVICE = "xyz.openbmc_project.Network";
 constexpr auto INTERFACE = "eth0";
 constexpr auto IP_TYPE = "ipv4";
 constexpr auto IP_INTERFACE = "xyz.openbmc_project.Network.IP";
@@ -143,6 +143,8 @@
 constexpr auto SYSTEMCONFIG_INTERFACE = "xyz.openbmc_project.Network.SystemConfiguration";
 constexpr auto ETHERNET_INTERFACE = "xyz.openbmc_project.Network.EthernetInterface";
 constexpr auto IP_CREATE_INTERFACE = "xyz.openbmc_project.Network.IP.Create";
+constexpr auto VLAN_CREATE_INTERFACE = "xyz.openbmc_project.Network.VLAN.Create";
+constexpr auto VLAN_INTERFACE = "xyz.openbmc_project.Network.VLAN";
 
 /* @brief converts the given subnet into prefix notation.
  * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
@@ -167,8 +169,25 @@
               const std::string& ipaddress,
               uint8_t prefix);
 
-}//namespace network
+/** @brief Creates the VLAN on the given interface.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] service - Dbus service name.
+ *  @param[in] objPath - Dbus object path.
+ *  @param[in] interface - EthernetInterface.
+ *  @param[in] vlanID - Vlan ID.
+ */
+void createVLAN(sdbusplus::bus::bus& bus,
+                const std::string& service,
+                const std::string& objPath,
+                const std::string& interface,
+                uint32_t vlanID);
 
+/** @brief Gets the vlan id from the given object path.
+ *  @param[in] path - Dbus object path.
+ */
+uint32_t getVLAN(const std::string& path);
+
+} //namespace network
 } // namespace ipmi