KVM Public IPv4 – How to Automatically Assign a Public IPv4 to Every KVM VM Created

ip addresskvm-virtualizationlibvirtnetworkingvirtualization

Im wondering how would i go on about assigning a public ipv4 to each vm thats being created.

Setup :
Host Server with 3 IPs on CentOS8
using libvirt and kvm to virtualize
Bridge br0 using eth0 as interface.

After a lot of Trial and Error i managed to do it manually by using a bridge and assigning the IP-Address to the interface of the Guest OS Network File.

Though i wish this would be automatic considering that if i reinstall the OS now it would go back to not having the IP-Address and id have to connect to the Guest and edit the ipv4-address in the network files manually everytime. How can i avoid this ?

Goal : each IPv4 is hardlocked to a Virtual Machine and will stay no matter if the OS gets reinstalled.

Optional Goal : If any IPv4 from the Host OS is unused, it should be assigned to the next VM created.

Do i have to code my own software to do this everytime or is there a simpler way ?

Best Answer

This is what DHCP is for.

You can freely choose their MAC addresses, right? Setup a DHCP server on the system in the same (possibly, virtual) ethernet segment as VMs, and bind your IPs to certain MACs.

You will also need to distribute some routes (use DHCP options 121 and 249). The machine with DHCP server itself doesn't need to have public IP or IP in the same network as all clients; the communication with DHCP server takes place when there are still no addresses configured on the client anyway.

If you use ISC DHCP, you need to do something like the following. I am assuming your host machine is your router and NAT box for VMs and it also will host your DHCP server. If you want to do other way, slight adjustments will be required:

  • define options 121 and 249 somewhere around top of the dhcpd.conf:
option rfc3442-classless-static-routes code 121 = array of integer 8;
option ms-classless-static-routes code 249 = array of integer 8;

probably, new versions don't need this, but mine does.

  • create a shared-network block, put your dynamic private subnet and your public addresses as "subnets" with masks 32:
option routers 192.168.210.1;
option domain-name-servers 8.8.8.8, 8.8.4.4;

shared-network libvirt-vm-net {
    subnet 192.168.210.0 netmask 255.255.255.0 {
        range 192.168.210.2 192.168.210.254;
    }

    subnet 192.0.2.1 netmask 255.255.255.255 {
        option rfc3442-classless-static-routes 32, 192,168,210,1, 0,0,0,0, 0, 192,168,210,1;        
        option ms-classless-static-routes      32, 192,168,210,1, 0,0,0,0, 0, 192,168,210,1;        
    }

    ...
}

I am assuming your "private" network for machines behind NAT will be 192.168.210.0/24 and the host will be .1 in that network (assigned to a bridge). Static classless routes (options 121 and 249) setting will add following routes:

ip route add 192.168.210.1 dev eth0
ip route add default via 192.168.210.1

which are needed for everything to work despite the fact there is no subnet containing 192.0.2.1/32 and 192.168.210.1; better try such setup in lab environment if you are unsure how this routing works

  • create host definitions with your desired MAC addresses bound to your static IP addresses:
host public-server-1 { hardware ethernet 00:11:22:33:44:55; fixed-address 192.0.2.1; }
...
  • add static neighbour ("ARP") entries for those MACs into /etc/ethers (on the host):
00:11:22:33:44:55 192.0.2.1
...
  • add static routes towards those addreses through the corresponding bridge interface; I don't know how to do this using standard networking configuration of your OS distro, but generic Linux way is like this:
ip route add 192.0.2.1 dev br0
...

Then make sure you assign those MACs to your important VMs within libvirt configs. Other VMs (whose MACs aren't bound) will get their addresses from the configured range as usual.

Actually I implemented and tested this setup in even "more complex" setup (DHCP, host and router all were distinct systems), also I dont' use libvirt and my host is PVE. Even PXE booting perfectly works for both "normal" subnetwork machines (like 192.168.210.0 in the example) and for "public IP" machines (like 192.0.2.1).

To allow all VMs to access Internet you have to use NAT for private IPs and to not to use it for public; it is not a problem, use the rule like iptables -t nat -A POSTROUTING -o <physical-interface-with-public-ip> -s <private-ip-range> -j MASQUERADE. This way, your private range will be translated to the public-facing address of the host itself, but public addresses will not get translated, but will be routed as they are. Again, it is no problem having public and private addresses on the same network segment and walking through the same virtual NIC.

Related Topic