From 0a121a8ecd6de894c14d60daf9da2022ec47405c Mon Sep 17 00:00:00 2001 From: Guillaume Cottenceau Date: Mon, 11 Jun 2001 13:49:39 +0000 Subject: Initial revision --- mdk-stage1/rp-pppoe/src/relay.c | 1541 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1541 insertions(+) create mode 100644 mdk-stage1/rp-pppoe/src/relay.c (limited to 'mdk-stage1/rp-pppoe/src/relay.c') diff --git a/mdk-stage1/rp-pppoe/src/relay.c b/mdk-stage1/rp-pppoe/src/relay.c new file mode 100644 index 000000000..9738cb8a8 --- /dev/null +++ b/mdk-stage1/rp-pppoe/src/relay.c @@ -0,0 +1,1541 @@ +/*********************************************************************** +* +* relay.c +* +* Implementation of PPPoE relay +* +* Copyright (C) 2001 Roaring Penguin Software Inc. +* +* This program may be distributed according to the terms of the GNU +* General Public License, version 2 or (at your option) any later version. +* +* $Id$ +* +***********************************************************************/ +static char const RCSID[] = +"$Id$"; + +#define _GNU_SOURCE 1 /* For SA_RESTART */ + +#include "relay.h" + +#include + +#ifdef HAVE_SYSLOG_H +#include +#endif + +#ifdef HAVE_GETOPT_H +#include +#endif + +#include +#include +#include + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + + +/* Interfaces (max MAX_INTERFACES) */ +PPPoEInterface Interfaces[MAX_INTERFACES]; +int NumInterfaces; + +/* Relay info */ +int NumSessions; +int MaxSessions; +PPPoESession *AllSessions; +PPPoESession *FreeSessions; +PPPoESession *ActiveSessions; + +SessionHash *AllHashes; +SessionHash *FreeHashes; +SessionHash *Buckets[HASHTAB_SIZE]; + +volatile unsigned int Epoch = 0; +volatile unsigned int CleanCounter = 0; + +/* How often to clean up stale sessions? */ +#define MIN_CLEAN_PERIOD 30 /* Minimum period to run cleaner */ +#define TIMEOUT_DIVISOR 20 /* How often to run cleaner per timeout period */ +unsigned int CleanPeriod = MIN_CLEAN_PERIOD; + +/* How long a session can be idle before it is cleaned up? */ +unsigned int IdleTimeout = MIN_CLEAN_PERIOD * TIMEOUT_DIVISOR; + +/* Pipe for breaking select() to initiate periodic cleaning */ +int CleanPipe[2]; + +/* Our relay: if_index followed by peer_mac */ +#define MY_RELAY_TAG_LEN (sizeof(int) + ETH_ALEN) + +/* Hack for daemonizing */ +#define CLOSEFD 64 + +/********************************************************************** +*%FUNCTION: keepDescriptor +*%ARGUMENTS: +* fd -- a file descriptor +*%RETURNS: +* 1 if descriptor should NOT be closed during daemonizing; 0 otherwise. +***********************************************************************/ +static int +keepDescriptor(int fd) +{ + int i; + if (fd == CleanPipe[0] || fd == CleanPipe[1]) return 1; + for (i=0; ipayload, tag, + ntohs(tag->length) + TAG_HDR_SIZE); +} + +/********************************************************************** +*%FUNCTION: insertBytes +*%ARGUMENTS: +* packet -- a PPPoE packet +* loc -- location at which to insert bytes of data +* bytes -- the data to insert +* len -- length of data to insert +*%RETURNS: +* -1 if no room in packet; len otherwise. +*%DESCRIPTION: +* Inserts "len" bytes of data at location "loc" in "packet", moving all +* other data up to make room. +***********************************************************************/ +int +insertBytes(PPPoEPacket *packet, + unsigned char *loc, + void const *bytes, + int len) +{ + int toMove; + int plen = ntohs(packet->length); + /* Sanity checks */ + if (loc < packet->payload || + loc > packet->payload + plen || + len + plen > MAX_PPPOE_PAYLOAD) { + return -1; + } + + toMove = (packet->payload + plen) - loc; + memmove(loc+len, loc, toMove); + memcpy(loc, bytes, len); + packet->length = htons(plen + len); + return len; +} + +/********************************************************************** +*%FUNCTION: removeBytes +*%ARGUMENTS: +* packet -- a PPPoE packet +* loc -- location at which to remove bytes of data +* len -- length of data to remove +*%RETURNS: +* -1 if there was a problem, len otherwise +*%DESCRIPTION: +* Removes "len" bytes of data from location "loc" in "packet", moving all +* other data down to close the gap +***********************************************************************/ +int +removeBytes(PPPoEPacket *packet, + unsigned char *loc, + int len) +{ + int toMove; + int plen = ntohs(packet->length); + /* Sanity checks */ + if (len < 0 || len > plen || + loc < packet->payload || + loc + len > packet->payload + plen) { + return -1; + } + + toMove = ((packet->payload + plen) - loc) - len; + memmove(loc, loc+len, toMove); + packet->length = htons(plen - len); + return len; +} + +/********************************************************************** +*%FUNCTION: usage +*%ARGUMENTS: +* argv0 -- program name +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Prints usage information and exits. +***********************************************************************/ +void +usage(char const *argv0) +{ + fprintf(stderr, "Usage: %s [options]\n", argv0); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -S if_name -- Specify interface for PPPoE Server\n"); + fprintf(stderr, " -C if_name -- Specify interface for PPPoE Client\n"); + fprintf(stderr, " -B if_name -- Specify interface for both clients and server\n"); + fprintf(stderr, " -n nsess -- Maxmimum number of sessions to relay\n"); + fprintf(stderr, " -i timeout -- Idle timeout in seconds (0 = no timeout)\n"); + fprintf(stderr, " -F -- Do not fork into background\n"); + fprintf(stderr, " -h -- Print this help message\n"); + + fprintf(stderr, "\nPPPoE Version %s, Copyright (C) 2001 Roaring Penguin Software Inc.\n", VERSION); + fprintf(stderr, "PPPoE comes with ABSOLUTELY NO WARRANTY.\n"); + fprintf(stderr, "This is free software, and you are welcome to redistribute it under the terms\n"); + fprintf(stderr, "of the GNU General Public License, version 2 or any later version.\n"); + fprintf(stderr, "http://www.roaringpenguin.com\n"); + exit(EXIT_SUCCESS); +} + +/********************************************************************** +*%FUNCTION: main +*%ARGUMENTS: +* argc, argv -- usual suspects +*%RETURNS: +* EXIT_SUCCESS or EXIT_FAILURE +*%DESCRIPTION: +* Main program. Options: +* -C ifname -- Use interface for PPPoE clients +* -S ifname -- Use interface for PPPoE servers +* -B ifname -- Use interface for both clients and servers +* -n sessions -- Maximum of "n" sessions +***********************************************************************/ +int +main(int argc, char *argv[]) +{ + int opt; + int nsess = DEFAULT_SESSIONS; + struct sigaction sa; + int beDaemon = 1; + openlog("pppoe-relay", LOG_PID, LOG_DAEMON); + + while((opt = getopt(argc, argv, "hC:S:B:n:i:F")) != -1) { + switch(opt) { + case 'h': + usage(argv[0]); + break; + case 'F': + beDaemon = 0; + break; + case 'C': + addInterface(optarg, 1, 0); + break; + case 'S': + addInterface(optarg, 0, 1); + break; + case 'B': + addInterface(optarg, 1, 1); + break; + case 'i': + if (sscanf(optarg, "%u", &IdleTimeout) != 1) { + fprintf(stderr, "Illegal argument to -i: should be -i timeout\n"); + exit(EXIT_FAILURE); + } + CleanPeriod = IdleTimeout / TIMEOUT_DIVISOR; + if (CleanPeriod < MIN_CLEAN_PERIOD) CleanPeriod = MIN_CLEAN_PERIOD; + break; + case 'n': + if (sscanf(optarg, "%d", &nsess) != 1) { + fprintf(stderr, "Illegal argument to -n: should be -n #sessions\n"); + exit(EXIT_FAILURE); + } + if (nsess < 1 || nsess > 65534) { + fprintf(stderr, "Illegal argument to -n: must range from 1 to 65534\n"); + exit(EXIT_FAILURE); + } + break; + default: + usage(argv[0]); + } + } + +#ifdef USE_LINUX_PACKET +#ifndef HAVE_STRUCT_SOCKADDR_LL + fprintf(stderr, "The PPPoE relay does not work on Linux 2.0 kernels.\n"); + exit(EXIT_FAILURE); +#endif +#endif + + /* Check that at least two interfaces were defined */ + if (NumInterfaces < 2) { + fprintf(stderr, "%s: Must define at least two interfaces\n", + argv[0]); + exit(EXIT_FAILURE); + } + + /* Make a pipe for the cleaner */ + if (pipe(CleanPipe) < 0) { + fatalSys("pipe"); + } + + /* Set up alarm handler */ + sa.sa_handler = alarmHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction(SIGALRM, &sa, NULL) < 0) { + fatalSys("sigaction"); + } + + /* Allocate memory for sessions, etc. */ + initRelay(nsess); + + /* Daemonize -- UNIX Network Programming, Vol. 1, Stevens */ + if (beDaemon) { + int i; + i = fork(); + if (i < 0) { + fatalSys("fork"); + } else if (i != 0) { + /* parent */ + exit(0); + } + setsid(); + signal(SIGHUP, SIG_IGN); + i = fork(); + if (i < 0) { + fatalSys("fork"); + } else if (i != 0) { + exit(0); + } + + chdir("/"); + closelog(); + for (i=0; i= MAX_INTERFACES) { + fprintf(stderr, "Too many interfaces (%d max)\n", + MAX_INTERFACES); + exit(EXIT_FAILURE); + } + i = &Interfaces[NumInterfaces++]; + strncpy(i->name, ifname, IFNAMSIZ); + i->name[IFNAMSIZ] = 0; + + i->discoverySock = openInterface(ifname, Eth_PPPOE_Discovery, i->mac); + i->sessionSock = openInterface(ifname, Eth_PPPOE_Session, NULL); + i->clientOK = clientOK; + i->acOK = acOK; +} + +/********************************************************************** +*%FUNCTION: initRelay +*%ARGUMENTS: +* nsess -- maximum allowable number of sessions +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Initializes relay hash table and session tables. +***********************************************************************/ +void +initRelay(int nsess) +{ + int i; + NumSessions = 0; + MaxSessions = nsess; + + AllSessions = calloc(MaxSessions, sizeof(PPPoESession)); + if (!AllSessions) { + rp_fatal("Unable to allocate memory for PPPoE session table"); + } + AllHashes = calloc(MaxSessions*2, sizeof(SessionHash)); + if (!AllHashes) { + rp_fatal("Unable to allocate memory for PPPoE hash table"); + } + + /* Initialize sessions in a linked list */ + AllSessions[0].prev = NULL; + if (MaxSessions > 1) { + AllSessions[0].next = &AllSessions[1]; + } else { + AllSessions[0].next = NULL; + } + for (i=1; i 1) { + AllSessions[MaxSessions-1].prev = &AllSessions[MaxSessions-2]; + AllSessions[MaxSessions-1].next = NULL; + } + + FreeSessions = AllSessions; + ActiveSessions = NULL; + + /* Initialize session numbers which we hand out */ + for (i=0; i= MaxSessions) { + printErr("Maximum number of sessions reached -- cannot create new session"); + return NULL; + } + + /* Grab a free session */ + sess = FreeSessions; + FreeSessions = sess->next; + NumSessions++; + + /* Link it to the active list */ + sess->next = ActiveSessions; + if (sess->next) { + sess->next->prev = sess; + } + ActiveSessions = sess; + sess->prev = NULL; + + sess->epoch = Epoch; + + /* Get two hash entries */ + acHash = FreeHashes; + cliHash = acHash->next; + FreeHashes = cliHash->next; + + acHash->peer = cliHash; + cliHash->peer = acHash; + + sess->acHash = acHash; + sess->clientHash = cliHash; + + acHash->interface = ac; + cliHash->interface = cli; + + memcpy(acHash->peerMac, acMac, ETH_ALEN); + acHash->sesNum = acSes; + acHash->ses = sess; + + memcpy(cliHash->peerMac, cliMac, ETH_ALEN); + cliHash->sesNum = sess->sesNum; + cliHash->ses = sess; + + addHash(acHash); + addHash(cliHash); + + /* Log */ + syslog(LOG_INFO, + "Opened session: server=%02x:%02x:%02x:%02x:%02x:%02x(%s:%d), client=%02x:%02x:%02x:%02x:%02x:%02x(%s:%d)", + acHash->peerMac[0], acHash->peerMac[1], + acHash->peerMac[2], acHash->peerMac[3], + acHash->peerMac[4], acHash->peerMac[5], + acHash->interface->name, + ntohs(acHash->sesNum), + cliHash->peerMac[0], cliHash->peerMac[1], + cliHash->peerMac[2], cliHash->peerMac[3], + cliHash->peerMac[4], cliHash->peerMac[5], + cliHash->interface->name, + ntohs(cliHash->sesNum)); + + return sess; +} + +/********************************************************************** +*%FUNCTION: freeSession +*%ARGUMENTS: +* ses -- session to free +* msg -- extra message to log on syslog. +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Frees data used by a PPPoE session -- adds hashes and session back +* to the free list +***********************************************************************/ +void +freeSession(PPPoESession *ses, char const *msg) +{ + syslog(LOG_INFO, + "Closed session: server=%02x:%02x:%02x:%02x:%02x:%02x(%s:%d), client=%02x:%02x:%02x:%02x:%02x:%02x(%s:%d): %s", + ses->acHash->peerMac[0], ses->acHash->peerMac[1], + ses->acHash->peerMac[2], ses->acHash->peerMac[3], + ses->acHash->peerMac[4], ses->acHash->peerMac[5], + ses->acHash->interface->name, + ntohs(ses->acHash->sesNum), + ses->clientHash->peerMac[0], ses->clientHash->peerMac[1], + ses->clientHash->peerMac[2], ses->clientHash->peerMac[3], + ses->clientHash->peerMac[4], ses->clientHash->peerMac[5], + ses->clientHash->interface->name, + ntohs(ses->clientHash->sesNum), msg); + + /* Unlink from active sessions */ + if (ses->prev) { + ses->prev->next = ses->next; + } else { + ActiveSessions = ses->next; + } + if (ses->next) { + ses->next->prev = ses->prev; + } + + /* Link onto free list -- this is a singly-linked list, so + we do not care about prev */ + ses->next = FreeSessions; + FreeSessions = ses; + + unhash(ses->acHash); + unhash(ses->clientHash); + NumSessions--; +} + +/********************************************************************** +*%FUNCTION: unhash +*%ARGUMENTS: +* sh -- session hash to free +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Frees a session hash -- takes it out of hash table and puts it on +* free list. +***********************************************************************/ +void +unhash(SessionHash *sh) +{ + unsigned int b = hash(sh->peerMac, sh->sesNum) % HASHTAB_SIZE; + if (sh->prev) { + sh->prev->next = sh->next; + } else { + Buckets[b] = sh->next; + } + + if (sh->next) { + sh->next->prev = sh->prev; + } + + /* Add to free list (singly-linked) */ + sh->next = FreeHashes; + FreeHashes = sh; +} + +/********************************************************************** +*%FUNCTION: addHash +*%ARGUMENTS: +* sh -- a session hash +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Adds a SessionHash to the hash table +***********************************************************************/ +void +addHash(SessionHash *sh) +{ + unsigned int b = hash(sh->peerMac, sh->sesNum) % HASHTAB_SIZE; + sh->next = Buckets[b]; + sh->prev = NULL; + if (sh->next) { + sh->next->prev = sh; + } + Buckets[b] = sh; +} + +/********************************************************************** +*%FUNCTION: hash +*%ARGUMENTS: +* mac -- an Ethernet address +* sesNum -- a session number +*%RETURNS: +* A hash value combining Ethernet address with session number. +* Currently very simplistic; we may need to experiment with different +* hash values. +***********************************************************************/ +unsigned int +hash(unsigned char const *mac, UINT16_t sesNum) +{ + unsigned int ans1 = + ((unsigned int) mac[0]) | + (((unsigned int) mac[1]) << 8) | + (((unsigned int) mac[2]) << 16) | + (((unsigned int) mac[3]) << 24); + unsigned int ans2 = + ((unsigned int) sesNum) | + (((unsigned int) mac[4]) << 16) | + (((unsigned int) mac[5]) << 24); + return ans1 ^ ans2; +} + +/********************************************************************** +*%FUNCTION: findSession +*%ARGUMENTS: +* mac -- an Ethernet address +* sesNum -- a session number +*%RETURNS: +* The session hash for peer address "mac", session number sesNum +***********************************************************************/ +SessionHash * +findSession(unsigned char const *mac, UINT16_t sesNum) +{ + unsigned int b = hash(mac, sesNum) % HASHTAB_SIZE; + SessionHash *sh = Buckets[b]; + while(sh) { + if (!memcmp(mac, sh->peerMac, ETH_ALEN) && sesNum == sh->sesNum) { + return sh; + } + sh = sh->next; + } + return NULL; +} + +/********************************************************************** +*%FUNCTION: fatalSys +*%ARGUMENTS: +* str -- error message +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Prints a message plus the errno value to stderr and syslog and exits. +***********************************************************************/ +void +fatalSys(char const *str) +{ + char buf[1024]; + sprintf(buf, "%.256s: %.256s", str, strerror(errno)); + printErr(buf); + exit(EXIT_FAILURE); +} + +/********************************************************************** +*%FUNCTION: sysErr +*%ARGUMENTS: +* str -- error message +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Prints a message plus the errno value to syslog. +***********************************************************************/ +void +sysErr(char const *str) +{ + char buf[1024]; + sprintf(buf, "%.256s: %.256s", str, strerror(errno)); + printErr(buf); +} + +/********************************************************************** +*%FUNCTION: rp_fatal +*%ARGUMENTS: +* str -- error message +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Prints a message to stderr and syslog and exits. +***********************************************************************/ +void +rp_fatal(char const *str) +{ + printErr(str); + exit(EXIT_FAILURE); +} + +/********************************************************************** +*%FUNCTION: relayLoop +*%ARGUMENTS: +* None +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Runs the relay loop. This function never returns +***********************************************************************/ +void +relayLoop() +{ + fd_set readable, readableCopy; + int maxFD; + int i, r; + int sock; + + /* Build the select set */ + FD_ZERO(&readable); + maxFD = 0; + for (i=0; i maxFD) maxFD = sock; + FD_SET(sock, &readable); + sock = Interfaces[i].sessionSock; + if (sock > maxFD) maxFD = sock; + FD_SET(sock, &readable); + if (CleanPipe[0] > maxFD) maxFD = CleanPipe[0]; + FD_SET(CleanPipe[0], &readable); + } + maxFD++; + for(;;) { + readableCopy = readable; + for(;;) { + r = select(maxFD, &readableCopy, NULL, NULL, NULL); + if (r >= 0 || errno != EINTR) break; + } + if (r < 0) { + sysErr("select (relayLoop)"); + continue; + } + + /* Handle session packets first */ + for (i=0; idiscoverySock, &packet, &size) < 0) { + return; + } + /* Ignore unknown code/version */ + if (packet.ver != 1 || packet.type != 1) { + return; + } + + /* Validate length */ + if (ntohs(packet.length) + HDR_SIZE > size) { + syslog(LOG_ERR, "Bogus PPPoE length field (%u)", + (unsigned int) ntohs(packet.length)); + return; + } + + /* Drop Ethernet frame padding */ + if (size > ntohs(packet.length) + HDR_SIZE) { + size = ntohs(packet.length) + HDR_SIZE; + } + + switch(packet.code) { + case CODE_PADT: + relayHandlePADT(iface, &packet, size); + break; + case CODE_PADI: + relayHandlePADI(iface, &packet, size); + break; + case CODE_PADO: + relayHandlePADO(iface, &packet, size); + break; + case CODE_PADR: + relayHandlePADR(iface, &packet, size); + break; + case CODE_PADS: + relayHandlePADS(iface, &packet, size); + break; + default: + syslog(LOG_ERR, "Discovery packet on %s with unknown code %d", + iface->name, (int) packet.code); + } +} + +/********************************************************************** +*%FUNCTION: relayGotSessionPacket +*%ARGUMENTS: +* iface -- interface on which packet is waiting +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Receives and processes a session packet. +***********************************************************************/ +void +relayGotSessionPacket(PPPoEInterface const *iface) +{ + PPPoEPacket packet; + int size; + SessionHash *sh; + PPPoESession *ses; + + if (receivePacket(iface->sessionSock, &packet, &size) < 0) { + return; + } + + /* Ignore unknown code/version */ + if (packet.ver != 1 || packet.type != 1) { + return; + } + + /* Must be a session packet */ + if (packet.code != CODE_SESS) { + syslog(LOG_ERR, "Session packet with code %d", (int) packet.code); + return; + } + + /* Ignore session packets whose destination address isn't ours */ + if (memcmp(packet.ethHdr.h_dest, iface->mac, ETH_ALEN)) { + return; + } + + /* Validate length */ + if (ntohs(packet.length) + HDR_SIZE > size) { + syslog(LOG_ERR, "Bogus PPPoE length field (%u)", + (unsigned int) ntohs(packet.length)); + return; + } + + /* Drop Ethernet frame padding */ + if (size > ntohs(packet.length) + HDR_SIZE) { + size = ntohs(packet.length) + HDR_SIZE; + } + + /* We're in business! Find the hash */ + sh = findSession(packet.ethHdr.h_source, packet.session); + if (!sh) { + /* Don't log this. Someone could be running the client and the + relay on the same box. */ + return; + } + + /* Relay it */ + ses = sh->ses; + ses->epoch = Epoch; + sh = sh->peer; + packet.session = sh->sesNum; + memcpy(packet.ethHdr.h_source, sh->interface->mac, ETH_ALEN); + memcpy(packet.ethHdr.h_dest, sh->peerMac, ETH_ALEN); +#if 0 + fprintf(stderr, "Relaying %02x:%02x:%02x:%02x:%02x:%02x(%s:%d) to %02x:%02x:%02x:%02x:%02x:%02x(%s:%d)\n", + sh->peer->peerMac[0], sh->peer->peerMac[1], sh->peer->peerMac[2], + sh->peer->peerMac[3], sh->peer->peerMac[4], sh->peer->peerMac[5], + sh->peer->interface->name, ntohs(sh->peer->sesNum), + sh->peerMac[0], sh->peerMac[1], sh->peerMac[2], + sh->peerMac[3], sh->peerMac[4], sh->peerMac[5], + sh->interface->name, ntohs(sh->sesNum)); +#endif + sendPacket(NULL, sh->interface->sessionSock, &packet, size); +} + +/********************************************************************** +*%FUNCTION: relayHandlePADT +*%ARGUMENTS: +* iface -- interface on which packet was received +* packet -- the PADT packet +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Receives and processes a PADT packet. +***********************************************************************/ +void +relayHandlePADT(PPPoEInterface const *iface, + PPPoEPacket *packet, + int size) +{ + SessionHash *sh; + PPPoESession *ses; + + sh = findSession(packet->ethHdr.h_source, packet->session); + if (!sh) { + return; + } + /* Relay the PADT to the peer */ + sh = sh->peer; + ses = sh->ses; + packet->session = sh->sesNum; + memcpy(packet->ethHdr.h_source, sh->interface->mac, ETH_ALEN); + memcpy(packet->ethHdr.h_dest, sh->peerMac, ETH_ALEN); + sendPacket(NULL, sh->interface->sessionSock, packet, size); + + /* Destroy the session */ + freeSession(ses, "Received PADT"); +} + +/********************************************************************** +*%FUNCTION: relayHandlePADI +*%ARGUMENTS: +* iface -- interface on which packet was received +* packet -- the PADI packet +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Receives and processes a PADI packet. +***********************************************************************/ +void +relayHandlePADI(PPPoEInterface const *iface, + PPPoEPacket *packet, + int size) +{ + PPPoETag tag; + unsigned char *loc; + int i, r; + + int ifIndex; + + /* Can a client legally be behind this interface? */ + if (!iface->clientOK) { + syslog(LOG_ERR, + "PADI packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not permitted", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Source address must be unicast */ + if (NOT_UNICAST(packet->ethHdr.h_source)) { + syslog(LOG_ERR, + "PADI packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not from a unicast address", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Destination address must be broadcast */ + if (NOT_BROADCAST(packet->ethHdr.h_dest)) { + syslog(LOG_ERR, + "PADI packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not to a broadcast address", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Get array index of interface */ + ifIndex = iface - Interfaces; + + loc = findTag(packet, TAG_RELAY_SESSION_ID, &tag); + if (!loc) { + tag.type = htons(TAG_RELAY_SESSION_ID); + tag.length = htons(MY_RELAY_TAG_LEN); + memcpy(tag.payload, &ifIndex, sizeof(ifIndex)); + memcpy(tag.payload+sizeof(ifIndex), packet->ethHdr.h_source, ETH_ALEN); + /* Add a relay tag if there's room */ + r = addTag(packet, &tag); + if (r < 0) return; + size += r; + } else { + /* We do not re-use relay-id tags. Drop the frame. The RFC says the + relay agent SHOULD return a Generic-Error tag, but this does not + make sense for PADI packets. */ + return; + } + + /* Broadcast the PADI on all AC-capable interfaces except the interface + on which it came */ + for (i=0; i < NumInterfaces; i++) { + if (iface == &Interfaces[i]) continue; + if (!Interfaces[i].acOK) continue; + memcpy(packet->ethHdr.h_source, Interfaces[i].mac, ETH_ALEN); + sendPacket(NULL, Interfaces[i].discoverySock, packet, size); + } + +} + +/********************************************************************** +*%FUNCTION: relayHandlePADO +*%ARGUMENTS: +* iface -- interface on which packet was received +* packet -- the PADO packet +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Receives and processes a PADO packet. +***********************************************************************/ +void +relayHandlePADO(PPPoEInterface const *iface, + PPPoEPacket *packet, + int size) +{ + PPPoETag tag; + unsigned char *loc; + int ifIndex; + int acIndex; + + /* Can a server legally be behind this interface? */ + if (!iface->acOK) { + syslog(LOG_ERR, + "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not permitted", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + acIndex = iface - Interfaces; + + /* Source address must be unicast */ + if (NOT_UNICAST(packet->ethHdr.h_source)) { + syslog(LOG_ERR, + "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not from a unicast address", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Destination address must be interface's MAC address */ + if (memcmp(packet->ethHdr.h_dest, iface->mac, ETH_ALEN)) { + return; + } + + /* Find relay tag */ + loc = findTag(packet, TAG_RELAY_SESSION_ID, &tag); + if (!loc) { + syslog(LOG_ERR, + "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* If it's the wrong length, ignore it */ + if (ntohs(tag.length) != MY_RELAY_TAG_LEN) { + syslog(LOG_ERR, + "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have correct length Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Extract interface index */ + memcpy(&ifIndex, tag.payload, sizeof(ifIndex)); + + if (ifIndex < 0 || ifIndex >= NumInterfaces || + !Interfaces[ifIndex].clientOK || + iface == &Interfaces[ifIndex]) { + syslog(LOG_ERR, + "PADO packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s has invalid interface in Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Replace Relay-ID tag with opposite-direction tag */ + memcpy(loc+TAG_HDR_SIZE, &acIndex, sizeof(acIndex)); + memcpy(loc+TAG_HDR_SIZE+sizeof(ifIndex), packet->ethHdr.h_source, ETH_ALEN); + + /* Set destination address to MAC address in relay ID */ + memcpy(packet->ethHdr.h_dest, tag.payload + sizeof(ifIndex), ETH_ALEN); + + /* Set source address to MAC address of interface */ + memcpy(packet->ethHdr.h_source, Interfaces[ifIndex].mac, ETH_ALEN); + + /* Send the PADO to the proper client */ + sendPacket(NULL, Interfaces[ifIndex].discoverySock, packet, size); +} + +/********************************************************************** +*%FUNCTION: relayHandlePADR +*%ARGUMENTS: +* iface -- interface on which packet was received +* packet -- the PADR packet +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Receives and processes a PADR packet. +***********************************************************************/ +void +relayHandlePADR(PPPoEInterface const *iface, + PPPoEPacket *packet, + int size) +{ + PPPoETag tag; + unsigned char *loc; + int ifIndex; + int cliIndex; + + /* Can a client legally be behind this interface? */ + if (!iface->clientOK) { + syslog(LOG_ERR, + "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not permitted", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + cliIndex = iface - Interfaces; + + /* Source address must be unicast */ + if (NOT_UNICAST(packet->ethHdr.h_source)) { + syslog(LOG_ERR, + "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not from a unicast address", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Destination address must be interface's MAC address */ + if (memcmp(packet->ethHdr.h_dest, iface->mac, ETH_ALEN)) { + return; + } + + /* Find relay tag */ + loc = findTag(packet, TAG_RELAY_SESSION_ID, &tag); + if (!loc) { + syslog(LOG_ERR, + "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* If it's the wrong length, ignore it */ + if (ntohs(tag.length) != MY_RELAY_TAG_LEN) { + syslog(LOG_ERR, + "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have correct length Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Extract interface index */ + memcpy(&ifIndex, tag.payload, sizeof(ifIndex)); + + if (ifIndex < 0 || ifIndex >= NumInterfaces || + !Interfaces[ifIndex].acOK || + iface == &Interfaces[ifIndex]) { + syslog(LOG_ERR, + "PADR packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s has invalid interface in Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Replace Relay-ID tag with opposite-direction tag */ + memcpy(loc+TAG_HDR_SIZE, &cliIndex, sizeof(cliIndex)); + memcpy(loc+TAG_HDR_SIZE+sizeof(ifIndex), packet->ethHdr.h_source, ETH_ALEN); + + /* Set destination address to MAC address in relay ID */ + memcpy(packet->ethHdr.h_dest, tag.payload + sizeof(ifIndex), ETH_ALEN); + + /* Set source address to MAC address of interface */ + memcpy(packet->ethHdr.h_source, Interfaces[ifIndex].mac, ETH_ALEN); + + /* Send the PADR to the proper access concentrator */ + sendPacket(NULL, Interfaces[ifIndex].discoverySock, packet, size); +} + +/********************************************************************** +*%FUNCTION: relayHandlePADS +*%ARGUMENTS: +* iface -- interface on which packet was received +* packet -- the PADS packet +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Receives and processes a PADS packet. +***********************************************************************/ +void +relayHandlePADS(PPPoEInterface const *iface, + PPPoEPacket *packet, + int size) +{ + PPPoETag tag; + unsigned char *loc; + int ifIndex; + int acIndex; + PPPoESession *ses = NULL; + SessionHash *sh; + + /* Can a server legally be behind this interface? */ + if (!iface->acOK) { + syslog(LOG_ERR, + "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not permitted", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + acIndex = iface - Interfaces; + + /* Source address must be unicast */ + if (NOT_UNICAST(packet->ethHdr.h_source)) { + syslog(LOG_ERR, + "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s not from a unicast address", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Destination address must be interface's MAC address */ + if (memcmp(packet->ethHdr.h_dest, iface->mac, ETH_ALEN)) { + return; + } + + /* Find relay tag */ + loc = findTag(packet, TAG_RELAY_SESSION_ID, &tag); + if (!loc) { + syslog(LOG_ERR, + "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* If it's the wrong length, ignore it */ + if (ntohs(tag.length) != MY_RELAY_TAG_LEN) { + syslog(LOG_ERR, + "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s does not have correct length Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* Extract interface index */ + memcpy(&ifIndex, tag.payload, sizeof(ifIndex)); + + if (ifIndex < 0 || ifIndex >= NumInterfaces || + !Interfaces[ifIndex].clientOK || + iface == &Interfaces[ifIndex]) { + syslog(LOG_ERR, + "PADS packet from %02x:%02x:%02x:%02x:%02x:%02x on interface %s has invalid interface in Relay-Session-Id tag", + packet->ethHdr.h_source[0], + packet->ethHdr.h_source[1], + packet->ethHdr.h_source[2], + packet->ethHdr.h_source[3], + packet->ethHdr.h_source[4], + packet->ethHdr.h_source[5], + iface->name); + return; + } + + /* If session ID is zero, it's the AC respoding with an error. + Just relay it; do not create a session */ + if (packet->session != htons(0)) { + /* Check for existing session */ + sh = findSession(packet->ethHdr.h_source, packet->session); + if (sh) ses = sh->ses; + + /* If already an existing session, assume it's a duplicate PADS. Send + the frame, but do not create a new session. Is this the right + thing to do? Arguably, should send an error to the client and + a PADT to the server, because this could happen due to a + server crash and reboot. */ + + if (!ses) { + /* Create a new session */ + ses = createSession(iface, &Interfaces[ifIndex], + packet->ethHdr.h_source, + loc + TAG_HDR_SIZE + sizeof(ifIndex), packet->session); + if (!ses) { + /* Can't allocate session -- send error PADS to client and + PADT to server */ + PPPoETag hostUniq, *hu; + if (findTag(packet, TAG_HOST_UNIQ, &hostUniq)) { + hu = &hostUniq; + } else { + hu = NULL; + } + relaySendError(CODE_PADS, htons(0), &Interfaces[ifIndex], + loc + TAG_HDR_SIZE + sizeof(ifIndex), + hu, "RP-PPPoE: Relay: Unable to allocate session"); + relaySendError(CODE_PADT, packet->session, iface, + packet->ethHdr.h_source, NULL, + "RP-PPPoE: Relay: Unable to allocate session"); + return; + } + } + /* Replace session number */ + packet->session = ses->sesNum; + } + + /* Remove relay-ID tag */ + removeBytes(packet, loc, MY_RELAY_TAG_LEN + TAG_HDR_SIZE); + size -= (MY_RELAY_TAG_LEN + TAG_HDR_SIZE); + + /* Set destination address to MAC address in relay ID */ + memcpy(packet->ethHdr.h_dest, tag.payload + sizeof(ifIndex), ETH_ALEN); + + /* Set source address to MAC address of interface */ + memcpy(packet->ethHdr.h_source, Interfaces[ifIndex].mac, ETH_ALEN); + + /* Send the PADS to the proper client */ + sendPacket(NULL, Interfaces[ifIndex].discoverySock, packet, size); +} + +/********************************************************************** +*%FUNCTION: relaySendError +*%ARGUMENTS: +* code -- PPPoE packet code (PADS or PADT, typically) +* session -- PPPoE session number +* iface -- interface on which to send frame +* mac -- Ethernet address to which frame should be sent +* hostUniq -- if non-NULL, a hostUniq tag to add to error frame +* errMsg -- error message to insert into Generic-Error tag. +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Sends either a PADS or PADT packet with a Generic-Error tag and an +* error message. +***********************************************************************/ +void +relaySendError(unsigned char code, + UINT16_t session, + PPPoEInterface const *iface, + unsigned char const *mac, + PPPoETag const *hostUniq, + char const *errMsg) +{ + PPPoEPacket packet; + PPPoETag errTag; + int size; + + memcpy(packet.ethHdr.h_source, iface->mac, ETH_ALEN); + memcpy(packet.ethHdr.h_dest, mac, ETH_ALEN); + packet.ethHdr.h_proto = htons(Eth_PPPOE_Discovery); + packet.type = 1; + packet.ver = 1; + packet.code = code; + packet.session = session; + packet.length = htons(0); + if (hostUniq) { + if (addTag(&packet, hostUniq) < 0) return; + } + errTag.type = htons(TAG_GENERIC_ERROR); + errTag.length = htons(strlen(errMsg)); + strcpy(errTag.payload, errMsg); + if (addTag(&packet, &errTag) < 0) return; + size = ntohs(packet.length) + HDR_SIZE; + if (code == CODE_PADT) { + sendPacket(NULL, iface->discoverySock, &packet, size); + } else { + sendPacket(NULL, iface->sessionSock, &packet, size); + } +} + +/********************************************************************** +*%FUNCTION: alarmHandler +*%ARGUMENTS: +* sig -- signal number +*%RETURNS: +* Nothing +*%DESCRIPTION: +* SIGALRM handler. Increments Epoch; if necessary, writes a byte of +* data to the alarm pipe to trigger the stale-session cleaner. +***********************************************************************/ +void +alarmHandler(int sig) +{ + alarm(1); + Epoch++; + CleanCounter++; + if (CleanCounter == CleanPeriod) { + write(CleanPipe[1], "", 1); + } +} + +/********************************************************************** +*%FUNCTION: cleanSessions +*%ARGUMENTS: +* None +*%RETURNS: +* Nothing +*%DESCRIPTION: +* Goes through active sessions and cleans sessions idle for longer +* than IdleTimeout seconds. +***********************************************************************/ +void cleanSessions(void) +{ + PPPoESession *cur, *next; + cur = ActiveSessions; + while(cur) { + next = cur->next; + if (Epoch - cur->epoch > IdleTimeout) { + /* Send PADT to each peer */ + relaySendError(CODE_PADT, cur->acHash->sesNum, + cur->acHash->interface, + cur->acHash->peerMac, NULL, + "RP-PPPoE: Relay: Session exceeded idle timeout"); + relaySendError(CODE_PADT, cur->clientHash->sesNum, + cur->clientHash->interface, + cur->clientHash->peerMac, NULL, + "RP-PPPoE: Relay: Session exceeded idle timeout"); + freeSession(cur, "Idle Timeout"); + } + cur = next; + } +} -- cgit v1.2.1