multitunnels 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +62 -0
- data/Rakefile +1 -0
- data/bin/multitunnels +13 -0
- data/lib/tunnels.rb +232 -0
- data/lib/tunnels/version.rb +3 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/tunnels_spec.rb +24 -0
- data/tunnels.gemspec +24 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5f6ffdbbf77a056e8f65856cb0cadf28981a6603
|
4
|
+
data.tar.gz: 13552a5f830d8b620d59192b98568c62a04a4057
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 59ec67f3950c913fd4ea360a1ff072bf1d883a21073b9f9420ea6388ce07c15f252772db3919ef644d17d0efa7d4252df84a311d4ab724f255d1c66de3bd34ff
|
7
|
+
data.tar.gz: 7068d4edd06ca66d1444c7e5ba9cb14473b19ee5e0bfd77521fc2967cf8e9299a9773a2eb3666303a8dba7cfc0b3f2363de59bca0f16d12453ae9903acd8fd09
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
Tunnels
|
2
|
+
=======
|
3
|
+
|
4
|
+

|
5
|
+
|
6
|
+
Tunnels is a proxy to http/https from http/https.
|
7
|
+
|
8
|
+
You can run the [Pow](http://pow.cx/) over SSL!
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
$ gem install multitunnels
|
14
|
+
|
15
|
+
Run
|
16
|
+
---
|
17
|
+
|
18
|
+
$ sudo multitunnels
|
19
|
+
|
20
|
+
If you are using rvm:
|
21
|
+
|
22
|
+
$ rvmsudo multitunnels
|
23
|
+
|
24
|
+
By default, proxy to 80 port from 443 port.
|
25
|
+
|
26
|
+
specify "http" port and "https" port:
|
27
|
+
|
28
|
+
$ sudo multitunnels 443 3000
|
29
|
+
|
30
|
+
or
|
31
|
+
|
32
|
+
$ sudo multitunnels 127.0.0.1:443 127.0.0.1:3000
|
33
|
+
or
|
34
|
+
|
35
|
+
$ sudo multitunnels https://127.0.0.1:443 http://127.0.0.1:3000
|
36
|
+
|
37
|
+
You can also proxy to 443 port from 80 port.
|
38
|
+
|
39
|
+
$ sudo multitunnels http://127.0.0.1:80 https://127.0.0.1:443
|
40
|
+
|
41
|
+
If hostname is '127.0.0.1' or 'localhost', you can choose to omit them.
|
42
|
+
|
43
|
+
$ sudo multitunnels http://:80 https://:443
|
44
|
+
|
45
|
+
You can proxy to other host, then you can `curl http://localhost:3000` to visit Google Hongkong
|
46
|
+
|
47
|
+
$ multitunnels http://:3000 https://www.google.com.hk:443
|
48
|
+
|
49
|
+
You can proxy from Baidu to Google :) someone might be confused (do not forget to add entry `127.0.0.1 www.baidu.com` to /etc/hosts)
|
50
|
+
|
51
|
+
$ sudo multitunnels http://www.baidu.com https://www.google.com.hk
|
52
|
+
|
53
|
+
As the name implies, you can create lots of proxies in one command (but do not forget to add entry `127.0.0.1 www.baidu.com www.samsung.com` to /etc/hosts)
|
54
|
+
|
55
|
+
$ sudo multitunnels http://www.baidu.com https://www.google.com.hk http://www.samsung.com http://www.apple.com 443 80 8000 3000
|
56
|
+
|
57
|
+
This command creates four proxies.
|
58
|
+
|
59
|
+
Copyright
|
60
|
+
---------
|
61
|
+
|
62
|
+
Copyright (c) 2012 jugyo, released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/multitunnels
ADDED
data/lib/tunnels.rb
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'tunnels/version'
|
2
|
+
require 'uri'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
# most of code is from [thin-glazed](https://github.com/freelancing-god/thin-glazed).
|
6
|
+
# Copyright © 2012, Thin::Glazed was a Rails Camp New Zealand project, and is developed and maintained by Pat Allan. It is released under the open MIT Licence.
|
7
|
+
|
8
|
+
class ClientError < StandardError; end
|
9
|
+
|
10
|
+
module Tunnels
|
11
|
+
def self.run!(params)
|
12
|
+
maps = parse_params params
|
13
|
+
|
14
|
+
maps.each do |from, to|
|
15
|
+
puts "#{from[0]}://#{from[1]}:#{from[2]} --(--)--> #{to[0]}://#{to[1]}:#{to[2]}"
|
16
|
+
end
|
17
|
+
|
18
|
+
maps = group_params maps
|
19
|
+
|
20
|
+
EventMachine.run do
|
21
|
+
servers = {}
|
22
|
+
maps.each do |from, map|
|
23
|
+
scheme, port = from
|
24
|
+
servers[from] ||= case scheme
|
25
|
+
when :https
|
26
|
+
EventMachine.start_server('127.0.0.1', port, HttpsProxy, map)
|
27
|
+
when :http
|
28
|
+
EventMachine.start_server('127.0.0.1', port, HttpProxy, map)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
puts "Ready :)"
|
32
|
+
end
|
33
|
+
rescue RuntimeError => e
|
34
|
+
STDERR.puts e.message
|
35
|
+
STDERR.puts "Maybe you should run on `sudo`"
|
36
|
+
rescue ClientError => e
|
37
|
+
STDERR.puts e.message
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.help
|
41
|
+
<<-HELP
|
42
|
+
Usage:
|
43
|
+
tunnels [from to] [from2 to2] ...
|
44
|
+
|
45
|
+
Examples:
|
46
|
+
tunnels 443 3000
|
47
|
+
tunnels localhost:443 localhost:3000
|
48
|
+
tunnels https://:443 http://:3000
|
49
|
+
tunnels https://localhost:443 http://localhost:3000
|
50
|
+
|
51
|
+
HELP
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def self.parse_params params = []
|
56
|
+
params = ['443', '80'] if params.empty?
|
57
|
+
maps = split_params params
|
58
|
+
maps.inject({}) do |h, (from, to)|
|
59
|
+
h.merge parse_host_str(from) => parse_host_str(to)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.group_params params = {}
|
64
|
+
params.inject({}) do |h, (from, to)|
|
65
|
+
h[[from[0], from[2]]] ||= {}
|
66
|
+
h[[from[0], from[2]]].merge! from => to
|
67
|
+
h
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.split_params params = []
|
72
|
+
Hash[*params]
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.parse_host_str str
|
76
|
+
raise ArgumentError if str.empty?
|
77
|
+
if str =~ /^:?(\d+)$/
|
78
|
+
[get_scheme($1), '127.0.0.1', $1.to_i]
|
79
|
+
elsif str =~ /^(\w+):\/\/:(\d+)$/
|
80
|
+
[$1.to_sym, '127.0.0.1', $2.to_i]
|
81
|
+
elsif str =~ /:\/\//
|
82
|
+
uri = URI str
|
83
|
+
[uri.scheme.to_sym, uri.host, uri.port]
|
84
|
+
else
|
85
|
+
parts = str.split(':')
|
86
|
+
raise ArgumentError if parts.size != 2
|
87
|
+
[get_scheme(parts[1]), parts[0], parts[1].to_i]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.get_scheme port
|
92
|
+
port.to_i == 443 ? :https : :http
|
93
|
+
end
|
94
|
+
|
95
|
+
class HttpClient < EventMachine::Connection
|
96
|
+
attr_reader :proxy, :to_host, :to_port
|
97
|
+
|
98
|
+
def initialize(proxy, from_host, from_port, to_host, to_port)
|
99
|
+
@proxy = proxy
|
100
|
+
@connected = EventMachine::DefaultDeferrable.new
|
101
|
+
@from_host, @from_port, @to_host, @to_port = from_host, from_port, to_host, to_port
|
102
|
+
end
|
103
|
+
|
104
|
+
def connection_completed
|
105
|
+
@connected.succeed
|
106
|
+
end
|
107
|
+
|
108
|
+
def receive_data(data)
|
109
|
+
proxy.relay_from_client(data)
|
110
|
+
end
|
111
|
+
|
112
|
+
def send(data)
|
113
|
+
@connected.callback { send_data data }
|
114
|
+
end
|
115
|
+
|
116
|
+
def unbind
|
117
|
+
proxy.unbind_client @from_host, @from_port
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class HttpsClient < HttpClient
|
122
|
+
def post_init
|
123
|
+
start_tls
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class HttpProxy < EventMachine::Connection
|
128
|
+
def initialize(map)
|
129
|
+
map.each do |from, to|
|
130
|
+
make_client from[1], from[2], to[0], to[1], to[2]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def receive_data(data)
|
135
|
+
unless data.nil?
|
136
|
+
client = nil
|
137
|
+
data.sub! /\r\nHost:\s*(.+?)\s*\r\n/i do
|
138
|
+
host, port = $1.split(':') if $1
|
139
|
+
client = client host, port
|
140
|
+
"\r\nHost: #{client.to_host}:#{client.to_port}\r\n"
|
141
|
+
end
|
142
|
+
|
143
|
+
client.send_data data
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def relay_from_client(data)
|
148
|
+
send_data data unless data.nil?
|
149
|
+
end
|
150
|
+
|
151
|
+
def unbind
|
152
|
+
close_all_connections
|
153
|
+
clear_clients
|
154
|
+
end
|
155
|
+
|
156
|
+
def unbind_client host, port
|
157
|
+
close_connection_after_writing
|
158
|
+
clear_client host, port.to_i
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
def make_client from_host, from_port, to_scheme, to_host, to_port
|
163
|
+
from_port, to_port = from_port.to_i, to_port.to_i
|
164
|
+
@clients ||= Hash.new {|h, k| h[k] = {} }
|
165
|
+
@clients[from_host][from_port] = case to_scheme
|
166
|
+
when :http
|
167
|
+
EventMachine.connect to_host, to_port, HttpClient, self, from_host, from_port, to_host, to_port
|
168
|
+
when :https
|
169
|
+
EventMachine.connect to_host, to_port, HttpsClient, self, from_host, from_port, to_host, to_port
|
170
|
+
else
|
171
|
+
raise ClientError.new "Cannot support scheme: #{to_scheme}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def client host, port
|
176
|
+
port = port.to_i
|
177
|
+
search_client_by_host_and_port(host, port) ||
|
178
|
+
search_client_by_host(host) ||
|
179
|
+
raise(ClientError.new "Cannot find a client for #{host}:#{port}")
|
180
|
+
end
|
181
|
+
|
182
|
+
def search_client_by_host_and_port host, port
|
183
|
+
@clients[host][port] ||
|
184
|
+
@clients['localhost'][port] ||
|
185
|
+
@clients['127.0.0.1'][port] ||
|
186
|
+
@clients['0.0.0.0'][port]
|
187
|
+
end
|
188
|
+
|
189
|
+
def search_client_by_host host
|
190
|
+
@clients[host].values.first ||
|
191
|
+
@clients['localhost'].values.first ||
|
192
|
+
@clients['127.0.0.1'].values.first ||
|
193
|
+
@clients['0.0.0.0'].values.first
|
194
|
+
end
|
195
|
+
|
196
|
+
def clear_client host, port
|
197
|
+
@clients[host].delete port.to_i
|
198
|
+
end
|
199
|
+
|
200
|
+
def close_all_connections
|
201
|
+
@clients.each do |_, host|
|
202
|
+
host.each do |_, client|
|
203
|
+
client.close_connection
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def clear_clients
|
209
|
+
@clients.clear
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class HttpsProxy < HttpProxy
|
214
|
+
def post_init
|
215
|
+
start_tls
|
216
|
+
end
|
217
|
+
|
218
|
+
def relay_from_client(data)
|
219
|
+
super
|
220
|
+
@x_forwarded_proto_header_inserted = false
|
221
|
+
end
|
222
|
+
|
223
|
+
def receive_data(data)
|
224
|
+
if !@x_forwarded_proto_header_inserted && data =~ /\r\n\r\n/
|
225
|
+
super data.gsub(/\r\n\r\n/, "\r\nX_FORWARDED_PROTO: https\r\n\r\n")
|
226
|
+
@x_forwarded_proto_header_inserted = true
|
227
|
+
else
|
228
|
+
super
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# to stub
|
4
|
+
def EventMachine.run(&block)
|
5
|
+
block.call
|
6
|
+
end
|
7
|
+
|
8
|
+
describe Tunnels do
|
9
|
+
describe '.run!' do
|
10
|
+
context 'with no args' do
|
11
|
+
it 'works' do
|
12
|
+
mock(EventMachine).start_server('127.0.0.1', 443, Tunnels::HttpsProxy, 80)
|
13
|
+
Tunnels.run!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with args' do
|
18
|
+
it 'works' do
|
19
|
+
mock(EventMachine).start_server('127.0.0.1', 443, Tunnels::HttpsProxy, 80)
|
20
|
+
Tunnels.run!('127.0.0.1:443', '127.0.0.1:80')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/tunnels.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'tunnels/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'multitunnels'
|
7
|
+
s.version = Tunnels::VERSION
|
8
|
+
s.authors = ['jugyo', 'bachue']
|
9
|
+
s.email = ['jugyo.org@gmail.com', 'bachue.shu@gmail.com']
|
10
|
+
s.homepage = "https://github.com/bachue/tunnels"
|
11
|
+
s.summary = %q{https/http --(--)--> https/http}
|
12
|
+
s.description = %q{This tunnels http/https to http/https.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
# specify any dependencies here; for example:
|
20
|
+
# s.add_development_dependency "rspec"
|
21
|
+
s.add_development_dependency 'rspec'
|
22
|
+
s.add_development_dependency 'rr'
|
23
|
+
s.add_runtime_dependency 'eventmachine'
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: multitunnels
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jugyo
|
8
|
+
- bachue
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-06-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rr
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: eventmachine
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: This tunnels http/https to http/https.
|
57
|
+
email:
|
58
|
+
- jugyo.org@gmail.com
|
59
|
+
- bachue.shu@gmail.com
|
60
|
+
executables:
|
61
|
+
- multitunnels
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- ".gitignore"
|
66
|
+
- Gemfile
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/multitunnels
|
70
|
+
- lib/tunnels.rb
|
71
|
+
- lib/tunnels/version.rb
|
72
|
+
- spec/spec_helper.rb
|
73
|
+
- spec/tunnels_spec.rb
|
74
|
+
- tunnels.gemspec
|
75
|
+
homepage: https://github.com/bachue/tunnels
|
76
|
+
licenses: []
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.2.2
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: https/http --(--)--> https/http
|
98
|
+
test_files:
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
- spec/tunnels_spec.rb
|
101
|
+
has_rdoc:
|