X-Git-Url: https://git.distorted.org.uk/~mdw/tripe/blobdiff_plain/1a19f86595b5c07ebf7ca7193093c54a75a15310..07212ba4fe69d4939492c67d482d017deafda9cd:/pkstream.c?ds=inline 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 -------------------------------------------------*/