Block and Non Block IO Socket

I have received an email from Honeynet project, they give me a code test for google summer of code 2015. The problem is

to write one program in python and another one in c to read arbitrary input from one socket and write to another for each.

This problem is much harder than it appears. I am writing a post for it here.

Solutions in C

  1. using the system select() function.
  2. set the sockets to be non-blocking and use polling.
  3. software interrupt whenever input on a socket arrives.
  4. spawn off a separate thread to handle I/O. sproc() in Saloris
  5. use XtAppAddInput() in X/Xt code.

The input socket

We call the small program proxy. The function of the proxy is to read bytes from a TCP or UDP socket sockIn, and write arbitrary bytes to another socket sockOut. For TCP, at the input end, in order to read bytes from sockIn, sockIn should have bytes available to write to proxy, and connection to proxy. There is a design decision need to be made: should the proxy actively connect to the sockIn, or the proxy listen for sockIn to connect?

Implementation 1: Proxy Passively listen and read:

SockIn Client ProxyIn Server
connect(), gets(), write(ProxyIn) listen(), accept(), read(ProxyIn), write(ProxyOut)

Implementation 2: Proxy actively connect and read:

SockIn Server ProxyIn Client
accept(), gets(), write(ProxyIn) connect(), read(ProxyIn), write(ProxyOut)

From the above tabulated function call for different implementation, you can see the complexity is similar. All the used function calls are to establish the connection, read arbitrary bytes from sockIn.

I will start with the first implmentation for the input end. The proxy should keep running for ever, altering between the following states:

state 1. listening for sockIn,

state 2. accepted sockIn,

state 3. read x bytes from sockIn,

state 4. wait for sockOut write ready.

state 5. write x bytes to sockOut.

state 6. complete write to sockOut, return to state 1.

The output socket

sockIn connect to sockOut and write data to it. So the whole picture would be looks like this:

1                      sockredirect.c
2                     +----------------+
3                     |bind            |
4                     |listen   connect|
5stdin---->sockIn---->|accept     write|---->sockOut---->stdout
6                     |read            |
7                     |                |
8                     +----------------+

The implementation of this scheme in c:

  1/**************************************************************************
  2 * This program read arbitrary bytes from sockIn and write it to sockOut
  3 *
  4 * USAGE:
  5 *   compile: gcc sockredirect.c -o sockredirect
  6 *   ./sockredirect <proxyIn_IP> <proxyIn_port> <sockOut_IP> <sockOut_port>
  7 *   create input socket: run "nc <proxyIn_IP> <proxyIn_port>" in second terminal
  8 *   create output socekt: run "nc -l <sockOut_port>" in third terminal
  9 *
 10 * test:
 11 *   type characters in second terminal
 12 * exit:
 13 *   when in the terminal run sockredirect, press Ctrl-C
 14 *
 15 ***************************************************************************/
 16#include <sys/types.h>
 17#include <sys/socket.h>
 18#include <netdb.h>
 19#include <stdio.h>
 20#include <string.h>
 21#include <stdlib.h>
 22
 23#define MAXLISTENQ 10
 24#define BUFFERSIZE 32
 25
 26void Die(char *mess) { perror(mess); exit(1);}
 27
 28void redirectBytes(int sockIn_fd, char *ip, char *port){
 29	char buff[BUFFERSIZE];
 30	long readbytes = 0;
 31	long totalbytes = 0;
 32	int write_fd;
 33	int conn_write_fd;
 34
 35	struct sockaddr_in writeservaddr;//the socket this program write to, it is server for the output end.
 36	if((write_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
 37		Die("problem creating ProxyOut!\n");
 38	}
 39
 40	bzero(&writeservaddr, sizeof(writeservaddr));
 41 	writeservaddr.sin_family = AF_INET;
 42	writeservaddr.sin_addr.s_addr = inet_addr(ip);
 43 	writeservaddr.sin_port = htons(atoi(port));
 44
 45	if((readbytes = recv(sockIn_fd, buff, BUFFERSIZE, 0)) < 0){
 46		Die("Problem receiving initial bytes from sockIn!\n");
 47	}
 48
 49	printf("connecting to SockOut...\n");
 50	if(connect(write_fd, (struct sockaddr *)&writeservaddr, sizeof(writeservaddr)) < 0){
 51		printf("ProxyOut fail to connect SockOut!\n");
 52		close(write_fd);
 53		return;
 54	}else
 55		printf("Output socket connected. SockOut(%s:%s)\n", ip, port);
 56
 57	while(readbytes > 0){
 58
 59		if(send(write_fd, buff, readbytes, 0) != readbytes){
 60			Die("Failed to send bytes to SockOut!\n");
 61		}else{
 62			totalbytes += readbytes;
 63		}
 64		printf(" %5ld bytes of data fowarded\n", totalbytes);
 65
 66		if((readbytes = recv(sockIn_fd, buff, BUFFERSIZE, 0)) < 0){
 67			Die("Problem receiving initial bytes from sockIn!\n");
 68		}
 69	}
 70}
 71
 72int main(int argc, char **argv){
 73	int read_fd;
 74	int conn_read_fd;
 75	struct sockaddr_in readcliaddr;	//the socket this program read, it is client
 76	struct sockaddr_in readservaddr;//this program's input end, it is a server when read bytes
 77	uint32_t readsockport;
 78	char readsockip[INET6_ADDRSTRLEN];
 79	socklen_t Rcliaddrlen;
 80
 81	if(argc != 5){
 82		printf("USAGE: \n  proxy <proxyIn_IP> <proxyIn_port> <sockOut_IP> <sockOut_port>\n");
 83		printf("  create input socket: run \"nc <proxyIn_IP> <proxyIn_port>\" in second terminal\n");
 84		printf("  create output socekt: run \"nc -l <sockOut_port>\" in third terminal\n\n");
 85		printf("test: \n  type characters in second terminal\n");
 86		printf("exit: \n  when in the proxy terminal, press Ctrl-C \n");
 87		return 0;
 88	}
 89	// create a socket that listen and accept the readcli to read bytes
 90	if((read_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
 91		Die("problem creating ProxyIn!\n");
 92	}
 93	// configure address, and bind
 94	bzero(&readservaddr, sizeof(readservaddr));
 95	readservaddr.sin_family = AF_INET;
 96	readservaddr.sin_addr.s_addr = inet_addr(argv[1]);
 97	//readservaddr.sin_addr.s_addr = inet_addr("10.33.13.144");
 98	readservaddr.sin_port = htons(atoi(argv[2]));
 99
100	if(bind(read_fd, (struct sockaddr *) &readservaddr, sizeof(readservaddr)) <0 ){
101		Die("Problem binding ProxyIn\n");
102	}
103
104	if(listen(read_fd, 10) < 0){
105		Die("Problem listen on ProxyIn!\n");
106	}
107
108	while(1){
109		printf("waiting for connecting input socket...\n");
110		conn_read_fd = accept(read_fd, (struct sockaddr *)&readcliaddr, &Rcliaddrlen);
111		if(conn_read_fd > 0){
112			inet_ntop(AF_INET, &readcliaddr.sin_addr, readsockip, sizeof(readsockip)),
113			readsockport = ntohs(readcliaddr.sin_port);
114			printf("input socket connected. sockIn(%s:%d)\n", readsockip, readsockport);
115		}else{
116			printf("Problem accept to sockIn !!!\n");
117		}
118
119		redirectBytes(conn_read_fd, argv[3], argv[4]);
120	}
121	close(read_fd);
122}

Note we could also just use one server socket. This implementation have to only use one socket for the redirection. In this scenario, both sockIn and sockOut works as a client to connect the program. sockIn and sockOut can operate in full-duplex mode.

Solutions in Python

Here We give the corresponding python implementation:

 1'''
 2USAGE:
 3 proxy <proxyIn_IP> <proxyIn_port> <sockOut_IP> <sockOut_port>
 4 create input socket: run "nc <proxyIn_IP> <proxyIn_port>" in second terminal
 5 create output socekt: run "nc -l <sockOut_port>" in third terminal
 6'''
 7from socket import *
 8import sys
 9
10def redirectBytes(sock, ip, port):
11	sockout = socket(AF_INET, SOCK_STREAM)
12	print "Connecting to SockOut...\n"
13	data = sock.recv(32)
14	sockout.connect((ip, int(port)))
15	print 'Output socket connected. sockOut: (\'{}\', {})'.format(ip, port)
16	total = 0
17	while data:
18		sockout.send(data)
19		total += len(data)
20		print total, " bytes of data forwarded\n"
21		data = sock.recv(32)
22
23	sock.close()
24	sockout.close()
25
26if len(sys.argv) != 5:
27	print __doc__
28else:
29	sock = socket(AF_INET, SOCK_STREAM)
30	sock.bind((sys.argv[1], int(sys.argv[2])))
31	sock.listen(5)
32	while 1:
33		print "waiting for connecting input socket...\n"
34		newsock, client_addr = sock.accept()
35		print "input socket connected. sockIn:", client_addr
36		print "\n"
37
38		redirectBytes(newsock, sys.argv[3], sys.argv[4]);
39

Testing the program

1. Bash exec

commands:

  1. create socket(client): exec file-descriptor<>/dev/tcp/IP-or-hostname-here/port
  2. echo "hello socket" >&3 #write to the socket
  3. cat <&3 #read from the socket
  4. exec 3<&- #close for read
  5. exec 3>&- #close for write

2. testing scheme

  1. run the proxy from first terminal,
  2. creat a sockIn from second terminal,
  3. creat a sockOut from third terminal,
  4. write arbitrary bytes to the sockIn in the second terminal
  5. read arbitrary bytes from the sockOut from third terminal

References

  1. http://man7.org/linux/man-pages/man7/socket.7.html
  2. http://www.gnu.org/software/libc/manual/html_node/Sockets.html#Sockets
  3. http://www.lowtek.com/sockets/select.html
  4. http://www.faqs.org/faqs/unix-faq/programmer/faq/