Building a TCP Port Scanner in F#

Introduction
One of the best ways to understand TCP networking is by building a simple port scanner.
A port scanner attempts to connect to remote TCP ports and determines whether they are available. While the implementation may be small, it exposes several important networking concepts:
- TCP connections
- Client-server communication
- TCP handshakes
- Open and closed ports
- Exception handling
- Socket programming
- F# pattern matching
This article explores how a simple F# program can test TCP port availability and introduces the exception-handling syntax commonly used in networking applications.
What Is a TCP Port?
A TCP port is a logical communication endpoint.
Servers listen on ports waiting for incoming connections.
Examples:
| Service | Port |
|---|---|
| HTTP | 80 |
| HTTPS | 443 |
| SSH | 22 |
| SMTP | 25 |
| FTP | 21 |
When a client wants to communicate with a service, it attempts to establish a TCP connection to the target port.
What Is Port Scanning?
Port scanning is the process of testing one or more ports to determine whether they are accepting connections.
A scanner typically reports:
| State | Meaning |
|---|---|
| Open | Service accepts connections |
| Closed | Service rejects connections |
| Filtered | Firewall blocks traffic |
| Unreachable | Host cannot be reached |
Port scanners are widely used in:
- Network administration
- Security auditing
- Asset discovery
- Troubleshooting
- Cybersecurity assessments
Understanding TCP Connections
Before data can be exchanged, TCP establishes a connection between client and server.
The client sends a connection request.
The server responds.
Once both sides agree, communication begins.
If the connection succeeds, the port is considered open.
If the connection fails, the scanner can infer that the service is unavailable or inaccessible.
Connecting with TcpClient
The .NET networking library provides the TcpClient class.
A connection can be created using:
use client =
new TcpClient("scanme.nmap.org", 80)
This statement attempts to connect to:
- Host:
scanme.nmap.org - Port:
80
If the connection succeeds, the object is created successfully.
A Minimal TCP Scanner
The entire scanner can fit into a few lines of code.
try
use client =
new TcpClient("scanme.nmap.org", 80)
printfn "Connection successful"
with
| :? SocketException ->
printfn "Connection failed"
The scanner attempts a TCP connection.
Success indicates an open port.
Failure generates a networking exception.
Why Error Handling Matters
Networks are unpredictable.
Many things can go wrong:
- Host offline
- Service unavailable
- Firewall blocking traffic
- DNS resolution failure
- Routing problems
Instead of crashing, the application should handle these failures gracefully.
That is where exception handling becomes important.
Understanding the try Block
The try keyword marks code that might fail.
Example:
try
connect()
Meaning:
Attempt this operation.
If an error occurs,
handle it below.
Networking operations are common candidates for try blocks because they depend on external systems.
Understanding the with Keyword
The with keyword begins exception handling.
Example:
try
connect()
with
Meaning:
If an exception occurs,
look for a matching handler.
The runtime compares the exception against each rule that follows.
Understanding the Pipe Operator
The pipe symbol introduces a pattern-matching case.
Example:
| pattern -> action
Each pipe represents a possible match.
Example:
with
| case1 -> ...
| case2 -> ...
| case3 -> ...
The first matching case executes.
Understanding the Type Test Operator
One of the most important operators in F# exception handling is:
:?
This performs a runtime type check.
Example:
:? SocketException
Meaning:
Is this exception a SocketException?
If yes, the match succeeds.
If no, F# continues searching for another handler.
Understanding SocketException
SocketException is a .NET exception used for networking errors.
Namespace:
System.Net.Sockets
Typical causes include:
| Error | Description |
|---|---|
| Connection Refused | Service not listening |
| Timeout | No response received |
| Host Not Found | DNS lookup failed |
| Network Unreachable | Routing issue |
| Connection Reset | Peer closed connection |
Because networking failures are common, SocketException appears frequently in TCP applications.
Understanding the Arrow Operator
The arrow operator is:
->
It means:
If this pattern matches,
execute the code on the right.
Example:
| :? SocketException ->
printfn "Connection failed"
Meaning:
If the exception is a SocketException,
print an error message.
Execution Flow
Let's walk through the scanner step by step.
Step 1
Attempt TCP connection.
new TcpClient(...)
Step 2
Connection succeeds.
Output:
Connection successful
or
Step 3
Connection fails.
A SocketException is thrown.
Step 4
The exception handler matches:
:? SocketException
Step 5
The failure message is displayed.
Connection failed
Why Port Scanners Use Exceptions
Port scanners depend heavily on connection outcomes.
Successful connection:
Port Open
Connection refused:
Port Closed
Timeout:
Port Filtered
Host unreachable:
Target Unreachable
Exceptions provide a simple way to classify these outcomes.
Real-World Applications
TCP scanning forms the foundation of:
- Vulnerability assessment
- Service discovery
- Asset inventory
- Penetration testing
- Network troubleshooting
- Security monitoring
Understanding how scanners work also helps administrators better understand firewall behavior and network exposure.
Key Takeaways
- TCP scanners determine port availability by attempting connections.
TcpClientprovides a simple way to establish TCP sessions.- Successful connections indicate open ports.
- Failed connections generate exceptions.
SocketExceptionrepresents common networking failures.tryidentifies code that may fail.withstarts exception handling.|introduces pattern-matching cases.:?performs runtime type testing.->specifies the action to execute after a successful match.- Concurrency is essential for high-performance scanning.
Conclusion
Building a TCP port scanner is an excellent introduction to networking and systems programming.
Even a small scanner demonstrates several foundational concepts, including TCP connections, port states, socket programming, exception handling, and pattern matching.
For F# developers, understanding how SocketException integrates with pattern matching provides a clean and expressive way to handle networking failures while building reliable network applications.



