face: bugfix of MulticastUdpFace receiving packets from all available NICs on Linux

Now on Linux the creation of MulticastUdpFace requires
CAP_NET_RAW capability if the name of the interface is specified

Change-Id: Iff53035371fb26c6ee40e1065a0935e5ed16dc60
Refs: #1475
diff --git a/README.md b/README.md
index 04aadb5..dc46009 100644
--- a/README.md
+++ b/README.md
@@ -90,3 +90,12 @@
 
     sudo chgrp admin /dev/bpf*
     sudo chmod g+rw /dev/bpf*
+
+## UDP multicast support in multi-homed Linux machines
+
+The UDP configuration file section contains settings for unicast and multicast UDP
+faces. If the Linux box is equipped with multiple network interfaces with multicast
+capabilities, the settings for multicast faces will **NOT** work without root
+or setting the appropriate permissions:
+
+    sudo setcap cap_net_raw=eip /full/path/nfd
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index cac94a2..e97e9bb 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -27,6 +27,10 @@
 #include "core/resolver.hpp"
 #include "core/network-interface.hpp"
 
+#if defined(__linux__)
+#include <sys/socket.h>
+#endif
+
 namespace nfd {
 
 using namespace boost::asio;
@@ -163,9 +167,10 @@
 
 shared_ptr<MulticastUdpFace>
 UdpFactory::createMulticastFace(const udp::Endpoint& localEndpoint,
-                                const udp::Endpoint& multicastEndpoint)
+                                const udp::Endpoint& multicastEndpoint,
+                                const std::string& networkInterfaceName /* "" */)
 {
-  //checking if the local and musticast endpoint are already in use for a multicast face
+  //checking if the local and multicast endpoint are already in use for a multicast face
   shared_ptr<MulticastUdpFace> multicastFace = findMulticastFace(localEndpoint);
   if (static_cast<bool>(multicastFace)) {
     if (multicastFace->getMulticastGroup() == multicastEndpoint)
@@ -224,6 +229,24 @@
     throw Error(msg.str());
   }
 
+#if defined(__linux__)
+  //On linux system, if there are more than one MulticastUdpFace for the same multicast group but
+  //bound on different network interfaces, the socket has to be bound with the specific interface
+  //using SO_BINDTODEVICE, otherwise the face will receive packets also from other interfaces.
+  //Without SO_BINDTODEVICE every MulticastUdpFace that have joined the same multicast group
+  //on different interfaces will receive the same packet.
+  //This applies only on linux, for OS X the ip::multicast::join_group is enough to get
+  //the desired behaviour
+  if (!networkInterfaceName.empty()) {
+    if (::setsockopt(clientSocket->native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
+                     networkInterfaceName.c_str(), networkInterfaceName.size()+1) == -1){
+      throw Error("Cannot bind multicast face to " + networkInterfaceName
+                  + " make sure you have CAP_NET_RAW capability" );
+    }
+  }
+
+#endif
+
   clientSocket->set_option(ip::multicast::enable_loopback(false));
 
   multicastFace = make_shared<MulticastUdpFace>(boost::cref(clientSocket), localEndpoint);
@@ -237,13 +260,15 @@
 shared_ptr<MulticastUdpFace>
 UdpFactory::createMulticastFace(const std::string& localIp,
                                 const std::string& multicastIp,
-                                const std::string& multicastPort)
+                                const std::string& multicastPort,
+                                const std::string& networkInterfaceName /* "" */)
 {
 
   return createMulticastFace(UdpResolver::syncResolve(localIp,
                                                       multicastPort),
                              UdpResolver::syncResolve(multicastIp,
-                                                      multicastPort));
+                                                      multicastPort),
+                             networkInterfaceName);
 }
 
 void
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index 8d0494c..fa146ad 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -115,6 +115,13 @@
    * If an unicast face is already active on the same local NIC and port, the
    * creation fails and an exception is thrown
    *
+   * \param networkInterfaceName name of the network interface on which the face will be bound
+   *        (Used only on multihomed linux machine with more than one MulticastUdpFace for
+   *        the same multicast group. If specified, will requires CAP_NET_RAW capability)
+   *        An empty string can be provided in other system or in linux machine with only one
+   *        MulticastUdpFace per multicast group
+   *
+   *
    * \returns always a valid pointer to a MulticastUdpFace object, an exception
    *          is thrown if it cannot be created.
    *
@@ -125,12 +132,14 @@
    */
   shared_ptr<MulticastUdpFace>
   createMulticastFace(const udp::Endpoint& localEndpoint,
-                      const udp::Endpoint& multicastEndpoint);
+                      const udp::Endpoint& multicastEndpoint,
+                      const std::string& networkInterfaceName = "");
 
   shared_ptr<MulticastUdpFace>
   createMulticastFace(const std::string& localIp,
                       const std::string& multicastIp,
-                      const std::string& multicastPort);
+                      const std::string& multicastPort,
+                      const std::string& networkInterfaceName = "");
 
   // from Factory
   virtual void
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index 79822f7..8397253 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -498,6 +498,7 @@
 
       if (useMcast && enableV4)
         {
+          std::list<shared_ptr<NetworkInterfaceInfo> > ipv4MulticastInterfaces;
           for (std::list<shared_ptr<NetworkInterfaceInfo> >::const_iterator i = nicList.begin();
                i != nicList.end();
                ++i)
@@ -505,13 +506,35 @@
               const shared_ptr<NetworkInterfaceInfo>& nic = *i;
               if (nic->isUp() && nic->isMulticastCapable() && !nic->ipv4Addresses.empty())
                 {
-                  shared_ptr<MulticastUdpFace> newFace =
-                    factory->createMulticastFace(nic->ipv4Addresses[0].to_string(),
-                                                 mcastGroup, mcastPort);
-
-                  addCreatedFaceToForwarder(newFace);
+                  ipv4MulticastInterfaces.push_back(nic);
                 }
             }
+
+          bool isNicNameNecessary = false;
+
+#if defined(__linux__)
+          if (ipv4MulticastInterfaces.size() > 1)
+            {
+              //On Linux, if we have more than one MulticastUdpFace we need to specify
+              //the name of the interface
+              isNicNameNecessary = true;
+            }
+#endif
+
+          for (std::list<shared_ptr<NetworkInterfaceInfo> >::const_iterator i =
+                 ipv4MulticastInterfaces.begin();
+               i != ipv4MulticastInterfaces.end();
+               ++i)
+            {
+              const shared_ptr<NetworkInterfaceInfo>& nic = *i;
+              shared_ptr<MulticastUdpFace> newFace;
+              newFace = factory->createMulticastFace(nic->ipv4Addresses[0].to_string(),
+                                                     mcastGroup,
+                                                     mcastPort,
+                                                     isNicNameNecessary ? nic->name : "");
+
+              addCreatedFaceToForwarder(newFace);
+            }
         }
     }
 }
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 22aae5a..ace10e1 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -66,7 +66,12 @@
 
     ; UDP multicast settings
     ; NFD creates one UDP multicast face per NIC
-
+    ;
+    ; In multi-homed Linux machines these settings will NOT work without
+    ; root or settings the appropriate permissions:
+    ;
+    ;    sudo setcap cap_net_raw=eip /full/path/nfd
+    ;
     mcast yes ; set to 'no' to disable UDP multicast, default 'yes'
     mcast_port 56363 ; UDP multicast port number
     mcast_group 224.0.23.170 ; UDP multicast group (IPv4 only)