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

    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"
    ! 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

    # 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
    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"

    # Check unsetting default target clears firewall rules.
    incus network forward unset "${netName}" 198.51.100.1 target_address
    ! 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

    # 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"
    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"

    # 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
    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"

    # 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
    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"

    # Check deleting multiple rules is prevented without --force, and that it takes effect with --force.
    [ "$(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 ]

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

    [ "$(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 ]

    # 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

    ! 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
}
