Home | History | Annotate | Download | only in rp-pppoe
      1 /***********************************************************************
      2 *
      3 * common.c
      4 *
      5 * Implementation of user-space PPPoE redirector for Linux.
      6 *
      7 * Common functions used by PPPoE client and server
      8 *
      9 * Copyright (C) 2000 by Roaring Penguin Software Inc.
     10 *
     11 * This program may be distributed according to the terms of the GNU
     12 * General Public License, version 2 or (at your option) any later version.
     13 *
     14 ***********************************************************************/
     15 
     16 static char const RCSID[] =
     17 "$Id: common.c,v 1.2 2004/01/13 04:03:58 paulus Exp $";
     18 
     19 #include "pppoe.h"
     20 
     21 #ifdef HAVE_SYSLOG_H
     22 #include <syslog.h>
     23 #endif
     24 
     25 #include <string.h>
     26 #include <errno.h>
     27 #include <stdlib.h>
     28 
     29 #ifdef HAVE_UNISTD_H
     30 #include <unistd.h>
     31 #endif
     32 
     33 /**********************************************************************
     34 *%FUNCTION: parsePacket
     35 *%ARGUMENTS:
     36 * packet -- the PPPoE discovery packet to parse
     37 * func -- function called for each tag in the packet
     38 * extra -- an opaque data pointer supplied to parsing function
     39 *%RETURNS:
     40 * 0 if everything went well; -1 if there was an error
     41 *%DESCRIPTION:
     42 * Parses a PPPoE discovery packet, calling "func" for each tag in the packet.
     43 * "func" is passed the additional argument "extra".
     44 ***********************************************************************/
     45 int
     46 parsePacket(PPPoEPacket *packet, ParseFunc *func, void *extra)
     47 {
     48     UINT16_t len = ntohs(packet->length);
     49     unsigned char *curTag;
     50     UINT16_t tagType, tagLen;
     51 
     52     if (packet->ver != 1) {
     53 	syslog(LOG_ERR, "Invalid PPPoE version (%d)", (int) packet->ver);
     54 	return -1;
     55     }
     56     if (packet->type != 1) {
     57 	syslog(LOG_ERR, "Invalid PPPoE type (%d)", (int) packet->type);
     58 	return -1;
     59     }
     60 
     61     /* Do some sanity checks on packet */
     62     if (len > ETH_DATA_LEN - 6) { /* 6-byte overhead for PPPoE header */
     63 	syslog(LOG_ERR, "Invalid PPPoE packet length (%u)", len);
     64 	return -1;
     65     }
     66 
     67     /* Step through the tags */
     68     curTag = packet->payload;
     69     while(curTag - packet->payload < len) {
     70 	/* Alignment is not guaranteed, so do this by hand... */
     71 	tagType = (((UINT16_t) curTag[0]) << 8) +
     72 	    (UINT16_t) curTag[1];
     73 	tagLen = (((UINT16_t) curTag[2]) << 8) +
     74 	    (UINT16_t) curTag[3];
     75 	if (tagType == TAG_END_OF_LIST) {
     76 	    return 0;
     77 	}
     78 	if ((curTag - packet->payload) + tagLen + TAG_HDR_SIZE > len) {
     79 	    syslog(LOG_ERR, "Invalid PPPoE tag length (%u)", tagLen);
     80 	    return -1;
     81 	}
     82 	func(tagType, tagLen, curTag+TAG_HDR_SIZE, extra);
     83 	curTag = curTag + TAG_HDR_SIZE + tagLen;
     84     }
     85     return 0;
     86 }
     87 
     88 /**********************************************************************
     89 *%FUNCTION: findTag
     90 *%ARGUMENTS:
     91 * packet -- the PPPoE discovery packet to parse
     92 * type -- the type of the tag to look for
     93 * tag -- will be filled in with tag contents
     94 *%RETURNS:
     95 * A pointer to the tag if one of the specified type is found; NULL
     96 * otherwise.
     97 *%DESCRIPTION:
     98 * Looks for a specific tag type.
     99 ***********************************************************************/
    100 unsigned char *
    101 findTag(PPPoEPacket *packet, UINT16_t type, PPPoETag *tag)
    102 {
    103     UINT16_t len = ntohs(packet->length);
    104     unsigned char *curTag;
    105     UINT16_t tagType, tagLen;
    106 
    107     if (packet->ver != 1) {
    108 	syslog(LOG_ERR, "Invalid PPPoE version (%d)", (int) packet->ver);
    109 	return NULL;
    110     }
    111     if (packet->type != 1) {
    112 	syslog(LOG_ERR, "Invalid PPPoE type (%d)", (int) packet->type);
    113 	return NULL;
    114     }
    115 
    116     /* Do some sanity checks on packet */
    117     if (len > ETH_DATA_LEN - 6) { /* 6-byte overhead for PPPoE header */
    118 	syslog(LOG_ERR, "Invalid PPPoE packet length (%u)", len);
    119 	return NULL;
    120     }
    121 
    122     /* Step through the tags */
    123     curTag = packet->payload;
    124     while(curTag - packet->payload < len) {
    125 	/* Alignment is not guaranteed, so do this by hand... */
    126 	tagType = (((UINT16_t) curTag[0]) << 8) +
    127 	    (UINT16_t) curTag[1];
    128 	tagLen = (((UINT16_t) curTag[2]) << 8) +
    129 	    (UINT16_t) curTag[3];
    130 	if (tagType == TAG_END_OF_LIST) {
    131 	    return NULL;
    132 	}
    133 	if ((curTag - packet->payload) + tagLen + TAG_HDR_SIZE > len) {
    134 	    syslog(LOG_ERR, "Invalid PPPoE tag length (%u)", tagLen);
    135 	    return NULL;
    136 	}
    137 	if (tagType == type) {
    138 	    memcpy(tag, curTag, tagLen + TAG_HDR_SIZE);
    139 	    return curTag;
    140 	}
    141 	curTag = curTag + TAG_HDR_SIZE + tagLen;
    142     }
    143     return NULL;
    144 }
    145 
    146 /**********************************************************************
    147 *%FUNCTION: printErr
    148 *%ARGUMENTS:
    149 * str -- error message
    150 *%RETURNS:
    151 * Nothing
    152 *%DESCRIPTION:
    153 * Prints a message to stderr and syslog.
    154 ***********************************************************************/
    155 void
    156 printErr(char const *str)
    157 {
    158     fprintf(stderr, "pppoe: %s\n", str);
    159     syslog(LOG_ERR, "%s", str);
    160 }
    161 
    162 
    163 /**********************************************************************
    164 *%FUNCTION: strDup
    165 *%ARGUMENTS:
    166 * str -- string to copy
    167 *%RETURNS:
    168 * A malloc'd copy of str.  Exits if malloc fails.
    169 ***********************************************************************/
    170 char *
    171 strDup(char const *str)
    172 {
    173     char *copy = malloc(strlen(str)+1);
    174     if (!copy) {
    175 	rp_fatal("strdup failed");
    176     }
    177     strcpy(copy, str);
    178     return copy;
    179 }
    180 
    181 #ifdef PPPOE_STANDALONE
    182 /**********************************************************************
    183 *%FUNCTION: computeTCPChecksum
    184 *%ARGUMENTS:
    185 * ipHdr -- pointer to IP header
    186 * tcpHdr -- pointer to TCP header
    187 *%RETURNS:
    188 * The computed TCP checksum
    189 ***********************************************************************/
    190 UINT16_t
    191 computeTCPChecksum(unsigned char *ipHdr, unsigned char *tcpHdr)
    192 {
    193     UINT32_t sum = 0;
    194     UINT16_t count = ipHdr[2] * 256 + ipHdr[3];
    195     unsigned char *addr = tcpHdr;
    196     unsigned char pseudoHeader[12];
    197 
    198     /* Count number of bytes in TCP header and data */
    199     count -= (ipHdr[0] & 0x0F) * 4;
    200 
    201     memcpy(pseudoHeader, ipHdr+12, 8);
    202     pseudoHeader[8] = 0;
    203     pseudoHeader[9] = ipHdr[9];
    204     pseudoHeader[10] = (count >> 8) & 0xFF;
    205     pseudoHeader[11] = (count & 0xFF);
    206 
    207     /* Checksum the pseudo-header */
    208     sum += * (UINT16_t *) pseudoHeader;
    209     sum += * ((UINT16_t *) (pseudoHeader+2));
    210     sum += * ((UINT16_t *) (pseudoHeader+4));
    211     sum += * ((UINT16_t *) (pseudoHeader+6));
    212     sum += * ((UINT16_t *) (pseudoHeader+8));
    213     sum += * ((UINT16_t *) (pseudoHeader+10));
    214 
    215     /* Checksum the TCP header and data */
    216     while (count > 1) {
    217 	sum += * (UINT16_t *) addr;
    218 	addr += 2;
    219 	count -= 2;
    220     }
    221     if (count > 0) {
    222 	sum += *addr;
    223     }
    224 
    225     while(sum >> 16) {
    226 	sum = (sum & 0xffff) + (sum >> 16);
    227     }
    228     return (UINT16_t) (~sum & 0xFFFF);
    229 }
    230 
    231 /**********************************************************************
    232 *%FUNCTION: clampMSS
    233 *%ARGUMENTS:
    234 * packet -- PPPoE session packet
    235 * dir -- either "incoming" or "outgoing"
    236 * clampMss -- clamp value
    237 *%RETURNS:
    238 * Nothing
    239 *%DESCRIPTION:
    240 * Clamps MSS option if TCP SYN flag is set.
    241 ***********************************************************************/
    242 void
    243 clampMSS(PPPoEPacket *packet, char const *dir, int clampMss)
    244 {
    245     unsigned char *tcpHdr;
    246     unsigned char *ipHdr;
    247     unsigned char *opt;
    248     unsigned char *endHdr;
    249     unsigned char *mssopt = NULL;
    250     UINT16_t csum;
    251 
    252     int len, minlen;
    253 
    254     /* check PPP protocol type */
    255     if (packet->payload[0] & 0x01) {
    256         /* 8 bit protocol type */
    257 
    258         /* Is it IPv4? */
    259         if (packet->payload[0] != 0x21) {
    260             /* Nope, ignore it */
    261             return;
    262         }
    263 
    264         ipHdr = packet->payload + 1;
    265 	minlen = 41;
    266     } else {
    267         /* 16 bit protocol type */
    268 
    269         /* Is it IPv4? */
    270         if (packet->payload[0] != 0x00 ||
    271             packet->payload[1] != 0x21) {
    272             /* Nope, ignore it */
    273             return;
    274         }
    275 
    276         ipHdr = packet->payload + 2;
    277 	minlen = 42;
    278     }
    279 
    280     /* Is it too short? */
    281     len = (int) ntohs(packet->length);
    282     if (len < minlen) {
    283 	/* 20 byte IP header; 20 byte TCP header; at least 1 or 2 byte PPP protocol */
    284 	return;
    285     }
    286 
    287     /* Verify once more that it's IPv4 */
    288     if ((ipHdr[0] & 0xF0) != 0x40) {
    289 	return;
    290     }
    291 
    292     /* Is it a fragment that's not at the beginning of the packet? */
    293     if ((ipHdr[6] & 0x1F) || ipHdr[7]) {
    294 	/* Yup, don't touch! */
    295 	return;
    296     }
    297     /* Is it TCP? */
    298     if (ipHdr[9] != 0x06) {
    299 	return;
    300     }
    301 
    302     /* Get start of TCP header */
    303     tcpHdr = ipHdr + (ipHdr[0] & 0x0F) * 4;
    304 
    305     /* Is SYN set? */
    306     if (!(tcpHdr[13] & 0x02)) {
    307 	return;
    308     }
    309 
    310     /* Compute and verify TCP checksum -- do not touch a packet with a bad
    311        checksum */
    312     csum = computeTCPChecksum(ipHdr, tcpHdr);
    313     if (csum) {
    314 	syslog(LOG_ERR, "Bad TCP checksum %x", (unsigned int) csum);
    315 
    316 	/* Upper layers will drop it */
    317 	return;
    318     }
    319 
    320     /* Look for existing MSS option */
    321     endHdr = tcpHdr + ((tcpHdr[12] & 0xF0) >> 2);
    322     opt = tcpHdr + 20;
    323     while (opt < endHdr) {
    324 	if (!*opt) break;	/* End of options */
    325 	switch(*opt) {
    326 	case 1:
    327 	    opt++;
    328 	    break;
    329 
    330 	case 2:
    331 	    if (opt[1] != 4) {
    332 		/* Something fishy about MSS option length. */
    333 		syslog(LOG_ERR,
    334 		       "Bogus length for MSS option (%u) from %u.%u.%u.%u",
    335 		       (unsigned int) opt[1],
    336 		       (unsigned int) ipHdr[12],
    337 		       (unsigned int) ipHdr[13],
    338 		       (unsigned int) ipHdr[14],
    339 		       (unsigned int) ipHdr[15]);
    340 		return;
    341 	    }
    342 	    mssopt = opt;
    343 	    break;
    344 	default:
    345 	    if (opt[1] < 2) {
    346 		/* Someone's trying to attack us? */
    347 		syslog(LOG_ERR,
    348 		       "Bogus TCP option length (%u) from %u.%u.%u.%u",
    349 		       (unsigned int) opt[1],
    350 		       (unsigned int) ipHdr[12],
    351 		       (unsigned int) ipHdr[13],
    352 		       (unsigned int) ipHdr[14],
    353 		       (unsigned int) ipHdr[15]);
    354 		return;
    355 	    }
    356 	    opt += (opt[1]);
    357 	    break;
    358 	}
    359 	/* Found existing MSS option? */
    360 	if (mssopt) break;
    361     }
    362 
    363     /* If MSS exists and it's low enough, do nothing */
    364     if (mssopt) {
    365 	unsigned mss = mssopt[2] * 256 + mssopt[3];
    366 	if (mss <= clampMss) {
    367 	    return;
    368 	}
    369 
    370 	mssopt[2] = (((unsigned) clampMss) >> 8) & 0xFF;
    371 	mssopt[3] = ((unsigned) clampMss) & 0xFF;
    372     } else {
    373 	/* No MSS option.  Don't add one; we'll have to use 536. */
    374 	return;
    375     }
    376 
    377     /* Recompute TCP checksum */
    378     tcpHdr[16] = 0;
    379     tcpHdr[17] = 0;
    380     csum = computeTCPChecksum(ipHdr, tcpHdr);
    381     (* (UINT16_t *) (tcpHdr+16)) = csum;
    382 }
    383 #endif /* PPPOE_STANDALONE */
    384 
    385 /***********************************************************************
    386 *%FUNCTION: sendPADT
    387 *%ARGUMENTS:
    388 * conn -- PPPoE connection
    389 * msg -- if non-NULL, extra error message to include in PADT packet.
    390 *%RETURNS:
    391 * Nothing
    392 *%DESCRIPTION:
    393 * Sends a PADT packet
    394 ***********************************************************************/
    395 void
    396 sendPADT(PPPoEConnection *conn, char const *msg)
    397 {
    398     PPPoEPacket packet;
    399     unsigned char *cursor = packet.payload;
    400 
    401     UINT16_t plen = 0;
    402 
    403     /* Do nothing if no session established yet */
    404     if (!conn->session) return;
    405 
    406     /* Do nothing if no discovery socket */
    407     if (conn->discoverySocket < 0) return;
    408 
    409     memcpy(packet.ethHdr.h_dest, conn->peerEth, ETH_ALEN);
    410     memcpy(packet.ethHdr.h_source, conn->myEth, ETH_ALEN);
    411 
    412     packet.ethHdr.h_proto = htons(Eth_PPPOE_Discovery);
    413     packet.ver = 1;
    414     packet.type = 1;
    415     packet.code = CODE_PADT;
    416     packet.session = conn->session;
    417 
    418     /* Reset Session to zero so there is no possibility of
    419        recursive calls to this function by any signal handler */
    420     conn->session = 0;
    421 
    422     /* If we're using Host-Uniq, copy it over */
    423     if (conn->useHostUniq) {
    424 	PPPoETag hostUniq;
    425 	pid_t pid = getpid();
    426 	hostUniq.type = htons(TAG_HOST_UNIQ);
    427 	hostUniq.length = htons(sizeof(pid));
    428 	memcpy(hostUniq.payload, &pid, sizeof(pid));
    429 	memcpy(cursor, &hostUniq, sizeof(pid) + TAG_HDR_SIZE);
    430 	cursor += sizeof(pid) + TAG_HDR_SIZE;
    431 	plen += sizeof(pid) + TAG_HDR_SIZE;
    432     }
    433 
    434     /* Copy error message */
    435     if (msg) {
    436 	PPPoETag err;
    437 	size_t elen = strlen(msg);
    438 	err.type = htons(TAG_GENERIC_ERROR);
    439 	err.length = htons(elen);
    440 	strcpy(err.payload, msg);
    441 	memcpy(cursor, &err, elen + TAG_HDR_SIZE);
    442 	cursor += elen + TAG_HDR_SIZE;
    443 	plen += elen + TAG_HDR_SIZE;
    444     }
    445 
    446     /* Copy cookie and relay-ID if needed */
    447     if (conn->cookie.type) {
    448 	CHECK_ROOM(cursor, packet.payload,
    449 		   ntohs(conn->cookie.length) + TAG_HDR_SIZE);
    450 	memcpy(cursor, &conn->cookie, ntohs(conn->cookie.length) + TAG_HDR_SIZE);
    451 	cursor += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
    452 	plen += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
    453     }
    454 
    455     if (conn->relayId.type) {
    456 	CHECK_ROOM(cursor, packet.payload,
    457 		   ntohs(conn->relayId.length) + TAG_HDR_SIZE);
    458 	memcpy(cursor, &conn->relayId, ntohs(conn->relayId.length) + TAG_HDR_SIZE);
    459 	cursor += ntohs(conn->relayId.length) + TAG_HDR_SIZE;
    460 	plen += ntohs(conn->relayId.length) + TAG_HDR_SIZE;
    461     }
    462 
    463     packet.length = htons(plen);
    464     sendPacket(conn, conn->discoverySocket, &packet, (int) (plen + HDR_SIZE));
    465     if (conn->debugFile) {
    466 	dumpPacket(conn->debugFile, &packet, "SENT");
    467 	fprintf(conn->debugFile, "\n");
    468 	fflush(conn->debugFile);
    469     }
    470     syslog(LOG_INFO,"Sent PADT");
    471 }
    472 
    473 /**********************************************************************
    474 *%FUNCTION: parseLogErrs
    475 *%ARGUMENTS:
    476 * type -- tag type
    477 * len -- tag length
    478 * data -- tag data
    479 * extra -- extra user data
    480 *%RETURNS:
    481 * Nothing
    482 *%DESCRIPTION:
    483 * Picks error tags out of a packet and logs them.
    484 ***********************************************************************/
    485 void
    486 parseLogErrs(UINT16_t type, UINT16_t len, unsigned char *data,
    487 	     void *extra)
    488 {
    489     switch(type) {
    490     case TAG_SERVICE_NAME_ERROR:
    491 	syslog(LOG_ERR, "PADT: Service-Name-Error: %.*s", (int) len, data);
    492 	fprintf(stderr, "PADT: Service-Name-Error: %.*s\n", (int) len, data);
    493 	break;
    494     case TAG_AC_SYSTEM_ERROR:
    495 	syslog(LOG_ERR, "PADT: System-Error: %.*s", (int) len, data);
    496 	fprintf(stderr, "PADT: System-Error: %.*s\n", (int) len, data);
    497 	break;
    498     case TAG_GENERIC_ERROR:
    499 	syslog(LOG_ERR, "PADT: Generic-Error: %.*s", (int) len, data);
    500 	fprintf(stderr, "PADT: Generic-Error: %.*s\n", (int) len, data);
    501 	break;
    502     }
    503 }
    504 
    505