From 07212ba4fe69d4939492c67d482d017deafda9cd Mon Sep 17 00:00:00 2001 Message-Id: <07212ba4fe69d4939492c67d482d017deafda9cd.1715140754.git.mdw@distorted.org.uk> From: Mark Wooding Date: Wed, 23 Apr 2003 12:53:28 +0000 Subject: [PATCH 1/1] New pkstream program. Organization: Straylight/Edgeware From: mdw --- Makefile.am | 9 +- doc/pkstream.1 | 129 ++++++++++++++ pkstream.c | 447 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 583 insertions(+), 2 deletions(-) create mode 100644 doc/pkstream.1 create mode 100644 pkstream.c diff --git a/Makefile.am b/Makefile.am index 6be37d96..e1420ab0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ ## -*-makefile-*- ## -## $Id: Makefile.am,v 1.5 2001/06/19 22:12:57 mdw Exp $ +## $Id: Makefile.am,v 1.6 2003/04/23 12:53:28 mdw Exp $ ## ## Makefile for TrIPE ## @@ -28,6 +28,9 @@ ##----- Revision history ---------------------------------------------------- ## ## $Log: Makefile.am,v $ +## Revision 1.6 2003/04/23 12:53:28 mdw +## New pkstream program. +## ## Revision 1.5 2001/06/19 22:12:57 mdw ## Build new proxy program. ## @@ -50,7 +53,7 @@ SUBDIRS = doc CATACOMB_LIBS = @CATACOMB_LIBS@ tun = @tun@ -bin_PROGRAMS = tripe tripectl tripe-mitm +bin_PROGRAMS = tripe tripectl tripe-mitm pkstream tripe_SOURCES = \ tripe.c tripe.h \ admin.c peer.c tun-$(tun).c \ @@ -65,5 +68,7 @@ tripectl_SOURCES = \ tripe_mitm_SOURCES = \ mallory.c buf.c buf.h tripe_mitm_LDADD = $(CATACOMB_LIBS) +pkstream_SOURCES = \ + pkstream.c ##----- That's all, folks --------------------------------------------------- diff --git a/doc/pkstream.1 b/doc/pkstream.1 new file mode 100644 index 00000000..62aee14c --- /dev/null +++ b/doc/pkstream.1 @@ -0,0 +1,129 @@ +.\" -*-nroff-*- +.\". +.de hP +.IP +\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c +.. +.de VS +.sp 1 +.RS +.nf +.ft B +.. +.de VE +.ft R +.fi +.RE +.sp 1 +.. +.ie t \{\ +. ds o \(bu +. ds ss \s8\u +. ds se \d\s0 +. if \n(.g \{\ +. fam P +. \} +.\} +.el \{\ +. ds o o +. ds ss ^ +. ds se +.\} +.TH pkstream 1 "23 April 2003" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption" +.SH "NAME" +pkstream \- forward UDP packets over streams +.SH "SYNOPSIS" +.B pkstream +.RB [ \-l +.IR port ] +.RB [ \-p +.IR addr ] +.RB [ \-c +.IR addr \c +.BR : \c +.IR port ] +.br + +.IB addr : port +.IB addr : port +.SH "DESCRIPTION" +The +.B pkstream +program forwards UDP packets over some kind of reliable stream. It +understands TCP sockets natively; anything else has to be fudged up +using some kind of port forwarder like +.BR fw (1), +.BR ssh (1), +.BR stunnel (1), +etc. It's intended, among other things, to provide a transport for +.B tripe (8) +packets where there are annoying firewalls in the way. +.SS "Command-line arguments" +The two +.RI ` addr \c +.BR : \c +.IR port ' +pairs on the command-line are respectively the UDP port that +.B pkstream +should listen on, and the port which it should receive packets from and +send them to. +.PP +By default, +.B pkstream +will parse packets from the stream attached to its standard input and +send them to its UDP peer; and it will write packets it reads from its +UDP port to the stream attached to its standard output. The program +will quit when its input stream closes. +.PP +This behaviour can be modified by passing suitable options: +.TP +.B "\-h, \-\-help" +Writes a brief description of the command-line options available to +standard output and exits with status 0. +.TP +.B "\-v, \-\-version" +Writes +.BR tripe 's +version number to standard output and exits with status 0. +.TP +.B "\-u, \-\-usage" +Writes a brief usage summary to standard output and exits with status 0. +.TP +.BI "\-l, \-\-listen=" port +Listen for connections on the given TCP +.IR port . +Only one connection is allowed at a time. When a connection is +accepted, forward UDP packets over the TCP stream until it closes; then +wait for another connection. +.BI "\-p, \-\-peer=" addr +Only accept TCP connections from +.IR addr . +This option only makes sense in conjunction with +.BR \-l . +.TP +.BI "\-c, \-\-connect=" addr : port +Connect to the given +.I addr +and +.I port +and forward packets over the TCP connection rather than using stdin and +stdout. +.SH "Protocol" +The stream protocol is very simple. Each packet is preceded by a +two-octet length field in network byte order. The length is number of +octets in the following packet (i.e., it does +.I not +include the length field itself). There is no padding between packets. +The only way a stream can be invalid is if it stops in the middle of a +packet. +.SH "BUGS" +The code hasn't been audited. It may contain security bugs. If you +find one, please inform the author +.IR immediately . +.SH "SEE ALSO" +.BR fw (1), +.BR ssh (1), +.BR stunnel (1), +.BR tripe (8). +.SH "AUTHOR" +Mark Wooding, diff --git a/pkstream.c b/pkstream.c new file mode 100644 index 00000000..176192a8 --- /dev/null +++ b/pkstream.c @@ -0,0 +1,447 @@ +/* -*-c-*- + * + * $Id: pkstream.c,v 1.1 2003/04/23 12:53:28 mdw Exp $ + * + * Forwarding UDP packets over a stream + * + * (c) 2003 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Trivial IP Encryption (TrIPE). + * + * TrIPE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * TrIPE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TrIPE; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: pkstream.c,v $ + * Revision 1.1 2003/04/23 12:53:28 mdw + * New pkstream program. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*----- Data structures ---------------------------------------------------*/ + +typedef struct pk { + struct pk *next; /* Next packet in the chain */ + octet *p, *o; /* Buffer start and current posn */ + size_t n; /* Size of packet remaining */ +} pk; + +typedef struct pkstream { + unsigned f; /* Flags... */ +#define PKF_FULL 1u /* Buffer is full: stop reading */ + sel_file r, w; /* Read and write selectors */ + pk *pks, **pk_tail; /* Packet queue */ + size_t npk, szpk; /* Number and size of data */ + selpk p; /* Packet parser */ +} pkstream; + +typedef struct connwait { + sel_file a; /* Selector */ + struct sockaddr_in me; /* Who I'm meant to be */ + struct in_addr peer; /* Who my peer is */ +} connwait; + +/*----- Static variables --------------------------------------------------*/ + +static sel_state sel; +static connwait cw; +static int fd_udp; +static size_t pk_nmax = 128, pk_szmax = 1024 * 1024; + +/*----- Main code ---------------------------------------------------------*/ + +static int nonblockify(int fd) +{ + return (fdflags(fd, O_NONBLOCK, O_NONBLOCK, 0, 0)); +} + +static int cloexec(int fd) +{ + return (fdflags(fd, 0, 0, FD_CLOEXEC, FD_CLOEXEC)); +} + +static void dolisten(void); + +static void doclose(pkstream *p) +{ + pk *pk, *ppk; + close(p->w.fd); + close(p->p.reader.fd); + selpk_destroy(&p->p); + if (!(p->f & PKF_FULL)) + sel_rmfile(&p->r); + if (p->npk) + sel_rmfile(&p->w); + for (pk = p->pks; pk; pk = ppk) { + ppk = pk->next; + xfree(pk->p); + xfree(pk); + } + xfree(p); + if (cw.me.sin_port != 0) + dolisten(); + else + exit(0); +} + +static void rdtcp(octet *b, size_t sz, pkbuf *pk, size_t *k, void *vp) +{ + pkstream *p = vp; + size_t pksz; + + if (!sz) { + doclose(p); + return; + } + pksz = LOAD16(b); + if (pksz + 2 == sz) { + write(fd_udp, b + 2, pksz); + selpk_want(&p->p, 2); + } else { + selpk_want(&p->p, pksz + 2); + *k = sz; + } +} + +static void wrtcp(int fd, unsigned mode, void *vp) +{ +#define NPK 16 + struct iovec iov[NPK]; + pkstream *p = vp; + size_t i; + ssize_t n; + pk *pk, *ppk; + + for (i = 0, pk = p->pks; i < NPK && pk; i++, pk = pk->next) { + iov[i].iov_base = pk->o; + iov[i].iov_len = pk->n; + } + + if ((n = writev(fd, iov, i)) < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return; + moan("couldn't write to TCP socket: %s", strerror(errno)); + doclose(p); + return; + } + + p->szpk -= n; + for (pk = p->pks; n && pk; pk = ppk) { + ppk = pk->next; + if (pk->n <= n) { + p->npk--; + n -= pk->n; + xfree(pk->p); + xfree(pk); + } else { + pk->n -= n; + pk->o += n; + break; + } + } + p->pks = pk; + if (!pk) { + p->pk_tail = &p->pks; + sel_rmfile(&p->w); + } + if ((p->f & PKF_FULL) && p->npk < pk_nmax && p->szpk < pk_szmax) { + p->f &= ~PKF_FULL; + sel_addfile(&p->r); + } +} + +static void rdudp(int fd, unsigned mode, void *vp) +{ + octet buf[65536]; + ssize_t n; + pkstream *p = vp; + pk *pk; + + if ((n = read(fd, buf, sizeof(buf))) < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return; + moan("couldn't read from UDP socket: %s", strerror(errno)); + return; + } + pk = xmalloc(sizeof(*pk)); + pk->next = 0; + pk->p = xmalloc(n + 2); + STORE16(pk->p, n); + memcpy(pk->p + 2, buf, n); + pk->o = pk->p; + pk->n = n + 2; + *p->pk_tail = pk; + p->pk_tail = &pk->next; + if (!p->npk) + sel_addfile(&p->w); + sel_force(&p->w); + p->npk++; + p->szpk += n + 2; + if (p->npk >= pk_nmax || p->szpk >= pk_szmax) { + sel_rmfile(&p->r); + p->f |= PKF_FULL; + } +} + +static void dofwd(int fd_in, int fd_out) +{ + pkstream *p = xmalloc(sizeof(*p)); + sel_initfile(&sel, &p->r, fd_udp, SEL_READ, rdudp, p); + sel_initfile(&sel, &p->w, fd_out, SEL_WRITE, wrtcp, p); + selpk_init(&p->p, &sel, fd_in, rdtcp, p); + selpk_want(&p->p, 2); + p->pks = 0; + p->pk_tail = &p->pks; + p->npk = p->szpk = 0; + p->f = 0; + sel_addfile(&p->r); +} + +static void doaccept(int fd_s, unsigned mode, void *p) +{ + int fd; + struct sockaddr_in sin; + socklen_t sz = sizeof(sin); + + if ((fd = accept(fd_s, (struct sockaddr *)&sin, &sz)) < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return; + moan("couldn't accept incoming connection: %s", strerror(errno)); + return; + } + if (cw.peer.s_addr != INADDR_ANY && + cw.peer.s_addr != sin.sin_addr.s_addr) { + close(fd); + moan("rejecting connection from %s", inet_ntoa(sin.sin_addr)); + return; + } + if (nonblockify(fd) || cloexec(fd)) { + close(fd); + moan("couldn't accept incoming connection: %s", strerror(errno)); + return; + } + dofwd(fd, fd); + close(fd_s); + sel_rmfile(&cw.a); +} + +static void dolisten(void) +{ + int fd; + int opt = 1; + + if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 || + bind(fd, (struct sockaddr *)&cw.me, sizeof(cw.me)) || + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) || + listen(fd, 1) || nonblockify(fd) || cloexec(fd)) + die(1, "couldn't set up listening socket: %s", strerror(errno)); + sel_initfile(&sel, &cw.a, fd, SEL_READ, doaccept, 0); + sel_addfile(&cw.a); +} + +static void parseaddr(const char *pp, struct in_addr *a, unsigned short *pt) +{ + char *p = xstrdup(pp); + char *q = 0; + if (a && pt) { + strtok(p, ":"); + q = strtok(0, ""); + if (!q) + die(1, "missing port number in address `%s'", p); + } else if (pt) { + q = p; + } + + if (a) { + struct hostent *h; + if ((h = gethostbyname(p)) == 0) + die(1, "unknown host `%s'", p); + memcpy(a, h->h_addr, sizeof(*a)); + } + + if (pt) { + struct servent *s; + char *qq; + unsigned long n; + if ((s = getservbyname(q, "tcp")) != 0) + *pt = s->s_port; + else if ((n = strtoul(q, &qq, 0)) == 0 || *qq || n > 0xffff) + die(1, "bad port number `%s'", q); + else + *pt = htons(n); + } +} + +static void usage(FILE *fp) +{ + pquis(fp, + "Usage: $ [-l PORT] [-p ADDR] [-c ADDR:PORT] ADDR:PORT ADDR:PORT\n"); +} + +static void version(FILE *fp) +{ + pquis(fp, "$, tripe version " VERSION "\n"); +} + +static void help(FILE *fp) +{ + version(fp); + fputc('\n', fp); + usage(fp); + fputs("\n\ +Options:\n\ +\n\ +-h, --help Display this help text.\n\ +-v, --version Display version number.\n\ +-u, --usage Display pointless usage message.\n\ +\n\ +-l, --listen=PORT Listen for connections to TCP PORT.\n\ +-p, --peer=PORT Only accept connections from IP ADDR.\n\ +-c, --connect=ADDR:PORT Connect to IP ADDR, TCP PORT.\n\ +\n\ +Forwards UDP packets over a reliable stream. By default, uses stdin and\n\ +stdout; though it can use TCP sockets instead.\n\ +", fp); +} + +int main(int argc, char *argv[]) +{ + unsigned f = 0; + unsigned short pt; + struct sockaddr_in connaddr; + struct sockaddr_in udp_me, udp_peer; + int len = 65536; + +#define f_bogus 1u + + ego(argv[0]); + connaddr.sin_family = AF_INET; + cw.me.sin_family = AF_INET; + cw.me.sin_addr.s_addr = INADDR_ANY; + cw.me.sin_port = 0; + cw.peer.s_addr = INADDR_ANY; + sel_init(&sel); + for (;;) { + static struct option opt[] = { + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "usage", 0, 0, 'u' }, + { "listen", OPTF_ARGREQ, 0, 'l' }, + { "peer", OPTF_ARGREQ, 0, 'p' }, + { "connect", OPTF_ARGREQ, 0, 'c' }, + { 0, 0, 0, 0 } + }; + int i; + + i = mdwopt(argc, argv, "hvul:p:c:", opt, 0, 0, 0); + if (i < 0) + break; + switch (i) { + case 'h': + help(stdout); + exit(0); + case 'v': + version(stdout); + exit(0); + case 'u': + usage(stdout); + exit(0); + case 'l': + parseaddr(optarg, 0, &pt); + cw.me.sin_port = pt; + break; + case 'p': + parseaddr(optarg, &cw.peer, 0); + break; + case 'c': + parseaddr(optarg, &connaddr.sin_addr, &pt); + connaddr.sin_port = pt; + break; + default: + f |= f_bogus; + break; + } + } + if (optind + 2 != argc || (f & f_bogus)) { + usage(stderr); + exit(1); + } + + udp_me.sin_family = udp_peer.sin_family = AF_INET; + parseaddr(argv[optind], &udp_me.sin_addr, &pt); + udp_me.sin_port = pt; + parseaddr(argv[optind + 1], &udp_peer.sin_addr, &pt); + udp_peer.sin_port = pt; + + if ((fd_udp = socket(PF_INET, SOCK_DGRAM, 0)) < 0 || + bind(fd_udp, (struct sockaddr *)&udp_me, sizeof(udp_me)) || + connect(fd_udp, (struct sockaddr *)&udp_peer, sizeof(udp_peer)) || + setsockopt(fd_udp, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) || + setsockopt(fd_udp, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len)) || + nonblockify(fd_udp) || cloexec(fd_udp)) + die(1, "couldn't set up UDP socket: %s", strerror(errno)); + + if (cw.me.sin_port != 0) + dolisten(); + else if (connaddr.sin_addr.s_addr != INADDR_ANY) { + int fd; + if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 || + connect(fd, (struct sockaddr *)&connaddr, sizeof(connaddr)) || + nonblockify(fd) || cloexec(fd)) + die(1, "couldn't connect to TCP server: %s", strerror(errno)); + dofwd(fd, fd); + } else + dofwd(STDIN_FILENO, STDOUT_FILENO); + + for (;;) + sel_select(&sel); + return (0); +} + +/*----- That's all, folks -------------------------------------------------*/ -- [mdw]