Networking Tools Every Developer Needs to Know
Tags: devops, linux, bash, sysadmin
Published: 2020-12-01 19:03:41

Networking is often overlooked topic by both developers and DevOps engineers. Yet it's at least to a certain extent integral part of every system and application. Being therefore able to perform basic troubleshooting and use common tools is very valuable skill and might come in handy next time you run into issues with DNS or routing, or when you just need to send HTTP request to some web application. So, let's go over following list of basic networking tools that every developer should know and explore examples that might even become your cheat sheet for _all things networking_.

## The Everyday Basics

Sending a HTTP request is one of the things that everyone of us does every day. And doing so with _cURL_ can be as simple as `curl google.com`, but `curl` command has lots of options that can make it a lot more useful. More often than not, sending plain request is not good enough and you will need to specify type of request or include some extra headers. You can do that with `-X` and `-H` arguments, respectively. Another common thing to do would be sending some data in request, which can be done with `-d`:

```shell
~ $ curl -X POST \  # POST Request
         -H "accept: application/json" \  # Set headers
         -H "Authorization: Bearer ..." \  # Can be used multiple times
         -d '{"text" : "Hello, World!"}' \  # Data in JSON format
         https://example.com/some/path
```

`curl` can be also helpful for debugging. You can turn on verbose output using `-vvv` or use `-k` to switch to insecure mode when you are dealing with website certificates.  

```shell
~ $ curl -X GET -k \
         -vvv \
         -H 'Content-type: application/json' \
         -d '{"text" : "Hello, World!"}' \
         https://example.com/some/path
```

When using verbose mode, there's going to be lots of data coming in and sometimes it makes sense to rather use `-I` to return only headers instead of whole responses.

Downloading files is another super common task that can be easily done from terminal using `wget`. Same as with `curl`, `wget` has a lot of options, but some of the more common and very useful ones include - `-O` to change filename, `-q` (quiet) to suppress noisy output, `-b` to run in background or `-c` to resume previous download. Simple example:

```shell
~ $ wget -q https://example.com/1.0.5/file-linux-amd64 -O new-filename
```

Things don't always work as expected and some services might become unavailable. So, to check if a host is reachable, one can use `ping <some-IP>` or `ping6 <some-IP>` if using IPv6. In case you need to check not just whether a host is reachable but also whether specific port is open, then you can use `telnet <host> <port>`.

`ping` is a lot simpler command than the ones above, but it still has some useful arguments, e.g. `-c <number>` to specify number pings to make or `-l <size>` to specify size of payload to send.

Looking up your IP address is probably not something you need to do every day, but you need it, then `hostname` command is there for you. Especially with `-I` argument which displays all IPs of your machine. `hostname` command can also be used to set your machines hostname, but not persistently, so preferably use `hostnamectl` for that instead.

## DNS is a Mess

General rule of thumb is that when something is not quite right with networking, then it's most likely because of DNS. Basic inspection and debugging of DNS can be done with either `nslookup`, `host` or `dig`. Out of these 3, I would recommend using `dig` even though its output is little hard to read, because `nslookup` is deprecated at this point and `host` doesn't provide as much data as `dig`.

```shell
~ $ dig github.com

; <<>> DiG 9.16.1-Ubuntu <<>> github.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31813
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; QUESTION SECTION:
;github.com.			IN	A

;; ANSWER SECTION:
github.com.		59	IN	A	140.82.121.3  # IP of website

;; Query time: 15 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)  # Nameserver Address
;; WHEN: So okt 10 17:15:03 CEST 2020
;; MSG SIZE  rcvd: 55

~ $ dig -t NS github.com
...
;; ANSWER SECTION:
github.com.		233	IN	NS	dns1.p08.nsone.net.  # List of nameservers
... # More records
github.com.		233	IN	NS	dns3.p08.nsone.net.
...

~ $ dig -t NS github.com +short  # Query nameservers with short output
dns2.p08.nsone.net.
...
ns-1707.awsdns-21.co.uk.

~ $ dig -x 140.82.121.3  # DNS Reverse lookup
...
;; QUESTION SECTION:
;3.121.82.140.in-addr.arpa.	IN	PTR

;; ANSWER SECTION:
3.121.82.140.in-addr.arpa. 1570	IN	PTR	lb-140-82-121-3-fra.github.com.
```

The example above shows some of the basic queries one can perform with `dig`. Using this command might seem a little intimidating as its syntax is frankly plain stupid. So, let's go over each query and its output to make little more sense of it - first query - `dig github.com` does a basic DNS query for `github.com`. Its output shows 4 sections - header, question, answer and stats. We only really care about question and answer. The former describes what we asked, which in this case is DNS information about _A Record_ for `github.com`. The later shows answer in form of IP address of website.

Second query above uses `-t NS` to do search for nameservers, which are displayed in the answer section. If you want just the answer without all the clutter around it, you can add `+short` as shown with the third query.

The last query above performs reverse DNS search. This is done by using `-x` argument while passing in IP address instead of hostname. If the information from these queries isn't enough for your debugging, then you can use `+trace` which will show whole delegation path.

If the `dig` command is not your cup of tea and you need to perform only simple queries or for example check DNS connectivity, you can also use previously mentioned `host` command:

```shell
~ $  host github.com
github.com has address 140.82.121.3
github.com mail is handled by 5 alt2.aspmx.l.google.com.

~ $ host 140.82.121.3
3.121.82.140.in-addr.arpa domain name pointer lb-140-82-121-3-fra.github.com.
```

In previous section I mentioned that you should not use `hostname` to set hostname as it will not persist across reboot. There are options for changing hostname persistently - one is to use `hostnamectl set-hostname` or you can also directly modify `/etc/hostname`, which is what `hostnamectl` does anyway.

```shell
~ $ hostnamectl set-hostname some-host.domain.com
~ $ hostnamectl status
   Static hostname: some-host.domain.com
         Icon name: computer-vm
           Chassis: vm
        Machine ID: gd99711b6414efb8bb8791f9f2d4d71a
           Boot ID: 8add50a8560541cb89d1ca2aede6961b
    Virtualization: kvm
  Operating System: Ubuntu 19.04
            Kernel: Linux 5.0.0-15-generic
      Architecture: x86-64
```

There are also other files related to DNS that you might want to check out when troubleshooting. First file that is checked when doing DNS search is `/etc/hosts`. If the entry is not found there, DNS search is performed using `/etc/resolv.conf`, which contains list IPs of nameservers for resolution. You can test hostname resolution manually using `getent hosts example.com`

```shell
~ $ cat /etc/resolv.conf
search some.example.com
nameserver 10.0.2.3 # IP of nameserver to query
nameserver 8.8.8.8
```

## Problems with Connection?

If you're confident that the DNS is not culprit of your problem, then it's time to check connection related issues. One can do that using `ip` command. When looking for any issues with connection, we should start from hardware and work our way up the stack, therefore let's first run `ip link show`:

```shell
~ $ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 52:ee:9a:00:33:f5 brd ff:ff:ff:ff:ff:ff

~ $ ip -s link show ens3
3: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DORMANT group default qlen 1000
    link/ether 52:ee:9a:00:33:f5 brd ff:ff:ff:ff:ff:ff
    RX: bytes  packets  errors  dropped overrun mcast   
    14624090276 14595993 0       0       0       0       
    TX: bytes  packets  errors  dropped carrier collsns 
    2059715701 6107955  0       0       0       0
```

This command displays all network interfaces available on your system. The important field in the output is `state`, which should show `UP`. When you find the interface being currently used, then you can also run `ip -s link show <name>` to see stats. The important fields in the output of said command are `RX` for received data and `TX` for transmitted.

Next, we can take a look at our IP addresses using `ip addr show`. This will show us both IPv4 and IPv6 addresses (if running in dual-stack mode) as well as hardware (MAC) address of the device (`link/ether` line):

```shell
~ $ 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 
       valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 52:ee:9a:00:33:f5 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.189/24 brd 192.168.1.255 scope global noprefixroute dynamic ens3
       valid_lft 72719sec preferred_lft 72719sec
    inet6 fe80::b3ec:c89:50bf:9cdd/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
```

Last but not least from the `ip` family of commands is `ip route`. This gives us IPv4 routing table and can be useful for finding IP of your router, which is the default gateway (first line in output):

```shell
~ $ ip route
default via 192.168.1.1 dev ens3 proto dhcp metric 600  # All other packets are sent through default gateway located at 192.168.1.1
192.168.1.0/24 dev ens3 proto kernel scope link src 192.168.1.189 metric 600  # All packets destined for 192.168.1.0/24 are sent directly through ens3 device
```

In case you need to dig deeper into routing issues or maybe if you are looking for specific router that's causing trouble, you can also use `tracepath`. `tracepath` shows path of your request, where each line is one hop (one router).
    
```shell
~ $ tracepath -b twitter.com
 1?: [LOCALHOST]                      pmtu 1500
 1:  router.asus.com (192.168.1.1)                         2.957ms 
 1:  router.asus.com (192.168.1.1)                         5.767ms 
 2:  192.168.100.1 (192.168.100.1)                         1.469ms 
 ...
 8:  ??? (104.244.42.129)                                 21.838ms reached
     Resume: pmtu 1500 hops 8 back 12
```

Apart from all the issues that you might encounter, there are some fairly basic connection configuration tasks which you might need to perform from time to time. There really are 2 tools that you can choose from - `nmcli` and `nmtui`. The former being classic CLI tool with all the possible options and the later being _curses_-based terminal GUI. I strongly recommend using `nmtui` as it's very easy to use, simple to navigate and fool-proof tool that will help you to avoid errors when configuring connections.

However, If you insist on using `nmcli`, please make sure to check out `man nmcli-examples`, which shows some common usage examples. I will just leave here one _simple_ example for `nmcli`, to _maybe_ deter you from touching it:

```shell
# Adding an ethernet connection profile with manual IP configuration
~ $ nmcli con add con-name my-con ifname ens3 type ethernet ip4 192.168.100.100/24 gw4 192.168.100.1 ip4 1.2.3.4 ip6 abbe::cafe
```

Regardless of whether you will use `nmcli` or `nmtui`, all the configs will be stored in `/etc/sysconfig/network-scripts` (on RHEL/CentOS, anyway). These configs can also be edited directly without any tool, but I do not recommend this as CLI options and file variables have often very different names, so it's hard to see what is what.

One word of caution for dealing with connections. I recommend to either modify these configs using physical console (not over SSH), because in case you mess up, you will not lose access to the machine. If that's not possible, you can alternatively setup deferred job (using `at`) that will revert your changes after hour or so (don't forget to cancel it if your config works).

## Securing Your Server with Firewall

One way to improve security of your server is to make sure only necessary ports are open. To set that up, one might need to deal with `iptables` and `netfilter`, which can be a daunting task. You are therefore better off using some kind of frontend for these tools. If you are on Ubuntu you can go with `ufw` which stands for _uncomplicated firewall_:

```shell
~ $ ufw allow ssh/tcp  # http (80), https (443)
~ $ ufw logging on  # Turn on logging
~ $ ufw enable  # Enable firewall

~ $ ufw allow from 15.15.15.0/24 to any port 22    # Allow SSH connections from specific IP or Subnet
~ $ ufw allow from 15.15.15.0/24 to any port 5432  # Allow PostgreSQL connections from specific IP or Subnet
```

I think the examples above don't really need much of an explanation as it really is _uncomplicated_. If you are not using Ubuntu though, but for example RHEL/CentOS, you will be probably by default running `firewalld` with its `firewall-cmd`. This tool is not so simple, but still pretty easy to use:

```shell
~ $ cat /usr/lib/firewalld/services/postgresql.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>PostgreSQL</short>
  <description>PostgreSQL Database Server</description>
  <port protocol="tcp" port="5432"/>
</service>

~ $ firewall-cmd --permanent --zone=internal --add-service=postgresql  # Add PostgreSQL service instead of specific port
~ $ firewall-cmd --reload  # Apply changes to runtime
```

`firewalld` provides set of files in `/usr/lib/firewalld/services` which describe predefined services. These files make it easier to allow/block various common services like DBs, SSH, HTTP, LDAP, etc. In the example above you can see contents of PostgreSQL service file which defines protocol and port used by PostgreSQL. Thanks to that file we can the above command to allow connection to PostgreSQL database (`--add-service=postgresql`). 

```shell
~ $ cat /usr/lib/firewalld/zones/block.xml 
<?xml version="1.0" encoding="utf-8"?>
<zone target="%%REJECT%%">
  <short>Block</short>
  <description>Unsolicited incoming network packets are rejected. Incoming packets that are related to outgoing network connections are accepted. Outgoing network connections are allowed.</description>
</zone>

~ $ firewall-cmd --add-source=172.217.23.238/32 --zone=block --permanent  # Block traffic from specific IP address
~ $ firewall-cmd --reload
```

There is also a second set of files in `/usr/lib/firewalld/zones/block.xml` which describes zones like Public, DMZ, Block or Work. All traffic is diverted into zones based on criteria like source IP or source port. This classification simplifies management of firewall. In the example above you can see that we can simply block specific IP by diverting all its traffic to `block` zone.

You probably noticed that in both examples we first used command with `--permanent` and then `--reload` , that's because `firewalld` doesn't automatically apply changes to both runtime and persistent configs, therefore it's important to run both of these command to avoid troubleshooting issues that don't exist. 

## Scanning All Kinds of Stuff

Last but not least - it's pretty common networking task to run device scans in network to - for example - find connected machines in local network or to run port scan to see which ports are open or which application is listening and where.

For the former you can use `nmap` which can scan hosts, ports or whole subnets. This tool has _a lot_ of options and really warrants article on its own, but to give you quick overview of the most common arguments and options:
    
```shell
~ $ nmap -sP ...  # Ping Scan - Scan network and find out which devices are up and running
~ $ nmap -sL ...  # List targets in network
~ $ nmap -O ...  # OS detection
~ $ nmap -p 22,21,80,8080 -sV ...  # Detect version of service running on port
~ $ nmap -A ...  #  OS detection, version detection, script scanning, and traceroute

~ $ nmap -p 22,21,80,8080 172.217.17.110  # IP of goggle.com; Also `-p "*"` works for scanning all ports
Nmap scan report for ams15s29-in-f14.1e100.net (172.217.17.110)
Host is up (0.0072s latency).

PORT     STATE    SERVICE
21/tcp   filtered ftp
22/tcp   filtered ssh
80/tcp   open     http
8080/tcp filtered http-proxy

Nmap done: 1 IP address (1 host up) scanned in 1.46 seconds
```

These examples cover basic usage of `nmap` and I personally rarely need anything except for the arguments and options shown above. One extra argument you might want to use though, is `-F` (fast scan) argument, which can be useful to speed up scans, as it will omit most of the ports to be scanned, leaving only the most common ones.

Other tool worth mentioning in this section is `ss` which is replacement for old `netstat`. It's used to investigate sockets (combination of IP, protocol and port). It's very handy when you need to find where some service listens. I generally use `-plat` arguments which shows all (`a`) listening (`l`) TCP (`t`) sockets and their respective processes (`p`):

```shell
~ $ ss -plat
State        Recv-Q    Send-Q     Local Address:Port        Peer Address:Port      Process
LISTEN       0         128              0.0.0.0:ssh              0.0.0.0:*
ESTAB        0         0           192.168.43.1:40790     192.168.43.151:ssh      users:(("ssh",pid=886049,fd=3)) 
LISTEN       0         32                     *:ftp                    *:*
LISTEN       0         128                 [::]:ssh                 [::]:*
LISTEN       0         128                    *:https                  *:*
LISTEN       0         128                    *:http                   *:*
```

## Conclusion

Our jobs - regardless of whether you're developer, DevOps engineer or SRE - often have wide range of duties, so it's important to strive to have at least basic understanding of fundamentals of networking (among other things). It might seem like there's no immediate benefit to learning things like networking, so lot of people tend to ignore it. Learning these fundamentals is in my opinion very much worth the time though, especially in the long run. It should also go beyond just being able to run the above mentioned commands (even though that helps a lot), but rather understanding the concepts and internal workings of things like TCP/IP, DNS, DHCP, TLS, etc.