[ONOS-7786] Support to retrieve metadata from virtual instances
Change-Id: Ieb3658ba06790b91cbe8b3075e0f64c5a86ee700
(cherry picked from commit 634302085df3587fb25f65a0a41ff0319cfa325c)
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();
}