caldecott-client 0.0.1
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/LICENSE +23 -0
- data/README.md +6 -0
- data/lib/caldecott-client/client.rb +195 -0
- data/lib/caldecott-client/tunnel/http_tunnel.rb +132 -0
- data/lib/caldecott-client/tunnel/tunnel.rb +56 -0
- data/lib/caldecott-client/version.rb +7 -0
- data/lib/caldecott-client.rb +4 -0
- metadata +167 -0
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2010-2011 VMware Inc, All Rights Reserved
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
21
|
+
This software downloads additional open source software components upon install
|
22
|
+
that are distributed under separate terms and conditions. Please see the license
|
23
|
+
information provided in the individual software components for more information.
|
data/README.md
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
require "logger"
|
5
|
+
|
6
|
+
$:.unshift(File.join(File.dirname(__FILE__), "tunnel"))
|
7
|
+
|
8
|
+
require "tunnel"
|
9
|
+
require "http_tunnel"
|
10
|
+
|
11
|
+
module Caldecott
|
12
|
+
|
13
|
+
class NotImplemented < StandardError; end
|
14
|
+
class InvalidTunnelUrl < StandardError; end
|
15
|
+
class ServerError < StandardError; end
|
16
|
+
class ClientError < StandardError; end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :logger
|
20
|
+
|
21
|
+
def init
|
22
|
+
@logger = Logger.new(STDOUT)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
init
|
27
|
+
|
28
|
+
module Client
|
29
|
+
|
30
|
+
BUFFER_SIZE = 1024 * 1024 # 1Mb
|
31
|
+
SOCKET_TIMEOUT = 30000 # 30s
|
32
|
+
|
33
|
+
# This is how it's currently used by VMC:
|
34
|
+
# Caldecott::Client.start(
|
35
|
+
# :local_port => local_port,
|
36
|
+
# :tun_url => tunnel_url,
|
37
|
+
# :dst_host => conn_info['hostname'],
|
38
|
+
# :dst_port => conn_info['port'],
|
39
|
+
# :log_file => STDOUT,
|
40
|
+
# :log_level => ENV["VMC_TUNNEL_DEBUG"] || "ERROR",
|
41
|
+
# :auth_token => auth,
|
42
|
+
# :quiet => true
|
43
|
+
# )
|
44
|
+
def self.start(options)
|
45
|
+
@client = CaldecottClient.new(options)
|
46
|
+
@client.start
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.stop
|
50
|
+
@client.close if @client
|
51
|
+
end
|
52
|
+
|
53
|
+
class CaldecottClient
|
54
|
+
|
55
|
+
def initialize(options)
|
56
|
+
raise InvalidTunnelUrl, "Tunnel URL is required" unless options[:tun_url]
|
57
|
+
|
58
|
+
if options[:log_file]
|
59
|
+
Caldecott.logger = Logger.new(options[:log_file])
|
60
|
+
end
|
61
|
+
|
62
|
+
Caldecott.logger.level = logger_severity_from_string(options[:log_level])
|
63
|
+
|
64
|
+
@logger = Caldecott.logger
|
65
|
+
|
66
|
+
@local_port = options[:local_port] || 20000
|
67
|
+
|
68
|
+
@tunnel_url = sanitize_url(options[:tun_url])
|
69
|
+
|
70
|
+
@tunnel_options = {
|
71
|
+
:dst_host => options[:dst_host],
|
72
|
+
:dst_port => options[:dst_port],
|
73
|
+
:token => options[:auth_token]
|
74
|
+
}
|
75
|
+
|
76
|
+
@closed = false
|
77
|
+
end
|
78
|
+
|
79
|
+
def close
|
80
|
+
@closed = true
|
81
|
+
end
|
82
|
+
|
83
|
+
def closed?
|
84
|
+
@closed
|
85
|
+
end
|
86
|
+
|
87
|
+
def start
|
88
|
+
@logger.info("Starting the tunnel on port #{@local_port}...")
|
89
|
+
server = TCPServer.new("127.0.0.1", @local_port)
|
90
|
+
@logger.info("Tunnel started")
|
91
|
+
|
92
|
+
loop do
|
93
|
+
# server.accept is blocking until request is received
|
94
|
+
Thread.new(server.accept) do |conn|
|
95
|
+
@logger.info("Connection accepted on #{@local_port}...")
|
96
|
+
|
97
|
+
tunnel = create_tunnel
|
98
|
+
tunnel.start
|
99
|
+
|
100
|
+
w = Thread.new do
|
101
|
+
write_to_tunnel(tunnel, conn)
|
102
|
+
end
|
103
|
+
|
104
|
+
r = Thread.new do
|
105
|
+
read_from_tunnel(tunnel, conn)
|
106
|
+
end
|
107
|
+
|
108
|
+
c = Thread.new do
|
109
|
+
control_threads(tunnel, conn, [w, r])
|
110
|
+
end
|
111
|
+
|
112
|
+
r.join
|
113
|
+
w.join
|
114
|
+
c.join
|
115
|
+
|
116
|
+
@logger.info("Closing tunnel")
|
117
|
+
conn.close
|
118
|
+
tunnel.stop
|
119
|
+
|
120
|
+
break if closed?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
server.close
|
125
|
+
end
|
126
|
+
|
127
|
+
def create_tunnel
|
128
|
+
Tunnel.for_url(@tunnel_url, @tunnel_options)
|
129
|
+
end
|
130
|
+
|
131
|
+
def control_threads(tunnel, conn, threads)
|
132
|
+
loop do
|
133
|
+
if tunnel.closed? || conn.closed? || closed?
|
134
|
+
threads.each { |t| t.exit }
|
135
|
+
break
|
136
|
+
end
|
137
|
+
|
138
|
+
sleep(0.01)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def read_from_tunnel(tunnel, conn)
|
143
|
+
in_buf = ""
|
144
|
+
loop do
|
145
|
+
in_buf << tunnel.read unless tunnel.closed?
|
146
|
+
|
147
|
+
break if in_buf.size < 1
|
148
|
+
|
149
|
+
if in_buf.size > 0
|
150
|
+
n_sent = conn.sendmsg(in_buf.slice!(0, BUFFER_SIZE))
|
151
|
+
@logger.debug("l <- t: #{n_sent}, buf: #{in_buf.size}")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def write_to_tunnel(tunnel, conn)
|
157
|
+
loop do
|
158
|
+
out_data = conn.recv(BUFFER_SIZE)
|
159
|
+
|
160
|
+
break if out_data.size < 1 # connection closed
|
161
|
+
|
162
|
+
@logger.debug("l -> t: #{out_data.size}")
|
163
|
+
tunnel.write(out_data)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def sanitize_url(tun_url)
|
170
|
+
tun_url = tun_url.strip.downcase
|
171
|
+
tun_url =~ /(http|https|ws).*/i ? tun_url : "https://#{tun_url}"
|
172
|
+
end
|
173
|
+
|
174
|
+
def logger_severity_from_string(str)
|
175
|
+
case str.to_s.upcase
|
176
|
+
when "DEBUG"
|
177
|
+
Logger::DEBUG
|
178
|
+
when "INFO"
|
179
|
+
Logger::INFO
|
180
|
+
when "WARN"
|
181
|
+
Logger::WARN
|
182
|
+
when "ERROR"
|
183
|
+
Logger::ERROR
|
184
|
+
when "FATAL"
|
185
|
+
Logger::FATAL
|
186
|
+
when "UNKNOWN"
|
187
|
+
Logger::UNKNOWN
|
188
|
+
else
|
189
|
+
Logger::ERROR
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "net/http"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
module Caldecott
|
8
|
+
module Client
|
9
|
+
class HttpTunnel < Tunnel
|
10
|
+
MAX_RETRIES = 10
|
11
|
+
|
12
|
+
attr_reader :path_in, :path_out, :write_seq, :read_seq
|
13
|
+
|
14
|
+
def initialize(url, options)
|
15
|
+
super
|
16
|
+
|
17
|
+
begin
|
18
|
+
@tun_url = URI.parse(url)
|
19
|
+
rescue URI::Error
|
20
|
+
raise ClientError, "Parsing tunnel URL failed"
|
21
|
+
end
|
22
|
+
|
23
|
+
@path_in = nil
|
24
|
+
@path_out = nil
|
25
|
+
@write_seq = nil
|
26
|
+
@read_seq = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def start
|
30
|
+
req = Net::HTTP::Post.new("/tunnels")
|
31
|
+
req.body = JSON.generate(:host => dst_host, :port => dst_port)
|
32
|
+
req["Content-Type"] = "application/json"
|
33
|
+
|
34
|
+
resp = request(req, "creating tunnel remotely", [200, 201, 204])
|
35
|
+
|
36
|
+
begin
|
37
|
+
payload = JSON.parse(resp.body)
|
38
|
+
rescue
|
39
|
+
raise ClientError, "Parsing response data failed"
|
40
|
+
end
|
41
|
+
|
42
|
+
@tunnel_path = payload["path"]
|
43
|
+
@path_in = payload["path_in"]
|
44
|
+
@path_out = payload["path_out"]
|
45
|
+
|
46
|
+
logger.info("Init success: tunnel_path=#{@tunnel_path}, " +
|
47
|
+
"path_in=#{@path_in}, path_out=#{@path_out}")
|
48
|
+
@read_seq = 1
|
49
|
+
@write_seq = 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def write(data)
|
53
|
+
if @path_in.nil? || @write_seq.nil?
|
54
|
+
raise ClientError, "Cannot write, tunnel isn't ready"
|
55
|
+
end
|
56
|
+
|
57
|
+
req = Net::HTTP::Put.new(@path_in + "/#{@write_seq}")
|
58
|
+
req.body = data
|
59
|
+
logger.debug("Sending #{data.bytesize} bytes")
|
60
|
+
|
61
|
+
resp = request(req, "sending data", [200, 202, 204, 404, 410])
|
62
|
+
|
63
|
+
if [404, 410].include?(resp.code.to_i)
|
64
|
+
close
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
@write_seq += 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def read
|
72
|
+
if @path_out.nil? || @read_seq.nil?
|
73
|
+
raise ClientError, "Cannot read, tunnel isn't ready"
|
74
|
+
end
|
75
|
+
|
76
|
+
req = Net::HTTP::Get.new(@path_out + "/#{@read_seq}")
|
77
|
+
resp = request(req, "reading data", [200, 404, 410])
|
78
|
+
|
79
|
+
@read_seq += 1
|
80
|
+
|
81
|
+
if [404, 410].include?(resp.code.to_i)
|
82
|
+
close
|
83
|
+
return ""
|
84
|
+
end
|
85
|
+
|
86
|
+
resp.body
|
87
|
+
end
|
88
|
+
|
89
|
+
def stop
|
90
|
+
return unless @tunnel_path # failed to start
|
91
|
+
req = Net::HTTP::Delete.new(@tunnel_path)
|
92
|
+
request(req, "closing remote tunnel", [200, 202, 204, 404])
|
93
|
+
close
|
94
|
+
end
|
95
|
+
|
96
|
+
def request(req, msg, success_codes)
|
97
|
+
req["Auth-Token"] = token
|
98
|
+
retries = 0
|
99
|
+
resp = nil
|
100
|
+
|
101
|
+
loop do
|
102
|
+
retries += 1
|
103
|
+
logger.info("#{msg.capitalize} (retries=#{retries})")
|
104
|
+
|
105
|
+
begin
|
106
|
+
http = Net::HTTP.new(@tun_url.host, @tun_url.port)
|
107
|
+
http.use_ssl = @tun_url.scheme == "https"
|
108
|
+
|
109
|
+
resp = http.request(req)
|
110
|
+
|
111
|
+
break if success_codes.include?(resp.code.to_i)
|
112
|
+
|
113
|
+
logger.debug("Failed #{msg}: HTTP #{resp.code.to_i}")
|
114
|
+
rescue Timeout::Error
|
115
|
+
logger.error("Failed #{msg}: Timeout error")
|
116
|
+
rescue EOFError # HTTP quirk?
|
117
|
+
logger.debug("Failed #{msg}: #{e.message}")
|
118
|
+
rescue StandardError => e # To satisfy current specs, do we need this really?
|
119
|
+
logger.error("Failed #{msg}: #{e.message}")
|
120
|
+
end
|
121
|
+
|
122
|
+
if retries >= MAX_RETRIES
|
123
|
+
raise ServerError, "Failed #{msg}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
resp
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
|
3
|
+
module Caldecott
|
4
|
+
module Client
|
5
|
+
class Tunnel
|
6
|
+
|
7
|
+
def self.for_url(url, options)
|
8
|
+
case url
|
9
|
+
when /^https?/
|
10
|
+
HttpTunnel.new(url, options)
|
11
|
+
when /^ws/
|
12
|
+
# TODO: implement
|
13
|
+
raise NotImplemented, "Web Sockets support coming soon"
|
14
|
+
else
|
15
|
+
raise InvalidTunnelUrl,
|
16
|
+
"Invalid tunnel url: #{url}, only HTTP and WS schemas supported"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :url, :dst_host, :dst_port, :token, :logger
|
21
|
+
|
22
|
+
def initialize(url, options)
|
23
|
+
@url = url
|
24
|
+
@dst_host = options[:dst_host]
|
25
|
+
@dst_port = options[:dst_port]
|
26
|
+
@token = options[:token]
|
27
|
+
@logger = Caldecott.logger
|
28
|
+
@closed = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@closed = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def closed?
|
36
|
+
@closed
|
37
|
+
end
|
38
|
+
|
39
|
+
def start
|
40
|
+
raise NotImplemented, "#start not implemented for #{self.class.name}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def write(data)
|
44
|
+
raise NotImplemented, "#write not implemented for #{self.class.name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def read
|
48
|
+
raise NotImplemented, "#read not implemented for #{self.class.name}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def stop
|
52
|
+
raise NotImplemented, "#stop not implemented for #{self.class.name}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: caldecott-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- VMware
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-01-10 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: json
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 13
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 6
|
32
|
+
- 1
|
33
|
+
version: 1.6.1
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 63
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 9
|
48
|
+
- 2
|
49
|
+
version: 0.9.2
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: rcov
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 47
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
- 9
|
64
|
+
- 10
|
65
|
+
version: 0.9.10
|
66
|
+
type: :development
|
67
|
+
version_requirements: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rack-test
|
70
|
+
prerelease: false
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 5
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
- 6
|
80
|
+
- 1
|
81
|
+
version: 0.6.1
|
82
|
+
type: :development
|
83
|
+
version_requirements: *id004
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rspec
|
86
|
+
prerelease: false
|
87
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ~>
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 35
|
93
|
+
segments:
|
94
|
+
- 2
|
95
|
+
- 11
|
96
|
+
- 0
|
97
|
+
version: 2.11.0
|
98
|
+
type: :development
|
99
|
+
version_requirements: *id005
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: webmock
|
102
|
+
prerelease: false
|
103
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ~>
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
hash: 7
|
109
|
+
segments:
|
110
|
+
- 1
|
111
|
+
- 7
|
112
|
+
- 6
|
113
|
+
version: 1.7.6
|
114
|
+
type: :development
|
115
|
+
version_requirements: *id006
|
116
|
+
description: Caldecott Client HTTP/Websocket Tunneling Library
|
117
|
+
email: support@vmware.com
|
118
|
+
executables: []
|
119
|
+
|
120
|
+
extensions: []
|
121
|
+
|
122
|
+
extra_rdoc_files:
|
123
|
+
- README.md
|
124
|
+
- LICENSE
|
125
|
+
files:
|
126
|
+
- LICENSE
|
127
|
+
- README.md
|
128
|
+
- lib/caldecott-client/client.rb
|
129
|
+
- lib/caldecott-client/tunnel/http_tunnel.rb
|
130
|
+
- lib/caldecott-client/tunnel/tunnel.rb
|
131
|
+
- lib/caldecott-client/version.rb
|
132
|
+
- lib/caldecott-client.rb
|
133
|
+
homepage: http://vmware.com
|
134
|
+
licenses: []
|
135
|
+
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
hash: 3
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
version: "0"
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
hash: 3
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
version: "0"
|
159
|
+
requirements: []
|
160
|
+
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 1.8.24
|
163
|
+
signing_key:
|
164
|
+
specification_version: 3
|
165
|
+
summary: Caldecott Client HTTP/Websocket Tunneling Library
|
166
|
+
test_files: []
|
167
|
+
|