Concurrent Port Scanning in F# with Tasks

Search for a command to run...

No comments yet. Be the first to comment.
Modern software systems often separate control logic from high-performance execution logic. This design is common in networking, distributed systems, operating systems, storage engines, and embedded s

Introduction Engineering software often combines multiple programming languages to leverage their individual strengths. A common approach is to implement computational algorithms in native C or C++ wh

Deadlocks are one of the most common synchronization problems encountered in operating systems and concurrent programming. Although the concept is frequently introduced in textbooks, observing it insi

Memory and resource allocation are fundamental operations inside the Linux kernel. Whether assigning device IDs, managing CPU masks, allocating interrupt vectors, or tracking hardware resources, the k

The Linux scheduler is one of the most important components of the operating system. Every running program, background service, and kernel thread eventually interacts with the scheduler. In this artic

Scanning one TCP port at a time is simple, but it is inefficient. Network operations spend most of their time waiting for remote hosts to respond. Instead of scanning ports sequentially, we can launch multiple connection attempts concurrently and significantly reduce the total scan time.
In .NET, the Task Parallel Library (TPL) provides a lightweight mechanism for running large numbers of asynchronous operations without creating hundreds or thousands of operating system threads.
A sequential scanner performs the following workflow:
Scan Port 1
Wait
Scan Port 2
Wait
Scan Port 3
Wait
...
This approach wastes time because the CPU sits idle while waiting for network responses.
A concurrent scanner launches many connection attempts simultaneously:
Port 1 ─┐
Port 2 ─┼─> Running Together
Port 3 ─┤
...
Port N ─┘
The result is a much faster scan.
let scanPort port =
task {
try
use client = new TcpClient()
do! client.ConnectAsync("scanme.nmap.org", port)
printfn "%d open" port
with
| _ -> ()
}
| Statement | Purpose |
|---|---|
task {} |
Creates an asynchronous Task |
TcpClient() |
Creates a TCP client |
ConnectAsync() |
Attempts a non-blocking connection |
printfn |
Displays open ports |
try/with |
Ignores failed connections |
If the connection succeeds, the port is considered open.
[1 .. 1024]
|> List.map scanPort
|> Task.WhenAll
|> fun t -> t.Wait()
This compact pipeline performs three important operations.
[1 .. 1024]
Creates a list of ports from 1 through 1024.
List.map scanPort
Transforms:
[1;2;3;...;1024]
into:
[Task1;Task2;Task3;...;Task1024]
Each task represents one asynchronous connection attempt.
Task.WhenAll
Combines all scan tasks into a single master task.
fun t -> t.Wait()
Blocks until every scan operation finishes.
Ports List
|
v
Create Scan Tasks
|
v
Task1 Task2 Task3 ... Task1024
|
v
Task.WhenAll
|
v
Single Master Task
|
v
Wait()
Creating 1024 operating system threads would be expensive.
Tasks provide:
For network-heavy workloads such as port scanners, Tasks are typically the preferred solution in modern .NET applications.
Concurrent scanning demonstrates a fundamental principle of network programming: don't wait on one connection when you can wait on many simultaneously.
Using TcpClient.ConnectAsync() together with Task.WhenAll() allows F# developers to build scalable network tools with surprisingly little code while taking advantage of the .NET runtime's efficient asynchronous I/O infrastructure.