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