multitunnels 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![image](http://i.imgur.com/Ej5dz.png)
|
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:
|