caldecott-client 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|