Hairpin NAT rules no longer work

I previously configured Hairpin NAT before it was time to deploy the VyOS router and tested it behind a temporary double NAT (configured an interface on my EdgeRouter to provide 10.0.0.0/8 addresses that I let the VyOS router NAT to 192.168.1.0/24).

This configuration appeared to be working fine after making the suggested changes. However, now that I’ve replaced the EdgeRouter with my VyOS router I no longer have working Hairpin NAT. :thinking:

What am I missing? Any help would be greatly appreciated. :slightly_smiling_face:

Sanitized configuration:

firewall {
    flowtable FT-OFFLOAD {
        interface "eth2"
        interface "eth1"
    }
    group {
        domain-group WAN-IP-DOMAIN {
            address "mydomain.com"
        }
        interface-group LAN {
            interface "eth4"
            interface "eth1"
        }
        interface-group MANAGEMENT {
            include "LAN"
            interface "eth8"
        }
        interface-group WAN {
            interface "eth2"
        }
        network-group NET-LAN-v4 {
            network "192.168.10.0/24"
            network "192.168.1.0/24"
        }
    }
    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"
                }
            }
        }
        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"
                    }
                }
            }
        }
        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"
            }
        }
    }
}
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 {
                    domain-group "WAN-IP-DOMAIN"
                }
                port "443"
            }
            inbound-interface {
                group "LAN"
            }
            protocol "tcp_udp"
            translation {
                address "192.168.1.10"
            }
        }
        rule 110 {
            description "NAT Reflection: SSH"
            destination {
                group {
                    domain-group "WAN-IP-DOMAIN"
                }
                port "22"
            }
            inbound-interface {
                group "LAN"
            }
            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 110 {
            description "NAT Reflection: INSIDE"
            destination {
                address "192.168.1.0/24"
            }
            outbound-interface {
                group "WAN"
            }
            protocol "tcp_udp"
            source {
                address "192.168.1.0/24"
            }
            translation {
                address "masquerade"
            }
        }
    }
}

I would try removing the domain-group and instead just manually (to test) putting the IP address of your public interface in there.

That’s what I have in my NAT rules and that works fine. I’m not 100% that domain-groups works as you’ve got it entered there, but it’s not a feature I’ve used.

But that’d be my suggestion as it otherwise looks good to me.

1 Like

Doesn’t seem to make a difference unfortunately. :slightly_frowning_face:

This is what you meant, right?

xx.xx.xx.xx below is my WAN IP, which is indeed what my domain name resolves to.

        rule 108 {
            description "NAT Reflection: HTTPS Server"
            destination {
                address xx.xx.xx.xx
                port 443
            }
            inbound-interface {
                group LAN
            }
            protocol tcp_udp
            translation {
                address 192.168.1.10
            }
        }

Check this task T2196 and other by name “hairpin”

Thank you. :slightly_smiling_face: Unfortunately I didn’t find anything new on there.

My issue is also that it doesn’t work even with the external IP address specified as the destination, which is supposed to work from what I understand?

Yea that’s what I meant.

This is what I have for hairping NAT to my Home Assistant, which works fine.

eth1 is my LAN interface, 192.168.0.1/24
192.168.0.7 is my Home Assistant’s IP address.

nat {
    destination {
        rule 90 {
            description "Home Assistant"
            destination {
                port 8123
            }
            inbound-interface {
                group wan-interfaces
            }
            protocol tcp
            translation {
                address 192.168.0.7
            }
        }
        rule 200 {
            description "Hairpin NAT for Home Assistant"
            destination {
                address <my public ip>
                port 8123
            }
            inbound-interface {
                name eth1
            }
            protocol tcp
            translation {
                address 192.168.0.7
            }
        }
    source {
        rule 200 {
            description "Hairpin NAT for Home Assistant"
            destination {
                address 192.168.0.7
                port 8123
            }
            outbound-interface {
                name eth1
            }
            protocol tcp
            source {
                address 192.168.0.0/16
            }
            translation {
                address masquerade
            }
        }

Here’s a thing I wrote about pinholing a while ago, I think it’s good to link it because it’s an easy mistake to make and it stuffs up your logging badly.

1 Like

Thank you! :smiley:

Your real world example made me compare it again line by line and helped me identify the issue. There were two of them in fact:

  1. You were right in that the domain group cannot be used in this way. Instead, I created a network-group containing my WAN IP that I can reference as the destination in my DNAT rules. That way I only need to update it in one location at least. :slightly_smiling_face:

  2. The outbound-interface of my SNAT rule 110 was wrong. I had mistakenly set it to WAN instead of LAN. :person_facepalming:

Before that, I also managed to solve another issue that I noticed with a DNAT rule where I do both address and port translation. Turns out that I needed to change the firewall rule to allow the port it got translated to, not the one you connect to externally. I had previously seen the note in the documentation about how the translation happens before the firewall rules but I apparently didn’t realize that it applied to the port as well when I converted the rules manually from EdgeOS.

1 Like

Glad to hear you got it working!

This is a great idea and I’ve now done this myself in my config based on your comment. I’ve been looking to optmise it in all these sorts of ways and this was one area I’d not thought to do, so thank you!

It’s nice to hear that I provided som value as well, not just take up people’s time with questions. :smile:

1 Like

Next step: Probably, you’re already using some ddns host name. Use a scheduled script to query cuurent ddns name, and alter config automatically with returned IP address

I have DDNS set up, yes. I haven’t automated configuration changes though. I don’t expect the IPv4 address to change much though. At worst, the IPv6 prefix is static so I can always just SSH to my VPS (to make sure I have IPv6) and then SSH to a server at home over IPv6 to figure out the prefix. :slightly_smiling_face:

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