Red Green Repeat Adventures of a Spec Driven Junkie

Socket Listener in Ruby

Transverse Flute in D-flat

I was in a situation where I wanted to test out the port forwarding configuration of a client to a server. This is to configure a resource so a long process’ callback can happen locally and I want to ensure the port forwarding was happening correctly with different tools (otherwise, there’s lots of waiting.)

A simple way to test is to listen to the port and just make a similar call using curl, right?

The curl part is trivial, create a request, just like to any server.

$ curl localhost:port

The listener part… well, I thought I could get a ready made application that does this, which prints anything received on the port and have it printed to the screen.

Simple right?

Well, my search for a listener program only resulted in netstat, which tells me all the connected programs to each port (super useful if you want to kill programs based on ports - cough microservice architecture cough)

Wireshark was one I thought that could ‘sniff out’ what’s coming in over a port, but it is overwhelming as Wireshark reports EVERYTHING going through the network. Too much for my use case.

At the end, I found a quick article that wrote a listener program in Ruby and modified it further to write request contents to the screen and send a proper HTTP response back.

The original program from Tutorials Point

1
2
3
4
5
6
7
8
9
require 'socket'                 # Get sockets from stdlib

server = TCPServer.open(2000)    # Socket to listen on port 2000
loop {                           # Servers run forever
   client = server.accept        # Wait for a client to connect
   client.puts(Time.now.ctime)   # Send the time to the client
   client.puts "Closing the connection. Bye!"
   client.close                  # Disconnect from the client
}

My final version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require 'socket'

port_to_listen_to = 8080

puts "starting to listen to: #{port_to_listen_to}"
server = TCPServer.open(port_to_listen_to)
loop {
  client = server.accept

  puts 'receiving data ' + Time.now.ctime
  puts '--------------------------------------------------------------------------------'
  
  while (a = client.gets) != "\r\n" do
    puts a
  end

  puts '--------------------------------------------------------------------------------'


  client.puts "HTTP/1.1 200 Success"
  client.puts ""
  client.puts "Success\n"
  client.close
}

My changes

I added changes to the original version, specifically these areas:

  • \r\n marks the end of the HTTP header in the HTTP 1.1 spec, so print everything until encountering this value.
  • puts prints section breaks and incoming data to the screen.
  • client.puts sends a HTTP 1.1 formatted message so other web clients such as Postman responds properly.

Testing out

Let’s see results using this new Ruby script by making a request, seeing the listener output, and the client’s output.

To set up, install Ruby, write the above contents into a file named: listener.rb and run the listener with command: $ ruby listener.rb

With modern security or firewall set up, testing is easiest on the same computer.

This test requires a minimum of one computer with two terminals: one to run the listener program and another to make the curl request.

Request

I will make a request using curl, it’s sufficient for this use case.

vagrant@ubuntu-xenial:~$ curl localhost:8080

Listener Result

With the listener program running, this is the output:

vagrant@ubuntu-xenial:~$ ruby listener.rb
starting to listen to: 8080
receiving data Fri Mar 29 18:19:28 2019
--------------------------------------------------------------------------------
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.47.0
Accept: */*
--------------------------------------------------------------------------------

Client Result

The client sees:

vagrant@ubuntu-xenial:~$ curl localhost:8080
Success

Tada~!

Other tips

Listening to ports less than 1024 with the script was tricky because those ports require root access.

The way I set this up: changed to root and installed rvm, to install ruby.

$ sudo su -
# standard rvm install
# standard ruby install

If the goal is to listen to ports <1024, definitely run as root, or find a way to run a ruby program as root.

Possible Future Work

Getting this to work on a basic level was enough, but for the future there are areas to investigate or improve:

  • Use Traveling Ruby to make a portable version so running as root would be as easy as: $ sudo listener ?
  • Print out additional information from the request, especially a POST request’s body. Hint: probably use the content-length field.

Conclusion

This was fun delving into networking guts and making a useful utility that I expected to be present for every system already (maybe I just need to read more man pages?)