nats 0.4.10 → 0.4.22

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.
@@ -10,9 +10,12 @@ require "#{ep}/ext/bytesize"
10
10
  require "#{ep}/ext/json"
11
11
  require "#{ep}/server/server"
12
12
  require "#{ep}/server/sublist"
13
+ require "#{ep}/server/connection"
13
14
  require "#{ep}/server/options"
14
15
  require "#{ep}/server/const"
15
16
  require "#{ep}/server/util"
17
+ require "#{ep}/server/varz"
18
+ require "#{ep}/server/connz"
16
19
 
17
20
  # Do setup
18
21
  NATSD::Server.setup(ARGV.dup)
@@ -20,6 +23,7 @@ NATSD::Server.setup(ARGV.dup)
20
23
  # Event Loop
21
24
  EM.run {
22
25
  log "Starting #{NATSD::APP_NAME} version #{NATSD::VERSION} on port #{NATSD::Server.port}"
26
+ log "TLS/SSL Support Enabled" if NATSD::Server.options[:ssl]
23
27
  begin
24
28
  EM.set_descriptor_table_size(32768) # Requires Root privileges
25
29
  EventMachine::start_server(NATSD::Server.host, NATSD::Server.port, NATSD::Connection)
@@ -28,4 +32,15 @@ EM.run {
28
32
  log_error
29
33
  exit(1)
30
34
  end
35
+
36
+ # Check to see if we need to fire up the http monitor port and server
37
+ if NATSD::Server.options[:http_port]
38
+ begin
39
+ NATSD::Server.start_http_server
40
+ rescue => e
41
+ log "Could not start monitoring server on port #{NATSD::Server.options[:http_port]}"
42
+ log_error
43
+ exit(1)
44
+ end
45
+ end
31
46
  }
@@ -0,0 +1,269 @@
1
+ module NATSD #:nodoc: all
2
+
3
+ module Connection #:nodoc: all
4
+
5
+ attr_accessor :in_msgs, :out_msgs, :in_bytes, :out_bytes
6
+ attr_reader :cid, :closing, :last_activity, :writev_size
7
+ alias :closing? :closing
8
+
9
+ def flush_data
10
+ return if @writev.nil? || closing?
11
+ send_data(@writev.join)
12
+ @writev, @writev_size = nil, 0
13
+ end
14
+
15
+ def queue_data(data)
16
+ EM.next_tick { flush_data } if @writev.nil?
17
+ (@writev ||= []) << data
18
+ @writev_size += data.bytesize
19
+ flush_data if @writev_size > MAX_WRITEV_SIZE
20
+ end
21
+
22
+ def client_info
23
+ @client_info ||= (get_peername.nil? ? 'N/A' : Socket.unpack_sockaddr_in(get_peername))
24
+ end
25
+
26
+ def info
27
+ {
28
+ :cid => cid,
29
+ :ip => client_info[1],
30
+ :port => client_info[0],
31
+ :subscriptions => @subscriptions.size,
32
+ :pending_size => get_outbound_data_size,
33
+ :in_msgs => @in_msgs,
34
+ :out_msgs => @out_msgs,
35
+ :in_bytes => @in_bytes,
36
+ :out_bytes => @out_bytes
37
+ }
38
+ end
39
+
40
+ def max_connections_exceeded?
41
+ return false unless (Server.num_connections > Server.max_connections)
42
+ error_close MAX_CONNS_EXCEEDED
43
+ debug "Maximum #{Server.max_connections} connections exceeded, c:#{cid} will be closed"
44
+ true
45
+ end
46
+
47
+ def post_init
48
+ @cid = Server.cid
49
+ @subscriptions = {}
50
+ @verbose = @pedantic = true # suppressed by most clients, but allows friendly telnet
51
+ @in_msgs = @out_msgs = @in_bytes = @out_bytes = 0
52
+ @writev_size = 0
53
+ @parse_state = AWAITING_CONTROL_LINE
54
+ send_info
55
+ debug "Client connection created", client_info, cid
56
+ if Server.ssl_required?
57
+ debug "Starting TLS/SSL", client_info, cid
58
+ flush_data
59
+ @ssl_pending = EM.add_timer(NATSD::Server.ssl_timeout) { connect_ssl_timeout }
60
+ start_tls(:verify_peer => true) if Server.ssl_required?
61
+ end
62
+ @auth_pending = EM.add_timer(NATSD::Server.auth_timeout) { connect_auth_timeout } if Server.auth_required?
63
+ @ping_timer = EM.add_periodic_timer(NATSD::Server.ping_interval) { send_ping }
64
+ @pings_outstanding = 0
65
+ Server.num_connections += 1
66
+ return if max_connections_exceeded?
67
+ end
68
+
69
+ def send_ping
70
+ return if @closing
71
+ if @pings_outstanding > NATSD::Server.ping_max
72
+ error_close UNRESPONSIVE
73
+ return
74
+ end
75
+ queue_data(PING_RESPONSE)
76
+ flush_data
77
+ @pings_outstanding += 1
78
+ end
79
+
80
+ def connect_auth_timeout
81
+ error_close AUTH_REQUIRED
82
+ debug "Connection timeout due to lack of auth credentials", cid
83
+ end
84
+
85
+ def connect_ssl_timeout
86
+ error_close SSL_REQUIRED
87
+ debug "Connection timeout due to lack of TLS/SSL negotiations", cid
88
+ end
89
+
90
+ def receive_data(data)
91
+ @buf = @buf ? @buf << data : data
92
+ return close_connection if @buf =~ /(\006|\004)/ # ctrl+c or ctrl+d for telnet friendly
93
+
94
+ # while (@buf && !@buf.empty? && !@closing)
95
+ while (@buf && !@closing)
96
+ case @parse_state
97
+ when AWAITING_CONTROL_LINE
98
+ case @buf
99
+ when PUB_OP
100
+ ctrace('PUB OP', strip_op($&)) if NATSD::Server.trace_flag?
101
+ return connect_auth_timeout if @auth_pending
102
+ @buf = $'
103
+ @parse_state = AWAITING_MSG_PAYLOAD
104
+ @msg_sub, @msg_reply, @msg_size = $1, $3, $4.to_i
105
+ if (@msg_size > NATSD::Server.max_payload)
106
+ debug_print_msg_too_big(@msg_size)
107
+ error_close PAYLOAD_TOO_BIG
108
+ end
109
+ queue_data(INVALID_SUBJECT) if (@pedantic && !(@msg_sub =~ SUB_NO_WC))
110
+ when SUB_OP
111
+ ctrace('SUB OP', strip_op($&)) if NATSD::Server.trace_flag?
112
+ return connect_auth_timeout if @auth_pending
113
+ @buf = $'
114
+ sub, qgroup, sid = $1, $3, $4
115
+ return queue_data(INVALID_SUBJECT) if !($1 =~ SUB)
116
+ return queue_data(INVALID_SID_TAKEN) if @subscriptions[sid]
117
+ sub = Subscriber.new(self, sub, sid, qgroup, 0)
118
+ @subscriptions[sid] = sub
119
+ Server.subscribe(sub)
120
+ queue_data(OK) if @verbose
121
+ when UNSUB_OP
122
+ ctrace('UNSUB OP', strip_op($&)) if NATSD::Server.trace_flag?
123
+ return connect_auth_timeout if @auth_pending
124
+ @buf = $'
125
+ sid, sub = $1, @subscriptions[$1]
126
+ if sub
127
+ # If we have set max_responses, we will unsubscribe once we have received
128
+ # the appropriate amount of responses.
129
+ sub.max_responses = ($2 && $3) ? $3.to_i : nil
130
+ delete_subscriber(sub) unless (sub.max_responses && (sub.num_responses < sub.max_responses))
131
+ queue_data(OK) if @verbose
132
+ else
133
+ queue_data(INVALID_SID_NOEXIST) if @pedantic
134
+ end
135
+ when PING
136
+ ctrace('PING OP', strip_op($&)) if NATSD::Server.trace_flag?
137
+ @buf = $'
138
+ queue_data(PONG_RESPONSE)
139
+ flush_data
140
+ when PONG
141
+ ctrace('PONG OP', strip_op($&)) if NATSD::Server.trace_flag?
142
+ @buf = $'
143
+ @pings_outstanding -= 1
144
+ when CONNECT
145
+ ctrace('CONNECT OP', strip_op($&)) if NATSD::Server.trace_flag?
146
+ @buf = $'
147
+ begin
148
+ config = JSON.parse($1)
149
+ process_connect_config(config)
150
+ rescue => e
151
+ queue_data(INVALID_CONFIG)
152
+ log_error
153
+ end
154
+ when INFO
155
+ ctrace('INFO OP', strip_op($&)) if NATSD::Server.trace_flag?
156
+ return connect_auth_timeout if @auth_pending
157
+ @buf = $'
158
+ send_info
159
+ when UNKNOWN
160
+ ctrace('Unknown Op', strip_op($&)) if NATSD::Server.trace_flag?
161
+ return connect_auth_timeout if @auth_pending
162
+ @buf = $'
163
+ queue_data(UNKNOWN_OP)
164
+ else
165
+ # If we are here we do not have a complete line yet that we understand.
166
+ # If too big, cut the connection off.
167
+ if @buf.bytesize > NATSD::Server.max_control_line
168
+ debug_print_controlline_too_big(@buf.bytesize)
169
+ close_connection
170
+ end
171
+ return
172
+ end
173
+ @buf = nil if (@buf && @buf.empty?)
174
+
175
+ when AWAITING_MSG_PAYLOAD
176
+ return unless (@buf.bytesize >= (@msg_size + CR_LF_SIZE))
177
+ msg = @buf.slice(0, @msg_size)
178
+ ctrace('Processing msg', @msg_sub, @msg_reply, msg) if NATSD::Server.trace_flag?
179
+ queue_data(OK) if @verbose
180
+ Server.route_to_subscribers(@msg_sub, @msg_reply, msg)
181
+ @in_msgs += 1
182
+ @in_bytes += @msg_size
183
+ @buf = @buf.slice((@msg_size + CR_LF_SIZE), @buf.bytesize)
184
+ @msg_sub = @msg_size = @reply = nil
185
+ @parse_state = AWAITING_CONTROL_LINE
186
+ @buf = nil if (@buf && @buf.empty?)
187
+ end
188
+ end
189
+ end
190
+
191
+ def send_info
192
+ queue_data("INFO #{Server.info_string}#{CR_LF}")
193
+ end
194
+
195
+ def process_connect_config(config)
196
+ @verbose = config['verbose'] unless config['verbose'].nil?
197
+ @pedantic = config['pedantic'] unless config['pedantic'].nil?
198
+
199
+ return queue_data(OK) unless Server.auth_required?
200
+
201
+ EM.cancel_timer(@auth_pending)
202
+ if Server.auth_ok?(config['user'], config['pass'])
203
+ queue_data(OK) if @verbose
204
+ @auth_pending = nil
205
+ else
206
+ error_close AUTH_FAILED
207
+ debug "Authorization failed for connection", cid
208
+ end
209
+ end
210
+
211
+ def delete_subscriber(sub)
212
+ ctrace('DELSUB OP', sub.subject, sub.qgroup, sub.sid) if NATSD::Server.trace_flag?
213
+ Server.unsubscribe(sub)
214
+ @subscriptions.delete(sub.sid)
215
+ end
216
+
217
+ def error_close(msg)
218
+ queue_data(msg)
219
+ flush_data
220
+ EM.next_tick { close_connection_after_writing }
221
+ @closing = true
222
+ end
223
+
224
+ def debug_print_controlline_too_big(line_size)
225
+ sizes = "#{pretty_size(line_size)} vs #{pretty_size(NATSD::Server.max_control_line)} max"
226
+ debug "Control line size exceeded (#{sizes}), closing connection.."
227
+ end
228
+
229
+ def debug_print_msg_too_big(msg_size)
230
+ sizes = "#{pretty_size(msg_size)} vs #{pretty_size(NATSD::Server.max_payload)} max"
231
+ debug "Message payload size exceeded (#{sizes}), closing connection"
232
+ end
233
+
234
+ def unbind
235
+ debug "Client connection closed", client_info, cid
236
+ Server.num_connections -= 1
237
+ @subscriptions.each_value { |sub| Server.unsubscribe(sub) }
238
+ EM.cancel_timer(@ssl_pending) if @ssl_pending
239
+ @ssl_pending = nil
240
+ EM.cancel_timer(@auth_pending) if @auth_pending
241
+ @auth_pending = nil
242
+ EM.cancel_timer(@ping_timer) if @ping_timer
243
+ @ping_timer = nil
244
+
245
+ @closing = true
246
+ end
247
+
248
+ def ssl_handshake_completed
249
+ EM.cancel_timer(@ssl_pending)
250
+ @ssl_pending = nil
251
+ cert = get_peer_cert
252
+ debug "Client Certificate:", cert ? cert : 'N/A', cid
253
+ end
254
+
255
+ # FIXME! Cert accepted by default
256
+ def ssl_verify_peer(cert)
257
+ true
258
+ end
259
+
260
+ def ctrace(*args)
261
+ trace(args, "c: #{cid}")
262
+ end
263
+
264
+ def strip_op(op='')
265
+ op.dup.sub(CR_LF, EMPTY)
266
+ end
267
+ end
268
+
269
+ end
@@ -0,0 +1,54 @@
1
+ module NATSD #:nodoc: all
2
+
3
+ class Connz
4
+ def call(env)
5
+ c_info = Server.dump_connections
6
+ qs = env['QUERY_STRING']
7
+ if (qs =~ /n=(\d+)/)
8
+ sort_key = :pending_size
9
+ n = $1.to_i
10
+ if (qs =~ /s=(\S+)/)
11
+ case $1.downcase
12
+ when 'in_msgs'; sort_key = :in_msgs
13
+ when 'msgs_from'; sort_key = :in_msgs
14
+ when 'out_msgs'; sort_key = :out_msgs
15
+ when 'msgs_to'; sort_key = :out_msgs
16
+ when 'in_bytes'; sort_key = :in_bytes
17
+ when 'bytes_from'; sort_key = :in_bytes
18
+ when 'out_bytes'; sort_key = :out_bytes
19
+ when 'bytes_to'; sort_key = :out_bytes
20
+ when 'subs'; sort_key = :subscriptions
21
+ when 'subscriptions'; sort_key = :subscriptions
22
+ end
23
+ end
24
+ conns = c_info[:connections]
25
+ c_info[:connections] = conns.sort { |a,b| b[sort_key] <=> a[sort_key] } [0, n]
26
+ end
27
+ connz_json = JSON.pretty_generate(c_info) + "\n"
28
+ hdrs = RACK_JSON_HDR.dup
29
+ hdrs['Content-Length'] = connz_json.bytesize.to_s
30
+ [200, hdrs, connz_json]
31
+ end
32
+ end
33
+
34
+ class Server
35
+ class << self
36
+
37
+ def dump_connections
38
+ conns, total = [], 0
39
+ ObjectSpace.each_object(NATSD::Connection) do |c|
40
+ next if c.closing?
41
+ total += c.info[:pending_size]
42
+ conns << c.info
43
+ end
44
+ {
45
+ :pending_size => total,
46
+ :num_connections => conns.size,
47
+ :connections => conns
48
+ }
49
+ end
50
+
51
+ end
52
+ end
53
+
54
+ end
@@ -1,7 +1,7 @@
1
1
 
2
2
  module NATSD #:nodoc:
3
3
 
4
- VERSION = '0.4.10'
4
+ VERSION = '0.4.22'
5
5
  APP_NAME = 'nats-server'
6
6
 
7
7
  DEFAULT_PORT = 4222
@@ -26,6 +26,7 @@ module NATSD #:nodoc:
26
26
  CR_LF_SIZE = CR_LF.bytesize
27
27
  EMPTY = ''.freeze
28
28
  OK = "+OK#{CR_LF}".freeze
29
+ PING_RESPONSE = "PING#{CR_LF}".freeze
29
30
  PONG_RESPONSE = "PONG#{CR_LF}".freeze
30
31
  INFO_RESPONSE = "#{CR_LF}".freeze
31
32
 
@@ -38,8 +39,12 @@ module NATSD #:nodoc:
38
39
  INVALID_CONFIG = "-ERR 'Invalid config, valid JSON required for connection configuration'#{CR_LF}".freeze
39
40
  AUTH_REQUIRED = "-ERR 'Authorization is required'#{CR_LF}".freeze
40
41
  AUTH_FAILED = "-ERR 'Authorization failed'#{CR_LF}".freeze
42
+ SSL_REQUIRED = "-ERR 'TSL/SSL is required'#{CR_LF}".freeze
43
+ SSL_FAILED = "-ERR 'TLS/SSL failed'#{CR_LF}".freeze
41
44
  UNKNOWN_OP = "-ERR 'Unknown Protocol Operation'#{CR_LF}".freeze
42
45
  SLOW_CONSUMER = "-ERR 'Slow consumer detected, connection dropped'#{CR_LF}".freeze
46
+ UNRESPONSIVE = "-ERR 'Unresponsive client detected, connection dropped'#{CR_LF}".freeze
47
+ MAX_CONNS_EXCEEDED = "-ERR 'Maximum client connections exceeded, connection dropped'#{CR_LF}".freeze
43
48
 
44
49
  # Pedantic Mode
45
50
  SUB = /^([^\.\*>\s]+|>$|\*)(\.([^\.\*>\s]+|>$|\*))*$/
@@ -56,7 +61,24 @@ module NATSD #:nodoc:
56
61
  # Maximum outbound size per client
57
62
  MAX_PENDING_SIZE = (10*1024*1024)
58
63
 
64
+ # Maximum pending bucket size
65
+ MAX_WRITEV_SIZE = (64*1024)
66
+
67
+ # Maximum connections default
68
+ DEFAULT_MAX_CONNECTIONS = (64*1024)
69
+
70
+ # TLS/SSL wait time
71
+ SSL_TIMEOUT = 0.5
72
+
59
73
  # Authorization wait time
60
- AUTH_TIMEOUT = 1
74
+ AUTH_TIMEOUT = SSL_TIMEOUT + 0.5
75
+
76
+ # Ping intervals
77
+ DEFAULT_PING_INTERVAL = 120
78
+ DEFAULT_PING_MAX = 2
79
+
80
+ # HTTP
81
+ RACK_JSON_HDR = { 'Content-Type' => 'application/json' }
82
+ RACK_TEXT_HDR = { 'Content-Type' => 'text/plain' }
61
83
 
62
84
  end
@@ -17,7 +17,9 @@ module NATSD
17
17
  "(default: #{DEFAULT_HOST})") { |host| @options[:addr] = host }
18
18
  opts.on("-p", "--port PORT", "Use PORT (default: #{DEFAULT_PORT})") { |port| @options[:port] = port.to_i }
19
19
  opts.on("-d", "--daemonize", "Run daemonized in the background") { @options[:daemonize] = true }
20
- opts.on("-P", "--pid FILE", "File to store PID") { |file| @options[:pid_file] = file }
20
+ opts.on("-P", "--pid FILE", "File to store PID") { |file| @options[:pid_file] = file }
21
+
22
+ opts.on("-m", "--http_port PORT", "Use HTTP PORT ") { |port| @options[:http_port] = port.to_i }
21
23
 
22
24
  opts.on("-c", "--config FILE", "Configuration File") { |file| @options[:config_file] = file }
23
25
 
@@ -35,6 +37,9 @@ module NATSD
35
37
  opts.on("--user user", "User required for connections") { |user| @options[:user] = user }
36
38
  opts.on("--pass password", "Password required for connections") { |pass| @options[:pass] = pass }
37
39
 
40
+ opts.separator ""
41
+ opts.on("--ssl", "Enable SSL") { |ssl| @options[:ssl] = true }
42
+
38
43
  opts.separator ""
39
44
  opts.separator "Advanced IO options:"
40
45
 
@@ -63,8 +68,13 @@ module NATSD
63
68
  @options[:pass] = auth['pass'] if @options[:pass].nil?
64
69
  @options[:token] = auth['token'] if @options[:token].nil?
65
70
  @options[:auth_timeout] = auth['timeout'] if @options[:auth_timeout].nil?
71
+ # Multiple Users setup
72
+ @options[:users] = symbolize_users(auth['users']) || []
66
73
  end
67
74
 
75
+ # TLS/SSL
76
+ @options[:ssl] = config['ssl'] if @options[:ssl].nil?
77
+
68
78
  @options[:pid_file] = config['pid_file'] if @options[:pid_file].nil?
69
79
  @options[:log_file] = config['log_file'] if @options[:log_file].nil?
70
80
  @options[:log_time] = config['logtime'] if @options[:log_time].nil?
@@ -75,11 +85,26 @@ module NATSD
75
85
  @options[:max_control_line] = config['max_control_line'] if config['max_control_line']
76
86
  @options[:max_payload] = config['max_payload'] if config['max_payload']
77
87
  @options[:max_pending] = config['max_pending'] if config['max_pending']
88
+ @options[:max_connections] = config['max_connections'] if config['max_connections']
78
89
 
79
90
  # just set
80
- @options[:noepoll] = config['no_epoll'] if config['no_epoll']
91
+ @options[:noepoll] = config['no_epoll'] if config['no_epoll']
81
92
  @options[:nokqueue] = config['no_kqueue'] if config['no_kqueue']
82
93
 
94
+ if http = config['http']
95
+ if @options[:http_net].nil?
96
+ @options[:http_net] = http['net'] || @options[:addr]
97
+ end
98
+ @options[:http_port] = http['port'] if @options[:http_port].nil?
99
+ @options[:http_user] = http['user'] if @options[:http_user].nil?
100
+ @options[:http_password] = http['password'] if @options[:http_password].nil?
101
+ end
102
+
103
+ if ping = config['ping']
104
+ @options[:ping_interval] = ping['interval'] if @options[:ping_interval].nil?
105
+ @options[:ping_max] = ping['max_outstanding'] if @options[:ping_max].nil?
106
+ end
107
+
83
108
  rescue => e
84
109
  log "Could not read configuration file: #{e}"
85
110
  exit
@@ -87,16 +112,29 @@ module NATSD
87
112
 
88
113
  def setup_logs
89
114
  return unless @options[:log_file]
90
- $stdout.reopen(@options[:log_file], "w")
115
+ $stdout.reopen(@options[:log_file], 'a')
91
116
  $stdout.sync = true
92
117
  $stderr.reopen($stdout)
93
118
  end
94
119
 
120
+ def symbolize_users(users)
121
+ return nil unless users
122
+ auth_users = []
123
+ users.each do |u|
124
+ auth_users << { :user => u['user'], :pass => u['pass'] || u['password'] }
125
+ end
126
+ auth_users
127
+ end
128
+
95
129
  def finalize_options
96
130
  # Addr/Port
97
131
  @options[:port] ||= DEFAULT_PORT
98
132
  @options[:addr] ||= DEFAULT_HOST
99
133
 
134
+ # Max Connections
135
+ @options[:max_connections] ||= DEFAULT_MAX_CONNECTIONS
136
+ @max_connections = @options[:max_connections]
137
+
100
138
  # Debug and Tracing
101
139
  @debug_flag = @options[:debug]
102
140
  @trace_flag = @options[:trace]
@@ -109,8 +147,28 @@ module NATSD
109
147
  trace "TRACE is on"
110
148
 
111
149
  # Authorization
150
+
151
+ # Multi-user setup for auth
152
+ if @options[:user]
153
+ # Multiple Users setup
154
+ @options[:users] ||= []
155
+ @options[:users].unshift({:user => @options[:user], :pass => @options[:pass]}) if @options[:user]
156
+ elsif @options[:users]
157
+ first = @options[:users].first
158
+ @options[:user], @options[:pass] = first[:user], first[:pass]
159
+ end
160
+
112
161
  @auth_required = (not @options[:user].nil?)
113
162
 
163
+ @ssl_required = (not @options[:ssl].nil?)
164
+
165
+ # Pings
166
+ @options[:ping_interval] ||= DEFAULT_PING_INTERVAL
167
+ @ping_interval = @options[:ping_interval]
168
+
169
+ @options[:ping_max] ||= DEFAULT_PING_MAX
170
+ @ping_max = @options[:ping_max]
171
+
114
172
  # Thresholds
115
173
  @options[:max_control_line] ||= MAX_CONTROL_LINE_SIZE
116
174
  @max_control_line = @options[:max_control_line]
@@ -123,6 +181,9 @@ module NATSD
123
181
 
124
182
  @options[:auth_timeout] ||= AUTH_TIMEOUT
125
183
  @auth_timeout = @options[:auth_timeout]
184
+
185
+ @options[:ssl_timeout] ||= SSL_TIMEOUT
186
+ @ssl_timeout = @options[:ssl_timeout]
126
187
  end
127
188
 
128
189
  end