home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
AmigActive 13
/
AACD13.ISO
/
AACD
/
Online
/
tcpxd
/
tcpxd.c
< prev
Wrap
C/C++ Source or Header
|
2000-02-18
|
23KB
|
765 lines
/* $Id: tcpxd.c,v 1.4 2000/02/18 06:16:08 root Exp root $ */
#ifndef lint
static char vcid[] = "$Id: tcpxd.c,v 1.4 2000/02/18 06:16:08 root Exp root $";
#endif /* lint */
#define VERSION "0.4"
/*
tcpxd-0.4, a generic TCP/IP relay proxy
Copyright (C) 2000 James Cameron <quozl@us.netrek.org>
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
wishlist
default to background
use --foreground to override
handle sighup by repeating hostname lookups
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/uio.h>
#ifndef TCP_NODELAY
#include <netinet/tcp.h>
#endif
#include <netdb.h>
#define MAXCHUNK 65536 /* maximum transfer in any net I/O */
/*
* One service structure exists for each port that the relay is to listen for
* connections on. The services are connected with a doubly linked list.
*/
struct service {
struct service *prior;
struct service *next;
short int bound; /* listening port number */
short int local; /* local source port number */
short int port; /* destination port number */
char *host; /* remote host name */
struct sockaddr_in remote;
int bandwidth; /* bytes per second */
int listener; /* file descriptor for listening on */
int accepts; /* count of accept() calls on socket */
} *services;
/*
* For each connection that arrives on a service listening port, a new per-
* connection structure is created to maintain context. Connections are also
* arranged as a doubly linked list, with a pointer back to the owning service.
*/
struct connection {
struct connection *prior; /* link to prior connection */
struct connection *next; /* link to next connection */
struct service *service; /* link to owning service */
int number; /* which connection this is */
int connected; /* whether connection is done */
int incoming; /* socket file descriptor */
int outgoing; /* socket file descriptor */
int fast; /* incoming socket has shut */
int constipate; /* outgoing socket has shut */
int total; /* total byte count so far */
int allow; /* currently allowed byte count */
time_t start, now; /* start time of connection */
} *connections;
fd_set sr, sw, se; /* set bits for select() */
fd_set or, ow, oe; /* our bits after select() */
int verbose = 0; /* how verbose to be in logging */
static void block (int fd) /* set socket blocking */
{
int flags;
flags = (~O_NONBLOCK) & fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags);
}
static void unblock (int fd) /* set socket non-blocking */
{
int flags;
flags = O_NONBLOCK | fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags);
}
/*
* Transfer data from one side to the other for a connection. The code
* presumes that select() has already indicated that the incoming socket
* has data ready, and the outgoing socket is writeable.
*/
static int transfer ( int incoming, int outgoing, int allow )
{
char buffer[MAXCHUNK];
int bytes;
bytes = recv(outgoing, buffer, allow, 0);
if (bytes == 0) return 0;
if (bytes < 0) { perror("recv(): outgoing"); return 0; }
bytes = send(incoming, buffer, bytes, 0);
if (bytes < 0) { perror("send(): incoming"); return 0; }
return bytes;
}
/*
* Lookup the IP address of a service target host and save it in the service
* structure. This is done during initialisation only, to reduce delays when
* a connection arrives.
*/
static void resolve ( struct service *service )
{
struct hostent *entry;
static struct hostent saved;
if (verbose > 0)
printf("%d: gethostbyname(): resolving %s\n",
service->bound, service->host);
entry = gethostbyname(service->host);
if (entry == NULL) {
herror(service->host);
exit(1);
}
saved = *entry;
service->remote.sin_family = saved.h_addrtype;
service->remote.sin_port = htons(service->port);
service->remote.sin_addr = * ((struct in_addr *) saved.h_addr);
}
static void usage()
{
fprintf(stderr,
"tcpxd version " VERSION ", Copyright (C) 2000 James Cameron\n"
"tcpxd comes with ABSOLUTELY NO WARRANTY; for details see source.\n"
"This is free software, and you are welcome to redistribute it\n"
"under certain conditions; see source for details.\n\n" );
fprintf(stderr,
"Usage: tcpxd [options] [listen-port call-host call-port]\n"
"\n"
"Options can be\n"
" --once allow only one connection to happen then exit,\n"
" --local port specify the port to call out on,\n"
" --input file list of 'port host port' triplets to use,\n"
" --verbose increment verbosity, repeat as required.\n"
"\n"
"Parameters are\n"
" listen-port which local port to accept connection on\n"
" call-host host to connect to when relaying connection\n"
" call-port port to connect to when relaying connection\n"
"\n"
"Example: tcpxd 110 pop.example.com 110\n"
" relays POP3 connections to another host\n"
"\n"
"Example: tcpxd 8023 localhost 23\n"
" redirects connections on port 8023 to port 23\n" );
}
int main ( int argc, char *argv[] )
{
/* command line arguments */
int arg_bound = 0; /* port number to listen on */
char *arg_host = NULL; /* remote host to connect to */
int arg_port = 0; /* remote port to connect to */
int arg_local = 0; /* local source port to use */
int arg_once = 0; /* do only one session? */
int arg_band = 0; /* bandwidth limit bytes/second */
char *arg_input = NULL; /* file name of services */
int i;
int status;
struct service *service;
FD_ZERO(&sr);
FD_ZERO(&sw);
FD_ZERO(&se);
services = NULL;
connections = NULL;
if (argc == 1) {
usage();
exit(1);
}
/* process command line arguments */
for(i=1;i<argc;i++) {
/* --input takes a parameter which is the file containing services */
if (!strcmp(argv[i], "-i")||!strcmp(argv[i], "--input")) {
if ( i++ == argc ) break;
arg_input = argv[i];
continue;
}
/* --bandwidth takes a parameter in bytes per second */
if (!strcmp(argv[i], "-b")||!strcmp(argv[i], "--bandwidth")) {
if ( i++ == argc ) break;
arg_band = atoi(argv[i]);
continue;
}
/* --verbose increments the verbosity value, more messages */
if (!strcmp(argv[i], "-v")||!strcmp(argv[i], "--verbose")) {
verbose++;
continue;
}
/* --version reports the version */
if (!strcmp(argv[i], "-V")||!strcmp(argv[i], "--version")) {
printf("%s\n", vcid);
continue;
}
/* --once allows just one incoming connection and stops listening */
if (!strcmp(argv[i], "--once")) {
arg_once = 1;
continue;
}
/* --local takes a parameter to use as the local port number */
if (!strcmp(argv[i], "--local")) {
if ( i++ == argc ) break;
arg_local = atoi(argv[i]);
continue;
}
if (!strcmp(argv[i], "-h")||!strcmp(argv[i], "--help")) {
usage();
exit(1);
}
if (arg_bound == 0) { arg_bound = atoi(argv[i]); continue; }
if (arg_host == NULL) { arg_host = argv[i]; continue; }
if (arg_port == 0) { arg_port = atoi(argv[i]); continue; }
}
/* did user give us a file name to read for services */
if (arg_input == NULL) {
/* no, so add the command line service only */
/* die if not given command line service description */
if (arg_port == 0 || arg_host == NULL || arg_bound == 0) {
usage();
exit(1);
}
service = malloc ( sizeof ( struct service ) );
service->bound = arg_bound;
service->local = arg_local;
service->port = arg_port;
service->host = arg_host;
resolve(service);
service->bandwidth = arg_band;
service->listener = -1;
service->next = services;
service->prior = NULL;
service->accepts = 0;
services = service;
} else {
/* yes, so read the file */
FILE *input;
input = fopen(arg_input, "r");
if (input == NULL) {
perror(arg_input);
exit(1);
}
while(1) {
char buffer[1024], *line, *token;
line = fgets(buffer, 1024, input);
if (line == NULL) break;
/* ignore lines starting with comment characters */
if (line[0] == '#') continue;
if (line[0] == '!') continue;
token = strtok(line, " ");
if (token == NULL) continue;
arg_bound = atoi(token);
token = strtok(NULL, " ");
if (token == NULL) continue;
arg_host = strdup(token);
token = strtok(NULL, " ");
if (token == NULL) continue;
arg_port = atoi(token);
service = malloc ( sizeof ( struct service ) );
service->bound = arg_bound;
service->local = 0;
service->port = arg_port;
service->host = arg_host;
resolve(service);
service->bandwidth = 0;
service->listener = -1;
service->next = services;
service->prior = NULL;
service->accepts = 0;
services = service;
}
fclose(input);
}
/* for each service defined */
for(service = services;service != NULL;service = service->next) {
/* create the listening socket */
service->listener = socket(AF_INET, SOCK_STREAM, 0);
if (service->listener == -1) { perror("socket"); exit(1); }
/* set the socket to allow address re-use */
{
int option_value = 1;
status = setsockopt(service->listener, SOL_SOCKET, SO_REUSEADDR,
(char *) &option_value, sizeof(option_value));
if (status < 0) { perror("setsockopt"); exit(1); }
}
/* bind the socket to the specified port */
{
struct sockaddr_in bound;
memset((char *) &bound, 0, sizeof(bound));
bound.sin_family = AF_INET;
bound.sin_port = htons(service->bound);
bound.sin_addr.s_addr = htonl(INADDR_ANY);
status = bind(service->listener, &bound, sizeof bound);
if (status) { perror("bind"); exit(1); }
if (verbose > 1)
printf("%d: bind(): listener socket %d bound to %s:%d\n",
service->bound, service->listener, inet_ntoa(bound.sin_addr),
ntohs(bound.sin_port));
}
/* start listening for connections */
status = listen(service->listener, 10);
if (status) { perror("listen"); exit(1); }
if (verbose > 0)
printf("%d: listen(): listener socket %d waiting for connection\n",
service->bound, service->listener);
/* make a note that we need to watch this file descriptor */
FD_SET(service->listener,&sr);
}
/* main loop, exits only on error or if --once is used */
while(1) {
struct connection *connection;
/* copy our file descriptor masks to use in select() call */
memcpy(&or,&sr,sizeof(fd_set));
memcpy(&ow,&sw,sizeof(fd_set));
memcpy(&oe,&se,sizeof(fd_set));
if (verbose > 2) {
int i;
printf("select: ");
for(i=0;i<FD_SETSIZE;i++) {
if (FD_ISSET(i,&or)||FD_ISSET(i,&ow)) {
printf("%d",i);
if (FD_ISSET(i,&or)) printf("r"); else printf(" ");
if (FD_ISSET(i,&ow)) printf("w"); else printf(" ");
printf(" ");
}
}
printf("\r");
fflush(stdout);
}
/* wait for activity on the current file descriptors */
status = select(FD_SETSIZE,&or,&ow,NULL,NULL);
if (status < 0) { perror("select"); exit(1); }
if (verbose > 2) {
int i;
printf("select: ");
for(i=0;i<FD_SETSIZE;i++) {
if (FD_ISSET(i,&sr)||FD_ISSET(i,&sw)) {
printf("%d",i);
if (FD_ISSET(i,&sr))
if (FD_ISSET(i,&or))
printf("R");
else
printf("r");
else
printf(" ");
if (FD_ISSET(i,&sw))
if (FD_ISSET(i,&ow))
printf("R");
else
printf("r");
else
printf(" ");
printf(" ");
}
}
printf("\r");
fflush(stdout);
}
/* for each defined service ... */
for(service = services;service != NULL;service = service->next) {
/* is the listening socket ready for reading? */
if (FD_ISSET(service->listener,&or)) {
/* yes, it is, so a connection has arrived */
struct connection *connection;
struct sockaddr_in peer;
int length = sizeof(peer);
/* allocate heap for a connection structure and accept it */
connection = malloc(sizeof(struct connection));
connection->incoming = accept(service->listener, &peer, &length);
if (connection->incoming == -1) {
perror("accept");
free(connection);
continue;
}
service->accepts++;
connection->number = service->accepts;
if (verbose > 0)
printf("%d.%d: accept(): incoming socket %d accepted from %s:%d\n",
service->bound, connection->number, connection->incoming,
inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
/*
* Set the socket to disable the Nagle algorithm, trust the other ends
* to use Nagle if required ... thus we don't almalgamate packets but
* rather pass them right through at the same rate. Useful for X.
*/
{
int option_value = 1;
status = setsockopt(connection->incoming, IPPROTO_TCP, TCP_NODELAY,
(char *) &option_value, sizeof(option_value));
if (status < 0) { perror("setsockopt"); }
}
retry:
connection->outgoing = socket(AF_INET, SOCK_STREAM, 0);
if (connection->outgoing == -1) {
perror("socket");
close(connection->incoming);
free(connection);
continue;
}
{
int option_value = 1;
status = setsockopt(connection->outgoing, SOL_SOCKET, SO_REUSEADDR,
(char *) &option_value, sizeof(option_value));
if (status < 0) { perror("setsockopt"); }
}
if (service->bandwidth != 0) {
int option_value = service->bandwidth*10;
status = setsockopt
(
connection->outgoing, /* socket */
SOL_SOCKET, /* level */
SO_RCVBUF, /* option name */
(char *) &option_value, /* option value */
sizeof(option_value) /* option length */
);
if (status < 0) { perror("setsockopt (SO_RCVBUF)"); }
} else {
int option_value = 1;
status = setsockopt(connection->outgoing, IPPROTO_TCP, TCP_NODELAY,
(char *) &option_value, sizeof(option_value));
if (status < 0) { perror("setsockopt"); }
}
if (service->local != 0) {
struct sockaddr_in local;
local.sin_family = AF_INET;
memset((char *) &local.sin_addr, 0, sizeof(local.sin_addr));
local.sin_port = htons(service->local);
status = bind(connection->outgoing, (struct sockaddr *)&local,
sizeof(local));
if (status < 0) { perror("bind"); }
if (verbose > 1)
printf("%d.%d: bind(): outgoing socket %d bound to %s:%d\n",
service->bound, connection->number, connection->outgoing,
inet_ntoa(local.sin_addr), ntohs(local.sin_port));
}
unblock(connection->outgoing);
status = connect(connection->outgoing, &service->remote,
sizeof(service->remote));
if (status) {
if (errno != EINPROGRESS) {
if (errno != EADDRINUSE) {
perror("connect");
close(connection->incoming);
close(connection->outgoing);
free(connection);
continue;
}
service->remote.sin_port =
htons(ntohs(service->remote.sin_port)+1);
close(connection->outgoing);
printf("tcpxd: retry\n");
goto retry;
}
}
if (verbose > 1)
printf("%d.%d: connect(): outgoing socket %d connecting to %s:%d\n",
service->bound, connection->number, connection->outgoing,
inet_ntoa(service->remote.sin_addr),
ntohs(service->remote.sin_port));
connection->connected = 0;
connection->fast = 0;
connection->constipate = 0;
connection->service = service;
connection->total = 0;
connection->allow = service->bandwidth;
if (connection->allow == 0) connection->allow = MAXCHUNK;
connection->start = time(NULL);
connection->now = connection->start;
/* add to start of connection chain */
connection->next = connections;
if (connections != NULL) connection->next->prior = connection;
connection->prior = NULL;
connections = connection;
/* begin by waiting for the sockets to be writeable */
FD_SET(connection->incoming,&sw);
FD_SET(connection->outgoing,&sw);
}
}
/* from this point the file descriptor bits dance around in an amusingly
complex manner; first we set the bit to indicate we want to be told
when the socket is writeable, and only when it is do we dare ask to be
told that the peer socket is readable, and once both of those have
happened we can transfer data without stalling. */
/* process each active connection */
for(connection = connections;connection != NULL;) {
int bytes;
/* if the outgoing connection is now writeable */
if (FD_ISSET(connection->outgoing,&ow)) {
/* check result of connection? */
if (connection->connected == 0) {
int deferred_errno, length;
struct service *service = connection->service;
length = sizeof(deferred_errno);
if (getsockopt(connection->outgoing, SOL_SOCKET, SO_ERROR,
&deferred_errno, &length) < 0) deferred_errno = errno;
block(connection->outgoing);
if (deferred_errno == 0) {
if (verbose > 0)
printf("%d.%d: connect(): outgoing socket %d "
"connected to %s:%d\n",
service->bound, connection->number, connection->outgoing,
service->host, service->port);
connection->connected++;
} else {
/* connection failed! */
printf("%d.%d: connect(): failed, to %s:%d, %s\n",
service->bound, connection->number,
service->host, service->port,
strerror(deferred_errno));
/* simulate close of both ends */
connection->fast++;
connection->constipate++;
/* ignore this connection for writeability test now */
FD_CLR(connection->outgoing,&sw);
FD_CLR(connection->incoming,&sw);
FD_CLR(connection->outgoing,&sr);
FD_CLR(connection->incoming,&sr);
FD_CLR(connection->outgoing,&ow);
FD_CLR(connection->incoming,&ow);
FD_CLR(connection->outgoing,&or);
FD_CLR(connection->incoming,&or);
}
} else {
/* start looking for incoming readable data */
FD_SET(connection->incoming,&sr);
/* and stop looking for outgoing writeability */
FD_CLR(connection->outgoing,&sw);
}
}
/* if the incoming connection is now writeable */
if (FD_ISSET(connection->incoming,&ow)) {
/* start looking for outgoing readable data */
FD_SET(connection->outgoing,&sr);
/* and stop looking for incoming writeability */
FD_CLR(connection->incoming,&sw);
}
/* if there is outgoing readable data */
if (FD_ISSET(connection->outgoing,&or)) {
/* transfer it */
bytes = transfer(connection->incoming, connection->outgoing,
connection->allow);
if (bytes == 0) {
if (verbose > 1)
printf("%d.%d: recv(): outgoing socket %d connection closed\n",
connection->service->bound, connection->number,
connection->outgoing);
connection->constipate++;
FD_CLR(connection->outgoing,&sr);
shutdown(connection->incoming,1);
shutdown(connection->outgoing,0);
} else {
/* stop looking for readable data */
FD_CLR(connection->outgoing,&sr);
/* wait for ability to write again */
FD_SET(connection->incoming,&sw);
}
connection->total += bytes;
}
if (FD_ISSET(connection->incoming,&or)) {
bytes = transfer(connection->outgoing, connection->incoming,
connection->allow);
if (bytes == 0) {
if (verbose > 1)
printf("%d.%d: recv(): incoming socket %d connection closed\n",
connection->service->bound, connection->number,
connection->incoming);
connection->fast++;
FD_CLR(connection->incoming,&sr);
shutdown(connection->outgoing,1);
shutdown(connection->incoming,0);
} else {
/* stop looking for readable data */
FD_CLR(connection->incoming,&sr);
/* wait for ability to write again */
FD_SET(connection->outgoing,&sw);
}
connection->total += bytes;
}
/* bandwidth support temporarily disabled until i rework it */
/*
if (connection->service->bandwidth != 0) {
int actual;
connection->now = time(NULL);
actual = (total/(now-start+1));
connection->allow = (connection->service->bandwidth-actual)*5+connection->service->bandwidth;
printf("tcpxd: interim bandwidth %d bytes per second\n", actual);
if (connection->allow < 1) connection->allow = 1;
if (connection->allow > MAXCHUNK) connection->allow = MAXCHUNK;
if (actual > connection->service->bandwidth/2) sleep(1);
}
*/
if (connection->fast && connection->constipate) {
struct connection *waste;
if (verbose > 0)
printf("%d.%d: connection is closed\n", connection->service->bound,
connection->number );
if (verbose > 2)
if (connection->service->bandwidth != 0)
printf("%d.%d: full run bandwidth %d bytes per second\n",
connection->service->bound, connection->number,
(int)(connection->total/(connection->now-connection->start+1)));
status = shutdown(connection->incoming, 2);
if (status == -1) {
if (errno != ENOTCONN) perror("shutdown(): incoming");
}
status = shutdown(connection->outgoing, 2);
if (status == -1) {
if (errno != ENOTCONN) perror("shutdown(): outgoing");
}
status = close(connection->incoming);
if (status) { perror("close(): incoming"); }
status = close(connection->outgoing);
if (status) { perror("close(): outgoing"); }
/* delete connection from list */
if (connection->prior != NULL) {
connection->prior->next = connection->next;
} else {
connections = connection->next;
}
if (connection->next != NULL) {
connection->next->prior = connection->prior;
}
/* gymnastics for freeing */
waste = connection;
connection = connection->next;
free(waste);
if (verbose > 1) printf("\n");
} else {
connection = connection->next;
}
}
} /* while(1) */
}