ONOS-3830: Adding support for serialization/de-serialization of QinQ packets, support for TPID other than 0x88A8
Change-Id: I6f56c5afe0fcd439ca2be848e7da8a68b577cc16
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
index e317818..09a79f5 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
@@ -46,6 +46,7 @@
public static final short TYPE_IPV6 = EthType.EtherType.IPV6.ethType().toShort();
public static final short TYPE_LLDP = EthType.EtherType.LLDP.ethType().toShort();
public static final short TYPE_VLAN = EthType.EtherType.VLAN.ethType().toShort();
+ public static final short TYPE_QINQ = EthType.EtherType.QINQ.ethType().toShort();
public static final short TYPE_BSN = EthType.EtherType.BDDP.ethType().toShort();
public static final short MPLS_UNICAST = EthType.EtherType.MPLS_UNICAST.ethType().toShort();
@@ -73,7 +74,10 @@
protected MacAddress destinationMACAddress;
protected MacAddress sourceMACAddress;
protected byte priorityCode;
+ protected byte qInQPriorityCode;
protected short vlanID;
+ protected short qinqVID;
+ protected short qinqTPID;
protected short etherType;
protected boolean pad = false;
@@ -83,6 +87,8 @@
public Ethernet() {
super();
this.vlanID = Ethernet.VLAN_UNTAGGED;
+ this.qinqVID = Ethernet.VLAN_UNTAGGED;
+ this.qinqTPID = TYPE_QINQ;
}
/**
@@ -208,6 +214,26 @@
}
/**
+ * Gets the QinQ priority code.
+ *
+ * @return the qInQPriorityCode
+ */
+ public byte getQinQPriorityCode() {
+ return this.qInQPriorityCode;
+ }
+
+ /**
+ * Sets the QinQ priority code.
+ *
+ * @param priority the priorityCode to set
+ * @return the Ethernet frame
+ */
+ public Ethernet setQinQPriorityCode(final byte priority) {
+ this.qInQPriorityCode = priority;
+ return this;
+ }
+
+ /**
* Gets the VLAN ID.
*
* @return the vlanID
@@ -228,6 +254,47 @@
}
/**
+ * Gets the QinQ VLAN ID.
+ *
+ * @return the QinQ vlanID
+ */
+ public short getQinQVID() {
+ return this.qinqVID;
+ }
+
+ /**
+ * Sets the QinQ VLAN ID.
+ *
+ * @param vlan the vlanID to set
+ * @return the Ethernet frame
+ */
+ public Ethernet setQinQVID(final short vlan) {
+ this.qinqVID = vlan;
+ return this;
+ }
+ /**
+ * Gets the QinQ TPID.
+ *
+ * @return the QinQ TPID
+ */
+ public short getQinQTPID() {
+ return this.qinqTPID;
+ }
+
+ /**
+ * Sets the QinQ TPID.
+ *
+ * @param tpId the TPID to set
+ * @return the Ethernet frame
+ */
+ public Ethernet setQinQTPID(final short tpId) {
+ if (tpId != TYPE_VLAN && tpId != TYPE_QINQ) {
+ return null;
+ }
+ this.qinqTPID = tpId;
+ return this;
+ }
+ /**
* Gets the Ethernet type.
*
* @return the etherType
@@ -291,6 +358,7 @@
payloadData = this.payload.serialize();
}
int length = 14 + (this.vlanID == Ethernet.VLAN_UNTAGGED ? 0 : 4)
+ + (this.qinqVID == Ethernet.VLAN_UNTAGGED ? 0 : 4)
+ (payloadData == null ? 0 : payloadData.length);
if (this.pad && length < 60) {
length = 60;
@@ -299,6 +367,10 @@
final ByteBuffer bb = ByteBuffer.wrap(data);
bb.put(this.destinationMACAddress.toBytes());
bb.put(this.sourceMACAddress.toBytes());
+ if (this.qinqVID != Ethernet.VLAN_UNTAGGED) {
+ bb.putShort(this.qinqTPID);
+ bb.putShort((short) (this.qInQPriorityCode << 13 | this.qinqVID & 0x0fff));
+ }
if (this.vlanID != Ethernet.VLAN_UNTAGGED) {
bb.putShort(TYPE_VLAN);
bb.putShort((short) (this.priorityCode << 13 | this.vlanID & 0x0fff));
@@ -335,11 +407,36 @@
this.sourceMACAddress = MacAddress.valueOf(srcAddr);
short ethType = bb.getShort();
+ if (ethType == TYPE_QINQ) {
+ final short tci = bb.getShort();
+ this.qInQPriorityCode = (byte) (tci >> 13 & 0x07);
+ this.qinqVID = (short) (tci & 0x0fff);
+ this.qinqTPID = TYPE_QINQ;
+ ethType = bb.getShort();
+ }
+
if (ethType == TYPE_VLAN) {
final short tci = bb.getShort();
this.priorityCode = (byte) (tci >> 13 & 0x07);
this.vlanID = (short) (tci & 0x0fff);
ethType = bb.getShort();
+
+ // there might be one more tag with 1q TPID
+ if (ethType == TYPE_VLAN) {
+ // packet is double tagged with 1q TPIDs
+ // We handle only double tagged packets here and assume that in this case
+ // TYPE_QINQ above was not hit
+ // We put the values retrieved above with TYPE_VLAN in
+ // qInQ fields
+ this.qInQPriorityCode = this.priorityCode;
+ this.qinqVID = this.vlanID;
+ this.qinqTPID = TYPE_VLAN;
+
+ final short innerTci = bb.getShort();
+ this.priorityCode = (byte) (innerTci >> 13 & 0x07);
+ this.vlanID = (short) (innerTci & 0x0fff);
+ ethType = bb.getShort();
+ }
} else {
this.vlanID = Ethernet.VLAN_UNTAGGED;
}
@@ -427,6 +524,8 @@
int result = super.hashCode();
result = prime * result + this.destinationMACAddress.hashCode();
result = prime * result + this.etherType;
+ result = prime * result + this.qinqVID;
+ result = prime * result + this.qInQPriorityCode;
result = prime * result + this.vlanID;
result = prime * result + this.priorityCode;
result = prime * result + (this.pad ? 1231 : 1237);
@@ -454,6 +553,12 @@
if (!this.destinationMACAddress.equals(other.destinationMACAddress)) {
return false;
}
+ if (this.qInQPriorityCode != other.qInQPriorityCode) {
+ return false;
+ }
+ if (this.qinqVID != other.qinqVID) {
+ return false;
+ }
if (this.priorityCode != other.priorityCode) {
return false;
}
@@ -504,6 +609,13 @@
Integer.toHexString(this.getEtherType() & 0xffff)));
}
+ if (this.getQinQVID() != Ethernet.VLAN_UNTAGGED) {
+ sb.append("\ndl_qinqVlan: ");
+ sb.append(this.getQinQVID());
+ sb.append("\ndl_qinqVlan_pcp: ");
+ sb.append(this.getQinQPriorityCode());
+ }
+
sb.append("\ndl_vlan: ");
if (this.getVlanID() == Ethernet.VLAN_UNTAGGED) {
sb.append("untagged");
@@ -695,12 +807,37 @@
eth.setSourceMACAddress(addressBuffer);
short ethType = bb.getShort();
+ if (ethType == TYPE_QINQ) {
+ // in this case we excpect 2 VLAN headers
+ checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH + VLAN_HEADER_LENGTH);
+ final short tci = bb.getShort();
+ eth.setQinQPriorityCode((byte) (tci >> 13 & 0x07));
+ eth.setQinQVID((short) (tci & 0x0fff));
+ eth.setQinQTPID(TYPE_QINQ);
+ ethType = bb.getShort();
+ }
if (ethType == TYPE_VLAN) {
checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH);
final short tci = bb.getShort();
eth.setPriorityCode((byte) (tci >> 13 & 0x07));
eth.setVlanID((short) (tci & 0x0fff));
ethType = bb.getShort();
+
+ if (ethType == TYPE_VLAN) {
+ // We handle only double tagged packets here and assume that in this case
+ // TYPE_QINQ above was not hit
+ // We put the values retrieved above with TYPE_VLAN in
+ // qInQ fields
+ checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH);
+ eth.setQinQPriorityCode(eth.getPriorityCode());
+ eth.setQinQVID(eth.getVlanID());
+ eth.setQinQTPID(TYPE_VLAN);
+
+ final short innerTci = bb.getShort();
+ eth.setPriorityCode((byte) (innerTci >> 13 & 0x07));
+ eth.setVlanID((short) (innerTci & 0x0fff));
+ ethType = bb.getShort();
+ }
} else {
eth.setVlanID(Ethernet.VLAN_UNTAGGED);
}
diff --git a/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java b/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java
index 7edcc33..508e569 100644
--- a/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java
@@ -21,6 +21,8 @@
import java.nio.ByteBuffer;
+import java.util.Arrays;
+
import static org.junit.Assert.assertEquals;
/**
@@ -32,11 +34,22 @@
private MacAddress srcMac;
private short ethertype = 6;
private short vlan = 5;
+ private short qinqVlan = 55;
private Deserializer<Ethernet> deserializer;
private byte[] byteHeader;
private byte[] vlanByteHeader;
+ private byte[] qinq8100ByteHeader;
+ private byte[] qinq88a8ByteHeader;
+
+ private static byte[] qinqHeaderExpected = {
+ (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88,
+ (byte) 0x88, (byte) 0x88, (byte) 0xaa, (byte) 0xaa,
+ (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa,
+ (byte) 0x88, (byte) 0xa8, (byte) 0x00, (byte) 0x37,
+ (byte) 0x81, (byte) 0x00, (byte) 0x00, (byte) 0x05,
+ (byte) 0x00, (byte) 0x06 };
@Before
public void setUp() {
@@ -68,6 +81,33 @@
bb.putShort(ethertype);
vlanByteHeader = bb.array();
+
+ // Create Ethernet byte array with a QinQ header with TPID 0x8100
+ bb = ByteBuffer.allocate(Ethernet.ETHERNET_HEADER_LENGTH + Ethernet.VLAN_HEADER_LENGTH
+ + Ethernet.VLAN_HEADER_LENGTH);
+ bb.put(dstMacBytes);
+ bb.put(srcMacBytes);
+ bb.putShort(Ethernet.TYPE_VLAN);
+ bb.putShort(vlan);
+ bb.putShort(Ethernet.TYPE_VLAN);
+ bb.putShort(vlan);
+ bb.putShort(ethertype);
+
+ qinq8100ByteHeader = bb.array();
+
+ // Create Ethernet byte array with a QinQ header with TPID 0x88a8
+ bb = ByteBuffer.allocate(Ethernet.ETHERNET_HEADER_LENGTH + Ethernet.VLAN_HEADER_LENGTH
+ + Ethernet.VLAN_HEADER_LENGTH);
+ bb.put(dstMacBytes);
+ bb.put(srcMacBytes);
+ bb.putShort(Ethernet.TYPE_QINQ);
+ bb.putShort(qinqVlan);
+ bb.putShort(Ethernet.TYPE_VLAN);
+ bb.putShort(vlan);
+ bb.putShort(ethertype);
+
+ qinq88a8ByteHeader = bb.array();
+
}
@Test
@@ -100,4 +140,37 @@
assertEquals(ethertype, eth.getEtherType());
}
+ @Test
+ public void testDeserializeWithQinQ() throws Exception {
+ Ethernet eth = deserializer.deserialize(qinq8100ByteHeader, 0, qinq8100ByteHeader.length);
+
+ assertEquals(dstMac, eth.getDestinationMAC());
+ assertEquals(srcMac, eth.getSourceMAC());
+ assertEquals(vlan, eth.getVlanID());
+ assertEquals(vlan, eth.getQinQVID());
+ assertEquals(ethertype, eth.getEtherType());
+
+ eth = deserializer.deserialize(qinq88a8ByteHeader, 0, qinq88a8ByteHeader.length);
+
+ assertEquals(dstMac, eth.getDestinationMAC());
+ assertEquals(srcMac, eth.getSourceMAC());
+ assertEquals(vlan, eth.getVlanID());
+ assertEquals(qinqVlan, eth.getQinQVID());
+ assertEquals(ethertype, eth.getEtherType());
+ }
+
+ @Test
+ public void testSerializeWithQinQ() throws Exception {
+ Ethernet eth = new Ethernet();
+ eth.setDestinationMACAddress(dstMac);
+ eth.setSourceMACAddress(srcMac);
+ eth.setVlanID(vlan);
+ eth.setQinQVID(qinqVlan);
+ eth.setEtherType(ethertype);
+
+ byte[] encoded = eth.serialize();
+
+ assertEquals(Arrays.toString(encoded), Arrays.toString(qinqHeaderExpected));
+ }
+
}