test_network_forward() {
    ensure_import_testimage
    ensure_has_localhost_remote "${INCUS_ADDR}"

    firewallDriver=$(incus info | awk -F ":" '/firewall:/{gsub(/ /, "", $0); print $2}')
    netName=inct$$

    incus network create "${netName}" \
        ipv4.address=192.0.2.1/24 \
        ipv6.address=fd42:4242:4242:1010::1/64

    # Check creating a forward with an unspecified IPv4 address fails.
    ! incus network forward create "${netName}" 0.0.0.0 || false

    # Check creating a forward with an unspecified IPv6 address fails.
    ! incus network forward create "${netName}" :: || false

    # Check creating empty forward doesn't create any firewall rules.
    incus network forward create "${netName}" 198.51.100.1 --description "Test network forward"
    if [ "$firewallDriver" = "xtables" ]; then
        ! iptables -w -t nat -S | grep -c "generated for Incus network-forward ${netName}" || false
    else
        ! nft -nn list chain inet incus "fwdprert.${netName}" || false
        ! nft -nn list chain inet incus "fwdout.${netName}" || false
        ! nft -nn list chain inet incus "fwdpstrt.${netName}" || false
    fi

    # Check that description is set
    incus network forward list "${netName}" | grep -q -F "Test network forward"
    incus network forward show "${netName}" 198.51.100.1 | grep -q -F "description: Test network forward"

    # Check forward is exported via BGP prefixes.
    incus query /internal/debug/bgp | grep "198.51.100.1/32"

    incus network forward delete "${netName}" 198.51.100.1

    # Check deleting network forward removes forward BGP prefix.
    ! incus query /internal/debug/bgp | grep "198.51.100.1/32" || false

    # Check creating forward with default target creates valid firewall rules.
    incus network forward create "${netName}" 198.51.100.1 target_address=192.0.2.2
    if [ "$firewallDriver" = "xtables" ]; then
        iptables -w -t nat -S | grep -- "-A PREROUTING -d 198.51.100.1/32 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.2"
        iptables -w -t nat -S | grep -- "-A OUTPUT -d 198.51.100.1/32 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.2"
        iptables -w -t nat -S | grep -- "-A POSTROUTING -s 192.0.2.2/32 -d 192.0.2.2/32 -m comment --comment \"generated for Incus network-forward ${netName}\" -j MASQUERADE"
    else
        nft -nn list chain inet incus "fwdprert.${netName}" | grep "ip daddr 198.51.100.1 dnat ip to 192.0.2.2"
        nft -nn list chain inet incus "fwdout.${netName}" | grep "ip daddr 198.51.100.1 dnat ip to 192.0.2.2"
        nft -nn list chain inet incus "fwdpstrt.${netName}" | grep "ip saddr 192.0.2.2 ip daddr 192.0.2.2 masquerade"
    fi

    # Check unsetting default target clears firewall rules.
    incus network forward unset "${netName}" 198.51.100.1 target_address
    if [ "$firewallDriver" = "xtables" ]; then
        ! iptables -w -t nat -S | grep -c "generated for Incus network-forward ${netName}" || false
    else
        ! nft -nn list chain inet incus "fwdprert.${netName}" || false
        ! nft -nn list chain inet incus "fwdout.${netName}" || false
        ! nft -nn list chain inet incus "fwdpstrt.${netName}" || false
    fi

    # Check can't add a port based rule to the same target IP as the default target.
    incus network forward set "${netName}" 198.51.100.1 target_address=192.0.2.2
    ! incus network forward port add "${netName}" 198.51.100.1 tcp 80 192.0.2.2 || false

    # Check can't add a port based rule to multiple target ports if only one listener port.
    ! incus network forward port add "${netName}" 198.51.100.1 tcp 80 192.0.2.3 80-81 || false

    # Check can add a port with a listener range and no target port (so it uses same range for target ports).
    incus network forward port add "${netName}" 198.51.100.1 tcp 80-81 192.0.2.3 --description "Test network forward port"
    if [ "$firewallDriver" = "xtables" ]; then
        iptables -w -t nat -S | grep -- "-A PREROUTING -d 198.51.100.1/32 -p tcp -m tcp --dport 80:81 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3"
        iptables -w -t nat -S | grep -- "-A OUTPUT -d 198.51.100.1/32 -p tcp -m tcp --dport 80:81 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3"
        iptables -w -t nat -S | grep -- "-A POSTROUTING -s 192.0.2.3/32 -d 192.0.2.3/32 -p tcp -m tcp --dport 80:81 -m comment --comment \"generated for Incus network-forward ${netName}\" -j MASQUERADE"
    else
        nft -nn list chain inet incus "fwdprert.${netName}" | grep "ip daddr 198.51.100.1 tcp dport 80-81 dnat ip to 192.0.2.3"
        nft -nn list chain inet incus "fwdout.${netName}" | grep "ip daddr 198.51.100.1 tcp dport 80-81 dnat ip to 192.0.2.3"
        nft -nn list chain inet incus "fwdpstrt.${netName}" | grep "ip saddr 192.0.2.3 ip daddr 192.0.2.3 tcp dport 80-81 masquerade"
    fi

    # Check that description is set
    incus network forward show "${netName}" 198.51.100.1 | grep -q -F 'description: Test network forward port'

    # Check can't add port with duplicate listen port.
    ! incus network forward port add "${netName}" 198.51.100.1 tcp 80 192.0.2.3 90 || false

    # Check adding port with single listen and target port.
    incus network forward port add "${netName}" 198.51.100.1 udp 80 192.0.2.3 90
    if [ "$firewallDriver" = "xtables" ]; then
        iptables -w -t nat -S | grep -- "-A PREROUTING -d 198.51.100.1/32 -p udp -m udp --dport 80 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3:90"
        iptables -w -t nat -S | grep -- "-A OUTPUT -d 198.51.100.1/32 -p udp -m udp --dport 80 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3:90"
        iptables -w -t nat -S | grep -- "-A POSTROUTING -s 192.0.2.3/32 -d 192.0.2.3/32 -p udp -m udp --dport 90 -m comment --comment \"generated for Incus network-forward ${netName}\" -j MASQUERADE"
    else
        nft -nn list chain inet incus "fwdprert.${netName}" | grep "ip daddr 198.51.100.1 udp dport 80 dnat ip to 192.0.2.3:90"
        nft -nn list chain inet incus "fwdout.${netName}" | grep "ip daddr 198.51.100.1 udp dport 80 dnat ip to 192.0.2.3:90"
        nft -nn list chain inet incus "fwdpstrt.${netName}" | grep "ip saddr 192.0.2.3 ip daddr 192.0.2.3 udp dport 90 masquerade"
    fi

    # Check can't add multi-port listener with mismatch target ports.
    ! incus network forward port add "${netName}" 198.51.100.1 tcp 82,83,84 192.0.2.3 90,91 || false

    # Check adding port with listen port range and single target port (using mixture of commas and dashes).
    incus network forward port add "${netName}" 198.51.100.1 tcp 82-83,84 192.0.2.3 90,91-92
    if [ "$firewallDriver" = "xtables" ]; then
        iptables -w -t nat -S | grep -- "-A PREROUTING -d 198.51.100.1/32 -p tcp -m tcp --dport 84 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3:92"
        iptables -w -t nat -S | grep -- "-A PREROUTING -d 198.51.100.1/32 -p tcp -m tcp --dport 83 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3:91"
        iptables -w -t nat -S | grep -- "-A PREROUTING -d 198.51.100.1/32 -p tcp -m tcp --dport 82 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3:90"
        iptables -w -t nat -S | grep -- "-A OUTPUT -d 198.51.100.1/32 -p tcp -m tcp --dport 84 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3:92"
        iptables -w -t nat -S | grep -- "-A OUTPUT -d 198.51.100.1/32 -p tcp -m tcp --dport 83 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3:91"
        iptables -w -t nat -S | grep -- "-A OUTPUT -d 198.51.100.1/32 -p tcp -m tcp --dport 82 -m comment --comment \"generated for Incus network-forward ${netName}\" -j DNAT --to-destination 192.0.2.3:90"
        iptables -w -t nat -S | grep -- "-A POSTROUTING -s 192.0.2.3/32 -d 192.0.2.3/32 -p tcp -m tcp --dport 90:92 -m comment --comment \"generated for Incus network-forward ${netName}\" -j MASQUERADE"
    else
        nft -nn list chain inet incus "fwdprert.${netName}" | grep "ip daddr 198.51.100.1 tcp dport 82 dnat ip to 192.0.2.3:90"
        nft -nn list chain inet incus "fwdprert.${netName}" | grep "ip daddr 198.51.100.1 tcp dport 83 dnat ip to 192.0.2.3:91"
        nft -nn list chain inet incus "fwdprert.${netName}" | grep "ip daddr 198.51.100.1 tcp dport 84 dnat ip to 192.0.2.3:92"
        nft -nn list chain inet incus "fwdout.${netName}" | grep "ip daddr 198.51.100.1 tcp dport 82 dnat ip to 192.0.2.3:90"
        nft -nn list chain inet incus "fwdout.${netName}" | grep "ip daddr 198.51.100.1 tcp dport 83 dnat ip to 192.0.2.3:91"
        nft -nn list chain inet incus "fwdout.${netName}" | grep "ip daddr 198.51.100.1 tcp dport 84 dnat ip to 192.0.2.3:92"
        nft -nn list chain inet incus "fwdpstrt.${netName}" | grep "ip saddr 192.0.2.3 ip daddr 192.0.2.3 tcp dport 90-92 masquerade"
    fi

    # Check deleting multiple rules is prevented without --force, and that it takes effect with --force.
    if [ "$firewallDriver" = "xtables" ]; then
        [ "$(iptables -w -t nat -S | grep -c "generated for Incus network-forward ${netName}")" -eq 16 ]
    else
        [ "$(nft -nn list chain inet incus "fwdprert.${netName}" | wc -l)" -eq 11 ]
        [ "$(nft -nn list chain inet incus "fwdout.${netName}" | wc -l)" -eq 11 ]
        [ "$(nft -nn list chain inet incus "fwdpstrt.${netName}" | wc -l)" -eq 9 ]
    fi

    ! incus network forward port remove "${netName}" 198.51.100.1 tcp || false
    incus network forward port remove "${netName}" 198.51.100.1 tcp --force

    if [ "$firewallDriver" = "xtables" ]; then
        [ "$(iptables -w -t nat -S | grep -c "generated for Incus network-forward ${netName}")" -eq 6 ]
    else
        [ "$(nft -nn list chain inet incus "fwdprert.${netName}" | wc -l)" -eq 7 ]
        [ "$(nft -nn list chain inet incus "fwdout.${netName}" | wc -l)" -eq 7 ]
        [ "$(nft -nn list chain inet incus "fwdpstrt.${netName}" | wc -l)" -eq 7 ]
    fi

    # Check forward is exported via BGP prefixes before network delete.
    incus query /internal/debug/bgp | grep "198.51.100.1/32"

    # Check deleting the network clears the forward firewall rules.
    incus network delete "${netName}"

    # Check deleting network removes forward BGP prefix.
    ! incus query /internal/debug/bgp | grep "198.51.100.1/32" || false

    if [ "$firewallDriver" = "xtables" ]; then
        ! iptables -w -t nat -S | grep -c "generated for Incus network-forward ${netName}" || false
    else
        ! nft -nn list chain inet incus "fwdprert.${netName}" || false
        ! nft -nn list chain inet incus "fwdout.${netName}" || false
        ! nft -nn list chain inet incus "fwdpstrt.${netName}" || false
    fi
}
