blob: 04308dbd42596f01cd3db00816ac435c2293683d [file] [log] [blame]
Rusty Eddy1da61a22015-09-01 00:48:58 +00001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Rusty Eddy1da61a22015-09-01 00:48:58 +00003 *
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
Luca Pretec8ec4b52016-09-22 16:40:13 -070018import com.google.common.collect.ImmutableList;
Jonathan Hart81d73102016-02-19 10:32:05 -080019import org.slf4j.Logger;
20
Rusty Eddy1da61a22015-09-01 00:48:58 +000021import java.nio.ByteBuffer;
22import java.util.ArrayList;
Ray Milkey9f87e512016-01-05 10:00:22 -080023import java.util.Arrays;
Rusty Eddy1da61a22015-09-01 00:48:58 +000024import java.util.List;
Ray Milkey9f87e512016-01-05 10:00:22 -080025
Jian Li5fc14292015-12-04 11:30:46 -080026import static com.google.common.base.MoreObjects.toStringHelper;
Rusty Eddy1da61a22015-09-01 00:48:58 +000027import static com.google.common.base.Preconditions.checkNotNull;
28import static org.onlab.packet.PacketUtils.checkInput;
Jian Li5fc14292015-12-04 11:30:46 -080029import static org.slf4j.LoggerFactory.getLogger;
Rusty Eddy1da61a22015-09-01 00:48:58 +000030
Rusty Eddy1da61a22015-09-01 00:48:58 +000031/**
32 * Implements IGMP control packet format.
33 */
Luca Pretec8ec4b52016-09-22 16:40:13 -070034public abstract class IGMP extends BasePacket {
35 protected static final Logger log = getLogger(IGMP.class);
Rusty Eddy1da61a22015-09-01 00:48:58 +000036
37 public static final byte TYPE_IGMPV3_MEMBERSHIP_QUERY = 0x11;
38 public static final byte TYPE_IGMPV1_MEMBERSHIP_REPORT = 0x12;
39 public static final byte TYPE_IGMPV2_MEMBERSHIP_REPORT = 0x16;
40 public static final byte TYPE_IGMPV2_LEAVE_GROUP = 0x17;
41 public static final byte TYPE_IGMPV3_MEMBERSHIP_REPORT = 0x22;
Rusty Eddy1da61a22015-09-01 00:48:58 +000042
43 List<IGMPGroup> groups = new ArrayList<>();
44
45 // Fields contained in the IGMP header
Luca Pretec8ec4b52016-09-22 16:40:13 -070046 protected byte igmpType;
47 protected byte resField = 0;
48 protected short checksum = 0;
Rusty Eddy1da61a22015-09-01 00:48:58 +000049
50 private byte[] unsupportTypeData;
51
52 public IGMP() {
53 }
54
55 /**
56 * Get the IGMP message type.
57 *
58 * @return the IGMP message type
59 */
60 public byte getIgmpType() {
61 return igmpType;
62 }
63
64 /**
65 * Set the IGMP message type.
66 *
67 * @param msgType IGMP message type
68 */
69 public void setIgmpType(byte msgType) {
70 igmpType = msgType;
71 }
72
73 /**
74 * Get the checksum of this message.
75 *
76 * @return the checksum
77 */
78 public short getChecksum() {
79 return checksum;
80 }
81
82 /**
83 * get the Max Resp Code.
84 *
85 * @return The Maximum Time allowed before before sending a responding report.
86 */
87 public byte getMaxRespField() {
88 return resField;
89 }
90
91 /**
92 * Set the Max Resp Code.
93 *
94 * @param respCode the Maximum Response Code.
95 */
Luca Pretec8ec4b52016-09-22 16:40:13 -070096 public abstract void setMaxRespCode(byte respCode);
Rusty Eddy1da61a22015-09-01 00:48:58 +000097
98 /**
99 * Get the list of IGMPGroups. The group objects will be either IGMPQuery or IGMPMembership
100 * depending on the IGMP message type. For IGMP Query, the groups list should only be
101 * one group.
102 *
103 * @return The list of IGMP groups.
104 */
105 public List<IGMPGroup> getGroups() {
Luca Pretec8ec4b52016-09-22 16:40:13 -0700106 return ImmutableList.copyOf(groups);
Rusty Eddy1da61a22015-09-01 00:48:58 +0000107 }
108
109 /**
110 * Add a multicast group to this IGMP message.
111 *
112 * @param group the IGMPGroup will be IGMPQuery or IGMPMembership depending on the message type.
113 * @return true if group was valid and added, false otherwise.
114 */
Luca Pretec8ec4b52016-09-22 16:40:13 -0700115 public abstract boolean addGroup(IGMPGroup group);
Rusty Eddy1da61a22015-09-01 00:48:58 +0000116
117 /**
118 * Serialize this IGMP packet. This will take care
119 * of serializing IGMPv3 Queries and IGMPv3 Membership
120 * Reports.
121 *
122 * @return the serialized IGMP message
123 */
Ray Milkeyaef45852016-01-11 17:13:19 -0800124 @java.lang.SuppressWarnings("squid:S128") // suppress switch fall through warning
Rusty Eddy1da61a22015-09-01 00:48:58 +0000125 @Override
126 public byte[] serialize() {
Jian Li68c4fc42016-01-11 16:07:03 -0800127 byte[] data = new byte[8915];
Rusty Eddy1da61a22015-09-01 00:48:58 +0000128
129 ByteBuffer bb = ByteBuffer.wrap(data);
130 bb.put(this.getIgmpType());
131
132 // reserved or max resp code depending on type.
133 bb.put(this.resField);
134
135 // Must calculate checksum
136 bb.putShort((short) 0);
137
Luca Pretec8ec4b52016-09-22 16:40:13 -0700138 if (this instanceof IGMPv3) {
139 switch (this.igmpType) {
Jonathan Hart81d73102016-02-19 10:32:05 -0800140
Luca Pretec8ec4b52016-09-22 16:40:13 -0700141 case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
142 // reserved
143 bb.putShort((short) 0);
144 // Number of groups
145 bb.putShort((short) groups.size());
146 // Fall through
Jonathan Hart81d73102016-02-19 10:32:05 -0800147
Luca Pretec8ec4b52016-09-22 16:40:13 -0700148 case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
Rusty Eddy1da61a22015-09-01 00:48:58 +0000149
Luca Pretec8ec4b52016-09-22 16:40:13 -0700150 for (IGMPGroup grp : groups) {
151 grp.serialize(bb);
152 }
153 break;
Rusty Eddy1da61a22015-09-01 00:48:58 +0000154
Luca Pretec8ec4b52016-09-22 16:40:13 -0700155 default:
156 bb.put(this.unsupportTypeData);
157 break;
158 }
159 } else if (this instanceof IGMPv2) {
160 if (this.groups.isEmpty()) {
161 bb.putInt(0);
162 } else {
163 bb.putInt(groups.get(0).getGaddr().getIp4Address().toInt());
164 }
165 } else {
166 throw new UnsupportedOperationException();
Rusty Eddy1da61a22015-09-01 00:48:58 +0000167 }
168
169 int size = bb.position();
Jonathan Hart81d73102016-02-19 10:32:05 -0800170
171 // compute checksum if needed
172 if (this.checksum == 0) {
173 bb.rewind();
174 int accumulation = 0;
175 for (int i = 0; i < size * 2; ++i) {
176 accumulation += 0xffff & bb.getShort();
177 }
178 accumulation = (accumulation >> 16 & 0xffff)
179 + (accumulation & 0xffff);
180 this.checksum = (short) (~accumulation & 0xffff);
181 bb.putShort(2, this.checksum);
182 }
183
184
Rusty Eddy1da61a22015-09-01 00:48:58 +0000185 bb.position(0);
Jian Li68c4fc42016-01-11 16:07:03 -0800186 byte[] rdata = new byte[size];
Rusty Eddy1da61a22015-09-01 00:48:58 +0000187 bb.get(rdata, 0, size);
188 return rdata;
189 }
190
191 /**
192 * Deserialize an IGMP message.
193 *
194 * @param data bytes to deserialize
195 * @param offset offset to start deserializing from
196 * @param length length of the data to deserialize
197 * @return populated IGMP object
198 */
199 @Override
200 public IPacket deserialize(final byte[] data, final int offset,
201 final int length) {
202
Luca Pretec8ec4b52016-09-22 16:40:13 -0700203 final IGMP igmp;
Rusty Eddy1da61a22015-09-01 00:48:58 +0000204 try {
205 igmp = IGMP.deserializer().deserialize(data, offset, length);
206 } catch (DeserializationException e) {
Luca Pretec8ec4b52016-09-22 16:40:13 -0700207 log.error("Deserialization exception", e);
Rusty Eddy1da61a22015-09-01 00:48:58 +0000208 return this;
209 }
210 this.igmpType = igmp.igmpType;
211 this.resField = igmp.resField;
212 this.checksum = igmp.checksum;
213 this.groups = igmp.groups;
214 return this;
215 }
216
217 /**
218 * Deserializer function for IPv4 packets.
219 *
220 * @return deserializer function
221 */
222 public static Deserializer<IGMP> deserializer() {
223 return (data, offset, length) -> {
Luca Pretec8ec4b52016-09-22 16:40:13 -0700224 checkInput(data, offset, length, IGMPv2.HEADER_LENGTH);
Rusty Eddy1da61a22015-09-01 00:48:58 +0000225
ke hande5f6a72017-06-23 10:36:48 +0800226 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
227 byte igmpType = bb.get();
228 boolean isV2;
229 if (igmpType == TYPE_IGMPV2_MEMBERSHIP_REPORT || igmpType == TYPE_IGMPV2_LEAVE_GROUP ||
230 length == IGMPv2.HEADER_LENGTH) {
231 isV2 = true;
232 } else {
233 isV2 = false;
234 }
Luca Pretec8ec4b52016-09-22 16:40:13 -0700235
236 IGMP igmp = isV2 ? new IGMPv2() : new IGMPv3();
Rusty Eddy1da61a22015-09-01 00:48:58 +0000237
ke hande5f6a72017-06-23 10:36:48 +0800238 igmp.igmpType = igmpType;
Rusty Eddy1da61a22015-09-01 00:48:58 +0000239 igmp.resField = bb.get();
240 igmp.checksum = bb.getShort();
Jonathan Hart81d73102016-02-19 10:32:05 -0800241
Luca Pretec8ec4b52016-09-22 16:40:13 -0700242 if (isV2) {
243 igmp.addGroup(new IGMPQuery(IpAddress.valueOf(bb.getInt()), 0));
244 if (igmp.validChecksum()) {
245 return igmp;
246 }
247 throw new DeserializationException("invalid checksum");
248 }
249
250 // second check for IGMPv3
251 checkInput(data, offset, length, IGMPv3.MINIMUM_HEADER_LEN);
252
Rusty Eddy1da61a22015-09-01 00:48:58 +0000253 String msg;
254
255 switch (igmp.igmpType) {
256
257 case TYPE_IGMPV3_MEMBERSHIP_QUERY:
258 IGMPQuery qgroup = new IGMPQuery();
259 qgroup.deserialize(bb);
260 igmp.groups.add(qgroup);
261 break;
262
263 case TYPE_IGMPV3_MEMBERSHIP_REPORT:
264 bb.getShort(); // Ignore resvd
265 int ngrps = bb.getShort();
266
267 for (; ngrps > 0; ngrps--) {
268 IGMPMembership mgroup = new IGMPMembership();
269 mgroup.deserialize(bb);
270 igmp.groups.add(mgroup);
271 }
272 break;
273
274 /*
275 * NOTE: according to the IGMPv3 spec. These previous IGMP type fields
276 * must be supported. At this time we are going to <b>assume</b> we run
277 * in a modern network where all devices are IGMPv3 capable.
278 */
279 case TYPE_IGMPV1_MEMBERSHIP_REPORT:
280 case TYPE_IGMPV2_MEMBERSHIP_REPORT:
281 case TYPE_IGMPV2_LEAVE_GROUP:
282 igmp.unsupportTypeData = bb.array(); // Is this the entire array?
283 msg = "IGMP message type: " + igmp.igmpType + " is not supported";
284 igmp.log.debug(msg);
285 break;
286
287 default:
alshabib0588e572015-09-04 12:00:42 -0700288 msg = "IGMP message type: " + igmp.igmpType + " is not recognized";
289 igmp.unsupportTypeData = bb.array();
Rusty Eddy1da61a22015-09-01 00:48:58 +0000290 igmp.log.debug(msg);
291 break;
292 }
293 return igmp;
294 };
295 }
296
Luca Pretec8ec4b52016-09-22 16:40:13 -0700297 /**
298 * Validates the message's checksum.
299 *
300 * @return true if valid, false if not
301 */
302 protected abstract boolean validChecksum();
303
Rusty Eddy1da61a22015-09-01 00:48:58 +0000304 /*
305 * (non-Javadoc)
306 *
307 * @see java.lang.Object#equals(java.lang.Object)
308 */
309 @Override
310 public boolean equals(final Object obj) {
311 if (this == obj) {
312 return true;
313 }
314 if (!super.equals(obj)) {
315 return false;
316 }
317 if (!(obj instanceof IGMP)) {
318 return false;
319 }
320 final IGMP other = (IGMP) obj;
321 if (this.igmpType != other.igmpType) {
322 return false;
323 }
324 if (this.resField != other.resField) {
325 return false;
326 }
327 if (this.checksum != other.checksum) {
328 return false;
329 }
330 if (this.groups.size() != other.groups.size()) {
331 return false;
332 }
333 // TODO: equals should be true regardless of order.
334 if (!groups.equals(other.groups)) {
335 return false;
336 }
337 return true;
338 }
339
340 /*
341 * (non-Javadoc)
342 *
343 * @see java.lang.Object#hashCode()
344 */
345 @Override
346 public int hashCode() {
347 final int prime = 2521;
348 int result = super.hashCode();
349 result = prime * result + this.igmpType;
350 result = prime * result + this.groups.size();
351 result = prime * result + this.resField;
352 result = prime * result + this.checksum;
353 result = prime * result + this.groups.hashCode();
354 return result;
355 }
Jian Li5fc14292015-12-04 11:30:46 -0800356
357 @Override
358 public String toString() {
359 return toStringHelper(getClass())
360 .add("igmpType", Byte.toString(igmpType))
361 .add("resField", Byte.toString(resField))
362 .add("checksum", Short.toString(checksum))
363 .add("unsupportTypeData", Arrays.toString(unsupportTypeData))
364 .toString();
365 // TODO: need to handle groups
366 }
Luca Pretec8ec4b52016-09-22 16:40:13 -0700367
368 public static class IGMPv3 extends IGMP {
369 public static final int MINIMUM_HEADER_LEN = 12;
370
371 @Override
372 public void setMaxRespCode(byte respCode) {
373 if (igmpType != IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY) {
374 log.debug("Requesting the max response code for an incorrect field: ");
375 }
376 this.resField = respCode;
377 }
378
379 @Override
380 public boolean addGroup(IGMPGroup group) {
381 checkNotNull(group);
382 switch (this.igmpType) {
383 case TYPE_IGMPV3_MEMBERSHIP_QUERY:
384 if (group instanceof IGMPMembership) {
385 return false;
386 }
387
388 if (group.sources.size() > 1) {
389 return false;
390 }
391 break;
392
393 case TYPE_IGMPV3_MEMBERSHIP_REPORT:
ke hane9082712016-10-18 13:19:05 +0800394 if (group instanceof IGMPQuery) {
Luca Pretec8ec4b52016-09-22 16:40:13 -0700395 return false;
396 }
397 break;
398
399 default:
400 log.debug("Warning no IGMP message type has been set");
401 }
402
403 this.groups.add(group);
404 return true;
405 }
406
407 @Override
408 protected boolean validChecksum() {
409 return true; //FIXME
410 }
411 }
412
413 public static class IGMPv2 extends IGMP {
414 public static final int HEADER_LENGTH = 8;
415
416 @Override
417 public void setMaxRespCode(byte respCode) {
418 this.resField = respCode;
419 }
420
421 @Override
422 public boolean addGroup(IGMPGroup group) {
423 if (groups.isEmpty()) {
424 groups = ImmutableList.of(group);
425 return true;
426 }
427 return false;
428 }
429
430 @Override
431 protected boolean validChecksum() {
432 int accumulation = (((int) this.igmpType) & 0xff) << 8;
433 accumulation += ((int) this.resField) & 0xff;
434 if (!groups.isEmpty()) {
435 int ipaddr = groups.get(0).getGaddr().getIp4Address().toInt();
436 accumulation += (ipaddr >> 16) & 0xffff;
437 accumulation += ipaddr & 0xffff;
438 }
439 accumulation = (accumulation >> 16 & 0xffff)
440 + (accumulation & 0xffff);
441 short checksum = (short) (~accumulation & 0xffff);
442 return checksum == this.checksum;
443 }
444 }
Rusty Eddy1da61a22015-09-01 00:48:58 +0000445}