evilfactorylabs

Cover image for Setup Bridged Network di libvirt
Rizaldy
Rizaldy

Posted on

Setup Bridged Network di libvirt

3 minggu lalu saya menjalankan Asahi Linux (Fedora) di Mac Mini M2 (via asahi-fedora-builder) dan menjadikannya anggota baru di cluster jaringan rumah a.k.a homelab. Server ini menjalankan aplikasi-aplikasi krusial seperti database dan beberapa aplikasi yang cukup rakus dengan memory dan CPU seperti PeerTube.

Update: Per tulisan ini diterbitkan, Asahi Linux sudah dapat dipasang di Mac Mini M2 melalui "cara resmi" yang dapat dilihat disini.

Aplikasi-aplikasi tersebut berjalan di VM menggunakan libvirt. Dan, yes, menggunakan KVM.

Untuk konfigurasi jaringan di libvirt, bawaannya adalah menggunakan NAT-based network, yang singkatnya, si libvirt server bertindak sebagai router, dan, ya, mungkin sudah bosan ya (gw) membahas tentang NAT?

Jenis jaringan bawaan si libvirt yang NAT-based ini umumnya menggunakan subnet 192.168.122.0/24, dan traffic masuk dari/ke subnet tersebut terjadi di host tersebut.

Saya ada 2 subnet yang satu 192.168.122.0/24 dan yang satu 192.168.123.0/24, yang mana berada di 2 jaringan berbeda, lalu bagaimana cara menghubungkannya?

NAT!

Cara paling mudah adalah dengan menggunakan Tailscale:

  • Setup subnet router untuk kedua subnet tersebut
  • Aktifkan --accept-routes
  • ...
  • Profit

Ini ideal untuk setup "remote access" ke mesin yang tidak menjalankan Tailscale node. Tapi proses routing tetap melalui device tailscale0 yang entah bagaimana sering terjadi masalah khususnya pasca reboot dan satu-satunya solusinya adalah menjalankan ulang tailscale daemon sehingga subnet 122 dan 123 bisa kembali berkomunikasi.

Dan, perlu konfigurasi lagi di router (harusnya di bagian "next hop") untuk memastikan si subnet router dapat bertukar paket, dan ini relatif mudah.

Tapi karena cukup frustasi karena harus menjalankan ulang tersebut (yang mana tidak bisa dilakukan secara remote) akhirnya tulisan ini dibuat.

Subnet subnet something

Komputer berkomunikasi dengan komputer lainnya menggunakan alamat IP, dan mari kita kesampingan pembahasan untuk media lain seperti bluetooth dan inframerah (pernah kirim lagu dengan menempelkan 2 hp?).

Komputer terhubung melalui sebuah "modular connector" yang umumnya disebut sebagai "kabel LAN/RJ/UTP" jika bertanya ke toko listrik. Kabel tersebut menghubungkan sebuah komponen yang bernama Network Interface Controller/Card (NIC) yang nantinya digunakan untuk bertukar paket/informasi.

Secara teknis, menghubungkan dua komputer dengan 1 "kabel LAN" yang saling terhubung saja sudah cukup selama tidak ada konflik di penggunaan alamat IP dan Media Access Control (MAC) Address. Bagiamana bila komputer yang ingin berhubungan ada 3 atau 30 atau 300?

Media itu beragam, ada Hub; Switch, dan Router. Media atau perangkat tersebut umumnya menyediakan lebih dari 1 port dan yang cukup favorit untuk skala rumah adalah 5, yang mana dapat menghubungkan 4 komputer sekaligus melalui kabel. Dan mari kita kesampingkan pembahasan tentang Router vs Switch.

Dalam membuat jaringan, biasanya kita melakukan segmentasi untuk sebuah alasan. Misal, dengan membuat jaringan per-lantai, sehingga semua perangkat di lantai dua tidak perlu tersambung semuanya ke lantai satu menggunakan kabel sepanjang 20 meter. Dalam praktiknya, tujuan melakukan segmentasi adalah untuk alasan keamanan dan efisiensi.

Subnet (subnetwork) gampangnya adalah jaringan dalam jaringan. Secara teori, total alamat IP (v4) yang bisa digunakan adalah 4,294,967,296 (2^32) dan bisa dikategorikan berdasarkan "network mask" dan "prefix size" nya (IP class). Paling umum di skala jaringan yang kecil adalah menggunakan prefix /24, ada total 256 (2^(32-24)) alamat IP yang bisa digunakan.

Seharusnya 2 alamat IP sudah "dipesan" untuk alamat broadcast, jadi tersisa hanya 254. Jika melihat subnet 192.168.122.0/24, berarti total alamat IP yang bisa digunakan oleh komputer pada jaringan tersebut adalah 192.168.122.1 s/d 192.168.122.254.

Yang mana 192.168.122.0 dan 192.168.122.255 adalah alamat broadcast.

Singkatnya, membuat 300 VM di jaringan dengan subnet /24 tidak akan ideal bila setiap VM memiliki 1 alamat IP dari subnet tersebut.

DHCP DHCP something

Mungkin ada pertanyaan: siapa yang memberikan alamat IP kepada komputer-komputer saya? Setiap komputer pada dasarnya dapat mengatur alamat IP nya secara mandiri, namun dalam praktiknya, umumnya menggunakan sebuah teknologi bernama DHCP.

Model komunikasi DHCP adalah client-server: si dhcp client akan "mem-broadcast" paket, si dhcp server akan menawarkan alamat IP, si dhcp client akan mengajukan ip tersebut, lalu si dhcp server akan mengakui jika alamat IP tersebut adalah milik si X, yang mana nilai X adalah MAC address. Alur ini terkenal dengan Discover, Offer, Request, Acknowledge (DORA).

Bagaimna jika ada 2 atau lebih DHCP server? Bagiamana jika yang menawarkan alamat IP tersebut bukanlah DHCP server yang seharusnya? Well, itu pembahasan lain.

Setiap router umumnya menjalankan DHCP server, dan jika di lingkungan GNU/Linux umumnya melalui program bernama dnsmasq.

Saat menjalankan DHCP server, seharusnya kita perlu mengkonfigurasi dari subnet yang akan digunakan; jangkauan alamat IP yang bisa digunakan, sampai ke DNS server yang harus digunakan.

Routing antar subnet melalui Tailscale

Baiklah cukup sudah computer networking crash course nya dan kita kembali ke topik utama. Gambaran sekarang dari jaringan homelab kita misal seperti ini:

Image description

Untuk saat ini, routing terjadi di overlay network via Tailscale, jadi bila vm3 di server2 melakukan ping ke vm1 di server1, alur nya adalah seperti ini di perspektif server2:

  • vnet20 P IP 192.168.122.4 > 192.168.123.2: ICMP echo request
  • virbr0 In IP 192.168.122.4 > 192.168.123.2: ICMP echo request
  • tailscale0 Out IP 100.80.2.61 > 192.168.123.2: ICMP echo request
  • tailscale0 In IP 192.168.123.2 > 100.80.2.61: ICMP echo reply
  • virbr0 Out IP 192.168.123.2 > 192.168.122.4: ICMP echo reply
  • vnet20 Out IP 192.168.123.2 > 192.168.122.4: ICMP echo reply

Dan seperti ini di perspektif si server1:

  1. tailscale0 In IP 100.80.2.61 > 192.168.123.2 ICMP echo request
  2. virbr0 Out IP 192.168.123.1 > 192.168.123.2: ICMP echo request
  3. vnet1 Out IP 192.168.123.1 > 192.168.123.2: ICMP echo request
  4. vnet1 P IP 192.168.123.2 > 192.168.123.1: ICMP echo reply
  5. virbr0 In IP 192.168.123.2 > 192.168.123.1: ICMP echo reply
  6. tailscale0 Out IP 192.168.123.2 > 100.80.2.61: ICMP echo reply

Jika tidak melakukan "rewrite" source IP nya (lihat di poin nomor 2), berarti seperti ini:

  1. tailscale0 In IP 100.80.2.61 > 192.168.123.2: ICMP echo request
  2. virbr0 Out IP 100.80.2.61 > 192.168.123.2: ICMP echo request
  3. vnet1 Out IP 100.80.2.61 > 192.168.123.2: ICMP echo request
  4. vnet1 P IP 192.168.123.2 > 100.80.2.61: ICMP echo reply
  5. virbr0 In IP 192.168.123.2 > 100.80.2.61: ICMP echo reply
  6. tailscale0 Out IP 192.168.123.2 > 100.80.2.61: ICMP echo reply

Setup bridge device

Bridged Network hanya possible bila si libvirt server (host) tersambung ke LAN melalui Ethernet. Jika tidak (misal seperti menggunakan Wi-Fi), tetaplah menggunakan NAT-based network (default).

Anyway, ini kita perlu sedikit pengetahuan terkait ehm per sysadmin-an. Biasanya, mesin dengan sistem operasi GNU/Linux menggunakan antara NetworkManager atau systemd-networkd untuk mengatur jaringan.

Disini anggap kita menggunakan NetworkManager, dan tools favorit saya adalah nmcli(1).

First thing first, kita perlu membuat "bridge devices". Untuk memastikan (iseng) apakah sudah ada bridge devices di komputer mu, bisa jalankan nmcli dev status yang misal output nya seperti ini:

$ nmcli device status

DEVICE        TYPE      STATE                   CONNECTION       
eno1          ethernet  connected               Wired connection 1
tailscale0    tun       connected (externally)  tailscale0  
virbr0        bridge    connected (externally)  virbr0     
vnet20        tun       connected (externally)  vnet20     
vnet24        tun       connected (externally)  vnet24     
vnet28        tun       connected (externally)  vnet28    
Enter fullscreen mode Exit fullscreen mode

Jika menggunakan Docker, kemungkinan sudah ada bridge devices yang dibuat doi. fwiw.

Untuk mulai membuatnya, bisa menjalankan nmcli conn add seperti ini:

$ nmcli conn add type bridge con-name br0 ifname br0
Enter fullscreen mode Exit fullscreen mode

Penggunaan br0 hanyalah preferensi. Bisa menggunakan nama lain seperti br00 atau b00135 misalnya.

Setelah itu, kita perlu membuat device type Ethernet baru yang nantinya digunakan sebagai "jembatan" dengan device fisik yang ada di komputer (yang dalam kasus saya adalah eno1):

$ nmcli conn add type ethernet slave-type bridge con-name bridge-br0 ifname eno1 master br0
Enter fullscreen mode Exit fullscreen mode

Penggunaan nama bridge-br0 adalah preferensi, sekali lagi.

Setelah itu, kita bisa melihat device baru kita menggunakan nmcli conn show seperti ini:

$ nmcli conn show

NAME          UUID                                  TYPE      DEVICE     
br0           836ce290-afee-4354-a1e2-ee2b5b4cd145  bridge    br0        
netplan-eno1  10838d80-caeb-349e-ba73-08ed16d4d666  ethernet  eno1       
tailscale0    59f3e6fa-ce8b-4670-973f-bcd1310ef928  tun       tailscale0 
virbr0        1e834475-b336-454d-a30a-dd26300fdb36  bridge    virbr0     
vnet20        5ede6630-c754-49cc-a82f-2a5a6a268243  tun       vnet20     
vnet24        96555b9a-363c-466d-b22e-c7aa4b16a279  tun       vnet24     
vnet28        94be3f7f-79da-4f01-a42c-f3647a78c610  tun       vnet28
Enter fullscreen mode Exit fullscreen mode

Lalu kita aktifkan:

$ nmcli conn up br0

Connection successfully activated (master waiting for slaves) (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/69
Enter fullscreen mode Exit fullscreen mode

Dan kita matikan Ethernet devices yang sudah berjalan, karena you know, master waiting for slaves.

$ nmcli conn down netplan-eno1

Connection 'netplan-eno1' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/60
Enter fullscreen mode Exit fullscreen mode

Nama netplan-eno1 diatas bisa disesuaikan dengan nama device lama dengan type Ethernet di keluaran nmcli conn show sebelumnya.

Jika tersambung melalui SSH, lalu terputus, tenang, jangan panik. Berdiri dulu selama 15 detik, tarik napas, ambil air mineral di kulkas, bikin kopi, lihat pemandangan di jendela, dan relax. Nikmati hidup sebentar setelah cosplay menjadi sysadmin.

Koneksi SSH terputus karena alamat IP kemungkinan berubah (khususnya jika MAC Address nya somehow berubah). Lihat di router untuk alamat IP barunya, dan sambung kembali melalui SSH.

Daan device br0 kita sudah muncul!

$ nmcli dev status

DEVICE        TYPE      STATE                   CONNECTION 
br0           bridge    connected               br0        
tailscale0    tun       connected (externally)  tailscale0 
virbr0        bridge    connected (externally)  virbr0     
vnet20        tun       connected (externally)  vnet20     
vnet24        tun       connected (externally)  vnet24     
vnet28        tun       connected (externally)  vnet28     
eno1          ethernet  connected               bridge-br0
Enter fullscreen mode Exit fullscreen mode

Hell yeah.

Setup bridged network di libvirt

Ini bagian yang paling singkat. Dan saya menganggap anda menggunakan virsh(1) juga. Disini saya ingin mengubah konfigurasi jaringan VM yang sebelumnya menggunakan bridge virbr0 menjadi br0 yang telah kita buat tadi.

First thing first, kita perlu membuat "network" baru di virsh. Buat file dengan nama bridge.xml dengan konten seperti ini:

<network>
    <name>bridge-host</name>
    <forward mode="bridge"/>
    <bridge name="br0"/>
</network>
Enter fullscreen mode Exit fullscreen mode

Bagian bridge-host dan br0 bisa disesuaikan sesuai kebutuhan dan selera. Selanjutnya, kita import konfigurasi tersebut:

$ virsh net-define bridge.xml

Network bridge-host defined from bridge.xml
Enter fullscreen mode Exit fullscreen mode

Sip. Lalu kita jalankan:

$ virsh net-start bridge-host

Network bridge-host started
Enter fullscreen mode Exit fullscreen mode

Dan cek untuk memastikan:

$ virsh net-list

 Name          State    Autostart   Persistent
------------------------------------------------
 bridge-host   active   no          yes
 default       active   yes         yes

$ virsh net-info bridge-host

Name:           bridge-host
UUID:           28b5ee7b-a258-4cf4-b64a-4d5231d7ffcf
Active:         yes
Persistent:     yes
Autostart:      no
Bridge:         br0
Enter fullscreen mode Exit fullscreen mode

Sip. Kita buat tu network "autostart" dengan perintah virsh net-autostart bridge-host.

Lalu kita network interfaces di VM yang digunakan menggunakan virsh edit <name>. Bisa jalankan virsh list jika lupa name nya apa.

Cari bagian <interface>:

-   <interface type='bridge'>
+   <interface type='network'>
-      <source bridge='virbr0'/>
+      <source network='bridge-host'/>
    </interface>
Enter fullscreen mode Exit fullscreen mode

Alternatif lain jika tidak ingin mendefinisikan jaringan, bisa langsung seperti ini:

    <interface type='bridge'>
-      <source bridge='virbr0'/>
+      <source bridge='br0'/>
    </interface>
Enter fullscreen mode Exit fullscreen mode

Dan reboot menggunakan virsh reboot <name> atau virsh shutdown <name> dan virsh start <name>. Untuk memastikan vm sudah menggunakan DHCP server baru, jalankan ip a dan pastikan sudah menggunakan subnet yang seharusnya.

Bagaimana jika VM tiba-tiba tidak mendapatkan IP??? Santai. Jalankan dhclient untuk yang menggunakan Debian atau /etc/init.d/networking restart untuk Alpine enjoyer dan relaxxx.

Routing antar komputer dalam komputer

Sekarang seharusnya routing tidak berada di overlay network lagi (so long, Tailscale!). Masih dengan kasus yang sama (server2 melakukan ping ke vm1 di server1), berikut sekarang gambarannya di server2:

  • end0 P IP 192.168.100.45 > 192.168.100.47: ICMP echo request
  • vnet4 Out IP 192.168.100.45 > 192.168.100.47: ICMP echo request
  • vnet4 P IP 192.168.100.47 > 192.168.100.45: ICMP echo reply
  • end0 Out IP 192.168.100.47 > 192.168.100.45: ICMP echo reply

Daan di server1:

  • vnet31 P IP 192.168.100.45 > 192.168.100.47: ICMP echo request
  • eno1 Out IP 192.168.100.45 > 192.168.100.47: ICMP echo request
  • eno1 P IP 192.168.100.47 > 192.168.100.45: ICMP echo reply
  • vnet31 Out IP 192.168.100.47 > 192.168.100.45: ICMP echo reply

Setup VLAN

Ini bonus dan opsional. Karena Access Point (router "Wi-Fi") saya tersambung ke router utama, berarti doi menjadi "gateway" terhadap perangkat-perangkat yang berada di 1 subnet yang sama yakni 192.168.100.0/24.

Image description

Tentu saja ini bisa diatasi dengan membuat firewall rules, tapi karena saya menggunakan Switch, saya bisa melakukan isolasi secara "virtual" menggunakan 802.1Q alias dot1q:

Image description

Sip.

Dengan begini, aplikasi-aplikasi yang terpasang di perangkat nirkabel tidak bisa iseng menjelajahi LAN saya.

Dan jika harus kembali berurusan dengan firewall/packet filter, untuk apa tulisan ini diterbitkan?

Mengawinkan antar subnet

Remote access ke vm sudah bisa dilakukan darimanapun, namun ada sedikit kendala saat melakukan remote connect dari rumah. Jaringan 192.168.1.0/24 (AP) dan 192.168.100.0/24 (homelab) logically isolated melalui VLAN sehingga "direct connect" alias peer-to-peer hampir tidak bisa terjadi: koneksi selalu "di relay" melalui DERP yang meskipun saya sudah menjalankan DERP sendiri di Jakarta, tempat homelab saya berada sekarang — bebas untuk digunakan siapapun btw.

Artinya, saat saya mengakses vm water7 di server nuc dari steam deck yang jaraknya hanya 5cm dari tempat saya duduk, paket akan dibawa terlebih dahulu ke router saya; lalu ke internet, lalu ke vm yang berjalan di aws, balik lagi ke router saya, ke server nuc, dan barulah sampai.

Image description

Solusinya relatif sederhana: jalankan tailscale yang satu member dengan si VLAN, yang dalam kasus ini adalah ehm si Edge Router.

Image description

Problem solved. Di perangkat yang tidak terpasang Tailscale tetap tidak bisa mengakses 192.168.100.0/24 dan setiap perangkat di 192.168.100.0/24 tetap menggunakan routing di default interface.

Image description

Lalu kita memiliki masalah lagi (lmao): Kita tidak bisa direct connect ke host antar tailscale node (diluar LAN) karena 1) host menggunakan subnet yang sama via bridge network dan 2) kita tidak mengaktifkan --accept-routes di host.

Solusinya yang paling ideal nya adalah... dengan uninstall tailscale di host tersebut. Tapi jika begitu saya tidak bisa menggunakan fitur Tailscale SSH ke si host, jadi saya biarkan koneksi ke host melalui jaringan rumah melalui DERP, karena jarang banget juga remote connect ke host selain untuk keperluan yang langka (seperti provision vm baru i guess)

Cloudflare Tunnel

5 hari sebelum tulisan ini diterbitkan ada kendala dengan setup Cloudflare Tunnel di jaringan saya dan setelah melakukan trial-error dari tcpdump sampai port mirroring, baru ketemu masalah utama nya: TUNNEL_TRANSPORT_PROTOCOL.

Konfigurasi ini untuk menentukan protokol yang digunakan untuk membuat koneksi antara cloudflared alias agent yang berjalan di mesin saya dan jaringan Cloudflare. Tanpa konfigurasi khusus, nilai dari TUNNEL_TRANSPORT_PROTOCOL adalah auto alias menggunakan QUIC lalu berpindah menjadi http2 bila koneksi UDP tidak bisa terbuat.

Berbeda dengan HTTP/1.1 ataupun HTTP/2 yang menggunakan TCP, QUIC (yang digunakan di HTTP/3) menggunakan UDP sebagai transport layer nya (meskipun QUIC itu sendiri pun adalah transport layer).

Masalahnya, di lingkungan virtualisasi khususnya di network driver yang menggunakan "virtio" besar kemungkinan akan menggunakan module "vhost-net" yang gampangnya untuk mengurangi overhead virtualisasi dengan memindahkan tugas pemrosesan paket virtio di "userspace" (proses qemu) ke "kernel space" (driver vhost-net). Yang singkatnya, ini akan bermasalah saat berurusan khususnya dengan traffic UDP dari host ke guest, yang long story short, akan membuat banyak packet drop.

Solusinya adalah dengan men-disable module vhost-net di guest, dan expect CPU akan lebih sibuk dari biasanya.

Solusi yang lain adalah (yang saya gunakan) dengan mengatur nilai TUNNEL_TRANSPORT_PROTOCOL menjadi http2. Dan pada akhirnya pun di perspektif end users akan menggunakan HTTP/3 (jika komputer mereka mendukung) saat tersambung ke jaringan si Cloudflare.

Dan akses pun sekarang sudah tidak intermiten lagi.

Disable Netfilter di bridge

Ini bagian dari "kernel tuning" ala-ala yang direkomendasikan juga saat menggunakan bridge network di libvirt.

net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0
Enter fullscreen mode Exit fullscreen mode

Jika merujuk ke halaman ini issue tersebut dibuat pada tahun 2009. Dan jika tidak yakin dengan nilai net.bridge.bridge-nf-call-iptables jalankan sysctl -a | grep net.bridge.bridge-nf-call di host.

Jika nilainya something selain 0, mungkin bisa dipertimbangkan untuk mengubahnya menjadi 0.

Penutup

Setup bridged network mungkin cukup ideal untuk setup yang sederhana.

Untuk setup yang lebih kompleks, sepertinya bisa mempertimbangkan menggunakan VLAN, VXLAN, atau apapun solusi lain yang biasanya menggunakan overlay network.

Sebagai penutup, jika ada pertanyaan "bagaimana cara menghubungkan banyak VMs di hosts yang berbeda?" salah satu jawabannya adalah dengan membuat bridged network ("physical device sharing") yang baru saja dibahas disini.

Top comments (0)