blob: 7244b02ace7225733c21b60b7c009fb82f2aacba [file] [log] [blame]
Rusty Eddy1da61a22015-09-01 00:48:58 +00001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
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
Jonathan Hart81d73102016-02-19 10:32:05 -080018import org.slf4j.Logger;
19
Rusty Eddy1da61a22015-09-01 00:48:58 +000020import java.nio.ByteBuffer;
21import java.util.ArrayList;
Ray Milkey9f87e512016-01-05 10:00:22 -080022import java.util.Arrays;
Rusty Eddy1da61a22015-09-01 00:48:58 +000023import java.util.HashMap;
24import java.util.List;
25import java.util.Map;
Ray Milkey9f87e512016-01-05 10:00:22 -080026
Jian Li5fc14292015-12-04 11:30:46 -080027import static com.google.common.base.MoreObjects.toStringHelper;
Rusty Eddy1da61a22015-09-01 00:48:58 +000028import static com.google.common.base.Preconditions.checkNotNull;
29import static org.onlab.packet.PacketUtils.checkInput;
Jian Li5fc14292015-12-04 11:30:46 -080030import static org.slf4j.LoggerFactory.getLogger;
Rusty Eddy1da61a22015-09-01 00:48:58 +000031
Rusty Eddy1da61a22015-09-01 00:48:58 +000032/**
33 * Implements IGMP control packet format.
34 */
35public class IGMP extends BasePacket {
Jonathan Hart81d73102016-02-19 10:32:05 -080036 private static final Logger log = getLogger(IGMP.class);
Rusty Eddy1da61a22015-09-01 00:48:58 +000037
38 public static final byte TYPE_IGMPV3_MEMBERSHIP_QUERY = 0x11;
39 public static final byte TYPE_IGMPV1_MEMBERSHIP_REPORT = 0x12;
40 public static final byte TYPE_IGMPV2_MEMBERSHIP_REPORT = 0x16;
41 public static final byte TYPE_IGMPV2_LEAVE_GROUP = 0x17;
42 public static final byte TYPE_IGMPV3_MEMBERSHIP_REPORT = 0x22;
43 public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP = new HashMap<>();
44
45 public static final int MINIMUM_HEADER_LEN = 12;
46
47 List<IGMPGroup> groups = new ArrayList<>();
48
49 // Fields contained in the IGMP header
50 private byte igmpType;
51 private byte resField = 0;
52 private short checksum = 0;
53
54 private byte[] unsupportTypeData;
55
56 public IGMP() {
57 }
58
59 /**
60 * Get the IGMP message type.
61 *
62 * @return the IGMP message type
63 */
64 public byte getIgmpType() {
65 return igmpType;
66 }
67
68 /**
69 * Set the IGMP message type.
70 *
71 * @param msgType IGMP message type
72 */
73 public void setIgmpType(byte msgType) {
74 igmpType = msgType;
75 }
76
77 /**
78 * Get the checksum of this message.
79 *
80 * @return the checksum
81 */
82 public short getChecksum() {
83 return checksum;
84 }
85
86 /**
87 * get the Max Resp Code.
88 *
89 * @return The Maximum Time allowed before before sending a responding report.
90 */
91 public byte getMaxRespField() {
92 return resField;
93 }
94
95 /**
96 * Set the Max Resp Code.
97 *
98 * @param respCode the Maximum Response Code.
99 */
100 public void setMaxRespCode(byte respCode) {
101 if (igmpType != IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY) {
102 log.debug("Requesting the max response code for an incorrect field: ");
103 }
104 this.resField = respCode;
105 }
106
107 /**
108 * Get the list of IGMPGroups. The group objects will be either IGMPQuery or IGMPMembership
109 * depending on the IGMP message type. For IGMP Query, the groups list should only be
110 * one group.
111 *
112 * @return The list of IGMP groups.
113 */
114 public List<IGMPGroup> getGroups() {
115 return groups;
116 }
117
118 /**
119 * Add a multicast group to this IGMP message.
120 *
121 * @param group the IGMPGroup will be IGMPQuery or IGMPMembership depending on the message type.
122 * @return true if group was valid and added, false otherwise.
123 */
124 public boolean addGroup(IGMPGroup group) {
125 checkNotNull(group);
126 switch (this.igmpType) {
127 case TYPE_IGMPV3_MEMBERSHIP_QUERY:
128 if (group instanceof IGMPMembership) {
129 return false;
130 }
131
132 if (group.sources.size() > 1) {
133 return false;
134 }
135 break;
136
137 case TYPE_IGMPV3_MEMBERSHIP_REPORT:
138 if (group instanceof IGMPMembership) {
139 return false;
140 }
141 break;
142
143 default:
144 log.debug("Warning no IGMP message type has been set");
145 }
146
147 this.groups.add(group);
148 return true;
149 }
150
151 /**
152 * Serialize this IGMP packet. This will take care
153 * of serializing IGMPv3 Queries and IGMPv3 Membership
154 * Reports.
155 *
156 * @return the serialized IGMP message
157 */
Ray Milkeyaef45852016-01-11 17:13:19 -0800158 @java.lang.SuppressWarnings("squid:S128") // suppress switch fall through warning
Rusty Eddy1da61a22015-09-01 00:48:58 +0000159 @Override
160 public byte[] serialize() {
Jian Li68c4fc42016-01-11 16:07:03 -0800161 byte[] data = new byte[8915];
Rusty Eddy1da61a22015-09-01 00:48:58 +0000162
163 ByteBuffer bb = ByteBuffer.wrap(data);
164 bb.put(this.getIgmpType());
165
166 // reserved or max resp code depending on type.
167 bb.put(this.resField);
168
169 // Must calculate checksum
170 bb.putShort((short) 0);
171
Jonathan Hart81d73102016-02-19 10:32:05 -0800172
173
Rusty Eddy1da61a22015-09-01 00:48:58 +0000174 switch (this.igmpType) {
175
176 case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
177 // reserved
178 bb.putShort((short) 0);
179 // Number of groups
180 bb.putShort((short) groups.size());
181 // Fall through
182
183 case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
184
185 for (IGMPGroup grp : groups) {
186 grp.serialize(bb);
187 }
188 break;
189
190 default:
191 bb.put(this.unsupportTypeData);
192 break;
193 }
194
195 int size = bb.position();
Jonathan Hart81d73102016-02-19 10:32:05 -0800196
197 // compute checksum if needed
198 if (this.checksum == 0) {
199 bb.rewind();
200 int accumulation = 0;
201 for (int i = 0; i < size * 2; ++i) {
202 accumulation += 0xffff & bb.getShort();
203 }
204 accumulation = (accumulation >> 16 & 0xffff)
205 + (accumulation & 0xffff);
206 this.checksum = (short) (~accumulation & 0xffff);
207 bb.putShort(2, this.checksum);
208 }
209
210
Rusty Eddy1da61a22015-09-01 00:48:58 +0000211 bb.position(0);
Jian Li68c4fc42016-01-11 16:07:03 -0800212 byte[] rdata = new byte[size];
Rusty Eddy1da61a22015-09-01 00:48:58 +0000213 bb.get(rdata, 0, size);
214 return rdata;
215 }
216
217 /**
218 * Deserialize an IGMP message.
219 *
220 * @param data bytes to deserialize
221 * @param offset offset to start deserializing from
222 * @param length length of the data to deserialize
223 * @return populated IGMP object
224 */
225 @Override
226 public IPacket deserialize(final byte[] data, final int offset,
227 final int length) {
228
229 IGMP igmp = new IGMP();
230 try {
231 igmp = IGMP.deserializer().deserialize(data, offset, length);
232 } catch (DeserializationException e) {
233 log.error(e.getStackTrace().toString());
234 return this;
235 }
236 this.igmpType = igmp.igmpType;
237 this.resField = igmp.resField;
238 this.checksum = igmp.checksum;
239 this.groups = igmp.groups;
240 return this;
241 }
242
243 /**
244 * Deserializer function for IPv4 packets.
245 *
246 * @return deserializer function
247 */
248 public static Deserializer<IGMP> deserializer() {
249 return (data, offset, length) -> {
250 checkInput(data, offset, length, MINIMUM_HEADER_LEN);
251
252 IGMP igmp = new IGMP();
253
Rusty Eddy158d5d82015-10-12 16:59:04 -0700254 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
Rusty Eddy1da61a22015-09-01 00:48:58 +0000255 igmp.igmpType = bb.get();
256 igmp.resField = bb.get();
257 igmp.checksum = bb.getShort();
Jonathan Hart81d73102016-02-19 10:32:05 -0800258
Rusty Eddy1da61a22015-09-01 00:48:58 +0000259 String msg;
260
261 switch (igmp.igmpType) {
262
263 case TYPE_IGMPV3_MEMBERSHIP_QUERY:
264 IGMPQuery qgroup = new IGMPQuery();
265 qgroup.deserialize(bb);
266 igmp.groups.add(qgroup);
267 break;
268
269 case TYPE_IGMPV3_MEMBERSHIP_REPORT:
270 bb.getShort(); // Ignore resvd
271 int ngrps = bb.getShort();
272
273 for (; ngrps > 0; ngrps--) {
274 IGMPMembership mgroup = new IGMPMembership();
275 mgroup.deserialize(bb);
276 igmp.groups.add(mgroup);
277 }
278 break;
279
280 /*
281 * NOTE: according to the IGMPv3 spec. These previous IGMP type fields
282 * must be supported. At this time we are going to <b>assume</b> we run
283 * in a modern network where all devices are IGMPv3 capable.
284 */
285 case TYPE_IGMPV1_MEMBERSHIP_REPORT:
286 case TYPE_IGMPV2_MEMBERSHIP_REPORT:
287 case TYPE_IGMPV2_LEAVE_GROUP:
288 igmp.unsupportTypeData = bb.array(); // Is this the entire array?
289 msg = "IGMP message type: " + igmp.igmpType + " is not supported";
290 igmp.log.debug(msg);
291 break;
292
293 default:
alshabib0588e572015-09-04 12:00:42 -0700294 msg = "IGMP message type: " + igmp.igmpType + " is not recognized";
295 igmp.unsupportTypeData = bb.array();
Rusty Eddy1da61a22015-09-01 00:48:58 +0000296 igmp.log.debug(msg);
297 break;
298 }
299 return igmp;
300 };
301 }
302
303 /*
304 * (non-Javadoc)
305 *
306 * @see java.lang.Object#equals(java.lang.Object)
307 */
308 @Override
309 public boolean equals(final Object obj) {
310 if (this == obj) {
311 return true;
312 }
313 if (!super.equals(obj)) {
314 return false;
315 }
316 if (!(obj instanceof IGMP)) {
317 return false;
318 }
319 final IGMP other = (IGMP) obj;
320 if (this.igmpType != other.igmpType) {
321 return false;
322 }
323 if (this.resField != other.resField) {
324 return false;
325 }
326 if (this.checksum != other.checksum) {
327 return false;
328 }
329 if (this.groups.size() != other.groups.size()) {
330 return false;
331 }
332 // TODO: equals should be true regardless of order.
333 if (!groups.equals(other.groups)) {
334 return false;
335 }
336 return true;
337 }
338
339 /*
340 * (non-Javadoc)
341 *
342 * @see java.lang.Object#hashCode()
343 */
344 @Override
345 public int hashCode() {
346 final int prime = 2521;
347 int result = super.hashCode();
348 result = prime * result + this.igmpType;
349 result = prime * result + this.groups.size();
350 result = prime * result + this.resField;
351 result = prime * result + this.checksum;
352 result = prime * result + this.groups.hashCode();
353 return result;
354 }
Jian Li5fc14292015-12-04 11:30:46 -0800355
356 @Override
357 public String toString() {
358 return toStringHelper(getClass())
359 .add("igmpType", Byte.toString(igmpType))
360 .add("resField", Byte.toString(resField))
361 .add("checksum", Short.toString(checksum))
362 .add("unsupportTypeData", Arrays.toString(unsupportTypeData))
363 .toString();
364 // TODO: need to handle groups
365 }
Rusty Eddy1da61a22015-09-01 00:48:58 +0000366}