core: enable wildcard matching on interface names for whitelist/blacklist

Change-Id: Iabf5084028d7e8c4a26ec5289c331f4a779c0bf7
Refs: #4009
diff --git a/core/network-interface-predicate.cpp b/core/network-interface-predicate.cpp
index 9c1bb11..81f199b 100644
--- a/core/network-interface-predicate.cpp
+++ b/core/network-interface-predicate.cpp
@@ -29,6 +29,8 @@
 #include "network-interface.hpp"
 #include "network.hpp"
 
+#include <fnmatch.h>
+
 namespace nfd {
 
 NetworkInterfacePredicate::NetworkInterfacePredicate()
@@ -95,6 +97,14 @@
 }
 
 static bool
+doesMatchPattern(const std::string& ifname, const std::string& pattern)
+{
+  // use fnmatch(3) to provide unix glob-style matching for interface names
+  // fnmatch returns 0 if there is a match
+  return ::fnmatch(pattern.data(), ifname.data(), 0) == 0;
+}
+
+static bool
 doesMatchRule(const NetworkInterfaceInfo& netif, const std::string& rule)
 {
   // if '/' is in rule, this is a subnet, check if IP in subnet
@@ -108,7 +118,7 @@
   }
 
   return rule == "*" ||
-         netif.name == rule ||
+         doesMatchPattern(netif.name, rule) ||
          netif.etherAddress.toString() == rule;
 }
 
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 819e038..fe3efef 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -44,7 +44,6 @@
 ; The tables section configures the CS, PIT, FIB, Strategy Choice, and Measurements
 tables
 {
-
   ; ContentStore size limit in number of packets
   ; default is 65536, about 500MB with 8KB packet size
   cs_max_packets 65536
@@ -118,7 +117,7 @@
     keep_alive_interval 25; interval (seconds) between keep-alive refreshes
 
     ; UDP multicast settings
-    ; NFD creates one UDP multicast face per NIC
+    ; By default, 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:
@@ -130,11 +129,10 @@
     mcast_group 224.0.23.170 ; UDP multicast group (IPv4 only)
 
     ; Whitelist and blacklist can contain, in no particular order:
-    ; interface names (e.g., ifname eth0),
-    ; mac addresses (e.g., ether 85:3b:4d:d3:5f:c2),
-    ; subnets (e.g., subnet 192.0.2.0/24, note that only IPv4 is supported here),
-    ; or a wildcard (*) that matches all interfaces.
-
+    ; interface names, including wildcard patterns (e.g., 'ifname eth0', 'ifname en*', 'ifname wlp?s0'),
+    ; mac addresses (e.g., 'ether 85:3b:4d:d3:5f:c2'),
+    ; subnets (e.g., 'subnet 192.0.2.0/24', note that only IPv4 is supported here),
+    ; or a single asterisk ('*') that matches all interfaces.
     whitelist
     {
       *
@@ -170,17 +168,16 @@
   @IF_HAVE_LIBPCAP@ether
   @IF_HAVE_LIBPCAP@{
   @IF_HAVE_LIBPCAP@  ; Ethernet multicast settings
-  @IF_HAVE_LIBPCAP@  ; NFD creates one Ethernet multicast face per NIC
+  @IF_HAVE_LIBPCAP@  ; By default, NFD creates one Ethernet multicast face per NIC
   @IF_HAVE_LIBPCAP@
   @IF_HAVE_LIBPCAP@  mcast yes ; set to 'no' to disable Ethernet multicast, default 'yes'
   @IF_HAVE_LIBPCAP@  mcast_group 01:00:5E:00:17:AA ; Ethernet multicast group
   @IF_HAVE_LIBPCAP@
   @IF_HAVE_LIBPCAP@  ; Whitelist and blacklist can contain, in no particular order:
-  @IF_HAVE_LIBPCAP@  ; interface names (e.g., ifname eth0),
-  @IF_HAVE_LIBPCAP@  ; mac addresses (e.g., ether 85:3b:4d:d3:5f:c2),
-  @IF_HAVE_LIBPCAP@  ; subnets (e.g., subnet 192.0.2.0/24, note that only IPv4 is supported here),
-  @IF_HAVE_LIBPCAP@  ; or a wildcard (*) that matches all interfaces.
-  @IF_HAVE_LIBPCAP@
+  @IF_HAVE_LIBPCAP@  ; interface names, including wildcard patterns (e.g., 'ifname eth0', 'ifname en*', 'ifname wlp?s0'),
+  @IF_HAVE_LIBPCAP@  ; mac addresses (e.g., 'ether 85:3b:4d:d3:5f:c2'),
+  @IF_HAVE_LIBPCAP@  ; subnets (e.g., 'subnet 192.0.2.0/24', note that only IPv4 is supported here),
+  @IF_HAVE_LIBPCAP@  ; or a single asterisk ('*') that matches all interfaces.
   @IF_HAVE_LIBPCAP@  whitelist
   @IF_HAVE_LIBPCAP@  {
   @IF_HAVE_LIBPCAP@    *
diff --git a/tests/core/network-interface-predicate.t.cpp b/tests/core/network-interface-predicate.t.cpp
index 75beb81..4b6a375 100644
--- a/tests/core/network-interface-predicate.t.cpp
+++ b/tests/core/network-interface-predicate.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -61,6 +61,14 @@
         {address_v6::from_string("2001:db8::1")},
         address_v4::from_string("198.51.100.255"),
         IFF_MULTICAST | IFF_BROADCAST | IFF_UP});
+    interfaces.push_back(
+      NetworkInterfaceInfo{3, "enp68s0f1",
+        ethernet::Address::fromString("3e:15:c2:8b:65:03"),
+        {address_v4::from_string("192.168.2.3")},
+        {},
+        address_v4::from_string("192.168.2.255"),
+        IFF_UP});
+
   }
 
   void
@@ -94,6 +102,7 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), true);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), true);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), true);
 }
 
 BOOST_AUTO_TEST_CASE(EmptyWhitelist)
@@ -105,6 +114,7 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), false);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), false);
 }
 
 BOOST_AUTO_TEST_CASE(WildcardBlacklist)
@@ -117,6 +127,7 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), false);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), false);
 }
 
 BOOST_AUTO_TEST_CASE(IfnameWhitelist)
@@ -130,6 +141,7 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), true);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), true);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), false);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), false);
 }
 
 BOOST_AUTO_TEST_CASE(IfnameBlacklist)
@@ -143,6 +155,72 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), true);
+}
+
+BOOST_AUTO_TEST_CASE(IfnameWildcardStart)
+{
+  parseConfig("whitelist\n"
+              "{\n"
+              "  ifname enp*\n"
+              "}");
+
+  BOOST_CHECK_EQUAL(predicate(interfaces[0]), false);
+  BOOST_CHECK_EQUAL(predicate(interfaces[1]), false);
+  BOOST_CHECK_EQUAL(predicate(interfaces[2]), false);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), true);
+}
+
+BOOST_AUTO_TEST_CASE(IfnameWildcardMiddle)
+{
+  parseConfig("whitelist\n"
+              "{\n"
+              "  ifname *th*\n"
+              "}");
+
+  BOOST_CHECK_EQUAL(predicate(interfaces[0]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[1]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[2]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), false);
+}
+
+BOOST_AUTO_TEST_CASE(IfnameWildcardDouble)
+{
+  parseConfig("whitelist\n"
+              "{\n"
+              "  ifname eth**\n"
+              "}");
+
+  BOOST_CHECK_EQUAL(predicate(interfaces[0]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[1]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[2]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), false);
+}
+
+BOOST_AUTO_TEST_CASE(IfnameWildcardOnly)
+{
+  parseConfig("whitelist\n"
+              "{\n"
+              "  ifname *\n"
+              "}");
+
+  BOOST_CHECK_EQUAL(predicate(interfaces[0]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[1]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[2]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), true);
+}
+
+BOOST_AUTO_TEST_CASE(IfnameQuestionMark)
+{
+  parseConfig("whitelist\n"
+              "{\n"
+              "  ifname eth?\n"
+              "}");
+
+  BOOST_CHECK_EQUAL(predicate(interfaces[0]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[1]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[2]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), false);
 }
 
 BOOST_AUTO_TEST_CASE(IfnameMalformed)
@@ -166,6 +244,7 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), true);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), true);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), false);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), false);
 }
 
 BOOST_AUTO_TEST_CASE(EtherBlacklist)
@@ -179,6 +258,7 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), true);
 }
 
 BOOST_AUTO_TEST_CASE(EtherMalformed)
@@ -201,6 +281,7 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), true);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), false);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), true);
 }
 
 BOOST_AUTO_TEST_CASE(SubnetBlacklist)
@@ -213,6 +294,7 @@
   BOOST_CHECK_EQUAL(predicate(interfaces[0]), true);
   BOOST_CHECK_EQUAL(predicate(interfaces[1]), false);
   BOOST_CHECK_EQUAL(predicate(interfaces[2]), true);
+  BOOST_CHECK_EQUAL(predicate(interfaces[3]), false);
 }
 
 BOOST_AUTO_TEST_CASE(SubnetMalformed)