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
Returns the value of attribute logger.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Class Method Summary collapse
Instance Method Summary collapse
- #decr ⇒ Object
- #fast_shutdown(signal) ⇒ Object
- #get_cipher_protocol ⇒ Object
- #get_obfs_protocol ⇒ Object
- #get_packet_protocol ⇒ Object
- #graceful_shutdown(signal) ⇒ Object
- #incr ⇒ Object
-
#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
- #stats ⇒ Object
- #trap_signals ⇒ Object
- #update_procline ⇒ Object
Constructor Details
#initialize ⇒ App
Returns a new instance of App.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/shadowsocks_ruby/app.rb', line 42 def initialize @options = @@options @totalcounter = 0 @maxcounter = 0 @counter = 0 if [:__server] name = "ssserver-ruby" host = [:server] port = [:port] elsif [:__client] name = "sslocal-ruby" host = [:local_addr] port = [:local_port] end @name = name @listen = "#{host}:#{port}" update_procline @logger = Logger.new(STDOUT).tap do |log| log.datetime_format = '%Y-%m-%d %H:%M:%S ' if [:__server] log.progname = "ssserver" elsif [:__client] log.progname = "sslocal" 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
Returns the value of attribute logger.
34 35 36 |
# File 'lib/shadowsocks_ruby/app.rb', line 34 def logger @logger end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
33 34 35 |
# File 'lib/shadowsocks_ruby/app.rb', line 33 def @options end |
Class Method Details
.options=(options) ⇒ Object
38 39 40 |
# File 'lib/shadowsocks_ruby/app.rb', line 38 def self. @@options = end |
Instance Method Details
#decr ⇒ Object
98 99 100 101 102 103 104 105 106 107 |
# File 'lib/shadowsocks_ruby/app.rb', line 98 def decr @counter -= 1 @server ||= nil # quick fix for warning in connection unit test if @server.nil? logger.info "Waiting for #{@counter} connections to finish." end update_procline EventMachine.stop_event_loop if @server.nil? and @counter == 0 @counter end |
#fast_shutdown(signal) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/shadowsocks_ruby/app.rb', line 144 def fast_shutdown(signal) EventMachine.stop_server(@server) if @server threads = [] threads << Thread.new{ logger.info "Received #{signal} signal. No longer accepting new connections." } threads << Thread.new{ logger.info "Maximum time to wait for connections is #{MAX_FAST_SHUTDOWN_SECONDS} seconds." } threads << Thread.new{ logger.info "Waiting for #{@counter} connections to finish." } threads.each { |thr| thr.join } @server = nil EventMachine.stop_event_loop if @counter == 0 Thread.new do sleep MAX_FAST_SHUTDOWN_SECONDS $kernel.exit! end end |
#get_cipher_protocol ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/shadowsocks_ruby/app.rb', line 231 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
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/shadowsocks_ruby/app.rb', line 250 def get_obfs_protocol if [:obfs_name] == nil return nil end case [:obfs_name] when "http_simple", "http_simple_compatible" ["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 "tls1.2_ticket_auth", "tls1.2_ticket_auth_compatible" ["tls_ticket", {:host => [:server], :obfs_param => [:obfs_param]}] when "tls1.2_ticket_auth_strict" ["tls_ticket", {:host => [:server], :obfs_param => [:obfs_param], :compatible => false}] else raise AppError, "no such protocol: #{[:obfs_name]}" end end |
#get_packet_protocol ⇒ Object
222 223 224 225 226 227 228 229 |
# File 'lib/shadowsocks_ruby/app.rb', line 222 def get_packet_protocol case [:packet_name] when "origin", "verify_sha1", "verify_sha1_strict" ["shadowsocks", {}] else raise AppError, "no such protocol: #{[:packet_name]}" end end |
#graceful_shutdown(signal) ⇒ Object
134 135 136 137 138 139 140 141 142 |
# File 'lib/shadowsocks_ruby/app.rb', line 134 def graceful_shutdown(signal) EventMachine.stop_server(@server) if @server threads = [] threads << Thread.new{ logger.info "Received #{signal} signal. No longer accepting new connections." } threads << Thread.new{ logger.info "Waiting for #{@counter} connections to finish." } threads.each { |thr| thr.join } @server = nil EventMachine.stop_event_loop if @counter == 0 end |
#incr ⇒ Object
90 91 92 93 94 95 96 |
# File 'lib/shadowsocks_ruby/app.rb', line 90 def incr @totalcounter += 1 @counter += 1 @maxcounter = @counter if @counter > @maxcounter update_procline @counter end |
#run! ⇒ Object
109 110 111 112 113 114 115 116 117 |
# File 'lib/shadowsocks_ruby/app.rb', line 109 def run! if [:__server] start_server elsif [:__client] start_client end rescue => e logger.fatal e end |
#start_client ⇒ Object
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/shadowsocks_ruby/app.rb', line 181 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], :timeout => [:timeout]}, stack2, {:timeout => [:timeout]} ] start_em [:local_addr], [:local_port], Connections::TCP::ClientConnection, local_args end |
#start_em(host, port, klass_server, server_args) ⇒ Object
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/shadowsocks_ruby/app.rb', line 202 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 "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
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/shadowsocks_ruby/app.rb', line 160 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, {:timeout => [:timeout]}, stack4, {:timeout => [:timeout]} ] start_em [:server], [:port], Connections::TCP::LocalBackendConnection, server_args end |
#stats ⇒ Object
86 87 88 |
# File 'lib/shadowsocks_ruby/app.rb', line 86 def stats "#{@counter}/#{@maxcounter}/#{@totalcounter}" end |
#trap_signals ⇒ Object
120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/shadowsocks_ruby/app.rb', line 120 def trap_signals STDOUT.sync = true STDERR.sync = true Signal.trap('QUIT') do graceful_shutdown('QUIT') end Signal.trap('TERM') do fast_shutdown('TERM') end Signal.trap('INT') do fast_shutdown('INT') end end |
#update_procline ⇒ Object
82 83 84 |
# File 'lib/shadowsocks_ruby/app.rb', line 82 def update_procline $0 = "shadowsocks_ruby #{VERSION} - #{@name} #{@listen} - #{stats} cur/max/tot conns" end |