mirror of
https://github.com/Zxilly/UA2F.git
synced 2025-12-25 20:48:41 +00:00
parent
6be399f59e
commit
38d8ca89e2
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y cmake libnetfilter-queue-dev libmnl-dev libnetfilter-conntrack-dev
|
||||
sudo apt-fast install -y cmake libnetfilter-queue-dev libmnl-dev libnetfilter-conntrack-dev
|
||||
|
||||
- name: Build and install libubox
|
||||
working-directory: /tmp
|
||||
@ -50,6 +50,25 @@ jobs:
|
||||
cmake -S . -B build -DUA2F_BUILD_TESTS=ON
|
||||
cmake --build build --target ua2f_test
|
||||
./build/ua2f_test
|
||||
|
||||
test-standalone:
|
||||
name: Run unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-fast install -y cmake libnetfilter-queue-dev libmnl-dev libnetfilter-conntrack-dev
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
cmake -S . -B build -DUA2F_BUILD_TESTS=ON -DUA2F_USE_UCI=OFF
|
||||
cmake --build build --target ua2f_test
|
||||
./build/ua2f_test
|
||||
|
||||
|
||||
build:
|
||||
@ -57,6 +76,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
- test-standalone
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
@ -4,6 +4,7 @@ project(UA2F LANGUAGES C CXX)
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
|
||||
OPTION(UA2F_BUILD_TESTS "Build tests" OFF)
|
||||
OPTION(UA2F_ENABLE_UCI "Enable UCI support" ON)
|
||||
|
||||
find_package(Git)
|
||||
if(GIT_FOUND)
|
||||
@ -81,7 +82,14 @@ add_executable(ua2f
|
||||
src/config.c
|
||||
src/third/nfqueue-mnl.c)
|
||||
|
||||
target_link_libraries(ua2f mnl netfilter_queue pthread nfnetlink uci)
|
||||
target_link_libraries(ua2f mnl netfilter_queue pthread nfnetlink)
|
||||
|
||||
if (UA2F_ENABLE_UCI)
|
||||
add_compile_definitions(UA2F_USE_UCI=1)
|
||||
target_link_libraries(ua2f uci)
|
||||
else ()
|
||||
message(STATUS "UCI support is disabled.")
|
||||
endif ()
|
||||
|
||||
install(TARGETS ua2f RUNTIME DESTINATION bin)
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
#ifdef UA2F_USE_UCI
|
||||
#include <uci.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
@ -38,4 +39,5 @@ void load_config() {
|
||||
|
||||
cleanup:
|
||||
uci_free_context(ctx);
|
||||
}
|
||||
}
|
||||
#endif //UA2F_USE_UCI
|
||||
@ -1,3 +1,4 @@
|
||||
#ifdef UA2F_USE_UCI
|
||||
#ifndef UA2F_CONFIG_H
|
||||
#define UA2F_CONFIG_H
|
||||
|
||||
@ -13,3 +14,4 @@ void load_config();
|
||||
extern struct ua2f_config config;
|
||||
|
||||
#endif //UA2F_CONFIG_H
|
||||
#endif //UA2F_USE_UCI
|
||||
|
||||
115
src/handler.c
115
src/handler.c
@ -3,16 +3,21 @@
|
||||
#include "cache.h"
|
||||
#include "util.h"
|
||||
#include "statistics.h"
|
||||
#include "config.h"
|
||||
#include "custom.h"
|
||||
|
||||
#ifdef UA2F_USE_UCI
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#endif
|
||||
|
||||
#include <libnetfilter_queue/pktbuff.h>
|
||||
#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
|
||||
#include <libnetfilter_queue/libnetfilter_queue_ipv4.h>
|
||||
#include <libnetfilter_queue/libnetfilter_queue_ipv6.h>
|
||||
|
||||
#define MAX_USER_AGENT_LENGTH (0xffff + (MNL_SOCKET_BUFFER_SIZE / 2))
|
||||
static char* replacement_user_agent_string = NULL;
|
||||
static char *replacement_user_agent_string = NULL;
|
||||
|
||||
#define USER_AGENT_MATCH "\r\nUser-Agent:"
|
||||
#define USER_AGENT_MATCH_LENGTH 13
|
||||
@ -27,36 +32,42 @@ static char* replacement_user_agent_string = NULL;
|
||||
void init_handler() {
|
||||
replacement_user_agent_string = malloc(MAX_USER_AGENT_LENGTH);
|
||||
|
||||
bool ua_set = false;
|
||||
|
||||
#ifdef UA2F_USE_UCI
|
||||
if (config.use_custom_ua) {
|
||||
memset(replacement_user_agent_string, ' ', MAX_USER_AGENT_LENGTH);
|
||||
strncpy(replacement_user_agent_string, config.custom_ua, strlen(config.custom_ua));
|
||||
syslog(LOG_INFO, "Using config user agent string: %s", replacement_user_agent_string);
|
||||
ua_set = true;
|
||||
}
|
||||
else {
|
||||
#endif
|
||||
|
||||
#ifdef UA2F_CUSTOM_UA
|
||||
memset(replacement_user_agent_string, ' ', MAX_USER_AGENT_LENGTH);
|
||||
strncpy(replacement_user_agent_string, UA2F_CUSTOM_UA, strlen(UA2F_CUSTOM_UA));
|
||||
syslog(LOG_INFO, "Using embed user agent string: %s", replacement_user_agent_string);
|
||||
#else
|
||||
memset(replacement_user_agent_string, ' ', MAX_USER_AGENT_LENGTH);
|
||||
strncpy(replacement_user_agent_string, UA2F_CUSTOM_UA, strlen(UA2F_CUSTOM_UA));
|
||||
syslog(LOG_INFO, "Using embed user agent string: %s", replacement_user_agent_string);
|
||||
ua_set = true;
|
||||
#endif
|
||||
|
||||
if (!ua_set) {
|
||||
memset(replacement_user_agent_string, 'F', MAX_USER_AGENT_LENGTH);
|
||||
syslog(LOG_INFO, "Custom user agent string not set, using default F-string.");
|
||||
#endif
|
||||
}
|
||||
|
||||
syslog(LOG_INFO, "Handler initialized.");
|
||||
}
|
||||
|
||||
// should free the ret value
|
||||
static char* ip_to_str(const ip_address_t* ip, const uint16_t port, const int ip_version) {
|
||||
static char *ip_to_str(const ip_address_t *ip, const uint16_t port, const int ip_version) {
|
||||
ASSERT(ip_version == IPV4 || ip_version == IPV6);
|
||||
char* ip_buf = malloc(MAX_ADDR_PORT_LENGTH);
|
||||
char *ip_buf = malloc(MAX_ADDR_PORT_LENGTH);
|
||||
memset(ip_buf, 0, MAX_ADDR_PORT_LENGTH);
|
||||
const char* retval = NULL;
|
||||
const char *retval = NULL;
|
||||
|
||||
if (ip_version == IPV4) {
|
||||
retval = inet_ntop(AF_INET, &ip->in4, ip_buf, INET_ADDRSTRLEN);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
retval = inet_ntop(AF_INET6, &ip->in6, ip_buf, INET6_ADDRSTRLEN);
|
||||
}
|
||||
ASSERT(retval != NULL);
|
||||
@ -74,11 +85,11 @@ struct mark_op {
|
||||
};
|
||||
|
||||
static void send_verdict(
|
||||
const struct nf_queue* queue,
|
||||
const struct nf_packet* pkt,
|
||||
const struct mark_op mark,
|
||||
struct pkt_buff* mangled_pkt_buff) {
|
||||
struct nlmsghdr* nlh = nfqueue_put_header(pkt->queue_num, NFQNL_MSG_VERDICT);
|
||||
const struct nf_queue *queue,
|
||||
const struct nf_packet *pkt,
|
||||
const struct mark_op mark,
|
||||
struct pkt_buff *mangled_pkt_buff) {
|
||||
struct nlmsghdr *nlh = nfqueue_put_header(pkt->queue_num, NFQNL_MSG_VERDICT);
|
||||
if (nlh == NULL) {
|
||||
syslog(LOG_ERR, "failed to put nfqueue header");
|
||||
goto end;
|
||||
@ -86,7 +97,7 @@ static void send_verdict(
|
||||
nfq_nlmsg_verdict_put(nlh, pkt->packet_id, NF_ACCEPT);
|
||||
|
||||
if (mark.should_set) {
|
||||
struct nlattr* nest = mnl_attr_nest_start_check(nlh, SEND_BUF_LEN, NFQA_CT);
|
||||
struct nlattr *nest = mnl_attr_nest_start_check(nlh, SEND_BUF_LEN, NFQA_CT);
|
||||
if (nest == NULL) {
|
||||
syslog(LOG_ERR, "failed to put nfqueue attr");
|
||||
goto end;
|
||||
@ -107,7 +118,7 @@ static void send_verdict(
|
||||
syslog(LOG_ERR, "failed to send verdict: %s", strerror(errno));
|
||||
}
|
||||
|
||||
end:
|
||||
end:
|
||||
if (nlh != NULL) {
|
||||
free(nlh);
|
||||
}
|
||||
@ -116,67 +127,66 @@ end:
|
||||
static bool conntrack_info_available = true;
|
||||
static bool cache_initialized = false;
|
||||
|
||||
static void add_to_cache(const struct nf_packet* pkt) {
|
||||
char* ip_str = ip_to_str(&pkt->orig.dst, pkt->orig.dst_port, pkt->orig.ip_version);
|
||||
static void add_to_cache(const struct nf_packet *pkt) {
|
||||
char *ip_str = ip_to_str(&pkt->orig.dst, pkt->orig.dst_port, pkt->orig.ip_version);
|
||||
cache_add(ip_str);
|
||||
free(ip_str);
|
||||
}
|
||||
|
||||
static struct mark_op get_next_mark(const struct nf_packet* pkt, const bool has_ua) {
|
||||
static struct mark_op get_next_mark(const struct nf_packet *pkt, const bool has_ua) {
|
||||
if (!conntrack_info_available) {
|
||||
return (struct mark_op){false, 0};
|
||||
return (struct mark_op) {false, 0};
|
||||
}
|
||||
|
||||
// I didn't think this will happen, but just in case
|
||||
// firewall should already have a rule to return all marked with CONNMARK_NOT_HTTP packets
|
||||
if (pkt->conn_mark == CONNMARK_NOT_HTTP) {
|
||||
syslog(LOG_WARNING, "Packet has already been marked as not http. Maybe firewall rules are wrong?");
|
||||
return (struct mark_op){false, 0};
|
||||
return (struct mark_op) {false, 0};
|
||||
}
|
||||
|
||||
if (pkt->conn_mark == CONNMARK_HTTP) {
|
||||
return (struct mark_op){false, 0};
|
||||
return (struct mark_op) {false, 0};
|
||||
}
|
||||
|
||||
if (has_ua) {
|
||||
return (struct mark_op){true, CONNMARK_HTTP};
|
||||
return (struct mark_op) {true, CONNMARK_HTTP};
|
||||
}
|
||||
|
||||
if (!pkt->has_connmark || pkt->conn_mark == 0) {
|
||||
return (struct mark_op){true, CONNMARK_ESTIMATE_LOWER};
|
||||
return (struct mark_op) {true, CONNMARK_ESTIMATE_LOWER};
|
||||
}
|
||||
|
||||
if (pkt->conn_mark == CONNMARK_ESTIMATE_VERDICT) {
|
||||
add_to_cache(pkt);
|
||||
return (struct mark_op){true, CONNMARK_NOT_HTTP};
|
||||
return (struct mark_op) {true, CONNMARK_NOT_HTTP};
|
||||
}
|
||||
|
||||
if (pkt->conn_mark >= CONNMARK_ESTIMATE_LOWER && pkt->conn_mark <= CONNMARK_ESTIMATE_UPPER) {
|
||||
return (struct mark_op){true, pkt->conn_mark + 1};
|
||||
return (struct mark_op) {true, pkt->conn_mark + 1};
|
||||
}
|
||||
|
||||
syslog(LOG_WARNING, "Unexpected connmark value: %d, Maybe other program has changed connmark?", pkt->conn_mark);
|
||||
return (struct mark_op){true, pkt->conn_mark + 1};
|
||||
return (struct mark_op) {true, pkt->conn_mark + 1};
|
||||
}
|
||||
|
||||
bool should_ignore(const struct nf_packet* pkt) {
|
||||
bool should_ignore(const struct nf_packet *pkt) {
|
||||
bool retval = false;
|
||||
|
||||
char* ip_str = ip_to_str(&pkt->orig.dst, pkt->orig.dst_port, pkt->orig.ip_version);
|
||||
char *ip_str = ip_to_str(&pkt->orig.dst, pkt->orig.dst_port, pkt->orig.ip_version);
|
||||
retval = cache_contains(ip_str);
|
||||
free(ip_str);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
void handle_packet(const struct nf_queue *queue, const struct nf_packet *pkt) {
|
||||
if (conntrack_info_available) {
|
||||
if (!pkt->has_conntrack) {
|
||||
conntrack_info_available = false;
|
||||
syslog(LOG_WARNING, "Packet has no conntrack. Switching to no cache mode.");
|
||||
syslog(LOG_WARNING, "Note that this may lead to performance degradation. Especially on low-end routers.");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (!cache_initialized) {
|
||||
init_not_http_cache();
|
||||
cache_initialized = true;
|
||||
@ -184,9 +194,9 @@ void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
}
|
||||
}
|
||||
|
||||
struct pkt_buff* pkt_buff = NULL;
|
||||
struct pkt_buff *pkt_buff = NULL;
|
||||
if (conntrack_info_available && should_ignore(pkt)) {
|
||||
send_verdict(queue, pkt, (struct mark_op){true, CONNMARK_NOT_HTTP}, NULL);
|
||||
send_verdict(queue, pkt, (struct mark_op) {true, CONNMARK_NOT_HTTP}, NULL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
@ -198,21 +208,18 @@ void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
|
||||
if (conntrack_info_available) {
|
||||
type = pkt->orig.ip_version;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const __auto_type ip_hdr = nfq_ip_get_hdr(pkt_buff);
|
||||
if (ip_hdr == NULL) {
|
||||
type = IPV6;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
type = IPV4;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == IPV4) {
|
||||
count_ipv4_packet();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
count_ipv6_packet();
|
||||
}
|
||||
|
||||
@ -222,8 +229,7 @@ void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
syslog(LOG_ERR, "Failed to set ipv4 transport header");
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const __auto_type ip_hdr = nfq_ip6_get_hdr(pkt_buff);
|
||||
if (nfq_ip6_set_transport_header(pkt_buff, ip_hdr, IPPROTO_TCP) < 0) {
|
||||
syslog(LOG_ERR, "Failed to set ipv6 transport header");
|
||||
@ -234,7 +240,7 @@ void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
const __auto_type tcp_hdr = nfq_tcp_get_hdr(pkt_buff);
|
||||
if (tcp_hdr == NULL) {
|
||||
// This packet is not tcp, pass it
|
||||
send_verdict(queue, pkt, (struct mark_op){false, 0}, NULL);
|
||||
send_verdict(queue, pkt, (struct mark_op) {false, 0}, NULL);
|
||||
syslog(LOG_WARNING, "Received non-tcp packet. You may set wrong firewall rules.");
|
||||
goto end;
|
||||
}
|
||||
@ -264,7 +270,7 @@ void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
// }
|
||||
count_http_packet();
|
||||
|
||||
const void* search_start = tcp_payload;
|
||||
const void *search_start = tcp_payload;
|
||||
unsigned int search_length = tcp_payload_len;
|
||||
bool has_ua = false;
|
||||
|
||||
@ -274,21 +280,21 @@ void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
break;
|
||||
}
|
||||
|
||||
char* ua_pos = memncasemem(search_start, search_length, USER_AGENT_MATCH, USER_AGENT_MATCH_LENGTH);
|
||||
char *ua_pos = memncasemem(search_start, search_length, USER_AGENT_MATCH, USER_AGENT_MATCH_LENGTH);
|
||||
if (ua_pos == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
has_ua = true;
|
||||
|
||||
void* ua_start = ua_pos + USER_AGENT_MATCH_LENGTH;
|
||||
void *ua_start = ua_pos + USER_AGENT_MATCH_LENGTH;
|
||||
|
||||
// for non-standard user-agent like User-Agent:XXX with no space after colon
|
||||
if (*(char *)ua_start == ' ') {
|
||||
if (*(char *) ua_start == ' ') {
|
||||
ua_start++;
|
||||
}
|
||||
|
||||
const void* ua_end = memchr(ua_start, '\r', tcp_payload_len - (ua_start - tcp_payload));
|
||||
const void *ua_end = memchr(ua_start, '\r', tcp_payload_len - (ua_start - tcp_payload));
|
||||
if (ua_end == NULL) {
|
||||
syslog(LOG_INFO, "User-Agent header is not terminated with \\r, not mangled.");
|
||||
send_verdict(queue, pkt, get_next_mark(pkt, true), NULL);
|
||||
@ -300,8 +306,7 @@ void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
// Looks it's impossible to mangle pocket failed, so we just drop it
|
||||
if (type == IPV4) {
|
||||
nfq_tcp_mangle_ipv4(pkt_buff, ua_offset, ua_len, replacement_user_agent_string, ua_len);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
nfq_tcp_mangle_ipv6(pkt_buff, ua_offset, ua_len, replacement_user_agent_string, ua_len);
|
||||
}
|
||||
|
||||
@ -315,7 +320,7 @@ void handle_packet(const struct nf_queue* queue,const struct nf_packet* pkt) {
|
||||
|
||||
send_verdict(queue, pkt, get_next_mark(pkt, has_ua), pkt_buff);
|
||||
|
||||
end:
|
||||
end:
|
||||
free(pkt->payload);
|
||||
if (pkt_buff != NULL) {
|
||||
pktb_free(pkt_buff);
|
||||
|
||||
@ -2,9 +2,12 @@
|
||||
#include "handler.h"
|
||||
#include "util.h"
|
||||
#include "cli.h"
|
||||
#include "config.h"
|
||||
#include "third/nfqueue-mnl.h"
|
||||
|
||||
#ifdef UA2F_USE_UCI
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
@ -23,7 +26,11 @@ void signal_handler(const int signum) {
|
||||
int main(const int argc, char *argv[]) {
|
||||
openlog("UA2F", LOG_PID, LOG_SYSLOG);
|
||||
|
||||
#ifdef UA2F_USE_UCI
|
||||
load_config();
|
||||
#else
|
||||
syslog(LOG_INFO, "uci support is disabled");
|
||||
#endif //UA2F_USE_UCI
|
||||
|
||||
try_print_info(argc, argv);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user