class Arachni::Reactor::Connection

@author Tasos “Zapotek” Laskos <tasos.laskos@gmail.com>

Constants

BLOCK_SIZE

Maximum amount of data to be written or read at a time.

We set this to the same max block size as the OpenSSL buffers because more than this tends to cause SSL errors and broken select behavior – 1024 * 16 at the time of writing.

Attributes

reactor[RW]

@return [Reactor]

Reactor associated with this connection.
role[R]

@return [Symbol]

`:client` or `:server`
socket[R]

@return [Socket]

Ruby `Socket` associated with this connection.

Public Instance Methods

_connect() click to toggle source

@private

# File lib/arachni/reactor/connection.rb, line 229
def _connect
    return true if unix? || connected?

    begin
        Error.translate do
            socket.connect_nonblock( Socket.sockaddr_in( @port, @host ) )
        end
    # Already connected. :)
    rescue Errno::EISCONN, Errno::EALREADY
    end

    @connected = true
    on_connect

    true
rescue IO::WaitReadable, IO::WaitWritable, Errno::EINPROGRESS
rescue Error => e
    close e
end
_read() click to toggle source

@note If this is a server {#listener?} it will delegate to {#accept}. @note If this is a normal socket it will read {BLOCK_SIZE} amount of data.

and pass it to {#on_read}.

Processes a `read` event for this connection.

@private

# File lib/arachni/reactor/connection.rb, line 256
def _read
    return _connect if !listener? && !connected?
    return accept   if listener?

    Error.translate do
        on_read @socket.read_nonblock( BLOCK_SIZE )
    end

# Not ready to read or write yet, we'll catch it on future Reactor ticks.
rescue IO::WaitReadable, IO::WaitWritable
rescue Error => e
    close e
end
_write() click to toggle source

@note Will call {#on_write} every time any of the buffer is consumed,

can be multiple times when performing partial writes.

@note Will call {#on_flush} once all of the buffer has been consumed.

Processes a `write` event for this connection.

Consumes and writes {BLOCK_SIZE} amount of data from the the beginning of the {#write} buffer to the socket.

@return [Integer]

Amount of the buffer consumed.

@private

# File lib/arachni/reactor/connection.rb, line 283
def _write
    return _connect if !connected?

    chunk = write_buffer.byteslice( 0, BLOCK_SIZE )
    total_written = 0

    begin
        Error.translate do
            # Send out the chunk, **all** of it, or at least try to.
            loop do
                total_written += written = @socket.write_nonblock( chunk )
                @write_buffer = @write_buffer.byteslice( written..-1 )

                # Call #on_write every time any of the buffer is consumed.
                on_write

                break if written == chunk.bytesize
                chunk = chunk.byteslice( written..-1 )
            end
        end

    # Not ready to read or write yet, we'll catch it on future Reactor ticks.
    rescue IO::WaitReadable, IO::WaitWritable
    end

    if write_buffer.empty?
        @socket.flush
        on_flush
    end

    total_written
rescue Error => e
    close e
end
accept() click to toggle source

Accepts a new client connection.

@return [Connection, nil]

New connection or `nil` if the socket isn't ready to accept new
connections yet.

@private

# File lib/arachni/reactor/connection.rb, line 191
def accept
    return if !(accepted = socket_accept)

    connection = @server_handler.call
    connection.configure socket: accepted, role: :server
    @reactor.attach connection
    connection
end
attach( reactor ) click to toggle source

@note Will first detach if already {#attached?}. @note Sets {#reactor}. @note Calls {#on_attach}.

@param [Reactor] reactor

{Reactor} to which to attach {Reactor#attach}.

@return [Bool]

`true` if the connection was attached, `nil` if the connection was
already attached.
# File lib/arachni/reactor/connection.rb, line 111
def attach( reactor )
    return if reactor.attached?( self )
    detach if attached?

    reactor.attach self

    true
end
attached?() click to toggle source

@return [Bool]

`true` if the connection is {Reactor#attached?} to a {#reactor},
`false` otherwise.
# File lib/arachni/reactor/connection.rb, line 90
def attached?
    @reactor && @reactor.attached?( self )
end
close( reason = nil ) click to toggle source

@note Will call {#on_close} right before closing the socket and detaching

from the Reactor.

Closes the connection and {Reactor#detach detaches} it from the {Reactor}.

@param [Exception] reason

Reason for the close.
# File lib/arachni/reactor/connection.rb, line 176
def close( reason = nil )
    return if closed?

    on_close reason
    close_without_callback
    nil
end
close_without_callback() click to toggle source

@note Will not call {#on_close}.

Closes the connection and {#detach detaches} it from the {Reactor}.

# File lib/arachni/reactor/connection.rb, line 139
def close_without_callback
    return if closed?
    @closed = true

    if listener? && unix? && (path = to_io.path) && File.exist?( path )
        File.delete( path )
    end

    if @socket
        @socket.close rescue nil
    end

    detach

    nil
end
closed?() click to toggle source

@return [Bool]

`true` if the connection has been {#close closed}, `false` otherwise.
# File lib/arachni/reactor/connection.rb, line 158
def closed?
    !!@closed
end
configure( options = {} ) click to toggle source

@param [Socket] socket

Ruby `Socket` associated with this connection.

@param [Symbol] role

`:server` or `:client`.

@param [Block] server_handler

Block that generates a handler as specified in {Reactor#listen}.

@private

# File lib/arachni/reactor/connection.rb, line 208
def configure( options = {} )
    @socket         = options[:socket]
    @role           = options[:role]
    @host           = options[:host]
    @port           = options[:port]
    @server_handler = options[:server_handler]

    # If we're a server without a handler then we're an accepted connection.
    if unix? || role == :server
        @connected = true
        on_connect
    end

    nil
end
connected?() click to toggle source
# File lib/arachni/reactor/connection.rb, line 224
def connected?
    !!@connected
end
detach() click to toggle source

@note Removes {#reactor}. @note Calls {#on_detach}.

{Reactor#detach Detaches} `self` from the {#reactor}.

@return [Bool]

`true` if the connection was detached, `nil` if the connection was
already detached.
# File lib/arachni/reactor/connection.rb, line 128
def detach
    return if detached?

    @reactor.detach self

    true
end
detached?() click to toggle source

@return [Bool]

`true` if the connection is not {Reactor#attached?} to a {#reactor},
`false` otherwise.
# File lib/arachni/reactor/connection.rb, line 97
def detached?
    !attached?
end
has_outgoing_data?() click to toggle source

@return [Bool]

`true` if the connection has {#write outgoing data} that have not
yet been {#write written}, `false` otherwise.
# File lib/arachni/reactor/connection.rb, line 165
def has_outgoing_data?
    !write_buffer.empty?
end
inet?() click to toggle source

@return [Bool]

`true` when using an Internet socket, `nil` if no {#socket} is
available, `false` otherwise.
# File lib/arachni/reactor/connection.rb, line 54
def inet?
    return @is_inet if !@is_inet.nil?
    return if !to_io

    @is_inet = to_io.is_a?( TCPServer ) || to_io.is_a?( TCPSocket ) || to_io.is_a?( Socket )
end
listener?() click to toggle source

@return [Bool]

`true` if the connection is a server listener.
# File lib/arachni/reactor/connection.rb, line 70
def listener?
    return @is_listener if !@is_listener.nil?
    return if !to_io

    @is_listener = to_io.is_a?( TCPServer ) || (unix? && to_io.is_a?( UNIXServer ))
end
to_io() click to toggle source

@return [IO, nil]

IO stream or `nil` if no {#socket} is available.
# File lib/arachni/reactor/connection.rb, line 63
def to_io
    return if !@socket
    @socket.to_io
end
unix?() click to toggle source

@return [Bool, nil]

`true` when using a UNIX-domain socket, `nil` if no {#socket} is
available, `false` otherwise.
# File lib/arachni/reactor/connection.rb, line 43
def unix?
    return @is_unix if !@is_unix.nil?
    return if !to_io
    return false if !Arachni::Reactor.supports_unix_sockets?

    @is_unix = to_io.is_a?( UNIXServer ) || to_io.is_a?( UNIXSocket )
end
write( data ) click to toggle source

@note The data will be buffered and sent in future {Reactor} ticks.

@param [String] data

Data to send to the peer.
# File lib/arachni/reactor/connection.rb, line 81
def write( data )
    @reactor.schedule do
        write_buffer << data
    end
end

Private Instance Methods

socket_accept() click to toggle source

Accepts a new client connection.

@return [Socket, nil]

New connection or `nil` if the socket isn't ready to accept new
connections yet.

@private

# File lib/arachni/reactor/connection.rb, line 331
def socket_accept
    begin
        @socket.accept_nonblock
    rescue IO::WaitReadable, IO::WaitWritable
    end
end
write_buffer() click to toggle source
# File lib/arachni/reactor/connection.rb, line 320
def write_buffer
    @write_buffer ||= ''
end