Send PIM Join/Prune messages based on events from the McastService.

Also change Interface to return a list of addresses rather than a set
to allow applications to rely on the order of configuration

Change-Id: Ie7f62fee507639325ee0a77b8db4088dae34597e
diff --git a/apps/pim/pom.xml b/apps/pim/pom.xml
index 80283e4..9685aee 100644
--- a/apps/pim/pom.xml
+++ b/apps/pim/pom.xml
@@ -47,6 +47,13 @@
 
         <dependency>
             <groupId>org.onosproject</groupId>
+            <artifactId>onos-app-routing-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
             <artifactId>onos-cli</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/apps/pim/src/main/java/org/onosproject/pim/cli/PimInterfacesListCommand.java b/apps/pim/src/main/java/org/onosproject/pim/cli/PimInterfacesListCommand.java
index c83871e..64dd207 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/cli/PimInterfacesListCommand.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/cli/PimInterfacesListCommand.java
@@ -31,6 +31,7 @@
 public class PimInterfacesListCommand extends AbstractShellCommand {
 
     private static final String FORMAT = "interfaceName=%s, holdTime=%s, priority=%s, genId=%s";
+    private static final String ROUTE_FORMAT = "    %s";
 
     @Override
     protected void execute() {
@@ -38,10 +39,13 @@
 
         Set<PIMInterface> interfaces = interfaceService.getPimInterfaces();
 
-        interfaces.forEach(
-                pimIntf -> print(FORMAT, pimIntf.getInterface().name(),
-                        pimIntf.getHoldtime(), pimIntf.getPriority(),
-                        pimIntf.getGenerationId()));
+        interfaces.forEach(pimIntf -> {
+            print(FORMAT, pimIntf.getInterface().name(),
+                    pimIntf.getHoldtime(), pimIntf.getPriority(),
+                    pimIntf.getGenerationId());
+
+            pimIntf.getRoutes().forEach(route -> print(ROUTE_FORMAT, route));
+        });
     }
 
 }
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
index ce28908..1d09e35 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
@@ -22,6 +22,7 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.PIM;
+import org.onlab.packet.pim.PIMAddrUnicast;
 import org.onlab.packet.pim.PIMHello;
 import org.onlab.packet.pim.PIMHelloOption;
 import org.onlab.packet.pim.PIMJoinPrune;
@@ -30,6 +31,7 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.mcast.McastRoute;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.PacketService;
 import org.slf4j.Logger;
@@ -37,9 +39,11 @@
 import java.nio.ByteBuffer;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -55,6 +59,9 @@
 
     private final Logger log = getLogger(getClass());
 
+    private static final int JOIN_PERIOD = 60;
+    private static final double HOLD_TIME_MULTIPLIER = 3.5;
+
     private final PacketService packetService;
 
     private Interface onosInterface;
@@ -82,6 +89,8 @@
     // A map of all our PIM neighbors keyed on our neighbors IP address
     private Map<IpAddress, PIMNeighbor> pimNeighbors = new HashMap<>();
 
+    private Map<McastRoute, RouteData> routes = new ConcurrentHashMap<>();
+
     /**
      * Create a PIMInterface from an ONOS Interface.
      *
@@ -151,8 +160,8 @@
      *
      * @return a set of Ip Addresses on this interface
      */
-    public Set<InterfaceIpAddress> getIpAddresses() {
-        return onosInterface.ipAddresses();
+    public List<InterfaceIpAddress> getIpAddresses() {
+        return onosInterface.ipAddressesList();
     }
 
     /**
@@ -161,12 +170,12 @@
      * @return the choosen IP address or null if none
      */
     public IpAddress getIpAddress() {
-        if (onosInterface.ipAddresses().isEmpty()) {
+        if (onosInterface.ipAddressesList().isEmpty()) {
             return null;
         }
 
         IpAddress ipaddr = null;
-        for (InterfaceIpAddress ifipaddr : onosInterface.ipAddresses()) {
+        for (InterfaceIpAddress ifipaddr : onosInterface.ipAddressesList()) {
             ipaddr = ifipaddr.ipAddress();
             break;
         }
@@ -218,6 +227,10 @@
         return pimNeighbors.values();
     }
 
+    public Collection<McastRoute> getRoutes() {
+        return routes.keySet();
+    }
+
     /**
      * Checks whether any of our neighbors have expired, and cleans up their
      * state if they have.
@@ -402,6 +415,100 @@
 
     }
 
+    public void addRoute(McastRoute route, IpAddress nextHop, MacAddress nextHopMac) {
+        RouteData data = new RouteData(nextHop, nextHopMac);
+        routes.put(route, data);
+
+        sendJoinPrune(route, data, true);
+    }
+
+    public void removeRoute(McastRoute route) {
+        RouteData data = routes.remove(route);
+
+        if (data != null) {
+            sendJoinPrune(route, data, false);
+        }
+    }
+
+    public void sendJoins() {
+        routes.entrySet().forEach(entry -> {
+            if (entry.getValue().timestamp + TimeUnit.SECONDS.toMillis(JOIN_PERIOD) >
+                    System.currentTimeMillis()) {
+                return;
+            }
+
+            sendJoinPrune(entry.getKey(), entry.getValue(), true);
+        });
+    }
+
+    private void sendJoinPrune(McastRoute route, RouteData data, boolean join) {
+        PIMJoinPrune jp = new PIMJoinPrune();
+
+        jp.addJoinPrune(route.source().toIpPrefix(), route.group().toIpPrefix(), join);
+        jp.setHoldTime(join ? (short) Math.floor(JOIN_PERIOD * HOLD_TIME_MULTIPLIER) : 0);
+        jp.setUpstreamAddr(new PIMAddrUnicast(data.ipAddress.toString()));
+
+        PIM pim = new PIM();
+        pim.setPIMType(PIM.TYPE_JOIN_PRUNE_REQUEST);
+        pim.setPayload(jp);
+
+        IPv4 ipv4 = new IPv4();
+        ipv4.setDestinationAddress(PIM.PIM_ADDRESS.getIp4Address().toInt());
+        ipv4.setSourceAddress(getIpAddress().getIp4Address().toInt());
+        ipv4.setProtocol(IPv4.PROTOCOL_PIM);
+        ipv4.setTtl((byte) 1);
+        ipv4.setDiffServ((byte) 0xc0);
+        ipv4.setPayload(pim);
+
+        Ethernet eth = new Ethernet();
+        eth.setSourceMACAddress(onosInterface.mac());
+        eth.setDestinationMACAddress(MacAddress.valueOf("01:00:5E:00:00:0d"));
+        eth.setEtherType(Ethernet.TYPE_IPV4);
+        eth.setPayload(ipv4);
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(onosInterface.connectPoint().port())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(onosInterface.connectPoint().deviceId(),
+                treatment, ByteBuffer.wrap(eth.serialize())));
+
+        data.timestamp = System.currentTimeMillis();
+    }
+
+    /*private void sendPrune(McastRoute route, RouteData data) {
+        PIMJoinPrune jp = new PIMJoinPrune();
+
+        jp.addJoinPrune(route.source().toIpPrefix(), route.group().toIpPrefix(), false);
+        jp.setHoldTime((short) 0);
+        jp.setUpstreamAddr(new PIMAddrUnicast(data.ipAddress.toString()));
+
+        PIM pim = new PIM();
+        pim.setPIMType(PIM.TYPE_JOIN_PRUNE_REQUEST);
+        pim.setPayload(jp);
+
+        IPv4 ipv4 = new IPv4();
+        ipv4.setDestinationAddress(PIM.PIM_ADDRESS.getIp4Address().toInt());
+        ipv4.setSourceAddress(getIpAddress().getIp4Address().toInt());
+        ipv4.setProtocol(IPv4.PROTOCOL_PIM);
+        ipv4.setTtl((byte) 1);
+        ipv4.setDiffServ((byte) 0xc0);
+        ipv4.setPayload(pim);
+
+        Ethernet eth = new Ethernet();
+        eth.setSourceMACAddress(onosInterface.mac());
+        eth.setDestinationMACAddress(MacAddress.valueOf("01:00:5E:00:00:0d"));
+        eth.setEtherType(Ethernet.TYPE_IPV4);
+        eth.setPayload(ipv4);
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(onosInterface.connectPoint().port())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(onosInterface.connectPoint().deviceId(),
+                treatment, ByteBuffer.wrap(eth.serialize())));
+    }*/
+
     /**
      * Returns a builder for a PIM interface.
      *
@@ -514,4 +621,16 @@
         }
 
     }
+
+    private static class RouteData {
+        public final IpAddress ipAddress;
+        public final MacAddress macAddress;
+        public long timestamp;
+
+        public RouteData(IpAddress ip, MacAddress mac) {
+            this.ipAddress = ip;
+            this.macAddress = mac;
+            timestamp = System.currentTimeMillis();
+        }
+    }
 }
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceManager.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceManager.java
index 9f56f10..342ef14 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceManager.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterfaceManager.java
@@ -29,12 +29,22 @@
 import org.onosproject.incubator.net.intf.InterfaceListener;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
+
+import org.onosproject.net.Host;
+
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
 import org.onosproject.net.config.NetworkConfigRegistry;
 import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.mcast.McastEvent;
+import org.onosproject.net.mcast.McastListener;
+import org.onosproject.net.mcast.McastRoute;
+import org.onosproject.net.mcast.MulticastRouteService;
 import org.onosproject.net.packet.PacketService;
+import org.onosproject.routing.RouteEntry;
+import org.onosproject.routing.RoutingService;
 import org.slf4j.Logger;
 
 import java.util.Map;
@@ -73,6 +83,8 @@
 
     private final int timeoutTaskPeriod = DEFAULT_TASK_PERIOD_MS;
 
+    private final int joinTaskPeriod = 10000;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected PacketService packetService;
 
@@ -82,13 +94,26 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected InterfaceService interfaceService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MulticastRouteService multicastRouteService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected RoutingService unicastRoutingService;
+
     // Store PIM Interfaces in a map key'd by ConnectPoint
     private final Map<ConnectPoint, PIMInterface> pimInterfaces = Maps.newConcurrentMap();
 
+    private final Map<McastRoute, PIMInterface> routes = Maps.newConcurrentMap();
+
     private final InternalNetworkConfigListener configListener =
             new InternalNetworkConfigListener();
     private final InternalInterfaceListener interfaceListener =
             new InternalInterfaceListener();
+    private final InternalMulticastListener multicastListener =
+            new InternalMulticastListener();
 
     private final ConfigFactory<ConnectPoint, PimInterfaceConfig> pimConfigFactory
             = new ConfigFactory<ConnectPoint, PimInterfaceConfig>(
@@ -115,6 +140,9 @@
 
         networkConfig.addListener(configListener);
         interfaceService.addListener(interfaceListener);
+        multicastRouteService.addListener(multicastListener);
+
+        multicastRouteService.getRoutes().forEach(this::addRoute);
 
         // Schedule the periodic hello sender.
         scheduledExecutorService.scheduleAtFixedRate(
@@ -128,6 +156,11 @@
                         () -> pimInterfaces.values().forEach(PIMInterface::checkNeighborTimeouts)),
                 0, timeoutTaskPeriod, TimeUnit.MILLISECONDS);
 
+        scheduledExecutorService.scheduleAtFixedRate(
+                SafeRecurringTask.wrap(
+                        () -> pimInterfaces.values().forEach(PIMInterface::sendJoins)),
+                0, joinTaskPeriod, TimeUnit.MILLISECONDS);
+
         log.info("Started");
     }
 
@@ -135,6 +168,7 @@
     public void deactivate() {
         interfaceService.removeListener(interfaceListener);
         networkConfig.removeListener(configListener);
+        multicastRouteService.removeListener(multicastListener);
         networkConfig.unregisterConfigFactory(pimConfigFactory);
 
         // Shutdown the periodic hello task.
@@ -202,6 +236,65 @@
         return builder.build();
     }
 
+    private void addRoute(McastRoute route) {
+        PIMInterface pimInterface = getSourceInterface(route);
+
+        if (pimInterface == null) {
+            return;
+        }
+
+        routes.put(route, pimInterface);
+    }
+
+    private void removeRoute(McastRoute route) {
+        PIMInterface pimInterface = routes.remove(route);
+
+        if (pimInterface == null) {
+            return;
+        }
+
+        pimInterface.removeRoute(route);
+    }
+
+    private PIMInterface getSourceInterface(McastRoute route) {
+        RouteEntry routeEntry = unicastRoutingService.getLongestMatchableRouteEntry(route.source());
+
+        if (routeEntry == null) {
+            log.warn("No route to source {}", route.source());
+            return null;
+        }
+
+        Interface intf = interfaceService.getMatchingInterface(routeEntry.nextHop());
+
+        if (intf == null) {
+            log.warn("No interface with route to next hop {}", routeEntry.nextHop());
+            return null;
+        }
+
+        PIMInterface pimInterface = pimInterfaces.get(intf.connectPoint());
+
+        if (pimInterface == null) {
+            log.warn("PIM is not enabled on interface {}", intf);
+            return null;
+        }
+
+        Set<Host> hosts = hostService.getHostsByIp(routeEntry.nextHop());
+        Host host = null;
+        for (Host h : hosts) {
+            if (h.vlan().equals(intf.vlan())) {
+                host = h;
+            }
+        }
+        if (host == null) {
+            log.warn("Next hop host entry not found: {}", routeEntry.nextHop());
+            return null;
+        }
+
+        pimInterface.addRoute(route, routeEntry.nextHop(), host.mac());
+
+        return pimInterface;
+    }
+
     /**
      * Listener for network config events.
      */
@@ -261,4 +354,26 @@
             }
         }
     }
+
+    /**
+     * Listener for multicast route events.
+     */
+    private class InternalMulticastListener implements McastListener {
+        @Override
+        public void event(McastEvent event) {
+            switch (event.type()) {
+            case ROUTE_ADDED:
+                addRoute(event.subject().route());
+                break;
+            case ROUTE_REMOVED:
+                removeRoute(event.subject().route());
+                break;
+            case SOURCE_ADDED:
+            case SINK_ADDED:
+            case SINK_REMOVED:
+            default:
+                break;
+            }
+        }
+    }
 }
diff --git a/apps/routing/src/test/java/org/onosproject/routing/impl/IntentSynchronizerTest.java b/apps/routing/src/test/java/org/onosproject/routing/impl/IntentSynchronizerTest.java
index 351a099..527faf2 100644
--- a/apps/routing/src/test/java/org/onosproject/routing/impl/IntentSynchronizerTest.java
+++ b/apps/routing/src/test/java/org/onosproject/routing/impl/IntentSynchronizerTest.java
@@ -15,9 +15,9 @@
  */
 package org.onosproject.routing.impl;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.MoreExecutors;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.junit.TestUtils;
@@ -57,6 +57,7 @@
 
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
@@ -124,7 +125,7 @@
      * Sets up InterfaceService.
      */
     private void setUpInterfaceService() {
-        Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
+        List<InterfaceIpAddress> interfaceIpAddresses1 = Lists.newArrayList();
         interfaceIpAddresses1.add(new InterfaceIpAddress(
                 IpAddress.valueOf("192.168.10.101"),
                 IpPrefix.valueOf("192.168.10.0/24")));
@@ -133,7 +134,7 @@
                 VlanId.NONE);
         interfaces.add(sw1Eth1);
 
-        Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
+        List<InterfaceIpAddress> interfaceIpAddresses2 = Lists.newArrayList();
         interfaceIpAddresses2.add(
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
                                        IpPrefix.valueOf("192.168.20.0/24")));
@@ -142,7 +143,7 @@
                 VlanId.NONE);
         interfaces.add(sw2Eth1);
 
-        Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
+        List<InterfaceIpAddress> interfaceIpAddresses3 = Lists.newArrayList();
         interfaceIpAddresses3.add(
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
                                        IpPrefix.valueOf("192.168.30.0/24")));
@@ -155,7 +156,7 @@
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"),
                                        IpPrefix.valueOf("192.168.40.0/24"));
         Interface sw4Eth1 = new Interface(SW4_ETH1,
-                                          Sets.newHashSet(interfaceIpAddress4),
+                                          Lists.newArrayList(interfaceIpAddress4),
                                           MacAddress.valueOf("00:00:00:00:00:04"),
                                           VlanId.vlanId((short) 1));
 
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
index cfb0c79..48f097e 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
@@ -173,7 +173,7 @@
             new InterfaceIpAddress(IpAddress.valueOf("192.168.10.101"),
                                    IpPrefix.valueOf("192.168.10.0/24"));
         Interface intfsw1eth1 = new Interface(s1Eth1,
-                Collections.singleton(ia1),
+                Collections.singletonList(ia1),
                 MacAddress.valueOf("00:00:00:00:00:01"),
                 VlanId.NONE);
 
@@ -183,7 +183,7 @@
             new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
                                    IpPrefix.valueOf("192.168.20.0/24"));
         Interface intfsw2eth1 = new Interface(s2Eth1,
-                Collections.singleton(ia2),
+                Collections.singletonList(ia2),
                 MacAddress.valueOf("00:00:00:00:00:02"),
                 VlanId.NONE);
         configuredInterfaces.put(interfaceSw2Eth1, intfsw2eth1);
@@ -193,7 +193,7 @@
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
                         IpPrefix.valueOf("192.168.30.0/24"));
         Interface intfsw2eth1intf2 = new Interface(s2Eth1,
-                Collections.singleton(ia3),
+                Collections.singletonList(ia3),
                 MacAddress.valueOf("00:00:00:00:00:03"),
                 VlanId.NONE);
         configuredInterfaces.put(interfaceSw2Eth1intf2, intfsw2eth1intf2);
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
index 1c70276..3fe048b 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.sdnip;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import org.junit.Before;
 import org.junit.Test;
@@ -53,6 +54,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -151,7 +153,7 @@
      * Sets up InterfaceService.
      */
     private void setUpInterfaceService() {
-        Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
+        List<InterfaceIpAddress> interfaceIpAddresses1 = Lists.newArrayList();
         interfaceIpAddresses1.add(new InterfaceIpAddress(
                 IpAddress.valueOf("192.168.10.101"),
                 IpPrefix.valueOf("192.168.10.0/24")));
@@ -160,7 +162,7 @@
                 VlanId.NONE);
         interfaces.add(sw1Eth1);
 
-        Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
+        List<InterfaceIpAddress> interfaceIpAddresses2 = Lists.newArrayList();
         interfaceIpAddresses2.add(
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
                         IpPrefix.valueOf("192.168.20.0/24")));
@@ -169,7 +171,7 @@
                 VlanId.NONE);
         interfaces.add(sw2Eth1);
 
-        Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
+        List<InterfaceIpAddress> interfaceIpAddresses3 = Lists.newArrayList();
         interfaceIpAddresses3.add(
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
                         IpPrefix.valueOf("192.168.30.0/24")));
@@ -182,7 +184,7 @@
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"),
                         IpPrefix.valueOf("192.168.40.0/24"));
         Interface sw4Eth1 = new Interface(SW4_ETH1,
-                Sets.newHashSet(interfaceIpAddress4),
+                Lists.newArrayList(interfaceIpAddress4),
                 MacAddress.valueOf("00:00:00:00:00:04"),
                 VlanId.vlanId((short) 1));
 
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index f222bb3..bf8c4d7 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
@@ -137,7 +137,7 @@
                 // skip if there is no corresponding device for this ConenctPoint
                 if (info != null) {
                     // Extract subnet information
-                    Set<InterfaceIpAddress> interfaceAddresses = networkInterface.ipAddresses();
+                    List<InterfaceIpAddress> interfaceAddresses = networkInterface.ipAddressesList();
                     interfaceAddresses.forEach(interfaceAddress -> {
                         // Do not add /0 and /32 to gateway IP list
                         int prefixLength = interfaceAddress.subnetAddress().prefixLength();
@@ -495,4 +495,4 @@
     public Set<ConnectPoint> excludedPorts() {
         return excludedPorts;
     }
-}
\ No newline at end of file
+}
diff --git a/cli/src/main/java/org/onosproject/cli/net/InterfaceAddCommand.java b/cli/src/main/java/org/onosproject/cli/net/InterfaceAddCommand.java
index ae4e410..cee8c08 100644
--- a/cli/src/main/java/org/onosproject/cli/net/InterfaceAddCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/InterfaceAddCommand.java
@@ -16,7 +16,7 @@
 
 package org.onosproject.cli.net;
 
-import com.google.common.collect.Sets;
+import com.google.common.collect.Lists;
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
@@ -28,7 +28,7 @@
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.host.InterfaceIpAddress;
 
-import java.util.Set;
+import java.util.List;
 
 /**
  * Adds a new interface configuration.
@@ -66,7 +66,7 @@
     protected void execute() {
         InterfaceAdminService interfaceService = get(InterfaceAdminService.class);
 
-        Set<InterfaceIpAddress> ipAddresses = Sets.newHashSet();
+        List<InterfaceIpAddress> ipAddresses = Lists.newArrayList();
         if (ips != null) {
             for (String strIp : ips) {
                 ipAddresses.add(InterfaceIpAddress.valueOf(strIp));
diff --git a/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java b/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java
index 3cd2ca2..6260af7 100644
--- a/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java
+++ b/core/net/src/test/java/org/onosproject/net/host/impl/HostMonitorTest.java
@@ -185,7 +185,7 @@
 
         InterfaceService interfaceService = createMock(InterfaceService.class);
         expect(interfaceService.getMatchingInterface(TARGET_IPV4_ADDR))
-                .andReturn(new Interface(cp, Collections.singleton(IA1), sourceMac, VlanId.NONE))
+                .andReturn(new Interface(cp, Collections.singletonList(IA1), sourceMac, VlanId.NONE))
                 .anyTimes();
         replay(interfaceService);
 
@@ -253,7 +253,7 @@
 
         InterfaceService interfaceService = createMock(InterfaceService.class);
         expect(interfaceService.getMatchingInterface(TARGET_IPV6_ADDR))
-                .andReturn(new Interface(cp, Collections.singleton(IA2), sourceMac2, VlanId.NONE))
+                .andReturn(new Interface(cp, Collections.singletonList(IA2), sourceMac2, VlanId.NONE))
                 .anyTimes();
         replay(interfaceService);
 
@@ -323,7 +323,7 @@
 
         InterfaceService interfaceService = createMock(InterfaceService.class);
         expect(interfaceService.getMatchingInterface(TARGET_IPV4_ADDR))
-                .andReturn(new Interface(cp, Collections.singleton(IA1), sourceMac, VlanId.vlanId(vlan)))
+                .andReturn(new Interface(cp, Collections.singletonList(IA1), sourceMac, VlanId.vlanId(vlan)))
                 .anyTimes();
         replay(interfaceService);
 
@@ -392,7 +392,7 @@
 
         InterfaceService interfaceService = createMock(InterfaceService.class);
         expect(interfaceService.getMatchingInterface(TARGET_IPV6_ADDR))
-                .andReturn(new Interface(cp, Collections.singleton(IA2), sourceMac2, VlanId.vlanId(vlan)))
+                .andReturn(new Interface(cp, Collections.singletonList(IA2), sourceMac2, VlanId.vlanId(vlan)))
                 .anyTimes();
         replay(interfaceService);
 
diff --git a/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java b/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java
index 70fdb40..4c53917 100644
--- a/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.net.proxyarp.impl;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import org.junit.Before;
 import org.junit.Test;
@@ -295,10 +296,10 @@
             InterfaceIpAddress ia4 = new InterfaceIpAddress(addr4, prefix4);
 
             // Setting up interfaces
-            Interface intf1 = new Interface(cp, Sets.newHashSet(ia1, ia3),
+            Interface intf1 = new Interface(cp, Lists.newArrayList(ia1, ia3),
                     MacAddress.valueOf(2 * i - 1),
                     VlanId.vlanId((short) 1));
-            Interface intf2 = new Interface(cp, Sets.newHashSet(ia2, ia4),
+            Interface intf2 = new Interface(cp, Lists.newArrayList(ia2, ia4),
                     MacAddress.valueOf(2 * i),
                     VlanId.NONE);
 
@@ -312,7 +313,7 @@
         }
         for (int i = LAST_CONF_DEVICE_INTF_VLAN_IP + 1; i <= LAST_CONF_DEVICE_INTF_VLAN; i++) {
             ConnectPoint cp = new ConnectPoint(getDeviceId(i), P1);
-            Interface intf1 = new Interface(cp, null,
+            Interface intf1 = new Interface(cp, Collections.emptyList(),
                     MacAddress.NONE,
                     VlanId.vlanId((short) 1));
 
@@ -850,7 +851,7 @@
         expect(hostService.getHostsByIp(theirIp)).andReturn(Collections.emptySet());
         expect(interfaceService.getInterfacesByIp(ourIp))
                 .andReturn(Collections.singleton(new Interface(getLocation(1),
-                        Collections.singleton(new InterfaceIpAddress(ourIp, IpPrefix.valueOf("10.0.1.1/24"))),
+                        Collections.singletonList(new InterfaceIpAddress(ourIp, IpPrefix.valueOf("10.0.1.1/24"))),
                         ourMac, VLAN1)));
         expect(hostService.getHost(HostId.hostId(ourMac, VLAN1))).andReturn(null);
         replay(hostService);
@@ -883,7 +884,7 @@
         expect(hostService.getHostsByIp(theirIp)).andReturn(Collections.emptySet());
         expect(interfaceService.getInterfacesByIp(ourIp))
                 .andReturn(Collections.singleton(new Interface(getLocation(1),
-                        Collections.singleton(new InterfaceIpAddress(
+                        Collections.singletonList(new InterfaceIpAddress(
                                 ourIp,
                                 IpPrefix.valueOf("1000::1/64"))),
                                 ourMac,
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
index 3c41291..8d938ba 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/config/basics/InterfaceConfig.java
@@ -20,6 +20,7 @@
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.annotations.Beta;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
@@ -29,6 +30,7 @@
 import org.onosproject.net.host.InterfaceIpAddress;
 
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -61,7 +63,7 @@
             for (JsonNode intfNode : array) {
                 String name = intfNode.path(NAME).asText(null);
 
-                Set<InterfaceIpAddress> ips = getIps(intfNode);
+                List<InterfaceIpAddress> ips = getIps(intfNode);
 
                 String mac = intfNode.path(MAC).asText();
                 MacAddress macAddr = mac.isEmpty() ? null : MacAddress.valueOf(mac);
@@ -98,7 +100,7 @@
         }
 
         if (!intf.ipAddresses().isEmpty()) {
-            intfNode.set(IPS, putIps(intf.ipAddresses()));
+            intfNode.set(IPS, putIps(intf.ipAddressesList()));
         }
 
         if (!intf.vlan().equals(VlanId.NONE)) {
@@ -133,8 +135,8 @@
         return vlan;
     }
 
-    private Set<InterfaceIpAddress> getIps(JsonNode node) {
-        Set<InterfaceIpAddress> ips = Sets.newHashSet();
+    private List<InterfaceIpAddress> getIps(JsonNode node) {
+        List<InterfaceIpAddress> ips = Lists.newArrayList();
 
         JsonNode ipsNode = node.get(IPS);
         if (ipsNode != null) {
@@ -145,7 +147,7 @@
         return ips;
     }
 
-    private ArrayNode putIps(Set<InterfaceIpAddress> intfIpAddresses) {
+    private ArrayNode putIps(List<InterfaceIpAddress> intfIpAddresses) {
         ArrayNode ipArray = mapper.createArrayNode();
 
         intfIpAddresses.forEach(i -> ipArray.add(i.toString()));
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/intf/Interface.java b/incubator/api/src/main/java/org/onosproject/incubator/net/intf/Interface.java
index e210968..08a4d1e 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/intf/Interface.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/intf/Interface.java
@@ -17,14 +17,16 @@
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
-import com.google.common.collect.Sets;
+import com.google.common.collect.Lists;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.host.InterfaceIpAddress;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -38,7 +40,7 @@
 
     private final String name;
     private final ConnectPoint connectPoint;
-    private final Set<InterfaceIpAddress> ipAddresses;
+    private final List<InterfaceIpAddress> ipAddresses;
     private final MacAddress macAddress;
     private final VlanId vlan;
 
@@ -47,16 +49,38 @@
      *
      * @param name name of the interface
      * @param connectPoint the connect point this interface maps to
-     * @param ipAddresses Set of IP addresses
+     * @param ipAddresses list of IP addresses
      * @param macAddress MAC address
      * @param vlan VLAN ID
      */
     public Interface(String name, ConnectPoint connectPoint,
+                     List<InterfaceIpAddress> ipAddresses,
+                     MacAddress macAddress, VlanId vlan) {
+        this.name = name == null ? NO_INTERFACE_NAME : name;
+        this.connectPoint = checkNotNull(connectPoint);
+        this.ipAddresses = ipAddresses == null ? Lists.newArrayList() : ipAddresses;
+        this.macAddress = macAddress == null ? MacAddress.NONE : macAddress;
+        this.vlan = vlan == null ? VlanId.NONE : vlan;
+    }
+
+    /**
+     * Creates new Interface with the provided configuration.
+     *
+     * @param name name of the interface
+     * @param connectPoint the connect point this interface maps to
+     * @param ipAddresses set of IP addresses
+     * @param macAddress MAC address
+     * @param vlan VLAN ID
+     * @deprecated in Falcon release, in favour of ordered list
+     */
+    @Deprecated
+    public Interface(String name, ConnectPoint connectPoint,
                      Set<InterfaceIpAddress> ipAddresses,
                      MacAddress macAddress, VlanId vlan) {
         this.name = name == null ? NO_INTERFACE_NAME : name;
         this.connectPoint = checkNotNull(connectPoint);
-        this.ipAddresses = ipAddresses == null ? Sets.newHashSet() : ipAddresses;
+        this.ipAddresses = ipAddresses == null ? Lists.newArrayList() :
+                           ipAddresses.stream().collect(Collectors.toList());
         this.macAddress = macAddress == null ? MacAddress.NONE : macAddress;
         this.vlan = vlan == null ? VlanId.NONE : vlan;
     }
@@ -68,7 +92,9 @@
      * @param ipAddresses Set of IP addresses
      * @param macAddress MAC address
      * @param vlan VLAN ID
+     * @deprecated in Falcon release - use constructors with names instead
      */
+    @Deprecated
     public Interface(ConnectPoint connectPoint,
                      Set<InterfaceIpAddress> ipAddresses,
                      MacAddress macAddress, VlanId vlan) {
@@ -76,6 +102,22 @@
     }
 
     /**
+     * Creates new Interface with the provided configuration.
+     *
+     * @param connectPoint the connect point this interface maps to
+     * @param ipAddresses Set of IP addresses
+     * @param macAddress MAC address
+     * @param vlan VLAN ID
+     * @deprecated in Falcon release - use constructors with names instead
+     */
+    @Deprecated
+    public Interface(ConnectPoint connectPoint,
+                     List<InterfaceIpAddress> ipAddresses,
+                     MacAddress macAddress, VlanId vlan) {
+        this(NO_INTERFACE_NAME, connectPoint, ipAddresses, macAddress, vlan);
+    }
+
+    /**
      * Retrieves the name of the interface.
      *
      * @return name
@@ -97,8 +139,20 @@
      * Retrieves the set of IP addresses that are assigned to the interface.
      *
      * @return the set of interface IP addresses
+     * @deprecated in Falcon release in favour of an ordered list
      */
+    @Deprecated
     public Set<InterfaceIpAddress> ipAddresses() {
+        return ipAddresses.stream().collect(Collectors.toSet());
+    }
+
+    /**
+     * Retrieves a list of IP addresses that are assigned to the interface in
+     * the order that they were configured.
+     *
+     * @return list of IP addresses
+     */
+    public List<InterfaceIpAddress> ipAddressesList() {
         return ipAddresses;
     }
 
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/intf/impl/InterfaceManagerTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/intf/impl/InterfaceManagerTest.java
index cc2908b..af29572 100644
--- a/incubator/net/src/test/java/org/onosproject/incubator/net/intf/impl/InterfaceManagerTest.java
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/intf/impl/InterfaceManagerTest.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.incubator.net.intf.impl;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import org.junit.Before;
@@ -35,6 +36,7 @@
 import org.onosproject.net.host.InterfaceIpAddress;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -87,7 +89,7 @@
         InterfaceIpAddress ia = InterfaceIpAddress.valueOf("192.168." + i + ".1/24");
 
         Interface intf = new Interface(cp,
-                Sets.newHashSet(ia),
+                Collections.singletonList(ia),
                 MacAddress.valueOf(i),
                 VlanId.vlanId((short) i));
 
@@ -150,7 +152,7 @@
         VlanId vlanId = VlanId.vlanId((short) 1);
         ConnectPoint cp = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
         Interface newIntf = new Interface(cp,
-                Collections.emptySet(),
+                Collections.emptyList(),
                 MacAddress.valueOf(100),
                 vlanId);
 
@@ -184,14 +186,14 @@
         // Create an interface that is the same as the existing one, but adds a
         // new IP address
         Interface intf = createInterface(1);
-        Set<InterfaceIpAddress> addresses = Sets.newHashSet(intf.ipAddresses());
+        List<InterfaceIpAddress> addresses = Lists.newArrayList(intf.ipAddresses());
         addresses.add(InterfaceIpAddress.valueOf("192.168.100.1/24"));
         intf = new Interface(intf.connectPoint(), addresses, intf.mac(), intf.vlan());
 
         // Create a new interface on the same connect point as the existing one
         InterfaceIpAddress newAddr = InterfaceIpAddress.valueOf("192.168.101.1/24");
         Interface newIntf = new Interface(cp,
-                Collections.singleton(newAddr),
+                Collections.singletonList(newAddr),
                 MacAddress.valueOf(101),
                 VlanId.vlanId((short) 101));