caldecott-client 0.0.1

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