17. Sockets

Introduction

/* ------------------------------------------------------------------ */
/* REXX socket support is via third-party library. The one utilised   */
/* in this chapter is the RxSock Library [see Appendix for details],  */
/* an implementation which, because it is so widely available, and on */
/* so many different platforms, can be taken as being the standard    */
/* [albeit a 'defacto' one] approach to socket handling. Its use      */
/* [on most platforms] assumes the following prologue code is used:   */
/*                                                                    */
/*     call rxFuncAdd 'sockLoadFuncs', 'rxSock', 'SockLoadFuncs'      */
/*     call sockLoadFuncs                                             */
/*                                                                    */
/* and the following epilogue code [usually at program's end]:        */
/*                                                                    */
/*     call sockDropFuncs                                             */
/*                                                                    */
/* Please note that the name of the library may be case-sensitive in  */
/* some environments e.g. in *NIX / Linux environments the name may   */
/* need to be changed to, 'rxsock' to match part of the file name,    */
/* 'librxsock.so'. Other RxSock library issues are discussed below.   */
/*                                                                    */
/* Like similar libraries in other languages, the RxSock library is   */
/* basically a set of routines that map to the TCP/IP Socket API [i.e */
/* they are 'wrappers' around  C functions] that is available on prob-*/
/* ably every Internet-connected computer. Using this library will be */
/* quite straightforward for those already familiar with this API. For*/
/* others it should be no more difficult than learning file handling  */
/* and related techniques. The following tutorials may be of use:     */
/*                                                                    */
/* * http://www.frostbytes.com/~jimf/papers/sockets/winsock.html      */
/* * http://wi.wu-wien.ac.at/rgf/rexx/tmp/socktut.pdf                 */
/*                                                                    */
/* The RxSock library is notable for being easy to use partly because */
/* there are fewer data conversion issues with which to contend. For  */
/* example an issue needing handling in even high-level languages like*/
/* Perl and Python is the conversion of a host address in a 'packed'  */
/* format [i.e. 32 bit binary value] to / from human-readable string. */
/* In REXX both hostnames and IP addresses are represented in string  */
/* form [from the programmer's perspective], thus eliminating one area*/
/* of potential difficulty.                                           */
/*                                                                    */
/* RxSock library issues:                                             */
/*                                                                    */
/* * Behavioural and syntax variations among implementations:         */
/*   - Not all socket options universally applicable                  */
/*   - Not all socket routines work on all platforms                  */
/*   - Protocal inconsistancies such as UDP working well in one imple-*/
/*     mentation but not on another                                   */
/*   - Routine name/parameter differences                             */
/*                                                                    */
/* * Minimal and/or inconsistent raw socket support. Whilst "SOCK_RAW"*/
/*   type sockets may be specified in 'sockSocket' calls, not all     */
/*   implementations create such sockets; some simply return an error */
/*   value in place of a socket. Also, there is no support for either */
/*   IPPROTO_RAW or IPPROTO_ICMP protocols, thus, effectively, there  */
/*   really is *no* raw socket support                                */
/*                                                                    */
/* * Error handling differs between implementations. Some update the  */
/*   'errNo' variable, whilst others implement a 'sockErrNo' variable;*/
/*   safest to rely on checking for negative return value as an error */
/*   indicator                                                        */
/*                                                                    */
/* It is probably safe to say that the RxSock library is suitable for */
/* conventional socket programming [i.e. projects involving either TCP*/
/* or UDP transports]. If anything more specialist is required either */
/* the library needs to be modified [possible: source code supplied], */
/* or access to suitable wrapper functions or another external library*/
/* be made available.                                                 */
/*                                                                    */
/* Some comments on forking ...                                       */
/*                                                                    */
/* Finally, it is possible to use the General Call Interface [GCI] to */
/* directly access socket-related functions [e.g. 'gethostname']; it  */
/* is safest to avoid this practice because it can see the avoidance  */
/* of sometimes vital initialisation steps. For example, in the Win32 */
/* environment, the 'WSAStartup' function needs to be called prior to */
/* using any socket-related functions, and, for proper cleanup, the   */
/* corresponding 'WSACleanup' function should be called. Such steps   */
/* are performed as part of 'rxSock' external library housekeeping,   */
/* so best use those routines for such functionality.                 */
/* ------------------------------------------------------------------ */

/* Load general-purpose functions from external library */
call rxFuncAdd 'sysLoadFuncs', 'rexxUtil', 'sysLoadFuncs'
call sysLoadFuncs

/* Load rxSock functions from external library */
call rxFuncAdd 'sockLoadFuncs', 'rxSock', 'SockLoadFuncs'
call sockLoadFuncs

/* ----------------------------- */

/*
   RxSock library does not use 'packed' IP addresses, so the custom
   subroutines, 'inet_aton', and 'inet_ntoa', whilst having been
   implemented [to illustrate byte <=> string conversion in REXX],
   are not actually required for socket-based communication
*/

/* Convert human readable form to 32 bit value */
packed_ip = inet_aton("208.201.239.36")

drop host. ; call sockGetHostByName "www.oreilly.com", 'host.!'
packed_ip = inet_aton(host.!ADDR)

/* ----------------------------- */

/* Convert 32 bit value to ip address */
ip_address = inet_ntoa(packed_ip)

/* ----------------------------- */

/* Create socket object */
family = 'AF_INET' ; type = 'SOCK_STREAM'
protocol = 'IPPROTO_TCP' /* or '0' for default */

socketobj = sockSocket(family, type, protocol)

/* ----------- */

/* Extract local-side details of this socket */ 
drop host. ; call sockGetSockName socketobj, 'host.!'

/*
   host.!ADDR => 0.0.0.0 unless connected
   host.!PORT => 0 unless connected
*/

/* ----------------------------- */

/*
   Helper functions
*/

die : procedure expose (globals)
  call LINEOUT , ARG(1) ; exit ARG(2) ; return NULL

/* ----------- */

isValidIP : procedure expose (globals)
  ip = ARG(1) ; lastType = DATATYPE(SUBSTR(ip, LASTPOS(".", ip) + 1))
  if ip == NULL | COUNTSTR(".", ip) \= 3 | lastType \= 'NUM' then
    return FALSE
  do while ip <> NULL
    parse var ip octet "." ip
    if DATATYPE(octet) \= 'NUM' then ; return FALSE
    if octet < 0 | octet > 255 then ; return FALSE
  end
  return TRUE

/* ----------- */

inet_aton : procedure expose (globals)
  ip = ARG(1) ; packed = NULL
  do while ip <> NULL
    parse var ip octet "." ip
    packed = packed || D2X(octet, 2)
  end
  return X2C(packed)

/* ----------- */

inet_ntoa : procedure expose (globals)
  packed = C2X(ARG(1)) ; parse var packed octet +2 packed
  ip = X2D(octet)
  do while packed <> NULL
    parse var packed octet +2 packed
    ip = ip || "." || X2D(octet)
  end
  return ip

Writing a TCP Client

/*
   Establishing a simple, TCP-based client connection to a host
*/

/* Create socket */
peer = sockSocket('AF_INET', 'SOCK_STREAM', '0')

if peer < 0 then
  call die "Couldn't create socket :" socksock_errno(), 1

/* Set up for peer connection */
drop peer.
peer.!FAMILY = 'AF_INET'
peer.!ADDR = "192.168.1.1"  /* Target ipaddr */
peer.!PORT = 13             /* Target port */

/* Make the connection */
if sockConnect(peer, 'peer.!') < 0 then
  call die "Couldn't connect socket to" peer.!ADDR || ":" ||,
           peer.!PORT, ":" socksock_errno(), 1

/* Do something with the socket ... */
if sockSend(peer, "Why don't you call anymore?" || NEWLINE) < 0 then
  call die "Error sending data on socket :" socksock_errno(), 1

MAX_BYTES = 256

if sockRecv(peer, 'answer', MAX_BYTES) < 0 then
  call die "Error receiving data from socket :" socksock_errno(), 1

/* Terminate when done - close socket, releasing all its resources */
if sockClose(peer) < 0 then
  call die "Error closing socket :" socksock_errno(), 1

/* ----------------------------- */

/*
   Additional Perl examples either redundant because with RxSock:
   * It is not possible to specify host information as part of the socket
     creation call
   * There is only one set of procedures for setting up and handling
     connections
*/

/* ----------------------------- */

/* Set up for listening on specified port */
drop assoc.
assoc.!FAMILY = 'AF_INET'
assoc.!ADDR = 'INADDR_ANY'   /* Use first defined [local] ipaddr */
assoc.!PORT = 23             /* Port on which to listen */

/* Bind to socket */
if sockBind(socket, 'assoc.!') < 0 then
  call die "Error binding to socket :" socksock_errno(), 1

/* ----------- */

drop assoc. ; call sockGetHostByName "www.oreilly.com", 'assoc.!'

/* Bind to socket */
if sockBind(socket, 'assoc.!') < 0 then
  call die "Error binding to socket :" socksock_errno(), 1

Writing a TCP Server

/*
   Implementing a simple, TCP-based server

   The Perl examples are consolidated into the following, single code,
   example because, while Perl offers multiple socket libraries and
   options, REXX uses 'rxSock', a set of wrapper functions, whose use
   varies little except for argument-passing differences 
*/

/* Service will be available on port 1996 */
port = 1996

/* Create socket */
server = sockSocket('AF_INET', 'SOCK_STREAM', '0')

if server < 0 then
  call die "Couldn't create server socket :" socksock_errno(), 1

/* Allow socket reuse */
if sockSetSockOpt(server, 'SOL_SOCKET', 'SO_REUSEADDR', 1) < 0 then
  call die "Couldn't set options on server socket :" socksock_errno(), 1

/* Set up for server connection */
drop server.
server.!FAMILY = 'AF_INET'
server.!ADDR = 'INADDR_ANY'   /* Use first defined [local] ipaddr */
server.!PORT = port           /* Port on which to listen */

/* Need to bind socket to port, then commence listening on that port */
if sockBind(server, 'server.') < 0 then
  call die "Couldn't bind server socket to port :" socksock_errno(), 1

/* Queue up 1 connection only */
if sockListen(server, 1) < 0 then
  call die "Couldn't commence listening on server socket :",
           socksock_errno(), 1

MAX_BYTES = 256

/* Poll for client connections */
do forever
  /* Block until a connection comes in */
  client = sockAccept(server)

  if client < 0 then
    call die "Error accepting client connection :" sockErrNo, 1
  
  /* Service client ... */

  if sockRecv(client, 'buffer', MAX_BYTES) < 0 then
    call die "Error receiving data from client socket :",
             socksock_errno(), 1

  say "|" buffer "|"

  if sockSend(client, "..." || NEWLINE) < 0 then
    call die "Error responding on client socket :" socksock_errno(), 1

  if sockShutDown(client) < 0 then
    call die "Error shutting down client socket :" socksock_errno(), 1

  if sockClose(client) < 0 then
    call die "Error closing client socket :" socksock_errno(), 1

  leave

  /* ... */
end

/*
   Terminate when done - shutdown, close the socket, and release all
   its resources
*/
if sockShutDown(server) < 0 then
  call die "Error shutting down server socket :" socksock_errno(), 1

if sockClose(server) < 0 then
  call die "Error closing server socket :" socksock_errno(), 1

Communicating over TCP

/* ------------------------------------------------------------------ */
/* Once a socket is created, and a TCP-based connection established,  */
/* it may be used for *bidirectional* communication, that is, for both*/
/* writing to, and reading data from, a host.                         */
/*                                                                    */
/* Perl allows the use of 'file descriptor semantics' for this task in*/
/* addition to specialised subroutines / methods. This is not the case*/
/* in REXX where the following 'rxSock' library routines must be used:*/
/*                                                                    */
/* * Reading: sockRecv, sockRecvFrom [datagrams]                      */
/* * Writing: sockSend, sockSendTo [datagrams]                        */
/*                                                                    */
/* ------------------------------------------------------------------ */

/*
   No REXX equivalent to Perl 'file descriptor semantics':

       print $SERVER "What is your name?\n";
       chomp ($response = <SERVER>);
*/

/* ----------------------------- */

/*
   Examples assume same names / values from previous two examples:

   peer    --> client-side socket connected to server; peer reads and
               writes to server using this socket 
   server  --> server-side socket; waits for clients to call, then
               creates a client socket for each new client
   client  --> server-side socket representing connection from peer;
               server reads and writes to peer using this socket
*/

/* ----------------------------- */

data_to_send = "..."

call sockSend peer, data_to_send || NEWLINE

if sockErrNo \= 0 then
  call die "Error sending data on socket :" sockErrNo, 1

/* ----------- */

data_read_buffer = NULL ; MAX_BYTES = 256

call sockRecv peer, 'data_read_buffer', MAX_BYTES

if sockErrNo \= 0 then
  call die "Error receiving data from socket :" sockErrNo, 1

/* ----------------------------- */

/*
  'select' [using 'sockSelect'] example
  ---
  's1', 's2', and 's3' are previously-created sockets. The compound
  variable contains the sockets for which 'readable' status is to be
  determined
*/
reads.0 = 3 ; reads.1 = s1 ; reads.2 = s2 ; reads.3 = s3

/* 5 second timeout [omit argument to have 'sockSelect' block] */
timeout = 5

/* Issue call; success sees 'count' > 0, 'reads.' updated */
count = sockSelect('reads.',,, timeout)

/* Check 'select' status */
select
  when count == -1 then
    /* Same as checking for 'sockErrNo' \= 0 */
    call die "Error in 'sockSelect' call :" sockErrNo, 1

  when count == 0 then
    /* ... handle timeout ...*/
    say "'sockSelect' timeout"

  when count > 0 then
    /*
       Successful status update:

       reads.0       --> Number of 'readable' sockets 
       reads.1 ... N --> Set of sockets that are 'readable' [input
                         values have been overwritten]
    */

    /* ... handle success ...*/

end

/* ----------------------------- */

/* Enable TCP_NODELAY on socket */
call sockSetSockOpt server, 'SOL_SOCKET', 'TCP_NODELAY', 1

if sockErrNo \= 0 then
  call die "Couldn't disable Nagle's algorithm :" sockErrNo, 1

/* ----------- */

/* Disable TCP_NODELAY on socket */
call sockSetSockOpt server, 'SOL_SOCKET', 'TCP_NODELAY', 2

if sockErrNo \= 0 then
  call die "Couldn't enable Nagle's algorithm :" sockErrNo, 1

Setting Up a UDP Client

Setting Up a UDP Server

Using UNIX Domain Sockets

/* ------------------------------------------------------------------ */
/* 'rxSock' library supports Internet Domain sockets [AF_INET]; there */
/* is no support for UNIX Domain Sockets [AF_UNIX].                   */
/*                                                                    */
/* It is, however, possible [*NIX-only] to use GCI to access C library*/
/* functions like 'socketpair' to handle such entities, an approach   */
/* that will not be shown here. See section 6.9 of UNIX Network Progr-*/
/* amming by W. R. Stevens for an in-depth treatment of this topic.   */
/* ------------------------------------------------------------------ */

/* *** Translation skipped *** */

Identifying the Other End of a Socket

/* ------------------------------------------------------------------ */
/* This task relies on the 'getpeername' functionality commonly found */
/* in socket libraries. Rather than tap into this directly via GCI, an*/
/* intermediary in the form of an external library wrapper function is*/
/* more commonly used.                                                */
/* ------------------------------------------------------------------ */

/*
   RxSock library does not use 'packed' IP addresses, so the custom
   subroutines, 'inet_aton', and 'inet_ntoa', whilst having been
   implemented [to illustrate byte <=> string conversion in REXX],
   are not actually required for socket-based communication
*/

/* Default socket: blocking, 'AF_INET', 'SOCK_STREAM' */
socket = sockSocket()

if sockErrNo \= 0 then
  call die "Couldn't create socket :" sockErrNo, 1

/* Retrieve peer [i.e. remote side] information from socket */
drop host. ; call sockGetPeerName socket, 'host.!'
if sockErrNo \= 0 then
  call die "Couldn't identify other end :" sockErrNo, 1

iaddr = host.!ADDR ; port = host.!PORT

/* ----------------------------- */

drop host. ; call sockGetHostByAddr iaddr, 'host.!'
if sockErrNo \= 0 then
  call die "Couldn't identify other end :" sockErrNo, 1

claimed_hostname = host.!NAME

drop host. ; call sockGetHostByName claimed_hostname, 'host.!'
if sockErrNo \= 0 then
  call die "Couldn't look up" claimed_hostname ":" sockErrNo, 1

if iaddr \== host.!ADDR then
  call die "Mismatch between" claimed_hostname "and" iaddr ":",
           sockErrNo, 1

Finding Your Own Name and Address

/* ------------------------------------------------------------------ */
/* This task relies on the 'gethostname' functionality commonly found */
/* in socket libraries. Rather than tap into this directly via GCI, an*/
/* intermediary, be it a system command or wrapper functions, is more */
/* commonly used.                                                     */
/* ------------------------------------------------------------------ */

/* Execute system command, 'hostname', placing output in 'hostname.1' */
address SYSTEM "hostname" with OUTPUT STEM hostname.

/* ----------------------------- */

/* Call the non-ANSI [*NIX-only] 'UNAME' BIF extracting all data */
parse value UNAME() with kernel, hostname, release, version, hardware

/* Alternatively, call 'UNAME' for a specified field */
hostname = UNAME('N')

/* ----------------------------- */

hostname = "sparx.net"

drop host. ; call sockGetHostByName hostname, 'host.!'
if sockErrNo \= 0 then
  call die "Couldn't resolve" hostname ":" sockErrNo, 1

iaddr = host.!ADDR

drop host. ; call sockGetHostByAddr iaddr, 'host.!'
if sockErrNo \= 0 then
  call die "Couldn't re-resolve" hostname ":" sockErrNo, 1

/* 'host.!NAME' should now be the same value as: 'hostname' */

Closing a Socket After Forking

/* ------------------------------------------------------------------ */
/* Two caveats when using 'sockShutdown':                             */
/*                                                                    */
/* * Non-blocking call, so a delay may be needed after calling it     */
/* * A call to 'sockClose' is still required at some point to release */
/*   socket resources [it does not replace this function]             */
/* ------------------------------------------------------------------ */

call sockShutdown socket, 0             /* Stopped reading data */
call sockShutdown socket, 1             /* Stopped writing data */
call sockShutdown socket, 2             /* Stopped using this socket */

/* ----------------------------- */

call sockShutdown socket, 0

if sockErrNo \= 0 then
  call die "Couldn't effect socket shutdown :" sockErrNo, 1
else
  say "I have stopped reading"

/* ----------------------------- */

/* Send some data */
buffer = "my request"||NEWLINE
call sockSend SERVER, buffer

/* Send eof; no more writing */
call sockShutdown SERVER, 1                  

/* Can, however, still read */ 
call sockRecv SERVER, 'answer', MAX_BYTES


Writing Bidirectional Clients

Forking Servers

Pre-Forking Servers

Non-Forking Servers

Writing a Multi-Homed Server

Making a Daemon Server

Restarting a Server on Demand

Program: backsniff

Program: fwdport