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.
data/lib/shadowsocks.rb CHANGED
@@ -1,11 +1,18 @@
1
1
  require 'eventmachine'
2
2
 
3
3
  module Shadowsocks
4
- autoload :Crypto, 'shadowsocks/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
@@ -10,8 +10,9 @@ module Shadowsocks
10
10
  attr_accessor :side, :args, :config
11
11
 
12
12
  def initialize(options)
13
- @side = options[:side]
14
- @config = options[: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
@@ -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 }
@@ -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
- else
19
- if method_supported.nil?
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
@@ -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 connection_cleanup
29
- connector.close_connection if connector
30
- self.close_connection
31
- end
28
+ def parse_data parser
29
+ @addrtype = parser.addr_type
32
30
 
33
- def resolve_addrtype data
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
- def domain_address data
46
- @remote_addr = data[2, @addr_len]
47
- @remote_port = data[2 + @addr_len, 2].unpack('s>')[0]
48
- @header_length = 2 + @addr_len + 2
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 ip_address data
52
- @remote_addr = inet_ntoa data[1..4]
53
- @remote_port = data[5, 2].unpack('s>')[0]
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
@@ -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
- connector.send_data(encrypt(data)) and return
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
- @addr_to_send = data[3]
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
- @connector = EventMachine.connect config.server, config.server_port, ServerConnector, self, crypto
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
@@ -0,0 +1,15 @@
1
+ module Shadowsocks
2
+ module Parser
3
+ class Local < Base
4
+ def addr_type
5
+ data[3]
6
+ end
7
+
8
+ def addr_len
9
+ if mode == :domain
10
+ data[4].unpack('c')[0]
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Shadowsocks
2
+ module Parser
3
+ class Server < Base
4
+ def addr_type
5
+ data[0]
6
+ end
7
+
8
+ def addr_len
9
+ if mode == :domain
10
+ data[1].unpack('c')[0]
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -33,7 +33,7 @@ module Shadowsocks
33
33
 
34
34
  def fireup_tunnel data
35
35
  begin
36
- resolve_addrtype data
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
@@ -1,3 +1,3 @@
1
1
  module Shadowsocks
2
- VERSION = '0.6'
2
+ VERSION = '0.7'
3
3
  end
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.1"
23
- s.add_development_dependency "mocha", "~> 0.14.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