Is there yet a reliable solution to port forward to an hostname rather than a (dynamic) IP address?

Hi,

Without making a DHCP lease of any kind

Is there yet a reliable method to port forward, not to an hardcoded IP address but an hostname ?

I am fine with a process making a dns request at a regular interval for each such hostname.

I am fine with short interruption periods when the IP address associated with the hostname occurs.

It would be great if when the IP address does change in the DNS server, that it would be the DNS server that would inform the port forward rules rather than having the port forward rules polling the DNS server but that’s not strictly necessary.

So has anyone done this yet ?

uci add firewall redirect

uci set firewall.@redirect[-1].dest='lan'

uci set firewall.@redirect[-1].target='DNAT'

uci set firewall.@redirect[-1].name='example'

uci add_list firewall.@redirect[-1].proto='tcp'

uci add_list firewall.@redirect[-1].proto='udp'

uci add_list firewall.@redirect[-1].proto='icmp'

uci set firewall.@redirect[-1].src='wan'

uci set firewall.@redirect[-1].src_dport='10000'

uci set firewall.@redirect[-1].dest_ip='example.lan'

uci set firewall.@redirect[-1].dest_port='10000'

I’m asking because, something like that could be scripted, in a manner similar to how we do dynamic dns, where you poll your external ip address and if you detect that it changes, you update your dns record.

In this case this is almost the same thing, we poll the dns record and if it changes we update the firewall rules. The question is doing that, reliably, minimizing disruption duration and scope (like for instance /etc/init.d/firewall restart might be more crude than necessary) and still being efficient with lots of records.

And if the DNS server or DHCP server are accessible, using them to push update to the “Dynamic Port Forward” process.

And I know this is starting to sound like the universally revilled UPnP which, I swear, I have never used and don’t even know how it works and I, as a shut-in hermit, also spit on the grave of the “end-to-end principle” as much as the next networking forum dweller,

but I hope you can appreciate the nuance between the firewall maintaining dynamic port forwarding based on router-based policies, rather than allowing the anarchy of UPnP to run rampant on the internet and just “have things work” without spending 3 hours with grandma to explain to her how to forward a port, no, of course I would never want to live in such a dystopia ! If it just worked like that when why would Grandma give me cookies if I don’t spend 2 weeks setting up her self-hosted infrastructure so she can finally renounce the cloud, but I digress.

Why? The IP address that DHCP hands out doesn't have to come from the dynamic IP range. You can configure it to hand out a specific, non-changing IP address based on MAC address, DUID, or hostname. The port forwarding rule can then use that specific IP address.

This can work even with dynamic IPv6 prefixes because OpenWrt's firewall supports dynamic prefix forwarding.

The firewall (including port forwarding) is handled by nftables which runs in kernel space. DNS resolution always happens in user space. There is a way for the kernel to call into user space for DNS resolution, but this is way more complicated than just using DHCP to assign a static IP address and writing the firewall rule with it.

There are situation where fixed IP addresses are impractical, unfeasible or otherwise undesirable.

I’m curious if the kernel could keep it’s own firewall rules up to date with the cached dns entries, but I think the only practical way to do this is a userspace process, that monitors DNS/IP address pairs and update the “hardcoded” IP addresses in the firewall tables as they change.

I’m curious if anyone maybe already made such a process, a kind of “reverse dynamic dns”

Can you describe what situations would make a fixed IP address problematic? The suggestion was for a DHCP reservation so that the host always gets the same IP, but it is handled by the DHCP server (so the host just uses DHCP and doesn't need any special configuration). This ensures that the host in question always gets the same IP address,

In your proposed environment, what system is responsible for handling the DNS responses so that you can resolve the hostname to an IP address on your lan? In other words, if you were to want to resolve myhost.mydomain to its IP (say 192.168.51.32), who is answering that DNS resolution request? Is it dnsmasq on the OpenWrt router?

In the barebone version where the DNS and DHCP are not modifiable but query-able, they could be on another leg of a decentralized LAN, maybe it’s something inside a virtual machine that will identify itself by hostname to the DHCP server but have a randomnized MAC on every boot.

The one constant we can expect is that the hostname can be somehow resolved to an IP address, that’s the only guarantee in this scenario.

You're able to modify the network-wide firewall settings but can't modify the network-wide DNS and DHCP settings? This is a very unlikely scenario, especially since these things are usually handled by the same OpenWrt box. And even if they're on separate machines, they're hopefully administered by the same person (or team, if your setup is big enough for that).

I mentioned this earlier:

To be fair, as I understand it the "match-on-hostname" feature only works with Dnsmasq. Odhcpd (which is what OpenWrt uses for DHCPv6) matches only on MAC or DUID. In any case, MAC randomization isn't useful or relevant on server hosts and should be disabled.

What barebones version is this? Something you are actually using, or just a hypothetical?

Meanwhile, if DNS is not modifiable, how does it get the domain name records for your internal lan?

Not sure I'm understanding this.... are you talking about a network with multiple subnets/VLANs? Some host still needs to be the DHCP server, and the same or a different host needs to be a DNS server if you need internal DNS resolution. So what host is that? Because the DHCP server needs to advertise the appropriate DNS server, and you're saying that both the DNS and DHCP servers are not modifiable... so how do they get configured?

Why would you randomize the MAC on a host (virtual or real) that is setup for services that need to be accessible to the internet? And what is to prevent the hostname from changing (or simply not being sent/advertised)? Or worse, what stops a second host from claiming (incorrectly/accidentally or maliciously) the hostname?

As it is now, DNS doesn't resolve the hostnames that are (sometimes) advertised from the host.

It’s hypothetical, if you search the internet, you will find many instances of people who would much rather use a hostname rather than maintain static coded ip address tables, but since that’s apparently impossible the discussion dies there or get XYed into some other way of doing it.

I don’t want to maintain static addresses anywhere if I can help it. The clients dynamically identify themselves by hostname by their dhcp client, that should be the only that host is identified.

I understand client can then spoof their hostname, that’s not a concern for me.

I just want host example.lan to get port 10000 forwarded. I don’t want to create static leases, record MAC addresses or other XY solution.

On my network there is a dhcp client that announces itself as example.lan, I want that host to get port 10000 forwarded with as little written exception or infrastructure than that.

And I don’t want to assume that the router is also the dhcp & dns server, it might be but not necessarily accessible.

So I’m looking at something that continuously polls the dns server for the lookup of example.lan’s address, and keeps the firewall rules updated.

Has there ever been a script, deamon, nft-trick done to do this, or has the solution up to this point always always always been “just set a static ip” ?

Ok, I think this code *might* work

Here is how it works

When it starts, checks if nftable set NFT_TABLE exists,

(check the table, the prerouting chain, the table set and the forward rule)

Then it checks, if there is a record for example.lan from server DNS_SERVER

If there is, this IP is added to the table set else the set is cleared

then the loop starts, checks if the record still exists

if it doesn’t, table set is cleared, then wait and loop

if it changed, the table set is updated, then wait and loop

#!/bin/sh

HOSTNAME="example.lan"
DNS_SERVER="192.168.1.35"

TABLE_FAMILY="inet"
TABLE_NAME="dynpf"
SET_NAME="example_lan"

PROTO="tcp"
DPORT="10000"
TARGET_PORT="10000"

POLL_INTERVAL=5

NFT_TABLE="$TABLE_FAMILY $TABLE_NAME"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $*"
}

resolve_ip() {
    dig +short @"$DNS_SERVER" "$HOSTNAME" A \
        | grep -E '^[0-9.]+' \
        | head -n1
}

table_exists() {
    nft list table $NFT_TABLE >/dev/null 2>&1
}

set_exists() {
    nft list set $NFT_TABLE $SET_NAME >/dev/null 2>&1
}

rule_exists() {
    nft list chain $NFT_TABLE prerouting 2>/dev/null \
        | grep -q "@$SET_NAME"
}

ensure_nft_objects() {
    if ! table_exists; then
        log "Creating nft table $NFT_TABLE"
        nft add table $NFT_TABLE
    fi

    if ! nft list chain $NFT_TABLE prerouting >/dev/null 2>&1; then
        log "Creating prerouting chain"
        nft add chain $NFT_TABLE prerouting \
            "{ type nat hook prerouting priority dstnat; }"
    fi

    if ! set_exists; then
        log "Creating nft set $SET_NAME"
        nft add set $NFT_TABLE $SET_NAME "{ type ipv4_addr; flags interval; }"
    fi

    if ! rule_exists; then
        log "Creating DNAT rule"
        nft add rule $NFT_TABLE prerouting \
            $PROTO dport $DPORT \
            dnat to ip daddr @$SET_NAME:$TARGET_PORT
    fi
}

get_current_ip() {
    nft list set $NFT_TABLE $SET_NAME 2>/dev/null \
        | awk '/elements/ {getline; gsub(/[ ,]/,""); print}'
}

set_ip() {
    IP="$1"
    nft flush set $NFT_TABLE $SET_NAME
    nft add element $NFT_TABLE $SET_NAME { "$IP" }
}

clear_set() {
    nft flush set $NFT_TABLE $SET_NAME
}

# --- Startup -------------------------------------------------

log "Starting dynamic port forward for $HOSTNAME"

ensure_nft_objects

# Initial population
IP="$(resolve_ip)"
if [ -n "$IP" ]; then
    log "Initial DNS resolution: $IP"
    set_ip "$IP"
else
    log "No DNS record at startup, leaving set empty"
    clear_set
fi

# --- Main loop -----------------------------------------------

LAST_IP=""

while true; do
    IP="$(resolve_ip)"

    if [ -z "$IP" ]; then
        if [ -n "$LAST_IP" ]; then
            log "DNS record disappeared, disabling forwarding"
            clear_set
            LAST_IP=""
        fi
        sleep "$POLL_INTERVAL"
        continue
    fi

    if [ "$IP" != "$LAST_IP" ]; then
        log "DNS update detected: $LAST_IP -> $IP"
        set_ip "$IP"
        LAST_IP="$IP"
    fi

    sleep "$POLL_INTERVAL"
done

I think that might work, although I think I rather go through uci for that

Because I would like the web user interface to always show what the “dynamic port forward” is actually pointing to at that moment.

Given that this isn't actually an active concern on OpenWrt, I would say that the extra work to achieve this unusual goal is both unnecessary and actually counter to the best practices and tools that exist within not just OpenWrt, but practically all router firmware options.

In truth, I've never seen this type of request before. While I do understand what is being asked from a technical perspective, I don't really understand the premise of it from a logical and practical perspective. If you have the ability to not only adjust the firewall, but also run arbitrary scripts on the router, why jump through so many hoops when there are tools and methods already available?

That is not good network design, especially because not all hosts actually advertise a hostname (it is optional). But if this is how you want to architect your network, by all means.

Sure.... that's fine. Some people run these services on a PiHole, for example. But the same problem, and the same pre-existing solutions are present in this case, too.

That is, IMO, much more complex and convoluted than just setting a simple static DHCP reservation.

I'm guessing this is AI generated? Have you tested it?

1 Like

Yes, I understand this design goes again IT orthodoxy, but filtering by hostname makes a lot of sense than trying to remember a IP address spaghetti. I mean, that’s why DNS exist, to have words that make sense instead of gibberish numbers.

The reason of this post was first to find if anyone had actually done it before and second, to formulate a solution, I think I have done that, but I’m going to refactor it to use uci instead because I want the webui to display the actual configuration rather than a shadow of it. Maybe I can update both at once, I’m not sure.

I have often stumble into this idea, I didn’t keep notes but here are some links to those discussions and how they usually turn out.

https://old.reddit.com/r/nginxproxymanager/comments/1os8x0j/allow_hosts_by_ip_resolved_via_dns_domain_name/

this next one is just about using dns for readability, but it’s close

https://old.reddit.com/r/linuxquestions/comments/fe288e/iptables_and_dns/

image

https://old.reddit.com/r/linuxquestions/comments/vjhq8i/how_to_make_nftables_resolve_dns_entry_during_the/

There’s people who would like the DNS resolved at creation time and then leave them as is, as if DNS don’t change (and they generally don’t of course). It points to how ineffective the filtering system, lacking this very basic feature. It’s not a openwrt thing, it’s a kernel thing, because the kernel still thinks it’s running on a 16 MHz realtime microcontroller so it can’t wait hundreds of milliseconds for a DNS to resolve or GASP just letting the packet through and filtering later packets when the response comes in.

This problem has a sort of chicken-and-egg quality to it, of course nobody does this because the system is simply incapable, so it looks like there’s no reason to improve the system because nobody is doing it.

It’s similar to how multicast is all sorts of broken and useless, so no one uses it so no one fixes it.

And while doing this research I found …

The thing I was searching for maybe ?

Also I found this

I’m not sure if this is “it”

Yeah, but it's more than "IT orthodoxy" -- there are really good reasons why the methods exist the way they do.

Yeah. But that's not the whole of it...

  • DNS was made to make it easier to navigate to your desired servers by allowing names instead of just numbers, and those names could be nested as a function of the hierarchical nature of the system.
  • Initially, all hosts using TCP/IP were static meaning that they were manually configured which meant that there was always an easy way to map DNS > IP address.
  • DHCP was specified in 1993, and 1.0 came out 1998, making it much easier to handle networks where the client IP addresses largely did not need to be kept static (i.e. most of those hosts were not being used as servers).
  • The idea of DHCP reservations allows hosts to have "effective" static IPs via a centrally managed DHCP server file. This makes it easier on the host side in that they simply use DHCP client (essentially no configuration needed), but they always get the expected IP address.
  • DNS and DHCP reservations are configured at the server level.
  • Because many consumer/residential customers do not get a static IP from their ISP (instead often using DHCP), the idea of dynamic DNS arose which allows DNS records to be mapped to DHCP hosts that may have address changes at some given interval or situation. Here, the client sends a message to the server to update its DNS record with its current IP. This is useful for large scale or global network DNS, but usually doesn't make sense in a LAN.

Think about that lineage and realize that that what you're doing is re-inventing dynamic dns, but in a way that is much more fragile and error prone.

You basically want a local dynamic DNS thing and you want it to be based purely on the advertised hostname from the host. What you're not considering is the fact that, because it is not centrally managed, there can be collisions. Further, not all OS's send a hostname when requesting a DHCP lease, so the information may not always be available.

Multicast has its issues, but is very commonly used (think of all the local devices that auto-discover each other on your lan, including things like Airplay, Chromecast, DLNA, and all the other media systems, not to mention simply sharing files between computers).

Suffice to say that by refusing to use the tools that exist (i.e. DHCP reservations that can include DNS entries), you're adding a huge amount of additional complexity and risk trying to reinvent a combination of DHCP+DNS+ddns that has already been solved.

That said... you didn't answer this:

2 Likes