-
Notifications
You must be signed in to change notification settings - Fork 4
/
README.txt
242 lines (167 loc) · 8.95 KB
/
README.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
Problem: write a generic TCP relay.
We have two processes that can reach a relay box via TCP, but can not reach
each other (because of NAT or firewall issues). You are asked to:
1. Implement a relay server. This server will listen for connections on behalf
of another process, alert the other process when an incoming connection has
been made, and forward all incoming/outgoing traffic from the incoming
connection to the process and vice versa, regardless of application
protocol (maybe HTTP/SSL/SMTP/POP3/who knows). The relay server will be
contacted by two types of applications - processes needing a relay, and
other processes wanting to talk to the process needing a relay.
2. Describe in clear terms to another programmer how they would enable a
process they have, behind some firewall, to be able to use your relay
server. Remember that every process in this scenario can contact the relay
server, but the relay server can't *initiate* communication with any other
process (due to firewalls, etc).
3. To help yourself debug and for us to evaluate your work, write a small
application that uses the relay server (maybe an echo server). Once your
server has established a successfully relayed port, it should output on
stdout what its new public address is, and we should be able to contact
it, through the relay, with telnet or netcat or something. Note that this
requires your relay server interface to notify relayed clients of their
public address.
Use whatever language and tools you feel most comfortable with.
In order to test this, make sure that your relay process can be started via the
invocation:
./relay port
where port is how your echoserver will contact the relay.
You can (and probably should) accept other arguments, but choose reasonable
defaults.
Your echo server process should be started with the invocation:
./echoserver relayhost relayport
where relayhost is the host the relay is running on, and port is the port you
provide to the relay process. Upon receiving a (host, port) pair from the
relay, the echo server process should notify us what its relayed hostname/port
pair is.
Example session:
$ ./relay 8080 &
$ ./echoserver localhost 8080 &
localhost:8081
$ telnet localhost 8081
Hello, world
Hello, world
________________________________________________________________________________
Assumptions
I can use a server/socket library (eg. for Java).
The request & response terminators can be CR or LF or CR-LF.
The protocol is always one request from the client which results in
one response from the server. So if a server isn't finished with a
request yet, subsequent requests by the same client will queue; also,
multiline requests/responses will depend on an acknowledgement of each
line from the other side.
I don't have to do connection management, eg. signals back-and-forth
for when the relay shuts down or when the servers shut down. I can do
my own thing if a server shuts down (like return "null" or error or no
responses to clients).
I can ignore problems of too much data on one newline, too many
servers, too many clients, connections being open for too long, or
disconnects... essentially, all those pesky high-traffic concerns.
________________________________________________________________________________
EXPLANATION OF RELAY SERVER
This relay server is your public intermediary to the rest of the
world, giving access to your own server that may be sitting behind a
firewall.
To run it, make sure Java (1.6+) is installed. Unpack the relay
server:
tar -xvf relay.tgz
cd relay
You can run it in basic mode using "localhost" and port 8080 by default:
relay &
But it's best to customize the address you're using, and probably the
port. Let's say your server is running at 1234.amazonaws.com on port
8888:
relay -h 1234.amazonaws.com 8888 &
If you want to see all connections made to the server for debugging,
use the -v option (or -vv for even more info):
relay -h 1234.amazonaws.com -v 8888 &
Now, the relay server will accept connections from clients anywhere
and forward them along to your server... once you make a few changes
to your server.
CHANGES TO YOUR SERVER
Now you have to modify your own server: it needs to register itself
and retrieve it's own public address. Here's how: your server
currently listens for any incoming connections, so you'll modify it to
do this instead:
1) Make a single connection to your relay server,
2) accept one HOST:PORT line of input for your new public address,
3) then wait for more HOST:PORT addresses to tell you where to connect
to each client as they make connection requests.
Let's go through the steps. First, after you connect to the server,
it will send back the host and port information, like this:
1234.amazonaws.com:8889
That address is for you to share with the world. Keep that socket
open and listen on it; if someone comes in and connects to that
address, you will get another HOST:PORT combination that tells you
where to connect to that client. For example, the next line might
come to you as:
1234.amazonaws.com:8895
That means that a client has made a connection to the relay, so you
should connect to that address and receive the requests and give
responses through it.
Let's give a quick Java code example to demonstrate how you would need
to modify an existing server to work with this relay. Assume you've
got some class or method that runs your inner loops for one client,
like so:
public class ResponseHandler implements Runnable {
...
public void run() {
...
incoming = new BufferedReader(new InputStreamReader(clientConn.getInputStream()));
outgoing = new PrintWriter(clientConn.getOutputStream(), true);
String messageIn = incoming.readLine();
while (messageIn != null) { // loop until the stream is closed
outgoing.println(response(messageIn));
messageIn = incoming.readLine();
}
...
}
...
}
You've also got an outer loop that accepts client connections;
probably something like this, which reads from a ServerSocket and then
calls the sample RequestWaiter for each client connection you get:
serverSocket = new ServerSocket(port);
while(true) { // loop forever, spawning a thread for each new client
clientSocket = serverSocket.accept();
new Thread(new ResponseHandler(clientSocket)).start(); // talk to client
}
Here's the change: instead of listening for all your clients as a
server, make one connection to our relay, get the public HOST:PORT
which you can advertize, and then listen for other addresses where you
can connect to each client as they contact your public address.
clientAddressSocket = new Socket("1234.amazonaws.com", 8888); // for the relay server
// grab my public address from the relay
incoming = new BufferedReader(new InputStreamReader(clientAddressSocket.getInputStream()));
String publicHostAndPort = incoming.readLine();
System.out.println(publicHostAndPort); // this is the HOST:PORT to give clients
// now listen on that socket for new connections to make for new clients
String messageIn = incoming.readLine();
while (messageIn != null) { // loop until closed, spawning a thread for each new client
// parse out the host and port where to connect for this new client
String clientHost = messageIn.substring(0, messageIn.indexOf(":"));
int clientPort = Integer.valueOf(messageIn.substring(messageIn.indexOf(":") + 1));
clientSocket = new Socket(clientHost, clientPort);
new Thread(new ResponseHandler(clientSocket)).start(); // talk to client
// now wait for the next client HOST:PORT
messageIn = incoming.readLine();
}
Now give everyone the HOST:PORT that you got back (in "publicHostAndPort") and you're ready for the world.
________________________________________________________________________________
Commentary
You asked for some feedback, but the problem is actually pretty well
specified. In my case, I focused on the routing and I aimed for a
very simple (simplistic!) server modification. You could conceivably
warn people, but I think you want to find people who will do it right
the first time (or maybe understand their mistakes with little
explanation :-).
I thought about supplying test cases, but there's the same issue: you
want people who will come up with good test cases or grok their
mistakes quickly.
Of course, I'm sure you have test servers to help with your grading.
One thing that is a pain, I'm sure, is to modify those servers to work
with people's relays; I thought it was clunky to architect and run two
different versions of my servers. So I created the relay-adapter.py
to sit in between the servers and the client or the relay: when run as
a simple proxy, it passes client requests to a server; then it can be
modified to work with the relay and route things to the servers.
Anyway, I thought it worthwhile to share that part of my testing.