summaryrefslogtreecommitdiffstats
path: root/mdk-stage1/rp-pppoe/src/pppoe-server.c
diff options
context:
space:
mode:
authorGuillaume Cottenceau <gc@mandriva.com>2001-06-11 13:49:39 +0000
committerGuillaume Cottenceau <gc@mandriva.com>2001-06-11 13:49:39 +0000
commit0a121a8ecd6de894c14d60daf9da2022ec47405c (patch)
tree3705a0c51f96ffdd2a0594ef43a5677c926eb0cc /mdk-stage1/rp-pppoe/src/pppoe-server.c
parentab5559aaabd1167a18ac882e64d97c5adc0e7d03 (diff)
downloaddrakx-backup-do-not-use-0a121a8ecd6de894c14d60daf9da2022ec47405c.tar
drakx-backup-do-not-use-0a121a8ecd6de894c14d60daf9da2022ec47405c.tar.gz
drakx-backup-do-not-use-0a121a8ecd6de894c14d60daf9da2022ec47405c.tar.bz2
drakx-backup-do-not-use-0a121a8ecd6de894c14d60daf9da2022ec47405c.tar.xz
drakx-backup-do-not-use-0a121a8ecd6de894c14d60daf9da2022ec47405c.zip
Initial revision
Diffstat (limited to 'mdk-stage1/rp-pppoe/src/pppoe-server.c')
-rw-r--r--mdk-stage1/rp-pppoe/src/pppoe-server.c1247
1 files changed, 1247 insertions, 0 deletions
diff --git a/mdk-stage1/rp-pppoe/src/pppoe-server.c b/mdk-stage1/rp-pppoe/src/pppoe-server.c
new file mode 100644
index 000000000..e43e63553
--- /dev/null
+++ b/mdk-stage1/rp-pppoe/src/pppoe-server.c
@@ -0,0 +1,1247 @@
+/***********************************************************************
+*
+* pppoe.h
+*
+* Implementation of a user-space PPPoE server
+*
+* Copyright (C) 2000 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$";
+
+#include "config.h"
+
+#if defined(HAVE_NETPACKET_PACKET_H) || defined(HAVE_LINUX_IF_PACKET_H)
+#define _POSIX_SOURCE 1 /* For sigaction defines */
+#endif
+
+#define _BSD_SOURCE 1 /* for gethostname */
+
+#include "pppoe.h"
+#include "md5.h"
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+#include <signal.h>
+
+/* Hack for daemonizing */
+#define CLOSEFD 64
+
+/* Max. 64 sessions by default */
+#define DEFAULT_MAX_SESSIONS 64
+
+/* A list of client sessions */
+struct ClientSession *Sessions = NULL;
+
+/* The number of session slots */
+size_t NumSessionSlots;
+
+/* Offset of first session */
+size_t SessOffset = 0;
+
+/* Socket for client's discovery phases */
+int Socket = -1;
+
+/* Pipe written on reception of SIGCHLD */
+int Pipe[2] = {-1, -1};
+int ReapPending = 0;
+
+/* Synchronous mode */
+int Synchronous = 0;
+
+/* Random seed for cookie generation */
+#define SEED_LEN 16
+#define MD5_LEN 16
+#define COOKIE_LEN (MD5_LEN + sizeof(pid_t)) /* Cookie is 16-byte MD5 + PID of server */
+
+unsigned char CookieSeed[SEED_LEN];
+
+/* Default interface if no -I option given */
+#define DEFAULT_IF "eth0"
+char *IfName = NULL;
+
+/* Access concentrator name */
+char *ACName = NULL;
+
+/* Options to pass to pppoe process */
+char PppoeOptions[SMALLBUF] = "";
+
+/* Our local IP address */
+unsigned char LocalIP[IPV4ALEN] = {10, 0, 0, 1};
+unsigned char RemoteIP[IPV4ALEN] = {10, 67, 15, 1}; /* Counter STARTS here */
+
+PPPoETag hostUniq;
+PPPoETag relayId;
+PPPoETag receivedCookie;
+PPPoETag requestedService;
+
+#define HOSTNAMELEN 256
+
+static void startPPPD(struct ClientSession *sess);
+static void sendErrorPADS(int sock, unsigned char *source, unsigned char *dest,
+ int errorTag, char *errorMsg);
+
+#define CHECK_ROOM(cursor, start, len) \
+do {\
+ if (((cursor)-(start))+(len) > MAX_PPPOE_PAYLOAD) { \
+ syslog(LOG_ERR, "Would create too-long packet"); \
+ return; \
+ } \
+} while(0)
+
+/* Use Linux kernel-mode PPPoE? */
+int UseLinuxKernelModePPPoE = 0;
+
+/**********************************************************************
+*%FUNCTION: parseAddressPool
+*%ARGUMENTS:
+* fname -- name of file containing IP address pool.
+* install -- if true, install IP addresses in sessions.
+*%RETURNS:
+* Number of valid IP addresses found.
+*%DESCRIPTION:
+* Reads a list of IP addresses from a file.
+***********************************************************************/
+static int
+parseAddressPool(char const *fname, int install)
+{
+ FILE *fp = fopen(fname, "r");
+ int numAddrs = 0;
+ unsigned int a, b, c, d;
+
+ if (!fp) {
+ sysErr("Cannot open address pool file");
+ }
+
+ while (!feof(fp)) {
+ if ((fscanf(fp, "%u.%u.%u.%u", &a, &b, &c, &d) == 4) &&
+ a < 256 && b < 256 && c < 256 && d < 256) {
+ if (install) {
+ Sessions[numAddrs].ip[0] = (unsigned char) a;
+ Sessions[numAddrs].ip[1] = (unsigned char) b;
+ Sessions[numAddrs].ip[2] = (unsigned char) c;
+ Sessions[numAddrs].ip[3] = (unsigned char) d;
+ }
+ numAddrs++;
+ }
+ }
+ if (!numAddrs) {
+ rp_fatal("No valid ip addresses found in pool file");
+ }
+ return numAddrs;
+}
+
+/**********************************************************************
+*%FUNCTION: parsePADITags
+*%ARGUMENTS:
+* type -- tag type
+* len -- tag length
+* data -- tag data
+* extra -- extra user data.
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Picks interesting tags out of a PADI packet
+***********************************************************************/
+void
+parsePADITags(UINT16_t type, UINT16_t len, unsigned char *data,
+ void *extra)
+{
+ switch(type) {
+ case TAG_SERVICE_NAME:
+ /* Should do something -- currently ignored */
+ break;
+ case TAG_RELAY_SESSION_ID:
+ relayId.type = htons(type);
+ relayId.length = htons(len);
+ memcpy(relayId.payload, data, len);
+ break;
+ case TAG_HOST_UNIQ:
+ hostUniq.type = htons(type);
+ hostUniq.length = htons(len);
+ memcpy(hostUniq.payload, data, len);
+ break;
+ }
+}
+
+/**********************************************************************
+*%FUNCTION: parsePADRTags
+*%ARGUMENTS:
+* type -- tag type
+* len -- tag length
+* data -- tag data
+* extra -- extra user data.
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Picks interesting tags out of a PADR packet
+***********************************************************************/
+void
+parsePADRTags(UINT16_t type, UINT16_t len, unsigned char *data,
+ void *extra)
+{
+ switch(type) {
+ case TAG_RELAY_SESSION_ID:
+ relayId.type = htons(type);
+ relayId.length = htons(len);
+ memcpy(relayId.payload, data, len);
+ break;
+ case TAG_HOST_UNIQ:
+ hostUniq.type = htons(type);
+ hostUniq.length = htons(len);
+ memcpy(hostUniq.payload, data, len);
+ break;
+ case TAG_AC_COOKIE:
+ receivedCookie.type = htons(type);
+ receivedCookie.length = htons(len);
+ memcpy(receivedCookie.payload, data, len);
+ break;
+ case TAG_SERVICE_NAME:
+ requestedService.type = htons(type);
+ requestedService.length = htons(len);
+ memcpy(requestedService.payload, data, len);
+ break;
+ }
+}
+
+/**********************************************************************
+*%FUNCTION: findSession
+*%ARGUMENTS:
+* pid -- PID of child which owns session. If PID is 0, searches for
+* empty session slots.
+*%RETURNS:
+* A pointer to the session, or NULL if no such session found.
+*%DESCRIPTION:
+* Searches for specified session.
+**********************************************************************/
+struct ClientSession *
+findSession(pid_t pid)
+{
+ size_t i;
+ for (i=0; i<NumSessionSlots; i++) {
+ if (Sessions[i].pid == pid) {
+ return &Sessions[i];
+ }
+ }
+ return NULL;
+}
+
+/**********************************************************************
+*%FUNCTION: reapSessions
+*%ARGUMENTS:
+* myAddr -- my Ethernet address
+* sock -- my discovery socket
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Reaps children which have exited and removes their sessions
+**********************************************************************/
+void
+reapSessions(unsigned char *myAddr, int sock)
+{
+ int status;
+ pid_t pid;
+ struct ClientSession *session;
+
+ /* Temporary structure for sending PADT's. */
+ PPPoEConnection conn;
+ memset(&conn, 0, sizeof(conn));
+
+ /* Initialize fields of conn which do not depend on peer */
+ memcpy(conn.myEth, myAddr, ETH_ALEN);
+ conn.useHostUniq = 0;
+ conn.discoverySocket = sock;
+
+ while((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ session = findSession(pid);
+ if (!session) {
+ syslog(LOG_ERR, "Child %d died but couldn't find session!",
+ (int) pid);
+ } else {
+ syslog(LOG_INFO,
+ "Session %d closed for client %02x:%02x:%02x:%02x:%02x:%02x (%d.%d.%d.%d)",
+ ntohs(session->sess),
+ session->eth[0], session->eth[1], session->eth[2],
+ session->eth[3], session->eth[4], session->eth[5],
+ (int) session->ip[0], (int) session->ip[1],
+ (int) session->ip[2], (int) session->ip[3]);
+ conn.session = session->sess;
+ memcpy(conn.peerEth, session->eth, ETH_ALEN);
+ if (session->recvdPADT) {
+ sendPADT(&conn, "RP-PPPoE: Received PADT from peer");
+ } else {
+ sendPADT(&conn, "RP-PPPoE: Child pppd process terminated");
+ }
+ session->pid = 0;
+ }
+ }
+}
+
+/**********************************************************************
+*%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[SMALLBUF];
+ snprintf(buf, SMALLBUF, "%s: %s", 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: genCookie
+*%ARGUMENTS:
+* peerEthAddr -- peer Ethernet address (6 bytes)
+* myEthAddr -- my Ethernet address (6 bytes)
+* seed -- random cookie seed to make things tasty (16 bytes)
+* cookie -- buffer which is filled with server PID and
+* md5 sum of previous items
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Forms the md5 sum of peer MAC address, our MAC address and seed, useful
+* in a PPPoE Cookie tag.
+***********************************************************************/
+void
+genCookie(unsigned char const *peerEthAddr,
+ unsigned char const *myEthAddr,
+ unsigned char const *seed,
+ unsigned char *cookie)
+{
+ struct MD5Context ctx;
+ pid_t pid = getpid();
+
+ MD5Init(&ctx);
+ MD5Update(&ctx, peerEthAddr, ETH_ALEN);
+ MD5Update(&ctx, myEthAddr, ETH_ALEN);
+ MD5Update(&ctx, seed, SEED_LEN);
+ MD5Final(cookie, &ctx);
+ memcpy(cookie+MD5_LEN, &pid, sizeof(pid));
+}
+
+/**********************************************************************
+*%FUNCTION: processPADI
+*%ARGUMENTS:
+* sock -- Ethernet socket
+* myAddr -- my Ethernet address
+* packet -- PPPoE PADI packet
+* len -- length of received packet
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Sends a PADO packet back to client
+***********************************************************************/
+void
+processPADI(int sock, unsigned char *myAddr,
+ PPPoEPacket *packet, int len)
+{
+ PPPoEPacket pado;
+ PPPoETag acname;
+ PPPoETag servname;
+ PPPoETag cookie;
+ size_t acname_len;
+ unsigned char *cursor = pado.payload;
+ UINT16_t plen;
+
+ /* Ignore PADI's which don't come from a unicast address */
+ if (NOT_UNICAST(packet->ethHdr.h_source)) {
+ syslog(LOG_ERR, "PADI packet from non-unicast source address");
+ return;
+ }
+
+ acname.type = htons(TAG_AC_NAME);
+ acname_len = strlen(ACName);
+ acname.length = htons(acname_len);
+ memcpy(acname.payload, ACName, acname_len);
+
+ servname.type = htons(TAG_SERVICE_NAME);
+ servname.length = 0;
+
+ relayId.type = 0;
+ hostUniq.type = 0;
+ parsePacket(packet, parsePADITags, NULL);
+
+ /* Generate a cookie */
+ cookie.type = htons(TAG_AC_COOKIE);
+ cookie.length = htons(COOKIE_LEN);
+ genCookie(packet->ethHdr.h_source, myAddr, CookieSeed, cookie.payload);
+
+ /* Construct a PADO packet */
+ memcpy(pado.ethHdr.h_dest, packet->ethHdr.h_source, ETH_ALEN);
+ memcpy(pado.ethHdr.h_source, myAddr, ETH_ALEN);
+ pado.ethHdr.h_proto = htons(Eth_PPPOE_Discovery);
+ pado.ver = 1;
+ pado.type = 1;
+ pado.code = CODE_PADO;
+ pado.session = 0;
+ plen = TAG_HDR_SIZE + acname_len;
+
+ CHECK_ROOM(cursor, pado.payload, acname_len+TAG_HDR_SIZE);
+ memcpy(cursor, &acname, acname_len + TAG_HDR_SIZE);
+ cursor += acname_len + TAG_HDR_SIZE;
+
+ CHECK_ROOM(cursor, pado.payload, TAG_HDR_SIZE);
+ memcpy(cursor, &servname, TAG_HDR_SIZE);
+ cursor += TAG_HDR_SIZE;
+ plen += TAG_HDR_SIZE;
+
+ CHECK_ROOM(cursor, pado.payload, TAG_HDR_SIZE + COOKIE_LEN);
+ memcpy(cursor, &cookie, TAG_HDR_SIZE + COOKIE_LEN);
+ cursor += TAG_HDR_SIZE + COOKIE_LEN;
+ plen += TAG_HDR_SIZE + COOKIE_LEN;
+
+ if (relayId.type) {
+ CHECK_ROOM(cursor, pado.payload, ntohs(relayId.length) + TAG_HDR_SIZE);
+ memcpy(cursor, &relayId, ntohs(relayId.length) + TAG_HDR_SIZE);
+ cursor += ntohs(relayId.length) + TAG_HDR_SIZE;
+ plen += ntohs(relayId.length) + TAG_HDR_SIZE;
+ }
+ if (hostUniq.type) {
+ CHECK_ROOM(cursor, pado.payload, ntohs(hostUniq.length)+TAG_HDR_SIZE);
+ memcpy(cursor, &hostUniq, ntohs(hostUniq.length) + TAG_HDR_SIZE);
+ cursor += ntohs(hostUniq.length) + TAG_HDR_SIZE;
+ plen += ntohs(hostUniq.length) + TAG_HDR_SIZE;
+ }
+ pado.length = htons(plen);
+ sendPacket(NULL, sock, &pado, (int) (plen + HDR_SIZE));
+}
+
+/**********************************************************************
+*%FUNCTION: processPADT
+*%ARGUMENTS:
+* sock -- Ethernet socket
+* myAddr -- my Ethernet address
+* packet -- PPPoE PADT packet
+* len -- length of received packet
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Kills session whose session-ID is in PADT packet.
+***********************************************************************/
+void
+processPADT(int sock, unsigned char *myAddr,
+ PPPoEPacket *packet, int len)
+{
+ size_t i;
+
+ /* Ignore PADT's not directed at us */
+ if (memcmp(packet->ethHdr.h_dest, myAddr, ETH_ALEN)) return;
+
+ /* Get session's index */
+ i = ntohs(packet->session) - 1 - SessOffset;
+ if (i >= NumSessionSlots) return;
+ if (Sessions[i].sess != packet->session) {
+ syslog(LOG_ERR, "Session index %u doesn't match session number %u",
+ (unsigned int) i, (unsigned int) ntohs(packet->session));
+ return;
+ }
+ if (Sessions[i].pid) {
+ Sessions[i].recvdPADT = 1;
+ parsePacket(packet, parseLogErrs, NULL);
+ kill(Sessions[i].pid, SIGTERM);
+ }
+}
+
+/**********************************************************************
+*%FUNCTION: processPADR
+*%ARGUMENTS:
+* sock -- Ethernet socket
+* myAddr -- my Ethernet address
+* packet -- PPPoE PADR packet
+* len -- length of received packet
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Sends a PADS packet back to client and starts a PPP session if PADR
+* packet is OK.
+***********************************************************************/
+void
+processPADR(int sock, unsigned char *myAddr,
+ PPPoEPacket *packet, int len)
+{
+ unsigned char cookieBuffer[COOKIE_LEN];
+ struct ClientSession *cliSession;
+ pid_t child;
+ PPPoEPacket pads;
+ unsigned char *cursor = pads.payload;
+ UINT16_t plen;
+ PPPoETag servname;
+
+ /* Initialize some globals */
+ relayId.type = 0;
+ hostUniq.type = 0;
+ receivedCookie.type = 0;
+ requestedService.type = 0;
+
+ /* Ignore PADR's not directed at us */
+ if (memcmp(packet->ethHdr.h_dest, myAddr, ETH_ALEN)) return;
+
+ /* Ignore PADR's from non-unicast addresses */
+ if (NOT_UNICAST(packet->ethHdr.h_source)) {
+ syslog(LOG_ERR, "PADR packet from non-unicast source address");
+ return;
+ }
+
+ parsePacket(packet, parsePADRTags, NULL);
+
+ /* Check that everything's cool */
+ if (!receivedCookie.type) {
+ /* Drop it -- do not send error PADS */
+ return;
+ }
+
+ /* Is cookie kosher? */
+ if (receivedCookie.length != htons(COOKIE_LEN)) {
+ /* Drop it -- do not send error PADS */
+ return;
+ }
+
+ genCookie(packet->ethHdr.h_source, myAddr, CookieSeed, cookieBuffer);
+ if (memcmp(receivedCookie.payload, cookieBuffer, COOKIE_LEN)) {
+ /* Drop it -- do not send error PADS */
+ return;
+ }
+
+ /* Check service name -- we only offer service "" */
+ if (!requestedService.type) {
+ syslog(LOG_ERR, "Received PADR packet with no SERVICE_NAME tag");
+ sendErrorPADS(sock, myAddr, packet->ethHdr.h_source,
+ TAG_SERVICE_NAME_ERROR, "RP-PPPoE: Server: No service name tag");
+ return;
+ }
+
+ if (requestedService.length) {
+ syslog(LOG_ERR, "Received PADR packet asking for unsupported service %.*s", (int) ntohs(requestedService.length), requestedService.payload);
+ sendErrorPADS(sock, myAddr, packet->ethHdr.h_source,
+ TAG_SERVICE_NAME_ERROR, "RP-PPPoE: Server: Invalid service name tag");
+ return;
+ }
+
+ /* Looks cool... find a slot for the session */
+ cliSession = findSession(0);
+ if (!cliSession) {
+ syslog(LOG_ERR, "No client slots available (%02x:%02x:%02x:%02x:%02x:%02x)",
+ (unsigned int) packet->ethHdr.h_source[0],
+ (unsigned int) packet->ethHdr.h_source[1],
+ (unsigned int) packet->ethHdr.h_source[2],
+ (unsigned int) packet->ethHdr.h_source[3],
+ (unsigned int) packet->ethHdr.h_source[4],
+ (unsigned int) packet->ethHdr.h_source[5]);
+ sendErrorPADS(sock, myAddr, packet->ethHdr.h_source,
+ TAG_AC_SYSTEM_ERROR, "RP-PPPoE: Server: No client slots available");
+ return;
+ }
+
+ /* Set up client session peer Ethernet address */
+ memcpy(cliSession->eth, packet->ethHdr.h_source, ETH_ALEN);
+ cliSession->recvdPADT = 0;
+
+ /* Create child process, send PADS packet back */
+ child = fork();
+ if (child < 0) {
+ sendErrorPADS(sock, myAddr, packet->ethHdr.h_source,
+ TAG_AC_SYSTEM_ERROR, "RP-PPPoE: Server: Unable to start session process");
+ return;
+ }
+ if (child != 0) {
+ /* In the parent process. Mark pid in session slot */
+ cliSession->pid = child;
+ return;
+ }
+
+ /* In the child process. */
+
+ /* pppd has a nasty habit of killing all processes in its process group.
+ Start a new session to stop pppd from killing us! */
+ setsid();
+
+ /* Send PADS and Start pppd */
+ memcpy(pads.ethHdr.h_dest, packet->ethHdr.h_source, ETH_ALEN);
+ memcpy(pads.ethHdr.h_source, myAddr, ETH_ALEN);
+ pads.ethHdr.h_proto = htons(Eth_PPPOE_Discovery);
+ pads.ver = 1;
+ pads.type = 1;
+ pads.code = CODE_PADS;
+
+ pads.session = cliSession->sess;
+ plen = 0;
+
+ servname.type = htons(TAG_SERVICE_NAME);
+ servname.length = 0;
+
+ memcpy(cursor, &servname, TAG_HDR_SIZE);
+ cursor += TAG_HDR_SIZE;
+ plen += TAG_HDR_SIZE;
+
+ if (relayId.type) {
+ memcpy(cursor, &relayId, ntohs(relayId.length) + TAG_HDR_SIZE);
+ cursor += ntohs(relayId.length) + TAG_HDR_SIZE;
+ plen += ntohs(relayId.length) + TAG_HDR_SIZE;
+ }
+ if (hostUniq.type) {
+ memcpy(cursor, &hostUniq, ntohs(hostUniq.length) + TAG_HDR_SIZE);
+ cursor += ntohs(hostUniq.length) + TAG_HDR_SIZE;
+ plen += ntohs(hostUniq.length) + TAG_HDR_SIZE;
+ }
+ pads.length = htons(plen);
+ sendPacket(NULL, sock, &pads, (int) (plen + HDR_SIZE));
+ startPPPD(cliSession);
+}
+
+/**********************************************************************
+*%FUNCTION: childHandler
+*%ARGUMENTS:
+* sig -- signal number
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Called by SIGCHLD. Writes one byte to Pipe to wake up the select
+* loop and cause reaping of dead sessions
+***********************************************************************/
+void
+childHandler(int sig)
+{
+ if (!ReapPending) {
+ ReapPending = 1;
+ write(Pipe[1], &ReapPending, 1);
+ }
+}
+
+/**********************************************************************
+*%FUNCTION: usage
+*%ARGUMENTS:
+* argv0 -- argv[0] from main
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Prints usage instructions
+***********************************************************************/
+void
+usage(char const *argv0)
+{
+ fprintf(stderr, "Usage: %s [options]\n", argv0);
+ fprintf(stderr, "Options:\n");
+#ifdef USE_BPF
+ fprintf(stderr, " -I if_name -- Specify interface (REQUIRED)\n");
+#else
+ fprintf(stderr, " -I if_name -- Specify interface (default %s.)\n",
+ DEFAULT_IF);
+#endif
+ fprintf(stderr, " -T timeout -- Specify inactivity timeout in seconds.\n");
+ fprintf(stderr, " -C name -- Set access concentrator name.\n");
+ fprintf(stderr, " -m MSS -- Clamp incoming and outgoing MSS options.\n");
+ fprintf(stderr, " -L ip -- Set local IP address.\n");
+ fprintf(stderr, " -R ip -- Set start address of remote IP pool.\n");
+ fprintf(stderr, " -p fname -- Optain IP address pool from specified file.\n");
+ fprintf(stderr, " -N num -- Allow 'num' concurrent sessions.\n");
+ fprintf(stderr, " -o offset -- Assign session numbers starting at offset+1.\n");
+ fprintf(stderr, " -f disc:sess -- Set Ethernet frame types (hex).\n");
+ fprintf(stderr, " -s -- Use synchronous PPP mode.\n");
+#ifdef HAVE_LINUX_KERNEL_PPPOE
+ fprintf(stderr, " -k -- Use kernel-mode PPPoE.\n");
+#endif
+ fprintf(stderr, " -h -- Print usage information.\n\n");
+ fprintf(stderr, "PPPoE-Server Version %s, Copyright (C) 2001 Roaring Penguin Software Inc.\n", VERSION);
+ fprintf(stderr, "PPPoE-Server comes with ABSOLUTELY NO WARRANTY.\n");
+ fprintf(stderr, "This is free software, and you are welcome to redistribute it\n");
+ fprintf(stderr, "under the terms of the GNU General Public License, version 2\n");
+ fprintf(stderr, "or (at your option) any later version.\n");
+ fprintf(stderr, "http://www.roaringpenguin.com\n");
+}
+
+/**********************************************************************
+*%FUNCTION: main
+*%ARGUMENTS:
+* argc, argv -- usual suspects
+*%RETURNS:
+* Exit status
+*%DESCRIPTION:
+* Main program of PPPoE server
+***********************************************************************/
+int
+main(int argc, char **argv)
+{
+
+ FILE *fp;
+ int i;
+ int opt;
+ unsigned char myAddr[ETH_ALEN];
+ PPPoEPacket packet;
+ int len;
+ int sock;
+ int d[IPV4ALEN];
+ int beDaemon = 1;
+ struct sigaction act;
+ int maxFD;
+ unsigned int discoveryType, sessionType;
+ char *addressPoolFname = NULL;
+
+#ifndef HAVE_LINUX_KERNEL_PPPOE
+ char *options = "hI:C:L:R:T:m:FN:f:o:sp:";
+#else
+ char *options = "hI:C:L:R:T:m:FN:f:o:skp:";
+#endif
+
+ /* Initialize syslog */
+ openlog("pppoe-server", LOG_PID, LOG_DAEMON);
+
+ /* Default number of session slots */
+ NumSessionSlots = DEFAULT_MAX_SESSIONS;
+
+ /* Parse command-line options */
+ while((opt = getopt(argc, argv, options)) != -1) {
+ switch(opt) {
+#ifdef HAVE_LINUX_KERNEL_PPPOE
+ case 'k':
+ UseLinuxKernelModePPPoE = 1;
+ break;
+#endif
+ case 'p':
+ addressPoolFname = optarg;
+ break;
+ case 's':
+ Synchronous = 1;
+ /* Pass the Synchronous option on to pppoe */
+ snprintf(PppoeOptions + strlen(PppoeOptions),
+ SMALLBUF-strlen(PppoeOptions),
+ " -s");
+ break;
+ case 'f':
+ if (sscanf(optarg, "%x:%x", &discoveryType, &sessionType) != 2) {
+ fprintf(stderr, "Illegal argument to -f: Should be disc:sess in hex\n");
+ exit(EXIT_FAILURE);
+ }
+ Eth_PPPOE_Discovery = (UINT16_t) discoveryType;
+ Eth_PPPOE_Session = (UINT16_t) sessionType;
+ /* This option gets passed to pppoe */
+ snprintf(PppoeOptions + strlen(PppoeOptions),
+ SMALLBUF-strlen(PppoeOptions),
+ " -%c %s", opt, optarg);
+ break;
+ case 'F':
+ beDaemon = 0;
+ break;
+ case 'N':
+ if (sscanf(optarg, "%d", &opt) != 1) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ if (opt <= 0) {
+ fprintf(stderr, "-N: Value must be positive\n");
+ exit(EXIT_FAILURE);
+ }
+ NumSessionSlots = opt;
+ break;
+ case 'o':
+ if (sscanf(optarg, "%d", &opt) != 1) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ if (opt < 0) {
+ fprintf(stderr, "-o: Value must be non-negative\n");
+ exit(EXIT_FAILURE);
+ }
+ SessOffset = (size_t) opt;
+ break;
+
+ case 'I':
+ SET_STRING(IfName, optarg);
+ break;
+ case 'C':
+ SET_STRING(ACName, optarg);
+ break;
+ case 'L':
+ case 'R':
+ /* Get local/remote IP address */
+ if (sscanf(optarg, "%d.%d.%d.%d", &d[0], &d[1], &d[2], &d[3]) != 4) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ for (i=0; i<IPV4ALEN; i++) {
+ if (d[i] < 0 || d[i] > 255) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ if (opt == 'L') {
+ LocalIP[i] = (unsigned char) d[i];
+ } else {
+ RemoteIP[i] = (unsigned char) d[i];
+ }
+ }
+ break;
+ case 'T':
+ case 'm':
+ /* These just get passed to pppoe */
+ snprintf(PppoeOptions + strlen(PppoeOptions),
+ SMALLBUF-strlen(PppoeOptions),
+ " -%c %s", opt, optarg);
+ break;
+ case 'h':
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+#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
+
+ if (!IfName) {
+ IfName = DEFAULT_IF;
+ }
+
+ if (!ACName) {
+ ACName = malloc(HOSTNAMELEN);
+ if (gethostname(ACName, HOSTNAMELEN) < 0) {
+ fatalSys("gethostname");
+ }
+ }
+
+ /* If address pool filename given, count number of addresses */
+ if (addressPoolFname) {
+ NumSessionSlots = parseAddressPool(addressPoolFname, 0);
+ }
+
+ /* Max 65534 - SessOffset sessions */
+ if (NumSessionSlots + SessOffset > 65534) {
+ fprintf(stderr, "-N and -o options must add up to at most 65534\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Allocate memory for sessions */
+ Sessions = calloc(NumSessionSlots, sizeof(struct ClientSession));
+ if (!Sessions) {
+ rp_fatal("Cannot allocate memory for session slots");
+ }
+
+ /* Fill in remote IP addresses from pool */
+ if (addressPoolFname) {
+ (void) parseAddressPool(addressPoolFname, 1);
+ }
+
+ /* For testing -- generate sequential remote IP addresses */
+ for(i=0; i<NumSessionSlots; i++) {
+ Sessions[i].pid = 0;
+ Sessions[i].sess = htons(i+1+SessOffset);
+
+ if (!addressPoolFname) {
+ memcpy(Sessions[i].ip, RemoteIP, sizeof(RemoteIP));
+
+ /* Increment IP */
+ RemoteIP[3]++;
+ if (!RemoteIP[3]) {
+ RemoteIP[3] = 0;
+ RemoteIP[2]++;
+ if (!RemoteIP[2]) {
+ RemoteIP[1]++;
+ if (!RemoteIP[1]) {
+ RemoteIP[0]++;
+ }
+ }
+ }
+ }
+ }
+
+ /* Daemonize -- UNIX Network Programming, Vol. 1, Stevens */
+ if (beDaemon) {
+ i = fork();
+ if (i < 0) {
+ fatalSys("fork");
+ } else if (i != 0) {
+ /* parent */
+ exit(EXIT_SUCCESS);
+ }
+ setsid();
+ signal(SIGHUP, SIG_IGN);
+ i = fork();
+ if (i < 0) {
+ fatalSys("fork");
+ } else if (i != 0) {
+ exit(EXIT_SUCCESS);
+ }
+
+ chdir("/");
+ closelog();
+ for (i=0; i<CLOSEFD; i++) close(i);
+ /* We nuked our syslog descriptor... */
+ openlog("pppoe-server", LOG_PID, LOG_DAEMON);
+ }
+
+ /* Initialize our random cookie. Try /dev/urandom; if that fails,
+ use PID and rand() */
+ fp = fopen("/dev/urandom", "r");
+ if (fp) {
+ fread(&CookieSeed, 1, SEED_LEN, fp);
+ fclose(fp);
+ } else {
+ CookieSeed[0] = getpid() & 0xFF;
+ CookieSeed[1] = (getpid() >> 8) & 0xFF;
+ for (i=2; i<SEED_LEN; i++) {
+ CookieSeed[i] = (rand() >> (i % 9)) & 0xFF;
+ }
+ }
+
+ sock = openInterface(IfName, Eth_PPPOE_Discovery, myAddr);
+
+ /* Set signal handler for SIGCHLD */
+ act.sa_handler = childHandler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_NOCLDSTOP | SA_RESTART;
+ if (sigaction(SIGCHLD, &act, NULL) < 0) {
+ fatalSys("sigaction");
+ }
+
+ /* Set up pipe for signal handler */
+ if (pipe(Pipe) < 0) {
+ fatalSys("pipe");
+ }
+
+ /* Main server loop */
+ maxFD = sock;
+ if (Pipe[0] > maxFD) maxFD = Pipe[0];
+ maxFD++;
+
+ for(;;) {
+ fd_set readable;
+ FD_ZERO(&readable);
+ FD_SET(sock, &readable);
+ FD_SET(Pipe[0], &readable);
+
+ while(1) {
+ i = select(maxFD, &readable, NULL, NULL, NULL);
+ if (i >= 0 || errno != EINTR) break;
+ }
+ if (i < 0) {
+ fatalSys("select");
+ }
+
+ if (FD_ISSET(Pipe[0], &readable)) {
+ /* Clear pipe */
+ char buf[SMALLBUF];
+ read(Pipe[0], buf, SMALLBUF);
+ }
+
+ if (ReapPending) {
+ ReapPending = 0;
+ reapSessions(myAddr, sock);
+ }
+ if (!FD_ISSET(sock, &readable)) {
+ continue;
+ }
+
+ if (receivePacket(sock, &packet, &len) < 0) {
+ continue;
+ }
+
+ /* Check length */
+ if (ntohs(packet.length) + HDR_SIZE > len) {
+ syslog(LOG_ERR, "Bogus PPPoE length field (%u)",
+ (unsigned int) ntohs(packet.length));
+ continue;
+ }
+
+ /* Sanity check on packet */
+ if (packet.ver != 1 || packet.type != 1) {
+ /* Syslog an error */
+ continue;
+ }
+ switch(packet.code) {
+ case CODE_PADI:
+ processPADI(sock, myAddr, &packet, len);
+ break;
+ case CODE_PADR:
+ processPADR(sock, myAddr, &packet, len);
+ break;
+ case CODE_PADT:
+ /* Kill the child */
+ processPADT(sock, myAddr, &packet, len);
+ break;
+ case CODE_SESS:
+ /* Ignore SESS -- children will handle them */
+ break;
+ case CODE_PADO:
+ case CODE_PADS:
+ /* Ignore PADO and PADS totally */
+ break;
+ default:
+ /* Syslog an error */
+ break;
+ }
+ }
+ return 0;
+}
+
+/**********************************************************************
+*%FUNCTION: sendErrorPADS
+*%ARGUMENTS:
+* sock -- socket to write to
+* source -- source Ethernet address
+* dest -- destination Ethernet address
+* errorTag -- error tag
+* errorMsg -- error message
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Sends a PADS packet with an error message
+***********************************************************************/
+void
+sendErrorPADS(int sock,
+ unsigned char *source,
+ unsigned char *dest,
+ int errorTag,
+ char *errorMsg)
+{
+ PPPoEPacket pads;
+ unsigned char *cursor = pads.payload;
+ UINT16_t plen;
+ PPPoETag err;
+ int elen = strlen(errorMsg);
+
+ memcpy(pads.ethHdr.h_dest, dest, ETH_ALEN);
+ memcpy(pads.ethHdr.h_source, source, ETH_ALEN);
+ pads.ethHdr.h_proto = htons(Eth_PPPOE_Discovery);
+ pads.ver = 1;
+ pads.type = 1;
+ pads.code = CODE_PADS;
+
+ pads.session = htons(0);
+ plen = 0;
+
+ err.type = htons(errorTag);
+ err.length = htons(elen);
+
+ memcpy(err.payload, errorMsg, elen);
+ memcpy(cursor, &err, TAG_HDR_SIZE+elen);
+ cursor += TAG_HDR_SIZE + elen;
+ plen += TAG_HDR_SIZE + elen;
+
+ if (relayId.type) {
+ memcpy(cursor, &relayId, ntohs(relayId.length) + TAG_HDR_SIZE);
+ cursor += ntohs(relayId.length) + TAG_HDR_SIZE;
+ plen += ntohs(relayId.length) + TAG_HDR_SIZE;
+ }
+ if (hostUniq.type) {
+ memcpy(cursor, &hostUniq, ntohs(hostUniq.length) + TAG_HDR_SIZE);
+ cursor += ntohs(hostUniq.length) + TAG_HDR_SIZE;
+ plen += ntohs(hostUniq.length) + TAG_HDR_SIZE;
+ }
+ pads.length = htons(plen);
+ sendPacket(NULL, sock, &pads, (int) (plen + HDR_SIZE));
+}
+
+
+/**********************************************************************
+*%FUNCTION: startPPPDUserMode
+*%ARGUMENTS:
+* session -- client session record
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Starts PPPD for user-mode PPPoE
+***********************************************************************/
+void
+startPPPDUserMode(struct ClientSession *session)
+{
+ /* Leave some room */
+ char *argv[20];
+
+ char buffer[SMALLBUF];
+
+ argv[0] = "pppd";
+ argv[1] = "pty";
+
+ snprintf(buffer, SMALLBUF, "%s -n -I %s -e %d:%02x:%02x:%02x:%02x:%02x:%02x%s",
+ PPPOE_PATH, IfName,
+ ntohs(session->sess),
+ session->eth[0], session->eth[1], session->eth[2],
+ session->eth[3], session->eth[4], session->eth[5],
+ PppoeOptions);
+ argv[2] = strdup(buffer);
+ if (!argv[2]) {
+ /* TODO: Send a PADT */
+ exit(EXIT_FAILURE);
+ }
+
+ argv[3] = "file";
+ argv[4] = PPPOE_SERVER_OPTIONS;
+
+ snprintf(buffer, SMALLBUF, "%d.%d.%d.%d:%d.%d.%d.%d",
+ (int) LocalIP[0], (int) LocalIP[1],
+ (int) LocalIP[2], (int) LocalIP[3],
+ (int) session->ip[0], (int) session->ip[1],
+ (int) session->ip[2], (int) session->ip[3]);
+ syslog(LOG_INFO,
+ "Session %d created for client %02x:%02x:%02x:%02x:%02x:%02x (%d.%d.%d.%d)",
+ ntohs(session->sess),
+ session->eth[0], session->eth[1], session->eth[2],
+ session->eth[3], session->eth[4], session->eth[5],
+ (int) session->ip[0], (int) session->ip[1],
+ (int) session->ip[2], (int) session->ip[3]);
+ argv[5] = buffer; /* No need for strdup -- about to execv! */
+ argv[6] = "nodetach";
+ argv[7] = "noaccomp";
+ argv[8] = "nobsdcomp";
+ argv[9] = "nodeflate";
+ argv[10] = "nopcomp";
+ argv[11] = "novj";
+ argv[12] = "novjccomp";
+ argv[13] = "default-asyncmap";
+ if (Synchronous) {
+ argv[14] = "sync";
+ argv[15] = NULL;
+ } else {
+ argv[14] = NULL;
+ }
+
+ execv(PPPD_PATH, argv);
+ exit(EXIT_FAILURE);
+}
+
+/**********************************************************************
+*%FUNCTION: startPPPDLinuxKernelMode
+*%ARGUMENTS:
+* session -- client session record
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Starts PPPD for kernel-mode PPPoE on Linux
+***********************************************************************/
+void
+startPPPDLinuxKernelMode(struct ClientSession *session)
+{
+ /* Leave some room */
+ char *argv[20];
+
+ char buffer[SMALLBUF];
+
+ argv[0] = "pppd";
+ argv[1] = "plugin";
+ argv[2] = PLUGIN_PATH;
+ argv[3] = IfName;
+ snprintf(buffer, SMALLBUF, "%d:%02x:%02x:%02x:%02x:%02x:%02x",
+ ntohs(session->sess),
+ session->eth[0], session->eth[1], session->eth[2],
+ session->eth[3], session->eth[4], session->eth[5]);
+ argv[4] = "rp_pppoe_sess";
+ argv[5] = strdup(buffer);
+ if (!argv[5]) {
+ /* TODO: Send a PADT */
+ exit(EXIT_FAILURE);
+ }
+ argv[6] = "file";
+ argv[7] = PPPOE_SERVER_OPTIONS;
+
+ snprintf(buffer, SMALLBUF, "%d.%d.%d.%d:%d.%d.%d.%d",
+ (int) LocalIP[0], (int) LocalIP[1],
+ (int) LocalIP[2], (int) LocalIP[3],
+ (int) session->ip[0], (int) session->ip[1],
+ (int) session->ip[2], (int) session->ip[3]);
+ syslog(LOG_INFO,
+ "Session %d created for client %02x:%02x:%02x:%02x:%02x:%02x (%d.%d.%d.%d)",
+ ntohs(session->sess),
+ session->eth[0], session->eth[1], session->eth[2],
+ session->eth[3], session->eth[4], session->eth[5],
+ (int) session->ip[0], (int) session->ip[1],
+ (int) session->ip[2], (int) session->ip[3]);
+ argv[8] = buffer;
+ argv[9] = "nodetach";
+ argv[10] = "noaccomp";
+ argv[11] = "nobsdcomp";
+ argv[12] = "nodeflate";
+ argv[13] = "nopcomp";
+ argv[14] = "novj";
+ argv[15] = "novjccomp";
+ argv[16] = "default-asyncmap";
+ argv[17] = NULL;
+ execv(PPPD_PATH, argv);
+ exit(EXIT_FAILURE);
+}
+
+/**********************************************************************
+*%FUNCTION: startPPPD
+*%ARGUMENTS:
+* session -- client session record
+*%RETURNS:
+* Nothing
+*%DESCRIPTION:
+* Starts PPPD
+***********************************************************************/
+void
+startPPPD(struct ClientSession *session)
+{
+ if (UseLinuxKernelModePPPoE) startPPPDLinuxKernelMode(session);
+ else startPPPDUserMode(session);
+}
+