-
Notifications
You must be signed in to change notification settings - Fork 0
/
clientloop.js
298 lines (256 loc) · 13.2 KB
/
clientloop.js
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/*When a browser connects, the header of the HTTP request is processed to identify the ip:port of the web server.
A stream number, S, that is not in use on the source router's circuit is chosen.
A Relay Begin cell is sent with circuit number C and stream number S. It also contains the ip:port of the web server.
The last router on the circuit receives that cell and attempts to establish a TCP connection with the web server. If successful, it sends a Relay Connected cell back to the source router.*/
//TODO: close clientSocket if first_hop_socket closes or we get an end
var net = require('net');
var dns = require('dns');
var date = new Date();
var mappings = require('./mappings');
var protocol = require('./protocol');
var torutils = require('./torutils');
var stream_id_counter = 1;
function getNewStreamID() {
return stream_id_counter++;
}
var server;
exports.startClientLoop = function(nid, proxyPort) {
var first_hop_socket;
var circuit_id = mappings.BASE_CIRC_ID;
server = net.createServer(function (clientSocket) {
var haveSeenEndOfHeader = false;
var header = "";
var stream_id = getNewStreamID();
clientSocket.on('end', function() {
if (mappings.BASE_CIRC_ID != 0) {
torutils.sendWithoutPromise(protocol.sendRelay)(first_hop_socket, circuit_id, stream_id, protocol.RELAY_END, null);
} else {
first_hop_socket.end();
}
});
clientSocket.on('error', function(err) {
clientSocket.end();
if (mappings.BASE_CIRC_ID != 0) {
torutils.sendWithoutPromise(protocol.sendRelay)(first_hop_socket, circuit_id, stream_id, protocol.RELAY_END, null);
} else {
first_hop_socket.end();
}
});
// do we need to pass as an argument
clientSocket.on('data', function (data, serverSock) {
// console.log("Got client data.");
if (!haveSeenEndOfHeader) {
var dataString = data.toString('ascii');
header += dataString;
if (header.includes('\r\n\r\n') || header.includes('\n\n')) {
haveSeenEndOfHeader = true;
// pause the socket so that we can initiate a stream.
clientSocket.pause();
var trimmedHeader = header.split(/(\r\n\r\n|\n\n)/);
var headerLines = trimmedHeader[0].split(/[\r]?\n/);
var extraData = trimmedHeader[1];
// Take the first line and split it on white space
var requestLineComponents = headerLines.shift().trim().split(/\s+/);
if (requestLineComponents.length != 3) {
console.log("Malformed request line, invalid length");
clientSocket.end();
return;
}
var requestType = requestLineComponents[0].toUpperCase();
var requestURI = requestLineComponents[1];
var requestVersion = requestLineComponents[2].toUpperCase();
if (HTTP_METHODS.indexOf(requestType) == -1){
// Malformed request.
console.log("Malformed request line, method not valid");
clientSocket.end();
return;
}
if (requestVersion != "HTTP/1.1"){
// We only support 1.1
console.log("Unsupported version", requestVersion);
clientSocket.end();
return
}
logRequest(requestType, requestURI)
var optionMap = buildOptionMap(headerLines);
// Modify header fields
requestLineComponents[2] = "HTTP/1.0"
if ("connection" in optionMap) {
optionMap["connection"] = "close";
}
if ("proxy-connection" in optionMap) {
optionMap["proxy-connection"] = "close";
}
if (!("host" in optionMap)) {
// All 1.1 messages should have a host field
clientSocket.end();
return;
}
// Could ipv6 cause there to be multiple : in host?
var hostFieldComponents = optionMap.host.split(':');
var hostName = hostFieldComponents[0];
var hostPort = determineServerPort(hostFieldComponents, requestURI);
var modifiedHeader = buildHTTPHeader(requestLineComponents, optionMap);
dns.lookup(hostName, (err, address, family) => {
if (err) {
console.log('lookup failure');
// some sort of 404 or could not resolve
clientSocket.end();
return;
}
if (mappings.BASE_CIRC_ID == 0) {
first_hop_socket = net.createConnection({host:hostName, port:hostPort}, function() {
// console.log("connected to server");
clientSocket.resume();
// first_hop_socket.write(modifiedHeader);
if (requestType == "CONNECT") {
var msg = "HTTP/1.1 200 OK\r\n\r\n";
clientSocket.write(msg);
// console.log("HTTP CONNECT");
} else {
first_hop_socket.write(modifiedHeader);
}
}.bind(this));
first_hop_socket.on("data", (data) => {
// console.log("data!");
clientSocket.write(data);
});
} else {
// console.log("BASE CIRC ID is " + mappings.BASE_CIRC_ID);
circuit_mapping = mappings.getCircuitMapping(nid, mappings.BASE_CIRC_ID);
// console.log("Maps to " + circuit_mapping.nid);
first_hop_socket = mappings.getNodeToSocketMapping(circuit_mapping.nid);
// console.log("Which maps to socket " + first_hop_socket);
// console.log(first_hop_socket);
beginRelay(address, hostPort);
}
if (first_hop_socket == null) {
console.log("Got client data but first hop socket has been closed.");
}
first_hop_socket.on("error", (err) => {
//TODO: error handling
console.log("first hop sock err");
console.log(err);
if (mappings.BASE_CIRC_ID != 0) {
otherNode = mappings.getCircuitMapping(nid, mappings.BASE_CIRC_ID);
mappings.removeNodeToSocketMapping(otherNode.nid);
mappings.removeStreamToSocketMapping(stream_id);
}
first_hop_socket.end();
clientSocket.destroy();
});
first_hop_socket.on("close", () => {
clientSocket.end();
first_hop_socket.end();
})
});
function beginRelay(hostname, port) {
// Assign on msg based upon connection type Connect vs Get
// each callback should have a static definition (?)
var body = new Buffer(hostname + ":" + port + "\0");
// first_hop_socket.write(relay_begin_cell);
torutils.sendWithPromise(protocol.sendRelay,
function() { //success callback
// console.log("Stream successfully created");
// console.log(nid + ", " + circuit_id + ", " + stream_id);
// console.log(clientSocket);
var first_hop = mappings.getCircuitMapping(nid, circuit_id);
mappings.addStreamToSocketMapping(first_hop.nid, first_hop.circid, stream_id, clientSocket);
// console.log("Added stream to socket mapping");
//TODO: break up header before sending (if necessary)
if (requestType == "CONNECT") {
var msg = "HTTP/1.1 200 OK\r\n\r\n";
// console.log("About to send a 200");
clientSocket.write(msg);
// console.log("Sent 200 OK to client");
first_hop_socket.on("error", function() {
var msg = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
clientSocket.write(msg, function() {
clientSocket.end();
});
});
} else {
// console.log("Not a CONNECT");
torutils.sendWithoutPromise(protocol.sendRelay)(first_hop_socket, circuit_id, stream_id, protocol.RELAY_DATA, data);
}
// Resume listening for data on client socket so
// that we can forward it along the new stream.
clientSocket.resume();
}.bind(this),
function () { //fail callback
var msg = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
clientSocket.write(msg, function() {
clientSocket.end();
});
}.bind(this))(first_hop_socket, circuit_id, stream_id, protocol.RELAY_BEGIN, body);
}
}
} else {
if (mappings.BASE_CIRC_ID != 0) {
while (data.length > protocol.MAX_BODY_SIZE) {
smaller_data = data.slice(0, Math.min(protocol.MAX_BODY_SIZE, data.length));
torutils.sendWithoutPromise(protocol.sendRelay)(first_hop_socket, circuit_id, stream_id, protocol.RELAY_DATA, data);
data = data.slice(protocol.MAX_BODY_SIZE);
}
torutils.sendWithoutPromise(protocol.sendRelay)(first_hop_socket, circuit_id, stream_id, protocol.RELAY_DATA, data);
} else {
// console.log("client data!");
first_hop_socket.write(data);
}
}
});
});
server.on('error', (err) => {
console.log("Client listening server error");
console.log(err);
process.exit(1);
//TODO: try broadcasting to clients that we hit an error?
})
server.listen(proxyPort);
}
function buildOptionMap(lines) {
var options = {};
for (lineNum in lines) {
var optionComponents = splitHeaderOptionString(lines[lineNum], ":");
if (optionComponents == null) { continue; }
var key = optionComponents[0].trim().toLowerCase();
var value = optionComponents[1].trim();
options[key] = value;
}
return options;
}
function splitHeaderOptionString(s, delim) {
var index = s.indexOf(delim);
if (index < 0) { return null;}
return [s.substring(0, index), s.substring(index + 1, s.length)];
}
function buildHTTPHeader(requestLineComponents, optionMap) {
var header = "";
header += requestLineComponents.join(" ");
header += "\r\n";
for (var optionKey in optionMap) {
header += optionKey + ": " + optionMap[optionKey] + "\r\n";
}
header += "\r\n";
return header;
}
// Checks in the host field and uri for a port. If no port is found, returns 80.
function determineServerPort(hostFieldComponents, requestURI) {
var serverPort = 80;
if (hostFieldComponents.length == 1) {
// Port not included in host field
var portMatches = requestURI.match(/:[0-9]{1,5}/);
if (portMatches != null) {
serverPort = portMatches[0];
}
}else{
// Pull port from host field
serverPort = hostFieldComponents[1];
}
return serverPort;
}
function logRequest(method, uri) {
var time = new Date();
console.log(time + " >>> " + method.toUpperCase() + " " + uri);
};
const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"];