Trying to get Hairpin NAT for WireGuard working (IPv4)

While I’m sure you guys are starting to get tired of my posts at this point, I’ve run into one final obstacle.

I currently have a WireGuard server running on a separate low power Linux server to access internal services as well as to provide internet access when I’m traveling. For performance reasons, I decided to move it to my VyOS router. So far, I’ve gotten it up and running in parallel on a different port.

Current progress:

IPv4:
:white_check_mark: Browse the internet
:white_check_mark: Access internal services via their internal IP
:x: Access internal services via their external IP (i.e. Hairpin NAT)

IPv6:
:white_check_mark: Browse the internet (without NAT :grinning:)
:white_check_mark: Access internal services via their GUA IP

In other words, what remains is to get Hairpin NAT working for the IPv4 addresses over WireGuard as well.

The reason I even need it is because I have a few services that sadly don’t have IPv6 support and I need to use the external domain to access them to avoid certificate errors. No, I’m not interested in split-DNS for reasons mentioned previously. :slight_smile:

I’ve tried to get it set up but I’ve gotten stuck. :slightly_frowning_face: I have a feeling that source nat rule 120 (NAT Reflection: WIREGUARD INSIDE) is wrong but I’ve tried a number of different versions of that rule and I’m still not sure what the proper rule is.

Has anyone got an example of a working WireGuard + Hairpin NAT configuration? Or some idea of what I might’ve missed? It might be something very simple. :thinking:

Sanitized configuration:

firewall {
    flowtable FT-OFFLOAD {
        interface "eth2"
        interface "eth1"
    }
    group {
        interface-group LAN {
            interface "eth1"
        }
        interface-group LAN-AND-WG {
            include "LAN"
            interface "wg0"
        }
        interface-group MANAGEMENT {
            include "LAN"
            interface "eth8"
        }
        interface-group WAN {
            interface "eth2"
        }
        ipv6-address-group HOST-1-IPV6 {
            address "xxxx:xxxx:xxxx:1000::10"
        }
        ipv6-address-group HOST-2-IPV6 {
            address "xxxx:xxxx:xxxx:1000::20"
        }
        ipv6-network-group IPV6-LAN-PREFIX-0 {
            network "xxxx:xxxx:xxxx:1000::/64"
        }
        ipv6-network-group IPV6-WG-PREFIX-1 {
            network "xxxx:xxxx:xxxx:2000::/64"
        }
        network-group NET-LAN-v4 {
            network "192.168.10.0/24"
            network "192.168.1.0/24"
        }
        network-group WAN-IP {
            network "xx.xx.xx.xx/32"
        }
    }
    ipv4 {
        forward {
            filter {
                rule 5 {
                    action "offload"
                    offload-target "FT-OFFLOAD"
                    state "established"
                    state "related"
                }
                rule 10 {
                    action "jump"
                    jump-target "CONN_FILTER"
                }
                rule 100 {
                    action "jump"
                    destination {
                        group {
                            network-group "NET-LAN-v4"
                        }
                    }
                    inbound-interface {
                        group "WAN"
                    }
                    jump-target "OUTSIDE-IN"
                }
                rule 110 {
                    action "jump"
                    destination {
                        group {
                            network-group "NET-LAN-v4"
                        }
                    }
                    inbound-interface {
                        name "wg0"
                    }
                    jump-target "WIREGUARD-LAN"
                }
            }
        }
        input {
            filter {
                default-action "drop"
                rule 10 {
                    action "jump"
                    jump-target "CONN_FILTER"
                }
                rule 20 {
                    action "jump"
                    destination {
                        port "22"
                    }
                    jump-target "VyOS_MANAGEMENT"
                    protocol "tcp"
                }
                rule 30 {
                    action "accept"
                    icmp {
                        type-name "echo-request"
                    }
                    protocol "icmp"
                    state "new"
                }
                rule 40 {
                    action "accept"
                    destination {
                        port "53"
                    }
                    protocol "tcp_udp"
                    source {
                        group {
                            network-group "NET-LAN-v4"
                        }
                    }
                }
                rule 50 {
                    action "accept"
                    source {
                        address "127.0.0.0/8"
                    }
                }
                rule 60 {
                    action "jump"
                    inbound-interface {
                        group "WAN"
                    }
                    jump-target "WAN-LOCAL"
                }
            }
        }
        name CONN_FILTER {
            default-action "return"
            rule 10 {
                action "accept"
                description "Allow established/related"
                state "established"
                state "related"
            }
            rule 20 {
                action "drop"
                description "Drop invalid packets"
                state "invalid"
            }
        }
        name OUTSIDE-IN {
            default-action "drop"
            rule 8 {
                action "accept"
                description "HTTPS Server"
                destination {
                    address "192.168.1.10"
                    port "443"
                }
                protocol "tcp_udp"
                state "new"
            }
            rule 10 {
                action "accept"
                description "SSH"
                destination {
                    address "192.168.1.10"
                    port "22"
                }
                protocol "tcp"
                state "new"
            }
        }
        name VyOS_MANAGEMENT {
            default-action "return"
            rule 15 {
                action "accept"
                inbound-interface {
                    group "MANAGEMENT"
                }
            }
            rule 20 {
                action "drop"
                inbound-interface {
                    group "WAN"
                }
                state "new"
            }
        }
        name WAN-LOCAL {
            rule 20 {
                action "accept"
                description "WireGuard"
                destination {
                    port "1196"
                }
                protocol "udp"
                source
            }
        }
        name WIREGUARD-LAN {
            default-action "drop"
            rule 50 {
                action "accept"
                description "Allow HTTPS"
                destination {
                    address "192.168.1.10"
                    port "443"
                }
                protocol "tcp_udp"
                state "new"
            }
        }
    }
    ipv6 {
        forward {
            filter {
                rule 5 {
                    action "offload"
                    offload-target "FT-OFFLOAD"
                    state "established"
                    state "related"
                }
                rule 10 {
                    action "jump"
                    jump-target "CONN_FILTER"
                }
                rule 100 {
                    action "jump"
                    inbound-interface {
                        group "WAN"
                    }
                    jump-target "OUTSIDE-IN"
                }
                rule 110 {
                    action "jump"
                    destination {
                        group {
                            network-group "IPV6-LAN-PREFIX-0"
                        }
                    }
                    inbound-interface {
                        name "wg0"
                    }
                    jump-target "WIREGUARD-LAN"
                }
            }
        }
        input {
            filter {
                default-action "drop"
                rule 10 {
                    action "jump"
                    jump-target "CONN_FILTER"
                }
                rule 20 {
                    action "jump"
                    destination {
                        port "22"
                    }
                    jump-target "VyOS_MANAGEMENT"
                    protocol "tcp"
                }
                rule 30 {
                    action "accept"
                    protocol "ipv6-icmp"
                }
                rule 35 {
                    action "accept"
                    destination {
                        port "53"
                    }
                    protocol "tcp_udp"
                    source {
                        group {
                            network-group "IPV6-LAN-PREFIX-0"
                        }
                    }
                }
                rule 40 {
                    action "jump"
                    inbound-interface {
                        group "WAN"
                    }
                    jump-target "WAN-LOCAL"
                }
            }
        }
        name CONN_FILTER {
            default-action "return"
            rule 10 {
                action "accept"
                state "established"
                state "related"
            }
            rule 20 {
                action "drop"
                state "invalid"
            }
        }
        name OUTSIDE-IN {
            default-action "drop"
            rule 10 {
                action "accept"
                protocol "ipv6-icmp"
            }
            rule 108 {
                action "accept"
                description "HTTPS Server"
                destination {
                    group {
                        address-group "HOST-2-IPV6"
                    }
                    port "443"
                }
                protocol "tcp_udp"
                state "new"
            }
            rule 110 {
                action "accept"
                description "SSH"
                destination {
                    group {
                        address-group "HOST-2-IPV6"
                    }
                    port "22"
                }
                protocol "tcp"
                state "new"
            }
        }
        name VyOS_MANAGEMENT {
            default-action "return"
            rule 15 {
                action "accept"
                inbound-interface {
                    group "LAN"
                }
            }
            rule 20 {
                action "drop"
                inbound-interface {
                    group "WAN"
                }
                state "new"
            }
        }
        name WAN-LOCAL {
            rule 20 {
                action "accept"
                description "WireGuard"
                destination {
                    address "xxxx:xxxx:xxxx:2000::1"
                    port "1196"
                }
                protocol "udp"
                source
            }
        }
        name WIREGUARD-LAN {
            default-action "drop"
            rule 50 {
                action "accept"
                description "Allow HTTPS"
                destination {
                    group {
                        address-group "HOST-1-IPV6"
                    }
                    port "443"
                }
                protocol "tcp_udp"
                state "new"
            }
        }
    }
}
interfaces {
    ethernet eth1 {
        address "192.168.1.1/24"
        description "LAN (SFP+ 2)"
    }
    ethernet eth2 {
        address "dhcp"
        description "WAN (SFP+ 3)"
        dhcpv6-options {
            duid "..."
            no-release
            pd 0 {
                interface eth1 {
                    address "1"
                    sla-id "0"
                }
                interface wg0 {
                    address "1"
                    sla-id "16"
                }
                length "56"
            }
        }
        ipv6 {
            address {
                autoconf
            }
        }
    }
    ethernet eth4 {
        address "192.168.10.1/24"
        description "LAN (RJ45 4)"
    }
    ethernet eth8 {
        address "192.168.100.1/24"
        description "Management (RJ45 5)"
    }
    loopback lo {
    }
    wireguard wg0 {
        address "10.10.0.1/24"
        address "xxxx:xxxx:xxxx:2000::1/64"
        peer Pixel {
            allowed-ips "10.10.0.2/32"
            allowed-ips "xxxx:xxxx:xxxx:2000::2/128"
            persistent-keepalive "25"
            public-key "..."
        }
        port "1196"
        private-key "..."
    }
}
nat {
    destination {
        rule 8 {
            description "HTTPS Server"
            destination {
                port "443"
            }
            inbound-interface {
                group "WAN"
            }
            protocol "tcp_udp"
            translation {
                address "192.168.1.10"
            }
        }
        rule 10 {
            description "SSH"
            destination {
                port "22"
            }
            inbound-interface {
                group "WAN"
            }
            protocol "tcp"
            translation {
                address "192.168.1.10"
            }
        }
        rule 108 {
            description "NAT Reflection: HTTPS Server"
            destination {
                group {
                    network-group "WAN-IP"
                }
                port "443"
            }
            inbound-interface {
                group "LAN-AND-WG"
            }
            protocol "tcp_udp"
            translation {
                address "192.168.1.10"
            }
        }
        rule 110 {
            description "NAT Reflection: SSH"
            destination {
                group {
                    network-group "WAN-IP"
                }
                port "22"
            }
            inbound-interface {
                group "LAN-AND-WG"
            }
            protocol "tcp"
            translation {
                address "192.168.1.10"
            }
        }
    }
    source {
        rule 100 {
            description "NAT"
            outbound-interface {
                group "WAN"
            }
            source {
                address "192.168.0.0/16"
            }
            translation {
                address "masquerade"
            }
        }
        rule 105 {
            description "WireGuard NAT"
            outbound-interface {
                group "WAN"
            }
            source {
                address "10.10.0.0/24"
            }
            translation {
                address "masquerade"
            }
        }
        rule 110 {
            description "NAT Reflection: INSIDE"
            destination {
                address "192.168.1.0/24"
            }
            outbound-interface {
                group "LAN"
            }
            protocol "tcp_udp"
            source {
                address "192.168.1.0/24"
            }
            translation {
                address "masquerade"
            }
        }
        rule 120 {
            description "NAT Reflection: WIREGUARD INSIDE"
            destination {
                address "10.10.0.0/24"
            }
            outbound-interface {
                name "wg0"
            }
            protocol "tcp_udp"
            source {
                address "192.168.1.0/24"
            }
            translation {
                address "masquerade"
            }
        }
    }
}
protocols {
    static {
        route 10.10.0.0/24 {
            interface wg0 {
            }
        }
        route6 xxxx:xxxx:xxxx:2000::/64 {
            interface wg0 {
            }
        }
    }
}
service {
    router-advert {
        interface eth1 {
            default-preference "high"
            name-server "xxxx:xxxx:xxxx:1000::1"
            prefix ::/64 {
            }
        }
    }
}

Try this:

        rule 120 {
            description "NAT Reflection: WIREGUARD INSIDE"
            destination {
                address "192.168.1.0/24"
            }
            outbound-interface {
                group "LAN"
            }
            protocol "tcp_udp"
            source {
                address "10.10.0.0/24"
            }
            translation {
                address "masquerade"
            }
        }

If this is going to my Internal Server’s Address Range (192.168.10.0/24) via the LAN interface (outbound-interface group LAN) and the source address is from my Wireguard Clients (10.10.0.0/24) then set the source IP to the IP of the Interface it’s egressing from.

I think you could even get away with editing rule110 and making it “source address group” that has both your 192.168.0.0/16 and 10.10.0.0/24 in it.

I think anyway - no easy way to test, could be totally wrong. Try the 120 suggestion first and report back :slight_smile:

2 Likes

That’s it! Thanks! :grinning:

I looked at my original attempt at that rule and it was like that, but I had reversed the destination and source addresses. Then I modified the outbound interface and tried all combinations of that, without going back to try the working one you show above. :smile:

The “problem” (if you can call it that) with the example in the documentation is that you end up with a rule where source and destination is the same, which is suitable for the case described there, but makes it harder to conceptualize what the source and destinations are in other situations. :slight_smile: However, Ubiquiti’s documentation for Hairpin NAT on the EdgeRouter (for cases where you can’t just use their simple checkbox) has the same “issue”.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.