spawn one rmcpp bridge per interface

According to the new architecture, each bridge should be a separate
process, or at the very least, be attached on a separate D-Bus
connection with a well-known name that corresponds to the channel name.

This commit brings netipmid to a state where it can be launched as:
systemctl start phosphor-ipmi-net@eth0

with a parameterized unit file and socket file that binds the socket to
the interface specified. If not launched this way, it will by default be
bound to all interfaces.

This includes the new parameterized service and socket file and the
autoconf/automake magic to install them to the correct place.

Tested-by: launch netipmid via current unit file/socket file
           launch netipmid via parameterize unit file/socket file

Change-Id: Ib202015fb560da269e535f11c0ac316b17f6c262
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/sd_event_loop.cpp b/sd_event_loop.cpp
index ee25960..7a647cc 100644
--- a/sd_event_loop.cpp
+++ b/sd_event_loop.cpp
@@ -46,6 +46,89 @@
                           });
 }
 
+int EventLoop::setupSocket(std::shared_ptr<sdbusplus::asio::connection>& bus,
+                           std::string iface, uint16_t reqPort)
+{
+    static constexpr const char* unboundIface = "rmcpp";
+    if (iface == "")
+    {
+        iface = unboundIface;
+    }
+    // Create our own socket if SysD did not supply one.
+    int listensFdCount = sd_listen_fds(0);
+    if (listensFdCount > 1)
+    {
+        log<level::ERR>("Too many file descriptors received");
+        return EXIT_FAILURE;
+    }
+    if (listensFdCount == 1)
+    {
+        int openFd = SD_LISTEN_FDS_START;
+        if (!sd_is_socket(openFd, AF_UNSPEC, SOCK_DGRAM, -1))
+        {
+            log<level::ERR>("Failed to set up systemd-passed socket");
+            return EXIT_FAILURE;
+        }
+        udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
+            *io, boost::asio::ip::udp::v6(), openFd);
+    }
+    else
+    {
+        // asio does not natively offer a way to bind to an interface
+        // so it must be done in steps
+        boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v6(), reqPort);
+        udpSocket = std::make_shared<boost::asio::ip::udp::socket>(*io);
+        udpSocket->open(ep.protocol());
+        // bind
+        udpSocket->set_option(
+            boost::asio::ip::udp::socket::reuse_address(true));
+        udpSocket->bind(ep);
+    }
+    // SO_BINDTODEVICE
+    char nameout[IFNAMSIZ];
+    unsigned int lenout = sizeof(nameout);
+    if ((::getsockopt(udpSocket->native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
+                      nameout, &lenout) == -1))
+    {
+        log<level::ERR>("Failed to read bound device",
+                        entry("ERROR=%s", strerror(errno)));
+    }
+    if (iface != nameout && iface != unboundIface)
+    {
+        // SO_BINDTODEVICE
+        if ((::setsockopt(udpSocket->native_handle(), SOL_SOCKET,
+                          SO_BINDTODEVICE, iface.c_str(),
+                          iface.size() + 1) == -1))
+        {
+            log<level::ERR>("Failed to bind to requested interface",
+                            entry("ERROR=%s", strerror(errno)));
+            return EXIT_FAILURE;
+        }
+    }
+    // cannot be constexpr because it gets passed by address
+    const int option_enabled = 1;
+    // common socket stuff; set options to get packet info (DST addr)
+    ::setsockopt(udpSocket->native_handle(), IPPROTO_IP, IP_PKTINFO,
+                 &option_enabled, sizeof(option_enabled));
+    ::setsockopt(udpSocket->native_handle(), IPPROTO_IPV6, IPV6_RECVPKTINFO,
+                 &option_enabled, sizeof(option_enabled));
+
+    // set the dbus name
+    std::string busName = "xyz.openbmc_project.Ipmi.Channel." + iface;
+    try
+    {
+        bus->request_name(busName.c_str());
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Failed to acquire D-Bus name",
+                        entry("NAME=%s", busName.c_str()),
+                        entry("ERROR=%s", e.what()));
+        return EXIT_FAILURE;
+    }
+    return 0;
+}
+
 int EventLoop::startEventLoop()
 {
     // set up boost::asio signal handling
@@ -57,32 +140,6 @@
             io->stop();
         });
 
-    // Create our own socket if SysD did not supply one.
-    int listensFdCount = sd_listen_fds(0);
-    if (listensFdCount == 1)
-    {
-        if (sd_is_socket(SD_LISTEN_FDS_START, AF_UNSPEC, SOCK_DGRAM, -1))
-        {
-            udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
-                *io, boost::asio::ip::udp::v6(), SD_LISTEN_FDS_START);
-        }
-    }
-    else if (listensFdCount > 1)
-    {
-        log<level::ERR>("Too many file descriptors received");
-        return EXIT_FAILURE;
-    }
-    if (!udpSocket)
-    {
-        udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
-            *io, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v6(),
-                                                IPMI_STD_PORT));
-        if (!udpSocket)
-        {
-            log<level::ERR>("Failed to start listening on RMCP socket");
-            return EXIT_FAILURE;
-        }
-    }
     startRmcpReceive();
 
     io->run();