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 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,6 @@
1
+ # caldecott-client
2
+ TCP over HTTP tunnel.
3
+
4
+ ## File a Bug
5
+
6
+ To file a bug against Cloud Foundry Open Source and its components, sign up and use our bug tracking system: [http://cloudfoundry.atlassian.net](http://cloudfoundry.atlassian.net)
@@ -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
@@ -0,0 +1,7 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ module Caldecott
4
+ module Client
5
+ VERSION = "0.0.1"
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require "caldecott-client/client"
4
+ require "caldecott-client/version"
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
+