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
- using the system
select()
function. - set the sockets to be non-blocking and use polling.
- software interrupt whenever input on a socket arrives.
- spawn off a separate thread to handle I/O.
sproc()
in Saloris - 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:
- create socket(client):
exec file-descriptor<>/dev/tcp/IP-or-hostname-here/port
echo "hello socket" >&3 #write to the socket
cat <&3 #read from the socket
exec 3<&- #close for read
exec 3>&- #close for write
2. testing scheme
- run the proxy from first terminal,
- creat a sockIn from second terminal,
- creat a sockOut from third terminal,
- write arbitrary bytes to the sockIn in the second terminal
- read arbitrary bytes from the sockOut from third terminal