shadowsocks 0.6 → 0.7
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 +4 -4
- data/.gitignore +1 -0
- data/README.md +4 -2
- data/config.json +4 -3
- data/data/gfwlist.txt +3737 -0
- data/lib/shadowsocks.rb +8 -1
- data/lib/shadowsocks/cli.rb +4 -3
- data/lib/shadowsocks/config.rb +4 -2
- data/lib/shadowsocks/crypto.rb +6 -7
- data/lib/shadowsocks/ip_detector.rb +30 -0
- data/lib/shadowsocks/listener.rb +12 -25
- data/lib/shadowsocks/local.rb +35 -21
- data/lib/shadowsocks/parser/base.rb +71 -0
- data/lib/shadowsocks/parser/local.rb +15 -0
- data/lib/shadowsocks/parser/server.rb +15 -0
- data/lib/shadowsocks/server.rb +1 -11
- data/lib/shadowsocks/version.rb +1 -1
- data/shadowsocks.gemspec +2 -2
- data/tasks/chnroutes.rake +61 -0
- metadata +13 -7
data/lib/shadowsocks.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
1
|
require 'eventmachine'
|
2
2
|
|
3
3
|
module Shadowsocks
|
4
|
-
autoload :Crypto,
|
4
|
+
autoload :Crypto, 'shadowsocks/crypto'
|
5
5
|
autoload :Connection, 'shadowsocks/connection'
|
6
6
|
autoload :Server, 'shadowsocks/server'
|
7
7
|
autoload :Local, 'shadowsocks/local'
|
8
8
|
autoload :Table, 'shadowsocks/table'
|
9
9
|
autoload :Tunnel, 'shadowsocks/tunnel'
|
10
10
|
autoload :Listener, 'shadowsocks/listener'
|
11
|
+
autoload :IPDetector, 'shadowsocks/ip_detector'
|
12
|
+
|
13
|
+
module Parser
|
14
|
+
autoload :Base, 'shadowsocks/parser/base'
|
15
|
+
autoload :Local, 'shadowsocks/parser/local'
|
16
|
+
autoload :Server, 'shadowsocks/parser/server'
|
17
|
+
end
|
11
18
|
end
|
data/lib/shadowsocks/cli.rb
CHANGED
@@ -10,8 +10,9 @@ module Shadowsocks
|
|
10
10
|
attr_accessor :side, :args, :config
|
11
11
|
|
12
12
|
def initialize(options)
|
13
|
-
@side
|
14
|
-
@config
|
13
|
+
@side = options[:side]
|
14
|
+
@config = options[:config]
|
15
|
+
@ip_detector = Shadowsocks::IPDetector.new if @config.chnroutes
|
15
16
|
|
16
17
|
@method_options = {
|
17
18
|
method: config.method,
|
@@ -58,7 +59,7 @@ module Shadowsocks
|
|
58
59
|
connection.crypto = Shadowsocks::Crypto.new @method_options
|
59
60
|
connection.pending_connect_timeout = @config.timeout
|
60
61
|
connection.comm_inactivity_timeout = @config.timeout
|
62
|
+
connection.ip_detector = @ip_detector if @config.chnroutes
|
61
63
|
end
|
62
|
-
|
63
64
|
end
|
64
65
|
end
|
data/lib/shadowsocks/config.rb
CHANGED
@@ -2,7 +2,7 @@ require 'json'
|
|
2
2
|
|
3
3
|
module Shadowsocks
|
4
4
|
class Config
|
5
|
-
attr_reader :args, :server, :server_port, :local_port, :password, :timeout, :method, :config_path
|
5
|
+
attr_reader :args, :server, :server_port, :local_port, :password, :timeout, :method, :config_path, :chnroutes
|
6
6
|
|
7
7
|
def initialize(_args)
|
8
8
|
@args = _args || []
|
@@ -23,12 +23,13 @@ module Shadowsocks
|
|
23
23
|
@local_port = json["local_port"].to_i if @local_port.nil?
|
24
24
|
@timeout = json["timeout"].to_i if @timeout.nil?
|
25
25
|
@method = json["method"] if @method.nil?
|
26
|
+
@chnroutes = json["chnroutes"] if @chnroutes.nil?
|
26
27
|
end
|
27
28
|
|
28
29
|
private
|
29
30
|
|
30
31
|
def parse_args
|
31
|
-
opt_parser = OptionParser.new do |opts|
|
32
|
+
opt_parser = OptionParser.new do |opts|
|
32
33
|
opts.banner = "Usage: ss-server [options]"
|
33
34
|
|
34
35
|
opts.separator ""
|
@@ -41,6 +42,7 @@ module Shadowsocks
|
|
41
42
|
opts.on("-l", "--local_port PORT", Integer, "Local client port") { |c| @local_port = c }
|
42
43
|
opts.on("-m", "--method METHOD", "Encryption method") { |c| @method = c }
|
43
44
|
opts.on("-t", "--timeout NUMBER", Integer, "connection timeout") { |c| @timeout = c }
|
45
|
+
opts.on("-d", "--detect", "chnroutes feature") { |c| @chnroutes = c }
|
44
46
|
|
45
47
|
opts.on_tail("-v", "--version", "Show shadowsocks gem version") { puts Shadowsocks::VERSION; exit }
|
46
48
|
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
data/lib/shadowsocks/crypto.rb
CHANGED
@@ -15,13 +15,11 @@ module Shadowsocks
|
|
15
15
|
if method == 'table'
|
16
16
|
@encrypt_table = options[:encrypt_table]
|
17
17
|
@decrypt_table = options[:decrypt_table]
|
18
|
-
|
19
|
-
|
20
|
-
raise "Encrypt method not support"
|
21
|
-
end
|
18
|
+
elsif method_supported.nil?
|
19
|
+
raise "Encrypt method not support"
|
22
20
|
end
|
23
21
|
|
24
|
-
if method != 'table'
|
22
|
+
if method != 'table' and method != 'none'
|
25
23
|
@cipher = get_cipher(1, SecureRandom.hex(32))
|
26
24
|
end
|
27
25
|
end
|
@@ -41,12 +39,13 @@ module Shadowsocks
|
|
41
39
|
when 'rc2-cfb' then [16, 8 ]
|
42
40
|
when 'rc4' then [16, 0 ]
|
43
41
|
when 'seed-cfb' then [16, 16]
|
42
|
+
when 'none' then [0, 0]
|
44
43
|
end
|
45
44
|
end
|
46
45
|
alias_method :get_cipher_len, :method_supported
|
47
46
|
|
48
47
|
def encrypt buf
|
49
|
-
return buf if buf.length == 0
|
48
|
+
return buf if buf.length == 0 or method == 'none'
|
50
49
|
if method == 'table'
|
51
50
|
translate @encrypt_table, buf
|
52
51
|
else
|
@@ -60,7 +59,7 @@ module Shadowsocks
|
|
60
59
|
end
|
61
60
|
|
62
61
|
def decrypt buf
|
63
|
-
return buf if buf.length == 0
|
62
|
+
return buf if buf.length == 0 or method == 'none'
|
64
63
|
if method == 'table'
|
65
64
|
translate @decrypt_table, buf
|
66
65
|
else
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module Shadowsocks
|
5
|
+
class IPDetector
|
6
|
+
GFW_LIST_PATH = './data/gfwlist.txt'
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@internals = {}
|
10
|
+
@nums = []
|
11
|
+
lines = File.readlines(GFW_LIST_PATH)
|
12
|
+
lines.each do |line|
|
13
|
+
num = IPAddr.new(line).to_i
|
14
|
+
@nums << num
|
15
|
+
@internals[num.to_s] = line
|
16
|
+
end
|
17
|
+
@nums.sort!
|
18
|
+
end
|
19
|
+
|
20
|
+
def behind_gfw?(domain)
|
21
|
+
ip = IPSocket::getaddress(domain)
|
22
|
+
|
23
|
+
ip_num = IPAddr.new(ip).to_i
|
24
|
+
|
25
|
+
i = @nums.bsearch { |x| x > ip_num }
|
26
|
+
index = @nums.index(i) - 1
|
27
|
+
IPAddr.new(@internals[@nums[index].to_s]).include? ip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/shadowsocks/listener.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Shadowsocks
|
2
2
|
class Listener < ::Shadowsocks::Connection
|
3
3
|
attr_accessor :stage, :remote_addr, :remote_port, :addr_to_send, :cached_pieces,
|
4
|
-
:header_length, :connector, :config
|
4
|
+
:header_length, :connector, :config, :ip_detector
|
5
5
|
|
6
6
|
def receive_data data
|
7
7
|
data_handler data
|
@@ -25,37 +25,24 @@ module Shadowsocks
|
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
-
def
|
29
|
-
|
30
|
-
self.close_connection
|
31
|
-
end
|
28
|
+
def parse_data parser
|
29
|
+
@addrtype = parser.addr_type
|
32
30
|
|
33
|
-
|
34
|
-
case @addrtype
|
35
|
-
when "\x01"
|
36
|
-
ip_address data
|
37
|
-
when "\x03"
|
38
|
-
domain_address data
|
39
|
-
else
|
31
|
+
if parser.mode == :unsupported
|
40
32
|
warn "unsupported addrtype: " + @addrtype.unpack('c')[0].to_s
|
41
33
|
connection_cleanup
|
42
34
|
end
|
43
|
-
end
|
44
35
|
|
45
|
-
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@
|
36
|
+
@addr_len = parser.addr_len
|
37
|
+
@addr_to_send = parser.addr_to_send
|
38
|
+
@remote_addr = parser.remote_addr
|
39
|
+
@remote_port = parser.remote_port
|
40
|
+
@header_length = parser.header_length
|
49
41
|
end
|
50
42
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
@header_length = 7
|
55
|
-
end
|
56
|
-
|
57
|
-
def inet_ntoa n
|
58
|
-
n.unpack("C*").join "."
|
43
|
+
def connection_cleanup
|
44
|
+
connector.close_connection if connector
|
45
|
+
self.close_connection
|
59
46
|
end
|
60
47
|
end
|
61
48
|
end
|
data/lib/shadowsocks/local.rb
CHANGED
@@ -18,7 +18,24 @@ module Shadowsocks
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
class DirectConnector < ::Shadowsocks::Tunnel
|
22
|
+
def post_init
|
23
|
+
p "connecting #{server.remote_addr[3..-1]} directly"
|
24
|
+
server.cached_pieces.each { |piece| send_data piece }
|
25
|
+
server.cached_pieces = []
|
26
|
+
|
27
|
+
server.stage = 5
|
28
|
+
end
|
29
|
+
|
30
|
+
def receive_data data
|
31
|
+
server.send_data data
|
32
|
+
outbound_scheduler
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
21
36
|
class LocalListener < ::Shadowsocks::Listener
|
37
|
+
attr_accessor :behind_gfw
|
38
|
+
|
22
39
|
private
|
23
40
|
|
24
41
|
def data_handler data
|
@@ -31,7 +48,13 @@ module Shadowsocks
|
|
31
48
|
when 4
|
32
49
|
cached_pieces.push data
|
33
50
|
when 5
|
34
|
-
|
51
|
+
to_send = \
|
52
|
+
if behind_gfw
|
53
|
+
data
|
54
|
+
else
|
55
|
+
encrypt(data)
|
56
|
+
end
|
57
|
+
connector.send_data(to_send) and return
|
35
58
|
end
|
36
59
|
end
|
37
60
|
|
@@ -42,14 +65,21 @@ module Shadowsocks
|
|
42
65
|
connection_cleanup and return
|
43
66
|
end
|
44
67
|
|
45
|
-
|
46
|
-
|
47
|
-
resolve_addrtype data
|
68
|
+
parse_data Shadowsocks::Parser::Local.new(data)
|
48
69
|
|
49
70
|
send_data "\x05\x00\x00\x01\x00\x00\x00\x00" + [config.server_port].pack('s>')
|
50
71
|
|
51
72
|
@stage = 4
|
52
|
-
|
73
|
+
|
74
|
+
if config.chnroutes
|
75
|
+
@behind_gfw = @ip_detector.behind_gfw?(@remote_addr[3..-1])
|
76
|
+
end
|
77
|
+
|
78
|
+
if config.chnroutes == true and behind_gfw
|
79
|
+
@connector = EventMachine.connect @remote_addr[3..-1], @remote_port, DirectConnector, self, crypto
|
80
|
+
else
|
81
|
+
@connector = EventMachine.connect config.server, config.server_port, ServerConnector, self, crypto
|
82
|
+
end
|
53
83
|
|
54
84
|
if data.size > header_length
|
55
85
|
cached_pieces.push data[header_length, data.size]
|
@@ -59,22 +89,6 @@ module Shadowsocks
|
|
59
89
|
connection_cleanup
|
60
90
|
end
|
61
91
|
end
|
62
|
-
|
63
|
-
def resolve_addrtype data
|
64
|
-
@addrtype = data[3]
|
65
|
-
super
|
66
|
-
end
|
67
|
-
|
68
|
-
def domain_address data
|
69
|
-
@addr_len = data[4].unpack('c')[0]
|
70
|
-
@addr_to_send += data[4..5 + @addr_len + 2]
|
71
|
-
super
|
72
|
-
end
|
73
|
-
|
74
|
-
def ip_address data
|
75
|
-
@addr_to_send += data[4..9]
|
76
|
-
super
|
77
|
-
end
|
78
92
|
end
|
79
93
|
end
|
80
94
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Shadowsocks
|
2
|
+
module Parser
|
3
|
+
class Base
|
4
|
+
attr_accessor :data, :mode
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@data = data
|
8
|
+
|
9
|
+
@mode = \
|
10
|
+
case addr_type
|
11
|
+
when "\x01"
|
12
|
+
:ip
|
13
|
+
when "\x03"
|
14
|
+
:domain
|
15
|
+
else
|
16
|
+
:unsupported
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def addr_type
|
21
|
+
raise 'Called abstract method: addr_type'
|
22
|
+
end
|
23
|
+
|
24
|
+
def addr_len
|
25
|
+
raise 'Called abstract method: addr_len'
|
26
|
+
end
|
27
|
+
|
28
|
+
def addr_to_send
|
29
|
+
case mode
|
30
|
+
when :domain
|
31
|
+
data[3..5 + addr_len + 2]
|
32
|
+
when :ip
|
33
|
+
data[3..9]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def remote_addr
|
38
|
+
case mode
|
39
|
+
when :domain
|
40
|
+
data[2, addr_len + 3]
|
41
|
+
when :ip
|
42
|
+
inet_ntoa data[1..4]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def remote_port
|
47
|
+
case mode
|
48
|
+
when :domain
|
49
|
+
data[5 + addr_len, 2].unpack('s>')[0]
|
50
|
+
when :ip
|
51
|
+
data[5, 2].unpack('s>')[0]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def header_length
|
56
|
+
case mode
|
57
|
+
when :domain
|
58
|
+
4 + addr_len
|
59
|
+
when :ip
|
60
|
+
7
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def inet_ntoa n
|
67
|
+
n.unpack("C*").join "."
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/shadowsocks/server.rb
CHANGED
@@ -33,7 +33,7 @@ module Shadowsocks
|
|
33
33
|
|
34
34
|
def fireup_tunnel data
|
35
35
|
begin
|
36
|
-
|
36
|
+
parse_data Shadowsocks::Parser::Server.new(data)
|
37
37
|
|
38
38
|
@stage = 4
|
39
39
|
|
@@ -47,16 +47,6 @@ module Shadowsocks
|
|
47
47
|
connection_cleanup
|
48
48
|
end
|
49
49
|
end
|
50
|
-
|
51
|
-
def resolve_addrtype data
|
52
|
-
@addrtype = data[0]
|
53
|
-
super
|
54
|
-
end
|
55
|
-
|
56
|
-
def domain_address data
|
57
|
-
@addr_len = data[1].unpack('c')[0]
|
58
|
-
super
|
59
|
-
end
|
60
50
|
end
|
61
51
|
end
|
62
52
|
end
|
data/lib/shadowsocks/version.rb
CHANGED
data/shadowsocks.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_dependency "json", "~> 1.8.0"
|
20
20
|
s.add_dependency "ffi", "~> 1.9.0"
|
21
21
|
|
22
|
-
s.add_development_dependency "rake-compiler", "~> 0.9.
|
23
|
-
s.add_development_dependency "mocha", "~> 0.
|
22
|
+
s.add_development_dependency "rake-compiler", "~> 0.9.2"
|
23
|
+
s.add_development_dependency "mocha", "~> 1.0.0"
|
24
24
|
s.add_development_dependency "rake"
|
25
25
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'ipaddress'
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
task :fetch_chnroutes do
|
6
|
+
url = 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest'
|
7
|
+
unless File.exists?('./delegated-apnic-latest')
|
8
|
+
`wget -v #{url}`
|
9
|
+
end
|
10
|
+
|
11
|
+
lines = File.readlines './delegated-apnic-latest'
|
12
|
+
file = File.open("./data/gfwlist.txt", "w")
|
13
|
+
lines.each do |line|
|
14
|
+
unit_items = line.split('|')
|
15
|
+
if unit_items.length >= 5 and unit_items[2] == 'ipv4' and unit_items[1] == "CN"
|
16
|
+
starting_ip = unit_items[3]
|
17
|
+
num_ip = unit_items[4].to_i
|
18
|
+
|
19
|
+
file.puts "#{starting_ip}/#{32 - (Math.log(num_ip, 2).ceil).to_i}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
task :test_ip do
|
25
|
+
ip = '162.243.140.72'
|
26
|
+
lines = File.readlines("./data/gfwlist.txt")
|
27
|
+
internals = []
|
28
|
+
lines.each do |line|
|
29
|
+
internals << IPAddr.new(line)
|
30
|
+
end
|
31
|
+
|
32
|
+
Benchmark.bm do |x|
|
33
|
+
x.report { 50.times { internals.any? { |i| i.include?(ip) } } }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
task :test_new_ip do
|
38
|
+
ip = '122.97.254.169'
|
39
|
+
lines = File.readlines("./data/gfwlist.txt")
|
40
|
+
internals = {}
|
41
|
+
nums = []
|
42
|
+
lines.each do |line|
|
43
|
+
num = IPAddr.new(line).to_i
|
44
|
+
nums << num
|
45
|
+
internals[num.to_s] = line
|
46
|
+
end
|
47
|
+
nums.sort!
|
48
|
+
|
49
|
+
Benchmark.bm do |x|
|
50
|
+
x.report do
|
51
|
+
1000.times do
|
52
|
+
ip_num = IPAddr.new(ip).to_i
|
53
|
+
|
54
|
+
i = nums.bsearch { |x| x > ip_num }
|
55
|
+
index = nums.index(i) - 1
|
56
|
+
IPAddr.new(internals[nums[index].to_s]).include? ip
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|