"],_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 @@
-
-
-
-
-
-
-
-
<%:Settings saved successfully%>
-
-
-
-
-
-
-
-
- --
-
-
-
-
- --
-
-
-
-
-
-
-
-
- <%: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 %>
-
- <%: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%>
-
-
-
-
-
- <%:Current hardware acceleration module is not disabled, which may affect filtering functionality. For better filtering results, please disable the acceleration module in Advanced Settings.%>
-
-
-
-
-
-
- ⏱ <%:Today Time Usage%>
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:Basic Settings%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:In bypass mode, down rate cannot be measured, because data is directly forwarded from the gateway to the terminal.%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:Filter Rules%>
-
-
-
-
-
-
-
-
-
-
-
- <%:In this mode, after the rule takes effect, all apps will be unable to connect to the network, similar to scheduled internet disconnection%>
-
-
-
-
-
-
-
-
-
- <%: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.%>
-
- <%: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.%>
-
- <%:Dynamic time mode refers to dynamically adjusting app filter switches, such as allowing children to play games for 20 minutes after studying for 1 hour, automatically enabling filtering after exceeding entertainment time, and repeating the cycle.%>
-
-
-
-
-
-
- <%:minutes%>
-
-
-
-
- <%:minutes%>
-
-
-
-
-
- <%:Advanced Settings%>
-
-
-
-
-
<%:Mon%>
-
<%:Tue%>
-
<%:Wed%>
-
<%:Thur%>
-
<%:Fri%>
-
<%:Sat%>
-
<%:Sun%>
-
-
-
-
-
-
- -
-
-
-
- <%:This time range represents the daily school time period, which is a continuous time period. During this time period, filtering rules are dynamically enabled and disabled. Outside the time range, filtering is disabled by default.%>
-
-
-
-
-
-
-
-
-
- <%:Daily time limit mode allows you to set a daily internet time limit for each weekday. Once the limit is reached, internet access will be restricted.%>
-
-
-
-
-
-
- <%:Today Time Usage%>
-
-
-
-
-
-
-
-
-
-
-
-
<%:Weekday%>
-
<%:Enable%>
-
<%:Morning Time Limit (minutes)%>
-
<%:Afternoon Time Limit (minutes)%>
-
-
-
-
-
<%:Monday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Tuesday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Wednesday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Thursday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Friday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Saturday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Sunday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:Note: Set to 0 to disable time limit for that period.%>
-
-
-
-
-
-
-
-
-
-
-
\ 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%>
-
-
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%>
-
-
-
-
-
-
-
-
-
-
<%:Updating, please wait...%>
-
-
-
- <%: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.%>
+
<%:Bypass mode cannot count downstream traffic because traffic is directly forwarded to terminals at Layer 2 through the gateway.%>
+
<%:In bypass mode, it is recommended to disable IPv6 on terminals to prevent terminals from forwarding directly to the main router through IPv6.%>
+
+
+
+
+
+
+
+
<%: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.%>
+ <%: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.%>
+
<%: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.%>
+ <%: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