Score:1

Port forward DNS using pf

br flag

On OpenBSD, I can successfully & transparently forward ports 80 and 443 to services running on custom, unprivileged ports using the following /etc/pf.conf:

tcp_pass = "{ 22 80 123 443 }"

block all
pass out log on egress proto tcp to any port $tcp_pass keep state

pass in log on egress proto tcp from any to any port 80 rdr-to 127.0.0.1 port 3080 keep state
pass in log on egress proto tcp from any to any port 443 rdr-to 127.0.0.1 port 3443 keep state

I tested this by loading up a static file server bound to 3080, then doing a cURL call to the server from another machine like so:

curl -v 192.168.1.xxx

... and I got back a 200 status code and the HTML content I'd expect.


Now I'd like to do the same with DNS. First I updated the tcp_pass macro to include 53 and created a second macro for udp_pass and put 53 in that too, followed by a udp rule.

Then I tried adding the following rules (see comment in code block below):

tcp_pass = "{ 22 53 80 123 443 }"
udp_pass = "{ 53 }"

# ...

pass out log on egress proto udp to any port $udp_pass keep state

# ...

# the new rules I added -- emulating the http(s) rules from before
pass in log on egress proto tcp from any to any port 53 rdr-to 127.0.0.1 port 5353 keep state
pass in log on egress proto udp from any to any port 53 rdr-to 127.0.0.1 port 5353 keep state

I started up a DNS server on port 5353 and tried making a request from an external machine to this one:

  • dig @192.168.1.xxx -p 5353 cnn.com works as expected: returns instantly with the correct response

  • dig @192.168.1.xxx cnn.com hangs, then times out with the following error

    ; <<>> DiG 9.10.6 <<>> @192.168.1.xxx cnn.com
    ; (1 server found)
    ;; global options: +cmd
    ;; connection timed out; no servers could be reached
    

I follow the pf logs with tcpdump -nettti pflog0 when I make the DNS queries and I see only the following entries when requesting on port 53:

Jun 09 17:15:22.513529 rule 10/(match) pass in on iwm0: 192.168.1.yyy.58201 > 192.168.1.xxx.53: 64594+ [1au] A? cnn.com.(36)
Jun 09 17:15:22.513933 rule 6/(match) pass out on iwm0: 192.168.1.xxx.47191 > 9.9.9.9.53: 64594+ [1au] A? cnn.com.(36)

If I rebind the DNS server to port 53 directly using root, resolution works, so I know the problem is with my PF configuration and not my network.

My goal is to run my DNS server on an unprivileged port and port forward 53 to that port. I'm not sure what to try next and would appreciate any insight.

poige avatar
ke flag
tcpdump, tcpdump, tcpdump…
Tom Yan avatar
in flag
I don't know much about BSDs, but in Linux redirecting to `127/8` IPs doesn't work by default as they are not routable. One has to make them so by setting the sysctl `route_localnet` to `1`. Unless you have reasons to bind to `127.0.0.1` only, you may want to try not to and DNAT only the port number (but keeping the destination address untouched).
Paul avatar
cn flag
@TomYan I don't want to discourage you from contributing to this site, but do please try to leave comments on topics that you know about.
Tom Yan avatar
in flag
@Paul To be frank, I'm not seeing how my comment "hurt". If you know OpenBSD well, you can reassure both the OP and me that it does not do something similar. Otherwise my comment could at least give the OP a potential direction to finding the cause. (The *reason* that `route_localnet` exists isn't quite "Linux-specific" anyway.)
Paul avatar
cn flag
@TomYan Why would you expect an OpenBSD user to research how to configure Linux just to understand what it is you are trying to advise them of?
Tom Yan avatar
in flag
@Paul `research how to configure Linux` that's not at all what I expect. And again, if the OP thinks it would be a waste of time to google what `route_localnet` does in Linux, he can ignore my comment. (And that's not even what I suggest he does. I practically already told what it does. My point has always been that he can check if OpenBSD by default does not route for `127/8` and whether there's a sysctl or whatever setting that is equivalent to `route_localnet`. It's *bizarre* that you interpreted my comment the way you did.)
Tom Yan avatar
in flag
@Paul I even gave another trial-and-error aprroach, namely by avoiding binding only and/or redirecting to 127.0.0.1 (as a test).
Paul avatar
cn flag
@TomYan As you can see from the answer below, you are guessing about how to troubleshoot a system you know nothing about. It isn't useful.
Score:1
br flag

I think I solved it!

The crux of my problem—as I understand it—is that my initial rule worked for 53 but I had no rules allowing traffic in & out of 5353, so I was effectively redirecting to a dead-end. I realized my original question did mention my locally-running nameserver, but neglected to mention it is resolved/queried/whatever by way of another daemon (Tailscale)... and traffic out of that daemon was getting blocked. ‍♂️

Here's my up-to-date pf.conf (note that I switched to using service names instead of numerical ports, but the two are interchangeable AFAIK):

tcp_pass = "{ ssh domain www ntp https }"
udp_pass = "{ domain }"

# block everything
block all

# allow outgoing $tcp_pass & $udp_pass
pass out proto tcp to port $tcp_pass keep state
pass out proto udp to port $udp_pass keep state

# allow ping
pass in inet proto icmp all icmp-type 8 code 0

# allow ssh
pass in proto tcp to port ssh keep state

# allow local traffic
pass in on lo proto tcp from lo to lo keep state
pass out on lo proto tcp from lo to lo keep state

# redirect www and https to custom ports
pass in proto tcp to port www rdr-to 127.0.0.1 port 3080 keep state
pass in proto tcp to port https rdr-to 127.0.0.1 port 3443 keep state

# redirect domain to custom port
pass in proto { tcp, udp } to port domain rdr-to 127.0.0.1 port 5353 keep state
pass in proto udp from port 5353 # <-- the piece (1) I was missing!
pass out proto udp from port 5353 # <-- the piece (2) I was missing!

This configuration appears to work as I desire, allowing me to serve www, https, and domain on custom, unprivileged ports transparently.


Other notes:

  • For a time I was changing /etc/pf.conf and applying my updates with pfctl -f /etc/pf.conf and not seeing the changes I had expected, only to remember hours later that I had previously disabled pf using pfctl -d! I re-enabled pf with pfctl -e and saw results matching my expectations, after which I stepped outside, touched some grass, drank a , and watched some ☁️s.
  • When debugging with tcpdump, I was overly focused on 53 and didn't consider my issue could've been related to 5353. In the future, I'll remember to check all the ports in play.
  • @Tom Yan I haven't had any issues routing to 127/8 ips; the above solution required no extra system configuration changes on OpenBSD 7.3.
  • @poige I tried repeating "tcpdump, tcpdump, tcpdump" out loud, thinking it might summon Puffy or Theo de Raadt like Beetlejuice, but alas, nothing happened. After that disappointment, I continued to read pf.conf(5), tcpdump(8), Chapters 21 & 22 of Absolute OpenBSD, and the pf guide on openbsd.org. My epiphany arrived after a rule change blocked a request on localhost to the admin api for my http server on a custom port, and I thought to check if pf was blocking 5353 too.
Paul avatar
cn flag
It takes me time to read through a `pf.conf` and figure out what is going on, so great that you solved your problem. There is no default limitation in `pf` for routing packets to `127/8`. Note that there is a system limitation if you want `pf` to route packets between interfaces (set `sysctl net.inet.ip.forwarding=1` - well covered in *The book of PF*, an indespensible book for all OpenBSD users).
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.