]> git.ozlabs.org Git - ppp.git/blob - pppd/plugins/rp-pppoe/discovery.c
Updates and fixes for the rp-pppoe plugin
[ppp.git] / pppd / plugins / rp-pppoe / discovery.c
1 /***********************************************************************
2 *
3 * discovery.c
4 *
5 * Perform PPPoE discovery
6 *
7 * Copyright (C) 1999 by Roaring Penguin Software Inc.
8 *
9 ***********************************************************************/
10
11 static char const RCSID[] =
12 "$Id: discovery.c,v 1.5 2008/06/09 08:34:23 paulus Exp $";
13
14 #define _GNU_SOURCE 1
15 #include "pppoe.h"
16 #include "pppd/pppd.h"
17
18 #include <string.h>
19 #include <stdlib.h>
20 #include <errno.h>
21
22 #ifdef HAVE_SYS_TIME_H
23 #include <sys/time.h>
24 #endif
25
26 #ifdef HAVE_SYS_UIO_H
27 #include <sys/uio.h>
28 #endif
29
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33
34 #ifdef USE_LINUX_PACKET
35 #include <sys/ioctl.h>
36 #include <fcntl.h>
37 #endif
38
39 #include <signal.h>
40
41 /**********************************************************************
42 *%FUNCTION: parseForHostUniq
43 *%ARGUMENTS:
44 * type -- tag type
45 * len -- tag length
46 * data -- tag data.
47 * extra -- user-supplied pointer.  This is assumed to be a pointer to int.
48 *%RETURNS:
49 * Nothing
50 *%DESCRIPTION:
51 * If a HostUnique tag is found which matches our PID, sets *extra to 1.
52 ***********************************************************************/
53 static void
54 parseForHostUniq(UINT16_t type, UINT16_t len, unsigned char *data,
55                  void *extra)
56 {
57     int *val = (int *) extra;
58     if (type == TAG_HOST_UNIQ && len == sizeof(pid_t)) {
59         pid_t tmp;
60         memcpy(&tmp, data, len);
61         if (tmp == getpid()) {
62             *val = 1;
63         }
64     }
65 }
66
67 /**********************************************************************
68 *%FUNCTION: packetIsForMe
69 *%ARGUMENTS:
70 * conn -- PPPoE connection info
71 * packet -- a received PPPoE packet
72 *%RETURNS:
73 * 1 if packet is for this PPPoE daemon; 0 otherwise.
74 *%DESCRIPTION:
75 * If we are using the Host-Unique tag, verifies that packet contains
76 * our unique identifier.
77 ***********************************************************************/
78 static int
79 packetIsForMe(PPPoEConnection *conn, PPPoEPacket *packet)
80 {
81     int forMe = 0;
82
83     /* If packet is not directed to our MAC address, forget it */
84     if (memcmp(packet->ethHdr.h_dest, conn->myEth, ETH_ALEN)) return 0;
85
86     /* If we're not using the Host-Unique tag, then accept the packet */
87     if (!conn->useHostUniq) return 1;
88
89     parsePacket(packet, parseForHostUniq, &forMe);
90     return forMe;
91 }
92
93 /**********************************************************************
94 *%FUNCTION: parsePADOTags
95 *%ARGUMENTS:
96 * type -- tag type
97 * len -- tag length
98 * data -- tag data
99 * extra -- extra user data.  Should point to a PacketCriteria structure
100 *          which gets filled in according to selected AC name and service
101 *          name.
102 *%RETURNS:
103 * Nothing
104 *%DESCRIPTION:
105 * Picks interesting tags out of a PADO packet
106 ***********************************************************************/
107 static void
108 parsePADOTags(UINT16_t type, UINT16_t len, unsigned char *data,
109               void *extra)
110 {
111     struct PacketCriteria *pc = (struct PacketCriteria *) extra;
112     PPPoEConnection *conn = pc->conn;
113     int i;
114
115     switch(type) {
116     case TAG_AC_NAME:
117         pc->seenACName = 1;
118         if (conn->printACNames) {
119             info("Access-Concentrator: %.*s", (int) len, data);
120         }
121         if (conn->acName && len == strlen(conn->acName) &&
122             !strncmp((char *) data, conn->acName, len)) {
123             pc->acNameOK = 1;
124         }
125         break;
126     case TAG_SERVICE_NAME:
127         pc->seenServiceName = 1;
128         if (conn->serviceName && len == strlen(conn->serviceName) &&
129             !strncmp((char *) data, conn->serviceName, len)) {
130             pc->serviceNameOK = 1;
131         }
132         break;
133     case TAG_AC_COOKIE:
134         conn->cookie.type = htons(type);
135         conn->cookie.length = htons(len);
136         memcpy(conn->cookie.payload, data, len);
137         break;
138     case TAG_RELAY_SESSION_ID:
139         conn->relayId.type = htons(type);
140         conn->relayId.length = htons(len);
141         memcpy(conn->relayId.payload, data, len);
142         break;
143     case TAG_SERVICE_NAME_ERROR:
144         error("PADO: Service-Name-Error: %.*s", (int) len, data);
145         conn->error = 1;
146         break;
147     case TAG_AC_SYSTEM_ERROR:
148         error("PADO: System-Error: %.*s", (int) len, data);
149         conn->error = 1;
150         break;
151     case TAG_GENERIC_ERROR:
152         error("PADO: Generic-Error: %.*s", (int) len, data);
153         conn->error = 1;
154         break;
155     }
156 }
157
158 /**********************************************************************
159 *%FUNCTION: parsePADSTags
160 *%ARGUMENTS:
161 * type -- tag type
162 * len -- tag length
163 * data -- tag data
164 * extra -- extra user data (pointer to PPPoEConnection structure)
165 *%RETURNS:
166 * Nothing
167 *%DESCRIPTION:
168 * Picks interesting tags out of a PADS packet
169 ***********************************************************************/
170 static void
171 parsePADSTags(UINT16_t type, UINT16_t len, unsigned char *data,
172               void *extra)
173 {
174     PPPoEConnection *conn = (PPPoEConnection *) extra;
175     switch(type) {
176     case TAG_SERVICE_NAME:
177         dbglog("PADS: Service-Name: '%.*s'", (int) len, data);
178         break;
179     case TAG_SERVICE_NAME_ERROR:
180         error("PADS: Service-Name-Error: %.*s", (int) len, data);
181         conn->error = 1;
182         break;
183     case TAG_AC_SYSTEM_ERROR:
184         error("PADS: System-Error: %.*s", (int) len, data);
185         conn->error = 1;
186         break;
187     case TAG_GENERIC_ERROR:
188         error("PADS: Generic-Error: %.*s", (int) len, data);
189         conn->error = 1;
190         break;
191     case TAG_RELAY_SESSION_ID:
192         conn->relayId.type = htons(type);
193         conn->relayId.length = htons(len);
194         memcpy(conn->relayId.payload, data, len);
195         break;
196     }
197 }
198
199 /***********************************************************************
200 *%FUNCTION: sendPADI
201 *%ARGUMENTS:
202 * conn -- PPPoEConnection structure
203 *%RETURNS:
204 * Nothing
205 *%DESCRIPTION:
206 * Sends a PADI packet
207 ***********************************************************************/
208 static void
209 sendPADI(PPPoEConnection *conn)
210 {
211     PPPoEPacket packet;
212     unsigned char *cursor = packet.payload;
213     PPPoETag *svc = (PPPoETag *) (&packet.payload);
214     UINT16_t namelen = 0;
215     UINT16_t plen;
216     int omit_service_name = 0;
217
218     if (conn->serviceName) {
219         namelen = (UINT16_t) strlen(conn->serviceName);
220         if (!strcmp(conn->serviceName, "NO-SERVICE-NAME-NON-RFC-COMPLIANT")) {
221             omit_service_name = 1;
222         }
223     }
224
225     /* Set destination to Ethernet broadcast address */
226     memset(packet.ethHdr.h_dest, 0xFF, ETH_ALEN);
227     memcpy(packet.ethHdr.h_source, conn->myEth, ETH_ALEN);
228
229     packet.ethHdr.h_proto = htons(Eth_PPPOE_Discovery);
230     packet.vertype = PPPOE_VER_TYPE(1, 1);
231     packet.code = CODE_PADI;
232     packet.session = 0;
233
234     if (!omit_service_name) {
235         plen = TAG_HDR_SIZE + namelen;
236         CHECK_ROOM(cursor, packet.payload, plen);
237
238         svc->type = TAG_SERVICE_NAME;
239         svc->length = htons(namelen);
240
241         if (conn->serviceName) {
242             memcpy(svc->payload, conn->serviceName, strlen(conn->serviceName));
243         }
244         cursor += namelen + TAG_HDR_SIZE;
245     } else {
246         plen = 0;
247     }
248
249     /* If we're using Host-Uniq, copy it over */
250     if (conn->useHostUniq) {
251         PPPoETag hostUniq;
252         pid_t pid = getpid();
253         hostUniq.type = htons(TAG_HOST_UNIQ);
254         hostUniq.length = htons(sizeof(pid));
255         memcpy(hostUniq.payload, &pid, sizeof(pid));
256         CHECK_ROOM(cursor, packet.payload, sizeof(pid) + TAG_HDR_SIZE);
257         memcpy(cursor, &hostUniq, sizeof(pid) + TAG_HDR_SIZE);
258         cursor += sizeof(pid) + TAG_HDR_SIZE;
259         plen += sizeof(pid) + TAG_HDR_SIZE;
260     }
261
262     packet.length = htons(plen);
263
264     sendPacket(conn, conn->discoverySocket, &packet, (int) (plen + HDR_SIZE));
265 }
266
267 /**********************************************************************
268 *%FUNCTION: waitForPADO
269 *%ARGUMENTS:
270 * conn -- PPPoEConnection structure
271 * timeout -- how long to wait (in seconds)
272 *%RETURNS:
273 * Nothing
274 *%DESCRIPTION:
275 * Waits for a PADO packet and copies useful information
276 ***********************************************************************/
277 static void
278 waitForPADO(PPPoEConnection *conn, int timeout)
279 {
280     fd_set readable;
281     int r;
282     struct timeval tv;
283     PPPoEPacket packet;
284     int len;
285
286     struct PacketCriteria pc;
287     pc.conn          = conn;
288     pc.acNameOK      = (conn->acName)      ? 0 : 1;
289     pc.serviceNameOK = (conn->serviceName) ? 0 : 1;
290     pc.seenACName    = 0;
291     pc.seenServiceName = 0;
292     conn->error = 0;
293
294     do {
295         if (BPF_BUFFER_IS_EMPTY) {
296             tv.tv_sec = timeout;
297             tv.tv_usec = 0;
298
299             FD_ZERO(&readable);
300             FD_SET(conn->discoverySocket, &readable);
301
302             while(1) {
303                 r = select(conn->discoverySocket+1, &readable, NULL, NULL, &tv);
304                 if (r >= 0 || errno != EINTR) break;
305             }
306             if (r < 0) {
307                 error("select (waitForPADO): %m");
308                 return;
309             }
310             if (r == 0) return;        /* Timed out */
311         }
312
313         /* Get the packet */
314         receivePacket(conn->discoverySocket, &packet, &len);
315
316         /* Check length */
317         if (ntohs(packet.length) + HDR_SIZE > len) {
318             error("Bogus PPPoE length field (%u)",
319                    (unsigned int) ntohs(packet.length));
320             continue;
321         }
322
323 #ifdef USE_BPF
324         /* If it's not a Discovery packet, loop again */
325         if (etherType(&packet) != Eth_PPPOE_Discovery) continue;
326 #endif
327
328         /* If it's not for us, loop again */
329         if (!packetIsForMe(conn, &packet)) continue;
330
331         if (packet.code == CODE_PADO) {
332             if (NOT_UNICAST(packet.ethHdr.h_source)) {
333                 error("Ignoring PADO packet from non-unicast MAC address");
334                 continue;
335             }
336             if (parsePacket(&packet, parsePADOTags, &pc) < 0)
337                 return;
338             if (conn->error)
339                 return;
340             if (!pc.seenACName) {
341                 error("Ignoring PADO packet with no AC-Name tag");
342                 continue;
343             }
344             if (!pc.seenServiceName) {
345                 error("Ignoring PADO packet with no Service-Name tag");
346                 continue;
347             }
348             conn->numPADOs++;
349             if (pc.acNameOK && pc.serviceNameOK) {
350                 memcpy(conn->peerEth, packet.ethHdr.h_source, ETH_ALEN);
351                 conn->discoveryState = STATE_RECEIVED_PADO;
352                 break;
353             }
354         }
355     } while (conn->discoveryState != STATE_RECEIVED_PADO);
356 }
357
358 /***********************************************************************
359 *%FUNCTION: sendPADR
360 *%ARGUMENTS:
361 * conn -- PPPoE connection structur
362 *%RETURNS:
363 * Nothing
364 *%DESCRIPTION:
365 * Sends a PADR packet
366 ***********************************************************************/
367 static void
368 sendPADR(PPPoEConnection *conn)
369 {
370     PPPoEPacket packet;
371     PPPoETag *svc = (PPPoETag *) packet.payload;
372     unsigned char *cursor = packet.payload;
373
374     UINT16_t namelen = 0;
375     UINT16_t plen;
376
377     if (conn->serviceName) {
378         namelen = (UINT16_t) strlen(conn->serviceName);
379     }
380     plen = TAG_HDR_SIZE + namelen;
381     CHECK_ROOM(cursor, packet.payload, plen);
382
383     memcpy(packet.ethHdr.h_dest, conn->peerEth, ETH_ALEN);
384     memcpy(packet.ethHdr.h_source, conn->myEth, ETH_ALEN);
385
386     packet.ethHdr.h_proto = htons(Eth_PPPOE_Discovery);
387     packet.vertype = PPPOE_VER_TYPE(1, 1);
388     packet.code = CODE_PADR;
389     packet.session = 0;
390
391     svc->type = TAG_SERVICE_NAME;
392     svc->length = htons(namelen);
393     if (conn->serviceName) {
394         memcpy(svc->payload, conn->serviceName, namelen);
395     }
396     cursor += namelen + TAG_HDR_SIZE;
397
398     /* If we're using Host-Uniq, copy it over */
399     if (conn->useHostUniq) {
400         PPPoETag hostUniq;
401         pid_t pid = getpid();
402         hostUniq.type = htons(TAG_HOST_UNIQ);
403         hostUniq.length = htons(sizeof(pid));
404         memcpy(hostUniq.payload, &pid, sizeof(pid));
405         CHECK_ROOM(cursor, packet.payload, sizeof(pid)+TAG_HDR_SIZE);
406         memcpy(cursor, &hostUniq, sizeof(pid) + TAG_HDR_SIZE);
407         cursor += sizeof(pid) + TAG_HDR_SIZE;
408         plen += sizeof(pid) + TAG_HDR_SIZE;
409     }
410
411     /* Copy cookie and relay-ID if needed */
412     if (conn->cookie.type) {
413         CHECK_ROOM(cursor, packet.payload,
414                    ntohs(conn->cookie.length) + TAG_HDR_SIZE);
415         memcpy(cursor, &conn->cookie, ntohs(conn->cookie.length) + TAG_HDR_SIZE);
416         cursor += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
417         plen += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
418     }
419
420     if (conn->relayId.type) {
421         CHECK_ROOM(cursor, packet.payload,
422                    ntohs(conn->relayId.length) + TAG_HDR_SIZE);
423         memcpy(cursor, &conn->relayId, ntohs(conn->relayId.length) + TAG_HDR_SIZE);
424         cursor += ntohs(conn->relayId.length) + TAG_HDR_SIZE;
425         plen += ntohs(conn->relayId.length) + TAG_HDR_SIZE;
426     }
427
428     packet.length = htons(plen);
429     sendPacket(conn, conn->discoverySocket, &packet, (int) (plen + HDR_SIZE));
430 }
431
432 /**********************************************************************
433 *%FUNCTION: waitForPADS
434 *%ARGUMENTS:
435 * conn -- PPPoE connection info
436 * timeout -- how long to wait (in seconds)
437 *%RETURNS:
438 * Nothing
439 *%DESCRIPTION:
440 * Waits for a PADS packet and copies useful information
441 ***********************************************************************/
442 static void
443 waitForPADS(PPPoEConnection *conn, int timeout)
444 {
445     fd_set readable;
446     int r;
447     struct timeval tv;
448     PPPoEPacket packet;
449     int len;
450
451     conn->error = 0;
452     do {
453         if (BPF_BUFFER_IS_EMPTY) {
454             tv.tv_sec = timeout;
455             tv.tv_usec = 0;
456
457             FD_ZERO(&readable);
458             FD_SET(conn->discoverySocket, &readable);
459
460             while(1) {
461                 r = select(conn->discoverySocket+1, &readable, NULL, NULL, &tv);
462                 if (r >= 0 || errno != EINTR) break;
463             }
464             if (r < 0) {
465                 error("select (waitForPADS): %m");
466                 return;
467             }
468             if (r == 0) return;
469         }
470
471         /* Get the packet */
472         receivePacket(conn->discoverySocket, &packet, &len);
473
474         /* Check length */
475         if (ntohs(packet.length) + HDR_SIZE > len) {
476             error("Bogus PPPoE length field (%u)",
477                    (unsigned int) ntohs(packet.length));
478             continue;
479         }
480
481 #ifdef USE_BPF
482         /* If it's not a Discovery packet, loop again */
483         if (etherType(&packet) != Eth_PPPOE_Discovery) continue;
484 #endif
485
486         /* If it's not from the AC, it's not for me */
487         if (memcmp(packet.ethHdr.h_source, conn->peerEth, ETH_ALEN)) continue;
488
489         /* If it's not for us, loop again */
490         if (!packetIsForMe(conn, &packet)) continue;
491
492         /* Is it PADS?  */
493         if (packet.code == CODE_PADS) {
494             /* Parse for goodies */
495             if (parsePacket(&packet, parsePADSTags, conn) < 0)
496                 return;
497             if (conn->error)
498                 return;
499             conn->discoveryState = STATE_SESSION;
500             break;
501         }
502     } while (conn->discoveryState != STATE_SESSION);
503
504     /* Don't bother with ntohs; we'll just end up converting it back... */
505     conn->session = packet.session;
506
507     info("PPP session is %d", (int) ntohs(conn->session));
508
509     /* RFC 2516 says session id MUST NOT be zero or 0xFFFF */
510     if (ntohs(conn->session) == 0 || ntohs(conn->session) == 0xFFFF) {
511         error("Access concentrator used a session value of %x -- the AC is violating RFC 2516", (unsigned int) ntohs(conn->session));
512     }
513 }
514
515 /**********************************************************************
516 *%FUNCTION: discovery
517 *%ARGUMENTS:
518 * conn -- PPPoE connection info structure
519 *%RETURNS:
520 * Nothing
521 *%DESCRIPTION:
522 * Performs the PPPoE discovery phase
523 ***********************************************************************/
524 void
525 discovery(PPPoEConnection *conn)
526 {
527     int padiAttempts = 0;
528     int padrAttempts = 0;
529     int timeout = conn->discoveryTimeout;
530
531     conn->discoverySocket =
532         openInterface(conn->ifName, Eth_PPPOE_Discovery, conn->myEth);
533
534     do {
535         padiAttempts++;
536         if (padiAttempts > MAX_PADI_ATTEMPTS) {
537             warn("Timeout waiting for PADO packets");
538             close(conn->discoverySocket);
539             conn->discoverySocket = -1;
540             return;
541         }
542         sendPADI(conn);
543         conn->discoveryState = STATE_SENT_PADI;
544         waitForPADO(conn, timeout);
545
546         timeout *= 2;
547     } while (conn->discoveryState == STATE_SENT_PADI);
548
549     timeout = conn->discoveryTimeout;
550     do {
551         padrAttempts++;
552         if (padrAttempts > MAX_PADI_ATTEMPTS) {
553             warn("Timeout waiting for PADS packets");
554             close(conn->discoverySocket);
555             conn->discoverySocket = -1;
556             return;
557         }
558         sendPADR(conn);
559         conn->discoveryState = STATE_SENT_PADR;
560         waitForPADS(conn, timeout);
561         timeout *= 2;
562     } while (conn->discoveryState == STATE_SENT_PADR);
563
564     /* We're done. */
565     conn->discoveryState = STATE_SESSION;
566     return;
567 }