diff --git a/dae/Makefile b/dae/Makefile index c39820d3..7e9d9848 100644 --- a/dae/Makefile +++ b/dae/Makefile @@ -5,12 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=dae -PKG_VERSION:=2026.08.25 +PKG_VERSION:=2026.08.26 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_PROTO:=git -PKG_SOURCE_VERSION:=502d976ee63539f198be18c40221691701b1cbcb +PKG_SOURCE_VERSION:=04137361d599099d9b1aa390785137adec72e372 PKG_SOURCE_URL:=https://github.com/olicesx/dae.git PKG_MIRROR_HASH:=skip diff --git a/luci-app-oaf/Makefile b/luci-app-oaf/Makefile index 80b14288..bc8a304a 100644 --- a/luci-app-oaf/Makefile +++ b/luci-app-oaf/Makefile @@ -1,15 +1,13 @@ - -# -# This is free software, licensed under the Apache License, Version 2.0 . -# - include $(TOPDIR)/rules.mk -LUCI_TITLE:=LuCI support for OAF +LUCI_TITLE:=LuCI app for OAF LUCI_PKGARCH:=all -LUCI_DEPENDS:=+appfilter +kmod-oaf +luci-compat +LUCI_DEPENDS:=+appfilter +kmod-oaf +luci-compat +luci-lib-jsonc + PKG_NAME:=luci-app-oaf -PKG_VERSION:=6.1.4 +PKG_VERSION:=7.0 PKG_RELEASE:=1 + include $(TOPDIR)/feeds/luci/luci.mk + # call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10003.png deleted file mode 100755 index 81f76f4f..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10003.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10004.png deleted file mode 100755 index e3447dd1..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10005.png deleted file mode 100755 index b208cea6..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10006.png deleted file mode 100755 index e9e9c4e5..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10007.png deleted file mode 100755 index 9d8923b2..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10007.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10008.png deleted file mode 100755 index a37cc9be..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10010.png deleted file mode 100755 index dd251642..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10011.png deleted file mode 100755 index 6bbdc0e8..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10011.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10012.png deleted file mode 100755 index 23046e6d..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10012.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10013.png deleted file mode 100755 index cbdc7d08..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10013.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10014.png deleted file mode 100755 index 27541b57..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10014.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10015.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10015.png deleted file mode 100755 index ebefaf5a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10015.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10016.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10016.png deleted file mode 100755 index 69a2c969..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10016.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10017.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10017.png deleted file mode 100755 index 8cb17a18..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10017.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10018.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10018.png deleted file mode 100755 index 41a1d52e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10018.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10019.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10019.png deleted file mode 100755 index 37a93217..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10019.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10020.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10020.png deleted file mode 100755 index 5dce396e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10020.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10021.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10021.png deleted file mode 100755 index d4c79dcf..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10021.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10022.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10022.png deleted file mode 100755 index 6bc4e323..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10022.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1003.png deleted file mode 100755 index ced03216..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1003.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10034.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10034.png deleted file mode 100755 index 4825ca47..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10034.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10035.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10035.png deleted file mode 100755 index 07e56e14..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10035.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1004.png deleted file mode 100755 index 6d73e955..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1005.png deleted file mode 100755 index 75e702e9..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1006.png deleted file mode 100755 index 83430bdb..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1007.png deleted file mode 100755 index 6ff6bbcd..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1007.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1008.png deleted file mode 100755 index 77283307..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1009.png deleted file mode 100755 index 9b6c8e53..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1009.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1010.png deleted file mode 100755 index 0e4e232d..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1012.png deleted file mode 100755 index 526b2064..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1012.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1013.png deleted file mode 100755 index 8b30ba1b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1013.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1014.png deleted file mode 100755 index 9b068599..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1014.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/11001.png deleted file mode 100755 index 74060db5..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11001.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/11002.png deleted file mode 100755 index a8d35fd0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11002.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/11003.png deleted file mode 100755 index 090bb5f1..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11003.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14001.png deleted file mode 100755 index 7f8e532e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14001.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14002.png deleted file mode 100755 index 67a04248..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14002.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14003.png deleted file mode 100755 index 6a790fe8..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14003.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14004.png deleted file mode 100755 index 4c374faf..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14005.png deleted file mode 100755 index f92f89f7..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14006.png deleted file mode 100755 index 7cb1b8d2..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14007.png deleted file mode 100755 index b9056dc9..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14007.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14008.png deleted file mode 100755 index 7a3b67ea..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14009.png deleted file mode 100755 index 0e6cd8e4..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14009.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14010.png deleted file mode 100755 index 8b54042c..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14011.png deleted file mode 100755 index b61b31ef..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14011.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14012.png deleted file mode 100755 index 64f342b0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14012.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14013.png deleted file mode 100755 index 29d301b0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14013.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14014.png deleted file mode 100755 index c283c48b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14014.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2001.png deleted file mode 100755 index 5aedcefa..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2001.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2005.png deleted file mode 100755 index 1aed8064..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2006.png deleted file mode 100755 index fa6ebaa7..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2007.png deleted file mode 100755 index 924a5a22..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2007.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2008.png deleted file mode 100755 index d5e1fd4c..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2009.png deleted file mode 100755 index 11da6624..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2009.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2010.png deleted file mode 100755 index f86222ab..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2011.png deleted file mode 100755 index 2ea2bd4e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2011.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2012.png deleted file mode 100755 index dc012b16..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2012.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2013.png deleted file mode 100755 index 8526f285..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2013.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2014.png deleted file mode 100755 index 0f2a8ed4..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2014.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2015.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2015.png deleted file mode 100755 index 97a9c5c0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2015.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2016.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2016.png deleted file mode 100755 index 5b3e18d3..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2016.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2017.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2017.png deleted file mode 100755 index d4db13fb..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2017.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2023.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2023.png deleted file mode 100755 index 14626ce0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2023.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2025.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2025.png deleted file mode 100755 index 5d8b5f81..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2025.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2026.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2026.png deleted file mode 100755 index e7027432..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2026.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2027.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2027.png deleted file mode 100755 index 7e907bb4..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2027.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2033.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2033.png deleted file mode 100755 index a419e2b1..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2033.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2034.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2034.png deleted file mode 100755 index 7863637a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2034.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2040.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2040.png deleted file mode 100755 index 59dc8fbf..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2040.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2041.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2041.png deleted file mode 100755 index 9eeb0bca..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2041.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2042.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2042.png deleted file mode 100755 index a102313b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2042.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2050.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2050.png deleted file mode 100755 index e75d5adb..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2050.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2051.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2051.png deleted file mode 100755 index 1ce8fbd1..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2051.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2067.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2067.png deleted file mode 100755 index 5a9647e4..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2067.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2068.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2068.png deleted file mode 100755 index 3e3ed9ea..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2068.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2069.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2069.png deleted file mode 100755 index 1ab35931..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2069.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2070.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2070.png deleted file mode 100755 index 96cb6fe3..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2070.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2071.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2071.png deleted file mode 100755 index 419765fb..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2071.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2074.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2074.png deleted file mode 100755 index 479549ab..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2074.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2075.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2075.png deleted file mode 100755 index c73c4d84..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2075.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2080.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2080.png deleted file mode 100755 index d5b4536a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2080.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3001.png deleted file mode 100755 index bc5c054f..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3001.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3004.png deleted file mode 100755 index 2a87819f..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3006.png deleted file mode 100755 index 5b0eb2bf..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3008.png deleted file mode 100755 index d546b752..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3010.png deleted file mode 100755 index a1d17050..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3011.png deleted file mode 100755 index 8c0f1b74..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3011.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3012.png deleted file mode 100755 index bf399d46..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3012.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3016.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3016.png deleted file mode 100755 index 7ffb2630..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3016.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3017.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3017.png deleted file mode 100755 index d7888ba5..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3017.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3018.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3018.png deleted file mode 100755 index 9f216f0a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3018.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3019.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3019.png deleted file mode 100755 index 523ec84b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3019.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3020.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3020.png deleted file mode 100755 index 3e67dff7..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3020.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3021.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3021.png deleted file mode 100755 index e1bb4642..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3021.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3022.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3022.png deleted file mode 100755 index 47d311e8..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3022.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3023.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3023.png deleted file mode 100755 index e1262611..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3023.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3024.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3024.png deleted file mode 100755 index 15791e61..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3024.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3025.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3025.png deleted file mode 100755 index 6c35b893..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3025.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3026.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3026.png deleted file mode 100755 index a0f25595..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3026.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3027.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3027.png deleted file mode 100755 index 92f98764..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3027.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3028.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3028.png deleted file mode 100755 index 1ef08a86..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3028.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3029.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3029.png deleted file mode 100755 index 630e7765..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3029.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3030.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3030.png deleted file mode 100755 index 630e7765..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3030.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3043.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3043.png deleted file mode 100755 index a8aed22b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3043.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3049.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3049.png deleted file mode 100755 index 8647fba9..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3049.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3084.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3084.png deleted file mode 100755 index 694a38f3..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3084.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3085.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3085.png deleted file mode 100755 index b2df1e17..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3085.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3086.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3086.png deleted file mode 100755 index 2acf4a4a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3086.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3089.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3089.png deleted file mode 100755 index 5db0102a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3089.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3094.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3094.png deleted file mode 100755 index f7799c9e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3094.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3121.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3121.png deleted file mode 100755 index a243e5c9..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3121.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4001.png deleted file mode 100755 index dfabf0f0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4001.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4002.png deleted file mode 100755 index 51f88a1c..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4002.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4003.png deleted file mode 100755 index c19cbd87..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4003.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4004.png deleted file mode 100755 index c8c87c63..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4005.png deleted file mode 100755 index 2a983244..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4006.png deleted file mode 100755 index ad6f7e6d..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4007.png deleted file mode 100755 index 350b7515..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4007.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4008.png deleted file mode 100755 index 951310ff..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4009.png deleted file mode 100755 index 3bff5319..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4009.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4010.png deleted file mode 100755 index 89be88cb..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4012.png deleted file mode 100755 index 19aaee6f..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4012.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4013.png deleted file mode 100755 index b3c53808..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4013.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4014.png deleted file mode 100755 index e3cd7859..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4014.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4015.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4015.png deleted file mode 100755 index 00ec2e05..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4015.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4016.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4016.png deleted file mode 100755 index edee7bfe..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4016.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4018.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4018.png deleted file mode 100755 index 9a06f47a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4018.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4019.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4019.png deleted file mode 100755 index f573b6e9..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4019.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4021.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4021.png deleted file mode 100755 index c4e5b369..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4021.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4022.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4022.png deleted file mode 100755 index 19640f9a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4022.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4023.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4023.png deleted file mode 100755 index 45478b31..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4023.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4024.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4024.png deleted file mode 100755 index 912813d5..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4024.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4025.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4025.png deleted file mode 100755 index b7257ebd..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4025.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4026.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4026.png deleted file mode 100755 index 49e49998..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4026.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4040.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4040.png deleted file mode 100755 index 4ac3afed..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4040.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4041.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4041.png deleted file mode 100755 index 18355f22..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4041.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4052.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4052.png deleted file mode 100755 index b7729e77..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4052.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4053.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4053.png deleted file mode 100755 index d65070f0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4053.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4054.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4054.png deleted file mode 100755 index 33d5fe53..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4054.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5001.png deleted file mode 100755 index 8d9bcdc4..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5001.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5002.png deleted file mode 100755 index b3d60616..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5002.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5003.png deleted file mode 100755 index 630e7765..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5003.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5004.png deleted file mode 100755 index f2bdb15b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5005.png deleted file mode 100755 index 7b6f20ce..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5006.png deleted file mode 100755 index 12149c1e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5007.png deleted file mode 100755 index 61837fa3..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5007.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5008.png deleted file mode 100755 index 20fe9c14..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5009.png deleted file mode 100755 index 26e95d30..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5009.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5010.png deleted file mode 100755 index b1f7d4ae..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5011.png deleted file mode 100755 index fc3cee01..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5011.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5012.png deleted file mode 100755 index 662ed392..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5012.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6001.png deleted file mode 100755 index bf81ed94..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6001.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6002.png deleted file mode 100755 index 48b4bd3b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6002.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6003.png deleted file mode 100755 index f3e14a00..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6003.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6004.png deleted file mode 100755 index a4a1916b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6005.png deleted file mode 100755 index a1b4de7d..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6006.png deleted file mode 100755 index a286157b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6007.png deleted file mode 100755 index 08e6f737..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6007.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6008.png deleted file mode 100755 index 2a3eab41..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6009.png deleted file mode 100755 index 81a10ed5..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6009.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6010.png deleted file mode 100755 index d2d7c413..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6011.png deleted file mode 100755 index e5699059..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6011.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6012.png deleted file mode 100755 index 185bb04b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6012.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6013.png deleted file mode 100755 index 07280c32..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6013.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6014.png deleted file mode 100755 index 417f3031..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6014.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7002.png deleted file mode 100755 index 3ecd6146..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7002.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7004.png deleted file mode 100755 index a8d35fd0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7005.png deleted file mode 100755 index 6607839d..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7006.png deleted file mode 100755 index 5aedcefa..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7007.png deleted file mode 100755 index 8253fd84..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7007.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7008.png deleted file mode 100755 index 421a9f0a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7009.png deleted file mode 100755 index 87bcebc8..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7009.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7010.png deleted file mode 100755 index d98d56aa..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7011.png deleted file mode 100755 index d62cb630..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7011.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7020.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7020.png deleted file mode 100755 index 8e899f28..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7020.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7030.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7030.png deleted file mode 100755 index 9152277b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7030.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7031.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7031.png deleted file mode 100755 index 3d611605..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7031.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7032.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7032.png deleted file mode 100755 index d2c8bc55..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7032.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8001.png deleted file mode 100755 index b2df1e17..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8001.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8002.png deleted file mode 100755 index 51193da3..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8002.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8003.png deleted file mode 100755 index bc90b67c..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8003.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8004.png deleted file mode 100755 index b0c4142e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8004.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8005.png deleted file mode 100755 index 6fd95f73..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8005.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8006.png deleted file mode 100755 index 4c217b2b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8006.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8008.png deleted file mode 100755 index f3e3a72e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8008.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8009.png deleted file mode 100755 index 479549ab..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8009.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8010.png deleted file mode 100755 index ee01aad9..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8010.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8020.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8020.png deleted file mode 100755 index 5d7bc0a1..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8020.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8026.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8026.png deleted file mode 100755 index a16637e5..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8026.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8027.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8027.png deleted file mode 100755 index 9333d453..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8027.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8029.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8029.png deleted file mode 100755 index 5bc1c777..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8029.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8030.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8030.png deleted file mode 100755 index fd08701c..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8030.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8031.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8031.png deleted file mode 100755 index ed3af349..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8031.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8035.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8035.png deleted file mode 100755 index cdf5d235..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8035.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8036.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8036.png deleted file mode 100755 index 2cb0c32b..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8036.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8037.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8037.png deleted file mode 100755 index c1b8dc8e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8037.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8038.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8038.png deleted file mode 100755 index f1dff01f..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8038.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8039.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8039.png deleted file mode 100755 index cda7743a..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8039.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8041.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8041.png deleted file mode 100755 index e67c0fce..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8041.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8044.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8044.png deleted file mode 100755 index ff615ca0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8044.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8046.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8046.png deleted file mode 100755 index 75537572..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8046.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8064.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8064.png deleted file mode 100755 index 33483b65..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8064.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8065.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8065.png deleted file mode 100755 index 4f336852..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8065.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8066.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8066.png deleted file mode 100755 index 14542a81..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8066.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8077.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8077.png deleted file mode 100755 index 1bb8c086..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8077.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8079.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8079.png deleted file mode 100755 index 8d1f6d40..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8079.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8087.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8087.png deleted file mode 100755 index f16c0de3..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8087.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8089.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8089.png deleted file mode 100755 index 03454029..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8089.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8090.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8090.png deleted file mode 100755 index c46f0806..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8090.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8092.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8092.png deleted file mode 100755 index 7ea6544d..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8092.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8093.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8093.png deleted file mode 100755 index ddced493..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8093.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8096.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8096.png deleted file mode 100755 index 420dcd84..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8096.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8098.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8098.png deleted file mode 100755 index 52ee3bc0..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8098.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8099.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8099.png deleted file mode 100755 index 08156734..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8099.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8100.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8100.png deleted file mode 100755 index dd251642..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8100.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8103.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8103.png deleted file mode 100755 index 83402263..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8103.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8104.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8104.png deleted file mode 100755 index 8004eb2c..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8104.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8105.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8105.png deleted file mode 100755 index ba7f02dd..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8105.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8106.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8106.png deleted file mode 100755 index 11a0790f..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8106.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8107.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8107.png deleted file mode 100755 index 854b57c3..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8107.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8108.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8108.png deleted file mode 100755 index a1265241..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8108.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8110.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8110.png deleted file mode 100755 index 1b6a493e..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8110.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8111.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8111.png deleted file mode 100755 index 7be639c8..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8111.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8112.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8112.png deleted file mode 100755 index 90c29ab5..00000000 Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8112.png and /dev/null differ diff --git a/luci-app-oaf/htdocs/luci-static/resources/css/common.css b/luci-app-oaf/htdocs/luci-static/resources/css/common.css deleted file mode 100644 index 5929e8a7..00000000 --- a/luci-app-oaf/htdocs/luci-static/resources/css/common.css +++ /dev/null @@ -1,83 +0,0 @@ - -body { - font-family: Arial, sans-serif; -} -h1, h2 { - color: #333; -} - - - -.button-check { - padding: 3px 8px; - background-color: transparent; - color: green; - border: 1px solid green; - border-radius: 3px; - cursor: pointer; - font-size: 12px; -} -.button-container { - display: flex; - justify-content: right; - margin-top: 5px; -} - -.submit-button { - margin-top: 30px; - width: 150px; - height: 30px; - background-color: #2885e8; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; -} -.submit-button:hover { - background-color: #0f77e6; -} - -.switch { - position: relative; - display: inline-block; - width: 40px; - height: 20px; -} - -.switch input { - opacity: 0; - width: 0; - height: 0; -} - -.slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #ccc; - transition: .4s; - border-radius: 20px; -} - -.slider:before { - position: absolute; - content: ""; - height: 16px; - width: 16px; - left: 2px; - bottom: 2px; - background-color: white; - transition: .4s; - border-radius: 50%; -} - -input:checked + .slider { - background-color: #2196F3; -} - -input:checked + .slider:before { - transform: translateX(20px); -} \ No newline at end of file diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/default.png b/luci-app-oaf/htdocs/luci-static/resources/oaf/app_icons/default.png old mode 100755 new mode 100644 similarity index 100% rename from luci-app-oaf/htdocs/luci-static/resources/app_icons/default.png rename to luci-app-oaf/htdocs/luci-static/resources/oaf/app_icons/default.png diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/common.js b/luci-app-oaf/htdocs/luci-static/resources/oaf/common.js new file mode 100644 index 00000000..5b07e803 --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/common.js @@ -0,0 +1,207 @@ +function showCommonModal(message, duration = 2000, type = 'info') { + const existingModal = document.querySelector('.common-modal-mask'); + if (existingModal) { + existingModal.remove(); + } + + const modalMask = document.createElement('div'); + modalMask.className = 'common-modal-mask'; + + const modalContent = document.createElement('div'); + modalContent.className = 'common-modal-content'; + + modalContent.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; + modalContent.style.color = 'white'; + + modalContent.textContent = message; + + modalMask.appendChild(modalContent); + document.body.appendChild(modalMask); + + setTimeout(() => { + modalMask.classList.add('show'); + }, 10); + + setTimeout(() => { + modalMask.classList.remove('show'); + setTimeout(() => { + if (modalMask.parentNode) { + modalMask.remove(); + } + }, 300); + }, duration); +} + +function showSuccess(message, duration = 2000) { + showCommonModal(message, duration); +} + +function showWarning(message, duration = 2000) { + showCommonModal(message, duration); +} + +function showError(message, duration = 3000) { + showCommonModal(message, duration); +} + +function showInfo(message, duration = 2000) { + showCommonModal(message, duration); +} + +function showConfirmModal(message, onConfirm, onCancel, confirmText = '确定', cancelText = '取消') { + const existingModal = document.querySelector('.common-modal-mask'); + if (existingModal) { + existingModal.remove(); + } + + const modalMask = document.createElement('div'); + modalMask.className = 'common-modal-mask'; + + const modalContent = document.createElement('div'); + modalContent.className = 'common-modal-content'; + modalContent.style.width = '300px'; + modalContent.style.height = 'auto'; + modalContent.style.minHeight = '120px'; + modalContent.style.padding = '20px'; + modalContent.style.flexDirection = 'column'; + modalContent.style.justifyContent = 'space-between'; + modalContent.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; + modalContent.style.color = 'white'; + + const messageDiv = document.createElement('div'); + messageDiv.textContent = message; + messageDiv.style.marginBottom = '20px'; + messageDiv.style.textAlign = 'center'; + + const buttonContainer = document.createElement('div'); + buttonContainer.style.display = 'flex'; + buttonContainer.style.justifyContent = 'center'; + buttonContainer.style.gap = '10px'; + + const confirmBtn = document.createElement('button'); + confirmBtn.textContent = confirmText; + confirmBtn.style.padding = '8px 16px'; + confirmBtn.style.backgroundColor = '#2885e8'; + confirmBtn.style.color = 'white'; + confirmBtn.style.border = 'none'; + confirmBtn.style.borderRadius = '4px'; + confirmBtn.style.cursor = 'pointer'; + confirmBtn.onclick = () => { + modalMask.remove(); + if (onConfirm) onConfirm(); + }; + + const cancelBtn = document.createElement('button'); + cancelBtn.textContent = cancelText; + cancelBtn.style.padding = '8px 16px'; + cancelBtn.style.backgroundColor = '#666'; + cancelBtn.style.color = 'white'; + cancelBtn.style.border = 'none'; + cancelBtn.style.borderRadius = '4px'; + cancelBtn.style.cursor = 'pointer'; + cancelBtn.onclick = () => { + modalMask.remove(); + if (onCancel) onCancel(); + }; + + buttonContainer.appendChild(confirmBtn); + buttonContainer.appendChild(cancelBtn); + + modalContent.appendChild(messageDiv); + modalContent.appendChild(buttonContainer); + modalMask.appendChild(modalContent); + document.body.appendChild(modalMask); + + setTimeout(() => { + modalMask.classList.add('show'); + }, 10); +} + +function formatFileSize(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +function throttle(func, limit) { + let inThrottle; + return function() { + const args = arguments; + const context = this; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; +} + +function getUrlParameter(name) { + const urlParams = new URLSearchParams(window.location.search); + return urlParams.get(name); +} + +function setUrlParameter(name, value) { + const url = new URL(window.location); + url.searchParams.set(name, value); + window.history.replaceState({}, '', url); +} + +function deepClone(obj) { + if (obj === null || typeof obj !== 'object') return obj; + if (obj instanceof Date) return new Date(obj.getTime()); + if (obj instanceof Array) return obj.map(item => deepClone(item)); + if (typeof obj === 'object') { + const clonedObj = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + clonedObj[key] = deepClone(obj[key]); + } + } + return clonedObj; + } +} + +function validateIP(ip) { + if (!ip || ip.trim() === '') return true; + const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + return ipRegex.test(ip.trim()); +} + +function validateNetmask(mask) { + if (!mask || mask.trim() === '') return true; + const maskRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + if (!maskRegex.test(mask.trim())) return false; + const parts = mask.split('.'); + const binary = parts.map(p => parseInt(p).toString(2).padStart(8, '0')).join(''); + return /^1+0*$/.test(binary); +} + +window.showCommonModal = showCommonModal; +window.showSuccess = showSuccess; +window.showWarning = showWarning; +window.showError = showError; +window.showInfo = showInfo; +window.showConfirmModal = showConfirmModal; +window.formatFileSize = formatFileSize; +window.debounce = debounce; +window.throttle = throttle; +window.getUrlParameter = getUrlParameter; +window.setUrlParameter = setUrlParameter; +window.deepClone = deepClone; +window.validateIP = validateIP; +window.validateNetmask = validateNetmask; diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/css/common.css b/luci-app-oaf/htdocs/luci-static/resources/oaf/css/common.css new file mode 100644 index 00000000..c5b7421f --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/css/common.css @@ -0,0 +1,269 @@ + +body { + font-family: Arial, sans-serif; +} +h1, h2 { + color: #333; +} + + + +.button-check { + padding: 3px 8px; + background-color: transparent; + color: green; + border: 1px solid green; + border-radius: 3px; + cursor: pointer; + font-size: 12px; +} +.button-container { + display: flex; + justify-content: right; + margin-top: 5px; +} + +.submit-button { + margin-top: 30px; + width: 150px; + height: 30px; + background-color: #2885e8; + color: #fff; + border: none; + border-radius: 5px; + cursor: pointer; +} +.submit-button:hover { + background-color: #0f77e6; +} + +.switch { + position: relative; + display: inline-block; + width: 40px; + height: 20px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .4s; + border-radius: 20px; +} + +.slider:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 2px; + bottom: 2px; + background-color: #fff; + transition: .4s; + border-radius: 50%; +} + +input:checked + .slider { + background-color: #2196F3; +} + +input:checked + .slider:before { + left: 22px; +} + +.fwx-form-card { + max-width: 780px; + margin: 0 auto 16px auto; + padding: 16px; + border: 1px solid #e5e7eb; + border-radius: 8px; + background: transparent; + box-shadow: none; +} + +.form-title { + font-size: 18px; + font-weight: 600; + margin-bottom: 8px; + display: flex; + align-items: center; +} + +.form-desc { + color: #6b7280; + font-size: 13px; + margin-bottom: 12px; +} + +[data-darkmode="true"] .form-desc { + color:#c0c0c0; +} + +.oaf-check-item { + display: inline-flex; + align-items: center; + gap: 5px; + min-width: 0; + line-height: 1.4; + white-space: nowrap; +} + +.oaf-check-item.oaf-check-item > input[type="checkbox"] { + margin: 0; + flex: 0 0 auto; +} + +.oaf-check-item.oaf-check-item > label { + display: inline-flex; + align-items: center; + margin: 0; + min-width: 0; + line-height: 1.4; + cursor: pointer; + white-space: nowrap; +} + +.oaf-check-item.oaf-check-item > label span { + min-width: 0; + white-space: nowrap; +} + +.common-modal-mask { + display: none; + position: fixed; + top: 0; left: 0; + width: 100%; height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + justify-content: center; + align-items: center; +} +.common-modal-mask.show { + display: flex; +} +.common-modal-content { + background-color: rgba(0, 0, 0, 0.5); + padding: 10px; + border-radius: 5px; + text-align: center; + width: 100px; + height: 70px; + color: #fff; + display: flex; + justify-content: center; + align-items: center; +} + +/* Common table styles */ +.common-table { + width: 100%; + border-collapse: collapse; +} + +.common-table th, +.common-table td { + padding: 8px; + border: 1px solid; + text-align: left; +} + +.common-table th { + font-weight: bold; +} + +.table-header { + display: grid; + padding: 10px; + font-weight: bold; + border: 1px solid; + border-bottom: none; +} + +.table-row { + display: grid; + padding: 10px; + border: 1px solid; + border-top: none; + align-items: center; +} + + +/* Table title style */ +.table-title { + font-size: 18px; + font-weight: bold; + margin-bottom: 20px; +} + +/* Common button styles */ +.btn-delete { + padding: 3px 8px; + background-color: #ff4444; + color: #fff; + border: none; + border-radius: 3px; + cursor: pointer; + font-size: 13px; +} + +.btn-delete:hover { + background-color: #cc0000; +} + +.btn-edit { + padding: 3px 8px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 3px; + cursor: pointer; + margin-right: 5px; + font-size: 13px; +} + +.btn-edit:hover { + background-color: #0056b3; +} + +.btn-add { + padding: 5px 15px; + background-color: #2885e8; + color: #fff; + border: none; + border-radius: 3px; + cursor: pointer; + font-size: 14px; +} + +.btn-add:hover { + background-color: #1e6bb8; +} + + + +.form-row { display: flex; align-items: center; margin-bottom: 10px; gap: 10px; } +.form-label { width: 140px; font-weight: 500; color: inherit; } +.form-row input { width: 360px; max-width: 100%; padding: 6px 10px; border: 1px solid #d1d5db; border-radius: 4px; background: inherit; color: inherit; } +.form-row select { width: 360px; max-width: 100%; } +.form-text-row { margin-bottom: 10px; font-size: 13px; color: #6b7280; } +.input-short { width: 200px !important; max-width: 100%; } + + +.btn { padding: 2px 25px; border: 1px solid #10b981; background: #10b981; color: #fff; border-radius: 4px; cursor: pointer; font-weight: 600; } +.btn:disabled { opacity: .5; cursor: not-allowed; } +.btn-secondary { background: transparent; color: #10b981; padding: 2px 25px; } +.section-header { font-weight: 600; margin: 12px 0 6px; color: inherit; } +.row-inline { display: flex; gap: 10px; } +.row-inline .form-row { flex: 1; } +.status-tip { margin-top: 8px; font-size: 13px; color: #6b7280; } diff --git a/luci-app-oaf/htdocs/luci-static/resources/echarts.min.js b/luci-app-oaf/htdocs/luci-static/resources/oaf/echarts.min.js similarity index 100% rename from luci-app-oaf/htdocs/luci-static/resources/echarts.min.js rename to luci-app-oaf/htdocs/luci-static/resources/oaf/echarts.min.js diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/application.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/application.svg new file mode 100644 index 00000000..c4978870 --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/application.svg @@ -0,0 +1 @@ + diff --git a/luci-app-oaf/htdocs/luci-static/resources/icons/arrow.png b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/arrow.png similarity index 100% rename from luci-app-oaf/htdocs/luci-static/resources/icons/arrow.png rename to luci-app-oaf/htdocs/luci-static/resources/oaf/icons/arrow.png diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/ethernet-port.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/ethernet-port.svg new file mode 100644 index 00000000..09ddd431 --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/ethernet-port.svg @@ -0,0 +1,16 @@ + + Ethernet port icon + A minimal RJ45 Ethernet port icon with contact pins and a stepped connector shape. + + + + + + + + + + + + + diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/filter.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/filter.svg new file mode 100644 index 00000000..e613e458 --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/filter.svg @@ -0,0 +1 @@ + diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/flow.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/flow.svg new file mode 100644 index 00000000..17877d21 --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/flow.svg @@ -0,0 +1 @@ + diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/sphere.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/sphere.svg new file mode 100644 index 00000000..b89ed9d1 --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/sphere.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/user.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/user.svg new file mode 100644 index 00000000..73a01cfe --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/user.svg @@ -0,0 +1 @@ + diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/jquery-3.7.1.min.js b/luci-app-oaf/htdocs/luci-static/resources/oaf/jquery-3.7.1.min.js new file mode 100644 index 00000000..7f37b5d9 --- /dev/null +++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0') + .addClass(options.className || 'oaf-generated-app-icon') + .css({ + width: options.size || '20px', + height: options.size || '20px', + borderRadius: options.radius || '5px', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + background: hashColor(name), + color: '#fff', + fontSize: options.fontSize || '11px', + fontWeight: '700', + flexShrink: 0, + lineHeight: 1 + }) + .text(firstLetter(name)); + } + + function createAppIcon(appId, name, resourceBase, options) { + options = options || {}; + var appName = name || ''; + var id = appId === undefined || appId === null ? '' : String(appId).trim(); + var src = appIconSrc(resourceBase, id); + var iconDisabled = options.icon === 0 || options.icon === '0' || options.hasIcon === false; + var $icon; + + if (id && iconDisabled) { + iconStatusCache[id] = 'failed'; + } + + if (id && !iconDisabled && iconStatusCache[id] === 'loaded') { + return $('') + .attr({ src: src, alt: appName }) + .css({ + width: options.size || '20px', + height: options.size || '20px', + borderRadius: options.radius || '5px', + objectFit: options.objectFit || 'cover', + display: 'block', + flexShrink: 0 + }); + } + + $icon = createLetterIcon(appName, options); + if (id && !iconDisabled && iconStatusCache[id] !== 'failed') { + var loader = new Image(); + loader.onload = function() { + iconStatusCache[id] = 'loaded'; + $icon.replaceWith($('') + .attr({ src: src, alt: appName }) + .css({ + width: options.size || '20px', + height: options.size || '20px', + borderRadius: options.radius || '5px', + objectFit: options.objectFit || 'cover', + display: 'block', + flexShrink: 0, + border: 'none', + boxShadow: 'none' + })); + }; + loader.onerror = function() { + iconStatusCache[id] = 'failed'; + }; + loader.src = src; + } + + return $icon; + } + + window.OAFIcon = { + colors: iconColors, + hashColor: hashColor, + appIconSrc: appIconSrc, + createLetterIcon: createLetterIcon, + createAppIcon: createAppIcon + }; +})(window, window.jQuery); diff --git a/luci-app-oaf/luasrc/controller/appfilter.lua b/luci-app-oaf/luasrc/controller/appfilter.lua deleted file mode 100644 index c4a59062..00000000 --- a/luci-app-oaf/luasrc/controller/appfilter.lua +++ /dev/null @@ -1,573 +0,0 @@ -module("luci.controller.appfilter", package.seeall) -local utl = require "luci.util" - -function index() - if not nixio.fs.access("/etc/config/appfilter") then - return - end - - local page - entry({"admin", "services", "appfilter"}, alias("admin", "services", "appfilter", "user_list"),_("App Filter"), 10).dependent = true - - - entry({"admin", "services", "appfilter", "user_list"}, - arcombine(cbi("appfilter/user_list",{hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), - cbi("appfilter/dev_status", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true})), - _("User List"), 20).leaf=true - - entry({"admin", "services", "appfilter", "time"}, cbi("appfilter/time", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Time Configuration"), 25).leaf=true - entry({"admin", "services", "appfilter", "app_filter"}, cbi("appfilter/app_filter", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("App Filter"), 21).leaf=true - entry({"admin", "services", "appfilter", "feature"}, cbi("appfilter/feature", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("App Feature Library"), 26).leaf=true - - entry({"admin", "services", "appfilter", "user"}, cbi("appfilter/user", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("User Configuration"), 24).leaf=true - entry({"admin", "services", "appfilter", "advance"}, cbi("appfilter/advance", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Advanced Settings"), 27).leaf=true - entry({"admin", "network", "user_status"}, call("user_status"), nil).leaf = true - entry({"admin", "network", "get_user_list"}, call("get_user_list"), nil).leaf = true - entry({"admin", "network", "dev_visit_list"}, call("get_dev_visit_list"), nil).leaf = true - entry({"admin", "network", "feature_upgrade"}, call("handle_feature_upgrade"), nil).leaf = true - entry({"admin", "network", "dev_visit_time"}, call("get_dev_visit_time"), nil).leaf = true - entry({"admin", "network", "app_class_visit_time"}, call("get_app_class_visit_time"), nil).leaf = true - entry({"admin", "network", "class_list"}, call("get_class_list"), nil).leaf = true - entry({"admin", "network", "set_app_filter"}, call("set_app_filter"), nil).leaf = true - entry({"admin", "network", "get_app_filter"}, call("get_app_filter"), nil).leaf = true - entry({"admin", "network", "get_app_filter_base"}, call("get_app_filter_base"), nil).leaf = true - entry({"admin", "network", "set_app_filter_base"}, call("set_app_filter_base"), nil).leaf = true - entry({"admin", "network", "set_app_filter_time"}, call("set_app_filter_time"), nil).leaf = true - entry({"admin", "network", "get_app_filter_time"}, call("get_app_filter_time"), nil).leaf = true - entry({"admin", "network", "get_all_users"}, call("get_all_users"), nil).leaf = true - entry({"admin", "network", "get_app_filter_user"}, call("get_app_filter_user"), nil).leaf = true - entry({"admin", "network", "set_app_filter_user"}, call("set_app_filter_user"), nil).leaf = true - entry({"admin", "network", "del_app_filter_user"}, call("del_app_filter_user"), nil).leaf = true - entry({"admin", "network", "add_app_filter_user"}, call("add_app_filter_user"), nil).leaf = true - entry({"admin", "network", "get_whitelist_user"}, call("get_whitelist_user"), nil).leaf = true - entry({"admin", "network", "add_whitelist_user"}, call("add_whitelist_user"), nil).leaf = true - entry({"admin", "network", "del_whitelist_user"}, call("del_whitelist_user"), nil).leaf = true - entry({"admin", "network", "upload_file"}, call("handle_file_upload"), nil).leaf = true - entry({"admin", "network", "set_nickname"}, call("set_nickname"), nil).leaf = true - entry({"admin", "network", "get_oaf_status"}, call("get_oaf_status"), nil).leaf = true - entry({"admin", "network", "get_app_filter_adv"}, call("get_app_filter_adv"), nil).leaf = true - entry({"admin", "network", "set_app_filter_adv"}, call("set_app_filter_adv"), nil).leaf = true - entry({"admin", "network", "disable_flow_offloading"}, call("disable_flow_offloading"), nil).leaf = true - entry({"admin", "network", "cmd"}, call("handle_cmd"), nil).leaf = true - - - entry({"admin", "appfilter", "feature", "info"}, call("get_feature_info"), nil).leaf = true - entry({"admin", "appfilter", "feature", "class_list"}, call("get_feature_class_list"), nil).leaf = true - entry({"admin", "appfilter", "feature", "upgrade_status"}, call("get_feature_upgrade_status"), nil).leaf = true - - - - -end - -function get_hostname_by_mac(dst_mac) - leasefile="/tmp/dhcp.leases" - local fd = io.open(leasefile, "r") - if not fd then return end - while true do - local ln = fd:read("*l") - if not ln then - break - end - local ts, mac, ip, name, duid = ln:match("^(%d+) (%S+) (%S+) (%S+) (%S+)") - if dst_mac == mac then - fd:close() - return name - end - end - fd:close() - return "" -end - - -function handle_feature_upgrade() - local fs = require "nixio.fs" - local http = require "luci.http" - local image_tmp = "/tmp/feature.cfg" - - local fp - http.setfilehandler( - function(meta, chunk, eof) - - fp = io.open(image_tmp, "w") - - if fp and chunk then - fp:write(chunk) - end - if fp and eof then - fp:close() - end - end - ) - - -end - -function get_app_name_by_id(appid) - local class_fd = io.popen("find /tmp/appfilter/ -type f -name *.class |xargs cat |grep "..appid.."|awk '{print $2}'") - if class_fd then - local name = class_fd:read("*l") - class_fd:close() - return name - end - return "" -end - -function cmp_func(a,b) - return a.latest_time > b.latest_time -end - - -function user_status() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local fd = io.open("/proc/net/af_client","r") - status_buf=fd:read('*a') - fd:close() - user_array=json.parse(status_buf) - - local visit_obj=utl.ubus("appfilter", "visit_list", {}); - local user_array=visit_obj.dev_list - local history={} - for i, v in pairs(user_array) do - visit_array=user_array[i].visit_info - for j,s in pairs(visit_array) do - print(user_array[i].mac, user_array[i].ip,visit_array[j].appid, visit_array[j].latest_time) - total_time=visit_array[j].latest_time - visit_array[j].first_time; - history[#history+1]={ - mac=user_array[i].mac, - ip=user_array[i].ip, - hostname=get_hostname_by_mac(user_array[i].mac), - appid=visit_array[j].appid, - appname=get_app_name_by_id(visit_array[j].appid), - total_num=0, - drop_num=0, - latest_action=visit_array[j].latest_action, - latest_time=os.date("%Y/%m/%d %H:%M:%S", visit_array[j].latest_time), - first_time=os.date("%Y/%m/%d %H:%M:%S", visit_array[j].first_time), - total_time=total_time - } - end - end - table.sort(history, cmp_func) - luci.http.write_json(history); -end - - -function get_user_list() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local visit_obj=utl.ubus("appfilter", "dev_list", {}); - luci.http.write_json(visit_obj); -end - -function get_class_list() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local class_obj=utl.ubus("appfilter", "class_list", {}); - llog("get class list"); - luci.http.write_json(class_obj); -end - -function get_all_users() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local flag = luci.http.formvalue("flag") - local page = luci.http.formvalue("page") - local page_size = luci.http.formvalue("page_size") - local params = {flag=flag, page=page} - if page_size then - params.page_size = page_size - end - local class_obj=utl.ubus("appfilter", "get_all_users", params); - luci.http.write_json(class_obj); -end - -function get_oaf_status() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local resp_obj=utl.ubus("appfilter", "get_oaf_status", {}); - luci.http.write_json(resp_obj); -end - -function get_app_filter_user() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local resp_obj=utl.ubus("appfilter", "get_app_filter_user", {}); - luci.http.write_json(resp_obj); -end - -function del_app_filter_user() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj.mac = luci.http.formvalue("mac") - llog("del appfilter user "..req_obj.mac); - local resp_obj=utl.ubus("appfilter", "del_app_filter_user", req_obj); - luci.http.write_json(resp_obj); -end - -function add_app_filter_user() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - local data_str = luci.http.formvalue("data") - req_obj = json.parse(data_str) - - local resp_obj=utl.ubus("appfilter", "add_app_filter_user", req_obj); - luci.http.write_json(resp_obj); -end - -function get_whitelist_user() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local resp_obj=utl.ubus("appfilter", "get_whitelist_user", {}); - luci.http.write_json(resp_obj); -end - -function add_whitelist_user() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - local data_str = luci.http.formvalue("data") - req_obj = json.parse(data_str) - - local resp_obj=utl.ubus("appfilter", "add_whitelist_user", req_obj); - luci.http.write_json(resp_obj); -end - -function del_whitelist_user() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj.mac = luci.http.formvalue("mac") - llog("del whitelist user "..req_obj.mac); - local resp_obj=utl.ubus("appfilter", "del_whitelist_user", req_obj); - luci.http.write_json(resp_obj); -end - -function get_app_filter() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local resp_obj=utl.ubus("appfilter", "get_app_filter", {}); - luci.http.write_json(resp_obj); -end - -function set_app_filter() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - - local app_list_str = luci.http.formvalue("app_list") - - local app_list = {} - for id in app_list_str:gmatch("([^,]+)") do - table.insert(app_list, tonumber(id)) - end - - local req_obj = { - app_list = app_list - } - - local resp_obj = utl.ubus("appfilter", "set_app_filter", req_obj) - luci.http.write_json(resp_obj) -end - -function set_nickname() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj.mac = luci.http.formvalue("mac") - req_obj.nickname = luci.http.formvalue("nickname") - llog("set nickname "..req_obj.mac.." "..req_obj.nickname); - local resp_obj=utl.ubus("appfilter", "set_nickname", req_obj); - luci.http.write_json(resp_obj); -end - -function get_app_filter_base() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local resp_obj=utl.ubus("appfilter", "get_app_filter_base", {}); - luci.http.write_json(resp_obj); -end - -function set_app_filter_user() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj.mode = luci.http.formvalue("mode") - local resp_obj=utl.ubus("appfilter", "set_app_filter_user", req_obj); - luci.http.write_json(resp_obj); -end - -function set_app_filter_base() - local json = require "luci.jsonc" - llog("set appfilter base"); - luci.http.prepare_content("application/json") - local req_obj = {} - - - local enable = luci.http.formvalue("enable") - local work_mode = luci.http.formvalue("work_mode") - local record_enable = luci.http.formvalue("record_enable") - local disable_quic = luci.http.formvalue("disable_quic") - local app_filter_mode = luci.http.formvalue("app_filter_mode") - - llog("enable: "..enable.." work_mode: "..work_mode.." record_enable: "..record_enable.." disable_quic: "..(disable_quic or "nil").." app_filter_mode: "..(app_filter_mode or "nil")) - req_obj.enable = enable - req_obj.work_mode = work_mode - req_obj.record_enable = record_enable - if disable_quic then - req_obj.disable_quic = disable_quic - end - if app_filter_mode then - req_obj.app_filter_mode = app_filter_mode - end - - local resp_obj=utl.ubus("appfilter", "set_app_filter_base", req_obj); - luci.http.write_json(resp_obj); -end - -function get_app_filter_adv() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local resp_obj=utl.ubus("appfilter", "get_app_filter_adv", {}); - luci.http.write_json(resp_obj); -end -function set_app_filter_adv() - local json = require "luci.jsonc" - llog("set appfilter base"); - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj.lan_ifname = luci.http.formvalue("lan_ifname") - req_obj.disable_hnat = luci.http.formvalue("disable_hnat") - req_obj.auto_load_engine = luci.http.formvalue("auto_load_engine") - local resp_obj=utl.ubus("appfilter", "set_app_filter_adv", req_obj); - luci.http.write_json(resp_obj); -end - -function disable_flow_offloading() - local json = require "luci.jsonc" - llog("disable flow offloading"); - luci.http.prepare_content("application/json") - local resp_obj=utl.ubus("appfilter", "disable_flow_offloading", {}); - luci.http.write_json(resp_obj); -end - -function handle_cmd() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local action = luci.http.formvalue("action") - if not action then - luci.http.write_json({code = -1, message = "action parameter is required"}); - return - end - local req_obj = {} - req_obj.action = action - local resp_obj=utl.ubus("appfilter", "cmd", req_obj); - luci.http.write_json(resp_obj); -end - --- data: {"mode":1,"weekday_list":[1,2,3,4,5,6,0],"start_time":"22:22","end_time":"12:00","allow_time":30,"deny_time":5} -function set_app_filter_time() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj = json.parse(luci.http.formvalue("data")) - local resp_obj=utl.ubus("appfilter", "set_app_filter_time", req_obj); - luci.http.write_json(resp_obj); -end - -function get_app_filter_time() - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local resp_obj=utl.ubus("appfilter", "get_app_filter_time", {}); - llog("controller get_app_filter_time: ubus response received"); - if resp_obj and resp_obj.data then - llog("controller get_app_filter_time: mode=" .. tostring(resp_obj.data.mode or "nil")); - if resp_obj.data.time_list then - llog("controller get_app_filter_time: time_list length=" .. tostring(#resp_obj.data.time_list)); - for i, time_item in ipairs(resp_obj.data.time_list) do - local weekday_str = "nil" - if time_item.weekday_list then - weekday_str = table.concat(time_item.weekday_list, ",") - end - - end - else - llog("controller get_app_filter_time: time_list is nil"); - end - local response_json = json.stringify(resp_obj); - if response_json then - llog("controller get_app_filter_time: final JSON response length=" .. tostring(string.len(response_json))); - else - llog("controller get_app_filter_time: failed to encode JSON"); - end - else - llog("controller get_app_filter_time: resp_obj or resp_obj.data is nil"); - end - luci.http.write_json(resp_obj); -end - -function get_dev_visit_time(mac) - - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj.mac = mac; - local visit_obj=utl.ubus("appfilter", "dev_visit_time", req_obj); - - local visit_list=visit_obj.list - luci.http.write_json(visit_list); -end - -function get_app_class_visit_time(mac) - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj.mac = mac; - local visit_obj=utl.ubus("appfilter", "app_class_visit_time", req_obj); - local class_array=visit_obj.class_list - luci.http.write_json(class_array); -end - - -function get_dev_visit_list(mac) - local json = require "luci.jsonc" - luci.http.prepare_content("application/json") - local req_obj = {} - req_obj.mac = mac; - local page = luci.http.formvalue("page") - local page_size = luci.http.formvalue("page_size") - if page then - req_obj.page = page - end - if page_size then - req_obj.page_size = page_size - end - local resp_obj=utl.ubus("appfilter", "dev_visit_list", req_obj); - luci.http.write_json(resp_obj); -end - -function handle_file_upload() - local http = require "luci.http" - local fs = require "nixio.fs" - local upload_dir = "/tmp/uploads/" - local file_name = "uploaded_file" - llog("handle_file_upload started"); - - -- Ensure the upload directory exists - if not fs.access(upload_dir) then - fs.mkdir(upload_dir) - end - - llog("Upload directory checked/created"); - - local file_path = upload_dir .. file_name - local fp - - llog("file_path: " .. file_path); - http.setfilehandler( - function(meta, chunk, eof) - -- Log metadata information - llog("File upload metadata: " .. (meta and meta.name or "nil") .. ", " .. (meta and meta.file or "nil")) - llog("File upload chunk size: " .. (chunk and #chunk or 0)) - - if not fp then - fp = io.open(file_path, "w") - llog("File opened for writing: " .. file_path) - end - if fp and chunk then - fp:write(chunk) - llog("Chunk written to file") - end - if fp and eof then - fp:close() - llog("File upload completed and file closed") - -- Ensure the file is processed or moved to the correct location - process_uploaded_file(file_path) - luci.http.prepare_content("application/json") - luci.http.write_json({ success = true, message = "File uploaded successfully" }) - end - end - ) - llog("handle_file_upload setup complete"); -end - -function process_uploaded_file(file_path) - -- Add logic here to process the uploaded file - llog("Processing uploaded file: " .. file_path) - -- Example: Move the file to a permanent location - local permanent_path = "/etc/config/" .. file_name - os.execute("mv " .. file_path .. " " .. permanent_path) - llog("File moved to: " .. permanent_path) -end - -function llog(message) - local log_file = "/tmp/log/oaf_luci.log" - local fd = io.open(log_file, "a") - if fd then - local timestamp = os.date("%Y-%m-%d %H:%M:%S") - fd:write(string.format("[%s] %s\n", timestamp, message)) - fd:close() - end -end - - - -function get_feature_upgrade_status() - local fs = require "nixio.fs" - local json = require "luci.jsonc" - local http = require "luci.http" - - local status_file = "/tmp/feature_upgrade.status" - local status = 0 - - if fs.access(status_file) then - local content = fs.readfile(status_file) or "" - status = tonumber(content:match("(%d+)")) or 0 - fs.writefile(status_file, "0") - end - - http.prepare_content("application/json") - http.write(json.stringify({code = 0, status = status})) -end - - - -function get_feature_info() - local json = require "luci.jsonc" - local nfs = require "nixio.fs" - local sys = require "luci.sys" - luci.http.prepare_content("application/json") - - local info = { - version = "", - format = "v3.0", - app_count = 0 - } - - if nfs.access("/tmp/feature.cfg") then - info.app_count = tonumber(sys.exec("cat /tmp/feature.cfg | grep -v ^$ |grep -v ^# | wc -l")) or 0 - info.version = sys.exec("cat /tmp/feature.cfg |grep \"#version\" | awk '{print $2}'") or "" - end - - luci.http.write(json.stringify({code = 0, data = info, message = "success"})) -end - -function get_feature_class_list() - local json = require "luci.jsonc" - local utl = require "luci.util" - luci.http.prepare_content("application/json") - - local req_obj = {} - req_obj.api = "class_list" - req_obj.data = {} - - local resp_obj = utl.ubus("fwx", "common", req_obj) - - if resp_obj and resp_obj.code == 2000 and resp_obj.data then - luci.http.write(json.stringify(resp_obj.data)) - else - luci.http.write(json.stringify({class_list = {}})) - end -end \ No newline at end of file diff --git a/luci-app-oaf/luasrc/controller/oaf.lua b/luci-app-oaf/luasrc/controller/oaf.lua new file mode 100644 index 00000000..b071cd7a --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf.lua @@ -0,0 +1,5 @@ +module("luci.controller.oaf", package.seeall) + +function index() + entry({"admin", "services", "oaf"}, firstchild(), _("Parental Control"), 20).dependent = true +end diff --git a/luci-app-oaf/luasrc/controller/oaf_about.lua b/luci-app-oaf/luasrc/controller/oaf_about.lua new file mode 100644 index 00000000..9921a96c --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_about.lua @@ -0,0 +1,5 @@ +module("luci.controller.oaf_about", package.seeall) + +function index() + entry({"admin", "services", "oaf", "about"}, template("oaf/about"), _("About"), 100).leaf = true +end diff --git a/luci-app-oaf/luasrc/controller/oaf_advanced.lua b/luci-app-oaf/luasrc/controller/oaf_advanced.lua new file mode 100644 index 00000000..e96585cf --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_advanced.lua @@ -0,0 +1,127 @@ +module("luci.controller.oaf_advanced", package.seeall) + +local util = require "luci.util" + +function index() + entry({"admin", "services", "oaf", "advanced"}, template("oaf/advanced"), _("Settings"), 90).leaf = true + entry({"admin", "services", "oaf", "api", "system", "get_system_info"}, call("get_system_info"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "system", "set_system_info"}, call("set_system_info"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "system", "get_work_mode"}, call("get_work_mode"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "system", "set_work_mode"}, call("set_work_mode"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "system", "get_tcp_rst"}, call("get_tcp_rst"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "system", "set_tcp_rst"}, call("set_tcp_rst"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "system", "get_advanced_settings"}, call("get_advanced_settings"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "system", "set_advanced_settings"}, call("set_advanced_settings"), nil).leaf = true +end + +local function ubus_call(api, payload) + payload = payload or {} + payload.api = api + return util.ubus("fwx", "common", payload) or {code = 1} +end + +function get_system_info() + local http = require "luci.http" + local resp = ubus_call("get_system_info", {data = {}}) + + http.prepare_content("application/json") + http.write_json(resp) +end + +function set_system_info() + local http = require "luci.http" + local lan_ifname = http.formvalue("lan_ifname") or "" + local theme_mode = tonumber(http.formvalue("theme_mode") or "0") or 0 + + if theme_mode ~= 0 and theme_mode ~= 1 then + theme_mode = 0 + end + + local resp = ubus_call("set_system_info", { + data = { + fwx = { + lan_ifname = lan_ifname, + theme_mode = theme_mode + } + } + }) + + http.prepare_content("application/json") + http.write_json(resp) +end + +function get_work_mode() + local http = require "luci.http" + local resp = ubus_call("get_work_mode", {data = {}}) + + http.prepare_content("application/json") + http.write_json(resp) +end + +function set_work_mode() + local http = require "luci.http" + local work_mode = tonumber(http.formvalue("work_mode") or "0") or 0 + + if work_mode ~= 0 and work_mode ~= 1 then + http.prepare_content("application/json") + http.write_json({code = 1}) + return + end + + local resp = ubus_call("set_work_mode", { + data = { + work_mode = work_mode + } + }) + + http.prepare_content("application/json") + http.write_json(resp) +end + +function get_tcp_rst() + local http = require "luci.http" + local resp = ubus_call("get_tcp_rst", {data = {}}) + + http.prepare_content("application/json") + http.write_json(resp) +end + +function set_tcp_rst() + local http = require "luci.http" + local tcp_rst = tonumber(http.formvalue("tcp_rst") or "1") or 1 + + tcp_rst = tcp_rst == 0 and 0 or 1 + + local resp = ubus_call("set_tcp_rst", { + data = { + tcp_rst = tcp_rst + } + }) + + http.prepare_content("application/json") + http.write_json(resp) +end + +function get_advanced_settings() + local http = require "luci.http" + local resp = ubus_call("get_advanced_settings", {data = {}}) + + http.prepare_content("application/json") + http.write_json(resp) +end + +function set_advanced_settings() + local http = require "luci.http" + local disable_hnat = tonumber(http.formvalue("disable_hnat") or "0") or 0 + + disable_hnat = disable_hnat == 1 and 1 or 0 + + local resp = ubus_call("set_advanced_settings", { + data = { + disable_hnat = disable_hnat + } + }) + + http.prepare_content("application/json") + http.write_json(resp) +end diff --git a/luci-app-oaf/luasrc/controller/oaf_app_filter.lua b/luci-app-oaf/luasrc/controller/oaf_app_filter.lua new file mode 100644 index 00000000..9cff55c4 --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_app_filter.lua @@ -0,0 +1,371 @@ +module("luci.controller.oaf_app_filter", package.seeall) +local utl = require "luci.util" +local nixio = require "nixio" + +function index() + if not nixio.fs.access("/etc/config/appfilter") then + return + end + entry({"admin", "services", "oaf", "app_filter"}, alias("admin", "services", "oaf", "app_filter", "rules"), _("App Filter"), 30).dependent = true + entry({"admin", "services", "oaf", "app_filter", "rules"}, cbi("oaf/app_filter/rules", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Filter Rules"), 10).leaf=true + entry({"admin", "services", "oaf", "api", "app_filter", "class_list"}, call("get_class_list"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "get_all_users"}, call("get_all_users"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "get_filter_rules"}, call("get_filter_rules"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "add_filter_rule"}, call("add_filter_rule"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "update_filter_rule"}, call("update_filter_rule"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "delete_filter_rule"}, call("delete_filter_rule"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "get_appfilter_whitelist"}, call("get_appfilter_whitelist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "add_appfilter_whitelist"}, call("add_appfilter_whitelist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "del_appfilter_whitelist"}, call("del_appfilter_whitelist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "get_app_filter_adv"}, call("get_app_filter_adv"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_filter", "set_app_filter_adv"}, call("set_app_filter_adv"), nil).leaf = true +end + + +function get_class_list() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.CopyRight = "www.fanchmwrt.com" + req_obj.api = "class_list" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + llog("get class list"); + luci.http.write_json(resp_obj.data) + else + llog("get class list failed"); + luci.http.write_json({class_list = {}}) + end +end + +function get_all_users() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_all_users" + req_obj.data = { + flag = luci.http.formvalue("flag"), + page = luci.http.formvalue("page") + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json({data = resp_obj.data}) + else + luci.http.write_json({data = resp_obj or {}}) + end +end + + + +function handle_file_upload() + local http = require "luci.http" + local fs = require "nixio.fs" + local upload_dir = "/tmp/uploads/" + local file_name = "uploaded_file" + llog("handle_file_upload started"); + + if not fs.access(upload_dir) then + fs.mkdir(upload_dir) + end + + llog("Upload directory checked/created"); + + local file_path = upload_dir .. file_name + local fp + + llog("file_path: " .. file_path); + http.setfilehandler( + function(meta, chunk, eof) + llog("File upload metadata: " .. (meta and meta.name or "nil") .. ", " .. (meta and meta.file or "nil")) + llog("File upload chunk size: " .. (chunk and #chunk or 0)) + + if not fp then + fp = io.open(file_path, "w") + llog("File opened for writing: " .. file_path) + end + if fp and chunk then + fp:write(chunk) + llog("Chunk written to file") + end + if fp and eof then + fp:close() + llog("File upload completed and file closed") + process_uploaded_file(file_path) + luci.http.prepare_content("application/json") + luci.http.write_json({ success = true, message = "File uploaded successfully" }) + end + end + ) + llog("handle_file_upload setup complete"); +end + +function process_uploaded_file(file_path) + llog("Processing uploaded file: " .. file_path) + local permanent_path = "/etc/config/" .. file_name + os.execute("mv " .. file_path .. " " .. permanent_path) + llog("File moved to: " .. permanent_path) +end + +function llog(message) + local log_file = "/tmp/log/oaf_luci.log" + local fd = io.open(log_file, "a") + if fd then + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + fd:write(string.format("[%s] %s\n", timestamp, message)) + fd:close() + end +end + +function get_filter_rules() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_filter_rules" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data and resp_obj.data.list then + local rules_data = resp_obj.data.list + llog("get_filter_rules: returning " .. #rules_data .. " rules") + local json_str = json.stringify({code = 0, data = rules_data, message = "success"}) + luci.http.write(json_str) + else + llog("get_filter_rules: failed, returning empty array") + local json_str = json.stringify({code = 0, data = {}, message = "success"}) + luci.http.write(json_str) + end +end + + +function add_filter_rule() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local data_str = luci.http.formvalue("data") + if not data_str then + luci.http.write_json({code = 1, message = "Invalid request data"}) + return + end + + local rule_data = json.parse(data_str) + llog("add_filter_rule: " .. json.stringify(rule_data)) + + + if not rule_data.name or not rule_data.mode or not rule_data.time_rules or not rule_data.app_ids then + luci.http.write_json({code = 1, message = "Missing required fields"}) + return + end + + local get_req_obj = {} + get_req_obj.api = "get_filter_rules" + get_req_obj.data = {} + local get_resp_obj = utl.ubus("fwx", "common", get_req_obj) + + if get_resp_obj and get_resp_obj.code == 2000 and get_resp_obj.data and get_resp_obj.data.data then + local existing_rules = get_resp_obj.data.data + if #existing_rules >= 32 then + luci.http.write_json({code = 1, message = "Maximum 32 rules allowed"}) + return + end + end + + local req_obj = {} + req_obj.api = "add_filter_rule" + req_obj.data = rule_data + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 0, message = "Rule added successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to add rule"}) + end +end + +function update_filter_rule() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local data_str = luci.http.formvalue("data") + if not data_str then + luci.http.write_json({code = 1, message = "Invalid request data"}) + return + end + + local rule_data = json.parse(data_str) + llog("update_filter_rule: " .. json.stringify(rule_data)) + + if not rule_data.id then + luci.http.write_json({code = 1, message = "Missing rule id"}) + return + end + + local req_obj = {} + req_obj.api = "update_filter_rule" + req_obj.data = rule_data + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 0, message = "Rule updated successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to update rule"}) + end +end + +function delete_filter_rule() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local rule_id = luci.http.formvalue("rule_id") + if not rule_id then + luci.http.write_json({code = 1, message = "Invalid rule_id"}) + return + end + + llog("delete_filter_rule: " .. rule_id) + + local req_obj = {} + req_obj.api = "delete_filter_rule" + req_obj.data = { + id = tonumber(rule_id) + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 0, message = "Rule deleted successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to delete rule"}) + end +end + +function get_appfilter_whitelist() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_appfilter_whitelist" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json({code = 2000, data = resp_obj.data, message = "success"}) + else + luci.http.write_json({code = 2000, data = {list = {}}, message = "success"}) + end +end + +function add_appfilter_whitelist() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local data_str = luci.http.formvalue("data") + if not data_str then + luci.http.write_json({code = 1, message = "Invalid request data"}) + return + end + + local whitelist_data = json.parse(data_str) + llog("add_appfilter_whitelist: " .. json.stringify(whitelist_data)) + + if not whitelist_data.mac_list or type(whitelist_data.mac_list) ~= "table" then + luci.http.write_json({code = 1, message = "Invalid mac_list"}) + return + end + + local req_obj = {} + req_obj.api = "add_appfilter_whitelist" + req_obj.data = whitelist_data + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 2000, message = "Whitelist added successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to add whitelist"}) + end +end + +function del_appfilter_whitelist() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local mac = luci.http.formvalue("mac") + if not mac then + luci.http.write_json({code = 1, message = "Invalid mac address"}) + return + end + + llog("del_appfilter_whitelist: " .. mac) + + local req_obj = {} + req_obj.api = "del_appfilter_whitelist" + req_obj.data = { + mac = mac + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 2000, message = "Whitelist deleted successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to delete whitelist"}) + end +end + +function get_app_filter_adv() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_app_filter_adv" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json({code = 0, data = resp_obj.data, message = "success"}) + end +end + +function set_app_filter_adv() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local enable = tonumber(luci.http.formvalue("enable")) or 1 + + local req_obj = {} + req_obj.api = "set_app_filter_adv" + req_obj.data = { + enable = enable, + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 0, message = "Saved successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to save"}) + end +end diff --git a/luci-app-oaf/luasrc/controller/oaf_app_record.lua b/luci-app-oaf/luasrc/controller/oaf_app_record.lua new file mode 100644 index 00000000..e1fe0169 --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_app_record.lua @@ -0,0 +1,83 @@ +module("luci.controller.oaf_app_record", package.seeall) + +local function normalize_page(value, default_value) + local n = tonumber(value or "") or default_value + if n < 1 then + n = default_value + end + return n +end + +local function write_empty_list(page, page_size) + luci.http.write_json({ + total_num = 0, + total_page = 1, + page = page, + page_size = page_size, + list = {} + }) +end + +function index() + entry({"admin", "services", "oaf", "app_record"}, template("oaf/app_record"), _("App Record"), 70).leaf = true + entry({"admin", "services", "oaf", "api", "app_record", "get_active_app_records"}, call("get_active_app_records")).leaf = true + entry({"admin", "services", "oaf", "api", "app_record", "get_app_history_records"}, call("get_app_history_records")).leaf = true +end + +function get_active_app_records() + local util = require "luci.util" + local page = normalize_page(luci.http.formvalue("page"), 1) + local page_size = normalize_page(luci.http.formvalue("page_size"), 15) + + luci.http.prepare_content("application/json") + + local req_obj = { + api = "get_active_app_records", + data = { + page = page, + page_size = page_size + } + } + + local resp_obj = util.ubus("fwx", "common", req_obj) + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + write_empty_list(page, page_size) + end +end + +function get_app_history_records() + local util = require "luci.util" + local mac = luci.http.formvalue("mac") + local start_time = tonumber(luci.http.formvalue("start_time") or "0") or 0 + local end_time = tonumber(luci.http.formvalue("end_time") or "0") or 0 + local appid = tonumber(luci.http.formvalue("appid") or "0") or 0 + local page = normalize_page(luci.http.formvalue("page"), 1) + local page_size = normalize_page(luci.http.formvalue("page_size"), 15) + + if appid < 0 then + appid = 0 + end + + luci.http.prepare_content("application/json") + + local req_obj = { + api = "get_app_history_records", + data = { + mac = mac, + start_time = start_time, + end_time = end_time, + appid = appid, + page = page, + page_size = page_size + } + } + + local resp_obj = util.ubus("fwx", "common", req_obj) + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + write_empty_list(page, page_size) + end +end diff --git a/luci-app-oaf/luasrc/controller/oaf_dashboard.lua b/luci-app-oaf/luasrc/controller/oaf_dashboard.lua new file mode 100644 index 00000000..0cf7f4de --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_dashboard.lua @@ -0,0 +1,229 @@ +module("luci.controller.oaf_dashboard", package.seeall) + +function index() + entry({"admin", "services", "oaf", "dashboard"}, cbi("oaf/dashboard", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), + _("Dashboard"), 10).leaf = true + + entry({"admin", "services", "oaf", "api", "dashboard", "get_dashboard_common"}, call("get_dashboard_common")).leaf = true + entry({"admin", "services", "oaf", "api", "dashboard", "get_init_status"}, call("get_init_status")).leaf = true + entry({"admin", "services", "oaf", "api", "dashboard", "set_init_status"}, call("set_init_status")).leaf = true + entry({"admin", "services", "oaf", "api", "dashboard", "set_notice_status"}, call("set_notice_status")).leaf = true + entry({"admin", "services", "oaf", "api", "dashboard", "get_daily_top_users"}, call("get_daily_top_users")).leaf = true + entry({"admin", "services", "oaf", "api", "dashboard", "get_history_traffic_stats"}, call("get_history_traffic_stats")).leaf = true + entry({"admin", "services", "oaf", "api", "dashboard", "get_global_traffic_stats"}, call("get_global_traffic_stats")).leaf = true + entry({"admin", "services", "oaf", "api", "dashboard", "get_active_users"}, call("get_active_users")).leaf = true + entry({"admin", "services", "oaf", "api", "dashboard", "get_app_type_stats"}, call("get_app_type_stats")).leaf = true +end + +function get_dashboard_common() + local json = require "luci.jsonc" + local utl = require "luci.util" + + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_dashboard_common" + req_obj.CopyRight = "www.fanchmwrt.com" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + system_status = {}, + network_status = {}, + active_app = {total = 0, list = {}}, + advanced = {disable_hnat = 0, notice_status = 0}, + interface_traffic = {interface = "wan", traffic = {}} + }) + end +end + +function get_global_traffic_stats() + local utl = require "luci.util" + + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_global_traffic_stats" + req_obj.data = {} + + local date = luci.http.formvalue("date") + if date then + req_obj.data.date = tonumber(date) or 0 + end + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + date = 0, + is_today = 1, + hourly_traffic = {} + }) + end +end + +function get_history_traffic_stats() + local utl = require "luci.util" + + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_history_traffic_stats" + req_obj.data = {} + + local days = luci.http.formvalue("days") + if days then + req_obj.data.days = tonumber(days) or 30 + else + req_obj.data.days = 30 + end + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + days = req_obj.data.days, + total_up_bytes = 0, + total_down_bytes = 0, + total_bytes = 0, + list = {} + }) + end +end + +function get_init_status() + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_init_status" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({init_status = 1}) + end +end + +function set_init_status() + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "set_init_status" + req_obj.data = { init_status = 1 } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({init_status = 0}) + end +end + +function set_notice_status() + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "set_dashboard_notice_status" + req_obj.data = { notice_status = 1 } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 2000, notice_status = 1}) + else + luci.http.write_json({code = 1}) + end +end + +function get_daily_top_users() + local json = require "luci.jsonc" + local utl = require "luci.util" + + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_daily_top_users" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + date = 0, + total_count = 0, + users = {} + }) + end +end + +function get_active_users() + local json = require "luci.jsonc" + local utl = require "luci.util" + + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_active_users" + req_obj.data = {} + + local count = luci.http.formvalue("count") + if count then + req_obj.data.count = tonumber(count) or 8 + else + req_obj.data.count = 8 + end + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + total_count = 0, + users = {} + }) + end +end + +function get_app_type_stats() + local json = require "luci.jsonc" + local utl = require "luci.util" + + luci.http.prepare_content("application/json") + + local stat_type = luci.http.formvalue("type") or "hourly" + + local req_obj = {} + req_obj.api = "get_global_app_type_stats" + req_obj.data = { + type = stat_type, + limit = 10 + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + type = stat_type, + limit = 10, + total_count = 0, + types = {} + }) + end +end diff --git a/luci-app-oaf/luasrc/controller/oaf_feature.lua b/luci-app-oaf/luasrc/controller/oaf_feature.lua new file mode 100644 index 00000000..593644ea --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_feature.lua @@ -0,0 +1,118 @@ +module("luci.controller.oaf_feature", package.seeall) + +function index() + entry({"admin", "services", "oaf", "feature"}, + template("oaf/feature"), + _("Feature Library"), 80).dependent = true + entry({"admin", "services", "oaf", "feature", "info"}, call("get_feature_info"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "class_list"}, call("get_feature_class_list"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "online_config"}, call("get_feature_online_config"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "online_save"}, call("set_feature_online_config"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "online_list"}, call("get_feature_online_list"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "online_start"}, call("start_feature_online_update"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "online_status"}, call("get_feature_online_update_status"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "custom_list"}, call("get_custom_feature_list"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "custom_class_list"}, call("get_custom_feature_class_list"), nil).leaf = true + entry({"admin", "services", "oaf", "feature", "custom_save"}, call("set_custom_feature_list"), nil).leaf = true +end + +function get_feature_info() + local json = require "luci.jsonc" + local util = require "luci.util" + local http = require "luci.http" + local resp = util.ubus("fwx", "common", {api = "get_feature_info", data = {}}) + + http.prepare_content("application/json") + http.write(json.stringify(resp or {code = 4000})) +end + +local function write_fwx_response(api, data) + local json = require "luci.jsonc" + local util = require "luci.util" + local http = require "luci.http" + local resp = util.ubus("fwx", "common", {api = api, data = data or {}}) + + http.prepare_content("application/json") + http.write(json.stringify(resp or {code = 4000})) +end + +function get_feature_online_config() + write_fwx_response("get_feature_online_config", {}) +end + +function set_feature_online_config() + local http = require "luci.http" + write_fwx_response("set_feature_online_config", { + token = http.formvalue("token") or "" + }) +end + +function get_feature_online_list() + local http = require "luci.http" + local lang = http.formvalue("lang") or "cn" + local refresh = tonumber(http.formvalue("refresh") or "0") or 0 + if lang ~= "cn" and lang ~= "en" then + lang = "cn" + end + write_fwx_response("get_feature_online_list", { + lang = lang, + device_lang = http.formvalue("device_lang") or "", + refresh = refresh + }) +end + +function start_feature_online_update() + local http = require "luci.http" + local lang = http.formvalue("lang") or "cn" + if lang ~= "cn" and lang ~= "en" then + lang = "cn" + end + write_fwx_response("start_feature_online_update", { + id = http.formvalue("id") or "", + lang = lang, + md5 = http.formvalue("md5") or "" + }) +end + +function get_feature_online_update_status() + write_fwx_response("get_feature_online_update_status", {}) +end + +function get_custom_feature_list() + write_fwx_response("get_custom_feature", {}) +end + +function get_custom_feature_class_list() + write_fwx_response("get_custom_feature_class_list", {}) +end + +function set_custom_feature_list() + local json = require "luci.jsonc" + local http = require "luci.http" + local data_str = http.formvalue("data") + local ok, data_obj = pcall(json.parse, data_str or "") + + if not ok then + data_obj = nil + end + if type(data_obj) ~= "table" or type(data_obj.app_list) ~= "table" then + http.prepare_content("application/json") + http.write(json.stringify({code = 4000, data = {error = "invalid request data"}})) + return + end + write_fwx_response("set_custom_feature", data_obj) +end + +function get_feature_class_list() + local json = require "luci.jsonc" + local util = require "luci.util" + local http = require "luci.http" + local resp = util.ubus("fwx", "common", {CopyRight = "www.fanchmwrt.com", api = "class_list", data = {}}) + + http.prepare_content("application/json") + if resp and resp.code == 2000 and resp.data then + http.write(json.stringify(resp.data)) + else + http.write(json.stringify({class_list = {}})) + end +end diff --git a/luci-app-oaf/luasrc/controller/oaf_internet_audit.lua b/luci-app-oaf/luasrc/controller/oaf_internet_audit.lua new file mode 100644 index 00000000..e25697a6 --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_internet_audit.lua @@ -0,0 +1,113 @@ +module("luci.controller.oaf_internet_audit", package.seeall) + +function index() + entry({"admin", "services", "oaf", "api", "internet_audit", "get_record_base"}, call("get_record_base"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "internet_audit", "set_record_base"}, call("set_record_base"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "internet_audit", "record_action"}, call("record_action"), nil).leaf = true +end + +function ensure_record_section(cur) + local sid + cur:foreach("fwx", "record", function(s) + sid = s[".name"] + end) + if not sid then + sid = cur:add("fwx", "record") + end + return sid +end + +function get_record_base() + local json = require "luci.jsonc" + local http = require "luci.http" + local utl = require "luci.util" + + local req_obj = { api = "get_record_base", data = {} } + local resp = utl.ubus("fwx", "common", req_obj) or {code = 1} + http.prepare_content("application/json") + http.write(json.stringify(resp)) +end + +function set_record_base() + local json = require "luci.jsonc" + local http = require "luci.http" + local utl = require "luci.util" + + local enable = tonumber(http.formvalue("enable") or 0) or 0 + local record_time = tonumber(http.formvalue("record_time") or 0) or 0 + local app_valid_time = tonumber(http.formvalue("app_valid_time") or 0) or 0 + local history_data_size = http.formvalue("history_data_size") or "" + local history_data_path = http.formvalue("history_data_path") or "" + local base_data_path = http.formvalue("base_data_path") or http.formvalue("terminal_data_path") or "" + + if record_time < 0 then record_time = 0 end + if app_valid_time < 0 then app_valid_time = 0 end + + if history_data_size and history_data_size ~= "" then + local size_num = tonumber(history_data_size) + if not size_num or size_num < 1 or size_num > 1024 or size_num ~= math.floor(size_num) then + http.prepare_content("application/json") + http.write(json.stringify({code = 1, msg = "History data size must be an integer between 1 and 1024 MB"})) + return + end + end + + if not history_data_path or history_data_path == "" or history_data_path == "/" then + http.prepare_content("application/json") + http.write(json.stringify({code = 1, msg = "History data path cannot be empty or /"})) + return + end + + if not base_data_path or base_data_path == "" or base_data_path == "/" then + http.prepare_content("application/json") + http.write(json.stringify({code = 1, msg = "Base data path cannot be empty or /"})) + return + end + + if #history_data_path > 64 then + http.prepare_content("application/json") + http.write(json.stringify({code = 1, msg = "History data path maximum length is 64 characters"})) + return + end + + if #base_data_path > 64 then + http.prepare_content("application/json") + http.write(json.stringify({code = 1, msg = "Base data path maximum length is 64 characters"})) + return + end + + local req_obj = { + api = "set_record_base", + data = { + enable = enable, + record_time = record_time, + app_valid_time = app_valid_time, + history_data_size = history_data_size, + history_data_path = history_data_path, + base_data_path = base_data_path + } + } + local resp = utl.ubus("fwx", "common", req_obj) or {code = 1} + + http.prepare_content("application/json") + http.write(json.stringify(resp)) +end + +function record_action() + local json = require "luci.jsonc" + local http = require "luci.http" + local utl = require "luci.util" + + local action = http.formvalue("action") or "" + + local req_obj = { + api = "record_action", + data = { + action = action + } + } + local resp = utl.ubus("fwx", "common", req_obj) or {code = 1} + + http.prepare_content("application/json") + http.write(json.stringify(resp)) +end diff --git a/luci-app-oaf/luasrc/controller/oaf_mac_filter.lua b/luci-app-oaf/luasrc/controller/oaf_mac_filter.lua new file mode 100644 index 00000000..e9e5b2d8 --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_mac_filter.lua @@ -0,0 +1,461 @@ +module("luci.controller.oaf_mac_filter", package.seeall) +local utl = require "luci.util" +local nixio = require "nixio" + +function index() + if not nixio.fs.access("/etc/config/macfilter") then + return + end + + entry({"admin", "services", "oaf", "mac_filter"}, alias("admin", "services", "oaf", "mac_filter", "rules"), _("Access Control"), 40).dependent = true + entry({"admin", "services", "oaf", "mac_filter", "rules"}, cbi("oaf/mac_filter/rules", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Filter Rules"), 10).leaf=true + + entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_base"}, call("get_mac_filter_base"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "set_mac_filter_base"}, call("set_mac_filter_base"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "set_mac_filter_time"}, call("set_mac_filter_time"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_time"}, call("get_mac_filter_time"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_user"}, call("get_mac_filter_user"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "set_mac_filter_user"}, call("set_mac_filter_user"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "del_mac_filter_user"}, call("del_mac_filter_user"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "add_mac_filter_user"}, call("add_mac_filter_user"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "get_mf_status"}, call("get_mf_status"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_whitelist"}, call("get_mac_filter_whitelist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "del_mac_filter_whitelist"}, call("del_mac_filter_whitelist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "add_mac_filter_whitelist"}, call("add_mac_filter_whitelist"), nil).leaf = true + + + entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_rules"}, call("get_mac_filter_rules"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "add_mac_filter_rule"}, call("add_mac_filter_rule"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "update_mac_filter_rule"}, call("update_mac_filter_rule"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "delete_mac_filter_rule"}, call("delete_mac_filter_rule"), nil).leaf = true + + + entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_adv"}, call("get_mac_filter_adv"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "mac_filter", "set_mac_filter_adv"}, call("set_mac_filter_adv"), nil).leaf = true +end + +function get_mf_status() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "get_mf_status" + req_obj.data = {} + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + +function get_mac_filter_user() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "get_mac_filter_user" + req_obj.data = {} + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + +function del_mac_filter_user() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local req_obj = {} + local mac = luci.http.formvalue("mac") + llog("del macfilter user "..mac); + req_obj.api = "del_mac_filter_user" + req_obj.data = { + mac = mac + } + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + +function add_mac_filter_user() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "add_mac_filter_user" + local data_str = luci.http.formvalue("data") + local data = json.parse(data_str) + req_obj.data = data + + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + + +function get_mac_filter_base() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "get_mac_filter_base" + req_obj.data = {} + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + +function set_mac_filter_user() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "set_mac_filter_user" + local mode = luci.http.formvalue("mode") + req_obj.data = { + mode = mode + } + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + +function set_mac_filter_base() + local json = require "luci.jsonc" + llog("set macfilter base"); + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "set_mac_filter_base" + local enable = luci.http.formvalue("enable") + req_obj.data = { + enable = enable + } + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + + + +function set_mac_filter_time() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "set_mac_filter_time" + local data_str = luci.http.formvalue("data") + local data = json.parse(data_str) + req_obj.data = data + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + +function get_mac_filter_time() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "get_mac_filter_time" + req_obj.data = {} + local resp_obj=utl.ubus("fwx", "common", req_obj); + luci.http.write_json(resp_obj); +end + + +function get_mac_filter_whitelist() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + + local req_obj = {} + req_obj.api = "get_mac_filter_whitelist" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json({code = 2000, data = resp_obj.data, message = "success"}) + else + + luci.http.write_json({code = 2000, data = {list = {}}, message = "success"}) + end +end + + +function add_mac_filter_whitelist() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local data_str = luci.http.formvalue("data") + if not data_str then + luci.http.write_json({code = 1, message = "Invalid request data"}) + return + end + + local whitelist_data = json.parse(data_str) + llog("add_mac_filter_whitelist: " .. json.stringify(whitelist_data)) + + if not whitelist_data.mac_list or type(whitelist_data.mac_list) ~= "table" then + luci.http.write_json({code = 1, message = "Invalid mac_list"}) + return + end + + + local req_obj = {} + req_obj.api = "add_mac_filter_whitelist" + req_obj.data = whitelist_data + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + + + if resp_obj and resp_obj.code == 2000 then + + luci.http.write_json({code = 2000, message = "Whitelist added successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to add whitelist"}) + end +end + + +function del_mac_filter_whitelist() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local mac = luci.http.formvalue("mac") + if not mac then + luci.http.write_json({code = 1, message = "Invalid mac address"}) + return + end + + llog("del_mac_filter_whitelist: " .. mac) + + + local req_obj = {} + req_obj.api = "del_mac_filter_whitelist" + req_obj.data = { + mac = mac + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + + + if resp_obj and resp_obj.code == 2000 then + + luci.http.write_json({code = 2000, message = "Whitelist deleted successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to delete whitelist"}) + end +end + +function llog(message) + local log_file = "/tmp/log/oaf_luci.log" + local fd = io.open(log_file, "a") + if fd then + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + fd:write(string.format("[%s] %s\n", timestamp, message)) + fd:close() + end +end + + +function get_mac_filter_rules() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + + local req_obj = {} + req_obj.api = "get_mac_filter_rules" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + + + if resp_obj and resp_obj.code == 2000 and resp_obj.data and resp_obj.data.list then + local rules_data = resp_obj.data.list + local json_str = json.stringify({code = 0, data = rules_data, message = "success"}) + luci.http.write(json_str) + else + + local json_str = json.stringify({code = 0, data = {}, message = "success"}) + luci.http.write(json_str) + end +end + + +function add_mac_filter_rule() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local data_str = luci.http.formvalue("data") + if not data_str then + luci.http.write_json({code = 1, message = "Invalid request data"}) + return + end + + local rule_data = json.parse(data_str) + llog("add_mac_filter_rule: " .. json.stringify(rule_data)) + + + if not rule_data.name or not rule_data.mode then + luci.http.write_json({code = 1, message = "Missing required fields"}) + return + end + + local time_mode = tonumber(rule_data.time_mode) or 1 + if time_mode == 1 then + if (not rule_data.time_list or #rule_data.time_list == 0) and (not rule_data.time_rules or #rule_data.time_rules == 0) then + luci.http.write_json({code = 1, message = "Missing required time_list"}) + return + end + elseif time_mode == 2 then + if (not rule_data.time_limit or rule_data.time_limit == "") and (not rule_data.time_rules or #rule_data.time_rules == 0) then + luci.http.write_json({code = 1, message = "Missing required time_limit"}) + return + end + elseif time_mode == 3 then + if (not rule_data.flow_limit or rule_data.flow_limit == "") and (not rule_data.time_rules or #rule_data.time_rules == 0) then + luci.http.write_json({code = 1, message = "Missing required flow_limit"}) + return + end + end + + + local get_req_obj = {} + get_req_obj.api = "get_mac_filter_rules" + get_req_obj.data = {} + local get_resp_obj = utl.ubus("fwx", "common", get_req_obj) + + if get_resp_obj and get_resp_obj.code == 2000 and get_resp_obj.data and get_resp_obj.data.data then + local existing_rules = get_resp_obj.data.data + if #existing_rules >= 32 then + luci.http.write_json({code = 1, message = "Maximum 32 rules allowed"}) + return + end + end + + + local req_obj = {} + req_obj.api = "add_mac_filter_rule" + req_obj.data = rule_data + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 0, message = "Rule added successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to add rule"}) + end +end + + +function update_mac_filter_rule() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local data_str = luci.http.formvalue("data") + if not data_str then + luci.http.write_json({code = 1, message = "Invalid request data"}) + return + end + + local rule_data = json.parse(data_str) + llog("update_mac_filter_rule: " .. json.stringify(rule_data)) + + if not rule_data.id then + luci.http.write_json({code = 1, message = "Missing rule id"}) + return + end + + + local req_obj = {} + req_obj.api = "update_mac_filter_rule" + req_obj.data = rule_data + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 then + + luci.http.write_json({code = 0, message = "Rule updated successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to update rule"}) + end +end + + +function delete_mac_filter_rule() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local rule_id = luci.http.formvalue("rule_id") + if not rule_id then + luci.http.write_json({code = 1, message = "Invalid rule_id"}) + return + end + + llog("delete_mac_filter_rule: " .. rule_id) + + + local req_obj = {} + req_obj.api = "delete_mac_filter_rule" + req_obj.data = { + id = tonumber(rule_id) + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 0, message = "Rule deleted successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to delete rule"}) + end +end + + +function get_mac_filter_adv() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + + local req_obj = {} + req_obj.api = "get_mac_filter_adv" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json({code = 0, data = resp_obj.data, message = "success"}) + else + luci.http.write_json({code = 1, message = "Failed to load"}) + end +end + + +function set_mac_filter_adv() + local json = require "luci.jsonc" + local utl = require "luci.util" + luci.http.prepare_content("application/json") + + local enable = tonumber(luci.http.formvalue("enable")) or 0 + + + if enable ~= 0 and enable ~= 1 then + luci.http.write_json({code = 1, message = "Invalid enable value, must be 0 or 1"}) + return + end + + local req_obj = {} + req_obj.api = "set_mac_filter_adv" + req_obj.data = { + enable = enable + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 then + llog("Set macfilter enable: " .. enable) + luci.http.write_json({code = 0, message = "Saved successfully"}) + else + luci.http.write_json({code = 1, message = "Failed to save"}) + end +end diff --git a/luci-app-oaf/luasrc/controller/oaf_user.lua b/luci-app-oaf/luasrc/controller/oaf_user.lua new file mode 100644 index 00000000..a201b626 --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_user.lua @@ -0,0 +1,452 @@ +module("luci.controller.oaf_user", package.seeall) +local utl = require "luci.util" + +function index() + local page + entry({"admin", "services", "oaf", "users"}, alias("admin", "services", "oaf", "users", "list"), _("User List"), 20).dependent = true + entry({"admin", "services", "oaf", "users", "list"}, cbi("oaf/user_list", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), + nil).leaf = true + + entry({"admin", "services", "oaf", "users", "detail"}, cbi("oaf/user_detail", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil).leaf = true + entry({"admin", "services", "oaf", "api", "user_status"}, call("user_status"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "dev_visit_list"}, call("get_dev_visit_list"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "dev_visit_time"}, call("get_dev_visit_time"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "app_class_visit_time"}, call("get_app_class_visit_time"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_class_list"}, call("get_class_list"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_all_users"}, call("get_all_users"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_system_base_info"}, call("get_system_base_info"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_mac_blacklist"}, call("get_mac_blacklist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "add_mac_blacklist"}, call("add_mac_blacklist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "del_mac_blacklist"}, call("del_mac_blacklist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_parental_control_detail"}, call("get_parental_control_detail"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "set_nickname"}, call("set_nickname"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_hourly_stats"}, call("get_hourly_stats"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_user_basic_info"}, call("get_user_basic_info"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_online_offline_records"}, call("get_online_offline_records"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "get_user_parental_control_rules"}, call("get_user_parental_control_rules"), nil).leaf = true +end + +function get_hostname_by_mac(dst_mac) + leasefile="/tmp/dhcp.leases" + local fd = io.open(leasefile, "r") + if not fd then return end + while true do + local ln = fd:read("*l") + if not ln then + break + end + local ts, mac, ip, name, duid = ln:match("^(%d+) (%S+) (%S+) (%S+) (%S+)") + if dst_mac == mac then + fd:close() + return name + end + end + fd:close() + return "" +end + + +function get_app_name_by_id(appid) + local class_fd = io.popen("find /tmp/appfilter/ -type f -name *.class |xargs cat |grep "..appid.."|awk '{print $2}'") + if class_fd then + local name = class_fd:read("*l") + class_fd:close() + if name and name ~= "" then + return name + end + end + if tonumber(appid) and tonumber(appid) > 0 then + return "App" .. tostring(appid) + end + return "" +end + +function cmp_func(a,b) + return a.latest_time > b.latest_time +end + +function normalize_mac(mac) + if not mac then + return "" + end + return tostring(mac):gsub("^%s+", ""):gsub("%s+$", ""):upper() +end + +function call_fwx_common(api, data) + local req_obj = {} + req_obj.api = api + req_obj.data = data or {} + return utl.ubus("fwx", "common", req_obj) +end + +function user_status() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + local fd = io.open("/proc/net/af_client","r") + status_buf=fd:read('*a') + fd:close() + user_array=json.parse(status_buf) + + local req_obj = {} + req_obj.api = "visit_list" + req_obj.data = {} + local visit_obj = utl.ubus("fwx", "common", req_obj) + + local user_array = {} + if visit_obj and visit_obj.code == 2000 and visit_obj.data and visit_obj.data.dev_list then + user_array = visit_obj.data.dev_list + end + local history={} + for i, v in pairs(user_array) do + visit_array=user_array[i].visit_info + for j,s in pairs(visit_array) do + print(user_array[i].mac, user_array[i].ip,visit_array[j].appid, visit_array[j].latest_time) + total_time=visit_array[j].latest_time - visit_array[j].first_time; + history[#history+1]={ + mac=user_array[i].mac, + ip=user_array[i].ip, + hostname=get_hostname_by_mac(user_array[i].mac), + appid=visit_array[j].appid, + appname=get_app_name_by_id(visit_array[j].appid), + total_num=0, + drop_num=0, + latest_action=visit_array[j].latest_action, + latest_time=os.date("%Y/%m/%d %H:%M:%S", visit_array[j].latest_time), + first_time=os.date("%Y/%m/%d %H:%M:%S", visit_array[j].first_time), + total_time=total_time + } + end + end + table.sort(history, cmp_func) + luci.http.write_json(history); +end + +function get_class_list() + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.CopyRight = "www.fanchmwrt.com" + req_obj.api = "class_list" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({class_list = {}}) + end +end + + +function get_all_users() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_all_users" + req_obj.data = { + flag = luci.http.formvalue("flag"), + page = luci.http.formvalue("page"), + page_size = luci.http.formvalue("page_size") + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json({data = resp_obj.data}) + else + luci.http.write_json({data = resp_obj or {}}) + end +end + +function get_system_base_info() + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_system_base_info" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + user_session_enable = 0 + }) + end +end + +function get_mac_blacklist() + luci.http.prepare_content("application/json") + + local resp_obj = call_fwx_common("get_mac_blacklist", {}) + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json({code = 0, data = resp_obj.data, message = "success"}) + else + luci.http.write_json({code = 1, data = {list = {}}, message = "failed"}) + end +end + +function add_mac_blacklist() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local mac_list = {} + local mac = normalize_mac(luci.http.formvalue("mac")) + if mac ~= "" then + table.insert(mac_list, mac) + else + local data_str = luci.http.formvalue("data") + if data_str and data_str ~= "" then + local data = json.parse(data_str) + if data and type(data.mac_list) == "table" then + for _, item in ipairs(data.mac_list) do + local normalized_mac = normalize_mac(item) + if normalized_mac ~= "" then + table.insert(mac_list, normalized_mac) + end + end + end + end + end + + if #mac_list == 0 then + luci.http.write_json({code = 1, message = "Invalid mac"}) + return + end + + local resp_obj = call_fwx_common("add_mac_blacklist", {mac_list = mac_list}) + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 0, message = "success"}) + else + luci.http.write_json({code = 1, message = "failed"}) + end +end + +function del_mac_blacklist() + luci.http.prepare_content("application/json") + local mac = normalize_mac(luci.http.formvalue("mac")) + if mac == "" then + luci.http.write_json({code = 1, message = "Invalid mac"}) + return + end + + local resp_obj = call_fwx_common("del_mac_blacklist", {mac = mac}) + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json({code = 0, message = "success"}) + else + luci.http.write_json({code = 1, message = "failed"}) + end +end + +function get_parental_control_detail() + luci.http.prepare_content("application/json") + local req_obj = {} + req_obj.api = "get_parental_control_detail" + req_obj.data = { + mac = luci.http.formvalue("mac") + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + pc_status = "unlimited", + pc_status_key = "unlimited", + af_whitelist = 0, + mf_whitelist = 0, + appfilter_rules = {}, + macfilter_rules = {} + }) + end +end + +function get_user_parental_control_rules() + luci.http.prepare_content("application/json") + + local target_mac = normalize_mac(luci.http.formvalue("mac")) + if target_mac == "" then + luci.http.write_json({ + mac = "", + af_whitelist = 0, + mf_whitelist = 0, + list = {} + }) + return + end + + local req_obj = { + api = "get_user_parental_control_rules", + data = { + mac = target_mac + } + } + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and type(resp_obj.data) == "table" then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + mac = target_mac, + af_whitelist = 0, + mf_whitelist = 0, + list = {} + }) + end +end + +function get_oaf_status() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_oaf_status" + req_obj.data = {} + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json(resp_obj or {}) + end +end + + +function set_nickname() + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "set_nickname" + req_obj.data = { + mac = luci.http.formvalue("mac"), + nickname = luci.http.formvalue("nickname") + } + + local resp_obj = utl.ubus("fwx", "common", req_obj); + + if resp_obj and resp_obj.code == 2000 then + luci.http.write_json(resp_obj.data or {}) + else + luci.http.write_json(resp_obj or {}) + end +end + + +function get_dev_visit_time(mac) + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "dev_visit_time" + req_obj.data = { + mac = mac + } + + local visit_obj = utl.ubus("fwx", "common", req_obj) + + local visit_list = {} + if visit_obj and visit_obj.code == 2000 and visit_obj.data and visit_obj.data.list then + visit_list = visit_obj.data.list + end + luci.http.write_json(visit_list) +end + +function get_app_class_visit_time(mac) + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "app_class_visit_time" + req_obj.data = { + mac = mac + } + + local visit_obj = utl.ubus("fwx", "common", req_obj) + + local class_array = {} + if visit_obj and visit_obj.code == 2000 and visit_obj.data and visit_obj.data.class_list then + class_array = visit_obj.data.class_list + end + luci.http.write_json(class_array) +end + + +function get_dev_visit_list(mac) + local json = require "luci.jsonc" + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "dev_visit_list" + req_obj.data = { + mac = mac + } + + local page = luci.http.formvalue("page") + local page_size = luci.http.formvalue("page_size") + if page then + req_obj.data.page = tonumber(page) or 1 + end + if page_size then + req_obj.data.page_size = tonumber(page_size) or 15 + end + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json(resp_obj or {}) + end +end + +function get_hourly_stats(mac) + local json = require "luci.jsonc" + local utl = require "luci.util" + + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_hourly_top_apps" + req_obj.data = { + mac = mac + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({ + mac = mac, + date = 0, + is_today = 1, + hourly_stats = {} + }) + end +end + +function get_user_basic_info(mac) + local json = require "luci.jsonc" + local utl = require "luci.util" + + luci.http.prepare_content("application/json") + + local req_obj = {} + req_obj.api = "get_user_basic_info" + req_obj.data = { + mac = mac + } + + local resp_obj = utl.ubus("fwx", "common", req_obj) + + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + luci.http.write_json(resp_obj.data) + else + luci.http.write_json({}) + end +end diff --git a/luci-app-oaf/luasrc/controller/oaf_whitelist.lua b/luci-app-oaf/luasrc/controller/oaf_whitelist.lua new file mode 100644 index 00000000..63ce59fb --- /dev/null +++ b/luci-app-oaf/luasrc/controller/oaf_whitelist.lua @@ -0,0 +1,181 @@ +module("luci.controller.oaf_whitelist", package.seeall) + +local nixio = require "nixio" +local util = require "luci.util" +local jsonc = require "luci.jsonc" + +local function normalize_mac(mac) + return string.upper((mac or ""):gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function is_valid_mac(mac) + return mac and mac:match("^%x%x:%x%x:%x%x:%x%x:%x%x:%x%x$") ~= nil +end + +local function write_json(data) + luci.http.prepare_content("application/json") + luci.http.write_json(data) +end + +local function get_record_whitelist_page(page, page_size) + local req_obj = { + api = "get_record_whitelist", + data = { + page = page, + page_size = page_size + } + } + + return util.ubus("fwx", "common", req_obj) +end + +function index() + local has_app_filter = nixio.fs.access("/etc/config/appfilter") + local has_access_control = nixio.fs.access("/etc/config/macfilter") + local has_record = nixio.fs.access("/etc/config/fwx_record") + + if not has_app_filter and not has_access_control and not has_record then + return + end + + entry({"admin", "services", "oaf", "whitelist"}, cbi("oaf/whitelist", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Whitelist"), 50).leaf = true + entry({"admin", "services", "oaf", "api", "record_whitelist", "get_record_whitelist"}, call("get_record_whitelist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "record_whitelist", "get_record_whitelist_all"}, call("get_record_whitelist_all"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "record_whitelist", "add_record_whitelist"}, call("add_record_whitelist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "record_whitelist", "del_record_whitelist"}, call("del_record_whitelist"), nil).leaf = true + entry({"admin", "services", "oaf", "api", "record_whitelist", "get_all_users"}, call("get_all_users"), nil).leaf = true +end + +function get_record_whitelist() + local page = tonumber(luci.http.formvalue("page") or "1") or 1 + local page_size = tonumber(luci.http.formvalue("page_size") or "200") or 200 + + if page < 1 then page = 1 end + if page_size < 1 then page_size = 200 end + if page_size > 200 then page_size = 200 end + + local resp_obj = get_record_whitelist_page(page, page_size) + if resp_obj and resp_obj.code == 2000 and resp_obj.data then + write_json({code = 2000, data = resp_obj.data, message = "success"}) + else + write_json({code = 2000, data = {total_num = 0, total_page = 1, page = page, page_size = page_size, list = {}}, message = "success"}) + end +end + +function get_record_whitelist_all() + local all_list = {} + local total_page = 1 + local page = 1 + local page_size = 200 + + while page <= total_page do + local resp_obj = get_record_whitelist_page(page, page_size) + if not (resp_obj and resp_obj.code == 2000 and resp_obj.data and type(resp_obj.data.list) == "table") then + break + end + + local i + for i = 1, #resp_obj.data.list do + all_list[#all_list + 1] = resp_obj.data.list[i] + end + + total_page = tonumber(resp_obj.data.total_page or 1) or 1 + if total_page < 1 then total_page = 1 end + page = page + 1 + end + + write_json({code = 2000, data = {list = all_list}, message = "success"}) +end + +function add_record_whitelist() + local data_str = luci.http.formvalue("data") + local mac = normalize_mac(luci.http.formvalue("mac")) + local mac_list = {} + + if data_str and data_str ~= "" then + local parsed = jsonc.parse(data_str) + if parsed and type(parsed.mac_list) == "table" then + local i + for i = 1, #parsed.mac_list do + local one_mac = normalize_mac(parsed.mac_list[i]) + if is_valid_mac(one_mac) then + mac_list[#mac_list + 1] = one_mac + end + end + end + end + + if #mac_list == 0 and is_valid_mac(mac) then + mac_list = {mac} + end + + if #mac_list == 0 then + write_json({code = 1, message = "Invalid mac_list"}) + return + end + + local resp_obj = util.ubus("fwx", "common", { + api = "add_record_whitelist", + data = {mac_list = mac_list} + }) + + if resp_obj and resp_obj.code == 2000 then + write_json({code = 2000, message = "Whitelist added successfully"}) + else + write_json({code = 1, message = "Failed to add whitelist"}) + end +end + +function del_record_whitelist() + local mac = normalize_mac(luci.http.formvalue("mac")) + + if not is_valid_mac(mac) then + write_json({code = 1, message = "Invalid mac address"}) + return + end + + local resp_obj = util.ubus("fwx", "common", { + api = "del_record_whitelist", + data = {mac = mac} + }) + + if resp_obj and resp_obj.code == 2000 then + write_json({code = 2000, message = "Whitelist deleted successfully"}) + else + write_json({code = 1, message = "Failed to delete whitelist"}) + end +end + +function get_all_users() + local all_list = {} + local total_page = 1 + local page = 1 + local page_size = 200 + + while page <= total_page do + local req_obj = { + api = "get_all_users", + data = { + flag = 2, + page = page, + page_size = page_size + } + } + + local resp_obj = util.ubus("fwx", "common", req_obj) + if not (resp_obj and resp_obj.code == 2000 and resp_obj.data and type(resp_obj.data.list) == "table") then + break + end + + local i + for i = 1, #resp_obj.data.list do + all_list[#all_list + 1] = resp_obj.data.list[i] + end + + total_page = tonumber(resp_obj.data.total_page or 1) or 1 + if total_page < 1 then total_page = 1 end + page = page + 1 + end + + write_json({code = 2000, data = {list = all_list}, message = "success"}) +end \ No newline at end of file diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/feature.lua b/luci-app-oaf/luasrc/model/cbi/appfilter/feature.lua deleted file mode 100644 index 690d531f..00000000 --- a/luci-app-oaf/luasrc/model/cbi/appfilter/feature.lua +++ /dev/null @@ -1,177 +0,0 @@ - -local nfs = require "nixio.fs" -local sys = require "luci.sys" -local SYS = require "luci.sys" -local http = luci.http - -local m, s - -m = Map("appfilter", translate("App Feature Library"), translate("The App feature library is used to describe the packet protocol of applications, including port, domain, and Layer7 payload. It is the core of the DPI engine and affects the effectiveness of OAF. You can also add or modify App features according to the official website tutorial.")) -s = m:section(SimpleSection) -s.template = "admin_network/feature" -s.anonymous = true - -local dir, fd -dir = "/tmp/upload/" -nixio.fs.mkdir(dir) - -local STATUS_FILE = "/tmp/feature_upgrade.status" -local MAX_SIZE = 20 * 1024 * 1024 - -local function write_status(code) - local f = io.open(STATUS_FILE, "w+") - if f then - f:write(tostring(code)) - f:close() - end -end - -local function log(msg) - local f = io.open("/tmp/log/luci.log", "a+") - if f then - f:write(os.date("%Y-%m-%d %H:%M:%S") .. " [fwx_feature_upload] " .. tostring(msg) .. "\n") - f:close() - end -end - -local function get_overlay_free_space() - local df_output = SYS.exec("df -k /overlay 2>/dev/null | tail -1 | awk '{print $4}'") - if df_output then - df_output = string.gsub(df_output, "%s+", "") - local free_kb = tonumber(df_output) - if free_kb then - return free_kb * 1024 -- 转换为字节 - end - end - return 0 -end - -local function get_dir_size(dir_path) - local du_output = SYS.exec("du -sb " .. dir_path .. " 2>/dev/null | awk '{print $1}'") - if du_output then - du_output = string.gsub(du_output, "%s+", "") - local size_bytes = tonumber(du_output) - if size_bytes then - return size_bytes - end - end - return 0 -end - -http.setfilehandler(function(meta, chunk, eof) - local feature_file = "/etc/appfilter/feature.cfg" - local f_format = "v3.0" - local format = "v3.0" - if not fd then - if not meta then - return - end - if meta and chunk then - log("start upload filename=" .. (meta.file or "")) - fd = nixio.open(dir .. meta.file, "w") - write_status(1) - end - if not fd then - log("open file failed: " .. (dir .. (meta.file or ""))) - write_status(401) - return - end - end - if chunk and fd then - fd:write(chunk) - end - if eof and fd then - fd:close() - log("upload finished, saved to " .. dir .. (meta.file or "")) - local meta_size = 0 - do - local file_path = dir .. (meta.file or "") - local stat = nixio.fs.stat(file_path) - if stat and stat.size then - meta_size = stat.size - end - end - log("meta_size: " .. tostring(meta_size) .. ", MAX_SIZE: " .. tostring(MAX_SIZE)) - if meta_size > MAX_SIZE then - log("file too large: " .. tostring(meta_size)) - write_status(402) - os.execute("rm /tmp/upload/* -fr") - return - end - - local tar_cmd = "tar -zxvf /tmp/upload/" .. meta.file .. " -C /tmp/upload/ >/dev/null" - local success = os.execute(tar_cmd) - if success ~= 0 then - log("tar extract failed: " .. tar_cmd) - write_status(401) - return - end - - local feature_dir = "/tmp/upload/feature" - local fd2 = io.open("/tmp/upload/feature.cfg") - if not fd2 then - log("feature.cfg not found after extract") - write_status(401) - os.execute("rm /tmp/upload/* -fr") - return - end - local version_line = fd2:read("*l") - local format_line = fd2:read("*l") - fd2:close() - local ret = string.match(version_line, "#version") - if ret ~= nil then - if string.match(format_line, "#format") then - f_format = SYS.exec("echo '"..format_line.."'|awk '{print $2}'") - end - if not string.match(f_format, format) then - log("format mismatch: got " .. f_format .. ", expected " .. format) - write_status(401) - os.execute("rm /tmp/upload/* -fr") - return - end - local cmd = "cp /tmp/upload/feature.cfg " .. feature_file - os.execute(cmd) - - local app_icons_src = "/tmp/upload/app_icons" - local app_icons_dst = "/www/luci-static/resources/app_icons" - - if nixio.fs.stat(app_icons_src) then - local app_icons_size = get_dir_size(app_icons_src) - local overlay_free = get_overlay_free_space() - - log("app_icons size: " .. tostring(app_icons_size) .. " bytes, overlay free: " .. tostring(overlay_free) .. " bytes") - - if overlay_free >= app_icons_size then - log("overlay space sufficient, copying app_icons to /www") - os.execute("rm -rf " .. app_icons_dst .. "/*") - cmd = "cp -r " .. app_icons_src .. "/* " .. app_icons_dst .. "/ >/dev/null 2>&1" - os.execute(cmd) - log("app_icons copied to /www/luci-static/resources/app_icons") - else - log("overlay space insufficient (" .. tostring(overlay_free) .. " < " .. tostring(app_icons_size) .. "), skipping app_icons copy") - end - else - log("app_icons directory not found in upload package, skipping") - end - os.execute("chmod 666 " .. feature_file) - luci.sys.exec("killall -SIGUSR1 oafd") - log("feature updated successfully") - write_status(200) - else - log("missing #version marker") - write_status(401) - end - os.execute("rm /tmp/upload/* -fr") - end - -end) - -if luci.http.formvalue("upload") then - local f = luci.http.formvalue("ulfile") - if #f <= 0 then - end -elseif luci.http.formvalue("download") then -end - -return m - diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/time_setting.lua b/luci-app-oaf/luasrc/model/cbi/appfilter/time_setting.lua deleted file mode 100644 index 52a8aa96..00000000 --- a/luci-app-oaf/luasrc/model/cbi/appfilter/time_setting.lua +++ /dev/null @@ -1,49 +0,0 @@ -local ds = require "luci.dispatcher" -local nxo = require "nixio" -local nfs = require "nixio.fs" -local ipc = require "luci.ip" -local sys = require "luci.sys" -local utl = require "luci.util" -local dsp = require "luci.dispatcher" -local uci = require "luci.model.uci" -local lng = require "luci.i18n" -local jsc = require "luci.jsonc" -local http = luci.http -local SYS = require "luci.sys" -local m, s - -m = Map("appfilter", translate(""), translate("")) - -s = m:section(TypedSection, "time", translate("Time Setting"),translate("The second time is optional, the end time must be greater than the start time")) -s.anonymous = true - - -o=s:option(ListValue, "time_mode", translate("Time Mode"),translate("")) -o.default=0 -o:value(0,translate("Blacklist mode")) -o:value(1,translate("Whitelist mode")) - -days = s:option(MultiValue, "days", "", translate("")) -days.widget = "checkbox" -days.size = 10 -days:value("0", translate("Sun")); -days:value("1", translate("Mon")); -days:value("2", translate("Tue")); -days:value("3", translate("Wed")); -days:value("4", translate("Thur")); -days:value("5", translate("Fri")); -days:value("6", translate("Sat")); - -hv = s:option(Value, "start_time", translate("Start Time1"),translate("xx:xx")) -hv.optional = false -hv = s:option(Value, "end_time", translate("End Time1")) -hv.optional = false - -hv = s:option(Value, "start_time2", translate("Start Time2")) -hv.optional = false -hv = s:option(Value, "end_time2", translate("End Time2")) -hv.optional = false - - - -return m diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/app_filter.lua b/luci-app-oaf/luasrc/model/cbi/oaf/app_filter/rules.lua similarity index 81% rename from luci-app-oaf/luasrc/model/cbi/appfilter/app_filter.lua rename to luci-app-oaf/luasrc/model/cbi/oaf/app_filter/rules.lua index 1961a489..d9c51577 100644 --- a/luci-app-oaf/luasrc/model/cbi/appfilter/app_filter.lua +++ b/luci-app-oaf/luasrc/model/cbi/oaf/app_filter/rules.lua @@ -11,10 +11,10 @@ local jsc = require "luci.jsonc" local m, s arg[1] = arg[1] or "" -m = Map("appfilter", translate(""), translate("")) +m = Map("app_filter", translate(""), translate("")) local v v = m:section(SimpleSection) -v.template = "admin_network/app_filter" +v.template = "oaf/app_filter/rules" return m diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/time.lua b/luci-app-oaf/luasrc/model/cbi/oaf/dashboard.lua similarity index 82% rename from luci-app-oaf/luasrc/model/cbi/appfilter/time.lua rename to luci-app-oaf/luasrc/model/cbi/oaf/dashboard.lua index baa1afca..31947d2a 100644 --- a/luci-app-oaf/luasrc/model/cbi/appfilter/time.lua +++ b/luci-app-oaf/luasrc/model/cbi/oaf/dashboard.lua @@ -11,9 +11,9 @@ local jsc = require "luci.jsonc" local m, s arg[1] = arg[1] or "" -m = Map("appfilter", translate(""), translate("")) +m = Map("fwx_dashboard", translate(""), translate("")) local v v = m:section(SimpleSection) -v.template = "admin_network/time" +v.template = "oaf/dashboard" return m diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/dev_status.lua b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/mac_filter.lua similarity index 65% rename from luci-app-oaf/luasrc/model/cbi/appfilter/dev_status.lua rename to luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/mac_filter.lua index 90d64024..a48a8a59 100644 --- a/luci-app-oaf/luasrc/model/cbi/appfilter/dev_status.lua +++ b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/mac_filter.lua @@ -11,11 +11,10 @@ local jsc = require "luci.jsonc" local m, s arg[1] = arg[1] or "" -m = Map("appfilter", translate("Data Statistics") .. "(" .. arg[1] .. ")", translate("")) +m = Map("macfilter", translate(""), translate("")) + local v v = m:section(SimpleSection) -v.template = "admin_network/dev_status" -v.mac = arg[1] -m.redirect = luci.dispatcher.build_url("admin", "services", "appfilter") +v.template = "oaf/mac_filter/mac_filter" return m diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/advance.lua b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/rules.lua similarity index 82% rename from luci-app-oaf/luasrc/model/cbi/appfilter/advance.lua rename to luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/rules.lua index 966950b8..f39c0dc1 100644 --- a/luci-app-oaf/luasrc/model/cbi/appfilter/advance.lua +++ b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/rules.lua @@ -11,9 +11,9 @@ local jsc = require "luci.jsonc" local m, s arg[1] = arg[1] or "" -m = Map("appfilter", translate(""), translate("")) +m = Map("macfilter", translate(""), translate("")) local v v = m:section(SimpleSection) -v.template = "admin_network/advance" +v.template = "oaf/mac_filter/rules" return m diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/user.lua b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/user.lua similarity index 80% rename from luci-app-oaf/luasrc/model/cbi/appfilter/user.lua rename to luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/user.lua index 29c02409..2719dbd0 100644 --- a/luci-app-oaf/luasrc/model/cbi/appfilter/user.lua +++ b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/user.lua @@ -11,9 +11,9 @@ local jsc = require "luci.jsonc" local m, s arg[1] = arg[1] or "" -m = Map("appfilter", translate(""), translate("")) +m = Map("macfilter", translate(""), translate("")) local v v = m:section(SimpleSection) -v.template = "admin_network/user" -return m \ No newline at end of file +v.template = "oaf/mac_filter/user" +return m diff --git a/luci-app-oaf/luasrc/model/cbi/oaf/user_detail.lua b/luci-app-oaf/luasrc/model/cbi/oaf/user_detail.lua new file mode 100644 index 00000000..04aae98b --- /dev/null +++ b/luci-app-oaf/luasrc/model/cbi/oaf/user_detail.lua @@ -0,0 +1,9 @@ +local ds = require "luci.dispatcher" +local m, s + +m = Map("appfilter", translate(""), translate("")) + +m:section(SimpleSection).template = "oaf/user_detail" + +return m + diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/user_list.lua b/luci-app-oaf/luasrc/model/cbi/oaf/user_list.lua similarity index 87% rename from luci-app-oaf/luasrc/model/cbi/appfilter/user_list.lua rename to luci-app-oaf/luasrc/model/cbi/oaf/user_list.lua index 6a0366b2..1a458534 100644 --- a/luci-app-oaf/luasrc/model/cbi/appfilter/user_list.lua +++ b/luci-app-oaf/luasrc/model/cbi/oaf/user_list.lua @@ -14,6 +14,6 @@ local m, s m = Map("appfilter", translate(""), translate("")) -m:section(SimpleSection).template = "admin_network/user_status" +m:section(SimpleSection).template = "oaf/user_status" return m diff --git a/luci-app-oaf/luasrc/model/cbi/oaf/whitelist.lua b/luci-app-oaf/luasrc/model/cbi/oaf/whitelist.lua new file mode 100644 index 00000000..ec7c8886 --- /dev/null +++ b/luci-app-oaf/luasrc/model/cbi/oaf/whitelist.lua @@ -0,0 +1,15 @@ +local nfs = require "nixio.fs" +local config = "fwx_record" + +if nfs.access("/etc/config/appfilter") then + config = "appfilter" +elseif nfs.access("/etc/config/macfilter") then + config = "macfilter" +end + +local m = Map(config, translate(""), translate("")) +local s = m:section(SimpleSection) + +s.template = "oaf/whitelist" + +return m \ No newline at end of file diff --git a/luci-app-oaf/luasrc/view/admin_network/advance.htm b/luci-app-oaf/luasrc/view/admin_network/advance.htm deleted file mode 100644 index 26618fc4..00000000 --- a/luci-app-oaf/luasrc/view/admin_network/advance.htm +++ /dev/null @@ -1,321 +0,0 @@ - - - - - - - -
-
- -
- - -- -
- -
- - -- -
- -
- - -
- -
- <%:If the OAF driver cannot be manually unloaded or the current driver is unstable, you can turn off auto-loading at startup and manually install a suitable driver. It is recommended to use the official stable OpenWrt firmware.%> -
- -
- - -
- -
- <%:Disable hardware acceleration (HNAT/ECM) and flow offloading to ensure application filtering works correctly. When enabled, this will automatically disable hardware acceleration and flow offloading features.%> -
- -
- - -
- -
- <%:The name of the LAN interface, used for detecting client info, supports fuzzy matching, but a complete interface name must be specified in bypass mode %> -
- - -
- - -
- -
- - -
- -
- - -
-
-
diff --git a/luci-app-oaf/luasrc/view/admin_network/app_filter.htm b/luci-app-oaf/luasrc/view/admin_network/app_filter.htm deleted file mode 100644 index 16c947ec..00000000 --- a/luci-app-oaf/luasrc/view/admin_network/app_filter.htm +++ /dev/null @@ -1,1549 +0,0 @@ - - - - - - - - -
-
- - -
-
- <%:App Filter is a powerful parental control software%> - - ? - - <%:OAF is now relatively stable. If the test fails, please disable ad filtering, proxy, acceleration, and other modules. If you are unsure which modules are conflicting, it is recommended to reset the device and then disable the acceleration module in the firewall for testing. If only some Apps are not working, you need to update the App feature library, as the filtering effect is related to the App feature library.%> - - -
- -
- - <%:Running%> - -
- - - - - - -
- - -
-
- - <%:Basic Settings%> -
-
-
-
- - -
- -
- - -
-
- -
- - -
- - - -
-
- - -
- -
- - -
-
-
-
- - -
-
- - <%:Filter Rules%> -
-
-
- -
- - -
-
- - - -
-
- - -
- -
- <%:Some Apps use encrypted QUIC protocol, which cannot be distinguished for filtering, Such as Youtube, Instagram, etc. For better filtering results, you can try disabling the QUIC protocol.%> -
- -
- <%:App Selection%> - (<%:Selected %> 0 <%:items%>) -
- -
- <%:If the App you want is not in the list, you can upgrade the feature library of the official website or customize the App%> -
- -
-
-
-
- -
- - -
-
-
- - -
-
-
-
<%:Time Configuration%>
- × -
-
-
-
- - -
-
-
-
<%:Effective Users%>
- × -
-
-
-
diff --git a/luci-app-oaf/luasrc/view/admin_network/dev_status.htm b/luci-app-oaf/luasrc/view/admin_network/dev_status.htm deleted file mode 100644 index 854334ad..00000000 --- a/luci-app-oaf/luasrc/view/admin_network/dev_status.htm +++ /dev/null @@ -1,394 +0,0 @@ - - - - - - - - - - -
-
-
-
-
- - - - - - - - - - - - -
- <%:App Name%> - - <%:Hostname%> - - <%:Mac%> - - <%:Start Time%> - - <%:Visit Time%> - - <%:Filter Status%> -

- <%:Collecting data...%> -
- -
\ No newline at end of file diff --git a/luci-app-oaf/luasrc/view/admin_network/feature.htm b/luci-app-oaf/luasrc/view/admin_network/feature.htm deleted file mode 100755 index 22556986..00000000 --- a/luci-app-oaf/luasrc/view/admin_network/feature.htm +++ /dev/null @@ -1,347 +0,0 @@ - - -
- -
-
-
- <%:Current version%> - -- -
-
- <%:Feature format%> - -- -
-
- <%:App number%> - -- -
-
- <%:Feature download%> - www.openappfilter.com -
-
- - -
-
- -
- - -
-
-
- <%:You can download the App feature library from the website(www.openappfilter.com),then upload it here.Please note that after downloading, you need to extract the files and select the one in .bin format.%> -
- -
- -
-
- -
-
-
- -
- -
-
- - - - - - - diff --git a/luci-app-oaf/luasrc/view/admin_network/time.htm b/luci-app-oaf/luasrc/view/admin_network/time.htm deleted file mode 100644 index cd58858f..00000000 --- a/luci-app-oaf/luasrc/view/admin_network/time.htm +++ /dev/null @@ -1,1114 +0,0 @@ - - - - - - - - - -
- - -
- -
-

<%:Time Rules%>

- - - -
- - -
- -
- - - - - - - - - - - - - -
<%:Weekdays%><%:Start Time%><%:End Time%><%:Actions%>
-
- -
- - - - -
- -
- - -
- -
-
\ No newline at end of file diff --git a/luci-app-oaf/luasrc/view/admin_network/user.htm b/luci-app-oaf/luasrc/view/admin_network/user.htm deleted file mode 100644 index 7f07a40e..00000000 --- a/luci-app-oaf/luasrc/view/admin_network/user.htm +++ /dev/null @@ -1,768 +0,0 @@ - - - - - -
-
-
- - -
- - -
- <%:In manual mode, only the following added terminals are controlled%> -
- - - - - - - - - - - - - - - - - - -
- - -
- -
- -
- -
-
- -
-
-

<%:Select Device%>

-
-
-
- - -
-
-
- -
-
-

<%:Select Device for Whitelist%>

-
-
-
- - -
-
-
- - diff --git a/luci-app-oaf/luasrc/view/admin_network/user_status.htm b/luci-app-oaf/luasrc/view/admin_network/user_status.htm deleted file mode 100644 index 5b43f69b..00000000 --- a/luci-app-oaf/luasrc/view/admin_network/user_status.htm +++ /dev/null @@ -1,1537 +0,0 @@ - -<% local dsp=require "luci.dispatcher" -%> - - - - - -
- - -
- -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - -
- <%:Device Info%> - - <%:IP Address%> - - <%:Up Rate%> - - <%:Down Rate%> - - <%:Today Active Time%> - - <%:Today Flow%> - - <%:Common App(TOP5)%> - - <%:Active App%> - - <%:Current URL%> - - <%:Online Status%> - - <%:Actions%> -

- <%:Collecting data...%> -
- -
- - - - - -
-
- -
-
- -

<%:Device Details%>

- - -
    - -
  • <%:App Statistics%>
  • -
  • <%:Access Records%>
  • -
- -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - -
- <%:App Name%> - - <%:Start Visit Time%> - - <%:Last Visit Time%> - - <%:Duration%> - - <%:Filter Status%> -

- <%:Collecting data...%> -
-
- - -
-
- -
-
- - -
-
- - -

<%:Modify Remark%>

- -
-

<%:MAC Address%>: --

-
- - - -
-

<%:Remark%>:

- -
- -
- - -
-
-
- - - diff --git a/luci-app-oaf/luasrc/view/cbi/oaf_dvalue.htm b/luci-app-oaf/luasrc/view/cbi/oaf_dvalue.htm deleted file mode 100644 index adf6a2b3..00000000 --- a/luci-app-oaf/luasrc/view/cbi/oaf_dvalue.htm +++ /dev/null @@ -1,10 +0,0 @@ -<%+cbi/valueheader%> - -<% - local val = self:cfgvalue(section) or self.default or "" - write(pcdata(val)) -%> - - - -<%+cbi/valuefooter%> diff --git a/luci-app-oaf/luasrc/view/cbi/oaf_upload.htm b/luci-app-oaf/luasrc/view/cbi/oaf_upload.htm deleted file mode 100644 index bdbae284..00000000 --- a/luci-app-oaf/luasrc/view/cbi/oaf_upload.htm +++ /dev/null @@ -1,32 +0,0 @@ -<%+cbi/valueheader%> - -
- - - -
- - -
- <%:Feature library files can be downloaded from the official website. After downloading, upload to upgrade. Note the feature code format version, which needs to be consistent with the current feature code format!%> -
-<%+cbi/valuefooter%> - - - - diff --git a/luci-app-oaf/luasrc/view/oaf/.dashboard.htm.swp b/luci-app-oaf/luasrc/view/oaf/.dashboard.htm.swp new file mode 100644 index 00000000..c0245d23 Binary files /dev/null and b/luci-app-oaf/luasrc/view/oaf/.dashboard.htm.swp differ diff --git a/luci-app-oaf/luasrc/view/oaf/about.htm b/luci-app-oaf/luasrc/view/oaf/about.htm new file mode 100644 index 00000000..7d5ecb48 --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/about.htm @@ -0,0 +1,128 @@ +<%+header%> + + +
+
+
OpenAppFilter
+

+ <%:OAF is a powerful parental control software. FanchmWrt integrates OAF by default and includes more plugins developed by the author.%> +

+ +
+
+
<%:Author%>
+
destan19(TT)
+
+
+
OAF
+ +
+
+
FanchmWrt
+ +
+
+
<%:GitHub Source%>
+ +
+
+ + +
+
+ + + +<%+footer%> diff --git a/luci-app-oaf/luasrc/view/oaf/advanced.htm b/luci-app-oaf/luasrc/view/oaf/advanced.htm new file mode 100644 index 00000000..6e5bcca9 --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/advanced.htm @@ -0,0 +1,462 @@ +<% +local nfs = require "nixio.fs" +local has_app_filter = nfs.access("/etc/config/appfilter") +local has_access_control = nfs.access("/etc/config/macfilter") +%> +<%+header%> + + + + +
+
<%:Settings%>
+ +
+ +
+
+ + +
+ +
    +
  1. <%:Bypass mode cannot count downstream traffic because traffic is directly forwarded to terminals at Layer 2 through the gateway.%>
  2. +
  3. <%:In bypass mode, it is recommended to disable IPv6 on terminals to prevent terminals from forwarding directly to the main router through IPv6.%>
  4. +
+ +
+ +
+ +
+
<%:LAN interface name for terminal detection. Default is bridge interface (br-lan). If LAN port is changed to physical interface, please modify to the corresponding name, such as eth0.%>
+
+
+ +
+ +
+ +
<%:After enabled, OAF sends TCP RST for filtered TCP connections.%>
+
+
+ + <% if has_app_filter then %> +
+ + +
+ <% end %> + + <% if has_access_control then %> +
+ + +
+ <% end %> +
+
+ +
+ +
+
+ + +
+ + + +
+ +
+ +
<%:Internet record retention time%>
+
+
+ +
+ +
+ +
<%:App records minimum duration%>
+
+
+ +
+ +
+
+ + MB +
+
<%:History data size limit%>
+
+
+ +
+ +
+ +
<%:User's App record data will be stored in this directory.%>
+
+
+ +
+ +
+ +
<%:The user's basic data and real-time data are stored in this directory, such as online duration, traffic usage and other information. The default is a temporary directory and will not be retained after reboot. You can change it to a persistent directory, such as /etc/fwx.%>
+
+
+
+
+ +
+ +
+
+ +
+ +
<%:After enabled, related acceleration modules will be automatically disabled, including hardware acceleration, software acceleration and others, to prevent filtering failures caused by acceleration. It is recommended to restart the device after modification.%>
+
+
+
+
+ +
+ + +
+
+
+ + + + + +<%+footer%> diff --git a/luci-app-oaf/luasrc/view/oaf/app_filter/rules.htm b/luci-app-oaf/luasrc/view/oaf/app_filter/rules.htm new file mode 100644 index 00000000..d8f8f6cc --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/app_filter/rules.htm @@ -0,0 +1,2307 @@ + + + + + + + + + +
+
+

<%:Filter Rules%>

+ +
+ +
+ + + <%:OAF already supports mobile app management. Mobile management is more convenient.%> + <%:Go to download%> + +
+ +
+
+
+ +
+ +
diff --git a/luci-app-oaf/luasrc/view/oaf/app_record.htm b/luci-app-oaf/luasrc/view/oaf/app_record.htm new file mode 100644 index 00000000..1c1e83ee --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/app_record.htm @@ -0,0 +1,437 @@ +<%+header%> + + + + +
+
+
    +
  • <%:Online Records%>
  • +
  • <%:History Records%>
  • +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
<%:App%>MAC<%:Hostname%><%:Nickname%><%:First Visit%><%:Last Visit%><%:Duration%><%:Online%><%:Filter Status%>

<%:Loading...%>
+
+
+ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%:App%>MAC<%:Hostname%><%:Nickname%><%:First Visit%><%:Last Visit%><%:Duration%><%:Online%><%:Filter Status%>

<%:Loading...%>
+
+
+
+
+ + + + +<%+footer%> diff --git a/luci-app-oaf/luasrc/view/oaf/dashboard.htm b/luci-app-oaf/luasrc/view/oaf/dashboard.htm new file mode 100644 index 00000000..74e06797 --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/dashboard.htm @@ -0,0 +1,4178 @@ + + + + + + + +
+
+
+
+ <%:Tip%>: + <%:Some modules may affect filtering and identification. If it does not work, please disable related functions such as software and hardware acceleration, ad filtering, mwan3, QoS, etc.%> +
+ +
+
+
+
+ <%:Online App%> + +
+
0
+ +
<%:Online App%>:0
+
<%:Offline App%>:0
+
+
+
+
+ <%:Online User%> + +
+
0
+ +
<%:Online User%>:0
+
<%:Offline User%>:0
+
+
+
+
+ <%:Filter Rules%> + +
+
0
+ +
<%:App Filter Rules%>:0
+
<%:Access Control Rules%>:0
+
+
+
+
+ <%:Today Traffic%> + +
+
0MB
+
+
+ +
+ + + +
+
+
<%:Traffic Statistics%>
+ + + +
+
+
+
+
<%:Visiting%>
+
+
+
+
+
<%:App Duration Statistics%>
+ + + +
+
+
+
+
<%:User Traffic Top8%>
+
+
+ +
+
+ +
+
+
<%:OAF Status%>
+
+
+ <%:Work Mode%> + -- +
+
+ <%:Service Version%> + -- +
+
+ <%:Engine Version%> + -- +
+
+ <%:Feature Library%> + -- +
+
+ <%:App Record Count%> + -- +
+
+
+ +
+
<%:Active Terminals%>
+
+
+ + +
+
<%:Active URL%>
+
+
+
+
+ + diff --git a/luci-app-oaf/luasrc/view/oaf/feature.htm b/luci-app-oaf/luasrc/view/oaf/feature.htm new file mode 100644 index 00000000..52579d21 --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/feature.htm @@ -0,0 +1,295 @@ +<%+header%> + + + + + +
+
+
<%:App Feature Library%>
+
<%:The feature library is used to describe app features, and determines the effectiveness of app filtering and internet usage records%>
+
+ +
    +
  • <%:Basic Info%>
  • +
  • <%:Online Update%>
  • +
  • <%:Custom Applications%>
  • +
+ +
+
+
+
+
+ <%:Current version%> + -- +
+
+ <%:Feature Library Type%> + -- +
+
+ <%:Feature format%> + -- +
+
+ <%:App number%> + -- +
+
+ <%:Official Website%> + www.openappfilter.com +
+
+
+ +
+
<%:App List%>
+
+
+
+ +
+
+
<%:Online Feature Library Update%>
+
<%:Visit%> www.openappfilter.com <%:to obtain the advanced feature library update Key.%>
+
+
+ +
+ + +
+
+ +
+
+
<%:When the Key is empty, the free version can be used. After setting the Key, the premium version can be used. The premium version supports more applications, is updated regularly, and the latest supported application list can be viewed on the official website.%>
+
+ + +
+ +
+
+
+
+ <%:Current Version Number%>: -- + <%:Feature Library Type%>: -- + <%:Feature format%>: v4.0 +
+ + +
<%:Click Update List to load available feature libraries%>
+ +
+
+ +
+
+
+
<%:Custom Applications%>
+ +
+
<%:Loading...%>
+ + +
+
+
+
+ + + + + + +<%+footer%> diff --git a/luci-app-oaf/luasrc/view/oaf/mac_filter/rules.htm b/luci-app-oaf/luasrc/view/oaf/mac_filter/rules.htm new file mode 100644 index 00000000..b682c88f --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/mac_filter/rules.htm @@ -0,0 +1,2135 @@ + + + + + + + + +
+
+

<%:Filter Rules%>

+ +
+ +
+
+
+ +
+ +
diff --git a/luci-app-oaf/luasrc/view/oaf/mac_filter/user.htm b/luci-app-oaf/luasrc/view/oaf/mac_filter/user.htm new file mode 100644 index 00000000..b4d45ccd --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/mac_filter/user.htm @@ -0,0 +1,495 @@ + + + + + +
+
+
+ <%:Auto mode%> + <%:Manual mode%> +
+ +
+ <%:In manual mode, only specified devices will be managed%> +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
<%:Whitelist%>
+
+
+
<%:MAC%>
+
<%:Hostname%>
+
<%:Device nickname%>
+
<%:Operation%>
+
+
+
+ + diff --git a/luci-app-oaf/luasrc/view/oaf/user_detail.htm b/luci-app-oaf/luasrc/view/oaf/user_detail.htm new file mode 100644 index 00000000..298829d9 --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/user_detail.htm @@ -0,0 +1,2052 @@ + +<% local dsp=require "luci.dispatcher" -%> + + + + + + +
+
+
+ + +
    +
  • <%:Basic Info%>
  • +
  • <%:Parental Control Rules%>
  • +
  • <%:App Statistics%>
  • +
  • <%:Today Traffic%>
  • +
  • <%:Today Top Apps%>
  • + +
  • <%:Visit Records%>
  • +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%:MAC Address%>--<%:Online Status%>--
<%:Hostname%>--<%:Remark%> +
+ -- + + + +
+
<%:IP Address%>--<%:IPv6%>--
<%:Today Internet Duration%>--<%:Today Online Duration%>--
<%:Today Upstream Traffic%>--<%:Today Downstream Traffic%>--
<%:Band%>--<%:Wireless Interface%>--
<%:Wireless Tx Rate%>--<%:Wireless Rx Rate%>--
+
+
+ +
+
+ + + + + + + + + + + + + + + +
<%:Function Module%><%:Effective Condition%><%:Today's Status%><%:Internet Permission%>
<%:Collecting data...%>
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+ + + + + + + + + + + + +
+ <%:App Name%> + + <%:First Visit%> + + <%:Last Visit%> + + <%:Duration%> + + <%:Online Status%> + + <%:Filter Status%> +

+ <%:Collecting data...%> +
+
+
+
+
+
+
+ +
+ + + + diff --git a/luci-app-oaf/luasrc/view/oaf/user_status.htm b/luci-app-oaf/luasrc/view/oaf/user_status.htm new file mode 100644 index 00000000..e9ef2a3a --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/user_status.htm @@ -0,0 +1,1974 @@ + +<% local dsp=require "luci.dispatcher" -%> + + + + + + + +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ <%:Device%> + + <%:IP%> + + <%:Up Rate%> + + <%:Down Rate%> + + <%:Net Traffic%> + + <%:Net Time%> + + <%:Apps%> + + <%:URL%> + + <%:Permission%> + + <%:Status%> + + <%:Actions%> +

+ <%:Collecting data...%> +
+ +
+ + + + + +
+
+ + + + + + + + diff --git a/luci-app-oaf/luasrc/view/oaf/whitelist.htm b/luci-app-oaf/luasrc/view/oaf/whitelist.htm new file mode 100644 index 00000000..20bb0fb5 --- /dev/null +++ b/luci-app-oaf/luasrc/view/oaf/whitelist.htm @@ -0,0 +1,672 @@ +<% +local dsp = require "luci.dispatcher" +local nfs = require "nixio.fs" +local has_app_filter = nfs.access("/etc/config/appfilter") +local has_access_control = nfs.access("/etc/config/macfilter") +local has_record = nfs.access("/etc/config/fwx_record") +%> + + + + + + + + +
+
+ <%:Users in the whitelist are not subject to control. For terminals using random MAC addresses, you can use the whitelist mechanism together with filtering rules: select All Users in a filtering rule, and the rule will apply to all new users.%> +
+ +
+ <% if has_app_filter then %> + + <% end %> + <% if has_access_control then %> + + <% end %> + <% if has_record then %> + + <% end %> +
+ + <% if has_app_filter then %> +
+
+

<%:App Filter Whitelist%>

+ +
+
+ + + +
<%:No.%><%:MAC Address%><%:Hostname%><%:Nickname%><%:Operation%>
<%:Loading...%>
+
+
+ <% end %> + + <% if has_access_control then %> +
+
+

<%:Access Control Whitelist%>

+ +
+
+ + + +
<%:No.%><%:MAC Address%><%:Hostname%><%:Nickname%><%:Operation%>
<%:Loading...%>
+
+
+ <% end %> + + <% if has_record then %> +
+
+

<%:Record Whitelist%>

+ +
+
+ + + +
<%:No.%><%:MAC Address%><%:Hostname%><%:Nickname%><%:Operation%>
<%:Loading...%>
+
+
+ <% end %> +
+ +
+
+
<%:Add to Whitelist%>
+
+ + <%:Selected%>: 0 / 0 +
+
+
+ + +
+
+
+ +
+
+

+
+
diff --git a/luci-app-oaf/po/zh_Hans/oaf.po b/luci-app-oaf/po/zh_Hans/oaf.po index d0b30ae1..488464c4 100644 --- a/luci-app-oaf/po/zh_Hans/oaf.po +++ b/luci-app-oaf/po/zh_Hans/oaf.po @@ -126,6 +126,17 @@ msgstr "刷新" msgid "Visiting" msgstr "正在访问" +msgid "Band" +msgstr "频段" + +msgid "Wireless Interface" +msgstr "无线接口" + +msgid "Wireless Tx Rate" +msgstr "无线发送速率" + +msgid "Wireless Rx Rate" +msgstr "无线接收速率" msgid "Update the feature file successfully, please refresh the page" msgstr "更新特征库成功,请刷新页面!" @@ -506,6 +517,9 @@ msgstr "暂无用户" msgid "Users in whitelist are not controlled" msgstr "白名单中的用户不受管控" +msgid "Users in the whitelist are not subject to control. For terminals using random MAC addresses, you can use the whitelist mechanism together with filtering rules: select All Users in a filtering rule, and the rule will apply to all new users." +msgstr "白名单中的用户不受管控。对于使用随机MAC地址的终端,可以通过白名单机制配合过滤规则实现控制:在过滤规则中选择全部用户,该规则会针对所有新用户生效。" + msgid "Whitelist Users" msgstr "白名单用户" @@ -817,3 +831,1131 @@ msgstr "禁用硬件加速(HNAT/ECM)和流加速,以确保应用过滤功 msgid "Current hardware acceleration module is not disabled, which may affect filtering functionality. For better filtering results, please disable the acceleration module in Advanced Settings." msgstr "当前未关闭加速模块,可能影响过滤功能,为了更好的过滤效果,请在高级设置中关闭加速模块。" + +msgid "Version" +msgstr "版本" + +msgid "Service Version" +msgstr "服务版本" + +msgid "OAF Status" +msgstr "OAF状态" + +msgid "Engine Version" +msgstr "引擎版本" + +msgid "Internet Audit" +msgstr "上网审计" + +msgid "Feature Library" +msgstr "特征库" + +msgid "Feature Library Type" +msgstr "特征库类型" + +msgid "Feature Format" +msgstr "特征码格式" + +msgid "App Count" +msgstr "应用个数" + +msgid "App Record Count" +msgstr "应用记录数" + +msgid "Enabled" +msgstr "开启" + +msgid "Disabled" +msgstr "关闭" + +msgid "Lite" +msgstr "Lite" + +msgid "Plus" +msgstr "Plus" + +msgid "Free" +msgstr "免费版" + +msgid "Premium" +msgstr "高级版" + +msgid "Last 30 Days Traffic" +msgstr "近30天流量" + +msgid "Traffic Statistics" +msgstr "流量统计" + +msgid "Today" +msgstr "今日" + +msgid "Last 30 Days" +msgstr "近30天" + +msgid "Active Terminals" +msgstr "活跃终端" + +msgid "Active Users" +msgstr "活跃用户" + +msgid "Online App" +msgstr "在线应用" + +msgid "Offline App" +msgstr "离线应用" + +msgid "Online User" +msgstr "在线用户" + +msgid "Offline User" +msgstr "离线用户" + +msgid "Access Control Rules" +msgstr "上网控制规则" + +msgid "Access Time" +msgstr "访问时间" + +msgid "Active Apps" +msgstr "活跃APP" + +msgid "Active Terminals Top8" +msgstr "活跃终端Top8" + +msgid "Active URL" +msgstr "活跃URL" + +msgid "App Duration Statistics" +msgstr "App时长统计" + +msgid "App ID" +msgstr "APP ID" + +msgid "App Usage Distribution" +msgstr "APP 使用时长分布" + +msgid "Boot Space" +msgstr "Boot空间" + +msgid "Close" +msgstr "关闭" + +msgid "Connections" +msgstr "连接数" + +msgid "Current App" +msgstr "当前APP" + +msgid "Day" +msgstr "天" + +msgid "Destination IP" +msgstr "目的IP地址" + +msgid "Destination Port" +msgstr "目的端口" + +msgid "Device" +msgstr "终端" + +msgid "Device Model" +msgstr "设备型号" + +msgid "Disk Space" +msgstr "磁盘空间" + +msgid "DNS" +msgstr "DNS" + +msgid "Downstream" +msgstr "下行" + +msgid "Downstream Rate" +msgstr "下行速率" + +msgid "Downstream Traffic" +msgstr "下行流量" + +msgid "Duplex" +msgstr "双工" + +msgid "Expanded" +msgstr "扩容版" + +msgid "Firmware Version" +msgstr "固件版本" + +msgid "Gateway" +msgstr "网关" + +msgid "Got it" +msgstr "知道了" + +msgid "Hour" +msgstr "小时" + +msgid "IP" +msgstr "IP" + +msgid "Internet Duration" +msgstr "上网时长" + +msgid "Internet Traffic" +msgstr "上网流量" + +msgid "Kernel Version" +msgstr "内核版本" + +msgid "Last Hour" +msgstr "近1小时" + +msgid "Mask" +msgstr "掩码" + +msgid "Matched Domain" +msgstr "匹配域名" + +msgid "Memory" +msgstr "内存" + +msgid "Minute" +msgstr "分" + +msgid "Name" +msgstr "名称" + +msgid "Network" +msgstr "网络" + +msgid "Net Time" +msgstr "上网时长" + +msgid "Net Traffic" +msgstr "上网流量" + +msgid "No Data" +msgstr "暂无数据" + +msgid "Port Status" +msgstr "网口状态" + +msgid "Protocol" +msgstr "协议" + +msgid "Permission" +msgstr "权限" + +msgid "Quick Guide" +msgstr "设置向导" + +msgid "Real-time Traffic" +msgstr "实时流量" + +msgid "Rx Bytes" +msgstr "接收字节" + +msgid "Rx Error Packets" +msgstr "接收错误包数" + +msgid "Rx Packets" +msgstr "接收包数" + +msgid "Some modules may affect filtering and identification. If it does not work, please disable related functions such as software and hardware acceleration, ad filtering, mwan3, QoS, etc." +msgstr "某些模块可能会影响过滤和识别。如果不生效,请关闭软件/硬件加速、广告过滤、mwan3、QoS 等相关功能。" + +msgid "Source IP" +msgstr "源IP地址" + +msgid "Source Port" +msgstr "源端口" + +msgid "Speed" +msgstr "速率" + +msgid "Status" +msgstr "状态" + +msgid "System Info" +msgstr "系统信息" + +msgid "Temp Space" +msgstr "临时空间" + +msgid "Temperature" +msgstr "温度" + +msgid "Tip" +msgstr "提示" + +msgid "Tip: Click here to switch modes." +msgstr "提示:点击这里可以切换模式。" + +msgid "Today Traffic" +msgstr "今日流量" + +msgid "Tx Bytes" +msgstr "发送字节" + +msgid "Tx Error Packets" +msgstr "发送错误包数" + +msgid "URL" +msgstr "URL" + +msgid "Tx Packets" +msgstr "发送包数" + +msgid "Unknown" +msgstr "未知" + +msgid "Upload" +msgstr "上传" + +msgid "Upstream" +msgstr "上行" + +msgid "Upstream Rate" +msgstr "上行速率" + +msgid "Upstream Traffic" +msgstr "上行流量" + +msgid "Uptime" +msgstr "运行时间" + +msgid "User" +msgstr "用户" + +msgid "User Traffic Top8" +msgstr "用户流量Top8" + +msgid "Access Control" +msgstr "上网控制" + +msgid "Added to blacklist" +msgstr "已加入黑名单" + +msgid "All Users" +msgstr "全部用户" + +msgid "App Restricted" +msgstr "应用受限" + +msgid "Are you sure you want to join the blacklist? After joining, this terminal will be disconnected from the Internet." +msgstr "确定要加入黑名单吗?加入后该终端将会断网。" + +msgid "Back" +msgstr "返回" + +msgid "Back to User List" +msgstr "返回终端列表" + +msgid "Blacklist" +msgstr "黑名单" + +msgid "Block" +msgstr "拉黑" + +msgid "Collecting data..." +msgstr "正在采集数据..." + +msgid "Common App" +msgstr "常用APP" + +msgid "Confirm" +msgstr "确认" + +msgid "Current Status" +msgstr "当前状态" + +msgid "Daily Duration" +msgstr "每日时长" + +msgid "Daily Flow" +msgstr "每日流量" + +msgid "Detail" +msgstr "详情" + +msgid "Disconnected" +msgstr "未联网" + +msgid "Edit Remark" +msgstr "修改备注" + +msgid "Effective Condition" +msgstr "生效条件" + +msgid "Failed to update remark" +msgstr "备注修改失败" + +msgid "First Visit" +msgstr "开始访问时间" + +msgid "Flow" +msgstr "流量" + +msgid "Function Module" +msgstr "功能模块" + +msgid "Hit Detail" +msgstr "命中详情" + +msgid "Internet Permission" +msgstr "联网权限" + +msgid "Internet Permission Detail" +msgstr "联网权限详情" + +msgid "IPv6" +msgstr "IPv6" + +msgid "Joined Internet Blacklist" +msgstr "命中上网黑名单" + +msgid "Last Visit" +msgstr "最后访问时间" + +msgid "Limit" +msgstr "限制" + +msgid "Matched" +msgstr "已匹配" + +msgid "Matched app filter whitelist, all app filter rules are ineffective." +msgstr "匹配到应用过滤白名单,所有应用过滤规则不生效。" + +msgid "Matched MAC filter whitelist, all MAC filter rules are ineffective." +msgstr "匹配到MAC过滤白名单,所有MAC过滤规则不生效。" + +msgid "Matched Rule" +msgstr "匹配规则" + +msgid "Missing MAC parameter" +msgstr "缺少MAC参数" + +msgid "Mode" +msgstr "模式" + +msgid "Next Page" +msgstr "下一页" + +msgid "No app usage in this period" +msgstr "该时段无APP使用记录" + +msgid "No parental control rules" +msgstr "暂无行为管理规则" + +msgid "No records" +msgstr "暂无记录" + +msgid "Not effective today" +msgstr "今日不生效" + +msgid "Not Matched" +msgstr "未匹配" + +msgid "Offline Event" +msgstr "离线" + +msgid "Online Event" +msgstr "上线" + +msgid "Online/Offline Records" +msgstr "上下线记录" + +msgid "Operation failed" +msgstr "操作失败" + +msgid "Operation succeeded" +msgstr "设置成功" + +msgid "Parental Control Rules" +msgstr "行为管理规则" + +msgid "Prev Page" +msgstr "上一页" + +msgid "Rate" +msgstr "速率" + +msgid "Records" +msgstr "条记录" + +msgid "Remark updated successfully" +msgstr "备注修改成功" + +msgid "Removed from blacklist" +msgstr "已解除黑名单" + +msgid "Restricted Apps" +msgstr "受限应用" + +msgid "Rule" +msgstr "规则" + +msgid "Rule Detail" +msgstr "规则详情" + +msgid "Rule Name" +msgstr "规则名称" + +msgid "Rule Type" +msgstr "规则类型" + +msgid "s" +msgstr "秒" + +msgid "Selected User" +msgstr "指定终端" + +msgid "Session Count" +msgstr "会话数" + +msgid "Sessions" +msgstr "会话" + +msgid "Showing" +msgstr "显示" + +msgid "Signal" +msgstr "信号强度" + +msgid "Terminal" +msgstr "终端" + +msgid "Terminal Detail" +msgstr "终端详情" + +msgid "Thu" +msgstr "周四" + +msgid "Time" +msgstr "时间" + +msgid "Time Rule" +msgstr "时间规则" + +msgid "Today Downstream Traffic" +msgstr "今日下行流量" + +msgid "Today Internet Duration" +msgstr "今日上网时长" + +msgid "Today Online Duration" +msgstr "今日在线时长" + +msgid "Today Top Apps" +msgstr "今日常用APP" + +msgid "Today Top Apps Statistics (24 Hours)" +msgstr "今日常用APP统计(24小时)" + +msgid "Today Traffic Statistics (24 Hours)" +msgstr "今日流量统计(24小时)" + +msgid "Today Upstream Traffic" +msgstr "今日上行流量" + +msgid "Today's Status" +msgstr "今日状态" + +msgid "Traffic" +msgstr "流量" + +msgid "Type" +msgstr "类型" + +msgid "Unblock" +msgstr "解除" + +msgid "Unlimited today" +msgstr "今日不限" + +msgid "Unrestricted" +msgstr "无限制" + +msgid "User Detail" +msgstr "终端详情" + +msgid "Visit Records" +msgstr "访问记录" + +msgid "Visiting URL" +msgstr "正在访问URL" + +msgid "0 apps" +msgstr "0个应用" + +msgid "Access Control Whitelist" +msgstr "上网控制白名单" + +msgid "Add Duration Rule" +msgstr "添加时长规则" + +msgid "Add failed" +msgstr "添加失败" + +msgid "Add Flow Rule" +msgstr "添加流量规则" + +msgid "Add Rule" +msgstr "添加规则" + +msgid "Add success" +msgstr "添加成功" + +msgid "Add Time Rule" +msgstr "添加时间规则" + +msgid "App Filter Whitelist" +msgstr "应用过滤白名单" + +msgid "Apps" +msgstr "应用" + +msgid "Are you sure you want to delete this rule?" +msgstr "确定要删除这条规则吗?" + +msgid "Are you sure you want to remove this device from whitelist?" +msgstr "确定要从白名单中移除此设备吗?" + +msgid "Auto mode" +msgstr "自动模式" + +msgid "Daily Traffic" +msgstr "每日流量" + +msgid "Delete failed" +msgstr "删除失败" + +msgid "Delete success" +msgstr "删除成功" + +msgid "Device nickname" +msgstr "设备昵称" + +msgid "Disconnect Mode" +msgstr "断网模式" + +msgid "Duration (minutes)" +msgstr "时长(分钟)" + +msgid "Duration Rules" +msgstr "时长规则" + +msgid "Edit" +msgstr "编辑" + +msgid "Edit Rule" +msgstr "编辑规则" + +msgid "Enable Rule" +msgstr "启用规则" + +msgid "Enter rule name" +msgstr "输入规则名称" + +msgid "Failed to add rule" +msgstr "添加规则失败" + +msgid "Failed to delete rule" +msgstr "删除规则失败" + +msgid "Failed to update rule" +msgstr "更新规则失败" + +msgid "Filter QUIC" +msgstr "过滤QUIC协议" + +msgid "Flow (MB)" +msgstr "流量(MB)" + +msgid "Flow Rules" +msgstr "流量规则" + +msgid "Hover to view time chart" +msgstr "悬停查看时间图表" + +msgid "In auto mode, all new devices will be managed, including those with random MAC addresses" +msgstr "自动模式下,所有新设备都将被管理,包括随机MAC地址的设备" + +msgid "In manual mode, only specified devices will be managed" +msgstr "手动模式下,仅管理指定的设备" + +msgid "Loading app list..." +msgstr "加载应用列表..." + +msgid "MAC" +msgstr "MAC地址" + +msgid "Maximum 16 time rules allowed" +msgstr "最大只能添加16条规则" + +msgid "Maximum 32 rules allowed. Please delete some rules before adding new ones." +msgstr "最多只能添加32条规则,请先删除一些规则后再添加新规则。" + +msgid "Mode switched successfully" +msgstr "模式切换成功" + +msgid "Network error" +msgstr "网络错误" + +msgid "Nickname" +msgstr "昵称" + +msgid "No available devices to add" +msgstr "没有可添加的设备" + +msgid "No rules configured" +msgstr "未配置规则" + +msgid "No whitelist entries yet" +msgstr "暂无白名单条目" + +msgid "No." +msgstr "序号" + +msgid "Operation" +msgstr "操作" + +msgid "Please add at least one time rule" +msgstr "请至少添加一条时间规则" + +msgid "Please enter rule name" +msgstr "请输入规则名称" + +msgid "Please enter valid duration minutes" +msgstr "请输入有效的时长分钟数" + +msgid "Please enter valid flow MB" +msgstr "请输入有效的流量MB值" + +msgid "Please select a user" +msgstr "请选择用户" + +msgid "Please select at least one app" +msgstr "请至少选择一个应用" + +msgid "Please select at least one device" +msgstr "请至少选择一个设备" + +msgid "Remove" +msgstr "移除" + +msgid "Rule added successfully" +msgstr "规则添加成功" + +msgid "Rule deleted successfully" +msgstr "规则删除成功" + +msgid "Rule Mode" +msgstr "规则模式" + +msgid "Rule updated successfully" +msgstr "规则更新成功" + +msgid "Select Apps" +msgstr "选择应用" + +msgid "Select User" +msgstr "选择用户" + +msgid "Setting success" +msgstr "设置成功" + +msgid "Single User" +msgstr "单个用户" + +msgid "Some apps need QUIC disabled to take effect, such as taobao, youtube, etc. This may also affect other apps. Please choose this switch according to actual conditions." +msgstr "少部分应用需要禁用QUIC协议才会有效果,比如taobao,youtube等,注意也可能影响其他应用,根据实际情况选择开关" + +msgid "T" +msgstr "时间" + +msgid "Unknown error" +msgstr "未知错误" + +msgid "Whitelist" +msgstr "白名单" + +msgid "A maximum of 15 payload matches is allowed" +msgstr "每个特征最多允许15个负载匹配项" + +msgid "A maximum of 16 features is allowed" +msgstr "最多允许16个特征" + +msgid "A maximum of 64 custom applications is allowed" +msgstr "最多允许添加64个自定义应用" + +msgid "A valid subscription is required" +msgstr "Key没有权限" + +msgid "Add Application" +msgstr "添加应用" + +msgid "Add Feature" +msgstr "添加特征" + +msgid "After enabled, related acceleration modules will be automatically disabled, including hardware acceleration, software acceleration and others, to prevent filtering failures caused by acceleration. It is recommended to restart the device after modification." +msgstr "启用后会自动关闭相关的加速模块,包括硬件加速、软件加速等,以防止加速导致过滤失效。修改后建议重启设备。" + +msgid "App" +msgstr "应用" + +msgid "App records minimum duration" +msgstr "应用记录最小时长" + +msgid "App Valid Time" +msgstr "应用有效时长" + +msgid "Application Count" +msgstr "应用个数" + +msgid "Application Features" +msgstr "应用特征" + +msgid "Application Name" +msgstr "应用名称" + +msgid "Applying" +msgstr "正在应用" + +msgid "Are you sure you want to clean all history data? This action cannot be undone." +msgstr "确认要清理全部历史数据吗?该操作不可恢复。" + +msgid "Are you sure you want to delete this application?" +msgstr "确定要删除这个应用吗?" + +msgid "At least one match field must be configured for each feature" +msgstr "每个特征至少需要配置一个匹配字段" + +msgid "Auto Disable Acceleration" +msgstr "自动关闭加速" + +msgid "Base Data Path" +msgstr "基础数据目录" + +msgid "Base data path cannot be empty or /" +msgstr "基础数据目录不能为空或 /" + +msgid "Base data path maximum length is 64 characters" +msgstr "基础数据目录最大长度为 64 个字符" + +msgid "Bypass mode cannot count downstream traffic because traffic is directly forwarded to terminals at Layer 2 through the gateway." +msgstr "旁路模式无法统计下行流量,因为流量是通过网关直接二层转发到了终端。" + +msgid "Category" +msgstr "分类" + +msgid "Chinese" +msgstr "中文" + +msgid "Clean" +msgstr "清除" + +msgid "Clean failed" +msgstr "清理失败" + +msgid "Click Update List to get the latest feature libraries" +msgstr "点击更新列表可以获取最新的特征库" + +msgid "Click Update List to load available feature libraries" +msgstr "点击更新列表加载可用特征库" + +msgid "Current Version Number" +msgstr "当前版本号" + +msgid "Custom Applications" +msgstr "自定义应用" + +msgid "Data cleaned successfully" +msgstr "数据清理成功" + +msgid "days" +msgstr "天" + +msgid "Destination port must be a single port or port range between 1 and 65535" +msgstr "目的端口必须是1到65535之间的单个端口或端口范围" + +msgid "Domain" +msgstr "域名" + +msgid "Downloading" +msgstr "正在下载" + +msgid "Edit Application" +msgstr "编辑应用" + +msgid "Enable" +msgstr "启用" + +msgid "Enable Access Control" +msgstr "启用上网控制" + +msgid "English" +msgstr "英文" + +msgid "Extracting" +msgstr "正在解压" + +msgid "Feature" +msgstr "特征" + +msgid "Feature Count" +msgstr "特征数量" + +msgid "Feature library format error. Please use the feature library for this version." +msgstr "特征库格式错误,请使用对应版本的特征库" + +msgid "Feature library update failed" +msgstr "特征库更新失败" + +msgid "Feature library update timeout" +msgstr "特征库更新超时" + +msgid "Feature library updated successfully" +msgstr "特征库更新成功" + +msgid "Features" +msgstr "特征" + +msgid "Filter" +msgstr "过滤" + +msgid "Hide" +msgstr "隐藏" + +msgid "History Data Path" +msgstr "历史数据目录" + +msgid "History data path cannot be empty or /" +msgstr "历史数据目录不能为空或 /" + +msgid "History data path maximum length is 64 characters" +msgstr "历史数据目录最大长度为 64 个字符" + +msgid "History Data Size" +msgstr "历史数据大小" + +msgid "History data size limit" +msgstr "历史数据大小限制" + +msgid "History data size must be an integer between 1 and 1024 MB" +msgstr "历史数据大小必须是 1-1024 MB 的整数" + +msgid "History Data Usage" +msgstr "历史数据占用" + +msgid "History Records" +msgstr "历史记录" + +msgid "In bypass mode, it is recommended to disable IPv6 on terminals to prevent terminals from forwarding directly to the main router through IPv6." +msgstr "旁路模式下建议关闭终端的IPv6,防止终端走IPv6直接转发到主路由。" + +msgid "Internet record retention time" +msgstr "上网记录保留时间" + +msgid "Invalid application name" +msgstr "应用名称无效" + +msgid "Invalid domain" +msgstr "域名无效" + +msgid "Invalid LAN interface name" +msgstr "无效的LAN接口名称" + +msgid "Invalid payload match format" +msgstr "负载匹配格式无效" + +msgid "Invalid subscription key" +msgstr "Key无效" + +msgid "Invalid URI" +msgstr "URI无效" + +msgid "LAN interface name for terminal detection. Default is bridge interface (br-lan). If LAN port is changed to physical interface, please modify to the corresponding name, such as eth0." +msgstr "用于终端探测的LAN接口名称,系统默认为桥接接口(br-lan)。如果将LAN口修改为物理接口,请修改为对应的名称,例如eth0。" + +msgid "Language" +msgstr "语言" + +msgid "Leave blank for no restriction" +msgstr "留空表示不限制" + +msgid "Load failed" +msgstr "加载失败" + +msgid "Matches hexadecimal Layer 7 payload content at specified positions. Format: pos:value,pos:value, for example 00:0a,02:ab. Position 0 is the first byte." +msgstr "匹配七层协议内容中指定位置的十六进制值,格式为pos:value,pos:value,例如00:0a,02:ab,位置0表示第一个字节。" + +msgid "Official Website" +msgstr "官网" + +msgid "No apps" +msgstr "暂无应用" + +msgid "No custom applications" +msgstr "暂无自定义应用" + +msgid "Online Feature Library Update" +msgstr "在线特征库更新" + +msgid "Online Records" +msgstr "在线记录" + +msgid "Online Update" +msgstr "在线更新" + +msgid "Payload Match" +msgstr "负载匹配" + +msgid "Payload position must be a signed integer" +msgstr "负载位置必须是有符号整数" + +msgid "Please select a category" +msgstr "请选择分类" + +msgid "Position" +msgstr "位置" + +msgid "Record Time" +msgstr "记录时间" + +msgid "Release Date" +msgstr "发布日期" + +msgid "Reset" +msgstr "重置" + +msgid "Save failed" +msgstr "保存失败" + +msgid "Saved successfully" +msgstr "保存成功" + +msgid "Search" +msgstr "查询" + +msgid "Select TCP or UDP." +msgstr "选择TCP或UDP协议。" + +msgid "Settings" +msgstr "设置" + +msgid "Show" +msgstr "显示" + +msgid "Subscription service is unavailable" +msgstr "订阅服务暂不可用" + +msgid "Subscription Key" +msgstr "订阅Key" + +msgid "Supports a single destination port or port range, for example 80 or 80-443." +msgstr "支持单个目的端口或端口范围,例如80或80-443。" + +msgid "Switch" +msgstr "切换" + +msgid "Switched successfully" +msgstr "切换成功" + +msgid "TCP Connection Reset" +msgstr "TCP连接重置" + +msgid "After enabled, OAF sends TCP RST for filtered TCP connections." +msgstr "开启后,OAF会为命中过滤规则的TCP连接发送RST重置。" + +msgid "Terminal MAC" +msgstr "终端MAC" + +msgid "The downloaded file is too large" +msgstr "下载文件过大" + +msgid "The feature library is used to describe app features, and determines the effectiveness of app filtering and internet usage records" +msgstr "特征库用于描述应用特征,决定应用过滤和上网记录的效果" + +msgid "The HTTP request URI. HTTPS does not support URI matching." +msgstr "HTTP的请求URI,HTTPS不支持URI匹配。" + +msgid "The subscription device limit has been reached" +msgstr "订阅设备数超过限制" + +msgid "The user's basic data and real-time data are stored in this directory, such as online duration, traffic usage and other information. The default is a temporary directory and will not be retained after reboot. You can change it to a persistent directory, such as /etc/oaf." +msgstr "用户的基础数据和实时数据存放在该目录,比如上网时长、上网流量等信息,默认为临时目录重启不会保存,可以自行修改为持久目录,比如/etc/oaf等。" + +msgid "to obtain the advanced feature library update Key." +msgstr "可获取高级特征库更新Key" + +msgid "Too many requests. Please try again later." +msgstr "访问过于频繁,请稍后再试" + +msgid "Update List" +msgstr "更新列表" + +msgid "Updated successfully" +msgstr "更新成功" + +msgid "Updating list, please wait..." +msgstr "正在更新列表,请稍候..." + +msgid "Use this feature library version now?" +msgstr "确定立即使用该版本的特征库吗?" + +msgid "User's App record data will be stored in this directory." +msgstr "用户的应用记录会存储在该目录。" + +msgid "Validating" +msgstr "正在校验" + +msgid "Value" +msgstr "值" + +msgid "Visit" +msgstr "访问" + +msgid "When the Key is empty, the free version can be used. After setting the Key, the premium version can be used. The premium version supports more applications, is updated regularly, and the latest supported application list can be viewed on the official website." +msgstr "Key为空可以使用免费版本,设置Key后可以使用高级版本,高级版本支持的应用更多,定期更新,最新支持的应用列表可以在官网查看。" + +msgid "Parental Control" +msgstr "OAF行为管理" + +msgid "Dashboard" +msgstr "仪表板" + +msgid "About" +msgstr "关于" + +msgid "OAF is a powerful parental control software. FanchmWrt integrates OAF by default and includes more plugins developed by the author." +msgstr "OAF是一款强大的行为管理软件。FanchmWrt中默认集成了OAF,并且包含了更多作者开发的插件。" + +msgid "OAF already supports mobile app management. Mobile management is more convenient." +msgstr "OAF已经支持手机App管理,手机管理更方便。" + +msgid "Go to download" +msgstr "去下载" + +msgid "Author" +msgstr "作者" + +msgid "GitHub Source" +msgstr "GitHub源码" + +msgid "Record Whitelist" +msgstr "上网记录白名单" + +msgid "Feature library checksum verification failed" +msgstr "特征库文件校验失败" + +msgid "Please enter a valid Key. You can apply for a Key on the official website." +msgstr "请输入正确的Key,Key可以通过官网申请" + +msgid "Enter the Key to use this feature library version" +msgstr "输入Key后可以使用该版本特征库" + +msgid "Invalid Key. Please get the correct Key from the official website and enable the advanced feature library permission." +msgstr "无效的Key,请通过官网获取正确的Key,并开通高级版特征库权限" + +msgid "No data" +msgstr "暂无数据" diff --git a/luci-app-oaf/root/etc/uci-defaults/94_feature_3.0 b/luci-app-oaf/root/etc/uci-defaults/94_feature_3.0 deleted file mode 100755 index b9050bce..00000000 --- a/luci-app-oaf/root/etc/uci-defaults/94_feature_3.0 +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -uci -q batch <<-EOF >/dev/null - set appfilter.feature.format='v3.0' - set appfilter.rule='rule' - set appfilter.global.tcp_rst='1' - set appfilter.global.lan_ifname='br-lan' - set appfilter.global.auto_load_engine='1' - commit appfilter -EOF \ No newline at end of file diff --git a/luci-app-oaf/root/etc/uci-defaults/95_time_daily_limit b/luci-app-oaf/root/etc/uci-defaults/95_time_daily_limit deleted file mode 100755 index 7913a952..00000000 --- a/luci-app-oaf/root/etc/uci-defaults/95_time_daily_limit +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -if ! uci -q get appfilter.time.daily_limit_0 >/dev/null 2>&1; then - uci -q batch <<-EOF >/dev/null - set appfilter.time.daily_limit_0='0:0:0' - set appfilter.time.daily_limit_1='0:0:0' - set appfilter.time.daily_limit_2='0:0:0' - set appfilter.time.daily_limit_3='0:0:0' - set appfilter.time.daily_limit_4='0:0:0' - set appfilter.time.daily_limit_5='0:0:0' - set appfilter.time.daily_limit_6='0:0:0' - set appfilter.global.disable_quic='0' - commit appfilter - EOF -fi - diff --git a/luci-app-oaf/root/usr/share/rpcd/acl.d/luci-app-oaf.json b/luci-app-oaf/root/usr/share/rpcd/acl.d/luci-app-oaf.json deleted file mode 100644 index c4e34c33..00000000 --- a/luci-app-oaf/root/usr/share/rpcd/acl.d/luci-app-oaf.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "luci-app-oaf": { - "description": "Grant access to OpenAppFilter configuration", - "read": { - "uci": [ "appfilter", "user_info" ], - "ubus": { - "appfilter": [ "get_all_users", "get_oaf_status", "get_app_filter", "set_app_filter", "class_list", "dev_list", "app_class_visit_time", "dev_visit_time", "dev_visit_list", "set_app_filter_base", "get_app_filter_base", "set_app_filter_adv", "get_app_filter_adv", "set_app_filter_time", "get_app_filter_time", "get_app_filter_user", "set_app_filter_user", "del_app_filter_user", "add_app_filter_user", "set_nickname", "get_whitelist_user", "add_whitelist_user", "del_whitelist_user", "disable_flow_offloading", "cmd", "service_config"] - } - - }, - "write": { - "cgi-io": [ "upload" ], - "file": { - "/etc/appfilter/*": [ "write" ] - }, - "uci": [ "appfilter" ] - } - } -} diff --git a/oaf/Makefile b/oaf/Makefile index 02080f61..0d6948b5 100644 --- a/oaf/Makefile +++ b/oaf/Makefile @@ -9,17 +9,15 @@ PKG_AUTOLOAD:=oaf RSTRIP:=: define KernelPackage/oaf - SECTION:=TT Apps - CATEGORY:=TT Apps - TITLE:=OAF kernel DPI driver + SUBMENU:=Netfilter Extensions + TITLE:=OAF netfilter module FILES:=$(PKG_BUILD_DIR)/oaf.ko DEPENDS:=+kmod-ipt-conntrack KCONFIG:= - # AUTOLOAD:=$(call AutoLoad,0,$(PKG_AUTOLOAD)) endef define KernelPackage/oaf/description - open appfilter kernel module + oaf netfilter module endef KCFLAGS := -Wno-error=missing-prototypes \ @@ -29,7 +27,9 @@ KCFLAGS := -Wno-error=missing-prototypes \ -Wno-error=implicit-fallthrough \ -Wno-error=missing-braces \ -Wno-error=parentheses \ - -Wno-error=format + -Wno-error=format \ + -Wno-frame-larger-than + MAKE_OPTS:= \ $(KERNEL_MAKE_FLAGS) \ diff --git a/oaf/src/Makefile b/oaf/src/Makefile index c8752c1a..7a983e43 100644 --- a/oaf/src/Makefile +++ b/oaf/src/Makefile @@ -1,2 +1,4 @@ -oaf-objs := app_filter.o af_utils.o af_config.o regexp.o cJSON.o af_log.o af_client.o af_client_fs.o af_conntrack.o af_rule_config.o af_user_config.o af_whitelist_config.o +oaf-objs := fwx_main.o fwx_utils.o regexp.o k_json.o fwx_log.o fwx_client.o fwx_client_fs.o +oaf-objs += fwx_conntrack.o fwx_mac.o fwx_config.o fwx_mac_filter.o +oaf-objs += fwx_app_filter.o obj-m += oaf.o diff --git a/oaf/src/af_client_fs.c b/oaf/src/af_client_fs.c deleted file mode 100644 index 7a8e8548..00000000 --- a/oaf/src/af_client_fs.c +++ /dev/null @@ -1,293 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "af_utils.h" - -#include "cJSON.h" -#include "af_log.h" -#include "af_client.h" -#include "af_client_fs.h" -extern struct list_head af_client_list_table[MAX_AF_CLIENT_HASH_SIZE]; -struct af_client_iter_state -{ - unsigned int bucket; - void *head; -}; - -static void *af_client_get_first(struct seq_file *seq) -{ - struct af_client_iter_state *st = seq->private; - for (st->bucket = 0; st->bucket < MAX_AF_CLIENT_HASH_SIZE; st->bucket++) - { - if (!list_empty(&(af_client_list_table[st->bucket]))) - { - st->head = &(af_client_list_table[st->bucket]); - return af_client_list_table[st->bucket].next; - } - } - return NULL; -} - -static void *af_client_get_next(struct seq_file *seq, - void *head) -{ - struct af_client_iter_state *st = seq->private; - struct hlist_node *node = (struct hlist_node *)head; - - node = node->next; - if (node != st->head) - { - return node; - } - else - { - st->bucket++; - for (; st->bucket < MAX_AF_CLIENT_HASH_SIZE; st->bucket++) - { - if (!list_empty(&(af_client_list_table[st->bucket]))) - { - st->head = &(af_client_list_table[st->bucket]); - return af_client_list_table[st->bucket].next; - } - } - return NULL; - } -} - -static void *af_client_get_idx(struct seq_file *seq, loff_t pos) -{ - void *head = af_client_get_first(seq); - - if (head) - while (pos && (head = af_client_get_next(seq, head))) - pos--; - - return pos ? NULL : head; -} - -static void *af_client_seq_start(struct seq_file *s, loff_t *pos) -{ - AF_CLIENT_LOCK_R(); - if (*pos == 0) - { - return SEQ_START_TOKEN; - } - - return af_client_get_idx(s, *pos - 1); -} - -static void *af_client_seq_next(struct seq_file *s, void *v, loff_t *pos) -{ - (*pos)++; - if (v == SEQ_START_TOKEN) - return af_client_get_idx(s, 0); - - return af_client_get_next(s, v); -} - -static void af_client_seq_stop(struct seq_file *s, void *v) -{ - AF_CLIENT_UNLOCK_R(); -} - -static int af_client_seq_show(struct seq_file *s, void *v) -{ - unsigned char mac_str[32] = {0}; - unsigned char ip_str[32] = {0}; - unsigned char ipv6_str[128]; - - static int index = 0; - af_client_info_t *node = (af_client_info_t *)v; - if (v == SEQ_START_TOKEN) - { - index = 0; - seq_printf(s, "%-4s %-20s %-20s %-32s %-16s %-16s\n", "Id", "Mac", "IP", "IPv6", "UpRate", "DownRate"); - return 0; - } - index++; - sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac)); - sprintf(ip_str, "%pI4", &node->ip); - ipv6_to_str(&node->ipv6, ipv6_str); - - - seq_printf(s, "%-4d %-20s %-20s %-32s %-16d %-16d\n", index, mac_str, ip_str, ipv6_str, node->rate.up_rate, node->rate.down_rate); - return 0; -} - -static const struct seq_operations nf_client_seq_ops = { - .start = af_client_seq_start, - .next = af_client_seq_next, - .stop = af_client_seq_stop, - .show = af_client_seq_show}; - -static int af_client_open(struct inode *inode, struct file *file) -{ - struct seq_file *seq; - struct af_client_iter_state *iter; - int err; - - iter = kzalloc(sizeof(*iter), GFP_KERNEL); - if (!iter) - return -ENOMEM; - - err = seq_open(file, &nf_client_seq_ops); - if (err) - { - kfree(iter); - return err; - } - - seq = file->private_data; - seq->private = iter; - return 0; -} - -#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) -static const struct file_operations af_client_fops = { - .owner = THIS_MODULE, - .open = af_client_open, - .read = seq_read, - .llseek = seq_lseek, - .release = seq_release_private, -}; -#else -static const struct proc_ops af_client_fops = { - .proc_flags = PROC_ENTRY_PERMANENT, - .proc_read = seq_read, - .proc_open = af_client_open, - .proc_lseek = seq_lseek, - .proc_release = seq_release_private, -}; -#endif - -#define AF_CLIENT_PROC_STR "af_client" - - - - -static int af_visiting_seq_show(struct seq_file *s, void *v) -{ - unsigned char mac_str[32] = {0}; - static int index = 0; - af_client_info_t *node = (af_client_info_t *)v; - if (v == SEQ_START_TOKEN) - { - index = 0; - seq_printf(s, "%-20s %-12s %-32s\n", "Mac", "Appid", "Url"); - return 0; - } - index++; - - sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac)); - int visiting_app = 0; - char visiting_url[64] = {0}; - if (af_get_timestamp_sec() - node->visiting.app_time < 120){ - visiting_app = node->visiting.visiting_app; - } - if ( af_get_timestamp_sec() - node->visiting.url_time < 120 ){ - strncpy(visiting_url, node->visiting.visiting_url, sizeof(visiting_url)); - } - else{ - strcpy(visiting_url, "none"); - } - seq_printf(s, "%-20s %-12d %-32s\n", mac_str, visiting_app, visiting_url); - - return 0; -} - -static const struct seq_operations nf_visiting_seq_ops = { - .start = af_client_seq_start, - .next = af_client_seq_next, - .stop = af_client_seq_stop, - .show = af_visiting_seq_show -}; - - -static int af_visiting_open(struct inode *inode, struct file *file) -{ - struct seq_file *seq; - struct af_client_iter_state *iter; - int err; - - iter = kzalloc(sizeof(*iter), GFP_KERNEL); - if (!iter) - return -ENOMEM; - - err = seq_open(file, &nf_visiting_seq_ops); - if (err) - { - kfree(iter); - return err; - } - - seq = file->private_data; - seq->private = iter; - return 0; -} - - - - -#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) -static const struct file_operations af_visiting_fops = { - .owner = THIS_MODULE, - .open = af_visiting_open, - .read = seq_read, - .llseek = seq_lseek, - .release = seq_release_private, -}; -#else -static const struct proc_ops af_visiting_fops = { - .proc_flags = PROC_ENTRY_PERMANENT, - .proc_read = seq_read, - .proc_open = af_visiting_open, - .proc_lseek = seq_lseek, - .proc_release = seq_release_private, -}; -#endif -#define AF_VISIT_INFO "af_visit" - -int init_af_client_procfs(void) -{ - struct proc_dir_entry *pde; - struct net *net = &init_net; - pde = proc_create(AF_CLIENT_PROC_STR, 0440, net->proc_net, &af_client_fops); - - if (!pde) - { - AF_ERROR("nf_client proc file created error\n"); - return -1; - } - pde = proc_create(AF_VISIT_INFO, 0440, net->proc_net, &af_visiting_fops); - - if (!pde) - { - AF_ERROR("client visiting proc file created error\n"); - return -1; - } - return 0; -} - -void finit_af_client_procfs(void) -{ - struct net *net = &init_net; - remove_proc_entry(AF_CLIENT_PROC_STR, net->proc_net); - remove_proc_entry(AF_VISIT_INFO, net->proc_net); -} diff --git a/oaf/src/af_client_fs.h b/oaf/src/af_client_fs.h deleted file mode 100644 index 0140c277..00000000 --- a/oaf/src/af_client_fs.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef __AF_CLIENT_FS_H__ -#define __AF_CLIENT_FS_H__ - -int init_af_client_procfs(void); -void finit_af_client_procfs(void); - -#endif diff --git a/oaf/src/af_config.c b/oaf/src/af_config.c deleted file mode 100644 index a4f796b3..00000000 --- a/oaf/src/af_config.c +++ /dev/null @@ -1,234 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "cJSON.h" -#include "app_filter.h" -#include "af_config.h" -#include "af_utils.h" -#include "af_log.h" -#include "af_rule_config.h" -#include "af_user_config.h" -#include "af_whitelist_config.h" - -#define AF_DEV_NAME "appfilter" - -extern u_int32_t g_update_jiffies; - -static struct mutex af_cdev_mutex; -struct af_config_dev -{ - dev_t id; - struct cdev char_dev; - struct class *c; -}; -struct af_config_dev g_af_dev; - -struct af_cdev_file -{ - size_t size; - char buf[256 << 10]; -}; - -static struct af_config_interface af_config_interfaces[] = { - {AF_CMD_ADD_APPID, af_config_add_appid, "Add App ID"}, - {AF_CMD_DEL_APPID, af_config_del_appid, "Delete App ID"}, - {AF_CMD_CLEAN_APPID, af_config_clean_appid, "Clean App ID"}, - {AF_CMD_SET_MAC_LIST, af_config_set_mac_list, "Set MAC List"}, - {AF_CMD_SET_WHITELIST_MAC_LIST, af_config_set_whitelist_mac_list, "Set Whitelist MAC List"}, - {0, NULL, NULL} -}; - -static af_config_handler_t af_find_handler(enum AF_CONFIG_CMD cmd) -{ - struct af_config_interface *interface = af_config_interfaces; - - while (interface->handler != NULL) { - if (interface->cmd == cmd) { - return interface->handler; - } - interface++; - } - return NULL; -} - -/* -add: -{ - "op":1, - "data":{ - "apps":[] - } -} -clean -{ - "op":3, -} -*/ -static int af_config_handle(char *config, unsigned int len) -{ - cJSON *config_obj = NULL; - cJSON *cmd_obj = NULL; - cJSON *data_obj = NULL; - int ret = 0; - af_config_handler_t handler = NULL; - - if (!config || len == 0) - { - AF_ERROR("config or len is invalid\n"); - return -1; - } - - AF_DEBUG("config = %s\n", config); - config_obj = cJSON_Parse(config); - if (!config_obj) - { - AF_ERROR("config_obj is NULL\n"); - return -1; - } - - cmd_obj = cJSON_GetObjectItem(config_obj, "op"); - if (!cmd_obj) - { - AF_ERROR("not find op object\n"); - cJSON_Delete(config_obj); - return -1; - } - - data_obj = cJSON_GetObjectItem(config_obj, "data"); - - handler = af_find_handler(cmd_obj->valueint); - if (handler) { - ret = handler(data_obj); - g_update_jiffies = jiffies; - cJSON_Delete(config_obj); - return ret; - } else { - AF_ERROR("invalid cmd %d\n", cmd_obj->valueint); - cJSON_Delete(config_obj); - return -1; - } -} - - -static int af_cdev_open(struct inode *inode, struct file *filp) -{ - struct af_cdev_file *file; - file = vzalloc(sizeof(*file)); - if (!file) - return -EINVAL; - - mutex_lock(&af_cdev_mutex); - filp->private_data = file; - return 0; -} - -static ssize_t af_cdev_read(struct file *filp, char *buf, size_t count, loff_t *off) -{ - return 0; -} - -static int af_cdev_release(struct inode *inode, struct file *filp) -{ - struct af_cdev_file *file = filp->private_data; - AF_DEBUG("config size: %d,data = %s\n", (int)file->size, file->buf); - af_config_handle(file->buf, file->size); - filp->private_data = NULL; - mutex_unlock(&af_cdev_mutex); - vfree(file); - return 0; -} - -static ssize_t af_cdev_write(struct file *filp, const char *buffer, size_t count, loff_t *off) -{ - struct af_cdev_file *file = filp->private_data; - int ret; - if (file->size + count > sizeof(file->buf)) - { - AF_ERROR("config overflow, cur_size: %d, block_size: %d, max_size: %d", - (int)file->size, (int)count, (int)sizeof(file->buf)); - return -EINVAL; - } - - ret = copy_from_user(file->buf + file->size, buffer, count); - if (ret != 0) - return -EINVAL; - - file->size += count; - return count; -} - -static struct file_operations af_cdev_ops = { - owner : THIS_MODULE, - release : af_cdev_release, - open : af_cdev_open, - write : af_cdev_write, - read : af_cdev_read, -}; - -int af_register_dev(void) -{ - struct device *dev; - int res; - mutex_init(&af_cdev_mutex); - - res = alloc_chrdev_region(&g_af_dev.id, 0, 1, AF_DEV_NAME); - if (res != 0) - { - return -EINVAL; - } - - cdev_init(&g_af_dev.char_dev, &af_cdev_ops); - res = cdev_add(&g_af_dev.char_dev, g_af_dev.id, 1); - if (res < 0) - { - goto REGION_OUT; - } - -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) - g_af_dev.c = class_create(THIS_MODULE, AF_DEV_NAME); -#else - g_af_dev.c = class_create(AF_DEV_NAME); -#endif - if (IS_ERR_OR_NULL(g_af_dev.c)) - { - goto CDEV_OUT; - } - - dev = device_create(g_af_dev.c, NULL, g_af_dev.id, NULL, AF_DEV_NAME); - if (IS_ERR_OR_NULL(dev)) - { - goto CLASS_OUT; - } - AF_INFO("register char dev....ok\n"); - return 0; - -CLASS_OUT: - class_destroy(g_af_dev.c); -CDEV_OUT: - cdev_del(&g_af_dev.char_dev); -REGION_OUT: - unregister_chrdev_region(g_af_dev.id, 1); - - AF_ERROR("register char dev....fail\n"); - return -EINVAL; -} - -void af_unregister_dev(void) -{ - device_destroy(g_af_dev.c, g_af_dev.id); - class_destroy(g_af_dev.c); - cdev_del(&g_af_dev.char_dev); - unregister_chrdev_region(g_af_dev.id, 1); - AF_INFO("unregister char dev....ok\n"); -} diff --git a/oaf/src/af_config.h b/oaf/src/af_config.h deleted file mode 100644 index cc1686c2..00000000 --- a/oaf/src/af_config.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __AF_CONFIG_H__ -#define __AF_CONFIG_H__ -#include "app_filter.h" - -enum AF_CONFIG_CMD -{ - AF_CMD_ADD_APPID = 1, - AF_CMD_DEL_APPID, - AF_CMD_CLEAN_APPID, - AF_CMD_SET_MAC_LIST, - AF_CMD_SET_WHITELIST_MAC_LIST, -}; - -typedef int (*af_config_handler_t)(cJSON *data); - -struct af_config_interface -{ - enum AF_CONFIG_CMD cmd; - af_config_handler_t handler; - const char *description; -}; - -int af_register_dev(void); -void af_unregister_dev(void); - -int af_config_add_appid(cJSON *data); -int af_config_del_appid(cJSON *data); -int af_config_clean_appid(cJSON *data); -int af_config_set_mac_list(cJSON *data); -int af_config_set_whitelist_mac_list(cJSON *data); - - -#endif \ No newline at end of file diff --git a/oaf/src/af_log.h b/oaf/src/af_log.h deleted file mode 100644 index f4583c0b..00000000 --- a/oaf/src/af_log.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef __AF_DEBUG_H__ -#define __AF_DEBUG_H__ -extern int af_log_lvl; -extern int af_test_mode; -extern int af_work_mode; -extern int g_oaf_filter_enable; -extern int g_oaf_record_enable; -extern int g_by_pass_accl; -extern unsigned int af_lan_ip; -extern unsigned int af_lan_mask; -extern int g_feature_init; -extern int g_user_mode; -extern int g_disable_quic; -extern int g_app_filter_mode; -extern char g_lan_ifname[64]; -extern int g_tcp_rst; -#define LOG(level, fmt, ...) do { \ - if ((level) <= af_log_lvl) { \ - printk(fmt, ##__VA_ARGS__); \ - } \ -} while (0) - -#define LLOG(level, fmt, ...) do { \ - if ((level) <= af_log_lvl) { \ - pr_info_ratelimited(fmt, ##__VA_ARGS__); \ - } \ -} while (0) - - -#define AF_ERROR(...) LOG(0, ##__VA_ARGS__) -#define AF_WARN(...) LOG(1, ##__VA_ARGS__) -#define AF_INFO(...) LOG(2, ##__VA_ARGS__) -#define AF_DEBUG(...) LOG(3, ##__VA_ARGS__) - -#define AF_LMT_ERROR(...) LLOG(0, ##__VA_ARGS__) -#define AF_LMT_WARN(...) LLOG(1, ##__VA_ARGS__) -#define AF_LMT_INFO(...) LLOG(2, ##__VA_ARGS__) -#define AF_LMT_DEBUG(...) LLOG(3, ##__VA_ARGS__) - - -#define TEST_MODE() (af_test_mode) -int af_log_init(void); -int af_log_exit(void); -#endif diff --git a/oaf/src/af_rule_config.c b/oaf/src/af_rule_config.c deleted file mode 100644 index 7a266f5b..00000000 --- a/oaf/src/af_rule_config.c +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include -#include -#include -#include "cJSON.h" -#include "app_filter.h" -#include "af_utils.h" -#include "af_log.h" -#include "af_config.h" -#include "af_rule_config.h" - -#define AF_MAX_APP_TYPE_NUM 32 -#define AF_MAX_APP_NUM 512 - -DEFINE_RWLOCK(af_rule_lock); - -#define af_rule_read_lock() read_lock_bh(&af_rule_lock); -#define af_rule_read_unlock() read_unlock_bh(&af_rule_lock); -#define af_rule_write_lock() write_lock_bh(&af_rule_lock); -#define af_rule_write_unlock() write_unlock_bh(&af_rule_lock); - -extern u_int32_t g_update_jiffies; - -char g_app_id_array[AF_MAX_APP_TYPE_NUM][AF_MAX_APP_NUM] = {0}; - - -static int af_change_app_status(cJSON *data_obj, int status) -{ - int i; - int id; - int type; - cJSON *appid_arr = NULL; - if (!data_obj) - { - AF_ERROR("data obj is null\n"); - return -1; - } - appid_arr = cJSON_GetObjectItem(data_obj, "apps"); - if (!appid_arr) - { - AF_ERROR("apps obj is null\n"); - return -1; - } - for (i = 0; i < cJSON_GetArraySize(appid_arr); i++) - { - cJSON *appid_obj = cJSON_GetArrayItem(appid_arr, i); - if (!appid_obj) - return -1; - id = AF_APP_ID(appid_obj->valueint); - type = AF_APP_TYPE(appid_obj->valueint); - af_rule_write_lock(); - g_app_id_array[type][id] = status; - af_rule_write_unlock(); - } - - return 0; -} - - - -void af_init_app_status(void) -{ - int i, j; - - for (i = 0; i < AF_MAX_APP_TYPE_NUM; i++) - { - for (j = 0; j < AF_MAX_APP_NUM; j++) - { - af_rule_write_lock(); - g_app_id_array[i][j] = AF_FALSE; - af_rule_write_unlock(); - } - } -} -int af_get_app_status(int appid) -{ - int status = 0; - int id = AF_APP_ID(appid); - int type = AF_APP_TYPE(appid); - af_rule_read_lock(); - status = g_app_id_array[type][id]; - af_rule_read_unlock(); - return status; -} - -int af_config_add_appid(cJSON *data) -{ - return af_change_app_status(data, 1); -} - -int af_config_del_appid(cJSON *data) -{ - return af_change_app_status(data, 0); -} - -int af_config_clean_appid(cJSON *data) -{ - af_init_app_status(); - return 0; -} diff --git a/oaf/src/af_rule_config.h b/oaf/src/af_rule_config.h deleted file mode 100644 index 69c1b9ed..00000000 --- a/oaf/src/af_rule_config.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __AF_RULE_CONFIG_H__ -#define __AF_RULE_CONFIG_H__ -#include "app_filter.h" -#include "af_utils.h" -#include "af_log.h" -void af_init_app_status(void); -int af_get_app_status(int appid); - -#endif \ No newline at end of file diff --git a/oaf/src/af_user_config.c b/oaf/src/af_user_config.c deleted file mode 100644 index 3aea0e7c..00000000 --- a/oaf/src/af_user_config.c +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include -#include -#include -#include -#include "app_filter.h" -#include "af_utils.h" -#include "af_log.h" -#include "cJSON.h" -#include "af_config.h" -#include "af_whitelist_config.h" -#include "af_user_config.h" - -DEFINE_RWLOCK(af_mac_lock); - -u32 total_mac = 0; -struct list_head af_mac_htable[MAX_AF_MAC_HASH_SIZE]; -void af_mac_list_init(void) -{ - int i; - write_lock_bh(&af_mac_lock); - for (i = 0; i < MAX_AF_MAC_HASH_SIZE; i++) - { - INIT_LIST_HEAD(&af_mac_htable[i]); - } - write_unlock_bh(&af_mac_lock); -} - -void af_mac_list_flush(void) -{ - int i; - af_mac_node_t *p = NULL; - char mac_str[32] = {0}; - write_lock_bh(&af_mac_lock); - for (i = 0; i < MAX_AF_MAC_HASH_SIZE; i++) - { - while (!list_empty(&af_mac_htable[i])) - { - p = list_first_entry(&af_mac_htable[i], af_mac_node_t, list); - memset(mac_str, 0x0, sizeof(mac_str)); - sprintf(mac_str, MAC_FMT, MAC_ARRAY(p->mac)); - list_del(&(p->list)); - kfree(p); - } - } - total_mac = 0; - write_unlock_bh(&af_mac_lock); -} - -af_mac_node_t *af_mac_find(unsigned char *mac) -{ - af_mac_node_t *node; - unsigned int index; - - index = hash_mac(mac); - read_lock_bh(&af_mac_lock); - list_for_each_entry(node, &af_mac_htable[index], list) - { - if (0 == memcmp(node->mac, mac, 6)) - { - read_unlock_bh(&af_mac_lock); - return node; - } - } - read_unlock_bh(&af_mac_lock); - return NULL; -} - -af_mac_node_t *af_mac_add(unsigned char *mac) -{ - af_mac_node_t *node; - int index = 0; - - node = (af_mac_node_t *)kmalloc(sizeof(af_mac_node_t), GFP_ATOMIC); - if (node == NULL) - { - return NULL; - } - - memset(node, 0, sizeof(af_mac_node_t)); - memcpy(node->mac, mac, MAC_ADDR_LEN); - - index = hash_mac(mac); - - printk("add user mac=" MAC_FMT "\n", MAC_ARRAY(node->mac)); - total_mac++; - write_lock_bh(&af_mac_lock); - list_add(&(node->list), &af_mac_htable[index]); - write_unlock_bh(&af_mac_lock); - return node; -} - -static __maybe_unused int is_user_match_enable(void) -{ - return total_mac > 0; -} - - - -int af_config_set_mac_list(cJSON *data_obj) -{ - int i; - cJSON *mac_arr = NULL; - u8 mac_hex[MAC_ADDR_LEN] = {0}; - if (!data_obj) - { - AF_ERROR("data obj is null\n"); - return -1; - } - mac_arr = cJSON_GetObjectItem(data_obj, "mac_list"); - if (!mac_arr) - { - AF_ERROR("mac_list obj is null\n"); - return -1; - } - af_mac_list_flush(); - for (i = 0; i < cJSON_GetArraySize(mac_arr); i++) - { - cJSON *mac_obj = cJSON_GetArrayItem(mac_arr, i); - if (!mac_obj) - { - AF_ERROR("mac obj is null\n"); - return -1; - } - if (-1 == mac_to_hex(mac_obj->valuestring, mac_hex)) - { - continue; - } - af_mac_add(mac_hex); - } - AF_DEBUG("## mac num = %d\n", total_mac); - return 0; -} diff --git a/oaf/src/af_user_config.h b/oaf/src/af_user_config.h deleted file mode 100644 index 6543f750..00000000 --- a/oaf/src/af_user_config.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef __AF_USER_CONFIG_H__ -#define __AF_USER_CONFIG_H__ -#include "app_filter.h" -#include "af_utils.h" - -typedef struct af_mac_node { - struct list_head list; - unsigned char mac[MAC_ADDR_LEN]; -}af_mac_node_t; - -void af_mac_list_init(void); -void af_mac_list_flush(void); -af_mac_node_t *af_mac_find(unsigned char *mac); -af_mac_node_t *af_mac_add(unsigned char *mac); - -#endif \ No newline at end of file diff --git a/oaf/src/af_whitelist_config.c b/oaf/src/af_whitelist_config.c deleted file mode 100644 index 000a7103..00000000 --- a/oaf/src/af_whitelist_config.c +++ /dev/null @@ -1,122 +0,0 @@ -#include -#include -#include -#include -#include -#include "app_filter.h" -#include "af_utils.h" -#include "af_log.h" -#include "cJSON.h" -#include "af_whitelist_config.h" - - -DEFINE_RWLOCK(af_whitelist_mac_lock); - -struct list_head af_whitelist_mac_htable[MAX_AF_MAC_HASH_SIZE]; - -void af_whitelist_mac_init(void) -{ - int i; - write_lock_bh(&af_whitelist_mac_lock); - for (i = 0; i < MAX_AF_MAC_HASH_SIZE; i++) - { - INIT_LIST_HEAD(&af_whitelist_mac_htable[i]); - } - write_unlock_bh(&af_whitelist_mac_lock); -} - -void af_whitelist_mac_flush(void) -{ - int i; - af_whitelist_mac_node_t *p = NULL; - char mac_str[32] = {0}; - write_lock_bh(&af_whitelist_mac_lock); - for (i = 0; i < MAX_AF_MAC_HASH_SIZE; i++) - { - while (!list_empty(&af_whitelist_mac_htable[i])) - { - p = list_first_entry(&af_whitelist_mac_htable[i], af_whitelist_mac_node_t, list); - memset(mac_str, 0x0, sizeof(mac_str)); - sprintf(mac_str, MAC_FMT, MAC_ARRAY(p->mac)); - list_del(&(p->list)); - kfree(p); - } - } - write_unlock_bh(&af_whitelist_mac_lock); -} - -af_whitelist_mac_node_t *af_whitelist_mac_find(unsigned char *mac) -{ - af_whitelist_mac_node_t *node = NULL; - unsigned int index = 0; - - index = hash_mac(mac); - read_lock_bh(&af_whitelist_mac_lock); - list_for_each_entry(node, &af_whitelist_mac_htable[index], list) - { - if (0 == memcmp(node->mac, mac, 6)) - { - read_unlock_bh(&af_whitelist_mac_lock); - return node; - } - } - read_unlock_bh(&af_whitelist_mac_lock); - return NULL; -} - -af_whitelist_mac_node_t *af_whitelist_mac_add(unsigned char *mac) -{ - af_whitelist_mac_node_t *node = NULL; - int index = 0; - - node = (af_whitelist_mac_node_t *)kmalloc(sizeof(af_whitelist_mac_node_t), GFP_ATOMIC); - if (node == NULL) - { - return NULL; - } - - memset(node, 0, sizeof(af_whitelist_mac_node_t)); - memcpy(node->mac, mac, MAC_ADDR_LEN); - index = hash_mac(mac); - - AF_DEBUG("add whitelist mac=" MAC_FMT "\n", MAC_ARRAY(node->mac)); - write_lock_bh(&af_whitelist_mac_lock); - list_add(&(node->list), &af_whitelist_mac_htable[index]); - write_unlock_bh(&af_whitelist_mac_lock); - return node; -} - - -int af_config_set_whitelist_mac_list(cJSON *data_obj) -{ - int i; - cJSON *mac_arr = NULL; - u8 mac_hex[MAC_ADDR_LEN] = {0}; - if (!data_obj) - { - AF_ERROR("data obj is null\n"); - return -1; - } - mac_arr = cJSON_GetObjectItem(data_obj, "mac_list"); - if (!mac_arr) - { - AF_ERROR("mac_list obj is null\n"); - return -1; - } - af_whitelist_mac_flush(); - for (i = 0; i < cJSON_GetArraySize(mac_arr); i++) - { - cJSON *mac_obj = cJSON_GetArrayItem(mac_arr, i); - if (!mac_obj) - { - AF_ERROR("mac obj is null\n"); - return -1; - } - if (-1 == mac_to_hex(mac_obj->valuestring, mac_hex)) - { - continue; - } - af_whitelist_mac_add(mac_hex); - } - return 0; -} diff --git a/oaf/src/af_whitelist_config.h b/oaf/src/af_whitelist_config.h deleted file mode 100644 index 6bdec2b9..00000000 --- a/oaf/src/af_whitelist_config.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef __AF_WHITELIST_CONFIG_H__ -#define __AF_WHITELIST_CONFIG_H__ -#include "app_filter.h" -#include "af_utils.h" - -#define MAX_AF_WHITELIST_MAC_HASH_SIZE 64 - -typedef struct af_whitelist_mac_node{ - struct list_head list; - unsigned char mac[MAC_ADDR_LEN]; -}af_whitelist_mac_node_t; - -void af_whitelist_mac_init(void); -void af_whitelist_mac_flush(void); -af_whitelist_mac_node_t *af_whitelist_mac_find(unsigned char *mac); -af_whitelist_mac_node_t *af_whitelist_mac_add(unsigned char *mac); -int af_config_set_whitelist_mac_list(cJSON *data_obj); - -#endif \ No newline at end of file diff --git a/oaf/src/app_filter.c b/oaf/src/app_filter.c deleted file mode 100644 index 848bf696..00000000 --- a/oaf/src/app_filter.c +++ /dev/null @@ -1,1955 +0,0 @@ -/* - author: derry - date:2019/1/10 -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "app_filter.h" -#include "af_utils.h" -#include "af_log.h" -#include "af_client.h" -#include "af_client_fs.h" -#include "cJSON.h" -#include "af_conntrack.h" -#include "af_config.h" -#include "af_rule_config.h" -#include "af_user_config.h" -#include "af_whitelist_config.h" - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("destan19@126.com"); -MODULE_DESCRIPTION("app filter module"); -MODULE_VERSION(AF_VERSION); -struct list_head af_feature_head = LIST_HEAD_INIT(af_feature_head); - -DEFINE_RWLOCK(af_feature_lock); - -u_int32_t g_update_jiffies = 0; - -#define feature_list_read_lock() read_lock_bh(&af_feature_lock); -#define feature_list_read_unlock() read_unlock_bh(&af_feature_lock); -#define feature_list_write_lock() write_lock_bh(&af_feature_lock); -#define feature_list_write_unlock() write_unlock_bh(&af_feature_lock); - -#define SET_APPID(mark, appid) (mark = appid) -#define GET_APPID(mark) (mark) -#define MAX_OAF_NETLINK_MSG_LEN 1024 -#define MAX_AF_SUPPORT_DATA_LEN 3000 -#define MAX_HOST_LEN 64 -#define MIN_HOST_LEN 4 -#define APPID_QUIC 10 - - -#if LINUX_VERSION_CODE > KERNEL_VERSION(5,10,197) -extern void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb, int hook); -#elif LINUX_VERSION_CODE > KERNEL_VERSION(4,4,1) -extern void nf_send_reset(struct net *net, struct sk_buff *oldskb, int hook); -#else -extern void nf_send_reset(sk_buff *oldskb, int hook); -#endif - -char *ipv6_to_str(const struct in6_addr *addr, char *str) -{ - sprintf(str, "%pI6c", addr); - return str; -} -int hash_mac(unsigned char *mac) -{ - if (!mac) - return 0; - return ((mac[0] ^ mac[1]) + (mac[2] ^ mac[3]) + (mac[4] ^ mac[5])) % MAX_AF_MAC_HASH_SIZE; -} - -static int __add_app_feature(char *feature, int appid, char *name, int proto, int src_port, - port_info_t dport_info, char *host_url, char *request_url, char *dict, char *search_str, int ignore) -{ - af_feature_node_t *node = NULL; - char *p = dict; - char *begin = dict; - char pos[64] = {0}; - int index = 0; - int value = 0; - node = kzalloc(sizeof(af_feature_node_t), GFP_ATOMIC); - if (node == NULL) - { - printk("malloc feature memory error\n"); - return -1; - } - else - { - node->app_id = appid; - strcpy(node->app_name, name); - node->proto = proto; - node->dport_info = dport_info; - node->sport = src_port; - strcpy(node->host_url, host_url); - strcpy(node->request_url, request_url); - strcpy(node->search_str, search_str); - node->ignore = ignore; - strcpy(node->feature, feature); - if (ignore) - AF_DEBUG("add feature %s, ignore = %d\n", feature, ignore); - - // 00:0a-01:11 - p = dict; - begin = dict; - index = 0; - value = 0; - while (*p++) - { - if (*p == '|') - { - memset(pos, 0x0, sizeof(pos)); - strncpy(pos, begin, p - begin); - k_sscanf(pos, "%d:%x", &index, &value); - begin = p + 1; - node->pos_info[node->pos_num].pos = index; - node->pos_info[node->pos_num].value = value; - node->pos_num++; - if (node->pos_num >= MAX_POS_INFO_PER_FEATURE - 1) - break; - } - } - - if (begin != dict) - strncpy(pos, begin, p - begin); - else - strcpy(pos, dict); - - int ret = k_sscanf(pos, "%d:%x", &index, &value); - if (ret == 2){ - node->pos_info[node->pos_num].pos = index; - node->pos_info[node->pos_num].value = value; - node->pos_num++; - } - - feature_list_write_lock(); - list_add(&(node->head), &af_feature_head); - feature_list_write_unlock(); - } - return 0; -} -static int validate_range_value(char *range_str) -{ - if (!range_str) - return 0; - char *p = range_str; - while (*p) - { - if (*p == ' ' || *p == '!' || *p == '-' || - ((*p >= '0') && (*p <= '9'))) - { - p++; - continue; - } - else - { - return 0; - } - } - return 1; -} - -static int parse_range_value(char *range_str, range_value_t *range) -{ - char pure_range[128] = {0}; - if (!validate_range_value(range_str)) - { - printk("validate range str failed, value = %s\n", range_str); - return -1; - } - k_trim(range_str); - if (range_str[0] == '!') - { - range->not = 1; - strcpy(pure_range, range_str + 1); - } - else - { - range->not = 0; - strcpy(pure_range, range_str); - } - k_trim(pure_range); - int start, end; - if (strstr(pure_range, "-")) - { - if (2 != sscanf(pure_range, "%d-%d", &start, &end)) - return -1; - } - else - { - if (1 != sscanf(pure_range, "%d", &start)) - return -1; - end = start; - } - range->start = start; - range->end = end; - return 0; -} - -static int parse_port_info(char *port_str, port_info_t *info) -{ - char *p = port_str; - char *begin = port_str; - int param_num = 0; - char one_port_buf[128] = {0}; - k_trim(port_str); - if (strlen(port_str) == 0) - return -1; - - while (*p++) - { - if (*p != '|') - continue; - memset(one_port_buf, 0x0, sizeof(one_port_buf)); - strncpy(one_port_buf, begin, p - begin); - if (0 == parse_range_value(one_port_buf, &info->range_list[info->num])) - { - info->num++; - } - param_num++; - begin = p + 1; - } - memset(one_port_buf, 0x0, sizeof(one_port_buf)); - strncpy(one_port_buf, begin, p - begin); - if (0 == parse_range_value(one_port_buf, &info->range_list[info->num])) - { - info->num++; - } - return 0; -} - -static int af_match_port(port_info_t *info, int port) -{ - int i; - int with_not = 0; - if (info->num == 0) - return 1; - for (i = 0; i < info->num; i++) - { - if (info->range_list[i].not ) - { - with_not = 1; - break; - } - } - for (i = 0; i < info->num; i++) - { - if (with_not) - { - if (info->range_list[i].not &&port >= info->range_list[i].start && port <= info->range_list[i].end) - { - return 0; - } - } - else - { - if (port >= info->range_list[i].start && port <= info->range_list[i].end) - { - return 1; - } - } - } - if (with_not) - return 1; - else - return 0; -} -//[tcp;;443;baidu.com;;] -static int add_app_feature(int appid, char *name, char *feature) -{ - char proto_str[16] = {0}; - char src_port_str[16] = {0}; - port_info_t dport_info; - char dst_port_str[16] = {0}; - char host_url[32] = {0}; - char request_url[128] = {0}; - char dict[128] = {0}; - int proto = IPPROTO_TCP; - int param_num = 0; - int src_port = 0; - char tmp_buf[128] = {0}; - int ignore = 0; - char search_str[128] = {0}; - char *p = feature; - char *begin = feature; - - if (!name || !feature) - { - AF_ERROR("error, name or feature is null\n"); - return -1; - } - - if (strlen(feature) < MIN_FEATURE_STR_LEN){ - return -1; - } - // tcp;8000;www.sina.com;0:get_name;00:0a-01:11 - memset(&dport_info, 0x0, sizeof(dport_info)); - while (*p++) - { - if (*p != ';') - continue; - - switch (param_num) - { - - case AF_PROTO_PARAM_INDEX: - strncpy(proto_str, begin, p - begin); - break; - case AF_SRC_PORT_PARAM_INDEX: - strncpy(src_port_str, begin, p - begin); - break; - case AF_DST_PORT_PARAM_INDEX: - strncpy(dst_port_str, begin, p - begin); - break; - - case AF_HOST_URL_PARAM_INDEX: - strncpy(host_url, begin, p - begin); - break; - - case AF_REQUEST_URL_PARAM_INDEX: - strncpy(request_url, begin, p - begin); - break; - case AF_DICT_PARAM_INDEX: - strncpy(dict, begin, p - begin); - break; - case AF_STR_PARAM_INDEX: - strncpy(search_str, begin, p - begin); - break; - case AF_IGNORE_PARAM_INDEX: - strncpy(tmp_buf, begin, p - begin); - ignore = k_atoi(tmp_buf); - break; - } - param_num++; - begin = p + 1; - } - - // old version - if (param_num == AF_DICT_PARAM_INDEX){ - strncpy(dict, begin, p - begin); - } - // new version - if (param_num == AF_IGNORE_PARAM_INDEX){ - strncpy(tmp_buf, begin, p - begin); - ignore = k_atoi(tmp_buf); - } - - if (0 == strcmp(proto_str, "tcp")) - proto = IPPROTO_TCP; - else if (0 == strcmp(proto_str, "udp")) - proto = IPPROTO_UDP; - else - { - printk("proto %s is not support, feature = %s\n", proto_str, feature); - return -1; - } - sscanf(src_port_str, "%d", &src_port); - // sscanf(dst_port_str, "%d", &dst_port); - parse_port_info(dst_port_str, &dport_info); - - __add_app_feature(feature, appid, name, proto, src_port, dport_info, host_url, request_url, dict, search_str, ignore); - return 0; -} - -static void af_init_feature(char *feature_str) -{ - int app_id; - char app_name[128] = {0}; - char *feature_buf = NULL; - char feature[MAX_FEATURE_STR_LEN] = {0}; - char *p = feature_str; - char *pos = NULL; - int len = 0; - char *begin = NULL; - - feature_buf = kmalloc(MAX_FEATURE_LINE_LEN, GFP_KERNEL); - if (!feature_buf) { - AF_ERROR("Failed to allocate memory for feature_buf\n"); - return; - } - memset(feature_buf, 0, MAX_FEATURE_LINE_LEN); - - if (strstr(feature_str, "#")) { - kfree(feature_buf); - return; - } - - k_sscanf(feature_str, "%d%[^:]", &app_id, app_name); - while (*p++) - { - if (*p == '[') - { - pos = p + 1; - continue; - } - if (*p == ']' && pos != NULL) - { - len = p - pos; - } - } - - if (pos && len) - strncpy(feature_buf, pos, len); - p = feature_buf; - begin = feature_buf; - - while (*p++) - { - if (*p == ',') - { - if (p - begin > MAX_FEATURE_STR_LEN){ - printk("error, feature len error %d\n", (int)(p - begin)); - break; - } - memcpy((char *)feature, begin, p - begin); - feature[p - begin] = '\0'; - add_app_feature(app_id, app_name, feature); - begin = p + 1; - } - } - if (p != begin) - { - - if (p - begin > MAX_FEATURE_STR_LEN){ - printk("error, feature len error %d\n", (int)(p - begin)); - } - else{ - memcpy((char *)feature, begin, p - begin); - feature[p - begin] = '\0'; - add_app_feature(app_id, app_name, feature); - } - } - - - if (feature_buf) - kfree(feature_buf); -} - -static void load_feature_buf_from_file(char **config_buf) -{ - struct inode *inode = NULL; - struct file *fp = NULL; -#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 7, 19) - mm_segment_t fs; -#endif - off_t size; - fp = filp_open(AF_FEATURE_CONFIG_FILE, O_RDONLY, 0); - - - if (IS_ERR(fp)) - { - return; - } - - inode = fp->f_inode; - size = inode->i_size; - if (size == 0) - { - return; - } - *config_buf = (char *)kzalloc(sizeof(char) * size, GFP_ATOMIC); - if (NULL == *config_buf) - { - AF_ERROR("alloc buf fail\n"); - filp_close(fp, NULL); - return; - } - -#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 7, 19) - fs = get_fs(); - set_fs(KERNEL_DS); -#endif -// 4.14rc3 vfs_read-->kernel_read -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) - kernel_read(fp, *config_buf, size, &(fp->f_pos)); -#else - vfs_read(fp, *config_buf, size, &(fp->f_pos)); -#endif - -#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 7, 19) - set_fs(fs); -#endif - filp_close(fp, NULL); -} - -static __maybe_unused int load_feature_config(void) -{ - char *feature_buf = NULL; - char *p; - char *begin; - char line[MAX_FEATURE_LINE_LEN] = {0}; - - load_feature_buf_from_file(&feature_buf); - if (!feature_buf) - { - return -1; - } - p = begin = feature_buf; - while (*p++) - { - if (*p == '\n') - { - if (p - begin < MIN_FEATURE_LINE_LEN || p - begin > MAX_FEATURE_LINE_LEN) - { - begin = p + 1; - continue; - } - memset(line, 0x0, sizeof(line)); - strncpy(line, begin, p - begin); - af_init_feature(line); - begin = p + 1; - } - } - - if (p != begin) - { - if (p - begin < MIN_FEATURE_LINE_LEN || p - begin > MAX_FEATURE_LINE_LEN) - return 0; - memset(line, 0x0, sizeof(line)); - strncpy(line, begin, p - begin); - af_init_feature(line); - begin = p + 1; - } - if (feature_buf) - kfree(feature_buf); - return 0; -} - - -static void af_clean_feature_list(void) -{ - af_feature_node_t *node; - int count = 0; - feature_list_write_lock(); - while (!list_empty(&af_feature_head)) - { - node = list_first_entry(&af_feature_head, af_feature_node_t, head); - list_del(&(node->head)); - kfree(node); - count++; - } - feature_list_write_unlock(); -} - -static void af_add_feature_msg_handle(char *data, int len) -{ - char feature[MAX_FEATURE_LINE_LEN] = {0}; - if (len <= 0 || len >= MAX_FEATURE_LINE_LEN){ - printk("warn, feature data len = %d\n", len); - return; - } - strncpy(feature, data, len); - AF_INFO("add feature %s\n", feature); - af_init_feature(feature); -} -// free by caller -static unsigned char *read_skb(struct sk_buff *skb, unsigned int from, unsigned int len) -{ - struct skb_seq_state state; - unsigned char *msg_buf = NULL; - unsigned int consumed = 0; -#if 0 - if (from <= 0 || from > 1500) - return NULL; - - if (len <= 0 || from+len > 1500) - return NULL; -#endif - - msg_buf = kmalloc(len, GFP_KERNEL); - if (!msg_buf) - return NULL; - - skb_prepare_seq_read(skb, from, from + len, &state); - while (1) - { - unsigned int avail; - const u8 *ptr; - avail = skb_seq_read(consumed, &ptr, &state); - if (avail == 0) - { - break; - } - memcpy(msg_buf + consumed, ptr, avail); - consumed += avail; - if (consumed >= len) - { - skb_abort_seq_read(&state); - break; - } - } - return msg_buf; -} - -static int parse_flow_proto(struct sk_buff *skb, flow_info_t *flow) -{ - unsigned char *ipp; - int ipp_len; - struct tcphdr *tcph = NULL; - struct udphdr *udph = NULL; - struct iphdr *iph = NULL; - struct ipv6hdr *ip6h = NULL; - if (!skb) - return -1; - switch (skb->protocol) - { - case htons(ETH_P_IP): - iph = ip_hdr(skb); - flow->src = iph->saddr; - flow->dst = iph->daddr; - flow->l4_protocol = iph->protocol; - ipp = ((unsigned char *)iph) + iph->ihl * 4; - ipp_len = ((unsigned char *)iph) + ntohs(iph->tot_len) - ipp; - break; - case htons(ETH_P_IPV6): - ip6h = ipv6_hdr(skb); - flow->src6 = &ip6h->saddr; - flow->dst6 = &ip6h->daddr; - flow->l4_protocol = ip6h->nexthdr; - ipp = ((unsigned char *)ip6h) + sizeof(struct ipv6hdr); - ipp_len = ntohs(ip6h->payload_len); - break; - default: - return -1; - } - - switch (flow->l4_protocol) - { - case IPPROTO_TCP: - tcph = (struct tcphdr *)ipp; - flow->l4_len = ipp_len - tcph->doff * 4; - flow->l4_data = ipp + tcph->doff * 4; - flow->dport = ntohs(tcph->dest); - flow->sport = ntohs(tcph->source); - return 0; - case IPPROTO_UDP: - udph = (struct udphdr *)ipp; - flow->l4_len = ntohs(udph->len) - 8; - flow->l4_data = ipp + 8; - flow->dport = ntohs(udph->dest); - flow->sport = ntohs(udph->source); - return 0; - case IPPROTO_ICMP: - break; - default: - return -1; - } - return -1; -} - -static int check_domain(char *h, int len) -{ - int i; - for (i = 0; i < len; i++) - { - if ((h[i] >= 'a' && h[i] <= 'z') || (h[i] >= 'A' && h[i] <= 'Z') || - (h[i] >= '0' && h[i] <= '9') || h[i] == '.' || h[i] == '-' || h[i] == ':') - { - continue; - } - else - return 0; - } - return 1; -} - -static int dpi_https_proto(flow_info_t *flow) -{ - int i; - short url_len = 0; - char *p = flow->l4_data; - int data_len = flow->l4_len; - - if (NULL == flow) - { - AF_ERROR("flow is NULL\n"); - return -1; - } - if (NULL == p || data_len < 16) - { - return -1; - } - if (!((p[0] == 0x16 && p[1] == 0x03 && p[5] == 0x01) || flow->client_hello)) - return -1; - - for (i = 0; i < data_len; i++) - { - if (i + HTTPS_URL_OFFSET >= data_len) - { - AF_LMT_INFO("match https host failed, data_len = %d, sport:%d, dport:%d\n", data_len, flow->sport,flow->dport); - - flow->client_hello = 1; - return -1; - } - - if (p[i] == 0x0 && p[i + 1] == 0x0 && p[i + 2] == 0x0 && p[i + 3] != 0x0) - { - // 2 bytes - memcpy(&url_len, p + i + HTTPS_LEN_OFFSET, 2); - - if (ntohs(url_len) <= MIN_HOST_LEN || ntohs(url_len) > data_len || ntohs(url_len) > MAX_HOST_LEN) - { - continue; - } - - if (i + HTTPS_URL_OFFSET + ntohs(url_len) < data_len) - { - if (!check_domain( p + i + HTTPS_URL_OFFSET, ntohs(url_len))){ - AF_INFO("invalid url, len = %d\n", ntohs(url_len)); - continue; - } - flow->https.match = AF_TRUE; - flow->https.url_pos = p + i + HTTPS_URL_OFFSET; - flow->https.url_len = ntohs(url_len); - flow->client_hello = 0; - return 0; - } - } - } - return -1; -} - -static void dpi_http_proto(flow_info_t *flow) -{ - int i = 0; - int start = 0; - char *data = NULL; - int data_len = 0; - if (!flow) - { - AF_ERROR("flow is null\n"); - return; - } - if (flow->l4_protocol != IPPROTO_TCP) - { - return; - } - - data = flow->l4_data; - data_len = flow->l4_len; - if (data_len < MIN_HTTP_DATA_LEN) - { - return; - } - - for (i = 0; i < data_len; i++) - { - if (data[i] == 0x0d && data[i + 1] == 0x0a) - { - if (0 == memcmp(&data[start], "POST ", 5)) - { - flow->http.match = AF_TRUE; - flow->http.method = HTTP_METHOD_POST; - flow->http.url_pos = data + start + 5; - flow->http.url_len = i - start - 5; - } - else if (0 == memcmp(&data[start], "GET ", 4)) - { - flow->http.match = AF_TRUE; - flow->http.method = HTTP_METHOD_GET; - flow->http.url_pos = data + start + 4; - flow->http.url_len = i - start - 4; - } - else if (0 == memcmp(&data[start], "Host:", 5)) - { - flow->http.host_pos = data + start + 6; - flow->http.host_len = i - start - 6; - } - if (data[i + 2] == 0x0d && data[i + 3] == 0x0a) - { - flow->http.data_pos = data + i + 4; - flow->http.data_len = data_len - i - 4; - break; - } - // 0x0d 0x0a - start = i + 2; - } - } -} - -static void dump_http_flow_info(http_proto_t *http) -{ - if (!http) - { - AF_ERROR("http ptr is NULL\n"); - return; - } - if (!http->match) - return; - if (http->method == HTTP_METHOD_GET) - { - printk("Http method: " HTTP_GET_METHOD_STR "\n"); - } - else if (http->method == HTTP_METHOD_POST) - { - printk("Http method: " HTTP_POST_METHOD_STR "\n"); - } - if (http->url_len > 0 && http->url_pos) - { - dump_str("Request url", http->url_pos, http->url_len); - } - - if (http->host_len > 0 && http->host_pos) - { - dump_str("Host", http->host_pos, http->host_len); - } - - printk("--------------------------------------------------------\n\n\n"); -} - -static void dump_https_flow_info(https_proto_t *https) -{ - if (!https) - { - AF_ERROR("https ptr is NULL\n"); - return; - } - if (!https->match) - return; - - if (https->url_len > 0 && https->url_pos) - { - dump_str("https server name", https->url_pos, https->url_len); - } - - printk("--------------------------------------------------------\n\n\n"); -} -static void dump_flow_info(flow_info_t *flow) -{ - if (!flow) - { - AF_ERROR("flow is null\n"); - return; - } - if (flow->l4_len > 0) - { - AF_LMT_INFO("src=" NIPQUAD_FMT ",dst=" NIPQUAD_FMT ",sport: %d, dport: %d, data_len: %d\n", - NIPQUAD(flow->src), NIPQUAD(flow->dst), flow->sport, flow->dport, flow->l4_len); - } - - if (flow->l4_protocol == IPPROTO_TCP) - { - if (AF_TRUE == flow->http.match) - { - printk("-------------------http protocol-------------------------\n"); - printk("protocol:TCP , sport: %-8d, dport: %-8d, data_len: %-8d\n", - flow->sport, flow->dport, flow->l4_len); - dump_http_flow_info(&flow->http); - } - if (AF_TRUE == flow->https.match) - { - printk("-------------------https protocol-------------------------\n"); - dump_https_flow_info(&flow->https); - } - } -} - - -static char *k_memstr(char *data, char *str, int size) -{ - char *p; - char len = strlen(str); - for (p = data; p <= (data - len + size); p++) - { - if (memcmp(p, str, len) == 0) - return p; - } - return NULL; -} - -static int af_match_by_pos(flow_info_t *flow, af_feature_node_t *node) -{ - int i; - unsigned int pos = 0; - - if (!flow || !node) - return AF_FALSE; - if (node->pos_num > 0) - { - - for (i = 0; i < node->pos_num && i < MAX_POS_INFO_PER_FEATURE; i++) - { - // -1 - if (node->pos_info[i].pos < 0) - { - pos = flow->l4_len + node->pos_info[i].pos; - } - else - { - pos = node->pos_info[i].pos; - } - if (pos >= flow->l4_len) - { - return AF_FALSE; - } - if (flow->l4_data[pos] != node->pos_info[i].value) - { - return AF_FALSE; - } - else{ - AF_DEBUG("match pos[%d] = %x\n", pos, node->pos_info[i].value); - } - } - if (strlen(node->search_str) > 0){ - if (k_memstr(flow->l4_data, node->search_str, flow->l4_len)){ - AF_DEBUG("match by search str, appid=%d, search_str=%s\n", node->app_id, node->search_str); - return AF_TRUE; - } - else{ - return AF_FALSE; - } - } - return AF_TRUE; - } - return AF_FALSE; -} - -static int af_match_by_url(flow_info_t *flow, af_feature_node_t *node) -{ - char reg_url_buf[MAX_URL_MATCH_LEN] = {0}; - - if (!flow || !node) - return AF_FALSE; - // match host or https url - if (flow->https.match == AF_TRUE && flow->https.url_pos) - { - if (flow->https.url_len >= MAX_URL_MATCH_LEN) - strncpy(reg_url_buf, flow->https.url_pos, MAX_URL_MATCH_LEN - 1); - else - strncpy(reg_url_buf, flow->https.url_pos, flow->https.url_len); - } - else if (flow->http.match == AF_TRUE && flow->http.host_pos) - { - if (flow->http.host_len >= MAX_URL_MATCH_LEN) - strncpy(reg_url_buf, flow->http.host_pos, MAX_URL_MATCH_LEN - 1); - else - strncpy(reg_url_buf, flow->http.host_pos, flow->http.host_len); - } - if (strlen(reg_url_buf) > 0 && strlen(node->host_url) > 0 && regexp_match(node->host_url, reg_url_buf)) - { - AF_DEBUG("match url:%s reg = %s, appid=%d\n", - reg_url_buf, node->host_url, node->app_id); - return AF_TRUE; - } - - // match request url - if (flow->http.match == AF_TRUE && flow->http.url_pos) - { - memset(reg_url_buf, 0x0, sizeof(reg_url_buf)); - if (flow->http.url_len >= MAX_URL_MATCH_LEN) - strncpy(reg_url_buf, flow->http.url_pos, MAX_URL_MATCH_LEN - 1); - else - strncpy(reg_url_buf, flow->http.url_pos, flow->http.url_len); - if (strlen(reg_url_buf) > 0 && strlen(node->request_url) && regexp_match(node->request_url, reg_url_buf)) - { - AF_DEBUG("match request:%s reg:%s appid=%d\n", - reg_url_buf, node->request_url, node->app_id); - return AF_TRUE; - } - } - return AF_FALSE; -} - -static int af_match_one(flow_info_t *flow, af_feature_node_t *node) -{ - int ret = AF_FALSE; - if (!flow || !node) - { - AF_ERROR("node or flow is NULL\n"); - return AF_FALSE; - } - if (node->proto > 0 && flow->l4_protocol != node->proto) - return AF_FALSE; - if (flow->l4_len == 0) - return AF_FALSE; - - if (node->sport != 0 && flow->sport != node->sport) - { - return AF_FALSE; - } - - if (!af_match_port(&node->dport_info, flow->dport)) - { - return AF_FALSE; - } - - if (strlen(node->request_url) > 0 || - strlen(node->host_url) > 0) - { - ret = af_match_by_url(flow, node); - } - else if (node->pos_num > 0) - { - - ret = af_match_by_pos(flow, node); - } - else - { - AF_DEBUG("node is empty, match sport:%d,dport:%d, appid = %d\n", - node->sport, node->dport, node->app_id); - return AF_TRUE; - } - - return ret; -} - - -static int af_match_quic(flow_info_t *flow) -{ - unsigned char *data; - unsigned char first_byte; - unsigned int version; - - if (flow->l4_protocol != IPPROTO_UDP) { - return AF_FALSE; - } - - if (!flow->l4_data || flow->l4_len < 8) { - return AF_FALSE; - } - - data = flow->l4_data; - first_byte = data[0]; - - if (first_byte & 0x80) { - if (flow->l4_len >= 5) { - version = (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4]; - - if (version == 0x00000001 || - version == 0x00000000 || - version == 0x6b3343cf || - (version >= 0xff000000 && version <= 0xffffffff)) { - AF_LMT_DEBUG("match quic, version = %x\n", version); - return AF_TRUE; - } - } - if (flow->dport == 443) { - return AF_TRUE; - } - return AF_FALSE; - } - return AF_FALSE; -} - - -static int match_feature(flow_info_t *flow) -{ - af_feature_node_t *n, *node; - - feature_list_read_lock(); - if (!list_empty(&af_feature_head)) - { - list_for_each_entry_safe(node, n, &af_feature_head, head) - { - if (af_match_one(flow, node)) - { - AF_LMT_INFO("match feature, appid=%d, feature = %s\n", node->app_id, node->feature); - flow->app_id = node->app_id; - flow->feature = node; - strncpy(flow->app_name, node->app_name, sizeof(flow->app_name) - 1); - feature_list_read_unlock(); - return AF_TRUE; - } - } - } - feature_list_read_unlock(); - return AF_FALSE; -} - - -static int match_app_filter_user(af_client_info_t *client){ - if (!g_user_mode){ // auto mode - if (af_whitelist_mac_find(client->mac)){ - AF_LMT_DEBUG("match whitelist mac = " MAC_FMT "\n", MAC_ARRAY(client->mac)); - return AF_FALSE; - } - } - else{ // manual mode - if (!af_mac_find(client->mac)) - return AF_FALSE; - } - return AF_TRUE; -} - - -static int match_app_filter_rule(int appid, af_client_info_t *client) -{ - if (!match_app_filter_user(client)) - return AF_FALSE; - - // All apps mode: skip appid check, match user only - if (g_app_filter_mode == 1) { - return AF_TRUE; - } - - // Specified apps mode: check appid status - if (af_get_app_status(appid)) - { - return AF_TRUE; - } - return AF_FALSE; -} - - -/*1000 0000 0000 0000 0000 0000 0000 0000*/ -#define NF_DROP_BIT 0x80000000 -/*0100 0000 0000 0000 0000 0000 0000 0000*/ -#define NF_CLIENT_HELLO_BIT 0x40000000 -/*0010 0000 0000 0000 0000 0000 0000 0000*/ -#define NF_IGNORE_BIT 0x20000000 - - -static int af_get_visit_index(af_client_info_t *node, int app_id) -{ - int i; - for (i = 0; i < MAX_RECORD_APP_NUM; i++) - { - if (node->visit_info[i].app_id == app_id || node->visit_info[i].app_id == 0) - { - return i; - } - } - // default 0 - return 0; -} - -static int af_update_client_app_info(af_client_info_t *node, int app_id, int drop) -{ - int index = -1; - if (!node) - return -1; - - index = af_get_visit_index(node, app_id); - if (index < 0 || index >= MAX_RECORD_APP_NUM) - return 0; - node->visit_info[index].total_num++; - if (drop) - node->visit_info[index].drop_num++; - node->visit_info[index].app_id = app_id; - node->visit_info[index].latest_time = af_get_timestamp_sec(); - node->visit_info[index].latest_action = drop; - if (app_id > 0){ - node->visiting.app_time = af_get_timestamp_sec(); - node->visiting.visiting_app = app_id; - } - return 0; -} - -int af_send_msg_to_user(char *pbuf, uint16_t len); -static __maybe_unused int af_match_bcast_packet(flow_info_t *f) -{ - if (!f) - return 0; - if (0 == f->src || 0 == f->dst || 0xffffffff == f->dst || 0 == f->dst) - return 1; - return 0; -} - -static int af_match_local_packet(flow_info_t *f) -{ - if (!f) - return 0; - if (0x0100007f == f->src || 0x0100007f == f->dst) - { - return 1; - } - return 0; -} - -static int update_url_visiting_info(af_client_info_t *client, flow_info_t *flow) -{ - char *host = NULL; - unsigned int len = 0; - if (!client || !flow) - return -1; - - if (flow->https.match){ - host = flow->https.url_pos; - len = flow->https.url_len; - } - else if (flow->http.match){ - host = flow->http.host_pos; - len = flow->http.host_len; - } - if (!host || len < MIN_REPORT_URL_LEN || len >= MAX_REPORT_URL_LEN) - return -1; - - memcpy(client->visiting.visiting_url, host, len); - client->visiting.visiting_url[len] = 0x0; - client->visiting.url_time = af_get_timestamp_sec(); - return 0; -} - - -static int dpi_main(struct sk_buff *skb, flow_info_t *flow) -{ - dpi_http_proto(flow); - dpi_https_proto(flow); - if (TEST_MODE()) - dump_flow_info(flow); - return 0; -} - -static void af_get_smac(struct sk_buff *skb, u_int8_t *smac) -{ - struct ethhdr *ethhdr = NULL; - ethhdr = eth_hdr(skb); - if (ethhdr) - memcpy(smac, ethhdr->h_source, ETH_ALEN); - else - memcpy(smac, &skb->cb[40], ETH_ALEN); -} -static int is_ipv4_broadcast(uint32_t ip) -{ - return (ip & 0x00FFFFFF) == 0x00FFFFFF; -} - -static int is_ipv4_multicast(uint32_t ip) -{ - return (ip & 0xF0000000) == 0xE0000000; -} -static int af_check_bcast_ip(flow_info_t *f) -{ - - if (0 == f->src || 0 == f->dst) - return 1; - if (is_ipv4_broadcast(ntohl(f->src)) || is_ipv4_broadcast(ntohl(f->dst))) - { - return 1; - } - if (is_ipv4_multicast(ntohl(f->src)) || is_ipv4_multicast(ntohl(f->dst))) - { - return 1; - } - - return 0; -} - -/* - action: 0: accept, 1: drop - return: 0: no change, 1: change -*/ -static u_int32_t check_app_action_changed(int action, u_int32_t app_id, af_client_info_t *client) -{ - int changed = 0; - u_int32_t max_jiffies = 30 * HZ; - u_int32_t interval_jiffies = jiffies - g_update_jiffies; - if (interval_jiffies < max_jiffies){ - AF_LMT_DEBUG("config changed, update app action\n"); - if (match_app_filter_rule(app_id, client)){ - AF_LMT_DEBUG("match appid = %d, action = %d\n", app_id, action); - if (!action) - changed = 1; - } - else{ - if (action) - changed = 1; - } - } - return changed; -} - -static u_int32_t app_filter_hook_bypass_handle(struct sk_buff *skb, struct net_device *dev) -{ - flow_info_t flow; - af_conn_t *conn; - u_int8_t smac[ETH_ALEN]; - af_client_info_t *client = NULL; - u_int32_t ret = NF_ACCEPT; - u_int8_t malloc_data = 0; - - if (!skb || !dev) - return NF_ACCEPT; - if (0 == af_lan_ip || 0 == af_lan_mask) - return NF_ACCEPT; - if (strstr(dev->name, "docker")) - return NF_ACCEPT; - - memset((char *)&flow, 0x0, sizeof(flow_info_t)); - if (parse_flow_proto(skb, &flow) < 0) - return NF_ACCEPT; - // bypass mode, only handle ipv4 - if (flow.src || flow.dst) - { - if (af_lan_ip == flow.src || af_lan_ip == flow.dst) - { - return NF_ACCEPT; - } - if (af_check_bcast_ip(&flow) || af_match_local_packet(&flow)) - return NF_ACCEPT; - - if ((flow.src & af_lan_mask) != (af_lan_ip & af_lan_mask)) - { - return NF_ACCEPT; - } - } - else - { - return NF_ACCEPT; - } - af_get_smac(skb, smac); - - AF_CLIENT_LOCK_W(); - client = find_and_add_af_client(smac); - if (!client) - { - AF_CLIENT_UNLOCK_W(); - return NF_ACCEPT; - } - client->update_jiffies = jiffies; - if (flow.src) - client->ip = flow.src; - AF_CLIENT_UNLOCK_W(); - - - spin_lock(&af_conn_lock); - conn = af_conn_find_and_add(flow.src, flow.dst, flow.sport, flow.dport, flow.l4_protocol); - if (!conn){ - return NF_ACCEPT; - } - - conn->last_jiffies = jiffies; - conn->total_pkts++; - spin_unlock(&af_conn_lock); - - if (conn->drop && g_app_filter_mode){ - AF_LMT_INFO("bypass mod drop all app\n"); - return NF_DROP; - } - - if (conn->app_id != 0) - { - flow.app_id = conn->app_id; - flow.drop = conn->drop; - if (g_disable_quic && flow.drop && flow.app_id == APPID_QUIC){ - AF_LMT_INFO("bypass drop quic\n"); - return NF_DROP; - } - - if (check_app_action_changed(flow.drop, flow.app_id, client)){ - flow.drop = !flow.drop; - AF_LMT_DEBUG("update appid %d action, new action = %s\n", flow.app_id, flow.drop ? "drop" : "accept"); - } - } - else{ - if (g_by_pass_accl) { - if (conn->total_pkts > 256) { - return NF_ACCEPT; - } - } - - - if (g_disable_quic && af_match_quic(&flow) && match_app_filter_user(client)){ - conn->app_id = APPID_QUIC; - conn->drop = 1; - AF_LMT_INFO("match quic proto, drop\n"); - return NF_DROP; - } - - - - if (skb_is_nonlinear(skb) && flow.l4_len < MAX_AF_SUPPORT_DATA_LEN) - { - flow.l4_data = read_skb(skb, flow.l4_data - skb->data, flow.l4_len); - if (!flow.l4_data) - return NF_ACCEPT; - AF_LMT_DEBUG("##match nonlinear skb, len = %d\n", flow.l4_len); - malloc_data = 1; - } - flow.client_hello = conn->client_hello; - - dpi_main(skb, &flow); - conn->client_hello = flow.client_hello; - update_url_visiting_info(client, &flow); - - if (!match_feature(&flow) && 0 == g_app_filter_mode) - goto EXIT; - - if (g_oaf_filter_enable){ - if (match_app_filter_rule(flow.app_id, client)){ - flow.drop = 1; - AF_INFO("##Drop appid %d\n",flow.app_id); - if (skb->protocol == htons(ETH_P_IP) && g_tcp_rst){ - #if LINUX_VERSION_CODE > KERNEL_VERSION(5,10,197) - nf_send_reset(&init_net, skb->sk, skb, NF_INET_PRE_ROUTING); - #elif LINUX_VERSION_CODE > KERNEL_VERSION(4,4,1) - // 5.4 kernel panic - // nf_send_reset(&init_net, skb, NF_INET_PRE_ROUTING); - #else - nf_send_reset(skb, NF_INET_PRE_ROUTING); - #endif - } - - } - } - conn->app_id = flow.app_id; - conn->drop = flow.drop; - if (flow.feature && flow.feature->ignore){ - AF_LMT_DEBUG("match ignore feature, feature = %s, appid = %d\n", flow.feature->feature ,flow.app_id); - conn->ignore = 1; - } - else{ - conn->ignore = 0; - } - conn->state = AF_CONN_DPI_FINISHED; - } - - if (g_oaf_record_enable ){ - if (!conn->ignore){ - af_update_client_app_info(client, flow.app_id, flow.drop); - } - else{ - AF_LMT_DEBUG("update ignore appid = %d, drop = %d\n", flow.app_id, flow.drop); - } - - } - - if (flow.drop && g_oaf_filter_enable) - { - AF_LMT_INFO("drop appid = %d\n", flow.app_id); - ret = NF_DROP; - } - -EXIT: - if (malloc_data) - { - if (flow.l4_data) - { - kfree(flow.l4_data); - } - } - return ret; -} - - -static u_int32_t app_filter_hook_gateway_handle(struct sk_buff *skb, struct net_device *dev) -{ - unsigned long long total_packets = 0; - flow_info_t flow; - enum ip_conntrack_info ctinfo; - struct nf_conn *ct = NULL; - struct nf_conn_acct *acct; - af_client_info_t *client = NULL; - u_int32_t ret = NF_ACCEPT; - u_int32_t app_id = 0; - u_int8_t malloc_data = 0; - - if (!strstr(dev->name, g_lan_ifname)) - return NF_ACCEPT; - - memset((char *)&flow, 0x0, sizeof(flow_info_t)); - if (parse_flow_proto(skb, &flow) < 0) - return NF_ACCEPT; - - ct = nf_ct_get(skb, &ctinfo); - if (ct == NULL) - return NF_ACCEPT; - - if (flow.l4_protocol == IPPROTO_TCP && !nf_ct_is_confirmed(ct)){ - return NF_ACCEPT; - } - - AF_CLIENT_LOCK_R(); - if (flow.src){ - client = find_af_client_by_ip(flow.src); - } - else if (flow.src6){ - client = find_af_client_by_ipv6(flow.src6); - } - - if (!client) - { - AF_CLIENT_UNLOCK_R(); - return NF_ACCEPT; - } - client->update_jiffies = jiffies; - AF_CLIENT_UNLOCK_R(); - - - - if (ct->mark != 0) - { - app_id = ct->mark & 0xffff; - u_int32_t orig_mark = ct->mark; - // 1: drop , 0: accept - int ct_action = (NF_DROP_BIT == (ct->mark & NF_DROP_BIT)) ? 1 : 0; - flow.ignore = (NF_IGNORE_BIT == (ct->mark & NF_IGNORE_BIT)) ? 1 : 0; - if (flow.ignore){ - AF_LMT_DEBUG("match ignore appid = %d, drop = %d\n", app_id, ct_action); - } - - if (g_oaf_filter_enable){ - // quic proto - if (g_disable_quic && app_id == APPID_QUIC && ct_action){ - AF_LMT_INFO("mark = %x,drop appid = %d\n", ct->mark, app_id); - return NF_DROP; - } - - if (g_app_filter_mode && ct_action){ - AF_LMT_INFO("ct drop all app\n"); - return NF_DROP; - } - } - - if (app_id > 1000 && app_id < 32000) - { - AF_LMT_DEBUG("appid = %d, ct_action = %d\n", app_id, ct_action); - if (check_app_action_changed(ct_action, app_id, client)){ - if (ct_action) // drop --> accept - ct->mark &= ~NF_DROP_BIT; - else - ct->mark |= NF_DROP_BIT; - ct_action = !ct_action; - AF_LMT_DEBUG("update appid %d action to %s, mark = %x-->%x\n", - app_id, ct_action ? "drop" : "accept", orig_mark, ct->mark); - } - - if (g_oaf_record_enable){ - AF_CLIENT_LOCK_W(); - if (!flow.ignore){ - af_update_client_app_info(client, app_id, ct_action); - } - else{ - AF_LMT_DEBUG(" ignore appid = %d, drop = %d, not update status\n", app_id, ct_action); - } - AF_CLIENT_UNLOCK_W(); - } - if (g_oaf_filter_enable && ct_action) { - AF_LMT_DEBUG("drop appid = %d, ct_action = %d\n", app_id, ct_action); - return NF_DROP; - } - else{ - AF_LMT_DEBUG("accept appid = %d, ct_action = %d\n", app_id, ct_action); - return NF_ACCEPT; - } - } - else { - AF_LMT_DEBUG("ct->mark = %x\n", ct->mark); - if (ct->mark & NF_CLIENT_HELLO_BIT) { - AF_LMT_INFO("match ct client hello...\n"); - flow.client_hello = 1; - } - } - } - - acct = nf_conn_acct_find(ct); - if (!acct) - return NF_ACCEPT; - total_packets = (unsigned long long)atomic64_read(&acct->counter[IP_CT_DIR_ORIGINAL].packets) + (unsigned long long)atomic64_read(&acct->counter[IP_CT_DIR_REPLY].packets); - - if (total_packets > MAX_DPI_PKT_NUM) - return NF_ACCEPT; - - - if (g_oaf_filter_enable && g_disable_quic && af_match_quic(&flow) && match_app_filter_user(client)){ - ct->mark = (ct->mark & 0xFFFF0000) | (APPID_QUIC & 0xFFFF); - ct->mark |= NF_DROP_BIT; - AF_LMT_INFO("match quick drop, %s %pI4(%d)--> %pI4(%d) len = %d [%02x %02x %02x %02x %02x %02x %02x %02x] \n ", IPPROTO_TCP == flow.l4_protocol ? "tcp" : "udp", - &flow.src, flow.sport, &flow.dst, flow.dport, flow.l4_len, flow.l4_data[0], flow.l4_data[1],flow.l4_data[2], flow.l4_data[3],flow.l4_data[4], flow.l4_data[5],flow.l4_data[6], flow.l4_data[7]); - return NF_DROP; - } - - - if (skb_is_nonlinear(skb) && flow.l4_len < MAX_AF_SUPPORT_DATA_LEN) - { - flow.l4_data = read_skb(skb, flow.l4_data - skb->data, flow.l4_len); - if (!flow.l4_data) - return NF_ACCEPT; - malloc_data = 1; - } - dpi_main(skb, &flow); - - update_url_visiting_info(client, &flow); - if (flow.client_hello) { - ct->mark |= NF_CLIENT_HELLO_BIT; - } - else { - ct->mark &= ~NF_CLIENT_HELLO_BIT; - } - - - if (!match_feature(&flow) && 0 == g_app_filter_mode) - goto EXIT; - - - if (TEST_MODE()){ - if (flow.l4_protocol == IPPROTO_UDP){ - if (flow.dport > 5000 && flow.l4_len > 16 && flow.l4_len < 500){ - printk(" %s %pI4(%d)--> %pI4(%d) len = %d [%02x %02x %02x %02x %02x %02x %02x %02x] \n ", IPPROTO_TCP == flow.l4_protocol ? "tcp" : "udp", - &flow.src, flow.sport, &flow.dst, flow.dport, flow.l4_len, flow.l4_data[0], flow.l4_data[1],flow.l4_data[2], flow.l4_data[3],flow.l4_data[4], flow.l4_data[5],flow.l4_data[6], flow.l4_data[7]); - } - } - } - - - ct->mark = (ct->mark & 0xFFFF0000) | (flow.app_id & 0xFFFF); - if (flow.feature && flow.feature->ignore){ - ct->mark |= NF_IGNORE_BIT; - flow.ignore = 1; - AF_LMT_DEBUG("gateway set ignore bit, ct->mark = %x\n", ct->mark); - } - - if (g_oaf_filter_enable){ - if (match_app_filter_rule(flow.app_id, client)) - { - ct->mark |= NF_DROP_BIT; - flow.drop = 1; - AF_LMT_INFO("##Drop app %s flow, appid is %d\n", flow.app_name, flow.app_id); - if (skb->protocol == htons(ETH_P_IP) && g_tcp_rst){ - #if LINUX_VERSION_CODE > KERNEL_VERSION(5,10,197) - nf_send_reset(&init_net, skb->sk, skb, NF_INET_PRE_ROUTING); - #elif LINUX_VERSION_CODE > KERNEL_VERSION(4,4,1) - //5.4 kernel panic - //nf_send_reset(&init_net, skb, NF_INET_PRE_ROUTING); - #else - nf_send_reset(skb, NF_INET_PRE_ROUTING); - #endif - } - ret = NF_DROP; - } - } - - if (g_oaf_record_enable){ - AF_CLIENT_LOCK_W(); - if (!flow.ignore){ - af_update_client_app_info(client, flow.app_id, flow.drop); - } - - AF_CLIENT_UNLOCK_W(); - AF_LMT_INFO("match %s %pI4(%d)--> %pI4(%d) len = %d, %d\n ", IPPROTO_TCP == flow.l4_protocol ? "tcp" : "udp", - &flow.src, flow.sport, &flow.dst, flow.dport, skb->len, flow.app_id); - } - -EXIT: - if (malloc_data) - { - if (flow.l4_data) - { - kfree(flow.l4_data); - } - } - return ret; -} - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) -static u_int32_t app_filter_hook(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ -#else -static u_int32_t app_filter_hook(unsigned int hook, - struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - int (*okfn)(struct sk_buff *)) -{ -#endif - - if (AF_MODE_BYPASS == af_work_mode) - return NF_ACCEPT; - return app_filter_hook_gateway_handle(skb, skb->dev); -} - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) -static u_int32_t app_filter_by_pass_hook(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ -#else -static u_int32_t app_filter_by_pass_hook(unsigned int hook, - struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - int (*okfn)(struct sk_buff *)) -{ -#endif - if (AF_MODE_GATEWAY == af_work_mode) - return NF_ACCEPT; - return app_filter_hook_bypass_handle(skb, skb->dev); -} - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) -static struct nf_hook_ops app_filter_ops[] __read_mostly = { - { - .hook = app_filter_hook, - .pf = NFPROTO_INET, - .hooknum = NF_INET_FORWARD, - .priority = NF_IP_PRI_MANGLE + 1, - - }, - { - .hook = app_filter_by_pass_hook, - .pf = NFPROTO_INET, - .hooknum = NF_INET_PRE_ROUTING, - .priority = NF_IP_PRI_MANGLE + 1, - }, -}; -#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) -static struct nf_hook_ops app_filter_ops[] __read_mostly = { - { - .hook = app_filter_hook, - .pf = NFPROTO_IPV4, - .hooknum = NF_INET_FORWARD, - .priority = NF_IP_PRI_MANGLE + 1, - }, - { - .hook = app_filter_by_pass_hook, - .pf = NFPROTO_IPV4, - .hooknum = NF_INET_PRE_ROUTING, - .priority = NF_IP_PRI_MANGLE + 1, - }, - { - .hook = app_filter_hook, - .pf = NFPROTO_IPV6, - .hooknum = NF_INET_FORWARD, - .priority = NF_IP_PRI_MANGLE + 1, - - }, - { - .hook = app_filter_by_pass_hook, - .pf = NFPROTO_IPV6, - .hooknum = NF_INET_PRE_ROUTING, - .priority = NF_IP_PRI_MANGLE + 1, - }, -}; -#else -static struct nf_hook_ops app_filter_ops[] __read_mostly = { - { - .hook = app_filter_hook, - .owner = THIS_MODULE, - .pf = NFPROTO_IPV4, - .hooknum = NF_INET_FORWARD, - .priority = NF_IP_PRI_MANGLE + 1, - }, - { - .hook = app_filter_hook, - .owner = THIS_MODULE, - .pf = NFPROTO_IPV6, - .hooknum = NF_INET_FORWARD, - .priority = NF_IP_PRI_MANGLE + 1, - }, -}; -#endif - -struct timer_list oaf_timer; -int report_flag = 0; -#define OAF_TIMER_INTERVAL 1 -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) -static void oaf_timer_func(struct timer_list *t) -#else -static void oaf_timer_func(unsigned long ptr) -#endif -{ - static int count = 0; - if (count % 60 == 0) - check_client_expire(); - - count++; - af_conn_clean_timeout(); - - mod_timer(&oaf_timer, jiffies + OAF_TIMER_INTERVAL * HZ); -} - -static void init_oaf_timer(void) -{ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) - timer_setup(&oaf_timer, oaf_timer_func, 0); -#else - setup_timer(&oaf_timer, oaf_timer_func, OAF_TIMER_INTERVAL * HZ); -#endif - mod_timer(&oaf_timer, jiffies + OAF_TIMER_INTERVAL * HZ); - AF_INFO("init oaf timer...ok"); -} - -static void fini_oaf_timer(void) -{ -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 16, 0) - del_timer_sync(&oaf_timer); -#else - timer_delete_sync(&oaf_timer); -#endif - AF_INFO("del oaf timer...ok"); -} - -static struct sock *oaf_sock = NULL; - -#define OAF_EXTRA_MSG_BUF_LEN 128 -int af_send_msg_to_user(char *pbuf, uint16_t len) -{ - struct sk_buff *nl_skb; - struct nlmsghdr *nlh; - int buf_len = OAF_EXTRA_MSG_BUF_LEN + len; - char *msg_buf = NULL; - struct af_msg_hdr *hdr = NULL; - char *p_data = NULL; - int ret; - if (len >= MAX_OAF_NL_MSG_LEN) - return -1; - - msg_buf = kmalloc(buf_len, GFP_ATOMIC); - if (!msg_buf) - return -1; - - memset(msg_buf, 0x0, buf_len); - nl_skb = nlmsg_new(len + sizeof(struct af_msg_hdr), GFP_ATOMIC); - if (!nl_skb) - { - ret = -1; - goto fail; - } - - nlh = nlmsg_put(nl_skb, 0, 0, OAF_NETLINK_ID, len + sizeof(struct af_msg_hdr), 0); - if (nlh == NULL) - { - nlmsg_free(nl_skb); - ret = -1; - goto fail; - } - - hdr = (struct af_msg_hdr *)msg_buf; - hdr->magic = 0xa0b0c0d0; - hdr->len = len; - p_data = msg_buf + sizeof(struct af_msg_hdr); - memcpy(p_data, pbuf, len); - memcpy(nlmsg_data(nlh), msg_buf, len + sizeof(struct af_msg_hdr)); - ret = netlink_unicast(oaf_sock, nl_skb, 999, MSG_DONTWAIT); - -fail: - kfree(msg_buf); - return ret; -} - -static void oaf_user_msg_handle(char *data, int len) -{ - char *msg_data = data + sizeof(af_msg_t); - if (len < sizeof(af_msg_t)) - return; - af_msg_t *msg = (af_msg_t *)data; - AF_INFO("msg action = %d\n", msg->action); - switch (msg->action) - { - case AF_MSG_INIT: - af_client_list_reset_report_num(); - report_flag = 1; - break; - case AF_MSG_ADD_FEATURE: - af_add_feature_msg_handle(msg_data, len - sizeof(af_msg_t)); - break; - case AF_MSG_CLEAN_FEATURE: - AF_INFO("clean feature\n"); - af_clean_feature_list(); - break; - default: - break; - } -} -static void oaf_msg_rcv(struct sk_buff *skb) -{ - struct nlmsghdr *nlh = NULL; - char *umsg = NULL; - void *udata = NULL; - struct af_msg_hdr *af_hdr = NULL; - if (skb->len >= nlmsg_total_size(0)) - { - nlh = nlmsg_hdr(skb); - umsg = NLMSG_DATA(nlh); - af_hdr = (struct af_msg_hdr *)umsg; - if (af_hdr->magic != 0xa0b0c0d0) - return; - if (af_hdr->len <= 0 || af_hdr->len >= MAX_OAF_NETLINK_MSG_LEN) - return; - udata = umsg + sizeof(struct af_msg_hdr); - - if (udata) - oaf_user_msg_handle(udata, af_hdr->len); - } -} - -static int netlink_oaf_init(void) -{ - struct netlink_kernel_cfg nl_cfg = {0}; - nl_cfg.input = oaf_msg_rcv; - oaf_sock = netlink_kernel_create(&init_net, OAF_NETLINK_ID, &nl_cfg); - - if (NULL == oaf_sock) - { - AF_ERROR("init oaf netlink failed, id=%d\n", OAF_NETLINK_ID); - return -1; - } - AF_INFO("init oaf netlink ok, id = %d\n", OAF_NETLINK_ID); - return 0; -} - -static int __init app_filter_init(void) -{ - int err; - af_conn_init(); - netlink_oaf_init(); - af_log_init(); - af_register_dev(); - af_mac_list_init(); - af_whitelist_mac_init(); - - af_init_app_status(); - init_af_client_procfs(); - af_client_init(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) - err = nf_register_net_hooks(&init_net, app_filter_ops, ARRAY_SIZE(app_filter_ops)); -#else - err = nf_register_hooks(app_filter_ops, ARRAY_SIZE(app_filter_ops)); -#endif - if (err) - { - AF_ERROR("oaf register filter hooks failed!\n"); - } - init_oaf_timer(); - printk("oaf: Driver ver. %s - Copyright(c) 2019-2026, destan19(TT), \n", AF_VERSION); - printk("oaf: init ok\n"); - return 0; -} - -static void app_filter_fini(void) -{ - AF_INFO("app filter module exit\n"); - fini_oaf_timer(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) - nf_unregister_net_hooks(&init_net, app_filter_ops, ARRAY_SIZE(app_filter_ops)); -#else - nf_unregister_hooks(app_filter_ops, ARRAY_SIZE(app_filter_ops)); -#endif - finit_af_client_procfs(); - af_clean_feature_list(); - af_mac_list_flush(); - af_whitelist_mac_flush(); - af_unregister_dev(); - af_log_exit(); - af_client_exit(); - if (oaf_sock) - netlink_kernel_release(oaf_sock); - af_conn_exit(); - return; -} - -module_init(app_filter_init); -module_exit(app_filter_fini); diff --git a/oaf/src/app_filter.h b/oaf/src/app_filter.h deleted file mode 100644 index 945787fe..00000000 --- a/oaf/src/app_filter.h +++ /dev/null @@ -1,178 +0,0 @@ -#ifndef APP_FILTER_H -#define APP_FILTER_H - -#define AF_VERSION "5.3.3" -#define AF_FEATURE_CONFIG_FILE "/tmp/feature.cfg" - -#define MAX_DPI_PKT_NUM 64 -#define MIN_HTTP_DATA_LEN 16 -#define MAX_APP_NAME_LEN 64 -#define MAX_FEATURE_NUM_PER_APP 16 -#define MIN_FEATURE_STR_LEN 8 -#define MAX_FEATURE_STR_LEN 128 -#define MAX_HOST_URL_LEN 128 -#define MAX_REQUEST_URL_LEN 128 -#define MAX_FEATURE_BITS 16 -#define MAX_POS_INFO_PER_FEATURE 16 -#define MAX_FEATURE_LINE_LEN 600 -#define MIN_FEATURE_LINE_LEN 16 -#define MAX_URL_MATCH_LEN 64 -#define MAX_BYPASS_DPI_PKT_LEN 600 -#define MAX_AF_MAC_HASH_SIZE 64 - -#define HTTP_GET_METHOD_STR "GET" -#define HTTP_POST_METHOD_STR "POST" -#define HTTP_HEADER "HTTP" -#define NIPQUAD(addr) \ - ((unsigned char *)&addr)[0], \ - ((unsigned char *)&addr)[1], \ - ((unsigned char *)&addr)[2], \ - ((unsigned char *)&addr)[3] -#define NIPQUAD_FMT "%u.%u.%u.%u" -#define MAC_ARRAY(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] -#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" - -#define AF_TRUE 1 -#define AF_FALSE 0 - -#define AF_APP_TYPE(a) (a) / 1000 -#define AF_APP_ID(a) (a) % 1000 -#define MAC_ADDR_LEN 6 - -#define HTTPS_URL_OFFSET 9 -#define HTTPS_LEN_OFFSET 7 - -#define MAX_SEARCH_STR_LEN 32 - -enum AF_FEATURE_PARAM_INDEX{ - AF_PROTO_PARAM_INDEX, - AF_SRC_PORT_PARAM_INDEX, - AF_DST_PORT_PARAM_INDEX, - AF_HOST_URL_PARAM_INDEX, - AF_REQUEST_URL_PARAM_INDEX, - AF_DICT_PARAM_INDEX, - AF_STR_PARAM_INDEX, - AF_IGNORE_PARAM_INDEX, -}; - - -#define OAF_NETLINK_ID 29 -#define MAX_OAF_NL_MSG_LEN 1024 - -enum E_MSG_TYPE{ - AF_MSG_INIT, - AF_MSG_ADD_FEATURE, - AF_MSG_CLEAN_FEATURE, - AF_MSG_MAX -}; -enum AF_WORK_MODE { - AF_MODE_GATEWAY, - AF_MODE_BYPASS, - AF_MODE_BRIDGE, -}; -#define MAX_AF_MSG_DATA_LEN 800 -typedef struct af_msg{ - int action; -}af_msg_t; - -struct af_msg_hdr{ - int magic; - int len; -}; - -enum e_http_method{ - HTTP_METHOD_GET = 1, - HTTP_METHOD_POST, -}; -typedef struct http_proto{ - int match; - int method; - char *url_pos; - int url_len; - char *host_pos; - int host_len; - char *data_pos; - int data_len; -}http_proto_t; - -typedef struct https_proto{ - int match; - char *url_pos; - int url_len; -}https_proto_t; - - - - -typedef struct af_pos_info{ - int pos; - unsigned char value; -}af_pos_info_t; - -#define MAX_PORT_RANGE_NUM 5 - -typedef struct range_value -{ - int not ; - int start; - int end; -} range_value_t; - -typedef struct port_info -{ - u_int8_t mode; // 0: match, 1: not match - int num; - range_value_t range_list[MAX_PORT_RANGE_NUM]; -} port_info_t; - -typedef struct af_feature_node{ - struct list_head head; - u_int32_t app_id; - char app_name[MAX_APP_NAME_LEN]; - char feature[MAX_FEATURE_STR_LEN]; - u_int32_t proto; - u_int32_t sport; - u_int32_t dport; - port_info_t dport_info; - char host_url[MAX_HOST_URL_LEN]; - char request_url[MAX_REQUEST_URL_LEN]; - int pos_num; - char search_str[MAX_SEARCH_STR_LEN]; - int ignore; - af_pos_info_t pos_info[MAX_POS_INFO_PER_FEATURE]; -}af_feature_node_t; - - - - -typedef struct flow_info{ - struct nf_conn *ct; - u_int32_t src; - u_int32_t dst; - struct in6_addr *src6; - struct in6_addr *dst6; - int l4_protocol; - u_int16_t sport; - u_int16_t dport; - unsigned char *l4_data; - int l4_len; - http_proto_t http; - https_proto_t https; - u_int32_t app_id; - u_int8_t app_name[MAX_APP_NAME_LEN]; - u_int8_t drop; - u_int8_t ignore; - u_int8_t dir; - u_int16_t total_len; - u_int8_t client_hello; - af_feature_node_t *feature; -}flow_info_t; - - - -int regexp_match(char *reg, char *text); -int hash_mac(unsigned char *mac); -char *ipv6_to_str(const struct in6_addr *addr, char *str); -int af_send_msg_to_user(char *pbuf, uint16_t len); - -#endif diff --git a/oaf/src/fwx.h b/oaf/src/fwx.h new file mode 100644 index 00000000..5e0a718b --- /dev/null +++ b/oaf/src/fwx.h @@ -0,0 +1,320 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_H__ +#define __FWX_H__ +#define FWX_VERSION "6.0.1" +#define MAX_FWX_NL_MSG_LEN 1024 +#define FWX_TIMER_INTERVAL 1 +#define MAX_HOST_LEN 40 +#define MIN_HOST_LEN 4 +#define MAX_FWX_NETLINK_MSG_LEN 1024 +#define MAX_MATCH_PKT_NUM 20 +#define FWX_NETLINK_ID 29 + +#define MAX_NETLINK_MSG_LEN 1024 + + +#include +#include +#include + +#define AF_FEATURE_CONFIG_FILE "/tmp/feature.cfg" + +#define MAX_DPI_PKT_NUM 256 +#define MIN_HTTP_DATA_LEN 16 +#define MAX_APP_NAME_LEN 64 +#define MAX_FEATURE_NUM_PER_APP 16 +#define MIN_FEATURE_STR_LEN 8 +#define MAX_FEATURE_STR_LEN 128 +#define MAX_HOST_URL_LEN 128 +#define MAX_REQUEST_URL_LEN 128 +#define MAX_FEATURE_BITS 16 +#define MAX_POS_INFO_PER_FEATURE 16 +#define MAX_FEATURE_LINE_LEN 800 +#define MIN_FEATURE_LINE_LEN 16 +#define MAX_URL_MATCH_LEN 64 +#define MAX_BYPASS_DPI_PKT_LEN 600 + +#define FWX_QUIC_PROTO 10 +#define DNS_PORT 53 +#define DNS_TYPE_HTTPS 65 +#define DNS_HEADER_LEN 12 +#define DNS_TCP_PREFIX_LEN 2 +#define MAX_DNS_DOMAIN_LEN MAX_HOST_URL_LEN +#define MAX_DNS_QUERY_NUM 16 + +extern u_int32_t fwx_log_level; + + + +#define HTTP_GET_METHOD_STR "GET" +#define HTTP_POST_METHOD_STR "POST" +#define HTTP_HEADER "HTTP" +#define NIPQUAD(addr) \ + ((unsigned char *)&addr)[0], \ + ((unsigned char *)&addr)[1], \ + ((unsigned char *)&addr)[2], \ + ((unsigned char *)&addr)[3] +#define NIPQUAD_FMT "%u.%u.%u.%u" +#define MAC_ARRAY(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] +#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" + +#define AF_TRUE 1 +#define AF_FALSE 0 + +#define AF_APP_TYPE(a) (a) / 1000 +#define AF_APP_ID(a) (a) % 1000 +#define MAC_ADDR_LEN 6 + +#define HTTPS_URL_OFFSET 9 +#define HTTPS_LEN_OFFSET 7 + +#define MAX_SEARCH_STR_LEN 32 + +enum AF_FEATURE_PARAM_INDEX{ + AF_PROTO_PARAM_INDEX, + AF_SRC_PORT_PARAM_INDEX, + AF_DST_PORT_PARAM_INDEX, + AF_HOST_URL_PARAM_INDEX, + AF_REQUEST_URL_PARAM_INDEX, + AF_DICT_PARAM_INDEX, + AF_STR_PARAM_INDEX, + AF_IGNORE_PARAM_INDEX, +}; + + +#define OAF_NETLINK_ID 29 +#define MAX_OAF_NL_MSG_LEN 1024 + + +enum E_FWX_NL_MSG_TYPE +{ + FWX_NL_MSG_INIT, + FWX_NL_MSG_ADD_FEATURE, + FWX_NL_MSG_CLEAN_FEATURE, + FWX_NL_MSG_FEATURE_LOAD_DONE, + FWX_NL_MSG_MAX +}; + +enum AF_WORK_MODE { + AF_MODE_GATEWAY, + AF_MODE_BYPASS, + AF_MODE_BRIDGE, +}; +#define MAX_AF_MSG_DATA_LEN 800 +typedef struct af_msg{ + int action; +}af_msg_t; + +struct af_msg_hdr{ + int magic; + int len; +}; + +enum e_http_method{ + HTTP_METHOD_GET = 1, + HTTP_METHOD_POST, +}; +typedef struct http_proto{ + int match; + int method; + char *url_pos; + int url_len; + char *host_pos; + int host_len; + char *data_pos; + int data_len; +}http_proto_t; + +typedef struct https_proto{ + int match; + char *url_pos; + int url_len; +}https_proto_t; + + + + +typedef struct af_pos_info{ + int pos; + unsigned char value; +}af_pos_info_t; + +#define MAX_PORT_RANGE_NUM 5 + +typedef struct range_value +{ + int not ; + int start; + int end; +} range_value_t; + +typedef struct port_info +{ + u_int8_t mode; // 0: match, 1: not match + int num; + range_value_t range_list[MAX_PORT_RANGE_NUM]; +} port_info_t; + +typedef struct dns_proto{ + int match; + int query_num; + int qdcount; + u_int16_t qtype[MAX_DNS_QUERY_NUM]; + char domain[MAX_DNS_QUERY_NUM][MAX_DNS_DOMAIN_LEN]; +}dns_proto_t; + + + +typedef struct af_feature_node{ + struct list_head head; + u_int32_t app_id; + char app_name[MAX_APP_NAME_LEN]; + char feature[MAX_FEATURE_STR_LEN]; + u_int32_t proto; + u_int32_t sport; + u_int32_t dport; + port_info_t dport_info; + char host_url[MAX_HOST_URL_LEN]; + char request_url[MAX_REQUEST_URL_LEN]; + int pos_num; + char search_str[MAX_SEARCH_STR_LEN]; + int ignore; + af_pos_info_t pos_info[MAX_POS_INFO_PER_FEATURE]; +}af_feature_node_t; + +typedef struct af_mac_info { + struct list_head hlist; + unsigned char mac[MAC_ADDR_LEN]; +}af_mac_info_t; + +typedef struct flow_info{ + struct nf_conn *ct; + u_int32_t src; + u_int32_t dst; + struct in6_addr *src6; + struct in6_addr *dst6; + int l4_protocol; + u_int16_t sport; + u_int16_t dport; + unsigned char *l4_data; + int l4_len; + http_proto_t http; + https_proto_t https; + dns_proto_t dns; + u_int32_t app_id; + u_int8_t app_name[MAX_APP_NAME_LEN]; + u_int8_t drop; + u_int8_t ignore; + u_int8_t match_by_dns; + u_int8_t dir; + u_int16_t total_len; + u_int8_t client_hello; + af_feature_node_t *feature; +}flow_info_t; + + +#define MAX_ACTIVE_APP_LIST_SIZE 10 +#define MAX_ACTIVE_HOST_LIST_SIZE 10 + + +typedef struct active_app_node { + struct list_head list; + u_int32_t app_id; + unsigned char mac[MAC_ADDR_LEN]; + u_int32_t src_ip; + u_int32_t dst_ip; + struct in6_addr src_ip6; + struct in6_addr dst_ip6; + u_int16_t src_port; + u_int16_t dst_port; + u_int8_t l4_protocol; + u_int8_t drop; + u_int8_t proto_type; + char host[32]; + char uri[32]; + u_int32_t update_time; +} active_app_node_t; + + +typedef struct active_host_node { + struct list_head list; + char host[64]; + unsigned char mac[MAC_ADDR_LEN]; + u_int32_t src_ip; + u_int32_t dst_ip; + struct in6_addr src_ip6; + struct in6_addr dst_ip6; + u_int16_t src_port; + u_int16_t dst_port; + u_int8_t l4_protocol; + u_int8_t drop; + u_int8_t proto_type; + u_int32_t update_time; +} active_host_node_t; + +int regexp_match(char *reg, char *text); +int is_user_match_enable(void); + + +struct af_client_info; +typedef struct af_client_info af_client_info_t; + +void af_update_active_app_list(af_client_info_t *client, flow_info_t *flow); +active_app_node_t *af_find_active_app(u_int32_t app_id); +void af_clear_active_app_list(void); + +void af_update_active_host_list(af_client_info_t *client, flow_info_t *flow); +active_host_node_t *af_find_active_host(const char *host); +void af_clear_active_host_list(void); + + +enum FWX_PKT_DIR +{ + PKT_DIR_DOWN, + PKT_DIR_UP +}; + +typedef struct fwx_msg +{ + int action; + void *data; +} fwx_msg_t; + +struct fwx_msg_hdr +{ + int len; + int msg_type; +}; + + +extern int af_log_lvl; + +#define LOG(level, fmt, ...) do { \ + if ((level) <= af_log_lvl) { \ + printk(KERN_CONT"%s %d " fmt, __func__, __LINE__, ##__VA_ARGS__); \ + } \ +} while (0) + +#define LLOG(level, fmt, ...) do { \ + if ((level) <= af_log_lvl) { \ + pr_info_ratelimited(KERN_CONT "%s %d " fmt, __func__, __LINE__, ##__VA_ARGS__); \ + } \ +} while (0) + + +#define AF_ERROR(...) LOG(0, ##__VA_ARGS__) +#define AF_WARN(...) LOG(1, ##__VA_ARGS__) +#define AF_INFO(...) LOG(2, ##__VA_ARGS__) +#define AF_DEBUG(...) LOG(3, ##__VA_ARGS__) + +#define AF_LMT_ERROR(...) LLOG(0, ##__VA_ARGS__) +#define AF_LMT_WARN(...) LLOG(1, ##__VA_ARGS__) +#define AF_LMT_INFO(...) LLOG(2, ##__VA_ARGS__) +#define AF_LMT_DEBUG(...) LLOG(3, ##__VA_ARGS__) + + +#endif diff --git a/oaf/src/fwx_app_filter.c b/oaf/src/fwx_app_filter.c new file mode 100644 index 00000000..30060769 --- /dev/null +++ b/oaf/src/fwx_app_filter.c @@ -0,0 +1,819 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "k_json.h" +#include "fwx.h" +#include "fwx_app_filter.h" +#include "fwx_mac.h" +#include "fwx_log.h" + +DEFINE_RWLOCK(app_filter_lock); + +#define app_filter_read_lock() read_lock_bh(&app_filter_lock); +#define app_filter_read_unlock() read_unlock_bh(&app_filter_lock); +#define app_filter_write_lock() write_lock_bh(&app_filter_lock); +#define app_filter_write_unlock() write_unlock_bh(&app_filter_lock); +int g_appfilter_enable = 0; +u_int32_t g_appfilter_update_jiffies = 0; + +static int g_app_rule_count = 0; +static LIST_HEAD(app_filter_rule_list); + + +static mac_config_t g_app_filter_whitelist; + + +static void app_id_config_init(app_id_config_t *config) { + int i; + for (i = 0; i < APPID_HASH_SIZE; i++) { + INIT_HLIST_HEAD(&config->hash_table[i]); + } + config->count = 0; +} + + +static void flush_app_id_list(app_id_config_t *config) { + int i; + app_id_node_t *node; + struct hlist_node *n; + + for (i = 0; i < APPID_HASH_SIZE; i++) { + hlist_for_each_entry_safe(node, n, &config->hash_table[i], hlist) { + hlist_del(&node->hlist); + kfree(node); + } + } + config->count = 0; +} + + +static app_id_node_t *find_app_id_node(app_id_config_t *config, int app_id) { + int hash = app_id % APPID_HASH_SIZE; + app_id_node_t *node; + + hlist_for_each_entry(node, &config->hash_table[hash], hlist) { + if (node->app_id == app_id) { + return node; + } + } + return NULL; +} + + +static int add_app_id_node(app_id_config_t *config, int app_id) { + app_id_node_t *node; + int hash; + + + if (find_app_id_node(config, app_id)) { + return 0; // 已存在,返回成功 + } + + if (config->count >= MAX_APP_ID_PER_RULE) { + AF_ERROR("app id count exceeds limit\n"); + return -1; + } + + node = kmalloc(sizeof(app_id_node_t), GFP_ATOMIC); + if (!node) { + AF_ERROR("kmalloc app_id_node failed\n"); + return -1; + } + + node->app_id = app_id; + hash = app_id % APPID_HASH_SIZE; + hlist_add_head(&node->hlist, &config->hash_table[hash]); + config->count++; + + return 0; +} + +static int add_app_id_token_to_rule(app_filter_rule_t *rule, cJSON *app_id_obj) +{ + int start_id = 0, end_id = 0; + int single_id = 0; + int i = 0; + const char *token = NULL; + + + if (!rule || !app_id_obj) { + return -1; + } + + if (app_id_obj->type == cJSON_Number) { + if (app_id_obj->valueint > 0) { + return add_app_id_node(&rule->app_id_list, app_id_obj->valueint); + } + return -1; + } + + if (app_id_obj->type != cJSON_String || !app_id_obj->valuestring) { + return -1; + } + + token = app_id_obj->valuestring; + if (sscanf(token, "%d-%d", &start_id, &end_id) == 2) { + if (start_id <= 0 || end_id < start_id) { + AF_ERROR("invalid app_id range: %s\n", token); + return -1; + } + for (i = start_id; i <= end_id; i++) { + AF_INFO("token parse %s, id = %d\n", token, i); + if (add_app_id_node(&rule->app_id_list, i) < 0) { + return -1; + } + } + return 0; + } + + if (kstrtoint(token, 10, &single_id) == 0 && single_id > 0) { + return add_app_id_node(&rule->app_id_list, single_id); + } + + AF_ERROR("invalid app_id token: %s\n", token); + return -1; +} + +static int del_app_id_token_from_rule(int rule_id, cJSON *app_id_obj) +{ + int start_id = 0, end_id = 0; + int single_id = 0; + int i = 0; + const char *token = NULL; + + if (!app_id_obj) { + return -1; + } + + if (app_id_obj->type == cJSON_Number) { + return fwx_del_app_id_from_rule(rule_id, app_id_obj->valueint); + } + + if (app_id_obj->type != cJSON_String || !app_id_obj->valuestring) { + return -1; + } + + token = app_id_obj->valuestring; + if (sscanf(token, "%d-%d", &start_id, &end_id) == 2) { + if (start_id <= 0 || end_id < start_id) { + AF_ERROR("invalid app_id range for delete: %s\n", token); + return -1; + } + for (i = start_id; i <= end_id; i++) { + fwx_del_app_id_from_rule(rule_id, i); + } + return 0; + } + + if (kstrtoint(token, 10, &single_id) == 0 && single_id > 0) { + return fwx_del_app_id_from_rule(rule_id, single_id); + } + + AF_ERROR("invalid app_id token for delete: %s\n", token); + return -1; +} + +int fwx_app_filter_init(void) { + app_filter_write_lock(); + INIT_LIST_HEAD(&app_filter_rule_list); + g_app_rule_count = 0; + fwx_mac_config_init(&g_app_filter_whitelist); + app_filter_write_unlock(); + + AF_INFO("app filter init...ok\n"); + return 0; +} + +void fwx_app_filter_exit(void) { + app_filter_rule_t *rule, *next; + app_filter_write_lock(); + list_for_each_entry_safe(rule, next, &app_filter_rule_list, list) { + flush_app_id_list(&rule->app_id_list); + fwx_flush_mac_list(&rule->mac_list); + list_del(&rule->list); + kfree(rule); + } + g_app_rule_count = 0; + fwx_flush_mac_list(&g_app_filter_whitelist); + app_filter_write_unlock(); + AF_INFO("app filter exit...ok\n"); +} + +app_filter_rule_t *fwx_find_app_filter_rule(int rule_id) { + app_filter_rule_t *rule; + list_for_each_entry(rule, &app_filter_rule_list, list) { + if (rule->rule_id == rule_id) { + return rule; + } + } + return NULL; +} + +void fwx_update_appfilter_jiffies(void){ + g_appfilter_update_jiffies = jiffies; +} + + + +int fwx_add_app_filter_rule(int rule_id) { + app_filter_rule_t *rule; + + if (g_app_rule_count >= MAX_APP_FILTER_RULE_NUM) { + AF_ERROR("app filter rule count exceeds limit\n"); + return -1; + } + + if (fwx_find_app_filter_rule(rule_id)) { + AF_ERROR("app filter rule %d already exists\n", rule_id); + return -1; + } + + rule = kmalloc(sizeof(app_filter_rule_t), GFP_ATOMIC); + if (!rule) { + AF_ERROR("kmalloc app filter rule failed\n"); + return -1; + } + + rule->rule_id = rule_id; + rule->enable = 1; + rule->filter_quic = 0; + fwx_mac_config_init(&rule->mac_list); + app_id_config_init(&rule->app_id_list); + INIT_LIST_HEAD(&rule->list); + + app_filter_write_lock(); + list_add(&rule->list, &app_filter_rule_list); + g_app_rule_count++; + app_filter_write_unlock(); + + AF_INFO("add app filter rule %d ok\n", rule_id); + return 0; +} + +int fwx_del_app_filter_rule(int rule_id) { + app_filter_rule_t *rule; + + app_filter_write_lock(); + rule = fwx_find_app_filter_rule(rule_id); + if (rule) { + flush_app_id_list(&rule->app_id_list); + fwx_flush_mac_list(&rule->mac_list); + list_del(&rule->list); + kfree(rule); + g_app_rule_count--; + app_filter_write_unlock(); + AF_INFO("del app filter rule %d ok\n", rule_id); + return 0; + } + app_filter_write_unlock(); + + AF_ERROR("app filter rule %d not found\n", rule_id); + return -1; +} + +int fwx_add_app_id_to_rule(int rule_id, int app_id) { + app_filter_rule_t *rule; + + app_filter_write_lock(); + rule = fwx_find_app_filter_rule(rule_id); + if (rule) { + add_app_id_node(&rule->app_id_list, app_id); + app_filter_write_unlock(); + return 0; + } + app_filter_write_unlock(); + + AF_ERROR("app filter rule %d not found\n", rule_id); + return -1; +} + +int fwx_del_app_id_from_rule(int rule_id, int app_id) { + app_filter_rule_t *rule; + app_id_node_t *node; + int hash; + + app_filter_write_lock(); + rule = fwx_find_app_filter_rule(rule_id); + if (rule) { + hash = app_id % APPID_HASH_SIZE; + hlist_for_each_entry(node, &rule->app_id_list.hash_table[hash], hlist) { + if (node->app_id == app_id) { + hlist_del(&node->hlist); + kfree(node); + rule->app_id_list.count--; + app_filter_write_unlock(); + return 0; + } + } + } + app_filter_write_unlock(); + + AF_ERROR("app_id %d or rule %d not found\n", app_id, rule_id); + return -1; +} + + +app_filter_rule_t *fwx_match_app_filter_rule(int app_id, const unsigned char *mac) { + app_filter_rule_t *rule; + app_id_node_t *node; + struct mac_node *mac_node; + int i; + int mac_list_empty; + + app_filter_read_lock(); + list_for_each_entry(rule, &app_filter_rule_list, list) { + if (!rule->enable) { + continue; + } + + + + + mac_node = fwx_find_mac_node(&rule->mac_list, mac); + if (!mac_node) { + + mac_list_empty = 1; + for (i = 0; i < MAC_HASH_SIZE; i++) { + if (!hlist_empty(&rule->mac_list.hash_table[i])) { + mac_list_empty = 0; + break; + } + } + if (!mac_list_empty) { + + continue; + } + } + + + if (app_id == FWX_QUIC_PROTO) { + if (rule->filter_quic == 1) { + app_filter_read_unlock(); + return rule; + } + continue; + } + + node = find_app_id_node(&rule->app_id_list, app_id); + if (node) { + app_filter_read_unlock(); + return rule; + } + } + app_filter_read_unlock(); + return NULL; +} + +int fwx_api_add_app_filter_rule(cJSON *data_obj) { + cJSON *rule_id_obj; + + if (!data_obj) { + return -1; + } + + rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id"); + + if (!rule_id_obj) { + AF_ERROR("invalid rule format\n"); + return -1; + } + + fwx_update_appfilter_jiffies(); + + if (fwx_add_app_filter_rule(rule_id_obj->valueint) < 0) { + return -1; + } + AF_INFO("add app filter rule %d ok\n", rule_id_obj->valueint); + + return 0; +} + +int fwx_api_mod_app_filter_rule(cJSON *data_obj) { + int i; + cJSON *rule_id_obj; + cJSON *app_id_array; + cJSON *app_id_obj; + cJSON *action_obj; + cJSON *enable_obj; + cJSON *filter_quic_obj; + app_filter_rule_t *rule = NULL; + + if (!data_obj) { + return -1; + } + + rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id"); + action_obj = cJSON_GetObjectItem(data_obj, "app_action"); + cJSON *mac_action_obj = cJSON_GetObjectItem(data_obj, "mac_action"); + + if (!rule_id_obj) { + AF_ERROR("rule_id not found\n"); + return -1; + } + + + rule = fwx_find_app_filter_rule(rule_id_obj->valueint); + if (!rule) { + AF_ERROR("rule %d not found\n", rule_id_obj->valueint); + return -1; + } + + + if (mac_action_obj) { + if (mac_action_obj->valueint == 1 || mac_action_obj->valueint == 2) { + cJSON *mac_array = cJSON_GetObjectItem(data_obj, "mac_list"); + if (mac_array) { + app_filter_write_lock(); + if (mac_action_obj->valueint == 1) { // flush old + fwx_flush_mac_list(&rule->mac_list); + } + for (i = 0; i < cJSON_GetArraySize(mac_array); i++) { + cJSON *mac_obj = cJSON_GetArrayItem(mac_array, i); + u8 mac_bin[ETH_ALEN] = {0}; + if (mac_obj && mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + fwx_add_mac_node(&rule->mac_list, mac_bin); + } + } + app_filter_write_unlock(); + } + } else if (mac_action_obj->valueint == 3) { + cJSON *mac_obj = cJSON_GetObjectItem(data_obj, "mac"); + if (mac_obj) { + app_filter_write_lock(); + u8 mac_bin[ETH_ALEN] = {0}; + if (mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + fwx_add_mac_node(&rule->mac_list, mac_bin); + } + app_filter_write_unlock(); + } + } else { + + app_filter_write_lock(); + fwx_flush_mac_list(&rule->mac_list); + app_filter_write_unlock(); + } + } + + + if (action_obj) { + if (action_obj->valueint == 1 || action_obj->valueint == 2) { + + app_id_array = cJSON_GetObjectItem(data_obj, "app_id_list"); + if (app_id_array) { + app_filter_write_lock(); + if (action_obj->valueint == 1) { // flush old + flush_app_id_list(&rule->app_id_list); + } + for (i = 0; i < cJSON_GetArraySize(app_id_array); i++) { + app_id_obj = cJSON_GetArrayItem(app_id_array, i); + if (app_id_obj) { + add_app_id_token_to_rule(rule, app_id_obj); + } + } + app_filter_write_unlock(); + } + } else if (action_obj->valueint == 3) { + + app_id_obj = cJSON_GetObjectItem(data_obj, "app_id"); + if (app_id_obj) { + app_filter_write_lock(); + add_app_id_token_to_rule(rule, app_id_obj); + app_filter_write_unlock(); + } + } else { + + app_filter_write_lock(); + flush_app_id_list(&rule->app_id_list); + app_filter_write_unlock(); + } + } + + enable_obj = cJSON_GetObjectItem(data_obj, "enable"); + if (enable_obj) { + rule->enable = enable_obj->valueint; + } + + filter_quic_obj = cJSON_GetObjectItem(data_obj, "filter_quic"); + if (filter_quic_obj) { + rule->filter_quic = (filter_quic_obj->valueint == 1) ? 1 : 0; + } + + fwx_update_appfilter_jiffies(); + return 0; +} + +int fwx_api_del_app_filter_rule(cJSON *data_obj) { + cJSON *rule_id_obj; + cJSON *app_id_obj; + + if (!data_obj) { + return -1; + } + + rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id"); + if (!rule_id_obj) { + AF_ERROR("rule_id not found\n"); + return -1; + } + + fwx_update_appfilter_jiffies(); + app_id_obj = cJSON_GetObjectItem(data_obj, "app_id"); + if (app_id_obj) { + + return del_app_id_token_from_rule(rule_id_obj->valueint, app_id_obj); + } else { + + return fwx_del_app_filter_rule(rule_id_obj->valueint); + } +} + +int fwx_api_dump_app_filter_rule(cJSON *data_obj) { + app_filter_rule_t *rule; + app_id_node_t *node; + struct mac_node *mac_node; + int app_count = 0; + int mac_count = 0; + int i; + + if (!data_obj) { + return -1; + } + + cJSON *rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id"); + + app_filter_read_lock(); + + + printk("\n"); + printk("+--------+-------+-----------+------------------+------------------+\n"); + printk("| RuleID | Enable| FilterQUIC| MAC List | App ID List |\n"); + printk("+--------+-------+-----------+------------------+------------------+\n"); + + if (rule_id_obj) { + rule = fwx_find_app_filter_rule(rule_id_obj->valueint); + if (rule) { + + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(mac_node, &rule->mac_list.hash_table[i], hlist) { + mac_count++; + } + } + for (i = 0; i < APPID_HASH_SIZE; i++) { + hlist_for_each_entry(node, &rule->app_id_list.hash_table[i], hlist) { + app_count++; + } + } + + + printk(KERN_CONT "| %-6d | %-5d | %-9d | ", rule->rule_id, rule->enable, rule->filter_quic); + + + if (mac_count == 0) { + printk(KERN_CONT "%-16s | ", "(all MACs)"); + } else { + int total_mac_count = mac_count; + mac_count = 0; + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(mac_node, &rule->mac_list.hash_table[i], hlist) { + if (mac_count > 0) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "%pM", mac_node->mac); + mac_count++; + if (mac_count >= 32) { + if (mac_count < total_mac_count) { + printk(KERN_CONT "..."); + } + break; + } + } + } + printk(KERN_CONT " | "); + } + + + if (app_count == 0) { + printk(KERN_CONT "%-16s |\n", "(empty)"); + } else { + int total_app_count = app_count; + int printed_count = 0; + int should_break = 0; + app_count = 0; + for (i = 0; i < APPID_HASH_SIZE && !should_break; i++) { + hlist_for_each_entry(node, &rule->app_id_list.hash_table[i], hlist) { + if (printed_count > 0) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "%d", node->app_id); + printed_count++; + app_count++; + if (printed_count >= 128) { + if (app_count < total_app_count) { + printk(KERN_CONT "..."); + } + should_break = 1; + break; + } + } + } + printk(KERN_CONT " |\n"); + } + } + } else { + list_for_each_entry(rule, &app_filter_rule_list, list) { + app_count = 0; + mac_count = 0; + + + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(mac_node, &rule->mac_list.hash_table[i], hlist) { + mac_count++; + } + } + for (i = 0; i < 256; i++) { + hlist_for_each_entry(node, &rule->app_id_list.hash_table[i], hlist) { + app_count++; + } + } + + + printk(KERN_CONT "| %-6d | %-5d | %-9d | ", rule->rule_id, rule->enable, rule->filter_quic); + + + if (mac_count == 0) { + printk(KERN_CONT "%-16s | ", "(all MACs)"); + } else { + int total_mac_count = mac_count; + mac_count = 0; + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(mac_node, &rule->mac_list.hash_table[i], hlist) { + if (mac_count > 0) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "%pM", mac_node->mac); + mac_count++; + if (mac_count >= 32) { + if (mac_count < total_mac_count) { + printk(KERN_CONT "..."); + } + break; + } + } + } + printk(KERN_CONT " | "); + } + + + if (app_count == 0) { + printk(KERN_CONT "%-16s |\n", "(empty)"); + } else { + int total_app_count = app_count; + int printed_count = 0; + int should_break = 0; + app_count = 0; + for (i = 0; i < APPID_HASH_SIZE && !should_break; i++) { + hlist_for_each_entry(node, &rule->app_id_list.hash_table[i], hlist) { + if (printed_count > 0) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "%d", node->app_id); + printed_count++; + app_count++; + if (printed_count >= 128) { + if (app_count < total_app_count) { + printk(KERN_CONT "..."); + } + should_break = 1; + break; + } + } + } + printk(KERN_CONT " |\n"); + } + } + } + + printk("+--------+-------+-----------+------------------+------------------+\n"); + + + printk("\n"); + printk("App Filter Whitelist:\n"); + printk("+----------------------------------------+\n"); + printk("| MAC Address |\n"); + printk("+----------------------------------------+\n"); + + int total_whitelist_count = 0; + struct mac_node *whitelist_node; + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(whitelist_node, &g_app_filter_whitelist.hash_table[i], hlist) { + total_whitelist_count++; + } + } + + if (total_whitelist_count == 0) { + printk(KERN_CONT "| %-38s |\n", "(empty)"); + } else { + int printed_count = 0; + int should_break = 0; + + for (i = 0; i < MAC_HASH_SIZE && !should_break; i++) { + hlist_for_each_entry(whitelist_node, &g_app_filter_whitelist.hash_table[i], hlist) { + printk(KERN_CONT "| %-38pM |\n", whitelist_node->mac); + printed_count++; + if (printed_count >= 10) { + if (printed_count < total_whitelist_count) { + printk(KERN_CONT "| %-38s |\n", "..."); + } + should_break = 1; + break; + } + } + } + } + + printk("+----------------------------------------+\n"); + printk("Total whitelist entries: %d\n", total_whitelist_count); + + app_filter_read_unlock(); + + return 0; +} + +int fwx_api_flush_app_filter_rule(cJSON *data_obj) { + app_filter_rule_t *rule, *next; + + app_filter_write_lock(); + list_for_each_entry_safe(rule, next, &app_filter_rule_list, list) { + flush_app_id_list(&rule->app_id_list); + list_del(&rule->list); + kfree(rule); + } + g_app_rule_count = 0; + app_filter_write_unlock(); + + fwx_update_appfilter_jiffies(); + return 0; +} + + +int fwx_match_app_filter_whitelist(const unsigned char *mac) { + struct mac_node *node; + int ret = 0; + + app_filter_read_lock(); + node = fwx_find_mac_node(&g_app_filter_whitelist, mac); + ret = (node != NULL); + app_filter_read_unlock(); + + return ret; +} + + +int fwx_api_add_app_filter_whitelist(cJSON *data_obj) { + cJSON *mac_array; + int i; + u8 mac_bin[ETH_ALEN]; + + if (!data_obj) { + return -1; + } + + mac_array = cJSON_GetObjectItem(data_obj, "mac_list"); + if (!mac_array) { + printk("mac_list not found\n"); + return -1; + } + + app_filter_write_lock(); + for (i = 0; i < cJSON_GetArraySize(mac_array); i++) { + cJSON *mac_obj = cJSON_GetArrayItem(mac_array, i); + if (mac_obj && mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + fwx_add_mac_node(&g_app_filter_whitelist, mac_bin); + } + } + app_filter_write_unlock(); + + fwx_update_appfilter_jiffies(); + return 0; +} + + +int fwx_api_flush_app_filter_whitelist(cJSON *data_obj) { + app_filter_write_lock(); + fwx_flush_mac_list(&g_app_filter_whitelist); + app_filter_write_unlock(); + + fwx_update_appfilter_jiffies(); + return 0; +} + diff --git a/oaf/src/fwx_app_filter.h b/oaf/src/fwx_app_filter.h new file mode 100644 index 00000000..a8926cf3 --- /dev/null +++ b/oaf/src/fwx_app_filter.h @@ -0,0 +1,89 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_APP_FILTER_H__ +#define __FWX_APP_FILTER_H__ +#include "k_json.h" +#include "fwx_mac.h" +#include +#include + +#define MAX_APP_FILTER_RULE_NUM 64 +#define MAX_APP_ID_PER_RULE 1024 +#define APPID_HASH_SIZE 256 + +extern u_int32_t g_appfilter_update_jiffies; + +typedef struct app_id_node { + int app_id; + struct hlist_node hlist; +} app_id_node_t; + + +typedef struct app_id_config { + struct hlist_head hash_table[APPID_HASH_SIZE]; + int count; +} app_id_config_t; + + +typedef struct app_filter_rule { + int rule_id; + int enable; + int filter_quic; + mac_config_t mac_list; + app_id_config_t app_id_list; + struct list_head list; +} app_filter_rule_t; + +extern int g_appfilter_enable; + + +int fwx_app_filter_init(void); + + +void fwx_app_filter_exit(void); + + +int fwx_add_app_filter_rule(int rule_id); + + +int fwx_del_app_filter_rule(int rule_id); + + +app_filter_rule_t *fwx_find_app_filter_rule(int rule_id); + + +int fwx_add_app_id_to_rule(int rule_id, int app_id); + + +int fwx_del_app_id_from_rule(int rule_id, int app_id); + + +app_filter_rule_t *fwx_match_app_filter_rule(int app_id, const unsigned char *mac); + + +int fwx_api_add_app_filter_rule(cJSON *data_obj); + + +int fwx_api_del_app_filter_rule(cJSON *data_obj); + + +int fwx_api_dump_app_filter_rule(cJSON *data_obj); + + +int fwx_api_flush_app_filter_rule(cJSON *data_obj); + + +int fwx_api_mod_app_filter_rule(cJSON *data_obj); + + +int fwx_api_add_app_filter_whitelist(cJSON *data_obj); +int fwx_api_flush_app_filter_whitelist(cJSON *data_obj); + + +int fwx_match_app_filter_whitelist(const unsigned char *mac); + +#endif + diff --git a/oaf/src/af_client.c b/oaf/src/fwx_client.c similarity index 58% rename from oaf/src/af_client.c rename to oaf/src/fwx_client.c index 2b772656..73ce0876 100644 --- a/oaf/src/af_client.c +++ b/oaf/src/fwx_client.c @@ -1,7 +1,8 @@ -/* - Author:Derry - Date: 2019/11/12 +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) */ + #include #include #include @@ -21,22 +22,119 @@ #include #include #include +#include +#include +#include -#include "af_client.h" -#include "af_client_fs.h" -#include "af_log.h" -#include "af_utils.h" -#include "app_filter.h" -#include "cJSON.h" +#include "fwx_client.h" +#include "fwx_client_fs.h" +#include "fwx_log.h" +#include "fwx_mac.h" +#include "fwx_mac_filter.h" +#include "fwx_utils.h" +#include "fwx.h" +#include "k_json.h" DEFINE_RWLOCK(af_client_lock); u32 total_client = 0; struct list_head af_client_list_table[MAX_AF_CLIENT_HASH_SIZE]; +int g_max_app_report_count = 3; +int g_min_http_match_count = 3; + +static DEFINE_RWLOCK(record_whitelist_lock); +#define record_whitelist_read_lock() read_lock_bh(&record_whitelist_lock); +#define record_whitelist_read_unlock() read_unlock_bh(&record_whitelist_lock); +#define record_whitelist_write_lock() write_lock_bh(&record_whitelist_lock); +#define record_whitelist_write_unlock() write_unlock_bh(&record_whitelist_lock); +static mac_config_t g_record_whitelist; + + +int af_send_msg_to_user(char *pbuf, uint16_t len); +extern char *ipv6_to_str(const struct in6_addr *addr, char *str); + static void init_client_timer(af_client_info_t *client); static void stop_client_timer(af_client_info_t *client); +static int is_client_mac_filter_blocked(af_client_info_t *node) +{ + if (!node || !g_mac_filter_enable) { + return 0; + } + + if (fwx_match_mac_filter_whitelist(node->mac)) { + return 0; + } + + return fwx_match_mac_filter_rule(node->mac) ? 1 : 0; +} + +int fwx_match_record_whitelist(const unsigned char *mac) +{ + struct mac_node *node; + int ret = 0; + + record_whitelist_read_lock(); + node = fwx_find_mac_node(&g_record_whitelist, mac); + ret = (node != NULL); + record_whitelist_read_unlock(); + + return ret; +} + +static void fwx_sync_record_whitelist_clients(void) +{ + int i; + af_client_info_t *node = NULL; + + AF_CLIENT_LOCK_W(); + for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++) + { + list_for_each_entry(node, &af_client_list_table[i], hlist) + { + node->record_whitelist = fwx_match_record_whitelist(node->mac); + } + } + AF_CLIENT_UNLOCK_W(); +} + +int fwx_set_record_whitelist(const char *mac_list_str) +{ + char mac_buf[1024] = {0}; + char *token = NULL; + char *save_ptr = NULL; + u8 mac_bin[ETH_ALEN]; + struct mac_node *node = NULL; + + if (!mac_list_str) { + return -1; + } + + strcpy(mac_buf, mac_list_str); + + record_whitelist_write_lock(); + fwx_flush_mac_list(&g_record_whitelist); + save_ptr = mac_buf; + while ((token = strsep(&save_ptr, ",")) != NULL) { + token = strim(token); + if (!token || token[0] == '\0') { + continue; + } + if (!mac_str_to_bin(token, mac_bin)) { + continue; + } + node = fwx_find_mac_node(&g_record_whitelist, mac_bin); + if (!node) { + fwx_add_mac_node(&g_record_whitelist, mac_bin); + } + } + record_whitelist_write_unlock(); + + fwx_sync_record_whitelist_clients(); + return 0; +} + static void nf_client_list_init(void) @@ -47,6 +145,7 @@ nf_client_list_init(void) { INIT_LIST_HEAD(&af_client_list_table[i]); } + fwx_mac_config_init(&g_record_whitelist); AF_CLIENT_UNLOCK_W(); AF_INFO("client list init......ok\n"); } @@ -54,9 +153,12 @@ nf_client_list_init(void) static void nf_client_list_clear(void) { - int i; + int i, j; af_client_info_t *p = NULL; char mac_str[32] = {0}; + struct hlist_head *head; + struct hlist_node *n; + app_visit_info_t *info; AF_DEBUG("clean list\n"); AF_CLIENT_LOCK_W(); @@ -69,11 +171,26 @@ nf_client_list_clear(void) sprintf(mac_str, MAC_FMT, MAC_ARRAY(p->mac)); AF_DEBUG("clean mac:%s\n", mac_str); stop_client_timer(p); + remove_client_proc_dir(p); + + spin_lock_bh(&p->visit_info_lock); + for (j = 0; j < MAX_VISIT_INFO_HASH_SIZE; j++) { + head = &p->visit_info_hash[j]; + hlist_for_each_entry_safe(info, n, head, hlist) { + hlist_del(&info->hlist); + kfree(info); + } + } + spin_unlock_bh(&p->visit_info_lock); + list_del(&(p->hlist)); kfree(p); } } AF_CLIENT_UNLOCK_W(); + record_whitelist_write_lock(); + fwx_flush_mac_list(&g_record_whitelist); + record_whitelist_write_unlock(); } void af_client_list_reset_report_num(void) @@ -91,7 +208,7 @@ void af_client_list_reset_report_num(void) AF_CLIENT_UNLOCK_W(); } -static int get_mac_hash_code(unsigned char *mac) +int get_mac_hash_code(unsigned char *mac) { if (!mac) return 0; @@ -178,15 +295,25 @@ nf_client_add(unsigned char *mac) memset(node, 0, sizeof(af_client_info_t)); memcpy(node->mac, mac, MAC_ADDR_LEN); + node->record_whitelist = fwx_match_record_whitelist(node->mac); node->create_jiffies = jiffies; node->update_jiffies = jiffies; + node->timer_count = 0; + spin_lock_init(&node->visit_info_lock); + { + int i; + for (i = 0; i < MAX_VISIT_INFO_HASH_SIZE; i++) { + INIT_HLIST_HEAD(&node->visit_info_hash[i]); + } + } index = get_mac_hash_code(mac); AF_LMT_INFO("new client mac=" MAC_FMT "\n", MAC_ARRAY(node->mac)); total_client++; init_client_timer(node); list_add(&(node->hlist), &af_client_list_table[index]); + create_client_proc_dir(node); return node; } @@ -208,6 +335,7 @@ void check_client_expire(void) { AF_INFO("del client:" MAC_FMT "\n", MAC_ARRAY(node->mac)); stop_client_timer(node); + remove_client_proc_dir(node); list_del(&(node->hlist)); kfree(node); AF_CLIENT_UNLOCK_W(); @@ -219,55 +347,144 @@ void check_client_expire(void) } #define MAX_EXPIRED_VISIT_INFO_COUNT 10 -static void flush_expired_visit_info(af_client_info_t *node) +static inline int get_app_id_hash_code(unsigned int app_id) +{ + return app_id & (MAX_VISIT_INFO_HASH_SIZE - 1); +} + +static app_visit_info_t *find_visit_info(af_client_info_t *node, unsigned int app_id) +{ + struct hlist_head *head; + app_visit_info_t *info; + + head = &node->visit_info_hash[get_app_id_hash_code(app_id)]; + hlist_for_each_entry(info, head, hlist) { + if (info->app_id == app_id) { + return info; + } + } + return NULL; +} + +app_visit_info_t *get_or_create_visit_info(af_client_info_t *node, unsigned int app_id) +{ + app_visit_info_t *info; + + info = find_visit_info(node, app_id); + if (info) { + return info; + } + + info = (app_visit_info_t *)kmalloc(sizeof(app_visit_info_t), GFP_ATOMIC); + if (!info) { + return NULL; + } + + memset(info, 0, sizeof(app_visit_info_t)); + info->app_id = app_id; + INIT_HLIST_NODE(&info->hlist); + + hlist_add_head(&info->hlist, &node->visit_info_hash[get_app_id_hash_code(app_id)]); + return info; +} + +void flush_expired_visit_info(af_client_info_t *node) { int i; int count = 0; u_int32_t cur_timep = 0; int timeout = 0; + struct hlist_head *head; + struct hlist_node *n; + app_visit_info_t *info; + cur_timep = af_get_timestamp_sec(); - for (i = 0; i < MAX_RECORD_APP_NUM; i++) - { - if (node->visit_info[i].app_id == 0) - { - return; - } - } - for (i = 0; i < MAX_RECORD_APP_NUM; i++) - { - if (count >= MAX_EXPIRED_VISIT_INFO_COUNT) - break; - - if (node->visit_info[i].total_num > 3) - { - timeout = 180; - } - else - { - timeout = 60; - } - - if (cur_timep - node->visit_info[i].latest_time > timeout) - { - // 3?��o?��??3y???? - memset(&node->visit_info[i], 0x0, sizeof(app_visit_info_t)); - count++; + + spin_lock_bh(&node->visit_info_lock); + for (i = 0; i < MAX_VISIT_INFO_HASH_SIZE; i++) { + head = &node->visit_info_hash[i]; + hlist_for_each_entry_safe(info, n, head, hlist) { + if (count >= MAX_EXPIRED_VISIT_INFO_COUNT) + break; + + if (info->total_num > 3) { + timeout = 180; + } else { + timeout = 60; + } + + if (cur_timep - info->latest_time > timeout) { + hlist_del(&info->hlist); + spin_unlock_bh(&node->visit_info_lock); + kfree(info); + spin_lock_bh(&node->visit_info_lock); + count++; + } } } + spin_unlock_bh(&node->visit_info_lock); } -static int __af_visit_info_report(af_client_info_t *node) +#define VISIT_INFO_TIMEOUT_SEC 300 + +void check_expired_visit_info(af_client_info_t *node) +{ + int i; + u_int32_t cur_timep = 0; + struct hlist_head *head; + struct hlist_node *n; + app_visit_info_t *info; + + if (!node) + return; + + cur_timep = af_get_timestamp_sec(); + + spin_lock_bh(&node->visit_info_lock); + for (i = 0; i < MAX_VISIT_INFO_HASH_SIZE; i++) { + head = &node->visit_info_hash[i]; + hlist_for_each_entry_safe(info, n, head, hlist) { + if (cur_timep - info->latest_time > VISIT_INFO_TIMEOUT_SEC) { + hlist_del(&info->hlist); + spin_unlock_bh(&node->visit_info_lock); + kfree(info); + spin_lock_bh(&node->visit_info_lock); + } + } + } + spin_unlock_bh(&node->visit_info_lock); +} + +static int compare_visit_info_count(const void *a, const void *b) +{ + const app_visit_info_t *info_a = *(const app_visit_info_t **)a; + const app_visit_info_t *info_b = *(const app_visit_info_t **)b; + + if (info_a->total_num > info_b->total_num) + return -1; + else if (info_a->total_num < info_b->total_num) + return 1; + return 0; +} + + + +int __af_visit_info_report(af_client_info_t *node) { unsigned char mac_str[32] = {0}; unsigned char ip_str[32] = {0}; int i; int count = 0; + int total_count = 0; char *out = NULL; cJSON *visit_obj = NULL; cJSON *visit_info_array = NULL; cJSON *root_obj = NULL; - - flush_expired_visit_info(node); + struct hlist_head *head; + struct hlist_node *tmp; + app_visit_info_t *info; + app_visit_info_t *info_array[MAX_RECORD_APP_NUM]; + int report_count = 0; root_obj = cJSON_CreateObject(); if (!root_obj) @@ -284,28 +501,44 @@ static int __af_visit_info_report(af_client_info_t *node) cJSON_AddNumberToObject(root_obj, "down_flow", (u32)(node->period_flow.down_bytes >> 10)); cJSON_AddNumberToObject(root_obj, "active", node->active); - visit_info_array = cJSON_CreateArray(); - for (i = 0; i < MAX_RECORD_APP_NUM; i++) - { - if (node->visit_info[i].app_id == 0) - continue; - count++; - visit_obj = cJSON_CreateObject(); - cJSON_AddNumberToObject(visit_obj, "appid", node->visit_info[i].app_id); - cJSON_AddNumberToObject(visit_obj, "latest_action", node->visit_info[i].latest_action); - memset((char *)&node->visit_info[i], 0x0, sizeof(app_visit_info_t)); - cJSON_AddItemToArray(visit_info_array, visit_obj); + spin_lock_bh(&node->visit_info_lock); + for (i = 0; i < MAX_VISIT_INFO_HASH_SIZE; i++) { + head = &node->visit_info_hash[i]; + hlist_for_each_entry(info, head, hlist) { + if (info->total_num == 0) + continue; + if (info->is_http && info->conn_count <= g_min_http_match_count) { + info->total_num = 0; + continue; + } + info_array[total_count++] = info; + info->total_num = 0; //clean all + } } + + if (total_count > 0) { + sort(info_array, total_count, sizeof(app_visit_info_t *), compare_visit_info_count, NULL); + report_count = total_count > g_max_app_report_count ? g_max_app_report_count : total_count; + } + + visit_info_array = cJSON_CreateArray(); + for (i = 0; i < report_count; i++) { + info = info_array[i]; + visit_obj = cJSON_CreateObject(); + cJSON_AddNumberToObject(visit_obj, "appid", info->app_id); + cJSON_AddNumberToObject(visit_obj, "latest_action", info->latest_action); + info->total_num = 0; + cJSON_AddItemToArray(visit_info_array, visit_obj); + count++; + } + spin_unlock_bh(&node->visit_info_lock); cJSON_AddItemToObject(root_obj, "visit_info", visit_info_array); out = cJSON_Print(root_obj); - if (!out) { - cJSON_Delete(root_obj); + if (!out) return 0; - } cJSON_Minify(out); - AF_INFO("report:%s count=%d\n", out, node->report_count); node->report_count++; af_send_msg_to_user(out, strlen(out)); cJSON_Delete(root_obj); @@ -330,7 +563,7 @@ static inline int get_packet_dir(struct net_device *in) -static void af_update_client_status(af_client_info_t *node) +void af_update_client_status(af_client_info_t *node) { if (node->last_flow.down_bytes > 0){ node->period_flow.down_bytes += (node->flow.down_bytes - node->last_flow.down_bytes); @@ -340,7 +573,7 @@ static void af_update_client_status(af_client_info_t *node) } AF_LMT_DEBUG("period flow down:%llu up: %llu pkg up %d\n", node->period_flow.down_bytes, node->period_flow.up_bytes, node->rate.pkt_up_rate); - // 2s + node->rate.up_rate = (node->flow.up_bytes - node->last_flow.up_bytes) >> 1; node->rate.down_rate = (node->flow.down_bytes - node->last_flow.down_bytes) >> 1; node->rate.pkt_up_rate = (node->flow.up_pkts - node->last_flow.up_pkts) >> 1; @@ -350,7 +583,15 @@ static void af_update_client_status(af_client_info_t *node) node->last_flow.down_bytes = node->flow.down_bytes; node->last_flow.up_pkts = node->flow.up_pkts; node->last_flow.down_pkts = node->flow.down_pkts; - if (node->rate.pkt_down_rate > 10 || node->rate.pkt_up_rate > 5){ + + if (is_client_mac_filter_blocked(node)) { + node->active = 0; + node->active_time = 0; + node->inactive_time = 0; + return; + } + + if (node->rate.pkt_down_rate > 20){ node->active_time++; node->inactive_time = 0; node->active = 1; @@ -448,6 +689,7 @@ static u_int32_t af_client_hook(unsigned int hook, } nfc->flow.up_bytes += skb->len; nfc->flow.up_pkts++; + nfc->update_jiffies = jiffies; } AF_CLIENT_UNLOCK_W(); @@ -470,6 +712,8 @@ static u_int32_t af_client_hook2(unsigned int hook, int (*okfn)(struct sk_buff *)) { #endif + struct ethhdr *ethhdr = NULL; + unsigned char smac[ETH_ALEN]; af_client_info_t *nfc = NULL; int pkt_dir = 0; struct iphdr *iph = NULL; @@ -523,6 +767,7 @@ static u_int32_t af_client_hook2(unsigned int hook, if (nfc){ nfc->flow.down_bytes += skb->len; nfc->flow.down_pkts++; + nfc->update_jiffies = jiffies; } AF_CLIENT_UNLOCK_R(); @@ -572,10 +817,10 @@ static struct nf_hook_ops af_client_ops[] = { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) static void client_timer_handler(struct timer_list *t) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 16, 0) - af_client_info_t *client = from_timer(client, t, client_timer); -#else +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 16, 0) af_client_info_t *client = timer_container_of(client, t, client_timer); +#else + af_client_info_t *client = from_timer(client, t, client_timer); #endif #else static void client_timer_handler(unsigned long data) @@ -586,13 +831,14 @@ static void client_timer_handler(unsigned long data) AF_ERROR("client timer handler: invalid client\n"); return; } - - if (client->timer_count >= 30) { - __af_visit_info_report(client); + + if (client->timer_count >= 30) { + __af_visit_info_report(client); client->timer_count = 0; - } + } - af_update_client_status(client); + check_expired_visit_info(client); + af_update_client_status(client); client->timer_count++; mod_timer(&client->client_timer, jiffies + HZ * 2); } @@ -621,10 +867,10 @@ static void client_timer_handler(unsigned long data) return; } -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 16, 0) - del_timer_sync(&client->client_timer); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0) + timer_shutdown_sync(&client->client_timer); #else - timer_delete_sync(&client->client_timer); + del_timer_sync(&client->client_timer); #endif } @@ -641,13 +887,84 @@ int af_client_init(void) err = nf_register_hooks(af_client_ops, ARRAY_SIZE(af_client_ops)); #endif if (err) { - AF_ERROR("oaf register client hooks failed!\n"); + AF_ERROR("register client hooks failed!\n"); } - AF_INFO("init app afclient ........ok\n"); return 0; } +int fwx_api_add_record_whitelist(cJSON *data_obj) +{ + cJSON *mac_array; + int i; + u8 mac_bin[ETH_ALEN]; + + if (!data_obj) { + return -1; + } + + mac_array = cJSON_GetObjectItem(data_obj, "mac_list"); + if (!mac_array) { + printk("mac_list not found\n"); + return -1; + } + + record_whitelist_write_lock(); + for (i = 0; i < cJSON_GetArraySize(mac_array); i++) { + cJSON *mac_obj = cJSON_GetArrayItem(mac_array, i); + if (mac_obj && mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + fwx_add_mac_node(&g_record_whitelist, mac_bin); + } + } + record_whitelist_write_unlock(); + + fwx_sync_record_whitelist_clients(); + return 0; +} + +int fwx_api_del_record_whitelist(cJSON *data_obj) +{ + cJSON *mac_obj; + u8 mac_bin[ETH_ALEN]; + struct mac_node *node; + + if (!data_obj) { + return -1; + } + + mac_obj = cJSON_GetObjectItem(data_obj, "mac"); + if (!mac_obj) { + printk("mac not found\n"); + return -1; + } + + if (!mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + printk("invalid mac format\n"); + return -1; + } + + record_whitelist_write_lock(); + node = fwx_find_mac_node(&g_record_whitelist, mac_bin); + if (node) { + hlist_del(&node->hlist); + kfree(node); + record_whitelist_write_unlock(); + fwx_sync_record_whitelist_clients(); + return 0; + } + record_whitelist_write_unlock(); + return -1; +} + +int fwx_api_flush_record_whitelist(cJSON *data_obj) +{ + record_whitelist_write_lock(); + fwx_flush_mac_list(&g_record_whitelist); + record_whitelist_write_unlock(); + fwx_sync_record_whitelist_clients(); + return 0; +} + void af_client_exit(void) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) diff --git a/oaf/src/af_client.h b/oaf/src/fwx_client.h similarity index 66% rename from oaf/src/af_client.h rename to oaf/src/fwx_client.h index 4f6ca991..04917452 100644 --- a/oaf/src/af_client.h +++ b/oaf/src/fwx_client.h @@ -1,14 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ #ifndef __AF_CLIENT_H__ #define __AF_CLIENT_H__ -#include "app_filter.h" +#include "fwx.h" +struct cJSON; extern rwlock_t af_client_lock; extern u32 nfc_debug_level; + + +extern int g_max_app_report_count; +extern int g_min_http_match_count; + +#define VISIT_INFO_TIMEOUT_SEC 300 + #define MAX_AF_CLIENT_HASH_SIZE 64 #define NF_CLIENT_TIMER_EXPIRE 1 -#define MAX_CLIENT_ACTIVE_TIME 90 +#define MAX_CLIENT_ACTIVE_TIME 180 #define AF_CLIENT_LOCK_R() read_lock_bh(&af_client_lock); #define AF_CLIENT_UNLOCK_R() read_unlock_bh(&af_client_lock); @@ -22,14 +34,11 @@ extern u32 nfc_debug_level; ((unsigned char *)&addr)[3] #define NIPQUAD_FMT "%u.%u.%u.%u" -enum NFC_PKT_DIR -{ - PKT_DIR_DOWN, - PKT_DIR_UP -}; + #define MAX_VISIT_HISTORY_TIME 24 #define MAX_RECORD_APP_NUM 64 +#define MAX_VISIT_INFO_HASH_SIZE 32 #define MIN_REPORT_URL_LEN 4 #define MAX_REPORT_URL_LEN 64 @@ -50,11 +59,14 @@ typedef struct flow_rate }flow_rate_t; typedef struct app_visit_info { + struct hlist_node hlist; unsigned int app_id; unsigned int total_num; unsigned int drop_num; unsigned long latest_time; unsigned int latest_action; + unsigned int conn_count; + unsigned int is_http; } app_visit_info_t; typedef struct visiting_info{ @@ -81,10 +93,13 @@ typedef struct af_client_info int active_time; int inactive_time; int active; + int record_whitelist; visiting_info_t visiting; - int timer_count; int report_count; - app_visit_info_t visit_info[MAX_RECORD_APP_NUM]; + unsigned int timer_count; + spinlock_t visit_info_lock; + struct hlist_head visit_info_hash[MAX_VISIT_INFO_HASH_SIZE]; + struct proc_dir_entry *proc_dir; } af_client_info_t; int af_client_init(void); @@ -102,5 +117,14 @@ void af_visit_info_report(void); void af_client_list_reset_report_num(void); af_client_info_t *nf_client_add(unsigned char *mac); af_client_info_t *find_and_add_af_client(unsigned char *mac); +app_visit_info_t *get_or_create_visit_info(af_client_info_t *node, unsigned int app_id); +int af_update_client_app_info(af_client_info_t *node, int app_id, int drop, int from_conntrack, int is_http, int update_visiting); +void check_expired_visit_info(af_client_info_t *node); + +int fwx_match_record_whitelist(const unsigned char *mac); +int fwx_set_record_whitelist(const char *mac_list_str); +int fwx_api_add_record_whitelist(struct cJSON *data_obj); +int fwx_api_del_record_whitelist(struct cJSON *data_obj); +int fwx_api_flush_record_whitelist(struct cJSON *data_obj); #endif diff --git a/oaf/src/fwx_client_fs.c b/oaf/src/fwx_client_fs.c new file mode 100644 index 00000000..066ce9f0 --- /dev/null +++ b/oaf/src/fwx_client_fs.c @@ -0,0 +1,862 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fwx_utils.h" + +#include "k_json.h" +#include "fwx_log.h" +#include "fwx_client.h" +extern char *ipv6_to_str(const struct in6_addr *addr, char *str); + +extern struct list_head af_client_list_table[MAX_AF_CLIENT_HASH_SIZE]; +struct af_client_iter_state +{ + unsigned int bucket; + void *head; +}; + +static void *af_client_get_first(struct seq_file *seq) +{ + struct af_client_iter_state *st = seq->private; + for (st->bucket = 0; st->bucket < MAX_AF_CLIENT_HASH_SIZE; st->bucket++) + { + if (!list_empty(&(af_client_list_table[st->bucket]))) + { + st->head = &(af_client_list_table[st->bucket]); + return af_client_list_table[st->bucket].next; + } + } + return NULL; +} + +static void *af_client_get_next(struct seq_file *seq, + void *head) +{ + struct af_client_iter_state *st = seq->private; + struct hlist_node *node = (struct hlist_node *)head; + + node = node->next; + if (node != st->head) + { + return node; + } + else + { + st->bucket++; + for (; st->bucket < MAX_AF_CLIENT_HASH_SIZE; st->bucket++) + { + if (!list_empty(&(af_client_list_table[st->bucket]))) + { + st->head = &(af_client_list_table[st->bucket]); + return af_client_list_table[st->bucket].next; + } + } + return NULL; + } +} + +static void *af_client_get_idx(struct seq_file *seq, loff_t pos) +{ + void *head = af_client_get_first(seq); + + if (head) + while (pos && (head = af_client_get_next(seq, head))) + pos--; + + return pos ? NULL : head; +} + +static void *af_client_seq_start(struct seq_file *s, loff_t *pos) +{ + AF_CLIENT_LOCK_R(); + if (*pos == 0) + { + return SEQ_START_TOKEN; + } + + return af_client_get_idx(s, *pos - 1); +} + +static void *af_client_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + (*pos)++; + if (v == SEQ_START_TOKEN) + return af_client_get_idx(s, 0); + + return af_client_get_next(s, v); +} + +static void af_client_seq_stop(struct seq_file *s, void *v) +{ + AF_CLIENT_UNLOCK_R(); +} + +static int af_client_seq_show(struct seq_file *s, void *v) +{ + unsigned char mac_str[32] = {0}; + unsigned char ip_str[32] = {0}; + unsigned char ipv6_str[128]; + int status = 1; + + static int index = 0; + af_client_info_t *node = (af_client_info_t *)v; + if (v == SEQ_START_TOKEN) + { + index = 0; + seq_printf(s, "%-4s %-20s %-20s %-32s %-12s %-8s %-16s %-16s\n", "Id", "Mac", "IP", "IPv6", "RecordWl", "Status", "UpRate", "DownRate"); + return 0; + } + index++; + sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac)); + sprintf(ip_str, "%pI4", &node->ip); + ipv6_to_str(&node->ipv6, ipv6_str); + status = node->active ? 2 : 1; + + seq_printf(s, "%-4d %-20s %-20s %-32s %-12d %-8d %-16d %-16d\n", index, mac_str, ip_str, ipv6_str, node->record_whitelist, status, node->rate.up_rate, node->rate.down_rate); + return 0; +} + +static const struct seq_operations nf_client_seq_ops = { + .start = af_client_seq_start, + .next = af_client_seq_next, + .stop = af_client_seq_stop, + .show = af_client_seq_show}; + +static int af_client_open(struct inode *inode, struct file *file) +{ + struct seq_file *seq; + struct af_client_iter_state *iter; + int err; + + iter = kzalloc(sizeof(*iter), GFP_KERNEL); + if (!iter) + return -ENOMEM; + + err = seq_open(file, &nf_client_seq_ops); + if (err) + { + kfree(iter); + return err; + } + + seq = file->private_data; + seq->private = iter; + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) +static const struct file_operations af_client_fops = { + .owner = THIS_MODULE, + .open = af_client_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; +#else +static const struct proc_ops af_client_fops = { + .proc_flags = PROC_ENTRY_PERMANENT, + .proc_read = seq_read, + .proc_open = af_client_open, + .proc_lseek = seq_lseek, + .proc_release = seq_release_private, +}; +#endif + +#define AF_CLIENT_PROC_STR "af_client" + + + + +static int af_visiting_seq_show(struct seq_file *s, void *v) +{ + unsigned char mac_str[32] = {0}; + unsigned char ip_str[32] = {0}; + static int index = 0; + int i; + af_client_info_t *node = (af_client_info_t *)v; + if (v == SEQ_START_TOKEN) + { + index = 0; + seq_printf(s, "%-20s %-12s %-32s\n", "Mac", "Appid", "Url"); + return 0; + } + index++; + + sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac)); + int visiting_app = 0; + char visiting_url[64] = {0}; + if (af_get_timestamp_sec() - node->visiting.app_time < 120){ + visiting_app = node->visiting.visiting_app; + } + if ( af_get_timestamp_sec() - node->visiting.url_time < 120 ){ + strncpy(visiting_url, node->visiting.visiting_url, sizeof(visiting_url)); + } + else{ + strcpy(visiting_url, "none"); + } + seq_printf(s, "%-20s %-12d %-32s\n", mac_str, visiting_app, visiting_url); + + return 0; +} + +static const struct seq_operations nf_visiting_seq_ops = { + .start = af_client_seq_start, + .next = af_client_seq_next, + .stop = af_client_seq_stop, + .show = af_visiting_seq_show +}; + + +static int af_visiting_open(struct inode *inode, struct file *file) +{ + struct seq_file *seq; + struct af_client_iter_state *iter; + int err; + + iter = kzalloc(sizeof(*iter), GFP_KERNEL); + if (!iter) + return -ENOMEM; + + err = seq_open(file, &nf_visiting_seq_ops); + if (err) + { + kfree(iter); + return err; + } + + seq = file->private_data; + seq->private = iter; + return 0; +} + + + + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) +static const struct file_operations af_visiting_fops = { + .owner = THIS_MODULE, + .open = af_visiting_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; +#else +static const struct proc_ops af_visiting_fops = { + .proc_flags = PROC_ENTRY_PERMANENT, + .proc_read = seq_read, + .proc_open = af_visiting_open, + .proc_lseek = seq_lseek, + .proc_release = seq_release_private, +}; +#endif +#define AF_VISIT_INFO "af_visit" +#define AF_CLIENT_VISIT_LIST "af_client_visit_list" +#define AF_CLIENT_BASE_DIR "fwx_client" + + +static struct proc_dir_entry *g_af_client_base_dir = NULL; + +static DEFINE_MUTEX(af_client_base_dir_mutex); + + + + + +void remove_client_proc_dir(af_client_info_t *client); + +struct af_client_visit_iter_state +{ + unsigned int client_bucket; + unsigned int visit_bucket; + af_client_info_t *current_client; + struct hlist_node *current_visit_node; +}; + +static void *af_client_visit_get_first_client(struct seq_file *seq) +{ + struct af_client_visit_iter_state *st = seq->private; + af_client_info_t *client; + + for (st->client_bucket = 0; st->client_bucket < MAX_AF_CLIENT_HASH_SIZE; st->client_bucket++) { + if (!list_empty(&af_client_list_table[st->client_bucket])) { + client = list_first_entry(&af_client_list_table[st->client_bucket], af_client_info_t, hlist); + st->current_client = client; + st->visit_bucket = 0; + return client; + } + } + return NULL; +} + +static void *af_client_visit_get_next_visit(struct seq_file *seq, af_client_info_t *client) +{ + struct af_client_visit_iter_state *st = seq->private; + struct hlist_head *head; + app_visit_info_t *info; + + if (!client) + return NULL; + + for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) { + head = &client->visit_info_hash[st->visit_bucket]; + if (!hlist_empty(head)) { + info = hlist_entry(head->first, app_visit_info_t, hlist); + st->current_visit_node = head->first; + return info; + } + } + return NULL; +} + +static void *af_client_visit_get_next(struct seq_file *seq) +{ + struct af_client_visit_iter_state *st = seq->private; + app_visit_info_t *info; + struct hlist_node *next; + struct hlist_head *head; + + if (!st->current_client) + return NULL; + + if (st->current_visit_node) { + next = st->current_visit_node->next; + if (next) { + st->current_visit_node = next; + info = hlist_entry(next, app_visit_info_t, hlist); + return info; + } + st->visit_bucket++; + st->current_visit_node = NULL; + } + + for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) { + head = &st->current_client->visit_info_hash[st->visit_bucket]; + if (!hlist_empty(head)) { + st->current_visit_node = head->first; + info = hlist_entry(head->first, app_visit_info_t, hlist); + return info; + } + } + + return NULL; +} + +static void *af_client_visit_get_next_client(struct seq_file *seq) +{ + struct af_client_visit_iter_state *st = seq->private; + af_client_info_t *client; + struct list_head *next; + + if (!st->current_client) + return NULL; + + next = st->current_client->hlist.next; + if (next != &af_client_list_table[st->client_bucket]) { + client = list_entry(next, af_client_info_t, hlist); + st->current_client = client; + st->visit_bucket = 0; + st->current_visit_node = NULL; + return af_client_visit_get_next_visit(seq, client); + } + + st->client_bucket++; + for (; st->client_bucket < MAX_AF_CLIENT_HASH_SIZE; st->client_bucket++) { + if (!list_empty(&af_client_list_table[st->client_bucket])) { + client = list_first_entry(&af_client_list_table[st->client_bucket], af_client_info_t, hlist); + st->current_client = client; + st->visit_bucket = 0; + st->current_visit_node = NULL; + return af_client_visit_get_next_visit(seq, client); + } + } + return NULL; +} + +static void *af_client_visit_seq_start(struct seq_file *s, loff_t *pos) +{ + struct af_client_visit_iter_state *st = s->private; + void *v = NULL; + + AF_CLIENT_LOCK_R(); + + if (*pos == 0) { + return SEQ_START_TOKEN; + } + + v = af_client_visit_get_first_client(s); + if (!v) + return NULL; + + v = af_client_visit_get_next_visit(s, (af_client_info_t *)v); + (*pos)--; + + while (*pos > 0 && v) { + v = af_client_visit_get_next(s); + if (!v) { + v = af_client_visit_get_next_client(s); + } + (*pos)--; + } + + return v; +} + +static void *af_client_visit_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct af_client_visit_iter_state *st = s->private; + void *next; + + (*pos)++; + + if (v == SEQ_START_TOKEN) { + next = af_client_visit_get_first_client(s); + if (!next) + return NULL; + return af_client_visit_get_next_visit(s, (af_client_info_t *)next); + } + + next = af_client_visit_get_next(s); + if (!next) { + next = af_client_visit_get_next_client(s); + } + + return next; +} + +static void af_client_visit_seq_stop(struct seq_file *s, void *v) +{ + AF_CLIENT_UNLOCK_R(); +} + +static void print_client_visit_header(struct seq_file *s) +{ + seq_printf(s, "%-20s %-8s %-8s %-8s %-8s %-8s %-12s %-12s %-10s\n", + "MAC", "AppID", "TotalNum", "DropNum", "Conn", "IsHttp", "LatestTime", "LatestAction", "OfflineTime"); +} + +static int af_client_visit_seq_show(struct seq_file *s, void *v) +{ + unsigned char mac_str[32] = {0}; + unsigned char ip_str[32] = {0}; + app_visit_info_t *info = (app_visit_info_t *)v; + struct af_client_visit_iter_state *st = s->private; + + if (v == SEQ_START_TOKEN) { + print_client_visit_header(s); + return 0; + } + + if (!info || !st->current_client) + return 0; + + sprintf(mac_str, MAC_FMT, MAC_ARRAY(st->current_client->mac)); + + spin_lock_bh(&st->current_client->visit_info_lock); + u_int32_t cur_time = af_get_timestamp_sec(); + u_int32_t offline_time = (cur_time > info->latest_time) ? (cur_time - info->latest_time) : 0; + seq_printf(s, "%-20s %-8u %-8u %-8u %-8u %-8u %-12lu %-12u %-10u\n", + mac_str, + info->app_id, + info->total_num, + info->drop_num, + info->conn_count, + info->is_http, + info->latest_time, + info->latest_action, + offline_time); + spin_unlock_bh(&st->current_client->visit_info_lock); + + return 0; +} + +static const struct seq_operations af_client_visit_seq_ops = { + .start = af_client_visit_seq_start, + .next = af_client_visit_seq_next, + .stop = af_client_visit_seq_stop, + .show = af_client_visit_seq_show +}; + +static int af_client_visit_open(struct inode *inode, struct file *file) +{ + struct seq_file *seq; + struct af_client_visit_iter_state *iter; + int err; + + iter = kzalloc(sizeof(*iter), GFP_KERNEL); + if (!iter) + return -ENOMEM; + + err = seq_open(file, &af_client_visit_seq_ops); + if (err) { + kfree(iter); + return err; + } + + seq = file->private_data; + seq->private = iter; + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) +static const struct file_operations af_client_visit_fops = { + .owner = THIS_MODULE, + .open = af_client_visit_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; +#else +static const struct proc_ops af_client_visit_fops = { + .proc_flags = PROC_ENTRY_PERMANENT, + .proc_read = seq_read, + .proc_open = af_client_visit_open, + .proc_lseek = seq_lseek, + .proc_release = seq_release_private, +}; +#endif + +int init_af_client_procfs(void) +{ + struct proc_dir_entry *pde; + struct net *net = &init_net; + pde = proc_create(AF_CLIENT_PROC_STR, 0440, net->proc_net, &af_client_fops); + + if (!pde) + { + AF_ERROR("nf_client proc file created error\n"); + return -1; + } + + pde = proc_create(AF_VISIT_INFO, 0440, net->proc_net, &af_visiting_fops); + if (!pde) + { + AF_ERROR("nf_client visiting info proc file created error\n"); + return -1; + } + + pde = proc_create(AF_CLIENT_VISIT_LIST, 0440, net->proc_net, &af_client_visit_fops); + if (!pde) + { + AF_ERROR("nf_client visit list proc file created error\n"); + return -1; + } + return 0; +} + +void finit_af_client_procfs(void) +{ + struct net *net = &init_net; + int i; + af_client_info_t *client; + + mutex_lock(&af_client_base_dir_mutex); + + + if (g_af_client_base_dir) { + AF_CLIENT_LOCK_R(); + for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++) { + list_for_each_entry(client, &af_client_list_table[i], hlist) { + if (client && client->proc_dir) { + remove_client_proc_dir(client); + } + } + } + AF_CLIENT_UNLOCK_R(); + } + + + remove_proc_entry(AF_CLIENT_PROC_STR, net->proc_net); + remove_proc_entry(AF_VISIT_INFO, net->proc_net); + remove_proc_entry(AF_CLIENT_VISIT_LIST, net->proc_net); + + + remove_proc_entry(AF_CLIENT_BASE_DIR, net->proc_net); + g_af_client_base_dir = NULL; // 重置静态变量 + mutex_unlock(&af_client_base_dir_mutex); +} + +static void print_single_client_visit_header(struct seq_file *s) +{ + seq_printf(s, "%-8s %-8s %-8s %-8s %-8s %-12s %-12s %-10s\n", + "AppID", "TotalNum", "DropNum", "Conn", "IsHttp", "LatestTime", "LatestAction", "OfflineTime"); +} + +struct single_client_visit_iter_state +{ + unsigned int visit_bucket; + struct hlist_node *current_visit_node; + af_client_info_t *client; +}; + +static void *single_client_visit_seq_start(struct seq_file *s, loff_t *pos) +{ + struct single_client_visit_iter_state *st = s->private; + struct hlist_head *head; + app_visit_info_t *info; + + if (!st->client) + return NULL; + + spin_lock_bh(&st->client->visit_info_lock); + + if (*pos == 0) { + return SEQ_START_TOKEN; + } + + st->visit_bucket = 0; + st->current_visit_node = NULL; + + for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) { + head = &st->client->visit_info_hash[st->visit_bucket]; + if (!hlist_empty(head)) { + st->current_visit_node = head->first; + info = hlist_entry(head->first, app_visit_info_t, hlist); + (*pos)--; + if (*pos == 0) { + return info; + } + break; + } + } + + while (*pos > 0 && st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE) { + if (st->current_visit_node) { + struct hlist_node *next = st->current_visit_node->next; + if (next) { + st->current_visit_node = next; + info = hlist_entry(next, app_visit_info_t, hlist); + (*pos)--; + if (*pos == 0) { + return info; + } + continue; + } + st->visit_bucket++; + st->current_visit_node = NULL; + } + + for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) { + head = &st->client->visit_info_hash[st->visit_bucket]; + if (!hlist_empty(head)) { + st->current_visit_node = head->first; + info = hlist_entry(head->first, app_visit_info_t, hlist); + (*pos)--; + if (*pos == 0) { + return info; + } + break; + } + } + } + + return NULL; +} + +static void *single_client_visit_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct single_client_visit_iter_state *st = s->private; + struct hlist_head *head; + app_visit_info_t *info; + + (*pos)++; + + if (v == SEQ_START_TOKEN) { + st->visit_bucket = 0; + st->current_visit_node = NULL; + for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) { + head = &st->client->visit_info_hash[st->visit_bucket]; + if (!hlist_empty(head)) { + st->current_visit_node = head->first; + return hlist_entry(head->first, app_visit_info_t, hlist); + } + } + return NULL; + } + + if (st->current_visit_node) { + struct hlist_node *next = st->current_visit_node->next; + if (next) { + st->current_visit_node = next; + return hlist_entry(next, app_visit_info_t, hlist); + } + st->visit_bucket++; + st->current_visit_node = NULL; + } + + for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) { + head = &st->client->visit_info_hash[st->visit_bucket]; + if (!hlist_empty(head)) { + st->current_visit_node = head->first; + return hlist_entry(head->first, app_visit_info_t, hlist); + } + } + + return NULL; +} + +static void single_client_visit_seq_stop(struct seq_file *s, void *v) +{ + struct single_client_visit_iter_state *st = s->private; + if (st->client) { + spin_unlock_bh(&st->client->visit_info_lock); + } +} + +static int single_client_visit_seq_show(struct seq_file *s, void *v) +{ + app_visit_info_t *info = (app_visit_info_t *)v; + + if (v == SEQ_START_TOKEN) { + print_single_client_visit_header(s); + return 0; + } + + if (!info) + return 0; + + u_int32_t cur_time = af_get_timestamp_sec(); + u_int32_t offline_time = (cur_time > info->latest_time) ? (cur_time - info->latest_time) : 0; + seq_printf(s, "%-8u %-8u %-8u %-8u %-8u %-12lu %-12u %-10u\n", + info->app_id, + info->total_num, + info->drop_num, + info->conn_count, + info->is_http, + info->latest_time, + info->latest_action, + offline_time); + + return 0; +} + +static const struct seq_operations single_client_visit_seq_ops = { + .start = single_client_visit_seq_start, + .next = single_client_visit_seq_next, + .stop = single_client_visit_seq_stop, + .show = single_client_visit_seq_show +}; + +static int single_client_visit_open(struct inode *inode, struct file *file) +{ + struct seq_file *seq; + struct single_client_visit_iter_state *iter; + af_client_info_t *client; + int err; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) + client = PDE_DATA(inode); +#else + client = (af_client_info_t *)proc_get_parent_data(inode); +#endif + + if (!client) + return -ENOENT; + + iter = kzalloc(sizeof(*iter), GFP_KERNEL); + if (!iter) + return -ENOMEM; + + iter->client = client; + + err = seq_open(file, &single_client_visit_seq_ops); + if (err) { + kfree(iter); + return err; + } + + seq = file->private_data; + seq->private = iter; + return 0; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) +static const struct file_operations single_client_visit_fops = { + .owner = THIS_MODULE, + .open = single_client_visit_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; +#else +static const struct proc_ops single_client_visit_fops = { + .proc_flags = PROC_ENTRY_PERMANENT, + .proc_read = seq_read, + .proc_open = single_client_visit_open, + .proc_lseek = seq_lseek, + .proc_release = seq_release_private, +}; +#endif + +int create_client_proc_dir(af_client_info_t *client) +{ + struct proc_dir_entry *client_dir; + struct proc_dir_entry *visit_file; + char mac_str[32] = {0}; + struct net *net = &init_net; + + if (!client) + return -1; + + sprintf(mac_str, MAC_FMT, MAC_ARRAY(client->mac)); + + mutex_lock(&af_client_base_dir_mutex); + if (!g_af_client_base_dir) { + + g_af_client_base_dir = proc_mkdir(AF_CLIENT_BASE_DIR, net->proc_net); + if (!g_af_client_base_dir) { + mutex_unlock(&af_client_base_dir_mutex); + AF_ERROR("create af_client base dir failed\n"); + return -1; + } + } + mutex_unlock(&af_client_base_dir_mutex); + + client_dir = proc_mkdir_data(mac_str, 0555, g_af_client_base_dir, client); + if (!client_dir) { + AF_ERROR("create client dir failed: %s\n", mac_str); + return -1; + } + + client->proc_dir = client_dir; + + visit_file = proc_create_data("visit_list", 0444, client_dir, &single_client_visit_fops, client); + if (!visit_file) { + AF_ERROR("create visit_list file failed for client: %s\n", mac_str); + proc_remove(client_dir); + client->proc_dir = NULL; + return -1; + } + + return 0; +} + +void remove_client_proc_dir(af_client_info_t *client) +{ + if (!client || !client->proc_dir) + return; + + proc_remove(client->proc_dir); + client->proc_dir = NULL; +} diff --git a/oaf/src/fwx_client_fs.h b/oaf/src/fwx_client_fs.h new file mode 100644 index 00000000..fed33f12 --- /dev/null +++ b/oaf/src/fwx_client_fs.h @@ -0,0 +1,14 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __AF_CLIENT_FS_H__ +#define __AF_CLIENT_FS_H__ + +int init_af_client_procfs(void); +void finit_af_client_procfs(void); +int create_client_proc_dir(af_client_info_t *client); +void remove_client_proc_dir(af_client_info_t *client); + +#endif diff --git a/oaf/src/fwx_config.c b/oaf/src/fwx_config.c new file mode 100644 index 00000000..734587bd --- /dev/null +++ b/oaf/src/fwx_config.c @@ -0,0 +1,201 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fwx_config.h" +#include "k_json.h" +#include "fwx.h" +#include "fwx_mac_filter.h" +#include "fwx_app_filter.h" +#include "fwx_client.h" +static struct mutex fwx_cdev_mutex; +struct fwx_config_dev +{ + dev_t id; + struct cdev char_dev; + struct class *c; +}; +struct fwx_config_dev g_fwx_dev; + +struct fwx_cdev_file +{ + size_t size; + char buf[256 << 10]; +}; + +k_request_item_t k_request_api_list[]={ + {"add_mac_filter_rule", fwx_api_add_mac_filter_rule}, + {"del_mac_filter_rule", fwx_api_del_mac_filter_rule}, + {"mod_mac_filter_rule", fwx_api_mod_mac_filter_rule}, + {"dump_mac_filter_rule", fwx_api_dump_mac_filter_rule}, + {"flush_mac_filter_rule", fwx_api_flush_mac_filter_rule}, + {"add_app_filter_rule", fwx_api_add_app_filter_rule}, + {"del_app_filter_rule", fwx_api_del_app_filter_rule}, + {"mod_app_filter_rule", fwx_api_mod_app_filter_rule}, + {"dump_app_filter_rule", fwx_api_dump_app_filter_rule}, + {"flush_app_filter_rule", fwx_api_flush_app_filter_rule}, + {"add_mac_filter_whitelist", fwx_api_add_mac_filter_whitelist}, + {"del_mac_filter_whitelist", fwx_api_del_mac_filter_whitelist}, + {"flush_mac_filter_whitelist", fwx_api_flush_mac_filter_whitelist}, + {"add_app_filter_whitelist", fwx_api_add_app_filter_whitelist}, + {"flush_app_filter_whitelist", fwx_api_flush_app_filter_whitelist}, + {"add_record_whitelist", fwx_api_add_record_whitelist}, + {"del_record_whitelist", fwx_api_del_record_whitelist}, + {"flush_record_whitelist", fwx_api_flush_record_whitelist}, +}; + +int fwx_config_handle(char *config, unsigned int len) +{ + int i; + cJSON *config_obj = NULL; + cJSON *api_obj = NULL; + cJSON *data_obj = NULL; + if (!config || len == 0) + return -1; + + config_obj = cJSON_Parse(config); + if (!config_obj){ + printk("parse json failed, value = %s\n", config); + return -1; + } + api_obj = cJSON_GetObjectItem(config_obj, "api"); + data_obj = cJSON_GetObjectItem(config_obj, "data"); + if (!api_obj){ + printk("error, api obj not set\n"); + cJSON_Delete(config_obj); + return -1; + } + + cJSON *temp_data_obj = NULL; + if (!data_obj){ + temp_data_obj = cJSON_CreateObject(); // 创建一个空的data对象 + data_obj = temp_data_obj; + } + + for (i = 0; i < ARRAY_SIZE(k_request_api_list); i++){ + k_request_item_t *req_item = &k_request_api_list[i]; + if (0 == strcmp(req_item->api, api_obj->valuestring)){ + req_item->handle(data_obj); + break; + } + } + + + if (temp_data_obj){ + cJSON_Delete(temp_data_obj); + } + cJSON_Delete(config_obj); + return 0; +} + +static int fwx_cdev_open(struct inode *inode, struct file *filp) +{ + struct fwx_cdev_file *file; + file = vzalloc(sizeof(*file)); + if (!file) + return -EINVAL; + + mutex_lock(&fwx_cdev_mutex); + filp->private_data = file; + return 0; +} + +static ssize_t fwx_cdev_read(struct file *filp, char *buf, size_t count, loff_t *off) +{ + return 0; +} + +static int fwx_cdev_release(struct inode *inode, struct file *filp) +{ + struct fwx_cdev_file *file = filp->private_data; + fwx_config_handle(file->buf, file->size); + filp->private_data = NULL; + mutex_unlock(&fwx_cdev_mutex); + vfree(file); + return 0; +} + +static ssize_t fwx_cdev_write(struct file *filp, const char *buffer, size_t count, loff_t *off) +{ + struct fwx_cdev_file *file = filp->private_data; + int ret; + if (file->size + count > sizeof(file->buf)) + return -EINVAL; + + ret = copy_from_user(file->buf + file->size, buffer, count); + if (ret != 0) + return -EINVAL; + + file->size += count; + return count; +} + +static struct file_operations fwx_cdev_ops = { + owner : THIS_MODULE, + release : fwx_cdev_release, + open : fwx_cdev_open, + write : fwx_cdev_write, + read : fwx_cdev_read, +}; + +int fwx_register_dev(void) +{ + struct device *dev; + int res; + mutex_init(&fwx_cdev_mutex); + + res = alloc_chrdev_region(&g_fwx_dev.id, 0, 1, FWX_CHAR_DEV); + if (res != 0) + return -EINVAL; + + cdev_init(&g_fwx_dev.char_dev, &fwx_cdev_ops); + res = cdev_add(&g_fwx_dev.char_dev, g_fwx_dev.id, 1); + if (res < 0) + goto REGION_OUT; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + g_fwx_dev.c = class_create(THIS_MODULE, FWX_CHAR_DEV); +#else + g_fwx_dev.c = class_create(FWX_CHAR_DEV); +#endif + + if (IS_ERR_OR_NULL(g_fwx_dev.c)) + goto CDEV_OUT; + + dev = device_create(g_fwx_dev.c, NULL, g_fwx_dev.id, NULL, FWX_CHAR_DEV); + if (IS_ERR_OR_NULL(dev)) + goto CLASS_OUT; + return 0; + +CLASS_OUT: + class_destroy(g_fwx_dev.c); +CDEV_OUT: + cdev_del(&g_fwx_dev.char_dev); +REGION_OUT: + unregister_chrdev_region(g_fwx_dev.id, 1); + printk("register char dev....fail\n"); + return -EINVAL; +} + +void fwx_unregister_dev(void) +{ + device_destroy(g_fwx_dev.c, g_fwx_dev.id); + class_destroy(g_fwx_dev.c); + cdev_del(&g_fwx_dev.char_dev); + unregister_chrdev_region(g_fwx_dev.id, 1); +} diff --git a/oaf/src/fwx_config.h b/oaf/src/fwx_config.h new file mode 100644 index 00000000..e11eaf81 --- /dev/null +++ b/oaf/src/fwx_config.h @@ -0,0 +1,19 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_CONFIG_H__ +#define __FWX_CONFIG_H__ +#include "k_json.h" +#define FWX_CHAR_DEV "fwx" +typedef int (*k_request_handler)(cJSON *data_obj); +typedef struct k_request_item{ + const char *api; + k_request_handler handle; +}k_request_item_t; + + +int fwx_register_dev(void); +void fwx_unregister_dev(void); +#endif diff --git a/oaf/src/af_conntrack.c b/oaf/src/fwx_conntrack.c similarity index 93% rename from oaf/src/af_conntrack.c rename to oaf/src/fwx_conntrack.c index 710381d2..69096775 100644 --- a/oaf/src/af_conntrack.c +++ b/oaf/src/fwx_conntrack.c @@ -1,3 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ #include #include #include @@ -16,8 +20,9 @@ #include #include #include -#include "af_conntrack.h" -#include "af_log.h" +#include "fwx_conntrack.h" +#include "fwx_log.h" +#include "fwx.h" struct hlist_head af_conn_table[AF_CONN_HASH_SIZE]; @@ -264,7 +269,7 @@ static const struct proc_ops af_conn_fops = { #define AF_CONN_PROC_STR "af_conn" -static int af_conn_init_procfs(void) +int af_conn_init_procfs(void) { struct proc_dir_entry *pde; struct net *net = &init_net; @@ -278,7 +283,7 @@ static int af_conn_init_procfs(void) return 0; } -static void af_conn_remove_procfs(void) +void af_conn_remove_procfs(void) { struct net *net = &init_net; remove_proc_entry(AF_CONN_PROC_STR, net->proc_net); diff --git a/oaf/src/af_conntrack.h b/oaf/src/fwx_conntrack.h similarity index 87% rename from oaf/src/af_conntrack.h rename to oaf/src/fwx_conntrack.h index 888896e3..2e93f276 100644 --- a/oaf/src/af_conntrack.h +++ b/oaf/src/fwx_conntrack.h @@ -1,3 +1,8 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ #ifndef __AF_SIMPLE_CONNTRACK_H__ #define __AF_SIMPLE_CONNTRACK_H__ diff --git a/oaf/src/af_log.c b/oaf/src/fwx_log.c similarity index 53% rename from oaf/src/af_log.c rename to oaf/src/fwx_log.c index 187e3a82..6e82d311 100644 --- a/oaf/src/af_log.c +++ b/oaf/src/fwx_log.c @@ -1,183 +1,228 @@ -#include -#include -#include -#include -#include -#include -#include "app_filter.h" -#include "af_log.h" -int af_log_lvl = 1; -int af_test_mode = 0; -// todo: rename af_log.c -int g_oaf_filter_enable __read_mostly = 0; -int g_oaf_record_enable __read_mostly = 0; -int g_by_pass_accl = 1; -int g_user_mode = 0; -int af_work_mode = AF_MODE_GATEWAY; -unsigned int af_lan_ip = 0; -unsigned int af_lan_mask = 0; -char g_lan_ifname[64] = "br-lan"; -int g_tcp_rst = 1; -int g_feature_init = 0; -char g_oaf_version[64] = AF_VERSION; -int g_disable_quic = 0; -int g_app_filter_mode = 0; // 0 = specified apps, 1 = all apps -/* - cat /proc/sys/oaf/debug -*/ -static struct ctl_table oaf_table[] = { - { - .procname = "debug", - .data = &af_log_lvl, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "feature_init", - .data = &g_feature_init, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "version", - .data = g_oaf_version, - .maxlen = 64, - .mode = 0444, - .proc_handler = proc_dostring, - }, - { - .procname = "test_mode", - .data = &af_test_mode, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "enable", - .data = &g_oaf_filter_enable, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "by_pass_accl", - .data = &g_by_pass_accl, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "tcp_rst", - .data = &g_tcp_rst, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "lan_ifname", - .data = g_lan_ifname, - .maxlen = 64, - .mode = 0666, - .proc_handler = proc_dostring, - }, - { - .procname = "record_enable", - .data = &g_oaf_record_enable, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "user_mode", - .data = &g_user_mode, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "work_mode", - .data = &af_work_mode, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "lan_ip", - .data = &af_lan_ip, - .maxlen = sizeof(unsigned int), - .mode = 0666, - .proc_handler = proc_douintvec, - }, - { - .procname = "lan_mask", - .data = &af_lan_mask, - .maxlen = sizeof(unsigned int), - .mode = 0666, - .proc_handler = proc_douintvec, - }, - { - .procname = "disable_quic", - .data = &g_disable_quic, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, - { - .procname = "app_filter_mode", - .data = &g_app_filter_mode, - .maxlen = sizeof(int), - .mode = 0666, - .proc_handler = proc_dointvec, - }, -#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0)) - { - } -#endif -}; -#define OAF_SYS_PROC_DIR "oaf" - -#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)) -static struct ctl_table oaf_root_table[] = { - { - .procname = OAF_SYS_PROC_DIR, - .mode = 0555, - .child = oaf_table, - }, - {} -}; -#endif -static struct ctl_table_header *oaf_table_header; - - -static int af_init_log_sysctl(void) -{ -#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)) - oaf_table_header = register_sysctl_table(oaf_root_table); -#else - oaf_table_header = register_sysctl(OAF_SYS_PROC_DIR, oaf_table); -#endif - if (oaf_table_header == NULL){ - printk("init log sysctl...failed\n"); - return -ENOMEM; - } - return 0; -} - -static int af_fini_log_sysctl(void) -{ - if (oaf_table_header) - unregister_sysctl_table(oaf_table_header); - return 0; -} - -int af_log_init(void){ - af_init_log_sysctl(); - return 0; -} - -int af_log_exit(void){ - af_fini_log_sysctl(); - return 0; -} +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include "fwx.h" +#include "fwx_log.h" +#include "fwx_mac_filter.h" +#include "fwx_app_filter.h" +#include "fwx_client.h" +int af_log_lvl = 0; +int fwx_test_mode = 0; + +int g_record_enable = 0; +int g_by_pass_accl = 1; +int g_user_mode = 0; +int af_work_mode = AF_MODE_GATEWAY; +unsigned int fwx_lan_ip = 0; +unsigned int fwx_lan_mask = 0; +char g_lan_ifname[64] = "br-lan"; +int g_tcp_rst = 1; +int g_feature_init = 0; +char g_fwx_version[64] = FWX_VERSION; +int g_feature_count = 0; +char g_record_whitelist[1024] = {0}; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0)) +static int fwx_proc_record_whitelist_handler(struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +#else +static int fwx_proc_record_whitelist_handler(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +#endif +{ + int ret = 0; + + ret = proc_dostring(table, write, buffer, lenp, ppos); + if (ret || !write) { + return ret; + } + + fwx_set_record_whitelist(g_record_whitelist); + return ret; +} + +static struct ctl_table fwx_table[] = { + { + .procname = "debug", + .data = &af_log_lvl, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "feature_init", + .data = &g_feature_init, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "version", + .data = g_fwx_version, + .maxlen = 64, + .mode = 0444, + .proc_handler = proc_dostring, + }, + { + .procname = "feature_count", + .data = &g_feature_count, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "test_mode", + .data = &fwx_test_mode, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "appfilter_enable", + .data = &g_appfilter_enable, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "macfilter_enable", + .data = &g_mac_filter_enable, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "by_pass_accl", + .data = &g_by_pass_accl, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "tcp_rst", + .data = &g_tcp_rst, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "lan_ifname", + .data = g_lan_ifname, + .maxlen = 64, + .mode = 0666, + .proc_handler = proc_dostring, + }, + { + .procname = "record_enable", + .data = &g_record_enable, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "record_whitelist", + .data = g_record_whitelist, + .maxlen = sizeof(g_record_whitelist), + .mode = 0666, + .proc_handler = fwx_proc_record_whitelist_handler, + }, + { + .procname = "user_mode", + .data = &g_user_mode, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "work_mode", + .data = &af_work_mode, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "lan_ip", + .data = &fwx_lan_ip, + .maxlen = sizeof(unsigned int), + .mode = 0666, + .proc_handler = proc_douintvec, + }, + { + .procname = "lan_mask", + .data = &fwx_lan_mask, + .maxlen = sizeof(unsigned int), + .mode = 0666, + .proc_handler = proc_douintvec, + }, + { + .procname = "max_app_report_count", + .data = &g_max_app_report_count, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + { + .procname = "min_http_match_count", + .data = &g_min_http_match_count, + .maxlen = sizeof(int), + .mode = 0666, + .proc_handler = proc_dointvec, + }, + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0)) + { + } +#endif +}; +#define FWX_SYS_PROC_DIR "fwx" + +static struct ctl_table fwx_root_table[] = { + { + .procname = FWX_SYS_PROC_DIR, + .mode = 0555, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)) + .child = fwx_table, +#endif + }, + {} +}; +static struct ctl_table_header *fwx_table_header; + + +static int af_init_log_sysctl(void) +{ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)) + fwx_table_header = register_sysctl_table(fwx_root_table); +#else + fwx_table_header = register_sysctl(FWX_SYS_PROC_DIR, fwx_table); +#endif + if (fwx_table_header == NULL){ + printk("init log sysctl...failed\n"); + return -ENOMEM; + } + return 0; +} + +static int af_fini_log_sysctl(void) +{ + if (fwx_table_header) + unregister_sysctl_table(fwx_table_header); + return 0; +} + +int af_log_init(void){ + af_init_log_sysctl(); + return 0; +} + +int af_log_exit(void){ + af_fini_log_sysctl(); + return 0; +} diff --git a/oaf/src/fwx_log.h b/oaf/src/fwx_log.h new file mode 100644 index 00000000..1e0a256f --- /dev/null +++ b/oaf/src/fwx_log.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __AF_DEBUG_H__ +#define __AF_DEBUG_H__ +extern int fwx_test_mode; +extern int af_work_mode; +extern int g_app_filter_enable; +extern int g_record_enable; +extern int g_by_pass_accl; +extern unsigned int fwx_lan_ip; +extern unsigned int fwx_lan_mask; +extern int g_feature_init; +extern int g_user_mode; +extern char g_lan_ifname[64]; +extern int g_tcp_rst; +extern int g_feature_count; + +#define TEST_MODE() (fwx_test_mode) +int af_log_init(void); +int af_log_exit(void); +#endif diff --git a/oaf/src/fwx_mac.c b/oaf/src/fwx_mac.c new file mode 100644 index 00000000..91ba1bd6 --- /dev/null +++ b/oaf/src/fwx_mac.c @@ -0,0 +1,81 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include "fwx.h" +#include "fwx_mac.h" + +static int mac_hash(const unsigned char *mac) { + int hash = 0; + int i; + for (i = 0; i < ETH_ALEN; i++) { + hash += mac[i]; + } + return hash % MAC_HASH_SIZE; +} + +void fwx_mac_config_init(mac_config_t *config){ + int i; + for (i = 0; i < MAC_HASH_SIZE; i++){ + INIT_HLIST_HEAD(&config->hash_table[i]); + } +} + +void fwx_add_mac_node(mac_config_t *config, const unsigned char *mac) { + struct mac_node *new_node; + int hash = mac_hash(mac); + + new_node = kmalloc(sizeof(struct mac_node), GFP_KERNEL); + if (!new_node) { + pr_err("Memory allocation failed\n"); + return; + } + memcpy(new_node->mac, mac, ETH_ALEN); + INIT_HLIST_NODE(&new_node->hlist); + + hlist_add_head(&new_node->hlist, &config->hash_table[hash]); +} + +void fwx_dump_mac_node(mac_config_t *config) { + int i; + struct mac_node *node; + for(i = 0; i < MAC_HASH_SIZE; i++){ + hlist_for_each_entry(node, &config->hash_table[i], hlist) { + printk("dump node: %pM\n", node->mac); + } + } +} + +struct mac_node *fwx_find_mac_node(mac_config_t *config, const unsigned char *mac){ + struct mac_node *node; + if (!config || !mac) + return NULL; + int hash = mac_hash(mac); + hlist_for_each_entry(node, &config->hash_table[hash], hlist) { + if (memcmp(node->mac, mac, ETH_ALEN) == 0) { + return node; + } + } + return NULL; +} + +struct mac_node *fwx_flush_mac_list(mac_config_t *config){ + int i; + struct mac_node *node; + struct hlist_node *n; + if (!config) + return NULL; + for (i = 0; i < MAC_HASH_SIZE; i++){ + hlist_for_each_entry_safe(node, n, &config->hash_table[i], hlist) { + hlist_del(&node->hlist); + kfree(node); + } + } + return NULL; +} diff --git a/oaf/src/fwx_mac.h b/oaf/src/fwx_mac.h new file mode 100644 index 00000000..7ccf23c8 --- /dev/null +++ b/oaf/src/fwx_mac.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_MAC_H__ +#define __FWX_MAC_H__ +#define MAC_HASH_SIZE 128 +#ifndef ETH_ALEN +#define ETH_ALEN 6 +#endif +struct mac_node { + unsigned char mac[ETH_ALEN]; + struct hlist_node hlist; +}; + +typedef struct mac_config{ + struct hlist_head hash_table[MAC_HASH_SIZE]; +}mac_config_t; + +void fwx_mac_config_init(mac_config_t *config); +void fwx_add_mac_node(mac_config_t *config, const unsigned char *mac); +void fwx_dump_mac_node(mac_config_t *config); +struct mac_node *fwx_find_mac_node(mac_config_t *config, const unsigned char *mac); +int mac_str_to_bin(const char *mac_str, u8 *mac_bin); +struct mac_node *fwx_flush_mac_list(mac_config_t *config); + +#endif diff --git a/oaf/src/fwx_mac_filter.c b/oaf/src/fwx_mac_filter.c new file mode 100644 index 00000000..b712779a --- /dev/null +++ b/oaf/src/fwx_mac_filter.c @@ -0,0 +1,545 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include "k_json.h" +#include "fwx.h" +#include "fwx_mac.h" +#include "fwx_mac_filter.h" + +DEFINE_RWLOCK(mac_filter_lock); + +#define mac_filter_read_lock() read_lock_bh(&mac_filter_lock); +#define mac_filter_read_unlock() read_unlock_bh(&mac_filter_lock); +#define mac_filter_write_lock() write_lock_bh(&mac_filter_lock); +#define mac_filter_write_unlock() write_unlock_bh(&mac_filter_lock); +static int g_mac_rule_count = 0; +static LIST_HEAD(mac_filter_rule_list); +int g_mac_filter_enable = 0; + +static mac_config_t g_mac_filter_whitelist; + +int fwx_mac_filter_init(void) { + mac_filter_write_lock(); + INIT_LIST_HEAD(&mac_filter_rule_list); + g_mac_rule_count = 0; + fwx_mac_config_init(&g_mac_filter_whitelist); + mac_filter_write_unlock(); + return 0; +} + +void fwx_mac_filter_exit(void) { + mac_filter_rule_t *rule, *next; + mac_filter_write_lock(); + list_for_each_entry_safe(rule, next, &mac_filter_rule_list, list) { + fwx_flush_mac_list(&rule->mac_list); + list_del(&rule->list); + kfree(rule); + } + g_mac_rule_count = 0; + fwx_flush_mac_list(&g_mac_filter_whitelist); + mac_filter_write_unlock(); +} + +mac_filter_rule_t *fwx_find_mac_filter_rule(int rule_id) { + mac_filter_rule_t *rule; + list_for_each_entry(rule, &mac_filter_rule_list, list) { + if (rule->rule_id == rule_id) { + return rule; + } + } + return NULL; +} + +int fwx_add_mac_filter_rule(int rule_id, int mode) { + mac_filter_rule_t *rule; + + if (g_mac_rule_count >= MAX_MAC_FILTER_RULE_NUM) { + return -1; + } + + if (fwx_find_mac_filter_rule(rule_id)) { + return -1; + } + + rule = kmalloc(sizeof(mac_filter_rule_t), GFP_ATOMIC); + if (!rule) { + printk("kmalloc mac filter rule failed\n"); + return -1; + } + + rule->rule_id = rule_id; + rule->mode = (mode == MAC_FILTER_MODE_SINGLE_USER) ? MAC_FILTER_MODE_SINGLE_USER : MAC_FILTER_MODE_ALL_USERS; + fwx_mac_config_init(&rule->mac_list); + INIT_LIST_HEAD(&rule->list); + + mac_filter_write_lock(); + list_add(&rule->list, &mac_filter_rule_list); + g_mac_rule_count++; + mac_filter_write_unlock(); + + return 0; +} + +int fwx_del_mac_filter_rule(int rule_id) { + mac_filter_rule_t *rule; + + mac_filter_write_lock(); + rule = fwx_find_mac_filter_rule(rule_id); + if (rule) { + fwx_flush_mac_list(&rule->mac_list); + list_del(&rule->list); + kfree(rule); + g_mac_rule_count--; + mac_filter_write_unlock(); + return 0; + } + mac_filter_write_unlock(); + + return -1; +} + +int fwx_add_mac_to_rule(int rule_id, const unsigned char *mac) { + mac_filter_rule_t *rule; + + mac_filter_write_lock(); + rule = fwx_find_mac_filter_rule(rule_id); + if (rule) { + fwx_add_mac_node(&rule->mac_list, mac); + mac_filter_write_unlock(); + return 0; + } + mac_filter_write_unlock(); + + return -1; +} + +int fwx_del_mac_from_rule(int rule_id, const unsigned char *mac) { + mac_filter_rule_t *rule; + struct mac_node *node; + + mac_filter_write_lock(); + rule = fwx_find_mac_filter_rule(rule_id); + if (rule) { + node = fwx_find_mac_node(&rule->mac_list, mac); + if (node) { + hlist_del(&node->hlist); + kfree(node); + mac_filter_write_unlock(); + return 0; + } + } + mac_filter_write_unlock(); + + return -1; +} + +mac_filter_rule_t *fwx_match_mac_filter_rule(const unsigned char *mac) { + mac_filter_rule_t *rule; + struct mac_node *node; + + mac_filter_read_lock(); + list_for_each_entry(rule, &mac_filter_rule_list, list) { + if (rule->mode == MAC_FILTER_MODE_ALL_USERS) { + mac_filter_read_unlock(); + return rule; + } + + node = fwx_find_mac_node(&rule->mac_list, mac); + if (node) { + mac_filter_read_unlock(); + return rule; + } + } + mac_filter_read_unlock(); + return NULL; +} + +int fwx_api_add_mac_filter_rule(cJSON *data_obj) { + cJSON *rule_id_obj; + cJSON *mode_obj; + int mode = MAC_FILTER_MODE_ALL_USERS; + + if (!data_obj) { + return -1; + } + + rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id"); + mode_obj = cJSON_GetObjectItem(data_obj, "mode"); + if (mode_obj) { + mode = mode_obj->valueint; + } + if (mode != MAC_FILTER_MODE_SINGLE_USER) { + mode = MAC_FILTER_MODE_ALL_USERS; + } + if (!rule_id_obj) { + printk("invalid rule format\n"); + return -1; + } + + if (fwx_add_mac_filter_rule(rule_id_obj->valueint, mode) < 0) { + return -1; + } + + return 0; +} + +int fwx_api_mod_mac_filter_rule(cJSON *data_obj) { + int i; + cJSON *rule_id_obj; + cJSON *mac_array; + cJSON *mac_obj; + cJSON *action_obj; + cJSON *mode_obj; + mac_filter_rule_t *rule = NULL; + + if (!data_obj) { + return -1; + } + rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id"); + + action_obj = cJSON_GetObjectItem(data_obj, "mac_action"); + + + if (!rule_id_obj) { + printk("rule_id not found\n"); + return -1; + } + + + rule = fwx_find_mac_filter_rule(rule_id_obj->valueint); + if (!rule) { + printk("rule %d not found\n", rule_id_obj->valueint); + return -1; + } + + mode_obj = cJSON_GetObjectItem(data_obj, "mode"); + if (mode_obj) { + int mode = mode_obj->valueint; + if (mode != MAC_FILTER_MODE_SINGLE_USER) { + mode = MAC_FILTER_MODE_ALL_USERS; + } + mac_filter_write_lock(); + rule->mode = mode; + mac_filter_write_unlock(); + } + + + if (action_obj){ + if (action_obj->valueint == 1 || action_obj->valueint == 2) { + mac_array = cJSON_GetObjectItem(data_obj, "mac_list"); + if (mac_array) { + mac_filter_write_lock(); + if (action_obj->valueint == 1){ // flush old + fwx_flush_mac_list(&rule->mac_list); + } + for (i = 0; i < cJSON_GetArraySize(mac_array); i++) { + mac_obj = cJSON_GetArrayItem(mac_array, i); + u8 mac_bin[ETH_ALEN] = {0}; + if (mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + fwx_add_mac_node(&rule->mac_list, mac_bin); + } + } + mac_filter_write_unlock(); + } + } + + else if (action_obj->valueint == 3) { + mac_obj = cJSON_GetObjectItem(data_obj, "mac"); + if (mac_obj) { + mac_filter_write_lock(); + u8 mac_bin[ETH_ALEN] = {0}; + if (mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + fwx_add_mac_node(&rule->mac_list, mac_bin); + } + mac_filter_write_unlock(); + } + } + else{ + mac_filter_write_lock(); + fwx_flush_mac_list(&rule->mac_list); + mac_filter_write_unlock(); + } + } + + return 0; +} + +int fwx_api_del_mac_filter_rule(cJSON *data_obj) { + cJSON *rule_id_obj; + cJSON *mac_obj; + + if (!data_obj) { + return -1; + } + + rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id"); + if (!rule_id_obj) { + printk("rule_id not found\n"); + return -1; + } + + mac_obj = cJSON_GetObjectItem(data_obj, "mac"); + if (mac_obj) { + + u8 mac_bin[ETH_ALEN] = {0}; + if (mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + return fwx_del_mac_from_rule(rule_id_obj->valueint, mac_bin); + } + } else { + + return fwx_del_mac_filter_rule(rule_id_obj->valueint); + } + + return -1; +} + +int fwx_api_dump_mac_filter_rule(cJSON *data_obj) { + mac_filter_rule_t *rule; + struct mac_node *node; + int mac_count = 0; + int i; + + if (!data_obj) { + return -1; + } + + cJSON *rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id"); + + mac_filter_read_lock(); + + printk("\n"); + printk("+--------+------+----------------------------------------+\n"); + printk("| RuleID | Mode | MAC List |\n"); + printk("+--------+------+----------------------------------------+\n"); + + if (rule_id_obj) { + rule = fwx_find_mac_filter_rule(rule_id_obj->valueint); + if (rule) { + + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(node, &rule->mac_list.hash_table[i], hlist) { + mac_count++; + } + } + + + printk(KERN_CONT "| %-6d | %-4d | ", rule->rule_id, rule->mode); + + + if (rule->mode == MAC_FILTER_MODE_ALL_USERS) { + printk(KERN_CONT "%-38s |\n", "(all users)"); + } else if (mac_count == 0) { + printk(KERN_CONT "%-38s |\n", "(empty)"); + } else { + int total_mac_count = mac_count; + mac_count = 0; + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(node, &rule->mac_list.hash_table[i], hlist) { + if (mac_count > 0) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "%pM", node->mac); + mac_count++; + if (mac_count >= 5) { // 最多显示5个MAC + if (mac_count < total_mac_count) { + printk(KERN_CONT "..."); + } + break; + } + } + } + printk(KERN_CONT " |\n"); + } + } + } else { + list_for_each_entry(rule, &mac_filter_rule_list, list) { + mac_count = 0; + + + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(node, &rule->mac_list.hash_table[i], hlist) { + mac_count++; + } + } + + + printk(KERN_CONT "| %-6d | %-4d | ", rule->rule_id, rule->mode); + + + if (rule->mode == MAC_FILTER_MODE_ALL_USERS) { + printk(KERN_CONT "%-38s |\n", "(all users)"); + } else if (mac_count == 0) { + printk(KERN_CONT "%-38s |\n", "(empty)"); + } else { + int total_mac_count = mac_count; + mac_count = 0; + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(node, &rule->mac_list.hash_table[i], hlist) { + if (mac_count > 0) { + printk(KERN_CONT ", "); + } + printk(KERN_CONT "%pM", node->mac); + mac_count++; + if (mac_count >= 5) { // 最多显示5个MAC + if (mac_count < total_mac_count) { + printk(KERN_CONT "..."); + } + break; + } + } + } + printk(KERN_CONT " |\n"); + } + } + } + + printk("+--------+------+----------------------------------------+\n"); + + + printk("\n"); + printk("MAC Filter Whitelist:\n"); + printk("+----------------------------------------+\n"); + printk("| MAC Address |\n"); + printk("+----------------------------------------+\n"); + + int total_whitelist_count = 0; + for (i = 0; i < MAC_HASH_SIZE; i++) { + hlist_for_each_entry(node, &g_mac_filter_whitelist.hash_table[i], hlist) { + total_whitelist_count++; + } + } + + if (total_whitelist_count == 0) { + printk(KERN_CONT "| %-38s |\n", "(empty)"); + } else { + int printed_count = 0; + int should_break = 0; + + for (i = 0; i < MAC_HASH_SIZE && !should_break; i++) { + hlist_for_each_entry(node, &g_mac_filter_whitelist.hash_table[i], hlist) { + printk(KERN_CONT "| %-38pM |\n", node->mac); + printed_count++; + if (printed_count >= 10) { // 最多显示10个MAC + if (printed_count < total_whitelist_count) { + printk(KERN_CONT "| %-38s |\n", "..."); + } + should_break = 1; + break; + } + } + } + } + + printk("+----------------------------------------+\n"); + printk("Total whitelist entries: %d\n", total_whitelist_count); + + mac_filter_read_unlock(); + + return 0; +} + +int fwx_api_flush_mac_filter_rule(cJSON *data_obj) { + mac_filter_rule_t *rule, *next; + + mac_filter_write_lock(); + list_for_each_entry_safe(rule, next, &mac_filter_rule_list, list) { + fwx_flush_mac_list(&rule->mac_list); + list_del(&rule->list); + kfree(rule); + } + g_mac_rule_count = 0; + mac_filter_write_unlock(); + + return 0; +} + + +int fwx_match_mac_filter_whitelist(const unsigned char *mac) { + struct mac_node *node; + int ret = 0; + + mac_filter_read_lock(); + node = fwx_find_mac_node(&g_mac_filter_whitelist, mac); + ret = (node != NULL); + mac_filter_read_unlock(); + + return ret; +} + + +int fwx_api_add_mac_filter_whitelist(cJSON *data_obj) { + cJSON *mac_array; + int i; + u8 mac_bin[ETH_ALEN]; + + if (!data_obj) { + return -1; + } + + mac_array = cJSON_GetObjectItem(data_obj, "mac_list"); + if (!mac_array) { + printk("mac_list not found\n"); + return -1; + } + + mac_filter_write_lock(); + for (i = 0; i < cJSON_GetArraySize(mac_array); i++) { + cJSON *mac_obj = cJSON_GetArrayItem(mac_array, i); + if (mac_obj && mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + fwx_add_mac_node(&g_mac_filter_whitelist, mac_bin); + } + } + mac_filter_write_unlock(); + + return 0; +} + + +int fwx_api_del_mac_filter_whitelist(cJSON *data_obj) { + cJSON *mac_obj; + u8 mac_bin[ETH_ALEN]; + struct mac_node *node; + + if (!data_obj) { + return -1; + } + + mac_obj = cJSON_GetObjectItem(data_obj, "mac"); + if (!mac_obj) { + printk("mac not found\n"); + return -1; + } + + if (!mac_str_to_bin(mac_obj->valuestring, mac_bin)) { + printk("invalid mac format\n"); + return -1; + } + + mac_filter_write_lock(); + node = fwx_find_mac_node(&g_mac_filter_whitelist, mac_bin); + if (node) { + hlist_del(&node->hlist); + kfree(node); + mac_filter_write_unlock(); + return 0; + } + mac_filter_write_unlock(); + + return -1; +} + + +int fwx_api_flush_mac_filter_whitelist(cJSON *data_obj) { + mac_filter_write_lock(); + fwx_flush_mac_list(&g_mac_filter_whitelist); + mac_filter_write_unlock(); + return 0; +} diff --git a/oaf/src/fwx_mac_filter.h b/oaf/src/fwx_mac_filter.h new file mode 100644 index 00000000..cd57bad8 --- /dev/null +++ b/oaf/src/fwx_mac_filter.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_MAC_FILTER_H__ +#define __FWX_MAC_FILTER_H__ +#include "k_json.h" +#include "fwx_mac.h" +#include + +#define MAX_MAC_FILTER_RULE_NUM 64 +#define MAC_FILTER_MODE_ALL_USERS 1 +#define MAC_FILTER_MODE_SINGLE_USER 2 + +typedef struct mac_filter_rule { + int rule_id; + int mode; + mac_config_t mac_list; + struct list_head list; +} mac_filter_rule_t; + +extern int g_mac_filter_enable; + +int fwx_api_add_mac_filter_whitelist(cJSON *data_obj); +int fwx_api_del_mac_filter_whitelist(cJSON *data_obj); +int fwx_api_flush_mac_filter_whitelist(cJSON *data_obj); + +int fwx_match_mac_filter_whitelist(const unsigned char *mac); + +int fwx_mac_filter_init(void); + +void fwx_mac_filter_exit(void); + +int fwx_add_mac_filter_rule(int rule_id, int mode); + +int fwx_del_mac_filter_rule(int rule_id); + +mac_filter_rule_t *fwx_find_mac_filter_rule(int rule_id); + +int fwx_add_mac_to_rule(int rule_id, const unsigned char *mac); + +int fwx_del_mac_from_rule(int rule_id, const unsigned char *mac); + +mac_filter_rule_t *fwx_match_mac_filter_rule(const unsigned char *mac); + +int fwx_api_add_mac_filter_rule(cJSON *data_obj); + +int fwx_api_del_mac_filter_rule(cJSON *data_obj); + +int fwx_api_dump_mac_filter_rule(cJSON *data_obj); + +int fwx_api_flush_mac_filter_rule(cJSON *data_obj); + +int fwx_api_mod_mac_filter_rule(cJSON *data_obj); + +#endif \ No newline at end of file diff --git a/oaf/src/fwx_main.c b/oaf/src/fwx_main.c new file mode 100644 index 00000000..d5c596bb --- /dev/null +++ b/oaf/src/fwx_main.c @@ -0,0 +1,3501 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: destan19(TT) + * Date: 2019/1/10 + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fwx.h" +#include "fwx_utils.h" +#include "fwx_log.h" +#include "fwx_client.h" +#include "fwx_client_fs.h" +#include "k_json.h" +#include "fwx_conntrack.h" +#include "fwx_config.h" +#include "fwx_mac_filter.h" +#include "fwx_app_filter.h" + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("www.fanchmwrt.com"); +MODULE_DESCRIPTION("fwx module"); +MODULE_VERSION(FWX_VERSION); +struct list_head af_feature_head = LIST_HEAD_INIT(af_feature_head); + +DEFINE_RWLOCK(af_feature_lock); + + + + +static LIST_HEAD(active_app_list); +static DEFINE_SPINLOCK(active_app_list_lock); + + +static LIST_HEAD(active_host_list); +static DEFINE_SPINLOCK(active_host_list_lock); + +u_int32_t fwx_log_level = 3; + +#define feature_list_read_lock() read_lock_bh(&af_feature_lock); +#define feature_list_read_unlock() read_unlock_bh(&af_feature_lock); +#define feature_list_write_lock() write_lock_bh(&af_feature_lock); +#define feature_list_write_unlock() write_unlock_bh(&af_feature_lock); + + +#define FWX_CT_APPID_MASK 0x0000FFFFU +#define FWX_CT_DNS_MATCH_BIT 0x10000000U +#define FWX_CT_IGNORE_BIT 0x20000000U +#define FWX_CT_CLIENT_HELLO_BIT 0x40000000U +#define FWX_CT_DROP_BIT 0x80000000U + +static inline u_int32_t fwx_ct_mark_get(const struct nf_conn *ct) +{ + return READ_ONCE(ct->mark); +} + +static inline void fwx_ct_mark_update(struct nf_conn *ct, u_int32_t mask, + u_int32_t value) +{ + u_int32_t mark = fwx_ct_mark_get(ct); + + WRITE_ONCE(ct->mark, (mark & ~mask) | (value & mask)); +} + +static inline u_int32_t fwx_ct_get_appid(const struct nf_conn *ct) +{ + return fwx_ct_mark_get(ct) & FWX_CT_APPID_MASK; +} + +static inline int fwx_ct_is_valid_appid(u_int32_t app_id) +{ + return app_id > 0 && app_id <= 32000; +} + +static inline void fwx_ct_set_appid(struct nf_conn *ct, u_int32_t app_id) +{ + fwx_ct_mark_update(ct, FWX_CT_APPID_MASK, app_id); +} + +static inline int fwx_ct_test_bit(const struct nf_conn *ct, u_int32_t bit) +{ + return (fwx_ct_mark_get(ct) & bit) != 0; +} + +static inline void fwx_ct_set_bit(struct nf_conn *ct, u_int32_t bit, int set) +{ + fwx_ct_mark_update(ct, bit, set ? bit : 0); +} + +static inline int fwx_ct_has_valid_drop_mark(const struct nf_conn *ct) +{ + u_int32_t app_id = fwx_ct_get_appid(ct); + + return fwx_ct_test_bit(ct, FWX_CT_DROP_BIT) && + (app_id == 0 || fwx_ct_is_valid_appid(app_id)); +} +#define MAX_OAF_NETLINK_MSG_LEN 1024 +#define MAX_AF_SUPPORT_DATA_LEN 3000 +#define AF_AC_CHARSET_SIZE 256 + + +int af_match_port(port_info_t *info, int port); +int af_match_one(flow_info_t *flow, af_feature_node_t *node); + +typedef struct af_ac_output_item { + struct list_head list; + af_feature_node_t *feature; +} af_ac_output_item_t; + +typedef struct af_ac_node af_ac_node_t; + +typedef struct af_ac_edge { + struct hlist_node hnode; + u8 ch; + af_ac_node_t *next; +} af_ac_edge_t; + +struct af_ac_node { + struct hlist_head edges; + struct list_head output_list; + struct list_head all_list; + af_ac_node_t *fail; +}; + +typedef struct af_url_ac { + af_ac_node_t *root; + struct list_head node_list; + u32 node_count; + u32 edge_count; + u32 output_count; + int ready; +} af_url_ac_t; + +typedef struct af_ac_match_stat { + u32 ac_state_steps; + u32 ac_fail_jumps; + u32 ac_candidate_checks; + u32 ac_match_count; + u32 fallback_checks; +} af_ac_match_stat_t; + +static af_url_ac_t g_url_ac = { + .root = NULL, + .node_list = LIST_HEAD_INIT(g_url_ac.node_list), + .node_count = 0, + .edge_count = 0, + .output_count = 0, + .ready = 0, +}; +static int g_url_ac_dirty = 1; + +#if LINUX_VERSION_CODE > KERNEL_VERSION(5,10,197) +extern void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb, int hook); +#elif LINUX_VERSION_CODE > KERNEL_VERSION(4,4,1) +extern void nf_send_reset(struct net *net, struct sk_buff *oldskb, int hook); +#else +extern void nf_send_reset(sk_buff *oldskb, int hook); +#endif + +char *ipv6_to_str(const struct in6_addr *addr, char *str) +{ + sprintf(str, "%pI6c", addr); + return str; +} + +static int af_is_digit_str(const char *str) +{ + if (!str || !*str) { + return 0; + } + + while (*str) { + if (*str < '0' || *str > '9') { + return 0; + } + str++; + } + + return 1; +} + +static int af_is_ipv4_literal(const char *str) +{ + u8 buf[4]; + + if (!str || !*str) { + return 0; + } + + return in4_pton(str, -1, buf, -1, NULL) ? 1 : 0; +} + +static int af_is_ipv6_literal(const char *str) +{ + u8 buf[sizeof(struct in6_addr)]; + + if (!str || !*str) { + return 0; + } + + return in6_pton(str, -1, buf, -1, NULL) ? 1 : 0; +} + +static int af_is_ip_literal_host(const char *host) +{ + char literal_buf[MAX_URL_MATCH_LEN] = {0}; + const char *start = host; + const char *end = NULL; + const char *port_sep = NULL; + int host_len = 0; + int colon_count = 0; + int i = 0; + + if (!host || !*host) { + return 0; + } + + if (*start == '[') { + end = strchr(start + 1, ']'); + if (!end) { + return 0; + } + + host_len = end - (start + 1); + if (host_len <= 0 || host_len >= (MAX_URL_MATCH_LEN - 1)) { + return 0; + } + + memcpy(literal_buf, start + 1, host_len); + literal_buf[host_len] = '\0'; + return af_is_ipv6_literal(literal_buf); + } + + for (i = 0; start[i]; i++) { + if (start[i] == ':') { + colon_count++; + port_sep = start + i; + } + } + + if (colon_count > 1) { + return af_is_ipv6_literal(start); + } + + if (colon_count == 1 && port_sep && af_is_digit_str(port_sep + 1)) { + host_len = port_sep - start; + if (host_len <= 0 || host_len >= (MAX_URL_MATCH_LEN - 1)) { + return 0; + } + + memcpy(literal_buf, start, host_len); + literal_buf[host_len] = '\0'; + return af_is_ipv4_literal(literal_buf) || af_is_ipv6_literal(literal_buf); + } + + return af_is_ipv4_literal(start) || af_is_ipv6_literal(start); +} + +static int af_get_flow_host(flow_info_t *flow, char *host_buf, int host_buf_len, int *host_len) +{ + int copy_len = 0; + + if (!flow || !host_buf || host_buf_len <= 1 || !host_len) { + return -1; + } + + if (flow->https.match == AF_TRUE && flow->https.url_pos && flow->https.url_len > 0) { + copy_len = flow->https.url_len >= (host_buf_len - 1) ? (host_buf_len - 1) : flow->https.url_len; + strncpy(host_buf, flow->https.url_pos, copy_len); + } else if (flow->http.match == AF_TRUE && flow->http.host_pos && flow->http.host_len > 0) { + copy_len = flow->http.host_len >= (host_buf_len - 1) ? (host_buf_len - 1) : flow->http.host_len; + strncpy(host_buf, flow->http.host_pos, copy_len); + } else { + return -1; + } + + host_buf[copy_len] = '\0'; + if (af_is_ip_literal_host(host_buf)) { + return -1; + } + *host_len = copy_len; + return 0; +} + +static int af_copy_dns_domain(flow_info_t *flow, int index, char *host_buf, int host_buf_len, int *host_len) +{ + int copy_len = 0; + + if (!flow || !host_buf || host_buf_len <= 1) { + return -1; + } + if (flow->dns.match != AF_TRUE || index < 0 || index >= flow->dns.query_num || index >= MAX_DNS_QUERY_NUM) { + return -1; + } + if (flow->dns.domain[index][0] == '\0') { + return -1; + } + + copy_len = strlen(flow->dns.domain[index]); + copy_len = copy_len >= (host_buf_len - 1) ? (host_buf_len - 1) : copy_len; + strncpy(host_buf, flow->dns.domain[index], copy_len); + host_buf[copy_len] = '\0'; + if (host_len) { + *host_len = copy_len; + } + return 0; +} + +static int af_match_basic_cond(flow_info_t *flow, af_feature_node_t *node) +{ + if (!flow || !node) { + return AF_FALSE; + } + + if (node->proto > 0 && flow->l4_protocol != node->proto) { + return AF_FALSE; + } + if (node->sport != 0 && flow->sport != node->sport) { + return AF_FALSE; + } + if (!af_match_port(&node->dport_info, flow->dport)) { + return AF_FALSE; + } + return AF_TRUE; +} + +static int af_is_regex_host_pattern(const char *host_url) +{ + if (!host_url || host_url[0] == '\0') { + return 0; + } + + if (host_url[0] != '^') { + return 0; + } + if (!strchr(host_url, '*')) { + return 0; + } + if (!strchr(host_url, '$')) { + return 0; + } + return 1; +} + +static af_ac_node_t *af_ac_alloc_node(void) +{ + af_ac_node_t *node = kzalloc(sizeof(*node), GFP_ATOMIC); + if (!node) { + return NULL; + } + INIT_HLIST_HEAD(&node->edges); + INIT_LIST_HEAD(&node->output_list); + INIT_LIST_HEAD(&node->all_list); + node->fail = NULL; + list_add_tail(&node->all_list, &g_url_ac.node_list); + g_url_ac.node_count++; + return node; +} + +static af_ac_node_t *af_ac_find_next(af_ac_node_t *node, u8 ch) +{ + af_ac_edge_t *edge = NULL; + if (!node) { + return NULL; + } + hlist_for_each_entry(edge, &node->edges, hnode) { + if (edge->ch == ch) { + return edge->next; + } + } + return NULL; +} + +static af_ac_node_t *af_ac_get_or_create_next(af_ac_node_t *node, u8 ch) +{ + af_ac_edge_t *edge = NULL; + af_ac_node_t *child = af_ac_find_next(node, ch); + if (child) { + return child; + } + + child = af_ac_alloc_node(); + if (!child) { + return NULL; + } + edge = kzalloc(sizeof(*edge), GFP_ATOMIC); + if (!edge) { + list_del(&child->all_list); + kfree(child); + g_url_ac.node_count--; + return NULL; + } + edge->ch = ch; + edge->next = child; + hlist_add_head(&edge->hnode, &node->edges); + g_url_ac.edge_count++; + return child; +} + +static int af_ac_add_output(af_ac_node_t *node, af_feature_node_t *feature) +{ + af_ac_output_item_t *item = NULL; + if (!node || !feature) { + return -1; + } + + item = kzalloc(sizeof(*item), GFP_ATOMIC); + if (!item) { + return -1; + } + item->feature = feature; + INIT_LIST_HEAD(&item->list); + list_add_tail(&item->list, &node->output_list); + g_url_ac.output_count++; + return 0; +} + +static void af_ac_reset_locked(void) +{ + af_ac_node_t *node = NULL; + af_ac_node_t *tmp_node = NULL; + + list_for_each_entry_safe(node, tmp_node, &g_url_ac.node_list, all_list) { + af_ac_edge_t *edge = NULL; + struct hlist_node *edge_tmp = NULL; + af_ac_output_item_t *item = NULL; + af_ac_output_item_t *tmp_item = NULL; + + hlist_for_each_entry_safe(edge, edge_tmp, &node->edges, hnode) { + hlist_del(&edge->hnode); + kfree(edge); + } + list_for_each_entry_safe(item, tmp_item, &node->output_list, list) { + list_del(&item->list); + kfree(item); + } + list_del(&node->all_list); + kfree(node); + } + + g_url_ac.root = NULL; + g_url_ac.node_count = 0; + g_url_ac.edge_count = 0; + g_url_ac.output_count = 0; + g_url_ac.ready = 0; +} + +static int af_ac_insert_pattern(af_feature_node_t *feature) +{ + int i = 0; + int len = 0; + af_ac_node_t *cur = g_url_ac.root; + + if (!feature || !cur) { + return -1; + } + len = strlen(feature->host_url); + if (len <= 0) { + return 0; + } + + for (i = 0; i < len; i++) { + cur = af_ac_get_or_create_next(cur, (u8)feature->host_url[i]); + if (!cur) { + return -1; + } + } + + return af_ac_add_output(cur, feature); +} + +static int af_ac_build_fail_links_locked(void) +{ + u32 head = 0; + u32 tail = 0; + af_ac_node_t **queue = NULL; + af_ac_edge_t *edge = NULL; + + if (!g_url_ac.root || g_url_ac.node_count == 0) { + return 0; + } + + queue = kzalloc(sizeof(*queue) * g_url_ac.node_count, GFP_ATOMIC); + if (!queue) { + return -1; + } + + g_url_ac.root->fail = g_url_ac.root; + hlist_for_each_entry(edge, &g_url_ac.root->edges, hnode) { + edge->next->fail = g_url_ac.root; + queue[tail++] = edge->next; + } + + while (head < tail) { + af_ac_node_t *cur = queue[head++]; + hlist_for_each_entry(edge, &cur->edges, hnode) { + af_ac_node_t *fail = cur->fail; + af_ac_node_t *fallback = NULL; + + while (fail != g_url_ac.root) { + fallback = af_ac_find_next(fail, edge->ch); + if (fallback) { + break; + } + fail = fail->fail; + } + if (!fallback) { + fallback = af_ac_find_next(g_url_ac.root, edge->ch); + } + edge->next->fail = fallback ? fallback : g_url_ac.root; + queue[tail++] = edge->next; + } + } + + kfree(queue); + return 0; +} + +static int af_rebuild_url_ac_locked(void) +{ + af_feature_node_t *node = NULL; + + af_ac_reset_locked(); + INIT_LIST_HEAD(&g_url_ac.node_list); + g_url_ac.root = af_ac_alloc_node(); + if (!g_url_ac.root) { + g_url_ac.ready = 0; + g_url_ac_dirty = 1; + return -1; + } + + list_for_each_entry(node, &af_feature_head, head) { + if (strlen(node->host_url) == 0) { + continue; + } + if (af_is_regex_host_pattern(node->host_url)) { + continue; + } + if (af_ac_insert_pattern(node) < 0) { + AF_ERROR("insert ac pattern failed, host_url=%s\n", node->host_url); + } + } + + if (af_ac_build_fail_links_locked() < 0) { + AF_ERROR("build ac fail links failed\n"); + g_url_ac.ready = 0; + g_url_ac_dirty = 1; + return -1; + } + + g_url_ac.ready = 1; + g_url_ac_dirty = 0; + AF_INFO("ac rebuild done, nodes=%u, edges=%u, outputs=%u\n", + g_url_ac.node_count, g_url_ac.edge_count, g_url_ac.output_count); + return 0; +} + +static int af_rebuild_url_ac(void) +{ + int ret = 0; + feature_list_write_lock(); + ret = af_rebuild_url_ac_locked(); + feature_list_write_unlock(); + return ret; +} + +static af_feature_node_t *af_match_url_text_ac(flow_info_t *flow, af_ac_match_stat_t *stat, + char *host_buf, int host_len, int check_basic_cond) +{ + int i = 0; + af_ac_node_t *state = NULL; + + if (!flow || !stat || !host_buf || host_len <= 0 || !g_url_ac.ready || !g_url_ac.root) { + return NULL; + } + + state = g_url_ac.root; + for (i = 0; i < host_len; i++) { + u8 ch = (u8)host_buf[i]; + af_ac_node_t *next = af_ac_find_next(state, ch); + af_ac_node_t *iter = NULL; + + stat->ac_state_steps++; + while (!next && state != g_url_ac.root) { + state = state->fail; + stat->ac_fail_jumps++; + next = af_ac_find_next(state, ch); + } + state = next ? next : g_url_ac.root; + + iter = state; + while (iter && iter != g_url_ac.root) { + af_ac_output_item_t *item = NULL; + list_for_each_entry(item, &iter->output_list, list) { + af_feature_node_t *feature = item->feature; + stat->ac_candidate_checks++; + if (!feature) { + continue; + } + if (check_basic_cond && !af_match_basic_cond(flow, feature)) { + continue; + } + stat->ac_match_count++; + return feature; + } + iter = iter->fail; + } + } + return NULL; +} + +static af_feature_node_t *af_match_url_feature_ac(flow_info_t *flow, af_ac_match_stat_t *stat) +{ + af_feature_node_t *node = NULL; + char host_buf[MAX_URL_MATCH_LEN] = {0}; + int host_len = 0; + int i = 0; + + if (!flow || !stat || !g_url_ac.ready || !g_url_ac.root) { + return NULL; + } + if (af_get_flow_host(flow, host_buf, sizeof(host_buf), &host_len) == 0 && host_len > 0) { + node = af_match_url_text_ac(flow, stat, host_buf, host_len, 1); + if (node) { + return node; + } + } + + if (flow->dns.match != AF_TRUE) { + return NULL; + } + for (i = 0; i < flow->dns.query_num && i < MAX_DNS_QUERY_NUM; i++) { + host_buf[0] = 0x0; + host_len = 0; + if (af_copy_dns_domain(flow, i, host_buf, sizeof(host_buf), &host_len) < 0 || host_len <= 0) { + continue; + } + node = af_match_url_text_ac(flow, stat, host_buf, host_len, 0); + if (node) { + flow->match_by_dns = 1; + AF_LMT_INFO("match dns domain:%s, appid=%d\n", host_buf, node->app_id); + return node; + } + } + return NULL; +} + +static af_feature_node_t *af_match_dns_regex_feature(flow_info_t *flow, af_ac_match_stat_t *stat) +{ + af_feature_node_t *n = NULL; + af_feature_node_t *node = NULL; + char domain_buf[MAX_URL_MATCH_LEN] = {0}; + int i = 0; + + if (!flow || flow->dns.match != AF_TRUE || list_empty(&af_feature_head)) { + return NULL; + } + + for (i = 0; i < flow->dns.query_num && i < MAX_DNS_QUERY_NUM; i++) { + domain_buf[0] = 0x0; + if (af_copy_dns_domain(flow, i, domain_buf, sizeof(domain_buf), NULL) < 0 || domain_buf[0] == '\0') { + continue; + } + list_for_each_entry_safe(node, n, &af_feature_head, head) { + if (strlen(node->host_url) == 0 || !af_is_regex_host_pattern(node->host_url)) { + continue; + } + if (stat) { + stat->fallback_checks++; + } + if (regexp_match(node->host_url, domain_buf)) { + flow->match_by_dns = 1; + AF_LMT_INFO("match dns domain:%s reg = %s, appid=%d\n", + domain_buf, node->host_url, node->app_id); + return node; + } + } + } + return NULL; +} + +static af_feature_node_t *af_match_non_url_feature(flow_info_t *flow, af_ac_match_stat_t *stat) +{ + af_feature_node_t *n = NULL; + af_feature_node_t *node = NULL; + + if (list_empty(&af_feature_head)) { + return NULL; + } + list_for_each_entry_safe(node, n, &af_feature_head, head) { + if (strlen(node->host_url) > 0 && + !af_is_regex_host_pattern(node->host_url) && + strlen(node->request_url) == 0) { + continue; + } + if (stat) { + stat->fallback_checks++; + } + if (af_match_one(flow, node)) { + return node; + } + } + return NULL; +} + + +int __add_app_feature(char *feature, int appid, char *name, int proto, int src_port, + port_info_t dport_info, char *host_url, char *request_url, char *dict, char *search_str, int ignore) +{ + af_feature_node_t *node = NULL; + char *p = dict; + char *begin = dict; + char pos[64] = {0}; + int index = 0; + int value = 0; + node = kzalloc(sizeof(af_feature_node_t), GFP_ATOMIC); + if (node == NULL) + { + printk("malloc feature memory error\n"); + return -1; + } + else + { + node->app_id = appid; + strncpy(node->app_name, name, sizeof(node->app_name) - 1); + node->proto = proto; + node->dport_info = dport_info; + node->sport = src_port; + strcpy(node->host_url, host_url); + strcpy(node->request_url, request_url); + strcpy(node->search_str, search_str); + node->ignore = ignore; + strcpy(node->feature, feature); + if (ignore) + AF_DEBUG("add feature %s, ignore = %d\n", feature, ignore); + + p = dict; + begin = dict; + index = 0; + value = 0; + while (*p++) + { + if (*p == '|') + { + memset(pos, 0x0, sizeof(pos)); + strncpy(pos, begin, p - begin); + k_sscanf(pos, "%d:%x", &index, &value); + begin = p + 1; + node->pos_info[node->pos_num].pos = index; + node->pos_info[node->pos_num].value = value; + node->pos_num++; + if (node->pos_num >= MAX_POS_INFO_PER_FEATURE - 1) + break; + } + } + + if (begin != dict) + strncpy(pos, begin, p - begin); + else + strcpy(pos, dict); + + int ret = k_sscanf(pos, "%d:%x", &index, &value); + if (ret == 2){ + node->pos_info[node->pos_num].pos = index; + node->pos_info[node->pos_num].value = value; + node->pos_num++; + } + + feature_list_write_lock(); + list_add(&(node->head), &af_feature_head); + if (strlen(node->host_url) > 0 && !af_is_regex_host_pattern(node->host_url)) { + g_url_ac_dirty = 1; + } + feature_list_write_unlock(); + } + return 0; +} +int validate_range_value(char *range_str) +{ + if (!range_str) + return 0; + char *p = range_str; + while (*p) + { + if (*p == ' ' || *p == '!' || *p == '-' || + ((*p >= '0') && (*p <= '9'))) + { + p++; + continue; + } + else + { + return 0; + } + } + return 1; +} + +int parse_range_value(char *range_str, range_value_t *range) +{ + char pure_range[128] = {0}; + if (!validate_range_value(range_str)) + { + printk("validate range str failed, value = %s\n", range_str); + return -1; + } + k_trim(range_str); + if (range_str[0] == '!') + { + range->not = 1; + strcpy(pure_range, range_str + 1); + } + else + { + range->not = 0; + strcpy(pure_range, range_str); + } + k_trim(pure_range); + int start, end; + if (strstr(pure_range, "-")) + { + if (2 != sscanf(pure_range, "%d-%d", &start, &end)) + return -1; + } + else + { + if (1 != sscanf(pure_range, "%d", &start)) + return -1; + end = start; + } + range->start = start; + range->end = end; + return 0; +} + +int parse_port_info(char *port_str, port_info_t *info) +{ + char *p = port_str; + char *begin = port_str; + int param_num = 0; + char one_port_buf[128] = {0}; + k_trim(port_str); + if (strlen(port_str) == 0) + return -1; + + while (*p++) + { + if (*p != '|') + continue; + memset(one_port_buf, 0x0, sizeof(one_port_buf)); + strncpy(one_port_buf, begin, p - begin); + if (0 == parse_range_value(one_port_buf, &info->range_list[info->num])) + { + info->num++; + } + param_num++; + begin = p + 1; + } + memset(one_port_buf, 0x0, sizeof(one_port_buf)); + strncpy(one_port_buf, begin, p - begin); + if (0 == parse_range_value(one_port_buf, &info->range_list[info->num])) + { + info->num++; + } + return 0; +} + +int af_match_port(port_info_t *info, int port) +{ + int i; + int with_not = 0; + if (info->num == 0) + return 1; + for (i = 0; i < info->num; i++) + { + if (info->range_list[i].not ) + { + with_not = 1; + break; + } + } + for (i = 0; i < info->num; i++) + { + if (with_not) + { + if (info->range_list[i].not &&port >= info->range_list[i].start && port <= info->range_list[i].end) + { + return 0; + } + } + else + { + if (port >= info->range_list[i].start && port <= info->range_list[i].end) + { + return 1; + } + } + } + if (with_not) + return 1; + else + return 0; +} + +int add_app_feature(int appid, char *name, char *feature) +{ + char proto_str[16] = {0}; + char src_port_str[16] = {0}; + port_info_t dport_info; + char dst_port_str[16] = {0}; + char host_url[32] = {0}; + char request_url[128] = {0}; + char dict[128] = {0}; + int proto = IPPROTO_TCP; + int param_num = 0; + int dst_port = 0; + int src_port = 0; + char tmp_buf[128] = {0}; + int ignore = 0; + char search_str[128] = {0}; + char *p = feature; + char *begin = feature; + + if (!name || !feature) + { + AF_ERROR("error, name or feature is null\n"); + return -1; + } + + if (strlen(feature) < MIN_FEATURE_STR_LEN){ + return -1; + } + + memset(&dport_info, 0x0, sizeof(dport_info)); + while (*p++) + { + if (*p != ';') + continue; + + switch (param_num) + { + + case AF_PROTO_PARAM_INDEX: + strncpy(proto_str, begin, p - begin); + break; + case AF_SRC_PORT_PARAM_INDEX: + strncpy(src_port_str, begin, p - begin); + break; + case AF_DST_PORT_PARAM_INDEX: + strncpy(dst_port_str, begin, p - begin); + break; + + case AF_HOST_URL_PARAM_INDEX: + strncpy(host_url, begin, p - begin); + break; + + case AF_REQUEST_URL_PARAM_INDEX: + strncpy(request_url, begin, p - begin); + break; + case AF_DICT_PARAM_INDEX: + strncpy(dict, begin, p - begin); + break; + case AF_STR_PARAM_INDEX: + strncpy(search_str, begin, p - begin); + break; + case AF_IGNORE_PARAM_INDEX: + strncpy(tmp_buf, begin, p - begin); + ignore = k_atoi(tmp_buf); + break; + } + param_num++; + begin = p + 1; + } + + + + if (param_num == AF_DICT_PARAM_INDEX){ + strncpy(dict, begin, p - begin); + } + + if (param_num == AF_IGNORE_PARAM_INDEX){ + strncpy(tmp_buf, begin, p - begin); + ignore = k_atoi(tmp_buf); + } + + if (0 == strcmp(proto_str, "tcp")) + proto = IPPROTO_TCP; + else if (0 == strcmp(proto_str, "udp")) + proto = IPPROTO_UDP; + else + { + printk("proto %s is not support, feature = %s\n", proto_str, feature); + return -1; + } + sscanf(src_port_str, "%d", &src_port); + + parse_port_info(dst_port_str, &dport_info); + AF_DEBUG("host_url = %s, request = %s, dict = %s\n", host_url, request_url, dict); + + __add_app_feature(feature, appid, name, proto, src_port, dport_info, host_url, request_url, dict, search_str, ignore); + AF_DEBUG("id = %d name = %s, add feature %s, ignore = %d\n", appid, name, feature, ignore); + return 0; +} + +static char *af_trim_space(char *text) +{ + char *end; + + if (!text) + return NULL; + while (*text && isspace(*text)) + text++; + end = text + strlen(text); + while (end > text && isspace(end[-1])) + *--end = '\0'; + return text; +} + +static int af_parse_feature_app_header(char *feature_str, int *app_id, + char *app_name, size_t app_name_len) +{ + char header[128] = {0}; + char *slash; + char *id_text; + char *name_text; + char *colon; + char *end_text; + long id = 0; + size_t header_len; + + if (!feature_str || !app_id || !app_name || app_name_len == 0) + return -1; + colon = strchr(feature_str, ':'); + if (!colon) + return -1; + header_len = colon - feature_str; + if (header_len == 0 || header_len >= sizeof(header)) + return -1; + memcpy(header, feature_str, header_len); + header[header_len] = '\0'; + + slash = strchr(header, '~'); + if (!slash) + return -1; + *slash = '\0'; + id_text = af_trim_space(header); + name_text = af_trim_space(slash + 1); + if (!id_text || !id_text[0] || !name_text || !name_text[0]) + return -1; + if (kstrtol(id_text, 10, &id) < 0 || id <= 0) + return -1; + end_text = id_text; + while (*end_text && !isspace(*end_text)) + end_text++; + end_text = af_trim_space(end_text); + if (end_text && end_text[0] != '\0') + return -1; + + *app_id = (int)id; + strncpy(app_name, name_text, app_name_len - 1); + app_name[app_name_len - 1] = '\0'; + return 0; +} + +void af_init_feature(char *feature_str) +{ + int app_id = 0; + char app_name[128] = {0}; + char *feature_buf = NULL; + char feature[MAX_FEATURE_STR_LEN] = {0}; + char *p = feature_str; + char *pos = NULL; + int len = 0; + char *begin = NULL; + + feature_buf = kmalloc(MAX_FEATURE_LINE_LEN, GFP_KERNEL); + if (!feature_buf) { + AF_ERROR("Failed to allocate memory for feature_buf\n"); + return; + } + memset(feature_buf, 0, MAX_FEATURE_LINE_LEN); + + if (strstr(feature_str, "#")) + goto out; + + if (af_parse_feature_app_header(feature_str, &app_id, app_name, sizeof(app_name)) < 0) + goto out; + while (*p++) + { + if (*p == '[') + { + pos = p + 1; + continue; + } + if (*p == ']' && pos != NULL) + { + len = p - pos; + } + } + + if (pos && len) + strncpy(feature_buf, pos, len); + p = feature_buf; + begin = feature_buf; + + while (*p++) + { + if (*p == ',') + { + if (p - begin > MAX_FEATURE_STR_LEN){ + printk("error, feature len error %d\n", p - len); + break; + } + memcpy((char *)feature, begin, p - begin); + feature[p - begin] = '\0'; + add_app_feature(app_id, app_name, feature); + begin = p + 1; + } + } + if (p != begin) + { + + if (p - begin > MAX_FEATURE_STR_LEN){ + printk("error, feature len error %d\n", p - len); + } + else{ + memcpy((char *)feature, begin, p - begin); + feature[p - begin] = '\0'; + add_app_feature(app_id, app_name, feature); + } + } + g_feature_count++; + +out: + if (feature_buf) + kfree(feature_buf); +} + +void load_feature_buf_from_file(char **config_buf) +{ + struct inode *inode = NULL; + struct file *fp = NULL; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 7, 19) + mm_segment_t fs; +#endif + off_t size; + fp = filp_open(AF_FEATURE_CONFIG_FILE, O_RDONLY, 0); + + + if (IS_ERR(fp)) + { + return; + } + + inode = fp->f_inode; + size = inode->i_size; + if (size == 0) + { + return; + } + *config_buf = (char *)kzalloc(sizeof(char) * size, GFP_ATOMIC); + if (NULL == *config_buf) + { + AF_ERROR("alloc buf fail\n"); + filp_close(fp, NULL); + return; + } + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 7, 19) + fs = get_fs(); + set_fs(KERNEL_DS); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + kernel_read(fp, *config_buf, size, &(fp->f_pos)); +#else + vfs_read(fp, *config_buf, size, &(fp->f_pos)); +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 7, 19) + set_fs(fs); +#endif + filp_close(fp, NULL); +} + +static void af_clean_feature_list(void) +{ + af_feature_node_t *node; + feature_list_write_lock(); + while (!list_empty(&af_feature_head)) + { + node = list_first_entry(&af_feature_head, af_feature_node_t, head); + list_del(&(node->head)); + kfree(node); + } + af_ac_reset_locked(); + INIT_LIST_HEAD(&g_url_ac.node_list); + g_url_ac_dirty = 1; + g_feature_count = 0; // 清零特征码计数 + feature_list_write_unlock(); +} + +void af_add_feature_msg_handle(char *data, int len) +{ + char feature[MAX_FEATURE_LINE_LEN] = {0}; + if (len <= 0 || len >= MAX_FEATURE_LINE_LEN){ + printk("warn, feature data len = %d\n", len); + return; + } + strncpy(feature, data, len); + AF_INFO("add feature %s\n", feature); + af_init_feature(feature); +} + +void af_feature_load_done_msg_handle(void) +{ + if (g_url_ac_dirty) { + af_rebuild_url_ac(); + } +} + +static unsigned char *read_skb(struct sk_buff *skb, unsigned int from, unsigned int len) +{ + struct skb_seq_state state; + unsigned char *msg_buf = NULL; + unsigned int consumed = 0; + + msg_buf = kmalloc(len, GFP_KERNEL); + if (!msg_buf) + return NULL; + + skb_prepare_seq_read(skb, from, from + len, &state); + while (1) + { + unsigned int avail; + const u8 *ptr; + avail = skb_seq_read(consumed, &ptr, &state); + if (avail == 0) + { + break; + } + memcpy(msg_buf + consumed, ptr, avail); + consumed += avail; + if (consumed >= len) + { + skb_abort_seq_read(&state); + break; + } + } + return msg_buf; +} + +int parse_flow_proto(struct sk_buff *skb, flow_info_t *flow) +{ + unsigned char *ipp; + int ipp_len; + struct tcphdr *tcph = NULL; + struct udphdr *udph = NULL; + struct nf_conn *ct = NULL; + struct iphdr *iph = NULL; + struct ipv6hdr *ip6h = NULL; + if (!skb) + return -1; + switch (skb->protocol) + { + case htons(ETH_P_IP): + iph = ip_hdr(skb); + flow->src = iph->saddr; + flow->dst = iph->daddr; + flow->l4_protocol = iph->protocol; + ipp = ((unsigned char *)iph) + iph->ihl * 4; + ipp_len = ((unsigned char *)iph) + ntohs(iph->tot_len) - ipp; + break; + case htons(ETH_P_IPV6): + ip6h = ipv6_hdr(skb); + flow->src6 = &ip6h->saddr; + flow->dst6 = &ip6h->daddr; + flow->l4_protocol = ip6h->nexthdr; + ipp = ((unsigned char *)ip6h) + sizeof(struct ipv6hdr); + ipp_len = ntohs(ip6h->payload_len); + break; + default: + return -1; + } + + switch (flow->l4_protocol) + { + case IPPROTO_TCP: + tcph = (struct tcphdr *)ipp; + flow->l4_len = ipp_len - tcph->doff * 4; + flow->l4_data = ipp + tcph->doff * 4; + flow->dport = ntohs(tcph->dest); + flow->sport = ntohs(tcph->source); + return 0; + case IPPROTO_UDP: + udph = (struct udphdr *)ipp; + flow->l4_len = ntohs(udph->len) - 8; + flow->l4_data = ipp + 8; + flow->dport = ntohs(udph->dest); + flow->sport = ntohs(udph->source); + return 0; + case IPPROTO_ICMP: + break; + default: + return -1; + } + return -1; +} + +int check_domain(char *h, int len) +{ + int i; + for (i = 0; i < len; i++) + { + if ((h[i] >= 'a' && h[i] <= 'z') || (h[i] >= 'A' && h[i] <= 'Z') || + (h[i] >= '0' && h[i] <= '9') || h[i] == '.' || h[i] == '-' || h[i] == ':') + { + continue; + } + else + return 0; + } + return 1; +} + +int dpi_https_proto(flow_info_t *flow) +{ + int i; + short url_len = 0; + char *p = flow->l4_data; + int data_len = flow->l4_len; + + if (NULL == flow) + { + AF_ERROR("flow is NULL\n"); + return -1; + } + if (NULL == p || data_len < 16) + { + return -1; + } + if (!((p[0] == 0x16 && p[1] == 0x03 && p[5] == 0x01) || flow->client_hello)) + return -1; + + + + for (i = 0; i < data_len; i++) + { + if (i + HTTPS_URL_OFFSET >= data_len) + { + AF_LMT_DEBUG("match https host failed, data_len = %d, sport:%d, dport:%d\n", data_len, flow->sport,flow->dport); + if ((TEST_MODE())){ + print_hex_ascii(flow->l4_data, flow->l4_len); + } + flow->client_hello = 1; + return -1; + } + + if (p[i] == 0x0 && p[i + 1] == 0x0 && p[i + 2] == 0x0 && p[i + 3] != 0x0) + { + + memcpy(&url_len, p + i + HTTPS_LEN_OFFSET, 2); + + if (ntohs(url_len) <= MIN_HOST_LEN || ntohs(url_len) > data_len || ntohs(url_len) > MAX_HOST_LEN) + { + continue; + } + + if (i + HTTPS_URL_OFFSET + ntohs(url_len) < data_len) + { + if (!check_domain( p + i + HTTPS_URL_OFFSET, ntohs(url_len))){ + AF_INFO("invalid url, len = %d\n", ntohs(url_len)); + continue; + } + + flow->https.match = AF_TRUE; + flow->https.url_pos = p + i + HTTPS_URL_OFFSET; + flow->https.url_len = ntohs(url_len); + flow->client_hello = 0; + return 0; + } + } + } + return -1; +} + +void dpi_http_proto(flow_info_t *flow) +{ + int i = 0; + int start = 0; + char *data = NULL; + int data_len = 0; + if (!flow) + { + AF_ERROR("flow is null\n"); + return; + } + if (flow->l4_protocol != IPPROTO_TCP) + { + return; + } + + data = flow->l4_data; + data_len = flow->l4_len; + if (data_len < MIN_HTTP_DATA_LEN) + { + return; + } + + for (i = 0; i < data_len; i++) + { + if (data[i] == 0x0d && data[i + 1] == 0x0a) + { + if (0 == memcmp(&data[start], "POST ", 5)) + { + flow->http.match = AF_TRUE; + flow->http.method = HTTP_METHOD_POST; + flow->http.url_pos = data + start + 5; + flow->http.url_len = i - start - 5; + } + else if (0 == memcmp(&data[start], "GET ", 4)) + { + flow->http.match = AF_TRUE; + flow->http.method = HTTP_METHOD_GET; + flow->http.url_pos = data + start + 4; + flow->http.url_len = i - start - 4; + } + else if (0 == memcmp(&data[start], "Host:", 5)) + { + flow->http.host_pos = data + start + 6; + flow->http.host_len = i - start - 6; + } + if (data[i + 2] == 0x0d && data[i + 3] == 0x0a) + { + flow->http.data_pos = data + i + 4; + flow->http.data_len = data_len - i - 4; + break; + } + + start = i + 2; + } + } +} + +static void dump_http_flow_info(http_proto_t *http) +{ + if (!http) + { + AF_ERROR("http ptr is NULL\n"); + return; + } + if (!http->match) + return; + if (http->method == HTTP_METHOD_GET) + { + printk("Http method: " HTTP_GET_METHOD_STR "\n"); + } + else if (http->method == HTTP_METHOD_POST) + { + printk("Http method: " HTTP_POST_METHOD_STR "\n"); + } + if (http->url_len > 0 && http->url_pos) + { + dump_str("Request url", http->url_pos, http->url_len); + } + + if (http->host_len > 0 && http->host_pos) + { + dump_str("Host", http->host_pos, http->host_len); + } + + printk("--------------------------------------------------------\n\n\n"); +} + +static void dump_https_flow_info(https_proto_t *https) +{ + if (!https) + { + AF_ERROR("https ptr is NULL\n"); + return; + } + if (!https->match) + return; + + if (https->url_len > 0 && https->url_pos) + { + dump_str("https server name", https->url_pos, https->url_len); + } + + printk("--------------------------------------------------------\n\n\n"); +} +static void dump_flow_info(flow_info_t *flow) +{ + if (!flow) + { + AF_ERROR("flow is null\n"); + return; + } + if (flow->l4_len > 0) + { + AF_LMT_INFO("src=" NIPQUAD_FMT ",dst=" NIPQUAD_FMT ",sport: %d, dport: %d, data_len: %d\n", + NIPQUAD(flow->src), NIPQUAD(flow->dst), flow->sport, flow->dport, flow->l4_len); + } + + if (flow->l4_protocol == IPPROTO_TCP) + { + if (AF_TRUE == flow->http.match) + { + printk("-------------------http protocol-------------------------\n"); + printk("protocol:TCP , sport: %-8d, dport: %-8d, data_len: %-8d\n", + flow->sport, flow->dport, flow->l4_len); + dump_http_flow_info(&flow->http); + } + if (AF_TRUE == flow->https.match) + { + printk("-------------------https protocol-------------------------\n"); + dump_https_flow_info(&flow->https); + } + } +} + + +char *k_memstr(char *data, char *str, int size) +{ + char *p; + char len = strlen(str); + for (p = data; p <= (data - len + size); p++) + { + if (memcmp(p, str, len) == 0) + return p; + } + return NULL; +} + +int af_match_by_pos(flow_info_t *flow, af_feature_node_t *node) +{ + int i; + unsigned int pos = 0; + + if (!flow || !node) + return AF_FALSE; + if (node->pos_num > 0) + { + + for (i = 0; i < node->pos_num && i < MAX_POS_INFO_PER_FEATURE; i++) + { + + if (node->pos_info[i].pos < 0) + { + pos = flow->l4_len + node->pos_info[i].pos; + } + else + { + pos = node->pos_info[i].pos; + } + if (pos >= flow->l4_len) + { + return AF_FALSE; + } + if (flow->l4_data[pos] != node->pos_info[i].value) + { + return AF_FALSE; + } + else{ + AF_DEBUG("match pos[%d] = %x\n", pos, node->pos_info[i].value); + } + } + if (strlen(node->search_str) > 0){ + if (k_memstr(flow->l4_data, node->search_str, flow->l4_len)){ + AF_DEBUG("match by search str, appid=%d, search_str=%s\n", node->app_id, node->search_str); + return AF_TRUE; + } + else{ + return AF_FALSE; + } + } + return AF_TRUE; + } + return AF_FALSE; +} + +int af_match_by_url(flow_info_t *flow, af_feature_node_t *node) +{ + char reg_url_buf[MAX_URL_MATCH_LEN] = {0}; + int i = 0; + int host_len = 0; + + if (!flow || !node) + return AF_FALSE; + + + if (af_get_flow_host(flow, reg_url_buf, sizeof(reg_url_buf), &host_len) < 0) + reg_url_buf[0] = '\0'; + if (strlen(reg_url_buf) > 0 && strlen(node->host_url) > 0 && regexp_match(node->host_url, reg_url_buf)) + { + AF_DEBUG("match url:%s reg = %s, appid=%d\n", + reg_url_buf, node->host_url, node->app_id); + return AF_TRUE; + } + + if (flow->dns.match == AF_TRUE && strlen(node->host_url) > 0) + { + for (i = 0; i < flow->dns.query_num && i < MAX_DNS_QUERY_NUM; i++) + { + memset(reg_url_buf, 0x0, sizeof(reg_url_buf)); + if (af_copy_dns_domain(flow, i, reg_url_buf, sizeof(reg_url_buf), NULL) < 0) + continue; + if (strlen(reg_url_buf) > 0 && regexp_match(node->host_url, reg_url_buf)) + { + flow->match_by_dns = 1; + AF_DEBUG("match dns domain:%s reg = %s, appid=%d\n", + reg_url_buf, node->host_url, node->app_id); + return AF_TRUE; + } + } + } + + + if (flow->http.match == AF_TRUE && flow->http.url_pos) + { + memset(reg_url_buf, 0x0, sizeof(reg_url_buf)); + if (flow->http.url_len >= MAX_URL_MATCH_LEN) + strncpy(reg_url_buf, flow->http.url_pos, MAX_URL_MATCH_LEN - 1); + else + strncpy(reg_url_buf, flow->http.url_pos, flow->http.url_len); + if (strlen(reg_url_buf) > 0 && strlen(node->request_url) && regexp_match(node->request_url, reg_url_buf)) + { + AF_DEBUG("match request:%s reg:%s appid=%d\n", + reg_url_buf, node->request_url, node->app_id); + return AF_TRUE; + } + } + return AF_FALSE; +} + +int af_match_one(flow_info_t *flow, af_feature_node_t *node) +{ + int ret = AF_FALSE; + if (!flow || !node) + { + AF_ERROR("node or flow is NULL\n"); + return AF_FALSE; + } + if (!af_match_basic_cond(flow, node)) { + return AF_FALSE; + } + + if (strlen(node->request_url) > 0 || + strlen(node->host_url) > 0) + { + ret = af_match_by_url(flow, node); + } + else if (node->pos_num > 0) + { + + ret = af_match_by_pos(flow, node); + } + else + { + AF_DEBUG("node is empty, match sport:%d,dport:%d, appid = %d\n", + flow->sport, flow->dport, node->app_id); + return AF_TRUE; + } + + return ret; +} + + +int is_quic_flow(flow_info_t *flow) +{ + unsigned char *data; + unsigned char first_byte; + unsigned int version; + + if (flow->l4_protocol != IPPROTO_UDP) { + return AF_FALSE; + } + + if (!flow->l4_data || flow->l4_len < 8) { + return AF_FALSE; + } + + data = flow->l4_data; + first_byte = data[0]; + + if (first_byte & 0x80) { + if (flow->l4_len >= 5) { + version = (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4]; + + if (version == 0x00000001 || + version == 0x00000000 || + version == 0x6b3343cf || + (version >= 0xff000000 && version <= 0xffffffff)) { + AF_LMT_DEBUG("match quic, version = %x\n", version); + return AF_TRUE; + } + } + if (flow->dport == 443) { + return AF_TRUE; + } + return AF_FALSE; + } + return AF_FALSE; +} + + +int fwx_match_feature(flow_info_t *flow) +{ + af_feature_node_t *node = NULL; + af_ac_match_stat_t stat; + + memset(&stat, 0x0, sizeof(stat)); + flow->match_by_dns = 0; + feature_list_read_lock(); + + if (is_quic_flow(flow)) { + + flow->app_id = FWX_QUIC_PROTO; + strcpy(flow->app_name, "QUIC"); + feature_list_read_unlock(); + return AF_TRUE; + } + + node = af_match_url_feature_ac(flow, &stat); + if (node) { + AF_LMT_INFO("ac match feature, appid=%d, feature=%s, ac_state_steps=%u, ac_fail_jumps=%u, ac_candidate_checks=%u, ac_match_count=%u\n", + node->app_id, node->feature, stat.ac_state_steps, stat.ac_fail_jumps, stat.ac_candidate_checks, stat.ac_match_count); + + + + flow->app_id = node->app_id; + flow->feature = node; + strncpy(flow->app_name, node->app_name, sizeof(flow->app_name) - 1); + feature_list_read_unlock(); + return AF_TRUE; + } + + node = af_match_dns_regex_feature(flow, &stat); + if (node) { + AF_LMT_INFO("match dns regex feature, appid=%d, feature = %s\n", node->app_id, node->feature); + flow->app_id = node->app_id; + flow->feature = node; + strncpy(flow->app_name, node->app_name, sizeof(flow->app_name) - 1); + feature_list_read_unlock(); + return AF_TRUE; + } + + node = af_match_non_url_feature(flow, &stat); + if (node) { + AF_LMT_DEBUG("match feature, appid=%d, feature = %s\n", node->app_id, node->feature); + flow->app_id = node->app_id; + flow->feature = node; + strncpy(flow->app_name, node->app_name, sizeof(flow->app_name) - 1); + feature_list_read_unlock(); + return AF_TRUE; + } + + if (flow->https.match || flow->http.match) { + AF_LMT_INFO("feature miss, ac_state_steps=%u, ac_fail_jumps=%u, ac_candidate_checks=%u, fallback_checks=%u\n", + stat.ac_state_steps, stat.ac_fail_jumps, stat.ac_candidate_checks, stat.fallback_checks); + } + feature_list_read_unlock(); + return AF_FALSE; +} + + +static int af_parse_dns_query_name(unsigned char *dns_payload, int dns_len, int query_offset, + char *domain, int domain_len, u_int16_t *qtype, int *next_query_offset) +{ + int offset; + int label_len; + int out_len = 0; + int i; + unsigned char c; + + if (!dns_payload || !domain || !qtype || !next_query_offset || domain_len <= 1 || query_offset < DNS_HEADER_LEN) + return -1; + if (dns_len <= DNS_HEADER_LEN || query_offset >= dns_len) + return -1; + + offset = query_offset; + while (offset < dns_len) + { + label_len = dns_payload[offset++]; + if (label_len == 0) + break; + if (label_len & 0xC0) + return -1; + if (label_len > 63 || offset + label_len > dns_len) + return -1; + + if (out_len > 0) + { + if (out_len >= domain_len - 1) + return -1; + domain[out_len++] = '.'; + } + + for (i = 0; i < label_len; i++) + { + c = dns_payload[offset + i]; + if (c >= 'A' && c <= 'Z') + c = c + ('a' - 'A'); + if (out_len >= domain_len - 1) + return -1; + domain[out_len++] = c; + } + offset += label_len; + } + + if (out_len <= 0) + return -1; + if (offset + 4 > dns_len) /* qtype + qclass */ + return -1; + + domain[out_len] = 0x0; + *qtype = (dns_payload[offset] << 8) | dns_payload[offset + 1]; + *next_query_offset = offset + 4; + return 0; +} + +int dpi_dns_proto(flow_info_t *flow) +{ + unsigned char *dns_payload; + int flags; + int qdcount; + int query_offset; + int i; + int dns_len; + int msg_len; + int parsed_num = 0; + + //printk("%s %d begin dpi dns sport = %d, dport = %d, proto = %d\n", __func__, __LINE__, + //flow->sport, flow->dport, flow->l4_protocol); + if (!flow || !flow->l4_data || flow->l4_len <= DNS_HEADER_LEN) + return -1; + + if ((flow->sport != DNS_PORT) && (flow->dport != DNS_PORT)) + return -1; + + + dns_payload = flow->l4_data; + dns_len = flow->l4_len; + + if (flow->l4_protocol == IPPROTO_TCP) + { + if (dns_len <= DNS_TCP_PREFIX_LEN + DNS_HEADER_LEN) + return -1; + + msg_len = (dns_payload[0] << 8) | dns_payload[1]; + dns_payload += DNS_TCP_PREFIX_LEN; + dns_len -= DNS_TCP_PREFIX_LEN; + if (msg_len <= 0 || msg_len > dns_len) + return -1; + dns_len = msg_len; + } + else if (flow->l4_protocol != IPPROTO_UDP) + { + return -1; + } + + + flags = (dns_payload[2] << 8) | dns_payload[3]; + qdcount = (dns_payload[4] << 8) | dns_payload[5]; + if ((flags & 0x8000) || qdcount <= 0) + return -1; + + flow->dns.qdcount = qdcount; + query_offset = DNS_HEADER_LEN; + + for (i = 0; i < qdcount && parsed_num < MAX_DNS_QUERY_NUM; i++) + { + if (0 != af_parse_dns_query_name(dns_payload, dns_len, query_offset, + flow->dns.domain[parsed_num], sizeof(flow->dns.domain[parsed_num]), + &flow->dns.qtype[parsed_num], &query_offset)) + { + break; + } + + AF_LMT_INFO("src=" NIPQUAD_FMT ",dst=" NIPQUAD_FMT ",sport: %d, dport: %d, data_len: %d\n", + NIPQUAD(flow->src), NIPQUAD(flow->dst), flow->sport, flow->dport, flow->l4_len); + + + AF_LMT_INFO("match dns domain[%d/%d]: %s, sport:%d, dport:%d\n", + parsed_num + 1, qdcount, flow->dns.domain[parsed_num], flow->sport, flow->dport); + parsed_num++; + } + + if (parsed_num > 0) + { + flow->dns.match = AF_TRUE; + flow->dns.query_num = parsed_num; + return 0; + } + + return -1; +} + + +int match_app_filter_rule(int appid, af_client_info_t *client) +{ + + if (!g_appfilter_enable) { + return AF_FALSE; + } + + if (fwx_match_app_filter_whitelist(client->mac)){ + AF_LMT_DEBUG("match appfilter whitelist mac = " MAC_FMT "\n", MAC_ARRAY(client->mac)); + return AF_FALSE; + } + + app_filter_rule_t *rule = fwx_match_app_filter_rule(appid, client->mac); + if (rule) { + AF_LMT_INFO("drop appid = %d, rule_id = %d\n", appid, rule->rule_id); + return AF_TRUE; + } + return AF_FALSE; +} + +int match_mac_filter_rule(af_client_info_t *client) +{ + + if (!g_mac_filter_enable) { + return AF_FALSE; + } + + if (fwx_match_mac_filter_whitelist(client->mac)){ + AF_LMT_DEBUG("match macfilter whitelist mac = " MAC_FMT "\n", MAC_ARRAY(client->mac)); + return AF_FALSE; + } + + mac_filter_rule_t *rule = fwx_match_mac_filter_rule(client->mac); + if (rule) { + AF_LMT_INFO("drop mac, rule_id = %d, mac = " MAC_FMT "\n", rule->rule_id, MAC_ARRAY(client->mac)); + return AF_TRUE; + } + return AF_FALSE; +} + +int af_update_client_app_info(af_client_info_t *node, int app_id, int drop, int from_conntrack, int is_http, int update_visiting) +{ + app_visit_info_t *info; + if (!node || app_id <= 0) + return -1; + + spin_lock_bh(&node->visit_info_lock); + + info = get_or_create_visit_info(node, app_id); + if (!info){ + spin_unlock_bh(&node->visit_info_lock); + return -1; + } + + info->total_num++; + if (drop) + info->drop_num++; + info->latest_time = af_get_timestamp_sec(); + info->latest_action = drop; + + + + if (!from_conntrack) { + info->conn_count++; + info->is_http = is_http; + } + + if (update_visiting && ((info->is_http && info->conn_count >= 3) || (!info->is_http))){ + node->visiting.app_time = af_get_timestamp_sec(); + node->visiting.visiting_app = app_id; + } + + spin_unlock_bh(&node->visit_info_lock); + return 0; +} + +int af_send_msg_to_user(char *pbuf, uint16_t len); +int af_match_bcast_packet(flow_info_t *f) +{ + if (!f) + return 0; + if (0 == f->src || 0 == f->dst || 0xffffffff == f->dst || 0 == f->dst) + return 1; + return 0; +} + +int af_match_local_packet(flow_info_t *f) +{ + if (!f) + return 0; + if (0x0100007f == f->src || 0x0100007f == f->dst) + { + return 1; + } + return 0; +} + +int update_url_visiting_info(af_client_info_t *client, flow_info_t *flow) +{ + char *host = NULL; + char host_buf[MAX_REPORT_URL_LEN] = {0}; + unsigned int len = 0; + if (!client || !flow || flow->match_by_dns) + return -1; + + if (flow->https.match){ + host = flow->https.url_pos; + + len = flow->https.url_len; + } + else if (flow->http.match){ + host = flow->http.host_pos; + len = flow->http.host_len; + } + if (!host || len < MIN_REPORT_URL_LEN || len >= MAX_REPORT_URL_LEN) + return -1; + + memcpy(host_buf, host, len); + host_buf[len] = 0x0; + if (af_is_ip_literal_host(host_buf)) + return -1; + + memcpy(client->visiting.visiting_url, host_buf, len); + client->visiting.visiting_url[len] = 0x0; + client->visiting.url_time = af_get_timestamp_sec(); + return 0; +} + + +int dpi_main(flow_info_t *flow) +{ + dpi_http_proto(flow); + dpi_https_proto(flow); + dpi_dns_proto(flow); + if (TEST_MODE()) + dump_flow_info(flow); + + + return 0; +} + +void af_get_smac(struct sk_buff *skb, u_int8_t *smac) +{ + struct ethhdr *ethhdr = NULL; + ethhdr = eth_hdr(skb); + if (ethhdr) + memcpy(smac, ethhdr->h_source, ETH_ALEN); + else + memcpy(smac, &skb->cb[40], ETH_ALEN); +} +int is_ipv4_broadcast(uint32_t ip) +{ + return (ip & 0x00FFFFFF) == 0x00FFFFFF; +} + +int is_ipv4_multicast(uint32_t ip) +{ + return (ip & 0xF0000000) == 0xE0000000; +} + +static int is_ipv4_private_lan(uint32_t ip) +{ + if ((ip & 0xFF000000) == 0x0A000000) + return 1; + if ((ip & 0xFFF00000) == 0xAC100000) + return 1; + if ((ip & 0xFFFF0000) == 0xC0A80000) + return 1; + return 0; +} + +static int af_is_ipv6_private_lan(const struct in6_addr *addr) +{ + if (!addr) + return 0; + + if (ipv6_addr_loopback(addr)) + return 1; + if ((addr->s6_addr[0] & 0xFE) == 0xFC) + return 1; + if (addr->s6_addr[0] == 0xFE && (addr->s6_addr[1] & 0xC0) == 0x80) + return 1; + + return 0; +} + +static int af_match_private_lan_dst(flow_info_t *f) +{ + if (!f) + return 0; + + if (f->dst && is_ipv4_private_lan(ntohl(f->dst))) + return 1; + if (f->dst6 && af_is_ipv6_private_lan(f->dst6)) + return 1; + + return 0; +} + +int af_check_bcast_ip(flow_info_t *f) +{ + + if (0 == f->src || 0 == f->dst) + return 1; + if (is_ipv4_broadcast(ntohl(f->src)) || is_ipv4_broadcast(ntohl(f->dst))) + { + return 1; + } + if (is_ipv4_multicast(ntohl(f->src)) || is_ipv4_multicast(ntohl(f->dst))) + { + return 1; + } + + return 0; +} +static int af_should_send_tcp_rst(struct sk_buff *skb, flow_info_t *flow) +{ + if (!g_tcp_rst || !skb || !flow || flow->l4_protocol != IPPROTO_TCP) + return 0; + + if (skb->protocol != htons(ETH_P_IP)) + return 0; + + return 1; +} + +static void af_send_tcp_reset(struct sk_buff *skb) +{ +#if LINUX_VERSION_CODE > KERNEL_VERSION(5,10,197) + nf_send_reset(&init_net, skb->sk, skb, NF_INET_PRE_ROUTING); +#elif LINUX_VERSION_CODE > KERNEL_VERSION(4,4,1) + + +#else + nf_send_reset(skb, NF_INET_PRE_ROUTING); +#endif +} + +void send_reset_packet(struct sk_buff *skb, flow_info_t *flow) +{ + if (af_should_send_tcp_rst(skb, flow)) + af_send_tcp_reset(skb); +} + + +u_int32_t check_app_action_changed(int action, u_int32_t app_id, af_client_info_t *client) +{ + u_int8_t drop = 0; + int changed = 0; + u_int32_t max_jiffies = 30 * HZ; + u_int32_t interval_jiffies = jiffies - g_appfilter_update_jiffies; + + if (interval_jiffies < max_jiffies){ + AF_LMT_DEBUG("config changed, update app action\n"); + if (match_app_filter_rule(app_id, client)){ + AF_LMT_DEBUG("match appid = %d, action = %d\n", app_id, action); + if (!action) // accept --> drop + changed = 1; + } + else{ + if (action) // drop --> accept + changed = 1; + } + } + return changed; +} + +u_int32_t fwx_hook_bypass_handle(struct sk_buff *skb, struct net_device *dev) +{ + flow_info_t flow; + af_conn_t *conn; + u_int8_t smac[ETH_ALEN]; + af_client_info_t *client = NULL; + u_int32_t ret = NF_ACCEPT; + u_int8_t malloc_data = 0; + int is_record_whitelist = 0; + + if (!skb || !dev) + return NF_ACCEPT; + if (0 == fwx_lan_ip || 0 == fwx_lan_mask) + return NF_ACCEPT; + if (strstr(dev->name, "docker")) + return NF_ACCEPT; + + memset((char *)&flow, 0x0, sizeof(flow_info_t)); + if (parse_flow_proto(skb, &flow) < 0) + return NF_ACCEPT; + if (af_match_private_lan_dst(&flow) && flow.dport != 53) + return NF_ACCEPT; + + if (flow.src || flow.dst) + { + if (fwx_lan_ip == flow.src || fwx_lan_ip == flow.dst) + { + return NF_ACCEPT; + } + if (af_check_bcast_ip(&flow) || af_match_local_packet(&flow)) + return NF_ACCEPT; + + if ((flow.src & fwx_lan_mask) != (fwx_lan_ip & fwx_lan_mask)) + { + return NF_ACCEPT; + } + } + else + { + return NF_ACCEPT; + } + af_get_smac(skb, smac); + + AF_CLIENT_LOCK_W(); + client = find_and_add_af_client(smac); + if (!client) + { + AF_CLIENT_UNLOCK_W(); + return NF_ACCEPT; + } + client->update_jiffies = jiffies; + if (flow.src) + client->ip = flow.src; + is_record_whitelist = client->record_whitelist; + AF_CLIENT_UNLOCK_W(); + + + spin_lock(&af_conn_lock); + conn = af_conn_find_and_add(flow.src, flow.dst, flow.sport, flow.dport, flow.l4_protocol); + if (!conn){ + return NF_ACCEPT; + } + + conn->last_jiffies = jiffies; + conn->total_pkts++; + spin_unlock(&af_conn_lock); + + + if (conn->app_id == 0 && conn->drop == 1){ + //send_reset_packet(skb, &flow); + return NF_DROP; + } + if (conn->app_id != 0) + { + flow.app_id = conn->app_id; + flow.drop = conn->drop; + if (flow.app_id > 1000){ + if (check_app_action_changed(flow.drop, flow.app_id, client)){ + flow.drop = !flow.drop; + AF_LMT_DEBUG("update appid %d action, new action = %s\n", flow.app_id, flow.drop ? "drop" : "accept"); + } + } + } + else{ + if (g_by_pass_accl) { + if (conn->total_pkts > 256) { + return NF_ACCEPT; + } + } + if (skb_is_nonlinear(skb) && flow.l4_len < MAX_AF_SUPPORT_DATA_LEN) + { + flow.l4_data = read_skb(skb, flow.l4_data - skb->data, flow.l4_len); + if (!flow.l4_data) + return NF_ACCEPT; + AF_LMT_DEBUG("##match nonlinear skb, len = %d\n", flow.l4_len); + malloc_data = 1; + } + flow.client_hello = conn->client_hello; + if (flow.client_hello > 0) + AF_LMT_DEBUG("client hello is %d\n", flow.client_hello); + + dpi_main(&flow); + conn->client_hello = flow.client_hello; + if (!is_record_whitelist) { + update_url_visiting_info(client, &flow); + af_update_active_host_list(client, &flow); + } + + if (fwx_match_feature(&flow)){ + conn->app_id = flow.app_id; + conn->drop = flow.drop; + if (flow.app_id < 1000){ + conn->ignore = 1; + } + else{ + if (flow.feature && flow.feature->ignore){ + AF_LMT_DEBUG("match ignore feature, feature = %s, appid = %d\n", flow.feature->feature ,flow.app_id); + conn->ignore = 1; + } + else{ + conn->ignore = 0; + } + } + conn->state = AF_CONN_DPI_FINISHED; + if (!conn->ignore && !is_record_whitelist) + af_update_active_app_list(client, &flow); + if (match_app_filter_rule(flow.app_id, client)) { + flow.drop = 1; + conn->drop = 1; + AF_LMT_INFO("##Drop App filter rule, appid = %d, mac = " MAC_FMT "\n", + flow.app_id, MAC_ARRAY(client->mac)); + send_reset_packet(skb, &flow); + } + } + + } + + + + if (!flow.drop && match_mac_filter_rule(client)) { + flow.drop = 1; + conn->drop = 1; + AF_LMT_INFO("##Drop MAC filter rule, mac = " MAC_FMT "\n", + MAC_ARRAY(client->mac)); + send_reset_packet(skb, &flow); + } + + if (g_record_enable ){ + if (!conn->ignore && !is_record_whitelist){ + int is_http = (flow.http.match || flow.https.match) ? 1 : 0; + af_update_client_app_info(client, flow.app_id, flow.drop, 0, is_http, !flow.match_by_dns); + } + } + + + if (flow.drop) + { + AF_LMT_INFO("drop appid = %d\n", flow.app_id); + ret = NF_DROP; + } + + if (malloc_data) + { + if (flow.l4_data) + { + kfree(flow.l4_data); + } + } + return ret; +} + +u_int32_t fwx_hook_gateway_handle(struct sk_buff *skb, struct net_device *dev) +{ + unsigned long long total_packets = 0; + flow_info_t flow; + u_int8_t smac[ETH_ALEN]; + enum ip_conntrack_info ctinfo; + struct nf_conn *ct = NULL; + struct nf_conn_acct *acct; + af_client_info_t *client = NULL; + u_int32_t ret = NF_ACCEPT; + u_int32_t app_id = 0; + u_int8_t drop = 0; + u_int8_t malloc_data = 0; + int is_record_whitelist = 0; + if (!strstr(dev->name, g_lan_ifname)) + return NF_ACCEPT; + + memset((char *)&flow, 0x0, sizeof(flow_info_t)); + if (parse_flow_proto(skb, &flow) < 0) + return NF_ACCEPT; + if (af_match_private_lan_dst(&flow) && flow.dport != 53) + return NF_ACCEPT; + ct = nf_ct_get(skb, &ctinfo); + if (ct == NULL) + return NF_ACCEPT; + + if (flow.l4_protocol == IPPROTO_TCP && !nf_ct_is_confirmed(ct)){ + return NF_ACCEPT; + } + + AF_CLIENT_LOCK_R(); + if (flow.src){ + client = find_af_client_by_ip(flow.src); + } + else if (flow.src6){ + client = find_af_client_by_ipv6(flow.src6); + } + + if (!client) + { + AF_CLIENT_UNLOCK_R(); + return NF_ACCEPT; + } + client->update_jiffies = jiffies; + is_record_whitelist = client->record_whitelist; + AF_CLIENT_UNLOCK_R(); + + app_id = fwx_ct_get_appid(ct); + if (app_id != 0 && fwx_ct_is_valid_appid(app_id)) + { + + AF_LMT_DEBUG("ct appid = %d\n", app_id); + u_int32_t orig_action = fwx_ct_test_bit(ct, FWX_CT_DROP_BIT); + + int ct_action = fwx_ct_test_bit(ct, FWX_CT_DROP_BIT); + flow.ignore = fwx_ct_test_bit(ct, FWX_CT_IGNORE_BIT); + + + if (app_id > 0 && app_id < 1000){ + if (g_appfilter_enable && ct_action) { + AF_LMT_DEBUG("ct drop appid = %d\n", app_id); + return NF_DROP; + } + } + + if (app_id > 1000 && app_id <= 32000) + { + if (check_app_action_changed(ct_action, app_id, client)){ + ct_action = !ct_action; + fwx_ct_set_bit(ct, FWX_CT_DROP_BIT, ct_action); + AF_LMT_DEBUG("update appid %d action to %s, action = %d-->%d\n", + app_id, ct_action ? "drop" : "accept", orig_action, ct_action); + } + + if (g_record_enable){ + if (!flow.ignore && !is_record_whitelist){ + af_update_client_app_info(client, app_id, ct_action, 1, 0, !fwx_ct_test_bit(ct, FWX_CT_DNS_MATCH_BIT)); + } + } + if (g_appfilter_enable && ct_action) { + AF_LMT_DEBUG("drop appid = %d, ct_action = %d\n", app_id, ct_action); + return NF_DROP; + } + } + + } + + if (fwx_ct_test_bit(ct, FWX_CT_DROP_BIT)){ + if (fwx_ct_has_valid_drop_mark(ct)) { + AF_LMT_DEBUG("ct drop, mark = 0x%x\n", fwx_ct_mark_get(ct)); + return NF_DROP; + } + AF_LMT_DEBUG("ignore invalid ct drop mark, mark = 0x%x\n", fwx_ct_mark_get(ct)); + } + + app_id = fwx_ct_get_appid(ct); + if (app_id != 0 && fwx_ct_is_valid_appid(app_id)) + return NF_ACCEPT; + + + if (fwx_ct_test_bit(ct, FWX_CT_CLIENT_HELLO_BIT)) { + flow.client_hello = 1; + + } + + + acct = nf_conn_acct_find(ct); + if (!acct) + return NF_ACCEPT; + total_packets = (unsigned long long)atomic64_read(&acct->counter[IP_CT_DIR_ORIGINAL].packets) + (unsigned long long)atomic64_read(&acct->counter[IP_CT_DIR_REPLY].packets); + + if (total_packets > MAX_DPI_PKT_NUM) + return NF_ACCEPT; + + if (skb_is_nonlinear(skb) && flow.l4_len < MAX_AF_SUPPORT_DATA_LEN) + { + flow.l4_data = read_skb(skb, flow.l4_data - skb->data, flow.l4_len); + if (!flow.l4_data) + return NF_ACCEPT; + malloc_data = 1; + } + dpi_main(&flow); + + if (!is_record_whitelist) { + update_url_visiting_info(client, &flow); + } + if (flow.client_hello) { + fwx_ct_set_bit(ct, FWX_CT_CLIENT_HELLO_BIT, 1); + } + else { + fwx_ct_set_bit(ct, FWX_CT_CLIENT_HELLO_BIT, 0); + } + + + if (fwx_match_feature(&flow)){ + fwx_ct_set_appid(ct, flow.app_id); + fwx_ct_set_bit(ct, FWX_CT_DNS_MATCH_BIT, flow.match_by_dns); + + if (flow.app_id < 1000){ + flow.ignore = 1; + } + else if (flow.feature && flow.feature->ignore){ + fwx_ct_set_bit(ct, FWX_CT_IGNORE_BIT, 1); + flow.ignore = 1; + AF_LMT_DEBUG("gateway set ignore bit, mark = 0x%x\n", fwx_ct_mark_get(ct)); + } + + if (match_app_filter_rule(flow.app_id, client)) { + flow.drop = 1; + fwx_ct_set_bit(ct, FWX_CT_DROP_BIT, 1); + AF_LMT_INFO("##Drop App filter rule, appid = %d, mac = " MAC_FMT "\n", + flow.app_id, MAC_ARRAY(client->mac)); + if (af_should_send_tcp_rst(skb, &flow)) + af_send_tcp_reset(skb); + ret = NF_DROP; + } + } + + if (ret != NF_DROP){ + if (match_mac_filter_rule(client)) { + flow.drop = 1; + fwx_ct_set_bit(ct, FWX_CT_DROP_BIT, 1); + AF_LMT_WARN("##Drop MAC filter rule, mac = " MAC_FMT "\n", + MAC_ARRAY(client->mac)); + if (af_should_send_tcp_rst(skb, &flow)) + af_send_tcp_reset(skb); + ret = NF_DROP; + } + } + + if (g_record_enable){ + if (!flow.ignore && !is_record_whitelist){ + int is_http = (flow.http.match || flow.https.match) ? 1 : 0; + af_update_client_app_info(client, flow.app_id, flow.drop, 0, is_http, !flow.match_by_dns); + if (flow.app_id > 0) { + af_update_active_app_list(client, &flow); + } + } + + + if (!is_record_whitelist) { + af_update_active_host_list(client, &flow); + } + + AF_LMT_INFO("match %s %pI4(%d)--> %pI4(%d) len = %d, %d\n ", IPPROTO_TCP == flow.l4_protocol ? "tcp" : "udp", + &flow.src, flow.sport, &flow.dst, flow.dport, skb->len, flow.app_id); + } + + if (malloc_data) + { + if (flow.l4_data) + { + kfree(flow.l4_data); + } + } + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) +static u_int32_t fwx_pre_hook(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ +#else +static u_int32_t fwx_pre_hook(unsigned int hook, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ +#endif + if (AF_MODE_BYPASS == af_work_mode) + return NF_ACCEPT; + return fwx_hook_gateway_handle(skb, skb->dev); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) +static u_int32_t fwx_by_pass_hook(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ +#else +static u_int32_t fwx_by_pass_hook(unsigned int hook, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ +#endif + if (AF_MODE_GATEWAY == af_work_mode) + return NF_ACCEPT; + return fwx_hook_bypass_handle(skb, skb->dev); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +static struct nf_hook_ops fwx_ops[] __read_mostly = { + { + .hook = fwx_pre_hook, + .pf = NFPROTO_INET, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1, + + }, + { + .hook = fwx_by_pass_hook, + .pf = NFPROTO_INET, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1, + }, +}; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) +static struct nf_hook_ops fwx_ops[] __read_mostly = { + { + .hook = fwx_pre_hook, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1, + }, + { + .hook = fwx_by_pass_hook, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1, + }, + { + .hook = fwx_pre_hook, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1, + + }, + { + .hook = fwx_by_pass_hook, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1, + }, +}; +#else +static struct nf_hook_ops fwx_ops[] __read_mostly = { + { + .hook = fwx_pre_hook, + .owner = THIS_MODULE, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1, + }, + { + .hook = fwx_pre_hook, + .owner = THIS_MODULE, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1, + }, +}; +#endif + +struct timer_list fwx_timer; +int report_flag = 0; +#define FWX_TIMER_INTERVAL 1 +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) +static void fwx_timer_func(struct timer_list *t) +#else +static void fwx_timer_func(unsigned long ptr) +#endif +{ + static int count = 0; + if (count % 60 == 0) + check_client_expire(); + + count++; + af_conn_clean_timeout(); + + mod_timer(&fwx_timer, jiffies + FWX_TIMER_INTERVAL * HZ); +} + +static void init_fwx_timer(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) + timer_setup(&fwx_timer, fwx_timer_func, 0); +#else + setup_timer(&fwx_timer, fwx_timer_func, FWX_TIMER_INTERVAL * HZ); +#endif + mod_timer(&fwx_timer, jiffies + FWX_TIMER_INTERVAL * HZ); + AF_INFO("init fwx timer...ok"); +} + +static void fini_fwx_timer(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0) + timer_shutdown_sync(&fwx_timer); +#else + del_timer_sync(&fwx_timer); +#endif + AF_INFO("del fwx timer...ok"); +} + + static struct sock *fwx_sock = NULL; + +#define FWX_EXTRA_MSG_BUF_LEN 128 +int af_send_msg_to_user(char *pbuf, uint16_t len) +{ + struct sk_buff *nl_skb; + struct nlmsghdr *nlh; + int buf_len = FWX_EXTRA_MSG_BUF_LEN + len; + char *msg_buf = NULL; + struct af_msg_hdr *hdr = NULL; + char *p_data = NULL; + int ret; + if (len >= MAX_FWX_NL_MSG_LEN) + return -1; + + msg_buf = kmalloc(buf_len, GFP_ATOMIC); + if (!msg_buf) + return -1; + + memset(msg_buf, 0x0, buf_len); + nl_skb = nlmsg_new(len + sizeof(struct af_msg_hdr), GFP_ATOMIC); + if (!nl_skb) + { + ret = -1; + goto fail; + } + + nlh = nlmsg_put(nl_skb, 0, 0, FWX_NETLINK_ID, len + sizeof(struct af_msg_hdr), 0); + if (nlh == NULL) + { + nlmsg_free(nl_skb); + ret = -1; + goto fail; + } + + hdr = (struct af_msg_hdr *)msg_buf; + hdr->magic = 0xa0b0c0d0; + hdr->len = len; + p_data = msg_buf + sizeof(struct af_msg_hdr); + memcpy(p_data, pbuf, len); + memcpy(nlmsg_data(nlh), msg_buf, len + sizeof(struct af_msg_hdr)); + ret = netlink_unicast(fwx_sock, nl_skb, 999, MSG_DONTWAIT); + +fail: + kfree(msg_buf); + return ret; +} + +static void fwx_user_msg_handle(char *data, int len) +{ + char *msg_data = data + sizeof(af_msg_t); + if (len < sizeof(af_msg_t)) + return; + af_msg_t *msg = (af_msg_t *)data; + switch (msg->action) + { + case FWX_NL_MSG_INIT: + af_client_list_reset_report_num(); + report_flag = 1; + break; + case FWX_NL_MSG_ADD_FEATURE: + af_add_feature_msg_handle(msg_data, len - sizeof(af_msg_t)); + break; + case FWX_NL_MSG_CLEAN_FEATURE: + AF_INFO("clean feature\n"); + af_clean_feature_list(); + break; + case FWX_NL_MSG_FEATURE_LOAD_DONE: + AF_INFO("feature load done\n"); + af_feature_load_done_msg_handle(); + break; + default: + break; + } +} +static void fwx_netlink_msg_rcv(struct sk_buff *skb) +{ + struct nlmsghdr *nlh = NULL; + char *umsg = NULL; + void *udata = NULL; + struct af_msg_hdr *af_hdr = NULL; + if (skb->len >= nlmsg_total_size(0)) + { + nlh = nlmsg_hdr(skb); + umsg = NLMSG_DATA(nlh); + af_hdr = (struct af_msg_hdr *)umsg; + if (af_hdr->magic != 0xa0b0c0d0) + return; + if (af_hdr->len <= 0 || af_hdr->len >= MAX_FWX_NETLINK_MSG_LEN) + return; + udata = umsg + sizeof(struct af_msg_hdr); + + if (udata) + fwx_user_msg_handle(udata, af_hdr->len); + } +} + +static int netlink_fwx_init(void) +{ + struct netlink_kernel_cfg nl_cfg = {0}; + nl_cfg.input = fwx_netlink_msg_rcv; + fwx_sock = netlink_kernel_create(&init_net, FWX_NETLINK_ID, &nl_cfg); + + if (NULL == fwx_sock) + { + AF_ERROR("init fwx netlink failed, id=%d\n", FWX_NETLINK_ID); + return -1; + } + AF_INFO("init fwx netlink ok, id = %d\n", FWX_NETLINK_ID); + return 0; +} + + +int af_active_app_init_procfs(void); +void af_active_app_clean_procfs(void); +int af_active_host_init_procfs(void); +void af_active_host_clean_procfs(void); + +static int __init fwx_init(void) +{ + int err; + af_conn_init(); + netlink_fwx_init(); + af_log_init(); + init_af_client_procfs(); + af_client_init(); + af_active_app_init_procfs(); + af_active_host_init_procfs(); + fwx_register_dev(); + fwx_mac_filter_init(); + fwx_app_filter_init(); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) + err = nf_register_net_hooks(&init_net, fwx_ops, ARRAY_SIZE(fwx_ops)); +#else + err = nf_register_hooks(fwx_ops, ARRAY_SIZE(fwx_ops)); +#endif + if (err) + { + AF_ERROR("fwx register filter hooks failed!\n"); + } + init_fwx_timer(); + AF_INFO("fwx: Driver ver. %s - Copyright(c) 2026, fanchmwrt, \n", FWX_VERSION); + AF_INFO("fwx: init ok\n"); + return 0; +} + +static void fwx_fini(void) +{ + AF_INFO("fwx module exit\n"); + fini_fwx_timer(); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) + nf_unregister_net_hooks(&init_net, fwx_ops, ARRAY_SIZE(fwx_ops)); +#else + nf_unregister_hooks(fwx_ops, ARRAY_SIZE(fwx_ops)); +#endif + finit_af_client_procfs(); + af_active_app_clean_procfs(); + af_active_host_clean_procfs(); + af_clean_feature_list(); + af_clear_active_app_list(); + af_clear_active_host_list(); + af_log_exit(); + af_client_exit(); + fwx_app_filter_exit(); + fwx_mac_filter_exit(); + fwx_unregister_dev(); + if (fwx_sock) + netlink_kernel_release(fwx_sock); + af_conn_exit(); + return; +} + + +void af_update_active_app_list(af_client_info_t *client, flow_info_t *flow) +{ + active_app_node_t *node = NULL, *tmp_node = NULL; + active_app_node_t *new_node = NULL; + int found = 0; + int list_count = 0; + + if (!client || !flow || flow->app_id == 0 || flow->match_by_dns) + return; + + spin_lock_bh(&active_app_list_lock); + + + list_for_each_entry_safe(node, tmp_node, &active_app_list, list) { + list_count++; + if (node->app_id == flow->app_id) { + + memcpy(node->mac, client->mac, MAC_ADDR_LEN); + node->src_ip = flow->src; + node->dst_ip = flow->dst; + if (flow->src6) { + memcpy(&node->src_ip6, flow->src6, sizeof(struct in6_addr)); + } + if (flow->dst6) { + memcpy(&node->dst_ip6, flow->dst6, sizeof(struct in6_addr)); + } + node->src_port = flow->sport; + node->dst_port = flow->dport; + node->l4_protocol = flow->l4_protocol; + node->drop = flow->drop; + + + if (flow->http.match) { + node->proto_type = 1; + if (flow->http.host_pos && flow->http.host_len > 0) { + int copy_len = (flow->http.host_len > 31) ? 31 : flow->http.host_len; + memcpy(node->host, flow->http.host_pos, copy_len); + node->host[copy_len] = '\0'; + } else { + node->host[0] = '\0'; + } + + if (flow->http.url_pos && flow->http.url_len > 0) { + int copy_len = (flow->http.url_len > 31) ? 31 : flow->http.url_len; + memcpy(node->uri, flow->http.url_pos, copy_len); + node->uri[copy_len] = '\0'; + } else { + node->uri[0] = '\0'; + } + } else if (flow->https.match) { + node->proto_type = 2; + if (flow->https.url_pos && flow->https.url_len > 0) { + int copy_len = (flow->https.url_len > 31) ? 31 : flow->https.url_len; + memcpy(node->host, flow->https.url_pos, copy_len); + node->host[copy_len] = '\0'; + } else { + node->host[0] = '\0'; + } + + node->uri[0] = '\0'; + } else { + node->proto_type = 0; + node->host[0] = '\0'; + node->uri[0] = '\0'; + } + + node->update_time = ktime_get_real_seconds(); + + + list_move(&node->list, &active_app_list); + + found = 1; + break; + } + } + + + if (!found) { + + if (list_count >= MAX_ACTIVE_APP_LIST_SIZE) { + if (!list_empty(&active_app_list)) { + node = list_last_entry(&active_app_list, active_app_node_t, list); + list_del(&node->list); + kfree(node); + } + } + + + new_node = kzalloc(sizeof(active_app_node_t), GFP_ATOMIC); + if (new_node) { + INIT_LIST_HEAD(&new_node->list); + new_node->app_id = flow->app_id; + memcpy(new_node->mac, client->mac, MAC_ADDR_LEN); + new_node->src_ip = flow->src; + new_node->dst_ip = flow->dst; + if (flow->src6) { + memcpy(&new_node->src_ip6, flow->src6, sizeof(struct in6_addr)); + } + if (flow->dst6) { + memcpy(&new_node->dst_ip6, flow->dst6, sizeof(struct in6_addr)); + } + new_node->src_port = flow->sport; + new_node->dst_port = flow->dport; + new_node->l4_protocol = flow->l4_protocol; + new_node->drop = flow->drop; + + + if (flow->http.match) { + new_node->proto_type = 1; + if (flow->http.host_pos && flow->http.host_len > 0) { + int copy_len = (flow->http.host_len > 31) ? 31 : flow->http.host_len; + memcpy(new_node->host, flow->http.host_pos, copy_len); + new_node->host[copy_len] = '\0'; + } + + if (flow->http.url_pos && flow->http.url_len > 0) { + int copy_len = (flow->http.url_len > 31) ? 31 : flow->http.url_len; + memcpy(new_node->uri, flow->http.url_pos, copy_len); + new_node->uri[copy_len] = '\0'; + } + } else if (flow->https.match) { + new_node->proto_type = 2; + if (flow->https.url_pos && flow->https.url_len > 0) { + int copy_len = (flow->https.url_len > 31) ? 31 : flow->https.url_len; + memcpy(new_node->host, flow->https.url_pos, copy_len); + new_node->host[copy_len] = '\0'; + } + + new_node->uri[0] = '\0'; + } else { + new_node->proto_type = 0; + new_node->host[0] = '\0'; + new_node->uri[0] = '\0'; + } + + new_node->update_time = ktime_get_real_seconds(); + + + list_add(&new_node->list, &active_app_list); + } + } + + spin_unlock_bh(&active_app_list_lock); +} + + +active_app_node_t *af_find_active_app(u_int32_t app_id) +{ + active_app_node_t *node = NULL; + + if (app_id == 0) + return NULL; + + spin_lock_bh(&active_app_list_lock); + list_for_each_entry(node, &active_app_list, list) { + if (node->app_id == app_id) { + spin_unlock_bh(&active_app_list_lock); + return node; + } + } + spin_unlock_bh(&active_app_list_lock); + + return NULL; +} + + +void af_clear_active_app_list(void) +{ + active_app_node_t *node = NULL, *tmp_node = NULL; + + spin_lock_bh(&active_app_list_lock); + list_for_each_entry_safe(node, tmp_node, &active_app_list, list) { + list_del(&node->list); + kfree(node); + } + spin_unlock_bh(&active_app_list_lock); +} + +static int af_is_invalid_active_host(const char *host) +{ + if (!host) + return 1; + if (af_is_ip_literal_host(host)) + return 1; + + return strchr(host, '.') ? 0 : 1; +} + +void af_update_active_host_list(af_client_info_t *client, flow_info_t *flow) +{ + active_host_node_t *node = NULL, *tmp_node = NULL; + active_host_node_t *new_node = NULL; + int found = 0; + int list_count = 0; + char host_buf[64] = {0}; + int host_len = 0; + + if (!client || !flow || flow->match_by_dns) + return; + + + if (!flow->http.match && !flow->https.match) + return; + + + if (flow->http.match && flow->http.host_pos && flow->http.host_len > 0) { + host_len = (flow->http.host_len > 63) ? 63 : flow->http.host_len; + memcpy(host_buf, flow->http.host_pos, host_len); + host_buf[host_len] = '\0'; + } else if (flow->https.match && flow->https.url_pos && flow->https.url_len > 0) { + host_len = (flow->https.url_len > 63) ? 63 : flow->https.url_len; + memcpy(host_buf, flow->https.url_pos, host_len); + host_buf[host_len] = '\0'; + } else { + return; + } + + if (af_is_invalid_active_host(host_buf)) + return; + + spin_lock_bh(&active_host_list_lock); + + + list_for_each_entry_safe(node, tmp_node, &active_host_list, list) { + list_count++; + if (strncmp(node->host, host_buf, 64) == 0) { + + memcpy(node->mac, client->mac, MAC_ADDR_LEN); + node->src_ip = flow->src; + node->dst_ip = flow->dst; + if (flow->src6) { + memcpy(&node->src_ip6, flow->src6, sizeof(struct in6_addr)); + } + if (flow->dst6) { + memcpy(&node->dst_ip6, flow->dst6, sizeof(struct in6_addr)); + } + node->src_port = flow->sport; + node->dst_port = flow->dport; + node->l4_protocol = flow->l4_protocol; + node->drop = flow->drop; + + + if (flow->http.match) { + node->proto_type = 1; + } else if (flow->https.match) { + node->proto_type = 2; + } else { + node->proto_type = 0; + } + + node->update_time = ktime_get_real_seconds(); + + + list_move(&node->list, &active_host_list); + + found = 1; + break; + } + } + + + if (!found) { + + if (list_count >= MAX_ACTIVE_HOST_LIST_SIZE) { + if (!list_empty(&active_host_list)) { + node = list_last_entry(&active_host_list, active_host_node_t, list); + list_del(&node->list); + kfree(node); + } + } + + + new_node = kzalloc(sizeof(active_host_node_t), GFP_ATOMIC); + if (new_node) { + INIT_LIST_HEAD(&new_node->list); + strncpy(new_node->host, host_buf, 63); + new_node->host[63] = '\0'; + memcpy(new_node->mac, client->mac, MAC_ADDR_LEN); + new_node->src_ip = flow->src; + new_node->dst_ip = flow->dst; + if (flow->src6) { + memcpy(&new_node->src_ip6, flow->src6, sizeof(struct in6_addr)); + } + if (flow->dst6) { + memcpy(&new_node->dst_ip6, flow->dst6, sizeof(struct in6_addr)); + } + new_node->src_port = flow->sport; + new_node->dst_port = flow->dport; + new_node->l4_protocol = flow->l4_protocol; + new_node->drop = flow->drop; + + + if (flow->http.match) { + new_node->proto_type = 1; + } else if (flow->https.match) { + new_node->proto_type = 2; + } else { + new_node->proto_type = 0; + } + + new_node->update_time = ktime_get_real_seconds(); + + + list_add(&new_node->list, &active_host_list); + } + } + + spin_unlock_bh(&active_host_list_lock); +} + + +active_host_node_t *af_find_active_host(const char *host) +{ + active_host_node_t *node = NULL; + + if (!host || strlen(host) == 0) + return NULL; + + spin_lock_bh(&active_host_list_lock); + list_for_each_entry(node, &active_host_list, list) { + if (strncmp(node->host, host, 64) == 0) { + spin_unlock_bh(&active_host_list_lock); + return node; + } + } + spin_unlock_bh(&active_host_list_lock); + + return NULL; +} + + +void af_clear_active_host_list(void) +{ + active_host_node_t *node = NULL, *tmp_node = NULL; + + spin_lock_bh(&active_host_list_lock); + list_for_each_entry_safe(node, tmp_node, &active_host_list, list) { + list_del(&node->list); + kfree(node); + } + spin_unlock_bh(&active_host_list_lock); +} + + +static void print_active_app_header(struct seq_file *s) +{ + seq_printf(s, "%-6s %-18s %-16s %-8s %-16s %-8s %-6s %-8s %-5s %-32s %-12s %-32s\n", + "AppID", "MAC", "SrcIP", "SrcPort", "DstIP", "DstPort", "Proto", "AppProto", "Drop", "Host", "LastUpdate", "URI"); +} + +static void *af_active_app_seq_start(struct seq_file *s, loff_t *pos) +{ + spin_lock_bh(&active_app_list_lock); + return seq_list_start(&active_app_list, *pos); +} + +static void *af_active_app_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return seq_list_next(v, &active_app_list, pos); +} + +static void af_active_app_seq_stop(struct seq_file *s, void *v) +{ + spin_unlock_bh(&active_app_list_lock); +} + +static int af_active_app_seq_show(struct seq_file *s, void *v) +{ + char mac_str[32] = {0}; + char src_ip_str[64] = {0}; + char dst_ip_str[64] = {0}; + char proto_str[8] = {0}; + + active_app_node_t *node = list_entry(v, active_app_node_t, list); + + + if (v == active_app_list.next) + print_active_app_header(s); + + + sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac)); + + + if (node->src_ip != 0) { + sprintf(src_ip_str, "%pI4", &node->src_ip); + } else { + char ip6_str[64] = {0}; + ipv6_to_str(&node->src_ip6, ip6_str); + sprintf(src_ip_str, "[%s]", ip6_str); + } + + + if (node->dst_ip != 0) { + sprintf(dst_ip_str, "%pI4", &node->dst_ip); + } else { + char ip6_str[64] = {0}; + ipv6_to_str(&node->dst_ip6, ip6_str); + sprintf(dst_ip_str, "[%s]", ip6_str); + } + + + switch (node->l4_protocol) { + case IPPROTO_TCP: + strcpy(proto_str, "TCP"); + break; + case IPPROTO_UDP: + strcpy(proto_str, "UDP"); + break; + default: + sprintf(proto_str, "%d", node->l4_protocol); + break; + } + + + seq_printf(s, "%-6u %-18s %-16s %-8u %-16s %-8u %-6s %-8u %-5u %-32s %-12u %-32s\n", + node->app_id, + mac_str, + src_ip_str, + node->src_port, + dst_ip_str, + node->dst_port, + proto_str, + node->proto_type, + node->drop, + node->host[0] ? node->host : "-", + node->update_time, + + (node->proto_type == 1 && node->uri[0]) ? node->uri : "-" + + ); + + return 0; +} + +static const struct seq_operations af_active_app_seq_ops = { + .start = af_active_app_seq_start, + .next = af_active_app_seq_next, + .stop = af_active_app_seq_stop, + .show = af_active_app_seq_show +}; + +static int af_active_app_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &af_active_app_seq_ops); +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) +static const struct file_operations af_active_app_fops = { + .owner = THIS_MODULE, + .open = af_active_app_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; +#else +static const struct proc_ops af_active_app_fops = { + .proc_flags = PROC_ENTRY_PERMANENT, + .proc_read = seq_read, + .proc_open = af_active_app_open, + .proc_lseek = seq_lseek, + .proc_release = seq_release_private, +}; +#endif + +#define AF_ACTIVE_APP_PROC_STR "af_active_app" + + +int af_active_app_init_procfs(void) +{ + struct proc_dir_entry *pde; + struct net *net = &init_net; + + pde = proc_create(AF_ACTIVE_APP_PROC_STR, 0444, net->proc_net, &af_active_app_fops); + if (!pde) { + AF_ERROR("af_active_app proc file created error\n"); + return -1; + } + return 0; +} + + +void af_active_app_clean_procfs(void) +{ + struct net *net = &init_net; + remove_proc_entry(AF_ACTIVE_APP_PROC_STR, net->proc_net); +} + + +static void print_active_host_header(struct seq_file *s) +{ + seq_printf(s, "%-48s %-18s %-16s %-8s %-16s %-8s %-6s %-8s %-5s %-12s\n", + "Host", "MAC", "SrcIP", "SrcPort", "DstIP", "DstPort", "Proto", "AppProto", "Drop", "LastUpdate"); +} + +static void *af_active_host_seq_start(struct seq_file *s, loff_t *pos) +{ + spin_lock_bh(&active_host_list_lock); + return seq_list_start(&active_host_list, *pos); +} + +static void *af_active_host_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return seq_list_next(v, &active_host_list, pos); +} + +static void af_active_host_seq_stop(struct seq_file *s, void *v) +{ + spin_unlock_bh(&active_host_list_lock); +} + +static int af_active_host_seq_show(struct seq_file *s, void *v) +{ + char mac_str[32] = {0}; + char src_ip_str[64] = {0}; + char dst_ip_str[64] = {0}; + char proto_str[8] = {0}; + + active_host_node_t *node = list_entry(v, active_host_node_t, list); + + + if (v == active_host_list.next) + print_active_host_header(s); + + + sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac)); + + + if (node->src_ip != 0) { + sprintf(src_ip_str, "%pI4", &node->src_ip); + } else { + char ip6_str[64] = {0}; + ipv6_to_str(&node->src_ip6, ip6_str); + sprintf(src_ip_str, "[%s]", ip6_str); + } + + + if (node->dst_ip != 0) { + sprintf(dst_ip_str, "%pI4", &node->dst_ip); + } else { + char ip6_str[64] = {0}; + ipv6_to_str(&node->dst_ip6, ip6_str); + sprintf(dst_ip_str, "[%s]", ip6_str); + } + + + switch (node->l4_protocol) { + case IPPROTO_TCP: + strcpy(proto_str, "TCP"); + break; + case IPPROTO_UDP: + strcpy(proto_str, "UDP"); + break; + default: + sprintf(proto_str, "%d", node->l4_protocol); + break; + } + + + seq_printf(s, "%-48s %-18s %-16s %-8u %-16s %-8u %-6s %-8u %-5u %-12u\n", + node->host, + mac_str, + src_ip_str, + node->src_port, + dst_ip_str, + node->dst_port, + proto_str, + node->proto_type, + node->drop, + node->update_time); + + return 0; +} + +static const struct seq_operations af_active_host_seq_ops = { + .start = af_active_host_seq_start, + .next = af_active_host_seq_next, + .stop = af_active_host_seq_stop, + .show = af_active_host_seq_show +}; + +static int af_active_host_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &af_active_host_seq_ops); +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0) +static const struct file_operations af_active_host_fops = { + .owner = THIS_MODULE, + .open = af_active_host_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; +#else +static const struct proc_ops af_active_host_fops = { + .proc_flags = PROC_ENTRY_PERMANENT, + .proc_read = seq_read, + .proc_open = af_active_host_open, + .proc_lseek = seq_lseek, + .proc_release = seq_release_private, +}; +#endif + +#define AF_ACTIVE_HOST_PROC_STR "af_active_host" + + +int af_active_host_init_procfs(void) +{ + struct proc_dir_entry *pde; + struct net *net = &init_net; + + pde = proc_create(AF_ACTIVE_HOST_PROC_STR, 0444, net->proc_net, &af_active_host_fops); + if (!pde) { + AF_ERROR("af_active_host proc file created error\n"); + return -1; + } + return 0; +} + + +void af_active_host_clean_procfs(void) +{ + struct net *net = &init_net; + remove_proc_entry(AF_ACTIVE_HOST_PROC_STR, net->proc_net); +} + +module_init(fwx_init); +module_exit(fwx_fini); diff --git a/oaf/src/af_utils.c b/oaf/src/fwx_utils.c similarity index 86% rename from oaf/src/af_utils.c rename to oaf/src/fwx_utils.c index dcd355a1..30fc185a 100644 --- a/oaf/src/af_utils.c +++ b/oaf/src/fwx_utils.c @@ -1,3 +1,8 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ #include #include #include @@ -5,7 +10,7 @@ #include #include #include -#include "af_utils.h" +#include "fwx_utils.h" #define MAX_DUMP_STR_LEN 256 u_int32_t af_get_timestamp_sec(void) @@ -22,15 +27,37 @@ u_int32_t af_get_timestamp_sec(void) } + +int mac_str_to_bin(const char *mac_str, u8 *mac_bin) { + if (!mac_str || !mac_bin) + return 0; + + const char *p = mac_str; + int i = 0; + + while (*p && i < 6) { + unsigned int byte; + char hex[3] = {0}; + + if (sscanf(p, "%2hhx", &byte) != 1) + return false; + + mac_bin[i++] = (u8)byte; + while (*p && *p != ':') + p++; + + if (*p == ':') + p++; + } + + return i == 6; +} + int k_atoi(const char *str) { int result = 0; - - // Skip whitespace while (*str == ' ' || *str == '\t') { str++; } - - // Convert characters to integer while (*str >= '0' && *str <= '9') { result = result * 10 + (*str - '0'); str++; @@ -83,7 +110,7 @@ void dump_str(char *name, unsigned char *p, int len) strncpy(buf, p, len); printk("[%s]\n", buf); } -static int isprint_char(unsigned char c) +int isprint_char(unsigned char c) { if (c >= 0x20 && c <= 0x7e) return 1; @@ -187,11 +214,7 @@ static int k_vsscanf(const char *buf, const char *fmt, va_list args) int num = 0; u8 qualifier; u8 base; -#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,22) - int field_width; -#else s16 field_width; -#endif bool is_sign; while (*fmt && *str) { if (isspace(*fmt)) { @@ -251,13 +274,8 @@ static int k_vsscanf(const char *buf, const char *fmt, va_list args) case 's': { char *s = (char *)va_arg(args, char *); -#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,22) - if(field_width == -1) - field_width = INT_MAX; -#else if (field_width == -1) field_width = SHRT_MAX; -#endif str = skip_spaces(str); while (*str && (!isspace(*str) || ((unsigned char )*str == 0xA0) )&& field_width--) @@ -279,14 +297,12 @@ static int k_vsscanf(const char *buf, const char *fmt, va_list args) case 'X': base = 16; break; - case 'i': - base = 0; - fallthrough; - case 'd': - is_sign = 1; - fallthrough; - case 'u': - break; + case 'i': + base = 0; + case 'd': + is_sign = 1; + case 'u': + break; case '%': if (*str++ != '%') return num; @@ -383,22 +399,3 @@ int k_sscanf(const char *buf, const char *fmt, ...) } -int mac_to_hex(u8 *mac, u8 *mac_hex) -{ - u32 mac_tmp[6]; - int ret = 0, i = 0; - ret = sscanf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", - (unsigned int *)&mac_tmp[0], - (unsigned int *)&mac_tmp[1], - (unsigned int *)&mac_tmp[2], - (unsigned int *)&mac_tmp[3], - (unsigned int *)&mac_tmp[4], - (unsigned int *)&mac_tmp[5]); - if (6 != ret) - return -1; - for (i = 0; i < 6; i++) - { - mac_hex[i] = mac_tmp[i]; - } - return 0; -} diff --git a/oaf/src/af_utils.h b/oaf/src/fwx_utils.h similarity index 68% rename from oaf/src/af_utils.h rename to oaf/src/fwx_utils.h index fe2497f4..165a279a 100644 --- a/oaf/src/af_utils.h +++ b/oaf/src/fwx_utils.h @@ -1,8 +1,14 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ #ifndef AF_UTILS_H #define AF_UTILS_H u_int32_t af_get_timestamp_sec(void); char *k_trim(char *s); +int mac_str_to_bin(const char *mac_str, u8 *mac_bin); int check_local_network_ip(unsigned int ip); @@ -13,8 +19,6 @@ void dump_hex(char *name, unsigned char *p, int len); int k_sscanf(const char *buf, const char *fmt, ...); int k_atoi(const char *str); void print_hex_ascii(const unsigned char *data, size_t size); -int hash_mac(unsigned char *mac); -int mac_to_hex(u8 *mac, u8 *mac_hex); #endif diff --git a/oaf/src/cJSON.c b/oaf/src/k_json.c similarity index 92% rename from oaf/src/cJSON.c rename to oaf/src/k_json.c index fe42bc5e..12a91386 100644 --- a/oaf/src/cJSON.c +++ b/oaf/src/k_json.c @@ -20,8 +20,8 @@ THE SOFTWARE. */ -// cJSON -// JSON parser in C. + + #if 0 #include @@ -31,7 +31,7 @@ #include #endif -#include "cJSON.h" +#include "k_json.h" #include #include @@ -78,7 +78,7 @@ static char* cJSON_strdup(const char* str) #if 0 void cJSON_InitHooks(cJSON_Hooks* hooks) { - if (!hooks) { /* Reset hooks */ + if (!hooks) { cJSON_malloc = malloc; cJSON_realloc = realloc; cJSON_free = free; @@ -91,7 +91,7 @@ void cJSON_InitHooks(cJSON_Hooks* hooks) } #endif -// Internal constructor. + static cJSON *cJSON_New_Item(void) { cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); @@ -99,7 +99,7 @@ static cJSON *cJSON_New_Item(void) return node; } -// Delete a cJSON structure. + void cJSON_Delete(cJSON *c) { cJSON *next; @@ -115,20 +115,20 @@ void cJSON_Delete(cJSON *c) } -/* Parse the input text to generate a number, and populate the result into item. */ + static const char *parse_number(cJSON *item,const char *num) { int n=0,sign=1; - if (*num=='-') sign=-1,num++; /* Has sign? */ - if (*num=='0') num++; /* is zero */ - if (*num>='1' && *num<='9') do n=(n*10)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ + if (*num=='-') sign=-1,num++; + if (*num=='0') num++; + if (*num>='1' && *num<='9') do n=(n*10)+(*num++ -'0'); while (*num>='0' && *num<='9'); item->valueint=(int)n; item->type=cJSON_Number; return num; } -/* Render the number nicely from the given item into a string. */ + static char *print_number(cJSON *item) { char *str; @@ -139,7 +139,7 @@ static char *print_number(cJSON *item) } -// Parse the input text into an unescaped cstring, and populate item. + static const char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; static const char *parse_string(cJSON *item,const char *str) { @@ -190,7 +190,7 @@ static const char *parse_string(cJSON *item,const char *str) return ptr; } -// Render the cstring provided to an escaped version that can be printed. + static char *print_string_ptr(const char *str) { const char *ptr;char *ptr2,*out;int len=0; @@ -222,10 +222,10 @@ static char *print_string_ptr(const char *str) *ptr2++='\"';*ptr2++=0; return out; } -// Invote print_string_ptr (which is useful) on an item. + static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);} -// Predeclare these prototypes. + static const char *parse_value(cJSON *item,const char *value); static char *print_value(cJSON *item,int depth); static const char *parse_array(cJSON *item,const char *value); @@ -233,23 +233,23 @@ static char *print_array(cJSON *item,int depth); static const char *parse_object(cJSON *item,const char *value); static char *print_object(cJSON *item,int depth); -// Utility to jump whitespace and cr/lf + static const char *skip(const char *in) {while (in && *in<=32) in++; return in;} -// Parse an object - create a new root, and populate. + cJSON *cJSON_Parse(const char *value) { cJSON *c=cJSON_New_Item(); - if (!c) return 0; /* memory fail */ + if (!c) return 0; if (!parse_value(c,skip(value))) {cJSON_Delete(c);return 0;} return c; } -// Render a cJSON item/entity/structure to text. + char *cJSON_Print(cJSON *item) {return print_value(item,0);} -// Parser core - when encountering text, process appropriately. + static const char *parse_value(cJSON *item,const char *value) { if (!value) return 0; // Fail on null. @@ -264,7 +264,7 @@ static const char *parse_value(cJSON *item,const char *value) return 0; // failure. } -// Render a value to text. + static char *print_value(cJSON *item,int depth) { char *out=0; @@ -281,7 +281,7 @@ static char *print_value(cJSON *item,int depth) return out; } -// Build an array from input text. + static const char *parse_array(cJSON *item,const char *value) { cJSON *child; @@ -309,7 +309,7 @@ static const char *parse_array(cJSON *item,const char *value) return 0; // malformed. } -// Render an array to text + static char *print_array(cJSON *item,int depth) { char *out,*ptr,*ret;int len=5; @@ -333,7 +333,7 @@ static char *print_array(cJSON *item,int depth) return out; } -// Build an object from the text. + static const char *parse_object(cJSON *item,const char *value) { cJSON *child; @@ -368,7 +368,7 @@ static const char *parse_object(cJSON *item,const char *value) return 0; // malformed. } -// Render an object to text. + static char *print_object(cJSON *item,int depth) { char *out,*ptr,*ret,*str;int len=7,i; @@ -491,23 +491,23 @@ void cJSON_Minify(char *json) } } - /* and null-terminate. */ + *into = '\0'; } -// Get Array size/item / object item. + int cJSON_GetArraySize(cJSON *array) {cJSON *c=array->child;int i=0;while(c)i++,c=c->next;return i;} cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item) item--,c=c->next; return c;} cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && strcasecmp(c->string,string)) c=c->next; return c;} -// Utility for array list handling. + static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} -// Add item to array/object. + void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} -// Create basic types: + cJSON *cJSON_CreateNull() {cJSON *item=cJSON_New_Item();item->type=cJSON_NULL;return item;} cJSON *cJSON_CreateTrue() {cJSON *item=cJSON_New_Item();item->type=cJSON_True;return item;} cJSON *cJSON_CreateFalse() {cJSON *item=cJSON_New_Item();item->type=cJSON_False;return item;} @@ -516,6 +516,6 @@ cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();item cJSON *cJSON_CreateArray() {cJSON *item=cJSON_New_Item();item->type=cJSON_Array;return item;} cJSON *cJSON_CreateObject() {cJSON *item=cJSON_New_Item();item->type=cJSON_Object;return item;} -// Create Arrays: + cJSON *cJSON_CreateIntArray(int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;ichild=n;else suffix_object(p,n);p=n;}return a;} cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;ichild=n;else suffix_object(p,n);p=n;}return a;} diff --git a/oaf/src/cJSON.h b/oaf/src/k_json.h similarity index 82% rename from oaf/src/cJSON.h rename to oaf/src/k_json.h index b0ead3fb..6c8b776c 100644 --- a/oaf/src/cJSON.h +++ b/oaf/src/k_json.h @@ -24,7 +24,7 @@ #define cJSON__h #include -// cJSON Types: + #define cJSON_False 0 #define cJSON_True 1 #define cJSON_NULL 2 @@ -33,7 +33,7 @@ #define cJSON_Array 5 #define cJSON_Object 6 -// The cJSON structure: + typedef struct cJSON { struct cJSON *next,*prev; // next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem struct cJSON *child; // An array or object item will have a child pointer pointing to a chain of the items in the array/object. @@ -51,25 +51,25 @@ typedef struct cJSON_Hooks { void (*free_fn)(void *ptr); } cJSON_Hooks; -// Supply malloc, realloc and free functions to cJSON + extern void cJSON_InitHooks(cJSON_Hooks* hooks); -// Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. + extern cJSON *cJSON_Parse(const char *value); -// Render a cJSON entity to text for transfer/storage. Free the char* when finished. + extern char *cJSON_Print(cJSON *item); -// Delete a cJSON entity and all subentities. + extern void cJSON_Delete(cJSON *c); -// Returns the number of items in an array (or object). + extern int cJSON_GetArraySize(cJSON *array); -// Retrieve item number "item" from array "array". Returns NULL if unsuccessful. + extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); -// Get item "string" from object. Case insensitive. + extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); -// These calls create a cJSON item of the appropriate type. + extern cJSON *cJSON_CreateNull(void); extern cJSON *cJSON_CreateTrue(void); extern cJSON *cJSON_CreateFalse(void); @@ -78,11 +78,10 @@ extern cJSON *cJSON_CreateString(const char *string); extern cJSON *cJSON_CreateArray(void); extern cJSON *cJSON_CreateObject(void); extern void cJSON_Minify(char *json); -// These utilities create an Array of count items. -extern cJSON *cJSON_CreateIntArray(int *numbers,int count); -extern cJSON *cJSON_CreateStringArray(const char **strings,int count); -// Append item to the specified array/object. +extern cJSON *cJSON_CreateIntArray(int *numbers,int count); + + extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); diff --git a/oaf/src/regexp.c b/oaf/src/regexp.c index e31fc3c0..41281c17 100644 --- a/oaf/src/regexp.c +++ b/oaf/src/regexp.c @@ -3,7 +3,7 @@ #include #include #include -//#include "regexp.h" + typedef enum{CHAR, DOT, BEGIN, END, STAR, PLUS, QUES, LIST, TYPENUM}TYPE; @@ -18,8 +18,6 @@ typedef struct RE{ int match_longest = 0; char *match_first = NULL; -int regexp_match(char *reg, char *text); - static void * getmem(size_t size) { @@ -272,32 +270,3 @@ out: regexp_free(regexp); return ret; } - - -static __maybe_unused void TEST_reg_func(char *reg, char * str, int ret) -{ - - if (ret != regexp_match(reg, str)) { - if (reg) - printk("reg = %s,", reg); - else - printk("reg = null"); - if (str) - printk("str = %s ", str); - else - printk("str= null"); - printk("error, unit test.... failed, ret = %d\n",ret); - } - else { - if (reg && str) - printk("[unit test] %s %s......ok,ret = %d\n", reg, str, ret); - } -} - -static __maybe_unused void TEST_regexp(void) -{ - TEST_reg_func(".*baidu.com$", "www.baidu.com", 1); - TEST_reg_func("^sina.com", "www.sina.com.cn", 0); - TEST_reg_func("^sina.com", "sina.com.cn", 1); - TEST_reg_func(".*baidu.com$", "www.baidu.com223", 0); -} diff --git a/open-app-filter/Makefile b/open-app-filter/Makefile index 59e64f60..9ad1223c 100644 --- a/open-app-filter/Makefile +++ b/open-app-filter/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=appfilter -PKG_VERSION:=6.1.8 +PKG_VERSION:=7.0.1 PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) @@ -10,10 +10,10 @@ include $(INCLUDE_DIR)/package.mk TARGET_CFLAGS +=-Werror=implicit-function-declaration define Package/appfilter - SECTION:=TT Apps - CATEGORY:=TT Apps - DEPENDS:=+libubox +libubus +libuci +libpthread +libjson-c +libblobmsg-json - TITLE:=OAF client and config service + SECTION:=Base system + CATEGORY:=Base system + DEPENDS:=+libubox +libubus +libuci +libpthread +libjson-c +libblobmsg-json +libuci-lua +libsqlite3 +libcurl + TITLE:=OAF service endef define Build/Prepare @@ -25,7 +25,7 @@ define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR)/ \ CC="$(TARGET_CROSS)gcc" \ CFLAGS="$(TARGET_CFLAGS)" \ - LIBS="$(TARGET_LDFLAGS) -lm -lpthread -lubox -luci -lubus -ljson-c -lblobmsg_json" \ + LIBS="$(TARGET_LDFLAGS) -lm -lpthread -lubox -luci -lubus -ljson-c -lblobmsg_json -lsqlite3 -lcurl" \ all endef @@ -35,28 +35,29 @@ define Build/Compile/Default endef define Package/appfilter/description - openappfilter app + OAF service endef define Package/appfilter/conffiles -/etc/config/appfilter -/etc/config/user_info +/etc/fwxd/custom_feature.cfg +/etc/config/fwx endef + define Package/appfilter/install $(INSTALL_DIR) $(1)/usr/bin $(1)/etc/init.d - $(INSTALL_DIR) $(1)/etc/appfilter + $(INSTALL_DIR) $(1)/etc/fwxd $(INSTALL_DIR) $(1)/etc/config - $(CP) ./files/*.cfg $(1)/etc/appfilter/ + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_DATA) ./files/feature.bin $(1)/etc/fwxd/feature.bin + $(INSTALL_DATA) ./files/custom_feature.cfg $(1)/etc/fwxd/custom_feature.cfg $(INSTALL_BIN) ./files/appfilter.init $(1)/etc/init.d/appfilter - $(INSTALL_BIN) ./files/oaf_rule $(1)/usr/bin - $(INSTALL_BIN) ./files/gen_class.sh $(1)/usr/bin - $(INSTALL_DATA) ./files/appfilter.config $(1)/etc/config/appfilter - $(INSTALL_DATA) ./files/user_info.config $(1)/etc/config/user_info $(INSTALL_BIN) $(PKG_BUILD_DIR)/oafd $(1)/usr/bin - $(INSTALL_BIN) ./files/hnat.sh $(1)/usr/bin + $(INSTALL_BIN) ./files/rule_manager.lua $(1)/usr/bin/rule_manager + $(INSTALL_BIN) ./files/hnat.sh $(1)/usr/bin/hnat.sh + $(INSTALL_DATA) ./files/oaf_version $(1)/etc/oaf_version + $(INSTALL_DATA) ./files/luci-app-oaf.json $(1)/usr/share/rpcd/acl.d/ endef $(eval $(call BuildPackage,appfilter)) - diff --git a/open-app-filter/files/appfilter.config b/open-app-filter/files/appfilter.config index b95c6b7b..b28b04f6 100644 --- a/open-app-filter/files/appfilter.config +++ b/open-app-filter/files/appfilter.config @@ -1,36 +1,3 @@ -config global global - option enable '0' - option work_mode '0' - option record_enable '1' - option disable_hnat '0' - option tcp_rst '1' - option lan_ifname 'br-lan' - option auto_load_engine '1' - option disable_quic '0' - -config appfilter appfilter - -config feature feature - option update 0 - option format 'v3.0' - -config time 'time' - option deny_time '60' - option start_time '00:00' - option end_time '23:59' - option allow_time '20' - option time_mode '0' - option days '1 2 3 4 5 6 0' - list time '00:00-23:59' - option daily_limit_0 '0:0:0' - option daily_limit_1 '0:0:0' - option daily_limit_2 '0:0:0' - option daily_limit_3 '0:0:0' - option daily_limit_4 '0:0:0' - option daily_limit_5 '0:0:0' - option daily_limit_6 '0:0:0' - -config user user -config rule 'rule' \ No newline at end of file + diff --git a/open-app-filter/files/appfilter.init b/open-app-filter/files/appfilter.init old mode 100755 new mode 100644 index f5b4de75..e309e46b --- a/open-app-filter/files/appfilter.init +++ b/open-app-filter/files/appfilter.init @@ -1,27 +1,112 @@ #!/bin/sh /etc/rc.common +. /usr/share/libubox/jshn.sh +. /lib/functions.sh START=96 USE_PROCD=1 -OAFD_BIN="/usr/bin/oafd" -FEATURE_FILE="/tmp/feature.cfg" +FWXD_BIN="/usr/bin/oafd" +CUSTOM_FEATURE_FILE="/etc/fwxd/custom_feature.cfg" +LEGACY_CUSTOM_FEATURE_FILE="/etc/custom_feature.cfg" + +ensure_config_file() +{ + local file="$1" + + [ -e "$file" ] && return + mkdir -p /etc/config + cat > "$file" + chmod 0644 "$file" +} + +ensure_default_configs() +{ + ensure_config_file /etc/config/appfilter <<'EOF' +EOF + + ensure_config_file /etc/config/appfilter_whitelist <<'EOF' +EOF + + ensure_config_file /etc/config/fwx <<'EOF' +config global global + option lan_ifname 'br-lan' + option tcp_rst '1' + option theme_mode '1' + option feature_token '' + +config appfilter appfilter + option enable 1 + +config macfilter macfilter + option enable 1 + +config record 'record' + option enable '1' + option record_time '3' + option app_valid_time '3' + option history_data_size '10' + option history_data_path '/tmp/oaf' + option base_data_path '/tmp/oaf' + +config network network + option work_mode 0 + +config dashboard 'dashboard' + +config advanced 'advanced' + option disable_hnat '0' + +config status 'status' + option notice_status '0' +EOF + + ensure_config_file /etc/config/fwx_record <<'EOF' +config whitelist 'whitelist' +EOF + + ensure_config_file /etc/config/macfilter <<'EOF' +config global 'global' + option enable '1' +EOF + + ensure_config_file /etc/config/macfilter_whitelist <<'EOF' +EOF + + ensure_config_file /etc/config/mac_blacklist <<'EOF' +config settings 'base' +EOF + + ensure_config_file /etc/config/user_info <<'EOF' +EOF +} stop_service(){ killall -9 oafd + killall -9 rule_manager } start_service(){ - test -f $FEATURE_FILE &&{ - rm $FEATURE_FILE - } + ensure_default_configs - if [ ! -f /etc/appfilter/feature.cfg ]; then - cp /etc/appfilter/feature_cn.cfg /etc/appfilter/feature.cfg + lsmod |grep "oaf" >/dev/null + if [ $? -ne 0 ];then + modprobe oaf + fi + + [ -x /usr/bin/hnat.sh ] && /usr/bin/hnat.sh 1 + + if [ -s "$LEGACY_CUSTOM_FEATURE_FILE" ] && [ ! -s "$CUSTOM_FEATURE_FILE" ]; then + mv -f "$LEGACY_CUSTOM_FEATURE_FILE" "$CUSTOM_FEATURE_FILE" + chmod 0644 "$CUSTOM_FEATURE_FILE" fi - hnat.sh 1 - ln -s /etc/appfilter/feature.cfg $FEATURE_FILE procd_open_instance procd_set_param respawn 60 5 5 procd_set_param stderr 1 - procd_set_param command "$OAFD_BIN" + procd_set_param command "$FWXD_BIN" + procd_close_instance + + procd_open_instance + procd_set_param respawn 60 5 5 + procd_set_param stderr 1 + procd_set_param command "/usr/bin/rule_manager" procd_close_instance } diff --git a/open-app-filter/files/appfilter_whitelist.config b/open-app-filter/files/appfilter_whitelist.config new file mode 100644 index 00000000..e69de29b diff --git a/open-app-filter/files/custom_feature.cfg b/open-app-filter/files/custom_feature.cfg new file mode 100644 index 00000000..e69de29b diff --git a/open-app-filter/files/feature.bin b/open-app-filter/files/feature.bin new file mode 100644 index 00000000..58c0fb37 Binary files /dev/null and b/open-app-filter/files/feature.bin differ diff --git a/open-app-filter/files/feature.cfg b/open-app-filter/files/feature.cfg deleted file mode 100644 index a8ea0b82..00000000 --- a/open-app-filter/files/feature.cfg +++ /dev/null @@ -1,256 +0,0 @@ -#version v22.3.24 -#format v3.0 -#id name:[proto;sport;dport;host url;request;dict] -#class chat 1 聊天 -1003 微博:[tcp;;;weibo;;] -1004 陌陌:[tcp;;;momo;;,tcp;;;;;04:2f|05:66|06:65|07:65;;1,tcp;;;;;00:03|01:03|02:00;;1] -1005 支付宝:[tcp;;;alipay.com;;,tcp;;;alipayobjects.com;;,tcp;;;alive.alipay.com;;,udp;;1100-1200;;;00:00|01:01|02:00,tcp;;80;;;00:50|01:52|02:49] -1006 钉钉:[tcp;;;dingtalk;;,tcp;;;;d?host=;,tcp;;;;/man/api;] -1007 Soul:[tcp;;;soulapp;;] -1008 伊对:[tcp;;;520yidui;;] -1009 探探:[tcp;;;tancdn;;,tcp;;;tantanapp;;] -1010 多闪:[tcp;;;ppkankan;;] -1012 Instagram:[tcp;;;instagram;;] -1013 Facebook:[tcp;;;facebook;;] -1014 WhatsApp:[tcp;;;whatsapp;;] - -#class game 2 游戏 -2001 王者荣耀:[tcp;;6000-9000;;;00:33|1:66|02:00|03:0a] -2027 英雄联盟手游:[tcp;;10001;;;00:33|1:66|02:00|03:0b,tcp;;11001;;;00:33|1:66|02:00|03:0b] -2005 欢乐斗地主:[tcp;;12000;;;00:43|01:66|02:aa|03:00,tcp;;;huanle.qq.com;;] -2015 我的世界:[tcp;;443;g79mclobt.nie.netease;;,tcp;;443;x19.*.netease.com;;,tcp;;443;mc.*.netease;;] -2006 梦幻西游:[tcp;;;;;00:0e|01:00|02:fe|03:ff,tcp;;;g18.proxima.nie;;] -2007 明日之后:[tcp;;12500-14000;;;00:02|01:00|02:00|03:00|04:00|05:00,tcp;;;g66.update.netease;;] -2008 QQ飞车:[udp;;;;;00:28|01:28,tcp;;10000;;;00:33|01:66|02:00|03:08] -2009 跑跑卡丁车:[tcp;;8888;;;00:33|01:66|02:00|03:0b,tcp;;49150-49159;;wepop/;] -2010 开心消消乐:[tcp;;80;happyelements;;] -2011 狂野飙车:[tcp;;;asphalt9;;] -2012 率土之滨:[tcp;;10001;;;00:00|01:00,tcp;;8001;;;00:00|01:00] -2013 一刀传世:[tcp;;8040;;;00:47|01:45] -2014 第五人格:[tcp;;;h55.proxima;;,tcp;;;h55.update;;] -2016 皇室战争:[udp;;9339;;;] -2017 炉石传说:[tcp;;3724;;;00:73:01:00:02:00] -2023 原神:[tcp;;443;yuanshen.com;;] -2025 天涯明月刀:[tcp;;10000;;;00:43|01:66|02:aa] -2026 微信小游戏:[tcp;;443;mmgame;;,tcp;;443;game.weixin.qq;;] -2033 我叫MT4:[tcp;;21248;;;,tcp;;;dir.mt4.qq.com;;] -2034 神都夜行录:[udp;;;;;00:00|01:00|02:00|03:00|04:56|05:40] -2041 光遇:[udp;;10000-15000;;;00:8f|01:ff,tcp;;;ma75.update.netease.com;;,tcp;;;ma75.proxima.nie.netease;;] -2042 保卫萝卜4:[tcp;;;s4.luobo.cn;;] -2040 哈利波特:[tcp;;10021-12000;;;00:02|01:00|02:00|03:00|04:00|05:00,tcp;;443;g92.proxima;;] -2067 9377游戏:[tcp;;;www.9377.com;;] -2068 4399游戏:[tcp;;;4399.com;;] -2069 7k7k游戏:[tcp;;;7k7k.com;;] -2070 17173游戏:[tcp;;;17173.com;;] -2071 37网游:[tcp;;;37.com;;] -2074 hao123游戏:[tcp;;;game.hao123.com;;] -2075 51游戏:[tcp;;;www.51.com;;] -2050 uu加速器:[tcp;;;mg.uu.163.com;;] -2051 腾讯加速器:[tcp;;;m.acc.qq.com;;] -2080 乐逗游戏:[tcp;;;.uu.cc;;] - -#class video 3 视频 -3001 抖音:[tcp;;;-dy-;;,tcp;;;-dy.;;,tcp;;;douyin;;,tcp;;;amemv.com;;,tcp;;;pstatp.com;;,tcp;;;volcsirius.com;;,tcp;;80;;^/pull.*.douyincdn.com;,tcp;;;ecombdapi.com;;,udp;;443;;;09:51|10:30|11:34;amemv.com;0,udp;;16000;;;00:00|01:01,udp;;1000-2000;;;00:00|01:01] -3049 Youtube:[tcp;;;youtube;;] -3006 斗鱼:[tcp;;;douyu;;,tcp;;;douyu;;-2:2f|-1:00] -3004 爱奇艺:[tcp;;;iqiyi;;,tcp;;;qy.net;;,tcp;;;inter.71edge.com;;,tcp;;;;^/videos;,tcp;;80;;;00:51|01:48|02:54,tcp;;80;;;09:00|10:00|11:00] -3043 Netflix:[tcp;;;netflix.com;;] -3008 虎牙直播:[tcp;;;huya;;,udp;;;;;01:00|02:00|03:00|04:23,udp;;;;;01:00|02:00|03:00|04:24] -3010 小红书:[tcp;;;xiaohongshu;;,tcp;;;xhscdn;;] -3011 花椒直播:[tcp;;;huajiao;;] -3012 映客直播:[tcp;;;;.inke.cn;] -3016 芒果tv:[tcp;;443;mgtv;;,tcp;;80;mgtv;;,tcp;;443;hitv;;] -3017 西瓜视频:[tcp;;;ixigua;;,tcp;;443;snsdk;;,tcp;;;xg-p.ixigua;;,tcp;;;bdxigua;;] -3018 搜狐视频:[tcp;;;aty.sohu.com;;,tcp;;;tv.itc.cn;;] -3020 咪咕视频:[tcp;;;miguvideo;;,tcp;;;migu.cn;;] -3022 人人视频:[tcp;;;rr.tv;;] -3023 央视影音:[tcp;;;cntv;;] -3024 优酷&酷喵:[tcp;;;youku;;,tcp;;;ykimg;;,tcp;;;;/youku;,tcp;;;galitv.alicdn.com;;,tcp;;;cibntv.;;,tcp;;;miaozhen.com;;;;1] -3025 最右:[tcp;;;izuiyou;;] -3026 风行视频:[tcp;;;funshion;;] -3019 播聊:[tcp;;80;randlove.cn;;,tcp;;;yueliao;;,tcp;;;5glive;;] -3021 韩剧TV:[tcp;;;hanju.koudaibaobao;;] -3027 企鹅电竞:[tcp;;;egame.qq;;,tcp;;;liveplay;;,tcp;;;;pggame;] -3028 波波视频:[tcp;;;miaopai;;] -3029 酷狗短酷:[tcp;;;bssdl.kugou;;] -3030 酷狗直播:[tcp;;;rt-m.kugou;;,tcp;;;kgimg.com;;] -3023 央视影音:[tcp;;;cntv;;] -3026 风行视频:[tcp;;;funshion;;] -3089 华数TV:[tcp;;;wasu.cn;;] -3121 梨视频:[tcp;;;pearvideo.com;;] -3094 南瓜电影:[tcp;;;vcinema.cn;;] -3084 六间房:[tcp;;;v.6.cn;;] -3085 百度直播:[tcp;;;live.baidu.com;;] -3086 度小视:[tcp;;;quanmin.baidu.com;;] - -#class shopping 4 购物 -4001 淘宝:[tcp;;;taobao;;,tcp;;;alicdn.com;;,tcp;;;tmall.com;;,tcp;;443;;;00:d1|01:00,tcp;;443;;;00:d2|01:00,tcp;;443;;;00:d3|01:00,tcp;;443;;;00:d4|01:00,tcp;;443;;;00:d5|01:00,tcp;;443;;;00:b1|01:00,tcp;;443;;;00:b2|01:00,tcp;;443;;;00:b3|01:00,tcp;;443;;;00:b4|01:00,tcp;;443;;;00:b5|01:00,udp;;1000-1200;;;00:82|01:cc,tcp;;;;/mediaplatform;] -4002 京东:[tcp;;;360buyimg;;,tcp;;;jd.com;;,tcp;;;jdcdn.com;;,tcp;;;vod.300hu.com;;] -4003 唯品会:[tcp;;;vips-mobile;;,tcp;;;vipshop;;,tcp;;;vip.com;;,tcp;;;vipstatic.com;;,tcp;;;appsimg.com;;] -4004 拼多多:[tcp;;;pinduoduo;;,tcp;;;yangkeduo.com;;,tcp;;;s1p.cdntip.com;;] -4010 饿了么:[tcp;;;eleme;;] -4012 闲鱼:[tcp;;;xianyu;;] -4021 转转:[tcp;;;zhuanzhuan;;,tcp;;;zhuanstatic;;] -4005 蘑菇街:[tcp;;;mogujie;;,tcp;;;mogucdn;;,tcp;;;;;00:73|01:ea|02:68|03:fb|04:3f] -4006 苏宁易购:[tcp;;;.suning.;;] -4007 当当网:[tcp;;;.dangdang.com;;] -4008 1号店:[tcp;;;.yhd.com;;] -4009 朴朴超市:[tcp;;;pupumall;;,tcp;;;pupuapi;;] -4013 叮咚买菜:[tcp;;;ddxq.mobi;;] -4014 小米有品:[tcp;;;youpin;;,tcp;;;shopapi.io.mi.com;;] -4015 微店:[tcp;;;weidian;;] -4016 折800:[tcp;;;zhe800.com;;] -4018 好省:[tcp;;;hzhstb.com;;] -4019 什么值得买:[tcp;;;smzdm.com;;] -4022 网易严选:[tcp;;;yanxuan;;] -4023 识货:[tcp;;;shihuo;;] -4024 考拉海购:[tcp;;;kaola;;] -4025 宜家家居:[tcp;;;ikea.cn;;] -4026 小象优品:[tcp;;;xiaoxiangyoupin;;] -4040 国美:[tcp;;;gome.com;;] -4041 酒仙网:[tcp;;;jiuxian.com;;] -4052 1688:[tcp;;;1688.com;;] -4053 亚马逊:[tcp;;;amazon.cn;;] -4054 Lazada:[tcp;;;lazada.com;;] - -#class music 5 音乐 -5001 网易云音乐:[tcp;;;music.163;;,tcp;;;music.126;;] -5002 QQ音乐:[tcp;;;;^/amobile.music.tc.qq.com;,tcp;;;qqmusic;;] -5003 酷狗音乐:[tcp;;;kugou;;,tcp;;;kgimg;;,tcp;;;fanxing;;] -5004 酷我音乐:[tcp;;;.kuwo.cn;;] -5005 喜马拉雅:[tcp;;;.ximalaya.com;;] -5006 千千音乐:[tcp;;;music.taihe.com;;] -5007 虾米音乐:[tcp;;;xiami;;] -5008 音悦台:[tcp;;;yinyuetai.com;;] -5009 豆瓣FM:[tcp;;;douban.fm;;] -5010 唱吧:[tcp;;;changba.com;;] -5011 音乐随心听:[tcp;;;fm.taihe.com;;] -5012 懒人听书:[tcp;;;lrts.me;;] - -#class employee 6 招聘 -6001 前程无忧:[tcp;;;51job;;] -6002 智联招聘:[tcp;;;zhaopin;;] -6003 猎聘:[tcp;;;liepin;;] -6004 赶集网:[tcp;;;58.com;;,tcp;;;58cdn;;] -6005 同城急聘:[tcp;;;xiaomei;;] -6006 领英:[tcp;;;linkedin;;] -6007 斗米:[tcp;;;doumi;;] -6008 看准:[tcp;;;kanzhun.com;;] -6009 应届生求职:[tcp;;;yingjiesheng.com;;] -6010 中华英才网:[tcp;;;chinahr.com;;] -6011 拉勾网:[tcp;;;lagou.com;;] -6012 大街网:[tcp;;;dajie.com;;] -6013 boss直聘:[tcp;;;zhipin.com;;] -6014 实习僧:[tcp;;;shixiseng.com;;] - -#class download 7 下载 -7002 AppStore:[tcp;;;itunes.apple.com;;] -7004 ftp文件传输:[tcp;;21;;;] -7005 vivo应用商店:[tcp;;443;appstore.vivo;;,tcp;;443;apkappdefwsdl.vivo;;] -7006 王者荣耀更新:[tcp;;80;;/sgame/;] -7007 天翼云盘:[tcp;;;ctyunapi;;] -7008 腾讯微云:[tcp;;;weiyun.com;;,tcp;;;aegis.qq.com;;,tcp;;;pingtas.qq.com;;,tcp;;443;;;00:77|01:6e|02:73] -7009 坚果云:[tcp;;;jianguoyun;;] -7010 蓝奏云:[tcp;;;pan.lanzou.com;;] -7011 华为云:[tcp;;;cloud.huawei.com;;,tcp;;;hicloud.com;;,tcp;;;myhuaweicloud.cn;;] -7020 windows更新:[tcp;;80;update.microsoft.com;;,tcp;;;windowsupdate.com;;] -7030 向日葵:[tcp;;;oray.com;;,tcp;;;oray.net;;] -7031 TeamViewer:[tcp;;;teamviewer;;] -7032 阿里云盘:[tcp;;;aliyundrive;;] - - -#class website 8 常用网站 -8079 Google:[tcp;;;google.com;;] -8001 百度:[tcp;;;www.baidu.com;;,tcp;;;m.baidu.com;;] -8112 Apple:[tcp;;;www.apple.com;;,tcp;;;m.apple.com;;] -8002 新浪:[tcp;;;www.sina.com;;,tcp;;;m.sina.com;;] -8003 搜狐:[tcp;;;www.sohu.com;;,tcp;;;m.sohu.com;;] -8004 网易:[tcp;;;www.163.com;;,tcp;;443;www.126.com;;] -8005 凤凰网:[tcp;;;ifeng.com;;] -8009 hao123:[tcp;;;www.hao123.com;;,tcp;;;m.hao123.com;;] -8010 2345:[tcp;;;www.2345.com;;,tcp;;;m.2345.com;;] -8006 人民网:[tcp;;;people.com.cn;;] -8008 中华网:[tcp;;;www.china.com;;] -8020 天涯社区:[tcp;;;tianya.cn;;] -8026 穷游网:[tcp;;;qyer.com;;] -8027 驴妈妈:[tcp;;;lvmama.com;;] -8029 太平洋汽车:[tcp;;;pcauto.com.cn;;] -8030 易车网:[tcp;;;bitauto.com;;] -8031 爱卡汽车:[tcp;;;xcar.com.cn;;] -8035 和讯:[tcp;;;hexun.com;;] -8036 第一财经:[tcp;;;yicai.com;;] -8037 全景网:[tcp;;;p5w.net;;] -8038 中彩网:[tcp;;;zhcw.com;;] -8039 体育彩票:[tcp;;;lottery.gov.cn;;] -8041 豆丁:[tcp;;;docin.com;;] -8044 缤客:[tcp;;;booking.com;;] -8046 猫扑:[tcp;;;mop.com;;] -8064 潇湘书院:[tcp;;;xxsy.net;;] -8065 cctv5:[tcp;;;sports.cctv.com;;] -8066 虎扑体育:[tcp;;;hupu.com;;] -8077 知网:[tcp;;;www.cnki.net;;] -8087 github:[tcp;;;github.com;;] -8089 gitee:[tcp;;;gitee.com;;] -8090 必应:[tcp;;;bing.com;;] -8092 中国福利彩:[tcp;;;www.cwl.gov.cn;;] -8093 新浪彩票:[tcp;;;lottery.sina.com.cn;;] -8094 竞彩网:[tcp;;;www.sporttery.cn;;] -8096 新浪体育:[tcp;;;sports.sina.com.cn;;] -8098 小米官网:[tcp;;;www.mi.com;;] -8099 BBC:[tcp;;;www.bbc.com;;] -8100 腾讯智影:[tcp;;;zenvideo.qq.com;;] -8103 IT之家:[tcp;;;www.ithome.com;;] -8104 太平洋电脑:[tcp;;;www.pconline.com.cn;;] -8105 中国移动:[tcp;;;www.10086.cn;;] -8106 中国联通:[tcp;;;www.10010.com;;] -8107 中国电信:[tcp;;;www.189.cn;;] -8108 华为商城:[tcp;;;www.vmall.com;;] -8110 vivo官网:[tcp;;;www.vivo.com.cn;;] -8111 华为官网:[tcp;;;www.huawei.com;;] - - -#class life 10 生活 -10003 京东钱包:[tcp;;;jdpay.com;;] -10034 饿了么:[tcp;;;eleme.com;;] -10035 美团:[tcp;;;meituan;;] -10004 豆瓣:[tcp;;;douban.com;;] -10005 知乎:[tcp;;;zhihu.com;;] -10006 链家:[tcp;;;lianjia.com;;] -10007 天眼查:[tcp;;;tianyancha.com;;] -10008 有道词典:[tcp;;;dict.youdao.com;;] -10010 萤石云:[tcp;;;ys7.com;;,udp;;;;;00:e2|01:62|02:0c,tcp;;11001;;;00:24|01:0a] -10011 掌阅:[tcp;;;ireader.com;;,tcp;;;zhangyue;;] -10012 安居客:[tcp;;;anjuke.com;;] -10013 房天下:[tcp;;;fang.com;;] -10014 58同城:[tcp;;;58.com;;] -10015 动漫之家:[tcp;;;dmzj.com;;] -10016 汽车之家:[tcp;;;autohome.com.cn;;] -10017 飞猪:[tcp;;;fliggy.com;;] -10018 12306:[tcp;;;12306.cn;;] -10019 马蜂窝:[tcp;;;mafengwo.cn;;] -10020 途牛:[tcp;;;tuniu.com;;] -10021 小爱音箱:[tcp;;;ai.xiaomi.com;;,tcp;;;mina.mi.com;;] -10022 搜狗拼音:[tcp;;;pinyin.sogou.com;;]:5:1 - - -#class finance 14 金融 -14001 建设银行:[tcp;;;ccb.com;;] -14002 农业银行:[tcp;;;abchina.com;;] -14003 中国银行:[tcp;;;boc.cn;;] -14004 交通银行:[tcp;;;bankcomm.com;;] -14005 招商银行:[tcp;;;cmbchina.com;;] -14006 邮政储蓄:[tcp;;;psbc.com;;] -14007 兴业银行:[tcp;;;cib.com.cn;;] -14008 浦发银行:[tcp;;;spdb.com.cn;;] -14009 中信银行:[tcp;;;citicbank.com;;] -14010 上海银行:[tcp;;;bosc.cn;;] -14011 平安银行:[tcp;;;pingan.com.cn;;] -14012 人民银行:[tcp;;;pbc.gov.cn;;] -14013 北京银行:[tcp;;;bankofbeijing;;] -14014 银联在线:[tcp;;;95516.com;;] - - -#class tools 11 工具 -11001 samba共享:[tcp;;445;;;] -11002 ftp文件传输:[tcp;;21;;;] -11003 SSH:[tcp;;;;;00:53|01:53|02:48] \ No newline at end of file diff --git a/open-app-filter/files/feature_cn.cfg b/open-app-filter/files/feature_cn.cfg deleted file mode 100644 index a9c4ac57..00000000 --- a/open-app-filter/files/feature_cn.cfg +++ /dev/null @@ -1,234 +0,0 @@ -#version v22.3.24 -#format v2.0 -#id name:[proto;sport;dport;host url;request;dict] -#class chat 1 聊天 -1003 微博:[tcp;;443;weibo;;] -1004 陌陌:[tcp;;;momo;;,tcp;;;;;04:2f|05:66|06:65|07:65,tcp;;;;;00:03|01:03|02:00] -1005 支付宝:[tcp;;443;alipay.com;;] -1006 钉钉:[tcp;;;dingtalk;;,tcp;;;;d?host=;,tcp;;;;/man/api;,tcp;;;;/beacon;] -1007 Soul:[tcp;;;soulapp;;] -1008 伊对:[tcp;;;520yidui;;] -1009 探探:[tcp;;;tancdn;;,tcp;;;tantanapp;;] -1010 多闪:[tcp;;;ppkankan;;] - -#class game 2 游戏 -2001 王者荣耀:[tcp;;;;;00:33|1:66|02:00|03:0b] -2002 和平精英:[tcp;;17500;;;00:33|1:66|03:0a|05:0a] -2003 英雄联盟手游:[tcp;;;;;00:33|01:66|02:00|03:0b,tcp;;443;;;00:01|01:00|10:86|11:47] -2015 我的世界:[tcp;;443;g79mclobt.nie.netease;;] -2005 欢乐斗地主:[tcp;;8000;;;00:74|01:67|02:77|03:5f] -2006 梦幻西游:[tcp;;;;;00:0e|01:00|02:fe|03:ff] -2007 明日之后:[udp;;;;;00:05|01:09|02:00,tcp;;;;;00:02|01:00|02:00|03:00|04:00|05:00] -2008 QQ飞车:[udp;;;;;00:28|01:28,tcp;;10000;;;00:33|01:66|02:00|03:08] -2009 跑跑卡丁车:[tcp;;8888;;;00:33|01:66|02:00|03:08] -2010 开心消消乐:[tcp;;80;happyelements;;] -2011 狂野飙车:[tcp;;;asphalt9;;] -2012 率土之滨:[tcp;;10001;;;00:00|01:00,tcp;;8001;;;00:00|01:00] -2013 一刀传世:[tcp;;8040;;;00:47|01:45] -2014 第五人格:[tcp;;4010;;;,tcp;;4010;;;,tcp;;4020;;;,tcp;;4030;;;,tcp;;4040;;;,tcp;;4050;;;,tcp;;4060;;;,tcp;;4070;;;,tcp;;4080;;;,tcp;;4090;;;] -2016 皇室战争:[udp;;9339;;;] -2017 炉石传说:[tcp;;3724;;;00:73:01:00:02:00] -2023 原神:[tcp;;443;yuanshen.com;;] -2025 天涯明月刀:[tcp;;10000;;;00:43|01:66|02:aa] -2026 微信小游戏:[tcp;;443;mmgame;;,tcp;;443;game.weixin.qq;;] -2033 我叫MT4:[tcp;;21248;;;,tcp;;;dir.mt4.qq.com;;] -2034 神都夜行录:[udp;;;;;00:00|01:00|02:00|03:00|04:56|05:40] -2041 光遇:[udp;;10000-15000;;;00:8f|01:ff,tcp;;;ma75.update.netease.com;;,tcp;;;ma75.proxima.nie.netease;;] -2042 保卫萝卜4:[tcp;;;s4.luobo.cn;;] -2040 哈利波特:[tcp;;10021-12000;;;00:02|01:00|02:00|03:00|04:00|05:00,tcp;;443;g92.proxima;;] -2067 9377游戏:[tcp;;;www.9377.com;;] -2068 4399游戏:[tcp;;;4399.com;;] -2069 7k7k游戏:[tcp;;;7k7k.com;;] -2070 17173游戏:[tcp;;;17173.com;;] -2071 37网游:[tcp;;;37.com;;] -2072 游民星空:[tcp;;;gamersky.com;;] -2073 游侠网:[tcp;;;ali213.net;;] -2074 hao123游戏:[tcp;;;game.hao123.com;;] -2075 51游戏:[tcp;;;www.51.com;;] -2050 uu加速器:[tcp;;;mg.uu.163.com;;] -2051 腾讯加速器:[tcp;;;m.acc.qq.com;;] -2080 乐逗游戏:[tcp;;;.uu.cc;;] - -#class video 3 视频 -3001 抖音短视频:[tcp;;;-dy-;;,tcp;;;-dy.;;,tcp;;;douyin;;] -3002 火山小视频:[tcp;;;.huoshan.com;;,tcp;;;hs.pstatp.com;;,tcp;;;hs.ixigua.com;;] -3003 腾讯视频:[tcp;;443;v.qq.com;;,tcp;;443;video.qq.com;;,tcp;;443;btrace.qq.com;;] -3004 爱奇艺:[tcp;;;iqiyi;;,tcp;;;qy.net;;] -3005 微视:[tcp;;80;;;00:34|01:16|02:75,tcp;;80;weishi.qq.com;;] -3006 斗鱼直播:[tcp;;;douyu;;,tcp;;;douyu;;-2:2f|-1:00] -3008 虎牙直播:[tcp;;;huya;;,udp;;;;;01:00|02:00|03:00|04:23,udp;;;;;01:00|02:00|03:00|04:24] -3009 快手:[tcp;;;kuaishou;;,tcp;;;ksyuncdn.com;;,tcp;;;.gifshow.com;;,tcp;;;yximgs.com;;,tcp;;80;;/ksc;,tcp;;;kwaicdn;;,tcp;;;kwimgs;;] -3010 小红书:[tcp;;;xiaohongshu;;,tcp;;;xhscdn;;] -3011 花椒直播:[tcp;;;huajiao;;] -3012 映客直播:[tcp;;;;.inke.cn;] -3013 YY:[udp;;;;;02:00|03:00|04:08,udp;;;;;00:4f|01:00|02:00] -3014 哔哩哔哩:[tcp;;;bilivideo;;,tcp;;;bilibili.com;;,tcp;;;;;00:47|05:75|06:70|07:67,tcp;;;;/bfs/emote/;,,tcp;;;hdslb.com;;] -3016 芒果tv:[tcp;;443;mgtv;;,tcp;;80;mgtv;;,tcp;;443;hitv;;] -3017 西瓜视频:[tcp;;;ixigua;;,tcp;;443;snsdk;;,tcp;;;xg-p.ixigua;;,tcp;;;bdxigua;;] -3018 搜狐视频:[tcp;;;aty.sohu.com;;,tcp;;;tv.itc.cn;;] -3019 播聊:[tcp;;80;randlove.cn;;,tcp;;;yueliao;;,tcp;;;5glive;;] -3020 咪咕视频:[tcp;;;miguvideo;;,tcp;;;migu.cn;;] -3021 韩剧TV:[tcp;;;hanju.koudaibaobao;;] -3022 人人视频:[tcp;;;rr.tv;;] -3023 央视影音:[tcp;;;cntv;;] -3024 土豆视频:[tcp;;;youku;;,tcp;;;ykimg;;] -3025 最右:[tcp;;;izuiyou;;] -3026 风行视频:[tcp;;;funshion;;] -3027 企鹅电竞:[tcp;;;egame.qq;;,tcp;;;liveplay;;,tcp;;;;pggame;] -3028 波波视频:[tcp;;;miaopai;;] -3029 酷狗短酷:[tcp;;;bssdl.kugou;;] -3030 酷狗直播:[tcp;;;rt-m.kugou;;,tcp;;;kgimg.com;;] - -#class shopping 4 购物 -4001 淘宝:[tcp;;;taobao;;,tcp;;;alicdn.com;;,tcp;;;tmall.com;;,tcp;;;;;00:d3|01:00,,tcp;;;;;00:d4|01:00,,tcp;;;;;00:d3|01:00] -4002 京东:[tcp;;;360buyimg;;,tcp;;;jd.com;;,tcp;;;jdcdn.com;;,tcp;;;;;00:d5|01:00] -4003 唯品会:[tcp;;;vips-mobile;;,tcp;;;vipshop;;,tcp;;;vip.com;;,tcp;;;vipstatic.com;;,tcp;;;appsimg.com;;] -4004 拼多多:[tcp;;;pinduoduo;;,tcp;;;yangkeduo.com;;,tcp;;;s1p.cdntip.com;;] -4010 饿了么:[tcp;;;eleme;;] -4011 美团:[tcp;;;meituan;;] -4012 闲鱼:[tcp;;;xianyu;;] -4021 转转:[tcp;;;zhuanzhuan;;,tcp;;;zhuanstatic;;] -4005 蘑菇街:[tcp;;;mogujie;;,tcp;;;mogucdn;;,tcp;;;;;00:73|01:ea|02:68|03:fb|04:3f] -4006 苏宁易购:[tcp;;;.suning.;;] -4007 当当网:[tcp;;;.dangdang.com;;] -4008 1号店:[tcp;;;.yhd.com;;] -4009 朴朴超市:[tcp;;;pupumall;;,tcp;;;pupuapi;;] -4013 叮咚买菜:[tcp;;;ddxq.mobi;;] -4014 小米有品:[tcp;;;youpin;;,tcp;;;shopapi.io.mi.com;;] -4015 微店:[tcp;;;weidian;;] -4016 折800:[tcp;;;zhe800.com;;] -4017 HM:[tcp;;;www.hm.com;;,tcp;;;measurement.com;;] -4018 好省:[tcp;;;hzhstb.com;;] -4019 什么值得买:[tcp;;;smzdm.com;;] -4020 大众点评:[tcp;;;dianping.com;;] -4022 网易严选:[tcp;;;yanxuan;;] -4023 识货:[tcp;;;shihuo;;] -4024 考拉海购:[tcp;;;kaola;;] -4025 宜家家居:[tcp;;;ikea.cn;;] -4026 小象优品:[tcp;;;xiaoxiangyoupin;;] - -#class music 5 音乐 -5001 网易云音乐:[tcp;;;music.163;;,tcp;;;music.126;;] -5002 QQ音乐:[tcp;;;;^/amobile.music.tc.qq.com;,tcp;;;qqmusic;;] -5003 酷狗音乐:[tcp;;;kugou;;,tcp;;;kgimg;;,tcp;;;fanxing;;] -5004 酷我音乐:[tcp;;;.kuwo.cn;;] -5005 喜马拉雅:[tcp;;;.ximalaya.com;;] -5006 千千音乐:[tcp;;;music.taihe.com;;] -5007 虾米音乐:[tcp;;;xiami;;] -5008 音悦台:[tcp;;;yinyuetai.com;;] -5009 豆瓣FM:[tcp;;;douban.fm;;] -5010 唱吧:[tcp;;;changba.com;;] -5011 音乐随心听:[tcp;;;fm.taihe.com;;] -5012 懒人听书:[tcp;;;lrts.me;;] - -#class employee 6 招聘 -6001 前程无忧:[tcp;;;51job;;] -6002 智联招聘:[tcp;;;zhaopin;;] -6003 猎聘:[tcp;;;liepin;;] -6004 赶集网:[tcp;;;58.com;;,tcp;;;58cdn;;] -6005 同城急聘:[tcp;;;xiaomei;;] -6006 领英:[tcp;;;linkedin;;] -6007 斗米:[tcp;;;doumi;;] -6008 看准:[tcp;;;kanzhun.com;;] -6009 应届生求职:[tcp;;;yingjiesheng.com;;] -6010 中华英才网:[tcp;;;chinahr.com;;] -6011 拉勾网:[tcp;;;lagou.com;;] -6012 大街网:[tcp;;;dajie.com;;] -6013 boss直聘:[tcp;;;zhipin.com;;] -6014 实习僧:[tcp;;;shixiseng.com;;] - -#class download 7 下载 -7001 迅雷:[udp;12345;;;;,udp;15000;;;;,tcp;;54321;;;,tcp;;12345;;;,udp;6881;;;;,udp;;12346;;;,udp;12346;;;;] -7002 AppStore:[tcp;;;itunes.apple.com;;] HIDE:0 -7003 samba共享:[tcp;;445;;;] HIDE:0 -7004 ftp文件传输:[tcp;;21;;;] HIDE:0 -7005 vivo应用商店:[tcp;;443;appstore.vivo;;,tcp;;443;apkappdefwsdl.vivo;;] HIDE:0 -7006 王者荣耀更新:[tcp;;80;;/sgame/;] -7007 天翼云盘:[tcp;;;ctyunapi;;] -7008 腾讯微云:[tcp;;;weiyun.com;;,tcp;;;aegis.qq.com;;,tcp;;;pingtas.qq.com;;,tcp;;443;;;00:77|01:6e|02:73] -7009 坚果云:[tcp;;;jianguoyun;;] -7010 蓝奏云:[tcp;;;pan.lanzou.com;;] -7011 华为云:[tcp;;;cloud.huawei.com;;,tcp;;;hicloud.com;;,tcp;;;myhuaweicloud.cn;;] -7020 windows更新:[tcp;;80;update.microsoft.com;;,tcp;;;windowsupdate.com;;] -7030 向日葵:[tcp;;;oray.com;;,tcp;;;oray.net;;] -7031 TeamViewer:[tcp;;;teamviewer;;] -7032 阿里云盘:[tcp;;;aliyundrive;;] -7035 SSH:[tcp;;;;;00:53|01:53|02:48] - -#class website 8 常用网站 -8001 百度:[tcp;;;baidu.com;;] -8002 新浪:[tcp;;;sina.com;;] -8003 搜狐:[tcp;;;sohu.com;;] -8004 网易:[tcp;;;163.com;;,tcp;;443;126.com;;] -8005 凤凰网:[tcp;;;ifeng.com;;] -8006 人民网:[tcp;;;people.com.cn;;] -8007 凤凰网:[tcp;;;ifeng.com;;] -8008 中华网:[tcp;;;china.com;;] -8009 hao123:[tcp;;;hao123.com;;,] -8010 2345:[tcp;;;2345.com;;,] -8011 4399游戏:[tcp;;;4399.com;;] -8012 7k7k游戏:[tcp;;;7k7k.com;;] -8013 17173游戏:[tcp;;;17173.com;;] -8014 37网游:[tcp;;;37.com;;] -8015 游民星空:[tcp;;;gamersky.com;;] -8016 游侠网:[tcp;;;ali213.net;;] -8017 世纪佳缘:[tcp;;;jiayuan.com;;] -8018 珍爱网:[tcp;;;zhenai.com;;] -8019 百合网:[tcp;;;baihe.com;;] -8020 天涯社区:[tcp;;;tianya.cn;;] -8021 携程网:[tcp;;;ctrip.com;;] -8022 飞猪:[tcp;;;fliggy.com;;] -8023 12306:[tcp;;;12306.cn;;] -8024 马蜂窝:[tcp;;;mafengwo.cn;;] -8025 途牛:[tcp;;;tuniu.com;;] -8026 穷游网:[tcp;;;qyer.com;;] -8027 驴妈妈:[tcp;;;lvmama.com;;] -8028 同程旅游:[tcp;;;ly.com;;] -8029 太平洋汽车:[tcp;;;pcauto.com.cn;;] -8030 易车网:[tcp;;;bitauto.com;;] -8031 爱卡汽车:[tcp;;;xcar.com.cn;;] -8032 雪球:[tcp;;;xueqiu.com;;] -8033 东方财富:[tcp;;;eastmoney.com;;] -8034 证券之星:[tcp;;;stockstar.com;;] -8035 和讯:[tcp;;;hexun.com;;] -8036 第一财经:[tcp;;;yicai.com;;] -8037 全景网:[tcp;;;p5w.net;;] -8038 中彩网:[tcp;;;zhcw.com;;] -8039 中国体育彩票:[tcp;;;lottery.gov.cn;;] -8040 竞彩网:[tcp;;;sporttery.cn;;] -8041 豆丁:[tcp;;;docin.com;;] -8042 豆瓣:[tcp;;;douban.com;;] -8043 知乎:[tcp;;;zhihu.com;;] -8044 缤客:[tcp;;;booking.com;;] -8046 猫扑:[tcp;;;mop.com;;] -8047 赶集网:[tcp;;;ganji.com;;] -8048 安居客:[tcp;;;anjuke.com;;] -8049 房天下:[tcp;;;fang.com;;] -8050 链家:[tcp;;;lianjia.com;;] -8051 百姓网:[tcp;;;baixing.com;;] -8052 下厨房:[tcp;;;xiachufang.com;;] -8053 大众点评:[tcp;;;dianping.com;;] -8054 58同城:[tcp;;;58.com;;] -8055 天眼查:[tcp;;;tianyancha.com;;] -8056 千图网:[tcp;;;58pic.com;;] -8057 csdn社区:[tcp;;;csdn.net;;] -8058 有道词典:[tcp;;;dict.youdao.com;;] -8059 动漫之家:[tcp;;;dmzj.com;;] -8060 汽车之家:[tcp;;;autohome.com.cn;;] -8061 纵横中文网:[tcp;;;zongheng.com;;] -8062 起点中文网:[tcp;;;qidian.com;;] -8063 飞卢:[tcp;;;faloo.com;;] -8064 潇湘书院:[tcp;;;xxsy.net;;] -8065 cctv5:[tcp;;;sports.cctv.com;;] -8066 虎扑体育:[tcp;;;www.hupu.com;;] -8067 建设银行:[tcp;;;ccb.com;;] -8068 农业银行:[tcp;;;abchina.com;;] -8069 中国银行:[tcp;;;boc.cn;;] -8070 交通银行:[tcp;;;bankcomm.com;;] -8071 招商银行:[tcp;;;cmbchina.com;;] -8072 邮政储蓄:[tcp;;;psbc.com;;] -8073 兴业银行:[tcp;;;cib.com.cn;;] -8074 浦发银行:[tcp;;;spdb.com.cn;;] -8075 中信银行:[tcp;;;citicbank.com;;] -8076 上海银行:[tcp;;;bosc.cn;;] - diff --git a/open-app-filter/files/feature_en.cfg b/open-app-filter/files/feature_en.cfg deleted file mode 100644 index 35378746..00000000 --- a/open-app-filter/files/feature_en.cfg +++ /dev/null @@ -1,99 +0,0 @@ -#version v22.11.11 -#format v2.0 -#id name:[proto;sport;dport;host url;request;dict] -#class chat 1 Chat -1001 Facebook:[tcp;;;facebook.com;;] -1002 Whatsapp:[tcp;;;whatsapp;;] -1003 Twitter:[tcp;;;twitter.com;;] -1004 Instagram:[tcp;;;instagram.com;;] -1005 VK:[tcp;;;vk.com;;] -1006 Line:[tcp;;;line;;] -1007 Snapchat:[tcp;;;snapchat.com;;] -1008 Tinder:[tcp;;;tinder.com;;] - -#class video 3 Video -3001 YouTube:[tcp;;;youtube;;] -3002 Tiktok:[tcp;;;tiktok;;] -3003 NetFlix:[tcp;;;netflix;;] -3004 Vimeo:[tcp;;;vimeo;;] -3005 DailyMotion:[tcp;;;dailymotion;;] -3006 Hulu:[tcp;;;hulu;;] -3007 Vube:[tcp;;;vube;;] -3008 Twitch:[tcp;;;twitch;;] -3009 LiveLeak:[tcp;;;itemfix;;] -3010 Spotify:[tcp;;;spotify.com;;] -3050 Xvideos:[tcp;;;xvideos.com;;] -3051 Pornhub:[tcp;;;pornhub.com;;] -3052 Xnxx:[tcp;;;xnxx.com;;] - -#class shopping 4 Shopping -4001 Amazon:[tcp;;;amazon.com;;] -4002 eBay:[tcp;;;ebay.com;;] -4003 Etsy:[tcp;;;etsy.com;;] -4004 Wish:[tcp;;;wish.com;;] -4005 Alibaba:[tcp;;;alibaba;;] -4006 Aliexpress:[tcp;;;aliexpress.com;;] -4007 Walmart:[tcp;;;walmart.com;;] -4008 Sears:[tcp;;;sears.com;;] -4009 Kohls:[tcp;;;kohls.com;;] -4010 Costco:[tcp;;;costco.com;;] -4011 Asos:[tcp;;;asos.com;;] -4012 Cuyana:[tcp;;;cuyana.com;;] - -#class download 7 Download -7001 GooglePlay:[tcp;;;play.google.com;;] -7002 AppStore:[tcp;;;iosapps.itunes.apple.com;;] -7003 WindowsUpdate:[tcp;;80;update.microsoft.com;;,tcp;;;windowsupdate.com;;] -7050 Speedtest:[tcp;;;speedtest.net;;] -7060 samba:[tcp;;445;;;] -7061 ftp:[tcp;;21;;;] -7062 ssh:[tcp;;22;;;] - -#class website 8 Website -8001 Google:[tcp;;;www.google.com;;] -8002 Wiki:[tcp;;;wikipedia.com;;] -8003 Yahoo:[tcp;;;yahoo;;] -8004 Apple:[tcp;;;www.apple.com;;] -8010 Reddit:[tcp;;;reddit.com;;] -8011 Outlook:[tcp;;;outlook.live.com;;] -8012 Naver:[tcp;;;naver.com;;] -8013 Fandom:[tcp;;;fandom.com;;] -8015 Globo:[tcp;;;globo.com;;] -8016 Yelp:[tcp;;;yelp.com;;] -8017 Pinterest:[tcp;;;www.pinterest.com;;] -8018 BBC:[tcp;;;www.bbc.com;;] -8020 Linkedin:[tcp;;;linkedin.com;;] -8022 Merriam-webster:[tcp;;;merriam-webster.com;;] -8027 Dictionary:[tcp;;;dictionary.com;;] -8028 Tripadvisor:[tcp;;;tripadvisor.com;;] -8029 Britannica:[tcp;;;britannica.com;;] -8030 Cambridge:[tcp;;;cambridge.org;;] -8032 Weather:[tcp;;;weather.com;;] -8033 Wiktionary:[tcp;;;wiktionary.org;;] -8034 Espn:[tcp;;;espn.com;;] -8035 Microsoft:[tcp;;;microsoft.com;;] -8038 Gsmarena:[tcp;;;gsmarena.com;;] -8039 Webmd:[tcp;;;webmd.com;;] -8040 Craigslist:[tcp;;;craigslist.org;;] -8041 Cricbuzz:[tcp;;;cricbuzz.com;;] -8042 Mayoclinic:[tcp;;;mayoclinic.org;;] -8043 Timeanddate:[tcp;;;timeanddate.com;;] -8044 Espncricinfo:[tcp;;;espncricinfo.com;;] -8045 Healthline:[tcp;;;healthline.com;;] -8047 Rottentomatoes:[tcp;;;rottentomatoes.com;;] -8049 Thefreedictionary:[tcp;;;thefreedictionary.com;;] -8052 Bestbuy:[tcp;;;bestbuy.com;;] -8053 Indeed:[tcp;;;indeed.com;;] -8058 Samsung:[tcp;;;samsung.com;;] -8059 Investopedia:[tcp;;;investopedia.com;;] -8060 Flashscore:[tcp;;;flashscore.com;;] -8061 Steampowered:[tcp;;;steampowered.com;;] -8064 Roblox:[tcp;;;roblox.com;;] -8065 Nordstrom:[tcp;;;nordstrom.com;;] -8066 Thepiratebay:[tcp;;;thepiratebay.org;;] -8067 Indiatimes:[tcp;;;indiatimes.com;;] -8068 Cnbc:[tcp;;;cnbc.com;;] -8069 Ssyoutube:[tcp;;;ssyoutube.com;;] -8070 Adobe:[tcp;;;adobe.com;;] -8071 Speedtest:[tcp;;;speedtest.net;;] -8072 Lowes:[tcp;;;lowes.com;;] diff --git a/open-app-filter/files/fwx.config b/open-app-filter/files/fwx.config new file mode 100644 index 00000000..6fe9146f --- /dev/null +++ b/open-app-filter/files/fwx.config @@ -0,0 +1,30 @@ +config global global + option lan_ifname 'br-lan' + option tcp_rst '1' + option theme_mode '1' + option feature_token '' + +config appfilter appfilter + option enable 1 + +config macfilter macfilter + option enable 1 + +config record 'record' + option enable '1' + option record_time '3' + option app_valid_time '3' + option history_data_size '10' + option history_data_path '/tmp/oaf' + option base_data_path '/tmp/oaf' + +config network network + option work_mode 0 + +config dashboard 'dashboard' + +config advanced 'advanced' + option disable_hnat '0' + +config status 'status' + option notice_status '0' diff --git a/open-app-filter/files/fwx_record.config b/open-app-filter/files/fwx_record.config new file mode 100644 index 00000000..89a2d3f5 --- /dev/null +++ b/open-app-filter/files/fwx_record.config @@ -0,0 +1 @@ +config whitelist 'whitelist' diff --git a/open-app-filter/files/gen_class.sh b/open-app-filter/files/gen_class.sh deleted file mode 100755 index 2127df3c..00000000 --- a/open-app-filter/files/gen_class.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -CLASS_NAME_FILE="/tmp/app_class.txt" -f_file=$1 -test -z "$f_file" && return - -test -f $CLASS_NAME_FILE &&{ - rm $CLASS_NAME_FILE -} -cat $f_file |grep "#class" | awk '{print $3 " " $2 " " $4}' >$CLASS_NAME_FILE - diff --git a/open-app-filter/files/hnat.sh b/open-app-filter/files/hnat.sh old mode 100755 new mode 100644 index 7dabba75..56762b69 --- a/open-app-filter/files/hnat.sh +++ b/open-app-filter/files/hnat.sh @@ -1,3 +1,5 @@ +#!/bin/sh + . /usr/share/libubox/jshn.sh . /lib/functions.sh @@ -7,7 +9,7 @@ if [ "$1" = "1" ] ; then IS_BOOT=1 fi -DISABLE_HNAT=$(uci -q get appfilter.global.disable_hnat) +DISABLE_HNAT=$(uci -q get fwx.advanced.disable_hnat) if [ "$DISABLE_HNAT" != "1" ]; then exit 0 fi @@ -41,4 +43,4 @@ if [ $IS_BOOT -ne 1 ] ; then /etc/init.d/firewall reload -fi \ No newline at end of file +fi diff --git a/open-app-filter/files/luci-app-oaf.json b/open-app-filter/files/luci-app-oaf.json new file mode 100644 index 00000000..751a852f --- /dev/null +++ b/open-app-filter/files/luci-app-oaf.json @@ -0,0 +1,12 @@ +{ + "oaf": { + "description": "Grant access to OAF configuration", + "read": { + "uci": [ "fwx"], + "ubus": { + "fwx": ["common"] + } + + } + } +} diff --git a/open-app-filter/files/mac_blacklist.config b/open-app-filter/files/mac_blacklist.config new file mode 100644 index 00000000..296766b4 --- /dev/null +++ b/open-app-filter/files/mac_blacklist.config @@ -0,0 +1,2 @@ +config settings 'base' + diff --git a/open-app-filter/files/macfilter.config b/open-app-filter/files/macfilter.config new file mode 100644 index 00000000..798f7a7b --- /dev/null +++ b/open-app-filter/files/macfilter.config @@ -0,0 +1,3 @@ +config global 'global' + option enable '1' + diff --git a/open-app-filter/files/macfilter_whitelist.config b/open-app-filter/files/macfilter_whitelist.config new file mode 100644 index 00000000..e69de29b diff --git a/open-app-filter/files/oaf_rule b/open-app-filter/files/oaf_rule deleted file mode 100755 index 8eb80458..00000000 --- a/open-app-filter/files/oaf_rule +++ /dev/null @@ -1,117 +0,0 @@ -. /usr/share/libubox/jshn.sh -. /lib/functions.sh - - -config_apply() -{ - test -z "$1" && return 1 - if [ -e "/dev/appfilter" ];then - echo "$1" >/dev/appfilter - fi -} - -clean_rule() -{ - json_init - json_add_int "op" 3 - json_add_object "data" - json_str=`json_dump` - config_apply "$json_str" - json_cleanup -} - -load_rule() -{ - json_init - json_add_int "op" 1 - json_add_object "data" - json_add_array "apps" - config_get appid_list rule app_list - if ! test -z "$appid_list";then - for appid in $appid_list: - do - json_add_int "" $appid - done - fi - json_str=`json_dump` - config_apply "$json_str" - json_cleanup -} - -load_whitelist_mac() -{ - json_init - config_load appfilter - json_add_int "op" 5 - json_add_object "data" - json_add_array "mac_list" - - config_foreach add_mac_to_array_callback whitelist - - json_str=`json_dump` - config_apply "$json_str" - json_cleanup -} - - - -add_mac_to_array_callback() { - local section="$1" - local mac - config_get mac "$section" "mac" - if [ -n "$mac" ]; then - json_add_string "" "$mac" - fi -} - -load_mac_list() -{ - json_init - config_load appfilter - json_add_int "op" 4 - json_add_object "data" - json_add_array "mac_list" - - local user_mode=`uci get appfilter.global.user_mode` - if [ x"1" == x"$user_mode" ];then - config_foreach add_mac_to_array_callback af_user - fi - - json_str=`json_dump` - config_apply "$json_str" - json_cleanup -} - - - -reload_rule(){ - config_load appfilter - clean_rule - load_rule - load_mac_list - load_whitelist_mac -} - -reload_base_config(){ - ! test -d /proc/sys/oaf && return - config_load appfilter - config_get work_mode "global" "work_mode" - config_get lan_ifname "global" "lan_ifname" - config_get user_mode "global" "user_mode" - config_get app_filter_mode "global" "app_filter_mode" - - echo "$work_mode" >/proc/sys/oaf/work_mode - echo "$user_mode" >/proc/sys/oaf/user_mode - echo "${app_filter_mode:-0}" >/proc/sys/oaf/app_filter_mode - - if [ x"" != x"$lan_ifname" ];then - echo "$lan_ifname" >/proc/sys/oaf/lan_ifname - fi -} - -case $1 in -"reload") - reload_base_config - reload_rule -;; -esac diff --git a/open-app-filter/files/oaf_version b/open-app-filter/files/oaf_version new file mode 100644 index 00000000..9fe9ff9d --- /dev/null +++ b/open-app-filter/files/oaf_version @@ -0,0 +1 @@ +7.0.1 diff --git a/open-app-filter/files/rule_manager.lua b/open-app-filter/files/rule_manager.lua new file mode 100644 index 00000000..0b1a087d --- /dev/null +++ b/open-app-filter/files/rule_manager.lua @@ -0,0 +1,2165 @@ +#!/usr/bin/lua +-- Copyright (C) 2026 destan19 + +local uci = require "uci" +local os = require "os" +local io = require "io" +local has_jsonc, jsonc = pcall(require, "luci.jsonc") +if not has_jsonc then + jsonc = nil +end + +local CHECK_INTERVAL = 10 +local LOG_FILE = "/tmp/log/rule_manager.log" +local SINGLE_MAC_FILTER_RULE_ID = 101 +local BLACKLIST_MAC_FILTER_RULE_ID = 102 +local USER_PARENTAL_CONTROL_STATUS_FILE = "/tmp/fwx_cache/user_parental_control_status" +local USER_PARENTAL_CONTROL_DETAIL_FILE = "/tmp/fwx_cache/user_parental_control_detail.json" +local TIME_MODE_RANGE = 1 +local TIME_MODE_DURATION = 2 +local TIME_MODE_FLOW = 3 +local MACFILTER_RULE_MODE_ALL_USERS = 1 +local MACFILTER_RULE_MODE_SINGLE_USER = 2 +local BLACKLIST_RULE_NAME = "Internet Blacklist" +local PC_STATUS_UNLIMITED = "unlimited" +local PC_STATUS_APP_LIMITED = "app_limited" +local PC_STATUS_MAC_BLOCKED = "mac_blocked" + +local APPFILTER_STATE_FILE = "/tmp/appfilter_rules_state" +local MACFILTER_STATE_FILE = "/tmp/macfilter_rules_state" +local APPFILTER_WHITELIST_STATE_FILE = "/tmp/appfilter_whitelist_state" +local MACFILTER_WHITELIST_STATE_FILE = "/tmp/macfilter_whitelist_state" +local RECORD_WHITELIST_STATE_FILE = "/tmp/record_whitelist_state" + +local appfilter_rules_state = {} +local macfilter_rules_state = {} + +local appfilter_enable_state = nil +local macfilter_enable_state = nil +local record_enable_state = nil + +local function ensure_log_dir() + os.execute(string.format("mkdir -p %s", string.match(LOG_FILE, "^(.*)/"))) +end + +local function log(message) + ensure_log_dir() + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + local log_msg = string.format("[%s] %s\n", timestamp, message) + -- for debug + --local file = io.open(LOG_FILE, "a") + --if file then + -- file:write(log_msg) + -- file:close() + --end + print(log_msg) +end + +local function parse_json_obj(output) + if not jsonc or not jsonc.parse or not output or output == "" then + return nil + end + local ok, obj = pcall(jsonc.parse, output) + if not ok or type(obj) ~= "table" then + return nil + end + return obj +end + +local function get_current_time_info() + local now = os.time() + local date = os.date("*t", now) + + local weekday = date.wday - 1 + + local current_minutes = date.hour * 60 + date.min + + return { + weekday = weekday, + hour = date.hour, + min = date.min, + minutes = current_minutes + } +end + +local function parse_time(time_str) + if not time_str or time_str == "" then + return nil + end + + local hour, min = time_str:match("(%d+):(%d+)") + if hour and min then + return tonumber(hour) * 60 + tonumber(min) + end + return nil +end + +local function is_time_in_range(time_rules, current_info) + if not time_rules or #time_rules == 0 then + return false + end + + for _, time_rule in ipairs(time_rules) do + if time_rule.weekdays and time_rule.start_time and time_rule.end_time then + local weekday_match = false + for _, wd in ipairs(time_rule.weekdays) do + if wd == current_info.weekday then + weekday_match = true + break + end + end + + if weekday_match then + local start_minutes = parse_time(time_rule.start_time) + local end_minutes = parse_time(time_rule.end_time) + + if start_minutes and end_minutes then + if start_minutes <= end_minutes then + if current_info.minutes >= start_minutes and current_info.minutes <= end_minutes then + return true + end + else + if current_info.minutes >= start_minutes or current_info.minutes <= end_minutes then + return true + end + end + end + end + end + end + + return false +end + +local function write_to_dev_fwx(json_str) + local dev_file = "/dev/fwx" + local check_cmd = string.format('test -e %s', dev_file) + local check_result = os.execute(check_cmd) + if check_result ~= 0 then + log(string.format("WARNING: Device file %s does not exist, skipping", dev_file)) + return false + end + + local payload = json_str or "" + local payload_len = string.len(payload) + if payload_len > 1024 then + payload = string.sub(payload, 1, 1024) .. "..." + end + log(string.format("write_to_dev_fwx: payload(len=%d): %s", payload_len, payload)) + + local file = io.open(dev_file, "w") + if not file then + log(string.format("ERROR: Failed to open %s for writing", dev_file)) + return false + end + + file:write(json_str) + file:close() + return true +end + +local function delete_appfilter_rule(rule_id) + log(string.format("AppFilter: Deleting rule %d", rule_id)) + local json_str = string.format('{"api":"del_app_filter_rule","data":{"rule_id":%d}}', rule_id) + if write_to_dev_fwx(json_str) then + log(string.format("AppFilter: Rule %d deleted successfully", rule_id)) + return true + else + log(string.format("AppFilter: Rule %d delete failed", rule_id)) + return false + end +end + +local function create_appfilter_rule(rule_id) + log(string.format("AppFilter: Creating rule %d", rule_id)) + local json_str = string.format('{"api":"add_app_filter_rule","data":{"rule_id":%d}}', rule_id) + if write_to_dev_fwx(json_str) then + log(string.format("AppFilter: Rule %d created successfully", rule_id)) + return true + else + log(string.format("AppFilter: Rule %d create failed", rule_id)) + return false + end +end + + +local function set_appfilter_rule_mac_list(rule_id, mac_list) + log(string.format("AppFilter: Setting MAC list for rule %d, count=%d", rule_id, #mac_list)) + + local mac_array_str = "" + if #mac_list > 0 then + local mac_strs = {} + for _, mac in ipairs(mac_list) do + table.insert(mac_strs, string.format('"%s"', mac)) + end + mac_array_str = "[" .. table.concat(mac_strs, ",") .. "]" + else + mac_array_str = "[]" + end + + local json_str = string.format('{"api":"mod_app_filter_rule","data":{"rule_id":%d,"mac_action":1,"mac_list":%s}}', + rule_id, mac_array_str) + if write_to_dev_fwx(json_str) then + log(string.format("AppFilter: MAC list for rule %d set successfully", rule_id)) + return true + else + log(string.format("AppFilter: MAC list for rule %d set failed", rule_id)) + return false + end +end + +local function set_appfilter_rule_app_id_list(rule_id, app_id_list) + log(string.format("AppFilter: Setting App ID list for rule %d, count=%d", rule_id, #app_id_list)) + + local app_id_array_str = "" + if #app_id_list > 0 then + local app_id_strs = {} + for _, app_id in ipairs(app_id_list) do + local token = tostring(app_id) + token = token:gsub("\\", "\\\\"):gsub("\"", "\\\"") + table.insert(app_id_strs, string.format('"%s"', token)) + end + app_id_array_str = "[" .. table.concat(app_id_strs, ",") .. "]" + else + app_id_array_str = "[]" + end + + local json_str = string.format('{"api":"mod_app_filter_rule","data":{"rule_id":%d,"app_action":1,"app_id_list":%s}}', + rule_id, app_id_array_str) + if write_to_dev_fwx(json_str) then + log(string.format("AppFilter: App ID list for rule %d set successfully", rule_id)) + return true + else + log(string.format("AppFilter: App ID list for rule %d set failed", rule_id)) + return false + end +end + +local function set_appfilter_rule_filter_quic(rule_id, filter_quic) + local filter_quic_value = tonumber(filter_quic) or 0 + if filter_quic_value ~= 1 then + filter_quic_value = 0 + end + + log(string.format("AppFilter: Setting filter_quic for rule %d, value=%d", rule_id, filter_quic_value)) + local json_str = string.format('{"api":"mod_app_filter_rule","data":{"rule_id":%d,"filter_quic":%d}}', + rule_id, filter_quic_value) + if write_to_dev_fwx(json_str) then + log(string.format("AppFilter: filter_quic for rule %d set successfully", rule_id)) + return true + else + log(string.format("AppFilter: filter_quic for rule %d set failed", rule_id)) + return false + end +end + +local function apply_macfilter_rule(rule_id, enable) + log(string.format("MACFilter: apply_macfilter_rule called but enable field is no longer sent to kernel")) + return true +end + +local function create_macfilter_rule(rule_id, mode) + local rule_mode = tonumber(mode) or MACFILTER_RULE_MODE_ALL_USERS + if rule_mode ~= MACFILTER_RULE_MODE_SINGLE_USER then + rule_mode = MACFILTER_RULE_MODE_ALL_USERS + end + log(string.format("MACFilter: Creating rule %d, mode=%d", rule_id, rule_mode)) + local json_str = string.format('{"api":"add_mac_filter_rule","data":{"rule_id":%d,"mode":%d}}', rule_id, rule_mode) + if write_to_dev_fwx(json_str) then + log(string.format("MACFilter: Rule %d created successfully", rule_id)) + return true + else + log(string.format("MACFilter: Rule %d create failed", rule_id)) + return false + end +end + +local function delete_macfilter_rule(rule_id) + log(string.format("MACFilter: Deleting rule %d", rule_id)) + local json_str = string.format('{"api":"del_mac_filter_rule","data":{"rule_id":%d}}', rule_id) + if write_to_dev_fwx(json_str) then + log(string.format("MACFilter: Rule %d deleted successfully", rule_id)) + return true + else + log(string.format("MACFilter: Rule %d delete failed", rule_id)) + return false + end +end + +local function set_macfilter_rule_mac_list(rule_id, mac_list, mode) + local rule_mode = tonumber(mode) or MACFILTER_RULE_MODE_ALL_USERS + if rule_mode ~= MACFILTER_RULE_MODE_SINGLE_USER then + rule_mode = MACFILTER_RULE_MODE_ALL_USERS + end + log(string.format("MACFilter: Setting MAC list for rule %d, mode=%d, count=%d", rule_id, rule_mode, #mac_list)) + + local clear_json = string.format('{"api":"mod_mac_filter_rule","data":{"rule_id":%d,"mode":%d,"mac_action":0}}', rule_id, rule_mode) + if not write_to_dev_fwx(clear_json) then + log(string.format("MACFilter: Failed to clear MAC list for rule %d", rule_id)) + return false + end + + if #mac_list == 0 then + log(string.format("MACFilter: MAC list for rule %d cleared (empty list, no MACs to set)", rule_id)) + return true + end + + local mac_strs = {} + for _, mac in ipairs(mac_list) do + table.insert(mac_strs, string.format('"%s"', mac)) + end + local mac_array_str = "[" .. table.concat(mac_strs, ",") .. "]" + + local json_str = string.format('{"api":"mod_mac_filter_rule","data":{"rule_id":%d,"mode":%d,"mac_action":1,"mac_list":%s}}', + rule_id, rule_mode, mac_array_str) + if write_to_dev_fwx(json_str) then + log(string.format("MACFilter: MAC list for rule %d set successfully", rule_id)) + return true + else + log(string.format("MACFilter: MAC list for rule %d set failed", rule_id)) + return false + end +end + +local function uci_get_all_sections(config, section_type) + local sections = {} + local cmd = string.format("uci show %s.@%s 2>/dev/null | grep -E '^%s\\.@%s\\[\\-?\\d+\\]'", config, section_type, config, section_type) + local handle = io.popen(cmd) + if not handle then + return sections + end + + local seen_ids = {} + for line in handle:lines() do + local section_path = line:match("^([^=]+)=") + if section_path then + local section_index = section_path:match("%[([%d%-]+)%]") + if section_index then + local index = tonumber(section_index) + if index and index >= 0 and not seen_ids[index] then + seen_ids[index] = true + table.insert(sections, index) + end + end + end + end + handle:close() + + table.sort(sections) + return sections +end + +local function load_appfilter_rules() + log("=== Loading AppFilter rules from UCI ===") + + local uci_cursor = uci.cursor() + local rules = {} + + uci_cursor:foreach("appfilter", "rule", function(section) + local rule = { + id = tonumber(section.id) or 0, + name = section.name or "", + mode = tonumber(section.mode) or 1, + enabled = tonumber(section.enabled) or 1, + filter_quic = tonumber(section.filter_quic) or 0, + user_mac = section.user_mac or "", + time_rules = {}, + app_ids = {} + } + + if section.app_id then + local app_ids = type(section.app_id) == "table" and section.app_id or {section.app_id} + for _, app_id_token in ipairs(app_ids) do + if app_id_token and app_id_token ~= "" then + table.insert(rule.app_ids, tostring(app_id_token)) + end + end + end + + if section.time_rule then + local time_rules = type(section.time_rule) == "table" and section.time_rule or {section.time_rule} + for _, time_rule_str in ipairs(time_rules) do + if time_rule_str and time_rule_str ~= "" then + local parts = {} + for part in time_rule_str:gmatch("[^,]+") do + table.insert(parts, part) + end + + if #parts >= 3 then + local weekdays = {} + local start_time = nil + local end_time = nil + + for i, part in ipairs(parts) do + if part:match(":") then + if not start_time then + start_time = part + else + end_time = part + end + else + local wd = tonumber(part) + if wd then + table.insert(weekdays, wd) + end + end + end + + if start_time and end_time and #weekdays > 0 then + table.insert(rule.time_rules, { + weekdays = weekdays, + start_time = start_time, + end_time = end_time + }) + end + end + end + end + end + + table.insert(rules, rule) + end) + + uci_cursor:unload("appfilter") + + log(string.format("Loaded %d AppFilter rules", #rules)) + return rules +end + +local function load_macfilter_rules() + local uci_cursor = uci.cursor() + local rules = {} + + local function parse_weekdays_csv(weekdays_csv) + local weekdays = {} + if not weekdays_csv then + return weekdays + end + for wd_str in tostring(weekdays_csv):gmatch("[^,]+") do + local wd = tonumber(wd_str) + if wd and wd >= 0 and wd <= 6 then + table.insert(weekdays, wd) + end + end + return weekdays + end + + local function parse_macfilter_time_rule(rule, time_rule_str) + if not time_rule_str or time_rule_str == "" then + return + end + + local weekday_part, mode_part, value_part = time_rule_str:match("^([^;]+);([^;]+);(.+)$") + if weekday_part and mode_part and value_part then + local parsed_rule_mode = tonumber(mode_part) or 0 + local parsed_time_mode = TIME_MODE_RANGE + if parsed_rule_mode == 1 then + parsed_time_mode = TIME_MODE_DURATION + elseif parsed_rule_mode == 2 then + parsed_time_mode = TIME_MODE_FLOW + end + + local weekdays = parse_weekdays_csv(weekday_part) + if #weekdays <= 0 then + return + end + + rule.time_mode = parsed_time_mode + if parsed_time_mode == TIME_MODE_DURATION then + local duration_minutes = tonumber(value_part) or 0 + if duration_minutes > 0 then + table.insert(rule.duration_rules, { + weekdays = weekdays, + duration_minutes = duration_minutes + }) + end + elseif parsed_time_mode == TIME_MODE_FLOW then + local flow_mb = tonumber(value_part) or 0 + if flow_mb > 0 then + table.insert(rule.flow_rules, { + weekdays = weekdays, + flow_mb = flow_mb + }) + end + else + local start_time, end_time = value_part:match("^([^%-]+)%-(.+)$") + if start_time and end_time then + table.insert(rule.time_rules, { + weekdays = weekdays, + start_time = start_time, + end_time = end_time + }) + end + end + return + end + + local parts = {} + for part in time_rule_str:gmatch("[^,]+") do + table.insert(parts, part) + end + if #parts <= 0 then + return + end + + local effective_time_mode = rule.time_mode + if #parts >= 3 and parts[#parts - 1] == "duration" then + effective_time_mode = TIME_MODE_DURATION + elseif #parts >= 3 and parts[#parts - 1] == "flow" then + effective_time_mode = TIME_MODE_FLOW + end + rule.time_mode = effective_time_mode + + if effective_time_mode == TIME_MODE_DURATION then + local weekdays = {} + local duration_minutes = tonumber(parts[#parts]) or 0 + local last_weekday_idx = #parts - 1 + if #parts >= 3 and parts[#parts - 1] == "duration" then + last_weekday_idx = #parts - 2 + end + + for i = 1, last_weekday_idx do + local wd = tonumber(parts[i]) + if wd and wd >= 0 and wd <= 6 then + table.insert(weekdays, wd) + end + end + + if duration_minutes > 0 and #weekdays > 0 then + table.insert(rule.duration_rules, { + weekdays = weekdays, + duration_minutes = duration_minutes + }) + end + return + end + + if effective_time_mode == TIME_MODE_FLOW then + local weekdays = {} + local flow_mb = tonumber(parts[#parts]) or 0 + local last_weekday_idx = #parts - 1 + if #parts >= 3 and parts[#parts - 1] == "flow" then + last_weekday_idx = #parts - 2 + end + + for i = 1, last_weekday_idx do + local wd = tonumber(parts[i]) + if wd and wd >= 0 and wd <= 6 then + table.insert(weekdays, wd) + end + end + + if flow_mb > 0 and #weekdays > 0 then + table.insert(rule.flow_rules, { + weekdays = weekdays, + flow_mb = flow_mb + }) + end + return + end + + if #parts >= 3 then + local weekdays = {} + local start_time = nil + local end_time = nil + + for _, part in ipairs(parts) do + if part:match(":") then + if not start_time then + start_time = part + else + end_time = part + end + else + local wd = tonumber(part) + if wd and wd >= 0 and wd <= 6 then + table.insert(weekdays, wd) + end + end + end + + if start_time and end_time and #weekdays > 0 then + table.insert(rule.time_rules, { + weekdays = weekdays, + start_time = start_time, + end_time = end_time + }) + end + end + end + + local function parse_week_limit_rules(limit_str, max_value, value_key) + local result = {} + local parsed_map = {} + local order = {1, 2, 3, 4, 5, 6, 0} + if type(limit_str) ~= "string" then + limit_str = "" + end + for pair in tostring(limit_str):gmatch("[^,]+") do + local day_str, value_str = pair:match("^%s*(%d+)%s*:%s*(%d+)%s*$") + local day = tonumber(day_str) + local value = tonumber(value_str) + if day and value and day >= 0 and day <= 6 then + if value < 0 then + value = 0 + end + if max_value and value > max_value then + value = max_value + end + parsed_map[day] = value + end + end + for _, day in ipairs(order) do + table.insert(result, { + weekdays = {day}, + [value_key] = parsed_map[day] or 0 + }) + end + return result + end + + uci_cursor:foreach("macfilter", "rule", function(section) + local rule = { + id = tonumber(section.id) or 0, + name = section.name or "", + mode = tonumber(section.mode) or 1, + time_mode = tonumber(section.time_mode) or TIME_MODE_RANGE, + enabled = tonumber(section.enabled) or 1, + user_mac = section.user_mac or "", + time_list = {}, + time_limit = section.time_limit or "", + flow_limit = section.flow_limit or "", + time_rules = {}, + duration_rules = {}, + flow_rules = {} + } + + local loaded_new_field = false + if rule.time_mode == TIME_MODE_RANGE then + if section.time_list then + local time_list = type(section.time_list) == "table" and section.time_list or {section.time_list} + for _, time_rule_str in ipairs(time_list) do + if time_rule_str and time_rule_str ~= "" then + table.insert(rule.time_list, time_rule_str) + parse_macfilter_time_rule(rule, time_rule_str) + end + end + if #rule.time_list > 0 then + loaded_new_field = true + end + end + elseif rule.time_mode == TIME_MODE_DURATION then + if rule.time_limit and rule.time_limit ~= "" then + rule.duration_rules = parse_week_limit_rules(rule.time_limit, 1440, "duration_minutes") + loaded_new_field = true + end + elseif rule.time_mode == TIME_MODE_FLOW then + if rule.flow_limit and rule.flow_limit ~= "" then + rule.flow_rules = parse_week_limit_rules(rule.flow_limit, 1048576, "flow_mb") + loaded_new_field = true + end + end + + if (not loaded_new_field) and section.time_rule then + local time_rules = type(section.time_rule) == "table" and section.time_rule or {section.time_rule} + for _, time_rule_str in ipairs(time_rules) do + parse_macfilter_time_rule(rule, time_rule_str) + end + end + + table.insert(rules, rule) + end) + + uci_cursor:unload("macfilter") + + log(string.format("Loaded %d MACFilter rules", #rules)) + return rules +end + +local function load_mac_blacklist() + local uci_cursor = uci.cursor() + local mac_list = {} + local ok = false + + if type(uci_cursor.get_list) == "function" then + local list_ret = uci_cursor:get_list("mac_blacklist", "base", "mac_list") + if type(list_ret) == "table" then + mac_list = list_ret + ok = true + elseif type(list_ret) == "string" then + mac_list = {list_ret} + ok = true + end + end + + if not ok then + local raw = uci_cursor:get("mac_blacklist", "base", "mac_list") + if type(raw) == "table" then + mac_list = raw + elseif type(raw) == "string" then + mac_list = {raw} + else + mac_list = {} + end + end + + if type(uci_cursor.unload) == "function" then + pcall(function() + uci_cursor:unload("mac_blacklist") + end) + end + + local uniq = {} + local normalized_list = {} + for _, mac in ipairs(mac_list) do + local normalized_mac = tostring(mac or ""):upper() + if normalized_mac ~= "" and not uniq[normalized_mac] then + uniq[normalized_mac] = true + table.insert(normalized_list, normalized_mac) + end + end + table.sort(normalized_list) + log(string.format("Loaded %d MAC blacklist entries", #normalized_list)) + return normalized_list +end + +local function weekday_match(weekdays, current_weekday) + if not weekdays then + return false + end + for _, weekday in ipairs(weekdays) do + if weekday == current_weekday then + return true + end + end + return false +end + +local function get_macfilter_user_stat() + local cmd = "ubus call fwx common '{\"api\":\"get_user_stat\",\"data\":{}}' 2>/dev/null" + local handle = io.popen(cmd) + local output = "" + log(string.format("MACFilter usage mode: exec ubus cmd: %s", cmd)) + if handle then + output = handle:read("*a") or "" + handle:close() + end + + local output_len = string.len(output or "") + if output_len <= 1024 then + log(string.format("MACFilter usage mode: ubus raw result(len=%d): %s", output_len, output)) + else + log(string.format("MACFilter usage mode: ubus raw result(len=%d, first_1024): %s", output_len, string.sub(output, 1, 1024))) + end + + if output == "" or not output:match('"code"%s*:%s*2000') then + log("MACFilter usage mode: ubus get_user_stat invalid response") + return nil, nil, nil + end + + local user_active_map = {} + local user_flow_bytes_map = {} + local user_mac_list = {} + local user_mac_set = {} + + local parsed = parse_json_obj(output) + if parsed and tonumber(parsed.code) == 2000 and type(parsed.data) == "table" then + local stat_list = nil + if type(parsed.data.l) == "table" then + stat_list = parsed.data.l + elseif type(parsed.data.list) == "table" then + stat_list = parsed.data.list + elseif type(parsed.data.data) == "table" then + stat_list = parsed.data.data + end + + if type(stat_list) == "table" then + for _, item in ipairs(stat_list) do + if type(item) == "table" then + local mac = item.m or item.mac + local active_time = item.at or item.today_active_time + local up_flow = item.uf or item.today_up_flow + local down_flow = item.df or item.today_down_flow + if mac and mac ~= "" then + user_active_map[mac] = math.floor((tonumber(active_time) or 0) / 60) + user_flow_bytes_map[mac] = (tonumber(up_flow) or 0) + (tonumber(down_flow) or 0) + if not user_mac_set[mac] then + user_mac_set[mac] = true + table.insert(user_mac_list, mac) + end + end + end + end + end + end + + if #user_mac_list == 0 then + for mac, active_time, up_flow, down_flow in output:gmatch('"m"%s*:%s*"([^"]+)".-"at"%s*:%s*(%d+).-"uf"%s*:%s*(%d+).-"df"%s*:%s*(%d+)') do + if mac and mac ~= "" then + user_active_map[mac] = math.floor((tonumber(active_time) or 0) / 60) + user_flow_bytes_map[mac] = (tonumber(up_flow) or 0) + (tonumber(down_flow) or 0) + if not user_mac_set[mac] then + user_mac_set[mac] = true + table.insert(user_mac_list, mac) + end + end + end + end + + if #user_mac_list == 0 then + for mac, seconds, up_flow, down_flow in output:gmatch('"mac"%s*:%s*"([^"]+)".-"today_active_time"%s*:%s*(%d+).-"today_up_flow"%s*:%s*(%d+).-"today_down_flow"%s*:%s*(%d+)') do + if mac and mac ~= "" then + user_active_map[mac] = math.floor((tonumber(seconds) or 0) / 60) + user_flow_bytes_map[mac] = (tonumber(up_flow) or 0) + (tonumber(down_flow) or 0) + if not user_mac_set[mac] then + user_mac_set[mac] = true + table.insert(user_mac_list, mac) + end + end + end + end + + log(string.format("MACFilter usage mode: parsed user stat count=%d", #user_mac_list)) + return user_active_map, user_flow_bytes_map, user_mac_list +end + +local function append_detail_for_mac(detail_map, mac, detail) + if not detail_map or not mac or mac == "" or not detail then + return + end + if not detail_map[mac] then + detail_map[mac] = {} + end + table.insert(detail_map[mac], detail) +end + +local function parse_app_rule_category_stats(app_ids) + local category_count_map = {} + local category_ids = {} + local app_count = 0 + local app_list = app_ids or {} + + local function add_category_count(category_id, count) + if not category_id or category_id <= 0 or not count or count <= 0 then + return + end + category_count_map[category_id] = (category_count_map[category_id] or 0) + count + end + + local function add_range_category_count(start_id, end_id) + local cur = tonumber(start_id) or 0 + local finish = tonumber(end_id) or 0 + if cur <= 0 or finish <= 0 then + return 0 + end + if cur > finish then + cur, finish = finish, cur + end + + local total = 0 + while cur <= finish do + local category_id = math.floor(cur / 1000) + if category_id <= 0 then + cur = cur + 1 + else + local category_end = category_id * 1000 + 999 + local seg_end = math.min(finish, category_end) + local seg_count = seg_end - cur + 1 + add_category_count(category_id, seg_count) + total = total + seg_count + cur = seg_end + 1 + end + end + return total + end + + for _, app_id in ipairs(app_list) do + local token = tostring(app_id or "") + local start_str, end_str = token:match("^%s*(%d+)%s*%-%s*(%d+)%s*$") + if start_str and end_str then + app_count = app_count + add_range_category_count(start_str, end_str) + else + local app_num = tonumber(token) + if app_num and app_num > 0 then + local category_id = math.floor(app_num / 1000) + if category_id > 0 then + add_category_count(category_id, 1) + app_count = app_count + 1 + end + end + end + end + + local category_stats = {} + for category_id, count in pairs(category_count_map) do + table.insert(category_ids, category_id) + table.insert(category_stats, { + id = category_id, + count = count + }) + end + + table.sort(category_ids) + table.sort(category_stats, function(a, b) + return (a.id or 0) < (b.id or 0) + end) + + return category_ids, category_stats, app_count +end + +local function build_appfilter_rule_detail(rule) + local app_ids = rule.app_ids or {} + local category_ids, category_stats, app_count = parse_app_rule_category_stats(app_ids) + return { + rule_id = tonumber(rule.id) or 0, + rule_name = rule.name or "", + mode = tonumber(rule.mode) or 1, + user_mac = rule.user_mac or "", + time_rules = rule.time_rules or {}, + category_ids = category_ids, + category_stats = category_stats, + category_count = #category_ids, + app_count = app_count + } +end + +local function build_macfilter_rule_detail(rule, match_type) + local detail = { + rule_id = tonumber(rule.id) or 0, + rule_name = rule.name or "", + mode = tonumber(rule.mode) or 1, + time_mode = tonumber(rule.time_mode) or TIME_MODE_RANGE, + user_mac = rule.user_mac or "", + match_type = match_type or "time_range" + } + if detail.time_mode == TIME_MODE_DURATION then + detail.duration_rules = rule.duration_rules or {} + elseif detail.time_mode == TIME_MODE_FLOW then + detail.flow_rules = rule.flow_rules or {} + else + detail.time_rules = rule.time_rules or {} + end + return detail +end + +local function get_all_user_macs_for_status() + local cmd = "ubus call fwx common '{\"api\":\"get_all_users\",\"data\":{\"flag\":0,\"page\":1,\"page_size\":1024}}' 2>/dev/null" + local handle = io.popen(cmd) + local output = "" + local user_mac_list = {} + local user_mac_set = {} + + if handle then + output = handle:read("*a") or "" + handle:close() + end + + if output == "" or not output:match('"code"%s*:%s*2000') then + log("ParentalControlStatus: get_all_users failed, fallback to matched MAC set only") + return user_mac_list, user_mac_set + end + + local parsed = parse_json_obj(output) + if parsed and tonumber(parsed.code) == 2000 and type(parsed.data) == "table" then + local users = nil + if type(parsed.data.data) == "table" then + users = parsed.data.data + elseif type(parsed.data.list) == "table" then + users = parsed.data.list + end + + if type(users) == "table" then + for _, item in ipairs(users) do + if type(item) == "table" then + local mac = item.mac + if mac and mac ~= "" and not user_mac_set[mac] then + user_mac_set[mac] = true + table.insert(user_mac_list, mac) + end + end + end + end + end + + if #user_mac_list == 0 then + for mac in output:gmatch('"mac"%s*:%s*"([^"]+)"') do + if mac and mac ~= "" and not user_mac_set[mac] then + user_mac_set[mac] = true + table.insert(user_mac_list, mac) + end + end + end + + log(string.format("ParentalControlStatus: loaded %d MACs from get_all_users", #user_mac_list)) + return user_mac_list, user_mac_set +end + +local function write_user_parental_control_status(appfilter_mac_set, appfilter_all_users_active, macfilter_block_set, appfilter_detail_map, appfilter_all_user_rule_details, macfilter_all_user_rule_details, macfilter_detail_map) + local final_mac_set = {} + local _, all_mac_set = get_all_user_macs_for_status() + local output_mac_list = {} + local app_set = appfilter_mac_set or {} + local mac_block_set = macfilter_block_set or {} + local app_detail_set = appfilter_detail_map or {} + local app_all_user_detail_list = appfilter_all_user_rule_details or {} + local mac_all_user_detail_list = macfilter_all_user_rule_details or {} + local mac_detail_set = macfilter_detail_map or {} + local detail_data = { + appfilter_all_user_rules = app_all_user_detail_list, + macfilter_all_user_rules = mac_all_user_detail_list, + users = {} + } + + for mac, _ in pairs(all_mac_set) do + final_mac_set[mac] = true + end + for mac, _ in pairs(app_set) do + final_mac_set[mac] = true + end + for mac, _ in pairs(mac_block_set) do + final_mac_set[mac] = true + end + + for mac, _ in pairs(final_mac_set) do + table.insert(output_mac_list, mac) + end + table.sort(output_mac_list) + + os.execute("mkdir -p /tmp/fwx_cache") + local tmp_file = USER_PARENTAL_CONTROL_STATUS_FILE .. ".tmp" + local file = io.open(tmp_file, "w") + if not file then + log(string.format("ParentalControlStatus: failed to open temp file %s", tmp_file)) + return false + end + + local unlimited_count = 0 + local app_limited_count = 0 + local mac_blocked_count = 0 + + for _, mac in ipairs(output_mac_list) do + local status = PC_STATUS_UNLIMITED + local app_rules = {} + local mac_rules = mac_detail_set[mac] or {} + local mac_rule_hit = mac_block_set[mac] and true or false + + if app_detail_set[mac] then + for _, detail in ipairs(app_detail_set[mac]) do + table.insert(app_rules, detail) + end + end + if #app_rules > 0 or appfilter_all_users_active or app_set[mac] then + status = PC_STATUS_APP_LIMITED + end + if mac_rule_hit then + status = PC_STATUS_MAC_BLOCKED + end + + file:write(string.format("%s %s\n", mac, status)) + if status == PC_STATUS_MAC_BLOCKED then + mac_blocked_count = mac_blocked_count + 1 + elseif status == PC_STATUS_APP_LIMITED then + app_limited_count = app_limited_count + 1 + else + unlimited_count = unlimited_count + 1 + end + + detail_data.users[mac] = { + pc_status = status, + appfilter_rules = app_rules, + macfilter_rules = mac_rules + } + end + + file:close() + os.rename(tmp_file, USER_PARENTAL_CONTROL_STATUS_FILE) + + if jsonc and jsonc.stringify then + local detail_tmp_file = USER_PARENTAL_CONTROL_DETAIL_FILE .. ".tmp" + local detail_file = io.open(detail_tmp_file, "w") + if detail_file then + detail_file:write(jsonc.stringify(detail_data)) + detail_file:close() + os.rename(detail_tmp_file, USER_PARENTAL_CONTROL_DETAIL_FILE) + else + log(string.format("ParentalControlStatus: failed to open detail temp file %s", detail_tmp_file)) + end + else + log("ParentalControlStatus: luci.jsonc unavailable, skip detail json output") + end + + log(string.format("ParentalControlStatus: written %d items to %s (unlimited=%d, app_limited=%d, mac_blocked=%d, all_user_app=%s)", + #output_mac_list, USER_PARENTAL_CONTROL_STATUS_FILE, unlimited_count, app_limited_count, mac_blocked_count, tostring(appfilter_all_users_active))) + return true +end + +local function init_effective_mac_rules() + local ok_regular = create_macfilter_rule(SINGLE_MAC_FILTER_RULE_ID, MACFILTER_RULE_MODE_SINGLE_USER) + local ok_blacklist = create_macfilter_rule(BLACKLIST_MAC_FILTER_RULE_ID, MACFILTER_RULE_MODE_SINGLE_USER) + + if ok_regular then + log(string.format("MACFilter effective rule initialized: rule_id=%d", SINGLE_MAC_FILTER_RULE_ID)) + else + log(string.format("MACFilter effective rule init failed: rule_id=%d", SINGLE_MAC_FILTER_RULE_ID)) + end + + if ok_blacklist then + log(string.format("MACFilter blacklist rule initialized: rule_id=%d", BLACKLIST_MAC_FILTER_RULE_ID)) + else + log(string.format("MACFilter blacklist rule init failed: rule_id=%d", BLACKLIST_MAC_FILTER_RULE_ID)) + end + + return ok_regular and ok_blacklist +end + +local function sync_effective_mac_rule(rule_id, rule_name, mac_list) + local mac_count = #mac_list + local rule_state = macfilter_rules_state[rule_id] + local rule_active = rule_state and rule_state.active or false + + local mac_list_changed = true + if rule_state and rule_state.mac_list then + local old_mac_set = {} + for _, old_mac in ipairs(rule_state.mac_list) do + old_mac_set[old_mac] = true + end + + if mac_count == #rule_state.mac_list then + mac_list_changed = false + for _, new_mac in ipairs(mac_list) do + if not old_mac_set[new_mac] then + mac_list_changed = true + break + end + end + end + end + + local need_update = false + if rule_active then + need_update = mac_list_changed + else + need_update = (mac_count > 0) or mac_list_changed + end + + if not need_update then + log(string.format("%s: no changes needed (rule_id=%d, active=%s, mac_count=%d)", + rule_name, rule_id, tostring(rule_active), mac_count)) + return true + end + + log(string.format("%s: apply to kernel (rule_id=%d, active=%s, mac_count=%d, changed=%s)", + rule_name, rule_id, tostring(rule_active), mac_count, tostring(mac_list_changed))) + + if not set_macfilter_rule_mac_list(rule_id, mac_list, MACFILTER_RULE_MODE_SINGLE_USER) then + log(string.format("%s: apply failed (rule_id=%d)", rule_name, rule_id)) + return false + end + + if not macfilter_rules_state[rule_id] then + macfilter_rules_state[rule_id] = {} + end + macfilter_rules_state[rule_id].active = (mac_count > 0) + macfilter_rules_state[rule_id].mode = MACFILTER_RULE_MODE_SINGLE_USER + macfilter_rules_state[rule_id].mac_list = mac_list + macfilter_rules_state[rule_id].name = rule_name + log(string.format("%s: apply success (rule_id=%d, mac_count=%d)", rule_name, rule_id, mac_count)) + return true +end + +local function process_appfilter_rules(current_info) + log(string.format("=== Processing AppFilter rules (time: %02d:%02d, weekday: %d) ===", + current_info.hour, current_info.min, current_info.weekday)) + + local rules = load_appfilter_rules() + local appfilter_effective_mac_set = {} + local appfilter_all_users_active = false + local appfilter_detail_map = {} + local appfilter_all_user_rule_details = {} + local rule_map = {} + for _, rule in ipairs(rules) do + rule_map[tonumber(rule.id) or 0] = rule + end + + for _, rule in ipairs(rules) do + local time_match = is_time_in_range(rule.time_rules, current_info) + local should_active = (rule.enabled == 1) and time_match + local current_state = appfilter_rules_state[rule.id] + local is_active = current_state and current_state.active or false + + log(string.format("AppFilter rule %d (%s): enabled=%d, time_match=%s, should_active=%s, is_active=%s", + rule.id, rule.name, rule.enabled, tostring(time_match), tostring(should_active), tostring(is_active))) + + if should_active then + local mac_list = {} + if rule.mode == 2 and rule.user_mac and rule.user_mac ~= "" then + table.insert(mac_list, rule.user_mac) + elseif rule.mode == 1 then + end + + local app_id_list = rule.app_ids or {} + + local config_changed = false + if not current_state then + config_changed = true + else + if #mac_list ~= (current_state.mac_list and #current_state.mac_list or 0) then + config_changed = true + else + local old_mac_set = {} + if current_state.mac_list then + for _, mac in ipairs(current_state.mac_list) do + old_mac_set[mac] = true + end + end + for _, mac in ipairs(mac_list) do + if not old_mac_set[mac] then + config_changed = true + break + end + end + end + + if not config_changed then + if #app_id_list ~= (current_state.app_id_list and #current_state.app_id_list or 0) then + config_changed = true + else + local old_app_id_set = {} + if current_state.app_id_list then + for _, app_id in ipairs(current_state.app_id_list) do + old_app_id_set[app_id] = true + end + end + for _, app_id in ipairs(app_id_list) do + if not old_app_id_set[app_id] then + config_changed = true + break + end + end + end + end + + if not config_changed then + if (tonumber(rule.filter_quic) or 0) ~= (tonumber(current_state.filter_quic) or 0) then + config_changed = true + end + end + end + + if not is_active or config_changed then + if is_active then + log(string.format("AppFilter rule %d (%s): config changed, recreating", rule.id, rule.name)) + delete_appfilter_rule(rule.id) + else + log(string.format("AppFilter rule %d (%s): activating", rule.id, rule.name)) + end + + if create_appfilter_rule(rule.id) then + if not appfilter_rules_state[rule.id] then + appfilter_rules_state[rule.id] = {} + end + + if set_appfilter_rule_mac_list(rule.id, mac_list) then + appfilter_rules_state[rule.id].mac_list = mac_list + end + + if set_appfilter_rule_app_id_list(rule.id, app_id_list) then + appfilter_rules_state[rule.id].app_id_list = app_id_list + end + + if set_appfilter_rule_filter_quic(rule.id, rule.filter_quic) then + appfilter_rules_state[rule.id].filter_quic = tonumber(rule.filter_quic) or 0 + end + + appfilter_rules_state[rule.id].active = true + appfilter_rules_state[rule.id].name = rule.name + appfilter_rules_state[rule.id].mode = rule.mode + appfilter_rules_state[rule.id].enabled = rule.enabled + log(string.format("AppFilter rule %d: activated successfully", rule.id)) + end + else + log(string.format("AppFilter rule %d: no changes needed", rule.id)) + end + else + if is_active then + log(string.format("AppFilter rule %d (%s): deactivating (enabled=%d, time_match=%s)", + rule.id, rule.name, rule.enabled, tostring(time_match))) + if delete_appfilter_rule(rule.id) then + appfilter_rules_state[rule.id].active = false + log(string.format("AppFilter rule %d: deactivated", rule.id)) + end + end + end + end + + for rule_id, state in pairs(appfilter_rules_state) do + local found = false + for _, rule in ipairs(rules) do + if rule.id == rule_id then + found = true + break + end + end + if not found then + if state.active then + log(string.format("AppFilter rule %d: removed from UCI, deleting", rule_id)) + if delete_appfilter_rule(rule_id) then + appfilter_rules_state[rule_id] = nil + end + else + appfilter_rules_state[rule_id] = nil + end + end + end + + for rule_id, state in pairs(appfilter_rules_state) do + if state and state.active then + local rule_cfg = rule_map[tonumber(rule_id) or 0] + if rule_cfg then + local detail = build_appfilter_rule_detail(rule_cfg) + if tonumber(state.mode) == 1 then + appfilter_all_users_active = true + table.insert(appfilter_all_user_rule_details, detail) + elseif tonumber(state.mode) == 2 and state.mac_list then + for _, mac in ipairs(state.mac_list) do + if mac and mac ~= "" then + appfilter_effective_mac_set[mac] = true + append_detail_for_mac(appfilter_detail_map, mac, detail) + end + end + end + end + end + end + + return appfilter_effective_mac_set, appfilter_all_users_active, appfilter_detail_map, appfilter_all_user_rule_details +end + +local function process_macfilter_rules(current_info) + log(string.format("=== Processing MACFilter rules (time: %02d:%02d, weekday: %d) ===", + current_info.hour, current_info.min, current_info.weekday)) + + local rules = load_macfilter_rules() + + local all_user_rules = {} + local regular_mac_set = {} + local blacklist_mac_set = {} + local duration_rules = {} + local flow_rules = {} + local blacklist_macs = load_mac_blacklist() + local macfilter_detail_map = {} + local all_user_range_rule_details = {} + + for _, rule in ipairs(rules) do + if rule.enabled == 1 then + local time_mode = tonumber(rule.time_mode) or TIME_MODE_RANGE + if time_mode == TIME_MODE_DURATION then + table.insert(duration_rules, rule) + elseif time_mode == TIME_MODE_FLOW then + table.insert(flow_rules, rule) + else + local should_active = is_time_in_range(rule.time_rules, current_info) + if should_active then + if rule.mode == 1 then + table.insert(all_user_rules, rule) + table.insert(all_user_range_rule_details, build_macfilter_rule_detail(rule, "time_range")) + elseif rule.mode == 2 and rule.user_mac and rule.user_mac ~= "" then + regular_mac_set[rule.user_mac] = true + append_detail_for_mac(macfilter_detail_map, rule.user_mac, build_macfilter_rule_detail(rule, "time_range")) + end + end + end + end + end + + log(string.format("MACFilter: Found %d time-range all-user rules, %d duration rules, %d flow rules (effective-mac-list mode)", + #all_user_rules, #duration_rules, #flow_rules)) + + local user_active_map = nil + local user_flow_bytes_map = nil + local user_mac_list = nil + local need_user_stat = (#all_user_rules > 0) or (#duration_rules > 0) or (#flow_rules > 0) + if need_user_stat then + user_active_map, user_flow_bytes_map, user_mac_list = get_macfilter_user_stat() + if not user_active_map or not user_flow_bytes_map or not user_mac_list then + log("MACFilter: failed to get user stat, all-user/duration/flow evaluation will be skipped this round") + user_active_map = nil + user_flow_bytes_map = nil + user_mac_list = nil + else + log(string.format("MACFilter: got user stat, total users=%d", #user_mac_list)) + end + end + + if #all_user_rules > 0 then + if user_mac_list then + local added_count = 0 + for _, mac in ipairs(user_mac_list) do + if not regular_mac_set[mac] then + added_count = added_count + 1 + end + regular_mac_set[mac] = true + end + log(string.format("MACFilter all-user range: %d active rules, add %d users into effective mac list", #all_user_rules, added_count)) + else + log("MACFilter all-user range: skip because user stat unavailable") + end + end + + if #duration_rules > 0 then + if not user_active_map or not user_mac_list then + log("MACFilter duration mode: failed to get user active minutes, skipping duration match this round") + else + log(string.format("MACFilter duration mode: begin match, users=%d, rules=%d", #user_mac_list, #duration_rules)) + for _, rule in ipairs(duration_rules) do + local target_macs = {} + if rule.mode == 1 then + for _, mac in ipairs(user_mac_list) do + table.insert(target_macs, mac) + end + elseif rule.mode == 2 and rule.user_mac and rule.user_mac ~= "" then + table.insert(target_macs, rule.user_mac) + end + + log(string.format("MACFilter duration rule %d (%s): mode=%d, target_macs=%d, duration_items=%d", + rule.id, rule.name, rule.mode, #target_macs, #(rule.duration_rules or {}))) + + for _, target_mac in ipairs(target_macs) do + local active_minutes = user_active_map[target_mac] or 0 + local exceeded = false + local weekday_hit = false + local min_remaining = nil + local exceeded_limit_minutes = 0 + local duration_rule_list = rule.duration_rules or {} + for _, duration_rule in ipairs(duration_rule_list) do + if weekday_match(duration_rule.weekdays, current_info.weekday) then + local limit_minutes = duration_rule.duration_minutes or 0 + if limit_minutes <= 0 then + weekday_hit = true + log(string.format("MACFilter duration check: rule_id=%d, mac=%s, limit=0(unlimited), skip block", + rule.id, target_mac)) + break + end + local remaining_minutes = limit_minutes - active_minutes + local current_exceeded = active_minutes > limit_minutes + weekday_hit = true + if min_remaining == nil or remaining_minutes < min_remaining then + min_remaining = remaining_minutes + end + log(string.format("MACFilter duration check: rule_id=%d, mac=%s, used=%dmin, limit=%dmin, remain=%dmin, exceeded=%s", + rule.id, target_mac, active_minutes, limit_minutes, remaining_minutes, tostring(current_exceeded))) + if current_exceeded then + exceeded = true + exceeded_limit_minutes = limit_minutes + break + end + end + end + if exceeded then + regular_mac_set[target_mac] = true + local detail = build_macfilter_rule_detail(rule, "duration") + detail.used_minutes = active_minutes + detail.limit_minutes = exceeded_limit_minutes + append_detail_for_mac(macfilter_detail_map, target_mac, detail) + log(string.format("MACFilter duration result: rule_id=%d, mac=%s, final=block", rule.id, target_mac)) + else + if weekday_hit then + log(string.format("MACFilter duration result: rule_id=%d, mac=%s, final=allow, remain=%dmin", + rule.id, target_mac, min_remaining or 0)) + else + log(string.format("MACFilter duration result: rule_id=%d, mac=%s, final=allow, reason=no weekday match", + rule.id, target_mac)) + end + end + end + end + end + end + + if #flow_rules > 0 then + if not user_flow_bytes_map or not user_mac_list then + log("MACFilter flow mode: failed to get user flow stats, skipping flow match this round") + else + log(string.format("MACFilter flow mode: begin match, users=%d, rules=%d", #user_mac_list, #flow_rules)) + for _, rule in ipairs(flow_rules) do + local target_macs = {} + if rule.mode == 1 then + for _, mac in ipairs(user_mac_list) do + table.insert(target_macs, mac) + end + elseif rule.mode == 2 and rule.user_mac and rule.user_mac ~= "" then + table.insert(target_macs, rule.user_mac) + end + + log(string.format("MACFilter flow rule %d (%s): mode=%d, target_macs=%d, flow_items=%d", + rule.id, rule.name, rule.mode, #target_macs, #(rule.flow_rules or {}))) + + for _, target_mac in ipairs(target_macs) do + local used_flow_bytes = user_flow_bytes_map[target_mac] or 0 + local used_flow_mb = used_flow_bytes / (1024 * 1024) + local exceeded = false + local weekday_hit = false + local min_remaining_mb = nil + local exceeded_limit_mb = 0 + local flow_rule_list = rule.flow_rules or {} + for _, flow_rule in ipairs(flow_rule_list) do + if weekday_match(flow_rule.weekdays, current_info.weekday) then + local limit_mb = tonumber(flow_rule.flow_mb) or 0 + if limit_mb <= 0 then + weekday_hit = true + log(string.format("MACFilter flow check: rule_id=%d, mac=%s, limit=0(unlimited), skip block", + rule.id, target_mac)) + break + end + local limit_bytes = limit_mb * 1024 * 1024 + local remaining_mb = limit_mb - used_flow_mb + local current_exceeded = used_flow_bytes > limit_bytes + weekday_hit = true + if min_remaining_mb == nil or remaining_mb < min_remaining_mb then + min_remaining_mb = remaining_mb + end + log(string.format("MACFilter flow check: rule_id=%d, mac=%s, used=%.2fMB, limit=%dMB, remain=%.2fMB, exceeded=%s", + rule.id, target_mac, used_flow_mb, limit_mb, remaining_mb, tostring(current_exceeded))) + if current_exceeded then + exceeded = true + exceeded_limit_mb = limit_mb + break + end + end + end + if exceeded then + regular_mac_set[target_mac] = true + local detail = build_macfilter_rule_detail(rule, "flow") + detail.used_mb = tonumber(string.format("%.2f", used_flow_mb)) or used_flow_mb + detail.limit_mb = exceeded_limit_mb + append_detail_for_mac(macfilter_detail_map, target_mac, detail) + log(string.format("MACFilter flow result: rule_id=%d, mac=%s, final=block", rule.id, target_mac)) + else + if weekday_hit then + log(string.format("MACFilter flow result: rule_id=%d, mac=%s, final=allow, remain=%.2fMB", + rule.id, target_mac, min_remaining_mb or 0)) + else + log(string.format("MACFilter flow result: rule_id=%d, mac=%s, final=allow, reason=no weekday match", + rule.id, target_mac)) + end + end + end + end + end + end + + if #blacklist_macs > 0 then + for _, blacklist_mac in ipairs(blacklist_macs) do + blacklist_mac_set[blacklist_mac] = true + append_detail_for_mac(macfilter_detail_map, blacklist_mac, { + rule_id = BLACKLIST_MAC_FILTER_RULE_ID, + rule_name = BLACKLIST_RULE_NAME, + mode = MACFILTER_RULE_MODE_SINGLE_USER, + time_mode = TIME_MODE_RANGE, + user_mac = blacklist_mac, + match_type = "blacklist" + }) + end + log(string.format("MACFilter blacklist: total=%d", #blacklist_macs)) + end + + local regular_mac_list = {} + for mac, _ in pairs(regular_mac_set) do + table.insert(regular_mac_list, mac) + end + table.sort(regular_mac_list) + + local blacklist_mac_list = {} + for mac, _ in pairs(blacklist_mac_set) do + table.insert(blacklist_mac_list, mac) + end + table.sort(blacklist_mac_list) + + if #duration_rules > 0 or #flow_rules > 0 or #all_user_rules > 0 or #blacklist_macs > 0 then + local regular_preview = table.concat(regular_mac_list, ",") + if string.len(regular_preview) > 512 then + regular_preview = string.sub(regular_preview, 1, 512) .. "..." + end + local blacklist_preview = table.concat(blacklist_mac_list, ",") + if string.len(blacklist_preview) > 512 then + blacklist_preview = string.sub(blacklist_preview, 1, 512) .. "..." + end + log(string.format("MACFilter effective mac summary: regular_count=%d, regular_macs=%s, blacklist_count=%d, blacklist_macs=%s", + #regular_mac_list, regular_preview, #blacklist_mac_list, blacklist_preview)) + end + + sync_effective_mac_rule(SINGLE_MAC_FILTER_RULE_ID, "MAC Filter (Effective List)", regular_mac_list) + sync_effective_mac_rule(BLACKLIST_MAC_FILTER_RULE_ID, "Internet Blacklist (Effective List)", blacklist_mac_list) + + for rule_id, state in pairs(macfilter_rules_state) do + if rule_id ~= SINGLE_MAC_FILTER_RULE_ID and rule_id ~= BLACKLIST_MAC_FILTER_RULE_ID then + local found = false + for _, rule in ipairs(rules) do + if rule.id == rule_id then + found = true + break + end + end + if not found then + log(string.format("MACFilter rule %d: removed from UCI, cleaning up state", rule_id)) + macfilter_rules_state[rule_id] = nil + end + end + end + + local merged_block_set = {} + for mac, _ in pairs(regular_mac_set) do + merged_block_set[mac] = true + end + for mac, _ in pairs(blacklist_mac_set) do + merged_block_set[mac] = true + end + + return merged_block_set, macfilter_detail_map +end + +local function get_uci_enable(config, section, option) + local uci_cursor = uci.cursor() + local value = tonumber(uci_cursor:get(config, section, option)) or 0 + uci_cursor:unload(config) + return value +end + +local function apply_appfilter_enable(enable) + log(string.format("=== Applying AppFilter enable: %d ===", enable)) + local proc_file = "/proc/sys/fwx/appfilter_enable" + local file = io.open(proc_file, "w") + if file then + file:write(tostring(enable)) + file:close() + log(string.format("AppFilter enable set to %d successfully", enable)) + return true + else + log(string.format("Failed to open %s for writing", proc_file)) + return false + end +end + +local function apply_macfilter_enable(enable) + log(string.format("=== Applying MACFilter enable: %d ===", enable)) + local proc_file = "/proc/sys/fwx/macfilter_enable" + local file = io.open(proc_file, "w") + if file then + file:write(tostring(enable)) + file:close() + log(string.format("MACFilter enable set to %d successfully", enable)) + return true + else + log(string.format("Failed to open %s for writing", proc_file)) + return false + end +end + +local function check_and_apply_appfilter_enable() + local current_enable = get_uci_enable("fwx", "appfilter", "enable") + if appfilter_enable_state == nil or appfilter_enable_state ~= current_enable then + log(string.format("AppFilter enable changed: %s -> %d", + appfilter_enable_state == nil and "nil" or tostring(appfilter_enable_state), current_enable)) + if apply_appfilter_enable(current_enable) then + appfilter_enable_state = current_enable + end + end +end + +local function check_and_apply_macfilter_enable() + local current_enable = get_uci_enable("fwx", "macfilter", "enable") + if macfilter_enable_state == nil or macfilter_enable_state ~= current_enable then + log(string.format("MACFilter enable changed: %s -> %d", + macfilter_enable_state == nil and "nil" or tostring(macfilter_enable_state), current_enable)) + if apply_macfilter_enable(current_enable) then + macfilter_enable_state = current_enable + end + end +end + +local function apply_record_enable(enable) + log(string.format("=== Applying Record enable: %d ===", enable)) + local proc_file = "/proc/sys/fwx/record_enable" + local file = io.open(proc_file, "w") + if file then + file:write(tostring(enable)) + file:close() + log(string.format("Record enable set to %d successfully", enable)) + return true + else + log(string.format("Failed to open %s for writing", proc_file)) + return false + end +end + +local function check_and_apply_record_enable() + local current_enable = get_uci_enable("fwx", "record", "enable") + if record_enable_state == nil or record_enable_state ~= current_enable then + log(string.format("Record enable changed: %s -> %d", + record_enable_state == nil and "nil" or tostring(record_enable_state), current_enable)) + if apply_record_enable(current_enable) then + record_enable_state = current_enable + end + end +end + +local function check_state_file(file_path) + local file = io.open(file_path, "r") + if not file then + return false + end + + local content = file:read("*line") + file:close() + + return (content == "1") +end + +local function reset_state_file(file_path) + local file = io.open(file_path, "w") + if file then + file:write("0") + file:close() + return true + end + return false +end + +local function check_reinit_flags() + local appfilter_reinit = check_state_file(APPFILTER_STATE_FILE) + local macfilter_reinit = check_state_file(MACFILTER_STATE_FILE) + local appfilter_whitelist_reinit = check_state_file(APPFILTER_WHITELIST_STATE_FILE) + local macfilter_whitelist_reinit = check_state_file(MACFILTER_WHITELIST_STATE_FILE) + local record_whitelist_reinit = check_state_file(RECORD_WHITELIST_STATE_FILE) + + return appfilter_reinit, macfilter_reinit, appfilter_whitelist_reinit, macfilter_whitelist_reinit, record_whitelist_reinit +end + +local function flush_appfilter_rules() + log("=== Flushing AppFilter rules ===") + local json_str = '{"api":"flush_app_filter_rule","data":{}}' + if write_to_dev_fwx(json_str) then + log("AppFilter rules flushed successfully") + return true + else + log("Failed to flush AppFilter rules") + return false + end +end + +local function flush_macfilter_rules() + log("=== Flushing MACFilter rules ===") + local json_str = '{"api":"flush_mac_filter_rule","data":{}}' + if write_to_dev_fwx(json_str) then + log("MACFilter rules flushed successfully") + return true + else + log("Failed to flush MACFilter rules") + return false + end +end + +local function flush_appfilter_whitelist() + log("=== Flushing AppFilter whitelist ===") + local json_str = '{"api":"flush_app_filter_whitelist","data":{}}' + if write_to_dev_fwx(json_str) then + log("AppFilter whitelist flushed successfully") + return true + else + log("Failed to flush AppFilter whitelist") + return false + end +end + +local function flush_macfilter_whitelist() + log("=== Flushing MACFilter whitelist ===") + local json_str = '{"api":"flush_mac_filter_whitelist","data":{}}' + if write_to_dev_fwx(json_str) then + log("MACFilter whitelist flushed successfully") + return true + else + log("Failed to flush MACFilter whitelist") + return false + end +end + +local function load_appfilter_whitelist() + log("=== Loading AppFilter whitelist from UCI ===") + + local uci_cursor = uci.cursor() + local mac_list = {} + + uci_cursor:foreach("appfilter_whitelist", "whitelist_mac", function(section) + local mac = section.mac or "" + if mac and mac ~= "" then + table.insert(mac_list, mac) + end + end) + + uci_cursor:unload("appfilter_whitelist") + + log(string.format("AppFilter whitelist: loaded %d MAC addresses", #mac_list)) + return mac_list +end + +local function load_macfilter_whitelist() + log("=== Loading MACFilter whitelist from UCI ===") + + local uci_cursor = uci.cursor() + local mac_list = {} + + uci_cursor:foreach("macfilter_whitelist", "whitelist_mac", function(section) + local mac = section.mac or "" + if mac and mac ~= "" then + table.insert(mac_list, mac) + end + end) + + uci_cursor:unload("macfilter_whitelist") + + log(string.format("MACFilter whitelist: loaded %d MAC addresses", #mac_list)) + return mac_list +end + +local function load_record_whitelist() + log("=== Loading Record whitelist from UCI ===") + + local uci_cursor = uci.cursor() + local mac_list = {} + local mac_set = {} + local function append_section_macs(section) + local whitelist = section.whitelist + if type(whitelist) == "table" then + for _, mac in ipairs(whitelist) do + if mac and mac ~= "" and not mac_set[mac] then + mac_set[mac] = true + table.insert(mac_list, mac) + end + end + elseif type(whitelist) == "string" then + if whitelist ~= "" and not mac_set[whitelist] then + mac_set[whitelist] = true + table.insert(mac_list, whitelist) + end + end + end + + uci_cursor:foreach("fwx_record", "whitelist", function(section) + append_section_macs(section) + end) + + if #mac_list == 0 then + uci_cursor:foreach("fwx_record", "record", function(section) + append_section_macs(section) + end) + end + + uci_cursor:unload("fwx_record") + + log(string.format("Record whitelist: loaded %d MAC addresses", #mac_list)) + return mac_list +end + +local function apply_appfilter_whitelist(mac_list) + log(string.format("=== Applying AppFilter whitelist, count=%d ===", #mac_list)) + + flush_appfilter_whitelist() + + if #mac_list > 0 then + local mac_strs = {} + for _, mac in ipairs(mac_list) do + table.insert(mac_strs, string.format('"%s"', mac)) + end + local mac_array_str = "[" .. table.concat(mac_strs, ",") .. "]" + + local json_str = string.format('{"api":"add_app_filter_whitelist","data":{"mac_list":%s}}', mac_array_str) + if write_to_dev_fwx(json_str) then + log(string.format("AppFilter whitelist applied successfully: %d MACs", #mac_list)) + return true + else + log("Failed to apply AppFilter whitelist") + return false + end + else + log("AppFilter whitelist is empty, no MACs to add") + return true + end +end + +local function apply_macfilter_whitelist(mac_list) + log(string.format("=== Applying MACFilter whitelist, count=%d ===", #mac_list)) + + flush_macfilter_whitelist() + + if #mac_list > 0 then + local mac_strs = {} + for _, mac in ipairs(mac_list) do + table.insert(mac_strs, string.format('"%s"', mac)) + end + local mac_array_str = "[" .. table.concat(mac_strs, ",") .. "]" + + local json_str = string.format('{"api":"add_mac_filter_whitelist","data":{"mac_list":%s}}', mac_array_str) + if write_to_dev_fwx(json_str) then + log(string.format("MACFilter whitelist applied successfully: %d MACs", #mac_list)) + return true + else + log("Failed to apply MACFilter whitelist") + return false + end + else + log("MACFilter whitelist is empty, no MACs to add") + return true + end +end + +local function apply_record_whitelist(mac_list) + log(string.format("=== Applying Record whitelist, count=%d ===", #mac_list)) + + local proc_file = "/proc/sys/fwx/record_whitelist" + local file = io.open(proc_file, "w") + if not file then + log(string.format("Failed to open %s for writing", proc_file)) + return false + end + + local content = "" + if #mac_list > 0 then + content = table.concat(mac_list, ",") + else + content = "\n" + end + + file:write(content) + file:close() + log(string.format("Record whitelist applied successfully: %d MACs", #mac_list)) + return true +end + +local function interruptible_sleep(seconds) + for i = 1, seconds do + local result = os.execute("sleep 1") + if result ~= 0 and result ~= true then + return + end + end +end + +local function flush_all_rules() + log("=== Flushing all rules before initialization ===") + + local json_str = '{"api":"flush_mac_filter_rule","data":{}}' + if write_to_dev_fwx(json_str) then + log("MAC filter rules flushed") + else + log("Failed to flush MAC filter rules") + end + + json_str = '{"api":"flush_app_filter_rule","data":{}}' + if write_to_dev_fwx(json_str) then + log("App filter rules flushed") + else + log("Failed to flush App filter rules") + end + + json_str = '{"api":"flush_mac_filter_whitelist","data":{}}' + if write_to_dev_fwx(json_str) then + log("MAC filter whitelist flushed") + else + log("Failed to flush MAC filter whitelist") + end + + json_str = '{"api":"flush_app_filter_whitelist","data":{}}' + if write_to_dev_fwx(json_str) then + log("App filter whitelist flushed") + else + log("Failed to flush App filter whitelist") + end + + if apply_record_whitelist({}) then + log("Record whitelist flushed") + else + log("Failed to flush Record whitelist") + end + + log("All rules flushed successfully") + return true +end + +local function initialize_rules() + flush_all_rules() + check_and_apply_appfilter_enable() + check_and_apply_macfilter_enable() + check_and_apply_record_enable() + + init_effective_mac_rules() + + local appfilter_rules = load_appfilter_rules() + for _, rule in ipairs(appfilter_rules) do + appfilter_rules_state[rule.id] = { + active = false, + name = rule.name, + mode = rule.mode, + filter_quic = tonumber(rule.filter_quic) or 0 + } + end + + local macfilter_rules = load_macfilter_rules() + for _, rule in ipairs(macfilter_rules) do + macfilter_rules_state[rule.id] = { + active = false, + mode = rule.mode, + name = rule.name, + user_mac = rule.user_mac + } + end + + log(string.format("Initialized: %d AppFilter rules, %d MACFilter rules", + #appfilter_rules, #macfilter_rules)) + + log("=== Loading whitelists ===") + local appfilter_whitelist = load_appfilter_whitelist() + apply_appfilter_whitelist(appfilter_whitelist) + + local macfilter_whitelist = load_macfilter_whitelist() + apply_macfilter_whitelist(macfilter_whitelist) + + local record_whitelist = load_record_whitelist() + apply_record_whitelist(record_whitelist) + + log("Whitelists initialized successfully") +end + +local function main_loop() + log("Rule manager started") + + initialize_rules() + + local running = true + + while running do + local appfilter_reinit, macfilter_reinit, appfilter_whitelist_reinit, macfilter_whitelist_reinit, record_whitelist_reinit = check_reinit_flags() + + if appfilter_whitelist_reinit then + log("=== AppFilter whitelist state file detected change, reloading ===") + local appfilter_whitelist = load_appfilter_whitelist() + apply_appfilter_whitelist(appfilter_whitelist) + if reset_state_file(APPFILTER_WHITELIST_STATE_FILE) then + log("AppFilter whitelist state file reset to 0") + else + log("Failed to reset AppFilter whitelist state file") + end + end + + if macfilter_whitelist_reinit then + log("=== MACFilter whitelist state file detected change, reloading ===") + local macfilter_whitelist = load_macfilter_whitelist() + apply_macfilter_whitelist(macfilter_whitelist) + if reset_state_file(MACFILTER_WHITELIST_STATE_FILE) then + log("MACFilter whitelist state file reset to 0") + else + log("Failed to reset MACFilter whitelist state file") + end + end + + if record_whitelist_reinit then + log("=== Record whitelist state file detected change, reloading ===") + local record_whitelist = load_record_whitelist() + apply_record_whitelist(record_whitelist) + if reset_state_file(RECORD_WHITELIST_STATE_FILE) then + log("Record whitelist state file reset to 0") + else + log("Failed to reset Record whitelist state file") + end + end + + if appfilter_reinit then + log("=== AppFilter rules state file detected change, reinitializing ===") + flush_appfilter_rules() + + check_and_apply_appfilter_enable() + + appfilter_rules_state = {} + local appfilter_rules = load_appfilter_rules() + for _, rule in ipairs(appfilter_rules) do + appfilter_rules_state[rule.id] = { + active = false, + name = rule.name, + mode = rule.mode, + filter_quic = tonumber(rule.filter_quic) or 0 + } + end + log(string.format("AppFilter rules reinitialized: %d rules", #appfilter_rules)) + if reset_state_file(APPFILTER_STATE_FILE) then + log("AppFilter state file reset to 0") + else + log("Failed to reset AppFilter state file") + end + end + + if macfilter_reinit then + flush_macfilter_rules() + + check_and_apply_macfilter_enable() + + macfilter_rules_state = {} + init_effective_mac_rules() + local macfilter_rules = load_macfilter_rules() + for _, rule in ipairs(macfilter_rules) do + macfilter_rules_state[rule.id] = { + active = false, + mode = rule.mode, + name = rule.name, + user_mac = rule.user_mac + } + end + log(string.format("MACFilter rules reinitialized: %d rules", #macfilter_rules)) + if reset_state_file(MACFILTER_STATE_FILE) then + log("MACFilter state file reset to 0") + else + log("Failed to reset MACFilter state file") + end + end + + local current_info = get_current_time_info() + local appfilter_effective_mac_set = {} + local appfilter_all_users_active = false + local appfilter_detail_map = {} + local appfilter_all_user_rule_details = {} + local macfilter_block_set = {} + local macfilter_detail_map = {} + local macfilter_all_user_rule_details = {} + + local ok, err = pcall(function() + appfilter_effective_mac_set, appfilter_all_users_active, appfilter_detail_map, appfilter_all_user_rule_details = + process_appfilter_rules(current_info) + macfilter_block_set, macfilter_detail_map, macfilter_all_user_rule_details = process_macfilter_rules(current_info) + macfilter_block_set = macfilter_block_set or {} + macfilter_detail_map = macfilter_detail_map or {} + macfilter_all_user_rule_details = macfilter_all_user_rule_details or {} + end) + + if not ok then + log("ERROR processing rules: " .. tostring(err)) + end + + local status_ok, status_err = pcall(function() + write_user_parental_control_status( + appfilter_effective_mac_set or {}, + appfilter_all_users_active or false, + macfilter_block_set or {}, + appfilter_detail_map or {}, + appfilter_all_user_rule_details or {}, + macfilter_all_user_rule_details or {}, + macfilter_detail_map or {} + ) + end) + if not status_ok then + log("ERROR writing parental control status: " .. tostring(status_err)) + end + interruptible_sleep(CHECK_INTERVAL) + end +end + +if arg[0] and arg[0]:match("rule_manager") then + main_loop() +end diff --git a/open-app-filter/src/Makefile b/open-app-filter/src/Makefile index 2236babd..7b8093ba 100644 --- a/open-app-filter/src/Makefile +++ b/open-app-filter/src/Makefile @@ -1,6 +1,6 @@ -OBJS:=appfilter_user.o appfilter_netlink.o appfilter_ubus.o appfilter_config.o utils.o main.o -EXEC:=oafd -all: $(OBJS) - $(CC) -o $(EXEC) $(OBJS) $(LIBS) +OBJS:=fwx_user.o fwx_netlink.o fwx_ubus.o fwx_stat.o fwx_config.o fwx_feature.o fwx_feature_online.o fwx_custom_feature.o fwx_utils.o main.o fwx_app_filter.o fwx_mac_filter.o fwx_record.o fwx_common.o fwx_system.o fwx_network.o fwx_firewall.o fwx_wireless.o check_main.o fwx_uci.o +EXEC:=oafd +all: $(OBJS) + $(CC) -o $(EXEC) $(OBJS) $(LIBS) clean: rm $(EXEC) *.o diff --git a/open-app-filter/src/appfilter.h b/open-app-filter/src/appfilter.h deleted file mode 100644 index 5393fd00..00000000 --- a/open-app-filter/src/appfilter.h +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef __APPFILTER_H__ -#define __APPFILTER_H__ -#define MIN_INET_ADDR_LEN 7 - -#include -#include -#include -#include -#include "utils.h" - -#define LOG_FILE_PATH "/tmp/log/appfilter.log" -#define OAF_VERSION "6.1.8" - -typedef enum { - LOG_LEVEL_ERROR, - LOG_LEVEL_WARN, - LOG_LEVEL_INFO, - LOG_LEVEL_DEBUG -} LogLevel; - -extern int current_log_level; - - static void af_log(LogLevel level, const char *format, ...){ - if (level > current_log_level) - return; - - FILE *log_file = fopen(LOG_FILE_PATH, "a"); - if (!log_file) { - perror("Failed to open log file"); - return; - } - - time_t now = time(NULL); - struct tm *t = localtime(&now); - char time_str[20]; - strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", t); - - const char *level_str; - switch (level) { - case LOG_LEVEL_DEBUG: level_str = "DEBUG"; break; - case LOG_LEVEL_INFO: level_str = "INFO"; break; - case LOG_LEVEL_WARN: level_str = "WARN"; break; - case LOG_LEVEL_ERROR: level_str = "ERROR"; break; - default: level_str = "UNKNOWN"; break; - } - - fprintf(log_file, "[%s] [%s] ", time_str, level_str); - - va_list args; - va_start(args, format); - vfprintf(log_file, format, args); - va_end(args); - fclose(log_file); -} - -#define LOG_DEBUG(format, ...) af_log(LOG_LEVEL_DEBUG, format, ##__VA_ARGS__) -#define LOG_INFO(format, ...) af_log(LOG_LEVEL_INFO, format, ##__VA_ARGS__) -#define LOG_WARN(format, ...) af_log(LOG_LEVEL_WARN, format, ##__VA_ARGS__) -#define LOG_ERROR(format, ...) af_log(LOG_LEVEL_ERROR, format, ##__VA_ARGS__) - - - -#define MAX_TIME_LIST_LEN 1024 -#define MAX_TIME_LIST 64 -typedef struct af_time -{ - int hour; - int min; -} af_time_t; - -typedef struct af_global_config_t{ - int enable; - int user_mode; - int work_mode; - int record_enable; - int disable_hnat; - int auto_load_engine; - int tcp_rst; - int disable_quic; - int app_filter_mode; // 0 = specified apps, 1 = all apps - char lan_ifname[16]; -}af_global_config_t; - -typedef struct time_config{ - af_time_t start_time; - af_time_t end_time; - int days[7]; -}time_config_t; - -typedef struct daily_limit_config { - int enable; - int am_time; - int pm_time; -} daily_limit_config_t; - -typedef struct af_time_config_t{ - int time_mode; - time_config_t seg_time; - int deny_time; - int allow_time; - int days[7]; - int time_num; - time_config_t time_list[MAX_TIME_LIST]; - daily_limit_config_t daily_limit[7]; -}af_time_config_t; - -typedef struct af_config_t{ - af_global_config_t global; - af_time_config_t time; -}af_config_t; - -typedef struct af_run_time_status{ - int deny_time; - int allow_time; - int filter; - int match_time; - int remain_time; - int used_time; - int period_blocked; -}af_run_time_status_t; - - -extern af_config_t g_af_config; -#endif diff --git a/open-app-filter/src/appfilter_config.h b/open-app-filter/src/appfilter_config.h deleted file mode 100644 index 887e52a0..00000000 --- a/open-app-filter/src/appfilter_config.h +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright (C) 2020 Derry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -#ifndef __APPFILTER_CONFIG_H__ -#define __APPFILTER_CONFIG_H__ -#include - -#define MAX_SUPPORT_APP_NUM 1024 -#define MAX_CLASS_NAME_LEN 32 -#define MAX_PARAM_LIST_LEN 1024 - -#include "appfilter_user.h" -extern int g_cur_class_num; -extern int g_app_count; -extern char CLASS_NAME_TABLE[MAX_APP_TYPE][MAX_CLASS_NAME_LEN]; - -typedef struct app_name_info -{ - int id; - char name[64]; -} app_name_info_t; -void init_app_name_table(void); -void init_app_class_name_table(void); -char *get_app_name_by_id(int id); - -int appfilter_config_alloc(void); - -int appfilter_config_free(void); -int config_get_appfilter_enable(void); -int config_get_lan_ip(char *lan_ip, int len); -int config_get_lan_mask(char *lan_mask, int len); -int af_uci_delete(struct uci_context *ctx, char *key); -int af_uci_add_list(struct uci_context *ctx, char *key, char *value); -int af_uci_add_int_list(struct uci_context *ctx, char *key, int value); -int af_uci_del_list(struct uci_context *ctx, char *key, char *value); -int af_uci_get_list_value(struct uci_context *ctx, char *key, char *output, int out_len, char *delimt); -int af_uci_set_value(struct uci_context *ctx, char *key, char *value); -int af_uci_set_int_value(struct uci_context *ctx, char *key, int value); -int af_uci_del_array_value(struct uci_context *ctx, char *key_fmt, int index); -int af_uci_set_array_value(struct uci_context *ctx, char *key_fmt, int index, char *value); -int af_get_uci_list_num(struct uci_context * ctx, char *package, char *section); -int af_uci_get_array_value(struct uci_context *ctx, char *key_fmt, int index, char *output, int out_len); -int af_uci_get_int_value(struct uci_context *ctx, char *key); -int af_uci_get_value(struct uci_context *ctx, char *key, char *output, int out_len); -int af_uci_add_section(struct uci_context * ctx, char *package_name, char *section); -int af_uci_commit(struct uci_context *ctx, const char * package); -char *get_app_name_by_id(int id); - -#endif - diff --git a/open-app-filter/src/appfilter_netlink.c b/open-app-filter/src/appfilter_netlink.c deleted file mode 100644 index 87e1a817..00000000 --- a/open-app-filter/src/appfilter_netlink.c +++ /dev/null @@ -1,279 +0,0 @@ -/* -Copyright (C) 2020 Derry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "appfilter_user.h" -#include "appfilter_netlink.h" -#include "appfilter.h" -#include "appfilter_config.h" - -#define MAX_NL_RCV_BUF_SIZE 4096 - -#define REPORT_INTERVAL_SECS 60 -extern int hash_appid(int appid); -extern unsigned int g_feature_update_time; -void appfilter_nl_handler(struct uloop_fd *u, unsigned int ev) -{ - int ret; - int i; - char buf[MAX_NL_RCV_BUF_SIZE]; - struct sockaddr_nl nladdr; - struct iovec iov = {buf, sizeof(buf)}; - struct nlmsghdr *h; - int type; - int id; - char *mac = NULL; - u_int32_t cur_time = get_timestamp(); - - struct msghdr msg = { - .msg_name = &nladdr, - .msg_namelen = sizeof(nladdr), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - - do - { - ret = recvmsg(u->fd, &msg, 0); - } while ((-1 == ret) && (EINTR == errno)); - - if (ret < 0) - { - printf("recv msg error\n"); - return; - } - else if (0 == ret) - { - return; - } - - h = (struct nlmsghdr *)buf; - char *kmsg = (char *)NLMSG_DATA(h); - struct af_msg_hdr *af_hdr = (struct af_msg_hdr *)kmsg; - if (af_hdr->magic != 0xa0b0c0d0) - { - printf("magic error %x\n", af_hdr->magic); - return; - } - - if (af_hdr->len <= 0 || af_hdr->len >= MAX_OAF_NETLINK_MSG_LEN) - { - printf("data len error\n"); - return; - } - - char *kdata = kmsg + sizeof(struct af_msg_hdr); - struct json_object *root = json_tokener_parse(kdata); - if (!root) - { - printf("parse json failed:%s", kdata); - return; - } - - LOG_DEBUG("report %s\n", kdata); - struct json_object *mac_obj = json_object_object_get(root, "mac"); - - if (!mac_obj) - { - printf("parse mac obj failed\n"); - json_object_put(root); - return; - } - - mac = json_object_get_string(mac_obj); - - dev_node_t *node = find_dev_node(mac); - - if (!node) - { - node = add_dev_node(mac); - if (!node) - { - goto EXIT; - } - } - - struct json_object *ip_obj = json_object_object_get(root, "ip"); - if (ip_obj) - strncpy(node->ip, json_object_get_string(ip_obj), sizeof(node->ip)); - - - struct json_object *active_obj = json_object_object_get(root, "active"); - if (active_obj) { - node->active = json_object_get_int(active_obj); - if (node->active) - { - // 根据当前时间判断是上午还是下午,分别累加 - time_t now = time(NULL); - struct tm *tm_info = localtime(&now); - int current_hour = tm_info->tm_hour; - - if (current_hour < 12) { - // 上午:0:00-11:59 - node->today_am_active_time += 1; //min - } else { - // 下午:12:00-23:59 - node->today_pm_active_time += 1; //min - } - } - - } - - - struct json_object *up_flow_obj = json_object_object_get(root, "up_flow"); - struct json_object *down_flow_obj = json_object_object_get(root, "down_flow"); - unsigned long long total_up_bytes = 0; - unsigned long long total_down_bytes = 0; - - if (up_flow_obj) { - node->today_up_bytes += (unsigned long long)json_object_get_int64(up_flow_obj) * 1024; - } - - if (down_flow_obj) { - node->today_down_bytes += (unsigned long long)json_object_get_int64(down_flow_obj) * 1024; - } - - - struct json_object *visit_array = json_object_object_get(root, "visit_info"); - if (!visit_array) - { - goto EXIT; - } - - - for (i = 0; i < json_object_array_length(visit_array); i++) - { - struct json_object *visit_obj = json_object_array_get_idx(visit_array, i); - struct json_object *appid_obj = json_object_object_get(visit_obj, "appid"); - struct json_object *action_obj = json_object_object_get(visit_obj, "latest_action"); - - // old appid may be not in the feature list - if (cur_time - g_feature_update_time < 300){ - if (strlen(get_app_name_by_id(json_object_get_int(appid_obj))) == 0){ - LOG_INFO("ignore appid %d because it is not in the feature list\n", json_object_get_int(appid_obj)); - continue; - } - } - - int appid = json_object_get_int(appid_obj); - int action = json_object_get_int(action_obj); - - type = appid / 1000; - id = appid % 1000; - if (id <= 0 || type <= 0) - continue; - node->stat[type - 1][id - 1].total_time += REPORT_INTERVAL_SECS; - int hash = hash_appid(appid); - visit_info_t *head = node->visit_htable[hash]; - visit_info_t *p = head; - while(p){ - if((p->appid == appid) && (cur_time - p->latest_time < 300)){ - LOG_DEBUG("match appid = %d\n", appid, cur_time - p->latest_time); - break; - } - p = p->next; - } - if (!p){ - p = (visit_info_t *)calloc(1, sizeof(visit_info_t)); - p->appid = appid; - p->next = NULL; - p->first_time = cur_time; - add_visit_info_node(&node->visit_htable[hash], p); - } - p->action = action; - p->latest_time = cur_time; - } -EXIT: - json_object_put(root); -} - -#define MAX_NL_MSG_LEN 1024 -int send_msg_to_kernel(int fd, void *msg, int len) -{ - struct sockaddr_nl saddr, daddr; - memset(&daddr, 0, sizeof(daddr)); - daddr.nl_family = AF_NETLINK; - daddr.nl_pid = 0; // to kernel - daddr.nl_groups = 0; - int ret = 0; - struct nlmsghdr *nlh = NULL; - nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_NL_MSG_LEN)); - nlh->nlmsg_len = NLMSG_SPACE(MAX_NL_MSG_LEN); - nlh->nlmsg_flags = 0; - nlh->nlmsg_type = 0; - nlh->nlmsg_seq = 0; - nlh->nlmsg_pid = DEFAULT_USR_NL_PID; - - char msg_buf[MAX_NL_MSG_LEN] = {0}; - struct af_msg_hdr *hdr = (struct af_msg_hdr *)msg_buf; - hdr->magic = 0xa0b0c0d0; - hdr->len = len; - char *p_data = msg_buf + sizeof(struct af_msg_hdr); - memcpy(p_data, msg, len); - - memcpy(NLMSG_DATA(nlh), msg_buf, len + sizeof(struct af_msg_hdr)); - - ret = sendto(fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl)); - free(nlh); - if (!ret) - { - perror("sendto error\n"); - return -1; - } - - return 0; -} - -int appfilter_nl_init(void) -{ - int fd; - struct sockaddr_nl nls; - fd = socket(AF_NETLINK, SOCK_RAW, OAF_NETLINK_ID); - if (fd < 0) - { - LOG_DEBUG("Connect netlink %d failed %s\n", OAF_NETLINK_ID, strerror(errno)); - return -1; - } - memset(&nls, 0, sizeof(struct sockaddr_nl)); - nls.nl_pid = DEFAULT_USR_NL_PID; - nls.nl_groups = 0; - nls.nl_family = AF_NETLINK; - - if (bind(fd, (void *)&nls, sizeof(struct sockaddr_nl))) - { - LOG_DEBUG("Bind failed %s\n", strerror(errno)); - return -1; - } - - return fd; -} diff --git a/open-app-filter/src/appfilter_netlink.h b/open-app-filter/src/appfilter_netlink.h deleted file mode 100644 index 01e12122..00000000 --- a/open-app-filter/src/appfilter_netlink.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright (C) 2020 Derry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -#ifndef __APPFILTER_NETLINK_H__ -#define __APPFILTER_NETLINK_H__ -#define DEFAULT_USR_NL_PID 999 -#define OAF_NETLINK_ID 29 -#define MAX_OAF_NETLINK_MSG_LEN 1024 -#define MAX_AF_MSG_DATA_LEN 800 -#define MAX_FEATURE_LINE_LEN 600 - -struct af_msg_hdr -{ - int magic; - int len; -}; - -enum E_MSG_TYPE -{ - AF_MSG_INIT, - AF_MSG_ADD_FEATURE, - AF_MSG_CLEAN_FEATURE, - AF_MSG_MAX -}; - -typedef struct af_msg -{ - int action; -} af_msg_t; - -typedef struct af_feature_msg{ - af_msg_t hdr; - char feature[MAX_FEATURE_LINE_LEN]; -} af_feature_msg_t; - -int appfilter_nl_init(void); -void appfilter_nl_handler(struct uloop_fd *u, unsigned int ev); -int send_msg_to_kernel(int fd, void *msg, int len); -#endif \ No newline at end of file diff --git a/open-app-filter/src/appfilter_ubus.c b/open-app-filter/src/appfilter_ubus.c deleted file mode 100644 index ad9b6852..00000000 --- a/open-app-filter/src/appfilter_ubus.c +++ /dev/null @@ -1,2485 +0,0 @@ -/* -Copyright (C) 2020 Derry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "appfilter_user.h" -#include "appfilter_config.h" -#include -#include "appfilter.h" -#include "utils.h" - -extern int g_oaf_config_change; -int g_enable_agent = 0; - -struct ubus_context *ubus_ctx = NULL; -static struct blob_buf b; - -extern char *format_time(int timetamp); - -void reload_oaf_rule(){ - system("/usr/bin/oaf_rule reload"); -} - -void get_hostname_by_mac(char *mac, char *hostname) -{ - if (!mac || !hostname) - return; - FILE *fp = fopen("/tmp/dhcp.leases", "r"); - if (!fp) - { - printf("open dhcp lease file....failed\n"); - return; - } - char line_buf[256] = {0}; - while (fgets(line_buf, sizeof(line_buf), fp)) - { - char hostname_buf[128] = {0}; - char mac_buf[32] = {0}; - sscanf(line_buf, "%*s %s %*s %s", mac_buf, hostname_buf); - if (0 == strcmp(mac, mac_buf)) - { - strcpy(hostname, hostname_buf); - } - } - fclose(fp); -} - -int check_app_icon_exist(int app_id) -{ - char icon_path[512]; - snprintf(icon_path, sizeof(icon_path), "/www/luci-static/resources/app_icons/%d.png", app_id); - int with_icon = access(icon_path, F_OK) == 0 ? 1 : 0; - return with_icon; -} - -void ubus_dump_visit_list(struct blob_buf *b, char *mac) -{ - int i, j; - void *c, *array; - void *t; - void *s; - - array = blobmsg_open_array(b, "dev_list"); - - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - if (mac && strcmp(mac, node->mac)) - { - node = node->next; - continue; - } - t = blobmsg_open_table(b, NULL); - blobmsg_add_string(b, "hostname", "unknown"); - blobmsg_add_string(b, "mac", node->mac); - blobmsg_add_string(b, "ip", node->ip); - void *visit_array; - - visit_array = blobmsg_open_array(b, "visit_info"); - for (j = 0; j < MAX_VISIT_HASH_SIZE; j++) - { - visit_info_t *p_info = node->visit_htable[j]; - while (p_info) - { - char *first_time_str = format_time(p_info->first_time); - char *latest_time_str = format_time(p_info->latest_time); - int total_time = p_info->latest_time - p_info->first_time; - s = blobmsg_open_table(b, NULL); - blobmsg_add_string(b, "appname", "unknown"); - blobmsg_add_u32(b, "appid", p_info->appid); - blobmsg_add_u32(b, "latest_action", p_info->action); - blobmsg_add_u32(b, "first_time", p_info->first_time); - blobmsg_add_u32(b, "latest_time", p_info->latest_time); - blobmsg_close_table(b, s); - if (first_time_str) - free(first_time_str); - if (latest_time_str) - free(latest_time_str); - p_info = p_info->next; - } - } - - blobmsg_close_array(b, visit_array); - blobmsg_close_table(b, t); - node = node->next; - } - } - blobmsg_close_array(b, array); -} - -// Function to compare JSON objects based on the "lt" field -int compare_lt(const void *a, const void *b) { - struct json_object *obj_a = *(struct json_object **)a; - struct json_object *obj_b = *(struct json_object **)b; - - struct json_object *lt_a, *lt_b; - json_object_object_get_ex(obj_a, "lt", <_a); - json_object_object_get_ex(obj_b, "lt", <_b); - - int lt_val_a = json_object_get_int(lt_a); - int lt_val_b = json_object_get_int(lt_b); - - return lt_val_b - lt_val_a; -} - - -static int -appfilter_handle_dev_visit_list(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - int i, j; - struct json_object *root_obj = json_object_new_object(); - struct json_object *visit_array = json_object_new_array(); - int page = 0; - int page_size = 20; // Default page size - - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) - { - printf("format json failed\n"); - return 0; - } - - printf("msg_obj_str:%s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); - if (!mac_obj) - { - printf("mac is null\n"); - json_object_put(req_obj); - free(msg_obj_str); - return 0; - } - - // Parse pagination parameters - struct json_object *page_obj = json_object_object_get(req_obj, "page"); - struct json_object *page_size_obj = json_object_object_get(req_obj, "page_size"); - if (page_obj) { - page = json_object_get_int(page_obj); - } - if (page_size_obj) { - page_size = json_object_get_int(page_size_obj); - if (page_size <= 0) { - page_size = 20; // Default to 20 if invalid - } - } - - char *mac = json_object_get_string(mac_obj); - dev_node_t *node = find_dev_node(mac); - - if (!node) - { - printf("not found mac:%s\n", mac); - json_object_put(req_obj); - free(msg_obj_str); - return 0; - } - - json_object_object_add(root_obj, "hostname", json_object_new_string(node->hostname)); - json_object_object_add(root_obj, "mac", json_object_new_string(node->mac)); - json_object_object_add(root_obj, "ip", json_object_new_string(node->ip)); - - for (j = 0; j < MAX_VISIT_HASH_SIZE; j++) - { - visit_info_t *p_info = node->visit_htable[j]; - while (p_info) - { - char *first_time_str = format_time(p_info->first_time); - char *latest_time_str = format_time(p_info->latest_time); - int total_time = p_info->latest_time - p_info->first_time; - if (strlen(get_app_name_by_id(p_info->appid)) == 0){ - p_info = p_info->next; - if (first_time_str) - free(first_time_str); - if (latest_time_str) - free(latest_time_str); - continue; - } - struct json_object *visit_obj = json_object_new_object(); - json_object_object_add(visit_obj, "name", json_object_new_string(get_app_name_by_id(p_info->appid))); - json_object_object_add(visit_obj, "id", json_object_new_int(p_info->appid)); - if (check_app_icon_exist(p_info->appid)) { - json_object_object_add(visit_obj, "icon", json_object_new_int(1)); - } - else - json_object_object_add(visit_obj, "icon", json_object_new_int(0)); - json_object_object_add(visit_obj, "act", json_object_new_int(p_info->action)); - json_object_object_add(visit_obj, "ft", json_object_new_int(p_info->first_time)); - json_object_object_add(visit_obj, "lt", json_object_new_int(p_info->latest_time)); - json_object_object_add(visit_obj, "tt", json_object_new_int(total_time)); - json_object_array_add(visit_array, visit_obj); - - if (first_time_str) - free(first_time_str); - if (latest_time_str) - free(latest_time_str); - p_info = p_info->next; - } - } - - json_object_array_sort(visit_array, compare_lt); - - int total_count = json_object_array_length(visit_array); - - struct json_object *paged_array = NULL; - if (page <= 0) { - /* Reuse visit_array as response data while keeping a local reference for cleanup. */ - paged_array = json_object_get(visit_array); - } else { - paged_array = json_object_new_array(); - int start_idx = (page - 1) * page_size; - int end_idx = start_idx + page_size; - int i; - for (i = start_idx; i < end_idx && i < total_count; i++) { - struct json_object *visit_obj = json_object_array_get_idx(visit_array, i); - if (visit_obj) { - json_object_get(visit_obj); - json_object_array_add(paged_array, visit_obj); - } - } - } - - json_object_object_add(root_obj, "total", json_object_new_int(total_count)); - json_object_object_add(root_obj, "list", paged_array); - json_object_object_add(root_obj, "page", json_object_new_int(page)); - json_object_object_add(root_obj, "page_size", json_object_new_int(page_size)); - - int total_pages = 0; - if (page_size > 0) { - total_pages = (total_count + page_size - 1) / page_size; // Ceiling division - } - json_object_object_add(root_obj, "total_pages", json_object_new_int(total_pages)); - json_object_put(visit_array); - - if (req_obj) { - json_object_put(req_obj); - } - if (msg_obj_str) { - free(msg_obj_str); - } - blob_buf_init(&b, 0); - blobmsg_add_object(&b, root_obj); - ubus_send_reply(ctx, req, b.head); - json_object_put(root_obj); - return 0; -} - - -void update_app_visit_time_list(char *mac, struct app_visit_stat_info *visit_info) -{ - int i, j, s; - int num = 0; - - dev_node_t *node = find_dev_node(mac); - if (!node) - { - printf("not found mac:%s\n", mac); - return; - } - for (i = 0; i < MAX_APP_TYPE; i++) - { - for (j = 0; j < MAX_APP_ID_NUM; j++) - { - unsigned long long min = visit_info->visit_list[0].total_time; - int min_index = 0; - if (node->stat[i][j].total_time == 0) - continue; - if (num < MAX_APP_STAT_NUM) - { - min_index = num; - } - else - { - for (s = 0; s < MAX_APP_STAT_NUM; s++) - { - if (visit_info->visit_list[s].total_time < min) - { - min_index = s; - break; - } - } - } - num++; - if (node->stat[i][j].total_time > visit_info->visit_list[min_index].total_time) - { - visit_info->visit_list[min_index].total_time = node->stat[i][j].total_time; - visit_info->visit_list[min_index].app_id = (i + 1) * 1000 + j + 1; - } - } - } - if (num < MAX_APP_STAT_NUM) - visit_info->num = num; - else - visit_info->num = MAX_APP_STAT_NUM; -} - -void update_app_class_visit_time_list(char *mac, int *visit_time) -{ - int i, j, s; - int num = 0; - - dev_node_t *node = find_dev_node(mac); - if (!node) - { - printf("not found mac:%s\n", mac); - return; - } - for (i = 0; i < MAX_APP_TYPE; i++) - { - for (j = 0; j < MAX_APP_ID_NUM; j++) - { - if (node->stat[i][j].total_time == 0) - continue; - visit_time[i] += node->stat[i][j].total_time; - } - } -} - -void ubus_get_dev_visit_time_info(char *mac, struct blob_buf *b) -{ - int i, j; - void *c, *array; - void *t; - void *s; - struct app_visit_stat_info info; - memset((char *)&info, 0x0, sizeof(info)); - update_app_visit_time_list(mac, &info); -} - -static int -appfilter_handle_visit_list(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - int ret; - blob_buf_init(&b, 0); - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) - { - printf("format json failed\n"); - return 0; - } - - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); - - if (!mac_obj) - { - ubus_dump_visit_list(&b, NULL); - } - else - ubus_dump_visit_list(&b, json_object_get_string(mac_obj)); - ubus_send_reply(ctx, req, b.head); - json_object_put(req_obj); - free(msg_obj_str); - return 0; -} -static int handle_debug(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - int ret; - blob_buf_init(&b, 0); - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) - { - printf("format json failed\n"); - return 0; - } - - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *debug_obj = json_object_object_get(req_obj, "debug"); - - if (debug_obj) - { - current_log_level = json_object_get_int(debug_obj); - LOG_WARN("debug level set to %d\n", current_log_level); - } - - ubus_send_reply(ctx, req, b.head); - json_object_put(req_obj); - free(msg_obj_str); - return 0; -} - - - -typedef struct app_visit_time_info -{ - int app_id; - unsigned long long total_time; -} app_visit_time_info_t; - -int visit_time_compare(const void *a, const void *b) -{ - app_visit_time_info_t *p1 = (app_visit_time_info_t *)a; - app_visit_time_info_t *p2 = (app_visit_time_info_t *)b; - return p1->total_time < p2->total_time ? 1 : -1; -} - -#define MAX_STAT_APP_NUM 128 -void update_top5_app(dev_node_t *node, app_visit_time_info_t top5_app_list[]) -{ - int i, j; - app_visit_time_info_t app_visit_array[MAX_STAT_APP_NUM]; - memset(app_visit_array, 0x0, sizeof(app_visit_array)); - int app_visit_num = 0; - - for (i = 0; i < MAX_APP_TYPE; i++) - { - for (j = 0; j < MAX_APP_ID_NUM; j++) - { - if (node->stat[i][j].total_time == 0) - continue; - app_visit_array[app_visit_num].app_id = (i + 1) * 1000 + j + 1; - app_visit_array[app_visit_num].total_time = node->stat[i][j].total_time; - app_visit_num++; - } - } - - qsort((void *)app_visit_array, app_visit_num, sizeof(app_visit_time_info_t), visit_time_compare); -#if 0 -for (i = 0; i < app_visit_num; i++){ -printf("appid %d-----------total time %llu\n", app_visit_array[i].app_id, -app_visit_array[i].total_time); -} -#endif - for (i = 0; i < 5; i++) - { - top5_app_list[i] = app_visit_array[i]; - } -} - -static int -appfilter_handle_dev_list(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - int i, j; - struct json_object *root_obj = json_object_new_object(); - - struct json_object *dev_array = json_object_new_array(); - int count = 0; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - - dev_node_t *node = dev_hash_table[i]; - while (node) - { - struct json_object *dev_obj = json_object_new_object(); - struct json_object *app_array = json_object_new_array(); - app_visit_time_info_t top5_app_list[5]; - memset(top5_app_list, 0x0, sizeof(top5_app_list)); - update_top5_app(node, top5_app_list); - - for (j = 0; j < 5; j++) - { - if (top5_app_list[j].app_id == 0 || strlen(get_app_name_by_id(top5_app_list[j].app_id)) == 0) - break; - struct json_object *app_obj = json_object_new_object(); - json_object_object_add(app_obj, "id", json_object_new_int(top5_app_list[j].app_id)); - json_object_object_add(app_obj, "name", json_object_new_string(get_app_name_by_id(top5_app_list[j].app_id))); - json_object_array_add(app_array, app_obj); - } - - json_object_object_add(dev_obj, "applist", app_array); - json_object_object_add(dev_obj, "mac", json_object_new_string(node->mac)); - char hostname[128] = {0}; - get_hostname_by_mac(node->mac, hostname); - json_object_object_add(dev_obj, "ip", json_object_new_string(node->ip)); - - json_object_object_add(dev_obj, "online", json_object_new_int(1)); - json_object_object_add(dev_obj, "hostname", json_object_new_string(hostname)); - json_object_object_add(dev_obj, "nickname", json_object_new_string("")); - - - json_object_array_add(dev_array, dev_obj); - - node = node->next; - count++; - } - } - -END: - - json_object_object_add(root_obj, "devlist", dev_array); - blob_buf_init(&b, 0); - blobmsg_add_object(&b, root_obj); - ubus_send_reply(ctx, req, b.head); - json_object_put(root_obj); - return 0; -} - - -static int appfilter_handle_visit_time(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - int ret; - struct app_visit_stat_info info; - blob_buf_init(&b, 0); - memset((char *)&info, 0x0, sizeof(info)); - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) - { - printf("format json failed\n"); - return 0; - } - - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); - if (!mac_obj) - { - printf("mac is NULL\n"); - return 0; - } - update_app_visit_time_list(json_object_get_string(mac_obj), &info); - - struct json_object *resp_obj = json_object_new_object(); - struct json_object *app_info_array = json_object_new_array(); - json_object_object_add(resp_obj, "list", app_info_array); - json_object_object_add(resp_obj, "total_num", json_object_new_int(info.num)); - int i; - for (i = 0; i < info.num; i++) - { - if (strlen(get_app_name_by_id(info.visit_list[i].app_id)) == 0){ - continue; - } - struct json_object *app_info_obj = json_object_new_object(); - json_object_object_add(app_info_obj, "id", json_object_new_int(info.visit_list[i].app_id)); - json_object_object_add(app_info_obj, "name", json_object_new_string(get_app_name_by_id(info.visit_list[i].app_id))); - json_object_object_add(app_info_obj, "t", json_object_new_int(info.visit_list[i].total_time)); - json_object_array_add(app_info_array, app_info_obj); - } - - blobmsg_add_object(&b, resp_obj); - ubus_send_reply(ctx, req, b.head); - json_object_put(resp_obj); - json_object_put(req_obj); - free(msg_obj_str); - return 0; -} - -static int -handle_app_class_visit_time(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - int ret; - int i; - blob_buf_init(&b, 0); - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) - { - printf("format json failed\n"); - return 0; - } - - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); - if (!mac_obj) - { - printf("mac is NULL\n"); - json_object_put(req_obj); - free(msg_obj_str); - return 0; - } - int app_class_visit_time[MAX_APP_TYPE]; - memset(app_class_visit_time, 0x0, sizeof(app_class_visit_time)); - update_app_class_visit_time_list(json_object_get_string(mac_obj), app_class_visit_time); - - struct json_object *resp_obj = json_object_new_object(); - struct json_object *app_class_array = json_object_new_array(); - json_object_object_add(resp_obj, "class_list", app_class_array); - for (i = 0; i < MAX_APP_TYPE; i++) - { - if (i >= g_cur_class_num) - break; - struct json_object *app_class_obj = json_object_new_object(); - json_object_object_add(app_class_obj, "type", json_object_new_int(i)); - json_object_object_add(app_class_obj, "name", json_object_new_string(CLASS_NAME_TABLE[i])); - json_object_object_add(app_class_obj, "visit_time", json_object_new_int(app_class_visit_time[i])); - json_object_array_add(app_class_array, app_class_obj); - } - - blobmsg_add_object(&b, resp_obj); - ubus_send_reply(ctx, req, b.head); - json_object_put(resp_obj); - json_object_put(req_obj); - free(msg_obj_str); - return 0; -} - - -static int parse_feature_cfg(struct json_object *class_list) { - FILE *file = fopen("/tmp/feature.cfg", "r"); - if (!file) { - perror("Failed to open /tmp/feature.cfg"); - return -1; - } - - char line[1024]; - char app_buf[128]; - struct json_object *current_class = NULL; - struct json_object *app_list = NULL; - - while (fgets(line, sizeof(line), file)) { - line[strcspn(line, "\n")] = 0; - - if (strncmp(line, "#class", 6) == 0) { - if (current_class) { - json_object_object_add(current_class, "app_list", app_list); - json_object_array_add(class_list, current_class); - } - - char *name = strtok(line + 7, " "); - char *class_name = NULL; - while (name != NULL) { - class_name = name; - name = strtok(NULL, " "); - } - current_class = json_object_new_object(); - json_object_object_add(current_class, "name", json_object_new_string(class_name)); - app_list = json_object_new_array(); - } else if (current_class) { - char *p_end = strstr(line, ":"); - if (!p_end) { - continue; - } - strncpy(app_buf, line, p_end - line); - app_buf[p_end - line] = '\0'; - char *appid_str = strtok(app_buf, " "); - char *name = strtok(NULL, " "); - if (appid_str && name) { - char combined[256]; - char icon_path[512]; - snprintf(icon_path, sizeof(icon_path), "/www/luci-static/resources/app_icons/%s.png", appid_str); - int with_icon = access(icon_path, F_OK) == 0 ? 1 : 0; - snprintf(combined, sizeof(combined), "%s,%s,%d", appid_str, name, with_icon); - json_object_array_add(app_list, json_object_new_string(combined)); - } - } - } - - // Add the last class to the class list - if (current_class) { - json_object_object_add(current_class, "app_list", app_list); - json_object_array_add(class_list, current_class); - } - - fclose(file); - return 0; -} - -static int handle_get_class_list(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - struct json_object *class_list = json_object_new_array(); - - if (parse_feature_cfg(class_list) != 0) { - json_object_put(response); - return UBUS_STATUS_UNKNOWN_ERROR; - } - - json_object_object_add(response, "class_list", class_list); - - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - - return 0; -} -#define MAX_APPFILTER_STR_LEN 8192 -static int handle_get_app_filter(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - - int i; - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - return 0; - } - char *appfilter_buf = (char *)malloc(MAX_APPFILTER_STR_LEN); - if (!appfilter_buf){ - return 0; - } - appfilter_buf[0] = '\0'; - struct json_object *response = json_object_new_object(); - struct json_object *app_list = json_object_new_array(); - af_uci_get_list_value(uci_ctx, "appfilter.rule.app_list", appfilter_buf, MAX_APPFILTER_STR_LEN - 1, " "); - char *app_id_str = strtok(appfilter_buf, " "); - while (app_id_str) { - json_object_array_add(app_list, json_object_new_int(atoi(app_id_str))); - app_id_str = strtok(NULL, " "); - } - json_object_object_add(response, "app_list", app_list); - uci_free_context(uci_ctx); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - free(appfilter_buf); - json_object_put(response); - return 0; -} - - - -void af_forward_msg_to_agent(char *api, char *msg_str, int msg_len) -{ - if (!api || !msg_str || !msg_len) - return; - char *cmd_buf = (char *)malloc(msg_len + 128); - if (!cmd_buf) - return; - sprintf(cmd_buf, "ubus -t 2 call oaf_agent %s '%s'", api, msg_str); - printf("exec %s\n", cmd_buf); - system(cmd_buf); - free(cmd_buf); -} - - -static int handle_set_app_filter(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - printf("handle_set_app_filter\n"); - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - return 0; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *app_list = json_object_object_get(req_obj, "app_list"); - if (!app_list) { - printf("app_list is NULL\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - - af_uci_delete(uci_ctx, "appfilter.rule.app_list"); - - int len = json_object_array_length(app_list); - for (i = 0; i < json_object_array_length(app_list); i++) { - struct json_object *app_id_obj = json_object_array_get_idx(app_list, i); - af_uci_add_int_list(uci_ctx, "appfilter.rule.app_list", json_object_get_int(app_id_obj)); - } - af_uci_commit(uci_ctx, "appfilter"); - reload_oaf_rule(); - g_oaf_config_change = 1; - - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - - - -static int handle_get_app_filter_base(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - struct json_object *data_obj = json_object_new_object(); - int i; - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - return 0; - } - int enable = 0; - int work_mode = 0; - int record_enable = 0; - int disable_quic = 0; - int app_filter_mode = 0; - enable = af_uci_get_int_value(uci_ctx, "appfilter.global.enable"); - work_mode = af_uci_get_int_value(uci_ctx, "appfilter.global.work_mode"); - record_enable = af_uci_get_int_value(uci_ctx, "appfilter.global.record_enable"); - disable_quic = af_uci_get_int_value(uci_ctx, "appfilter.global.disable_quic"); - app_filter_mode = af_uci_get_int_value(uci_ctx, "appfilter.global.app_filter_mode"); - if (app_filter_mode < 0) { - app_filter_mode = 0; // Default to specified apps mode - } - - - json_object_object_add(data_obj, "enable", json_object_new_int(enable)); - json_object_object_add(data_obj, "work_mode", json_object_new_int(work_mode)); - json_object_object_add(data_obj, "record_enable", json_object_new_int(record_enable)); - json_object_object_add(data_obj, "disable_quic", json_object_new_int(disable_quic)); - json_object_object_add(data_obj, "app_filter_mode", json_object_new_int(app_filter_mode)); - - - json_object_object_add(response, "data", data_obj); - uci_free_context(uci_ctx); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - -static int handle_set_app_filter_base(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - printf("handle_set_app_filter_base\n"); - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - return 0; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *enable_obj = json_object_object_get(req_obj, "enable"); - struct json_object *record_enable_obj = json_object_object_get(req_obj, "record_enable"); - struct json_object *work_mode_obj = json_object_object_get(req_obj, "work_mode"); - struct json_object *disable_quic_obj = json_object_object_get(req_obj, "disable_quic"); - struct json_object *app_filter_mode_obj = json_object_object_get(req_obj, "app_filter_mode"); - if (!enable_obj || !work_mode_obj) { - printf("enable_obj or work_mode_obj is NULL\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - printf("enable_obj: %d\n", json_object_get_int(enable_obj)); - printf("work_mode_obj: %d\n", json_object_get_int(work_mode_obj)); - - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - - af_uci_set_int_value(uci_ctx, "appfilter.global.enable", json_object_get_int(enable_obj)); - af_uci_set_int_value(uci_ctx, "appfilter.global.work_mode", json_object_get_int(work_mode_obj)); - - if (record_enable_obj) - af_uci_set_int_value(uci_ctx, "appfilter.global.record_enable", json_object_get_int(record_enable_obj)); - else - af_uci_set_int_value(uci_ctx, "appfilter.global.record_enable", 0); - - if (disable_quic_obj) - af_uci_set_int_value(uci_ctx, "appfilter.global.disable_quic", json_object_get_int(disable_quic_obj)); - else - af_uci_set_int_value(uci_ctx, "appfilter.global.disable_quic", 0); - - if (app_filter_mode_obj) - af_uci_set_int_value(uci_ctx, "appfilter.global.app_filter_mode", json_object_get_int(app_filter_mode_obj)); - else - af_uci_set_int_value(uci_ctx, "appfilter.global.app_filter_mode", 0); - - - af_uci_commit(uci_ctx, "appfilter"); - reload_oaf_rule(); - g_oaf_config_change = 1; - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - -static int handle_get_app_filter_adv(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - struct json_object *data_obj = json_object_new_object(); - - int i; - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - return 0; - } - char lan_ifname[16]; - - int tcp_rst = af_uci_get_int_value(uci_ctx, "appfilter.global.tcp_rst"); - af_uci_get_value(uci_ctx, "appfilter.global.lan_ifname", lan_ifname, sizeof(lan_ifname)); - int disable_hnat = af_uci_get_int_value(uci_ctx, "appfilter.global.disable_hnat"); - int auto_load_engine = af_uci_get_int_value(uci_ctx, "appfilter.global.auto_load_engine"); - - json_object_object_add(data_obj, "tcp_rst", json_object_new_int(tcp_rst)); - json_object_object_add(data_obj, "lan_ifname", json_object_new_string(lan_ifname)); - json_object_object_add(data_obj, "disable_hnat", json_object_new_int(disable_hnat)); - json_object_object_add(data_obj, "auto_load_engine", json_object_new_int(auto_load_engine)); - - json_object_object_add(response, "data", data_obj); - uci_free_context(uci_ctx); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} -static int handle_set_app_filter_adv(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - return 0; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *tcp_rst_obj = json_object_object_get(req_obj, "tcp_rst"); - struct json_object *lan_ifname_obj = json_object_object_get(req_obj, "lan_ifname"); - struct json_object *disable_hnat_obj = json_object_object_get(req_obj, "disable_hnat"); - struct json_object *auto_load_engine_obj = json_object_object_get(req_obj, "auto_load_engine"); - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - return 0; - } - - if (tcp_rst_obj) - af_uci_set_int_value(uci_ctx, "appfilter.global.tcp_rst", json_object_get_int(tcp_rst_obj)); - if (lan_ifname_obj) - af_uci_set_value(uci_ctx, "appfilter.global.lan_ifname", json_object_get_string(lan_ifname_obj)); - if (disable_hnat_obj) - af_uci_set_int_value(uci_ctx, "appfilter.global.disable_hnat", json_object_get_int(disable_hnat_obj)); - if (auto_load_engine_obj){ - af_uci_set_int_value(uci_ctx, "appfilter.global.auto_load_engine", json_object_get_int(auto_load_engine_obj)); - if (json_object_get_int(auto_load_engine_obj) == 0){ - system("rm /etc/modules.d/oaf"); - } - } - - - af_uci_commit(uci_ctx, "appfilter"); - g_oaf_config_change = 1; - reload_oaf_rule(); - system("/usr/bin/hnat.sh &"); - if (g_enable_agent) { - af_forward_msg_to_agent("set_app_filter_adv", msg_obj_str, strlen(msg_obj_str)); - } - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - -static int handle_get_app_filter_time(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - struct json_object *data_obj = json_object_new_object(); - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - return 0; - } - - // Get current system time (format: 2025/2/27 10:00) - time_t now = time(NULL); - struct tm *current_time = localtime(&now); - char current_time_str[64] = {0}; - snprintf(current_time_str, sizeof(current_time_str), "%d/%d/%d %d:%02d", - current_time->tm_year + 1900, - current_time->tm_mon + 1, - current_time->tm_mday, - current_time->tm_hour, - current_time->tm_min); - json_object_object_add(data_obj, "current_time", json_object_new_string(current_time_str)); - - // Get time_mode - int time_mode = af_uci_get_int_value(uci_ctx, "appfilter.time.time_mode"); - json_object_object_add(data_obj, "mode", json_object_new_int(time_mode)); - - // Get days (global weekday_list) - char days_str[128] = {0}; - af_uci_get_value(uci_ctx, "appfilter.time.days", days_str, sizeof(days_str)); - printf("days_str: %s\n", days_str); - struct json_object *days_array = json_object_new_array(); - char *day = strtok(days_str, " "); - while (day) { - json_object_array_add(days_array, json_object_new_int(atoi(day))); - day = strtok(NULL, " "); - } - json_object_object_add(data_obj, "weekday_list", days_array); - - // Get start_time and end_time - char start_time[32] = {0}; - char end_time[32] = {0}; - af_uci_get_value(uci_ctx, "appfilter.time.start_time", start_time, sizeof(start_time)); - af_uci_get_value(uci_ctx, "appfilter.time.end_time", end_time, sizeof(end_time)); - json_object_object_add(data_obj, "start_time", json_object_new_string(start_time)); - json_object_object_add(data_obj, "end_time", json_object_new_string(end_time)); - - // Get deny_time and allow_time - int deny_time = af_uci_get_int_value(uci_ctx, "appfilter.time.deny_time"); - int allow_time = af_uci_get_int_value(uci_ctx, "appfilter.time.allow_time"); - json_object_object_add(data_obj, "deny_time", json_object_new_int(deny_time)); - json_object_object_add(data_obj, "allow_time", json_object_new_int(allow_time)); - - // Get time list and parse into objects with start_time and end_time - char time_str[MAX_TIME_LIST_LEN] = {0}; - af_uci_get_list_value(uci_ctx, "appfilter.time.time", time_str, sizeof(time_str), " "); - printf("time_str from uci: %s\n", time_str); - struct json_object *time_array = json_object_new_array(); - - // Parse global weekday_list (used as fallback if time period doesn't have weekdays) - int global_weekdays[7] = {0}; - char days_str_copy[128] = {0}; - strncpy(days_str_copy, days_str, sizeof(days_str_copy) - 1); - char *day_token = strtok(days_str_copy, " "); - while (day_token) { - int day_val = atoi(day_token); - if (day_val >= 0 && day_val < 7) { - global_weekdays[day_val] = 1; - } - day_token = strtok(NULL, " "); - } - - char time_str_copy[MAX_TIME_LIST_LEN] = {0}; - strncpy(time_str_copy, time_str, sizeof(time_str_copy) - 1); - printf("ubus parsing time_str_copy: %s\n", time_str_copy); - - // Use strtok_r (reentrant version) to avoid issues with nested strtok calls - char *saveptr1 = NULL; - char *time_period = strtok_r(time_str_copy, " ", &saveptr1); - int period_count = 0; - - while (time_period) { - period_count++; - printf("ubus parsing period[%d]: %s\n", period_count, time_period); - - char start[16] = {0}; - char end[16] = {0}; - char weekdays_str[64] = {0}; - int has_weekdays = 0; - - char *semicolon = strchr(time_period, ';'); - if (semicolon) { - has_weekdays = 1; - strncpy(weekdays_str, time_period, semicolon - time_period); - weekdays_str[semicolon - time_period] = '\0'; - time_period = semicolon + 1; - printf("ubus period[%d] has weekdays: %s, time_part: %s\n", period_count, weekdays_str, time_period); - } - - char *delimiter = strchr(time_period, '-'); - if (delimiter) { - strncpy(start, time_period, delimiter - time_period); - start[delimiter - time_period] = '\0'; - - strcpy(end, delimiter + 1); - - printf("ubus period[%d] parsed: start=%s, end=%s\n", period_count, start, end); - - struct json_object *period_obj = json_object_new_object(); - json_object_object_add(period_obj, "start", json_object_new_string(start)); - json_object_object_add(period_obj, "end", json_object_new_string(end)); - - struct json_object *period_weekdays = json_object_new_array(); - if (has_weekdays) { - char weekdays_copy[64] = {0}; - strncpy(weekdays_copy, weekdays_str, sizeof(weekdays_copy) - 1); - char *saveptr2 = NULL; - char *wd = strtok_r(weekdays_copy, ",", &saveptr2); - while (wd) { - json_object_array_add(period_weekdays, json_object_new_int(atoi(wd))); - wd = strtok_r(NULL, ",", &saveptr2); - } - printf("ubus period[%d] weekday_list size: %d\n", period_count, json_object_array_length(period_weekdays)); - } else { - // Use global weekdays as fallback - int i; - for (i = 0; i < 7; i++) { - if (global_weekdays[i]) { - json_object_array_add(period_weekdays, json_object_new_int(i)); - } - } - } - json_object_object_add(period_obj, "weekday_list", period_weekdays); - - json_object_array_add(time_array, period_obj); - printf("ubus period[%d] added to array, current array length: %d\n", period_count, json_object_array_length(time_array)); - } else { - printf("ubus period[%d] ERROR: no delimiter found\n", period_count); - } - - // Get next period using strtok_r - time_period = strtok_r(NULL, " ", &saveptr1); - if (time_period) { - printf("ubus next period will be: %s\n", time_period); - } else { - printf("ubus no more periods\n"); - } - } - json_object_object_add(data_obj, "time_list", time_array); - - - if (time_mode == 2) { - struct json_object *daily_time_list_array = json_object_new_array(); - - // Initialize array with 7 elements (Sunday to Saturday) - int weekday; - for (weekday = 0; weekday < 7; weekday++) { - char uci_key[64] = {0}; - snprintf(uci_key, sizeof(uci_key), "appfilter.time.daily_limit_%d", weekday); - - char daily_limit_str[128] = {0}; - af_uci_get_value(uci_ctx, uci_key, daily_limit_str, sizeof(daily_limit_str)); - - - int enable = 0; - int am_time = 0; - int pm_time = 0; - if (strlen(daily_limit_str) > 0) { - char *first_colon = strchr(daily_limit_str, ':'); - if (first_colon) { - char *second_colon = strchr(first_colon + 1, ':'); - if (second_colon) { - // New format: "enable:am_time:pm_time" - enable = atoi(daily_limit_str); - am_time = atoi(first_colon + 1); - pm_time = atoi(second_colon + 1); - } else { - enable = 1; // Default to enabled for old format - am_time = atoi(daily_limit_str); - pm_time = atoi(first_colon + 1); - } - } else { - enable = 1; // Default to enabled - am_time = atoi(daily_limit_str); - } - } - - struct json_object *day_obj = json_object_new_object(); - json_object_object_add(day_obj, "enable", json_object_new_int(enable)); - json_object_object_add(day_obj, "am_time", json_object_new_int(am_time)); - json_object_object_add(day_obj, "pm_time", json_object_new_int(pm_time)); - json_object_array_add(daily_time_list_array, day_obj); - } - - json_object_object_add(data_obj, "daily_time_list", daily_time_list_array); - - time_t now = time(NULL); - struct tm *current_time = localtime(&now); - int current_weekday = current_time->tm_wday; // 0=Sunday, 1=Monday, ..., 6=Saturday - int current_hour = current_time->tm_hour; - - json_object_object_add(data_obj, "current_weekday", json_object_new_int(current_weekday)); - - int total_am_time = 0; - int total_pm_time = 0; - int i; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) { - dev_node_t *node = dev_hash_table[i]; - while (node) { - if (node->is_selected) { - total_am_time += node->today_am_active_time; - total_pm_time += node->today_pm_active_time; - } - node = node->next; - } - } - - json_object_object_add(data_obj, "current_am_used_time", json_object_new_int(total_am_time)); - json_object_object_add(data_obj, "current_pm_used_time", json_object_new_int(total_pm_time)); - - daily_limit_config_t *daily_limit = &g_af_config.time.daily_limit[current_weekday]; - json_object_object_add(data_obj, "current_am_limit", json_object_new_int(daily_limit->am_time)); - json_object_object_add(data_obj, "current_pm_limit", json_object_new_int(daily_limit->pm_time)); - json_object_object_add(data_obj, "current_day_enabled", json_object_new_int(daily_limit->enable)); - } - - json_object_object_add(response, "data", data_obj); - - printf("response_json: %s\n", json_object_to_json_string(response)); - uci_free_context(uci_ctx); - - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - -// {"end_time":"12:00","weekday_list":[1,2,3,4,5,6,0],"deny_time":5,"start_time":"22:22","allow_time":30,"mode":1} -// {"mode":0,"weekday_list":[1,2],"time_list":[{"start":"00:11","end":"00:12"},{"start":"12:00","end":"14:00"}]} -static int handle_set_app_filter_time(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - printf("set appfilter time\n"); - int mode = 0; - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - return 0; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - - struct json_object *mode_obj = json_object_object_get(req_obj, "mode"); - if (!mode_obj) { - printf("mode_obj is NULL\n"); - json_object_put(req_obj); - free(msg_obj_str); - return 0; - } - printf("mode_obj: %d\n", json_object_get_int(mode_obj)); - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(req_obj); - free(msg_obj_str); - return 0; - } - mode = json_object_get_int(mode_obj); - af_uci_set_int_value(uci_ctx, "appfilter.time.time_mode", mode); - - struct json_object *weekday_list_obj = json_object_object_get(req_obj, "weekday_list"); - if (weekday_list_obj && (mode == 0 || mode == 1)) { - char days_str[128] = {0}; - for (i = 0; i < json_object_array_length(weekday_list_obj); i++) { - struct json_object *weekday_obj = json_object_array_get_idx(weekday_list_obj, i); - char tmp[8]; - snprintf(tmp, sizeof(tmp), "%d", json_object_get_int(weekday_obj)); - if (i > 0) strcat(days_str, " "); - strcat(days_str, tmp); - } - af_uci_set_value(uci_ctx, "appfilter.time.days", days_str); - } - - if (mode == 0) { - struct json_object *time_list_obj = json_object_object_get(req_obj, "time_list"); - if (!time_list_obj) { - printf("time_list_obj is NULL\n"); - goto EXIT; - } - af_uci_delete(uci_ctx, "appfilter.time.time"); - int time_list_len = json_object_array_length(time_list_obj); - for (i = 0; i < time_list_len; i++) { - struct json_object *time_obj = json_object_array_get_idx(time_list_obj, i); - struct json_object *start_time_obj = json_object_object_get(time_obj, "start"); - struct json_object *end_time_obj = json_object_object_get(time_obj, "end"); - if (!start_time_obj || !end_time_obj) { - printf("start_time_obj or end_time_obj is NULL\n"); - goto EXIT; - } - - char time_str[256] = {0}; - - struct json_object *period_weekday_list_obj = json_object_object_get(time_obj, "weekday_list"); - if (period_weekday_list_obj && json_object_array_length(period_weekday_list_obj) > 0) { - // Build weekday string: "1,2,4,5" - char weekday_str[64] = {0}; - int j; - for (j = 0; j < json_object_array_length(period_weekday_list_obj); j++) { - struct json_object *wd_obj = json_object_array_get_idx(period_weekday_list_obj, j); - if (j > 0) strcat(weekday_str, ","); - char tmp[8]; - snprintf(tmp, sizeof(tmp), "%d", json_object_get_int(wd_obj)); - strcat(weekday_str, tmp); - } - sprintf(time_str, "%s;%s-%s", weekday_str, - json_object_get_string(start_time_obj), - json_object_get_string(end_time_obj)); - } else { - // No weekday_list in time_obj: use global weekday_list (fallback to old format) - sprintf(time_str, "%s-%s", - json_object_get_string(start_time_obj), - json_object_get_string(end_time_obj)); - } - - printf("time_str: %s\n", time_str); - af_uci_add_list(uci_ctx, "appfilter.time.time", time_str); - } - } - else if (mode == 1) { - struct json_object *deny_time_obj = json_object_object_get(req_obj, "deny_time"); - struct json_object *allow_time_obj = json_object_object_get(req_obj, "allow_time"); - struct json_object *start_time_obj = json_object_object_get(req_obj, "start_time"); - struct json_object *end_time_obj = json_object_object_get(req_obj, "end_time"); - if (!deny_time_obj || !allow_time_obj || !start_time_obj || !end_time_obj) { - printf("deny_time_obj or allow_time_obj or start_time_obj or end_time_obj is NULL\n"); - goto EXIT; - } - af_uci_set_int_value(uci_ctx, "appfilter.time.deny_time", json_object_get_int(deny_time_obj)); - af_uci_set_int_value(uci_ctx, "appfilter.time.allow_time", json_object_get_int(allow_time_obj)); - af_uci_set_value(uci_ctx, "appfilter.time.start_time", json_object_get_string(start_time_obj)); - af_uci_set_value(uci_ctx, "appfilter.time.end_time", json_object_get_string(end_time_obj)); - } - else if (mode == 2) { - - struct json_object *daily_time_list_obj = json_object_object_get(req_obj, "daily_time_list"); - if (!daily_time_list_obj) { - printf("daily_time_list_obj is NULL\n"); - goto EXIT; - } - - int daily_time_list_len = json_object_array_length(daily_time_list_obj); - if (daily_time_list_len != 7) { - printf("daily_time_list length should be 7, got %d\n", daily_time_list_len); - goto EXIT; - } - - for (i = 0; i < 7; i++) { - char uci_key[64] = {0}; - snprintf(uci_key, sizeof(uci_key), "appfilter.time.daily_limit_%d", i); - af_uci_delete(uci_ctx, uci_key); - } - - for (i = 0; i < 7; i++) { - struct json_object *day_obj = json_object_array_get_idx(daily_time_list_obj, i); - struct json_object *enable_obj = json_object_object_get(day_obj, "enable"); - struct json_object *am_time_obj = json_object_object_get(day_obj, "am_time"); - struct json_object *pm_time_obj = json_object_object_get(day_obj, "pm_time"); - - if (enable_obj && am_time_obj && pm_time_obj) { - int enable = json_object_get_int(enable_obj); - int am_time = json_object_get_int(am_time_obj); - int pm_time = json_object_get_int(pm_time_obj); - - // Format: "enable:am_time:pm_time" (e.g., "1:100:200") - char limit_str[32] = {0}; - snprintf(limit_str, sizeof(limit_str), "%d:%d:%d", enable, am_time, pm_time); - - char uci_key[64] = {0}; - snprintf(uci_key, sizeof(uci_key), "appfilter.time.daily_limit_%d", i); - printf("daily_limit_%d: %s\n", i, limit_str); - af_uci_set_value(uci_ctx, uci_key, limit_str); - } - } - } - af_uci_commit(uci_ctx, "appfilter"); - g_oaf_config_change = 1; - if (g_enable_agent) { - af_forward_msg_to_agent("set_app_filter_time", msg_obj_str, strlen(msg_obj_str)); - } -EXIT: - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - -typedef struct all_users_info { - int flag; - struct json_object *users_array; -} all_users_info_t; - - - -void all_users_callback(void *arg, dev_node_t *dev) -{ - int flag = 0; - int i; - all_users_info_t *au_info = (all_users_info_t *)arg; - flag = au_info->flag; - struct json_object *users_array = au_info->users_array; - - struct json_object *user_obj = json_object_new_object(); - json_object_object_add(user_obj, "mac", json_object_new_string(dev->mac)); - - int online_status = dev->online; - if (dev->active == 1 && dev->online == 1) { - online_status = 2; - } - json_object_object_add(user_obj, "online", json_object_new_int(online_status)); - json_object_object_add(user_obj, "online_time", json_object_new_int(dev->online_time)); - json_object_object_add(user_obj, "offline_time", json_object_new_int(dev->offline_time)); - json_object_object_add(user_obj, "today_am_active_time", json_object_new_int(dev->today_am_active_time)); - json_object_object_add(user_obj, "today_pm_active_time", json_object_new_int(dev->today_pm_active_time)); - - if (flag > 0) { - json_object_object_add(user_obj, "ip", json_object_new_string(dev->ip)); - - } - - if (flag > 1){ - json_object_object_add(user_obj, "hostname", json_object_new_string(dev->hostname)); - json_object_object_add(user_obj, "nickname", json_object_new_string(dev->nickname)); - json_object_object_add(user_obj, "is_whitelist", json_object_new_int(dev->is_whitelist)); - json_object_object_add(user_obj, "is_selected", json_object_new_int(dev->is_selected)); - } - - if (flag > 2){ - struct json_object *app_array = json_object_new_array(); - app_visit_time_info_t top5_app_list[5]; - memset(top5_app_list, 0x0, sizeof(top5_app_list)); - update_top5_app(dev, top5_app_list); - for (i = 0; i < 5; i++) - { - if (top5_app_list[i].app_id == 0 || strlen(get_app_name_by_id(top5_app_list[i].app_id)) == 0) - break; - - struct json_object *app_obj = json_object_new_object(); - json_object_object_add(app_obj, "id", json_object_new_int(top5_app_list[i].app_id)); - - if (check_app_icon_exist(top5_app_list[i].app_id)) { - json_object_object_add(app_obj, "icon", json_object_new_int(1)); - } - else - json_object_object_add(app_obj, "icon", json_object_new_int(0)); - - - json_object_object_add(app_obj, "name", json_object_new_string(get_app_name_by_id(top5_app_list[i].app_id))); - - json_object_array_add(app_array, app_obj); - } - json_object_object_add(user_obj, "applist", app_array); - - if (strlen(dev->visiting_url) > 0) - json_object_object_add(user_obj, "url", json_object_new_string(dev->visiting_url)); - else - json_object_object_add(user_obj, "url", json_object_new_string("")); - if (dev->visiting_app > 0 && strlen(get_app_name_by_id(dev->visiting_app)) > 0) - json_object_object_add(user_obj, "app", json_object_new_string(get_app_name_by_id(dev->visiting_app))); - else - json_object_object_add(user_obj, "app", json_object_new_string("")); - - json_object_object_add(user_obj, "up_rate", json_object_new_int(dev->up_rate / 1024)); - json_object_object_add(user_obj, "down_rate", json_object_new_int(dev->down_rate / 1024)); - u_int32_t up_flow = (u_int32_t)(dev->today_up_bytes / 1024); - u_int32_t down_flow = (u_int32_t)(dev->today_down_bytes / 1024); - json_object_object_add(user_obj, "up_rate", json_object_new_int(dev->up_rate / 1024)); - json_object_object_add(user_obj, "down_rate", json_object_new_int(dev->down_rate / 1024)); - json_object_object_add(user_obj, "today_up_flow", json_object_new_int(up_flow)); - json_object_object_add(user_obj, "today_down_flow", json_object_new_int(down_flow)); - } - json_object_array_add(users_array, user_obj); -} - -int compare_users(const void *a, const void *b) -{ - struct json_object *user_a = *(struct json_object **)a; - struct json_object *user_b = *(struct json_object **)b; - - struct json_object *online_a, *online_b; - json_object_object_get_ex(user_a, "online", &online_a); - json_object_object_get_ex(user_b, "online", &online_b); - - int online_val_a = json_object_get_int(online_a); - int online_val_b = json_object_get_int(online_b); - - if (online_val_a != online_val_b) - return online_val_b - online_val_a; - - struct json_object *online_time_a, *online_time_b; - json_object_object_get_ex(user_a, "online_time", &online_time_a); - json_object_object_get_ex(user_b, "online_time", &online_time_b); - - int online_time_val_a = json_object_get_int(online_time_a); - int online_time_val_b = json_object_get_int(online_time_b); - - if (online_val_a == 1 && online_val_b == 1) { - // Both are online, sort by online_time - return online_time_val_a - online_time_val_b; - } else { - // Both are offline, sort by offline_time - struct json_object *offline_time_a, *offline_time_b; - json_object_object_get_ex(user_a, "offline_time", &offline_time_a); - json_object_object_get_ex(user_b, "offline_time", &offline_time_b); - - int offline_time_val_a = json_object_get_int(offline_time_a); - int offline_time_val_b = json_object_get_int(offline_time_b); - - return offline_time_val_a - offline_time_val_b; - } -} - -static int handle_get_all_users(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - struct json_object *data_obj = json_object_new_object(); - int flag = 0; - int page = 0; - int page_size = 20; // Default page size - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(response); - json_object_put(data_obj); - return 0; - } - - char *msg_obj_str = blobmsg_format_json(msg, true); - struct json_object *req_obj = NULL; - if (msg_obj_str) - { - req_obj = json_tokener_parse(msg_obj_str); - struct json_object *flag_obj = json_object_object_get(req_obj, "flag"); - struct json_object *page_obj = json_object_object_get(req_obj, "page"); - struct json_object *page_size_obj = json_object_object_get(req_obj, "page_size"); - if (flag_obj) { - flag = json_object_get_int(flag_obj); - } - if (page_obj) { - page = json_object_get_int(page_obj); - } - if (page_size_obj) { - page_size = json_object_get_int(page_size_obj); - if (page_size <= 0) { - page_size = 20; // Default to 20 if invalid - } - } - } - - printf("flag: %d, page: %d, page_size: %d\n", flag, page, page_size); - all_users_info_t au_info; - au_info.flag = flag; - au_info.users_array = json_object_new_array(); - - update_dev_nickname(); - update_dev_visiting_info(); - update_dev_whitelist_flag(); - dev_foreach(&au_info, all_users_callback); - - json_object_array_sort(au_info.users_array, compare_users); - - int total_count = json_object_array_length(au_info.users_array); - - struct json_object *paged_array = NULL; - if (page <= 0) { - /* Reuse users_array as response data while keeping a local reference for cleanup. */ - paged_array = json_object_get(au_info.users_array); - } else { - paged_array = json_object_new_array(); - int start_idx = (page - 1) * page_size; - int end_idx = start_idx + page_size; - int i; - for (i = start_idx; i < end_idx && i < total_count; i++) { - struct json_object *user_obj = json_object_array_get_idx(au_info.users_array, i); - if (user_obj) { - json_object_get(user_obj); // Increment reference count - json_object_array_add(paged_array, user_obj); - } - } - } - - json_object_object_add(data_obj, "list", paged_array); - json_object_object_add(data_obj, "total", json_object_new_int(total_count)); - json_object_object_add(data_obj, "page", json_object_new_int(page)); - json_object_object_add(data_obj, "page_size", json_object_new_int(page_size)); - - // Calculate total pages - int total_pages = 0; - if (page_size > 0) { - total_pages = (total_count + page_size - 1) / page_size; // Ceiling division - } - json_object_object_add(data_obj, "total_pages", json_object_new_int(total_pages)); - json_object_put(au_info.users_array); - - json_object_object_add(response, "data", data_obj); - - if (req_obj) { - json_object_put(req_obj); - } - if (msg_obj_str) { - free(msg_obj_str); - } - uci_free_context(uci_ctx); - - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - -static int handle_get_app_filter_user(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - struct json_object *data_obj = json_object_new_object(); - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(response); - json_object_put(data_obj); - return 0; - } - - int mode = af_uci_get_int_value(uci_ctx, "appfilter.global.user_mode"); - if (mode < 0) - mode = 0; - json_object_object_add(data_obj, "mode", json_object_new_int(mode)); - - struct json_object *user_array = json_object_new_array(); - char mac_str[128] = {0}; - int num = af_get_uci_list_num(uci_ctx, "appfilter", "af_user"); - for (int i = 0; i < num; i++) { - af_uci_get_array_value(uci_ctx, "appfilter.@af_user[%d].mac", i, mac_str, sizeof(mac_str)); - - struct json_object *user_obj = json_object_new_object(); - json_object_object_add(user_obj, "mac", json_object_new_string(mac_str)); - dev_node_t *dev = find_dev_node(mac_str); - if (dev){ - json_object_object_add(user_obj, "nickname", json_object_new_string(dev->nickname)); - json_object_object_add(user_obj, "hostname", json_object_new_string(dev->hostname)); - }else{ - json_object_object_add(user_obj, "nickname", json_object_new_string("")); - json_object_object_add(user_obj, "hostname", json_object_new_string("")); - } - json_object_array_add(user_array, user_obj); - } - - json_object_object_add(data_obj, "list", user_array); - json_object_object_add(response, "data", data_obj); - - uci_free_context(uci_ctx); - - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; - -} - - -static int handle_set_app_filter_user(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - return 0; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *mode_object = json_object_object_get(req_obj, "mode"); - if (!mode_object) { - printf("mode_object is NULL\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - printf("mode_object: %d\n", json_object_get_int(mode_object)); - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - - af_uci_set_int_value(uci_ctx, "appfilter.global.user_mode", json_object_get_int(mode_object)); - af_uci_commit(uci_ctx, "appfilter"); - reload_oaf_rule(); - if (g_enable_agent) { - af_forward_msg_to_agent("set_app_filter_user", msg_obj_str, strlen(msg_obj_str)); - } - - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - - - -static int handle_del_app_filter_user(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - printf("handle_del_app_filter_user\n"); - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - return 0; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); - if (!mac_obj) { - printf("mac_obj is NULL\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - printf("mac: %s\n", json_object_get_string(mac_obj)); - - - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - char mac_str[128] = {0}; - int num = af_get_uci_list_num(uci_ctx, "appfilter", "af_user"); - for (int i = 0; i < num; i++) { - af_uci_get_array_value(uci_ctx, "appfilter.@af_user[%d].mac", i, mac_str, sizeof(mac_str)); - if (strcmp(mac_str, json_object_get_string(mac_obj)) == 0) { - printf("delete af_user[%d]\n", i); - - char buf[128] = {0}; - sprintf(buf, "appfilter.@af_user[%d]", i); - af_uci_delete(uci_ctx, buf); - break; - } - } - - af_uci_commit(uci_ctx, "appfilter"); - reload_oaf_rule(); - if (g_enable_agent) { - af_forward_msg_to_agent("del_app_filter_user", msg_obj_str, strlen(msg_obj_str)); - } - - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - - - -static int handle_add_app_filter_user(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - printf("handle_add_app_filter_user\n"); - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - json_object_put(response); - return -1; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - if (!req_obj) { - printf("parse json failed\n"); - free(msg_obj_str); - json_object_put(response); - return -1; - } - struct json_object *mac_array = json_object_object_get(req_obj, "mac_list"); - if (!mac_array) { - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return -1; - } - - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return -1; - } - - int len = json_object_array_length(mac_array); - printf("len: %d\n", len); - for (int i = 0; i < len; i++) { - struct json_object *mac_obj = json_object_array_get_idx(mac_array, i); - af_uci_add_section(uci_ctx, "appfilter", "af_user"); - af_uci_set_value(uci_ctx, "appfilter.@af_user[-1].mac", json_object_get_string(mac_obj)); - } - printf("add af_user ok\n"); - af_uci_commit(uci_ctx, "appfilter"); - reload_oaf_rule(); - if (g_enable_agent) { - af_forward_msg_to_agent("add_app_filter_user", msg_obj_str, strlen(msg_obj_str)); - } - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - -static int handle_set_nickname(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - json_object_put(response); - return -1; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - if (!req_obj) { - printf("parse json failed\n"); - free(msg_obj_str); - json_object_put(response); - return -1; - } - struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); - - struct json_object *nickname_obj = json_object_object_get(req_obj, "nickname"); - if (!nickname_obj || !mac_obj) { - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return -1; - } - - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return -1; - } - int num = af_get_uci_list_num(uci_ctx, "user_info", "user_info"); - char mac_str[128] = {0}; - int index = -1; - for (i = 0; i < num; i++) { - af_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].mac", i, mac_str, sizeof(mac_str)); - if (strcmp(mac_str, json_object_get_string(mac_obj)) == 0) { - index = i; - printf("found nickname index: %d\n", index); - break; - } - } - - if (strlen(json_object_get_string(nickname_obj)) > 0) { - if (index == -1) { - af_uci_add_section(uci_ctx, "user_info", "user_info"); - } - af_uci_set_array_value(uci_ctx, "user_info.@user_info[%d].mac", index, json_object_get_string(mac_obj)); - af_uci_set_array_value(uci_ctx, "user_info.@user_info[%d].nickname", index, json_object_get_string(nickname_obj)); - } - else{ - char uci_option[128] = {0}; - sprintf(uci_option, "user_info.@user_info[%d]", index); - af_uci_delete(uci_ctx, uci_option); - printf("delete nickname mac = %s\n", json_object_get_string(mac_obj)); - } - - - af_uci_commit(uci_ctx, "user_info"); - reload_oaf_rule(); - if (g_enable_agent) { - af_forward_msg_to_agent("set_nickname", msg_obj_str, strlen(msg_obj_str)); - } - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - -extern af_run_time_status_t g_af_status; - - -static int handle_get_oaf_status(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - struct json_object *data_obj = json_object_new_object(); - char result[128] = {0}; - char kernel_version[128] = {0}; - int enable = 0; - int ret = 0; - int engine_status = 0; - struct uci_context *uci_ctx = uci_alloc_context(); - - ret = af_read_file_value("/proc/sys/oaf/enable", result, sizeof(result)); - if (ret !=0 || strlen(result) == 0){ - engine_status = 0; - enable = 0; - } - else{ - enable = atoi(result); - engine_status = 1; - } - - json_object_object_add(data_obj, "enable", json_object_new_int(enable)); - json_object_object_add(data_obj, "version", json_object_new_string(OAF_VERSION)); - - json_object_object_add(data_obj, "engine_status", json_object_new_int(engine_status)); - - // Read disable_hnat configuration - if (uci_ctx) { - int disable_hnat = af_uci_get_int_value(uci_ctx, "appfilter.global.disable_hnat"); - json_object_object_add(data_obj, "disable_hnat", json_object_new_int(disable_hnat)); - uci_free_context(uci_ctx); - } else { - json_object_object_add(data_obj, "disable_hnat", json_object_new_int(0)); - } - - ret = exec_with_result_line("cat /proc/sys/oaf/version", kernel_version, sizeof(kernel_version)); - if (ret >= 0){ - json_object_object_add(data_obj, "engine_version", json_object_new_string(kernel_version)); - } - else{ - json_object_object_add(data_obj, "engine_version", json_object_new_string("")); - } - - ret = exec_with_result_line("uname -r", kernel_version, sizeof(kernel_version)); - if (ret >= 0){ - json_object_object_add(data_obj, "kernel_version", json_object_new_string(kernel_version)); - } - else{ - json_object_object_add(data_obj, "kernel_version", json_object_new_string("")); - } - - - json_object_object_add(data_obj, "config_enable", json_object_new_int(g_af_config.global.enable)); - json_object_object_add(data_obj, "time_mode", json_object_new_int(g_af_config.time.time_mode)); - json_object_object_add(data_obj, "match_time", json_object_new_int(g_af_status.match_time)); - - if (g_af_config.time.time_mode == 1) { - json_object_object_add(data_obj, "filter", json_object_new_int(g_af_status.filter)); - if (g_af_status.filter == 1) { - json_object_object_add(data_obj, "remain_time", json_object_new_int(g_af_config.time.deny_time - g_af_status.deny_time)); - } - else { - json_object_object_add(data_obj, "remain_time", json_object_new_int(g_af_config.time.allow_time - g_af_status.allow_time)); - } - } else if (g_af_config.time.time_mode == 2) { - json_object_object_add(data_obj, "remain_time", json_object_new_int(g_af_status.remain_time)); - json_object_object_add(data_obj, "used_time", json_object_new_int(g_af_status.used_time)); - json_object_object_add(data_obj, "period_blocked", json_object_new_int(g_af_status.period_blocked)); - - time_t now = time(NULL); - struct tm *current_time = localtime(&now); - int current_weekday = current_time->tm_wday; - int current_hour = current_time->tm_hour; - - json_object_object_add(data_obj, "current_weekday", json_object_new_int(current_weekday)); - - daily_limit_config_t *daily_limit = &g_af_config.time.daily_limit[current_weekday]; - if (daily_limit->enable) { - if (current_hour < 12) { - json_object_object_add(data_obj, "am_time_limit", json_object_new_int(daily_limit->am_time)); - json_object_object_add(data_obj, "pm_time_limit", json_object_new_int(daily_limit->pm_time)); - } else { - json_object_object_add(data_obj, "am_time_limit", json_object_new_int(daily_limit->am_time)); - json_object_object_add(data_obj, "pm_time_limit", json_object_new_int(daily_limit->pm_time)); - } - } else { - json_object_object_add(data_obj, "am_time_limit", json_object_new_int(0)); - json_object_object_add(data_obj, "pm_time_limit", json_object_new_int(0)); - } - - - int total_am_time = 0; - int total_pm_time = 0; - int selected_user_count = 0; - int i; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) { - dev_node_t *node = dev_hash_table[i]; - while (node) { - if (node->is_selected) { - total_am_time += node->today_am_active_time; - total_pm_time += node->today_pm_active_time; - if (node->online) { - selected_user_count++; - } - } - node = node->next; - } - } - - json_object_object_add(data_obj, "current_am_used_time", json_object_new_int(total_am_time)); - json_object_object_add(data_obj, "current_pm_used_time", json_object_new_int(total_pm_time)); - json_object_object_add(data_obj, "selected_user_count", json_object_new_int(selected_user_count)); - json_object_object_add(data_obj, "current_am_limit", json_object_new_int(daily_limit->am_time)); - json_object_object_add(data_obj, "current_pm_limit", json_object_new_int(daily_limit->pm_time)); - json_object_object_add(data_obj, "current_day_enabled", json_object_new_int(daily_limit->enable)); - } - - - json_object_object_add(response, "data", data_obj); - - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; - -} - -static int handle_get_whitelist_user(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - struct json_object *data_obj = json_object_new_object(); - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(response); - json_object_put(data_obj); - return 0; - } - - struct json_object *user_array = json_object_new_array(); - char mac_str[128] = {0}; - int num = af_get_uci_list_num(uci_ctx, "appfilter", "whitelist"); - for (int i = 0; i < num; i++) { - af_uci_get_array_value(uci_ctx, "appfilter.@whitelist[%d].mac", i, mac_str, sizeof(mac_str)); - struct json_object *user_obj = json_object_new_object(); - json_object_object_add(user_obj, "mac", json_object_new_string(mac_str)); - dev_node_t *dev = find_dev_node(mac_str); - if (dev){ - json_object_object_add(user_obj, "nickname", json_object_new_string(dev->nickname)); - json_object_object_add(user_obj, "hostname", json_object_new_string(dev->hostname)); - }else{ - json_object_object_add(user_obj, "nickname", json_object_new_string("")); - json_object_object_add(user_obj, "hostname", json_object_new_string("")); - } - json_object_array_add(user_array, user_obj); - } - json_object_object_add(data_obj, "list", user_array); - json_object_object_add(response, "data", data_obj); - - uci_free_context(uci_ctx); - - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - -static int handle_add_whitelist_user(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - json_object_put(response); - return -1; - } - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - if (!req_obj) { - printf("parse json failed\n"); - free(msg_obj_str); - json_object_put(response); - return -1; - } - struct json_object *mac_array = json_object_object_get(req_obj, "mac_list"); - if (!mac_array) { - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return -1; - } - - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return -1; - } - - int len = json_object_array_length(mac_array); - for (int i = 0; i < len; i++) { - struct json_object *mac_obj = json_object_array_get_idx(mac_array, i); - af_uci_add_section(uci_ctx, "appfilter", "whitelist"); - af_uci_set_value(uci_ctx, "appfilter.@whitelist[-1].mac", json_object_get_string(mac_obj)); - } - af_uci_commit(uci_ctx, "appfilter"); - reload_oaf_rule(); - - if (g_enable_agent) { - af_forward_msg_to_agent("add_whitelist_user", msg_obj_str, strlen(msg_obj_str)); - } - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - -static int handle_service_config(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - json_object_put(response); - return -1; - } - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - if (!req_obj) { - printf("parse json failed\n"); - free(msg_obj_str); - json_object_put(response); - return -1; - } - struct json_object *agent_enable_obj = json_object_object_get(req_obj, "agent_enable"); - if (!agent_enable_obj) { - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return -1; - } - - g_enable_agent = json_object_get_int(agent_enable_obj); - - printf("g_enable_agent: %d\n", g_enable_agent); - - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - - - -static int handle_del_whitelist_user(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - int i; - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - return 0; - } - printf("msg_obj_str: %s\n", msg_obj_str); - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); - if (!mac_obj) { - printf("mac_obj is NULL\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - char mac_str[128] = {0}; - int num = af_get_uci_list_num(uci_ctx, "appfilter", "whitelist"); - for (int i = 0; i < num; i++) { - af_uci_get_array_value(uci_ctx, "appfilter.@whitelist[%d].mac", i, mac_str, sizeof(mac_str)); - if (strcmp(mac_str, json_object_get_string(mac_obj)) == 0) { - char buf[128] = {0}; - sprintf(buf, "appfilter.@whitelist[%d]", i); - af_uci_delete(uci_ctx, buf); - break; - } - } - - af_uci_commit(uci_ctx, "appfilter"); - reload_oaf_rule(); - - if (g_enable_agent) { - af_forward_msg_to_agent("del_whitelist_user", msg_obj_str, strlen(msg_obj_str)); - } - - uci_free_context(uci_ctx); - json_object_put(req_obj); - free(msg_obj_str); - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - json_object_put(response); - return 0; -} - - - -static int handle_cmd(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) { - struct json_object *response = json_object_new_object(); - char *msg_obj_str = blobmsg_format_json(msg, true); - if (!msg_obj_str) { - printf("format json failed\n"); - json_object_put(response); - return 0; - } - printf("handle_cmd: msg_obj_str: %s\n", msg_obj_str); - - struct json_object *req_obj = json_tokener_parse(msg_obj_str); - if (!req_obj) { - printf("parse json failed\n"); - free(msg_obj_str); - json_object_put(response); - return 0; - } - - struct json_object *action_obj = json_object_object_get(req_obj, "action"); - if (!action_obj) { - printf("action is NULL\n"); - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; - } - - const char *action = json_object_get_string(action_obj); - printf("handle_cmd: action = %s\n", action); - - int ret = 0; - const char *result_msg = NULL; - - if (strcmp(action, "clear_active_time") == 0) { - // Clear all users' today active time (AM and PM) - reset_all_users_today_active_time(); - result_msg = "Successfully cleared all users' active time"; - ret = 0; - printf("handle_cmd: cleared all users' active time\n"); - } else if (strcmp(action, "clear_offline_users") == 0) { - // Clear all offline users - flush_offline_users(); - result_msg = "Successfully cleared all offline users"; - ret = 0; - printf("handle_cmd: cleared all offline users\n"); - } else { - result_msg = "Unknown action"; - ret = -1; - printf("handle_cmd: unknown action: %s\n", action); - } - - json_object_object_add(response, "code", json_object_new_int(ret)); - json_object_object_add(response, "message", json_object_new_string(result_msg)); - - struct blob_buf b = {}; - blob_buf_init(&b, 0); - blobmsg_add_object(&b, response); - ubus_send_reply(ctx, req, b.head); - blob_buf_free(&b); - - json_object_put(req_obj); - free(msg_obj_str); - json_object_put(response); - return 0; -} - -static const struct blobmsg_policy empty_policy[1] = { - //[DEV_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, -}; - -static struct ubus_method appfilter_object_methods[] = { - UBUS_METHOD("dev_visit_list", appfilter_handle_dev_visit_list, empty_policy), - UBUS_METHOD("dev_visit_time", appfilter_handle_visit_time, empty_policy), - UBUS_METHOD("app_class_visit_time", handle_app_class_visit_time, empty_policy), - UBUS_METHOD("dev_list", appfilter_handle_dev_list, empty_policy), - UBUS_METHOD("class_list", handle_get_class_list, empty_policy), - UBUS_METHOD("set_app_filter", handle_set_app_filter, empty_policy), - UBUS_METHOD("get_app_filter", handle_get_app_filter, empty_policy), - UBUS_METHOD("set_app_filter_base", handle_set_app_filter_base, empty_policy), - UBUS_METHOD("get_app_filter_base", handle_get_app_filter_base, empty_policy), - UBUS_METHOD("set_app_filter_adv", handle_set_app_filter_adv, empty_policy), - UBUS_METHOD("get_app_filter_adv", handle_get_app_filter_adv, empty_policy), - UBUS_METHOD("set_app_filter_time", handle_set_app_filter_time, empty_policy), - UBUS_METHOD("get_app_filter_time", handle_get_app_filter_time, empty_policy), - UBUS_METHOD("get_all_users", handle_get_all_users, empty_policy), - UBUS_METHOD("get_app_filter_user", handle_get_app_filter_user, empty_policy), - UBUS_METHOD("set_app_filter_user", handle_set_app_filter_user, empty_policy), - UBUS_METHOD("del_app_filter_user", handle_del_app_filter_user, empty_policy), - UBUS_METHOD("add_app_filter_user", handle_add_app_filter_user, empty_policy), - UBUS_METHOD("set_nickname", handle_set_nickname, empty_policy), - UBUS_METHOD("get_oaf_status", handle_get_oaf_status, empty_policy), - UBUS_METHOD("debug", handle_debug, empty_policy), - UBUS_METHOD("get_whitelist_user", handle_get_whitelist_user, empty_policy), - UBUS_METHOD("add_whitelist_user", handle_add_whitelist_user, empty_policy), - UBUS_METHOD("del_whitelist_user", handle_del_whitelist_user, empty_policy), - UBUS_METHOD("service_config", handle_service_config, empty_policy), - UBUS_METHOD("cmd", handle_cmd, empty_policy), -}; - - - - -static struct ubus_object_type main_object_type = - UBUS_OBJECT_TYPE("appfilter", appfilter_object_methods); - -static struct ubus_object main_object = { - .name = "appfilter", - .type = &main_object_type, - .methods = appfilter_object_methods, - .n_methods = ARRAY_SIZE(appfilter_object_methods), -}; - -static void appfilter_add_object(struct ubus_object *obj) -{ - ubus_add_object(ubus_ctx, obj); -} - -int appfilter_ubus_init(void) -{ - ubus_ctx = ubus_connect("/var/run/ubus/ubus.sock"); - if (!ubus_ctx){ - ubus_ctx = ubus_connect("/var/run/ubus.sock"); - } - if (!ubus_ctx){ - return -EIO; - } - - appfilter_add_object(&main_object); - ubus_add_uloop(ubus_ctx); - return 0; -} diff --git a/open-app-filter/src/appfilter_ubus.h b/open-app-filter/src/appfilter_ubus.h deleted file mode 100644 index c53dde59..00000000 --- a/open-app-filter/src/appfilter_ubus.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - Copyright (C) 2020 Derry - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ -#ifndef __APPFILTER_UBUS_H__ -#define __APPFILTER_UBUS_H__ -int appfilter_ubus_init(void); -#endif \ No newline at end of file diff --git a/open-app-filter/src/appfilter_user.c b/open-app-filter/src/appfilter_user.c deleted file mode 100644 index 96821cb8..00000000 --- a/open-app-filter/src/appfilter_user.c +++ /dev/null @@ -1,1000 +0,0 @@ -/* -Copyright (C) 2020 Derry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "appfilter_config.h" -#include "appfilter.h" -#include "appfilter_user.h" - -dev_node_t *dev_hash_table[MAX_DEV_NODE_HASH_SIZE]; -int g_cur_user_num = 0; -unsigned int hash_mac(unsigned char *mac) -{ - unsigned int hash = 0; - int i; - if (!mac) - return 0; - for (i = 0; mac[i] != '\0' && i < MAX_MAC_LEN; i++) { - hash = hash * 31 + mac[i]; - } - return hash & (MAX_DEV_NODE_HASH_SIZE - 1); -} - -int hash_appid(int appid) -{ - return appid % (MAX_VISIT_HASH_SIZE - 1); -} - -void add_visit_info_node(visit_info_t **head, visit_info_t *node) -{ - if (*head == NULL) - { - *head = node; - } - else - { - node->next = *head; - *head = node; - } -} - -void init_dev_node_htable() -{ - int i; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_hash_table[i] = NULL; - } - printf("init dev node htable ok...\n"); -} - -dev_node_t *add_dev_node(char *mac) -{ - unsigned int hash = 0; - hash = hash_mac(mac); - if (hash >= MAX_DEV_NODE_HASH_SIZE) - { - printf("hash code error %d\n", hash); - return NULL; - } - dev_node_t *node = (dev_node_t *)calloc(1, sizeof(dev_node_t)); - if (!node) - return NULL; - strncpy(node->mac, mac, sizeof(node->mac)); - node->online = 1; - node->online_time = get_timestamp(); - if (dev_hash_table[hash] == NULL) - dev_hash_table[hash] = node; - else - { - node->next = dev_hash_table[hash]; - dev_hash_table[hash] = node; - } - g_cur_user_num++; - printf("add mac:%s to htable[%d]....success\n", mac, hash); - return node; -} - -dev_node_t *find_dev_node(char *mac) -{ - unsigned int hash = 0; - dev_node_t *p = NULL; - hash = hash_mac(mac); - if (hash >= MAX_DEV_NODE_HASH_SIZE) - { - printf("hash code error %d\n", hash); - return NULL; - } - p = dev_hash_table[hash]; - while (p) - { - if (0 == strncmp(p->mac, mac, sizeof(p->mac))) - { - return p; - } - p = p->next; - } - return NULL; -} - -void dev_foreach(void *arg, iter_func iter) -{ - int i, j; - dev_node_t *node = NULL; - - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - iter(arg, node); - node = node->next; - } - } -} - -char *format_time(int timetamp) -{ - char time_buf[64] = {0}; - time_t seconds = timetamp; - struct tm *auth_tm = localtime(&seconds); - strftime(time_buf, sizeof(time_buf), "%Y %m %d %H:%M:%S", auth_tm); - return strdup(time_buf); -} - -void update_dev_hostname(void) -{ - char line_buf[256] = {0}; - char hostname_buf[128] = {0}; - char mac_buf[32] = {0}; - char ip_buf[32] = {0}; - - FILE *fp = fopen("/tmp/dhcp.leases", "r"); - if (!fp) - { - printf("open dhcp lease file....failed\n"); - return; - } - while (fgets(line_buf, sizeof(line_buf), fp)) - { - if (strlen(line_buf) <= 16) - continue; - sscanf(line_buf, "%*s %s %s %s", mac_buf, ip_buf, hostname_buf); - dev_node_t *node = find_dev_node(mac_buf); - if (!node) - { - node = add_dev_node(mac_buf); - if (!node) - continue; - strncpy(node->ip, ip_buf, sizeof(node->ip)); - node->online = 0; - node->offline_time = get_timestamp(); - } - - if (strlen(hostname_buf) > 0 && hostname_buf[0] != '*') - { - strncpy(node->hostname, hostname_buf, sizeof(node->hostname)); - } - } - fclose(fp); -} - -void clean_dev_nickname_iter(void *arg, dev_node_t *node) -{ - node->nickname[0] = '\0'; -} - -void clean_dev_nickname(void) -{ - dev_foreach(NULL, clean_dev_nickname_iter); -} - -void update_dev_nickname(void) -{ - char nickname_buf[128] = {0}; - char mac_str[128] = {0}; - struct uci_context *uci_ctx = uci_alloc_context(); - clean_dev_nickname(); - int num = af_get_uci_list_num(uci_ctx, "user_info", "user_info"); - - for (int i = 0; i < num; i++) { - af_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].mac", i, mac_str, sizeof(mac_str)); - dev_node_t *node = find_dev_node(mac_str); - if (!node) - continue; - - af_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].nickname", i, nickname_buf, sizeof(nickname_buf)); - strncpy(node->nickname, nickname_buf, sizeof(node->nickname)); - } - uci_free_context(uci_ctx); -} - - -void clean_dev_whitelist_flag_iter(void *arg, dev_node_t *node) -{ - node->is_whitelist = 0; -} - -void clean_dev_whitelist_flag(void) -{ - dev_foreach(NULL, clean_dev_whitelist_flag_iter); -} - - -void update_dev_whitelist_flag(void) -{ - clean_dev_whitelist_flag(); - dev_node_t *node = NULL; - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - return; - } - char mac_str[128] = {0}; - int num = af_get_uci_list_num(uci_ctx, "appfilter", "whitelist"); - for (int i = 0; i < num; i++) { - af_uci_get_array_value(uci_ctx, "appfilter.@whitelist[%d].mac", i, mac_str, sizeof(mac_str)); - node = find_dev_node(mac_str); - if (node) { - node->is_whitelist = 1; - } - } - uci_free_context(uci_ctx); -} - -void clean_dev_selected_flag_iter(void *arg, dev_node_t *node) -{ - node->is_selected = 0; -} - -void clean_dev_selected_flag(void) -{ - dev_foreach(NULL, clean_dev_selected_flag_iter); -} - -void update_dev_selected_flag(void) -{ - extern af_config_t g_af_config; - int user_mode = g_af_config.global.user_mode; - - clean_dev_selected_flag(); - - if (user_mode == 0) { - int i; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) { - dev_node_t *node = dev_hash_table[i]; - while (node) { - if (!node->is_whitelist) { - node->is_selected = 1; - } - node = node->next; - } - } - LOG_DEBUG("Auto mode: all users except whitelist are selected\n"); - } else { - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - LOG_ERROR("Failed to allocate UCI context in update_dev_selected_flag\n"); - return; - } - - char mac_str[128] = {0}; - int num = af_get_uci_list_num(uci_ctx, "appfilter", "af_user"); - for (int i = 0; i < num; i++) { - af_uci_get_array_value(uci_ctx, "appfilter.@af_user[%d].mac", i, mac_str, sizeof(mac_str)); - dev_node_t *node = find_dev_node(mac_str); - if (node) { - node->is_selected = 1; - } - } - uci_free_context(uci_ctx); - LOG_DEBUG("Manual mode: %d users from af_user config are selected\n", num); - } -} - - - -void clean_dev_online_status(void) -{ - int i; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - - if (node->online) - { - node->offline_time = get_timestamp(); - node->online = 0; - } - node = node->next; - } - } - -} - - -void update_dev_from_kernel(void) -{ - char line_buf[256] = {0}; - char mac_buf[32] = {0}; - char ip_buf[32] = {0}; - char ipv6_buf[128] = {0}; - unsigned int up_rate = 0; - unsigned int down_rate = 0; - - FILE *fp = fopen("/proc/net/af_client", "r"); - if (!fp) - { - printf("open client file....failed\n"); - return; - } - fgets(line_buf, sizeof(line_buf), fp); // title - while (fgets(line_buf, sizeof(line_buf), fp)) - { - int id; - int parsed = sscanf(line_buf, "%d %s %s %s %u %u", &id, mac_buf, ip_buf, ipv6_buf, &up_rate, &down_rate); - if (parsed < 3) - { - printf("invalid line format:%s\n", line_buf); - continue; - } - if (strlen(mac_buf) < 17) - { - printf("invalid mac:%s\n", mac_buf); - continue; - } - dev_node_t *node = find_dev_node(mac_buf); - if (!node) - { - node = add_dev_node(mac_buf); - if (!node) - continue; - strncpy(node->ip, ip_buf, sizeof(node->ip)); - } - - strncpy(node->ip, ip_buf, sizeof(node->ip)); - - if (parsed >= 4 && strlen(ipv6_buf) > 0) - { - strncpy(node->ipv6, ipv6_buf, sizeof(node->ipv6)); - } - else - { - node->ipv6[0] = '\0'; - } - - if (parsed >= 5) - { - node->up_rate = up_rate; - } - else - { - node->up_rate = 0; - } - if (parsed >= 6) - { - node->down_rate = down_rate; - } - else - { - node->down_rate = 0; - } - node->online = 1; - } - fclose(fp); -} - - -void update_dev_online_status(void) -{ - update_dev_from_kernel(); -} - -#define DEV_OFFLINE_TIME (SECONDS_PER_DAY * 7) - -int check_dev_expire(void) -{ - int i, j; - int count = 0; - int cur_time = get_timestamp(); - int offline_time = 0; - int expire_count = 0; - int visit_count = 0; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - if (node->online) - goto NEXT; - visit_count = 0; - offline_time = cur_time - node->offline_time; - if (offline_time > DEV_OFFLINE_TIME) - { - node->expire = 1; - for (j = 0; j < MAX_VISIT_HASH_SIZE; j++) - { - visit_info_t *p_info = node->visit_htable[j]; - while (p_info) - { - p_info->expire = 1; - visit_count++; - p_info = p_info->next; - } - } - expire_count++; - LOG_WARN("dev:%s expired, offline time = %ds, count=%d, visit_count=%d\n", - node->mac, offline_time, expire_count, visit_count); - } - NEXT: - node = node->next; - } - } - return expire_count; -} - -void flush_dev_expire_node(void) -{ - int i, j; - int count = 0; - dev_node_t *node = NULL; - dev_node_t *prev = NULL; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - prev = NULL; - while (node) - { - if (node->expire) - { - if (NULL == prev) - { - dev_hash_table[i] = node->next; - free(node); - node = dev_hash_table[i]; - prev = NULL; - } - else - { - prev->next = node->next; - free(node); - node = prev->next; - } - } - else - { - prev = node; - node = node->next; - } - } - } -} - -void flush_offline_users(void) -{ - int i, j; - int count = 0; - dev_node_t *node = NULL; - dev_node_t *prev = NULL; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - prev = NULL; - while (node) - { - if (!node->online) // Clear all offline users - { - // Free visit info first - for (j = 0; j < MAX_VISIT_HASH_SIZE; j++) - { - visit_info_t *p_info = node->visit_htable[j]; - while (p_info) - { - visit_info_t *next = p_info->next; - free(p_info); - p_info = next; - } - } - - // Remove node from hash table - if (NULL == prev) - { - dev_hash_table[i] = node->next; - free(node); - node = dev_hash_table[i]; - prev = NULL; - } - else - { - prev->next = node->next; - free(node); - node = prev->next; - } - count++; - } - else - { - prev = node; - node = node->next; - } - } - } - LOG_WARN("Cleared %d offline users\n", count); -} - -void save_user_time_to_file(void) -{ - int i; - int count = 0; - FILE *fp = fopen(OAF_USER_FILE, "w"); - if (!fp) { - LOG_ERROR("Failed to open file %s for writing\n", OAF_USER_FILE); - return; - } - - fprintf(fp, "MAC,AM_Time,PM_Time\n"); - - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) { - dev_node_t *node = dev_hash_table[i]; - while (node) { - fprintf(fp, "%s,%u,%u\n", - node->mac, - node->today_am_active_time, - node->today_pm_active_time); - count++; - node = node->next; - } - } - - fclose(fp); - LOG_DEBUG("Saved %d users' time data to %s\n", count, OAF_USER_FILE); -} - -void load_user_time_from_file(void) -{ - FILE *fp = fopen(OAF_USER_FILE, "r"); - if (!fp) { - LOG_DEBUG("File %s not found or cannot be opened, starting with empty data\n", OAF_USER_FILE); - return; - } - - char line_buf[256] = {0}; - int count = 0; - - if (fgets(line_buf, sizeof(line_buf), fp) == NULL) { - fclose(fp); - return; - } - - while (fgets(line_buf, sizeof(line_buf), fp)) { - char mac[32] = {0}; - unsigned int am_time = 0; - unsigned int pm_time = 0; - - if (sscanf(line_buf, "%31[^,],%u,%u", mac, &am_time, &pm_time) == 3) { - dev_node_t *node = find_dev_node(mac); - if (!node) { - // If node doesn't exist, create a new one - node = add_dev_node(mac); - if (!node) { - LOG_WARN("Failed to create device node for MAC=%s\n", mac); - continue; - } - LOG_DEBUG("Created new device node for MAC=%s\n", mac); - } - node->today_am_active_time = am_time; - node->today_pm_active_time = pm_time; - count++; - LOG_DEBUG("Loaded user time: MAC=%s, AM=%u, PM=%u\n", mac, am_time, pm_time); - } else { - LOG_WARN("Failed to parse line: %s\n", line_buf); - } - } - - fclose(fp); - LOG_WARN("Loaded %d users' time data from %s\n", count, OAF_USER_FILE); -} - -void update_dev_visiting_info(void){ - char line_buf[256] = {0}; - char mac_buf[32] = {0}; - char url_buf[32] = {0}; - char app_buf[32] = {0}; - char time_buf[32] = {0}; - - FILE *fp = fopen("/proc/net/af_visit", "r"); - if (!fp) - { - printf("open af_visit file....failed\n"); - return; - } - fgets(line_buf, sizeof(line_buf), fp); // title - while (fgets(line_buf, sizeof(line_buf), fp)) - { - sscanf(line_buf, "%s %s %s", mac_buf, app_buf, url_buf); - dev_node_t *node = find_dev_node(mac_buf); - if (!node) - continue; - if (strcmp(url_buf, "none") == 0) { - node->visiting_url[0] = '\0'; - } - else { - strncpy(node->visiting_url, url_buf, sizeof(node->visiting_url)); - } - node->visiting_app = atoi(app_buf); - } - fclose(fp); -} - -void update_dev_list(void) -{ - clean_dev_online_status(); - update_dev_hostname(); - update_dev_nickname(); - update_dev_online_status(); - update_dev_visiting_info(); - update_dev_selected_flag(); -} - - -void dump_dev_list(void) - -{ - int i, j; - int count = 0; - char hostname_buf[MAX_HOSTNAME_SIZE] = {0}; - char ip_buf[MAX_IP_LEN] = {0}; - - FILE *fp = fopen(OAF_DEV_LIST_FILE, "w"); - if (!fp) - { - return; - } - fprintf(fp, "%-4s %-20s %-20s %-32s %-8s\n", "Id", "Mac Addr", "Ip Addr", "Hostname", "Online"); - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - if (node->online != 0) - { - if (strlen(node->hostname) == 0) - strcpy(hostname_buf, "*"); - else - strcpy(hostname_buf, node->hostname); - if (strlen(node->ip) == 0) - strcpy(ip_buf, "*"); - else - strcpy(ip_buf, node->ip); - fprintf(fp, "%-4d %-20s %-20s %-32s %-8d\n", - i + 1, node->mac, ip_buf, hostname_buf, node->online); - count++; - } - - node = node->next; - } - } - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - if (node->online == 0) - { - if (strlen(node->hostname) == 0) - strcpy(hostname_buf, "*"); - else - strcpy(hostname_buf, node->hostname); - - if (strlen(node->ip) == 0) - strcpy(ip_buf, "*"); - else - strcpy(ip_buf, node->ip); - - fprintf(fp, "%-4d %-20s %-20s %-32s %-8d\n", - i + 1, node->mac, ip_buf, hostname_buf, node->online); - } - - node = node->next; - } - } -EXIT: - fclose(fp); -} - -#define MAX_RECORD_TIME (1 * 24 * 60 * 60) // 1day -#define RECORD_REMAIN_TIME (60 * 60) // 1hour -#define INVALID_RECORD_TIME (5 * 60) // 5min -void check_dev_visit_info_expire(void) -{ - int i, j; - int count = 0; - int cur_time = get_timestamp(); - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - for (j = 0; j < MAX_VISIT_HASH_SIZE; j++) - { - visit_info_t *p_info = node->visit_htable[j]; - while (p_info) - { - - int total_time = p_info->latest_time - p_info->first_time; - int interval_time = cur_time - p_info->first_time; - if (interval_time > MAX_RECORD_TIME || interval_time < 0) - { - p_info->expire = 1; - } - else if (interval_time > RECORD_REMAIN_TIME) - { - if (total_time < INVALID_RECORD_TIME) - p_info->expire = 1; - } - LOG_DEBUG("[%s] appid:%d total_time:%ds interval:%ds, expire = %d\n", node->mac, p_info->appid, total_time, interval_time, p_info->expire); - p_info = p_info->next; - } - } - node = node->next; - } - } -} - -void flush_expire_visit_info(void) -{ - int i, j; - int count = 0; - visit_info_t *prev = NULL; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - for (j = 0; j < MAX_VISIT_HASH_SIZE; j++) - { - visit_info_t *p_info = node->visit_htable[j]; - prev = NULL; - while (p_info) - { - if (p_info->expire) - { - LOG_DEBUG("check expire,flush expire visit info: %s, appid=%d\n", node->mac, p_info->appid); - if (NULL == prev) - { - node->visit_htable[j] = p_info->next; - free(p_info); - p_info = node->visit_htable[j]; - prev = NULL; - } - else - { - prev->next = p_info->next; - free(p_info); - p_info = prev->next; - } - } - else - { - prev = p_info; - p_info = p_info->next; - } - } - } - node = node->next; - } - } -} - -void dump_dev_visit_list(void) -{ - int i, j; - int count = 0; - FILE *fp = fopen(OAF_VISIT_LIST_FILE, "w"); - if (!fp) - { - return; - } - - fprintf(fp, "%-4s %-20s %-20s %-8s %-32s %-32s %-32s %-8s\n", "Id", "Mac Addr", - "Ip Addr", "Appid", "First Time", "Latest Time", "Total Time(s)", "Expire"); - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - for (j = 0; j < MAX_VISIT_HASH_SIZE; j++) - { - visit_info_t *p_info = node->visit_htable[j]; - while (p_info) - { - char *first_time_str = format_time(p_info->first_time); - char *latest_time_str = format_time(p_info->latest_time); - int total_time = p_info->latest_time - p_info->first_time; - fprintf(fp, "%-4d %-20s %-20s %-8d %-32s %-32s %-32d %-4d\n", - count, node->mac, node->ip, p_info->appid, first_time_str, - latest_time_str, total_time, p_info->expire); - if (first_time_str) - free(first_time_str); - if (latest_time_str) - free(latest_time_str); - p_info = p_info->next; - count++; - if (count > 50) - goto EXIT; - } - } - node = node->next; - } - } -EXIT: - fclose(fp); -} - -void clean_invalid_app_records(void) -{ - int i, j; - int invalid_count = 0; - int total_count = 0; - - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - for (j = 0; j < MAX_VISIT_HASH_SIZE; j++) - { - visit_info_t *p_info = node->visit_htable[j]; - while (p_info) - { - total_count++; - char *app_name = get_app_name_by_id(p_info->appid); - if (app_name && strlen(app_name) == 0) - { - p_info->expire = 1; - invalid_count++; - LOG_DEBUG("clean: MAC=%s, AppID=%d\n", node->mac, p_info->appid); - } - p_info = p_info->next; - } - } - node = node->next; - } - } - if (invalid_count > 0) - { - flush_expire_visit_info(); - } -} - -void clear_device_app_statistics(void) -{ - int i; - - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - memset(node->stat, 0, sizeof(node->stat)); - node = node->next; - } - } - -} - -void check_and_reset_today_active_time(dev_node_t *node) -{ - if (!node) - return; - - time_t now = time(NULL); - struct tm *tm_info = localtime(&now); - int current_hour = tm_info->tm_hour; - int current_min = tm_info->tm_min; - - - static int last_reset_hour = -1; - static int last_reset_min = -1; - - if (current_hour == 12 && current_min == 0) { - if (last_reset_hour != 12 || last_reset_min != 0) { - LOG_DEBUG("Reset today_am_active_time for %s: %d -> 0 (12:00 reset)\n", - node->mac, node->today_am_active_time); - node->today_am_active_time = 0; - last_reset_hour = 12; - last_reset_min = 0; - } - } else { - last_reset_hour = current_hour; - last_reset_min = current_min; - } -} - -void reset_all_users_today_active_time(void) -{ - extern af_run_time_status_t g_af_status; - int i; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) { - dev_node_t *node = dev_hash_table[i]; - while (node) { - LOG_DEBUG("Reset today active time for %s: am=%d->0, pm=%d->0 (day changed)\n", - node->mac, node->today_am_active_time, node->today_pm_active_time); - node->today_am_active_time = 0; - node->today_pm_active_time = 0; - node = node->next; - } - } - g_af_status.period_blocked = 0; -} - - -void reset_all_users_today_flow(void) -{ - int i; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) { - dev_node_t *node = dev_hash_table[i]; - while (node) { - - node->today_down_bytes = 0; - node->today_up_bytes = 0; - node = node->next; - } - } -} - - -void check_all_users_period_time(void) -{ - int i; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) - { - dev_node_t *node = dev_hash_table[i]; - while (node) - { - check_and_reset_today_active_time(node); - - node = node->next; - } - } -} - -void save_user_active_time_to_file(void) -{ - FILE *fp = fopen(OAF_USER_FILE, "w"); - if (!fp) { - LOG_ERROR("Failed to open file for writing: %s\n", OAF_USER_FILE); - return; - } - - fprintf(fp, "mac,today_am_active_time,today_pm_active_time\n"); - - int i; - int count = 0; - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) { - dev_node_t *node = dev_hash_table[i]; - while (node) { - if (node->today_am_active_time > 0 || node->today_pm_active_time > 0) { - fprintf(fp, "%s,%u,%u\n", - node->mac, - node->today_am_active_time, - node->today_pm_active_time); - count++; - } - node = node->next; - } - } - - fclose(fp); - LOG_DEBUG("Saved %d users' active time to %s\n", count, OAF_USER_FILE); -} diff --git a/open-app-filter/src/appfilter_user.h b/open-app-filter/src/appfilter_user.h deleted file mode 100644 index a70f7c52..00000000 --- a/open-app-filter/src/appfilter_user.h +++ /dev/null @@ -1,136 +0,0 @@ -/* -Copyright (C) 2020 Derry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#ifndef __FILTER_USER_H__ -#define __FILTER_USER_H__ -#include -#define MAX_IP_LEN 32 -#define MAX_MAC_LEN 32 -#define MAX_VISIT_HASH_SIZE 64 -#define MAX_DEV_NODE_HASH_SIZE 64 -#define MAX_HOSTNAME_SIZE 64 -#define OAF_VISIT_LIST_FILE "/tmp/visit_list" -#define OAF_DEV_LIST_FILE "/tmp/dev_list" -#define OAF_USER_FILE "/etc/user_list.dat" -#define MIN_VISIT_TIME 5 // default 5s -#define MAX_APP_STAT_NUM 8 -#define MAX_VISITLIST_DUMP_NUM 16 -#define MAX_APP_TYPE 32 -#define MAX_APP_ID_NUM 512 -#define MAX_SUPPORT_DEV_NUM 256 -#define SECONDS_PER_DAY (24 * 3600) -#define MAX_NICKNAME_SIZE 64 -#define MAX_REPORT_URL_LEN 64 - - -typedef struct visit_info -{ - int appid; - u_int32_t first_time; - u_int32_t latest_time; - int action; - int expire; - struct visit_info *next; -} visit_info_t; - -typedef struct visit_stat -{ - u_int32_t total_time; -} visit_stat_t; - -typedef struct dev_node -{ - char mac[MAX_MAC_LEN]; - char ip[MAX_IP_LEN]; - char ipv6[64]; - char hostname[MAX_HOSTNAME_SIZE]; - char nickname[MAX_NICKNAME_SIZE]; - int online; - int expire; - u_int32_t offline_time; - u_int32_t online_time; - visit_info_t *visit_htable[MAX_VISIT_HASH_SIZE]; - visit_stat_t stat[MAX_APP_TYPE][MAX_APP_ID_NUM]; // todo: list - char visiting_url[MAX_REPORT_URL_LEN]; - int visiting_app; - int is_whitelist; - u_int32_t up_rate; - u_int32_t down_rate; - u_int64_t today_up_bytes; - u_int64_t today_down_bytes; - int active; - u_int32_t today_am_active_time; - u_int32_t today_pm_active_time; - int is_selected; - struct dev_node *next; -} dev_node_t; - -struct app_visit_info -{ - int app_id; - char app_name[32]; - int total_time; -}; - -struct app_visit_stat_info -{ - int num; - struct app_visit_info visit_list[MAX_APP_STAT_NUM]; -}; -typedef void (*iter_func)(void *arg, dev_node_t *dev); -//todo:dev for each -extern dev_node_t *dev_hash_table[MAX_DEV_NODE_HASH_SIZE]; - -dev_node_t *add_dev_node(char *mac); -void init_dev_node_htable(); -void dump_dev_list(void); -void dump_dev_visit_list(void); -dev_node_t *find_dev_node(char *mac); -void dev_foreach(void *arg, iter_func iter); -void add_visit_info_node(visit_info_t **head, visit_info_t *node); -void check_dev_visit_info_expire(void); -void flush_expire_visit_info(); -int check_dev_expire(void); -void flush_dev_expire_node(void); -void flush_expire_visit_info(void); -void flush_offline_users(void); -void save_user_time_to_file(void); -void load_user_time_from_file(void); -void update_dev_list(void); -void update_dev_nickname(void); -void update_dev_visiting_info(void); -void update_dev_whitelist_flag(void); -void update_dev_selected_flag(void); -void clean_invalid_app_records(void); - -void clear_device_app_statistics(void); - -void check_and_reset_today_active_time(dev_node_t *node); -void reset_all_users_today_active_time(void); - -void reset_all_users_today_flow(void); - -void check_all_users_period_time(void); -void update_dev_online_status(void); - - -#endif diff --git a/open-app-filter/src/check_main.c b/open-app-filter/src/check_main.c new file mode 100644 index 00000000..1e633f31 --- /dev/null +++ b/open-app-filter/src/check_main.c @@ -0,0 +1,82 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include "fwx.h" +#include "fwx_utils.h" +#include "check_main.h" + +#define CHECK_INTERVAL 30 +#define LOG_DIR_PATH "/tmp/log" +#define LOG_DIR_MAX_SIZE_KB 10240 + +static pthread_t check_thread; +static int check_thread_running = 0; +static int check_thread_exit = 0; + +static void check_and_cleanup_log_dir(void) { + char cmd_buf[256]; + char result_buf[64]; + memset(result_buf, 0, sizeof(result_buf)); + snprintf(cmd_buf, sizeof(cmd_buf), "du -sk %s 2>/dev/null | awk '{print $1}'", LOG_DIR_PATH); + exec_with_result_line(cmd_buf, result_buf, sizeof(result_buf)); + if (result_buf[0] == '\0') + return; + int size_kb = atoi(result_buf); + LOG_INFO("check_and_cleanup_log_dir: log dir size = %d KB\n", size_kb); + if (size_kb <= LOG_DIR_MAX_SIZE_KB) + return; + snprintf(cmd_buf, sizeof(cmd_buf), "rm -rf %s/*", LOG_DIR_PATH); + system(cmd_buf); +} + +static void* check_thread_func(void *arg) { + LOG_DEBUG("check_thread: thread function started\n"); + + check_thread_running = 1; + LOG_DEBUG("check_thread: running\n"); + + check_and_cleanup_log_dir(); + + while (!check_thread_exit) { + sleep(CHECK_INTERVAL); + + if (!check_thread_exit) { + check_and_cleanup_log_dir(); + } + } + + check_thread_running = 0; + LOG_DEBUG("check_thread: exited\n"); + return NULL; +} + +int start_check_thread(void) { + int ret; + + check_thread_exit = 0; + check_thread_running = 0; + + ret = pthread_create(&check_thread, NULL, check_thread_func, NULL); + if (ret != 0) { + LOG_ERROR("Failed to create check_thread: %s\n", strerror(ret)); + return -1; + } + LOG_INFO("check_thread: created\n"); + return 0; +} + +void stop_check_thread(void) { + if (!check_thread_running) { + return; + } + check_thread_exit = 1; + pthread_join(check_thread, NULL); + +} diff --git a/open-app-filter/src/check_main.h b/open-app-filter/src/check_main.h new file mode 100644 index 00000000..15c428b3 --- /dev/null +++ b/open-app-filter/src/check_main.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __CHECK_MAIN_H__ +#define __CHECK_MAIN_H__ + + +int start_check_thread(void); + + +void stop_check_thread(void); + +#endif + diff --git a/open-app-filter/src/fwx.h b/open-app-filter/src/fwx.h new file mode 100644 index 00000000..766bf22a --- /dev/null +++ b/open-app-filter/src/fwx.h @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_H___ +#define __FWX_H___ +#define MIN_INET_ADDR_LEN 7 + +#include +#include +#include +#include +#include +#include "fwx_uci.h" + +#define API_CODE_SUCCESS 2000 +#define API_CODE_ERROR 4000 +#define LOG_FILE_PATH "/tmp/log/fwxd.log" + + +typedef struct fwx_status { + + int internet; +} fwx_status_t; + +extern fwx_status_t g_fwx_status; + +typedef struct fwx_capability { + int wireless_support; +} fwx_capability_t; + +extern fwx_capability_t g_fwx_capability; + +typedef enum { + LOG_LEVEL_ERROR, + LOG_LEVEL_WARN, + LOG_LEVEL_INFO, + LOG_LEVEL_DEBUG +} LogLevel; + +extern int current_log_level; +extern int g_fwxd_debug_mode; + +static void af_log(LogLevel level, const char *func, int line, const char *format, ...){ + if (level > current_log_level) + return; + + FILE *log_file = fopen(LOG_FILE_PATH, "a"); + if (!log_file) { + perror("Failed to open log file"); + return; + } + + time_t now = time(NULL); + struct tm *t = localtime(&now); + char time_str[20]; + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", t); + + const char *level_str; + switch (level) { + case LOG_LEVEL_DEBUG: level_str = "DEBUG"; break; + case LOG_LEVEL_INFO: level_str = "INFO"; break; + case LOG_LEVEL_WARN: level_str = "WARN"; break; + case LOG_LEVEL_ERROR: level_str = "ERROR"; break; + default: level_str = "UNKNOWN"; break; + } + + fprintf(log_file, "[%s] [%s] %s:%d ", time_str, level_str, func, line); + + va_list args; + va_start(args, format); + vfprintf(log_file, format, args); + va_end(args); + fprintf(log_file, "\n"); + fclose(log_file); +} + +#define LOG_DEBUG(format, ...) af_log(LOG_LEVEL_DEBUG, __func__, __LINE__, format, ##__VA_ARGS__) +#define LOG_INFO(format, ...) af_log(LOG_LEVEL_INFO, __func__, __LINE__, format, ##__VA_ARGS__) +#define LOG_WARN(format, ...) af_log(LOG_LEVEL_WARN, __func__, __LINE__, format, ##__VA_ARGS__) +#define LOG_ERROR(format, ...) af_log(LOG_LEVEL_ERROR, __func__, __LINE__, format, ##__VA_ARGS__) + +#define MAX_TIME_LIST_LEN 1024 +#define MAX_TIME_LIST 64 +typedef struct af_time +{ + int hour; + int min; +} af_time_t; + + +typedef struct time_config{ + af_time_t start_time; + af_time_t end_time; +}time_config_t; + +typedef struct weekday_time_config{ + af_time_t start_time; + af_time_t end_time; + unsigned char weekday_map[7]; +}weekday_time_config_t; + +typedef struct fwx_time_config_t{ + int time_mode; + weekday_time_config_t seg_time; + int deny_time; + int allow_time; + int time_num; + weekday_time_config_t time_list[MAX_TIME_LIST]; +}fwx_time_config_t; + + +typedef struct fwx_run_time_status{ + int deny_time; + int allow_time; + int filter; + int match_time; + int enable; +}fwx_run_time_status_t; + + + +void fwx_init_time_status(fwx_run_time_status_t *status); +int fwx_check_time(fwx_time_config_t *t_config, fwx_run_time_status_t *status); +struct json_object *fwx_gen_api_response_data(int code, struct json_object *data_obj); + + +#endif diff --git a/open-app-filter/src/fwx_app_filter.c b/open-app-filter/src/fwx_app_filter.c new file mode 100644 index 00000000..249a05c3 --- /dev/null +++ b/open-app-filter/src/fwx_app_filter.c @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "fwx_user.h" +#include "fwx_netlink.h" +#include "fwx_ubus.h" +#include "fwx_config.h" +#include +#include +#include +#include +#include "fwx.h" +#include +#include + +#include "fwx_utils.h" + + +#define APPFILTER_RULES_STATE_FILE "/tmp/appfilter_rules_state" +#define APPFILTER_WHITELIST_STATE_FILE "/tmp/appfilter_whitelist_state" + + +static void set_state_file(const char *file_path) { + FILE *fd = fopen(file_path, "w"); + if (fd) { + fprintf(fd, "1"); + fclose(fd); + LOG_DEBUG("Set state file: %s\n", file_path); + } else { + LOG_ERROR("Failed to set state file: %s\n", file_path); + } +} + +fwx_run_time_status_t g_af_status; + +void dev_list_timeout_handler(struct uloop_timeout *t); + + +static int find_rule_index_by_id(struct uci_context *uci_ctx, int id) { + char id_str_uci[32]; + int i; + int num = fwx_uci_get_list_num(uci_ctx, "appfilter", "rule"); + for (i = 0; i < num; i++) { + char buf[128]; + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].id", i); + if (fwx_uci_get_value(uci_ctx, buf, id_str_uci, sizeof(id_str_uci)) != 0) { + continue; + } + if (atoi(id_str_uci) == id) { + return i; + } + } + return -1; // Not found +} + +// Helper function to get option value from uci section +static const char *get_option_value(struct uci_section *s, const char *option_name) { + if (!s || !option_name) return NULL; + struct uci_option *o = uci_lookup_option(s->package->ctx, s, option_name); + if (!o || o->type != UCI_TYPE_STRING) { + return NULL; + } + return o->v.string; +} + +static int normalize_app_id_token(const char *raw, char *out, int out_len) { + int start = 0, end = 0, val = 0; + char extra = '\0'; + if (!raw || !out || out_len <= 0) return 0; + + if (sscanf(raw, "%d-%d%c", &start, &end, &extra) == 2) { + if (start > 0 && end > 0 && start <= end) { + snprintf(out, out_len, "%d-%d", start, end); + return 1; + } + return 0; + } + + if (sscanf(raw, "%d%c", &val, &extra) == 1 && val > 0) { + snprintf(out, out_len, "%d", val); + return 1; + } + return 0; +} + +struct json_object *fwx_api_get_filter_rules(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + struct json_object *rules_array = json_object_new_array(); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + // Load appfilter package once + struct uci_package *pkg = NULL; + if (uci_load(uci_ctx, "appfilter", &pkg) != UCI_OK) { + LOG_ERROR("Failed to load appfilter package\n"); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + // Traverse all sections using uci_foreach_element + struct uci_element *e; + uci_foreach_element(&pkg->sections, e) { + struct uci_section *s = uci_to_section(e); + + // Only process "rule" type sections + if (strcmp(s->type, "rule") != 0) { + continue; + } + + LOG_DEBUG("Loading rule: %s\n", s->e.name); + + // Get id - required field + const char *id_str = get_option_value(s, "id"); + if (!id_str) { + LOG_ERROR("Failed to get id for rule %s, skipping\n", s->e.name); + continue; + } + + // Get other fields with defaults + const char *name_str = get_option_value(s, "name"); + const char *mode_str = get_option_value(s, "mode"); + const char *user_mac_str = get_option_value(s, "user_mac"); + const char *user_name_str = get_option_value(s, "user_name"); + const char *enabled_str = get_option_value(s, "enabled"); + const char *filter_quic_str = get_option_value(s, "filter_quic"); + + struct json_object *rule_obj = json_object_new_object(); + if (!rule_obj) { + LOG_ERROR("Failed to create rule_obj for rule %s\n", s->e.name); + continue; + } + + json_object_object_add(rule_obj, "id", json_object_new_int(atoi(id_str))); + json_object_object_add(rule_obj, "name", json_object_new_string(name_str ? name_str : "")); + json_object_object_add(rule_obj, "mode", json_object_new_int(mode_str ? atoi(mode_str) : 1)); + json_object_object_add(rule_obj, "user_mac", json_object_new_string(user_mac_str ? user_mac_str : "")); + json_object_object_add(rule_obj, "user_name", json_object_new_string(user_name_str ? user_name_str : "")); + json_object_object_add(rule_obj, "enabled", json_object_new_int(enabled_str ? atoi(enabled_str) : 1)); + json_object_object_add(rule_obj, "filter_quic", json_object_new_int(filter_quic_str ? atoi(filter_quic_str) : 0)); + + // Process time_rule list + struct json_object *time_rules_array = json_object_new_array(); + struct uci_option *time_rule_opt = uci_lookup_option(uci_ctx, s, "time_rule"); + if (time_rule_opt && time_rule_opt->type == UCI_TYPE_LIST) { + struct uci_element *time_elem; + uci_foreach_element(&time_rule_opt->v.list, time_elem) { + const char *time_rule_str = time_elem->name; + if (!time_rule_str) continue; + + LOG_DEBUG("Loading time_rule: %s\n", time_rule_str); + struct json_object *time_rule_obj = json_object_new_object(); + struct json_object *weekdays_array = json_object_new_array(); + + // Parse time_rule string: "weekday1,weekday2,...,start_time,end_time" + char *time_rule_copy = strdup(time_rule_str); + if (time_rule_copy) { + char *saveptr; + char *token = strtok_r(time_rule_copy, ",", &saveptr); + char *start_time = NULL; + char *end_time = NULL; + + while (token) { + if (strchr(token, ':') != NULL) { + // This is a time string (HH:MM format) + if (start_time == NULL) { + start_time = token; + } else if (end_time == NULL) { + end_time = token; + break; + } + } else { + // This is a weekday number + int weekday = atoi(token); + if (weekday >= 0 && weekday <= 6) { + json_object_array_add(weekdays_array, json_object_new_int(weekday)); + } + } + token = strtok_r(NULL, ",", &saveptr); + } + + if (start_time) { + json_object_object_add(time_rule_obj, "start_time", json_object_new_string(start_time)); + } + if (end_time) { + json_object_object_add(time_rule_obj, "end_time", json_object_new_string(end_time)); + } + free(time_rule_copy); + } + + json_object_object_add(time_rule_obj, "weekdays", weekdays_array); + json_object_array_add(time_rules_array, time_rule_obj); + } + } + json_object_object_add(rule_obj, "time_rules", time_rules_array); + + // Process app_id list(支持字符串段格式:1001-1005) + struct json_object *app_ids_array = json_object_new_array(); + struct uci_option *app_id_opt = uci_lookup_option(uci_ctx, s, "app_id"); + if (app_id_opt && app_id_opt->type == UCI_TYPE_LIST) { + struct uci_element *app_elem; + uci_foreach_element(&app_id_opt->v.list, app_elem) { + const char *app_id_str = app_elem->name; + if (!app_id_str) continue; + json_object_array_add(app_ids_array, json_object_new_string(app_id_str)); + } + } + json_object_object_add(rule_obj, "app_ids", app_ids_array); + + json_object_array_add(rules_array, rule_obj); + LOG_DEBUG("Successfully loaded rule: id=%s, name=%s\n", id_str, name_str ? name_str : ""); + } + + // Unload package + uci_unload(uci_ctx, pkg); + uci_free_context(uci_ctx); + + json_object_object_add(data_obj, "list", rules_array); + + LOG_DEBUG("Returning %d rules\n", json_object_array_length(rules_array)); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_add_filter_rule(struct json_object *req_obj) { + struct json_object *name_obj = json_object_object_get(req_obj, "name"); + struct json_object *mode_obj = json_object_object_get(req_obj, "mode"); + struct json_object *user_mac_obj = json_object_object_get(req_obj, "user_mac"); + struct json_object *user_name_obj = json_object_object_get(req_obj, "user_name"); + struct json_object *enabled_obj = json_object_object_get(req_obj, "enabled"); + struct json_object *filter_quic_obj = json_object_object_get(req_obj, "filter_quic"); + struct json_object *time_rules_obj = json_object_object_get(req_obj, "time_rules"); + struct json_object *app_ids_obj = json_object_object_get(req_obj, "app_ids"); + int i, j; + if (!name_obj || !mode_obj || !time_rules_obj || !app_ids_obj) { + LOG_ERROR("Missing required fields\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + int rule_id = (int)time(NULL); + + + fwx_uci_add_section(uci_ctx, "appfilter", "rule"); + + char buf[256]; + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].id"); + char id_str[32]; + snprintf(id_str, sizeof(id_str), "%d", rule_id); + fwx_uci_set_value(uci_ctx, buf, id_str); + + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].name"); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(name_obj)); + + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].mode"); + char mode_str[16]; + snprintf(mode_str, sizeof(mode_str), "%d", json_object_get_int(mode_obj)); + fwx_uci_set_value(uci_ctx, buf, mode_str); + + if (user_mac_obj) { + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].user_mac"); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_mac_obj)); + } + + if (user_name_obj) { + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].user_name"); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_name_obj)); + } + + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].enabled"); + char enabled_str[16]; + int enabled = enabled_obj ? json_object_get_int(enabled_obj) : 1; + snprintf(enabled_str, sizeof(enabled_str), "%d", enabled); + fwx_uci_set_value(uci_ctx, buf, enabled_str); + + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].filter_quic"); + { + char filter_quic_str[16]; + int filter_quic = filter_quic_obj ? json_object_get_int(filter_quic_obj) : 0; + snprintf(filter_quic_str, sizeof(filter_quic_str), "%d", filter_quic ? 1 : 0); + fwx_uci_set_value(uci_ctx, buf, filter_quic_str); + } + + + int time_rules_len = json_object_array_length(time_rules_obj); + for (i = 0; i < time_rules_len; i++) { + struct json_object *time_rule_obj = json_object_array_get_idx(time_rules_obj, i); + struct json_object *weekdays_obj = json_object_object_get(time_rule_obj, "weekdays"); + struct json_object *start_time_obj = json_object_object_get(time_rule_obj, "start_time"); + struct json_object *end_time_obj = json_object_object_get(time_rule_obj, "end_time"); + + if (!weekdays_obj || !start_time_obj || !end_time_obj) { + continue; + } + + + char time_rule_str[256] = {0}; + int weekdays_len = json_object_array_length(weekdays_obj); + for (j = 0; j < weekdays_len; j++) { + struct json_object *weekday_obj = json_object_array_get_idx(weekdays_obj, j); + char weekday_str[16]; + snprintf(weekday_str, sizeof(weekday_str), "%d", json_object_get_int(weekday_obj)); + if (j > 0) strcat(time_rule_str, ","); + strcat(time_rule_str, weekday_str); + } + strcat(time_rule_str, ","); + strcat(time_rule_str, json_object_get_string(start_time_obj)); + strcat(time_rule_str, ","); + strcat(time_rule_str, json_object_get_string(end_time_obj)); + + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].time_rule"); + fwx_uci_add_list(uci_ctx, buf, time_rule_str); + } + + + int app_ids_len = json_object_array_length(app_ids_obj); + for (i = 0; i < app_ids_len; i++) { + struct json_object *app_id_obj = json_object_array_get_idx(app_ids_obj, i); + const char *raw = json_object_get_string(app_id_obj); + char app_id_str[64] = {0}; + if (normalize_app_id_token(raw, app_id_str, sizeof(app_id_str))) { + snprintf(buf, sizeof(buf), "appfilter.@rule[-1].app_id"); + fwx_uci_add_list(uci_ctx, buf, app_id_str); + } + } + + fwx_uci_commit(uci_ctx, "appfilter"); + uci_free_context(uci_ctx); + + + set_state_file(APPFILTER_RULES_STATE_FILE); + + LOG_DEBUG("Added filter rule: id=%d, name=%s\n", rule_id, json_object_get_string(name_obj)); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_update_filter_rule(struct json_object *req_obj) { + struct json_object *id_obj = json_object_object_get(req_obj, "id"); + int i, j; + if (!id_obj) { + LOG_ERROR("Missing id field\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int rule_id = json_object_get_int(id_obj); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int index = find_rule_index_by_id(uci_ctx, rule_id); + if (index < 0) { + LOG_ERROR("Rule not found: id=%d\n", rule_id); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char buf[256]; + + + struct json_object *name_obj = json_object_object_get(req_obj, "name"); + if (name_obj) { + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].name", index); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(name_obj)); + } + + struct json_object *mode_obj = json_object_object_get(req_obj, "mode"); + if (mode_obj) { + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].mode", index); + char mode_str[16]; + snprintf(mode_str, sizeof(mode_str), "%d", json_object_get_int(mode_obj)); + fwx_uci_set_value(uci_ctx, buf, mode_str); + } + + struct json_object *user_mac_obj = json_object_object_get(req_obj, "user_mac"); + if (user_mac_obj) { + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].user_mac", index); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_mac_obj)); + } + + struct json_object *user_name_obj = json_object_object_get(req_obj, "user_name"); + if (user_name_obj) { + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].user_name", index); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_name_obj)); + } + + struct json_object *enabled_obj = json_object_object_get(req_obj, "enabled"); + if (enabled_obj) { + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].enabled", index); + char enabled_str[16]; + snprintf(enabled_str, sizeof(enabled_str), "%d", json_object_get_int(enabled_obj)); + fwx_uci_set_value(uci_ctx, buf, enabled_str); + } + + struct json_object *filter_quic_obj = json_object_object_get(req_obj, "filter_quic"); + if (filter_quic_obj) { + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].filter_quic", index); + char filter_quic_str[16]; + snprintf(filter_quic_str, sizeof(filter_quic_str), "%d", json_object_get_int(filter_quic_obj) ? 1 : 0); + fwx_uci_set_value(uci_ctx, buf, filter_quic_str); + } + + + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].time_rule", index); + fwx_uci_delete(uci_ctx, buf); + + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].app_id", index); + fwx_uci_delete(uci_ctx, buf); + + + struct json_object *time_rules_obj = json_object_object_get(req_obj, "time_rules"); + if (time_rules_obj) { + int time_rules_len = json_object_array_length(time_rules_obj); + for (i = 0; i < time_rules_len; i++) { + struct json_object *time_rule_obj = json_object_array_get_idx(time_rules_obj, i); + struct json_object *weekdays_obj = json_object_object_get(time_rule_obj, "weekdays"); + struct json_object *start_time_obj = json_object_object_get(time_rule_obj, "start_time"); + struct json_object *end_time_obj = json_object_object_get(time_rule_obj, "end_time"); + + if (!weekdays_obj || !start_time_obj || !end_time_obj) { + continue; + } + + char time_rule_str[256] = {0}; + int weekdays_len = json_object_array_length(weekdays_obj); + for (j = 0; j < weekdays_len; j++) { + struct json_object *weekday_obj = json_object_array_get_idx(weekdays_obj, j); + char weekday_str[16]; + snprintf(weekday_str, sizeof(weekday_str), "%d", json_object_get_int(weekday_obj)); + if (j > 0) strcat(time_rule_str, ","); + strcat(time_rule_str, weekday_str); + } + strcat(time_rule_str, ","); + strcat(time_rule_str, json_object_get_string(start_time_obj)); + strcat(time_rule_str, ","); + strcat(time_rule_str, json_object_get_string(end_time_obj)); + + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].time_rule", index); + fwx_uci_add_list(uci_ctx, buf, time_rule_str); + } + } + + + struct json_object *app_ids_obj = json_object_object_get(req_obj, "app_ids"); + if (app_ids_obj) { + int app_ids_len = json_object_array_length(app_ids_obj); + for (i = 0; i < app_ids_len; i++) { + struct json_object *app_id_obj = json_object_array_get_idx(app_ids_obj, i); + const char *raw = json_object_get_string(app_id_obj); + char app_id_str[64] = {0}; + if (normalize_app_id_token(raw, app_id_str, sizeof(app_id_str))) { + snprintf(buf, sizeof(buf), "appfilter.@rule[%d].app_id", index); + fwx_uci_add_list(uci_ctx, buf, app_id_str); + } + } + } + + fwx_uci_commit(uci_ctx, "appfilter"); + uci_free_context(uci_ctx); + + + set_state_file(APPFILTER_RULES_STATE_FILE); + + LOG_DEBUG("Updated filter rule: id=%d\n", rule_id); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_delete_filter_rule(struct json_object *req_obj) { + struct json_object *id_obj = json_object_object_get(req_obj, "id"); + if (!id_obj) { + LOG_ERROR("Missing id field\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int rule_id = json_object_get_int(id_obj); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int index = find_rule_index_by_id(uci_ctx, rule_id); + if (index < 0) { + LOG_ERROR("Rule not found: id=%d\n", rule_id); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char buf[128]; + snprintf(buf, sizeof(buf), "appfilter.@rule[%d]", index); + fwx_uci_delete(uci_ctx, buf); + + fwx_uci_commit(uci_ctx, "appfilter"); + uci_free_context(uci_ctx); + + + set_state_file(APPFILTER_RULES_STATE_FILE); + + LOG_DEBUG("Deleted filter rule: id=%d\n", rule_id); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_get_appfilter_whitelist(struct json_object *req_obj){ + + int i; + struct json_object *data_obj = json_object_new_object(); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *mac_array = json_object_new_array(); + char mac_str[128] = {0}; + int num = fwx_uci_get_list_num(uci_ctx, "appfilter_whitelist", "whitelist_mac"); + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "appfilter_whitelist.@whitelist_mac[%d].mac", i, mac_str, sizeof(mac_str)); + + struct json_object *mac_obj = json_object_new_object(); + json_object_object_add(mac_obj, "mac", json_object_new_string(mac_str)); + client_node_t *dev = find_client_node(mac_str); + if (dev){ + json_object_object_add(mac_obj, "nickname", json_object_new_string(dev->nickname)); + json_object_object_add(mac_obj, "hostname", json_object_new_string(dev->hostname)); + }else{ + json_object_object_add(mac_obj, "nickname", json_object_new_string("")); + json_object_object_add(mac_obj, "hostname", json_object_new_string("")); + } + json_object_array_add(mac_array, mac_obj); + } + + json_object_object_add(data_obj, "list", mac_array); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_del_appfilter_whitelist(struct json_object *req_obj){ + int i; + LOG_DEBUG("fwx_api_del_appfilter_whitelist\n"); + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + LOG_ERROR("mac_obj is NULL\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + LOG_DEBUG("mac: %s\n", json_object_get_string(mac_obj)); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + char mac_str[128] = {0}; + int num = fwx_uci_get_list_num(uci_ctx, "appfilter_whitelist", "whitelist_mac"); + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "appfilter_whitelist.@whitelist_mac[%d].mac", i, mac_str, sizeof(mac_str)); + if (strcmp(mac_str, json_object_get_string(mac_obj)) == 0) { + LOG_DEBUG("delete appfilter_whitelist_mac[%d]\n", i); + char buf[128] = {0}; + sprintf(buf, "appfilter_whitelist.@whitelist_mac[%d]", i); + fwx_uci_delete(uci_ctx, buf); + break; + } + } + + fwx_uci_commit(uci_ctx, "appfilter_whitelist"); + uci_free_context(uci_ctx); + + + set_state_file(APPFILTER_WHITELIST_STATE_FILE); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_add_appfilter_whitelist(struct json_object *req_obj){ + int i; + LOG_DEBUG("fwx_api_add_appfilter_whitelist\n"); + struct json_object *mac_array = json_object_object_get(req_obj, "mac_list"); + if (!mac_array) { + LOG_ERROR("mac_list not found\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int len = json_object_array_length(mac_array); + for (i = 0; i < len; i++) { + struct json_object *mac_obj = json_object_array_get_idx(mac_array, i); + fwx_uci_add_section(uci_ctx, "appfilter_whitelist", "whitelist_mac"); + fwx_uci_set_value(uci_ctx, "appfilter_whitelist.@whitelist_mac[-1].mac", json_object_get_string(mac_obj)); + } + fwx_uci_commit(uci_ctx, "appfilter_whitelist"); + uci_free_context(uci_ctx); + + + set_state_file(APPFILTER_WHITELIST_STATE_FILE); + + LOG_DEBUG("Added %d MAC addresses to appfilter whitelist\n", len); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_get_app_filter_adv(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int enable = fwx_uci_get_int_value(uci_ctx, "fwx.appfilter.enable"); + json_object_object_add(data_obj, "enable", json_object_new_int(enable)); + uci_free_context(uci_ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_set_app_filter_adv(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *enable_obj = json_object_object_get(req_obj, "enable"); + if (!enable_obj) { + LOG_ERROR("Missing enable parameter\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int enable_value = json_object_get_int(enable_obj); + fwx_uci_set_int_value(uci_ctx, "fwx.appfilter.enable", enable_value); + fwx_uci_commit(uci_ctx, "fwx"); + + set_state_file(APPFILTER_RULES_STATE_FILE); + uci_free_context(uci_ctx); + LOG_DEBUG("Set appfilter advanced settings\n"); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} diff --git a/open-app-filter/src/fwx_app_filter.h b/open-app-filter/src/fwx_app_filter.h new file mode 100644 index 00000000..62c256c3 --- /dev/null +++ b/open-app-filter/src/fwx_app_filter.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_APP_FILTER_H__ +#define __FWX_APP_FILTER_H__ + +#include "fwx.h" + +int fwx_app_filter_init(void); +void update_oaf_status(void); + +struct json_object *fwx_api_get_filter_rules(struct json_object *req_obj); +struct json_object *fwx_api_add_filter_rule(struct json_object *req_obj); +struct json_object *fwx_api_update_filter_rule(struct json_object *req_obj); +struct json_object *fwx_api_delete_filter_rule(struct json_object *req_obj); + + +struct json_object *fwx_api_get_appfilter_whitelist(struct json_object *req_obj); +struct json_object *fwx_api_del_appfilter_whitelist(struct json_object *req_obj); +struct json_object *fwx_api_add_appfilter_whitelist(struct json_object *req_obj); + + +struct json_object *fwx_api_get_app_filter_base(struct json_object *req_obj); +struct json_object *fwx_api_set_app_filter_base(struct json_object *req_obj); + + +struct json_object *fwx_api_get_app_filter_adv(struct json_object *req_obj); +struct json_object *fwx_api_set_app_filter_adv(struct json_object *req_obj); + +#endif diff --git a/open-app-filter/src/fwx_common.c b/open-app-filter/src/fwx_common.c new file mode 100644 index 00000000..39673bec --- /dev/null +++ b/open-app-filter/src/fwx_common.c @@ -0,0 +1,133 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include "fwx_user.h" +#include "fwx_netlink.h" +#include "fwx_ubus.h" +#include "fwx_config.h" +#include +#include +#include +#include +#include "fwx.h" +#include +#include "fwx_utils.h" + + + +void fwx_init_time_status(fwx_run_time_status_t *status){ + status->filter = 0; + status->deny_time = 0; + status->allow_time = 0; + status->match_time = 0; +} + + +int fwx_check_time_manual(fwx_time_config_t *t_config, fwx_run_time_status_t *status) { + int i; + time_t now = time(NULL); + + struct tm *current_time = localtime(&now); + int current_minutes = current_time->tm_hour * 60 + current_time->tm_min; + int current_wday = current_time->tm_wday; // 0=Sunday, 1=Monday, ..., 6=Saturday + + LOG_DEBUG("current time: %02d:%02d, weekday: %d\n", current_time->tm_hour, current_time->tm_min, current_wday); + for (i = 0; i < t_config->time_num; i++) { + + if (t_config->time_list[i].weekday_map[current_wday] == 0) { + continue; // Skip if weekday is not enabled + } + + int start_minutes = t_config->time_list[i].start_time.hour * 60 + t_config->time_list[i].start_time.min; + int end_minutes = t_config->time_list[i].end_time.hour * 60 + t_config->time_list[i].end_time.min; + LOG_DEBUG("check time: %02d:%02d-%02d:%02d, weekday_map[%d]=%d\n", + t_config->time_list[i].start_time.hour, t_config->time_list[i].start_time.min, + t_config->time_list[i].end_time.hour, t_config->time_list[i].end_time.min, + current_wday, t_config->time_list[i].weekday_map[current_wday]); + + if (current_minutes >= start_minutes && current_minutes <= end_minutes) { + LOG_DEBUG("current time in time list\n"); + status->match_time = 1; + return 1; + } + } + status->match_time = 0; + return 0; +} + +int fwx_check_time_dynamic(fwx_time_config_t *t_config, fwx_run_time_status_t *status) { + time_t now = time(NULL); + struct tm *current_time = localtime(&now); + int current_minutes = current_time->tm_hour * 60 + current_time->tm_min; + + int start_minutes = t_config->seg_time.start_time.hour * 60 + t_config->seg_time.start_time.min; + int end_minutes = t_config->seg_time.end_time.hour * 60 + t_config->seg_time.end_time.min; + printf("check seg_time: %02d:%02d-%02d:%02d\n", + t_config->seg_time.start_time.hour, t_config->seg_time.start_time.min, + t_config->seg_time.end_time.hour, t_config->seg_time.end_time.min); + if (!(current_minutes >= start_minutes && current_minutes <= end_minutes)) { + printf("current time not in seg_time\n"); + fwx_init_time_status(status); + return 0; + } + + status->match_time = 1; + if (status->filter == 1) { + status->deny_time++; + if (status->deny_time >= t_config->deny_time) { + status->filter = 0; + status->deny_time = 0; + printf("deny time over, filter = 0"); + } + printf("deny_time: %d\n", status->deny_time); + } else { + status->allow_time++; + if (status->allow_time >= t_config->allow_time) { + status->filter = 1; + status->allow_time = 0; + printf("allow time over, filter = 1"); + } + printf("allow_time: %d\n", status->allow_time); + } + return status->filter; +} + +int fwx_check_time(fwx_time_config_t *t_config, fwx_run_time_status_t *status) { + time_t now = time(NULL); + struct tm *current_time = localtime(&now); + int current_wday = current_time->tm_wday; // 0=Sunday, 1=Monday, ..., 6=Saturday + LOG_DEBUG("current day: %d\n", current_wday); + + if (t_config->time_mode == 0) { + LOG_DEBUG("manual mode\n"); + return fwx_check_time_manual(t_config, status); + } else { + LOG_DEBUG("dynamic mode\n"); + + if (t_config->seg_time.weekday_map[current_wday] == 0) { + LOG_DEBUG("current day not in configured days\n"); + fwx_init_time_status(status); + return 0; + } + return fwx_check_time_dynamic(t_config, status); + } +} + + +struct json_object * fwx_gen_api_response_data(int code, struct json_object *data_obj){ + struct json_object *root_obj = json_object_new_object(); + if (!root_obj) + return NULL; + json_object_object_add(root_obj, "code", json_object_new_int(code)); + if (data_obj) + json_object_object_add(root_obj, "data", data_obj); + return root_obj; +} diff --git a/open-app-filter/src/fwx_config.c b/open-app-filter/src/fwx_config.c new file mode 100644 index 00000000..bd1de0e4 --- /dev/null +++ b/open-app-filter/src/fwx_config.c @@ -0,0 +1,232 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include "fwx_config.h" +#include "fwx.h" +#include "fwx_feature.h" +#include + +app_name_info_t app_name_table[MAX_SUPPORT_APP_NUM]; +int g_app_count = 0; +static int g_base_app_count = 0; +int g_cur_class_num = 0; +char CLASS_NAME_TABLE[MAX_APP_TYPE][MAX_CLASS_NAME_LEN]; + +static char *trim_space(char *text) +{ + char *end; + + if (!text) + return NULL; + while (*text && isspace((unsigned char)*text)) + text++; + end = text + strlen(text); + while (end > text && isspace((unsigned char)end[-1])) + *--end = '\0'; + return text; +} + +static int parse_feature_app_header(const char *line, int *app_id, char *app_name, size_t app_name_len) +{ + char header[128]; + char *slash; + char *id_text; + char *name_text; + char *endptr; + long id; + const char *colon; + size_t header_len; + + if (!line || !app_id || !app_name || app_name_len == 0) + return -1; + + colon = strchr(line, ':'); + if (!colon) + return -1; + header_len = (size_t)(colon - line); + if (header_len == 0 || header_len >= sizeof(header)) + return -1; + + memcpy(header, line, header_len); + header[header_len] = '\0'; + slash = strchr(header, '~'); + if (!slash) + return -1; + *slash = '\0'; + + id_text = trim_space(header); + name_text = trim_space(slash + 1); + if (!id_text || !id_text[0] || !name_text || !name_text[0]) + return -1; + + id = strtol(id_text, &endptr, 10); + endptr = trim_space(endptr); + if (id <= 0 || (endptr && endptr[0] != '\0')) + return -1; + + *app_id = (int)id; + strncpy(app_name, name_text, app_name_len - 1); + app_name[app_name_len - 1] = '\0'; + return 0; +} + +int app_icon_exists_by_id(int id) +{ + char icon_path[256]; + + if (id <= 0) + return 0; + + snprintf(icon_path, sizeof(icon_path), + "/www/luci-static/resources/oaf/app_icons/%d.png", id); + return access(icon_path, F_OK) == 0; +} + +char *get_app_name_by_id(int id) +{ + int i; + static char fallback_name[32]; + + for (i = 0; i < g_app_count; i++) + { + if (id == app_name_table[i].id) + return app_name_table[i].name; + } + if (id > 0) { + snprintf(fallback_name, sizeof(fallback_name), "App%d", id); + return fallback_name; + } + return ""; +} + +int add_app_name_to_table(int id, const char *name) +{ + if (!name || !name[0] || g_app_count >= MAX_SUPPORT_APP_NUM) + return -1; + app_name_table[g_app_count].id = id; + strncpy(app_name_table[g_app_count].name, name, + sizeof(app_name_table[g_app_count].name) - 1); + app_name_table[g_app_count].name[sizeof(app_name_table[g_app_count].name) - 1] = '\0'; + g_app_count++; + return 0; +} + +int get_base_app_count(void) +{ + return g_base_app_count; +} + +void init_app_name_table(void) +{ + char line_buf[2048] = {0}; + const char *feature_data; + size_t feature_len = 0; + size_t offset = 0; + + feature_data = fwx_feature_get_data(&feature_len); + if (!feature_data) + return; + memset(app_name_table, 0, sizeof(app_name_table)); + g_app_count = 0; + while (fwx_feature_next_line(feature_data, feature_len, &offset, + line_buf, sizeof(line_buf)) != 0) { + int app_id = 0; + char app_name[64] = {0}; + + if (strstr(line_buf, "#")) + continue; + if (strlen(line_buf) < 10) + continue; + if (parse_feature_app_header(line_buf, &app_id, app_name, sizeof(app_name)) < 0) + continue; + if (g_app_count >= MAX_SUPPORT_APP_NUM) + break; + app_name_table[g_app_count].id = app_id; + strncpy(app_name_table[g_app_count].name, app_name, + sizeof(app_name_table[g_app_count].name) - 1); + g_app_count++; + } + g_base_app_count = g_app_count; +} + +void init_app_class_name_table(void) +{ + char line_buf[2048] = {0}; + const char *feature_data; + size_t feature_len = 0; + size_t offset = 0; + + feature_data = fwx_feature_get_data(&feature_len); + if (!feature_data) + return; + memset(CLASS_NAME_TABLE, 0, sizeof(CLASS_NAME_TABLE)); + g_cur_class_num = 0; + while (fwx_feature_next_line(feature_data, feature_len, &offset, + line_buf, sizeof(line_buf)) != 0) { + int class_id = 0; + char class_name[MAX_CLASS_NAME_LEN] = {0}; + + if (strncmp(line_buf, "#class ", 7) != 0) + continue; + if (sscanf(line_buf, "#class %*s %d %31s", &class_id, class_name) != 2 || + class_id < 1 || class_id > MAX_APP_TYPE) + continue; + strncpy(CLASS_NAME_TABLE[class_id - 1], class_name, MAX_CLASS_NAME_LEN - 1); + if (class_id > g_cur_class_num) + g_cur_class_num = class_id; + } +} + +int check_time_valid(char *t) +{ + if (!t) + return 0; + if (strlen(t) < 3 || strlen(t) > 5 || (!strstr(t, ":"))) + return 0; + else + return 1; +} + + +int config_get_appfilter_enable(void) +{ + int enable = 0; + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) + return -1; + enable = fwx_uci_get_int_value(ctx, "appfilter.global.enable"); + if (enable < 0) + enable = 0; + + uci_free_context(ctx); + return enable; +} + +int config_get_lan_ip(char *lan_ip, int len) +{ + int ret = 0; + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) + return -1; + ret = fwx_uci_get_value(ctx, "network.lan.ipaddr", lan_ip, len); + uci_free_context(ctx); + return ret; +} + +int config_get_lan_mask(char *lan_mask, int len) +{ + int ret = 0; + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) + return -1; + ret = fwx_uci_get_value(ctx, "network.lan.netmask", lan_mask, len); + uci_free_context(ctx); + return ret; +} diff --git a/open-app-filter/src/fwx_config.h b/open-app-filter/src/fwx_config.h new file mode 100644 index 00000000..9ec926c5 --- /dev/null +++ b/open-app-filter/src/fwx_config.h @@ -0,0 +1,35 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_CONFIG_H__ +#define __FWX_CONFIG_H__ +#include + +#define MAX_SUPPORT_APP_NUM 1024 +#define MAX_CLASS_NAME_LEN 32 + +#include "fwx_user.h" +extern int g_cur_class_num; +extern int g_app_count; +extern char CLASS_NAME_TABLE[MAX_APP_TYPE][MAX_CLASS_NAME_LEN]; + +typedef struct app_name_info +{ + int id; + char name[64]; +} app_name_info_t; +void init_app_name_table(void); +void init_app_class_name_table(void); +char *get_app_name_by_id(int id); +int app_icon_exists_by_id(int id); + +int appfilter_config_alloc(void); + +int appfilter_config_free(void); +int config_get_appfilter_enable(void); +int config_get_lan_ip(char *lan_ip, int len); +int config_get_lan_mask(char *lan_mask, int len); +#endif + diff --git a/open-app-filter/src/fwx_custom_feature.c b/open-app-filter/src/fwx_custom_feature.c new file mode 100644 index 00000000..04043282 --- /dev/null +++ b/open-app-filter/src/fwx_custom_feature.c @@ -0,0 +1,1005 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "fwx_custom_feature.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fwx.h" +#include "fwx_config.h" + +#define CUSTOM_FALLBACK_CLASS_COUNT 5 +#define CUSTOM_APP_MIN_SUFFIX 900 +#define CUSTOM_APP_MAX_SUFFIX 999 +#define CUSTOM_MAX_FEATURES 16 +#define CUSTOM_MAX_PAYLOADS 15 +#define CUSTOM_MAX_FEATURE_TEXT 127 +#define CUSTOM_MAX_APP_LINE 799 + +typedef struct custom_class { + int id; + const char *name; +} custom_class_t; + +typedef struct custom_payload { + int position; + unsigned int value; +} custom_payload_t; + +typedef struct custom_feature { + char protocol[4]; + char dest_port[16]; + char domain[32]; + char uri[64]; + custom_payload_t payload[CUSTOM_MAX_PAYLOADS]; + int payload_count; +} custom_feature_t; + +typedef struct custom_app { + int appid; + int class_id; + char name[64]; + custom_feature_t features[CUSTOM_MAX_FEATURES]; + int feature_count; +} custom_app_t; + +typedef struct custom_config { + custom_app_t *apps; + size_t count; + size_t capacity; +} custom_config_t; + +static const custom_class_t fallback_classes[CUSTOM_FALLBACK_CLASS_COUNT] = { + {1, "Chat"}, + {2, "Game"}, + {3, "Video"}, + {4, "Shopping"}, + {5, "Music"} +}; + +static custom_config_t custom_config; +extern int reload_feature(void); +extern int add_app_name_to_table(int id, const char *name); +extern int get_base_app_count(void); + +static void set_error(char *error, size_t error_len, const char *message) +{ + if (error && error_len > 0) + snprintf(error, error_len, "%s", message); +} + +static void config_free(custom_config_t *config) +{ + if (!config) + return; + free(config->apps); + memset(config, 0, sizeof(*config)); +} + +static custom_app_t *config_add_app(custom_config_t *config) +{ + custom_app_t *apps; + size_t capacity; + + if (config->count >= MAX_SUPPORT_APP_NUM) + return NULL; + if (config->count == config->capacity) { + capacity = config->capacity ? config->capacity * 2 : 16; + if (capacity > MAX_SUPPORT_APP_NUM) + capacity = MAX_SUPPORT_APP_NUM; + apps = realloc(config->apps, capacity * sizeof(*apps)); + if (!apps) + return NULL; + config->apps = apps; + config->capacity = capacity; + } + memset(&config->apps[config->count], 0, sizeof(config->apps[config->count])); + return &config->apps[config->count++]; +} + +static char *trim_space(char *text) +{ + char *end; + + if (!text) + return NULL; + while (*text && isspace((unsigned char)*text)) + text++; + end = text + strlen(text); + while (end > text && isspace((unsigned char)end[-1])) + *--end = '\0'; + return text; +} + +static int has_builtin_classes(void) +{ + int i; + + for (i = 0; i < MAX_APP_TYPE; i++) { + if (CLASS_NAME_TABLE[i][0]) + return 1; + } + return 0; +} + +static int class_is_available(int class_id) +{ + int i; + + if (has_builtin_classes()) + return class_id >= 1 && class_id <= MAX_APP_TYPE && + CLASS_NAME_TABLE[class_id - 1][0]; + for (i = 0; i < CUSTOM_FALLBACK_CLASS_COUNT; i++) { + if (fallback_classes[i].id == class_id) + return 1; + } + return 0; +} + +static int has_forbidden_text_char(const char *text, int app_name) +{ + const unsigned char *p = (const unsigned char *)text; + + while (*p) { + if ((!app_name && isspace(*p)) || strchr(app_name ? ":~[];," : ";,[]", *p)) + return 1; + p++; + } + return 0; +} + +static int parse_integer(const char *text, int minimum, int maximum, int *value) +{ + char *end = NULL; + long number; + + if (!text || !text[0]) + return -1; + errno = 0; + number = strtol(text, &end, 10); + if (errno || !end || *end || number < minimum || number > maximum) + return -1; + *value = (int)number; + return 0; +} + +static int normalize_dest_port(const char *text, char *output, size_t output_len) +{ + char buffer[16]; + char *separator; + int start; + int end; + int written; + + if (!text || !output || output_len == 0) + return -1; + if (!text[0]) { + output[0] = '\0'; + return 0; + } + if (strlen(text) >= sizeof(buffer)) + return -1; + snprintf(buffer, sizeof(buffer), "%s", text); + separator = strchr(buffer, '-'); + if (!separator) { + if (parse_integer(buffer, 1, 65535, &start) < 0) + return -1; + written = snprintf(output, output_len, "%d", start); + return written >= 0 && written < (int)output_len ? 0 : -1; + } + if (strchr(separator + 1, '-')) + return -1; + *separator++ = '\0'; + if (parse_integer(buffer, 1, 65535, &start) < 0 || + parse_integer(separator, 1, 65535, &end) < 0 || start > end) + return -1; + written = snprintf(output, output_len, "%d-%d", start, end); + return written >= 0 && written < (int)output_len ? 0 : -1; +} + +static int split_exact(char *text, char delimiter, char **fields, int field_count) +{ + int i; + char *separator; + + fields[0] = text; + for (i = 1; i < field_count; i++) { + separator = strchr(fields[i - 1], delimiter); + if (!separator) + return -1; + *separator = '\0'; + fields[i] = separator + 1; + } + return strchr(fields[field_count - 1], delimiter) ? -1 : 0; +} + +static int parse_payload_text(char *text, custom_feature_t *feature) +{ + char *item = text; + size_t length = strlen(text); + + if (!text[0]) + return 0; + if (text[length - 1] == '|') + return -1; + while (item && item[0]) { + char *next = strchr(item, '|'); + char *colon; + char *end = NULL; + long position; + unsigned long value; + + if (feature->payload_count >= CUSTOM_MAX_PAYLOADS) + return -1; + if (next) + *next++ = '\0'; + colon = strchr(item, ':'); + if (!colon || strchr(colon + 1, ':')) + return -1; + *colon++ = '\0'; + errno = 0; + position = strtol(item, &end, 10); + if (errno || !end || *end || position < INT_MIN || position > INT_MAX) + return -1; + if (strlen(colon) != 2 || !isxdigit((unsigned char)colon[0]) || + !isxdigit((unsigned char)colon[1])) + return -1; + errno = 0; + value = strtoul(colon, &end, 16); + if (errno || !end || *end || value > 0xff) + return -1; + feature->payload[feature->payload_count].position = (int)position; + feature->payload[feature->payload_count].value = (unsigned int)value; + feature->payload_count++; + item = next; + } + return 0; +} + +static int validate_feature(custom_feature_t *feature, char *error, size_t error_len) +{ + char normalized_port[sizeof(feature->dest_port)]; + + if (strcmp(feature->protocol, "tcp") && strcmp(feature->protocol, "udp")) { + set_error(error, error_len, "protocol must be tcp or udp"); + return -1; + } + if (normalize_dest_port(feature->dest_port, normalized_port, + sizeof(normalized_port)) < 0) { + set_error(error, error_len, "dest_port is invalid"); + return -1; + } + snprintf(feature->dest_port, sizeof(feature->dest_port), "%s", normalized_port); + if (strlen(feature->domain) > 31 || has_forbidden_text_char(feature->domain, 0)) { + set_error(error, error_len, "domain is invalid"); + return -1; + } + if (strlen(feature->uri) > 63 || has_forbidden_text_char(feature->uri, 0)) { + set_error(error, error_len, "uri is invalid"); + return -1; + } + if (!feature->dest_port[0] && !feature->domain[0] && !feature->uri[0] && + !feature->payload_count) { + set_error(error, error_len, "feature match fields are empty"); + return -1; + } + return 0; +} + +static int parse_feature_text(const char *text, custom_feature_t *feature, + char *error, size_t error_len) +{ + char buffer[CUSTOM_MAX_FEATURE_TEXT + 1]; + char *fields[6]; + size_t i; + + if (!text || !text[0] || strlen(text) > CUSTOM_MAX_FEATURE_TEXT) { + set_error(error, error_len, "feature text is too long"); + return -1; + } + memset(feature, 0, sizeof(*feature)); + snprintf(buffer, sizeof(buffer), "%s", text); + if (split_exact(buffer, ';', fields, 6) < 0 || fields[1][0]) { + set_error(error, error_len, "feature field format is invalid"); + return -1; + } + if (strlen(fields[0]) != 3) { + set_error(error, error_len, "protocol is invalid"); + return -1; + } + for (i = 0; i < 3; i++) + feature->protocol[i] = (char)tolower((unsigned char)fields[0][i]); + if (normalize_dest_port(fields[2], feature->dest_port, + sizeof(feature->dest_port)) < 0) { + set_error(error, error_len, "dest_port is invalid"); + return -1; + } + if (strlen(fields[3]) >= sizeof(feature->domain) || + strlen(fields[4]) >= sizeof(feature->uri)) { + set_error(error, error_len, "domain or uri is too long"); + return -1; + } + snprintf(feature->domain, sizeof(feature->domain), "%s", fields[3]); + snprintf(feature->uri, sizeof(feature->uri), "%s", fields[4]); + if (parse_payload_text(fields[5], feature) < 0) { + set_error(error, error_len, "payload format is invalid"); + return -1; + } + return validate_feature(feature, error, error_len); +} + +static int feature_to_text(const custom_feature_t *feature, char *buffer, size_t length) +{ + int written; + int i; + size_t used; + + written = snprintf(buffer, length, "%s;;%s;%s;%s;", feature->protocol, + feature->dest_port, feature->domain, feature->uri); + if (written < 0 || (size_t)written >= length) + return -1; + used = (size_t)written; + for (i = 0; i < feature->payload_count; i++) { + written = snprintf(buffer + used, length - used, "%s%d:%02x", + i ? "|" : "", feature->payload[i].position, + feature->payload[i].value); + if (written < 0 || (size_t)written >= length - used) + return -1; + used += (size_t)written; + } + return used <= CUSTOM_MAX_FEATURE_TEXT ? 0 : -1; +} + +static int app_to_line(const custom_app_t *app, char *line, size_t length) +{ + char feature_text[CUSTOM_MAX_FEATURE_TEXT + 1]; + int written; + int i; + size_t used; + + written = snprintf(line, length, "%d~%s:[", app->appid, app->name); + if (written < 0 || (size_t)written >= length) + return -1; + used = (size_t)written; + for (i = 0; i < app->feature_count; i++) { + if (feature_to_text(&app->features[i], feature_text, sizeof(feature_text)) < 0) + return -1; + written = snprintf(line + used, length - used, "%s%s", i ? "," : "", + feature_text); + if (written < 0 || (size_t)written >= length - used) + return -1; + used += (size_t)written; + } + written = snprintf(line + used, length - used, "]"); + if (written != 1 || used + 1 > CUSTOM_MAX_APP_LINE) + return -1; + return 0; +} + +static int validate_app_identity(custom_config_t *config, custom_app_t *app, + int current_index, char *error, size_t error_len) +{ + size_t i; + int suffix; + + if (!class_is_available(app->class_id)) { + set_error(error, error_len, "class_id is invalid"); + return -1; + } + if (!app->name[0] || strlen(app->name) > 63 || has_forbidden_text_char(app->name, 1)) { + set_error(error, error_len, "application name is invalid"); + return -1; + } + if (app->appid) { + suffix = app->appid - app->class_id * 1000; + if (suffix < CUSTOM_APP_MIN_SUFFIX || suffix > CUSTOM_APP_MAX_SUFFIX) { + set_error(error, error_len, "appid is outside the custom range"); + return -1; + } + } + for (i = 0; i < (size_t)current_index; i++) { + if (!strcmp(config->apps[i].name, app->name) || + (app->appid && config->apps[i].appid == app->appid)) { + set_error(error, error_len, "appid or application name is duplicated"); + return -1; + } + } + return 0; +} + +static int app_compare(const void *left, const void *right) +{ + const custom_app_t *a = left; + const custom_app_t *b = right; + + if (a->class_id != b->class_id) + return a->class_id - b->class_id; + return a->appid - b->appid; +} + +static int allocate_appids(custom_config_t *config, char *error, size_t error_len) +{ + unsigned char used[MAX_APP_TYPE + 1][100] = {{0}}; + size_t i; + int suffix; + + for (i = 0; i < config->count; i++) { + if (!config->apps[i].appid) + continue; + suffix = config->apps[i].appid % 1000; + if (used[config->apps[i].class_id][suffix - CUSTOM_APP_MIN_SUFFIX]) { + set_error(error, error_len, "appid is duplicated"); + return -1; + } + used[config->apps[i].class_id][suffix - CUSTOM_APP_MIN_SUFFIX] = 1; + } + for (i = 0; i < config->count; i++) { + if (config->apps[i].appid) + continue; + for (suffix = CUSTOM_APP_MIN_SUFFIX; suffix <= CUSTOM_APP_MAX_SUFFIX; suffix++) { + if (!used[config->apps[i].class_id][suffix - CUSTOM_APP_MIN_SUFFIX]) + break; + } + if (suffix > CUSTOM_APP_MAX_SUFFIX) { + set_error(error, error_len, "custom appid range is full"); + return -1; + } + used[config->apps[i].class_id][suffix - CUSTOM_APP_MIN_SUFFIX] = 1; + config->apps[i].appid = config->apps[i].class_id * 1000 + suffix; + } + qsort(config->apps, config->count, sizeof(*config->apps), app_compare); + return 0; +} + +static int parse_payload_json(struct json_object *array, custom_feature_t *feature, + char *error, size_t error_len) +{ + size_t i; + + if (!array) + return 0; + if (!json_object_is_type(array, json_type_array) || + json_object_array_length(array) > CUSTOM_MAX_PAYLOADS) { + set_error(error, error_len, "payload is invalid"); + return -1; + } + for (i = 0; i < json_object_array_length(array); i++) { + struct json_object *item = json_object_array_get_idx(array, i); + struct json_object *position_obj; + struct json_object *value_obj; + const char *value_text; + int64_t position; + char *end = NULL; + unsigned long value; + + if (!item || !json_object_is_type(item, json_type_object) || + !json_object_object_get_ex(item, "position", &position_obj) || + !json_object_is_type(position_obj, json_type_int) || + !json_object_object_get_ex(item, "value", &value_obj) || + !json_object_is_type(value_obj, json_type_string)) { + set_error(error, error_len, "payload item is invalid"); + return -1; + } + position = json_object_get_int64(position_obj); + value_text = json_object_get_string(value_obj); + if (position < INT_MIN || position > INT_MAX || strlen(value_text) != 2 || + !isxdigit((unsigned char)value_text[0]) || !isxdigit((unsigned char)value_text[1])) { + set_error(error, error_len, "payload position or value is invalid"); + return -1; + } + value = strtoul(value_text, &end, 16); + if (!end || *end || value > 0xff) + return -1; + feature->payload[i].position = (int)position; + feature->payload[i].value = (unsigned int)value; + feature->payload_count++; + } + return 0; +} + +static int parse_feature_json(struct json_object *object, custom_feature_t *feature, + char *error, size_t error_len) +{ + struct json_object *value; + const char *text; + size_t i; + + memset(feature, 0, sizeof(*feature)); + if (!object || !json_object_is_type(object, json_type_object) || + !json_object_object_get_ex(object, "protocol", &value) || + !json_object_is_type(value, json_type_string)) { + set_error(error, error_len, "protocol is required"); + return -1; + } + text = json_object_get_string(value); + if (strlen(text) != 3) { + set_error(error, error_len, "protocol is invalid"); + return -1; + } + for (i = 0; i < 3; i++) + feature->protocol[i] = (char)tolower((unsigned char)text[i]); + if (json_object_object_get_ex(object, "dest_port", &value)) { + if (json_object_is_type(value, json_type_int)) { + int64_t port = json_object_get_int64(value); + if (port < 0 || port > 65535 || + (port > 0 && snprintf(feature->dest_port, + sizeof(feature->dest_port), "%lld", + (long long)port) >= (int)sizeof(feature->dest_port))) { + set_error(error, error_len, "dest_port is invalid"); + return -1; + } + } else if (json_object_is_type(value, json_type_string)) { + if (normalize_dest_port(json_object_get_string(value), feature->dest_port, + sizeof(feature->dest_port)) < 0) { + set_error(error, error_len, "dest_port is invalid"); + return -1; + } + } else { + set_error(error, error_len, "dest_port is invalid"); + return -1; + } + } + if (json_object_object_get_ex(object, "domain", &value)) { + if (!json_object_is_type(value, json_type_string) || + json_object_get_string_len(value) > 31) { + set_error(error, error_len, "domain is invalid"); + return -1; + } + snprintf(feature->domain, sizeof(feature->domain), "%s", json_object_get_string(value)); + } + if (json_object_object_get_ex(object, "uri", &value)) { + if (!json_object_is_type(value, json_type_string) || + json_object_get_string_len(value) > 63) { + set_error(error, error_len, "uri is invalid"); + return -1; + } + snprintf(feature->uri, sizeof(feature->uri), "%s", json_object_get_string(value)); + } + if (json_object_object_get_ex(object, "payload", &value) && + parse_payload_json(value, feature, error, error_len) < 0) + return -1; + return validate_feature(feature, error, error_len); +} + +static int config_from_json(struct json_object *request, custom_config_t *config, + char *error, size_t error_len) +{ + struct json_object *app_list; + size_t i; + + if (!request || !json_object_is_type(request, json_type_object) || + !json_object_object_get_ex(request, "app_list", &app_list) || + !json_object_is_type(app_list, json_type_array)) { + set_error(error, error_len, "app_list is required"); + return -1; + } + for (i = 0; i < json_object_array_length(app_list); i++) { + struct json_object *item = json_object_array_get_idx(app_list, i); + struct json_object *value; + struct json_object *features; + custom_app_t *app = config_add_app(config); + size_t feature_index; + int64_t number; + + if (!app || !item || !json_object_is_type(item, json_type_object)) + goto invalid_app; + if (json_object_object_get_ex(item, "appid", &value)) { + if (!json_object_is_type(value, json_type_int)) + goto invalid_app; + number = json_object_get_int64(value); + if (number < 0 || number > INT_MAX) + goto invalid_app; + app->appid = (int)number; + } + if (!json_object_object_get_ex(item, "class_id", &value) || + !json_object_is_type(value, json_type_int)) + goto invalid_app; + app->class_id = json_object_get_int(value); + if (!json_object_object_get_ex(item, "name", &value) || + !json_object_is_type(value, json_type_string) || json_object_get_string_len(value) > 63) + goto invalid_app; + snprintf(app->name, sizeof(app->name), "%s", json_object_get_string(value)); + { + char *trimmed_name = trim_space(app->name); + if (trimmed_name && trimmed_name != app->name) + memmove(app->name, trimmed_name, strlen(trimmed_name) + 1); + } + if (validate_app_identity(config, app, (int)i, error, error_len) < 0) + return -1; + if (!json_object_object_get_ex(item, "features", &features) || + !json_object_is_type(features, json_type_array) || + json_object_array_length(features) < 1 || + json_object_array_length(features) > CUSTOM_MAX_FEATURES) + goto invalid_features; + app->feature_count = (int)json_object_array_length(features); + for (feature_index = 0; feature_index < (size_t)app->feature_count; feature_index++) { + if (parse_feature_json(json_object_array_get_idx(features, feature_index), + &app->features[feature_index], error, error_len) < 0) + return -1; + } + } + if (get_base_app_count() + (int)config->count > MAX_SUPPORT_APP_NUM) { + set_error(error, error_len, "application table capacity is exceeded"); + return -1; + } + if (allocate_appids(config, error, error_len) < 0) + return -1; + for (i = 0; i < config->count; i++) { + char line[CUSTOM_MAX_APP_LINE + 2]; + if (app_to_line(&config->apps[i], line, sizeof(line)) < 0) { + set_error(error, error_len, "serialized application line is too long"); + return -1; + } + } + return 0; + +invalid_features: + set_error(error, error_len, "features must contain 1 to 16 items"); + return -1; +invalid_app: + set_error(error, error_len, "application item is invalid"); + return -1; +} + +static int parse_app_line(char *line, custom_config_t *config, + char *error, size_t error_len) +{ + custom_app_t *app; + char *open = strstr(line, ":["); + char *close; + char *feature_text; + char *next; + char *slash; + char *id_text; + char *name_text; + char *endptr; + long appid; + + if (!open || strlen(line) > CUSTOM_MAX_APP_LINE) + goto invalid; + close = strrchr(open + 2, ']'); + if (!close || close[1]) + goto invalid; + *open = '\0'; + app = config_add_app(config); + if (!app) + goto invalid; + slash = strchr(line, '~'); + if (!slash) + goto invalid; + *slash = '\0'; + id_text = trim_space(line); + name_text = trim_space(slash + 1); + if (!id_text || !id_text[0] || !name_text || !name_text[0]) + goto invalid; + appid = strtol(id_text, &endptr, 10); + endptr = trim_space(endptr); + if (appid <= 0 || appid > INT_MAX || (endptr && endptr[0] != '\0') || + strlen(name_text) >= sizeof(app->name)) + goto invalid; + app->appid = (int)appid; + snprintf(app->name, sizeof(app->name), "%s", name_text); + app->class_id = app->appid / 1000; + if (validate_app_identity(config, app, (int)config->count - 1, + error, error_len) < 0) + return -1; + *close = '\0'; + feature_text = open + 2; + if (!feature_text[0] || feature_text[strlen(feature_text) - 1] == ',') + goto invalid_features; + while (feature_text && feature_text[0]) { + if (app->feature_count >= CUSTOM_MAX_FEATURES) + goto invalid_features; + next = strchr(feature_text, ','); + if (next) + *next++ = '\0'; + if (parse_feature_text(feature_text, &app->features[app->feature_count], + error, error_len) < 0) + return -1; + app->feature_count++; + feature_text = next; + } + if (!app->feature_count) + goto invalid_features; + return 0; + +invalid_features: + set_error(error, error_len, "features must contain 1 to 16 items"); + return -1; +invalid: + set_error(error, error_len, "custom feature line is invalid"); + return -1; +} + +static int config_read_file(const char *path, custom_config_t *config, + char *error, size_t error_len) +{ + FILE *file = fopen(path, "r"); + char line[1024]; + int line_number = 0; + + if (!file) { + if (errno == ENOENT) + return 0; + set_error(error, error_len, "failed to open custom feature file"); + return -1; + } + while (fgets(line, sizeof(line), file)) { + size_t length; + + line_number++; + length = strlen(line); + if (length && line[length - 1] != '\n' && !feof(file)) + goto invalid_line; + while (length && (line[length - 1] == '\n' || line[length - 1] == '\r')) + line[--length] = '\0'; + if (!line[0]) + continue; + if (!strncmp(line, "#class ", 7)) + continue; + if (parse_app_line(line, config, error, error_len) < 0) + goto failed; + } + if (ferror(file)) { + set_error(error, error_len, "failed to read custom feature file"); + goto failed; + } + fclose(file); + if (get_base_app_count() + (int)config->count > MAX_SUPPORT_APP_NUM) { + set_error(error, error_len, "application table capacity is exceeded"); + return -1; + } + qsort(config->apps, config->count, sizeof(*config->apps), app_compare); + return 0; + +invalid_line: + snprintf(error, error_len, "invalid custom feature file at line %d", line_number); +failed: + fclose(file); + return -1; +} + +static int config_write_file(const custom_config_t *config, char *error, size_t error_len) +{ + char temporary[256]; + char line[CUSTOM_MAX_APP_LINE + 2]; + FILE *file; + size_t app_index; + int failed = 0; + + snprintf(temporary, sizeof(temporary), "%s.tmp", FWX_CUSTOM_FEATURE_PATH); + file = fopen(temporary, "w"); + if (!file) { + set_error(error, error_len, "failed to create temporary custom feature file"); + return -1; + } + for (app_index = 0; app_index < config->count && !failed; app_index++) { + if (app_to_line(&config->apps[app_index], line, sizeof(line)) < 0 || + fprintf(file, "%s\n", line) < 0) + failed = 1; + } + if (!failed && fflush(file) < 0) + failed = 1; + if (!failed && fsync(fileno(file)) < 0) + failed = 1; + if (fclose(file) < 0) + failed = 1; + if (!failed && chmod(temporary, 0644) < 0) + failed = 1; + if (!failed && rename(temporary, FWX_CUSTOM_FEATURE_PATH) < 0) + failed = 1; + if (failed) { + unlink(temporary); + set_error(error, error_len, "failed to save custom feature file"); + return -1; + } + return 0; +} + +static struct json_object *feature_to_json(const custom_feature_t *feature) +{ + struct json_object *object = json_object_new_object(); + struct json_object *payload = json_object_new_array(); + int i; + char value[3]; + + json_object_object_add(object, "protocol", json_object_new_string(feature->protocol)); + json_object_object_add(object, "dest_port", json_object_new_string(feature->dest_port)); + json_object_object_add(object, "domain", json_object_new_string(feature->domain)); + json_object_object_add(object, "uri", json_object_new_string(feature->uri)); + for (i = 0; i < feature->payload_count; i++) { + struct json_object *item = json_object_new_object(); + snprintf(value, sizeof(value), "%02x", feature->payload[i].value); + json_object_object_add(item, "position", + json_object_new_int(feature->payload[i].position)); + json_object_object_add(item, "value", json_object_new_string(value)); + json_object_array_add(payload, item); + } + json_object_object_add(object, "payload", payload); + return object; +} + +static struct json_object *config_to_data(const custom_config_t *config) +{ + struct json_object *data = json_object_new_object(); + struct json_object *app_list = json_object_new_array(); + size_t i; + + for (i = 0; i < config->count; i++) { + struct json_object *item = json_object_new_object(); + struct json_object *features = json_object_new_array(); + int feature_index; + + json_object_object_add(item, "appid", json_object_new_int(config->apps[i].appid)); + if (!app_icon_exists_by_id(config->apps[i].appid)) + json_object_object_add(item, "icon", json_object_new_int(0)); + json_object_object_add(item, "class_id", json_object_new_int(config->apps[i].class_id)); + json_object_object_add(item, "name", json_object_new_string(config->apps[i].name)); + for (feature_index = 0; feature_index < config->apps[i].feature_count; feature_index++) + json_object_array_add(features, feature_to_json(&config->apps[i].features[feature_index])); + json_object_object_add(item, "features", features); + json_object_array_add(app_list, item); + } + json_object_object_add(data, "app_list", app_list); + return data; +} + +static void add_class_json(struct json_object *class_list, int class_id, + const char *class_name) +{ + struct json_object *item = json_object_new_object(); + + json_object_object_add(item, "class_id", json_object_new_int(class_id)); + json_object_object_add(item, "class_name", json_object_new_string(class_name)); + json_object_array_add(class_list, item); +} + +static struct json_object *class_list_to_data(void) +{ + struct json_object *data = json_object_new_object(); + struct json_object *class_list = json_object_new_array(); + int i; + + if (has_builtin_classes()) { + for (i = 0; i < MAX_APP_TYPE; i++) { + if (CLASS_NAME_TABLE[i][0]) + add_class_json(class_list, i + 1, CLASS_NAME_TABLE[i]); + } + } else { + for (i = 0; i < CUSTOM_FALLBACK_CLASS_COUNT; i++) + add_class_json(class_list, fallback_classes[i].id, fallback_classes[i].name); + } + json_object_object_add(data, "class_list", class_list); + return data; +} + +static struct json_object *error_response(const char *error, int saved) +{ + struct json_object *data = json_object_new_object(); + + if (error && error[0]) + json_object_object_add(data, "error", json_object_new_string(error)); + if (saved) { + json_object_object_add(data, "saved", json_object_new_int(1)); + json_object_object_add(data, "reloaded", json_object_new_int(0)); + } + return fwx_gen_api_response_data(API_CODE_ERROR, data); +} + +int fwx_custom_feature_reload(void) +{ + custom_config_t loaded = {0}; + char error[128] = {0}; + + if (config_read_file(FWX_CUSTOM_FEATURE_PATH, &loaded, error, sizeof(error)) < 0) { + LOG_ERROR("Failed to load custom features: %s\n", error); + config_free(&loaded); + return -1; + } + config_free(&custom_config); + custom_config = loaded; + return 0; +} + +int fwx_custom_feature_send_to_kernel(int (*send_feature)(char *)) +{ + char line[CUSTOM_MAX_APP_LINE + 2]; + size_t i; + + if (!send_feature) + return -1; + for (i = 0; i < custom_config.count; i++) { + if (app_to_line(&custom_config.apps[i], line, sizeof(line)) < 0 || + send_feature(line) < 0) + return -1; + } + return (int)custom_config.count; +} + +int fwx_custom_feature_add_app_names(void) +{ + size_t i; + + if (get_base_app_count() + (int)custom_config.count > MAX_SUPPORT_APP_NUM) + return -1; + for (i = 0; i < custom_config.count; i++) { + if (add_app_name_to_table(custom_config.apps[i].appid, + custom_config.apps[i].name) < 0) + return -1; + } + return 0; +} + +void fwx_custom_feature_append_class_apps(struct json_object **class_map, + size_t class_map_len) +{ + size_t i; + + for (i = 0; i < custom_config.count; i++) { + struct json_object *app_list; + char combined[256]; + int class_id = custom_config.apps[i].class_id; + + if (class_id < 0 || (size_t)class_id >= class_map_len || !class_map[class_id] || + !json_object_object_get_ex(class_map[class_id], "app_list", &app_list)) + continue; + if (app_icon_exists_by_id(custom_config.apps[i].appid)) + snprintf(combined, sizeof(combined), "%d,%s", custom_config.apps[i].appid, + custom_config.apps[i].name); + else + snprintf(combined, sizeof(combined), "%d,%s,0", custom_config.apps[i].appid, + custom_config.apps[i].name); + json_object_array_add(app_list, json_object_new_string(combined)); + } +} + +struct json_object *fwx_api_get_custom_feature(struct json_object *req_obj) +{ + custom_config_t loaded = {0}; + char error[128] = {0}; + struct json_object *response; + + (void)req_obj; + if (config_read_file(FWX_CUSTOM_FEATURE_PATH, &loaded, error, sizeof(error)) < 0) { + config_free(&loaded); + return error_response(error, 0); + } + response = fwx_gen_api_response_data(API_CODE_SUCCESS, config_to_data(&loaded)); + config_free(&loaded); + return response; +} + +struct json_object *fwx_api_get_custom_feature_class_list(struct json_object *req_obj) +{ + (void)req_obj; + return fwx_gen_api_response_data(API_CODE_SUCCESS, class_list_to_data()); +} + +struct json_object *fwx_api_set_custom_feature(struct json_object *req_obj) +{ + custom_config_t submitted = {0}; + char error[128] = {0}; + struct json_object *response; + + if (config_from_json(req_obj, &submitted, error, sizeof(error)) < 0) { + config_free(&submitted); + return error_response(error, 0); + } + if (config_write_file(&submitted, error, sizeof(error)) < 0) { + config_free(&submitted); + return error_response(error, 0); + } + if (reload_feature() < 0) { + config_free(&submitted); + return error_response("custom feature file was saved but reload failed", 1); + } + response = fwx_gen_api_response_data(API_CODE_SUCCESS, config_to_data(&submitted)); + config_free(&submitted); + return response; +} diff --git a/open-app-filter/src/fwx_custom_feature.h b/open-app-filter/src/fwx_custom_feature.h new file mode 100644 index 00000000..1422c565 --- /dev/null +++ b/open-app-filter/src/fwx_custom_feature.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __FWX_CUSTOM_FEATURE_H__ +#define __FWX_CUSTOM_FEATURE_H__ + +#include +#include + +#define FWX_CUSTOM_FEATURE_PATH "/etc/fwxd/custom_feature.cfg" + +int fwx_custom_feature_reload(void); +int fwx_custom_feature_send_to_kernel(int (*send_feature)(char *)); +int fwx_custom_feature_add_app_names(void); +void fwx_custom_feature_append_class_apps(struct json_object **class_map, + size_t class_map_len); +struct json_object *fwx_api_get_custom_feature(struct json_object *req_obj); +struct json_object *fwx_api_get_custom_feature_class_list(struct json_object *req_obj); +struct json_object *fwx_api_set_custom_feature(struct json_object *req_obj); + +#endif diff --git a/open-app-filter/src/fwx_feature.c b/open-app-filter/src/fwx_feature.c new file mode 100644 index 00000000..ce5b0df8 --- /dev/null +++ b/open-app-filter/src/fwx_feature.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include "fwx_feature.h" + +#define FEATURE_HEADER_SIZE 24U +#define FEATURE_FORMAT_VERSION 1U +#define FEATURE_ALGORITHM_XTEA_CTR 1U + +static const unsigned char feature_magic[4] = {'F', 'W', 'X', 'B'}; +static const uint32_t feature_key[4] = { + 0x8f4c29a1U, 0x73b6d502U, 0xc14e87f3U, 0x2ad95b60U +}; +static char *g_feature_data; +static size_t g_feature_data_len; + +static void clear_memory(void *data, size_t len) +{ + volatile unsigned char *p = data; + + while (p && len-- > 0) + *p++ = 0; +} + +static uint16_t get_le16(const unsigned char *p) +{ + return (uint16_t)p[0] | ((uint16_t)p[1] << 8); +} + +static uint32_t get_le32(const unsigned char *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static uint64_t get_le64(const unsigned char *p) +{ + return (uint64_t)get_le32(p) | ((uint64_t)get_le32(p + 4) << 32); +} + +static void put_le16(unsigned char *p, uint16_t value) +{ + p[0] = (unsigned char)value; + p[1] = (unsigned char)(value >> 8); +} + +static void put_le32(unsigned char *p, uint32_t value) +{ + p[0] = (unsigned char)value; + p[1] = (unsigned char)(value >> 8); + p[2] = (unsigned char)(value >> 16); + p[3] = (unsigned char)(value >> 24); +} + +static void put_le64(unsigned char *p, uint64_t value) +{ + put_le32(p, (uint32_t)value); + put_le32(p + 4, (uint32_t)(value >> 32)); +} + +static uint32_t feature_crc32(const unsigned char *data, size_t len) +{ + uint32_t crc = 0xffffffffU; + size_t i; + int bit; + + for (i = 0; i < len; i++) { + crc ^= data[i]; + for (bit = 0; bit < 8; bit++) + crc = (crc >> 1) ^ (0xedb88320U & (0U - (crc & 1U))); + } + return ~crc; +} + +static void xtea_encrypt_block(uint32_t block[2]) +{ + uint32_t v0 = block[0]; + uint32_t v1 = block[1]; + uint32_t sum = 0; + const uint32_t delta = 0x9e3779b9U; + int round; + + for (round = 0; round < 32; round++) { + v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ + (sum + feature_key[sum & 3U]); + sum += delta; + v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ + (sum + feature_key[(sum >> 11) & 3U]); + } + block[0] = v0; + block[1] = v1; +} + +static void xtea_ctr_crypt(unsigned char *data, size_t len, uint64_t nonce) +{ + size_t offset = 0; + uint64_t counter = nonce; + + while (offset < len) { + unsigned char stream[8]; + uint32_t block[2]; + size_t i; + + block[0] = (uint32_t)counter; + block[1] = (uint32_t)(counter >> 32); + xtea_encrypt_block(block); + put_le32(stream, block[0]); + put_le32(stream + 4, block[1]); + for (i = 0; i < sizeof(stream) && offset < len; i++, offset++) + data[offset] ^= stream[i]; + counter++; + } +} + +static int read_file(const char *path, size_t max_len, unsigned char **data, size_t *len) +{ + FILE *fp; + long file_len; + unsigned char *buf; + + if (!path || !data || !len) + return -1; + fp = fopen(path, "rb"); + if (!fp) + return -1; + if (fseek(fp, 0, SEEK_END) != 0 || (file_len = ftell(fp)) <= 0 || + (size_t)file_len > max_len || fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + return -1; + } + buf = malloc((size_t)file_len); + if (!buf) { + fclose(fp); + return -1; + } + if (fread(buf, 1, (size_t)file_len, fp) != (size_t)file_len) { + free(buf); + fclose(fp); + return -1; + } + fclose(fp); + *data = buf; + *len = (size_t)file_len; + return 0; +} + +static int read_nonce(uint64_t *nonce) +{ + FILE *fp; + unsigned char buf[8]; + + fp = fopen("/dev/urandom", "rb"); + if (!fp) + return -1; + if (fread(buf, 1, sizeof(buf), fp) != sizeof(buf)) { + fclose(fp); + return -1; + } + fclose(fp); + *nonce = get_le64(buf); + return 0; +} + + +int fwx_feature_decrypt_file(const char *path, char **data, size_t *data_len) +{ + unsigned char *file_data = NULL; + char *plain = NULL; + size_t file_len = 0; + uint32_t plain_len = 0; + uint32_t expected_crc; + uint64_t nonce; + int ret = -1; + + if (!data || !data_len) + return -1; + *data = NULL; + *data_len = 0; + if (read_file(path, FEATURE_HEADER_SIZE + FWX_FEATURE_MAX_SIZE, &file_data, &file_len) != 0) + return -1; + if (file_len < FEATURE_HEADER_SIZE || memcmp(file_data, feature_magic, sizeof(feature_magic)) != 0 || + file_data[4] != FEATURE_FORMAT_VERSION || file_data[5] != FEATURE_ALGORITHM_XTEA_CTR || + get_le16(file_data + 6) != FEATURE_HEADER_SIZE) + goto out; + plain_len = get_le32(file_data + 8); + expected_crc = get_le32(file_data + 12); + nonce = get_le64(file_data + 16); + if (plain_len == 0 || plain_len > FWX_FEATURE_MAX_SIZE || + file_len != FEATURE_HEADER_SIZE + (size_t)plain_len) + goto out; + plain = malloc((size_t)plain_len + 1U); + if (!plain) + goto out; + memcpy(plain, file_data + FEATURE_HEADER_SIZE, plain_len); + xtea_ctr_crypt((unsigned char *)plain, plain_len, nonce); + plain[plain_len] = '\0'; + if (feature_crc32((const unsigned char *)plain, plain_len) != expected_crc) + goto out; + *data = plain; + *data_len = plain_len; + plain = NULL; + ret = 0; +out: + clear_memory(plain, plain_len); + free(plain); + free(file_data); + return ret; +} + +static int copy_file_atomic(const char *source, const char *target, + const char *temporary) +{ + unsigned char *data = NULL; + size_t data_len = 0; + FILE *fp = NULL; + int ret = -1; + + if (read_file(source, + FEATURE_HEADER_SIZE + FWX_FEATURE_MAX_SIZE, + &data, &data_len) != 0) + return -1; + fp = fopen(temporary, "wb"); + if (!fp || fwrite(data, 1, data_len, fp) != data_len || + fflush(fp) != 0 || fsync(fileno(fp)) != 0) + goto out; + if (fclose(fp) != 0) { + fp = NULL; + goto out; + } + fp = NULL; + if (chmod(temporary, 0644) != 0 || + rename(temporary, target) != 0) + goto out; + ret = 0; +out: + if (fp) + fclose(fp); + if (ret != 0) + unlink(temporary); + free(data); + return ret; +} + +int fwx_feature_validate_candidate(char *version, size_t version_len, + char *format, size_t format_len) +{ + char *feature_data = NULL; + size_t feature_len = 0; + size_t offset = 0; + char line[1024]; + char parsed_version[64] = {0}; + char parsed_format[32] = {0}; + int line_ret; + int ret = -1; + + if (!version || version_len == 0 || !format || format_len == 0) + return -1; + version[0] = '\0'; + format[0] = '\0'; + if (fwx_feature_decrypt_file(FWX_FEATURE_CANDIDATE_PATH, + &feature_data, &feature_len) < 0) + return FWX_FEATURE_VALIDATE_DECRYPT_FAILED; + while ((line_ret = fwx_feature_next_line(feature_data, feature_len, + &offset, line, sizeof(line))) != 0) { + if (line_ret < 0) + continue; + if (!strncmp(line, "#version ", 9)) + sscanf(line, "#version %63s", parsed_version); + else if (!strncmp(line, "#format ", 8)) + sscanf(line, "#format %31s", parsed_format); + if (parsed_version[0] && parsed_format[0]) + break; + } + snprintf(version, version_len, "%s", parsed_version); + snprintf(format, format_len, "%s", parsed_format); + if (parsed_version[0] && !strcmp(parsed_format, FWX_FEATURE_DATA_FORMAT)) + ret = 0; + else + ret = FWX_FEATURE_VALIDATE_INFO_INVALID; + clear_memory(feature_data, feature_len); + free(feature_data); + return ret; +} + +int fwx_feature_apply_candidate(void) +{ + const char *backup_temporary = FWX_FEATURE_BACKUP_PATH ".tmp"; + const char *target_temporary = FWX_FEATURE_BIN_PATH ".tmp"; + + if (access(FWX_FEATURE_BIN_PATH, F_OK) == 0 && + copy_file_atomic(FWX_FEATURE_BIN_PATH, FWX_FEATURE_BACKUP_PATH, + backup_temporary) < 0) + return FWX_FEATURE_APPLY_BACKUP_FAILED; + if (copy_file_atomic(FWX_FEATURE_CANDIDATE_PATH, FWX_FEATURE_BIN_PATH, + target_temporary) < 0) + return FWX_FEATURE_APPLY_TARGET_FAILED; + unlink(FWX_FEATURE_CANDIDATE_PATH); + return 0; +} + +int fwx_feature_process_candidate(char *version, size_t version_len, + char *format, size_t format_len, + int *status_code, + void (*stage_callback)(const char *stage)) +{ + int ret; + + if (status_code) + *status_code = 400; + ret = fwx_feature_validate_candidate(version, version_len, format, format_len); + if (ret < 0) { + if (status_code) + *status_code = 401; + unlink(FWX_FEATURE_CANDIDATE_PATH); + return ret; + } + if (stage_callback) + stage_callback("applying"); + ret = fwx_feature_apply_candidate(); + if (ret < 0) { + unlink(FWX_FEATURE_CANDIDATE_PATH); + return ret; + } + if (status_code) + *status_code = 200; + return 0; +} + +int fwx_feature_restore_backup(void) +{ + return copy_file_atomic(FWX_FEATURE_BACKUP_PATH, FWX_FEATURE_BIN_PATH, + FWX_FEATURE_BIN_PATH ".restore"); +} + + +void fwx_feature_replace_data(char *data, size_t data_len) +{ + clear_memory(g_feature_data, g_feature_data_len); + free(g_feature_data); + g_feature_data = data; + g_feature_data_len = data_len; +} + +const char *fwx_feature_get_data(size_t *data_len) +{ + if (data_len) + *data_len = g_feature_data_len; + return g_feature_data; +} + +int fwx_feature_next_line(const char *data, size_t data_len, size_t *offset, + char *line, size_t line_size) +{ + size_t start; + size_t end; + size_t line_len; + + if (!data || !offset || !line || line_size == 0 || *offset >= data_len) + return 0; + start = *offset; + end = start; + while (end < data_len && data[end] != '\n') + end++; + *offset = end < data_len ? end + 1U : end; + line_len = end - start; + if (line_len > 0 && data[start + line_len - 1U] == '\r') + line_len--; + if (line_len >= line_size) { + line[0] = '\0'; + return -1; + } + memcpy(line, data + start, line_len); + line[line_len] = '\0'; + return 1; +} diff --git a/open-app-filter/src/fwx_feature.h b/open-app-filter/src/fwx_feature.h new file mode 100644 index 00000000..d8a8d136 --- /dev/null +++ b/open-app-filter/src/fwx_feature.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __FWX_FEATURE_H__ +#define __FWX_FEATURE_H__ + +#include + +#define FWX_FEATURE_BIN_PATH "/etc/fwxd/feature.bin" +#define FWX_FEATURE_BACKUP_PATH "/etc/fwxd/feature.bin.bak" +#define FWX_FEATURE_LIST_CACHE_PATH "/etc/fwxd/feature_list.json" +#define FWX_FEATURE_CANDIDATE_PATH "/tmp/feature.bin" +#define FWX_FEATURE_INFO_PATH "/tmp/feature_info.json" +#define FWX_FEATURE_UPGRADE_STATUS_PATH "/tmp/feature_upgrade.status" +#define FWX_FEATURE_DATA_FORMAT "v4.0" +#define FWX_FEATURE_MAX_SIZE (20U * 1024U * 1024U) + +#define FWX_FEATURE_VALIDATE_DECRYPT_FAILED -1 +#define FWX_FEATURE_VALIDATE_INFO_INVALID -2 +#define FWX_FEATURE_APPLY_BACKUP_FAILED -1 +#define FWX_FEATURE_APPLY_TARGET_FAILED -2 + +int fwx_feature_decrypt_file(const char *path, char **data, size_t *data_len); +int fwx_feature_validate_candidate(char *version, size_t version_len, + char *format, size_t format_len); +int fwx_feature_apply_candidate(void); +int fwx_feature_restore_backup(void); +int fwx_feature_process_candidate(char *version, size_t version_len, + char *format, size_t format_len, + int *status_code, + void (*stage_callback)(const char *stage)); +int test_encrypt_feature_file(void); + +void fwx_feature_replace_data(char *data, size_t data_len); +const char *fwx_feature_get_data(size_t *data_len); +int fwx_feature_next_line(const char *data, size_t data_len, size_t *offset, + char *line, size_t line_size); + +#endif diff --git a/open-app-filter/src/fwx_feature_online.c b/open-app-filter/src/fwx_feature_online.c new file mode 100644 index 00000000..3953e7f3 --- /dev/null +++ b/open-app-filter/src/fwx_feature_online.c @@ -0,0 +1,1637 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fwx.h" +#include "fwx_feature.h" +#include "fwx_feature_online.h" +#include "fwx_utils.h" + +#define ONLINE_BASE_URL "https://api.openappfilter.com" +#define ONLINE_VERSION "v4.0" +#define ONLINE_STATUS_PATH "/tmp/feature_online_upgrade.status" +#define ONLINE_WORK_DIR "/tmp/feature_online" +#define ONLINE_ARCHIVE_PATH ONLINE_WORK_DIR "/feature_package.bin" +#define ONLINE_EXTRACT_DIR ONLINE_WORK_DIR "/extract" +#define ONLINE_FEATURE_TEMP ONLINE_WORK_DIR "/feature.bin" +#define ONLINE_ICON_DIR ONLINE_EXTRACT_DIR "/app_icons" +#define ONLINE_ICON_TARGET "/www/luci-static/resources/oaf/app_icons" +#define ONLINE_FAILED_ARCHIVE_PATH "/tmp/feature_online_failed.bin" +#define ONLINE_COMMAND_STATUS "/tmp/feature_online_command.status" +#define ONLINE_RESPONSE_MAX (256U * 1024U) +#define ONLINE_ARCHIVE_MAX FWX_FEATURE_MAX_SIZE +#define ONLINE_FILE_MAX 128 +#define ONLINE_UPDATE_TIMEOUT 180 +#define ONLINE_DOWNLOAD_TIMEOUT 300 +#define ONLINE_TOKEN_MAX 256 +#define ONLINE_HOSTNAME_MAX 64 +#define ONLINE_DEVICE_LANG_MAX 64 + +typedef struct { + char state[16]; + char stage[24]; + int status_code; + char message[160]; + char id[65]; + int icons_skipped; + curl_off_t download_total; + curl_off_t download_now; + time_t started_at; + time_t updated_at; +} online_status_t; + +typedef struct { + char id[65]; + char token[ONLINE_TOKEN_MAX + 1]; + char device_id[33]; + char model[129]; + char lang[3]; + char expected_md5[33]; +} online_worker_t; + +typedef struct { + char *data; + size_t size; + size_t limit; +} memory_buffer_t; + +typedef struct { + FILE *fp; + size_t size; + size_t limit; + int too_large; +} file_buffer_t; + +typedef struct { + time_t last_update; + curl_off_t last_now; +} progress_ctx_t; + +static pthread_mutex_t online_mutex = PTHREAD_MUTEX_INITIALIZER; +static online_status_t online_status = {.state = "idle", .stage = "idle"}; +static int online_pipe[2] = {-1, -1}; +static int online_ready; +static int online_worker_running; +static struct uloop_fd online_uloop_fd; + +extern int g_feature_update; + +static void release_upgrade_lock(void); + +static void free_worker(online_worker_t *worker) +{ + if (!worker) + return; + memset(worker, 0, sizeof(*worker)); + free(worker); +} + +static int token_valid(const char *token) +{ + size_t i; + size_t len = token ? strlen(token) : 0; + + if (!token || len > ONLINE_TOKEN_MAX) + return 0; + for (i = 0; i < len; i++) { + if (iscntrl((unsigned char)token[i])) + return 0; + } + return 1; +} + +static int id_valid(const char *id) +{ + size_t i; + size_t len = id ? strlen(id) : 0; + + if (len == 0 || len > 64) + return 0; + for (i = 0; i < len; i++) { + if (!isalnum((unsigned char)id[i]) && id[i] != '-' && id[i] != '_') + return 0; + } + return 1; +} + +static int lang_valid(const char *lang) +{ + return lang && (!strcmp(lang, "cn") || !strcmp(lang, "en")); +} + +static int md5_text_valid(const char *md5) +{ + size_t i; + + if (!md5 || strlen(md5) != 32) + return 0; + for (i = 0; i < 32; i++) { + if (!isxdigit((unsigned char)md5[i])) + return 0; + } + return 1; +} + +static void copy_md5_lower(char out[33], const char *md5) +{ + size_t i; + + for (i = 0; i < 32; i++) + out[i] = (char)tolower((unsigned char)md5[i]); + out[32] = '\0'; +} + +static int file_md5_hex(const char *path, char out[33]) +{ + struct stat st; + FILE *fp; + unsigned char *data; + unsigned char digest[16]; + size_t read_len; + int i; + + if (!path || !out || stat(path, &st) != 0 || st.st_size <= 0 || + st.st_size > (off_t)ONLINE_ARCHIVE_MAX) + return -1; + data = malloc((size_t)st.st_size); + if (!data) + return -1; + fp = fopen(path, "rb"); + if (!fp) { + free(data); + return -1; + } + read_len = fread(data, 1, (size_t)st.st_size, fp); + fclose(fp); + if (read_len != (size_t)st.st_size) { + free(data); + return -1; + } + fwx_md5(data, read_len, digest); + free(data); + for (i = 0; i < 16; i++) + snprintf(out + i * 2, 3, "%02x", digest[i]); + out[32] = '\0'; + return 0; +} + +static void trim_text(char *text) +{ + size_t len; + + if (!text) + return; + len = strlen(text); + while (len > 0 && isspace((unsigned char)text[len - 1])) + text[--len] = '\0'; + while (*text && isspace((unsigned char)*text)) + memmove(text, text + 1, strlen(text)); +} + +static int read_first_line(const char *path, char *out, size_t out_len) +{ + FILE *fp; + + if (!path || !out || out_len < 2) + return -1; + fp = fopen(path, "r"); + if (!fp) + return -1; + if (!fgets(out, (int)out_len, fp)) { + fclose(fp); + return -1; + } + fclose(fp); + trim_text(out); + return out[0] ? 0 : -1; +} + +static int query_text_valid(const char *text, size_t max_len) +{ + size_t i, len; + + if (!text) + return 0; + len = strlen(text); + if (len > max_len) + return 0; + for (i = 0; i < len; i++) { + if (iscntrl((unsigned char)text[i])) + return 0; + } + return 1; +} + +static void sanitize_query_text(char *text) +{ + size_t i; + + if (!text) + return; + trim_text(text); + for (i = 0; text[i]; i++) { + if (isspace((unsigned char)text[i])) + text[i] = '-'; + else if (iscntrl((unsigned char)text[i])) + text[i] = '_'; + } +} + +static int get_system_hostname(char *hostname, size_t hostname_len) +{ + if (!hostname || hostname_len < 2) + return -1; + hostname[0] = '\0'; + if (read_first_line("/proc/sys/kernel/hostname", hostname, hostname_len) < 0) + return -1; + sanitize_query_text(hostname); + return hostname[0] ? 0 : -1; +} +static int get_saved_token(char *token, size_t token_len) +{ + struct uci_context *ctx; + int ret; + + if (!token || token_len == 0) + return -1; + token[0] = '\0'; + ctx = uci_alloc_context(); + if (!ctx) + return -1; + ret = fwx_uci_get_value(ctx, "fwx.global.feature_token", token, (int)token_len); + uci_free_context(ctx); + return ret; +} + +static int get_device_id(char *device_id, size_t device_id_len) +{ + const char *ifnames[] = {"br-lan", "eth0", "eth1"}; + char path[96]; + char mac[32] = {0}; + char normalized[13] = {0}; + unsigned char digest[16]; + size_t i; + size_t used = 0; + + if (!device_id || device_id_len < 33) + return -1; + for (i = 0; i < sizeof(ifnames) / sizeof(ifnames[0]); i++) { + snprintf(path, sizeof(path), "/sys/class/net/%s/address", ifnames[i]); + if (read_first_line(path, mac, sizeof(mac)) == 0) + break; + } + if (!mac[0]) + return -1; + for (i = 0; mac[i]; i++) { + if (isxdigit((unsigned char)mac[i])) { + if (used >= sizeof(normalized) - 1) + return -1; + normalized[used++] = (char)tolower((unsigned char)mac[i]); + } + } + if (used != 12) + return -1; + fwx_md5((const unsigned char *)normalized, used, digest); + for (i = 0; i < 16; i++) + snprintf(device_id + i * 2, device_id_len - i * 2, "%02x", digest[i]); + device_id[32] = '\0'; + return 0; +} + +static int get_device_model(char *model, size_t model_len) +{ + struct json_object *board; + struct json_object *model_obj; + struct json_object *name_obj; + size_t i; + + if (!model || model_len < 2) + return -1; + model[0] = '\0'; + board = json_object_from_file("/etc/board.json"); + if (board && json_object_object_get_ex(board, "model", &model_obj) && + json_object_object_get_ex(model_obj, "name", &name_obj) && + json_object_is_type(name_obj, json_type_string)) + snprintf(model, model_len, "%s", json_object_get_string(name_obj)); + if (board) + json_object_put(board); + if (!model[0] && read_first_line("/proc/device-tree/model", model, model_len) < 0) + read_first_line("/tmp/sysinfo/board_name", model, model_len); + trim_text(model); + if (!model[0]) + return -1; + for (i = 0; model[i]; i++) { + if (isspace((unsigned char)model[i])) + model[i] = '-'; + else if (iscntrl((unsigned char)model[i])) + model[i] = '_'; + } + return 0; +} + +static int write_status_file_locked(void) +{ + const char *temporary = ONLINE_STATUS_PATH ".tmp"; + const char *text; + struct json_object *obj; + FILE *fp; + int ret = -1; + + obj = json_object_new_object(); + if (!obj) + return -1; + json_object_object_add(obj, "state", json_object_new_string(online_status.state)); + json_object_object_add(obj, "stage", json_object_new_string(online_status.stage)); + json_object_object_add(obj, "status_code", json_object_new_int(online_status.status_code)); + json_object_object_add(obj, "message", json_object_new_string(online_status.message)); + json_object_object_add(obj, "id", json_object_new_string(online_status.id)); + json_object_object_add(obj, "icons_skipped", json_object_new_int(online_status.icons_skipped)); + json_object_object_add(obj, "download_total", json_object_new_int64((int64_t)online_status.download_total)); + json_object_object_add(obj, "download_now", json_object_new_int64((int64_t)online_status.download_now)); + json_object_object_add(obj, "started_at", json_object_new_int64((int64_t)online_status.started_at)); + json_object_object_add(obj, "updated_at", json_object_new_int64((int64_t)online_status.updated_at)); + text = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN); + fp = fopen(temporary, "w"); + if (!fp || fwrite(text, 1, strlen(text), fp) != strlen(text) || + fflush(fp) != 0 || fsync(fileno(fp)) != 0) + goto out; + if (fclose(fp) != 0) { + fp = NULL; + goto out; + } + fp = NULL; + if (chmod(temporary, 0644) == 0 && rename(temporary, ONLINE_STATUS_PATH) == 0) + ret = 0; +out: + if (fp) + fclose(fp); + if (ret != 0) + unlink(temporary); + json_object_put(obj); + return ret; +} + +static void set_status(const char *state, const char *stage, int code, + const char *message, int icons_skipped) +{ + time_t now = time(NULL); + + pthread_mutex_lock(&online_mutex); + if (state) { + if (strcmp(state, "running") == 0 && + (strcmp(online_status.state, "running") != 0 || online_status.started_at == 0)) + online_status.started_at = now; + else if (strcmp(state, "running") != 0) + online_status.started_at = 0; + snprintf(online_status.state, sizeof(online_status.state), "%s", state); + } + if (stage) + snprintf(online_status.stage, sizeof(online_status.stage), "%s", stage); + online_status.status_code = code; + if (message) + snprintf(online_status.message, sizeof(online_status.message), "%s", message); + online_status.icons_skipped = icons_skipped; + if (stage && strcmp(stage, "downloading") == 0) { + online_status.download_total = 0; + online_status.download_now = 0; + } else if (state && strcmp(state, "running") != 0) { + online_status.download_total = 0; + online_status.download_now = 0; + } + online_status.updated_at = now; + write_status_file_locked(); + pthread_mutex_unlock(&online_mutex); + LOG_INFO("feature online status: state=%s stage=%s code=%d message=%s icons_skipped=%d", + state ? state : "-", stage ? stage : "-", code, + message ? message : "", icons_skipped); +} + +static struct json_object *status_data(void) +{ + struct json_object *data = json_object_new_object(); + time_t now = time(NULL); + int elapsed = 0; + int timed_out = 0; + + pthread_mutex_lock(&online_mutex); + if (strcmp(online_status.state, "running") == 0 && online_status.started_at > 0) { + elapsed = (int)(now - online_status.started_at); + if (elapsed > ONLINE_UPDATE_TIMEOUT) { + snprintf(online_status.state, sizeof(online_status.state), "%s", "failed"); + snprintf(online_status.stage, sizeof(online_status.stage), "%s", "idle"); + online_status.status_code = 408; + snprintf(online_status.message, sizeof(online_status.message), "%s", + "feature update timeout"); + online_status.icons_skipped = 0; + online_status.download_total = 0; + online_status.download_now = 0; + online_status.started_at = 0; + online_status.updated_at = now; + online_worker_running = 0; + write_status_file_locked(); + timed_out = 1; + } + } + json_object_object_add(data, "state", json_object_new_string(online_status.state)); + json_object_object_add(data, "stage", json_object_new_string(online_status.stage)); + json_object_object_add(data, "status_code", json_object_new_int(online_status.status_code)); + json_object_object_add(data, "message", json_object_new_string(online_status.message)); + json_object_object_add(data, "id", json_object_new_string(online_status.id)); + json_object_object_add(data, "icons_skipped", json_object_new_int(online_status.icons_skipped)); + json_object_object_add(data, "download_total", json_object_new_int64((int64_t)online_status.download_total)); + json_object_object_add(data, "download_now", json_object_new_int64((int64_t)online_status.download_now)); + json_object_object_add(data, "elapsed", json_object_new_int(elapsed)); + pthread_mutex_unlock(&online_mutex); + if (timed_out) { + LOG_WARN("feature online update timeout, elapsed=%d", elapsed); + release_upgrade_lock(); + } + return data; +} + +static struct json_object *error_response(int status_code, const char *message) +{ + struct json_object *data = json_object_new_object(); + + json_object_object_add(data, "status_code", json_object_new_int(status_code)); + json_object_object_add(data, "message", json_object_new_string(message ? message : "request failed")); + return fwx_gen_api_response_data(API_CODE_ERROR, data); +} + +static size_t memory_write(void *contents, size_t size, size_t count, void *userp) +{ + size_t bytes = size * count; + memory_buffer_t *buffer = userp; + char *next; + + if (!buffer || bytes > buffer->limit || buffer->size > buffer->limit - bytes) + return 0; + next = realloc(buffer->data, buffer->size + bytes + 1); + if (!next) + return 0; + buffer->data = next; + memcpy(buffer->data + buffer->size, contents, bytes); + buffer->size += bytes; + buffer->data[buffer->size] = '\0'; + return bytes; +} + +static int download_progress_cb(void *clientp, curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow) +{ + progress_ctx_t *ctx = clientp; + time_t now = time(NULL); + + (void)ultotal; + (void)ulnow; + if (!ctx) + return 0; + if (now == ctx->last_update && dlnow != dltotal) + return 0; + if (dlnow == ctx->last_now && dlnow != dltotal) + return 0; + ctx->last_update = now; + ctx->last_now = dlnow; + pthread_mutex_lock(&online_mutex); + if (strcmp(online_status.state, "running") == 0 && + strcmp(online_status.stage, "downloading") == 0) { + online_status.download_total = dltotal > 0 ? dltotal : 0; + online_status.download_now = dlnow > 0 ? dlnow : 0; + online_status.updated_at = now; + write_status_file_locked(); + } + pthread_mutex_unlock(&online_mutex); + LOG_INFO("feature online download progress: now=%lld total=%lld", + (long long)dlnow, (long long)dltotal); + return 0; +} + +static size_t file_write(void *contents, size_t size, size_t count, void *userp) +{ + size_t bytes = size * count; + file_buffer_t *buffer = userp; + + if (!buffer || !buffer->fp || bytes > buffer->limit || buffer->size > buffer->limit - bytes) { + if (buffer) + buffer->too_large = 1; + return 0; + } + if (fwrite(contents, 1, bytes, buffer->fp) != bytes) + return 0; + buffer->size += bytes; + return bytes; +} + +static void set_curl_options(CURL *curl, const char *url) +{ + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "fwxd-feature-update/1.0"); +} + +static int build_url(CURL *curl, const char *path, const char *token, + const char *device_id, const char *model, const char *lang, + const char *hostname, const char *device_lang, const char *id, + char *url, size_t url_len) +{ + char *token_escaped = NULL; + char *device_escaped = NULL; + char *model_escaped = NULL; + char *version_escaped = NULL; + char *lang_escaped = NULL; + char *hostname_escaped = NULL; + char *device_lang_escaped = NULL; + char *id_escaped = NULL; + int ret = -1; + + token_escaped = curl_easy_escape(curl, token, 0); + device_escaped = curl_easy_escape(curl, device_id, 0); + model_escaped = curl_easy_escape(curl, model, 0); + version_escaped = curl_easy_escape(curl, ONLINE_VERSION, 0); + if (lang) + lang_escaped = curl_easy_escape(curl, lang, 0); + if (hostname) + hostname_escaped = curl_easy_escape(curl, hostname, 0); + if (device_lang) + device_lang_escaped = curl_easy_escape(curl, device_lang, 0); + if (id) + id_escaped = curl_easy_escape(curl, id, 0); + if (!token_escaped || !device_escaped || !model_escaped || !version_escaped || + (lang && !lang_escaped) || (hostname && !hostname_escaped) || + (device_lang && !device_lang_escaped) || (id && !id_escaped)) + goto out; + if (snprintf(url, url_len, + "%s%s?token=%s&device_id=%s&model=%s&version=%s%s%s%s%s%s%s%s%s", + ONLINE_BASE_URL, path, token_escaped, device_escaped, model_escaped, + version_escaped, lang ? "&lang=" : "", lang ? lang_escaped : "", + hostname ? "&hostname=" : "", hostname ? hostname_escaped : "", + device_lang ? "&device_lang=" : "", + device_lang ? device_lang_escaped : "", id ? "&id=" : "", + id ? id_escaped : "") >= (int)url_len) + goto out; + ret = 0; +out: + curl_free(token_escaped); + curl_free(device_escaped); + curl_free(model_escaped); + curl_free(version_escaped); + curl_free(lang_escaped); + curl_free(hostname_escaped); + curl_free(device_lang_escaped); + curl_free(id_escaped); + return ret; +} + +static int parse_remote_json_error(const char *body, int *code, + char *message, size_t message_len) +{ + struct json_object *root; + struct json_object *code_obj; + struct json_object *msg_obj; + int remote_code; + + if (!body || !body[0]) + return -1; + root = json_tokener_parse(body); + if (!root) + return -1; + if (!json_object_is_type(root, json_type_object) || + !json_object_object_get_ex(root, "code", &code_obj)) { + json_object_put(root); + return -1; + } + remote_code = json_object_get_int(code_obj); + if (remote_code == 20000) { + json_object_put(root); + return -1; + } + *code = remote_code; + snprintf(message, message_len, "subscription request failed"); + if (json_object_object_get_ex(root, "msg", &msg_obj) && + json_object_is_type(msg_obj, json_type_string)) + snprintf(message, message_len, "%s", json_object_get_string(msg_obj)); + json_object_put(root); + return 0; +} + +static void parse_remote_error(const char *body, long http_code, int *code, + char *message, size_t message_len) +{ + *code = http_code >= 400 && http_code <= 599 ? (int)http_code : 400; + snprintf(message, message_len, "subscription request failed"); + parse_remote_json_error(body, code, message, message_len); +} + +static int get_request_credentials(char *token, size_t token_len, + char *device_id, size_t device_len, + char *model, size_t model_len) +{ + if (get_saved_token(token, token_len) < 0) + token[0] = '\0'; + if (!token_valid(token)) + return -1; + if (get_device_id(device_id, device_len) < 0) + return -2; + if (get_device_model(model, model_len) < 0) + return -3; + return 0; +} + +static int acquire_upgrade_lock(void) +{ + struct stat st; + time_t now = time(NULL); + + if (mkdir(FWX_FEATURE_UPGRADE_LOCK_PATH, 0700) == 0) + return 0; + if (errno != EEXIST || stat(FWX_FEATURE_UPGRADE_LOCK_PATH, &st) != 0 || + now - st.st_mtime < 600) + return -1; + rmdir(FWX_FEATURE_UPGRADE_LOCK_PATH); + return mkdir(FWX_FEATURE_UPGRADE_LOCK_PATH, 0700) == 0 ? 0 : -1; +} + +static int run_shell_command(const char *command, int timeout_seconds) +{ + const char *temporary = ONLINE_COMMAND_STATUS ".tmp"; + char wrapper[1024]; + char status[32] = {0}; + pid_t pid; + int retry; + + if (!command || snprintf(wrapper, sizeof(wrapper), + "%s; result=$?; printf '%%d' \"$result\" >%s; mv %s %s", + command, temporary, temporary, ONLINE_COMMAND_STATUS) >= (int)sizeof(wrapper)) + return -1; + unlink(temporary); + unlink(ONLINE_COMMAND_STATUS); + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) { + setpgid(0, 0); + execl("/bin/sh", "sh", "-c", wrapper, (char *)NULL); + _exit(127); + } + setpgid(pid, pid); + for (retry = 0; retry < timeout_seconds * 10; retry++) { + if (read_first_line(ONLINE_COMMAND_STATUS, status, sizeof(status)) == 0) { + unlink(ONLINE_COMMAND_STATUS); + return atoi(status); + } + usleep(100000); + } + kill(-pid, SIGKILL); + unlink(temporary); + unlink(ONLINE_COMMAND_STATUS); + return -1; +} + +static void cleanup_work_files(void) +{ + run_shell_command("rm -rf " ONLINE_WORK_DIR, 10); +} + +static void release_upgrade_lock(void) +{ + cleanup_work_files(); + rmdir(FWX_FEATURE_UPGRADE_LOCK_PATH); +} + +static int icon_entry_valid(const char *entry) +{ + const char *name; + size_t i; + size_t len; + + if (!strcmp(entry, "app_icons") || !strcmp(entry, "app_icons/")) + return 1; + if (strncmp(entry, "app_icons/", 10) != 0) + return 0; + name = entry + 10; + len = strlen(name); + if (len < 5 || len > 128 || strcmp(name + len - 4, ".png") != 0) + return 0; + for (i = 0; i < len; i++) { + if (!isalnum((unsigned char)name[i]) && name[i] != '.' && + name[i] != '_' && name[i] != '-') + return 0; + } + return 1; +} + +static int archive_entry_path_safe(const char *entry) +{ + const char *p; + const char *part; + size_t part_len; + + if (!entry || !entry[0] || entry[0] == '/' || strchr(entry, '\\')) + return 0; + p = entry; + while (*p) { + while (*p == '/') + p++; + part = p; + while (*p && *p != '/') + p++; + part_len = (size_t)(p - part); + if (part_len == 2 && part[0] == '.' && part[1] == '.') + return 0; + } + return 1; +} + +static void normalize_archive_entry(char *entry) +{ + while (entry && !strncmp(entry, "./", 2)) + memmove(entry, entry + 2, strlen(entry + 2) + 1); +} + +static int feature_entry_valid(const char *entry) +{ + return entry && !strcmp(entry, "feature.bin"); +} + +static int validate_archive(int *icon_count) +{ + char line[512]; + FILE *pipe; + + *icon_count = 0; + pipe = popen("tar -ztf " ONLINE_ARCHIVE_PATH " 2>/dev/null", "r"); + if (!pipe) { + LOG_ERROR("feature online archive list command failed"); + return -1; + } + while (fgets(line, sizeof(line), pipe)) { + trim_text(line); + if (!archive_entry_path_safe(line)) { + LOG_WARN("feature online archive unsafe entry: %s", line); + pclose(pipe); + return -1; + } + normalize_archive_entry(line); + if (!line[0] || !strcmp(line, ".")) + continue; + if (feature_entry_valid(line)) { + LOG_INFO("feature online archive feature entry: %s", line); + } else if (icon_entry_valid(line)) { + if (strcmp(line, "app_icons") != 0 && + strcmp(line, "app_icons/") != 0 && + ++(*icon_count) > 4096) { + LOG_WARN("feature online archive has too many icons"); + pclose(pipe); + return -1; + } + } else { + LOG_WARN("feature online archive invalid entry: %s", line); + pclose(pipe); + return -1; + } + } + pclose(pipe); + LOG_INFO("feature online archive validated, icons=%d", *icon_count); + return 0; +} + +static int validate_icon_tree(const char *path, int depth) +{ + struct stat st; + struct dirent *entry; + DIR *dir; + char child[512]; + int ret = 0; + + if (depth > 2 || lstat(path, &st) != 0 || !S_ISDIR(st.st_mode)) + return -1; + dir = opendir(path); + if (!dir) + return -1; + while ((entry = readdir(dir)) != NULL) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + if (snprintf(child, sizeof(child), "%s/%s", path, entry->d_name) >= (int)sizeof(child) || + lstat(child, &st) != 0) { + ret = -1; + break; + } + if (S_ISDIR(st.st_mode)) { + if (validate_icon_tree(child, depth + 1) < 0) { + ret = -1; + break; + } + } else if (!S_ISREG(st.st_mode) || st.st_size > 2U * 1024U * 1024U) { + ret = -1; + break; + } + } + closedir(dir); + return ret; +} + +static unsigned long long directory_size(const char *path); + +static int prepare_archive_candidate(int icon_count) +{ + struct stat st; + const char *feature_source = ONLINE_EXTRACT_DIR "/feature.bin"; + int ret; + + ret = run_shell_command("rm -rf " ONLINE_EXTRACT_DIR, 10); + if (ret != 0) { + LOG_ERROR("feature online cleanup extract dir failed, ret=%d", ret); + return -1; + } + if (mkdir(ONLINE_EXTRACT_DIR, 0700) != 0) { + LOG_ERROR("feature online create extract dir failed, errno=%d", errno); + return -1; + } + ret = run_shell_command("tar -zxf " ONLINE_ARCHIVE_PATH + " -C " ONLINE_EXTRACT_DIR " >/dev/null 2>&1", 60); + if (ret != 0) { + LOG_WARN("feature online extract tar.gz failed, ret=%d", ret); + return -1; + } + if (lstat(feature_source, &st) != 0) { + LOG_WARN("feature online extracted feature file missing: %s errno=%d", + feature_source, errno); + return -1; + } + if (!S_ISREG(st.st_mode) || st.st_size <= 0 || + (size_t)st.st_size > FWX_FEATURE_MAX_SIZE + 24U) { + LOG_WARN("feature online invalid candidate file, mode=%o size=%lld", + st.st_mode, (long long)st.st_size); + return -1; + } + LOG_INFO("feature online candidate extracted, member=%s size=%lld", + "feature.bin", (long long)st.st_size); + unlink(ONLINE_FEATURE_TEMP); + if (rename(feature_source, ONLINE_FEATURE_TEMP) != 0) { + LOG_ERROR("feature online move extracted feature failed, errno=%d", errno); + return -1; + } + if (chmod(ONLINE_FEATURE_TEMP, 0644) != 0 || + rename(ONLINE_FEATURE_TEMP, FWX_FEATURE_CANDIDATE_PATH) != 0) { + LOG_ERROR("feature online stage candidate failed, errno=%d", errno); + return -1; + } + if (stat(ONLINE_ICON_DIR, &st) == 0 && S_ISDIR(st.st_mode)) { + if (validate_icon_tree(ONLINE_ICON_DIR, 0) < 0 || + directory_size(ONLINE_ICON_DIR) > 50U * 1024U * 1024U) { + LOG_WARN("feature online icon tree validation failed"); + return -1; + } + LOG_INFO("feature online icon tree validated, listed_icons=%d", icon_count); + } + LOG_INFO("feature online candidate ready: %s", FWX_FEATURE_CANDIDATE_PATH); + return 0; +} + +static unsigned long long directory_size(const char *path) +{ + struct stat st; + struct dirent *entry; + DIR *dir; + char child[512]; + unsigned long long total = 0; + + dir = opendir(path); + if (!dir) + return 0; + while ((entry = readdir(dir)) != NULL) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + snprintf(child, sizeof(child), "%s/%s", path, entry->d_name); + if (lstat(child, &st) != 0) + continue; + if (S_ISREG(st.st_mode)) + total += (unsigned long long)st.st_size; + else if (S_ISDIR(st.st_mode)) + total += directory_size(child); + } + closedir(dir); + return total; +} + +static int install_icons(void) +{ + struct stat st; + struct statvfs space; + unsigned long long required; + unsigned long long available; + + if (stat(ONLINE_ICON_DIR, &st) != 0 || !S_ISDIR(st.st_mode)) + return 0; + required = directory_size(ONLINE_ICON_DIR); + if (statvfs("/overlay", &space) != 0) + return 1; + available = (unsigned long long)space.f_bavail * space.f_frsize; + if (required > available) + return 1; + if (run_shell_command("mkdir -p " ONLINE_ICON_TARGET " && rm -rf " ONLINE_ICON_TARGET + "/* && cp -r " ONLINE_ICON_DIR "/. " ONLINE_ICON_TARGET "/", 60) != 0) + return 1; + if (directory_size(ONLINE_ICON_TARGET) < required) + return 1; + return 0; +} + +static int read_small_file(const char *path, char *buffer, size_t buffer_len) +{ + FILE *fp; + size_t len; + + fp = fopen(path, "rb"); + if (!fp) + return -1; + len = fread(buffer, 1, buffer_len - 1, fp); + fclose(fp); + buffer[len] = '\0'; + return 0; +} + +static int write_json_file_atomic(const char *path, struct json_object *obj) +{ + char temporary[256]; + const char *text; + FILE *fp = NULL; + int ret = -1; + + if (!path || !obj || + snprintf(temporary, sizeof(temporary), "%s.tmp", path) >= (int)sizeof(temporary)) + return -1; + mkdir("/etc/fwxd", 0755); + text = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN); + fp = fopen(temporary, "w"); + if (!fp || fwrite(text, 1, strlen(text), fp) != strlen(text) || + fflush(fp) != 0 || fsync(fileno(fp)) != 0) + goto out; + if (fclose(fp) != 0) { + fp = NULL; + goto out; + } + fp = NULL; + if (chmod(temporary, 0644) != 0 || rename(temporary, path) != 0) + goto out; + ret = 0; +out: + if (fp) + fclose(fp); + if (ret != 0) + unlink(temporary); + return ret; +} + +static int backup_download_archive(const char *reason) +{ + const char *temporary = ONLINE_FAILED_ARCHIVE_PATH ".tmp"; + FILE *in = NULL; + FILE *out = NULL; + char buffer[4096]; + size_t len; + int ret = -1; + + in = fopen(ONLINE_ARCHIVE_PATH, "rb"); + if (!in) + goto out; + out = fopen(temporary, "wb"); + if (!out) + goto out; + while ((len = fread(buffer, 1, sizeof(buffer), in)) > 0) { + if (fwrite(buffer, 1, len, out) != len) + goto out; + } + if (ferror(in) || fflush(out) != 0 || fsync(fileno(out)) != 0) + goto out; + if (fclose(out) != 0) { + out = NULL; + goto out; + } + out = NULL; + if (chmod(temporary, 0644) != 0 || rename(temporary, ONLINE_FAILED_ARCHIVE_PATH) != 0) + goto out; + ret = 0; +out: + if (out) + fclose(out); + if (in) + fclose(in); + if (ret != 0) + unlink(temporary); + LOG_WARN("feature online backup downloaded package: reason=%s path=%s ret=%d", + reason ? reason : "", ONLINE_FAILED_ARCHIVE_PATH, ret); + return ret; +} + +static void worker_failed(int code, const char *message) +{ + LOG_WARN("feature online update failed: code=%d message=%s", code, + message ? message : ""); + set_status("failed", "idle", code, message, 0); + pthread_mutex_lock(&online_mutex); + online_worker_running = 0; + pthread_mutex_unlock(&online_mutex); + release_upgrade_lock(); +} + +static void *online_update_worker(void *arg) +{ + online_worker_t *worker = arg; + file_buffer_t output = {0}; + progress_ctx_t progress = {0}; + char url[2048]; + char error_body[4096] = {0}; + char error_message[160] = {0}; + long http_code = 0; + CURLcode curl_ret; + CURL *curl; + int error_code; + int icon_count = 0; + char event = '1'; + + LOG_INFO("feature online update worker start: id=%s lang=%s model=%s device_id=%s timeout=%d", + worker->id, worker->lang, worker->model, worker->device_id, ONLINE_UPDATE_TIMEOUT); + set_status("running", "downloading", 0, "", 0); + cleanup_work_files(); + if (mkdir(ONLINE_WORK_DIR, 0700) != 0) { + LOG_ERROR("feature online create work dir failed, errno=%d", errno); + worker_failed(400, "failed to prepare download directory"); + free_worker(worker); + return NULL; + } + output.fp = fopen(ONLINE_ARCHIVE_PATH, "wb"); + output.limit = ONLINE_ARCHIVE_MAX; + curl = curl_easy_init(); + if (!output.fp || !curl || build_url(curl, "/api/download_feature", + worker->token, worker->device_id, worker->model, + worker->lang, NULL, NULL, worker->id, + url, sizeof(url)) < 0) { + if (output.fp) + fclose(output.fp); + if (curl) + curl_easy_cleanup(curl); + worker_failed(400, "failed to initialize download"); + free_worker(worker); + return NULL; + } + set_curl_options(curl, url); + LOG_INFO("feature online download request prepared: id=%s", worker->id); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)ONLINE_DOWNLOAD_TIMEOUT); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1024L); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 30L); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, download_progress_cb); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, file_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &output); + LOG_INFO("feature online download start: id=%s timeout=%d low_speed=1024B/s low_speed_time=30", + worker->id, ONLINE_DOWNLOAD_TIMEOUT); + curl_ret = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + fclose(output.fp); + curl_easy_cleanup(curl); + LOG_INFO("feature online download complete: id=%s curl=%d http=%ld size=%zu too_large=%d", + worker->id, curl_ret, http_code, output.size, output.too_large); + if (output.too_large) { + backup_download_archive("file too large"); + worker_failed(402, "downloaded file is too large"); + free_worker(worker); + return NULL; + } + if (curl_ret != CURLE_OK || http_code != 200 || output.size == 0) { + if (output.size > 0) + backup_download_archive("download failed"); + read_small_file(ONLINE_ARCHIVE_PATH, error_body, sizeof(error_body)); + parse_remote_error(error_body, http_code, &error_code, error_message, sizeof(error_message)); + LOG_WARN("feature online download failed: curl=%d(%s) http=%ld size=%zu remote_code=%d message=%s", + curl_ret, curl_easy_strerror(curl_ret), http_code, output.size, + error_code, error_message); + worker_failed(error_code, error_message); + free_worker(worker); + return NULL; + } + if (read_small_file(ONLINE_ARCHIVE_PATH, error_body, sizeof(error_body)) == 0 && + parse_remote_json_error(error_body, &error_code, error_message, + sizeof(error_message)) == 0) { + backup_download_archive("download remote error"); + LOG_WARN("feature online download got remote json error: code=%d message=%s", + error_code, error_message); + worker_failed(error_code, error_message); + free_worker(worker); + return NULL; + } + if (worker->expected_md5[0]) { + char actual_md5[33] = {0}; + if (file_md5_hex(ONLINE_ARCHIVE_PATH, actual_md5) < 0 || + strcmp(actual_md5, worker->expected_md5) != 0) { + backup_download_archive("md5 mismatch"); + LOG_WARN("feature online md5 mismatch: id=%s expected=%s actual=%s", + worker->id, worker->expected_md5, + actual_md5[0] ? actual_md5 : "calc_failed"); + worker_failed(403, "feature library checksum verification failed"); + free_worker(worker); + return NULL; + } + LOG_INFO("feature online md5 verified: id=%s md5=%s", worker->id, actual_md5); + } + set_status("running", "extracting", 0, "", 0); + LOG_INFO("feature online archive validation start: id=%s path=%s size=%zu", + worker->id, ONLINE_ARCHIVE_PATH, output.size); + if (validate_archive(&icon_count) < 0) { + backup_download_archive("archive validation failed"); + read_small_file(ONLINE_ARCHIVE_PATH, error_body, sizeof(error_body)); + parse_remote_error(error_body, http_code, &error_code, error_message, sizeof(error_message)); + unlink(FWX_FEATURE_CANDIDATE_PATH); + if (error_code != 400) { + LOG_WARN("feature online archive validation got remote error: code=%d message=%s", + error_code, error_message); + worker_failed(error_code, error_message); + } else { + worker_failed(401, "invalid feature library package"); + } + free_worker(worker); + return NULL; + } + LOG_INFO("feature online archive extract start: id=%s icons=%d", worker->id, icon_count); + if (prepare_archive_candidate(icon_count) < 0) { + backup_download_archive("archive extract failed"); + unlink(FWX_FEATURE_CANDIDATE_PATH); + worker_failed(401, "invalid feature library package"); + free_worker(worker); + return NULL; + } + LOG_INFO("feature online notify main loop: id=%s", worker->id); + if (write(online_pipe[1], &event, 1) != 1) { + LOG_ERROR("feature online pipe notify failed: id=%s errno=%d", worker->id, errno); + unlink(FWX_FEATURE_CANDIDATE_PATH); + worker_failed(400, "failed to notify feature update"); + } + LOG_INFO("feature online worker finished download/extract stage: id=%s", worker->id); + free_worker(worker); + return NULL; +} + +static void candidate_stage_changed(const char *stage) +{ + set_status("running", stage, 0, "", 0); +} + +static void online_pipe_handler(struct uloop_fd *fd, unsigned int events) +{ + char buffer[16]; + char version[64] = {0}; + char format[32] = {0}; + int status_code = 400; + int icons_skipped = 0; + + (void)events; + LOG_INFO("feature online pipe handler start"); + while (read(fd->fd, buffer, sizeof(buffer)) > 0) + ; + set_status("running", "validating", 0, "", 0); + LOG_INFO("feature online candidate process start: path=%s", FWX_FEATURE_CANDIDATE_PATH); + if (fwx_feature_process_candidate(version, sizeof(version), format, sizeof(format), + &status_code, candidate_stage_changed) < 0) { + backup_download_archive("candidate validation failed"); + LOG_WARN("feature online candidate validation failed: status=%d version=%s format=%s", + status_code, version, format); + worker_failed(status_code, status_code == 401 ? + "feature library format error" : "failed to apply feature library"); + return; + } + g_feature_update = 1; + LOG_INFO("feature online install icons start"); + icons_skipped = install_icons(); + LOG_INFO("feature online candidate applied: version=%s format=%s icons_skipped=%d", + version, format, icons_skipped); + set_status("success", "idle", 200, "feature library updated", icons_skipped); + pthread_mutex_lock(&online_mutex); + online_worker_running = 0; + pthread_mutex_unlock(&online_mutex); + release_upgrade_lock(); +} + +struct json_object *fwx_api_get_feature_online_config(struct json_object *req_obj) +{ + struct json_object *data = json_object_new_object(); + char token[ONLINE_TOKEN_MAX + 1] = {0}; + char device_id[33] = {0}; + char model[129] = {0}; + + (void)req_obj; + get_saved_token(token, sizeof(token)); + if (get_device_id(device_id, sizeof(device_id)) < 0 || + get_device_model(model, sizeof(model)) < 0) { + json_object_put(data); + return error_response(400, "failed to read device information"); + } + json_object_object_add(data, "token", json_object_new_string(token)); + json_object_object_add(data, "device_id", json_object_new_string(device_id)); + json_object_object_add(data, "model", json_object_new_string(model)); + json_object_object_add(data, "version", json_object_new_string(ONLINE_VERSION)); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data); +} + +struct json_object *fwx_api_set_feature_online_config(struct json_object *req_obj) +{ + struct json_object *token_obj; + struct uci_context *ctx; + const char *token; + + if (!req_obj || !json_object_object_get_ex(req_obj, "token", &token_obj) || + !json_object_is_type(token_obj, json_type_string)) + return error_response(400, "token is required"); + token = json_object_get_string(token_obj); + if (!token_valid(token)) + return error_response(400, "failed to save token"); + ctx = uci_alloc_context(); + if (!ctx) + return error_response(400, "failed to save token"); + if (fwx_uci_set_value(ctx, "fwx.global.feature_token", (char *)token) != 0 || + fwx_uci_commit(ctx, "fwx") != 0) { + uci_free_context(ctx); + return error_response(400, "failed to save token"); + } + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +static struct json_object *normalize_feature_online_list(struct json_object *files_obj, + const char *request_lang, + const char *announcement) +{ + struct json_object *result_data; + struct json_object *result_files; + int i; + + if (!files_obj || !json_object_is_type(files_obj, json_type_array)) + return NULL; + result_data = json_object_new_object(); + result_files = json_object_new_array(); + if (!result_data || !result_files) { + if (result_files) + json_object_put(result_files); + if (result_data) + json_object_put(result_data); + return NULL; + } + for (i = 0; i < json_object_array_length(files_obj) && i < ONLINE_FILE_MAX; i++) { + struct json_object *item = json_object_array_get_idx(files_obj, i); + struct json_object *id_obj, *version_obj, *type_obj = NULL; + struct json_object *lang_obj = NULL, *count_obj = NULL; + struct json_object *free_obj = NULL; + struct json_object *md5_obj = NULL; + struct json_object *desc_obj = NULL, *date_obj = NULL; + const char *id; + const char *version; + const char *item_lang = request_lang; + const char *description = ""; + const char *date = ""; + const char *md5 = ""; + int feature_type = 0; + int feature_count = 0; + int is_free = 0; + struct json_object *normalized; + + if (!item || !json_object_object_get_ex(item, "id", &id_obj) || + !json_object_object_get_ex(item, "version", &version_obj)) + continue; + id = json_object_get_string(id_obj); + version = json_object_get_string(version_obj); + if (!id_valid(id) || !version || !version[0] || strlen(version) > 64) + continue; + if (json_object_object_get_ex(item, "type", &type_obj)) { + feature_type = json_object_get_int(type_obj); + if (feature_type != 0 && feature_type != 1) + feature_type = 0; + } + if (json_object_object_get_ex(item, "lang", &lang_obj) && + json_object_is_type(lang_obj, json_type_string) && + lang_valid(json_object_get_string(lang_obj))) + item_lang = json_object_get_string(lang_obj); + if (json_object_object_get_ex(item, "count", &count_obj)) { + feature_count = json_object_get_int(count_obj); + if (feature_count < 0) + feature_count = 0; + } + if (json_object_object_get_ex(item, "free", &free_obj)) + is_free = json_object_get_int(free_obj) ? 1 : 0; + if (!json_object_object_get_ex(item, "desc", &desc_obj)) + json_object_object_get_ex(item, "description", &desc_obj); + json_object_object_get_ex(item, "date", &date_obj); + if (desc_obj && json_object_is_type(desc_obj, json_type_string) && + strlen(json_object_get_string(desc_obj)) <= 256) + description = json_object_get_string(desc_obj); + if (date_obj && json_object_is_type(date_obj, json_type_string) && + strlen(json_object_get_string(date_obj)) <= 32) + date = json_object_get_string(date_obj); + if (json_object_object_get_ex(item, "md5", &md5_obj) && + json_object_is_type(md5_obj, json_type_string) && + md5_text_valid(json_object_get_string(md5_obj))) + md5 = json_object_get_string(md5_obj); + normalized = json_object_new_object(); + json_object_object_add(normalized, "id", json_object_new_string(id)); + json_object_object_add(normalized, "version", json_object_new_string(version)); + json_object_object_add(normalized, "type", json_object_new_int(feature_type)); + json_object_object_add(normalized, "free", json_object_new_int(is_free)); + json_object_object_add(normalized, "lang", json_object_new_string(item_lang)); + json_object_object_add(normalized, "md5", json_object_new_string(md5)); + json_object_object_add(normalized, "count", json_object_new_int(feature_count)); + json_object_object_add(normalized, "desc", json_object_new_string(description)); + json_object_object_add(normalized, "date", json_object_new_string(date)); + json_object_array_add(result_files, normalized); + } + json_object_object_add(result_data, "version", json_object_new_string(ONLINE_VERSION)); + json_object_object_add(result_data, "announcement", + json_object_new_string(announcement ? announcement : "")); + json_object_object_add(result_data, "count", + json_object_new_int(json_object_array_length(result_files))); + json_object_object_add(result_data, "files", result_files); + return result_data; +} + +static struct json_object *new_empty_feature_online_list(void) +{ + struct json_object *data = json_object_new_object(); + + json_object_object_add(data, "version", json_object_new_string(ONLINE_VERSION)); + json_object_object_add(data, "announcement", json_object_new_string("")); + json_object_object_add(data, "count", json_object_new_int(0)); + json_object_object_add(data, "files", json_object_new_array()); + return data; +} + +static struct json_object *read_feature_online_list_cache(void) +{ + struct json_object *root; + struct json_object *files_obj; + + root = json_object_from_file(FWX_FEATURE_LIST_CACHE_PATH); + if (!root) + return NULL; + if (!json_object_object_get_ex(root, "files", &files_obj) || + !json_object_is_type(files_obj, json_type_array)) { + json_object_put(root); + return NULL; + } + return root; +} + +struct json_object *fwx_api_get_feature_online_list(struct json_object *req_obj) +{ + memory_buffer_t response = {.limit = ONLINE_RESPONSE_MAX}; + struct json_object *root = NULL; + struct json_object *code_obj; + struct json_object *data_obj; + struct json_object *files_obj; + struct json_object *announcement_obj = NULL; + struct json_object *result_data; + struct json_object *request_lang_obj; + struct json_object *device_lang_obj; + struct json_object *refresh_obj; + char token[ONLINE_TOKEN_MAX + 1], device_id[33], model[129], url[2048], message[160]; + char hostname[ONLINE_HOSTNAME_MAX + 1] = {0}; + char device_lang[ONLINE_DEVICE_LANG_MAX + 1] = {0}; + const char *request_lang = "cn"; + long http_code = 0; + int remote_code = 400; + int refresh = 0; + CURLcode curl_ret; + CURL *curl; + if (req_obj && json_object_object_get_ex(req_obj, "lang", &request_lang_obj) && + json_object_is_type(request_lang_obj, json_type_string) && + lang_valid(json_object_get_string(request_lang_obj))) + request_lang = json_object_get_string(request_lang_obj); + if (req_obj && json_object_object_get_ex(req_obj, "device_lang", &device_lang_obj) && + json_object_is_type(device_lang_obj, json_type_string) && + query_text_valid(json_object_get_string(device_lang_obj), ONLINE_DEVICE_LANG_MAX)) + snprintf(device_lang, sizeof(device_lang), "%s", + json_object_get_string(device_lang_obj)); + if (req_obj && json_object_object_get_ex(req_obj, "refresh", &refresh_obj)) + refresh = json_object_get_int(refresh_obj) ? 1 : 0; + if (!refresh) { + result_data = read_feature_online_list_cache(); + if (result_data) + return fwx_gen_api_response_data(API_CODE_SUCCESS, result_data); + return fwx_gen_api_response_data(API_CODE_SUCCESS, new_empty_feature_online_list()); + } + if (get_request_credentials(token, sizeof(token), device_id, sizeof(device_id), + model, sizeof(model)) < 0) + return error_response(400, "token or device information is invalid"); + get_system_hostname(hostname, sizeof(hostname)); + curl = curl_easy_init(); + if (!curl || build_url(curl, "/api/get_feature_list", token, device_id, + model, request_lang, hostname, device_lang, NULL, + url, sizeof(url)) < 0) { + if (curl) + curl_easy_cleanup(curl); + return error_response(400, "failed to initialize subscription request"); + } + LOG_INFO("feature online list request prepared: lang=%s device_lang=%s hostname=%s", + request_lang, device_lang, hostname); + set_curl_options(curl, url); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L); // 2min + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memory_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_ret = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + curl_easy_cleanup(curl); + LOG_INFO("feature online list response: curl=%d http=%ld size=%zu", + curl_ret, http_code, response.size); + if (curl_ret != CURLE_OK || !response.data) { + free(response.data); + return error_response(400, "failed to request subscription server"); + } + root = json_tokener_parse(response.data); + if (http_code != 200 || !root || !json_object_object_get_ex(root, "code", &code_obj) || + json_object_get_int(code_obj) != 20000 || + !json_object_object_get_ex(root, "data", &data_obj) || + !json_object_object_get_ex(data_obj, "files", &files_obj) || + !json_object_is_type(files_obj, json_type_array)) { + parse_remote_error(response.data, http_code, &remote_code, message, sizeof(message)); + if (root) + json_object_put(root); + free(response.data); + return error_response(remote_code, message); + } + json_object_object_get_ex(data_obj, "announcement", &announcement_obj); + if (!announcement_obj) + json_object_object_get_ex(data_obj, "Announcement", &announcement_obj); + if (!announcement_obj) + json_object_object_get_ex(root, "announcement", &announcement_obj); + if (!announcement_obj) + json_object_object_get_ex(root, "Announcement", &announcement_obj); + result_data = normalize_feature_online_list( + files_obj, request_lang, + announcement_obj && json_object_is_type(announcement_obj, json_type_string) && + strlen(json_object_get_string(announcement_obj)) <= 1024 ? + json_object_get_string(announcement_obj) : ""); + if (!result_data) { + json_object_put(root); + free(response.data); + return error_response(400, "invalid subscription file list"); + } + if (json_object_array_length(files_obj) > 0) { + struct json_object *result_files = NULL; + if (!json_object_object_get_ex(result_data, "files", &result_files) || + json_object_array_length(result_files) == 0) { + json_object_put(result_data); + json_object_put(root); + free(response.data); + return error_response(400, "invalid subscription file list"); + } + } + if (write_json_file_atomic(FWX_FEATURE_LIST_CACHE_PATH, result_data) != 0) + LOG_WARN("feature online list cache write failed: %s", FWX_FEATURE_LIST_CACHE_PATH); + json_object_put(root); + free(response.data); + return fwx_gen_api_response_data(API_CODE_SUCCESS, result_data); +} + +struct json_object *fwx_api_start_feature_online_update(struct json_object *req_obj) +{ + struct json_object *id_obj; + struct json_object *lang_obj; + struct json_object *md5_obj; + online_worker_t *worker; + pthread_attr_t attr; + pthread_t thread; + const char *id; + const char *lang = "cn"; + const char *md5 = ""; + int ret; + + if (!online_ready) + return error_response(400, "online update service is unavailable"); + if (!req_obj || !json_object_object_get_ex(req_obj, "id", &id_obj)) + return error_response(400, "file id is required"); + id = json_object_get_string(id_obj); + if (!id_valid(id)) + return error_response(400, "invalid file id"); + if (json_object_object_get_ex(req_obj, "lang", &lang_obj) && + json_object_is_type(lang_obj, json_type_string) && + lang_valid(json_object_get_string(lang_obj))) + lang = json_object_get_string(lang_obj); + if (json_object_object_get_ex(req_obj, "md5", &md5_obj) && + json_object_is_type(md5_obj, json_type_string)) { + md5 = json_object_get_string(md5_obj); + if (md5[0] && !md5_text_valid(md5)) + return error_response(400, "invalid feature library md5"); + } + LOG_INFO("feature online start request: id=%s lang=%s md5=%s", id, lang, + md5[0] ? "set" : "empty"); + pthread_mutex_lock(&online_mutex); + if (online_worker_running) { + pthread_mutex_unlock(&online_mutex); + return error_response(400, "feature update is already running"); + } + pthread_mutex_unlock(&online_mutex); + if (acquire_upgrade_lock() < 0) + return error_response(400, "another feature update is running"); + worker = calloc(1, sizeof(*worker)); + if (!worker) { + release_upgrade_lock(); + return error_response(400, "failed to start feature update"); + } + snprintf(worker->id, sizeof(worker->id), "%s", id); + snprintf(worker->lang, sizeof(worker->lang), "%s", lang); + if (md5[0]) + copy_md5_lower(worker->expected_md5, md5); + ret = get_request_credentials(worker->token, sizeof(worker->token), + worker->device_id, sizeof(worker->device_id), + worker->model, sizeof(worker->model)); + if (ret < 0) { + free_worker(worker); + release_upgrade_lock(); + return error_response(400, "token or device information is invalid"); + } + pthread_mutex_lock(&online_mutex); + online_worker_running = 1; + snprintf(online_status.id, sizeof(online_status.id), "%s", id); + pthread_mutex_unlock(&online_mutex); + set_status("running", "downloading", 0, "", 0); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ret = pthread_create(&thread, &attr, online_update_worker, worker); + pthread_attr_destroy(&attr); + if (ret != 0) { + free_worker(worker); + pthread_mutex_lock(&online_mutex); + online_worker_running = 0; + pthread_mutex_unlock(&online_mutex); + release_upgrade_lock(); + return error_response(400, "failed to start feature update"); + } + return fwx_gen_api_response_data(API_CODE_SUCCESS, status_data()); +} + +struct json_object *fwx_api_get_feature_online_update_status(struct json_object *req_obj) +{ + (void)req_obj; + return fwx_gen_api_response_data(API_CODE_SUCCESS, status_data()); +} + +int fwx_feature_online_init(void) +{ + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) + return -1; + if (pipe(online_pipe) != 0) { + curl_global_cleanup(); + return -1; + } + fcntl(online_pipe[0], F_SETFL, fcntl(online_pipe[0], F_GETFL) | O_NONBLOCK); + fcntl(online_pipe[1], F_SETFL, fcntl(online_pipe[1], F_GETFL) | O_NONBLOCK); + online_uloop_fd.fd = online_pipe[0]; + online_uloop_fd.cb = online_pipe_handler; + if (uloop_fd_add(&online_uloop_fd, ULOOP_READ) != 0) { + close(online_pipe[0]); + close(online_pipe[1]); + online_pipe[0] = online_pipe[1] = -1; + curl_global_cleanup(); + return -1; + } + set_status("idle", "idle", 0, "", 0); + online_ready = 1; + return 0; +} + +void fwx_feature_online_cleanup(void) +{ + int running; + + if (!online_ready) + return; + pthread_mutex_lock(&online_mutex); + running = online_worker_running; + pthread_mutex_unlock(&online_mutex); + if (running) + return; + online_ready = 0; + if (online_pipe[0] >= 0) { + uloop_fd_delete(&online_uloop_fd); + close(online_pipe[0]); + close(online_pipe[1]); + online_pipe[0] = online_pipe[1] = -1; + } + curl_global_cleanup(); +} diff --git a/open-app-filter/src/fwx_feature_online.h b/open-app-filter/src/fwx_feature_online.h new file mode 100644 index 00000000..07908bb2 --- /dev/null +++ b/open-app-filter/src/fwx_feature_online.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __FWX_FEATURE_ONLINE_H__ +#define __FWX_FEATURE_ONLINE_H__ + +#include + +#define FWX_FEATURE_UPGRADE_LOCK_PATH "/tmp/feature_upgrade.lock" + +int fwx_feature_online_init(void); +void fwx_feature_online_cleanup(void); +struct json_object *fwx_api_get_feature_online_config(struct json_object *req_obj); +struct json_object *fwx_api_set_feature_online_config(struct json_object *req_obj); +struct json_object *fwx_api_get_feature_online_list(struct json_object *req_obj); +struct json_object *fwx_api_start_feature_online_update(struct json_object *req_obj); +struct json_object *fwx_api_get_feature_online_update_status(struct json_object *req_obj); + +#endif diff --git a/open-app-filter/src/fwx_firewall.c b/open-app-filter/src/fwx_firewall.c new file mode 100644 index 00000000..e21164e7 --- /dev/null +++ b/open-app-filter/src/fwx_firewall.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include "fwx.h" +#include "fwx_firewall.h" +#include "fwx_uci.h" + +static struct json_object *get_basic_setting(void) +{ + struct uci_context *ctx = uci_alloc_context(); + struct json_object *data_obj = NULL; + struct json_object *basic_obj = NULL; + char syn_flood[16] = {0}; + char input[32] = {0}; + char output[32] = {0}; + char forward[32] = {0}; + char fullcone[16] = {0}; + + if (!ctx) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + if (fwx_uci_get_value(ctx, "firewall.@defaults[0].syn_flood", + syn_flood, sizeof(syn_flood)) != UCI_OK) { + LOG_ERROR("Failed to read firewall defaults\n"); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + fwx_uci_get_value(ctx, "firewall.@defaults[0].input", input, sizeof(input)); + fwx_uci_get_value(ctx, "firewall.@defaults[0].output", output, sizeof(output)); + fwx_uci_get_value(ctx, "firewall.@defaults[0].forward", forward, sizeof(forward)); + fwx_uci_get_value(ctx, "firewall.@defaults[0].fullcone", fullcone, sizeof(fullcone)); + + data_obj = json_object_new_object(); + basic_obj = json_object_new_object(); + if (!data_obj || !basic_obj) { + if (data_obj) { + json_object_put(data_obj); + } + if (basic_obj) { + json_object_put(basic_obj); + } + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + json_object_object_add(basic_obj, "syn_flood", + json_object_new_int(atoi(syn_flood))); + json_object_object_add(basic_obj, "input", json_object_new_string(input)); + json_object_object_add(basic_obj, "output", json_object_new_string(output)); + json_object_object_add(basic_obj, "forward", json_object_new_string(forward)); + json_object_object_add(basic_obj, "fullcone", + json_object_new_int(atoi(fullcone))); + json_object_object_add(data_obj, "basic", basic_obj); + + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +static struct json_object *set_basic_setting(struct json_object *req_obj) +{ + struct json_object *fullcone_obj = NULL; + struct uci_context *ctx = NULL; + int fullcone = 0; + + if (!json_object_object_get_ex(req_obj, "fullcone", &fullcone_obj)) { + LOG_ERROR("FullCone setting is missing\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + if (!json_object_is_type(fullcone_obj, json_type_int) && + !json_object_is_type(fullcone_obj, json_type_boolean)) { + LOG_ERROR("FullCone setting type is invalid\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + fullcone = json_object_get_int(fullcone_obj); + LOG_INFO("set_firewall parsed fullcone: %d\n", fullcone); + if (fullcone != 0 && fullcone != 1) { + LOG_ERROR("Invalid FullCone setting: %d\n", fullcone); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + ctx = uci_alloc_context(); + if (!ctx) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + fwx_uci_set_int_value(ctx, "firewall.@defaults[0].fullcone", fullcone); + fwx_uci_commit(ctx, "firewall"); + uci_free_context(ctx); + + system("/etc/init.d/firewall reload >/dev/null 2>&1"); + LOG_INFO("set_firewall basic setting applied\n"); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_get_firewall(struct json_object *req_obj) +{ + (void)req_obj; + return get_basic_setting(); +} + +struct json_object *fwx_api_set_firewall(struct json_object *req_obj) +{ + struct json_object *action_obj = NULL; + struct json_object *basic_obj = NULL; + struct json_object *response_obj = NULL; + const char *action = NULL; + + LOG_INFO("fwx_api_set_firewall request: %s\n", + req_obj ? json_object_to_json_string(req_obj) : "null"); + + if (!req_obj || + !json_object_object_get_ex(req_obj, "action", &action_obj)) { + LOG_ERROR("Firewall setting request is invalid\n"); + response_obj = fwx_gen_api_response_data(API_CODE_ERROR, NULL); + goto done; + } + + action = json_object_get_string(action_obj); + if (action && strcmp(action, "set_basic") == 0) { + if (!json_object_object_get_ex(req_obj, "basic", &basic_obj) || + !json_object_is_type(basic_obj, json_type_object)) { + LOG_ERROR("Firewall basic setting is invalid\n"); + response_obj = fwx_gen_api_response_data(API_CODE_ERROR, NULL); + goto done; + } + response_obj = set_basic_setting(basic_obj); + goto done; + } + + LOG_ERROR("Unsupported firewall action: %s\n", action ? action : ""); + response_obj = fwx_gen_api_response_data(API_CODE_ERROR, NULL); + +done: + LOG_INFO("fwx_api_set_firewall response: %s\n", + response_obj ? json_object_to_json_string(response_obj) : "null"); + return response_obj; +} diff --git a/open-app-filter/src/fwx_firewall.h b/open-app-filter/src/fwx_firewall.h new file mode 100644 index 00000000..d21df283 --- /dev/null +++ b/open-app-filter/src/fwx_firewall.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __FWX_FIREWALL_H__ +#define __FWX_FIREWALL_H__ + +#include + +struct json_object *fwx_api_get_firewall(struct json_object *req_obj); +struct json_object *fwx_api_set_firewall(struct json_object *req_obj); + +#endif diff --git a/open-app-filter/src/fwx_mac_filter.c b/open-app-filter/src/fwx_mac_filter.c new file mode 100644 index 00000000..af3b60a0 --- /dev/null +++ b/open-app-filter/src/fwx_mac_filter.c @@ -0,0 +1,1354 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ + +#include +#include +#include +#include +#include +#include +#include "fwx_user.h" +#include "fwx_netlink.h" +#include "fwx_ubus.h" +#include "fwx_config.h" +#include +#include +#include +#include +#include "fwx.h" +#include +#include +#include "fwx_utils.h" +#include "fwx_mac_filter.h" + + +#define MACFILTER_RULES_STATE_FILE "/tmp/macfilter_rules_state" +#define MACFILTER_WHITELIST_STATE_FILE "/tmp/macfilter_whitelist_state" +#define MACFILTER_TIME_MODE_RANGE 1 +#define MACFILTER_TIME_MODE_DURATION 2 +#define MACFILTER_TIME_MODE_FLOW 3 + + +static void set_state_file(const char *file_path) { + FILE *fd = fopen(file_path, "w"); + if (fd) { + fprintf(fd, "1"); + fclose(fd); + LOG_DEBUG("Set state file: %s\n", file_path); + } else { + LOG_ERROR("Failed to set state file: %s\n", file_path); + } +} + + +static int find_mac_filter_rule_index_by_id(struct uci_context *uci_ctx, int id) { + int i; + char id_str_uci[32]; + int num = fwx_uci_get_list_num(uci_ctx, "macfilter", "rule"); + for (i = 0; i < num; i++) { + char buf[128]; + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].id", i); + if (fwx_uci_get_value(uci_ctx, buf, id_str_uci, sizeof(id_str_uci)) != 0) { + continue; + } + if (atoi(id_str_uci) == id) { + return i; + } + } + return -1; // Not found +} + +static int normalize_macfilter_time_mode(int time_mode) +{ + if (time_mode == MACFILTER_TIME_MODE_DURATION || time_mode == MACFILTER_TIME_MODE_FLOW) { + return time_mode; + } + return MACFILTER_TIME_MODE_RANGE; +} + +static int parse_uint_token(const char *token, int *value) +{ + int i; + int v = 0; + + if (!token || token[0] == '\0') { + return 0; + } + + for (i = 0; token[i] != '\0'; i++) { + if (token[i] < '0' || token[i] > '9') { + return 0; + } + v = v * 10 + (token[i] - '0'); + } + + if (value) { + *value = v; + } + return 1; +} + +static int macfilter_rule_mode_to_time_mode(int rule_mode, int fallback_time_mode) +{ + if (rule_mode == 1) { + return MACFILTER_TIME_MODE_DURATION; + } + if (rule_mode == 2) { + return MACFILTER_TIME_MODE_FLOW; + } + if (rule_mode == 0) { + return MACFILTER_TIME_MODE_RANGE; + } + return normalize_macfilter_time_mode(fallback_time_mode); +} + +static int append_time_rule_obj(struct json_object *time_rules_array, const char *rule_str, int fallback_time_mode) +{ + char rule_buf[256] = {0}; + char *parts[32] = {0}; + int part_count = 0; + char *token = NULL; + char *saveptr = NULL; + int parse_time_mode = normalize_macfilter_time_mode(fallback_time_mode); + int i; + + if (!time_rules_array || !rule_str || rule_str[0] == '\0') { + return 0; + } + + if (strchr(rule_str, ';') != NULL) { + char sem_buf[256] = {0}; + char *weekday_part = NULL; + char *mode_part = NULL; + char *value_part = NULL; + char *sem_saveptr = NULL; + struct json_object *time_rule_obj = NULL; + struct json_object *weekdays_array = NULL; + int weekday_count = 0; + int parsed_rule_mode = 0; + int parsed_time_mode = parse_time_mode; + char weekday_buf[128] = {0}; + char *weekday_token = NULL; + char *weekday_saveptr = NULL; + + strncpy(sem_buf, rule_str, sizeof(sem_buf) - 1); + weekday_part = strtok_r(sem_buf, ";", &sem_saveptr); + mode_part = strtok_r(NULL, ";", &sem_saveptr); + value_part = strtok_r(NULL, ";", &sem_saveptr); + if (weekday_part && mode_part && value_part) { + parsed_rule_mode = atoi(mode_part); + parsed_time_mode = macfilter_rule_mode_to_time_mode(parsed_rule_mode, parse_time_mode); + + time_rule_obj = json_object_new_object(); + weekdays_array = json_object_new_array(); + if (!time_rule_obj || !weekdays_array) { + if (time_rule_obj) json_object_put(time_rule_obj); + if (weekdays_array) json_object_put(weekdays_array); + return 0; + } + + strncpy(weekday_buf, weekday_part, sizeof(weekday_buf) - 1); + weekday_token = strtok_r(weekday_buf, ",", &weekday_saveptr); + while (weekday_token) { + int weekday = -1; + if (parse_uint_token(weekday_token, &weekday) && weekday >= 0 && weekday <= 6) { + json_object_array_add(weekdays_array, json_object_new_int(weekday)); + weekday_count++; + } + weekday_token = strtok_r(NULL, ",", &weekday_saveptr); + } + + if (weekday_count <= 0) { + json_object_put(weekdays_array); + json_object_put(time_rule_obj); + return 0; + } + + json_object_object_add(time_rule_obj, "weekdays", weekdays_array); + if (parsed_time_mode == MACFILTER_TIME_MODE_DURATION) { + int duration_minutes = atoi(value_part); + if (duration_minutes <= 0) { + json_object_put(time_rule_obj); + return 0; + } + json_object_object_add(time_rule_obj, "duration_minutes", json_object_new_int(duration_minutes)); + } else if (parsed_time_mode == MACFILTER_TIME_MODE_FLOW) { + int flow_mb = atoi(value_part); + if (flow_mb <= 0) { + json_object_put(time_rule_obj); + return 0; + } + json_object_object_add(time_rule_obj, "flow_mb", json_object_new_int(flow_mb)); + } else { + char value_copy[128] = {0}; + char *sep = NULL; + char *start_time = NULL; + char *end_time = NULL; + + strncpy(value_copy, value_part, sizeof(value_copy) - 1); + sep = strchr(value_copy, '-'); + if (!sep) { + json_object_put(time_rule_obj); + return 0; + } + *sep = '\0'; + start_time = value_copy; + end_time = sep + 1; + if (!start_time[0] || !end_time[0]) { + json_object_put(time_rule_obj); + return 0; + } + json_object_object_add(time_rule_obj, "start_time", json_object_new_string(start_time)); + json_object_object_add(time_rule_obj, "end_time", json_object_new_string(end_time)); + } + + json_object_array_add(time_rules_array, time_rule_obj); + return parsed_time_mode; + } + } + + strncpy(rule_buf, rule_str, sizeof(rule_buf) - 1); + token = strtok_r(rule_buf, ",", &saveptr); + while (token && part_count < (int)(sizeof(parts) / sizeof(parts[0]))) { + parts[part_count++] = token; + token = strtok_r(NULL, ",", &saveptr); + } + + if (part_count <= 0) { + return 0; + } + + if (parse_time_mode == MACFILTER_TIME_MODE_DURATION) { + int duration_minutes = 0; + int weekday_end = part_count - 1; + int weekday_count = 0; + struct json_object *time_rule_obj = NULL; + struct json_object *weekdays_array = NULL; + + if (part_count >= 3) { + int maybe_rule_mode = 0; + if (parse_uint_token(parts[part_count - 2], &maybe_rule_mode) && maybe_rule_mode == 1) { + weekday_end = part_count - 2; + } + } + + if (!parse_uint_token(parts[part_count - 1], &duration_minutes)) { + return 0; + } + if (duration_minutes <= 0) { + return 0; + } + + time_rule_obj = json_object_new_object(); + weekdays_array = json_object_new_array(); + if (!time_rule_obj || !weekdays_array) { + if (time_rule_obj) json_object_put(time_rule_obj); + if (weekdays_array) json_object_put(weekdays_array); + return 0; + } + + for (i = 0; i < weekday_end; i++) { + int weekday = -1; + if (parse_uint_token(parts[i], &weekday) && weekday >= 0 && weekday <= 6) { + json_object_array_add(weekdays_array, json_object_new_int(weekday)); + weekday_count++; + } + } + + if (weekday_count <= 0) { + json_object_put(weekdays_array); + json_object_put(time_rule_obj); + return 0; + } + + json_object_object_add(time_rule_obj, "weekdays", weekdays_array); + json_object_object_add(time_rule_obj, "duration_minutes", json_object_new_int(duration_minutes)); + json_object_array_add(time_rules_array, time_rule_obj); + return MACFILTER_TIME_MODE_DURATION; + } + + if (parse_time_mode == MACFILTER_TIME_MODE_FLOW) { + int flow_mb = 0; + int weekday_end = part_count - 1; + int weekday_count = 0; + struct json_object *time_rule_obj = NULL; + struct json_object *weekdays_array = NULL; + + if (part_count >= 3) { + int maybe_rule_mode = 0; + if (parse_uint_token(parts[part_count - 2], &maybe_rule_mode) && maybe_rule_mode == 2) { + weekday_end = part_count - 2; + } + } + + if (!parse_uint_token(parts[part_count - 1], &flow_mb)) { + return 0; + } + if (flow_mb <= 0) { + return 0; + } + + time_rule_obj = json_object_new_object(); + weekdays_array = json_object_new_array(); + if (!time_rule_obj || !weekdays_array) { + if (time_rule_obj) json_object_put(time_rule_obj); + if (weekdays_array) json_object_put(weekdays_array); + return 0; + } + + for (i = 0; i < weekday_end; i++) { + int weekday = -1; + if (parse_uint_token(parts[i], &weekday) && weekday >= 0 && weekday <= 6) { + json_object_array_add(weekdays_array, json_object_new_int(weekday)); + weekday_count++; + } + } + + if (weekday_count <= 0) { + json_object_put(weekdays_array); + json_object_put(time_rule_obj); + return 0; + } + + json_object_object_add(time_rule_obj, "weekdays", weekdays_array); + json_object_object_add(time_rule_obj, "flow_mb", json_object_new_int(flow_mb)); + json_object_array_add(time_rules_array, time_rule_obj); + return MACFILTER_TIME_MODE_FLOW; + } + + if (part_count >= 3) { + struct json_object *time_rule_obj = NULL; + struct json_object *weekdays_array = NULL; + char *start_time = NULL; + char *end_time = NULL; + + time_rule_obj = json_object_new_object(); + weekdays_array = json_object_new_array(); + if (!time_rule_obj || !weekdays_array) { + if (time_rule_obj) json_object_put(time_rule_obj); + if (weekdays_array) json_object_put(weekdays_array); + return 0; + } + + for (i = 0; i < part_count; i++) { + if (strchr(parts[i], ':') != NULL) { + if (!start_time) { + start_time = parts[i]; + } else if (!end_time) { + end_time = parts[i]; + break; + } + } else { + int weekday = -1; + if (parse_uint_token(parts[i], &weekday) && weekday >= 0 && weekday <= 6) { + json_object_array_add(weekdays_array, json_object_new_int(weekday)); + } + } + } + + if (!start_time || !end_time || json_object_array_length(weekdays_array) <= 0) { + json_object_put(weekdays_array); + json_object_put(time_rule_obj); + return 0; + } + + json_object_object_add(time_rule_obj, "start_time", json_object_new_string(start_time)); + json_object_object_add(time_rule_obj, "end_time", json_object_new_string(end_time)); + json_object_object_add(time_rule_obj, "weekdays", weekdays_array); + json_object_array_add(time_rules_array, time_rule_obj); + return MACFILTER_TIME_MODE_RANGE; + } + + return 0; +} + +static char *trim_space(char *str) +{ + char *end = NULL; + if (!str) { + return str; + } + while (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n') { + str++; + } + if (*str == '\0') { + return str; + } + end = str + strlen(str) - 1; + while (end > str && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) { + *end = '\0'; + end--; + } + return str; +} + +static int get_limit_max_value(int time_mode) +{ + if (time_mode == MACFILTER_TIME_MODE_DURATION) { + return 1440; + } + return 1048576; +} + +static int normalize_limit_str(const char *input_str, int time_mode, char *output_str, int output_len) +{ + int values[7] = {0, 0, 0, 0, 0, 0, 0}; + int order[7] = {1, 2, 3, 4, 5, 6, 0}; + int i; + int offset = 0; + int max_value = get_limit_max_value(time_mode); + char buf[512] = {0}; + char *token = NULL; + char *saveptr = NULL; + + if (!output_str || output_len <= 0) { + return -1; + } + output_str[0] = '\0'; + + if (input_str && input_str[0] != '\0') { + strncpy(buf, input_str, sizeof(buf) - 1); + token = strtok_r(buf, ",", &saveptr); + while (token) { + char pair_buf[64] = {0}; + char *pair = NULL; + char *day_str = NULL; + char *val_str = NULL; + char *sep = NULL; + int day = -1; + int value = 0; + + strncpy(pair_buf, token, sizeof(pair_buf) - 1); + pair = trim_space(pair_buf); + sep = strchr(pair, ':'); + if (sep) { + *sep = '\0'; + day_str = trim_space(pair); + val_str = trim_space(sep + 1); + if (parse_uint_token(day_str, &day) && + parse_uint_token(val_str, &value) && + day >= 0 && day <= 6) { + if (value < 0) { + value = 0; + } + if (value > max_value) { + value = max_value; + } + values[day] = value; + } + } + token = strtok_r(NULL, ",", &saveptr); + } + } + + for (i = 0; i < 7; i++) { + int day = order[i]; + int n = snprintf(output_str + offset, output_len - offset, "%d:%d", day, values[day]); + if (n < 0 || n >= output_len - offset) { + return -1; + } + offset += n; + if (i < 6) { + if (offset + 1 >= output_len) { + return -1; + } + output_str[offset++] = ','; + output_str[offset] = '\0'; + } + } + return 0; +} + +static int build_limit_str_from_time_rules(struct json_object *time_rules_obj, int time_mode, char *output_str, int output_len) +{ + int values[7] = {0, 0, 0, 0, 0, 0, 0}; + int order[7] = {1, 2, 3, 4, 5, 6, 0}; + int max_value = get_limit_max_value(time_mode); + int i; + int offset = 0; + int len = 0; + + if (!time_rules_obj || !json_object_is_type(time_rules_obj, json_type_array) || !output_str || output_len <= 0) { + return -1; + } + + len = json_object_array_length(time_rules_obj); + for (i = 0; i < len; i++) { + struct json_object *time_rule_obj = json_object_array_get_idx(time_rules_obj, i); + struct json_object *weekdays_obj = json_object_object_get(time_rule_obj, "weekdays"); + int value = 0; + int j; + int weekdays_len = 0; + if (!weekdays_obj || !json_object_is_type(weekdays_obj, json_type_array)) { + continue; + } + + if (time_mode == MACFILTER_TIME_MODE_DURATION) { + struct json_object *duration_obj = json_object_object_get(time_rule_obj, "duration_minutes"); + value = duration_obj ? json_object_get_int(duration_obj) : 0; + } else { + struct json_object *flow_obj = json_object_object_get(time_rule_obj, "flow_mb"); + value = flow_obj ? json_object_get_int(flow_obj) : 0; + } + if (value < 0) { + value = 0; + } + if (value > max_value) { + value = max_value; + } + + weekdays_len = json_object_array_length(weekdays_obj); + for (j = 0; j < weekdays_len; j++) { + int day = json_object_get_int(json_object_array_get_idx(weekdays_obj, j)); + if (day >= 0 && day <= 6) { + values[day] = value; + } + } + } + + output_str[0] = '\0'; + for (i = 0; i < 7; i++) { + int day = order[i]; + int n = snprintf(output_str + offset, output_len - offset, "%d:%d", day, values[day]); + if (n < 0 || n >= output_len - offset) { + return -1; + } + offset += n; + if (i < 6) { + if (offset + 1 >= output_len) { + return -1; + } + output_str[offset++] = ','; + output_str[offset] = '\0'; + } + } + return 0; +} + +static void append_limit_rules_to_array(struct json_object *time_rules_array, const char *limit_str, int time_mode) +{ + char buf[512] = {0}; + char *token = NULL; + char *saveptr = NULL; + + if (!time_rules_array || !limit_str || limit_str[0] == '\0') { + return; + } + + strncpy(buf, limit_str, sizeof(buf) - 1); + token = strtok_r(buf, ",", &saveptr); + while (token) { + char pair_buf[64] = {0}; + char *pair = NULL; + char *day_str = NULL; + char *val_str = NULL; + char *sep = NULL; + int day = -1; + int value = 0; + + strncpy(pair_buf, token, sizeof(pair_buf) - 1); + pair = trim_space(pair_buf); + sep = strchr(pair, ':'); + if (sep) { + struct json_object *rule_obj = NULL; + struct json_object *weekdays_array = NULL; + *sep = '\0'; + day_str = trim_space(pair); + val_str = trim_space(sep + 1); + if (parse_uint_token(day_str, &day) && + parse_uint_token(val_str, &value) && + day >= 0 && day <= 6) { + if (value < 0) { + value = 0; + } + rule_obj = json_object_new_object(); + weekdays_array = json_object_new_array(); + json_object_array_add(weekdays_array, json_object_new_int(day)); + json_object_object_add(rule_obj, "weekdays", weekdays_array); + if (time_mode == MACFILTER_TIME_MODE_DURATION) { + json_object_object_add(rule_obj, "duration_minutes", json_object_new_int(value)); + } else { + json_object_object_add(rule_obj, "flow_mb", json_object_new_int(value)); + } + json_object_array_add(time_rules_array, rule_obj); + } + } + token = strtok_r(NULL, ",", &saveptr); + } +} + +static int build_time_list_item_str(struct json_object *time_rule_obj, char *time_rule_str, int str_len) +{ + struct json_object *weekdays_obj = NULL; + struct json_object *start_time_obj = NULL; + struct json_object *end_time_obj = NULL; + int weekdays_len = 0; + int i; + int offset = 0; + int appended = 0; + + if (!time_rule_obj || !time_rule_str || str_len <= 0) { + return -1; + } + + weekdays_obj = json_object_object_get(time_rule_obj, "weekdays"); + start_time_obj = json_object_object_get(time_rule_obj, "start_time"); + end_time_obj = json_object_object_get(time_rule_obj, "end_time"); + if (!weekdays_obj || !start_time_obj || !end_time_obj || !json_object_is_type(weekdays_obj, json_type_array)) { + return -1; + } + + time_rule_str[0] = '\0'; + weekdays_len = json_object_array_length(weekdays_obj); + for (i = 0; i < weekdays_len; i++) { + int day = json_object_get_int(json_object_array_get_idx(weekdays_obj, i)); + int n; + if (day < 0 || day > 6) { + continue; + } + n = snprintf(time_rule_str + offset, str_len - offset, "%s%d", appended > 0 ? "," : "", day); + if (n < 0 || n >= str_len - offset) { + return -1; + } + offset += n; + appended++; + } + if (appended <= 0) { + return -1; + } + + if (offset + 1 >= str_len) { + return -1; + } + time_rule_str[offset++] = ','; + time_rule_str[offset] = '\0'; + + { + const char *start_time = json_object_get_string(start_time_obj); + const char *end_time = json_object_get_string(end_time_obj); + int n = snprintf(time_rule_str + offset, str_len - offset, "%s,%s", start_time ? start_time : "", end_time ? end_time : ""); + if (n < 0 || n >= str_len - offset) { + return -1; + } + offset += n; + } + + return 0; +} + + +struct json_object *fwx_api_get_mac_filter_rules(struct json_object *req_obj) { + int i; + struct json_object *data_obj = json_object_new_object(); + struct json_object *rules_array = json_object_new_array(); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int num = fwx_uci_get_list_num(uci_ctx, "macfilter", "rule"); + LOG_DEBUG("Found %d rules in macfilter\n", num); + + for (i = 0; i < num; i++) { + char buf[256]; + char name_str[128] = {0}; + char mode_str[16] = {0}; + char time_mode_str[16] = {0}; + int time_mode = MACFILTER_TIME_MODE_RANGE; + char user_mac_str[32] = {0}; + char user_name_str[128] = {0}; + char enabled_str[16] = {0}; + char id_str[32] = {0}; + + + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].id", i); + if (fwx_uci_get_value(uci_ctx, buf, id_str, sizeof(id_str)) != 0) { + LOG_ERROR("Failed to get id for rule[%d], skipping\n", i); + continue; + } + + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].name", i); + fwx_uci_get_value(uci_ctx, buf, name_str, sizeof(name_str)); + + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].mode", i); + if (fwx_uci_get_value(uci_ctx, buf, mode_str, sizeof(mode_str)) != 0) { + strcpy(mode_str, "1"); + } + + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_mode", i); + if (fwx_uci_get_value(uci_ctx, buf, time_mode_str, sizeof(time_mode_str)) != 0) { + strcpy(time_mode_str, "1"); + } + + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].user_mac", i); + fwx_uci_get_value(uci_ctx, buf, user_mac_str, sizeof(user_mac_str)); + + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].user_name", i); + fwx_uci_get_value(uci_ctx, buf, user_name_str, sizeof(user_name_str)); + + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].enabled", i); + if (fwx_uci_get_value(uci_ctx, buf, enabled_str, sizeof(enabled_str)) != 0) { + strcpy(enabled_str, "1"); + } + + struct json_object *rule_obj = json_object_new_object(); + struct json_object *time_rules_array = json_object_new_array(); + struct json_object *time_list_array = json_object_new_array(); + char time_list_buf[2048] = {0}; + char limit_buf[512] = {0}; + if (!rule_obj || !time_rules_array || !time_list_array) { + LOG_ERROR("Failed to create rule_obj for rule[%d]\n", i); + if (rule_obj) json_object_put(rule_obj); + if (time_rules_array) json_object_put(time_rules_array); + if (time_list_array) json_object_put(time_list_array); + continue; + } + + json_object_object_add(rule_obj, "id", json_object_new_int(atoi(id_str))); + json_object_object_add(rule_obj, "name", json_object_new_string(name_str)); + json_object_object_add(rule_obj, "mode", json_object_new_int(atoi(mode_str))); + time_mode = normalize_macfilter_time_mode(atoi(time_mode_str)); + json_object_object_add(rule_obj, "user_mac", json_object_new_string(user_mac_str)); + json_object_object_add(rule_obj, "user_name", json_object_new_string(user_name_str)); + json_object_object_add(rule_obj, "enabled", json_object_new_int(atoi(enabled_str))); + + if (time_mode == MACFILTER_TIME_MODE_RANGE) { + int loaded_from_new = 0; + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_list", i); + if (fwx_uci_get_list_value(uci_ctx, buf, time_list_buf, sizeof(time_list_buf), " ") == 0) { + char *p = strtok(time_list_buf, " "); + loaded_from_new = 1; + while (p) { + json_object_array_add(time_list_array, json_object_new_string(p)); + append_time_rule_obj(time_rules_array, p, MACFILTER_TIME_MODE_RANGE); + p = strtok(NULL, " "); + } + } + if (!loaded_from_new) { + char old_time_rule_buf[2048] = {0}; + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_rule", i); + if (fwx_uci_get_list_value(uci_ctx, buf, old_time_rule_buf, sizeof(old_time_rule_buf), " ") == 0) { + char *p = strtok(old_time_rule_buf, " "); + while (p) { + append_time_rule_obj(time_rules_array, p, MACFILTER_TIME_MODE_RANGE); + if (strchr(p, ';') == NULL) { + json_object_array_add(time_list_array, json_object_new_string(p)); + } + p = strtok(NULL, " "); + } + } + } + json_object_object_add(rule_obj, "time_limit", json_object_new_string("")); + json_object_object_add(rule_obj, "flow_limit", json_object_new_string("")); + } else if (time_mode == MACFILTER_TIME_MODE_DURATION) { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_limit", i); + if (fwx_uci_get_value(uci_ctx, buf, limit_buf, sizeof(limit_buf)) != 0 || limit_buf[0] == '\0') { + char old_time_rule_buf[2048] = {0}; + char normalized_limit[512] = {0}; + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_rule", i); + if (fwx_uci_get_list_value(uci_ctx, buf, old_time_rule_buf, sizeof(old_time_rule_buf), " ") == 0) { + char *p = strtok(old_time_rule_buf, " "); + while (p) { + append_time_rule_obj(time_rules_array, p, MACFILTER_TIME_MODE_DURATION); + p = strtok(NULL, " "); + } + } + if (build_limit_str_from_time_rules(time_rules_array, MACFILTER_TIME_MODE_DURATION, normalized_limit, sizeof(normalized_limit)) == 0) { + strncpy(limit_buf, normalized_limit, sizeof(limit_buf) - 1); + } + } else { + char normalized_limit[512] = {0}; + if (normalize_limit_str(limit_buf, MACFILTER_TIME_MODE_DURATION, normalized_limit, sizeof(normalized_limit)) == 0) { + strncpy(limit_buf, normalized_limit, sizeof(limit_buf) - 1); + } + append_limit_rules_to_array(time_rules_array, limit_buf, MACFILTER_TIME_MODE_DURATION); + } + json_object_object_add(rule_obj, "time_limit", json_object_new_string(limit_buf)); + json_object_object_add(rule_obj, "flow_limit", json_object_new_string("")); + } else { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].flow_limit", i); + if (fwx_uci_get_value(uci_ctx, buf, limit_buf, sizeof(limit_buf)) != 0 || limit_buf[0] == '\0') { + char old_time_rule_buf[2048] = {0}; + char normalized_limit[512] = {0}; + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_rule", i); + if (fwx_uci_get_list_value(uci_ctx, buf, old_time_rule_buf, sizeof(old_time_rule_buf), " ") == 0) { + char *p = strtok(old_time_rule_buf, " "); + while (p) { + append_time_rule_obj(time_rules_array, p, MACFILTER_TIME_MODE_FLOW); + p = strtok(NULL, " "); + } + } + if (build_limit_str_from_time_rules(time_rules_array, MACFILTER_TIME_MODE_FLOW, normalized_limit, sizeof(normalized_limit)) == 0) { + strncpy(limit_buf, normalized_limit, sizeof(limit_buf) - 1); + } + } else { + char normalized_limit[512] = {0}; + if (normalize_limit_str(limit_buf, MACFILTER_TIME_MODE_FLOW, normalized_limit, sizeof(normalized_limit)) == 0) { + strncpy(limit_buf, normalized_limit, sizeof(limit_buf) - 1); + } + append_limit_rules_to_array(time_rules_array, limit_buf, MACFILTER_TIME_MODE_FLOW); + } + json_object_object_add(rule_obj, "time_limit", json_object_new_string("")); + json_object_object_add(rule_obj, "flow_limit", json_object_new_string(limit_buf)); + } + + json_object_object_add(rule_obj, "time_mode", json_object_new_int(time_mode)); + json_object_object_add(rule_obj, "time_rules", time_rules_array); + json_object_object_add(rule_obj, "time_list", time_list_array); + + json_object_array_add(rules_array, rule_obj); + LOG_DEBUG("Successfully loaded macfilter rule[%d]: id=%s, name=%s\n", i, id_str, name_str); + } + + json_object_object_add(data_obj, "list", rules_array); + uci_free_context(uci_ctx); + + LOG_DEBUG("Returning %d macfilter rules\n", json_object_array_length(rules_array)); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_add_mac_filter_rule(struct json_object *req_obj) { + struct json_object *name_obj = json_object_object_get(req_obj, "name"); + struct json_object *mode_obj = json_object_object_get(req_obj, "mode"); + struct json_object *time_mode_obj = json_object_object_get(req_obj, "time_mode"); + struct json_object *user_mac_obj = json_object_object_get(req_obj, "user_mac"); + struct json_object *user_name_obj = json_object_object_get(req_obj, "user_name"); + struct json_object *enabled_obj = json_object_object_get(req_obj, "enabled"); + struct json_object *time_rules_obj = json_object_object_get(req_obj, "time_rules"); + struct json_object *time_list_obj = json_object_object_get(req_obj, "time_list"); + struct json_object *time_limit_obj = json_object_object_get(req_obj, "time_limit"); + struct json_object *flow_limit_obj = json_object_object_get(req_obj, "flow_limit"); + int i; + int time_mode = MACFILTER_TIME_MODE_RANGE; + + if (!name_obj || !mode_obj) { + LOG_ERROR("Missing required fields\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (time_mode_obj) { + time_mode = json_object_get_int(time_mode_obj); + } + time_mode = normalize_macfilter_time_mode(time_mode); + + if (time_mode == MACFILTER_TIME_MODE_RANGE) { + int has_time_list = time_list_obj && json_object_is_type(time_list_obj, json_type_array) && json_object_array_length(time_list_obj) > 0; + int has_time_rules = time_rules_obj && json_object_is_type(time_rules_obj, json_type_array) && json_object_array_length(time_rules_obj) > 0; + if (!has_time_list && !has_time_rules) { + LOG_ERROR("Missing required time list for range mode\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } else if (time_mode == MACFILTER_TIME_MODE_DURATION) { + int has_time_limit = time_limit_obj && json_object_is_type(time_limit_obj, json_type_string); + int has_time_rules = time_rules_obj && json_object_is_type(time_rules_obj, json_type_array) && json_object_array_length(time_rules_obj) > 0; + if (!has_time_limit && !has_time_rules) { + LOG_ERROR("Missing required time_limit for duration mode\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } else { + int has_flow_limit = flow_limit_obj && json_object_is_type(flow_limit_obj, json_type_string); + int has_time_rules = time_rules_obj && json_object_is_type(time_rules_obj, json_type_array) && json_object_array_length(time_rules_obj) > 0; + if (!has_flow_limit && !has_time_rules) { + LOG_ERROR("Missing required flow_limit for flow mode\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + int rule_id = (int)time(NULL); + + + fwx_uci_add_section(uci_ctx, "macfilter", "rule"); + + char buf[256]; + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].id"); + char id_str[32]; + snprintf(id_str, sizeof(id_str), "%d", rule_id); + fwx_uci_set_value(uci_ctx, buf, id_str); + + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].name"); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(name_obj)); + + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].mode"); + char mode_str[16]; + snprintf(mode_str, sizeof(mode_str), "%d", json_object_get_int(mode_obj)); + fwx_uci_set_value(uci_ctx, buf, mode_str); + + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].time_mode"); + char time_mode_str[16]; + snprintf(time_mode_str, sizeof(time_mode_str), "%d", time_mode); + fwx_uci_set_value(uci_ctx, buf, time_mode_str); + + if (user_mac_obj) { + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].user_mac"); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_mac_obj)); + } + + if (user_name_obj) { + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].user_name"); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_name_obj)); + } + + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].enabled"); + char enabled_str[16]; + int enabled = enabled_obj ? json_object_get_int(enabled_obj) : 1; + snprintf(enabled_str, sizeof(enabled_str), "%d", enabled); + fwx_uci_set_value(uci_ctx, buf, enabled_str); + + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].time_rule"); + fwx_uci_delete(uci_ctx, buf); + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].time_list"); + fwx_uci_delete(uci_ctx, buf); + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].time_limit"); + fwx_uci_delete(uci_ctx, buf); + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].flow_limit"); + fwx_uci_delete(uci_ctx, buf); + + if (time_mode == MACFILTER_TIME_MODE_RANGE) { + if (time_list_obj && json_object_is_type(time_list_obj, json_type_array) && json_object_array_length(time_list_obj) > 0) { + int time_list_len = json_object_array_length(time_list_obj); + for (i = 0; i < time_list_len; i++) { + struct json_object *time_item = json_object_array_get_idx(time_list_obj, i); + const char *time_rule_str = time_item ? json_object_get_string(time_item) : NULL; + if (!time_rule_str || time_rule_str[0] == '\0') { + continue; + } + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].time_list"); + fwx_uci_add_list(uci_ctx, buf, time_rule_str); + } + } else if (time_rules_obj && json_object_is_type(time_rules_obj, json_type_array)) { + int time_rules_len = json_object_array_length(time_rules_obj); + for (i = 0; i < time_rules_len; i++) { + struct json_object *time_rule_obj = json_object_array_get_idx(time_rules_obj, i); + char time_rule_str[256] = {0}; + if (build_time_list_item_str(time_rule_obj, time_rule_str, sizeof(time_rule_str)) != 0) { + continue; + } + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].time_list"); + fwx_uci_add_list(uci_ctx, buf, time_rule_str); + } + } + } else if (time_mode == MACFILTER_TIME_MODE_DURATION) { + char limit_str[512] = {0}; + if (time_limit_obj && json_object_is_type(time_limit_obj, json_type_string)) { + normalize_limit_str(json_object_get_string(time_limit_obj), MACFILTER_TIME_MODE_DURATION, limit_str, sizeof(limit_str)); + } else if (time_rules_obj && json_object_is_type(time_rules_obj, json_type_array)) { + build_limit_str_from_time_rules(time_rules_obj, MACFILTER_TIME_MODE_DURATION, limit_str, sizeof(limit_str)); + } + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].time_limit"); + fwx_uci_set_value(uci_ctx, buf, limit_str); + } else { + char limit_str[512] = {0}; + if (flow_limit_obj && json_object_is_type(flow_limit_obj, json_type_string)) { + normalize_limit_str(json_object_get_string(flow_limit_obj), MACFILTER_TIME_MODE_FLOW, limit_str, sizeof(limit_str)); + } else if (time_rules_obj && json_object_is_type(time_rules_obj, json_type_array)) { + build_limit_str_from_time_rules(time_rules_obj, MACFILTER_TIME_MODE_FLOW, limit_str, sizeof(limit_str)); + } + snprintf(buf, sizeof(buf), "macfilter.@rule[-1].flow_limit"); + fwx_uci_set_value(uci_ctx, buf, limit_str); + } + + fwx_uci_commit(uci_ctx, "macfilter"); + uci_free_context(uci_ctx); + + + set_state_file(MACFILTER_RULES_STATE_FILE); + + LOG_DEBUG("Added macfilter rule: id=%d, name=%s\n", rule_id, json_object_get_string(name_obj)); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_update_mac_filter_rule(struct json_object *req_obj) { + int i; + int time_mode = MACFILTER_TIME_MODE_RANGE; + int has_time_payload = 0; + struct json_object *id_obj = json_object_object_get(req_obj, "id"); + if (!id_obj) { + LOG_ERROR("Missing id field\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int rule_id = json_object_get_int(id_obj); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int index = find_mac_filter_rule_index_by_id(uci_ctx, rule_id); + if (index < 0) { + LOG_ERROR("Rule not found: id=%d\n", rule_id); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char buf[256]; + + + struct json_object *name_obj = json_object_object_get(req_obj, "name"); + if (name_obj) { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].name", index); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(name_obj)); + } + + struct json_object *mode_obj = json_object_object_get(req_obj, "mode"); + if (mode_obj) { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].mode", index); + char mode_str[16]; + snprintf(mode_str, sizeof(mode_str), "%d", json_object_get_int(mode_obj)); + fwx_uci_set_value(uci_ctx, buf, mode_str); + } + + char time_mode_str[16] = {0}; + struct json_object *time_mode_obj = json_object_object_get(req_obj, "time_mode"); + struct json_object *time_rules_obj = json_object_object_get(req_obj, "time_rules"); + struct json_object *time_list_obj = json_object_object_get(req_obj, "time_list"); + struct json_object *time_limit_obj = json_object_object_get(req_obj, "time_limit"); + struct json_object *flow_limit_obj = json_object_object_get(req_obj, "flow_limit"); + if (time_mode_obj || time_rules_obj || time_list_obj || time_limit_obj || flow_limit_obj) { + has_time_payload = 1; + } + + if (time_mode_obj) { + time_mode = normalize_macfilter_time_mode(json_object_get_int(time_mode_obj)); + } else { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_mode", index); + if (fwx_uci_get_value(uci_ctx, buf, time_mode_str, sizeof(time_mode_str)) == 0) { + time_mode = atoi(time_mode_str); + } + time_mode = normalize_macfilter_time_mode(time_mode); + } + + if (has_time_payload) { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_mode", index); + snprintf(time_mode_str, sizeof(time_mode_str), "%d", time_mode); + fwx_uci_set_value(uci_ctx, buf, time_mode_str); + } + + struct json_object *user_mac_obj = json_object_object_get(req_obj, "user_mac"); + if (user_mac_obj) { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].user_mac", index); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_mac_obj)); + } + + struct json_object *user_name_obj = json_object_object_get(req_obj, "user_name"); + if (user_name_obj) { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].user_name", index); + fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_name_obj)); + } + + struct json_object *enabled_obj = json_object_object_get(req_obj, "enabled"); + if (enabled_obj) { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].enabled", index); + char enabled_str[16]; + snprintf(enabled_str, sizeof(enabled_str), "%d", json_object_get_int(enabled_obj)); + fwx_uci_set_value(uci_ctx, buf, enabled_str); + } + + + if (has_time_payload) { + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_rule", index); + fwx_uci_delete(uci_ctx, buf); + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_list", index); + fwx_uci_delete(uci_ctx, buf); + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_limit", index); + fwx_uci_delete(uci_ctx, buf); + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].flow_limit", index); + fwx_uci_delete(uci_ctx, buf); + + if (time_mode == MACFILTER_TIME_MODE_RANGE) { + int has_time_list = time_list_obj && json_object_is_type(time_list_obj, json_type_array) && json_object_array_length(time_list_obj) > 0; + int has_time_rules = time_rules_obj && json_object_is_type(time_rules_obj, json_type_array) && json_object_array_length(time_rules_obj) > 0; + if (!has_time_list && !has_time_rules) { + LOG_ERROR("Missing required time list for range mode on update\n"); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + if (has_time_list) { + int time_list_len = json_object_array_length(time_list_obj); + for (i = 0; i < time_list_len; i++) { + struct json_object *time_item = json_object_array_get_idx(time_list_obj, i); + const char *time_rule_str = time_item ? json_object_get_string(time_item) : NULL; + if (!time_rule_str || time_rule_str[0] == '\0') { + continue; + } + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_list", index); + fwx_uci_add_list(uci_ctx, buf, time_rule_str); + } + } else { + int time_rules_len = json_object_array_length(time_rules_obj); + for (i = 0; i < time_rules_len; i++) { + struct json_object *time_rule_obj = json_object_array_get_idx(time_rules_obj, i); + char time_rule_str[256] = {0}; + if (build_time_list_item_str(time_rule_obj, time_rule_str, sizeof(time_rule_str)) != 0) { + continue; + } + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_list", index); + fwx_uci_add_list(uci_ctx, buf, time_rule_str); + } + } + } else if (time_mode == MACFILTER_TIME_MODE_DURATION) { + char limit_str[512] = {0}; + int has_time_limit = time_limit_obj && json_object_is_type(time_limit_obj, json_type_string); + int has_time_rules = time_rules_obj && json_object_is_type(time_rules_obj, json_type_array) && json_object_array_length(time_rules_obj) > 0; + if (!has_time_limit && !has_time_rules) { + LOG_ERROR("Missing time_limit for duration mode on update\n"); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + if (has_time_limit) { + normalize_limit_str(json_object_get_string(time_limit_obj), MACFILTER_TIME_MODE_DURATION, limit_str, sizeof(limit_str)); + } else { + build_limit_str_from_time_rules(time_rules_obj, MACFILTER_TIME_MODE_DURATION, limit_str, sizeof(limit_str)); + } + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].time_limit", index); + fwx_uci_set_value(uci_ctx, buf, limit_str); + } else { + char limit_str[512] = {0}; + int has_flow_limit = flow_limit_obj && json_object_is_type(flow_limit_obj, json_type_string); + int has_time_rules = time_rules_obj && json_object_is_type(time_rules_obj, json_type_array) && json_object_array_length(time_rules_obj) > 0; + if (!has_flow_limit && !has_time_rules) { + LOG_ERROR("Missing flow_limit for flow mode on update\n"); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + if (has_flow_limit) { + normalize_limit_str(json_object_get_string(flow_limit_obj), MACFILTER_TIME_MODE_FLOW, limit_str, sizeof(limit_str)); + } else { + build_limit_str_from_time_rules(time_rules_obj, MACFILTER_TIME_MODE_FLOW, limit_str, sizeof(limit_str)); + } + snprintf(buf, sizeof(buf), "macfilter.@rule[%d].flow_limit", index); + fwx_uci_set_value(uci_ctx, buf, limit_str); + } + } + + fwx_uci_commit(uci_ctx, "macfilter"); + uci_free_context(uci_ctx); + + + set_state_file(MACFILTER_RULES_STATE_FILE); + + LOG_DEBUG("Updated macfilter rule: id=%d\n", rule_id); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_delete_mac_filter_rule(struct json_object *req_obj) { + struct json_object *id_obj = json_object_object_get(req_obj, "id"); + if (!id_obj) { + LOG_ERROR("Missing id field\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int rule_id = json_object_get_int(id_obj); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int index = find_mac_filter_rule_index_by_id(uci_ctx, rule_id); + if (index < 0) { + LOG_ERROR("Rule not found: id=%d\n", rule_id); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char buf[128]; + snprintf(buf, sizeof(buf), "macfilter.@rule[%d]", index); + fwx_uci_delete(uci_ctx, buf); + + fwx_uci_commit(uci_ctx, "macfilter"); + uci_free_context(uci_ctx); + + + set_state_file(MACFILTER_RULES_STATE_FILE); + + LOG_DEBUG("Deleted macfilter rule: id=%d\n", rule_id); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_get_mac_filter_whitelist(struct json_object *req_obj) { + int i; + struct json_object *data_obj = json_object_new_object(); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *mac_array = json_object_new_array(); + char mac_str[128] = {0}; + int num = fwx_uci_get_list_num(uci_ctx, "macfilter_whitelist", "whitelist_mac"); + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "macfilter_whitelist.@whitelist_mac[%d].mac", i, mac_str, sizeof(mac_str)); + + struct json_object *mac_obj = json_object_new_object(); + json_object_object_add(mac_obj, "mac", json_object_new_string(mac_str)); + client_node_t *dev = find_client_node(mac_str); + if (dev) { + json_object_object_add(mac_obj, "nickname", json_object_new_string(dev->nickname)); + json_object_object_add(mac_obj, "hostname", json_object_new_string(dev->hostname)); + } else { + json_object_object_add(mac_obj, "nickname", json_object_new_string("")); + json_object_object_add(mac_obj, "hostname", json_object_new_string("")); + } + json_object_array_add(mac_array, mac_obj); + } + + json_object_object_add(data_obj, "list", mac_array); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_del_mac_filter_whitelist(struct json_object *req_obj) { + int i; + LOG_DEBUG("fwx_api_del_mac_filter_whitelist\n"); + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + LOG_ERROR("mac_obj is NULL\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + LOG_DEBUG("mac: %s\n", json_object_get_string(mac_obj)); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + char mac_str[128] = {0}; + int num = fwx_uci_get_list_num(uci_ctx, "macfilter_whitelist", "whitelist_mac"); + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "macfilter_whitelist.@whitelist_mac[%d].mac", i, mac_str, sizeof(mac_str)); + if (strcmp(mac_str, json_object_get_string(mac_obj)) == 0) { + LOG_DEBUG("delete macfilter_whitelist_mac[%d]\n", i); + char buf[128] = {0}; + sprintf(buf, "macfilter_whitelist.@whitelist_mac[%d]", i); + fwx_uci_delete(uci_ctx, buf); + break; + } + } + + fwx_uci_commit(uci_ctx, "macfilter_whitelist"); + uci_free_context(uci_ctx); + + + set_state_file(MACFILTER_WHITELIST_STATE_FILE); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_add_mac_filter_whitelist(struct json_object *req_obj) { + int i, j; + LOG_DEBUG("fwx_api_add_mac_filter_whitelist\n"); + struct json_object *mac_list_obj = json_object_object_get(req_obj, "mac_list"); + if (!mac_list_obj) { + LOG_ERROR("mac_list_obj is NULL\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int mac_list_len = json_object_array_length(mac_list_obj); + for (i = 0; i < mac_list_len; i++) { + struct json_object *mac_item = json_object_array_get_idx(mac_list_obj, i); + const char *mac = json_object_get_string(mac_item); + if (!mac || strlen(mac) == 0) { + continue; + } + + + char mac_str[128] = {0}; + int num = fwx_uci_get_list_num(uci_ctx, "macfilter_whitelist", "whitelist_mac"); + int exists = 0; + for (j = 0; j < num; j++) { + fwx_uci_get_array_value(uci_ctx, "macfilter_whitelist.@whitelist_mac[%d].mac", j, mac_str, sizeof(mac_str)); + if (strcmp(mac_str, mac) == 0) { + exists = 1; + break; + } + } + + if (!exists) { + fwx_uci_add_section(uci_ctx, "macfilter_whitelist", "whitelist_mac"); + char buf[128]; + snprintf(buf, sizeof(buf), "macfilter_whitelist.@whitelist_mac[-1].mac"); + fwx_uci_set_value(uci_ctx, buf, mac); + LOG_DEBUG("Added macfilter whitelist: %s\n", mac); + } + } + + fwx_uci_commit(uci_ctx, "macfilter_whitelist"); + uci_free_context(uci_ctx); + + + set_state_file(MACFILTER_WHITELIST_STATE_FILE); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_get_mac_filter_adv(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int enable = fwx_uci_get_int_value(uci_ctx, "fwx.macfilter.enable"); + json_object_object_add(data_obj, "enable", json_object_new_int(enable)); + uci_free_context(uci_ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_set_mac_filter_adv(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *enable_obj = json_object_object_get(req_obj, "enable"); + if (!enable_obj) { + LOG_ERROR("Missing enable parameter\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int enable_value = json_object_get_int(enable_obj); + fwx_uci_set_int_value(uci_ctx, "fwx.macfilter.enable", enable_value); + fwx_uci_commit(uci_ctx, "fwx"); + + set_state_file(MACFILTER_RULES_STATE_FILE); + uci_free_context(uci_ctx); + LOG_DEBUG("Set macfilter advanced settings\n"); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} diff --git a/open-app-filter/src/fwx_mac_filter.h b/open-app-filter/src/fwx_mac_filter.h new file mode 100644 index 00000000..d29a18d6 --- /dev/null +++ b/open-app-filter/src/fwx_mac_filter.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_MAC_FILTER_H__ +#define __FWX_MAC_FILTER_H__ + +#include "fwx.h" + + +struct json_object *fwx_api_get_mac_filter_rules(struct json_object *req_obj); +struct json_object *fwx_api_add_mac_filter_rule(struct json_object *req_obj); +struct json_object *fwx_api_update_mac_filter_rule(struct json_object *req_obj); +struct json_object *fwx_api_delete_mac_filter_rule(struct json_object *req_obj); + + +struct json_object *fwx_api_get_mac_filter_whitelist(struct json_object *req_obj); +struct json_object *fwx_api_add_mac_filter_whitelist(struct json_object *req_obj); +struct json_object *fwx_api_del_mac_filter_whitelist(struct json_object *req_obj); + + +struct json_object *fwx_api_get_mac_filter_adv(struct json_object *req_obj); +struct json_object *fwx_api_set_mac_filter_adv(struct json_object *req_obj); + +#endif + diff --git a/open-app-filter/src/fwx_netlink.c b/open-app-filter/src/fwx_netlink.c new file mode 100644 index 00000000..26db2948 --- /dev/null +++ b/open-app-filter/src/fwx_netlink.c @@ -0,0 +1,348 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fwx_user.h" +#include "fwx_netlink.h" +#include "fwx.h" +#define MAX_NL_RCV_BUF_SIZE 4096 + +#define REPORT_INTERVAL_SECS 60 + + +extern traffic_stat_t g_global_hourly_traffic[HOURS_PER_DAY]; +extern u_int32_t g_global_traffic_date; + +void fwx_netlink_handler(struct uloop_fd *u, unsigned int ev) +{ + int ret; + int i; + char buf[MAX_NL_RCV_BUF_SIZE]; + struct sockaddr_nl nladdr; + struct iovec iov = {buf, sizeof(buf)}; + struct nlmsghdr *h; + int type; + int id; + const char *mac = NULL; + + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + do + { + ret = recvmsg(u->fd, &msg, 0); + } while ((-1 == ret) && (EINTR == errno)); + + if (ret < 0) + { + printf("recv msg error\n"); + return; + } + else if (0 == ret) + { + return; + } + + h = (struct nlmsghdr *)buf; + char *kmsg = (char *)NLMSG_DATA(h); + struct fwx_nl_msg_hdr *af_hdr = (struct fwx_nl_msg_hdr *)kmsg; + if (af_hdr->magic != 0xa0b0c0d0) + { + printf("magic error %x\n", af_hdr->magic); + return; + } + + if (af_hdr->len <= 0 || af_hdr->len >= MAX_FWX_NETLINK_MSG_LEN) + { + printf("data len error\n"); + return; + } + + char *kdata = kmsg + sizeof(struct fwx_nl_msg_hdr); + struct json_object *root = json_tokener_parse(kdata); + if (!root) + { + LOG_ERROR("parse json failed:%s", kdata); + return; + } + + struct json_object *mac_obj = json_object_object_get(root, "mac"); + + if (!mac_obj) + { + printf("parse mac obj failed\n"); + json_object_put(root); + return; + } + + mac = json_object_get_string(mac_obj); + + client_node_t *node = find_client_node(mac); + + if (!node) + { + node = add_client_node(mac); + if (!node) + { + printf("add dev node failed\n"); + json_object_put(root); + return; + } + } + + struct json_object *ip_obj = json_object_object_get(root, "ip"); + if (ip_obj) + strncpy(node->ip, json_object_get_string(ip_obj), sizeof(node->ip)); + + struct json_object *ipv6_obj = json_object_object_get(root, "ipv6"); + if (ipv6_obj) { + const char *ipv6_str = json_object_get_string(ipv6_obj); + if (ipv6_str && strlen(ipv6_str) > 0) { + strncpy(node->ipv6, ipv6_str, sizeof(node->ipv6) - 1); + node->ipv6[sizeof(node->ipv6) - 1] = '\0'; + LOG_DEBUG("fwx_netlink: received ipv6=%s for %s\n", node->ipv6, mac); + } else { + node->ipv6[0] = '\0'; + } + } else { + node->ipv6[0] = '\0'; + } + + struct json_object *active_obj = json_object_object_get(root, "active"); + int prev_active = node->active; + if (active_obj) { + node->active = json_object_get_int(active_obj); + LOG_DEBUG("fwx_netlink: received active=%d for %s\n", node->active, mac); + } + + + struct json_object *up_flow_obj = json_object_object_get(root, "up_flow"); + struct json_object *down_flow_obj = json_object_object_get(root, "down_flow"); + unsigned long long total_up_bytes = 0; + unsigned long long total_down_bytes = 0; + + if (up_flow_obj) { + + total_up_bytes = (unsigned long long)json_object_get_int64(up_flow_obj) * 1024; + } + if (down_flow_obj) { + + total_down_bytes = (unsigned long long)json_object_get_int64(down_flow_obj) * 1024; + } + + LOG_DEBUG("fwx_netlink: received flow data for %s: up_flow=%llu KB (%llu bytes), down_flow=%llu KB (%llu bytes)\n", + mac, total_up_bytes / 1024, total_up_bytes, total_down_bytes / 1024, total_down_bytes); + + struct timeval cur_time; + gettimeofday(&cur_time, NULL); + time_t cur_time_t = cur_time.tv_sec; + u_int32_t today_start = get_today_start_timestamp(); + if ((u_int32_t)cur_time.tv_sec >= today_start && ((u_int32_t)cur_time.tv_sec - today_start) < 120) { + json_object_put(root); + return; + } + struct tm *tm_info = localtime(&cur_time_t); + int hour = -1; + if (tm_info) { + hour = tm_info->tm_hour; + } + + daily_hourly_stat_t *today_stat = NULL; + if (hour >= 0 && hour < HOURS_PER_DAY) { + today_stat = get_today_stat(node); + + if (node->online == 1) { + if (today_stat) { + today_stat->hourly_online_time[hour] += REPORT_INTERVAL_SECS; + LOG_DEBUG("fwx_netlink: updated hourly_online_time[%d] for %s: +%d seconds\n", + hour, mac, REPORT_INTERVAL_SECS); + } + } + + if (node->active == 1) { + if (today_stat) { + today_stat->hourly_active_time[hour] += REPORT_INTERVAL_SECS; + LOG_DEBUG("fwx_netlink: updated hourly_active_time[%d] for %s: +%d seconds\n", + hour, mac, REPORT_INTERVAL_SECS); + } + } + update_online_session_activity(node, REPORT_INTERVAL_SECS, node->active == 1 ? REPORT_INTERVAL_SECS : 0); + } + + + if (hour >= 0 && hour < HOURS_PER_DAY && (total_up_bytes > 0 || total_down_bytes > 0)) { + if (today_stat) { + + today_stat->hourly_traffic[hour].up_bytes += total_up_bytes; + today_stat->hourly_traffic[hour].down_bytes += total_down_bytes; + LOG_DEBUG("fwx_netlink: updated hourly_traffic[%d] for %s: up_bytes=%llu, down_bytes=%llu\n", + hour, mac, today_stat->hourly_traffic[hour].up_bytes, today_stat->hourly_traffic[hour].down_bytes); + } + + + u_int32_t today = get_today_start_timestamp(); + if (g_global_traffic_date != today) { + memset(g_global_hourly_traffic, 0, sizeof(g_global_hourly_traffic)); + g_global_traffic_date = today; + } + g_global_hourly_traffic[hour].up_bytes += total_up_bytes; + g_global_hourly_traffic[hour].down_bytes += total_down_bytes; + } + update_online_session_flow(node, total_up_bytes, total_down_bytes); + + struct json_object *visit_array = json_object_object_get(root, "visit_info"); + if (!visit_array) + { + json_object_put(root); + return; + } + + for (i = 0; i < json_object_array_length(visit_array); i++) + { + struct json_object *visit_obj = json_object_array_get_idx(visit_array, i); + struct json_object *appid_obj = json_object_object_get(visit_obj, "appid"); + struct json_object *action_obj = json_object_object_get(visit_obj, "latest_action"); + + int appid = json_object_get_int(appid_obj); + int action = json_object_get_int(action_obj); + + type = appid / 1000; + id = appid % 1000; + if (id <= 0 || type <= 0) + continue; + update_online_session_recent_app(node, appid); + + + visit_stat_t *stat_node = NULL; + int found_stat = 0; + list_for_each_entry(stat_node, &node->stat_list, list) { + if (stat_node->appid == appid) { + stat_node->total_time += REPORT_INTERVAL_SECS; + found_stat = 1; + break; + } + } + + + if (!found_stat) { + stat_node = (visit_stat_t *)calloc(1, sizeof(visit_stat_t)); + if (stat_node) { + stat_node->appid = appid; + stat_node->total_time = REPORT_INTERVAL_SECS; + INIT_LIST_HEAD(&stat_node->list); + list_add(&stat_node->list, &node->stat_list); + } + } + + + update_global_app_type_stats(appid, REPORT_INTERVAL_SECS); + + + + visit_info_t *p = NULL; + int found = 0; + int cur_time_sec = cur_time.tv_sec; + + list_for_each_entry(p, &node->online_visit, visit) { + if (p->appid == appid && p->action == action) { + p->latest_time = cur_time_sec; + found = 1; + break; + } + } + + + if (!found) { + p = (visit_info_t *)calloc(1, sizeof(visit_info_t)); + if (!p) + continue; + p->appid = appid; + p->first_time = cur_time_sec; + p->latest_time = cur_time_sec; + p->action = action; + INIT_LIST_HEAD(&p->visit); + + add_visit_info_node(&node->online_visit, p); + + } + } + json_object_put(root); +} + +#define MAX_NL_MSG_LEN 1024 +int fwx_nl_send_msg_to_kernel(int fd, void *msg, int len) +{ + struct sockaddr_nl saddr, daddr; + memset(&daddr, 0, sizeof(daddr)); + daddr.nl_family = AF_NETLINK; + daddr.nl_pid = 0; // to kernel + daddr.nl_groups = 0; + int ret = 0; + struct nlmsghdr *nlh = NULL; + nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_NL_MSG_LEN)); + nlh->nlmsg_len = NLMSG_SPACE(MAX_NL_MSG_LEN); + nlh->nlmsg_flags = 0; + nlh->nlmsg_type = 0; + nlh->nlmsg_seq = 0; + nlh->nlmsg_pid = DEFAULT_FWX_NL_PID; + + char msg_buf[MAX_NL_MSG_LEN] = {0}; + struct fwx_nl_msg_hdr *hdr = (struct fwx_nl_msg_hdr *)msg_buf; + hdr->magic = 0xa0b0c0d0; + hdr->len = len; + char *p_data = msg_buf + sizeof(struct fwx_nl_msg_hdr); + memcpy(p_data, msg, len); + + memcpy(NLMSG_DATA(nlh), msg_buf, len + sizeof(struct fwx_nl_msg_hdr)); + + ret = sendto(fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl)); + free(nlh); + if (!ret) + { + perror("sendto error\n"); + return -1; + } + + return 0; +} + +int fwx_netlink_init(void) +{ + int fd; + struct sockaddr_nl nls; + fd = socket(AF_NETLINK, SOCK_RAW, FWX_NETLINK_ID); + if (fd < 0) + { + LOG_DEBUG("Connect netlink %d failed %s\n", FWX_NETLINK_ID, strerror(errno)); + return -1; + } + memset(&nls, 0, sizeof(struct sockaddr_nl)); + nls.nl_pid = DEFAULT_FWX_NL_PID; + nls.nl_groups = 0; + nls.nl_family = AF_NETLINK; + + if (bind(fd, (void *)&nls, sizeof(struct sockaddr_nl))) + { + LOG_DEBUG("Bind failed %s\n", strerror(errno)); + return -1; + } + + return fd; +} diff --git a/open-app-filter/src/fwx_netlink.h b/open-app-filter/src/fwx_netlink.h new file mode 100644 index 00000000..a35b4015 --- /dev/null +++ b/open-app-filter/src/fwx_netlink.h @@ -0,0 +1,42 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_NETLINK_H__ +#define __FWX_NETLINK_H__ +#define DEFAULT_FWX_NL_PID 999 +#define FWX_NETLINK_ID 29 +#define MAX_FWX_NETLINK_MSG_LEN 1024 +#define MAX_AF_MSG_DATA_LEN 800 +#define MAX_FEATURE_LINE_LEN 800 + +struct fwx_nl_msg_hdr +{ + int magic; + int len; +}; + +enum E_FWX_NL_MSG_TYPE +{ + FWX_NL_MSG_INIT, + FWX_NL_MSG_ADD_FEATURE, + FWX_NL_MSG_CLEAN_FEATURE, + FWX_NL_MSG_FEATURE_LOAD_DONE, + FWX_NL_MSG_MAX +}; + +typedef struct fwx_nl_msg +{ + int action; +} fwx_nl_msg_t; + +typedef struct fwx_nl_feature_msg{ + fwx_nl_msg_t hdr; + char feature[MAX_FEATURE_LINE_LEN]; +} fwx_nl_feature_msg_t; + +int fwx_netlink_init(void); +void fwx_netlink_handler(struct uloop_fd *u, unsigned int ev); +int fwx_nl_send_msg_to_kernel(int fd, void *msg, int len); +#endif diff --git a/open-app-filter/src/fwx_network.c b/open-app-filter/src/fwx_network.c new file mode 100644 index 00000000..5d1c5ddd --- /dev/null +++ b/open-app-filter/src/fwx_network.c @@ -0,0 +1,1361 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fwx.h" +#include "fwx_user.h" +#include "fwx_netlink.h" +#include "fwx_ubus.h" +#include "fwx_config.h" +#include "fwx_utils.h" +#include "fwx_network.h" +#include "fwx_uci.h" +#define MAX_INET_ADDR_LEN 32 +#define MAX_MAC_ADDR_LEN 18 + +int get_iface_status(char *ifname, iface_status_t *status){ + int ret = -1; + char *buf = NULL; + + buf = get_interface_status_buf(ifname); + if (!buf){ + + LOG_ERROR("get interface status buf error\n"); + return -1; + } + struct json_object *resp_obj = json_tokener_parse(buf); + if (!resp_obj) { + LOG_ERROR("get_iface_status: failed to parse JSON\n"); + free(buf); + return -1; + } + + struct json_object *ipv4_addr_array = json_object_object_get(resp_obj, "ipv4-address"); + struct json_object *route_array = json_object_object_get(resp_obj, "route"); + struct json_object *dns_server_array = json_object_object_get(resp_obj, "dns-server"); + + if (ipv4_addr_array && json_object_array_length(ipv4_addr_array) > 0){ + struct json_object *ipv4_addr_obj = json_object_array_get_idx(ipv4_addr_array, 0); + struct json_object *addr_obj = json_object_object_get(ipv4_addr_obj, "address"); + struct json_object *mask_obj = json_object_object_get(ipv4_addr_obj, "mask"); + if (addr_obj && mask_obj){ + strcpy(status->ip, json_object_get_string(addr_obj)); + char *mask_str = cidr2str(json_object_get_int(mask_obj)); + if (mask_str) + strcpy(status->mask, mask_str); + + } + } + else{ + LOG_ERROR("parse json error\n"); + } + + if (route_array && json_object_array_length(route_array) > 0){ + struct json_object *route_obj = json_object_array_get_idx(route_array, 0); + struct json_object *nexhop_obj = json_object_object_get(route_obj, "nexthop"); + if (nexhop_obj){ + strcpy(status->gateway, json_object_get_string(nexhop_obj)); + } + } + + if (dns_server_array && json_object_array_length(dns_server_array) > 0){ + struct json_object *dns1_obj = json_object_array_get_idx(dns_server_array, 0); + if (dns1_obj){ + strcpy(status->dns1, json_object_get_string(dns1_obj)); + } + } + if (dns_server_array && json_object_array_length(dns_server_array) > 1){ + struct json_object *dns2_obj = json_object_array_get_idx(dns_server_array, 1); + if (dns2_obj){ + strcpy(status->dns2, json_object_get_string(dns2_obj)); + } + } + + ret = 0; +DONE: + if (buf) { + free(buf); + } + if (resp_obj) { + json_object_put(resp_obj); + } + return ret; +} + + +char *get_interface_status_buf(char *ifname) { + if (!ifname) { + return NULL; + } + + + char cmd[256] = {0}; + snprintf(cmd, sizeof(cmd), "ifstatus %s", ifname); + + FILE *fp = popen(cmd, "r"); + if (!fp) { + return NULL; + } + + + size_t buf_size = 4096; + char *buf = malloc(buf_size); + if (!buf) { + pclose(fp); + return NULL; + } + + size_t total_read = 0; + char line[256]; + while (fgets(line, sizeof(line), fp)) { + size_t line_len = strlen(line); + if (total_read + line_len >= buf_size) { + buf_size *= 2; + char *new_buf = realloc(buf, buf_size); + if (!new_buf) { + free(buf); + pclose(fp); + return NULL; + } + buf = new_buf; + } + strncpy(buf + total_read, line, line_len); + total_read += line_len; + buf[total_read] = '\0'; + } + + pclose(fp); + return buf; +} + + +char *cidr2str(int cidr) { + if (cidr < 0 || cidr > 32) { + return NULL; + } + + static char mask_str[16]; + unsigned int mask = 0xFFFFFFFF << (32 - cidr); + + + snprintf(mask_str, sizeof(mask_str), "%d.%d.%d.%d", + (mask >> 24) & 0xFF, + (mask >> 16) & 0xFF, + (mask >> 8) & 0xFF, + mask & 0xFF); + + return mask_str; +} + +static int netmask_to_prefix(const char *mask_str) +{ + struct in_addr addr; + uint32_t mask; + int prefix = 0; + int i; + + if (!mask_str || inet_aton(mask_str, &addr) == 0) { + return -1; + } + + mask = ntohl(addr.s_addr); + for (i = 31; i >= 0; i--) { + if (mask & (1u << i)) { + prefix++; + } else { + break; + } + } + + if (prefix < 0 || prefix > 32) { + return -1; + } + + if (prefix < 32) { + uint32_t expected = (prefix == 0) ? 0u : (~0u << (32 - prefix)); + if (mask != expected) { + return -1; + } + } else if (mask != 0xFFFFFFFFu) { + return -1; + } + + return prefix; +} + +static int parse_ipaddr_list_token(const char *token, char *ip_out, size_t ip_out_len, char *mask_out, size_t mask_out_len) +{ + char local[64] = {0}; + char *slash = NULL; + int prefix = -1; + char *mask_str = NULL; + + if (!token || !ip_out || !mask_out || ip_out_len == 0 || mask_out_len == 0) { + return -1; + } + + snprintf(local, sizeof(local), "%s", token); + slash = strchr(local, '/'); + if (!slash) { + return -1; + } + + *slash = '\0'; + slash++; + if (local[0] == '\0' || slash[0] == '\0') { + return -1; + } + + prefix = atoi(slash); + if (prefix < 0 || prefix > 32) { + return -1; + } + + mask_str = cidr2str(prefix); + if (!mask_str) { + return -1; + } + + snprintf(ip_out, ip_out_len, "%s", local); + snprintf(mask_out, mask_out_len, "%s", mask_str); + return 0; +} + +static const char *get_section_option_value(struct uci_section *s, const char *option_name); + +static int replace_first_list_item(struct uci_context *ctx, const char *uci_key, const char *first_item) +{ + char list_buf[256] = {0}; + char *items[32] = {0}; + int item_count = 0; + int i = 0; + char *saveptr = NULL; + char *token = NULL; + + if (!ctx || !uci_key || !first_item || first_item[0] == '\0') { + return -1; + } + + if (fwx_uci_get_list_value(ctx, (char *)uci_key, list_buf, sizeof(list_buf), " ") == 0 && list_buf[0] != '\0') { + token = strtok_r(list_buf, " ", &saveptr); + while (token && item_count < (int)(sizeof(items) / sizeof(items[0]))) { + items[item_count++] = token; + token = strtok_r(NULL, " ", &saveptr); + } + } + + fwx_uci_delete(ctx, (char *)uci_key); + fwx_uci_add_list(ctx, (char *)uci_key, (char *)first_item); + + for (i = 1; i < item_count; i++) { + if (items[i] && items[i][0] != '\0') { + fwx_uci_add_list(ctx, (char *)uci_key, items[i]); + } + } + + return 0; +} + +static void get_interface_ipaddr_and_mask(struct uci_context *ctx, struct uci_section *s, char *ip_out, size_t ip_out_len, char *mask_out, size_t mask_out_len) +{ + const char *ipaddr_str = NULL; + const char *netmask_str = NULL; + struct uci_option *ipaddr_opt = NULL; + struct uci_element *e = NULL; + + if (!ctx || !s || !ip_out || !mask_out || ip_out_len == 0 || mask_out_len == 0) { + return; + } + + ipaddr_str = get_section_option_value(s, "ipaddr"); + netmask_str = get_section_option_value(s, "netmask"); + if (ipaddr_str && ipaddr_str[0] != '\0') { + snprintf(ip_out, ip_out_len, "%s", ipaddr_str); + if (netmask_str && netmask_str[0] != '\0') { + snprintf(mask_out, mask_out_len, "%s", netmask_str); + } + return; + } + + ipaddr_opt = uci_lookup_option(ctx, s, "ipaddr"); + if (ipaddr_opt && ipaddr_opt->type == UCI_TYPE_LIST) { + uci_foreach_element(&ipaddr_opt->v.list, e) { + if (e->name && parse_ipaddr_list_token(e->name, ip_out, ip_out_len, mask_out, mask_out_len) == 0) { + return; + } + } + } +} + + +static int interface_name_matches(const char *ifname, const char *prefix) { + if (!ifname || !prefix) return 0; + int len = strlen(prefix); + if (strlen(ifname) < len) return 0; + return strncasecmp(ifname, prefix, len) == 0; +} + + +static void append_lan_dhcp_to_response(struct json_object *data_obj); +static int update_lan_dhcp_from_req(struct json_object *dhcp_obj); + + +static int ensure_fwx_network_section(struct uci_context *ctx) +{ + struct uci_ptr ptr; + if (uci_lookup_ptr(ctx, &ptr, "fwx.network", true) == UCI_OK) { + return 0; + } + struct uci_package *pkg = NULL; + if (uci_load(ctx, "fwx", &pkg) != UCI_OK) { + LOG_ERROR("ensure_fwx_network_section: load fwx failed\n"); + return -1; + } + char path[64]; + snprintf(path, sizeof(path), "fwx.network=network"); + if (uci_lookup_ptr(ctx, &ptr, path, true) != UCI_OK) { + LOG_ERROR("ensure_fwx_network_section: lookup ptr failed\n"); + uci_unload(ctx, pkg); + return -1; + } + if (uci_set(ctx, &ptr) != UCI_OK) { + LOG_ERROR("ensure_fwx_network_section: set failed\n"); + if (ptr.p) uci_unload(ctx, ptr.p); + return -1; + } + if (uci_save(ctx, ptr.p) != UCI_OK) { + LOG_ERROR("ensure_fwx_network_section: save failed\n"); + if (ptr.p) uci_unload(ctx, ptr.p); + return -1; + } + if (ptr.p) uci_unload(ctx, ptr.p); + return 0; +} + + +static const char *get_section_option_value(struct uci_section *s, const char *option_name) { + if (!s || !option_name) return NULL; + + struct uci_option *o = uci_lookup_option(s->package->ctx, s, option_name); + if (!o || o->type != UCI_TYPE_STRING) { + return NULL; + } + return o->v.string; +} + + +static void get_section_list_values(struct uci_section *s, const char *option_name, + struct json_object *array) { + if (!s || !option_name || !array) return; + + struct uci_option *o = uci_lookup_option(s->package->ctx, s, option_name); + if (!o || o->type != UCI_TYPE_LIST) { + return; + } + + struct uci_element *e; + uci_foreach_element(&o->v.list, e) { + json_object_array_add(array, json_object_new_string(e->name)); + } +} + + +static struct json_object *get_interface_list_by_type(const char *iftype) { + struct json_object *data_obj = json_object_new_object(); + struct json_object *interfaces_array = json_object_new_array(); + LOG_DEBUG("get_interface_list_by_type called\n"); + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_package *pkg = NULL; + if (uci_load(ctx, "network", &pkg) != UCI_OK) { + LOG_ERROR("Failed to load network package\n"); + uci_free_context(ctx); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct uci_element *e; + uci_foreach_element(&pkg->sections, e) { + + struct uci_section *s = uci_to_section(e); + + LOG_DEBUG("s = %p\n", s); + LOG_DEBUG("s->type: %s\n", s->type); + + if (strcmp(s->type, "interface") != 0) { + continue; + } + + const char *name_str = s->e.name; + + if (!name_str) { + continue; + } + LOG_DEBUG("name_str: %s\n", name_str); + + + if (!interface_name_matches(name_str, iftype)) { + LOG_DEBUG("not match \n"); + continue; + } + + const char *device_str = get_section_option_value(s, "device"); + const char *proto_str = get_section_option_value(s, "proto"); + char ipaddr_buf[32] = {0}; + char netmask_buf[32] = {0}; + const char *gateway_str = get_section_option_value(s, "gateway"); + get_interface_ipaddr_and_mask(ctx, s, ipaddr_buf, sizeof(ipaddr_buf), netmask_buf, sizeof(netmask_buf)); + + LOG_DEBUG("get_interface_list_by_type: device=%s, proto=%s, ipaddr=%s\n", + device_str ? device_str : "NULL", + proto_str ? proto_str : "NULL", + ipaddr_buf[0] ? ipaddr_buf : "NULL"); + + + struct json_object *dns_array = json_object_new_array(); + if (!dns_array) { + LOG_ERROR("get_interface_list_by_type: Failed to create dns_array\n"); + continue; + } + get_section_list_values(s, "dns", dns_array); + + LOG_DEBUG("get_interface_list_by_type: Creating interface object\n"); + struct json_object *iface_obj = json_object_new_object(); + if (!iface_obj) { + LOG_ERROR("get_interface_list_by_type: Failed to create iface_obj\n"); + json_object_put(dns_array); + continue; + } + + json_object_object_add(iface_obj, "name", json_object_new_string(name_str)); + json_object_object_add(iface_obj, "device", json_object_new_string(device_str ? device_str : "")); + json_object_object_add(iface_obj, "proto", json_object_new_string(proto_str ? proto_str : "")); + json_object_object_add(iface_obj, "ipaddr", json_object_new_string(ipaddr_buf)); + json_object_object_add(iface_obj, "netmask", json_object_new_string(netmask_buf)); + json_object_object_add(iface_obj, "gateway", json_object_new_string(gateway_str ? gateway_str : "")); + json_object_object_add(iface_obj, "dns", dns_array); + + LOG_DEBUG("get_interface_list_by_type: Added interface %s to array\n", name_str); + json_object_array_add(interfaces_array, iface_obj); + LOG_DEBUG("222222222222\n"); + } + + LOG_DEBUG("22222222\n"); + + + uci_free_context(ctx); + + + json_object_object_add(data_obj, "list", interfaces_array); + LOG_DEBUG("data_obj: %s\n", json_object_to_json_string(data_obj)); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_get_lan_list(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_get_lan_list: called\n"); + struct json_object *result = get_interface_list_by_type("lan"); + LOG_DEBUG("fwx_api_get_lan_list: returning result\n"); + return result; +} + + +struct json_object *fwx_api_get_wan_list(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_get_wan_list: called\n"); + struct json_object *result = get_interface_list_by_type("wan"); + LOG_DEBUG("fwx_api_get_wan_list: returning result\n"); + return result; +} + + +static struct json_object *add_or_mod_interface(struct json_object *req_obj, const char *iftype, int is_add) { + int i; + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *name_obj = json_object_object_get(req_obj, "name"); + struct json_object *device_obj = json_object_object_get(req_obj, "device"); + struct json_object *proto_obj = json_object_object_get(req_obj, "proto"); + + if (!name_obj || !device_obj || !proto_obj) { + LOG_ERROR("Missing required fields: name, device, proto\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *name = json_object_get_string(name_obj); + const char *device = json_object_get_string(device_obj); + const char *proto = json_object_get_string(proto_obj); + + + if (!interface_name_matches(name, iftype)) { + LOG_ERROR("Interface name '%s' must start with '%s'\n", name, iftype); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + if (strcmp(proto, "dhcp") != 0 && strcmp(proto, "static") != 0 && + (strcmp(iftype, "wan") != 0 || strcmp(proto, "pppoe") != 0)) { + LOG_ERROR("Invalid protocol '%s' for %s interface\n", proto, iftype); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + if (strcmp(proto, "static") == 0) { + struct json_object *ipaddr_obj = json_object_object_get(req_obj, "ipaddr"); + struct json_object *netmask_obj = json_object_object_get(req_obj, "netmask"); + if (!ipaddr_obj || !netmask_obj) { + LOG_ERROR("ipaddr and netmask are required for static protocol\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct uci_ptr ptr; + char uci_path[256]; + snprintf(uci_path, sizeof(uci_path), "network.%s", name); + + int exists = (uci_lookup_ptr(ctx, &ptr, uci_path, true) == UCI_OK); + + if (is_add) { + + if (exists) { + LOG_ERROR("Interface '%s' already exists\n", name); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } else { + + if (!exists) { + LOG_ERROR("Interface '%s' not found\n", name); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } + + + if (is_add) { + + + char section_path[256]; + snprintf(section_path, sizeof(section_path), "network.%s=interface", name); + + struct uci_ptr ptr; + if (uci_lookup_ptr(ctx, &ptr, section_path, true) != UCI_OK) { + LOG_ERROR("Failed to create interface section '%s'\n", name); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (uci_set(ctx, &ptr) != UCI_OK) { + LOG_ERROR("Failed to set interface section '%s'\n", name); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (uci_save(ctx, ptr.p) != UCI_OK) { + LOG_ERROR("Failed to save network package\n"); + if (ptr.p) uci_unload(ctx, ptr.p); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (ptr.p) uci_unload(ctx, ptr.p); + } + + + snprintf(uci_path, sizeof(uci_path), "network.%s.device", name); + fwx_uci_set_value(ctx, uci_path, (char *)device); + + + snprintf(uci_path, sizeof(uci_path), "network.%s.proto", name); + fwx_uci_set_value(ctx, uci_path, (char *)proto); + + + if (strcmp(proto, "static") == 0) { + struct json_object *ipaddr_obj = json_object_object_get(req_obj, "ipaddr"); + struct json_object *netmask_obj = json_object_object_get(req_obj, "netmask"); + struct json_object *gateway_obj = json_object_object_get(req_obj, "gateway"); + + if (ipaddr_obj) { + if (strcmp(iftype, "lan") == 0) { + const char *ipaddr = json_object_get_string(ipaddr_obj); + const char *netmask = netmask_obj ? json_object_get_string(netmask_obj) : NULL; + int prefix = netmask_to_prefix(netmask ? netmask : ""); + char ipaddr_with_prefix[64] = {0}; + + if (prefix < 0) { + prefix = 24; + } + snprintf(uci_path, sizeof(uci_path), "network.%s.ipaddr", name); + if (ipaddr && ipaddr[0] != '\0') { + snprintf(ipaddr_with_prefix, sizeof(ipaddr_with_prefix), "%s/%d", ipaddr, prefix); + replace_first_list_item(ctx, uci_path, ipaddr_with_prefix); + } + } else { + snprintf(uci_path, sizeof(uci_path), "network.%s.ipaddr", name); + fwx_uci_set_value(ctx, uci_path, (char *)json_object_get_string(ipaddr_obj)); + } + } + + if (strcmp(iftype, "lan") == 0) { + snprintf(uci_path, sizeof(uci_path), "network.%s.netmask", name); + fwx_uci_delete(ctx, uci_path); + } else if (netmask_obj) { + snprintf(uci_path, sizeof(uci_path), "network.%s.netmask", name); + fwx_uci_set_value(ctx, uci_path, (char *)json_object_get_string(netmask_obj)); + } + + if (gateway_obj) { + snprintf(uci_path, sizeof(uci_path), "network.%s.gateway", name); + fwx_uci_set_value(ctx, uci_path, (char *)json_object_get_string(gateway_obj)); + } + + + struct json_object *dns_obj = json_object_object_get(req_obj, "dns"); + if (dns_obj && json_object_is_type(dns_obj, json_type_array)) { + + char dns_path[256]; + snprintf(dns_path, sizeof(dns_path), "network.%s.dns", name); + fwx_uci_delete(ctx, dns_path); + + + struct uci_ptr ptr; + memset(&ptr, 0, sizeof(ptr)); + ptr.package = "network"; + ptr.section = name; + ptr.option = "dns"; + + int dns_len = json_object_array_length(dns_obj); + for (i = 0; i < dns_len; i++) { + struct json_object *dns_item = json_object_array_get_idx(dns_obj, i); + const char *dns_str = json_object_get_string(dns_item); + if (dns_str && strlen(dns_str) > 0) { + ptr.value = (char *)dns_str; + if (uci_add_list(ctx, &ptr) != UCI_OK) { + LOG_ERROR("Failed to add DNS to list: %s\n", dns_str); + } + } + } + } + } else if (strcmp(proto, "pppoe") == 0 && strcmp(iftype, "wan") == 0) { + + struct json_object *username_obj = json_object_object_get(req_obj, "username"); + struct json_object *password_obj = json_object_object_get(req_obj, "password"); + + if (username_obj) { + snprintf(uci_path, sizeof(uci_path), "network.%s.username", name); + fwx_uci_set_value(ctx, uci_path, (char *)json_object_get_string(username_obj)); + } + + if (password_obj) { + snprintf(uci_path, sizeof(uci_path), "network.%s.password", name); + fwx_uci_set_value(ctx, uci_path, (char *)json_object_get_string(password_obj)); + } + } + + fwx_uci_commit(ctx, "network"); + uci_free_context(ctx); + + LOG_DEBUG("%s interface '%s' %s successfully\n", iftype, name, is_add ? "added" : "modified"); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_add_lan(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_add_lan called\n"); + return add_or_mod_interface(req_obj, "lan", 1); +} + + +struct json_object *fwx_api_mod_lan(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_mod_lan called\n"); + return add_or_mod_interface(req_obj, "lan", 0); +} + + +struct json_object *fwx_api_add_wan(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_add_wan called\n"); + return add_or_mod_interface(req_obj, "wan", 1); +} + + +struct json_object *fwx_api_mod_wan(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_mod_wan called\n"); + return add_or_mod_interface(req_obj, "wan", 0); +} + + +static struct json_object *del_interface(struct json_object *req_obj, const char *iftype) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *name_obj = json_object_object_get(req_obj, "name"); + if (!name_obj) { + LOG_ERROR("Missing required field: name\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *name = json_object_get_string(name_obj); + + + if (!interface_name_matches(name, iftype)) { + LOG_ERROR("Interface name '%s' must start with '%s'\n", name, iftype); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct uci_ptr ptr; + char uci_path[256]; + snprintf(uci_path, sizeof(uci_path), "network.%s", name); + + if (uci_lookup_ptr(ctx, &ptr, uci_path, true) != UCI_OK) { + LOG_ERROR("Interface '%s' not found\n", name); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + fwx_uci_delete(ctx, uci_path); + + fwx_uci_commit(ctx, "network"); + uci_free_context(ctx); + + LOG_DEBUG("%s interface '%s' deleted successfully\n", iftype, name); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_del_lan(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_del_lan called\n"); + return del_interface(req_obj, "lan"); +} + + +struct json_object *fwx_api_del_wan(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_del_wan called\n"); + return del_interface(req_obj, "wan"); +} + + +struct json_object *fwx_api_get_lan_info(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_get_lan_info called\n"); + + struct json_object *data_obj = json_object_new_object(); + if (!data_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char ipaddr_str[32] = {0}; + char netmask_str[32] = {0}; + char proto_str[32] = {0}; + char gateway_str[32] = {0}; + char dns1_str[32] = {0}; + char dns2_str[32] = {0}; + + { + char ipaddr_list_buf[128] = {0}; + int list_ok = 0; + + if (fwx_uci_get_list_value(ctx, "network.lan.ipaddr", ipaddr_list_buf, sizeof(ipaddr_list_buf), " ") == 0 && + ipaddr_list_buf[0] != '\0') { + char *saveptr = NULL; + char *token = strtok_r(ipaddr_list_buf, " ", &saveptr); + if (token && parse_ipaddr_list_token(token, ipaddr_str, sizeof(ipaddr_str), netmask_str, sizeof(netmask_str)) == 0) { + list_ok = 1; + } + } + + if (!list_ok) { + fwx_uci_get_value(ctx, "network.lan.ipaddr", ipaddr_str, sizeof(ipaddr_str)); + fwx_uci_get_value(ctx, "network.lan.netmask", netmask_str, sizeof(netmask_str)); + } + } + fwx_uci_get_value(ctx, "network.lan.proto", proto_str, sizeof(proto_str)); + fwx_uci_get_value(ctx, "network.lan.gateway", gateway_str, sizeof(gateway_str)); + + + char dns_list_buf[128] = {0}; + if (fwx_uci_get_list_value(ctx, "network.lan.dns", dns_list_buf, sizeof(dns_list_buf), " ") == 0) { + char *saveptr = NULL; + char *p = strtok_r(dns_list_buf, " ", &saveptr); + if (p) { + strncpy(dns1_str, p, sizeof(dns1_str) - 1); + p = strtok_r(NULL, " ", &saveptr); + if (p) { + strncpy(dns2_str, p, sizeof(dns2_str) - 1); + } + } + } + + json_object_object_add(data_obj, "ipaddr", json_object_new_string(ipaddr_str)); + json_object_object_add(data_obj, "netmask", json_object_new_string(netmask_str)); + json_object_object_add(data_obj, "proto", json_object_new_string(proto_str)); + json_object_object_add(data_obj, "gateway", json_object_new_string(gateway_str)); + json_object_object_add(data_obj, "dns1", json_object_new_string(dns1_str)); + json_object_object_add(data_obj, "dns2", json_object_new_string(dns2_str)); + append_lan_dhcp_to_response(data_obj); + + uci_free_context(ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_set_lan_info(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_set_lan_info called22\n"); + + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + char test_buf[32] = {0}; + if (fwx_uci_get_value(ctx, "network.lan.proto", test_buf, sizeof(test_buf)) != 0) { + + if (fwx_uci_set_value(ctx, "network.lan", "interface") != 0) { + LOG_ERROR("Failed to create lan section\n"); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } + + + + struct json_object *ipaddr_obj = json_object_object_get(req_obj, "ipaddr"); + struct json_object *netmask_obj = json_object_object_get(req_obj, "netmask"); + struct json_object *proto_obj = json_object_object_get(req_obj, "proto"); + struct json_object *gateway_obj = json_object_object_get(req_obj, "gateway"); + struct json_object *dns1_obj = json_object_object_get(req_obj, "dns1"); + struct json_object *dns2_obj = json_object_object_get(req_obj, "dns2"); + + if (proto_obj) { + const char *proto = json_object_get_string(proto_obj); + if (proto && strcmp(proto, "pppoe") == 0) { + LOG_ERROR("LAN interface does not support PPPoE protocol\n"); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + fwx_uci_set_value(ctx, "network.lan.proto", (char *)proto); + } + + + if (ipaddr_obj) { + const char *ipaddr = json_object_get_string(ipaddr_obj); + const char *netmask = netmask_obj ? json_object_get_string(netmask_obj) : NULL; + int prefix = netmask_to_prefix(netmask ? netmask : ""); + + if (ipaddr && ipaddr[0] != '\0') { + char ipaddr_with_prefix[64] = {0}; + if (prefix < 0) { + prefix = 24; + } + snprintf(ipaddr_with_prefix, sizeof(ipaddr_with_prefix), "%s/%d", ipaddr, prefix); + /* 统一使用 list ipaddr(x.x.x.x/nn)格式,只替换第一项并保留其余项 */ + replace_first_list_item(ctx, "network.lan.ipaddr", ipaddr_with_prefix); + } + } + + fwx_uci_delete(ctx, "network.lan.netmask"); + + if (gateway_obj) { + fwx_uci_set_value(ctx, "network.lan.gateway", (char *)json_object_get_string(gateway_obj)); + } + + + if (dns1_obj || dns2_obj) { + fwx_uci_delete(ctx, "network.lan.dns"); + + struct uci_ptr ptr; + memset(&ptr, 0, sizeof(ptr)); + ptr.package = "network"; + ptr.section = "lan"; + ptr.option = "dns"; + + if (dns1_obj) { + const char *dns1 = json_object_get_string(dns1_obj); + if (dns1 && strlen(dns1) > 0) { + ptr.value = (char *)dns1; + if (uci_add_list(ctx, &ptr) != UCI_OK) { + LOG_ERROR("Failed to add DNS1 to list\n"); + } + } + } + + if (dns2_obj) { + const char *dns2 = json_object_get_string(dns2_obj); + if (dns2 && strlen(dns2) > 0) { + ptr.value = (char *)dns2; + if (uci_add_list(ctx, &ptr) != UCI_OK) { + LOG_ERROR("Failed to add DNS2 to list\n"); + } + } + } + } + fwx_uci_commit(ctx, "network"); + uci_free_context(ctx); + update_lan_dhcp_from_req(json_object_object_get(req_obj, "dhcp")); + system("/etc/init.d/network restart"); + system("/etc/init.d/dnsmasq restart"); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_get_wan_info(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_get_wan_info called\n"); + + struct json_object *data_obj = json_object_new_object(); + if (!data_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char ipaddr_str[32] = {0}; + char netmask_str[32] = {0}; + char proto_str[32] = {0}; + char gateway_str[32] = {0}; + char dns1_str[32] = {0}; + char dns2_str[32] = {0}; + char username_str[128] = {0}; + char password_str[128] = {0}; + + fwx_uci_get_value(ctx, "network.wan.ipaddr", ipaddr_str, sizeof(ipaddr_str)); + fwx_uci_get_value(ctx, "network.wan.netmask", netmask_str, sizeof(netmask_str)); + fwx_uci_get_value(ctx, "network.wan.proto", proto_str, sizeof(proto_str)); + fwx_uci_get_value(ctx, "network.wan.gateway", gateway_str, sizeof(gateway_str)); + fwx_uci_get_value(ctx, "network.wan.username", username_str, sizeof(username_str)); + fwx_uci_get_value(ctx, "network.wan.password", password_str, sizeof(password_str)); + + + char dns_list_buf[128] = {0}; + if (fwx_uci_get_list_value(ctx, "network.wan.dns", dns_list_buf, sizeof(dns_list_buf), " ") == 0) { + char *saveptr = NULL; + char *p = strtok_r(dns_list_buf, " ", &saveptr); + if (p) { + strncpy(dns1_str, p, sizeof(dns1_str) - 1); + p = strtok_r(NULL, " ", &saveptr); + if (p) { + strncpy(dns2_str, p, sizeof(dns2_str) - 1); + } + } + } + + json_object_object_add(data_obj, "ipaddr", json_object_new_string(ipaddr_str)); + json_object_object_add(data_obj, "netmask", json_object_new_string(netmask_str)); + json_object_object_add(data_obj, "proto", json_object_new_string(proto_str)); + json_object_object_add(data_obj, "gateway", json_object_new_string(gateway_str)); + json_object_object_add(data_obj, "dns1", json_object_new_string(dns1_str)); + json_object_object_add(data_obj, "dns2", json_object_new_string(dns2_str)); + json_object_object_add(data_obj, "username", json_object_new_string(username_str)); + json_object_object_add(data_obj, "password", json_object_new_string(password_str)); + + uci_free_context(ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_set_wan_info(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_set_wan_info called\n"); + + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + char test_buf[32] = {0}; + if (fwx_uci_get_value(ctx, "network.wan.proto", test_buf, sizeof(test_buf)) != 0) { + + if (fwx_uci_set_value(ctx, "network.wan", "interface") != 0) { + LOG_ERROR("Failed to create wan section\n"); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } + + + struct json_object *proto_obj = json_object_object_get(req_obj, "proto"); + const char *proto = NULL; + if (proto_obj) { + proto = json_object_get_string(proto_obj); + fwx_uci_set_value(ctx, "network.wan.proto", (char *)proto); + } else { + + char proto_buf[32] = {0}; + if (fwx_uci_get_value(ctx, "network.wan.proto", proto_buf, sizeof(proto_buf)) == 0) { + proto = proto_buf; + } + } + + if (!proto) { + LOG_ERROR("Protocol not specified and cannot be determined\n"); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + LOG_DEBUG("proto: %s\n", proto); + + if (strcmp(proto, "static") == 0) { + + struct json_object *ipaddr_obj = json_object_object_get(req_obj, "ipaddr"); + struct json_object *netmask_obj = json_object_object_get(req_obj, "netmask"); + struct json_object *gateway_obj = json_object_object_get(req_obj, "gateway"); + struct json_object *dns1_obj = json_object_object_get(req_obj, "dns1"); + struct json_object *dns2_obj = json_object_object_get(req_obj, "dns2"); + + if (ipaddr_obj) { + fwx_uci_set_value(ctx, "network.wan.ipaddr", (char *)json_object_get_string(ipaddr_obj)); + } + + if (netmask_obj) { + fwx_uci_set_value(ctx, "network.wan.netmask", (char *)json_object_get_string(netmask_obj)); + } + + if (gateway_obj) { + fwx_uci_set_value(ctx, "network.wan.gateway", (char *)json_object_get_string(gateway_obj)); + } + + if (dns1_obj || dns2_obj) { + fwx_uci_delete(ctx, "network.wan.dns"); + + struct uci_ptr ptr; + memset(&ptr, 0, sizeof(ptr)); + ptr.package = "network"; + ptr.section = "wan"; + ptr.option = "dns"; + + if (dns1_obj) { + const char *dns1 = json_object_get_string(dns1_obj); + if (dns1 && strlen(dns1) > 0) { + ptr.value = (char *)dns1; + + if (uci_add_list(ctx, &ptr) != UCI_OK) { + LOG_ERROR("Failed to add DNS1 to list\n"); + } + } + } + + if (dns2_obj) { + + const char *dns2 = json_object_get_string(dns2_obj); + if (dns2 && strlen(dns2) > 0) { + + ptr.value = (char *)dns2; + if (uci_add_list(ctx, &ptr) != UCI_OK) { + LOG_ERROR("Failed to add DNS2 to list\n"); + } + } + } + + } + } else if (strcmp(proto, "pppoe") == 0) { + struct json_object *username_obj = json_object_object_get(req_obj, "username"); + struct json_object *password_obj = json_object_object_get(req_obj, "password"); + if (username_obj) { + const char *username = json_object_get_string(username_obj); + if (username && strlen(username) > 0) { + + fwx_uci_set_value(ctx, "network.wan.username", (char *)username); + } + } + + if (password_obj) { + const char *password = json_object_get_string(password_obj); + if (password && strlen(password) > 0) { + + fwx_uci_set_value(ctx, "network.wan.password", (char *)password); + } + } + + } else if (strcmp(proto, "dhcp") == 0) { + + } + + fwx_uci_commit(ctx, "network"); + uci_free_context(ctx); + + + LOG_DEBUG("Reloading network configuration...\n"); + int ret = system("/etc/init.d/network reload"); + if (ret != 0) { + LOG_ERROR("Failed to reload network, return code: %d\n", ret); + } + + LOG_DEBUG("WAN interface info updated successfully\n"); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_get_work_mode(struct json_object *req_obj) +{ + LOG_DEBUG("fwx_api_get_work_mode called\n"); + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("fwx_api_get_work_mode: alloc ctx failed\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + int work_mode = fwx_uci_get_int_value(ctx, "fwx.network.work_mode"); + if (work_mode != 0 && work_mode != 1) { + work_mode = 0; + } + struct json_object *data_obj = json_object_new_object(); + if (!data_obj) { + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + json_object_object_add(data_obj, "work_mode", json_object_new_int(work_mode)); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_set_work_mode(struct json_object *req_obj) +{ + LOG_DEBUG("fwx_api_set_work_mode called\n"); + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + struct json_object *wm_obj = json_object_object_get(req_obj, "work_mode"); + if (!wm_obj) { + LOG_ERROR("fwx_api_set_work_mode: missing work_mode\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + int work_mode = json_object_get_int(wm_obj); + if (work_mode != 0 && work_mode != 1) { + LOG_ERROR("fwx_api_set_work_mode: invalid work_mode %d\n", work_mode); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("fwx_api_set_work_mode: alloc ctx failed\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + fwx_uci_set_int_value(ctx, "fwx.network.work_mode", work_mode); + fwx_uci_commit(ctx, "fwx"); + uci_free_context(ctx); + + update_fwx_proc_u32_value("work_mode", work_mode); + + LOG_DEBUG("fwx_api_set_work_mode: work_mode=%d\n", work_mode); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +static int parse_leasetime_to_minutes(const char *lt) +{ + if (!lt || lt[0] == '\0') return 0; + int len = strlen(lt); + char unit = lt[len - 1]; + int val = atoi(lt); + if (val < 0) val = 0; + if (unit == 'h' || unit == 'H') { + return val * 60; + } else if (unit == 'm' || unit == 'M') { + return val; + } + return val; +} + + +static void format_minutes_to_leasetime(int minutes, char *out, size_t out_len) +{ + if (minutes < 0) minutes = 0; + if (minutes > 60) { + int hours = minutes / 60; + snprintf(out, out_len, "%dh", hours > 0 ? hours : 1); + } else { + snprintf(out, out_len, "%dm", minutes); + } +} + + +static void fill_lan_dhcp_info(struct json_object *data_obj) +{ + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("fill_lan_dhcp_info: alloc ctx failed\n"); + return; + } + + int enable = 1; + int start = 0; + int limit = 0; + int lease_minutes = 0; + char lease_str[32] = {0}; + char val_buf[32] = {0}; + + if (fwx_uci_get_value(ctx, "dhcp.lan.ignore", val_buf, sizeof(val_buf)) == 0) { + if (strcmp(val_buf, "1") == 0) enable = 0; + } + if (fwx_uci_get_value(ctx, "dhcp.lan.start", val_buf, sizeof(val_buf)) == 0) { + start = atoi(val_buf); + } + if (fwx_uci_get_value(ctx, "dhcp.lan.limit", val_buf, sizeof(val_buf)) == 0) { + limit = atoi(val_buf); + } + if (fwx_uci_get_value(ctx, "dhcp.lan.leasetime", lease_str, sizeof(lease_str)) == 0) { + lease_minutes = parse_leasetime_to_minutes(lease_str); + } + + struct json_object *dhcp_obj = json_object_new_object(); + if (dhcp_obj) { + json_object_object_add(dhcp_obj, "enable", json_object_new_int(enable)); + json_object_object_add(dhcp_obj, "start", json_object_new_int(start)); + json_object_object_add(dhcp_obj, "limit", json_object_new_int(limit)); + json_object_object_add(dhcp_obj, "leasetime", json_object_new_int(lease_minutes)); + json_object_object_add(data_obj, "dhcp", dhcp_obj); + } + + uci_free_context(ctx); +} + + +static int ensure_dhcp_lan_section(struct uci_context *ctx) +{ + struct uci_ptr ptr; + if (uci_lookup_ptr(ctx, &ptr, "dhcp.lan", true) == UCI_OK) { + return 0; + } + struct uci_package *pkg = NULL; + if (uci_load(ctx, "dhcp", &pkg) != UCI_OK) { + LOG_ERROR("ensure_dhcp_lan_section: load dhcp failed\n"); + return -1; + } + char path[64]; + snprintf(path, sizeof(path), "dhcp.lan=dhcp"); + if (uci_lookup_ptr(ctx, &ptr, path, true) != UCI_OK) { + LOG_ERROR("ensure_dhcp_lan_section: lookup ptr failed\n"); + uci_unload(ctx, pkg); + return -1; + } + if (uci_set(ctx, &ptr) != UCI_OK) { + LOG_ERROR("ensure_dhcp_lan_section: set failed\n"); + if (ptr.p) uci_unload(ctx, ptr.p); + return -1; + } + if (uci_save(ctx, ptr.p) != UCI_OK) { + LOG_ERROR("ensure_dhcp_lan_section: save failed\n"); + if (ptr.p) uci_unload(ctx, ptr.p); + return -1; + } + if (ptr.p) uci_unload(ctx, ptr.p); + + fwx_uci_set_value(ctx, "dhcp.lan.interface", "lan"); + return 0; +} + + +static void append_lan_dhcp_to_response(struct json_object *data_obj) +{ + if (!data_obj) return; + fill_lan_dhcp_info(data_obj); +} + + +static int update_lan_dhcp_from_req(struct json_object *dhcp_obj) +{ + + + if (!dhcp_obj) return 0; + + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("update_lan_dhcp_from_req: alloc ctx failed\n"); + return -1; + } + + + + struct json_object *enable_obj = json_object_object_get(dhcp_obj, "enable"); + struct json_object *start_obj = json_object_object_get(dhcp_obj, "start"); + struct json_object *limit_obj = json_object_object_get(dhcp_obj, "limit"); + struct json_object *lt_obj = json_object_object_get(dhcp_obj, "leasetime"); + + + if (enable_obj) { + int en = json_object_get_int(enable_obj); + fwx_uci_set_value(ctx, "dhcp.lan.ignore", en ? "0" : "1"); + } + if (start_obj) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", json_object_get_int(start_obj)); + fwx_uci_set_value(ctx, "dhcp.lan.start", buf); + } + if (limit_obj) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", json_object_get_int(limit_obj)); + fwx_uci_set_value(ctx, "dhcp.lan.limit", buf); + } + if (lt_obj) { + int minutes = json_object_get_int(lt_obj); + char lease_buf[32]; + format_minutes_to_leasetime(minutes, lease_buf, sizeof(lease_buf)); + fwx_uci_set_value(ctx, "dhcp.lan.leasetime", lease_buf); + } + + + fwx_uci_commit(ctx, "dhcp"); + uci_free_context(ctx); + return 0; +} + diff --git a/open-app-filter/src/fwx_network.h b/open-app-filter/src/fwx_network.h new file mode 100644 index 00000000..68d1f9c5 --- /dev/null +++ b/open-app-filter/src/fwx_network.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_NETWORK_H__ +#define __FWX_NETWORK_H__ + +#include + +#define MAX_INET_ADDR_LEN 32 + +typedef struct iface_status{ + int proto; + char ip[MAX_INET_ADDR_LEN]; + char mask[MAX_INET_ADDR_LEN]; + char gateway[MAX_INET_ADDR_LEN]; + char dns1[MAX_INET_ADDR_LEN]; + char dns2[MAX_INET_ADDR_LEN]; +}iface_status_t; + +int get_iface_status(char *ifname, iface_status_t *status); +char *get_interface_status_buf(char *ifname); +char *cidr2str(int cidr); + + +struct json_object *fwx_api_get_lan_list(struct json_object *req_obj); +struct json_object *fwx_api_add_lan(struct json_object *req_obj); +struct json_object *fwx_api_mod_lan(struct json_object *req_obj); +struct json_object *fwx_api_del_lan(struct json_object *req_obj); +struct json_object *fwx_api_get_wan_list(struct json_object *req_obj); +struct json_object *fwx_api_add_wan(struct json_object *req_obj); +struct json_object *fwx_api_mod_wan(struct json_object *req_obj); +struct json_object *fwx_api_del_wan(struct json_object *req_obj); + + +struct json_object *fwx_api_get_lan_info(struct json_object *req_obj); +struct json_object *fwx_api_set_lan_info(struct json_object *req_obj); +struct json_object *fwx_api_get_wan_info(struct json_object *req_obj); +struct json_object *fwx_api_set_wan_info(struct json_object *req_obj); + + +struct json_object *fwx_api_get_work_mode(struct json_object *req_obj); +struct json_object *fwx_api_set_work_mode(struct json_object *req_obj); + +#endif + diff --git a/open-app-filter/src/fwx_record.c b/open-app-filter/src/fwx_record.c new file mode 100644 index 00000000..4d59459a --- /dev/null +++ b/open-app-filter/src/fwx_record.c @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fwx_user.h" +#include "fwx_netlink.h" +#include "fwx_ubus.h" +#include "fwx_config.h" +#include +#include +#include +#include +#include "fwx.h" +#include +#include +#include "fwx_record.h" + +#define RECORD_WHITELIST_STATE_FILE "/tmp/record_whitelist_state" +#define RECORD_WHITELIST_SECTION_TYPE "whitelist" +#define RECORD_WHITELIST_UCI_KEY "fwx_record.@whitelist[0].whitelist" +#define RECORD_WHITELIST_LEGACY_SECTION_TYPE "record" +#define RECORD_WHITELIST_LEGACY_UCI_KEY "fwx_record.@record[0].whitelist" + +static void set_state_file(const char *file_path) +{ + FILE *fd = fopen(file_path, "w"); + if (fd) { + fprintf(fd, "1"); + fclose(fd); + LOG_DEBUG("Set state file: %s\n", file_path); + } else { + LOG_ERROR("Failed to set state file: %s\n", file_path); + } +} + +static void normalize_mac(const char *src, char *dst, int dst_len) +{ + int i = 0; + int start = 0; + int end = 0; + + if (!src || !dst || dst_len <= 0) { + return; + } + + while (src[start] == ' ' || src[start] == '\t' || src[start] == '\r' || src[start] == '\n') { + start++; + } + + end = (int)strlen(src); + while (end > start && + (src[end - 1] == ' ' || src[end - 1] == '\t' || src[end - 1] == '\r' || src[end - 1] == '\n')) { + end--; + } + + for (i = start; i < end && (i - start) < (dst_len - 1); i++) { + dst[i - start] = (char)toupper((unsigned char)src[i]); + } + dst[i - start] = '\0'; +} + +static int is_valid_mac(const char *mac) +{ + int i; + if (!mac || strlen(mac) != 17) { + return 0; + } + + for (i = 0; i < 17; i++) { + if (i % 3 == 2) { + if (mac[i] != ':') { + return 0; + } + } else if (!isxdigit((unsigned char)mac[i])) { + return 0; + } + } + return 1; +} + +static int mac_exists_in_array(struct json_object *array_obj, const char *mac) +{ + int i; + int len; + if (!array_obj || !mac) { + return 0; + } + + len = json_object_array_length(array_obj); + for (i = 0; i < len; i++) { + struct json_object *mac_obj = json_object_array_get_idx(array_obj, i); + const char *exist_mac = json_object_get_string(mac_obj); + if (exist_mac && strcasecmp(exist_mac, mac) == 0) { + return 1; + } + } + return 0; +} + +static int ensure_record_section(struct uci_context *uci_ctx) +{ + int num = 0; + int legacy_num = 0; + char whitelist_buf[4096] = {0}; + char token_buf[4096] = {0}; + char *token = NULL; + char *save_ptr = NULL; + char norm_mac[32] = {0}; + + if (!uci_ctx) { + return -1; + } + + num = fwx_uci_get_list_num(uci_ctx, "fwx_record", RECORD_WHITELIST_SECTION_TYPE); + if (num > 0) { + return 0; + } + + legacy_num = fwx_uci_get_list_num(uci_ctx, "fwx_record", RECORD_WHITELIST_LEGACY_SECTION_TYPE); + + if (fwx_uci_add_section(uci_ctx, "fwx_record", RECORD_WHITELIST_SECTION_TYPE) != UCI_OK) { + LOG_ERROR("Failed to add fwx_record whitelist section\n"); + return -1; + } + + if (legacy_num > 0 && + fwx_uci_get_list_value(uci_ctx, RECORD_WHITELIST_LEGACY_UCI_KEY, + whitelist_buf, sizeof(whitelist_buf), " ") == 0) { + snprintf(token_buf, sizeof(token_buf), "%s", whitelist_buf); + token = strtok_r(token_buf, " ", &save_ptr); + while (token) { + memset(norm_mac, 0, sizeof(norm_mac)); + normalize_mac(token, norm_mac, sizeof(norm_mac)); + if (is_valid_mac(norm_mac)) { + fwx_uci_add_list(uci_ctx, RECORD_WHITELIST_UCI_KEY, norm_mac); + } + token = strtok_r(NULL, " ", &save_ptr); + } + } + + if (fwx_uci_commit(uci_ctx, "fwx_record") != UCI_OK) { + LOG_ERROR("Failed to commit fwx_record section\n"); + return -1; + } + + return 0; +} + +static int load_record_whitelist_from_uci(struct uci_context *uci_ctx, struct json_object *mac_array) +{ + char whitelist_buf[4096] = {0}; + char norm_mac[32] = {0}; + char *token = NULL; + char *save_ptr = NULL; + int count = 0; + + if (!uci_ctx || !mac_array) { + return 0; + } + + if (fwx_uci_get_list_value(uci_ctx, RECORD_WHITELIST_UCI_KEY, whitelist_buf, sizeof(whitelist_buf), " ") != 0) { + return 0; + } + + token = strtok_r(whitelist_buf, " ", &save_ptr); + while (token) { + memset(norm_mac, 0, sizeof(norm_mac)); + normalize_mac(token, norm_mac, sizeof(norm_mac)); + if (is_valid_mac(norm_mac) && !mac_exists_in_array(mac_array, norm_mac)) { + json_object_array_add(mac_array, json_object_new_string(norm_mac)); + count++; + } + token = strtok_r(NULL, " ", &save_ptr); + } + + return count; +} + +struct json_object *fwx_api_get_record_whitelist(struct json_object *req_obj) +{ + int i; + int page = 1; + int page_size = 15; + int total_num = 0; + int total_page = 1; + int start_idx = 0; + int end_idx = 0; + struct json_object *page_obj = NULL; + struct json_object *page_size_obj = NULL; + struct json_object *data_obj = json_object_new_object(); + struct json_object *all_list_obj = json_object_new_array(); + struct json_object *page_list_obj = json_object_new_array(); + struct uci_context *uci_ctx = NULL; + + if (req_obj) { + page_obj = json_object_object_get(req_obj, "page"); + page_size_obj = json_object_object_get(req_obj, "page_size"); + if (page_obj) { + page = json_object_get_int(page_obj); + if (page < 1) { + page = 1; + } + } + if (page_size_obj) { + page_size = json_object_get_int(page_size_obj); + if (page_size < 1) { + page_size = 15; + } + if (page_size > 200) { + page_size = 200; + } + } + } + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(data_obj); + json_object_put(all_list_obj); + json_object_put(page_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + ensure_record_section(uci_ctx); + load_record_whitelist_from_uci(uci_ctx, all_list_obj); + uci_free_context(uci_ctx); + + total_num = json_object_array_length(all_list_obj); + total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) { + total_page = 1; + } + if (page > total_page) { + page = total_page; + } + + start_idx = (page - 1) * page_size; + end_idx = start_idx + page_size; + if (end_idx > total_num) { + end_idx = total_num; + } + + for (i = start_idx; i < end_idx; i++) { + struct json_object *item_obj = json_object_new_object(); + const char *mac = NULL; + client_node_t *dev = NULL; + + mac = json_object_get_string(json_object_array_get_idx(all_list_obj, i)); + if (!mac) { + continue; + } + dev = find_client_node((char *)mac); + + json_object_object_add(item_obj, "mac", json_object_new_string(mac)); + if (dev) { + json_object_object_add(item_obj, "nickname", json_object_new_string(dev->nickname)); + json_object_object_add(item_obj, "hostname", json_object_new_string(dev->hostname)); + } else { + json_object_object_add(item_obj, "nickname", json_object_new_string("")); + json_object_object_add(item_obj, "hostname", json_object_new_string("")); + } + json_object_array_add(page_list_obj, item_obj); + } + + json_object_object_add(data_obj, "total_num", json_object_new_int(total_num)); + json_object_object_add(data_obj, "total_page", json_object_new_int(total_page)); + json_object_object_add(data_obj, "page", json_object_new_int(page)); + json_object_object_add(data_obj, "page_size", json_object_new_int(page_size)); + json_object_object_add(data_obj, "list", page_list_obj); + + json_object_put(all_list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_add_record_whitelist(struct json_object *req_obj) +{ + int i; + int added = 0; + int mac_list_len = 0; + char norm_mac[32] = {0}; + struct json_object *mac_list_obj = NULL; + struct json_object *exist_list_obj = json_object_new_array(); + struct uci_context *uci_ctx = NULL; + + if (!req_obj) { + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + mac_list_obj = json_object_object_get(req_obj, "mac_list"); + if (!mac_list_obj) { + LOG_ERROR("mac_list_obj is NULL\n"); + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (ensure_record_section(uci_ctx) != 0) { + uci_free_context(uci_ctx); + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + load_record_whitelist_from_uci(uci_ctx, exist_list_obj); + mac_list_len = json_object_array_length(mac_list_obj); + + for (i = 0; i < mac_list_len; i++) { + struct json_object *mac_item = json_object_array_get_idx(mac_list_obj, i); + const char *raw_mac = json_object_get_string(mac_item); + + memset(norm_mac, 0, sizeof(norm_mac)); + normalize_mac(raw_mac, norm_mac, sizeof(norm_mac)); + if (!is_valid_mac(norm_mac)) { + continue; + } + if (mac_exists_in_array(exist_list_obj, norm_mac)) { + continue; + } + fwx_uci_add_list(uci_ctx, RECORD_WHITELIST_UCI_KEY, norm_mac); + json_object_array_add(exist_list_obj, json_object_new_string(norm_mac)); + added++; + } + + if (added > 0) { + fwx_uci_commit(uci_ctx, "fwx_record"); + set_state_file(RECORD_WHITELIST_STATE_FILE); + } + + uci_free_context(uci_ctx); + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_del_record_whitelist(struct json_object *req_obj) +{ + int found = 0; + char norm_mac[32] = {0}; + struct json_object *mac_obj = NULL; + struct json_object *exist_list_obj = json_object_new_array(); + struct uci_context *uci_ctx = NULL; + + if (!req_obj) { + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + LOG_ERROR("mac_obj is NULL\n"); + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + normalize_mac(json_object_get_string(mac_obj), norm_mac, sizeof(norm_mac)); + if (!is_valid_mac(norm_mac)) { + LOG_ERROR("invalid mac: %s\n", norm_mac); + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (ensure_record_section(uci_ctx) != 0) { + uci_free_context(uci_ctx); + json_object_put(exist_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + load_record_whitelist_from_uci(uci_ctx, exist_list_obj); + if (mac_exists_in_array(exist_list_obj, norm_mac)) { + fwx_uci_del_list(uci_ctx, RECORD_WHITELIST_UCI_KEY, norm_mac); + fwx_uci_commit(uci_ctx, "fwx_record"); + set_state_file(RECORD_WHITELIST_STATE_FILE); + found = 1; + } + + uci_free_context(uci_ctx); + json_object_put(exist_list_obj); + LOG_DEBUG("Delete record whitelist mac=%s, found=%d\n", norm_mac, found); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_set_record_whitelist(struct json_object *req_obj) +{ + int i; + int mac_list_len = 0; + struct json_object *mac_list_obj = NULL; + struct json_object *new_list_obj = json_object_new_array(); + struct uci_context *uci_ctx = NULL; + + if (!req_obj) { + json_object_put(new_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + mac_list_obj = json_object_object_get(req_obj, "mac_list"); + if (!mac_list_obj) { + LOG_ERROR("mac_list_obj is NULL\n"); + json_object_put(new_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(new_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (ensure_record_section(uci_ctx) != 0) { + uci_free_context(uci_ctx); + json_object_put(new_list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + fwx_uci_delete(uci_ctx, RECORD_WHITELIST_UCI_KEY); + mac_list_len = json_object_array_length(mac_list_obj); + + for (i = 0; i < mac_list_len; i++) { + char norm_mac[32] = {0}; + struct json_object *mac_item = json_object_array_get_idx(mac_list_obj, i); + const char *raw_mac = json_object_get_string(mac_item); + + normalize_mac(raw_mac, norm_mac, sizeof(norm_mac)); + if (!is_valid_mac(norm_mac)) { + continue; + } + if (mac_exists_in_array(new_list_obj, norm_mac)) { + continue; + } + fwx_uci_add_list(uci_ctx, RECORD_WHITELIST_UCI_KEY, norm_mac); + json_object_array_add(new_list_obj, json_object_new_string(norm_mac)); + } + + fwx_uci_commit(uci_ctx, "fwx_record"); + set_state_file(RECORD_WHITELIST_STATE_FILE); + + uci_free_context(uci_ctx); + json_object_put(new_list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} diff --git a/open-app-filter/src/fwx_record.h b/open-app-filter/src/fwx_record.h new file mode 100644 index 00000000..5337c257 --- /dev/null +++ b/open-app-filter/src/fwx_record.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_RECORD_H__ +#define __FWX_RECORD_H__ + +#include "fwx.h" + +struct json_object *fwx_api_get_record_whitelist(struct json_object *req_obj); +struct json_object *fwx_api_add_record_whitelist(struct json_object *req_obj); +struct json_object *fwx_api_del_record_whitelist(struct json_object *req_obj); +struct json_object *fwx_api_set_record_whitelist(struct json_object *req_obj); + +#endif diff --git a/open-app-filter/src/fwx_stat.c b/open-app-filter/src/fwx_stat.c new file mode 100644 index 00000000..d9ffc4b3 --- /dev/null +++ b/open-app-filter/src/fwx_stat.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) + */ + +#include +#include +#include +#include "fwx.h" +#include "fwx_stat.h" + +#define SESSION_MAX_POINTS (24 * 60) +#define SESSION_SAMPLE_INTERVAL_SEC 5 +#define SESSION_DETAIL_MAX_POINTS (5 * 60 / SESSION_SAMPLE_INTERVAL_SEC) +#define SESSION_SAMPLES_PER_MINUTE (60 / SESSION_SAMPLE_INTERVAL_SEC) + +typedef struct session_point { + int value; +} session_point_t; + +typedef struct session_ring { + session_point_t *points; + int max_points; + int start; + int count; +} session_ring_t; + +typedef struct session_stat_ctx { + session_point_t minute_points[SESSION_MAX_POINTS]; + session_point_t detail_points[SESSION_DETAIL_MAX_POINTS]; + session_ring_t minute_ring; + session_ring_t detail_ring; + int detail_sample_count; + int inited; +} session_stat_ctx_t; + +static session_stat_ctx_t g_session_stat; + +static void session_stat_init(void) +{ + if (g_session_stat.inited) { + return; + } + + memset(&g_session_stat, 0, sizeof(g_session_stat)); + g_session_stat.minute_ring.points = g_session_stat.minute_points; + g_session_stat.minute_ring.max_points = SESSION_MAX_POINTS; + g_session_stat.detail_ring.points = g_session_stat.detail_points; + g_session_stat.detail_ring.max_points = SESSION_DETAIL_MAX_POINTS; + g_session_stat.inited = 1; +} + +int fwx_stat_read_conntrack_count(void) +{ + const char *path = "/proc/sys/net/netfilter/nf_conntrack_count"; + char line[64] = {0}; + FILE *fp = fopen(path, "r"); + char *endptr = NULL; + long v; + + if (!fp) { + return 0; + } + + if (!fgets(line, sizeof(line), fp)) { + fclose(fp); + return 0; + } + fclose(fp); + + v = strtol(line, &endptr, 10); + if (endptr == line || v < 0) { + return 0; + } + + return (int)v; +} + +static void session_ring_append(session_ring_t *ring, int value) +{ + int idx; + if (!ring || !ring->points || ring->max_points <= 0) { + return; + } + + if (ring->count < ring->max_points) { + idx = (ring->start + ring->count) % ring->max_points; + ring->count++; + } else { + idx = ring->start; + ring->start = (ring->start + 1) % ring->max_points; + } + ring->points[idx].value = value; +} + +void fwx_session_stat_tick(void) +{ + int current = fwx_stat_read_conntrack_count(); + session_stat_init(); + session_ring_append(&g_session_stat.detail_ring, current); + g_session_stat.detail_sample_count++; + if (g_session_stat.detail_sample_count >= SESSION_SAMPLES_PER_MINUTE) { + session_ring_append(&g_session_stat.minute_ring, current); + g_session_stat.detail_sample_count = 0; + } +} + +struct json_object *fwx_api_get_history_session(struct json_object *req_obj) +{ + const char *range = "hour"; + int minutes = 60; + int current; + int i; + int start_offset = 0; + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_obj = json_object_new_array(); + long long sum = 0; + int sample_count = 0; + int peak = 0; + int avg = 0; + int is_5min = 0; + int total_count = 0; + int ring_start = 0; + int ring_max = 0; + session_point_t *points = NULL; + struct json_object *range_obj = req_obj ? json_object_object_get(req_obj, "range") : NULL; + struct json_object *minutes_obj = req_obj ? json_object_object_get(req_obj, "minutes") : NULL; + session_ring_t *ring = NULL; + + session_stat_init(); + + if (range_obj) { + const char *range_str = json_object_get_string(range_obj); + if (range_str && strlen(range_str) > 0) { + range = range_str; + } + } + + if (range && (!strcmp(range, "5min") || !strcmp(range, "5m"))) { + is_5min = 1; + minutes = 5; + } else if (minutes_obj) { + int tmp = json_object_get_int(minutes_obj); + if (tmp > 0) { + minutes = tmp; + } + } else if (range && strcmp(range, "day") == 0) { + minutes = 24 * 60; + } else { + minutes = 60; + } + + if (is_5min) { + ring = &g_session_stat.detail_ring; + } else { + ring = &g_session_stat.minute_ring; + if (minutes > SESSION_MAX_POINTS) { + minutes = SESSION_MAX_POINTS; + } + if (minutes < 1) { + minutes = 1; + } + } + + total_count = ring->count; + ring_start = ring->start; + ring_max = ring->max_points; + points = ring->points; + + current = fwx_stat_read_conntrack_count(); + + if (is_5min) { + if (total_count > SESSION_DETAIL_MAX_POINTS) { + start_offset = total_count - SESSION_DETAIL_MAX_POINTS; + } + } else if (total_count > minutes) { + start_offset = total_count - minutes; + } + + for (i = start_offset; i < total_count; i++) { + int idx = (ring_start + i) % ring_max; + session_point_t *pt = &points[idx]; + json_object_array_add(list_obj, json_object_new_int(pt->value)); + sum += pt->value; + sample_count++; + if (sample_count == 1 || pt->value > peak) { + peak = pt->value; + } + } + + if (sample_count == 0) { + json_object_array_add(list_obj, json_object_new_int(current)); + avg = current; + peak = current; + } else { + avg = (int)((sum + (sample_count / 2)) / sample_count); + if (current > peak) { + peak = current; + } + } + + if (is_5min) { + json_object_object_add(data_obj, "range", json_object_new_string("5min")); + json_object_object_add(data_obj, "minutes", json_object_new_int(5)); + } else { + json_object_object_add(data_obj, "range", json_object_new_string((minutes >= 24 * 60) ? "day" : "hour")); + json_object_object_add(data_obj, "minutes", json_object_new_int(minutes)); + } + json_object_object_add(data_obj, "current", json_object_new_int(current)); + json_object_object_add(data_obj, "avg", json_object_new_int(avg)); + json_object_object_add(data_obj, "peak", json_object_new_int(peak)); + json_object_object_add(data_obj, "list", list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} diff --git a/open-app-filter/src/fwx_stat.h b/open-app-filter/src/fwx_stat.h new file mode 100644 index 00000000..dedd959d --- /dev/null +++ b/open-app-filter/src/fwx_stat.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) + */ + +#ifndef __FWX_STAT_H__ +#define __FWX_STAT_H__ + +#include + +int fwx_stat_read_conntrack_count(void); +void fwx_session_stat_tick(void); +struct json_object *fwx_api_get_history_session(struct json_object *req_obj); + +#endif diff --git a/open-app-filter/src/fwx_system.c b/open-app-filter/src/fwx_system.c new file mode 100644 index 00000000..464a5294 --- /dev/null +++ b/open-app-filter/src/fwx_system.c @@ -0,0 +1,292 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fwx.h" +#include "fwx_user.h" +#include "fwx_netlink.h" +#include "fwx_ubus.h" +#include "fwx_config.h" +#include "fwx_utils.h" +#include "fwx_uci.h" + +static void ensure_fwx_advanced_section(struct uci_context *uci_ctx) +{ + char section_type[32] = {0}; + + if (fwx_uci_get_value(uci_ctx, "fwx.advanced", section_type, sizeof(section_type)) != 0) + fwx_uci_set_value(uci_ctx, "fwx.advanced", "advanced"); +} + +static void ensure_fwx_status_section(struct uci_context *uci_ctx) +{ + char section_type[32] = {0}; + + if (fwx_uci_get_value(uci_ctx, "fwx.status", section_type, sizeof(section_type)) != 0) + fwx_uci_set_value(uci_ctx, "fwx.status", "status"); +} + +int fwx_get_disable_hnat(void) +{ + int disable_hnat = 0; + struct uci_context *uci_ctx = uci_alloc_context(); + + if (!uci_ctx) + return 0; + + disable_hnat = fwx_uci_get_int_value(uci_ctx, "fwx.advanced.disable_hnat"); + uci_free_context(uci_ctx); + + return disable_hnat == 1 ? 1 : 0; +} + +int fwx_get_notice_status(void) +{ + int notice_status = 0; + struct uci_context *uci_ctx = uci_alloc_context(); + + if (!uci_ctx) + return 0; + + notice_status = fwx_uci_get_int_value(uci_ctx, "fwx.status.notice_status"); + uci_free_context(uci_ctx); + + return notice_status == 1 ? 1 : 0; +} + +struct json_object *fwx_api_get_system_info(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + struct json_object *fwx_obj = json_object_new_object(); + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + json_object_put(fwx_obj); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char lan_ifname[32] = {0}; + int ret = fwx_uci_get_value(uci_ctx, "fwx.global.lan_ifname", lan_ifname, sizeof(lan_ifname) - 1); + if (ret != 0) { + strncpy(lan_ifname, "br-lan", sizeof(lan_ifname) - 1); + } + + char theme_mode_str[8] = {0}; + int theme_mode = 0; + ret = fwx_uci_get_value(uci_ctx, "fwx.global.theme_mode", theme_mode_str, sizeof(theme_mode_str) - 1); + if (ret == 0) { + theme_mode = atoi(theme_mode_str); + } + + json_object_object_add(fwx_obj, "lan_ifname", json_object_new_string(lan_ifname)); + json_object_object_add(fwx_obj, "theme_mode", json_object_new_int(theme_mode)); + json_object_object_add(data_obj, "fwx", fwx_obj); + uci_free_context(uci_ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_set_system_info(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *fwx_obj = json_object_object_get(req_obj, "fwx"); + if (!fwx_obj) { + LOG_ERROR("Missing fwx parameter\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *lan_ifname_obj = json_object_object_get(fwx_obj, "lan_ifname"); + if (!lan_ifname_obj) { + LOG_ERROR("Missing lan_ifname parameter\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *lan_ifname = json_object_get_string(lan_ifname_obj); + if (!lan_ifname || strlen(lan_ifname) == 0) { + LOG_ERROR("Invalid lan_ifname value\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (strlen(lan_ifname) < 2 || strlen(lan_ifname) > 16) { + LOG_ERROR("lan_ifname length invalid\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *theme_mode_obj = json_object_object_get(fwx_obj, "theme_mode"); + int theme_mode = 0; // 默认值为0(light) + if (theme_mode_obj) { + if (json_object_get_type(theme_mode_obj) == json_type_int) { + theme_mode = json_object_get_int(theme_mode_obj); + } else if (json_object_get_type(theme_mode_obj) == json_type_string) { + theme_mode = atoi(json_object_get_string(theme_mode_obj)); + } + // 验证值只能是0或1 + if (theme_mode != 0 && theme_mode != 1) { + LOG_ERROR("Invalid theme_mode value, must be 0 or 1\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + fwx_uci_set_value(uci_ctx, "fwx.global.lan_ifname", (char *)lan_ifname); + + char theme_mode_str[8] = {0}; + snprintf(theme_mode_str, sizeof(theme_mode_str), "%d", theme_mode); + fwx_uci_set_value(uci_ctx, "fwx.global.theme_mode", theme_mode_str); + + fwx_uci_commit(uci_ctx, "fwx"); + + update_fwx_proc_value("lan_ifname", lan_ifname); + + uci_free_context(uci_ctx); + LOG_DEBUG("Set system config: lan_ifname=%s, theme_mode=%d\n", lan_ifname, theme_mode); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +int fwx_get_tcp_rst(void) +{ + int tcp_rst = 1; + struct uci_context *uci_ctx = uci_alloc_context(); + + if (!uci_ctx) + return 1; + + tcp_rst = fwx_uci_get_int_value(uci_ctx, "fwx.global.tcp_rst"); + uci_free_context(uci_ctx); + + return tcp_rst == 0 ? 0 : 1; +} + +struct json_object *fwx_api_get_tcp_rst(struct json_object *req_obj) +{ + struct json_object *data_obj = json_object_new_object(); + + (void)req_obj; + json_object_object_add(data_obj, "tcp_rst", json_object_new_int(fwx_get_tcp_rst())); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_set_tcp_rst(struct json_object *req_obj) +{ + struct json_object *tcp_rst_obj; + int tcp_rst; + struct uci_context *uci_ctx; + + if (!req_obj) + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + + tcp_rst_obj = json_object_object_get(req_obj, "tcp_rst"); + if (!tcp_rst_obj) { + LOG_ERROR("Missing tcp_rst parameter\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + tcp_rst = json_object_get_int(tcp_rst_obj) == 0 ? 0 : 1; + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + fwx_uci_set_int_value(uci_ctx, "fwx.global.tcp_rst", tcp_rst); + fwx_uci_commit(uci_ctx, "fwx"); + uci_free_context(uci_ctx); + + update_fwx_proc_u32_value("tcp_rst", tcp_rst); + + LOG_DEBUG("Set tcp_rst=%d\n", tcp_rst); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_get_advanced_settings(struct json_object *req_obj) +{ + struct json_object *data_obj = json_object_new_object(); + + json_object_object_add(data_obj, "disable_hnat", json_object_new_int(fwx_get_disable_hnat())); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_set_advanced_settings(struct json_object *req_obj) +{ + struct json_object *disable_hnat_obj; + int disable_hnat; + struct uci_context *uci_ctx; + + if (!req_obj) + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + + disable_hnat_obj = json_object_object_get(req_obj, "disable_hnat"); + if (!disable_hnat_obj) { + LOG_ERROR("Missing disable_hnat parameter\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + disable_hnat = json_object_get_int(disable_hnat_obj) == 1 ? 1 : 0; + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + ensure_fwx_advanced_section(uci_ctx); + fwx_uci_set_int_value(uci_ctx, "fwx.advanced.disable_hnat", disable_hnat); + fwx_uci_commit(uci_ctx, "fwx"); + uci_free_context(uci_ctx); + + if (disable_hnat == 1) + system("/usr/bin/hnat.sh >/dev/null 2>&1"); + + LOG_DEBUG("Set advanced settings: disable_hnat=%d\n", disable_hnat); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_set_dashboard_notice_status(struct json_object *req_obj) +{ + struct json_object *notice_status_obj; + int notice_status = 1; + struct uci_context *uci_ctx; + + if (req_obj) { + notice_status_obj = json_object_object_get(req_obj, "notice_status"); + if (notice_status_obj) + notice_status = json_object_get_int(notice_status_obj); + } + + notice_status = notice_status == 1 ? 1 : 0; + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + ensure_fwx_status_section(uci_ctx); + fwx_uci_set_int_value(uci_ctx, "fwx.status.notice_status", notice_status); + fwx_uci_commit(uci_ctx, "fwx"); + uci_free_context(uci_ctx); + + LOG_DEBUG("Set dashboard notice_status=%d\n", notice_status); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} diff --git a/open-app-filter/src/fwx_system.h b/open-app-filter/src/fwx_system.h new file mode 100644 index 00000000..546c3158 --- /dev/null +++ b/open-app-filter/src/fwx_system.h @@ -0,0 +1,22 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_SYSTEM_H__ +#define __FWX_SYSTEM_H__ + +#include + +struct json_object *get_system_status(void); +int fwx_get_disable_hnat(void); +int fwx_get_notice_status(void); +struct json_object *fwx_api_get_system_info(struct json_object *req_obj); +struct json_object *fwx_api_set_system_info(struct json_object *req_obj); +struct json_object *fwx_api_get_tcp_rst(struct json_object *req_obj); +struct json_object *fwx_api_set_tcp_rst(struct json_object *req_obj); +struct json_object *fwx_api_get_advanced_settings(struct json_object *req_obj); +struct json_object *fwx_api_set_advanced_settings(struct json_object *req_obj); +struct json_object *fwx_api_set_dashboard_notice_status(struct json_object *req_obj); + +#endif diff --git a/open-app-filter/src/fwx_ubus.c b/open-app-filter/src/fwx_ubus.c new file mode 100644 index 00000000..59ebbfcd --- /dev/null +++ b/open-app-filter/src/fwx_ubus.c @@ -0,0 +1,8818 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fwx_user.h" +#include "fwx_config.h" +#include "fwx_feature.h" +#include "fwx_feature_online.h" +#include "fwx_custom_feature.h" +#include +#include "fwx.h" +#include "fwx_utils.h" + +#define CLIENT_DATA_BASE_DIR_DEFAULT "/tmp/fwx/client_data" +#include "fwx_app_filter.h" +#include "fwx_mac_filter.h" +#include "fwx_record.h" +#include "fwx_network.h" +#include "fwx_wireless.h" +#include "fwx_firewall.h" +#include "fwx_system.h" +#include "fwx_stat.h" +#include +#include +#include + +extern fwx_status_t g_fwx_status; +extern void reload_oaf_rule(void); + +static int ensure_feature_data_loaded(void) +{ + char *feature_data = NULL; + size_t feature_len = 0; + + if (fwx_feature_get_data(&feature_len) && feature_len > 0) + return 0; + + if (fwx_feature_decrypt_file(FWX_FEATURE_BIN_PATH, &feature_data, &feature_len) < 0) { + LOG_ERROR("class_list failed: decrypt feature file failed\n"); + return -1; + } + + fwx_feature_replace_data(feature_data, feature_len); + init_app_name_table(); + init_app_class_name_table(); + if (fwx_custom_feature_reload() < 0) + LOG_WARN("class_list warning: custom feature reload failed\n"); + else if (fwx_custom_feature_add_app_names() < 0) + LOG_WARN("class_list warning: custom app names init failed\n"); + + LOG_WARN("class_list loaded feature data without netlink\n"); + return 0; +} + +static void add_app_icon_missing_flag(struct json_object *obj, int appid) +{ + if (!obj || appid <= 0) + return; + if (!app_icon_exists_by_id(appid)) + json_object_object_add(obj, "icon", json_object_new_int(0)); +} + + +#define MAX_INTERFACE_TRAFFIC_POINTS 60 +#define INTERFACE_TRAFFIC_INTERVAL 2 +#define FWX_USER_SESSION_PROC_PATH "/proc/net/fwx_user" + +typedef struct interface_traffic_node { + struct list_head list; + unsigned long long up_bytes; + unsigned long long down_bytes; + unsigned int up_rate; + unsigned int down_rate; + u_int32_t timestamp; +} interface_traffic_node_t; + + +static LIST_HEAD(interface_traffic_list); +static int interface_traffic_count = 0; +static char g_interface_name[16] = {0}; +static unsigned long long last_up_bytes = 0; +static unsigned long long last_down_bytes = 0; +static u_int32_t last_traffic_time = 0; + +struct ubus_context *ubus_ctx = NULL; +static struct blob_buf b; + +extern char *format_time(int timetamp); + + + + +void ubus_response_json(struct ubus_context *ctx, struct ubus_request_data *req, struct json_object *response){ + struct blob_buf b_buf = {}; + blob_buf_init(&b_buf, 0); + blobmsg_add_object(&b_buf, response); + ubus_send_reply(ctx, req, b_buf.head); + blob_buf_free(&b_buf); +} + +void reload_oaf_rule(){ + system("/usr/bin/oaf_rule reload"); +} + +void get_hostname_by_mac(char *mac, char *hostname) +{ + if (!mac || !hostname) + return; + FILE *fp = fopen("/tmp/dhcp.leases", "r"); + if (!fp) + { + printf("open dhcp lease file....failed\n"); + return; + } + char line_buf[256] = {0}; + while (fgets(line_buf, sizeof(line_buf), fp)) + { + char hostname_buf[128] = {0}; + char mac_buf[32] = {0}; + sscanf(line_buf, "%*s %s %*s %s", mac_buf, hostname_buf); + if (0 == strcmp(mac, mac_buf)) + { + strcpy(hostname, hostname_buf); + } + } + fclose(fp); +} + + +int compare_lt(const void *a, const void *b) { + struct json_object *obj_a = *(struct json_object **)a; + struct json_object *obj_b = *(struct json_object **)b; + + struct json_object *lt_a, *lt_b; + json_object_object_get_ex(obj_a, "lt", <_a); + json_object_object_get_ex(obj_b, "lt", <_b); + + int lt_val_a = json_object_get_int(lt_a); + int lt_val_b = json_object_get_int(lt_b); + + return lt_val_b - lt_val_a; +} + +typedef struct active_app_visit_record { + char mac[MAX_MAC_LEN]; + char hostname[MAX_HOSTNAME_SIZE]; + char nickname[MAX_NICKNAME_SIZE]; + int appid; + int action; + u_int32_t first_time; + u_int32_t latest_time; + int total_time; +} active_app_visit_record_t; + +static int compare_active_app_visit_record(const void *a, const void *b) { + active_app_visit_record_t *pa = (active_app_visit_record_t *)a; + active_app_visit_record_t *pb = (active_app_visit_record_t *)b; + + if (pa->latest_time > pb->latest_time) + return -1; + if (pa->latest_time < pb->latest_time) + return 1; + + if (pa->total_time < pb->total_time) + return -1; + if (pa->total_time > pb->total_time) + return 1; + + return 0; +} + +static int collect_active_app_visit_records(active_app_visit_record_t *records, int max_records) { + int count = 0; + client_node_t *node = NULL; + visit_info_t *p_info = NULL; + + list_for_each_entry(node, &client_list, client) { + list_for_each_entry(p_info, &node->online_visit, visit) { + if (records && count < max_records) { + int total_time = p_info->latest_time - p_info->first_time; + if (total_time == 0) + total_time = 1; + + strncpy(records[count].mac, node->mac, sizeof(records[count].mac) - 1); + records[count].mac[sizeof(records[count].mac) - 1] = '\0'; + strncpy(records[count].hostname, node->hostname, sizeof(records[count].hostname) - 1); + records[count].hostname[sizeof(records[count].hostname) - 1] = '\0'; + strncpy(records[count].nickname, node->nickname, sizeof(records[count].nickname) - 1); + records[count].nickname[sizeof(records[count].nickname) - 1] = '\0'; + records[count].appid = p_info->appid; + records[count].action = p_info->action; + records[count].first_time = p_info->first_time; + records[count].latest_time = p_info->latest_time; + records[count].total_time = total_time; + } + count++; + } + } + + return count; +} + +static int bind_app_history_filters(sqlite3_stmt *stmt, const char *mac, int appid, u_int32_t start_time, u_int32_t end_time) { + int idx = 1; + + if (mac && strlen(mac) > 0) { + sqlite3_bind_text(stmt, idx++, mac, -1, SQLITE_STATIC); + } + + if (appid > 0) { + sqlite3_bind_int(stmt, idx++, appid); + } + + if (start_time > 0 && end_time > 0) { + sqlite3_bind_int64(stmt, idx++, start_time); + sqlite3_bind_int64(stmt, idx++, end_time); + } else if (start_time > 0) { + sqlite3_bind_int64(stmt, idx++, start_time); + } else if (end_time > 0) { + sqlite3_bind_int64(stmt, idx++, end_time); + } + + return idx; +} + + +static int +appfilter_handle_dev_visit_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int i; + struct json_object *root_obj = json_object_new_object(); + struct json_object *visit_array = json_object_new_array(); + int page = 1; + int page_size = 15; + + char *msg_obj_str = blobmsg_format_json(msg, true); + if (!msg_obj_str) + { + printf("format json failed\n"); + return 0; + } + + printf("msg_obj_str:%s\n", msg_obj_str); + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) + { + printf("mac is null\n"); + json_object_put(req_obj); + return 0; + } + + + struct json_object *page_obj = json_object_object_get(req_obj, "page"); + struct json_object *page_size_obj = json_object_object_get(req_obj, "page_size"); + if (page_obj) { + page = json_object_get_int(page_obj); + if (page < 1) page = 1; + } + if (page_size_obj) { + page_size = json_object_get_int(page_size_obj); + if (page_size < 1) page_size = 15; + } + + char *mac = json_object_get_string(mac_obj); + client_node_t *node = find_client_node(mac); + + if (!node) + { + printf("not found mac:%s\n", mac); + json_object_put(req_obj); + return 0; + } + + json_object_object_add(root_obj, "hostname", json_object_new_string(node->hostname)); + json_object_object_add(root_obj, "mac", json_object_new_string(node->mac)); + json_object_object_add(root_obj, "ip", json_object_new_string(node->ip)); + json_object_object_add(root_obj, "ipv6", json_object_new_string(node->ipv6)); + + + struct json_object *online_array = json_object_new_array(); + struct json_object *offline_array = json_object_new_array(); + visit_info_t *p_info = NULL; + + int online_num = 0; + int offline_num = 0; + + list_for_each_entry(p_info, &node->online_visit, visit) { + int total_time = p_info->latest_time - p_info->first_time; + struct json_object *visit_obj = json_object_new_object(); + json_object_object_add(visit_obj, "name", json_object_new_string(get_app_name_by_id(p_info->appid))); + json_object_object_add(visit_obj, "id", json_object_new_int(p_info->appid)); + add_app_icon_missing_flag(visit_obj, p_info->appid); + json_object_object_add(visit_obj, "act", json_object_new_int(p_info->action)); + json_object_object_add(visit_obj, "online", json_object_new_int(1)); + json_object_object_add(visit_obj, "ft", json_object_new_int(p_info->first_time)); + json_object_object_add(visit_obj, "lt", json_object_new_int(p_info->latest_time)); + json_object_object_add(visit_obj, "tt", json_object_new_int(total_time)); + json_object_array_add(online_array, visit_obj); + online_num++; + } + + list_for_each_entry(p_info, &node->visit, visit) { + int total_time = p_info->latest_time - p_info->first_time; + struct json_object *visit_obj = json_object_new_object(); + json_object_object_add(visit_obj, "name", json_object_new_string(get_app_name_by_id(p_info->appid))); + json_object_object_add(visit_obj, "id", json_object_new_int(p_info->appid)); + add_app_icon_missing_flag(visit_obj, p_info->appid); + json_object_object_add(visit_obj, "act", json_object_new_int(p_info->action)); + json_object_object_add(visit_obj, "online", json_object_new_int(0)); + json_object_object_add(visit_obj, "ft", json_object_new_int(p_info->first_time)); + json_object_object_add(visit_obj, "lt", json_object_new_int(p_info->latest_time)); + json_object_object_add(visit_obj, "tt", json_object_new_int(total_time)); + json_object_array_add(offline_array, visit_obj); + offline_num++; + } + + json_object_array_sort(online_array, compare_lt); + json_object_array_sort(offline_array, compare_lt); + + int total_num = online_num + offline_num; + int total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) total_page = 1; + if (page > total_page) page = total_page; + + + struct json_object *paged_array = json_object_new_array(); + int start_idx = (page - 1) * page_size; + int end_idx = start_idx + page_size; + if (end_idx > total_num) end_idx = total_num; + + for (i = start_idx; i < end_idx; i++) { + struct json_object *item = NULL; + if (i < online_num) { + item = json_object_array_get_idx(online_array, i); + } else { + item = json_object_array_get_idx(offline_array, i - online_num); + } + if (item) { + json_object_get(item); + json_object_array_add(paged_array, item); + } + } + + json_object_put(online_array); + json_object_put(offline_array); + + + json_object_object_add(root_obj, "total_num", json_object_new_int(total_num)); + json_object_object_add(root_obj, "total_page", json_object_new_int(total_page)); + json_object_object_add(root_obj, "page", json_object_new_int(page)); + json_object_object_add(root_obj, "page_size", json_object_new_int(page_size)); + json_object_object_add(root_obj, "list", paged_array); + + json_object_put(req_obj); + blob_buf_init(&b, 0); + blobmsg_add_object(&b, root_obj); + ubus_send_reply(ctx, req, b.head); + json_object_put(root_obj); + return 0; +} + + + +typedef struct { + int app_id; + unsigned long long total_time; +} app_stat_sort_t; + + +static int compare_app_stat_sort(const void *a, const void *b) { + app_stat_sort_t *pa = (app_stat_sort_t *)a; + app_stat_sort_t *pb = (app_stat_sort_t *)b; + if (pa->total_time > pb->total_time) + return -1; + if (pa->total_time < pb->total_time) + return 1; + return 0; +} + +void update_app_visit_time_list(char *mac, struct app_visit_stat_info *visit_info) +{ + int i; + client_node_t *node = find_client_node(mac); + if (!node) + { + printf("not found mac:%s\n", mac); + return; + } + + + app_stat_sort_t app_stats[MAX_APP_STAT_NUM * 2]; + int app_count = 0; + + visit_stat_t *stat_node = NULL; + list_for_each_entry(stat_node, &node->stat_list, list) { + if (stat_node->total_time == 0) + continue; + + + int found = 0; + for (i = 0; i < app_count; i++) { + if (app_stats[i].app_id == stat_node->appid) { + app_stats[i].total_time += stat_node->total_time; + found = 1; + break; + } + } + + + if (!found) { + if (app_count < MAX_APP_STAT_NUM * 2) { + app_stats[app_count].app_id = stat_node->appid; + app_stats[app_count].total_time = stat_node->total_time; + app_count++; + } + } + } + + + if (app_count > 0) { + qsort(app_stats, app_count, sizeof(app_stat_sort_t), compare_app_stat_sort); + + int top_count = (app_count < MAX_APP_STAT_NUM) ? app_count : MAX_APP_STAT_NUM; + for (i = 0; i < top_count; i++) { + visit_info->visit_list[i].app_id = app_stats[i].app_id; + visit_info->visit_list[i].total_time = app_stats[i].total_time; + } + visit_info->num = top_count; + } else { + visit_info->num = 0; + } +} + +void update_app_class_visit_time_list(char *mac, int *visit_time) +{ + client_node_t *node = find_client_node(mac); + if (!node) + { + printf("not found mac:%s\n", mac); + return; + } + + + visit_stat_t *stat_node = NULL; + list_for_each_entry(stat_node, &node->stat_list, list) { + if (stat_node->total_time == 0) + continue; + + int type = stat_node->appid / 1000; + if (type > 0 && type <= MAX_APP_TYPE) { + visit_time[type - 1] += stat_node->total_time; + } + } +} + +void ubus_get_dev_visit_time_info(char *mac, struct blob_buf *b) +{ + int i, j; + void *c, *array; + void *t; + void *s; + struct app_visit_stat_info info; + memset((char *)&info, 0x0, sizeof(info)); + update_app_visit_time_list(mac, &info); +} + +static int handle_debug(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int ret; + blob_buf_init(&b, 0); + char *msg_obj_str = blobmsg_format_json(msg, true); + if (!msg_obj_str) + { + printf("format json failed\n"); + return 0; + } + + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + struct json_object *debug_obj = json_object_object_get(req_obj, "debug"); + + if (debug_obj) + { + current_log_level = json_object_get_int(debug_obj); + LOG_WARN("debug level set to %d\n", current_log_level); + } + + ubus_send_reply(ctx, req, b.head); + return 0; +} + + + +typedef struct app_visit_time_info +{ + int app_id; + unsigned long long total_time; +} app_visit_time_info_t; + +int visit_time_compare(const void *a, const void *b) +{ + app_visit_time_info_t *p1 = (app_visit_time_info_t *)a; + app_visit_time_info_t *p2 = (app_visit_time_info_t *)b; + return p1->total_time < p2->total_time ? 1 : -1; +} + +#define MAX_STAT_APP_NUM 128 +void update_top5_app(client_node_t *node, app_visit_time_info_t top5_app_list[]) +{ + int i; + app_visit_time_info_t app_visit_array[MAX_STAT_APP_NUM]; + memset(app_visit_array, 0x0, sizeof(app_visit_array)); + int app_visit_num = 0; + + + visit_stat_t *stat_node = NULL; + list_for_each_entry(stat_node, &node->stat_list, list) { + if (stat_node->total_time == 0) + continue; + + + int found = 0; + for (i = 0; i < app_visit_num; i++) { + if (app_visit_array[i].app_id == stat_node->appid) { + app_visit_array[i].total_time += stat_node->total_time; + found = 1; + break; + } + } + + + if (!found && app_visit_num < MAX_STAT_APP_NUM) { + app_visit_array[app_visit_num].app_id = stat_node->appid; + app_visit_array[app_visit_num].total_time = stat_node->total_time; + app_visit_num++; + } + } + + qsort((void *)app_visit_array, app_visit_num, sizeof(app_visit_time_info_t), visit_time_compare); + + int top_count = (app_visit_num < 5) ? app_visit_num : 5; + for (i = 0; i < top_count; i++) + { + top5_app_list[i] = app_visit_array[i]; + + + } +} + +static int +appfilter_handle_dev_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int i, j; + struct json_object *root_obj = json_object_new_object(); + + struct json_object *dev_array = json_object_new_array(); + int count = 0; + client_node_t *node = NULL; + list_for_each_entry(node, &client_list, client) { + struct json_object *dev_obj = json_object_new_object(); + struct json_object *app_array = json_object_new_array(); + app_visit_time_info_t top5_app_list[5]; + memset(top5_app_list, 0x0, sizeof(top5_app_list)); + update_top5_app(node, top5_app_list); + + for (j = 0; j < 5; j++) + { + if (top5_app_list[j].app_id == 0) + break; + struct json_object *app_obj = json_object_new_object(); + json_object_object_add(app_obj, "id", json_object_new_int(top5_app_list[j].app_id)); + json_object_object_add(app_obj, "name", json_object_new_string(get_app_name_by_id(top5_app_list[j].app_id))); + add_app_icon_missing_flag(app_obj, top5_app_list[j].app_id); + json_object_array_add(app_array, app_obj); + } + + json_object_object_add(dev_obj, "applist", app_array); + json_object_object_add(dev_obj, "mac", json_object_new_string(node->mac)); + char hostname[128] = {0}; + get_hostname_by_mac(node->mac, hostname); + json_object_object_add(dev_obj, "ip", json_object_new_string(node->ip)); + json_object_object_add(dev_obj, "ipv6", json_object_new_string(node->ipv6)); + + json_object_object_add(dev_obj, "online", json_object_new_int(1)); + json_object_object_add(dev_obj, "hostname", json_object_new_string(hostname)); + json_object_object_add(dev_obj, "nickname", json_object_new_string("")); + + + json_object_array_add(dev_array, dev_obj); + + count++; + if (count >= MAX_SUPPORT_DEV_NUM) + goto END; + } + +END: + + json_object_object_add(root_obj, "devlist", dev_array); + blob_buf_init(&b, 0); + blobmsg_add_object(&b, root_obj); + ubus_send_reply(ctx, req, b.head); + json_object_put(root_obj); + return 0; +} + + +static int appfilter_handle_visit_time(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int ret; + struct app_visit_stat_info info; + blob_buf_init(&b, 0); + memset((char *)&info, 0x0, sizeof(info)); + char *msg_obj_str = blobmsg_format_json(msg, true); + if (!msg_obj_str) + { + printf("format json failed\n"); + return 0; + } + + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) + { + printf("mac is NULL\n"); + return 0; + } + update_app_visit_time_list(json_object_get_string(mac_obj), &info); + + struct json_object *resp_obj = json_object_new_object(); + struct json_object *app_info_array = json_object_new_array(); + json_object_object_add(resp_obj, "list", app_info_array); + json_object_object_add(resp_obj, "total_num", json_object_new_int(info.num)); + int i; + for (i = 0; i < info.num; i++) + { + struct json_object *app_info_obj = json_object_new_object(); + json_object_object_add(app_info_obj, "id", json_object_new_int(info.visit_list[i].app_id)); + add_app_icon_missing_flag(app_info_obj, info.visit_list[i].app_id); + json_object_object_add(app_info_obj, "name", json_object_new_string(get_app_name_by_id(info.visit_list[i].app_id))); + json_object_object_add(app_info_obj, "t", json_object_new_int(info.visit_list[i].total_time)); + json_object_array_add(app_info_array, app_info_obj); + } + + blobmsg_add_object(&b, resp_obj); + ubus_send_reply(ctx, req, b.head); + json_object_put(resp_obj); + json_object_put(req_obj); + return 0; +} + +static int +handle_app_class_visit_time(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int ret; + int i; + blob_buf_init(&b, 0); + char *msg_obj_str = blobmsg_format_json(msg, true); + if (!msg_obj_str) + { + printf("format json failed\n"); + return 0; + } + + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) + { + printf("mac is NULL\n"); + return 0; + } + int app_class_visit_time[MAX_APP_TYPE]; + memset(app_class_visit_time, 0x0, sizeof(app_class_visit_time)); + update_app_class_visit_time_list(json_object_get_string(mac_obj), app_class_visit_time); + + struct json_object *resp_obj = json_object_new_object(); + struct json_object *app_class_array = json_object_new_array(); + json_object_object_add(resp_obj, "class_list", app_class_array); + for (i = 0; i < MAX_APP_TYPE; i++) + { + if (i >= g_cur_class_num) + break; + struct json_object *app_class_obj = json_object_new_object(); + json_object_object_add(app_class_obj, "type", json_object_new_int(i)); + json_object_object_add(app_class_obj, "name", json_object_new_string(CLASS_NAME_TABLE[i])); + json_object_object_add(app_class_obj, "visit_time", json_object_new_int(app_class_visit_time[i])); + json_object_array_add(app_class_array, app_class_obj); + } + + blobmsg_add_object(&b, resp_obj); + ubus_send_reply(ctx, req, b.head); + json_object_put(resp_obj); + json_object_put(req_obj); + return 0; +} + + +static char *trim_feature_space(char *text) +{ + char *end; + + if (!text) + return NULL; + while (*text && isspace((unsigned char)*text)) + text++; + end = text + strlen(text); + while (end > text && isspace((unsigned char)end[-1])) + *--end = '\0'; + return text; +} + +static int parse_feature_app_header_text(const char *line, int *app_id, + char *app_name, size_t app_name_len) +{ + char header[128]; + char *slash; + char *id_text; + char *name_text; + char *endptr; + long id; + const char *colon; + size_t header_len; + + if (!line || !app_id || !app_name || app_name_len == 0) + return -1; + + colon = strchr(line, ':'); + if (!colon) + return -1; + header_len = (size_t)(colon - line); + if (header_len == 0 || header_len >= sizeof(header)) + return -1; + + memcpy(header, line, header_len); + header[header_len] = '\0'; + slash = strchr(header, '~'); + if (!slash) + return -1; + *slash = '\0'; + + id_text = trim_feature_space(header); + name_text = trim_feature_space(slash + 1); + if (!id_text || !id_text[0] || !name_text || !name_text[0]) + return -1; + + id = strtol(id_text, &endptr, 10); + endptr = trim_feature_space(endptr); + if (id <= 0 || (endptr && endptr[0] != '\0')) + return -1; + + *app_id = (int)id; + strncpy(app_name, name_text, app_name_len - 1); + app_name[app_name_len - 1] = '\0'; + return 0; +} + +static int parse_feature_cfg(struct json_object *class_list) { + const char *feature_data; + size_t feature_len = 0; + size_t offset = 0; + char line[1024]; + struct json_object *current_class = NULL; + struct json_object *app_list = NULL; + struct json_object *class_map[MAX_APP_TYPE + 1] = {0}; + int current_class_id = 0; + + if (ensure_feature_data_loaded() < 0) + return -1; + + feature_data = fwx_feature_get_data(&feature_len); + if (!feature_data || feature_len == 0) + return -1; + + while (fwx_feature_next_line(feature_data, feature_len, &offset, + line, sizeof(line)) != 0) { + + if (strncmp(line, "#class", 6) == 0) { + + if (current_class) { + + json_object_object_add(current_class, "app_list", app_list); + json_object_array_add(class_list, current_class); + if (current_class_id > 0 && current_class_id <= MAX_APP_TYPE) + class_map[current_class_id] = current_class; + current_class = NULL; + app_list = NULL; + } + + + char class_key[32] = {0}; + char class_name[64] = {0}; + if (sscanf(line + 7, "%31s %d %63s", class_key, + ¤t_class_id, class_name) != 3) + continue; + current_class = json_object_new_object(); + json_object_object_add(current_class, "name", json_object_new_string(class_name)); + app_list = json_object_new_array(); + } else if (current_class) { + + int appid = 0; + char app_name[64] = {0}; + if (parse_feature_app_header_text(line, &appid, app_name, sizeof(app_name)) == 0) { + char combined[256]; + if (app_icon_exists_by_id(appid)) + snprintf(combined, sizeof(combined), "%d,%s", appid, app_name); + else + snprintf(combined, sizeof(combined), "%d,%s,0", appid, app_name); + json_object_array_add(app_list, json_object_new_string(combined)); + } + } + } + + + if (current_class) { + json_object_object_add(current_class, "app_list", app_list); + json_object_array_add(class_list, current_class); + if (current_class_id > 0 && current_class_id <= MAX_APP_TYPE) + class_map[current_class_id] = current_class; + } + + fwx_custom_feature_append_class_apps(class_map, MAX_APP_TYPE + 1); + + return 0; +} + +static int handle_get_class_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { + struct json_object *response = json_object_new_object(); + struct json_object *class_list = json_object_new_array(); + + if (parse_feature_cfg(class_list) != 0) { + json_object_put(response); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + json_object_object_add(response, "class_list", class_list); + + struct blob_buf b = {}; + blob_buf_init(&b, 0); + blobmsg_add_object(&b, response); + ubus_send_reply(ctx, req, b.head); + blob_buf_free(&b); + json_object_put(response); + + return 0; +} + +typedef struct all_users_info { + int flag; + struct json_object *users_array; + int pc_status_num; + int blacklist_num; + int session_num; + struct { + char mac[32]; + char status[32]; + } pc_status_items[MAX_SUPPORT_DEV_NUM]; + char blacklist_macs[MAX_SUPPORT_DEV_NUM][32]; + struct { + char mac[32]; + int conn_count; + } session_items[MAX_SUPPORT_DEV_NUM]; +} all_users_info_t; + +#define USER_PARENTAL_CONTROL_STATUS_FILE "/tmp/fwx_cache/user_parental_control_status" +#define USER_PARENTAL_CONTROL_DETAIL_FILE "/tmp/fwx_cache/user_parental_control_detail.json" +#define BLACKLIST_MAC_FILTER_RULE_ID 102 +#define BLACKLIST_RULE_NAME "Internet Blacklist" +#define MAC_BLACKLIST_UCI_LIST "mac_blacklist.base.mac_list" +#define MAC_BLACKLIST_CONFIG "mac_blacklist" +#define MACFILTER_RULES_STATE_FILE "/tmp/macfilter_rules_state" + +static void normalize_mac_value(const char *mac, char *out, size_t out_len) +{ + const char *start = mac; + const char *end = NULL; + size_t len = 0; + size_t i; + + if (!out || out_len == 0) { + return; + } + out[0] = '\0'; + if (!mac) { + return; + } + + while (*start && isspace((unsigned char)*start)) { + start++; + } + + end = start + strlen(start); + while (end > start && isspace((unsigned char)*(end - 1))) { + end--; + } + + len = end > start ? (size_t)(end - start) : 0; + if (len >= out_len) { + len = out_len - 1; + } + + for (i = 0; i < len; i++) { + out[i] = (char)toupper((unsigned char)start[i]); + } + out[len] = '\0'; +} + +static int compare_mac_value(const void *a, const void *b) +{ + return strcasecmp((const char *)a, (const char *)b); +} + +static int mac_blacklist_has_item(char macs[][32], int count, const char *mac) +{ + int i; + + if (!mac || mac[0] == '\0') { + return 0; + } + + for (i = 0; i < count; i++) { + if (strcasecmp(macs[i], mac) == 0) { + return 1; + } + } + + return 0; +} + +static int load_mac_blacklist_items(char macs[][32], int max_count) +{ + struct uci_context *uci_ctx = NULL; + char list_buf[4096] = {0}; + char *token = NULL; + char *saveptr = NULL; + int count = 0; + + if (!macs || max_count <= 0) { + return 0; + } + + memset(macs, 0, max_count * sizeof(macs[0])); + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + return 0; + } + + if (fwx_uci_get_list_value(uci_ctx, MAC_BLACKLIST_UCI_LIST, list_buf, sizeof(list_buf), " ") == 0 && + list_buf[0] != '\0') { + token = strtok_r(list_buf, " ", &saveptr); + while (token && count < max_count) { + char normalized_mac[32] = {0}; + normalize_mac_value(token, normalized_mac, sizeof(normalized_mac)); + if (normalized_mac[0] != '\0' && !mac_blacklist_has_item(macs, count, normalized_mac)) { + strncpy(macs[count], normalized_mac, sizeof(macs[count]) - 1); + count++; + } + token = strtok_r(NULL, " ", &saveptr); + } + } + + if (count > 1) { + qsort(macs, count, sizeof(macs[0]), compare_mac_value); + } + + uci_free_context(uci_ctx); + return count; +} + +static int save_mac_blacklist_items(char macs[][32], int count) +{ + struct uci_context *uci_ctx = NULL; + int i; + int ret = 0; + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + return -1; + } + + fwx_uci_set_value(uci_ctx, "mac_blacklist.base", "settings"); + fwx_uci_delete(uci_ctx, MAC_BLACKLIST_UCI_LIST); + + for (i = 0; i < count; i++) { + if (macs[i][0] != '\0' && fwx_uci_add_list(uci_ctx, MAC_BLACKLIST_UCI_LIST, macs[i]) != UCI_OK) { + ret = -1; + break; + } + } + + if (ret == 0 && fwx_uci_commit(uci_ctx, MAC_BLACKLIST_CONFIG) != UCI_OK) { + ret = -1; + } + + uci_free_context(uci_ctx); + return ret; +} + +static void touch_macfilter_rules_state_file(void) +{ + FILE *fp = fopen(MACFILTER_RULES_STATE_FILE, "w"); + if (!fp) { + return; + } + fprintf(fp, "1\n"); + fclose(fp); +} +#define PC_PERMISSION_UNLIMITED "unlimited" +#define PC_PERMISSION_APP_LIMITED "app_limited" +#define PC_PERMISSION_MAC_BLOCKED "mac_blocked" + +static const char *map_pc_status_key(const char *status_token) +{ + if (!status_token || status_token[0] == '\0') { + return PC_PERMISSION_UNLIMITED; + } + if (strcmp(status_token, PC_PERMISSION_MAC_BLOCKED) == 0) { + return PC_PERMISSION_MAC_BLOCKED; + } + if (strcmp(status_token, PC_PERMISSION_APP_LIMITED) == 0) { + return PC_PERMISSION_APP_LIMITED; + } + return PC_PERMISSION_UNLIMITED; +} + +static void load_parental_control_status(all_users_info_t *au_info) +{ + FILE *fp = NULL; + char line[128] = {0}; + + if (!au_info) { + return; + } + + au_info->pc_status_num = 0; + fp = fopen(USER_PARENTAL_CONTROL_STATUS_FILE, "r"); + if (!fp) { + return; + } + + while (fgets(line, sizeof(line), fp)) { + char mac[32] = {0}; + char status[32] = {0}; + if (sscanf(line, "%31s %31s", mac, status) != 2) { + continue; + } + if (au_info->pc_status_num >= MAX_SUPPORT_DEV_NUM) { + break; + } + strncpy(au_info->pc_status_items[au_info->pc_status_num].mac, mac, + sizeof(au_info->pc_status_items[au_info->pc_status_num].mac) - 1); + strncpy(au_info->pc_status_items[au_info->pc_status_num].status, status, + sizeof(au_info->pc_status_items[au_info->pc_status_num].status) - 1); + au_info->pc_status_num++; + } + + fclose(fp); +} + +static void load_mac_blacklist(all_users_info_t *au_info) +{ + if (!au_info) { + return; + } + + au_info->blacklist_num = 0; + memset(au_info->blacklist_macs, 0, sizeof(au_info->blacklist_macs)); + au_info->blacklist_num = load_mac_blacklist_items(au_info->blacklist_macs, MAX_SUPPORT_DEV_NUM); +} + +static void load_user_session_count(all_users_info_t *au_info) +{ + FILE *fp = NULL; + char line[256] = {0}; + + if (!au_info) { + return; + } + + au_info->session_num = 0; + memset(au_info->session_items, 0, sizeof(au_info->session_items)); + + fp = fopen(FWX_USER_SESSION_PROC_PATH, "r"); + if (!fp) { + return; + } + + while (fgets(line, sizeof(line), fp)) { + int id = 0; + int conn_count = 0; + int session_count = 0; + char mac[32] = {0}; + + if (sscanf(line, "%d %31s %d %d", &id, mac, &conn_count, &session_count) != 4) { + continue; + } + + if (au_info->session_num >= MAX_SUPPORT_DEV_NUM) { + break; + } + + strncpy(au_info->session_items[au_info->session_num].mac, mac, + sizeof(au_info->session_items[au_info->session_num].mac) - 1); + au_info->session_items[au_info->session_num].conn_count = conn_count; + au_info->session_num++; + } + + fclose(fp); +} + +static int get_session_count_for_mac(all_users_info_t *au_info, const char *mac) +{ + int i; + + if (!au_info || !mac || mac[0] == '\0') { + return 0; + } + + for (i = 0; i < au_info->session_num; i++) { + if (strcasecmp(au_info->session_items[i].mac, mac) == 0) { + return au_info->session_items[i].conn_count; + } + } + + return 0; +} + +static int is_blacklist_mac(all_users_info_t *au_info, const char *mac) +{ + int i; + + if (!au_info || !mac || mac[0] == '\0') { + return 0; + } + + for (i = 0; i < au_info->blacklist_num; i++) { + if (strcasecmp(au_info->blacklist_macs[i], mac) == 0) { + return 1; + } + } + return 0; +} + +static int is_mac_in_blacklist_uci(const char *mac) +{ + struct uci_context *uci_ctx = NULL; + char list_buf[4096] = {0}; + char *token = NULL; + char *saveptr = NULL; + int matched = 0; + + if (!mac || mac[0] == '\0') { + return 0; + } + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + return 0; + } + + if (fwx_uci_get_list_value(uci_ctx, "mac_blacklist.base.mac_list", list_buf, sizeof(list_buf), " ") == 0 && + list_buf[0] != '\0') { + token = strtok_r(list_buf, " ", &saveptr); + while (token) { + if (strcasecmp(token, mac) == 0) { + matched = 1; + break; + } + token = strtok_r(NULL, " ", &saveptr); + } + } + + uci_free_context(uci_ctx); + return matched; +} + +static int is_mac_in_whitelist_config(const char *config, const char *mac) +{ + struct uci_context *uci_ctx = NULL; + char mac_path[128] = {0}; + char mac_str[32] = {0}; + int num = 0; + int i; + int matched = 0; + + if (!config || !mac || config[0] == '\0' || mac[0] == '\0') { + return 0; + } + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + return 0; + } + + snprintf(mac_path, sizeof(mac_path), "%s.@whitelist_mac[%%d].mac", config); + num = fwx_uci_get_list_num(uci_ctx, config, "whitelist_mac"); + for (i = 0; i < num; i++) { + memset(mac_str, 0, sizeof(mac_str)); + fwx_uci_get_array_value(uci_ctx, mac_path, i, mac_str, sizeof(mac_str)); + if (mac_str[0] != '\0' && strcasecmp(mac_str, mac) == 0) { + matched = 1; + break; + } + } + + uci_free_context(uci_ctx); + return matched; +} + +static int is_appfilter_whitelist_mac(const char *mac) +{ + return is_mac_in_whitelist_config("appfilter_whitelist", mac); +} + +static int is_macfilter_whitelist_mac(const char *mac) +{ + return is_mac_in_whitelist_config("macfilter_whitelist", mac); +} + +static const char *apply_whitelist_pc_status(const char *status_key, int af_whitelist, int mf_whitelist) +{ + if (!status_key || status_key[0] == '\0') { + return PC_PERMISSION_UNLIMITED; + } + if (af_whitelist && strcmp(status_key, PC_PERMISSION_APP_LIMITED) == 0) { + return PC_PERMISSION_UNLIMITED; + } + if (mf_whitelist && strcmp(status_key, PC_PERMISSION_MAC_BLOCKED) == 0) { + return PC_PERMISSION_UNLIMITED; + } + return status_key; +} + +static struct json_object *build_blacklist_rule_detail(const char *mac) +{ + struct json_object *rule_obj = json_object_new_object(); + if (!rule_obj) { + return NULL; + } + + json_object_object_add(rule_obj, "rule_id", json_object_new_int(BLACKLIST_MAC_FILTER_RULE_ID)); + json_object_object_add(rule_obj, "rule_name", json_object_new_string(BLACKLIST_RULE_NAME)); + json_object_object_add(rule_obj, "mode", json_object_new_int(2)); + json_object_object_add(rule_obj, "time_mode", json_object_new_int(1)); + json_object_object_add(rule_obj, "user_mac", json_object_new_string(mac ? mac : "")); + json_object_object_add(rule_obj, "match_type", json_object_new_string("blacklist")); + return rule_obj; +} + +static const char *get_pc_status_for_mac(all_users_info_t *au_info, const char *mac) +{ + int i; + + if (!au_info || !mac || mac[0] == '\0') { + return PC_PERMISSION_UNLIMITED; + } + + if (is_blacklist_mac(au_info, mac)) { + return PC_PERMISSION_MAC_BLOCKED; + } + + for (i = 0; i < au_info->pc_status_num; i++) { + if (strcasecmp(au_info->pc_status_items[i].mac, mac) == 0) { + return map_pc_status_key(au_info->pc_status_items[i].status); + } + } + return PC_PERMISSION_UNLIMITED; +} + +static const char *get_pc_status_key_for_mac(all_users_info_t *au_info, const char *mac) +{ + int i; + + if (!au_info || !mac || mac[0] == '\0') { + return PC_PERMISSION_UNLIMITED; + } + + if (is_blacklist_mac(au_info, mac)) { + return PC_PERMISSION_MAC_BLOCKED; + } + + for (i = 0; i < au_info->pc_status_num; i++) { + if (strcasecmp(au_info->pc_status_items[i].mac, mac) == 0) { + if (strcmp(au_info->pc_status_items[i].status, PC_PERMISSION_MAC_BLOCKED) == 0) { + return PC_PERMISSION_MAC_BLOCKED; + } + if (strcmp(au_info->pc_status_items[i].status, PC_PERMISSION_APP_LIMITED) == 0) { + return PC_PERMISSION_APP_LIMITED; + } + break; + } + } + + return PC_PERMISSION_UNLIMITED; +} + +static struct json_object *find_user_detail_by_mac(struct json_object *users_obj, const char *mac) +{ + struct json_object *user_obj = NULL; + if (!users_obj || !mac || mac[0] == '\0') { + return NULL; + } + + if (json_object_object_get_ex(users_obj, mac, &user_obj)) { + return user_obj; + } + + json_object_object_foreach(users_obj, key, val) { + if (key && val && strcasecmp(key, mac) == 0) { + return val; + } + } + + return NULL; +} + +static void pc_get_current_time_context(int *current_weekday, int *current_minutes) +{ + time_t now = time(NULL); + struct tm now_tm; + + if (!current_weekday || !current_minutes) { + return; + } + + *current_weekday = 0; + *current_minutes = 0; + if (localtime_r(&now, &now_tm) == NULL) { + return; + } + *current_weekday = now_tm.tm_wday; + *current_minutes = now_tm.tm_hour * 60 + now_tm.tm_min; +} + +static int pc_parse_time_minutes(const char *time_str, int *minutes) +{ + int hour = 0; + int minute = 0; + + if (!time_str || !minutes) { + return -1; + } + + if (sscanf(time_str, "%d:%d", &hour, &minute) != 2) { + return -1; + } + if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { + return -1; + } + + *minutes = hour * 60 + minute; + return 0; +} + +static int pc_weekday_in_list(struct json_object *weekdays_obj, int target_weekday) +{ + int i; + int len; + + if (!weekdays_obj || !json_object_is_type(weekdays_obj, json_type_array)) { + return 0; + } + + len = json_object_array_length(weekdays_obj); + for (i = 0; i < len; i++) { + struct json_object *day_obj = json_object_array_get_idx(weekdays_obj, i); + if (day_obj && json_object_get_int(day_obj) == target_weekday) { + return 1; + } + } + return 0; +} + +static int pc_is_time_rule_matched(struct json_object *time_rules_obj, int current_weekday, int current_minutes) +{ + int i; + int len; + int prev_weekday = (current_weekday + 6) % 7; + + if (!time_rules_obj || !json_object_is_type(time_rules_obj, json_type_array)) { + return 0; + } + + len = json_object_array_length(time_rules_obj); + for (i = 0; i < len; i++) { + struct json_object *rule_obj = json_object_array_get_idx(time_rules_obj, i); + struct json_object *weekdays_obj = NULL; + struct json_object *start_time_obj = NULL; + struct json_object *end_time_obj = NULL; + const char *start_time_str = NULL; + const char *end_time_str = NULL; + int start_minutes = 0; + int end_minutes = 0; + + if (!rule_obj || !json_object_is_type(rule_obj, json_type_object)) { + continue; + } + + if (!json_object_object_get_ex(rule_obj, "weekdays", &weekdays_obj)) { + continue; + } + if (!json_object_object_get_ex(rule_obj, "start_time", &start_time_obj) || + !json_object_object_get_ex(rule_obj, "end_time", &end_time_obj)) { + continue; + } + + start_time_str = json_object_get_string(start_time_obj); + end_time_str = json_object_get_string(end_time_obj); + if (pc_parse_time_minutes(start_time_str, &start_minutes) != 0 || + pc_parse_time_minutes(end_time_str, &end_minutes) != 0) { + continue; + } + + if (start_minutes <= end_minutes) { + if (pc_weekday_in_list(weekdays_obj, current_weekday) && + current_minutes >= start_minutes && + current_minutes <= end_minutes) { + return 1; + } + } else { + if (pc_weekday_in_list(weekdays_obj, current_weekday) && + current_minutes >= start_minutes) { + return 1; + } + if (pc_weekday_in_list(weekdays_obj, prev_weekday) && + current_minutes <= end_minutes) { + return 1; + } + } + } + + return 0; +} + +static void pc_get_today_limit(struct json_object *time_rules_obj, const char *field_key, int current_weekday, + int *has_today_rule, double *limit_value, int *has_unlimited) +{ + int i; + int len; + int found_limit = 0; + double min_limit = 0.0; + + if (has_today_rule) { + *has_today_rule = 0; + } + if (limit_value) { + *limit_value = 0.0; + } + if (has_unlimited) { + *has_unlimited = 0; + } + + if (!time_rules_obj || !json_object_is_type(time_rules_obj, json_type_array) || + !field_key || field_key[0] == '\0' || !has_today_rule || !limit_value || !has_unlimited) { + return; + } + + len = json_object_array_length(time_rules_obj); + for (i = 0; i < len; i++) { + struct json_object *rule_obj = json_object_array_get_idx(time_rules_obj, i); + struct json_object *weekdays_obj = NULL; + struct json_object *value_obj = NULL; + double value = 0.0; + + if (!rule_obj || !json_object_is_type(rule_obj, json_type_object)) { + continue; + } + + if (!json_object_object_get_ex(rule_obj, "weekdays", &weekdays_obj)) { + continue; + } + if (!pc_weekday_in_list(weekdays_obj, current_weekday)) { + continue; + } + + *has_today_rule = 1; + if (json_object_object_get_ex(rule_obj, field_key, &value_obj)) { + value = json_object_get_double(value_obj); + } + + if (value <= 0.0) { + *has_unlimited = 1; + *limit_value = 0.0; + return; + } + + if (!found_limit || value < min_limit) { + min_limit = value; + found_limit = 1; + } + } + + if (found_limit) { + *limit_value = min_limit; + } +} + +static int pc_is_rule_enabled(struct json_object *rule_obj) +{ + struct json_object *enabled_obj = NULL; + int enabled = 1; + + if (!rule_obj || !json_object_is_type(rule_obj, json_type_object)) { + return 0; + } + if (json_object_object_get_ex(rule_obj, "enabled", &enabled_obj)) { + enabled = json_object_get_int(enabled_obj); + } + return enabled == 1 ? 1 : 0; +} + +static int count_enabled_uci_rules(const char *package_name) +{ + struct uci_context *ctx = NULL; + struct uci_package *pkg = NULL; + struct uci_element *e = NULL; + int count = 0; + + if (!package_name || package_name[0] == '\0') { + return 0; + } + + ctx = uci_alloc_context(); + if (!ctx) { + return 0; + } + + if (uci_load(ctx, package_name, &pkg) != UCI_OK || !pkg) { + uci_free_context(ctx); + return 0; + } + + uci_foreach_element(&pkg->sections, e) { + struct uci_section *section = uci_to_section(e); + struct uci_option *enabled_opt = NULL; + int enabled = 1; + + if (!section || strcmp(section->type, "rule") != 0) { + continue; + } + + enabled_opt = uci_lookup_option(ctx, section, "enabled"); + if (enabled_opt && enabled_opt->type == UCI_TYPE_STRING && enabled_opt->v.string) { + enabled = atoi(enabled_opt->v.string); + } + + if (enabled == 1) { + count++; + } + } + + uci_unload(ctx, pkg); + uci_free_context(ctx); + return count; +} + +static int count_app_visit_record_rows(void) +{ + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + char db_path[512] = {0}; + int count = 0; + int rc = SQLITE_OK; + + snprintf(db_path, sizeof(db_path), "%s/client.db", get_history_data_root_dir()); + if (access(db_path, F_OK) != 0) { + return 0; + } + + rc = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY, NULL); + if (rc != SQLITE_OK || !db) { + if (db) { + sqlite3_close(db); + } + return 0; + } + + rc = sqlite3_prepare_v2(db, "SELECT COUNT(1) FROM app_visit_record;", -1, &stmt, NULL); + if (rc == SQLITE_OK && stmt && sqlite3_step(stmt) == SQLITE_ROW) { + count = sqlite3_column_int(stmt, 0); + } + + if (stmt) { + sqlite3_finalize(stmt); + } + sqlite3_close(db); + return count; +} + +static const char *json_get_string_value(struct json_object *obj, const char *key) +{ + struct json_object *value = NULL; + + if (!obj || !key || !json_object_object_get_ex(obj, key, &value) || !value) { + return ""; + } + + return json_object_get_string(value); +} + +static int json_get_int_value(struct json_object *obj, const char *key, int default_value) +{ + struct json_object *value = NULL; + + if (!obj || !key || !json_object_object_get_ex(obj, key, &value) || !value) { + return default_value; + } + + return json_object_get_int(value); +} + +static void build_dashboard_version(struct json_object *system_status, char *version, size_t len) +{ + const char *openwrt_version = json_get_string_value(system_status, "openwrt_version"); + const char *fwx_version = json_get_string_value(system_status, "fwx_version"); + const char *release_date = json_get_string_value(system_status, "release_date"); + int release_type = json_get_int_value(system_status, "release_type", 0); + char openwrt_display[96] = {0}; + + if (!version || len == 0) { + return; + } + + version[0] = '\0'; + if (openwrt_version && strcmp(openwrt_version, "SNAPSHOT") == 0 && release_date && release_date[0]) { + snprintf(openwrt_display, sizeof(openwrt_display), "s%s", release_date); + } else if (openwrt_version && openwrt_version[0]) { + snprintf(openwrt_display, sizeof(openwrt_display), "%s", openwrt_version); + } + + if (openwrt_display[0] && fwx_version && fwx_version[0]) { + snprintf(version, len, "%s-%s", openwrt_display, fwx_version); + } else if (openwrt_display[0]) { + snprintf(version, len, "%s", openwrt_display); + } else if (fwx_version && fwx_version[0]) { + snprintf(version, len, "%s", fwx_version); + } else { + snprintf(version, len, "--"); + } + + if (strcmp(version, "--") != 0) { + size_t used = strlen(version); + if (release_type == 1 && used + strlen("(alpha)") + 1 < len) { + strncat(version, "(alpha)", len - used - 1); + } else if (release_type == 2 && used + strlen("(beta)") + 1 < len) { + strncat(version, "(beta)", len - used - 1); + } + } +} + +static int pc_is_rule_applicable(struct json_object *rule_obj, const char *target_mac) +{ + struct json_object *mode_obj = NULL; + struct json_object *user_mac_obj = NULL; + const char *user_mac = NULL; + int mode = 1; + + if (!rule_obj || !json_object_is_type(rule_obj, json_type_object) || !target_mac || target_mac[0] == '\0') { + return 0; + } + + if (json_object_object_get_ex(rule_obj, "mode", &mode_obj)) { + mode = json_object_get_int(mode_obj); + } + if (json_object_object_get_ex(rule_obj, "user_mac", &user_mac_obj)) { + user_mac = json_object_get_string(user_mac_obj); + } + + if (mode == 1) { + return 1; + } + if (mode == 2) { + return (user_mac && user_mac[0] != '\0' && strcasecmp(user_mac, target_mac) == 0) ? 1 : 0; + } + + if (!user_mac || user_mac[0] == '\0') { + return 1; + } + return strcasecmp(user_mac, target_mac) == 0 ? 1 : 0; +} + +static struct json_object *pc_build_appfilter_rule(struct json_object *rule_obj, int current_weekday, int current_minutes) +{ + struct json_object *out_obj = NULL; + struct json_object *condition_obj = NULL; + struct json_object *status_obj = NULL; + struct json_object *time_rules_obj = NULL; + struct json_object *id_obj = NULL; + struct json_object *name_obj = NULL; + const char *rule_name = ""; + int rule_id = 0; + int matched = 0; + const char *permission_key = PC_PERMISSION_UNLIMITED; + + if (!rule_obj || !json_object_is_type(rule_obj, json_type_object)) { + return NULL; + } + + json_object_object_get_ex(rule_obj, "id", &id_obj); + json_object_object_get_ex(rule_obj, "name", &name_obj); + json_object_object_get_ex(rule_obj, "time_rules", &time_rules_obj); + rule_id = id_obj ? json_object_get_int(id_obj) : 0; + rule_name = name_obj ? json_object_get_string(name_obj) : ""; + if (!rule_name) { + rule_name = ""; + } + + matched = pc_is_time_rule_matched(time_rules_obj, current_weekday, current_minutes); + if (matched) { + permission_key = PC_PERMISSION_APP_LIMITED; + } + + out_obj = json_object_new_object(); + condition_obj = json_object_new_object(); + status_obj = json_object_new_object(); + if (!out_obj || !condition_obj || !status_obj) { + if (out_obj) json_object_put(out_obj); + if (condition_obj) json_object_put(condition_obj); + if (status_obj) json_object_put(status_obj); + return NULL; + } + + json_object_object_add(out_obj, "module", json_object_new_string("appfilter")); + json_object_object_add(out_obj, "rule_id", json_object_new_int(rule_id)); + json_object_object_add(out_obj, "rule_name", json_object_new_string(rule_name)); + json_object_object_add(out_obj, "condition_type", json_object_new_string("time_range")); + json_object_object_add(condition_obj, "time_rules", + (time_rules_obj && json_object_is_type(time_rules_obj, json_type_array)) ? + json_object_get(time_rules_obj) : json_object_new_array()); + json_object_object_add(out_obj, "condition", condition_obj); + + json_object_object_add(status_obj, "matched", json_object_new_int(matched)); + json_object_object_add(out_obj, "today_status", status_obj); + json_object_object_add(out_obj, "permission_key", json_object_new_string(permission_key)); + json_object_object_add(out_obj, "permission_text", json_object_new_string(permission_key)); + return out_obj; +} + +static struct json_object *pc_build_macfilter_rule(struct json_object *rule_obj, + unsigned long long today_active_time, + unsigned long long today_up_bytes, + unsigned long long today_down_bytes, + int current_weekday, + int current_minutes) +{ + struct json_object *out_obj = NULL; + struct json_object *condition_obj = NULL; + struct json_object *status_obj = NULL; + struct json_object *time_rules_obj = NULL; + struct json_object *id_obj = NULL; + struct json_object *name_obj = NULL; + struct json_object *time_mode_obj = NULL; + const char *rule_name = ""; + const char *permission_key = PC_PERMISSION_UNLIMITED; + int rule_id = 0; + int time_mode = 1; + int used_minutes = (int)(today_active_time / 60); + double used_flow_mb = ((double)(today_up_bytes + today_down_bytes)) / (1024.0 * 1024.0); + + if (!rule_obj || !json_object_is_type(rule_obj, json_type_object)) { + return NULL; + } + + json_object_object_get_ex(rule_obj, "id", &id_obj); + json_object_object_get_ex(rule_obj, "name", &name_obj); + json_object_object_get_ex(rule_obj, "time_mode", &time_mode_obj); + json_object_object_get_ex(rule_obj, "time_rules", &time_rules_obj); + + rule_id = id_obj ? json_object_get_int(id_obj) : 0; + rule_name = name_obj ? json_object_get_string(name_obj) : ""; + if (!rule_name) { + rule_name = ""; + } + if (time_mode_obj) { + time_mode = json_object_get_int(time_mode_obj); + } + + used_flow_mb = ((double)((long long)(used_flow_mb * 100.0 + 0.5))) / 100.0; + + out_obj = json_object_new_object(); + condition_obj = json_object_new_object(); + status_obj = json_object_new_object(); + if (!out_obj || !condition_obj || !status_obj) { + if (out_obj) json_object_put(out_obj); + if (condition_obj) json_object_put(condition_obj); + if (status_obj) json_object_put(status_obj); + return NULL; + } + + json_object_object_add(out_obj, "module", json_object_new_string("macfilter")); + json_object_object_add(out_obj, "rule_id", json_object_new_int(rule_id)); + json_object_object_add(out_obj, "rule_name", json_object_new_string(rule_name)); + + if (time_mode == 2) { + int has_today_rule = 0; + int has_unlimited = 0; + int exceeded = 0; + int progress_percent = 0; + double limit_value = 0.0; + int limit_minutes = 0; + + pc_get_today_limit(time_rules_obj, "duration_minutes", current_weekday, + &has_today_rule, &limit_value, &has_unlimited); + limit_minutes = (int)(limit_value + 0.5); + if (has_today_rule && !has_unlimited && limit_minutes > 0) { + exceeded = (used_minutes > limit_minutes) ? 1 : 0; + progress_percent = (int)(((double)used_minutes / (double)limit_minutes) * 100.0 + 0.5); + if (progress_percent < 0) { + progress_percent = 0; + } else if (progress_percent > 100) { + progress_percent = 100; + } + } + if (has_today_rule && !has_unlimited && limit_minutes > 0 && exceeded) { + permission_key = PC_PERMISSION_MAC_BLOCKED; + } + + json_object_object_add(out_obj, "condition_type", json_object_new_string("duration")); + json_object_object_add(condition_obj, "duration_rules", + (time_rules_obj && json_object_is_type(time_rules_obj, json_type_array)) ? + json_object_get(time_rules_obj) : json_object_new_array()); + json_object_object_add(status_obj, "used", json_object_new_int(used_minutes)); + json_object_object_add(status_obj, "limit", json_object_new_int(limit_minutes)); + json_object_object_add(status_obj, "progress_percent", json_object_new_int(progress_percent)); + json_object_object_add(status_obj, "effective_today", json_object_new_int(has_today_rule)); + json_object_object_add(status_obj, "exceeded", json_object_new_int(exceeded)); + json_object_object_add(status_obj, "unlimited", json_object_new_int(has_unlimited)); + } else if (time_mode == 3) { + int has_today_rule = 0; + int has_unlimited = 0; + int exceeded = 0; + int progress_percent = 0; + double limit_value = 0.0; + + pc_get_today_limit(time_rules_obj, "flow_mb", current_weekday, + &has_today_rule, &limit_value, &has_unlimited); + if (has_today_rule && !has_unlimited && limit_value > 0.0) { + exceeded = (used_flow_mb > limit_value) ? 1 : 0; + progress_percent = (int)((used_flow_mb / limit_value) * 100.0 + 0.5); + if (progress_percent < 0) { + progress_percent = 0; + } else if (progress_percent > 100) { + progress_percent = 100; + } + } + if (has_today_rule && !has_unlimited && limit_value > 0.0 && exceeded) { + permission_key = PC_PERMISSION_MAC_BLOCKED; + } + + json_object_object_add(out_obj, "condition_type", json_object_new_string("flow")); + json_object_object_add(condition_obj, "flow_rules", + (time_rules_obj && json_object_is_type(time_rules_obj, json_type_array)) ? + json_object_get(time_rules_obj) : json_object_new_array()); + json_object_object_add(status_obj, "used", json_object_new_double(used_flow_mb)); + json_object_object_add(status_obj, "limit", json_object_new_double(limit_value)); + json_object_object_add(status_obj, "progress_percent", json_object_new_int(progress_percent)); + json_object_object_add(status_obj, "effective_today", json_object_new_int(has_today_rule)); + json_object_object_add(status_obj, "exceeded", json_object_new_int(exceeded)); + json_object_object_add(status_obj, "unlimited", json_object_new_int(has_unlimited)); + } else { + int matched = pc_is_time_rule_matched(time_rules_obj, current_weekday, current_minutes); + if (matched) { + permission_key = PC_PERMISSION_MAC_BLOCKED; + } + + json_object_object_add(out_obj, "condition_type", json_object_new_string("time_range")); + json_object_object_add(condition_obj, "time_rules", + (time_rules_obj && json_object_is_type(time_rules_obj, json_type_array)) ? + json_object_get(time_rules_obj) : json_object_new_array()); + json_object_object_add(status_obj, "matched", json_object_new_int(matched)); + } + + json_object_object_add(out_obj, "condition", condition_obj); + json_object_object_add(out_obj, "today_status", status_obj); + json_object_object_add(out_obj, "permission_key", json_object_new_string(permission_key)); + json_object_object_add(out_obj, "permission_text", json_object_new_string(permission_key)); + return out_obj; +} + +static struct json_object *pc_build_blacklist_rule(const char *target_mac) +{ + struct json_object *out_obj = NULL; + struct json_object *condition_obj = NULL; + struct json_object *status_obj = NULL; + + (void)target_mac; + + out_obj = json_object_new_object(); + condition_obj = json_object_new_object(); + status_obj = json_object_new_object(); + if (!out_obj || !condition_obj || !status_obj) { + if (out_obj) json_object_put(out_obj); + if (condition_obj) json_object_put(condition_obj); + if (status_obj) json_object_put(status_obj); + return NULL; + } + + json_object_object_add(out_obj, "module", json_object_new_string("blacklist")); + json_object_object_add(out_obj, "rule_id", json_object_new_int(BLACKLIST_MAC_FILTER_RULE_ID)); + json_object_object_add(out_obj, "rule_name", json_object_new_string(BLACKLIST_RULE_NAME)); + json_object_object_add(out_obj, "condition_type", json_object_new_string("blacklist")); + json_object_object_add(out_obj, "condition", condition_obj); + json_object_object_add(status_obj, "matched", json_object_new_int(1)); + json_object_object_add(out_obj, "today_status", status_obj); + json_object_object_add(out_obj, "permission_key", json_object_new_string(PC_PERMISSION_MAC_BLOCKED)); + json_object_object_add(out_obj, "permission_text", json_object_new_string(PC_PERMISSION_MAC_BLOCKED)); + return out_obj; +} + +static struct json_object *pc_get_array_field(struct json_object *src_obj, const char *key) +{ + struct json_object *arr_obj = NULL; + + if (!src_obj || !key || key[0] == '\0') { + return json_object_new_array(); + } + if (json_object_object_get_ex(src_obj, key, &arr_obj) && + json_object_is_type(arr_obj, json_type_array)) { + return json_object_get(arr_obj); + } + return json_object_new_array(); +} + +static struct json_object *pc_build_cached_appfilter_rule(struct json_object *detail_obj) +{ + struct json_object *out_obj = NULL; + struct json_object *condition_obj = NULL; + struct json_object *status_obj = NULL; + struct json_object *id_obj = NULL; + struct json_object *name_obj = NULL; + const char *rule_name = ""; + int rule_id = 0; + + if (!detail_obj || !json_object_is_type(detail_obj, json_type_object)) { + return NULL; + } + + json_object_object_get_ex(detail_obj, "rule_id", &id_obj); + json_object_object_get_ex(detail_obj, "rule_name", &name_obj); + rule_id = id_obj ? json_object_get_int(id_obj) : 0; + rule_name = name_obj ? json_object_get_string(name_obj) : ""; + if (!rule_name) { + rule_name = ""; + } + + out_obj = json_object_new_object(); + condition_obj = json_object_new_object(); + status_obj = json_object_new_object(); + if (!out_obj || !condition_obj || !status_obj) { + if (out_obj) json_object_put(out_obj); + if (condition_obj) json_object_put(condition_obj); + if (status_obj) json_object_put(status_obj); + return NULL; + } + + json_object_object_add(out_obj, "module", json_object_new_string("appfilter")); + json_object_object_add(out_obj, "rule_id", json_object_new_int(rule_id)); + json_object_object_add(out_obj, "rule_name", json_object_new_string(rule_name)); + json_object_object_add(out_obj, "condition_type", json_object_new_string("time_range")); + json_object_object_add(condition_obj, "time_rules", pc_get_array_field(detail_obj, "time_rules")); + json_object_object_add(out_obj, "condition", condition_obj); + json_object_object_add(status_obj, "matched", json_object_new_int(1)); + json_object_object_add(out_obj, "today_status", status_obj); + json_object_object_add(out_obj, "permission_key", json_object_new_string(PC_PERMISSION_APP_LIMITED)); + json_object_object_add(out_obj, "permission_text", json_object_new_string(PC_PERMISSION_APP_LIMITED)); + return out_obj; +} + +static struct json_object *pc_build_cached_macfilter_rule(struct json_object *detail_obj) +{ + struct json_object *out_obj = NULL; + struct json_object *condition_obj = NULL; + struct json_object *status_obj = NULL; + struct json_object *id_obj = NULL; + struct json_object *name_obj = NULL; + struct json_object *match_type_obj = NULL; + struct json_object *used_obj = NULL; + struct json_object *limit_obj = NULL; + const char *rule_name = ""; + const char *match_type = "time_range"; + const char *module = "macfilter"; + int rule_id = 0; + + if (!detail_obj || !json_object_is_type(detail_obj, json_type_object)) { + return NULL; + } + + json_object_object_get_ex(detail_obj, "rule_id", &id_obj); + json_object_object_get_ex(detail_obj, "rule_name", &name_obj); + json_object_object_get_ex(detail_obj, "match_type", &match_type_obj); + rule_id = id_obj ? json_object_get_int(id_obj) : 0; + rule_name = name_obj ? json_object_get_string(name_obj) : ""; + match_type = match_type_obj ? json_object_get_string(match_type_obj) : "time_range"; + if (!rule_name) rule_name = ""; + if (!match_type || match_type[0] == '\0') match_type = "time_range"; + if (strcmp(match_type, "blacklist") == 0) module = "blacklist"; + + out_obj = json_object_new_object(); + condition_obj = json_object_new_object(); + status_obj = json_object_new_object(); + if (!out_obj || !condition_obj || !status_obj) { + if (out_obj) json_object_put(out_obj); + if (condition_obj) json_object_put(condition_obj); + if (status_obj) json_object_put(status_obj); + return NULL; + } + + json_object_object_add(out_obj, "module", json_object_new_string(module)); + json_object_object_add(out_obj, "rule_id", json_object_new_int(rule_id)); + json_object_object_add(out_obj, "rule_name", json_object_new_string(rule_name)); + + if (strcmp(match_type, "duration") == 0) { + int used_minutes = 0; + int limit_minutes = 0; + int progress_percent = 0; + + json_object_object_get_ex(detail_obj, "used_minutes", &used_obj); + json_object_object_get_ex(detail_obj, "limit_minutes", &limit_obj); + used_minutes = used_obj ? json_object_get_int(used_obj) : 0; + limit_minutes = limit_obj ? json_object_get_int(limit_obj) : 0; + if (limit_minutes > 0) { + progress_percent = (int)(((double)used_minutes / (double)limit_minutes) * 100.0 + 0.5); + if (progress_percent < 0) progress_percent = 0; + if (progress_percent > 100) progress_percent = 100; + } + + json_object_object_add(out_obj, "condition_type", json_object_new_string("duration")); + json_object_object_add(condition_obj, "duration_rules", pc_get_array_field(detail_obj, "duration_rules")); + json_object_object_add(status_obj, "used", json_object_new_int(used_minutes)); + json_object_object_add(status_obj, "limit", json_object_new_int(limit_minutes)); + json_object_object_add(status_obj, "progress_percent", json_object_new_int(progress_percent)); + json_object_object_add(status_obj, "effective_today", json_object_new_int(1)); + json_object_object_add(status_obj, "exceeded", json_object_new_int(1)); + json_object_object_add(status_obj, "unlimited", json_object_new_int(0)); + } else if (strcmp(match_type, "flow") == 0) { + double used_mb = 0.0; + double limit_mb = 0.0; + int progress_percent = 0; + + json_object_object_get_ex(detail_obj, "used_mb", &used_obj); + json_object_object_get_ex(detail_obj, "limit_mb", &limit_obj); + used_mb = used_obj ? json_object_get_double(used_obj) : 0.0; + limit_mb = limit_obj ? json_object_get_double(limit_obj) : 0.0; + if (limit_mb > 0.0) { + progress_percent = (int)((used_mb / limit_mb) * 100.0 + 0.5); + if (progress_percent < 0) progress_percent = 0; + if (progress_percent > 100) progress_percent = 100; + } + + json_object_object_add(out_obj, "condition_type", json_object_new_string("flow")); + json_object_object_add(condition_obj, "flow_rules", pc_get_array_field(detail_obj, "flow_rules")); + json_object_object_add(status_obj, "used", json_object_new_double(used_mb)); + json_object_object_add(status_obj, "limit", json_object_new_double(limit_mb)); + json_object_object_add(status_obj, "progress_percent", json_object_new_int(progress_percent)); + json_object_object_add(status_obj, "effective_today", json_object_new_int(1)); + json_object_object_add(status_obj, "exceeded", json_object_new_int(1)); + json_object_object_add(status_obj, "unlimited", json_object_new_int(0)); + } else if (strcmp(match_type, "blacklist") == 0) { + json_object_object_add(out_obj, "condition_type", json_object_new_string("blacklist")); + json_object_object_add(status_obj, "matched", json_object_new_int(1)); + } else { + json_object_object_add(out_obj, "condition_type", json_object_new_string("time_range")); + json_object_object_add(condition_obj, "time_rules", pc_get_array_field(detail_obj, "time_rules")); + json_object_object_add(status_obj, "matched", json_object_new_int(1)); + } + + json_object_object_add(out_obj, "condition", condition_obj); + json_object_object_add(out_obj, "today_status", status_obj); + json_object_object_add(out_obj, "permission_key", json_object_new_string(PC_PERMISSION_MAC_BLOCKED)); + json_object_object_add(out_obj, "permission_text", json_object_new_string(PC_PERMISSION_MAC_BLOCKED)); + return out_obj; +} + +static int pc_add_cached_rules(struct json_object *list_obj, struct json_object *rules_obj, int appfilter) +{ + int i; + int len; + int count = 0; + + if (!list_obj || !rules_obj || !json_object_is_type(rules_obj, json_type_array)) { + return 0; + } + + len = json_object_array_length(rules_obj); + for (i = 0; i < len; i++) { + struct json_object *detail_obj = json_object_array_get_idx(rules_obj, i); + struct json_object *item_obj = appfilter ? + pc_build_cached_appfilter_rule(detail_obj) : pc_build_cached_macfilter_rule(detail_obj); + if (item_obj) { + json_object_array_add(list_obj, item_obj); + count++; + } + } + + return count; +} + +static int pc_append_array_items(struct json_object *dst_obj, struct json_object *src_obj) +{ + int i; + int len; + int count = 0; + + if (!dst_obj || !src_obj || !json_object_is_type(dst_obj, json_type_array) || + !json_object_is_type(src_obj, json_type_array)) { + return 0; + } + + len = json_object_array_length(src_obj); + for (i = 0; i < len; i++) { + struct json_object *item_obj = json_object_array_get_idx(src_obj, i); + if (item_obj && json_object_is_type(item_obj, json_type_object)) { + json_object_array_add(dst_obj, json_object_get(item_obj)); + count++; + } + } + + return count; +} + +static struct json_object *pc_build_appfilter_rule_detail_summary(struct json_object *rule_obj) +{ + struct json_object *detail_obj = NULL; + struct json_object *id_obj = NULL; + struct json_object *name_obj = NULL; + struct json_object *mode_obj = NULL; + struct json_object *user_mac_obj = NULL; + struct json_object *time_rules_obj = NULL; + struct json_object *app_ids_obj = NULL; + const char *rule_name = ""; + const char *user_mac = ""; + int app_count = 0; + + if (!rule_obj || !json_object_is_type(rule_obj, json_type_object)) { + return NULL; + } + + detail_obj = json_object_new_object(); + if (!detail_obj) { + return NULL; + } + + json_object_object_get_ex(rule_obj, "id", &id_obj); + json_object_object_get_ex(rule_obj, "name", &name_obj); + json_object_object_get_ex(rule_obj, "mode", &mode_obj); + json_object_object_get_ex(rule_obj, "user_mac", &user_mac_obj); + json_object_object_get_ex(rule_obj, "time_rules", &time_rules_obj); + json_object_object_get_ex(rule_obj, "app_ids", &app_ids_obj); + + rule_name = name_obj ? json_object_get_string(name_obj) : ""; + user_mac = user_mac_obj ? json_object_get_string(user_mac_obj) : ""; + if (!rule_name) rule_name = ""; + if (!user_mac) user_mac = ""; + if (app_ids_obj && json_object_is_type(app_ids_obj, json_type_array)) { + app_count = json_object_array_length(app_ids_obj); + } + + json_object_object_add(detail_obj, "rule_id", json_object_new_int(id_obj ? json_object_get_int(id_obj) : 0)); + json_object_object_add(detail_obj, "rule_name", json_object_new_string(rule_name)); + json_object_object_add(detail_obj, "mode", json_object_new_int(mode_obj ? json_object_get_int(mode_obj) : 1)); + json_object_object_add(detail_obj, "user_mac", json_object_new_string(user_mac)); + json_object_object_add(detail_obj, "time_rules", + (time_rules_obj && json_object_is_type(time_rules_obj, json_type_array)) ? + json_object_get(time_rules_obj) : json_object_new_array()); + json_object_object_add(detail_obj, "category_ids", json_object_new_array()); + json_object_object_add(detail_obj, "category_stats", json_object_new_array()); + json_object_object_add(detail_obj, "category_count", json_object_new_int(0)); + json_object_object_add(detail_obj, "app_count", json_object_new_int(app_count)); + return detail_obj; +} + +static int pc_get_module_order(const char *module) +{ + if (!module || module[0] == '\0') { + return 99; + } + if (strcmp(module, "appfilter") == 0) { + return 1; + } + if (strcmp(module, "macfilter") == 0) { + return 2; + } + if (strcmp(module, "blacklist") == 0) { + return 3; + } + return 99; +} + +static int pc_compare_rule_item(const void *a, const void *b) +{ + struct json_object *obj_a = *(struct json_object **)a; + struct json_object *obj_b = *(struct json_object **)b; + struct json_object *module_a_obj = NULL; + struct json_object *module_b_obj = NULL; + struct json_object *id_a_obj = NULL; + struct json_object *id_b_obj = NULL; + struct json_object *name_a_obj = NULL; + struct json_object *name_b_obj = NULL; + const char *module_a = ""; + const char *module_b = ""; + const char *name_a = ""; + const char *name_b = ""; + int order_a; + int order_b; + int id_a = 0; + int id_b = 0; + int cmp; + + if (!obj_a && !obj_b) { + return 0; + } + if (!obj_a) { + return 1; + } + if (!obj_b) { + return -1; + } + + json_object_object_get_ex(obj_a, "module", &module_a_obj); + json_object_object_get_ex(obj_b, "module", &module_b_obj); + module_a = module_a_obj ? json_object_get_string(module_a_obj) : ""; + module_b = module_b_obj ? json_object_get_string(module_b_obj) : ""; + if (!module_a) module_a = ""; + if (!module_b) module_b = ""; + order_a = pc_get_module_order(module_a); + order_b = pc_get_module_order(module_b); + if (order_a != order_b) { + return order_a - order_b; + } + + json_object_object_get_ex(obj_a, "rule_id", &id_a_obj); + json_object_object_get_ex(obj_b, "rule_id", &id_b_obj); + id_a = id_a_obj ? json_object_get_int(id_a_obj) : 0; + id_b = id_b_obj ? json_object_get_int(id_b_obj) : 0; + if (id_a != id_b) { + return id_a - id_b; + } + + json_object_object_get_ex(obj_a, "rule_name", &name_a_obj); + json_object_object_get_ex(obj_b, "rule_name", &name_b_obj); + name_a = name_a_obj ? json_object_get_string(name_a_obj) : ""; + name_b = name_b_obj ? json_object_get_string(name_b_obj) : ""; + if (!name_a) name_a = ""; + if (!name_b) name_b = ""; + + cmp = strcmp(name_a, name_b); + if (cmp != 0) { + return cmp; + } + return 0; +} + +static struct json_object *pc_sort_rule_list(struct json_object *list_obj) +{ + int i; + int len; + struct json_object **items = NULL; + struct json_object *sorted_obj = NULL; + + if (!list_obj || !json_object_is_type(list_obj, json_type_array)) { + return NULL; + } + + len = json_object_array_length(list_obj); + sorted_obj = json_object_new_array(); + if (!sorted_obj) { + return NULL; + } + if (len <= 0) { + return sorted_obj; + } + + items = (struct json_object **)calloc((size_t)len, sizeof(struct json_object *)); + if (!items) { + json_object_put(sorted_obj); + return NULL; + } + + for (i = 0; i < len; i++) { + items[i] = json_object_array_get_idx(list_obj, i); + } + + qsort(items, (size_t)len, sizeof(struct json_object *), pc_compare_rule_item); + for (i = 0; i < len; i++) { + if (items[i]) { + json_object_array_add(sorted_obj, json_object_get(items[i])); + } + } + + free(items); + return sorted_obj; +} + +static struct json_object *pc_get_response_data(struct json_object *resp_obj) +{ + struct json_object *code_obj = NULL; + struct json_object *data_obj = NULL; + + if (!resp_obj || !json_object_is_type(resp_obj, json_type_object)) { + return NULL; + } + + if (!json_object_object_get_ex(resp_obj, "code", &code_obj) || + json_object_get_int(code_obj) != API_CODE_SUCCESS) { + return NULL; + } + + if (!json_object_object_get_ex(resp_obj, "data", &data_obj) || + !json_object_is_type(data_obj, json_type_object)) { + return NULL; + } + + return data_obj; +} + +static struct json_object *pc_get_response_list(struct json_object *resp_obj, const char *key) +{ + struct json_object *data_obj = NULL; + struct json_object *list_obj = NULL; + + if (!key || key[0] == '\0') { + return NULL; + } + + data_obj = pc_get_response_data(resp_obj); + if (!data_obj) { + return NULL; + } + if (!json_object_object_get_ex(data_obj, key, &list_obj) || + !json_object_is_type(list_obj, json_type_array)) { + return NULL; + } + return list_obj; +} + +static int pc_get_today_usage_by_mac(const char *target_mac, + unsigned long long *today_online_time, + unsigned long long *today_active_time, + unsigned long long *today_up_bytes, + unsigned long long *today_down_bytes) +{ + client_node_t *client = NULL; + int hour; + + if (!target_mac || target_mac[0] == '\0' || + !today_online_time || !today_active_time || !today_up_bytes || !today_down_bytes) { + return -1; + } + + *today_online_time = 0; + *today_active_time = 0; + *today_up_bytes = 0; + *today_down_bytes = 0; + + list_for_each_entry(client, &client_list, client) { + if (strcasecmp(client->mac, target_mac) == 0) { + daily_hourly_stat_t *today_stat = get_today_stat(client); + if (!today_stat) { + return 0; + } + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + *today_up_bytes += today_stat->hourly_traffic[hour].up_bytes; + *today_down_bytes += today_stat->hourly_traffic[hour].down_bytes; + *today_online_time += today_stat->hourly_online_time[hour]; + *today_active_time += today_stat->hourly_active_time[hour]; + } + return 0; + } + } + + return -1; +} + +void all_users_callback(void *arg, client_node_t *client) +{ + int flag = 0; + int i; + int hour; + all_users_info_t *au_info = (all_users_info_t *)arg; + if (!au_info || !client) { + LOG_ERROR("all_users_callback: arg or client is NULL\n"); + return; + } + + flag = au_info->flag; + struct json_object *users_array = au_info->users_array; + if (!users_array) { + LOG_ERROR("all_users_callback: users_array is NULL\n"); + return; + } + + int current_count = json_object_array_length(users_array); + if (current_count >= MAX_SUPPORT_DEV_NUM) + { + LOG_ERROR("all_users_callback: users_array length (%d) >= MAX_SUPPORT_DEV_NUM (%d), skipping\n", + current_count, MAX_SUPPORT_DEV_NUM); + return; + } + + LOG_DEBUG("all_users_callback: Processing client - mac=%s, online=%d, flag=%d, current_count=%d\n", + client->mac, client->online, flag, current_count); + + struct json_object *user_obj = json_object_new_object(); + if (!user_obj) { + LOG_ERROR("all_users_callback: Failed to create user_obj for mac=%s\n", client->mac); + return; + } + int af_whitelist = is_appfilter_whitelist_mac(client->mac); + int mf_whitelist = is_macfilter_whitelist_mac(client->mac); + const char *pc_status = apply_whitelist_pc_status(get_pc_status_for_mac(au_info, client->mac), af_whitelist, mf_whitelist); + const char *pc_status_key = apply_whitelist_pc_status(get_pc_status_key_for_mac(au_info, client->mac), af_whitelist, mf_whitelist); + + json_object_object_add(user_obj, "mac", json_object_new_string(client->mac)); + json_object_object_add(user_obj, "pc_status", json_object_new_string(pc_status)); + json_object_object_add(user_obj, "pc_status_key", json_object_new_string(pc_status_key)); + json_object_object_add(user_obj, "in_blacklist", json_object_new_int(is_blacklist_mac(au_info, client->mac) ? 1 : 0)); + json_object_object_add(user_obj, "af_whitelist", json_object_new_int(af_whitelist ? 1 : 0)); + json_object_object_add(user_obj, "mf_whitelist", json_object_new_int(mf_whitelist ? 1 : 0)); + json_object_object_add(user_obj, "online", json_object_new_int(client->online)); + json_object_object_add(user_obj, "active", json_object_new_int(client->active)); + json_object_object_add(user_obj, "is_wireless", json_object_new_int(client->is_wireless)); + json_object_object_add(user_obj, "terminal_type", json_object_new_string(client->is_wireless ? "wireless" : "wired")); + json_object_object_add(user_obj, "session", json_object_new_int(get_session_count_for_mac(au_info, client->mac))); + json_object_object_add(user_obj, "online_time", json_object_new_int(client->online_time)); + json_object_object_add(user_obj, "offline_time", json_object_new_int(client->offline_time)); + + if (flag > 0) { + json_object_object_add(user_obj, "ip", json_object_new_string(client->ip)); + json_object_object_add(user_obj, "ipv6", json_object_new_string(client->ipv6)); + LOG_DEBUG("all_users_callback: Added IP: %s for mac=%s\n", client->ip, client->mac); + } + + if (flag > 1){ + json_object_object_add(user_obj, "hostname", json_object_new_string(client->hostname)); + json_object_object_add(user_obj, "nickname", json_object_new_string(client->nickname)); + LOG_DEBUG("all_users_callback: Added hostname=%s, nickname=%s for mac=%s\n", + client->hostname, client->nickname, client->mac); + } + + if (flag > 2){ + struct json_object *app_array = json_object_new_array(); + app_visit_time_info_t top5_app_list[5]; + memset(top5_app_list, 0x0, sizeof(top5_app_list)); + update_top5_app(client, top5_app_list); + int app_count = 0; + for (i = 0; i < 5; i++) + { + if (top5_app_list[i].app_id == 0) + break; + + struct json_object *app_obj = json_object_new_object(); + json_object_object_add(app_obj, "id", json_object_new_int(top5_app_list[i].app_id)); + const char *app_name = get_app_name_by_id(top5_app_list[i].app_id); + json_object_object_add(app_obj, "name", json_object_new_string(app_name)); + add_app_icon_missing_flag(app_obj, top5_app_list[i].app_id); + + json_object_array_add(app_array, app_obj); + app_count++; + } + json_object_object_add(user_obj, "applist", app_array); + LOG_DEBUG("all_users_callback: Added %d apps to applist for mac=%s\n", app_count, client->mac); + + if (strlen(client->visiting_url) > 0) + json_object_object_add(user_obj, "url", json_object_new_string(client->visiting_url)); + else + json_object_object_add(user_obj, "url", json_object_new_string("")); + if (client->visiting_app > 0) { + const char *app_name = get_app_name_by_id(client->visiting_app); + json_object_object_add(user_obj, "app_id", json_object_new_int(client->visiting_app)); + json_object_object_add(user_obj, "app", json_object_new_string(app_name)); + if (!app_icon_exists_by_id(client->visiting_app)) + json_object_object_add(user_obj, "app_icon", json_object_new_int(0)); + LOG_DEBUG("all_users_callback: Added visiting app=%s (id=%d), url=%s for mac=%s\n", + app_name, client->visiting_app, client->visiting_url, client->mac); + } else { + json_object_object_add(user_obj, "app_id", json_object_new_int(0)); + json_object_object_add(user_obj, "app", json_object_new_string("")); + } + + + json_object_object_add(user_obj, "up_rate", json_object_new_int(client->up_rate)); + json_object_object_add(user_obj, "down_rate", json_object_new_int(client->down_rate)); + json_object_object_add(user_obj, "rssi", json_object_new_int(client->rssi)); + json_object_object_add(user_obj, "rx_rate", json_object_new_int(client->rx_rate)); + json_object_object_add(user_obj, "tx_rate", json_object_new_int(client->tx_rate)); + json_object_object_add(user_obj, "band", json_object_new_string(client->band)); + json_object_object_add(user_obj, "wifi_ifname", json_object_new_string(client->wifi_ifname)); + + + daily_hourly_stat_t *today_stat = get_today_stat(client); + unsigned long long today_up_bytes = 0; + unsigned long long today_down_bytes = 0; + unsigned long long today_active_time = 0; + int today_active_minutes = 0; + + if (today_stat) { + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + today_up_bytes += today_stat->hourly_traffic[hour].up_bytes; + today_down_bytes += today_stat->hourly_traffic[hour].down_bytes; + today_active_time += today_stat->hourly_active_time[hour]; + } + } + today_active_minutes = (int)(today_active_time / 60); + + + json_object_object_add(user_obj, "today_up_bytes", json_object_new_int64(today_up_bytes)); + json_object_object_add(user_obj, "today_down_bytes", json_object_new_int64(today_down_bytes)); + json_object_object_add(user_obj, "today_active_time", json_object_new_int64(today_active_time)); + json_object_object_add(user_obj, "today_active_minutes", json_object_new_int(today_active_minutes)); + } + + json_object_array_add(users_array, user_obj); + int new_count = json_object_array_length(users_array); + LOG_DEBUG("all_users_callback: Successfully added user mac=%s, array length now: %d\n", + client->mac, new_count); +} + +int compare_users(const void *a, const void *b) +{ + struct json_object *user_a = *(struct json_object **)a; + struct json_object *user_b = *(struct json_object **)b; + + struct json_object *active_a, *active_b; + struct json_object *online_a, *online_b; + json_object_object_get_ex(user_a, "online", &online_a); + json_object_object_get_ex(user_b, "online", &online_b); + json_object_object_get_ex(user_a, "active", &active_a); + json_object_object_get_ex(user_b, "active", &active_b); + + int online_val_a = online_a ? json_object_get_int(online_a) : 0; + int online_val_b = online_b ? json_object_get_int(online_b) : 0; + int active_val_a = active_a ? json_object_get_int(active_a) : 0; + int active_val_b = active_b ? json_object_get_int(active_b) : 0; + int rank_a = (online_val_a == 1 && active_val_a == 1) ? 0 : (online_val_a == 1 ? 1 : 2); + int rank_b = (online_val_b == 1 && active_val_b == 1) ? 0 : (online_val_b == 1 ? 1 : 2); + + if (rank_a != rank_b) + return rank_a - rank_b; + + struct json_object *online_time_a, *online_time_b; + json_object_object_get_ex(user_a, "online_time", &online_time_a); + json_object_object_get_ex(user_b, "online_time", &online_time_b); + + int online_time_val_a = online_time_a ? json_object_get_int(online_time_a) : 0; + int online_time_val_b = online_time_b ? json_object_get_int(online_time_b) : 0; + + if (rank_a == 0 || rank_a == 1) { + return online_time_val_b - online_time_val_a; + } else { + + struct json_object *offline_time_a, *offline_time_b; + json_object_object_get_ex(user_a, "offline_time", &offline_time_a); + json_object_object_get_ex(user_b, "offline_time", &offline_time_b); + + int offline_time_val_a = offline_time_a ? json_object_get_int(offline_time_a) : 0; + int offline_time_val_b = offline_time_b ? json_object_get_int(offline_time_b) : 0; + + return offline_time_val_b - offline_time_val_a; + } +} + +static int handle_get_all_users(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { + struct json_object *response = json_object_new_object(); + struct json_object *data_obj = json_object_new_object(); + int flag = 0; + int page = 0; + int page_size = 15; + int use_paging = 0; + int i; + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + return 0; + } + + char *msg_obj_str = blobmsg_format_json(msg, true); + if (msg_obj_str) + { + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + if (!req_obj) { + LOG_ERROR("handle_get_all_users: Failed to parse request JSON\n"); + } else { + struct json_object *flag_obj = json_object_object_get(req_obj, "flag"); + struct json_object *page_obj = json_object_object_get(req_obj, "page"); + struct json_object *page_size_obj = json_object_object_get(req_obj, "page_size"); + if (flag_obj) { + flag = json_object_get_int(flag_obj); + } + if (page_obj) { + page = json_object_get_int(page_obj); + if (page > 0) { + use_paging = 1; + } else { + page = 0; + } + } + if (use_paging && page_size_obj) { + page_size = json_object_get_int(page_size_obj); + if (page_size < 1) page_size = 15; + } + json_object_put(req_obj); + } + free(msg_obj_str); + } + + extern struct list_head client_list; + extern int g_cur_user_num; + + all_users_info_t au_info; + memset(&au_info, 0, sizeof(au_info)); + au_info.flag = flag; + au_info.users_array = json_object_new_array(); + if (!au_info.users_array) { + uci_free_context(uci_ctx); + return 0; + } + load_parental_control_status(&au_info); + load_mac_blacklist(&au_info); + load_user_session_count(&au_info); + + update_client_nickname(); + update_client_visiting_info(); + + client_foreach(&au_info, all_users_callback); + + int user_count = json_object_array_length(au_info.users_array); + + json_object_array_sort(au_info.users_array, compare_users); + + + int total_num = json_object_array_length(au_info.users_array); + int total_page = 1; + int resp_page = 0; + int resp_page_size = total_num; + struct json_object *list_obj = au_info.users_array; + + if (use_paging) { + total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) total_page = 1; + if (page > total_page) page = total_page; + + struct json_object *paged_array = json_object_new_array(); + int start_idx = (page - 1) * page_size; + int end_idx = start_idx + page_size; + if (end_idx > total_num) end_idx = total_num; + + for (i = start_idx; i < end_idx; i++) { + struct json_object *item = json_object_array_get_idx(au_info.users_array, i); + if (item) { + json_object_get(item); + json_object_array_add(paged_array, item); + } + } + + json_object_put(au_info.users_array); + list_obj = paged_array; + resp_page = page; + resp_page_size = page_size; + } + + json_object_object_add(data_obj, "list", list_obj); + json_object_object_add(data_obj, "total_num", json_object_new_int(total_num)); + json_object_object_add(data_obj, "total_page", json_object_new_int(total_page)); + json_object_object_add(data_obj, "page", json_object_new_int(resp_page)); + json_object_object_add(data_obj, "page_size", json_object_new_int(resp_page_size)); + json_object_object_add(response, "data", data_obj); + + uci_free_context(uci_ctx); + + struct blob_buf b = {}; + blob_buf_init(&b, 0); + blobmsg_add_object(&b, response); + ubus_send_reply(ctx, req, b.head); + blob_buf_free(&b); + json_object_put(response); + return 0; +} + + + + +struct json_object *fwx_api_set_nickname(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + struct json_object *nickname_obj = json_object_object_get(req_obj, "nickname"); + + if (!nickname_obj || !mac_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *mac = json_object_get_string(mac_obj); + const char *nickname = json_object_get_string(nickname_obj); + + if (!mac) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int num = fwx_uci_get_list_num(uci_ctx, "user_info", "user_info"); + char mac_str[128] = {0}; + int index = -1; + int i; + + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].mac", i, mac_str, sizeof(mac_str)); + if (strcmp(mac_str, mac) == 0) { + index = i; + LOG_DEBUG("found nickname index: %d\n", index); + break; + } + } + + if (nickname && strlen(nickname) > 0) { + if (index == -1) { + fwx_uci_add_section(uci_ctx, "user_info", "user_info"); + index = num; + } + fwx_uci_set_array_value(uci_ctx, "user_info.@user_info[%d].mac", index, (char *)mac); + fwx_uci_set_array_value(uci_ctx, "user_info.@user_info[%d].nickname", index, (char *)nickname); + } else { + if (index >= 0) { + char uci_option[128] = {0}; + snprintf(uci_option, sizeof(uci_option), "user_info.@user_info[%d]", index); + fwx_uci_delete(uci_ctx, uci_option); + LOG_DEBUG("delete nickname mac = %s\n", mac); + } + } + + fwx_uci_commit(uci_ctx, "user_info"); + reload_oaf_rule(); + uci_free_context(uci_ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_get_nickname_list(struct json_object *req_obj) +{ + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_obj = json_object_new_array(); + struct uci_context *uci_ctx = NULL; + int num = 0; + int i = 0; + + (void)req_obj; + + if (!data_obj || !list_obj) { + if (data_obj) { + json_object_put(data_obj); + } + if (list_obj) { + json_object_put(list_obj); + } + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + json_object_put(data_obj); + json_object_put(list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + num = fwx_uci_get_list_num(uci_ctx, "user_info", "user_info"); + for (i = 0; i < num; i++) { + char mac[128] = {0}; + char nickname[128] = {0}; + struct json_object *item_obj = NULL; + + fwx_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].mac", i, mac, sizeof(mac)); + fwx_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].nickname", i, nickname, sizeof(nickname)); + if (mac[0] == '\0' || nickname[0] == '\0') { + continue; + } + + item_obj = json_object_new_object(); + if (!item_obj) { + continue; + } + json_object_object_add(item_obj, "mac", json_object_new_string(mac)); + json_object_object_add(item_obj, "nickname", json_object_new_string(nickname)); + json_object_array_add(list_obj, item_obj); + } + + uci_free_context(uci_ctx); + json_object_object_add(data_obj, "list", list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_get_mac_blacklist(struct json_object *req_obj) +{ + char macs[MAX_SUPPORT_DEV_NUM][32] = {{0}}; + int count = load_mac_blacklist_items(macs, MAX_SUPPORT_DEV_NUM); + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_obj = json_object_new_array(); + int i; + + (void)req_obj; + + if (!data_obj || !list_obj) { + if (data_obj) { + json_object_put(data_obj); + } + if (list_obj) { + json_object_put(list_obj); + } + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + update_client_nickname(); + + for (i = 0; i < count; i++) { + client_node_t *dev = find_client_node(macs[i]); + struct json_object *item_obj = json_object_new_object(); + if (!item_obj) { + continue; + } + + json_object_object_add(item_obj, "mac", json_object_new_string(macs[i])); + if (dev) { + json_object_object_add(item_obj, "hostname", json_object_new_string(dev->hostname)); + json_object_object_add(item_obj, "nickname", json_object_new_string(dev->nickname)); + } else { + json_object_object_add(item_obj, "hostname", json_object_new_string("--")); + json_object_object_add(item_obj, "nickname", json_object_new_string("--")); + } + json_object_array_add(list_obj, item_obj); + } + + json_object_object_add(data_obj, "list", list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_add_mac_blacklist(struct json_object *req_obj) +{ + char macs[MAX_SUPPORT_DEV_NUM][32] = {{0}}; + int count = load_mac_blacklist_items(macs, MAX_SUPPORT_DEV_NUM); + struct json_object *mac_obj = NULL; + struct json_object *mac_list_obj = NULL; + int valid_count = 0; + int overflow = 0; + int i; + + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + mac_obj = json_object_object_get(req_obj, "mac"); + if (mac_obj) { + char normalized_mac[32] = {0}; + normalize_mac_value(json_object_get_string(mac_obj), normalized_mac, sizeof(normalized_mac)); + if (normalized_mac[0] != '\0') { + valid_count++; + if (!mac_blacklist_has_item(macs, count, normalized_mac)) { + if (count >= MAX_SUPPORT_DEV_NUM) { + overflow = 1; + } else { + strncpy(macs[count], normalized_mac, sizeof(macs[count]) - 1); + count++; + } + } + } + } + + mac_list_obj = json_object_object_get(req_obj, "mac_list"); + if (mac_list_obj && json_object_is_type(mac_list_obj, json_type_array)) { + int array_len = json_object_array_length(mac_list_obj); + for (i = 0; i < array_len; i++) { + char normalized_mac[32] = {0}; + struct json_object *item_obj = json_object_array_get_idx(mac_list_obj, i); + normalize_mac_value(json_object_get_string(item_obj), normalized_mac, sizeof(normalized_mac)); + if (normalized_mac[0] == '\0') { + continue; + } + + valid_count++; + if (mac_blacklist_has_item(macs, count, normalized_mac)) { + continue; + } + if (count >= MAX_SUPPORT_DEV_NUM) { + overflow = 1; + break; + } + + strncpy(macs[count], normalized_mac, sizeof(macs[count]) - 1); + count++; + } + } + + if (valid_count == 0 || overflow) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (count > 1) { + qsort(macs, count, sizeof(macs[0]), compare_mac_value); + } + + if (save_mac_blacklist_items(macs, count) != 0) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + touch_macfilter_rules_state_file(); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +struct json_object *fwx_api_del_mac_blacklist(struct json_object *req_obj) +{ + char macs[MAX_SUPPORT_DEV_NUM][32] = {{0}}; + char normalized_mac[32] = {0}; + int count; + int write_idx = 0; + int i; + struct json_object *mac_obj = NULL; + + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + mac_obj = json_object_object_get(req_obj, "mac"); + normalize_mac_value(mac_obj ? json_object_get_string(mac_obj) : NULL, normalized_mac, sizeof(normalized_mac)); + if (normalized_mac[0] == '\0') { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + count = load_mac_blacklist_items(macs, MAX_SUPPORT_DEV_NUM); + for (i = 0; i < count; i++) { + if (strcasecmp(macs[i], normalized_mac) == 0) { + continue; + } + if (write_idx != i) { + strncpy(macs[write_idx], macs[i], sizeof(macs[write_idx]) - 1); + macs[write_idx][sizeof(macs[write_idx]) - 1] = '\0'; + macs[i][0] = '\0'; + } + write_idx++; + } + + if (save_mac_blacklist_items(macs, write_idx) != 0) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + touch_macfilter_rules_state_file(); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + + +struct json_object *fwx_api_dev_visit_list(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *mac = json_object_get_string(mac_obj); + if (!mac) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int page = 1; + int page_size = 15; + struct json_object *page_obj = json_object_object_get(req_obj, "page"); + struct json_object *page_size_obj = json_object_object_get(req_obj, "page_size"); + if (page_obj) { + page = json_object_get_int(page_obj); + if (page < 1) page = 1; + } + if (page_size_obj) { + page_size = json_object_get_int(page_size_obj); + if (page_size < 1) page_size = 15; + } + + struct json_object *root_obj = json_object_new_object(); + struct json_object *visit_array = json_object_new_array(); + + client_node_t *node = find_client_node(mac); + if (!node) { + json_object_object_add(root_obj, "hostname", json_object_new_string("")); + json_object_object_add(root_obj, "mac", json_object_new_string(mac)); + json_object_object_add(root_obj, "ip", json_object_new_string("")); + json_object_object_add(root_obj, "ipv6", json_object_new_string("")); + json_object_object_add(root_obj, "total_num", json_object_new_int(0)); + json_object_object_add(root_obj, "total_page", json_object_new_int(1)); + json_object_object_add(root_obj, "page", json_object_new_int(page)); + json_object_object_add(root_obj, "page_size", json_object_new_int(page_size)); + json_object_object_add(root_obj, "list", visit_array); + return fwx_gen_api_response_data(API_CODE_SUCCESS, root_obj); + } + + json_object_object_add(root_obj, "hostname", json_object_new_string(node->hostname)); + json_object_object_add(root_obj, "mac", json_object_new_string(node->mac)); + json_object_object_add(root_obj, "ip", json_object_new_string(node->ip)); + + + struct json_object *online_array = json_object_new_array(); + struct json_object *offline_array = json_object_new_array(); + visit_info_t *p_info = NULL; + + int online_num = 0; + int offline_num = 0; + + list_for_each_entry(p_info, &node->online_visit, visit) { + int total_time = p_info->latest_time - p_info->first_time; + struct json_object *visit_obj = json_object_new_object(); + json_object_object_add(visit_obj, "name", json_object_new_string(get_app_name_by_id(p_info->appid))); + json_object_object_add(visit_obj, "id", json_object_new_int(p_info->appid)); + add_app_icon_missing_flag(visit_obj, p_info->appid); + json_object_object_add(visit_obj, "act", json_object_new_int(p_info->action)); + json_object_object_add(visit_obj, "online", json_object_new_int(1)); + json_object_object_add(visit_obj, "ft", json_object_new_int(p_info->first_time)); + json_object_object_add(visit_obj, "lt", json_object_new_int(p_info->latest_time)); + json_object_object_add(visit_obj, "tt", json_object_new_int(total_time)); + json_object_array_add(online_array, visit_obj); + online_num++; + } + + list_for_each_entry(p_info, &node->visit, visit) { + int total_time = p_info->latest_time - p_info->first_time; + struct json_object *visit_obj = json_object_new_object(); + json_object_object_add(visit_obj, "name", json_object_new_string(get_app_name_by_id(p_info->appid))); + json_object_object_add(visit_obj, "id", json_object_new_int(p_info->appid)); + add_app_icon_missing_flag(visit_obj, p_info->appid); + json_object_object_add(visit_obj, "act", json_object_new_int(p_info->action)); + json_object_object_add(visit_obj, "online", json_object_new_int(0)); + json_object_object_add(visit_obj, "ft", json_object_new_int(p_info->first_time)); + json_object_object_add(visit_obj, "lt", json_object_new_int(p_info->latest_time)); + json_object_object_add(visit_obj, "tt", json_object_new_int(total_time)); + json_object_array_add(offline_array, visit_obj); + offline_num++; + } + + json_object_array_sort(online_array, compare_lt); + json_object_array_sort(offline_array, compare_lt); + + int total_num = online_num + offline_num; + int total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) total_page = 1; + if (page > total_page) page = total_page; + + + struct json_object *paged_array = json_object_new_array(); + int start_idx = (page - 1) * page_size; + int end_idx = start_idx + page_size; + if (end_idx > total_num) end_idx = total_num; + + int i; + for (i = start_idx; i < end_idx; i++) { + struct json_object *item = NULL; + if (i < online_num) { + item = json_object_array_get_idx(online_array, i); + } else { + item = json_object_array_get_idx(offline_array, i - online_num); + } + if (item) { + json_object_get(item); + json_object_array_add(paged_array, item); + } + } + + json_object_put(online_array); + json_object_put(offline_array); + + json_object_object_add(root_obj, "total_num", json_object_new_int(total_num)); + json_object_object_add(root_obj, "total_page", json_object_new_int(total_page)); + json_object_object_add(root_obj, "page", json_object_new_int(page)); + json_object_object_add(root_obj, "page_size", json_object_new_int(page_size)); + json_object_object_add(root_obj, "list", paged_array); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, root_obj); +} + +struct json_object *fwx_api_get_active_app_records(struct json_object *req_obj) { + int page = 1; + int page_size = 15; + int total_num = 0; + int total_page = 1; + int start_idx = 0; + int end_idx = 0; + int i = 0; + active_app_visit_record_t *records = NULL; + struct json_object *page_obj = NULL; + struct json_object *page_size_obj = NULL; + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_obj = json_object_new_array(); + + if (req_obj) { + page_obj = json_object_object_get(req_obj, "page"); + page_size_obj = json_object_object_get(req_obj, "page_size"); + if (page_obj) { + page = json_object_get_int(page_obj); + if (page < 1) + page = 1; + } + if (page_size_obj) { + page_size = json_object_get_int(page_size_obj); + if (page_size < 1) + page_size = 15; + if (page_size > 200) + page_size = 200; + } + } + + update_client_nickname(); + + total_num = collect_active_app_visit_records(NULL, 0); + total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) + total_page = 1; + if (page > total_page) + page = total_page; + + if (total_num > 0) { + records = (active_app_visit_record_t *)calloc(total_num, sizeof(active_app_visit_record_t)); + if (!records) { + json_object_put(data_obj); + json_object_put(list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + total_num = collect_active_app_visit_records(records, total_num); + if (total_num > 1) { + qsort(records, total_num, sizeof(active_app_visit_record_t), compare_active_app_visit_record); + } + + total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) + total_page = 1; + if (page > total_page) + page = total_page; + + start_idx = (page - 1) * page_size; + end_idx = start_idx + page_size; + if (end_idx > total_num) + end_idx = total_num; + + for (i = start_idx; i < end_idx; i++) { + struct json_object *item_obj = json_object_new_object(); + json_object_object_add(item_obj, "mac", json_object_new_string(records[i].mac)); + json_object_object_add(item_obj, "hostname", json_object_new_string(records[i].hostname)); + json_object_object_add(item_obj, "nickname", json_object_new_string(records[i].nickname)); + json_object_object_add(item_obj, "name", json_object_new_string(get_app_name_by_id(records[i].appid))); + json_object_object_add(item_obj, "id", json_object_new_int(records[i].appid)); + add_app_icon_missing_flag(item_obj, records[i].appid); + json_object_object_add(item_obj, "act", json_object_new_int(records[i].action)); + json_object_object_add(item_obj, "online", json_object_new_int(1)); + json_object_object_add(item_obj, "ft", json_object_new_int(records[i].first_time)); + json_object_object_add(item_obj, "lt", json_object_new_int(records[i].latest_time)); + json_object_object_add(item_obj, "tt", json_object_new_int(records[i].total_time)); + json_object_object_add(item_obj, "appname", json_object_new_string(get_app_name_by_id(records[i].appid))); + json_object_object_add(item_obj, "appid", json_object_new_int(records[i].appid)); + json_object_object_add(item_obj, "latest_action", json_object_new_int(records[i].action)); + json_object_object_add(item_obj, "first_time", json_object_new_int(records[i].first_time)); + json_object_object_add(item_obj, "latest_time", json_object_new_int(records[i].latest_time)); + json_object_object_add(item_obj, "total_time", json_object_new_int(records[i].total_time)); + json_object_array_add(list_obj, item_obj); + } + } + + if (records) { + free(records); + } + + json_object_object_add(data_obj, "total_num", json_object_new_int(total_num)); + json_object_object_add(data_obj, "total_page", json_object_new_int(total_page)); + json_object_object_add(data_obj, "page", json_object_new_int(page)); + json_object_object_add(data_obj, "page_size", json_object_new_int(page_size)); + json_object_object_add(data_obj, "list", list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_get_app_history_records(struct json_object *req_obj) { + int page = 1; + int page_size = 15; + int total_num = 0; + int total_page = 1; + int start_idx = 0; + int end_idx = 0; + int appid = 0; + u_int32_t start_time = 0; + u_int32_t end_time = 0; + const char *mac = NULL; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + int bind_idx = 1; + char db_path[512] = {0}; + char where_sql[512] = " WHERE 1=1"; + char count_sql[1024] = {0}; + char query_sql[1200] = {0}; + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_obj = json_object_new_array(); + + if (req_obj) { + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + struct json_object *appid_obj = json_object_object_get(req_obj, "appid"); + struct json_object *start_time_obj = json_object_object_get(req_obj, "start_time"); + struct json_object *end_time_obj = json_object_object_get(req_obj, "end_time"); + struct json_object *page_obj = json_object_object_get(req_obj, "page"); + struct json_object *page_size_obj = json_object_object_get(req_obj, "page_size"); + + if (mac_obj) { + mac = json_object_get_string(mac_obj); + if (mac && strlen(mac) == 0) + mac = NULL; + } + if (appid_obj) { + appid = json_object_get_int(appid_obj); + if (appid < 0) + appid = 0; + } + if (start_time_obj) { + start_time = (u_int32_t)json_object_get_int64(start_time_obj); + } + if (end_time_obj) { + end_time = (u_int32_t)json_object_get_int64(end_time_obj); + } + if (page_obj) { + page = json_object_get_int(page_obj); + if (page < 1) + page = 1; + } + if (page_size_obj) { + page_size = json_object_get_int(page_size_obj); + if (page_size < 1) + page_size = 15; + if (page_size > 200) + page_size = 200; + } + } + + if (start_time > 0 && end_time > 0 && start_time > end_time) { + u_int32_t temp = start_time; + start_time = end_time; + end_time = temp; + } + + update_client_nickname(); + + snprintf(db_path, sizeof(db_path), "%s/client.db", get_history_data_root_dir()); + if (access(db_path, F_OK) != 0) { + json_object_object_add(data_obj, "total_num", json_object_new_int(0)); + json_object_object_add(data_obj, "total_page", json_object_new_int(1)); + json_object_object_add(data_obj, "page", json_object_new_int(page)); + json_object_object_add(data_obj, "page_size", json_object_new_int(page_size)); + json_object_object_add(data_obj, "list", list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); + } + + rc = sqlite3_open(db_path, &db); + if (rc != SQLITE_OK) { + if (db) + sqlite3_close(db); + json_object_put(data_obj); + json_object_put(list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + sqlite3_exec(db, + "CREATE TABLE IF NOT EXISTS app_visit_record (" + "mac TEXT NOT NULL," + "record_date INTEGER NOT NULL," + "appid INTEGER NOT NULL," + "start_time INTEGER NOT NULL," + "end_time INTEGER NOT NULL," + "duration INTEGER NOT NULL," + "action INTEGER NOT NULL" + ");", + NULL, NULL, NULL); + + if (mac) { + strncat(where_sql, " AND mac = ?", sizeof(where_sql) - strlen(where_sql) - 1); + } + if (appid > 0) { + strncat(where_sql, " AND appid = ?", sizeof(where_sql) - strlen(where_sql) - 1); + } + if (start_time > 0 && end_time > 0) { + strncat(where_sql, " AND end_time >= ? AND start_time <= ?", sizeof(where_sql) - strlen(where_sql) - 1); + } else if (start_time > 0) { + strncat(where_sql, " AND end_time >= ?", sizeof(where_sql) - strlen(where_sql) - 1); + } else if (end_time > 0) { + strncat(where_sql, " AND start_time <= ?", sizeof(where_sql) - strlen(where_sql) - 1); + } + + snprintf(count_sql, sizeof(count_sql), "SELECT COUNT(1) FROM app_visit_record%s;", where_sql); + rc = sqlite3_prepare_v2(db, count_sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + goto CLEANUP; + } + bind_app_history_filters(stmt, mac, appid, start_time, end_time); + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + total_num = sqlite3_column_int(stmt, 0); + } else { + rc = SQLITE_ERROR; + goto CLEANUP; + } + sqlite3_finalize(stmt); + stmt = NULL; + + total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) + total_page = 1; + if (page > total_page) + page = total_page; + + start_idx = (page - 1) * page_size; + end_idx = start_idx + page_size; + if (end_idx > total_num) + end_idx = total_num; + + if (total_num > 0 && start_idx < end_idx) { + snprintf(query_sql, sizeof(query_sql), + "SELECT mac, appid, action, start_time, end_time, duration " + "FROM app_visit_record%s " + "ORDER BY end_time DESC, duration ASC " + "LIMIT ? OFFSET ?;", + where_sql); + rc = sqlite3_prepare_v2(db, query_sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + goto CLEANUP; + } + + bind_idx = bind_app_history_filters(stmt, mac, appid, start_time, end_time); + sqlite3_bind_int(stmt, bind_idx++, page_size); + sqlite3_bind_int(stmt, bind_idx++, start_idx); + + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + const char *row_mac = (const char *)sqlite3_column_text(stmt, 0); + int row_appid = sqlite3_column_int(stmt, 1); + int row_action = sqlite3_column_int(stmt, 2); + u_int32_t row_start = (u_int32_t)sqlite3_column_int64(stmt, 3); + u_int32_t row_end = (u_int32_t)sqlite3_column_int64(stmt, 4); + int row_duration = sqlite3_column_int(stmt, 5); + struct json_object *item_obj = json_object_new_object(); + client_node_t *node = find_client_node(row_mac ? row_mac : ""); + const char *hostname = ""; + const char *nickname = ""; + + if (node) { + hostname = node->hostname; + nickname = node->nickname; + } + + json_object_object_add(item_obj, "mac", json_object_new_string(row_mac ? row_mac : "")); + json_object_object_add(item_obj, "hostname", json_object_new_string(hostname ? hostname : "")); + json_object_object_add(item_obj, "nickname", json_object_new_string(nickname ? nickname : "")); + json_object_object_add(item_obj, "name", json_object_new_string(get_app_name_by_id(row_appid))); + json_object_object_add(item_obj, "id", json_object_new_int(row_appid)); + add_app_icon_missing_flag(item_obj, row_appid); + json_object_object_add(item_obj, "act", json_object_new_int(row_action)); + json_object_object_add(item_obj, "online", json_object_new_int(0)); + json_object_object_add(item_obj, "ft", json_object_new_int(row_start)); + json_object_object_add(item_obj, "lt", json_object_new_int(row_end)); + json_object_object_add(item_obj, "tt", json_object_new_int(row_duration)); + json_object_object_add(item_obj, "appname", json_object_new_string(get_app_name_by_id(row_appid))); + json_object_object_add(item_obj, "appid", json_object_new_int(row_appid)); + json_object_object_add(item_obj, "latest_action", json_object_new_int(row_action)); + json_object_object_add(item_obj, "first_time", json_object_new_int(row_start)); + json_object_object_add(item_obj, "latest_time", json_object_new_int(row_end)); + json_object_object_add(item_obj, "total_time", json_object_new_int(row_duration)); + json_object_array_add(list_obj, item_obj); + } + if (rc != SQLITE_DONE) { + goto CLEANUP; + } + } + + rc = SQLITE_OK; + +CLEANUP: + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + if (rc != SQLITE_OK) { + json_object_put(data_obj); + json_object_put(list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + json_object_object_add(data_obj, "total_num", json_object_new_int(total_num)); + json_object_object_add(data_obj, "total_page", json_object_new_int(total_page)); + json_object_object_add(data_obj, "page", json_object_new_int(page)); + json_object_object_add(data_obj, "page_size", json_object_new_int(page_size)); + json_object_object_add(data_obj, "list", list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_dev_visit_time(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *mac = json_object_get_string(mac_obj); + if (!mac) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct app_visit_stat_info info; + memset((char *)&info, 0x0, sizeof(info)); + update_app_visit_time_list((char *)mac, &info); + + struct json_object *resp_obj = json_object_new_object(); + struct json_object *app_info_array = json_object_new_array(); + json_object_object_add(resp_obj, "list", app_info_array); + json_object_object_add(resp_obj, "total_num", json_object_new_int(info.num)); + + int i; + for (i = 0; i < info.num; i++) { + struct json_object *app_info_obj = json_object_new_object(); + json_object_object_add(app_info_obj, "id", json_object_new_int(info.visit_list[i].app_id)); + add_app_icon_missing_flag(app_info_obj, info.visit_list[i].app_id); + json_object_object_add(app_info_obj, "name", json_object_new_string(get_app_name_by_id(info.visit_list[i].app_id))); + json_object_object_add(app_info_obj, "t", json_object_new_int(info.visit_list[i].total_time)); + json_object_array_add(app_info_array, app_info_obj); + } + + return fwx_gen_api_response_data(API_CODE_SUCCESS, resp_obj); +} + + +struct json_object *fwx_api_app_class_visit_time(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *mac = json_object_get_string(mac_obj); + if (!mac) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int app_class_visit_time[MAX_APP_TYPE]; + memset(app_class_visit_time, 0x0, sizeof(app_class_visit_time)); + update_app_class_visit_time_list((char *)mac, app_class_visit_time); + + struct json_object *resp_obj = json_object_new_object(); + struct json_object *app_class_array = json_object_new_array(); + json_object_object_add(resp_obj, "class_list", app_class_array); + + extern int g_cur_class_num; + int i; + for (i = 0; i < MAX_APP_TYPE; i++) { + if (i >= g_cur_class_num) + break; + struct json_object *app_class_obj = json_object_new_object(); + json_object_object_add(app_class_obj, "type", json_object_new_int(i)); + json_object_object_add(app_class_obj, "name", json_object_new_string(CLASS_NAME_TABLE[i])); + json_object_object_add(app_class_obj, "visit_time", json_object_new_int(app_class_visit_time[i])); + json_object_array_add(app_class_array, app_class_obj); + } + + return fwx_gen_api_response_data(API_CODE_SUCCESS, resp_obj); +} + + +struct json_object *fwx_api_dev_list(struct json_object *req_obj) { + int i, j; + struct json_object *root_obj = json_object_new_object(); + struct json_object *dev_array = json_object_new_array(); + + extern struct list_head client_list; + extern int g_cur_user_num; + + client_node_t *node = NULL; + list_for_each_entry(node, &client_list, client) { + struct json_object *dev_obj = json_object_new_object(); + json_object_object_add(dev_obj, "mac", json_object_new_string(node->mac)); + json_object_object_add(dev_obj, "ip", json_object_new_string(node->ip)); + json_object_object_add(dev_obj, "ipv6", json_object_new_string(node->ipv6)); + json_object_object_add(dev_obj, "hostname", json_object_new_string(node->hostname)); + json_object_object_add(dev_obj, "nickname", json_object_new_string(node->nickname)); + json_object_object_add(dev_obj, "online", json_object_new_int(node->online)); + + app_visit_time_info_t top5_app_list[5] = {0}; + update_top5_app(node, top5_app_list); + + struct json_object *visit_info_array = json_object_new_array(); + for (i = 0; i < 5; i++) { + if (top5_app_list[i].app_id == 0) + break; + struct json_object *visit_info_obj = json_object_new_object(); + json_object_object_add(visit_info_obj, "appid", json_object_new_int(top5_app_list[i].app_id)); + add_app_icon_missing_flag(visit_info_obj, top5_app_list[i].app_id); + json_object_object_add(visit_info_obj, "appname", json_object_new_string(get_app_name_by_id(top5_app_list[i].app_id))); + json_object_object_add(visit_info_obj, "latest_time", json_object_new_int64(top5_app_list[i].total_time)); + json_object_array_add(visit_info_array, visit_info_obj); + } + json_object_object_add(dev_obj, "visit_info", visit_info_array); + json_object_array_add(dev_array, dev_obj); + } + + json_object_object_add(root_obj, "dev_list", dev_array); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, root_obj); +} + + +struct json_object *fwx_api_class_list(struct json_object *req_obj) { + struct json_object *class_list = json_object_new_array(); + + if (parse_feature_cfg(class_list) != 0) { + json_object_put(class_list); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *response = json_object_new_object(); + json_object_object_add(response, "class_list", class_list); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, response); +} + +static struct json_object *build_feature_info_object(void) +{ + struct json_object *data_obj = json_object_new_object(); + const char *feature_data; + size_t feature_len = 0; + size_t offset = 0; + char line[1024]; + char version[64] = {0}; + int feature_type = 0; + int feature_free = 0; + int loaded = 0; + int line_ret; + + feature_data = fwx_feature_get_data(&feature_len); + if (feature_data && feature_len > 0) { + loaded = 1; + while ((line_ret = fwx_feature_next_line(feature_data, feature_len, &offset, + line, sizeof(line))) != 0) { + if (line_ret < 0) + continue; + if (!line[0]) + continue; + if (!strncmp(line, "#version ", 9)) { + sscanf(line, "#version %63s", version); + } else if (!strncmp(line, "#type ", 6)) { + sscanf(line, "#type %d", &feature_type); + if (feature_type != 0 && feature_type != 1) + feature_type = 0; + } else if (!strncmp(line, "#free ", 6)) { + sscanf(line, "#free %d", &feature_free); + feature_free = feature_free ? 1 : 0; + } + } + } + + json_object_object_add(data_obj, "loaded", json_object_new_int(loaded)); + json_object_object_add(data_obj, "version", json_object_new_string(version)); + json_object_object_add(data_obj, "type", json_object_new_int(feature_type)); + json_object_object_add(data_obj, "free", json_object_new_int(feature_free)); + json_object_object_add(data_obj, "format", json_object_new_string("v4.0")); + json_object_object_add(data_obj, "app_count", json_object_new_int(g_app_count)); + return data_obj; +} + +struct json_object *fwx_api_get_feature_info(struct json_object *req_obj) { + struct json_object *data_obj = NULL; + + (void)req_obj; + data_obj = build_feature_info_object(); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_get_all_users(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int flag = 0; + int page = 0; + int page_size = 15; + int use_paging = 0; + + struct json_object *flag_obj = json_object_object_get(req_obj, "flag"); + struct json_object *page_obj = json_object_object_get(req_obj, "page"); + struct json_object *page_size_obj = json_object_object_get(req_obj, "page_size"); + + if (flag_obj) { + flag = json_object_get_int(flag_obj); + } + if (page_obj) { + page = json_object_get_int(page_obj); + if (page > 0) { + use_paging = 1; + } else { + page = 0; + } + } + if (use_paging && page_size_obj) { + page_size = json_object_get_int(page_size_obj); + if (page_size < 1) page_size = 15; + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + extern struct list_head client_list; + extern int g_cur_user_num; + + all_users_info_t au_info; + memset(&au_info, 0, sizeof(au_info)); + au_info.flag = flag; + au_info.users_array = json_object_new_array(); + if (!au_info.users_array) { + LOG_ERROR("Failed to create users_array\n"); + uci_free_context(uci_ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + load_parental_control_status(&au_info); + load_mac_blacklist(&au_info); + load_user_session_count(&au_info); + + update_client_nickname(); + update_client_visiting_info(); + client_foreach(&au_info, all_users_callback); + + int user_count = json_object_array_length(au_info.users_array); + json_object_array_sort(au_info.users_array, compare_users); + + + int total_num = json_object_array_length(au_info.users_array); + int total_page = 1; + int resp_page = 0; + int resp_page_size = total_num; + struct json_object *list_obj = au_info.users_array; + + if (use_paging) { + total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) total_page = 1; + if (page > total_page) page = total_page; + + struct json_object *paged_array = json_object_new_array(); + int start_idx = (page - 1) * page_size; + int end_idx = start_idx + page_size; + if (end_idx > total_num) end_idx = total_num; + + int i; + for (i = start_idx; i < end_idx; i++) { + struct json_object *item = json_object_array_get_idx(au_info.users_array, i); + if (item) { + json_object_get(item); + json_object_array_add(paged_array, item); + } + } + + json_object_put(au_info.users_array); + list_obj = paged_array; + resp_page = page; + resp_page_size = page_size; + } + + struct json_object *data_obj = json_object_new_object(); + json_object_object_add(data_obj, "list", list_obj); + json_object_object_add(data_obj, "total_num", json_object_new_int(total_num)); + json_object_object_add(data_obj, "total_page", json_object_new_int(total_page)); + json_object_object_add(data_obj, "page", json_object_new_int(resp_page)); + json_object_object_add(data_obj, "page_size", json_object_new_int(resp_page_size)); + + uci_free_context(uci_ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_get_parental_control_detail(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + struct json_object *appfilter_rules_out = json_object_new_array(); + struct json_object *macfilter_rules_out = json_object_new_array(); + struct json_object *mac_obj = NULL; + const char *mac = NULL; + const char *status_key = PC_PERMISSION_UNLIMITED; + FILE *fp = NULL; + long file_len = 0; + char *json_buf = NULL; + struct json_object *root_obj = NULL; + struct json_object *users_obj = NULL; + struct json_object *user_obj = NULL; + struct json_object *global_app_rules_obj = NULL; + struct json_object *global_mac_rules_obj = NULL; + struct json_object *app_rules_resp = NULL; + struct json_object *app_rules = NULL; + int current_weekday = 0; + int current_minutes = 0; + int blacklist_hit = 0; + int af_whitelist = 0; + int mf_whitelist = 0; + int i; + int len; + if (!data_obj || !appfilter_rules_out || !macfilter_rules_out) { + if (data_obj) json_object_put(data_obj); + if (appfilter_rules_out) json_object_put(appfilter_rules_out); + if (macfilter_rules_out) json_object_put(macfilter_rules_out); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (req_obj) { + mac_obj = json_object_object_get(req_obj, "mac"); + } + if (mac_obj) { + mac = json_object_get_string(mac_obj); + } + if (mac && mac[0] != '\0') { + af_whitelist = is_appfilter_whitelist_mac(mac); + mf_whitelist = is_macfilter_whitelist_mac(mac); + blacklist_hit = is_mac_in_blacklist_uci(mac); + + fp = fopen(USER_PARENTAL_CONTROL_DETAIL_FILE, "r"); + if (fp) { + if (fseek(fp, 0, SEEK_END) == 0) { + file_len = ftell(fp); + if (file_len > 0) { + rewind(fp); + json_buf = (char *)calloc(1, (size_t)file_len + 1); + if (json_buf && fread(json_buf, 1, (size_t)file_len, fp) == (size_t)file_len) { + root_obj = json_tokener_parse(json_buf); + } + } + } + fclose(fp); + } + + if (root_obj) { + json_object_object_get_ex(root_obj, "appfilter_all_user_rules", &global_app_rules_obj); + json_object_object_get_ex(root_obj, "macfilter_all_user_rules", &global_mac_rules_obj); + } + + if (root_obj && json_object_object_get_ex(root_obj, "users", &users_obj)) { + user_obj = find_user_detail_by_mac(users_obj, mac); + if (user_obj) { + struct json_object *status_obj = NULL; + struct json_object *app_rules_obj = NULL; + struct json_object *mac_rules_obj = NULL; + const char *status_raw = NULL; + + if (json_object_object_get_ex(user_obj, "pc_status", &status_obj)) { + status_raw = json_object_get_string(status_obj); + if (status_raw && status_raw[0] != '\0') { + status_key = status_raw; + } + status_key = map_pc_status_key(status_key); + } + + if (!af_whitelist && + json_object_object_get_ex(user_obj, "appfilter_rules", &app_rules_obj) && + json_object_is_type(app_rules_obj, json_type_array)) { + struct json_object *tmp_rules_out = json_object_new_array(); + if (tmp_rules_out) { + pc_append_array_items(tmp_rules_out, app_rules_obj); + json_object_put(appfilter_rules_out); + appfilter_rules_out = tmp_rules_out; + } + } + + if (!mf_whitelist && + json_object_object_get_ex(user_obj, "macfilter_rules", &mac_rules_obj) && + json_object_is_type(mac_rules_obj, json_type_array)) { + struct json_object *tmp_rules_out = json_object_new_array(); + if (tmp_rules_out) { + pc_append_array_items(tmp_rules_out, mac_rules_obj); + json_object_put(macfilter_rules_out); + macfilter_rules_out = tmp_rules_out; + } + } + } + } + + if (!af_whitelist) { + pc_append_array_items(appfilter_rules_out, global_app_rules_obj); + } + if (!mf_whitelist) { + pc_append_array_items(macfilter_rules_out, global_mac_rules_obj); + } + + if (blacklist_hit && !mf_whitelist) { + struct json_object *blacklist_rule = NULL; + struct json_object *blacklist_rules_out = NULL; + + status_key = PC_PERMISSION_MAC_BLOCKED; + + blacklist_rules_out = json_object_new_array(); + if (blacklist_rules_out) { + blacklist_rule = build_blacklist_rule_detail(mac); + if (blacklist_rule) { + json_object_array_add(blacklist_rules_out, blacklist_rule); + } + json_object_put(macfilter_rules_out); + macfilter_rules_out = blacklist_rules_out; + } + } + + if (!af_whitelist && json_object_array_length(appfilter_rules_out) == 0) { + pc_get_current_time_context(¤t_weekday, ¤t_minutes); + app_rules_resp = fwx_api_get_filter_rules(NULL); + app_rules = pc_get_response_list(app_rules_resp, "list"); + if (app_rules) { + len = json_object_array_length(app_rules); + for (i = 0; i < len; i++) { + struct json_object *rule_obj = json_object_array_get_idx(app_rules, i); + struct json_object *time_rules_obj = NULL; + struct json_object *detail_obj = NULL; + + if (!pc_is_rule_enabled(rule_obj) || !pc_is_rule_applicable(rule_obj, mac)) { + continue; + } + json_object_object_get_ex(rule_obj, "time_rules", &time_rules_obj); + if (!pc_is_time_rule_matched(time_rules_obj, current_weekday, current_minutes)) { + continue; + } + + detail_obj = pc_build_appfilter_rule_detail_summary(rule_obj); + if (detail_obj) { + json_object_array_add(appfilter_rules_out, detail_obj); + status_key = PC_PERMISSION_APP_LIMITED; + } + } + } + } + + status_key = apply_whitelist_pc_status(status_key, af_whitelist, mf_whitelist); + } + + if (app_rules_resp) { + json_object_put(app_rules_resp); + } + if (json_buf) { + free(json_buf); + } + json_object_object_add(data_obj, "pc_status", json_object_new_string(status_key)); + json_object_object_add(data_obj, "pc_status_key", json_object_new_string(status_key)); + json_object_object_add(data_obj, "af_whitelist", json_object_new_int(af_whitelist ? 1 : 0)); + json_object_object_add(data_obj, "mf_whitelist", json_object_new_int(mf_whitelist ? 1 : 0)); + json_object_object_add(data_obj, "appfilter_rules", appfilter_rules_out); + json_object_object_add(data_obj, "macfilter_rules", macfilter_rules_out); + if (root_obj) { + json_object_put(root_obj); + } + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_get_user_parental_control_rules(struct json_object *req_obj) +{ + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_obj = json_object_new_array(); + struct json_object *sorted_list_obj = NULL; + struct json_object *mac_obj = NULL; + const char *target_mac = NULL; + int current_weekday = 0; + int current_minutes = 0; + struct json_object *app_rules_resp = NULL; + struct json_object *mac_rules_resp = NULL; + struct json_object *detail_req = NULL; + struct json_object *detail_resp = NULL; + struct json_object *detail_data = NULL; + struct json_object *app_rules = NULL; + struct json_object *mac_rules = NULL; + struct json_object *cached_rules = NULL; + unsigned long long today_online_time = 0; + unsigned long long today_active_time = 0; + unsigned long long today_up_bytes = 0; + unsigned long long today_down_bytes = 0; + int i; + int len; + int af_whitelist = 0; + int mf_whitelist = 0; + int cache_rule_count = 0; + + if (!data_obj || !list_obj) { + if (data_obj) json_object_put(data_obj); + if (list_obj) json_object_put(list_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (req_obj) { + json_object_object_get_ex(req_obj, "mac", &mac_obj); + } + target_mac = mac_obj ? json_object_get_string(mac_obj) : NULL; + if (!target_mac) { + target_mac = ""; + } + + if (target_mac[0] != '\0') { + af_whitelist = is_appfilter_whitelist_mac(target_mac); + mf_whitelist = is_macfilter_whitelist_mac(target_mac); + pc_get_current_time_context(¤t_weekday, ¤t_minutes); + update_client_nickname(); + update_client_visiting_info(); + pc_get_today_usage_by_mac(target_mac, &today_online_time, &today_active_time, + &today_up_bytes, &today_down_bytes); + + detail_req = json_object_new_object(); + if (detail_req) { + json_object_object_add(detail_req, "mac", json_object_new_string(target_mac)); + detail_resp = fwx_api_get_parental_control_detail(detail_req); + detail_data = pc_get_response_data(detail_resp); + if (detail_data) { + if (!af_whitelist && json_object_object_get_ex(detail_data, "appfilter_rules", &cached_rules)) { + cache_rule_count += pc_add_cached_rules(list_obj, cached_rules, 1); + } + cached_rules = NULL; + if (!mf_whitelist && json_object_object_get_ex(detail_data, "macfilter_rules", &cached_rules)) { + cache_rule_count += pc_add_cached_rules(list_obj, cached_rules, 0); + } + } + } + + if (cache_rule_count <= 0 && !af_whitelist) { + app_rules_resp = fwx_api_get_filter_rules(NULL); + app_rules = pc_get_response_list(app_rules_resp, "list"); + if (app_rules) { + len = json_object_array_length(app_rules); + for (i = 0; i < len; i++) { + struct json_object *rule_obj = json_object_array_get_idx(app_rules, i); + struct json_object *item_obj = NULL; + + if (!pc_is_rule_enabled(rule_obj) || !pc_is_rule_applicable(rule_obj, target_mac)) { + continue; + } + + item_obj = pc_build_appfilter_rule(rule_obj, current_weekday, current_minutes); + if (item_obj) { + json_object_array_add(list_obj, item_obj); + } + } + } + } + + if (cache_rule_count <= 0 && !mf_whitelist) { + mac_rules_resp = fwx_api_get_mac_filter_rules(NULL); + mac_rules = pc_get_response_list(mac_rules_resp, "list"); + if (mac_rules) { + len = json_object_array_length(mac_rules); + for (i = 0; i < len; i++) { + struct json_object *rule_obj = json_object_array_get_idx(mac_rules, i); + struct json_object *item_obj = NULL; + + if (!pc_is_rule_enabled(rule_obj) || !pc_is_rule_applicable(rule_obj, target_mac)) { + continue; + } + + item_obj = pc_build_macfilter_rule(rule_obj, today_active_time, today_up_bytes, today_down_bytes, + current_weekday, current_minutes); + if (item_obj) { + json_object_array_add(list_obj, item_obj); + } + } + } + + if (is_mac_in_blacklist_uci(target_mac)) { + struct json_object *blacklist_obj = pc_build_blacklist_rule(target_mac); + if (blacklist_obj) { + json_object_array_add(list_obj, blacklist_obj); + } + } + } + } + + sorted_list_obj = pc_sort_rule_list(list_obj); + json_object_object_add(data_obj, "mac", json_object_new_string(target_mac)); + json_object_object_add(data_obj, "af_whitelist", json_object_new_int(af_whitelist ? 1 : 0)); + json_object_object_add(data_obj, "mf_whitelist", json_object_new_int(mf_whitelist ? 1 : 0)); + if (sorted_list_obj) { + json_object_object_add(data_obj, "list", sorted_list_obj); + json_object_put(list_obj); + } else { + json_object_object_add(data_obj, "list", list_obj); + } + + if (app_rules_resp) { + json_object_put(app_rules_resp); + } + if (mac_rules_resp) { + json_object_put(mac_rules_resp); + } + if (detail_resp) { + json_object_put(detail_resp); + } + if (detail_req) { + json_object_put(detail_req); + } + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_get_user_stat(struct json_object *req_obj) { + int hour; + int total_num = 0; + (void)req_obj; + + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_obj = json_object_new_array(); + if (!data_obj || !list_obj) { + if (data_obj) { + json_object_put(data_obj); + } + if (list_obj) { + json_object_put(list_obj); + } + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + extern struct list_head client_list; + client_node_t *node = NULL; + + list_for_each_entry(node, &client_list, client) { + unsigned long long today_up_flow = 0; + unsigned long long today_down_flow = 0; + unsigned long long today_active_time = 0; + daily_hourly_stat_t *today_stat = get_today_stat(node); + struct json_object *item_obj = NULL; + + if (today_stat) { + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + today_up_flow += today_stat->hourly_traffic[hour].up_bytes; + today_down_flow += today_stat->hourly_traffic[hour].down_bytes; + today_active_time += today_stat->hourly_active_time[hour]; + } + } + + item_obj = json_object_new_object(); + if (!item_obj) { + continue; + } + + json_object_object_add(item_obj, "m", json_object_new_string(node->mac)); + json_object_object_add(item_obj, "at", json_object_new_int64(today_active_time)); + json_object_object_add(item_obj, "uf", json_object_new_int64(today_up_flow)); + json_object_object_add(item_obj, "df", json_object_new_int64(today_down_flow)); + json_object_array_add(list_obj, item_obj); + total_num++; + } + + json_object_object_add(data_obj, "l", list_obj); + json_object_object_add(data_obj, "n", json_object_new_int(total_num)); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + + +struct json_object *fwx_api_get_system_base_info(struct json_object *req_obj) { + int user_session_enable = 0; + struct json_object *data_obj = NULL; + + (void)req_obj; + + data_obj = json_object_new_object(); + if (!data_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (access(FWX_USER_SESSION_PROC_PATH, F_OK) == 0) { + user_session_enable = 1; + } + + json_object_object_add(data_obj, "user_session_enable", json_object_new_int(user_session_enable)); + if (g_fwx_capability.wireless_support) { + json_object_object_add(data_obj, "wireless_support", json_object_new_int(1)); + } + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_visit_list(struct json_object *req_obj) { + const char *mac = NULL; + + if (req_obj) { + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (mac_obj) { + mac = json_object_get_string(mac_obj); + } + } + + struct json_object *root_obj = json_object_new_object(); + struct json_object *dev_array = json_object_new_array(); + + extern struct list_head client_list; + client_node_t *node = NULL; + visit_info_t *p_info = NULL; + + list_for_each_entry(node, &client_list, client) { + if (mac && strcmp(mac, node->mac)) { + continue; + } + + struct json_object *dev_obj = json_object_new_object(); + json_object_object_add(dev_obj, "hostname", json_object_new_string(node->hostname ? node->hostname : "unknown")); + json_object_object_add(dev_obj, "mac", json_object_new_string(node->mac)); + json_object_object_add(dev_obj, "ip", json_object_new_string(node->ip)); + json_object_object_add(dev_obj, "ipv6", json_object_new_string(node->ipv6)); + + struct json_object *online_array = json_object_new_array(); + struct json_object *offline_array = json_object_new_array(); + + list_for_each_entry(p_info, &node->online_visit, visit) { + int total_time = p_info->latest_time - p_info->first_time; + + struct json_object *visit_obj = json_object_new_object(); + json_object_object_add(visit_obj, "appname", json_object_new_string(get_app_name_by_id(p_info->appid))); + json_object_object_add(visit_obj, "appid", json_object_new_int(p_info->appid)); + add_app_icon_missing_flag(visit_obj, p_info->appid); + json_object_object_add(visit_obj, "latest_action", json_object_new_int(p_info->action)); + json_object_object_add(visit_obj, "online", json_object_new_int(1)); + json_object_object_add(visit_obj, "first_time", json_object_new_int(p_info->first_time)); + json_object_object_add(visit_obj, "latest_time", json_object_new_int(p_info->latest_time)); + json_object_object_add(visit_obj, "total_time", json_object_new_int(total_time)); + json_object_array_add(online_array, visit_obj); + } + + list_for_each_entry(p_info, &node->visit, visit) { + char *first_time_str = format_time(p_info->first_time); + char *latest_time_str = format_time(p_info->latest_time); + int total_time = p_info->latest_time - p_info->first_time; + + struct json_object *visit_obj = json_object_new_object(); + json_object_object_add(visit_obj, "appname", json_object_new_string(get_app_name_by_id(p_info->appid))); + json_object_object_add(visit_obj, "appid", json_object_new_int(p_info->appid)); + add_app_icon_missing_flag(visit_obj, p_info->appid); + json_object_object_add(visit_obj, "latest_action", json_object_new_int(p_info->action)); + json_object_object_add(visit_obj, "online", json_object_new_int(0)); + json_object_object_add(visit_obj, "first_time", json_object_new_int(p_info->first_time)); + json_object_object_add(visit_obj, "latest_time", json_object_new_int(p_info->latest_time)); + json_object_object_add(visit_obj, "total_time", json_object_new_int(total_time)); + json_object_array_add(offline_array, visit_obj); + + if (first_time_str) + free(first_time_str); + if (latest_time_str) + free(latest_time_str); + } + + json_object_array_sort(online_array, compare_lt); + json_object_array_sort(offline_array, compare_lt); + + struct json_object *visit_array = json_object_new_array(); + int online_num = json_object_array_length(online_array); + int offline_num = json_object_array_length(offline_array); + int idx; + for (idx = 0; idx < online_num; idx++) { + struct json_object *item = json_object_array_get_idx(online_array, idx); + if (item) { + json_object_get(item); + json_object_array_add(visit_array, item); + } + } + for (idx = 0; idx < offline_num; idx++) { + struct json_object *item = json_object_array_get_idx(offline_array, idx); + if (item) { + json_object_get(item); + json_object_array_add(visit_array, item); + } + } + + json_object_put(online_array); + json_object_put(offline_array); + + json_object_object_add(dev_obj, "visit_info", visit_array); + json_object_array_add(dev_array, dev_obj); + } + + json_object_object_add(root_obj, "dev_list", dev_array); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, root_obj); +} + + +static int handle_set_nickname(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { + + struct json_object *response = json_object_new_object(); + int i; + char *msg_obj_str = blobmsg_format_json(msg, true); + if (!msg_obj_str) { + printf("format json failed\n"); + return -1; + } + printf("msg_obj_str: %s\n", msg_obj_str); + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + + struct json_object *nickname_obj = json_object_object_get(req_obj, "nickname"); + if (!nickname_obj || !mac_obj) + return -1; + + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + printf("Failed to allocate UCI context\n"); + return -1; + } + int num = fwx_uci_get_list_num(uci_ctx, "user_info", "user_info"); + char mac_str[128] = {0}; + int index = -1; + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].mac", i, mac_str, sizeof(mac_str)); + if (strcmp(mac_str, json_object_get_string(mac_obj)) == 0) { + index = i; + printf("found nickname index: %d\n", index); + break; + } + } + + if (strlen(json_object_get_string(nickname_obj)) > 0) { + if (index == -1) { + fwx_uci_add_section(uci_ctx, "user_info", "user_info"); + } + fwx_uci_set_array_value(uci_ctx, "user_info.@user_info[%d].mac", index, (char *)json_object_get_string(mac_obj)); + fwx_uci_set_array_value(uci_ctx, "user_info.@user_info[%d].nickname", index, (char *)json_object_get_string(nickname_obj)); + } + else{ + char uci_option[128] = {0}; + sprintf(uci_option, "user_info.@user_info[%d]", index); + fwx_uci_delete(uci_ctx, uci_option); + printf("delete nickname mac = %s\n", json_object_get_string(mac_obj)); + } + + + fwx_uci_commit(uci_ctx, "user_info"); + reload_oaf_rule(); + + uci_free_context(uci_ctx); + struct blob_buf b = {}; + blob_buf_init(&b, 0); + blobmsg_add_object(&b, response); + ubus_send_reply(ctx, req, b.head); + blob_buf_free(&b); + json_object_put(response); + return 0; +} + +extern fwx_run_time_status_t g_af_status; + + + +static int handle_get_whitelist_user(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { + int i; + struct json_object *response = json_object_new_object(); + struct json_object *data_obj = json_object_new_object(); + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + printf("Failed to allocate UCI context\n"); + return 0; + } + + struct json_object *user_array = json_object_new_array(); + char mac_str[128] = {0}; + int num = fwx_uci_get_list_num(uci_ctx, "appfilter", "whitelist"); + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "appfilter.@whitelist[%d].mac", i, mac_str, sizeof(mac_str)); + struct json_object *user_obj = json_object_new_object(); + json_object_object_add(user_obj, "mac", json_object_new_string(mac_str)); + client_node_t *dev = find_client_node(mac_str); + if (dev){ + json_object_object_add(user_obj, "nickname", json_object_new_string(dev->nickname)); + json_object_object_add(user_obj, "hostname", json_object_new_string(dev->hostname)); + }else{ + json_object_object_add(user_obj, "nickname", json_object_new_string("")); + json_object_object_add(user_obj, "hostname", json_object_new_string("")); + } + json_object_array_add(user_array, user_obj); + } + json_object_object_add(data_obj, "list", user_array); + json_object_object_add(response, "data", data_obj); + + uci_free_context(uci_ctx); + + struct blob_buf b = {}; + blob_buf_init(&b, 0); + blobmsg_add_object(&b, response); + ubus_send_reply(ctx, req, b.head); + blob_buf_free(&b); + json_object_put(response); + return 0; +} +static int handle_add_whitelist_user(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct json_object *response = json_object_new_object(); + int i; + char *msg_obj_str = blobmsg_format_json(msg, true); + if (!msg_obj_str) { + printf("format json failed\n"); + return -1; + } + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + struct json_object *mac_array = json_object_object_get(req_obj, "mac_list"); + if (!mac_array) + return -1; + + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + return -1; + } + + int len = json_object_array_length(mac_array); + for (i = 0; i < len; i++) { + struct json_object *mac_obj = json_object_array_get_idx(mac_array, i); + fwx_uci_add_section(uci_ctx, "appfilter", "whitelist"); + fwx_uci_set_value(uci_ctx, "appfilter.@whitelist[-1].mac", (char *)json_object_get_string(mac_obj)); + } + fwx_uci_commit(uci_ctx, "appfilter"); + reload_oaf_rule(); + + uci_free_context(uci_ctx); + struct blob_buf b = {}; + blob_buf_init(&b, 0); + blobmsg_add_object(&b, response); + ubus_send_reply(ctx, req, b.head); + blob_buf_free(&b); + json_object_put(response); + return 0; +} + + +static int handle_del_whitelist_user(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { + struct json_object *response = json_object_new_object(); + int i; + char *msg_obj_str = blobmsg_format_json(msg, true); + if (!msg_obj_str) { + printf("format json failed\n"); + return 0; + } + printf("msg_obj_str: %s\n", msg_obj_str); + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + printf("mac_obj is NULL\n"); + return 0; + } + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + printf("Failed to allocate UCI context\n"); + return 0; + } + char mac_str[128] = {0}; + int num = fwx_uci_get_list_num(uci_ctx, "appfilter", "whitelist"); + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "appfilter.@whitelist[%d].mac", i, mac_str, sizeof(mac_str)); + if (strcmp(mac_str, json_object_get_string(mac_obj)) == 0) { + char buf[128] = {0}; + sprintf(buf, "appfilter.@whitelist[%d]", i); + fwx_uci_delete(uci_ctx, buf); + break; + } + } + + fwx_uci_commit(uci_ctx, "appfilter"); + reload_oaf_rule(); + + uci_free_context(uci_ctx); + struct blob_buf b = {}; + blob_buf_init(&b, 0); + blobmsg_add_object(&b, response); + ubus_send_reply(ctx, req, b.head); + blob_buf_free(&b); + json_object_put(response); + return 0; +} + + +static char *get_model(void) { + char model[32] = {0}; + struct json_object *board_json = json_object_from_file("/etc/board.json"); + if (!board_json) { + strcpy(model, "Unknown"); + } else { + struct json_object *model_obj = json_object_object_get(board_json, "model"); + if (model_obj) { + struct json_object *name_obj = json_object_object_get(model_obj, "name"); + if (name_obj) + strncpy(model, json_object_get_string(name_obj), sizeof(model) - 1); + else + strcpy(model, "Unknown"); + } else { + strcpy(model, "Unknown"); + } + } + if (board_json) + json_object_put(board_json); + + + if (strcmp(model, "Unknown") == 0) { + char buf[256] = {0}; + if (exec_with_result_line("cat /proc/device-tree/model 2>/dev/null || cat /tmp/sysinfo/board_name 2>/dev/null || echo Unknown", buf, sizeof(buf)) == 0 && strlen(buf) > 0) { + strncpy(model, buf, sizeof(model) - 1); + } + } + + return strdup(model); +} + + +static int get_uptime(void) { + FILE *uptime_fp = fopen("/proc/uptime", "r"); + if (uptime_fp) { + double uptime_sec = 0; + if (fscanf(uptime_fp, "%lf", &uptime_sec) == 1) { + fclose(uptime_fp); + return (int)uptime_sec; + } + fclose(uptime_fp); + } + return 0; +} + + +static int read_file_buf(const char *file, char *buf, int len) { + if (!file || !buf || len <= 0) + return -1; + + int fd = open(file, O_RDONLY | O_NONBLOCK, 0644); + if (fd < 0) { + return -1; + } + + int size = read(fd, buf, len - 1); + close(fd); + + if (size > 0) { + buf[size] = '\0'; + str_trim(buf); + return size; + } + + return -1; +} + +static int temperature_value_valid(int temp) +{ + return temp >= -50 && temp <= 150; +} + +static int parse_temperature_value(const char *buf, int *temp) +{ + const char *p; + + if (!buf || !temp) { + return -1; + } + + p = buf; + while (*p) { + if (isdigit((unsigned char)*p) || *p == '-' || *p == '+') { + char *end = NULL; + float value = strtof(p, &end); + if (end && end > p) { + int value_int; + + if (value > 1000.0f || value < -1000.0f) { + value = value / 1000.0f; + } + + value_int = (int)(value >= 0.0f ? value + 0.5f : value - 0.5f); + if (temperature_value_valid(value_int)) { + *temp = value_int; + return 0; + } + p = end; + continue; + } + } + p++; + } + + return -1; +} + +static int get_temperature_from_file(const char *path) +{ + char temp_buf[64] = {0}; + int temp = -1; + + if (read_file_buf(path, temp_buf, sizeof(temp_buf)) <= 0) { + return -1; + } + + if (parse_temperature_value(temp_buf, &temp) == 0) { + return temp; + } + + return -1; +} + +static int is_x86_board(void) +{ + char buf[512] = {0}; + char arch[64] = {0}; + + if (read_file_buf("/etc/openwrt_release", buf, sizeof(buf)) > 0) { + if (strstr(buf, "DISTRIB_TARGET='x86/") || + strstr(buf, "DISTRIB_TARGET=\"x86/") || + strstr(buf, "DISTRIB_TARGET=x86/")) { + return 1; + } + } + + if (exec_with_result_line("uname -m", arch, sizeof(arch)) == 0) { + if (strcmp(arch, "x86_64") == 0 || + strcmp(arch, "i386") == 0 || + strcmp(arch, "i486") == 0 || + strcmp(arch, "i586") == 0 || + strcmp(arch, "i686") == 0) { + return 1; + } + } + + return 0; +} + +static int hwmon_name_score(const char *name) +{ + if (!name || name[0] == '\0') { + return 0; + } + + if (strstr(name, "coretemp") || + strstr(name, "k10temp") || + strstr(name, "zenpower")) { + return 100; + } + + if (strstr(name, "x86_pkg_temp") || + strstr(name, "cpu_thermal")) { + return 90; + } + + if (strstr(name, "acpitz")) { + return 70; + } + + if (strstr(name, "cpu")) { + return 60; + } + + return 0; +} + +static int hwmon_label_score(const char *label) +{ + if (!label || label[0] == '\0') { + return 0; + } + + if (strstr(label, "Package id") || + strstr(label, "Tctl") || + strstr(label, "Tdie")) { + return 40; + } + + if (strstr(label, "CPU") || + strstr(label, "Core")) { + return 30; + } + + return 0; +} + +static int get_x86_hwmon_temperature(void) +{ + DIR *hwmon_root = opendir("/sys/class/hwmon"); + struct dirent *hwmon_entry; + int best_temp = -1; + int best_score = 0; + + if (!hwmon_root) { + return -1; + } + + while ((hwmon_entry = readdir(hwmon_root)) != NULL) { + char hwmon_dir[256] = {0}; + char name_path[320] = {0}; + char hwmon_name[64] = {0}; + DIR *temp_dir = NULL; + struct dirent *temp_entry; + int name_score = 0; + + if (strncmp(hwmon_entry->d_name, "hwmon", 5) != 0) { + continue; + } + + snprintf(hwmon_dir, sizeof(hwmon_dir), "/sys/class/hwmon/%s", hwmon_entry->d_name); + snprintf(name_path, sizeof(name_path), "%s/name", hwmon_dir); + if (read_file_buf(name_path, hwmon_name, sizeof(hwmon_name)) > 0) { + name_score = hwmon_name_score(hwmon_name); + } + + temp_dir = opendir(hwmon_dir); + if (!temp_dir) { + continue; + } + + while ((temp_entry = readdir(temp_dir)) != NULL) { + char input_path[320] = {0}; + char label_path[320] = {0}; + char label[64] = {0}; + char temp_id[32] = {0}; + char *suffix; + int label_score = 0; + int temp = -1; + int score = name_score; + size_t id_len; + + if (strncmp(temp_entry->d_name, "temp", 4) != 0) { + continue; + } + + suffix = strstr(temp_entry->d_name, "_input"); + if (!suffix) { + continue; + } + + id_len = suffix - temp_entry->d_name; + if (id_len <= 0 || id_len >= sizeof(temp_id)) { + continue; + } + + strncpy(temp_id, temp_entry->d_name, id_len); + temp_id[id_len] = '\0'; + snprintf(input_path, sizeof(input_path), "%s/%s", hwmon_dir, temp_entry->d_name); + temp = get_temperature_from_file(input_path); + if (temp < 0) { + continue; + } + + snprintf(label_path, sizeof(label_path), "%s/%s_label", hwmon_dir, temp_id); + if (read_file_buf(label_path, label, sizeof(label)) > 0) { + label_score = hwmon_label_score(label); + score += label_score; + } + + if (score <= 0) { + continue; + } + + if (best_temp < 0 || score > best_score) { + best_temp = temp; + best_score = score; + } + } + + closedir(temp_dir); + } + + closedir(hwmon_root); + return best_temp; +} + +static int get_x86_sensors_temperature(void) +{ + char temp_buf[64] = {0}; + int temp = -1; + const char *cmds[] = { + "sensors \"coretemp-*\" 2>/dev/null | awk '/Package id|Core / {print $2; exit}'", + "sensors \"k10temp-*\" 2>/dev/null | awk '/Tctl|Tdie|temp1/ {print $2; exit}'", + "sensors \"zenpower-*\" 2>/dev/null | awk '/Tctl|Tdie|temp1/ {print $2; exit}'", + "sensors \"acpitz-*\" 2>/dev/null | awk '/temp[0-9]+/ {print $2; exit}'", + "sensors 2>/dev/null | awk '/Package id|Tctl|Tdie|CPU|Core / {print $2; exit}'", + NULL + }; + int i; + + if (access("/usr/bin/sensors", X_OK) != 0 && access("/usr/sbin/sensors", X_OK) != 0) { + return -1; + } + + for (i = 0; cmds[i] != NULL; i++) { + memset(temp_buf, 0, sizeof(temp_buf)); + if (exec_with_result_line((char *)cmds[i], temp_buf, sizeof(temp_buf)) == 0 && + parse_temperature_value(temp_buf, &temp) == 0) { + return temp; + } + } + + return -1; +} + +static int get_x86_cpu_temperature(void) +{ + int temp = get_x86_hwmon_temperature(); + + if (temp > 0) { + return temp; + } + + return get_x86_sensors_temperature(); +} + + +static int is_safe_dashboard_port_name(const char *name) { + if (!name || name[0] == '\0') { + return 0; + } + if (strchr(name, '/') || strstr(name, "..")) { + return 0; + } + return 1; +} + +static int dashboard_port_exists(struct json_object *port_array, const char *name) { + int i; + int len; + + if (!port_array || !name) { + return 0; + } + + len = json_object_array_length(port_array); + for (i = 0; i < len; i++) { + struct json_object *item = json_object_array_get_idx(port_array, i); + struct json_object *name_obj = NULL; + const char *item_name = NULL; + + if (!item || !json_object_object_get_ex(item, "name", &name_obj)) { + continue; + } + item_name = json_object_get_string(name_obj); + if (item_name && strcmp(item_name, name) == 0) { + return 1; + } + } + + return 0; +} + +static void add_dashboard_port_status(struct json_object *port_array, const char *name, const char *role) { + char path[128] = {0}; + char state[32] = {0}; + char carrier[32] = {0}; + char speed_buf[32] = {0}; + char mac[64] = {0}; + char duplex[32] = {0}; + char stat_buf[32] = {0}; + int up = 0; + int speed = 0; + unsigned long long rx_bytes = 0; + unsigned long long tx_bytes = 0; + unsigned long long rx_packets = 0; + unsigned long long tx_packets = 0; + unsigned long long rx_error_packets = 0; + unsigned long long tx_error_packets = 0; + struct json_object *port_obj = NULL; + + if (!port_array || !is_safe_dashboard_port_name(name) || dashboard_port_exists(port_array, name)) { + return; + } + + snprintf(path, sizeof(path), "/sys/class/net/%s/carrier", name); + if (read_file_buf(path, carrier, sizeof(carrier)) > 0) { + up = atoi(carrier) == 1 ? 1 : 0; + } else { + snprintf(path, sizeof(path), "/sys/class/net/%s/operstate", name); + if (read_file_buf(path, state, sizeof(state)) > 0 && strcmp(state, "up") == 0) { + up = 1; + } + } + + snprintf(path, sizeof(path), "/sys/class/net/%s/speed", name); + if (read_file_buf(path, speed_buf, sizeof(speed_buf)) > 0) { + speed = atoi(speed_buf); + if (speed < 0) { + speed = 0; + } + } + + snprintf(path, sizeof(path), "/sys/class/net/%s/address", name); + read_file_buf(path, mac, sizeof(mac)); + + snprintf(path, sizeof(path), "/sys/class/net/%s/duplex", name); + read_file_buf(path, duplex, sizeof(duplex)); + + snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/rx_bytes", name); + if (read_file_buf(path, stat_buf, sizeof(stat_buf)) > 0) rx_bytes = strtoull(stat_buf, NULL, 10); + snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/tx_bytes", name); + if (read_file_buf(path, stat_buf, sizeof(stat_buf)) > 0) tx_bytes = strtoull(stat_buf, NULL, 10); + snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/rx_packets", name); + if (read_file_buf(path, stat_buf, sizeof(stat_buf)) > 0) rx_packets = strtoull(stat_buf, NULL, 10); + snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/tx_packets", name); + if (read_file_buf(path, stat_buf, sizeof(stat_buf)) > 0) tx_packets = strtoull(stat_buf, NULL, 10); + snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/rx_errors", name); + if (read_file_buf(path, stat_buf, sizeof(stat_buf)) > 0) rx_error_packets = strtoull(stat_buf, NULL, 10); + snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/tx_errors", name); + if (read_file_buf(path, stat_buf, sizeof(stat_buf)) > 0) tx_error_packets = strtoull(stat_buf, NULL, 10); + + port_obj = json_object_new_object(); + if (!port_obj) { + return; + } + + json_object_object_add(port_obj, "name", json_object_new_string(name)); + json_object_object_add(port_obj, "role", json_object_new_string(role ? role : "")); + json_object_object_add(port_obj, "up", json_object_new_int(up)); + json_object_object_add(port_obj, "speed", json_object_new_int(speed)); + json_object_object_add(port_obj, "mac", json_object_new_string(mac)); + json_object_object_add(port_obj, "duplex", json_object_new_string(duplex)); + json_object_object_add(port_obj, "rx_bytes", json_object_new_int64(rx_bytes)); + json_object_object_add(port_obj, "tx_bytes", json_object_new_int64(tx_bytes)); + json_object_object_add(port_obj, "rx_packets", json_object_new_int64(rx_packets)); + json_object_object_add(port_obj, "tx_packets", json_object_new_int64(tx_packets)); + json_object_object_add(port_obj, "rx_error_packets", json_object_new_int64(rx_error_packets)); + json_object_object_add(port_obj, "tx_error_packets", json_object_new_int64(tx_error_packets)); + json_object_array_add(port_array, port_obj); +} + +static void add_dashboard_board_ports(struct json_object *port_array, struct json_object *network_obj, const char *role) { + struct json_object *role_obj = NULL; + struct json_object *ports_obj = NULL; + struct json_object *device_obj = NULL; + int i; + int len; + + if (!port_array || !network_obj || !role) { + return; + } + if (!json_object_object_get_ex(network_obj, role, &role_obj) || !role_obj) { + return; + } + + if (json_object_object_get_ex(role_obj, "ports", &ports_obj) && + json_object_get_type(ports_obj) == json_type_array) { + len = json_object_array_length(ports_obj); + for (i = 0; i < len; i++) { + struct json_object *port_obj = json_object_array_get_idx(ports_obj, i); + const char *name = port_obj ? json_object_get_string(port_obj) : NULL; + add_dashboard_port_status(port_array, name, role); + } + } + + if (json_object_object_get_ex(role_obj, "device", &device_obj)) { + add_dashboard_port_status(port_array, json_object_get_string(device_obj), role); + } +} + +static void load_dashboard_ports_from_board_json(struct json_object *port_array) { + FILE *fp = NULL; + long file_len = 0; + char *json_buf = NULL; + struct json_object *root_obj = NULL; + struct json_object *network_obj = NULL; + + if (!port_array) { + return; + } + + fp = fopen("/etc/board.json", "r"); + if (!fp) { + return; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return; + } + file_len = ftell(fp); + if (file_len <= 0) { + fclose(fp); + return; + } + rewind(fp); + + json_buf = (char *)calloc(1, (size_t)file_len + 1); + if (!json_buf) { + fclose(fp); + return; + } + if (fread(json_buf, 1, (size_t)file_len, fp) != (size_t)file_len) { + free(json_buf); + fclose(fp); + return; + } + fclose(fp); + + root_obj = json_tokener_parse(json_buf); + free(json_buf); + if (!root_obj) { + return; + } + + if (json_object_object_get_ex(root_obj, "network", &network_obj)) { + add_dashboard_board_ports(port_array, network_obj, "lan"); + add_dashboard_board_ports(port_array, network_obj, "wan"); + } + + json_object_put(root_obj); +} + +static void load_dashboard_eth_ports_from_sysfs(struct json_object *port_array) { + DIR *dir = NULL; + struct dirent *entry = NULL; + + if (!port_array) { + return; + } + + dir = opendir("/sys/class/net"); + if (!dir) { + return; + } + + while ((entry = readdir(dir)) != NULL) { + if (strncmp(entry->d_name, "eth", 3) != 0) { + continue; + } + add_dashboard_port_status(port_array, entry->d_name, "unknown"); + } + + closedir(dir); +} + +static struct json_object *get_dashboard_port_status(void) { + struct json_object *port_array = json_object_new_array(); + + if (!port_array) { + return NULL; + } + + load_dashboard_ports_from_board_json(port_array); + if (json_object_array_length(port_array) == 0) { + load_dashboard_eth_ports_from_sysfs(port_array); + } + + return port_array; +} + + + + + + + +static int parse_tempinfo_output(char *output, int *cpu_temp, int *wifi_temp) { + if (!output || !cpu_temp || !wifi_temp) { + return -1; + } + + + *cpu_temp = -1; + *wifi_temp = -1; + + + char *cpu_label = strstr(output, "CPU:"); + if (cpu_label) { + cpu_label += 4; + + while (*cpu_label == ' ' || *cpu_label == '\t') { + cpu_label++; + } + + char *celsius_pos = strstr(cpu_label, ""); + if (celsius_pos && celsius_pos > cpu_label) { + + char temp_str[64] = {0}; + size_t len = celsius_pos - cpu_label; + if (len < sizeof(temp_str)) { + strncpy(temp_str, cpu_label, len); + temp_str[len] = '\0'; + + float cpu_temp_float = 0.0; + if (sscanf(temp_str, "%f", &cpu_temp_float) == 1) { + *cpu_temp = (int)(cpu_temp_float + 0.5); + + if (*cpu_temp < -50 || *cpu_temp > 150) { + *cpu_temp = -1; + } + } + } + } + } + + + char *wifi_label = strstr(output, "WiFi:"); + if (wifi_label) { + wifi_label += 5; + + while (*wifi_label == ' ' || *wifi_label == '\t') { + wifi_label++; + } + + char *celsius_pos = strstr(wifi_label, ""); + if (celsius_pos && celsius_pos > wifi_label) { + + char temp_str[64] = {0}; + size_t len = celsius_pos - wifi_label; + if (len < sizeof(temp_str)) { + strncpy(temp_str, wifi_label, len); + temp_str[len] = '\0'; + + float wifi_temp_float = 0.0; + if (sscanf(temp_str, "%f", &wifi_temp_float) == 1) { + *wifi_temp = (int)(wifi_temp_float + 0.5); + + if (*wifi_temp < -50 || *wifi_temp > 150) { + *wifi_temp = -1; + } + } + } + } + } + + + if (*cpu_temp > 0 || *wifi_temp > 0) { + return 0; + } + + return -1; +} + + +static int get_cpu_temperature(void) { + int cpu_temp = -1; + int wifi_temp = -1; + int i; + + if (is_x86_board()) { + int x86_temp = get_x86_cpu_temperature(); + if (x86_temp > 0) { + return x86_temp; + } + } + + if (access("/sbin/tempinfo", F_OK) == 0) { + FILE *fp = popen("/sbin/tempinfo", "r"); + if (fp) { + char output[256] = {0}; + char line[256] = {0}; + size_t total_read = 0; + + + while (fgets(line, sizeof(line), fp) != NULL && total_read < sizeof(output) - 1) { + size_t line_len = strlen(line); + if (total_read + line_len < sizeof(output) - 1) { + strncpy(output + total_read, line, line_len); + total_read += line_len; + output[total_read] = '\0'; + } else { + break; + } + } + pclose(fp); + + + if (parse_tempinfo_output(output, &cpu_temp, &wifi_temp) == 0 && cpu_temp > 0) { + return cpu_temp; + } + } + } + + + char temp_buf[64] = {0}; + int temp_millidegrees = 0; + int temp_degrees = 0; + + + + if (read_file_buf("/sys/class/thermal/thermal_zone0/temp", temp_buf, sizeof(temp_buf)) > 0) { + temp_millidegrees = atoi(temp_buf); + if (temp_millidegrees > 0) { + + temp_degrees = temp_millidegrees / 1000; + + if (temp_degrees >= -50 && temp_degrees <= 150) { + return temp_degrees; + } + } + } + + + + for (i = 0; i < 10; i++) { + char path[256] = {0}; + snprintf(path, sizeof(path), "/sys/class/thermal/thermal_zone%d/temp", i); + if (read_file_buf(path, temp_buf, sizeof(temp_buf)) > 0) { + temp_millidegrees = atoi(temp_buf); + if (temp_millidegrees > 0) { + temp_degrees = temp_millidegrees / 1000; + if (temp_degrees >= -50 && temp_degrees <= 150) { + return temp_degrees; + } + } + } + } + + + return -1; +} + + +static int get_wifi_temperature(void) { + int cpu_temp = -1; + int wifi_temp = -1; + int i; + + if (access("/sbin/tempinfo", F_OK) == 0) { + FILE *fp = popen("/sbin/tempinfo", "r"); + if (fp) { + char output[256] = {0}; + char line[256] = {0}; + size_t total_read = 0; + + + while (fgets(line, sizeof(line), fp) != NULL && total_read < sizeof(output) - 1) { + size_t line_len = strlen(line); + if (total_read + line_len < sizeof(output) - 1) { + strncpy(output + total_read, line, line_len); + total_read += line_len; + output[total_read] = '\0'; + } else { + break; + } + } + pclose(fp); + + + if (parse_tempinfo_output(output, &cpu_temp, &wifi_temp) == 0 && wifi_temp > 0) { + return wifi_temp; + } + } + } + + + char temp_buf[64] = {0}; + int temp_degrees = 0; + + + + if (read_file_buf("/sys/class/ieee80211/phy0/temperature", temp_buf, sizeof(temp_buf)) > 0) { + temp_degrees = atoi(temp_buf); + + if (temp_degrees >= -50 && temp_degrees <= 150) { + return temp_degrees; + } + } + + + for (i = 0; i < 10; i++) { + char path[256] = {0}; + snprintf(path, sizeof(path), "/sys/class/ieee80211/phy%d/temperature", i); + if (read_file_buf(path, temp_buf, sizeof(temp_buf)) > 0) { + temp_degrees = atoi(temp_buf); + if (temp_degrees >= -50 && temp_degrees <= 150) { + return temp_degrees; + } + } + } + + + return -1; +} + + +static int get_cpu_model_name_from_proc(char *model_name, size_t len); +static int get_cpu_model_name_from_ubus(char *model_name, size_t len); + + +static int get_cpu_model_name_from_proc(char *model_name, size_t len) { + FILE *fp = fopen("/proc/cpuinfo", "r"); + if (!fp) { + return -1; + } + + char line[256] = {0}; + int found = 0; + + while (fgets(line, sizeof(line), fp)) { + + if (strncmp(line, "model name", 10) == 0) { + char *colon = strchr(line, ':'); + if (colon) { + + char *name_start = colon + 1; + + while (*name_start == ' ' || *name_start == '\t') { + name_start++; + } + + char *newline = strchr(name_start, '\n'); + if (newline) { + *newline = '\0'; + } + + char *carriage = strchr(name_start, '\r'); + if (carriage) { + *carriage = '\0'; + } + + strncpy(model_name, name_start, len - 1); + model_name[len - 1] = '\0'; + str_trim(model_name); + found = 1; + break; + } + } + + else if (strncmp(line, "cpu model", 9) == 0 || strncmp(line, "Processor", 9) == 0) { + char *colon = strchr(line, ':'); + if (colon) { + char *name_start = colon + 1; + while (*name_start == ' ' || *name_start == '\t') { + name_start++; + } + char *newline = strchr(name_start, '\n'); + if (newline) { + *newline = '\0'; + } + char *carriage = strchr(name_start, '\r'); + if (carriage) { + *carriage = '\0'; + } + strncpy(model_name, name_start, len - 1); + model_name[len - 1] = '\0'; + str_trim(model_name); + found = 1; + break; + } + } + } + + fclose(fp); + + if (!found) { + return -1; + } + + LOG_DEBUG("get_cpu_model_name_from_proc: found CPU model: %s\n", model_name); + return 0; +} + + +static int get_cpu_model_name(char *model_name, size_t len) { + + if (get_cpu_model_name_from_proc(model_name, len) == 0) { + return 0; + } + + + LOG_DEBUG("get_cpu_model_name: CPU model name not found in /proc/cpuinfo, trying ubus call system board\n"); + if (get_cpu_model_name_from_ubus(model_name, len) == 0) { + return 0; + } + + return -1; +} + + +static int get_cpu_model_name_from_ubus(char *model_name, size_t len) { + + FILE *ubus_fp = popen("ubus call system board 2>/dev/null", "r"); + if (!ubus_fp) { + LOG_ERROR("get_cpu_model_name_from_ubus: failed to call ubus system board\n"); + return -1; + } + + + char ubus_output[2048] = {0}; + size_t total_read = 0; + char line_buf[256] = {0}; + + while (fgets(line_buf, sizeof(line_buf), ubus_fp) && total_read < sizeof(ubus_output) - 1) { + size_t line_len = strlen(line_buf); + if (total_read + line_len < sizeof(ubus_output) - 1) { + strcat(ubus_output, line_buf); + total_read += line_len; + } else { + break; + } + } + pclose(ubus_fp); + + if (strlen(ubus_output) == 0) { + LOG_ERROR("get_cpu_model_name_from_ubus: ubus output is empty\n"); + return -1; + } + + + struct json_object *board_obj = json_tokener_parse(ubus_output); + if (!board_obj) { + LOG_ERROR("get_cpu_model_name_from_ubus: failed to parse ubus JSON output\n"); + return -1; + } + + int found = 0; + + + struct json_object *release_obj = json_object_object_get(board_obj, "release"); + if (release_obj) { + struct json_object *target_obj = json_object_object_get(release_obj, "target"); + if (target_obj) { + const char *target_str = json_object_get_string(target_obj); + if (target_str && strlen(target_str) > 0) { + strncpy(model_name, target_str, len - 1); + model_name[len - 1] = '\0'; + str_trim(model_name); + found = 1; + } + } + } + + + if (!found) { + struct json_object *system_obj = json_object_object_get(board_obj, "system"); + if (system_obj) { + const char *system_str = json_object_get_string(system_obj); + if (system_str && strlen(system_str) > 0) { + strncpy(model_name, system_str, len - 1); + model_name[len - 1] = '\0'; + str_trim(model_name); + found = 1; + } + } + } + + json_object_put(board_obj); + + if (!found) { + LOG_ERROR("get_cpu_model_name_from_ubus: CPU model name not found in ubus system board\n"); + return -1; + } + + LOG_DEBUG("get_cpu_model_name_from_ubus: found CPU model: %s\n", model_name); + return 0; +} + + +static int get_os_release_field(const char *field_name, char *value, size_t len) { + FILE *fp = fopen("/etc/os-release", "r"); + if (!fp) { + LOG_ERROR("get_os_release_field: failed to open /etc/os-release\n"); + return -1; + } + + char line[256] = {0}; + size_t field_len = strlen(field_name); + int found = 0; + + while (fgets(line, sizeof(line), fp)) { + + if (strncmp(line, field_name, field_len) == 0 && line[field_len] == '=') { + char *value_start = line + field_len + 1; + + if (*value_start == '"' || *value_start == '\'') { + value_start++; + } + + + char *value_end = value_start; + while (*value_end != '\0' && *value_end != '\n' && *value_end != '\r') { + if ((*value_end == '"' || *value_end == '\'') && value_end > value_start) { + break; + } + value_end++; + } + + + size_t value_size = value_end - value_start; + if (value_size > 0 && value_size < len) { + strncpy(value, value_start, value_size); + value[value_size] = '\0'; + str_trim(value); + found = 1; + break; + } + } + } + + fclose(fp); + + if (!found) { + LOG_ERROR("get_os_release_field: field %s not found in /etc/os-release\n", field_name); + return -1; + } + + LOG_DEBUG("get_os_release_field: found %s = %s\n", field_name, value); + return 0; +} + +static int get_fwx_release_field(const char *field_name, char *value, size_t len) { + FILE *fp = fopen("/etc/fwx_release", "r"); + if (!fp) { + return -1; + } + + char line[256] = {0}; + size_t field_len = strlen(field_name); + int found = 0; + + while (fgets(line, sizeof(line), fp)) { + char *line_ptr = line; + while (*line_ptr == ' ' || *line_ptr == '\t') { + line_ptr++; + } + if (*line_ptr == '#' || *line_ptr == '\0' || *line_ptr == '\n' || *line_ptr == '\r') { + continue; + } + + if (strncmp(line_ptr, field_name, field_len) == 0 && line_ptr[field_len] == '=') { + char *value_start = line_ptr + field_len + 1; + while (*value_start == ' ' || *value_start == '\t') { + value_start++; + } + + char *value_end = value_start; + while (*value_end != '\0' && *value_end != '\n' && *value_end != '\r') { + value_end++; + } + + size_t value_size = value_end - value_start; + if (value_size > 0 && value_size < len) { + strncpy(value, value_start, value_size); + value[value_size] = '\0'; + str_trim(value); + value_size = strlen(value); + if (value_size >= 2) { + if ((value[0] == '\'' && value[value_size - 1] == '\'') || + (value[0] == '"' && value[value_size - 1] == '"')) { + memmove(value, value + 1, value_size - 2); + value[value_size - 2] = '\0'; + } + } + found = 1; + break; + } + } + } + + fclose(fp); + + if (!found) { + return -1; + } + return 0; +} + +static int get_product_feature_field(const char *field_name, char *value, size_t len) { + FILE *fp = fopen("/etc/product_feature", "r"); + if (!fp) { + return -1; + } + + char line[256] = {0}; + size_t field_len = strlen(field_name); + int found = 0; + + while (fgets(line, sizeof(line), fp)) { + char *line_ptr = line; + while (*line_ptr == ' ' || *line_ptr == '\t') { + line_ptr++; + } + if (*line_ptr == '#' || *line_ptr == '\0' || *line_ptr == '\n' || *line_ptr == '\r') { + continue; + } + + if (strncmp(line_ptr, field_name, field_len) == 0 && line_ptr[field_len] == '=') { + char *value_start = line_ptr + field_len + 1; + while (*value_start == ' ' || *value_start == '\t') { + value_start++; + } + + char *value_end = value_start; + while (*value_end != '\0' && *value_end != '\n' && *value_end != '\r') { + value_end++; + } + + size_t value_size = value_end - value_start; + if (value_size > 0 && value_size < len) { + strncpy(value, value_start, value_size); + value[value_size] = '\0'; + str_trim(value); + value_size = strlen(value); + if (value_size >= 2) { + if ((value[0] == '\'' && value[value_size - 1] == '\'') || + (value[0] == '"' && value[value_size - 1] == '"')) { + memmove(value, value + 1, value_size - 2); + value[value_size - 2] = '\0'; + } + } + found = 1; + break; + } + } + } + + fclose(fp); + + if (!found) { + return -1; + } + return 0; +} + +static int get_dashboard_init_status(void) { + char init_status_buf[16] = {0}; + int init_status = 1; + + if (read_file_buf("/etc/fwx_init_status", init_status_buf, sizeof(init_status_buf)) > 0) { + str_trim(init_status_buf); + if (atoi(init_status_buf) == 0) { + init_status = 0; + } + } + + return init_status; +} + +static int set_dashboard_init_status(int init_status) { + int fd = -1; + char status_buf[8] = {0}; + int len = 0; + + if (init_status != 0 && init_status != 1) { + return -1; + } + + fd = open("/etc/fwx_init_status", O_WRONLY | O_TRUNC | O_CREAT, 0644); + if (fd < 0) { + return -1; + } + + len = snprintf(status_buf, sizeof(status_buf), "%d\n", init_status); + if (len <= 0 || write(fd, status_buf, len) != len) { + close(fd); + return -1; + } + + close(fd); + return 0; +} + + +static struct json_object *get_dashboard_system_status(void) { + struct json_object *system_status = json_object_new_object(); + char buf[256] = {0}; + char result[128] = {0}; + int hour; + + + char *model = get_model(); + json_object_object_add(system_status, "model", json_object_new_string(model)); + free(model); + + + char cpu_model_name[256] = {0}; + if (get_cpu_model_name(cpu_model_name, sizeof(cpu_model_name)) == 0) { + json_object_object_add(system_status, "cpu_model_name", json_object_new_string(cpu_model_name)); + } else { + json_object_object_add(system_status, "cpu_model_name", json_object_new_string("Unknown")); + } + + + char hostname[128] = {0}; + if (read_file_buf("/proc/sys/kernel/hostname", hostname, sizeof(hostname)) > 0) { + json_object_object_add(system_status, "hostname", json_object_new_string(hostname)); + } else { + json_object_object_add(system_status, "hostname", json_object_new_string("Unknown")); + } + + + char openwrt_version[64] = {0}; + if (get_os_release_field("VERSION", openwrt_version, sizeof(openwrt_version)) == 0) { + json_object_object_add(system_status, "openwrt_version", json_object_new_string(openwrt_version)); + } else { + json_object_object_add(system_status, "openwrt_version", json_object_new_string("Unknown")); + } + + + char arch[64] = {0}; + if (get_os_release_field("OPENWRT_ARCH", arch, sizeof(arch)) == 0) { + json_object_object_add(system_status, "arch", json_object_new_string(arch)); + } else { + json_object_object_add(system_status, "arch", json_object_new_string("Unknown")); + } + + + char oaf_version_buf[32] = {0}; + if (read_file_buf("/etc/oaf_version", oaf_version_buf, sizeof(oaf_version_buf)) > 0) { + str_trim(oaf_version_buf); + json_object_object_add(system_status, "fwx_version", json_object_new_string(oaf_version_buf)); + } else { + + if (read_file_buf("/etc/version", oaf_version_buf, sizeof(oaf_version_buf)) > 0) { + str_trim(oaf_version_buf); + json_object_object_add(system_status, "fwx_version", json_object_new_string(oaf_version_buf)); + } else { + json_object_object_add(system_status, "fwx_version", json_object_new_string("Unknown")); + } + } + + char release_type_buf[16] = {0}; + if (get_fwx_release_field("RELEASE_TYPE", release_type_buf, sizeof(release_type_buf)) == 0) { + json_object_object_add(system_status, "release_type", json_object_new_int(atoi(release_type_buf))); + } else { + json_object_object_add(system_status, "release_type", json_object_new_int(0)); + } + + char snapshot_buf[16] = {0}; + if (get_fwx_release_field("SNAPSHOT", snapshot_buf, sizeof(snapshot_buf)) == 0) { + json_object_object_add(system_status, "snapshot", json_object_new_int(atoi(snapshot_buf))); + } else { + json_object_object_add(system_status, "snapshot", json_object_new_int(0)); + } + + char release_date[32] = {0}; + if (get_fwx_release_field("RELEASE_DATE", release_date, sizeof(release_date)) == 0) { + str_trim(release_date); + json_object_object_add(system_status, "release_date", json_object_new_string(release_date)); + } else { + json_object_object_add(system_status, "release_date", json_object_new_string("")); + } + + char expand_root_buf[16] = {0}; + if (get_product_feature_field("EXPAND_ROOT", expand_root_buf, sizeof(expand_root_buf)) == 0) { + json_object_object_add(system_status, "expand_root", json_object_new_int(atoi(expand_root_buf))); + } else { + json_object_object_add(system_status, "expand_root", json_object_new_int(0)); + } + + memset(buf, 0, sizeof(buf)); + if (exec_with_result_line("uname -r", buf, sizeof(buf)) == 0 && strlen(buf) > 0) { + str_trim(buf); + json_object_object_add(system_status, "kernel_version", json_object_new_string(buf)); + } else { + json_object_object_add(system_status, "kernel_version", json_object_new_string("Unknown")); + } + + + int uptime = get_uptime(); + json_object_object_add(system_status, "uptime", json_object_new_int(uptime)); + json_object_object_add(system_status, "oaf", json_object_new_int(1)); + + memset(result, 0, sizeof(result)); + int total_mem_kb = 0; + int used_mem_kb = 0; + if (exec_with_result_line("free | grep Mem | awk '{print $2}'", result, sizeof(result)) == 0) { + total_mem_kb = atoi(result); + } + memset(result, 0, sizeof(result)); + if (exec_with_result_line("free | grep Mem | awk '{print $3}'", result, sizeof(result)) == 0) { + used_mem_kb = atoi(result); + } + + + json_object_object_add(system_status, "total_mem", json_object_new_int(total_mem_kb)); + json_object_object_add(system_status, "used_mem", json_object_new_int(used_mem_kb)); + + + memset(result, 0, sizeof(result)); + int cpu_usage = 0; + + if (exec_with_result_line("top -n 1 | grep 'CPU:' | awk -F '%' '{print$4}' | awk -F ' ' '{print$2}'", result, sizeof(result)) < 0) { + + cpu_usage = 0; + } else { + cpu_usage = 100 - atoi(result); + } + + + snprintf(buf, sizeof(buf), "%d", cpu_usage); + json_object_object_add(system_status, "cpu", json_object_new_string(buf)); + + + int connections = fwx_stat_read_conntrack_count(); + json_object_object_add(system_status, "connections", json_object_new_int(connections)); + + + + extern struct list_head client_list; + int online_client_num = 0; + int offline_client_num = 0; + client_node_t *client = NULL; + list_for_each_entry(client, &client_list, client) { + if (client->online == 1) { + online_client_num++; + } else { + offline_client_num++; + } + } + json_object_object_add(system_status, "client_num", json_object_new_int(online_client_num)); + json_object_object_add(system_status, "online_user_count", json_object_new_int(online_client_num)); + json_object_object_add(system_status, "offline_user_count", json_object_new_int(offline_client_num)); + + int af_rule_count = count_enabled_uci_rules("appfilter"); + int mf_rule_count = count_enabled_uci_rules("macfilter"); + json_object_object_add(system_status, "af_rule_count", json_object_new_int(af_rule_count)); + json_object_object_add(system_status, "mf_rule_count", json_object_new_int(mf_rule_count)); + json_object_object_add(system_status, "filter_rule_count", json_object_new_int(af_rule_count + mf_rule_count)); + + + struct json_object *storage_obj = json_object_new_object(); + FILE *df_fp = popen("df -k", "r"); + if (df_fp) { + char line[512]; + int found_tmp = 0, found_root = 0, found_boot = 0; + + + if (fgets(line, sizeof(line), df_fp)) { + + while (fgets(line, sizeof(line), df_fp)) { + char filesystem[256] = {0}; + unsigned long long total_kb = 0, used_kb = 0; + unsigned long long available_kb = 0; + int use_percent = 0; + char mount_point[256] = {0}; + + + + if (sscanf(line, "%255s %llu %llu %llu %d%% %255s", + filesystem, &total_kb, &used_kb, &available_kb, &use_percent, mount_point) >= 6) { + + if (strcmp(mount_point, "/tmp") == 0 && !found_tmp) { + struct json_object *tmp_obj = json_object_new_object(); + json_object_object_add(tmp_obj, "total_kb", json_object_new_int64(total_kb)); + json_object_object_add(tmp_obj, "used_kb", json_object_new_int64(used_kb)); + json_object_object_add(storage_obj, "tmp", tmp_obj); + found_tmp = 1; + } else if (strcmp(mount_point, "/") == 0 && !found_root) { + struct json_object *root_obj = json_object_new_object(); + json_object_object_add(root_obj, "total_kb", json_object_new_int64(total_kb)); + json_object_object_add(root_obj, "used_kb", json_object_new_int64(used_kb)); + json_object_object_add(storage_obj, "root", root_obj); + found_root = 1; + } else if (strcmp(mount_point, "/boot") == 0 && !found_boot) { + struct json_object *boot_obj = json_object_new_object(); + json_object_object_add(boot_obj, "total_kb", json_object_new_int64(total_kb)); + json_object_object_add(boot_obj, "used_kb", json_object_new_int64(used_kb)); + json_object_object_add(storage_obj, "boot", boot_obj); + found_boot = 1; + } + } + + + if (found_tmp && found_root && found_boot) { + break; + } + } + } + pclose(df_fp); + } + + + if (!json_object_object_get(storage_obj, "tmp")) { + struct json_object *tmp_obj = json_object_new_object(); + json_object_object_add(tmp_obj, "total_kb", json_object_new_int64(0)); + json_object_object_add(tmp_obj, "used_kb", json_object_new_int64(0)); + json_object_object_add(storage_obj, "tmp", tmp_obj); + } + if (!json_object_object_get(storage_obj, "root")) { + struct json_object *root_obj = json_object_new_object(); + json_object_object_add(root_obj, "total_kb", json_object_new_int64(0)); + json_object_object_add(root_obj, "used_kb", json_object_new_int64(0)); + json_object_object_add(storage_obj, "root", root_obj); + } + if (!json_object_object_get(storage_obj, "boot")) { + struct json_object *boot_obj = json_object_new_object(); + json_object_object_add(boot_obj, "total_kb", json_object_new_int64(0)); + json_object_object_add(boot_obj, "used_kb", json_object_new_int64(0)); + json_object_object_add(storage_obj, "boot", boot_obj); + } + + json_object_object_add(system_status, "storage", storage_obj); + + + struct json_object *flow_obj = json_object_new_object(); + unsigned long long today_up = 0; + unsigned long long today_down = 0; + + + extern traffic_stat_t g_global_hourly_traffic[HOURS_PER_DAY]; + extern u_int32_t g_global_traffic_date; + + + u_int32_t today = get_today_start_timestamp(); + if (g_global_traffic_date == today) { + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + today_down += g_global_hourly_traffic[hour].down_bytes; + today_up += g_global_hourly_traffic[hour].up_bytes; + } + } else { + + + today_down = 0; + today_up = 0; + } + + + today_down = today_down / 1024; + today_up = today_up / 1024; + + json_object_object_add(flow_obj, "today_up", json_object_new_int64(today_up)); + json_object_object_add(flow_obj, "today_down", json_object_new_int64(today_down)); + json_object_object_add(system_status, "flow", flow_obj); + + + int cpu_temp = get_cpu_temperature(); + if (cpu_temp > 0) { + json_object_object_add(system_status, "cpu_temp", json_object_new_int(cpu_temp)); + } + + + int wifi_temp = get_wifi_temperature(); + if (wifi_temp > 0) { + json_object_object_add(system_status, "wifi_temp", json_object_new_int(wifi_temp)); + } + + return system_status; +} + + +static struct json_object *get_dashboard_network_status(void) { + struct json_object *network_status = json_object_new_object(); + struct uci_context *uci_ctx = uci_alloc_context(); + + + int work_mode = 1; + if (uci_ctx) { + work_mode = fwx_uci_get_int_value(uci_ctx, "appfilter.global.work_mode"); + if (work_mode < 0) work_mode = 1; + } + json_object_object_add(network_status, "work_mode", json_object_new_int(work_mode)); + + + + struct json_object *lan_obj = json_object_new_object(); + iface_status_t lan_status; + memset(&lan_status, 0, sizeof(lan_status)); + + if (get_iface_status("lan", &lan_status) == 0) { + + json_object_object_add(lan_obj, "ip", json_object_new_string(strlen(lan_status.ip) > 0 ? lan_status.ip : "")); + json_object_object_add(lan_obj, "mask", json_object_new_string(strlen(lan_status.mask) > 0 ? lan_status.mask : "")); + + json_object_object_add(lan_obj, "gateway", json_object_new_string(strlen(lan_status.gateway) > 0 ? lan_status.gateway : "")); + + struct json_object *lan_dns = json_object_new_array(); + if (strlen(lan_status.dns1) > 0) { + json_object_array_add(lan_dns, json_object_new_string(lan_status.dns1)); + } + if (strlen(lan_status.dns2) > 0) { + json_object_array_add(lan_dns, json_object_new_string(lan_status.dns2)); + } + json_object_object_add(lan_obj, "dns", lan_dns); + } + + json_object_object_add(network_status, "lan", lan_obj); + + struct json_object *wan_obj = json_object_new_object(); + iface_status_t wan_status; + memset(&wan_status, 0, sizeof(wan_status)); + + if (get_iface_status("wan", &wan_status) == 0){ + json_object_object_add(wan_obj, "ip", json_object_new_string(wan_status.ip)); + json_object_object_add(wan_obj, "mask", json_object_new_string(wan_status.mask)); + json_object_object_add(wan_obj, "gateway", json_object_new_string(wan_status.gateway)); + struct json_object *wan_dns = json_object_new_array(); + if (strlen(wan_status.dns1) > 0) { + json_object_array_add(wan_dns, json_object_new_string(wan_status.dns1)); + } + if (strlen(wan_status.dns2) > 0) { + json_object_array_add(wan_dns, json_object_new_string(wan_status.dns2)); + } + + json_object_object_add(wan_obj, "dns", wan_dns); + } + json_object_object_add(network_status, "wan", wan_obj); + json_object_object_add(network_status, "port_status", get_dashboard_port_status()); + + if (uci_ctx) { + uci_free_context(uci_ctx); + } + + return network_status; +} + + +static struct json_object *get_dashboard_active_app(void) { + struct json_object *active_app = json_object_new_object(); + struct json_object *app_list = json_object_new_array(); + + int online_record_count = collect_active_app_visit_records(NULL, 0); + int offline_record_count = count_app_visit_record_rows(); + FILE *fp = fopen("/proc/net/af_active_app", "r"); + if (!fp) { + + json_object_object_add(active_app, "total", json_object_new_int(online_record_count)); + json_object_object_add(active_app, "online_total", json_object_new_int(online_record_count)); + json_object_object_add(active_app, "offline_total", json_object_new_int(offline_record_count)); + json_object_object_add(active_app, "list", app_list); + return active_app; + } + + char line[1024] = {0}; + int line_count = 0; + int total_count = 0; + + + while (fgets(line, sizeof(line), fp)) { + + if (line_count == 0) { + line_count++; + continue; + } + + + str_trim(line); + if (strlen(line) == 0) { + continue; + } + + + + + unsigned int app_id; + char mac[32] = {0}; + char src_ip[64] = {0}; + unsigned int src_port; + char dst_ip[64] = {0}; + unsigned int dst_port; + char proto[8] = {0}; + unsigned int app_proto; + unsigned int drop; + char host[64] = {0}; + unsigned int last_update; + char uri[64] = {0}; + u_int32_t current_time = get_timestamp(); + + + + int parsed = sscanf(line, "%u %s %s %u %s %u %s %u %u %63s %u %63s", + &app_id, mac, src_ip, &src_port, dst_ip, &dst_port, + proto, &app_proto, &drop, host, &last_update, uri); + + + if (parsed < 10) { + continue; + } + if (current_time > last_update && (current_time - last_update) > 180) { + continue; + } + + if (parsed < 12) { + uri[0] = '\0'; + } + + + str_trim(host); + str_trim(uri); + + + struct json_object *app = json_object_new_object(); + json_object_object_add(app, "id", json_object_new_int(app_id)); + add_app_icon_missing_flag(app, app_id); + + + const char *app_name = get_app_name_by_id(app_id); + if (app_name && strlen(app_name) > 0) { + json_object_object_add(app, "name", json_object_new_string(app_name)); + } else { + json_object_object_add(app, "name", json_object_new_string("")); + } + + + json_object_object_add(app, "mac", json_object_new_string(mac)); + + client_node_t *client = find_client_node(mac); + if (client) { + json_object_object_add(app, "hostname", json_object_new_string(client->hostname[0] ? client->hostname : "")); + json_object_object_add(app, "nickname", json_object_new_string(client->nickname[0] ? client->nickname : "")); + } else { + json_object_object_add(app, "hostname", json_object_new_string("")); + json_object_object_add(app, "nickname", json_object_new_string("")); + } + + + json_object_object_add(app, "src_ip", json_object_new_string(src_ip)); + json_object_object_add(app, "dst_ip", json_object_new_string(dst_ip)); + + + json_object_object_add(app, "src_port", json_object_new_int(src_port)); + json_object_object_add(app, "dst_port", json_object_new_int(dst_port)); + + + json_object_object_add(app, "protocol", json_object_new_string(proto)); + + + json_object_object_add(app, "app_proto", json_object_new_int(app_proto)); + + + json_object_object_add(app, "drop", json_object_new_int(drop)); + + + if (host[0] != '\0' && strcmp(host, "-") != 0) { + json_object_object_add(app, "domain", json_object_new_string(host)); + } else { + json_object_object_add(app, "domain", json_object_new_string("")); + } + + + if (app_proto == 1 && uri[0] != '\0' && strcmp(uri, "-") != 0) { + json_object_object_add(app, "uri", json_object_new_string(uri)); + } else { + json_object_object_add(app, "uri", json_object_new_string("")); + } + + + json_object_object_add(app, "timestamp", json_object_new_int(last_update)); + + json_object_array_add(app_list, app); + total_count++; + } + + fclose(fp); + + json_object_object_add(active_app, "total", json_object_new_int(online_record_count)); + json_object_object_add(active_app, "online_total", json_object_new_int(online_record_count)); + json_object_object_add(active_app, "offline_total", json_object_new_int(offline_record_count)); + json_object_object_add(active_app, "list", app_list); + + return active_app; +} + + +static int compare_host_timestamp(const void *a, const void *b) { + struct json_object *obj_a = *(struct json_object **)a; + struct json_object *obj_b = *(struct json_object **)b; + + struct json_object *ts_a, *ts_b; + json_object_object_get_ex(obj_a, "timestamp", &ts_a); + json_object_object_get_ex(obj_b, "timestamp", &ts_b); + + int ts_val_a = ts_a ? json_object_get_int(ts_a) : 0; + int ts_val_b = ts_b ? json_object_get_int(ts_b) : 0; + + return ts_val_b - ts_val_a; +} + +static int is_invalid_active_host_value(const char *host) { + if (!host || host[0] == '\0') { + return 1; + } + if (strcmp(host, "-") == 0) { + return 1; + } + if (strcasecmp(host, "undefined") == 0 || + strncasecmp(host, "undefined.", 10) == 0 || + strncasecmp(host, "undefined:", 10) == 0) { + return 1; + } + if (strcasecmp(host, "null") == 0 || + strcasecmp(host, "(null)") == 0) { + return 1; + } + return 0; +} + + +static struct json_object *get_dashboard_active_host(void) { + struct json_object *active_host = json_object_new_object(); + struct json_object *host_list = json_object_new_array(); + int i; + FILE *fp = fopen("/proc/net/af_active_host", "r"); + if (!fp) { + + json_object_object_add(active_host, "total", json_object_new_int(0)); + json_object_object_add(active_host, "list", host_list); + return active_host; + } + + char line[1024] = {0}; + int line_count = 0; + int total_count = 0; + + + while (fgets(line, sizeof(line), fp)) { + + if (line_count == 0) { + line_count++; + continue; + } + + + str_trim(line); + if (strlen(line) == 0) { + continue; + } + + + + char host_buf[64] = {0}; + char mac[32] = {0}; + char src_ip[64] = {0}; + unsigned int src_port; + char dst_ip[64] = {0}; + unsigned int dst_port; + char proto[8] = {0}; + unsigned int app_proto; + unsigned int drop; + unsigned int last_update; + + + int parsed = sscanf(line, "%63s %s %s %u %s %u %s %u %u %u", + host_buf, mac, src_ip, &src_port, dst_ip, &dst_port, + proto, &app_proto, &drop, &last_update); + + + if (parsed < 10) { + continue; + } + + + time_t current_time = time(NULL); + if (current_time > last_update && (current_time - last_update) > 180) { + continue; + } + + + str_trim(host_buf); + + + if (is_invalid_active_host_value(host_buf)) { + continue; + } + + + struct json_object *host_obj = json_object_new_object(); + + + json_object_object_add(host_obj, "mac", json_object_new_string(mac)); + + + extern struct list_head client_list; + client_node_t *client = find_client_node(mac); + + + char display_name[128] = {0}; + if (client && client->nickname[0] != '\0') { + strncpy(display_name, client->nickname, sizeof(display_name) - 1); + } else if (client && client->hostname[0] != '\0') { + strncpy(display_name, client->hostname, sizeof(display_name) - 1); + } else { + strncpy(display_name, mac, sizeof(display_name) - 1); + } + json_object_object_add(host_obj, "name", json_object_new_string(display_name)); + + + if (client) { + json_object_object_add(host_obj, "hostname", json_object_new_string(client->hostname[0] ? client->hostname : "")); + json_object_object_add(host_obj, "nickname", json_object_new_string(client->nickname[0] ? client->nickname : "")); + } else { + json_object_object_add(host_obj, "hostname", json_object_new_string("")); + json_object_object_add(host_obj, "nickname", json_object_new_string("")); + } + + + char url[128] = {0}; + if (app_proto == 1) { + snprintf(url, sizeof(url), "http://%s", host_buf); + } else if (app_proto == 2) { + snprintf(url, sizeof(url), "https://%s", host_buf); + } else { + + strncpy(url, host_buf, sizeof(url) - 1); + } + json_object_object_add(host_obj, "url", json_object_new_string(url)); + json_object_object_add(host_obj, "host", json_object_new_string(host_buf)); + + + json_object_object_add(host_obj, "app_proto", json_object_new_int(app_proto)); + + + + json_object_object_add(host_obj, "timestamp", json_object_new_int(last_update)); + + json_object_array_add(host_list, host_obj); + total_count++; + } + + fclose(fp); + + + if (total_count > 0) { + json_object_array_sort(host_list, compare_host_timestamp); + } + + + #define MAX_HOST_LIST_SIZE 10 + int array_len = json_object_array_length(host_list); + if (array_len > MAX_HOST_LIST_SIZE) { + + for (i = array_len - 1; i >= MAX_HOST_LIST_SIZE; i--) { + struct json_object *old_obj = json_object_array_get_idx(host_list, i); + json_object_array_del_idx(host_list, i, 1); + } + } + + json_object_object_add(active_host, "total", json_object_new_int(total_count)); + json_object_object_add(active_host, "list", host_list); + + return active_host; +} + + + +static int get_interface_device(const char *interface_name, char *device_name, size_t device_name_len) { + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("get_interface_device: failed to allocate UCI context\n"); + return -1; + } + + char uci_key[128] = {0}; + snprintf(uci_key, sizeof(uci_key), "network.%s.device", interface_name); + + int ret = fwx_uci_get_value(uci_ctx, uci_key, device_name, device_name_len); + uci_free_context(uci_ctx); + + if (ret != 0) { + LOG_ERROR("get_interface_device: failed to get device for interface %s\n", interface_name); + return -1; + } + + return 0; +} + + +static int read_interface_traffic(const char *ifname, unsigned long long *up_bytes, unsigned long long *down_bytes) { + FILE *netdev_fp = fopen("/proc/net/dev", "r"); + if (!netdev_fp) { + LOG_ERROR("read_interface_traffic: failed to open /proc/net/dev\n"); + return -1; + } + + char line[512]; + int found = 0; + + + fgets(line, sizeof(line), netdev_fp); + fgets(line, sizeof(line), netdev_fp); + + while (fgets(line, sizeof(line), netdev_fp)) { + char interface[32] = {0}; + unsigned long long rx_pkts, rx_errs, rx_drop, rx_fifo, rx_frame, rx_compressed, rx_multicast; + unsigned long long tx_pkts, tx_errs, tx_drop, tx_fifo, tx_colls, tx_carrier, tx_compressed; + unsigned long long rx_bytes = 0, tx_bytes = 0; + + + char *colon = strchr(line, ':'); + if (colon) { + int ifname_len = colon - line; + if (ifname_len > 0 && ifname_len < sizeof(interface)) { + strncpy(interface, line, ifname_len); + interface[ifname_len] = '\0'; + str_trim(interface); + + + if (sscanf(colon + 1, "%llu %llu %llu %llu %llu %llu %llu %llu %llu", + &rx_bytes, &rx_pkts, &rx_errs, &rx_drop, &rx_fifo, &rx_frame, &rx_compressed, &rx_multicast, &tx_bytes) >= 9) { + if (strcmp(interface, ifname) == 0) { + *down_bytes = rx_bytes; + *up_bytes = tx_bytes; + found = 1; + break; + } + } + } + } + } + + fclose(netdev_fp); + return found ? 0 : -1; +} + + +static void add_interface_traffic_point(unsigned long long up_bytes, unsigned long long down_bytes) { + u_int32_t current_time = time(NULL); + unsigned int up_rate = 0; + unsigned int down_rate = 0; + + + if (last_traffic_time > 0 && current_time > last_traffic_time) { + unsigned long long up_diff = (up_bytes > last_up_bytes) ? (up_bytes - last_up_bytes) : 0; + unsigned long long down_diff = (down_bytes > last_down_bytes) ? (down_bytes - last_down_bytes) : 0; + unsigned int time_diff = current_time - last_traffic_time; + + if (time_diff > 0) { + up_rate = (unsigned int)(up_diff / time_diff); + down_rate = (unsigned int)(down_diff / time_diff); + } + } + + + interface_traffic_node_t *node = (interface_traffic_node_t *)calloc(1, sizeof(interface_traffic_node_t)); + if (!node) { + return; + } + + node->up_bytes = up_bytes; + node->down_bytes = down_bytes; + node->up_rate = up_rate; + node->down_rate = down_rate; + node->timestamp = current_time; + + + list_add(&node->list, &interface_traffic_list); + interface_traffic_count++; + + + if (interface_traffic_count > MAX_INTERFACE_TRAFFIC_POINTS) { + interface_traffic_node_t *old_node = list_last_entry(&interface_traffic_list, interface_traffic_node_t, list); + list_del(&old_node->list); + free(old_node); + interface_traffic_count--; + } + + + last_up_bytes = up_bytes; + last_down_bytes = down_bytes; + last_traffic_time = current_time; + + LOG_DEBUG("add_interface_traffic_point: up_rate=%u B/s, down_rate=%u B/s\n", up_rate, down_rate); +} + +void check_and_update_monitor_device(void) { + char monitor_device[64] = {0}; + char wan_device_name[16] = {0}; + + monitor_device[0] = '\0'; + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) + return; + + int ret = fwx_uci_get_value(uci_ctx, "fwx.dashboard.monitor_device", monitor_device, sizeof(monitor_device)); + if (ret == 0 && strlen(monitor_device) > 0) { + strncpy(g_interface_name, monitor_device, sizeof(g_interface_name) - 1); + } + else{ + if (get_interface_device("wan", wan_device_name, sizeof(wan_device_name)) == 0 && strlen(wan_device_name) > 0) { + strncpy(g_interface_name, wan_device_name, sizeof(g_interface_name) - 1); + } else { + strcpy(g_interface_name, "br-lan"); + } + fwx_uci_set_value(uci_ctx, "fwx.dashboard.monitor_device", g_interface_name); + fwx_uci_commit(uci_ctx, "fwx"); + } + uci_free_context(uci_ctx); +} + + +void collect_interface_traffic_rate(void) { + unsigned long long up_bytes = 0, down_bytes = 0; + if (strlen(g_interface_name) == 0){ + check_and_update_monitor_device(); + if (strlen(g_interface_name) == 0) { + return; + } + } + + if (read_interface_traffic(g_interface_name, &up_bytes, &down_bytes) == 0) + add_interface_traffic_point(up_bytes, down_bytes); +} + +static struct json_object *get_dashboard_interface_traffic(void) { + struct json_object *interface_traffic = json_object_new_object(); + json_object_object_add(interface_traffic, "interface", json_object_new_string(g_interface_name)); + struct json_object *traffic_array = json_object_new_array(); + interface_traffic_node_t *node = NULL; + list_for_each_entry_reverse(node, &interface_traffic_list, list) { + struct json_object *traffic = json_object_new_object(); + json_object_object_add(traffic, "up", json_object_new_int64(node->up_rate)); + json_object_object_add(traffic, "down", json_object_new_int64(node->down_rate)); + json_object_array_add(traffic_array, traffic); + } + + int current_count = interface_traffic_count; + while (current_count < MAX_INTERFACE_TRAFFIC_POINTS) { + struct json_object *traffic = json_object_new_object(); + json_object_object_add(traffic, "up", json_object_new_int64(0)); + json_object_object_add(traffic, "down", json_object_new_int64(0)); + json_object_array_add(traffic_array, traffic); + current_count++; + } + + json_object_object_add(interface_traffic, "traffic", traffic_array); + + return interface_traffic; +} + +static struct json_object *get_dashboard_oaf_status(struct json_object *system_status) +{ + struct json_object *oaf_status = json_object_new_object(); + char version[160] = {0}; + char buf[128] = {0}; + int work_mode = -1; + int record_enable = 0; + + (void)system_status; + if (read_file_buf("/etc/oaf_version", version, sizeof(version)) > 0) { + str_trim(version); + } else { + version[0] = '\0'; + } + json_object_object_add(oaf_status, "version", json_object_new_string(version)); + + if (read_file_buf("/proc/sys/fwx/version", buf, sizeof(buf)) > 0) { + str_trim(buf); + json_object_object_add(oaf_status, "engine_version", json_object_new_string(buf)); + } else { + json_object_object_add(oaf_status, "engine_version", json_object_new_string("")); + } + + memset(buf, 0, sizeof(buf)); + if (read_file_buf("/proc/sys/fwx/work_mode", buf, sizeof(buf)) > 0) { + str_trim(buf); + work_mode = atoi(buf); + } + json_object_object_add(oaf_status, "work_mode", json_object_new_int(work_mode)); + + memset(buf, 0, sizeof(buf)); + if (read_file_buf("/proc/sys/fwx/record_enable", buf, sizeof(buf)) > 0) { + str_trim(buf); + record_enable = atoi(buf); + } + json_object_object_add(oaf_status, "record_enable", json_object_new_int(record_enable)); + + json_object_object_add(oaf_status, "app_record_count", json_object_new_int(count_app_visit_record_rows())); + json_object_object_add(oaf_status, "feature", build_feature_info_object()); + + return oaf_status; +} + + +struct json_object *fwx_api_get_dashboard_common(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + update_client_nickname(); + + struct json_object *system_status = get_dashboard_system_status(); + struct json_object *network_status = get_dashboard_network_status(); + struct json_object *active_app = get_dashboard_active_app(); + struct json_object *active_host = get_dashboard_active_host(); + struct json_object *interface_traffic = get_dashboard_interface_traffic(); + struct json_object *oaf_status = get_dashboard_oaf_status(system_status); + struct json_object *advanced = json_object_new_object(); + json_object_object_add(advanced, "disable_hnat", json_object_new_int(fwx_get_disable_hnat())); + json_object_object_add(advanced, "notice_status", json_object_new_int(fwx_get_notice_status())); + + json_object_object_add(data_obj, "system_status", system_status); + json_object_object_add(data_obj, "network_status", network_status); + json_object_object_add(data_obj, "active_app", active_app); + json_object_object_add(data_obj, "active_host", active_host); + json_object_object_add(data_obj, "interface_traffic", interface_traffic); + json_object_object_add(data_obj, "oaf_status", oaf_status); + json_object_object_add(data_obj, "advanced", advanced); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_get_hourly_top_apps(struct json_object *req_obj) { + int i; + struct json_object *data_obj = json_object_new_object(); + + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *mac = json_object_get_string(mac_obj); + if (!mac) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *date_obj = json_object_object_get(req_obj, "date"); + u_int32_t target_date = 0; + int is_today = 1; + u_int32_t today = get_today_start_timestamp(); + + if (date_obj) { + target_date = (u_int32_t)json_object_get_int64(date_obj); + is_today = (target_date == today); + } else { + target_date = today; + } + + + client_node_t *client = find_client_node(mac); + if (!client) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + daily_hourly_stat_t *stat = NULL; + + if (is_today) { + + update_hourly_top_apps(client); + stat = get_today_stat(client); + if (!stat) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } else { + + char stats_dir[512] = {0}; + char mac_dirname[64] = {0}; + char mac_buf[MAX_MAC_LEN] = {0}; + strncpy(mac_buf, client->mac, sizeof(mac_buf) - 1); + + + for (i = 0; mac_buf[i] != '\0'; i++) { + if (mac_buf[i] == ':') { + mac_buf[i] = '_'; + } + } + + char date_str[32] = {0}; + time_t t = (time_t)target_date; + struct tm *tm_info = localtime(&t); + if (tm_info) { + strftime(date_str, sizeof(date_str), "%Y-%m-%d", tm_info); + } + snprintf(stats_dir, sizeof(stats_dir), "%s/%s/stats/hourly_%s.json", + get_client_data_base_dir(), mac_buf, date_str); + + struct json_object *file_json = json_object_from_file(stats_dir); + if (!file_json) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + json_object_object_add(data_obj, "mac", json_object_new_string(client->mac)); + json_object_object_add(data_obj, "date", json_object_new_int64(target_date)); + json_object_object_add(data_obj, "is_today", json_object_new_int(0)); + + struct json_object *hourly_stats = json_object_object_get(file_json, "hourly_stats"); + if (hourly_stats) { + + json_object_object_add(data_obj, "hourly_stats", hourly_stats); + } else { + json_object_object_add(data_obj, "hourly_stats", json_object_new_array()); + } + + json_object_put(file_json); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); + } + + + struct json_object *hourly_array = json_object_new_array(); + int hour; + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + struct json_object *hour_obj = json_object_new_object(); + struct json_object *apps_array = json_object_new_array(); + + json_object_object_add(hour_obj, "hour", json_object_new_int(hour)); + for (i = 0; i < TOP_APP_PER_HOUR; i++) { + if (stat->hourly_top_apps[hour][i] > 0) { + struct json_object *app_obj = json_object_new_object(); + int appid = stat->hourly_top_apps[hour][i]; + json_object_object_add(app_obj, "appid", json_object_new_int(appid)); + add_app_icon_missing_flag(app_obj, appid); + const char *app_name = get_app_name_by_id(appid); + if (app_name) { + json_object_object_add(app_obj, "name", json_object_new_string(app_name)); + } else { + json_object_object_add(app_obj, "name", json_object_new_string("unknown")); + } + + + unsigned long long app_time = 0; + if (is_today && client) { + u_int32_t today_start = get_today_start_timestamp(); + visit_info_t *p_info = NULL; + list_for_each_entry(p_info, &client->online_visit, visit) { + if (p_info->first_time < today_start || p_info->appid != appid) + continue; + + app_time += get_visit_duration_in_hour(p_info, hour); + } + list_for_each_entry(p_info, &client->visit, visit) { + if (p_info->first_time < today_start || p_info->appid != appid) + continue; + + app_time += get_visit_duration_in_hour(p_info, hour); + } + if (app_time > 3600ULL) { + app_time = 3600ULL; + } + } + json_object_object_add(app_obj, "time", json_object_new_int64(app_time)); + + json_object_array_add(apps_array, app_obj); + } + } + json_object_object_add(hour_obj, "apps", apps_array); + struct json_object *traffic_obj = json_object_new_object(); + json_object_object_add(traffic_obj, "up_bytes", json_object_new_int64(stat->hourly_traffic[hour].up_bytes)); + json_object_object_add(traffic_obj, "down_bytes", json_object_new_int64(stat->hourly_traffic[hour].down_bytes)); + json_object_object_add(hour_obj, "traffic", traffic_obj); + json_object_object_add(hour_obj, "online_time", json_object_new_int64(stat->hourly_online_time[hour])); + json_object_object_add(hour_obj, "active_time", json_object_new_int64(stat->hourly_active_time[hour])); + json_object_array_add(hourly_array, hour_obj); + } + + json_object_object_add(data_obj, "mac", json_object_new_string(client->mac)); + json_object_object_add(data_obj, "date", json_object_new_int64(stat->date)); + json_object_object_add(data_obj, "is_today", json_object_new_int(stat->is_today)); + json_object_object_add(data_obj, "hourly_stats", hourly_array); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_get_daily_top_apps(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + int i; + int count; + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + const char *mac = json_object_get_string(mac_obj); + if (!mac) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + struct json_object *date_obj = json_object_object_get(req_obj, "date"); + u_int32_t target_date = 0; + int is_today = 1; + u_int32_t today = get_today_start_timestamp(); + + if (date_obj) { + target_date = (u_int32_t)json_object_get_int64(date_obj); + is_today = (target_date == today); + } else { + target_date = today; + } + + client_node_t *client = find_client_node(mac); + if (!client) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + daily_top_apps_stat_t *stat = NULL; + + if (is_today) { + + update_daily_top_apps(client); + stat = get_today_top_apps_stat(client); + if (!stat) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct json_object *apps_array = json_object_new_array(); + + for (i = 0; i < stat->count; i++) { + struct json_object *app_obj = json_object_new_object(); + json_object_object_add(app_obj, "appid", json_object_new_int(stat->apps[i].appid)); + json_object_object_add(app_obj, "total_time", json_object_new_int64(stat->apps[i].total_time)); + add_app_icon_missing_flag(app_obj, stat->apps[i].appid); + const char *app_name = get_app_name_by_id(stat->apps[i].appid); + if (app_name) { + json_object_object_add(app_obj, "name", json_object_new_string(app_name)); + } else { + json_object_object_add(app_obj, "name", json_object_new_string("unknown")); + } + json_object_array_add(apps_array, app_obj); + } + + json_object_object_add(data_obj, "mac", json_object_new_string(client->mac)); + json_object_object_add(data_obj, "date", json_object_new_int64(stat->date)); + json_object_object_add(data_obj, "is_today", json_object_new_int(stat->is_today)); + json_object_object_add(data_obj, "count", json_object_new_int(stat->count)); + json_object_object_add(data_obj, "apps", apps_array); + } else { + + char stats_dir[512] = {0}; + char mac_buf[64] = {0}; + strncpy(mac_buf, client->mac, sizeof(mac_buf) - 1); + mac_buf[sizeof(mac_buf) - 1] = '\0'; + + + for (i = 0; mac_buf[i] != '\0'; i++) { + if (mac_buf[i] == ':') { + mac_buf[i] = '_'; + } + } + + char date_str[32] = {0}; + time_t t = (time_t)target_date; + struct tm *tm_info = localtime(&t); + if (tm_info) { + strftime(date_str, sizeof(date_str), "%Y-%m-%d", tm_info); + } + + snprintf(stats_dir, sizeof(stats_dir), "%s/%s/stats/top_apps_%s.json", + get_client_data_base_dir(), mac_buf, date_str); + + + struct json_object *file_json = json_object_from_file(stats_dir); + if (!file_json) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct json_object *mac_obj = json_object_object_get(file_json, "mac"); + struct json_object *date_obj = json_object_object_get(file_json, "date"); + struct json_object *count_obj = json_object_object_get(file_json, "count"); + struct json_object *apps_obj = json_object_object_get(file_json, "apps"); + + if (mac_obj) { + json_object_object_add(data_obj, "mac", mac_obj); + } else { + json_object_object_add(data_obj, "mac", json_object_new_string(client->mac)); + } + + if (date_obj) { + json_object_object_add(data_obj, "date", date_obj); + } else { + json_object_object_add(data_obj, "date", json_object_new_int64(target_date)); + } + + json_object_object_add(data_obj, "is_today", json_object_new_int(0)); + + if (count_obj) { + json_object_object_add(data_obj, "count", count_obj); + } else { + json_object_object_add(data_obj, "count", json_object_new_int(0)); + } + + if (apps_obj) { + json_object_object_add(data_obj, "apps", apps_obj); + } else { + json_object_object_add(data_obj, "apps", json_object_new_array()); + } + + + json_object_put(file_json); + } + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_delete_record_files(struct json_object *req_obj) { + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + struct json_object *start_date_obj = json_object_object_get(req_obj, "start_date"); + struct json_object *end_date_obj = json_object_object_get(req_obj, "end_date"); + struct json_object *type_obj = json_object_object_get(req_obj, "type"); + + const char *mac = NULL; + const char *start_date = NULL; + const char *end_date = NULL; + const char *delete_type = NULL; + + if (mac_obj) { + mac = json_object_get_string(mac_obj); + } + if (start_date_obj) { + start_date = json_object_get_string(start_date_obj); + } + if (end_date_obj) { + end_date = json_object_get_string(end_date_obj); + } + if (type_obj) { + delete_type = json_object_get_string(type_obj); + } + + LOG_DEBUG("Delete record files: mac=%s, start_date=%s, end_date=%s, type=%s\n", + mac ? mac : "all", + start_date ? start_date : "none", + end_date ? end_date : "none", + delete_type ? delete_type : "all"); + + + delete_client_record_files(mac, start_date, end_date, delete_type); + + struct json_object *data_obj = json_object_new_object(); + json_object_object_add(data_obj, "message", json_object_new_string("Delete request processed")); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +typedef struct { + int app_type; + unsigned long long total_time; +} app_type_stat_sort_t; + + +static int compare_app_type_stat_sort(const void *a, const void *b) { + app_type_stat_sort_t *pa = (app_type_stat_sort_t *)a; + app_type_stat_sort_t *pb = (app_type_stat_sort_t *)b; + if (pa->total_time > pb->total_time) + return -1; + if (pa->total_time < pb->total_time) + return 1; + return 0; +} + + +struct json_object *fwx_api_get_global_app_type_stats(struct json_object *req_obj) { + struct json_object *type_obj = json_object_object_get(req_obj, "type"); + struct json_object *limit_obj = json_object_object_get(req_obj, "limit"); + int i; + const char *stat_type = "daily"; + int limit = 10; + + if (type_obj) { + stat_type = json_object_get_string(type_obj); + } + if (limit_obj) { + limit = json_object_get_int(limit_obj); + if (limit <= 0 || limit > MAX_APP_TYPE) + limit = MAX_APP_TYPE; + } + + unsigned long long type_time_array[MAX_APP_TYPE] = {0}; + + + if (stat_type && strcmp(stat_type, "hourly") == 0) { + get_global_hourly_app_type_stats(type_time_array); + } else { + get_global_daily_app_type_stats(type_time_array); + } + + + app_type_stat_sort_t stats[MAX_APP_TYPE]; + int valid_count = 0; + + for (i = 0; i < MAX_APP_TYPE; i++) { + if (type_time_array[i] > 0) { + stats[valid_count].app_type = i + 1; + stats[valid_count].total_time = type_time_array[i]; + valid_count++; + } + } + + + if (valid_count == 0) { + for (i = 0; i < 5; i++) { + stats[valid_count].app_type = i + 1; + stats[valid_count].total_time = 0; + valid_count++; + } + } + + + if (valid_count > 0) { + qsort(stats, valid_count, sizeof(app_type_stat_sort_t), compare_app_type_stat_sort); + } + + + struct json_object *data_obj = json_object_new_object(); + json_object_object_add(data_obj, "type", json_object_new_string(stat_type)); + json_object_object_add(data_obj, "limit", json_object_new_int(limit)); + json_object_object_add(data_obj, "total_count", json_object_new_int(valid_count)); + + struct json_object *types_array = json_object_new_array(); + int return_count = (valid_count < limit) ? valid_count : limit; + + for (i = 0; i < return_count; i++) { + struct json_object *type_obj = json_object_new_object(); + json_object_object_add(type_obj, "app_type", json_object_new_int(stats[i].app_type)); + json_object_object_add(type_obj, "total_time", json_object_new_int64(stats[i].total_time)); + + + int type_index = stats[i].app_type - 1; + const char *type_name = NULL; + if (type_index >= 0 && type_index < MAX_APP_TYPE && strlen(CLASS_NAME_TABLE[type_index]) > 0) { + type_name = CLASS_NAME_TABLE[type_index]; + } + if (!type_name) { + + static char default_name[32] = {0}; + snprintf(default_name, sizeof(default_name), "Category %d", stats[i].app_type); + type_name = default_name; + } + json_object_object_add(type_obj, "name", json_object_new_string(type_name)); + + json_object_array_add(types_array, type_obj); + } + + json_object_object_add(data_obj, "types", types_array); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_get_global_traffic_stats(struct json_object *req_obj) { + struct json_object *date_obj = json_object_object_get(req_obj, "date"); + int hour; + u_int32_t target_date = 0; + int is_today = 1; + u_int32_t today = get_today_start_timestamp(); + + if (date_obj) { + target_date = (u_int32_t)json_object_get_int64(date_obj); + is_today = (target_date == today); + } else { + target_date = today; + } + + struct json_object *data_obj = json_object_new_object(); + + if (is_today) { + + traffic_stat_t traffic_array[HOURS_PER_DAY]; + get_global_traffic_stats(traffic_array); + + json_object_object_add(data_obj, "date", json_object_new_int64(today)); + json_object_object_add(data_obj, "is_today", json_object_new_int(1)); + + struct json_object *hourly_array = json_object_new_array(); + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + struct json_object *hour_obj = json_object_new_object(); + json_object_object_add(hour_obj, "hour", json_object_new_int(hour)); + + struct json_object *traffic_obj = json_object_new_object(); + json_object_object_add(traffic_obj, "up_bytes", json_object_new_int64(traffic_array[hour].up_bytes)); + json_object_object_add(traffic_obj, "down_bytes", json_object_new_int64(traffic_array[hour].down_bytes)); + json_object_object_add(hour_obj, "traffic", traffic_obj); + + json_object_array_add(hourly_array, hour_obj); + } + + json_object_object_add(data_obj, "hourly_traffic", hourly_array); + } else { + + char date_str[32] = {0}; + time_t t = (time_t)target_date; + struct tm *tm_info = localtime(&t); + if (tm_info) { + strftime(date_str, sizeof(date_str), "%Y-%m-%d", tm_info); + } + + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/global/stats/traffic_%s.json", get_history_data_root_dir(), date_str); + + + struct json_object *file_json = json_object_from_file(file_path); + if (!file_json) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + json_object_object_add(data_obj, "date", json_object_new_int64(target_date)); + json_object_object_add(data_obj, "is_today", json_object_new_int(0)); + + struct json_object *hourly_traffic = json_object_object_get(file_json, "hourly_traffic"); + if (hourly_traffic) { + json_object_object_add(data_obj, "hourly_traffic", hourly_traffic); + } else { + json_object_object_add(data_obj, "hourly_traffic", json_object_new_array()); + } + + json_object_put(file_json); + } + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +static void get_dashboard_date_string(u_int32_t timestamp, char *date_str, size_t len) +{ + time_t t = (time_t)timestamp; + struct tm *tm_info = localtime(&t); + + if (!date_str || len == 0) { + return; + } + + date_str[0] = '\0'; + if (tm_info) { + strftime(date_str, len, "%Y-%m-%d", tm_info); + } +} + +static void sum_hourly_traffic_array(struct json_object *hourly_traffic, + unsigned long long *up_bytes, + unsigned long long *down_bytes) +{ + int i; + int len; + + if (!up_bytes || !down_bytes) { + return; + } + + if (!hourly_traffic || !json_object_is_type(hourly_traffic, json_type_array)) { + return; + } + + len = json_object_array_length(hourly_traffic); + for (i = 0; i < len; i++) { + struct json_object *hour_obj = json_object_array_get_idx(hourly_traffic, i); + struct json_object *traffic_obj = NULL; + struct json_object *up_obj = NULL; + struct json_object *down_obj = NULL; + + if (!hour_obj || !json_object_is_type(hour_obj, json_type_object)) { + continue; + } + if (!json_object_object_get_ex(hour_obj, "traffic", &traffic_obj) || !traffic_obj) { + continue; + } + if (json_object_object_get_ex(traffic_obj, "up_bytes", &up_obj) && up_obj) { + *up_bytes += (unsigned long long)json_object_get_int64(up_obj); + } + if (json_object_object_get_ex(traffic_obj, "down_bytes", &down_obj) && down_obj) { + *down_bytes += (unsigned long long)json_object_get_int64(down_obj); + } + } +} + +struct json_object *fwx_api_get_history_traffic_stats(struct json_object *req_obj) +{ + struct json_object *days_obj = NULL; + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_array = json_object_new_array(); + unsigned long long total_up = 0; + unsigned long long total_down = 0; + u_int32_t today = get_today_start_timestamp(); + int days = 30; + int i; + + if (req_obj && json_object_object_get_ex(req_obj, "days", &days_obj) && days_obj) { + days = json_object_get_int(days_obj); + } + if (days <= 0) { + days = 1; + } + if (days > 365) { + days = 365; + } + + for (i = days - 1; i >= 0; i--) { + u_int32_t date = today - (u_int32_t)(i * SECONDS_PER_DAY); + unsigned long long day_up = 0; + unsigned long long day_down = 0; + char date_str[32] = {0}; + struct json_object *day_obj = json_object_new_object(); + + get_dashboard_date_string(date, date_str, sizeof(date_str)); + + if (date == today) { + int hour; + traffic_stat_t traffic_array[HOURS_PER_DAY]; + + get_global_traffic_stats(traffic_array); + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + day_up += traffic_array[hour].up_bytes; + day_down += traffic_array[hour].down_bytes; + } + json_object_object_add(day_obj, "is_today", json_object_new_int(1)); + } else if (date_str[0]) { + char file_path[512] = {0}; + struct json_object *file_json = NULL; + struct json_object *hourly_traffic = NULL; + + snprintf(file_path, sizeof(file_path), "%s/global/stats/traffic_%s.json", + get_history_data_root_dir(), date_str); + file_json = json_object_from_file(file_path); + if (file_json) { + if (json_object_object_get_ex(file_json, "hourly_traffic", &hourly_traffic)) { + sum_hourly_traffic_array(hourly_traffic, &day_up, &day_down); + } + json_object_put(file_json); + } + json_object_object_add(day_obj, "is_today", json_object_new_int(0)); + } else { + json_object_object_add(day_obj, "is_today", json_object_new_int(0)); + } + + total_up += day_up; + total_down += day_down; + + json_object_object_add(day_obj, "date", json_object_new_int64(date)); + json_object_object_add(day_obj, "date_str", json_object_new_string(date_str)); + json_object_object_add(day_obj, "up_bytes", json_object_new_int64(day_up)); + json_object_object_add(day_obj, "down_bytes", json_object_new_int64(day_down)); + json_object_object_add(day_obj, "total_bytes", json_object_new_int64(day_up + day_down)); + json_object_array_add(list_array, day_obj); + } + + json_object_object_add(data_obj, "days", json_object_new_int(days)); + json_object_object_add(data_obj, "start_date", json_object_new_int64(today - (u_int32_t)((days - 1) * SECONDS_PER_DAY))); + json_object_object_add(data_obj, "end_date", json_object_new_int64(today)); + json_object_object_add(data_obj, "total_up_bytes", json_object_new_int64(total_up)); + json_object_object_add(data_obj, "total_down_bytes", json_object_new_int64(total_down)); + json_object_object_add(data_obj, "total_bytes", json_object_new_int64(total_up + total_down)); + json_object_object_add(data_obj, "list", list_array); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +typedef struct user_traffic_sort { + char mac[MAX_MAC_LEN]; + char ip[MAX_IP_LEN]; + char hostname[MAX_HOSTNAME_SIZE]; + char nickname[MAX_NICKNAME_SIZE]; + unsigned long long up_bytes; + unsigned long long down_bytes; + unsigned long long total_bytes; +} user_traffic_sort_t; + + +static int compare_user_traffic(const void *a, const void *b) { + const user_traffic_sort_t *ua = (const user_traffic_sort_t *)a; + const user_traffic_sort_t *ub = (const user_traffic_sort_t *)b; + + if (ua->total_bytes > ub->total_bytes) { + return -1; + } else if (ua->total_bytes < ub->total_bytes) { + return 1; + } + return 0; +} + + +struct json_object *fwx_api_get_daily_top_users(struct json_object *req_obj) { + int i; + int hour; + LOG_DEBUG("fwx_api_get_daily_top_users: called\n"); + + struct json_object *data_obj = json_object_new_object(); + if (!data_obj) { + LOG_ERROR("fwx_api_get_daily_top_users: failed to create data_obj\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + u_int32_t today = get_today_start_timestamp(); + LOG_DEBUG("fwx_api_get_daily_top_users: today timestamp = %u\n", today); + + + user_traffic_sort_t user_traffic_list[1024]; + int user_count = 0; + + LOG_DEBUG("fwx_api_get_daily_top_users: start iterating client_list\n"); + client_node_t *node = NULL; + list_for_each_entry(node, &client_list, client) { + if (user_count >= 1024) { + LOG_ERROR("fwx_api_get_daily_top_users: user_count reached max limit 1024\n"); + break; + } + + + daily_hourly_stat_t *today_stat = get_today_stat(node); + if (!today_stat) { + LOG_DEBUG("fwx_api_get_daily_top_users: client %s has no today_stat, skip\n", node->mac); + continue; + } + + + unsigned long long up_bytes = 0; + unsigned long long down_bytes = 0; + + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + up_bytes += today_stat->hourly_traffic[hour].up_bytes; + down_bytes += today_stat->hourly_traffic[hour].down_bytes; + } + + LOG_DEBUG("fwx_api_get_daily_top_users: client %s, up_bytes=%llu, down_bytes=%llu\n", + node->mac, up_bytes, down_bytes); + + + if (up_bytes > 0 || down_bytes > 0) { + user_traffic_sort_t *user = &user_traffic_list[user_count]; + + strncpy(user->mac, node->mac, sizeof(user->mac) - 1); + user->mac[sizeof(user->mac) - 1] = '\0'; + + strncpy(user->ip, node->ip, sizeof(user->ip) - 1); + user->ip[sizeof(user->ip) - 1] = '\0'; + + strncpy(user->hostname, node->hostname, sizeof(user->hostname) - 1); + user->hostname[sizeof(user->hostname) - 1] = '\0'; + + strncpy(user->nickname, node->nickname, sizeof(user->nickname) - 1); + user->nickname[sizeof(user->nickname) - 1] = '\0'; + + user->up_bytes = up_bytes; + user->down_bytes = down_bytes; + user->total_bytes = up_bytes + down_bytes; + + LOG_DEBUG("fwx_api_get_daily_top_users: added user %s (mac=%s, total_bytes=%llu)\n", + user->nickname[0] ? user->nickname : (user->hostname[0] ? user->hostname : user->mac), + user->mac, user->total_bytes); + + user_count++; + } + } + + LOG_DEBUG("fwx_api_get_daily_top_users: total user_count = %d\n", user_count); + + + if (user_count > 0) { + LOG_DEBUG("fwx_api_get_daily_top_users: sorting %d users\n", user_count); + qsort(user_traffic_list, user_count, sizeof(user_traffic_sort_t), compare_user_traffic); + } + + + int return_count = (user_count < 8) ? user_count : 8; + LOG_DEBUG("fwx_api_get_daily_top_users: return_count = %d\n", return_count); + + + json_object_object_add(data_obj, "date", json_object_new_int64(today)); + json_object_object_add(data_obj, "total_count", json_object_new_int(user_count)); + + struct json_object *users_array = json_object_new_array(); + if (!users_array) { + LOG_ERROR("fwx_api_get_daily_top_users: failed to create users_array\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + for (i = 0; i < return_count; i++) { + struct json_object *user_obj = json_object_new_object(); + if (!user_obj) { + LOG_ERROR("fwx_api_get_daily_top_users: failed to create user_obj[%d]\n", i); + continue; + } + + json_object_object_add(user_obj, "mac", json_object_new_string(user_traffic_list[i].mac)); + json_object_object_add(user_obj, "ip", json_object_new_string(user_traffic_list[i].ip)); + json_object_object_add(user_obj, "hostname", json_object_new_string(user_traffic_list[i].hostname)); + json_object_object_add(user_obj, "nickname", json_object_new_string(user_traffic_list[i].nickname)); + json_object_object_add(user_obj, "up_bytes", json_object_new_int64(user_traffic_list[i].up_bytes)); + json_object_object_add(user_obj, "down_bytes", json_object_new_int64(user_traffic_list[i].down_bytes)); + json_object_object_add(user_obj, "total_bytes", json_object_new_int64(user_traffic_list[i].total_bytes)); + + json_object_array_add(users_array, user_obj); + + LOG_DEBUG("fwx_api_get_daily_top_users: added user[%d]: mac=%s, total_bytes=%llu\n", + i, user_traffic_list[i].mac, user_traffic_list[i].total_bytes); + } + + json_object_object_add(data_obj, "users", users_array); + + LOG_DEBUG("fwx_api_get_daily_top_users: success, returning %d users\n", return_count); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +typedef struct active_user_sort { + char mac[MAX_MAC_LEN]; + char ip[MAX_IP_LEN]; + char hostname[MAX_HOSTNAME_SIZE]; + char nickname[MAX_NICKNAME_SIZE]; + unsigned int up_rate; + unsigned int down_rate; + int rssi; + unsigned int rx_rate; + unsigned int tx_rate; + char band[16]; + char wifi_ifname[64]; + int is_wireless; + unsigned long long today_up_bytes; + unsigned long long today_down_bytes; + int visiting_app; + char visiting_url[MAX_REPORT_URL_LEN]; + unsigned int total_rate; + int active; + u_int32_t online_duration; + u_int32_t last_update_time; +} active_user_sort_t; + + +static int compare_active_users(const void *a, const void *b) { + const active_user_sort_t *user_a = (const active_user_sort_t *)a; + const active_user_sort_t *user_b = (const active_user_sort_t *)b; + + + if (user_b->total_rate > user_a->total_rate) + return 1; + else if (user_b->total_rate < user_a->total_rate) + return -1; + + + if (user_b->active > user_a->active) + return 1; + else if (user_b->active < user_a->active) + return -1; + + + int user_a_visiting = (user_a->visiting_app > 0 || (user_a->visiting_url && strlen(user_a->visiting_url) > 0)) ? 1 : 0; + int user_b_visiting = (user_b->visiting_app > 0 || (user_b->visiting_url && strlen(user_b->visiting_url) > 0)) ? 1 : 0; + if (user_b_visiting > user_a_visiting) + return 1; + else if (user_b_visiting < user_a_visiting) + return -1; + + + if (user_b->online_duration > user_a->online_duration) + return 1; + else if (user_b->online_duration < user_a->online_duration) + return -1; + + + if (user_b->last_update_time > user_a->last_update_time) + return 1; + else if (user_b->last_update_time < user_a->last_update_time) + return -1; + + return 0; +} + + +struct json_object *fwx_api_get_active_users(struct json_object *req_obj) { + int i; + int hour; + struct json_object *data_obj = json_object_new_object(); + if (!data_obj) { + LOG_ERROR("fwx_api_get_active_users: failed to create data_obj\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + int count = 10; + if (req_obj) { + struct json_object *count_obj = json_object_object_get(req_obj, "count"); + if (count_obj) { + count = json_object_get_int(count_obj); + if (count <= 0 || count > 100) { + count = 10; + } + } + } + + LOG_DEBUG("fwx_api_get_active_users: start, count=%d\n", count); + + + update_client_nickname(); + update_client_visiting_info(); + + active_user_sort_t active_user_list[1024]; + int user_count = 0; + + client_node_t *node = NULL; + list_for_each_entry(node, &client_list, client) { + if (user_count >= 1024) { + LOG_ERROR("fwx_api_get_active_users: user_count reached max limit 1024\n"); + break; + } + + + if (!node->online) { + continue; + } + LOG_DEBUG("mac = %s, online = %d\n", node->mac, node->online); + + + unsigned int total_rate = node->up_rate + node->down_rate; + + active_user_sort_t *user = &active_user_list[user_count]; + + strncpy(user->mac, node->mac, sizeof(user->mac) - 1); + user->mac[sizeof(user->mac) - 1] = '\0'; + + strncpy(user->ip, node->ip, sizeof(user->ip) - 1); + user->ip[sizeof(user->ip) - 1] = '\0'; + + strncpy(user->hostname, node->hostname, sizeof(user->hostname) - 1); + user->hostname[sizeof(user->hostname) - 1] = '\0'; + + strncpy(user->nickname, node->nickname, sizeof(user->nickname) - 1); + user->nickname[sizeof(user->nickname) - 1] = '\0'; + + user->up_rate = node->up_rate; + user->down_rate = node->down_rate; + user->rssi = node->rssi; + user->rx_rate = node->rx_rate; + user->tx_rate = node->tx_rate; + snprintf(user->band, sizeof(user->band), "%s", node->band); + snprintf(user->wifi_ifname, sizeof(user->wifi_ifname), "%s", node->wifi_ifname); + user->is_wireless = node->is_wireless; + user->total_rate = total_rate; + user->active = node->active; + user->visiting_app = node->visiting_app; + + strncpy(user->visiting_url, node->visiting_url, sizeof(user->visiting_url) - 1); + user->visiting_url[sizeof(user->visiting_url) - 1] = '\0'; + + + u_int32_t current_time = get_timestamp(); + if (node->online_time > 0) { + user->online_duration = current_time - node->online_time; + user->last_update_time = node->online_time; + } else { + user->online_duration = 0; + user->last_update_time = 0; + } + + + daily_hourly_stat_t *today_stat = get_today_stat(node); + unsigned long long today_up_bytes = 0; + unsigned long long today_down_bytes = 0; + + if (today_stat) { + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + today_up_bytes += today_stat->hourly_traffic[hour].up_bytes; + today_down_bytes += today_stat->hourly_traffic[hour].down_bytes; + } + } + + user->today_up_bytes = today_up_bytes; + user->today_down_bytes = today_down_bytes; + + user_count++; + + LOG_DEBUG("fwx_api_get_active_users: added user: mac=%s, total_rate=%u\n", + user->mac, user->total_rate); + + } + + + if (user_count > 0) { + LOG_DEBUG("fwx_api_get_active_users: sorting %d users\n", user_count); + qsort(active_user_list, user_count, sizeof(active_user_sort_t), compare_active_users); + } + + + int return_count = (user_count < count) ? user_count : count; + LOG_DEBUG("fwx_api_get_active_users: return_count = %d\n", return_count); + + + json_object_object_add(data_obj, "total_count", json_object_new_int(user_count)); + + struct json_object *users_array = json_object_new_array(); + if (!users_array) { + LOG_ERROR("fwx_api_get_active_users: failed to create users_array\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + for (i = 0; i < return_count; i++) { + struct json_object *user_obj = json_object_new_object(); + if (!user_obj) { + LOG_ERROR("fwx_api_get_active_users: failed to create user_obj[%d]\n", i); + continue; + } + + json_object_object_add(user_obj, "mac", json_object_new_string(active_user_list[i].mac)); + json_object_object_add(user_obj, "ip", json_object_new_string(active_user_list[i].ip)); + json_object_object_add(user_obj, "hostname", json_object_new_string(active_user_list[i].hostname)); + json_object_object_add(user_obj, "nickname", json_object_new_string(active_user_list[i].nickname)); + json_object_object_add(user_obj, "up_rate", json_object_new_int(active_user_list[i].up_rate)); + json_object_object_add(user_obj, "down_rate", json_object_new_int(active_user_list[i].down_rate)); + json_object_object_add(user_obj, "rssi", json_object_new_int(active_user_list[i].rssi)); + json_object_object_add(user_obj, "rx_rate", json_object_new_int(active_user_list[i].rx_rate)); + json_object_object_add(user_obj, "tx_rate", json_object_new_int(active_user_list[i].tx_rate)); + json_object_object_add(user_obj, "band", json_object_new_string(active_user_list[i].band)); + json_object_object_add(user_obj, "wifi_ifname", json_object_new_string(active_user_list[i].wifi_ifname)); + json_object_object_add(user_obj, "is_wireless", json_object_new_int(active_user_list[i].is_wireless)); + json_object_object_add(user_obj, "terminal_type", json_object_new_string(active_user_list[i].is_wireless ? "wireless" : "wired")); + json_object_object_add(user_obj, "today_up_bytes", json_object_new_int64(active_user_list[i].today_up_bytes)); + json_object_object_add(user_obj, "today_down_bytes", json_object_new_int64(active_user_list[i].today_down_bytes)); + + + if (active_user_list[i].visiting_app > 0) { + json_object_object_add(user_obj, "app", json_object_new_string(get_app_name_by_id(active_user_list[i].visiting_app))); + } else { + json_object_object_add(user_obj, "app", json_object_new_string("")); + } + + if (strlen(active_user_list[i].visiting_url) > 0) { + json_object_object_add(user_obj, "url", json_object_new_string(active_user_list[i].visiting_url)); + } else { + json_object_object_add(user_obj, "url", json_object_new_string("")); + } + + json_object_array_add(users_array, user_obj); + + LOG_DEBUG("fwx_api_get_active_users: added user[%d]: mac=%s, total_rate=%u\n", + i, active_user_list[i].mac, active_user_list[i].total_rate); + } + + json_object_object_add(data_obj, "users", users_array); + LOG_DEBUG("data_obj: %s\n", json_object_to_json_string(data_obj)); + + LOG_DEBUG("fwx_api_get_active_users: success, returning %d users\n", return_count); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_get_user_basic_info(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + int hour; + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct json_object *mac_obj = json_object_object_get(req_obj, "mac"); + if (!mac_obj) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *target_mac = json_object_get_string(mac_obj); + if (!target_mac) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + update_client_nickname(); + update_client_visiting_info(); + + extern struct list_head client_list; + client_node_t *client = NULL; + u_int32_t cur_time = get_timestamp(); + u_int32_t today_start = get_today_start_timestamp(); + + + list_for_each_entry(client, &client_list, client) { + if (strcmp(client->mac, target_mac) == 0) { + + json_object_object_add(data_obj, "mac", json_object_new_string(client->mac)); + json_object_object_add(data_obj, "ip", json_object_new_string(client->ip)); + json_object_object_add(data_obj, "ipv6", json_object_new_string(client->ipv6)); + json_object_object_add(data_obj, "nickname", json_object_new_string(client->nickname)); + json_object_object_add(data_obj, "hostname", json_object_new_string(client->hostname)); + json_object_object_add(data_obj, "online", json_object_new_int(client->online)); + json_object_object_add(data_obj, "active", json_object_new_int(client->active)); + json_object_object_add(data_obj, "af_whitelist", json_object_new_int(is_appfilter_whitelist_mac(client->mac) ? 1 : 0)); + json_object_object_add(data_obj, "mf_whitelist", json_object_new_int(is_macfilter_whitelist_mac(client->mac) ? 1 : 0)); + json_object_object_add(data_obj, "up_rate", json_object_new_int(client->up_rate)); + json_object_object_add(data_obj, "down_rate", json_object_new_int(client->down_rate)); + json_object_object_add(data_obj, "rssi", json_object_new_int(client->rssi)); + json_object_object_add(data_obj, "rx_rate", json_object_new_int(client->rx_rate)); + json_object_object_add(data_obj, "tx_rate", json_object_new_int(client->tx_rate)); + json_object_object_add(data_obj, "band", json_object_new_string(client->band)); + json_object_object_add(data_obj, "wifi_ifname", json_object_new_string(client->wifi_ifname)); + json_object_object_add(data_obj, "is_wireless", json_object_new_int(client->is_wireless)); + json_object_object_add(data_obj, "terminal_type", json_object_new_string(client->is_wireless ? "wireless" : "wired")); + + + daily_hourly_stat_t *today_stat = get_today_stat(client); + unsigned long long today_up_bytes = 0; + unsigned long long today_down_bytes = 0; + unsigned long long today_online_time = 0; + unsigned long long today_active_time = 0; + + if (today_stat) { + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + today_up_bytes += today_stat->hourly_traffic[hour].up_bytes; + today_down_bytes += today_stat->hourly_traffic[hour].down_bytes; + today_online_time += today_stat->hourly_online_time[hour]; + today_active_time += today_stat->hourly_active_time[hour]; + } + } + + json_object_object_add(data_obj, "today_up_bytes", json_object_new_int64(today_up_bytes)); + json_object_object_add(data_obj, "today_down_bytes", json_object_new_int64(today_down_bytes)); + json_object_object_add(data_obj, "today_online_time", json_object_new_int64(today_online_time)); + json_object_object_add(data_obj, "today_active_time", json_object_new_int64(today_active_time)); + + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); + } + } + + + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); +} + + +struct json_object *fwx_api_get_online_offline_records(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct json_object *mac_obj = NULL; + if (!json_object_object_get_ex(req_obj, "mac", &mac_obj) || !mac_obj) { + LOG_ERROR("fwx_api_get_online_offline_records: missing mac parameter\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *mac = json_object_get_string(mac_obj); + if (!mac || strlen(mac) == 0) { + LOG_ERROR("fwx_api_get_online_offline_records: invalid mac parameter\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + extern struct list_head client_list; + client_node_t *client = find_client_node(mac); + if (!client) { + LOG_ERROR("fwx_api_get_online_offline_records: client not found for mac=%s\n", mac); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + struct json_object *records_array = json_object_new_array(); + if (!records_array) { + LOG_ERROR("fwx_api_get_online_offline_records: failed to create records_array\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + + online_offline_record_t *record = NULL; + int record_count = 0; + list_for_each_entry(record, &client->online_offline_records, record) { + struct json_object *record_obj = json_object_new_object(); + if (!record_obj) { + LOG_ERROR("fwx_api_get_online_offline_records: failed to create record_obj\n"); + continue; + } + + json_object_object_add(record_obj, "type", json_object_new_int(record->type)); + json_object_object_add(record_obj, "timestamp", json_object_new_int64(record->timestamp)); + json_object_object_add(record_obj, "duration", json_object_new_int64(record->duration)); + + + char time_str[64] = {0}; + time_t t = (time_t)record->timestamp; + struct tm *tm_info = localtime(&t); + if (tm_info) { + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); + } + json_object_object_add(record_obj, "time_str", json_object_new_string(time_str)); + + + char duration_str[64] = {0}; + unsigned long long hours = record->duration / 3600; + unsigned long long minutes = (record->duration % 3600) / 60; + unsigned long long seconds = record->duration % 60; + if (hours > 0) { + snprintf(duration_str, sizeof(duration_str), "%lluh%llum%llus", hours, minutes, seconds); + } else if (minutes > 0) { + snprintf(duration_str, sizeof(duration_str), "%llum%llus", minutes, seconds); + } else { + snprintf(duration_str, sizeof(duration_str), "%llus", seconds); + } + json_object_object_add(record_obj, "duration_str", json_object_new_string(duration_str)); + + json_object_array_add(records_array, record_obj); + record_count++; + } + + json_object_object_add(data_obj, "mac", json_object_new_string(mac)); + json_object_object_add(data_obj, "records", records_array); + json_object_object_add(data_obj, "count", json_object_new_int(record_count)); + + LOG_DEBUG("fwx_api_get_online_offline_records: success, returning %d records for mac=%s\n", record_count, mac); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_get_user_records(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + struct json_object *list_obj = json_object_new_array(); + struct json_object *mac_obj = NULL; + struct json_object *start_obj = NULL; + struct json_object *end_obj = NULL; + struct json_object *page_obj = NULL; + struct json_object *page_size_obj = NULL; + const char *mac_filter = NULL; + u_int32_t start_time = 0; + u_int32_t end_time = 0; + int page = 1; + int page_size = 15; + int total_num = 0; + int total_page = 1; + int start_idx = 0; + int end_idx = 0; + int idx = 0; + user_record_t *record = NULL; + + if (req_obj) { + if (json_object_object_get_ex(req_obj, "mac", &mac_obj) && mac_obj) { + mac_filter = json_object_get_string(mac_obj); + } + if (json_object_object_get_ex(req_obj, "start_time", &start_obj) && start_obj) { + start_time = (u_int32_t)json_object_get_int64(start_obj); + } + if (json_object_object_get_ex(req_obj, "end_time", &end_obj) && end_obj) { + end_time = (u_int32_t)json_object_get_int64(end_obj); + } + if (json_object_object_get_ex(req_obj, "page", &page_obj) && page_obj) { + page = json_object_get_int(page_obj); + if (page < 1) page = 1; + } + if (json_object_object_get_ex(req_obj, "page_size", &page_size_obj) && page_size_obj) { + page_size = json_object_get_int(page_size_obj); + if (page_size < 1) page_size = 15; + if (page_size > 200) page_size = 200; + } + } + + list_for_each_entry(record, &user_record_list, list) { + if (mac_filter && strlen(mac_filter) > 0) { + if (!strstr(record->mac, mac_filter)) { + continue; + } + } + if (start_time > 0 && record->timestamp < start_time) { + continue; + } + if (end_time > 0 && record->timestamp > end_time) { + continue; + } + total_num++; + } + + total_page = (total_num + page_size - 1) / page_size; + if (total_page < 1) total_page = 1; + if (page > total_page) page = total_page; + start_idx = (page - 1) * page_size; + end_idx = start_idx + page_size; + + idx = 0; + list_for_each_entry(record, &user_record_list, list) { + int i = 0; + char time_str[64] = {0}; + struct json_object *item = NULL; + struct json_object *apps_obj = NULL; + time_t t; + struct tm *tm_info = NULL; + + if (mac_filter && strlen(mac_filter) > 0) { + if (!strstr(record->mac, mac_filter)) { + continue; + } + } + if (start_time > 0 && record->timestamp < start_time) { + continue; + } + if (end_time > 0 && record->timestamp > end_time) { + continue; + } + if (idx < start_idx) { + idx++; + continue; + } + if (idx >= end_idx) { + break; + } + + item = json_object_new_object(); + t = (time_t)record->timestamp; + tm_info = localtime(&t); + if (tm_info) { + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); + } + json_object_object_add(item, "timestamp", json_object_new_int64(record->timestamp)); + json_object_object_add(item, "time_str", json_object_new_string(time_str)); + json_object_object_add(item, "action", json_object_new_int(record->action)); + json_object_object_add(item, "action_str", json_object_new_string(record->action == 0 ? "online" : "offline")); + json_object_object_add(item, "mac", json_object_new_string(record->mac)); + json_object_object_add(item, "nickname", json_object_new_string(record->nickname)); + json_object_object_add(item, "hostname", json_object_new_string(record->hostname)); + json_object_object_add(item, "up_bytes", json_object_new_int64(record->up_bytes)); + json_object_object_add(item, "down_bytes", json_object_new_int64(record->down_bytes)); + json_object_object_add(item, "online_duration", json_object_new_int64(record->online_duration)); + json_object_object_add(item, "active_duration", json_object_new_int64(record->active_duration)); + + apps_obj = json_object_new_array(); + for (i = 0; i < record->recent_app_count; i++) { + int appid = record->recent_apps[i]; + struct json_object *app_obj = json_object_new_object(); + json_object_object_add(app_obj, "id", json_object_new_int(appid)); + json_object_object_add(app_obj, "name", json_object_new_string(get_app_name_by_id(appid))); + add_app_icon_missing_flag(app_obj, appid); + json_object_array_add(apps_obj, app_obj); + } + json_object_object_add(item, "recent_apps", apps_obj); + json_object_array_add(list_obj, item); + idx++; + } + + json_object_object_add(data_obj, "total_num", json_object_new_int(total_num)); + json_object_object_add(data_obj, "total_page", json_object_new_int(total_page)); + json_object_object_add(data_obj, "page", json_object_new_int(page)); + json_object_object_add(data_obj, "page_size", json_object_new_int(page_size)); + json_object_object_add(data_obj, "list", list_obj); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_get_record_base(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int enable = fwx_uci_get_int_value(ctx, "fwx.record.enable"); + int record_time = fwx_uci_get_int_value(ctx, "fwx.record.record_time"); + int app_valid_time = fwx_uci_get_int_value(ctx, "fwx.record.app_valid_time"); + + char history_data_size[64] = {0}; + char history_data_path[256] = {0}; + char base_data_path[256] = {0}; + char terminal_data_path[256] = {0}; + fwx_uci_get_value(ctx, "fwx.record.history_data_size", history_data_size, sizeof(history_data_size)); + fwx_uci_get_value(ctx, "fwx.record.history_data_path", history_data_path, sizeof(history_data_path)); + fwx_uci_get_value(ctx, "fwx.record.base_data_path", base_data_path, sizeof(base_data_path)); + if (strlen(base_data_path) == 0) { + fwx_uci_get_value(ctx, "fwx.record.terminal_data_path", terminal_data_path, sizeof(terminal_data_path)); + if (strlen(terminal_data_path) > 0) { + strncpy(base_data_path, terminal_data_path, sizeof(base_data_path) - 1); + } else if (strlen(history_data_path) > 0) { + strncpy(base_data_path, history_data_path, sizeof(base_data_path) - 1); + } + } + + json_object_object_add(data_obj, "enable", json_object_new_int(enable)); + json_object_object_add(data_obj, "record_time", json_object_new_int(record_time)); + json_object_object_add(data_obj, "app_valid_time", json_object_new_int(app_valid_time)); + json_object_object_add(data_obj, "history_data_size", json_object_new_string(history_data_size)); + json_object_object_add(data_obj, "history_data_path", json_object_new_string(history_data_path)); + json_object_object_add(data_obj, "base_data_path", json_object_new_string(base_data_path)); + + struct json_object *status_obj = json_object_new_object(); + char terminal_root_dir[512] = {0}; + char history_root_dir[512] = {0}; + char data_dir[512] = {0}; + char backup_dir[512] = {0}; + char global_dir[512] = {0}; + char visit_db_path[512] = {0}; + char cmd[1024] = {0}; + char result[256] = {0}; + unsigned long long data_size_kb = 0; + unsigned long long client_data_kb = 0; + unsigned long long client_backup_kb = 0; + unsigned long long global_kb = 0; + unsigned long long visit_db_kb = 0; + unsigned long long history_root_kb = 0; + + strncpy(terminal_root_dir, get_client_data_root_dir(), sizeof(terminal_root_dir) - 1); + strncpy(history_root_dir, get_history_data_root_dir(), sizeof(history_root_dir) - 1); + snprintf(data_dir, sizeof(data_dir), "%s/client_data", terminal_root_dir); + snprintf(backup_dir, sizeof(backup_dir), "%s/client_backup", terminal_root_dir); + snprintf(global_dir, sizeof(global_dir), "%s/global", history_root_dir); + snprintf(visit_db_path, sizeof(visit_db_path), "%s/client.db", history_root_dir); + + snprintf(cmd, sizeof(cmd), "du -sk %s 2>/dev/null | awk '{print $1}'", history_root_dir); + if (exec_with_result_line(cmd, result, sizeof(result)) == 0 && strlen(result) > 0) { + history_root_kb = strtoull(result, NULL, 10); + } + data_size_kb = history_root_kb; + + memset(result, 0, sizeof(result)); + snprintf(cmd, sizeof(cmd), "du -sk %s 2>/dev/null | awk '{print $1}'", data_dir); + if (exec_with_result_line(cmd, result, sizeof(result)) == 0 && strlen(result) > 0) { + client_data_kb = strtoull(result, NULL, 10); + } + + memset(result, 0, sizeof(result)); + snprintf(cmd, sizeof(cmd), "du -sk %s 2>/dev/null | awk '{print $1}'", backup_dir); + if (exec_with_result_line(cmd, result, sizeof(result)) == 0 && strlen(result) > 0) { + client_backup_kb = strtoull(result, NULL, 10); + } + + memset(result, 0, sizeof(result)); + snprintf(cmd, sizeof(cmd), "du -sk %s 2>/dev/null | awk '{print $1}'", global_dir); + if (exec_with_result_line(cmd, result, sizeof(result)) == 0 && strlen(result) > 0) { + global_kb = strtoull(result, NULL, 10); + } + + memset(result, 0, sizeof(result)); + snprintf(cmd, sizeof(cmd), "du -sk %s 2>/dev/null | awk '{print $1}'", visit_db_path); + if (exec_with_result_line(cmd, result, sizeof(result)) == 0 && strlen(result) > 0) { + visit_db_kb = strtoull(result, NULL, 10); + } + + json_object_object_add(status_obj, "data_size", json_object_new_int64(data_size_kb)); + json_object_object_add(status_obj, "data_size_unit", json_object_new_string("KB")); + json_object_object_add(status_obj, "client_data_size", json_object_new_int64(client_data_kb)); + json_object_object_add(status_obj, "client_backup_size", json_object_new_int64(client_backup_kb)); + json_object_object_add(status_obj, "global_size", json_object_new_int64(global_kb)); + json_object_object_add(status_obj, "visit_db_size", json_object_new_int64(visit_db_kb)); + json_object_object_add(data_obj, "status", status_obj); + + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_set_record_base(struct json_object *req_obj) { + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int enable = 0, record_time = 0, app_valid_time = 0; + const char *history_data_size = NULL; + const char *history_data_path = NULL; + const char *base_data_path = NULL; + const char *terminal_data_path = NULL; + struct json_object *v; + if (req_obj && json_object_object_get_ex(req_obj, "enable", &v) && v) + enable = json_object_get_int(v); + if (req_obj && json_object_object_get_ex(req_obj, "record_time", &v) && v) + record_time = json_object_get_int(v); + if (req_obj && json_object_object_get_ex(req_obj, "app_valid_time", &v) && v) + app_valid_time = json_object_get_int(v); + if (req_obj && json_object_object_get_ex(req_obj, "history_data_size", &v) && v) + history_data_size = json_object_get_string(v); + if (req_obj && json_object_object_get_ex(req_obj, "history_data_path", &v) && v) + history_data_path = json_object_get_string(v); + if (req_obj && json_object_object_get_ex(req_obj, "base_data_path", &v) && v) + base_data_path = json_object_get_string(v); + if (req_obj && json_object_object_get_ex(req_obj, "terminal_data_path", &v) && v) + terminal_data_path = json_object_get_string(v); + if ((!base_data_path || strlen(base_data_path) == 0) && terminal_data_path && strlen(terminal_data_path) > 0) { + base_data_path = terminal_data_path; + } + + if (enable < 0) enable = 0; + if (record_time < 0) record_time = 0; + if (app_valid_time < 0) app_valid_time = 0; + + if (history_data_size && strlen(history_data_size) > 0) { + char *endptr = NULL; + long size_val = strtol(history_data_size, &endptr, 10); + if (*endptr != '\0' || size_val < 1 || size_val > 1024) { + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + } + + if (!history_data_path || strlen(history_data_path) == 0) { + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (!base_data_path || strlen(base_data_path) == 0) { + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (strcmp(history_data_path, "/") == 0) { + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (strcmp(base_data_path, "/") == 0) { + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (strlen(history_data_path) > 64) { + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (strlen(base_data_path) > 64) { + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + int num = fwx_uci_get_list_num(ctx, "fwx", "record"); + if (num <= 0) { + fwx_uci_add_section(ctx, "fwx", "record"); + } + + char buf[16]; + snprintf(buf, sizeof(buf), "%d", enable); + fwx_uci_set_value(ctx, "fwx.record.enable", buf); + snprintf(buf, sizeof(buf), "%d", record_time); + fwx_uci_set_value(ctx, "fwx.record.record_time", buf); + snprintf(buf, sizeof(buf), "%d", app_valid_time); + fwx_uci_set_value(ctx, "fwx.record.app_valid_time", buf); + + if (history_data_size && strlen(history_data_size) > 0) { + fwx_uci_set_value(ctx, "fwx.record.history_data_size", (char *)history_data_size); + } else { + fwx_uci_delete(ctx, "fwx.record.history_data_size"); + } + + char old_history_path[256] = {0}; + char old_base_path[256] = {0}; + char old_terminal_path[256] = {0}; + fwx_uci_get_value(ctx, "fwx.record.history_data_path", old_history_path, sizeof(old_history_path)); + fwx_uci_get_value(ctx, "fwx.record.base_data_path", old_base_path, sizeof(old_base_path)); + if (strlen(old_base_path) == 0) { + fwx_uci_get_value(ctx, "fwx.record.terminal_data_path", old_terminal_path, sizeof(old_terminal_path)); + if (strlen(old_terminal_path) > 0) { + strncpy(old_base_path, old_terminal_path, sizeof(old_base_path) - 1); + } else if (strlen(old_history_path) > 0) { + strncpy(old_base_path, old_history_path, sizeof(old_base_path) - 1); + } + } + + fwx_uci_set_value(ctx, "fwx.record.history_data_path", (char *)history_data_path); + fwx_uci_set_value(ctx, "fwx.record.base_data_path", (char *)base_data_path); + fwx_uci_delete(ctx, "fwx.record.terminal_data_path"); + + fwx_uci_commit(ctx, "fwx"); + + if ((history_data_path && strlen(history_data_path) > 0) || + (base_data_path && strlen(base_data_path) > 0)) { + if (strlen(old_base_path) > 0 && strcmp(old_base_path, base_data_path) != 0) { + char old_data_dir[512] = {0}; + char new_data_dir[512] = {0}; + char old_backup_dir[512] = {0}; + char new_backup_dir[512] = {0}; + char cmd[4096] = {0}; + + snprintf(old_data_dir, sizeof(old_data_dir), "%s/client_data", old_base_path); + snprintf(new_data_dir, sizeof(new_data_dir), "%s/client_data", base_data_path); + snprintf(old_backup_dir, sizeof(old_backup_dir), "%s/client_backup", old_base_path); + snprintf(new_backup_dir, sizeof(new_backup_dir), "%s/client_backup", base_data_path); + + snprintf(cmd, sizeof(cmd), + "mkdir -p %s %s %s 2>/dev/null; " + "mv %s/* %s/ 2>/dev/null; " + "mv %s/* %s/ 2>/dev/null; true", + base_data_path, new_data_dir, new_backup_dir, + old_data_dir, new_data_dir, + old_backup_dir, new_backup_dir); + system(cmd); + LOG_WARN("move base data path: %s -> %s\n", old_base_path, base_data_path); + } + + if (strlen(old_history_path) > 0 && strcmp(old_history_path, history_data_path) != 0) { + char old_global_dir[512] = {0}; + char new_global_dir[512] = {0}; + char old_db_path[512] = {0}; + char new_db_path[512] = {0}; + char cmd[4096] = {0}; + + snprintf(old_global_dir, sizeof(old_global_dir), "%s/global", old_history_path); + snprintf(new_global_dir, sizeof(new_global_dir), "%s/global", history_data_path); + snprintf(old_db_path, sizeof(old_db_path), "%s/client.db", old_history_path); + snprintf(new_db_path, sizeof(new_db_path), "%s/client.db", history_data_path); + + snprintf(cmd, sizeof(cmd), + "mkdir -p %s %s 2>/dev/null; " + "mv %s/* %s/ 2>/dev/null; " + "mv %s %s 2>/dev/null; true", + history_data_path, new_global_dir, + old_global_dir, new_global_dir, + old_db_path, new_db_path); + system(cmd); + LOG_WARN("move history data path: %s -> %s\n", old_history_path, history_data_path); + } + + reset_client_data_base_dir_cache(); + } + + uci_free_context(ctx); + + update_fwx_proc_u32_value("record_enable", enable); + + + load_app_valid_time_config(); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +static int clear_visit_db_tables(const char *db_path) +{ + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + + if (!db_path || strlen(db_path) == 0) { + return -1; + } + + rc = sqlite3_open(db_path, &db); + if (rc != SQLITE_OK || !db) { + if (db) { + sqlite3_close(db); + } + return -1; + } + + sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL); + + rc = sqlite3_prepare_v2(db, + "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';", + -1, &stmt, NULL); + if (rc == SQLITE_OK && stmt) { + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + const char *table_name = (const char *)sqlite3_column_text(stmt, 0); + if (!table_name || table_name[0] == '\0') { + continue; + } + + char sql[256] = {0}; + snprintf(sql, sizeof(sql), "DELETE FROM \"%s\";", table_name); + if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) { + LOG_WARN("clear_visit_db_tables delete table failed: %s\n", table_name); + } + } + } + + if (stmt) { + sqlite3_finalize(stmt); + } + + sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); + sqlite3_close(db); + return 0; +} + +struct json_object *fwx_api_record_action(struct json_object *req_obj) { + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *action_obj = json_object_object_get(req_obj, "action"); + if (!action_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *action = json_object_get_string(action_obj); + if (!action) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (strcmp(action, "clean_all_data") == 0) { + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char history_data_path[256] = {0}; + char base_data_path[256] = {0}; + char terminal_data_path[256] = {0}; + fwx_uci_get_value(ctx, "fwx.record.history_data_path", history_data_path, sizeof(history_data_path)); + fwx_uci_get_value(ctx, "fwx.record.base_data_path", base_data_path, sizeof(base_data_path)); + if (strlen(base_data_path) == 0) { + fwx_uci_get_value(ctx, "fwx.record.terminal_data_path", terminal_data_path, sizeof(terminal_data_path)); + if (strlen(terminal_data_path) > 0) { + strncpy(base_data_path, terminal_data_path, sizeof(base_data_path) - 1); + } else if (strlen(history_data_path) > 0) { + strncpy(base_data_path, history_data_path, sizeof(base_data_path) - 1); + } + } + uci_free_context(ctx); + + char terminal_root_dir[512] = {0}; + char history_root_dir[512] = {0}; + char base_data_dir[512] = {0}; + char base_backup_dir[512] = {0}; + char base_global_dir[512] = {0}; + char history_data_dir[512] = {0}; + char history_backup_dir[512] = {0}; + char history_global_dir[512] = {0}; + char history_visit_db_path[512] = {0}; + char cmd[8192] = {0}; + if (strlen(base_data_path) > 0) { + strncpy(terminal_root_dir, base_data_path, sizeof(terminal_root_dir) - 1); + } else { + strncpy(terminal_root_dir, get_client_data_root_dir(), sizeof(terminal_root_dir) - 1); + } + if (strlen(history_data_path) > 0) { + strncpy(history_root_dir, history_data_path, sizeof(history_root_dir) - 1); + } else { + strncpy(history_root_dir, get_history_data_root_dir(), sizeof(history_root_dir) - 1); + } + + snprintf(base_data_dir, sizeof(base_data_dir), "%s/client_data", terminal_root_dir); + snprintf(base_backup_dir, sizeof(base_backup_dir), "%s/client_backup", terminal_root_dir); + snprintf(base_global_dir, sizeof(base_global_dir), "%s/global", terminal_root_dir); + + snprintf(history_data_dir, sizeof(history_data_dir), "%s/client_data", history_root_dir); + snprintf(history_backup_dir, sizeof(history_backup_dir), "%s/client_backup", history_root_dir); + snprintf(history_global_dir, sizeof(history_global_dir), "%s/global", history_root_dir); + snprintf(history_visit_db_path, sizeof(history_visit_db_path), "%s/client.db", history_root_dir); + snprintf(cmd, sizeof(cmd), + "rm -rf %s %s %s %s %s %s 2>/dev/null; " + "mkdir -p %s %s %s %s %s %s 2>/dev/null; true", + base_data_dir, base_backup_dir, base_global_dir, + history_data_dir, history_backup_dir, history_global_dir, + base_data_dir, base_backup_dir, base_global_dir, + history_data_dir, history_backup_dir, history_global_dir); + system(cmd); + + if (access(history_visit_db_path, F_OK) == 0) { + clear_visit_db_tables(history_visit_db_path); + } + + reset_client_data_base_dir_cache(); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); + } + + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); +} + +struct json_object *fwx_api_add_mock_visit_records(struct json_object *req_obj) { + int record_count = 1000; + int mac_count = 32; + int inserted = 0; + unsigned long long old_size_bytes = 0; + unsigned long long new_size_bytes = 0; + struct json_object *count_obj = NULL; + struct json_object *mac_count_obj = NULL; + struct json_object *data_obj = NULL; + + if (!g_fwxd_debug_mode) { + data_obj = json_object_new_object(); + json_object_object_add(data_obj, "msg", json_object_new_string("debug mode is disabled")); + json_object_object_add(data_obj, "debug_mode", json_object_new_int(0)); + return fwx_gen_api_response_data(API_CODE_ERROR, data_obj); + } + + if (req_obj) { + count_obj = json_object_object_get(req_obj, "count"); + if (count_obj) { + record_count = json_object_get_int(count_obj); + } + + mac_count_obj = json_object_object_get(req_obj, "mac_count"); + if (mac_count_obj) { + mac_count = json_object_get_int(mac_count_obj); + } + } + + if (record_count <= 0) { + data_obj = json_object_new_object(); + json_object_object_add(data_obj, "msg", json_object_new_string("invalid count")); + json_object_object_add(data_obj, "count", json_object_new_int(record_count)); + return fwx_gen_api_response_data(API_CODE_ERROR, data_obj); + } + + inserted = add_mock_visit_records_to_db(record_count, mac_count, &old_size_bytes, &new_size_bytes); + if (inserted < 0) { + data_obj = json_object_new_object(); + json_object_object_add(data_obj, "msg", json_object_new_string("insert mock records failed")); + return fwx_gen_api_response_data(API_CODE_ERROR, data_obj); + } + + data_obj = json_object_new_object(); + json_object_object_add(data_obj, "inserted", json_object_new_int(inserted)); + json_object_object_add(data_obj, "db_size_before", json_object_new_int64((long long)old_size_bytes)); + json_object_object_add(data_obj, "db_size_after", json_object_new_int64((long long)new_size_bytes)); + json_object_object_add(data_obj, "debug_mode", json_object_new_int(1)); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_get_device_list(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_get_device_list: called\n"); + + struct json_object *data_obj = json_object_new_object(); + struct json_object *device_array = json_object_new_array(); + + FILE *fp = fopen("/proc/net/dev", "r"); + if (!fp) { + LOG_ERROR("Failed to open /proc/net/dev\n"); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char line[256]; + int line_num = 0; + + + while (fgets(line, sizeof(line), fp)) { + line_num++; + + + if (line_num <= 2) { + continue; + } + + + char *colon = strchr(line, ':'); + if (!colon) { + continue; + } + + + int ifname_len = colon - line; + if (ifname_len <= 0 || ifname_len >= 64) { + continue; + } + + char ifname[64] = {0}; + strncpy(ifname, line, ifname_len); + + + char *ifname_start = ifname; + while (*ifname_start == ' ' || *ifname_start == '\t') { + ifname_start++; + } + + if (strcmp(ifname_start, "lo") == 0) { + continue; + } + + if (strlen(ifname_start) == 0) { + continue; + } + + json_object_array_add(device_array, json_object_new_string(ifname_start)); + LOG_DEBUG("Added interface: %s\n", ifname_start); + } + + fclose(fp); + + json_object_object_add(data_obj, "device_list", device_array); + LOG_DEBUG("fwx_api_get_device_list: returning %d devices\n", json_object_array_length(device_array)); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + + +struct json_object *fwx_api_get_dashboard_param(struct json_object *req_obj) { + LOG_DEBUG("fwx_api_get_dashboard_param: called\n"); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + char monitor_device[64] = {0}; + int ret = fwx_uci_get_value(uci_ctx, "fwx.dashboard.monitor_device", monitor_device, sizeof(monitor_device)); + if (ret != 0) { + monitor_device[0] = '\0'; + } + + uci_free_context(uci_ctx); + + struct json_object *data_obj = json_object_new_object(); + json_object_object_add(data_obj, "monitor_device", json_object_new_string(monitor_device)); + + LOG_DEBUG("fwx_api_get_dashboard_param: monitor_device=%s\n", monitor_device); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_get_init_status(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + (void)req_obj; + json_object_object_add(data_obj, "init_status", json_object_new_int(get_dashboard_init_status())); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_set_init_status(struct json_object *req_obj) { + struct json_object *data_obj = json_object_new_object(); + int init_status = 1; + + if (req_obj) { + struct json_object *init_status_obj = json_object_object_get(req_obj, "init_status"); + if (init_status_obj) { + init_status = json_object_get_int(init_status_obj); + } + } + + if (init_status != 0 && init_status != 1) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (set_dashboard_init_status(init_status) != 0) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + json_object_object_add(data_obj, "init_status", json_object_new_int(init_status)); + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_set_dashboard_param(struct json_object *req_obj) { + + if (!req_obj) { + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + struct json_object *monitor_device_obj = json_object_object_get(req_obj, "monitor_device"); + if (!monitor_device_obj) { + LOG_ERROR("monitor_device parameter missing\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + const char *monitor_device = json_object_get_string(monitor_device_obj); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) { + LOG_ERROR("Failed to allocate UCI context\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (monitor_device && strlen(monitor_device) > 0) { + fwx_uci_set_value(uci_ctx, "fwx.dashboard.monitor_device", (char *)monitor_device); + fwx_uci_commit(uci_ctx, "fwx"); + g_interface_name[0] = '\0'; + } + + uci_free_context(uci_ctx); + + LOG_DEBUG("fwx_api_set_dashboard_param: set monitor_device=%s\n", monitor_device ? monitor_device : "(empty)"); + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} + +typedef struct json_object * (*fwx_api_handler)(struct json_object *data_obj); + +typedef enum { + FWX_API_METHOD_GET, + FWX_API_METHOD_POST +} fwx_api_method_t; + +typedef struct fwx_api_node{ + char *api_name; + fwx_api_handler handler; + int forward; + fwx_api_method_t method; +}fwx_api_node_t; + +static int fwx_validate_api_request(struct json_object *req_obj) { + struct json_object *copyright_obj = NULL; + + if (!req_obj || !json_object_is_type(req_obj, json_type_object) || + !json_object_object_get_ex(req_obj, "CopyRight", ©right_obj) || + !json_object_is_type(copyright_obj, json_type_string)) { + return 0; + } + + return strcmp(json_object_get_string(copyright_obj), + "www.fanchmwrt.com") == 0; +} + +static void fwx_add_api_response_copyright(struct json_object *response_obj) { + struct json_object *code_obj = NULL; + + if (!response_obj || !json_object_is_type(response_obj, json_type_object) || + !json_object_object_get_ex(response_obj, "code", &code_obj) || + json_object_get_int(code_obj) != API_CODE_SUCCESS) { + return; + } + + json_object_object_add(response_obj, "CopyRight", + json_object_new_string("www.fanchmwrt.com")); +} + +static int fwx_api_need_copyright(const char *api_name) { + return api_name && strcmp(api_name, "class_list") == 0; +} + +static void fwx_forward_to_agent(struct json_object *req_obj) { + char *cmd_buf = NULL; + if (!req_obj) + return; + const char *req_str = json_object_to_json_string(req_obj); + int buf_len = strlen(req_str) + 128; + cmd_buf = (char *)malloc(buf_len); + if (!cmd_buf) + return; + snprintf(cmd_buf, buf_len, "ubus -t 2 call fwx_agent forward '%s'", req_str); + + system(cmd_buf); + free(cmd_buf); +} + +struct json_object *fwx_api_get_dashboard_common(struct json_object *req_obj); +struct json_object *fwx_api_get_history_session(struct json_object *req_obj); +struct json_object *fwx_api_get_hourly_top_apps(struct json_object *req_obj); +struct json_object *fwx_api_get_daily_top_apps(struct json_object *req_obj); +struct json_object *fwx_api_delete_record_files(struct json_object *req_obj); +struct json_object *fwx_api_get_global_app_type_stats(struct json_object *req_obj); +struct json_object *fwx_api_get_global_traffic_stats(struct json_object *req_obj); +struct json_object *fwx_api_get_history_traffic_stats(struct json_object *req_obj); +struct json_object *fwx_api_get_daily_top_users(struct json_object *req_obj); +struct json_object *fwx_api_get_active_users(struct json_object *req_obj); +struct json_object *fwx_api_get_active_app_records(struct json_object *req_obj); +struct json_object *fwx_api_get_app_history_records(struct json_object *req_obj); +struct json_object *fwx_api_get_filter_rules(struct json_object *req_obj); +struct json_object *fwx_api_add_filter_rule(struct json_object *req_obj); +struct json_object *fwx_api_update_filter_rule(struct json_object *req_obj); +struct json_object *fwx_api_delete_filter_rule(struct json_object *req_obj); +struct json_object *fwx_api_get_online_offline_records(struct json_object *req_obj); +struct json_object *fwx_api_get_user_records(struct json_object *req_obj); +struct json_object *fwx_api_get_record_base(struct json_object *req_obj); +struct json_object *fwx_api_set_record_base(struct json_object *req_obj); +struct json_object *fwx_api_record_action(struct json_object *req_obj); +struct json_object *fwx_api_set_nickname(struct json_object *req_obj); +struct json_object *fwx_api_get_nickname_list(struct json_object *req_obj); +struct json_object *fwx_api_get_mac_blacklist(struct json_object *req_obj); +struct json_object *fwx_api_add_mac_blacklist(struct json_object *req_obj); +struct json_object *fwx_api_del_mac_blacklist(struct json_object *req_obj); +struct json_object *fwx_api_dev_visit_list(struct json_object *req_obj); +struct json_object *fwx_api_dev_visit_time(struct json_object *req_obj); +struct json_object *fwx_api_app_class_visit_time(struct json_object *req_obj); +struct json_object *fwx_api_dev_list(struct json_object *req_obj); +struct json_object *fwx_api_class_list(struct json_object *req_obj); +struct json_object *fwx_api_get_all_users(struct json_object *req_obj); +struct json_object *fwx_api_get_parental_control_detail(struct json_object *req_obj); +struct json_object *fwx_api_get_user_parental_control_rules(struct json_object *req_obj); +struct json_object *fwx_api_get_user_stat(struct json_object *req_obj); +struct json_object *fwx_api_visit_list(struct json_object *req_obj); +struct json_object *fwx_api_add_mock_visit_records(struct json_object *req_obj); +struct json_object *fwx_api_get_device_list(struct json_object *req_obj); +struct json_object *fwx_api_get_dashboard_param(struct json_object *req_obj); +struct json_object *fwx_api_get_init_status(struct json_object *req_obj); +struct json_object *fwx_api_set_init_status(struct json_object *req_obj); +struct json_object *fwx_api_set_dashboard_param(struct json_object *req_obj); +struct json_object *fwx_api_get_system_base_info(struct json_object *req_obj); + + + +static fwx_api_node_t fwx_api_node_list[] = { + {"get_custom_feature", fwx_api_get_custom_feature, 0, FWX_API_METHOD_GET}, + {"get_custom_feature_class_list", fwx_api_get_custom_feature_class_list, 0, FWX_API_METHOD_GET}, + {"set_custom_feature", fwx_api_set_custom_feature, 0, FWX_API_METHOD_POST}, + {"get_feature_info", fwx_api_get_feature_info, 0, FWX_API_METHOD_GET}, + {"get_feature_online_config", fwx_api_get_feature_online_config, 0, FWX_API_METHOD_GET}, + {"set_feature_online_config", fwx_api_set_feature_online_config, 0, FWX_API_METHOD_POST}, + {"get_feature_online_list", fwx_api_get_feature_online_list, 0, FWX_API_METHOD_GET}, + {"start_feature_online_update", fwx_api_start_feature_online_update, 0, FWX_API_METHOD_POST}, + {"get_feature_online_update_status", fwx_api_get_feature_online_update_status, 0, FWX_API_METHOD_GET}, + {"get_dashboard_common", fwx_api_get_dashboard_common, 0, FWX_API_METHOD_GET}, + {"get_history_session", fwx_api_get_history_session, 0, FWX_API_METHOD_GET}, + {"get_hourly_top_apps", fwx_api_get_hourly_top_apps, 0, FWX_API_METHOD_GET}, + {"get_daily_top_apps", fwx_api_get_daily_top_apps, 0, FWX_API_METHOD_GET}, + {"delete_record_files", fwx_api_delete_record_files, 0, FWX_API_METHOD_POST}, + {"get_global_app_type_stats", fwx_api_get_global_app_type_stats, 0, FWX_API_METHOD_GET}, + {"get_global_traffic_stats", fwx_api_get_global_traffic_stats, 0, FWX_API_METHOD_GET}, + {"get_history_traffic_stats", fwx_api_get_history_traffic_stats, 0, FWX_API_METHOD_GET}, + {"get_daily_top_users", fwx_api_get_daily_top_users, 0, FWX_API_METHOD_GET}, + {"get_active_users", fwx_api_get_active_users, 0, FWX_API_METHOD_GET}, + {"get_active_app_records", fwx_api_get_active_app_records, 0, FWX_API_METHOD_GET}, + {"get_app_history_records", fwx_api_get_app_history_records, 0, FWX_API_METHOD_GET}, + {"get_filter_rules", fwx_api_get_filter_rules, 0, FWX_API_METHOD_GET}, + {"add_filter_rule", fwx_api_add_filter_rule, 1, FWX_API_METHOD_POST}, + {"update_filter_rule", fwx_api_update_filter_rule, 1, FWX_API_METHOD_POST}, + {"delete_filter_rule", fwx_api_delete_filter_rule, 1, FWX_API_METHOD_POST}, + {"get_user_basic_info", fwx_api_get_user_basic_info, 0, FWX_API_METHOD_GET}, + {"get_online_offline_records", fwx_api_get_online_offline_records, 0, FWX_API_METHOD_GET}, + {"get_user_records", fwx_api_get_user_records, 0, FWX_API_METHOD_GET}, + {"get_appfilter_whitelist", fwx_api_get_appfilter_whitelist, 0, FWX_API_METHOD_GET}, + {"add_appfilter_whitelist", fwx_api_add_appfilter_whitelist, 1, FWX_API_METHOD_POST}, + {"del_appfilter_whitelist", fwx_api_del_appfilter_whitelist, 1, FWX_API_METHOD_POST}, + {"get_app_filter_adv", fwx_api_get_app_filter_adv, 0, FWX_API_METHOD_GET}, + {"set_app_filter_adv", fwx_api_set_app_filter_adv, 1, FWX_API_METHOD_POST}, + {"get_system_info", fwx_api_get_system_info, 0, FWX_API_METHOD_GET}, + {"get_system_base_info", fwx_api_get_system_base_info, 0, FWX_API_METHOD_GET}, + {"set_system_info", fwx_api_set_system_info, 1, FWX_API_METHOD_POST}, + {"get_tcp_rst", fwx_api_get_tcp_rst, 0, FWX_API_METHOD_GET}, + {"set_tcp_rst", fwx_api_set_tcp_rst, 1, FWX_API_METHOD_POST}, + {"get_advanced_settings", fwx_api_get_advanced_settings, 0, FWX_API_METHOD_GET}, + {"set_advanced_settings", fwx_api_set_advanced_settings, 1, FWX_API_METHOD_POST}, + {"set_dashboard_notice_status", fwx_api_set_dashboard_notice_status, 1, FWX_API_METHOD_POST}, + {"get_mac_filter_rules", fwx_api_get_mac_filter_rules, 0, FWX_API_METHOD_GET}, + {"add_mac_filter_rule", fwx_api_add_mac_filter_rule, 1, FWX_API_METHOD_POST}, + {"update_mac_filter_rule", fwx_api_update_mac_filter_rule, 1, FWX_API_METHOD_POST}, + {"delete_mac_filter_rule", fwx_api_delete_mac_filter_rule, 1, FWX_API_METHOD_POST}, + {"get_mac_filter_whitelist", fwx_api_get_mac_filter_whitelist, 0, FWX_API_METHOD_GET}, + {"add_mac_filter_whitelist", fwx_api_add_mac_filter_whitelist, 1, FWX_API_METHOD_POST}, + {"del_mac_filter_whitelist", fwx_api_del_mac_filter_whitelist, 1, FWX_API_METHOD_POST}, + {"get_mac_filter_adv", fwx_api_get_mac_filter_adv, 0, FWX_API_METHOD_GET}, + {"set_mac_filter_adv", fwx_api_set_mac_filter_adv, 1, FWX_API_METHOD_POST}, + {"get_record_base", fwx_api_get_record_base, 0, FWX_API_METHOD_GET}, + {"set_record_base", fwx_api_set_record_base, 1, FWX_API_METHOD_POST}, + {"get_record_whitelist", fwx_api_get_record_whitelist, 0, FWX_API_METHOD_GET}, + {"add_record_whitelist", fwx_api_add_record_whitelist, 1, FWX_API_METHOD_POST}, + {"del_record_whitelist", fwx_api_del_record_whitelist, 1, FWX_API_METHOD_POST}, + {"set_record_whitelist", fwx_api_set_record_whitelist, 1, FWX_API_METHOD_POST}, + {"update_record_whitelist", fwx_api_set_record_whitelist, 1, FWX_API_METHOD_POST}, + {"record_action", fwx_api_record_action, 0, FWX_API_METHOD_POST}, + {"add_mock_visit_records", fwx_api_add_mock_visit_records, 0, FWX_API_METHOD_POST}, + {"get_lan_list", fwx_api_get_lan_list, 0, FWX_API_METHOD_GET}, + {"add_lan", fwx_api_add_lan, 1, FWX_API_METHOD_POST}, + {"mod_lan", fwx_api_mod_lan, 1, FWX_API_METHOD_POST}, + {"del_lan", fwx_api_del_lan, 1, FWX_API_METHOD_POST}, + {"get_wan_list", fwx_api_get_wan_list, 0, FWX_API_METHOD_GET}, + {"add_wan", fwx_api_add_wan, 1, FWX_API_METHOD_POST}, + {"mod_wan", fwx_api_mod_wan, 1, FWX_API_METHOD_POST}, + {"del_wan", fwx_api_del_wan, 1, FWX_API_METHOD_POST}, + {"get_lan_info", fwx_api_get_lan_info, 0, FWX_API_METHOD_GET}, + {"set_lan_info", fwx_api_set_lan_info, 1, FWX_API_METHOD_POST}, + {"get_wan_info", fwx_api_get_wan_info, 0, FWX_API_METHOD_GET}, + {"set_wan_info", fwx_api_set_wan_info, 1, FWX_API_METHOD_POST}, + {"get_firewall", fwx_api_get_firewall, 0, FWX_API_METHOD_GET}, + {"set_firewall", fwx_api_set_firewall, 1, FWX_API_METHOD_POST}, + {"get_wireless_base_setting", fwx_api_get_wireless_base_setting, 0, FWX_API_METHOD_GET}, + {"set_wireless_base_setting", fwx_api_set_wireless_base_setting, 1, FWX_API_METHOD_POST}, + {"get_work_mode", fwx_api_get_work_mode, 0, FWX_API_METHOD_GET}, + {"set_work_mode", fwx_api_set_work_mode, 1, FWX_API_METHOD_POST}, + {"set_nickname", fwx_api_set_nickname, 1, FWX_API_METHOD_POST}, + {"get_nickname_list", fwx_api_get_nickname_list, 0, FWX_API_METHOD_GET}, + {"get_mac_blacklist", fwx_api_get_mac_blacklist, 0, FWX_API_METHOD_GET}, + {"add_mac_blacklist", fwx_api_add_mac_blacklist, 1, FWX_API_METHOD_POST}, + {"del_mac_blacklist", fwx_api_del_mac_blacklist, 1, FWX_API_METHOD_POST}, + {"dev_visit_list", fwx_api_dev_visit_list, 0, FWX_API_METHOD_GET}, + {"dev_visit_time", fwx_api_dev_visit_time, 0, FWX_API_METHOD_GET}, + {"app_class_visit_time", fwx_api_app_class_visit_time, 0, FWX_API_METHOD_GET}, + {"dev_list", fwx_api_dev_list, 0, FWX_API_METHOD_GET}, + {"class_list", fwx_api_class_list, 0, FWX_API_METHOD_GET}, + {"get_all_users", fwx_api_get_all_users, 0, FWX_API_METHOD_GET}, + {"get_parental_control_detail", fwx_api_get_parental_control_detail, 0, FWX_API_METHOD_GET}, + {"get_user_parental_control_rules", fwx_api_get_user_parental_control_rules, 0, FWX_API_METHOD_GET}, + {"get_user_stat", fwx_api_get_user_stat, 0, FWX_API_METHOD_GET}, + {"visit_list", fwx_api_visit_list, 0, FWX_API_METHOD_GET}, + {"get_device_list", fwx_api_get_device_list, 0, FWX_API_METHOD_GET}, + {"get_dashboard_param", fwx_api_get_dashboard_param, 0, FWX_API_METHOD_GET}, + {"get_init_status", fwx_api_get_init_status, 0, FWX_API_METHOD_GET}, + {"set_init_status", fwx_api_set_init_status, 0, FWX_API_METHOD_POST}, + {"set_dashboard_param", fwx_api_set_dashboard_param, 1, FWX_API_METHOD_POST}, + + {NULL, NULL, 0, FWX_API_METHOD_GET} +}; + +int ubus_handle_common(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, + const char *method, struct blob_attr *msg) { + int i = 0; + fwx_api_node_t *api_node = NULL; + LOG_DEBUG("method: %s\n", method); + char *msg_obj_str = blobmsg_format_json(msg, true); + if (!msg_obj_str) + return 0; + LOG_DEBUG("received common ubus request\n"); + struct json_object *req_obj = json_tokener_parse(msg_obj_str); + if (!req_obj) { + LOG_ERROR("Failed to parse JSON request\n"); + ubus_response_json(ctx, req, fwx_gen_api_response_data(API_CODE_ERROR, NULL)); + free(msg_obj_str); + return 0; + } + const char *api_name = NULL; + struct json_object *api_obj = json_object_object_get(req_obj, "api"); + if (api_obj) { + api_name = json_object_get_string(api_obj); + } else { + LOG_ERROR("api_obj is NULL\n"); + struct json_object *error_response = fwx_gen_api_response_data(API_CODE_ERROR, NULL); + ubus_response_json(ctx, req, error_response); + json_object_put(error_response); + json_object_put(req_obj); + free(msg_obj_str); + return 0; + } + LOG_DEBUG("req data = %s\n", json_object_get_string(req_obj)); + if (fwx_api_need_copyright(api_name) && !fwx_validate_api_request(req_obj)) { + struct json_object *error_response; + + LOG_INFO("Invalid or missing CopyRight in API request: %s\n", api_name); + error_response = fwx_gen_api_response_data(API_CODE_ERROR, NULL); + ubus_response_json(ctx, req, error_response); + json_object_put(error_response); + json_object_put(req_obj); + free(msg_obj_str); + return 0; + } + + struct json_object *data_obj = json_object_object_get(req_obj, "data"); + if (!data_obj) { + data_obj = req_obj; + } + + + for (i = 0; i < sizeof(fwx_api_node_list) / sizeof(fwx_api_node_t); i++) { + + if (fwx_api_node_list[i].api_name == NULL) { + break; + } + if (strcmp(fwx_api_node_list[i].api_name, api_name) == 0) { + api_node = &fwx_api_node_list[i]; + LOG_DEBUG("found api_node: %s\n", api_node->api_name); + break; + } + } + struct json_object *response_obj = NULL; + if (api_node && api_node->handler) { + response_obj = api_node->handler(data_obj); + if (response_obj) { + fwx_add_api_response_copyright(response_obj); + ubus_response_json(ctx, req, response_obj); + if (api_node->forward) { + struct json_object *code_obj = json_object_object_get(response_obj, "code"); + struct json_object *no_forward_obj = json_object_object_get(data_obj, "no_forward"); + int no_forward = no_forward_obj ? json_object_get_int(no_forward_obj) : 0; + if (code_obj && json_object_get_int(code_obj) == API_CODE_SUCCESS && !no_forward) { + fwx_forward_to_agent(req_obj); + } + } + } else { + LOG_ERROR("Handler returned NULL for API: %s\n", api_name); + response_obj = fwx_gen_api_response_data(API_CODE_ERROR, NULL); + ubus_response_json(ctx, req, response_obj); + } + } + else { + LOG_WARN("API not found: %s\n", api_name); + response_obj = fwx_gen_api_response_data(API_CODE_ERROR, NULL); + ubus_response_json(ctx, req, response_obj); + } + + if (response_obj) { + json_object_put(response_obj); + } + json_object_put(req_obj); + free(msg_obj_str); + return 0; +} + +static const struct blobmsg_policy def_policy[1] = { + +}; + + +int ubus_handle_common(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, + const char *method, struct blob_attr *msg); + +static struct ubus_method fwx_object_methods[] = { + UBUS_METHOD("common", ubus_handle_common, def_policy), + UBUS_METHOD("debug", handle_debug, def_policy), +}; + + +static struct ubus_object_type fwx_object_type = + UBUS_OBJECT_TYPE("fwx", fwx_object_methods); + + +static struct ubus_object fwx_object = { + .name = "fwx", + .type = &fwx_object_type, + .methods = fwx_object_methods, + .n_methods = ARRAY_SIZE(fwx_object_methods), +}; + +static void fwx_add_object(struct ubus_object *obj) +{ + int ret = ubus_add_object(ubus_ctx, obj); + if (ret != 0) + LOG_ERROR("Failed to publish object '%s': %s\n", obj->name, ubus_strerror(ret)); +} + +int fwx_ubus_init(void) +{ + LOG_INFO("fwx ubus init...\n"); + ubus_ctx = ubus_connect("/var/run/ubus/ubus.sock"); + if (!ubus_ctx){ + ubus_ctx = ubus_connect("/var/run/ubus.sock"); + } + if (!ubus_ctx){ + LOG_ERROR("Failed to connect to ubus\n"); + return -EIO; + } + + fwx_add_object(&fwx_object); + ubus_add_uloop(ubus_ctx); + return 0; +} diff --git a/open-app-filter/src/fwx_ubus.h b/open-app-filter/src/fwx_ubus.h new file mode 100644 index 00000000..beae198a --- /dev/null +++ b/open-app-filter/src/fwx_ubus.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ + +#ifndef __APPFILTER_UBUS_H__ +#define __APPFILTER_UBUS_H__ +int fwx_ubus_init(void); +#endif diff --git a/open-app-filter/src/appfilter_config.c b/open-app-filter/src/fwx_uci.c similarity index 55% rename from open-app-filter/src/appfilter_config.c rename to open-app-filter/src/fwx_uci.c index 140a84f1..b5e12ca1 100644 --- a/open-app-filter/src/appfilter_config.c +++ b/open-app-filter/src/fwx_uci.c @@ -1,42 +1,19 @@ -/* -Copyright (C) 2020 Derry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) */ #include #include #include -#include "appfilter_config.h" -#include "appfilter.h" #include - -app_name_info_t app_name_table[MAX_SUPPORT_APP_NUM]; -int g_app_count = 0; -int g_cur_class_num = 0; -char CLASS_NAME_TABLE[MAX_APP_TYPE][MAX_CLASS_NAME_LEN]; +#include "fwx_uci.h" const char *config_path = "./config"; static struct uci_context *uci_ctx = NULL; static struct uci_package *uci_appfilter; -int af_uci_get_int_value(struct uci_context *ctx, char *key) +int fwx_uci_get_int_value(struct uci_context *ctx, char *key) { struct uci_element *e; struct uci_ptr ptr; @@ -73,7 +50,7 @@ done: } -int af_uci_get_value(struct uci_context *ctx, char *key, char *output, int out_len) +int fwx_uci_get_value(struct uci_context *ctx, char *key, char *output, int out_len) { struct uci_element *e; struct uci_ptr ptr; @@ -112,7 +89,7 @@ done: } -int af_uci_delete(struct uci_context *ctx, char *key) +int fwx_uci_delete(struct uci_context *ctx, char *key) { struct uci_element *e; struct uci_ptr ptr; @@ -136,7 +113,7 @@ int af_uci_delete(struct uci_context *ctx, char *key) -int af_uci_add_list(struct uci_context *ctx, char *key, char *value) +int fwx_uci_add_list(struct uci_context *ctx, char *key, char *value) { struct uci_element *e; struct uci_ptr ptr; @@ -163,7 +140,7 @@ int af_uci_add_list(struct uci_context *ctx, char *key, char *value) } -int af_uci_get_list_value(struct uci_context *ctx, char *key, char *output, int out_len, char *delimt) +int fwx_uci_get_list_value(struct uci_context *ctx, char *key, char *output, int out_len, char *delimt) { struct uci_element *e; struct uci_ptr ptr; @@ -212,7 +189,7 @@ done: } -int af_uci_add_int_list(struct uci_context *ctx, char *key, int value) +int fwx_uci_add_int_list(struct uci_context *ctx, char *key, int value) { struct uci_element *e; struct uci_ptr ptr; @@ -234,7 +211,7 @@ int af_uci_add_int_list(struct uci_context *ctx, char *key, int value) return ret; } -int af_uci_del_list(struct uci_context *ctx, char *key, char *value) +int fwx_uci_del_list(struct uci_context *ctx, char *key, char *value) { struct uci_element *e; struct uci_ptr ptr; @@ -257,7 +234,7 @@ int af_uci_del_list(struct uci_context *ctx, char *key, char *value) } -int af_uci_set_value(struct uci_context *ctx, char *key, char *value) +int fwx_uci_set_value(struct uci_context *ctx, char *key, char *value) { struct uci_element *e; struct uci_ptr ptr; @@ -281,7 +258,7 @@ int af_uci_set_value(struct uci_context *ctx, char *key, char *value) return ret; } -int af_uci_set_int_value(struct uci_context *ctx, char *key, int value) +int fwx_uci_set_int_value(struct uci_context *ctx, char *key, int value) { struct uci_element *e; struct uci_ptr ptr; @@ -304,19 +281,19 @@ int af_uci_set_int_value(struct uci_context *ctx, char *key, int value) return ret; } -int af_uci_del_array_value(struct uci_context *ctx, char *key_fmt, int index){ +int fwx_uci_del_array_value(struct uci_context *ctx, char *key_fmt, int index){ char key[128] = {0}; sprintf(key, key_fmt, index); - return af_uci_delete(ctx, key); + return fwx_uci_delete(ctx, key); } -int af_uci_set_array_value(struct uci_context *ctx, char *key_fmt, int index, char *value){ +int fwx_uci_set_array_value(struct uci_context *ctx, char *key_fmt, int index, char *value){ char key[128] = {0}; sprintf(key, key_fmt, index); - return af_uci_set_value(ctx, key, value); + return fwx_uci_set_value(ctx, key, value); } -int af_uci_commit(struct uci_context *ctx, const char * package) { +int fwx_uci_commit(struct uci_context *ctx, const char * package) { struct uci_ptr ptr; int ret = UCI_OK; if (!package){ @@ -337,7 +314,7 @@ done: return UCI_OK; } -int af_get_uci_list_num(struct uci_context * ctx, char *package, char *section){ +int fwx_uci_get_list_num(struct uci_context * ctx, char *package, char *section){ int count = 0; struct uci_ptr p; struct uci_element *e; @@ -356,14 +333,14 @@ int af_get_uci_list_num(struct uci_context * ctx, char *package, char *section){ uci_unload(ctx, pkg); return count; } -int af_uci_get_array_value(struct uci_context *ctx, char *key_fmt, int index, char *output, int out_len) +int fwx_uci_get_array_value(struct uci_context *ctx, char *key_fmt, int index, char *output, int out_len) { char key[128] = {0}; sprintf(key, key_fmt, index); - return af_uci_get_value(ctx, key, output, out_len); + return fwx_uci_get_value(ctx, key, output, out_len); } -int af_uci_add_section(struct uci_context * ctx, char *package_name, char *section) +int fwx_uci_add_section(struct uci_context * ctx, char *package_name, char *section) { struct uci_section *s = NULL; struct uci_package *p = NULL; @@ -381,9 +358,8 @@ done: fprintf(stdout, "%s\n", s->e.name); return ret; } -// -static struct uci_package * -config_init_package(const char *config) + +static struct uci_package *fwx_uci_get_package(const char *config) { struct uci_context *ctx = uci_ctx; struct uci_package *p = NULL; @@ -393,8 +369,8 @@ config_init_package(const char *config) ctx = uci_alloc_context(); uci_ctx = ctx; ctx->flags &= ~UCI_FLAG_STRICT; - //if (config_path) - // uci_set_confdir(ctx, config_path); + + } else { @@ -408,145 +384,3 @@ config_init_package(const char *config) return p; } -char *get_app_name_by_id(int id) -{ - int i; - for (i = 0; i < g_app_count; i++) - { - if (id == app_name_table[i].id) - return app_name_table[i].name; - } - return ""; -} - -void init_app_name_table(void) -{ - int count = 0; - char line_buf[2048] = {0}; - - FILE *fp = fopen("/tmp/feature.cfg", "r"); - if (!fp) - { - printf("open file failed\n"); - return; - } - g_app_count = 0; - while (fgets(line_buf, sizeof(line_buf), fp)) - { - if (strstr(line_buf, "#")) - continue; - if (strlen(line_buf) < 10) - continue; - if (!strstr(line_buf, ":")) - continue; - char *pos1 = strstr(line_buf, ":"); - char app_info_buf[128] = {0}; - int app_id; - char app_name[64] = {0}; - memset(app_name, 0x0, sizeof(app_name)); - strncpy(app_info_buf, line_buf, pos1 - line_buf); - sscanf(app_info_buf, "%d %s", &app_id, app_name); - app_name_table[g_app_count].id = app_id; - strcpy(app_name_table[g_app_count].name, app_name); - g_app_count++; - } - fclose(fp); -} - -void init_app_class_name_table(void) -{ - char line_buf[2048] = {0}; - int class_id; - char class_name[64] = {0}; - FILE *fp = fopen("/tmp/app_class.txt", "r"); - if (!fp) - { - printf("open file failed\n"); - return; - } - g_cur_class_num = 0; - while (fgets(line_buf, sizeof(line_buf), fp)) - { - sscanf(line_buf, "%d %*s %s", &class_id, class_name); - strcpy(CLASS_NAME_TABLE[class_id - 1], class_name); - g_cur_class_num++; - } - fclose(fp); -} -//00:00 9:1 -int check_time_valid(char *t) -{ - if (!t) - return 0; - if (strlen(t) < 3 || strlen(t) > 5 || (!strstr(t, ":"))) - return 0; - else - return 1; -} - - -int config_get_appfilter_enable(void) -{ - int enable = 0; - struct uci_context *ctx = uci_alloc_context(); - if (!ctx) - return -1; - enable = af_uci_get_int_value(ctx, "appfilter.global.enable"); - if (enable < 0) - enable = 0; - - uci_free_context(ctx); - return enable; -} - -int config_get_lan_ip(char *lan_ip, int len) -{ - int ret = 0; - struct uci_context *ctx = uci_alloc_context(); - if (!ctx) - return -1; - ret = af_uci_get_value(ctx, "network.lan.ipaddr", lan_ip, len); - uci_free_context(ctx); - return ret; -} - -int config_get_lan_mask(char *lan_mask, int len) -{ - int ret = 0; - struct uci_context *ctx = uci_alloc_context(); - if (!ctx) - return -1; - ret = af_uci_get_value(ctx, "network.lan.netmask", lan_mask, len); - uci_free_context(ctx); - return ret; -} - - -int appfilter_config_alloc(void) -{ - char *err; - uci_appfilter = config_init_package("appfilter"); - if (!uci_appfilter) - { - uci_get_errorstr(uci_ctx, &err, NULL); - printf("Failed to load appfilter config (%s)\n", err); - free(err); - return -1; - } - - return 0; -} - -int appfilter_config_free(void) -{ - if (uci_appfilter) - { - uci_unload(uci_ctx, uci_appfilter); - uci_appfilter = NULL; - } - if (uci_ctx) - { - uci_free_context(uci_ctx); - uci_ctx = NULL; - } -} diff --git a/open-app-filter/src/fwx_uci.h b/open-app-filter/src/fwx_uci.h new file mode 100644 index 00000000..31fd0dad --- /dev/null +++ b/open-app-filter/src/fwx_uci.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_UCI_H__ +#define __FWX_UCI_H__ +#include + +#define MAX_PARAM_LIST_LEN 1024 + +int fwx_uci_get_int_value(struct uci_context *ctx, char *key); +int fwx_uci_get_value(struct uci_context *ctx, char *key, char *output, int out_len); +int fwx_uci_add_list(struct uci_context *ctx, char *key, char *value); +int fwx_uci_get_list_value(struct uci_context *ctx, char *key, char *output, int out_len, char *delimt); +int fwx_uci_add_int_list(struct uci_context *ctx, char *key, int value); +int fwx_uci_del_list(struct uci_context *ctx, char *key, char *value); +int fwx_uci_set_value(struct uci_context *ctx, char *key, char *value); +int fwx_uci_set_int_value(struct uci_context *ctx, char *key, int value); +int fwx_uci_del_array_value(struct uci_context *ctx, char *key_fmt, int index); +int fwx_uci_set_array_value(struct uci_context *ctx, char *key_fmt, int index, char *value); +int fwx_uci_get_list_num(struct uci_context * ctx, char *package, char *section); +int fwx_uci_get_array_value(struct uci_context *ctx, char *key_fmt, int index, char *output, int out_len); +int fwx_uci_add_section(struct uci_context * ctx, char *package_name, char *section); +int fwx_uci_commit(struct uci_context *ctx, const char * package); +int fwx_uci_delete(struct uci_context *ctx, char *key); +#endif + diff --git a/open-app-filter/src/fwx_user.c b/open-app-filter/src/fwx_user.c new file mode 100644 index 00000000..f12c8c77 --- /dev/null +++ b/open-app-filter/src/fwx_user.c @@ -0,0 +1,4413 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fwx_config.h" +#include "fwx.h" +#include "fwx_user.h" +#include "fwx_utils.h" + + +LIST_HEAD(client_list); +LIST_HEAD(user_record_list); +int g_cur_user_num = 0; + + +static int g_app_valid_time = 300; + +unsigned long long g_daily_type_stats[MAX_APP_TYPE] = {0}; +u_int32_t g_daily_stat_date = 0; + + +LIST_HEAD(global_hourly_records); + + +traffic_stat_t g_global_hourly_traffic[HOURS_PER_DAY] = {{0}}; +u_int32_t g_global_traffic_date = 0; + + +#define CLIENT_DATA_BASE_DIR_DEFAULT "/tmp/fwx/client_data" +#define MAX_WIRELESS_IFACE_NUM 32 +#define MAX_WIRELESS_IFNAME_LEN 64 +#define MAX_WIRELESS_BAND_LEN 16 + +static char g_client_data_base_dir[256] = {0}; +static int g_client_data_base_dir_initialized = 0; +static char g_client_data_root_dir[256] = {0}; +static int g_client_data_root_dir_initialized = 0; +static char g_history_data_root_dir[256] = {0}; +static int g_history_data_root_dir_initialized = 0; + +static void mac_to_dirname(const char *mac, char *dirname, size_t len); +static int ensure_dir_exists(const char *path); +static void get_date_string(u_int32_t timestamp, char *date_str, size_t len); +static void format_time_string(u_int32_t timestamp, char *time_str, size_t len); +static void cleanup_old_record_files(void); +static u_int32_t parse_date_string(const char *date_str); +static int extract_date_from_filename(const char *filename, char *date_str, size_t len); +static void get_client_backup_dir(char *dir_path, size_t len); +static void build_client_backup_mac_dir(const char *mac, char *dir_path, size_t len); +static void build_client_backup_base_info_dir(const char *mac, char *dir_path, size_t len); +static void build_client_backup_file_path(const char *mac, char *file_path, size_t len); +static int load_client_backup_from_file(const char *file_path); +static u_int32_t get_today_start_timestamp_from_ts(u_int32_t timestamp); +static void build_client_visit_db_path(char *db_path, size_t len); +static int open_client_visit_db(sqlite3 **db); +static int save_visit_record_to_db(const char *mac, visit_info_t *visit); +static int find_oldest_date_in_visit_db(char *oldest_date, size_t len); +static int delete_visit_records_by_date(const char *date_str); +static int delete_expired_visit_records(u_int32_t expire_timestamp); +static int delete_oldest_visit_records_batch(int batch_count); +static int get_visit_record_count(void); +static int vacuum_visit_db_file(void); +static int delete_visit_records_in_range(const char *mac, u_int32_t start_timestamp, u_int32_t end_timestamp); +static char *get_command_output(const char *cmd); +static struct json_object *get_command_json(const char *cmd); +static void clear_client_wireless_status(void); +static client_node_t *find_client_node_nocase(const char *mac); +static unsigned int get_wireless_rate_value(struct json_object *rate_obj); +static void append_wireless_ifname(char ifnames[][MAX_WIRELESS_IFNAME_LEN], char bands[][MAX_WIRELESS_BAND_LEN], int *if_num, int max_if_num, const char *ifname, const char *band); +static int collect_wireless_ifnames(char ifnames[][MAX_WIRELESS_IFNAME_LEN], char bands[][MAX_WIRELESS_BAND_LEN], int max_if_num); +static void update_client_wireless_status_by_ifname(const char *ifname, const char *band); + +static void session_push_recent_app(online_session_stat_t *session, int appid) { + int i; + if (!session || appid <= 0) + return; + + for (i = 0; i < session->recent_app_count; i++) { + if (session->recent_apps[i] == appid) { + int j; + for (j = i; j > 0; j--) { + session->recent_apps[j] = session->recent_apps[j - 1]; + } + session->recent_apps[0] = appid; + return; + } + } + + if (session->recent_app_count < MAX_RECENT_APPS) { + for (i = session->recent_app_count; i > 0; i--) { + session->recent_apps[i] = session->recent_apps[i - 1]; + } + session->recent_apps[0] = appid; + session->recent_app_count++; + } else { + for (i = MAX_RECENT_APPS - 1; i > 0; i--) { + session->recent_apps[i] = session->recent_apps[i - 1]; + } + session->recent_apps[0] = appid; + } +} + +void reset_online_session_stat(client_node_t *client, u_int32_t start_time) { + if (!client) + return; + memset(&client->online_session, 0, sizeof(client->online_session)); + client->online_session.start_time = start_time; +} + +void update_online_session_flow(client_node_t *client, unsigned long long up_bytes, unsigned long long down_bytes) { + if (!client || !client->online) + return; + client->online_session.up_bytes += up_bytes; + client->online_session.down_bytes += down_bytes; +} + +void update_online_session_activity(client_node_t *client, int online_seconds, int active_seconds) { + if (!client || !client->online) + return; + if (online_seconds > 0) + client->online_session.online_duration += (unsigned long long)online_seconds; + if (active_seconds > 0) + client->online_session.active_duration += (unsigned long long)active_seconds; +} + +void update_online_session_recent_app(client_node_t *client, int appid) { + if (!client || !client->online) + return; + session_push_recent_app(&client->online_session, appid); +} + +void add_user_record(client_node_t *client, int action, u_int32_t timestamp) { + user_record_t *record = NULL; + int total = 0; + struct list_head *pos, *n; + if (!client) + return; + + record = (user_record_t *)calloc(1, sizeof(user_record_t)); + if (!record) + return; + + record->action = action; + record->timestamp = timestamp; + strncpy(record->mac, client->mac, sizeof(record->mac) - 1); + strncpy(record->nickname, client->nickname, sizeof(record->nickname) - 1); + strncpy(record->hostname, client->hostname, sizeof(record->hostname) - 1); + + if (action == 1) { + record->up_bytes = client->online_session.up_bytes; + record->down_bytes = client->online_session.down_bytes; + record->online_duration = client->online_session.online_duration; + record->active_duration = client->online_session.active_duration; + record->recent_app_count = client->online_session.recent_app_count; + if (record->recent_app_count > MAX_RECENT_APPS) { + record->recent_app_count = MAX_RECENT_APPS; + } + if (record->recent_app_count > 0) { + memcpy(record->recent_apps, client->online_session.recent_apps, + sizeof(int) * record->recent_app_count); + } + } + + INIT_LIST_HEAD(&record->list); + list_add(&record->list, &user_record_list); + + list_for_each(pos, &user_record_list) { + total++; + } + while (total > MAX_USER_RECORDS) { + user_record_t *last = NULL; + list_for_each_safe(pos, n, &user_record_list) { + last = list_entry(pos, user_record_t, list); + } + if (!last) { + break; + } + list_del(&last->list); + free(last); + total--; + } +} + +const char *get_client_data_root_dir(void) { + if (!g_client_data_root_dir_initialized) { + struct uci_context *uci_ctx = uci_alloc_context(); + if (uci_ctx) { + char base_data_path[256] = {0}; + char terminal_data_path[256] = {0}; + char history_data_path[256] = {0}; + int ret = fwx_uci_get_value(uci_ctx, "fwx.record.base_data_path", base_data_path, sizeof(base_data_path)); + if (ret == 0 && strlen(base_data_path) > 0) { + strncpy(g_client_data_root_dir, base_data_path, sizeof(g_client_data_root_dir) - 1); + } else if (fwx_uci_get_value(uci_ctx, "fwx.record.terminal_data_path", terminal_data_path, sizeof(terminal_data_path)) == 0 && + strlen(terminal_data_path) > 0) { + strncpy(g_client_data_root_dir, terminal_data_path, sizeof(g_client_data_root_dir) - 1); + } else if (fwx_uci_get_value(uci_ctx, "fwx.record.history_data_path", history_data_path, sizeof(history_data_path)) == 0 && + strlen(history_data_path) > 0) { + strncpy(g_client_data_root_dir, history_data_path, sizeof(g_client_data_root_dir) - 1); + } else { + strncpy(g_client_data_root_dir, "/tmp/fwx", sizeof(g_client_data_root_dir) - 1); + } + uci_free_context(uci_ctx); + } else { + strncpy(g_client_data_root_dir, "/tmp/fwx", sizeof(g_client_data_root_dir) - 1); + } + g_client_data_root_dir[sizeof(g_client_data_root_dir) - 1] = '\0'; + g_client_data_root_dir_initialized = 1; + } + return g_client_data_root_dir; +} + +const char *get_history_data_root_dir(void) { + if (!g_history_data_root_dir_initialized) { + struct uci_context *uci_ctx = uci_alloc_context(); + if (uci_ctx) { + char history_data_path[256] = {0}; + char base_data_path[256] = {0}; + char terminal_data_path[256] = {0}; + int ret = fwx_uci_get_value(uci_ctx, "fwx.record.history_data_path", history_data_path, sizeof(history_data_path)); + if (ret == 0 && strlen(history_data_path) > 0) { + strncpy(g_history_data_root_dir, history_data_path, sizeof(g_history_data_root_dir) - 1); + } else if (fwx_uci_get_value(uci_ctx, "fwx.record.base_data_path", base_data_path, sizeof(base_data_path)) == 0 && + strlen(base_data_path) > 0) { + strncpy(g_history_data_root_dir, base_data_path, sizeof(g_history_data_root_dir) - 1); + } else if (fwx_uci_get_value(uci_ctx, "fwx.record.terminal_data_path", terminal_data_path, sizeof(terminal_data_path)) == 0 && + strlen(terminal_data_path) > 0) { + strncpy(g_history_data_root_dir, terminal_data_path, sizeof(g_history_data_root_dir) - 1); + } else { + strncpy(g_history_data_root_dir, "/tmp/fwx", sizeof(g_history_data_root_dir) - 1); + } + uci_free_context(uci_ctx); + } else { + strncpy(g_history_data_root_dir, "/tmp/fwx", sizeof(g_history_data_root_dir) - 1); + } + g_history_data_root_dir[sizeof(g_history_data_root_dir) - 1] = '\0'; + g_history_data_root_dir_initialized = 1; + } + return g_history_data_root_dir; +} + +const char *get_client_data_base_dir(void) { + if (!g_client_data_base_dir_initialized) { + snprintf(g_client_data_base_dir, sizeof(g_client_data_base_dir), "%s/client_data", get_client_data_root_dir()); + g_client_data_base_dir[sizeof(g_client_data_base_dir) - 1] = '\0'; + g_client_data_base_dir_initialized = 1; + } + return g_client_data_base_dir; +} + +void reset_client_data_base_dir_cache(void) { + g_client_data_base_dir_initialized = 0; + g_client_data_base_dir[0] = '\0'; + g_client_data_root_dir_initialized = 0; + g_client_data_root_dir[0] = '\0'; + g_history_data_root_dir_initialized = 0; + g_history_data_root_dir[0] = '\0'; +} + +static void init_daily_stats_default(daily_hourly_stat_t *stat) { + int i, j; + + if (!stat) + return; + + for (i = 0; i < HOURS_PER_DAY; i++) { + stat->hourly_traffic[i].up_bytes = 0; + stat->hourly_traffic[i].down_bytes = 0; + stat->hourly_online_time[i] = 0; + stat->hourly_active_time[i] = 0; + for (j = 0; j < TOP_APP_PER_HOUR; j++) { + stat->hourly_top_apps[i][j] = -1; + } + } +} + +static void init_daily_top_apps_default(daily_top_apps_stat_t *stat) { + int i; + + if (!stat) + return; + + stat->count = 0; + for (i = 0; i < MAX_TOP_APPS_PER_DAY; i++) { + stat->apps[i].appid = -1; + stat->apps[i].total_time = 0; + } +} + +static void reset_client_runtime_fields(client_node_t *client) { + if (!client) + return; + + client->up_rate = 0; + client->down_rate = 0; + client->rssi = 0; + client->rx_rate = 0; + client->tx_rate = 0; + client->band[0] = '\0'; + client->wifi_ifname[0] = '\0'; + client->wireless_online = 0; + client->online = 0; + client->visiting_url[0] = '\0'; + client->visiting_app = 0; + client->active = 0; + client->last_online_state = 0; + client->session_online_recorded = 0; +} + +static void get_client_backup_dir(char *dir_path, size_t len) { + if (!dir_path || len == 0) + return; + + snprintf(dir_path, len, "%s/client_backup", get_client_data_root_dir()); +} + +static void build_client_backup_mac_dir(const char *mac, char *dir_path, size_t len) { + char backup_dir[512] = {0}; + char mac_dirname[64] = {0}; + + if (!mac || !dir_path || len == 0) + return; + + get_client_backup_dir(backup_dir, sizeof(backup_dir)); + mac_to_dirname(mac, mac_dirname, sizeof(mac_dirname)); + snprintf(dir_path, len, "%s/%s", backup_dir, mac_dirname); +} + +static void build_client_backup_base_info_dir(const char *mac, char *dir_path, size_t len) { + char mac_dir[512] = {0}; + + if (!mac || !dir_path || len == 0) + return; + + build_client_backup_mac_dir(mac, mac_dir, sizeof(mac_dir)); + snprintf(dir_path, len, "%s/base_info", mac_dir); +} + +static void build_client_backup_file_path(const char *mac, char *file_path, size_t len) { + char base_info_dir[512] = {0}; + + if (!mac || !file_path || len == 0) + return; + + build_client_backup_base_info_dir(mac, base_info_dir, sizeof(base_info_dir)); + snprintf(file_path, len, "%s/client.json", base_info_dir); +} + +static struct json_object *serialize_online_session_stat(const online_session_stat_t *session) { + int i; + struct json_object *session_obj = json_object_new_object(); + struct json_object *recent_apps = json_object_new_array(); + + if (!session) + return session_obj; + + json_object_object_add(session_obj, "up_bytes", json_object_new_int64(session->up_bytes)); + json_object_object_add(session_obj, "down_bytes", json_object_new_int64(session->down_bytes)); + json_object_object_add(session_obj, "online_duration", json_object_new_int64(session->online_duration)); + json_object_object_add(session_obj, "active_duration", json_object_new_int64(session->active_duration)); + json_object_object_add(session_obj, "recent_app_count", json_object_new_int(session->recent_app_count)); + json_object_object_add(session_obj, "start_time", json_object_new_int64(session->start_time)); + + for (i = 0; i < session->recent_app_count && i < MAX_RECENT_APPS; i++) { + json_object_array_add(recent_apps, json_object_new_int(session->recent_apps[i])); + } + json_object_object_add(session_obj, "recent_apps", recent_apps); + + return session_obj; +} + +static struct json_object *serialize_daily_stats(const daily_hourly_stat_t *stat) { + int i, j; + struct json_object *stat_obj = json_object_new_object(); + struct json_object *hourly_top_apps = json_object_new_array(); + struct json_object *hourly_traffic = json_object_new_array(); + struct json_object *hourly_online_time = json_object_new_array(); + struct json_object *hourly_active_time = json_object_new_array(); + + if (!stat) + return stat_obj; + + json_object_object_add(stat_obj, "date", json_object_new_int64(stat->date)); + json_object_object_add(stat_obj, "is_today", json_object_new_int(stat->is_today)); + + for (i = 0; i < HOURS_PER_DAY; i++) { + struct json_object *top_apps = json_object_new_array(); + struct json_object *traffic_obj = json_object_new_object(); + for (j = 0; j < TOP_APP_PER_HOUR; j++) { + json_object_array_add(top_apps, json_object_new_int(stat->hourly_top_apps[i][j])); + } + json_object_array_add(hourly_top_apps, top_apps); + + json_object_object_add(traffic_obj, "up_bytes", json_object_new_int64(stat->hourly_traffic[i].up_bytes)); + json_object_object_add(traffic_obj, "down_bytes", json_object_new_int64(stat->hourly_traffic[i].down_bytes)); + json_object_array_add(hourly_traffic, traffic_obj); + + json_object_array_add(hourly_online_time, json_object_new_int64(stat->hourly_online_time[i])); + json_object_array_add(hourly_active_time, json_object_new_int64(stat->hourly_active_time[i])); + } + + json_object_object_add(stat_obj, "hourly_top_apps", hourly_top_apps); + json_object_object_add(stat_obj, "hourly_traffic", hourly_traffic); + json_object_object_add(stat_obj, "hourly_online_time", hourly_online_time); + json_object_object_add(stat_obj, "hourly_active_time", hourly_active_time); + + return stat_obj; +} + +static struct json_object *serialize_daily_top_apps_stats(const daily_top_apps_stat_t *stat) { + int i; + struct json_object *stat_obj = json_object_new_object(); + struct json_object *apps = json_object_new_array(); + + if (!stat) + return stat_obj; + + json_object_object_add(stat_obj, "date", json_object_new_int64(stat->date)); + json_object_object_add(stat_obj, "is_today", json_object_new_int(stat->is_today)); + json_object_object_add(stat_obj, "count", json_object_new_int(stat->count)); + + for (i = 0; i < MAX_TOP_APPS_PER_DAY; i++) { + struct json_object *app_obj = json_object_new_object(); + json_object_object_add(app_obj, "appid", json_object_new_int(stat->apps[i].appid)); + if (!app_icon_exists_by_id(stat->apps[i].appid)) + json_object_object_add(app_obj, "icon", json_object_new_int(0)); + json_object_object_add(app_obj, "total_time", json_object_new_int64(stat->apps[i].total_time)); + json_object_array_add(apps, app_obj); + } + + json_object_object_add(stat_obj, "apps", apps); + return stat_obj; +} + +void save_client_backup_to_file(client_node_t *client) { + char backup_dir[512] = {0}; + char mac_dir[512] = {0}; + char base_info_dir[512] = {0}; + char file_path[512] = {0}; + struct json_object *json_obj = NULL; + if (!client) + return; + + get_client_backup_dir(backup_dir, sizeof(backup_dir)); + if (ensure_dir_exists(backup_dir) != 0) { + LOG_ERROR("Failed to create client backup directory: %s\n", backup_dir); + return; + } + + build_client_backup_mac_dir(client->mac, mac_dir, sizeof(mac_dir)); + if (ensure_dir_exists(mac_dir) != 0) { + LOG_ERROR("Failed to create client backup mac directory: %s\n", mac_dir); + return; + } + + build_client_backup_base_info_dir(client->mac, base_info_dir, sizeof(base_info_dir)); + if (ensure_dir_exists(base_info_dir) != 0) { + LOG_ERROR("Failed to create client backup base_info directory: %s\n", base_info_dir); + return; + } + + build_client_backup_file_path(client->mac, file_path, sizeof(file_path)); + + json_obj = json_object_new_object(); + json_object_object_add(json_obj, "mac", json_object_new_string(client->mac)); + json_object_object_add(json_obj, "ip", json_object_new_string(client->ip)); + json_object_object_add(json_obj, "ipv6", json_object_new_string(client->ipv6)); + json_object_object_add(json_obj, "hostname", json_object_new_string(client->hostname)); + json_object_object_add(json_obj, "nickname", json_object_new_string(client->nickname)); + json_object_object_add(json_obj, "expire", json_object_new_int(client->expire)); + json_object_object_add(json_obj, "offline_time", json_object_new_int64(client->offline_time)); + json_object_object_add(json_obj, "online_time", json_object_new_int64(client->online_time)); + json_object_object_add(json_obj, "mf_user_loaded", json_object_new_int(client->mf_user_loaded)); + json_object_object_add(json_obj, "is_wireless", json_object_new_int(client->is_wireless)); + json_object_object_add(json_obj, "online_session", serialize_online_session_stat(&client->online_session)); + json_object_object_add(json_obj, "daily_stats", serialize_daily_stats(&client->daily_stats)); + json_object_object_add(json_obj, "daily_top_apps_stats", serialize_daily_top_apps_stats(&client->daily_top_apps_stats)); + + if (json_object_to_file_ext(file_path, json_obj, JSON_C_TO_STRING_PRETTY) != 0) { + LOG_ERROR("Failed to save client backup to file: %s (errno: %d)\n", file_path, errno); + } + LOG_INFO("444 path = %s\n", file_path); + LOG_INFO("data = %s\n", json_object_get_string(json_obj)); + + json_object_put(json_obj); +} + +void save_all_client_backup_to_files(void) { + client_node_t *node = NULL; + + list_for_each_entry(node, &client_list, client) { + LOG_INFO("begin save %s\n",node->mac); + save_client_backup_to_file(node); + } +} + +static void load_online_session_stat_from_json(online_session_stat_t *session, struct json_object *session_obj) { + int i; + struct json_object *value_obj = NULL; + struct json_object *recent_apps_obj = NULL; + + if (!session) { + return; + } + + memset(session, 0, sizeof(*session)); + if (!session_obj) + return; + + if (json_object_object_get_ex(session_obj, "up_bytes", &value_obj)) + session->up_bytes = json_object_get_int64(value_obj); + if (json_object_object_get_ex(session_obj, "down_bytes", &value_obj)) + session->down_bytes = json_object_get_int64(value_obj); + if (json_object_object_get_ex(session_obj, "online_duration", &value_obj)) + session->online_duration = json_object_get_int64(value_obj); + if (json_object_object_get_ex(session_obj, "active_duration", &value_obj)) + session->active_duration = json_object_get_int64(value_obj); + if (json_object_object_get_ex(session_obj, "recent_app_count", &value_obj)) + session->recent_app_count = json_object_get_int(value_obj); + if (json_object_object_get_ex(session_obj, "start_time", &value_obj)) + session->start_time = json_object_get_int64(value_obj); + + if (session->recent_app_count < 0) + session->recent_app_count = 0; + if (session->recent_app_count > MAX_RECENT_APPS) + session->recent_app_count = MAX_RECENT_APPS; + + if (json_object_object_get_ex(session_obj, "recent_apps", &recent_apps_obj) && + json_object_get_type(recent_apps_obj) == json_type_array) { + int count = json_object_array_length(recent_apps_obj); + if (count > session->recent_app_count) + count = session->recent_app_count; + for (i = 0; i < count; i++) { + session->recent_apps[i] = json_object_get_int(json_object_array_get_idx(recent_apps_obj, i)); + } + } +} + +static void load_daily_stats_from_json(daily_hourly_stat_t *stat, struct json_object *stat_obj) { + int i, j; + struct json_object *value_obj = NULL; + struct json_object *top_apps_obj = NULL; + struct json_object *traffic_obj = NULL; + struct json_object *online_time_obj = NULL; + struct json_object *active_time_obj = NULL; + + if (!stat) + return; + + memset(stat, 0, sizeof(*stat)); + init_daily_stats_default(stat); + if (!stat_obj) + return; + + if (json_object_object_get_ex(stat_obj, "date", &value_obj)) + stat->date = json_object_get_int64(value_obj); + if (json_object_object_get_ex(stat_obj, "is_today", &value_obj)) + stat->is_today = json_object_get_int(value_obj); + + if (json_object_object_get_ex(stat_obj, "hourly_top_apps", &top_apps_obj) && + json_object_get_type(top_apps_obj) == json_type_array) { + int hour_count = json_object_array_length(top_apps_obj); + if (hour_count > HOURS_PER_DAY) + hour_count = HOURS_PER_DAY; + for (i = 0; i < hour_count; i++) { + struct json_object *hour_obj = json_object_array_get_idx(top_apps_obj, i); + if (!hour_obj || json_object_get_type(hour_obj) != json_type_array) + continue; + for (j = 0; j < TOP_APP_PER_HOUR && j < json_object_array_length(hour_obj); j++) { + stat->hourly_top_apps[i][j] = json_object_get_int(json_object_array_get_idx(hour_obj, j)); + } + } + } + + if (json_object_object_get_ex(stat_obj, "hourly_traffic", &traffic_obj) && + json_object_get_type(traffic_obj) == json_type_array) { + int hour_count = json_object_array_length(traffic_obj); + if (hour_count > HOURS_PER_DAY) + hour_count = HOURS_PER_DAY; + for (i = 0; i < hour_count; i++) { + struct json_object *hour_obj = json_object_array_get_idx(traffic_obj, i); + struct json_object *up_obj = NULL; + struct json_object *down_obj = NULL; + if (!hour_obj) + continue; + if (json_object_object_get_ex(hour_obj, "up_bytes", &up_obj)) + stat->hourly_traffic[i].up_bytes = json_object_get_int64(up_obj); + if (json_object_object_get_ex(hour_obj, "down_bytes", &down_obj)) + stat->hourly_traffic[i].down_bytes = json_object_get_int64(down_obj); + } + } + + if (json_object_object_get_ex(stat_obj, "hourly_online_time", &online_time_obj) && + json_object_get_type(online_time_obj) == json_type_array) { + int hour_count = json_object_array_length(online_time_obj); + if (hour_count > HOURS_PER_DAY) + hour_count = HOURS_PER_DAY; + for (i = 0; i < hour_count; i++) { + stat->hourly_online_time[i] = json_object_get_int64(json_object_array_get_idx(online_time_obj, i)); + } + } + + if (json_object_object_get_ex(stat_obj, "hourly_active_time", &active_time_obj) && + json_object_get_type(active_time_obj) == json_type_array) { + int hour_count = json_object_array_length(active_time_obj); + if (hour_count > HOURS_PER_DAY) + hour_count = HOURS_PER_DAY; + for (i = 0; i < hour_count; i++) { + stat->hourly_active_time[i] = json_object_get_int64(json_object_array_get_idx(active_time_obj, i)); + } + } +} + +static void load_daily_top_apps_from_json(daily_top_apps_stat_t *stat, struct json_object *stat_obj) { + int i; + struct json_object *value_obj = NULL; + struct json_object *apps_obj = NULL; + + if (!stat) + return; + + memset(stat, 0, sizeof(*stat)); + init_daily_top_apps_default(stat); + if (!stat_obj) + return; + + if (json_object_object_get_ex(stat_obj, "date", &value_obj)) + stat->date = json_object_get_int64(value_obj); + if (json_object_object_get_ex(stat_obj, "is_today", &value_obj)) + stat->is_today = json_object_get_int(value_obj); + if (json_object_object_get_ex(stat_obj, "count", &value_obj)) + stat->count = json_object_get_int(value_obj); + + if (stat->count < 0) + stat->count = 0; + if (stat->count > MAX_TOP_APPS_PER_DAY) + stat->count = MAX_TOP_APPS_PER_DAY; + + if (json_object_object_get_ex(stat_obj, "apps", &apps_obj) && + json_object_get_type(apps_obj) == json_type_array) { + int app_count = json_object_array_length(apps_obj); + if (app_count > MAX_TOP_APPS_PER_DAY) + app_count = MAX_TOP_APPS_PER_DAY; + for (i = 0; i < app_count; i++) { + struct json_object *app_obj = json_object_array_get_idx(apps_obj, i); + struct json_object *appid_obj = NULL; + struct json_object *time_obj = NULL; + if (!app_obj) + continue; + if (json_object_object_get_ex(app_obj, "appid", &appid_obj)) + stat->apps[i].appid = json_object_get_int(appid_obj); + if (json_object_object_get_ex(app_obj, "total_time", &time_obj)) + stat->apps[i].total_time = json_object_get_int64(time_obj); + } + } +} + +static int load_client_backup_from_file(const char *file_path) { + struct json_object *json_obj = NULL; + struct json_object *mac_obj = NULL; + struct json_object *value_obj = NULL; + struct json_object *online_session_obj = NULL; + struct json_object *daily_stats_obj = NULL; + struct json_object *daily_top_apps_obj = NULL; + const char *mac = NULL; + client_node_t *client = NULL; + + if (!file_path) + return -1; + + json_obj = json_object_from_file(file_path); + if (!json_obj) { + LOG_ERROR("Failed to load client backup file: %s\n", file_path); + return -1; + } + + if (!json_object_object_get_ex(json_obj, "mac", &mac_obj)) { + json_object_put(json_obj); + return -1; + } + + mac = json_object_get_string(mac_obj); + if (!mac || strlen(mac) == 0) { + json_object_put(json_obj); + return -1; + } + + client = find_client_node(mac); + if (!client) { + client = add_client_node((char *)mac); + if (!client) { + json_object_put(json_obj); + return -1; + } + } + + reset_client_runtime_fields(client); + + if (json_object_object_get_ex(json_obj, "ip", &value_obj)) + strncpy(client->ip, json_object_get_string(value_obj), sizeof(client->ip) - 1); + client->ip[sizeof(client->ip) - 1] = '\0'; + if (json_object_object_get_ex(json_obj, "ipv6", &value_obj)) + strncpy(client->ipv6, json_object_get_string(value_obj), sizeof(client->ipv6) - 1); + client->ipv6[sizeof(client->ipv6) - 1] = '\0'; + if (json_object_object_get_ex(json_obj, "hostname", &value_obj)) + strncpy(client->hostname, json_object_get_string(value_obj), sizeof(client->hostname) - 1); + client->hostname[sizeof(client->hostname) - 1] = '\0'; + if (json_object_object_get_ex(json_obj, "nickname", &value_obj)) + strncpy(client->nickname, json_object_get_string(value_obj), sizeof(client->nickname) - 1); + client->nickname[sizeof(client->nickname) - 1] = '\0'; + if (json_object_object_get_ex(json_obj, "expire", &value_obj)) + client->expire = json_object_get_int(value_obj); + if (json_object_object_get_ex(json_obj, "offline_time", &value_obj)) + client->offline_time = json_object_get_int64(value_obj); + if (json_object_object_get_ex(json_obj, "online_time", &value_obj)) + client->online_time = json_object_get_int64(value_obj); + if (json_object_object_get_ex(json_obj, "mf_user_loaded", &value_obj)) + client->mf_user_loaded = json_object_get_int(value_obj); + if (json_object_object_get_ex(json_obj, "is_wireless", &value_obj)) + client->is_wireless = json_object_get_int(value_obj); + + if (json_object_object_get_ex(json_obj, "online_session", &online_session_obj)) + load_online_session_stat_from_json(&client->online_session, online_session_obj); + if (json_object_object_get_ex(json_obj, "daily_stats", &daily_stats_obj)) + load_daily_stats_from_json(&client->daily_stats, daily_stats_obj); + if (json_object_object_get_ex(json_obj, "daily_top_apps_stats", &daily_top_apps_obj)) + load_daily_top_apps_from_json(&client->daily_top_apps_stats, daily_top_apps_obj); + + json_object_put(json_obj); + return 0; +} + +void load_client_backup_from_files(void) { + DIR *backup_dir = NULL; + struct dirent *entry = NULL; + char dir_path[512] = {0}; + + get_client_backup_dir(dir_path, sizeof(dir_path)); + if (ensure_dir_exists(dir_path) != 0) { + LOG_ERROR("Failed to create client backup directory: %s\n", dir_path); + return; + } + + backup_dir = opendir(dir_path); + if (!backup_dir) { + LOG_ERROR("Failed to open client backup directory: %s\n", dir_path); + return; + } + + while ((entry = readdir(backup_dir)) != NULL) { + char file_path[512] = {0}; + char mac_dir[512] = {0}; + char base_info_file[512] = {0}; + size_t name_len = 0; + struct stat st; + + if (entry->d_name[0] == '.') + continue; + + snprintf(mac_dir, sizeof(mac_dir), "%s/%s", dir_path, entry->d_name); + if (stat(mac_dir, &st) == 0 && S_ISDIR(st.st_mode)) { + snprintf(base_info_file, sizeof(base_info_file), "%s/base_info/client.json", mac_dir); + if (stat(base_info_file, &st) == 0 && S_ISREG(st.st_mode)) { + load_client_backup_from_file(base_info_file); + } + continue; + } + + name_len = strlen(entry->d_name); + if (name_len < 6 || strcmp(entry->d_name + name_len - 5, ".json") != 0) + continue; + + snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, entry->d_name); + load_client_backup_from_file(file_path); + } + + closedir(backup_dir); +} + + +void load_app_valid_time_config(void) { + struct uci_context *uci_ctx = uci_alloc_context(); + if (uci_ctx) { + int app_valid_time = fwx_uci_get_int_value(uci_ctx, "fwx.record.app_valid_time"); + if (app_valid_time > 0) { + g_app_valid_time = app_valid_time; + } else { + g_app_valid_time = 300; + } + uci_free_context(uci_ctx); + LOG_DEBUG("Loaded app_valid_time config: %d seconds\n", g_app_valid_time); + } +} + + +int get_app_valid_time(void) { + return g_app_valid_time; +} + +static void build_client_visit_db_path(char *db_path, size_t len) { + if (!db_path || len == 0) + return; + + snprintf(db_path, len, "%s/client.db", get_history_data_root_dir()); +} + +static int open_client_visit_db(sqlite3 **db) { + char db_path[512] = {0}; + int rc = SQLITE_OK; + + if (!db) + return -1; + + if (ensure_dir_exists(get_history_data_root_dir()) != 0) { + LOG_ERROR("Failed to create root directory: %s\n", get_history_data_root_dir()); + return -1; + } + + build_client_visit_db_path(db_path, sizeof(db_path)); + rc = sqlite3_open(db_path, db); + if (rc != SQLITE_OK) { + LOG_ERROR("Failed to open sqlite db: %s (rc: %d)\n", db_path, rc); + if (*db) { + sqlite3_close(*db); + *db = NULL; + } + return -1; + } + + rc = sqlite3_exec(*db, + "CREATE TABLE IF NOT EXISTS app_visit_record (" + "mac TEXT NOT NULL," + "record_date INTEGER NOT NULL," + "appid INTEGER NOT NULL," + "start_time INTEGER NOT NULL," + "end_time INTEGER NOT NULL," + "duration INTEGER NOT NULL," + "action INTEGER NOT NULL" + ");" + "CREATE INDEX IF NOT EXISTS idx_app_visit_record_date ON app_visit_record (record_date);" + "CREATE INDEX IF NOT EXISTS idx_app_visit_record_mac_date ON app_visit_record (mac, record_date);", + NULL, NULL, NULL); + if (rc != SQLITE_OK) { + LOG_ERROR("Failed to init sqlite db: %s (rc: %d)\n", db_path, rc); + sqlite3_close(*db); + *db = NULL; + return -1; + } + + return 0; +} + +static int save_visit_record_to_db(const char *mac, visit_info_t *visit) { + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + int duration = 0; + u_int32_t record_date = 0; + + if (!mac || !visit) + return -1; + + if (open_client_visit_db(&db) != 0) + return -1; + + rc = sqlite3_prepare_v2(db, + "INSERT INTO app_visit_record (mac, record_date, appid, start_time, end_time, duration, action) " + "VALUES (?, ?, ?, ?, ?, ?, ?);", + -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + duration = visit->latest_time - visit->first_time; + if (duration == 0) + duration = 1; + record_date = get_today_start_timestamp_from_ts(visit->first_time); + + sqlite3_bind_text(stmt, 1, mac, -1, SQLITE_STATIC); + sqlite3_bind_int64(stmt, 2, record_date); + sqlite3_bind_int(stmt, 3, visit->appid); + sqlite3_bind_int64(stmt, 4, visit->first_time); + sqlite3_bind_int64(stmt, 5, visit->latest_time); + sqlite3_bind_int(stmt, 6, duration); + sqlite3_bind_int(stmt, 7, visit->action); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + goto CLEANUP; + + rc = SQLITE_OK; + +CLEANUP: + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + if (rc != SQLITE_OK) { + LOG_ERROR("Failed to save visit record to sqlite db: mac=%s, appid=%d, start=%u, end=%u, rc=%d\n", + mac, visit->appid, visit->first_time, visit->latest_time, rc); + return -1; + } + + return 0; +} + +void init_client_visit_db(void) { + sqlite3 *db = NULL; + char db_path[512] = {0}; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (open_client_visit_db(&db) != 0) { + LOG_ERROR("Failed to init client visit db on startup: %s\n", db_path); + return; + } + + sqlite3_close(db); + LOG_INFO("Client visit db ready: %s\n", db_path); +} + +int add_mock_visit_records_to_db(int record_count, int mac_count, unsigned long long *old_size_bytes, unsigned long long *new_size_bytes) { + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + int i = 0; + int inserted = 0; + int transaction_started = 0; + int safe_record_count = record_count; + int safe_mac_count = mac_count; + u_int32_t now = 0; + u_int32_t base_time = 0; + char db_path[512] = {0}; + struct stat st = {0}; + + if (old_size_bytes) + *old_size_bytes = 0; + if (new_size_bytes) + *new_size_bytes = 0; + + if (safe_record_count <= 0) + return -1; + if (safe_record_count > 500000) + safe_record_count = 500000; + + if (safe_mac_count <= 0) + safe_mac_count = 16; + if (safe_mac_count > 512) + safe_mac_count = 512; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (stat(db_path, &st) == 0 && old_size_bytes) { + *old_size_bytes = (unsigned long long)st.st_size; + } + + if (open_client_visit_db(&db) != 0) + return -1; + + rc = sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + transaction_started = 1; + + rc = sqlite3_prepare_v2(db, + "INSERT INTO app_visit_record (mac, record_date, appid, start_time, end_time, duration, action) " + "VALUES (?, ?, ?, ?, ?, ?, ?);", + -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + now = (u_int32_t)time(NULL); + base_time = now - (u_int32_t)(7 * SECONDS_PER_DAY); + for (i = 0; i < safe_record_count; i++) { + char mac[32] = {0}; + int mac_idx = i % safe_mac_count; + int appid = 1000 + (i % 4096); + u_int32_t start_time = base_time + (u_int32_t)((i * 37) % (7 * SECONDS_PER_DAY)); + int duration = 10 + (i % 1800); + u_int32_t end_time = start_time + (u_int32_t)duration; + u_int32_t record_date = get_today_start_timestamp_from_ts(start_time); + int action = i % 2; + + snprintf(mac, sizeof(mac), "02:%02X:%02X:%02X:%02X:%02X", + (mac_idx >> 16) & 0xFF, (mac_idx >> 12) & 0xFF, (mac_idx >> 8) & 0xFF, + (mac_idx >> 4) & 0xFF, mac_idx & 0xFF); + + sqlite3_bind_text(stmt, 1, mac, -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(stmt, 2, record_date); + sqlite3_bind_int(stmt, 3, appid); + sqlite3_bind_int64(stmt, 4, start_time); + sqlite3_bind_int64(stmt, 5, end_time); + sqlite3_bind_int(stmt, 6, duration); + sqlite3_bind_int(stmt, 7, action); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + goto CLEANUP; + + inserted++; + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + } + + rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + transaction_started = 0; + rc = SQLITE_OK; + +CLEANUP: + if (rc != SQLITE_OK && transaction_started) { + sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL); + } + + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + if (stat(db_path, &st) == 0 && new_size_bytes) { + *new_size_bytes = (unsigned long long)st.st_size; + } + + if (rc != SQLITE_OK) { + LOG_ERROR("add_mock_visit_records_to_db failed: rc=%d, record_count=%d, mac_count=%d\n", + rc, record_count, mac_count); + return -1; + } + + LOG_WARN("add_mock_visit_records_to_db done: inserted=%d, record_count=%d, mac_count=%d\n", + inserted, record_count, safe_mac_count); + return inserted; +} + +static int find_oldest_date_in_visit_db(char *oldest_date, size_t len) { + char db_path[512] = {0}; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + sqlite3_int64 record_date = 0; + int rc = SQLITE_OK; + + if (!oldest_date || len == 0) + return -1; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (access(db_path, F_OK) != 0) + return -1; + + if (open_client_visit_db(&db) != 0) + return -1; + + rc = sqlite3_prepare_v2(db, "SELECT MIN(record_date) FROM app_visit_record;", -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW || sqlite3_column_type(stmt, 0) == SQLITE_NULL) + goto CLEANUP; + + record_date = sqlite3_column_int64(stmt, 0); + if (record_date > 0) { + get_date_string((u_int32_t)record_date, oldest_date, len); + rc = SQLITE_OK; + } else { + rc = SQLITE_ERROR; + } + +CLEANUP: + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + return rc == SQLITE_OK ? 0 : -1; +} + +static int delete_visit_records_by_date(const char *date_str) { + u_int32_t record_date = 0; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + int deleted_count = 0; + char db_path[512] = {0}; + + if (!date_str || strlen(date_str) == 0) + return 0; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (access(db_path, F_OK) != 0) + return 0; + + record_date = parse_date_string(date_str); + if (record_date == 0) + return 0; + + if (open_client_visit_db(&db) != 0) + return 0; + + rc = sqlite3_prepare_v2(db, "DELETE FROM app_visit_record WHERE record_date = ?;", -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + sqlite3_bind_int64(stmt, 1, record_date); + rc = sqlite3_step(stmt); + if (rc == SQLITE_DONE) + deleted_count = sqlite3_changes(db); + +CLEANUP: + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + return deleted_count; +} + +static int delete_expired_visit_records(u_int32_t expire_timestamp) { + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + int deleted_count = 0; + char db_path[512] = {0}; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (access(db_path, F_OK) != 0) + return 0; + + if (open_client_visit_db(&db) != 0) + return 0; + + rc = sqlite3_prepare_v2(db, "DELETE FROM app_visit_record WHERE record_date < ?;", -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + sqlite3_bind_int64(stmt, 1, expire_timestamp); + rc = sqlite3_step(stmt); + if (rc == SQLITE_DONE) + deleted_count = sqlite3_changes(db); + +CLEANUP: + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + return deleted_count; +} + +static int delete_oldest_visit_records_batch(int batch_count) { + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + int deleted_count = 0; + char db_path[512] = {0}; + + if (batch_count <= 0) + return 0; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (access(db_path, F_OK) != 0) + return 0; + + if (open_client_visit_db(&db) != 0) + return 0; + + rc = sqlite3_prepare_v2( + db, + "DELETE FROM app_visit_record " + "WHERE rowid IN (" + "SELECT rowid FROM app_visit_record " + "ORDER BY record_date ASC, rowid ASC " + "LIMIT ?" + ");", + -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + sqlite3_bind_int(stmt, 1, batch_count); + rc = sqlite3_step(stmt); + if (rc == SQLITE_DONE) + deleted_count = sqlite3_changes(db); + +CLEANUP: + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + return deleted_count; +} + +static int get_visit_record_count(void) { + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + int record_count = -1; + char db_path[512] = {0}; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (access(db_path, F_OK) != 0) + return -1; + + if (open_client_visit_db(&db) != 0) + return -1; + + rc = sqlite3_prepare_v2(db, "SELECT COUNT(1) FROM app_visit_record;", -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + record_count = sqlite3_column_int(stmt, 0); + rc = SQLITE_OK; + } + +CLEANUP: + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + if (rc != SQLITE_OK) + return -1; + + return record_count; +} + +static int vacuum_visit_db_file(void) { + sqlite3 *db = NULL; + int rc = SQLITE_OK; + + if (open_client_visit_db(&db) != 0) + return -1; + + rc = sqlite3_exec(db, "VACUUM;", NULL, NULL, NULL); + sqlite3_close(db); + + if (rc != SQLITE_OK) + return -1; + + return 0; +} + +static int delete_visit_records_in_range(const char *mac, u_int32_t start_timestamp, u_int32_t end_timestamp) { + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc = SQLITE_OK; + int deleted_count = 0; + char db_path[512] = {0}; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (access(db_path, F_OK) != 0) + return 0; + + if (open_client_visit_db(&db) != 0) + return 0; + + if (mac && strlen(mac) > 0) { + rc = sqlite3_prepare_v2(db, + "DELETE FROM app_visit_record WHERE mac = ? AND record_date >= ? AND record_date <= ?;", + -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + sqlite3_bind_text(stmt, 1, mac, -1, SQLITE_STATIC); + sqlite3_bind_int64(stmt, 2, start_timestamp); + sqlite3_bind_int64(stmt, 3, end_timestamp); + } else { + rc = sqlite3_prepare_v2(db, + "DELETE FROM app_visit_record WHERE record_date >= ? AND record_date <= ?;", + -1, &stmt, NULL); + if (rc != SQLITE_OK) + goto CLEANUP; + + sqlite3_bind_int64(stmt, 1, start_timestamp); + sqlite3_bind_int64(stmt, 2, end_timestamp); + } + + rc = sqlite3_step(stmt); + if (rc == SQLITE_DONE) + deleted_count = sqlite3_changes(db); + +CLEANUP: + if (stmt) + sqlite3_finalize(stmt); + if (db) + sqlite3_close(db); + + return deleted_count; +} + +static int find_oldest_date_in_dir(const char *dir_path, char *oldest_date, size_t date_len) { + DIR *base_dir = opendir(dir_path); + char oldest[32] = {0}; + int found = 0; + struct dirent *client_entry; + + if (base_dir) { + while ((client_entry = readdir(base_dir)) != NULL) { + if (client_entry->d_name[0] == '.') + continue; + + char client_dir[512] = {0}; + snprintf(client_dir, sizeof(client_dir), "%s/%s", dir_path, client_entry->d_name); + + struct stat st; + if (stat(client_dir, &st) != 0 || !S_ISDIR(st.st_mode)) + continue; + + char stats_dir[512] = {0}; + snprintf(stats_dir, sizeof(stats_dir), "%s/stats", client_dir); + + DIR *stats_d = opendir(stats_dir); + if (stats_d) { + struct dirent *file_entry; + while ((file_entry = readdir(stats_d)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + if (!found || strcmp(date_str, oldest) < 0) { + strncpy(oldest, date_str, sizeof(oldest) - 1); + found = 1; + } + } + } + closedir(stats_d); + } + + char visits_dir[512] = {0}; + snprintf(visits_dir, sizeof(visits_dir), "%s/visits", client_dir); + + DIR *visits_d = opendir(visits_dir); + if (visits_d) { + struct dirent *file_entry; + while ((file_entry = readdir(visits_d)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) != 0) + continue; + + if (strlen(date_str) > 0) { + if (!found || strcmp(date_str, oldest) < 0) { + strncpy(oldest, date_str, sizeof(oldest) - 1); + found = 1; + } + } + } + closedir(visits_d); + } + } + + closedir(base_dir); + } + + char global_stats_dir[512] = {0}; + snprintf(global_stats_dir, sizeof(global_stats_dir), "%s/global/stats", get_history_data_root_dir()); + + DIR *global_stats_d = opendir(global_stats_dir); + if (global_stats_d) { + struct dirent *file_entry; + while ((file_entry = readdir(global_stats_d)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + if (!found || strcmp(date_str, oldest) < 0) { + strncpy(oldest, date_str, sizeof(oldest) - 1); + found = 1; + } + } + } + closedir(global_stats_d); + } + + { + char visit_oldest[32] = {0}; + if (find_oldest_date_in_visit_db(visit_oldest, sizeof(visit_oldest)) == 0) { + if (!found || strcmp(visit_oldest, oldest) < 0) { + strncpy(oldest, visit_oldest, sizeof(oldest) - 1); + found = 1; + } + } + } + + if (found) { + strncpy(oldest_date, oldest, date_len - 1); + oldest_date[date_len - 1] = '\0'; + return 0; + } + + return -1; +} + +static void delete_date_files(const char *date_str) { + DIR *base_dir = opendir(get_client_data_base_dir()); + if (!base_dir) { + delete_visit_records_by_date(date_str); + return; + } + + struct dirent *client_entry; + while ((client_entry = readdir(base_dir)) != NULL) { + if (client_entry->d_name[0] == '.') + continue; + + char client_dir[512] = {0}; + snprintf(client_dir, sizeof(client_dir), "%s/%s", get_client_data_base_dir(), client_entry->d_name); + + struct stat st; + if (stat(client_dir, &st) != 0 || !S_ISDIR(st.st_mode)) + continue; + + char stats_dir[512] = {0}; + snprintf(stats_dir, sizeof(stats_dir), "%s/stats", client_dir); + + DIR *stats_d = opendir(stats_dir); + if (!stats_d) + continue; + + struct dirent *file_entry; + while ((file_entry = readdir(stats_d)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char file_date[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, file_date, sizeof(file_date)) == 0) { + if (strcmp(file_date, date_str) == 0) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", stats_dir, file_entry->d_name); + unlink(file_path); + } + } + } + closedir(stats_d); + + char visits_dir[512] = {0}; + snprintf(visits_dir, sizeof(visits_dir), "%s/visits", client_dir); + + DIR *visits_d = opendir(visits_dir); + if (!visits_d) + continue; + + while ((file_entry = readdir(visits_d)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char file_date[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, file_date, sizeof(file_date)) != 0) + continue; + + if (strcmp(file_date, date_str) == 0) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", visits_dir, file_entry->d_name); + unlink(file_path); + } + } + closedir(visits_d); + } + + closedir(base_dir); + delete_visit_records_by_date(date_str); +} + +static void cleanup_expired_files_by_days(void) { + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) + return; + + int record_time = fwx_uci_get_int_value(uci_ctx, "fwx.record.record_time"); + uci_free_context(uci_ctx); + LOG_INFO("cleanup_expired_files_by_days: record_time: %d\n", record_time); + if (record_time <= 0) { + return; + } + + time_t now = time(NULL); + u_int32_t expire_timestamp = (u_int32_t)now - (record_time * SECONDS_PER_DAY); + int deleted_count = 0; + deleted_count += delete_expired_visit_records(expire_timestamp); + + DIR *base_dir = opendir(get_client_data_base_dir()); + if (!base_dir) { + return; + } + + struct dirent *client_entry; + + while ((client_entry = readdir(base_dir)) != NULL) { + if (client_entry->d_name[0] == '.') + continue; + + char client_dir[512] = {0}; + snprintf(client_dir, sizeof(client_dir), "%s/%s", get_client_data_base_dir(), client_entry->d_name); + + struct stat st; + if (stat(client_dir, &st) != 0 || !S_ISDIR(st.st_mode)) + continue; + + char stats_dir[512] = {0}; + snprintf(stats_dir, sizeof(stats_dir), "%s/stats", client_dir); + + DIR *stats_d = opendir(stats_dir); + if (stats_d) { + struct dirent *file_entry; + while ((file_entry = readdir(stats_d)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + u_int32_t file_date = parse_date_string(date_str); + if (file_date > 0 && file_date < expire_timestamp) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", stats_dir, file_entry->d_name); + if (unlink(file_path) == 0) { + deleted_count++; + } + } + } + } + closedir(stats_d); + } + + char visits_dir[512] = {0}; + snprintf(visits_dir, sizeof(visits_dir), "%s/visits", client_dir); + + DIR *visits_d = opendir(visits_dir); + if (visits_d) { + struct dirent *file_entry; + while ((file_entry = readdir(visits_d)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char file_date[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, file_date, sizeof(file_date)) != 0) + continue; + + u_int32_t file_date_ts = parse_date_string(file_date); + if (file_date_ts > 0 && file_date_ts < expire_timestamp) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", visits_dir, file_entry->d_name); + if (unlink(file_path) == 0) { + deleted_count++; + } + } + } + closedir(visits_d); + } + } + + closedir(base_dir); + + char global_stats_dir[512] = {0}; + snprintf(global_stats_dir, sizeof(global_stats_dir), "%s/global/stats", get_history_data_root_dir()); + + DIR *global_stats_d = opendir(global_stats_dir); + if (global_stats_d) { + struct dirent *file_entry; + while ((file_entry = readdir(global_stats_d)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + u_int32_t file_date = parse_date_string(date_str); + if (file_date > 0 && file_date < expire_timestamp) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", global_stats_dir, file_entry->d_name); + if (unlink(file_path) == 0) { + deleted_count++; + } + } + } + } + closedir(global_stats_d); + } + + if (deleted_count > 0) { + LOG_INFO("Cleaned up %d expired files (record_time: %d days)\n", deleted_count, record_time); + } +} + +void check_and_cleanup_history_data_by_size(void) { + LOG_INFO("check_and_cleanup_history_data_by_size: start\n"); + + struct uci_context *uci_ctx = uci_alloc_context(); + if (!uci_ctx) + return; + + char history_data_size[64] = {0}; + fwx_uci_get_value(uci_ctx, "fwx.record.history_data_size", history_data_size, sizeof(history_data_size)); + uci_free_context(uci_ctx); + + if (strlen(history_data_size) == 0) { + return; + } + + char *endptr = NULL; + long max_size_mb = strtol(history_data_size, &endptr, 10); + if (*endptr != '\0' || max_size_mb <= 0) { + return; + } + + char db_path[512] = {0}; + struct stat st = {0}; + unsigned long long old_size_bytes = 0; + unsigned long long new_size_bytes = 0; + unsigned long long max_size_bytes = ((unsigned long long)max_size_mb) * 1024ULL * 1024ULL; + + build_client_visit_db_path(db_path, sizeof(db_path)); + if (access(db_path, F_OK) != 0) { + return; + } + + if (stat(db_path, &st) != 0) { + return; + } + + old_size_bytes = (unsigned long long)st.st_size; + if (old_size_bytes <= max_size_bytes) { + return; + } + + LOG_WARN("client.db cleanup begin: size=%llu bytes, limit=%llu bytes, over=%llu bytes\n", + old_size_bytes, max_size_bytes, old_size_bytes - max_size_bytes); + + int current_record_count = get_visit_record_count(); + int cleanup_batch_count = 0; + if (current_record_count > 0) { + cleanup_batch_count = current_record_count / 4; + if (cleanup_batch_count <= 0) + cleanup_batch_count = 1; + } + + if (cleanup_batch_count <= 0) { + LOG_WARN("client.db cleanup skipped: invalid batch, record_count=%d\n", current_record_count); + return; + } + + int deleted_count = delete_oldest_visit_records_batch(cleanup_batch_count); + if (deleted_count <= 0) { + LOG_WARN("client.db exceeds limit but no records deleted: size=%llu bytes, limit=%llu bytes\n", + old_size_bytes, max_size_bytes); + return; + } + + if (stat(db_path, &st) == 0) { + new_size_bytes = (unsigned long long)st.st_size; + } else { + new_size_bytes = old_size_bytes; + } + + if (new_size_bytes > max_size_bytes) { + unsigned long long before_vacuum_size = new_size_bytes; + if (vacuum_visit_db_file() == 0) { + if (stat(db_path, &st) == 0) { + new_size_bytes = (unsigned long long)st.st_size; + } + LOG_WARN("client.db vacuum done: size=%llu->%llu bytes\n", + before_vacuum_size, new_size_bytes); + } else { + LOG_WARN("client.db vacuum failed\n"); + } + } + + LOG_WARN("client.db cleanup end: deleted=%d, batch=%d, size=%llu->%llu bytes, reduced=%lld bytes, limit=%llu bytes\n", + deleted_count, cleanup_batch_count, old_size_bytes, new_size_bytes, + (long long)old_size_bytes - (long long)new_size_bytes, max_size_bytes); + + current_record_count = get_visit_record_count(); + LOG_WARN("client.db current record count: %d\n", current_record_count); +} + +int get_timestamp(void) +{ + struct timeval cur_time; + gettimeofday(&cur_time, NULL); + return cur_time.tv_sec; +} + + + + +void add_visit_info_node(struct list_head *visit_list, visit_info_t *node) +{ + if (!visit_list || !node) + return; + + + list_add(&node->visit, visit_list); +} + +static void normalize_mac_lower(const char *src, char *dst, size_t dst_len) +{ + size_t i = 0; + + if (!dst || dst_len == 0) { + return; + } + dst[0] = '\0'; + if (!src) { + return; + } + + for (i = 0; i + 1 < dst_len && src[i] != '\0'; i++) { + char c = src[i]; + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + dst[i] = c; + } + dst[i] = '\0'; +} + +void init_client_list(void) +{ + INIT_LIST_HEAD(&client_list); + printf("init client list ok...\n"); +} + +client_node_t *add_client_node(char *mac) +{ + int j; + client_node_t *node = NULL; + client_node_t *exist = NULL; + char mac_norm[MAX_MAC_LEN] = {0}; + u_int32_t now = get_timestamp(); + + if (!mac || mac[0] == '\0') { + return NULL; + } + normalize_mac_lower(mac, mac_norm, sizeof(mac_norm)); + exist = find_client_node(mac_norm); + if (exist) { + return exist; + } + + node = (client_node_t *)calloc(1, sizeof(client_node_t)); + if (!node) + return NULL; + strncpy(node->mac, mac_norm, sizeof(node->mac) - 1); + node->mac[sizeof(node->mac) - 1] = '\0'; + node->online = 0; + node->online_time = now; + node->offline_time = now; + + node->ipv6[0] = '\0'; + node->up_rate = 0; + node->down_rate = 0; + node->rssi = 0; + node->rx_rate = 0; + node->tx_rate = 0; + node->band[0] = '\0'; + node->wifi_ifname[0] = '\0'; + node->is_wireless = 0; + node->wireless_online = 0; + node->active = 0; + node->last_online_state = 0; + node->session_online_recorded = 0; + reset_online_session_stat(node, now); + + INIT_LIST_HEAD(&node->online_visit); + INIT_LIST_HEAD(&node->visit); + + INIT_LIST_HEAD(&node->stat_list); + + INIT_LIST_HEAD(&node->online_offline_records); + + INIT_LIST_HEAD(&node->client); + + u_int32_t today = get_today_start_timestamp(); + node->daily_stats.date = today; + node->daily_stats.is_today = 1; + for (j = 0; j < HOURS_PER_DAY; j++) { + for (int k = 0; k < TOP_APP_PER_HOUR; k++) { + node->daily_stats.hourly_top_apps[j][k] = -1; + } + } + + + node->daily_top_apps_stats.date = today; + node->daily_top_apps_stats.is_today = 1; + node->daily_top_apps_stats.count = 0; + for (j = 0; j < MAX_TOP_APPS_PER_DAY; j++) { + node->daily_top_apps_stats.apps[j].appid = -1; + node->daily_top_apps_stats.apps[j].total_time = 0; + } + + list_add(&node->client, &client_list); + g_cur_user_num++; + printf("add mac:%s to client list....success\n", node->mac); + return node; +} + +client_node_t *find_client_node(const char *mac) +{ + client_node_t *p = NULL; + char mac_norm[MAX_MAC_LEN] = {0}; + + if (!mac || mac[0] == '\0') { + return NULL; + } + normalize_mac_lower(mac, mac_norm, sizeof(mac_norm)); + + list_for_each_entry(p, &client_list, client) { + if (0 == strcasecmp(p->mac, mac_norm)) + { + if (strncmp(p->mac, mac_norm, sizeof(p->mac)) != 0) { + strncpy(p->mac, mac_norm, sizeof(p->mac) - 1); + p->mac[sizeof(p->mac) - 1] = '\0'; + } + return p; + } + } + return NULL; +} + +void client_foreach(void *arg, iter_func iter) +{ + client_node_t *node = NULL; + int count = 0; + + LOG_DEBUG("client_foreach: Starting iteration over client_list...\n"); + list_for_each_entry(node, &client_list, client) { + count++; + LOG_DEBUG("client_foreach: Processing client[%d] - mac=%s, online=%d\n", + count, node->mac, node->online); + iter(arg, node); + } + LOG_DEBUG("client_foreach: Finished iteration, processed %d clients\n", count); +} + +char *format_time(int timetamp) +{ + char time_buf[64] = {0}; + time_t seconds = timetamp; + struct tm *auth_tm = localtime(&seconds); + strftime(time_buf, sizeof(time_buf), "%Y %m %d %H:%M:%S", auth_tm); + return strdup(time_buf); +} + +void update_client_hostname(void) +{ + char line_buf[256] = {0}; + char hostname_buf[128] = {0}; + char mac_buf[32] = {0}; + char ip_buf[32] = {0}; + + FILE *fp = fopen("/tmp/dhcp.leases", "r"); + if (!fp) + { + printf("open dhcp lease file....failed\n"); + return; + } + while (fgets(line_buf, sizeof(line_buf), fp)) + { + if (strlen(line_buf) <= 16) + continue; + sscanf(line_buf, "%*s %s %s %s", mac_buf, ip_buf, hostname_buf); + client_node_t *node = find_client_node(mac_buf); + if (!node) + { + node = add_client_node(mac_buf); + strncpy(node->ip, ip_buf, sizeof(node->ip)); + node->online = 0; + node->offline_time = get_timestamp(); + } + + if (strlen(hostname_buf) > 0 && hostname_buf[0] != '*') + { + strncpy(node->hostname, hostname_buf, sizeof(node->hostname)); + } + } + fclose(fp); +} + +void clean_client_nickname_iter(void *arg, client_node_t *client) +{ + client->nickname[0] = '\0'; +} + +void clean_client_nickname(void) +{ + client_foreach(NULL, clean_client_nickname_iter); +} + +void update_client_nickname(void) +{ + int i; + char nickname_buf[128] = {0}; + char mac_str[128] = {0}; + struct uci_context *uci_ctx = uci_alloc_context(); + clean_client_nickname(); + int num = fwx_uci_get_list_num(uci_ctx, "user_info", "user_info"); + + for (i = 0; i < num; i++) { + fwx_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].mac", i, mac_str, sizeof(mac_str)); + client_node_t *node = find_client_node(mac_str); + if (!node) + continue; + + fwx_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].nickname", i, nickname_buf, sizeof(nickname_buf)); + strncpy(node->nickname, nickname_buf, sizeof(node->nickname)); + } + uci_free_context(uci_ctx); +} + +static char *get_command_output(const char *cmd) +{ + FILE *fp = NULL; + char *buf = NULL; + size_t buf_size = 4096; + size_t total_read = 0; + size_t n_read = 0; + + if (!cmd || cmd[0] == '\0') { + return NULL; + } + + fp = popen(cmd, "r"); + if (!fp) { + return NULL; + } + + buf = (char *)calloc(1, buf_size); + if (!buf) { + pclose(fp); + return NULL; + } + + while (!feof(fp)) { + size_t remain = buf_size - total_read - 1; + if (remain < 512) { + char *new_buf = NULL; + buf_size *= 2; + new_buf = (char *)realloc(buf, buf_size); + if (!new_buf) { + free(buf); + pclose(fp); + return NULL; + } + buf = new_buf; + remain = buf_size - total_read - 1; + } + + n_read = fread(buf + total_read, 1, remain, fp); + total_read += n_read; + + if (ferror(fp)) { + free(buf); + pclose(fp); + return NULL; + } + } + + pclose(fp); + + if (total_read == 0) { + free(buf); + return NULL; + } + + buf[total_read] = '\0'; + return buf; +} + +static struct json_object *get_command_json(const char *cmd) +{ + char *output = NULL; + struct json_object *obj = NULL; + + output = get_command_output(cmd); + if (!output) { + return NULL; + } + + obj = json_tokener_parse(output); + free(output); + return obj; +} + +static void clear_client_wireless_status(void) +{ + client_node_t *node = NULL; + + list_for_each_entry(node, &client_list, client) { + node->rssi = 0; + node->rx_rate = 0; + node->tx_rate = 0; + node->band[0] = '\0'; + node->wifi_ifname[0] = '\0'; + node->wireless_online = 0; + } +} + +static client_node_t *find_client_node_nocase(const char *mac) +{ + client_node_t *node = NULL; + + if (!mac || mac[0] == '\0') { + return NULL; + } + + list_for_each_entry(node, &client_list, client) { + if (strcasecmp(node->mac, mac) == 0) { + return node; + } + } + + return NULL; +} + +static unsigned int get_wireless_rate_value(struct json_object *rate_obj) +{ + struct json_object *rate_value_obj = NULL; + long long rate = 0; + + if (!rate_obj || json_object_get_type(rate_obj) != json_type_object) { + return 0; + } + + if (!json_object_object_get_ex(rate_obj, "rate", &rate_value_obj) || !rate_value_obj) { + return 0; + } + + rate = json_object_get_int64(rate_value_obj); + if (rate <= 0) { + return 0; + } + + if ((unsigned long long)rate > UINT_MAX) { + return UINT_MAX; + } + + return (unsigned int)rate; +} + +static void append_wireless_ifname(char ifnames[][MAX_WIRELESS_IFNAME_LEN], char bands[][MAX_WIRELESS_BAND_LEN], int *if_num, int max_if_num, const char *ifname, const char *band) +{ + int i; + + if (!ifnames || !bands || !if_num || !ifname || ifname[0] == '\0') { + return; + } + + for (i = 0; i < *if_num; i++) { + if (strcmp(ifnames[i], ifname) == 0) { + if (bands[i][0] == '\0' && band && band[0] != '\0') { + snprintf(bands[i], MAX_WIRELESS_BAND_LEN, "%s", band); + } + return; + } + } + + if (*if_num >= max_if_num) { + return; + } + + snprintf(ifnames[*if_num], MAX_WIRELESS_IFNAME_LEN, "%s", ifname); + if (band && band[0] != '\0') { + snprintf(bands[*if_num], MAX_WIRELESS_BAND_LEN, "%s", band); + } else { + bands[*if_num][0] = '\0'; + } + (*if_num)++; +} + +static int collect_wireless_ifnames(char ifnames[][MAX_WIRELESS_IFNAME_LEN], char bands[][MAX_WIRELESS_BAND_LEN], int max_if_num) +{ + int if_num = 0; + struct json_object *status_obj = NULL; + + status_obj = get_command_json("ubus call network.wireless status 2>/dev/null"); + if (!status_obj) { + return 0; + } + + json_object_object_foreach(status_obj, radio_name, radio_obj) { + char radio_band[MAX_WIRELESS_BAND_LEN] = {0}; + struct json_object *config_obj = NULL; + struct json_object *band_obj = NULL; + struct json_object *interfaces_obj = NULL; + int i; + + (void)radio_name; + + if (!radio_obj || json_object_get_type(radio_obj) != json_type_object) { + continue; + } + + if (json_object_object_get_ex(radio_obj, "config", &config_obj) && + config_obj && json_object_get_type(config_obj) == json_type_object && + json_object_object_get_ex(config_obj, "band", &band_obj) && band_obj) { + snprintf(radio_band, sizeof(radio_band), "%s", json_object_get_string(band_obj)); + } else if (json_object_object_get_ex(radio_obj, "band", &band_obj) && band_obj) { + snprintf(radio_band, sizeof(radio_band), "%s", json_object_get_string(band_obj)); + } + + if (!json_object_object_get_ex(radio_obj, "interfaces", &interfaces_obj) || + !interfaces_obj || json_object_get_type(interfaces_obj) != json_type_array) { + continue; + } + + for (i = 0; i < json_object_array_length(interfaces_obj); i++) { + struct json_object *iface_obj = json_object_array_get_idx(interfaces_obj, i); + struct json_object *ifname_obj = NULL; + struct json_object *vlans_obj = NULL; + int j; + + if (!iface_obj || json_object_get_type(iface_obj) != json_type_object) { + continue; + } + + if (json_object_object_get_ex(iface_obj, "ifname", &ifname_obj) && ifname_obj) { + append_wireless_ifname(ifnames, bands, &if_num, max_if_num, json_object_get_string(ifname_obj), radio_band); + } + + if (!json_object_object_get_ex(iface_obj, "vlans", &vlans_obj) || + !vlans_obj || json_object_get_type(vlans_obj) != json_type_array) { + continue; + } + + for (j = 0; j < json_object_array_length(vlans_obj); j++) { + struct json_object *vlan_obj = json_object_array_get_idx(vlans_obj, j); + struct json_object *vlan_ifname_obj = NULL; + + if (!vlan_obj || json_object_get_type(vlan_obj) != json_type_object) { + continue; + } + + if (json_object_object_get_ex(vlan_obj, "ifname", &vlan_ifname_obj) && vlan_ifname_obj) { + append_wireless_ifname(ifnames, bands, &if_num, max_if_num, json_object_get_string(vlan_ifname_obj), radio_band); + } + } + } + } + + json_object_put(status_obj); + return if_num; +} + +static void update_client_wireless_status_by_ifname(const char *ifname, const char *band) +{ + char cmd[256] = {0}; + struct json_object *assoc_obj = NULL; + struct json_object *results_obj = NULL; + int i; + + if (!ifname || ifname[0] == '\0') { + return; + } + + snprintf(cmd, sizeof(cmd), "ubus call iwinfo assoclist '{\"device\":\"%s\"}' 2>/dev/null", ifname); + assoc_obj = get_command_json(cmd); + if (!assoc_obj) { + return; + } + + if (!json_object_object_get_ex(assoc_obj, "results", &results_obj) || !results_obj) { + if (json_object_get_type(assoc_obj) == json_type_array) { + results_obj = assoc_obj; + } + } + + if (!results_obj || json_object_get_type(results_obj) != json_type_array) { + json_object_put(assoc_obj); + return; + } + + for (i = 0; i < json_object_array_length(results_obj); i++) { + struct json_object *station_obj = json_object_array_get_idx(results_obj, i); + struct json_object *mac_obj = NULL; + struct json_object *signal_obj = NULL; + struct json_object *rx_obj = NULL; + struct json_object *tx_obj = NULL; + client_node_t *node = NULL; + const char *mac = NULL; + + if (!station_obj || json_object_get_type(station_obj) != json_type_object) { + continue; + } + + if (!json_object_object_get_ex(station_obj, "mac", &mac_obj) || !mac_obj) { + continue; + } + + mac = json_object_get_string(mac_obj); + if (!mac || mac[0] == '\0') { + continue; + } + + node = find_client_node_nocase(mac); + if (!node) { + char mac_buf[MAX_MAC_LEN] = {0}; + snprintf(mac_buf, sizeof(mac_buf), "%s", mac); + node = add_client_node(mac_buf); + if (!node) { + continue; + } + } + + if (json_object_object_get_ex(station_obj, "signal", &signal_obj) && signal_obj) { + node->rssi = json_object_get_int(signal_obj); + } else if (json_object_object_get_ex(station_obj, "signal_avg", &signal_obj) && signal_obj) { + node->rssi = json_object_get_int(signal_obj); + } else { + node->rssi = 0; + } + + if (json_object_object_get_ex(station_obj, "rx", &rx_obj) && rx_obj) { + node->rx_rate = get_wireless_rate_value(rx_obj); + } + + if (json_object_object_get_ex(station_obj, "tx", &tx_obj) && tx_obj) { + node->tx_rate = get_wireless_rate_value(tx_obj); + } + + if (band && band[0] != '\0') { + snprintf(node->band, sizeof(node->band), "%s", band); + } + snprintf(node->wifi_ifname, sizeof(node->wifi_ifname), "%s", ifname); + node->is_wireless = 1; + node->wireless_online = 1; + } + + json_object_put(assoc_obj); +} + +void refresh_client_wireless_status(void) +{ + char ifnames[MAX_WIRELESS_IFACE_NUM][MAX_WIRELESS_IFNAME_LEN] = {{0}}; + char bands[MAX_WIRELESS_IFACE_NUM][MAX_WIRELESS_BAND_LEN] = {{0}}; + int if_num = 0; + int i; + + clear_client_wireless_status(); + + if (!g_fwx_capability.wireless_support) { + return; + } + + if_num = collect_wireless_ifnames(ifnames, bands, MAX_WIRELESS_IFACE_NUM); + LOG_DEBUG("refresh_client_wireless_status: if_num=%d\n", if_num); + for (i = 0; i < if_num; i++) { + update_client_wireless_status_by_ifname(ifnames[i], bands[i]); + } +} + + + +void clean_client_online_status(void) +{ + client_node_t *node = NULL; + + list_for_each_entry(node, &client_list, client) { + node->last_online_state = node->online; + node->active = 0; + if (node->online) + { + node->offline_time = get_timestamp(); + node->online = 0; + } + } +} + + +void update_client_from_kernel(void) +{ + char line_buf[256] = {0}; + char mac_buf[32] = {0}; + char ip_buf[32] = {0}; + char ipv6_buf[128] = {0}; + unsigned int status = 1; + unsigned int up_rate = 0; + unsigned int down_rate = 0; + + FILE *fp = fopen("/proc/net/af_client", "r"); + if (!fp) + { + printf("open client file....failed\n"); + return; + } + fgets(line_buf, sizeof(line_buf), fp); // title + while (fgets(line_buf, sizeof(line_buf), fp)) + { + int id; + int parsed = 0; + int has_status = 0; + + status = 1; + up_rate = 0; + down_rate = 0; + + parsed = sscanf(line_buf, "%d %31s %31s %127s %*u %u %u %u", + &id, mac_buf, ip_buf, ipv6_buf, &status, &up_rate, &down_rate); + if (parsed == 7) { + has_status = 1; + } else { + parsed = sscanf(line_buf, "%d %31s %31s %127s %*u %u %u", + &id, mac_buf, ip_buf, ipv6_buf, &up_rate, &down_rate); + if (parsed == 6) { + has_status = 0; + status = 1; + } else { + parsed = sscanf(line_buf, "%d %31s %31s %127s %u %u", + &id, mac_buf, ip_buf, ipv6_buf, &up_rate, &down_rate); + if (parsed == 6) { + has_status = 0; + status = 1; + } + } + } + LOG_DEBUG("update_client_from_kernel: parsed = %d, line_buf = %s\n", parsed, line_buf); + if (parsed < 3) + { + printf("invalid line format:%s\n", line_buf); + continue; + } + if (strlen(mac_buf) < 17) + { + printf("invalid mac:%s\n", mac_buf); + continue; + } + client_node_t *node = find_client_node(mac_buf); + if (!node) + { + node = add_client_node(mac_buf); + if (!node) + continue; + strncpy(node->ip, ip_buf, sizeof(node->ip)); + } + + strncpy(node->ip, ip_buf, sizeof(node->ip)); + + if (parsed >= 4 && strlen(ipv6_buf) > 0) + { + strncpy(node->ipv6, ipv6_buf, sizeof(node->ipv6)); + LOG_DEBUG("update_client_from_kernel: ipv6 = %s\n", ipv6_buf); + } + else + { + node->ipv6[0] = '\0'; + } + + if (parsed >= 5) + { + node->up_rate = up_rate; + LOG_DEBUG("update_client_from_kernel: up_rate = %d\n", up_rate); + } + else + { + node->up_rate = 0; + LOG_DEBUG("update_client_from_kernel: up_rate = 0\n"); + } + if (parsed >= 6) + { + node->down_rate = down_rate; + LOG_DEBUG("update_client_from_kernel: down_rate = %d\n", down_rate); + } + else + { + node->down_rate = 0; + } + node->online = 1; + if (has_status) { + node->active = (status >= 2) ? 1 : 0; + } + } + fclose(fp); +} + +void update_client_online_status(void) +{ + int now = get_timestamp(); + int work_mode = 0; + int is_bypass_mode = 0; + client_node_t *node = NULL; + struct uci_context *uci_ctx = uci_alloc_context(); + + if (uci_ctx) { + work_mode = fwx_uci_get_int_value(uci_ctx, "fwx.network.work_mode"); + if (work_mode != 0 && work_mode != 1) { + work_mode = 0; + } + uci_free_context(uci_ctx); + } + is_bypass_mode = (work_mode == 1); + + update_client_from_kernel(); + refresh_client_wireless_status(); + list_for_each_entry(node, &client_list, client) { + if (!node->is_wireless) { + continue; + } + if (node->wireless_online) { + node->online = 1; + } else { + node->online = 0; + node->active = 0; + } + } + list_for_each_entry(node, &client_list, client) { + int was_online = node->last_online_state; + int is_online = node->online; + if (!was_online && is_online) { + node->online_time = now; + reset_online_session_stat(node, now); + node->session_online_recorded = 0; + } else if (was_online && !is_online) { + node->offline_time = now; + if (node->session_online_recorded) { + add_user_record(node, 1, now); + } + node->session_online_recorded = 0; + save_client_backup_to_file(node); + } else if (is_online && !node->session_online_recorded && + (is_bypass_mode || node->active)) { + add_user_record(node, 0, now); + node->session_online_recorded = 1; + } + node->last_online_state = is_online; + } +} + +#define CLIENT_OFFLINE_TIME (SECONDS_PER_DAY * 3) + +int check_client_expire(void) +{ + int count = 0; + int cur_time = get_timestamp(); + int offline_time = 0; + int expire_count = 0; + int visit_count = 0; + client_node_t *node = NULL; + visit_info_t *p_info = NULL; + + list_for_each_entry(node, &client_list, client) { + if (node->online) + continue; + visit_count = 0; + offline_time = cur_time - node->offline_time; + if (offline_time > CLIENT_OFFLINE_TIME) + { + node->expire = 1; + list_for_each_entry(p_info, &node->visit, visit) { + p_info->expire = 1; + visit_count++; + } + expire_count++; + LOG_WARN("client:%s expired, offline time = %ds, count=%d, visit_count=%d\n", + node->mac, offline_time, expire_count, visit_count); + } + } + return expire_count; +} + +void flush_expire_client_node(void) +{ + int count = 0; + client_node_t *node = NULL, *tmp = NULL; + visit_info_t *p_info = NULL, *tmp_info = NULL; + visit_stat_t *stat_node = NULL, *tmp_stat_node = NULL; + + list_for_each_entry_safe(node, tmp, &client_list, client) { + if (node->expire) + { + list_for_each_entry_safe(p_info, tmp_info, &node->online_visit, visit) { + list_del(&p_info->visit); + free(p_info); + } + + list_for_each_entry_safe(p_info, tmp_info, &node->visit, visit) { + list_del(&p_info->visit); + free(p_info); + } + + list_for_each_entry_safe(stat_node, tmp_stat_node, &node->stat_list, list) { + list_del(&stat_node->list); + free(stat_node); + } + list_del(&node->client); + free(node); + count++; + g_cur_user_num--; + } + } +} + +#define ONLINE_VISIT_TIMEOUT_SEC 300 + +void move_expired_online_visit_to_offline(void) +{ + int cur_time = get_timestamp(); + client_node_t *node = NULL; + visit_info_t *p_info = NULL, *tmp_info = NULL; + + list_for_each_entry(node, &client_list, client) { + list_for_each_entry_safe(p_info, tmp_info, &node->online_visit, visit) { + int diff = cur_time - (int)p_info->latest_time; + LOG_INFO("move_expired_online_visit_to_offline: mac = %s, diff = %d\n", node->mac, diff); + if (diff > ONLINE_VISIT_TIMEOUT_SEC) { + list_del(&p_info->visit); + LOG_INFO("move_expired_online_visit_to_offline: mac = %s, appid = %d, action = %d, first_time = %d, latest_time = %d\n", node->mac, p_info->appid, p_info->action, p_info->first_time, p_info->latest_time); + + + int total_time = p_info->latest_time - p_info->first_time; + if (total_time < g_app_valid_time) { + LOG_DEBUG("Discard visit record (too short): mac=%s, appid=%d, duration=%ds < %ds\n", + node->mac, p_info->appid, total_time, g_app_valid_time); + free(p_info); + } else { + p_info->expire = 0; + add_visit_info_node(&node->visit, p_info); + save_visit_record_to_db(node->mac, p_info); + } + } + } + } +} + +static int is_invalid_visit_url(const char *url) +{ + if (!url || url[0] == '\0') { + return 1; + } + if (strcasecmp(url, "none") == 0 || + strcasecmp(url, "undefined") == 0 || + strcasecmp(url, "null") == 0) { + return 1; + } + return 0; +} + +void update_client_visiting_info(void){ + char line_buf[256] = {0}; + char mac_buf[32] = {0}; + char url_buf[MAX_REPORT_URL_LEN] = {0}; + char app_buf[32] = {0}; + + FILE *fp = fopen("/proc/net/af_visit", "r"); + if (!fp) + { + printf("open af_visit file....failed\n"); + return; + } + fgets(line_buf, sizeof(line_buf), fp); // title + while (fgets(line_buf, sizeof(line_buf), fp)) + { + memset(mac_buf, 0, sizeof(mac_buf)); + memset(app_buf, 0, sizeof(app_buf)); + memset(url_buf, 0, sizeof(url_buf)); + if (sscanf(line_buf, "%31s %31s %63s", mac_buf, app_buf, url_buf) != 3) { + continue; + } + client_node_t *node = find_client_node(mac_buf); + if (!node) + continue; + if (is_invalid_visit_url(url_buf)) { + node->visiting_url[0] = '\0'; + } + else { + strncpy(node->visiting_url, url_buf, sizeof(node->visiting_url) - 1); + node->visiting_url[sizeof(node->visiting_url) - 1] = '\0'; + } + node->visiting_app = atoi(app_buf); + } + fclose(fp); +} + +void update_client_list(void) +{ + clean_client_online_status(); + update_client_hostname(); + update_client_nickname(); + update_client_online_status(); + update_client_visiting_info(); +} + + +void dump_client_list(void) +{ + int count = 0; + char hostname_buf[MAX_HOSTNAME_SIZE] = {0}; + char ip_buf[MAX_IP_LEN] = {0}; + + FILE *fp = fopen(OAF_DEV_LIST_FILE, "w"); + if (!fp) + { + return; + } + + fprintf(fp, "%-4s %-20s %-20s %-32s %-8s %-12s %-12s\n", + "Id", "Mac Addr", "Ip Addr", "Hostname", "Online", "OnlineTime", "OfflineTime"); + + + client_node_t *node = NULL; + list_for_each_entry(node, &client_list, client) { + if (node->online != 0) + { + if (strlen(node->hostname) == 0) + strcpy(hostname_buf, "*"); + else + strcpy(hostname_buf, node->hostname); + if (strlen(node->ip) == 0) + strcpy(ip_buf, "*"); + else + strcpy(ip_buf, node->ip); + fprintf(fp, "%-4d %-20s %-20s %-32s %-8d %-12u %-12u\n", + count + 1, node->mac, ip_buf, hostname_buf, node->online, + node->online_time, node->offline_time); + count++; + if (count >= MAX_SUPPORT_DEV_NUM) + goto EXIT; + } + } + + + list_for_each_entry(node, &client_list, client) { + if (node->online == 0) + { + if (strlen(node->hostname) == 0) + strcpy(hostname_buf, "*"); + else + strcpy(hostname_buf, node->hostname); + + if (strlen(node->ip) == 0) + strcpy(ip_buf, "*"); + else + strcpy(ip_buf, node->ip); + + fprintf(fp, "%-4d %-20s %-20s %-32s %-8d %-12u %-12u\n", + count + 1, node->mac, ip_buf, hostname_buf, node->online, + node->online_time, node->offline_time); + count++; + if (count >= MAX_SUPPORT_DEV_NUM) + goto EXIT; + } + } +EXIT: + fclose(fp); +} + + +#define MAX_RECORD_TIME (3 * 24 * 60 * 60) // 7day + +#define RECORD_REMAIN_TIME (24 * 60 * 60) // 1day +#define INVALID_RECORD_TIME (5 * 60) // 5min + +void check_client_visit_info_expire(void) +{ + int count = 0; + int cur_time = get_timestamp(); + client_node_t *node = NULL; + visit_info_t *p_info = NULL, *tmp_info = NULL; + + list_for_each_entry(node, &client_list, client) { + + list_for_each_entry_safe(p_info, tmp_info, &node->visit, visit) { + int total_time = p_info->latest_time - p_info->first_time; + int interval_time = cur_time - p_info->first_time; + if (interval_time > MAX_RECORD_TIME || interval_time < 0) + { + p_info->expire = 1; + } + else if (interval_time > RECORD_REMAIN_TIME) + { + if (total_time < INVALID_RECORD_TIME) + p_info->expire = 1; + } + } + } +} + +void flush_expire_visit_info(void) +{ + int count = 0; + client_node_t *node = NULL; + visit_info_t *p_info = NULL, *tmp_info = NULL; + + list_for_each_entry(node, &client_list, client) { + + list_for_each_entry_safe(p_info, tmp_info, &node->visit, visit) { + if (p_info->expire) + { + list_del(&p_info->visit); + free(p_info); + count++; + } + } + } +} + +void dump_client_visit_list(void) +{ + int count = 0; + FILE *fp = fopen(OAF_VISIT_LIST_FILE, "w"); + if (!fp) + { + return; + } + + fprintf(fp, "%-4s %-20s %-20s %-8s %-32s %-32s %-32s %-8s\n", "Id", "Mac Addr", + "Ip Addr", "Appid", "First Time", "Latest Time", "Total Time(s)", "Expire"); + + client_node_t *node = NULL; + visit_info_t *p_info = NULL; + list_for_each_entry(node, &client_list, client) { + + list_for_each_entry(p_info, &node->visit, visit) { + char *first_time_str = format_time(p_info->first_time); + char *latest_time_str = format_time(p_info->latest_time); + int total_time = p_info->latest_time - p_info->first_time; + fprintf(fp, "%-4d %-20s %-20s %-8d %-32s %-32s %-32d %-4d\n", + count, node->mac, node->ip, p_info->appid, first_time_str, + latest_time_str, total_time, p_info->expire); + if (first_time_str) + free(first_time_str); + if (latest_time_str) + free(latest_time_str); + count++; + if (count > 50) + goto EXIT; + } + } +EXIT: + fclose(fp); +} + + +typedef struct app_hour_stat { + int appid; + unsigned long long total_time; +} app_hour_stat_t; + + +static int compare_app_stat(const void *a, const void *b) { + app_hour_stat_t *pa = (app_hour_stat_t *)a; + app_hour_stat_t *pb = (app_hour_stat_t *)b; + if (pa->total_time > pb->total_time) + return -1; + if (pa->total_time < pb->total_time) + return 1; + return 0; +} + + +int get_hour_from_timestamp(u_int32_t timestamp) { + time_t t = (time_t)timestamp; + struct tm *tm_info = localtime(&t); + if (!tm_info) + return -1; + return tm_info->tm_hour; +} + +static u_int32_t get_next_hour_timestamp(u_int32_t timestamp) { + time_t t = (time_t)timestamp; + struct tm *tm_info = localtime(&t); + if (!tm_info) { + return timestamp + 3600; + } + + struct tm next_hour = *tm_info; + next_hour.tm_min = 0; + next_hour.tm_sec = 0; + next_hour.tm_hour += 1; + + time_t next_ts = mktime(&next_hour); + if (next_ts <= (time_t)timestamp) { + return timestamp + 3600; + } + return (u_int32_t)next_ts; +} + +unsigned long long get_visit_duration_in_hour(visit_info_t *visit, int target_hour) { + u_int32_t start; + u_int32_t end; + u_int32_t cursor; + unsigned long long total = 0; + + if (!visit || target_hour < 0 || target_hour >= HOURS_PER_DAY) { + return 0; + } + + start = visit->first_time; + end = visit->latest_time; + if (end <= start) { + int hour = get_hour_from_timestamp(start); + return (hour == target_hour) ? 1 : 0; + } + + cursor = start; + while (cursor < end) { + int hour = get_hour_from_timestamp(cursor); + u_int32_t next_hour = get_next_hour_timestamp(cursor); + u_int32_t segment_end = end < next_hour ? end : next_hour; + unsigned long long segment_duration = (unsigned long long)(segment_end - cursor); + + if (segment_duration == 0) { + segment_duration = 1; + } + + if (hour == target_hour) { + total += segment_duration; + } + cursor = segment_end; + } + + return total; +} + +static void add_app_duration_to_hour_stats( + app_hour_stat_t hour_stats[HOURS_PER_DAY][MAX_APP_STAT_NUM], + int hour_count[HOURS_PER_DAY], + int hour, + int appid, + unsigned long long duration) { + int i; + int found = 0; + + if (hour < 0 || hour >= HOURS_PER_DAY || appid <= 0 || duration == 0) { + return; + } + + for (i = 0; i < hour_count[hour]; i++) { + if (hour_stats[hour][i].appid == appid) { + hour_stats[hour][i].total_time += duration; + if (hour_stats[hour][i].total_time > 3600ULL) { + hour_stats[hour][i].total_time = 3600ULL; + } + found = 1; + break; + } + } + + if (!found && hour_count[hour] < MAX_APP_STAT_NUM) { + hour_stats[hour][hour_count[hour]].appid = appid; + hour_stats[hour][hour_count[hour]].total_time = duration; + if (hour_stats[hour][hour_count[hour]].total_time > 3600ULL) { + hour_stats[hour][hour_count[hour]].total_time = 3600ULL; + } + hour_count[hour]++; + } else if (!found && hour_count[hour] >= MAX_APP_STAT_NUM) { + int min_idx = 0; + unsigned long long min_time = hour_stats[hour][0].total_time; + + for (i = 1; i < MAX_APP_STAT_NUM; i++) { + if (hour_stats[hour][i].total_time < min_time) { + min_time = hour_stats[hour][i].total_time; + min_idx = i; + } + } + + if (duration > min_time) { + hour_stats[hour][min_idx].appid = appid; + hour_stats[hour][min_idx].total_time = duration; + if (hour_stats[hour][min_idx].total_time > 3600ULL) { + hour_stats[hour][min_idx].total_time = 3600ULL; + } + } + } +} + +static void accumulate_visit_to_hour_stats( + app_hour_stat_t hour_stats[HOURS_PER_DAY][MAX_APP_STAT_NUM], + int hour_count[HOURS_PER_DAY], + visit_info_t *visit) { + u_int32_t start; + u_int32_t end; + u_int32_t cursor; + + if (!visit || visit->appid <= 0) { + return; + } + + start = visit->first_time; + end = visit->latest_time; + + if (end <= start) { + int hour = get_hour_from_timestamp(start); + add_app_duration_to_hour_stats(hour_stats, hour_count, hour, visit->appid, 1); + return; + } + + cursor = start; + while (cursor < end) { + int hour = get_hour_from_timestamp(cursor); + u_int32_t next_hour = get_next_hour_timestamp(cursor); + u_int32_t segment_end = end < next_hour ? end : next_hour; + unsigned long long segment_duration = (unsigned long long)(segment_end - cursor); + + if (segment_duration == 0) { + segment_duration = 1; + } + + add_app_duration_to_hour_stats(hour_stats, hour_count, hour, visit->appid, segment_duration); + cursor = segment_end; + } +} + + +static int is_same_day(u_int32_t timestamp1, u_int32_t timestamp2) { + time_t t1 = (time_t)timestamp1; + time_t t2 = (time_t)timestamp2; + struct tm *tm1 = localtime(&t1); + struct tm *tm2 = localtime(&t2); + if (!tm1 || !tm2) + return 0; + return (tm1->tm_year == tm2->tm_year && + tm1->tm_mon == tm2->tm_mon && + tm1->tm_mday == tm2->tm_mday); +} + + +u_int32_t get_today_start_timestamp(void) { + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + if (!tm_info) + return 0; + tm_info->tm_hour = 0; + tm_info->tm_min = 0; + tm_info->tm_sec = 0; + return (u_int32_t)mktime(tm_info); +} + + +daily_hourly_stat_t *get_today_stat(client_node_t *client) { + int i, j; + if (!client) + return NULL; + + u_int32_t today = get_today_start_timestamp(); + + + if (client->daily_stats.date == today && client->daily_stats.is_today == 1) { + return &client->daily_stats; + } + + + if (client->daily_stats.date != 0 && client->daily_stats.date != today) { + save_daily_stats_to_file(client, client->daily_stats.date); + } + + + client->daily_stats.date = today; + client->daily_stats.is_today = 1; + for (i = 0; i < HOURS_PER_DAY; i++) { + for (j = 0; j < TOP_APP_PER_HOUR; j++) { + client->daily_stats.hourly_top_apps[i][j] = -1; + } + + client->daily_stats.hourly_traffic[i].up_bytes = 0; + client->daily_stats.hourly_traffic[i].down_bytes = 0; + client->daily_stats.hourly_online_time[i] = 0; + client->daily_stats.hourly_active_time[i] = 0; + } + + return &client->daily_stats; +} + + +daily_hourly_stat_t *load_history_stat_from_file(client_node_t *client, u_int32_t date) { + if (!client) + return NULL; + + + + return NULL; +} + + +void update_hourly_top_apps(client_node_t *client) { + if (!client) + return; + + + daily_hourly_stat_t *today_stat = get_today_stat(client); + if (!today_stat) + return; + + int cur_time = get_timestamp(); + app_hour_stat_t hour_stats[HOURS_PER_DAY][MAX_APP_STAT_NUM]; + int hour_count[HOURS_PER_DAY] = {0}; + int i, j; + + + for (i = 0; i < HOURS_PER_DAY; i++) { + hour_count[i] = 0; + for (j = 0; j < MAX_APP_STAT_NUM; j++) { + hour_stats[i][j].appid = -1; + hour_stats[i][j].total_time = 0; + } + } + + + visit_info_t *p_info = NULL; + list_for_each_entry(p_info, &client->online_visit, visit) { + if (!is_same_day(p_info->first_time, cur_time)) + continue; + accumulate_visit_to_hour_stats(hour_stats, hour_count, p_info); + } + + list_for_each_entry(p_info, &client->visit, visit) { + + if (!is_same_day(p_info->first_time, cur_time)) + continue; + accumulate_visit_to_hour_stats(hour_stats, hour_count, p_info); + } + + + for (i = 0; i < HOURS_PER_DAY; i++) { + + for (j = 0; j < TOP_APP_PER_HOUR; j++) { + today_stat->hourly_top_apps[i][j] = -1; + } + + if (hour_count[i] == 0) + continue; + + + qsort(hour_stats[i], hour_count[i], sizeof(app_hour_stat_t), compare_app_stat); + + + int top_count = (hour_count[i] < TOP_APP_PER_HOUR) ? hour_count[i] : TOP_APP_PER_HOUR; + for (j = 0; j < top_count; j++) { + if (hour_stats[i][j].appid > 0 && hour_stats[i][j].total_time > 0) { + today_stat->hourly_top_apps[i][j] = hour_stats[i][j].appid; + } + } + } +} + + +void get_hourly_top_apps(client_node_t *client, int hour, int *appids, int max_count) { + + int i; + if (!client || !appids || hour < 0 || hour >= HOURS_PER_DAY || max_count <= 0) + return; + + daily_hourly_stat_t *today_stat = get_today_stat(client); + if (!today_stat) + return; + + int count = (max_count < TOP_APP_PER_HOUR) ? max_count : TOP_APP_PER_HOUR; + for (i = 0; i < count; i++) { + appids[i] = today_stat->hourly_top_apps[hour][i]; + } + + for (i = count; i < max_count; i++) { + appids[i] = -1; + } +} + + +void save_daily_stats_to_file(client_node_t *client, u_int32_t date) { + int i; + int hour; + if (!client) + return; + + daily_hourly_stat_t *stat = &client->daily_stats; + if (stat->date != date) { + char date_str[32] = {0}; + char stat_date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + get_date_string(stat->date, stat_date_str, sizeof(stat_date_str)); + LOG_DEBUG("Date mismatch for client %s hourly stats: requested %s, but stat date is %s, skip saving\n", + client->mac, date_str, stat_date_str); + return; + } + + + char stats_dir[512] = {0}; + char mac_dirname[64] = {0}; + mac_to_dirname(client->mac, mac_dirname, sizeof(mac_dirname)); + snprintf(stats_dir, sizeof(stats_dir), "%s/%s/stats", get_client_data_base_dir(), mac_dirname); + + if (ensure_dir_exists(stats_dir) != 0) { + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + LOG_ERROR("Failed to create stats directory %s for client %s hourly stats (date: %s)\n", + stats_dir, client->mac, date_str); + return; + } + + + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/hourly_%s.json", stats_dir, date_str); + + + struct json_object *json_obj = json_object_new_object(); + json_object_object_add(json_obj, "date", json_object_new_int64(date)); + json_object_object_add(json_obj, "mac", json_object_new_string(client->mac)); + + + struct json_object *hourly_array = json_object_new_array(); + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + struct json_object *hour_obj = json_object_new_object(); + struct json_object *apps_array = json_object_new_array(); + + json_object_object_add(hour_obj, "hour", json_object_new_int(hour)); + + for (i = 0; i < TOP_APP_PER_HOUR; i++) { + if (stat->hourly_top_apps[hour][i] > 0) { + struct json_object *app_obj = json_object_new_object(); + json_object_object_add(app_obj, "appid", json_object_new_int(stat->hourly_top_apps[hour][i])); + if (!app_icon_exists_by_id(stat->hourly_top_apps[hour][i])) + json_object_object_add(app_obj, "icon", json_object_new_int(0)); + const char *app_name = get_app_name_by_id(stat->hourly_top_apps[hour][i]); + if (app_name) { + json_object_object_add(app_obj, "name", json_object_new_string(app_name)); + } + json_object_array_add(apps_array, app_obj); + } + } + + json_object_object_add(hour_obj, "apps", apps_array); + + + struct json_object *traffic_obj = json_object_new_object(); + json_object_object_add(traffic_obj, "up_bytes", json_object_new_int64(stat->hourly_traffic[hour].up_bytes)); + json_object_object_add(traffic_obj, "down_bytes", json_object_new_int64(stat->hourly_traffic[hour].down_bytes)); + json_object_object_add(hour_obj, "traffic", traffic_obj); + + + json_object_object_add(hour_obj, "online_time", json_object_new_int64(stat->hourly_online_time[hour])); + json_object_object_add(hour_obj, "active_time", json_object_new_int64(stat->hourly_active_time[hour])); + + json_object_array_add(hourly_array, hour_obj); + } + + json_object_object_add(json_obj, "hourly_stats", hourly_array); + + + const char *json_string = json_object_to_json_string_ext(json_obj, JSON_C_TO_STRING_PRETTY); + FILE *fp = fopen(file_path, "w"); + if (fp) { + fprintf(fp, "%s\n", json_string); + fclose(fp); + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + LOG_DEBUG("Saved daily hourly stats for client %s (date: %s) to %s\n", + client->mac, date_str, file_path); + } else { + LOG_ERROR("Failed to save daily stats to file: %s (errno: %d)\n", file_path, errno); + } + + json_object_put(json_obj); +} + + +static u_int32_t get_today_start_timestamp_from_ts(u_int32_t timestamp) { + time_t t = (time_t)timestamp; + struct tm *tm_info = localtime(&t); + if (!tm_info) + return 0; + + struct tm tm_day_start = *tm_info; + tm_day_start.tm_hour = 0; + tm_day_start.tm_min = 0; + tm_day_start.tm_sec = 0; + + return (u_int32_t)mktime(&tm_day_start); +} + + +void add_online_offline_record(client_node_t *client, int type, u_int32_t timestamp, unsigned long long duration) { + if (!client) + return; + + online_offline_record_t *record = (online_offline_record_t *)calloc(1, sizeof(online_offline_record_t)); + if (!record) { + LOG_ERROR("Failed to allocate memory for online_offline_record\n"); + return; + } + + record->type = type; + record->timestamp = timestamp; + record->duration = duration; + + + list_add(&record->record, &client->online_offline_records); + + LOG_DEBUG("Added %s record for client %s at timestamp %u, duration: %llu seconds\n", + type == 0 ? "online" : "offline", client->mac, timestamp, duration); +} + + +void save_online_offline_records_to_file(client_node_t *client, u_int32_t date) { + if (!client) + return; + + + char stats_dir[512] = {0}; + char mac_dirname[64] = {0}; + mac_to_dirname(client->mac, mac_dirname, sizeof(mac_dirname)); + snprintf(stats_dir, sizeof(stats_dir), "%s/%s/stats", get_client_data_base_dir(), mac_dirname); + + if (ensure_dir_exists(stats_dir) != 0) { + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + LOG_ERROR("Failed to create stats directory %s for client %s online_offline records (date: %s)\n", + stats_dir, client->mac, date_str); + return; + } + + + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/online_offline_%s.json", stats_dir, date_str); + + + struct json_object *json_obj = json_object_new_object(); + json_object_object_add(json_obj, "date", json_object_new_int64(date)); + json_object_object_add(json_obj, "mac", json_object_new_string(client->mac)); + + + struct json_object *records_array = json_object_new_array(); + online_offline_record_t *record = NULL; + u_int32_t date_end = date + SECONDS_PER_DAY - 1; + + + list_for_each_entry(record, &client->online_offline_records, record) { + + if (record->timestamp >= date && record->timestamp <= date_end) { + struct json_object *record_obj = json_object_new_object(); + json_object_object_add(record_obj, "type", json_object_new_int(record->type)); + json_object_object_add(record_obj, "timestamp", json_object_new_int64(record->timestamp)); + json_object_object_add(record_obj, "duration", json_object_new_int64(record->duration)); + json_object_array_add(records_array, record_obj); + } + } + + json_object_object_add(json_obj, "records", records_array); + + + const char *json_string = json_object_to_json_string_ext(json_obj, JSON_C_TO_STRING_PRETTY); + FILE *fp = fopen(file_path, "w"); + if (fp) { + fprintf(fp, "%s\n", json_string); + fclose(fp); + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + LOG_DEBUG("Saved online_offline records for client %s (date: %s) to %s\n", + client->mac, date_str, file_path); + } else { + LOG_ERROR("Failed to save online_offline records to file: %s (errno: %d)\n", file_path, errno); + } + + json_object_put(json_obj); +} + + +void archive_and_save_online_offline_records(void) { + client_node_t *client = NULL; + u_int32_t today = get_today_start_timestamp(); + + list_for_each_entry(client, &client_list, client) { + + online_offline_record_t *record = NULL; + u_int32_t oldest_date = 0; + + + list_for_each_entry(record, &client->online_offline_records, record) { + u_int32_t record_date = get_today_start_timestamp_from_ts(record->timestamp); + if (oldest_date == 0 || record_date < oldest_date) { + oldest_date = record_date; + } + } + + + if (oldest_date != 0 && oldest_date < today) { + + for (u_int32_t date = oldest_date; date < today; date += SECONDS_PER_DAY) { + + int has_records = 0; + list_for_each_entry(record, &client->online_offline_records, record) { + u_int32_t record_date = get_today_start_timestamp_from_ts(record->timestamp); + if (record_date == date) { + has_records = 1; + break; + } + } + + if (has_records) { + save_online_offline_records_to_file(client, date); + } + } + + + struct list_head *pos, *n; + list_for_each_safe(pos, n, &client->online_offline_records) { + record = list_entry(pos, online_offline_record_t, record); + u_int32_t record_date = get_today_start_timestamp_from_ts(record->timestamp); + if (record_date < today) { + list_del(pos); + free(record); + } + } + } + } +} + + +void save_global_traffic_stats_to_file(u_int32_t date) { + char date_str[32] = {0}; + int hour; + get_date_string(date, date_str, sizeof(date_str)); + + + char global_stats_dir[512] = {0}; + snprintf(global_stats_dir, sizeof(global_stats_dir), "%s/global/stats", get_history_data_root_dir()); + + if (ensure_dir_exists(global_stats_dir) != 0) { + LOG_ERROR("Failed to create global stats directory %s for traffic stats (date: %s)\n", + global_stats_dir, date_str); + return; + } + + + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/traffic_%s.json", global_stats_dir, date_str); + + + struct json_object *json_obj = json_object_new_object(); + json_object_object_add(json_obj, "date", json_object_new_int64(date)); + + + struct json_object *hourly_array = json_object_new_array(); + for (hour = 0; hour < HOURS_PER_DAY; hour++) { + struct json_object *hour_obj = json_object_new_object(); + + json_object_object_add(hour_obj, "hour", json_object_new_int(hour)); + + + struct json_object *traffic_obj = json_object_new_object(); + json_object_object_add(traffic_obj, "up_bytes", json_object_new_int64(g_global_hourly_traffic[hour].up_bytes)); + json_object_object_add(traffic_obj, "down_bytes", json_object_new_int64(g_global_hourly_traffic[hour].down_bytes)); + json_object_object_add(hour_obj, "traffic", traffic_obj); + + json_object_array_add(hourly_array, hour_obj); + } + + json_object_object_add(json_obj, "hourly_traffic", hourly_array); + + + const char *json_string = json_object_to_json_string_ext(json_obj, JSON_C_TO_STRING_PRETTY); + FILE *fp = fopen(file_path, "w"); + if (fp) { + fprintf(fp, "%s\n", json_string); + fclose(fp); + LOG_DEBUG("Saved global traffic stats (date: %s) to %s\n", date_str, file_path); + } else { + LOG_ERROR("Failed to save global traffic stats to file: %s (errno: %d)\n", file_path, errno); + } + + json_object_put(json_obj); +} + + + +void check_and_archive_all_clients(void) { + int i, j; + client_node_t *node = NULL; + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + if (!tm_info){ + LOG_ERROR("Failed to get local time\n"); + return; + } + + u_int32_t today = get_today_start_timestamp(); + + u_int32_t yesterday = today - SECONDS_PER_DAY; + + char yesterday_str[32] = {0}; + char today_str[32] = {0}; + get_date_string(yesterday, yesterday_str, sizeof(yesterday_str)); + get_date_string(today, today_str, sizeof(today_str)); + + LOG_WARN("Date changed: %s -> %s, starting archive process...\n", yesterday_str, today_str); + + int client_count = 0; + list_for_each_entry(node, &client_list, client) { + client_count++; + LOG_DEBUG("archiving client %s\n", node->mac); + + visit_info_t *p_info = NULL, *tmp_info = NULL; + list_for_each_entry_safe(p_info, tmp_info, &node->online_visit, visit) { + + int total_time = p_info->latest_time - p_info->first_time; + if (total_time < g_app_valid_time) { + LOG_DEBUG("Discard visit record on date change (too short): mac=%s, appid=%d, duration=%ds < %ds\n", + node->mac, p_info->appid, total_time, g_app_valid_time); + list_del(&p_info->visit); + free(p_info); // 直接删除,不加入visit列表 + } else { + p_info->expire = 0; + + + list_move(&p_info->visit, &node->visit); + save_visit_record_to_db(node->mac, p_info); + } + } + + save_client_visit_data_to_file(node, yesterday); + + + if (node->daily_stats.date == yesterday) { + save_daily_stats_to_file(node, yesterday); + } + if (node->daily_top_apps_stats.date == yesterday) { + save_daily_top_apps_stats_to_file(node, yesterday); + } + + + u_int32_t date_end = yesterday + SECONDS_PER_DAY - 1; + + list_for_each_entry_safe(p_info, tmp_info, &node->visit, visit) { + if (p_info->first_time >= yesterday && p_info->first_time <= date_end) { + list_del(&p_info->visit); + free(p_info); + } + } + + + visit_stat_t *stat_node = NULL, *tmp_stat_node = NULL; + list_for_each_entry_safe(stat_node, tmp_stat_node, &node->stat_list, list) { + list_del(&stat_node->list); + free(stat_node); + } + + + node->daily_stats.date = today; + node->daily_stats.is_today = 1; + for (i = 0; i < HOURS_PER_DAY; i++) { + for (j = 0; j < TOP_APP_PER_HOUR; j++) { + node->daily_stats.hourly_top_apps[i][j] = -1; + } + + node->daily_stats.hourly_traffic[i].up_bytes = 0; + node->daily_stats.hourly_traffic[i].down_bytes = 0; + node->daily_stats.hourly_online_time[i] = 0; + node->daily_stats.hourly_active_time[i] = 0; + } + + node->daily_top_apps_stats.date = today; + node->daily_top_apps_stats.is_today = 1; + node->daily_top_apps_stats.count = 0; + for (i = 0; i < MAX_TOP_APPS_PER_DAY; i++) { + node->daily_top_apps_stats.apps[i].appid = -1; + node->daily_top_apps_stats.apps[i].total_time = 0; + } + } + + + today = get_today_start_timestamp(); + if (g_daily_stat_date != today) { + memset(g_daily_type_stats, 0, sizeof(g_daily_type_stats)); + g_daily_stat_date = today; + LOG_DEBUG("Reset global daily type stats for new day\n"); + } + + + if (g_global_traffic_date != 0 && g_global_traffic_date != today) { + save_global_traffic_stats_to_file(g_global_traffic_date); + } + + + if (g_global_traffic_date != today) { + memset(g_global_hourly_traffic, 0, sizeof(g_global_hourly_traffic)); + g_global_traffic_date = today; + LOG_DEBUG("Reset global traffic stats for new day\n"); + } + + + global_app_type_record_t *record = NULL, *tmp_record = NULL; + list_for_each_entry_safe(record, tmp_record, &global_hourly_records, list) { + list_del(&record->list); + free(record); + } + LOG_DEBUG("Reset global hourly type stats for new day\n"); + + LOG_DEBUG("Archive completed: processed %d clients for date %s\n", client_count, yesterday_str); + + save_all_client_backup_to_files(); + + + cleanup_old_record_files(); +} + + +typedef struct app_time_stat { + int appid; + unsigned long long total_time; +} app_time_stat_t; + + +static int compare_app_time_stat(const void *a, const void *b) { + app_time_stat_t *pa = (app_time_stat_t *)a; + app_time_stat_t *pb = (app_time_stat_t *)b; + if (pa->total_time > pb->total_time) + return -1; + if (pa->total_time < pb->total_time) + return 1; + return 0; +} + + +daily_top_apps_stat_t *get_today_top_apps_stat(client_node_t *client) { + int i; + if (!client) + return NULL; + + u_int32_t today = get_today_start_timestamp(); + + + if (client->daily_top_apps_stats.date == today && client->daily_top_apps_stats.is_today == 1) { + return &client->daily_top_apps_stats; + } + + + if (client->daily_top_apps_stats.date != 0 && client->daily_top_apps_stats.date != today) { + save_daily_top_apps_stats_to_file(client, client->daily_top_apps_stats.date); + } + + + client->daily_top_apps_stats.date = today; + client->daily_top_apps_stats.is_today = 1; + client->daily_top_apps_stats.count = 0; + for (i = 0; i < MAX_TOP_APPS_PER_DAY; i++) { + client->daily_top_apps_stats.apps[i].appid = -1; + client->daily_top_apps_stats.apps[i].total_time = 0; + } + + return &client->daily_top_apps_stats; +} + + +daily_top_apps_stat_t *load_history_top_apps_stat_from_file(client_node_t *client, u_int32_t date) { + if (!client) + return NULL; + + + + return NULL; +} + + +void update_daily_top_apps(client_node_t *client) { + if (!client) + return; + + + daily_top_apps_stat_t *today_stat = get_today_top_apps_stat(client); + if (!today_stat) + return; + + int cur_time = get_timestamp(); + app_time_stat_t app_stats[MAX_APP_STAT_NUM]; + int app_count = 0; + int i; + + + for (i = 0; i < MAX_APP_STAT_NUM; i++) { + app_stats[i].appid = -1; + app_stats[i].total_time = 0; + } + + + visit_info_t *p_info = NULL; + list_for_each_entry(p_info, &client->visit, visit) { + + if (!is_same_day(p_info->first_time, cur_time)) + continue; + + + unsigned long long visit_time = p_info->latest_time - p_info->first_time; + if (visit_time == 0) + visit_time = 1; + + + int found = 0; + for (i = 0; i < app_count; i++) { + if (app_stats[i].appid == p_info->appid) { + app_stats[i].total_time += visit_time; + found = 1; + break; + } + } + + + if (!found && app_count < MAX_APP_STAT_NUM) { + app_stats[app_count].appid = p_info->appid; + app_stats[app_count].total_time = visit_time; + app_count++; + } else if (!found && app_count >= MAX_APP_STAT_NUM) { + + int min_idx = 0; + unsigned long long min_time = app_stats[0].total_time; + for (i = 1; i < MAX_APP_STAT_NUM; i++) { + if (app_stats[i].total_time < min_time) { + min_time = app_stats[i].total_time; + min_idx = i; + } + } + if (visit_time > min_time) { + app_stats[min_idx].appid = p_info->appid; + app_stats[min_idx].total_time = visit_time; + } + } + } + + if (app_count == 0) { + + today_stat->count = 0; + for (i = 0; i < MAX_TOP_APPS_PER_DAY; i++) { + today_stat->apps[i].appid = -1; + today_stat->apps[i].total_time = 0; + } + return; + } + + + qsort(app_stats, app_count, sizeof(app_time_stat_t), compare_app_time_stat); + + + int top_count = (app_count < MAX_TOP_APPS_PER_DAY) ? app_count : MAX_TOP_APPS_PER_DAY; + today_stat->count = 0; + + for (i = 0; i < top_count; i++) { + if (app_stats[i].appid > 0 && app_stats[i].total_time > 0) { + today_stat->apps[today_stat->count].appid = app_stats[i].appid; + today_stat->apps[today_stat->count].total_time = app_stats[i].total_time; + today_stat->count++; + } + } + + + for (i = today_stat->count; i < MAX_TOP_APPS_PER_DAY; i++) { + today_stat->apps[i].appid = -1; + today_stat->apps[i].total_time = 0; + } +} + + +void save_daily_top_apps_stats_to_file(client_node_t *client, u_int32_t date) { + int i; + if (!client) + return; + + daily_top_apps_stat_t *stat = &client->daily_top_apps_stats; + if (stat->date != date) { + char date_str[32] = {0}; + char stat_date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + get_date_string(stat->date, stat_date_str, sizeof(stat_date_str)); + LOG_DEBUG("Date mismatch for client %s: requested %s, but stat date is %s, skip saving\n", + client->mac, date_str, stat_date_str); + return; + } + + + char stats_dir[512] = {0}; + char mac_dirname[64] = {0}; + mac_to_dirname(client->mac, mac_dirname, sizeof(mac_dirname)); + snprintf(stats_dir, sizeof(stats_dir), "%s/%s/stats", get_client_data_base_dir(), mac_dirname); + + if (ensure_dir_exists(stats_dir) != 0) { + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + LOG_ERROR("Failed to create stats directory %s for client %s top apps (date: %s)\n", + stats_dir, client->mac, date_str); + return; + } + + + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/top_apps_%s.json", stats_dir, date_str); + + + struct json_object *json_obj = json_object_new_object(); + json_object_object_add(json_obj, "date", json_object_new_int64(date)); + json_object_object_add(json_obj, "mac", json_object_new_string(client->mac)); + json_object_object_add(json_obj, "count", json_object_new_int(stat->count)); + + + struct json_object *apps_array = json_object_new_array(); + for (i = 0; i < stat->count; i++) { + struct json_object *app_obj = json_object_new_object(); + json_object_object_add(app_obj, "appid", json_object_new_int(stat->apps[i].appid)); + if (!app_icon_exists_by_id(stat->apps[i].appid)) + json_object_object_add(app_obj, "icon", json_object_new_int(0)); + json_object_object_add(app_obj, "total_time", json_object_new_int64(stat->apps[i].total_time)); + const char *app_name = get_app_name_by_id(stat->apps[i].appid); + if (app_name) { + json_object_object_add(app_obj, "name", json_object_new_string(app_name)); + } + json_object_array_add(apps_array, app_obj); + } + + json_object_object_add(json_obj, "apps", apps_array); + + + const char *json_string = json_object_to_json_string_ext(json_obj, JSON_C_TO_STRING_PRETTY); + FILE *fp = fopen(file_path, "w"); + if (fp) { + fprintf(fp, "%s\n", json_string); + fclose(fp); + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + LOG_DEBUG("Saved daily top apps stats for client %s (date: %s, count: %d) to %s\n", + client->mac, date_str, stat->count, file_path); + } else { + LOG_ERROR("Failed to save daily top apps stats to file: %s (errno: %d)\n", file_path, errno); + } + + json_object_put(json_obj); +} + + +static void mac_to_dirname(const char *mac, char *dirname, size_t len) { + int i; + if (!mac || !dirname || len == 0) + return; + + strncpy(dirname, mac, len - 1); + dirname[len - 1] = '\0'; + + + for (i = 0; dirname[i] != '\0'; i++) { + if (dirname[i] == ':') { + dirname[i] = '_'; + } + } +} + + +static int ensure_dir_exists(const char *path) { + char cmd[512] = {0}; + snprintf(cmd, sizeof(cmd), "mkdir -p %s", path); + system(cmd); + + return 0; +} + + +static void get_date_string(u_int32_t timestamp, char *date_str, size_t len) { + if (!date_str || len == 0) + return; + + time_t t = (time_t)timestamp; + struct tm *tm_info = localtime(&t); + if (!tm_info) { + date_str[0] = '\0'; + return; + } + + strftime(date_str, len, "%Y-%m-%d", tm_info); +} + + +static void format_time_string(u_int32_t timestamp, char *time_str, size_t len) { + if (!time_str || len == 0) + return; + + time_t t = (time_t)timestamp; + struct tm *tm_info = localtime(&t); + if (!tm_info) { + time_str[0] = '\0'; + return; + } + + strftime(time_str, len, "%Y-%m-%d %H:%M:%S", tm_info); +} + + +void save_client_visit_data_to_file(client_node_t *client, u_int32_t date) { + if (!client) + return; + LOG_DEBUG("begin save_client_visit_data_to_file: %s, date: %u\n", client->mac, date); + + int visit_count = 0; + visit_info_t *p_info = NULL; + u_int32_t date_end = date + SECONDS_PER_DAY - 1; + + list_for_each_entry(p_info, &client->visit, visit) { + + if (p_info->first_time >= date && p_info->first_time <= date_end) { + visit_count++; + } + } + + + if (visit_count == 0) { + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + LOG_DEBUG("No visit records for client %s on date %s, skip saving\n", client->mac, date_str); + return; + } + + + if (ensure_dir_exists(get_history_data_root_dir()) != 0) { + LOG_ERROR("Failed to create root directory: %s\n", get_history_data_root_dir()); + return; + } + + char date_str[32] = {0}; + get_date_string(date, date_str, sizeof(date_str)); + + char file_path[512] = {0}; + sqlite3 *db = NULL; + sqlite3_stmt *delete_stmt = NULL; + sqlite3_stmt *insert_stmt = NULL; + int rc = SQLITE_OK; + int transaction_started = 0; + build_client_visit_db_path(file_path, sizeof(file_path)); + + if (open_client_visit_db(&db) != 0) { + LOG_ERROR("Failed to open client visit db: %s (client: %s, date: %s)\n", + file_path, client->mac, date_str); + return; + } + + rc = sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); + if (rc != SQLITE_OK) { + LOG_ERROR("Failed to begin transaction: %s (rc: %d)\n", file_path, rc); + sqlite3_close(db); + return; + } + transaction_started = 1; + + rc = sqlite3_prepare_v2(db, + "DELETE FROM app_visit_record WHERE mac = ? AND record_date = ?;", + -1, &delete_stmt, NULL); + if (rc != SQLITE_OK) { + LOG_ERROR("Failed to prepare delete statement: %s (rc: %d)\n", file_path, rc); + goto CLEANUP; + } + + sqlite3_bind_text(delete_stmt, 1, client->mac, -1, SQLITE_STATIC); + sqlite3_bind_int64(delete_stmt, 2, date); + rc = sqlite3_step(delete_stmt); + if (rc != SQLITE_DONE) { + LOG_ERROR("Failed to clear app visit records: %s (rc: %d)\n", file_path, rc); + goto CLEANUP; + } + + rc = sqlite3_prepare_v2(db, + "INSERT INTO app_visit_record (mac, record_date, appid, start_time, end_time, duration, action) " + "VALUES (?, ?, ?, ?, ?, ?, ?);", + -1, &insert_stmt, NULL); + if (rc != SQLITE_OK) { + LOG_ERROR("Failed to prepare insert statement: %s (rc: %d)\n", file_path, rc); + goto CLEANUP; + } + + + list_for_each_entry(p_info, &client->visit, visit) { + + if (p_info->first_time < date || p_info->first_time > date_end) { + LOG_DEBUG("skip visit record: %u, %u\n", p_info->first_time, date_end); + continue; + } + + int duration = p_info->latest_time - p_info->first_time; + if (duration == 0) + duration = 1; + + sqlite3_bind_text(insert_stmt, 1, client->mac, -1, SQLITE_STATIC); + sqlite3_bind_int64(insert_stmt, 2, date); + sqlite3_bind_int(insert_stmt, 3, p_info->appid); + sqlite3_bind_int64(insert_stmt, 4, p_info->first_time); + sqlite3_bind_int64(insert_stmt, 5, p_info->latest_time); + sqlite3_bind_int(insert_stmt, 6, duration); + sqlite3_bind_int(insert_stmt, 7, p_info->action); + + rc = sqlite3_step(insert_stmt); + if (rc != SQLITE_DONE) { + LOG_ERROR("Failed to insert app visit record: %s (rc: %d)\n", file_path, rc); + goto CLEANUP; + } + sqlite3_reset(insert_stmt); + sqlite3_clear_bindings(insert_stmt); + } + + rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); + if (rc != SQLITE_OK) { + LOG_ERROR("Failed to commit transaction: %s (rc: %d)\n", file_path, rc); + goto CLEANUP; + } + transaction_started = 0; + + LOG_DEBUG("Saved visit data for client %s (date: %s, records: %d) to sqlite db %s\n", + client->mac, date_str, visit_count, file_path); + +CLEANUP: + if (delete_stmt) + sqlite3_finalize(delete_stmt); + if (insert_stmt) + sqlite3_finalize(insert_stmt); + if (transaction_started) + sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL); + if (db) + sqlite3_close(db); +} + + +static u_int32_t parse_date_string(const char *date_str) { + if (!date_str) + return 0; + + struct tm tm_info = {0}; + if (sscanf(date_str, "%d-%d-%d", &tm_info.tm_year, &tm_info.tm_mon, &tm_info.tm_mday) != 3) + return 0; + + tm_info.tm_year -= 1900; + tm_info.tm_mon -= 1; + tm_info.tm_hour = 0; + tm_info.tm_min = 0; + tm_info.tm_sec = 0; + + return (u_int32_t)mktime(&tm_info); +} + + +static int extract_date_from_filename(const char *filename, char *date_str, size_t len) { + int i; + if (!filename || !date_str || len == 0) + return -1; + + + const char *prefixes[] = {"hourly_", "top_apps_", "traffic_", NULL}; + const char *start = filename; + + + for (i = 0; prefixes[i] != NULL; i++) { + size_t prefix_len = strlen(prefixes[i]); + if (strncmp(filename, prefixes[i], prefix_len) == 0) { + start = filename + prefix_len; + break; + } + } + + + int year, mon, mday; + if (sscanf(start, "%d-%d-%d", &year, &mon, &mday) != 3) + return -1; + + snprintf(date_str, len, "%04d-%02d-%02d", year, mon, mday); + return 0; +} + + +static void cleanup_old_record_files(void) { + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + if (!tm_info) { + LOG_ERROR("Failed to get local time for cleanup\n"); + return; + } + + u_int32_t expire_timestamp = (u_int32_t)now - (MAX_RECORD_DAY * SECONDS_PER_DAY); + int deleted_count = 0; + deleted_count += delete_expired_visit_records(expire_timestamp); + + DIR *base_dir = opendir(get_client_data_base_dir()); + if (!base_dir) { + LOG_DEBUG("Base directory %s does not exist, skip cleanup\n", get_client_data_base_dir()); + return; + } + + struct dirent *client_entry; + + + while ((client_entry = readdir(base_dir)) != NULL) { + if (client_entry->d_name[0] == '.') + continue; + + char client_dir[512] = {0}; + snprintf(client_dir, sizeof(client_dir), "%s/%s", get_client_data_base_dir(), client_entry->d_name); + + struct stat st; + if (stat(client_dir, &st) != 0 || !S_ISDIR(st.st_mode)) + continue; + + + char visits_dir[512] = {0}; + snprintf(visits_dir, sizeof(visits_dir), "%s/visits", client_dir); + + DIR *visits_dp = opendir(visits_dir); + if (visits_dp) { + struct dirent *file_entry; + while ((file_entry = readdir(visits_dp)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + u_int32_t file_date = parse_date_string(date_str); + if (file_date > 0 && file_date < expire_timestamp) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", visits_dir, file_entry->d_name); + if (unlink(file_path) == 0) { + deleted_count++; + LOG_WARN("Deleted expired visit file: %s (date: %s)\n", file_path, date_str); + } else { + LOG_ERROR("Failed to delete file: %s (errno: %d)\n", file_path, errno); + } + } + } + } + closedir(visits_dp); + } + + + char stats_dir[512] = {0}; + snprintf(stats_dir, sizeof(stats_dir), "%s/stats", client_dir); + + DIR *stats_dp = opendir(stats_dir); + if (stats_dp) { + struct dirent *file_entry; + while ((file_entry = readdir(stats_dp)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + u_int32_t file_date = parse_date_string(date_str); + if (file_date > 0 && file_date < expire_timestamp) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", stats_dir, file_entry->d_name); + if (unlink(file_path) == 0) { + deleted_count++; + LOG_WARN("Deleted expired stats file: %s (date: %s)\n", file_path, date_str); + } else { + LOG_ERROR("Failed to delete file: %s (errno: %d)\n", file_path, errno); + } + } + } + } + closedir(stats_dp); + } + } + + closedir(base_dir); + + + char global_stats_dir[512] = {0}; + snprintf(global_stats_dir, sizeof(global_stats_dir), "%s/global/stats", get_history_data_root_dir()); + + DIR *global_stats_dp = opendir(global_stats_dir); + if (global_stats_dp) { + struct dirent *file_entry; + while ((file_entry = readdir(global_stats_dp)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + u_int32_t file_date = parse_date_string(date_str); + if (file_date > 0 && file_date < expire_timestamp) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", global_stats_dir, file_entry->d_name); + if (unlink(file_path) == 0) { + deleted_count++; + LOG_WARN("Deleted expired global traffic stats file: %s (date: %s)\n", file_path, date_str); + } else { + LOG_ERROR("Failed to delete file: %s (errno: %d)\n", file_path, errno); + } + } + } + } + closedir(global_stats_dp); + } + + if (deleted_count > 0) { + LOG_WARN("Cleanup completed: deleted %d expired record files (older than %d days)\n", + deleted_count, MAX_RECORD_DAY); + } +} + + +void get_global_traffic_stats(traffic_stat_t *traffic_array) { + if (!traffic_array) + return; + + + u_int32_t today = get_today_start_timestamp(); + if (g_global_traffic_date != today) { + memset(g_global_hourly_traffic, 0, sizeof(g_global_hourly_traffic)); + g_global_traffic_date = today; + } + + + memcpy(traffic_array, g_global_hourly_traffic, sizeof(g_global_hourly_traffic)); +} + + + + + + +void delete_client_record_files(const char *mac, const char *start_date, const char *end_date, const char *delete_type) { + u_int32_t start_timestamp = 0; + u_int32_t end_timestamp = UINT32_MAX; + + + if (start_date && strlen(start_date) > 0) { + + if (strchr(start_date, '-')) { + start_timestamp = parse_date_string(start_date); + } else { + start_timestamp = (u_int32_t)atoi(start_date); + } + } + + + if (end_date && strlen(end_date) > 0) { + if (strchr(end_date, '-')) { + u_int32_t end_date_ts = parse_date_string(end_date); + + end_timestamp = end_date_ts + SECONDS_PER_DAY - 1; + } else { + end_timestamp = (u_int32_t)atoi(end_date); + } + } + + + int delete_visits = 1; + int delete_stats = 1; + if (delete_type && strlen(delete_type) > 0) { + if (strcmp(delete_type, "visits") == 0) { + delete_visits = 1; + delete_stats = 0; + } else if (strcmp(delete_type, "stats") == 0) { + delete_visits = 0; + delete_stats = 1; + } else if (strcmp(delete_type, "all") == 0) { + delete_visits = 1; + delete_stats = 1; + } + } + + char mac_dirname[64] = {0}; + int specific_mac = 0; + if (mac && strlen(mac) > 0) { + mac_to_dirname(mac, mac_dirname, sizeof(mac_dirname)); + specific_mac = 1; + } + + DIR *base_dir = opendir(get_client_data_base_dir()); + int deleted_count = 0; + + if (delete_visits) { + deleted_count += delete_visit_records_in_range(specific_mac ? mac : NULL, start_timestamp, end_timestamp); + } + + if (!base_dir) { + LOG_DEBUG("Base directory %s does not exist, skip client directory cleanup\n", get_client_data_base_dir()); + } else { + struct dirent *client_entry; + + while ((client_entry = readdir(base_dir)) != NULL) { + if (client_entry->d_name[0] == '.') + continue; + + + if (specific_mac && strcmp(client_entry->d_name, mac_dirname) != 0) + continue; + + char client_dir[512] = {0}; + snprintf(client_dir, sizeof(client_dir), "%s/%s", get_client_data_base_dir(), client_entry->d_name); + + struct stat st; + if (stat(client_dir, &st) != 0 || !S_ISDIR(st.st_mode)) + continue; + + + if (delete_visits) { + char visits_dir[512] = {0}; + snprintf(visits_dir, sizeof(visits_dir), "%s/visits", client_dir); + + DIR *visits_dp = opendir(visits_dir); + if (visits_dp) { + struct dirent *file_entry; + while ((file_entry = readdir(visits_dp)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + u_int32_t file_date = parse_date_string(date_str); + if (file_date > 0 && file_date >= start_timestamp && file_date <= end_timestamp) { + char file_path[512] = {0}; + snprintf(file_path, sizeof(file_path), "%s/%s", visits_dir, file_entry->d_name); + if (unlink(file_path) == 0) { + deleted_count++; + LOG_DEBUG("Deleted visit file: %s (date: %s)\n", file_path, date_str); + } else { + LOG_ERROR("Failed to delete file: %s (errno: %d)\n", file_path, errno); + } + } + } + } + closedir(visits_dp); + } + } + + + if (delete_stats) { + char stats_dir[512] = {0}; + snprintf(stats_dir, sizeof(stats_dir), "%s/stats", client_dir); + + DIR *stats_dp = opendir(stats_dir); + if (stats_dp) { + struct dirent *file_entry; + while ((file_entry = readdir(stats_dp)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + u_int32_t file_date = parse_date_string(date_str); + if (file_date > 0 && file_date >= start_timestamp && file_date <= end_timestamp) { + char file_path[768] = {0}; + int path_len = snprintf(file_path, sizeof(file_path), "%s/%s", stats_dir, file_entry->d_name); + if (path_len >= 0 && path_len < sizeof(file_path)) { + if (unlink(file_path) == 0) { + deleted_count++; + LOG_DEBUG("Deleted stats file: %s (date: %s)\n", file_path, date_str); + } else { + LOG_ERROR("Failed to delete file: %s (errno: %d)\n", file_path, errno); + } + } else { + LOG_ERROR("File path too long: %s/%s\n", stats_dir, file_entry->d_name); + } + } + } + } + closedir(stats_dp); + } + } + } + + closedir(base_dir); + } + + if (delete_stats && !specific_mac) { + char global_stats_dir[512] = {0}; + snprintf(global_stats_dir, sizeof(global_stats_dir), "%s/global/stats", get_history_data_root_dir()); + DIR *global_dp = opendir(global_stats_dir); + if (global_dp) { + struct dirent *file_entry; + while ((file_entry = readdir(global_dp)) != NULL) { + if (file_entry->d_name[0] == '.') + continue; + char date_str[32] = {0}; + if (extract_date_from_filename(file_entry->d_name, date_str, sizeof(date_str)) == 0) { + u_int32_t file_date = parse_date_string(date_str); + if (file_date > 0 && file_date >= start_timestamp && file_date <= end_timestamp) { + char file_path[768] = {0}; + int path_len = snprintf(file_path, sizeof(file_path), "%s/%s", global_stats_dir, file_entry->d_name); + if (path_len >= 0 && path_len < sizeof(file_path)) { + if (unlink(file_path) == 0) { + deleted_count++; + LOG_DEBUG("Deleted global stats file: %s (date: %s)\n", file_path, date_str); + } else { + LOG_ERROR("Failed to delete global stats file: %s (errno: %d)\n", file_path, errno); + } + } + } + } + } + closedir(global_dp); + } + } + + LOG_DEBUG("Delete completed: deleted %d record files\n", deleted_count); +} + + +void update_global_app_type_stats(int appid, unsigned long long time_delta) { + if (appid <= 0 || time_delta == 0) + return; + + int app_type = appid / 1000; + if (app_type <= 0 || app_type > MAX_APP_TYPE) + return; + + int type_index = app_type - 1; + u_int32_t cur_time = get_timestamp(); + + + u_int32_t today = get_today_start_timestamp(); + if (g_daily_stat_date != today) { + memset(g_daily_type_stats, 0, sizeof(g_daily_type_stats)); + g_daily_stat_date = today; + } + + + g_daily_type_stats[type_index] += time_delta; + + + global_app_type_record_t *record = (global_app_type_record_t *)calloc(1, sizeof(global_app_type_record_t)); + if (record) { + record->app_type = app_type; + record->time_delta = time_delta; + record->timestamp = cur_time; + INIT_LIST_HEAD(&record->list); + list_add_tail(&record->list, &global_hourly_records); + } +} + + +void cleanup_expired_hourly_stats(void) { + u_int32_t cur_time = get_timestamp(); + u_int32_t expire_time = cur_time - 3600; + + global_app_type_record_t *record = NULL, *tmp_record = NULL; + int cleared_count = 0; + + + list_for_each_entry_safe(record, tmp_record, &global_hourly_records, list) { + if (record->timestamp < expire_time) { + + list_del(&record->list); + free(record); + cleared_count++; + } else { + + break; + } + } + + if (cleared_count > 0) { + LOG_DEBUG("Cleared %d expired hourly records\n", cleared_count); + } +} + + +void get_global_daily_app_type_stats(unsigned long long *type_time_array) { + if (!type_time_array) + return; + + + u_int32_t today = get_today_start_timestamp(); + if (g_daily_stat_date != today) { + memset(g_daily_type_stats, 0, sizeof(g_daily_type_stats)); + g_daily_stat_date = today; + } + + + memcpy(type_time_array, g_daily_type_stats, sizeof(g_daily_type_stats)); +} + + +void get_global_hourly_app_type_stats(unsigned long long *type_time_array) { + if (!type_time_array) + return; + + + cleanup_expired_hourly_stats(); + + + memset(type_time_array, 0, sizeof(unsigned long long) * MAX_APP_TYPE); + + + u_int32_t cur_time = get_timestamp(); + u_int32_t expire_time = cur_time - 3600; + + global_app_type_record_t *record = NULL; + list_for_each_entry(record, &global_hourly_records, list) { + if (record->timestamp >= expire_time) { + int type_index = record->app_type - 1; + if (type_index >= 0 && type_index < MAX_APP_TYPE) { + type_time_array[type_index] += record->time_delta; + } + } + } +} diff --git a/open-app-filter/src/fwx_user.h b/open-app-filter/src/fwx_user.h new file mode 100644 index 00000000..176c2eda --- /dev/null +++ b/open-app-filter/src/fwx_user.h @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#ifndef __FILTER_USER_H__ +#define __FILTER_USER_H__ +#define MAX_IP_LEN 32 +#define MAX_MAC_LEN 32 + +#define MAX_HOSTNAME_SIZE 64 +#define OAF_VISIT_LIST_FILE "/tmp/visit_list" +#define OAF_DEV_LIST_FILE "/tmp/dev_list" +#define MIN_VISIT_TIME 5 // default 5s +#define MAX_APP_STAT_NUM 8 +#define MAX_VISITLIST_DUMP_NUM 16 +#define MAX_APP_TYPE 16 +#define MAX_APP_ID_NUM 128 +#define MAX_SUPPORT_DEV_NUM 64 +#define SECONDS_PER_DAY (24 * 3600) +#define MAX_NICKNAME_SIZE 64 +#define HOURS_PER_DAY 24 +#define TOP_APP_PER_HOUR 3 +#define MAX_DAILY_STAT_DAYS 30 +#define MAX_TOP_APPS_PER_DAY 10 +#define MAX_RECORD_DAY 30 +#define MAX_RECENT_APPS 5 +#define MAX_USER_RECORDS 4096 + + +typedef struct visit_info +{ + int appid; + u_int32_t first_time; + u_int32_t latest_time; + int action; + int expire; + struct list_head visit; + +} visit_info_t; + + +typedef struct online_offline_record +{ + int type; + u_int32_t timestamp; + unsigned long long duration; + struct list_head record; +} online_offline_record_t; + +typedef struct online_session_stat +{ + unsigned long long up_bytes; + unsigned long long down_bytes; + unsigned long long online_duration; + unsigned long long active_duration; + int recent_apps[MAX_RECENT_APPS]; + int recent_app_count; + u_int32_t start_time; +} online_session_stat_t; + +typedef struct user_record +{ + int action; + u_int32_t timestamp; + char mac[MAX_MAC_LEN]; + char nickname[MAX_NICKNAME_SIZE]; + char hostname[MAX_HOSTNAME_SIZE]; + unsigned long long up_bytes; + unsigned long long down_bytes; + unsigned long long online_duration; + unsigned long long active_duration; + int recent_apps[MAX_RECENT_APPS]; + int recent_app_count; + struct list_head list; +} user_record_t; + + +typedef struct visit_stat +{ + int appid; + unsigned long long total_time; + struct list_head list; +} visit_stat_t; + + +typedef struct global_app_type_record +{ + int app_type; + unsigned long long time_delta; + u_int32_t timestamp; + struct list_head list; +} global_app_type_record_t; + + +typedef struct traffic_stat +{ + unsigned long long up_bytes; + unsigned long long down_bytes; +} traffic_stat_t; + + +typedef struct daily_hourly_stat +{ + u_int32_t date; + int is_today; + + int hourly_top_apps[HOURS_PER_DAY][TOP_APP_PER_HOUR]; + + traffic_stat_t hourly_traffic[HOURS_PER_DAY]; + + unsigned long long hourly_online_time[HOURS_PER_DAY]; + unsigned long long hourly_active_time[HOURS_PER_DAY]; +} daily_hourly_stat_t; + + +typedef struct daily_top_apps_stat +{ + u_int32_t date; + int is_today; + int count; + struct { + int appid; + unsigned long long total_time; + } apps[MAX_TOP_APPS_PER_DAY]; +} daily_top_apps_stat_t; + + +#define MAX_REPORT_URL_LEN 64 +typedef struct client_node +{ + char mac[MAX_MAC_LEN]; + char ip[MAX_IP_LEN]; + char ipv6[128]; + unsigned int up_rate; + unsigned int down_rate; + int rssi; + unsigned int rx_rate; + unsigned int tx_rate; + char band[16]; + char wifi_ifname[64]; + int is_wireless; + int wireless_online; + char hostname[MAX_HOSTNAME_SIZE]; + char nickname[MAX_NICKNAME_SIZE]; + int online; + int expire; + u_int32_t offline_time; + u_int32_t online_time; + struct list_head online_visit; + struct list_head visit; + struct list_head stat_list; + struct list_head online_offline_records; + int mf_user_loaded; + char visiting_url[MAX_REPORT_URL_LEN]; + int visiting_app; + int active; + int last_online_state; + int session_online_recorded; + online_session_stat_t online_session; + + daily_hourly_stat_t daily_stats; + + daily_top_apps_stat_t daily_top_apps_stats; + struct list_head client; + +} client_node_t; + +struct app_visit_info +{ + int app_id; + char app_name[32]; + int total_time; +}; + +struct app_visit_stat_info +{ + int num; + struct app_visit_info visit_list[MAX_APP_STAT_NUM]; +}; +typedef void (*iter_func)(void *arg, client_node_t *client); + +extern struct list_head client_list; +extern struct list_head user_record_list; + +int get_timestamp(void); +client_node_t *add_client_node(char *mac); +void init_client_list(void); +void add_debug_test_users(void); +void dump_client_list(void); +void dump_client_visit_list(void); +client_node_t *find_client_node(const char *mac); +void client_foreach(void *arg, iter_func iter); +void add_visit_info_node(struct list_head *visit_list, visit_info_t *node); +void check_client_visit_info_expire(void); +void flush_expire_visit_info(void); +int check_client_expire(void); +void flush_expire_client_node(void); +void move_expired_online_visit_to_offline(void); +void update_client_list(void); +void update_client_nickname(void); +void update_client_visiting_info(void); +void refresh_client_wireless_status(void); +void update_hourly_top_apps(client_node_t *client); +void get_hourly_top_apps(client_node_t *client, int hour, int *appids, int max_count); +int get_hour_from_timestamp(u_int32_t timestamp); +unsigned long long get_visit_duration_in_hour(visit_info_t *visit, int target_hour); +daily_hourly_stat_t *get_today_stat(client_node_t *client); +daily_hourly_stat_t *load_history_stat_from_file(client_node_t *client, u_int32_t date); +void save_daily_stats_to_file(client_node_t *client, u_int32_t date); +void check_and_archive_all_clients(void); +u_int32_t get_today_start_timestamp(void); +void update_daily_top_apps(client_node_t *client); +daily_top_apps_stat_t *get_today_top_apps_stat(client_node_t *client); +daily_top_apps_stat_t *load_history_top_apps_stat_from_file(client_node_t *client, u_int32_t date); +void save_daily_top_apps_stats_to_file(client_node_t *client, u_int32_t date); +void save_client_visit_data_to_file(client_node_t *client, u_int32_t date); +void init_client_visit_db(void); +int add_mock_visit_records_to_db(int record_count, int mac_count, unsigned long long *old_size_bytes, unsigned long long *new_size_bytes); +const char *get_client_data_root_dir(void); +const char *get_history_data_root_dir(void); +const char *get_client_data_base_dir(void); +void reset_client_data_base_dir_cache(void); +void load_client_backup_from_files(void); +void save_client_backup_to_file(client_node_t *client); +void save_all_client_backup_to_files(void); +void load_app_valid_time_config(void); +int get_app_valid_time(void); +void check_and_cleanup_history_data_by_size(void); +void archive_and_save_client_visits(void); +void delete_client_record_files(const char *mac, const char *start_date, const char *end_date, const char *delete_type); +void update_global_app_type_stats(int appid, unsigned long long time_delta); +void cleanup_expired_hourly_stats(void); +void get_global_daily_app_type_stats(unsigned long long *type_time_array); +void get_global_hourly_app_type_stats(unsigned long long *type_time_array); +struct json_object *fwx_api_get_global_app_type_stats(struct json_object *req_obj); +void save_global_traffic_stats_to_file(u_int32_t date); +void get_global_traffic_stats(traffic_stat_t *traffic_array); +struct json_object *fwx_api_get_global_traffic_stats(struct json_object *req_obj); +void reset_online_session_stat(client_node_t *client, u_int32_t start_time); +void update_online_session_flow(client_node_t *client, unsigned long long up_bytes, unsigned long long down_bytes); +void update_online_session_activity(client_node_t *client, int online_seconds, int active_seconds); +void update_online_session_recent_app(client_node_t *client, int appid); +void add_user_record(client_node_t *client, int action, u_int32_t timestamp); + +#endif diff --git a/open-app-filter/src/fwx_utils.c b/open-app-filter/src/fwx_utils.c new file mode 100644 index 00000000..f0dd412d --- /dev/null +++ b/open-app-filter/src/fwx_utils.c @@ -0,0 +1,337 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fwx_utils.h" + +char *str_trim(char *s) { + char *start, *last, *bk; + int len; + + start = s; + while (isspace(*start)) + start++; + + bk = last = s + strlen(s) - 1; + while (last > start && isspace(*last)) + last--; + + if ((s != start) || (bk != last)) { + len = last - start + 1; + strncpy(s, start, len); + s[len] = '\0'; + } + return s; +} + +int exec_with_result_line(char *cmd, char *result, int len) +{ + FILE *fp = NULL; + if (!cmd || !result || !len) + return -1; + fp = popen(cmd, "r"); + if (!fp) + return -1; + fgets(result, len, fp); + str_trim(result); + pclose(fp); + return 0; +} + +int fwx_send_msg_to_kernel(char *buf){ + + if (access("/dev/fwx", F_OK) != 0) { + return 0; // Device doesn't exist, silently skip + } + + FILE *fp = fopen("/dev/fwx", "w"); + if (fp) { + fprintf(fp, "%s", buf); + fclose(fp); + } + return 0; +} + +int check_same_network(char *ip1, char *netmask, char *ip2) { + struct in_addr addr1, addr2, mask; + + if (inet_pton(AF_INET, ip1, &addr1) != 1) { + printf("Invalid IP address: %s\n", ip1); + return -1; + } + if (inet_pton(AF_INET, netmask, &mask) != 1) { + printf("Invalid netmask: %s\n", netmask); + return -1; + } + if (inet_pton(AF_INET, ip2, &addr2) != 1) { + printf("Invalid IP address: %s\n", ip2); + return -1; + } + + if ((addr1.s_addr & mask.s_addr) == (addr2.s_addr & mask.s_addr)) { + return 1; + } else { + return 0; + } +} + + +int af_read_file_value(const char *file_path, char *value, int value_len) { + FILE *file = fopen(file_path, "r"); + if (!file) { + perror("Failed to open file"); + return -1; + } + + if (fgets(value, value_len, file) == NULL) { + perror("Failed to read line from file"); + fclose(file); + return -1; + } + + size_t len = strlen(value); + if (len > 0 && value[len - 1] == '\n') { + value[len - 1] = '\0'; + } + + fclose(file); + return 0; +} + +int af_read_file_int_value(const char *file_path, int *value) { + char line_buf[128] = {0}; + if (af_read_file_value(file_path, line_buf, sizeof(line_buf)) < 0){ + return -1; + } + *value = atoi(line_buf); + return 0; +} + +/** + * Parse time_str from UCI format into time period structures + * Format: "HH:MM-HH:MM-w1,w2,w3 HH:MM-HH:MM-w4,w5 ..." + * Example: "00:00-23:59-2,3,6 00:00-02:05-1,2,3,0" + */ +int fwx_parse_time_str(const char *time_str, fwx_time_period_t *periods, int max_periods) { + if (!time_str || !periods || max_periods <= 0) { + return -1; + } + + int period_count = 0; + char *save_ptr1 = NULL; + char *save_ptr2 = NULL; + + + char time_str_copy[512] = {0}; + strncpy(time_str_copy, time_str, sizeof(time_str_copy) - 1); + + + char *time_period = strtok_r(time_str_copy, " ", &save_ptr1); + while (time_period && period_count < max_periods) { + fwx_time_period_t *period = &periods[period_count]; + memset(period, 0, sizeof(fwx_time_period_t)); + + char start[16] = {0}; + char end[16] = {0}; + char weekdays[64] = {0}; + + + char *first_delim = strchr(time_period, '-'); + if (!first_delim) { + + time_period = strtok_r(NULL, " ", &save_ptr1); + continue; + } + + + strncpy(start, time_period, first_delim - time_period); + start[first_delim - time_period] = '\0'; + + + char *second_delim = strchr(first_delim + 1, '-'); + if (second_delim) { + + strncpy(end, first_delim + 1, second_delim - first_delim - 1); + end[second_delim - first_delim - 1] = '\0'; + strncpy(weekdays, second_delim + 1, sizeof(weekdays) - 1); + } else { + + strncpy(end, first_delim + 1, sizeof(end) - 1); + } + + + strncpy(period->start_time, start, sizeof(period->start_time) - 1); + strncpy(period->end_time, end, sizeof(period->end_time) - 1); + + + if (strlen(weekdays) > 0) { + char weekdays_copy[64] = {0}; + strncpy(weekdays_copy, weekdays, sizeof(weekdays_copy) - 1); + + char *weekday_str = strtok_r(weekdays_copy, ",", &save_ptr2); + while (weekday_str && period->weekday_count < MAX_WEEKDAYS) { + int weekday = atoi(weekday_str); + if (weekday >= 0 && weekday <= 6) { + period->weekdays[period->weekday_count] = weekday; + period->weekday_count++; + } + weekday_str = strtok_r(NULL, ",", &save_ptr2); + } + } + + period_count++; + time_period = strtok_r(NULL, " ", &save_ptr1); + } + + return period_count; +} + + +void update_fwx_proc_value(char *key, char *value){ + char cmd_buf[128] = {0}; + char file_path[128] = {0}; + char old_value[128] = {0}; + sprintf(file_path, "/proc/sys/fwx/%s", key); + + af_read_file_value(file_path, old_value, sizeof(old_value)); + if (strcmp(old_value, value) != 0){ + sprintf(cmd_buf, "echo %s >/proc/sys/fwx/%s", value, key); + system(cmd_buf); + } +} + +void update_fwx_proc_u32_value(char *key, u_int32_t value){ + char buf[32] = {0}; + sprintf(buf, "%u", value); + update_fwx_proc_value(key, buf); +} + +#define MD5_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define MD5_G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define MD5_H(x, y, z) ((x) ^ (y) ^ (z)) +#define MD5_I(x, y, z) ((y) ^ ((x) | ~(z))) +#define MD5_ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +static const uint32_t md5_k[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +}; + +static const uint8_t md5_s[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 +}; + +static uint32_t md5_load_le32(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void md5_store_le32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static void md5_transform(uint32_t *state, const uint8_t block[64]) +{ + uint32_t a = state[0]; + uint32_t b = state[1]; + uint32_t c = state[2]; + uint32_t d = state[3]; + uint32_t x[16]; + int i; + + for (i = 0; i < 16; i++) + x[i] = md5_load_le32(block + i * 4); + + for (i = 0; i < 64; i++) { + uint32_t f; + int g; + if (i < 16) { + f = MD5_F(b, c, d); + g = i; + } else if (i < 32) { + f = MD5_G(b, c, d); + g = (5 * i + 1) & 15; + } else if (i < 48) { + f = MD5_H(b, c, d); + g = (3 * i + 5) & 15; + } else { + f = MD5_I(b, c, d); + g = (7 * i) & 15; + } + f = f + a + md5_k[i] + x[g]; + a = d; + d = c; + c = b; + b = b + MD5_ROTL(f, md5_s[i]); + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +void fwx_md5(const unsigned char *data, size_t len, unsigned char digest[16]) +{ + uint32_t state[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; + uint64_t bit_len = (uint64_t)len * 8; + uint8_t block[64]; + size_t rem; + int i; + + while (len >= 64) { + md5_transform(state, data); + data += 64; + len -= 64; + } + + memset(block, 0, sizeof(block)); + memcpy(block, data, len); + block[len] = 0x80; + + rem = len + 1; + if (rem > 56) { + md5_transform(state, block); + memset(block, 0, sizeof(block)); + } + md5_store_le32(block + 56, (uint32_t)bit_len); + md5_store_le32(block + 60, (uint32_t)(bit_len >> 32)); + md5_transform(state, block); + + for (i = 0; i < 4; i++) + md5_store_le32(digest + i * 4, state[i]); +} diff --git a/open-app-filter/src/fwx_utils.h b/open-app-filter/src/fwx_utils.h new file mode 100644 index 00000000..90aba57b --- /dev/null +++ b/open-app-filter/src/fwx_utils.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __UTILS_H__ +#define __UTILS_H__ +#include + +#define MAX_WEEKDAYS 7 + + +typedef struct fwx_time_period { + char start_time[16]; + char end_time[16]; + int weekdays[MAX_WEEKDAYS]; + int weekday_count; +} fwx_time_period_t; + +char *str_trim(char *s); +int exec_with_result_line(char *cmd, char *result, int len); +int check_same_network(char *ip1, char *netmask, char *ip2); +int af_read_file_value(const char *file_path, char *value, int value_len); +int af_read_file_int_value(const char *file_path, int *value); +int fwx_send_msg_to_kernel(char *buf); +int fwx_parse_time_str(const char *time_str, fwx_time_period_t *periods, int max_periods); +void update_fwx_proc_value(char *key, char *value); +void update_fwx_proc_u32_value(char *key, u_int32_t value); +void fwx_md5(const unsigned char *data, size_t len, unsigned char digest[16]); +#endif diff --git a/open-app-filter/src/fwx_wireless.c b/open-app-filter/src/fwx_wireless.c new file mode 100644 index 00000000..d188f5de --- /dev/null +++ b/open-app-filter/src/fwx_wireless.c @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#include +#include +#include +#include +#include +#include +#include +#include "fwx.h" +#include "fwx_wireless.h" + +#define MAX_WIRELESS_SECTION_NUM 64 +#define MAX_WIRELESS_NAME_LEN 64 + +static int get_wireless_option_value(struct uci_context *ctx, const char *section_name, const char *option_name, char *out, size_t out_len) +{ + char uci_key[128] = {0}; + + if (!ctx || !section_name || !option_name || !out || out_len == 0) { + return -1; + } + + snprintf(uci_key, sizeof(uci_key), "wireless.%s.%s", section_name, option_name); + if (fwx_uci_get_value(ctx, uci_key, out, (int)out_len) == 0) { + return 0; + } + + out[0] = '\0'; + return -1; +} + +static int set_wireless_option_value(struct uci_context *ctx, const char *section_name, const char *option_name, const char *value) +{ + char uci_key[128] = {0}; + + if (!ctx || !section_name || !option_name || !value) { + return -1; + } + + snprintf(uci_key, sizeof(uci_key), "wireless.%s.%s", section_name, option_name); + return fwx_uci_set_value(ctx, uci_key, (char *)value); +} + +static int delete_wireless_option_value(struct uci_context *ctx, const char *section_name, const char *option_name) +{ + char uci_key[128] = {0}; + + if (!ctx || !section_name || !option_name) { + return -1; + } + + snprintf(uci_key, sizeof(uci_key), "wireless.%s.%s", section_name, option_name); + return fwx_uci_delete(ctx, uci_key); +} + +static int collect_wireless_sections(struct uci_context *ctx, + char radio_names[][MAX_WIRELESS_NAME_LEN], int *radio_num, + char iface_names[][MAX_WIRELESS_NAME_LEN], int *iface_num) +{ + struct uci_package *pkg = NULL; + struct uci_element *e = NULL; + + if (!ctx || !radio_names || !radio_num || !iface_names || !iface_num) { + return -1; + } + + *radio_num = 0; + *iface_num = 0; + if (uci_load(ctx, "wireless", &pkg) != UCI_OK) { + return -1; + } + + uci_foreach_element(&pkg->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (strcmp(s->type, "wifi-device") == 0) { + if (*radio_num < MAX_WIRELESS_SECTION_NUM) { + snprintf(radio_names[*radio_num], MAX_WIRELESS_NAME_LEN, "%s", e->name); + (*radio_num)++; + } + continue; + } + + if (strcmp(s->type, "wifi-iface") == 0) { + if (*iface_num < MAX_WIRELESS_SECTION_NUM) { + snprintf(iface_names[*iface_num], MAX_WIRELESS_NAME_LEN, "%s", e->name); + (*iface_num)++; + } + } + } + + if (pkg) { + uci_unload(ctx, pkg); + } + return 0; +} + +static int find_first_iface_by_radio(struct uci_context *ctx, const char *radio_name, + char iface_names[][MAX_WIRELESS_NAME_LEN], int iface_num, + char *out_iface, size_t out_iface_len) +{ + int i = 0; + + if (!ctx || !radio_name || !iface_names || !out_iface || out_iface_len == 0) { + return -1; + } + + for (i = 0; i < iface_num; i++) { + char device[64] = {0}; + if (get_wireless_option_value(ctx, iface_names[i], "device", device, sizeof(device)) == 0 && + strcmp(device, radio_name) == 0) { + snprintf(out_iface, out_iface_len, "%s", iface_names[i]); + return 0; + } + } + + return -1; +} + +static int is_iface_exists(char iface_names[][MAX_WIRELESS_NAME_LEN], int iface_num, const char *section_name) +{ + int i = 0; + + if (!iface_names || !section_name) { + return 0; + } + + for (i = 0; i < iface_num; i++) { + if (strcmp(iface_names[i], section_name) == 0) { + return 1; + } + } + + return 0; +} + +static const char *format_band_label(const char *band, const char *hwmode, char *buf, size_t buf_len) +{ + if (!buf || buf_len == 0) { + return "Unknown"; + } + + if (band && band[0] != '\0') { + if (strcmp(band, "2g") == 0) { + snprintf(buf, buf_len, "2.4G"); + } else if (strcmp(band, "5g") == 0) { + snprintf(buf, buf_len, "5G"); + } else if (strcmp(band, "6g") == 0) { + snprintf(buf, buf_len, "6G"); + } else if (strcmp(band, "60g") == 0) { + snprintf(buf, buf_len, "60G"); + } else { + snprintf(buf, buf_len, "%s", band); + } + + return buf; + } + + if (hwmode && hwmode[0] != '\0') { + if (strstr(hwmode, "11ad") || strstr(hwmode, "11ay")) { + snprintf(buf, buf_len, "60G"); + } else if (strstr(hwmode, "11a")) { + snprintf(buf, buf_len, "5G"); + } else { + snprintf(buf, buf_len, "2.4G"); + } + + return buf; + } + + snprintf(buf, buf_len, "Unknown"); + return buf; +} + +static int parse_hidden_value(const char *hidden) +{ + if (!hidden) { + return 0; + } + + if (strcmp(hidden, "1") == 0 || + strcasecmp(hidden, "true") == 0 || + strcasecmp(hidden, "yes") == 0 || + strcasecmp(hidden, "on") == 0) { + return 1; + } + + return 0; +} + +static int json_to_bool(struct json_object *obj, int default_value) +{ + enum json_type type; + const char *val_str = NULL; + + if (!obj) { + return default_value; + } + + type = json_object_get_type(obj); + if (type == json_type_boolean || type == json_type_int) { + return json_object_get_int(obj) ? 1 : 0; + } + + if (type == json_type_string) { + val_str = json_object_get_string(obj); + return parse_hidden_value(val_str); + } + + return default_value; +} + +static int is_open_encryption(const char *encryption) +{ + if (!encryption || encryption[0] == '\0') { + return 1; + } + + if (strcmp(encryption, "none") == 0) { + return 1; + } + + if (strncmp(encryption, "owe", 3) == 0) { + return 1; + } + + return 0; +} + +static int apply_ssid_item(struct uci_context *ctx, struct json_object *item, + char iface_names[][MAX_WIRELESS_NAME_LEN], int iface_num) +{ + struct json_object *radio_obj = NULL; + struct json_object *section_obj = NULL; + struct json_object *ssid_obj = NULL; + struct json_object *password_obj = NULL; + struct json_object *encryption_obj = NULL; + struct json_object *hidden_obj = NULL; + struct json_object *isolate_obj = NULL; + const char *radio_name = NULL; + const char *section_name = NULL; + const char *password = NULL; + char encryption[64] = {0}; + char iface_name[MAX_WIRELESS_NAME_LEN] = {0}; + int has_password = 0; + int has_encryption = 0; + int has_change = 0; + int hidden = 0; + int isolate = 0; + char hidden_str[4] = {0}; + char isolate_str[4] = {0}; + + if (!ctx || !item || json_object_get_type(item) != json_type_object) { + return 0; + } + + json_object_object_get_ex(item, "radio", &radio_obj); + json_object_object_get_ex(item, "section", §ion_obj); + json_object_object_get_ex(item, "ssid", &ssid_obj); + has_password = json_object_object_get_ex(item, "password", &password_obj); + has_encryption = json_object_object_get_ex(item, "encryption", &encryption_obj); + json_object_object_get_ex(item, "hidden", &hidden_obj); + json_object_object_get_ex(item, "isolate", &isolate_obj); + + radio_name = radio_obj ? json_object_get_string(radio_obj) : NULL; + section_name = section_obj ? json_object_get_string(section_obj) : NULL; + + if (section_name && section_name[0] != '\0' && is_iface_exists(iface_names, iface_num, section_name)) { + snprintf(iface_name, sizeof(iface_name), "%s", section_name); + } + + if (iface_name[0] == '\0' && radio_name && radio_name[0] != '\0') { + find_first_iface_by_radio(ctx, radio_name, iface_names, iface_num, iface_name, sizeof(iface_name)); + } + + if (iface_name[0] == '\0') { + LOG_WARN("set_wireless_base_setting skip item, iface not found, radio=%s section=%s\n", + radio_name ? radio_name : "", section_name ? section_name : ""); + return 0; + } + + if (ssid_obj) { + const char *ssid = json_object_get_string(ssid_obj); + if (set_wireless_option_value(ctx, iface_name, "ssid", ssid ? ssid : "") == 0) { + has_change = 1; + } + } + + if (has_encryption) { + const char *enc_value = json_object_get_string(encryption_obj); + if (!enc_value || enc_value[0] == '\0') { + enc_value = "none"; + } + snprintf(encryption, sizeof(encryption), "%s", enc_value); + if (set_wireless_option_value(ctx, iface_name, "encryption", encryption) == 0) { + has_change = 1; + } + } else { + if (get_wireless_option_value(ctx, iface_name, "encryption", encryption, sizeof(encryption)) != 0 || + encryption[0] == '\0') { + snprintf(encryption, sizeof(encryption), "none"); + } + } + + if (hidden_obj) { + hidden = json_to_bool(hidden_obj, 0); + snprintf(hidden_str, sizeof(hidden_str), "%d", hidden ? 1 : 0); + if (set_wireless_option_value(ctx, iface_name, "hidden", hidden_str) == 0) { + has_change = 1; + } + } + if (isolate_obj) { + isolate = json_to_bool(isolate_obj, 0); + snprintf(isolate_str, sizeof(isolate_str), "%d", isolate ? 1 : 0); + if (set_wireless_option_value(ctx, iface_name, "isolate", isolate_str) == 0) { + has_change = 1; + } + } + + if (is_open_encryption(encryption)) { + int del_key = delete_wireless_option_value(ctx, iface_name, "key"); + int del_key1 = delete_wireless_option_value(ctx, iface_name, "key1"); + if (del_key == 0 || del_key1 == 0) { + has_change = 1; + } + } else if (has_password) { + password = json_object_get_string(password_obj); + if (password && password[0] != '\0') { + if (set_wireless_option_value(ctx, iface_name, "key", password) == 0) { + has_change = 1; + } + } else { + if (delete_wireless_option_value(ctx, iface_name, "key") == 0) { + has_change = 1; + } + } + } + + return has_change; +} + +struct json_object *fwx_api_get_wireless_base_setting(struct json_object *req_obj) +{ + struct uci_context *ctx = NULL; + struct json_object *data_obj = NULL; + struct json_object *ssid_list_obj = NULL; + char radio_names[MAX_WIRELESS_SECTION_NUM][MAX_WIRELESS_NAME_LEN] = {{0}}; + char iface_names[MAX_WIRELESS_SECTION_NUM][MAX_WIRELESS_NAME_LEN] = {{0}}; + int radio_num = 0; + int iface_num = 0; + int i = 0; + + (void)req_obj; + + data_obj = json_object_new_object(); + ssid_list_obj = json_object_new_array(); + if (!data_obj || !ssid_list_obj) { + if (ssid_list_obj) { + json_object_put(ssid_list_obj); + } + if (data_obj) { + json_object_put(data_obj); + } + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + ctx = uci_alloc_context(); + if (!ctx) { + json_object_put(ssid_list_obj); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (collect_wireless_sections(ctx, radio_names, &radio_num, iface_names, &iface_num) != 0) { + uci_free_context(ctx); + json_object_put(ssid_list_obj); + json_object_put(data_obj); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + for (i = 0; i < radio_num; i++) { + struct json_object *ssid_obj = NULL; + char iface_name[MAX_WIRELESS_NAME_LEN] = {0}; + char ssid[128] = {0}; + char key[128] = {0}; + char encryption[64] = {0}; + char hidden[16] = {0}; + char isolate[16] = {0}; + char band[16] = {0}; + char hwmode[32] = {0}; + char band_label[16] = {0}; + + if (find_first_iface_by_radio(ctx, radio_names[i], iface_names, iface_num, iface_name, sizeof(iface_name)) != 0) { + continue; + } + + get_wireless_option_value(ctx, iface_name, "ssid", ssid, sizeof(ssid)); + get_wireless_option_value(ctx, iface_name, "key", key, sizeof(key)); + get_wireless_option_value(ctx, iface_name, "encryption", encryption, sizeof(encryption)); + get_wireless_option_value(ctx, iface_name, "hidden", hidden, sizeof(hidden)); + get_wireless_option_value(ctx, iface_name, "isolate", isolate, sizeof(isolate)); + get_wireless_option_value(ctx, radio_names[i], "band", band, sizeof(band)); + get_wireless_option_value(ctx, radio_names[i], "hwmode", hwmode, sizeof(hwmode)); + + ssid_obj = json_object_new_object(); + if (!ssid_obj) { + continue; + } + + json_object_object_add(ssid_obj, "radio", json_object_new_string(radio_names[i])); + json_object_object_add(ssid_obj, "section", json_object_new_string(iface_name)); + json_object_object_add(ssid_obj, "band", json_object_new_string(format_band_label(band, hwmode, band_label, sizeof(band_label)))); + json_object_object_add(ssid_obj, "ssid", json_object_new_string(ssid)); + json_object_object_add(ssid_obj, "password", json_object_new_string(key)); + json_object_object_add(ssid_obj, "encryption", json_object_new_string(encryption[0] ? encryption : "none")); + json_object_object_add(ssid_obj, "hidden", json_object_new_int(parse_hidden_value(hidden))); + json_object_object_add(ssid_obj, "isolate", json_object_new_int(parse_hidden_value(isolate))); + json_object_array_add(ssid_list_obj, ssid_obj); + } + + json_object_object_add(data_obj, "ssid_list", ssid_list_obj); + uci_free_context(ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj); +} + +struct json_object *fwx_api_set_wireless_base_setting(struct json_object *req_obj) +{ + struct uci_context *ctx = NULL; + struct json_object *ssid_list_obj = NULL; + enum json_type ssid_list_type; + char radio_names[MAX_WIRELESS_SECTION_NUM][MAX_WIRELESS_NAME_LEN] = {{0}}; + char iface_names[MAX_WIRELESS_SECTION_NUM][MAX_WIRELESS_NAME_LEN] = {{0}}; + int radio_num = 0; + int iface_num = 0; + int i = 0; + int list_len = 0; + int has_change = 0; + int processed_num = 0; + + if (!req_obj) { + LOG_ERROR("set_wireless_base_setting: req_obj is null\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + LOG_INFO("set_wireless_base_setting req=%s\n", json_object_to_json_string_ext(req_obj, JSON_C_TO_STRING_PLAIN)); + + ssid_list_obj = json_object_object_get(req_obj, "ssid_list"); + if (!ssid_list_obj) { + LOG_ERROR("set_wireless_base_setting: missing ssid_list\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + ssid_list_type = json_object_get_type(ssid_list_obj); + if (ssid_list_type != json_type_array && ssid_list_type != json_type_object) { + LOG_ERROR("set_wireless_base_setting: invalid ssid_list type=%d\n", ssid_list_type); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + ctx = uci_alloc_context(); + if (!ctx) { + LOG_ERROR("set_wireless_base_setting: alloc uci ctx failed\n"); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (collect_wireless_sections(ctx, radio_names, &radio_num, iface_names, &iface_num) != 0) { + LOG_ERROR("set_wireless_base_setting: collect_wireless_sections failed\n"); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + + if (ssid_list_type == json_type_array) { + list_len = json_object_array_length(ssid_list_obj); + for (i = 0; i < list_len; i++) { + struct json_object *item = json_object_array_get_idx(ssid_list_obj, i); + has_change |= apply_ssid_item(ctx, item, iface_names, iface_num); + processed_num++; + } + } else { + json_object_object_foreach(ssid_list_obj, key, val) { + (void)key; + has_change |= apply_ssid_item(ctx, val, iface_names, iface_num); + processed_num++; + } + } + LOG_INFO("set_wireless_base_setting: list_type=%d processed=%d changed=%d\n", + ssid_list_type, processed_num, has_change); + + if (has_change) { + if (fwx_uci_commit(ctx, "wireless") != UCI_OK) { + LOG_ERROR("set_wireless_base_setting: commit wireless failed\n"); + uci_free_context(ctx); + return fwx_gen_api_response_data(API_CODE_ERROR, NULL); + } + system("wifi reload"); + } + + uci_free_context(ctx); + + return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL); +} diff --git a/open-app-filter/src/fwx_wireless.h b/open-app-filter/src/fwx_wireless.h new file mode 100644 index 00000000..985ea5bc --- /dev/null +++ b/open-app-filter/src/fwx_wireless.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) +*/ +#ifndef __FWX_WIRELESS_H__ +#define __FWX_WIRELESS_H__ + +#include + +struct json_object *fwx_api_get_wireless_base_setting(struct json_object *req_obj); +struct json_object *fwx_api_set_wireless_base_setting(struct json_object *req_obj); + +#endif diff --git a/open-app-filter/src/main.c b/open-app-filter/src/main.c index 94bf0732..87b13519 100644 --- a/open-app-filter/src/main.c +++ b/open-app-filter/src/main.c @@ -1,23 +1,6 @@ -/* -Copyright (C) 2020 Derry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2026 destan19(TT) */ #include #include @@ -25,399 +8,275 @@ THE SOFTWARE. #include #include #include -#include "appfilter_user.h" -#include "appfilter_netlink.h" -#include "appfilter_ubus.h" -#include "appfilter_config.h" +#include "fwx_user.h" +#include "fwx_netlink.h" +#include "fwx_ubus.h" +#include "fwx_stat.h" +#include "fwx_config.h" #include #include -#include #include -#include "appfilter.h" +#include +#include +#include +#include "fwx.h" #include -#include "utils.h" +#include "fwx_utils.h" +#include "fwx_app_filter.h" +#include "check_main.h" +#include "fwx_feature.h" +#include "fwx_feature_online.h" +#include "fwx_custom_feature.h" + +int current_log_level = LOG_LEVEL_WARN; +//int current_log_level = LOG_LEVEL_INFO; +int g_fwxd_debug_mode = 0; #define CMD_GET_LAN_IP_FMT "ifconfig %s | grep 'inet addr' | awk '{print $2}' | awk -F: '{print $2}'" #define CMD_GET_LAN_MASK_FMT "ifconfig %s | grep 'inet addr' | awk '{print $4}' | awk -F: '{print $2}'" - - -int current_log_level = LOG_LEVEL_INFO; -af_run_time_status_t g_af_status; -int g_oaf_config_change = 1; -af_config_t g_af_config; +#define CLIENT_BACKUP_SYNC_INTERVAL_SEC 600 +int g_fwx_config_chage = 1; int g_hnat_init = 0; int g_feature_update = 0; -int g_feature_update_time = 0; -void oaf_timeout_handler(struct uloop_timeout *t); -void af_init_time_status(void){ - g_af_status.filter = 0; - g_af_status.deny_time = 0; - g_af_status.allow_time = 0; - g_af_status.match_time = 0; - g_af_status.remain_time = 0; - g_af_status.used_time = 0; - g_af_status.period_blocked = 0; -} +extern void check_and_cleanup_history_data_by_size(void); +extern void collect_interface_traffic_rate(void); - -void af_init_status(void){ - af_init_time_status(); -} -struct uloop_timeout dev_tm = { - .cb = oaf_timeout_handler}; - - -static struct uloop_fd appfilter_nl_fd = { - .cb = appfilter_nl_handler, +fwx_status_t g_fwx_status = { + .internet = 1 }; +fwx_capability_t g_fwx_capability = { + .wireless_support = 0 +}; -void apply_time_config_to_uci(af_time_config_t *time_config){ - struct uci_context *uci_ctx = uci_alloc_context(); - if (!uci_ctx) { - printf("Failed to allocate UCI context\n"); - return; - } - af_uci_set_int_value(uci_ctx, "appfilter.time.time_mode", time_config->time_mode); - - // Build days string from global weekday array (used as fallback) - char days_str[128] = {0}; - int first = 1; - int i, j; - for (i = 0; i < 7; i++) { - if (time_config->days[i] == 1) { - if (!first) { - strcat(days_str, " "); - } - char tmp[8]; - snprintf(tmp, sizeof(tmp), "%d", i); - strcat(days_str, tmp); - first = 0; - } - } - af_uci_set_value(uci_ctx, "appfilter.time.days", days_str); - - if (time_config->time_mode == 0) { - // Manual mode: write time_list - af_uci_delete(uci_ctx, "appfilter.time.time"); - int time_list_len = time_config->time_num; - for (i = 0; i < time_list_len; i++) { - char time_str[256] = {0}; - // Build weekday string: "1,2,4,5" - char weekday_str[64] = {0}; - first = 1; - for (j = 0; j < 7; j++) { - if (time_config->time_list[i].days[j] == 1) { - if (!first) { - strcat(weekday_str, ","); - } - char tmp[8]; - snprintf(tmp, sizeof(tmp), "%d", j); - strcat(weekday_str, tmp); - first = 0; - } - } - // Format time: "HH:MM" with zero padding - char start_time_str[16] = {0}; - char end_time_str[16] = {0}; - snprintf(start_time_str, sizeof(start_time_str), "%02d:%02d", - time_config->time_list[i].start_time.hour, - time_config->time_list[i].start_time.min); - snprintf(end_time_str, sizeof(end_time_str), "%02d:%02d", - time_config->time_list[i].end_time.hour, - time_config->time_list[i].end_time.min); - - // Format: "1,2,4,5;00:00-23:59" - snprintf(time_str, sizeof(time_str), "%s;%s-%s", weekday_str, start_time_str, end_time_str); - - printf("time_str: %s\n", time_str); - af_uci_add_list(uci_ctx, "appfilter.time.time", time_str); - } - } else { - // Dynamic mode: write seg_time, deny_time, allow_time - af_uci_set_int_value(uci_ctx, "appfilter.time.deny_time", time_config->deny_time); - af_uci_set_int_value(uci_ctx, "appfilter.time.allow_time", time_config->allow_time); - - char start_time_str[16] = {0}; - char end_time_str[16] = {0}; - // Format time: "HH:MM" with zero padding - snprintf(start_time_str, sizeof(start_time_str), "%02d:%02d", - time_config->seg_time.start_time.hour, - time_config->seg_time.start_time.min); - snprintf(end_time_str, sizeof(end_time_str), "%02d:%02d", - time_config->seg_time.end_time.hour, - time_config->seg_time.end_time.min); - - af_uci_set_value(uci_ctx, "appfilter.time.start_time", start_time_str); - af_uci_set_value(uci_ctx, "appfilter.time.end_time", end_time_str); - } - af_uci_commit(uci_ctx, "appfilter"); - - uci_free_context(uci_ctx); -} +void fwx_timeout_handler(struct uloop_timeout *t); -int af_load_time_config(af_time_config_t *t_config) +struct uloop_timeout fwx_tm = { + .cb = fwx_timeout_handler}; + +static struct uloop_fd fwx_nl_fd = { + .cb = fwx_netlink_handler, +}; + +#define FEATURE_UPGRADE_SUCCESS 200 +#define FEATURE_UPGRADE_FAILED 400 + +static int write_feature_upgrade_status(int status) { - char time_list_buf[MAX_TIME_LIST_LEN] = {0}; - char days_buf[128] = {0}; - char start_time_buf[128] = {0}; - char end_time_buf[128] = {0}; - struct uci_context *ctx = uci_alloc_context(); - int old_ver_config = 0; - printf("af_load_time_config: start\n"); - if (!ctx) + const char *temporary = FWX_FEATURE_UPGRADE_STATUS_PATH ".tmp"; + FILE *fp = fopen(temporary, "w"); + + if (!fp) + return -1; + if (fprintf(fp, "%d", status) < 0 || fflush(fp) != 0 || + fsync(fileno(fp)) != 0) { + fclose(fp); + unlink(temporary); return -1; - memset(t_config, 0, sizeof(af_time_config_t)); - t_config->time_mode = af_uci_get_int_value(ctx, "appfilter.time.time_mode"); - t_config->deny_time = af_uci_get_int_value(ctx, "appfilter.time.deny_time"); - t_config->allow_time = af_uci_get_int_value(ctx, "appfilter.time.allow_time"); - - af_uci_get_value(ctx, "appfilter.time.start_time", start_time_buf, sizeof(start_time_buf)); - af_uci_get_value(ctx, "appfilter.time.end_time", end_time_buf, sizeof(end_time_buf)); - af_uci_get_value(ctx, "appfilter.time.days", days_buf, sizeof(days_buf)); - sscanf(start_time_buf, "%d:%d", &t_config->seg_time.start_time.hour, &t_config->seg_time.start_time.min); - sscanf(end_time_buf, "%d:%d", &t_config->seg_time.end_time.hour, &t_config->seg_time.end_time.min); - t_config->time_num = 0; - // Parse global days (may be empty, continue even if empty) - char *saveptr2 = NULL; - char *p = strtok_r(days_buf, " ", &saveptr2); - if (p) { - do { - t_config->days[atoi(p)] = 1; - printf("af_load_time_config: day[%d] = 1\n", atoi(p)); - p = strtok_r(NULL, " ", &saveptr2); - } while (p != NULL); } + if (fclose(fp) != 0 || chmod(temporary, 0644) != 0 || + rename(temporary, FWX_FEATURE_UPGRADE_STATUS_PATH) != 0) { + unlink(temporary); + return -1; + } + return 0; +} - af_uci_get_list_value(ctx, "appfilter.time.time", time_list_buf, sizeof(time_list_buf), " "); - printf("af_load_time_config: time_list_buf from uci: %s\n", time_list_buf); - - char time_list_copy[MAX_TIME_LIST_LEN] = {0}; - strncpy(time_list_copy, time_list_buf, sizeof(time_list_copy) - 1); - - // Use strtok_r to avoid issues with nested strtok calls - char *saveptr1 = NULL; - p = strtok_r(time_list_copy, " ", &saveptr1); - if (!p) { - printf("af_load_time_config: no time periods found\n"); - goto EXIT; +static int write_feature_info_file(void) +{ + const char *feature_data; + const char *json_text; + const char *temporary = FWX_FEATURE_INFO_PATH ".tmp"; + size_t feature_len = 0; + size_t offset = 0; + char line[1024]; + char version[64] = {0}; + int feature_type = 0; + int feature_free = 0; + int type_seen = 0; + int free_seen = 0; + struct json_object *info_obj = NULL; + FILE *fp = NULL; + int line_ret; + int ret = -1; + + feature_data = fwx_feature_get_data(&feature_len); + while (feature_data && + (line_ret = fwx_feature_next_line(feature_data, feature_len, &offset, + line, sizeof(line))) != 0) { + if (line_ret < 0) + continue; + if (!strncmp(line, "#version ", 9)) { + sscanf(line, "#version %63s", version); + } else if (!strncmp(line, "#type ", 6)) { + sscanf(line, "#type %d", &feature_type); + if (feature_type != 0 && feature_type != 1) + feature_type = 0; + type_seen = 1; + } else if (!strncmp(line, "#free ", 6)) { + sscanf(line, "#free %d", &feature_free); + feature_free = feature_free ? 1 : 0; + free_seen = 1; + } + if (version[0] && type_seen && free_seen) + break; } - - int period_idx = 0; - do - { - printf("af_load_time_config: parsing period[%d]: %s\n", period_idx, p); - - char *time_part = p; - // Initialize days array for this time period (use global days as default) - int i; - for (i = 0; i < 7; i++) { - t_config->time_list[t_config->time_num].days[i] = t_config->days[i]; - } - - // Check if format is new (with weekdays): "1,2,4,5;00:00-23:59" - char *semicolon = strchr(p, ';'); - if (semicolon) { - // New format: parse weekdays and store in this time period's days array - char weekday_str[64] = {0}; - strncpy(weekday_str, p, semicolon - p); - weekday_str[semicolon - p] = '\0'; - time_part = semicolon + 1; - - printf("af_load_time_config: period[%d] has weekdays: %s, time_part: %s\n", period_idx, weekday_str, time_part); - - // Clear days array for this time period first - for (i = 0; i < 7; i++) { - t_config->time_list[t_config->time_num].days[i] = 0; - } - - // Parse weekdays: "1,2,4,5" using strtok_r - char weekday_copy[64] = {0}; - strncpy(weekday_copy, weekday_str, sizeof(weekday_copy) - 1); - char *saveptr2 = NULL; - char *wd = strtok_r(weekday_copy, ",", &saveptr2); - while (wd) { - int day_val = atoi(wd); - if (day_val >= 0 && day_val < 7) { - t_config->time_list[t_config->time_num].days[day_val] = 1; - printf("af_load_time_config: period[%d] set day %d\n", period_idx, day_val); - } - wd = strtok_r(NULL, ",", &saveptr2); - } - } else { - LOG_WARN("af_load_time_config: period[%d] no weekdays, using global days\n", period_idx); - old_ver_config = 1; - } - // If no semicolon, use global days (already copied above) - - // Parse time: "00:00-23:59" or "1,2,4,5;00:00-23:59" (time_part already points to time part) - int ret = sscanf(time_part, "%d:%d-%d:%d", &t_config->time_list[t_config->time_num].start_time.hour, - &t_config->time_list[t_config->time_num].start_time.min, &t_config->time_list[t_config->time_num].end_time.hour, &t_config->time_list[t_config->time_num].end_time.min); - if (ret != 4) { - printf("af_load_time_config: period[%d] ERROR: failed to parse time from %s\n", period_idx, time_part); - } else { - printf("af_load_time_config: time[%d] %d:%d-%d:%d, days: ", t_config->time_num, t_config->time_list[t_config->time_num].start_time.hour, t_config->time_list[t_config->time_num].start_time.min, - t_config->time_list[t_config->time_num].end_time.hour, t_config->time_list[t_config->time_num].end_time.min); - for (i = 0; i < 7; i++) { - if (t_config->time_list[t_config->time_num].days[i]) { - printf("%d ", i); - } - } - printf("\n"); - t_config->time_num++; - } - period_idx++; - } while (p = strtok_r(NULL, " ", &saveptr1)); - - printf("af_load_time_config: total periods loaded: %d\n", t_config->time_num); - - // Load mode 2 daily limit config (if time_mode is 2) - if (t_config->time_mode == 2) { - int weekday; - for (weekday = 0; weekday < 7; weekday++) { - char uci_key[64] = {0}; - snprintf(uci_key, sizeof(uci_key), "appfilter.time.daily_limit_%d", weekday); - - char daily_limit_str[128] = {0}; - af_uci_get_value(ctx, uci_key, daily_limit_str, sizeof(daily_limit_str)); - - // Initialize to default values - t_config->daily_limit[weekday].enable = 0; - t_config->daily_limit[weekday].am_time = 0; - t_config->daily_limit[weekday].pm_time = 0; - - // Parse format: "enable:am_time:pm_time" - if (strlen(daily_limit_str) > 0) { - char *first_colon = strchr(daily_limit_str, ':'); - if (first_colon) { - char *second_colon = strchr(first_colon + 1, ':'); - if (second_colon) { - // New format: "enable:am_time:pm_time" - t_config->daily_limit[weekday].enable = atoi(daily_limit_str); - t_config->daily_limit[weekday].am_time = atoi(first_colon + 1); - t_config->daily_limit[weekday].pm_time = atoi(second_colon + 1); - } else { - // Old format: "am_time:pm_time" - t_config->daily_limit[weekday].enable = 1; - t_config->daily_limit[weekday].am_time = atoi(daily_limit_str); - t_config->daily_limit[weekday].pm_time = atoi(first_colon + 1); - } - } else { - t_config->daily_limit[weekday].enable = 1; - t_config->daily_limit[weekday].am_time = atoi(daily_limit_str); - } - } - printf("af_load_time_config: daily_limit[%d] enable=%d, am_time=%d, pm_time=%d\n", - weekday, t_config->daily_limit[weekday].enable, - t_config->daily_limit[weekday].am_time, t_config->daily_limit[weekday].pm_time); - } + info_obj = json_object_new_object(); + if (!info_obj) + return -1; + json_object_object_add(info_obj, "version", json_object_new_string(version)); + json_object_object_add(info_obj, "type", json_object_new_int(feature_type)); + json_object_object_add(info_obj, "free", json_object_new_int(feature_free)); + json_object_object_add(info_obj, "app_count", json_object_new_int(g_app_count)); + json_object_object_add(info_obj, "format", json_object_new_string("v4.0")); + json_text = json_object_to_json_string_ext(info_obj, JSON_C_TO_STRING_PLAIN); + fp = fopen(temporary, "w"); + if (!fp || fwrite(json_text, 1, strlen(json_text), fp) != strlen(json_text) || + fflush(fp) != 0 || fsync(fileno(fp)) != 0) + goto out; + if (fclose(fp) != 0) { + fp = NULL; + goto out; } - -EXIT: - uci_free_context(ctx); - return old_ver_config; + fp = NULL; + if (chmod(temporary, 0644) != 0 || rename(temporary, FWX_FEATURE_INFO_PATH) != 0) + goto out; + ret = 0; +out: + if (fp) + fclose(fp); + if (ret != 0) + unlink(temporary); + json_object_put(info_obj); + return ret; } -void af_load_global_config(af_global_config_t *config){ - int ret = 0; - char lan_ifname[32] = {0}; - struct uci_context *ctx = uci_alloc_context(); - if (!ctx) - return; - ret = af_uci_get_int_value(ctx, "appfilter.global.enable"); - if (ret < 0) - config->enable = 0; - else - config->enable = ret; - - ret = af_uci_get_int_value(ctx, "appfilter.global.record_enable"); - if (ret < 0) - config->record_enable = 0; - else - config->record_enable = ret; - - ret = af_uci_get_int_value(ctx, "appfilter.global.user_mode"); - if (ret < 0) - config->user_mode = 0; - else - config->user_mode = ret; - - ret = af_uci_get_int_value(ctx, "appfilter.global.work_mode"); - if (ret < 0) - config->work_mode = 0; - else - config->work_mode = ret; - ret = af_uci_get_int_value(ctx, "appfilter.global.tcp_rst"); - if (ret < 0) - config->tcp_rst = 1; - else - config->tcp_rst = ret; - - ret = af_uci_get_int_value(ctx, "appfilter.global.disable_hnat"); - if (ret < 0) - config->disable_hnat = 1; - else - config->disable_hnat = ret; - - ret = af_uci_get_int_value(ctx, "appfilter.global.auto_load_engine"); - if (ret < 0) - config->auto_load_engine = 0; - else - config->auto_load_engine = ret; - - ret = af_uci_get_int_value(ctx, "appfilter.global.disable_quic"); - if (ret < 0) - config->disable_quic = 0; - else - config->disable_quic = ret; - - ret = af_uci_get_int_value(ctx, "appfilter.global.app_filter_mode"); - if (ret < 0) - config->app_filter_mode = 0; // Default to specified apps mode - else - config->app_filter_mode = ret; - - ret = af_uci_get_value(ctx, "appfilter.global.lan_ifname", lan_ifname, sizeof(lan_ifname)); - if (ret < 0) - strncpy(config->lan_ifname, "br-lan", sizeof(config->lan_ifname) - 1); - else - strncpy(config->lan_ifname, lan_ifname, sizeof(config->lan_ifname) - 1); - - uci_free_context(ctx); - LOG_DEBUG("enable=%d, user_mode=%d, work_mode=%d, disable_quic=%d, app_filter_mode=%d\n", config->enable, config->user_mode, config->work_mode, config->disable_quic, config->app_filter_mode); +int fwx_nl_clean_feature(void){ + fwx_nl_msg_t msg; + if (fwx_nl_fd.fd < 0){ + return -1; + } + msg.action = FWX_NL_MSG_CLEAN_FEATURE; + + return fwx_nl_send_msg_to_kernel(fwx_nl_fd.fd,(void *)&msg, sizeof(msg)); } -void af_load_config(af_config_t *config){ - memset(config, 0, sizeof(af_config_t)); - af_load_global_config(&config->global); - if (1 == af_load_time_config(&config->time)){ - apply_time_config_to_uci(&config->time); +int fwx_nl_add_feature(char *feature){ + char msg_buf[1024] = {0}; + if (fwx_nl_fd.fd < 0){ + return -1; + } + char *p_data = msg_buf + sizeof(fwx_nl_msg_t); + memset(msg_buf, 0, sizeof(msg_buf)); + + fwx_nl_msg_t *hdr = (fwx_nl_msg_t *)msg_buf; + hdr->action = FWX_NL_MSG_ADD_FEATURE; + strncpy(p_data, feature, strlen(feature)); + return fwx_nl_send_msg_to_kernel(fwx_nl_fd.fd,(void *)msg_buf, + sizeof(fwx_nl_msg_t) + strlen(feature) + 1); +} + +int fwx_nl_feature_load_done(void){ + fwx_nl_msg_t msg; + if (fwx_nl_fd.fd < 0){ + return -1; + } + msg.action = FWX_NL_MSG_FEATURE_LOAD_DONE; + return fwx_nl_send_msg_to_kernel(fwx_nl_fd.fd, (void *)&msg, sizeof(msg)); +} + + + +int fwx_load_feature_to_kernel(void){ + char line_buf[MAX_FEATURE_LINE_LEN] = {0}; + int feature_count = 0; + int custom_count; + size_t feature_len = 0; + size_t offset = 0; + const char *feature_data = fwx_feature_get_data(&feature_len); + + if (!feature_data || feature_len == 0) + return -1; + if (fwx_nl_clean_feature() < 0){ + LOG_ERROR("Failed to clean feature\n"); + return -1; + } + while (offset < feature_len) { + int line_ret = fwx_feature_next_line(feature_data, feature_len, &offset, + line_buf, sizeof(line_buf)); + if (line_ret < 0) { + LOG_ERROR("feature line too long\n"); + continue; + } + if (line_ret == 0) + break; + str_trim(line_buf); + if (strlen(line_buf) < 8) + continue; + if (strstr(line_buf, "#")) + continue; + + if (strlen(line_buf) >= MAX_FEATURE_LINE_LEN - 1){ + LOG_ERROR("feature line too long: %s\n", line_buf); + continue; + } + if (fwx_nl_add_feature(line_buf) < 0) { + LOG_ERROR("Failed to send feature to kernel\n"); + return -1; + } + feature_count++; } -} - - -void update_oaf_proc_value(char *key, char *value){ - char cmd_buf[128] = {0}; - char file_path[128] = {0}; - char old_value[128] = {0}; - sprintf(file_path, "/proc/sys/oaf/%s", key); - - if (af_read_file_value(file_path, old_value, sizeof(old_value)) == -1) - return; - if (strcmp(old_value, value) != 0){ - sprintf(cmd_buf, "echo %s >/proc/sys/oaf/%s", value, key); - system(cmd_buf); - LOG_DEBUG("update %s %s-->%s\n", key, old_value, value); + custom_count = fwx_custom_feature_send_to_kernel(fwx_nl_add_feature); + if (custom_count < 0) { + LOG_ERROR("Failed to send custom feature to kernel\n"); + return -1; } + feature_count += custom_count; + if (fwx_nl_feature_load_done() < 0){ + LOG_ERROR("Failed to notify feature load done\n"); + return -1; + } + LOG_INFO("load %d features to kernel\n", feature_count); + return 0; } -void update_oaf_proc_u32_value(char *key, u_int32_t value){ - char buf[32] = {0}; - sprintf(buf, "%u", value); - update_oaf_proc_value(key, buf); +int reload_feature(void){ + char *feature_data = NULL; + size_t feature_len = 0; + + if (fwx_feature_decrypt_file(FWX_FEATURE_BIN_PATH, &feature_data, &feature_len) < 0) { + LOG_ERROR("Failed to decrypt feature file\n"); + if (fwx_feature_restore_backup() < 0) { + LOG_ERROR("Failed to restore feature backup\n"); + return -1; + } + LOG_WARN("Restored feature file from backup\n"); + if (fwx_feature_decrypt_file(FWX_FEATURE_BIN_PATH, + &feature_data, &feature_len) < 0) { + LOG_ERROR("Failed to decrypt restored feature file\n"); + return -1; + } + } + fwx_feature_replace_data(feature_data, feature_len); + init_app_name_table(); + init_app_class_name_table(); + if (fwx_custom_feature_reload() < 0) + return -1; + if (fwx_custom_feature_add_app_names() < 0) { + LOG_ERROR("Failed to initialize custom application names\n"); + return -1; + } + if (fwx_load_feature_to_kernel() < 0){ + LOG_ERROR("Failed to load feature to kernel\n"); + return -1; + } + if (write_feature_info_file() < 0) + LOG_ERROR("Failed to write feature info file\n"); + LOG_WARN("reload feature success\n"); + return 0; } void update_lan_ip(void){ @@ -435,449 +294,246 @@ void update_lan_ip(void){ if (!ctx) return; - int ret = af_uci_get_value(ctx, "appfilter.global.lan_ifname", lan_ifname, sizeof(lan_ifname) - 1); + int ret = fwx_uci_get_value(ctx, "appfilter.global.lan_ifname", lan_ifname, sizeof(lan_ifname) - 1); if (ret != 0){ strcpy(lan_ifname, "br-lan"); } - sprintf(ip_cmd_buf, CMD_GET_LAN_IP_FMT , lan_ifname); + sprintf(ip_cmd_buf, CMD_GET_LAN_IP_FMT, lan_ifname); sprintf(mask_cmd_buf, CMD_GET_LAN_MASK_FMT , lan_ifname); exec_with_result_line(ip_cmd_buf, ip_str, sizeof(ip_str)); if (strlen(ip_str) < MIN_INET_ADDR_LEN){ - update_oaf_proc_u32_value("lan_ip", 0); + update_fwx_proc_u32_value("lan_ip", 0); } else{ inet_aton(ip_str, &addr); lan_ip = addr.s_addr; - update_oaf_proc_u32_value("lan_ip", lan_ip); + update_fwx_proc_u32_value("lan_ip", lan_ip); } exec_with_result_line(mask_cmd_buf, mask_str, sizeof(mask_str)); if (strlen(mask_str) < MIN_INET_ADDR_LEN){ - update_oaf_proc_u32_value("lan_mask", 0); + update_fwx_proc_u32_value("lan_mask", 0); } else{ inet_aton(mask_str, &mask_addr); lan_mask = mask_addr.s_addr; - update_oaf_proc_u32_value("lan_mask", lan_mask); + update_fwx_proc_u32_value("lan_mask", lan_mask); } uci_free_context(ctx); } - - -int af_check_time_manual(af_time_config_t *t_config) { - time_t now = time(NULL); - struct tm *current_time = localtime(&now); - int current_minutes = current_time->tm_hour * 60 + current_time->tm_min; - int current_wday = current_time->tm_wday; - - int i; - for (i = 0; i < t_config->time_num; i++) { - if (!t_config->time_list[i].days[current_wday]) { - printf("current day %d not in time[%d] days\n", current_wday, i); - continue; - } - - int start_minutes = t_config->time_list[i].start_time.hour * 60 + t_config->time_list[i].start_time.min; - int end_minutes = t_config->time_list[i].end_time.hour * 60 + t_config->time_list[i].end_time.min; - printf("check time: %02d:%02d-%02d:%02d\n", - t_config->time_list[i].start_time.hour, t_config->time_list[i].start_time.min, - t_config->time_list[i].end_time.hour, t_config->time_list[i].end_time.min); - - if (current_minutes >= start_minutes && current_minutes <= end_minutes) { - printf("current time in time list\n"); - g_af_status.match_time = 1; - return 1; - } - } - g_af_status.match_time = 0; - return 0; -} - -int af_check_time_dynamic(af_time_config_t *t_config) { - return g_af_status.filter; -} - - -int update_dynamic_used_time(af_time_config_t *t_config){ - if (t_config->time_mode != 1) - return -1; - time_t now = time(NULL); - struct tm *current_time = localtime(&now); - - if (!t_config->days[current_time->tm_wday]) { - LOG_DEBUG("current day not in configured days\n"); - af_init_time_status(); - return -1; - } - - - int current_minutes = current_time->tm_hour * 60 + current_time->tm_min; - - int start_minutes = t_config->seg_time.start_time.hour * 60 + t_config->seg_time.start_time.min; - int end_minutes = t_config->seg_time.end_time.hour * 60 + t_config->seg_time.end_time.min; - LOG_DEBUG("check seg_time: %02d:%02d-%02d:%02d\n", - t_config->seg_time.start_time.hour, t_config->seg_time.start_time.min, - t_config->seg_time.end_time.hour, t_config->seg_time.end_time.min); - if (!(current_minutes >= start_minutes && current_minutes <= end_minutes)) { - LOG_DEBUG("current time not in seg_time\n"); - af_init_time_status(); - return 0; - } - - g_af_status.match_time = 1; - if (g_af_status.filter == 1) { - g_af_status.deny_time++; - if (g_af_status.deny_time >= t_config->deny_time) { - g_af_status.filter = 0; - g_af_status.deny_time = 0; - } - } else { - g_af_status.allow_time++; - if (g_af_status.allow_time >= t_config->allow_time) { - g_af_status.filter = 1; - g_af_status.allow_time = 0; - } - } - return 0; -} - - -int af_check_time_period_limit(af_time_config_t *t_config) { - int total_active_time = 0; - int selected_user_count = 0; - int i; - - time_t now = time(NULL); - struct tm *current_time = localtime(&now); - int current_weekday = current_time->tm_wday; - int current_hour = current_time->tm_hour; - - LOG_DEBUG("check period limit mode: weekday=%d, hour=%d\n", current_weekday, current_hour); - - daily_limit_config_t *daily_limit = &t_config->daily_limit[current_weekday]; - - if (!daily_limit->enable) { - LOG_DEBUG("Time limit not enabled for weekday %d\n", current_weekday); - g_af_status.match_time = 0; - g_af_status.remain_time = 0; - g_af_status.used_time = 0; - g_af_status.period_blocked = 0; - return 0; - } - - int max_allowed_time = 0; - int is_morning = (current_hour < 12); - - if (is_morning) { - max_allowed_time = daily_limit->am_time; - LOG_DEBUG("Morning period: max_allowed_time=%d\n", max_allowed_time); - } else { - max_allowed_time = daily_limit->pm_time; - LOG_DEBUG("Afternoon period: max_allowed_time=%d\n", max_allowed_time); - } - - if (max_allowed_time <= 0) { - LOG_DEBUG("No time limit set for current period\n"); - g_af_status.match_time = 0; - g_af_status.remain_time = 0; - g_af_status.used_time = 0; - g_af_status.period_blocked = 0; - return 0; - } - - check_all_users_period_time(); - - for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) { - dev_node_t *node = dev_hash_table[i]; - while (node) { - if (node->is_selected) { - if (is_morning) { - total_active_time += node->today_am_active_time; - LOG_DEBUG("Selected user %s (online=%d): today_am_active_time=%d, total=%d\n", - node->mac, node->online, node->today_am_active_time, total_active_time); - } else { - total_active_time += node->today_pm_active_time; - LOG_DEBUG("Selected user %s (online=%d): today_pm_active_time=%d, total=%d\n", - node->mac, node->online, node->today_pm_active_time, total_active_time); - } - if (node->online) { - selected_user_count++; - } - } - node = node->next; - } - } - - g_af_status.used_time = total_active_time; - - int remain_time = max_allowed_time - total_active_time; - if (remain_time < 0) { - remain_time = 0; - } - g_af_status.remain_time = remain_time; - - LOG_DEBUG("Selected users count: %d, total_active_time=%d, max_allowed=%d, remain_time=%d\n", - selected_user_count, total_active_time, max_allowed_time, remain_time); - - if (total_active_time >= max_allowed_time) { - g_af_status.match_time = 1; - g_af_status.period_blocked = 1; - LOG_DEBUG("Period limit mode: enable filter (total time exceeded: %d >= %d)\n", - total_active_time, max_allowed_time); - return 1; - } else { - g_af_status.match_time = 1; - g_af_status.period_blocked = 0; - LOG_DEBUG("Period limit mode: disable filter (total time: %d < %d, remain: %d)\n", - total_active_time, max_allowed_time, remain_time); - return 0; - } -} - -int af_check_time_valid(af_time_config_t *t_config) { - time_t now = time(NULL); - struct tm *current_time = localtime(&now); - - if (t_config->time_mode == 0) { - return af_check_time_manual(t_config); - } else if (t_config->time_mode == 1) { - return af_check_time_dynamic(t_config); - } else if (t_config->time_mode == 2) { - return af_check_time_period_limit(t_config); - }else{ - return 0; - } -} - - -void update_oaf_status(void){ - int ret = 0; - int cur_enable = 0; - if(g_af_config.global.enable == 1){ - ret = af_check_time_valid(&g_af_config.time); - } - update_oaf_proc_value("enable", ret == 1 ? "1" : "0"); -} - -void update_oaf_record_status(void){ - update_oaf_proc_value("record_enable", g_af_config.global.record_enable==1?"1":"0"); -} - -void update_oaf_disable_quic_status(void){ - update_oaf_proc_value("disable_quic", g_af_config.global.disable_quic==1?"1":"0"); -} - -void update_oaf_app_filter_mode_status(void){ - update_oaf_proc_value("app_filter_mode", g_af_config.global.app_filter_mode==1?"1":"0"); -} - - -int af_nl_clean_feature(void){ - af_msg_t msg; - if (appfilter_nl_fd.fd < 0){ - return -1; - } - msg.action = AF_MSG_CLEAN_FEATURE; - - send_msg_to_kernel(appfilter_nl_fd.fd,(void *)&msg, sizeof(msg)); - return 0; -} - -int af_nl_add_feature(char *feature){ - char msg_buf[1024] = {0}; - if (appfilter_nl_fd.fd < 0){ - return -1; - } - char *p_data = msg_buf + sizeof(af_msg_t); - memset(msg_buf, 0, sizeof(msg_buf)); - - af_msg_t *hdr = (af_msg_t *)msg_buf; - hdr->action = AF_MSG_ADD_FEATURE; - strncpy(p_data, feature, strlen(feature)); - send_msg_to_kernel(appfilter_nl_fd.fd,(void *)msg_buf, sizeof(af_msg_t) + strlen(feature) + 1); - return 0; -} - - - -int af_load_feature_to_kernel(void){ - char line_buf[MAX_FEATURE_LINE_LEN] = {0}; - FILE *fp = fopen("/tmp/feature.cfg", "r"); - if (!fp) - { - printf("open file failed\n"); - return -1; - } - if (af_nl_clean_feature() < 0){ - return -1; - } - while (fgets(line_buf, sizeof(line_buf), fp)) - { - str_trim(line_buf); - if (strlen(line_buf) < 8) - continue; - if (strstr(line_buf, "#")) - continue; - - if (strlen(line_buf) >= MAX_FEATURE_LINE_LEN - 1){ - continue; - } - af_nl_add_feature(line_buf); - } - fclose(fp); - return 0; -} - -int reload_feature(void){ - system("gen_class.sh /tmp/feature.cfg"); - init_app_name_table(); - init_app_class_name_table(); - if (af_load_feature_to_kernel() < 0){ - LOG_ERROR("Failed to load feature to kernel\n"); - return -1; - } - clean_invalid_app_records(); - clear_device_app_statistics(); - LOG_WARN("reload feature success\n"); - g_feature_update_time = get_timestamp(); - return 0; -} - - -void check_date_change(void) -{ - static int last_day = -1; +void daily_archive_handle(void){ time_t now = time(NULL); struct tm *tm_info = localtime(&now); - int current_day = tm_info->tm_mday; - if (last_day != current_day ) - { - LOG_WARN("day changed: %d -> %d\n",last_day, current_day); - if (last_day != -1){ - clear_device_app_statistics(); - reset_all_users_today_active_time(); - reset_all_users_today_flow(); + if (tm_info) { + static int last_mday = -1; + int current_mday = tm_info->tm_mday; + LOG_INFO("current_mday: %d, last_mday: %d\n", current_mday, last_mday); + + if (last_mday != -1 && last_mday != current_mday) { + LOG_INFO("date changed, need to archive\n"); + + check_and_archive_all_clients(); } - last_day = current_day; + else{ + LOG_INFO("date not changed, no need to archive\n"); + } + + last_mday = current_mday; } } -void oaf_timeout_handler(struct uloop_timeout *t) + +void fwx_timeout_handler(struct uloop_timeout *t) { static int count = 0; + static u_int32_t last_check_date = 0; + u_int32_t current_time = time(NULL); + count++; if (count % 10 == 0){ - update_dev_list(); - update_oaf_status(); + update_client_list(); + move_expired_online_visit_to_offline(); } - if (count % 60 == 0){ - LOG_DEBUG("begin check dev count = %d\n", count); - check_dev_visit_info_expire(); - flush_expire_visit_info(); - update_dynamic_used_time(&g_af_config.time); - update_oaf_status(); + if (count % 20 == 0){ + daily_archive_handle(); update_lan_ip(); - if (check_dev_expire()){ - flush_dev_expire_node(); + if (check_client_expire()){ + flush_expire_client_node(); } - check_date_change(); - check_all_users_period_time(); - dump_dev_list(); + dump_client_list(); + cleanup_expired_hourly_stats(); + check_and_cleanup_history_data_by_size(); } - if (count % 300 == 0 && count > 0 && g_af_config.time.time_mode == 2){ - save_user_time_to_file(); + if (count % CLIENT_BACKUP_SYNC_INTERVAL_SEC == 0) { + LOG_INFO("begin save all client to files\n"); + save_all_client_backup_to_files(); } - if (g_oaf_config_change == 1){ - LOG_WARN("config changed\n"); - update_lan_ip(); - af_load_config(&g_af_config); - update_dev_selected_flag(); - update_dynamic_used_time(&g_af_config.time); - update_oaf_status(); - update_oaf_record_status(); - update_oaf_disable_quic_status(); - update_oaf_app_filter_mode_status(); - g_oaf_config_change = 0; + + if (count % 2 == 0) { + collect_interface_traffic_rate(); } - if (appfilter_nl_fd.fd < 0 && access("/proc/sys/oaf", F_OK) == 0){ - appfilter_nl_fd.fd = appfilter_nl_init(); - if (appfilter_nl_fd.fd > 0){ - uloop_fd_add(&appfilter_nl_fd, ULOOP_READ); - system("oaf_rule reload &"); + if (fwx_nl_fd.fd < 0){ + fwx_nl_fd.fd = fwx_netlink_init(); + if (fwx_nl_fd.fd > 0){ + uloop_fd_add(&fwx_nl_fd, ULOOP_READ); + + system("killall -9 rule_manager"); LOG_INFO("netlink connect success\n"); } } - if (g_feature_update == 1 && appfilter_nl_fd.fd > 0){ + if (g_feature_update == 1 && fwx_nl_fd.fd > 0){ if (0 == reload_feature()){ g_feature_update = 0; } } - count++; + if (count % 5 == 0){ + fwx_session_stat_tick(); + } + uloop_timeout_set(t, 1000); } -void af_load_engine(void){ - if (g_af_config.global.auto_load_engine == 1){ - if (access("/lib/modules/oaf.ko", F_OK) == 0) { - system("insmod /lib/modules/oaf.ko"); - LOG_WARN("insmod /lib/modules/oaf.ko"); - } else { - system("modprobe oaf"); - LOG_WARN("modprobe oaf"); +void init_system_config_to_proc(void) { + struct uci_context *ctx = uci_alloc_context(); + if (ctx) { + char lan_ifname[32] = {0}; + int ret = fwx_uci_get_value(ctx, "fwx.global.lan_ifname", lan_ifname, sizeof(lan_ifname) - 1); + if (ret != 0) { + strcpy(lan_ifname, "br-lan"); } - } - else{ - LOG_WARN("auto load disabled, not load oaf.ko\n"); + update_fwx_proc_value("lan_ifname", lan_ifname); + + int tcp_rst = fwx_uci_get_int_value(ctx, "fwx.global.tcp_rst"); + if (tcp_rst != 0 && tcp_rst != 1) { + tcp_rst = 1; + } + update_fwx_proc_u32_value("tcp_rst", tcp_rst); + + int work_mode = fwx_uci_get_int_value(ctx, "fwx.network.work_mode"); + if (work_mode < 0) { + work_mode = 0; + } + update_fwx_proc_u32_value("work_mode", work_mode); + uci_free_context(ctx); } } +void fwx_handle_sigusr1(int sig) { + char version[64] = {0}; + char format[32] = {0}; + int status_code = FEATURE_UPGRADE_FAILED; + int process_ret; -void handle_sigusr1(int sig) { - LOG_WARN("Received SIGUSR1 signal\n"); + (void)sig; + LOG_WARN("Received feature upgrade signal, candidate=%s\n", + FWX_FEATURE_CANDIDATE_PATH); + if (access(FWX_FEATURE_CANDIDATE_PATH, F_OK) != 0) { + LOG_ERROR("Feature candidate file does not exist: %s\n", + FWX_FEATURE_CANDIDATE_PATH); + if (write_feature_upgrade_status(FEATURE_UPGRADE_FAILED) < 0) + LOG_ERROR("Failed to write feature upgrade status: %d\n", + FEATURE_UPGRADE_FAILED); + return; + } + process_ret = fwx_feature_process_candidate(version, sizeof(version), + format, sizeof(format), + &status_code, NULL); + if (process_ret < 0) { + LOG_ERROR("Feature candidate processing failed, version=%s, format=%s, status=%d\n", + version[0] ? version : "--", format[0] ? format : "--", + status_code); + if (write_feature_upgrade_status(status_code) < 0) + LOG_ERROR("Failed to write feature upgrade status: %d\n", status_code); + return; + } + LOG_WARN("Feature candidate applied, target=%s, backup=%s\n", + FWX_FEATURE_BIN_PATH, FWX_FEATURE_BACKUP_PATH); g_feature_update = 1; + if (write_feature_upgrade_status(FEATURE_UPGRADE_SUCCESS) < 0) { + LOG_ERROR("Failed to write feature upgrade status: %d\n", + FEATURE_UPGRADE_SUCCESS); + return; + } + LOG_WARN("Feature candidate processing complete, status=%d\n", + FEATURE_UPGRADE_SUCCESS); } -void handle_sigusr2(int sig) { +void fwx_handle_sigusr2(int sig) { LOG_INFO("Received SIGUSR2 signal\n"); - if (current_log_level >= LOG_LEVEL_ERROR) - current_log_level = LOG_LEVEL_DEBUG; + if (current_log_level < LOG_LEVEL_DEBUG) + current_log_level++; else - current_log_level++; + current_log_level = LOG_LEVEL_WARN; + LOG_WARN("change log level to %d\n", current_log_level); } +void init_fwx_capability(void) { + g_fwx_capability.wireless_support = (access("/etc/config/wireless", F_OK) == 0) ? 1 : 0; + LOG_INFO("init capability: wireless_support=%d\n", g_fwx_capability.wireless_support); +} + +static void parse_fwxd_args(int argc, char **argv) +{ + int i = 0; + if (!argv || argc <= 1) + return; + + for (i = 1; i < argc; i++) { + if (!argv[i]) + continue; + + if (strcmp(argv[i], "--debug-mode") == 0 || strcmp(argv[i], "-d") == 0) { + g_fwxd_debug_mode = 1; + continue; + } + + LOG_WARN("Unknown argument ignored: %s\n", argv[i]); + } +} int main(int argc, char **argv) { - int ret = 0; - LOG_INFO("appfilter start"); + LOG_INFO("fwx start"); + parse_fwxd_args(argc, argv); g_feature_update = 1; - af_load_config(&g_af_config); - af_load_engine(); - af_init_status(); uloop_init(); - signal(SIGUSR1, handle_sigusr1); - signal(SIGUSR2, handle_sigusr2); + signal(SIGUSR1, fwx_handle_sigusr1); + signal(SIGUSR2, fwx_handle_sigusr2); signal(SIGCHLD, SIG_IGN); - init_dev_node_htable(); - - load_user_time_from_file(); - - if (appfilter_ubus_init() < 0) + init_client_list(); + load_app_valid_time_config(); + init_client_visit_db(); + load_client_backup_from_files(); + init_system_config_to_proc(); + init_fwx_capability(); + + if (fwx_feature_online_init() < 0) + LOG_ERROR("Failed to initialize online feature update\n"); + + if (fwx_ubus_init() < 0) { LOG_ERROR("Failed to connect to ubus\n"); return 1; } - appfilter_nl_fd.fd = -1; - uloop_timeout_set(&dev_tm, 5000); - uloop_timeout_add(&dev_tm); + + if (start_check_thread() < 0) { + LOG_ERROR("Failed to start check_thread\n"); + return 1; + } + + fwx_nl_fd.fd = -1; + uloop_timeout_set(&fwx_tm, 5000); + uloop_timeout_add(&fwx_tm); uloop_run(); + stop_check_thread(); + fwx_feature_online_cleanup(); uloop_done(); return 0; } diff --git a/open-app-filter/src/utils.c b/open-app-filter/src/utils.c deleted file mode 100644 index 52b420fe..00000000 --- a/open-app-filter/src/utils.c +++ /dev/null @@ -1,107 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -char *str_trim(char *s) { - char *start, *last, *bk; - int len; - - start = s; - while (isspace(*start)) - start++; - - bk = last = s + strlen(s) - 1; - while (last > start && isspace(*last)) - last--; - - if ((s != start) || (bk != last)) { - len = last - start + 1; - strncpy(s, start, len); - s[len] = '\0'; - } - return s; -} - -int exec_with_result_line(char *cmd, char *result, int len) -{ - FILE *fp = NULL; - if (!cmd || !result || !len) - return -1; - fp = popen(cmd, "r"); - if (!fp) - return -1; - fgets(result, len, fp); - str_trim(result); - pclose(fp); - return 0; -} -unsigned int get_timestamp(void) -{ - struct timeval cur_time; - gettimeofday(&cur_time, NULL); - return cur_time.tv_sec; -} - -int check_same_network(char *ip1, char *netmask, char *ip2) { - struct in_addr addr1, addr2, mask; - - if (inet_pton(AF_INET, ip1, &addr1) != 1) { - printf("Invalid IP address: %s\n", ip1); - return -1; - } - if (inet_pton(AF_INET, netmask, &mask) != 1) { - printf("Invalid netmask: %s\n", netmask); - return -1; - } - if (inet_pton(AF_INET, ip2, &addr2) != 1) { - printf("Invalid IP address: %s\n", ip2); - return -1; - } - - if ((addr1.s_addr & mask.s_addr) == (addr2.s_addr & mask.s_addr)) { - return 1; - } else { - return 0; - } -} - - -int af_read_file_value(const char *file_path, char *value, int value_len) { - FILE *file = fopen(file_path, "r"); - if (!file) { - //perror("Failed to open file"); - return -1; - } - - if (fgets(value, value_len, file) == NULL) { - perror("Failed to read line from file"); - fclose(file); - return -2; - } - - size_t len = strlen(value); - if (len > 0 && value[len - 1] == '\n') { - value[len - 1] = '\0'; - } - - fclose(file); - return 0; -} - -int af_read_file_int_value(const char *file_path, int *value) { - char line_buf[128] = {0}; - if (af_read_file_value(file_path, line_buf, sizeof(line_buf)) < 0){ - return -1; - } - *value = atoi(line_buf); - return 0; -} diff --git a/open-app-filter/src/utils.h b/open-app-filter/src/utils.h deleted file mode 100644 index c0174a43..00000000 --- a/open-app-filter/src/utils.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __UTILS_H__ -#define __UTILS_H__ -char *str_trim(char *s); -int exec_with_result_line(char *cmd, char *result, int len); -int check_same_network(char *ip1, char *netmask, char *ip2); -int af_read_file_value(const char *file_path, char *value, int value_len); -int af_read_file_int_value(const char *file_path, int *value); -unsigned int get_timestamp(void); -#endif \ No newline at end of file diff --git a/v2ray-geodata/Makefile b/v2ray-geodata/Makefile index ce72e3bc..10a48199 100644 --- a/v2ray-geodata/Makefile +++ b/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=c67bd077eb102cec74fab759b73d17f99275f56af10a87c14d9fd983508f5ce1 endef -GEOSITE_VER:=20260826043142 +GEOSITE_VER:=20260826065759 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=016e7351da8c97455fd75a90fe285a9dd5316ab4f048bfe98441a162ccfc5f86 + HASH:=2c2d0b45924d73c9ae65c5a41b81a2c2e28edef06ede619954dbe1df613d5a5c endef GEOSITE_IRAN_VER:=202608240025