update 2026-09-24 16:56:26

This commit is contained in:
action committed 2026-09-24 16:56:26 +08:00
1 parent b31a5c3878
commit e4cee331e9
6885 files changed
+3240523

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=oaf
include $(INCLUDE_DIR)/package.mk
PKG_AUTOLOAD:=oaf
RSTRIP:=:
define KernelPackage/oaf
SUBMENU:=Netfilter Extensions
TITLE:=OAF netfilter module
FILES:=$(PKG_BUILD_DIR)/oaf.ko
DEPENDS:=+kmod-nf-conntrack +kmod-nf-reject
KCONFIG:=
endef
define KernelPackage/oaf/description
oaf netfilter module
endef
KCFLAGS := -Wno-error=missing-prototypes \
-Wno-error=missing-declarations \
-Wno-error=unused-variable \
-Wno-error=declaration-after-statement \
-Wno-error=implicit-fallthrough \
-Wno-error=missing-braces \
-Wno-error=parentheses \
-Wno-error=format \
-Wno-frame-larger-than
MAKE_OPTS:= \
$(KERNEL_MAKE_FLAGS) \
M="$(PKG_BUILD_DIR)" \
KCFLAGS="$(KCFLAGS)" \
$(EXTRA_KCONFIG)
define Build/Compile
$(MAKE) -C "$(LINUX_DIR)" \
$(MAKE_OPTS) \
modules
endef
$(eval $(call KernelPackage,oaf))
+4
View File
@@ -0,0 +1,4 @@
oaf-objs := fwx_main.o fwx_utils.o regexp.o k_json.o fwx_log.o fwx_client.o fwx_client_fs.o
oaf-objs += fwx_conntrack.o fwx_mac.o fwx_config.o fwx_mac_filter.o
oaf-objs += fwx_app_filter.o
obj-m += oaf.o
+320
View File
@@ -0,0 +1,320 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#ifndef __FWX_H__
#define __FWX_H__
#define FWX_VERSION "6.0.1"
#define MAX_FWX_NL_MSG_LEN 1024
#define FWX_TIMER_INTERVAL 1
#define MAX_HOST_LEN 40
#define MIN_HOST_LEN 4
#define MAX_FWX_NETLINK_MSG_LEN 1024
#define MAX_MATCH_PKT_NUM 20
#define FWX_NETLINK_ID 29
#define MAX_NETLINK_MSG_LEN 1024
#include <linux/types.h>
#include <linux/list.h>
#include <linux/in6.h>
#define AF_FEATURE_CONFIG_FILE "/tmp/feature.cfg"
#define MAX_DPI_PKT_NUM 256
#define MIN_HTTP_DATA_LEN 16
#define MAX_APP_NAME_LEN 64
#define MAX_FEATURE_NUM_PER_APP 16
#define MIN_FEATURE_STR_LEN 8
#define MAX_FEATURE_STR_LEN 128
#define MAX_HOST_URL_LEN 128
#define MAX_REQUEST_URL_LEN 128
#define MAX_FEATURE_BITS 16
#define MAX_POS_INFO_PER_FEATURE 16
#define MAX_FEATURE_LINE_LEN 800
#define MIN_FEATURE_LINE_LEN 16
#define MAX_URL_MATCH_LEN 64
#define MAX_BYPASS_DPI_PKT_LEN 600
#define FWX_QUIC_PROTO 10
#define DNS_PORT 53
#define DNS_TYPE_HTTPS 65
#define DNS_HEADER_LEN 12
#define DNS_TCP_PREFIX_LEN 2
#define MAX_DNS_DOMAIN_LEN MAX_HOST_URL_LEN
#define MAX_DNS_QUERY_NUM 16
extern u_int32_t fwx_log_level;
#define HTTP_GET_METHOD_STR "GET"
#define HTTP_POST_METHOD_STR "POST"
#define HTTP_HEADER "HTTP"
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
#define NIPQUAD_FMT "%u.%u.%u.%u"
#define MAC_ARRAY(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
#define AF_TRUE 1
#define AF_FALSE 0
#define AF_APP_TYPE(a) (a) / 1000
#define AF_APP_ID(a) (a) % 1000
#define MAC_ADDR_LEN 6
#define HTTPS_URL_OFFSET 9
#define HTTPS_LEN_OFFSET 7
#define MAX_SEARCH_STR_LEN 32
enum AF_FEATURE_PARAM_INDEX{
AF_PROTO_PARAM_INDEX,
AF_SRC_PORT_PARAM_INDEX,
AF_DST_PORT_PARAM_INDEX,
AF_HOST_URL_PARAM_INDEX,
AF_REQUEST_URL_PARAM_INDEX,
AF_DICT_PARAM_INDEX,
AF_STR_PARAM_INDEX,
AF_IGNORE_PARAM_INDEX,
};
#define OAF_NETLINK_ID 29
#define MAX_OAF_NL_MSG_LEN 1024
enum E_FWX_NL_MSG_TYPE
{
FWX_NL_MSG_INIT,
FWX_NL_MSG_ADD_FEATURE,
FWX_NL_MSG_CLEAN_FEATURE,
FWX_NL_MSG_FEATURE_LOAD_DONE,
FWX_NL_MSG_MAX
};
enum AF_WORK_MODE {
AF_MODE_GATEWAY,
AF_MODE_BYPASS,
AF_MODE_BRIDGE,
};
#define MAX_AF_MSG_DATA_LEN 800
typedef struct af_msg{
int action;
}af_msg_t;
struct af_msg_hdr{
int magic;
int len;
};
enum e_http_method{
HTTP_METHOD_GET = 1,
HTTP_METHOD_POST,
};
typedef struct http_proto{
int match;
int method;
char *url_pos;
int url_len;
char *host_pos;
int host_len;
char *data_pos;
int data_len;
}http_proto_t;
typedef struct https_proto{
int match;
char *url_pos;
int url_len;
}https_proto_t;
typedef struct af_pos_info{
int pos;
unsigned char value;
}af_pos_info_t;
#define MAX_PORT_RANGE_NUM 5
typedef struct range_value
{
int not ;
int start;
int end;
} range_value_t;
typedef struct port_info
{
u_int8_t mode; // 0: match, 1: not match
int num;
range_value_t range_list[MAX_PORT_RANGE_NUM];
} port_info_t;
typedef struct dns_proto{
int match;
int query_num;
int qdcount;
u_int16_t qtype[MAX_DNS_QUERY_NUM];
char domain[MAX_DNS_QUERY_NUM][MAX_DNS_DOMAIN_LEN];
}dns_proto_t;
typedef struct af_feature_node{
struct list_head head;
u_int32_t app_id;
char app_name[MAX_APP_NAME_LEN];
char feature[MAX_FEATURE_STR_LEN];
u_int32_t proto;
u_int32_t sport;
u_int32_t dport;
port_info_t dport_info;
char host_url[MAX_HOST_URL_LEN];
char request_url[MAX_REQUEST_URL_LEN];
int pos_num;
char search_str[MAX_SEARCH_STR_LEN];
int ignore;
af_pos_info_t pos_info[MAX_POS_INFO_PER_FEATURE];
}af_feature_node_t;
typedef struct af_mac_info {
struct list_head hlist;
unsigned char mac[MAC_ADDR_LEN];
}af_mac_info_t;
typedef struct flow_info{
struct nf_conn *ct;
u_int32_t src;
u_int32_t dst;
struct in6_addr *src6;
struct in6_addr *dst6;
int l4_protocol;
u_int16_t sport;
u_int16_t dport;
unsigned char *l4_data;
int l4_len;
http_proto_t http;
https_proto_t https;
dns_proto_t dns;
u_int32_t app_id;
u_int8_t app_name[MAX_APP_NAME_LEN];
u_int8_t drop;
u_int8_t ignore;
u_int8_t match_by_dns;
u_int8_t dir;
u_int16_t total_len;
u_int8_t client_hello;
af_feature_node_t *feature;
}flow_info_t;
#define MAX_ACTIVE_APP_LIST_SIZE 10
#define MAX_ACTIVE_HOST_LIST_SIZE 10
typedef struct active_app_node {
struct list_head list;
u_int32_t app_id;
unsigned char mac[MAC_ADDR_LEN];
u_int32_t src_ip;
u_int32_t dst_ip;
struct in6_addr src_ip6;
struct in6_addr dst_ip6;
u_int16_t src_port;
u_int16_t dst_port;
u_int8_t l4_protocol;
u_int8_t drop;
u_int8_t proto_type;
char host[32];
char uri[32];
u_int32_t update_time;
} active_app_node_t;
typedef struct active_host_node {
struct list_head list;
char host[64];
unsigned char mac[MAC_ADDR_LEN];
u_int32_t src_ip;
u_int32_t dst_ip;
struct in6_addr src_ip6;
struct in6_addr dst_ip6;
u_int16_t src_port;
u_int16_t dst_port;
u_int8_t l4_protocol;
u_int8_t drop;
u_int8_t proto_type;
u_int32_t update_time;
} active_host_node_t;
int regexp_match(char *reg, char *text);
int is_user_match_enable(void);
struct af_client_info;
typedef struct af_client_info af_client_info_t;
void af_update_active_app_list(af_client_info_t *client, flow_info_t *flow);
active_app_node_t *af_find_active_app(u_int32_t app_id);
void af_clear_active_app_list(void);
void af_update_active_host_list(af_client_info_t *client, flow_info_t *flow);
active_host_node_t *af_find_active_host(const char *host);
void af_clear_active_host_list(void);
enum FWX_PKT_DIR
{
PKT_DIR_DOWN,
PKT_DIR_UP
};
typedef struct fwx_msg
{
int action;
void *data;
} fwx_msg_t;
struct fwx_msg_hdr
{
int len;
int msg_type;
};
extern int af_log_lvl;
#define LOG(level, fmt, ...) do { \
if ((level) <= af_log_lvl) { \
printk(KERN_CONT"%s %d " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} \
} while (0)
#define LLOG(level, fmt, ...) do { \
if ((level) <= af_log_lvl) { \
pr_info_ratelimited(KERN_CONT "%s %d " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} \
} while (0)
#define AF_ERROR(...) LOG(0, ##__VA_ARGS__)
#define AF_WARN(...) LOG(1, ##__VA_ARGS__)
#define AF_INFO(...) LOG(2, ##__VA_ARGS__)
#define AF_DEBUG(...) LOG(3, ##__VA_ARGS__)
#define AF_LMT_ERROR(...) LLOG(0, ##__VA_ARGS__)
#define AF_LMT_WARN(...) LLOG(1, ##__VA_ARGS__)
#define AF_LMT_INFO(...) LLOG(2, ##__VA_ARGS__)
#define AF_LMT_DEBUG(...) LLOG(3, ##__VA_ARGS__)
#endif
+819
View File
@@ -0,0 +1,819 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/etherdevice.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include "k_json.h"
#include "fwx.h"
#include "fwx_app_filter.h"
#include "fwx_mac.h"
#include "fwx_log.h"
DEFINE_RWLOCK(app_filter_lock);
#define app_filter_read_lock() read_lock_bh(&app_filter_lock);
#define app_filter_read_unlock() read_unlock_bh(&app_filter_lock);
#define app_filter_write_lock() write_lock_bh(&app_filter_lock);
#define app_filter_write_unlock() write_unlock_bh(&app_filter_lock);
int g_appfilter_enable = 0;
u_int32_t g_appfilter_update_jiffies = 0;
static int g_app_rule_count = 0;
static LIST_HEAD(app_filter_rule_list);
static mac_config_t g_app_filter_whitelist;
static void app_id_config_init(app_id_config_t *config) {
int i;
for (i = 0; i < APPID_HASH_SIZE; i++) {
INIT_HLIST_HEAD(&config->hash_table[i]);
}
config->count = 0;
}
static void flush_app_id_list(app_id_config_t *config) {
int i;
app_id_node_t *node;
struct hlist_node *n;
for (i = 0; i < APPID_HASH_SIZE; i++) {
hlist_for_each_entry_safe(node, n, &config->hash_table[i], hlist) {
hlist_del(&node->hlist);
kfree(node);
}
}
config->count = 0;
}
static app_id_node_t *find_app_id_node(app_id_config_t *config, int app_id) {
int hash = app_id % APPID_HASH_SIZE;
app_id_node_t *node;
hlist_for_each_entry(node, &config->hash_table[hash], hlist) {
if (node->app_id == app_id) {
return node;
}
}
return NULL;
}
static int add_app_id_node(app_id_config_t *config, int app_id) {
app_id_node_t *node;
int hash;
if (find_app_id_node(config, app_id)) {
return 0; // 已存在,返回成功
}
if (config->count >= MAX_APP_ID_PER_RULE) {
AF_ERROR("app id count exceeds limit\n");
return -1;
}
node = kmalloc(sizeof(app_id_node_t), GFP_ATOMIC);
if (!node) {
AF_ERROR("kmalloc app_id_node failed\n");
return -1;
}
node->app_id = app_id;
hash = app_id % APPID_HASH_SIZE;
hlist_add_head(&node->hlist, &config->hash_table[hash]);
config->count++;
return 0;
}
static int add_app_id_token_to_rule(app_filter_rule_t *rule, cJSON *app_id_obj)
{
int start_id = 0, end_id = 0;
int single_id = 0;
int i = 0;
const char *token = NULL;
if (!rule || !app_id_obj) {
return -1;
}
if (app_id_obj->type == cJSON_Number) {
if (app_id_obj->valueint > 0) {
return add_app_id_node(&rule->app_id_list, app_id_obj->valueint);
}
return -1;
}
if (app_id_obj->type != cJSON_String || !app_id_obj->valuestring) {
return -1;
}
token = app_id_obj->valuestring;
if (sscanf(token, "%d-%d", &start_id, &end_id) == 2) {
if (start_id <= 0 || end_id < start_id) {
AF_ERROR("invalid app_id range: %s\n", token);
return -1;
}
for (i = start_id; i <= end_id; i++) {
AF_INFO("token parse %s, id = %d\n", token, i);
if (add_app_id_node(&rule->app_id_list, i) < 0) {
return -1;
}
}
return 0;
}
if (kstrtoint(token, 10, &single_id) == 0 && single_id > 0) {
return add_app_id_node(&rule->app_id_list, single_id);
}
AF_ERROR("invalid app_id token: %s\n", token);
return -1;
}
static int del_app_id_token_from_rule(int rule_id, cJSON *app_id_obj)
{
int start_id = 0, end_id = 0;
int single_id = 0;
int i = 0;
const char *token = NULL;
if (!app_id_obj) {
return -1;
}
if (app_id_obj->type == cJSON_Number) {
return fwx_del_app_id_from_rule(rule_id, app_id_obj->valueint);
}
if (app_id_obj->type != cJSON_String || !app_id_obj->valuestring) {
return -1;
}
token = app_id_obj->valuestring;
if (sscanf(token, "%d-%d", &start_id, &end_id) == 2) {
if (start_id <= 0 || end_id < start_id) {
AF_ERROR("invalid app_id range for delete: %s\n", token);
return -1;
}
for (i = start_id; i <= end_id; i++) {
fwx_del_app_id_from_rule(rule_id, i);
}
return 0;
}
if (kstrtoint(token, 10, &single_id) == 0 && single_id > 0) {
return fwx_del_app_id_from_rule(rule_id, single_id);
}
AF_ERROR("invalid app_id token for delete: %s\n", token);
return -1;
}
int fwx_app_filter_init(void) {
app_filter_write_lock();
INIT_LIST_HEAD(&app_filter_rule_list);
g_app_rule_count = 0;
fwx_mac_config_init(&g_app_filter_whitelist);
app_filter_write_unlock();
AF_INFO("app filter init...ok\n");
return 0;
}
void fwx_app_filter_exit(void) {
app_filter_rule_t *rule, *next;
app_filter_write_lock();
list_for_each_entry_safe(rule, next, &app_filter_rule_list, list) {
flush_app_id_list(&rule->app_id_list);
fwx_flush_mac_list(&rule->mac_list);
list_del(&rule->list);
kfree(rule);
}
g_app_rule_count = 0;
fwx_flush_mac_list(&g_app_filter_whitelist);
app_filter_write_unlock();
AF_INFO("app filter exit...ok\n");
}
app_filter_rule_t *fwx_find_app_filter_rule(int rule_id) {
app_filter_rule_t *rule;
list_for_each_entry(rule, &app_filter_rule_list, list) {
if (rule->rule_id == rule_id) {
return rule;
}
}
return NULL;
}
void fwx_update_appfilter_jiffies(void){
g_appfilter_update_jiffies = jiffies;
}
int fwx_add_app_filter_rule(int rule_id) {
app_filter_rule_t *rule;
if (g_app_rule_count >= MAX_APP_FILTER_RULE_NUM) {
AF_ERROR("app filter rule count exceeds limit\n");
return -1;
}
if (fwx_find_app_filter_rule(rule_id)) {
AF_ERROR("app filter rule %d already exists\n", rule_id);
return -1;
}
rule = kmalloc(sizeof(app_filter_rule_t), GFP_ATOMIC);
if (!rule) {
AF_ERROR("kmalloc app filter rule failed\n");
return -1;
}
rule->rule_id = rule_id;
rule->enable = 1;
rule->filter_quic = 0;
fwx_mac_config_init(&rule->mac_list);
app_id_config_init(&rule->app_id_list);
INIT_LIST_HEAD(&rule->list);
app_filter_write_lock();
list_add(&rule->list, &app_filter_rule_list);
g_app_rule_count++;
app_filter_write_unlock();
AF_INFO("add app filter rule %d ok\n", rule_id);
return 0;
}
int fwx_del_app_filter_rule(int rule_id) {
app_filter_rule_t *rule;
app_filter_write_lock();
rule = fwx_find_app_filter_rule(rule_id);
if (rule) {
flush_app_id_list(&rule->app_id_list);
fwx_flush_mac_list(&rule->mac_list);
list_del(&rule->list);
kfree(rule);
g_app_rule_count--;
app_filter_write_unlock();
AF_INFO("del app filter rule %d ok\n", rule_id);
return 0;
}
app_filter_write_unlock();
AF_ERROR("app filter rule %d not found\n", rule_id);
return -1;
}
int fwx_add_app_id_to_rule(int rule_id, int app_id) {
app_filter_rule_t *rule;
app_filter_write_lock();
rule = fwx_find_app_filter_rule(rule_id);
if (rule) {
add_app_id_node(&rule->app_id_list, app_id);
app_filter_write_unlock();
return 0;
}
app_filter_write_unlock();
AF_ERROR("app filter rule %d not found\n", rule_id);
return -1;
}
int fwx_del_app_id_from_rule(int rule_id, int app_id) {
app_filter_rule_t *rule;
app_id_node_t *node;
int hash;
app_filter_write_lock();
rule = fwx_find_app_filter_rule(rule_id);
if (rule) {
hash = app_id % APPID_HASH_SIZE;
hlist_for_each_entry(node, &rule->app_id_list.hash_table[hash], hlist) {
if (node->app_id == app_id) {
hlist_del(&node->hlist);
kfree(node);
rule->app_id_list.count--;
app_filter_write_unlock();
return 0;
}
}
}
app_filter_write_unlock();
AF_ERROR("app_id %d or rule %d not found\n", app_id, rule_id);
return -1;
}
app_filter_rule_t *fwx_match_app_filter_rule(int app_id, const unsigned char *mac) {
app_filter_rule_t *rule;
app_id_node_t *node;
struct mac_node *mac_node;
int i;
int mac_list_empty;
app_filter_read_lock();
list_for_each_entry(rule, &app_filter_rule_list, list) {
if (!rule->enable) {
continue;
}
mac_node = fwx_find_mac_node(&rule->mac_list, mac);
if (!mac_node) {
mac_list_empty = 1;
for (i = 0; i < MAC_HASH_SIZE; i++) {
if (!hlist_empty(&rule->mac_list.hash_table[i])) {
mac_list_empty = 0;
break;
}
}
if (!mac_list_empty) {
continue;
}
}
if (app_id == FWX_QUIC_PROTO) {
if (rule->filter_quic == 1) {
app_filter_read_unlock();
return rule;
}
continue;
}
node = find_app_id_node(&rule->app_id_list, app_id);
if (node) {
app_filter_read_unlock();
return rule;
}
}
app_filter_read_unlock();
return NULL;
}
int fwx_api_add_app_filter_rule(cJSON *data_obj) {
cJSON *rule_id_obj;
if (!data_obj) {
return -1;
}
rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id");
if (!rule_id_obj) {
AF_ERROR("invalid rule format\n");
return -1;
}
fwx_update_appfilter_jiffies();
if (fwx_add_app_filter_rule(rule_id_obj->valueint) < 0) {
return -1;
}
AF_INFO("add app filter rule %d ok\n", rule_id_obj->valueint);
return 0;
}
int fwx_api_mod_app_filter_rule(cJSON *data_obj) {
int i;
cJSON *rule_id_obj;
cJSON *app_id_array;
cJSON *app_id_obj;
cJSON *action_obj;
cJSON *enable_obj;
cJSON *filter_quic_obj;
app_filter_rule_t *rule = NULL;
if (!data_obj) {
return -1;
}
rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id");
action_obj = cJSON_GetObjectItem(data_obj, "app_action");
cJSON *mac_action_obj = cJSON_GetObjectItem(data_obj, "mac_action");
if (!rule_id_obj) {
AF_ERROR("rule_id not found\n");
return -1;
}
rule = fwx_find_app_filter_rule(rule_id_obj->valueint);
if (!rule) {
AF_ERROR("rule %d not found\n", rule_id_obj->valueint);
return -1;
}
if (mac_action_obj) {
if (mac_action_obj->valueint == 1 || mac_action_obj->valueint == 2) {
cJSON *mac_array = cJSON_GetObjectItem(data_obj, "mac_list");
if (mac_array) {
app_filter_write_lock();
if (mac_action_obj->valueint == 1) { // flush old
fwx_flush_mac_list(&rule->mac_list);
}
for (i = 0; i < cJSON_GetArraySize(mac_array); i++) {
cJSON *mac_obj = cJSON_GetArrayItem(mac_array, i);
u8 mac_bin[ETH_ALEN] = {0};
if (mac_obj && mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
fwx_add_mac_node(&rule->mac_list, mac_bin);
}
}
app_filter_write_unlock();
}
} else if (mac_action_obj->valueint == 3) {
cJSON *mac_obj = cJSON_GetObjectItem(data_obj, "mac");
if (mac_obj) {
app_filter_write_lock();
u8 mac_bin[ETH_ALEN] = {0};
if (mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
fwx_add_mac_node(&rule->mac_list, mac_bin);
}
app_filter_write_unlock();
}
} else {
app_filter_write_lock();
fwx_flush_mac_list(&rule->mac_list);
app_filter_write_unlock();
}
}
if (action_obj) {
if (action_obj->valueint == 1 || action_obj->valueint == 2) {
app_id_array = cJSON_GetObjectItem(data_obj, "app_id_list");
if (app_id_array) {
app_filter_write_lock();
if (action_obj->valueint == 1) { // flush old
flush_app_id_list(&rule->app_id_list);
}
for (i = 0; i < cJSON_GetArraySize(app_id_array); i++) {
app_id_obj = cJSON_GetArrayItem(app_id_array, i);
if (app_id_obj) {
add_app_id_token_to_rule(rule, app_id_obj);
}
}
app_filter_write_unlock();
}
} else if (action_obj->valueint == 3) {
app_id_obj = cJSON_GetObjectItem(data_obj, "app_id");
if (app_id_obj) {
app_filter_write_lock();
add_app_id_token_to_rule(rule, app_id_obj);
app_filter_write_unlock();
}
} else {
app_filter_write_lock();
flush_app_id_list(&rule->app_id_list);
app_filter_write_unlock();
}
}
enable_obj = cJSON_GetObjectItem(data_obj, "enable");
if (enable_obj) {
rule->enable = enable_obj->valueint;
}
filter_quic_obj = cJSON_GetObjectItem(data_obj, "filter_quic");
if (filter_quic_obj) {
rule->filter_quic = (filter_quic_obj->valueint == 1) ? 1 : 0;
}
fwx_update_appfilter_jiffies();
return 0;
}
int fwx_api_del_app_filter_rule(cJSON *data_obj) {
cJSON *rule_id_obj;
cJSON *app_id_obj;
if (!data_obj) {
return -1;
}
rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id");
if (!rule_id_obj) {
AF_ERROR("rule_id not found\n");
return -1;
}
fwx_update_appfilter_jiffies();
app_id_obj = cJSON_GetObjectItem(data_obj, "app_id");
if (app_id_obj) {
return del_app_id_token_from_rule(rule_id_obj->valueint, app_id_obj);
} else {
return fwx_del_app_filter_rule(rule_id_obj->valueint);
}
}
int fwx_api_dump_app_filter_rule(cJSON *data_obj) {
app_filter_rule_t *rule;
app_id_node_t *node;
struct mac_node *mac_node;
int app_count = 0;
int mac_count = 0;
int i;
if (!data_obj) {
return -1;
}
cJSON *rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id");
app_filter_read_lock();
printk("\n");
printk("+--------+-------+-----------+------------------+------------------+\n");
printk("| RuleID | Enable| FilterQUIC| MAC List | App ID List |\n");
printk("+--------+-------+-----------+------------------+------------------+\n");
if (rule_id_obj) {
rule = fwx_find_app_filter_rule(rule_id_obj->valueint);
if (rule) {
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(mac_node, &rule->mac_list.hash_table[i], hlist) {
mac_count++;
}
}
for (i = 0; i < APPID_HASH_SIZE; i++) {
hlist_for_each_entry(node, &rule->app_id_list.hash_table[i], hlist) {
app_count++;
}
}
printk(KERN_CONT "| %-6d | %-5d | %-9d | ", rule->rule_id, rule->enable, rule->filter_quic);
if (mac_count == 0) {
printk(KERN_CONT "%-16s | ", "(all MACs)");
} else {
int total_mac_count = mac_count;
mac_count = 0;
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(mac_node, &rule->mac_list.hash_table[i], hlist) {
if (mac_count > 0) {
printk(KERN_CONT ", ");
}
printk(KERN_CONT "%pM", mac_node->mac);
mac_count++;
if (mac_count >= 32) {
if (mac_count < total_mac_count) {
printk(KERN_CONT "...");
}
break;
}
}
}
printk(KERN_CONT " | ");
}
if (app_count == 0) {
printk(KERN_CONT "%-16s |\n", "(empty)");
} else {
int total_app_count = app_count;
int printed_count = 0;
int should_break = 0;
app_count = 0;
for (i = 0; i < APPID_HASH_SIZE && !should_break; i++) {
hlist_for_each_entry(node, &rule->app_id_list.hash_table[i], hlist) {
if (printed_count > 0) {
printk(KERN_CONT ", ");
}
printk(KERN_CONT "%d", node->app_id);
printed_count++;
app_count++;
if (printed_count >= 128) {
if (app_count < total_app_count) {
printk(KERN_CONT "...");
}
should_break = 1;
break;
}
}
}
printk(KERN_CONT " |\n");
}
}
} else {
list_for_each_entry(rule, &app_filter_rule_list, list) {
app_count = 0;
mac_count = 0;
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(mac_node, &rule->mac_list.hash_table[i], hlist) {
mac_count++;
}
}
for (i = 0; i < 256; i++) {
hlist_for_each_entry(node, &rule->app_id_list.hash_table[i], hlist) {
app_count++;
}
}
printk(KERN_CONT "| %-6d | %-5d | %-9d | ", rule->rule_id, rule->enable, rule->filter_quic);
if (mac_count == 0) {
printk(KERN_CONT "%-16s | ", "(all MACs)");
} else {
int total_mac_count = mac_count;
mac_count = 0;
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(mac_node, &rule->mac_list.hash_table[i], hlist) {
if (mac_count > 0) {
printk(KERN_CONT ", ");
}
printk(KERN_CONT "%pM", mac_node->mac);
mac_count++;
if (mac_count >= 32) {
if (mac_count < total_mac_count) {
printk(KERN_CONT "...");
}
break;
}
}
}
printk(KERN_CONT " | ");
}
if (app_count == 0) {
printk(KERN_CONT "%-16s |\n", "(empty)");
} else {
int total_app_count = app_count;
int printed_count = 0;
int should_break = 0;
app_count = 0;
for (i = 0; i < APPID_HASH_SIZE && !should_break; i++) {
hlist_for_each_entry(node, &rule->app_id_list.hash_table[i], hlist) {
if (printed_count > 0) {
printk(KERN_CONT ", ");
}
printk(KERN_CONT "%d", node->app_id);
printed_count++;
app_count++;
if (printed_count >= 128) {
if (app_count < total_app_count) {
printk(KERN_CONT "...");
}
should_break = 1;
break;
}
}
}
printk(KERN_CONT " |\n");
}
}
}
printk("+--------+-------+-----------+------------------+------------------+\n");
printk("\n");
printk("App Filter Whitelist:\n");
printk("+----------------------------------------+\n");
printk("| MAC Address |\n");
printk("+----------------------------------------+\n");
int total_whitelist_count = 0;
struct mac_node *whitelist_node;
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(whitelist_node, &g_app_filter_whitelist.hash_table[i], hlist) {
total_whitelist_count++;
}
}
if (total_whitelist_count == 0) {
printk(KERN_CONT "| %-38s |\n", "(empty)");
} else {
int printed_count = 0;
int should_break = 0;
for (i = 0; i < MAC_HASH_SIZE && !should_break; i++) {
hlist_for_each_entry(whitelist_node, &g_app_filter_whitelist.hash_table[i], hlist) {
printk(KERN_CONT "| %-38pM |\n", whitelist_node->mac);
printed_count++;
if (printed_count >= 10) {
if (printed_count < total_whitelist_count) {
printk(KERN_CONT "| %-38s |\n", "...");
}
should_break = 1;
break;
}
}
}
}
printk("+----------------------------------------+\n");
printk("Total whitelist entries: %d\n", total_whitelist_count);
app_filter_read_unlock();
return 0;
}
int fwx_api_flush_app_filter_rule(cJSON *data_obj) {
app_filter_rule_t *rule, *next;
app_filter_write_lock();
list_for_each_entry_safe(rule, next, &app_filter_rule_list, list) {
flush_app_id_list(&rule->app_id_list);
list_del(&rule->list);
kfree(rule);
}
g_app_rule_count = 0;
app_filter_write_unlock();
fwx_update_appfilter_jiffies();
return 0;
}
int fwx_match_app_filter_whitelist(const unsigned char *mac) {
struct mac_node *node;
int ret = 0;
app_filter_read_lock();
node = fwx_find_mac_node(&g_app_filter_whitelist, mac);
ret = (node != NULL);
app_filter_read_unlock();
return ret;
}
int fwx_api_add_app_filter_whitelist(cJSON *data_obj) {
cJSON *mac_array;
int i;
u8 mac_bin[ETH_ALEN];
if (!data_obj) {
return -1;
}
mac_array = cJSON_GetObjectItem(data_obj, "mac_list");
if (!mac_array) {
printk("mac_list not found\n");
return -1;
}
app_filter_write_lock();
for (i = 0; i < cJSON_GetArraySize(mac_array); i++) {
cJSON *mac_obj = cJSON_GetArrayItem(mac_array, i);
if (mac_obj && mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
fwx_add_mac_node(&g_app_filter_whitelist, mac_bin);
}
}
app_filter_write_unlock();
fwx_update_appfilter_jiffies();
return 0;
}
int fwx_api_flush_app_filter_whitelist(cJSON *data_obj) {
app_filter_write_lock();
fwx_flush_mac_list(&g_app_filter_whitelist);
app_filter_write_unlock();
fwx_update_appfilter_jiffies();
return 0;
}
+89
View File
@@ -0,0 +1,89 @@
// 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 "k_json.h"
#include "fwx_mac.h"
#include <linux/list.h>
#include <linux/types.h>
#define MAX_APP_FILTER_RULE_NUM 64
#define MAX_APP_ID_PER_RULE 1024
#define APPID_HASH_SIZE 256
extern u_int32_t g_appfilter_update_jiffies;
typedef struct app_id_node {
int app_id;
struct hlist_node hlist;
} app_id_node_t;
typedef struct app_id_config {
struct hlist_head hash_table[APPID_HASH_SIZE];
int count;
} app_id_config_t;
typedef struct app_filter_rule {
int rule_id;
int enable;
int filter_quic;
mac_config_t mac_list;
app_id_config_t app_id_list;
struct list_head list;
} app_filter_rule_t;
extern int g_appfilter_enable;
int fwx_app_filter_init(void);
void fwx_app_filter_exit(void);
int fwx_add_app_filter_rule(int rule_id);
int fwx_del_app_filter_rule(int rule_id);
app_filter_rule_t *fwx_find_app_filter_rule(int rule_id);
int fwx_add_app_id_to_rule(int rule_id, int app_id);
int fwx_del_app_id_from_rule(int rule_id, int app_id);
app_filter_rule_t *fwx_match_app_filter_rule(int app_id, const unsigned char *mac);
int fwx_api_add_app_filter_rule(cJSON *data_obj);
int fwx_api_del_app_filter_rule(cJSON *data_obj);
int fwx_api_dump_app_filter_rule(cJSON *data_obj);
int fwx_api_flush_app_filter_rule(cJSON *data_obj);
int fwx_api_mod_app_filter_rule(cJSON *data_obj);
int fwx_api_add_app_filter_whitelist(cJSON *data_obj);
int fwx_api_flush_app_filter_whitelist(cJSON *data_obj);
int fwx_match_app_filter_whitelist(const unsigned char *mac);
#endif
+977
View File
@@ -0,0 +1,977 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/version.h>
#include <net/tcp.h>
#include <linux/netfilter.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_acct.h>
#include <linux/skbuff.h>
#include <net/ip.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/etherdevice.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/netfilter_ipv6.h>
#include <linux/ipv6.h>
#include <linux/in6.h>
#include <linux/timer.h>
#include <linux/sort.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include "fwx_client.h"
#include "fwx_client_fs.h"
#include "fwx_log.h"
#include "fwx_mac.h"
#include "fwx_mac_filter.h"
#include "fwx_utils.h"
#include "fwx.h"
#include "k_json.h"
DEFINE_RWLOCK(af_client_lock);
u32 total_client = 0;
struct list_head af_client_list_table[MAX_AF_CLIENT_HASH_SIZE];
int g_max_app_report_count = 3;
int g_min_http_match_count = 3;
static DEFINE_RWLOCK(record_whitelist_lock);
#define record_whitelist_read_lock() read_lock_bh(&record_whitelist_lock);
#define record_whitelist_read_unlock() read_unlock_bh(&record_whitelist_lock);
#define record_whitelist_write_lock() write_lock_bh(&record_whitelist_lock);
#define record_whitelist_write_unlock() write_unlock_bh(&record_whitelist_lock);
static mac_config_t g_record_whitelist;
int af_send_msg_to_user(char *pbuf, uint16_t len);
extern char *ipv6_to_str(const struct in6_addr *addr, char *str);
static void init_client_timer(af_client_info_t *client);
static void stop_client_timer(af_client_info_t *client);
static int is_client_mac_filter_blocked(af_client_info_t *node)
{
if (!node || !g_mac_filter_enable) {
return 0;
}
if (fwx_match_mac_filter_whitelist(node->mac)) {
return 0;
}
return fwx_match_mac_filter_rule(node->mac) ? 1 : 0;
}
int fwx_match_record_whitelist(const unsigned char *mac)
{
struct mac_node *node;
int ret = 0;
record_whitelist_read_lock();
node = fwx_find_mac_node(&g_record_whitelist, mac);
ret = (node != NULL);
record_whitelist_read_unlock();
return ret;
}
static void fwx_sync_record_whitelist_clients(void)
{
int i;
af_client_info_t *node = NULL;
AF_CLIENT_LOCK_W();
for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++)
{
list_for_each_entry(node, &af_client_list_table[i], hlist)
{
node->record_whitelist = fwx_match_record_whitelist(node->mac);
}
}
AF_CLIENT_UNLOCK_W();
}
int fwx_set_record_whitelist(const char *mac_list_str)
{
char mac_buf[1024] = {0};
char *token = NULL;
char *save_ptr = NULL;
u8 mac_bin[ETH_ALEN];
struct mac_node *node = NULL;
if (!mac_list_str) {
return -1;
}
strcpy(mac_buf, mac_list_str);
record_whitelist_write_lock();
fwx_flush_mac_list(&g_record_whitelist);
save_ptr = mac_buf;
while ((token = strsep(&save_ptr, ",")) != NULL) {
token = strim(token);
if (!token || token[0] == '\0') {
continue;
}
if (!mac_str_to_bin(token, mac_bin)) {
continue;
}
node = fwx_find_mac_node(&g_record_whitelist, mac_bin);
if (!node) {
fwx_add_mac_node(&g_record_whitelist, mac_bin);
}
}
record_whitelist_write_unlock();
fwx_sync_record_whitelist_clients();
return 0;
}
static void
nf_client_list_init(void)
{
int i;
AF_CLIENT_LOCK_W();
for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++)
{
INIT_LIST_HEAD(&af_client_list_table[i]);
}
fwx_mac_config_init(&g_record_whitelist);
AF_CLIENT_UNLOCK_W();
AF_INFO("client list init......ok\n");
}
static void
nf_client_list_clear(void)
{
int i, j;
af_client_info_t *p = NULL;
char mac_str[32] = {0};
struct hlist_head *head;
struct hlist_node *n;
app_visit_info_t *info;
AF_DEBUG("clean list\n");
AF_CLIENT_LOCK_W();
for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++)
{
while (!list_empty(&af_client_list_table[i]))
{
p = list_first_entry(&af_client_list_table[i], af_client_info_t, hlist);
memset(mac_str, 0x0, sizeof(mac_str));
sprintf(mac_str, MAC_FMT, MAC_ARRAY(p->mac));
AF_DEBUG("clean mac:%s\n", mac_str);
stop_client_timer(p);
remove_client_proc_dir(p);
spin_lock_bh(&p->visit_info_lock);
for (j = 0; j < MAX_VISIT_INFO_HASH_SIZE; j++) {
head = &p->visit_info_hash[j];
hlist_for_each_entry_safe(info, n, head, hlist) {
hlist_del(&info->hlist);
kfree(info);
}
}
spin_unlock_bh(&p->visit_info_lock);
list_del(&(p->hlist));
kfree(p);
}
}
AF_CLIENT_UNLOCK_W();
record_whitelist_write_lock();
fwx_flush_mac_list(&g_record_whitelist);
record_whitelist_write_unlock();
}
void af_client_list_reset_report_num(void)
{
int i;
af_client_info_t *node = NULL;
AF_CLIENT_LOCK_W();
for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++)
{
list_for_each_entry(node, &af_client_list_table[i], hlist)
{
node->report_count = 0;
}
}
AF_CLIENT_UNLOCK_W();
}
int get_mac_hash_code(unsigned char *mac)
{
if (!mac)
return 0;
else
return mac[5] & (MAX_AF_CLIENT_HASH_SIZE - 1);
}
af_client_info_t *find_af_client(unsigned char *mac)
{
af_client_info_t *node;
unsigned int index;
index = get_mac_hash_code(mac);
list_for_each_entry(node, &af_client_list_table[index], hlist)
{
if (0 == memcmp(node->mac, mac, 6))
{
return node;
}
}
return NULL;
}
af_client_info_t *find_and_add_af_client(unsigned char *mac)
{
af_client_info_t *nfc;
nfc = find_af_client(mac);
if (!nfc){
nfc = nf_client_add(mac);
}
return nfc;
}
af_client_info_t *find_af_client_by_ip(unsigned int ip)
{
af_client_info_t *node;
int i;
for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++)
{
list_for_each_entry(node, &af_client_list_table[i], hlist)
{
if (node->ip == ip)
{
AF_LMT_DEBUG("match node->ip=%pI4, ip=%pI4\n", &node->ip, &ip);
return node;
}
}
}
return NULL;
}
af_client_info_t *find_af_client_by_ipv6(struct in6_addr *ipv6)
{
af_client_info_t *node;
int i;
char addr_str[64] = {0};
for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++)
{
list_for_each_entry(node, &af_client_list_table[i], hlist)
{
if (ipv6_addr_equal(&node->ipv6, ipv6))
{
AF_INFO("match node->ipv6=%s\n", ipv6_to_str(&node->ipv6, addr_str));
return node;
}
}
}
return NULL;
}
af_client_info_t *
nf_client_add(unsigned char *mac)
{
af_client_info_t *node;
int index = 0;
node = (af_client_info_t *)kmalloc(sizeof(af_client_info_t), GFP_ATOMIC);
if (node == NULL)
{
AF_ERROR("kmalloc failed\n");
return NULL;
}
memset(node, 0, sizeof(af_client_info_t));
memcpy(node->mac, mac, MAC_ADDR_LEN);
node->record_whitelist = fwx_match_record_whitelist(node->mac);
node->create_jiffies = jiffies;
node->update_jiffies = jiffies;
node->timer_count = 0;
spin_lock_init(&node->visit_info_lock);
{
int i;
for (i = 0; i < MAX_VISIT_INFO_HASH_SIZE; i++) {
INIT_HLIST_HEAD(&node->visit_info_hash[i]);
}
}
index = get_mac_hash_code(mac);
AF_LMT_INFO("new client mac=" MAC_FMT "\n", MAC_ARRAY(node->mac));
total_client++;
init_client_timer(node);
list_add(&(node->hlist), &af_client_list_table[index]);
create_client_proc_dir(node);
return node;
}
void check_client_expire(void)
{
af_client_info_t *node;
int i;
AF_CLIENT_LOCK_W();
for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++)
{
list_for_each_entry(node, &af_client_list_table[i], hlist)
{
AF_DEBUG("mac:" MAC_FMT " update:%lu interval:%lu\n", MAC_ARRAY(node->mac),
node->update_jiffies, (jiffies - node->update_jiffies) / HZ);
if (jiffies > (node->update_jiffies + MAX_CLIENT_ACTIVE_TIME * HZ))
{
AF_INFO("del client:" MAC_FMT "\n", MAC_ARRAY(node->mac));
stop_client_timer(node);
remove_client_proc_dir(node);
list_del(&(node->hlist));
kfree(node);
AF_CLIENT_UNLOCK_W();
return;
}
}
}
AF_CLIENT_UNLOCK_W();
}
#define MAX_EXPIRED_VISIT_INFO_COUNT 10
static inline int get_app_id_hash_code(unsigned int app_id)
{
return app_id & (MAX_VISIT_INFO_HASH_SIZE - 1);
}
static app_visit_info_t *find_visit_info(af_client_info_t *node, unsigned int app_id)
{
struct hlist_head *head;
app_visit_info_t *info;
head = &node->visit_info_hash[get_app_id_hash_code(app_id)];
hlist_for_each_entry(info, head, hlist) {
if (info->app_id == app_id) {
return info;
}
}
return NULL;
}
app_visit_info_t *get_or_create_visit_info(af_client_info_t *node, unsigned int app_id)
{
app_visit_info_t *info;
info = find_visit_info(node, app_id);
if (info) {
return info;
}
info = (app_visit_info_t *)kmalloc(sizeof(app_visit_info_t), GFP_ATOMIC);
if (!info) {
return NULL;
}
memset(info, 0, sizeof(app_visit_info_t));
info->app_id = app_id;
INIT_HLIST_NODE(&info->hlist);
hlist_add_head(&info->hlist, &node->visit_info_hash[get_app_id_hash_code(app_id)]);
return info;
}
void flush_expired_visit_info(af_client_info_t *node)
{
int i;
int count = 0;
u_int32_t cur_timep = 0;
int timeout = 0;
struct hlist_head *head;
struct hlist_node *n;
app_visit_info_t *info;
cur_timep = af_get_timestamp_sec();
spin_lock_bh(&node->visit_info_lock);
for (i = 0; i < MAX_VISIT_INFO_HASH_SIZE; i++) {
head = &node->visit_info_hash[i];
hlist_for_each_entry_safe(info, n, head, hlist) {
if (count >= MAX_EXPIRED_VISIT_INFO_COUNT)
break;
if (info->total_num > 3) {
timeout = 180;
} else {
timeout = 60;
}
if (cur_timep - info->latest_time > timeout) {
hlist_del(&info->hlist);
spin_unlock_bh(&node->visit_info_lock);
kfree(info);
spin_lock_bh(&node->visit_info_lock);
count++;
}
}
}
spin_unlock_bh(&node->visit_info_lock);
}
#define VISIT_INFO_TIMEOUT_SEC 300
void check_expired_visit_info(af_client_info_t *node)
{
int i;
u_int32_t cur_timep = 0;
struct hlist_head *head;
struct hlist_node *n;
app_visit_info_t *info;
if (!node)
return;
cur_timep = af_get_timestamp_sec();
spin_lock_bh(&node->visit_info_lock);
for (i = 0; i < MAX_VISIT_INFO_HASH_SIZE; i++) {
head = &node->visit_info_hash[i];
hlist_for_each_entry_safe(info, n, head, hlist) {
if (cur_timep - info->latest_time > VISIT_INFO_TIMEOUT_SEC) {
hlist_del(&info->hlist);
spin_unlock_bh(&node->visit_info_lock);
kfree(info);
spin_lock_bh(&node->visit_info_lock);
}
}
}
spin_unlock_bh(&node->visit_info_lock);
}
static int compare_visit_info_count(const void *a, const void *b)
{
const app_visit_info_t *info_a = *(const app_visit_info_t **)a;
const app_visit_info_t *info_b = *(const app_visit_info_t **)b;
if (info_a->total_num > info_b->total_num)
return -1;
else if (info_a->total_num < info_b->total_num)
return 1;
return 0;
}
int __af_visit_info_report(af_client_info_t *node)
{
unsigned char mac_str[32] = {0};
unsigned char ip_str[32] = {0};
int i;
int count = 0;
int total_count = 0;
char *out = NULL;
cJSON *visit_obj = NULL;
cJSON *visit_info_array = NULL;
cJSON *root_obj = NULL;
struct hlist_head *head;
struct hlist_node *tmp;
app_visit_info_t *info;
app_visit_info_t *info_array[MAX_RECORD_APP_NUM];
int report_count = 0;
root_obj = cJSON_CreateObject();
if (!root_obj)
{
AF_ERROR("create json obj failed");
return 0;
}
sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac));
sprintf(ip_str, "%pI4", &node->ip);
cJSON_AddStringToObject(root_obj, "mac", mac_str);
cJSON_AddStringToObject(root_obj, "ip", ip_str);
cJSON_AddNumberToObject(root_obj, "app_num", node->visit_app_num);
cJSON_AddNumberToObject(root_obj, "up_flow", (u32)(node->period_flow.up_bytes >> 10));
cJSON_AddNumberToObject(root_obj, "down_flow", (u32)(node->period_flow.down_bytes >> 10));
cJSON_AddNumberToObject(root_obj, "active", node->active);
spin_lock_bh(&node->visit_info_lock);
for (i = 0; i < MAX_VISIT_INFO_HASH_SIZE; i++) {
head = &node->visit_info_hash[i];
hlist_for_each_entry(info, head, hlist) {
if (info->total_num == 0)
continue;
if (info->is_http && info->conn_count <= g_min_http_match_count) {
info->total_num = 0;
continue;
}
info_array[total_count++] = info;
info->total_num = 0; //clean all
}
}
if (total_count > 0) {
sort(info_array, total_count, sizeof(app_visit_info_t *), compare_visit_info_count, NULL);
report_count = total_count > g_max_app_report_count ? g_max_app_report_count : total_count;
}
visit_info_array = cJSON_CreateArray();
for (i = 0; i < report_count; i++) {
info = info_array[i];
visit_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(visit_obj, "appid", info->app_id);
cJSON_AddNumberToObject(visit_obj, "latest_action", info->latest_action);
info->total_num = 0;
cJSON_AddItemToArray(visit_info_array, visit_obj);
count++;
}
spin_unlock_bh(&node->visit_info_lock);
cJSON_AddItemToObject(root_obj, "visit_info", visit_info_array);
out = cJSON_Print(root_obj);
if (!out)
return 0;
cJSON_Minify(out);
node->report_count++;
af_send_msg_to_user(out, strlen(out));
cJSON_Delete(root_obj);
memset(&node->period_flow, 0x0, sizeof(node->period_flow));
kfree(out);
return 0;
}
static inline int get_packet_dir(struct net_device *in)
{
if (strstr(in->name, g_lan_ifname))
{
return PKT_DIR_UP;
}
else
{
return PKT_DIR_DOWN;
}
}
void af_update_client_status(af_client_info_t *node)
{
if (node->last_flow.down_bytes > 0){
node->period_flow.down_bytes += (node->flow.down_bytes - node->last_flow.down_bytes);
}
if (node->last_flow.up_bytes > 0){
node->period_flow.up_bytes += (node->flow.up_bytes - node->last_flow.up_bytes);
}
AF_LMT_DEBUG("period flow down:%llu up: %llu pkg up %d\n", node->period_flow.down_bytes,
node->period_flow.up_bytes, node->rate.pkt_up_rate);
node->rate.up_rate = (node->flow.up_bytes - node->last_flow.up_bytes) >> 1;
node->rate.down_rate = (node->flow.down_bytes - node->last_flow.down_bytes) >> 1;
node->rate.pkt_up_rate = (node->flow.up_pkts - node->last_flow.up_pkts) >> 1;
node->rate.pkt_down_rate = (node->flow.down_pkts - node->last_flow.down_pkts) >> 1;
node->last_flow.up_bytes = node->flow.up_bytes;
node->last_flow.down_bytes = node->flow.down_bytes;
node->last_flow.up_pkts = node->flow.up_pkts;
node->last_flow.down_pkts = node->flow.down_pkts;
if (is_client_mac_filter_blocked(node)) {
node->active = 0;
node->active_time = 0;
node->inactive_time = 0;
return;
}
if (node->rate.pkt_down_rate > 20){
node->active_time++;
node->inactive_time = 0;
node->active = 1;
}
else{
node->inactive_time++;
node->active_time = 0;
if (node->active && node->inactive_time > 30){
node->active = 0;
}
}
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
static u_int32_t af_client_hook(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
#else
static u_int32_t af_client_hook(unsigned int hook,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
#endif
struct ethhdr *ethhdr = NULL;
unsigned char smac[ETH_ALEN];
af_client_info_t *nfc = NULL;
int pkt_dir = 0;
struct iphdr *iph = NULL;
unsigned int ip = 0;
struct ipv6hdr *ip6h = NULL;
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
if (NULL == ct)
return NF_ACCEPT;
if (skb->protocol == htons(ETH_P_IPV6) && AF_MODE_GATEWAY != af_work_mode){
return NF_ACCEPT;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
if (!skb->dev)
return NF_ACCEPT;
pkt_dir = get_packet_dir(skb->dev);
#else
if (!in)
{
AF_ERROR("in is NULL\n");
return NF_ACCEPT;
}
pkt_dir = get_packet_dir(in);
#endif
if (PKT_DIR_UP != pkt_dir)
return NF_ACCEPT;
ethhdr = eth_hdr(skb);
if (ethhdr)
{
memcpy(smac, ethhdr->h_source, ETH_ALEN);
}
else
{
memcpy(smac, &skb->cb[40], ETH_ALEN);
}
AF_CLIENT_LOCK_W();
nfc = find_af_client(smac);
if (!nfc)
{
if (skb->dev)
AF_DEBUG("from dev:%s %pI4", skb->dev->name, &ip);
nfc = nf_client_add(smac);
}
if (nfc) {
if (skb->protocol == htons(ETH_P_IP)) {
iph = ip_hdr(skb);
if (iph && nfc->ip != iph->saddr) {
AF_DEBUG("update node " MAC_FMT " ipv4 %pI4--->%pI4\n",
MAC_ARRAY(nfc->mac), &nfc->ip, &iph->saddr);
nfc->ip = iph->saddr;
}
}
else if (skb->protocol == htons(ETH_P_IPV6)) {
ip6h = ipv6_hdr(skb);
if (ip6h && !ipv6_addr_equal(&nfc->ipv6, &ip6h->saddr)) {
nfc->ipv6 = ip6h->saddr;
}
}
nfc->flow.up_bytes += skb->len;
nfc->flow.up_pkts++;
nfc->update_jiffies = jiffies;
}
AF_CLIENT_UNLOCK_W();
return NF_ACCEPT;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
static u_int32_t af_client_hook2(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
#else
static u_int32_t af_client_hook2(unsigned int hook,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
#endif
struct ethhdr *ethhdr = NULL;
unsigned char smac[ETH_ALEN];
af_client_info_t *nfc = NULL;
int pkt_dir = 0;
struct iphdr *iph = NULL;
struct ipv6hdr *ip6h = NULL;
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
if (ct == NULL)
{
return NF_ACCEPT;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
if (!skb->dev)
return NF_ACCEPT;
pkt_dir = get_packet_dir(skb->dev);
#else
if (!in)
{
AF_ERROR("in is NULL\n");
return NF_ACCEPT;
}
pkt_dir = get_packet_dir(in);
#endif
if (!skb->dev)
{
return NF_ACCEPT;
}
if (PKT_DIR_DOWN != pkt_dir)
return NF_ACCEPT;
AF_CLIENT_LOCK_R();
if (skb->protocol == htons(ETH_P_IP)) {
iph = ip_hdr(skb);
nfc = find_af_client_by_ip(iph->daddr);
}
else if (skb->protocol == htons(ETH_P_IPV6)) {
ip6h = ipv6_hdr(skb);
nfc = find_af_client_by_ipv6(&ip6h->daddr);
if (nfc){
AF_LMT_DEBUG("found ipv6 %pI6 client\n", &ip6h->daddr);
}
else{
AF_LMT_DEBUG("not found ipv6 %pI6 client\n", &ip6h->daddr);
}
}
if (nfc){
nfc->flow.down_bytes += skb->len;
nfc->flow.down_pkts++;
nfc->update_jiffies = jiffies;
}
AF_CLIENT_UNLOCK_R();
return NF_ACCEPT;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
static struct nf_hook_ops af_client_ops[] = {
{
.hook = af_client_hook,
.pf = NFPROTO_INET,
.hooknum = NF_INET_FORWARD,
.priority = NF_IP_PRI_FIRST + 1,
},
{
.hook = af_client_hook2,
.pf = NFPROTO_INET,
.hooknum = NF_INET_FORWARD,
.priority = NF_IP_PRI_LAST - 1,
},
};
#else
static struct nf_hook_ops af_client_ops[] = {
{
.hook = af_client_hook,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
.owner = THIS_MODULE,
#endif
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_FORWARD,
.priority = NF_IP_PRI_FIRST + 1,
},
{
.hook = af_client_hook,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
.owner = THIS_MODULE,
#endif
.pf = NFPROTO_IPV6,
.hooknum = NF_INET_FORWARD,
.priority = NF_IP_PRI_FIRST + 1,
},
};
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)
static void client_timer_handler(struct timer_list *t)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 16, 0)
af_client_info_t *client = timer_container_of(client, t, client_timer);
#else
af_client_info_t *client = from_timer(client, t, client_timer);
#endif
#else
static void client_timer_handler(unsigned long data)
{
af_client_info_t *client = (af_client_info_t *)data;
#endif
if (!client) {
AF_ERROR("client timer handler: invalid client\n");
return;
}
if (client->timer_count >= 30) {
__af_visit_info_report(client);
client->timer_count = 0;
}
check_expired_visit_info(client);
af_update_client_status(client);
client->timer_count++;
mod_timer(&client->client_timer, jiffies + HZ * 2);
}
void init_client_timer(af_client_info_t *client)
{
if (!client) {
AF_ERROR("init_client_timer: invalid client\n");
return;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)
timer_setup(&client->client_timer, client_timer_handler, 0);
#else
setup_timer(&client->client_timer, client_timer_handler, (unsigned long)client);
#endif
mod_timer(&client->client_timer, jiffies + HZ * 1);
}
void stop_client_timer(af_client_info_t *client)
{
if (!client) {
AF_ERROR("stop_client_timer: invalid client\n");
return;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)
timer_shutdown_sync(&client->client_timer);
#else
del_timer_sync(&client->client_timer);
#endif
}
int af_client_init(void)
{
int err;
nf_client_list_init();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0)
err = nf_register_net_hooks(&init_net, af_client_ops, ARRAY_SIZE(af_client_ops));
#else
err = nf_register_hooks(af_client_ops, ARRAY_SIZE(af_client_ops));
#endif
if (err) {
AF_ERROR("register client hooks failed!\n");
}
return 0;
}
int fwx_api_add_record_whitelist(cJSON *data_obj)
{
cJSON *mac_array;
int i;
u8 mac_bin[ETH_ALEN];
if (!data_obj) {
return -1;
}
mac_array = cJSON_GetObjectItem(data_obj, "mac_list");
if (!mac_array) {
printk("mac_list not found\n");
return -1;
}
record_whitelist_write_lock();
for (i = 0; i < cJSON_GetArraySize(mac_array); i++) {
cJSON *mac_obj = cJSON_GetArrayItem(mac_array, i);
if (mac_obj && mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
fwx_add_mac_node(&g_record_whitelist, mac_bin);
}
}
record_whitelist_write_unlock();
fwx_sync_record_whitelist_clients();
return 0;
}
int fwx_api_del_record_whitelist(cJSON *data_obj)
{
cJSON *mac_obj;
u8 mac_bin[ETH_ALEN];
struct mac_node *node;
if (!data_obj) {
return -1;
}
mac_obj = cJSON_GetObjectItem(data_obj, "mac");
if (!mac_obj) {
printk("mac not found\n");
return -1;
}
if (!mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
printk("invalid mac format\n");
return -1;
}
record_whitelist_write_lock();
node = fwx_find_mac_node(&g_record_whitelist, mac_bin);
if (node) {
hlist_del(&node->hlist);
kfree(node);
record_whitelist_write_unlock();
fwx_sync_record_whitelist_clients();
return 0;
}
record_whitelist_write_unlock();
return -1;
}
int fwx_api_flush_record_whitelist(cJSON *data_obj)
{
record_whitelist_write_lock();
fwx_flush_mac_list(&g_record_whitelist);
record_whitelist_write_unlock();
fwx_sync_record_whitelist_clients();
return 0;
}
void af_client_exit(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0)
nf_unregister_net_hooks(&init_net, af_client_ops, ARRAY_SIZE(af_client_ops));
#else
nf_unregister_hooks(af_client_ops, ARRAY_SIZE(af_client_ops));
#endif
nf_client_list_clear();
return;
}
+130
View File
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#ifndef __AF_CLIENT_H__
#define __AF_CLIENT_H__
#include "fwx.h"
struct cJSON;
extern rwlock_t af_client_lock;
extern u32 nfc_debug_level;
extern int g_max_app_report_count;
extern int g_min_http_match_count;
#define VISIT_INFO_TIMEOUT_SEC 300
#define MAX_AF_CLIENT_HASH_SIZE 64
#define NF_CLIENT_TIMER_EXPIRE 1
#define MAX_CLIENT_ACTIVE_TIME 180
#define AF_CLIENT_LOCK_R() read_lock_bh(&af_client_lock);
#define AF_CLIENT_UNLOCK_R() read_unlock_bh(&af_client_lock);
#define AF_CLIENT_LOCK_W() write_lock_bh(&af_client_lock);
#define AF_CLIENT_UNLOCK_W() write_unlock_bh(&af_client_lock);
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
#define NIPQUAD_FMT "%u.%u.%u.%u"
#define MAX_VISIT_HISTORY_TIME 24
#define MAX_RECORD_APP_NUM 64
#define MAX_VISIT_INFO_HASH_SIZE 32
#define MIN_REPORT_URL_LEN 4
#define MAX_REPORT_URL_LEN 64
typedef struct flow_stat
{
long long up_bytes;
long long down_bytes;
long long up_pkts;
long long down_pkts;
} flow_stat_t;
typedef struct flow_rate
{
unsigned int up_rate;
unsigned int down_rate;
unsigned int pkt_up_rate;
unsigned int pkt_down_rate;
}flow_rate_t;
typedef struct app_visit_info
{
struct hlist_node hlist;
unsigned int app_id;
unsigned int total_num;
unsigned int drop_num;
unsigned long latest_time;
unsigned int latest_action;
unsigned int conn_count;
unsigned int is_http;
} app_visit_info_t;
typedef struct visiting_info{
int visiting_app;
int app_time;
char visiting_url[MAX_REPORT_URL_LEN];
int url_time;
}visiting_info_t;
typedef struct af_client_info
{
struct list_head hlist;
unsigned char mac[MAC_ADDR_LEN];
unsigned int ip;
struct in6_addr ipv6;
unsigned long create_jiffies;
unsigned long update_jiffies;
flow_stat_t flow;
flow_stat_t last_flow;
flow_stat_t period_flow;
flow_rate_t rate;
struct timer_list client_timer;
unsigned int visit_app_num;
int active_time;
int inactive_time;
int active;
int record_whitelist;
visiting_info_t visiting;
int report_count;
unsigned int timer_count;
spinlock_t visit_info_lock;
struct hlist_head visit_info_hash[MAX_VISIT_INFO_HASH_SIZE];
struct proc_dir_entry *proc_dir;
} af_client_info_t;
int af_client_init(void);
void af_client_exit(void);
af_client_info_t *find_af_client_by_ip(unsigned int ip);
af_client_info_t *find_af_client_by_ipv6(struct in6_addr *addr);
af_client_info_t *find_af_client(unsigned char *mac);
void check_client_expire(void);
void af_visit_info_report(void);
void af_client_list_reset_report_num(void);
af_client_info_t *nf_client_add(unsigned char *mac);
af_client_info_t *find_and_add_af_client(unsigned char *mac);
app_visit_info_t *get_or_create_visit_info(af_client_info_t *node, unsigned int app_id);
int af_update_client_app_info(af_client_info_t *node, int app_id, int drop, int from_conntrack, int is_http, int update_visiting);
void check_expired_visit_info(af_client_info_t *node);
int fwx_match_record_whitelist(const unsigned char *mac);
int fwx_set_record_whitelist(const char *mac_list_str);
int fwx_api_add_record_whitelist(struct cJSON *data_obj);
int fwx_api_del_record_whitelist(struct cJSON *data_obj);
int fwx_api_flush_record_whitelist(struct cJSON *data_obj);
#endif
+862
View File
@@ -0,0 +1,862 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/netlink.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/version.h>
#include <net/sock.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter_bridge.h>
#include <linux/version.h>
#include <linux/time.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/etherdevice.h>
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#include <linux/mutex.h>
#include "fwx_utils.h"
#include "k_json.h"
#include "fwx_log.h"
#include "fwx_client.h"
extern char *ipv6_to_str(const struct in6_addr *addr, char *str);
extern struct list_head af_client_list_table[MAX_AF_CLIENT_HASH_SIZE];
struct af_client_iter_state
{
unsigned int bucket;
void *head;
};
static void *af_client_get_first(struct seq_file *seq)
{
struct af_client_iter_state *st = seq->private;
for (st->bucket = 0; st->bucket < MAX_AF_CLIENT_HASH_SIZE; st->bucket++)
{
if (!list_empty(&(af_client_list_table[st->bucket])))
{
st->head = &(af_client_list_table[st->bucket]);
return af_client_list_table[st->bucket].next;
}
}
return NULL;
}
static void *af_client_get_next(struct seq_file *seq,
void *head)
{
struct af_client_iter_state *st = seq->private;
struct hlist_node *node = (struct hlist_node *)head;
node = node->next;
if (node != st->head)
{
return node;
}
else
{
st->bucket++;
for (; st->bucket < MAX_AF_CLIENT_HASH_SIZE; st->bucket++)
{
if (!list_empty(&(af_client_list_table[st->bucket])))
{
st->head = &(af_client_list_table[st->bucket]);
return af_client_list_table[st->bucket].next;
}
}
return NULL;
}
}
static void *af_client_get_idx(struct seq_file *seq, loff_t pos)
{
void *head = af_client_get_first(seq);
if (head)
while (pos && (head = af_client_get_next(seq, head)))
pos--;
return pos ? NULL : head;
}
static void *af_client_seq_start(struct seq_file *s, loff_t *pos)
{
AF_CLIENT_LOCK_R();
if (*pos == 0)
{
return SEQ_START_TOKEN;
}
return af_client_get_idx(s, *pos - 1);
}
static void *af_client_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
if (v == SEQ_START_TOKEN)
return af_client_get_idx(s, 0);
return af_client_get_next(s, v);
}
static void af_client_seq_stop(struct seq_file *s, void *v)
{
AF_CLIENT_UNLOCK_R();
}
static int af_client_seq_show(struct seq_file *s, void *v)
{
unsigned char mac_str[32] = {0};
unsigned char ip_str[32] = {0};
unsigned char ipv6_str[128];
int status = 1;
static int index = 0;
af_client_info_t *node = (af_client_info_t *)v;
if (v == SEQ_START_TOKEN)
{
index = 0;
seq_printf(s, "%-4s %-20s %-20s %-32s %-12s %-8s %-16s %-16s\n", "Id", "Mac", "IP", "IPv6", "RecordWl", "Status", "UpRate", "DownRate");
return 0;
}
index++;
sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac));
sprintf(ip_str, "%pI4", &node->ip);
ipv6_to_str(&node->ipv6, ipv6_str);
status = node->active ? 2 : 1;
seq_printf(s, "%-4d %-20s %-20s %-32s %-12d %-8d %-16d %-16d\n", index, mac_str, ip_str, ipv6_str, node->record_whitelist, status, node->rate.up_rate, node->rate.down_rate);
return 0;
}
static const struct seq_operations nf_client_seq_ops = {
.start = af_client_seq_start,
.next = af_client_seq_next,
.stop = af_client_seq_stop,
.show = af_client_seq_show};
static int af_client_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
struct af_client_iter_state *iter;
int err;
iter = kzalloc(sizeof(*iter), GFP_KERNEL);
if (!iter)
return -ENOMEM;
err = seq_open(file, &nf_client_seq_ops);
if (err)
{
kfree(iter);
return err;
}
seq = file->private_data;
seq->private = iter;
return 0;
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0)
static const struct file_operations af_client_fops = {
.owner = THIS_MODULE,
.open = af_client_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
#else
static const struct proc_ops af_client_fops = {
.proc_flags = PROC_ENTRY_PERMANENT,
.proc_read = seq_read,
.proc_open = af_client_open,
.proc_lseek = seq_lseek,
.proc_release = seq_release_private,
};
#endif
#define AF_CLIENT_PROC_STR "af_client"
static int af_visiting_seq_show(struct seq_file *s, void *v)
{
unsigned char mac_str[32] = {0};
unsigned char ip_str[32] = {0};
static int index = 0;
int i;
af_client_info_t *node = (af_client_info_t *)v;
if (v == SEQ_START_TOKEN)
{
index = 0;
seq_printf(s, "%-20s %-12s %-32s\n", "Mac", "Appid", "Url");
return 0;
}
index++;
sprintf(mac_str, MAC_FMT, MAC_ARRAY(node->mac));
int visiting_app = 0;
char visiting_url[64] = {0};
if (af_get_timestamp_sec() - node->visiting.app_time < 120){
visiting_app = node->visiting.visiting_app;
}
if ( af_get_timestamp_sec() - node->visiting.url_time < 120 ){
strncpy(visiting_url, node->visiting.visiting_url, sizeof(visiting_url));
}
else{
strcpy(visiting_url, "none");
}
seq_printf(s, "%-20s %-12d %-32s\n", mac_str, visiting_app, visiting_url);
return 0;
}
static const struct seq_operations nf_visiting_seq_ops = {
.start = af_client_seq_start,
.next = af_client_seq_next,
.stop = af_client_seq_stop,
.show = af_visiting_seq_show
};
static int af_visiting_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
struct af_client_iter_state *iter;
int err;
iter = kzalloc(sizeof(*iter), GFP_KERNEL);
if (!iter)
return -ENOMEM;
err = seq_open(file, &nf_visiting_seq_ops);
if (err)
{
kfree(iter);
return err;
}
seq = file->private_data;
seq->private = iter;
return 0;
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0)
static const struct file_operations af_visiting_fops = {
.owner = THIS_MODULE,
.open = af_visiting_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
#else
static const struct proc_ops af_visiting_fops = {
.proc_flags = PROC_ENTRY_PERMANENT,
.proc_read = seq_read,
.proc_open = af_visiting_open,
.proc_lseek = seq_lseek,
.proc_release = seq_release_private,
};
#endif
#define AF_VISIT_INFO "af_visit"
#define AF_CLIENT_VISIT_LIST "af_client_visit_list"
#define AF_CLIENT_BASE_DIR "fwx_client"
static struct proc_dir_entry *g_af_client_base_dir = NULL;
static DEFINE_MUTEX(af_client_base_dir_mutex);
void remove_client_proc_dir(af_client_info_t *client);
struct af_client_visit_iter_state
{
unsigned int client_bucket;
unsigned int visit_bucket;
af_client_info_t *current_client;
struct hlist_node *current_visit_node;
};
static void *af_client_visit_get_first_client(struct seq_file *seq)
{
struct af_client_visit_iter_state *st = seq->private;
af_client_info_t *client;
for (st->client_bucket = 0; st->client_bucket < MAX_AF_CLIENT_HASH_SIZE; st->client_bucket++) {
if (!list_empty(&af_client_list_table[st->client_bucket])) {
client = list_first_entry(&af_client_list_table[st->client_bucket], af_client_info_t, hlist);
st->current_client = client;
st->visit_bucket = 0;
return client;
}
}
return NULL;
}
static void *af_client_visit_get_next_visit(struct seq_file *seq, af_client_info_t *client)
{
struct af_client_visit_iter_state *st = seq->private;
struct hlist_head *head;
app_visit_info_t *info;
if (!client)
return NULL;
for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) {
head = &client->visit_info_hash[st->visit_bucket];
if (!hlist_empty(head)) {
info = hlist_entry(head->first, app_visit_info_t, hlist);
st->current_visit_node = head->first;
return info;
}
}
return NULL;
}
static void *af_client_visit_get_next(struct seq_file *seq)
{
struct af_client_visit_iter_state *st = seq->private;
app_visit_info_t *info;
struct hlist_node *next;
struct hlist_head *head;
if (!st->current_client)
return NULL;
if (st->current_visit_node) {
next = st->current_visit_node->next;
if (next) {
st->current_visit_node = next;
info = hlist_entry(next, app_visit_info_t, hlist);
return info;
}
st->visit_bucket++;
st->current_visit_node = NULL;
}
for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) {
head = &st->current_client->visit_info_hash[st->visit_bucket];
if (!hlist_empty(head)) {
st->current_visit_node = head->first;
info = hlist_entry(head->first, app_visit_info_t, hlist);
return info;
}
}
return NULL;
}
static void *af_client_visit_get_next_client(struct seq_file *seq)
{
struct af_client_visit_iter_state *st = seq->private;
af_client_info_t *client;
struct list_head *next;
if (!st->current_client)
return NULL;
next = st->current_client->hlist.next;
if (next != &af_client_list_table[st->client_bucket]) {
client = list_entry(next, af_client_info_t, hlist);
st->current_client = client;
st->visit_bucket = 0;
st->current_visit_node = NULL;
return af_client_visit_get_next_visit(seq, client);
}
st->client_bucket++;
for (; st->client_bucket < MAX_AF_CLIENT_HASH_SIZE; st->client_bucket++) {
if (!list_empty(&af_client_list_table[st->client_bucket])) {
client = list_first_entry(&af_client_list_table[st->client_bucket], af_client_info_t, hlist);
st->current_client = client;
st->visit_bucket = 0;
st->current_visit_node = NULL;
return af_client_visit_get_next_visit(seq, client);
}
}
return NULL;
}
static void *af_client_visit_seq_start(struct seq_file *s, loff_t *pos)
{
struct af_client_visit_iter_state *st = s->private;
void *v = NULL;
AF_CLIENT_LOCK_R();
if (*pos == 0) {
return SEQ_START_TOKEN;
}
v = af_client_visit_get_first_client(s);
if (!v)
return NULL;
v = af_client_visit_get_next_visit(s, (af_client_info_t *)v);
(*pos)--;
while (*pos > 0 && v) {
v = af_client_visit_get_next(s);
if (!v) {
v = af_client_visit_get_next_client(s);
}
(*pos)--;
}
return v;
}
static void *af_client_visit_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
struct af_client_visit_iter_state *st = s->private;
void *next;
(*pos)++;
if (v == SEQ_START_TOKEN) {
next = af_client_visit_get_first_client(s);
if (!next)
return NULL;
return af_client_visit_get_next_visit(s, (af_client_info_t *)next);
}
next = af_client_visit_get_next(s);
if (!next) {
next = af_client_visit_get_next_client(s);
}
return next;
}
static void af_client_visit_seq_stop(struct seq_file *s, void *v)
{
AF_CLIENT_UNLOCK_R();
}
static void print_client_visit_header(struct seq_file *s)
{
seq_printf(s, "%-20s %-8s %-8s %-8s %-8s %-8s %-12s %-12s %-10s\n",
"MAC", "AppID", "TotalNum", "DropNum", "Conn", "IsHttp", "LatestTime", "LatestAction", "OfflineTime");
}
static int af_client_visit_seq_show(struct seq_file *s, void *v)
{
unsigned char mac_str[32] = {0};
unsigned char ip_str[32] = {0};
app_visit_info_t *info = (app_visit_info_t *)v;
struct af_client_visit_iter_state *st = s->private;
if (v == SEQ_START_TOKEN) {
print_client_visit_header(s);
return 0;
}
if (!info || !st->current_client)
return 0;
sprintf(mac_str, MAC_FMT, MAC_ARRAY(st->current_client->mac));
spin_lock_bh(&st->current_client->visit_info_lock);
u_int32_t cur_time = af_get_timestamp_sec();
u_int32_t offline_time = (cur_time > info->latest_time) ? (cur_time - info->latest_time) : 0;
seq_printf(s, "%-20s %-8u %-8u %-8u %-8u %-8u %-12lu %-12u %-10u\n",
mac_str,
info->app_id,
info->total_num,
info->drop_num,
info->conn_count,
info->is_http,
info->latest_time,
info->latest_action,
offline_time);
spin_unlock_bh(&st->current_client->visit_info_lock);
return 0;
}
static const struct seq_operations af_client_visit_seq_ops = {
.start = af_client_visit_seq_start,
.next = af_client_visit_seq_next,
.stop = af_client_visit_seq_stop,
.show = af_client_visit_seq_show
};
static int af_client_visit_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
struct af_client_visit_iter_state *iter;
int err;
iter = kzalloc(sizeof(*iter), GFP_KERNEL);
if (!iter)
return -ENOMEM;
err = seq_open(file, &af_client_visit_seq_ops);
if (err) {
kfree(iter);
return err;
}
seq = file->private_data;
seq->private = iter;
return 0;
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0)
static const struct file_operations af_client_visit_fops = {
.owner = THIS_MODULE,
.open = af_client_visit_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
#else
static const struct proc_ops af_client_visit_fops = {
.proc_flags = PROC_ENTRY_PERMANENT,
.proc_read = seq_read,
.proc_open = af_client_visit_open,
.proc_lseek = seq_lseek,
.proc_release = seq_release_private,
};
#endif
int init_af_client_procfs(void)
{
struct proc_dir_entry *pde;
struct net *net = &init_net;
pde = proc_create(AF_CLIENT_PROC_STR, 0440, net->proc_net, &af_client_fops);
if (!pde)
{
AF_ERROR("nf_client proc file created error\n");
return -1;
}
pde = proc_create(AF_VISIT_INFO, 0440, net->proc_net, &af_visiting_fops);
if (!pde)
{
AF_ERROR("nf_client visiting info proc file created error\n");
return -1;
}
pde = proc_create(AF_CLIENT_VISIT_LIST, 0440, net->proc_net, &af_client_visit_fops);
if (!pde)
{
AF_ERROR("nf_client visit list proc file created error\n");
return -1;
}
return 0;
}
void finit_af_client_procfs(void)
{
struct net *net = &init_net;
int i;
af_client_info_t *client;
mutex_lock(&af_client_base_dir_mutex);
if (g_af_client_base_dir) {
AF_CLIENT_LOCK_R();
for (i = 0; i < MAX_AF_CLIENT_HASH_SIZE; i++) {
list_for_each_entry(client, &af_client_list_table[i], hlist) {
if (client && client->proc_dir) {
remove_client_proc_dir(client);
}
}
}
AF_CLIENT_UNLOCK_R();
}
remove_proc_entry(AF_CLIENT_PROC_STR, net->proc_net);
remove_proc_entry(AF_VISIT_INFO, net->proc_net);
remove_proc_entry(AF_CLIENT_VISIT_LIST, net->proc_net);
remove_proc_entry(AF_CLIENT_BASE_DIR, net->proc_net);
g_af_client_base_dir = NULL; // 重置静态变量
mutex_unlock(&af_client_base_dir_mutex);
}
static void print_single_client_visit_header(struct seq_file *s)
{
seq_printf(s, "%-8s %-8s %-8s %-8s %-8s %-12s %-12s %-10s\n",
"AppID", "TotalNum", "DropNum", "Conn", "IsHttp", "LatestTime", "LatestAction", "OfflineTime");
}
struct single_client_visit_iter_state
{
unsigned int visit_bucket;
struct hlist_node *current_visit_node;
af_client_info_t *client;
};
static void *single_client_visit_seq_start(struct seq_file *s, loff_t *pos)
{
struct single_client_visit_iter_state *st = s->private;
struct hlist_head *head;
app_visit_info_t *info;
if (!st->client)
return NULL;
spin_lock_bh(&st->client->visit_info_lock);
if (*pos == 0) {
return SEQ_START_TOKEN;
}
st->visit_bucket = 0;
st->current_visit_node = NULL;
for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) {
head = &st->client->visit_info_hash[st->visit_bucket];
if (!hlist_empty(head)) {
st->current_visit_node = head->first;
info = hlist_entry(head->first, app_visit_info_t, hlist);
(*pos)--;
if (*pos == 0) {
return info;
}
break;
}
}
while (*pos > 0 && st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE) {
if (st->current_visit_node) {
struct hlist_node *next = st->current_visit_node->next;
if (next) {
st->current_visit_node = next;
info = hlist_entry(next, app_visit_info_t, hlist);
(*pos)--;
if (*pos == 0) {
return info;
}
continue;
}
st->visit_bucket++;
st->current_visit_node = NULL;
}
for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) {
head = &st->client->visit_info_hash[st->visit_bucket];
if (!hlist_empty(head)) {
st->current_visit_node = head->first;
info = hlist_entry(head->first, app_visit_info_t, hlist);
(*pos)--;
if (*pos == 0) {
return info;
}
break;
}
}
}
return NULL;
}
static void *single_client_visit_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
struct single_client_visit_iter_state *st = s->private;
struct hlist_head *head;
app_visit_info_t *info;
(*pos)++;
if (v == SEQ_START_TOKEN) {
st->visit_bucket = 0;
st->current_visit_node = NULL;
for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) {
head = &st->client->visit_info_hash[st->visit_bucket];
if (!hlist_empty(head)) {
st->current_visit_node = head->first;
return hlist_entry(head->first, app_visit_info_t, hlist);
}
}
return NULL;
}
if (st->current_visit_node) {
struct hlist_node *next = st->current_visit_node->next;
if (next) {
st->current_visit_node = next;
return hlist_entry(next, app_visit_info_t, hlist);
}
st->visit_bucket++;
st->current_visit_node = NULL;
}
for (; st->visit_bucket < MAX_VISIT_INFO_HASH_SIZE; st->visit_bucket++) {
head = &st->client->visit_info_hash[st->visit_bucket];
if (!hlist_empty(head)) {
st->current_visit_node = head->first;
return hlist_entry(head->first, app_visit_info_t, hlist);
}
}
return NULL;
}
static void single_client_visit_seq_stop(struct seq_file *s, void *v)
{
struct single_client_visit_iter_state *st = s->private;
if (st->client) {
spin_unlock_bh(&st->client->visit_info_lock);
}
}
static int single_client_visit_seq_show(struct seq_file *s, void *v)
{
app_visit_info_t *info = (app_visit_info_t *)v;
if (v == SEQ_START_TOKEN) {
print_single_client_visit_header(s);
return 0;
}
if (!info)
return 0;
u_int32_t cur_time = af_get_timestamp_sec();
u_int32_t offline_time = (cur_time > info->latest_time) ? (cur_time - info->latest_time) : 0;
seq_printf(s, "%-8u %-8u %-8u %-8u %-8u %-12lu %-12u %-10u\n",
info->app_id,
info->total_num,
info->drop_num,
info->conn_count,
info->is_http,
info->latest_time,
info->latest_action,
offline_time);
return 0;
}
static const struct seq_operations single_client_visit_seq_ops = {
.start = single_client_visit_seq_start,
.next = single_client_visit_seq_next,
.stop = single_client_visit_seq_stop,
.show = single_client_visit_seq_show
};
static int single_client_visit_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
struct single_client_visit_iter_state *iter;
af_client_info_t *client;
int err;
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0)
client = PDE_DATA(inode);
#else
client = (af_client_info_t *)proc_get_parent_data(inode);
#endif
if (!client)
return -ENOENT;
iter = kzalloc(sizeof(*iter), GFP_KERNEL);
if (!iter)
return -ENOMEM;
iter->client = client;
err = seq_open(file, &single_client_visit_seq_ops);
if (err) {
kfree(iter);
return err;
}
seq = file->private_data;
seq->private = iter;
return 0;
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0)
static const struct file_operations single_client_visit_fops = {
.owner = THIS_MODULE,
.open = single_client_visit_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
#else
static const struct proc_ops single_client_visit_fops = {
.proc_flags = PROC_ENTRY_PERMANENT,
.proc_read = seq_read,
.proc_open = single_client_visit_open,
.proc_lseek = seq_lseek,
.proc_release = seq_release_private,
};
#endif
int create_client_proc_dir(af_client_info_t *client)
{
struct proc_dir_entry *client_dir;
struct proc_dir_entry *visit_file;
char mac_str[32] = {0};
struct net *net = &init_net;
if (!client)
return -1;
sprintf(mac_str, MAC_FMT, MAC_ARRAY(client->mac));
mutex_lock(&af_client_base_dir_mutex);
if (!g_af_client_base_dir) {
g_af_client_base_dir = proc_mkdir(AF_CLIENT_BASE_DIR, net->proc_net);
if (!g_af_client_base_dir) {
mutex_unlock(&af_client_base_dir_mutex);
AF_ERROR("create af_client base dir failed\n");
return -1;
}
}
mutex_unlock(&af_client_base_dir_mutex);
client_dir = proc_mkdir_data(mac_str, 0555, g_af_client_base_dir, client);
if (!client_dir) {
AF_ERROR("create client dir failed: %s\n", mac_str);
return -1;
}
client->proc_dir = client_dir;
visit_file = proc_create_data("visit_list", 0444, client_dir, &single_client_visit_fops, client);
if (!visit_file) {
AF_ERROR("create visit_list file failed for client: %s\n", mac_str);
proc_remove(client_dir);
client->proc_dir = NULL;
return -1;
}
return 0;
}
void remove_client_proc_dir(af_client_info_t *client)
{
if (!client || !client->proc_dir)
return;
proc_remove(client->proc_dir);
client->proc_dir = NULL;
}
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#ifndef __AF_CLIENT_FS_H__
#define __AF_CLIENT_FS_H__
int init_af_client_procfs(void);
void finit_af_client_procfs(void);
int create_client_proc_dir(af_client_info_t *client);
void remove_client_proc_dir(af_client_info_t *client);
#endif
+201
View File
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <net/tcp.h>
#include <linux/netfilter.h>
#include <net/netfilter/nf_conntrack.h>
#include <linux/skbuff.h>
#include <net/ip.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/etherdevice.h>
#include <linux/cdev.h>
#include <linux/vmalloc.h>
#include <linux/device.h>
#include <linux/version.h>
#include "fwx_config.h"
#include "k_json.h"
#include "fwx.h"
#include "fwx_mac_filter.h"
#include "fwx_app_filter.h"
#include "fwx_client.h"
static struct mutex fwx_cdev_mutex;
struct fwx_config_dev
{
dev_t id;
struct cdev char_dev;
struct class *c;
};
struct fwx_config_dev g_fwx_dev;
struct fwx_cdev_file
{
size_t size;
char buf[256 << 10];
};
k_request_item_t k_request_api_list[]={
{"add_mac_filter_rule", fwx_api_add_mac_filter_rule},
{"del_mac_filter_rule", fwx_api_del_mac_filter_rule},
{"mod_mac_filter_rule", fwx_api_mod_mac_filter_rule},
{"dump_mac_filter_rule", fwx_api_dump_mac_filter_rule},
{"flush_mac_filter_rule", fwx_api_flush_mac_filter_rule},
{"add_app_filter_rule", fwx_api_add_app_filter_rule},
{"del_app_filter_rule", fwx_api_del_app_filter_rule},
{"mod_app_filter_rule", fwx_api_mod_app_filter_rule},
{"dump_app_filter_rule", fwx_api_dump_app_filter_rule},
{"flush_app_filter_rule", fwx_api_flush_app_filter_rule},
{"add_mac_filter_whitelist", fwx_api_add_mac_filter_whitelist},
{"del_mac_filter_whitelist", fwx_api_del_mac_filter_whitelist},
{"flush_mac_filter_whitelist", fwx_api_flush_mac_filter_whitelist},
{"add_app_filter_whitelist", fwx_api_add_app_filter_whitelist},
{"flush_app_filter_whitelist", fwx_api_flush_app_filter_whitelist},
{"add_record_whitelist", fwx_api_add_record_whitelist},
{"del_record_whitelist", fwx_api_del_record_whitelist},
{"flush_record_whitelist", fwx_api_flush_record_whitelist},
};
int fwx_config_handle(char *config, unsigned int len)
{
int i;
cJSON *config_obj = NULL;
cJSON *api_obj = NULL;
cJSON *data_obj = NULL;
if (!config || len == 0)
return -1;
config_obj = cJSON_Parse(config);
if (!config_obj){
printk("parse json failed, value = %s\n", config);
return -1;
}
api_obj = cJSON_GetObjectItem(config_obj, "api");
data_obj = cJSON_GetObjectItem(config_obj, "data");
if (!api_obj){
printk("error, api obj not set\n");
cJSON_Delete(config_obj);
return -1;
}
cJSON *temp_data_obj = NULL;
if (!data_obj){
temp_data_obj = cJSON_CreateObject(); // 创建一个空的data对象
data_obj = temp_data_obj;
}
for (i = 0; i < ARRAY_SIZE(k_request_api_list); i++){
k_request_item_t *req_item = &k_request_api_list[i];
if (0 == strcmp(req_item->api, api_obj->valuestring)){
req_item->handle(data_obj);
break;
}
}
if (temp_data_obj){
cJSON_Delete(temp_data_obj);
}
cJSON_Delete(config_obj);
return 0;
}
static int fwx_cdev_open(struct inode *inode, struct file *filp)
{
struct fwx_cdev_file *file;
file = vzalloc(sizeof(*file));
if (!file)
return -EINVAL;
mutex_lock(&fwx_cdev_mutex);
filp->private_data = file;
return 0;
}
static ssize_t fwx_cdev_read(struct file *filp, char *buf, size_t count, loff_t *off)
{
return 0;
}
static int fwx_cdev_release(struct inode *inode, struct file *filp)
{
struct fwx_cdev_file *file = filp->private_data;
fwx_config_handle(file->buf, file->size);
filp->private_data = NULL;
mutex_unlock(&fwx_cdev_mutex);
vfree(file);
return 0;
}
static ssize_t fwx_cdev_write(struct file *filp, const char *buffer, size_t count, loff_t *off)
{
struct fwx_cdev_file *file = filp->private_data;
int ret;
if (file->size + count > sizeof(file->buf))
return -EINVAL;
ret = copy_from_user(file->buf + file->size, buffer, count);
if (ret != 0)
return -EINVAL;
file->size += count;
return count;
}
static struct file_operations fwx_cdev_ops = {
owner : THIS_MODULE,
release : fwx_cdev_release,
open : fwx_cdev_open,
write : fwx_cdev_write,
read : fwx_cdev_read,
};
int fwx_register_dev(void)
{
struct device *dev;
int res;
mutex_init(&fwx_cdev_mutex);
res = alloc_chrdev_region(&g_fwx_dev.id, 0, 1, FWX_CHAR_DEV);
if (res != 0)
return -EINVAL;
cdev_init(&g_fwx_dev.char_dev, &fwx_cdev_ops);
res = cdev_add(&g_fwx_dev.char_dev, g_fwx_dev.id, 1);
if (res < 0)
goto REGION_OUT;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
g_fwx_dev.c = class_create(THIS_MODULE, FWX_CHAR_DEV);
#else
g_fwx_dev.c = class_create(FWX_CHAR_DEV);
#endif
if (IS_ERR_OR_NULL(g_fwx_dev.c))
goto CDEV_OUT;
dev = device_create(g_fwx_dev.c, NULL, g_fwx_dev.id, NULL, FWX_CHAR_DEV);
if (IS_ERR_OR_NULL(dev))
goto CLASS_OUT;
return 0;
CLASS_OUT:
class_destroy(g_fwx_dev.c);
CDEV_OUT:
cdev_del(&g_fwx_dev.char_dev);
REGION_OUT:
unregister_chrdev_region(g_fwx_dev.id, 1);
printk("register char dev....fail\n");
return -EINVAL;
}
void fwx_unregister_dev(void)
{
device_destroy(g_fwx_dev.c, g_fwx_dev.id);
class_destroy(g_fwx_dev.c);
cdev_del(&g_fwx_dev.char_dev);
unregister_chrdev_region(g_fwx_dev.id, 1);
}
+19
View File
@@ -0,0 +1,19 @@
// 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 "k_json.h"
#define FWX_CHAR_DEV "fwx"
typedef int (*k_request_handler)(cJSON *data_obj);
typedef struct k_request_item{
const char *api;
k_request_handler handle;
}k_request_item_t;
int fwx_register_dev(void);
void fwx_unregister_dev(void);
#endif
+307
View File
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/jhash.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#include <net/ip.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/etherdevice.h>
#include <linux/cdev.h>
#include "fwx_conntrack.h"
#include "fwx_log.h"
#include "fwx.h"
struct hlist_head af_conn_table[AF_CONN_HASH_SIZE];
DEFINE_SPINLOCK(af_conn_lock);
static u32 af_conn_hash(u32 src_ip, u32 dst_ip,
u16 src_port, u16 dst_port,
u8 protocol)
{
return jhash_3words(src_ip, dst_ip,
((u32)protocol << 16) | src_port,
dst_port) % AF_CONN_HASH_SIZE;
}
void af_conn_cleanup(void)
{
int i;
spin_lock(&af_conn_lock);
af_conn_t *p = NULL;
struct hlist_node *n;
for (i = 0; i < AF_CONN_HASH_SIZE; i++)
{
hlist_for_each_entry_safe(p, n, &af_conn_table[i], node)
{
hlist_del(&p->node);
kfree(p);
}
}
spin_unlock(&af_conn_lock);
}
af_conn_t *af_conn_add(u32 src_ip, u32 dst_ip, u16 src_port, u16 dst_port, u8 protocol)
{
u32 hash;
af_conn_t *conn;
hash = af_conn_hash(src_ip, dst_ip, src_port, dst_port, protocol);
conn = kmalloc(sizeof(af_conn_t), GFP_ATOMIC);
if (!conn) {
return NULL;
}
conn->src_ip = src_ip;
conn->dst_ip = dst_ip;
conn->src_port = src_port;
conn->dst_port = dst_port;
conn->protocol = protocol;
conn->total_pkts = 0;
conn->app_id = 0;
conn->client_hello = 0;
conn->drop = 0;
conn->state = AF_CONN_NEW;
conn->last_jiffies = jiffies;
hlist_add_head(&conn->node, &af_conn_table[hash]);
AF_LMT_INFO("add new conn ok...%pI4:%d->%pI4:%d %d\n",
&conn->src_ip, conn->src_port, &conn->dst_ip, conn->dst_port, conn->protocol);
return conn;
}
af_conn_t* af_conn_find(u32 src_ip, u32 dst_ip, u16 src_port, u16 dst_port, u8 protocol)
{
u32 hash;
af_conn_t *conn;
hash = af_conn_hash(src_ip, dst_ip, src_port, dst_port, protocol);
hlist_for_each_entry(conn, &af_conn_table[hash], node)
{
if (conn->src_ip == src_ip && conn->dst_ip == dst_ip &&
conn->src_port == src_port && conn->dst_port == dst_port &&
conn->protocol == protocol) {
return conn;
}
}
return NULL;
}
af_conn_t* af_conn_find_and_add(u32 src_ip, u32 dst_ip, u16 src_port, u16 dst_port, u8 protocol)
{
af_conn_t *conn;
conn = af_conn_find(src_ip, dst_ip, src_port, dst_port, protocol);
if (!conn)
{
conn = af_conn_add(src_ip, dst_ip, src_port, dst_port, protocol);
}
return conn;
}
void af_conn_update(af_conn_t *conn, u32 app_id, u8 drop)
{
spin_lock(&af_conn_lock);
conn->app_id = app_id;
conn->drop = drop;
conn->last_jiffies = jiffies;
spin_unlock(&af_conn_lock);
}
#define MAX_AF_CONN_CHECK_COUNT 5
void af_conn_clean_timeout(void)
{
int i;
af_conn_t *conn;
struct hlist_node *n;
unsigned long timeout = AF_CONN_TIMEOUT * HZ;
static int last_bucket = 0;
int count = 0;
spin_lock(&af_conn_lock);
for (i = last_bucket; i < AF_CONN_HASH_SIZE; i++)
{
hlist_for_each_entry_safe(conn, n, &af_conn_table[i], node)
{
if (time_after(jiffies, conn->last_jiffies + timeout)) {
AF_LMT_INFO("clean timeout conn ok...%pI4:%d->%pI4:%d %d\n",
&conn->src_ip, conn->src_port, &conn->dst_ip, conn->dst_port, conn->protocol);
hlist_del(&(conn->node));
kfree(conn);
}
}
last_bucket = i;
count++;
if (count > MAX_AF_CONN_CHECK_COUNT)
break;
}
if (last_bucket == AF_CONN_HASH_SIZE - 1)
{
last_bucket = 0;
}
spin_unlock(&af_conn_lock);
}
struct af_conn_iter_state
{
unsigned int bucket;
};
static void *af_conn_seq_start(struct seq_file *s, loff_t *pos)
{
if (*pos == 0)
{
return SEQ_START_TOKEN;
}
struct af_conn_iter_state *st = s->private;
while (st->bucket < AF_CONN_HASH_SIZE){
if (!hlist_empty(&(af_conn_table[st->bucket])))
{
return &af_conn_table[st->bucket++].first->next;
}
st->bucket++;
}
return NULL;
}
static void *af_conn_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
if (v == SEQ_START_TOKEN){
return NULL;
}
struct hlist_node *node = (struct hlist_node *)v;
node = node->next;
if (node != NULL)
return node;
return NULL;
}
static void af_conn_seq_stop(struct seq_file *s, void *v)
{
}
static int af_conn_seq_show(struct seq_file *s, void *v)
{
unsigned char src_ip_str[32] = {0};
unsigned char dst_ip_str[32] = {0};
static int index = 0;
af_conn_t *node = (af_conn_t *)v;
if (v == SEQ_START_TOKEN)
{
index = 0;
seq_printf(s, "%-4s %-20s %-20s %-12s %-12s %-12s %-12s %-12s %-12s %-12s\n",
"Id", "src_ip", "dst_ip", "src_port", "dst_port", "protocol", "app_id", "drop", "inactive", "total_pkts");
return 0;
}
index++;
sprintf(src_ip_str, "%pI4", &node->src_ip);
sprintf(dst_ip_str, "%pI4", &node->dst_ip);
u_int32_t inactive_time = jiffies - node->last_jiffies;
seq_printf(s, "%-4d %-20s %-20s %-12d %-12d %-12d %-12d %-12d %-12d %-12d\n", index, src_ip_str, dst_ip_str,
node->src_port, node->dst_port, node->protocol, node->app_id, node->drop, inactive_time, node->total_pkts);
return 0;
}
static const struct seq_operations af_conn_seq_ops = {
.start = af_conn_seq_start,
.next = af_conn_seq_next,
.stop = af_conn_seq_stop,
.show = af_conn_seq_show
};
static int af_conn_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
struct af_conn_iter_state *iter;
int err;
iter = kzalloc(sizeof(*iter), GFP_KERNEL);
if (!iter)
return -ENOMEM;
err = seq_open(file, &af_conn_seq_ops);
if (err)
{
kfree(iter);
return err;
}
seq = file->private_data;
seq->private = iter;
return 0;
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 5, 0)
static const struct file_operations af_conn_fops = {
.owner = THIS_MODULE,
.open = af_conn_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
#else
static const struct proc_ops af_conn_fops = {
.proc_flags = PROC_ENTRY_PERMANENT,
.proc_read = seq_read,
.proc_open = af_conn_open,
.proc_lseek = seq_lseek,
.proc_release = seq_release_private,
};
#endif
#define AF_CONN_PROC_STR "af_conn"
int af_conn_init_procfs(void)
{
struct proc_dir_entry *pde;
struct net *net = &init_net;
pde = proc_create(AF_CONN_PROC_STR, 0644, net->proc_net, &af_conn_fops);
if (!pde)
{
printk("af_conn seq file created error\n");
return -1;
}
return 0;
}
void af_conn_remove_procfs(void)
{
struct net *net = &init_net;
remove_proc_entry(AF_CONN_PROC_STR, net->proc_net);
}
int af_conn_init(void)
{
int i;
for (i = 0; i < AF_CONN_HASH_SIZE; i++)
{
INIT_HLIST_HEAD(&af_conn_table[i]);
}
af_conn_init_procfs();
return 0;
}
void af_conn_exit(void){
af_conn_remove_procfs();
af_conn_cleanup();
}
+58
View File
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#ifndef __AF_SIMPLE_CONNTRACK_H__
#define __AF_SIMPLE_CONNTRACK_H__
#include <linux/types.h>
#include <linux/list.h>
#define AF_CONN_TIMEOUT 30
#define AF_CONN_HASH_SIZE 256
extern spinlock_t af_conn_lock;
typedef enum {
AF_CONN_NEW = 0,
AF_CONN_ESTABLISHED,
AF_CONN_DPI_FINISHED,
} af_conn_state_t;
typedef struct {
struct hlist_node node;
u32 src_ip;
u32 dst_ip;
u16 src_port;
u16 dst_port;
u8 protocol;
u32 total_pkts;
u32 app_id;
u8 client_hello;
u8 drop;
u8 ignore;
af_conn_state_t state;
unsigned long last_jiffies;
} af_conn_t;
int af_conn_init(void);
void af_conn_cleanup(void);
af_conn_t* af_conn_add(u32 src_ip, u32 dst_ip,
u16 src_port, u16 dst_port,
u8 protocol);
af_conn_t* af_conn_find(u32 src_ip, u32 dst_ip,
u16 src_port, u16 dst_port,
u8 protocol);
af_conn_t* af_conn_find_and_add(u32 src_ip, u32 dst_ip,
u16 src_port, u16 dst_port,
u8 protocol);
void af_conn_update(af_conn_t *conn, u32 app_id, u8 drop);
void af_conn_clean_timeout(void);
void af_conn_exit(void);
#endif
+228
View File
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/sysctl.h>
#include "fwx.h"
#include "fwx_log.h"
#include "fwx_mac_filter.h"
#include "fwx_app_filter.h"
#include "fwx_client.h"
int af_log_lvl = 0;
int fwx_test_mode = 0;
int g_record_enable = 0;
int g_by_pass_accl = 1;
int g_user_mode = 0;
int af_work_mode = AF_MODE_GATEWAY;
unsigned int fwx_lan_ip = 0;
unsigned int fwx_lan_mask = 0;
char g_lan_ifname[64] = "br-lan";
int g_tcp_rst = 1;
int g_feature_init = 0;
char g_fwx_version[64] = FWX_VERSION;
int g_feature_count = 0;
char g_record_whitelist[1024] = {0};
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0))
static int fwx_proc_record_whitelist_handler(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
#else
static int fwx_proc_record_whitelist_handler(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
#endif
{
int ret = 0;
ret = proc_dostring(table, write, buffer, lenp, ppos);
if (ret || !write) {
return ret;
}
fwx_set_record_whitelist(g_record_whitelist);
return ret;
}
static struct ctl_table fwx_table[] = {
{
.procname = "debug",
.data = &af_log_lvl,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "feature_init",
.data = &g_feature_init,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "version",
.data = g_fwx_version,
.maxlen = 64,
.mode = 0444,
.proc_handler = proc_dostring,
},
{
.procname = "feature_count",
.data = &g_feature_count,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "test_mode",
.data = &fwx_test_mode,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "appfilter_enable",
.data = &g_appfilter_enable,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "macfilter_enable",
.data = &g_mac_filter_enable,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "by_pass_accl",
.data = &g_by_pass_accl,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "tcp_rst",
.data = &g_tcp_rst,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "lan_ifname",
.data = g_lan_ifname,
.maxlen = 64,
.mode = 0666,
.proc_handler = proc_dostring,
},
{
.procname = "record_enable",
.data = &g_record_enable,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "record_whitelist",
.data = g_record_whitelist,
.maxlen = sizeof(g_record_whitelist),
.mode = 0666,
.proc_handler = fwx_proc_record_whitelist_handler,
},
{
.procname = "user_mode",
.data = &g_user_mode,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "work_mode",
.data = &af_work_mode,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "lan_ip",
.data = &fwx_lan_ip,
.maxlen = sizeof(unsigned int),
.mode = 0666,
.proc_handler = proc_douintvec,
},
{
.procname = "lan_mask",
.data = &fwx_lan_mask,
.maxlen = sizeof(unsigned int),
.mode = 0666,
.proc_handler = proc_douintvec,
},
{
.procname = "max_app_report_count",
.data = &g_max_app_report_count,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
{
.procname = "min_http_match_count",
.data = &g_min_http_match_count,
.maxlen = sizeof(int),
.mode = 0666,
.proc_handler = proc_dointvec,
},
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0))
{
}
#endif
};
#define FWX_SYS_PROC_DIR "fwx"
static struct ctl_table fwx_root_table[] = {
{
.procname = FWX_SYS_PROC_DIR,
.mode = 0555,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0))
.child = fwx_table,
#endif
},
{}
};
static struct ctl_table_header *fwx_table_header;
static int af_init_log_sysctl(void)
{
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0))
fwx_table_header = register_sysctl_table(fwx_root_table);
#else
fwx_table_header = register_sysctl(FWX_SYS_PROC_DIR, fwx_table);
#endif
if (fwx_table_header == NULL){
printk("init log sysctl...failed\n");
return -ENOMEM;
}
return 0;
}
static int af_fini_log_sysctl(void)
{
if (fwx_table_header)
unregister_sysctl_table(fwx_table_header);
return 0;
}
int af_log_init(void){
af_init_log_sysctl();
return 0;
}
int af_log_exit(void){
af_fini_log_sysctl();
return 0;
}
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#ifndef __AF_DEBUG_H__
#define __AF_DEBUG_H__
extern int fwx_test_mode;
extern int af_work_mode;
extern int g_app_filter_enable;
extern int g_record_enable;
extern int g_by_pass_accl;
extern unsigned int fwx_lan_ip;
extern unsigned int fwx_lan_mask;
extern int g_feature_init;
extern int g_user_mode;
extern char g_lan_ifname[64];
extern int g_tcp_rst;
extern int g_feature_count;
#define TEST_MODE() (fwx_test_mode)
int af_log_init(void);
int af_log_exit(void);
#endif
+81
View File
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/list.h>
#include <linux/slab.h>
#include "fwx.h"
#include "fwx_mac.h"
static int mac_hash(const unsigned char *mac) {
int hash = 0;
int i;
for (i = 0; i < ETH_ALEN; i++) {
hash += mac[i];
}
return hash % MAC_HASH_SIZE;
}
void fwx_mac_config_init(mac_config_t *config){
int i;
for (i = 0; i < MAC_HASH_SIZE; i++){
INIT_HLIST_HEAD(&config->hash_table[i]);
}
}
void fwx_add_mac_node(mac_config_t *config, const unsigned char *mac) {
struct mac_node *new_node;
int hash = mac_hash(mac);
new_node = kmalloc(sizeof(struct mac_node), GFP_KERNEL);
if (!new_node) {
pr_err("Memory allocation failed\n");
return;
}
memcpy(new_node->mac, mac, ETH_ALEN);
INIT_HLIST_NODE(&new_node->hlist);
hlist_add_head(&new_node->hlist, &config->hash_table[hash]);
}
void fwx_dump_mac_node(mac_config_t *config) {
int i;
struct mac_node *node;
for(i = 0; i < MAC_HASH_SIZE; i++){
hlist_for_each_entry(node, &config->hash_table[i], hlist) {
printk("dump node: %pM\n", node->mac);
}
}
}
struct mac_node *fwx_find_mac_node(mac_config_t *config, const unsigned char *mac){
struct mac_node *node;
if (!config || !mac)
return NULL;
int hash = mac_hash(mac);
hlist_for_each_entry(node, &config->hash_table[hash], hlist) {
if (memcmp(node->mac, mac, ETH_ALEN) == 0) {
return node;
}
}
return NULL;
}
struct mac_node *fwx_flush_mac_list(mac_config_t *config){
int i;
struct mac_node *node;
struct hlist_node *n;
if (!config)
return NULL;
for (i = 0; i < MAC_HASH_SIZE; i++){
hlist_for_each_entry_safe(node, n, &config->hash_table[i], hlist) {
hlist_del(&node->hlist);
kfree(node);
}
}
return NULL;
}
+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_MAC_H__
#define __FWX_MAC_H__
#define MAC_HASH_SIZE 128
#ifndef ETH_ALEN
#define ETH_ALEN 6
#endif
struct mac_node {
unsigned char mac[ETH_ALEN];
struct hlist_node hlist;
};
typedef struct mac_config{
struct hlist_head hash_table[MAC_HASH_SIZE];
}mac_config_t;
void fwx_mac_config_init(mac_config_t *config);
void fwx_add_mac_node(mac_config_t *config, const unsigned char *mac);
void fwx_dump_mac_node(mac_config_t *config);
struct mac_node *fwx_find_mac_node(mac_config_t *config, const unsigned char *mac);
int mac_str_to_bin(const char *mac_str, u8 *mac_bin);
struct mac_node *fwx_flush_mac_list(mac_config_t *config);
#endif
+545
View File
@@ -0,0 +1,545 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/etherdevice.h>
#include <linux/list.h>
#include "k_json.h"
#include "fwx.h"
#include "fwx_mac.h"
#include "fwx_mac_filter.h"
DEFINE_RWLOCK(mac_filter_lock);
#define mac_filter_read_lock() read_lock_bh(&mac_filter_lock);
#define mac_filter_read_unlock() read_unlock_bh(&mac_filter_lock);
#define mac_filter_write_lock() write_lock_bh(&mac_filter_lock);
#define mac_filter_write_unlock() write_unlock_bh(&mac_filter_lock);
static int g_mac_rule_count = 0;
static LIST_HEAD(mac_filter_rule_list);
int g_mac_filter_enable = 0;
static mac_config_t g_mac_filter_whitelist;
int fwx_mac_filter_init(void) {
mac_filter_write_lock();
INIT_LIST_HEAD(&mac_filter_rule_list);
g_mac_rule_count = 0;
fwx_mac_config_init(&g_mac_filter_whitelist);
mac_filter_write_unlock();
return 0;
}
void fwx_mac_filter_exit(void) {
mac_filter_rule_t *rule, *next;
mac_filter_write_lock();
list_for_each_entry_safe(rule, next, &mac_filter_rule_list, list) {
fwx_flush_mac_list(&rule->mac_list);
list_del(&rule->list);
kfree(rule);
}
g_mac_rule_count = 0;
fwx_flush_mac_list(&g_mac_filter_whitelist);
mac_filter_write_unlock();
}
mac_filter_rule_t *fwx_find_mac_filter_rule(int rule_id) {
mac_filter_rule_t *rule;
list_for_each_entry(rule, &mac_filter_rule_list, list) {
if (rule->rule_id == rule_id) {
return rule;
}
}
return NULL;
}
int fwx_add_mac_filter_rule(int rule_id, int mode) {
mac_filter_rule_t *rule;
if (g_mac_rule_count >= MAX_MAC_FILTER_RULE_NUM) {
return -1;
}
if (fwx_find_mac_filter_rule(rule_id)) {
return -1;
}
rule = kmalloc(sizeof(mac_filter_rule_t), GFP_ATOMIC);
if (!rule) {
printk("kmalloc mac filter rule failed\n");
return -1;
}
rule->rule_id = rule_id;
rule->mode = (mode == MAC_FILTER_MODE_SINGLE_USER) ? MAC_FILTER_MODE_SINGLE_USER : MAC_FILTER_MODE_ALL_USERS;
fwx_mac_config_init(&rule->mac_list);
INIT_LIST_HEAD(&rule->list);
mac_filter_write_lock();
list_add(&rule->list, &mac_filter_rule_list);
g_mac_rule_count++;
mac_filter_write_unlock();
return 0;
}
int fwx_del_mac_filter_rule(int rule_id) {
mac_filter_rule_t *rule;
mac_filter_write_lock();
rule = fwx_find_mac_filter_rule(rule_id);
if (rule) {
fwx_flush_mac_list(&rule->mac_list);
list_del(&rule->list);
kfree(rule);
g_mac_rule_count--;
mac_filter_write_unlock();
return 0;
}
mac_filter_write_unlock();
return -1;
}
int fwx_add_mac_to_rule(int rule_id, const unsigned char *mac) {
mac_filter_rule_t *rule;
mac_filter_write_lock();
rule = fwx_find_mac_filter_rule(rule_id);
if (rule) {
fwx_add_mac_node(&rule->mac_list, mac);
mac_filter_write_unlock();
return 0;
}
mac_filter_write_unlock();
return -1;
}
int fwx_del_mac_from_rule(int rule_id, const unsigned char *mac) {
mac_filter_rule_t *rule;
struct mac_node *node;
mac_filter_write_lock();
rule = fwx_find_mac_filter_rule(rule_id);
if (rule) {
node = fwx_find_mac_node(&rule->mac_list, mac);
if (node) {
hlist_del(&node->hlist);
kfree(node);
mac_filter_write_unlock();
return 0;
}
}
mac_filter_write_unlock();
return -1;
}
mac_filter_rule_t *fwx_match_mac_filter_rule(const unsigned char *mac) {
mac_filter_rule_t *rule;
struct mac_node *node;
mac_filter_read_lock();
list_for_each_entry(rule, &mac_filter_rule_list, list) {
if (rule->mode == MAC_FILTER_MODE_ALL_USERS) {
mac_filter_read_unlock();
return rule;
}
node = fwx_find_mac_node(&rule->mac_list, mac);
if (node) {
mac_filter_read_unlock();
return rule;
}
}
mac_filter_read_unlock();
return NULL;
}
int fwx_api_add_mac_filter_rule(cJSON *data_obj) {
cJSON *rule_id_obj;
cJSON *mode_obj;
int mode = MAC_FILTER_MODE_ALL_USERS;
if (!data_obj) {
return -1;
}
rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id");
mode_obj = cJSON_GetObjectItem(data_obj, "mode");
if (mode_obj) {
mode = mode_obj->valueint;
}
if (mode != MAC_FILTER_MODE_SINGLE_USER) {
mode = MAC_FILTER_MODE_ALL_USERS;
}
if (!rule_id_obj) {
printk("invalid rule format\n");
return -1;
}
if (fwx_add_mac_filter_rule(rule_id_obj->valueint, mode) < 0) {
return -1;
}
return 0;
}
int fwx_api_mod_mac_filter_rule(cJSON *data_obj) {
int i;
cJSON *rule_id_obj;
cJSON *mac_array;
cJSON *mac_obj;
cJSON *action_obj;
cJSON *mode_obj;
mac_filter_rule_t *rule = NULL;
if (!data_obj) {
return -1;
}
rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id");
action_obj = cJSON_GetObjectItem(data_obj, "mac_action");
if (!rule_id_obj) {
printk("rule_id not found\n");
return -1;
}
rule = fwx_find_mac_filter_rule(rule_id_obj->valueint);
if (!rule) {
printk("rule %d not found\n", rule_id_obj->valueint);
return -1;
}
mode_obj = cJSON_GetObjectItem(data_obj, "mode");
if (mode_obj) {
int mode = mode_obj->valueint;
if (mode != MAC_FILTER_MODE_SINGLE_USER) {
mode = MAC_FILTER_MODE_ALL_USERS;
}
mac_filter_write_lock();
rule->mode = mode;
mac_filter_write_unlock();
}
if (action_obj){
if (action_obj->valueint == 1 || action_obj->valueint == 2) {
mac_array = cJSON_GetObjectItem(data_obj, "mac_list");
if (mac_array) {
mac_filter_write_lock();
if (action_obj->valueint == 1){ // flush old
fwx_flush_mac_list(&rule->mac_list);
}
for (i = 0; i < cJSON_GetArraySize(mac_array); i++) {
mac_obj = cJSON_GetArrayItem(mac_array, i);
u8 mac_bin[ETH_ALEN] = {0};
if (mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
fwx_add_mac_node(&rule->mac_list, mac_bin);
}
}
mac_filter_write_unlock();
}
}
else if (action_obj->valueint == 3) {
mac_obj = cJSON_GetObjectItem(data_obj, "mac");
if (mac_obj) {
mac_filter_write_lock();
u8 mac_bin[ETH_ALEN] = {0};
if (mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
fwx_add_mac_node(&rule->mac_list, mac_bin);
}
mac_filter_write_unlock();
}
}
else{
mac_filter_write_lock();
fwx_flush_mac_list(&rule->mac_list);
mac_filter_write_unlock();
}
}
return 0;
}
int fwx_api_del_mac_filter_rule(cJSON *data_obj) {
cJSON *rule_id_obj;
cJSON *mac_obj;
if (!data_obj) {
return -1;
}
rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id");
if (!rule_id_obj) {
printk("rule_id not found\n");
return -1;
}
mac_obj = cJSON_GetObjectItem(data_obj, "mac");
if (mac_obj) {
u8 mac_bin[ETH_ALEN] = {0};
if (mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
return fwx_del_mac_from_rule(rule_id_obj->valueint, mac_bin);
}
} else {
return fwx_del_mac_filter_rule(rule_id_obj->valueint);
}
return -1;
}
int fwx_api_dump_mac_filter_rule(cJSON *data_obj) {
mac_filter_rule_t *rule;
struct mac_node *node;
int mac_count = 0;
int i;
if (!data_obj) {
return -1;
}
cJSON *rule_id_obj = cJSON_GetObjectItem(data_obj, "rule_id");
mac_filter_read_lock();
printk("\n");
printk("+--------+------+----------------------------------------+\n");
printk("| RuleID | Mode | MAC List |\n");
printk("+--------+------+----------------------------------------+\n");
if (rule_id_obj) {
rule = fwx_find_mac_filter_rule(rule_id_obj->valueint);
if (rule) {
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(node, &rule->mac_list.hash_table[i], hlist) {
mac_count++;
}
}
printk(KERN_CONT "| %-6d | %-4d | ", rule->rule_id, rule->mode);
if (rule->mode == MAC_FILTER_MODE_ALL_USERS) {
printk(KERN_CONT "%-38s |\n", "(all users)");
} else if (mac_count == 0) {
printk(KERN_CONT "%-38s |\n", "(empty)");
} else {
int total_mac_count = mac_count;
mac_count = 0;
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(node, &rule->mac_list.hash_table[i], hlist) {
if (mac_count > 0) {
printk(KERN_CONT ", ");
}
printk(KERN_CONT "%pM", node->mac);
mac_count++;
if (mac_count >= 5) { // 最多显示5个MAC
if (mac_count < total_mac_count) {
printk(KERN_CONT "...");
}
break;
}
}
}
printk(KERN_CONT " |\n");
}
}
} else {
list_for_each_entry(rule, &mac_filter_rule_list, list) {
mac_count = 0;
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(node, &rule->mac_list.hash_table[i], hlist) {
mac_count++;
}
}
printk(KERN_CONT "| %-6d | %-4d | ", rule->rule_id, rule->mode);
if (rule->mode == MAC_FILTER_MODE_ALL_USERS) {
printk(KERN_CONT "%-38s |\n", "(all users)");
} else if (mac_count == 0) {
printk(KERN_CONT "%-38s |\n", "(empty)");
} else {
int total_mac_count = mac_count;
mac_count = 0;
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(node, &rule->mac_list.hash_table[i], hlist) {
if (mac_count > 0) {
printk(KERN_CONT ", ");
}
printk(KERN_CONT "%pM", node->mac);
mac_count++;
if (mac_count >= 5) { // 最多显示5个MAC
if (mac_count < total_mac_count) {
printk(KERN_CONT "...");
}
break;
}
}
}
printk(KERN_CONT " |\n");
}
}
}
printk("+--------+------+----------------------------------------+\n");
printk("\n");
printk("MAC Filter Whitelist:\n");
printk("+----------------------------------------+\n");
printk("| MAC Address |\n");
printk("+----------------------------------------+\n");
int total_whitelist_count = 0;
for (i = 0; i < MAC_HASH_SIZE; i++) {
hlist_for_each_entry(node, &g_mac_filter_whitelist.hash_table[i], hlist) {
total_whitelist_count++;
}
}
if (total_whitelist_count == 0) {
printk(KERN_CONT "| %-38s |\n", "(empty)");
} else {
int printed_count = 0;
int should_break = 0;
for (i = 0; i < MAC_HASH_SIZE && !should_break; i++) {
hlist_for_each_entry(node, &g_mac_filter_whitelist.hash_table[i], hlist) {
printk(KERN_CONT "| %-38pM |\n", node->mac);
printed_count++;
if (printed_count >= 10) { // 最多显示10个MAC
if (printed_count < total_whitelist_count) {
printk(KERN_CONT "| %-38s |\n", "...");
}
should_break = 1;
break;
}
}
}
}
printk("+----------------------------------------+\n");
printk("Total whitelist entries: %d\n", total_whitelist_count);
mac_filter_read_unlock();
return 0;
}
int fwx_api_flush_mac_filter_rule(cJSON *data_obj) {
mac_filter_rule_t *rule, *next;
mac_filter_write_lock();
list_for_each_entry_safe(rule, next, &mac_filter_rule_list, list) {
fwx_flush_mac_list(&rule->mac_list);
list_del(&rule->list);
kfree(rule);
}
g_mac_rule_count = 0;
mac_filter_write_unlock();
return 0;
}
int fwx_match_mac_filter_whitelist(const unsigned char *mac) {
struct mac_node *node;
int ret = 0;
mac_filter_read_lock();
node = fwx_find_mac_node(&g_mac_filter_whitelist, mac);
ret = (node != NULL);
mac_filter_read_unlock();
return ret;
}
int fwx_api_add_mac_filter_whitelist(cJSON *data_obj) {
cJSON *mac_array;
int i;
u8 mac_bin[ETH_ALEN];
if (!data_obj) {
return -1;
}
mac_array = cJSON_GetObjectItem(data_obj, "mac_list");
if (!mac_array) {
printk("mac_list not found\n");
return -1;
}
mac_filter_write_lock();
for (i = 0; i < cJSON_GetArraySize(mac_array); i++) {
cJSON *mac_obj = cJSON_GetArrayItem(mac_array, i);
if (mac_obj && mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
fwx_add_mac_node(&g_mac_filter_whitelist, mac_bin);
}
}
mac_filter_write_unlock();
return 0;
}
int fwx_api_del_mac_filter_whitelist(cJSON *data_obj) {
cJSON *mac_obj;
u8 mac_bin[ETH_ALEN];
struct mac_node *node;
if (!data_obj) {
return -1;
}
mac_obj = cJSON_GetObjectItem(data_obj, "mac");
if (!mac_obj) {
printk("mac not found\n");
return -1;
}
if (!mac_str_to_bin(mac_obj->valuestring, mac_bin)) {
printk("invalid mac format\n");
return -1;
}
mac_filter_write_lock();
node = fwx_find_mac_node(&g_mac_filter_whitelist, mac_bin);
if (node) {
hlist_del(&node->hlist);
kfree(node);
mac_filter_write_unlock();
return 0;
}
mac_filter_write_unlock();
return -1;
}
int fwx_api_flush_mac_filter_whitelist(cJSON *data_obj) {
mac_filter_write_lock();
fwx_flush_mac_list(&g_mac_filter_whitelist);
mac_filter_write_unlock();
return 0;
}
+56
View File
@@ -0,0 +1,56 @@
// 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 "k_json.h"
#include "fwx_mac.h"
#include <linux/list.h>
#define MAX_MAC_FILTER_RULE_NUM 64
#define MAC_FILTER_MODE_ALL_USERS 1
#define MAC_FILTER_MODE_SINGLE_USER 2
typedef struct mac_filter_rule {
int rule_id;
int mode;
mac_config_t mac_list;
struct list_head list;
} mac_filter_rule_t;
extern int g_mac_filter_enable;
int fwx_api_add_mac_filter_whitelist(cJSON *data_obj);
int fwx_api_del_mac_filter_whitelist(cJSON *data_obj);
int fwx_api_flush_mac_filter_whitelist(cJSON *data_obj);
int fwx_match_mac_filter_whitelist(const unsigned char *mac);
int fwx_mac_filter_init(void);
void fwx_mac_filter_exit(void);
int fwx_add_mac_filter_rule(int rule_id, int mode);
int fwx_del_mac_filter_rule(int rule_id);
mac_filter_rule_t *fwx_find_mac_filter_rule(int rule_id);
int fwx_add_mac_to_rule(int rule_id, const unsigned char *mac);
int fwx_del_mac_from_rule(int rule_id, const unsigned char *mac);
mac_filter_rule_t *fwx_match_mac_filter_rule(const unsigned char *mac);
int fwx_api_add_mac_filter_rule(cJSON *data_obj);
int fwx_api_del_mac_filter_rule(cJSON *data_obj);
int fwx_api_dump_mac_filter_rule(cJSON *data_obj);
int fwx_api_flush_mac_filter_rule(cJSON *data_obj);
int fwx_api_mod_mac_filter_rule(cJSON *data_obj);
#endif
+3501
View File
File diff suppressed because it is too large. Load diff
+401
View File
@@ -0,0 +1,401 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/version.h>
#include "fwx_utils.h"
#define MAX_DUMP_STR_LEN 256
u_int32_t af_get_timestamp_sec(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)
struct timespec64 ts;
ktime_get_real_ts64(&ts);
return (u_int32_t)ts.tv_sec;
#else
struct timespec ts;
ts = current_kernel_time();
return ts.tv_sec;
#endif
}
int mac_str_to_bin(const char *mac_str, u8 *mac_bin) {
if (!mac_str || !mac_bin)
return 0;
const char *p = mac_str;
int i = 0;
while (*p && i < 6) {
unsigned int byte;
char hex[3] = {0};
if (sscanf(p, "%2hhx", &byte) != 1)
return false;
mac_bin[i++] = (u8)byte;
while (*p && *p != ':')
p++;
if (*p == ':')
p++;
}
return i == 6;
}
int k_atoi(const char *str) {
int result = 0;
while (*str == ' ' || *str == '\t') {
str++;
}
while (*str >= '0' && *str <= '9') {
result = result * 10 + (*str - '0');
str++;
}
return result;
}
char *k_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 check_local_network_ip(unsigned int ip)
{
if ((ip & 0xffff0000) == 0xc0a80000)
return 1;
else if ((ip & 0xfff00000) == 0xac100000)
return 1;
else if ((ip & 0xff000000) == 0x0a000000)
return 1;
else
return 0;
}
void dump_str(char *name, unsigned char *p, int len)
{
char buf[MAX_DUMP_STR_LEN] = {0};
if (len > MAX_DUMP_STR_LEN) {
len = MAX_DUMP_STR_LEN - 1;
}
printk("%s: ",name);
strncpy(buf, p, len);
printk("[%s]\n", buf);
}
int isprint_char(unsigned char c)
{
if (c >= 0x20 && c <= 0x7e)
return 1;
else
return 0;
}
void print_hex_ascii(const unsigned char *data, size_t size) {
size_t i, j;
for (i = 0; i < size; i += 16) {
printk(KERN_CONT"%08lx ", (unsigned long)i);
for (j = 0; j < 16; ++j) {
if (i + j < size) {
printk(KERN_CONT"%02x ", data[i + j]);
} else {
printk(KERN_CONT" ");
}
}
printk(KERN_CONT" ");
for (j = 0; j < 16; ++j) {
if (i + j < size) {
unsigned char c = data[i + j];
printk(KERN_CONT"%c", isprint_char(c) ? c : '.');
}
}
printk(KERN_CONT"\n");
}
printk(KERN_CONT"---------------------------------------\n");
}
void dump_hex(char *name, unsigned char *p, int len)
{
int i;
if (len > MAX_DUMP_STR_LEN) {
len = MAX_DUMP_STR_LEN - 1;
}
printk("%s: ",name);
for (i = 0; i < len; i++) {
if (i % 16 == 0)
printk(KERN_CONT "\n");
printk(KERN_CONT "%02X ",*(p + i));
}
printk(KERN_CONT "\n");
}
#ifndef va_arg
typedef signed int acpi_native_int;
#ifndef _VALIST
#define _VALIST
typedef char *va_list;
#endif
#define _AUPBND (sizeof (acpi_native_int) - 1)
#define _ADNBND (sizeof (acpi_native_int) - 1)
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap) (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
#endif
#ifndef TOLOWER
#define TOLOWER(x) ((x) | 0x20)
#endif
static long long k_simple_strtoll(const char *cp, char **endp, unsigned int base)
{
if (*cp == '-')
return -simple_strtoull(cp + 1, endp, base);
return simple_strtoull(cp, endp, base);
}
static int skip_atoi(const char **s)
{
int i=0;
while (isdigit(**s))
i = i*10 + *((*s)++) - '0';
return i;
}
char *skip_spaces(const char *str)
{
while (isspace(*str) && ((unsigned char )*str != 0xa0))
++str;
return (char *)str;
}
static int k_vsscanf(const char *buf, const char *fmt, va_list args)
{
const char *str = buf;
char *next;
char digit;
int num = 0;
u8 qualifier;
u8 base;
s16 field_width;
bool is_sign;
while (*fmt && *str) {
if (isspace(*fmt)) {
fmt = skip_spaces(++fmt);
str = skip_spaces(str);
}
if (*fmt != '%' && *fmt) {
if (*fmt++ != *str++)
break;
continue;
}
if (!*fmt)
break;
++fmt;
if (*fmt == '*') {
while (!isspace(*fmt) && *fmt != '%' && *fmt)
fmt++;
while (!isspace(*str) && *str)
str++;
continue;
}
field_width = -1;
if (isdigit(*fmt))
field_width = skip_atoi(&fmt);
qualifier = -1;
if (*fmt == 'h' || TOLOWER(*fmt) == 'l' ||
TOLOWER(*fmt) == 'z') {
qualifier = *fmt++;
if (unlikely(qualifier == *fmt)) {
if (qualifier == 'h') {
qualifier = 'H';
fmt++;
} else if (qualifier == 'l') {
qualifier = 'L';
fmt++;
}
}
}
if (!*fmt || !*str)
break;
base = 10;
is_sign = 0;
switch (*fmt++) {
case 'c':
{
char *s = (char *)va_arg(args, char*);
if (field_width == -1)
field_width = 1;
do {
*s++ = *str++;
} while (--field_width > 0 && *str);
num++;
}
continue;
case 's':
{
char *s = (char *)va_arg(args, char *);
if (field_width == -1)
field_width = SHRT_MAX;
str = skip_spaces(str);
while (*str && (!isspace(*str) || ((unsigned char )*str == 0xA0) )&& field_width--)
*s++ = *str++;
*s = '\0';
num++;
}
continue;
case 'n':
{
int *i = (int *)va_arg(args, int*);
*i = str - buf;
}
continue;
case 'o':
base = 8;
break;
case 'x':
case 'X':
base = 16;
break;
case 'i':
base = 0;
case 'd':
is_sign = 1;
case 'u':
break;
case '%':
if (*str++ != '%')
return num;
continue;
default:
return num;
}
str = skip_spaces(str);
digit = *str;
if (is_sign && digit == '-')
digit = *(str + 1);
if (!digit
|| (base == 16 && !isxdigit(digit))
|| (base == 10 && !isdigit(digit))
|| (base == 8 && (!isdigit(digit) || digit > '7'))
|| (base == 0 && !isdigit(digit)))
break;
switch (qualifier) {
case 'H':
if (is_sign) {
signed char *s = (signed char *)va_arg(args, signed char *);
*s = (signed char)simple_strtol(str, &next, base);
} else {
unsigned char *s = (unsigned char *)va_arg(args, unsigned char *);
*s = (unsigned char)simple_strtoul(str, &next, base);
}
break;
case 'h':
if (is_sign) {
short *s = (short *)va_arg(args, short *);
*s = (short)simple_strtol(str, &next, base);
} else {
unsigned short *s = (unsigned short *)va_arg(args, unsigned short *);
*s = (unsigned short)simple_strtoul(str, &next, base);
}
break;
case 'l':
if (is_sign) {
long *l = (long *)va_arg(args, long *);
*l = simple_strtol(str, &next, base);
} else {
unsigned long *l = (unsigned long *)va_arg(args, unsigned long *);
*l = simple_strtoul(str, &next, base);
}
break;
case 'L':
if (is_sign) {
long long *l = (long long *)va_arg(args, long long *);
*l = k_simple_strtoll(str, &next, base);
} else {
unsigned long long *l = (unsigned long long *)va_arg(args, unsigned long long *);
*l = simple_strtoull(str, &next, base);
}
break;
case 'Z':
case 'z':
{
size_t *s = (size_t *)va_arg(args, size_t *);
*s = (size_t)simple_strtoul(str, &next, base);
}
break;
default:
if (is_sign) {
int *i = (int *)va_arg(args, int *);
*i = (int)simple_strtol(str, &next, base);
} else {
unsigned int *i = (unsigned int *)va_arg(args, unsigned int*);
*i = (unsigned int)simple_strtoul(str, &next, base);
}
break;
}
num++;
if (!next)
break;
str = next;
}
if (*fmt == '%' && *(fmt + 1) == 'n') {
int *p = (int *)va_arg(args, int *);
*p = str - buf;
}
return num;
}
int k_sscanf(const char *buf, const char *fmt, ...)
{
va_list args;
int i;
va_start(args, fmt);
i = k_vsscanf(buf, fmt, args);
va_end(args);
return i;
}
+24
View File
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright(c) 2026 destan19(TT) <www.fanchmwrt.com>
*/
#ifndef AF_UTILS_H
#define AF_UTILS_H
u_int32_t af_get_timestamp_sec(void);
char *k_trim(char *s);
int mac_str_to_bin(const char *mac_str, u8 *mac_bin);
int check_local_network_ip(unsigned int ip);
void dump_str(char *name, unsigned char *p, int len);
void dump_hex(char *name, unsigned char *p, int len);
int k_sscanf(const char *buf, const char *fmt, ...);
int k_atoi(const char *str);
void print_hex_ascii(const unsigned char *data, size_t size);
#endif
+521
View File
@@ -0,0 +1,521 @@
/*
Copyright (c) 2009 Dave Gamble
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#if 0
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <float.h>
#endif
#include "k_json.h"
#include <linux/module.h>
#include <linux/slab.h>
#if 0
#if defined(WINDOWS) || defined(__WIN32__) || defined(WIN32) || defined(_WIN32)
#define strcasecmp stricmp
#define strdup _strdup
#endif
#endif
#if 0
static void *(*cJSON_malloc)(size_t sz) = malloc;
static void *(*cJSON_realloc)(void *ptr, size_t sz) = realloc;
static void (*cJSON_free)(void *ptr) = free;
#endif
static void *cJSON_malloc(size_t sz) {
return kmalloc(sz, GFP_KERNEL);
}
static void *cJSON_realloc(void *ptr, size_t sz)
{
return krealloc(ptr, sz, GFP_KERNEL);
}
static void cJSON_free(void *ptr)
{
kfree(ptr);
}
static char* cJSON_strdup(const char* str)
{
size_t len;
char* copy;
len = strlen(str) + 1;
if (!(copy = (char*)cJSON_malloc(len))) return 0;
memcpy(copy,str,len);
return copy;
}
#if 0
void cJSON_InitHooks(cJSON_Hooks* hooks)
{
if (!hooks) {
cJSON_malloc = malloc;
cJSON_realloc = realloc;
cJSON_free = free;
return;
}
cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc;
cJSON_realloc= (hooks->realloc_fn)?hooks->realloc_fn:realloc;
cJSON_free = (hooks->free_fn)?hooks->free_fn:free;
}
#endif
static cJSON *cJSON_New_Item(void)
{
cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));
if (node) memset(node,0,sizeof(cJSON));
return node;
}
void cJSON_Delete(cJSON *c)
{
cJSON *next;
while (c)
{
next=c->next;
if (c->child) cJSON_Delete(c->child);
if (c->valuestring) cJSON_free(c->valuestring);
if (c->string) cJSON_free(c->string);
cJSON_free(c);
c=next;
}
}
static const char *parse_number(cJSON *item,const char *num)
{
int n=0,sign=1;
if (*num=='-') sign=-1,num++;
if (*num=='0') num++;
if (*num>='1' && *num<='9') do n=(n*10)+(*num++ -'0'); while (*num>='0' && *num<='9');
item->valueint=(int)n;
item->type=cJSON_Number;
return num;
}
static char *print_number(cJSON *item)
{
char *str;
str=(char*)cJSON_malloc(21);
if (str)
sprintf(str,"%d",item->valueint);
return str;
}
static const char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
static const char *parse_string(cJSON *item,const char *str)
{
const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc;
if (*str!='\"') return 0; // not a string!
while (*ptr!='\"' && *ptr>31 && ++len) if (*ptr++ == '\\') ptr++; // Skip escaped quotes.
out=(char*)cJSON_malloc(len+1); // This is how long we need for the string, roughly.
if (!out) return 0;
ptr=str+1;ptr2=out;
while (*ptr!='\"' && *ptr>31)
{
if (*ptr!='\\') *ptr2++=*ptr++;
else
{
ptr++;
switch (*ptr)
{
case 'b': *ptr2++='\b'; break;
case 'f': *ptr2++='\f'; break;
case 'n': *ptr2++='\n'; break;
case 'r': *ptr2++='\r'; break;
case 't': *ptr2++='\t'; break;
case 'u': // transcode utf16 to utf8. DOES NOT SUPPORT SURROGATE PAIRS CORRECTLY.
sscanf(ptr+1,"%4x",&uc); // get the unicode char.
len=3;if (uc<0x80) len=1;else if (uc<0x800) len=2;ptr2+=len;
switch (len) {
case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
fallthrough;
case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
fallthrough;
case 1: *--ptr2 =(uc | firstByteMark[len]);
}
ptr2+=len;ptr+=4;
break;
default: *ptr2++=*ptr; break;
}
ptr++;
}
}
*ptr2=0;
if (*ptr=='\"') ptr++;
item->valuestring=out;
item->type=cJSON_String;
return ptr;
}
static char *print_string_ptr(const char *str)
{
const char *ptr;char *ptr2,*out;int len=0;
ptr=str;while (*ptr && ++len) {if (*ptr<32 || *ptr=='\"' || *ptr=='\\') len++;ptr++;}
out=(char*)cJSON_malloc(len+3);
ptr2=out;ptr=str;
*ptr2++='\"';
while (*ptr)
{
if (*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++;
else
{
*ptr2++='\\';
switch (*ptr++)
{
case '\\': *ptr2++='\\'; break;
case '\"': *ptr2++='\"'; break;
case '\b': *ptr2++='b'; break;
case '\f': *ptr2++='f'; break;
case '\n': *ptr2++='n'; break;
case '\r': *ptr2++='r'; break;
case '\t': *ptr2++='t'; break;
default: ptr2--; break; // eviscerate with prejudice.
}
}
}
*ptr2++='\"';*ptr2++=0;
return out;
}
static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);}
static const char *parse_value(cJSON *item,const char *value);
static char *print_value(cJSON *item,int depth);
static const char *parse_array(cJSON *item,const char *value);
static char *print_array(cJSON *item,int depth);
static const char *parse_object(cJSON *item,const char *value);
static char *print_object(cJSON *item,int depth);
static const char *skip(const char *in) {while (in && *in<=32) in++; return in;}
cJSON *cJSON_Parse(const char *value)
{
cJSON *c=cJSON_New_Item();
if (!c) return 0;
if (!parse_value(c,skip(value))) {cJSON_Delete(c);return 0;}
return c;
}
char *cJSON_Print(cJSON *item) {return print_value(item,0);}
static const char *parse_value(cJSON *item,const char *value)
{
if (!value) return 0; // Fail on null.
if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; }
if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; }
if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; }
if (*value=='\"') { return parse_string(item,value); }
if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); }
if (*value=='[') { return parse_array(item,value); }
if (*value=='{') { return parse_object(item,value); }
return 0; // failure.
}
static char *print_value(cJSON *item,int depth)
{
char *out=0;
switch (item->type)
{
case cJSON_NULL: out=cJSON_strdup("null"); break;
case cJSON_False: out=cJSON_strdup("false");break;
case cJSON_True: out=cJSON_strdup("true"); break;
case cJSON_Number: out=print_number(item);break;
case cJSON_String: out=print_string(item);break;
case cJSON_Array: out=print_array(item,depth);break;
case cJSON_Object: out=print_object(item,depth);break;
}
return out;
}
static const char *parse_array(cJSON *item,const char *value)
{
cJSON *child;
if (*value!='[') return 0; // not an array!
item->type=cJSON_Array;
value=skip(value+1);
if (*value==']') return value+1; // empty array.
item->child=child=cJSON_New_Item();
if (!item->child) return 0; // memory fail
value=skip(parse_value(child,skip(value))); // skip any spacing, get the value.
if (!value) return 0;
while (*value==',')
{
cJSON *new_item;
if (!(new_item=cJSON_New_Item())) return 0; // memory fail
child->next=new_item;new_item->prev=child;child=new_item;
value=skip(parse_value(child,skip(value+1)));
if (!value) return 0; // memory fail
}
if (*value==']') return value+1; // end of array
return 0; // malformed.
}
static char *print_array(cJSON *item,int depth)
{
char *out,*ptr,*ret;int len=5;
cJSON *child=item->child;
out=(char*)cJSON_malloc(len);*out='[';
ptr=out+1;*ptr=0;
while (child)
{
ret=print_value(child,depth+1);
if (!ret) {cJSON_free(out);return 0;} // Check for failure!
len+=strlen(ret)+3;
out=(char*)cJSON_realloc(out,len);
ptr=out+strlen(out);
ptr+=sprintf(ptr,ret);
if (child->next) {*ptr++=',';*ptr++=' ';*ptr=0;}
child=child->next;
cJSON_free(ret);
}
*ptr++=']';*ptr++=0;
return out;
}
static const char *parse_object(cJSON *item,const char *value)
{
cJSON *child;
if (*value!='{') return 0; // not an object!
item->type=cJSON_Object;
value=skip(value+1);
if (*value=='}') return value+1; // empty array.
item->child=child=cJSON_New_Item();
value=skip(parse_string(child,skip(value)));
if (!value) return 0;
child->string=child->valuestring;child->valuestring=0;
if (*value!=':') return 0; // fail!
value=skip(parse_value(child,skip(value+1))); // skip any spacing, get the value.
if (!value) return 0;
while (*value==',')
{
cJSON *new_item;
if (!(new_item=cJSON_New_Item())) return 0; // memory fail
child->next=new_item;new_item->prev=child;child=new_item;
value=skip(parse_string(child,skip(value+1)));
if (!value) return 0;
child->string=child->valuestring;child->valuestring=0;
if (*value!=':') return 0; // fail!
value=skip(parse_value(child,skip(value+1))); // skip any spacing, get the value.
if (!value) return 0;
}
if (*value=='}') return value+1; // end of array
return 0; // malformed.
}
static char *print_object(cJSON *item,int depth)
{
char *out,*ptr,*ret,*str;int len=7,i;
cJSON *child=item->child;
depth++;len+=depth;out=(char*)cJSON_malloc(len);*out='{';
ptr=out+1;*ptr++='\n';*ptr=0;
while (child)
{
str=print_string_ptr(child->string);
if (!str) {cJSON_free(out);return 0;}
ret=print_value(child,depth);
if (!ret) {cJSON_free(str);cJSON_free(out);return 0;} // Check for failure!
len+=strlen(ret)+strlen(str)+4+depth;
out=(char*)cJSON_realloc(out,len);
ptr=out+strlen(out);
for (i=0;i<depth;i++) *ptr++='\t';
ptr+=sprintf(ptr,str);
*ptr++=':';*ptr++='\t';
ptr+=sprintf(ptr,ret);
if (child->next) *ptr++=',';
*ptr++='\n';*ptr=0;
child=child->next;
cJSON_free(str);cJSON_free(ret);
}
for (i=0;i<depth-1;i++) *ptr++='\t';
*ptr++='}';*ptr++=0;
return out;
}
#define static_strlen(string_literal) (sizeof(string_literal) - sizeof(""))
static void skip_oneline_comment(char **input)
{
*input += static_strlen("//");
for (; (*input)[0] != '\0'; ++(*input))
{
if ((*input)[0] == '\n') {
*input += static_strlen("\n");
return;
}
}
}
static void skip_multiline_comment(char **input)
{
*input += static_strlen("/*");
for (; (*input)[0] != '\0'; ++(*input))
{
if (((*input)[0] == '*') && ((*input)[1] == '/'))
{
*input += static_strlen("*/");
return;
}
}
}
static void minify_string(char **input, char **output) {
(*output)[0] = (*input)[0];
*input += static_strlen("\"");
*output += static_strlen("\"");
for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) {
(*output)[0] = (*input)[0];
if ((*input)[0] == '\"') {
(*output)[0] = '\"';
*input += static_strlen("\"");
*output += static_strlen("\"");
return;
} else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) {
(*output)[1] = (*input)[1];
*input += static_strlen("\"");
*output += static_strlen("\"");
}
}
}
void cJSON_Minify(char *json)
{
char *into = json;
if (json == NULL)
{
return;
}
while (json[0] != '\0')
{
switch (json[0])
{
case ' ':
case '\t':
case '\r':
case '\n':
json++;
break;
case '/':
if (json[1] == '/')
{
skip_oneline_comment(&json);
}
else if (json[1] == '*')
{
skip_multiline_comment(&json);
} else {
json++;
}
break;
case '\"':
minify_string(&json, (char**)&into);
break;
default:
into[0] = json[0];
json++;
into++;
}
}
*into = '\0';
}
int cJSON_GetArraySize(cJSON *array) {cJSON *c=array->child;int i=0;while(c)i++,c=c->next;return i;}
cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item) item--,c=c->next; return c;}
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && strcasecmp(c->string,string)) c=c->next; return c;}
static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;}
void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}}
void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);}
cJSON *cJSON_CreateNull() {cJSON *item=cJSON_New_Item();item->type=cJSON_NULL;return item;}
cJSON *cJSON_CreateTrue() {cJSON *item=cJSON_New_Item();item->type=cJSON_True;return item;}
cJSON *cJSON_CreateFalse() {cJSON *item=cJSON_New_Item();item->type=cJSON_False;return item;}
cJSON *cJSON_CreateNumber(int num) {cJSON *item=cJSON_New_Item();item->type=cJSON_Number;item->valueint=(int)num;return item;}
cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();item->type=cJSON_String;item->valuestring=cJSON_strdup(string);return item;}
cJSON *cJSON_CreateArray() {cJSON *item=cJSON_New_Item();item->type=cJSON_Array;return item;}
cJSON *cJSON_CreateObject() {cJSON *item=cJSON_New_Item();item->type=cJSON_Object;return item;}
cJSON *cJSON_CreateIntArray(int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;i<count;i++){n=cJSON_CreateString(strings[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
+94
View File
@@ -0,0 +1,94 @@
/*
Copyright (c) 2009 Dave Gamble
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#include <linux/slab.h>
#define cJSON_False 0
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3
#define cJSON_String 4
#define cJSON_Array 5
#define cJSON_Object 6
typedef struct cJSON {
struct cJSON *next,*prev; // next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem
struct cJSON *child; // An array or object item will have a child pointer pointing to a chain of the items in the array/object.
int type; // The type of the item, as above.
char *valuestring; // The item's string, if type==cJSON_String
int valueint; // The item's number, if type==cJSON_Number
char *string; // The item's name string, if this item is the child of, or is in the list of subitems of an object.
} cJSON;
typedef struct cJSON_Hooks {
void *(*malloc_fn)(size_t sz);
void *(*realloc_fn)(void *ptr, size_t sz);
void (*free_fn)(void *ptr);
} cJSON_Hooks;
extern void cJSON_InitHooks(cJSON_Hooks* hooks);
extern cJSON *cJSON_Parse(const char *value);
extern char *cJSON_Print(cJSON *item);
extern void cJSON_Delete(cJSON *c);
extern int cJSON_GetArraySize(cJSON *array);
extern cJSON *cJSON_GetArrayItem(cJSON *array,int item);
extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
extern cJSON *cJSON_CreateNull(void);
extern cJSON *cJSON_CreateTrue(void);
extern cJSON *cJSON_CreateFalse(void);
extern cJSON *cJSON_CreateNumber(int num);
extern cJSON *cJSON_CreateString(const char *string);
extern cJSON *cJSON_CreateArray(void);
extern cJSON *cJSON_CreateObject(void);
extern void cJSON_Minify(char *json);
extern cJSON *cJSON_CreateIntArray(int *numbers,int count);
extern void cJSON_AddItemToArray(cJSON *array, cJSON *item);
extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item);
#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())
#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
#endif
+272
View File
@@ -0,0 +1,272 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/slab.h>
typedef enum{CHAR, DOT, BEGIN, END, STAR, PLUS, QUES, LIST, TYPENUM}TYPE;
typedef struct RE{
TYPE type;
int ch;
char *ccl;
int nccl;
struct RE *next;
}RE;
int match_longest = 0;
char *match_first = NULL;
static void * getmem(size_t size)
{
void *tmp;
if((tmp = kmalloc(size, GFP_ATOMIC))==NULL)
{
printk("malloc failed");
return NULL;
}
return tmp;
}
static size_t creat_list(char *str, int start, int end)
{
size_t cnt = end - start + 1;
for(; start <= end ;start++)
*str++ = start;
return (cnt > 0)?cnt:0;
}
static int in_list(char ch, RE *regexp)
{
char *str = regexp->ccl;
if(regexp->type != LIST)
return 0;
for(; *str && ch != *str; str++)
;
return (*str != '\0') ^ regexp->nccl;
}
static void regexp_free(RE *regexp)
{
RE *tmp;
for(; regexp; regexp = tmp)
{
tmp = regexp->next;
kfree(regexp);
}
}
static RE* compile(char *regexp)
{
RE head, *tail, *tmp;
char *pstr;
int err_flag = 0;
for(tail = &head; *regexp != '\0' && err_flag == 0; regexp++)
{
tmp = getmem(sizeof(RE));
switch(*regexp){
case '\\':
regexp++;
if(*regexp == 'd')
{
tmp->type = LIST;
tmp->nccl = 0;
tmp->ccl = getmem(11);
creat_list(tmp->ccl, '0','9');
tmp->ccl[11] = '\0';
}else if(*regexp == 'D')
{
tmp->type = LIST;
tmp->nccl = 1;
tmp->ccl = getmem(11);
creat_list(tmp->ccl, '0','9');
tmp->ccl[11] = '\0';
}else
{
tmp->type = CHAR;
tmp->ch = *regexp;
}
break;
case '.':
tmp->type = DOT;
break;
case '^':
tmp->type = BEGIN;
tmp->ch = '^';
break;
case '$':
tmp->type = END;
tmp->ch = '$';
break;
case '*':
tmp->type = STAR;
break;
case '+':
tmp->type = PLUS;
break;
case '?':
tmp->type = QUES;
break;
case '[':
pstr = tmp->ccl = getmem(256);
tmp->nccl = 0;
if(*++regexp == '^')
{
tmp->nccl = 1;
regexp++;
}
while(*regexp != '\0' && *regexp != ']')
{
if(*regexp != '-')
{
*pstr++ = *regexp++;
continue;
}
if(pstr == tmp->ccl || *(regexp + 1) == ']')
{
err_flag = 1;
break;
}
pstr += creat_list(pstr, *(regexp - 1) + 1, *(regexp + 1));
regexp += 2;
}
*pstr = '\0';
if(*regexp == '\0')
err_flag = 1;
tmp->type = LIST;
break;
default:
tmp->type = CHAR;
tmp->ch = *regexp;
}
tail->next = tmp;
tail = tmp;
}
tail->next = NULL;
if(err_flag)
{
regexp_free(head.next);
return NULL;
}
return head.next;
}
#define MATCH_ONE(reg, text) \
(reg->type == DOT || in_list(*text, reg) || *text == reg->ch)
#define MATCH_ONE_P(reg, text) \
(in_list(*text++, reg) || *(text - 1) == reg->ch || reg->type == DOT)
static int matchhere(RE *regexp, char *text);
static int matchstar(RE *cur, RE *regexp, char *text)
{
do{
if(matchhere(regexp, text))
return 1;
}while(*text != '\0' && MATCH_ONE_P(cur, text));
return 0;
}
static int matchstar_l(RE *cur, RE *regexp, char *text)
{
char *t;
for(t = text; *t != '\0' && MATCH_ONE(cur, t); t++)
;
do{
if(matchhere(regexp, t))
return 1;
}while(t-- > text);
return 0;
}
static int matchplus(RE *cur, RE *regexp, char *text)
{
while(*text != '\0' && MATCH_ONE_P(cur, text))
{
if(matchhere(regexp, text))
return 1;
}
return 0;
}
static int matchplus_l(RE *cur, RE *regexp, char *text)
{
char *t;
for(t = text; *t != '\0' && MATCH_ONE(cur, t); t++)
;
for(; t > text; t--)
{
if(matchhere(regexp, t))
return 1;
}
return 0;
}
static int matchques(RE *cur, RE *regexp, char *text)
{
int cnt = 1;
char *t = text;
if(*t != '\0' && cnt-- && MATCH_ONE(cur, t))
t++;
do{
if(matchhere(regexp, t))
return 1;
}while(t-- > text);
return 0;
}
static int (*matchfun[TYPENUM][2])(RE *, RE *, char *) = {
[STAR] = { matchstar, matchstar_l },
[PLUS] = { matchplus, matchplus_l },
[QUES] = { matchques, matchques },
};
static int matchhere(RE *regexp, char *text)
{
if(regexp == NULL)
return 1;
if(regexp->type == END && regexp->next == NULL)
return *text == '\0';
if(regexp->next && matchfun[regexp->next->type][match_longest])
return matchfun[regexp->next->type][match_longest](regexp, regexp->next->next, text);
if(*text != '\0' && MATCH_ONE(regexp, text))
return matchhere(regexp->next, text + 1);
return 0;
}
/*
* return value:
* -1 error
* 0 not match
* 1 matched
*/
int regexp_match(char *reg, char *text)
{
int ret;
RE *regexp = compile(reg);
if(regexp == NULL)
return -1;
if(regexp->type == BEGIN)
{
ret = matchhere(regexp->next, text);
goto out;
}
do{
if ((ret = matchhere(regexp, text)))
{
goto out;
}
}while(*text++ != '\0');
out:
regexp_free(regexp);
return ret;
}