Simulate Network Layer Connectivity in Linux

CompTIABeginner
Practice Now

Introduction

In this lab, you will explore the fundamental principles of network layer connectivity in a Linux environment. Using Docker containers to simulate two separate nodes, you will learn how to manually assign static IP addresses with the ip command. You will then use the ping utility to test the direct communication path between these nodes on a shared virtual network.

By first configuring the nodes on the same subnet, you will observe successful connectivity. You will then reconfigure one node to a different subnet to witness a predictable communication failure—the 'Destination Host Unreachable' error. This hands-on exercise provides a clear, practical demonstration of how IP subnets govern direct communication and why devices on different logical networks cannot connect without a router.

Prepare the Two-Node Lab Environment

In this step, you will set up the foundational environment for our lab. Instead of using full virtual machines, we will leverage lightweight and isolated Docker containers to simulate two separate network nodes. To enable communication between them, we will first create a custom Docker network. This virtual network acts like a physical network switch, placing both our nodes on the same shared communication segment.

Let's pull the Ubuntu 22.04 Docker image for the nodes.

docker pull ubuntu:22.04

First, let's create the virtual network that our two nodes will use. We'll name it lab_net and assign it a specific IP address range, which will be important in later steps.

Execute the following command in your terminal:

docker network create --subnet=192.168.56.0/24 lab_net

This command tells Docker to create a new bridge network named lab_net and configures it to use the 192.168.56.0/24 subnet. You will see a long unique ID for the network as output, confirming its creation.

e8c1c2a3b4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1

Next, we will launch our first node, a container named node1, and connect it to the lab_net network.

docker run -d --name node1 --network lab_net --cap-add=NET_ADMIN ubuntu:22.04 sleep infinity

Let's break down this command:

  • docker run: The standard command to create and start a new container.
  • -d: Runs the container in "detached" mode, meaning it runs in the background.
  • --name node1: Assigns a simple, memorable name to our container.
  • --network lab_net: Connects the container to the virtual network we created earlier.
  • --cap-add=NET_ADMIN: Grants the container the NET_ADMIN capability, which is required to modify network settings like adding IP addresses.
  • ubuntu:22.04: The Docker image we are using, which provides a standard Ubuntu environment.
  • sleep infinity: A simple command that runs forever to keep the container active.

Now, launch the second node, node2, using a similar command:

docker run -d --name node2 --network lab_net --cap-add=NET_ADMIN ubuntu:22.04 sleep infinity

Finally, let's verify that both of our nodes are running correctly. The docker ps command lists all currently running containers.

docker ps

You should see an output similar to the following, confirming that node1 and node2 are both up and running.

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS     NAMES
a1b2c3d4e5f6   ubuntu:22.04   "sleep infinity"         5 seconds ago    Up 4 seconds              node2
g7h8i9j0k1l2   ubuntu:22.04   "sleep infinity"         15 seconds ago   Up 14 seconds             node1

With both nodes running on the same virtual network, our lab environment is now prepared. In the following steps, we will configure their IP addresses and test their connectivity.

Configure a Static IP on the First Node using ip addr

In this step, you will assign a static IP address to our first container, node1. A static IP is an address that is manually configured and does not change, unlike a dynamic IP which is often assigned automatically by a DHCP server. For our simulation, using static IPs gives us precise control over the network configuration.

We will perform all actions from your main terminal on the host machine, using the docker exec command to run commands inside the node1 container.

First, the base ubuntu:22.04 image is very minimal. We need to install the necessary networking tools. Let's start by updating the package list inside the node1 container:

docker exec node1 apt-get update

You will see output as the container fetches the latest package information.

Next, install the iproute2 package (which provides the ip command) and iputils-ping (which provides the ping command we'll use later).

docker exec node1 apt-get install -y iproute2 iputils-ping

Now that the tools are installed, let's inspect the current network configuration of node1. The network interface inside a standard Docker container is typically named eth0.

docker exec node1 ip addr show eth0

The output will show the details for the eth0 interface. You might see an IP address already assigned by Docker's internal DHCP server (e.g., 192.168.56.2). We are going to add our own static IP.

9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:c0:a8:38:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.56.2/24 brd 192.168.56.255 scope global eth0
       valid_lft forever preferred_lft forever

Now, let's assign the static IP address 192.168.56.10 to node1. The /24 is CIDR notation for the netmask 255.255.255.0, defining the size of the network.

docker exec node1 ip addr add 192.168.56.10/24 dev eth0

This command should not produce any output if it is successful. To confirm the change, run the ip addr show eth0 command again:

docker exec node1 ip addr show eth0

You should now see your new static IP address listed alongside the original one, marked as secondary. This confirms that node1 is now configured with the address 192.168.56.10.

9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:c0:a8:38:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.56.2/24 brd 192.168.56.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.56.10/24 scope global secondary eth0
       valid_lft forever preferred_lft forever

Configure a Static IP on the Second Node in the Same Subnet

In this step, we will configure our second node, node2, with a static IP address. For two devices to communicate directly without a router, they must reside on the same logical subnet. We will assign node2 an IP address from the same 192.168.56.0/24 subnet that we used for node1. This setup logically mimics two PCs connected with a crossover cable, where both are part of the same local network.

First, just as we did for node1, we need to install the necessary networking tools inside the node2 container. Start by updating the package list:

docker exec node2 apt-get update

Next, install the iproute2 and iputils-ping packages on node2:

docker exec node2 apt-get install -y iproute2 iputils-ping

With the tools installed, we can now assign a static IP address to node2. We will use 192.168.56.11, which is on the same subnet as node1 (192.168.56.10) but is a unique address.

docker exec node2 ip addr add 192.168.56.11/24 dev eth0

This command adds the IP address 192.168.56.11 with a /24 netmask to the eth0 network interface of the node2 container. If the command is successful, it will produce no output.

To verify that the IP address was assigned correctly, let's inspect the network configuration of node2:

docker exec node2 ip addr show eth0

The output should now display the newly added static IP address, marked as secondary. This confirms that node2 is properly configured and ready for the next step where we will test connectivity.

11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:c0:a8:38:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.56.3/24 brd 192.168.56.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.56.11/24 scope global secondary eth0
       valid_lft forever preferred_lft forever

Verify Direct Connectivity Between Nodes with ping

In this step, you will test the network connectivity between node1 and node2. Now that both nodes have IP addresses on the same subnet, they should be able to communicate directly. We will use the ping command, a fundamental network utility that sends a small data packet (an ICMP Echo Request) to a target host and waits for a reply. A successful reply confirms that a network path exists between the two devices.

This successful test is analogous to connecting two PCs with a crossover cable, where both devices are on the same local network and can see each other directly.

First, let's try to ping node2 from node1. We will execute the ping command inside the node1 container, targeting node2's IP address, 192.168.56.11.

docker exec node1 ping -c 4 192.168.56.11

Let's break down this command:

  • docker exec node1: Executes the following command inside the node1 container.
  • ping: The utility to test connectivity.
  • -c 4: A flag that tells ping to send exactly 4 packets and then stop. Without this, ping would run indefinitely.
  • 192.168.56.11: The destination IP address of node2.

You should see a successful output, with replies received from node2.

PING 192.168.56.11 (192.168.56.11) 56(84) bytes of data.
64 bytes from 192.168.56.11: icmp_seq=1 ttl=64 time=0.123 ms
64 bytes from 192.168.56.11: icmp_seq=2 ttl=64 time=0.087 ms
64 bytes from 192.168.56.11: icmp_seq=3 ttl=64 time=0.091 ms
64 bytes from 192.168.56.11: icmp_seq=4 ttl=64 time=0.085 ms

--- 192.168.56.11 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3074ms
rtt min/avg/max/mdev = 0.085/0.096/0.123/0.015 ms

The "4 received, 0% packet loss" line confirms that the connection is working. Now, let's verify the connection in the other direction by pinging node1 from node2.

docker exec node2 ping -c 4 192.168.56.10

Again, you should see a successful series of replies, confirming that communication is bidirectional.

PING 192.168.56.10 (192.168.56.10) 56(84) bytes of data.
64 bytes from 192.168.56.10: icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from 192.168.56.10: icmp_seq=2 ttl=64 time=0.088 ms
64 bytes from 192.168.56.10: icmp_seq=3 ttl=64 time=0.092 ms
64 bytes from 192.168.56.10: icmp_seq=4 ttl=64 time=0.089 ms

--- 192.168.56.10 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3081ms
rtt min/avg/max/mdev = 0.088/0.092/0.099/0.004 ms

Success! Both nodes can communicate with each other, just as if they were two computers on the same local network segment.

Reconfigure the Second Node to a Different Subnet

In this step, we will intentionally break the direct connectivity between our two nodes. We'll do this by reconfiguring node2 to be on a completely different IP subnet from node1. This simulates a scenario where two devices are physically connected but are logically separated into different networks.

Currently, node1 is on the 192.168.56.0/24 subnet. We will now move node2 to the 192.168.58.0/24 subnet. Because the third number (octet) is different (56 vs. 58), these are considered separate subnets.

To ensure node2 is completely isolated on the new network, we must first remove all existing IP addresses from its eth0 interface. This includes both the static IP we added earlier and the original IP that Docker assigned automatically. The ip addr flush command is the correct tool for this job.

docker exec node2 ip addr flush dev eth0

This command removes all IP configurations from eth0, ensuring a clean slate. It should not produce any output if successful.

Now, let's add the new IP address, 192.168.58.11, which belongs to the new subnet.

docker exec node2 ip addr add 192.168.58.11/24 dev eth0

To confirm the changes, let's inspect the network configuration of node2 again.

docker exec node2 ip addr show eth0

You will see that all old IP addresses are gone, and only the new one (192.168.58.11) exists. This confirms node2 is no longer on the 192.168.56.0/24 subnet.

11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:c0:a8:38:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.58.11/24 scope global eth0
       valid_lft forever preferred_lft forever

With node1 on the 192.168.56.0/24 subnet and node2 on the 192.168.58.0/24 subnet, they are now logically isolated.

Test Connectivity and Observe Failure

In this final step, we will attempt to ping between the nodes again. With node2 now truly isolated on a new subnet, we expect communication to fail, but in two different and important ways that reveal how networking layers operate.

First, let's try to ping node2's new IP address (192.168.58.11) from node1.

docker exec node1 ping -c 4 192.168.58.11

Observe the output. The command will time out, resulting in 100% packet loss.

PING 192.168.58.11 (192.168.58.11) 56(84) bytes of data.

--- 192.168.58.11 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3076ms

This timeout happens because node1 still has a route to the Docker network, so it sends the ping packet. The network forwards it to node2. However, since node2 is no longer on that network and has no route back, it cannot send a reply. The ping from node1 never gets a response.

Next, let's test the other direction. Try to ping node1 from node2.

docker exec node2 ping -c 4 192.168.56.10

This time, you will see a different error message almost immediately.

ping: connect: Network is unreachable

The message ping: connect: Network is unreachable is significant. It's an immediate response from the operating system on node2. It means the OS checked its routing table, saw that the destination 192.168.56.10 is on a network it has no path to, and refused to even attempt to send the packet. This is a direct result of using ip addr flush, which cleared all routes, completely isolating the container from other networks.

This lab has successfully demonstrated the critical role of IP subnetting and routing. When devices are on the same subnet, they can communicate directly. When they are on different subnets, a Layer 3 device like a router with appropriate routes is required to forward traffic between them.

Summary

In this lab, you learned to simulate a two-node network environment using Docker containers connected to a custom bridge network. You practiced the essential Linux skill of configuring static IP addresses on network interfaces using the ip addr command. By assigning IP addresses from the same subnet to both nodes, you successfully verified direct Layer 3 connectivity between them with the ping utility, demonstrating the fundamental requirement for communication on a local network segment.

The lab further illustrated a critical networking concept by reconfiguring one node with an IP address from a different subnet after completely flushing its original network configuration. The subsequent attempts to communicate via ping failed with two distinct results: a timeout in one direction and a 'Destination Host Unreachable' error in the other. This outcome effectively showed that nodes on different logical subnets cannot communicate directly without a router, and it highlighted how routing tables and interface configuration affect network connectivity.