Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

v47i103: tcp_relay - async relay of TCP packets by a Finite Automaton, Part01/01

12 views
Skip to first unread message

Kiselyov Oleg

unread,
Mar 27, 1995, 1:26:51 PM3/27/95
to
Submitted-by: ol...@ponder.csci.unt.edu (Kiselyov Oleg)
Posting-number: Volume 47, Issue 103
Archive-name: tcp_relay/part01
Environment: C++, tested_on{G++ 2.5.8, Sequent}

Relaying TCP packets from one computer/port to another and doing snooping
at the same time.

The title says it all: this code relays TCP packets between two different
computers (and/or ports). That is, the program listens on port Listen_port.
After it got a connection, it connects to Target_host:Target_port, and,
if everything goes well, the program relays packets between the two
connections, and also, it keeps a log of all packets received/sent.
After the connection is closed (normally or semi-normally) the program
listens again.

The feature of this program is an asynchronous (by SIGIO) reading
of communication channels. So, most of the time the program just
sleeps (without taking of any CPU time) and wakes up only when
some packet shows up in one of the channels. The program also contains
a fairly complete class library for the TCP (stream) sockets.
Another feature of the code is using of a Finite State Machine to
do relay and handle all possible special situations.

The code has a lot of comments in it. I hope it will be useful.
In any case, I'll try to answer any questions/problems/comments.

Oleg
------------
#! /bin/sh
# This is a shell archive. Remove anything before this line, then feed it
# into a shell via "sh file" or similar. To overwrite existing files,
# type "sh file -c".
# Contents: myenv.cc myenv.h tcp_relay.cc
# Wrapped by kent@ftp on Sat Mar 25 15:37:16 1995
PATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:/usr/lbin:$PATH ; export PATH
echo If this archive is complete, you will see the following message:
echo ' "shar: End of archive 1 (of 1)."'
if test -f 'myenv.cc' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'myenv.cc'\"
else
echo shar: Extracting \"'myenv.cc'\" \(1950 characters\)
sed "s/^X//" >'myenv.cc' <<'END_OF_FILE'
X// This may look like C code, but it is really -*- C++ -*-
X/*
X ************************************************************************
X * Service C++ functions
X * that support the standard environment for me
X */
X
X#pragma implementation
X
X#include "myenv.h"
X#include <builtin.h>
X#include <stdarg.h>
X
X/*
X *-----------------------------------------------------------------------
X * Some global constant pertaining to input/output
X */
X
Xconst char _Minuses [] = "\
X-------------------------------------------------------------------------------";
X
Xconst char _Asteriscs [] = "\
X*******************************************************************************";
X
Xconst char _Equals [] = "\
X===============================================================================";
X
X
X/*
X *------------------------------------------------------------------------
X * Print an error message at stderr and abort
X * Synopsis
X * volatile void _error(const char * message,... );
X * Message may contain format control sequences %x. Items to print
X * with the control sequences are to be passed as additional arguments to
X * the function call.
X */
X
Xvolatile void _error(const char * message,...)
X{
X va_list args;
X va_start(args,message); /* Init 'args' to the beginning of */
X /* the variable length list of args*/
X fprintf(stderr,"\n_error:\n");
X vfprintf(stderr,message,args);
X fputs("\n",stderr);
X abort();
X}
X
X
X/*
X *------------------------------------------------------------------------
X * Print a message at stderr
X * Synopsis
X * void message(const char * text,... );
X * Message may contain format control sequences %x. Items to print
X * with the control sequences are to be passed as additional arguments to
X * the function call.
X */
X
Xvoid message(const char * text,...)
X{
X va_list args;
X va_start(args,text); /* Init 'args' to the beginning of */
X /* the variable length list of args*/
X vfprintf(stderr,text,args);
X}
X
END_OF_FILE
if test 1950 -ne `wc -c <'myenv.cc'`; then
echo shar: \"'myenv.cc'\" unpacked with wrong size!
fi
# end of 'myenv.cc'
fi
if test -f 'myenv.h' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'myenv.h'\"
else
echo shar: Extracting \"'myenv.h'\" \(1362 characters\)
sed "s/^X//" >'myenv.h' <<'END_OF_FILE'
X// This may look like C code, but it is really -*- C++ -*-
X//************************************************************************
X//
X// A standard environment
X// I am accustomed to
X
X//#pragma once
X#ifndef _myenv_h
X
X#define _myenv_h
X#pragma interface
X
X /* Strings of symbols */
X /* They may be used as a delimiting lines*/
Xextern const char _Minuses [];
Xextern const char _Asteriscs [];
Xextern const char _Equals [];
X
X /* Print an error message at stderr and */
X /* abort */
Xvolatile void _error(
X const char * message, /* Message to be printed */
X ... /* Additional args to printf */
X );
X
X /* Print a message at stderr */
Xvoid message(
X const char * text, /* Message to be printed */
X ... /* Additional args to printf */
X );
X
X
X//------------------------------------------------------------------------
X// Verify the assertion
X
X#if 0
X /* Print a message and abort*/
Xextern volatile void _error( const char * message,... );
X#endif
X
X#define assert(ex) \
X (void)((ex) ? 1 : \
X (_error("Failed assertion " #ex " at line %d of `%s'.\n", \
X __LINE__, __FILE__), 0))
X#define assertval(ex) assert(ex)
X
X#define assure(expr,message) \
X if (expr) ; \
X else _error("%s\n at line %d of '%s'.",message,__LINE__, __FILE__);
X
X
X#endif
END_OF_FILE
if test 1362 -ne `wc -c <'myenv.h'`; then
echo shar: \"'myenv.h'\" unpacked with wrong size!
fi
# end of 'myenv.h'
fi
if test -f 'tcp_relay.cc' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'tcp_relay.cc'\"
else
echo shar: Extracting \"'tcp_relay.cc'\" \(17752 characters\)
sed "s/^X//" >'tcp_relay.cc' <<'END_OF_FILE'
X// This may look like C code, but it is really -*- C++ -*-
X/*
X ************************************************************************
X *
X * Relaying TCP packets from one computer/port to another
X * and doing snooping at the same time
X *
X * The title says it all: this code relays TCP packets between two different
X * computers (and/or ports). That is, the program listens on port Listen_port.
X * After it got a connection, it connects to Target_host:Target_port, and,
X * if everything goes well, the program relays packets between the two
X * connections, and also, it keeps a log of all packets received/sent.
X * After the connection is closed (normally or semi-normally) the program
X * listens again.
X *
X * Possible applications of the program: relaying packets from one port
X * to another (say, some netnazi sites, in a stupid attempt to prevent people
X * from playing MUD, firewall all connections to "suspicios" ports,
X * usually ones above 6000). So, one can run this program on some computer
X * (outside the firewall) and have it take packets from some legitimate
X * port, say, 25 or 119, and relay them to the "real" port, 9000 or so, on a
X * MUD machine.
X * Another possible application (which actually I had in mind writing
X * this program) is to snoop on the protocol, say, get a "carbon copy"
X * of the http chat between two hosts.
X *
X * The feature of this program is an asynchronous (by SIGIO) reading
X * of communication channels. So, most of the time the program just
X * sleeps (without taking of any CPU time) and wakes up only when
X * some packet shows up in one of the channels. The program also contains
X * a fairly complete class library for the TCP (stream) sockets.
X * Another feature of the code is using of a Finite State Machine to
X * do relay and handle all possible special situations.
X *
X * $Id: tcp_relay.cc,v 1.2 1995/01/24 15:30:20 oleg Exp oleg $
X *
X ************************************************************************
X */
X
X#include "myenv.h"
X#include <std.h>
X#include <fstream.h>
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <netdb.h>
X#include <signal.h>
X#include <fcntl.h>
Xextern "C"
X{
X // Convertion between the host and the network
X // byte orders
Xunsigned short htons(unsigned short data); // For a short data item
Xunsigned short ntohs(unsigned short data); // For a short data item
Xunsigned long htonl(unsigned long data); // For a long data item
X#include <netinet/in.h>
X#include <arpa/inet.h>
X}
X
Xenum BOOL { FALSE=0, TRUE };
X
Xstatic const char * Target_host = "zaphod.csci.unt.edu";
X//static const char * Target_host = "hypatia.gsfc.nasa.gov";
X//static const char * Target_host = "pubweb.parc.xerox.com";
Xstatic const short Target_port = 80; // http port
X//static const short Target_port = 7; // echo port
Xstatic const short Listen_port = 4003;
X
X // Make sure that a system call went well
X // If a system call (say, fcntl()) fails
X // it returns -1 and sets the errno
X#define do_well(FX) (void)( (FX) < 0 ? (perror("System error"), \
X _error("Failed system call " #FX " at line %d of `%s'.\n", \
X __LINE__, __FILE__),0) : 1)
X
X
X/*
X *------------------------------------------------------------------------
X * Class IPaddress that provides
X * addressing and naming in the IP network
X */
X
Xclass SocketAddr;
X
X // IP address
Xclass IPaddress
X{
X friend class SocketAddr;
X
X unsigned long address; // Address: 4 bytes in the network
X // byte order
X IPaddress(const unsigned int netaddr) : address(netaddr) {}
X
Xpublic:
X IPaddress(void) { address = INADDR_ANY; } // Wildcard address
X IPaddress(const char * name); // Involves the name resolution
X ~IPaddress(void) {}
X unsigned long net_addr(void) const { return address; }
X operator const char * (void) const; // get a STATIC symbolic representation
X // of an IP address
X};
X
X // Obtains the IP address of the specified
X // host. The host name may be specified either
X // in the dot notation, or as a host "name".
X // Name resolution is performed in the
X // latter case.
XIPaddress::IPaddress(const char * host_name)
X{
X // First check to see if the host
X // name is specified in the IP address
X // dot notation
X if( (address = inet_addr(host_name)) != (unsigned long)(-1) )
X return;
X
X struct hostent *host_ptr = ::gethostbyname(host_name);
X if( host_ptr == 0 )
X _error("Host name '%s' cannot be resolved",host_name);
X if( host_ptr->h_addrtype != AF_INET )
X _error("'%s' isn't an Internet site, or so the DNS says",host_name);
X
X address = *(unsigned long *)(host_ptr->h_addr);
X}
X
X
X // get a STATIC symbolic representation
X // of an IP address
X // We use the fact that the IP address is in
X // the net order, that is, the MSB first
X // We do reverse name resolution if necessary
XIPaddress::operator const char * (void) const
X{
X struct hostent *host_ptr = ::gethostbyaddr((char *)&address,
X sizeof(address), AF_INET);
X if( host_ptr != 0 )
X return host_ptr->h_name;
X
X // Reverse DNS failed, use print in
X // the dot notation
X static char buffer[80];
X sprintf(buffer,"%0d.%0d.%0d.%0d", address & 0xff, (address >> 8) & 0xff,
X (address >> 16) & 0xff, (address >> 24) & 0xff );
X return buffer;
X}
X
X/*
X *------------------------------------------------------------------------
X * Sockets
X */
X
Xclass StreamSocket;
X
Xclass SocketAddr : sockaddr_in
X{
X friend class StreamSocket;
X SocketAddr(void) {}
X
Xpublic:
X SocketAddr(const IPaddress host, const short port_no);
X ~SocketAddr(void) {}
X operator sockaddr * (void) const { return (sockaddr *)this; }
X operator const char * (void) const; // Give a STATIC string representation
X};
X
X // This can be either listening (passive)
X // socket or connective (active) socket.
X // That's why we don't have a public
X // constructor here
Xclass StreamSocket
X{
Xpublic:
X enum Status { Good, Nodata, Eof, Failure };
X
Xprotected:
X const int socket_handle; // Socket handle = file handle
X Status status;
X
X StreamSocket(void);
X StreamSocket(const int _socket_handle); // wrap a class around
X
Xpublic:
X ~StreamSocket(void);
X SocketAddr get_my_name(void) const; // Name of this socket
X SocketAddr get_peer_name(void) const; // Name of the socket this
X // socket is connected to
X void print(const char * title) const; // Print the socket status
X
X void set_blocking_io(const BOOL onoff);
X void enable_sigio(const BOOL onoff);
X
X // Write to the socket, return status
X Status write(const void * buffer, const unsigned int len);
X // Read from the socket returning the
X // status and size of the block read
X struct ReadResult { unsigned int len; Status status; };
X ReadResult read(void * buffer, const unsigned int len);
X};
X
Xclass Listen : public StreamSocket
X{
Xpublic:
X Listen(const SocketAddr bind_address);
X ~Listen(void) {}
X StreamSocket * accept(const int backlog=1);
X};
X
X // This is a socket to initiate an active
X // TCP open, that is, a connection. The
X // constructor attempts to connect to another
X // (hopefully listening) socket on
X // probably another computer/port
Xclass ConnectingSocket : public StreamSocket
X{
Xpublic:
X ConnectingSocket(const SocketAddr target_address);
X ~ConnectingSocket(void) {}
X};
X
X // Create an IP address space entity
XSocketAddr::SocketAddr(const IPaddress host, const short port_no)
X{
X sin_family = AF_INET;
X sin_port = htons((short)port_no);
X sin_addr.s_addr = host.net_addr();
X}
X
X // Give a STATIC string representation of a socket
X // address
XSocketAddr::operator const char * (void) const
X{
X assert( sin_family == AF_INET );
X static char buffer[80];
X
X sprintf(buffer,"%s:%d",(const char *)IPaddress(sin_addr.s_addr),
X ntohs((short)sin_port));
X return buffer;
X}
X
X // Create an unbound stream socket
XStreamSocket::StreamSocket(void)
X : socket_handle(socket(AF_INET,SOCK_STREAM,0))
X{
X do_well( socket_handle );
X status = Good;
X}
X
X // Wrap a class around a (supposedly open)
X // socket
XStreamSocket::StreamSocket(const int _socket_handle)
X : socket_handle(_socket_handle)
X{
X if( socket_handle < 0 )
X _error("Bad _socket_handle was passed to the class StreamSocket");
X status = Good;
X}
X
X // Close the socket
XStreamSocket::~StreamSocket(void)
X{
X assert( socket_handle >= 0 );
X assert( close(socket_handle) == 0 );
X status = Failure;
X}
X
X // Get the name of this socket
XSocketAddr StreamSocket::get_my_name(void) const
X{
X SocketAddr sock_addr;
X int namelen = sizeof(sock_addr);
X do_well( ::getsockname(socket_handle, (sockaddr *)sock_addr, &namelen) );
X assert( namelen == sizeof(sock_addr) );
X return sock_addr;
X}
X
X // Get the name of the socket this socket is
X // connected to
XSocketAddr StreamSocket::get_peer_name(void) const
X{
X SocketAddr sock_addr;
X int namelen = sizeof(sock_addr);
X do_well( ::getpeername(socket_handle, (sockaddr *)sock_addr, &namelen) );
X assert( namelen == sizeof(sock_addr) );
X return sock_addr;
X}
X
X // Print the socket status
Xvoid StreamSocket::print(const char * title) const
X{
X message("%s socket %s ",title,(const char *)get_my_name());
X message("is connected to %s ",(const char *)get_peer_name());
X switch(status)
X {
X case Good:
X message("\n");
X break;
X
X case EOF:
X message("(got EOF)\n");
X break;
X
X case Failure:
X message("Failed\n");
X break;
X
X default:
X _error("socket is in a weird state %d",status);
X }
X}
X
X // Set blocking/non-blocking I/O
X // If the blocking i/o is set, reading
X // on socket blocks the process until
X // the packet arrives. If i/o is non-blocking,
X // read() returns -1 with errno=EWOULDBLOCK
X // if there is nothing to read
Xvoid StreamSocket::set_blocking_io(const BOOL onoff)
X{
X int arg = fcntl(socket_handle,F_GETFL,0);
X do_well( arg );
X do_well( fcntl(socket_handle,F_SETFL,onoff ? arg & ~FNDELAY :
X arg | FNDELAY) );
X}
X
X // Enable/disable SIGIO upon arriving of a
X // new packet
Xvoid StreamSocket::enable_sigio(const BOOL onoff)
X{
X int arg = fcntl(socket_handle,F_GETFL,0);
X do_well( arg );
X do_well( fcntl(socket_handle,F_SETFL,onoff ? arg | FASYNC :
X arg & ~FASYNC) );
X if( onoff ) // Tell which process should get SIGIO
X do_well( fcntl(socket_handle,F_SETOWN,-getpid()) );
X// message("after enable_sigio: flags 0x%x\n",fcntl(socket_handle,F_GETFL,0));
X// message("owner %d\n",fcntl(socket_handle,F_GETOWN,0));
X}
X
X
X // Write to the socket, return the
X // status
XStreamSocket::Status
XStreamSocket::write(const void * buffer, const unsigned int len)
X{
X assert( status == Good );
X assert( len != 0 );
X if( ::write(socket_handle,buffer,len) < 0 )
X {
X perror("Socket write failed");
X return status = Failure;
X }
X return Good;
X}
X // Read from the socket returning the
X // size of the block read
XStreamSocket::ReadResult
XStreamSocket::read(void * buffer, const unsigned int len)
X{
X assert( status == Good );
X assert( len != 0 );
X ReadResult result = { ::read(socket_handle,buffer,len), Good };
X if( result.len == 0 )
X return result.status = (status = Eof), result;
X if( result.len == (unsigned)(-1L) )
X if( errno == EWOULDBLOCK )
X return result.len = 0, result.status = Nodata, result;
X else
X {
X perror("reading from the socket");
X return result.len = 0, result.status = (status = Failure), result;
X }
X return result;
X}
X // Create a bound socket
XListen::Listen(const SocketAddr bind_address)
X{
X do_well( ::bind(socket_handle, (sockaddr *)bind_address,
X sizeof(bind_address)) );
X}
X
X // Listen for a connection and accept one
XStreamSocket * Listen::accept(const int backlog)
X{
X do_well( ::listen(socket_handle,backlog) );
X const int accepted_socket = ::accept(socket_handle,0,0);
X do_well( accepted_socket );
X return new StreamSocket(accepted_socket);
X}
X
X // Initiate a TCP connection
XConnectingSocket::ConnectingSocket(const SocketAddr target_address)
X{
X do_well( ::connect(socket_handle, (sockaddr *)target_address,
X sizeof(target_address)) );
X}
X
X/*
X *------------------------------------------------------------------------
X * Here's where all relaying is going on...
X * Note we're forced to use static members in the PacketRelay class
X * because the relay function is called asynchronously, upon the SIGIO (that
X * is, when some packet arrives), so the relay function has to be static.
X */
X
Xclass PacketRelay
X{
X StreamSocket& sock1;
X StreamSocket& sock2;
X
X char sock1_name[80]; // Socket names for easy reference
X char sock2_name[80]; // (name cache)
X
X char * buffer;
X enum { IO_buffer_size=12000 };
X
X BOOL done;
X
X static PacketRelay * the_PacketRelay;
X
X void intr_enable(void);
X void intr_disable(void);
X
X // Finite State Machine "Actors"
X enum StatusCode {OK=0, NoData, Eof, Failed}; // what Actors return
X enum SOCK_ID { id_sock1, id_sock2 };
X typedef StatusCode (*Actor)(PacketRelay * _this, const SOCK_ID id);
X
X // Meaning write to another socket and
X // keep the log
X StatusCode relay_socket(const SOCK_ID id);
X BOOL done_loop;
X StatusCode is_loop_done(const SOCK_ID id) { return done_loop ? Eof : OK; }
X StatusCode set_loop_done(const SOCK_ID id) { done_loop = TRUE; return OK; }
X StatusCode reset_loop_done(const SOCK_ID id) { done_loop = FALSE; return OK;}
X StatusCode set_finished(const SOCK_ID id) { done = TRUE; return OK;}
X
X // Relay Finite State Machine
X struct FSMAction { Actor actor;
X SOCK_ID socket_to_use;
X short next_state[Eof+1]; };
X static FSMAction FSMProgram [];
X
Xpublic:
X PacketRelay(StreamSocket& _sock1, StreamSocket& _sock2);
X ~PacketRelay(void);
X BOOL q_done(void) const { return done; }
X static int relay(void); // That's what relays (if anything)
X};
X
XPacketRelay * PacketRelay::the_PacketRelay = 0;
X
X // Here is the FSM relay program
XPacketRelay::FSMAction PacketRelay::FSMProgram [] =
X{
X /* 0 */ {0, id_sock1, {0,0,0}}, // Dummy (stop) state
X
X// State What to do on which socket OK ND EOF
X /* 1 */ { (Actor)&relay_socket, id_sock1, {2, 3, 5} }, // start state
X /* 2 */ { (Actor)&relay_socket, id_sock2, {1, 4, 6} },
X /* 3 */ { (Actor)&set_loop_done, id_sock1, {2, -1, -1} },
X /* 4 */ { (Actor)&is_loop_done, id_sock1, {1, 0, 0} },
X /* 5 */ { (Actor)&relay_socket, id_sock2, {5, 7, 7} },
X /* 6 */ { (Actor)&relay_socket, id_sock1, {6, 7, 7} },
X /* 7 */ { (Actor)&set_finished, id_sock1, {0, 0, 0} }
X};
X
X // Initialize relay and set up signal
X // handling
XPacketRelay::PacketRelay(StreamSocket& _sock1, StreamSocket& _sock2)
X : sock1(_sock1), sock2(_sock2), done(FALSE), done_loop(FALSE)
X{
X the_PacketRelay = this; // For ref by static members
X buffer = new char[IO_buffer_size];
X assert( buffer != 0 );
X strncpy(sock1_name,sock1.get_peer_name(),sizeof(sock1_name)-2);
X strncpy(sock2_name,sock2.get_peer_name(),sizeof(sock2_name)-2);
X sock1.set_blocking_io(FALSE);
X sock2.set_blocking_io(FALSE);
X intr_enable();
X sock1.enable_sigio(TRUE);
X sock2.enable_sigio(TRUE);
X relay(); // Push to start
X}
X
X // Do some minor clean-up
XPacketRelay::~PacketRelay(void)
X{
X sock1.enable_sigio(FALSE); // do it first do disable all further
X sock2.enable_sigio(FALSE); // signals
X intr_disable(); // now we can disengage our handler
X assert( buffer != 0 );
X delete buffer;
X buffer = 0;
X the_PacketRelay = 0;
X}
X
Xvoid PacketRelay::intr_enable(void)
X{
X do_well( (int)signal(SIGIO,relay) );
X}
X
Xvoid PacketRelay::intr_disable(void)
X{
X do_well( (int)signal(SIGIO,SIG_DFL) );
X}
X
X // Read the specified socket and relay
X // data to the other socket
X // and keep the log
XPacketRelay::StatusCode PacketRelay::relay_socket(const SOCK_ID id)
X{
X StreamSocket::ReadResult read_result =
X (id == id_sock1 ? sock1 : sock2).read(buffer,IO_buffer_size);
X
X if( read_result.status == StreamSocket::Nodata )
X return NoData;
X else if( read_result.status == StreamSocket::Eof )
X {
X message("\nEOF from '%s':\n",id == id_sock1 ? sock1_name : sock2_name);
X return Eof;
X }
X else if( read_result.status != StreamSocket::Good )
X return Failed;
X
X assert( read_result.len > 0 );
X message("\ndata from '%s':\n", id == id_sock1 ? sock1_name : sock2_name);
X for(register char * bp=buffer; bp < buffer+read_result.len; bp++)
X if( *bp != '\r' )
X message("%c",*bp);
X
X StreamSocket& socket = id == id_sock1 ? sock2 : sock1;
X if( socket.write(buffer,read_result.len) != StreamSocket::Good )
X return Failed;
X return reset_loop_done(id);
X}
X
X // This is where it relays: the Finite
X // State Machine
X // Start with the state #1, do the action and
X // pick up the next state based on the action's
X // return code. Keep doing it until hit the
X // stop state (pc=0)
Xint PacketRelay::relay(void)
X{
X PacketRelay& me = *the_PacketRelay;
X if( me.done )
X return 0;
X
X int pc; // "Program" counter
X for(pc=1; pc != 0; )
X {
X assert( pc > 0 );
X const FSMAction& action = FSMProgram[pc];
X StatusCode result = action.actor(&me,action.socket_to_use);
X if( result == Failed )
X return me.done = TRUE, 0;
X pc = action.next_state[result];
X }
X return 0;
X}
X
X/*
X *------------------------------------------------------------------------
X * Root module
X */
X
Xmain(void)
X{
X Listen listen_socket(SocketAddr(IPaddress(),Listen_port));
X message("\nPrepare to listen on port %d\n",Listen_port);
X
X for(;;)
X {
X StreamSocket& source_socket = *listen_socket.accept(1);
X source_socket.print("Got connection");
X ConnectingSocket target_socket(SocketAddr(Target_host,Target_port));
X target_socket.print("Target");
X
X {
X PacketRelay relay(source_socket,target_socket);
X
X while( !relay.q_done() )
X pause();
X } // relay is to be destroyed here
X
X delete &source_socket;
X }
X}
X
END_OF_FILE
if test 17752 -ne `wc -c <'tcp_relay.cc'`; then
echo shar: \"'tcp_relay.cc'\" unpacked with wrong size!
fi
# end of 'tcp_relay.cc'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have the archive.
rm -f ark[1-9]isdone
else
echo You still must unpack the following archives:
echo " " ${MISSING}
fi
exit 0
exit 0 # Just in case...

0 new messages