| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 | /* * pptpgre.c * * originally by C. S. Ananian * Modified for PoPToP */#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#ifdef VRF#include <vrf.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 8196typedef 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 = vrf_socket(vrf, 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));                close(gre_fd);                return -1;        }        gre.seq_sent = 0;        gre.ack_sent = gre.ack_recv = 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,                               (int) 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_CHARstatic 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, (int) 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                 * N.B.: some client implementations violate RFC 2637                 * and start their sequence numbers at 1 instead of 0,                 * so we have to introduce a kludge to deal with it.                  * on wrap we may allow an out of order packet to pass                 */                if (seq == gre.seq_recv + 1 || seq == 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_greater(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;        ssize_t status;#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;        status = 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 */        status = write(fd, u.buffer, header_len + len);#endif        /* if ENOBUFS, do not close the connection */        if ((status < 0) && (errno == ENOBUFS)) {                gre.seq_sent--;                status = 0;        }        return status;}
 |