Fix: segment TCP packet when the payload is larger than MTU size
Change-Id: I241fceac51e6511b394e16ab2aefdf69fa97eb8c
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
index dbbc26b..7567364 100644
--- 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
@@ -17,6 +17,7 @@
package org.onosproject.openstacknetworking.impl;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntityEnclosingRequest;
@@ -64,6 +65,7 @@
import org.onosproject.openstacknode.api.OpenstackNodeEvent;
import org.onosproject.openstacknode.api.OpenstackNodeListener;
import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Network;
import org.openstack4j.model.network.Port;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
@@ -75,6 +77,7 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -109,12 +112,16 @@
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 short FIN_ACK_PUSH_FLAG = (short) 0x19;
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 int IP_HEADER_SIZE = 20;
+ private static final int TCP_HEADER_SIZE = 20;
+
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";
@@ -267,13 +274,13 @@
response.setEntity(proxyResponse.getEntity());
response.setHeaders(proxyResponse.getAllHeaders());
- Http httpResponse = new Http();
- httpResponse.setType(RESPONSE);
- httpResponse.setMessage(response);
+ Network osNetwork = osNetworkService.network(instPort.networkId());
+ int tcpPayloadSize = osNetwork.getMTU() - IP_HEADER_SIZE - TCP_HEADER_SIZE;
- TCP tcpReply = buildTcpDataPacket(tcpPacket, byteData.length, response);
- Ethernet ethReply = buildEthFrame(ethPacket, ipv4Packet, tcpReply);
- sendReply(context, ethReply);
+ List<TCP> tcpReplies = buildTcpDataPackets(tcpPacket,
+ byteData.length, response, tcpPayloadSize);
+ List<Ethernet> ethReplies = buildEthFrames(ethPacket, ipv4Packet, tcpReplies);
+ ethReplies.forEach(e -> sendReply(context, e));
try {
proxyResponse.close();
@@ -363,25 +370,70 @@
* @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);
+ private List<TCP> buildTcpDataPackets(TCP tcpRequest, int requestLength,
+ HttpResponse response, int payloadSize) {
+ List<TCP> tcpReplies = Lists.newArrayList();
Http httpResponse = new Http();
httpResponse.setType(RESPONSE);
httpResponse.setMessage(response);
- tcpReply.setPayload(httpResponse);
+ byte[] httpBytes = httpResponse.serialize();
- return tcpReply;
+ int numOfSegments = (int) Math.ceil((double) httpBytes.length / payloadSize);
+
+ if (numOfSegments == 1) {
+ 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, 20 bytes
+ tcpReply.setFlags(ACK_FLAG);
+ tcpReply.setWindowSize(WINDOW_SIZE);
+ tcpReply.setUrgentPointer(URGENT_POINTER);
+
+ Data data = new Data(httpBytes);
+ tcpReply.setPayload(data);
+
+ tcpReplies.add(tcpReply);
+ }
+
+ if (numOfSegments > 1) {
+
+ for (int i = 0; i < numOfSegments; i++) {
+
+ int byteStartIndex = i * payloadSize;
+ int byteEndIndex;
+
+ TCP tcpReply = new TCP();
+ tcpReply.setSourcePort(tcpRequest.getDestinationPort());
+ tcpReply.setDestinationPort(tcpRequest.getSourcePort());
+ tcpReply.setSequence(tcpRequest.getAcknowledge() + byteStartIndex);
+ tcpReply.setAcknowledge(tcpRequest.getSequence() + requestLength);
+ tcpReply.setDataOffset(DATA_OFFSET); // no options, 20 bytes
+ tcpReply.setWindowSize(WINDOW_SIZE);
+ tcpReply.setUrgentPointer(URGENT_POINTER);
+
+ if (i == numOfSegments - 1) {
+ tcpReply.setFlags(FIN_ACK_PUSH_FLAG);
+ byteEndIndex = httpBytes.length;
+ } else {
+ tcpReply.setFlags(ACK_FLAG);
+ byteEndIndex = (i + 1) * payloadSize;
+ }
+
+ byte[] httpSegmentBytes = Arrays.copyOfRange(httpBytes,
+ byteStartIndex, byteEndIndex);
+
+ Data data = new Data(httpSegmentBytes);
+ tcpReply.setPayload(data);
+
+ tcpReplies.add(tcpReply);
+ }
+ }
+
+ return tcpReplies;
}
/**
@@ -411,6 +463,43 @@
}
/**
+ * Builds a set of ethernet frames with the given IPv4 and TCP payload.
+ *
+ * @param ethRequest ethernet request frame
+ * @param ipv4Request IPv4 request
+ * @param tcpReplies TCP replies
+ * @return a set of ethernet frames
+ */
+ private List<Ethernet> buildEthFrames(Ethernet ethRequest,
+ IPv4 ipv4Request,
+ List<TCP> tcpReplies) {
+
+ List<Ethernet> ethReplies = Lists.newArrayList();
+
+ for (TCP tcpReply : tcpReplies) {
+
+ 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.setProtocol(IPv4.PROTOCOL_TCP);
+ ipv4Reply.setPayload(tcpReply);
+
+ ethReply.setPayload(ipv4Reply);
+
+ ethReplies.add(ethReply);
+ }
+
+ return ethReplies;
+ }
+
+ /**
* Obtains the metadata path.
*
* @param uri metadata request URI