blob: 2dad044d7c0237e2eda8a5ac6952419a5776b69b
1 | /*********************************************************************** |
2 | * |
3 | * relay.c |
4 | * |
5 | * Implementation of PPPoE relay |
6 | * |
7 | * Copyright (C) 2001-2006 Roaring Penguin Software Inc. |
8 | * |
9 | * This program may be distributed according to the terms of the GNU |
10 | * General Public License, version 2 or (at your option) any later version. |
11 | * |
12 | * LIC: GPL |
13 | * |
14 | * $Id$ |
15 | * |
16 | ***********************************************************************/ |
17 | static char const RCSID[] = |
18 | "$Id$"; |
19 | |
20 | #define _GNU_SOURCE 1 /* For SA_RESTART */ |
21 | |
22 | #include "relay.h" |
23 | |
24 | #include <signal.h> |
25 | |
26 | #ifdef HAVE_SYSLOG_H |
27 | #include <syslog.h> |
28 | #endif |
29 | |
30 | #ifdef HAVE_GETOPT_H |
31 | #include <getopt.h> |
32 | #endif |
33 | |
34 | #include <stdlib.h> |
35 | #include <string.h> |
36 | #include <errno.h> |
37 | |
38 | #ifdef HAVE_SYS_TIME_H |
39 | #include <sys/time.h> |
40 | #endif |
41 | |
42 | #ifdef HAVE_SYS_UIO_H |
43 | #include <sys/uio.h> |
44 | #endif |
45 | |
46 | #ifdef HAVE_UNISTD_H |
47 | #include <unistd.h> |
48 | #endif |
49 | |
50 | |
51 | /* Interfaces (max MAX_INTERFACES) */ |
52 | PPPoEInterface Interfaces[MAX_INTERFACES]; |
53 | int NumInterfaces; |
54 | |
55 | /* Relay info */ |
56 | int NumSessions; |
57 | int MaxSessions; |
58 | PPPoESession *AllSessions; |
59 | PPPoESession *FreeSessions; |
60 | PPPoESession *ActiveSessions; |
61 | |
62 | SessionHash *AllHashes; |
63 | SessionHash *FreeHashes; |
64 | SessionHash *Buckets[HASHTAB_SIZE]; |
65 | |
66 | volatile unsigned int Epoch = 0; |
67 | volatile unsigned int CleanCounter = 0; |
68 | |
69 | /* How often to clean up stale sessions? */ |
70 | #define MIN_CLEAN_PERIOD 30 /* Minimum period to run cleaner */ |
71 | #define TIMEOUT_DIVISOR 20 /* How often to run cleaner per timeout period */ |
72 | unsigned int CleanPeriod = MIN_CLEAN_PERIOD; |
73 | |
74 | /* How long a session can be idle before it is cleaned up? */ |
75 | unsigned int IdleTimeout = MIN_CLEAN_PERIOD * TIMEOUT_DIVISOR; |
76 | |
77 | /* Pipe for breaking select() to initiate periodic cleaning */ |
78 | int CleanPipe[2]; |
79 | |
80 | /* Our relay: if_index followed by peer_mac */ |
81 | #define MY_RELAY_TAG_LEN (sizeof(int) + ETH_ALEN) |
82 | |
83 | /* Hack for daemonizing */ |
84 | #define CLOSEFD 64 |
85 | |
86 | /********************************************************************** |
87 | *%FUNCTION: keepDescriptor |
88 | *%ARGUMENTS: |
89 | * fd -- a file descriptor |
90 | *%RETURNS: |
91 | * 1 if descriptor should NOT be closed during daemonizing; 0 otherwise. |
92 | ***********************************************************************/ |
93 | static int |
94 | keepDescriptor(int fd) |
95 | { |
96 | int i; |
97 | if (fd == CleanPipe[0] || fd == CleanPipe[1]) return 1; |
98 | for (i=0; i<NumInterfaces; i++) { |
99 | if (fd == Interfaces[i].discoverySock || |
100 | fd == Interfaces[i].sessionSock) return 1; |
101 | } |
102 | return 0; |
103 | } |
104 | |
105 | /********************************************************************** |
106 | *%FUNCTION: addTag |
107 | *%ARGUMENTS: |
108 | * packet -- a PPPoE packet |
109 | * tag -- tag to add |
110 | *%RETURNS: |
111 | * -1 if no room in packet; number of bytes added otherwise. |
112 | *%DESCRIPTION: |
113 | * Inserts a tag as the first tag in a PPPoE packet. |
114 | ***********************************************************************/ |
115 | int |
116 | addTag(PPPoEPacket *packet, PPPoETag const *tag) |
117 | { |
118 | return insertBytes(packet, packet->payload, tag, |
119 | ntohs(tag->length) + TAG_HDR_SIZE); |
120 | } |
121 | |
122 | /********************************************************************** |
123 | *%FUNCTION: insertBytes |
124 | *%ARGUMENTS: |
125 | * packet -- a PPPoE packet |
126 | * loc -- location at which to insert bytes of data |
127 | * bytes -- the data to insert |
128 | * len -- length of data to insert |
129 | *%RETURNS: |
130 | * -1 if no room in packet; len otherwise. |
131 | *%DESCRIPTION: |
132 | * Inserts "len" bytes of data at location "loc" in "packet", moving all |
133 | * other data up to make room. |
134 | ***********************************************************************/ |
135 | int |
136 | insertBytes(PPPoEPacket *packet, |
137 | unsigned char *loc, |
138 | void const *bytes, |
139 | int len) |
140 | { |
141 | int toMove; |
142 | int plen = ntohs(packet->length); |
143 | /* Sanity checks */ |
144 | if (loc < packet->payload || |
145 | loc > packet->payload + plen || |
146 | len + plen > MAX_PPPOE_PAYLOAD) { |
147 | return -1; |
148 | } |
149 | |
150 | toMove = (packet->payload + plen) - loc; |
151 | memmove(loc+len, loc, toMove); |
152 | memcpy(loc, bytes, len); |
153 | packet->length = htons(plen + len); |
154 | return len; |
155 | } |
156 | |
157 | /********************************************************************** |
158 | *%FUNCTION: removeBytes |
159 | *%ARGUMENTS: |
160 | * packet -- a PPPoE packet |
161 | * loc -- location at which to remove bytes of data |
162 | * len -- length of data to remove |
163 | *%RETURNS: |
164 | * -1 if there was a problem, len otherwise |
165 | *%DESCRIPTION: |
166 | * Removes "len" bytes of data from location "loc" in "packet", moving all |
167 | * other data down to close the gap |
168 | ***********************************************************************/ |
169 | int |
170 | removeBytes(PPPoEPacket *packet, |
171 | unsigned char *loc, |
172 | int len) |
173 | { |
174 | int toMove; |
175 | int plen = ntohs(packet->length); |
176 | /* Sanity checks */ |
177 | if (len < 0 || len > plen || |
178 | loc < packet->payload || |
179 | loc + len > packet->payload + plen) { |
180 | return -1; |
181 | } |
182 | |
183 | toMove = ((packet->payload + plen) - loc) - len; |
184 | memmove(loc, loc+len, toMove); |
185 | packet->length = htons(plen - len); |
186 | return len; |
187 | } |
188 | |
189 | /********************************************************************** |
190 | *%FUNCTION: usage |
191 | *%ARGUMENTS: |
192 | * argv0 -- program name |
193 | *%RETURNS: |
194 | * Nothing |
195 | *%DESCRIPTION: |
196 | * Prints usage information and exits. |
197 | ***********************************************************************/ |
198 | void |
199 | usage(char const *argv0) |
200 | { |
201 | fprintf(stderr, "Usage: %s [options]\n", argv0); |
202 | fprintf(stderr, "Options:\n"); |
203 | fprintf(stderr, " -S if_name -- Specify interface for PPPoE Server\n"); |
204 | fprintf(stderr, " -C if_name -- Specify interface for PPPoE Client\n"); |
205 | fprintf(stderr, " -B if_name -- Specify interface for both clients and server\n"); |
206 | fprintf(stderr, " -n nsess -- Maxmimum number of sessions to relay\n"); |
207 | fprintf(stderr, " -i timeout -- Idle timeout in seconds (0 = no timeout)\n"); |
208 | fprintf(stderr, " -F -- Do not fork into background\n"); |
209 | fprintf(stderr, " -h -- Print this help message\n"); |
210 | |
211 | fprintf(stderr, "\nPPPoE Version %s, Copyright (C) 2001-2006 Roaring Penguin Software Inc.\n", VERSION); |
212 | fprintf(stderr, "PPPoE comes with ABSOLUTELY NO WARRANTY.\n"); |
213 | fprintf(stderr, "This is free software, and you are welcome to redistribute it under the terms\n"); |
214 | fprintf(stderr, "of the GNU General Public License, version 2 or any later version.\n"); |
215 | fprintf(stderr, "http://www.roaringpenguin.com\n"); |
216 | exit(EXIT_SUCCESS); |
217 | } |
218 | |
219 | /********************************************************************** |
220 | *%FUNCTION: main |
221 | *%ARGUMENTS: |
222 | * argc, argv -- usual suspects |
223 | *%RETURNS: |
224 | * EXIT_SUCCESS or EXIT_FAILURE |
225 | *%DESCRIPTION: |
226 | * Main program. Options: |
227 | * -C ifname -- Use interface for PPPoE clients |
228 | * -S ifname -- Use interface for PPPoE servers |
229 | * -B ifname -- Use interface for both clients and servers |
230 | * -n sessions -- Maximum of "n" sessions |
231 | ***********************************************************************/ |
232 | int |
233 | main(int argc, char *argv[]) |
234 | { |
235 | int opt; |
236 | int nsess = DEFAULT_SESSIONS; |
237 | struct sigaction sa; |
238 | int beDaemon = 1; |
239 | |
240 | if (getuid() != geteuid() || |
241 | getgid() != getegid()) { |
242 | fprintf(stderr, "SECURITY WARNING: pppoe-relay will NOT run suid or sgid. Fix your installation.\n"); |
243 | exit(1); |
244 | } |
245 | |
246 | |
247 | openlog("pppoe-relay", LOG_PID, LOG_DAEMON); |
248 | |
249 | while((opt = getopt(argc, argv, "hC:S:B:n:i:F")) != -1) { |
250 | switch(opt) { |
251 | case 'h': |
252 | usage(argv[0]); |
253 | break; |
254 | case 'F': |
255 | beDaemon = 0; |
256 | break; |
257 | case 'C': |
258 | addInterface(optarg, 1, 0); |
259 | break; |
260 | case 'S': |
261 | addInterface(optarg, 0, 1); |
262 | break; |
263 | case 'B': |
264 | addInterface(optarg, 1, 1); |
265 | break; |
266 | case 'i': |
267 | if (sscanf(optarg, "%u", &IdleTimeout) != 1) { |
268 | fprintf(stderr, "Illegal argument to -i: should be -i timeout\n"); |
269 | exit(EXIT_FAILURE); |
270 | } |
271 | CleanPeriod = IdleTimeout / TIMEOUT_DIVISOR; |
272 | if (CleanPeriod < MIN_CLEAN_PERIOD) CleanPeriod = MIN_CLEAN_PERIOD; |
273 | break; |
274 | case 'n': |
275 | if (sscanf(optarg, "%d", &nsess) != 1) { |
276 | fprintf(stderr, "Illegal argument to -n: should be -n #sessions\n"); |
277 | exit(EXIT_FAILURE); |
278 | } |
279 | if (nsess < 1 || nsess > 65534) { |
280 | fprintf(stderr, "Illegal argument to -n: must range from 1 to 65534\n"); |
281 | exit(EXIT_FAILURE); |
282 | } |
283 | break; |
284 | default: |
285 | usage(argv[0]); |
286 | } |
287 | } |
288 | |
289 | #ifdef USE_LINUX_PACKET |
290 | #ifndef HAVE_STRUCT_SOCKADDR_LL |
291 | fprintf(stderr, "The PPPoE relay does not work on Linux 2.0 kernels.\n"); |
292 | exit(EXIT_FAILURE); |
293 | #endif |
294 | #endif |
295 | |
296 | /* Check that at least two interfaces were defined */ |
297 | if (NumInterfaces < 2) { |
298 | fprintf(stderr, "%s: Must define at least two interfaces\n", |
299 | argv[0]); |
300 | exit(EXIT_FAILURE); |
301 | } |
302 | |
303 | /* Make a pipe for the cleaner */ |
304 | if (pipe(CleanPipe) < 0) { |
305 | fatalSys("pipe"); |
306 | } |
307 | |
308 | /* Set up alarm handler */ |
309 | sa.sa_handler = alarmHandler; |
310 | sigemptyset(&sa.sa_mask); |
311 | sa.sa_flags = SA_RESTART; |
312 | if (sigaction(SIGALRM, &sa, NULL) < 0) { |
313 | fatalSys("sigaction"); |
314 | } |
315 | |
316 | /* Allocate memory for sessions, etc. */ |
317 | initRelay(nsess); |
318 | |
319 | /* Daemonize -- UNIX Network Programming, Vol. 1, Stevens */ |
320 | if (beDaemon) { |
321 | int i; |
322 | i = fork(); |
323 | if (i < 0) { |
324 | fatalSys("fork"); |
325 | } else if (i != 0) { |
326 | /* parent */ |
327 | exit(0); |
328 | } |
329 | setsid(); |
330 | signal(SIGHUP, SIG_IGN); |
331 | i = fork(); |
332 | if (i < 0) { |
333 | fatalSys("fork"); |
334 | } else if (i != 0) { |
335 | exit(0); |
336 | } |
337 | |
338 | chdir("/"); |
339 | closelog(); |
340 | for (i=0; i<CLOSEFD; i++) { |
341 | if (!keepDescriptor(i)) { |
342 | close(i); |
343 | } |
344 | } |
345 | /* We nuked our syslog descriptor... */ |
346 | openlog("pppoe-relay", LOG_PID, LOG_DAEMON); |
347 | } |
348 | |
349 | /* Kick off SIGALRM if there is an idle timeout */ |
350 | if (IdleTimeout) alarm(1); |
351 | |
352 | /* Enter the relay loop */ |
353 | relayLoop(); |
354 | |
355 | /* Shouldn't ever get here... */ |
356 | return EXIT_FAILURE; |
357 | } |
358 | |
359 | /********************************************************************** |
360 | *%FUNCTION: addInterface |
361 | *%ARGUMENTS: |
362 | * ifname -- interface name |
363 | * clientOK -- true if this interface should relay PADI, PADR packets. |
364 | * acOK -- true if this interface should relay PADO, PADS packets. |
365 | *%RETURNS: |
366 | * Nothing |
367 | *%DESCRIPTION: |
368 | * Opens an interface; sets up discovery and session sockets. |
369 | ***********************************************************************/ |
370 | void |
371 | addInterface(char const *ifname, |
372 | int clientOK, |
373 | int acOK) |
374 | { |
375 | PPPoEInterface *i; |
376 | int j; |
377 | for (j=0; j<NumInterfaces; j++) { |
378 | if (!strncmp(Interfaces[j].name, ifname, IFNAMSIZ)) { |
379 | fprintf(stderr, "Interface %s specified more than once.\n", ifname); |
380 | exit(EXIT_FAILURE); |
381 | } |
382 | } |
383 | |
384 | if (NumInterfaces >= MAX_INTERFACES) { |
385 | fprintf(stderr, "Too many interfaces (%d max)\n", |
386 | MAX_INTERFACES); |
387 | exit(EXIT_FAILURE); |
388 | } |
389 | i = &Interfaces[NumInterfaces++]; |
390 | strncpy(i->name, ifname, IFNAMSIZ); |
391 | i->name[IFNAMSIZ] = 0; |
392 | |
393 | i->discoverySock = openInterface(ifname, Eth_PPPOE_Discovery, i->mac); |
394 | i->sessionSock = openInterface(ifname, Eth_PPPOE_Session, NULL); |
395 | i->clientOK = clientOK; |
396 | i->acOK = acOK; |
397 | } |
398 | |
399 | /********************************************************************** |
400 | *%FUNCTION: initRelay |
401 | *%ARGUMENTS: |
402 | * nsess -- maximum allowable number of sessions |
403 | *%RETURNS: |
404 | * Nothing |
405 | *%DESCRIPTION: |
406 | * Initializes relay hash table and session tables. |
407 | ***********************************************************************/ |
408 | void |
409 | initRelay(int nsess) |
410 | { |
411 | int i; |
412 | NumSessions = 0; |
413 | MaxSessions = nsess; |
414 | |
415 | AllSessions = calloc(MaxSessions, sizeof(PPPoESession)); |
416 | if (!AllSessions) { |
417 | rp_fatal("Unable to allocate memory for PPPoE session table"); |
418 | } |
419 | AllHashes = calloc(MaxSessions*2, sizeof(SessionHash)); |
420 | if (!AllHashes) { |
421 | rp_fatal("Unable to allocate memory for PPPoE hash table"); |
422 | } |
423 | |
424 | /* Initialize sessions in a linked list */ |
425 | AllSessions[0].prev = NULL; |
426 | if (MaxSessions > 1) { |
427 | AllSessions[0].next = &AllSessions[1]; |
428 | } else { |
429 | AllSessions[0].next = NULL; |
430 | } |
431 | for (i=1; i<MaxSessions-1; i++) { |
432 | AllSessions[i].prev = &AllSessions[i-1]; |
433 | AllSessions[i].next = &AllSessions[i+1]; |
434 | } |
435 | if (MaxSessions > 1) { |
436 | AllSessions[MaxSessions-1].prev = &AllSessions[MaxSessions-2]; |
437 | AllSessions[MaxSessions-1].next = NULL; |
438 | } |
439 | |
440 | FreeSessions = AllSessions; |
441 | ActiveSessions = NULL; |
442 | |
443 | /* Initialize session numbers which we hand out */ |
444 | for (i=0; i<MaxSessions; i++) { |
445 | AllSessions[i].sesNum = htons((UINT16_t) i+1); |
446 | } |
447 | |
448 | /* Initialize hashes in a linked list */ |
449 | AllHashes[0].prev = NULL; |
450 | AllHashes[0].next = &AllHashes[1]; |
451 | for (i=1; i<2*MaxSessions-1; i++) { |
452 | AllHashes[i].prev = &AllHashes[i-1]; |
453 | AllHashes[i].next = &AllHashes[i+1]; |
454 | } |
455 | AllHashes[2*MaxSessions-1].prev = &AllHashes[2*MaxSessions-2]; |
456 | AllHashes[2*MaxSessions-1].next = NULL; |
457 | |
458 | FreeHashes = AllHashes; |
459 | } |
460 | |
461 | /********************************************************************** |
462 | *%FUNCTION: createSession |
463 | *%ARGUMENTS: |
464 | * ac -- Ethernet interface on access-concentrator side |
465 | * cli -- Ethernet interface on client side |
466 | * acMac -- Access concentrator's MAC address |
467 | * cliMac -- Client's MAC address |
468 | * acSess -- Access concentrator's session ID. |
469 | *%RETURNS: |
470 | * PPPoESession structure; NULL if one could not be allocated |
471 | *%DESCRIPTION: |
472 | * Initializes relay hash table and session tables. |
473 | ***********************************************************************/ |
474 | PPPoESession * |
475 | createSession(PPPoEInterface const *ac, |
476 | PPPoEInterface const *cli, |
477 | unsigned char const *acMac, |
478 | unsigned char const *cliMac, |
479 | UINT16_t acSes) |
480 | { |
481 | PPPoESession *sess; |
482 | SessionHash *acHash, *cliHash; |
483 | |
484 | if (NumSessions >= MaxSessions) { |
485 | printErr("Maximum number of sessions reached -- cannot create new session"); |
486 | return NULL; |
487 | } |
488 | |
489 | /* Grab a free session */ |
490 | sess = FreeSessions; |
491 | FreeSessions = sess->next; |
492 | NumSessions++; |
493 | |
494 | /* Link it to the active list */ |
495 | sess->next = ActiveSessions; |
496 | if (sess->next) { |
497 | sess->next->prev = sess; |
498 | } |
499 | ActiveSessions = sess; |
500 | sess->prev = NULL; |
501 | |
502 | sess->epoch = Epoch; |
503 | |
504 | /* Get two hash entries */ |
505 | acHash = FreeHashes; |
506 | cliHash = acHash->next; |
507 | FreeHashes = cliHash->next; |
508 | |
509 | acHash->peer = cliHash; |
510 | cliHash->peer = acHash; |
511 | |
512 | sess->acHash = acHash; |
513 | sess->clientHash = cliHash; |
514 | |
515 | acHash->interface = ac; |
516 | cliHash->interface = cli; |
517 | |
518 | memcpy(acHash->peerMac, acMac, ETH_ALEN); |
519 | acHash->sesNum = acSes; |
520 | acHash->ses = sess; |
521 | |
522 | memcpy(cliHash->peerMac, cliMac, ETH_ALEN); |
523 | cliHash->sesNum = sess->sesNum; |
524 | cliHash->ses = sess; |
525 | |
526 | addHash(acHash); |
527 | addHash(cliHash); |
528 | |
529 | /* Log */ |
530 | syslog(LOG_INFO, |
531 | "Opened session: server=%02x:%02x:%02x:%02x:%02x:%02x(%s:%d), client=%02x:%02x:%02x:%02x:%02x:%02x(%s:%d)", |
532 | acHash->peerMac[0], acHash->peerMac[1], |
533 | acHash->peerMac[2], acHash->peerMac[3], |
534 | acHash->peerMac[4], acHash->peerMac[5], |
535 | acHash->interface->name, |
536 | ntohs(acHash->sesNum), |
537 | cliHash->peerMac[0], cliHash->peerMac[1], |
538 | cliHash->peerMac[2], cliHash->peerMac[3], |
539 | cliHash->peerMac[4], cliHash->peerMac[5], |
540 | cliHash->interface->name, |
541 | ntohs(cliHash->sesNum)); |
542 | |
543 | return sess; |
544 | } |
545 | |
546 | /********************************************************************** |
547 | *%FUNCTION: freeSession |
548 | *%ARGUMENTS: |
549 | * ses -- session to free |
550 | * msg -- extra message to log on syslog. |
551 | *%RETURNS: |
552 | * Nothing |
553 | *%DESCRIPTION: |
554 | * Frees data used by a PPPoE session -- adds hashes and session back |
555 | * to the free list |
556 | ***********************************************************************/ |
557 | void |
558 | freeSession(PPPoESession *ses, char const *msg) |
559 | { |
560 | syslog(LOG_INFO, |
561 | "Closed session: server=%02x:%02x:%02x:%02x:%02x:%02x(%s:%d), client=%02x:%02x:%02x:%02x:%02x:%02x(%s:%d): %s", |
562 | ses->acHash->peerMac[0], ses->acHash->peerMac[1], |
563 | ses->acHash->peerMac[2], ses->acHash->peerMac[3], |
564 | ses->acHash->peerMac[4], ses->acHash->peerMac[5], |
565 | ses->acHash->interface->name, |
566 | ntohs(ses->acHash->sesNum), |
567 | ses->clientHash->peerMac[0], ses->clientHash->peerMac[1], |
568 | ses->clientHash->peerMac[2], ses->clientHash->peerMac[3], |
569 | ses->clientHash->peerMac[4], ses->clientHash->peerMac[5], |
570 | ses->clientHash->interface->name, |
571 | ntohs(ses->clientHash->sesNum), msg); |
572 | |
573 | /* Unlink from active sessions */ |
574 | if (ses->prev) { |
575 | ses->prev->next = ses->next; |
576 | } else { |
577 | ActiveSessions = ses->next; |
578 | } |
579 | if (ses->next) { |
580 | ses->next->prev = ses->prev; |
581 | } |
582 | |
583 | /* Link onto free list -- this is a singly-linked list, so |
584 | we do not care about prev */ |
585 | ses->next = FreeSessions; |
586 | FreeSessions = ses; |
587 | |
588 | unhash(ses->acHash); |
589 | unhash(ses->clientHash); |
590 | NumSessions--; |
591 | } |
592 | |
593 | /********************************************************************** |
594 | *%FUNCTION: unhash |
595 | *%ARGUMENTS: |
596 | * sh -- session hash to free |
597 | *%RETURNS: |
598 | * Nothing |
599 | *%DESCRIPTION: |
600 | * Frees a session hash -- takes it out of hash table and puts it on |
601 | * free list. |
602 | ***********************************************************************/ |
603 | void |
604 | unhash(SessionHash *sh) |
605 | { |
606 | unsigned int b = hash(sh->peerMac, sh->sesNum) % HASHTAB_SIZE; |
607 | if (sh->prev) { |
608 | sh->prev->next = sh->next; |
609 | } else { |
610 | Buckets[b] = sh->next; |
611 | } |
612 | |
613 | if (sh->next) { |
614 | sh->next->prev = sh->prev; |
615 | } |
616 | |
617 | /* Add to free list (singly-linked) */ |
618 | sh->next = FreeHashes; |
619 | FreeHashes = sh; |
620 | } |
621 | |
622 | /********************************************************************** |
623 | *%FUNCTION: addHash |
624 | *%ARGUMENTS: |
625 | * sh -- a session hash |
626 | *%RETURNS: |
627 | * Nothing |
628 | *%DESCRIPTION: |
629 | * Adds a SessionHash to the hash table |
630 | ***********************************************************************/ |
631 | void |
632 | addHash(SessionHash *sh) |
633 | { |
634 | unsigned int b = hash(sh->peerMac, sh->sesNum) % HASHTAB_SIZE; |
635 | sh->next = Buckets[b]; |
636 | sh->prev = NULL; |
637 | if (sh->next) { |
638 | sh->next->prev = sh; |
639 | } |
640 | Buckets[b] = sh; |
641 | } |
642 | |
643 | /********************************************************************** |
644 | *%FUNCTION: hash |
645 | *%ARGUMENTS: |
646 | * mac -- an Ethernet address |
647 | * sesNum -- a session number |
648 | *%RETURNS: |
649 | * A hash value combining Ethernet address with session number. |
650 | * Currently very simplistic; we may need to experiment with different |
651 | * hash values. |
652 | ***********************************************************************/ |
653 | unsigned int |
654 | hash(unsigned char const *mac, UINT16_t sesNum) |
655 | { |
656 | unsigned int ans1 = |
657 | ((unsigned int) mac[0]) | |
658 | (((unsigned int) mac[1]) << 8) | |
659 | (((unsigned int) mac[2]) << 16) | |
660 | (((unsigned int) mac[3]) << 24); |
661 | unsigned int ans2 = |
662 | ((unsigned int) sesNum) | |
663 | (((unsigned int) mac[4]) << 16) | |
664 | (((unsigned int) mac[5]) << 24); |
665 | return ans1 ^ ans2; |
666 | } |
667 | |
668 | /********************************************************************** |
669 | *%FUNCTION: findSession |
670 | *%ARGUMENTS: |
671 | * mac -- an Ethernet address |
672 | * sesNum -- a session number |
673 | *%RETURNS: |
674 | * The session hash for peer address "mac", session number sesNum |
675 | ***********************************************************************/ |
676 | SessionHash * |
677 | findSession(unsigned char const *mac, UINT16_t sesNum) |
678 | { |
679 | unsigned int b = hash(mac, sesNum) % HASHTAB_SIZE; |
680 | SessionHash *sh = Buckets[b]; |
681 | while(sh) { |
682 | if (!memcmp(mac, sh->peerMac, ETH_ALEN) && sesNum == sh->sesNum) { |
683 | return sh; |
684 | } |
685 | sh = sh->next; |
686 | } |
687 | return NULL; |
688 | } |
689 | |
690 | /********************************************************************** |
691 | *%FUNCTION: fatalSys |
692 | *%ARGUMENTS: |
693 | * str -- error message |
694 | *%RETURNS: |
695 | * Nothing |
696 | *%DESCRIPTION: |
697 | * Prints a message plus the errno value to stderr and syslog and exits. |
698 | ***********************************************************************/ |
699 | void |
700 | fatalSys(char const *str) |
701 | { |
702 | char buf[1024]; |
703 | sprintf(buf, "%.256s: %.256s", str, strerror(errno)); |
704 | printErr(buf); |
705 | exit(EXIT_FAILURE); |
706 | } |
707 | |
708 | /********************************************************************** |
709 | *%FUNCTION: sysErr |
710 | *%ARGUMENTS: |
711 | * str -- error message |
712 | *%RETURNS: |
713 | * Nothing |
714 | *%DESCRIPTION: |
715 | * Prints a message plus the errno value to syslog. |
716 | ***********************************************************************/ |
717 | void |
718 | sysErr(char const *str) |
719 | { |
720 | char buf[1024]; |
721 | sprintf(buf, "%.256s: %.256s", str, strerror(errno)); |
722 | printErr(buf); |
723 | } |
724 | |
725 | /********************************************************************** |
726 | *%FUNCTION: rp_fatal |
727 | *%ARGUMENTS: |
728 | * str -- error message |
729 | *%RETURNS: |
730 | * Nothing |
731 | *%DESCRIPTION: |
732 | * Prints a message to stderr and syslog and exits. |
733 | ***********************************************************************/ |
734 | void |
735 | rp_fatal(char const *str) |
736 | { |
737 | printErr(str); |
738 | exit(EXIT_FAILURE); |
739 | } |
740 | |
741 | /********************************************************************** |
742 | *%FUNCTION: relayLoop |
743 | *%ARGUMENTS: |
744 | * None |
745 | *%RETURNS: |
746 | * Nothing |
747 | *%DESCRIPTION: |
748 | * Runs the relay loop. This function never returns |
749 | ***********************************************************************/ |
750 | void |
751 | relayLoop() |
752 | { |
753 | fd_set readable, readableCopy; |
754 | int maxFD; |
755 | int i, r; |
756 | int sock; |
757 | |
758 | /* Build the select set */ |
759 | FD_ZERO(&readable); |
760 | maxFD = 0; |
761 | for (i=0; i<NumInterfaces; i++) { |
762 | sock = Interfaces[i].discoverySock; |
763 | if (sock > maxFD) maxFD = sock; |
764 | FD_SET(sock, &readable); |
765 | sock = Interfaces[i].sessionSock; |
766 | if (sock > maxFD) maxFD = sock; |
767 | FD_SET(sock, &readable); |
768 | if (CleanPipe[0] > maxFD) maxFD = CleanPipe[0]; |
769 | FD_SET(CleanPipe[0], &readable); |
770 | } |
771 | maxFD++; |
772 | for(;;) { |
773 | readableCopy = readable; |
774 | for(;;) { |
775 | r = select(maxFD, &readableCopy, NULL, NULL, NULL); |
776 | if (r >= 0 || errno != EINTR) break; |
777 | } |
778 | if (r < 0) { |
779 | sysErr("select (relayLoop)"); |
780 | continue; |
781 | } |
782 | |
783 | /* Handle session packets first */ |
784 | for (i=0; i<NumInterfaces; i++) { |
785 | if (FD_ISSET(Interfaces[i].sessionSock, &readableCopy)) { |
786 | relayGotSessionPacket(&Interfaces[i]); |
787 | } |
788 | } |
789 | |
790 | /* Now handle discovery packets */ |
791 | for (i=0; i<NumInterfaces; i++) { |
792 | if (FD_ISSET(Interfaces[i].discoverySock, &readableCopy)) { |
793 | relayGotDiscoveryPacket(&Interfaces[i]); |
794 | } |
795 | } |
796 | |
797 | /* Handle the session-cleaning process */ |
798 | if (FD_ISSET(CleanPipe[0], &readableCopy)) { |
799 | char dummy; |
800 | CleanCounter = 0; |
801 | read(CleanPipe[0], &dummy, 1); |
802 | if (IdleTimeout) cleanSessions(); |
803 | } |
804 | } |
805 | } |
806 | |
807 | /********************************************************************** |
808 | *%FUNCTION: relayGotDiscoveryPacket |
809 | *%ARGUMENTS: |
810 | * iface -- interface on which packet is waiting |
811 | *%RETURNS: |
812 | * Nothing |
813 | *%DESCRIPTION: |
814 | * Receives and processes a discovery packet. |
815 | ***********************************************************************/ |
816 | void |
817 | relayGotDiscoveryPacket(PPPoEInterface const *iface) |
818 | { |
819 | PPPoEPacket packet; |
820 | int size; |
821 | |
822 | if (receivePacket(iface->discoverySock, &packet, &size) < 0) { |
823 | return; |
824 | } |
825 | /* Ignore unknown code/version */ |
826 | if (packet.ver != 1 || packet.type != 1) { |
827 | return; |
828 | } |
829 | |
830 | /* Validate length */ |
831 | if (ntohs(packet.length) + HDR_SIZE > (unsigned int)size) { |
832 | syslog(LOG_ERR, "Bogus PPPoE length field (%u)", |
833 | (unsigned int) ntohs(packet.length)); |
834 | return; |
835 | } |
836 | |
837 | /* Drop Ethernet frame padding */ |
838 | if ((unsigned int)size > ntohs(packet.length) + HDR_SIZE) { |
839 | size = ntohs(packet.length) + HDR_SIZE; |
840 | } |
841 | |
842 | switch(packet.code) { |
843 | case CODE_PADT: |
844 | relayHandlePADT(iface, &packet, size); |
845 | break; |
846 | case CODE_PADI: |
847 | relayHandlePADI(iface, &packet, size); |
848 | break; |
849 | case CODE_PADO: |
850 | relayHandlePADO(iface, &packet, size); |
851 | break; |
852 | case CODE_PADR: |
853 | relayHandlePADR(iface, &packet, size); |
854 | break; |
855 | case CODE_PADS: |
856 | relayHandlePADS(iface, &packet, size); |
857 | break; |
858 | default: |
859 | syslog(LOG_ERR, "Discovery packet on %s with unknown code %d", |
860 | iface->name, (int) packet.code); |
861 | } |
862 | } |
863 | |
864 | /********************************************************************** |
865 | *%FUNCTION: relayGotSessionPacket |
866 | *%ARGUMENTS: |
867 | * iface -- interface on which packet is waiting |
868 | *%RETURNS: |
869 | * Nothing |
870 | *%DESCRIPTION: |
871 | * Receives and processes a session packet. |
872 | ***********************************************************************/ |
873 | void |
874 | relayGotSessionPacket(PPPoEInterface const *iface) |
875 | { |
876 | PPPoEPacket packet; |
877 | int size; |
878 | SessionHash *sh; |
879 | PPPoESession *ses; |
880 | |
881 | if (receivePacket(iface->sessionSock, &packet, &size) < 0) { |
882 | return; |
883 | } |
884 | |
885 | /* Ignore unknown code/version */ |
886 | if (packet.ver != 1 || packet.type != 1) { |
887 | return; |
888 | } |
889 | |
890 | /* Must be a session packet */ |
891 | if (packet.code != CODE_SESS) { |
892 | syslog(LOG_ERR, "Session packet with code %d", (int) packet.code); |
893 | return; |
894 | } |
895 | |
896 | /* Ignore session packets whose destination address isn't ours */ |
897 | if (memcmp(packet.ethHdr.h_dest, iface->mac, ETH_ALEN)) { |
898 | return; |
899 | } |
900 | |
901 | /* Validate length */ |
902 | if (ntohs(packet.length) + HDR_SIZE > (unsigned int)size) { |
903 | syslog(LOG_ERR, "Bogus PPPoE length field (%u)", |
904 | (unsigned int) ntohs(packet.length)); |
905 | return; |
906 | } |
907 | |
908 | /* Drop Ethernet frame padding */ |
909 | if ((unsigned int)size > ntohs(packet.length) + HDR_SIZE) { |
910 | size = ntohs(packet.length) + HDR_SIZE; |
911 | } |
912 | |
913 | /* We're in business! Find the hash */ |
914 | sh = findSession(packet.ethHdr.h_source, packet.session); |
915 | if (!sh) { |
916 | /* Don't log this. Someone could be running the client and the |
917 | relay on the same box. */ |
918 | return; |
919 | } |
920 | |
921 | /* Relay it */ |
922 | ses = sh->ses; |
923 | ses->epoch = Epoch; |
924 | sh = sh->peer; |
925 | packet.session = sh->sesNum; |
926 | memcpy(packet.ethHdr.h_source, sh->interface->mac, ETH_ALEN); |
927 | memcpy(packet.ethHdr.h_dest, sh->peerMac, ETH_ALEN); |
928 | #if 0 |
929 | fprintf(stderr, "Relaying %02x:%02x:%02x:%02x:%02x:%02x(%s:%d) to %02x:%02x:%02x:%02x:%02x:%02x(%s:%d)\n", |
930 | sh->peer->peerMac[0], sh->peer->peerMac[1], sh->peer->peerMac[2], |
931 | sh->peer->peerMac[3], sh->peer->peerMac[4], sh->peer->peerMac[5], |
932 | sh->peer->interface->name, ntohs(sh->peer->sesNum), |
933 | sh->peerMac[0], sh->peerMac[1], sh->peerMac[2], |
934 | sh->peerMac[3], sh->peerMac[4], sh->peerMac[5], |
935 | sh->interface->name, ntohs(sh->sesNum)); |
936 | #endif |
937 | sendPacket(NULL, sh->interface->sessionSock, &packet, size); |
938 | } |
939 | |
940 | /********************************************************************** |
941 | *%FUNCTION: relayHandlePADT |
942 | *%ARGUMENTS: |
943 | * iface -- interface on which packet was received |
944 | * packet -- the PADT packet |
945 | *%RETURNS: |
946 | * Nothing |
947 | *%DESCRIPTION: |
948 | * Receives and processes a PADT packet. |
949 | ***********************************************************************/ |
950 | void |
951 | relayHandlePADT(PPPoEInterface const *iface, |
952 | PPPoEPacket *packet, |
953 | int size) |
954 | { |
955 | SessionHash *sh; |
956 | PPPoESession *ses; |
957 | |
958 | sh = findSession(packet->ethHdr.h_source, packet->session); |
959 | if (!sh) { |
960 | return; |
961 | } |
962 | /* Relay the PADT to the peer */ |
963 | sh = sh->peer; |
964 | ses = sh->ses; |
965 | packet->session = sh->sesNum; |
966 | memcpy(packet->ethHdr.h_source, sh->interface->mac, ETH_ALEN); |
967 | memcpy(packet->ethHdr.h_dest, sh->peerMac, ETH_ALEN); |
968 | sendPacket(NULL, sh->interface->sessionSock, packet, size); |
969 | |
970 | /* Destroy the session */ |
971 | freeSession(ses, "Received PADT"); |
972 | } |
973 | |
974 | /********************************************************************** |
975 | *%FUNCTION: relayHandlePADI |
976 | *%ARGUMENTS: |
977 | * iface -- interface on which packet was received |
978 | * packet -- the PADI packet |
979 | *%RETURNS: |
980 | * Nothing |
981 | *%DESCRIPTION: |
982 | * Receives and processes a PADI packet. |
983 | ***********************************************************************/ |
984 | void |
985 | relayHandlePADI(PPPoEInterface const *iface, |
986 | PPPoEPacket *packet, |
987 | int size) |
988 | { |
989 | PPPoETag tag; |
990 | unsigned char *loc; |
991 | int i, r; |
992 | |
993 | int ifIndex; |
994 | |
995 | /* Can a client legally be behind this interface? */ |
996 | if (!iface->clientOK) { |
997 | syslog(LOG_ERR, |
998 | "PADI packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not permitted", |
999 | packet->ethHdr.h_source[0], |
1000 | packet->ethHdr.h_source[1], |
1001 | packet->ethHdr.h_source[2], |
1002 | packet->ethHdr.h_source[3], |
1003 | packet->ethHdr.h_source[4], |
1004 | packet->ethHdr.h_source[5], |
1005 | iface->name); |
1006 | return; |
1007 | } |
1008 | |
1009 | /* Source address must be unicast */ |
1010 | if (NOT_UNICAST(packet->ethHdr.h_source)) { |
1011 | syslog(LOG_ERR, |
1012 | "PADI packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not from a unicast address", |
1013 | packet->ethHdr.h_source[0], |
1014 | packet->ethHdr.h_source[1], |
1015 | packet->ethHdr.h_source[2], |
1016 | packet->ethHdr.h_source[3], |
1017 | packet->ethHdr.h_source[4], |
1018 | packet->ethHdr.h_source[5], |
1019 | iface->name); |
1020 | return; |
1021 | } |
1022 | |
1023 | /* Destination address must be broadcast */ |
1024 | if (NOT_BROADCAST(packet->ethHdr.h_dest)) { |
1025 | syslog(LOG_ERR, |
1026 | "PADI packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not to a broadcast address", |
1027 | packet->ethHdr.h_source[0], |
1028 | packet->ethHdr.h_source[1], |
1029 | packet->ethHdr.h_source[2], |
1030 | packet->ethHdr.h_source[3], |
1031 | packet->ethHdr.h_source[4], |
1032 | packet->ethHdr.h_source[5], |
1033 | iface->name); |
1034 | return; |
1035 | } |
1036 | |
1037 | /* Get array index of interface */ |
1038 | ifIndex = iface - Interfaces; |
1039 | |
1040 | loc = findTag(packet, TAG_RELAY_SESSION_ID, &tag); |
1041 | if (!loc) { |
1042 | tag.type = htons(TAG_RELAY_SESSION_ID); |
1043 | tag.length = htons(MY_RELAY_TAG_LEN); |
1044 | memcpy(tag.payload, &ifIndex, sizeof(ifIndex)); |
1045 | memcpy(tag.payload+sizeof(ifIndex), packet->ethHdr.h_source, ETH_ALEN); |
1046 | /* Add a relay tag if there's room */ |
1047 | r = addTag(packet, &tag); |
1048 | if (r < 0) return; |
1049 | size += r; |
1050 | } else { |
1051 | /* We do not re-use relay-id tags. Drop the frame. The RFC says the |
1052 | relay agent SHOULD return a Generic-Error tag, but this does not |
1053 | make sense for PADI packets. */ |
1054 | return; |
1055 | } |
1056 | |
1057 | /* Broadcast the PADI on all AC-capable interfaces except the interface |
1058 | on which it came */ |
1059 | for (i=0; i < NumInterfaces; i++) { |
1060 | if (iface == &Interfaces[i]) continue; |
1061 | if (!Interfaces[i].acOK) continue; |
1062 | memcpy(packet->ethHdr.h_source, Interfaces[i].mac, ETH_ALEN); |
1063 | sendPacket(NULL, Interfaces[i].discoverySock, packet, size); |
1064 | } |
1065 | |
1066 | } |
1067 | |
1068 | /********************************************************************** |
1069 | *%FUNCTION: relayHandlePADO |
1070 | *%ARGUMENTS: |
1071 | * iface -- interface on which packet was received |
1072 | * packet -- the PADO packet |
1073 | *%RETURNS: |
1074 | * Nothing |
1075 | *%DESCRIPTION: |
1076 | * Receives and processes a PADO packet. |
1077 | ***********************************************************************/ |
1078 | void |
1079 | relayHandlePADO(PPPoEInterface const *iface, |
1080 | PPPoEPacket *packet, |
1081 | int size) |
1082 | { |
1083 | PPPoETag tag; |
1084 | unsigned char *loc; |
1085 | int ifIndex; |
1086 | int acIndex; |
1087 | |
1088 | /* Can a server legally be behind this interface? */ |
1089 | if (!iface->acOK) { |
1090 | syslog(LOG_ERR, |
1091 | "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not permitted", |
1092 | packet->ethHdr.h_source[0], |
1093 | packet->ethHdr.h_source[1], |
1094 | packet->ethHdr.h_source[2], |
1095 | packet->ethHdr.h_source[3], |
1096 | packet->ethHdr.h_source[4], |
1097 | packet->ethHdr.h_source[5], |
1098 | iface->name); |
1099 | return; |
1100 | } |
1101 | |
1102 | acIndex = iface - Interfaces; |
1103 | |
1104 | /* Source address must be unicast */ |
1105 | if (NOT_UNICAST(packet->ethHdr.h_source)) { |
1106 | syslog(LOG_ERR, |
1107 | "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not from a unicast address", |
1108 | packet->ethHdr.h_source[0], |
1109 | packet->ethHdr.h_source[1], |
1110 | packet->ethHdr.h_source[2], |
1111 | packet->ethHdr.h_source[3], |
1112 | packet->ethHdr.h_source[4], |
1113 | packet->ethHdr.h_source[5], |
1114 | iface->name); |
1115 | return; |
1116 | } |
1117 | |
1118 | /* Destination address must be interface's MAC address */ |
1119 | if (memcmp(packet->ethHdr.h_dest, iface->mac, ETH_ALEN)) { |
1120 | return; |
1121 | } |
1122 | |
1123 | /* Find relay tag */ |
1124 | loc = findTag(packet, TAG_RELAY_SESSION_ID, &tag); |
1125 | if (!loc) { |
1126 | syslog(LOG_ERR, |
1127 | "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have Relay-Session-Id tag", |
1128 | packet->ethHdr.h_source[0], |
1129 | packet->ethHdr.h_source[1], |
1130 | packet->ethHdr.h_source[2], |
1131 | packet->ethHdr.h_source[3], |
1132 | packet->ethHdr.h_source[4], |
1133 | packet->ethHdr.h_source[5], |
1134 | iface->name); |
1135 | return; |
1136 | } |
1137 | |
1138 | /* If it's the wrong length, ignore it */ |
1139 | if (ntohs(tag.length) != MY_RELAY_TAG_LEN) { |
1140 | syslog(LOG_ERR, |
1141 | "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have correct length Relay-Session-Id tag", |
1142 | packet->ethHdr.h_source[0], |
1143 | packet->ethHdr.h_source[1], |
1144 | packet->ethHdr.h_source[2], |
1145 | packet->ethHdr.h_source[3], |
1146 | packet->ethHdr.h_source[4], |
1147 | packet->ethHdr.h_source[5], |
1148 | iface->name); |
1149 | return; |
1150 | } |
1151 | |
1152 | /* Extract interface index */ |
1153 | memcpy(&ifIndex, tag.payload, sizeof(ifIndex)); |
1154 | |
1155 | if (ifIndex < 0 || ifIndex >= NumInterfaces || |
1156 | !Interfaces[ifIndex].clientOK || |
1157 | iface == &Interfaces[ifIndex]) { |
1158 | syslog(LOG_ERR, |
1159 | "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s has invalid interface in Relay-Session-Id tag", |
1160 | packet->ethHdr.h_source[0], |
1161 | packet->ethHdr.h_source[1], |
1162 | packet->ethHdr.h_source[2], |
1163 | packet->ethHdr.h_source[3], |
1164 | packet->ethHdr.h_source[4], |
1165 | packet->ethHdr.h_source[5], |
1166 | iface->name); |
1167 | return; |
1168 | } |
1169 | |
1170 | /* Replace Relay-ID tag with opposite-direction tag */ |
1171 | memcpy(loc+TAG_HDR_SIZE, &acIndex, sizeof(acIndex)); |
1172 | memcpy(loc+TAG_HDR_SIZE+sizeof(ifIndex), packet->ethHdr.h_source, ETH_ALEN); |
1173 | |
1174 | /* Set destination address to MAC address in relay ID */ |
1175 | memcpy(packet->ethHdr.h_dest, tag.payload + sizeof(ifIndex), ETH_ALEN); |
1176 | |
1177 | /* Set source address to MAC address of interface */ |
1178 | memcpy(packet->ethHdr.h_source, Interfaces[ifIndex].mac, ETH_ALEN); |
1179 | |
1180 | /* Send the PADO to the proper client */ |
1181 | sendPacket(NULL, Interfaces[ifIndex].discoverySock, packet, size); |
1182 | } |
1183 | |
1184 | /********************************************************************** |
1185 | *%FUNCTION: relayHandlePADR |
1186 | *%ARGUMENTS: |
1187 | * iface -- interface on which packet was received |
1188 | * packet -- the PADR packet |
1189 | *%RETURNS: |
1190 | * Nothing |
1191 | *%DESCRIPTION: |
1192 | * Receives and processes a PADR packet. |
1193 | ***********************************************************************/ |
1194 | void |
1195 | relayHandlePADR(PPPoEInterface const *iface, |
1196 | PPPoEPacket *packet, |
1197 | int size) |
1198 | { |
1199 | PPPoETag tag; |
1200 | unsigned char *loc; |
1201 | int ifIndex; |
1202 | int cliIndex; |
1203 | |
1204 | /* Can a client legally be behind this interface? */ |
1205 | if (!iface->clientOK) { |
1206 | syslog(LOG_ERR, |
1207 | "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not permitted", |
1208 | packet->ethHdr.h_source[0], |
1209 | packet->ethHdr.h_source[1], |
1210 | packet->ethHdr.h_source[2], |
1211 | packet->ethHdr.h_source[3], |
1212 | packet->ethHdr.h_source[4], |
1213 | packet->ethHdr.h_source[5], |
1214 | iface->name); |
1215 | return; |
1216 | } |
1217 | |
1218 | cliIndex = iface - Interfaces; |
1219 | |
1220 | /* Source address must be unicast */ |
1221 | if (NOT_UNICAST(packet->ethHdr.h_source)) { |
1222 | syslog(LOG_ERR, |
1223 | "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not from a unicast address", |
1224 | packet->ethHdr.h_source[0], |
1225 | packet->ethHdr.h_source[1], |
1226 | packet->ethHdr.h_source[2], |
1227 | packet->ethHdr.h_source[3], |
1228 | packet->ethHdr.h_source[4], |
1229 | packet->ethHdr.h_source[5], |
1230 | iface->name); |
1231 | return; |
1232 | } |
1233 | |
1234 | /* Destination address must be interface's MAC address */ |
1235 | if (memcmp(packet->ethHdr.h_dest, iface->mac, ETH_ALEN)) { |
1236 | return; |
1237 | } |
1238 | |
1239 | /* Find relay tag */ |
1240 | loc = findTag(packet, TAG_RELAY_SESSION_ID, &tag); |
1241 | if (!loc) { |
1242 | syslog(LOG_ERR, |
1243 | "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have Relay-Session-Id tag", |
1244 | packet->ethHdr.h_source[0], |
1245 | packet->ethHdr.h_source[1], |
1246 | packet->ethHdr.h_source[2], |
1247 | packet->ethHdr.h_source[3], |
1248 | packet->ethHdr.h_source[4], |
1249 | packet->ethHdr.h_source[5], |
1250 | iface->name); |
1251 | return; |
1252 | } |
1253 | |
1254 | /* If it's the wrong length, ignore it */ |
1255 | if (ntohs(tag.length) != MY_RELAY_TAG_LEN) { |
1256 | syslog(LOG_ERR, |
1257 | "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have correct length Relay-Session-Id tag", |
1258 | packet->ethHdr.h_source[0], |
1259 | packet->ethHdr.h_source[1], |
1260 | packet->ethHdr.h_source[2], |
1261 | packet->ethHdr.h_source[3], |
1262 | packet->ethHdr.h_source[4], |
1263 | packet->ethHdr.h_source[5], |
1264 | iface->name); |
1265 | return; |
1266 | } |
1267 | |
1268 | /* Extract interface index */ |
1269 | memcpy(&ifIndex, tag.payload, sizeof(ifIndex)); |
1270 | |
1271 | if (ifIndex < 0 || ifIndex >= NumInterfaces || |
1272 | !Interfaces[ifIndex].acOK || |
1273 | iface == &Interfaces[ifIndex]) { |
1274 | syslog(LOG_ERR, |
1275 | "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s has invalid interface in Relay-Session-Id tag", |
1276 | packet->ethHdr.h_source[0], |
1277 | packet->ethHdr.h_source[1], |
1278 | packet->ethHdr.h_source[2], |
1279 | packet->ethHdr.h_source[3], |
1280 | packet->ethHdr.h_source[4], |
1281 | packet->ethHdr.h_source[5], |
1282 | iface->name); |
1283 | return; |
1284 | } |
1285 | |
1286 | /* Replace Relay-ID tag with opposite-direction tag */ |
1287 | memcpy(loc+TAG_HDR_SIZE, &cliIndex, sizeof(cliIndex)); |
1288 | memcpy(loc+TAG_HDR_SIZE+sizeof(ifIndex), packet->ethHdr.h_source, ETH_ALEN); |
1289 | |
1290 | /* Set destination address to MAC address in relay ID */ |
1291 | memcpy(packet->ethHdr.h_dest, tag.payload + sizeof(ifIndex), ETH_ALEN); |
1292 | |
1293 | /* Set source address to MAC address of interface */ |
1294 | memcpy(packet->ethHdr.h_source, Interfaces[ifIndex].mac, ETH_ALEN); |
1295 | |
1296 | /* Send the PADR to the proper access concentrator */ |
1297 | sendPacket(NULL, Interfaces[ifIndex].discoverySock, packet, size); |
1298 | } |
1299 | |
1300 | /********************************************************************** |
1301 | *%FUNCTION: relayHandlePADS |
1302 | *%ARGUMENTS: |
1303 | * iface -- interface on which packet was received |
1304 | * packet -- the PADS packet |
1305 | *%RETURNS: |
1306 | * Nothing |
1307 | *%DESCRIPTION: |
1308 | * Receives and processes a PADS packet. |
1309 | ***********************************************************************/ |
1310 | void |
1311 | relayHandlePADS(PPPoEInterface const *iface, |
1312 | PPPoEPacket *packet, |
1313 | int size) |
1314 | { |
1315 | PPPoETag tag; |
1316 | unsigned char *loc; |
1317 | int ifIndex; |
1318 | int acIndex; |
1319 | PPPoESession *ses = NULL; |
1320 | SessionHash *sh; |
1321 | |
1322 | /* Can a server legally be behind this interface? */ |
1323 | if (!iface->acOK) { |
1324 | syslog(LOG_ERR, |
1325 | "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not permitted", |
1326 | packet->ethHdr.h_source[0], |
1327 | packet->ethHdr.h_source[1], |
1328 | packet->ethHdr.h_source[2], |
1329 | packet->ethHdr.h_source[3], |
1330 | packet->ethHdr.h_source[4], |
1331 | packet->ethHdr.h_source[5], |
1332 | iface->name); |
1333 | return; |
1334 | } |
1335 | |
1336 | acIndex = iface - Interfaces; |
1337 | |
1338 | /* Source address must be unicast */ |
1339 | if (NOT_UNICAST(packet->ethHdr.h_source)) { |
1340 | syslog(LOG_ERR, |
1341 | "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not from a unicast address", |
1342 | packet->ethHdr.h_source[0], |
1343 | packet->ethHdr.h_source[1], |
1344 | packet->ethHdr.h_source[2], |
1345 | packet->ethHdr.h_source[3], |
1346 | packet->ethHdr.h_source[4], |
1347 | packet->ethHdr.h_source[5], |
1348 | iface->name); |
1349 | return; |
1350 | } |
1351 | |
1352 | /* Destination address must be interface's MAC address */ |
1353 | if (memcmp(packet->ethHdr.h_dest, iface->mac, ETH_ALEN)) { |
1354 | return; |
1355 | } |
1356 | |
1357 | /* Find relay tag */ |
1358 | loc = findTag(packet, TAG_RELAY_SESSION_ID, &tag); |
1359 | if (!loc) { |
1360 | syslog(LOG_ERR, |
1361 | "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have Relay-Session-Id tag", |
1362 | packet->ethHdr.h_source[0], |
1363 | packet->ethHdr.h_source[1], |
1364 | packet->ethHdr.h_source[2], |
1365 | packet->ethHdr.h_source[3], |
1366 | packet->ethHdr.h_source[4], |
1367 | packet->ethHdr.h_source[5], |
1368 | iface->name); |
1369 | return; |
1370 | } |
1371 | |
1372 | /* If it's the wrong length, ignore it */ |
1373 | if (ntohs(tag.length) != MY_RELAY_TAG_LEN) { |
1374 | syslog(LOG_ERR, |
1375 | "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have correct length Relay-Session-Id tag", |
1376 | packet->ethHdr.h_source[0], |
1377 | packet->ethHdr.h_source[1], |
1378 | packet->ethHdr.h_source[2], |
1379 | packet->ethHdr.h_source[3], |
1380 | packet->ethHdr.h_source[4], |
1381 | packet->ethHdr.h_source[5], |
1382 | iface->name); |
1383 | return; |
1384 | } |
1385 | |
1386 | /* Extract interface index */ |
1387 | memcpy(&ifIndex, tag.payload, sizeof(ifIndex)); |
1388 | |
1389 | if (ifIndex < 0 || ifIndex >= NumInterfaces || |
1390 | !Interfaces[ifIndex].clientOK || |
1391 | iface == &Interfaces[ifIndex]) { |
1392 | syslog(LOG_ERR, |
1393 | "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s has invalid interface in Relay-Session-Id tag", |
1394 | packet->ethHdr.h_source[0], |
1395 | packet->ethHdr.h_source[1], |
1396 | packet->ethHdr.h_source[2], |
1397 | packet->ethHdr.h_source[3], |
1398 | packet->ethHdr.h_source[4], |
1399 | packet->ethHdr.h_source[5], |
1400 | iface->name); |
1401 | return; |
1402 | } |
1403 | |
1404 | /* If session ID is zero, it's the AC respoding with an error. |
1405 | Just relay it; do not create a session */ |
1406 | if (packet->session != htons(0)) { |
1407 | /* Check for existing session */ |
1408 | sh = findSession(packet->ethHdr.h_source, packet->session); |
1409 | if (sh) ses = sh->ses; |
1410 | |
1411 | /* If already an existing session, assume it's a duplicate PADS. Send |
1412 | the frame, but do not create a new session. Is this the right |
1413 | thing to do? Arguably, should send an error to the client and |
1414 | a PADT to the server, because this could happen due to a |
1415 | server crash and reboot. */ |
1416 | |
1417 | if (!ses) { |
1418 | /* Create a new session */ |
1419 | ses = createSession(iface, &Interfaces[ifIndex], |
1420 | packet->ethHdr.h_source, |
1421 | loc + TAG_HDR_SIZE + sizeof(ifIndex), packet->session); |
1422 | if (!ses) { |
1423 | /* Can't allocate session -- send error PADS to client and |
1424 | PADT to server */ |
1425 | PPPoETag hostUniq, *hu; |
1426 | if (findTag(packet, TAG_HOST_UNIQ, &hostUniq)) { |
1427 | hu = &hostUniq; |
1428 | } else { |
1429 | hu = NULL; |
1430 | } |
1431 | relaySendError(CODE_PADS, htons(0), &Interfaces[ifIndex], |
1432 | loc + TAG_HDR_SIZE + sizeof(ifIndex), |
1433 | hu, "RP-PPPoE: Relay: Unable to allocate session"); |
1434 | relaySendError(CODE_PADT, packet->session, iface, |
1435 | packet->ethHdr.h_source, NULL, |
1436 | "RP-PPPoE: Relay: Unable to allocate session"); |
1437 | return; |
1438 | } |
1439 | } |
1440 | /* Replace session number */ |
1441 | packet->session = ses->sesNum; |
1442 | } |
1443 | |
1444 | /* Remove relay-ID tag */ |
1445 | removeBytes(packet, loc, MY_RELAY_TAG_LEN + TAG_HDR_SIZE); |
1446 | size -= (MY_RELAY_TAG_LEN + TAG_HDR_SIZE); |
1447 | |
1448 | /* Set destination address to MAC address in relay ID */ |
1449 | memcpy(packet->ethHdr.h_dest, tag.payload + sizeof(ifIndex), ETH_ALEN); |
1450 | |
1451 | /* Set source address to MAC address of interface */ |
1452 | memcpy(packet->ethHdr.h_source, Interfaces[ifIndex].mac, ETH_ALEN); |
1453 | |
1454 | /* Send the PADS to the proper client */ |
1455 | sendPacket(NULL, Interfaces[ifIndex].discoverySock, packet, size); |
1456 | } |
1457 | |
1458 | /********************************************************************** |
1459 | *%FUNCTION: relaySendError |
1460 | *%ARGUMENTS: |
1461 | * code -- PPPoE packet code (PADS or PADT, typically) |
1462 | * session -- PPPoE session number |
1463 | * iface -- interface on which to send frame |
1464 | * mac -- Ethernet address to which frame should be sent |
1465 | * hostUniq -- if non-NULL, a hostUniq tag to add to error frame |
1466 | * errMsg -- error message to insert into Generic-Error tag. |
1467 | *%RETURNS: |
1468 | * Nothing |
1469 | *%DESCRIPTION: |
1470 | * Sends either a PADS or PADT packet with a Generic-Error tag and an |
1471 | * error message. |
1472 | ***********************************************************************/ |
1473 | void |
1474 | relaySendError(unsigned char code, |
1475 | UINT16_t session, |
1476 | PPPoEInterface const *iface, |
1477 | unsigned char const *mac, |
1478 | PPPoETag const *hostUniq, |
1479 | char const *errMsg) |
1480 | { |
1481 | PPPoEPacket packet; |
1482 | PPPoETag errTag; |
1483 | int size; |
1484 | |
1485 | memcpy(packet.ethHdr.h_source, iface->mac, ETH_ALEN); |
1486 | memcpy(packet.ethHdr.h_dest, mac, ETH_ALEN); |
1487 | packet.ethHdr.h_proto = htons(Eth_PPPOE_Discovery); |
1488 | packet.type = 1; |
1489 | packet.ver = 1; |
1490 | packet.code = code; |
1491 | packet.session = session; |
1492 | packet.length = htons(0); |
1493 | if (hostUniq) { |
1494 | if (addTag(&packet, hostUniq) < 0) return; |
1495 | } |
1496 | errTag.type = htons(TAG_GENERIC_ERROR); |
1497 | errTag.length = htons(strlen(errMsg)); |
1498 | strcpy((char *) errTag.payload, errMsg); |
1499 | if (addTag(&packet, &errTag) < 0) return; |
1500 | size = ntohs(packet.length) + HDR_SIZE; |
1501 | if (code == CODE_PADT) { |
1502 | sendPacket(NULL, iface->discoverySock, &packet, size); |
1503 | } else { |
1504 | sendPacket(NULL, iface->sessionSock, &packet, size); |
1505 | } |
1506 | } |
1507 | |
1508 | /********************************************************************** |
1509 | *%FUNCTION: alarmHandler |
1510 | *%ARGUMENTS: |
1511 | * sig -- signal number |
1512 | *%RETURNS: |
1513 | * Nothing |
1514 | *%DESCRIPTION: |
1515 | * SIGALRM handler. Increments Epoch; if necessary, writes a byte of |
1516 | * data to the alarm pipe to trigger the stale-session cleaner. |
1517 | ***********************************************************************/ |
1518 | void |
1519 | alarmHandler(int sig) |
1520 | { |
1521 | alarm(1); |
1522 | Epoch++; |
1523 | CleanCounter++; |
1524 | if (CleanCounter == CleanPeriod) { |
1525 | write(CleanPipe[1], "", 1); |
1526 | } |
1527 | } |
1528 | |
1529 | /********************************************************************** |
1530 | *%FUNCTION: cleanSessions |
1531 | *%ARGUMENTS: |
1532 | * None |
1533 | *%RETURNS: |
1534 | * Nothing |
1535 | *%DESCRIPTION: |
1536 | * Goes through active sessions and cleans sessions idle for longer |
1537 | * than IdleTimeout seconds. |
1538 | ***********************************************************************/ |
1539 | void cleanSessions(void) |
1540 | { |
1541 | PPPoESession *cur, *next; |
1542 | cur = ActiveSessions; |
1543 | while(cur) { |
1544 | next = cur->next; |
1545 | if (Epoch - cur->epoch > IdleTimeout) { |
1546 | /* Send PADT to each peer */ |
1547 | relaySendError(CODE_PADT, cur->acHash->sesNum, |
1548 | cur->acHash->interface, |
1549 | cur->acHash->peerMac, NULL, |
1550 | "RP-PPPoE: Relay: Session exceeded idle timeout"); |
1551 | relaySendError(CODE_PADT, cur->clientHash->sesNum, |
1552 | cur->clientHash->interface, |
1553 | cur->clientHash->peerMac, NULL, |
1554 | "RP-PPPoE: Relay: Session exceeded idle timeout"); |
1555 | freeSession(cur, "Idle Timeout"); |
1556 | } |
1557 | cur = next; |
1558 | } |
1559 | } |
1560 |