Building a Simple Nonconcurrent TCP Port Scanner in F#

Building a Simple Nonconcurrent TCP Port Scanner in F#
Port scanning is one of the most fundamental techniques in networking and security. Before diving into high-performance concurrent scanners, it's useful to understand how a basic scanner works by checking one port at a time.
In this article, we'll build the foundation of a nonconcurrent TCP port scanner using F#.
What Is a Port Scanner?
A port scanner attempts to connect to ports on a target system and determines whether those ports are:
| State | Meaning |
|---|---|
| Open | A service is listening and accepts connections |
| Closed | No service is listening |
| Filtered | Traffic is blocked by a firewall or filtering device |
TCP ports range from:
1 - 65535
For demonstration purposes, we'll scan only:
1 - 1024
These are commonly known as the well-known ports.
Step 1: Generate Target Addresses
Before attempting any network connections, we need a way to generate:
hostname:port
combinations.
In F#, a simple loop can accomplish this.
open System
for i in 1 .. 1024 do
let address = sprintf "scanme.nmap.org:%d" i
printfn "%s" address
Example output:
scanme.nmap.org:1
scanme.nmap.org:2
scanme.nmap.org:3
...
scanme.nmap.org:1024
Understanding sprintf
The sprintf function formats strings similarly to C's printf.
let address = sprintf "scanme.nmap.org:%d" i
Here:
| Component | Purpose |
|---|---|
%d |
Integer placeholder |
i |
Current port number |
sprintf |
Returns a formatted string |
For port 80, the generated string becomes:
scanme.nmap.org:80
Step 2: Attempt a TCP Connection
A TCP scanner determines whether a port is open by trying to establish a connection.
Conceptually:
Connect to target port
|
+-- Success -> Open
|
+-- Error -> Closed/Filtered
In .NET, this is typically done using:
System.Net.Sockets.TcpClient
Minimal example:
let client = new System.Net.Sockets.TcpClient()
If the connection succeeds, the port is likely open.
Step 3: Always Close Connections
A successful connection consumes operating system resources.
Good network citizenship requires closing connections immediately after testing.
Conceptually:
client.Close()
Benefits:
- Releases socket resources
- Prevents connection leaks
- Reduces system overhead
- Mimics professional scanner behavior
Scanner Workflow
The complete nonconcurrent scanning algorithm is straightforward:
for each port
generate address
attempt TCP connection
if success
print OPEN
close connection
else
continue
Why Is It Called Nonconcurrent?
Only one port is tested at a time.
Port 1 -> wait
Port 2 -> wait
Port 3 -> wait
...
Port 1024
Advantages:
- Easy to understand
- Easy to debug
- Minimal code complexity
Disadvantages:
- Slow
- Network latency accumulates
- Does not scale to large scans
Modern scanners solve this using:
- Threads
- Async I/O
- Tasks
- Event-driven networking
Educational Value
Even though professional scanners use concurrency, a nonconcurrent scanner teaches several important networking concepts:
- TCP connection establishment
- Socket programming
- Port states
- Error handling
- Resource management
- Network reconnaissance fundamentals
Understanding this sequential model makes it much easier to appreciate how high-performance scanners achieve their speed.
Key Takeaways
- TCP scanners determine port availability by attempting connections.
- A loop can generate target addresses for thousands of ports.
sprintfprovides convenient address formatting in F#.- Successful connections should always be closed.
- Nonconcurrent scanners are simple but relatively slow.
- This approach forms the foundation for advanced concurrent port scanners.
A nonconcurrent scanner may not be the fastest tool in a security engineer's toolkit, but it provides an excellent introduction to how network discovery and TCP-based reconnaissance actually work under the hood.



