Build a Port Scanner in Ruby

Share this article

zoll, schmugel, scanner,
Sometimes, we box ourselves up in our HTTP-only world. Sometimes, Rails eclipses all other facets of Ruby. In this article, we’ll build a simple port scanner in pure Ruby, using the sockets support that comes with the Ruby distribution. Port scanners aren’t typically things we expect Rubyists create; that’s the kind of stuff that the C people deal with. A lot of people are surprised when they find out that Metasploit , an amazing penetration suite, is actually written in Ruby. The truth is that Ruby has grown into an incredibly versatile language, making it a tool that can be used in a wide variety of things, including something as systems-y as a port scanner. Let’s dive right in!

The Basics

First of all, what is a port scanner? It is exactly what it sounds like; it takes in an IP address, and tells you which ports are “open”, i.e. on which ports the computer is ready to communicate. What is the point of scanning ports? Open ports can pose major security risks, especially if the administrator doesn’t know why exactly they have been left open. The program at the other end may be an ancient version of some server software that has a ton of publicized buffer overflow attacks, making the machine an easy target for attackers. So, knowing what ports you have open is important. Finally, how do we know if ports are open or not? There’s actually a ton of ways to do this, because TCP/IP (the protocol that computers usually use to talk to one another) provides a lot of different ways (all with different advantages and disadvantages) to check a port. The simplest one (and the one we will be using) is called “TCP connect scanning”. The idea is to try to connect to a host on a specified port, if the host responds, the port is open, otherwise it isn’t. This approach does have disadvantages. If you’re an attacker, the host will probably log the request. Secondly, it is much less efficient than some other methods (such as a TCP SYN scan). Our port scanner will employ a simple technique; it will take a range of ports and a host, then try to connect to the host on each of the given ports. Ports are open on whichever connections are successful. Let’s get into the code.

The Simple Method

We’re going to be using the socket library provided by Ruby in order to accomplish our ends. Here’s the code:
require 'socket' #socket library

def open_port(host, port)
  sock = Socket.new(:INET, :STREAM)
  raw = Socket.sockaddr_in(port, host)
  puts "#{port} open." if sock.connect(raw)

rescue (Errno::ECONNREFUSED)
  rescue(Errno::ETIMEDOUT)
end

def main(host, start_port, end_port)
  until start_port == end_port do
    open_port(host, start_port)
    start_port += 1
  end
end

main ARGV[0], ARGV[1].to_i, ARGV[2].to_i
As you can tell, the code is very simple. Before we go in to a bit of detail, you can run the scanner on your own machine:
ruby scanner.rb localhost 1 100
That will run the scanner on localhost (i.e. the machine you ran the code on), checking all ports from 1 to 100, inclusive. The code takes the address to scan as the first argument, the starting port as the second argument and the ending port as the third one and prints the ports that are open. Let’s breakdown the code to see how it does this. First of all, use the ARGV array to read the arguments off the command line. Pass these arguments into the main method, which then simply loops through the required ports and checks if they are open with the open_port method (which also prints the output). open_port functions by, as discussed, opening a connection to the machine we’re scanning at a specified port to see if it responds. But, this implementation has a few issues. It waits until a port is scanned before moving to the next one. However, it clearly doesn’t need to; the order that we check the ports in has no relevance to the results. So, let’s try writing an a version of the port scanner that doesn’t wait.

Taking It Another Step

How do we do this? We obviously need some way to delegate tasks so that they can be done outside the “normal” course of things, ie. we don’t have to wait around for them. This is usually called concurrency. There’s a few problems with doing concurrency in Ruby. First of all, the “official” (meaning, MRI) implementation of Ruby implements a system where separate “threads” (kind of like processes, except more lightweight) can’t run at the same time; they all run under one thread. That means that two things cannot occur at exactly the same moment. As such, we can’t scan two ports at once. However, this doesn’t mean that Ruby is useless in this case – if we split up our ports into different threads, those threads can still be scheduled to run while we are waiting on other sockets. How are we going to deal with all this threading? I’m personally a fan of the Celluloid library for Ruby, which uses the Actor model to make threading a breeze. In fact, I like it so much that I even wrote an article about it
, which you should probably read since I’ll be using Celluloid in the rest of this article. Here’s a very quick n’ dirty implementation that we’re going to improve on:
require 'celluloid'
require 'socket'

class ScanPort
  include Celluloid

  def initialize(port, host)
    @port = port
    @host = host
  end

  def run
    begin
      sock = Socket.new(:INET, :STREAM)
      raw = Socket.sockaddr_in(@port, @host)

    puts "#{@port} open." if sock.connect(raw)

    rescue
      if sock != nil
        sock.close
      end
    end

  end
end

def main
  host = ARGV[0]
  start_port = ARGV[1].to_i
  end_port = ARGV[2].to_i

  until start_port == end_port do
    sp = ScanPort.new start_port, host
    sp.async.run

    start_port += 1
  end
end

main
Wow, that seems quite complicated at first glance. Actually, we’ve reused a lot of code from our earlier version. First of all, we define a class called ScanPort which will be used to spawn “actors” (basically, objects in threads). Notice the include line within the class; this essentially makes the class an actor. We have the run method within the ScanPort class (I just chose to call it run; we could have called it anything we wanted) It does essentially the same thing as the openport method in the earlier version, except that it closes the socket immediately after checking the port. We do this so that the number of open sockets does not exceed the system limit. The main method is quite simple. The most important line is line 36 – notice we don’t call sp.run, instead, it is sp.async.run
. This calls the run method asynchronously, i.e. we move on without waiting for it to end. Another important point to note is that the code sample omits a lot of error handling for the sake of brevity and simplicity – it should definitely use some kind of supervision group (that’s a Celluloid concept). When run this gives roughly the same result as earlier. You might notice that it runs in about the same or more time than the synchronous version! The thing is, we are starting a thread for every single port in the range we provide – that takes up time and resources! What if, instead, we allotted each actor with only a range of ports to check? That would mean less threads, so less time taken starting up and shutting down threads! Let’s give it a go:
require 'celluloid'
require 'socket'

class ScanPort
  include Celluloid

  def initialize(start_port, end_port, host)
    @start_port = start_port
    @end_port = end_port
    @host = host
  end

  def run
    until @start_port == @end_port do
      scan @start_port
      @start_port += 1
    end
  end

  def scan(port)
      begin
        sock = Socket.new(:INET, :STREAM)        #build the socket
        raw = Socket.sockaddr_in(port, @host)

        puts "#{port} open." if sock.connect(raw)
      rescue
        if sock != nil
          sock.close
        end
      end
    end
end

def main
  host = ARGV[0]
  start_port = ARGV[1].to_i
  end_port = ARGV[2].to_i

  segment_size = 100

  until start_port >= end_port do
    sp = ScanPort.new start_port, start_port + segment_size, host
    sp.async.run

    start_port += segment_size
  end
end

main
The code is largely the same, however, we have added a segment_size variable, which you can change depending on how many ports you want each actor to process. Setting this number too high will mean that there will be little concurrency, setting it too low will mean that a lot of time is spent just creating and destroying threads. I ran this from 1 to 2000 ports on my machine and it turned out roughly 50% of the real time of the synchronous version. Obviously, there’s a ton of factors that affect this, but that’s the general idea. If you want your code to run with “real” concurrency, i.e. with threads running at exactly the same time as others, you’ll have to pick an implementation of Ruby that doesn’t have the “everything under single thread” issue. JRuby is a popular choice.

Wrapping it Up

Port scanners are incredibly interesting pieces of software. Hopefully, you enjoyed creating a version of a port scanner (albeit quite simple) in Ruby.

Frequently Asked Questions about Building a Port Scanner in Ruby

What is a port scanner and why is it important?

A port scanner is a software application designed to probe a server or host for open ports. These open ports are doorways into a computer’s systems and are often used by hackers to gain unauthorized access. Therefore, a port scanner is an essential tool in network security as it helps identify open ports and vulnerabilities that could be exploited by attackers.

Why should I use Ruby to build a port scanner?

Ruby is a high-level, interpreted programming language that is known for its simplicity and productivity. It has a clean syntax that is easy to read and write, making it a great choice for building a port scanner. Moreover, Ruby has a rich set of built-in functions and libraries, which can simplify the process of creating complex network applications like a port scanner.

How does a port scanner work in Ruby?

A port scanner in Ruby works by establishing a connection to a specific port on a host or server. If the connection is successful, it means the port is open. If not, the port is closed. This is done using Ruby’s Socket library, which provides methods for creating and manipulating network sockets.

Can I use a port scanner to scan multiple hosts at once?

Yes, you can. By using Ruby’s threading capabilities, you can create a multi-threaded port scanner that can scan multiple hosts simultaneously. This can significantly speed up the scanning process, especially when scanning a large network.

What is Nmap and how does it compare to a Ruby port scanner?

Nmap, or Network Mapper, is a free and open-source tool for network discovery and security auditing. It can be used to detect hosts and services on a computer network, thus creating a “map” of the network. While Nmap is a powerful tool, a Ruby port scanner can be a simpler and more customizable alternative, especially for those already familiar with Ruby programming.

How can I protect my network from port scanning?

There are several ways to protect your network from port scanning. One is to use a firewall to block incoming connections from unknown sources. Another is to regularly update and patch your systems to fix any known vulnerabilities. Regularly monitoring your network for unusual activity can also help detect and prevent port scanning.

Can I use a port scanner to scan for specific services?

Yes, you can. By specifying the port numbers associated with specific services, you can use a port scanner to check if those services are running on a host or server. For example, you can scan port 80 to check if a web server is running, or port 22 to check for an SSH server.

Is it legal to use a port scanner?

The legality of using a port scanner can vary depending on your location and the intent behind the scanning. In general, it is legal to use a port scanner for legitimate, authorized purposes, such as network security testing. However, using a port scanner to find vulnerabilities with the intent to exploit them is illegal.

How can I optimize the performance of my Ruby port scanner?

There are several ways to optimize the performance of your Ruby port scanner. One is to use threading to scan multiple ports or hosts simultaneously. Another is to adjust the timeout value to reduce the time spent waiting for a response from each port. You can also use a technique called “port knocking” to scan for a sequence of ports in a specific order.

Can I integrate my Ruby port scanner with other security tools?

Yes, you can. Ruby’s flexibility and interoperability make it easy to integrate your port scanner with other security tools. For example, you can use Ruby’s Net::HTTP library to send the results of your port scan to a web server, or use the JSON library to format the results for easy parsing by other tools.

Dhaivat PandyaDhaivat Pandya
View Author

I'm a developer, math enthusiast and student.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week