> ## Content Index
> Fetch the complete content index at: https://www.pinglabz.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# The Linux ip Command: The Complete Guide
- URL: https://www.pinglabz.com/linux-ip-command/
- Published: 2026-08-17T00:09:20.000Z
- Updated: 2026-08-24T05:04:10.000Z
- Description: The grammar, the four objects you use every day, the output modifiers that make it readable, and how to create interfaces out of nothing.
- Author: Jaime
- Tags: Linux, iproute2, Networking, Labs

If you learned networking on Cisco gear and then inherited a fleet of Linux boxes, the `ip` command is the single tool that closes the gap. It is the Linux equivalent of `show ip interface brief`, `show ip route`, `show arp` and half of interface configuration mode, all behind one binary. It is also the tool that most engineers use at about ten percent of its capability, because they learned three invocations years ago and never went back.

This guide is the full pass. It covers the grammar that makes `ip` predictable, the four objects you will use every day (`address`, `link`, `route`, `neigh`), the output modifiers that turn a wall of text into something readable, how to make changes and why none of them survive a reboot, and the interface types you can create out of thin air. Every command below was run on a Debian 13 host wired into a Cisco Modeling Labs topology, so the output is what you will see, not what the man page implies. It is the anchor article for the [Linux networking commands](https://www.pinglabz.com/linux-networking-commands/) cluster.

## The tool you are replacing

`ifconfig`, `route` and `arp` come from the `net-tools` package, which stopped being actively developed around 2001\. It still works, and Debian still ships version 2.10, but it talks to the kernel through an interface that the kernel stopped extending a long time ago. Anything added to Linux networking since then is either invisible to it or displayed wrong.

`ip` comes from `iproute2`, which talks to the kernel over netlink. Netlink is the modern, structured, still-evolving interface, which is why `ip` can show you address lifetimes, policy routing tables, neighbor states and VRFs while `ifconfig` cannot. On this host:

```
j@llmbits:~$ ip -V
ip utility, iproute2-6.15.0, libbpf 1.5.0
```

The full argument for the switch, with the legacy commands and their replacements side by side, is in [ifconfig, route and netstat: mapping legacy net-tools to iproute2](https://www.pinglabz.com/ifconfig-vs-ip/). This article assumes you have already accepted the premise.

## The grammar: object, then command, then arguments

`ip` is not one command. It is a dispatcher for about thirty subsystems, and every invocation has the same shape:

```
ip [OPTIONS] OBJECT { COMMAND | help }
```

Ask it what the objects are and it tells you:

```
j@llmbits:~$ ip help
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] -batch filename
where  OBJECT := { address | addrlabel | fou | help | ila | ioam | l2tp | link |
                   macsec | maddress | monitor | mptcp | mroute | mrule |
                   neighbor | neighbour | netconf | netns | nexthop | ntable |
                   ntbl | route | rule | sr | stats | tap | tcpmetrics |
                   token | tunnel | tuntap | vrf | xfrm }
       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
                    -h[uman-readable] | -iec | -j[son] | -p[retty] |
                    -f[amily] { inet | inet6 | mpls | bridge | link } |
                    -4 | -6 | -M | -B | -0 |
                    -l[oops] { maximum-addr-flush-attempts } | -echo | -br[ief] |
                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |
                    -rc[vbuf] [size] | -n[etns] name | -N[umeric] | -a[ll] |
```

Two conveniences follow from this. First, every object accepts `help`, so `ip route help` and `ip neigh help` give you a syntax summary without leaving the shell. Second, objects and commands can be abbreviated to any unambiguous prefix. `ip a` is `ip address`, `ip r` is `ip route`, `ip n` is `ip neighbor`, and `ip a s` is `ip address show`. Type the short form interactively and the long form in scripts, because a future object starting with the same letter can make an abbreviation ambiguous and break your script years later.

The command defaults to `show` (or `list`) when you leave it off, which is why `ip addr` and `ip addr show` do the same thing.

## ip address: what is actually configured

This is the one you will type most. With no arguments it dumps every address on every interface:

```
j@llmbits:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:b1:cc:3d brd ff:ff:ff:ff:ff:ff
    altname enp11s0
    altname enx000c29b1cc3d
    inet 192.168.88.156/24 brd 192.168.88.255 scope global dynamic noprefixroute ens192
       valid_lft 6409sec preferred_lft 6409sec
    inet6 fd64:f725:df42:4f01:8498:4c62:15f0:8139/64 scope global temporary dynamic
       valid_lft 1775sec preferred_lft 1775sec
    inet6 fd64:f725:df42:4f01:20c:29ff:feb1:cc3d/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 1775sec preferred_lft 1775sec
    inet6 fe80::20c:29ff:feb1:cc3d/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff
    altname enp19s0
    altname enx000c29b1cc47
    inet 10.77.0.100/24 scope global ens224
       valid_lft forever preferred_lft forever
```

There is more in there than most people read. Working left to right on the `ens192` block:

The flags in angle brackets are the interface's administrative and operational state. `UP` means the admin brought it up. `LOWER_UP` means the driver sees carrier. An interface that is `UP` without `LOWER_UP` is the Linux equivalent of "up / down" on a Cisco box: you configured it, the cable is not there. That distinction is the first thing to check and the easiest one to miss.

`qdisc mq` is the queueing discipline, in this case multiqueue. `state UP` is the operational state, and `UNKNOWN` on the loopback is normal rather than broken (loopback has no carrier concept).

The `altname` lines are alternative interface names the kernel will also answer to. `ip link show enp19s0` works exactly as well as `ip link show ens224` here. If you have ever written a script against a predictable-name scheme and had it break on a different distro, altnames are the fix.

`valid_lft` and `preferred_lft` are the address lifetimes. On the DHCP-managed interface they count down; the moment they hit zero without a renewal the address goes away. On the statically configured interface they read `forever`. `ifconfig` shows you neither, which is why a host that is about to lose its address looks identical to one that is not.

`scope` matters more than it looks. `global` is routable anywhere, `link` is valid only on this segment (every IPv6 `fe80::` address), and `host` never leaves the machine. `dynamic` means DHCP or SLAAC put it there. `noprefixroute` means the kernel did not automatically add a connected route for it, which NetworkManager sets so it can manage routes itself.

Filter that dump down with the standard modifiers:

```
j@llmbits:~$ ip -br addr show
lo               UNKNOWN        127.0.0.1/8 ::1/128
ens192           UP             192.168.88.156/24 fd64:f725:df42:4f01:8498:4c62:15f0:8139/64 fd64:f725:df42:4f01:20c:29ff:feb1:cc3d/64 fe80::20c:29ff:feb1:cc3d/64
ens224           UP             10.77.0.100/24
```

`-br` (brief) is the closest thing Linux has to `show ip interface brief`, and it is the command to reach for when someone asks "what is this box's address" over the phone. Add `-4` to drop the IPv6 noise and a device name to narrow it further:

```
j@llmbits:~$ ip -4 addr show dev ens224
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    altname enp19s0
    altname enx000c29b1cc47
    inet 10.77.0.100/24 scope global ens224
       valid_lft forever preferred_lft forever
```

## ip link: the layer 2 view

`ip link` is the same interfaces with the addresses stripped out. It is where MAC, MTU, queue length and driver state live.

```
j@llmbits:~$ ip link show dev ens224
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff
    altname enp19s0
    altname enx000c29b1cc47
```

Add `-s` and you get counters, which is the Linux answer to `show interfaces`:

```
j@llmbits:~$ ip -s link show dev ens224
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast
        250454    3685      0       0       0       0
    TX:  bytes packets errors dropped carrier collsns
       9332264    3388      0       0       0       0
    altname enp19s0
    altname enx000c29b1cc47
```

Double it up as `-s -s` and the error columns expand into the breakdown that tells you which layer is unhappy:

```
j@llmbits:~$ ip -s -s link show ens224
    RX errors:  length    crc   frame    fifo overrun
                     0      0       0       0       0
    TX:  bytes packets errors dropped carrier collsns
          2030      27      0       0       0       0
    TX errors: aborted   fifo  window heartbt transns
                     0      0       0       0       5
```

CRC errors point at cabling or a duplex mismatch. FIFO and overrun point at the host not draining the ring fast enough. `transns` counts carrier transitions, so a number that climbs while you watch is a link flapping. For the layer below this (actual speed, duplex, offloads and driver statistics), you need [ethtool](https://www.pinglabz.com/ethtool-linux/), because the kernel's generic counters stop where the NIC driver begins.

## ip route: where packets go

```
j@llmbits:~$ ip route show
default via 192.168.88.1 dev ens192 proto dhcp src 192.168.88.156 metric 101
10.77.0.0/24 dev ens224 proto kernel scope link src 10.77.0.100
10.77.0.0/16 via 10.77.0.1 dev ens224
192.168.88.0/24 dev ens192 proto kernel scope link src 192.168.88.156 metric 101
```

Four routes, and each field earns its place. `proto` records who installed the route: `kernel` for the connected routes the kernel derived from the interface addresses, `dhcp` for the default route a lease supplied, `static` for one you typed, and `bird` or `bgp` if a routing daemon is running. `src` is the source address the kernel will use for locally originated traffic on that route, which is the setting that decides which IP your monitoring traffic appears to come from on a multihomed box. `metric` breaks ties between routes of equal prefix length, lower wins.

The single most useful routing command is not `show`, it is `get`. It asks the kernel to perform an actual lookup and report the decision:

```
j@llmbits:~$ ip route get 10.77.3.10
10.77.3.10 via 10.77.0.1 dev ens224 src 10.77.0.100 uid 1000
    cache

j@llmbits:~$ ip route get 8.8.8.8
8.8.8.8 via 192.168.88.1 dev ens192 src 192.168.88.156 uid 1000
    cache
```

This is the equivalent of `show ip route 10.77.3.10` on IOS, and it settles arguments. It applies longest-prefix match, policy rules and source selection, then tells you the exit interface, next hop and source address it would use. When someone insists traffic "should" be going out the other interface, this is the command that ends the conversation. Routing tables, metrics, multiple tables and policy rules get the full treatment in [ip route: managing the Linux routing table](https://www.pinglabz.com/ip-route-linux/).

## ip neigh: the ARP table, with states

```
j@llmbits:~$ ip neigh show
192.168.88.124 dev ens192 lladdr 34:9f:7b:7f:ca:ee STALE
10.77.0.1 dev ens224 lladdr aa:bb:cc:00:04:00 STALE
192.168.88.1 dev ens192 lladdr 98:ba:5f:11:1b:3a REACHABLE
192.168.88.125 dev ens192 lladdr 58:ef:68:e7:26:1a REACHABLE
fe80::18d8:e4e1:ba78:179e dev ens192 lladdr 1c:b3:c9:02:f4:2f router STALE
fe80::1072:e4df:6063:2046 dev ens192 lladdr c4:f7:c1:09:40:9d router STALE
fe80::c1f:b024:94ab:44c3 dev ens192 lladdr f4:34:f0:69:8a:76 router STALE
```

Two things here that `arp -n` cannot show you. IPv4 ARP and IPv6 neighbor discovery are the same table, so both appear in one listing. And each entry carries a state: `REACHABLE` means the kernel has confirmed this neighbor within the last thirty seconds or so, `STALE` means the mapping is cached but unverified. `arp` flattens both to the flag `C`, which hides exactly the information you want during an incident. The full state machine, walked live, is in [ARP on Linux: ip neigh, arp and neighbor states](https://www.pinglabz.com/ip-neigh-arp-linux/).

## The output modifiers that make ip usable

These are global options, so they go before the object and work across most of them.

`-br` brief

One line per interface. The closest thing to `show ip interface brief`. Works with `addr`, `link` and `neigh`.

`-s` statistics

Adds counters. Repeat it (`-s -s`) for the per-error-type breakdown and neighbor probe counts.

`-4` / `-6`

Restrict to one address family. Essential on dual-stack hosts where the IPv6 addresses triple the output.

`-j -p` JSON

Machine-readable output, pretty-printed. The correct way to feed `ip` into a script instead of parsing text with awk.

`-d` details

Expands device-type specifics: VLAN ID and protocol, bridge settings, tunnel parameters, offload maximums.

`-o` oneline

Collapses each record onto a single line. Makes `grep` behave, since a match no longer loses its continuation lines.

The JSON option deserves a demonstration, because it is the difference between a fragile script and a reliable one:

```
j@llmbits:~$ ip -j -p addr show dev ens224
[ {
        "ifindex": 3,
        "ifname": "ens224",
        "flags": [ "BROADCAST","MULTICAST","UP","LOWER_UP" ],
        "mtu": 1500,
        "qdisc": "mq",
        "operstate": "UP",
        "group": "default",
        "txqlen": 1000,
        "link_type": "ether",
        "address": "00:0c:29:b1:cc:47",
        "broadcast": "ff:ff:ff:ff:ff:ff",
        "altnames": [ "enp19s0","enx000c29b1cc47" ],
        "addr_info": [ {
                "family": "inet",
                "local": "10.77.0.100",
                "prefixlen": 24,
                "scope": "global",
                "label": "ens224",
                "valid_life_time": 4294967295,
                "preferred_life_time": 4294967295
            } ]
    } ]
```

Piping that into `jq -r '.[0].addr_info[0].local'` will keep working after an iproute2 upgrade changes the text layout. An `awk '{print $2}'` against the human output will not. If you are already doing structured collection with the tools in the [network automation](https://www.pinglabz.com/network-automation/) cluster, `ip -j` is how Linux hosts join that pipeline.

## Making changes, and why they vanish

Everything so far was read-only. The write side follows the same grammar, with `add`, `del`, `change`, `replace` and `set` replacing `show`. Adding a second address to an interface:

```
j@llmbits:~$ sudo ip addr add 10.77.0.200/24 dev ens224 label ens224:1
j@llmbits:~$ ip addr show dev ens224 | grep inet
    inet 10.77.0.100/24 scope global ens224
    inet 10.77.0.200/24 scope global secondary ens224:1
```

Note `secondary`. The first global address in a subnet on an interface is primary and the rest are secondary, exactly as on IOS. The difference from `ifconfig` is that Linux has always supported multiple addresses per interface natively; the `ens224:1` label is a compatibility shim so that `ifconfig` can see the address at all. You do not need it unless something old is reading the interface list.

Removing it takes the same prefix length you added:

```
j@llmbits:~$ sudo ip addr del 10.77.0.200/24 dev ens224
```

Changing an MTU shows the write path on `ip link`, and immediately shows why you should be careful:

```
j@llmbits:~$ sudo ip link set dev ens224 mtu 1400
j@llmbits:~$ ip link show ens224
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff

j@llmbits:~$ ping -c 2 -M do -s 1400 10.77.3.10
--- 10.77.3.10 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1018ms
```

A 1400 byte payload with the do-not-fragment bit set no longer fits in a 1400 byte MTU once headers are added, and the kernel refuses to send it. That is the correct behavior and it is worth having seen deliberately before you meet it accidentally across a tunnel. The [ping MTU and DF bit](https://www.pinglabz.com/ping-mtu-df-bit/) article takes that further.

Now the part that catches people. **None of these changes persist.** `ip` writes to the running kernel and nothing else. Reboot the host and every address, route and MTU you set by hand is gone, back to whatever the configuration system says. Depending on the distribution that system is NetworkManager, `systemd-networkd`, or `/etc/network/interfaces`. On this host it is NetworkManager, which is why [nmcli](https://www.pinglabz.com/nmcli-linux/) is the tool that makes a change survive. Use `ip` to test and to troubleshoot, then write the working configuration into whatever owns the interface.

Get the Linux Networking Field Reference - 10 pages, free

Every command in this cluster on ten printable pages: iproute2, sockets, DNS, capture, monitors and all three firewall front ends. Includes a ten-symptom troubleshooting decision tree and annotated real lab output. Free for PingLabz members, just sign up with your email.

[Get the Linux networking cheat sheet](https://www.pinglabz.com/linux-networking-cheatsheet/)

## Creating interfaces out of nothing

`ip link add` creates virtual interfaces, and this is where `ip` stops having a `net-tools` equivalent at all. A dot1q subinterface, in one command:

```
j@llmbits:~$ sudo ip link add link ens224 name ens224.50 type vlan id 50
j@llmbits:~$ sudo ip link set ens224.50 up
j@llmbits:~$ ip -d link show ens224.50
5: ens224.50@ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:b1:cc:47 brd ff:ff:ff:ff:ff:ff promiscuity 0 allmulti 0 minmtu 0 maxmtu 65535
    vlan protocol 802.1Q id 50 <REORDER_HDR> addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
```

The `@ens224` suffix names the parent device, and `-d` is what reveals `vlan protocol 802.1Q id 50`. Without `-d` you would have no way to tell which VLAN this interface is tagging. It inherits the parent's MAC, exactly like router-on-a-stick subinterfaces on Cisco gear, which is worth remembering when you are reading a [VLAN](https://www.pinglabz.com/vlans-layer-2-switching/) trunk from the host side.

Delete it with `sudo ip link del ens224.50`. The same `add ... type` pattern builds bridges (`type bridge`), bonds (`type bond`), veth pairs for containers (`type veth`), dummy interfaces for testing (`type dummy`), and [GRE tunnels](https://www.pinglabz.com/gre-on-linux/). Any of them can exist a second after you decide you want one, with no reboot and no hardware.

## The object map

You will not use most of the thirty objects. These are the ones worth knowing by name.

The objects you will actually type

**`ip address` (`ip a`)**IP addresses on interfaces, with scope and lifetimes 

**`ip link` (`ip l`)**Layer 2: MAC, MTU, state, counters, and creating virtual devices 

**`ip route` (`ip r`)**The routing table, plus `ip route get` for a real lookup 

**`ip neigh` (`ip n`)**ARP and IPv6 neighbor discovery, with per-entry state 

**`ip rule`**Policy routing: which table gets consulted, and in what order 

**`ip netns`**Network namespaces: separate routing and interface stacks on one kernel 

**`ip vrf`**VRF membership, the same concept you already know from IOS 

**`ip monitor`**Watch netlink events live: addresses, routes and neighbors as they change 

## Proving it end to end

Reading configuration is half the job. The other half is confirming the path exists. From this host, through the lab's edge router, across two more hops to a server four hops away:

```
j@llmbits:~$ traceroute -n 10.77.3.10
traceroute to 10.77.3.10 (10.77.3.10), 30 hops max, 60 byte packets
 1  10.77.0.1  4.452 ms  4.379 ms  4.437 ms
 2  10.77.12.2  4.686 ms  6.932 ms  6.960 ms
 3  10.77.23.3  7.029 ms  7.022 ms  8.543 ms
 4  10.77.3.10  8.506 ms  10.428 ms  10.354 ms
```

And the router at hop one, seeing the same host from the other side:

```
R1#show ip arp
Protocol  Address          Age (min)  Hardware Addr   Type   Interface
Internet  10.77.0.1               -   aabb.cc00.0400  ARPA   Ethernet0/0
Internet  10.77.0.100             0   000c.29b1.cc47  ARPA   Ethernet0/0
Internet  10.77.12.1              -   aabb.cc00.0410  ARPA   Ethernet0/1
Internet  10.77.12.2              4   aabb.cc00.0300  ARPA   Ethernet0/1
```

`000c.29b1.cc47` on the router is `00:0c:29:b1:cc:47` on the host. Same MAC, two vendors' formatting conventions, one adjacency. That is the entire trick to working across both worlds: the protocols are identical, only the presentation changed.

## FAQ

### Is ifconfig actually deprecated?

Upstream `net-tools` has had no meaningful development for two decades, and several distributions stopped installing it by default. It is not removed and it is not going to stop working, but it cannot show you address lifetimes, policy routing, neighbor states or VRFs, because the kernel interface it uses was never extended to carry them. Treat it as read-only muscle memory for hosts where it happens to be installed, and configure with `ip`.

### Why do my ip command changes disappear after a reboot?

Because they were only ever in the running kernel. `ip` has no configuration file and writes to no persistent store. Whatever manages your interfaces at boot (NetworkManager, `systemd-networkd`, `/etc/network/interfaces`, `netplan`) reapplies its own view of the world and yours is gone. That is a feature when you are testing, since a reboot is a guaranteed rollback.

### What is the difference between ip route show and ip route get?

`show` prints the table. `get` performs a lookup for one destination and reports what the kernel decided, including policy rules, longest-prefix match and source address selection. When the table is complex, `get` is the only one that answers the question you are actually asking.

### Can I use ip inside scripts safely?

Yes, with two rules. Use `-j` for JSON output and parse it with `jq` rather than scraping the human-readable text, and spell out full object and command names instead of abbreviations. Text layout and abbreviation resolution can both change between releases; the JSON schema and the long names are stable.

### Does ip work the same on every distribution?

The command grammar and output format come from `iproute2`, so yes, allowing for version differences in which fields exist. What changes between distributions is what manages the interfaces at boot and therefore what you have to edit to make a change stick. The read commands transfer everywhere; the persistence step does not.

## Key takeaways

- `ip` follows one grammar: `ip [OPTIONS] OBJECT COMMAND`, with `show` as the default command and any unambiguous abbreviation accepted.
- Four objects cover nearly everything: `address` for what is configured, `link` for layer 2 and counters, `route` for forwarding, `neigh` for ARP and neighbor discovery.
- `UP` and `LOWER_UP` are different things. Admin up without carrier is the Linux version of "up / down".
- `ip -br addr` is your `show ip interface brief`; `ip route get` is your `show ip route x.x.x.x` and it ends arguments about which interface traffic takes.
- `-s -s` expands the error counters into the per-cause breakdown that tells you whether to suspect cabling, duplex or the host itself.
- `-j -p` gives you JSON. Parse that in scripts, never the human output.
- Nothing `ip` does persists across a reboot. Test with `ip`, then write the result into NetworkManager, `systemd-networkd` or your distribution's config.
- `ip link add` creates VLANs, bridges, bonds, veth pairs and tunnels on demand, which `net-tools` cannot do at all.

Next in the cluster, go deeper on the routing table with [ip route: managing the Linux routing table](https://www.pinglabz.com/ip-route-linux/), or on the neighbor table with [ARP on Linux](https://www.pinglabz.com/ip-neigh-arp-linux/). If you are still translating old habits, [the net-tools to iproute2 map](https://www.pinglabz.com/ifconfig-vs-ip/) is the fastest way across. Everything in this cluster is indexed on the [Linux networking commands guide](https://www.pinglabz.com/linux-networking-commands/).