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