/*********************************************************************** * * common.c * * Implementation of user-space PPPoE redirector for Linux. * * Common functions used by PPPoE client and server * * Copyright (C) 2000 by 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. * ***********************************************************************/ static char const RCSID[] = "$Id$"; #include "pppoe.h" #ifdef HAVE_SYSLOG_H #include #endif #include #include #include #ifdef HAVE_UNISTD_H #include #endif /********************************************************************** *%FUNCTION: parsePacket *%ARGUMENTS: * packet -- the PPPoE discovery packet to parse * func -- function called for each tag in the packet * extra -- an opaque data pointer supplied to parsing function *%RETURNS: * 0 if everything went well; -1 if there was an error *%DESCRIPTION: * Parses a PPPoE discovery packet, calling "func" for each tag in the packet. * "func" is passed the additional argument "extra". ***********************************************************************/ int parsePacket(PPPoEPacket *packet, ParseFunc *func, void *extra) { UINT16_t len = ntohs(packet->length); unsigned char *curTag; UINT16_t tagType, tagLen; if (packet->ver != 1) { syslog(LOG_ERR, "Invalid PPPoE version (%d)", (int) packet->ver); return -1; } if (packet->type != 1) { syslog(LOG_ERR, "Invalid PPPoE type (%d)", (int) packet->type); return -1; } /* Do some sanity checks on packet */ if (len > ETH_DATA_LEN - 6) { /* 6-byte overhead for PPPoE header */ syslog(LOG_ERR, "Invalid PPPoE packet length (%u)", len); return -1; } /* Step through the tags */ curTag = packet->payload; while(curTag - packet->payload < len) { /* Alignment is not guaranteed, so do this by hand... */ tagType = (((UINT16_t) curTag[0]) << 8) + (UINT16_t) curTag[1]; tagLen = (((UINT16_t) curTag[2]) << 8) + (UINT16_t) curTag[3]; if (tagType == TAG_END_OF_LIST) { return 0; } if ((curTag - packet->payload) + tagLen + TAG_HDR_SIZE > len) { syslog(LOG_ERR, "Invalid PPPoE tag length (%u)", tagLen); return -1; } func(tagType, tagLen, curTag+TAG_HDR_SIZE, extra); curTag = curTag + TAG_HDR_SIZE + tagLen; } return 0; } /********************************************************************** *%FUNCTION: findTag *%ARGUMENTS: * packet -- the PPPoE discovery packet to parse * type -- the type of the tag to look for * tag -- will be filled in with tag contents *%RETURNS: * A pointer to the tag if one of the specified type is found; NULL * otherwise. *%DESCRIPTION: * Looks for a specific tag type. ***********************************************************************/ unsigned char * findTag(PPPoEPacket *packet, UINT16_t type, PPPoETag *tag) { UINT16_t len = ntohs(packet->length); unsigned char *curTag; UINT16_t tagType, tagLen; if (packet->ver != 1) { syslog(LOG_ERR, "Invalid PPPoE version (%d)", (int) packet->ver); return NULL; } if (packet->type != 1) { syslog(LOG_ERR, "Invalid PPPoE type (%d)", (int) packet->type); return NULL; } /* Do some sanity checks on packet */ if (len > ETH_DATA_LEN - 6) { /* 6-byte overhead for PPPoE header */ syslog(LOG_ERR, "Invalid PPPoE packet length (%u)", len); return NULL; } /* Step through the tags */ curTag = packet->payload; while(curTag - packet->payload < len) { /* Alignment is not guaranteed, so do this by hand... */ tagType = (((UINT16_t) curTag[0]) << 8) + (UINT16_t) curTag[1]; tagLen = (((UINT16_t) curTag[2]) << 8) + (UINT16_t) curTag[3]; if (tagType == TAG_END_OF_LIST) { return NULL; } if ((curTag - packet->payload) + tagLen + TAG_HDR_SIZE > len) { syslog(LOG_ERR, "Invalid PPPoE tag length (%u)", tagLen); return NULL; } if (tagType == type) { memcpy(tag, curTag, tagLen + TAG_HDR_SIZE); return curTag; } curTag = curTag + TAG_HDR_SIZE + tagLen; } return NULL; } /********************************************************************** *%FUNCTION: printErr *%ARGUMENTS: * str -- error message *%RETURNS: * Nothing *%DESCRIPTION: * Prints a message to stderr and syslog. ***********************************************************************/ void printErr(char const *str) { fprintf(stderr, "pppoe: %s\n", str); syslog(LOG_ERR, "%s", str); } /********************************************************************** *%FUNCTION: strDup *%ARGUMENTS: * str -- string to copy *%RETURNS: * A malloc'd copy of str. Exits if malloc fails. ***********************************************************************/ char * strDup(char const *str) { char *copy = malloc(strlen(str)+1); if (!copy) { rp_fatal("strdup failed"); } strcpy(copy, str); return copy; } /********************************************************************** *%FUNCTION: computeTCPChecksum *%ARGUMENTS: * ipHdr -- pointer to IP header * tcpHdr -- pointer to TCP header *%RETURNS: * The computed TCP checksum ***********************************************************************/ UINT16_t computeTCPChecksum(unsigned char *ipHdr, unsigned char *tcpHdr) { UINT32_t sum = 0; UINT16_t count = ipHdr[2] * 256 + ipHdr[3]; unsigned char *addr = tcpHdr; unsigned char pseudoHeader[12]; /* Count number of bytes in TCP header and data */ count -= (ipHdr[0] & 0x0F) * 4; memcpy(pseudoHeader, ipHdr+12, 8); pseudoHeader[8] = 0; pseudoHeader[9] = ipHdr[9]; pseudoHeader[10] = (count >> 8) & 0xFF; pseudoHeader[11] = (count & 0xFF); /* Checksum the pseudo-header */ sum += * (UINT16_t *) pseudoHeader; sum += * ((UINT16_t *) (pseudoHeader+2)); sum += * ((UINT16_t *) (pseudoHeader+4)); sum += * ((UINT16_t *) (pseudoHeader+6)); sum += * ((UINT16_t *) (pseudoHeader+8)); sum += * ((UINT16_t *) (pseudoHeader+10)); /* Checksum the TCP header and data */ while (count > 1) { sum += * (UINT16_t *) addr; addr += 2; count -= 2; } if (count > 0) { sum += *addr; } while(sum >> 16) { sum = (sum & 0xffff) + (sum >> 16); } return (UINT16_t) (~sum & 0xFFFF); } /********************************************************************** *%FUNCTION: clampMSS *%ARGUMENTS: * packet -- PPPoE session packet * dir -- either "incoming" or "outgoing" * clampMss -- clamp value *%RETURNS: * Nothing *%DESCRIPTION: * Clamps MSS option if TCP SYN flag is set. ***********************************************************************/ void clampMSS(PPPoEPacket *packet, char const *dir, int clampMss) { unsigned char *tcpHdr; unsigned char *ipHdr; unsigned char *opt; unsigned char *endHdr; unsigned char *mssopt = NULL; UINT16_t csum; int len; /* Is it IPv4? */ if (packet->payload[0] != 0x00 || packet->payload[1] != 0x21) { /* Nope, ignore it */ return; } ipHdr = packet->payload + 2; /* Is it too short? */ len = (int) ntohs(packet->length); if (len < 42) { /* 20 byte IP header; 20 byte TCP header; 2 byte PPP protocol */ return; } /* Verify once more that it's IPv4 */ if ((ipHdr[0] & 0xF0) != 0x40) { return; } /* Is it a fragment that's not at the beginning of the packet? */ if ((ipHdr[6] & 0x1F) || ipHdr[7]) { /* Yup, don't touch! */ return; } /* Is it TCP? */ if (ipHdr[9] != 0x06) { return; } /* Get start of TCP header */ tcpHdr = ipHdr + (ipHdr[0] & 0x0F) * 4; /* Is SYN set? */ if (!(tcpHdr[13] & 0x02)) { return; } /* Compute and verify TCP checksum -- do not touch a packet with a bad checksum */ csum = computeTCPChecksum(ipHdr, tcpHdr); if (csum) { syslog(LOG_ERR, "Bad TCP checksum %x", (unsigned int) csum); /* Upper layers will drop it */ return; } /* Look for existing MSS option */ endHdr = tcpHdr + ((tcpHdr[12] & 0xF0) >> 2); opt = tcpHdr + 20; while (opt < endHdr) { if (!*opt) break; /* End of options */ switch(*opt) { case 1: opt++; break; case 2: if (opt[1] != 4) { /* Something fishy about MSS option length. */ syslog(LOG_ERR, "Bogus length for MSS option (%u) from %u.%u.%u.%u", (unsigned int) opt[1], (unsigned int) ipHdr[12], (unsigned int) ipHdr[13], (unsigned int) ipHdr[14], (unsigned int) ipHdr[15]); return; } mssopt = opt; break; default: if (opt[1] < 2) { /* Someone's trying to attack us? */ syslog(LOG_ERR, "Bogus TCP option length (%u) from %u.%u.%u.%u", (unsigned int) opt[1], (unsigned int) ipHdr[12], (unsigned int) ipHdr[13], (unsigned int) ipHdr[14], (unsigned int) ipHdr[15]); return; } opt += (opt[1]); break; } /* Found existing MSS option? */ if (mssopt) break; } /* If MSS exists and it's low enough, do nothing */ if (mssopt) { unsigned mss = mssopt[2] * 256 + mssopt[3]; if (mss <= clampMss) { return; } mssopt[2] = (((unsigned) clampMss) >> 8) & 0xFF; mssopt[3] = ((unsigned) clampMss) & 0xFF; } else { /* No MSS option. Don't add one; we'll have to use 536. */ return; } /* Recompute TCP checksum */ tcpHdr[16] = 0; tcpHdr[17] = 0; csum = computeTCPChecksum(ipHdr, tcpHdr); (* (UINT16_t *) (tcpHdr+16)) = csum; } /*********************************************************************** *%FUNCTION: sendPADT *%ARGUMENTS: * conn -- PPPoE connection * msg -- if non-NULL, extra error message to include in PADT packet. *%RETURNS: * Nothing *%DESCRIPTION: * Sends a PADT packet ***********************************************************************/ void sendPADT(PPPoEConnection *conn, char const *msg) { PPPoEPacket packet; unsigned char *cursor = packet.payload; UINT16_t plen = 0; /* Do nothing if no session established yet */ if (!conn->session) return; /* Do nothing if no discovery socket */ if (conn->discoverySocket < 0) return; memcpy(packet.ethHdr.h_dest, conn->peerEth, ETH_ALEN); memcpy(packet.ethHdr.h_source, conn->myEth, ETH_ALEN); packet.ethHdr.h_proto = htons(Eth_PPPOE_Discovery); packet.ver = 1; packet.type = 1; packet.code = CODE_PADT; packet.session = conn->session; /* Reset Session to zero so there is no possibility of recursive calls to this function by any signal handler */ conn->session = 0; /* If we're using Host-Uniq, copy it over */ if (conn->useHostUniq) { PPPoETag hostUniq; pid_t pid = getpid(); hostUniq.type = htons(TAG_HOST_UNIQ); hostUniq.length = htons(sizeof(pid)); memcpy(hostUniq.payload, &pid, sizeof(pid)); memcpy(cursor, &hostUniq, sizeof(pid) + TAG_HDR_SIZE); cursor += sizeof(pid) + TAG_HDR_SIZE; plen += sizeof(pid) + TAG_HDR_SIZE; } /* Copy error message */ if (msg) { PPPoETag err; size_t elen = strlen(msg); err.type = htons(TAG_GENERIC_ERROR); err.length = htons(elen); strcpy(err.payload, msg); memcpy(cursor, &err, elen + TAG_HDR_SIZE); cursor += elen + TAG_HDR_SIZE; plen += elen + TAG_HDR_SIZE; } /* Copy cookie and relay-ID if needed */ if (conn->cookie.type) { CHECK_ROOM(cursor, packet.payload, ntohs(conn->cookie.length) + TAG_HDR_SIZE); memcpy(cursor, &conn->cookie, ntohs(conn->cookie.length) + TAG_HDR_SIZE); cursor += ntohs(conn->cookie.length) + TAG_HDR_SIZE; plen += ntohs(conn->cookie.length) + TAG_HDR_SIZE; } if (conn->relayId.type) { CHECK_ROOM(cursor, packet.payload, ntohs(conn->relayId.length) + TAG_HDR_SIZE); memcpy(cursor, &conn->relayId, ntohs(conn->relayId.length) + TAG_HDR_SIZE); cursor += ntohs(conn->relayId.length) + TAG_HDR_SIZE; plen += ntohs(conn->relayId.length) + TAG_HDR_SIZE; } packet.length = htons(plen); sendPacket(conn, conn->discoverySocket, &packet, (int) (plen + HDR_SIZE)); if (conn->debugFile) { dumpPacket(conn->debugFile, &packet, "SENT"); fprintf(conn->debugFile, "\n"); fflush(conn->debugFile); } syslog(LOG_INFO,"Sent PADT"); } /********************************************************************** *%FUNCTION: parseLogErrs *%ARGUMENTS: * type -- tag type * len -- tag length * data -- tag data * extra -- extra user data *%RETURNS: * Nothing *%DESCRIPTION: * Picks error tags out of a packet and logs them. ***********************************************************************/ void parseLogErrs(UINT16_t type, UINT16_t len, unsigned char *data, void *extra) { switch(type) { case TAG_SERVICE_NAME_ERROR: syslog(LOG_ERR, "PADT: Service-Name-Error: %.*s", (int) len, data); fprintf(stderr, "PADT: Service-Name-Error: %.*s\n", (int) len, data); break; case TAG_AC_SYSTEM_ERROR: syslog(LOG_ERR, "PADT: System-Error: %.*s", (int) len, data); fprintf(stderr, "PADT: System-Error: %.*s\n", (int) len, data); break; case TAG_GENERIC_ERROR: syslog(LOG_ERR, "PADT: Generic-Error: %.*s", (int) len, data); fprintf(stderr, "PADT: Generic-Error: %.*s\n", (int) len, data); break; } }