xref: /haiku/src/add-ons/kernel/network/ppp/pppoe/PPPoEDevice.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
1 /*
2  * Copyright 2003-2006, Waldemar Kornewald <wkornew@gmx.net>
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include "PPPoEDevice.h"
7 #include "DiscoveryPacket.h"
8 
9 #include <core_funcs.h>
10 #include <cstdlib>
11 
12 // from libkernelppp
13 #include <settings_tools.h>
14 
15 
16 #if DEBUG
17 static char sDigits[] = "0123456789ABCDEF";
18 void
19 dump_packet(struct mbuf *packet)
20 {
21 	if(!packet)
22 		return;
23 
24 	uint8 *data = mtod(packet, uint8*);
25 	uint8 buffer[33];
26 	uint8 bufferIndex = 0;
27 
28 	TRACE("Dumping packet;len=%ld;pkthdr.len=%d\n", packet->m_len,
29 		packet->m_flags & M_PKTHDR ? packet->m_pkthdr.len : -1);
30 
31 	for(uint32 index = 0; index < packet->m_len; index++) {
32 		buffer[bufferIndex++] = sDigits[data[index] >> 4];
33 		buffer[bufferIndex++] = sDigits[data[index] & 0x0F];
34 		if(bufferIndex == 32 || index == packet->m_len - 1) {
35 			buffer[bufferIndex] = 0;
36 			TRACE("%s\n", buffer);
37 			bufferIndex = 0;
38 		}
39 	}
40 }
41 #endif
42 
43 
44 PPPoEDevice::PPPoEDevice(KPPPInterface& interface, driver_parameter *settings)
45 	: KPPPDevice("PPPoE", PPPoE_HEADER_SIZE + ETHER_HDR_LEN, interface, settings),
46 	fEthernetIfnet(NULL),
47 	fSessionID(0),
48 	fHostUniq(NewHostUniq()),
49 	fACName(NULL),
50 	fServiceName(NULL),
51 	fAttempts(0),
52 	fNextTimeout(0),
53 	fState(INITIAL)
54 {
55 #if DEBUG
56 	TRACE("PPPoEDevice: Constructor\n");
57 	if(!settings || !settings->parameters)
58 		TRACE("PPPoEDevice::ctor: No settings!\n");
59 #endif
60 
61 	interface.SetPFCOptions(PPP_ALLOW_PFC);
62 		// we do not want to fail just because the other side requests PFC
63 
64 	memset(fPeer, 0xFF, sizeof(fPeer));
65 	SetMTU(1494);
66 		// MTU size does not contain PPP header
67 
68 	// find ethernet device
69 	const char *interfaceName = get_parameter_value(PPPoE_INTERFACE_KEY, settings);
70 	if(!interfaceName)
71 		return;
72 
73 	fACName = get_parameter_value(PPPoE_AC_NAME_KEY, settings);
74 	fServiceName = get_parameter_value(PPPoE_SERVICE_NAME_KEY, settings);
75 
76 	fEthernetIfnet = FindPPPoEInterface(interfaceName);
77 
78 #if DEBUG
79 	if(!fEthernetIfnet)
80 		TRACE("PPPoEDevice::ctor: could not find ethernet interface\n");
81 #endif
82 }
83 
84 
85 status_t
86 PPPoEDevice::InitCheck() const
87 {
88 	return EthernetIfnet() && EthernetIfnet()->output
89 		&& KPPPDevice::InitCheck() == B_OK ? B_OK : B_ERROR;
90 }
91 
92 
93 bool
94 PPPoEDevice::Up()
95 {
96 	TRACE("PPPoEDevice: Up()\n");
97 
98 	if(InitCheck() != B_OK)
99 		return false;
100 
101 	if(IsUp())
102 		return true;
103 
104 	add_device(this);
105 
106 	fState = INITIAL;
107 		// reset state
108 
109 	if(fAttempts > PPPoE_MAX_ATTEMPTS) {
110 		fAttempts = 0;
111 		return false;
112 	}
113 
114 	++fAttempts;
115 	// reset connection settings
116 	memset(fPeer, 0xFF, sizeof(fPeer));
117 
118 	// create PADI
119 	DiscoveryPacket discovery(PADI);
120 	if(ServiceName())
121 		discovery.AddTag(SERVICE_NAME, ServiceName(), strlen(ServiceName()));
122 	else
123 		discovery.AddTag(SERVICE_NAME, NULL, 0);
124 	discovery.AddTag(HOST_UNIQ, &fHostUniq, sizeof(fHostUniq));
125 	discovery.AddTag(END_OF_LIST, NULL, 0);
126 
127 	// set up PPP header
128 	struct mbuf *packet = discovery.ToMbuf(MTU());
129 	if(!packet)
130 		return false;
131 
132 	// create destination
133 	struct ether_header *ethernetHeader;
134 	struct sockaddr destination;
135 	memset(&destination, 0, sizeof(destination));
136 	destination.sa_family = AF_UNSPEC;
137 		// raw packet with ethernet header
138 	ethernetHeader = (struct ether_header*) destination.sa_data;
139 	ethernetHeader->ether_type = ETHERTYPE_PPPOEDISC;
140 	memcpy(ethernetHeader->ether_dhost, fPeer, sizeof(fPeer));
141 
142 	// check if we are allowed to go up now (user intervention might disallow that)
143 	if(fAttempts > 0 && !UpStarted()) {
144 		fAttempts = 0;
145 		remove_device(this);
146 		DownEvent();
147 		return true;
148 			// there was no error
149 	}
150 
151 	fState = PADI_SENT;
152 		// needed before sending, otherwise we might not get all packets
153 
154 	if(EthernetIfnet()->output(EthernetIfnet(), packet, &destination, NULL) != B_OK) {
155 		fState = INITIAL;
156 		fAttempts = 0;
157 		ERROR("PPPoEDevice::Up(): EthernetIfnet()->output() failed!\n");
158 		return false;
159 	}
160 
161 	fNextTimeout = system_time() + PPPoE_TIMEOUT;
162 
163 	return true;
164 }
165 
166 
167 bool
168 PPPoEDevice::Down()
169 {
170 	TRACE("PPPoEDevice: Down()\n");
171 
172 	remove_device(this);
173 
174 	if(InitCheck() != B_OK)
175 		return false;
176 
177 	fState = INITIAL;
178 	fAttempts = 0;
179 	fNextTimeout = 0;
180 		// disable timeouts
181 
182 	if(!IsUp()) {
183 		DownEvent();
184 		return true;
185 	}
186 
187 	DownStarted();
188 		// this tells StateMachine that DownEvent() does not mean we lost connection
189 
190 	// create PADT
191 	DiscoveryPacket discovery(PADT, SessionID());
192 	discovery.AddTag(END_OF_LIST, NULL, 0);
193 
194 	struct mbuf *packet = discovery.ToMbuf(MTU());
195 	if(!packet) {
196 		ERROR("PPPoEDevice::Down(): ToMbuf() failed; MTU=%ld\n", MTU());
197 		DownEvent();
198 		return false;
199 	}
200 
201 	// create destination
202 	struct ether_header *ethernetHeader;
203 	struct sockaddr destination;
204 	memset(&destination, 0, sizeof(destination));
205 	destination.sa_family = AF_UNSPEC;
206 		// raw packet with ethernet header
207 	ethernetHeader = (struct ether_header*) destination.sa_data;
208 	ethernetHeader->ether_type = ETHERTYPE_PPPOEDISC;
209 	memcpy(ethernetHeader->ether_dhost, fPeer, sizeof(fPeer));
210 
211 	// reset connection settings
212 	memset(fPeer, 0xFF, sizeof(fPeer));
213 
214 	EthernetIfnet()->output(EthernetIfnet(), packet, &destination, NULL);
215 	DownEvent();
216 
217 	return true;
218 }
219 
220 
221 uint32
222 PPPoEDevice::InputTransferRate() const
223 {
224 	return 10000000;
225 }
226 
227 
228 uint32
229 PPPoEDevice::OutputTransferRate() const
230 {
231 	return 10000000;
232 }
233 
234 
235 uint32
236 PPPoEDevice::CountOutputBytes() const
237 {
238 	// TODO:
239 	// ?look through ethernet queue for outgoing pppoe packets coming from our device?
240 	return 0;
241 }
242 
243 
244 status_t
245 PPPoEDevice::Send(struct mbuf *packet, uint16 protocolNumber)
246 {
247 	// Send() is only for PPP packets. PPPoE packets are sent directly to ethernet.
248 
249 	TRACE("PPPoEDevice: Send()\n");
250 
251 	if(!packet)
252 		return B_ERROR;
253 	else if(InitCheck() != B_OK || protocolNumber != 0) {
254 		ERROR("PPPoEDevice::Send(): InitCheck() != B_OK!\n");
255 		m_freem(packet);
256 		return B_ERROR;
257 	}
258 
259 	if(!IsUp()) {
260 		ERROR("PPPoEDevice::Send(): no connection!\n");
261 		m_freem(packet);
262 		return PPP_NO_CONNECTION;
263 	}
264 
265 	uint16 length = packet->m_flags & M_PKTHDR ? packet->m_pkthdr.len : packet->m_len;
266 
267 	// encapsulate packet into pppoe header
268 	M_PREPEND(packet, PPPoE_HEADER_SIZE);
269 	pppoe_header *header = mtod(packet, pppoe_header*);
270 	header->version = PPPoE_VERSION;
271 	header->type = PPPoE_TYPE;
272 	header->code = 0x00;
273 	header->sessionID = SessionID();
274 	header->length = htons(length);
275 
276 	// create destination
277 	struct ether_header *ethernetHeader;
278 	struct sockaddr destination;
279 	memset(&destination, 0, sizeof(destination));
280 	destination.sa_family = AF_UNSPEC;
281 		// raw packet with ethernet header
282 	ethernetHeader = (struct ether_header*) destination.sa_data;
283 	ethernetHeader->ether_type = ETHERTYPE_PPPOE;
284 	memcpy(ethernetHeader->ether_dhost, fPeer, sizeof(fPeer));
285 
286 	if(!packet || !mtod(packet, pppoe_header*))
287 		ERROR("PPPoEDevice::Send(): packet is NULL!\n");
288 
289 	if(EthernetIfnet()->output(EthernetIfnet(), packet, &destination, NULL) != B_OK) {
290 		ERROR("PPPoEDevice::Send(): EthernetIfnet()->output() failed!\n");
291 		remove_device(this);
292 		DownEvent();
293 			// DownEvent() without DownStarted() indicates connection lost
294 		return PPP_NO_CONNECTION;
295 	}
296 
297 	return B_OK;
298 }
299 
300 
301 status_t
302 PPPoEDevice::Receive(struct mbuf *packet, uint16 protocolNumber)
303 {
304 	if(!packet)
305 		return B_ERROR;
306 	else if(InitCheck() != B_OK || IsDown()) {
307 		m_freem(packet);
308 		return B_ERROR;
309 	}
310 
311 	complete_pppoe_header *completeHeader = mtod(packet, complete_pppoe_header*);
312 	if(!completeHeader) {
313 		m_freem(packet);
314 		return B_ERROR;
315 	}
316 
317 	uint8 ethernetSource[6];
318 	memcpy(ethernetSource, completeHeader->ethernetHeader.ether_shost, sizeof(fPeer));
319 	status_t result = B_OK;
320 
321 	if(completeHeader->ethernetHeader.ether_type == ETHERTYPE_PPPOE) {
322 		m_adj(packet, ETHER_HDR_LEN);
323 		pppoe_header *header = mtod(packet, pppoe_header*);
324 
325 		if(!IsUp() || header->version != PPPoE_VERSION || header->type != PPPoE_TYPE
326 				|| header->code != 0x0 || header->sessionID != SessionID()) {
327 			m_freem(packet);
328 			return B_ERROR;
329 		}
330 
331 		m_adj(packet, PPPoE_HEADER_SIZE);
332 		return Interface().ReceiveFromDevice(packet);
333 	} else if(completeHeader->ethernetHeader.ether_type == ETHERTYPE_PPPOEDISC) {
334 		m_adj(packet, ETHER_HDR_LEN);
335 		pppoe_header *header = mtod(packet, pppoe_header*);
336 
337 		// we do not need to check HOST_UNIQ tag as this is done in pppoe.cpp
338 		if(header->version != PPPoE_VERSION || header->type != PPPoE_TYPE) {
339 			m_freem(packet);
340 			return B_ERROR;
341 		}
342 
343 		if(IsDown()) {
344 			m_freem(packet);
345 			return B_ERROR;
346 		}
347 
348 		DiscoveryPacket discovery(packet);
349 		switch(discovery.Code()) {
350 			case PADO: {
351 				if(fState != PADI_SENT) {
352 					m_freem(packet);
353 					return B_OK;
354 				}
355 
356 				bool hasServiceName = false, hasACName = false;
357 				pppoe_tag *tag;
358 				DiscoveryPacket reply(PADR);
359 				for(int32 index = 0; index < discovery.CountTags(); index++) {
360 					tag = discovery.TagAt(index);
361 					if(!tag)
362 						continue;
363 
364 					switch(tag->type) {
365 						case SERVICE_NAME:
366 							if(!hasServiceName && (!ServiceName()
367 									|| (strlen(ServiceName()) == tag->length)
368 										&& !memcmp(tag->data, ServiceName(),
369 											tag->length))) {
370 								hasServiceName = true;
371 								reply.AddTag(tag->type, tag->data, tag->length);
372 							}
373 						break;
374 
375 						case AC_NAME:
376 							if(!hasACName && (!ACName()
377 									|| (strlen(ACName()) == tag->length)
378 										&& !memcmp(tag->data, ACName(),
379 											tag->length))) {
380 								hasACName = true;
381 								reply.AddTag(tag->type, tag->data, tag->length);
382 							}
383 						break;
384 
385 						case AC_COOKIE:
386 						case RELAY_SESSION_ID:
387 							reply.AddTag(tag->type, tag->data, tag->length);
388 						break;
389 
390 						case SERVICE_NAME_ERROR:
391 						case AC_SYSTEM_ERROR:
392 						case GENERIC_ERROR:
393 							m_freem(packet);
394 							return B_ERROR;
395 						break;
396 
397 						default:
398 							;
399 					}
400 				}
401 
402 				if(!hasServiceName) {
403 					m_freem(packet);
404 					return B_ERROR;
405 				}
406 
407 				reply.AddTag(HOST_UNIQ, &fHostUniq, sizeof(fHostUniq));
408 				reply.AddTag(END_OF_LIST, NULL, 0);
409 				struct mbuf *replyPacket = reply.ToMbuf(MTU());
410 				if(!replyPacket) {
411 					m_freem(packet);
412 					return B_ERROR;
413 				}
414 
415 				memcpy(fPeer, ethernetSource, sizeof(fPeer));
416 
417 				// create destination
418 				struct ether_header *ethernetHeader;
419 				struct sockaddr destination;
420 				memset(&destination, 0, sizeof(destination));
421 				destination.sa_family = AF_UNSPEC;
422 					// raw packet with ethernet header
423 				ethernetHeader = (struct ether_header*) destination.sa_data;
424 				ethernetHeader->ether_type = ETHERTYPE_PPPOEDISC;
425 				memcpy(ethernetHeader->ether_dhost, fPeer, sizeof(fPeer));
426 
427 				fState = PADR_SENT;
428 
429 				if(EthernetIfnet()->output(EthernetIfnet(), replyPacket, &destination,
430 						NULL) != B_OK) {
431 					m_freem(packet);
432 					return B_ERROR;
433 				}
434 
435 				fNextTimeout = system_time() + PPPoE_TIMEOUT;
436 			} break;
437 
438 			case PADS:
439 				if(fState != PADR_SENT
440 						|| memcmp(ethernetSource, fPeer, sizeof(fPeer))) {
441 					m_freem(packet);
442 					return B_ERROR;
443 				}
444 
445 				fSessionID = header->sessionID;
446 				fState = OPENED;
447 				fNextTimeout = 0;
448 				UpEvent();
449 			break;
450 
451 			case PADT:
452 				if(!IsUp()
453 						|| memcmp(ethernetSource, fPeer, sizeof(fPeer))
454 						|| header->sessionID != SessionID()) {
455 					m_freem(packet);
456 					return B_ERROR;
457 				}
458 
459 				fState = INITIAL;
460 				fAttempts = 0;
461 				fSessionID = 0;
462 				fNextTimeout = 0;
463 				remove_device(this);
464 				DownEvent();
465 			break;
466 
467 			default:
468 				m_freem(packet);
469 				return B_ERROR;
470 		}
471 	} else
472 		result = B_ERROR;
473 
474 	m_freem(packet);
475 	return result;
476 }
477 
478 
479 void
480 PPPoEDevice::Pulse()
481 {
482 	// We use Pulse() for timeout of connection establishment.
483 
484 	if(fNextTimeout == 0 || IsUp() || IsDown())
485 		return;
486 
487 	// check if timed out
488 	if(system_time() >= fNextTimeout) {
489 		if(!Up())
490 			UpFailedEvent();
491 	}
492 }
493