File: pptpgre.c

package info (click to toggle)
pptpd 1.3.0-2etch2
  • links: PTS
  • area: main
  • in suites: etch
  • size: 1,036 kB
  • ctags: 856
  • sloc: ansic: 5,338; sh: 613; perl: 157; makefile: 130
file content (496 lines) | stat: -rwxr-xr-x 14,446 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
/*
 * pptpgre.c
 *
 * originally by C. S. Ananian
 * Modified for PoPToP
 *
 * $Id: pptpgre.c,v 1.6 2005/08/02 11:33:31 quozl Exp $
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef __linux__
#define _GNU_SOURCE 1		/* broken arpa/inet.h */
#endif

#include "our_syslog.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#ifdef HAVE_SYS_UIO_H
#include <sys/uio.h>
#endif

#include "ppphdlc.h"
#include "pptpgre.h"
#include "pptpdefs.h"
#include "pptpctrl.h"
#include "defaults.h"
#include "pqueue.h"

#ifndef HAVE_STRERROR
#include "compat.h"
#endif

#define PACKET_MAX 8196

typedef int (*callback_t)(int cl, void *pack, unsigned int len);

/* test for a 32 bit counter overflow */
#define WRAPPED( curseq, lastseq) \
    ((((curseq) & 0xffffff00) == 0) && \
     (((lastseq) & 0xffffff00 ) == 0xffffff00))

static struct gre_state gre; 
gre_stats_t stats; 

static uint64_t time_now_usecs()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (tv.tv_sec * 1000000) + tv.tv_usec;
}

int pptp_gre_init(u_int32_t call_id_pair, int pty_fd, struct in_addr *inetaddrs)
{
	struct sockaddr_in addr;
	int gre_fd;

	/* Open IP protocol socket */
	gre_fd = socket(AF_INET, SOCK_RAW, PPTP_PROTO);
	if (gre_fd < 0) {
		syslog(LOG_ERR, "GRE: socket() failed");
		return -1;
	}

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_addr = inetaddrs[0];
	addr.sin_port = 0;
	if (bind(gre_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		syslog(LOG_ERR, "GRE: bind() failed: %s", strerror(errno));
		syslog(LOG_ERR, "GRE: continuing, but may not work if multi-homed");
	}

	addr.sin_family = AF_INET;
	addr.sin_addr = inetaddrs[1];
	addr.sin_port = 0;
	if (connect(gre_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		syslog(LOG_ERR, "GRE: connect() failed: %s", strerror(errno));
		return -1;
	}

	gre.ack_sent = gre.ack_recv = gre.seq_sent = 0;
	gre.seq_recv = 0xFFFFFFFF;
	/* seq_recv is -1, therefore next packet expected is seq 0,
	   to comply with RFC 2637: 'The sequence number for each
	   user session is set to zero at session startup.' */
				      
	gre.call_id_pair = call_id_pair;	       /* network byte order */
	return gre_fd;
}

/* ONE blocking read per call; dispatches all packets possible */
/* returns 0 on success, or <0 on read failure                 */
int decaps_hdlc(int fd, int (*cb) (int cl, void *pack, unsigned len), int cl)
{
	static unsigned char buffer[PACKET_MAX], copy[PACKET_MAX];
	static unsigned start = 0, end = 0;
	static unsigned len = 0, escape = 0;
	static u_int16_t fcs = PPPINITFCS16;
	static unsigned char err = 0;
	unsigned char c;
	int status;

	/* we do one read only, since it may block.  and only if the
	 * buffer is empty (start == end)
	 */

	if (fd == -1) {
		if(cb == NULL) {
			/* peek mode */
			return err ? -1 : 0;
		} else if (!err) {
			/* re-xmit and nothing queued */
			syslog(LOG_ERR, "GRE: Re-xmit called with nothing queued");
			return -1;
		}
	}

	if (!err) {
		/* All known data is processed.  This true unless the last
		 * network write failed.
		 */
		if ((status = read(fd, buffer, sizeof(buffer))) <= 0) {
			syslog(LOG_ERR, "GRE: read(fd=%d,buffer=%lx,len=%d) from PTY failed: status = %d error = %s%s",
			       fd, (unsigned long) buffer, sizeof(buffer), 
			       status, status ? strerror(errno) : "No error", 
			       errno != EIO ? "" : ", usually caused by unexpected termination of pppd, check option syntax and pppd logs");
			/* FAQ: mistakes in pppd option spelling in
			 * /etc/ppp/options.pptpd often cause EIO,
			 * with pppd not reporting the problem to any
			 * logs.  Termination of pppd by signal can
			 * *also* cause this situation. -- James Cameron
			 */
			return -1;
		}
		end = status;
		start = 0;
	} else {
		/* We're here because of a network write failure.  Try again.
		 * Then do what we would do normally and enter the loop as if
		 * just continuing the while(1).  Not sure that this ever
		 * really happens, but since we error-check status then we
		 * should have the code to handle an error :-)
		 */
		err = 0;
		if ((status = cb(cl, copy, len)) < 0) {
			syslog(LOG_ERR, "GRE: re-xmit failed from decaps_hdlc: %s", strerror(errno));
			err = 1;
			return status;	/* return error */
		}
		/* Great!  Let's do more! */
		fcs = PPPINITFCS16;
		len = 0;
		escape = 0;
	}

	while (1) {
		/* Infinite loop, we return when we're out of data */

		/* Check if out of data */
		if (start == end)
			return 0;

		/* Add to the packet up till the next HDLC_FLAG (start/end of
		 * packet marker).  Copy to 'copy', un-escape and checksum as we go.
		 */
		while (buffer[start] != HDLC_FLAG) {

			/* Dispose of 'too long' packets */
			if (len >= PACKET_MAX) {
				syslog(LOG_ERR, "GRE: Received too long packet from pppd.");
				while (buffer[start] != HDLC_FLAG && start < end)
					start++;
				if (start < end) {
					goto newpacket;
				} else
					return 0;
			}
			/* Read a character, un-escaping if needed */
			if (buffer[start] == HDLC_ESCAPE && !escape)
				escape = 1;
			else {
				if (escape) {
					copy[len] = c = buffer[start] ^ 0x20;
					escape = 0;
				} else
					copy[len] = c = buffer[start];
				fcs = (fcs >> 8) ^ fcstab[(fcs ^ c) & 0xff];
				len++;
			}
			start++;

			/* Check if out of data */
			if (start == end)
				return 0;
		}

		/* Found flag.  Skip past it */
		start++;

		/* Check for over-short packets and silently discard, as per RFC1662 */
		if ((len < 4) || (escape == 1)) {
			/* len == 0 is possible, we generate it :-) [using HDLC_ESCAPE at
			 * start and end of packet].  Others are worth recording.
			 */
			if (len && len < 4)
				syslog(LOG_ERR, "GRE: Received too short packet from pppd.");
			if (escape)
				syslog(LOG_ERR, "GRE: Received bad packet from pppd.");
			goto newpacket;
		}
		/* Check, then remove the 16-bit FCS checksum field */
		if (fcs != PPPGOODFCS16) {
			syslog(LOG_ERR, "GRE: Bad checksum from pppd.");
			goto newpacket;
		}
		len -= sizeof(u_int16_t);

		/* So now we have a packet of length 'len' in 'copy' */
		if ((status = cb(cl, copy, len)) < 0) {
			syslog(LOG_ERR, "GRE: xmit failed from decaps_hdlc: %s", strerror(errno));
			err = 1;
			return status;	/* return error */
		}
	      newpacket:
		/* Great!  Let's do more! */
		fcs = PPPINITFCS16;
		len = 0;
		escape = 0;
	}
}

#define seq_greater(A,B) ((A)>(B) || \
                         (((u_int32_t)(A)<0xff) && ((~((u_int32_t)(B)))<0xff)))

/* Macro used in encaps_hdlc().  add "val" to "dest" at position "pos",
 * incrementing "pos" to point after the added value.  set "tmp" to "val"
 * as a side-effect.
 */
#define ADD_CHAR(dest, pos, val, tmp) \
	tmp = (val); \
	if ((tmp<0x20) || (tmp==HDLC_FLAG) || (tmp==HDLC_ESCAPE)) { \
		dest[pos++]=HDLC_ESCAPE; \
		dest[pos++]=tmp^0x20; \
	} else \
		dest[pos++]=tmp

/* Make stripped packet into HDLC packet */
int encaps_hdlc(int fd, void *pack, unsigned len)
{
	unsigned char *source = (unsigned char *) pack;
	/* largest expansion possible - double all + double fcs + 2 flags */
	static unsigned char dest[2 * PACKET_MAX + 6];
	unsigned pos = 1, i;
	u_int16_t fcs;
	unsigned char c;

	fcs = PPPINITFCS16;

	/* make sure overflow is impossible so we don't have to bounds check
	 * in loop.  drop large packets.
	 */
	if (len > PACKET_MAX) {
		syslog(LOG_ERR, "GRE: Asked to encapsulate too large packet (len = %d)", len);
		return -1;
	}
	/* start character */
	dest[0] = HDLC_FLAG;

	/* escape the payload */
	for (i = 0; i < len; i++) {
		ADD_CHAR(dest, pos, source[i], c);
		fcs = (fcs >> 8) ^ fcstab[(fcs ^ c) & 0xff];
	}

	fcs ^= 0xFFFF;

	ADD_CHAR(dest, pos, fcs & 0xFF, c);
	ADD_CHAR(dest, pos, fcs >> 8, c);

	/* tack on the end-flag */
	dest[pos++] = HDLC_FLAG;

	/* now write this packet */
	return write(fd, dest, pos);
}

#undef ADD_CHAR


static int dequeue_gre (callback_t callback, int cl)
{
	pqueue_t *head;
	int status;
	/* process packets in the queue that either are expected or
	   have timed out. */
	head = pqueue_head();
	while ( head != NULL &&
		( (head->seq == gre.seq_recv + 1) || /* wrap-around safe */
		  (pqueue_expiry_time(head) <= 0)
		  )
		) {
		/* if it is timed out... */
		if (head->seq != gre.seq_recv + 1 ) {  /* wrap-around safe */
			stats.rx_lost += head->seq - gre.seq_recv - 1;
			if (pptpctrl_debug) {
				syslog(LOG_DEBUG, "GRE: timeout waiting for %d packets", head->seq - gre.seq_recv - 1);
			}
		}
		if (pptpctrl_debug) {
			syslog(LOG_DEBUG, "GRE: accepting #%d from queue", head->seq);
		}
		gre.seq_recv = head->seq;
		status = callback(cl, head->packet, head->packlen);
		pqueue_del(head);
		if (status < 0) return status;
		head = pqueue_head();
	}
	return 0;
}


int decaps_gre(int fd, int (*cb) (int cl, void *pack, unsigned len), int cl)
{
	static unsigned char buffer[PACKET_MAX + 64 /*ip header */ ];
	struct pptp_gre_header *header;
	int status, ip_len = 0;

	dequeue_gre(cb, cl);
	if ((status = read(fd, buffer, sizeof(buffer))) <= 0) {
		syslog(LOG_ERR, "GRE: read(fd=%d,buffer=%lx,len=%d) from network failed: status = %d error = %s",
		       fd, (unsigned long) buffer, sizeof(buffer), status, status ? strerror(errno) : "No error");
		stats.rx_errors++;
		return -1;
	}
	/* strip off IP header, if present */
	if ((buffer[0] & 0xF0) == 0x40)
		ip_len = (buffer[0] & 0xF) * 4;
	header = (struct pptp_gre_header *) (buffer + ip_len);

	/* verify packet (else discard) */
	if (((ntoh8(header->ver) & 0x7F) != PPTP_GRE_VER) ||	/* version should be 1   */
	    (ntoh16(header->protocol) != PPTP_GRE_PROTO) ||	/* GRE protocol for PPTP */
	    PPTP_GRE_IS_C(ntoh8(header->flags)) ||	/* flag C should be clear   */
	    PPTP_GRE_IS_R(ntoh8(header->flags)) ||	/* flag R should be clear   */
	    (!PPTP_GRE_IS_K(ntoh8(header->flags))) ||	/* flag K should be set     */
	    ((ntoh8(header->flags) & 0xF) != 0)) {	/* routing and recursion ctrl = 0  */
		/* if invalid, discard this packet */
		syslog(LOG_ERR, "GRE: Discarding packet by header check");
		stats.rx_invalid++;
		return 0;
	}
	if (header->call_id != GET_VALUE(PAC, gre.call_id_pair)) {
                /*
                 * Discard silently to allow more than one GRE tunnel from
                 * the same IP address in case clients are behind the
                 * firewall.
                 *
                 * syslog(LOG_ERR, "GRE: Discarding for incorrect call");
                 */
		return 0;
	}
	if (PPTP_GRE_IS_A(ntoh8(header->ver))) {	/* acknowledgement present */
		u_int32_t ack = (PPTP_GRE_IS_S(ntoh8(header->flags))) ?
			ntoh32(header->ack) : ntoh32(header->seq);
			/* ack in different place if S=0 */

		if (seq_greater(ack, gre.ack_recv))
			gre.ack_recv = ack;

		/* also handle sequence number wrap-around  */
		if (WRAPPED(ack,gre.ack_recv)) gre.ack_recv = ack;
		if (gre.ack_recv == stats.pt.seq) {
			int rtt = time_now_usecs() - stats.pt.time;
			stats.rtt = (stats.rtt + rtt) / 2;
		}
	}
	if (PPTP_GRE_IS_S(ntoh8(header->flags))) {	/* payload present */
		unsigned headersize = sizeof(*header);
		unsigned payload_len = ntoh16(header->payload_len);
		u_int32_t seq = ntoh32(header->seq);

		if (!PPTP_GRE_IS_A(ntoh8(header->ver)))
			headersize -= sizeof(header->ack);
		/* check for incomplete packet (length smaller than expected) */
		if (status - headersize < payload_len) {
			stats.rx_truncated++;
			return 0;
		}
		/* check for out-of-order sequence number */
		if (seq == gre.seq_recv + 1) {
			if (pptpctrl_debug) {
				syslog(LOG_DEBUG, "GRE: accepting packet #%d", seq);
			}
			stats.rx_accepted++;
			gre.seq_recv = seq;
			return cb(cl, buffer + ip_len + headersize, payload_len);
		} else if (seq == gre.seq_recv) {
			if (pptpctrl_debug) {
				syslog(LOG_DEBUG, "GRE: discarding duplicate or old packet #%d (expecting #%d)", seq, gre.seq_recv + 1);
			}
			return 0;	/* discard duplicate packets */
		} else {
			stats.rx_buffered++;
			if (pptpctrl_debug) {
				syslog(LOG_DEBUG, "GRE: buffering packet #%d (expecting #%d, lost or reordered)", seq, gre.seq_recv + 1);
			}
			pqueue_add(seq, buffer + ip_len + headersize, payload_len);
			return 0;	/* discard out-of-order packets */
		}
	}
	return 0;		/* ack, but no payload */
}


int encaps_gre(int fd, void *pack, unsigned len)
{
	static union {
		struct pptp_gre_header header;
		unsigned char buffer[PACKET_MAX + sizeof(struct pptp_gre_header)];
	} u;
	unsigned header_len;
#ifdef HAVE_WRITEV
	struct iovec iovec[2];
#endif

	if(fd == -1)
		/* peek mode */
		return (gre.ack_sent == gre.seq_recv) ? 0 : -1;

	/* package this up in a GRE shell. */
	u.header.flags = hton8(PPTP_GRE_FLAG_K);
	u.header.ver = hton8(PPTP_GRE_VER);
	u.header.protocol = hton16(PPTP_GRE_PROTO);
	u.header.payload_len = hton16(len);
	u.header.call_id = GET_VALUE(PNS, gre.call_id_pair);

	/* special case ACK with no payload */
	if (pack == NULL) {
		if (gre.ack_sent != gre.seq_recv) {
			u.header.ver |= hton8(PPTP_GRE_FLAG_A);
			u.header.payload_len = hton16(0);
			u.header.seq = hton32(gre.seq_recv);	/* ack is in odd place because S=0 */
			gre.ack_sent = gre.seq_recv;
			/* don't sent ACK field, ACK is in SYN field */
			return write(fd, u.buffer, sizeof(u.header) - sizeof(u.header.ack));
		} else
			return 0;	/* we don't need to send ACK */
	}
	/* send packet with payload */
	u.header.flags |= hton8(PPTP_GRE_FLAG_S);
	u.header.seq = hton32(gre.seq_sent);
	gre.seq_sent++;
	if (gre.ack_sent != gre.seq_recv) {	/* send ack with this message */
		u.header.ver |= hton8(PPTP_GRE_FLAG_A);
		u.header.ack = hton32(gre.seq_recv);
		gre.ack_sent = gre.seq_recv;
		header_len = sizeof(u.header);
	} else {	/* don't send ack */
		header_len = sizeof(u.header) - sizeof(u.header.ack);
	}
	if (len > PACKET_MAX) {
		syslog(LOG_ERR, "GRE: packet is too large %d", len);
		stats.tx_oversize++;
		return 0;	/* drop this, it's too big */
	}
#ifdef HAVE_WRITEV
	/* write header and buffer without copying. */
	iovec[0].iov_base = u.buffer;
	iovec[0].iov_len = header_len;
	iovec[1].iov_base = pack;
	iovec[1].iov_len = len;
	return writev(fd, iovec, 2);
#else
	/* copy payload into buffer */
	memcpy(u.buffer + header_len, pack, len);
	/* record and increment sequence numbers */
	/* write this baby out to the net */
	return write(fd, u.buffer, header_len + len);
#endif
}