Add support for unicast ethernet faces to utility and helper methods.

Refs #5321

Change-Id: I2e28d092853fc21f59109cfbc23b946d30626a8c
diff --git a/examples/tests/faces_test.py b/examples/tests/faces_test.py
new file mode 100644
index 0000000..0589441
--- /dev/null
+++ b/examples/tests/faces_test.py
@@ -0,0 +1,396 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2021, The University of Memphis,
+#                          Arizona Board of Regents,
+#                          Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+from subprocess import PIPE
+from time import sleep
+
+from mininet.log import setLogLevel, info, debug
+from mininet.topo import Topo
+
+from minindn.minindn import Minindn
+from minindn.apps.app_manager import AppManager
+from minindn.util import MiniNDNCLI, getPopen
+from minindn.apps.nfd import Nfd
+from minindn.apps.nlsr import Nlsr
+from minindn.helpers.experiment import Experiment
+from minindn.helpers.nfdc import Nfdc
+from minindn.helpers.ndn_routing_helper import NdnRoutingHelper
+
+PREFIX = "/example"
+
+def printOutput(output):
+    _out = output.decode("utf-8").split("\n")
+    for _line in _out:
+        info(_line + "\n")
+
+def udp_run():
+    Minindn.cleanUp()
+    Minindn.verifyDependencies()
+
+    # Topology can be created/modified using Mininet topo object
+    topo = Topo()
+    info("Setup\n")
+    # add hosts
+    a = topo.addHost('a')
+    b = topo.addHost('b')
+    c = topo.addHost('c')
+
+    # add links
+    topo.addLink(a, b, delay='10ms', bw=10) # bw = bandwidth
+    topo.addLink(b, c, delay='10ms', bw=10)
+    topo.addLink(a, c, delay='10ms', bw=10)
+
+    ndn = Minindn(topo=topo)
+    ndn.start()
+
+    # configure and start nfd on each node
+    info("Configuring NFD\n")
+    AppManager(ndn, ndn.net.hosts, Nfd, logLevel="DEBUG")
+
+    """
+    There are multiple ways of setting up routes in Mini-NDN
+    refer: https://minindn.memphis.edu/experiment.html#routing-options
+    It can also be set manually as follows. The important bit to note here
+    is the use of the Nfdc command
+    """
+    links = {"a":["b", "c"], "b":["c"]}
+    nfdc_batches = dict()
+    for first in links:
+        for second in links[first]:
+            host1 = ndn.net[first]
+            host2 = ndn.net[second]
+            interface = host2.connectionsTo(host1)[0][0]
+            interface_ip = interface.IP()
+            Nfdc.createFace(host1, interface_ip)
+            Nfdc.registerRoute(host1, PREFIX, interface_ip, cost=0)
+            Nfdc.setStrategy(host1, PREFIX, Nfdc.STRATEGY_ASF)
+    sleep(1) 
+    debug(ndn.net["a"].cmd("nfdc face list"))
+    debug(ndn.net["a"].cmd("nfdc fib list"))
+    debug(ndn.net["a"].cmd("nfdc strategy show /example"))
+
+    # Start ping server
+    info("Starting pings...\n")
+    pingserver_log = open("{}/c/ndnpingserver.log".format(ndn.workDir), "w")
+    getPopen(ndn.net["c"], "ndnpingserver {}".format(PREFIX), stdout=pingserver_log,\
+             stderr=pingserver_log)
+
+    # start ping client
+    ping1 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
+    ping1.wait()
+    printOutput(ping1.stdout.read())
+
+    links = {"a":["b", "c"], "b":["c"]}
+    for first in links:
+        for second in links[first]:
+            host1 = ndn.net[first]
+            host2 = ndn.net[second]
+            interface = host2.connectionsTo(host1)[0][0]
+            interface_ip = interface.IP()
+            Nfdc.unregisterRoute(host1, PREFIX, interface_ip)
+            Nfdc.destroyFace(host1, interface_ip)
+            Nfdc.unsetStrategy(host1, PREFIX)
+    sleep(1)
+    debug(ndn.net["a"].cmd("nfdc face list"))
+    debug(ndn.net["a"].cmd("nfdc fib list"))
+    debug(ndn.net["a"].cmd("nfdc strategy show /example"))
+
+    ping2 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
+    ping2.wait()
+    printOutput(ping2.stdout.read())
+
+    info("\nExperiment Completed!\n")
+    # MiniNDNCLI(ndn.net)
+    ndn.stop()
+
+def eth_run():
+    Minindn.cleanUp()
+    Minindn.verifyDependencies()
+
+    # Topology can be created/modified using Mininet topo object
+    topo = Topo()
+    info("Setup\n")
+    # add hosts
+    a = topo.addHost('a')
+    b = topo.addHost('b')
+    c = topo.addHost('c')
+
+    # add links
+    topo.addLink(a, b, delay='10ms', bw=10) # bw = bandwidth
+    topo.addLink(b, c, delay='10ms', bw=10)
+    topo.addLink(a, c, delay='10ms', bw=10)
+
+    ndn = Minindn(topo=topo)
+    ndn.start()
+
+    # configure and start nfd on each node
+    info("Configuring NFD\n")
+    AppManager(ndn, ndn.net.hosts, Nfd, logLevel="DEBUG")
+
+    """
+    There are multiple ways of setting up routes in Mini-NDN
+    refer: https://minindn.memphis.edu/experiment.html#routing-options
+    It can also be set manually as follows. The important bit to note here
+    is the use of the Nfdc command
+    """
+    links = {"a":["b", "c"], "b":["c"]}
+    nfdc_batches = dict()
+    for first in links:
+        for second in links[first]:
+            host1 = ndn.net[first]
+            host2 = ndn.net[second]
+            sender_interface = host1.connectionsTo(host2)[0][0]
+            interface = host2.connectionsTo(host1)[0][0]
+            interface_addr = interface.MAC()
+            Nfdc.createFace(host1, interface_addr, protocol=Nfdc.PROTOCOL_ETHER, localInterface=sender_interface)
+            Nfdc.registerRoute(host1, PREFIX, interface_addr, cost=0, protocol=Nfdc.PROTOCOL_ETHER)
+            Nfdc.setStrategy(host1, PREFIX, Nfdc.STRATEGY_ASF)
+    sleep(1) 
+    debug(ndn.net["a"].cmd("nfdc face list"))
+    debug(ndn.net["a"].cmd("nfdc fib list"))
+    debug(ndn.net["a"].cmd("nfdc strategy show /example"))
+
+    # Start ping server
+    info("Starting pings...\n")
+    pingserver_log = open("{}/c/ndnpingserver.log".format(ndn.workDir), "w")
+    getPopen(ndn.net["c"], "ndnpingserver {}".format(PREFIX), stdout=pingserver_log,\
+             stderr=pingserver_log)
+
+    # start ping client
+    ping1 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
+    ping1.wait()
+    printOutput(ping1.stdout.read())
+
+    links = {"a":["b", "c"], "b":["c"]}
+    for first in links:
+        for second in links[first]:
+            host1 = ndn.net[first]
+            host2 = ndn.net[second]
+            interface = host2.connectionsTo(host1)[0][0]
+            interface_addr = interface.MAC()
+            Nfdc.unregisterRoute(host1, PREFIX, interface_addr, protocol=Nfdc.PROTOCOL_ETHER)
+            Nfdc.destroyFace(host1, interface_addr, protocol=Nfdc.PROTOCOL_ETHER)
+            Nfdc.unsetStrategy(host1, PREFIX)
+    sleep(1)
+    debug(ndn.net["a"].cmd("nfdc face list"))
+    debug(ndn.net["a"].cmd("nfdc fib list"))
+    debug(ndn.net["a"].cmd("nfdc strategy show /example"))
+
+    ping2 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
+    ping2.wait()
+    printOutput(ping2.stdout.read())
+
+    info("\nExperiment Completed!\n")
+    # MiniNDNCLI(ndn.net)
+    ndn.stop()
+
+def udp_nlsr_run():
+    Minindn.cleanUp()
+    Minindn.verifyDependencies()
+
+    # Topology can be created/modified using Mininet topo object
+    topo = Topo()
+    info("Setup\n")
+    # add hosts
+    a = topo.addHost('a')
+    b = topo.addHost('b')
+    c = topo.addHost('c')
+
+    # add links
+    topo.addLink(a, b, delay='10ms', bw=10) # bw = bandwidth
+    topo.addLink(b, c, delay='10ms', bw=10)
+    topo.addLink(a, c, delay='10ms', bw=10)
+
+    ndn = Minindn(topo=topo)
+    ndn.start()
+
+    nfds = AppManager(ndn, ndn.net.hosts, Nfd)
+    nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr, faceType=Nfdc.PROTOCOL_UDP,
+                       logLevel='ndn.*=TRACE:nlsr.*=TRACE')
+
+    Experiment.checkConvergence(ndn, ndn.net.hosts, 60, quit=False)
+
+    Experiment.setupPing(ndn.net.hosts, Nfdc.STRATEGY_BEST_ROUTE)
+    Experiment.startPctPings(ndn.net, 60)
+
+    sleep(70)
+
+    ndn.stop()
+
+def eth_nlsr_run():
+    Minindn.cleanUp()
+    Minindn.verifyDependencies()
+
+    # Topology can be created/modified using Mininet topo object
+    topo = Topo()
+    info("Setup\n")
+    # add hosts
+    a = topo.addHost('a')
+    b = topo.addHost('b')
+    c = topo.addHost('c')
+
+    # add links
+    topo.addLink(a, b, delay='10ms', bw=10) # bw = bandwidth
+    topo.addLink(b, c, delay='10ms', bw=10)
+    topo.addLink(a, c, delay='10ms', bw=10)
+
+    ndn = Minindn(topo=topo)
+    ndn.start()
+
+    nfds = AppManager(ndn, ndn.net.hosts, Nfd)
+    nlsrs = AppManager(ndn, ndn.net.hosts, Nlsr, faceType=Nfdc.PROTOCOL_ETHER,
+                       logLevel='ndn.*=TRACE:nlsr.*=TRACE')
+
+    Experiment.checkConvergence(ndn, ndn.net.hosts, 60, quit=False)
+
+    Experiment.setupPing(ndn.net.hosts, Nfdc.STRATEGY_BEST_ROUTE)
+    Experiment.startPctPings(ndn.net, 60)
+
+    sleep(70)
+
+    ndn.stop()
+
+def udp_static_run():
+    Minindn.cleanUp()
+    Minindn.verifyDependencies()
+
+    # Topology can be created/modified using Mininet topo object
+    topo = Topo()
+    info("Setup\n")
+    # add hosts
+    a = topo.addHost('a')
+    b = topo.addHost('b')
+    c = topo.addHost('c')
+
+    # add links
+    topo.addLink(a, b, delay='10ms', bw=10) # bw = bandwidth
+    topo.addLink(b, c, delay='10ms', bw=10)
+    topo.addLink(a, c, delay='10ms', bw=10)
+
+    ndn = Minindn(topo=topo)
+    ndn.start()
+
+    nfds = AppManager(ndn, ndn.net.hosts, Nfd)
+    info('Adding static routes to NFD\n')
+    grh = NdnRoutingHelper(ndn.net, Nfdc.PROTOCOL_UDP)
+    # For all host, pass ndn.net.hosts or a list, [ndn.net['a'], ..] or [ndn.net.hosts[0],.]
+    grh.addOrigin([ndn.net['c']], ["/example"])
+    grh.calculateNPossibleRoutes()
+
+    '''
+    prefix "/abc" is advertise from node A, it should be reachable from all other nodes.
+    '''
+    routesFromA = ndn.net['a'].cmd("nfdc route | grep -v '/localhost/nfd'")
+    if '/ndn/b-site/b' not in routesFromA or '/ndn/c-site/c' not in routesFromA:
+        info("Route addition failed\n")
+
+    routesToPrefix = ndn.net['b'].cmd("nfdc fib | grep '/example'")
+    if '/example' not in routesToPrefix:
+        info("Missing route to advertised prefix, Route addition failed\n")
+        ndn.net.stop()
+
+    info('Route addition to NFD completed succesfully\n')
+
+    debug(ndn.net["a"].cmd("nfdc face list"))
+    debug(ndn.net["a"].cmd("nfdc fib list"))
+    debug(ndn.net["a"].cmd("nfdc strategy show /example"))
+
+    # Start ping server
+    info("Starting pings...\n")
+    pingserver_log = open("{}/c/ndnpingserver.log".format(ndn.workDir), "w")
+    getPopen(ndn.net["c"], "ndnpingserver {}".format(PREFIX), stdout=pingserver_log,\
+             stderr=pingserver_log)
+
+    # start ping client
+    ping1 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
+    ping1.wait()
+    printOutput(ping1.stdout.read())
+
+    ndn.stop()
+
+def eth_static_run():
+    Minindn.cleanUp()
+    Minindn.verifyDependencies()
+
+    # Topology can be created/modified using Mininet topo object
+    topo = Topo()
+    info("Setup\n")
+    # add hosts
+    a = topo.addHost('a')
+    b = topo.addHost('b')
+    c = topo.addHost('c')
+
+    # add links
+    topo.addLink(a, b, delay='10ms', bw=10) # bw = bandwidth
+    topo.addLink(b, c, delay='10ms', bw=10)
+    topo.addLink(a, c, delay='10ms', bw=10)
+
+    ndn = Minindn(topo=topo)
+    ndn.start()
+
+    nfds = AppManager(ndn, ndn.net.hosts, Nfd)
+    info('Adding static routes to NFD\n')
+    grh = NdnRoutingHelper(ndn.net, Nfdc.PROTOCOL_ETHER)
+    # For all host, pass ndn.net.hosts or a list, [ndn.net['a'], ..] or [ndn.net.hosts[0],.]
+    grh.addOrigin([ndn.net['c']], ["/example"])
+    grh.calculateNPossibleRoutes()
+
+    '''
+    prefix "/abc" is advertise from node A, it should be reachable from all other nodes.
+    '''
+    routesFromA = ndn.net['a'].cmd("nfdc route | grep -v '/localhost/nfd'")
+    if '/ndn/b-site/b' not in routesFromA or '/ndn/c-site/c' not in routesFromA:
+        info("Route addition failed\n")
+
+    routesToPrefix = ndn.net['b'].cmd("nfdc fib | grep '/example'")
+    if '/example' not in routesToPrefix:
+        info("Missing route to advertised prefix, Route addition failed\n")
+        ndn.net.stop()
+
+    info('Route addition to NFD completed succesfully\n')
+
+    debug(ndn.net["a"].cmd("nfdc face list"))
+    debug(ndn.net["a"].cmd("nfdc fib list"))
+    debug(ndn.net["a"].cmd("nfdc strategy show /example"))
+
+    # Start ping server
+    info("Starting pings...\n")
+    pingserver_log = open("{}/c/ndnpingserver.log".format(ndn.workDir), "w")
+    getPopen(ndn.net["c"], "ndnpingserver {}".format(PREFIX), stdout=pingserver_log,\
+             stderr=pingserver_log)
+
+    # start ping client
+    ping1 = getPopen(ndn.net["a"], "ndnping {} -c 5".format(PREFIX), stdout=PIPE, stderr=PIPE)
+    ping1.wait()
+    printOutput(ping1.stdout.read())
+
+    ndn.stop()
+
+if __name__ == '__main__':
+    setLogLevel("debug")
+    udp_run()
+    eth_run()
+    udp_nlsr_run()
+    eth_nlsr_run()
+    udp_static_run()
+    eth_static_run()
\ No newline at end of file
diff --git a/minindn/apps/nlsr.py b/minindn/apps/nlsr.py
index 12a12d7..43da64f 100644
--- a/minindn/apps/nlsr.py
+++ b/minindn/apps/nlsr.py
@@ -30,7 +30,7 @@
 from mininet.node import Switch
 
 from minindn.apps.application import Application
-from minindn.util import scp, copyExistentFile
+from minindn.util import scp, copyExistentFile, MACToEther
 from minindn.helpers.nfdc import Nfdc
 from minindn.minindn import Minindn
 
@@ -41,7 +41,7 @@
     SYNC_PSYNC = 'psync'
 
     def __init__(self, node, logLevel='NONE', security=False, sync=SYNC_PSYNC,
-                 faceType='udp', nFaces=3, routingType=ROUTING_LINK_STATE, faceDict=None):
+                 faceType=Nfdc.PROTOCOL_UDP, nFaces=3, routingType=ROUTING_LINK_STATE, faceDict=None):
         Application.__init__(self, node)
         try:
             from mn_wifi.node import Node_wifi
@@ -92,7 +92,8 @@
             warn('Check that each node has one radius value and one or two angle value(s).')
             sys.exit(1)
 
-        self.neighborIPs = []
+        self.neighborLocations = []
+        self.interfaceForNeighbor = dict()
         possibleConfPaths = ['/usr/local/etc/ndn/nlsr.conf.sample', '/etc/ndn/nlsr.conf.sample']
         copyExistentFile(node, possibleConfPaths, '{}/nlsr.conf'.format(self.homeDir))
 
@@ -107,8 +108,12 @@
         Minindn.sleep(1)
 
     def createFaces(self):
-        for ip in self.neighborIPs:
-            Nfdc.createFace(self.node, ip, self.faceType, isPermanent=True)
+        for location in self.neighborLocations:
+            if self.faceType == Nfdc.PROTOCOL_ETHER:
+                localIntf = self.interfaceForNeighbor[location]
+                Nfdc.createFace(self.node, location, self.faceType, localInterface=localIntf, isPermanent=True)
+            else:
+                Nfdc.createFace(self.node, location, self.faceType, isPermanent=True)
 
     @staticmethod
     def createKey(host, name, outputFile):
@@ -224,19 +229,27 @@
 
             if node1 == self.node:
                 other = node2
-                ip = other.IP(str(link.intf2))
+                if self.faceType == Nfdc.PROTOCOL_ETHER:
+                    location = MACToEther(link.intf2.MAC())
+                else:
+                    location = link.intf2.IP()
             else:
                 other = node1
-                ip = other.IP(str(link.intf1))
+                if self.faceType == Nfdc.PROTOCOL_ETHER:
+                    location = MACToEther(link.intf1.MAC())
+                else:
+                    location = link.intf1.IP()
 
             linkCost = intf.params.get('delay', '10ms').replace('ms', '')
 
-            self.neighborIPs.append(ip)
+            self.neighborLocations.append(location)
+            if self.faceType == Nfdc.PROTOCOL_ETHER:
+                self.interfaceForNeighbor[location] = intf
 
             self.node.cmd('{} -a neighbors.neighbor \
                           <<<\'name {}{}-site/%C1.Router/cs/{} face-uri {}://{}\n link-cost {}\''
                           .format(self.infocmd, self.network, other.name, other.name,
-                                  self.faceType, ip, linkCost))
+                                  self.faceType, location, linkCost))
 
     def __editNeighborsSectionManual(self):
 
diff --git a/minindn/helpers/ndn_routing_helper.py b/minindn/helpers/ndn_routing_helper.py
index d614926..7ea0de9 100644
--- a/minindn/helpers/ndn_routing_helper.py
+++ b/minindn/helpers/ndn_routing_helper.py
@@ -38,6 +38,8 @@
 from mininet.log import info, debug, error, warn
 from minindn.helpers.nfdc import Nfdc as nfdc
 
+from minindn.util import MACToEther
+
 UNKNOWN_DISTANCE = -1
 HYPERBOLIC_COST_ADJUSTMENT_FACTOR = 1000
 
@@ -297,8 +299,13 @@
     def globalRoutingHelperHandler(self):
         info('Creating faces and adding routes to FIB\n')
 
+        if self.faceType == nfdc.PROTOCOL_ETHER:
+            add_route_method = self.addNodeRoutesEther
+        else:
+            add_route_method = self.addNodeRoutes
+
         res = Parallel(n_jobs=-1, require='sharedmem',
-                       prefer="threads", verbose=1)(delayed(self.addNodeRoutes)(host) for host in self.net.hosts)
+                       prefer="threads", verbose=1)(delayed(add_route_method)(host) for host in self.net.hosts)
 
         info('Processed all the routes to NFD\n')
 
@@ -308,10 +315,20 @@
 
         :param Node node: Node from net object
         """
-        neighborIPs = self.getNeighbor(node)
+        neighborIPs = self.getNeighborIP(node)
         neighborFaces = self.createFaces(node, neighborIPs)
         self.routeAdd(node, neighborFaces)
 
+    def addNodeRoutesEther(self, node):
+        """
+        Create faces to neighbors and add all routes for one node
+
+        :param Node node: Node from net object
+        """
+        neighborAddrs = self.getNeighborEther(node)
+        neighborFaces = self.createEtherFaces(node, neighborAddrs)
+        self.routeAdd(node, neighborFaces)
+
     def addOrigin(self, nodes, prefix):
         """
         Add prefix/s as origin on node/s
@@ -355,6 +372,15 @@
             neighborFaces[k] = faceID
         return neighborFaces
 
+    def createEtherFaces(self, node, neighborLocations):
+        neighborFaces = {}
+        for k, intfLocTuple in neighborLocations.items():
+            localIntf, etherAddr = intfLocTuple 
+            faceID = nfdc.createFace(node, etherAddr, self.faceType, self.permanentFaces, localIntf)
+            if not isinstance(faceID, str): raise ValueError(faceID)
+            neighborFaces[k] = faceID
+        return neighborFaces
+
 
     def routeAdd(self, node, neighborFaces):
         """
@@ -373,8 +399,9 @@
             for prefix in prefixes:
                 # Register routes to all the available destination name prefix/s
                 nfdc.registerRoute(node, prefix, neighborFaces[nextHop], cost=cost)
+
     @staticmethod
-    def getNeighbor(node):
+    def getNeighborIP(node):
         # Nodes to IP mapping
         neighborIPs = defaultdict()
         for intf in node.intfList():
@@ -392,3 +419,21 @@
                 # Used later to create faces
                 neighborIPs[other.name] = ip
         return neighborIPs
+
+    @staticmethod
+    def getNeighborEther(node):
+        # Nodes to IP mapping
+        neighborLocations = defaultdict()
+        for intf in node.intfList():
+            link = intf.link
+            if link:
+                node1, node2 = link.intf1.node, link.intf2.node
+                if node1 == node:
+                    other = node2
+                    intf_location = (link.intf1.name, MACToEther(link.intf2.MAC()))
+                else:
+                    other = node1
+                    intf_location = (link.intf2.name, MACToEther(link.intf1.MAC()))
+                # Used later to create faces
+                neighborLocations[other.name] = intf_location
+        return neighborLocations
\ No newline at end of file
diff --git a/minindn/helpers/nfdc.py b/minindn/helpers/nfdc.py
index f2558d5..08aeccf 100644
--- a/minindn/helpers/nfdc.py
+++ b/minindn/helpers/nfdc.py
@@ -23,6 +23,7 @@
 
 from mininet.log import debug, warn
 from minindn.minindn import Minindn
+from minindn.util import MACToEther
 
 # If needed (e.g. to speed up the process), use a smaller (or larger value) 
 # based on your machines resource (CPU, memory)
@@ -52,6 +53,8 @@
                 'expires {}'.format(expirationInMillis) if expirationInMillis else ''
             )
         else:
+            if protocol == "ether":
+                remoteNode = MACToEther(remoteNode)
             cmd = ('nfdc route add {} {}://{} origin {} cost {} {}{}{}').format(
                 namePrefix,
                 protocol,
@@ -66,22 +69,33 @@
         Minindn.sleep(SLEEP_TIME)
 
     @staticmethod
-    def unregisterRoute(node, namePrefix, remoteNode, origin=255):
+    def unregisterRoute(node, namePrefix, remoteNode, protocol=PROTOCOL_UDP, origin=255):
         cmd = ""
         if remoteNode.isdigit() and not protocol == "fd":
-            cmd = 'nfdc route remove {} {} {}'.format(namePrefix, remoteNode, origin)
+            cmd = 'nfdc route remove {} {} origin {}'.format(namePrefix, remoteNode, origin)
         else:
-            cmd = 'nfdc route remove {} {} {}'.format(namePrefix, remoteNode, origin)
+            if protocol == "ether":
+                remoteNode = MACToEther(remoteNode)
+            cmd = 'nfdc route remove {} {}://{} origin {}'.format(namePrefix, protocol, remoteNode, origin)
         debug(node.cmd(cmd))
         Minindn.sleep(SLEEP_TIME)
 
     @staticmethod
-    def createFace(node, remoteNodeAddress, protocol='udp', isPermanent=False, allowExisting=True):
+    def createFace(node, remoteNodeAddress, protocol=PROTOCOL_UDP, isPermanent=False, localInterface='', allowExisting=True):
         '''Create face in node's NFD instance. Returns FaceID of created face or -1 if failed.'''
-        cmd = ('nfdc face create {}://{} {}'.format(
+        if protocol == "ether" and not localInterface:
+            warn("Cannot create ethernet face without local interface!")
+            return
+        elif protocol != "ether" and localInterface:
+            warn("Cannot create non-ethernet face with local interface specified!")
+            return
+        elif protocol == "ether" and localInterface:
+            remoteNodeAddress = MACToEther(remoteNodeAddress)
+        cmd = ('nfdc face create {}://{} {}{}'.format(
             protocol,
             remoteNodeAddress,
-            'permanent' if isPermanent else 'persistent'
+            'local dev://{} '.format(localInterface) if localInterface else '',
+            'persistency permanent' if isPermanent else 'persistency persistent'
         ))
         output = node.cmd(cmd)
         debug(output)
@@ -95,10 +109,12 @@
         return -1
 
     @staticmethod
-    def destroyFace(node, remoteNode, protocol='udp'):
+    def destroyFace(node, remoteNode, protocol=PROTOCOL_UDP):
         if remoteNode.isdigit() and not protocol == "fd":
-            debug(node.cmd('nfdc face destroy {}'.format(protocol, remoteNode)))
+            debug(node.cmd('nfdc face destroy {}'.format(remoteNode)))
         else:
+            if protocol == "ether":
+                remoteNode = MACToEther(remoteNode)
             debug(node.cmd('nfdc face destroy {}://{}'.format(protocol, remoteNode)))
         Minindn.sleep(SLEEP_TIME)
 
@@ -116,13 +132,17 @@
         Minindn.sleep(SLEEP_TIME)
 
     @staticmethod
-    def getFaceId(node, remoteNodeAddress, localEndpoint=None, protocol="udp", portNum="6363"):
+    def getFaceId(node, remoteNodeAddress, localEndpoint=None, protocol=PROTOCOL_UDP, portNum="6363"):
         '''Returns the faceId for a remote node based on FaceURI, or -1 if a face is not found'''
         #Should this be cached or is the hit not worth it?
         local = ""
         if localEndpoint:
             local = " local {}".format(localEndpoint)
-        output = node.cmd("nfdc face list remote {}://{}:{}{}".format(protocol, remoteNodeAddress, portNum, local))
+        if protocol == "ether":
+            remoteNodeAddress = MACToEther(remoteNodeAddress)
+            output = node.cmd("nfdc face list remote {}://{}{}".format(protocol, remoteNodeAddress, local))
+        else:
+            output = node.cmd("nfdc face list remote {}://{}:{}{}".format(protocol, remoteNodeAddress, portNum, local))
         debug(output)
         Minindn.sleep(SLEEP_TIME)
         # This is fragile but we don't have that many better options
diff --git a/minindn/util.py b/minindn/util.py
index 23092da..d44f20c 100644
--- a/minindn/util.py
+++ b/minindn/util.py
@@ -28,6 +28,10 @@
 
 from mininet.cli import CLI
 
+from mininet.log import error
+
+import re
+
 sshbase = ['ssh', '-q', '-t', '-i/home/mininet/.ssh/id_rsa']
 scpbase = ['scp', '-i', '/home/mininet/.ssh/id_rsa']
 devnull = open('/dev/null', 'w')
@@ -84,6 +88,17 @@
     return host.popen(cmd, cwd=host.params['params']['homeDir'],
                       env=popenGetEnv(host, envDict), **params)
 
+def MACToEther(mac):
+    # We use the regex filters from face-uri.cpp in ndn-cxx with minor modifications
+    if re.match('^\[((?:[a-fA-F0-9]{1,2}\:){5}(?:[a-fA-F0-9]{1,2}))\]$', mac):
+        return mac
+    elif re.match('^((?:[a-fA-F0-9]{1,2}\:){5}(?:[a-fA-F0-9]{1,2}))$', mac):
+        # URI syntax requires nfdc to use brackets for MAC and ethernet addresses due
+        # to the use of colons as separators. Incomplete brackets are a code issue.
+        return '[%s]' % mac
+    error('Potentially malformed MAC address, passing without alteration: %s' % mac)
+    return mac
+
 class MiniNDNCLI(CLI):
     prompt = 'mini-ndn> '
     def __init__(self, mininet, stdin=sys.stdin, script=None):