mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-10-29 19:35:13 +08:00
update 2026-08-26 20:40:17
This commit is contained in:
1 parent
d64e131165
commit
1bfa02b7c5
397 files changed
+52332
-16577
No files matched your search
@@ -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
|
||||
@@ -1,124 +0,0 @@
|
||||
#ifndef __APPFILTER_H__
|
||||
#define __APPFILTER_H__
|
||||
#define MIN_INET_ADDR_LEN 7
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#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
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2020 Derry <destan19@126.com>
|
||||
|
||||
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 <uci.h>
|
||||
|
||||
#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
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2020 Derry <destan19@126.com>
|
||||
|
||||
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 <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/socket.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/utils.h>
|
||||
#include <libubus.h>
|
||||
#include <json-c/json.h>
|
||||
#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;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2020 Derry <destan19@126.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
#ifndef __APPFILTER_NETLINK_H__
|
||||
#define __APPFILTER_NETLINK_H__
|
||||
#define DEFAULT_USR_NL_PID 999
|
||||
#define OAF_NETLINK_ID 29
|
||||
#define MAX_OAF_NETLINK_MSG_LEN 1024
|
||||
#define MAX_AF_MSG_DATA_LEN 800
|
||||
#define MAX_FEATURE_LINE_LEN 600
|
||||
|
||||
struct af_msg_hdr
|
||||
{
|
||||
int magic;
|
||||
int len;
|
||||
};
|
||||
|
||||
enum E_MSG_TYPE
|
||||
{
|
||||
AF_MSG_INIT,
|
||||
AF_MSG_ADD_FEATURE,
|
||||
AF_MSG_CLEAN_FEATURE,
|
||||
AF_MSG_MAX
|
||||
};
|
||||
|
||||
typedef struct af_msg
|
||||
{
|
||||
int action;
|
||||
} af_msg_t;
|
||||
|
||||
typedef struct af_feature_msg{
|
||||
af_msg_t hdr;
|
||||
char feature[MAX_FEATURE_LINE_LEN];
|
||||
} af_feature_msg_t;
|
||||
|
||||
int appfilter_nl_init(void);
|
||||
void appfilter_nl_handler(struct uloop_fd *u, unsigned int ev);
|
||||
int send_msg_to_kernel(int fd, void *msg, int len);
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2020 Derry <destan19@126.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
#ifndef __APPFILTER_UBUS_H__
|
||||
#define __APPFILTER_UBUS_H__
|
||||
int appfilter_ubus_init(void);
|
||||
#endif
|
||||
@@ -1,1000 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2020 Derry <destan19@126.com>
|
||||
|
||||
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 <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/utils.h>
|
||||
#include <libubus.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <json-c/json.h>
|
||||
#include <linux/socket.h>
|
||||
#include <sys/socket.h>
|
||||
#include "appfilter_config.h"
|
||||
#include "appfilter.h"
|
||||
#include "appfilter_user.h"
|
||||
|
||||
dev_node_t *dev_hash_table[MAX_DEV_NODE_HASH_SIZE];
|
||||
int g_cur_user_num = 0;
|
||||
unsigned int hash_mac(unsigned char *mac)
|
||||
{
|
||||
unsigned int hash = 0;
|
||||
int i;
|
||||
if (!mac)
|
||||
return 0;
|
||||
for (i = 0; mac[i] != '\0' && i < MAX_MAC_LEN; i++) {
|
||||
hash = hash * 31 + mac[i];
|
||||
}
|
||||
return hash & (MAX_DEV_NODE_HASH_SIZE - 1);
|
||||
}
|
||||
|
||||
int hash_appid(int appid)
|
||||
{
|
||||
return appid % (MAX_VISIT_HASH_SIZE - 1);
|
||||
}
|
||||
|
||||
void add_visit_info_node(visit_info_t **head, visit_info_t *node)
|
||||
{
|
||||
if (*head == NULL)
|
||||
{
|
||||
*head = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
node->next = *head;
|
||||
*head = node;
|
||||
}
|
||||
}
|
||||
|
||||
void init_dev_node_htable()
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_hash_table[i] = NULL;
|
||||
}
|
||||
printf("init dev node htable ok...\n");
|
||||
}
|
||||
|
||||
dev_node_t *add_dev_node(char *mac)
|
||||
{
|
||||
unsigned int hash = 0;
|
||||
hash = hash_mac(mac);
|
||||
if (hash >= MAX_DEV_NODE_HASH_SIZE)
|
||||
{
|
||||
printf("hash code error %d\n", hash);
|
||||
return NULL;
|
||||
}
|
||||
dev_node_t *node = (dev_node_t *)calloc(1, sizeof(dev_node_t));
|
||||
if (!node)
|
||||
return NULL;
|
||||
strncpy(node->mac, mac, sizeof(node->mac));
|
||||
node->online = 1;
|
||||
node->online_time = get_timestamp();
|
||||
if (dev_hash_table[hash] == NULL)
|
||||
dev_hash_table[hash] = node;
|
||||
else
|
||||
{
|
||||
node->next = dev_hash_table[hash];
|
||||
dev_hash_table[hash] = node;
|
||||
}
|
||||
g_cur_user_num++;
|
||||
printf("add mac:%s to htable[%d]....success\n", mac, hash);
|
||||
return node;
|
||||
}
|
||||
|
||||
dev_node_t *find_dev_node(char *mac)
|
||||
{
|
||||
unsigned int hash = 0;
|
||||
dev_node_t *p = NULL;
|
||||
hash = hash_mac(mac);
|
||||
if (hash >= MAX_DEV_NODE_HASH_SIZE)
|
||||
{
|
||||
printf("hash code error %d\n", hash);
|
||||
return NULL;
|
||||
}
|
||||
p = dev_hash_table[hash];
|
||||
while (p)
|
||||
{
|
||||
if (0 == strncmp(p->mac, mac, sizeof(p->mac)))
|
||||
{
|
||||
return p;
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void dev_foreach(void *arg, iter_func iter)
|
||||
{
|
||||
int i, j;
|
||||
dev_node_t *node = NULL;
|
||||
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
iter(arg, node);
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *format_time(int timetamp)
|
||||
{
|
||||
char time_buf[64] = {0};
|
||||
time_t seconds = timetamp;
|
||||
struct tm *auth_tm = localtime(&seconds);
|
||||
strftime(time_buf, sizeof(time_buf), "%Y %m %d %H:%M:%S", auth_tm);
|
||||
return strdup(time_buf);
|
||||
}
|
||||
|
||||
void update_dev_hostname(void)
|
||||
{
|
||||
char line_buf[256] = {0};
|
||||
char hostname_buf[128] = {0};
|
||||
char mac_buf[32] = {0};
|
||||
char ip_buf[32] = {0};
|
||||
|
||||
FILE *fp = fopen("/tmp/dhcp.leases", "r");
|
||||
if (!fp)
|
||||
{
|
||||
printf("open dhcp lease file....failed\n");
|
||||
return;
|
||||
}
|
||||
while (fgets(line_buf, sizeof(line_buf), fp))
|
||||
{
|
||||
if (strlen(line_buf) <= 16)
|
||||
continue;
|
||||
sscanf(line_buf, "%*s %s %s %s", mac_buf, ip_buf, hostname_buf);
|
||||
dev_node_t *node = find_dev_node(mac_buf);
|
||||
if (!node)
|
||||
{
|
||||
node = add_dev_node(mac_buf);
|
||||
if (!node)
|
||||
continue;
|
||||
strncpy(node->ip, ip_buf, sizeof(node->ip));
|
||||
node->online = 0;
|
||||
node->offline_time = get_timestamp();
|
||||
}
|
||||
|
||||
if (strlen(hostname_buf) > 0 && hostname_buf[0] != '*')
|
||||
{
|
||||
strncpy(node->hostname, hostname_buf, sizeof(node->hostname));
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void clean_dev_nickname_iter(void *arg, dev_node_t *node)
|
||||
{
|
||||
node->nickname[0] = '\0';
|
||||
}
|
||||
|
||||
void clean_dev_nickname(void)
|
||||
{
|
||||
dev_foreach(NULL, clean_dev_nickname_iter);
|
||||
}
|
||||
|
||||
void update_dev_nickname(void)
|
||||
{
|
||||
char nickname_buf[128] = {0};
|
||||
char mac_str[128] = {0};
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
clean_dev_nickname();
|
||||
int num = af_get_uci_list_num(uci_ctx, "user_info", "user_info");
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
af_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].mac", i, mac_str, sizeof(mac_str));
|
||||
dev_node_t *node = find_dev_node(mac_str);
|
||||
if (!node)
|
||||
continue;
|
||||
|
||||
af_uci_get_array_value(uci_ctx, "user_info.@user_info[%d].nickname", i, nickname_buf, sizeof(nickname_buf));
|
||||
strncpy(node->nickname, nickname_buf, sizeof(node->nickname));
|
||||
}
|
||||
uci_free_context(uci_ctx);
|
||||
}
|
||||
|
||||
|
||||
void clean_dev_whitelist_flag_iter(void *arg, dev_node_t *node)
|
||||
{
|
||||
node->is_whitelist = 0;
|
||||
}
|
||||
|
||||
void clean_dev_whitelist_flag(void)
|
||||
{
|
||||
dev_foreach(NULL, clean_dev_whitelist_flag_iter);
|
||||
}
|
||||
|
||||
|
||||
void update_dev_whitelist_flag(void)
|
||||
{
|
||||
clean_dev_whitelist_flag();
|
||||
dev_node_t *node = NULL;
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
return;
|
||||
}
|
||||
char mac_str[128] = {0};
|
||||
int num = af_get_uci_list_num(uci_ctx, "appfilter", "whitelist");
|
||||
for (int i = 0; i < num; i++) {
|
||||
af_uci_get_array_value(uci_ctx, "appfilter.@whitelist[%d].mac", i, mac_str, sizeof(mac_str));
|
||||
node = find_dev_node(mac_str);
|
||||
if (node) {
|
||||
node->is_whitelist = 1;
|
||||
}
|
||||
}
|
||||
uci_free_context(uci_ctx);
|
||||
}
|
||||
|
||||
void clean_dev_selected_flag_iter(void *arg, dev_node_t *node)
|
||||
{
|
||||
node->is_selected = 0;
|
||||
}
|
||||
|
||||
void clean_dev_selected_flag(void)
|
||||
{
|
||||
dev_foreach(NULL, clean_dev_selected_flag_iter);
|
||||
}
|
||||
|
||||
void update_dev_selected_flag(void)
|
||||
{
|
||||
extern af_config_t g_af_config;
|
||||
int user_mode = g_af_config.global.user_mode;
|
||||
|
||||
clean_dev_selected_flag();
|
||||
|
||||
if (user_mode == 0) {
|
||||
int i;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) {
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node) {
|
||||
if (!node->is_whitelist) {
|
||||
node->is_selected = 1;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("Auto mode: all users except whitelist are selected\n");
|
||||
} else {
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context in update_dev_selected_flag\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char mac_str[128] = {0};
|
||||
int num = af_get_uci_list_num(uci_ctx, "appfilter", "af_user");
|
||||
for (int i = 0; i < num; i++) {
|
||||
af_uci_get_array_value(uci_ctx, "appfilter.@af_user[%d].mac", i, mac_str, sizeof(mac_str));
|
||||
dev_node_t *node = find_dev_node(mac_str);
|
||||
if (node) {
|
||||
node->is_selected = 1;
|
||||
}
|
||||
}
|
||||
uci_free_context(uci_ctx);
|
||||
LOG_DEBUG("Manual mode: %d users from af_user config are selected\n", num);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void clean_dev_online_status(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
|
||||
if (node->online)
|
||||
{
|
||||
node->offline_time = get_timestamp();
|
||||
node->online = 0;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void update_dev_from_kernel(void)
|
||||
{
|
||||
char line_buf[256] = {0};
|
||||
char mac_buf[32] = {0};
|
||||
char ip_buf[32] = {0};
|
||||
char ipv6_buf[128] = {0};
|
||||
unsigned int up_rate = 0;
|
||||
unsigned int down_rate = 0;
|
||||
|
||||
FILE *fp = fopen("/proc/net/af_client", "r");
|
||||
if (!fp)
|
||||
{
|
||||
printf("open client file....failed\n");
|
||||
return;
|
||||
}
|
||||
fgets(line_buf, sizeof(line_buf), fp); // title
|
||||
while (fgets(line_buf, sizeof(line_buf), fp))
|
||||
{
|
||||
int id;
|
||||
int parsed = sscanf(line_buf, "%d %s %s %s %u %u", &id, mac_buf, ip_buf, ipv6_buf, &up_rate, &down_rate);
|
||||
if (parsed < 3)
|
||||
{
|
||||
printf("invalid line format:%s\n", line_buf);
|
||||
continue;
|
||||
}
|
||||
if (strlen(mac_buf) < 17)
|
||||
{
|
||||
printf("invalid mac:%s\n", mac_buf);
|
||||
continue;
|
||||
}
|
||||
dev_node_t *node = find_dev_node(mac_buf);
|
||||
if (!node)
|
||||
{
|
||||
node = add_dev_node(mac_buf);
|
||||
if (!node)
|
||||
continue;
|
||||
strncpy(node->ip, ip_buf, sizeof(node->ip));
|
||||
}
|
||||
|
||||
strncpy(node->ip, ip_buf, sizeof(node->ip));
|
||||
|
||||
if (parsed >= 4 && strlen(ipv6_buf) > 0)
|
||||
{
|
||||
strncpy(node->ipv6, ipv6_buf, sizeof(node->ipv6));
|
||||
}
|
||||
else
|
||||
{
|
||||
node->ipv6[0] = '\0';
|
||||
}
|
||||
|
||||
if (parsed >= 5)
|
||||
{
|
||||
node->up_rate = up_rate;
|
||||
}
|
||||
else
|
||||
{
|
||||
node->up_rate = 0;
|
||||
}
|
||||
if (parsed >= 6)
|
||||
{
|
||||
node->down_rate = down_rate;
|
||||
}
|
||||
else
|
||||
{
|
||||
node->down_rate = 0;
|
||||
}
|
||||
node->online = 1;
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
|
||||
void update_dev_online_status(void)
|
||||
{
|
||||
update_dev_from_kernel();
|
||||
}
|
||||
|
||||
#define DEV_OFFLINE_TIME (SECONDS_PER_DAY * 7)
|
||||
|
||||
int check_dev_expire(void)
|
||||
{
|
||||
int i, j;
|
||||
int count = 0;
|
||||
int cur_time = get_timestamp();
|
||||
int offline_time = 0;
|
||||
int expire_count = 0;
|
||||
int visit_count = 0;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
if (node->online)
|
||||
goto NEXT;
|
||||
visit_count = 0;
|
||||
offline_time = cur_time - node->offline_time;
|
||||
if (offline_time > DEV_OFFLINE_TIME)
|
||||
{
|
||||
node->expire = 1;
|
||||
for (j = 0; j < MAX_VISIT_HASH_SIZE; j++)
|
||||
{
|
||||
visit_info_t *p_info = node->visit_htable[j];
|
||||
while (p_info)
|
||||
{
|
||||
p_info->expire = 1;
|
||||
visit_count++;
|
||||
p_info = p_info->next;
|
||||
}
|
||||
}
|
||||
expire_count++;
|
||||
LOG_WARN("dev:%s expired, offline time = %ds, count=%d, visit_count=%d\n",
|
||||
node->mac, offline_time, expire_count, visit_count);
|
||||
}
|
||||
NEXT:
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
return expire_count;
|
||||
}
|
||||
|
||||
void flush_dev_expire_node(void)
|
||||
{
|
||||
int i, j;
|
||||
int count = 0;
|
||||
dev_node_t *node = NULL;
|
||||
dev_node_t *prev = NULL;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
prev = NULL;
|
||||
while (node)
|
||||
{
|
||||
if (node->expire)
|
||||
{
|
||||
if (NULL == prev)
|
||||
{
|
||||
dev_hash_table[i] = node->next;
|
||||
free(node);
|
||||
node = dev_hash_table[i];
|
||||
prev = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev->next = node->next;
|
||||
free(node);
|
||||
node = prev->next;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = node;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flush_offline_users(void)
|
||||
{
|
||||
int i, j;
|
||||
int count = 0;
|
||||
dev_node_t *node = NULL;
|
||||
dev_node_t *prev = NULL;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
prev = NULL;
|
||||
while (node)
|
||||
{
|
||||
if (!node->online) // Clear all offline users
|
||||
{
|
||||
// Free visit info first
|
||||
for (j = 0; j < MAX_VISIT_HASH_SIZE; j++)
|
||||
{
|
||||
visit_info_t *p_info = node->visit_htable[j];
|
||||
while (p_info)
|
||||
{
|
||||
visit_info_t *next = p_info->next;
|
||||
free(p_info);
|
||||
p_info = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove node from hash table
|
||||
if (NULL == prev)
|
||||
{
|
||||
dev_hash_table[i] = node->next;
|
||||
free(node);
|
||||
node = dev_hash_table[i];
|
||||
prev = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev->next = node->next;
|
||||
free(node);
|
||||
node = prev->next;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = node;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_WARN("Cleared %d offline users\n", count);
|
||||
}
|
||||
|
||||
void save_user_time_to_file(void)
|
||||
{
|
||||
int i;
|
||||
int count = 0;
|
||||
FILE *fp = fopen(OAF_USER_FILE, "w");
|
||||
if (!fp) {
|
||||
LOG_ERROR("Failed to open file %s for writing\n", OAF_USER_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "MAC,AM_Time,PM_Time\n");
|
||||
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) {
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node) {
|
||||
fprintf(fp, "%s,%u,%u\n",
|
||||
node->mac,
|
||||
node->today_am_active_time,
|
||||
node->today_pm_active_time);
|
||||
count++;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
LOG_DEBUG("Saved %d users' time data to %s\n", count, OAF_USER_FILE);
|
||||
}
|
||||
|
||||
void load_user_time_from_file(void)
|
||||
{
|
||||
FILE *fp = fopen(OAF_USER_FILE, "r");
|
||||
if (!fp) {
|
||||
LOG_DEBUG("File %s not found or cannot be opened, starting with empty data\n", OAF_USER_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
char line_buf[256] = {0};
|
||||
int count = 0;
|
||||
|
||||
if (fgets(line_buf, sizeof(line_buf), fp) == NULL) {
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
while (fgets(line_buf, sizeof(line_buf), fp)) {
|
||||
char mac[32] = {0};
|
||||
unsigned int am_time = 0;
|
||||
unsigned int pm_time = 0;
|
||||
|
||||
if (sscanf(line_buf, "%31[^,],%u,%u", mac, &am_time, &pm_time) == 3) {
|
||||
dev_node_t *node = find_dev_node(mac);
|
||||
if (!node) {
|
||||
// If node doesn't exist, create a new one
|
||||
node = add_dev_node(mac);
|
||||
if (!node) {
|
||||
LOG_WARN("Failed to create device node for MAC=%s\n", mac);
|
||||
continue;
|
||||
}
|
||||
LOG_DEBUG("Created new device node for MAC=%s\n", mac);
|
||||
}
|
||||
node->today_am_active_time = am_time;
|
||||
node->today_pm_active_time = pm_time;
|
||||
count++;
|
||||
LOG_DEBUG("Loaded user time: MAC=%s, AM=%u, PM=%u\n", mac, am_time, pm_time);
|
||||
} else {
|
||||
LOG_WARN("Failed to parse line: %s\n", line_buf);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
LOG_WARN("Loaded %d users' time data from %s\n", count, OAF_USER_FILE);
|
||||
}
|
||||
|
||||
void update_dev_visiting_info(void){
|
||||
char line_buf[256] = {0};
|
||||
char mac_buf[32] = {0};
|
||||
char url_buf[32] = {0};
|
||||
char app_buf[32] = {0};
|
||||
char time_buf[32] = {0};
|
||||
|
||||
FILE *fp = fopen("/proc/net/af_visit", "r");
|
||||
if (!fp)
|
||||
{
|
||||
printf("open af_visit file....failed\n");
|
||||
return;
|
||||
}
|
||||
fgets(line_buf, sizeof(line_buf), fp); // title
|
||||
while (fgets(line_buf, sizeof(line_buf), fp))
|
||||
{
|
||||
sscanf(line_buf, "%s %s %s", mac_buf, app_buf, url_buf);
|
||||
dev_node_t *node = find_dev_node(mac_buf);
|
||||
if (!node)
|
||||
continue;
|
||||
if (strcmp(url_buf, "none") == 0) {
|
||||
node->visiting_url[0] = '\0';
|
||||
}
|
||||
else {
|
||||
strncpy(node->visiting_url, url_buf, sizeof(node->visiting_url));
|
||||
}
|
||||
node->visiting_app = atoi(app_buf);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void update_dev_list(void)
|
||||
{
|
||||
clean_dev_online_status();
|
||||
update_dev_hostname();
|
||||
update_dev_nickname();
|
||||
update_dev_online_status();
|
||||
update_dev_visiting_info();
|
||||
update_dev_selected_flag();
|
||||
}
|
||||
|
||||
|
||||
void dump_dev_list(void)
|
||||
|
||||
{
|
||||
int i, j;
|
||||
int count = 0;
|
||||
char hostname_buf[MAX_HOSTNAME_SIZE] = {0};
|
||||
char ip_buf[MAX_IP_LEN] = {0};
|
||||
|
||||
FILE *fp = fopen(OAF_DEV_LIST_FILE, "w");
|
||||
if (!fp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
fprintf(fp, "%-4s %-20s %-20s %-32s %-8s\n", "Id", "Mac Addr", "Ip Addr", "Hostname", "Online");
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
if (node->online != 0)
|
||||
{
|
||||
if (strlen(node->hostname) == 0)
|
||||
strcpy(hostname_buf, "*");
|
||||
else
|
||||
strcpy(hostname_buf, node->hostname);
|
||||
if (strlen(node->ip) == 0)
|
||||
strcpy(ip_buf, "*");
|
||||
else
|
||||
strcpy(ip_buf, node->ip);
|
||||
fprintf(fp, "%-4d %-20s %-20s %-32s %-8d\n",
|
||||
i + 1, node->mac, ip_buf, hostname_buf, node->online);
|
||||
count++;
|
||||
}
|
||||
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
if (node->online == 0)
|
||||
{
|
||||
if (strlen(node->hostname) == 0)
|
||||
strcpy(hostname_buf, "*");
|
||||
else
|
||||
strcpy(hostname_buf, node->hostname);
|
||||
|
||||
if (strlen(node->ip) == 0)
|
||||
strcpy(ip_buf, "*");
|
||||
else
|
||||
strcpy(ip_buf, node->ip);
|
||||
|
||||
fprintf(fp, "%-4d %-20s %-20s %-32s %-8d\n",
|
||||
i + 1, node->mac, ip_buf, hostname_buf, node->online);
|
||||
}
|
||||
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
EXIT:
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
#define MAX_RECORD_TIME (1 * 24 * 60 * 60) // 1day
|
||||
#define RECORD_REMAIN_TIME (60 * 60) // 1hour
|
||||
#define INVALID_RECORD_TIME (5 * 60) // 5min
|
||||
void check_dev_visit_info_expire(void)
|
||||
{
|
||||
int i, j;
|
||||
int count = 0;
|
||||
int cur_time = get_timestamp();
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
for (j = 0; j < MAX_VISIT_HASH_SIZE; j++)
|
||||
{
|
||||
visit_info_t *p_info = node->visit_htable[j];
|
||||
while (p_info)
|
||||
{
|
||||
|
||||
int total_time = p_info->latest_time - p_info->first_time;
|
||||
int interval_time = cur_time - p_info->first_time;
|
||||
if (interval_time > MAX_RECORD_TIME || interval_time < 0)
|
||||
{
|
||||
p_info->expire = 1;
|
||||
}
|
||||
else if (interval_time > RECORD_REMAIN_TIME)
|
||||
{
|
||||
if (total_time < INVALID_RECORD_TIME)
|
||||
p_info->expire = 1;
|
||||
}
|
||||
LOG_DEBUG("[%s] appid:%d total_time:%ds interval:%ds, expire = %d\n", node->mac, p_info->appid, total_time, interval_time, p_info->expire);
|
||||
p_info = p_info->next;
|
||||
}
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flush_expire_visit_info(void)
|
||||
{
|
||||
int i, j;
|
||||
int count = 0;
|
||||
visit_info_t *prev = NULL;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
for (j = 0; j < MAX_VISIT_HASH_SIZE; j++)
|
||||
{
|
||||
visit_info_t *p_info = node->visit_htable[j];
|
||||
prev = NULL;
|
||||
while (p_info)
|
||||
{
|
||||
if (p_info->expire)
|
||||
{
|
||||
LOG_DEBUG("check expire,flush expire visit info: %s, appid=%d\n", node->mac, p_info->appid);
|
||||
if (NULL == prev)
|
||||
{
|
||||
node->visit_htable[j] = p_info->next;
|
||||
free(p_info);
|
||||
p_info = node->visit_htable[j];
|
||||
prev = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev->next = p_info->next;
|
||||
free(p_info);
|
||||
p_info = prev->next;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = p_info;
|
||||
p_info = p_info->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dump_dev_visit_list(void)
|
||||
{
|
||||
int i, j;
|
||||
int count = 0;
|
||||
FILE *fp = fopen(OAF_VISIT_LIST_FILE, "w");
|
||||
if (!fp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "%-4s %-20s %-20s %-8s %-32s %-32s %-32s %-8s\n", "Id", "Mac Addr",
|
||||
"Ip Addr", "Appid", "First Time", "Latest Time", "Total Time(s)", "Expire");
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
for (j = 0; j < MAX_VISIT_HASH_SIZE; j++)
|
||||
{
|
||||
visit_info_t *p_info = node->visit_htable[j];
|
||||
while (p_info)
|
||||
{
|
||||
char *first_time_str = format_time(p_info->first_time);
|
||||
char *latest_time_str = format_time(p_info->latest_time);
|
||||
int total_time = p_info->latest_time - p_info->first_time;
|
||||
fprintf(fp, "%-4d %-20s %-20s %-8d %-32s %-32s %-32d %-4d\n",
|
||||
count, node->mac, node->ip, p_info->appid, first_time_str,
|
||||
latest_time_str, total_time, p_info->expire);
|
||||
if (first_time_str)
|
||||
free(first_time_str);
|
||||
if (latest_time_str)
|
||||
free(latest_time_str);
|
||||
p_info = p_info->next;
|
||||
count++;
|
||||
if (count > 50)
|
||||
goto EXIT;
|
||||
}
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
EXIT:
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void clean_invalid_app_records(void)
|
||||
{
|
||||
int i, j;
|
||||
int invalid_count = 0;
|
||||
int total_count = 0;
|
||||
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
for (j = 0; j < MAX_VISIT_HASH_SIZE; j++)
|
||||
{
|
||||
visit_info_t *p_info = node->visit_htable[j];
|
||||
while (p_info)
|
||||
{
|
||||
total_count++;
|
||||
char *app_name = get_app_name_by_id(p_info->appid);
|
||||
if (app_name && strlen(app_name) == 0)
|
||||
{
|
||||
p_info->expire = 1;
|
||||
invalid_count++;
|
||||
LOG_DEBUG("clean: MAC=%s, AppID=%d\n", node->mac, p_info->appid);
|
||||
}
|
||||
p_info = p_info->next;
|
||||
}
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
if (invalid_count > 0)
|
||||
{
|
||||
flush_expire_visit_info();
|
||||
}
|
||||
}
|
||||
|
||||
void clear_device_app_statistics(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
memset(node->stat, 0, sizeof(node->stat));
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void check_and_reset_today_active_time(dev_node_t *node)
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm *tm_info = localtime(&now);
|
||||
int current_hour = tm_info->tm_hour;
|
||||
int current_min = tm_info->tm_min;
|
||||
|
||||
|
||||
static int last_reset_hour = -1;
|
||||
static int last_reset_min = -1;
|
||||
|
||||
if (current_hour == 12 && current_min == 0) {
|
||||
if (last_reset_hour != 12 || last_reset_min != 0) {
|
||||
LOG_DEBUG("Reset today_am_active_time for %s: %d -> 0 (12:00 reset)\n",
|
||||
node->mac, node->today_am_active_time);
|
||||
node->today_am_active_time = 0;
|
||||
last_reset_hour = 12;
|
||||
last_reset_min = 0;
|
||||
}
|
||||
} else {
|
||||
last_reset_hour = current_hour;
|
||||
last_reset_min = current_min;
|
||||
}
|
||||
}
|
||||
|
||||
void reset_all_users_today_active_time(void)
|
||||
{
|
||||
extern af_run_time_status_t g_af_status;
|
||||
int i;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) {
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node) {
|
||||
LOG_DEBUG("Reset today active time for %s: am=%d->0, pm=%d->0 (day changed)\n",
|
||||
node->mac, node->today_am_active_time, node->today_pm_active_time);
|
||||
node->today_am_active_time = 0;
|
||||
node->today_pm_active_time = 0;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
g_af_status.period_blocked = 0;
|
||||
}
|
||||
|
||||
|
||||
void reset_all_users_today_flow(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) {
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node) {
|
||||
|
||||
node->today_down_bytes = 0;
|
||||
node->today_up_bytes = 0;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void check_all_users_period_time(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++)
|
||||
{
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node)
|
||||
{
|
||||
check_and_reset_today_active_time(node);
|
||||
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void save_user_active_time_to_file(void)
|
||||
{
|
||||
FILE *fp = fopen(OAF_USER_FILE, "w");
|
||||
if (!fp) {
|
||||
LOG_ERROR("Failed to open file for writing: %s\n", OAF_USER_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "mac,today_am_active_time,today_pm_active_time\n");
|
||||
|
||||
int i;
|
||||
int count = 0;
|
||||
for (i = 0; i < MAX_DEV_NODE_HASH_SIZE; i++) {
|
||||
dev_node_t *node = dev_hash_table[i];
|
||||
while (node) {
|
||||
if (node->today_am_active_time > 0 || node->today_pm_active_time > 0) {
|
||||
fprintf(fp, "%s,%u,%u\n",
|
||||
node->mac,
|
||||
node->today_am_active_time,
|
||||
node->today_pm_active_time);
|
||||
count++;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
LOG_DEBUG("Saved %d users' active time to %s\n", count, OAF_USER_FILE);
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2020 Derry <destan19@126.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __FILTER_USER_H__
|
||||
#define __FILTER_USER_H__
|
||||
#include <sys/types.h>
|
||||
#define MAX_IP_LEN 32
|
||||
#define MAX_MAC_LEN 32
|
||||
#define MAX_VISIT_HASH_SIZE 64
|
||||
#define MAX_DEV_NODE_HASH_SIZE 64
|
||||
#define MAX_HOSTNAME_SIZE 64
|
||||
#define OAF_VISIT_LIST_FILE "/tmp/visit_list"
|
||||
#define OAF_DEV_LIST_FILE "/tmp/dev_list"
|
||||
#define OAF_USER_FILE "/etc/user_list.dat"
|
||||
#define MIN_VISIT_TIME 5 // default 5s
|
||||
#define MAX_APP_STAT_NUM 8
|
||||
#define MAX_VISITLIST_DUMP_NUM 16
|
||||
#define MAX_APP_TYPE 32
|
||||
#define MAX_APP_ID_NUM 512
|
||||
#define MAX_SUPPORT_DEV_NUM 256
|
||||
#define SECONDS_PER_DAY (24 * 3600)
|
||||
#define MAX_NICKNAME_SIZE 64
|
||||
#define MAX_REPORT_URL_LEN 64
|
||||
|
||||
|
||||
typedef struct visit_info
|
||||
{
|
||||
int appid;
|
||||
u_int32_t first_time;
|
||||
u_int32_t latest_time;
|
||||
int action;
|
||||
int expire;
|
||||
struct visit_info *next;
|
||||
} visit_info_t;
|
||||
|
||||
typedef struct visit_stat
|
||||
{
|
||||
u_int32_t total_time;
|
||||
} visit_stat_t;
|
||||
|
||||
typedef struct dev_node
|
||||
{
|
||||
char mac[MAX_MAC_LEN];
|
||||
char ip[MAX_IP_LEN];
|
||||
char ipv6[64];
|
||||
char hostname[MAX_HOSTNAME_SIZE];
|
||||
char nickname[MAX_NICKNAME_SIZE];
|
||||
int online;
|
||||
int expire;
|
||||
u_int32_t offline_time;
|
||||
u_int32_t online_time;
|
||||
visit_info_t *visit_htable[MAX_VISIT_HASH_SIZE];
|
||||
visit_stat_t stat[MAX_APP_TYPE][MAX_APP_ID_NUM]; // todo: list
|
||||
char visiting_url[MAX_REPORT_URL_LEN];
|
||||
int visiting_app;
|
||||
int is_whitelist;
|
||||
u_int32_t up_rate;
|
||||
u_int32_t down_rate;
|
||||
u_int64_t today_up_bytes;
|
||||
u_int64_t today_down_bytes;
|
||||
int active;
|
||||
u_int32_t today_am_active_time;
|
||||
u_int32_t today_pm_active_time;
|
||||
int is_selected;
|
||||
struct dev_node *next;
|
||||
} dev_node_t;
|
||||
|
||||
struct app_visit_info
|
||||
{
|
||||
int app_id;
|
||||
char app_name[32];
|
||||
int total_time;
|
||||
};
|
||||
|
||||
struct app_visit_stat_info
|
||||
{
|
||||
int num;
|
||||
struct app_visit_info visit_list[MAX_APP_STAT_NUM];
|
||||
};
|
||||
typedef void (*iter_func)(void *arg, dev_node_t *dev);
|
||||
//todo:dev for each
|
||||
extern dev_node_t *dev_hash_table[MAX_DEV_NODE_HASH_SIZE];
|
||||
|
||||
dev_node_t *add_dev_node(char *mac);
|
||||
void init_dev_node_htable();
|
||||
void dump_dev_list(void);
|
||||
void dump_dev_visit_list(void);
|
||||
dev_node_t *find_dev_node(char *mac);
|
||||
void dev_foreach(void *arg, iter_func iter);
|
||||
void add_visit_info_node(visit_info_t **head, visit_info_t *node);
|
||||
void check_dev_visit_info_expire(void);
|
||||
void flush_expire_visit_info();
|
||||
int check_dev_expire(void);
|
||||
void flush_dev_expire_node(void);
|
||||
void flush_expire_visit_info(void);
|
||||
void flush_offline_users(void);
|
||||
void save_user_time_to_file(void);
|
||||
void load_user_time_from_file(void);
|
||||
void update_dev_list(void);
|
||||
void update_dev_nickname(void);
|
||||
void update_dev_visiting_info(void);
|
||||
void update_dev_whitelist_flag(void);
|
||||
void update_dev_selected_flag(void);
|
||||
void clean_invalid_app_records(void);
|
||||
|
||||
void clear_device_app_statistics(void);
|
||||
|
||||
void check_and_reset_today_active_time(dev_node_t *node);
|
||||
void reset_all_users_today_active_time(void);
|
||||
|
||||
void reset_all_users_today_flow(void);
|
||||
|
||||
void check_all_users_period_time(void);
|
||||
void update_dev_online_status(void);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include "fwx.h"
|
||||
#include "fwx_utils.h"
|
||||
#include "check_main.h"
|
||||
|
||||
#define CHECK_INTERVAL 30
|
||||
#define LOG_DIR_PATH "/tmp/log"
|
||||
#define LOG_DIR_MAX_SIZE_KB 10240
|
||||
|
||||
static pthread_t check_thread;
|
||||
static int check_thread_running = 0;
|
||||
static int check_thread_exit = 0;
|
||||
|
||||
static void check_and_cleanup_log_dir(void) {
|
||||
char cmd_buf[256];
|
||||
char result_buf[64];
|
||||
memset(result_buf, 0, sizeof(result_buf));
|
||||
snprintf(cmd_buf, sizeof(cmd_buf), "du -sk %s 2>/dev/null | awk '{print $1}'", LOG_DIR_PATH);
|
||||
exec_with_result_line(cmd_buf, result_buf, sizeof(result_buf));
|
||||
if (result_buf[0] == '\0')
|
||||
return;
|
||||
int size_kb = atoi(result_buf);
|
||||
LOG_INFO("check_and_cleanup_log_dir: log dir size = %d KB\n", size_kb);
|
||||
if (size_kb <= LOG_DIR_MAX_SIZE_KB)
|
||||
return;
|
||||
snprintf(cmd_buf, sizeof(cmd_buf), "rm -rf %s/*", LOG_DIR_PATH);
|
||||
system(cmd_buf);
|
||||
}
|
||||
|
||||
static void* check_thread_func(void *arg) {
|
||||
LOG_DEBUG("check_thread: thread function started\n");
|
||||
|
||||
check_thread_running = 1;
|
||||
LOG_DEBUG("check_thread: running\n");
|
||||
|
||||
check_and_cleanup_log_dir();
|
||||
|
||||
while (!check_thread_exit) {
|
||||
sleep(CHECK_INTERVAL);
|
||||
|
||||
if (!check_thread_exit) {
|
||||
check_and_cleanup_log_dir();
|
||||
}
|
||||
}
|
||||
|
||||
check_thread_running = 0;
|
||||
LOG_DEBUG("check_thread: exited\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int start_check_thread(void) {
|
||||
int ret;
|
||||
|
||||
check_thread_exit = 0;
|
||||
check_thread_running = 0;
|
||||
|
||||
ret = pthread_create(&check_thread, NULL, check_thread_func, NULL);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("Failed to create check_thread: %s\n", strerror(ret));
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("check_thread: created\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void stop_check_thread(void) {
|
||||
if (!check_thread_running) {
|
||||
return;
|
||||
}
|
||||
check_thread_exit = 1;
|
||||
pthread_join(check_thread, NULL);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __CHECK_MAIN_H__
|
||||
#define __CHECK_MAIN_H__
|
||||
|
||||
|
||||
int start_check_thread(void);
|
||||
|
||||
|
||||
void stop_check_thread(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_H___
|
||||
#define __FWX_H___
|
||||
#define MIN_INET_ADDR_LEN 7
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#include <json-c/json.h>
|
||||
#include "fwx_uci.h"
|
||||
|
||||
#define API_CODE_SUCCESS 2000
|
||||
#define API_CODE_ERROR 4000
|
||||
#define LOG_FILE_PATH "/tmp/log/fwxd.log"
|
||||
|
||||
|
||||
typedef struct fwx_status {
|
||||
|
||||
int internet;
|
||||
} fwx_status_t;
|
||||
|
||||
extern fwx_status_t g_fwx_status;
|
||||
|
||||
typedef struct fwx_capability {
|
||||
int wireless_support;
|
||||
} fwx_capability_t;
|
||||
|
||||
extern fwx_capability_t g_fwx_capability;
|
||||
|
||||
typedef enum {
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARN,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_DEBUG
|
||||
} LogLevel;
|
||||
|
||||
extern int current_log_level;
|
||||
extern int g_fwxd_debug_mode;
|
||||
|
||||
static void af_log(LogLevel level, const char *func, int line, const char *format, ...){
|
||||
if (level > current_log_level)
|
||||
return;
|
||||
|
||||
FILE *log_file = fopen(LOG_FILE_PATH, "a");
|
||||
if (!log_file) {
|
||||
perror("Failed to open log file");
|
||||
return;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm *t = localtime(&now);
|
||||
char time_str[20];
|
||||
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", t);
|
||||
|
||||
const char *level_str;
|
||||
switch (level) {
|
||||
case LOG_LEVEL_DEBUG: level_str = "DEBUG"; break;
|
||||
case LOG_LEVEL_INFO: level_str = "INFO"; break;
|
||||
case LOG_LEVEL_WARN: level_str = "WARN"; break;
|
||||
case LOG_LEVEL_ERROR: level_str = "ERROR"; break;
|
||||
default: level_str = "UNKNOWN"; break;
|
||||
}
|
||||
|
||||
fprintf(log_file, "[%s] [%s] %s:%d ", time_str, level_str, func, line);
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(log_file, format, args);
|
||||
va_end(args);
|
||||
fprintf(log_file, "\n");
|
||||
fclose(log_file);
|
||||
}
|
||||
|
||||
#define LOG_DEBUG(format, ...) af_log(LOG_LEVEL_DEBUG, __func__, __LINE__, format, ##__VA_ARGS__)
|
||||
#define LOG_INFO(format, ...) af_log(LOG_LEVEL_INFO, __func__, __LINE__, format, ##__VA_ARGS__)
|
||||
#define LOG_WARN(format, ...) af_log(LOG_LEVEL_WARN, __func__, __LINE__, format, ##__VA_ARGS__)
|
||||
#define LOG_ERROR(format, ...) af_log(LOG_LEVEL_ERROR, __func__, __LINE__, format, ##__VA_ARGS__)
|
||||
|
||||
#define MAX_TIME_LIST_LEN 1024
|
||||
#define MAX_TIME_LIST 64
|
||||
typedef struct af_time
|
||||
{
|
||||
int hour;
|
||||
int min;
|
||||
} af_time_t;
|
||||
|
||||
|
||||
typedef struct time_config{
|
||||
af_time_t start_time;
|
||||
af_time_t end_time;
|
||||
}time_config_t;
|
||||
|
||||
typedef struct weekday_time_config{
|
||||
af_time_t start_time;
|
||||
af_time_t end_time;
|
||||
unsigned char weekday_map[7];
|
||||
}weekday_time_config_t;
|
||||
|
||||
typedef struct fwx_time_config_t{
|
||||
int time_mode;
|
||||
weekday_time_config_t seg_time;
|
||||
int deny_time;
|
||||
int allow_time;
|
||||
int time_num;
|
||||
weekday_time_config_t time_list[MAX_TIME_LIST];
|
||||
}fwx_time_config_t;
|
||||
|
||||
|
||||
typedef struct fwx_run_time_status{
|
||||
int deny_time;
|
||||
int allow_time;
|
||||
int filter;
|
||||
int match_time;
|
||||
int enable;
|
||||
}fwx_run_time_status_t;
|
||||
|
||||
|
||||
|
||||
void fwx_init_time_status(fwx_run_time_status_t *status);
|
||||
int fwx_check_time(fwx_time_config_t *t_config, fwx_run_time_status_t *status);
|
||||
struct json_object *fwx_gen_api_response_data(int code, struct json_object *data_obj);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,674 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/utils.h>
|
||||
#include <libubus.h>
|
||||
#include <uci.h>
|
||||
#include "fwx_user.h"
|
||||
#include "fwx_netlink.h"
|
||||
#include "fwx_ubus.h"
|
||||
#include "fwx_config.h"
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "fwx.h"
|
||||
#include <stdio.h>
|
||||
#include <json-c/json.h>
|
||||
|
||||
#include "fwx_utils.h"
|
||||
|
||||
|
||||
#define APPFILTER_RULES_STATE_FILE "/tmp/appfilter_rules_state"
|
||||
#define APPFILTER_WHITELIST_STATE_FILE "/tmp/appfilter_whitelist_state"
|
||||
|
||||
|
||||
static void set_state_file(const char *file_path) {
|
||||
FILE *fd = fopen(file_path, "w");
|
||||
if (fd) {
|
||||
fprintf(fd, "1");
|
||||
fclose(fd);
|
||||
LOG_DEBUG("Set state file: %s\n", file_path);
|
||||
} else {
|
||||
LOG_ERROR("Failed to set state file: %s\n", file_path);
|
||||
}
|
||||
}
|
||||
|
||||
fwx_run_time_status_t g_af_status;
|
||||
|
||||
void dev_list_timeout_handler(struct uloop_timeout *t);
|
||||
|
||||
|
||||
static int find_rule_index_by_id(struct uci_context *uci_ctx, int id) {
|
||||
char id_str_uci[32];
|
||||
int i;
|
||||
int num = fwx_uci_get_list_num(uci_ctx, "appfilter", "rule");
|
||||
for (i = 0; i < num; i++) {
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].id", i);
|
||||
if (fwx_uci_get_value(uci_ctx, buf, id_str_uci, sizeof(id_str_uci)) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (atoi(id_str_uci) == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
// Helper function to get option value from uci section
|
||||
static const char *get_option_value(struct uci_section *s, const char *option_name) {
|
||||
if (!s || !option_name) return NULL;
|
||||
struct uci_option *o = uci_lookup_option(s->package->ctx, s, option_name);
|
||||
if (!o || o->type != UCI_TYPE_STRING) {
|
||||
return NULL;
|
||||
}
|
||||
return o->v.string;
|
||||
}
|
||||
|
||||
static int normalize_app_id_token(const char *raw, char *out, int out_len) {
|
||||
int start = 0, end = 0, val = 0;
|
||||
char extra = '\0';
|
||||
if (!raw || !out || out_len <= 0) return 0;
|
||||
|
||||
if (sscanf(raw, "%d-%d%c", &start, &end, &extra) == 2) {
|
||||
if (start > 0 && end > 0 && start <= end) {
|
||||
snprintf(out, out_len, "%d-%d", start, end);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sscanf(raw, "%d%c", &val, &extra) == 1 && val > 0) {
|
||||
snprintf(out, out_len, "%d", val);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_filter_rules(struct json_object *req_obj) {
|
||||
struct json_object *data_obj = json_object_new_object();
|
||||
struct json_object *rules_array = json_object_new_array();
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
// Load appfilter package once
|
||||
struct uci_package *pkg = NULL;
|
||||
if (uci_load(uci_ctx, "appfilter", &pkg) != UCI_OK) {
|
||||
LOG_ERROR("Failed to load appfilter package\n");
|
||||
uci_free_context(uci_ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
// Traverse all sections using uci_foreach_element
|
||||
struct uci_element *e;
|
||||
uci_foreach_element(&pkg->sections, e) {
|
||||
struct uci_section *s = uci_to_section(e);
|
||||
|
||||
// Only process "rule" type sections
|
||||
if (strcmp(s->type, "rule") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Loading rule: %s\n", s->e.name);
|
||||
|
||||
// Get id - required field
|
||||
const char *id_str = get_option_value(s, "id");
|
||||
if (!id_str) {
|
||||
LOG_ERROR("Failed to get id for rule %s, skipping\n", s->e.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get other fields with defaults
|
||||
const char *name_str = get_option_value(s, "name");
|
||||
const char *mode_str = get_option_value(s, "mode");
|
||||
const char *user_mac_str = get_option_value(s, "user_mac");
|
||||
const char *user_name_str = get_option_value(s, "user_name");
|
||||
const char *enabled_str = get_option_value(s, "enabled");
|
||||
const char *filter_quic_str = get_option_value(s, "filter_quic");
|
||||
|
||||
struct json_object *rule_obj = json_object_new_object();
|
||||
if (!rule_obj) {
|
||||
LOG_ERROR("Failed to create rule_obj for rule %s\n", s->e.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object_object_add(rule_obj, "id", json_object_new_int(atoi(id_str)));
|
||||
json_object_object_add(rule_obj, "name", json_object_new_string(name_str ? name_str : ""));
|
||||
json_object_object_add(rule_obj, "mode", json_object_new_int(mode_str ? atoi(mode_str) : 1));
|
||||
json_object_object_add(rule_obj, "user_mac", json_object_new_string(user_mac_str ? user_mac_str : ""));
|
||||
json_object_object_add(rule_obj, "user_name", json_object_new_string(user_name_str ? user_name_str : ""));
|
||||
json_object_object_add(rule_obj, "enabled", json_object_new_int(enabled_str ? atoi(enabled_str) : 1));
|
||||
json_object_object_add(rule_obj, "filter_quic", json_object_new_int(filter_quic_str ? atoi(filter_quic_str) : 0));
|
||||
|
||||
// Process time_rule list
|
||||
struct json_object *time_rules_array = json_object_new_array();
|
||||
struct uci_option *time_rule_opt = uci_lookup_option(uci_ctx, s, "time_rule");
|
||||
if (time_rule_opt && time_rule_opt->type == UCI_TYPE_LIST) {
|
||||
struct uci_element *time_elem;
|
||||
uci_foreach_element(&time_rule_opt->v.list, time_elem) {
|
||||
const char *time_rule_str = time_elem->name;
|
||||
if (!time_rule_str) continue;
|
||||
|
||||
LOG_DEBUG("Loading time_rule: %s\n", time_rule_str);
|
||||
struct json_object *time_rule_obj = json_object_new_object();
|
||||
struct json_object *weekdays_array = json_object_new_array();
|
||||
|
||||
// Parse time_rule string: "weekday1,weekday2,...,start_time,end_time"
|
||||
char *time_rule_copy = strdup(time_rule_str);
|
||||
if (time_rule_copy) {
|
||||
char *saveptr;
|
||||
char *token = strtok_r(time_rule_copy, ",", &saveptr);
|
||||
char *start_time = NULL;
|
||||
char *end_time = NULL;
|
||||
|
||||
while (token) {
|
||||
if (strchr(token, ':') != NULL) {
|
||||
// This is a time string (HH:MM format)
|
||||
if (start_time == NULL) {
|
||||
start_time = token;
|
||||
} else if (end_time == NULL) {
|
||||
end_time = token;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// This is a weekday number
|
||||
int weekday = atoi(token);
|
||||
if (weekday >= 0 && weekday <= 6) {
|
||||
json_object_array_add(weekdays_array, json_object_new_int(weekday));
|
||||
}
|
||||
}
|
||||
token = strtok_r(NULL, ",", &saveptr);
|
||||
}
|
||||
|
||||
if (start_time) {
|
||||
json_object_object_add(time_rule_obj, "start_time", json_object_new_string(start_time));
|
||||
}
|
||||
if (end_time) {
|
||||
json_object_object_add(time_rule_obj, "end_time", json_object_new_string(end_time));
|
||||
}
|
||||
free(time_rule_copy);
|
||||
}
|
||||
|
||||
json_object_object_add(time_rule_obj, "weekdays", weekdays_array);
|
||||
json_object_array_add(time_rules_array, time_rule_obj);
|
||||
}
|
||||
}
|
||||
json_object_object_add(rule_obj, "time_rules", time_rules_array);
|
||||
|
||||
// Process app_id list(支持字符串段格式:1001-1005)
|
||||
struct json_object *app_ids_array = json_object_new_array();
|
||||
struct uci_option *app_id_opt = uci_lookup_option(uci_ctx, s, "app_id");
|
||||
if (app_id_opt && app_id_opt->type == UCI_TYPE_LIST) {
|
||||
struct uci_element *app_elem;
|
||||
uci_foreach_element(&app_id_opt->v.list, app_elem) {
|
||||
const char *app_id_str = app_elem->name;
|
||||
if (!app_id_str) continue;
|
||||
json_object_array_add(app_ids_array, json_object_new_string(app_id_str));
|
||||
}
|
||||
}
|
||||
json_object_object_add(rule_obj, "app_ids", app_ids_array);
|
||||
|
||||
json_object_array_add(rules_array, rule_obj);
|
||||
LOG_DEBUG("Successfully loaded rule: id=%s, name=%s\n", id_str, name_str ? name_str : "");
|
||||
}
|
||||
|
||||
// Unload package
|
||||
uci_unload(uci_ctx, pkg);
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
json_object_object_add(data_obj, "list", rules_array);
|
||||
|
||||
LOG_DEBUG("Returning %d rules\n", json_object_array_length(rules_array));
|
||||
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
|
||||
struct json_object *fwx_api_add_filter_rule(struct json_object *req_obj) {
|
||||
struct json_object *name_obj = json_object_object_get(req_obj, "name");
|
||||
struct json_object *mode_obj = json_object_object_get(req_obj, "mode");
|
||||
struct json_object *user_mac_obj = json_object_object_get(req_obj, "user_mac");
|
||||
struct json_object *user_name_obj = json_object_object_get(req_obj, "user_name");
|
||||
struct json_object *enabled_obj = json_object_object_get(req_obj, "enabled");
|
||||
struct json_object *filter_quic_obj = json_object_object_get(req_obj, "filter_quic");
|
||||
struct json_object *time_rules_obj = json_object_object_get(req_obj, "time_rules");
|
||||
struct json_object *app_ids_obj = json_object_object_get(req_obj, "app_ids");
|
||||
int i, j;
|
||||
if (!name_obj || !mode_obj || !time_rules_obj || !app_ids_obj) {
|
||||
LOG_ERROR("Missing required fields\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
|
||||
int rule_id = (int)time(NULL);
|
||||
|
||||
|
||||
fwx_uci_add_section(uci_ctx, "appfilter", "rule");
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].id");
|
||||
char id_str[32];
|
||||
snprintf(id_str, sizeof(id_str), "%d", rule_id);
|
||||
fwx_uci_set_value(uci_ctx, buf, id_str);
|
||||
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].name");
|
||||
fwx_uci_set_value(uci_ctx, buf, json_object_get_string(name_obj));
|
||||
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].mode");
|
||||
char mode_str[16];
|
||||
snprintf(mode_str, sizeof(mode_str), "%d", json_object_get_int(mode_obj));
|
||||
fwx_uci_set_value(uci_ctx, buf, mode_str);
|
||||
|
||||
if (user_mac_obj) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].user_mac");
|
||||
fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_mac_obj));
|
||||
}
|
||||
|
||||
if (user_name_obj) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].user_name");
|
||||
fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_name_obj));
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].enabled");
|
||||
char enabled_str[16];
|
||||
int enabled = enabled_obj ? json_object_get_int(enabled_obj) : 1;
|
||||
snprintf(enabled_str, sizeof(enabled_str), "%d", enabled);
|
||||
fwx_uci_set_value(uci_ctx, buf, enabled_str);
|
||||
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].filter_quic");
|
||||
{
|
||||
char filter_quic_str[16];
|
||||
int filter_quic = filter_quic_obj ? json_object_get_int(filter_quic_obj) : 0;
|
||||
snprintf(filter_quic_str, sizeof(filter_quic_str), "%d", filter_quic ? 1 : 0);
|
||||
fwx_uci_set_value(uci_ctx, buf, filter_quic_str);
|
||||
}
|
||||
|
||||
|
||||
int time_rules_len = json_object_array_length(time_rules_obj);
|
||||
for (i = 0; i < time_rules_len; i++) {
|
||||
struct json_object *time_rule_obj = json_object_array_get_idx(time_rules_obj, i);
|
||||
struct json_object *weekdays_obj = json_object_object_get(time_rule_obj, "weekdays");
|
||||
struct json_object *start_time_obj = json_object_object_get(time_rule_obj, "start_time");
|
||||
struct json_object *end_time_obj = json_object_object_get(time_rule_obj, "end_time");
|
||||
|
||||
if (!weekdays_obj || !start_time_obj || !end_time_obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
char time_rule_str[256] = {0};
|
||||
int weekdays_len = json_object_array_length(weekdays_obj);
|
||||
for (j = 0; j < weekdays_len; j++) {
|
||||
struct json_object *weekday_obj = json_object_array_get_idx(weekdays_obj, j);
|
||||
char weekday_str[16];
|
||||
snprintf(weekday_str, sizeof(weekday_str), "%d", json_object_get_int(weekday_obj));
|
||||
if (j > 0) strcat(time_rule_str, ",");
|
||||
strcat(time_rule_str, weekday_str);
|
||||
}
|
||||
strcat(time_rule_str, ",");
|
||||
strcat(time_rule_str, json_object_get_string(start_time_obj));
|
||||
strcat(time_rule_str, ",");
|
||||
strcat(time_rule_str, json_object_get_string(end_time_obj));
|
||||
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].time_rule");
|
||||
fwx_uci_add_list(uci_ctx, buf, time_rule_str);
|
||||
}
|
||||
|
||||
|
||||
int app_ids_len = json_object_array_length(app_ids_obj);
|
||||
for (i = 0; i < app_ids_len; i++) {
|
||||
struct json_object *app_id_obj = json_object_array_get_idx(app_ids_obj, i);
|
||||
const char *raw = json_object_get_string(app_id_obj);
|
||||
char app_id_str[64] = {0};
|
||||
if (normalize_app_id_token(raw, app_id_str, sizeof(app_id_str))) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[-1].app_id");
|
||||
fwx_uci_add_list(uci_ctx, buf, app_id_str);
|
||||
}
|
||||
}
|
||||
|
||||
fwx_uci_commit(uci_ctx, "appfilter");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
|
||||
set_state_file(APPFILTER_RULES_STATE_FILE);
|
||||
|
||||
LOG_DEBUG("Added filter rule: id=%d, name=%s\n", rule_id, json_object_get_string(name_obj));
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
|
||||
struct json_object *fwx_api_update_filter_rule(struct json_object *req_obj) {
|
||||
struct json_object *id_obj = json_object_object_get(req_obj, "id");
|
||||
int i, j;
|
||||
if (!id_obj) {
|
||||
LOG_ERROR("Missing id field\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
int rule_id = json_object_get_int(id_obj);
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
int index = find_rule_index_by_id(uci_ctx, rule_id);
|
||||
if (index < 0) {
|
||||
LOG_ERROR("Rule not found: id=%d\n", rule_id);
|
||||
uci_free_context(uci_ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
char buf[256];
|
||||
|
||||
|
||||
struct json_object *name_obj = json_object_object_get(req_obj, "name");
|
||||
if (name_obj) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].name", index);
|
||||
fwx_uci_set_value(uci_ctx, buf, json_object_get_string(name_obj));
|
||||
}
|
||||
|
||||
struct json_object *mode_obj = json_object_object_get(req_obj, "mode");
|
||||
if (mode_obj) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].mode", index);
|
||||
char mode_str[16];
|
||||
snprintf(mode_str, sizeof(mode_str), "%d", json_object_get_int(mode_obj));
|
||||
fwx_uci_set_value(uci_ctx, buf, mode_str);
|
||||
}
|
||||
|
||||
struct json_object *user_mac_obj = json_object_object_get(req_obj, "user_mac");
|
||||
if (user_mac_obj) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].user_mac", index);
|
||||
fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_mac_obj));
|
||||
}
|
||||
|
||||
struct json_object *user_name_obj = json_object_object_get(req_obj, "user_name");
|
||||
if (user_name_obj) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].user_name", index);
|
||||
fwx_uci_set_value(uci_ctx, buf, json_object_get_string(user_name_obj));
|
||||
}
|
||||
|
||||
struct json_object *enabled_obj = json_object_object_get(req_obj, "enabled");
|
||||
if (enabled_obj) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].enabled", index);
|
||||
char enabled_str[16];
|
||||
snprintf(enabled_str, sizeof(enabled_str), "%d", json_object_get_int(enabled_obj));
|
||||
fwx_uci_set_value(uci_ctx, buf, enabled_str);
|
||||
}
|
||||
|
||||
struct json_object *filter_quic_obj = json_object_object_get(req_obj, "filter_quic");
|
||||
if (filter_quic_obj) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].filter_quic", index);
|
||||
char filter_quic_str[16];
|
||||
snprintf(filter_quic_str, sizeof(filter_quic_str), "%d", json_object_get_int(filter_quic_obj) ? 1 : 0);
|
||||
fwx_uci_set_value(uci_ctx, buf, filter_quic_str);
|
||||
}
|
||||
|
||||
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].time_rule", index);
|
||||
fwx_uci_delete(uci_ctx, buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].app_id", index);
|
||||
fwx_uci_delete(uci_ctx, buf);
|
||||
|
||||
|
||||
struct json_object *time_rules_obj = json_object_object_get(req_obj, "time_rules");
|
||||
if (time_rules_obj) {
|
||||
int time_rules_len = json_object_array_length(time_rules_obj);
|
||||
for (i = 0; i < time_rules_len; i++) {
|
||||
struct json_object *time_rule_obj = json_object_array_get_idx(time_rules_obj, i);
|
||||
struct json_object *weekdays_obj = json_object_object_get(time_rule_obj, "weekdays");
|
||||
struct json_object *start_time_obj = json_object_object_get(time_rule_obj, "start_time");
|
||||
struct json_object *end_time_obj = json_object_object_get(time_rule_obj, "end_time");
|
||||
|
||||
if (!weekdays_obj || !start_time_obj || !end_time_obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char time_rule_str[256] = {0};
|
||||
int weekdays_len = json_object_array_length(weekdays_obj);
|
||||
for (j = 0; j < weekdays_len; j++) {
|
||||
struct json_object *weekday_obj = json_object_array_get_idx(weekdays_obj, j);
|
||||
char weekday_str[16];
|
||||
snprintf(weekday_str, sizeof(weekday_str), "%d", json_object_get_int(weekday_obj));
|
||||
if (j > 0) strcat(time_rule_str, ",");
|
||||
strcat(time_rule_str, weekday_str);
|
||||
}
|
||||
strcat(time_rule_str, ",");
|
||||
strcat(time_rule_str, json_object_get_string(start_time_obj));
|
||||
strcat(time_rule_str, ",");
|
||||
strcat(time_rule_str, json_object_get_string(end_time_obj));
|
||||
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].time_rule", index);
|
||||
fwx_uci_add_list(uci_ctx, buf, time_rule_str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct json_object *app_ids_obj = json_object_object_get(req_obj, "app_ids");
|
||||
if (app_ids_obj) {
|
||||
int app_ids_len = json_object_array_length(app_ids_obj);
|
||||
for (i = 0; i < app_ids_len; i++) {
|
||||
struct json_object *app_id_obj = json_object_array_get_idx(app_ids_obj, i);
|
||||
const char *raw = json_object_get_string(app_id_obj);
|
||||
char app_id_str[64] = {0};
|
||||
if (normalize_app_id_token(raw, app_id_str, sizeof(app_id_str))) {
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d].app_id", index);
|
||||
fwx_uci_add_list(uci_ctx, buf, app_id_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fwx_uci_commit(uci_ctx, "appfilter");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
|
||||
set_state_file(APPFILTER_RULES_STATE_FILE);
|
||||
|
||||
LOG_DEBUG("Updated filter rule: id=%d\n", rule_id);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
|
||||
struct json_object *fwx_api_delete_filter_rule(struct json_object *req_obj) {
|
||||
struct json_object *id_obj = json_object_object_get(req_obj, "id");
|
||||
if (!id_obj) {
|
||||
LOG_ERROR("Missing id field\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
int rule_id = json_object_get_int(id_obj);
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
int index = find_rule_index_by_id(uci_ctx, rule_id);
|
||||
if (index < 0) {
|
||||
LOG_ERROR("Rule not found: id=%d\n", rule_id);
|
||||
uci_free_context(uci_ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "appfilter.@rule[%d]", index);
|
||||
fwx_uci_delete(uci_ctx, buf);
|
||||
|
||||
fwx_uci_commit(uci_ctx, "appfilter");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
|
||||
set_state_file(APPFILTER_RULES_STATE_FILE);
|
||||
|
||||
LOG_DEBUG("Deleted filter rule: id=%d\n", rule_id);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_appfilter_whitelist(struct json_object *req_obj){
|
||||
|
||||
int i;
|
||||
struct json_object *data_obj = json_object_new_object();
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
struct json_object *mac_array = json_object_new_array();
|
||||
char mac_str[128] = {0};
|
||||
int num = fwx_uci_get_list_num(uci_ctx, "appfilter_whitelist", "whitelist_mac");
|
||||
for (i = 0; i < num; i++) {
|
||||
fwx_uci_get_array_value(uci_ctx, "appfilter_whitelist.@whitelist_mac[%d].mac", i, mac_str, sizeof(mac_str));
|
||||
|
||||
struct json_object *mac_obj = json_object_new_object();
|
||||
json_object_object_add(mac_obj, "mac", json_object_new_string(mac_str));
|
||||
client_node_t *dev = find_client_node(mac_str);
|
||||
if (dev){
|
||||
json_object_object_add(mac_obj, "nickname", json_object_new_string(dev->nickname));
|
||||
json_object_object_add(mac_obj, "hostname", json_object_new_string(dev->hostname));
|
||||
}else{
|
||||
json_object_object_add(mac_obj, "nickname", json_object_new_string(""));
|
||||
json_object_object_add(mac_obj, "hostname", json_object_new_string(""));
|
||||
}
|
||||
json_object_array_add(mac_array, mac_obj);
|
||||
}
|
||||
|
||||
json_object_object_add(data_obj, "list", mac_array);
|
||||
uci_free_context(uci_ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_del_appfilter_whitelist(struct json_object *req_obj){
|
||||
int i;
|
||||
LOG_DEBUG("fwx_api_del_appfilter_whitelist\n");
|
||||
struct json_object *mac_obj = json_object_object_get(req_obj, "mac");
|
||||
if (!mac_obj) {
|
||||
LOG_ERROR("mac_obj is NULL\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
LOG_DEBUG("mac: %s\n", json_object_get_string(mac_obj));
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
char mac_str[128] = {0};
|
||||
int num = fwx_uci_get_list_num(uci_ctx, "appfilter_whitelist", "whitelist_mac");
|
||||
for (i = 0; i < num; i++) {
|
||||
fwx_uci_get_array_value(uci_ctx, "appfilter_whitelist.@whitelist_mac[%d].mac", i, mac_str, sizeof(mac_str));
|
||||
if (strcmp(mac_str, json_object_get_string(mac_obj)) == 0) {
|
||||
LOG_DEBUG("delete appfilter_whitelist_mac[%d]\n", i);
|
||||
char buf[128] = {0};
|
||||
sprintf(buf, "appfilter_whitelist.@whitelist_mac[%d]", i);
|
||||
fwx_uci_delete(uci_ctx, buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fwx_uci_commit(uci_ctx, "appfilter_whitelist");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
|
||||
set_state_file(APPFILTER_WHITELIST_STATE_FILE);
|
||||
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_add_appfilter_whitelist(struct json_object *req_obj){
|
||||
int i;
|
||||
LOG_DEBUG("fwx_api_add_appfilter_whitelist\n");
|
||||
struct json_object *mac_array = json_object_object_get(req_obj, "mac_list");
|
||||
if (!mac_array) {
|
||||
LOG_ERROR("mac_list not found\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
int len = json_object_array_length(mac_array);
|
||||
for (i = 0; i < len; i++) {
|
||||
struct json_object *mac_obj = json_object_array_get_idx(mac_array, i);
|
||||
fwx_uci_add_section(uci_ctx, "appfilter_whitelist", "whitelist_mac");
|
||||
fwx_uci_set_value(uci_ctx, "appfilter_whitelist.@whitelist_mac[-1].mac", json_object_get_string(mac_obj));
|
||||
}
|
||||
fwx_uci_commit(uci_ctx, "appfilter_whitelist");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
|
||||
set_state_file(APPFILTER_WHITELIST_STATE_FILE);
|
||||
|
||||
LOG_DEBUG("Added %d MAC addresses to appfilter whitelist\n", len);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_app_filter_adv(struct json_object *req_obj) {
|
||||
struct json_object *data_obj = json_object_new_object();
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
json_object_put(data_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
int enable = fwx_uci_get_int_value(uci_ctx, "fwx.appfilter.enable");
|
||||
json_object_object_add(data_obj, "enable", json_object_new_int(enable));
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
|
||||
struct json_object *fwx_api_set_app_filter_adv(struct json_object *req_obj) {
|
||||
if (!req_obj) {
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
struct json_object *enable_obj = json_object_object_get(req_obj, "enable");
|
||||
if (!enable_obj) {
|
||||
LOG_ERROR("Missing enable parameter\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
int enable_value = json_object_get_int(enable_obj);
|
||||
fwx_uci_set_int_value(uci_ctx, "fwx.appfilter.enable", enable_value);
|
||||
fwx_uci_commit(uci_ctx, "fwx");
|
||||
|
||||
set_state_file(APPFILTER_RULES_STATE_FILE);
|
||||
uci_free_context(uci_ctx);
|
||||
LOG_DEBUG("Set appfilter advanced settings\n");
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_APP_FILTER_H__
|
||||
#define __FWX_APP_FILTER_H__
|
||||
|
||||
#include "fwx.h"
|
||||
|
||||
int fwx_app_filter_init(void);
|
||||
void update_oaf_status(void);
|
||||
|
||||
struct json_object *fwx_api_get_filter_rules(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_add_filter_rule(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_update_filter_rule(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_delete_filter_rule(struct json_object *req_obj);
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_appfilter_whitelist(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_del_appfilter_whitelist(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_add_appfilter_whitelist(struct json_object *req_obj);
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_app_filter_base(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_app_filter_base(struct json_object *req_obj);
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_app_filter_adv(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_app_filter_adv(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,133 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/utils.h>
|
||||
#include <libubus.h>
|
||||
#include "fwx_user.h"
|
||||
#include "fwx_netlink.h"
|
||||
#include "fwx_ubus.h"
|
||||
#include "fwx_config.h"
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "fwx.h"
|
||||
#include <stdio.h>
|
||||
#include "fwx_utils.h"
|
||||
|
||||
|
||||
|
||||
void fwx_init_time_status(fwx_run_time_status_t *status){
|
||||
status->filter = 0;
|
||||
status->deny_time = 0;
|
||||
status->allow_time = 0;
|
||||
status->match_time = 0;
|
||||
}
|
||||
|
||||
|
||||
int fwx_check_time_manual(fwx_time_config_t *t_config, fwx_run_time_status_t *status) {
|
||||
int i;
|
||||
time_t now = time(NULL);
|
||||
|
||||
struct tm *current_time = localtime(&now);
|
||||
int current_minutes = current_time->tm_hour * 60 + current_time->tm_min;
|
||||
int current_wday = current_time->tm_wday; // 0=Sunday, 1=Monday, ..., 6=Saturday
|
||||
|
||||
LOG_DEBUG("current time: %02d:%02d, weekday: %d\n", current_time->tm_hour, current_time->tm_min, current_wday);
|
||||
for (i = 0; i < t_config->time_num; i++) {
|
||||
|
||||
if (t_config->time_list[i].weekday_map[current_wday] == 0) {
|
||||
continue; // Skip if weekday is not enabled
|
||||
}
|
||||
|
||||
int start_minutes = t_config->time_list[i].start_time.hour * 60 + t_config->time_list[i].start_time.min;
|
||||
int end_minutes = t_config->time_list[i].end_time.hour * 60 + t_config->time_list[i].end_time.min;
|
||||
LOG_DEBUG("check time: %02d:%02d-%02d:%02d, weekday_map[%d]=%d\n",
|
||||
t_config->time_list[i].start_time.hour, t_config->time_list[i].start_time.min,
|
||||
t_config->time_list[i].end_time.hour, t_config->time_list[i].end_time.min,
|
||||
current_wday, t_config->time_list[i].weekday_map[current_wday]);
|
||||
|
||||
if (current_minutes >= start_minutes && current_minutes <= end_minutes) {
|
||||
LOG_DEBUG("current time in time list\n");
|
||||
status->match_time = 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
status->match_time = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fwx_check_time_dynamic(fwx_time_config_t *t_config, fwx_run_time_status_t *status) {
|
||||
time_t now = time(NULL);
|
||||
struct tm *current_time = localtime(&now);
|
||||
int current_minutes = current_time->tm_hour * 60 + current_time->tm_min;
|
||||
|
||||
int start_minutes = t_config->seg_time.start_time.hour * 60 + t_config->seg_time.start_time.min;
|
||||
int end_minutes = t_config->seg_time.end_time.hour * 60 + t_config->seg_time.end_time.min;
|
||||
printf("check seg_time: %02d:%02d-%02d:%02d\n",
|
||||
t_config->seg_time.start_time.hour, t_config->seg_time.start_time.min,
|
||||
t_config->seg_time.end_time.hour, t_config->seg_time.end_time.min);
|
||||
if (!(current_minutes >= start_minutes && current_minutes <= end_minutes)) {
|
||||
printf("current time not in seg_time\n");
|
||||
fwx_init_time_status(status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
status->match_time = 1;
|
||||
if (status->filter == 1) {
|
||||
status->deny_time++;
|
||||
if (status->deny_time >= t_config->deny_time) {
|
||||
status->filter = 0;
|
||||
status->deny_time = 0;
|
||||
printf("deny time over, filter = 0");
|
||||
}
|
||||
printf("deny_time: %d\n", status->deny_time);
|
||||
} else {
|
||||
status->allow_time++;
|
||||
if (status->allow_time >= t_config->allow_time) {
|
||||
status->filter = 1;
|
||||
status->allow_time = 0;
|
||||
printf("allow time over, filter = 1");
|
||||
}
|
||||
printf("allow_time: %d\n", status->allow_time);
|
||||
}
|
||||
return status->filter;
|
||||
}
|
||||
|
||||
int fwx_check_time(fwx_time_config_t *t_config, fwx_run_time_status_t *status) {
|
||||
time_t now = time(NULL);
|
||||
struct tm *current_time = localtime(&now);
|
||||
int current_wday = current_time->tm_wday; // 0=Sunday, 1=Monday, ..., 6=Saturday
|
||||
LOG_DEBUG("current day: %d\n", current_wday);
|
||||
|
||||
if (t_config->time_mode == 0) {
|
||||
LOG_DEBUG("manual mode\n");
|
||||
return fwx_check_time_manual(t_config, status);
|
||||
} else {
|
||||
LOG_DEBUG("dynamic mode\n");
|
||||
|
||||
if (t_config->seg_time.weekday_map[current_wday] == 0) {
|
||||
LOG_DEBUG("current day not in configured days\n");
|
||||
fwx_init_time_status(status);
|
||||
return 0;
|
||||
}
|
||||
return fwx_check_time_dynamic(t_config, status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct json_object * fwx_gen_api_response_data(int code, struct json_object *data_obj){
|
||||
struct json_object *root_obj = json_object_new_object();
|
||||
if (!root_obj)
|
||||
return NULL;
|
||||
json_object_object_add(root_obj, "code", json_object_new_int(code));
|
||||
if (data_obj)
|
||||
json_object_object_add(root_obj, "data", data_obj);
|
||||
return root_obj;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include "fwx_config.h"
|
||||
#include "fwx.h"
|
||||
#include "fwx_feature.h"
|
||||
#include <uci.h>
|
||||
|
||||
app_name_info_t app_name_table[MAX_SUPPORT_APP_NUM];
|
||||
int g_app_count = 0;
|
||||
static int g_base_app_count = 0;
|
||||
int g_cur_class_num = 0;
|
||||
char CLASS_NAME_TABLE[MAX_APP_TYPE][MAX_CLASS_NAME_LEN];
|
||||
|
||||
static char *trim_space(char *text)
|
||||
{
|
||||
char *end;
|
||||
|
||||
if (!text)
|
||||
return NULL;
|
||||
while (*text && isspace((unsigned char)*text))
|
||||
text++;
|
||||
end = text + strlen(text);
|
||||
while (end > text && isspace((unsigned char)end[-1]))
|
||||
*--end = '\0';
|
||||
return text;
|
||||
}
|
||||
|
||||
static int parse_feature_app_header(const char *line, int *app_id, char *app_name, size_t app_name_len)
|
||||
{
|
||||
char header[128];
|
||||
char *slash;
|
||||
char *id_text;
|
||||
char *name_text;
|
||||
char *endptr;
|
||||
long id;
|
||||
const char *colon;
|
||||
size_t header_len;
|
||||
|
||||
if (!line || !app_id || !app_name || app_name_len == 0)
|
||||
return -1;
|
||||
|
||||
colon = strchr(line, ':');
|
||||
if (!colon)
|
||||
return -1;
|
||||
header_len = (size_t)(colon - line);
|
||||
if (header_len == 0 || header_len >= sizeof(header))
|
||||
return -1;
|
||||
|
||||
memcpy(header, line, header_len);
|
||||
header[header_len] = '\0';
|
||||
slash = strchr(header, '~');
|
||||
if (!slash)
|
||||
return -1;
|
||||
*slash = '\0';
|
||||
|
||||
id_text = trim_space(header);
|
||||
name_text = trim_space(slash + 1);
|
||||
if (!id_text || !id_text[0] || !name_text || !name_text[0])
|
||||
return -1;
|
||||
|
||||
id = strtol(id_text, &endptr, 10);
|
||||
endptr = trim_space(endptr);
|
||||
if (id <= 0 || (endptr && endptr[0] != '\0'))
|
||||
return -1;
|
||||
|
||||
*app_id = (int)id;
|
||||
strncpy(app_name, name_text, app_name_len - 1);
|
||||
app_name[app_name_len - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
int app_icon_exists_by_id(int id)
|
||||
{
|
||||
char icon_path[256];
|
||||
|
||||
if (id <= 0)
|
||||
return 0;
|
||||
|
||||
snprintf(icon_path, sizeof(icon_path),
|
||||
"/www/luci-static/resources/oaf/app_icons/%d.png", id);
|
||||
return access(icon_path, F_OK) == 0;
|
||||
}
|
||||
|
||||
char *get_app_name_by_id(int id)
|
||||
{
|
||||
int i;
|
||||
static char fallback_name[32];
|
||||
|
||||
for (i = 0; i < g_app_count; i++)
|
||||
{
|
||||
if (id == app_name_table[i].id)
|
||||
return app_name_table[i].name;
|
||||
}
|
||||
if (id > 0) {
|
||||
snprintf(fallback_name, sizeof(fallback_name), "App%d", id);
|
||||
return fallback_name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int add_app_name_to_table(int id, const char *name)
|
||||
{
|
||||
if (!name || !name[0] || g_app_count >= MAX_SUPPORT_APP_NUM)
|
||||
return -1;
|
||||
app_name_table[g_app_count].id = id;
|
||||
strncpy(app_name_table[g_app_count].name, name,
|
||||
sizeof(app_name_table[g_app_count].name) - 1);
|
||||
app_name_table[g_app_count].name[sizeof(app_name_table[g_app_count].name) - 1] = '\0';
|
||||
g_app_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_base_app_count(void)
|
||||
{
|
||||
return g_base_app_count;
|
||||
}
|
||||
|
||||
void init_app_name_table(void)
|
||||
{
|
||||
char line_buf[2048] = {0};
|
||||
const char *feature_data;
|
||||
size_t feature_len = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
feature_data = fwx_feature_get_data(&feature_len);
|
||||
if (!feature_data)
|
||||
return;
|
||||
memset(app_name_table, 0, sizeof(app_name_table));
|
||||
g_app_count = 0;
|
||||
while (fwx_feature_next_line(feature_data, feature_len, &offset,
|
||||
line_buf, sizeof(line_buf)) != 0) {
|
||||
int app_id = 0;
|
||||
char app_name[64] = {0};
|
||||
|
||||
if (strstr(line_buf, "#"))
|
||||
continue;
|
||||
if (strlen(line_buf) < 10)
|
||||
continue;
|
||||
if (parse_feature_app_header(line_buf, &app_id, app_name, sizeof(app_name)) < 0)
|
||||
continue;
|
||||
if (g_app_count >= MAX_SUPPORT_APP_NUM)
|
||||
break;
|
||||
app_name_table[g_app_count].id = app_id;
|
||||
strncpy(app_name_table[g_app_count].name, app_name,
|
||||
sizeof(app_name_table[g_app_count].name) - 1);
|
||||
g_app_count++;
|
||||
}
|
||||
g_base_app_count = g_app_count;
|
||||
}
|
||||
|
||||
void init_app_class_name_table(void)
|
||||
{
|
||||
char line_buf[2048] = {0};
|
||||
const char *feature_data;
|
||||
size_t feature_len = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
feature_data = fwx_feature_get_data(&feature_len);
|
||||
if (!feature_data)
|
||||
return;
|
||||
memset(CLASS_NAME_TABLE, 0, sizeof(CLASS_NAME_TABLE));
|
||||
g_cur_class_num = 0;
|
||||
while (fwx_feature_next_line(feature_data, feature_len, &offset,
|
||||
line_buf, sizeof(line_buf)) != 0) {
|
||||
int class_id = 0;
|
||||
char class_name[MAX_CLASS_NAME_LEN] = {0};
|
||||
|
||||
if (strncmp(line_buf, "#class ", 7) != 0)
|
||||
continue;
|
||||
if (sscanf(line_buf, "#class %*s %d %31s", &class_id, class_name) != 2 ||
|
||||
class_id < 1 || class_id > MAX_APP_TYPE)
|
||||
continue;
|
||||
strncpy(CLASS_NAME_TABLE[class_id - 1], class_name, MAX_CLASS_NAME_LEN - 1);
|
||||
if (class_id > g_cur_class_num)
|
||||
g_cur_class_num = class_id;
|
||||
}
|
||||
}
|
||||
|
||||
int check_time_valid(char *t)
|
||||
{
|
||||
if (!t)
|
||||
return 0;
|
||||
if (strlen(t) < 3 || strlen(t) > 5 || (!strstr(t, ":")))
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int config_get_appfilter_enable(void)
|
||||
{
|
||||
int enable = 0;
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (!ctx)
|
||||
return -1;
|
||||
enable = fwx_uci_get_int_value(ctx, "appfilter.global.enable");
|
||||
if (enable < 0)
|
||||
enable = 0;
|
||||
|
||||
uci_free_context(ctx);
|
||||
return enable;
|
||||
}
|
||||
|
||||
int config_get_lan_ip(char *lan_ip, int len)
|
||||
{
|
||||
int ret = 0;
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (!ctx)
|
||||
return -1;
|
||||
ret = fwx_uci_get_value(ctx, "network.lan.ipaddr", lan_ip, len);
|
||||
uci_free_context(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int config_get_lan_mask(char *lan_mask, int len)
|
||||
{
|
||||
int ret = 0;
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (!ctx)
|
||||
return -1;
|
||||
ret = fwx_uci_get_value(ctx, "network.lan.netmask", lan_mask, len);
|
||||
uci_free_context(ctx);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_CONFIG_H__
|
||||
#define __FWX_CONFIG_H__
|
||||
#include <uci.h>
|
||||
|
||||
#define MAX_SUPPORT_APP_NUM 1024
|
||||
#define MAX_CLASS_NAME_LEN 32
|
||||
|
||||
#include "fwx_user.h"
|
||||
extern int g_cur_class_num;
|
||||
extern int g_app_count;
|
||||
extern char CLASS_NAME_TABLE[MAX_APP_TYPE][MAX_CLASS_NAME_LEN];
|
||||
|
||||
typedef struct app_name_info
|
||||
{
|
||||
int id;
|
||||
char name[64];
|
||||
} app_name_info_t;
|
||||
void init_app_name_table(void);
|
||||
void init_app_class_name_table(void);
|
||||
char *get_app_name_by_id(int id);
|
||||
int app_icon_exists_by_id(int id);
|
||||
|
||||
int appfilter_config_alloc(void);
|
||||
|
||||
int appfilter_config_free(void);
|
||||
int config_get_appfilter_enable(void);
|
||||
int config_get_lan_ip(char *lan_ip, int len);
|
||||
int config_get_lan_mask(char *lan_mask, int len);
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#ifndef __FWX_CUSTOM_FEATURE_H__
|
||||
#define __FWX_CUSTOM_FEATURE_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <json-c/json.h>
|
||||
|
||||
#define FWX_CUSTOM_FEATURE_PATH "/etc/fwxd/custom_feature.cfg"
|
||||
|
||||
int fwx_custom_feature_reload(void);
|
||||
int fwx_custom_feature_send_to_kernel(int (*send_feature)(char *));
|
||||
int fwx_custom_feature_add_app_names(void);
|
||||
void fwx_custom_feature_append_class_apps(struct json_object **class_map,
|
||||
size_t class_map_len);
|
||||
struct json_object *fwx_api_get_custom_feature(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_get_custom_feature_class_list(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_custom_feature(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,379 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include "fwx_feature.h"
|
||||
|
||||
#define FEATURE_HEADER_SIZE 24U
|
||||
#define FEATURE_FORMAT_VERSION 1U
|
||||
#define FEATURE_ALGORITHM_XTEA_CTR 1U
|
||||
|
||||
static const unsigned char feature_magic[4] = {'F', 'W', 'X', 'B'};
|
||||
static const uint32_t feature_key[4] = {
|
||||
0x8f4c29a1U, 0x73b6d502U, 0xc14e87f3U, 0x2ad95b60U
|
||||
};
|
||||
static char *g_feature_data;
|
||||
static size_t g_feature_data_len;
|
||||
|
||||
static void clear_memory(void *data, size_t len)
|
||||
{
|
||||
volatile unsigned char *p = data;
|
||||
|
||||
while (p && len-- > 0)
|
||||
*p++ = 0;
|
||||
}
|
||||
|
||||
static uint16_t get_le16(const unsigned char *p)
|
||||
{
|
||||
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
|
||||
}
|
||||
|
||||
static uint32_t get_le32(const unsigned char *p)
|
||||
{
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
static uint64_t get_le64(const unsigned char *p)
|
||||
{
|
||||
return (uint64_t)get_le32(p) | ((uint64_t)get_le32(p + 4) << 32);
|
||||
}
|
||||
|
||||
static void put_le16(unsigned char *p, uint16_t value)
|
||||
{
|
||||
p[0] = (unsigned char)value;
|
||||
p[1] = (unsigned char)(value >> 8);
|
||||
}
|
||||
|
||||
static void put_le32(unsigned char *p, uint32_t value)
|
||||
{
|
||||
p[0] = (unsigned char)value;
|
||||
p[1] = (unsigned char)(value >> 8);
|
||||
p[2] = (unsigned char)(value >> 16);
|
||||
p[3] = (unsigned char)(value >> 24);
|
||||
}
|
||||
|
||||
static void put_le64(unsigned char *p, uint64_t value)
|
||||
{
|
||||
put_le32(p, (uint32_t)value);
|
||||
put_le32(p + 4, (uint32_t)(value >> 32));
|
||||
}
|
||||
|
||||
static uint32_t feature_crc32(const unsigned char *data, size_t len)
|
||||
{
|
||||
uint32_t crc = 0xffffffffU;
|
||||
size_t i;
|
||||
int bit;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for (bit = 0; bit < 8; bit++)
|
||||
crc = (crc >> 1) ^ (0xedb88320U & (0U - (crc & 1U)));
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
static void xtea_encrypt_block(uint32_t block[2])
|
||||
{
|
||||
uint32_t v0 = block[0];
|
||||
uint32_t v1 = block[1];
|
||||
uint32_t sum = 0;
|
||||
const uint32_t delta = 0x9e3779b9U;
|
||||
int round;
|
||||
|
||||
for (round = 0; round < 32; round++) {
|
||||
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^
|
||||
(sum + feature_key[sum & 3U]);
|
||||
sum += delta;
|
||||
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^
|
||||
(sum + feature_key[(sum >> 11) & 3U]);
|
||||
}
|
||||
block[0] = v0;
|
||||
block[1] = v1;
|
||||
}
|
||||
|
||||
static void xtea_ctr_crypt(unsigned char *data, size_t len, uint64_t nonce)
|
||||
{
|
||||
size_t offset = 0;
|
||||
uint64_t counter = nonce;
|
||||
|
||||
while (offset < len) {
|
||||
unsigned char stream[8];
|
||||
uint32_t block[2];
|
||||
size_t i;
|
||||
|
||||
block[0] = (uint32_t)counter;
|
||||
block[1] = (uint32_t)(counter >> 32);
|
||||
xtea_encrypt_block(block);
|
||||
put_le32(stream, block[0]);
|
||||
put_le32(stream + 4, block[1]);
|
||||
for (i = 0; i < sizeof(stream) && offset < len; i++, offset++)
|
||||
data[offset] ^= stream[i];
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
static int read_file(const char *path, size_t max_len, unsigned char **data, size_t *len)
|
||||
{
|
||||
FILE *fp;
|
||||
long file_len;
|
||||
unsigned char *buf;
|
||||
|
||||
if (!path || !data || !len)
|
||||
return -1;
|
||||
fp = fopen(path, "rb");
|
||||
if (!fp)
|
||||
return -1;
|
||||
if (fseek(fp, 0, SEEK_END) != 0 || (file_len = ftell(fp)) <= 0 ||
|
||||
(size_t)file_len > max_len || fseek(fp, 0, SEEK_SET) != 0) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
buf = malloc((size_t)file_len);
|
||||
if (!buf) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
if (fread(buf, 1, (size_t)file_len, fp) != (size_t)file_len) {
|
||||
free(buf);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
fclose(fp);
|
||||
*data = buf;
|
||||
*len = (size_t)file_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_nonce(uint64_t *nonce)
|
||||
{
|
||||
FILE *fp;
|
||||
unsigned char buf[8];
|
||||
|
||||
fp = fopen("/dev/urandom", "rb");
|
||||
if (!fp)
|
||||
return -1;
|
||||
if (fread(buf, 1, sizeof(buf), fp) != sizeof(buf)) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
fclose(fp);
|
||||
*nonce = get_le64(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int fwx_feature_decrypt_file(const char *path, char **data, size_t *data_len)
|
||||
{
|
||||
unsigned char *file_data = NULL;
|
||||
char *plain = NULL;
|
||||
size_t file_len = 0;
|
||||
uint32_t plain_len = 0;
|
||||
uint32_t expected_crc;
|
||||
uint64_t nonce;
|
||||
int ret = -1;
|
||||
|
||||
if (!data || !data_len)
|
||||
return -1;
|
||||
*data = NULL;
|
||||
*data_len = 0;
|
||||
if (read_file(path, FEATURE_HEADER_SIZE + FWX_FEATURE_MAX_SIZE, &file_data, &file_len) != 0)
|
||||
return -1;
|
||||
if (file_len < FEATURE_HEADER_SIZE || memcmp(file_data, feature_magic, sizeof(feature_magic)) != 0 ||
|
||||
file_data[4] != FEATURE_FORMAT_VERSION || file_data[5] != FEATURE_ALGORITHM_XTEA_CTR ||
|
||||
get_le16(file_data + 6) != FEATURE_HEADER_SIZE)
|
||||
goto out;
|
||||
plain_len = get_le32(file_data + 8);
|
||||
expected_crc = get_le32(file_data + 12);
|
||||
nonce = get_le64(file_data + 16);
|
||||
if (plain_len == 0 || plain_len > FWX_FEATURE_MAX_SIZE ||
|
||||
file_len != FEATURE_HEADER_SIZE + (size_t)plain_len)
|
||||
goto out;
|
||||
plain = malloc((size_t)plain_len + 1U);
|
||||
if (!plain)
|
||||
goto out;
|
||||
memcpy(plain, file_data + FEATURE_HEADER_SIZE, plain_len);
|
||||
xtea_ctr_crypt((unsigned char *)plain, plain_len, nonce);
|
||||
plain[plain_len] = '\0';
|
||||
if (feature_crc32((const unsigned char *)plain, plain_len) != expected_crc)
|
||||
goto out;
|
||||
*data = plain;
|
||||
*data_len = plain_len;
|
||||
plain = NULL;
|
||||
ret = 0;
|
||||
out:
|
||||
clear_memory(plain, plain_len);
|
||||
free(plain);
|
||||
free(file_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int copy_file_atomic(const char *source, const char *target,
|
||||
const char *temporary)
|
||||
{
|
||||
unsigned char *data = NULL;
|
||||
size_t data_len = 0;
|
||||
FILE *fp = NULL;
|
||||
int ret = -1;
|
||||
|
||||
if (read_file(source,
|
||||
FEATURE_HEADER_SIZE + FWX_FEATURE_MAX_SIZE,
|
||||
&data, &data_len) != 0)
|
||||
return -1;
|
||||
fp = fopen(temporary, "wb");
|
||||
if (!fp || fwrite(data, 1, data_len, fp) != data_len ||
|
||||
fflush(fp) != 0 || fsync(fileno(fp)) != 0)
|
||||
goto out;
|
||||
if (fclose(fp) != 0) {
|
||||
fp = NULL;
|
||||
goto out;
|
||||
}
|
||||
fp = NULL;
|
||||
if (chmod(temporary, 0644) != 0 ||
|
||||
rename(temporary, target) != 0)
|
||||
goto out;
|
||||
ret = 0;
|
||||
out:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
if (ret != 0)
|
||||
unlink(temporary);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fwx_feature_validate_candidate(char *version, size_t version_len,
|
||||
char *format, size_t format_len)
|
||||
{
|
||||
char *feature_data = NULL;
|
||||
size_t feature_len = 0;
|
||||
size_t offset = 0;
|
||||
char line[1024];
|
||||
char parsed_version[64] = {0};
|
||||
char parsed_format[32] = {0};
|
||||
int line_ret;
|
||||
int ret = -1;
|
||||
|
||||
if (!version || version_len == 0 || !format || format_len == 0)
|
||||
return -1;
|
||||
version[0] = '\0';
|
||||
format[0] = '\0';
|
||||
if (fwx_feature_decrypt_file(FWX_FEATURE_CANDIDATE_PATH,
|
||||
&feature_data, &feature_len) < 0)
|
||||
return FWX_FEATURE_VALIDATE_DECRYPT_FAILED;
|
||||
while ((line_ret = fwx_feature_next_line(feature_data, feature_len,
|
||||
&offset, line, sizeof(line))) != 0) {
|
||||
if (line_ret < 0)
|
||||
continue;
|
||||
if (!strncmp(line, "#version ", 9))
|
||||
sscanf(line, "#version %63s", parsed_version);
|
||||
else if (!strncmp(line, "#format ", 8))
|
||||
sscanf(line, "#format %31s", parsed_format);
|
||||
if (parsed_version[0] && parsed_format[0])
|
||||
break;
|
||||
}
|
||||
snprintf(version, version_len, "%s", parsed_version);
|
||||
snprintf(format, format_len, "%s", parsed_format);
|
||||
if (parsed_version[0] && !strcmp(parsed_format, FWX_FEATURE_DATA_FORMAT))
|
||||
ret = 0;
|
||||
else
|
||||
ret = FWX_FEATURE_VALIDATE_INFO_INVALID;
|
||||
clear_memory(feature_data, feature_len);
|
||||
free(feature_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fwx_feature_apply_candidate(void)
|
||||
{
|
||||
const char *backup_temporary = FWX_FEATURE_BACKUP_PATH ".tmp";
|
||||
const char *target_temporary = FWX_FEATURE_BIN_PATH ".tmp";
|
||||
|
||||
if (access(FWX_FEATURE_BIN_PATH, F_OK) == 0 &&
|
||||
copy_file_atomic(FWX_FEATURE_BIN_PATH, FWX_FEATURE_BACKUP_PATH,
|
||||
backup_temporary) < 0)
|
||||
return FWX_FEATURE_APPLY_BACKUP_FAILED;
|
||||
if (copy_file_atomic(FWX_FEATURE_CANDIDATE_PATH, FWX_FEATURE_BIN_PATH,
|
||||
target_temporary) < 0)
|
||||
return FWX_FEATURE_APPLY_TARGET_FAILED;
|
||||
unlink(FWX_FEATURE_CANDIDATE_PATH);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fwx_feature_process_candidate(char *version, size_t version_len,
|
||||
char *format, size_t format_len,
|
||||
int *status_code,
|
||||
void (*stage_callback)(const char *stage))
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (status_code)
|
||||
*status_code = 400;
|
||||
ret = fwx_feature_validate_candidate(version, version_len, format, format_len);
|
||||
if (ret < 0) {
|
||||
if (status_code)
|
||||
*status_code = 401;
|
||||
unlink(FWX_FEATURE_CANDIDATE_PATH);
|
||||
return ret;
|
||||
}
|
||||
if (stage_callback)
|
||||
stage_callback("applying");
|
||||
ret = fwx_feature_apply_candidate();
|
||||
if (ret < 0) {
|
||||
unlink(FWX_FEATURE_CANDIDATE_PATH);
|
||||
return ret;
|
||||
}
|
||||
if (status_code)
|
||||
*status_code = 200;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fwx_feature_restore_backup(void)
|
||||
{
|
||||
return copy_file_atomic(FWX_FEATURE_BACKUP_PATH, FWX_FEATURE_BIN_PATH,
|
||||
FWX_FEATURE_BIN_PATH ".restore");
|
||||
}
|
||||
|
||||
|
||||
void fwx_feature_replace_data(char *data, size_t data_len)
|
||||
{
|
||||
clear_memory(g_feature_data, g_feature_data_len);
|
||||
free(g_feature_data);
|
||||
g_feature_data = data;
|
||||
g_feature_data_len = data_len;
|
||||
}
|
||||
|
||||
const char *fwx_feature_get_data(size_t *data_len)
|
||||
{
|
||||
if (data_len)
|
||||
*data_len = g_feature_data_len;
|
||||
return g_feature_data;
|
||||
}
|
||||
|
||||
int fwx_feature_next_line(const char *data, size_t data_len, size_t *offset,
|
||||
char *line, size_t line_size)
|
||||
{
|
||||
size_t start;
|
||||
size_t end;
|
||||
size_t line_len;
|
||||
|
||||
if (!data || !offset || !line || line_size == 0 || *offset >= data_len)
|
||||
return 0;
|
||||
start = *offset;
|
||||
end = start;
|
||||
while (end < data_len && data[end] != '\n')
|
||||
end++;
|
||||
*offset = end < data_len ? end + 1U : end;
|
||||
line_len = end - start;
|
||||
if (line_len > 0 && data[start + line_len - 1U] == '\r')
|
||||
line_len--;
|
||||
if (line_len >= line_size) {
|
||||
line[0] = '\0';
|
||||
return -1;
|
||||
}
|
||||
memcpy(line, data + start, line_len);
|
||||
line[line_len] = '\0';
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#ifndef __FWX_FEATURE_H__
|
||||
#define __FWX_FEATURE_H__
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define FWX_FEATURE_BIN_PATH "/etc/fwxd/feature.bin"
|
||||
#define FWX_FEATURE_BACKUP_PATH "/etc/fwxd/feature.bin.bak"
|
||||
#define FWX_FEATURE_LIST_CACHE_PATH "/etc/fwxd/feature_list.json"
|
||||
#define FWX_FEATURE_CANDIDATE_PATH "/tmp/feature.bin"
|
||||
#define FWX_FEATURE_INFO_PATH "/tmp/feature_info.json"
|
||||
#define FWX_FEATURE_UPGRADE_STATUS_PATH "/tmp/feature_upgrade.status"
|
||||
#define FWX_FEATURE_DATA_FORMAT "v4.0"
|
||||
#define FWX_FEATURE_MAX_SIZE (20U * 1024U * 1024U)
|
||||
|
||||
#define FWX_FEATURE_VALIDATE_DECRYPT_FAILED -1
|
||||
#define FWX_FEATURE_VALIDATE_INFO_INVALID -2
|
||||
#define FWX_FEATURE_APPLY_BACKUP_FAILED -1
|
||||
#define FWX_FEATURE_APPLY_TARGET_FAILED -2
|
||||
|
||||
int fwx_feature_decrypt_file(const char *path, char **data, size_t *data_len);
|
||||
int fwx_feature_validate_candidate(char *version, size_t version_len,
|
||||
char *format, size_t format_len);
|
||||
int fwx_feature_apply_candidate(void);
|
||||
int fwx_feature_restore_backup(void);
|
||||
int fwx_feature_process_candidate(char *version, size_t version_len,
|
||||
char *format, size_t format_len,
|
||||
int *status_code,
|
||||
void (*stage_callback)(const char *stage));
|
||||
int test_encrypt_feature_file(void);
|
||||
|
||||
void fwx_feature_replace_data(char *data, size_t data_len);
|
||||
const char *fwx_feature_get_data(size_t *data_len);
|
||||
int fwx_feature_next_line(const char *data, size_t data_len, size_t *offset,
|
||||
char *line, size_t line_size);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#ifndef __FWX_FEATURE_ONLINE_H__
|
||||
#define __FWX_FEATURE_ONLINE_H__
|
||||
|
||||
#include <json-c/json.h>
|
||||
|
||||
#define FWX_FEATURE_UPGRADE_LOCK_PATH "/tmp/feature_upgrade.lock"
|
||||
|
||||
int fwx_feature_online_init(void);
|
||||
void fwx_feature_online_cleanup(void);
|
||||
struct json_object *fwx_api_get_feature_online_config(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_feature_online_config(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_get_feature_online_list(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_start_feature_online_update(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_get_feature_online_update_status(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,140 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <json-c/json.h>
|
||||
#include <uci.h>
|
||||
#include "fwx.h"
|
||||
#include "fwx_firewall.h"
|
||||
#include "fwx_uci.h"
|
||||
|
||||
static struct json_object *get_basic_setting(void)
|
||||
{
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
struct json_object *data_obj = NULL;
|
||||
struct json_object *basic_obj = NULL;
|
||||
char syn_flood[16] = {0};
|
||||
char input[32] = {0};
|
||||
char output[32] = {0};
|
||||
char forward[32] = {0};
|
||||
char fullcone[16] = {0};
|
||||
|
||||
if (!ctx) {
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
if (fwx_uci_get_value(ctx, "firewall.@defaults[0].syn_flood",
|
||||
syn_flood, sizeof(syn_flood)) != UCI_OK) {
|
||||
LOG_ERROR("Failed to read firewall defaults\n");
|
||||
uci_free_context(ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
fwx_uci_get_value(ctx, "firewall.@defaults[0].input", input, sizeof(input));
|
||||
fwx_uci_get_value(ctx, "firewall.@defaults[0].output", output, sizeof(output));
|
||||
fwx_uci_get_value(ctx, "firewall.@defaults[0].forward", forward, sizeof(forward));
|
||||
fwx_uci_get_value(ctx, "firewall.@defaults[0].fullcone", fullcone, sizeof(fullcone));
|
||||
|
||||
data_obj = json_object_new_object();
|
||||
basic_obj = json_object_new_object();
|
||||
if (!data_obj || !basic_obj) {
|
||||
if (data_obj) {
|
||||
json_object_put(data_obj);
|
||||
}
|
||||
if (basic_obj) {
|
||||
json_object_put(basic_obj);
|
||||
}
|
||||
uci_free_context(ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
json_object_object_add(basic_obj, "syn_flood",
|
||||
json_object_new_int(atoi(syn_flood)));
|
||||
json_object_object_add(basic_obj, "input", json_object_new_string(input));
|
||||
json_object_object_add(basic_obj, "output", json_object_new_string(output));
|
||||
json_object_object_add(basic_obj, "forward", json_object_new_string(forward));
|
||||
json_object_object_add(basic_obj, "fullcone",
|
||||
json_object_new_int(atoi(fullcone)));
|
||||
json_object_object_add(data_obj, "basic", basic_obj);
|
||||
|
||||
uci_free_context(ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
static struct json_object *set_basic_setting(struct json_object *req_obj)
|
||||
{
|
||||
struct json_object *fullcone_obj = NULL;
|
||||
struct uci_context *ctx = NULL;
|
||||
int fullcone = 0;
|
||||
|
||||
if (!json_object_object_get_ex(req_obj, "fullcone", &fullcone_obj)) {
|
||||
LOG_ERROR("FullCone setting is missing\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
if (!json_object_is_type(fullcone_obj, json_type_int) &&
|
||||
!json_object_is_type(fullcone_obj, json_type_boolean)) {
|
||||
LOG_ERROR("FullCone setting type is invalid\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
fullcone = json_object_get_int(fullcone_obj);
|
||||
LOG_INFO("set_firewall parsed fullcone: %d\n", fullcone);
|
||||
if (fullcone != 0 && fullcone != 1) {
|
||||
LOG_ERROR("Invalid FullCone setting: %d\n", fullcone);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
ctx = uci_alloc_context();
|
||||
if (!ctx) {
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
fwx_uci_set_int_value(ctx, "firewall.@defaults[0].fullcone", fullcone);
|
||||
fwx_uci_commit(ctx, "firewall");
|
||||
uci_free_context(ctx);
|
||||
|
||||
system("/etc/init.d/firewall reload >/dev/null 2>&1");
|
||||
LOG_INFO("set_firewall basic setting applied\n");
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_firewall(struct json_object *req_obj)
|
||||
{
|
||||
(void)req_obj;
|
||||
return get_basic_setting();
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_set_firewall(struct json_object *req_obj)
|
||||
{
|
||||
struct json_object *action_obj = NULL;
|
||||
struct json_object *basic_obj = NULL;
|
||||
struct json_object *response_obj = NULL;
|
||||
const char *action = NULL;
|
||||
|
||||
LOG_INFO("fwx_api_set_firewall request: %s\n",
|
||||
req_obj ? json_object_to_json_string(req_obj) : "null");
|
||||
|
||||
if (!req_obj ||
|
||||
!json_object_object_get_ex(req_obj, "action", &action_obj)) {
|
||||
LOG_ERROR("Firewall setting request is invalid\n");
|
||||
response_obj = fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
goto done;
|
||||
}
|
||||
|
||||
action = json_object_get_string(action_obj);
|
||||
if (action && strcmp(action, "set_basic") == 0) {
|
||||
if (!json_object_object_get_ex(req_obj, "basic", &basic_obj) ||
|
||||
!json_object_is_type(basic_obj, json_type_object)) {
|
||||
LOG_ERROR("Firewall basic setting is invalid\n");
|
||||
response_obj = fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
goto done;
|
||||
}
|
||||
response_obj = set_basic_setting(basic_obj);
|
||||
goto done;
|
||||
}
|
||||
|
||||
LOG_ERROR("Unsupported firewall action: %s\n", action ? action : "");
|
||||
response_obj = fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
|
||||
done:
|
||||
LOG_INFO("fwx_api_set_firewall response: %s\n",
|
||||
response_obj ? json_object_to_json_string(response_obj) : "null");
|
||||
return response_obj;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#ifndef __FWX_FIREWALL_H__
|
||||
#define __FWX_FIREWALL_H__
|
||||
|
||||
#include <json-c/json.h>
|
||||
|
||||
struct json_object *fwx_api_get_firewall(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_firewall(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_MAC_FILTER_H__
|
||||
#define __FWX_MAC_FILTER_H__
|
||||
|
||||
#include "fwx.h"
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_mac_filter_rules(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_add_mac_filter_rule(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_update_mac_filter_rule(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_delete_mac_filter_rule(struct json_object *req_obj);
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_mac_filter_whitelist(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_add_mac_filter_whitelist(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_del_mac_filter_whitelist(struct json_object *req_obj);
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_mac_filter_adv(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_mac_filter_adv(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/socket.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/utils.h>
|
||||
#include <libubus.h>
|
||||
#include <json-c/json.h>
|
||||
#include "fwx_user.h"
|
||||
#include "fwx_netlink.h"
|
||||
#include "fwx.h"
|
||||
#define MAX_NL_RCV_BUF_SIZE 4096
|
||||
|
||||
#define REPORT_INTERVAL_SECS 60
|
||||
|
||||
|
||||
extern traffic_stat_t g_global_hourly_traffic[HOURS_PER_DAY];
|
||||
extern u_int32_t g_global_traffic_date;
|
||||
|
||||
void fwx_netlink_handler(struct uloop_fd *u, unsigned int ev)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
char buf[MAX_NL_RCV_BUF_SIZE];
|
||||
struct sockaddr_nl nladdr;
|
||||
struct iovec iov = {buf, sizeof(buf)};
|
||||
struct nlmsghdr *h;
|
||||
int type;
|
||||
int id;
|
||||
const char *mac = NULL;
|
||||
|
||||
struct msghdr msg = {
|
||||
.msg_name = &nladdr,
|
||||
.msg_namelen = sizeof(nladdr),
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
};
|
||||
|
||||
do
|
||||
{
|
||||
ret = recvmsg(u->fd, &msg, 0);
|
||||
} while ((-1 == ret) && (EINTR == errno));
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
printf("recv msg error\n");
|
||||
return;
|
||||
}
|
||||
else if (0 == ret)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
h = (struct nlmsghdr *)buf;
|
||||
char *kmsg = (char *)NLMSG_DATA(h);
|
||||
struct fwx_nl_msg_hdr *af_hdr = (struct fwx_nl_msg_hdr *)kmsg;
|
||||
if (af_hdr->magic != 0xa0b0c0d0)
|
||||
{
|
||||
printf("magic error %x\n", af_hdr->magic);
|
||||
return;
|
||||
}
|
||||
|
||||
if (af_hdr->len <= 0 || af_hdr->len >= MAX_FWX_NETLINK_MSG_LEN)
|
||||
{
|
||||
printf("data len error\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char *kdata = kmsg + sizeof(struct fwx_nl_msg_hdr);
|
||||
struct json_object *root = json_tokener_parse(kdata);
|
||||
if (!root)
|
||||
{
|
||||
LOG_ERROR("parse json failed:%s", kdata);
|
||||
return;
|
||||
}
|
||||
|
||||
struct json_object *mac_obj = json_object_object_get(root, "mac");
|
||||
|
||||
if (!mac_obj)
|
||||
{
|
||||
printf("parse mac obj failed\n");
|
||||
json_object_put(root);
|
||||
return;
|
||||
}
|
||||
|
||||
mac = json_object_get_string(mac_obj);
|
||||
|
||||
client_node_t *node = find_client_node(mac);
|
||||
|
||||
if (!node)
|
||||
{
|
||||
node = add_client_node(mac);
|
||||
if (!node)
|
||||
{
|
||||
printf("add dev node failed\n");
|
||||
json_object_put(root);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct json_object *ip_obj = json_object_object_get(root, "ip");
|
||||
if (ip_obj)
|
||||
strncpy(node->ip, json_object_get_string(ip_obj), sizeof(node->ip));
|
||||
|
||||
struct json_object *ipv6_obj = json_object_object_get(root, "ipv6");
|
||||
if (ipv6_obj) {
|
||||
const char *ipv6_str = json_object_get_string(ipv6_obj);
|
||||
if (ipv6_str && strlen(ipv6_str) > 0) {
|
||||
strncpy(node->ipv6, ipv6_str, sizeof(node->ipv6) - 1);
|
||||
node->ipv6[sizeof(node->ipv6) - 1] = '\0';
|
||||
LOG_DEBUG("fwx_netlink: received ipv6=%s for %s\n", node->ipv6, mac);
|
||||
} else {
|
||||
node->ipv6[0] = '\0';
|
||||
}
|
||||
} else {
|
||||
node->ipv6[0] = '\0';
|
||||
}
|
||||
|
||||
struct json_object *active_obj = json_object_object_get(root, "active");
|
||||
int prev_active = node->active;
|
||||
if (active_obj) {
|
||||
node->active = json_object_get_int(active_obj);
|
||||
LOG_DEBUG("fwx_netlink: received active=%d for %s\n", node->active, mac);
|
||||
}
|
||||
|
||||
|
||||
struct json_object *up_flow_obj = json_object_object_get(root, "up_flow");
|
||||
struct json_object *down_flow_obj = json_object_object_get(root, "down_flow");
|
||||
unsigned long long total_up_bytes = 0;
|
||||
unsigned long long total_down_bytes = 0;
|
||||
|
||||
if (up_flow_obj) {
|
||||
|
||||
total_up_bytes = (unsigned long long)json_object_get_int64(up_flow_obj) * 1024;
|
||||
}
|
||||
if (down_flow_obj) {
|
||||
|
||||
total_down_bytes = (unsigned long long)json_object_get_int64(down_flow_obj) * 1024;
|
||||
}
|
||||
|
||||
LOG_DEBUG("fwx_netlink: received flow data for %s: up_flow=%llu KB (%llu bytes), down_flow=%llu KB (%llu bytes)\n",
|
||||
mac, total_up_bytes / 1024, total_up_bytes, total_down_bytes / 1024, total_down_bytes);
|
||||
|
||||
struct timeval cur_time;
|
||||
gettimeofday(&cur_time, NULL);
|
||||
time_t cur_time_t = cur_time.tv_sec;
|
||||
u_int32_t today_start = get_today_start_timestamp();
|
||||
if ((u_int32_t)cur_time.tv_sec >= today_start && ((u_int32_t)cur_time.tv_sec - today_start) < 120) {
|
||||
json_object_put(root);
|
||||
return;
|
||||
}
|
||||
struct tm *tm_info = localtime(&cur_time_t);
|
||||
int hour = -1;
|
||||
if (tm_info) {
|
||||
hour = tm_info->tm_hour;
|
||||
}
|
||||
|
||||
daily_hourly_stat_t *today_stat = NULL;
|
||||
if (hour >= 0 && hour < HOURS_PER_DAY) {
|
||||
today_stat = get_today_stat(node);
|
||||
|
||||
if (node->online == 1) {
|
||||
if (today_stat) {
|
||||
today_stat->hourly_online_time[hour] += REPORT_INTERVAL_SECS;
|
||||
LOG_DEBUG("fwx_netlink: updated hourly_online_time[%d] for %s: +%d seconds\n",
|
||||
hour, mac, REPORT_INTERVAL_SECS);
|
||||
}
|
||||
}
|
||||
|
||||
if (node->active == 1) {
|
||||
if (today_stat) {
|
||||
today_stat->hourly_active_time[hour] += REPORT_INTERVAL_SECS;
|
||||
LOG_DEBUG("fwx_netlink: updated hourly_active_time[%d] for %s: +%d seconds\n",
|
||||
hour, mac, REPORT_INTERVAL_SECS);
|
||||
}
|
||||
}
|
||||
update_online_session_activity(node, REPORT_INTERVAL_SECS, node->active == 1 ? REPORT_INTERVAL_SECS : 0);
|
||||
}
|
||||
|
||||
|
||||
if (hour >= 0 && hour < HOURS_PER_DAY && (total_up_bytes > 0 || total_down_bytes > 0)) {
|
||||
if (today_stat) {
|
||||
|
||||
today_stat->hourly_traffic[hour].up_bytes += total_up_bytes;
|
||||
today_stat->hourly_traffic[hour].down_bytes += total_down_bytes;
|
||||
LOG_DEBUG("fwx_netlink: updated hourly_traffic[%d] for %s: up_bytes=%llu, down_bytes=%llu\n",
|
||||
hour, mac, today_stat->hourly_traffic[hour].up_bytes, today_stat->hourly_traffic[hour].down_bytes);
|
||||
}
|
||||
|
||||
|
||||
u_int32_t today = get_today_start_timestamp();
|
||||
if (g_global_traffic_date != today) {
|
||||
memset(g_global_hourly_traffic, 0, sizeof(g_global_hourly_traffic));
|
||||
g_global_traffic_date = today;
|
||||
}
|
||||
g_global_hourly_traffic[hour].up_bytes += total_up_bytes;
|
||||
g_global_hourly_traffic[hour].down_bytes += total_down_bytes;
|
||||
}
|
||||
update_online_session_flow(node, total_up_bytes, total_down_bytes);
|
||||
|
||||
struct json_object *visit_array = json_object_object_get(root, "visit_info");
|
||||
if (!visit_array)
|
||||
{
|
||||
json_object_put(root);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < json_object_array_length(visit_array); i++)
|
||||
{
|
||||
struct json_object *visit_obj = json_object_array_get_idx(visit_array, i);
|
||||
struct json_object *appid_obj = json_object_object_get(visit_obj, "appid");
|
||||
struct json_object *action_obj = json_object_object_get(visit_obj, "latest_action");
|
||||
|
||||
int appid = json_object_get_int(appid_obj);
|
||||
int action = json_object_get_int(action_obj);
|
||||
|
||||
type = appid / 1000;
|
||||
id = appid % 1000;
|
||||
if (id <= 0 || type <= 0)
|
||||
continue;
|
||||
update_online_session_recent_app(node, appid);
|
||||
|
||||
|
||||
visit_stat_t *stat_node = NULL;
|
||||
int found_stat = 0;
|
||||
list_for_each_entry(stat_node, &node->stat_list, list) {
|
||||
if (stat_node->appid == appid) {
|
||||
stat_node->total_time += REPORT_INTERVAL_SECS;
|
||||
found_stat = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!found_stat) {
|
||||
stat_node = (visit_stat_t *)calloc(1, sizeof(visit_stat_t));
|
||||
if (stat_node) {
|
||||
stat_node->appid = appid;
|
||||
stat_node->total_time = REPORT_INTERVAL_SECS;
|
||||
INIT_LIST_HEAD(&stat_node->list);
|
||||
list_add(&stat_node->list, &node->stat_list);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
update_global_app_type_stats(appid, REPORT_INTERVAL_SECS);
|
||||
|
||||
|
||||
|
||||
visit_info_t *p = NULL;
|
||||
int found = 0;
|
||||
int cur_time_sec = cur_time.tv_sec;
|
||||
|
||||
list_for_each_entry(p, &node->online_visit, visit) {
|
||||
if (p->appid == appid && p->action == action) {
|
||||
p->latest_time = cur_time_sec;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!found) {
|
||||
p = (visit_info_t *)calloc(1, sizeof(visit_info_t));
|
||||
if (!p)
|
||||
continue;
|
||||
p->appid = appid;
|
||||
p->first_time = cur_time_sec;
|
||||
p->latest_time = cur_time_sec;
|
||||
p->action = action;
|
||||
INIT_LIST_HEAD(&p->visit);
|
||||
|
||||
add_visit_info_node(&node->online_visit, p);
|
||||
|
||||
}
|
||||
}
|
||||
json_object_put(root);
|
||||
}
|
||||
|
||||
#define MAX_NL_MSG_LEN 1024
|
||||
int fwx_nl_send_msg_to_kernel(int fd, void *msg, int len)
|
||||
{
|
||||
struct sockaddr_nl saddr, daddr;
|
||||
memset(&daddr, 0, sizeof(daddr));
|
||||
daddr.nl_family = AF_NETLINK;
|
||||
daddr.nl_pid = 0; // to kernel
|
||||
daddr.nl_groups = 0;
|
||||
int ret = 0;
|
||||
struct nlmsghdr *nlh = NULL;
|
||||
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_NL_MSG_LEN));
|
||||
nlh->nlmsg_len = NLMSG_SPACE(MAX_NL_MSG_LEN);
|
||||
nlh->nlmsg_flags = 0;
|
||||
nlh->nlmsg_type = 0;
|
||||
nlh->nlmsg_seq = 0;
|
||||
nlh->nlmsg_pid = DEFAULT_FWX_NL_PID;
|
||||
|
||||
char msg_buf[MAX_NL_MSG_LEN] = {0};
|
||||
struct fwx_nl_msg_hdr *hdr = (struct fwx_nl_msg_hdr *)msg_buf;
|
||||
hdr->magic = 0xa0b0c0d0;
|
||||
hdr->len = len;
|
||||
char *p_data = msg_buf + sizeof(struct fwx_nl_msg_hdr);
|
||||
memcpy(p_data, msg, len);
|
||||
|
||||
memcpy(NLMSG_DATA(nlh), msg_buf, len + sizeof(struct fwx_nl_msg_hdr));
|
||||
|
||||
ret = sendto(fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
|
||||
free(nlh);
|
||||
if (!ret)
|
||||
{
|
||||
perror("sendto error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fwx_netlink_init(void)
|
||||
{
|
||||
int fd;
|
||||
struct sockaddr_nl nls;
|
||||
fd = socket(AF_NETLINK, SOCK_RAW, FWX_NETLINK_ID);
|
||||
if (fd < 0)
|
||||
{
|
||||
LOG_DEBUG("Connect netlink %d failed %s\n", FWX_NETLINK_ID, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
memset(&nls, 0, sizeof(struct sockaddr_nl));
|
||||
nls.nl_pid = DEFAULT_FWX_NL_PID;
|
||||
nls.nl_groups = 0;
|
||||
nls.nl_family = AF_NETLINK;
|
||||
|
||||
if (bind(fd, (void *)&nls, sizeof(struct sockaddr_nl)))
|
||||
{
|
||||
LOG_DEBUG("Bind failed %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_NETLINK_H__
|
||||
#define __FWX_NETLINK_H__
|
||||
#define DEFAULT_FWX_NL_PID 999
|
||||
#define FWX_NETLINK_ID 29
|
||||
#define MAX_FWX_NETLINK_MSG_LEN 1024
|
||||
#define MAX_AF_MSG_DATA_LEN 800
|
||||
#define MAX_FEATURE_LINE_LEN 800
|
||||
|
||||
struct fwx_nl_msg_hdr
|
||||
{
|
||||
int magic;
|
||||
int len;
|
||||
};
|
||||
|
||||
enum E_FWX_NL_MSG_TYPE
|
||||
{
|
||||
FWX_NL_MSG_INIT,
|
||||
FWX_NL_MSG_ADD_FEATURE,
|
||||
FWX_NL_MSG_CLEAN_FEATURE,
|
||||
FWX_NL_MSG_FEATURE_LOAD_DONE,
|
||||
FWX_NL_MSG_MAX
|
||||
};
|
||||
|
||||
typedef struct fwx_nl_msg
|
||||
{
|
||||
int action;
|
||||
} fwx_nl_msg_t;
|
||||
|
||||
typedef struct fwx_nl_feature_msg{
|
||||
fwx_nl_msg_t hdr;
|
||||
char feature[MAX_FEATURE_LINE_LEN];
|
||||
} fwx_nl_feature_msg_t;
|
||||
|
||||
int fwx_netlink_init(void);
|
||||
void fwx_netlink_handler(struct uloop_fd *u, unsigned int ev);
|
||||
int fwx_nl_send_msg_to_kernel(int fd, void *msg, int len);
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_NETWORK_H__
|
||||
#define __FWX_NETWORK_H__
|
||||
|
||||
#include <json-c/json.h>
|
||||
|
||||
#define MAX_INET_ADDR_LEN 32
|
||||
|
||||
typedef struct iface_status{
|
||||
int proto;
|
||||
char ip[MAX_INET_ADDR_LEN];
|
||||
char mask[MAX_INET_ADDR_LEN];
|
||||
char gateway[MAX_INET_ADDR_LEN];
|
||||
char dns1[MAX_INET_ADDR_LEN];
|
||||
char dns2[MAX_INET_ADDR_LEN];
|
||||
}iface_status_t;
|
||||
|
||||
int get_iface_status(char *ifname, iface_status_t *status);
|
||||
char *get_interface_status_buf(char *ifname);
|
||||
char *cidr2str(int cidr);
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_lan_list(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_add_lan(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_mod_lan(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_del_lan(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_get_wan_list(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_add_wan(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_mod_wan(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_del_wan(struct json_object *req_obj);
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_lan_info(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_lan_info(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_get_wan_info(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_wan_info(struct json_object *req_obj);
|
||||
|
||||
|
||||
struct json_object *fwx_api_get_work_mode(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_work_mode(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,462 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <ctype.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/utils.h>
|
||||
#include <libubus.h>
|
||||
#include <uci.h>
|
||||
#include "fwx_user.h"
|
||||
#include "fwx_netlink.h"
|
||||
#include "fwx_ubus.h"
|
||||
#include "fwx_config.h"
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "fwx.h"
|
||||
#include <stdio.h>
|
||||
#include <json-c/json.h>
|
||||
#include "fwx_record.h"
|
||||
|
||||
#define RECORD_WHITELIST_STATE_FILE "/tmp/record_whitelist_state"
|
||||
#define RECORD_WHITELIST_SECTION_TYPE "whitelist"
|
||||
#define RECORD_WHITELIST_UCI_KEY "fwx_record.@whitelist[0].whitelist"
|
||||
#define RECORD_WHITELIST_LEGACY_SECTION_TYPE "record"
|
||||
#define RECORD_WHITELIST_LEGACY_UCI_KEY "fwx_record.@record[0].whitelist"
|
||||
|
||||
static void set_state_file(const char *file_path)
|
||||
{
|
||||
FILE *fd = fopen(file_path, "w");
|
||||
if (fd) {
|
||||
fprintf(fd, "1");
|
||||
fclose(fd);
|
||||
LOG_DEBUG("Set state file: %s\n", file_path);
|
||||
} else {
|
||||
LOG_ERROR("Failed to set state file: %s\n", file_path);
|
||||
}
|
||||
}
|
||||
|
||||
static void normalize_mac(const char *src, char *dst, int dst_len)
|
||||
{
|
||||
int i = 0;
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
|
||||
if (!src || !dst || dst_len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (src[start] == ' ' || src[start] == '\t' || src[start] == '\r' || src[start] == '\n') {
|
||||
start++;
|
||||
}
|
||||
|
||||
end = (int)strlen(src);
|
||||
while (end > start &&
|
||||
(src[end - 1] == ' ' || src[end - 1] == '\t' || src[end - 1] == '\r' || src[end - 1] == '\n')) {
|
||||
end--;
|
||||
}
|
||||
|
||||
for (i = start; i < end && (i - start) < (dst_len - 1); i++) {
|
||||
dst[i - start] = (char)toupper((unsigned char)src[i]);
|
||||
}
|
||||
dst[i - start] = '\0';
|
||||
}
|
||||
|
||||
static int is_valid_mac(const char *mac)
|
||||
{
|
||||
int i;
|
||||
if (!mac || strlen(mac) != 17) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < 17; i++) {
|
||||
if (i % 3 == 2) {
|
||||
if (mac[i] != ':') {
|
||||
return 0;
|
||||
}
|
||||
} else if (!isxdigit((unsigned char)mac[i])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int mac_exists_in_array(struct json_object *array_obj, const char *mac)
|
||||
{
|
||||
int i;
|
||||
int len;
|
||||
if (!array_obj || !mac) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
len = json_object_array_length(array_obj);
|
||||
for (i = 0; i < len; i++) {
|
||||
struct json_object *mac_obj = json_object_array_get_idx(array_obj, i);
|
||||
const char *exist_mac = json_object_get_string(mac_obj);
|
||||
if (exist_mac && strcasecmp(exist_mac, mac) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ensure_record_section(struct uci_context *uci_ctx)
|
||||
{
|
||||
int num = 0;
|
||||
int legacy_num = 0;
|
||||
char whitelist_buf[4096] = {0};
|
||||
char token_buf[4096] = {0};
|
||||
char *token = NULL;
|
||||
char *save_ptr = NULL;
|
||||
char norm_mac[32] = {0};
|
||||
|
||||
if (!uci_ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
num = fwx_uci_get_list_num(uci_ctx, "fwx_record", RECORD_WHITELIST_SECTION_TYPE);
|
||||
if (num > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
legacy_num = fwx_uci_get_list_num(uci_ctx, "fwx_record", RECORD_WHITELIST_LEGACY_SECTION_TYPE);
|
||||
|
||||
if (fwx_uci_add_section(uci_ctx, "fwx_record", RECORD_WHITELIST_SECTION_TYPE) != UCI_OK) {
|
||||
LOG_ERROR("Failed to add fwx_record whitelist section\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (legacy_num > 0 &&
|
||||
fwx_uci_get_list_value(uci_ctx, RECORD_WHITELIST_LEGACY_UCI_KEY,
|
||||
whitelist_buf, sizeof(whitelist_buf), " ") == 0) {
|
||||
snprintf(token_buf, sizeof(token_buf), "%s", whitelist_buf);
|
||||
token = strtok_r(token_buf, " ", &save_ptr);
|
||||
while (token) {
|
||||
memset(norm_mac, 0, sizeof(norm_mac));
|
||||
normalize_mac(token, norm_mac, sizeof(norm_mac));
|
||||
if (is_valid_mac(norm_mac)) {
|
||||
fwx_uci_add_list(uci_ctx, RECORD_WHITELIST_UCI_KEY, norm_mac);
|
||||
}
|
||||
token = strtok_r(NULL, " ", &save_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (fwx_uci_commit(uci_ctx, "fwx_record") != UCI_OK) {
|
||||
LOG_ERROR("Failed to commit fwx_record section\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_record_whitelist_from_uci(struct uci_context *uci_ctx, struct json_object *mac_array)
|
||||
{
|
||||
char whitelist_buf[4096] = {0};
|
||||
char norm_mac[32] = {0};
|
||||
char *token = NULL;
|
||||
char *save_ptr = NULL;
|
||||
int count = 0;
|
||||
|
||||
if (!uci_ctx || !mac_array) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fwx_uci_get_list_value(uci_ctx, RECORD_WHITELIST_UCI_KEY, whitelist_buf, sizeof(whitelist_buf), " ") != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
token = strtok_r(whitelist_buf, " ", &save_ptr);
|
||||
while (token) {
|
||||
memset(norm_mac, 0, sizeof(norm_mac));
|
||||
normalize_mac(token, norm_mac, sizeof(norm_mac));
|
||||
if (is_valid_mac(norm_mac) && !mac_exists_in_array(mac_array, norm_mac)) {
|
||||
json_object_array_add(mac_array, json_object_new_string(norm_mac));
|
||||
count++;
|
||||
}
|
||||
token = strtok_r(NULL, " ", &save_ptr);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_record_whitelist(struct json_object *req_obj)
|
||||
{
|
||||
int i;
|
||||
int page = 1;
|
||||
int page_size = 15;
|
||||
int total_num = 0;
|
||||
int total_page = 1;
|
||||
int start_idx = 0;
|
||||
int end_idx = 0;
|
||||
struct json_object *page_obj = NULL;
|
||||
struct json_object *page_size_obj = NULL;
|
||||
struct json_object *data_obj = json_object_new_object();
|
||||
struct json_object *all_list_obj = json_object_new_array();
|
||||
struct json_object *page_list_obj = json_object_new_array();
|
||||
struct uci_context *uci_ctx = NULL;
|
||||
|
||||
if (req_obj) {
|
||||
page_obj = json_object_object_get(req_obj, "page");
|
||||
page_size_obj = json_object_object_get(req_obj, "page_size");
|
||||
if (page_obj) {
|
||||
page = json_object_get_int(page_obj);
|
||||
if (page < 1) {
|
||||
page = 1;
|
||||
}
|
||||
}
|
||||
if (page_size_obj) {
|
||||
page_size = json_object_get_int(page_size_obj);
|
||||
if (page_size < 1) {
|
||||
page_size = 15;
|
||||
}
|
||||
if (page_size > 200) {
|
||||
page_size = 200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
json_object_put(data_obj);
|
||||
json_object_put(all_list_obj);
|
||||
json_object_put(page_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
ensure_record_section(uci_ctx);
|
||||
load_record_whitelist_from_uci(uci_ctx, all_list_obj);
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
total_num = json_object_array_length(all_list_obj);
|
||||
total_page = (total_num + page_size - 1) / page_size;
|
||||
if (total_page < 1) {
|
||||
total_page = 1;
|
||||
}
|
||||
if (page > total_page) {
|
||||
page = total_page;
|
||||
}
|
||||
|
||||
start_idx = (page - 1) * page_size;
|
||||
end_idx = start_idx + page_size;
|
||||
if (end_idx > total_num) {
|
||||
end_idx = total_num;
|
||||
}
|
||||
|
||||
for (i = start_idx; i < end_idx; i++) {
|
||||
struct json_object *item_obj = json_object_new_object();
|
||||
const char *mac = NULL;
|
||||
client_node_t *dev = NULL;
|
||||
|
||||
mac = json_object_get_string(json_object_array_get_idx(all_list_obj, i));
|
||||
if (!mac) {
|
||||
continue;
|
||||
}
|
||||
dev = find_client_node((char *)mac);
|
||||
|
||||
json_object_object_add(item_obj, "mac", json_object_new_string(mac));
|
||||
if (dev) {
|
||||
json_object_object_add(item_obj, "nickname", json_object_new_string(dev->nickname));
|
||||
json_object_object_add(item_obj, "hostname", json_object_new_string(dev->hostname));
|
||||
} else {
|
||||
json_object_object_add(item_obj, "nickname", json_object_new_string(""));
|
||||
json_object_object_add(item_obj, "hostname", json_object_new_string(""));
|
||||
}
|
||||
json_object_array_add(page_list_obj, item_obj);
|
||||
}
|
||||
|
||||
json_object_object_add(data_obj, "total_num", json_object_new_int(total_num));
|
||||
json_object_object_add(data_obj, "total_page", json_object_new_int(total_page));
|
||||
json_object_object_add(data_obj, "page", json_object_new_int(page));
|
||||
json_object_object_add(data_obj, "page_size", json_object_new_int(page_size));
|
||||
json_object_object_add(data_obj, "list", page_list_obj);
|
||||
|
||||
json_object_put(all_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_add_record_whitelist(struct json_object *req_obj)
|
||||
{
|
||||
int i;
|
||||
int added = 0;
|
||||
int mac_list_len = 0;
|
||||
char norm_mac[32] = {0};
|
||||
struct json_object *mac_list_obj = NULL;
|
||||
struct json_object *exist_list_obj = json_object_new_array();
|
||||
struct uci_context *uci_ctx = NULL;
|
||||
|
||||
if (!req_obj) {
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
mac_list_obj = json_object_object_get(req_obj, "mac_list");
|
||||
if (!mac_list_obj) {
|
||||
LOG_ERROR("mac_list_obj is NULL\n");
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (ensure_record_section(uci_ctx) != 0) {
|
||||
uci_free_context(uci_ctx);
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
load_record_whitelist_from_uci(uci_ctx, exist_list_obj);
|
||||
mac_list_len = json_object_array_length(mac_list_obj);
|
||||
|
||||
for (i = 0; i < mac_list_len; i++) {
|
||||
struct json_object *mac_item = json_object_array_get_idx(mac_list_obj, i);
|
||||
const char *raw_mac = json_object_get_string(mac_item);
|
||||
|
||||
memset(norm_mac, 0, sizeof(norm_mac));
|
||||
normalize_mac(raw_mac, norm_mac, sizeof(norm_mac));
|
||||
if (!is_valid_mac(norm_mac)) {
|
||||
continue;
|
||||
}
|
||||
if (mac_exists_in_array(exist_list_obj, norm_mac)) {
|
||||
continue;
|
||||
}
|
||||
fwx_uci_add_list(uci_ctx, RECORD_WHITELIST_UCI_KEY, norm_mac);
|
||||
json_object_array_add(exist_list_obj, json_object_new_string(norm_mac));
|
||||
added++;
|
||||
}
|
||||
|
||||
if (added > 0) {
|
||||
fwx_uci_commit(uci_ctx, "fwx_record");
|
||||
set_state_file(RECORD_WHITELIST_STATE_FILE);
|
||||
}
|
||||
|
||||
uci_free_context(uci_ctx);
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_del_record_whitelist(struct json_object *req_obj)
|
||||
{
|
||||
int found = 0;
|
||||
char norm_mac[32] = {0};
|
||||
struct json_object *mac_obj = NULL;
|
||||
struct json_object *exist_list_obj = json_object_new_array();
|
||||
struct uci_context *uci_ctx = NULL;
|
||||
|
||||
if (!req_obj) {
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
mac_obj = json_object_object_get(req_obj, "mac");
|
||||
if (!mac_obj) {
|
||||
LOG_ERROR("mac_obj is NULL\n");
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
normalize_mac(json_object_get_string(mac_obj), norm_mac, sizeof(norm_mac));
|
||||
if (!is_valid_mac(norm_mac)) {
|
||||
LOG_ERROR("invalid mac: %s\n", norm_mac);
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (ensure_record_section(uci_ctx) != 0) {
|
||||
uci_free_context(uci_ctx);
|
||||
json_object_put(exist_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
load_record_whitelist_from_uci(uci_ctx, exist_list_obj);
|
||||
if (mac_exists_in_array(exist_list_obj, norm_mac)) {
|
||||
fwx_uci_del_list(uci_ctx, RECORD_WHITELIST_UCI_KEY, norm_mac);
|
||||
fwx_uci_commit(uci_ctx, "fwx_record");
|
||||
set_state_file(RECORD_WHITELIST_STATE_FILE);
|
||||
found = 1;
|
||||
}
|
||||
|
||||
uci_free_context(uci_ctx);
|
||||
json_object_put(exist_list_obj);
|
||||
LOG_DEBUG("Delete record whitelist mac=%s, found=%d\n", norm_mac, found);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_set_record_whitelist(struct json_object *req_obj)
|
||||
{
|
||||
int i;
|
||||
int mac_list_len = 0;
|
||||
struct json_object *mac_list_obj = NULL;
|
||||
struct json_object *new_list_obj = json_object_new_array();
|
||||
struct uci_context *uci_ctx = NULL;
|
||||
|
||||
if (!req_obj) {
|
||||
json_object_put(new_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
mac_list_obj = json_object_object_get(req_obj, "mac_list");
|
||||
if (!mac_list_obj) {
|
||||
LOG_ERROR("mac_list_obj is NULL\n");
|
||||
json_object_put(new_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
json_object_put(new_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (ensure_record_section(uci_ctx) != 0) {
|
||||
uci_free_context(uci_ctx);
|
||||
json_object_put(new_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
fwx_uci_delete(uci_ctx, RECORD_WHITELIST_UCI_KEY);
|
||||
mac_list_len = json_object_array_length(mac_list_obj);
|
||||
|
||||
for (i = 0; i < mac_list_len; i++) {
|
||||
char norm_mac[32] = {0};
|
||||
struct json_object *mac_item = json_object_array_get_idx(mac_list_obj, i);
|
||||
const char *raw_mac = json_object_get_string(mac_item);
|
||||
|
||||
normalize_mac(raw_mac, norm_mac, sizeof(norm_mac));
|
||||
if (!is_valid_mac(norm_mac)) {
|
||||
continue;
|
||||
}
|
||||
if (mac_exists_in_array(new_list_obj, norm_mac)) {
|
||||
continue;
|
||||
}
|
||||
fwx_uci_add_list(uci_ctx, RECORD_WHITELIST_UCI_KEY, norm_mac);
|
||||
json_object_array_add(new_list_obj, json_object_new_string(norm_mac));
|
||||
}
|
||||
|
||||
fwx_uci_commit(uci_ctx, "fwx_record");
|
||||
set_state_file(RECORD_WHITELIST_STATE_FILE);
|
||||
|
||||
uci_free_context(uci_ctx);
|
||||
json_object_put(new_list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_RECORD_H__
|
||||
#define __FWX_RECORD_H__
|
||||
|
||||
#include "fwx.h"
|
||||
|
||||
struct json_object *fwx_api_get_record_whitelist(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_add_record_whitelist(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_del_record_whitelist(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_record_whitelist(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,214 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "fwx.h"
|
||||
#include "fwx_stat.h"
|
||||
|
||||
#define SESSION_MAX_POINTS (24 * 60)
|
||||
#define SESSION_SAMPLE_INTERVAL_SEC 5
|
||||
#define SESSION_DETAIL_MAX_POINTS (5 * 60 / SESSION_SAMPLE_INTERVAL_SEC)
|
||||
#define SESSION_SAMPLES_PER_MINUTE (60 / SESSION_SAMPLE_INTERVAL_SEC)
|
||||
|
||||
typedef struct session_point {
|
||||
int value;
|
||||
} session_point_t;
|
||||
|
||||
typedef struct session_ring {
|
||||
session_point_t *points;
|
||||
int max_points;
|
||||
int start;
|
||||
int count;
|
||||
} session_ring_t;
|
||||
|
||||
typedef struct session_stat_ctx {
|
||||
session_point_t minute_points[SESSION_MAX_POINTS];
|
||||
session_point_t detail_points[SESSION_DETAIL_MAX_POINTS];
|
||||
session_ring_t minute_ring;
|
||||
session_ring_t detail_ring;
|
||||
int detail_sample_count;
|
||||
int inited;
|
||||
} session_stat_ctx_t;
|
||||
|
||||
static session_stat_ctx_t g_session_stat;
|
||||
|
||||
static void session_stat_init(void)
|
||||
{
|
||||
if (g_session_stat.inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&g_session_stat, 0, sizeof(g_session_stat));
|
||||
g_session_stat.minute_ring.points = g_session_stat.minute_points;
|
||||
g_session_stat.minute_ring.max_points = SESSION_MAX_POINTS;
|
||||
g_session_stat.detail_ring.points = g_session_stat.detail_points;
|
||||
g_session_stat.detail_ring.max_points = SESSION_DETAIL_MAX_POINTS;
|
||||
g_session_stat.inited = 1;
|
||||
}
|
||||
|
||||
int fwx_stat_read_conntrack_count(void)
|
||||
{
|
||||
const char *path = "/proc/sys/net/netfilter/nf_conntrack_count";
|
||||
char line[64] = {0};
|
||||
FILE *fp = fopen(path, "r");
|
||||
char *endptr = NULL;
|
||||
long v;
|
||||
|
||||
if (!fp) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!fgets(line, sizeof(line), fp)) {
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
v = strtol(line, &endptr, 10);
|
||||
if (endptr == line || v < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
static void session_ring_append(session_ring_t *ring, int value)
|
||||
{
|
||||
int idx;
|
||||
if (!ring || !ring->points || ring->max_points <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ring->count < ring->max_points) {
|
||||
idx = (ring->start + ring->count) % ring->max_points;
|
||||
ring->count++;
|
||||
} else {
|
||||
idx = ring->start;
|
||||
ring->start = (ring->start + 1) % ring->max_points;
|
||||
}
|
||||
ring->points[idx].value = value;
|
||||
}
|
||||
|
||||
void fwx_session_stat_tick(void)
|
||||
{
|
||||
int current = fwx_stat_read_conntrack_count();
|
||||
session_stat_init();
|
||||
session_ring_append(&g_session_stat.detail_ring, current);
|
||||
g_session_stat.detail_sample_count++;
|
||||
if (g_session_stat.detail_sample_count >= SESSION_SAMPLES_PER_MINUTE) {
|
||||
session_ring_append(&g_session_stat.minute_ring, current);
|
||||
g_session_stat.detail_sample_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_history_session(struct json_object *req_obj)
|
||||
{
|
||||
const char *range = "hour";
|
||||
int minutes = 60;
|
||||
int current;
|
||||
int i;
|
||||
int start_offset = 0;
|
||||
struct json_object *data_obj = json_object_new_object();
|
||||
struct json_object *list_obj = json_object_new_array();
|
||||
long long sum = 0;
|
||||
int sample_count = 0;
|
||||
int peak = 0;
|
||||
int avg = 0;
|
||||
int is_5min = 0;
|
||||
int total_count = 0;
|
||||
int ring_start = 0;
|
||||
int ring_max = 0;
|
||||
session_point_t *points = NULL;
|
||||
struct json_object *range_obj = req_obj ? json_object_object_get(req_obj, "range") : NULL;
|
||||
struct json_object *minutes_obj = req_obj ? json_object_object_get(req_obj, "minutes") : NULL;
|
||||
session_ring_t *ring = NULL;
|
||||
|
||||
session_stat_init();
|
||||
|
||||
if (range_obj) {
|
||||
const char *range_str = json_object_get_string(range_obj);
|
||||
if (range_str && strlen(range_str) > 0) {
|
||||
range = range_str;
|
||||
}
|
||||
}
|
||||
|
||||
if (range && (!strcmp(range, "5min") || !strcmp(range, "5m"))) {
|
||||
is_5min = 1;
|
||||
minutes = 5;
|
||||
} else if (minutes_obj) {
|
||||
int tmp = json_object_get_int(minutes_obj);
|
||||
if (tmp > 0) {
|
||||
minutes = tmp;
|
||||
}
|
||||
} else if (range && strcmp(range, "day") == 0) {
|
||||
minutes = 24 * 60;
|
||||
} else {
|
||||
minutes = 60;
|
||||
}
|
||||
|
||||
if (is_5min) {
|
||||
ring = &g_session_stat.detail_ring;
|
||||
} else {
|
||||
ring = &g_session_stat.minute_ring;
|
||||
if (minutes > SESSION_MAX_POINTS) {
|
||||
minutes = SESSION_MAX_POINTS;
|
||||
}
|
||||
if (minutes < 1) {
|
||||
minutes = 1;
|
||||
}
|
||||
}
|
||||
|
||||
total_count = ring->count;
|
||||
ring_start = ring->start;
|
||||
ring_max = ring->max_points;
|
||||
points = ring->points;
|
||||
|
||||
current = fwx_stat_read_conntrack_count();
|
||||
|
||||
if (is_5min) {
|
||||
if (total_count > SESSION_DETAIL_MAX_POINTS) {
|
||||
start_offset = total_count - SESSION_DETAIL_MAX_POINTS;
|
||||
}
|
||||
} else if (total_count > minutes) {
|
||||
start_offset = total_count - minutes;
|
||||
}
|
||||
|
||||
for (i = start_offset; i < total_count; i++) {
|
||||
int idx = (ring_start + i) % ring_max;
|
||||
session_point_t *pt = &points[idx];
|
||||
json_object_array_add(list_obj, json_object_new_int(pt->value));
|
||||
sum += pt->value;
|
||||
sample_count++;
|
||||
if (sample_count == 1 || pt->value > peak) {
|
||||
peak = pt->value;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_count == 0) {
|
||||
json_object_array_add(list_obj, json_object_new_int(current));
|
||||
avg = current;
|
||||
peak = current;
|
||||
} else {
|
||||
avg = (int)((sum + (sample_count / 2)) / sample_count);
|
||||
if (current > peak) {
|
||||
peak = current;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_5min) {
|
||||
json_object_object_add(data_obj, "range", json_object_new_string("5min"));
|
||||
json_object_object_add(data_obj, "minutes", json_object_new_int(5));
|
||||
} else {
|
||||
json_object_object_add(data_obj, "range", json_object_new_string((minutes >= 24 * 60) ? "day" : "hour"));
|
||||
json_object_object_add(data_obj, "minutes", json_object_new_int(minutes));
|
||||
}
|
||||
json_object_object_add(data_obj, "current", json_object_new_int(current));
|
||||
json_object_object_add(data_obj, "avg", json_object_new_int(avg));
|
||||
json_object_object_add(data_obj, "peak", json_object_new_int(peak));
|
||||
json_object_object_add(data_obj, "list", list_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
|
||||
#ifndef __FWX_STAT_H__
|
||||
#define __FWX_STAT_H__
|
||||
|
||||
#include <json-c/json.h>
|
||||
|
||||
int fwx_stat_read_conntrack_count(void);
|
||||
void fwx_session_stat_tick(void);
|
||||
struct json_object *fwx_api_get_history_session(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,292 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <libubox/uloop.h>
|
||||
#include <libubox/utils.h>
|
||||
#include <libubus.h>
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <json-c/json.h>
|
||||
#include <uci.h>
|
||||
#include "fwx.h"
|
||||
#include "fwx_user.h"
|
||||
#include "fwx_netlink.h"
|
||||
#include "fwx_ubus.h"
|
||||
#include "fwx_config.h"
|
||||
#include "fwx_utils.h"
|
||||
#include "fwx_uci.h"
|
||||
|
||||
static void ensure_fwx_advanced_section(struct uci_context *uci_ctx)
|
||||
{
|
||||
char section_type[32] = {0};
|
||||
|
||||
if (fwx_uci_get_value(uci_ctx, "fwx.advanced", section_type, sizeof(section_type)) != 0)
|
||||
fwx_uci_set_value(uci_ctx, "fwx.advanced", "advanced");
|
||||
}
|
||||
|
||||
static void ensure_fwx_status_section(struct uci_context *uci_ctx)
|
||||
{
|
||||
char section_type[32] = {0};
|
||||
|
||||
if (fwx_uci_get_value(uci_ctx, "fwx.status", section_type, sizeof(section_type)) != 0)
|
||||
fwx_uci_set_value(uci_ctx, "fwx.status", "status");
|
||||
}
|
||||
|
||||
int fwx_get_disable_hnat(void)
|
||||
{
|
||||
int disable_hnat = 0;
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
|
||||
if (!uci_ctx)
|
||||
return 0;
|
||||
|
||||
disable_hnat = fwx_uci_get_int_value(uci_ctx, "fwx.advanced.disable_hnat");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
return disable_hnat == 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
int fwx_get_notice_status(void)
|
||||
{
|
||||
int notice_status = 0;
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
|
||||
if (!uci_ctx)
|
||||
return 0;
|
||||
|
||||
notice_status = fwx_uci_get_int_value(uci_ctx, "fwx.status.notice_status");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
return notice_status == 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_system_info(struct json_object *req_obj) {
|
||||
struct json_object *data_obj = json_object_new_object();
|
||||
struct json_object *fwx_obj = json_object_new_object();
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
json_object_put(fwx_obj);
|
||||
json_object_put(data_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
char lan_ifname[32] = {0};
|
||||
int ret = fwx_uci_get_value(uci_ctx, "fwx.global.lan_ifname", lan_ifname, sizeof(lan_ifname) - 1);
|
||||
if (ret != 0) {
|
||||
strncpy(lan_ifname, "br-lan", sizeof(lan_ifname) - 1);
|
||||
}
|
||||
|
||||
char theme_mode_str[8] = {0};
|
||||
int theme_mode = 0;
|
||||
ret = fwx_uci_get_value(uci_ctx, "fwx.global.theme_mode", theme_mode_str, sizeof(theme_mode_str) - 1);
|
||||
if (ret == 0) {
|
||||
theme_mode = atoi(theme_mode_str);
|
||||
}
|
||||
|
||||
json_object_object_add(fwx_obj, "lan_ifname", json_object_new_string(lan_ifname));
|
||||
json_object_object_add(fwx_obj, "theme_mode", json_object_new_int(theme_mode));
|
||||
json_object_object_add(data_obj, "fwx", fwx_obj);
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_set_system_info(struct json_object *req_obj) {
|
||||
if (!req_obj) {
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
struct json_object *fwx_obj = json_object_object_get(req_obj, "fwx");
|
||||
if (!fwx_obj) {
|
||||
LOG_ERROR("Missing fwx parameter\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
struct json_object *lan_ifname_obj = json_object_object_get(fwx_obj, "lan_ifname");
|
||||
if (!lan_ifname_obj) {
|
||||
LOG_ERROR("Missing lan_ifname parameter\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
const char *lan_ifname = json_object_get_string(lan_ifname_obj);
|
||||
if (!lan_ifname || strlen(lan_ifname) == 0) {
|
||||
LOG_ERROR("Invalid lan_ifname value\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (strlen(lan_ifname) < 2 || strlen(lan_ifname) > 16) {
|
||||
LOG_ERROR("lan_ifname length invalid\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
struct json_object *theme_mode_obj = json_object_object_get(fwx_obj, "theme_mode");
|
||||
int theme_mode = 0; // 默认值为0(light)
|
||||
if (theme_mode_obj) {
|
||||
if (json_object_get_type(theme_mode_obj) == json_type_int) {
|
||||
theme_mode = json_object_get_int(theme_mode_obj);
|
||||
} else if (json_object_get_type(theme_mode_obj) == json_type_string) {
|
||||
theme_mode = atoi(json_object_get_string(theme_mode_obj));
|
||||
}
|
||||
// 验证值只能是0或1
|
||||
if (theme_mode != 0 && theme_mode != 1) {
|
||||
LOG_ERROR("Invalid theme_mode value, must be 0 or 1\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
fwx_uci_set_value(uci_ctx, "fwx.global.lan_ifname", (char *)lan_ifname);
|
||||
|
||||
char theme_mode_str[8] = {0};
|
||||
snprintf(theme_mode_str, sizeof(theme_mode_str), "%d", theme_mode);
|
||||
fwx_uci_set_value(uci_ctx, "fwx.global.theme_mode", theme_mode_str);
|
||||
|
||||
fwx_uci_commit(uci_ctx, "fwx");
|
||||
|
||||
update_fwx_proc_value("lan_ifname", lan_ifname);
|
||||
|
||||
uci_free_context(uci_ctx);
|
||||
LOG_DEBUG("Set system config: lan_ifname=%s, theme_mode=%d\n", lan_ifname, theme_mode);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
int fwx_get_tcp_rst(void)
|
||||
{
|
||||
int tcp_rst = 1;
|
||||
struct uci_context *uci_ctx = uci_alloc_context();
|
||||
|
||||
if (!uci_ctx)
|
||||
return 1;
|
||||
|
||||
tcp_rst = fwx_uci_get_int_value(uci_ctx, "fwx.global.tcp_rst");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
return tcp_rst == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_tcp_rst(struct json_object *req_obj)
|
||||
{
|
||||
struct json_object *data_obj = json_object_new_object();
|
||||
|
||||
(void)req_obj;
|
||||
json_object_object_add(data_obj, "tcp_rst", json_object_new_int(fwx_get_tcp_rst()));
|
||||
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_set_tcp_rst(struct json_object *req_obj)
|
||||
{
|
||||
struct json_object *tcp_rst_obj;
|
||||
int tcp_rst;
|
||||
struct uci_context *uci_ctx;
|
||||
|
||||
if (!req_obj)
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
|
||||
tcp_rst_obj = json_object_object_get(req_obj, "tcp_rst");
|
||||
if (!tcp_rst_obj) {
|
||||
LOG_ERROR("Missing tcp_rst parameter\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
tcp_rst = json_object_get_int(tcp_rst_obj) == 0 ? 0 : 1;
|
||||
uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
fwx_uci_set_int_value(uci_ctx, "fwx.global.tcp_rst", tcp_rst);
|
||||
fwx_uci_commit(uci_ctx, "fwx");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
update_fwx_proc_u32_value("tcp_rst", tcp_rst);
|
||||
|
||||
LOG_DEBUG("Set tcp_rst=%d\n", tcp_rst);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_advanced_settings(struct json_object *req_obj)
|
||||
{
|
||||
struct json_object *data_obj = json_object_new_object();
|
||||
|
||||
json_object_object_add(data_obj, "disable_hnat", json_object_new_int(fwx_get_disable_hnat()));
|
||||
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_set_advanced_settings(struct json_object *req_obj)
|
||||
{
|
||||
struct json_object *disable_hnat_obj;
|
||||
int disable_hnat;
|
||||
struct uci_context *uci_ctx;
|
||||
|
||||
if (!req_obj)
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
|
||||
disable_hnat_obj = json_object_object_get(req_obj, "disable_hnat");
|
||||
if (!disable_hnat_obj) {
|
||||
LOG_ERROR("Missing disable_hnat parameter\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
disable_hnat = json_object_get_int(disable_hnat_obj) == 1 ? 1 : 0;
|
||||
uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
ensure_fwx_advanced_section(uci_ctx);
|
||||
fwx_uci_set_int_value(uci_ctx, "fwx.advanced.disable_hnat", disable_hnat);
|
||||
fwx_uci_commit(uci_ctx, "fwx");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
if (disable_hnat == 1)
|
||||
system("/usr/bin/hnat.sh >/dev/null 2>&1");
|
||||
|
||||
LOG_DEBUG("Set advanced settings: disable_hnat=%d\n", disable_hnat);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_set_dashboard_notice_status(struct json_object *req_obj)
|
||||
{
|
||||
struct json_object *notice_status_obj;
|
||||
int notice_status = 1;
|
||||
struct uci_context *uci_ctx;
|
||||
|
||||
if (req_obj) {
|
||||
notice_status_obj = json_object_object_get(req_obj, "notice_status");
|
||||
if (notice_status_obj)
|
||||
notice_status = json_object_get_int(notice_status_obj);
|
||||
}
|
||||
|
||||
notice_status = notice_status == 1 ? 1 : 0;
|
||||
uci_ctx = uci_alloc_context();
|
||||
if (!uci_ctx) {
|
||||
LOG_ERROR("Failed to allocate UCI context\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
ensure_fwx_status_section(uci_ctx);
|
||||
fwx_uci_set_int_value(uci_ctx, "fwx.status.notice_status", notice_status);
|
||||
fwx_uci_commit(uci_ctx, "fwx");
|
||||
uci_free_context(uci_ctx);
|
||||
|
||||
LOG_DEBUG("Set dashboard notice_status=%d\n", notice_status);
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_SYSTEM_H__
|
||||
#define __FWX_SYSTEM_H__
|
||||
|
||||
#include <json-c/json.h>
|
||||
|
||||
struct json_object *get_system_status(void);
|
||||
int fwx_get_disable_hnat(void);
|
||||
int fwx_get_notice_status(void);
|
||||
struct json_object *fwx_api_get_system_info(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_system_info(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_get_tcp_rst(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_tcp_rst(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_get_advanced_settings(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_advanced_settings(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_dashboard_notice_status(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
|
||||
#ifndef __APPFILTER_UBUS_H__
|
||||
#define __APPFILTER_UBUS_H__
|
||||
int fwx_ubus_init(void);
|
||||
#endif
|
||||
@@ -1,42 +1,19 @@
|
||||
/*
|
||||
Copyright (C) 2020 Derry <destan19@126.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "appfilter_config.h"
|
||||
#include "appfilter.h"
|
||||
#include <uci.h>
|
||||
|
||||
app_name_info_t app_name_table[MAX_SUPPORT_APP_NUM];
|
||||
int g_app_count = 0;
|
||||
int g_cur_class_num = 0;
|
||||
char CLASS_NAME_TABLE[MAX_APP_TYPE][MAX_CLASS_NAME_LEN];
|
||||
#include "fwx_uci.h"
|
||||
|
||||
const char *config_path = "./config";
|
||||
static struct uci_context *uci_ctx = NULL;
|
||||
static struct uci_package *uci_appfilter;
|
||||
|
||||
|
||||
int af_uci_get_int_value(struct uci_context *ctx, char *key)
|
||||
int fwx_uci_get_int_value(struct uci_context *ctx, char *key)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -73,7 +50,7 @@ done:
|
||||
}
|
||||
|
||||
|
||||
int af_uci_get_value(struct uci_context *ctx, char *key, char *output, int out_len)
|
||||
int fwx_uci_get_value(struct uci_context *ctx, char *key, char *output, int out_len)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -112,7 +89,7 @@ done:
|
||||
}
|
||||
|
||||
|
||||
int af_uci_delete(struct uci_context *ctx, char *key)
|
||||
int fwx_uci_delete(struct uci_context *ctx, char *key)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -136,7 +113,7 @@ int af_uci_delete(struct uci_context *ctx, char *key)
|
||||
|
||||
|
||||
|
||||
int af_uci_add_list(struct uci_context *ctx, char *key, char *value)
|
||||
int fwx_uci_add_list(struct uci_context *ctx, char *key, char *value)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -163,7 +140,7 @@ int af_uci_add_list(struct uci_context *ctx, char *key, char *value)
|
||||
}
|
||||
|
||||
|
||||
int af_uci_get_list_value(struct uci_context *ctx, char *key, char *output, int out_len, char *delimt)
|
||||
int fwx_uci_get_list_value(struct uci_context *ctx, char *key, char *output, int out_len, char *delimt)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -212,7 +189,7 @@ done:
|
||||
}
|
||||
|
||||
|
||||
int af_uci_add_int_list(struct uci_context *ctx, char *key, int value)
|
||||
int fwx_uci_add_int_list(struct uci_context *ctx, char *key, int value)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -234,7 +211,7 @@ int af_uci_add_int_list(struct uci_context *ctx, char *key, int value)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int af_uci_del_list(struct uci_context *ctx, char *key, char *value)
|
||||
int fwx_uci_del_list(struct uci_context *ctx, char *key, char *value)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -257,7 +234,7 @@ int af_uci_del_list(struct uci_context *ctx, char *key, char *value)
|
||||
}
|
||||
|
||||
|
||||
int af_uci_set_value(struct uci_context *ctx, char *key, char *value)
|
||||
int fwx_uci_set_value(struct uci_context *ctx, char *key, char *value)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -281,7 +258,7 @@ int af_uci_set_value(struct uci_context *ctx, char *key, char *value)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int af_uci_set_int_value(struct uci_context *ctx, char *key, int value)
|
||||
int fwx_uci_set_int_value(struct uci_context *ctx, char *key, int value)
|
||||
{
|
||||
struct uci_element *e;
|
||||
struct uci_ptr ptr;
|
||||
@@ -304,19 +281,19 @@ int af_uci_set_int_value(struct uci_context *ctx, char *key, int value)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int af_uci_del_array_value(struct uci_context *ctx, char *key_fmt, int index){
|
||||
int fwx_uci_del_array_value(struct uci_context *ctx, char *key_fmt, int index){
|
||||
char key[128] = {0};
|
||||
sprintf(key, key_fmt, index);
|
||||
return af_uci_delete(ctx, key);
|
||||
return fwx_uci_delete(ctx, key);
|
||||
}
|
||||
|
||||
int af_uci_set_array_value(struct uci_context *ctx, char *key_fmt, int index, char *value){
|
||||
int fwx_uci_set_array_value(struct uci_context *ctx, char *key_fmt, int index, char *value){
|
||||
char key[128] = {0};
|
||||
sprintf(key, key_fmt, index);
|
||||
return af_uci_set_value(ctx, key, value);
|
||||
return fwx_uci_set_value(ctx, key, value);
|
||||
}
|
||||
|
||||
int af_uci_commit(struct uci_context *ctx, const char * package) {
|
||||
int fwx_uci_commit(struct uci_context *ctx, const char * package) {
|
||||
struct uci_ptr ptr;
|
||||
int ret = UCI_OK;
|
||||
if (!package){
|
||||
@@ -337,7 +314,7 @@ done:
|
||||
return UCI_OK;
|
||||
}
|
||||
|
||||
int af_get_uci_list_num(struct uci_context * ctx, char *package, char *section){
|
||||
int fwx_uci_get_list_num(struct uci_context * ctx, char *package, char *section){
|
||||
int count = 0;
|
||||
struct uci_ptr p;
|
||||
struct uci_element *e;
|
||||
@@ -356,14 +333,14 @@ int af_get_uci_list_num(struct uci_context * ctx, char *package, char *section){
|
||||
uci_unload(ctx, pkg);
|
||||
return count;
|
||||
}
|
||||
int af_uci_get_array_value(struct uci_context *ctx, char *key_fmt, int index, char *output, int out_len)
|
||||
int fwx_uci_get_array_value(struct uci_context *ctx, char *key_fmt, int index, char *output, int out_len)
|
||||
{
|
||||
char key[128] = {0};
|
||||
sprintf(key, key_fmt, index);
|
||||
return af_uci_get_value(ctx, key, output, out_len);
|
||||
return fwx_uci_get_value(ctx, key, output, out_len);
|
||||
}
|
||||
|
||||
int af_uci_add_section(struct uci_context * ctx, char *package_name, char *section)
|
||||
int fwx_uci_add_section(struct uci_context * ctx, char *package_name, char *section)
|
||||
{
|
||||
struct uci_section *s = NULL;
|
||||
struct uci_package *p = NULL;
|
||||
@@ -381,9 +358,8 @@ done:
|
||||
fprintf(stdout, "%s\n", s->e.name);
|
||||
return ret;
|
||||
}
|
||||
//
|
||||
static struct uci_package *
|
||||
config_init_package(const char *config)
|
||||
|
||||
static struct uci_package *fwx_uci_get_package(const char *config)
|
||||
{
|
||||
struct uci_context *ctx = uci_ctx;
|
||||
struct uci_package *p = NULL;
|
||||
@@ -393,8 +369,8 @@ config_init_package(const char *config)
|
||||
ctx = uci_alloc_context();
|
||||
uci_ctx = ctx;
|
||||
ctx->flags &= ~UCI_FLAG_STRICT;
|
||||
//if (config_path)
|
||||
// uci_set_confdir(ctx, config_path);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -408,145 +384,3 @@ config_init_package(const char *config)
|
||||
|
||||
return p;
|
||||
}
|
||||
char *get_app_name_by_id(int id)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < g_app_count; i++)
|
||||
{
|
||||
if (id == app_name_table[i].id)
|
||||
return app_name_table[i].name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void init_app_name_table(void)
|
||||
{
|
||||
int count = 0;
|
||||
char line_buf[2048] = {0};
|
||||
|
||||
FILE *fp = fopen("/tmp/feature.cfg", "r");
|
||||
if (!fp)
|
||||
{
|
||||
printf("open file failed\n");
|
||||
return;
|
||||
}
|
||||
g_app_count = 0;
|
||||
while (fgets(line_buf, sizeof(line_buf), fp))
|
||||
{
|
||||
if (strstr(line_buf, "#"))
|
||||
continue;
|
||||
if (strlen(line_buf) < 10)
|
||||
continue;
|
||||
if (!strstr(line_buf, ":"))
|
||||
continue;
|
||||
char *pos1 = strstr(line_buf, ":");
|
||||
char app_info_buf[128] = {0};
|
||||
int app_id;
|
||||
char app_name[64] = {0};
|
||||
memset(app_name, 0x0, sizeof(app_name));
|
||||
strncpy(app_info_buf, line_buf, pos1 - line_buf);
|
||||
sscanf(app_info_buf, "%d %s", &app_id, app_name);
|
||||
app_name_table[g_app_count].id = app_id;
|
||||
strcpy(app_name_table[g_app_count].name, app_name);
|
||||
g_app_count++;
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void init_app_class_name_table(void)
|
||||
{
|
||||
char line_buf[2048] = {0};
|
||||
int class_id;
|
||||
char class_name[64] = {0};
|
||||
FILE *fp = fopen("/tmp/app_class.txt", "r");
|
||||
if (!fp)
|
||||
{
|
||||
printf("open file failed\n");
|
||||
return;
|
||||
}
|
||||
g_cur_class_num = 0;
|
||||
while (fgets(line_buf, sizeof(line_buf), fp))
|
||||
{
|
||||
sscanf(line_buf, "%d %*s %s", &class_id, class_name);
|
||||
strcpy(CLASS_NAME_TABLE[class_id - 1], class_name);
|
||||
g_cur_class_num++;
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
//00:00 9:1
|
||||
int check_time_valid(char *t)
|
||||
{
|
||||
if (!t)
|
||||
return 0;
|
||||
if (strlen(t) < 3 || strlen(t) > 5 || (!strstr(t, ":")))
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int config_get_appfilter_enable(void)
|
||||
{
|
||||
int enable = 0;
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (!ctx)
|
||||
return -1;
|
||||
enable = af_uci_get_int_value(ctx, "appfilter.global.enable");
|
||||
if (enable < 0)
|
||||
enable = 0;
|
||||
|
||||
uci_free_context(ctx);
|
||||
return enable;
|
||||
}
|
||||
|
||||
int config_get_lan_ip(char *lan_ip, int len)
|
||||
{
|
||||
int ret = 0;
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (!ctx)
|
||||
return -1;
|
||||
ret = af_uci_get_value(ctx, "network.lan.ipaddr", lan_ip, len);
|
||||
uci_free_context(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int config_get_lan_mask(char *lan_mask, int len)
|
||||
{
|
||||
int ret = 0;
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (!ctx)
|
||||
return -1;
|
||||
ret = af_uci_get_value(ctx, "network.lan.netmask", lan_mask, len);
|
||||
uci_free_context(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int appfilter_config_alloc(void)
|
||||
{
|
||||
char *err;
|
||||
uci_appfilter = config_init_package("appfilter");
|
||||
if (!uci_appfilter)
|
||||
{
|
||||
uci_get_errorstr(uci_ctx, &err, NULL);
|
||||
printf("Failed to load appfilter config (%s)\n", err);
|
||||
free(err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int appfilter_config_free(void)
|
||||
{
|
||||
if (uci_appfilter)
|
||||
{
|
||||
uci_unload(uci_ctx, uci_appfilter);
|
||||
uci_appfilter = NULL;
|
||||
}
|
||||
if (uci_ctx)
|
||||
{
|
||||
uci_free_context(uci_ctx);
|
||||
uci_ctx = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_UCI_H__
|
||||
#define __FWX_UCI_H__
|
||||
#include <uci.h>
|
||||
|
||||
#define MAX_PARAM_LIST_LEN 1024
|
||||
|
||||
int fwx_uci_get_int_value(struct uci_context *ctx, char *key);
|
||||
int fwx_uci_get_value(struct uci_context *ctx, char *key, char *output, int out_len);
|
||||
int fwx_uci_add_list(struct uci_context *ctx, char *key, char *value);
|
||||
int fwx_uci_get_list_value(struct uci_context *ctx, char *key, char *output, int out_len, char *delimt);
|
||||
int fwx_uci_add_int_list(struct uci_context *ctx, char *key, int value);
|
||||
int fwx_uci_del_list(struct uci_context *ctx, char *key, char *value);
|
||||
int fwx_uci_set_value(struct uci_context *ctx, char *key, char *value);
|
||||
int fwx_uci_set_int_value(struct uci_context *ctx, char *key, int value);
|
||||
int fwx_uci_del_array_value(struct uci_context *ctx, char *key_fmt, int index);
|
||||
int fwx_uci_set_array_value(struct uci_context *ctx, char *key_fmt, int index, char *value);
|
||||
int fwx_uci_get_list_num(struct uci_context * ctx, char *package, char *section);
|
||||
int fwx_uci_get_array_value(struct uci_context *ctx, char *key_fmt, int index, char *output, int out_len);
|
||||
int fwx_uci_add_section(struct uci_context * ctx, char *package_name, char *section);
|
||||
int fwx_uci_commit(struct uci_context *ctx, const char * package);
|
||||
int fwx_uci_delete(struct uci_context *ctx, char *key);
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <libubox/list.h>
|
||||
#ifndef __FILTER_USER_H__
|
||||
#define __FILTER_USER_H__
|
||||
#define MAX_IP_LEN 32
|
||||
#define MAX_MAC_LEN 32
|
||||
|
||||
#define MAX_HOSTNAME_SIZE 64
|
||||
#define OAF_VISIT_LIST_FILE "/tmp/visit_list"
|
||||
#define OAF_DEV_LIST_FILE "/tmp/dev_list"
|
||||
#define MIN_VISIT_TIME 5 // default 5s
|
||||
#define MAX_APP_STAT_NUM 8
|
||||
#define MAX_VISITLIST_DUMP_NUM 16
|
||||
#define MAX_APP_TYPE 16
|
||||
#define MAX_APP_ID_NUM 128
|
||||
#define MAX_SUPPORT_DEV_NUM 64
|
||||
#define SECONDS_PER_DAY (24 * 3600)
|
||||
#define MAX_NICKNAME_SIZE 64
|
||||
#define HOURS_PER_DAY 24
|
||||
#define TOP_APP_PER_HOUR 3
|
||||
#define MAX_DAILY_STAT_DAYS 30
|
||||
#define MAX_TOP_APPS_PER_DAY 10
|
||||
#define MAX_RECORD_DAY 30
|
||||
#define MAX_RECENT_APPS 5
|
||||
#define MAX_USER_RECORDS 4096
|
||||
|
||||
|
||||
typedef struct visit_info
|
||||
{
|
||||
int appid;
|
||||
u_int32_t first_time;
|
||||
u_int32_t latest_time;
|
||||
int action;
|
||||
int expire;
|
||||
struct list_head visit;
|
||||
|
||||
} visit_info_t;
|
||||
|
||||
|
||||
typedef struct online_offline_record
|
||||
{
|
||||
int type;
|
||||
u_int32_t timestamp;
|
||||
unsigned long long duration;
|
||||
struct list_head record;
|
||||
} online_offline_record_t;
|
||||
|
||||
typedef struct online_session_stat
|
||||
{
|
||||
unsigned long long up_bytes;
|
||||
unsigned long long down_bytes;
|
||||
unsigned long long online_duration;
|
||||
unsigned long long active_duration;
|
||||
int recent_apps[MAX_RECENT_APPS];
|
||||
int recent_app_count;
|
||||
u_int32_t start_time;
|
||||
} online_session_stat_t;
|
||||
|
||||
typedef struct user_record
|
||||
{
|
||||
int action;
|
||||
u_int32_t timestamp;
|
||||
char mac[MAX_MAC_LEN];
|
||||
char nickname[MAX_NICKNAME_SIZE];
|
||||
char hostname[MAX_HOSTNAME_SIZE];
|
||||
unsigned long long up_bytes;
|
||||
unsigned long long down_bytes;
|
||||
unsigned long long online_duration;
|
||||
unsigned long long active_duration;
|
||||
int recent_apps[MAX_RECENT_APPS];
|
||||
int recent_app_count;
|
||||
struct list_head list;
|
||||
} user_record_t;
|
||||
|
||||
|
||||
typedef struct visit_stat
|
||||
{
|
||||
int appid;
|
||||
unsigned long long total_time;
|
||||
struct list_head list;
|
||||
} visit_stat_t;
|
||||
|
||||
|
||||
typedef struct global_app_type_record
|
||||
{
|
||||
int app_type;
|
||||
unsigned long long time_delta;
|
||||
u_int32_t timestamp;
|
||||
struct list_head list;
|
||||
} global_app_type_record_t;
|
||||
|
||||
|
||||
typedef struct traffic_stat
|
||||
{
|
||||
unsigned long long up_bytes;
|
||||
unsigned long long down_bytes;
|
||||
} traffic_stat_t;
|
||||
|
||||
|
||||
typedef struct daily_hourly_stat
|
||||
{
|
||||
u_int32_t date;
|
||||
int is_today;
|
||||
|
||||
int hourly_top_apps[HOURS_PER_DAY][TOP_APP_PER_HOUR];
|
||||
|
||||
traffic_stat_t hourly_traffic[HOURS_PER_DAY];
|
||||
|
||||
unsigned long long hourly_online_time[HOURS_PER_DAY];
|
||||
unsigned long long hourly_active_time[HOURS_PER_DAY];
|
||||
} daily_hourly_stat_t;
|
||||
|
||||
|
||||
typedef struct daily_top_apps_stat
|
||||
{
|
||||
u_int32_t date;
|
||||
int is_today;
|
||||
int count;
|
||||
struct {
|
||||
int appid;
|
||||
unsigned long long total_time;
|
||||
} apps[MAX_TOP_APPS_PER_DAY];
|
||||
} daily_top_apps_stat_t;
|
||||
|
||||
|
||||
#define MAX_REPORT_URL_LEN 64
|
||||
typedef struct client_node
|
||||
{
|
||||
char mac[MAX_MAC_LEN];
|
||||
char ip[MAX_IP_LEN];
|
||||
char ipv6[128];
|
||||
unsigned int up_rate;
|
||||
unsigned int down_rate;
|
||||
int rssi;
|
||||
unsigned int rx_rate;
|
||||
unsigned int tx_rate;
|
||||
char band[16];
|
||||
char wifi_ifname[64];
|
||||
int is_wireless;
|
||||
int wireless_online;
|
||||
char hostname[MAX_HOSTNAME_SIZE];
|
||||
char nickname[MAX_NICKNAME_SIZE];
|
||||
int online;
|
||||
int expire;
|
||||
u_int32_t offline_time;
|
||||
u_int32_t online_time;
|
||||
struct list_head online_visit;
|
||||
struct list_head visit;
|
||||
struct list_head stat_list;
|
||||
struct list_head online_offline_records;
|
||||
int mf_user_loaded;
|
||||
char visiting_url[MAX_REPORT_URL_LEN];
|
||||
int visiting_app;
|
||||
int active;
|
||||
int last_online_state;
|
||||
int session_online_recorded;
|
||||
online_session_stat_t online_session;
|
||||
|
||||
daily_hourly_stat_t daily_stats;
|
||||
|
||||
daily_top_apps_stat_t daily_top_apps_stats;
|
||||
struct list_head client;
|
||||
|
||||
} client_node_t;
|
||||
|
||||
struct app_visit_info
|
||||
{
|
||||
int app_id;
|
||||
char app_name[32];
|
||||
int total_time;
|
||||
};
|
||||
|
||||
struct app_visit_stat_info
|
||||
{
|
||||
int num;
|
||||
struct app_visit_info visit_list[MAX_APP_STAT_NUM];
|
||||
};
|
||||
typedef void (*iter_func)(void *arg, client_node_t *client);
|
||||
|
||||
extern struct list_head client_list;
|
||||
extern struct list_head user_record_list;
|
||||
|
||||
int get_timestamp(void);
|
||||
client_node_t *add_client_node(char *mac);
|
||||
void init_client_list(void);
|
||||
void add_debug_test_users(void);
|
||||
void dump_client_list(void);
|
||||
void dump_client_visit_list(void);
|
||||
client_node_t *find_client_node(const char *mac);
|
||||
void client_foreach(void *arg, iter_func iter);
|
||||
void add_visit_info_node(struct list_head *visit_list, visit_info_t *node);
|
||||
void check_client_visit_info_expire(void);
|
||||
void flush_expire_visit_info(void);
|
||||
int check_client_expire(void);
|
||||
void flush_expire_client_node(void);
|
||||
void move_expired_online_visit_to_offline(void);
|
||||
void update_client_list(void);
|
||||
void update_client_nickname(void);
|
||||
void update_client_visiting_info(void);
|
||||
void refresh_client_wireless_status(void);
|
||||
void update_hourly_top_apps(client_node_t *client);
|
||||
void get_hourly_top_apps(client_node_t *client, int hour, int *appids, int max_count);
|
||||
int get_hour_from_timestamp(u_int32_t timestamp);
|
||||
unsigned long long get_visit_duration_in_hour(visit_info_t *visit, int target_hour);
|
||||
daily_hourly_stat_t *get_today_stat(client_node_t *client);
|
||||
daily_hourly_stat_t *load_history_stat_from_file(client_node_t *client, u_int32_t date);
|
||||
void save_daily_stats_to_file(client_node_t *client, u_int32_t date);
|
||||
void check_and_archive_all_clients(void);
|
||||
u_int32_t get_today_start_timestamp(void);
|
||||
void update_daily_top_apps(client_node_t *client);
|
||||
daily_top_apps_stat_t *get_today_top_apps_stat(client_node_t *client);
|
||||
daily_top_apps_stat_t *load_history_top_apps_stat_from_file(client_node_t *client, u_int32_t date);
|
||||
void save_daily_top_apps_stats_to_file(client_node_t *client, u_int32_t date);
|
||||
void save_client_visit_data_to_file(client_node_t *client, u_int32_t date);
|
||||
void init_client_visit_db(void);
|
||||
int add_mock_visit_records_to_db(int record_count, int mac_count, unsigned long long *old_size_bytes, unsigned long long *new_size_bytes);
|
||||
const char *get_client_data_root_dir(void);
|
||||
const char *get_history_data_root_dir(void);
|
||||
const char *get_client_data_base_dir(void);
|
||||
void reset_client_data_base_dir_cache(void);
|
||||
void load_client_backup_from_files(void);
|
||||
void save_client_backup_to_file(client_node_t *client);
|
||||
void save_all_client_backup_to_files(void);
|
||||
void load_app_valid_time_config(void);
|
||||
int get_app_valid_time(void);
|
||||
void check_and_cleanup_history_data_by_size(void);
|
||||
void archive_and_save_client_visits(void);
|
||||
void delete_client_record_files(const char *mac, const char *start_date, const char *end_date, const char *delete_type);
|
||||
void update_global_app_type_stats(int appid, unsigned long long time_delta);
|
||||
void cleanup_expired_hourly_stats(void);
|
||||
void get_global_daily_app_type_stats(unsigned long long *type_time_array);
|
||||
void get_global_hourly_app_type_stats(unsigned long long *type_time_array);
|
||||
struct json_object *fwx_api_get_global_app_type_stats(struct json_object *req_obj);
|
||||
void save_global_traffic_stats_to_file(u_int32_t date);
|
||||
void get_global_traffic_stats(traffic_stat_t *traffic_array);
|
||||
struct json_object *fwx_api_get_global_traffic_stats(struct json_object *req_obj);
|
||||
void reset_online_session_stat(client_node_t *client, u_int32_t start_time);
|
||||
void update_online_session_flow(client_node_t *client, unsigned long long up_bytes, unsigned long long down_bytes);
|
||||
void update_online_session_activity(client_node_t *client, int online_seconds, int active_seconds);
|
||||
void update_online_session_recent_app(client_node_t *client, int appid);
|
||||
void add_user_record(client_node_t *client, int action, u_int32_t timestamp);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,337 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "fwx_utils.h"
|
||||
|
||||
char *str_trim(char *s) {
|
||||
char *start, *last, *bk;
|
||||
int len;
|
||||
|
||||
start = s;
|
||||
while (isspace(*start))
|
||||
start++;
|
||||
|
||||
bk = last = s + strlen(s) - 1;
|
||||
while (last > start && isspace(*last))
|
||||
last--;
|
||||
|
||||
if ((s != start) || (bk != last)) {
|
||||
len = last - start + 1;
|
||||
strncpy(s, start, len);
|
||||
s[len] = '\0';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int exec_with_result_line(char *cmd, char *result, int len)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
if (!cmd || !result || !len)
|
||||
return -1;
|
||||
fp = popen(cmd, "r");
|
||||
if (!fp)
|
||||
return -1;
|
||||
fgets(result, len, fp);
|
||||
str_trim(result);
|
||||
pclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fwx_send_msg_to_kernel(char *buf){
|
||||
|
||||
if (access("/dev/fwx", F_OK) != 0) {
|
||||
return 0; // Device doesn't exist, silently skip
|
||||
}
|
||||
|
||||
FILE *fp = fopen("/dev/fwx", "w");
|
||||
if (fp) {
|
||||
fprintf(fp, "%s", buf);
|
||||
fclose(fp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int check_same_network(char *ip1, char *netmask, char *ip2) {
|
||||
struct in_addr addr1, addr2, mask;
|
||||
|
||||
if (inet_pton(AF_INET, ip1, &addr1) != 1) {
|
||||
printf("Invalid IP address: %s\n", ip1);
|
||||
return -1;
|
||||
}
|
||||
if (inet_pton(AF_INET, netmask, &mask) != 1) {
|
||||
printf("Invalid netmask: %s\n", netmask);
|
||||
return -1;
|
||||
}
|
||||
if (inet_pton(AF_INET, ip2, &addr2) != 1) {
|
||||
printf("Invalid IP address: %s\n", ip2);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((addr1.s_addr & mask.s_addr) == (addr2.s_addr & mask.s_addr)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int af_read_file_value(const char *file_path, char *value, int value_len) {
|
||||
FILE *file = fopen(file_path, "r");
|
||||
if (!file) {
|
||||
perror("Failed to open file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fgets(value, value_len, file) == NULL) {
|
||||
perror("Failed to read line from file");
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t len = strlen(value);
|
||||
if (len > 0 && value[len - 1] == '\n') {
|
||||
value[len - 1] = '\0';
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int af_read_file_int_value(const char *file_path, int *value) {
|
||||
char line_buf[128] = {0};
|
||||
if (af_read_file_value(file_path, line_buf, sizeof(line_buf)) < 0){
|
||||
return -1;
|
||||
}
|
||||
*value = atoi(line_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse time_str from UCI format into time period structures
|
||||
* Format: "HH:MM-HH:MM-w1,w2,w3 HH:MM-HH:MM-w4,w5 ..."
|
||||
* Example: "00:00-23:59-2,3,6 00:00-02:05-1,2,3,0"
|
||||
*/
|
||||
int fwx_parse_time_str(const char *time_str, fwx_time_period_t *periods, int max_periods) {
|
||||
if (!time_str || !periods || max_periods <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int period_count = 0;
|
||||
char *save_ptr1 = NULL;
|
||||
char *save_ptr2 = NULL;
|
||||
|
||||
|
||||
char time_str_copy[512] = {0};
|
||||
strncpy(time_str_copy, time_str, sizeof(time_str_copy) - 1);
|
||||
|
||||
|
||||
char *time_period = strtok_r(time_str_copy, " ", &save_ptr1);
|
||||
while (time_period && period_count < max_periods) {
|
||||
fwx_time_period_t *period = &periods[period_count];
|
||||
memset(period, 0, sizeof(fwx_time_period_t));
|
||||
|
||||
char start[16] = {0};
|
||||
char end[16] = {0};
|
||||
char weekdays[64] = {0};
|
||||
|
||||
|
||||
char *first_delim = strchr(time_period, '-');
|
||||
if (!first_delim) {
|
||||
|
||||
time_period = strtok_r(NULL, " ", &save_ptr1);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
strncpy(start, time_period, first_delim - time_period);
|
||||
start[first_delim - time_period] = '\0';
|
||||
|
||||
|
||||
char *second_delim = strchr(first_delim + 1, '-');
|
||||
if (second_delim) {
|
||||
|
||||
strncpy(end, first_delim + 1, second_delim - first_delim - 1);
|
||||
end[second_delim - first_delim - 1] = '\0';
|
||||
strncpy(weekdays, second_delim + 1, sizeof(weekdays) - 1);
|
||||
} else {
|
||||
|
||||
strncpy(end, first_delim + 1, sizeof(end) - 1);
|
||||
}
|
||||
|
||||
|
||||
strncpy(period->start_time, start, sizeof(period->start_time) - 1);
|
||||
strncpy(period->end_time, end, sizeof(period->end_time) - 1);
|
||||
|
||||
|
||||
if (strlen(weekdays) > 0) {
|
||||
char weekdays_copy[64] = {0};
|
||||
strncpy(weekdays_copy, weekdays, sizeof(weekdays_copy) - 1);
|
||||
|
||||
char *weekday_str = strtok_r(weekdays_copy, ",", &save_ptr2);
|
||||
while (weekday_str && period->weekday_count < MAX_WEEKDAYS) {
|
||||
int weekday = atoi(weekday_str);
|
||||
if (weekday >= 0 && weekday <= 6) {
|
||||
period->weekdays[period->weekday_count] = weekday;
|
||||
period->weekday_count++;
|
||||
}
|
||||
weekday_str = strtok_r(NULL, ",", &save_ptr2);
|
||||
}
|
||||
}
|
||||
|
||||
period_count++;
|
||||
time_period = strtok_r(NULL, " ", &save_ptr1);
|
||||
}
|
||||
|
||||
return period_count;
|
||||
}
|
||||
|
||||
|
||||
void update_fwx_proc_value(char *key, char *value){
|
||||
char cmd_buf[128] = {0};
|
||||
char file_path[128] = {0};
|
||||
char old_value[128] = {0};
|
||||
sprintf(file_path, "/proc/sys/fwx/%s", key);
|
||||
|
||||
af_read_file_value(file_path, old_value, sizeof(old_value));
|
||||
if (strcmp(old_value, value) != 0){
|
||||
sprintf(cmd_buf, "echo %s >/proc/sys/fwx/%s", value, key);
|
||||
system(cmd_buf);
|
||||
}
|
||||
}
|
||||
|
||||
void update_fwx_proc_u32_value(char *key, u_int32_t value){
|
||||
char buf[32] = {0};
|
||||
sprintf(buf, "%u", value);
|
||||
update_fwx_proc_value(key, buf);
|
||||
}
|
||||
|
||||
#define MD5_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
|
||||
#define MD5_G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
|
||||
#define MD5_H(x, y, z) ((x) ^ (y) ^ (z))
|
||||
#define MD5_I(x, y, z) ((y) ^ ((x) | ~(z)))
|
||||
#define MD5_ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
||||
|
||||
static const uint32_t md5_k[64] = {
|
||||
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
|
||||
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
||||
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
||||
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
||||
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
|
||||
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
||||
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
|
||||
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
||||
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
||||
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
||||
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
|
||||
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
||||
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
|
||||
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
||||
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
||||
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
|
||||
};
|
||||
|
||||
static const uint8_t md5_s[64] = {
|
||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
|
||||
};
|
||||
|
||||
static uint32_t md5_load_le32(const uint8_t *p)
|
||||
{
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
static void md5_store_le32(uint8_t *p, uint32_t v)
|
||||
{
|
||||
p[0] = (uint8_t)v;
|
||||
p[1] = (uint8_t)(v >> 8);
|
||||
p[2] = (uint8_t)(v >> 16);
|
||||
p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
|
||||
static void md5_transform(uint32_t *state, const uint8_t block[64])
|
||||
{
|
||||
uint32_t a = state[0];
|
||||
uint32_t b = state[1];
|
||||
uint32_t c = state[2];
|
||||
uint32_t d = state[3];
|
||||
uint32_t x[16];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
x[i] = md5_load_le32(block + i * 4);
|
||||
|
||||
for (i = 0; i < 64; i++) {
|
||||
uint32_t f;
|
||||
int g;
|
||||
if (i < 16) {
|
||||
f = MD5_F(b, c, d);
|
||||
g = i;
|
||||
} else if (i < 32) {
|
||||
f = MD5_G(b, c, d);
|
||||
g = (5 * i + 1) & 15;
|
||||
} else if (i < 48) {
|
||||
f = MD5_H(b, c, d);
|
||||
g = (3 * i + 5) & 15;
|
||||
} else {
|
||||
f = MD5_I(b, c, d);
|
||||
g = (7 * i) & 15;
|
||||
}
|
||||
f = f + a + md5_k[i] + x[g];
|
||||
a = d;
|
||||
d = c;
|
||||
c = b;
|
||||
b = b + MD5_ROTL(f, md5_s[i]);
|
||||
}
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
}
|
||||
|
||||
void fwx_md5(const unsigned char *data, size_t len, unsigned char digest[16])
|
||||
{
|
||||
uint32_t state[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};
|
||||
uint64_t bit_len = (uint64_t)len * 8;
|
||||
uint8_t block[64];
|
||||
size_t rem;
|
||||
int i;
|
||||
|
||||
while (len >= 64) {
|
||||
md5_transform(state, data);
|
||||
data += 64;
|
||||
len -= 64;
|
||||
}
|
||||
|
||||
memset(block, 0, sizeof(block));
|
||||
memcpy(block, data, len);
|
||||
block[len] = 0x80;
|
||||
|
||||
rem = len + 1;
|
||||
if (rem > 56) {
|
||||
md5_transform(state, block);
|
||||
memset(block, 0, sizeof(block));
|
||||
}
|
||||
md5_store_le32(block + 56, (uint32_t)bit_len);
|
||||
md5_store_le32(block + 60, (uint32_t)(bit_len >> 32));
|
||||
md5_transform(state, block);
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
md5_store_le32(digest + i * 4, state[i]);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __UTILS_H__
|
||||
#define __UTILS_H__
|
||||
#include <sys/types.h>
|
||||
|
||||
#define MAX_WEEKDAYS 7
|
||||
|
||||
|
||||
typedef struct fwx_time_period {
|
||||
char start_time[16];
|
||||
char end_time[16];
|
||||
int weekdays[MAX_WEEKDAYS];
|
||||
int weekday_count;
|
||||
} fwx_time_period_t;
|
||||
|
||||
char *str_trim(char *s);
|
||||
int exec_with_result_line(char *cmd, char *result, int len);
|
||||
int check_same_network(char *ip1, char *netmask, char *ip2);
|
||||
int af_read_file_value(const char *file_path, char *value, int value_len);
|
||||
int af_read_file_int_value(const char *file_path, int *value);
|
||||
int fwx_send_msg_to_kernel(char *buf);
|
||||
int fwx_parse_time_str(const char *time_str, fwx_time_period_t *periods, int max_periods);
|
||||
void update_fwx_proc_value(char *key, char *value);
|
||||
void update_fwx_proc_u32_value(char *key, u_int32_t value);
|
||||
void fwx_md5(const unsigned char *data, size_t len, unsigned char digest[16]);
|
||||
#endif
|
||||
@@ -0,0 +1,502 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdio.h>
|
||||
#include <json-c/json.h>
|
||||
#include <uci.h>
|
||||
#include "fwx.h"
|
||||
#include "fwx_wireless.h"
|
||||
|
||||
#define MAX_WIRELESS_SECTION_NUM 64
|
||||
#define MAX_WIRELESS_NAME_LEN 64
|
||||
|
||||
static int get_wireless_option_value(struct uci_context *ctx, const char *section_name, const char *option_name, char *out, size_t out_len)
|
||||
{
|
||||
char uci_key[128] = {0};
|
||||
|
||||
if (!ctx || !section_name || !option_name || !out || out_len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(uci_key, sizeof(uci_key), "wireless.%s.%s", section_name, option_name);
|
||||
if (fwx_uci_get_value(ctx, uci_key, out, (int)out_len) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out[0] = '\0';
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int set_wireless_option_value(struct uci_context *ctx, const char *section_name, const char *option_name, const char *value)
|
||||
{
|
||||
char uci_key[128] = {0};
|
||||
|
||||
if (!ctx || !section_name || !option_name || !value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(uci_key, sizeof(uci_key), "wireless.%s.%s", section_name, option_name);
|
||||
return fwx_uci_set_value(ctx, uci_key, (char *)value);
|
||||
}
|
||||
|
||||
static int delete_wireless_option_value(struct uci_context *ctx, const char *section_name, const char *option_name)
|
||||
{
|
||||
char uci_key[128] = {0};
|
||||
|
||||
if (!ctx || !section_name || !option_name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(uci_key, sizeof(uci_key), "wireless.%s.%s", section_name, option_name);
|
||||
return fwx_uci_delete(ctx, uci_key);
|
||||
}
|
||||
|
||||
static int collect_wireless_sections(struct uci_context *ctx,
|
||||
char radio_names[][MAX_WIRELESS_NAME_LEN], int *radio_num,
|
||||
char iface_names[][MAX_WIRELESS_NAME_LEN], int *iface_num)
|
||||
{
|
||||
struct uci_package *pkg = NULL;
|
||||
struct uci_element *e = NULL;
|
||||
|
||||
if (!ctx || !radio_names || !radio_num || !iface_names || !iface_num) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*radio_num = 0;
|
||||
*iface_num = 0;
|
||||
if (uci_load(ctx, "wireless", &pkg) != UCI_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uci_foreach_element(&pkg->sections, e) {
|
||||
struct uci_section *s = uci_to_section(e);
|
||||
|
||||
if (strcmp(s->type, "wifi-device") == 0) {
|
||||
if (*radio_num < MAX_WIRELESS_SECTION_NUM) {
|
||||
snprintf(radio_names[*radio_num], MAX_WIRELESS_NAME_LEN, "%s", e->name);
|
||||
(*radio_num)++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(s->type, "wifi-iface") == 0) {
|
||||
if (*iface_num < MAX_WIRELESS_SECTION_NUM) {
|
||||
snprintf(iface_names[*iface_num], MAX_WIRELESS_NAME_LEN, "%s", e->name);
|
||||
(*iface_num)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pkg) {
|
||||
uci_unload(ctx, pkg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_first_iface_by_radio(struct uci_context *ctx, const char *radio_name,
|
||||
char iface_names[][MAX_WIRELESS_NAME_LEN], int iface_num,
|
||||
char *out_iface, size_t out_iface_len)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (!ctx || !radio_name || !iface_names || !out_iface || out_iface_len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < iface_num; i++) {
|
||||
char device[64] = {0};
|
||||
if (get_wireless_option_value(ctx, iface_names[i], "device", device, sizeof(device)) == 0 &&
|
||||
strcmp(device, radio_name) == 0) {
|
||||
snprintf(out_iface, out_iface_len, "%s", iface_names[i]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int is_iface_exists(char iface_names[][MAX_WIRELESS_NAME_LEN], int iface_num, const char *section_name)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (!iface_names || !section_name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < iface_num; i++) {
|
||||
if (strcmp(iface_names[i], section_name) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *format_band_label(const char *band, const char *hwmode, char *buf, size_t buf_len)
|
||||
{
|
||||
if (!buf || buf_len == 0) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
if (band && band[0] != '\0') {
|
||||
if (strcmp(band, "2g") == 0) {
|
||||
snprintf(buf, buf_len, "2.4G");
|
||||
} else if (strcmp(band, "5g") == 0) {
|
||||
snprintf(buf, buf_len, "5G");
|
||||
} else if (strcmp(band, "6g") == 0) {
|
||||
snprintf(buf, buf_len, "6G");
|
||||
} else if (strcmp(band, "60g") == 0) {
|
||||
snprintf(buf, buf_len, "60G");
|
||||
} else {
|
||||
snprintf(buf, buf_len, "%s", band);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
if (hwmode && hwmode[0] != '\0') {
|
||||
if (strstr(hwmode, "11ad") || strstr(hwmode, "11ay")) {
|
||||
snprintf(buf, buf_len, "60G");
|
||||
} else if (strstr(hwmode, "11a")) {
|
||||
snprintf(buf, buf_len, "5G");
|
||||
} else {
|
||||
snprintf(buf, buf_len, "2.4G");
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
snprintf(buf, buf_len, "Unknown");
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int parse_hidden_value(const char *hidden)
|
||||
{
|
||||
if (!hidden) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(hidden, "1") == 0 ||
|
||||
strcasecmp(hidden, "true") == 0 ||
|
||||
strcasecmp(hidden, "yes") == 0 ||
|
||||
strcasecmp(hidden, "on") == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int json_to_bool(struct json_object *obj, int default_value)
|
||||
{
|
||||
enum json_type type;
|
||||
const char *val_str = NULL;
|
||||
|
||||
if (!obj) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
type = json_object_get_type(obj);
|
||||
if (type == json_type_boolean || type == json_type_int) {
|
||||
return json_object_get_int(obj) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (type == json_type_string) {
|
||||
val_str = json_object_get_string(obj);
|
||||
return parse_hidden_value(val_str);
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
static int is_open_encryption(const char *encryption)
|
||||
{
|
||||
if (!encryption || encryption[0] == '\0') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(encryption, "none") == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strncmp(encryption, "owe", 3) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_ssid_item(struct uci_context *ctx, struct json_object *item,
|
||||
char iface_names[][MAX_WIRELESS_NAME_LEN], int iface_num)
|
||||
{
|
||||
struct json_object *radio_obj = NULL;
|
||||
struct json_object *section_obj = NULL;
|
||||
struct json_object *ssid_obj = NULL;
|
||||
struct json_object *password_obj = NULL;
|
||||
struct json_object *encryption_obj = NULL;
|
||||
struct json_object *hidden_obj = NULL;
|
||||
struct json_object *isolate_obj = NULL;
|
||||
const char *radio_name = NULL;
|
||||
const char *section_name = NULL;
|
||||
const char *password = NULL;
|
||||
char encryption[64] = {0};
|
||||
char iface_name[MAX_WIRELESS_NAME_LEN] = {0};
|
||||
int has_password = 0;
|
||||
int has_encryption = 0;
|
||||
int has_change = 0;
|
||||
int hidden = 0;
|
||||
int isolate = 0;
|
||||
char hidden_str[4] = {0};
|
||||
char isolate_str[4] = {0};
|
||||
|
||||
if (!ctx || !item || json_object_get_type(item) != json_type_object) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
json_object_object_get_ex(item, "radio", &radio_obj);
|
||||
json_object_object_get_ex(item, "section", §ion_obj);
|
||||
json_object_object_get_ex(item, "ssid", &ssid_obj);
|
||||
has_password = json_object_object_get_ex(item, "password", &password_obj);
|
||||
has_encryption = json_object_object_get_ex(item, "encryption", &encryption_obj);
|
||||
json_object_object_get_ex(item, "hidden", &hidden_obj);
|
||||
json_object_object_get_ex(item, "isolate", &isolate_obj);
|
||||
|
||||
radio_name = radio_obj ? json_object_get_string(radio_obj) : NULL;
|
||||
section_name = section_obj ? json_object_get_string(section_obj) : NULL;
|
||||
|
||||
if (section_name && section_name[0] != '\0' && is_iface_exists(iface_names, iface_num, section_name)) {
|
||||
snprintf(iface_name, sizeof(iface_name), "%s", section_name);
|
||||
}
|
||||
|
||||
if (iface_name[0] == '\0' && radio_name && radio_name[0] != '\0') {
|
||||
find_first_iface_by_radio(ctx, radio_name, iface_names, iface_num, iface_name, sizeof(iface_name));
|
||||
}
|
||||
|
||||
if (iface_name[0] == '\0') {
|
||||
LOG_WARN("set_wireless_base_setting skip item, iface not found, radio=%s section=%s\n",
|
||||
radio_name ? radio_name : "", section_name ? section_name : "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ssid_obj) {
|
||||
const char *ssid = json_object_get_string(ssid_obj);
|
||||
if (set_wireless_option_value(ctx, iface_name, "ssid", ssid ? ssid : "") == 0) {
|
||||
has_change = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_encryption) {
|
||||
const char *enc_value = json_object_get_string(encryption_obj);
|
||||
if (!enc_value || enc_value[0] == '\0') {
|
||||
enc_value = "none";
|
||||
}
|
||||
snprintf(encryption, sizeof(encryption), "%s", enc_value);
|
||||
if (set_wireless_option_value(ctx, iface_name, "encryption", encryption) == 0) {
|
||||
has_change = 1;
|
||||
}
|
||||
} else {
|
||||
if (get_wireless_option_value(ctx, iface_name, "encryption", encryption, sizeof(encryption)) != 0 ||
|
||||
encryption[0] == '\0') {
|
||||
snprintf(encryption, sizeof(encryption), "none");
|
||||
}
|
||||
}
|
||||
|
||||
if (hidden_obj) {
|
||||
hidden = json_to_bool(hidden_obj, 0);
|
||||
snprintf(hidden_str, sizeof(hidden_str), "%d", hidden ? 1 : 0);
|
||||
if (set_wireless_option_value(ctx, iface_name, "hidden", hidden_str) == 0) {
|
||||
has_change = 1;
|
||||
}
|
||||
}
|
||||
if (isolate_obj) {
|
||||
isolate = json_to_bool(isolate_obj, 0);
|
||||
snprintf(isolate_str, sizeof(isolate_str), "%d", isolate ? 1 : 0);
|
||||
if (set_wireless_option_value(ctx, iface_name, "isolate", isolate_str) == 0) {
|
||||
has_change = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_open_encryption(encryption)) {
|
||||
int del_key = delete_wireless_option_value(ctx, iface_name, "key");
|
||||
int del_key1 = delete_wireless_option_value(ctx, iface_name, "key1");
|
||||
if (del_key == 0 || del_key1 == 0) {
|
||||
has_change = 1;
|
||||
}
|
||||
} else if (has_password) {
|
||||
password = json_object_get_string(password_obj);
|
||||
if (password && password[0] != '\0') {
|
||||
if (set_wireless_option_value(ctx, iface_name, "key", password) == 0) {
|
||||
has_change = 1;
|
||||
}
|
||||
} else {
|
||||
if (delete_wireless_option_value(ctx, iface_name, "key") == 0) {
|
||||
has_change = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return has_change;
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_get_wireless_base_setting(struct json_object *req_obj)
|
||||
{
|
||||
struct uci_context *ctx = NULL;
|
||||
struct json_object *data_obj = NULL;
|
||||
struct json_object *ssid_list_obj = NULL;
|
||||
char radio_names[MAX_WIRELESS_SECTION_NUM][MAX_WIRELESS_NAME_LEN] = {{0}};
|
||||
char iface_names[MAX_WIRELESS_SECTION_NUM][MAX_WIRELESS_NAME_LEN] = {{0}};
|
||||
int radio_num = 0;
|
||||
int iface_num = 0;
|
||||
int i = 0;
|
||||
|
||||
(void)req_obj;
|
||||
|
||||
data_obj = json_object_new_object();
|
||||
ssid_list_obj = json_object_new_array();
|
||||
if (!data_obj || !ssid_list_obj) {
|
||||
if (ssid_list_obj) {
|
||||
json_object_put(ssid_list_obj);
|
||||
}
|
||||
if (data_obj) {
|
||||
json_object_put(data_obj);
|
||||
}
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
ctx = uci_alloc_context();
|
||||
if (!ctx) {
|
||||
json_object_put(ssid_list_obj);
|
||||
json_object_put(data_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (collect_wireless_sections(ctx, radio_names, &radio_num, iface_names, &iface_num) != 0) {
|
||||
uci_free_context(ctx);
|
||||
json_object_put(ssid_list_obj);
|
||||
json_object_put(data_obj);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
for (i = 0; i < radio_num; i++) {
|
||||
struct json_object *ssid_obj = NULL;
|
||||
char iface_name[MAX_WIRELESS_NAME_LEN] = {0};
|
||||
char ssid[128] = {0};
|
||||
char key[128] = {0};
|
||||
char encryption[64] = {0};
|
||||
char hidden[16] = {0};
|
||||
char isolate[16] = {0};
|
||||
char band[16] = {0};
|
||||
char hwmode[32] = {0};
|
||||
char band_label[16] = {0};
|
||||
|
||||
if (find_first_iface_by_radio(ctx, radio_names[i], iface_names, iface_num, iface_name, sizeof(iface_name)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
get_wireless_option_value(ctx, iface_name, "ssid", ssid, sizeof(ssid));
|
||||
get_wireless_option_value(ctx, iface_name, "key", key, sizeof(key));
|
||||
get_wireless_option_value(ctx, iface_name, "encryption", encryption, sizeof(encryption));
|
||||
get_wireless_option_value(ctx, iface_name, "hidden", hidden, sizeof(hidden));
|
||||
get_wireless_option_value(ctx, iface_name, "isolate", isolate, sizeof(isolate));
|
||||
get_wireless_option_value(ctx, radio_names[i], "band", band, sizeof(band));
|
||||
get_wireless_option_value(ctx, radio_names[i], "hwmode", hwmode, sizeof(hwmode));
|
||||
|
||||
ssid_obj = json_object_new_object();
|
||||
if (!ssid_obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object_object_add(ssid_obj, "radio", json_object_new_string(radio_names[i]));
|
||||
json_object_object_add(ssid_obj, "section", json_object_new_string(iface_name));
|
||||
json_object_object_add(ssid_obj, "band", json_object_new_string(format_band_label(band, hwmode, band_label, sizeof(band_label))));
|
||||
json_object_object_add(ssid_obj, "ssid", json_object_new_string(ssid));
|
||||
json_object_object_add(ssid_obj, "password", json_object_new_string(key));
|
||||
json_object_object_add(ssid_obj, "encryption", json_object_new_string(encryption[0] ? encryption : "none"));
|
||||
json_object_object_add(ssid_obj, "hidden", json_object_new_int(parse_hidden_value(hidden)));
|
||||
json_object_object_add(ssid_obj, "isolate", json_object_new_int(parse_hidden_value(isolate)));
|
||||
json_object_array_add(ssid_list_obj, ssid_obj);
|
||||
}
|
||||
|
||||
json_object_object_add(data_obj, "ssid_list", ssid_list_obj);
|
||||
uci_free_context(ctx);
|
||||
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, data_obj);
|
||||
}
|
||||
|
||||
struct json_object *fwx_api_set_wireless_base_setting(struct json_object *req_obj)
|
||||
{
|
||||
struct uci_context *ctx = NULL;
|
||||
struct json_object *ssid_list_obj = NULL;
|
||||
enum json_type ssid_list_type;
|
||||
char radio_names[MAX_WIRELESS_SECTION_NUM][MAX_WIRELESS_NAME_LEN] = {{0}};
|
||||
char iface_names[MAX_WIRELESS_SECTION_NUM][MAX_WIRELESS_NAME_LEN] = {{0}};
|
||||
int radio_num = 0;
|
||||
int iface_num = 0;
|
||||
int i = 0;
|
||||
int list_len = 0;
|
||||
int has_change = 0;
|
||||
int processed_num = 0;
|
||||
|
||||
if (!req_obj) {
|
||||
LOG_ERROR("set_wireless_base_setting: req_obj is null\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
LOG_INFO("set_wireless_base_setting req=%s\n", json_object_to_json_string_ext(req_obj, JSON_C_TO_STRING_PLAIN));
|
||||
|
||||
ssid_list_obj = json_object_object_get(req_obj, "ssid_list");
|
||||
if (!ssid_list_obj) {
|
||||
LOG_ERROR("set_wireless_base_setting: missing ssid_list\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
ssid_list_type = json_object_get_type(ssid_list_obj);
|
||||
if (ssid_list_type != json_type_array && ssid_list_type != json_type_object) {
|
||||
LOG_ERROR("set_wireless_base_setting: invalid ssid_list type=%d\n", ssid_list_type);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
ctx = uci_alloc_context();
|
||||
if (!ctx) {
|
||||
LOG_ERROR("set_wireless_base_setting: alloc uci ctx failed\n");
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (collect_wireless_sections(ctx, radio_names, &radio_num, iface_names, &iface_num) != 0) {
|
||||
LOG_ERROR("set_wireless_base_setting: collect_wireless_sections failed\n");
|
||||
uci_free_context(ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
|
||||
if (ssid_list_type == json_type_array) {
|
||||
list_len = json_object_array_length(ssid_list_obj);
|
||||
for (i = 0; i < list_len; i++) {
|
||||
struct json_object *item = json_object_array_get_idx(ssid_list_obj, i);
|
||||
has_change |= apply_ssid_item(ctx, item, iface_names, iface_num);
|
||||
processed_num++;
|
||||
}
|
||||
} else {
|
||||
json_object_object_foreach(ssid_list_obj, key, val) {
|
||||
(void)key;
|
||||
has_change |= apply_ssid_item(ctx, val, iface_names, iface_num);
|
||||
processed_num++;
|
||||
}
|
||||
}
|
||||
LOG_INFO("set_wireless_base_setting: list_type=%d processed=%d changed=%d\n",
|
||||
ssid_list_type, processed_num, has_change);
|
||||
|
||||
if (has_change) {
|
||||
if (fwx_uci_commit(ctx, "wireless") != UCI_OK) {
|
||||
LOG_ERROR("set_wireless_base_setting: commit wireless failed\n");
|
||||
uci_free_context(ctx);
|
||||
return fwx_gen_api_response_data(API_CODE_ERROR, NULL);
|
||||
}
|
||||
system("wifi reload");
|
||||
}
|
||||
|
||||
uci_free_context(ctx);
|
||||
|
||||
return fwx_gen_api_response_data(API_CODE_SUCCESS, NULL);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
|
||||
*/
|
||||
#ifndef __FWX_WIRELESS_H__
|
||||
#define __FWX_WIRELESS_H__
|
||||
|
||||
#include <json-c/json.h>
|
||||
|
||||
struct json_object *fwx_api_get_wireless_base_setting(struct json_object *req_obj);
|
||||
struct json_object *fwx_api_set_wireless_base_setting(struct json_object *req_obj);
|
||||
|
||||
#endif
|
||||
+404
-748
File diff suppressed because it is too large.
Load diff
@@ -1,107 +0,0 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
|
||||
char *str_trim(char *s) {
|
||||
char *start, *last, *bk;
|
||||
int len;
|
||||
|
||||
start = s;
|
||||
while (isspace(*start))
|
||||
start++;
|
||||
|
||||
bk = last = s + strlen(s) - 1;
|
||||
while (last > start && isspace(*last))
|
||||
last--;
|
||||
|
||||
if ((s != start) || (bk != last)) {
|
||||
len = last - start + 1;
|
||||
strncpy(s, start, len);
|
||||
s[len] = '\0';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int exec_with_result_line(char *cmd, char *result, int len)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
if (!cmd || !result || !len)
|
||||
return -1;
|
||||
fp = popen(cmd, "r");
|
||||
if (!fp)
|
||||
return -1;
|
||||
fgets(result, len, fp);
|
||||
str_trim(result);
|
||||
pclose(fp);
|
||||
return 0;
|
||||
}
|
||||
unsigned int get_timestamp(void)
|
||||
{
|
||||
struct timeval cur_time;
|
||||
gettimeofday(&cur_time, NULL);
|
||||
return cur_time.tv_sec;
|
||||
}
|
||||
|
||||
int check_same_network(char *ip1, char *netmask, char *ip2) {
|
||||
struct in_addr addr1, addr2, mask;
|
||||
|
||||
if (inet_pton(AF_INET, ip1, &addr1) != 1) {
|
||||
printf("Invalid IP address: %s\n", ip1);
|
||||
return -1;
|
||||
}
|
||||
if (inet_pton(AF_INET, netmask, &mask) != 1) {
|
||||
printf("Invalid netmask: %s\n", netmask);
|
||||
return -1;
|
||||
}
|
||||
if (inet_pton(AF_INET, ip2, &addr2) != 1) {
|
||||
printf("Invalid IP address: %s\n", ip2);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((addr1.s_addr & mask.s_addr) == (addr2.s_addr & mask.s_addr)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int af_read_file_value(const char *file_path, char *value, int value_len) {
|
||||
FILE *file = fopen(file_path, "r");
|
||||
if (!file) {
|
||||
//perror("Failed to open file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fgets(value, value_len, file) == NULL) {
|
||||
perror("Failed to read line from file");
|
||||
fclose(file);
|
||||
return -2;
|
||||
}
|
||||
|
||||
size_t len = strlen(value);
|
||||
if (len > 0 && value[len - 1] == '\n') {
|
||||
value[len - 1] = '\0';
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int af_read_file_int_value(const char *file_path, int *value) {
|
||||
char line_buf[128] = {0};
|
||||
if (af_read_file_value(file_path, line_buf, sizeof(line_buf)) < 0){
|
||||
return -1;
|
||||
}
|
||||
*value = atoi(line_buf);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#ifndef __UTILS_H__
|
||||
#define __UTILS_H__
|
||||
char *str_trim(char *s);
|
||||
int exec_with_result_line(char *cmd, char *result, int len);
|
||||
int check_same_network(char *ip1, char *netmask, char *ip2);
|
||||
int af_read_file_value(const char *file_path, char *value, int value_len);
|
||||
int af_read_file_int_value(const char *file_path, int *value);
|
||||
unsigned int get_timestamp(void);
|
||||
#endif
|
||||
Reference in new issue
Block a user