tcp snippets

Eventmachine Echo Server Example

Tagged eventmachine, echo, server, tcp  Languages ruby
require 'rubygems'
require 'eventmachine'
require 'logger'

#
# Each connection creates a new EchoServer.
#
module EchoServer
  LOG = Logger.new('echo.log')

  #
  # Called by the event loop immediately after the network connection has been
  # established, and before resumption of the network loop.
  #
  def post_init
    LOG.info "-- Connection established #{remote_ip} --"
  end

  #
  # Called by the event loop whenever data has been received by the network
  # connection. It is never called by user code.
  #
  def receive_data data
    message = "#{remote_ip}: #{data}"
    LOG.info message
    send_data message
    close_connection if data =~ /quit/i
  end

  #
  # Called by the framework whenever a connection (either a server or client
  # connection) is closed.
  #
  def unbind
    LOG.info "-- Connection closed #{remote_ip} --"
  end

  #
  # Return the IP and port of the remote client.
  #
  def remote_ip
    @remote_ip ||= begin
                     port, ip = Socket.unpack_sockaddr_in(get_peername)
                     "#{ip}:#{port}"
                   end
  end
end

EventMachine::run do
  Signal.trap("INT")  { EventMachine.stop }
  Signal.trap("TERM") { EventMachine.stop }
  EventMachine::start_server "0.0.0.0", 8080, EchoServer
end

Use telnet to test it:

$ telnet localhost 8080

How to record and replay TCP traffic

Tagged tcp, replay  Languages bash

First attempt

1) Record TCP traffic with tcpdump to pcap file

Record traffic on ethic from host 196.0.0.1 and port 2332, write to app-traffic.pcap:

tcpdump -vvv -i eth0 host 192.168.0.1 and port 2332 -w app-traffic.pcap &

2) View captured traffic

tcpdump -qns 0 -X -r app-traffic.pcap

Edit captured traffic if needed with Wireshark.

3) Edit source IP so that it’s on your own network

See the ”example in tcprewrite documentation”:http://tcpreplay.synfin.net/wiki/tcprewrite for details on how to rewrite source IP:

$ tcprewrite --pnat=10.0.0.0/8:172.16.0.0/12,192.168.0.0/16:172.16.0.0/12 --infile=input.pcap --outfile=output.pcap --skipbroadcast

4) Replay traffic with tcplivereplay from recorded pcap file (note step #3)

”See tcpliveplay documentation”:http://tcpreplay.synfin.net/wiki/tcpliveplay for details.

Note that tcpliveplay is only available on Linux not OSX.

Second attempt

Record:

sudo tcpdump -i en0 host 192.168.0.1 and port 2332 -w app-traffic.pcap


View:

tcpdump -s 0 -n -e -x -vvv -r app-traffic.pcap

Replay:

sudo tcpreplay -i en0 -t -K app-traffic.pcap

Or use tcplivereplay (Note: Linux only): http://tcpreplay.synfin.net/wiki/tcpliveplay\#tcpliveplay

Result

I couldn’t get it to work….

How to simulate TCP read, write, and connect timeouts

Tagged read, tcp, timeout, write, connect  Languages 

How to simulate TCP read, write, and connect timeouts:

  • Connect timeout

Option 1: Drop all SYN packets with firewall or iptables rules

Option 2: Try connecting to a non-routable IP, e.g., 10.0.0.0

  • Read timeout

Read from a socket to which the client or server is not writing while keeping the socket open on both ends.

  • Write timeout

You’re (almost) out of luck. There are OS-level buffers, so the write timeout might never happen.

Option 1: Use https://github.com/openresty/mockeagain and LD_PRELOAD to mock syscalls

Reference:

https://github.com/openresty/programming-openresty/blob/master/testing/testing-erroneous-cases.adoc

https://github.com/openresty/mockeagain

Ruby TCP socket with read, write, and connect timeout

Tagged write, read, ruby, socket, tcp, timeout, connect  Languages ruby

See Ruby’s Net::Protocol implementation for an example on how to implement connect, read, and write timeouts:

Or, write your own custom TCP socket implementation that supports read, write, and connect timeouts (warning, not tested):

require 'socket'
require 'timeout'

class TCPSocketWithTimeout
  class Error < StandardError; end
  class Timeout < StandardError; end
  class ReadTimeout < Timeout; end
  class WriteTimeout < Timeout; end
  attr_reader :host, :port, :tls, :read_timeout, :write_timeout, :connect_timeout, :socket

  def initialize(host:, port:, tls: false, connect_timeout: 5, read_timeout: 5, write_timeout: 5)
    @tls = tls
    @host = host
    @port = port
    @write_timeout = write_timeout
    @read_timeout = read_timeout
    @connect_timeout = connect_timeout
  end
  
  def ssl_context
    ctx = OpenSSL::SSL::SSLContext.new
    ctx.ssl_version = :TLSv1_2
    ctx.ca_file = ca_file if ca_file
    ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
    ctx
  end

  # NOTE: Upgrading Ruby 2.7 might change the Ruby's socket API
  def init_socket
    if tls
      sock = OpenSSL::SSL::SSLSocket.new(
        TCPSocket.open(host, port), # opens connection to server
        ssl_context
      )
      # Close both socket and encrypted layer
      sock.sync_close = true
    else
      sock = Socket.new(:INET, :STREAM, 0)
    end
    sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
    sock
  end

  def connect(timeout: CONNECT_TIMEOUT)
    @socket = init_socket
    deadline = Time.now.utc + timeout
    non_blocking(socket, deadline) do
      # NOTE: different method arity for non-SSL
      if tls
        socket.connect_nonblock
      else
        socket_address = Socket.pack_sockaddr_in(port, host)
        socket.connect_nonblock(socket_address)
      end
    end
  rescue Errno::EISCONN
    # Connection established
  rescue Timeout, Errno::ETIMEDOUT => e
    raise ConnectTimeout, "Connection timeout after #{timeout} seconds trying to connect to '#{host}:#{port}': #{e.class}: #{e.message}"
  rescue SystemCallError, IOError => e
    raise Error, "Connection failure while connecting to '#{host}:#{port}': #{e.class}: #{e.message}"
  end

  def read(timeout: READ_TIMEOUT, length: 1024)
    deadline = Time.now.utc + timeout
    non_blocking(socket, deadline) do
      socket.read_nonblock(length)
    end
  rescue Timeout
    raise ReadTimeout, "Timeout after #{timeout}s while reading data from #{host}:#{port}"
  rescue SystemCallError, IOError => e
    raise Error, "Connection error while reading data from #{host}:#{port} #{e.class}: #{e.message}"
  end

  def write(data, timeout: WRITE_TIMEOUT)
    deadline = Time.now.utc + timeout
    length = data.bytesize
    total_count = 0
    non_blocking(socket, deadline) do
      loop do
        count = socket.write_nonblock(data)
        total_count += count
        return total_count if total_count >= length

        data = data.byteslice(count..-1)
      end
    end
  rescue Timeout
    raise WriteTimeout, "Timeout after #{timeout}s while writing data to #{host}:#{port}"
  rescue SystemCallError, IOError => e
    raise Error, "Connection error while writing data to #{host}:#{port} #{e.class}: #{e.message}"
  end

  def disconnect
    socket&.close
  end

  def non_blocking(socket, deadline)
    raise Error, "Socket #{host}:#{port} is closed" if closed?

    yield
  rescue IO::WaitReadable => e
    time_remaining = calculate_remaining_time(deadline)
    raise Timeout, e unless IO.select([socket], nil, nil, time_remaining)

    retry
  rescue IO::WaitWritable => e
    time_remaining = calculate_remaining_time(deadline)
    raise Timeout, e unless IO.select(nil, [socket], nil, time_remaining)

    retry
  end

  def calculate_remaining_time(deadline)
    time_remaining = deadline - Time.now.utc
    raise Timeout if time_remaining.negative?

    time_remaining
  end
end

sock = TCPSocketWithTimeout.new(host: 'localhost', port: 8888)
sock.write "HELLO"
puts "Writing done"

Reference:

https://ruby-doc.org/core-2.6.2/IO.html#method-c-select https://workingwithruby.com/wwtcps/nonblocking/

A basic TCP server that can be used to test the client:

require 'socket'

server = TCPServer.open(8888)

while client = server.accept
  puts "Accepted"
  puts "Received #{client.read}"
  puts "Wrote #{client.write('Hello back')}"
  client.close
end

Notes

Use non-blocking methods and IO.select to implement timeouts using non-blocking methods.

Use TCPSocket with TLS connections.

Use Socket with SSL connections.

TCPSocket in Ruby version 3 and greater includes a connect_timeout parameter in the constructor: https://ruby-doc.org/stdlib-3.0.0/libdoc/socket/rdoc/TCPSocket.html

Ruby’s socket API is a work in progress…

References

Socket connection timeout in Ruby: https://spin.atomicobject.com/2013/09/30/socket-connection-timeout-ruby/

Working with TCP sockets: https://workingwithruby.com/downloads/Working%20With%20TCP%20Sockets.pdf

Ruby’s socket API and related documentation is not that great, so you might need to read the source: https://github.com/ruby/ruby/blob/v2_7_6/ext/socket/tcpsocket.c https://github.com/ruby/ruby/blob/v2_7_6/ext/socket/socket.c https://github.com/ruby/ruby/blob/v2_7_6/test/openssl/test_ssl.rb