blob: 86b46c98ee548ff244b5e6d3e1a02709a75ab5ba [file] [log] [blame]
Daniele Moro94660a02019-12-02 12:02:07 -08001/*
Joey Armstronga68e7852024-01-28 13:27:02 -05002 * Copyright 2019-2024 Open Networking Foundation (ONF) and the ONF Contributors
Daniele Moro94660a02019-12-02 12:02:07 -08003 *
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 */
16
17package org.opencord.bng.packets;
18
19import com.google.common.base.Objects;
20import com.google.common.collect.ImmutableMap;
21import org.onlab.packet.BasePacket;
22import org.onlab.packet.Data;
23import org.onlab.packet.Deserializer;
24import org.onlab.packet.Ethernet;
25import org.onlab.packet.IPacket;
26import org.onlab.packet.IPv4;
27import org.onlab.packet.IPv6;
28
29import java.nio.ByteBuffer;
30import java.util.LinkedList;
31import java.util.List;
32
33import static com.google.common.base.MoreObjects.toStringHelper;
34import static org.onlab.packet.PacketUtils.checkHeaderLength;
35import static org.onlab.packet.PacketUtils.checkInput;
36
37/**
38 * Implements PPPoE packet format.
39 */
40public class Pppoe extends BasePacket {
41
42 public static final short TYPE_PPPOED = (short) 0x8863;
43 public static final short TYPE_PPPOES = (short) 0x8864;
44
45 static final ImmutableMap<Short, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP =
46 ImmutableMap.<Short, Deserializer<? extends IPacket>>builder()
47 //FIXME: write the correct parser for LCP, PAP, and CHAP
48 .put(PppProtocolType.LCP.code(), GenericPpp.deserializer())
49 .put(PppProtocolType.PAP.code(), GenericPpp.deserializer())
50 .put(PppProtocolType.CHAP.code(), GenericPpp.deserializer())
51 .put(PppProtocolType.IPCP.code(), Ipcp.deserializer())
52 .put(PppProtocolType.IPv4.code(), IPv4.deserializer())
53 .put(PppProtocolType.IPv6.code(), IPv6.deserializer())
54 .build();
55 // Account of PPPoE standard header
56 static final int HEADER_LENGTH = 6;
57 // Fields part of PPPoE
58 private byte version; // 4bit
59 private byte typeId; // 4bit
60 private PppoeType packetType; // 8bit => code in PPPoE header
61 private short sessionId; // 16bit
62 private short payloadLength; // 16bit
63 // FIXME: use PPPProtocol enum type
64 private short pppProtocol = 0;
65 //TODO: TLV TAGs (ref. to RFC2516)
66 private PppoeTlvTag acName = null;
67 private PppoeTlvTag serviceName = null;
68 private List<PppoeTlvTag> optionalTagTlvList;
69
70 public Pppoe() {
71 super();
72 optionalTagTlvList = new LinkedList<>();
73 }
74
75 /**
76 * Get the packet type.
77 *
78 * @return the packet type
79 */
80 public PppoeType getPacketType() {
81 return packetType;
82 }
83
84 /**
85 * Set the Packet Type.
86 *
87 * @param type The packet type to set
88 * @return this
89 */
90 public Pppoe setPacketType(PppoeType type) {
91 this.packetType = type;
92 return this;
93 }
94
95 /**
96 * Get the session ID.
97 *
98 * @return the session ID
99 */
100 public short getSessionId() {
101 return this.sessionId;
102 }
103
104 /**
105 * Set the session ID.
106 *
107 * @param sessionId the session ID to set
108 * @return this
109 */
110 public Pppoe setSessionId(short sessionId) {
111 this.sessionId = sessionId;
112 return this;
113 }
114
115 /**
116 * Get the Point-to-Point Protocol.
117 *
118 * @return the Point-to-Point Protocol
119 */
120 public short getPppProtocol() {
121 return this.pppProtocol;
122 }
123
124 /**
125 * Get the AC-Name.
126 *
127 * @return the AC-Name
128 */
129 public PppoeTlvTag getAcName() {
130 return this.acName;
131 }
132
133 /**
134 * Set the AC-Name.
135 *
136 * @param acname AC-Name to set
137 * @return this
138 */
139 public Pppoe setAcName(final PppoeTlvTag acname) {
140 this.acName = acname;
141 return this;
142 }
143
144 /**
145 * Get the PPPoE version field.
146 *
147 * @return The version field
148 */
149 public byte getVersion() {
150 return this.version;
151 }
152
153 /**
154 * Set the version field.
155 *
156 * @param version version to set
157 * @return this
158 */
159 public Pppoe setVersion(byte version) {
160 this.version = (byte) (version & 0xF);
161 return this;
162 }
163
164 /**
165 * Get the PPPoE type ID.
166 *
167 * @return The type ID
168 */
169 public short getTypeId() {
170 return this.typeId;
171 }
172
173 /**
174 * Set the type ID.
175 *
176 * @param typeId Type ID to set
177 * @return this
178 */
179 public Pppoe setTypeId(byte typeId) {
180 this.typeId = (byte) (typeId & 0xF);
181 return this;
182 }
183
184 /**
185 * Get the PPPoE payload length header field.
186 *
187 * @return The payload length
188 */
189 public short getPayloadLength() {
190 return this.payloadLength;
191 }
192
193 /**
194 * Set the payload length.
195 *
196 * @param payloadLength the payload length to set.
197 * @return this
198 */
199 public Pppoe setPayloadLength(short payloadLength) {
200 this.payloadLength = payloadLength;
201 return this;
202 }
203
204 @Override
205 public byte[] serialize() {
206 byte[] payloadData = null;
207 int payloadLength = 0;
208 if (this.payload != null) {
209 this.payload.setParent(this);
210 payloadData = this.payload.serialize();
211 payloadLength = payloadData.length + HEADER_LENGTH +
212 (this.packetType == PppoeType.SESSION ? 2 : 0);
213 }
214 // PayloadLength account for PPP header field
215 int realLength = Math.max(this.payloadLength + HEADER_LENGTH, payloadLength);
216 final byte[] data = new byte[realLength];
217 final ByteBuffer bb = ByteBuffer.wrap(data);
218 bb.put((byte) (this.version << 4 | this.typeId & 0xf));
219 bb.put(this.packetType.code);
220 bb.putShort(this.sessionId);
221 bb.putShort(this.payloadLength);
222 if (this.packetType != PppoeType.SESSION) {
223 // Only session packet have PPP header
224 bb.putShort(this.pppProtocol);
225 } else {
226 // Only NON session packet have options
227 // TLV Tags
228 if (acName != null) {
229 bb.put(acName.serialize());
230 }
231 if (serviceName != null) {
232 bb.put(serviceName.serialize());
233 }
234 if (this.optionalTagTlvList != null) {
235 for (final PppoeTlvTag tlv : this.optionalTagTlvList) {
236 bb.put(tlv.serialize());
237 }
238 }
239 }
240 if (payloadData != null) {
241 bb.put(payloadData);
242 }
243 return data;
244 }
245
246 /**
247 * Deserializer function for PPPoE packets.
248 *
249 * @return deserializer function
250 */
251 public static Deserializer<Pppoe> deserializer() {
252 return (data, offset, length) -> {
253 checkInput(data, offset, length, HEADER_LENGTH);
254
255 Pppoe pppoe = new Pppoe();
256 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
257 byte versionAndType = bb.get();
258 pppoe.version = (byte) (versionAndType >> 4 & 0xF);
259 pppoe.typeId = (byte) (versionAndType & 0xF);
260 byte code = bb.get();
261 pppoe.sessionId = bb.getShort();
262 pppoe.payloadLength = bb.getShort();
263 pppoe.packetType = PppoeType.lookup(code);
264 // Check if the PPPoE packet is a SESSION packet
265 if (pppoe.packetType == PppoeType.SESSION) {
266 // Parse inner protocols
267 pppoe.pppProtocol = bb.getShort();
268 Deserializer<? extends IPacket> deserializer;
269 if (Pppoe.PROTOCOL_DESERIALIZER_MAP.containsKey(pppoe.pppProtocol)) {
270 deserializer = PROTOCOL_DESERIALIZER_MAP.get(pppoe.pppProtocol);
271 } else {
272 deserializer = Data.deserializer();
273 }
274 int remainingLength = bb.limit() - bb.position();
275 int bytesToRead = Math.min(pppoe.payloadLength - 2, remainingLength);
276 if (bytesToRead > 0) {
277 pppoe.payload = deserializer.deserialize(data, bb.position(), bytesToRead);
278 pppoe.payload.setParent(pppoe);
279 }
280 } else {
281 // PPPoE Packet is of Discovery type
282 // Parse TLV PPPoED Tags
283 int currentIndex = HEADER_LENGTH;
284 PppoeTlvTag tlv;
285 do {
286 // Each new TLV PPPoE TAG must be a minimum of 4 bytes
287 // (containing the type and length fields).
288 if (length - currentIndex < 4) {
289 // Probably the packet was zero-padded to reach the Ethernet minimum length.
290 // Let's skip and accept the packet
291 // FIXME: is there a "smarter" way to identify a padded packet?
292 break;
293 }
294 currentIndex += 4;
295 checkHeaderLength(length, currentIndex);
296 tlv = new PppoeTlvTag().deserialize(bb);
297 // if there was a failure to deserialize stop processing TLVs
298 if (tlv == null) {
299 break;
300 }
301 switch (tlv.getTagType()) {
302 case PppoeTlvTag.PPPOED_TAGTYPE_EOL:
303 // end delimiter
304 break;
305 case PppoeTlvTag.PPPOED_TAGTYPE_SERVICENAME:
306 // Service Name
307 pppoe.serviceName = tlv;
308 break;
309 case PppoeTlvTag.PPPOED_TAGTYPE_ACNAME:
310 // AC-Name
311 pppoe.acName = tlv;
312 break;
313 default:
314 pppoe.optionalTagTlvList.add(tlv);
315 break;
316 }
317 currentIndex += tlv.getLength();
318 } while (tlv.getTagType() != 0 && currentIndex < length);
319 }
320 return pppoe;
321 };
322 }
323
324 @Override
325 public String toString() {
326 return toStringHelper(getClass())
327 .add("version", Byte.toString(version))
328 .add("typeId", Byte.toString(typeId))
329 .add("code", Byte.toString(packetType.code))
330 .add("sessionId", Short.toString(sessionId))
331 .add("payloadLength", Short.toString(payloadLength))
332 .toString();
333 }
334
335 @Override
336 public int hashCode() {
337 return 31 * super.hashCode() +
338 Objects.hashCode(version, typeId, packetType,
339 sessionId, payloadLength, pppProtocol,
340 acName, serviceName, optionalTagTlvList);
341 }
342
343 @Override
344 public boolean equals(Object obj) {
345 if (this == obj) {
346 return true;
347 }
348 if (obj == null || getClass() != obj.getClass()) {
349 return false;
350 }
351 if (!super.equals(obj)) {
352 return false;
353 }
354 final Pppoe other = (Pppoe) obj;
355 return Objects.equal(this.version, other.version)
356 && Objects.equal(this.typeId, other.typeId)
357 && Objects.equal(this.packetType, other.packetType)
358 && Objects.equal(this.sessionId, other.sessionId)
359 && Objects.equal(this.payloadLength, other.payloadLength)
360 && Objects.equal(this.pppProtocol, other.pppProtocol)
361 && Objects.equal(this.acName, other.acName)
362 && Objects.equal(this.serviceName, other.serviceName)
363 && Objects.equal(this.optionalTagTlvList, other.optionalTagTlvList);
364 }
365
366 /**
367 * PPPoE Discovery types.
368 */
369 public enum PppoeType {
370 PADI(0x9, "padi"),
371 PADO(0x7, "pado"),
372 PADR(0x19, "padr"),
373 PADS(0x65, "pads"),
374 PADT(0xa7, "padt"),
375 SESSION(0x0, "session"),
376 UNKNOWN(0xFF, "unknown");
377
378 private final byte code;
379 private final String type;
380
381 /**
382 * Constructs new PPPoE Discovery types.
383 *
384 * @param code The PPPoED type.
385 * @param type Textual representation of the PPPoED type.
386 */
387 PppoeType(int code, String type) {
388 this.code = (byte) (code & 0xFF);
389 this.type = type;
390 }
391
392 public static PppoeType lookup(byte code) {
393 for (PppoeType type : PppoeType.values()) {
394 if (code == type.code()) {
395 return type;
396 }
397 }
398 return UNKNOWN;
399 }
400
401 public byte code() {
402 return code;
403 }
404
405 public String type() {
406 return type;
407 }
408 }
409
410 /**
411 * Checks if the passed Ethernet packet is PPPoE Service.
412 *
413 * @param eth Packet to check
414 * @return True if the packet contains PPPoE Service header
415 */
416 public static boolean isPPPoES(Ethernet eth) {
417 return eth.getEtherType() == TYPE_PPPOES;
418 }
419
420 /**
421 * Checks if the passed Ethernet packet is PPPoE Discovery.
422 *
423 * @param eth Packet to check
424 * @return True if the packet contains PPPoE Discovery header
425 */
426 public static boolean isPPPoED(Ethernet eth) {
427 return eth.getEtherType() == TYPE_PPPOED;
428 }
429}