[ONOS-7786] Support to retrieve metadata from virtual instances

Change-Id: Ieb3658ba06790b91cbe8b3075e0f64c5a86ee700
diff --git a/apps/openstacknetworking/BUCK b/apps/openstacknetworking/BUCK
index 9b6d8d0..ec7fad0 100644
--- a/apps/openstacknetworking/BUCK
+++ b/apps/openstacknetworking/BUCK
@@ -1,6 +1,9 @@
 BUNDLES = [
     '//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
     '//apps/openstacknetworking/app:onos-apps-openstacknetworking-app',
+    '//lib:httpclient-osgi',
+    '//lib:httpcore-osgi',
+    '//lib:commons-codec',
 ]
 
 onos_app (
diff --git a/apps/openstacknetworking/BUILD b/apps/openstacknetworking/BUILD
index e9e995c..6da2cbd 100644
--- a/apps/openstacknetworking/BUILD
+++ b/apps/openstacknetworking/BUILD
@@ -1,6 +1,9 @@
 BUNDLES = [
     "//apps/openstacknetworking/api:onos-apps-openstacknetworking-api",
     "//apps/openstacknetworking/app:onos-apps-openstacknetworking-app",
+    "@httpclient_osgi//jar",
+    "@httpcore_osgi//jar",
+    "@commons_codec//jar",
 ]
 
 onos_app(
diff --git a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/InstancePortService.java b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/InstancePortService.java
index 7dcbd32..d9890db 100644
--- a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/InstancePortService.java
+++ b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/InstancePortService.java
@@ -18,6 +18,8 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onosproject.event.ListenerService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 
 import java.util.Set;
 
@@ -53,6 +55,15 @@
     InstancePort instancePort(String osPortId);
 
     /**
+     * Returns instance port with the given device identifier and port number.
+     *
+     * @param deviceId      device identifier
+     * @param portNumber    port number
+     * @return instance port; null if not found
+     */
+    InstancePort instancePort(DeviceId deviceId, PortNumber portNumber);
+
+    /**
      * Returns all instance ports.
      *
      * @return set of instance ports; empty list if no port exists
diff --git a/apps/openstacknetworking/app/BUCK b/apps/openstacknetworking/app/BUCK
index 63b9477..24ed618 100644
--- a/apps/openstacknetworking/app/BUCK
+++ b/apps/openstacknetworking/app/BUCK
@@ -13,6 +13,9 @@
     '//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
     '//protocols/ovsdb/api:onos-protocols-ovsdb-api',
     '//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc',
+    '//lib:httpclient-osgi',
+    '//lib:httpcore-osgi',
+    '//lib:commons-codec',
     '//lib:openstack4j-core',
     '//lib:openstack4j-http-connector',
     '//lib:openstack4j-httpclient',
diff --git a/apps/openstacknetworking/app/BUILD b/apps/openstacknetworking/app/BUILD
index 7232305..c33828a 100644
--- a/apps/openstacknetworking/app/BUILD
+++ b/apps/openstacknetworking/app/BUILD
@@ -13,6 +13,9 @@
     "//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc",
     "//apps/openstacknode/api:onos-apps-openstacknode-api",
     "//apps/openstacknetworking/api:onos-apps-openstacknetworking-api",
+    "@httpclient_osgi//jar",
+    "@httpcore_osgi//jar",
+    "@commons_codec//jar",
     "@openstack4j_core//jar",
     "@openstack4j_http_connector//jar",
     "@openstack4j_httpclient//jar",
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
index 645a86d..476db2b 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
@@ -32,8 +32,10 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
@@ -84,6 +86,8 @@
     private static final String ERR_NULL_MAC_ADDRESS = "MAC address cannot be null";
     private static final String ERR_NULL_IP_ADDRESS = "IP address cannot be null";
     private static final String ERR_NULL_NETWORK_ID = "Network ID cannot be null";
+    private static final String ERR_NULL_DEVICE_ID = "Device ID cannot be null";
+    private static final String ERR_NULL_PORT_NUMBER = "Port number cannot be null";
 
     private static final String ERR_IN_USE = " still in use";
 
@@ -211,6 +215,17 @@
     }
 
     @Override
+    public InstancePort instancePort(DeviceId deviceId, PortNumber portNumber) {
+        checkNotNull(deviceId, ERR_NULL_DEVICE_ID);
+        checkNotNull(portNumber, ERR_NULL_PORT_NUMBER);
+
+        return instancePortStore.instancePorts().stream()
+                .filter(port -> port.deviceId().equals(deviceId))
+                .filter(port -> port.portNumber().equals(portNumber))
+                .findFirst().orElse(null);
+    }
+
+    @Override
     public Set<InstancePort> instancePorts() {
         Set<InstancePort> ports = instancePortStore.instancePorts();
 
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java
new file mode 100644
index 0000000..1a225ea
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.http.Header;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpMessage;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpResponse;
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Data;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TCP;
+import org.onlab.packet.TpPort;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Port;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.Objects;
+
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_DHCP_RULE;
+import static org.onosproject.openstacknetworking.impl.OpenstackMetadataProxyHandler.Http.Type.RESPONSE;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.hmacEncrypt;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.parseHttpRequest;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.unparseHttpResponseBody;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.unparseHttpResponseHeader;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.CONTROLLER;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles metadata requests for the virtual instances.
+ */
+@Component(immediate = true)
+public class OpenstackMetadataProxyHandler {
+    protected final Logger log = getLogger(getClass());
+
+    private static final String METADATA_SERVER_IP = "169.254.169.254";
+    private static final int METADATA_SERVER_PORT = 8775;
+    private static final int HTTP_SERVER_PORT = 80;
+    private static final int PREFIX_LENGTH = 32;
+    private static final short WINDOW_SIZE = (short) 0x1000;
+    private static final short FIN_FLAG = (short) 0x01;
+    private static final short SYN_FLAG = (short) 0x02;
+    private static final short ACK_FLAG = (short) 0x10;
+    private static final short SYN_ACK_FLAG = (short) 0x12;
+    private static final short FIN_ACK_FLAG = (short) 0x11;
+    private static final byte DATA_OFFSET = (byte) 0x5;
+    private static final short URGENT_POINTER = (short) 0x1;
+    private static final byte PACKET_TTL = (byte) 127;
+    private static final String HTTP_PREFIX = "http://";
+    private static final String COLON = ":";
+
+    private static final String INSTANCE_ID_HEADER = "X-Instance-ID";
+    private static final String INSTANCE_ID_SIGNATURE_HEADER = "X-Instance-ID-Signature";
+    private static final String TENANT_ID_HEADER = "X-Tenant-ID";
+    private static final String FORWARDED_FOR_HEADER = "X-Forwarded-For";
+
+    private static final String HTTP_GET_METHOD = "GET";
+    private static final String HTTP_POST_METHOD = "POST";
+
+    private static final String METADATA_SECRET = "metadataSecret";
+    private static final String DEFAULT_METADATA_SECRET = "nova";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNetworkService osNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService osNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstancePortService instancePortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackFlowRuleService osFlowRuleService;
+
+    @Property(name = METADATA_SECRET, value = DEFAULT_METADATA_SECRET,
+            label = "Metadata secret")
+    private String metadataSecret = DEFAULT_METADATA_SECRET;
+
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+    private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
+
+    private ApplicationId appId;
+    private NodeId localNodeId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+        localNodeId = clusterService.getLocalNode().id();
+        configService.registerProperties(getClass());
+        osNodeService.addListener(osNodeListener);
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+        leadershipService.runForLeadership(appId.name());
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        configService.unregisterProperties(getClass(), false);
+        osNodeService.removeListener(osNodeListener);
+        leadershipService.withdraw(appId.name());
+
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        String updatedMetadataSecret;
+
+        updatedMetadataSecret = Tools.get(properties, METADATA_SECRET);
+
+        if (!Strings.isNullOrEmpty(updatedMetadataSecret) &&
+                !updatedMetadataSecret.equals(metadataSecret)) {
+            metadataSecret = updatedMetadataSecret;
+        }
+
+        log.info("Modified");
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+
+            Ethernet ethPacket = context.inPacket().parsed();
+            if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
+                return;
+            }
+
+            IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
+            if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_TCP ||
+                    !IpAddress.valueOf(ipv4Packet.getDestinationAddress()).
+                            equals(IpAddress.valueOf(METADATA_SERVER_IP))) {
+                return;
+            }
+
+            TCP tcpPacket = (TCP) ipv4Packet.getPayload();
+            if (tcpPacket.getDestinationPort() != HTTP_SERVER_PORT) {
+                return;
+            }
+
+            if (tcpPacket.getFlags() == SYN_FLAG) {
+                Ethernet ethReply = buildTcpSynAckPacket(ethPacket, ipv4Packet, tcpPacket);
+                sendReply(context, ethReply);
+                return;
+            }
+
+            if (tcpPacket.getFlags() == FIN_ACK_FLAG) {
+                Ethernet ackReply = buildTcpAckPacket(ethPacket, ipv4Packet, tcpPacket);
+                sendReply(context, ackReply);
+                Ethernet finAckReply = buildTcpFinAckPacket(ethPacket, ipv4Packet, tcpPacket);
+                sendReply(context, finAckReply);
+                return;
+            }
+
+            Data data = (Data) tcpPacket.getPayload();
+            byte[] byteData = data.getData();
+
+            if (byteData.length != 0) {
+                HttpRequest request = parseHttpRequest(byteData);
+                ConnectPoint cp = context.inPacket().receivedFrom();
+                InstancePort instPort = instancePortService.instancePort(cp.deviceId(), cp.port());
+
+                if (instPort == null || request == null) {
+                    log.warn("Cannot send metadata request due to lack of information");
+                    return;
+                }
+
+                // attempt to send HTTP request to the meta-data server (nova-api),
+                // obtain the HTTP response
+                CloseableHttpResponse proxyResponse = proxyHttpRequest(request, instPort);
+
+                if (proxyResponse == null) {
+                    log.warn("No response was received from metadata server");
+                    return;
+                }
+
+                HttpResponse response = new BasicHttpResponse(proxyResponse.getStatusLine());
+                response.setEntity(proxyResponse.getEntity());
+                response.setHeaders(proxyResponse.getAllHeaders());
+
+                Http httpResponse = new Http();
+                httpResponse.setType(RESPONSE);
+                httpResponse.setMessage(response);
+
+                TCP tcpReply = buildTcpDataPacket(tcpPacket, byteData.length, response);
+                Ethernet ethReply = buildEthFrame(ethPacket, ipv4Packet, tcpReply);
+                sendReply(context, ethReply);
+
+                try {
+                    proxyResponse.close();
+                } catch (IOException e) {
+                    log.warn("Failed to close the response connection due to {}", e);
+                }
+            }
+        }
+
+        /**
+         * Builds an ethernet frame contains TCP sync-ack packet generated
+         * from the given TCP sync request packet.
+         *
+         * @param ethRequest    ethernet request frame
+         * @param ipv4Request   IPv4 request
+         * @param tcpRequest    TCP request
+         * @return an ethernet frame contains newly generated TCP reply
+         */
+        private Ethernet buildTcpSynAckPacket(Ethernet ethRequest,
+                                              IPv4 ipv4Request, TCP tcpRequest) {
+
+            TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getSequence(),
+                    tcpRequest.getSequence() + 1, SYN_ACK_FLAG);
+
+            return buildEthFrame(ethRequest, ipv4Request, tcpReply);
+        }
+
+        /**
+         * Builds a TCP ACK packet receiving SYN packet.
+         *
+         * @param ethRequest    ethernet request frame
+         * @param ipv4Request   IPv4 request
+         * @param tcpRequest    TCP request
+         * @return an ethernet frame contains newly generated TCP reply
+         */
+        private Ethernet buildTcpAckPacket(Ethernet ethRequest,
+                                           IPv4 ipv4Request, TCP tcpRequest) {
+            TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getAcknowledge(),
+                    tcpRequest.getSequence() + 1, ACK_FLAG);
+
+            return buildEthFrame(ethRequest, ipv4Request, tcpReply);
+        }
+
+        /**
+         * Builds a TCP FIN-ACK packet receiving FIN-ACK packet.
+         *
+         * @param ethRequest    ethernet request frame
+         * @param ipv4Request   IPv4 request
+         * @param tcpRequest    TCP request
+         * @return an ethernet frame contains newly generated TCP reply
+         */
+        private Ethernet buildTcpFinAckPacket(Ethernet ethRequest,
+                                              IPv4 ipv4Request, TCP tcpRequest) {
+            TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getAcknowledge(),
+                    tcpRequest.getSequence() + 1, FIN_ACK_FLAG);
+
+            return buildEthFrame(ethRequest, ipv4Request, tcpReply);
+        }
+
+        /**
+         * Builds a TCP signaling packet.
+         *
+         * @param tcpRequest    TCP request
+         * @param seq           sequence number
+         * @param ack           ack number
+         * @param flags         TCP flags
+         * @return TCP signal packet
+         */
+        private TCP buildTcpSignalPacket(TCP tcpRequest, int seq, int ack, short flags) {
+            TCP tcpReply = new TCP();
+            tcpReply.setSourcePort(tcpRequest.getDestinationPort());
+            tcpReply.setDestinationPort(tcpRequest.getSourcePort());
+            tcpReply.setSequence(seq);
+            tcpReply.setAcknowledge(ack);
+            tcpReply.setDataOffset(DATA_OFFSET);
+            tcpReply.setFlags(flags);
+            tcpReply.setWindowSize(WINDOW_SIZE);
+            tcpReply.setUrgentPointer(URGENT_POINTER);
+
+            return tcpReply;
+        }
+
+        /**
+         * Builds a TCP data packet.
+         *
+         * @param tcpRequest    TCP request
+         * @param requestLength TCP request data length
+         * @param response      HTTP response
+         * @return a TCP data packet
+         */
+        private TCP buildTcpDataPacket(TCP tcpRequest, int requestLength,
+                                       HttpResponse response) {
+            TCP tcpReply = new TCP();
+            tcpReply.setSourcePort(tcpRequest.getDestinationPort());
+            tcpReply.setDestinationPort(tcpRequest.getSourcePort());
+            tcpReply.setSequence(tcpRequest.getAcknowledge());
+            tcpReply.setAcknowledge(tcpRequest.getSequence() + requestLength);
+            tcpReply.setDataOffset(DATA_OFFSET);        // no options
+            tcpReply.setFlags(ACK_FLAG);
+            tcpReply.setWindowSize(WINDOW_SIZE);
+            tcpReply.setUrgentPointer(URGENT_POINTER);
+
+            Http httpResponse = new Http();
+            httpResponse.setType(RESPONSE);
+            httpResponse.setMessage(response);
+
+            tcpReply.setPayload(httpResponse);
+
+            return tcpReply;
+        }
+
+        /**
+         * Builds an ethernet frame with the given IPv4 and TCP payload.
+         *
+         * @param ethRequest    ethernet request frame
+         * @param ipv4Request   IPv4 request
+         * @param tcpReply      TCP reply
+         * @return an ethernet frame contains TCP payload
+         */
+        private Ethernet buildEthFrame(Ethernet ethRequest, IPv4 ipv4Request,
+                                       TCP tcpReply) {
+            Ethernet ethReply = new Ethernet();
+            ethReply.setSourceMACAddress(ethRequest.getDestinationMAC());
+            ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
+            ethReply.setEtherType(ethRequest.getEtherType());
+
+            IPv4 ipv4Reply = new IPv4();
+            ipv4Reply.setSourceAddress(ipv4Request.getDestinationAddress());
+            ipv4Reply.setDestinationAddress(ipv4Request.getSourceAddress());
+            ipv4Reply.setTtl(PACKET_TTL);
+
+            ipv4Reply.setPayload(tcpReply);
+            ethReply.setPayload(ipv4Reply);
+
+            return ethReply;
+        }
+
+        /**
+         * Proxyies HTTP request.
+         *
+         * @param oldRequest    HTTP request
+         * @param instPort      instance port
+         * @return HTTP response
+         */
+        private CloseableHttpResponse proxyHttpRequest(HttpRequest oldRequest,
+                                                       InstancePort instPort) {
+
+            CloseableHttpClient client = HttpClientBuilder.create().build();
+            OpenstackNode controller = osNodeService.completeNodes(CONTROLLER).
+                    stream().findFirst().orElse(null);
+            if (controller == null) {
+                return null;
+            }
+
+            String path = oldRequest.getRequestLine().getUri();
+            String url = HTTP_PREFIX + controller.managementIp().toString() +
+                    COLON + METADATA_SERVER_PORT + path;
+
+            if (StringUtils.isEmpty(url)) {
+                log.warn("The metadata endpoint is not configured!");
+                return null;
+            }
+
+            log.info("Sending request to metadata endpoint {}...", url);
+
+            HttpRequestBase request;
+
+            switch (oldRequest.getRequestLine().getMethod()) {
+                case HTTP_GET_METHOD:
+                    request = new HttpGet(url);
+                    break;
+                case HTTP_POST_METHOD:
+                    request = new HttpPost(url);
+                    HttpEntityEnclosingRequest entityRequest =
+                            (HttpEntityEnclosingRequest) oldRequest;
+                    ((HttpPost) request).setEntity(entityRequest.getEntity());
+                    break;
+                default:
+                    request = new HttpGet(url);
+                    break;
+            }
+
+            // configure headers from original HTTP request
+            for (Header header : oldRequest.getAllHeaders()) {
+                request.addHeader(header);
+            }
+
+            request.setProtocolVersion(oldRequest.getProtocolVersion());
+
+            Port port = osNetworkService.port(instPort.portId());
+
+            request.addHeader(new BasicHeader(INSTANCE_ID_HEADER, port.getDeviceId()));
+            request.addHeader(new BasicHeader(INSTANCE_ID_SIGNATURE_HEADER,
+                    hmacEncrypt(metadataSecret, port.getDeviceId())));
+            request.addHeader(new BasicHeader(TENANT_ID_HEADER, port.getTenantId()));
+            request.addHeader(new BasicHeader(
+                    FORWARDED_FOR_HEADER, instPort.ipAddress().toString()));
+
+            try {
+                return client.execute(request);
+            } catch (IOException e) {
+                log.warn("Failed to get response from metadata server due to {}", e);
+            }
+
+            return null;
+        }
+
+        /**
+         * Sends out ethernet frame.
+         *
+         * @param context   packet context
+         * @param ethReply  ethernet frame
+         */
+        private void sendReply(PacketContext context, Ethernet ethReply) {
+            if (ethReply == null) {
+                return;
+            }
+            ConnectPoint srcPoint = context.inPacket().receivedFrom();
+            TrafficTreatment treatment = DefaultTrafficTreatment
+                    .builder()
+                    .setOutput(srcPoint.port())
+                    .build();
+
+            packetService.emit(new DefaultOutboundPacket(
+                    srcPoint.deviceId(),
+                    treatment,
+                    ByteBuffer.wrap(ethReply.serialize())));
+            context.block();
+        }
+    }
+
+    private class InternalNodeEventListener implements OpenstackNodeListener {
+        @Override
+        public boolean isRelevant(OpenstackNodeEvent event) {
+            // do not allow to proceed without leadership
+            NodeId leader = leadershipService.getLeader(appId.name());
+            return Objects.equals(localNodeId, leader) &&
+                    event.subject().type() == COMPUTE;
+        }
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode osNode = event.subject();
+            switch (event.type()) {
+                case OPENSTACK_NODE_COMPLETE:
+                    setMetadataRule(osNode, true);
+                    break;
+                case OPENSTACK_NODE_INCOMPLETE:
+                    setMetadataRule(osNode, false);
+                    break;
+                case OPENSTACK_NODE_CREATED:
+                case OPENSTACK_NODE_UPDATED:
+                case OPENSTACK_NODE_REMOVED:
+                default:
+                    break;
+            }
+        }
+
+        /**
+         * Installs metadata rule for receiving all metadata request packets.
+         *
+         * @param osNode    openstack node
+         * @param install   installation flag
+         */
+        private void setMetadataRule(OpenstackNode osNode, boolean install) {
+            TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPProtocol(IPv4.PROTOCOL_TCP)
+                    .matchIPDst(IpPrefix.valueOf(
+                            IpAddress.valueOf(METADATA_SERVER_IP), PREFIX_LENGTH))
+                    .matchTcpDst(TpPort.tpPort(HTTP_SERVER_PORT))
+                    .build();
+
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .punt()
+                    .build();
+
+            osFlowRuleService.setRule(
+                    appId,
+                    osNode.intgBridge(),
+                    selector,
+                    treatment,
+                    PRIORITY_DHCP_RULE,
+                    DHCP_ARP_TABLE,
+                    install);
+        }
+    }
+
+    /**
+     * Implements Http packet format.
+     */
+    protected static class Http extends BasePacket {
+
+        public enum Type {
+
+            /**
+             * Signifies that this is a Http REQUEST packet.
+             */
+            REQUEST,
+
+            /**
+             * Signifies that this is a Http RESPONSE packet.
+             */
+            RESPONSE,
+        }
+
+        private Type type;
+        private HttpMessage message;
+
+        /**
+         * Obtains the Http type.
+         *
+         * @return Http type
+         */
+        public Type getType() {
+            return type;
+        }
+
+        /**
+         * Configures the Http type.
+         *
+         * @param type Http type
+         */
+        public void setType(Type type) {
+            this.type = type;
+        }
+
+        /**
+         * Obtains the Http message.
+         *
+         * @return Http message
+         */
+        public HttpMessage getMessage() {
+            return message;
+        }
+
+        /**
+         * Configures the Http message.
+         *
+         * @param message Http message
+         */
+        public void setMessage(HttpMessage message) {
+            this.message = message;
+        }
+
+        @Override
+        public byte[] serialize() {
+            if (type == RESPONSE) {
+
+                byte[] header = unparseHttpResponseHeader((HttpResponse) message);
+                byte[] body = unparseHttpResponseBody((HttpResponse) message);
+
+                if (header == null || body == null) {
+                    return new byte[0];
+                }
+
+                final byte[] data = new byte[header.length + body.length];
+                final ByteBuffer bb = ByteBuffer.wrap(data);
+                bb.put(header);
+                bb.put(body);
+
+                return data;
+            }
+            return new byte[0];
+        }
+
+        @Override
+        public boolean equals(Object o) {
+
+            if (this == o) {
+                return true;
+            }
+
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            if (!super.equals(o)) {
+                return false;
+            }
+
+            Http http = (Http) o;
+            return type == http.type &&
+                    com.google.common.base.Objects.equal(message, http.message);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(type, message);
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
index b6937c5..8c972e8 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
@@ -22,6 +22,18 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.base.Strings;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.impl.io.DefaultHttpRequestParser;
+import org.apache.http.impl.io.DefaultHttpRequestWriter;
+import org.apache.http.impl.io.DefaultHttpResponseParser;
+import org.apache.http.impl.io.DefaultHttpResponseWriter;
+import org.apache.http.impl.io.HttpTransportMetricsImpl;
+import org.apache.http.impl.io.SessionInputBufferImpl;
+import org.apache.http.impl.io.SessionOutputBufferImpl;
+import org.apache.http.io.HttpMessageWriter;
 import org.onosproject.cfg.ConfigProperty;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.device.DeviceService;
@@ -50,11 +62,15 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.cert.X509Certificate;
@@ -99,6 +115,10 @@
     private static final String PROXY_MODE = "proxy";
     private static final String BROADCAST_MODE = "broadcast";
 
+    private static final int HTTP_PAYLOAD_BUFFER = 8 * 1024;
+
+    private static final String HMAC_SHA256 = "HmacSHA256";
+
     private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
 
     /**
@@ -463,7 +483,8 @@
      * @param routerInterface2 router interface
      * @return returns true if two router interfaces are equal, false otherwise
      */
-    public static boolean routerInterfacesEquals(RouterInterface routerInterface1, RouterInterface routerInterface2) {
+    public static boolean routerInterfacesEquals(RouterInterface routerInterface1,
+                                                 RouterInterface routerInterface2) {
         return Objects.equals(routerInterface1.getId(), routerInterface2.getId()) &&
                 Objects.equals(routerInterface1.getPortId(), routerInterface2.getPortId()) &&
                 Objects.equals(routerInterface1.getSubnetId(), routerInterface2.getSubnetId()) &&
@@ -481,6 +502,147 @@
         }
     }
 
+    /**
+     * Deserializes raw payload into HttpRequest object.
+     *
+     * @param rawData raw http payload
+     * @return HttpRequest object
+     */
+    public static HttpRequest parseHttpRequest(byte[] rawData) {
+        SessionInputBufferImpl sessionInputBuffer =
+                new SessionInputBufferImpl(
+                        new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
+        sessionInputBuffer.bind(new ByteArrayInputStream(rawData));
+        DefaultHttpRequestParser requestParser = new DefaultHttpRequestParser(sessionInputBuffer);
+        try {
+            return requestParser.parse();
+        } catch (IOException | HttpException e) {
+            log.warn("Failed to parse HttpRequest, due to {}", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Serializes HttpRequest object to byte array.
+     *
+     * @param request http request object
+     * @return byte array
+     */
+    public static byte[] unparseHttpRequest(HttpRequest request) {
+        try {
+            SessionOutputBufferImpl sessionOutputBuffer =
+                    new SessionOutputBufferImpl(
+                            new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            sessionOutputBuffer.bind(baos);
+
+            HttpMessageWriter<HttpRequest> requestWriter = new DefaultHttpRequestWriter(
+                    sessionOutputBuffer);
+            requestWriter.write(request);
+            sessionOutputBuffer.flush();
+
+            return baos.toByteArray();
+        } catch (HttpException | IOException e) {
+            log.warn("Failed to unparse HttpRequest, due to {}", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Deserializes raw payload into HttpResponse object.
+     *
+     * @param rawData raw http payload
+     * @return HttpResponse object
+     */
+    public static HttpResponse parseHttpResponse(byte[] rawData) {
+        SessionInputBufferImpl sessionInputBuffer =
+                new SessionInputBufferImpl(
+                        new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
+        sessionInputBuffer.bind(new ByteArrayInputStream(rawData));
+        DefaultHttpResponseParser responseParser = new DefaultHttpResponseParser(sessionInputBuffer);
+        try {
+            return responseParser.parse();
+        } catch (IOException | HttpException e) {
+            log.warn("Failed to parse HttpResponse, due to {}", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Serializes HttpResponse header to byte array.
+     *
+     * @param response http response object
+     * @return byte array
+     */
+    public static byte[] unparseHttpResponseHeader(HttpResponse response) {
+        try {
+            SessionOutputBufferImpl sessionOutputBuffer =
+                    new SessionOutputBufferImpl(
+                            new HttpTransportMetricsImpl(), HTTP_PAYLOAD_BUFFER);
+
+            ByteArrayOutputStream headerBaos = new ByteArrayOutputStream();
+            sessionOutputBuffer.bind(headerBaos);
+
+            HttpMessageWriter<HttpResponse> responseWriter =
+                    new DefaultHttpResponseWriter(sessionOutputBuffer);
+            responseWriter.write(response);
+            sessionOutputBuffer.flush();
+
+            log.debug(headerBaos.toString());
+
+            return headerBaos.toByteArray();
+        } catch (IOException | HttpException e) {
+            log.warn("Failed to unparse HttpResponse headers, due to {}", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Serializes HttpResponse object to byte array.
+     *
+     * @param response http response object
+     * @return byte array
+     */
+    public static byte[] unparseHttpResponseBody(HttpResponse response) {
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            response.getEntity().writeTo(baos);
+
+            log.debug(response.toString());
+            log.debug(baos.toString());
+
+            return baos.toByteArray();
+        } catch (IOException e) {
+            log.warn("Failed to unparse HttpResponse, due to {}", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Encodes the given data using HmacSHA256 encryption method with given secret key.
+     *
+     * @param key       secret key
+     * @param data      data to be encrypted
+     * @return Hmac256 encrypted data
+     */
+    public static String hmacEncrypt(String key, String data) {
+        try {
+            Mac sha256Hmac = Mac.getInstance(HMAC_SHA256);
+            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), HMAC_SHA256);
+            sha256Hmac.init(secretKey);
+            return Hex.encodeHexString(sha256Hmac.doFinal(data.getBytes("UTF-8")));
+        } catch (Exception e) {
+            log.warn("Failed to encrypt data {} using key {}, due to {}", data, key, e);
+        }
+        return null;
+    }
+
     private static boolean isDirectPort(String portName) {
         return portNamePrefixMap().values().stream().anyMatch(p -> portName.startsWith(p));
     }
@@ -591,4 +753,4 @@
         }
         return gw;
     }
-}
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/InstancePortServiceAdapter.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/InstancePortServiceAdapter.java
index b0efdfa..d4f1d3f 100644
--- a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/InstancePortServiceAdapter.java
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/InstancePortServiceAdapter.java
@@ -18,6 +18,8 @@
 import com.google.common.collect.ImmutableSet;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.openstacknetworking.api.InstancePort;
 import org.onosproject.openstacknetworking.api.InstancePortListener;
 import org.onosproject.openstacknetworking.api.InstancePortService;
@@ -44,6 +46,11 @@
     }
 
     @Override
+    public InstancePort instancePort(DeviceId deviceId, PortNumber portNumber) {
+        return null;
+    }
+
+    @Override
     public Set<InstancePort> instancePorts() {
         return ImmutableSet.of();
     }