Class: ShadowsocksRuby::App
- Inherits:
-
Object
- Object
- ShadowsocksRuby::App
- Includes:
- Singleton
- Defined in:
- lib/shadowsocks_ruby/app.rb
Overview
App is a singleton object which provide either Shadowsocks Client functionality or Shadowsocks Server functionality. One App startup one EventMachine event loop on one Native Thread / CPU core.
Because Ruby MRI has a GIL, it is unable to utilize execution parallelism on multi-core CPU. However, one can use a shared socket manager to spin off a few Apps that can be executed parallelly and let the Kernel to do the load balance things.
A few things noticeable when using a shared socket manager:
-
At present, only Einhorn socket manager are supported. systemd shared socket manager support is in the plan.
-
At present, using socket manager could cause TLS1.2 obsfucation protocol’s replay attact detection malfunctions, because every process have it’s own copy of LRUCache.
-
Shared socket manager does not work on Windows
Constant Summary collapse
- MAX_FAST_SHUTDOWN_SECONDS =
10- @@options =
{}
Instance Attribute Summary collapse
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Class Method Summary collapse
Instance Method Summary collapse
-
#fast_shutdown(signal) ⇒ Object
TODO: where does EventMachine.connection_count come from?.
- #get_cipher_protocol ⇒ Object
- #get_obfs_protocol ⇒ Object
- #get_packet_protocol ⇒ Object
-
#graceful_shutdown(signal) ⇒ Object
TODO: next_tick can’t be called from trap context.
-
#graceful_shutdown_check ⇒ Object
TODO: next_tick can’t be called from trap context.
-
#initialize ⇒ App
constructor
A new instance of App.
- #run! ⇒ Object
- #start_client ⇒ Object
- #start_em(host, port, klass_server, server_args) ⇒ Object
- #start_server ⇒ Object
- #trap_signals ⇒ Object
Constructor Details
#initialize ⇒ App
Returns a new instance of App.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/shadowsocks_ruby/app.rb', line 41 def initialize = @logger = Logger.new(STDOUT).tap do |log| if [:__server] log.progname = "ssserver-ruby" elsif [:__client] log.progname = "sslocal-ruby" end if [:verbose] log.level = Logger::DEBUG elsif [:quiet] log.level = Logger::WARN else log.level = Logger::INFO end end end |
Instance Attribute Details
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
33 34 35 |
# File 'lib/shadowsocks_ruby/app.rb', line 33 def logger @logger end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
32 33 34 |
# File 'lib/shadowsocks_ruby/app.rb', line 32 def end |
Class Method Details
.options=(options) ⇒ Object
37 38 39 |
# File 'lib/shadowsocks_ruby/app.rb', line 37 def self.= = end |
Instance Method Details
#fast_shutdown(signal) ⇒ Object
TODO: where does EventMachine.connection_count come from?
111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/shadowsocks_ruby/app.rb', line 111 def fast_shutdown(signal) EventMachine.stop_server(@server) if @server Thread.new{ logger.info "Received #{signal} signal. No longer accepting new connections." } Thread.new{ logger.info "Maximum time to wait for connections is #{MAX_FAST_SHUTDOWN_SECONDS} seconds." } Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." } @server = nil EventMachine.stop_event_loop #EventMachine.stop_event_loop if EventMachine.connection_count == 0 #Thread.new do # sleep MAX_FAST_SHUTDOWN_SECONDS # $kernel.exit! #end end |
#get_cipher_protocol ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/shadowsocks_ruby/app.rb', line 198 def get_cipher_protocol if [:cipher_name] == nil || [:cipher_name] == "none" return nil end case [:cipher_name] when "table" ["no_iv_cipher", {}] else case [:packet_name] when "origin" ["iv_cipher", {}] when "verify_sha1" ["verify_sha1", {}] when "verify_sha1_strict" ["verify_sha1", {:compatible => false}] end end end |
#get_obfs_protocol ⇒ Object
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/shadowsocks_ruby/app.rb', line 217 def get_obfs_protocol if [:obfs_name] == nil return nil end case [:obfs_name] when "http_simple" ["http_simple", {:host => [:server], :port => [:port], :obfs_param => [:obfs_param]}] when "http_simple_strict" ["http_simple", {:host => [:server], :port => [:port], :obfs_param => [:obfs_param], :compatible => false}] when "tls_ticket" ["tls_ticket", {:host => [:server], :obfs_param => [:obfs_param]}] when "tls_ticket_strict" ["tls_ticket", {:host => [:server], :obfs_param => [:obfs_param], :compatible => false}] else raise AppError, "no such protocol: #{options[:obfs_name]}" end end |
#get_packet_protocol ⇒ Object
189 190 191 192 193 194 195 196 |
# File 'lib/shadowsocks_ruby/app.rb', line 189 def get_packet_protocol case [:packet_name] when "origin", "verify_sha1", "verify_sha1_strict" ["shadowsocks", {}] else raise AppError, "no such protocol: #{options[:packet_name]}" end end |
#graceful_shutdown(signal) ⇒ Object
TODO: next_tick can’t be called from trap context
88 89 90 91 92 93 94 |
# File 'lib/shadowsocks_ruby/app.rb', line 88 def graceful_shutdown(signal) EventMachine.stop_server(@server) if @server Thread.new{ logger.info "Received #{signal} signal. No longer accepting new connections." } Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." } @server = nil graceful_shutdown_check end |
#graceful_shutdown_check ⇒ Object
TODO: next_tick can’t be called from trap context
97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/shadowsocks_ruby/app.rb', line 97 def graceful_shutdown_check EventMachine.next_tick do count = EventMachine.connection_count if count == 0 EventMachine.stop_event_loop else @wait_count ||= count Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." if @wait_count != count } EventMachine.next_tick self.method(:graceful_shutdown_check) end end end |
#run! ⇒ Object
63 64 65 66 67 68 69 70 71 |
# File 'lib/shadowsocks_ruby/app.rb', line 63 def run! if [:__server] start_server elsif [:__client] start_client end #rescue Exception => e # logger.fatal { e.message + "\n" + e.backtrace.join("\n")} end |
#start_client ⇒ Object
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/shadowsocks_ruby/app.rb', line 147 def start_client stack1 = Protocols::ProtocolStack.new([ ["socks5", {}] ], [:cipher_name], [:password]) stack2 = Protocols::ProtocolStack.new([ get_packet_protocol, get_cipher_protocol, get_obfs_protocol ].compact, [:cipher_name], [:password]) local_args = [ stack1, {:host => [:server], :port => [:port]}, stack2, {} ] start_em [:local_addr], [:local_port], Connections::TCP::ClientConnection, local_args end |
#start_em(host, port, klass_server, server_args) ⇒ Object
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/shadowsocks_ruby/app.rb', line 168 def start_em host, port, klass_server, server_args EventMachine.epoll EventMachine.run do if [:einhorn] != true @server = EventMachine.start_server(host, port, klass_server, *server_args) else fd_num = Einhorn::Worker.socket! socket = Socket.for_fd(fd_num) @server = EventMachine.attach_server(socket, klass_server, *server_args) end logger.info "server started" logger.info "Listening on #{host}:#{port}" logger.info "Send QUIT to quit after waiting for all connections to finish." logger.info "Send TERM or INT to quit after waiting for up to #{MAX_FAST_SHUTDOWN_SECONDS} seconds for connections to finish." trap_signals end end |
#start_server ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/shadowsocks_ruby/app.rb', line 126 def start_server stack3 = Protocols::ProtocolStack.new([ get_packet_protocol, get_cipher_protocol, get_obfs_protocol ].compact, [:cipher_name], [:password]) stack4 = Protocols::ProtocolStack.new([ ["plain", {}] ], [:cipher_name], [:password]) server_args = [ stack3, {}, stack4, {} ] start_em [:server], [:port], Connections::TCP::LocalBackendConnection, server_args end |
#trap_signals ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/shadowsocks_ruby/app.rb', line 73 def trap_signals STDOUT.sync = true STDERR.sync = true trap('QUIT') do self.fast_shutdown('QUIT') end trap('TERM') do self.fast_shutdown('TERM') end trap('INT') do self.fast_shutdown('INT') end end |