shadowsocks 0.1

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.
@@ -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: []