1 of 3

Network Fault DiagnosisLesson 1 of 3

Diagnosing a network fault layer by layer

Test link, address, route, name, port and application in order, and read refused, timed out and unreachable correctly.

  • ip link
  • ip -br addr
  • ip route
  • ip neigh
  • ping
  • tracepath
  • ss -tlnp
  • nc
  • curl
  • curl -v
  • dig
  • getent
  • nmcli device status

45 min3 exercises

After this lesson you can

  • Apply a fixed order of checks that moves from the cable to the application and stops at the first failure.
  • Interpret connection refused, timeout, no route to host, and name resolution failures.
  • Choose the tool that tests each layer: ip, ping, tracepath, getent, nc, curl, ss.

The idea

“The network is broken” is almost never one problem. A TCP connection from an application to a server depends on a stack of things, each of which can fail on its own. Testing them in order, bottom to top, means the first failure you find is the real one, and everything above it is a consequence.

1  Link           is the interface up, does it have carrier?              ip -br link
2  Address        does it have the right IP and prefix?                   ip -br addr
3  Route          is there a route to the destination (or a default)?    ip route get ADDR
4  Neighbour      can the next hop's MAC address be found?                ip neigh
5  Reachability   does the destination answer ICMP?                      ping -c2 ADDR   (may be blocked)
6  Path           where do packets stop?                                  tracepath ADDR
7  Name           does the name resolve to the right address?            getent hosts NAME
8  Port           does TCP connect to the port?                           nc -zv HOST PORT
9  Service        does the application answer correctly?                  curl -v URL
10 Server side    is anything listening, and is the firewall open?        ss -tlnp ; firewall-cmd --list-all

Two rules:

  • Test by address before you test by name. If ping 192.168.50.12 works and ping node2 does not, the network is fine and the problem is naming.
  • Run the same check from both ends when you can. A listener bound to 127.0.0.1 accepts connections from the server itself and refuses everyone else.

What each failure means

The wording of an error is information. curl, nc, ssh and ping report the same few situations in slightly different words.

What you seeWhat happenedWhere to look
Network is unreachableThe local routing table has no route for the destinationip route, the default gateway
Name or service not known, Could not resolve hostName lookup failed/etc/hosts, /etc/nsswitch.conf, /etc/resolv.conf, DNS server
Connection refusedPacket arrived, host replied “no” (TCP RST or ICMP port unreachable): nothing is listeningss -tlnp on the server; wrong port; service down; bound to localhost
Connection timed outNo answer at all: packets dropped (firewall DROP), lost on the path, or the host is downFirewall on the server and on the path; routing back; tcpdump
No route to hostThe kernel or a router sent back “host unreachable” (ICMP) or ARP failedNeighbour table, gateway, VLAN, the target’s own link
Connection reset by peerConnection was made then torn downApplication crashed, TLS/protocol mismatch, firewall reset
Ping works, TCP does notThe host is up, the port or a firewall rule is the problemnc -zv, firewall rules
TCP works, application failsProtocol-level errorcurl -v, application logs
Works for small data, hangs on largeMTU mismatch (packets dropped when large)ping -M do -s 1472 ADDR, tracepath

The most useful distinction is the first one above all others: refused means you reached something; timed out means you reached nothing (or something silently dropped you).

Try it

All of the following are real, from a test machine.

Refused versus timed out

Connect to a port where nothing listens:

nc -zv 127.0.0.1 9
curl -sS -m 3 http://127.0.0.1:9/
nc: connect to 127.0.0.1 port 9 (tcp) failed: Connection refused
curl: (7) Failed to connect to 127.0.0.1 port 9 after 0 ms: Couldn't connect to server

curl exit code 7 is “failed to connect”. It happened in 0 ms: the answer came back immediately, because the kernel replied with a reset.

A name that does not exist:

curl -sS -m 3 http://no-such-host.invalid/
getent hosts nosuch.invalid; echo "rc=$?"
curl: (6) Could not resolve host: no-such-host.invalid
rc=2

curl exit code 6 is name resolution. getent hosts returns status 2 when the name is not found; it uses the same resolver order as applications (/etc/nsswitch.conf), which dig does not.

Reading the connection from the outside

curl -v shows each step: name resolution, the address tried, the TCP connection, and the HTTP exchange:

curl -v --max-time 5 http://node2.lab.example:8080/ 2>&1 | head -8
*   Trying 192.168.50.12:8080...
* Connected to node2.lab.example (192.168.50.12) port 8080
> GET / HTTP/1.1
> Host: node2.lab.example:8080
> User-Agent: curl/8.9.1
> Accept: */*
>
< HTTP/1.0 200 OK

(Representative output.) If it had stopped after Trying ... the problem is the TCP connect (route, firewall, nothing listening). If it stops at the very first line with an error about the host, it is naming.

The server side

On the server, the question “is it listening, on which address?” has a one-line answer:

sudo ss -tlnp 'sport = :8080'
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0      5            0.0.0.0:8080      0.0.0.0:*     users:(("python3",pid=5120,fd=3))

0.0.0.0:8080 means all IPv4 addresses. 127.0.0.1:8080 would mean local only: remote clients get refused, even though the service is running and the firewall is open. [::]:8080 is all IPv6 addresses (and usually IPv4 too).

Going further

Symptoms that only appear sometimes. Intermittent failures usually come from DNS round-robin pointing at a bad host, an IPv6 address that does not work while IPv4 does (try curl -4 and curl -6), connection tracking tables filling up, or an MTU problem. The tcpdump lesson shows how to prove which. Beyond the exam: mtr combines ping and traceroute over time, ethtool -S shows interface error counters, and nstat/ss -s summarise TCP statistics (retransmissions are the first sign of a lossy path).

Practice

0 of 3 done

Do these on your own system. They get harder. Check each result, then tick it off.

  1. Warm-up

    Match symptom to layer

    For each message, name the layer that failed and the next command: (1) Network is unreachable; (2) Could not resolve host: app.lab.example; (3) Connection refused; (4) Connection timed out after a long wait; (5) No route to host; (6) a browser shows 502 Bad Gateway.

    Check your result
    • (1) no route in the table, ip route; (2) name resolution, getent hosts / dig; (3) the host is reachable, nothing listens on that port, ss -tlnp on the server; (4) packets dropped by a firewall or lost on the path, tcpdump and the firewall rules; (5) the local host knows a route but ARP or an ICMP reply says unreachable, check the neighbour and the gateway; (6) the application layer: the proxy cannot reach its backend, read the proxy log.
    Hint

    Refused is an answer (an RST or ICMP came back). Timed out is the absence of an answer. They need different next steps.

    Solution
    Network is unreachable   -> routing table (no matching route)      ip route
    Could not resolve host   -> DNS / hosts / nsswitch                 getent hosts NAME
    Connection refused       -> reached, port closed                   ss -tlnp (server)
    Connection timed out     -> filtered or lost                       tcpdump, firewall
    No route to host         -> ARP failed or ICMP unreachable         ip neigh, gateway
    502 Bad Gateway          -> application layer                      proxy and backend logs
  2. Practice

    Walk a connection down the stack

    From node1, the service at http://node2.lab.example:8080/ is unreachable. Without assuming the cause, run the layered checks in order from node1 and record the result of each: link state, own address, default route, the gateway's neighbour entry, name resolution of node2.lab.example, ICMP to its address, TCP to port 8080, and an HTTP request. Stop and report at the first failure with the evidence.

    Check your result
    • Your list has these commands in this order: ip -br link, ip -br addr, ip route, ip neigh, getent hosts node2.lab.example, ping -c2, nc -zv node2.lab.example 8080, curl -v http://node2.lab.example:8080/.
    • You stopped at the first failure and named it with one line of evidence.
    Hint

    Each check depends on the one before it: a failing ping to a name and a working ping to its address means DNS, not the network. Test by address first, then by name.

    Solution
    ip -br link
    ip -br addr
    ip route
    ip neigh show
    getent hosts node2.lab.example
    ping -c2 192.168.50.12
    nc -zv node2.lab.example 8080
    curl -v --max-time 5 http://node2.lab.example:8080/
  3. Challenge

    Three faults in one path

    Setup (lab VMs node1 client and node2 server; run on node1 unless stated):

    # node2: serve something on 8080
    python3 -m http.server 8080 --bind 0.0.0.0 &
    # node1: break it (CON is the name of the active profile)
    CON=$(nmcli -g NAME connection show --active | head -1)
    sudo nmcli connection modify "$CON" ipv4.gateway 192.168.50.99
    sudo nmcli connection up "$CON"
    echo "192.168.50.13 node2.lab.example" | sudo tee -a /etc/hosts

    and on node2 sudo firewall-cmd --remove-port=8080/tcp (if open). From node1 curl http://node2.lab.example:8080/ fails. There are three independent faults. Find them one at a time, in layer order, with evidence for each, fix them persistently, and finish with a successful request.

    Check your result
    • curl -s http://node2.lab.example:8080/ | head -3 returns HTML.
    • ip route | grep default shows the real gateway; getent hosts node2.lab.example returns node2's real address; sudo firewall-cmd --permanent --list-ports on node2 contains 8080/tcp.
    • The wrong /etc/hosts line is gone.
    Hint

    Layer order: route (gateway), name (a stale /etc/hosts entry overrides DNS because hosts: files dns in nsswitch.conf reads the file first), then the firewall on the server. Use getent hosts, not dig, to see what applications see.

    Solution
    ip route; ping -c2 192.168.50.99 || echo "gateway unreachable"
    CON=$(nmcli -g NAME connection show --active | head -1)
    sudo nmcli connection modify "$CON" ipv4.gateway 192.168.50.1
    sudo nmcli connection up "$CON"
    getent hosts node2.lab.example; dig +short node2.lab.example
    sudo sed -i '/node2.lab.example/d' /etc/hosts
    nc -zv node2.lab.example 8080
    # node2:
    sudo ss -tlnp | grep 8080
    sudo firewall-cmd --permanent --add-port=8080/tcp && sudo firewall-cmd --reload
    # node1
    curl -s http://node2.lab.example:8080/ | head -3