]> git.ozlabs.org Git - ppp.git/blob - pppd/plugins/rp-pppoe/discovery.c
Add pppoe-mac option to 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.6 2008/06/15 04:35:50 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 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 (conn->req_peer
337                 && memcmp(packet.ethHdr.h_source, conn->req_peer_mac, ETH_ALEN) != 0) {
338                 warn("Ignoring PADO packet from wrong MAC address");
339                 continue;
340             }
341             if (parsePacket(&packet, parsePADOTags, &pc) < 0)
342                 return;
343             if (conn->error)
344                 return;
345             if (!pc.seenACName) {
346                 error("Ignoring PADO packet with no AC-Name tag");
347                 continue;
348             }
349             if (!pc.seenServiceName) {
350                 error("Ignoring PADO packet with no Service-Name tag");
351                 continue;
352             }
353             conn->numPADOs++;
354             if (pc.acNameOK && pc.serviceNameOK) {
355                 memcpy(conn->peerEth, packet.ethHdr.h_source, ETH_ALEN);
356                 conn->discoveryState = STATE_RECEIVED_PADO;
357                 break;
358             }
359         }
360     } while (conn->discoveryState != STATE_RECEIVED_PADO);
361 }
362
363 /***********************************************************************
364 *%FUNCTION: sendPADR
365 *%ARGUMENTS:
366 * conn -- PPPoE connection structur
367 *%RETURNS:
368 * Nothing
369 *%DESCRIPTION:
370 * Sends a PADR packet
371 ***********************************************************************/
372 static void
373 sendPADR(PPPoEConnection *conn)
374 {
375     PPPoEPacket packet;
376     PPPoETag *svc = (PPPoETag *) packet.payload;
377     unsigned char *cursor = packet.payload;
378
379     UINT16_t namelen = 0;
380     UINT16_t plen;
381
382     if (conn->serviceName) {
383         namelen = (UINT16_t) strlen(conn->serviceName);
384     }
385     plen = TAG_HDR_SIZE + namelen;
386     CHECK_ROOM(cursor, packet.payload, plen);
387
388     memcpy(packet.ethHdr.h_dest, conn->peerEth, ETH_ALEN);
389     memcpy(packet.ethHdr.h_source, conn->myEth, ETH_ALEN);
390
391     packet.ethHdr.h_proto = htons(Eth_PPPOE_Discovery);
392     packet.vertype = PPPOE_VER_TYPE(1, 1);
393     packet.code = CODE_PADR;
394     packet.session = 0;
395
396     svc->type = TAG_SERVICE_NAME;
397     svc->length = htons(namelen);
398     if (conn->serviceName) {
399         memcpy(svc->payload, conn->serviceName, namelen);
400     }
401     cursor += namelen + TAG_HDR_SIZE;
402
403     /* If we're using Host-Uniq, copy it over */
404     if (conn->useHostUniq) {
405         PPPoETag hostUniq;
406         pid_t pid = getpid();
407         hostUniq.type = htons(TAG_HOST_UNIQ);
408         hostUniq.length = htons(sizeof(pid));
409         memcpy(hostUniq.payload, &pid, sizeof(pid));
410         CHECK_ROOM(cursor, packet.payload, sizeof(pid)+TAG_HDR_SIZE);
411         memcpy(cursor, &hostUniq, sizeof(pid) + TAG_HDR_SIZE);
412         cursor += sizeof(pid) + TAG_HDR_SIZE;
413         plen += sizeof(pid) + TAG_HDR_SIZE;
414     }
415
416     /* Copy cookie and relay-ID if needed */
417     if (conn->cookie.type) {
418         CHECK_ROOM(cursor, packet.payload,
419                    ntohs(conn->cookie.length) + TAG_HDR_SIZE);
420         memcpy(cursor, &conn->cookie, ntohs(conn->cookie.length) + TAG_HDR_SIZE);
421         cursor += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
422         plen += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
423     }
424
425     if (conn->relayId.type) {
426         CHECK_ROOM(cursor, packet.payload,
427                    ntohs(conn->relayId.length) + TAG_HDR_SIZE);
428         memcpy(cursor, &conn->relayId, ntohs(conn->relayId.length) + TAG_HDR_SIZE);
429         cursor += ntohs(conn->relayId.length) + TAG_HDR_SIZE;
430         plen += ntohs(conn->relayId.length) + TAG_HDR_SIZE;
431     }
432
433     packet.length = htons(plen);
434     sendPacket(conn, conn->discoverySocket, &packet, (int) (plen + HDR_SIZE));
435 }
436
437 /**********************************************************************
438 *%FUNCTION: waitForPADS
439 *%ARGUMENTS:
440 * conn -- PPPoE connection info
441 * timeout -- how long to wait (in seconds)
442 *%RETURNS:
443 * Nothing
444 *%DESCRIPTION:
445 * Waits for a PADS packet and copies useful information
446 ***********************************************************************/
447 static void
448 waitForPADS(PPPoEConnection *conn, int timeout)
449 {
450     fd_set readable;
451     int r;
452     struct timeval tv;
453     PPPoEPacket packet;
454     int len;
455
456     conn->error = 0;
457     do {
458         if (BPF_BUFFER_IS_EMPTY) {
459             tv.tv_sec = timeout;
460             tv.tv_usec = 0;
461
462             FD_ZERO(&readable);
463             FD_SET(conn->discoverySocket, &readable);
464
465             while(1) {
466                 r = select(conn->discoverySocket+1, &readable, NULL, NULL, &tv);
467                 if (r >= 0 || errno != EINTR) break;
468             }
469             if (r < 0) {
470                 error("select (waitForPADS): %m");
471                 return;
472             }
473             if (r == 0) return;
474         }
475
476         /* Get the packet */
477         receivePacket(conn->discoverySocket, &packet, &len);
478
479         /* Check length */
480         if (ntohs(packet.length) + HDR_SIZE > len) {
481             error("Bogus PPPoE length field (%u)",
482                    (unsigned int) ntohs(packet.length));
483             continue;
484         }
485
486 #ifdef USE_BPF
487         /* If it's not a Discovery packet, loop again */
488         if (etherType(&packet) != Eth_PPPOE_Discovery) continue;
489 #endif
490
491         /* If it's not from the AC, it's not for me */
492         if (memcmp(packet.ethHdr.h_source, conn->peerEth, ETH_ALEN)) continue;
493
494         /* If it's not for us, loop again */
495         if (!packetIsForMe(conn, &packet)) continue;
496
497         /* Is it PADS?  */
498         if (packet.code == CODE_PADS) {
499             /* Parse for goodies */
500             if (parsePacket(&packet, parsePADSTags, conn) < 0)
501                 return;
502             if (conn->error)
503                 return;
504             conn->discoveryState = STATE_SESSION;
505             break;
506         }
507     } while (conn->discoveryState != STATE_SESSION);
508
509     /* Don't bother with ntohs; we'll just end up converting it back... */
510     conn->session = packet.session;
511
512     info("PPP session is %d", (int) ntohs(conn->session));
513
514     /* RFC 2516 says session id MUST NOT be zero or 0xFFFF */
515     if (ntohs(conn->session) == 0 || ntohs(conn->session) == 0xFFFF) {
516         error("Access concentrator used a session value of %x -- the AC is violating RFC 2516", (unsigned int) ntohs(conn->session));
517     }
518 }
519
520 /**********************************************************************
521 *%FUNCTION: discovery
522 *%ARGUMENTS:
523 * conn -- PPPoE connection info structure
524 *%RETURNS:
525 * Nothing
526 *%DESCRIPTION:
527 * Performs the PPPoE discovery phase
528 ***********************************************************************/
529 void
530 discovery(PPPoEConnection *conn)
531 {
532     int padiAttempts = 0;
533     int padrAttempts = 0;
534     int timeout = conn->discoveryTimeout;
535
536     conn->discoverySocket =
537         openInterface(conn->ifName, Eth_PPPOE_Discovery, conn->myEth);
538
539     do {
540         padiAttempts++;
541         if (padiAttempts > MAX_PADI_ATTEMPTS) {
542             warn("Timeout waiting for PADO packets");
543             close(conn->discoverySocket);
544             conn->discoverySocket = -1;
545             return;
546         }
547         sendPADI(conn);
548         conn->discoveryState = STATE_SENT_PADI;
549         waitForPADO(conn, timeout);
550
551         timeout *= 2;
552     } while (conn->discoveryState == STATE_SENT_PADI);
553
554     timeout = conn->discoveryTimeout;
555     do {
556         padrAttempts++;
557         if (padrAttempts > MAX_PADI_ATTEMPTS) {
558             warn("Timeout waiting for PADS packets");
559             close(conn->discoverySocket);
560             conn->discoverySocket = -1;
561             return;
562         }
563         sendPADR(conn);
564         conn->discoveryState = STATE_SENT_PADR;
565         waitForPADS(conn, timeout);
566         timeout *= 2;
567     } while (conn->discoveryState == STATE_SENT_PADR);
568
569     /* We're done. */
570     conn->discoveryState = STATE_SESSION;
571     return;
572 }