-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathtuns-client
executable file
·387 lines (365 loc) · 10.3 KB
/
tuns-client
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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
#!/usr/bin/ruby
# Tuns -- prototype of an IP over DNS tunnel
# Copyright (C) 2007 Lucas Nussbaum <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib', 'tuns')
require 'rbconfig'
$:.unshift File.join(RbConfig::CONFIG['rubylibdir'], 'tuns')
require 'socket'
require 'timeout'
require 'thread'
require 'optparse'
require 'tuns/tun'
require 'Net/DNS'
require 'base32'
require 'tuns/dns'
## Possible tags in non-quiet mode
# S we are sending a data packet
# R we just received a data packet
# > we are sending a request for data (polling)
# T no packet was received. we will send a request for data again.
# E answer packet, but without any anwer, received
# . we received an ack for a data packet we sent
# + we are sending additional request packets
# S(delay) we are sleeping, since server has nothing for us
RECV_TIMEOUT = 2.5
DELAY_INCR = 0.1
DELAY_MAX = 2 # max delay before polls
PKTSIZE=2000
MAX_NOTHING=60
$mtu=nil
$debug=false
$quiet=true
$domain=nil
$server = nil
$ip = "192.168.53.1"
$rip = "192.168.53.2"
$timeout = RECV_TIMEOUT
$rtt = false
progname = File::basename($PROGRAM_NAME)
opts = OptionParser::new do |opts|
opts.program_name = progname
opts.banner = "Usage: #{progname} [options]"
opts.separator ""
opts.separator "Mandatory options:"
opts.on("-d", "--domain DOMAIN", "Domain name to use to send/receive data") do |n|
$domain = n
end
opts.separator "Verbosity options:"
opts.on("-q", "--quiet", "Quiet mode") do |q|
$quiet = true
$debug = false
end
opts.on("", "--debug", "Debug mode") do |d|
$debug = true
$quiet = false
end
opts.separator ""
opts.separator "Overriding default values:"
opts.on("-m", "--mtu MTU", "MTU for tun device (overrides default calculation based on domain names)") do |m|
$mtu = m.to_i
end
opts.on("-n", "--nameserver SERVER", "Nameserver to use. By default, use first entry from /etc/resolv.conf") do |s|
$server = s
end
opts.on("-i", "--ip", "IP for tun device") do |i|
$ip = i
end
opts.on("-t", "--remote-ip", "IP for other endpoint on tun") do |i|
$rip = i
end
end
begin
opts.parse!(ARGV)
rescue OptionParser::ParseError => pe
opts.warn pe
puts opts
exit 1
end
if $domain.nil?
puts "ERROR: Domain to send and receive data (--domain) must be specified."
puts
puts opts
exit 1
end
# creates and configure tun device
if $mtu.nil?
$mtu = 140 # FIXME
end
$tun = File::new('/dev/net/tun', File::RDWR)
$dev = tun_allocate($tun)
def reset_interface
system("ifconfig #{$dev} #{$ip} mtu #{$mtu} up")
end
reset_interface
system("route add -host #{$rip} dev #{$dev}")
system("sysctl -w net.ipv4.tcp_frto=1") # better for lossy links
# creates and configure socket to server
if $server.nil?
s = IO::read('/etc/resolv.conf').split(/\n/).grep(/^nameserver /)
if s.length == 0
puts "No nameservers in /etc/resolv.conf, exiting."
exit 1
end
$server = s[0].split(' ')[1]
puts "Using nameserver #{$server}"
end
UDPSocket.do_not_reverse_lookup = true
$socket = UDPSocket.new
$socket.connect($server, 53)
$schedmutex = Mutex::new
# state variables
$num_nothing = 0 # number of successive reqs with nothing in remote queue
$num_req = 0 # number of requests. used to generate diff queries each time
$sentpacketsmutex = Mutex::new
$sentpackets = {}
$numpacket = 0
$sendmutex = Mutex::new # mutex to hold to write to socket
# function to send data to the DNS socket
def socketsend(socket, data, text)
$sendmutex.synchronize do
socket.send(data, 0)
end
if $rtt
$sentpacketsmutex.synchronize do
$sentpackets[text] = [$numpacket, Time::now]
$numpacket += 1
end
end
end
def received_packet(resp)
return if not $rtt
$sentpacketsmutex.synchronize do
qname = resp.question[0].qname
d = $sentpackets[qname]
if d.nil?
puts "RTT: recv #{qname} without corresponding query!"
return
end
n, t = d
puts "RTT: [#{n}] #{Time::now - t}s (#{qname[0..40]}) (not received: #{$sentpackets.length - 1})"
$sentpackets.delete(qname)
end
end
# Thread that reads from tun and sends to DNS
th_send = Thread::new($socket, $tun) do |socket, tun|
Thread::current.abort_on_exception = true
while (s = tun.sysread(PKTSIZE))
if s.length > $mtu
puts "Packet too long (#{s.length}) !!"
exit(1)
end
packet = dns_encode(s)
text = "d#{packet}.#{$domain}"
dnspacket = Net::DNS::Packet::new_from_values(text, 'CNAME', 'IN')
if $debug
puts "*" * 25 + ' SENDING DATA ' + '*' * 25
p dnspacket
end
if not $quiet
print 'S'
STDOUT.flush
end
socketsend(socket, dnspacket.data, text)
$num_nothing = 0
sendreq(socket)
end
end
# function to send a request for data
def sendreq(socket)
$num_req += 1
text = "r#{$num_req}.#{$domain}"
dnspacket = Net::DNS::Packet::new_from_values(text, 'CNAME', 'IN')
if $debug
puts "*" * 25 + " SENDING REQ FOR DATA (#{$num_req}) " + '*' * 25
end
if not $quiet
print '>'
STDOUT.flush
end
socketsend(socket, dnspacket.data, text)
end
# function to send a config change
def sendconf(socket, conf)
# FIXME add random number, maybe?
text = "c#{conf}.#{$domain}"
dnspacket = Net::DNS::Packet::new_from_values(text, 'A', 'IN')
if $debug
puts "*" * 25 + ' SENDING CONFIG ' + '*' * 25
p dnspacket
end
socketsend(socket, dnspacket.data, text)
end
def sendreqs(socket, length, zero = false)
(1..length).each do
# send reqs for additional packets
if not $quiet and length > 1
print "+"
STDOUT.flush
end
sendreq(socket)
end
end
# thread that reads from DNS and writes to tun
th_recv = Thread::new($socket, $tun) do |socket, tun|
Thread::current.abort_on_exception = true
sendreq(socket)
while true
ans, from = nil
begin
Timeout::timeout($timeout) do
ans, from = socket.recvfrom(PKTSIZE)
end
rescue Timeout::Error
if not $quiet
print 'T'
STDOUT.flush
end
sendreq(socket)
next
end
recvtime = Time::now
resp = Net::DNS::Packet.new_from_binary(ans)
received_packet(resp) if $rtt
if $debug
puts "*" * 25 + ' RECEIVED ' + '*' * 25
p resp
end
if resp.header.ancount != 1
if not $quiet
print "E"
STDOUT.flush
sendreq(socket)
end
next
end
type = resp.answer[0].type
if type == 'CNAME'
text = resp.answer[0].cname
if resp.question[0].qname !~ /\.#{$domain}$/ # FIXME
puts "Invalid domain name in reply: #{resp.question[0].qname}"
next
end
text.gsub!(/\.#{$domain}$/,'') # FIXME
else
text = ' ' # case for A records. nil doesn't work.
end
if text[0..0] == 'l' and type == 'CNAME'
# it's an ack for data we sent
if not $quiet
print '.'
STDOUT.flush
end
# FIXME not safe
length = text.gsub(/^l(\d+)$/, '\1').to_i
sendreqs(socket, length)
next
elsif text[0..0] == 'd' and type == 'CNAME'
text = text[1..-1]
# There's nothing waiting on the remote side
if text == 'zero'
$num_nothing += 1
if not $quiet
print "0"
STDOUT.flush
end
# sleep if needed
num_req = resp.question[0].qname.gsub(/^r(\d+)\..*/, '\1').to_i
if $num_nothing > MAX_NOTHING * 2 and num_req == $num_req
delay = ($num_nothing - (MAX_NOTHING*2)) * DELAY_INCR
delay = DELAY_MAX if delay > DELAY_MAX
elsif $num_nothing > MAX_NOTHING and num_req == $num_req
delay = 0.1
else
delay = 0
end
if delay > 0
if not $quiet
print "W(#{delay})"
STDOUT.flush
end
sleep delay
end
if num_req == $num_req # only resend if we received the last ack
sendreqs(socket, 1)
end
else
$num_nothing = 0
if not $quiet
print 'R'
STDOUT.flush
end
pack = dns_decode(text)
tun.syswrite(pack)
# send request for next packet.
sendreqs(socket, 1)
end
else resp.question[0].qname[0..0] == 'c' and resp.question[0].qtype == 'A'
if resp.answer[0].address == '1.2.3.4'
puts "Config: confirmed."
elsif resp.answer[0].address == '4.3.2.1'
puts "Config: unrecognized options! Strange."
else
puts "unrecognized config reply: \n#{resp}"
end
end
end
end
puts "TUNS started. type \"help\" for inline help."
sendconf($socket, 'reset')
loop do
print '[tuns] '
line = gets.chomp
next if line == ""
cmd, args = line.split(' ', 2)
case cmd
when nil then
when 'help' then
puts <<-EOF
verbose, quiet toggle verbose mode
debug toggle debug mode
maxsleep 2000 set max sleep time on server side (delay in ms)
mtu 200 set the interface's MTU
rtt display packets round trip time (nonsense if maxsleep > 0)
EOF
when 'verbose', 'quiet' then
$quiet = !$quiet
puts "Verbose mode #{$quiet ? 'off' : 'on'}"
when 'rtt' then
$rtt = !$rtt
puts "RTT mode #{$rtt ? 'on' : 'off'}"
when 'debug' then
$debug = !$debug
puts "Debug mode #{$debug ? 'on' : 'off'}"
when 'reset' then
puts "Config: reset to default values"
sendconf($socket, 'reset')
when 'maxsleep' then
ms = args.to_i.to_s
puts "Config: maxsleep = #{ms}"
sendconf($socket, "maxsleep.#{ms}")
when 'mtu' then
$mtu = args.to_i
puts "Config: mtu = #{$mtu}"
sendconf($socket, "mtu.#{$mtu}")
reset_interface
else
puts "Unknown command: #{cmd}. Try \"help\"!"
end
end
# wait until all threads finish
th_recv.join
th_send.join