shadowsocks 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 75e7362c5a7e492c1a72ce66fe087442c2196867
4
+ data.tar.gz: 0f9abb4370f9185c79ec822f62cce07b17bb43c8
5
+ SHA512:
6
+ metadata.gz: 0fb909540f9566f272e19db7eb289abd03106d94c6cf4862dd4af63aab55a047730c087024b74982959868acf4a41bc0cf98fbba89612e9f54624288da0c8dde
7
+ data.tar.gz: 5083e49dd9fed51a947b94ad0e4868f55237a6fb4d7d3a9aa2b3497e5ec69a4999fb4442fbb4ef67a0014cdcb5ac50c7e29a499f6f11f2f50bbe6795e85fcb97
@@ -0,0 +1,7 @@
1
+ *.so
2
+ *.o
3
+ .DS_Store
4
+ *.gem
5
+ tmp
6
+ *.bundle
7
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,20 @@
1
+ shadowsocks-ruby
2
+ ================
3
+
4
+ a Ruby EventMachine port of shadowsocks-nodejs (not stable yet). use shadowsocks-nodejs instead
5
+
6
+ install
7
+
8
+ ``` ruby
9
+ gem install eventmachine
10
+ gem install ffi
11
+ rake
12
+ ```
13
+
14
+ server side
15
+
16
+ ``` ruby
17
+ nohup ruby server.rb > log &
18
+ ```
19
+
20
+ then enjoy!
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+ require 'rake'
3
+
4
+ # Load custom tasks
5
+ Dir['tasks/*.rake'].sort.each { |f| load f }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'shadowsocks'
7
+ require 'shadowsocks/cli'
8
+ require 'shadowsocks/config'
9
+
10
+ begin
11
+ config = Shadowsocks::Config.new ARGV
12
+ cli = Shadowsocks::Cli.new({side: :local, config: config})
13
+ cli.run
14
+ rescue => e
15
+ raise e if $DEBUG
16
+ STDERR.puts e.message
17
+ STDERR.puts e.backtrace.join("\n")
18
+ exit 1
19
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'shadowsocks'
7
+ require 'shadowsocks/cli'
8
+ require 'shadowsocks/config'
9
+
10
+ begin
11
+ config = Shadowsocks::Config.new ARGV
12
+ cli = Shadowsocks::Cli.new({side: :server, config: config})
13
+ cli.run
14
+ rescue => e
15
+ raise e if $DEBUG
16
+ STDERR.puts e.message
17
+ STDERR.puts e.backtrace.join("\n")
18
+ exit 1
19
+ end
@@ -0,0 +1,7 @@
1
+ {
2
+ "server":"127.0.0.1",
3
+ "server_port":8388,
4
+ "local_port":1080,
5
+ "password":"foobar!",
6
+ "timeout":600
7
+ }
@@ -0,0 +1,11 @@
1
+ char* encrypt(int table[], char buf[], int len)
2
+ {
3
+ int j = 0;
4
+ char *end = buf + len;
5
+ while (buf < end) {
6
+ *buf = (char)table[(unsigned char)*buf];
7
+ buf++;
8
+ }
9
+
10
+ return buf - len;
11
+ }
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile('encrypt')
@@ -0,0 +1,9 @@
1
+ require 'eventmachine'
2
+
3
+ module Shadowsocks
4
+ autoload :Server, 'shadowsocks/server'
5
+ autoload :Local, 'shadowsocks/local'
6
+ autoload :Table, 'shadowsocks/table'
7
+ autoload :Tunnel, 'shadowsocks/tunnel'
8
+ autoload :Listener, 'shadowsocks/listener'
9
+ end
@@ -0,0 +1,51 @@
1
+ require 'optparse'
2
+ require 'pp'
3
+
4
+ require File.expand_path('../version', __FILE__)
5
+
6
+ module Shadowsocks
7
+ class Cli
8
+ include ::Shadowsocks::Table
9
+
10
+ attr_accessor :side, :args, :config, :table
11
+
12
+ def initialize(options)
13
+ @side = options[:side]
14
+ @config = options[:config]
15
+ @table = get_table(config.password)
16
+ end
17
+
18
+ def run
19
+ case side
20
+ when :local
21
+ EventMachine::run {
22
+ Signal.trap("INT") { EventMachine.stop }
23
+ Signal.trap("TERM") { EventMachine.stop }
24
+
25
+ puts "*** Local side is up, port:#{config.local_port}"
26
+ puts "*** Hit Ctrl+c to stop"
27
+ EventMachine::start_server "0.0.0.0", config.local_port, Shadowsocks::Local::LocalListener, &method(:initialize_connection)
28
+ }
29
+ when :server
30
+ EventMachine::run {
31
+ Signal.trap("INT") { EventMachine.stop }
32
+ Signal.trap("TERM") { EventMachine.stop }
33
+
34
+ puts "*** Server side is up, port:#{config.server_port}"
35
+ puts "*** Hit Ctrl+c to stop"
36
+
37
+ EventMachine::start_server "0.0.0.0", config.server_port, Shadowsocks::Server::ServerListener, &method(:initialize_connection)
38
+ }
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def initialize_connection connection
45
+ connection.config = @config
46
+ connection.table = @table
47
+ connection.pending_connect_timeout = @config.timeout
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+
3
+ module Shadowsocks
4
+ class Config
5
+ attr_reader :args, :server, :server_port, :local_port, :password, :timeout, :config_path
6
+
7
+ def initialize(_args)
8
+ @args = _args || []
9
+
10
+ parse_args
11
+ read_config
12
+ end
13
+
14
+ def read_config
15
+ @config_path = File.expand_path('../..', File.dirname(__FILE__)) + '/config.json' unless @config_file
16
+ cfg_file = File.open @config_path
17
+ json = JSON.parse cfg_file.read
18
+ cfg_file.close
19
+
20
+ @server = json["server"] if @server.nil?
21
+ @password = json["password"] if @password.nil?
22
+ @server_port = json["server_port"].to_i if @server_port.nil?
23
+ @local_port = json["local_port"].to_i if @local_port.nil?
24
+ @timeout = json["timeout"].to_i if @timeout.nil?
25
+ end
26
+
27
+ private
28
+
29
+ def parse_args
30
+ opt_parser = OptionParser.new do |opts|
31
+ opts.banner = "Usage: ss-server [options]"
32
+
33
+ opts.separator ""
34
+ opts.separator "Specific options:"
35
+
36
+ opts.on("-s", "--server ADDR", "Remote server, IP address or domain") { |c| @server = c }
37
+ opts.on("-k", "--password PASSWORD", "Password, should be same in client and server sides") { |c| @password = c }
38
+ opts.on("-c", "--config PATH", "config.json path") { |c| @config_path = c }
39
+ opts.on("-p", "--port PORT", Integer, "Remote server port") { |c| @server_port = c }
40
+ opts.on("-l", "--local_port PORT", Integer,"Local client port") { |c| @local_port = c }
41
+ opts.on("-t", "--timeout NUMBER", Integer, "connection timeout") { |c| @timeout = c }
42
+
43
+ opts.on_tail("-v", "--version", "Show shadowsocks gem version") { puts Shadowsocks::VERSION; exit }
44
+ opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
45
+ end
46
+
47
+ opt_parser.parse!(args)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ module Shadowsocks
2
+ class Listener < EventMachine::Connection
3
+ include ::Shadowsocks::Table
4
+
5
+ attr_accessor :stage, :remote_addr, :remote_port, :addr_to_send, :cached_pieces,
6
+ :header_length, :connector, :config, :table
7
+
8
+ def receive_data data
9
+ data_handler data
10
+ end
11
+
12
+ def post_init
13
+ @stage = 0
14
+ @cached_pieces = []
15
+ puts "A client has connected..."
16
+ end
17
+
18
+ def unbind
19
+ puts "A client has left..."
20
+ connection_cleanup
21
+ end
22
+
23
+ private
24
+
25
+ def connection_cleanup
26
+ connector.close_connection if connector
27
+ self.close_connection
28
+ end
29
+
30
+ def resolve_addrtype data
31
+ case @addrtype
32
+ when "\x01"
33
+ ip_address data
34
+ when "\x03"
35
+ domain_address data
36
+ else
37
+ warn "unsupported addrtype: " + @addrtype.unpack('c')[0].to_s
38
+ connection_cleanup
39
+ end
40
+ end
41
+
42
+ def domain_address data
43
+ @remote_addr = data[2, @addr_len]
44
+ @remote_port = data[2 + @addr_len, 2].unpack('s>')[0]
45
+ @header_length = 2 + @addr_len + 2
46
+ end
47
+
48
+ def ip_address data
49
+ @remote_addr = inet_ntoa data[1..4]
50
+ @remote_port = data[5, 2].unpack('s>')[0]
51
+ @header_length = 7
52
+ end
53
+
54
+ def inet_ntoa n
55
+ n.unpack("C*").join "."
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,79 @@
1
+ module Shadowsocks
2
+ module Local
3
+ class ServerConnector < ::Shadowsocks::Tunnel
4
+ def post_init
5
+ p "connecting #{server.remote_addr[3..-1]} via #{server.config.server}"
6
+ addr_to_send = server.addr_to_send.clone
7
+
8
+ send_data encrypt(table[:encrypt_table], addr_to_send)
9
+ server.cached_pieces.each { |piece| send_data encrypt(table[:encrypt_table], piece) }
10
+ server.cached_pieces = []
11
+
12
+ server.stage = 5
13
+ end
14
+
15
+ def receive_data data
16
+ server.send_data encrypt(table[:decrypt_table], data)
17
+ end
18
+ end
19
+
20
+ class LocalListener < ::Shadowsocks::Listener
21
+ private
22
+
23
+ def data_handler data
24
+ case stage
25
+ when 0
26
+ send_data "\x05\x00"
27
+ @stage = 1
28
+ when 1
29
+ fireup_tunnel data
30
+ when 4
31
+ cached_pieces.push data
32
+ when 5
33
+ connector.send_data(encrypt(table[:encrypt_table], data)) and return
34
+ end
35
+ end
36
+
37
+ def fireup_tunnel(data)
38
+ begin
39
+ unless data[1] == "\x01"
40
+ send_data "\x05\x07\x00\x01"
41
+ connection_cleanup and return
42
+ end
43
+
44
+ @addr_to_send = data[3]
45
+
46
+ resolve_addrtype data
47
+
48
+ send_data "\x05\x00\x00\x01\x00\x00\x00\x00" + [config.server_port].pack('s>')
49
+
50
+ @stage = 4
51
+ @connector = EventMachine.connect config.server, config.server_port, ServerConnector, self, @table
52
+
53
+ if data.size > header_length
54
+ cached_pieces.push data[header_length, data.size]
55
+ end
56
+ rescue Exception => e
57
+ warn e
58
+ connection_cleanup
59
+ end
60
+ end
61
+
62
+ def resolve_addrtype data
63
+ @addrtype = data[3]
64
+ super
65
+ end
66
+
67
+ def domain_address data
68
+ @addr_len = data[4].unpack('c')[0]
69
+ @addr_to_send += data[4..5 + @addr_len + 2]
70
+ super
71
+ end
72
+
73
+ def ip_address data
74
+ @addr_to_send += data[4..9]
75
+ super
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,62 @@
1
+ module Shadowsocks
2
+ module Server
3
+ class RequestConnector < ::Shadowsocks::Tunnel
4
+ def post_init
5
+ p "connecting #{server.remote_addr} via #{server.config.server}"
6
+
7
+ server.cached_pieces.each { |piece| send_data piece }
8
+ server.cached_pieces = nil
9
+
10
+ server.stage = 5
11
+ end
12
+
13
+ def receive_data data
14
+ server.send_data encrypt(table[:encrypt_table], data)
15
+ end
16
+ end
17
+
18
+ class ServerListener < ::Shadowsocks::Listener
19
+ private
20
+
21
+ def data_handler data
22
+ data = encrypt table[:decrypt_table], data
23
+ case stage
24
+ when 0
25
+ fireup_tunnel data
26
+ when 4
27
+ cached_pieces.push data
28
+ when 5
29
+ connector.send_data(data) and return
30
+ end
31
+ end
32
+
33
+ def fireup_tunnel data
34
+ begin
35
+ resolve_addrtype data
36
+
37
+ @stage = 4
38
+
39
+ if data.size > header_length
40
+ cached_pieces.push data[header_length, data.size]
41
+ end
42
+
43
+ @connector = EventMachine.connect @remote_addr, @remote_port, RequestConnector, self, table
44
+ rescue Exception => e
45
+ warn e
46
+ connection_cleanup
47
+ end
48
+ end
49
+
50
+ def resolve_addrtype data
51
+ @addrtype = data[0]
52
+ super
53
+ end
54
+
55
+ def domain_address data
56
+ @addr_len = data[1].unpack('c')[0]
57
+ super
58
+ end
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,84 @@
1
+ require "digest"
2
+ require 'ffi'
3
+
4
+ module Ext
5
+ def self.binary_path
6
+ path = ''
7
+ %w( so bundle dll ).each do |ext|
8
+ path = File.expand_path('..', File.dirname(__FILE__)) + "/encrypt.#{ext}"
9
+ break if File.exists? path
10
+ end
11
+
12
+ path
13
+ end
14
+
15
+ extend FFI::Library
16
+ ffi_lib binary_path
17
+ attach_function "encrypt", [:pointer, :pointer, :int], :pointer
18
+ end
19
+
20
+ module Shadowsocks
21
+ module Table
22
+ include Ext
23
+
24
+ def get_table key
25
+ table = Array.new(256, 0)
26
+ decrypt_table = Array.new(256, 0)
27
+
28
+ a = Digest::MD5.digest(key).unpack('Q<')[0]
29
+ i = 0
30
+
31
+ while i < 256
32
+ table[i] = i
33
+ i += 1
34
+ end
35
+ i = 1
36
+
37
+ while i < 1024
38
+ table = merge_sort(table, lambda { |x, y|
39
+ a % (x + i) - a % (y + i)
40
+ })
41
+ i += 1
42
+ end
43
+ i = 0
44
+ while i < 256
45
+ decrypt_table[table[i]] = i
46
+ i += 1
47
+ end
48
+ { encrypt_table: table, decrypt_table: decrypt_table }
49
+ end
50
+
51
+ def encrypt (table, buf)
52
+ table_ptr = FFI::MemoryPointer.new(:int, table.length)
53
+ table_ptr.put_array_of_int32(0, table)
54
+
55
+ buf_ptr = FFI::MemoryPointer.new(:string, buf.length)
56
+ buf_ptr.put_bytes(0, buf)
57
+
58
+ r = Ext.encrypt(table_ptr, buf_ptr, buf.length).get_bytes(0, buf.length)
59
+ table_ptr.free
60
+ buf_ptr.free
61
+ r
62
+ end
63
+
64
+ def merge (left, right, comparison)
65
+ result = []
66
+ while (left.length > 0) and (right.length > 0)
67
+ if comparison.call(left[0], right[0]) <= 0
68
+ result.push left.shift()
69
+ else
70
+ result.push right.shift()
71
+ end
72
+ end
73
+ result.push left.shift() while left.length > 0
74
+ result.push right.shift() while right.length > 0
75
+ result
76
+ end
77
+
78
+ def merge_sort (array, comparison)
79
+ return array if array.size < 2
80
+ middle = (array.size / 2).ceil
81
+ merge(merge_sort(array.slice(0, middle), comparison), merge_sort(array.slice(middle .. array.size), comparison), comparison)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,19 @@
1
+ module Shadowsocks
2
+ class Tunnel < EventMachine::Connection
3
+ attr_accessor :server, :table
4
+
5
+ def initialize server, table
6
+ @server = server
7
+ @table = table
8
+ super
9
+ end
10
+
11
+ def unbind
12
+ server.close_connection_after_writing
13
+ end
14
+
15
+ def encrypt table, data
16
+ server.encrypt table, data
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Shadowsocks
2
+ VERSION = '0.1'
3
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path('../lib/shadowsocks/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'shadowsocks'
5
+ s.version = Shadowsocks::VERSION
6
+ s.date = '2013-09-26'
7
+ s.summary = "ruby version of shadowsocks"
8
+ s.description = "Fuck GFW"
9
+ s.authors = ["Sen"]
10
+ s.email = 'sen9ob@gmail.com'
11
+ s.files = `git ls-files`.split("\n")
12
+ s.test_files = `git ls-files -- test/{functional,unit}/*`.split("\n")
13
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
14
+ s.homepage = 'http://rubygems.org/gems/shadowsocks'
15
+ s.license = 'MIT'
16
+ s.extensions = %w[ext/encrypt/extconf.rb]
17
+
18
+ s.add_dependency "eventmachine", "~> 1.0.3"
19
+ s.add_dependency "json", "~> 1.8.0"
20
+ s.add_dependency "ffi", "~> 1.9.0"
21
+
22
+ s.add_development_dependency "rake-compiler", "~> 0.9.1"
23
+ s.add_development_dependency "mocha", "~> 0.14.0"
24
+ s.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,9 @@
1
+ require 'rake/extensiontask'
2
+
3
+ def gemspec
4
+ @clean_gemspec ||= eval(File.read(File.expand_path('../../shadowsocks.gemspec', __FILE__)))
5
+ end
6
+
7
+ Rake::ExtensionTask.new('encrypt', gemspec) do |ext|
8
+ ext.ext_dir = 'ext/encrypt'
9
+ end
@@ -0,0 +1,7 @@
1
+ task :gem do
2
+ `gem build ./shadowsocks.gemspec`
3
+ end
4
+
5
+ task :install do
6
+ `gem install ./shadowsocks-#{Shadowsocks::VERSION}.gem`
7
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shadowsocks
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Sen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eventmachine
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 1.8.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: ffi
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.9.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.9.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compiler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: mocha
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 0.14.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 0.14.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Fuck GFW
98
+ email: sen9ob@gmail.com
99
+ executables:
100
+ - ss-local
101
+ - ss-server
102
+ extensions:
103
+ - ext/encrypt/extconf.rb
104
+ extra_rdoc_files: []
105
+ files:
106
+ - .gitignore
107
+ - Gemfile
108
+ - Gemfile.lock
109
+ - README.md
110
+ - Rakefile
111
+ - bin/ss-local
112
+ - bin/ss-server
113
+ - config.json
114
+ - ext/encrypt/encrypt.c
115
+ - ext/encrypt/extconf.rb
116
+ - lib/shadowsocks.rb
117
+ - lib/shadowsocks/cli.rb
118
+ - lib/shadowsocks/config.rb
119
+ - lib/shadowsocks/listener.rb
120
+ - lib/shadowsocks/local.rb
121
+ - lib/shadowsocks/server.rb
122
+ - lib/shadowsocks/table.rb
123
+ - lib/shadowsocks/tunnel.rb
124
+ - lib/shadowsocks/version.rb
125
+ - shadowsocks.gemspec
126
+ - tasks/compile.rake
127
+ - tasks/dev.rake
128
+ homepage: http://rubygems.org/gems/shadowsocks
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.0.3
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: ruby version of shadowsocks
152
+ test_files: []