From: Etienne Champetier Date: Sun, 22 Jun 2025 12:06:36 +0000 (-0400) Subject: simple-captive-portal: add new package X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=e0d761e79b1153a84633ca9a1de9644950148316;p=openwrt-packages.git 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 --- diff --git a/net/simple-captive-portal/Makefile b/net/simple-captive-portal/Makefile new file mode 100644 index 000000000..0788fb124 --- /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 000000000..5931968ed --- /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 000000000..ce6b42c9a --- /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 000000000..49ac18bb2 --- /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 000000000..3980983aa --- /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 000000000..7ef1f0471 --- /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 000000000..c9bbac88d --- /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 000000000..c25dc397b --- /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