minitcp 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/minitcp.rb ADDED
@@ -0,0 +1,341 @@
1
+ # LGPL, Author: Regis d'Aubarede <regis.aubarede@gmail.com>
2
+ #
3
+ ##############################################################
4
+ # minitcp.rb
5
+ ##############################################################
6
+
7
+ require 'thread'
8
+ require 'timeout'
9
+ require 'socket'
10
+ require 'gserver'
11
+
12
+
13
+ module SocketReactive
14
+
15
+ def data_readed=(v) @data_readed=v end
16
+ def data_readed() @data_readed||="" end
17
+
18
+ # read n byte, block the caller, return nil if socket if close
19
+ # if block is defined, it is yield with data, method return whith the value of yield
20
+ # if looping is true, the method loop until socket close, (or current thread is killed)
21
+ def receive_n_bytes(sizemax,looping=false,&b)
22
+ s=sizemax
23
+ if self.data_readed.size>=sizemax
24
+ buff,self.data_readed=self.data_readed[0..sizemax-1],self.data_readed[sizemax..-1]
25
+ buff=b.call(buff) if block_given?
26
+ return buff unless looping
27
+ end
28
+ s=sizemax-self.data_readed.size
29
+ loop do
30
+ #p ["waiting ",s,data_readed]
31
+ sd=s>1024 ? 1024 : s
32
+ data=(self.recv(sd) rescue nil)
33
+ #p "nrec: w#{sizemax}/ rec:#{(data||'').size} / #{sd} old=#{data_readed.size} /// #{(data||'').size<70 ? data : "."}"
34
+ if data && data.size>0
35
+ self.data_readed=self.data_readed+data
36
+ s=sizemax-self.data_readed.size
37
+ if s<=0
38
+ buff,self.data_readed=self.data_readed,""
39
+ s=sizemax
40
+ buff=b.call(buff) if block_given?
41
+ return buff unless looping
42
+ end
43
+ else
44
+ close rescue nil
45
+ break # socket close
46
+ end
47
+ end #loop
48
+ end
49
+ # wait n byte or timeout. if block is defined, it is yielded with data
50
+ # return nil if timeout/socket closed, or data if no bloc, or yield value
51
+ def received_n_timeout(sizemax,timeout_ms,&b)
52
+ timeout(timeout_ms/1000.0) {
53
+ ret=receive_n_bytes(sizemax,false,&b)
54
+ return ret
55
+ }
56
+ rescue Timeout::Error
57
+ return nil
58
+ rescue Exception => e
59
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
60
+ end
61
+
62
+ def received_any_timeout(sizemax,timeout_ms)
63
+ timeout(timeout_ms/1000.0) {
64
+ return recv(sizemax)
65
+ }
66
+ rescue Timeout::Error
67
+ return nil
68
+ rescue Exception => e
69
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
70
+ end
71
+
72
+ # async wait and read data on socket, yield values readed,
73
+ # return thread spawned, which can be kill
74
+ def on_any_receive()
75
+ Thread.new() do
76
+ begin
77
+ if self.data_readed.size>0
78
+ buff,self.data_readed=self.data_readed,""
79
+ yield(buff)
80
+ end
81
+ loop do
82
+ data=(self.recv(64*1024) rescue nil)
83
+ data && data.size>0 ? yield(data) : break
84
+ end
85
+ rescue Exception => e
86
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
87
+ end
88
+ close rescue nil
89
+ end
90
+ end
91
+
92
+
93
+ # async yield on received n bytes
94
+ # return thread spawned, which can be kill
95
+ def on_n_receive(sizemax=1,&b)
96
+ Thread.new() do
97
+ begin
98
+ receive_n_bytes(sizemax,true,&b)
99
+ rescue Exception => e
100
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
101
+ end
102
+ end
103
+ end
104
+
105
+ # read until separator reached, block the caller, return nil if socket is close
106
+ # if block is defined, it is yield with data, method return whith the value of yield
107
+ # if looping is true, the method loop until socket close, (or current thread is killed)
108
+ # this read some extra data. they can be retrieve with in socket.data_readed.
109
+ # data_readed is use for next calls to receives_n_byte/receive_sep
110
+ def receive_sep(separator,sizemax=1024,looping=false,&b)
111
+ if self.data_readed.size>0
112
+ a=self.data_readed.split(separator,2)
113
+ while a.size>1
114
+ buff= a.size>2 ? a[0..-2] : a.first
115
+ self.data_readed=a.last
116
+ buff=b.call(buff) if block_given?
117
+ return buff unless looping
118
+ a=self.data_readed.split(separator,2)
119
+ end
120
+ end
121
+ loop do
122
+ data=(self.recv(sizemax-self.data_readed.size) rescue nil)
123
+ if data && data.size>0
124
+ self.data_readed=self.data_readed+data
125
+ a=(self.data_readed).split(separator,2)
126
+ while a.size>1
127
+ buff= a.size>2 ? a[0..-2] : a.first
128
+ self.data_readed=a.last
129
+ buff=b.call(buff) if block_given?
130
+ return buff unless looping
131
+ a=(self.data_readed).split(separator,2)
132
+ end
133
+ else
134
+ close rescue nil
135
+ break
136
+ end
137
+ end
138
+ end
139
+
140
+ # async yield on received data until end-buffer string
141
+ # end-buffer can be string or regexp (args of data.split(,2))
142
+ # return thread spawned, which can be kill
143
+ # this read some extra data. they can be retrieve with in socket.data_readed.
144
+ # data_readed is use for next calls to receives_n_byte/receive_sep
145
+ def on_receive_sep(separator,sizemax=1024,&b)
146
+ Thread.new() do
147
+ begin
148
+ receive_sep(separator,sizemax,looping=true,&b)
149
+ rescue Exception => e
150
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
151
+ end
152
+ end
153
+ end
154
+
155
+ # async yield after a duration, if socket is open
156
+ # return thread spawned, which can be kill
157
+ def after(duration_ms)
158
+ Thread.new() do
159
+ begin
160
+ sleep(duration_ms/1000.0)
161
+ yield unless self.connected?()
162
+ rescue Exception => e
163
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
164
+ end
165
+ end
166
+ end
167
+
168
+ # async yield periodicaly, if socket is open
169
+ # return thread spawned, which can be kill
170
+ def on_timer(value=1000)
171
+ Thread.new() {
172
+ begin
173
+ nbtick=(value/TICK)+1
174
+ loop do
175
+ i=0
176
+ sleep(TICK/1000.0) while self.connected?() && (i+=1)<nbtick
177
+ self.connected?() ? yield() : break
178
+ end
179
+ rescue Exception => e
180
+ $stdout.puts "#{e} :\n #{e.backtrace.join("\n ")}"
181
+ end
182
+ }
183
+ end
184
+
185
+ # wait until curent socket is close.
186
+ def wait_end()
187
+ begin
188
+ loop do
189
+ sleep(TICK/1000.0) while (self.connected?() rescue nil)
190
+ break
191
+ end
192
+ rescue Exception => e
193
+ end
194
+ end
195
+
196
+ # Test if a socket is open. (use socket.remote_address() !)
197
+ def connected?()
198
+ (self.remote_address rescue nil) ? true : false
199
+ end
200
+ # duration of sleep when active wait (wait_end,on_timer...)
201
+ TICK=600
202
+
203
+ def self.make_socket_reactive(socket)
204
+ socket.extend(SocketReactive)
205
+ socket.data_readed=""
206
+ end
207
+ end
208
+
209
+ # Assure connection to server, extend socket connection by SocketReactive module.
210
+ #
211
+ # MClient.run_one_shot("localhost",2200) do |socket| .. end.join
212
+ #
213
+ # MClient.run_continous("localhost",2200,6000) do |socket| .. end.join
214
+ #
215
+ class MClient
216
+ # maintain a conntection to a TCP serveur, sleep timer_interconnection_ms millisecondes
217
+ # beetwen each reconnections
218
+ def self.run_continious(host,port,timer_interconnection_ms,&b)
219
+ Thread.new do
220
+ loop { run_one_shot(host,port,&b).join ; sleep timer_interconnection_ms/1000.0 }
221
+ end
222
+ end
223
+
224
+ def self.run_continous(host,port,timer_interconnection,&b)
225
+ self.run_continious(host,port,timer_interconnection,&b)
226
+ end
227
+
228
+ # Connecte to a TCP server, call block with client socket if connection sucess.
229
+ # enssure close connection after end of block
230
+ def self.run_one_shot(host="localhost",port=80)
231
+ begin
232
+ sleep(0.03) # give some time for server ready (for test...)
233
+ socket = TCPSocket.new(host,port)
234
+ rescue
235
+ puts "not connected to #{host}:#{port}: " + $!.to_s
236
+ return (Thread.new {})
237
+ end
238
+ SocketReactive::make_socket_reactive(socket)
239
+ Thread.new do
240
+ begin
241
+ yield(socket)
242
+ rescue Exception => e
243
+ puts "#{e}\n #{e.backtrace.join("\n ")}"
244
+ ensure
245
+ socket.close() rescue nil
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+
252
+ # Assure connection to server, extend socket connection by SocketReactive module.
253
+ #
254
+ # MClient.run_one_shot("localhost",2200) do |socket| .. end.join
255
+ #
256
+ # MClient.run_continous("localhost",2200,6000) do |socket| .. end.join
257
+ #
258
+ class UDPAgent
259
+ # maintain a conntection to a TCP serveur, sleep timer_interconnection_ms millisecondes
260
+ # beetwen each reconnections
261
+ def self.send_datagram(host,port,mess)
262
+ sock = UDPSocket.new
263
+ #p ["sock.send",mess, 0, host, port]
264
+ sock.send(mess, 0, host, port)
265
+ sock.close
266
+ end
267
+ def self.send_datagram_on_socket(socket,host,port,mess)
268
+ socket.send(mess, 0, host, port)
269
+ end
270
+
271
+ Thread.abort_on_exception=true
272
+
273
+ # send datagram on timer
274
+ def self.on_timer(periode,options)
275
+ Thread.new do
276
+ sleep periode/1000.0
277
+ sock = UDPSocket.new
278
+ if options[:port]
279
+ sock.bind("0.0.0.0", options[:port])
280
+ end
281
+ loop do
282
+ rep=IO.select([sock],nil,nil,periode/1000.0)
283
+ #puts "IO.SELECT => #{rep}"
284
+ if rep
285
+ Thread.new {
286
+ data,peer=sock.recvfrom(1024)
287
+ options[:on_receive].call(data,peer,sock)
288
+ } if options[:on_receive]
289
+ elsif options[:on_timer]
290
+ h=options[:on_timer].call()
291
+ self.send_datagram_on_socket(sock,h[:host], h[:port],h[:mess]) if h && h[:mess] && h[:host] && h[:port]
292
+ end
293
+ end
294
+ end
295
+ end
296
+ # recieved UDP datagramme, bloc can ellaborate a reply, which will be sending to client
297
+ def self.on_datagramme(host,port)
298
+ serv = UDPSocket.new
299
+ serv.bind(host, port)
300
+ Thread.new do
301
+ loop do
302
+ begin
303
+ Thread.new(*serv.recvfrom(1024)) do |data,peer| # peer=["AF_INET", 59340, "127.0.0.1", "127.0.0.1"]
304
+ proto,cli_port,srv_addr,cli_addr=*peer
305
+ response=yield(data,cli_addr,cli_port)
306
+ self.send_datagram_on_socket(serv,cli_addr,cli_port,response) if response
307
+ end
308
+ rescue Exception => e
309
+ puts "#{e}\n #{e.backtrace.join("\n ")}"
310
+ ensure
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ # Run a TCP serveur, with a max connection simultaneous,
318
+ # When connection succes, call the bloc given with socket (extended by SocketReactive).
319
+ #
320
+ # MServer( "8080" , "0.0.0.0" ,1) { |socket| loop { p socket.gets} }
321
+ class MServer < GServer
322
+ def self.service(port,host,max,&b)
323
+ srv=new(port,host,max,&b)
324
+ srv.audit = true
325
+ srv.start
326
+ srv
327
+ end
328
+ def initialize(port,host,max=1,&b)
329
+ super(port,host,max)
330
+ @bloc=b
331
+ end
332
+ def serve( io )
333
+ SocketReactive::make_socket_reactive(io)
334
+ begin
335
+ @bloc.call(io)
336
+ rescue Exception => e
337
+ puts "Error in Mserver block: #{e} :\n #{e.backtrace.join("\n ")}"
338
+ end
339
+ end
340
+ end
341
+
data/minitcp.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push('lib')
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "minitcp"
6
+ s.licenses = ['LGPL']
7
+ s.version = File.read("VERSION").strip
8
+ s.date = Time.now.to_s.split(/\s+/)[0]
9
+ s.email = "regis.aubarede@gmail.com"
10
+ s.homepage = "https://github.com/glurp/minitcp"
11
+ s.authors = ["Regis d'Aubarede"]
12
+ s.summary = "A DSL for programming little Tcp client and server"
13
+ s.description = <<EEND
14
+ A DSL for programming little Tcp client and server
15
+ EEND
16
+
17
+ dependencies = [
18
+ ]
19
+
20
+ s.files = Dir['**/*'].reject { |a| a =~ /^\.git/ || a =~ /\._$/ || a =~ /\.~$/}
21
+ s.test_files = Dir['samples/**']
22
+ s.require_paths = ["lib"]
23
+
24
+ ## Make sure you can build the gem on older versions of RubyGems too:
25
+ s.rubygems_version = "1.8.15"
26
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
27
+ s.specification_version = 3 if s.respond_to? :specification_version
28
+
29
+ dependencies.each do |type, name, version|
30
+ if s.respond_to?("add_#{type}_dependency")
31
+ s.send("add_#{type}_dependency", name, version)
32
+ else
33
+ s.add_dependency(name, version)
34
+ end
35
+ end
36
+ end
37
+
data/samples/README.md ADDED
@@ -0,0 +1,19 @@
1
+ Minitcp usages
2
+ ===
3
+
4
+ **proxy.rb** : real tcp proxy
5
+
6
+ **name_server.rb** : my dns service :)
7
+
8
+ **relay.rb** : not ready , tcp relay for bypass firewall/proxy protection
9
+
10
+ *spdy.rb** : SPDY server relay for http server
11
+
12
+ Dynamic ploting
13
+ ===
14
+
15
+ **plot.rb** : realy no tcp... , plot measures on stdin to screen
16
+
17
+ **pingpongplot.rb** : measure connection time to google.com (for piping to plot.rb)
18
+
19
+ **statcpu.rb** : linux : cpu time to stdout, for piping to plot.rb
@@ -0,0 +1,131 @@
1
+ # LGPL, Author: Regis d'Aubarede <regis.aubarede@gmail.com>
2
+ #################################################################
3
+ # name_server.rb
4
+ #################################################################
5
+ require_relative '../lib/minitcp.rb'
6
+
7
+ BasicSocket.do_not_reverse_lookup = true
8
+ Thread.abort_on_exception = true
9
+
10
+ $SIZE=30
11
+ $dico_name={}
12
+ $dico_ip={}
13
+
14
+ def run_net_relay()
15
+ MServer.service(4410,"0.0.0.0",1000) do |socket|
16
+ socket.on_n_receive($SIZE) do |data|
17
+ ip=socket.remote_address.ip_address
18
+ cmd,url,http=data.split(' ')
19
+ name,method,*args=url.split('/').last
20
+ next if cmd!="GET"
21
+ case method
22
+ when "server"
23
+ old=ActiveConnection.search(name)
24
+ if old && old.state==:free
25
+ old.deconnect
26
+ old=nil
27
+ end
28
+ if !old || old.state==:connected
29
+ ActiveConnection.add(socket)
30
+ end
31
+ when "client"
32
+ if srv=ActiveConnection.search(args.first)
33
+ srv.connect_to(name)
34
+ else
35
+ smess("NOTCONNECT")
36
+ end
37
+ end
38
+ rep=""
39
+ end
40
+ end
41
+ end
42
+
43
+ def smess(socket,*args)
44
+ data= args.join(";")+";"
45
+ socket.send(data+" "*($SIZE-data.length),0) rescue p $!
46
+ end
47
+
48
+ def run_server_name()
49
+ MServer.service(4400,"0.0.0.0",100) do |socket|
50
+ socket.on_n_receive($SIZE) do |data|
51
+ ip=socket.remote_address.ip_address
52
+ cmd,*args=data.split(";")
53
+ name=args.first
54
+ rep=""
55
+ case cmd
56
+ when "update"
57
+ $dico_name[name]=ip
58
+ $dico_ip[ip]=name
59
+ rep="OK"
60
+ when "forget"
61
+ oldip=$dico_ip[ip]
62
+ if oldip && $dico_ip[ip]==name && $dico_name[name]==ip
63
+ $dico_name.delete(name)
64
+ $dico_ip.delete(ip)
65
+ rep="OK"
66
+ end
67
+ when "get"
68
+ rep=$dico_name[name].to_s
69
+ when "geti"
70
+ rep=$dico_ip[name].to_s
71
+ end
72
+ rep.size>0 ? smess(socket,"ACK",name,rep) : smess(socket,"NACK","NOK","NOK")
73
+ end
74
+ socket.after(10*1000) { socket.close rescue nil}
75
+ socket.wait_end
76
+ end
77
+ end
78
+
79
+ ################# Client api ########################
80
+
81
+ def update_server_name(hostname)
82
+ MClient.run_one_shot("localhost",4400) do |socket|
83
+ socket.on_n_receive($SIZE) { |data| p data ; socket.close }
84
+ smess(socket,"update",hostname)
85
+ socket.wait_end
86
+ end
87
+ end
88
+ def fetch_name(hostname)
89
+ ret=""
90
+ MClient.run_one_shot("localhost",4400) do |socket|
91
+ socket.on_n_receive($SIZE) { |data| ret= data.split(";")[2] ; socket.close }
92
+ smess(socket,"get",hostname)
93
+ socket.wait_end
94
+ ret
95
+ end.join
96
+ ret
97
+ end
98
+ def fetch_ip(ip)
99
+ ret=""
100
+ MClient.run_one_shot("localhost",4400) do |socket|
101
+ socket.on_n_receive($SIZE) { |data| ret= data.split(";")[2] ; socket.close }
102
+ smess(socket,"geti",ip)
103
+ socket.wait_end
104
+ ret
105
+ end.join
106
+ ret
107
+ end
108
+ def forget_me(name)
109
+ MClient.run_one_shot("localhost",4400) do |socket|
110
+ ret="?"
111
+ socket.on_n_receive($SIZE) { |data| ret= data.split(";")[2] ; socket.close }
112
+ smess(socket,"forget",name)
113
+ socket.wait_end
114
+ p "forget : #{ret}"
115
+ end.join
116
+ end
117
+
118
+ if $0==__FILE__
119
+ run_server_name
120
+
121
+ Thread.new { 4.times { update_server_name("glurp") ; sleep 1 } }
122
+ Thread.new { 4.times { update_server_name("glurp22") ; sleep 1 } }
123
+
124
+ sleep 2
125
+ puts "\n\n***************** get name *******************\n\n"
126
+
127
+ p fetch_name("glurp")
128
+ p fetch_ip("127.0.0.1")
129
+ forget_me("glurp22")
130
+ sleep
131
+ end
@@ -0,0 +1,26 @@
1
+ # LGPL, Author: Regis d'Aubarede <regis.aubarede@gmail.com>
2
+ #################################################################
3
+ # pingpongplot.rb : measure pingpong time on google, plot it
4
+ #
5
+ # Usage :
6
+ # > ruby pingpongplot.rb | ruby plot.rb 0 0 300 pingpong auto
7
+ #################################################################
8
+ require_relative '../lib/minitcp.rb'
9
+
10
+
11
+ $stdout.sync=true
12
+
13
+ MClient.run_continious("google.com",80,100) do |socket|
14
+ s=Time.now.to_f
15
+ socket.on_receive_sep("\r\n") do |data|
16
+ $stdout.puts("#{(Time.now.to_f-s)*1000}")
17
+ socket.close rescue nil
18
+ Thread.current.kill
19
+ end
20
+ s=Time.now.to_f
21
+ socket.print "GET /blabla HTTP/1.0\r\n\r\n"
22
+ socket.wait_end
23
+ end
24
+
25
+
26
+ sleep