/* * 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> PROTOCOL_DESERIALIZER_MAP = new HashMap<>(); public static final int MINIMUM_HEADER_LEN = 12; List 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 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 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 assume 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; } }