caldecott 0.0.1 → 0.0.3
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.md +1 -2
- data/lib/caldecott.rb +11 -0
- data/lib/caldecott/client.rb +7 -0
- data/lib/caldecott/client/client.rb +79 -0
- data/lib/caldecott/client/http_tunnel.rb +236 -0
- data/lib/caldecott/client/tunnel.rb +23 -0
- data/lib/caldecott/client/websocket_tunnel.rb +36 -0
- data/lib/caldecott/server.rb +4 -0
- data/lib/caldecott/server/http_tunnel.rb +212 -0
- data/lib/caldecott/server/websocket_tunnel.rb +57 -0
- data/lib/caldecott/session_logger.rb +39 -0
- data/lib/caldecott/tcp_connection.rb +38 -0
- data/lib/caldecott/version.rb +1 -1
- metadata +137 -6
data/README.md
CHANGED
data/lib/caldecott.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
module Caldecott
|
6
|
+
module Client
|
7
|
+
def self.sanitize_url(tun_url)
|
8
|
+
tun_url = tun_url =~ /(http|https|ws).*/i ? tun_url : "https://#{tun_url}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.start(opts)
|
12
|
+
local_port = opts[:local_port]
|
13
|
+
tun_url = opts[:tun_url]
|
14
|
+
dst_host = opts[:dst_host]
|
15
|
+
dst_port = opts[:dst_port]
|
16
|
+
log_file = opts[:log_file]
|
17
|
+
log_level = opts[:log_level]
|
18
|
+
auth_token = opts[:auth_token]
|
19
|
+
|
20
|
+
@quiet = opts[:quiet]
|
21
|
+
|
22
|
+
trap("TERM") { stop }
|
23
|
+
trap("INT") { stop }
|
24
|
+
|
25
|
+
tun_url = sanitize_url(tun_url)
|
26
|
+
|
27
|
+
EM.run do
|
28
|
+
unless @quiet
|
29
|
+
puts "Starting local server on port #{local_port} to #{tun_url}"
|
30
|
+
end
|
31
|
+
|
32
|
+
EM.start_server("localhost", local_port, TcpConnection) do |conn|
|
33
|
+
# avoid races between tunnel setup and incoming local data
|
34
|
+
conn.pause
|
35
|
+
|
36
|
+
log = SessionLogger.new("client", log_file)
|
37
|
+
log.level = SessionLogger.severity_from_string(log_level)
|
38
|
+
|
39
|
+
tun = nil
|
40
|
+
|
41
|
+
conn.onopen do
|
42
|
+
log.debug "local connected"
|
43
|
+
tun = Tunnel.start(log, tun_url, dst_host, dst_port, auth_token)
|
44
|
+
end
|
45
|
+
|
46
|
+
tun.onopen do
|
47
|
+
log.debug "tunnel connected"
|
48
|
+
conn.resume
|
49
|
+
end
|
50
|
+
|
51
|
+
conn.onreceive do |data|
|
52
|
+
log.debug "l -> t #{data.length}"
|
53
|
+
tun.send_data(data)
|
54
|
+
end
|
55
|
+
|
56
|
+
tun.onreceive do |data|
|
57
|
+
log.debug("l <- t #{data.length}")
|
58
|
+
conn.send_data(data)
|
59
|
+
end
|
60
|
+
|
61
|
+
conn.onclose do
|
62
|
+
log.debug "local closed"
|
63
|
+
tun.close
|
64
|
+
end
|
65
|
+
|
66
|
+
tun.onclose do
|
67
|
+
log.debug "tunnel closed"
|
68
|
+
conn.close_connection_after_writing
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.stop
|
75
|
+
puts "Caldecott shutting down" unless @quiet
|
76
|
+
EM.stop
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require 'em-http'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Caldecott
|
7
|
+
module Client
|
8
|
+
class HttpTunnel
|
9
|
+
MAX_RETRIES = 10
|
10
|
+
|
11
|
+
def initialize(logger, url, dst_host, dst_port, auth_token)
|
12
|
+
@log, @auth_token = logger, auth_token
|
13
|
+
@closing = false
|
14
|
+
@retries = 0
|
15
|
+
init_msg = ""
|
16
|
+
|
17
|
+
# FIXME: why is this optional?
|
18
|
+
if dst_host
|
19
|
+
init_msg = { :host => dst_host, :port => dst_port }.to_json
|
20
|
+
end
|
21
|
+
|
22
|
+
start(url, init_msg)
|
23
|
+
end
|
24
|
+
|
25
|
+
def onopen(&blk)
|
26
|
+
@onopen = blk
|
27
|
+
@onopen.call if @opened
|
28
|
+
end
|
29
|
+
|
30
|
+
def onclose(&blk)
|
31
|
+
@onclose = blk
|
32
|
+
@onclose.call if @closed
|
33
|
+
end
|
34
|
+
|
35
|
+
def onreceive(&blk)
|
36
|
+
@onreceive = blk
|
37
|
+
end
|
38
|
+
|
39
|
+
def send_data(data)
|
40
|
+
@writer.send_data(data)
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
return if @closing or @closed
|
45
|
+
@closing = true
|
46
|
+
@writer.close if @writer
|
47
|
+
@reader.close if @reader
|
48
|
+
stop
|
49
|
+
end
|
50
|
+
|
51
|
+
def trigger_on_open
|
52
|
+
@opened = true
|
53
|
+
@onopen.call if @onopen
|
54
|
+
end
|
55
|
+
|
56
|
+
def trigger_on_close
|
57
|
+
close
|
58
|
+
@closed = true
|
59
|
+
@onclose.call if @onclose
|
60
|
+
@onclose = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def trigger_on_receive(data)
|
64
|
+
@onreceive.call(data)
|
65
|
+
end
|
66
|
+
|
67
|
+
def start(base_uri, init_msg)
|
68
|
+
if (@retries += 1) > MAX_RETRIES
|
69
|
+
trigger_on_close
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
begin
|
74
|
+
parsed_uri = Addressable::URI.parse(base_uri)
|
75
|
+
parsed_uri.path = '/tunnels'
|
76
|
+
|
77
|
+
@log.debug "post #{parsed_uri.to_s}"
|
78
|
+
req = EM::HttpRequest.new(parsed_uri.to_s).post :body => init_msg, :head => { "Auth-Token" => @auth_token }
|
79
|
+
|
80
|
+
req.callback do
|
81
|
+
@log.debug "post #{parsed_uri.to_s} #{req.response_header.status}"
|
82
|
+
unless [200, 201, 204].include?(req.response_header.status)
|
83
|
+
start(base_uri, init_msg)
|
84
|
+
else
|
85
|
+
@retries = 0
|
86
|
+
resp = JSON.parse(req.response)
|
87
|
+
|
88
|
+
parsed_uri.path = resp["path"]
|
89
|
+
@tun_uri = parsed_uri.to_s
|
90
|
+
|
91
|
+
parsed_uri.path = resp["path_out"]
|
92
|
+
@reader = Reader.new(@log, parsed_uri.to_s, self, @auth_token)
|
93
|
+
|
94
|
+
parsed_uri.path = resp["path_in"]
|
95
|
+
@writer = Writer.new(@log, parsed_uri.to_s, self, @auth_token)
|
96
|
+
trigger_on_open
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
req.errback do
|
101
|
+
@log.debug "post #{parsed_uri.to_s} error"
|
102
|
+
start(base_uri, init_msg)
|
103
|
+
end
|
104
|
+
|
105
|
+
rescue Exception => e
|
106
|
+
@log.error e
|
107
|
+
trigger_on_close
|
108
|
+
raise e
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def stop
|
113
|
+
if (@retries += 1) > MAX_RETRIES
|
114
|
+
trigger_on_close
|
115
|
+
return
|
116
|
+
end
|
117
|
+
|
118
|
+
return if @tun_uri.nil?
|
119
|
+
|
120
|
+
@log.debug "delete #{@tun_uri}"
|
121
|
+
req = EM::HttpRequest.new("#{@tun_uri}").delete :head => { "Auth-Token" => @auth_token }
|
122
|
+
|
123
|
+
req.errback do
|
124
|
+
@log.debug "delete #{@tun_uri} error"
|
125
|
+
stop
|
126
|
+
end
|
127
|
+
|
128
|
+
req.callback do
|
129
|
+
@log.debug "delete #{@tun_uri} #{req.response_header.status}"
|
130
|
+
if [200, 202, 204, 404].include?(req.response_header.status)
|
131
|
+
trigger_on_close
|
132
|
+
else
|
133
|
+
stop
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Reader
|
139
|
+
def initialize(log, uri, conn, auth_token)
|
140
|
+
@log, @base_uri, @conn, @auth_token = log, uri, conn, auth_token
|
141
|
+
@retries = 0
|
142
|
+
@closing = false
|
143
|
+
start
|
144
|
+
end
|
145
|
+
|
146
|
+
def close
|
147
|
+
@closing = true
|
148
|
+
end
|
149
|
+
|
150
|
+
def start(seq = 1)
|
151
|
+
if (@retries += 1) > MAX_RETRIES
|
152
|
+
@conn.trigger_on_close
|
153
|
+
return
|
154
|
+
end
|
155
|
+
|
156
|
+
return if @closing
|
157
|
+
uri = "#{@base_uri}/#{seq}"
|
158
|
+
@log.debug "get #{uri}"
|
159
|
+
req = EM::HttpRequest.new(uri).get :timeout => 0, :head => { "Auth-Token" => @auth_token }
|
160
|
+
|
161
|
+
req.errback do
|
162
|
+
@log.debug "get #{uri} error"
|
163
|
+
start(seq)
|
164
|
+
end
|
165
|
+
|
166
|
+
req.callback do
|
167
|
+
@log.debug "get #{uri} #{req.response_header.status}"
|
168
|
+
case req.response_header.status
|
169
|
+
when 200
|
170
|
+
@conn.trigger_on_receive(req.response)
|
171
|
+
@retries = 0
|
172
|
+
start(seq + 1)
|
173
|
+
when 404
|
174
|
+
@conn.trigger_on_close
|
175
|
+
else
|
176
|
+
start(seq)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class Writer
|
183
|
+
def initialize(log, uri, conn, auth_token)
|
184
|
+
@log, @uri, @conn, @auth_token = log, uri, conn, auth_token
|
185
|
+
@retries = 0
|
186
|
+
@seq, @write_buffer = 1, ""
|
187
|
+
@closing = @writing = false
|
188
|
+
end
|
189
|
+
|
190
|
+
def send_data(data)
|
191
|
+
@write_buffer << data
|
192
|
+
send_data_buffered
|
193
|
+
end
|
194
|
+
|
195
|
+
def close
|
196
|
+
@closing = true
|
197
|
+
end
|
198
|
+
|
199
|
+
def send_data_buffered
|
200
|
+
if (@retries += 1) > MAX_RETRIES
|
201
|
+
@conn.trigger_on_close
|
202
|
+
return
|
203
|
+
end
|
204
|
+
|
205
|
+
return if @closing
|
206
|
+
data, @write_buffer = @write_buffer, "" unless @writing
|
207
|
+
|
208
|
+
@writing = true
|
209
|
+
uri = "#{@uri}/#{@seq}"
|
210
|
+
@log.debug "put #{uri}"
|
211
|
+
req = EM::HttpRequest.new(uri).put :body => data, :head => { "Auth-Token" => @auth_token }
|
212
|
+
|
213
|
+
req.errback do
|
214
|
+
@log.debug "put #{uri} error"
|
215
|
+
send_data_buffered
|
216
|
+
end
|
217
|
+
|
218
|
+
req.callback do
|
219
|
+
@log.debug "put #{uri} #{req.response_header.status}"
|
220
|
+
case req.response_header.status
|
221
|
+
when 200, 202, 204
|
222
|
+
@writing = false
|
223
|
+
@seq += 1
|
224
|
+
@retries = 0
|
225
|
+
send_data_buffered unless @write_buffer.empty?
|
226
|
+
when 404
|
227
|
+
@conn.trigger_on_close
|
228
|
+
else
|
229
|
+
send_data_buffered
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require 'addressable/uri'
|
4
|
+
|
5
|
+
module Caldecott
|
6
|
+
module Client
|
7
|
+
module Tunnel
|
8
|
+
# Note: I wanted to do this with self#new but had issues
|
9
|
+
# with getting send :initialize to figure out the right
|
10
|
+
# number of arguments
|
11
|
+
def self.start(logger, tun_url, dst_host, dst_port, auth_token)
|
12
|
+
case Addressable::URI.parse(tun_url).normalized_scheme
|
13
|
+
when "http", "https"
|
14
|
+
HttpTunnel.new(logger, tun_url, dst_host, dst_port, auth_token)
|
15
|
+
when "ws"
|
16
|
+
WebSocketTunnel.new(logger, tun_url, dst_host, dst_port, auth_token)
|
17
|
+
else
|
18
|
+
raise "invalid url"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require 'em-http'
|
4
|
+
|
5
|
+
module Caldecott
|
6
|
+
module Client
|
7
|
+
class WebSocketTunnel
|
8
|
+
def initialize(logger, url, dst_host, dst_port, auth_token)
|
9
|
+
@ws = EM::HttpRequest.new("#{url}/websocket/#{dst_host}/#{dst_port}").get :timeout => 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def onopen(&blk)
|
13
|
+
@ws.callback { blk.call }
|
14
|
+
end
|
15
|
+
|
16
|
+
def onclose(&blk)
|
17
|
+
@ws.errback { blk.call }
|
18
|
+
@ws.disconnect { blk.call }
|
19
|
+
end
|
20
|
+
|
21
|
+
def onreceive(&blk)
|
22
|
+
@ws.stream { |data| blk.call(Base64.decode64(data)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_data(data)
|
26
|
+
# Um.. as soon as the em websocket object adds a better named
|
27
|
+
# method for this, start using it.
|
28
|
+
@ws.send(Base64.encode64(data))
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@ws.close_connection_after_writing
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'logger'
|
5
|
+
require 'sinatra'
|
6
|
+
require 'sinatra/async'
|
7
|
+
require 'json'
|
8
|
+
require 'uuidtools'
|
9
|
+
require 'eventmachine'
|
10
|
+
require 'caldecott/tcp_connection.rb'
|
11
|
+
require 'caldecott/session_logger.rb'
|
12
|
+
|
13
|
+
module Caldecott
|
14
|
+
module Server
|
15
|
+
class Tunnel
|
16
|
+
attr_reader :tun_id, :log, :last_active_at
|
17
|
+
DEFAULT_MAX_DATA_TO_BUFFER = 1 * 1024 * 1024 # 1MB
|
18
|
+
|
19
|
+
def initialize(log, tunnels, host, port, max_data_to_buffer = DEFAULT_MAX_DATA_TO_BUFFER)
|
20
|
+
@log, @tunnels, @host, @port = log, tunnels, host, port
|
21
|
+
@tun_id = UUIDTools::UUID.random_create.to_s
|
22
|
+
@data = @data_next = ""
|
23
|
+
@seq_out = @seq_in = 0
|
24
|
+
@max_data_to_buffer = max_data_to_buffer
|
25
|
+
@last_active_at = Time.now
|
26
|
+
end
|
27
|
+
|
28
|
+
def open(resp)
|
29
|
+
EM::connect(@host, @port, TcpConnection) do |dst_conn|
|
30
|
+
@dst_conn = dst_conn
|
31
|
+
|
32
|
+
@dst_conn.onopen do
|
33
|
+
@log.debug "dst connected"
|
34
|
+
@tunnels[@tun_id] = self
|
35
|
+
resp.content_type :json
|
36
|
+
resp.status 201
|
37
|
+
resp.body safe_hash.to_json
|
38
|
+
end
|
39
|
+
|
40
|
+
@dst_conn.onreceive do |data|
|
41
|
+
@log.debug "t <- d #{data.length}"
|
42
|
+
@data_next << data
|
43
|
+
trigger_reader
|
44
|
+
@dst_conn.pause if @data_next.length > @max_data_to_buffer
|
45
|
+
end
|
46
|
+
|
47
|
+
@dst_conn.onclose do
|
48
|
+
@log.debug "target disconnected"
|
49
|
+
@dst_conn = nil
|
50
|
+
trigger_reader
|
51
|
+
@tunnels.delete(@tun_id) if @data_next.empty?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@tunnel_created_at = Time.now
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete
|
58
|
+
@log.debug "target disconnected"
|
59
|
+
if @dst_conn
|
60
|
+
@dst_conn.close_connection_after_writing
|
61
|
+
else
|
62
|
+
@tunnels.delete(@tun_id)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def get(resp, seq)
|
67
|
+
@last_active_at = Time.now
|
68
|
+
resp.halt(400, "invalid sequence #{seq} for server seq #{@seq_out}") unless (seq == @seq_out or seq == @seq_out + 1)
|
69
|
+
if seq == @seq_out + 1
|
70
|
+
@data, @data_next = @data_next, ""
|
71
|
+
@seq_out = seq
|
72
|
+
end
|
73
|
+
|
74
|
+
if @data.empty?
|
75
|
+
resp.halt(410, "destination socket closed\n") if @dst_conn.nil?
|
76
|
+
@log.debug "get: waiting for data"
|
77
|
+
@reader = EM.Callback do
|
78
|
+
@data, @data_next = @data_next, ""
|
79
|
+
resp.ahalt(410, "destination socket closed\n") if @data.empty?
|
80
|
+
@log.debug "get: returning data (async)"
|
81
|
+
resp.body @data
|
82
|
+
end
|
83
|
+
else
|
84
|
+
@log.debug "get: returning data (immediate)"
|
85
|
+
resp.body @data
|
86
|
+
@dst_conn.resume
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def put(resp, seq)
|
91
|
+
@last_active_at = Time.now
|
92
|
+
resp.halt(400, "invalid sequence #{seq} for server seq #{@seq_in}") unless (seq == @seq_in or seq == @seq_in + 1)
|
93
|
+
if seq == @seq_in
|
94
|
+
resp.status 201
|
95
|
+
else
|
96
|
+
@seq_in = seq
|
97
|
+
@log.debug "t -> d #{resp.request.body.length}"
|
98
|
+
@dst_conn.send_data(resp.request.body.read)
|
99
|
+
resp.status 202
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def trigger_reader
|
104
|
+
return unless @reader
|
105
|
+
reader = @reader
|
106
|
+
@reader = nil
|
107
|
+
reader.call
|
108
|
+
end
|
109
|
+
|
110
|
+
def safe_hash
|
111
|
+
{
|
112
|
+
:path => "/tunnels/#{@tun_id}",
|
113
|
+
:path_in => "/tunnels/#{@tun_id}/in",
|
114
|
+
:path_out => "/tunnels/#{@tun_id}/out",
|
115
|
+
:dst_host => @host,
|
116
|
+
:dst_port => @port,
|
117
|
+
:dst_connected => @dst_conn.nil? == false,
|
118
|
+
:seq_out => @seq_out,
|
119
|
+
:seq_in => @seq_in
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
class HttpTunnel < Sinatra::Base
|
126
|
+
register Sinatra::Async
|
127
|
+
|
128
|
+
@@tunnels = {}
|
129
|
+
|
130
|
+
def self.tunnels
|
131
|
+
@@tunnels
|
132
|
+
end
|
133
|
+
|
134
|
+
# defaults are 1 hour of inactivity with sweeps every 5 minutes
|
135
|
+
def self.start_timer(inactive_timeout = 3600, sweep_interval = 300)
|
136
|
+
EventMachine::add_periodic_timer sweep_interval do
|
137
|
+
# This is needed because there seems to have a bug on the
|
138
|
+
# Connection#set_comm_inactivity_timeout (int overflow )
|
139
|
+
# Look at eventmachine/ext/em.cpp 2289
|
140
|
+
# It reaps the inactive connections
|
141
|
+
#
|
142
|
+
# We also can not seem to add our own timer per tunnel instance.
|
143
|
+
# When we do, the ruby interpreter freaks out and starts throwing
|
144
|
+
#
|
145
|
+
# errors like:
|
146
|
+
# undefined method `cancel' for 57:Fixnum
|
147
|
+
#
|
148
|
+
# for code like the following during shutdown:
|
149
|
+
# @inactivity_timer.cancel if @inactivity_timer
|
150
|
+
# @inactivity_timer.cancel
|
151
|
+
# @inactivity_timer = nil
|
152
|
+
@@tunnels.each do |id, t|
|
153
|
+
t.delete if (Time.now - t.last_active_at) > inactive_timeout
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def tunnel_from_id(tun_id)
|
159
|
+
tun = @@tunnels[tun_id]
|
160
|
+
not_found("tunnel #{tun_id} does not exist\n") unless tun
|
161
|
+
tun.log.debug "#{request.request_method} #{request.url}"
|
162
|
+
tun
|
163
|
+
end
|
164
|
+
|
165
|
+
before do
|
166
|
+
@log = SessionLogger.new("server", STDOUT)
|
167
|
+
@log.debug "#{request.request_method} #{request.url}"
|
168
|
+
if env['HTTP_AUTH_TOKEN'] != settings.auth_token
|
169
|
+
@log.debug "AUTH FAILURE #{env.inspect}"
|
170
|
+
not_found
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
get '/' do
|
175
|
+
return "Caldecott Tunnel (HTTP Transport) #{VERSION}\n"
|
176
|
+
end
|
177
|
+
|
178
|
+
get '/tunnels' do
|
179
|
+
content_type :json
|
180
|
+
resp = @@tunnels.values.collect { |t| t.safe_hash }
|
181
|
+
resp.to_json
|
182
|
+
end
|
183
|
+
|
184
|
+
apost '/tunnels' do
|
185
|
+
req = JSON.parse(request.body.read, :symbolize_names => true)
|
186
|
+
Tunnel.new(@log, @@tunnels, req[:host], req[:port]).open(self)
|
187
|
+
end
|
188
|
+
|
189
|
+
get '/tunnels/:tun' do |tun_id|
|
190
|
+
tun = tunnel_from_id(tun_id)
|
191
|
+
tun.safe_hash.to_json
|
192
|
+
end
|
193
|
+
|
194
|
+
delete '/tunnels/:tun' do |tun_id|
|
195
|
+
tun = tunnel_from_id(tun_id)
|
196
|
+
tun.delete
|
197
|
+
end
|
198
|
+
|
199
|
+
aget '/tunnels/:tun_id/out/:seq' do |tun_id, seq|
|
200
|
+
tun = tunnel_from_id(tun_id)
|
201
|
+
seq = seq.to_i
|
202
|
+
tun.get(self, seq)
|
203
|
+
end
|
204
|
+
|
205
|
+
put '/tunnels/:tun_id/in/:seq' do |tun_id, seq|
|
206
|
+
tun = tunnel_from_id(tun_id)
|
207
|
+
seq = seq.to_i
|
208
|
+
tun.put(self, seq)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require 'em-websocket'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module Caldecott
|
7
|
+
module Server
|
8
|
+
class WebSocketTunnel
|
9
|
+
|
10
|
+
# quack like sinatra
|
11
|
+
def self.run!(opts)
|
12
|
+
WebSocketTunnel.new.start(opts[:port])
|
13
|
+
end
|
14
|
+
|
15
|
+
def start(port)
|
16
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => port) do |ws|
|
17
|
+
log = SessionLogger::new("server", STDOUT)
|
18
|
+
dst_conn = nil
|
19
|
+
|
20
|
+
ws.onopen do
|
21
|
+
log.debug "tunnel connected"
|
22
|
+
slash, tunnel, host, port = ws.request['Path'].split('/')
|
23
|
+
|
24
|
+
EM::connect(host, port, TcpConnection) do |d|
|
25
|
+
dst_conn = d
|
26
|
+
|
27
|
+
dst_conn.onopen do
|
28
|
+
log.debug "target connected"
|
29
|
+
end
|
30
|
+
|
31
|
+
dst_conn.onreceive do |data|
|
32
|
+
log.debug("t <- d #{data.length}")
|
33
|
+
ws.send(Base64.encode64(data))
|
34
|
+
end
|
35
|
+
|
36
|
+
dst_conn.onclose do
|
37
|
+
log.debug "target disconnected"
|
38
|
+
ws.close_connection
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ws.onmessage do |msg|
|
44
|
+
decoded = Base64.decode64(msg)
|
45
|
+
log.debug("t -> d #{decoded.length}")
|
46
|
+
dst_conn.send_data(decoded)
|
47
|
+
end
|
48
|
+
|
49
|
+
ws.onclose do
|
50
|
+
log.debug "tunnel disconnected"
|
51
|
+
dst_conn.close_connection_after_writing if dst_conn
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Caldecott
|
6
|
+
class SessionLogger < Logger
|
7
|
+
attr_reader :component, :session
|
8
|
+
@@session = 0
|
9
|
+
|
10
|
+
def initialize(component, *args)
|
11
|
+
super(*args)
|
12
|
+
@component = component
|
13
|
+
@session = @@session += 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def format_message(severity, timestamp, progname, msg)
|
17
|
+
"#{@component} [#{@session}] #{msg}\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.severity_from_string(str)
|
21
|
+
case str.upcase
|
22
|
+
when 'DEBUG'
|
23
|
+
Logger::DEBUG
|
24
|
+
when 'INFO'
|
25
|
+
Logger::INFO
|
26
|
+
when 'WARN'
|
27
|
+
Logger::WARN
|
28
|
+
when 'ERROR'
|
29
|
+
Logger::ERROR
|
30
|
+
when 'FATAL'
|
31
|
+
Logger::FATAL
|
32
|
+
when 'UNKNOWN'
|
33
|
+
Logger::UNKNOWN
|
34
|
+
else
|
35
|
+
Logger::ERROR
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
module Caldecott
|
6
|
+
# wrapper to avoid callback and state passing spaghetti
|
7
|
+
class TcpConnection < EventMachine::Connection
|
8
|
+
@initialzied = false
|
9
|
+
|
10
|
+
# callbacks
|
11
|
+
def onopen(&blk)
|
12
|
+
@initialized ? blk.call : @onopen = blk
|
13
|
+
end
|
14
|
+
|
15
|
+
def onreceive(&blk)
|
16
|
+
@onreceive = blk
|
17
|
+
end
|
18
|
+
|
19
|
+
def onclose(&blk)
|
20
|
+
@onclose = blk
|
21
|
+
end
|
22
|
+
|
23
|
+
# handle EventMachine::Connection methods
|
24
|
+
def post_init
|
25
|
+
@initialized = true
|
26
|
+
@onopen.call if @onopen
|
27
|
+
end
|
28
|
+
|
29
|
+
def receive_data(data)
|
30
|
+
@onreceive.call(data) if @onreceive
|
31
|
+
end
|
32
|
+
|
33
|
+
def unbind
|
34
|
+
@onclose.call if @onclose
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/caldecott/version.rb
CHANGED
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: caldecott
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- VMware
|
@@ -10,11 +10,131 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-11-08 00:00:00 -08:00
|
14
14
|
default_executable:
|
15
|
-
dependencies:
|
16
|
-
|
17
|
-
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: em-http-request
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - "="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.3.0
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: em-websocket
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - "="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.3.1
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: async_sinatra
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - "="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.5.0
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: addressable
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - "="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 2.2.6
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: json
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - "="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.6.1
|
69
|
+
type: :runtime
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: uuidtools
|
73
|
+
prerelease: false
|
74
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - "="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 2.1.2
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: rake
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - "="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 0.9.2
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id007
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: rcov
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - "="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 0.9.10
|
102
|
+
type: :development
|
103
|
+
version_requirements: *id008
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: rack-test
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - "="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 0.6.1
|
113
|
+
type: :development
|
114
|
+
version_requirements: *id009
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: rspec
|
117
|
+
prerelease: false
|
118
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - "="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 2.6.0
|
124
|
+
type: :development
|
125
|
+
version_requirements: *id010
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: webmock
|
128
|
+
prerelease: false
|
129
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - "="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 1.7.6
|
135
|
+
type: :development
|
136
|
+
version_requirements: *id011
|
137
|
+
description: Caldecott HTTP/Websocket Tunneling Library
|
18
138
|
email: support@vmware.com
|
19
139
|
executables: []
|
20
140
|
|
@@ -26,7 +146,18 @@ extra_rdoc_files:
|
|
26
146
|
files:
|
27
147
|
- LICENSE
|
28
148
|
- README.md
|
149
|
+
- lib/caldecott/client/client.rb
|
150
|
+
- lib/caldecott/client/http_tunnel.rb
|
151
|
+
- lib/caldecott/client/tunnel.rb
|
152
|
+
- lib/caldecott/client/websocket_tunnel.rb
|
153
|
+
- lib/caldecott/client.rb
|
154
|
+
- lib/caldecott/server/http_tunnel.rb
|
155
|
+
- lib/caldecott/server/websocket_tunnel.rb
|
156
|
+
- lib/caldecott/server.rb
|
157
|
+
- lib/caldecott/session_logger.rb
|
158
|
+
- lib/caldecott/tcp_connection.rb
|
29
159
|
- lib/caldecott/version.rb
|
160
|
+
- lib/caldecott.rb
|
30
161
|
has_rdoc: true
|
31
162
|
homepage: http://vmware.com
|
32
163
|
licenses: []
|
@@ -54,6 +185,6 @@ rubyforge_project:
|
|
54
185
|
rubygems_version: 1.6.2
|
55
186
|
signing_key:
|
56
187
|
specification_version: 3
|
57
|
-
summary:
|
188
|
+
summary: Caldecott HTTP/Websocket Tunneling Library
|
58
189
|
test_files: []
|
59
190
|
|