NOTE: the below assumes you’re running the VMs in NAT mode.

nowadays most distros are very good at acquiring dynamic IP addys from DHCP servers during the install process, and libvirt comes with a lightweight DHCP server (as part of dnsmasq) which works just fine for the job at hand.

however, sometimes it’s handy to ensure your VMs have static IPs while playing around, e.g. on a lappy. there are a couple ways of achieving this, depending on whether you can easily change the network config of VMs themselves and are inclined to do so.

NOTE: in the below, keep in mind that changes done to libvirt XML network definitions with virsh net-edit do NOT take effect until the network is destroyed and restarted. therefore, these changes are best done ahead of time, before there are a bunch of VMs live on the affected network. to apply your changes on the fly, choose the virsh net-update instructions, which are a bit more tricky.

assigning proper static IPs

in this case you simply manually configure the relevant network interfaces inside the VMs for static addys (including IP, netmask, etc). which would be the end of it, if not for the fact that at least some versions of libvirt tend to assign the entire range of IP addys of the virtual bridge they create to the DHCP server by default. so, you’ll need to first reserve a block of addys in libvirt network config for static assignment, unless you want to spend time debugging IP collisions. actual VM-side config is distro-specific and is out of scope here, what follows is the libvirt side of things.

first, figure out which libvirt network you want to configure (often default):

# virsh net-list --all
     Name                 State      Autostart     Persistent
    ----------------------------------------------------------
     default              active     yes           yes

where:

--all   ensures that you're viewing both the live networks and those only
        defined, but not actively started by libvirt

check out the relevant net config to see if it doesn’t already have a reserved range using:

# virsh net-dumpxml <net-name>

where:

<net-name> is the network name from the 'virsh net-list' output

e.g.:

# virsh net-dumpxml default
    <network connections='1'>
      <name>default</name>
      <uuid>1234abcd-a6f8-4f46-a8a5-123456abcdef</uuid>
      <forward mode='nat'>
        <nat>
          <port start='1024' end='65535'/>
        </nat>
      </forward>
      <bridge name='virbr0' stp='on' delay='0'/>
      <mac address='52:54:00:11:22:33'/>
      <ip address='192.168.122.1' netmask='255.255.255.0'>
        <dhcp>
          <range start='192.168.122.2' end='192.168.122.254'/>
        </dhcp>
      </ip>
    </network>

<range start=... end=.../> tag inside the <dhcp> stanza spills the beans, in this case, the entire range of the bridge is handed over to dnsmasq for dynamic IP addys.

offline static IP range reservation

the easiest and hassle-free way to go about it is to just tear down the network using:

#  virsh net-destroy <net-name>

then edit the config XML using:

# virsh net-edit <net-name>

which will essentially just pop up a copy of the corresponding XML file in an editor for you to edit (or, if you’re adventurous, just hack at it in-situ, which might be:

/etc/libvirt/qemu/networks/default.xml

or whatever). find the <range start= tag of interest, edit the start and/or end IPs, save, and finally restart the network:

# virsh net-start <net-name>

needless to say, the VMs already live on that network (or, rather, their connections!) will not like their net being yanked down from under them like that…

online IP ranges modification to reserve static IPs

an alternative and less intrusive way uses virsh net-update which can operate on live nets.

unfortunately, since virsh net-update can’t modify ip-dhcp-range stanzas, you’ll need to delete the relevant one and add a new one instead. make sure to exactly specify an existing stanza to delete, e.g. by copy-pasting from the ‘virsh net-dumpxml’ output, otherwise virsh will refuse to cooperate. fortunately, libvirt appears to allow multiple overlapping addy ranges to co-exist (and, more importantly, apparently so does dnsmasq as that’s where these configs are immediately propagated, and you can verify afterwards under:

/var/lib/libvirt/dnsmasq/default.conf

that you ended up with a desired IP addy range). so you can add the new, narrower range first, then delete the original range, without leaving the DHCP server empty-handed at runtime, using:

# virsh net-update <net-name> add ip-dhcp-range "<new-range>" --live --config
# virsh net-update <net-name> delete ip-dhcp-range "<old-range>" --live --config

where:

<net-name>  - libvirt network name, e.g. 'default'
<old-range> - verbatim "<range start='...' end='...'/>" tag from the
        current 'virsh net-dumpxml' output, the one you want to narrow
        down
<new-range> - new range tag, presumably narrower than <old-range>

e.g.:

# virsh net-update default add ip-dhcp-range \
        "<range start='192.168.122.100' end='192.168.122.254'/>" \
        --live --config
    Updated network default persistent config and live state
# virsh net-update default delete ip-dhcp-range \
        "<range start='192.168.122.2' end='192.168.122.254'/>" \
        --live --config
    Updated network default persistent config and live state

now verify with a final virsh net-dumpxml that the <dhcp> stanza contains the desired narrower range of IP addys to hand out using DHCP and you’re free to statically assign the rest as you see fit.

assigning fixed IPs dynamically

this way will usually make sense if the VMs in question are already configured for dynamic IP addys and you don’t want to bother reconfiguring them, or if you actually want them to keep using their DHCP clients, e.g. for testing purposes, without having to check and recheck their IPs every time you bring them about.

in this case, you can just assign fixed IP addy(s) on the host DHCP server side, while keeping the VM in DHCP mode, which will give roughly the same end result of a VM having fixed IP(s). to do that you need to know the VM’s MAC addy(s) (or, in more recent versions of libvirt, since around CentOS v5.4 - just the host name that the DHCP client will send with its request) and which libvirt net(s) they’re on.

as with the proper static IPs method above, the easiest way is to tear down the libvirt network:

#  virsh net-destroy <net-name>

start editing the net config XML using:

# virsh net-edit <net-name>

(or hack on it directly as above). at this point you’ll want to find the <dhcp> stanza and add an entry in the spirit of:

    <host mac='<vm-mac>' ip='<desired-ip>'/>

OR, for libvirt v0.4.5 and on (CentOS v5.4+):

    <host name='<vm-hostname>' ip='<desired-ip>'/>

e.g.:

    <host mac='52:54:00:00:00:01' ip='192.168.122.1'/>
    <host name='foobar' ip='192.168.122.2'/>

save the XML and restart the network for the changes to take effect:

# virsh net-start <net-name>

same caveat as above about restarting the net like that.

the live virsh net-update route might be a bit easier to take with fixed dynamic IPs, since you just need to add a new config tag, using:

# virsh net-update <net-name> add ip-dhcp-host \
    "<host mac='<vm-mac>' ip='<desired-ip>' />" --live --config

where:

<net-name>  - libvirt network name, e.g. 'default'
<vm-mac>    - MAC addy of the VM nic in question, visible in the
        VM config GUI of virt-manager or in the 'mac address' tag of
        the VM XML definition file under /etc/libvirt/qemu/
<desired-ip> - desired fixed IP
the other angled braces should be kept as is (XML! <bleh/>)

e.g.:

# virsh net-update default add ip-dhcp-host \
          "<host mac='52:54:00:00:00:01' ip='192.168.122.1' />" \
           --live --config

presumably the same feat can be achieved using the name selector instead of MAC, though i’ve never tried that one…

for an overview of libvirt’s net config see: libvirt Networking
for a spec of libvirt’s network XML see: Network XML format