blob: 254f13252f69ab46ae319d5519000f4a69a0bd81 [file] [log] [blame]
Rusty Eddy1da61a22015-09-01 00:48:58 +00001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onlab.packet;
17
18import java.nio.ByteBuffer;
19import java.util.ArrayList;
20import java.util.HashMap;
21import java.util.List;
22import java.util.Map;
23import org.slf4j.Logger;
24
25import static org.slf4j.LoggerFactory.getLogger;
26import static com.google.common.base.Preconditions.checkNotNull;
27import static org.onlab.packet.PacketUtils.checkInput;
28
29
30/**
31 * Implements IGMP control packet format.
32 */
33public class IGMP extends BasePacket {
34 private final Logger log = getLogger(getClass());
35
36 public static final byte TYPE_IGMPV3_MEMBERSHIP_QUERY = 0x11;
37 public static final byte TYPE_IGMPV1_MEMBERSHIP_REPORT = 0x12;
38 public static final byte TYPE_IGMPV2_MEMBERSHIP_REPORT = 0x16;
39 public static final byte TYPE_IGMPV2_LEAVE_GROUP = 0x17;
40 public static final byte TYPE_IGMPV3_MEMBERSHIP_REPORT = 0x22;
41 public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP = new HashMap<>();
42
43 public static final int MINIMUM_HEADER_LEN = 12;
44
45 List<IGMPGroup> groups = new ArrayList<>();
46
47 // Fields contained in the IGMP header
48 private byte igmpType;
49 private byte resField = 0;
50 private short checksum = 0;
51
52 private byte[] unsupportTypeData;
53
54 public IGMP() {
55 }
56
57 /**
58 * Get the IGMP message type.
59 *
60 * @return the IGMP message type
61 */
62 public byte getIgmpType() {
63 return igmpType;
64 }
65
66 /**
67 * Set the IGMP message type.
68 *
69 * @param msgType IGMP message type
70 */
71 public void setIgmpType(byte msgType) {
72 igmpType = msgType;
73 }
74
75 /**
76 * Get the checksum of this message.
77 *
78 * @return the checksum
79 */
80 public short getChecksum() {
81 return checksum;
82 }
83
84 /**
85 * get the Max Resp Code.
86 *
87 * @return The Maximum Time allowed before before sending a responding report.
88 */
89 public byte getMaxRespField() {
90 return resField;
91 }
92
93 /**
94 * Set the Max Resp Code.
95 *
96 * @param respCode the Maximum Response Code.
97 */
98 public void setMaxRespCode(byte respCode) {
99 if (igmpType != IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY) {
100 log.debug("Requesting the max response code for an incorrect field: ");
101 }
102 this.resField = respCode;
103 }
104
105 /**
106 * Get the list of IGMPGroups. The group objects will be either IGMPQuery or IGMPMembership
107 * depending on the IGMP message type. For IGMP Query, the groups list should only be
108 * one group.
109 *
110 * @return The list of IGMP groups.
111 */
112 public List<IGMPGroup> getGroups() {
113 return groups;
114 }
115
116 /**
117 * Add a multicast group to this IGMP message.
118 *
119 * @param group the IGMPGroup will be IGMPQuery or IGMPMembership depending on the message type.
120 * @return true if group was valid and added, false otherwise.
121 */
122 public boolean addGroup(IGMPGroup group) {
123 checkNotNull(group);
124 switch (this.igmpType) {
125 case TYPE_IGMPV3_MEMBERSHIP_QUERY:
126 if (group instanceof IGMPMembership) {
127 return false;
128 }
129
130 if (group.sources.size() > 1) {
131 return false;
132 }
133 break;
134
135 case TYPE_IGMPV3_MEMBERSHIP_REPORT:
136 if (group instanceof IGMPMembership) {
137 return false;
138 }
139 break;
140
141 default:
142 log.debug("Warning no IGMP message type has been set");
143 }
144
145 this.groups.add(group);
146 return true;
147 }
148
149 /**
150 * Serialize this IGMP packet. This will take care
151 * of serializing IGMPv3 Queries and IGMPv3 Membership
152 * Reports.
153 *
154 * @return the serialized IGMP message
155 */
156 @Override
157 public byte[] serialize() {
158 byte [] data = new byte[8915];
159
160 ByteBuffer bb = ByteBuffer.wrap(data);
161 bb.put(this.getIgmpType());
162
163 // reserved or max resp code depending on type.
164 bb.put(this.resField);
165
166 // Must calculate checksum
167 bb.putShort((short) 0);
168
169 switch (this.igmpType) {
170
171 case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
172 // reserved
173 bb.putShort((short) 0);
174 // Number of groups
175 bb.putShort((short) groups.size());
176 // Fall through
177
178 case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
179
180 for (IGMPGroup grp : groups) {
181 grp.serialize(bb);
182 }
183 break;
184
185 default:
186 bb.put(this.unsupportTypeData);
187 break;
188 }
189
190 int size = bb.position();
191 bb.position(0);
192 byte [] rdata = new byte[size];
193 bb.get(rdata, 0, size);
194 return rdata;
195 }
196
197 /**
198 * Deserialize an IGMP message.
199 *
200 * @param data bytes to deserialize
201 * @param offset offset to start deserializing from
202 * @param length length of the data to deserialize
203 * @return populated IGMP object
204 */
205 @Override
206 public IPacket deserialize(final byte[] data, final int offset,
207 final int length) {
208
209 IGMP igmp = new IGMP();
210 try {
211 igmp = IGMP.deserializer().deserialize(data, offset, length);
212 } catch (DeserializationException e) {
213 log.error(e.getStackTrace().toString());
214 return this;
215 }
216 this.igmpType = igmp.igmpType;
217 this.resField = igmp.resField;
218 this.checksum = igmp.checksum;
219 this.groups = igmp.groups;
220 return this;
221 }
222
223 /**
224 * Deserializer function for IPv4 packets.
225 *
226 * @return deserializer function
227 */
228 public static Deserializer<IGMP> deserializer() {
229 return (data, offset, length) -> {
230 checkInput(data, offset, length, MINIMUM_HEADER_LEN);
231
232 IGMP igmp = new IGMP();
233
Rusty Eddy158d5d82015-10-12 16:59:04 -0700234 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
Rusty Eddy1da61a22015-09-01 00:48:58 +0000235 igmp.igmpType = bb.get();
236 igmp.resField = bb.get();
237 igmp.checksum = bb.getShort();
238 int len = MINIMUM_HEADER_LEN;
239 String msg;
240
241 switch (igmp.igmpType) {
242
243 case TYPE_IGMPV3_MEMBERSHIP_QUERY:
244 IGMPQuery qgroup = new IGMPQuery();
245 qgroup.deserialize(bb);
246 igmp.groups.add(qgroup);
247 break;
248
249 case TYPE_IGMPV3_MEMBERSHIP_REPORT:
250 bb.getShort(); // Ignore resvd
251 int ngrps = bb.getShort();
252
253 for (; ngrps > 0; ngrps--) {
254 IGMPMembership mgroup = new IGMPMembership();
255 mgroup.deserialize(bb);
256 igmp.groups.add(mgroup);
257 }
258 break;
259
260 /*
261 * NOTE: according to the IGMPv3 spec. These previous IGMP type fields
262 * must be supported. At this time we are going to <b>assume</b> we run
263 * in a modern network where all devices are IGMPv3 capable.
264 */
265 case TYPE_IGMPV1_MEMBERSHIP_REPORT:
266 case TYPE_IGMPV2_MEMBERSHIP_REPORT:
267 case TYPE_IGMPV2_LEAVE_GROUP:
268 igmp.unsupportTypeData = bb.array(); // Is this the entire array?
269 msg = "IGMP message type: " + igmp.igmpType + " is not supported";
270 igmp.log.debug(msg);
271 break;
272
273 default:
alshabib0588e572015-09-04 12:00:42 -0700274 msg = "IGMP message type: " + igmp.igmpType + " is not recognized";
275 igmp.unsupportTypeData = bb.array();
Rusty Eddy1da61a22015-09-01 00:48:58 +0000276 igmp.log.debug(msg);
277 break;
278 }
279 return igmp;
280 };
281 }
282
283 /*
284 * (non-Javadoc)
285 *
286 * @see java.lang.Object#equals(java.lang.Object)
287 */
288 @Override
289 public boolean equals(final Object obj) {
290 if (this == obj) {
291 return true;
292 }
293 if (!super.equals(obj)) {
294 return false;
295 }
296 if (!(obj instanceof IGMP)) {
297 return false;
298 }
299 final IGMP other = (IGMP) obj;
300 if (this.igmpType != other.igmpType) {
301 return false;
302 }
303 if (this.resField != other.resField) {
304 return false;
305 }
306 if (this.checksum != other.checksum) {
307 return false;
308 }
309 if (this.groups.size() != other.groups.size()) {
310 return false;
311 }
312 // TODO: equals should be true regardless of order.
313 if (!groups.equals(other.groups)) {
314 return false;
315 }
316 return true;
317 }
318
319 /*
320 * (non-Javadoc)
321 *
322 * @see java.lang.Object#hashCode()
323 */
324 @Override
325 public int hashCode() {
326 final int prime = 2521;
327 int result = super.hashCode();
328 result = prime * result + this.igmpType;
329 result = prime * result + this.groups.size();
330 result = prime * result + this.resField;
331 result = prime * result + this.checksum;
332 result = prime * result + this.groups.hashCode();
333 return result;
334 }
335}