Skip to content

Instantly share code, notes, and snippets.

@danielwellman
Last active December 18, 2015 20:39
Show Gist options
  • Save danielwellman/5841538 to your computer and use it in GitHub Desktop.
Save danielwellman/5841538 to your computer and use it in GitHub Desktop.
Spike to create a Bane behavior with a full listen queue.
require 'socket'
# This Behavior implements the same interface as BehaviorServer since we must set up our connection
# differently than GServer (the base class of BehaviorServer) does. This means we won't be able to define
# this behavior in the same was as other behaviors. Something will have to change to unify these.
class FullListenQueue
def initialize(port)
@port = port
end
# Start the server and the bad behavior.
#
# This starts a server and then creates a client which immediately connects to the server, effectively
# filling the server's listen queue.
#
# Note that we make no attempt to check that our client _actually_ connected to the server.
# It's a race, and possible that someone from the outside could connect to our server.
# To fix this race, we could make the server expect a known secret key from the client, such as a timestamp.
# We would need to then make the server accept and handle connections in a non-blocking loop.
#
# Once this has been run, any client that tries to connect to this server should get a ECONNREFUSED error.
#
# Question:
# - We listen on all interfaces. If you listen only to localhost and try to connect from a remote machine,
# what error do you get? I'm wondering if 'not listening to outside connections' is distinguishable from
# 'listen queue is full'?
def start
@server = TCPServer.new('0.0.0.0', @port) # The server is now accepting connections
@server.listen(1)
@client = TCPSocket.new('localhost', @port) # This connects right away
end
def join
# TODO What will we join on?
end
def stop
@client.close
@server.close
end
end
if __FILE__ == $0
server = FullListenQueue.new(3000)
server.start
puts 'Started, now sleeping...'
sleep 30
#server.join
puts 'Shutting it down!'
server.stop
end
require 'socket'
puts 'Creating a client'
client = TCPSocket.new('localhost', 3000)
puts 'Writing'
client.write('Anybody home?')
#puts 'Reading'
#client.read
puts 'Closing'
client.close
puts 'Done.'
@danielwellman
Copy link
Author

If you are a Bane user and reading this, I'd welcome your feedback:

This behavior spike will implement a full listen queue - any attempts to access this port will be denied since the TCP listen queue is already full.

To solve this, this behavior will create a server with a listen queue of size one, then immediately create a client that connects to the server and stays connected forever. Thus, the listen queue is filled up and any other clients who attempt to connect to the server will be denied.

There is a race condition here between line 30 and 32. The server is created but if another client is attempting to connect, it may steal the connection before the client on line 32 can connect.

To make this behavior more predictable to clients, we could prevent this - for example, by requiring some secret key that the client must pass to the server on connect. This would mean the server would need to drop connections until it got the right connection, and the client would need to try multiple times until it connected. This predictable behavior has a cost of a more complex client and server, so I ask -- is it worth it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment