What Is a Port? TCP vs UDP and Connection Faults
What a port number does, how TCP and UDP differ, how to check open ports, and, when a connection won't form, which side the fault is on and how to prove it.
- What Is a Port? Dozens of Doors at One Address
- The Five-Tuple Identifies a Connection, Not a Person
- Three Port Ranges, and Why They’re Split
- Where Does the 1024 Line Come From?
- Checking Open Ports: Which Ports Are Listening on Your Machine?
- TCP vs UDP: Waiter Service and Self-Service
- What Is TCP? Let It Be Late, but Complete
- The Three-Way Handshake: The Arithmetic of Confirmation
- What Is UDP? What Arrives Late Was Already Useless
- Is There a Third? QUIC and Reliability Built on UDP
- Which Protocol Chooses TCP or UDP, and Why?
- When a Connection Won’t Form: Three Different Silences
- First, Knock on the Door That Should Be Closed: Port 22
- Now Look at the Door That Should Be Open: Port 53
- Five Scenarios: Which Rule Makes You Wait How Long?
- A Connection Timeout Misleads: Same Duration, Two Different Events
- Same Rule, Two Operating Systems, a 21-Second Difference
- The Reverse Under Pure Silence: Where Linux Waits Longer
- Dual-Ended Capture: How “We’re Not Getting Anything” Is Disproved
- BEFORE and AFTER: The Only Thing That Changes Is One Line
- Where You Capture Decides What You Prove
- Capture Commands
- The Asymmetry of an Edge Capture
- What Does Silence Mean in UDP?
- Conclusion: What Ends the Argument Is the Packet Itself
Two organisations argue over a connection that won’t form. One side says “we’re not getting anything from you,” the other says “we sent it, it’s your firewall.” Both are looking at their own screen, both are honest, and neither can prove it. This article is about the thing that ends that argument. First we build up what a port is, why it’s split into three ranges, and what TCP and UDP actually do differently. Then we get our hands dirty: we measure the same connection attempt under five different firewall rules and put side by side what the stopwatch shows and what it hides. This article is part of the network segmentation guide, on the fault-finding side.
Up front: all the measurements here were taken in our own lab, and the outputs are real. The method itself wasn’t born at a desk either; it settled in as the only way through after enough of the same deadlock with different organisations’ teams.
Two shortcuts for the impatient: to see which ports are listening on your own server, start there; if you’re chasing why a connection won’t form, go straight to the fault-finding section.
What Is a Port? Dozens of Doors at One Address
A port number is a 16-bit number that separates traffic arriving at the same IP address by which service it belongs to, and it takes a value between 0 and 65535. The IP address gets the packet to the right machine; the port number delivers it to the right program inside that machine.
Picture a large food court. The building has a single address, you come in the door, and inside numbered counters are lined up. The address gets you to the building; but you get the flatbread from counter 3 and the coffee from counter 7. When you go to the wrong number, the building isn’t wrong, the counter is. The port number is exactly that number.
The mechanism works like this: when a packet reaches the machine, the operating-system kernel looks at the destination port in the header, checks whether a socket is listening on that number, and if so hands the packet to that socket. If not, the packet never reaches an application; what happens then is the subject of the article’s second half.
There’s a detail most explanations skip, and reading all the measurements ahead depends on it: a connection has two ports, not one. The destination port the server listens on is fixed (22, 443), but the client also picks itself a temporary source port. What makes a connection unique is this five-tuple:
- transport protocol (TCP or UDP)
- source IP address
- source port
- destination IP address
- destination port
The first item is left out of most explanations but it’s required: TCP 443 and UDP 443 are two different connections even though the port number is the same, and the operating system doesn’t mix them up. You’ll see the practical consequence of this later in the article.
The reason hundreds of people can connect to the same server’s 443 port at once is also this five-tuple: the destination is the same for everyone, the source port is different for everyone. Many people can order from one counter at the same time, because what separates the orders isn’t the counter’s number, it’s who placed them.
The Five-Tuple Identifies a Connection, Not a Person
This distinction is decisive for diagnosis: the five-tuple makes a connection unique at that moment, but doesn’t say who made it. The same five-tuple can belong to a completely different session two hours later, because the operating system returns the source port to the pool when the connection ends and hands it out again.
So the answer to the question of identity sits in other layers:
- Time. The only thing that makes a record meaningful in retrospect is the timestamp. A connection record with no time carries no evidential value, because which session it belongs to is unknown.
- The NAT translation record. On outbound traffic the address the other side sees is your public address. Which internal address it corresponded to at that moment lives only in the record of the device doing the translation.
- MAC address and DHCP lease. The MAC is not part of the connection’s five-tuple: it lives at the second layer and changes at every router hop, so it isn’t carried end to end. But the answer to “which physical device was that internal IP at that hour” is in the DHCP lease record, and that holds the MAC. Why these two addresses live separately, and which protocol translates between them, is covered in the ARP guide.
Put the three together and the chain closes: physical device → internal address → translation → external address and port → time. If one link is missing the chain breaks, and what’s left drops into “exists but useless.”
Keep the source port in the back of your mind for now. Shortly we’ll read from that number both how many times a fault was retried and whether those retries belong to a single connection.
Three Port Ranges, and Why They’re Split
Port numbers aren’t handed out at random; they split into three ranges, and that split carries a boundary of authority.
| Range | Name | Who uses it |
|---|---|---|
0-1023 | Well-known ports | Standard services: 22 SSH, 53 DNS, 80 HTTP, 443 HTTPS |
1024-49151 | Registered ports | Ports applications registered with IANA |
49152-65535 | Ephemeral ports | Source ports clients pick per connection |
0-1023 range is only 1.6% of the whole number space, yet every service you recognise sits there.The first range has a special rule: on Linux and similar systems, listening on a port below 1024 requires administrator privilege. This one-line constraint looks at first like a bureaucratic detail. It isn’t.
To see why it exists, imagine it gone. If an ordinary user account on a shared server could start its own program listening on 443, everyone reaching that machine for HTTPS would land on that program. Showing a page asking for a username and password would need no extra privilege. The privileged range is the technical way of saying “on this machine, only the person who runs the machine may announce the standard service.”
The third range is the quiet side. When you open a browser or start an SSH session, your operating system gives you a source port for that connection and takes it back when the connection ends. In the captures ahead you’ll see numbers like 54799, 60360, 41216; all from this range, all temporary numbers reserved for a single connection.
Where Does the 1024 Line Come From?
The number looks chosen for being round, but there’s a concrete security design behind it, and what’s instructive today is which assumption that design rested on.
The privileged-port idea came with BSD Unix in the early 1980s (IPPORT_RESERVED). The reason was the era’s remote-access commands, rlogin and rsh. These tools verified who the user on the other end was not with a password but with the port number the connection came from. The logic: if a connection came from a port below 1024, only a program running with root privilege could have opened that port; so the administrator of the machine on the other side vouches for this connection, and its “I am this user” claim can be trusted. An ordinary user couldn’t use a privileged port, so couldn’t impersonate anyone.
Thanks to that assumption, rlogin could in some configurations open a shell without ever asking for a password. Today that sentence is chilling, but the network of the era was the inside of a university building: every machine trusted, root in few hands, nobody connecting from outside. The BSD team knew this was a working shortcut, not a clean solution.
When that assumption collapsed, the constraint it left behind is still in force. The finest example of how the numbers were handed out is 22. When Tatu Ylönen wrote SSH in 1995, he wanted the gap between the two protocols he meant to replace: FTP 21, Telnet 23. He wrote IANA an email and got the answer the next day. So the number listening on millions of servers today has no technical meaning; it was just the empty slot between its two neighbours.
Checking Open Ports: Which Ports Are Listening on Your Machine?
You’ve learned the ranges; the next question is which of them are actually in use on your own server. The answer is one command, and reading the result matters more than running it.
To list listening ports on Linux:
ss -tulnOn Windows there are two ways, both giving the same information:
Get-NetTCPConnection -State Listen | Select-Object LocalAddress, LocalPort, OwningProcessnetstat -an | findstr LISTENINGThe place to look in the output isn’t the port number, it’s the address to its left. There are three possibilities, and the three mean three different things:
0.0.0.0or[::]: the service is listening on all of the machine’s network interfaces. This is the only case that’s a candidate for access over the network.127.0.0.1or::1: the service is listening only to the machine itself. It can’t be reached from outside in any way; opening the firewall changes nothing, because the problem isn’t in the firewall.- A specific IP (like
192.168.1.30): the service comes only from that interface.
The first column of ss -tuln also says whether it’s tcp or udp. That distinction is the subject of the next section, and far more decisive than it looks.
TCP vs UDP: Waiter Service and Self-Service
In the output just now, every line began with tcp or udp. That one word determines how the service talks.
The difference between TCP and UDP in one sentence: TCP first sets up a connection with the other side and confirms delivery; UDP neither sets up a connection nor confirms. The speed difference is the result of that choice, not its cause.
Back to the food court. UDP is self-service: you leave your order at the counter and walk off without looking back. You don’t know whether anyone’s on the other side, whether the order was heard. It’s fast, because it waits for nothing.
TCP is waiter service. You sit, the waiter comes, the order is taken, and every step is mutually confirmed. You talk knowing there’s someone across from you.
To see why the choice is made, take the two one at a time, because the difference isn’t “one reliable, the other fast” the way most sources put it.
What Is TCP? Let It Be Late, but Complete
TCP (Transmission Control Protocol) is the transport protocol that guarantees data is delivered complete and in order. It does this with three mechanisms: it numbers every byte, has the receiver confirm what was received, and resends what wasn’t confirmed.
You see why it bears this cost when downloading a file. When the network slows, the download stretches; a ten-second job becomes forty. But the file arrives complete in the end. If a packet was lost on the way, TCP re-requested it and you never even noticed. Why you didn’t notice matters: if a few bytes in the middle of a spreadsheet arrive missing, that file is corrupt and won’t open. So waiting there is an acceptable price; missing data is not.
TCP’s entire design rests on that choice: it sacrifices time, it protects integrity.
The Three-Way Handshake: The Arithmetic of Confirmation
What TCP means by “confirming” is concretely this: a connection is set up in three steps before data flows, and those steps are called the three-way handshake.
- The client sends a
SYN: “I want to open a session, my counter starts at this number.” - The server answers with
SYN-ACK: “Heard and accepted, my counter starts at this number too.” - The client sends
ACK: “Got your number too, let’s begin.”
The “counter” here isn’t a metaphor, it’s a field that really sits in the packet: it’s called the sequence number. Two of its properties make reading the rest of this article easier. First, TCP numbers bytes, not packets; that’s why the numbers look so large. Second, the counter doesn’t start at zero: every connection picks a random starting number for itself. That way a delayed packet from an old connection can’t slip into a new session, and an outsider can’t guess the number and inject a packet.
We give you this detail now for a practical reason: most of the proofs ahead look at this number. Seeing the same sequence number in two separate captures is the most definitive way of saying “these two records belong to the same packet”; and at the end of the article, that’s exactly what ends the argument between two organisations.
No data goes until the third step completes. In our lab these three steps look like this, a real capture taken between two machines:
IP 192.168.1.30.41216 > 192.168.1.15.9999: Flags [S], seq 3290926036
IP 192.168.1.15.9999 > 192.168.1.30.41216: Flags [S.], seq 2464608652, ack 3290926037
IP 192.168.1.30.41216 > 192.168.1.15.9999: Flags [.], ack 1Three things to read in this capture:
- The
Flagsfield says which step of the handshake you’re at.[S]is justSYN,[S.]isSYN-ACK(the dot shows theACKflag),[.]meansACKonly. - The
seqnumbers start independently on the two sides. The client says3290926036, the server opens its own counter at2464608652. Each direction keeps its own counter. ack 3290926037is one more than the client’s number. This means “I’ve got everything up to your such-and-such number, this is the next one I expect.” That’s the arithmetic of confirmation: TCP numbers every byte, and the other side reports up to which number it’s received.
The server’s answer came in 37 microseconds. This number will be useful later, because knowing how fast a working connection sets up is a precondition for interpreting one that doesn’t.
The analogy tells a small lie here, so let’s say it: a waiter can forget the order but comes back to the table and asks “what did you say.” A lost packet in TCP is asked of no one; the sender notices the confirmation didn’t come and resends the same packet. The difference matters, because shortly we’ll see the same packet go five times in a fault, and that’s not an error, it’s TCP doing exactly what it was designed to.
What Is UDP? What Arrives Late Was Already Useless
The three-step setup you just saw never happens in UDP. UDP (User Datagram Protocol) is the transport protocol that sends data without setting up a connection and without confirming delivery. It doesn’t resend a lost packet, and it doesn’t put things in order.
At first glance this looks like a shortcoming. It isn’t, and you probably lived the reason this week: in an online meeting the other side’s audio cuts for a moment, a syllable drops. That syllable’s audio packet was lost on the network. Now think what TCP logic would do there: the packet is re-requested, the request goes out, the copy comes back, half a second passes. But in that half second the conversation has moved on. The moment to play that returned audio packet is long gone; it’s useless now.
The difference sharpens right there: a byte lost from a downloaded file is still valuable, a lost audio packet is not.
So UDP not resending isn’t a weakness, it’s the right call. Trying to make up the loss doesn’t fix a voice call, it breaks it: the conversation would have to stop and wait on every loss.
| Property | Downloading a file | Joining a voice meeting |
|---|---|---|
| Value of the data | independent of time | time-bound |
| If a packet is lost | re-requested | skipped, not made up |
| Cost of slowness | the wait grows, result unchanged | that moment doesn’t return |
| Right choice | TCP | UDP |
From here comes the shortest form of the difference, and the sentence to remember: the question isn’t “which is reliable,” it’s “is this data still useful if it arrives late.” If yes, TCP; if no, UDP.
Is There a Third? QUIC and Reliability Built on UDP
Putting TCP and UDP side by side, the first question is: isn’t there something in between, do we have to pick one? There is, and you’re probably using it while reading this page. It’s called QUIC, and it isn’t a port, it’s a third transport protocol.
The shortest way to understand QUIC: UDP not making up loss doesn’t mean a protocol running on top of it can’t. QUIC builds its own loss management on top of UDP datagrams. So it takes UDP’s speed and adds the reliability itself.
And it does it better than TCP. TCP keeps a single order: when a packet is lost, everything behind it, even if unrelated, waits until that packet arrives. QUIC runs loss management separately for each stream, so a lost packet holds up only its own stream and doesn’t touch its neighbours.
The distinction that follows is subtle but corrects this article’s most-misunderstood sentence: “UDP is unreliable” is true of the protocol itself, not of everything built on top of it.
Its corporate-network face is directly this article’s subject. QUIC uses the 443 UDP port, and many organisations allow TCP 443 outbound while keeping UDP 443 closed. This is often not an oversight but a deliberate security decision. Corporate security products explicitly recommend blocking QUIC, because encrypted transport over UDP can’t be inspected by a proxy: content filtering, category policies and data-loss prevention can’t be applied while QUIC is in play. So the organisation can’t see its own rule being bypassed. Closing the traffic here isn’t a gap, it’s the price paid to keep visibility.
But the decision to close and the diagnosis of a fault often don’t sit at the same table, and the problem the article has described from the start repeats right here. When it’s closed no fault appears: the browser quietly falls back to the older version, everything keeps working, and all that’s left is an unexplained slowness, with no error message. The security team that set the policy knows this; the person investigating the slowness often doesn’t. If you’re curious which version your own network uses, the state of UDP 443 in the firewall rules is a good place to start.
Which Protocol Chooses TCP or UDP, and Why?
Now that you’ve seen the mechanism, a protocol’s choice between the two won’t look arbitrary; that choice gives away at which moment of life the protocol steps in.
Why does DHCP use UDP? Because at the moment DHCP speaks, the client has no IP address yet. A TCP handshake needs addresses on both sides; a device with no address can’t start the three-way handshake. DHCP’s job is precisely to get that address, so it can’t pick a transport it can’t meet the precondition of.
Why does DNS mostly use UDP, sometimes TCP? A typical DNS question and answer fit in one packet. Setting up and tearing down a three-way handshake for such an exchange produces more traffic than the data carried. But when the answer grows (a zone transfer, long record sets), one packet isn’t enough and DNS switches to TCP. The full picture of that name-resolution chain is in the DNS guide.
Why does NTP use UDP? In time synchronisation the thing being measured is the delay itself. If connection setup and retransmission logic get in the way, the measured delay carries the protocol’s own contribution too.
The same logic holds for the whole list: a directory query wants a reliable, ordered channel and uses TCP; a one-shot status notification uses UDP. Which protocol runs on which port and transport is a table of its own.
Up to here everything described a correctly configured world. Now we come to the real part: with the right port, the right protocol and a working service, what happens when the connection still won’t form?
When a Connection Won’t Form: Three Different Silences
The failure of a connection attempt isn’t one kind. Depending on the other side’s firewall configuration, at least three separate things happen, and all three look like “it didn’t work” to the user.
You can reproduce this on your own machine in a minute, no lab needed. What makes the experiment meaningful is that the target and the two ports are chosen deliberately, so let’s set that up first.
Target 8.8.8.8: the address of Google’s public DNS service. Not a single machine but a cluster spread around the world; 8.8.8.8 is that cluster’s door number. The reason we chose it is simple: anyone on the internet can reach it, so you can reproduce the result exactly.
First, Knock on the Door That Should Be Closed: Port 22
Port 22 belongs to SSH and is a management door. Someone who gets into a server through this port lands at the command line: changes the config, stops the service, adds users. So what we’re about to do is like a stranger from outside trying to sign in as an administrator on Google’s server. We know from the start no organisation leaves this door open to the internet; the point of the experiment is to record what a closed door looks like.
Test-NetConnection 8.8.8.8 -Port 22WARNING: TCP connect to (8.8.8.8 : 22) failed
ComputerName : 8.8.8.8
RemoteAddress : 8.8.8.8
RemotePort : 22
InterfaceAlias : Wi-Fi
SourceAddress : 192.168.1.115
PingSucceeded : True
PingReplyDetails (RTT) : 12 ms
TcpTestSucceeded : FalseWhat we expected happened: TcpTestSucceeded : False. But the instructive part isn’t the failure, it’s the line next to it.
PingSucceeded : True and TcpTestSucceeded : False sit side by side in the same output. They don’t contradict each other, they say different things: the machine is up and answering you (the ping came back in 12 ms, so the packet made the round trip), but the door you asked for is closed to you. That’s exactly why one of the most common sentences in the field, “I can ping it, the server’s up,” proves nothing: ping reaches the machine, not the service.
There’s one more thing the screen doesn’t show: this command didn’t answer at once, it kept you waiting about 22 seconds. That wait is the subject of the sections that follow.
Now Look at the Door That Should Be Open: Port 53
Port 53 belongs to DNS, and it’s the reason this server exists. Google offers this address to the world as a DNS service; with 53 closed there would be no service left. So unlike the door we just tried, this one has to be open to everyone. We run the same command, against the same address, changing only the port number.
Test-NetConnection 8.8.8.8 -Port 53ComputerName : 8.8.8.8
RemoteAddress : 8.8.8.8
RemotePort : 53
InterfaceAlias : Wi-Fi
SourceAddress : 192.168.1.115
TcpTestSucceeded : TrueThis time TcpTestSucceeded : True, and the answer came back in the blink of an eye.
Put the two outputs side by side and three things become clear:
- Same machine, two different answers. The address didn’t change, the distance didn’t change, the only thing that changed was the port number. So what decides the result isn’t where the server is, it’s what’s allowed at that door.
- The time difference is more than a thousandfold.
53came back instantly,22kept you waiting about 22 seconds. A closed door doesn’t just say “no”; how long it takes to say no is information too, and most of the rest of this article is built on it. - The successful test has no
PingSucceededline at all. WhenTest-NetConnectionmanages to connect it doesn’t print the ping details, because they’re no longer needed: the connection already formed. If the two outputs look different on your own screen, that’s not an oddity, it’s how the tool behaves.
This is exactly where the real problem in the field begins. You know the packet sent to port 22 was swallowed. You don’t know who swallowed it. Did Google close it, did a device in between drop it, did your own organisation’s firewall block it? Nothing on the screen separates those three.
Five Scenarios: Which Rule Makes You Wait How Long?
To separate them we go to the lab. The setup is plain: there are two machines, one the client trying to connect, the other the target server. On the server we change a single setting and try the same connection over and over, so we know the only thing affecting the result is that setting.
The setting we change: a firewall can do one of three things to an incoming packet.
- Pass it. The packet reaches the service, the connection forms.
- Reject it openly. It sends back an answer, “there’s no such thing here.” It can give this answer in two different forms, and I measured them separately: with TCP’s own reset signal (
reject with tcp reset) or with the network layer’s error message (reject, that is, ICMP unreachable). - Drop it silently. It throws the packet away and gives no answer. Its name is
drop.
I measured these three, plus “no service at all” and “service running,” as five separate scenarios.
I ran the measurement not with Test-NetConnection but with a single raw connection request. The reason matters: ready-made tools retry a few times on their own when they fail, so the time you see on screen includes the tool’s own patience. I wanted to measure the behaviour of the protocol, not the tool.
| What’s on the server | How long it took | The result on screen |
|---|---|---|
Service running (22) | 0 ms | connected |
| No service, no rule | 2.158 s | connection refused |
Rule: open reject with RST | 2.132 s | connection refused |
Rule: drop, swallow silently | 21.165 s | timeout |
Rule: reject, reject with ICMP | 21.170 s | timeout |
Three things read out of the table, and all three are useful in the field:
- Both accept and reject come at once. A running service connected in 0 milliseconds, a closed port gave a clear answer in 2 seconds. Fast failure is not bad news: it’s proof the packet reached the other machine.
- A long wait means one thing: no answer. 21 seconds is not the other side saying “no,” it’s the other side saying nothing.
- The difference between
dropandrejectis 5 milliseconds. So to someone watching the stopwatch, these two are the same fault.
The third point looks innocent but it’s the article’s turning point.
A Connection Timeout Misleads: Same Duration, Two Different Events
drop and reject are not the same thing. drop throws the packet away silently; the other side gets no answer. reject sends an answer: “there’s no such port here.”
So one is silent, the other speaks. On the network these are two entirely different behaviours.
Now look at the durations we measured: both took about 21 seconds. The exact values are 21.165 and 21.170 seconds, a 5-millisecond difference. That’s as small a deviation as you’d already see running the same test twice in a row; it’s within the measurement’s own margin of error.
The result: the stopwatch can’t tell these two apart. One was silent, the other spoke, but on screen both looked the same.
Look at the packet capture and it’s nothing like that. The receiver-side captures of the same two attempts:
# under the drop rule
00:00:00.000000 In IP 192.168.1.115.54821 > 192.168.1.15.9999: Flags [S], seq 2794383364
00:00:01.010682 In IP 192.168.1.115.54821 > 192.168.1.15.9999: Flags [S], seq 2794383364
00:00:02.005321 In IP 192.168.1.115.54821 > 192.168.1.15.9999: Flags [S], seq 2794383364
00:00:04.014247 In IP 192.168.1.115.54821 > 192.168.1.15.9999: Flags [S], seq 2794383364
00:00:08.012780 In IP 192.168.1.115.54821 > 192.168.1.15.9999: Flags [S], seq 2794383364# under the reject rule
00:00:00.000000 In IP 192.168.1.115.54799 > 192.168.1.15.9999: Flags [S], seq 2738215150
00:00:00.000042 Out IP 192.168.1.15 > 192.168.1.115: ICMP tcp port 9999 unreachable
00:00:01.009350 In IP 192.168.1.115.54799 > 192.168.1.15.9999: Flags [S], seq 2738215150
00:00:00.000070 Out IP 192.168.1.15 > 192.168.1.115: ICMP tcp port 9999 unreachableFour things to read in the two captures:
- The
00:00:...at the start of each line isn’t a clock, it’s the time since the previous packet. So the line reading00:00:01.010682says: this packet was sent 1 second after the one before it. (I took the capture withtcpdump -ttt; the default shows the time of day there.) Underdropthese gaps double, 1, 2, 4, 8 seconds: TCP doesn’t give up when it gets no answer, it retries at increasingly sparse intervals. - The source port is the same on all five lines (
54821,54799). So these aren’t five separate connection attempts, they’re the first step of a single connection repeated five times. - The
seqnumber is the same on all five lines too. The same packet is resent, no new one is produced. This is the proof that clinches the second point. - The real difference is in the second capture: the receiver sent an ICMP answer within 40 to 70 microseconds of each
SYN. So the other side didn’t stay silent, it clearly said “there’s no such port.” The protocol that produces this answer, the other message types it carries, and its counterparts inpingoutput are covered in the ARP and ICMP material of the network segmentation guide.
A question arises here, and its answer is this article’s most practical finding: if the other side answered, why did the client wait 21 seconds?
Same Rule, Two Operating Systems, a 21-Second Difference
The answer is in the client’s operating system. I tried the same rule, on the same server, this time from a Linux client:
Target: 192.168.1.15:9999, rule: reject (ICMP unreachable)
Windows client : 21.170 s → TimedOut
Linux client : 0 ms → ConnectionRefusedSame server, same firewall rule, same port. One client waits 21 seconds, the other gets an answer instantly. Windows’s TCP stack disregards the ICMP “port unreachable” message that arrives during connection setup and keeps retrying; Linux ends the connection the moment it gets the message.
This behaviour sits in a grey area of the standards, and the standard itself says it’s grey. RFC 1122 section 4.2.3.9 treats a port-unreachable message as a “hard error” and recommends ending the connection; section 3.2.2.1 of the same document treats it as functionally equal to RST and makes it mandatory. RFC 5927 explicitly writes that these two clauses conflict and calls the situation an “ambiguity.”
Does the common flexibility in implementations explain it? No, and the difference is here. The flexibility RFC 5927 documents is defined only for established connections (ESTABLISHED and later). The case we measured is the moment the connection is not yet established, that is, outside that list.
In the field the counterpart is this: the two sides live the same fault differently. The person on the Windows side sees a 21-second freeze and says “nothing is coming back.” The person on the Linux side gets an instant refusal and says “connection refused, there’s no service on the other side.” Both are honest, both are right on their own screen, and both are describing the same event. That’s exactly where the argument locks up.
What the same stack does against RST confirms this. Make the firewall send RST instead of ICMP and the Windows client returns ConnectionRefused, and the retry interval drops from exponential backoff to a fixed half-second. So the problem isn’t failing to get an answer, it’s which answer it listens to.
The Reverse Under Pure Silence: Where Linux Waits Longer
The reject result has a mirror image, and it flips the intuition. Under reject there’s an ICMP message, and the client’s OS decides who’s faster, with Linux winning because it acts on that message. Under a pure drop there is no message to act on: the client just keeps retransmitting its SYN until its own retry policy runs out, and there the order reverses.
The Windows side is the measurement above: against a silent drop it gave up after 21 seconds, retrying at 1, 2, 4 and 8 seconds. The Linux sender in the same lab retransmitted differently: its first five SYNs went out about a second apart, and only then did the gap start doubling. The reason is in two kernel settings, read on that machine: net.ipv4.tcp_syn_retries = 6 and net.ipv4.tcp_syn_linear_timeouts = 4 (the second exists from Linux 6.7). With those defaults a Linux client keeps trying for roughly two minutes before it gives up, not 21 seconds.
So the same firewall behaviour, seen through the same “nothing came back” symptom, is a 21-second freeze on one desk and a freeze of about two minutes on the next. Put the two findings together and the rule is complete: under reject Linux is the fast one, under drop it’s the slow one, and neither the stopwatch nor the operating system tells you which rule you hit. Only the capture does. (Both retry settings are tunable, so the exact numbers vary by build; what doesn’t vary is that the timeout reflects the client’s retransmission policy, not the server’s answer.)
The practical rule that comes out of this: don’t leave diagnosis to the stopwatch. The duration only tells you “no answer came,” when an answer may well have come.
Dual-Ended Capture: How “We’re Not Getting Anything” Is Disproved
We saw the stopwatch isn’t enough; so how is the diagnosis done when two separate organisations are involved? There it stops being technical and turns into a diplomatic process. Working with a different company, the answer from the administrator or network lead on the other side is often one sentence: “there’s no problem on our end, it’s on yours.” When you have nothing to disprove it, the argument locks up there.
What disproves it is this: capturing the same connection attempt at both ends at the same time.
BEFORE and AFTER: The Only Thing That Changes Is One Line
I set this up in the lab. Two servers, no other device between them. A real service listens on the receiver’s 9999 port, but the receiver’s firewall has a single-line drop rule: incoming packets are thrown away silently. From the sending side I try to connect, and I take a capture at both ends at once.
Before going on, make a guess. What we’ll see on the sending side is clear: unanswered SYN packets. The real question is: what will appear on the receiver’s own machine?
The intuitive answer is “nothing.” If the firewall dropped the packet there should be no record there; the other side is saying exactly that: “we’re not getting anything.”
Here is the BEFORE stage captured at both ends, side by side (the first two of nine SYNs):
# on the SENDER
00:00:00.000000 Out IP 192.168.1.30.60360 > 192.168.1.15.9999: Flags [S], seq 388863482, TS val 1793784401
00:00:01.021757 Out IP 192.168.1.30.60360 > 192.168.1.15.9999: Flags [S], seq 388863482, TS val 1793785424
# on the RECEIVER, at the same time
00:00:00.000000 In IP 192.168.1.30.60360 > 192.168.1.15.9999: Flags [S], seq 388863482, TS val 1793784401
00:00:01.021660 In IP 192.168.1.30.60360 > 192.168.1.15.9999: Flags [S], seq 388863482, TS val 1793785424The guess was wrong. On the receiver side the packets are there, all nine of the nine. And these aren’t “similar” packets:
- Same source port (
60360), same sequence number (388863482), same timestamp (TS val 1793784401). So these are the two ends’ views of the same packet. - The only difference is the direction:
Outon the sender,Inon the receiver. - So the packets reached the receiver’s machine and were recorded there. They just weren’t handed to the application.
What explains the intuition being wrong: the packet-capture tool sees the packet before the firewall filter. The capture point is earlier than the point where the rule is applied. So even when the firewall drops the packet, the capture has already recorded it.
The practical result may be this article’s most useful sentence: a capture taken on the machine of the side that says “we’re not getting anything” gives away its own rule.
Now delete the one line and repeat the same experiment. This time the expectation is clear: the rule is gone, so the connection should form.
And it did, in 0.75 milliseconds. The capture from the same two ends now looks like this:
# on the SENDER
00:00:00.000000 Out IP 192.168.1.30.41216 > 192.168.1.15.9999: Flags [S], seq 3290926036
00:00:00.000508 In IP 192.168.1.15.9999 > 192.168.1.30.41216: Flags [S.], seq 2464608652, ack 3290926037
00:00:00.000151 Out IP 192.168.1.30.41216 > 192.168.1.15.9999: Flags [.], ack 1
# on the RECEIVER, at the same time
00:00:00.000000 In IP 192.168.1.30.41216 > 192.168.1.15.9999: Flags [S], seq 3290926036
00:00:00.000037 Out IP 192.168.1.15.9999 > 192.168.1.30.41216: Flags [S.], seq 2464608652, ack 3290926037
00:00:00.000641 In IP 192.168.1.30.41216 > 192.168.1.15.9999: Flags [.], ack 1Compare the two with the BEFORE stage and the difference shows at a glance:
- The
Flagscolumn now carries three different values:[S]→[S.]→[.]. This is the three-way handshake you met at the start of the article. In the BEFORE stage all nine lines were[S], because no answer was coming back. - The direction alternates now. On the sender
Out→In→Out, on the receiverIn→Out→In. So the conversation is two-way. In the BEFORE stage the sender had onlyOut, the receiver onlyIn: a one-way repetition. - The server’s answer came in 37 microseconds (the second line of the receiver capture). Same cable, same two machines; the setup that kept you waiting 20 seconds a moment ago now completes in microseconds.
A clean close (FIN exchange) followed at both ends after this, but the real proof is the three lines above.
| Stage | Rule on receiver | What the sender saw | The receiver’s own capture |
|---|---|---|---|
| BEFORE | drop | 20.008 s, timeout | 9 SYN entries visible |
| AFTER | rule deleted | 0.75 ms, connected | full handshake + close |
The only thing that changed was that one line on the receiver. That’s why the result is closed to argument: there’s no other variable.
In the experiment no device was left between the two servers on purpose. This isn’t a shortcoming, it’s the experiment’s control: because the only variable is the receiver’s rule, no “maybe a device in between dropped it” doubt arises. In real life there are devices in between, and then the question turns into “who dropped it.” The next section is exactly the answer to that.
Where You Capture Decides What You Prove
Where a capture is taken directly determines what it proves. Three captures of the same fault from three different points support three different claims.
| Capture point | What it proves | What it doesn’t |
|---|---|---|
| On the client | The application really produced the packet | Whether the packet made it onto the network |
| On the edge device you control | Your whole internal network is cleared, the packet left | Whether it reached the other side |
| On the far end | Whether it arrived, and if so what happened | (the argument ends) |
The most productive point in the field is the second: the outermost device you control, the last stop the traffic passes on the way out from you. With a single capture your whole internal network is cleared. This isn’t a matter of device brand or model, it’s a principle.
Capture Commands
You can do the same job in any environment, only the tool’s name changes. All three filter a specific address and port pair, because an unfiltered capture both adds needless load and grows too big to read.
On Linux, on a server or a machine acting as a gateway:
sudo tcpdump -i any -nn 'host 203.0.113.10 and tcp port 443'On Windows, without installing extra software:
pktmon start --capture --pkt-size 128 -f C:\capture.etl
pktmon stop
pktmon etl2pcap C:\capture.etl -o C:\capture.pcappktmon ships with Windows 10 and Windows Server 2019, so it needs no Npcap or Wireshark install. It’s the most practical way to take a capture on a corporate machine where you have no install rights.
On a managed switch a copy of the traffic is sent to a monitoring port with port mirroring (SPAN), and the capture is taken on a machine connected to that port. Most firewalls also carry a similar capture command in their own command line.
The Asymmetry of an Edge Capture
This capture has an important asymmetry, and it’s the article’s most instructive sentence: an edge capture is strong evidence when it sees, weak when it doesn’t.
- If you see the packet at the edge, your internal network is cleared. The packet was produced, travelled, and left you.
- If you don’t see the packet at the edge, you’re left with two possibilities this capture can’t separate: the application may never have produced the packet, or the packet may have dropped inside the internal network.
In the second case you have to move the diagnosis one step back, to the client.
One more warning: a capture taken on a device along the path is not a reliable witness about that device itself. It’s enough to say “the packet passed through here,” not enough to say “this device didn’t drop it.”
What Does Silence Mean in UDP?
Everything up to here rested on TCP’s handshake. In UDP that handshake never happens, so diagnosis gets harder too. You know a TCP port is open because SYN-ACK comes back; in UDP there’s no such signal. An open UDP port stays silent as long as the application doesn’t answer. A closed UDP port usually produces an ICMP “port unreachable” answer.
The expectation that follows: silence means filtering, an ICMP answer means a closed port. So hitting a closed UDP port should make your test program say “closed” at once, and hitting a blocked port should make it wait. Sounds logical.
In the lab I hit a closed UDP port. The test program waited 6 seconds and said “no answer.” By the expectation, that means the port is blocked.
Then I looked at the packet capture of the same moment: the server had sent an ICMP “port unreachable” answer in 47 microseconds. So it had clearly said “there’s no such port.” The test program never saw that answer.
The cause isn’t in the network, it’s in the test program itself. And to understand it you need to know the call named connect().
In UDP, connect() seems to contradict itself at first: if UDP is a connectionless protocol, what is it connecting? The answer: nothing. This call doesn’t send a single packet onto the network. Its only job is to make a local note to the operating system: “this socket’s peer is this address, this port.”
What that note is for shows up exactly when an error comes. When an ICMP error returns from the server, the kernel has to deliver it to a socket, but which one? A socket that has declared its peer is known: if the error comes from that address, it belongs to that socket. A socket that hasn’t declared its peer stays open to talking to anyone; the kernel can’t match the incoming error to it and doesn’t deliver it. So the behaviour isn’t arbitrary, it’s inevitable.
Our test program had set up the socket without a peer. The answer reached the machine, came all the way to the kernel, and was never given to the program.
To prove it I changed one thing and repeated the test: this time I set up the socket declaring its peer. Same port, same machine. The result was ConnectionRefused in 0.68 milliseconds. The network was the same network, the server the same server; the only thing that changed was how the program opened the socket.
The full measurement to three targets:
| Target | What really happens on the network | Peerless socket | Socket with peer declared |
|---|---|---|---|
| Closed UDP port, no rule | ICMP unreachable in 47 microseconds | 6.006 s silence | 0.68 ms, ConnectionRefused |
UDP port, drop rule | zero answer | 6.006 s silence | 6.003 s silence |
Running service (NTP 123) | real answer in 1.3 ms | 2 ms, 48-byte answer | not measured |
The third column of the first two rows is exactly the same: 6.006 s of silence. One is a closed port, the other a firewall rule, but to someone looking through a peerless socket the two look like a single thing.
The practical result touches your work directly: a UDP test written without a peer can’t tell a “closed port” from a “blocked port.” The only thing that separates them is the packet capture.
This is the second, TCP-independent proof of the thesis running through the article. In the first, the stopwatch showed two different firewall behaviours the same. In this second, the test tool never sees an answer that clearly exists on the network. In both, the story the tool tells and what’s happening on the network are not the same.
Conclusion: What Ends the Argument Is the Packet Itself
A port number is the number that distributes traffic arriving at a single address to the right program; TCP does that distribution by confirming, UDP without confirming. That much is in every source.
The real matter is what this knowledge is good for in a fault. The measurements in this article showed three things:
- Duration doesn’t tell you the kind of fault. The difference between
dropandrejectwas 5 milliseconds, yet one is silence and the other an open refusal. - The same fault looks different on the two sides. Against the same rule one client froze for 21 seconds while another got an instant refusal, and under pure silence that order reverses. The technical source of the “no problem on our end, it’s yours” argument is usually this.
- The tool’s story and the network’s reality aren’t the same. A peerless UDP test can’t see an answer that exists on the wire.
The one line to keep: in a connection fault, the stopwatch and the error message are both witnesses that lie by omission. The packet doesn’t. Capture it at the right point, read the source port, the sequence number and the flags, and the argument is over.
Questions about ports
This article is adapted from a guide the author first published in Turkish on sercebilisim.com: Port Nedir? TCP ve UDP Farkı, Bağlantı Arızası Tespiti
Written by
İlker PehlivanNetwork and systems engineer, founder of Serçe Bilişim
I run the networks and servers that other people's work depends on. Before founding my own consultancy I administered the backbone network, firewalls and core systems of a large multi-site organisation with thousands of users. I write about the things that actually broke.
Related articles
What Is a VLAN? 802.1Q Tags, Access vs. Trunk Ports
A VLAN splits one switch into separate networks using a 4-byte tag. How 802.1Q works, access vs. trunk ports, and native VLAN risk, with real packet captures.
What Is ARP? ARP Table States, Broadcast Domains, Spoofing
ARP maps an IP address to a MAC address on the local network. Cache states, where the broadcast domain ends, and ARP spoofing, shown with real packet captures.
What Is DNS and How Does It Work? Records and TTL
What DNS is and how it works: the root, TLD and authoritative chain; A, CNAME, MX, TXT records; nameserver delegation, TTL and caching, and diagnosing faults with nslookup.
What Is NAT? SNAT, DNAT, and the Translation Table
What NAT really does, how SNAT and DNAT differ, how PAT reuses one address, why double NAT and CGNAT break port forwarding, and a measured look at hairpin NAT.
On this page
Part of this guide
Network Segmentation Guide: VLANs, Layers, and Access