update 2026-10-07 18:47:42

This commit is contained in:
action committed 2026-10-07 18:47:42 +08:00
1 parent 617f7ca2a6
commit bd061545ed
4832 files changed
+1693456

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
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
+82
View File
@@ -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);
}
+15
View File
@@ -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
+128
View File
@@ -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
+674
View File
@@ -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);
}
+31
View File
@@ -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
+133
View File
@@ -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;
}
+232
View File
@@ -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;
}
+35
View File
@@ -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
+19
View File
@@ -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
+379
View File
@@ -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;
}
+37
View File
@@ -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
+17
View File
@@ -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
+140
View File
@@ -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;
}
+10
View File
@@ -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
+26
View File
@@ -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
+348
View File
@@ -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;
}
+42
View File
@@ -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
+46
View File
@@ -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
+462
View File
@@ -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);
}
+15
View File
@@ -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
+214
View File
@@ -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);
}
+15
View File
@@ -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
+292
View File
@@ -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);
}
+22
View File
@@ -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
+9
View File
@@ -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
+386
View File
@@ -0,0 +1,386 @@
// 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 <uci.h>
#include "fwx_uci.h"
const char *config_path = "./config";
static struct uci_context *uci_ctx = NULL;
static struct uci_package *uci_appfilter;
int fwx_uci_get_int_value(struct uci_context *ctx, char *key)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = -1;
int dummy;
char *parameters ;
char param_tmp[128] = {0};
strcpy(param_tmp, key);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
return ret;
}
if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) {
ctx->err = UCI_ERR_NOTFOUND;
goto done;
}
e = ptr.last;
switch(e->type) {
case UCI_TYPE_SECTION:
ret = -1;
goto done;
case UCI_TYPE_OPTION:
ret = atoi(ptr.o->v.string);
goto done;
default:
break;
}
done:
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
int fwx_uci_get_value(struct uci_context *ctx, char *key, char *output, int out_len)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = UCI_OK;
int dummy;
char *parameters ;
char param_tmp[128] = {0};
strcpy(param_tmp, key);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
ret = 1;
return ret;
}
if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) {
ctx->err = UCI_ERR_NOTFOUND;
ret = 1;
goto done;
}
e = ptr.last;
switch(e->type) {
case UCI_TYPE_SECTION:
snprintf(output, out_len, "%s", ptr.s->type);
break;
case UCI_TYPE_OPTION:
snprintf(output, out_len, "%s", ptr.o->v.string);
break;
default:
ret = 1;
break;
}
done:
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
int fwx_uci_delete(struct uci_context *ctx, char *key)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = UCI_OK;
int dummy;
char *parameters ;
char param_tmp[128] = {0};
strcpy(param_tmp, key);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
ret = 1;
return ret;
}
ret = uci_delete(ctx, &ptr);
if (ret == UCI_OK)
ret = uci_save(ctx, ptr.p);
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
int fwx_uci_add_list(struct uci_context *ctx, char *key, char *value)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = UCI_OK;
int dummy;
char *parameters;
if (strlen(value) + strlen(key) >= MAX_PARAM_LIST_LEN - 1) {
printf("value too long\n");
return -1;
}
char param_tmp[MAX_PARAM_LIST_LEN] = {0};
sprintf(param_tmp, "%s=%s", key, value);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
ret = 1;
return ret;
}
ret = uci_add_list(ctx, &ptr);
if (ret == UCI_OK)
ret = uci_save(ctx, ptr.p);
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
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;
int ret = -1;
int dummy;
char *parameters ;
char param_tmp[128] = {0};
strcpy(param_tmp, key);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
return ret;
}
if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) {
ctx->err = UCI_ERR_NOTFOUND;
goto done;
}
int sep = 0;
e = ptr.last;
int len = 0;
switch(e->type) {
case UCI_TYPE_SECTION:
ret = -1;
goto done;
case UCI_TYPE_OPTION:
if (UCI_TYPE_LIST == ptr.o->type){
memset(output, 0x0, out_len);
uci_foreach_element(&ptr.o->v.list, e) {
len = strlen(output);
if (sep){
strncat(output + len, delimt, out_len);
}
len = strlen(output);
sprintf(output + len, "%s", e->name);
sep = 1;
}
ret = 0;
}
goto done;
default:
break;
}
done:
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
int fwx_uci_add_int_list(struct uci_context *ctx, char *key, int value)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = UCI_OK;
int dummy;
char *parameters ;
char param_tmp[128] = {0};
sprintf(param_tmp, "%s=%d", key, value);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
ret = 1;
return ret;
}
ret = uci_add_list(ctx, &ptr);
if (ret == UCI_OK)
ret = uci_save(ctx, ptr.p);
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
int fwx_uci_del_list(struct uci_context *ctx, char *key, char *value)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = UCI_OK;
int dummy;
char *parameters ;
char param_tmp[128] = {0};
sprintf(param_tmp, "%s=%s", key, value);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
ret = 1;
return ret;
}
ret = uci_del_list(ctx, &ptr);
if (ret == UCI_OK)
ret = uci_save(ctx, ptr.p);
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
int fwx_uci_set_value(struct uci_context *ctx, char *key, char *value)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = UCI_OK;
int dummy;
char *parameters ;
char param_tmp[2048] = {0};
sprintf(param_tmp, "%s=%s", key, value);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
ret = 1;
return ret;
}
e = ptr.last;
ret = uci_set(ctx, &ptr);
if (ret == UCI_OK)
ret = uci_save(ctx, ptr.p);
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
int fwx_uci_set_int_value(struct uci_context *ctx, char *key, int value)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = UCI_OK;
int dummy;
char *parameters ;
char param_tmp[128] = {0};
sprintf(param_tmp, "%s=%d", key, value);
if (uci_lookup_ptr(ctx, &ptr, param_tmp, true) != UCI_OK) {
ret = 1;
return ret;
}
e = ptr.last;
ret = uci_set(ctx, &ptr);
if (ret == UCI_OK)
ret = uci_save(ctx, ptr.p);
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
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 fwx_uci_delete(ctx, key);
}
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 fwx_uci_set_value(ctx, key, value);
}
int fwx_uci_commit(struct uci_context *ctx, const char * package) {
struct uci_ptr ptr;
int ret = UCI_OK;
if (!package){
return -1;
}
if (uci_lookup_ptr(ctx, &ptr, package, true) != UCI_OK) {
return -1;
}
if (uci_commit(ctx, &ptr.p, false) != UCI_OK) {
ret = -1;
goto done;
}
done:
if (ptr.p)
uci_unload(ctx, ptr.p);
return UCI_OK;
}
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;
struct uci_package *pkg = NULL;
if (UCI_OK != uci_load(ctx, package, &pkg)){
return -1;
}
uci_foreach_element(&pkg->sections, e){
struct uci_section *s = uci_to_section(e);
if (strcmp(s->type, section)){
continue;
}
count++;
}
uci_unload(ctx, pkg);
return count;
}
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 fwx_uci_get_value(ctx, key, output, out_len);
}
int fwx_uci_add_section(struct uci_context * ctx, char *package_name, char *section)
{
struct uci_section *s = NULL;
struct uci_package *p = NULL;
int ret;
ret = uci_load(ctx, package_name , &p);
if (ret != UCI_OK)
goto done;
ret = uci_add_section(ctx, p, section, &s);
if (ret != UCI_OK)
goto done;
ret = uci_save(ctx, p);
done:
if (s)
fprintf(stdout, "%s\n", s->e.name);
return ret;
}
static struct uci_package *fwx_uci_get_package(const char *config)
{
struct uci_context *ctx = uci_ctx;
struct uci_package *p = NULL;
if (!ctx)
{
ctx = uci_alloc_context();
uci_ctx = ctx;
ctx->flags &= ~UCI_FLAG_STRICT;
}
else
{
p = uci_lookup_package(ctx, config);
if (p)
uci_unload(ctx, p);
}
if (uci_load(ctx, config, &p))
return NULL;
return p;
}
+27
View File
@@ -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
+247
View File
@@ -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
+337
View File
@@ -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]);
}
+29
View File
@@ -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
+502
View File
@@ -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", &section_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);
}
+13
View File
@@ -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
+539
View File
@@ -0,0 +1,539 @@
// 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_stat.h"
#include "fwx_config.h"
#include <time.h>
#include <signal.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <json-c/json.h>
#include <sys/stat.h>
#include "fwx.h"
#include <stdio.h>
#include "fwx_utils.h"
#include "fwx_app_filter.h"
#include "check_main.h"
#include "fwx_feature.h"
#include "fwx_feature_online.h"
#include "fwx_custom_feature.h"
int current_log_level = LOG_LEVEL_WARN;
//int current_log_level = LOG_LEVEL_INFO;
int g_fwxd_debug_mode = 0;
#define CMD_GET_LAN_IP_FMT "ifconfig %s | grep 'inet addr' | awk '{print $2}' | awk -F: '{print $2}'"
#define CMD_GET_LAN_MASK_FMT "ifconfig %s | grep 'inet addr' | awk '{print $4}' | awk -F: '{print $2}'"
#define CLIENT_BACKUP_SYNC_INTERVAL_SEC 600
int g_fwx_config_chage = 1;
int g_hnat_init = 0;
int g_feature_update = 0;
extern void check_and_cleanup_history_data_by_size(void);
extern void collect_interface_traffic_rate(void);
fwx_status_t g_fwx_status = {
.internet = 1
};
fwx_capability_t g_fwx_capability = {
.wireless_support = 0
};
void fwx_timeout_handler(struct uloop_timeout *t);
struct uloop_timeout fwx_tm = {
.cb = fwx_timeout_handler};
static struct uloop_fd fwx_nl_fd = {
.cb = fwx_netlink_handler,
};
#define FEATURE_UPGRADE_SUCCESS 200
#define FEATURE_UPGRADE_FAILED 400
static int write_feature_upgrade_status(int status)
{
const char *temporary = FWX_FEATURE_UPGRADE_STATUS_PATH ".tmp";
FILE *fp = fopen(temporary, "w");
if (!fp)
return -1;
if (fprintf(fp, "%d", status) < 0 || fflush(fp) != 0 ||
fsync(fileno(fp)) != 0) {
fclose(fp);
unlink(temporary);
return -1;
}
if (fclose(fp) != 0 || chmod(temporary, 0644) != 0 ||
rename(temporary, FWX_FEATURE_UPGRADE_STATUS_PATH) != 0) {
unlink(temporary);
return -1;
}
return 0;
}
static int write_feature_info_file(void)
{
const char *feature_data;
const char *json_text;
const char *temporary = FWX_FEATURE_INFO_PATH ".tmp";
size_t feature_len = 0;
size_t offset = 0;
char line[1024];
char version[64] = {0};
int feature_type = 0;
int feature_free = 0;
int type_seen = 0;
int free_seen = 0;
struct json_object *info_obj = NULL;
FILE *fp = NULL;
int line_ret;
int ret = -1;
feature_data = fwx_feature_get_data(&feature_len);
while (feature_data &&
(line_ret = fwx_feature_next_line(feature_data, feature_len, &offset,
line, sizeof(line))) != 0) {
if (line_ret < 0)
continue;
if (!strncmp(line, "#version ", 9)) {
sscanf(line, "#version %63s", version);
} else if (!strncmp(line, "#type ", 6)) {
sscanf(line, "#type %d", &feature_type);
if (feature_type != 0 && feature_type != 1)
feature_type = 0;
type_seen = 1;
} else if (!strncmp(line, "#free ", 6)) {
sscanf(line, "#free %d", &feature_free);
feature_free = feature_free ? 1 : 0;
free_seen = 1;
}
if (version[0] && type_seen && free_seen)
break;
}
info_obj = json_object_new_object();
if (!info_obj)
return -1;
json_object_object_add(info_obj, "version", json_object_new_string(version));
json_object_object_add(info_obj, "type", json_object_new_int(feature_type));
json_object_object_add(info_obj, "free", json_object_new_int(feature_free));
json_object_object_add(info_obj, "app_count", json_object_new_int(g_app_count));
json_object_object_add(info_obj, "format", json_object_new_string("v4.0"));
json_text = json_object_to_json_string_ext(info_obj, JSON_C_TO_STRING_PLAIN);
fp = fopen(temporary, "w");
if (!fp || fwrite(json_text, 1, strlen(json_text), fp) != strlen(json_text) ||
fflush(fp) != 0 || fsync(fileno(fp)) != 0)
goto out;
if (fclose(fp) != 0) {
fp = NULL;
goto out;
}
fp = NULL;
if (chmod(temporary, 0644) != 0 || rename(temporary, FWX_FEATURE_INFO_PATH) != 0)
goto out;
ret = 0;
out:
if (fp)
fclose(fp);
if (ret != 0)
unlink(temporary);
json_object_put(info_obj);
return ret;
}
int fwx_nl_clean_feature(void){
fwx_nl_msg_t msg;
if (fwx_nl_fd.fd < 0){
return -1;
}
msg.action = FWX_NL_MSG_CLEAN_FEATURE;
return fwx_nl_send_msg_to_kernel(fwx_nl_fd.fd,(void *)&msg, sizeof(msg));
}
int fwx_nl_add_feature(char *feature){
char msg_buf[1024] = {0};
if (fwx_nl_fd.fd < 0){
return -1;
}
char *p_data = msg_buf + sizeof(fwx_nl_msg_t);
memset(msg_buf, 0, sizeof(msg_buf));
fwx_nl_msg_t *hdr = (fwx_nl_msg_t *)msg_buf;
hdr->action = FWX_NL_MSG_ADD_FEATURE;
strncpy(p_data, feature, strlen(feature));
return fwx_nl_send_msg_to_kernel(fwx_nl_fd.fd,(void *)msg_buf,
sizeof(fwx_nl_msg_t) + strlen(feature) + 1);
}
int fwx_nl_feature_load_done(void){
fwx_nl_msg_t msg;
if (fwx_nl_fd.fd < 0){
return -1;
}
msg.action = FWX_NL_MSG_FEATURE_LOAD_DONE;
return fwx_nl_send_msg_to_kernel(fwx_nl_fd.fd, (void *)&msg, sizeof(msg));
}
int fwx_load_feature_to_kernel(void){
char line_buf[MAX_FEATURE_LINE_LEN] = {0};
int feature_count = 0;
int custom_count;
size_t feature_len = 0;
size_t offset = 0;
const char *feature_data = fwx_feature_get_data(&feature_len);
if (!feature_data || feature_len == 0)
return -1;
if (fwx_nl_clean_feature() < 0){
LOG_ERROR("Failed to clean feature\n");
return -1;
}
while (offset < feature_len) {
int line_ret = fwx_feature_next_line(feature_data, feature_len, &offset,
line_buf, sizeof(line_buf));
if (line_ret < 0) {
LOG_ERROR("feature line too long\n");
continue;
}
if (line_ret == 0)
break;
str_trim(line_buf);
if (strlen(line_buf) < 8)
continue;
if (strstr(line_buf, "#"))
continue;
if (strlen(line_buf) >= MAX_FEATURE_LINE_LEN - 1){
LOG_ERROR("feature line too long: %s\n", line_buf);
continue;
}
if (fwx_nl_add_feature(line_buf) < 0) {
LOG_ERROR("Failed to send feature to kernel\n");
return -1;
}
feature_count++;
}
custom_count = fwx_custom_feature_send_to_kernel(fwx_nl_add_feature);
if (custom_count < 0) {
LOG_ERROR("Failed to send custom feature to kernel\n");
return -1;
}
feature_count += custom_count;
if (fwx_nl_feature_load_done() < 0){
LOG_ERROR("Failed to notify feature load done\n");
return -1;
}
LOG_INFO("load %d features to kernel\n", feature_count);
return 0;
}
int reload_feature(void){
char *feature_data = NULL;
size_t feature_len = 0;
if (fwx_feature_decrypt_file(FWX_FEATURE_BIN_PATH, &feature_data, &feature_len) < 0) {
LOG_ERROR("Failed to decrypt feature file\n");
if (fwx_feature_restore_backup() < 0) {
LOG_ERROR("Failed to restore feature backup\n");
return -1;
}
LOG_WARN("Restored feature file from backup\n");
if (fwx_feature_decrypt_file(FWX_FEATURE_BIN_PATH,
&feature_data, &feature_len) < 0) {
LOG_ERROR("Failed to decrypt restored feature file\n");
return -1;
}
}
fwx_feature_replace_data(feature_data, feature_len);
init_app_name_table();
init_app_class_name_table();
if (fwx_custom_feature_reload() < 0)
return -1;
if (fwx_custom_feature_add_app_names() < 0) {
LOG_ERROR("Failed to initialize custom application names\n");
return -1;
}
if (fwx_load_feature_to_kernel() < 0){
LOG_ERROR("Failed to load feature to kernel\n");
return -1;
}
if (write_feature_info_file() < 0)
LOG_ERROR("Failed to write feature info file\n");
LOG_WARN("reload feature success\n");
return 0;
}
void update_lan_ip(void){
char ip_str[32] = {0};
char mask_str[32] = {0};
struct in_addr addr;
struct in_addr mask_addr;
char cmd_buf[128] = {0};
u_int32_t lan_ip = 0;
u_int32_t lan_mask = 0;
char lan_ifname[32] = {0};
char ip_cmd_buf[128] = {0};
char mask_cmd_buf[128] = {0};
struct uci_context *ctx = uci_alloc_context();
if (!ctx)
return;
int ret = fwx_uci_get_value(ctx, "appfilter.global.lan_ifname", lan_ifname, sizeof(lan_ifname) - 1);
if (ret != 0){
strcpy(lan_ifname, "br-lan");
}
sprintf(ip_cmd_buf, CMD_GET_LAN_IP_FMT, lan_ifname);
sprintf(mask_cmd_buf, CMD_GET_LAN_MASK_FMT , lan_ifname);
exec_with_result_line(ip_cmd_buf, ip_str, sizeof(ip_str));
if (strlen(ip_str) < MIN_INET_ADDR_LEN){
update_fwx_proc_u32_value("lan_ip", 0);
}
else{
inet_aton(ip_str, &addr);
lan_ip = addr.s_addr;
update_fwx_proc_u32_value("lan_ip", lan_ip);
}
exec_with_result_line(mask_cmd_buf, mask_str, sizeof(mask_str));
if (strlen(mask_str) < MIN_INET_ADDR_LEN){
update_fwx_proc_u32_value("lan_mask", 0);
}
else{
inet_aton(mask_str, &mask_addr);
lan_mask = mask_addr.s_addr;
update_fwx_proc_u32_value("lan_mask", lan_mask);
}
uci_free_context(ctx);
}
void daily_archive_handle(void){
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
if (tm_info) {
static int last_mday = -1;
int current_mday = tm_info->tm_mday;
LOG_INFO("current_mday: %d, last_mday: %d\n", current_mday, last_mday);
if (last_mday != -1 && last_mday != current_mday) {
LOG_INFO("date changed, need to archive\n");
check_and_archive_all_clients();
}
else{
LOG_INFO("date not changed, no need to archive\n");
}
last_mday = current_mday;
}
}
void fwx_timeout_handler(struct uloop_timeout *t)
{
static int count = 0;
static u_int32_t last_check_date = 0;
u_int32_t current_time = time(NULL);
count++;
if (count % 10 == 0){
update_client_list();
move_expired_online_visit_to_offline();
}
if (count % 20 == 0){
daily_archive_handle();
update_lan_ip();
if (check_client_expire()){
flush_expire_client_node();
}
dump_client_list();
cleanup_expired_hourly_stats();
check_and_cleanup_history_data_by_size();
}
if (count % CLIENT_BACKUP_SYNC_INTERVAL_SEC == 0) {
LOG_INFO("begin save all client to files\n");
save_all_client_backup_to_files();
}
if (count % 2 == 0) {
collect_interface_traffic_rate();
}
if (fwx_nl_fd.fd < 0){
fwx_nl_fd.fd = fwx_netlink_init();
if (fwx_nl_fd.fd > 0){
uloop_fd_add(&fwx_nl_fd, ULOOP_READ);
system("killall -9 rule_manager");
LOG_INFO("netlink connect success\n");
}
}
if (g_feature_update == 1 && fwx_nl_fd.fd > 0){
if (0 == reload_feature()){
g_feature_update = 0;
}
}
if (count % 5 == 0){
fwx_session_stat_tick();
}
uloop_timeout_set(t, 1000);
}
void init_system_config_to_proc(void) {
struct uci_context *ctx = uci_alloc_context();
if (ctx) {
char lan_ifname[32] = {0};
int ret = fwx_uci_get_value(ctx, "fwx.global.lan_ifname", lan_ifname, sizeof(lan_ifname) - 1);
if (ret != 0) {
strcpy(lan_ifname, "br-lan");
}
update_fwx_proc_value("lan_ifname", lan_ifname);
int tcp_rst = fwx_uci_get_int_value(ctx, "fwx.global.tcp_rst");
if (tcp_rst != 0 && tcp_rst != 1) {
tcp_rst = 1;
}
update_fwx_proc_u32_value("tcp_rst", tcp_rst);
int work_mode = fwx_uci_get_int_value(ctx, "fwx.network.work_mode");
if (work_mode < 0) {
work_mode = 0;
}
update_fwx_proc_u32_value("work_mode", work_mode);
uci_free_context(ctx);
}
}
void fwx_handle_sigusr1(int sig) {
char version[64] = {0};
char format[32] = {0};
int status_code = FEATURE_UPGRADE_FAILED;
int process_ret;
(void)sig;
LOG_WARN("Received feature upgrade signal, candidate=%s\n",
FWX_FEATURE_CANDIDATE_PATH);
if (access(FWX_FEATURE_CANDIDATE_PATH, F_OK) != 0) {
LOG_ERROR("Feature candidate file does not exist: %s\n",
FWX_FEATURE_CANDIDATE_PATH);
if (write_feature_upgrade_status(FEATURE_UPGRADE_FAILED) < 0)
LOG_ERROR("Failed to write feature upgrade status: %d\n",
FEATURE_UPGRADE_FAILED);
return;
}
process_ret = fwx_feature_process_candidate(version, sizeof(version),
format, sizeof(format),
&status_code, NULL);
if (process_ret < 0) {
LOG_ERROR("Feature candidate processing failed, version=%s, format=%s, status=%d\n",
version[0] ? version : "--", format[0] ? format : "--",
status_code);
if (write_feature_upgrade_status(status_code) < 0)
LOG_ERROR("Failed to write feature upgrade status: %d\n", status_code);
return;
}
LOG_WARN("Feature candidate applied, target=%s, backup=%s\n",
FWX_FEATURE_BIN_PATH, FWX_FEATURE_BACKUP_PATH);
g_feature_update = 1;
if (write_feature_upgrade_status(FEATURE_UPGRADE_SUCCESS) < 0) {
LOG_ERROR("Failed to write feature upgrade status: %d\n",
FEATURE_UPGRADE_SUCCESS);
return;
}
LOG_WARN("Feature candidate processing complete, status=%d\n",
FEATURE_UPGRADE_SUCCESS);
}
void fwx_handle_sigusr2(int sig) {
LOG_INFO("Received SIGUSR2 signal\n");
if (current_log_level < LOG_LEVEL_DEBUG)
current_log_level++;
else
current_log_level = LOG_LEVEL_WARN;
LOG_WARN("change log level to %d\n", current_log_level);
}
void init_fwx_capability(void) {
g_fwx_capability.wireless_support = (access("/etc/config/wireless", F_OK) == 0) ? 1 : 0;
LOG_INFO("init capability: wireless_support=%d\n", g_fwx_capability.wireless_support);
}
static void parse_fwxd_args(int argc, char **argv)
{
int i = 0;
if (!argv || argc <= 1)
return;
for (i = 1; i < argc; i++) {
if (!argv[i])
continue;
if (strcmp(argv[i], "--debug-mode") == 0 || strcmp(argv[i], "-d") == 0) {
g_fwxd_debug_mode = 1;
continue;
}
LOG_WARN("Unknown argument ignored: %s\n", argv[i]);
}
}
int main(int argc, char **argv)
{
LOG_INFO("fwx start");
parse_fwxd_args(argc, argv);
g_feature_update = 1;
uloop_init();
signal(SIGUSR1, fwx_handle_sigusr1);
signal(SIGUSR2, fwx_handle_sigusr2);
signal(SIGCHLD, SIG_IGN);
init_client_list();
load_app_valid_time_config();
init_client_visit_db();
load_client_backup_from_files();
init_system_config_to_proc();
init_fwx_capability();
if (fwx_feature_online_init() < 0)
LOG_ERROR("Failed to initialize online feature update\n");
if (fwx_ubus_init() < 0)
{
LOG_ERROR("Failed to connect to ubus\n");
return 1;
}
if (start_check_thread() < 0) {
LOG_ERROR("Failed to start check_thread\n");
return 1;
}
fwx_nl_fd.fd = -1;
uloop_timeout_set(&fwx_tm, 5000);
uloop_timeout_add(&fwx_tm);
uloop_run();
stop_check_thread();
fwx_feature_online_cleanup();
uloop_done();
return 0;
}