shadowsocks 0.6 → 0.7

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