From e0d761e79b1153a84633ca9a1de9644950148316 Mon Sep 17 00:00:00 2001 From: Etienne Champetier Date: Sun, 22 Jun 2025 08:06:36 -0400 Subject: [PATCH] simple-captive-portal: add new package This package intercepts/blocks traffic from 'interface' and redirects http requests to a splash page that you can personalize, stored in '/etc/simple-captive-portal/'. After clicking on 'connect' the MAC of the client is allowed, for 'timeout' seconds (24h), allowing both IPv4 and IPv6. If your guest interface defaults to input drop or reject (recommended), make sure to allow tcp 8888-8889 on input (and also dns and dhcp). Signed-off-by: Etienne Champetier --- net/simple-captive-portal/Makefile | 47 +++++++++++ net/simple-captive-portal/README.md | 52 ++++++++++++ .../files/etc/config/simple-captive-portal | 5 ++ .../hotplug.d/net/00-simple-captive-portal | 5 ++ .../files/etc/init.d/simple-captive-portal | 81 +++++++++++++++++++ .../etc/simple-captive-portal/index.html | 13 +++ .../simple-captive-portal/capabilities.json | 17 ++++ .../share/simple-captive-portal/portal.lua | 27 +++++++ .../share/simple-captive-portal/redirect.lua | 13 +++ 9 files changed, 260 insertions(+) create mode 100644 net/simple-captive-portal/Makefile create mode 100644 net/simple-captive-portal/README.md create mode 100644 net/simple-captive-portal/files/etc/config/simple-captive-portal create mode 100644 net/simple-captive-portal/files/etc/hotplug.d/net/00-simple-captive-portal create mode 100644 net/simple-captive-portal/files/etc/init.d/simple-captive-portal create mode 100644 net/simple-captive-portal/files/etc/simple-captive-portal/index.html create mode 100644 net/simple-captive-portal/files/usr/share/simple-captive-portal/capabilities.json create mode 100644 net/simple-captive-portal/files/usr/share/simple-captive-portal/portal.lua create mode 100644 net/simple-captive-portal/files/usr/share/simple-captive-portal/redirect.lua diff --git a/net/simple-captive-portal/Makefile b/net/simple-captive-portal/Makefile new file mode 100644 index 0000000000..0788fb124a --- /dev/null +++ b/net/simple-captive-portal/Makefile @@ -0,0 +1,47 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=simple-captive-portal +PKG_VERSION:=2025.06.22 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=Etienne CHAMPETIER +PKG_LICENSE:=GPL-2.0-or-later + +include $(INCLUDE_DIR)/package.mk + +Build/Compile= + +define Package/simple-captive-portal + SUBMENU:=Captive Portals + SECTION:=net + CATEGORY:=Network + TITLE:=Simple captive portal + PKGARCH:=all + DEPENDS:=+uhttpd +uhttpd-mod-lua +luci-lib-ip +endef + +define Package/simple-captive-portal/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/simple-captive-portal $(1)/etc/config/simple-captive-portal + $(INSTALL_DIR) $(1)/etc/hotplug.d/net/ + $(INSTALL_DATA) ./files/etc/hotplug.d/net/00-simple-captive-portal $(1)/etc/hotplug.d/net/00-simple-captive-portal + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/simple-captive-portal $(1)/etc/init.d/simple-captive-portal + $(INSTALL_DIR) $(1)/etc/simple-captive-portal/ + $(INSTALL_DATA) ./files/etc/simple-captive-portal/index.html $(1)/etc/simple-captive-portal/index.html + $(INSTALL_DIR) $(1)/usr/share/simple-captive-portal/ + $(INSTALL_DATA) ./files/usr/share/simple-captive-portal/capabilities.json $(1)/usr/share/simple-captive-portal/capabilities.json + $(INSTALL_DATA) ./files/usr/share/simple-captive-portal/portal.lua $(1)/usr/share/simple-captive-portal/portal.lua + $(INSTALL_DATA) ./files/usr/share/simple-captive-portal/redirect.lua $(1)/usr/share/simple-captive-portal/redirect.lua +endef + +define Package/simple-captive-portal/conffiles +/etc/simple-captive-portal/ +/etc/config/simple-captive-portal +endef + +define Package/simple-captive-portal/description + Provides a simple captive portal splash page. +endef + +$(eval $(call BuildPackage,simple-captive-portal)) diff --git a/net/simple-captive-portal/README.md b/net/simple-captive-portal/README.md new file mode 100644 index 0000000000..5931968ed6 --- /dev/null +++ b/net/simple-captive-portal/README.md @@ -0,0 +1,52 @@ +# Simple captive portal + +This package intercepts/blocks traffic from 'interface' and +redirects http requests to a splash page that you can personalize, +stored in '/etc/simple-captive-portal/'. +After clicking on 'connect' the MAC of the client is allowed, +for 'timeout' seconds (24h), allowing both IPv4 and IPv6. + +If your guest interface defaults to input drop or reject (recommended), +make sure to allow tcp 8888-8889 on input (and also dns and dhcp). + +Here an example (ipv4) firewall configuration. + +``` +config zone + option name 'guest' + option forward 'REJECT' + option output 'ACCEPT' + option input 'REJECT' + option network 'guest' + +config forwarding + option dest 'wans' + option src 'guest' + +config rule + option name 'guest-dhcp' + option src 'guest' + option family 'ipv4' + option proto 'udp' + option dest_port '67' + option target 'ACCEPT' + +config rule + option name 'guest-dns' + option src 'guest' + option family 'ipv4' + list proto 'tcp' + list proto 'udp' + option dest_port '53' + option target 'ACCEPT' + +config rule + option name 'guest-portal' + option src 'guest' + option family 'ipv4' + list proto 'tcp' + option dest_port '8888-8889' + option target 'ACCEPT' +``` + +To disable simple-captive-portal, just unset/comment 'interface' in the uci config. diff --git a/net/simple-captive-portal/files/etc/config/simple-captive-portal b/net/simple-captive-portal/files/etc/config/simple-captive-portal new file mode 100644 index 0000000000..ce6b42c9ac --- /dev/null +++ b/net/simple-captive-portal/files/etc/config/simple-captive-portal @@ -0,0 +1,5 @@ +config simple-captive-portal main + #option interface guest + #option port_redirect 8888 + #option port_portal 8889 + #option timeout 86400 diff --git a/net/simple-captive-portal/files/etc/hotplug.d/net/00-simple-captive-portal b/net/simple-captive-portal/files/etc/hotplug.d/net/00-simple-captive-portal new file mode 100644 index 0000000000..49ac18bb20 --- /dev/null +++ b/net/simple-captive-portal/files/etc/hotplug.d/net/00-simple-captive-portal @@ -0,0 +1,5 @@ +INTF=$(uci -q get simple-captive-portal.main.interface) + +if [ "$ACTION" = add -a "$DEVICENAME" == "$INTF" ]; then + /etc/init.d/simple-captive-portal firewall +fi diff --git a/net/simple-captive-portal/files/etc/init.d/simple-captive-portal b/net/simple-captive-portal/files/etc/init.d/simple-captive-portal new file mode 100644 index 0000000000..3980983aac --- /dev/null +++ b/net/simple-captive-portal/files/etc/init.d/simple-captive-portal @@ -0,0 +1,81 @@ +#!/bin/sh /etc/rc.common +START=10 +USE_PROCD=1 +EXTRA_COMMANDS='firewall' + +firewall() { + local INTF PORT_REDIRECT TIMEOUT + config_load "simple-captive-portal" + config_get INTF main interface + [ -z "${INTF}" ] && exit 0 + config_get PORT_REDIRECT main port_redirect 8888 + config_get TIMEOUT main timeout 86400 + + /usr/sbin/nft -f- < + + + +Simple captive portal + + + +

Free Wifi

+

Click here to connect

+
+ + diff --git a/net/simple-captive-portal/files/usr/share/simple-captive-portal/capabilities.json b/net/simple-captive-portal/files/usr/share/simple-captive-portal/capabilities.json new file mode 100644 index 0000000000..7ef1f0471b --- /dev/null +++ b/net/simple-captive-portal/files/usr/share/simple-captive-portal/capabilities.json @@ -0,0 +1,17 @@ +{ + "bounding": [ + "CAP_NET_ADMIN" + ], + "effective": [ + "CAP_NET_ADMIN" + ], + "ambient": [ + "CAP_NET_ADMIN" + ], + "permitted": [ + "CAP_NET_ADMIN" + ], + "inheritable": [ + "CAP_NET_ADMIN" + ] +} diff --git a/net/simple-captive-portal/files/usr/share/simple-captive-portal/portal.lua b/net/simple-captive-portal/files/usr/share/simple-captive-portal/portal.lua new file mode 100644 index 0000000000..c9bbac88d3 --- /dev/null +++ b/net/simple-captive-portal/files/usr/share/simple-captive-portal/portal.lua @@ -0,0 +1,27 @@ +require "luci.ip" + +function handle_request(env) + local mac = nil + luci.ip.neighbors({ dest = env.REMOTE_ADDR }, function(n) mac = n.mac end) + if mac == nil then + uhttpd.send("Status: 500 Internal Server Error\r\n") + uhttpd.send("Server: simple-captive-portal\r\n") + uhttpd.send("Content-Type: text/plain\r\n\r\n") + uhttpd.send("ERROR: MAC not found for IP " .. env.REMOTE_ADDR) + return + end + + ret = os.execute("nft add element inet simple-captive-portal guest_macs { " .. tostring(mac) .. " }") + if ret ~= 0 then + uhttpd.send("Status: 500 Internal Server Error\r\n") + uhttpd.send("Server: simple-captive-portal\r\n") + uhttpd.send("Content-Type: text/plain\r\n\r\n") + uhttpd.send("ERROR: failed to add mac to set\n") + return + end + + uhttpd.send("Status: 200 OK\r\n") + uhttpd.send("Server: simple-captive-portal\r\n") + uhttpd.send("Content-Type: text/plain\r\n\r\n") + uhttpd.send("You now have internet access\n") +end diff --git a/net/simple-captive-portal/files/usr/share/simple-captive-portal/redirect.lua b/net/simple-captive-portal/files/usr/share/simple-captive-portal/redirect.lua new file mode 100644 index 0000000000..c25dc397b2 --- /dev/null +++ b/net/simple-captive-portal/files/usr/share/simple-captive-portal/redirect.lua @@ -0,0 +1,13 @@ +port_portal = os.getenv("PORT_PORTAL") + +function handle_request(env) + uhttpd.send("Status: 302 Found\r\n") + uhttpd.send("Server: simple-captive-portal\r\n") + if string.find(env.SERVER_ADDR, ":") == nil then + uhttpd.send("Location: http://" .. env.SERVER_ADDR .. ":" .. port_portal .. "/\r\n") + else + uhttpd.send("Location: http://[" .. env.SERVER_ADDR .. "]:" .. port_portal .. "/\r\n") + end + uhttpd.send("Cache-Control: no-cache\r\n") + uhttpd.send("Content-Length: 0\r\n\r\n") +end