/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_UIO_H #include #endif #ifdef VRF #include #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 = 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_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, (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; }