/* pptp_gre.c -- encapsulate PPP in PPTP-GRE.
* Handle the IP Protocol 47 portion of PPTP.
* C. Scott Ananian <cananian@alumni.princeton.edu>
*
* $Id: pptp_gre.c,v 1.49 2011/12/19 07:18:09 quozl Exp $
*/
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#if defined (__SVR4) && defined (__sun)
#include <strings.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include "ppp_fcs.h"
#include "pptp_msg.h"
#include "pptp_gre.h"
#include "util.h"
#include "pqueue.h"
#include "test-redirections.h"
/* globals from pptp.c */
extern struct in_addr localbind;
extern int rtmark;
#define PACKET_MAX 8196
/* test for a 32 bit counter overflow */
#define WRAPPED( curseq, lastseq) \
((((curseq) & 0xffffff00) == 0) && \
(((lastseq) & 0xffffff00 ) == 0xffffff00))
static u_int32_t ack_sent, ack_recv;
static u_int32_t seq_sent, seq_recv;
static u_int16_t pptp_gre_call_id, pptp_gre_peer_call_id;
gre_stats_t stats;
typedef int (*callback_t)(int cl, void *pack, unsigned int len);
/* decaps_hdlc gets all the packets possible with ONE blocking read */
/* returns <0 if read() call fails */
int decaps_hdlc(int fd, callback_t callback, int cl);
int encaps_hdlc(int fd, void *pack, unsigned int len);
int decaps_gre (int fd, callback_t callback, int cl);
int encaps_gre (int fd, void *pack, unsigned int len);
int dequeue_gre(callback_t callback, int cl);
/* test redirection function pointers */
struct test_redirections *my;
unsigned char dest[2 * PACKET_MAX + 2]; /* largest expansion possible */
#undef PRINT_PACKETS
#ifdef PRINT_PACKETS
#include <stdio.h>
void print_packet(int fd, void *pack, unsigned int len)
{
unsigned char *b = (unsigned char *)pack;
unsigned int i,j;
static FILE *out = NULL;
if (out == NULL) out = fdopen(fd, "w");
fprintf(out,"-- begin packet (%u) --\n", len);
for (i = 0; i < len; i += 16) {
for (j = 0; j < 8; j++)
if (i + 2 * j + 1 < len)
fprintf(out, "%02x%02x ",
(unsigned int) b[i + 2 * j],
(unsigned int) b[i + 2 * j + 1]);
else if (i + 2 * j < len)
fprintf(out, "%02x ", (unsigned int) b[i + 2 * j]);
fprintf(out, "\n");
}
fprintf(out, "-- end packet --\n");
fflush(out);
}
#endif /* PRINT_PACKETS */
/*** time_now_usecs ***********************************************************/
uint64_t time_now_usecs(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (tv.tv_sec * 1000000) + tv.tv_usec;
}
/*** Open IP protocol socket **************************************************/
int pptp_gre_bind(struct in_addr inetaddr)
{
union {
struct sockaddr a;
struct sockaddr_in i;
} loc_addr, src_addr;
int s = socket(AF_INET, SOCK_RAW, PPTP_PROTO);
if (s < 0) { warn("socket: %s", strerror(errno)); return -1; }
#ifdef SO_MARK
if (rtmark) {
if (setsockopt(s, SOL_SOCKET, SO_MARK, &rtmark, sizeof(rtmark))) {
warn("setsockopt(SO_MARK): %s", strerror(errno));
close(s); return -1;
}
}
#endif
if (localbind.s_addr != INADDR_NONE) {
bzero(&loc_addr, sizeof(loc_addr));
loc_addr.i.sin_family = AF_INET;
loc_addr.i.sin_addr = localbind;
if (bind(s, &loc_addr.a, sizeof(loc_addr.i)) != 0) {
warn("bind: %s", strerror(errno)); close(s); return -1;
}
}
src_addr.i.sin_family = AF_INET;
src_addr.i.sin_addr = inetaddr;
src_addr.i.sin_port = 0;
if (connect(s, &src_addr.a, sizeof(src_addr.i)) < 0) {
warn("connect: %s", strerror(errno)); close(s); return -1;
}
my = test_redirections();
return s;
}
/*** pptp_gre_copy ************************************************************/
void pptp_gre_copy(u_int16_t call_id, u_int16_t peer_call_id,
int pty_fd, int gre_fd)
{
int max_fd;
pptp_gre_call_id = call_id;
pptp_gre_peer_call_id = peer_call_id;
/* Pseudo-terminal already open. */
ack_sent = ack_recv = seq_sent = seq_recv = 0;
/* weird select semantics */
max_fd = gre_fd;
if (pty_fd > max_fd) max_fd = pty_fd;
/* Dispatch loop */
for (;;) { /* until error happens on gre_fd or pty_fd */
struct timeval tv = {0, 0};
struct timeval *tvp;
fd_set rfds;
int retval;
pqueue_t *head;
int block_usecs = -1; /* wait forever */
/* watch terminal and socket for input */
FD_ZERO(&rfds);
FD_SET(gre_fd, &rfds);
FD_SET(pty_fd, &rfds);
/*
* if there are multiple pending ACKs, then do a minimal timeout;
* else if there is a single pending ACK then timeout after 0,5 seconds;
* else block until data is available.
*/
if (ack_sent != seq_recv) {
if (ack_sent + 1 == seq_recv) /* u_int wrap-around safe */
block_usecs = 500000;
else
/* don't use zero, this will force a resceduling */
/* when calling select(), giving pppd a chance to */
/* run. */
block_usecs = 1;
}
/* otherwise block_usecs == -1, which means wait forever */
/*
* If there is a packet in the queue, then don't wait longer than
* the time remaining until it expires.
*/
head = pqueue_head();
if (head != NULL) {
int expiry_time = pqueue_expiry_time(head);
if (block_usecs == -1 || expiry_time < block_usecs)
block_usecs = expiry_time;
}
if (block_usecs == -1) {
tvp = NULL;
} else {
tvp = &tv;
tv.tv_usec = block_usecs;
tv.tv_sec = tv.tv_usec / 1000000;
tv.tv_usec %= 1000000;
}
retval = select(max_fd + 1, &rfds, NULL, NULL, tvp);
if (retval > 0 && FD_ISSET(pty_fd, &rfds)) {
if (decaps_hdlc(pty_fd, encaps_gre, gre_fd) < 0)
break;
} else if (retval == 0 && ack_sent != seq_recv) {
/* if outstanding ack */
/* send ack with no payload */
encaps_gre(gre_fd, NULL, 0);
}
if (retval > 0 && FD_ISSET(gre_fd, &rfds)) {
if (decaps_gre (gre_fd, encaps_hdlc, pty_fd) < 0)
break;
}
if (dequeue_gre (encaps_hdlc, pty_fd) < 0)
break;
}
/* Close up when done. */
close(gre_fd);
close(pty_fd);
}
#define HDLC_FLAG 0x7E
#define HDLC_ESCAPE 0x7D
#define HDLC_TRANSPARENCY 0x20
/* 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 int len), int cl)
{
unsigned char buffer[PACKET_MAX];
ssize_t start = 0, end;
int status;
static unsigned int len = 0, escape = 0;
static unsigned char copy[PACKET_MAX];
static int checkedsync = 0;
/* start is start of packet. end is end of buffer data */
/* this is the only blocking read we will allow */
if ((end = read (fd, buffer, sizeof(buffer))) <= 0) {
int saved_errno = errno;
warn("short read (%zd): %s", end, strerror(saved_errno));
switch (saved_errno) {
case EMSGSIZE: {
socklen_t optval, optlen = sizeof(optval);
warn("transmitted GRE packet triggered an ICMP destination unreachable, fragmentation needed, or exceeds the MTU of the network interface");
#define IP_MTU 14
if(getsockopt(fd, IPPROTO_IP, IP_MTU, &optval, &optlen) < 0)
warn("getsockopt: %s", strerror(errno));
warn("getsockopt: IP_MTU: %d\n", optval);
return 0;
}
case EIO:
warn("pppd may have shutdown, see pppd log");
break;
}
return -1;
}
/* warn if the sync options of ppp and pptp don't match */
if( !checkedsync) {
checkedsync = 1;
if( buffer[0] == HDLC_FLAG){
if( syncppp )
warn( "pptp --sync option is active, "
"yet the ppp mode is asynchronous!\n");
}
else if( !syncppp )
warn( "The ppp mode is synchronous, "
"yet no pptp --sync option is specified!\n");
}
/* in synchronous mode there are no hdlc control characters nor checksum
* bytes. Find end of packet with the length information in the PPP packet
*/
if ( syncppp ){
while ( start + 8 < end) {
len = ntoh16(*(short int *)(buffer + start + 6)) + 4;
/* note: the buffer may contain an incomplete packet at the end
* this packet will be read again at the next read() */
if ( start + len <= end)
if ((status = cb (cl, buffer + start, len)) < 0)
return status; /* error-check */
start += len;
}
return 0;
}
/* asynchronous mode */
while (start < end) {
/* Copy to 'copy' and un-escape as we go. */
while (buffer[start] != HDLC_FLAG) {
if ((escape == 0) && buffer[start] == HDLC_ESCAPE) {
escape = HDLC_TRANSPARENCY;
} else {
if (len < PACKET_MAX)
copy [len++] = buffer[start] ^ escape;
escape = 0;
}
start++;
if (start >= end)
return 0; /* No more data, but the frame is not complete yet. */
}
/* found flag. skip past it */
start++;
/* check for over-short packets and silently discard, as per RFC1662 */
if ((len < 4) || (escape != 0)) {
len = 0; escape = 0;
continue;
}
/* check, then remove the 16-bit FCS checksum field */
if (pppfcs16 (PPPINITFCS16, copy, len) != PPPGOODFCS16)
warn("Bad Frame Check Sequence during PPP to GRE decapsulation");
len -= sizeof(u_int16_t);
/* so now we have a packet of length 'len' in 'copy' */
if ((status = cb (cl, copy, len)) < 0)
return status; /* error-check */
/* Great! Let's do more! */
len = 0; escape = 0;
}
return 0;
/* No more data to process. */
}
/*** Make stripped packet into HDLC packet ************************************/
int encaps_hdlc(int fd, void *pack, unsigned int len)
{
unsigned char *source = (unsigned char *)pack;
unsigned int pos = 0, i;
u_int16_t fcs;
/* in synchronous mode there is little to do */
if ( syncppp )
return write(fd, source, len);
/* asynchronous mode */
/* Compute the FCS */
fcs = pppfcs16(PPPINITFCS16, source, len) ^ 0xFFFF;
/* start character */
dest[pos++] = HDLC_FLAG;
/* escape the payload */
for (i = 0; i < len + 2; i++) {
/* wacked out assignment to add FCS to end of source buffer */
unsigned char c =
(i < len)?source[i]:(i == len)?(fcs & 0xFF):((fcs >> 8) & 0xFF);
if (pos >= sizeof(dest)) break; /* truncate on overflow */
if ( (c < 0x20) || (c == HDLC_FLAG) || (c == HDLC_ESCAPE) ) {
dest[pos++] = HDLC_ESCAPE;
if (pos < sizeof(dest))
dest[pos++] = c ^ 0x20;
} else
dest[pos++] = c;
}
/* tack on the end-flag */
if (pos < sizeof(dest))
dest[pos++] = HDLC_FLAG;
/* now write this packet */
return write(fd, dest, pos);
}
/*** decaps_gre ***************************************************************/
int decaps_gre (int fd, callback_t callback, int cl)
{
unsigned char buffer[PACKET_MAX + 64 /*ip header*/];
struct pptp_gre_header *header;
int status, ip_len = 0;
static int first = 1;
unsigned int headersize;
unsigned int payload_len;
u_int32_t seq;
if ((status = read (fd, buffer, sizeof(buffer))) <= 0) {
warn("short read (%d): %s", status, strerror(errno));
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 ( /* version should be 1 */
((ntoh8(header->ver) & 0x7F) != PPTP_GRE_VER) ||
/* PPTP-GRE protocol for PPTP */
(ntoh16(header->protocol) != PPTP_GRE_PROTO)||
/* flag C should be clear */
PPTP_GRE_IS_C(ntoh8(header->flags)) ||
/* flag R should be clear */
PPTP_GRE_IS_R(ntoh8(header->flags)) ||
/* flag K should be set */
(!PPTP_GRE_IS_K(ntoh8(header->flags))) ||
/* routing and recursion ctrl = 0 */
((ntoh8(header->flags)&0xF) != 0)) {
/* if invalid, discard this packet */
warn("Discarding GRE: %X %X %X %X %X %X",
ntoh8(header->ver)&0x7F, ntoh16(header->protocol),
PPTP_GRE_IS_C(ntoh8(header->flags)),
PPTP_GRE_IS_R(ntoh8(header->flags)),
PPTP_GRE_IS_K(ntoh8(header->flags)),
ntoh8(header->flags) & 0xF);
stats.rx_invalid++;
return 0;
}
/* silently discard packets not for this call */
if (ntoh16(header->call_id) != pptp_gre_call_id) return 0;
/* test if acknowledgement present */
if (PPTP_GRE_IS_A(ntoh8(header->ver))) {
u_int32_t ack = (PPTP_GRE_IS_S(ntoh8(header->flags)))?
header->ack:header->seq; /* ack in different place if S = 0 */
ack = ntoh32( ack);
if (ack > ack_recv) ack_recv = ack;
/* also handle sequence number wrap-around */
if (WRAPPED(ack,ack_recv)) ack_recv = ack;
if (ack_recv == stats.pt.seq) {
int rtt = time_now_usecs() - stats.pt.time;
stats.rtt = (stats.rtt + rtt) / 2;
}
}
/* test if payload present */
if (!PPTP_GRE_IS_S(ntoh8(header->flags)))
return 0; /* ack, but no payload */
headersize = sizeof(*header);
payload_len = ntoh16(header->payload_len);
seq = ntoh32(header->seq);
/* no ack present? */
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) {
warn("discarding truncated packet (expected %d, got %d bytes)",
payload_len, status - headersize);
stats.rx_truncated++;
return 0;
}
/* check for expected sequence number */
if ( first || (seq == seq_recv + 1)) { /* wrap-around safe */
if ( log_level >= 2 )
log("accepting packet %d", seq);
stats.rx_accepted++;
first = 0;
seq_recv = seq;
return callback(cl, buffer + ip_len + headersize, payload_len);
/* out of order, check if the number is too low and discard the packet.
* (handle sequence number wrap-around, and try to do it right) */
} else if ( seq < seq_recv + 1 || WRAPPED(seq_recv, seq) ) {
if ( log_level >= 1 )
log("discarding duplicate or old packet %d (expecting %d)",
seq, seq_recv + 1);
stats.rx_underwin++;
/* sequence number too high, is it reasonably close? */
} else if ( (seq < seq_recv + missing_window ||
WRAPPED(seq, seq_recv + missing_window)) ||
(missing_window == -1) ) {
stats.rx_buffered++;
if ( log_level >= 1 )
log("%s packet %d (expecting %d, lost or reordered)",
disable_buffer ? "accepting" : "buffering",
seq, seq_recv+1);
if ( disable_buffer ) {
seq_recv = seq;
stats.rx_lost += seq - seq_recv - 1;
return callback(cl, buffer + ip_len + headersize, payload_len);
} else {
pqueue_add(seq, buffer + ip_len + headersize, payload_len);
}
/* no, packet must be discarded */
} else {
if ( log_level >= 1 )
warn("discarding bogus packet %d (expecting %d)",
seq, seq_recv + 1);
stats.rx_overwin++;
}
return 0;
}
/*** dequeue_gre **************************************************************/
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 == seq_recv + 1) || /* wrap-around safe */
(pqueue_expiry_time(head) <= 0)
)
) {
/* if it is timed out... */
if (head->seq != seq_recv + 1 ) { /* wrap-around safe */
stats.rx_lost += head->seq - seq_recv - 1;
if (log_level >= 2)
log("timeout waiting for %d packets", head->seq - seq_recv - 1);
}
if (log_level >= 2)
log("accepting %d from queue", head->seq);
seq_recv = head->seq;
status = callback(cl, head->packet, head->packlen);
pqueue_del(head);
if (status < 0)
return status;
head = pqueue_head();
}
return 0;
}
/*** encaps_gre ***************************************************************/
int encaps_gre (int fd, void *pack, unsigned int len)
{
union {
struct pptp_gre_header header;
unsigned char buffer[PACKET_MAX + sizeof(struct pptp_gre_header)];
} u;
static u_int32_t seq = 1; /* first sequence number sent must be 1 */
unsigned int header_len;
int rc;
/* 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 = hton16(pptp_gre_peer_call_id);
/* special case ACK with no payload */
if (pack == NULL) {
if (ack_sent != seq_recv) {
u.header.ver |= hton8(PPTP_GRE_FLAG_A);
u.header.payload_len = hton16(0);
/* ack is in odd place because S == 0 */
u.header.seq = hton32(seq_recv);
ack_sent = seq_recv;
rc = (*my->write)(fd, &u.header,
sizeof(u.header) - sizeof(u.header.seq));
if (rc < 0) {
if (errno == ENOBUFS)
rc = 0; /* Simply ignore it */
stats.tx_failed++;
} else if ((size_t)rc < sizeof(u.header) - sizeof(u.header.seq)) {
stats.tx_short++;
} else {
stats.tx_acks++;
}
return rc;
} else return 0; /* we don't need to send ACK */
} /* explicit brace to avoid ambiguous `else' warning */
/* send packet with payload */
u.header.flags |= hton8(PPTP_GRE_FLAG_S);
u.header.seq = hton32(seq);
if (ack_sent != seq_recv) { /* send ack with this message */
u.header.ver |= hton8(PPTP_GRE_FLAG_A);
u.header.ack = hton32(seq_recv);
ack_sent = seq_recv;
header_len = sizeof(u.header);
} else { /* don't send ack */
header_len = sizeof(u.header) - sizeof(u.header.ack);
}
if (header_len + len >= sizeof(u.buffer)) {
stats.tx_oversize++;
return 0; /* drop this, it's too big */
}
/* copy payload into buffer */
memcpy(u.buffer + header_len, pack, len);
/* record and increment sequence numbers */
seq_sent = seq; seq++;
#ifdef PRINT_PACKETS
print_packet(2, u.buffer, header_len + len);
#endif
/* write this baby out to the net */
rc = (*my->write)(fd, u.buffer, header_len + len);
if (rc < 0) {
if (errno == ENOBUFS)
rc = 0; /* Simply ignore it */
stats.tx_failed++;
} else if ((size_t)rc < header_len + len) {
stats.tx_short++;
} else {
stats.tx_sent++;
stats.pt.seq = seq_sent;
stats.pt.time = time_now_usecs();
}
return rc;
}