Skip to main content

Command Palette

Search for a command to run...

Building a TCP Port Scanner in F#

Updated
6 min read
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.
  • TcpClient provides a simple way to establish TCP sessions.
  • Successful connections indicate open ports.
  • Failed connections generate exceptions.
  • SocketException represents common networking failures.
  • try identifies code that may fail.
  • with starts 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.