[CORD-1735] Add "relayAgentIps" option to DHCP relay application config

Change-Id: I2d95b5a285c81c15002ad94686b26ce03910198e
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
index 4d2bebb..0df73e4 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
@@ -117,6 +117,7 @@
     private MacAddress dhcpConnectMac = null;
     private VlanId dhcpConnectVlan = null;
     private Ip4Address dhcpGatewayIp = null;
+    private Ip4Address relayAgentIp = null;
 
     @Activate
     protected void activate() {
@@ -128,6 +129,12 @@
         hostService.removeListener(hostListener);
         this.dhcpConnectMac = null;
         this.dhcpConnectVlan = null;
+
+        if (dhcpGatewayIp != null) {
+            hostService.stopMonitoringIp(dhcpGatewayIp);
+        } else if (dhcpServerIp != null) {
+            hostService.stopMonitoringIp(dhcpServerIp);
+        }
     }
 
     @Override
@@ -231,6 +238,8 @@
             this.dhcpConnectVlan = host.vlan();
             this.dhcpConnectMac = host.mac();
         }
+
+        this.relayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
     }
 
     @Override
@@ -238,6 +247,7 @@
         log.warn("Indirect config feature for DHCPv4 handler not implement yet");
     }
 
+    @Override
     public void processDhcpPacket(PacketContext context, BasePacket payload) {
         checkNotNull(payload, "DHCP payload can't be null");
         checkState(payload instanceof DHCP, "Payload is not a DHCP");
@@ -340,7 +350,7 @@
      * @return the first interface IP; null if not exists an IP address in
      *         these interfaces
      */
-    private Ip4Address getRelayAgentIPv4Address(Interface iface) {
+    private Ip4Address getFirstIpFromInterface(Interface iface) {
         checkNotNull(iface, "Interface can't be null");
         return iface.ipAddressesList().stream()
                 .map(InterfaceIpAddress::ipAddress)
@@ -398,9 +408,9 @@
             log.warn("Can't get server interface, ignore");
             return null;
         }
-        Ip4Address relayAgentIp = getRelayAgentIPv4Address(serverInterface);
-        MacAddress relayAgentMac = serverInterface.mac();
-        if (relayAgentIp == null || relayAgentMac == null) {
+        Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
+        MacAddress macFacingServer = serverInterface.mac();
+        if (ipFacingServer == null || macFacingServer == null) {
             log.warn("No IP address for server Interface {}", serverInterface);
             return null;
         }
@@ -414,11 +424,11 @@
         }
         // get dhcp header.
         Ethernet etherReply = (Ethernet) ethernetPacket.clone();
-        etherReply.setSourceMACAddress(relayAgentMac);
+        etherReply.setSourceMACAddress(macFacingServer);
         etherReply.setDestinationMACAddress(dhcpConnectMac);
         etherReply.setVlanID(dhcpConnectVlan.toShort());
         IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
-        ipv4Packet.setSourceAddress(relayAgentIp.toInt());
+        ipv4Packet.setSourceAddress(ipFacingServer.toInt());
         ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
         UDP udpPacket = (UDP) ipv4Packet.getPayload();
         DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
@@ -459,6 +469,12 @@
             dhcpPacket.setGatewayIPAddress(clientInterfaceIp.toInt());
         }
 
+        // replace giaddr if relay agent IP is set
+        // FIXME for both direct and indirect case now, should be separated
+        if (relayAgentIp != null) {
+            dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
+        }
+
         udpPacket.setPayload(dhcpPacket);
         // As a DHCP relay, the source port should be server port(67) instead
         // of client port(68)
@@ -590,8 +606,8 @@
 
         // we leave the srcMac from the original packet
         // figure out the relay agent IP corresponding to the original request
-        Ip4Address relayAgentIP = getRelayAgentIPv4Address(clientInterface);
-        if (relayAgentIP == null) {
+        Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
+        if (ipFacingClient == null) {
             log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
                              + "Aborting relay for dhcp packet from server {}",
                      etherReply.getDestinationMAC(), clientInterface.vlan(),
@@ -600,7 +616,7 @@
         }
         // SRC_IP: relay agent IP
         // DST_IP: offered IP
-        ipv4Packet.setSourceAddress(relayAgentIP.toInt());
+        ipv4Packet.setSourceAddress(ipFacingClient.toInt());
         ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
         udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
         if (directlyConnected(dhcpPayload)) {
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
index ba19fa7..e5c9973 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
@@ -181,9 +181,6 @@
         packetService.removeProcessor(dhcpRelayPacketProcessor);
         cancelDhcpPackets();
         cancelArpPackets();
-        v4Handler.getDhcpGatewayIp().ifPresent(hostService::stopMonitoringIp);
-        v4Handler.getDhcpServerIp().ifPresent(hostService::stopMonitoringIp);
-        // TODO: DHCPv6 Handler
 
         compCfgService.unregisterProperties(getClass(), false);
         log.info("DHCP-RELAY Stopped");
@@ -236,15 +233,15 @@
             // Ignore if config is not present
             return;
         }
-        if (config instanceof DefaultDhcpRelayConfig) {
-            DefaultDhcpRelayConfig defaultConfig = (DefaultDhcpRelayConfig) config;
-            v4Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
-            v6Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
-        }
+
         if (config instanceof IndirectDhcpRelayConfig) {
             IndirectDhcpRelayConfig indirectConfig = (IndirectDhcpRelayConfig) config;
             v4Handler.setIndirectDhcpServerConfigs(indirectConfig.dhcpServerConfigs());
             v6Handler.setIndirectDhcpServerConfigs(indirectConfig.dhcpServerConfigs());
+        } else if (config instanceof DefaultDhcpRelayConfig) {
+            DefaultDhcpRelayConfig defaultConfig = (DefaultDhcpRelayConfig) config;
+            v4Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
+            v6Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
         }
     }
 
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/DefaultDhcpRelayConfig.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/DefaultDhcpRelayConfig.java
index 959c01b..daf97bf 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/DefaultDhcpRelayConfig.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/DefaultDhcpRelayConfig.java
@@ -29,8 +29,6 @@
 public class DefaultDhcpRelayConfig extends Config<ApplicationId> {
     public static final String KEY = "default";
 
-
-
     @Override
     public boolean isValid() {
         // check if all configs are valid
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/DhcpServerConfig.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/DhcpServerConfig.java
index 2451a7a..a5c304b 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/DhcpServerConfig.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/DhcpServerConfig.java
@@ -33,12 +33,15 @@
     private static final String DHCP_CONNECT_POINT = "dhcpServerConnectPoint";
     private static final String DHCP_SERVER_IP = "serverIps";
     private static final String DHCP_GATEWAY_IP = "gatewayIps";
+    private static final String RELAY_AGENT_IP = "relayAgentIps";
 
     private ConnectPoint connectPoint;
     private Ip4Address serverIp4Addr;
     private Ip4Address gatewayIp4Addr;
+    private Ip4Address relayAgentIp4Addr;
     private Ip6Address serverIp6Addr;
     private Ip6Address gatewayIp6Addr;
+    private Ip6Address relayAgentIp6Addr;
 
     protected DhcpServerConfig() {
         // empty config not allowed here
@@ -68,22 +71,34 @@
             }
         });
 
-        if (!config.has(DHCP_GATEWAY_IP)) {
-            // gateway ip doesn't exist, ignore the gateway
-            return;
+        if (config.has(DHCP_GATEWAY_IP)) {
+            ArrayNode gatewayIps = (ArrayNode) config.path(DHCP_GATEWAY_IP);
+            gatewayIps.forEach(node -> {
+                if (node.isTextual()) {
+                    IpAddress ip = IpAddress.valueOf(node.asText());
+                    if (ip.isIp4() && gatewayIp4Addr == null) {
+                        gatewayIp4Addr = ip.getIp4Address();
+                    }
+                    if (ip.isIp6() && gatewayIp6Addr == null) {
+                        gatewayIp6Addr = ip.getIp6Address();
+                    }
+                }
+            });
         }
-        ArrayNode gatewayIps = (ArrayNode) config.path(DHCP_GATEWAY_IP);
-        gatewayIps.forEach(node -> {
-            if (node.isTextual()) {
-                IpAddress ip = IpAddress.valueOf(node.asText());
-                if (ip.isIp4() && gatewayIp4Addr == null) {
-                    gatewayIp4Addr = ip.getIp4Address();
+        if (config.has(RELAY_AGENT_IP)) {
+            ArrayNode relayAgentIps = (ArrayNode) config.path(RELAY_AGENT_IP);
+            relayAgentIps.forEach(node -> {
+                if (node.isTextual()) {
+                    IpAddress ip = IpAddress.valueOf(node.asText());
+                    if (ip.isIp4() && relayAgentIp4Addr == null) {
+                        relayAgentIp4Addr = ip.getIp4Address();
+                    }
+                    if (ip.isIp6() && relayAgentIp6Addr == null) {
+                        relayAgentIp6Addr = ip.getIp6Address();
+                    }
                 }
-                if (ip.isIp6() && gatewayIp6Addr == null) {
-                    gatewayIp6Addr = ip.getIp6Address();
-                }
-            }
-        });
+            });
+        }
     }
 
     /**
@@ -146,4 +161,26 @@
     public Optional<Ip6Address> getDhcpGatewayIp6() {
         return Optional.ofNullable(gatewayIp6Addr);
     }
+
+    /**
+     * Returns the optional IPv4 address for relay agent, if configured.
+     * This option is used if we want to replace the giaddr field in DHCPv4
+     * payload.
+     *
+     * @return the giaddr; empty value if not set
+     */
+    public Optional<Ip4Address> getRelayAgentIp4() {
+        return Optional.ofNullable(relayAgentIp4Addr);
+    }
+
+    /**
+     * Returns the optional IPv6 address for relay agent, if configured.
+     * This option is used if we want to replace the link-address field in DHCPv6
+     * payload.
+     *
+     * @return the giaddr; empty value if not set
+     */
+    public Optional<Ip6Address> getRelayAgentIp6() {
+        return Optional.ofNullable(relayAgentIp6Addr);
+    }
 }
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
index 71b3cef..45cfa23 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -27,6 +27,7 @@
 import org.junit.Test;
 import org.onlab.packet.ARP;
 import org.onlab.packet.DHCP;
+import org.onlab.packet.DeserializationException;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
@@ -152,6 +153,9 @@
                                                                     SERVER_IFACE_MAC,
                                                                     SERVER_VLAN);
 
+    // Relay agent config
+    private static final Ip4Address RELAY_AGENT_IP = Ip4Address.valueOf("10.0.4.254");
+
     // Components
     private static final ApplicationId APP_ID = TestApplicationId.create(DhcpRelayManager.DHCP_RELAY_APP);
     private static final DefaultDhcpRelayConfig CONFIG = new MockDefaultDhcpRelayConfig();
@@ -187,8 +191,8 @@
                 .andReturn(APP_ID).anyTimes();
 
         manager.hostService = createNiceMock(HostService.class);
-        expect(manager.hostService.getHostsByIp(anyObject())).andReturn(ImmutableSet.of(SERVER_HOST));
-        expect(manager.hostService.getHost(OUTER_RELAY_HOST_ID)).andReturn(OUTER_RELAY_HOST);
+        expect(manager.hostService.getHostsByIp(anyObject())).andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
+        expect(manager.hostService.getHost(OUTER_RELAY_HOST_ID)).andReturn(OUTER_RELAY_HOST).anyTimes();
 
         packetService = new MockPacketService();
         manager.packetService = packetService;
@@ -300,6 +304,24 @@
     }
 
     @Test
+    public void testWithRelayAgentConfig() throws DeserializationException {
+        manager.v4Handler
+                .setDefaultDhcpServerConfigs(ImmutableList.of(new MockDhcpServerConfig(RELAY_AGENT_IP)));
+        packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT2_MAC,
+                                                                     CLIENT2_VLAN,
+                                                                     CLIENT2_CP,
+                                                                     INTERFACE_IP.ipAddress().getIp4Address(),
+                                                                     true));
+        OutboundPacket outPacket = packetService.emittedPacket;
+        byte[] outData = outPacket.data().array();
+        Ethernet eth = Ethernet.deserializer().deserialize(outData, 0, outData.length);
+        IPv4 ip = (IPv4) eth.getPayload();
+        UDP udp = (UDP) ip.getPayload();
+        DHCP dhcp = (DHCP) udp.getPayload();
+        assertEquals(RELAY_AGENT_IP.toInt(), dhcp.getGatewayIPAddress());
+    }
+
+    @Test
     public void testArpRequest() throws Exception {
         packetService.processPacket(new TestArpRequestPacketContext(CLIENT_INTERFACE));
         OutboundPacket outboundPacket = packetService.emittedPacket;
@@ -319,11 +341,27 @@
 
         @Override
         public List<DhcpServerConfig> dhcpServerConfigs() {
-            return ImmutableList.of(new MockDhcpServerConfig());
+            return ImmutableList.of(new MockDhcpServerConfig(null));
         }
     }
 
     private static class MockDhcpServerConfig extends DhcpServerConfig {
+        Ip4Address relayAgentIp;
+
+        /**
+         * Create mocked version DHCP server config.
+         *
+         * @param relayAgentIp the relay agent Ip config; null if we don't need it
+         */
+        public MockDhcpServerConfig(Ip4Address relayAgentIp) {
+            this.relayAgentIp = relayAgentIp;
+        }
+
+        @Override
+        public Optional<Ip4Address> getRelayAgentIp4() {
+            return Optional.ofNullable(relayAgentIp);
+        }
+
         @Override
         public Optional<ConnectPoint> getDhcpServerConnectPoint() {
             return Optional.of(SERVER_CONNECT_POINT);
diff --git a/apps/dhcprelay/src/test/resources/dhcp-relay.json b/apps/dhcprelay/src/test/resources/dhcp-relay.json
index bd90b7a..dc724ee 100644
--- a/apps/dhcprelay/src/test/resources/dhcp-relay.json
+++ b/apps/dhcprelay/src/test/resources/dhcp-relay.json
@@ -5,13 +5,15 @@
         {
           "dhcpServerConnectPoint": "of:0000000000000002/2",
           "serverIps": ["172.168.10.2", "2000::200:1"],
-          "gatewayIps": ["192.168.10.254", "1000::100:1"]
+          "gatewayIps": ["192.168.10.254", "1000::100:1"],
+          "relayAgentIps": ["10.0.0.1", "1000:100::100:1"]
         }
       ],
       "indirect": [
         {
           "dhcpServerConnectPoint": "of:0000000000000002/3",
-          "serverIps": ["172.168.10.3"]
+          "serverIps": ["172.168.10.3"],
+          "relayAgentIps": ["10.0.1.1"]
         }
       ]
     }