Linux Networking: A Hands-On Journey Through Bridges, Tools, and Troubleshooting
Introduction — It Started With Just One Bridge
It began as something simple.
It started on a quiet evening with an old mini-server I hadn’t touched in months.
It used to host a few Docker containers and lightweight services, but after some upgrades, it sat idle under my desk collecting dust.
One weekend, I decided to bring it back to life — this time as a lab environment for network testing and running a Kali Linux VM using QEMU/KVM.
The goal: make the VM appear as if it were a real machine on my LAN — same subnet, same gateway, full access to other hosts.
That meant I needed a Linux network bridge (br0) that would connect my VM’s virtual NIC to the same physical network as my host’s NIC.
To do that, I’d need to create a Linux bridge (br0) and attach it to my main Ethernet interface so that both the host and the VM could share the same physical network.
Five minutes of setup, right?
That’s what I thought — It started as a simple idea — bring an old mini server back to life for some lab tests.
But once I began wiring up the bridge, I decided to take the opportunity to revisit everything about Linux networking from the ground up
By the end of it, I’d revisited every corner of how Linux handles interfaces, services, and packets.
Step 1 — Who’s Managing the Network?
Before touching anything, I first needed to find out which network service was running.
Modern Linux systems have several competing managers, and using the wrong one can cause strange side effects.
So I started by checking them all:
linux-admin:~# systemctl is-active NetworkManager
inactive
linux-admin:~# systemctl is-active networking
inactive
linux-admin:~# systemctl is-active systemd-networkd
active
This told me three things:
- NetworkManager (used mostly on desktops) was inactive.
- systemd-networkd was active and managing interfaces.
- ifupdown (the old /etc/network/interfaces system) was not being used either.
Then I confirmed which service actually owned the running interfaces:
linux-admin:~# networkctl list
IDX LINK TYPE OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 enp3s0 ether routable configured
2 links listed.
That confirmed it — systemd-networkd was in charge.
Step 2 — Understanding the Networking Services
🖧 ifupdown / networking.service
The traditional Debian way, using /etc/network/interfaces. Simple but dated.
🟢 Best for: older systems and minimal setups.
🌐 NetworkManager
Handles Wi-Fi, VPNs, and roaming networks. Comes with GUIs and nmcli.
🟢 Best for: desktops and laptops.
⚙️ systemd-networkd
Modern and declarative — uses .network files. Great for headless servers.
🟢 Best for: servers and labs.
📘 netplan
YAML abstraction layer that generates configs for the others (mainly Ubuntu).
🟢 Best for: Ubuntu Server and cloud images.
Since my Debian system was running systemd-networkd, that meant everything under /etc/network/interfaces was ignored.
Good to know — or else I’d be editing the wrong files and wondering why nothing worked.
Step 3 — My Existing Network Configuration
The interface enp3s0 was already set up with a static IP, managed by systemd-networkd.
Here’s what it looked like:
/etc/systemd/network/10-static.network
[Match]
Name=enp3s0
[Network]
Address=192.168.70.81/24
Gateway=192.168.70.254
DNS=8.8.8.8
Checking it:
linux-admin:~# ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
enp3s0 UP 192.168.70.81/24 fe80::aabb:ccff:fe12:2233/64
And verifying connectivity:
linux-admin:~# ping -c 3 192.168.70.254
64 bytes from 192.168.70.254: icmp_seq=1 ttl=64 time=0.54 ms
64 bytes from 192.168.70.254: icmp_seq=2 ttl=64 time=0.48 ms
64 bytes from 192.168.70.254: icmp_seq=3 ttl=64 time=0.53 ms
--- 192.168.70.254 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2015ms
rtt min/avg/max/mdev = 0.48/0.52/0.54/0.03 ms
Everything worked beautifully — fast and clean.
Now it was time to create the bridge.
Step 4 — Creating br0 (and the First Mistake)
To give my future Kali VM direct access to the LAN, I created a bridge interface:
linux-admin:~# ip link add name br0 type bridge
linux-admin:~# ip link set enp3s0 master br0
linux-admin:~# ip link set br0 up
Then I checked:
linux-admin:~# ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
enp3s0 UP 192.168.70.81/24
br0 UP
At first glance, it looks fine.
But here’s the critical part: the IP address (192.168.70.81) should no longer live on enp3s0.
Once a physical interface is enslaved to a bridge, that NIC acts purely as a Layer-2 device — no IPs, no routes.
All addressing must move to the bridge itself (br0).
🧠 Networking Note: Don’t Lock Yourself Out
If you’re doing this over SSH, don’t flush the IP immediately.
Your active SSH session is still bound to the IP on enp3s0.
If you remove it too soon, the bridge hasn’t yet taken over routing, and you’ll lose access.
Here are three safe ways to handle it:
- Local console access (monitor and keyboard) — safest option.
- Rollback timer:
(sleep 120 && ip addr add 192.168.70.81/24 dev enp3s0) &
That re-adds the IP automatically after two minutes if something breaks.
3. Temporary secondary IP: assign another IP to br0 before removing the old one.
Once ready, move the IP configuration to the bridge:
linux-admin:~# ip addr flush dev enp3s0
linux-admin:~# ip addr add 192.168.70.81/24 dev br0
linux-admin:~# ip link set br0 up
Now the setup looks correct:
linux-admin:~# ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
enp3s0 UP
br0 UP 192.168.70.81/24
That’s the proper state: enp3s0 is just a wire, br0 owns the IP.
Step 5 — The Packet Loss Mystery
I ran a simple ping test to the gateway:
linux-admin:~# ping -c 50 192.168.70.254
And got this:
50 packets transmitted, 33 received, 34% packet loss
rtt min/avg/max/mdev = 0.48/0.72/1.66/0.25 ms
Thirty percent loss on a gigabit wired link? Impossible — or so I thought.
But there it was, consistent and repeatable.
That’s when I realized my bridge wasn’t actually bridging correctly; it was competing with the interface below it.
Step 6 — Fixing the Bridge Properly
The fix was simple — but it had to be done correctly.
linux-admin:~# ip addr flush dev enp3s0
linux-admin:~# ip addr add 192.168.70.82/24 dev br0
linux-admin:~# ip link set br0 up
Now the setup looked like this:
linux-admin:~# ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
enp3s0 UP
br0 UP 192.168.70.82/24
And the ping results finally looked healthy again.
The connection stabilized — though I’d later discover that my unmanaged switch was quietly introducing its own issues with LLDP and MAC learning (more on that soon).
🧰 Bonus — Essential Linux Networking Commands
# --- Interface Overview ---
ip -br a # Show interfaces in brief format (status + IPs)
# --- Detailed Interface Info ---
ip -s link show enp3s0 # Show statistics, RX/TX errors, drops, and link state
# --- Add / Remove IP Addresses ---
ip addr add 192.168.70.81/24 dev br0 # Assign an IP to the bridge
ip addr del 192.168.70.81/24 dev enp3s0 # Remove IP from the physical NIC
# --- Manage Interface States ---
ip link set enp3s0 down # Bring interface down
ip link set enp3s0 up # Bring it back up
ip link set br0 up # Ensure the bridge is active
# --- Routing Table ---
ip route show # Show default gateway and active routes
# --- Ping & Traceroute ---
ping -c 4 8.8.8.8 # Test basic connectivity
traceroute 8.8.8.8 # Trace the route through intermediate hops
# --- Check Which Service Manages Networking ---
systemctl is-active systemd-networkd # Check if systemd-networkd is active
systemctl is-active NetworkManager # Check if NetworkManager is active
systemctl is-active networking # Check if legacy ifupdown is used
# --- List All Active Network Units (systemd-networkd only) ---
networkctl list # Overview of interfaces and their states
# --- DNS Debugging ---
resolvectl status # Show DNS servers and per-interface resolution
# --- Bridge Management ---
bridge link show # Show which interfaces belong to bridges
bridge vlan show # View VLANs configured on bridge ports
# --- Real-Time Packet Monitoring ---
tcpdump -i br0 -n # Watch packets on the bridge in real time
# --- Discover Neighbor Devices (Requires Managed Switch) ---
lldpcli show neighbors # Display discovered switch or host via LLDP
# --- Link Speed, Duplex, and EEE ---
ethtool enp3s0 # Show link negotiation, speed, duplex, and driver
ethtool --set-eee enp3s0 eee off # Disable Energy Efficient Ethernet (helps avoid instability)
ethtool -S enp3s0 # Show detailed NIC statistics (errors, FCS drops, missed packets)
# --- Check Open Ports and Listening Services ---
ss -tulpen # Show open TCP/UDP sockets with process IDs
# --- Process Check: Who’s Managing My Network? ---
ps aux | grep -E 'NetworkManager|systemd-networkd|netplan|ifup'
# Quickly see which network service is currently active or controlling interfaces