Rusty Eddy
Committed by Gerrit Code Review

Serialize / deserialize functions for IGMP, IGMPv3 Membership

Query and IGMPv3 Membership Report.  IGMP has been added to
the IPv4 deserialization map.

Change-Id: I6d46c3771b6589f1cbd839c58521ffab94b5e230
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onlab.packet;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import static org.slf4j.LoggerFactory.getLogger;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.packet.PacketUtils.checkInput;
/**
* Implements IGMP control packet format.
*/
public class IGMP extends BasePacket {
private final Logger log = getLogger(getClass());
public static final byte TYPE_IGMPV3_MEMBERSHIP_QUERY = 0x11;
public static final byte TYPE_IGMPV1_MEMBERSHIP_REPORT = 0x12;
public static final byte TYPE_IGMPV2_MEMBERSHIP_REPORT = 0x16;
public static final byte TYPE_IGMPV2_LEAVE_GROUP = 0x17;
public static final byte TYPE_IGMPV3_MEMBERSHIP_REPORT = 0x22;
public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP = new HashMap<>();
public static final int MINIMUM_HEADER_LEN = 12;
List<IGMPGroup> groups = new ArrayList<>();
// Fields contained in the IGMP header
private byte igmpType;
private byte resField = 0;
private short checksum = 0;
private byte[] unsupportTypeData;
public IGMP() {
}
/**
* Get the IGMP message type.
*
* @return the IGMP message type
*/
public byte getIgmpType() {
return igmpType;
}
/**
* Set the IGMP message type.
*
* @param msgType IGMP message type
*/
public void setIgmpType(byte msgType) {
igmpType = msgType;
}
/**
* Get the checksum of this message.
*
* @return the checksum
*/
public short getChecksum() {
return checksum;
}
/**
* get the Max Resp Code.
*
* @return The Maximum Time allowed before before sending a responding report.
*/
public byte getMaxRespField() {
return resField;
}
/**
* Set the Max Resp Code.
*
* @param respCode the Maximum Response Code.
*/
public void setMaxRespCode(byte respCode) {
if (igmpType != IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY) {
log.debug("Requesting the max response code for an incorrect field: ");
}
this.resField = respCode;
}
/**
* Get the list of IGMPGroups. The group objects will be either IGMPQuery or IGMPMembership
* depending on the IGMP message type. For IGMP Query, the groups list should only be
* one group.
*
* @return The list of IGMP groups.
*/
public List<IGMPGroup> getGroups() {
return groups;
}
/**
* Add a multicast group to this IGMP message.
*
* @param group the IGMPGroup will be IGMPQuery or IGMPMembership depending on the message type.
* @return true if group was valid and added, false otherwise.
*/
public boolean addGroup(IGMPGroup group) {
checkNotNull(group);
switch (this.igmpType) {
case TYPE_IGMPV3_MEMBERSHIP_QUERY:
if (group instanceof IGMPMembership) {
return false;
}
if (group.sources.size() > 1) {
return false;
}
break;
case TYPE_IGMPV3_MEMBERSHIP_REPORT:
if (group instanceof IGMPMembership) {
return false;
}
break;
default:
log.debug("Warning no IGMP message type has been set");
}
this.groups.add(group);
return true;
}
/**
* Serialize this IGMP packet. This will take care
* of serializing IGMPv3 Queries and IGMPv3 Membership
* Reports.
*
* @return the serialized IGMP message
*/
@Override
public byte[] serialize() {
byte [] data = new byte[8915];
ByteBuffer bb = ByteBuffer.wrap(data);
bb.put(this.getIgmpType());
// reserved or max resp code depending on type.
bb.put(this.resField);
// Must calculate checksum
bb.putShort((short) 0);
switch (this.igmpType) {
case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
// reserved
bb.putShort((short) 0);
// Number of groups
bb.putShort((short) groups.size());
// Fall through
case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
for (IGMPGroup grp : groups) {
grp.serialize(bb);
}
break;
default:
bb.put(this.unsupportTypeData);
break;
}
int size = bb.position();
bb.position(0);
byte [] rdata = new byte[size];
bb.get(rdata, 0, size);
return rdata;
}
/**
* Deserialize an IGMP message.
*
* @param data bytes to deserialize
* @param offset offset to start deserializing from
* @param length length of the data to deserialize
* @return populated IGMP object
*/
@Override
public IPacket deserialize(final byte[] data, final int offset,
final int length) {
IGMP igmp = new IGMP();
try {
igmp = IGMP.deserializer().deserialize(data, offset, length);
} catch (DeserializationException e) {
log.error(e.getStackTrace().toString());
return this;
}
this.igmpType = igmp.igmpType;
this.resField = igmp.resField;
this.checksum = igmp.checksum;
this.groups = igmp.groups;
return this;
}
/**
* Deserializer function for IPv4 packets.
*
* @return deserializer function
*/
public static Deserializer<IGMP> deserializer() {
return (data, offset, length) -> {
checkInput(data, offset, length, MINIMUM_HEADER_LEN);
IGMP igmp = new IGMP();
ByteBuffer bb = ByteBuffer.wrap(data);
igmp.igmpType = bb.get();
igmp.resField = bb.get();
igmp.checksum = bb.getShort();
int len = MINIMUM_HEADER_LEN;
String msg;
switch (igmp.igmpType) {
case TYPE_IGMPV3_MEMBERSHIP_QUERY:
IGMPQuery qgroup = new IGMPQuery();
qgroup.deserialize(bb);
igmp.groups.add(qgroup);
break;
case TYPE_IGMPV3_MEMBERSHIP_REPORT:
bb.getShort(); // Ignore resvd
int ngrps = bb.getShort();
for (; ngrps > 0; ngrps--) {
IGMPMembership mgroup = new IGMPMembership();
mgroup.deserialize(bb);
igmp.groups.add(mgroup);
}
break;
/*
* NOTE: according to the IGMPv3 spec. These previous IGMP type fields
* must be supported. At this time we are going to <b>assume</b> we run
* in a modern network where all devices are IGMPv3 capable.
*/
case TYPE_IGMPV1_MEMBERSHIP_REPORT:
case TYPE_IGMPV2_MEMBERSHIP_REPORT:
case TYPE_IGMPV2_LEAVE_GROUP:
igmp.unsupportTypeData = bb.array(); // Is this the entire array?
msg = "IGMP message type: " + igmp.igmpType + " is not supported";
igmp.log.debug(msg);
break;
default:
msg = "IGMP message type: " + igmp.igmpType + " is not recodnized";
igmp.log.debug(msg);
break;
}
return igmp;
};
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof IGMP)) {
return false;
}
final IGMP other = (IGMP) obj;
if (this.igmpType != other.igmpType) {
return false;
}
if (this.resField != other.resField) {
return false;
}
if (this.checksum != other.checksum) {
return false;
}
if (this.groups.size() != other.groups.size()) {
return false;
}
// TODO: equals should be true regardless of order.
if (!groups.equals(other.groups)) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 2521;
int result = super.hashCode();
result = prime * result + this.igmpType;
result = prime * result + this.groups.size();
result = prime * result + this.resField;
result = prime * result + this.checksum;
result = prime * result + this.groups.hashCode();
return result;
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onlab.packet;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* A class to represent Groups for membership query and reports.
*/
public abstract class IGMPGroup {
protected int auxInfo;
protected IpAddress gaddr;
protected List<IpAddress> sources = new ArrayList<>();
public IGMPGroup() {
}
/**
* Initialize this object with a multicast group address and additional info.
*
* @param gaddr: the multicast group address for this message type.
* @param auxInfo: additional info potentially used by IGMPQuery
*/
public IGMPGroup(IpAddress gaddr, int auxInfo) {
this.gaddr = gaddr;
this.auxInfo = auxInfo;
}
/**
* Get the multicast group address.
*
* @return the group address
*/
public IpAddress getGaddr() {
return this.gaddr;
}
/**
* Get the auxillary info.
*
* @return the auxillary info
*/
public int getAuxInfo() {
return this.auxInfo;
}
/**
* Add a unicast source address to this message.
*
* @param saddr IPv4 unicast source address
*/
public void addSource(IpAddress saddr) {
sources.add(saddr);
}
/**
* Return the list of source addresses.
*
* @return list of source addresses
*/
public List<IpAddress> getSources() {
return sources;
}
/**
* Deserialize an IGMPQuery or IGMPMembership message.
*
* @param bb the ByteBuffer wrapping the serialized message. The position of the
* ByteBuffer should be pointing at the head of either message type.
* @return An object populated with the respective IGMPGroup subclass
* @throws DeserializationException in case deserialization goes wrong
*/
public abstract IGMPGroup deserialize(ByteBuffer bb) throws DeserializationException;
/**
* Serialize the IGMPGroup subclass.
*
* @param bb the ByteBuffer to write into, positioned at the next spot to be written to.
* @return The serialized message
*/
public abstract byte[] serialize(ByteBuffer bb);
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onlab.packet;
import java.nio.ByteBuffer;
import static org.onlab.packet.PacketUtils.checkBufferLength;
public class IGMPMembership extends IGMPGroup {
public static final byte MODE_IS_INCLUDE = 0x1;
public static final byte MODE_IS_EXCLUDE = 0x2;
public static final byte CHANGE_TO_INCLUDE_MODE = 0x3;
public static final byte CHANGE_TO_EXCLUDE_MODE = 0x4;
public static final byte ALLOW_NEW_SOURCES = 0x5;
public static final byte BLOCK_OLD_SOURCES = 0x6;
private final int minGroupRecordLen = Ip4Address.BYTE_LENGTH + 4;
protected byte recordType;
protected byte auxDataLength = 0;
protected byte[] auxData;
/**
* Constructor initialized with a multicast group address.
*
* @param gaddr A multicast group address.
*/
public IGMPMembership(Ip4Address gaddr) {
super(gaddr, 0);
}
/**
* Default constructor.
*/
public IGMPMembership() {
super();
}
/**
* Serialize this Membership Report.
*
* @param bb the ByteBuffer to write into, positioned at the next spot to be written to.
* @return serialized IGMP message.
*/
@Override
public byte[] serialize(ByteBuffer bb) {
bb.put(recordType);
bb.put(auxDataLength); // reserved
bb.putShort((short) sources.size());
bb.put(gaddr.toOctets());
for (IpAddress ipaddr : sources) {
bb.put(ipaddr.toOctets());
}
if (auxDataLength > 0) {
bb.put(auxData);
}
return bb.array();
}
/**
* Deserialize the IGMP Membership report packet.
*
* @param bb the ByteBuffer wrapping the serialized message. The position of the
* ByteBuffer should be pointing at the head of either message type.
* @return
* @throws DeserializationException
*/
public IGMPGroup deserialize(ByteBuffer bb) throws DeserializationException {
// Make sure there is enough buffer to read the header,
// including the number of sources
checkBufferLength(bb.remaining(), 0, minGroupRecordLen);
recordType = bb.get();
auxDataLength = bb.get();
int nsrcs = bb.getShort();
gaddr = Ip4Address.valueOf(bb.getInt());
// Make sure we have enough buffer to hold all of these sources
checkBufferLength(bb.remaining(), 0, Ip4Address.BYTE_LENGTH * nsrcs);
for (; nsrcs > 0; nsrcs--) {
Ip4Address src = Ip4Address.valueOf(bb.getInt());
this.sources.add(src);
}
if (auxDataLength > 0) {
auxData = new byte[auxDataLength];
bb.get(auxData, 0, auxDataLength);
}
return this;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals()
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IGMPMembership)) {
return false;
}
IGMPMembership other = (IGMPMembership) obj;
if (!this.gaddr.equals(other.gaddr)) {
return false;
}
if (this.recordType != other.recordType) {
return false;
}
if (this.auxDataLength != other.auxDataLength) {
return false;
}
if (this.sources.size() != other.sources.size()) {
return false;
}
// TODO: make these tolerant of order
if (!this.sources.equals(other.sources)) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 2521;
int result = super.hashCode();
result = prime * result + this.gaddr.hashCode();
result = prime * result + this.recordType;
result = prime * result + this.auxDataLength;
result = prime * result + this.sources.hashCode();
return result;
}
}
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onlab.packet;
import java.nio.ByteBuffer;
public class IGMPQuery extends IGMPGroup {
// Bits and bytes after the group address
private byte resv = 0;
private boolean sbit = false;
private byte qrv = 2;
private byte qqic = 0x7d;
/**
* Create IGMP Query message.
*
* @param gaddr initiaze with a group address.
* @param auxInfo auxillary info.
*/
public IGMPQuery(IpAddress gaddr, int auxInfo) {
super(gaddr, auxInfo);
}
/**
* Create IGMP Query message.
*/
public IGMPQuery() {
super();
}
/**
* Is the S flag set? Telling adjacent routers to suppress normal timer updates.
*
* @return true if the flag is set, false otherwise
*/
public boolean isSbit() {
return sbit;
}
/**
* Set the S flag. Default is false.
*
* @param sbit true or false
*/
public void setSbit(boolean sbit) {
this.sbit = sbit;
}
/**
* Get the Querier Robustness Variable.
*
* @return
*/
public byte getQrv() {
return qrv;
}
/**
* Set the Querier Robustness Variable. Default is 2.
*
* @param qrv
*/
public void setQrv(byte qrv) {
this.qrv = qrv;
}
/**
* Get the reserved field. Should be zero, but ignored regardless of it's value.
*
* @return the reserved field.
*/
public byte getResv() {
return resv;
}
/**
* Set the reserved field. Should be 0 and ignored by receivers.
*
* @param resv the reserved field.
*/
public void setResv(byte resv) {
this.resv = resv;
}
/**
* Serialize this IGMPQuery.
*
* @param bb the ByteBuffer to write into, positioned at the next spot to be written to.
* @return the serialized message
*/
@Override
public byte[] serialize(ByteBuffer bb) {
bb.put(gaddr.toOctets());
byte fld = (byte) (0x7 & qrv);
bb.put(fld);
bb.put(qqic);
bb.putShort((short) sources.size());
for (IpAddress ipaddr : sources) {
bb.put(ipaddr.toOctets());
}
return bb.array();
}
/**
* Deserialize the IGMP Query group structure.
*
* @param bb ByteBuffer pointing at the IGMP Query group address
* @return the IGMP Group object
*/
public IGMPGroup deserialize(ByteBuffer bb) throws DeserializationException {
gaddr = Ip4Address.valueOf(bb.getInt());
byte fld = bb.get();
// Just ignore the reserved bits
resv = 0;
this.sbit = ((fld & 0x8) == 0x8);
qrv = (byte) (fld & 0x7);
// QQIC field
qqic = bb.get();
// Get the number of sources.
short nsrcs = bb.getShort();
// Do a sanity check on the amount of space we have in our buffer.
int lengthNeeded = (Ip4Address.BYTE_LENGTH * nsrcs);
PacketUtils.checkHeaderLength(bb.remaining(), lengthNeeded);
for (; nsrcs > 0; nsrcs--) {
Ip4Address ipaddr = Ip4Address.valueOf(bb.getInt());
this.sources.add(ipaddr);
}
return this;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals()
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IGMPQuery)) {
return false;
}
IGMPQuery other = (IGMPQuery) obj;
if (this.sbit != other.sbit) {
return false;
}
if (this.qrv != other.qrv) {
return false;
}
if (this.qqic != other.qqic) {
return false;
}
if (this.sources.size() != other.sources.size()) {
return false;
}
// TODO: make these tolerant of order
if (!this.sources.equals(other.sources)) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 2521;
int result = super.hashCode();
result = prime * result + this.gaddr.hashCode();
result = prime * result + this.qqic;
result = prime * result + this.qrv;
result = prime * result + this.sources.hashCode();
return result;
}
}
\ No newline at end of file
......@@ -32,6 +32,7 @@ import static org.onlab.packet.PacketUtils.*;
*/
public class IPv4 extends BasePacket {
public static final byte PROTOCOL_ICMP = 0x1;
public static final byte PROTOCOL_IGMP = 0x2;
public static final byte PROTOCOL_TCP = 0x6;
public static final byte PROTOCOL_UDP = 0x11;
public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP =
......@@ -39,6 +40,7 @@ public class IPv4 extends BasePacket {
static {
IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_ICMP, ICMP.deserializer());
IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_IGMP, IGMP.deserializer());
IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_TCP, TCP.deserializer());
IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_UDP, UDP.deserializer());
}
......
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onlab.packet;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.Assert.assertTrue;
/**
* Unit tests for IGMP class.
*/
public class IGMPTest {
private Deserializer<IGMP> deserializer;
private IGMP igmpQuery;
private IGMP igmpMembership;
private Ip4Address gaddr1;
private Ip4Address gaddr2;
private Ip4Address saddr1;
private Ip4Address saddr2;
@Before
public void setUp() throws Exception {
gaddr1 = Ip4Address.valueOf(0xe1010101);
gaddr2 = Ip4Address.valueOf(0xe2020202);
saddr1 = Ip4Address.valueOf(0x0a010101);
saddr2 = Ip4Address.valueOf(0x0b020202);
deserializer = IGMP.deserializer();
// Create an IGMP Query object
igmpQuery = new IGMP();
igmpQuery.setIgmpType(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY);
igmpQuery.setMaxRespCode((byte) 0x7f);
IGMPQuery q = new IGMPQuery(gaddr1, (byte) 0x7f);
q.addSource(saddr1);
q.addSource(saddr2);
q.setSbit(false);
igmpQuery.groups.add(q);
// Create an IGMP Membership Object
igmpMembership = new IGMP();
igmpMembership.setIgmpType(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT);
IGMPMembership g1 = new IGMPMembership(gaddr1);
g1.addSource(saddr1);
g1.addSource(saddr2);
igmpMembership.groups.add(g1);
IGMPMembership g2 = new IGMPMembership(gaddr2);
g2.addSource(saddr1);
g2.addSource(saddr2);
igmpMembership.groups.add(g2);
}
@Test
public void testDeserializeBadInput() throws Exception {
PacketTestUtils.testDeserializeBadInput(deserializer);
}
@Test
public void testDeserializeTruncated() throws Exception {
byte [] bits = igmpQuery.serialize();
PacketTestUtils.testDeserializeTruncated(deserializer, bits);
bits = igmpMembership.serialize();
PacketTestUtils.testDeserializeTruncated(deserializer, bits);
}
@Test
public void testDeserializeQuery() throws Exception {
byte [] data = igmpQuery.serialize();
IGMP igmp = deserializer.deserialize(data, 0, data.length);
assertTrue(igmp.equals(igmpQuery));
}
@Test
public void testDeserializeMembership() throws Exception {
byte [] data = igmpMembership.serialize();
IGMP igmp = deserializer.deserialize(data, 0, data.length);
assertTrue(igmp.equals(igmpMembership));
}
}