-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathdnsProxyServer.js
333 lines (289 loc) · 10.2 KB
/
dnsProxyServer.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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
var dns = require('native-dns'),
stdio = require('stdio'),
server = dns.createServer(),
dnt = require('./dnsProxyCommon.js'),
request = require('request');
var argDescr = {
'dnsname': {
key: 'd',
args: 1,
description: 'The dns name of the dns server example: dns.example.com',
required: true
},
'datalogkey': {
key: 'k',
args: 1,
description: 'data loging key. Spcial dns address that can take small commands usefull for super low data speed systems like satelite data loging. default is l usage on client nslookup ${logdata}.logpwd.${dnsname}',
default: 'logpwd',
required: false
},
'listenip': {
key: 'l',
args: 1,
default: '0.0.0.0',
description: 'The ip number to start listening to default is 0.0.0.0'
},
'port': {
key: 'p',
args: 1,
default: 53,
pharse: parseInt,
description: 'The port to listen to default i 53'
},
'timeout': {
key: 't',
args: 1,
default: 300,
pharse: function(timeout) {
return (parseInt(timeout) * 1000)
},
description: 'Nr of seconds of inacvivity untill the session is considerd dead default is 300'
},
'verbose': {
key: 'v',
description: 'Print more information to stderr'
}
};
var options = stdio.getopt(argDescr);
//Set Default Argument Values and pharse them
for (ArgName in argDescr) {
if (!options[ArgName] && argDescr[ArgName].default) {
options[ArgName] = argDescr[ArgName].default;
}
if (argDescr[ArgName].pharse && options[ArgName]) {
options[ArgName] = argDescr[ArgName].pharse(options[ArgName]);
}
}
//the system is built to support multiple targets the client can request a target based on the label in this struct
var Services = {
s: {
host: 'localhost',
port: 22
}
}
var datalogkey = options['datalogkey'];
//packetID just used for debuging
packetID = 0;
//Sessions holds information about active sessions
var Sessions = new dnt.SessionsHolder();
function decode_edns_options(edns_options){
var edns_subnet = false;
//The edns data ussaly contains the subnet of the asker
for(x in edns_options){
if(edns_options[x].code == 8){
family = edns_options[x].data.readUInt16BE();
source = edns_options[x].data.readUInt8(2);
scope = edns_options[x].data.readUInt8(3);
adress = edns_options[x].data.slice(4)
if(family == 1 || family == 2){//ipv4 or ipv6
sep = '.';
if(family == 2){
sep = ':';
}
pos=0;
adr = [];
while(adress.length>pos){
adr.push(adress.readUInt8(pos))
pos++;
}
edns_subnet = adress.join(sep)+'/'+source;
}
}
}
return edns_subnet;
}
function push_answer(response, BytesLeft, question_name, SubmitPacket){
var TxData = SubmitPacket.GetBinData();
response.answer.push(dns.TXT({
name: question_name,
data: TxData,
ttl: 1
}));
BytesLeft -= 13 + TxData.length
return BytesLeft;
}
function onPacketFromClient(RecivedPacket, edns_subnet, question_name, asking_server_address, response, BytesLeft){
var ret = {
ResonseDelayed: false,
BytesLeft: BytesLeft
};
var ThisPacketID = packetID;
var Session = Sessions.get(RecivedPacket.sessionID);
if(Session){
if(edns_subnet){
Session.clientLastKnownSubnet = edns_subnet;
}
Session.lastAskingServer = asking_server_address;
}
//PrintInfo("SessionID: "+RecivedPacket.sessionID+" RcOf: "+RecivedPacket.recivedoffset+" Of: "+RecivedPacket.offset+" DatLen: "+RecivedPacket.data.length);
switch (RecivedPacket.commando) {
case 1: //Data recive & recive
case 3: //Data retrive
if (!Session) {
PrintInfo("Recived unknown SessionID: " + RecivedPacket.sessionID);
var SubmitPacket = new dnt.ServerPacket();
SubmitPacket.commando = 5;
SubmitPacket.data = Buffer.from("1");
ret.BytesLeft = push_answer(response, ret.BytesLeft, question_name, SubmitPacket)
} else {
//console.log("Packet: " + ThisPacketID)
var ResponseDelay = 0;
if (RecivedPacket.data != 0) {
PrintInfo("FrClient(" + RecivedPacket.commando + ")[" + RecivedPacket.offset + ":" + RecivedPacket.data.length + "] <- (client: " + RecivedPacket.sessionID + ")" + ThisPacketID)
ResponseDelay = 5;
Session.AddData(RecivedPacket.offset, RecivedPacket.data);
}
var RequestedOffset = RecivedPacket.recivedoffset;
//We Wait {ResponseDelay}ms that way we can get the response We should not wait if there is allready a full message in the que, add that feature later...
//from the server in the answer if there was no data from the client we use a ResponseDelay of zero
ret.ResonseDelayed = true;
setTimeout(function() {
while (true) {
var SubmitPacket = new dnt.ServerPacket();
SubmitPacket.commando = 1;
SubmitPacket.offset = RequestedOffset;
SubmitPacket.recivedoffset = Session.NextByte;
var TxtDatLeft = ret.BytesLeft - 13;
var MaxDatLeft = Math.min(TxtDatLeft, 255);
var UsableTxDatBytesLeft = MaxDatLeft - SubmitPacket.HeaderLen;
var MaxData2Client_Len = Math.floor(UsableTxDatBytesLeft * (dnt.b32cbits / 8)); ///A Anser may maximumly be 254 bytes
if (MaxData2Client_Len > 0) {
SubmitPacket.data = Session.Read(MaxData2Client_Len, SubmitPacket.offset);
if (Session.IsThereUnReadBytes()) {
SubmitPacket.commando = 4;
}
RequestedOffset += SubmitPacket.data.length;
}
if (SubmitPacket.data.length == 0) {
if (response.answer.length == 0) {
SubmitPacket.commando = 3;
ret.BytesLeft = push_answer(response, ret.BytesLeft, question_name, SubmitPacket)
}
break;
} else {
PrintInfo("ToClient(" + SubmitPacket.commando + ")[" + SubmitPacket.offset + ":" + SubmitPacket.data.length + "] -> (client: " + RecivedPacket.sessionID + ")" + ThisPacketID)
ret.BytesLeft = push_answer(response, ret.BytesLeft, question_name, SubmitPacket)
}
}
response.send();
}, ResponseDelay);
}
break;
case 2: //New Session
var Service = RecivedPacket.data.toString();
var SubmitPacket = new dnt.ServerPacket();
if (typeof(Services[Service]) != 'undefined') {
var SessionID = Sessions.add(Services[Service].host, Services[Service].port);
SubmitPacket.commando = 2;
SubmitPacket.data = Buffer.from(SessionID.toString());
PrintInfo("Gave new SessionID to Client: " + SessionID.toString() + " source subnet: " + edns_subnet );
} else {
SubmitPacket.commando = 5;
SubmitPacket.data = Buffer.from("3");
PrintInfo("Client asked for a unknown service: " + Services[Service]);
}
ret.BytesLeft = push_answer(response, ret.BytesLeft, question_name, SubmitPacket)
break;
case 5: //Error
PrintInfo("Client reported error: " + RecivedPacket.data.toString());
break;
default:
PrintInfo("Unknown comamndo recived from client.");
var SubmitPacket = new dnt.ServerPacket();
SubmitPacket.commando = 5;
SubmitPacket.data = Buffer.from("2");
ret.BytesLeft = push_answer(response, ret.BytesLeft, question_name, SubmitPacket)
break;
}
return ret;
}
function onDnsRequest(request, input_response) {
var response = input_response;
var i;
//packetID just used for debuging
packetID += 1;
var ResonseDelayed = false;
//BytesLeft keeps track of the number of bytes used a UDP dns answerpacket may not exced 512 bytes
var BytesLeft = 500; //512-12 The static part of a dns response is 12 bytes
for (qt in response.question) {
BytesLeft -= response.question[qt].name.length + 8;
}
//If we get edns info populate it in the edns_subnet variable
var edns_subnet = decode_edns_options(request.edns_options);
//Asking DNS server and subnet
//console.log("asking server:", request.address.address, "source subnet:", adress);
//Do this once per dns question There is a bug in this as the response will
//be sent once per question. but the client only ever sends one question per message
for (x in request.question) {
//A question to one of the services that we support should look somthing
//like: base32NUMdata base32data.dnsproxy.example.com
var QuestionName = request.question[x].name;
var ownDomain = QuestionName.substr(QuestionName.length - options.dnsname.length);
var dataSubdomain = QuestionName.substr(0, QuestionName.length - options.dnsname.length);
//Does the question end with our Special Domain Example: dnsproxy.example.com
if (ownDomain == options.dnsname) {
var parts = dataSubdomain.split('.');
if(parts.length > 1 && parts[parts.length-2] == datalogkey){
//this is special data, not tunneling data
parts.pop();//remove last empty entry
parts.pop();//remove the special indicator entry the datalogkey
ResonseDelayed = DataLog(parts, edns_subnet, QuestionName, response)
}else{
var RecivedPacket = new dnt.ClientPacket(dataSubdomain);
//Was the message from the client decoded properly
if (RecivedPacket) {
ret = onPacketFromClient(RecivedPacket, edns_subnet, QuestionName, request.address.address, response, BytesLeft);
ResonseDelayed = ret.ResonseDelayed;
BytesLeft = ret.BytesLeft;
} else {
PrintInfo("The question does not have the correct number of heders");
}
}
} else {
PrintInfo("Question not for us:" + request.question[x].name);
}
}
if (!ResonseDelayed) {
response.send();
}
};
function DataLog(data, edns_subnet, question_name, response){
PrintInfo("recived dataloging message from subnet: "+edns_subnet)
console.log(data)
//register log in webserver
request('http://localhost/dnsdatalog/?'+data.join('.'), function (error, http_response, body) {
console.log('sending response:', body)
response.answer.push(dns.TXT({
name: question_name,
data: body,
ttl: 1
}));
response.send();
});
return true;
}
function PrintInfo(VerbTxT) {
if (options.verbose) {
var now = new Date();
console.error(now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds() + ' ' + VerbTxT);
}
}
var onError = function(err, buff, req, res) {
console.error('DNS ERROR:', err);
};
var onListening = function() {
//console.log('server listening on', this.address());
//this.close();
};
var onSocketError = function(err, socket) {
console.log(err);
};
var onClose = function() {
PrintInfo('server closed', this.address());
};
server.on('request', onDnsRequest);
server.on('error', onError);
server.on('listening', onListening);
server.on('socketError', onSocketError);
server.on('close', onClose);
server.serve(options.port, options.listenip);