Implementing DNS Captive Portal with Pi-Hole

Using Pi-Hole to implement DNS Captive Portal

Motivation

I recognize there are a few reasons to want a captive portal. I received an Amazon Echo Show which I'd like to customize to show my own landing page (I want to put a to-do list, upcoming nearby public transit times, etc). Someone on redit showed that the Echo Show works nicely with captive portal. If I can design my own web page and use it as the captive portal landing page, I think I'll have free reign on the device's displays. It has the happy side effect of disabling all amazon tracking.

Background

Captive portal can be implemented via HTTP redirects or by DNS redirects. With the tools at my disposal, only DNS redirection works on my home network. I thought I was going to have to write my own feature to implement captive portal on pi-hole, since I couldn't find any articles on the web about it. (That's the main motivation for writing this little blog post, in case others have similar desires). I spent a few days reading through the FTL code, debugged a bit, and was just getting ready to make some changes when a few out-of-the-box configuration settings I'd seen floating around fell into place and I realized no new features would be required.

Configuration

The missing piece for me was the Groups section, which I'd somehow never noticed. I needed two behaviors (an inescapable captive portal and normal ad-blocking DNS), and splitting my network into groups was precisely the tool for this task. Towards this end, I created a CaptivePortal group.

The next piece is IP blocking (where a blocked domain responds with the Pi-Hole's IP address). I prefer the NULL for general blocking, so I used the regex blocking extension to craft the following rule and apply it only to the CaptivePortal group: *;reply=IP (if the web page is hosted on the pi-hole machine) or *;reply=W.X.Y.Z for an arbitrary captive portal page IP. I'm not at home and can't test on the Echo Show yet, but I think it'll work.

The webpage itself isn't written yet. I'm thinking to hijack the pi-hole index.html page ("Did you mean to go to the admin panel?"), since the IP redirect won't include a route. In the same vein, there might be some complexity with getting the index page to show up, as I have no idea what route (if any) will be requested by the operating system when it detects a captive portal.

Caveats

DNS captive portal has some limits. The Echo Show runs android, if I'm not mistaken, and so it ought to be performing the expected HTTP lookups (which should trigger the built-in captive portal behavior). If it doesn't, this won't work so well: when I tried using it from my computer, I ran into some problems mentioned in the linked page. Specifically, sites that use HTTPS will show the error page (not great) and require manual intervention to continue due to certificate issues (since the requested domain and the returned domain differ). Sites that enable HTST will completely fail to load. Recall that, since this is DNS redirection and not HTTP redirection, the browser still thinks it's loading the requested site.

Conclusion

I think implementing captive portal via HTTP redirect would be better. The DNS hijacking solution means that mismatches in the expected and actual website can trigger security warnings (for good reason). I'll update this post once I can test with my device, and also would also be curious to hear about other solutions (for example, that might avoid the mentioned HTST and HTTPS issues) to the same problem.

No.
A captive portal is a HTML page presented by a browser on a device upon first joining a public wifi network, requiring you to accept some terms before being granted access to the network's services. Typically, you'd run into them at airports, hotels, internet cafes and the likes.
As such, a captive portal s created by the provider of such a wifi network and would have no ties whatsoever to Amazon, unless the provider itself would introduce them on the captive portal HTML page.

Specifically, a captive portal would do nothing to disable any tracking.

Ah, perhaps I should clarify: there are multiple ways to implement captive portal but at the end of the day they all result in a web page (HTML) being served, informing the user of their blocked access to the internet until conditions are met. In my case, I only want a single device (the Echo Show) on my home network to be served the captive portal page.

As for blocking Amazon tracking, bear in mind that my goal is use DNS to redirect all traffic to my captive portal server. The page I serve will not be a normal captive portal page, with terms to accept before continuing to the internet; the page itself will be the content I desire to see on the Echo Show display. As this is the desired final state, it means the blocking rule which redirects all DNS requests to my captive portal server will continue to completely isolate the device from the internet -- and thus disable data collection by Amazon.

What you have in mind is not a captive portal at all.

Rather, it would seem you are trying to reinstantiate something like Pi-hole's blocking page, which was removed with Pi-hole FTL v5.17, Web v5.14 and Core v5.12 released in September 2022.

You may still try to roll your own - see the suggestions for a custom.php page at Upcoming Pi-hole changes – Pi-hole, but note that a block page can only be injected with HTTP - it won't work with HTTPS sites.

Nowadays, the vast majority of websites is using HTTPS, which is the major reason why Pi-hole has switched to NULL blocking (unspecified IP blocking) as default and removed that blocking page.

I appreciate the links you've shared. I'd been thinking about where best to inject my custom page, and having a framework for it already in place should make it a simple task. As I mentioned in my Caveats section, many operating systems use HTTP (rather than HTTPS) requests to detect a captive portal situation for exactly the reason you've mentioned. I just have to cross my fingers and hope that the device I'm trying to hijack is running one such operating system.

I disagree. You've cited Wikipedia's definition of captive portal, and so I'll reference that. While it's true my page provides no way to gain broader access to the internet, the cited definition does not require captive pages to do so. It's also true that my page will not be shown to all devices on the network, but that again is not a requirement of captive pages per Wikipedia's definition. I therefore refute (and frankly don't understand the benefit of) your argument that my page is not a captive portal page. I would concede that I'm really "making use of captive portal technology to display my own webpage" but at that point things become pedantic.

That said, I'd already been thinking to write a post script for anyone who wanted to implement a common captive portal page. So here it is:

Post Script: Implementing a Captive Portal page for broader internet access

To make the captive portal page I've described into a captive portal page as commonly used (requiring login and/or acceptance of terms), you'd want to configure Pi-Hole a little differently. You'd want the regex blocking rule (see earlier) to apply to everyone by default (rather than just the CaptivePortal group) and then implement a separate group of clients for whom that regex rule does not apply (let's call them the AllowList). Then, you'd want to write some back-end to your captive portal page implementing the login / acceptance of terms (or whatever you'd like). After a client met the requirements, you'd need a script to automate the changing of their group to the AllowList, which should free them from the captive portal page. I haven't looked but I suspect there's a simple API for changing a client's group.