diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index 9ef4b8f4d..b3969f167 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -235,6 +235,10 @@ context.JK.JamServer.registerOnSocketClosed(socketClosed); } + function registerServerRejection() { + logger.debug("register for server rejection"); + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_REJECTION_ERROR, serverRejection); + } /** * Called whenever the websocket closes; this gives us a chance to cleanup things that should be stopped/cleared @@ -247,6 +251,14 @@ } + function serverRejection(header, payload) { + logger.warn("server rejected our websocket connection. reason=" + payload.error_msg) + + if(payload.error_msg == 'max_user_connections') { + context.JK.Banner.showAlert("Too Many Connections", "You have too many connections to the server. If you believe this is in error, please contact support.") + } + } + /////////////////// /// RECONNECT ///// /////////////////// @@ -672,6 +684,7 @@ registerLoginAck(); registerHeartbeatAck(); + registerServerRejection(); registerSocketClosed(); $inSituBanner = $('.server-connection'); diff --git a/web/config/application.rb b/web/config/application.rb index 9ad4baa8c..2293aee1e 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -115,6 +115,7 @@ if defined?(Bundler) # Runs the websocket gateway within the web app config.websocket_gateway_uri = "ws://localhost:#{config.websocket_gateway_port}/websocket" config.websocket_gateway_trusted_uri = "ws://localhost:#{config.websocket_gateway_port + 1}/websocket" + config.websocket_gateway_max_connections_per_user = 10 config.external_hostname = ENV['EXTERNAL_HOSTNAME'] || 'localhost' config.external_port = ENV['EXTERNAL_PORT'] || 3000 diff --git a/web/config/initializers/eventmachine.rb b/web/config/initializers/eventmachine.rb index 324d304de..529812127 100644 --- a/web/config/initializers/eventmachine.rb +++ b/web/config/initializers/eventmachine.rb @@ -13,6 +13,7 @@ unless $rails_rake_task :connect_time_expire_client => APP_CONFIG.websocket_gateway_connect_time_expire_client, :connect_time_stale_browser => APP_CONFIG.websocket_gateway_connect_time_stale_browser, :connect_time_expire_browser=> APP_CONFIG.websocket_gateway_connect_time_expire_browser, + :max_connections_per_user => APP_CONFIG.websocket_gateway_max_connections_per_user, :rabbitmq_host => APP_CONFIG.rabbitmq_host, :rabbitmq_port => APP_CONFIG.rabbitmq_port, :calling_thread => current, diff --git a/websocket-gateway/bin/websocket_gateway b/websocket-gateway/bin/websocket_gateway index 274760390..5d4e562e2 100755 --- a/websocket-gateway/bin/websocket_gateway +++ b/websocket-gateway/bin/websocket_gateway @@ -51,6 +51,7 @@ Server.new.run(:port => config["port"], :connect_time_expire_client => config["connect_time_expire_client"], :connect_time_stale_browser => config["connect_time_stale_browser"], :connect_time_expire_browser => config["connect_time_expire_browser"], + :max_connections_per_user => config["max_connections_per_user"], :rabbitmq_host => config['rabbitmq_host'], :rabbitmq_port => config['rabbitmq_port'], :cidr => config['cidr']) diff --git a/websocket-gateway/config/application.yml b/websocket-gateway/config/application.yml index 345eb0ed5..fcf36d31c 100644 --- a/websocket-gateway/config/application.yml +++ b/websocket-gateway/config/application.yml @@ -4,6 +4,7 @@ Defaults: &defaults connect_time_stale_browser: 40 connect_time_expire_browser: 60 cidr: [0.0.0.0/0] + max_connections_per_user: 20 development: port: 6767 diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb index 686cf06f0..37f458930 100644 --- a/websocket-gateway/lib/jam_websockets/router.rb +++ b/websocket-gateway/lib/jam_websockets/router.rb @@ -26,7 +26,8 @@ module JamWebsockets :connect_time_stale_client, :heartbeat_interval_browser, :connect_time_expire_browser, - :connect_time_stale_browser + :connect_time_stale_browser, + :max_connections_per_user def initialize() @log = Logging.logger[self] @@ -49,7 +50,7 @@ module JamWebsockets @ar_base_logger = ::Logging::Repository.instance[ActiveRecord::Base] end - def start(connect_time_stale_client, connect_time_expire_client, connect_time_stale_browser, connect_time_expire_browser, options={:host => "localhost", :port => 5672}, &block) + def start(connect_time_stale_client, connect_time_expire_client, connect_time_stale_browser, connect_time_expire_browser, options={:host => "localhost", :port => 5672, :max_connections_per_user => 10}, &block) @log.info "startup" @@ -59,6 +60,7 @@ module JamWebsockets @heartbeat_interval_browser = connect_time_stale_browser / 2 @connect_time_stale_browser = connect_time_stale_browser @connect_time_expire_browser = connect_time_expire_browser + @max_connections_per_user = options[:max_connections_per_user] begin @amqp_connection_manager = AmqpConnectionManager.new(true, 4, :host => options[:host], :port => options[:port]) @@ -566,6 +568,12 @@ module JamWebsockets user = valid_login(username, password, token, client_id) + # protect against this user swamping the server + @log.error "CONNENCTION COUNT #{Connection.where(user_id: user.id).count} MAX CONNECTIONS #{@max_connections_per_user}" + if Connection.where(user_id: user.id).count >= @max_connections_per_user + raise SessionError, 'max_user_connections' + end + # XXX This logic needs to instead be handled by a broadcast out to all websockets indicating dup # kill any websocket connections that have this same client_id, which can happen in race conditions # this code must happen here, before we go any further, so that there is only one websocket connection per client_id diff --git a/websocket-gateway/lib/jam_websockets/server.rb b/websocket-gateway/lib/jam_websockets/server.rb index 830d8c40e..7b25ea7c9 100644 --- a/websocket-gateway/lib/jam_websockets/server.rb +++ b/websocket-gateway/lib/jam_websockets/server.rb @@ -20,6 +20,7 @@ module JamWebsockets connect_time_expire_client = options[:connect_time_expire_client].to_i connect_time_stale_browser = options[:connect_time_stale_browser].to_i connect_time_expire_browser = options[:connect_time_expire_browser].to_i + max_connections_per_user = options[:max_connections_per_user].to_i rabbitmq_host = options[:rabbitmq_host] rabbitmq_port = options[:rabbitmq_port].to_i calling_thread = options[:calling_thread] @@ -33,7 +34,7 @@ module JamWebsockets } EventMachine.run do - @router.start(connect_time_stale_client, connect_time_expire_client, connect_time_stale_browser, connect_time_expire_browser, host: rabbitmq_host, port: rabbitmq_port) do + @router.start(connect_time_stale_client, connect_time_expire_client, connect_time_stale_browser, connect_time_expire_browser, host: rabbitmq_host, port: rabbitmq_port, max_connections_per_user: max_connections_per_user) do start_connection_expiration start_connection_flagger start_websocket_listener(host, port, trust_port, trust_check, options[:emwebsocket_debug]) diff --git a/websocket-gateway/spec/jam_websockets/router_spec.rb b/websocket-gateway/spec/jam_websockets/router_spec.rb index 3f38ad2c0..269cca773 100644 --- a/websocket-gateway/spec/jam_websockets/router_spec.rb +++ b/websocket-gateway/spec/jam_websockets/router_spec.rb @@ -111,6 +111,7 @@ describe Router do @router.heartbeat_interval_client = @router.connect_time_stale_client / 2 @router.connect_time_expire_browser = 60 @router.connect_time_stale_browser = 40 + @router.max_connections_per_user = 10 @router.heartbeat_interval_browser = @router.connect_time_stale_browser / 2 @router.amqp_connection_manager = AmqpConnectionManager.new(true, 4, host: 'localhost', port: 5672) end