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
16static char const RCSID[] =
17"$Id: common.c,v 1.1.1.1 2008/10/15 03:31:00 james26_jang 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***********************************************************************/
45int
46parsePacket(struct 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: printErr
90*%ARGUMENTS:
91* str -- error message
92*%RETURNS:
93* Nothing
94*%DESCRIPTION:
95* Prints a message to stderr and syslog.
96***********************************************************************/
97void
98printErr(char const *str)
99{
100    fprintf(stderr, "pppoe: %s\n", str);
101    syslog(LOG_ERR, "%s", str);
102}
103
104
105/**********************************************************************
106*%FUNCTION: strDup
107*%ARGUMENTS:
108* str -- string to copy
109*%RETURNS:
110* A malloc'd copy of str.  Exits if malloc fails.
111***********************************************************************/
112char *
113strDup(char const *str)
114{
115    char *copy = malloc(strlen(str)+1);
116    if (!copy) {
117	fatal("strdup failed");
118    }
119    strcpy(copy, str);
120    return copy;
121}
122
123/**********************************************************************
124*%FUNCTION: computeTCPChecksum
125*%ARGUMENTS:
126* ipHdr -- pointer to IP header
127* tcpHdr -- pointer to TCP header
128*%RETURNS:
129* The computed TCP checksum
130***********************************************************************/
131UINT16_t
132computeTCPChecksum(unsigned char *ipHdr, unsigned char *tcpHdr)
133{
134    UINT32_t sum = 0;
135    UINT16_t count = ipHdr[2] * 256 + ipHdr[3];
136    unsigned char *addr = tcpHdr;
137    unsigned char pseudoHeader[12];
138
139    /* Count number of bytes in TCP header and data */
140    count -= (ipHdr[0] & 0x0F) * 4;
141
142    memcpy(pseudoHeader, ipHdr+12, 8);
143    pseudoHeader[8] = 0;
144    pseudoHeader[9] = ipHdr[9];
145    pseudoHeader[10] = (count >> 8) & 0xFF;
146    pseudoHeader[11] = (count & 0xFF);
147
148    /* Checksum the pseudo-header */
149    sum += * (UINT16_t *) pseudoHeader;
150    sum += * ((UINT16_t *) (pseudoHeader+2));
151    sum += * ((UINT16_t *) (pseudoHeader+4));
152    sum += * ((UINT16_t *) (pseudoHeader+6));
153    sum += * ((UINT16_t *) (pseudoHeader+8));
154    sum += * ((UINT16_t *) (pseudoHeader+10));
155
156    /* Checksum the TCP header and data */
157    while (count > 1) {
158	sum += * (UINT16_t *) addr;
159	addr += 2;
160	count -= 2;
161    }
162    if (count > 0) {
163	sum += *addr;
164    }
165
166    while(sum >> 16) {
167	sum = (sum & 0xffff) + (sum >> 16);
168    }
169    return (UINT16_t) (~sum & 0xFFFF);
170}
171
172/**********************************************************************
173*%FUNCTION: clampMSS
174*%ARGUMENTS:
175* packet -- PPPoE session packet
176* dir -- either "incoming" or "outgoing"
177* clampMss -- clamp value
178*%RETURNS:
179* Nothing
180*%DESCRIPTION:
181* Clamps MSS option if TCP SYN flag is set.
182***********************************************************************/
183void
184clampMSS(struct PPPoEPacket *packet, char const *dir, int clampMss)
185{
186    unsigned char *tcpHdr;
187    unsigned char *ipHdr;
188    unsigned char *opt;
189    unsigned char *endHdr;
190    unsigned char *mssopt = NULL;
191    UINT16_t csum;
192
193    int len;
194
195    /* Is it IPv4? */
196    if (packet->payload[0] != 0x00 ||
197	packet->payload[1] != 0x21) {
198	/* Nope, ignore it */
199	return;
200    }
201
202    ipHdr = packet->payload + 2;
203
204    /* Is it too short? */
205    len = (int) ntohs(packet->length);
206    if (len < 42) {
207	/* 20 byte IP header; 20 byte TCP header; 2 byte PPP protocol */
208	return;
209    }
210
211    /* Verify once more that it's IPv4 */
212    if ((ipHdr[0] & 0xF0) != 0x40) {
213	return;
214    }
215
216    /* Is it a fragment that's not at the beginning of the packet? */
217    if ((ipHdr[6] & 0x1F) || ipHdr[7]) {
218	/* Yup, don't touch! */
219	return;
220    }
221    /* Is it TCP? */
222    if (ipHdr[9] != 0x06) {
223	return;
224    }
225
226    /* Get start of TCP header */
227    tcpHdr = ipHdr + (ipHdr[0] & 0x0F) * 4;
228
229    /* Is SYN set? */
230    if (!(tcpHdr[13] & 0x02)) {
231	return;
232    }
233
234    /* Compute and verify TCP checksum -- do not touch a packet with a bad
235       checksum */
236    csum = computeTCPChecksum(ipHdr, tcpHdr);
237    if (csum) {
238	syslog(LOG_ERR, "Bad TCP checksum %x", (unsigned int) csum);
239
240	/* Upper layers will drop it */
241	return;
242    }
243
244    /* Look for existing MSS option */
245    endHdr = tcpHdr + ((tcpHdr[12] & 0xF0) >> 2);
246    opt = tcpHdr + 20;
247    while (opt < endHdr) {
248	if (!*opt) break;	/* End of options */
249	switch(*opt) {
250	case 1:
251	    opt++;
252	    break;
253
254	case 2:
255	    if (opt[1] != 4) {
256		/* Something fishy about MSS option length. */
257		syslog(LOG_ERR,
258		       "Bogus length for MSS option (%u) from %u.%u.%u.%u",
259		       (unsigned int) opt[1],
260		       (unsigned int) ipHdr[12],
261		       (unsigned int) ipHdr[13],
262		       (unsigned int) ipHdr[14],
263		       (unsigned int) ipHdr[15]);
264		return;
265	    }
266	    mssopt = opt;
267	    break;
268	default:
269	    if (opt[1] < 2) {
270		/* Someone's trying to attack us? */
271		syslog(LOG_ERR,
272		       "Bogus TCP option length (%u) from %u.%u.%u.%u",
273		       (unsigned int) opt[1],
274		       (unsigned int) ipHdr[12],
275		       (unsigned int) ipHdr[13],
276		       (unsigned int) ipHdr[14],
277		       (unsigned int) ipHdr[15]);
278		return;
279	    }
280	    opt += (opt[1]);
281	    break;
282	}
283	/* Found existing MSS option? */
284	if (mssopt) break;
285    }
286
287    /* If MSS exists and it's low enough, do nothing */
288    if (mssopt) {
289	unsigned mss = mssopt[2] * 256 + mssopt[3];
290	if (mss <= clampMss) {
291	    return;
292	}
293
294	mssopt[2] = (((unsigned) clampMss) >> 8) & 0xFF;
295	mssopt[3] = ((unsigned) clampMss) & 0xFF;
296    } else {
297	/* No MSS option.  Don't add one; we'll have to use 536. */
298	return;
299    }
300
301    /* Recompute TCP checksum */
302    tcpHdr[16] = 0;
303    tcpHdr[17] = 0;
304    csum = computeTCPChecksum(ipHdr, tcpHdr);
305    (* (UINT16_t *) (tcpHdr+16)) = csum;
306}
307