socket_proxy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +15 -0
- data/bin/socket_proxy +63 -0
- data/lib/socket_proxy.rb +301 -0
- data/sample/monitor.rb +137 -0
- metadata +49 -0
data/README
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
socket_proxy - Creates I/O pipes for TCP socket tunneling.
|
2
|
+
|
3
|
+
|
4
|
+
== Author
|
5
|
+
|
6
|
+
Name:: Hiroshi Nakamura
|
7
|
+
E-mail:: nahi@ruby-lang.org
|
8
|
+
Project web site:: http://github.com/nahi/socket_proxy
|
9
|
+
|
10
|
+
|
11
|
+
== License
|
12
|
+
|
13
|
+
This program is copyrighted free software by Hiroshi Nakamura. You can
|
14
|
+
redistribute it and/or modify it under the same terms of Ruby's license;
|
15
|
+
either the dual license version in 2003, or any later version.
|
data/bin/socket_proxy
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# socket_proxy.rb -- Creates I/O pipes for TCP socket tunneling.
|
4
|
+
# Copyright (C) 1999-2001, 2003 NAKAMURA, Hiroshi
|
5
|
+
|
6
|
+
# This application is copyrighted free software by NAKAMURA, Hiroshi.
|
7
|
+
# You can redistribute it and/or modify it under the same term as Ruby.
|
8
|
+
|
9
|
+
require 'socket_proxy'
|
10
|
+
require 'getoptlong'
|
11
|
+
|
12
|
+
def main
|
13
|
+
opts = GetoptLong.new(['--debug', '-d', GetoptLong::NO_ARGUMENT], ['--daemon', '-s', GetoptLong::NO_ARGUMENT])
|
14
|
+
opts.each do |name, _|
|
15
|
+
case name
|
16
|
+
when '--debug'
|
17
|
+
debug = true
|
18
|
+
when '--daemon'
|
19
|
+
daemon = true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
destname = ARGV.shift
|
24
|
+
portpairs = []
|
25
|
+
while srcport = ARGV.shift
|
26
|
+
destport = ARGV.shift or raise ArgumentError.new("Port must be given as a pair of src and dest.")
|
27
|
+
portpairs << [srcport, destport]
|
28
|
+
end
|
29
|
+
usage if portpairs.empty? or !destname
|
30
|
+
|
31
|
+
# To run as a daemon...
|
32
|
+
if daemon
|
33
|
+
exit! if fork
|
34
|
+
Process.setsid
|
35
|
+
exit! if fork
|
36
|
+
STDIN.close
|
37
|
+
STDOUT.close
|
38
|
+
STDERR.close
|
39
|
+
end
|
40
|
+
|
41
|
+
app = SocketProxy.new(destname, portpairs)
|
42
|
+
app.dump_response = true if debug
|
43
|
+
app.start
|
44
|
+
end
|
45
|
+
|
46
|
+
def usage
|
47
|
+
STDERR.print <<EOM
|
48
|
+
Usage: #{$0} [OPTIONS] destname srcport destport [[srcport destport]...]
|
49
|
+
|
50
|
+
Creates I/O pipes for TCP socket tunneling.
|
51
|
+
|
52
|
+
destname ... hostname of a destination(name or ip-addr).
|
53
|
+
srcport .... source TCP port# or UNIX domain socket name of localhost.
|
54
|
+
destport ... destination port# of the destination host.
|
55
|
+
|
56
|
+
OPTIONS:
|
57
|
+
-d ......... dumps data from destination port(not dumped by default).
|
58
|
+
-s ......... run as a daemon.
|
59
|
+
EOM
|
60
|
+
exit 1
|
61
|
+
end
|
62
|
+
|
63
|
+
main
|
data/lib/socket_proxy.rb
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
# socket_proxy.rb -- Creates I/O pipes for TCP socket tunneling.
|
2
|
+
# Copyright (C) 1999-2001, 2003 NAKAMURA, Hiroshi
|
3
|
+
|
4
|
+
# This application is copyrighted free software by NAKAMURA, Hiroshi.
|
5
|
+
# You can redistribute it and/or modify it under the same term as Ruby.
|
6
|
+
|
7
|
+
# Ruby bundled library
|
8
|
+
require 'socket'
|
9
|
+
require 'logger'
|
10
|
+
|
11
|
+
class SocketProxy < Logger::Application
|
12
|
+
module Dump
|
13
|
+
# Written by Arai-san and published at [ruby-list:31987].
|
14
|
+
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/31987
|
15
|
+
def hexdump(str)
|
16
|
+
offset = 0
|
17
|
+
result = []
|
18
|
+
while raw = str.slice(offset, 16) and raw.length > 0
|
19
|
+
# data field
|
20
|
+
data = ''
|
21
|
+
for v in raw.unpack('N* a*')
|
22
|
+
if v.kind_of? Integer
|
23
|
+
data << sprintf("%08x ", v)
|
24
|
+
else
|
25
|
+
v.each_byte {|c| data << sprintf("%02x", c) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# text field
|
29
|
+
text = raw.tr("\000-\037\177-\377", ".")
|
30
|
+
result << sprintf("%08x %-36s %s", offset, data, text)
|
31
|
+
offset += 16
|
32
|
+
# omit duplicate line
|
33
|
+
if /^(#{ Regexp.quote(raw) })+/n =~ str[offset .. -1]
|
34
|
+
result << sprintf("%08x ...", offset)
|
35
|
+
offset += $&.length
|
36
|
+
# should print at the end
|
37
|
+
if offset == str.length
|
38
|
+
result << sprintf("%08x %-36s %s", offset-16, data, text)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
result
|
43
|
+
end
|
44
|
+
module_function :hexdump
|
45
|
+
end
|
46
|
+
|
47
|
+
include Logger::Severity
|
48
|
+
include Socket::Constants
|
49
|
+
|
50
|
+
attr_accessor :dump_request
|
51
|
+
attr_accessor :dump_response
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
Timeout = 100 # [sec]
|
56
|
+
ReadBlockSize = 10 * 1024 # [byte]
|
57
|
+
|
58
|
+
class SessionPool
|
59
|
+
def initialize
|
60
|
+
@pool = []
|
61
|
+
@sockets = []
|
62
|
+
end
|
63
|
+
|
64
|
+
def sockets
|
65
|
+
@sockets
|
66
|
+
end
|
67
|
+
|
68
|
+
def each
|
69
|
+
@pool.each do |i|
|
70
|
+
yield i
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def add(svrsock, clntsock)
|
75
|
+
@pool.push(Session.new(svrsock, clntsock))
|
76
|
+
@sockets.push(svrsock, clntsock)
|
77
|
+
end
|
78
|
+
|
79
|
+
def del(session)
|
80
|
+
@pool.delete(session)
|
81
|
+
@sockets.delete(session.server)
|
82
|
+
@sockets.delete(session.client)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
class Session
|
88
|
+
attr(:server)
|
89
|
+
attr(:client)
|
90
|
+
|
91
|
+
def closed?
|
92
|
+
@server.closed? and @client.closed?
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def initialize(server = nil, client = nil)
|
98
|
+
@server = server
|
99
|
+
@client = client
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class ListenSocketHash < Hash
|
105
|
+
def sockets
|
106
|
+
keys
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
AppName = File.basename(__FILE__)
|
111
|
+
ShiftAge = 0
|
112
|
+
ShiftSize = 0
|
113
|
+
|
114
|
+
def initialize(destname, portpairs)
|
115
|
+
super(AppName)
|
116
|
+
set_log(AppName + '.log', ShiftAge, ShiftSize)
|
117
|
+
@log.level = Logger::INFO
|
118
|
+
@destname = destname
|
119
|
+
@portpairs = portpairs
|
120
|
+
@dump_request = true
|
121
|
+
@dump_response = false
|
122
|
+
@sessionpool = SessionPool.new
|
123
|
+
@waitsockets = nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def init_waitsockets
|
127
|
+
@waitsockets = ListenSocketHash.new
|
128
|
+
@portpairs.each do |srcport, destport|
|
129
|
+
wait = if is_for_tcp(srcport)
|
130
|
+
TCPServer.new(srcport.to_i)
|
131
|
+
else
|
132
|
+
UNIXServer.new(srcport)
|
133
|
+
end
|
134
|
+
@waitsockets[wait] = [srcport, destport]
|
135
|
+
dump_start(srcport, destport)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def terminate_waitsockets
|
140
|
+
@waitsockets.each do |sock, portpair|
|
141
|
+
sock.close
|
142
|
+
srcport, destport = portpair
|
143
|
+
File.unlink(srcport) unless is_for_tcp(srcport)
|
144
|
+
dump_end(srcport, destport)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def is_for_tcp(srcport)
|
149
|
+
srcport.to_i != 0
|
150
|
+
end
|
151
|
+
|
152
|
+
def run
|
153
|
+
begin
|
154
|
+
init_waitsockets
|
155
|
+
while true
|
156
|
+
readwait = @sessionpool.sockets + @waitsockets.sockets
|
157
|
+
readready, = IO.select(readwait, nil, nil, Timeout)
|
158
|
+
next unless readready
|
159
|
+
readready.each do |sock|
|
160
|
+
if (portpair = @waitsockets[sock])
|
161
|
+
newsock = sock.accept
|
162
|
+
dump_accept(newsock.peeraddr[2], portpair)
|
163
|
+
if !add_session(newsock, portpair)
|
164
|
+
log(WARN) { 'Closing server socket...' }
|
165
|
+
newsock.close
|
166
|
+
end
|
167
|
+
else
|
168
|
+
@sessionpool.each do |session|
|
169
|
+
if sock.equal?(session.server)
|
170
|
+
transfer(session, true)
|
171
|
+
next
|
172
|
+
elsif sock.equal?(session.client)
|
173
|
+
transfer(session, false)
|
174
|
+
next
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
ensure
|
181
|
+
terminate_waitsockets
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def transfer(session, is_server)
|
186
|
+
readsock = nil
|
187
|
+
writesock = nil
|
188
|
+
if is_server
|
189
|
+
readsock = session.server
|
190
|
+
writesock = session.client
|
191
|
+
else
|
192
|
+
readsock = session.client
|
193
|
+
writesock = session.server
|
194
|
+
end
|
195
|
+
|
196
|
+
readbuf = nil
|
197
|
+
begin
|
198
|
+
readbuf = readsock.sysread(ReadBlockSize)
|
199
|
+
rescue IOError
|
200
|
+
# not opend for reading
|
201
|
+
close_session(session, readsock, writesock)
|
202
|
+
return
|
203
|
+
rescue EOFError
|
204
|
+
close_session(session, readsock, writesock)
|
205
|
+
return
|
206
|
+
rescue Errno::ECONNRESET
|
207
|
+
log(INFO) { "#{$!} while reading." }
|
208
|
+
close_session(session, readsock, writesock)
|
209
|
+
return
|
210
|
+
rescue
|
211
|
+
log(WARN) { "Detected an exception. Stopping ..." }
|
212
|
+
log(WARN) { $! }
|
213
|
+
log(WARN) { $@ }
|
214
|
+
close_session(session, readsock, writesock)
|
215
|
+
return
|
216
|
+
end
|
217
|
+
|
218
|
+
if is_server
|
219
|
+
dump_transfer_data(true, readbuf) if @dump_request
|
220
|
+
else
|
221
|
+
dump_transfer_data(false, readbuf) if @dump_response
|
222
|
+
end
|
223
|
+
|
224
|
+
writesize = 0
|
225
|
+
while (writesize < readbuf.size)
|
226
|
+
begin
|
227
|
+
writesize += writesock.syswrite(readbuf[writesize..-1])
|
228
|
+
rescue Errno::ECONNRESET
|
229
|
+
log(INFO) { "#{$!} while writing." }
|
230
|
+
log(INFO) { $@ }
|
231
|
+
close_session(session, readsock, writesock)
|
232
|
+
return
|
233
|
+
rescue
|
234
|
+
log(WARN) { "Detected an exception. Stopping ..." }
|
235
|
+
log(WARN) { $@ }
|
236
|
+
close_session(session, readsock, writesock)
|
237
|
+
return
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def add_session(svrsock, portpair)
|
243
|
+
srcport, destport = portpair
|
244
|
+
begin
|
245
|
+
clntsock = TCPSocket.new(@destname, destport)
|
246
|
+
rescue
|
247
|
+
log(ERROR) { 'Create client socket failed.' }
|
248
|
+
return
|
249
|
+
end
|
250
|
+
@sessionpool.add(svrsock, clntsock)
|
251
|
+
dump_add_session(portpair)
|
252
|
+
end
|
253
|
+
|
254
|
+
def close_session(session, readsock, writesock)
|
255
|
+
readsock.close_read
|
256
|
+
writesock.close_write
|
257
|
+
if session.closed?
|
258
|
+
@sessionpool.del(session)
|
259
|
+
dump_close_session
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def dump_start(srcport, destport)
|
264
|
+
log(INFO) { 'Started ... src=%s, dest=%s@%s' % [srcport, destport, @destname] }
|
265
|
+
end
|
266
|
+
|
267
|
+
def dump_accept(from, portpair)
|
268
|
+
log(INFO) {
|
269
|
+
srcport, destport = portpair
|
270
|
+
"Accepted ... src=%s from %s" % [srcport, from]
|
271
|
+
}
|
272
|
+
end
|
273
|
+
|
274
|
+
def dump_add_session(portpair)
|
275
|
+
log(INFO) {
|
276
|
+
srcport, destport = portpair
|
277
|
+
'Connection established ... src=%s dest=%s@%s' % [srcport, destport, @destname]
|
278
|
+
}
|
279
|
+
end
|
280
|
+
|
281
|
+
def dump_transfer_data(is_src2dest, data)
|
282
|
+
if is_src2dest
|
283
|
+
log(INFO) { 'Transfer data ... [src] -> [dest]' }
|
284
|
+
else
|
285
|
+
log(INFO) { 'Transfer data ... [src] <- [dest]' }
|
286
|
+
end
|
287
|
+
dump_data(data)
|
288
|
+
end
|
289
|
+
|
290
|
+
def dump_data(data)
|
291
|
+
log(INFO) { "Transferred data;\n" << Dump.hexdump(data).join("\n") }
|
292
|
+
end
|
293
|
+
|
294
|
+
def dump_close_session
|
295
|
+
log(INFO) { 'Connection closed.' }
|
296
|
+
end
|
297
|
+
|
298
|
+
def dump_end(srcport, destport)
|
299
|
+
log(INFO) { 'Stopped ... src=%s, dest=%s@%s' % [srcport, destport, @destname] }
|
300
|
+
end
|
301
|
+
end
|
data/sample/monitor.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/home/achilles/nakahiro/bin/ruby
|
2
|
+
#
|
3
|
+
# TCP Tunnel
|
4
|
+
# Copyright (c) 2001 by Michael Neumann (neumann@s-direktnet.de)
|
5
|
+
#
|
6
|
+
# $Id: monitor.rb,v 1.2 2001/07/13 04:24:29 nakahiro Exp $
|
7
|
+
#
|
8
|
+
# Modified to use TCPSocketPipe and some tk view.
|
9
|
+
# Copyright (c) 2001 NAKAMURA Hiroshi.
|
10
|
+
|
11
|
+
# This application is copyrighted free software by Michael Neumann and
|
12
|
+
# NAKAMURA, Hiroshi. You can redistribute it and/or modify it under
|
13
|
+
# the same term as Ruby or under BSD License.
|
14
|
+
|
15
|
+
require 'TCPSocketPipe'
|
16
|
+
require 'tk'
|
17
|
+
|
18
|
+
|
19
|
+
unless ARGV.size == 3
|
20
|
+
puts "USAGE: #$0 srcPort destName destPort"
|
21
|
+
puts " e.g. #$0 8070 localhost 8080"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
LISTENHOST = 'localhost'
|
26
|
+
LISTENPORT = ARGV.shift
|
27
|
+
TUNNELHOST = ARGV.shift
|
28
|
+
TUNNELPORT = ARGV.shift
|
29
|
+
|
30
|
+
WIDTH = 50
|
31
|
+
HEIGHT = 35
|
32
|
+
|
33
|
+
|
34
|
+
root = TkRoot.new { title "TCP Tunnel/Monitor: Tunneling #{LISTENHOST}:#{LISTENPORT} to #{TUNNELHOST}:#{TUNNELPORT}" }
|
35
|
+
|
36
|
+
top = TkFrame.new(root) {
|
37
|
+
pack( 'side' => 'top', 'fill' => 'x' )
|
38
|
+
}
|
39
|
+
|
40
|
+
bottom2 = TkFrame.new(root) {
|
41
|
+
pack( 'side' => 'bottom', 'fill' => 'both' )
|
42
|
+
}
|
43
|
+
|
44
|
+
bottom3 = TkFrame.new(bottom2) {
|
45
|
+
pack 'side' => 'bottom', 'fill' => 'x'
|
46
|
+
}
|
47
|
+
|
48
|
+
bottom = TkFrame.new(bottom2) {
|
49
|
+
pack( 'side' => 'top', 'fill' => 'both' )
|
50
|
+
}
|
51
|
+
|
52
|
+
bot_label = TkLabel.new(bottom3) {
|
53
|
+
text "Listening for connections on port #{LISTENPORT} for host #{LISTENHOST}"
|
54
|
+
pack
|
55
|
+
}
|
56
|
+
|
57
|
+
llabel = TkLabel.new(top) {
|
58
|
+
text "From #{LISTENHOST}:#{LISTENPORT}"
|
59
|
+
pack 'side' => 'right'
|
60
|
+
}
|
61
|
+
rlabel = TkLabel.new(top) {
|
62
|
+
text "From #{TUNNELHOST}:#{TUNNELPORT} "
|
63
|
+
pack 'side' => 'left'
|
64
|
+
}
|
65
|
+
|
66
|
+
$ltext = TkText.new(bottom, 'width' => WIDTH, 'height' => HEIGHT) {
|
67
|
+
pack( 'side' => 'left', 'fill' => 'y' )
|
68
|
+
}
|
69
|
+
$rtext = TkText.new(bottom, 'width' => WIDTH, 'height' => HEIGHT) {
|
70
|
+
pack( 'side' => 'right', 'fill' => 'y' )
|
71
|
+
}
|
72
|
+
|
73
|
+
scroll = TkScrollbar.new(bottom) {
|
74
|
+
command proc { |arg|
|
75
|
+
$ltext.yview *arg
|
76
|
+
$rtext.yview *arg
|
77
|
+
}
|
78
|
+
pack( 'side' => 'right', 'fill' => 'y' )
|
79
|
+
}
|
80
|
+
|
81
|
+
$ltext.configure( 'yscrollcommand' => proc { |arg| scroll.set *arg } )
|
82
|
+
$ltext.yscrollcommand( proc { |arg| scroll.set *arg } )
|
83
|
+
$rtext.configure( 'yscrollcommand' => proc { |arg| scroll.set *arg } )
|
84
|
+
$rtext.yscrollcommand( proc { |arg| scroll.set *arg } )
|
85
|
+
|
86
|
+
$sessionCount = 0
|
87
|
+
$sessionResetP = false
|
88
|
+
TkButton.new(top) {
|
89
|
+
text "Clear"
|
90
|
+
command {
|
91
|
+
$ltext.value = ""
|
92
|
+
$rtext.value = ""
|
93
|
+
$sessionResetP = true
|
94
|
+
}
|
95
|
+
pack
|
96
|
+
}
|
97
|
+
|
98
|
+
|
99
|
+
class TCPSocketPipe < Application
|
100
|
+
def dumpTransferData( isFromSrcToDestP, data )
|
101
|
+
if isFromSrcToDestP
|
102
|
+
log( SEV_INFO, 'Transfer data ... [src] -> [dest]' )
|
103
|
+
$ltext.insert( 'end', data ) unless $sessionResetP
|
104
|
+
else
|
105
|
+
log( SEV_INFO, 'Transfer data ... [src] <- [dest]' )
|
106
|
+
$rtext.insert( 'end', data ) unless $sessionResetP
|
107
|
+
end
|
108
|
+
dumpData( data )
|
109
|
+
end
|
110
|
+
|
111
|
+
def dumpAddSession
|
112
|
+
$sessionCount += 1
|
113
|
+
str = "--<open: ##{ $sessionCount }>--"
|
114
|
+
$ltext.insert( 'end', str + '-' * ( WIDTH - str.size ) << "\n" )
|
115
|
+
$rtext.insert( 'end', str + '-' * ( WIDTH - str.size ) << "\n" )
|
116
|
+
end
|
117
|
+
|
118
|
+
def dumpCloseSession
|
119
|
+
if $sessionResetP
|
120
|
+
$sessionCount = 0
|
121
|
+
$sessionResetP = false
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
125
|
+
str = "--<close: ##{ $sessionCount }>--"
|
126
|
+
$ltext.insert( 'end', "\n" << str << '-' * ( WIDTH - str.size ) << "\n" )
|
127
|
+
$rtext.insert( 'end', "\n" << str << '-' * ( WIDTH - str.size ) << "\n" )
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
Thread.new {
|
132
|
+
app = TCPSocketPipe.new( LISTENPORT, TUNNELHOST, TUNNELPORT )
|
133
|
+
app.dumpResponse = true
|
134
|
+
app.start()
|
135
|
+
}
|
136
|
+
|
137
|
+
Tk.mainloop
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: socket_proxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Hiroshi Nakamura
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-22 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: nahi@ruby-lang.org
|
16
|
+
executables:
|
17
|
+
- socket_proxy
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/socket_proxy.rb
|
22
|
+
- bin/socket_proxy
|
23
|
+
- sample/monitor.rb
|
24
|
+
- README
|
25
|
+
homepage: http://github.com/nahi/socket_proxy
|
26
|
+
licenses: []
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ! '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 1.8.11
|
46
|
+
signing_key:
|
47
|
+
specification_version: 3
|
48
|
+
summary: Creates I/O pipes for TCP socket tunneling.
|
49
|
+
test_files: []
|