[bug#77153,3/3] doc: cookbook: Document manual libvirt networking.

Message ID 60249f55cf80b1dbf41654728939cbc6e6bbcd4e.1742570314.git.45mg.writes@gmail.com
State New
Headers
Series doc: cookbook: Manual libvirt networking. |

Commit Message

45mg March 21, 2025, 3:22 p.m. UTC
  * doc/guix-cookbook.texi (Virtual Machines): [Manual libvirt
networking]: New section.

Change-Id: Ice79c5dc8183ec694ac8b846a5ec88cb98cac9ff
---
 doc/guix-cookbook.texi | 120 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)
  

Comments

Maxim Cournoyer March 22, 2025, 11:18 a.m. UTC | #1
Hi,

45mg <45mg.writes@gmail.com> writes:

> * doc/guix-cookbook.texi (Virtual Machines): [Manual libvirt
> networking]: New section.

Thanks for writing this.

> Change-Id: Ice79c5dc8183ec694ac8b846a5ec88cb98cac9ff
> ---
>  doc/guix-cookbook.texi | 120 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 120 insertions(+)
>
> diff --git a/doc/guix-cookbook.texi b/doc/guix-cookbook.texi
> index 325b1d9c2a..338dba25be 100644
> --- a/doc/guix-cookbook.texi
> +++ b/doc/guix-cookbook.texi
> @@ -3750,6 +3750,7 @@ Virtual Machines
>  @menu
>  * Network bridge for QEMU::
>  * Routed network for libvirt::
> +* Manual libvirt networking::
>  @end menu
>  
>  @node Network bridge for QEMU
> @@ -3974,6 +3975,125 @@ Routed network for libvirt
>  should work from within your VM; you can e.g.@: run @samp{ping gnu.org}
>  to verify that it functions correctly.
>  
> +@node Manual libvirt networking
> +@section Manual libvirt networking

Perhaps this should be named 'Custom NAT-based network', as in Jamie's
handbook, as every other configurations also involve manual steps?

> +
> +As mentioned in the preceding section (@pxref{Routed network for libvirt}),
> +libvirt allows virtual networks to be defined via XML files and managed
> +by the @command{virsh} command.  The details of the creation and removal
> +of virtual network switches are handled by libvirt, so the user does not
> +have to deal with them.

As discussed previously, I think it may be best to stick to our existing
terminology of 'virtual bridge' instead of 'virtual switch'.

> +However, libvirt's handling of virtual network switches can sometimes
> +clash with more complex networking setups.  In particular, the iptables
> +rules inserted by libvirt for switches operating in the NAT mode can
> +clash with existing iptables/nftables rules, leading to insecure or
> +broken packet filtering.
> +
> +In such cases, the only solution is to manually set up a virtual network
> +switch.  This section will provide instructions on how to do so using
> +Guix System services.
> +
> +This section is based on
> +@url{https://jamielinux.com/docs/libvirt-networking-handbook/custom-nat-based-network.html,
> +the corresponding section from the (unofficial) libvirt Networking
> +Handbook}.  It should be noted that at the time of writing (March 2025),
> +this resource had not been updated since 2015, and is therefore somewhat
> +outdated.  In particular, the creation of a `dummy interface' is no
> +longer necessary.

I would drop this paragraph.  The other sections are also based on that
same handbook.  It's up to us to extract the good bits and avoid the
obsolete one and keep our own doc up to date :-).

> +@subsection Creating the virtual network bridge
> +
> +The @code{static-networking-service-type} can be used to create a
> +virtual network bridge and assign an IP address to it:

'network bridge', hm? ;-)

> +
> +@example lisp
> +(service static-networking-service-type
> +         (list (static-networking
> +                ;; The default provision is 'networking; if you're using any
> +                ;; other service with this provision, such as
> +                ;; `network-manager-service-type`, then you need to change the
> +                ;; default

Use complete sentences for line comments (i.e. add a terminating period
above).

> +                (provision '(static-networking))
> +                (links
> +                 (list (network-link
> +                        (name "virbr0")
> +                        (type 'bridge)
> +                        (arguments '((stp_state . 1))))))

I've never seen this stp_state argument; is it useful?  Perhaps it
deserves a comment.

> +                (addresses
> +                 (list (network-address
> +                        (device "virbr0")
> +                        (value "192.168.10.1/24")))))))
> +@end example
> +
> +@subsection Running dnsmasq for the virtual network bridge
> +
> +The @code{dnsmasq-service-type} can be used to provide DNS and DHCP for
> +guests connected to this virtual network switch:
> +
> +@example lisp
> +(service dnsmasq-service-type
> +         (dnsmasq-configuration
> +          ;; You can have multiple instances of `dnsmasq-service-type` as long
> +          ;; as each one has a different provision

Missing ending period.

> +          (provision '(dnsmasq-virbr0))
> +          (extra-options (list
> +                          ;; Only bind to the virtual bridge. This
> +                          ;; avoids conflicts with other running
> +                          ;; DNSMASQ instances.
> +                          "--except-interface=lo"
> +                          "--interface=virbr0"
> +                          "--bind-dynamic"
> +                          ;; IPv4 addresses to offer to VMs. This
> +                          ;; should match the chosen subnet.
> +                          "--dhcp-range=192.168.10.2,192.168.10.254"))))
> +@end example
> +
> +@subsection Configuring NAT for the virtual network switch
> +
> +If you intend to use the virtual network switch in NAT mode, you will
> +need to use nftables (or iptables) rules to set up IP masquerading.  The
> +following example shows how to use @code{nftables-service-type} to do
> +this:
> +
> +@example lisp
> +(service nftables-service-type
> +         (nftables-configuration
> +          (ruleset
> +           (plain-file "nftables.conf"
> +                       "\
> +table inet filter @{
> +
> +  chain input @{
> +    type filter hook input priority filter; policy drop;
> +    # Add your existing packet filtering rules here....

s/..../.../

> +    iifname "virbr0" udp dport 67 counter accept comment "allow dhcp on virbr0"
> +    iifname "virbr0" meta l4proto @{tcp, udp@} th dport 53 accept comment "allow dns on virbr0"
> +  @}
> +
> +  chain forward @{
> +    type filter hook forward priority filter; policy drop;
> +    # Add your existing forwarding rules here....

s/..../.../

> +    iifname "virbr0" accept comment "allow outbound traffic from virbr0"
> +    oifname "virbr0" ct state @{established, related @} accept comment "allow established traffic to virbr0"
> +  @}
> +
> +@}
> +
> +table inet nat @{
> +  chain postrouting @{
> +    type nat hook postrouting priority srcnat; policy accept;
> +    # Add your existing nat rules here...


> +    iifname "virbr0" ip daddr @{ 224.0.0.0/24, 255.255.255.255/32 @} return comment "don't masquerade to reserved address blocks"
> +    iifname "virbr0" oifname != "virbr0" masquerade comment "masquerade all outgoing traffic from VMs"
> +  @}
> +@}
> +"))))
> +@end example

I think the long lines (> 80 columns) may be a problem in PDF-rendered
documentation, possible info as well.  It'd be better to break the long
lines; perhaps possible via a backslash before the newline?

Indeed, looking at 'man 8 nft', it says:

--8<---------------cut here---------------start------------->8---
INPUT FILE FORMATS
   LEXICAL CONVENTIONS
       Input is parsed line-wise. When the last character of a line,
       just before the newline character, is a non-quoted backslash (\),
       the next line is treated as a continuation. Multiple commands on
       the same line can be separated using a semicolon (;).
--8<---------------cut here---------------end--------------->8---

Could you please send a v2 with the above requested changes?  I'd also
drop the first two commits of this series and stick to 'virtual bridge'
in this current one, for consistency and matching the vocabulaty the
interfaces actually use.

Thanks for working on this!
  
45mg March 22, 2025, 11:40 a.m. UTC | #2
Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:

> Hi,
>
> 45mg <45mg.writes@gmail.com> writes:
>
>> * doc/guix-cookbook.texi (Virtual Machines): [Manual libvirt
>> networking]: New section.
>
> Thanks for writing this.
>
>> Change-Id: Ice79c5dc8183ec694ac8b846a5ec88cb98cac9ff
>> ---
>>  doc/guix-cookbook.texi | 120 +++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 120 insertions(+)
>>
>> diff --git a/doc/guix-cookbook.texi b/doc/guix-cookbook.texi
>> index 325b1d9c2a..338dba25be 100644
>> --- a/doc/guix-cookbook.texi
>> +++ b/doc/guix-cookbook.texi
>> @@ -3750,6 +3750,7 @@ Virtual Machines
>>  @menu
>>  * Network bridge for QEMU::
>>  * Routed network for libvirt::
>> +* Manual libvirt networking::
>>  @end menu
>>
>>  @node Network bridge for QEMU
>> @@ -3974,6 +3975,125 @@ Routed network for libvirt
>>  should work from within your VM; you can e.g.@: run @samp{ping gnu.org}
>>  to verify that it functions correctly.
>>
>> +@node Manual libvirt networking
>> +@section Manual libvirt networking
>
> Perhaps this should be named 'Custom NAT-based network', as in Jamie's
> handbook, as every other configurations also involve manual steps?

Ok, makes sense.

>> +
>> +As mentioned in the preceding section (@pxref{Routed network for libvirt}),
>> +libvirt allows virtual networks to be defined via XML files and managed
>> +by the @command{virsh} command.  The details of the creation and removal
>> +of virtual network switches are handled by libvirt, so the user does not
>> +have to deal with them.
>
> As discussed previously, I think it may be best to stick to our existing
> terminology of 'virtual bridge' instead of 'virtual switch'.

See previous message [1].

>> +However, libvirt's handling of virtual network switches can sometimes
>> +clash with more complex networking setups.  In particular, the iptables
>> +rules inserted by libvirt for switches operating in the NAT mode can
>> +clash with existing iptables/nftables rules, leading to insecure or
>> +broken packet filtering.
>> +
>> +In such cases, the only solution is to manually set up a virtual network
>> +switch.  This section will provide instructions on how to do so using
>> +Guix System services.
>> +
>> +This section is based on
>> +@url{https://jamielinux.com/docs/libvirt-networking-handbook/custom-nat-based-network.html,
>> +the corresponding section from the (unofficial) libvirt Networking
>> +Handbook}.  It should be noted that at the time of writing (March 2025),
>> +this resource had not been updated since 2015, and is therefore somewhat
>> +outdated.  In particular, the creation of a `dummy interface' is no
>> +longer necessary.
>
> I would drop this paragraph.  The other sections are also based on that
> same handbook.  It's up to us to extract the good bits and avoid the
> obsolete one and keep our own doc up to date :-).

We should probably link to it somewhere. It provides more explanation
and context for the steps listed in this and the other sections. And
just in principle, we should probably cite our sources. But it would be
irresponsible to do so without mentioning that parts of it are
outdated... hence that paragraph.

I'm open to linking it somewhere else in the Cookbook, though. Thoughts?

>> +@subsection Creating the virtual network bridge
>> +
>> +The @code{static-networking-service-type} can be used to create a
>> +virtual network bridge and assign an IP address to it:
>
> 'network bridge', hm? ;-)

Yes. We are referring specifically to a virtual network interface here.
The virtual network bridge is one component of the virtual network
switch.

>> +
>> +@example lisp
>> +(service static-networking-service-type
>> +         (list (static-networking
>> +                ;; The default provision is 'networking; if you're using any
>> +                ;; other service with this provision, such as
>> +                ;; `network-manager-service-type`, then you need to change the
>> +                ;; default
>
> Use complete sentences for line comments (i.e. add a terminating period
> above).

Ok.

>> +                (provision '(static-networking))
>> +                (links
>> +                 (list (network-link
>> +                        (name "virbr0")
>> +                        (type 'bridge)
>> +                        (arguments '((stp_state . 1))))))
>
> I've never seen this stp_state argument; is it useful?  Perhaps it
> deserves a comment.

I have no idea what it does, but I think I needed it for the bridge to
work? The handbook also includes it (`brctl stp virbr10 on`).

>> +                (addresses
>> +                 (list (network-address
>> +                        (device "virbr0")
>> +                        (value "192.168.10.1/24")))))))
>> +@end example
>> +
>> +@subsection Running dnsmasq for the virtual network bridge
>> +
>> +The @code{dnsmasq-service-type} can be used to provide DNS and DHCP for
>> +guests connected to this virtual network switch:
>> +
>> +@example lisp
>> +(service dnsmasq-service-type
>> +         (dnsmasq-configuration
>> +          ;; You can have multiple instances of `dnsmasq-service-type` as long
>> +          ;; as each one has a different provision
>
> Missing ending period.

Ok.

>> +          (provision '(dnsmasq-virbr0))
>> +          (extra-options (list
>> +                          ;; Only bind to the virtual bridge. This
>> +                          ;; avoids conflicts with other running
>> +                          ;; DNSMASQ instances.
>> +                          "--except-interface=lo"
>> +                          "--interface=virbr0"
>> +                          "--bind-dynamic"
>> +                          ;; IPv4 addresses to offer to VMs. This
>> +                          ;; should match the chosen subnet.
>> +                          "--dhcp-range=192.168.10.2,192.168.10.254"))))
>> +@end example
>> +
>> +@subsection Configuring NAT for the virtual network switch
>> +
>> +If you intend to use the virtual network switch in NAT mode, you will
>> +need to use nftables (or iptables) rules to set up IP masquerading.  The
>> +following example shows how to use @code{nftables-service-type} to do
>> +this:
>> +
>> +@example lisp
>> +(service nftables-service-type
>> +         (nftables-configuration
>> +          (ruleset
>> +           (plain-file "nftables.conf"
>> +                       "\
>> +table inet filter @{
>> +
>> +  chain input @{
>> +    type filter hook input priority filter; policy drop;
>> +    # Add your existing packet filtering rules here....
>
> s/..../.../

Ok.

>> +    iifname "virbr0" udp dport 67 counter accept comment "allow dhcp on virbr0"
>> +    iifname "virbr0" meta l4proto @{tcp, udp@} th dport 53 accept comment "allow dns on virbr0"
>> +  @}
>> +
>> +  chain forward @{
>> +    type filter hook forward priority filter; policy drop;
>> +    # Add your existing forwarding rules here....
>
> s/..../.../

Ok.

>> +    iifname "virbr0" accept comment "allow outbound traffic from virbr0"
>> +    oifname "virbr0" ct state @{established, related @} accept comment "allow established traffic to virbr0"
>> +  @}
>> +
>> +@}
>> +
>> +table inet nat @{
>> +  chain postrouting @{
>> +    type nat hook postrouting priority srcnat; policy accept;
>> +    # Add your existing nat rules here...
>
>
>> +    iifname "virbr0" ip daddr @{ 224.0.0.0/24, 255.255.255.255/32 @} return comment "don't masquerade to reserved address blocks"
>> +    iifname "virbr0" oifname != "virbr0" masquerade comment "masquerade all outgoing traffic from VMs"
>> +  @}
>> +@}
>> +"))))
>> +@end example
>
> I think the long lines (> 80 columns) may be a problem in PDF-rendered
> documentation, possible info as well.  It'd be better to break the long
> lines; perhaps possible via a backslash before the newline?
>
> Indeed, looking at 'man 8 nft', it says:
>
> --8<---------------cut here---------------start------------->8---
> INPUT FILE FORMATS
>    LEXICAL CONVENTIONS
>        Input is parsed line-wise. When the last character of a line,
>        just before the newline character, is a non-quoted backslash (\),
>        the next line is treated as a continuation. Multiple commands on
>        the same line can be separated using a semicolon (;).
> --8<---------------cut here---------------end--------------->8---

I'll look into it.

> Could you please send a v2 with the above requested changes?  I'd also
> drop the first two commits of this series and stick to 'virtual bridge'
> in this current one, for consistency and matching the vocabulaty the
> interfaces actually use.

Sure, but first let's see if we can come to a consensus on the
terminology here. See [1].

> Thanks for working on this!
>
> --
> Maxim

[1] https://yhetil.org/guix/87ldsx725q.fsf@gmail.com/
  
Maxim Cournoyer March 22, 2025, 12:20 p.m. UTC | #3
Hi,

45mg <45mg.writes@gmail.com> writes:

[...]

> Sure, but first let's see if we can come to a consensus on the
> terminology here. See [1].

Our two people consensus has been achieved (I agree to use your 2 first
commits).  More people are welcome to weigh in, of course.
  

Patch

diff --git a/doc/guix-cookbook.texi b/doc/guix-cookbook.texi
index 325b1d9c2a..338dba25be 100644
--- a/doc/guix-cookbook.texi
+++ b/doc/guix-cookbook.texi
@@ -3750,6 +3750,7 @@  Virtual Machines
 @menu
 * Network bridge for QEMU::
 * Routed network for libvirt::
+* Manual libvirt networking::
 @end menu
 
 @node Network bridge for QEMU
@@ -3974,6 +3975,125 @@  Routed network for libvirt
 should work from within your VM; you can e.g.@: run @samp{ping gnu.org}
 to verify that it functions correctly.
 
+@node Manual libvirt networking
+@section Manual libvirt networking
+
+As mentioned in the preceding section (@pxref{Routed network for libvirt}),
+libvirt allows virtual networks to be defined via XML files and managed
+by the @command{virsh} command.  The details of the creation and removal
+of virtual network switches are handled by libvirt, so the user does not
+have to deal with them.
+
+However, libvirt's handling of virtual network switches can sometimes
+clash with more complex networking setups.  In particular, the iptables
+rules inserted by libvirt for switches operating in the NAT mode can
+clash with existing iptables/nftables rules, leading to insecure or
+broken packet filtering.
+
+In such cases, the only solution is to manually set up a virtual network
+switch.  This section will provide instructions on how to do so using
+Guix System services.
+
+This section is based on
+@url{https://jamielinux.com/docs/libvirt-networking-handbook/custom-nat-based-network.html,
+the corresponding section from the (unofficial) libvirt Networking
+Handbook}.  It should be noted that at the time of writing (March 2025),
+this resource had not been updated since 2015, and is therefore somewhat
+outdated.  In particular, the creation of a `dummy interface' is no
+longer necessary.
+
+@subsection Creating the virtual network bridge
+
+The @code{static-networking-service-type} can be used to create a
+virtual network bridge and assign an IP address to it:
+
+@example lisp
+(service static-networking-service-type
+         (list (static-networking
+                ;; The default provision is 'networking; if you're using any
+                ;; other service with this provision, such as
+                ;; `network-manager-service-type`, then you need to change the
+                ;; default
+                (provision '(static-networking))
+                (links
+                 (list (network-link
+                        (name "virbr0")
+                        (type 'bridge)
+                        (arguments '((stp_state . 1))))))
+                (addresses
+                 (list (network-address
+                        (device "virbr0")
+                        (value "192.168.10.1/24")))))))
+@end example
+
+@subsection Running dnsmasq for the virtual network bridge
+
+The @code{dnsmasq-service-type} can be used to provide DNS and DHCP for
+guests connected to this virtual network switch:
+
+@example lisp
+(service dnsmasq-service-type
+         (dnsmasq-configuration
+          ;; You can have multiple instances of `dnsmasq-service-type` as long
+          ;; as each one has a different provision
+          (provision '(dnsmasq-virbr0))
+          (extra-options (list
+                          ;; Only bind to the virtual bridge. This
+                          ;; avoids conflicts with other running
+                          ;; dnsmasq instances.
+                          "--except-interface=lo"
+                          "--interface=virbr0"
+                          "--bind-dynamic"
+                          ;; IPv4 addresses to offer to VMs. This
+                          ;; should match the chosen subnet.
+                          "--dhcp-range=192.168.10.2,192.168.10.254"))))
+@end example
+
+@subsection Configuring NAT for the virtual network switch
+
+If you intend to use the virtual network switch in NAT mode, you will
+need to use nftables (or iptables) rules to set up IP masquerading.  The
+following example shows how to use @code{nftables-service-type} to do
+this:
+
+@example lisp
+(service nftables-service-type
+         (nftables-configuration
+          (ruleset
+           (plain-file "nftables.conf"
+                       "\
+table inet filter @{
+
+  chain input @{
+    type filter hook input priority filter; policy drop;
+    # Add your existing packet filtering rules here....
+    iifname "virbr0" udp dport 67 counter accept comment "allow dhcp on virbr0"
+    iifname "virbr0" meta l4proto @{tcp, udp@} th dport 53 accept comment "allow dns on virbr0"
+  @}
+
+  chain forward @{
+    type filter hook forward priority filter; policy drop;
+    # Add your existing forwarding rules here....
+    iifname "virbr0" accept comment "allow outbound traffic from virbr0"
+    oifname "virbr0" ct state @{established, related @} accept comment "allow established traffic to virbr0"
+  @}
+
+@}
+
+table inet nat @{
+  chain postrouting @{
+    type nat hook postrouting priority srcnat; policy accept;
+    # Add your existing nat rules here...
+    iifname "virbr0" ip daddr @{ 224.0.0.0/24, 255.255.255.255/32 @} return comment "don't masquerade to reserved address blocks"
+    iifname "virbr0" oifname != "virbr0" masquerade comment "masquerade all outgoing traffic from VMs"
+  @}
+@}
+"))))
+@end example
+
+Ensure that you have IPv4 forwarding enabled (you can use
+@code{sysctl-service-type} for this).
+
 @c *********************************************************************
 @node Advanced package management
 @chapter Advanced package management