diff options
Diffstat (limited to 'framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMP.java')
-rw-r--r-- | framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMP.java | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMP.java new file mode 100644 index 00000000..e7abbd6a --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/IGMP.java @@ -0,0 +1,335 @@ +/* + * 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 recognized"; + igmp.unsupportTypeData = bb.array(); + 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; + } +} |