Class: ShadowsocksRuby::App

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeApp

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
  @options = @@options

  @logger = Logger.new(STDOUT).tap do |log|
  
    if options[:__server]
      log.progname = "ssserver-ruby"
    elsif options[:__client]
      log.progname = "sslocal-ruby"
    end

    if options[:verbose]
      log.level = Logger::DEBUG 
    elsif options[:quiet]
      log.level = Logger::WARN 
    else
      log.level = Logger::INFO
    end
  end

end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



33
34
35
# File 'lib/shadowsocks_ruby/app.rb', line 33

def logger
  @logger
end

#optionsObject (readonly)

Returns the value of attribute options.



32
33
34
# File 'lib/shadowsocks_ruby/app.rb', line 32

def options
  @options
end

Class Method Details

.options=(options) ⇒ Object



37
38
39
# File 'lib/shadowsocks_ruby/app.rb', line 37

def self.options= options
  @@options = options
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_protocolObject



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 options[:cipher_name] == nil ||  options[:cipher_name] == "none"
    return nil
  end
  case options[:cipher_name]
  when "table"
    ["no_iv_cipher", {}]
  else
    case options[:packet_name]
    when "origin"
      ["iv_cipher", {}]
    when "verify_sha1"
      ["verify_sha1", {}]
    when "verify_sha1_strict"
      ["verify_sha1", {:compatible => false}]
    end
  end
end

#get_obfs_protocolObject



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 options[:obfs_name] == nil
    return nil
  end
  case options[:obfs_name]
  when "http_simple"
    ["http_simple", {:host => options[:server], :port => options[:port], :obfs_param => options[:obfs_param]}]
  when "http_simple_strict"
    ["http_simple", {:host => options[:server], :port => options[:port], :obfs_param => options[:obfs_param], :compatible => false}]
  when "tls_ticket"
    ["tls_ticket", {:host => options[:server], :obfs_param => options[:obfs_param]}]
  when "tls_ticket_strict"
    ["tls_ticket", {:host => options[:server], :obfs_param => options[:obfs_param], :compatible => false}]
  else
    raise AppError, "no such protocol: #{options[:obfs_name]}"
  end
end

#get_packet_protocolObject



189
190
191
192
193
194
195
196
# File 'lib/shadowsocks_ruby/app.rb', line 189

def get_packet_protocol
  case options[: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_checkObject

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 options[:__server]
    start_server
  elsif options[:__client]
    start_client
  end
#rescue Exception => e
#  logger.fatal { e.message + "\n" + e.backtrace.join("\n")}
end

#start_clientObject



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", {}]
  ], options[:cipher_name], options[:password])

  stack2 = Protocols::ProtocolStack.new([
    get_packet_protocol,
    get_cipher_protocol,
    get_obfs_protocol
  ].compact, options[:cipher_name], options[:password])

  local_args = [
    stack1,
    {:host => options[:server], :port => options[:port]},
    stack2,
    {}
  ]

  start_em options[:local_addr], options[: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 options[: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_serverObject



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, options[:cipher_name], options[:password])

  stack4 = Protocols::ProtocolStack.new([
    ["plain", {}]
  ], options[:cipher_name], options[:password])

  server_args = [
    stack3,
    {},
    stack4,
    {}
  ]

  start_em options[:server], options[:port], Connections::TCP::LocalBackendConnection, server_args
end

#trap_signalsObject



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