refactor: rewrite to support ipv6

Closes: #44
This commit is contained in:
Zxilly 2023-04-21 21:26:27 +08:00
parent 84e1ac90f1
commit 6c760a0807
20 changed files with 2558 additions and 1335 deletions

View File

@ -1,51 +1,35 @@
name: C/C++ CI
name: Build OpenWRT Package
on:
push:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Get current time
uses: 1466587594/get-current-time@v2
id: current-time
with:
format: YYYYMMDDTHHmmss
utcOffset: "+08:00"
- name: Setup Environment
run: |
sudo apt-fast update
sudo apt-fast -y install build-essential asciidoc binutils bzip2 gawk gettext git libncurses5-dev libz-dev patch python3 python2.7 unzip zlib1g-dev lib32gcc1 libc6-dev-i386 subversion flex uglifyjs git-core gcc-multilib p7zip p7zip-full msmtp libssl-dev texinfo libglib2.0-dev xmlto qemu-utils upx libelf-dev autoconf automake libtool autopoint device-tree-compiler g++-multilib antlr3 gperf wget curl swig rsync
- uses: actions/cache@v2
id: cache
with:
path: openwrt-sdk-21.02.0-rc1-ramips-mt7620_gcc-8.4.0_musl.Linux-x86_64
key: ${{ runner.os }}-openwrt
restore-keys: |
${{ runner.os }}-openwrt
sudo apt-fast -y install build-essential binutils bzip2 \
diff find flex gawk gcc-6+ getopt grep install libc-dev \
libz-dev make4.1+ perl python3.6+ rsync subversion unzip which
- name: Setup OpenWRT SDK
if: steps.cache.outputs.cache-hit != 'true'
run: |
wget https://downloads.openwrt.org/releases/21.02.0-rc1/targets/ramips/mt7620/openwrt-sdk-21.02.0-rc1-ramips-mt7620_gcc-8.4.0_musl.Linux-x86_64.tar.xz
tar -xvJf openwrt-sdk-21.02.0-rc1-ramips-mt7620_gcc-8.4.0_musl.Linux-x86_64.tar.xz
cd openwrt-sdk-21.02.0-rc1-ramips-mt7620_gcc-8.4.0_musl.Linux-x86_64
wget https://downloads.openwrt.org/releases/22.03.4/targets/ramips/mt7620/openwrt-sdk-22.03.4-ramips-mt7620_gcc-11.2.0_musl.Linux-x86_64.tar.xz -O openwrt.tar.xz
tar -xvJf openwrt.tar.xz
cd openwrt
./scripts/feeds update -a
./scripts/feeds install -a
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
path: openwrt-sdk-21.02.0-rc1-ramips-mt7620_gcc-8.4.0_musl.Linux-x86_64/package/UA2F
path: openwrt/package/UA2F
- name: Compile Package
working-directory: ./openwrt-sdk-21.02.0-rc1-ramips-mt7620_gcc-8.4.0_musl.Linux-x86_64
working-directory: ./openwrt
run: |
make defconfig
make package/UA2F/compile -j2 V=s
@ -57,7 +41,7 @@ jobs:
automatic_release_tag: "${{ steps.current-time.outputs.formattedTime }}"
prerelease: true
files: |
openwrt-sdk-21.02.0-rc1-ramips-mt7620_gcc-8.4.0_musl.Linux-x86_64/bin/packages/mipsel_24kc/base/ua2f*
openwrt/bin/packages/mipsel_24kc/base/ua2f*

View File

@ -1,9 +1,7 @@
cmake_minimum_required(VERSION 3.16)
project(UA2F C)
set(CMAKE_C_STANDARD 11)
include_directories("/usr/local/include")
set(CMAKE_C_STANDARD 17)
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
@ -11,10 +9,11 @@ add_link_options(-fsanitize=address)
add_executable(ua2f
src/ua2f.c
src/statistics.c
src/child.c
src/util.c
src/cache.c)
src/cache.c
src/handler.c
src/third/nfqueue-mnl.c)
target_link_libraries(ua2f mnl netfilter_queue pthread)
target_link_libraries(ua2f mnl netfilter_queue pthread nfnetlink)
install(TARGETS ua2f RUNTIME DESTINATION bin)

View File

@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=UA2F
PKG_VERSION:=3.10
PKG_VERSION:=4.0.0
PKG_RELEASE:=1
PKG_LICENSE:=GPL-3.0-only
@ -16,22 +16,24 @@ define Package/ua2f
TITLE:=Change User-Agent to Fwords on the fly.
URL:=https://github.com/Zxilly/UA2F
DEPENDS:=+iptables-mod-conntrack-extra +iptables-mod-nfqueue \
+libnetfilter-conntrack +libnetfilter-queue +iptables-mod-filter
+libnetfilter-conntrack +libnetfilter-queue +iptables-mod-filter \
+libnfnetlink +libmnl +libpthread
endef
define Package/ua2f/description
Change User-agent to Fwords to prevent being checked by Dr.Com.
endef
EXTRA_LDFLAGS += -lmnl -lnetfilter_queue -lpthread
EXTRA_LDFLAGS += -lmnl -lnetfilter_queue -lpthread -lnfnetlink
define Build/Compile
$(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_LDFLAGS) $(EXTRA_LDFLAGS) \
$(PKG_BUILD_DIR)/ua2f.c \
$(PKG_BUILD_DIR)/statistics.c \
$(PKG_BUILD_DIR)/child.c \
$(PKG_BUILD_DIR)/util.c \
$(PKG_BUILD_DIR)/cache.c \
$(PKG_BUILD_DIR)/handler.c \
$(PKG_BUILD_DIR)/third/nfqueue-mnl.c \
-o $(PKG_BUILD_DIR)/ua2f
endef

105
README.md
View File

@ -1,17 +1,11 @@
![UA2F](https://socialify.git.ci/Zxilly/UA2F/image?description=1&descriptionEditable=Change%20User-agent%20to%20F-words%20on%20OpenWRT%20router.&font=Inter&language=1&pattern=Plus&stargazers=1&theme=Light)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FZxilly%2FUA2F.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FZxilly%2FUA2F?ref=badge_shield)
~~**当前 git HEAD 是一个高度实验性版本,请查找 commit 以获得可用版本**~~
**我相信当前版本已经足够可用,但是仍然有很多改进等待完成**
暂时来说,懒得写 README请先参照 [博客文章](https://learningman.top/archives/304) 完成操作
如果遇到了任何问题,欢迎提出 Issues但是更欢迎直接提交 Pull Request
> 由于新加入的 CONNMARK 影响,编译内核时需要添加 `NETFILTER_NETLINK_GLUE_CT` flag否则会出现 `mnl_cb_run:Not supported` 错误
> 由于新加入的 ipset 影响,需要确保你的内核支持 `hash:ip,port` 的 ipset 类型
> 由于新加入的 CONNMARK 影响,编译内核时需要添加 `NETFILTER_NETLINK_GLUE_CT` flag
# uci command
@ -32,108 +26,11 @@ service ua2f enable
service ua2f start
```
# Manual configure
## ipset command
请确保添加此语句至开机自启
```bash
ipset create nohttp hash:ip,port hashsize 16384 timeout 300
```
`UA2F` 运行时依赖名称为 `nohttp`,类型为 `hash:ip,port` 的 ipset
## iptables rules
```shell
iptables -t mangle -N ua2f
iptables -t mangle -A ua2f -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A ua2f -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A ua2f -d 192.168.0.0/16 -j RETURN # 不处理流向保留地址的包
iptables -t mangle -A ua2f -p tcp --dport 443 -j RETURN
iptables -t mangle -A ua2f -p tcp --dport 22 -j RETURN # 不处理 SSH 和 https
iptables -t mangle -A ua2f -p tcp --dport 80 -j CONNMARK --set-mark 44
iptables -t mangle -A ua2f -m connmark --mark 43 -j RETURN # 不处理标记为非 http 的流 (实验性)
iptables -t mangle -A ua2f -m set --set nohttp dst,dst -j RETURN
iptables -t mangle -A ua2f -p tcp --dport 80 -m string --string "/mmtls/" --algo bm -j RETURN # 不处理微信的 mmtls
iptables -t mangle -A ua2f -j NFQUEUE --queue-num 10010
iptables -t mangle -A FORWARD -p tcp -m conntrack --ctdir ORIGINAL -j ua2f
```
## TODO
- [x] 灾难恢复
- [ ] pthread 支持,由不同线程完成入队出队
- [x] 修复偶现的非法内存访问,定位错误是一个麻烦的问题 (疑似修复,继续观察)
- [x] 配合 CONNMARK 与 ipset不再修改已被判定为非 http 的 tcp 连接,期望减少 80% 以上的负载 (高度实验性实现)
- [ ] 清除 TCP Header 中的 timestamp有论文认为这可以被用来识别 NAT 后的多设备,劫持 NTP 服务器并不一定有效
## Helpful Log
http 头包占比观察
```log
Sat Dec 5 23:57:23 2020 user.notice : UA2F try to start daemon parent at [10331], parent process will suicide.
Sat Dec 5 23:57:23 2020 user.notice : UA2F parent daemon start at [10331].
Sat Dec 5 23:57:23 2020 user.notice : UA2F parent daemon set sid at [10331].
Sat Dec 5 23:57:23 2020 user.notice : UA2F true daemon will start at [10332], daemon parent suicide.
Sat Dec 5 23:57:23 2020 user.notice : UA2F true daemon start at [10332].
Sat Dec 5 23:57:23 2020 syslog.notice UA2F[10332]: UA2F has inited successful.
Sat Dec 5 23:57:47 2020 syslog.info UA2F[10332]: UA2F has handled 8 http packet and 243 tcp packet in 24s
Sat Dec 5 23:57:47 2020 syslog.info UA2F[10332]: UA2F has handled 16 http packet and 356 tcp packet in 24s
Sat Dec 5 23:57:47 2020 syslog.info UA2F[10332]: UA2F has handled 32 http packet and 440 tcp packet in 24s
Sat Dec 5 23:57:48 2020 syslog.info UA2F[10332]: UA2F has handled 64 http packet and 609 tcp packet in 25s
Sat Dec 5 23:57:49 2020 syslog.info UA2F[10332]: UA2F has handled 128 http packet and 1287 tcp packet in 26s
Sat Dec 5 23:58:58 2020 syslog.info UA2F[10332]: UA2F has handled 256 http packet and 6052 tcp packet in 95s
Sat Dec 5 23:59:01 2020 syslog.info UA2F[10332]: UA2F has handled 512 http packet and 9003 tcp packet in 98s
Sat Dec 5 23:59:39 2020 syslog.info UA2F[10332]: UA2F has handled 1024 http packet and 13764 tcp packet in 136s
Sun Dec 6 00:08:21 2020 syslog.info UA2F[10332]: UA2F has handled 2048 http packet and 48231 tcp packet in 658s
Sun Dec 6 00:31:57 2020 syslog.info UA2F[10332]: UA2F has handled 4096 http packet and 163337 tcp packet in 2074s
Sun Dec 6 11:31:39 2020 syslog.info UA2F[10332]: UA2F has handled 8192 http packet and 588216 tcp packet in 41656s
```
当前运行时间
```log
Fri Jan 1 15:10:09 2021 syslog.notice UA2F[5219]: UA2F has inited successful.
Fri Jan 1 15:11:18 2021 syslog.info UA2F[5219]: UA2F has handled 8 http packet, 0 http packet without ua and 107 tcp packet in 1 minutes and 9 seconds
Fri Jan 1 15:12:23 2021 syslog.info UA2F[5219]: UA2F has handled 16 http packet, 4 http packet without ua and 370 tcp packet in 2 minutes and 14 seconds
Fri Jan 1 15:13:52 2021 syslog.info UA2F[5219]: UA2F has handled 32 http packet, 4 http packet without ua and 722 tcp packet in 3 minutes and 43 seconds
Fri Jan 1 15:13:57 2021 syslog.info UA2F[5219]: UA2F has handled 64 http packet, 4 http packet without ua and 850 tcp packet in 3 minutes and 48 seconds
Fri Jan 1 15:14:17 2021 syslog.info UA2F[5219]: UA2F has handled 128 http packet, 4 http packet without ua and 1243 tcp packet in 4 minutes and 8 seconds
Fri Jan 1 15:22:35 2021 syslog.info UA2F[5219]: UA2F has handled 256 http packet, 12 http packet without ua and 2565 tcp packet in 12 minutes and 26 seconds
Fri Jan 1 15:42:24 2021 syslog.info UA2F[5219]: UA2F has handled 512 http packet, 30 http packet without ua and 6491 tcp packet in 32 minutes and 15 seconds
Fri Jan 1 16:29:59 2021 syslog.info UA2F[5219]: UA2F has handled 1024 http packet, 68 http packet without ua and 19188 tcp packet in 1 hours, 19 minutes and 50 seconds
Fri Jan 1 18:06:01 2021 syslog.info UA2F[5219]: UA2F has handled 2048 http packet, 173 http packet without ua and 36951 tcp packet in 2 hours, 55 minutes and 52 seconds
Fri Jan 1 21:09:36 2021 syslog.info UA2F[5219]: UA2F has handled 4096 http packet, 849 http packet without ua and 137599 tcp packet in 5 hours, 59 minutes and 27 seconds
Sat Jan 2 01:39:39 2021 syslog.info UA2F[5219]: UA2F has handled 8192 http packet, 1747 http packet without ua and 249561 tcp packet in 10 hours, 29 minutes and 30 seconds
Sat Jan 2 15:06:43 2021 syslog.info UA2F[5219]: UA2F has handled 16384 http packet, 2844 http packet without ua and 551953 tcp packet in 23 hours, 56 minutes and 34 seconds
Sun Jan 3 10:22:28 2021 syslog.info UA2F[5219]: UA2F has handled 32768 http packet, 5047 http packet without ua and 1919845 tcp packet in 1 days, 19 hours, 12 minutes and 19 seconds
Mon Jan 4 13:25:04 2021 syslog.info UA2F[5219]: UA2F has handled 65536 http packet, 8435 http packet without ua and 3973193 tcp packet in 2 days, 22 hours, 14 minutes and 55 seconds
```
```
Sat Mar 13 14:26:48 2021 user.notice : Try to start UA2F processor at [24049].
Sat Mar 13 14:26:48 2021 user.notice : UA2F processor start at [24049].
Sat Mar 13 14:26:48 2021 syslog.notice UA2F[24049]: ipset inited.
Sat Mar 13 14:26:48 2021 syslog.notice UA2F[24049]: UA2F has inited successful.
Sat Mar 13 14:26:51 2021 syslog.info UA2F[24049]: UA2F has handled 8 http, 0 http 1.0, 0 noua http, 58 tcp. Set 0 mark and 0 nohttp mark in 3 seconds
Sat Mar 13 14:26:57 2021 syslog.info UA2F[24049]: UA2F has handled 16 http, 0 http 1.0, 1 noua http, 140 tcp. Set 0 mark and 1 nohttp mark in 9 seconds
Sat Mar 13 14:27:23 2021 syslog.info UA2F[24049]: UA2F has handled 32 http, 0 http 1.0, 3 noua http, 1286 tcp. Set 2 mark and 3 nohttp mark in 35 seconds
Sat Mar 13 14:27:35 2021 syslog.info UA2F[24049]: UA2F has handled 64 http, 0 http 1.0, 3 noua http, 2042 tcp. Set 4 mark and 3 nohttp mark in 47 seconds
Sat Mar 13 14:28:55 2021 syslog.info UA2F[24049]: UA2F has handled 128 http, 0 http 1.0, 5 noua http, 7052 tcp. Set 13 mark and 8 nohttp mark in 2 minutes and 7 seconds
Sat Mar 13 14:33:45 2021 syslog.info UA2F[24049]: UA2F has handled 256 http, 2 http 1.0, 39 noua http, 12965 tcp. Set 19 mark and 25 nohttp mark in 6 minutes and 57 seconds
Sat Mar 13 14:50:22 2021 syslog.info UA2F[24049]: UA2F has handled 512 http, 4 http 1.0, 82 noua http, 25230 tcp. Set 58 mark and 45 nohttp mark in 23 minutes and 34 seconds
Sat Mar 13 15:05:10 2021 syslog.info UA2F[24049]: UA2F has handled 1024 http, 9 http 1.0, 154 noua http, 76718 tcp. Set 72 mark and 69 nohttp mark in 38 minutes and 22 seconds
Sat Mar 13 15:40:06 2021 syslog.info UA2F[24049]: UA2F has handled 2048 http, 218 http 1.0, 630 noua http, 118648 tcp. Set 151 mark and 162 nohttp mark in 1 hours, 13 minutes and 18 seconds
Sat Mar 13 16:56:16 2021 syslog.info UA2F[24049]: UA2F has handled 4096 http, 481 http 1.0, 1012 noua http, 222476 tcp. Set 368 mark and 291 nohttp mark in 2 hours, 29 minutes and 28 seconds
Sat Mar 13 21:49:04 2021 syslog.info UA2F[24049]: UA2F has handled 8192 http, 610 http 1.0, 1659 noua http, 355347 tcp. Set 673 mark and 789 nohttp mark in 7 hours, 22 minutes and 16 seconds
Sun Mar 14 00:46:13 2021 syslog.info UA2F[24049]: UA2F has handled 16384 http, 2260 http 1.0, 3479 noua http, 888912 tcp. Set 863 mark and 1052 nohttp mark in 10 hours, 19 minutes and 25 seconds
Sun Mar 14 02:39:43 2021 syslog.info UA2F[24049]: UA2F has handled 24576 http, 4570 http 1.0, 5854 noua http, 1288440 tcp. Set 1121 mark and 1121 nohttp mark in 12 hours, 12 minutes and 55 seconds
Sun Mar 14 09:38:22 2021 syslog.info UA2F[24049]: UA2F has handled 32768 http, 5663 http 1.0, 7167 noua http, 1550242 tcp. Set 1231 mark and 1306 nohttp mark in 19 hours, 11 minutes and 34 seconds
Sun Mar 14 09:45:30 2021 syslog.info UA2F[24049]: UA2F has handled 40960 http, 5665 http 1.0, 7170 noua http, 1623063 tcp. Set 1236 mark and 1317 nohttp mark in 19 hours, 18 minutes and 42 seconds
Sun Mar 14 11:23:40 2021 syslog.info UA2F[24049]: UA2F has handled 49152 http, 8001 http 1.0, 10014 noua http, 2138665 tcp. Set 1424 mark and 1585 nohttp mark in 20 hours, 56 minutes and 52 seconds
Sun Mar 14 18:50:18 2021 syslog.info UA2F[24049]: UA2F has handled 57344 http, 8632 http 1.0, 11863 noua http, 2705798 tcp. Set 1668 mark and 2281 nohttp mark in 1 days, 4 hours, 23 minutes and 30 seconds
Mon Mar 15 00:41:03 2021 syslog.info UA2F[24049]: UA2F has handled 65536 http, 8686 http 1.0, 12738 noua http, 3194659 tcp. Set 1830 mark and 2941 nohttp mark in 1 days, 10 hours, 14 minutes and 15 seconds
Mon Mar 15 11:39:34 2021 syslog.info UA2F[24049]: UA2F has handled 73728 http, 8691 http 1.0, 13282 noua http, 3360247 tcp. Set 1867 mark and 4219 nohttp mark in 1 days, 21 hours, 12 minutes and 46 seconds
```
## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FZxilly%2FUA2F.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FZxilly%2FUA2F?ref=badge_large)

View File

@ -9,6 +9,7 @@ START=99
NAME="ua2f"
PROG="/usr/bin/$NAME"
IPT_M="iptables -t mangle"
IPT6_M="ip6tables -t mangle"
FW_DIR="/var/etc"
FW_CONF="$FW_DIR/ua2f.include"
@ -43,11 +44,23 @@ setup_firewall() {
$IPT_M -A ua2f -p tcp --dport 22 -j RETURN # 不处理 SSH
[ "$handle_tls" -eq "1" ] || $IPT_M -A ua2f -p tcp --dport 443 -j RETURN # 不处理 HTTPS
$IPT_M -A ua2f -p tcp --dport 80 -j CONNMARK --set-mark 44
$IPT_M -A ua2f -m connmark --mark 43 -j RETURN # 不处理标记为非 http 的流 (实验性)
$IPT_M -A ua2f -m connmark --mark 43 -j RETURN # 不处理标记为非 http 的流
[ "$handle_mmtls" -eq "1" ] || $IPT_M -A ua2f -p tcp --dport 80 -m string --string "/mmtls/" --algo bm -j RETURN # 不处理微信的mmtls
$IPT_M -A ua2f -j NFQUEUE --queue-num 10010
$IPT_M -A FORWARD -p tcp -m conntrack --ctdir ORIGINAL -j ua2f
# if ip6tables exists
if command -v ip6tables >"/dev/null" 2>&1; then
$IPT6_M -N ua2f
$IPT6_M -A ua2f -p tcp --dport 22 -j RETURN # 不处理 SSH
[ "$handle_tls" -eq "1" ] || $IPT6_M -A ua2f -p tcp --dport 443 -j RETURN # 不处理 HTTPS
$IPT6_M -A ua2f -p tcp --dport 80 -j CONNMARK --set-mark 44
$IPT6_M -A ua2f -m connmark --mark 43 -j RETURN # 不处理标记为非 http 的流
[ "$handle_mmtls" -eq "1" ] || $IPT6_M -A ua2f -p tcp --dport 80 -m string --string "/mmtls/" --algo bm -j RETURN # 不处理微信的mmtls
$IPT6_M -A ua2f -j NFQUEUE --queue-num 10010
$IPT6_M -A FORWARD -p tcp -m conntrack --ctdir ORIGINAL -j ua2f
fi
[ "$handle_intranet" -eq "1" ] && {
local wan="$(route -n | grep UG | awk '{print $2}')"

View File

@ -1,48 +1,42 @@
#include "cache.h"
#include "hashmap.h"
#include "rwmutex.h"
#include "third/uthash.h"
#include <pthread.h>
#include <stdatomic.h>
#include <sys/syslog.h>
#include <stdbool.h>
#include <unistd.h>
RWMutex lock;
pthread_rwlock_t cacheLock;
const double CACHE_TIMEOUT = 600;
struct hashmap_s no_http_dst_cache;
static int iterate_pairs(void *const context, struct hashmap_element_s *const e) {
__auto_type current_time = (time_t) context;
__auto_type store_time = (time_t) e->data;
if (difftime(current_time, store_time) > CACHE_TIMEOUT) {
return -1;
}
return 0;
}
struct cache *not_http_dst_cache = NULL;
_Noreturn static void check_cache() {
while (true) {
rw_mutex_write_lock(&lock);
pthread_rwlock_wrlock(&cacheLock);
__auto_type current_time = time(NULL);
time_t now = time(NULL);
struct cache *cur, *tmp;
hashmap_iterate_pairs(&no_http_dst_cache, iterate_pairs, (void *) current_time);
HASH_ITER(hh, not_http_dst_cache, cur, tmp) {
if (difftime(now, cur->last_time) > CACHE_TIMEOUT) {
HASH_DEL(not_http_dst_cache, cur);
free(cur);
}
}
rw_mutex_read_unlock(&lock);
pthread_rwlock_unlock(&cacheLock);
// wait for 1 minute
thrd_sleep(&(struct timespec) {60, 0}, NULL);
sleep(CACHE_CHECK_INTERVAL);
}
}
void init_cache() {
rw_mutex_init(&lock);
hashmap_create(1024, &no_http_dst_cache);
void init_not_http_cache() {
if (pthread_rwlock_init(&cacheLock, NULL) != 0) {
syslog(LOG_ERR, "Failed to init cache lock");
exit(EXIT_FAILURE);
}
syslog(LOG_INFO, "Cache lock initialized");
pthread_t cleanup_thread;
__auto_type ret = pthread_create(&cleanup_thread, NULL, (void *(*)(void *)) check_cache, NULL);
@ -50,18 +44,48 @@ void init_cache() {
syslog(LOG_ERR, "Failed to create cleanup thread: %d", ret);
exit(EXIT_FAILURE);
}
syslog(LOG_INFO, "Cleanup thread created");
}
bool check_addr_port(const char *addr_port, const int len) {
rw_mutex_read_lock(&lock);
__auto_type ret = hashmap_get(&no_http_dst_cache, addr_port, len) != NULL;
rw_mutex_read_unlock(&lock);
bool cache_contains(const char* addr_port) {
pthread_rwlock_rdlock(&cacheLock);
rw_mutex_write_lock(&lock);
if (ret) {
hashmap_put(&no_http_dst_cache, addr_port, len, (void *) time(NULL));
struct cache *s;
HASH_FIND_STR(not_http_dst_cache, addr_port, s);
pthread_rwlock_unlock(&cacheLock);
if (s != NULL) {
bool ret;
pthread_rwlock_wrlock(&cacheLock);
if (difftime(time(NULL), s->last_time) > CACHE_TIMEOUT) {
HASH_DEL(not_http_dst_cache, s);
free(s);
ret = false;
} else {
s->last_time = time(NULL);
ret = true;
}
pthread_rwlock_unlock(&cacheLock);
return ret;
}
rw_mutex_write_unlock(&lock);
return ret;
}
return false;
}
void cache_add(const char *addr_port) {
pthread_rwlock_wrlock(&cacheLock);
struct cache *s;
HASH_FIND_STR(not_http_dst_cache, addr_port, s);
if (s != NULL) {
s->last_time = time(NULL);
} else {
s = malloc(sizeof(struct cache));
strcpy(s->addr_port, addr_port);
s->last_time = time(NULL);
HASH_ADD_STR(not_http_dst_cache, addr_port, s);
}
pthread_rwlock_unlock(&cacheLock);
}

View File

@ -5,4 +5,28 @@
#ifndef UA2F_CACHE_H
#define UA2F_CACHE_H
#include <stdbool.h>
#include "third/nfqueue-mnl.h"
#include "third/uthash.h"
#define CACHE_TIMEOUT 127
#define CACHE_CHECK_INTERVAL 128
// 1111:1111:1111:1111:1111:1111:111.111.111.111:65535
// with null terminator
#define MAX_ADDR_PORT_LENGTH (INET6_ADDRSTRLEN + 7)
struct cache {
char addr_port[MAX_ADDR_PORT_LENGTH];
time_t last_time;
UT_hash_handle hh;
};
void init_not_http_cache();
// add addr_port to cache, assume it's not a http dst
void cache_add(const char* addr_port);
bool cache_contains(const char* addr_port);
#endif //UA2F_CACHE_H

View File

@ -1,61 +0,0 @@
#include "child.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/syslog.h>
#include <stdbool.h>
static __pid_t child_pid = 0;
static u_int8_t failure_count = 0;
const u_int8_t MAX_RETRY_COUNT = 8;
static volatile sig_atomic_t graceful_exit_requested = false;
void parent_sigterm_handler(int signum) {
graceful_exit_requested = true;
}
void child_sigterm_handler(int signum) {
syslog(LOG_NOTICE, "Received SIGTERM, gracefully exiting.");
exit(EXIT_SUCCESS);
}
void works_as_child() {
while (!graceful_exit_requested) {
if (failure_count++ > MAX_RETRY_COUNT) {
syslog(LOG_ERR, "UA2F processor failed to start after [%d] times.", MAX_RETRY_COUNT);
exit(EXIT_FAILURE);
}
child_pid = fork();
if (child_pid < 0) {
syslog(LOG_ERR, "Failed to fork child process");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
syslog(LOG_NOTICE, "UA2F processor start at [%d].", getpid());
signal(SIGTERM, child_sigterm_handler);
return;
}
signal(SIGTERM, parent_sigterm_handler);
syslog(LOG_NOTICE, "Try to start UA2F processor at [%d].", child_pid);
int exit_stat;
waitpid(child_pid, &exit_stat, 0);
if (WIFEXITED(exit_stat)) {
syslog(LOG_NOTICE, "UA2F processor at [%d] exit with code [%d].", child_pid, WEXITSTATUS(exit_stat));
if (WEXITSTATUS(exit_stat) == 0) {
exit(EXIT_SUCCESS);
}
}
}
syslog(LOG_NOTICE, "Received SIGTERM, gracefully exited.");
exit(EXIT_SUCCESS);
}

View File

@ -1,10 +0,0 @@
//
// Created by zxilly on 2023/4/13.
//
#ifndef UA2F_CHILD_H
#define UA2F_CHILD_H
void works_as_child();
#endif //UA2F_CHILD_H

234
src/handler.c Normal file
View File

@ -0,0 +1,234 @@
//
// Created by zxilly on 2023/4/20.
//
#include <arpa/inet.h>
#include "handler.h"
#include "cache.h"
#include "util.h"
#include "statistics.h"
#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;
#define USER_AGENT_MATCH "\r\nUser-Agent: "
#define USER_AGENT_MATCH_LENGTH 14
#define CONNMARK_ESTIMATE_START 16
#define CONNMARK_ESTIMATE_END 32
#define CONNMARK_ESTIMATE_VERDICT 33
#define CONNMARK_NOT_HTTP 43
#define CONNMARK_HTTP 44
void init_handler() {
replacement_user_agent_string = malloc(MAX_USER_AGENT_LENGTH);
memset(replacement_user_agent_string, 'F', MAX_USER_AGENT_LENGTH);
syslog(LOG_INFO, "Handler initialized.");
}
// should free the ret value
static char *ip_to_str(ip_address_t *ip, uint16_t port, int ip_version) {
ASSERT(ip_version == IPV4 || ip_version == IPV6);
char *ip_buf = malloc(MAX_ADDR_PORT_LENGTH);
memset(ip_buf, 0, MAX_ADDR_PORT_LENGTH);
const char *retval = NULL;
if (ip_version == IPV4) {
retval = inet_ntop(AF_INET, &ip->in4, ip_buf, INET_ADDRSTRLEN);
} else if (ip_version == IPV6) {
retval = inet_ntop(AF_INET6, &ip->in6, ip_buf, INET6_ADDRSTRLEN);
}
ASSERT(retval != NULL);
char port_buf[7];
sprintf(port_buf, ":%d", port);
strcat(ip_buf, port_buf);
return ip_buf;
}
struct mark_op {
bool should_set;
uint32_t mark;
};
static void send_verdict(
struct nf_queue *queue,
struct nf_packet *pkt,
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;
}
nfq_nlmsg_verdict_put(nlh, (int) pkt->packet_id, NF_ACCEPT);
if (mark.should_set) {
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;
}
if (!mnl_attr_put_u32_check(nlh, SEND_BUF_LEN, CTA_MARK, htonl(mark.mark))) {
syslog(LOG_ERR, "failed to put nfqueue attr");
goto end;
}
mnl_attr_nest_end(nlh, nest);
}
if (mangled_pkt_buff != NULL) {
nfq_nlmsg_verdict_put_pkt(nlh, pktb_data(mangled_pkt_buff), pktb_len(mangled_pkt_buff));
}
__auto_type ret = mnl_socket_sendto(queue->nl_socket, nlh, nlh->nlmsg_len);
if (ret == -1) {
syslog(LOG_ERR, "failed to send verdict: %s", strerror(errno));
}
end:
if (nlh != NULL) {
free(nlh);
}
}
static void add_to_no_http_cache(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(struct nf_packet *pkt, bool has_ua) {
// I didn't think this will happen, but just in case
// iptables should already have a rule to return all marked with CONNMARK_HTTP
if (pkt->conn_mark == CONNMARK_HTTP) {
return (struct mark_op) {false, 0};
}
if (has_ua) {
return (struct mark_op) {true, CONNMARK_HTTP};
}
if (!pkt->has_connmark) {
return (struct mark_op) {true, CONNMARK_ESTIMATE_START};
}
if (pkt->conn_mark == CONNMARK_ESTIMATE_VERDICT) {
add_to_no_http_cache(pkt);
return (struct mark_op) {true, CONNMARK_NOT_HTTP};
}
if (pkt->conn_mark >= CONNMARK_ESTIMATE_START && pkt->conn_mark <= CONNMARK_ESTIMATE_END) {
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};
}
bool should_ignore(struct nf_packet *pkt) {
bool retval = false;
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(struct nf_queue *queue, struct nf_packet *pkt) {
int type = pkt->orig.ip_version;
if (type == IPV4) {
count_ipv4_packet();
} else if (type == IPV6) {
count_ipv6_packet();
}
count_tcp_packet();
struct pkt_buff *pkt_buff = NULL;
if (!pkt->has_conntrack) {
syslog(LOG_ERR, "Packet has no conntrack.");
exit(EXIT_FAILURE);
}
if (should_ignore(pkt)) {
send_verdict(queue, pkt, (struct mark_op) {true, CONNMARK_NOT_HTTP}, NULL);
goto end;
}
if (type == IPV4) {
pkt_buff = pktb_alloc(AF_INET, pkt->payload, pkt->payload_len, 0);
} else if (type == IPV6) {
pkt_buff = pktb_alloc(AF_INET6, pkt->payload, pkt->payload_len, 0);
} else {
syslog(LOG_ERR, "Unknown ip version: %d", pkt->orig.ip_version);
exit(EXIT_FAILURE);
}
ASSERT(pkt_buff != NULL);
if (type == IPV4) {
__auto_type ip_hdr = nfq_ip_get_hdr(pkt_buff);
if (nfq_ip_set_transport_header(pkt_buff, ip_hdr) < 0) {
syslog(LOG_ERR, "Failed to set ipv4 transport header");
goto end;
}
} else {
__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");
goto end;
}
}
__auto_type tcp_hdr = nfq_tcp_get_hdr(pkt_buff);
__auto_type tcp_payload = nfq_tcp_get_payload(tcp_hdr, pkt_buff);
__auto_type tcp_payload_len = nfq_tcp_get_payload_len(tcp_hdr, pkt_buff);
if (tcp_payload == NULL || tcp_payload_len < USER_AGENT_MATCH_LENGTH) {
send_verdict(queue, pkt, get_next_mark(pkt, false), NULL);
goto end;
}
char *ua_pos = memncasemem(tcp_payload, tcp_payload_len, USER_AGENT_MATCH, USER_AGENT_MATCH_LENGTH);
if (ua_pos == NULL) {
send_verdict(queue, pkt, get_next_mark(pkt, false), NULL);
goto end;
}
count_user_agent_packet();
void *ua_start = ua_pos + USER_AGENT_MATCH_LENGTH;
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);
goto end;
}
unsigned int ua_len = ua_end - ua_start;
__auto_type ua_offset = ua_start - tcp_payload;
// Looks it's impossible to mangle pocked 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 {
nfq_tcp_mangle_ipv6(pkt_buff, ua_offset, ua_len, replacement_user_agent_string, ua_len);
}
count_user_agent_packet();
send_verdict(queue, pkt, get_next_mark(pkt, true), pkt_buff);
end:
free(pkt->payload);
if (pkt_buff != NULL) {
pktb_free(pkt_buff);
}
}

10
src/handler.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef UA2F_HANDLER_H
#define UA2F_HANDLER_H
#include "third/nfqueue-mnl.h"
void init_handler();
void handle_packet(struct nf_queue *queue, struct nf_packet *pkt);
#endif //UA2F_HANDLER_H

View File

@ -1,718 +0,0 @@
/*
The latest version of this library is available on GitHub;
https://github.com/sheredom/hashmap.h
*/
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
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 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.
For more information, please refer to <http://unlicense.org/>
*/
#ifndef SHEREDOM_HASHMAP_H_INCLUDED
#define SHEREDOM_HASHMAP_H_INCLUDED
#if defined(_MSC_VER)
// Workaround a bug in the MSVC runtime where it uses __cplusplus when not
// defined.
#pragma warning(push, 0)
#pragma warning(disable : 4668)
#endif
#include <stdlib.h>
#include <string.h>
#if (defined(_MSC_VER) && defined(__AVX__)) || \
(!defined(_MSC_VER) && defined(__SSE4_2__))
#define HASHMAP_X86_SSE42
#endif
#if defined(HASHMAP_X86_SSE42)
#include <nmmintrin.h>
#endif
#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32)
#define HASHMAP_ARM_CRC32
#endif
#if defined(HASHMAP_ARM_CRC32)
#include <arm_acle.h>
#endif
#if defined(_MSC_VER)
#include <intrin.h>
#endif
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#if defined(_MSC_VER)
#pragma warning(push)
/* Stop MSVC complaining about unreferenced functions */
#pragma warning(disable : 4505)
/* Stop MSVC complaining about not inlining functions */
#pragma warning(disable : 4710)
/* Stop MSVC complaining about inlining functions! */
#pragma warning(disable : 4711)
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wstatic-in-inline"
#endif
#if defined(_MSC_VER)
#define HASHMAP_WEAK __inline
#elif defined(__clang__) || defined(__GNUC__)
#define HASHMAP_WEAK __attribute__((weak))
#else
#error Non clang, non gcc, non MSVC compiler found!
#endif
#if defined(_MSC_VER)
#define HASHMAP_ALWAYS_INLINE __forceinline
#elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \
defined(__cplusplus)
#define HASHMAP_ALWAYS_INLINE __attribute__((always_inline)) inline
#else
/* If we cannot use inline, its not safe to use always_inline, so we mark the
* function weak. */
#define HASHMAP_ALWAYS_INLINE HASHMAP_WEAK
#endif
#if defined(_MSC_VER) && (_MSC_VER < 1920)
typedef unsigned __int8 hashmap_uint8_t;
typedef unsigned __int32 hashmap_uint32_t;
typedef unsigned __int64 hashmap_uint64_t;
#else
#include <stdint.h>
typedef uint8_t hashmap_uint8_t;
typedef uint32_t hashmap_uint32_t;
typedef uint64_t hashmap_uint64_t;
#endif
typedef struct hashmap_element_s {
const void *key;
hashmap_uint32_t key_len;
int in_use;
void *data;
} hashmap_element_t;
typedef hashmap_uint32_t (*hashmap_hasher_t)(hashmap_uint32_t seed,
const void *key,
hashmap_uint32_t key_len);
typedef int (*hashmap_comparer_t)(const void *a, hashmap_uint32_t a_len,
const void *b, hashmap_uint32_t b_len);
typedef struct hashmap_s {
hashmap_uint32_t log2_capacity;
hashmap_uint32_t size;
hashmap_hasher_t hasher;
hashmap_comparer_t comparer;
struct hashmap_element_s *data;
} hashmap_t;
#define HASHMAP_LINEAR_PROBE_LENGTH (8)
typedef struct hashmap_create_options_s {
hashmap_hasher_t hasher;
hashmap_comparer_t comparer;
hashmap_uint32_t initial_capacity;
hashmap_uint32_t _;
} hashmap_create_options_t;
#if defined(__cplusplus)
extern "C" {
#endif
/// @brief Create a hashmap.
/// @param initial_capacity The initial capacity of the hashmap.
/// @param out_hashmap The storage for the created hashmap.
/// @return On success 0 is returned.
HASHMAP_WEAK int hashmap_create(const hashmap_uint32_t initial_capacity,
struct hashmap_s *const out_hashmap);
/// @brief Create a hashmap.
/// @param options The options to create the hashmap with.
/// @param out_hashmap The storage for the created hashmap.
/// @return On success 0 is returned.
///
/// The options members work as follows:
/// - initial_capacity The initial capacity of the hashmap.
/// - hasher Which hashing function to use with the hashmap (by default the
// crc32 with Robert Jenkins' mix is used).
HASHMAP_WEAK int hashmap_create_ex(struct hashmap_create_options_s options,
struct hashmap_s *const out_hashmap);
/// @brief Put an element into the hashmap.
/// @param hashmap The hashmap to insert into.
/// @param key The string key to use.
/// @param len The length of the string key.
/// @param value The value to insert.
/// @return On success 0 is returned.
///
/// The key string slice is not copied when creating the hashmap entry, and thus
/// must remain a valid pointer until the hashmap entry is removed or the
/// hashmap is destroyed.
HASHMAP_WEAK int hashmap_put(struct hashmap_s *const hashmap,
const void *const key, const hashmap_uint32_t len,
void *const value);
/// @brief Get an element from the hashmap.
/// @param hashmap The hashmap to get from.
/// @param key The string key to use.
/// @param len The length of the string key.
/// @return The previously set element, or NULL if none exists.
HASHMAP_WEAK void *hashmap_get(const struct hashmap_s *const hashmap,
const void *const key,
const hashmap_uint32_t len);
/// @brief Remove an element from the hashmap.
/// @param hashmap The hashmap to remove from.
/// @param key The string key to use.
/// @param len The length of the string key.
/// @return On success 0 is returned.
HASHMAP_WEAK int hashmap_remove(struct hashmap_s *const hashmap,
const void *const key,
const hashmap_uint32_t len);
/// @brief Remove an element from the hashmap.
/// @param hashmap The hashmap to remove from.
/// @param key The string key to use.
/// @param len The length of the string key.
/// @return On success the original stored key pointer is returned, on failure
/// NULL is returned.
HASHMAP_WEAK const void *
hashmap_remove_and_return_key(struct hashmap_s *const hashmap,
const void *const key,
const hashmap_uint32_t len);
/// @brief Iterate over all the elements in a hashmap.
/// @param hashmap The hashmap to iterate over.
/// @param iterator The function pointer to call on each element.
/// @param context The context to pass as the first argument to f.
/// @return If the entire hashmap was iterated then 0 is returned. Otherwise if
/// the callback function f returned non-zero then non-zero is returned.
HASHMAP_WEAK int hashmap_iterate(const struct hashmap_s *const hashmap,
int (*iterator)(void *const context,
void *const value),
void *const context);
/// @brief Iterate over all the elements in a hashmap.
/// @param hashmap The hashmap to iterate over.
/// @param iterator The function pointer to call on each element.
/// @param context The context to pass as the first argument to f.
/// @return If the entire hashmap was iterated then 0 is returned.
/// Otherwise if the callback function f returned positive then the positive
/// value is returned. If the callback function returns -1, the current item
/// is removed and iteration continues.
HASHMAP_WEAK int hashmap_iterate_pairs(
struct hashmap_s *const hashmap,
int (*iterator)(void *const, struct hashmap_element_s *const),
void *const context);
/// @brief Get the size of the hashmap.
/// @param hashmap The hashmap to get the size of.
/// @return The size of the hashmap.
HASHMAP_ALWAYS_INLINE hashmap_uint32_t
hashmap_num_entries(const struct hashmap_s *const hashmap);
/// @brief Get the capacity of the hashmap.
/// @param hashmap The hashmap to get the size of.
/// @return The capacity of the hashmap.
HASHMAP_ALWAYS_INLINE hashmap_uint32_t
hashmap_capacity(const struct hashmap_s *const hashmap);
/// @brief Destroy the hashmap.
/// @param hashmap The hashmap to destroy.
HASHMAP_WEAK void hashmap_destroy(struct hashmap_s *const hashmap);
static hashmap_uint32_t hashmap_crc32_hasher(const hashmap_uint32_t seed,
const void *const s,
const hashmap_uint32_t len);
static int hashmap_memcmp_comparer(const void *const a,
const hashmap_uint32_t a_len,
const void *const b,
const hashmap_uint32_t b_len);
HASHMAP_ALWAYS_INLINE hashmap_uint32_t hashmap_hash_helper_int_helper(
const struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len);
HASHMAP_ALWAYS_INLINE int
hashmap_hash_helper(const struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len,
hashmap_uint32_t *const out_index);
HASHMAP_WEAK int hashmap_rehash_iterator(void *const new_hash,
struct hashmap_element_s *const e);
HASHMAP_ALWAYS_INLINE int hashmap_rehash_helper(struct hashmap_s *const m);
HASHMAP_ALWAYS_INLINE hashmap_uint32_t hashmap_clz(const hashmap_uint32_t x);
#if defined(__cplusplus)
}
#endif
#if defined(__cplusplus)
#define HASHMAP_CAST(type, x) static_cast<type>(x)
#define HASHMAP_PTR_CAST(type, x) reinterpret_cast<type>(x)
#define HASHMAP_NULL NULL
#else
#define HASHMAP_CAST(type, x) ((type)(x))
#define HASHMAP_PTR_CAST(type, x) ((type)(x))
#define HASHMAP_NULL 0
#endif
int hashmap_create(const hashmap_uint32_t initial_capacity,
struct hashmap_s *const out_hashmap) {
struct hashmap_create_options_s options;
memset(&options, 0, sizeof(options));
options.initial_capacity = initial_capacity;
return hashmap_create_ex(options, out_hashmap);
}
int hashmap_create_ex(struct hashmap_create_options_s options,
struct hashmap_s *const out_hashmap) {
if (2 > options.initial_capacity) {
options.initial_capacity = 2;
} else if (0 != (options.initial_capacity & (options.initial_capacity - 1))) {
options.initial_capacity = 1u
<< (32 - hashmap_clz(options.initial_capacity));
}
if (HASHMAP_NULL == options.hasher) {
options.hasher = &hashmap_crc32_hasher;
}
if (HASHMAP_NULL == options.comparer) {
options.comparer = &hashmap_memcmp_comparer;
}
out_hashmap->data = HASHMAP_CAST(
struct hashmap_element_s *,
calloc(options.initial_capacity + HASHMAP_LINEAR_PROBE_LENGTH,
sizeof(struct hashmap_element_s)));
out_hashmap->log2_capacity = 31 - hashmap_clz(options.initial_capacity);
out_hashmap->size = 0;
out_hashmap->hasher = options.hasher;
out_hashmap->comparer = options.comparer;
return 0;
}
int hashmap_put(struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len, void *const value) {
hashmap_uint32_t index;
if ((HASHMAP_NULL == key) || (0 == len)) {
return 1;
}
/* Find a place to put our value. */
while (!hashmap_hash_helper(m, key, len, &index)) {
if (hashmap_rehash_helper(m)) {
return 1;
}
}
/* Set the data. */
m->data[index].data = value;
m->data[index].key = key;
m->data[index].key_len = len;
/* If the hashmap element was not already in use, set that it is being used
* and bump our size. */
if (0 == m->data[index].in_use) {
m->data[index].in_use = 1;
m->size++;
}
return 0;
}
void *hashmap_get(const struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len) {
hashmap_uint32_t i, curr;
if ((HASHMAP_NULL == key) || (0 == len)) {
return HASHMAP_NULL;
}
curr = hashmap_hash_helper_int_helper(m, key, len);
/* Linear probing, if necessary */
for (i = 0; i < HASHMAP_LINEAR_PROBE_LENGTH; i++) {
const hashmap_uint32_t index = curr + i;
if (m->data[index].in_use) {
if (m->comparer(m->data[index].key, m->data[index].key_len, key, len)) {
return m->data[index].data;
}
}
}
/* Not found */
return HASHMAP_NULL;
}
int hashmap_remove(struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len) {
hashmap_uint32_t i, curr;
if ((HASHMAP_NULL == key) || (0 == len)) {
return 1;
}
curr = hashmap_hash_helper_int_helper(m, key, len);
/* Linear probing, if necessary */
for (i = 0; i < HASHMAP_LINEAR_PROBE_LENGTH; i++) {
const hashmap_uint32_t index = curr + i;
if (m->data[index].in_use) {
if (m->comparer(m->data[index].key, m->data[index].key_len, key, len)) {
/* Blank out the fields including in_use */
memset(&m->data[index], 0, sizeof(struct hashmap_element_s));
/* Reduce the size */
m->size--;
return 0;
}
}
}
return 1;
}
const void *hashmap_remove_and_return_key(struct hashmap_s *const m,
const void *const key,
const hashmap_uint32_t len) {
hashmap_uint32_t i, curr;
if ((HASHMAP_NULL == key) || (0 == len)) {
return HASHMAP_NULL;
}
curr = hashmap_hash_helper_int_helper(m, key, len);
/* Linear probing, if necessary */
for (i = 0; i < HASHMAP_LINEAR_PROBE_LENGTH; i++) {
const hashmap_uint32_t index = curr + i;
if (m->data[index].in_use) {
if (m->comparer(m->data[index].key, m->data[index].key_len, key, len)) {
const void *const stored_key = m->data[index].key;
/* Blank out the fields */
memset(&m->data[index], 0, sizeof(struct hashmap_element_s));
/* Reduce the size */
m->size--;
return stored_key;
}
}
}
return HASHMAP_NULL;
}
int hashmap_iterate(const struct hashmap_s *const m,
int (*f)(void *const, void *const), void *const context) {
hashmap_uint32_t i;
for (i = 0; i < (hashmap_capacity(m) + HASHMAP_LINEAR_PROBE_LENGTH); i++) {
if (m->data[i].in_use) {
if (!f(context, m->data[i].data)) {
return 1;
}
}
}
return 0;
}
int hashmap_iterate_pairs(struct hashmap_s *const m,
int (*f)(void *const,
struct hashmap_element_s *const),
void *const context) {
hashmap_uint32_t i;
struct hashmap_element_s *p;
int r;
for (i = 0; i < (hashmap_capacity(m) + HASHMAP_LINEAR_PROBE_LENGTH); i++) {
p = &m->data[i];
if (p->in_use) {
r = f(context, p);
switch (r) {
case -1: /* remove item */
memset(p, 0, sizeof(struct hashmap_element_s));
m->size--;
break;
case 0: /* continue iterating */
break;
default: /* early exit */
return 1;
}
}
}
return 0;
}
void hashmap_destroy(struct hashmap_s *const m) {
free(m->data);
memset(m, 0, sizeof(struct hashmap_s));
}
HASHMAP_ALWAYS_INLINE hashmap_uint32_t
hashmap_num_entries(const struct hashmap_s *const m) {
return m->size;
}
HASHMAP_ALWAYS_INLINE hashmap_uint32_t
hashmap_capacity(const struct hashmap_s *const m) {
return 1u << m->log2_capacity;
}
hashmap_uint32_t hashmap_crc32_hasher(const hashmap_uint32_t seed,
const void *const k,
const hashmap_uint32_t len) {
hashmap_uint32_t i = 0;
hashmap_uint32_t crc32val = seed;
const hashmap_uint8_t *const s = HASHMAP_PTR_CAST(const hashmap_uint8_t *, k);
#if defined(HASHMAP_X86_SSE42)
for (; (i + sizeof(hashmap_uint32_t)) < len; i += sizeof(hashmap_uint32_t)) {
hashmap_uint32_t next;
memcpy(&next, &s[i], sizeof(next));
crc32val = _mm_crc32_u32(crc32val, next);
}
for (; i < len; i++) {
crc32val = _mm_crc32_u8(crc32val, s[i]);
}
#elif defined(HASHMAP_ARM_CRC32)
for (; (i + sizeof(hashmap_uint64_t)) < len; i += sizeof(hashmap_uint64_t)) {
hashmap_uint64_t next;
memcpy(&next, &s[i], sizeof(next));
crc32val = __crc32d(crc32val, next);
}
for (; i < len; i++) {
crc32val = __crc32b(crc32val, s[i]);
}
#else
// Using polynomial 0x11EDC6F41 to match SSE 4.2's crc function.
static const hashmap_uint32_t crc32_tab[] = {
0x00000000U, 0xF26B8303U, 0xE13B70F7U, 0x1350F3F4U, 0xC79A971FU,
0x35F1141CU, 0x26A1E7E8U, 0xD4CA64EBU, 0x8AD958CFU, 0x78B2DBCCU,
0x6BE22838U, 0x9989AB3BU, 0x4D43CFD0U, 0xBF284CD3U, 0xAC78BF27U,
0x5E133C24U, 0x105EC76FU, 0xE235446CU, 0xF165B798U, 0x030E349BU,
0xD7C45070U, 0x25AFD373U, 0x36FF2087U, 0xC494A384U, 0x9A879FA0U,
0x68EC1CA3U, 0x7BBCEF57U, 0x89D76C54U, 0x5D1D08BFU, 0xAF768BBCU,
0xBC267848U, 0x4E4DFB4BU, 0x20BD8EDEU, 0xD2D60DDDU, 0xC186FE29U,
0x33ED7D2AU, 0xE72719C1U, 0x154C9AC2U, 0x061C6936U, 0xF477EA35U,
0xAA64D611U, 0x580F5512U, 0x4B5FA6E6U, 0xB93425E5U, 0x6DFE410EU,
0x9F95C20DU, 0x8CC531F9U, 0x7EAEB2FAU, 0x30E349B1U, 0xC288CAB2U,
0xD1D83946U, 0x23B3BA45U, 0xF779DEAEU, 0x05125DADU, 0x1642AE59U,
0xE4292D5AU, 0xBA3A117EU, 0x4851927DU, 0x5B016189U, 0xA96AE28AU,
0x7DA08661U, 0x8FCB0562U, 0x9C9BF696U, 0x6EF07595U, 0x417B1DBCU,
0xB3109EBFU, 0xA0406D4BU, 0x522BEE48U, 0x86E18AA3U, 0x748A09A0U,
0x67DAFA54U, 0x95B17957U, 0xCBA24573U, 0x39C9C670U, 0x2A993584U,
0xD8F2B687U, 0x0C38D26CU, 0xFE53516FU, 0xED03A29BU, 0x1F682198U,
0x5125DAD3U, 0xA34E59D0U, 0xB01EAA24U, 0x42752927U, 0x96BF4DCCU,
0x64D4CECFU, 0x77843D3BU, 0x85EFBE38U, 0xDBFC821CU, 0x2997011FU,
0x3AC7F2EBU, 0xC8AC71E8U, 0x1C661503U, 0xEE0D9600U, 0xFD5D65F4U,
0x0F36E6F7U, 0x61C69362U, 0x93AD1061U, 0x80FDE395U, 0x72966096U,
0xA65C047DU, 0x5437877EU, 0x4767748AU, 0xB50CF789U, 0xEB1FCBADU,
0x197448AEU, 0x0A24BB5AU, 0xF84F3859U, 0x2C855CB2U, 0xDEEEDFB1U,
0xCDBE2C45U, 0x3FD5AF46U, 0x7198540DU, 0x83F3D70EU, 0x90A324FAU,
0x62C8A7F9U, 0xB602C312U, 0x44694011U, 0x5739B3E5U, 0xA55230E6U,
0xFB410CC2U, 0x092A8FC1U, 0x1A7A7C35U, 0xE811FF36U, 0x3CDB9BDDU,
0xCEB018DEU, 0xDDE0EB2AU, 0x2F8B6829U, 0x82F63B78U, 0x709DB87BU,
0x63CD4B8FU, 0x91A6C88CU, 0x456CAC67U, 0xB7072F64U, 0xA457DC90U,
0x563C5F93U, 0x082F63B7U, 0xFA44E0B4U, 0xE9141340U, 0x1B7F9043U,
0xCFB5F4A8U, 0x3DDE77ABU, 0x2E8E845FU, 0xDCE5075CU, 0x92A8FC17U,
0x60C37F14U, 0x73938CE0U, 0x81F80FE3U, 0x55326B08U, 0xA759E80BU,
0xB4091BFFU, 0x466298FCU, 0x1871A4D8U, 0xEA1A27DBU, 0xF94AD42FU,
0x0B21572CU, 0xDFEB33C7U, 0x2D80B0C4U, 0x3ED04330U, 0xCCBBC033U,
0xA24BB5A6U, 0x502036A5U, 0x4370C551U, 0xB11B4652U, 0x65D122B9U,
0x97BAA1BAU, 0x84EA524EU, 0x7681D14DU, 0x2892ED69U, 0xDAF96E6AU,
0xC9A99D9EU, 0x3BC21E9DU, 0xEF087A76U, 0x1D63F975U, 0x0E330A81U,
0xFC588982U, 0xB21572C9U, 0x407EF1CAU, 0x532E023EU, 0xA145813DU,
0x758FE5D6U, 0x87E466D5U, 0x94B49521U, 0x66DF1622U, 0x38CC2A06U,
0xCAA7A905U, 0xD9F75AF1U, 0x2B9CD9F2U, 0xFF56BD19U, 0x0D3D3E1AU,
0x1E6DCDEEU, 0xEC064EEDU, 0xC38D26C4U, 0x31E6A5C7U, 0x22B65633U,
0xD0DDD530U, 0x0417B1DBU, 0xF67C32D8U, 0xE52CC12CU, 0x1747422FU,
0x49547E0BU, 0xBB3FFD08U, 0xA86F0EFCU, 0x5A048DFFU, 0x8ECEE914U,
0x7CA56A17U, 0x6FF599E3U, 0x9D9E1AE0U, 0xD3D3E1ABU, 0x21B862A8U,
0x32E8915CU, 0xC083125FU, 0x144976B4U, 0xE622F5B7U, 0xF5720643U,
0x07198540U, 0x590AB964U, 0xAB613A67U, 0xB831C993U, 0x4A5A4A90U,
0x9E902E7BU, 0x6CFBAD78U, 0x7FAB5E8CU, 0x8DC0DD8FU, 0xE330A81AU,
0x115B2B19U, 0x020BD8EDU, 0xF0605BEEU, 0x24AA3F05U, 0xD6C1BC06U,
0xC5914FF2U, 0x37FACCF1U, 0x69E9F0D5U, 0x9B8273D6U, 0x88D28022U,
0x7AB90321U, 0xAE7367CAU, 0x5C18E4C9U, 0x4F48173DU, 0xBD23943EU,
0xF36E6F75U, 0x0105EC76U, 0x12551F82U, 0xE03E9C81U, 0x34F4F86AU,
0xC69F7B69U, 0xD5CF889DU, 0x27A40B9EU, 0x79B737BAU, 0x8BDCB4B9U,
0x988C474DU, 0x6AE7C44EU, 0xBE2DA0A5U, 0x4C4623A6U, 0x5F16D052U,
0xAD7D5351U};
for (; i < len; i++) {
crc32val = crc32_tab[(HASHMAP_CAST(hashmap_uint8_t, crc32val) ^ s[i])] ^
(crc32val >> 8);
}
#endif
// Use the mix function from murmur3.
crc32val ^= len;
crc32val ^= crc32val >> 16;
crc32val *= 0x85ebca6b;
crc32val ^= crc32val >> 13;
crc32val *= 0xc2b2ae35;
crc32val ^= crc32val >> 16;
return crc32val;
}
int hashmap_memcmp_comparer(const void *const a, const hashmap_uint32_t a_len,
const void *const b, const hashmap_uint32_t b_len) {
return (a_len == b_len) && (0 == memcmp(a, b, a_len));
}
HASHMAP_ALWAYS_INLINE hashmap_uint32_t
hashmap_hash_helper_int_helper(const struct hashmap_s *const m,
const void *const k, const hashmap_uint32_t l) {
return (m->hasher(~0u, k, l) * 2654435769u) >> (32u - m->log2_capacity);
}
HASHMAP_ALWAYS_INLINE int
hashmap_hash_helper(const struct hashmap_s *const m, const void *const key,
const hashmap_uint32_t len,
hashmap_uint32_t *const out_index) {
hashmap_uint32_t curr;
hashmap_uint32_t i;
hashmap_uint32_t first_free;
/* If full, return immediately */
if (hashmap_num_entries(m) == hashmap_capacity(m)) {
return 0;
}
/* Find the best index */
curr = hashmap_hash_helper_int_helper(m, key, len);
first_free = ~0u;
for (i = 0; i < HASHMAP_LINEAR_PROBE_LENGTH; i++) {
const hashmap_uint32_t index = curr + i;
if (!m->data[index].in_use) {
first_free = (first_free < index) ? first_free : index;
} else if (m->comparer(m->data[index].key, m->data[index].key_len, key,
len)) {
*out_index = index;
return 1;
}
}
// Couldn't find a free element in the linear probe.
if (~0u == first_free) {
return 0;
}
*out_index = first_free;
return 1;
}
int hashmap_rehash_iterator(void *const new_hash,
struct hashmap_element_s *const e) {
int temp = hashmap_put(HASHMAP_PTR_CAST(struct hashmap_s *, new_hash), e->key,
e->key_len, e->data);
if (0 < temp) {
return 1;
}
/* clear old value to avoid stale pointers */
return -1;
}
/*
* Doubles the size of the hashmap, and rehashes all the elements
*/
HASHMAP_ALWAYS_INLINE int hashmap_rehash_helper(struct hashmap_s *const m) {
struct hashmap_create_options_s options;
struct hashmap_s new_m;
int flag;
memset(&options, 0, sizeof(options));
options.initial_capacity = hashmap_capacity(m) * 2;
options.hasher = m->hasher;
if (0 == options.initial_capacity) {
return 1;
}
flag = hashmap_create_ex(options, &new_m);
if (0 != flag) {
return flag;
}
/* copy the old elements to the new table */
flag = hashmap_iterate_pairs(m, hashmap_rehash_iterator,
HASHMAP_PTR_CAST(void *, &new_m));
if (0 != flag) {
return flag;
}
hashmap_destroy(m);
/* put new hash into old hash structure by copying */
memcpy(m, &new_m, sizeof(struct hashmap_s));
return 0;
}
HASHMAP_ALWAYS_INLINE hashmap_uint32_t hashmap_clz(const hashmap_uint32_t x) {
#if defined(_MSC_VER)
unsigned long result;
_BitScanReverse(&result, x);
return 31 - HASHMAP_CAST(hashmap_uint32_t, result);
#else
return HASHMAP_CAST(hashmap_uint32_t, __builtin_clz(x));
#endif
}
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif

View File

@ -1,51 +0,0 @@
#ifndef UA2F_RWMUTEX_H
#define UA2F_RWMUTEX_H
#include <stdio.h>
#include <threads.h>
#include <stdatomic.h>
typedef struct {
mtx_t write_mtx;
mtx_t read_mtx;
atomic_int read_counter;
} RWMutex;
void rw_mutex_init(RWMutex *rw_mutex) {
mtx_init(&rw_mutex->write_mtx, mtx_plain);
mtx_init(&rw_mutex->read_mtx, mtx_plain);
rw_mutex->read_counter = ATOMIC_VAR_INIT(0);
}
void rw_mutex_destroy(RWMutex *rw_mutex) {
mtx_destroy(&rw_mutex->write_mtx);
mtx_destroy(&rw_mutex->read_mtx);
}
void rw_mutex_read_lock(RWMutex *rw_mutex) {
mtx_lock(&rw_mutex->read_mtx);
__auto_type read_count = atomic_fetch_add(&rw_mutex->read_counter, 1);
if (read_count == 0) {
mtx_lock(&rw_mutex->write_mtx);
}
mtx_unlock(&rw_mutex->read_mtx);
}
void rw_mutex_read_unlock(RWMutex *rw_mutex) {
mtx_lock(&rw_mutex->read_mtx);
__auto_type read_count = atomic_fetch_sub(&rw_mutex->read_counter, 1);
if (read_count == 1) {
mtx_unlock(&rw_mutex->write_mtx);
}
mtx_unlock(&rw_mutex->read_mtx);
}
void rw_mutex_write_lock(RWMutex *rw_mutex) {
mtx_lock(&rw_mutex->write_mtx);
}
void rw_mutex_write_unlock(RWMutex *rw_mutex) {
mtx_unlock(&rw_mutex->write_mtx);
}
#endif //UA2F_RWMUTEX_H

View File

@ -4,32 +4,33 @@
#include <syslog.h>
#include "statistics.h"
static long long UserAgentPacketCount = 0;
static long long TcpPacketCount = 0;
static long long PacketWithUserAgentMark = 0;
static long long PacketWithoutUserAgentMark = 0;
static long long LastReportCount = 4;
static long long user_agent_packet_count = 0;
static long long tcp_packet_count = 0;
static long long ipv4_packet_count = 0;
static long long ipv6_packet_count = 0;
static long long last_report_count = 4;
static time_t start_t;
void init_statistics() {
start_t = time(NULL);
syslog(LOG_INFO, "Statistics initialized.");
}
void count_user_agent_packet() {
UserAgentPacketCount++;
user_agent_packet_count++;
}
void count_tcp_packet() {
TcpPacketCount++;
tcp_packet_count++;
}
void count_packet_with_user_agent_mark() {
PacketWithUserAgentMark++;
void count_ipv4_packet() {
ipv4_packet_count++;
}
void count_packet_without_user_agent_mark() {
PacketWithoutUserAgentMark++;
void count_ipv6_packet() {
ipv6_packet_count++;
}
static char TimeStringBuffer[60];
@ -52,13 +53,18 @@ char *fill_time_string(double sec) {
}
void try_print_statistics() {
if (UserAgentPacketCount / LastReportCount == 2 || UserAgentPacketCount - LastReportCount >= 16384) {
LastReportCount = UserAgentPacketCount;
if (user_agent_packet_count / last_report_count == 2 || user_agent_packet_count - last_report_count >= 16384) {
last_report_count = user_agent_packet_count;
time_t current_t = time(NULL);
syslog(LOG_INFO,
"UA2F has handled %lld ua http, %lld tcp. Set %lld mark and %lld noUA mark in %s",
UserAgentPacketCount, TcpPacketCount, PacketWithUserAgentMark, PacketWithoutUserAgentMark,
fill_time_string(difftime(current_t, start_t)));
syslog(
LOG_INFO,
"UA2F has handled %lld ua http, %lld tcp. %lld ipv4, %lld ipv6 packets in %s.",
user_agent_packet_count,
tcp_packet_count,
ipv4_packet_count,
ipv6_packet_count,
fill_time_string(difftime(current_t, start_t))
);
}
}

View File

@ -5,11 +5,9 @@ void count_user_agent_packet();
void count_tcp_packet();
void count_packet_with_user_agent_mark();
void count_ipv4_packet();
void count_packet_without_user_agent_mark();
void count_http_packet();
void count_ipv6_packet();
void init_statistics();

729
src/third/nfqueue-mnl.c Normal file
View File

@ -0,0 +1,729 @@
/*
* nfqueue-mnl.h - Interface to netfilter (nfqueue and conntrack) using libmnl
* Copyright (c) 2019 Maciej Puzio
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program - see the file COPYING.
*
* This file includes code from netfilter libnml example files,
* which had been placed in public domain by their author.
*/
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
#include "nfqueue-mnl.h"
#include <stdio.h> //fprintf
#include <stdlib.h> //malloc, free
#include <stdbool.h> //bool type
#include <sys/select.h> //pselect
#include <errno.h> //errno, EINTR, ...
#include <netinet/in.h> //in_addr, in6_addr, ...
#include <time.h> //timespec, clock_gettime
#include <string.h> //memset, strerror
#include <libmnl/libmnl.h>
#include <linux/netfilter.h> //NF_ACCEPT and NF_DROP
#include <linux/netfilter/nfnetlink_queue.h> //NFQA_* and NFQNL_*
#include <linux/netfilter/nfnetlink_conntrack.h> //CTA_*
#include <syslog.h> //LOG_*
#include <libnetfilter_queue/libnetfilter_queue.h>
/*
Kernel compatibility notes
Kernel 3.8 or later is required, as this code does not perform PF_BIND anf PF_UNBIND.
A bug in kernel causes it not to pass the timestamp attribute (always zero), depending on kernel version and NIC driver.
This is worked around by using our own timestamps.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MNL integer attr functions redefinitions
/*
Reason:
libmnl contains functions mnl_attr_get_u[16|32] that may return unaligned 16-bit and 32-bit integers.
Below we redefine these functions with alignment-safe implementations.
mnl_attr_get_u8 is immune from aligment issues, by virtue of uint8_t requiring alignment 1.
mnl_attr_get_u64 is implemented by libmnl in an alignment-safe manner, similar to one that we use below.
*/
static uint16_t fixed_attr_get_u16(const struct nlattr *attr) {
uint16_t tmp;
memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp));
return tmp;
}
static uint32_t fixed_attr_get_u32(const struct nlattr *attr) {
uint32_t tmp;
memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp));
return tmp;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// helper
// Return NULL on failure
struct nlmsghdr *nfqueue_put_header(int queue_num, int msg_type) {
if (MNL_ALIGN(sizeof(struct nlmsghdr)) > SEND_BUF_LEN) // check buffer size
return NULL;
void *buf = malloc(SEND_BUF_LEN);
ASSERT(buf != NULL);
struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
ASSERT(nlh == buf);
nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | msg_type;
nlh->nlmsg_flags = NLM_F_REQUEST;
if (nlh->nlmsg_len + MNL_ALIGN(sizeof(struct nfgenmsg)) > SEND_BUF_LEN) // check buffer size
{
free(buf);
return NULL;
}
struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg));
nfg->nfgen_family = AF_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(queue_num);
return nlh;
}
// Return <0 on failure
static int nfqueue_send_command(
struct mnl_socket *nl,
int queue_num,
int msg_type,
int attr_type,
void *data,
size_t data_size) {
int ret = -1;
struct nlmsghdr *nlh;
if ((nlh = nfqueue_put_header(queue_num, msg_type)) == NULL)
return ret;
if (!mnl_attr_put_check(nlh, SEND_BUF_LEN, attr_type, data_size, data))
goto end;
ret = (int) mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
end:
free(nlh);
return ret;
}
/*
Note about pf parameter of NFQNL_CFG_CMD_BIND command
This command can be executed only once, so we cannot call it twice with AF_INET and then AF_INET6.
It appears that pf parameter is ignored, and bind is for all AF types.
*/
// Return <0 on failure
static int nfqueue_bind(struct mnl_socket *nl, int queue_num) {
struct nfqnl_msg_config_cmd cmd =
{
.command = NFQNL_CFG_CMD_BIND,
.pf = AF_UNSPEC, // this parameter is ignored
};
return nfqueue_send_command(nl, queue_num, NFQNL_MSG_CONFIG, NFQA_CFG_CMD, &cmd, sizeof(cmd));
}
// Return <0 on failure
// If maxlen or flags are zero, use defaults
static int nfqueue_set_params(
struct mnl_socket *nl,
int queue_num,
uint8_t mode,
int range,
uint32_t maxlen,
uint32_t flags) {
struct nfqnl_msg_config_params params =
{
.copy_range = htonl(range),
.copy_mode = mode,
};
int ret = -1;
struct nlmsghdr *nlh;
if ((nlh = nfqueue_put_header(queue_num, NFQNL_MSG_CONFIG)) == NULL)
return ret;
if (!mnl_attr_put_check(nlh, SEND_BUF_LEN, NFQA_CFG_PARAMS, sizeof(params), &params))
goto end;
if (maxlen > 0) {
if (!mnl_attr_put_u32_check(nlh, SEND_BUF_LEN, NFQA_CFG_QUEUE_MAXLEN, htonl(maxlen)))
goto end;
}
if (flags) {
if (!mnl_attr_put_u32_check(nlh, SEND_BUF_LEN, NFQA_CFG_FLAGS, htonl(flags)))
goto end;
if (!mnl_attr_put_u32_check(nlh, SEND_BUF_LEN, NFQA_CFG_MASK, htonl(flags)))
goto end;
}
ret = (int) mnl_socket_sendto(nl, nlh, (size_t) nlh->nlmsg_len);
end:
free(nlh);
return ret;
}
// Version that does not set a connmark
static int nfqueue_send_verdict(struct mnl_socket *nl, int queue_num, uint32_t packet_id, int verdict) {
struct nfqnl_msg_verdict_hdr vh =
{
.verdict = htonl(verdict),
.id = htonl(packet_id),
};
return nfqueue_send_command(nl, queue_num, NFQNL_MSG_VERDICT, NFQA_VERDICT_HDR, &vh, sizeof(vh));
}
// Return <0 on failure
// Connmark is set if it is >= 0
// connmark is uint64_t to allow full 32-bit unsigned integer and also -1
static int nfqueue_send_verdict_connmark(
struct mnl_socket *nl,
int queue_num,
uint32_t packet_id,
int verdict,
int64_t connmark) {
struct nfqnl_msg_verdict_hdr vh =
{
.verdict = htonl(verdict),
.id = htonl(packet_id),
};
int ret = -1;
struct nlmsghdr *nlh;
if ((nlh = nfqueue_put_header(queue_num, NFQNL_MSG_VERDICT)) == NULL)
return ret;
if (!mnl_attr_put_check(nlh, SEND_BUF_LEN, NFQA_VERDICT_HDR, sizeof(vh), &vh))
goto end;
// Connmark
if (connmark >= 0) {
struct nlattr *nest;
if ((nest = mnl_attr_nest_start_check(nlh, SEND_BUF_LEN, NFQA_CT)) == NULL)
goto end;
if (!mnl_attr_put_u32_check(nlh, SEND_BUF_LEN, CTA_MARK, htonl((uint32_t) connmark)))
goto end;
mnl_attr_nest_end(nlh, nest);
}
ret = (int) mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
end:
free(nlh);
return ret;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NFQA and CTA attribute callbacks
// Return MNL_CB_ERROR on failure
static int parse_cta_attr_cb(const struct nlattr *attr, void *data) {
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
if (mnl_attr_type_valid(attr, CTA_MAX) < 0)
return MNL_CB_OK;
switch (type) {
case CTA_TUPLE_ORIG:
case CTA_TUPLE_REPLY:
case CTA_COUNTERS_ORIG:
case CTA_COUNTERS_REPLY:
if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
return MNL_CB_ERROR;
break;
case CTA_STATUS:
case CTA_TIMEOUT:
case CTA_MARK:
case CTA_SECMARK:
case CTA_ID:
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
return MNL_CB_ERROR;
break;
default:
break;
}
tb[type] = attr;
return MNL_CB_OK;
}
// Return MNL_CB_ERROR on failure
static int parse_tuple_cb(const struct nlattr *attr, void *data) {
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
if (mnl_attr_type_valid(attr, CTA_TUPLE_MAX) < 0)
return MNL_CB_OK;
switch (type) {
case CTA_TUPLE_IP:
case CTA_TUPLE_PROTO:
if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
return MNL_CB_ERROR;
break;
default:
break;
}
tb[type] = attr;
return MNL_CB_OK;
}
// Return MNL_CB_ERROR on failure
static int parse_ip_cb(const struct nlattr *attr, void *data) {
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
if (mnl_attr_type_valid(attr, CTA_IP_MAX) < 0)
return MNL_CB_OK;
switch (type) {
case CTA_IP_V4_SRC:
case CTA_IP_V4_DST:
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
return MNL_CB_ERROR;
break;
case CTA_IP_V6_SRC:
case CTA_IP_V6_DST:
if (mnl_attr_validate2(attr, MNL_TYPE_BINARY, sizeof(struct in6_addr)) < 0)
return MNL_CB_ERROR;
break;
default:
break;
}
tb[type] = attr;
return MNL_CB_OK;
}
// Return MNL_CB_ERROR on failure
static int parse_proto_cb(const struct nlattr *attr, void *data) {
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
if (mnl_attr_type_valid(attr, CTA_PROTO_MAX) < 0)
return MNL_CB_OK;
switch (type) {
case CTA_PROTO_NUM:
case CTA_PROTO_ICMP_TYPE:
case CTA_PROTO_ICMP_CODE:
if (mnl_attr_validate(attr, MNL_TYPE_U8) < 0)
return MNL_CB_ERROR;
break;
case CTA_PROTO_SRC_PORT:
case CTA_PROTO_DST_PORT:
case CTA_PROTO_ICMP_ID:
if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
return MNL_CB_ERROR;
break;
default:
break;
}
tb[type] = attr;
return MNL_CB_OK;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parse netfilter data into nf_packet
// helper
static bool inline read_ip_addr(struct nlattr *attr, int af, ip_address_t *ip_num, int *ip_version) {
if (attr) {
// Note: addr may be unaligned, so we use memcpy to copy it to an integer
void *addr = mnl_attr_get_payload(attr);
if (addr == NULL)
return false;
int ip_ver = 0;
if (af == AF_INET) {
ip_num->ip = 0; // clear unused bits
memcpy(&ip_num->ip4, addr, sizeof(uint32_t));
ip_ver = IPV4;
} else if (af == AF_INET6) {
memcpy(&ip_num->ip, addr, sizeof(__uint128_t));
ip_ver = IPV6;
}
if (*ip_version == 0) // not set yet
*ip_version = ip_ver;
else if (*ip_version != ip_ver) // mismatch
{
LOG(LOG_ERR, "Packet with mismatched IP versions"); // can that happen at all?
return false;
}
}
return true;
}
// helper
// Return false on failure
static bool read_addr_tuple(struct nlattr *attr, struct ip_tuple *tuple) {
struct nlattr *attr2[CTA_TUPLE_MAX + 1] = {0};
if (mnl_attr_parse_nested(attr, parse_tuple_cb, attr2) < 0)
return false;
if (attr2[CTA_TUPLE_IP]) {
struct nlattr *attr3[CTA_IP_MAX + 1] = {0};
if (mnl_attr_parse_nested(attr2[CTA_TUPLE_IP], parse_ip_cb, attr3) < 0)
return false;
bool success = true;
success &= read_ip_addr(attr3[CTA_IP_V4_SRC], AF_INET, &tuple->src, &tuple->ip_version);
success &= read_ip_addr(attr3[CTA_IP_V4_DST], AF_INET, &tuple->dst, &tuple->ip_version);
success &= read_ip_addr(attr3[CTA_IP_V6_SRC], AF_INET6, &tuple->src, &tuple->ip_version);
success &= read_ip_addr(attr3[CTA_IP_V6_DST], AF_INET6, &tuple->dst, &tuple->ip_version);
if (!success)
return false;
}
if (attr2[CTA_TUPLE_PROTO]) {
struct nlattr *attr3[CTA_PROTO_MAX + 1] = {0};
if (mnl_attr_parse_nested(attr2[CTA_TUPLE_PROTO], parse_proto_cb, attr3) < 0)
return false;
if (attr3[CTA_PROTO_SRC_PORT])
tuple->src_port = ntohs(mnl_attr_get_u16(attr3[CTA_PROTO_SRC_PORT]));
if (attr3[CTA_PROTO_DST_PORT])
tuple->dst_port = ntohs(mnl_attr_get_u16(attr3[CTA_PROTO_DST_PORT]));
}
return true;
}
// The functions below log errors
// Return false on failure
static bool nfqueue_parse(const struct nlmsghdr *nlh, struct nf_packet *packet) {
memset(packet, 0, sizeof(*packet));
clock_gettime(CLOCK_MONOTONIC_RAW, &packet->mono_time);
clock_gettime(CLOCK_REALTIME, &packet->wall_time);
struct nlattr *attr[NFQA_MAX + 1] = {0};
if (nfq_nlmsg_parse(nlh, attr) < 0) {
LOG_SYSERR("mnl_attr_parse");
return false;
}
if (attr[NFQA_PACKET_HDR] == NULL) {
LOG(LOG_ERR, "Packet metaheader not set");
return false;
}
if (attr[NFQA_PAYLOAD] == NULL) {
LOG(LOG_ERR, "Packet has no payload");
return false;
}
// Queue number
struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh);
packet->queue_num = ntohs(nfg->res_id);
// Data from netlink message header
struct nfqnl_msg_packet_hdr *ph = mnl_attr_get_payload(attr[NFQA_PACKET_HDR]);
if (ph == NULL) {
LOG(LOG_ERR, "Packet metaheader is null");
return false;
}
packet->packet_id = ntohl(ph->packet_id);
packet->hw_protocol = ntohs(ph->hw_protocol);
// Packet payload
// Create a separate copy so that buffer can be reused
void *payload = mnl_attr_get_payload(attr[NFQA_PAYLOAD]);
packet->payload_len = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]);
if (packet->payload_len == 0) {
LOG(LOG_ERR, "Packet payload has zero length");
return false;
}
packet->payload = malloc(packet->payload_len);
ASSERT(packet->payload != NULL);
memmove(packet->payload, payload, packet->payload_len);
// Timestamp
// Note: Packet timestamps do not always work reliably, e.g. kernel 4.4 always passes timestamp zero.
// See https://patchwork.ozlabs.org/patch/269090/
packet->has_timestamp = false;
if (attr[NFQA_TIMESTAMP]) {
struct nfqnl_msg_packet_timestamp *ts = mnl_attr_get_payload(attr[NFQA_TIMESTAMP]);
if (ts->sec != 0 || ts->usec != 0) {
packet->has_timestamp = true;
packet->timestamp.tv_sec = __be64_to_cpu(ts->sec);
packet->timestamp.tv_usec = __be64_to_cpu(ts->usec);
}
}
if (!packet->has_timestamp) {
LOG_ONCE(LOG_WARNING, "Kernel does not support packet timestamps");
DEBUG("Packet timestamp not set");
}
// Conntrack data
if (attr[NFQA_CT]) {
struct nlattr *attr1[CTA_MAX + 1] = {0};
packet->has_conntrack = true;
if (mnl_attr_parse_nested(attr[NFQA_CT], parse_cta_attr_cb, attr1) < 0) {
LOG_SYSERR("mnl_attr_parse_nested(CT)");
return false;
}
if (attr1[CTA_ID])
packet->conn_id = ntohl(mnl_attr_get_u32(attr1[CTA_ID]));
if (attr1[CTA_STATUS])
packet->conn_status = ntohl(mnl_attr_get_u32(attr1[CTA_STATUS]));
if (attr1[CTA_MARK]) {
packet->has_connmark = true;
packet->conn_mark = ntohl(mnl_attr_get_u32(attr1[CTA_MARK]));
}
if (attr1[CTA_TUPLE_ORIG])
if (!read_addr_tuple(attr1[CTA_TUPLE_ORIG], &packet->orig)) {
LOG_SYSERR("read_addr_tuple(orig)");
return false;
}
if (attr1[CTA_TUPLE_REPLY])
if (!read_addr_tuple(attr1[CTA_TUPLE_REPLY], &packet->reply)) {
LOG_SYSERR("read_addr_tuple(reply)");
return false;
}
} else {
packet->has_conntrack = false;
LOG_ONCE(LOG_WARNING, "Kernel does not support conntrack");
DEBUG("Conntrack data not passed by kernel");
}
// Conntrack state
if (attr[NFQA_CT_INFO])
packet->conn_state = ntohl(mnl_attr_get_u32(attr[NFQA_CT_INFO]));
else
DEBUG("Conntrack state not passed by kernel");
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helpers and workarounds
// Helper
// Return <0 on failure
// See http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch14lev1sec2.html
static int recv_timeout(int fd, int64_t timeout_ms) {
fd_set rset;
FD_ZERO(&rset);
FD_SET(fd, &rset);
struct timespec ts =
{
.tv_sec = timeout_ms / 1000,
.tv_nsec = (timeout_ms % 1000) * 1000000};
int retval = pselect(fd + 1, &rset, NULL, NULL, timeout_ms > 0 ? &ts : NULL, NULL);
if (retval == -1 && errno == EINTR) // signal
return 0; // no data
else
return retval; // >0 if descriptor is readable
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Public interface
/*
Example usage
struct nf_buffer buf[1];
memset(buf, 0, sizeof(struct nf_buffer));
while (...)
{
if (nfqueue_receive(nfqueue, buf, TIMEOUT) == IO_READY)
{
struct nf_packet packet[1];
while (nfqueue_next(buf, packet) == IO_READY)
{
handle_packet(packet);
free(packet->payload);
}
}
}
free(buf->data);
*/
/*
Note about thread safety
Function nfqueue_open() is not thread safe, but after the queue is open, most operation on it are.
Assuming that receive, send and select operations on a netlink socket are thread-safe, nfqueue_receive() and
nfqueue_verdict() are thread-safe with respect to nf_queue argument. This means that nf_queue object can be
shared between threads. On the other hand, operations on nf_buffer are not thread-safe, and because of that
every thread calling nfqueue_receive() and nfqueue_next() should use its own nf_buffer. By splitting nf_buffer
from nf_queue we allow for multithreaded access to the netfilter queue.
Note that nfqueue_next() copies the payload from nf_buffer to packet object, thus nf_buffer can be reused in
nfqueue_receive() while packet object is being processed by another thread.
*/
// Return false on failure
// If queue_len is zero, use default value
bool nfqueue_open(struct nf_queue *q, int queue_num, uint32_t queue_len) {
memset(q, 0, sizeof(*q));
q->queue_num = queue_num;
DEBUG("Initializing nfqueue %d", queue_num);
if ((q->nl_socket = mnl_socket_open2(NETLINK_NETFILTER, SOCK_NONBLOCK)) == NULL) {
LOG_SYSERR("mnl_socket_open");
return false;
}
if (mnl_socket_bind(q->nl_socket, 0, MNL_SOCKET_AUTOPID) < 0) {
LOG_SYSERR("mnl_socket_bind");
return false;
}
if (nfqueue_bind(q->nl_socket, queue_num) < 0) {
LOG_SYSERR("nfqueue_bind");
return false;
}
DEBUG("Configuring nfqueue %d (len=%u)", queue_num, queue_len);
/*
Note: For NFQA_CFG_F_CONNTRACK to be honored, modules nf_conntrack_ipv4 and nf_conntrack_ipv6 need to be loaded.
This can be done either by including ip[6]tables rules containing -m conntrack, or by using modprobe.
In older kernels (e.g. 4.4) module nf_conntrack_netlink also needs to be loaded.
In kernel 4.15 modprobe-only method is not sufficient, rule containing -m conntrack must be used.
Otherwise, conntrack info is not passed by kernel (both NFQA_CT and NFQA_CT_INFO attributes are missing).
*/
if (nfqueue_set_params(q->nl_socket, queue_num, NFQNL_COPY_PACKET, 0xFFFF, queue_len, NFQA_CFG_F_CONNTRACK) < 0) {
LOG_SYSERR("nfqueue_set_params");
return false;
}
ssize_t sockopt = 1;
mnl_socket_setsockopt(q->nl_socket, NETLINK_NO_ENOBUFS, &sockopt, sizeof(sockopt));
DEBUG("nfqueue %d initialized", queue_num);
return true;
}
void nfqueue_close(struct nf_queue *q) {
ASSERT(q);
ASSERT(q->nl_socket);
DEBUG("Closing socket for queue %d", q->queue_num);
mnl_socket_close(q->nl_socket); // nl_socket is freed here
}
// Return false on failure
// connmark is uint64_t to allow full 32-bit unsigned integer and also -1 (meaning: don't set connmark)
bool nfqueue_verdict(struct nf_queue *q, uint32_t packet_id, int verdict, int64_t connmark) {
ASSERT(q);
DEBUG("Sending verdict for packet %u: verdict: %d, connmark: %ld", packet_id, verdict, connmark);
if (nfqueue_send_verdict_connmark(q->nl_socket, q->queue_num, packet_id, verdict, connmark) < 0) {
LOG_SYSERR("nfqueue_send_verdict_connmark");
return false;
}
DEBUG("Verdict for packet %u sent successfully", packet_id);
return true;
}
// Return 1 on success, -1 on failure, 0 on timeout (if timeout_ms > 0) or data not ready
int nfqueue_receive(struct nf_queue *q, struct nf_buffer *buf, int64_t timeout_ms) {
ASSERT(q);
ASSERT(q->nl_socket);
ASSERT(buf);
if (buf->data == NULL) {
buf->data = malloc(RECV_BUF_LEN);
ASSERT(buf->data != NULL);
}
// Note: We execute recv_timeout() when timeout_ms is zero, because we had set SOCK_NONBLOCK when opening the socket.
// recv_timeout() with zero timeout blocks until data becomes available.
int retval = recv_timeout(mnl_socket_get_fd(q->nl_socket), timeout_ms);
if (retval == 0) // timeout
{
DEBUG("Netlink socket timeout");
return IO_NOTREADY;
} else if (retval < 1) // error
{
if (errno == EINTR)
return IO_NOTREADY;
LOG_SYSERR("recv_timeout");
return IO_ERROR;
}
// else data is ready
int len = (int) mnl_socket_recvfrom(q->nl_socket, buf->data, RECV_BUF_LEN);
if (len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) // no data
{
DEBUG("No data from Netlink socket");
return IO_NOTREADY;
} else {
LOG_SYSERR("mnl_socket_recvfrom");
return IO_ERROR;
}
} else if (len == 0) // EOF
{
LOG(LOG_ERR, "Netlink socket closed by peer");
return IO_ERROR;
}
buf->len = len;
buf->nlh = (struct nlmsghdr *) buf->data;
DEBUG("Received data from netfilter (length=%d)", len);
return IO_READY;
}
// Return 1 on success (result in packet), -1 on failure, 0 on no more data
int nfqueue_next(struct nf_buffer *buf, struct nf_packet *packet) {
ASSERT(buf);
ASSERT(buf->data);
ASSERT(packet);
while (mnl_nlmsg_ok(buf->nlh, buf->len)) {
if (buf->nlh->nlmsg_flags & NLM_F_DUMP_INTR) {
LOG(LOG_ERR, "Netlink dump interrupted");
return IO_ERROR;
}
if (buf->nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
if (!nfqueue_parse(buf->nlh, packet))
return IO_ERROR;
buf->nlh = mnl_nlmsg_next(buf->nlh, &buf->len);
return IO_READY;
}
switch (buf->nlh->nlmsg_type) {
case NLMSG_DONE: {
DEBUG("Netlink dump finished");
return IO_NOTREADY;
}
case NLMSG_ERROR: {
__auto_type err = (struct nlmsgerr *) NLMSG_DATA(buf->nlh);
if (err->error)
LOG(LOG_ERR, "Netlink error: %s", strerror(-err->error));
return IO_ERROR;
}
case NLMSG_NOOP: {
DEBUG("Netlink noop");
return IO_NOTREADY;
}
default: {
LOG(LOG_ERR, "Unknown Netlink message type: %d", buf->nlh->nlmsg_type);
return IO_ERROR;
}
}
}
return IO_NOTREADY;
}
#pragma clang diagnostic pop

260
src/third/nfqueue-mnl.h Normal file
View File

@ -0,0 +1,260 @@
/*
* nfqueue-mnl.h - Interface to netfilter (nfqueue and conntrack) using libmnl
* Copyright (c) 2019 Maciej Puzio
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program - see the file COPYING.
*
* This file includes code from netfilter libnml example files,
* which had been placed in public domain by their author.
*/
#ifndef NFQUEUE_MNL_H
#define NFQUEUE_MNL_H
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
#include <stdio.h> //fprintf
#include <stdlib.h> //malloc, free
#include <stdbool.h> //bool type
#include <sys/select.h> //pselect
#include <errno.h> //errno, EINTR, ...
#include <netinet/in.h> //in_addr, in6_addr, ...
#include <time.h> //timespec, clock_gettime
#include <string.h> //memset, strerror
#include <libmnl/libmnl.h>
#include <linux/netfilter.h> //NF_ACCEPT and NF_DROP
#include <linux/netfilter/nfnetlink_queue.h> //NFQA_* and NFQNL_*
#include <linux/netfilter/nfnetlink_conntrack.h> //CTA_*
#include <syslog.h> //LOG_*
#include <libnetfilter_queue/libnetfilter_queue.h>
/*
Kernel compatibility notes
Kernel 3.8 or later is required, as this code does not perform PF_BIND anf PF_UNBIND.
A bug in kernel causes it not to pass the timestamp attribute (always zero), depending on kernel version and NIC driver.
This is worked around by using our own timestamps.
*/
/*
Reasons for attributes packed and may_alias:
Attribute 'packed' is needed here to force the compiler to emit unaligned-access opcodes when accessing
objects of ip_address_t type. Such unaligned accesses happen because IP addresses are passed as Netlink
payloads that are not aligned to 16-byte boundaries. On x86_64 this means emiting movdqu and movups
instead of movdqa and movaps.
Attribute "may_alias" is used to prevent the compiler using strict aliasing optimizations for accesses
to ip_address_t. This is desirable because cross-type aliasing is what ip_address_t is designed for.
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Enums and object definitions
// ip_version
enum {
IPV4 = 4,
IPV6 = 6
};
// I/O result
enum {
IO_ERROR = -1,
IO_NOTREADY = 0, // timeout or no more data
IO_READY = 1 // data ready
};
typedef union __attribute__((packed, may_alias)) {
__uint128_t ip;
uint32_t ip4;
struct in_addr in4;
struct in6_addr in6;
struct {
uint64_t hi;
uint64_t lo;
};
} ip_address_t;
struct ip_tuple {
int ip_version;
ip_address_t src;
ip_address_t dst;
uint16_t src_port;
uint16_t dst_port;
};
// Netlink packet object
// This structure collects packet information passed by netfilter.
struct nf_packet {
int queue_num;
uint32_t packet_id; // from NFQA_PACKET_HDR
uint16_t hw_protocol; // from NFQA_PACKET_HDR; see https://en.wikipedia.org/wiki/EtherType
size_t payload_len; // NFQA_PAYLOAD length
void *payload; // NFQA_PAYLOAD
bool has_timestamp; // true if netfilter provided a timestamp
struct timeval timestamp; // NFQA_TIMESTAMP (if has_timestamp) or zero
struct timespec wall_time; // clock_gettime(CLOCK_REALTIME)
struct timespec mono_time; // clock_gettime(CLOCK_MONOTONIC_RAW)
bool has_conntrack; // true if netfilter provided conntrack info (if not, the following fields are zero)
bool has_connmark; // true if CTA_MARK is present
uint32_t conn_id; // NFQA_CT > CTA_ID; see https://www.spinics.net/lists/netdev/msg443125.html and https://patchwork.kernel.org/patch/9820809/
uint32_t conn_mark; // NFQA_CT > CTA_MARK
uint32_t conn_state; // NFQA_CT_INFO (IP_CT_NEW/ESTABLISHED/RELATED/...)
uint32_t conn_status; // NDQA_CT > CTA_STATUS (IPS_SEEN_REPLY/CONFIRMED/...)
struct ip_tuple orig; // NFQA_CT > CTA_TUPLE_ORIG
struct ip_tuple reply; // NFQA_CT > CTA_TUPLE_REPLY
};
/* addr_tuple fields
int ip_version; // IPV4 or IPV6
ip_address_t src; // CTA_TUPLE_IP > CTA_IP_V?_SRC
ip_address_t dst; // CTA_TUPLE_IP > CTA_IP_V?_DST
uint16_t src_port; // CTA_TUPLE_PROTO > CTA_PROTO_SRC_PORT
uint16_t dst_port; // CTA_TUPLE_PROTO > CTA_PROTO_DST_PORT
*/
struct nf_queue {
int queue_num;
struct mnl_socket *nl_socket;
};
struct nf_buffer {
void *data;
struct nlmsghdr *nlh;
int len; // used part of data buffer
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Error handling and logging
#ifndef LOG
#define LOG(priority, fmt, ...) syslog(priority, fmt, ##__VA_ARGS__)
#endif
#ifndef DIE
#define DIE() exit(EXIT_FAILURE)
#endif
#define LOG_ONCE(priority, fmt, ...) \
do \
{ \
static bool done = false; \
if (!done) \
{ \
LOG((priority), fmt, ##__VA_ARGS__); \
done = true; \
} \
} while (0)
#define LOG_SYSERR(fmt, ...) \
LOG(LOG_ERR, fmt ": %s", ##__VA_ARGS__, strerror(errno))
#ifdef IS_DEV
#define DEBUG(fmt, ...) \
LOG(LOG_DEBUG, fmt, ##__VA_ARGS__)
#else
#define DEBUG(fmt, ...)
#endif
#define ASSERT(condition) \
do \
{ \
if (!(condition)) \
{ \
LOG(LOG_CRIT, "Assert failed: %s [%s:%s:%d]", #condition, __func__, __FILE__, __LINE__); \
DIE(); \
} \
} while (0)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MNL integer attr functions redefinitions
/*
Reason:
libmnl contains functions mnl_attr_get_u[16|32] that may return unaligned 16-bit and 32-bit integers.
Below we redefine these functions with alignment-safe implementations.
mnl_attr_get_u8 is immune from aligment issues, by virtue of uint8_t requiring alignment 1.
mnl_attr_get_u64 is implemented by libmnl in an alignment-safe manner, similar to one that we use below.
*/
static uint16_t fixed_attr_get_u16(const struct nlattr *attr);
static uint32_t fixed_attr_get_u32(const struct nlattr *attr);
#define mnl_attr_get_u16(attr) fixed_attr_get_u16(attr)
#define mnl_attr_get_u32(attr) fixed_attr_get_u32(attr)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sending Netlink commands
#define SEND_BUF_LEN MNL_SOCKET_BUFFER_SIZE
#define RECV_BUF_LEN (MNL_SOCKET_BUFFER_SIZE + 0xFFFF)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Public interface
/*
Example usage
struct nf_buffer buf[1];
memset(buf, 0, sizeof(struct nf_buffer));
while (...)
{
if (nfqueue_receive(nfqueue, buf, TIMEOUT) == IO_READY)
{
struct nf_packet packet[1];
while (nfqueue_next(buf, packet) == IO_READY)
{
handle_packet(packet);
free(packet->payload);
}
}
}
free(buf->data);
*/
/*
Note about thread safety
Function nfqueue_open() is not thread safe, but after the queue is open, most operation on it are.
Assuming that receive, send and select operations on a netlink socket are thread-safe, nfqueue_receive() and
nfqueue_verdict() are thread-safe with respect to nf_queue argument. This means that nf_queue object can be
shared between threads. On the other hand, operations on nf_buffer are not thread-safe, and because of that
every thread calling nfqueue_receive() and nfqueue_next() should use its own nf_buffer. By splitting nf_buffer
from nf_queue we allow for multithreaded access to the netfilter queue.
Note that nfqueue_next() copies the payload from nf_buffer to packet object, thus nf_buffer can be reused in
nfqueue_receive() while packet object is being processed by another thread.
*/
// Return false on failure
// If queue_len is zero, use default value
bool nfqueue_open(struct nf_queue *q, int queue_num, uint32_t queue_len);
void nfqueue_close(struct nf_queue *q);
// Return false on failure
// connmark is uint64_t to allow full 32-bit unsigned integer and also -1 (meaning: don't set connmark)
bool nfqueue_verdict(struct nf_queue *q, uint32_t packet_id, int verdict, int64_t connmark);
// Return 1 on success, -1 on failure, 0 on timeout (if timeout_ms > 0) or data not ready
int nfqueue_receive(struct nf_queue *q, struct nf_buffer *buf, int64_t timeout_ms);
// Return 1 on success (result in packet), -1 on failure, 0 on no more data
int nfqueue_next(struct nf_buffer *buf, struct nf_packet *packet);
struct nlmsghdr *nfqueue_put_header(int queue_num, int msg_type);
#pragma clang diagnostic pop
#endif // NFQUEUE_MNL_H

1140
src/third/uthash.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,316 +1,57 @@
#include "statistics.h"
#include "handler.h"
#include "cache.h"
#include "util.h"
#include "child.h"
#include "third/nfqueue-mnl.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>
#include <syslog.h>
#include <bits/types/sig_atomic_t.h>
#include <signal.h>
#include <arpa/inet.h>
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
#include <linux/netfilter/nfnetlink_queue.h>
#include <linux/netfilter/nfnetlink_conntrack.h>
#include <libmnl/libmnl.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
#include <libnetfilter_queue/libnetfilter_queue_ipv4.h>
#include <libnetfilter_queue/pktbuff.h>
volatile sig_atomic_t should_exit = false;
#define NF_ACCEPT 1
static struct mnl_socket *nl;
const int queue_number = 10010;
char *replacement_user_agent_string = NULL;
static int parse_attrs(const struct nlattr *attr, void *data) {
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
tb[type] = attr;
return MNL_CB_OK;
}
static void
nfq_send_verdict(int queue_num, uint32_t id, struct pkt_buff *pktb, uint32_t mark, bool noUA,
char addcmd[50]) { // http mark = 24, ukn mark = 16-20, no http mark = 23
char buf[0xffff + (MNL_SOCKET_BUFFER_SIZE / 2)];
struct nlmsghdr *nlh;
struct nlattr *nest;
uint32_t setmark;
nlh = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, queue_num);
nfq_nlmsg_verdict_put(nlh, (int) id, NF_ACCEPT);
if (pktb_mangled(pktb)) {
nfq_nlmsg_verdict_put_pkt(nlh, pktb_data(pktb), pktb_len(pktb));
}
if (noUA) {
if (mark == 1) {
nest = mnl_attr_nest_start(nlh, NFQA_CT);
mnl_attr_put_u32(nlh, CTA_MARK, htonl(16));
mnl_attr_nest_end(nlh, nest);
}
if (mark >= 16 && mark <= 40) {
setmark = mark + 1;
nest = mnl_attr_nest_start(nlh, NFQA_CT);
mnl_attr_put_u32(nlh, CTA_MARK, htonl(setmark));
mnl_attr_nest_end(nlh, nest);
}
if (mark == 41) { // 21 统计确定此连接为不含UA连接
nest = mnl_attr_nest_start(nlh, NFQA_CT);
mnl_attr_put_u32(nlh, CTA_MARK, htonl(43));
mnl_attr_nest_end(nlh, nest); // 加 CONNMARK
count_packet_without_user_agent_mark();
}
} else {
if (mark != 44) {
nest = mnl_attr_nest_start(nlh, NFQA_CT);
mnl_attr_put_u32(nlh, CTA_MARK, htonl(44));
mnl_attr_nest_end(nlh, nest);
count_packet_with_user_agent_mark();
}
}
if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
perror("mnl_socket_send");
syslog(LOG_ERR, "Exit at breakpoint 1.");
exit(EXIT_FAILURE);
}
count_tcp_packet();
pktb_free(pktb);
}
static int queue_cb(const struct nlmsghdr *nlh, void *data) {
struct nfqnl_msg_packet_hdr *ph = NULL;
struct nlattr *attr[NFQA_MAX + 1] = {};
struct nlattr *ctattr[CTA_MAX + 1] = {};
struct nlattr *originattr[CTA_TUPLE_MAX + 1] = {};
struct nlattr *ipattr[CTA_IP_MAX + 1] = {};
struct nlattr *portattr[CTA_PROTO_MAX + 1] = {};
uint16_t payloadLength;
struct pkt_buff *pktb;
struct iphdr *ippkhdl;
struct tcphdr *tcppkhdl;
struct nfgenmsg *nfg;
char *tcppkpayload;
unsigned int tcppklen;
unsigned int uaoffset = 0;
unsigned int ualength = 0;
void *payload;
uint32_t mark = 0;
bool noUA = false;
char *ip;
uint16_t port = 0;
char addcmd[50];
if (nfq_nlmsg_parse(nlh, attr) < 0) {
perror("problems parsing");
return MNL_CB_ERROR;
}
nfg = mnl_nlmsg_get_payload(nlh);
if (attr[NFQA_PACKET_HDR] == NULL) {
syslog(LOG_ERR, "metaheader not set");
return MNL_CB_ERROR;
}
if (attr[NFQA_CT]) {
mnl_attr_parse_nested(attr[NFQA_CT], parse_attrs, ctattr);
if (ctattr[CTA_MARK]) {
mark = ntohl(mnl_attr_get_u32(ctattr[CTA_MARK]));
} else {
mark = 1; // no mark 1
}
if (ctattr[CTA_TUPLE_ORIG]) {
mnl_attr_parse_nested(ctattr[CTA_TUPLE_ORIG], parse_attrs, originattr);
if (originattr[CTA_TUPLE_IP]) {
mnl_attr_parse_nested(originattr[CTA_TUPLE_IP], parse_attrs, ipattr);
if (ipattr[CTA_IP_V4_DST]) {
uint32_t tmp = mnl_attr_get_u32(ipattr[CTA_IP_V4_DST]);
struct in_addr tmp2;
tmp2.s_addr = tmp;
ip = inet_ntoa(tmp2);
} else {
ip = "0.0.0.0";
}
} else {
ip = "0.0.0.0";
}
if (originattr[CTA_TUPLE_PROTO]) {
mnl_attr_parse_nested(originattr[CTA_TUPLE_PROTO], parse_attrs, portattr);
if (portattr[CTA_PROTO_DST_PORT]) {
port = ntohs(mnl_attr_get_u16(portattr[CTA_PROTO_DST_PORT]));
}
}
if (ip && port != 0) {
sprintf(addcmd, "add nohttp %s,%d", ip, port);
}
}
}
ph = mnl_attr_get_payload(attr[NFQA_PACKET_HDR]);
payloadLength = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]);
payload = mnl_attr_get_payload(attr[NFQA_PAYLOAD]);
pktb = pktb_alloc(AF_INET, payload, payloadLength, 0); //IP包
if (!pktb) {
syslog(LOG_ERR, "pktb malloc failed");
return MNL_CB_ERROR;
}
ippkhdl = nfq_ip_get_hdr(pktb); //获取ip header
if (nfq_ip_set_transport_header(pktb, ippkhdl) < 0) {
syslog(LOG_ERR, "set transport header failed");
pktb_free(pktb);
return MNL_CB_ERROR;
}
tcppkhdl = nfq_tcp_get_hdr(pktb); //获取 tcp header
tcppkpayload = nfq_tcp_get_payload(tcppkhdl, pktb); //获取 tcp载荷
tcppklen = nfq_tcp_get_payload_len(tcppkhdl, pktb); //获取 tcp长度
if (tcppkpayload) {
char *uapointer = memncasemem(tcppkpayload, tcppklen, "\r\nUser-Agent: ", 14); // 找到指向 \r 的指针
if (uapointer) {
uaoffset = uapointer - tcppkpayload + 14; // 应该指向 UA 的第一个字符
if (uaoffset >= tcppklen - 2) { // User-Agent: XXX\r\n
syslog(LOG_WARNING, "User-Agent has no content");
// https://github.com/Zxilly/UA2F/pull/42#issue-1159773997
nfq_send_verdict(ntohs(nfg->res_id), ntohl((uint32_t) ph->packet_id), pktb, mark, noUA, addcmd);
return MNL_CB_OK;
}
char *uaStartPointer = uapointer + 14;
const unsigned int uaLengthBound = tcppklen - uaoffset;
for (unsigned int i = 0; i < uaLengthBound; ++i) {
if (*(uaStartPointer + i) == '\r') {
ualength = i;
break;
}
}
if (ualength > 0) {
if (nfq_tcp_mangle_ipv4(pktb, uaoffset, ualength, replacement_user_agent_string, ualength) == 1) {
count_user_agent_packet();
} else {
syslog(LOG_ERR, "Mangle packet failed.");
pktb_free(pktb);
return MNL_CB_ERROR;
}
}
} else {
noUA = true;
}
}
nfq_send_verdict(ntohs(nfg->res_id), ntohl((uint32_t) ph->packet_id), pktb, mark, noUA, addcmd);
return MNL_CB_OK;
void signal_handler(int signum) {
should_exit = true;
}
int main(int argc, char *argv[]) {
char *buf;
struct nlmsghdr *nlh;
ssize_t ret;
unsigned int portid;
openlog("UA2F", LOG_PID, LOG_SYSLOG);
works_as_child();
init_statistics();
init_handler();
init_not_http_cache();
nl = mnl_socket_open(NETLINK_NETFILTER);
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGQUIT, signal_handler);
if (nl == NULL) {
perror("mnl_socket_open");
syslog(LOG_ERR, "Exit at mnl_socket_open.");
exit(EXIT_FAILURE);
struct nf_queue queue[1];
memset(queue, 0, sizeof(struct nf_queue));
struct nf_buffer buf[1];
memset(buf, 0, sizeof(struct nf_buffer));
__auto_type ret = nfqueue_open(queue, QUEUE_NUM, 0);
if (!ret) {
syslog(LOG_ERR, "Failed to open nfqueue");
return EXIT_FAILURE;
}
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
syslog(LOG_ERR, "Exit at mnl_socket_bind.");
exit(EXIT_FAILURE);
}
portid = mnl_socket_get_portid(nl);
buf = malloc(MNL_SOCKET_BUFFER_SIZE);
if (!buf) {
syslog(LOG_ERR, "Failed to allocate buffer memory.");
exit(EXIT_FAILURE);
}
replacement_user_agent_string = malloc(MNL_SOCKET_BUFFER_SIZE);
memset(replacement_user_agent_string, 'F', MNL_SOCKET_BUFFER_SIZE);
nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_number);
nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_BIND);
if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
perror("mnl_socket_send");
syslog(LOG_ERR, "Exit at breakpoint 7.");
exit(EXIT_FAILURE);
}
nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_number);
nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, NFQA_CFG_FLAGS,
htonl(NFQA_CFG_F_GSO | NFQA_CFG_F_FAIL_OPEN | NFQA_CFG_F_CONNTRACK));
mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, NFQA_CFG_MASK,
htonl(NFQA_CFG_F_GSO | NFQA_CFG_F_FAIL_OPEN | NFQA_CFG_F_CONNTRACK));
if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
perror("mnl_socket_send");
syslog(LOG_ERR, "Exit at mnl_socket_send.");
exit(EXIT_FAILURE);
}
ret = 1;
mnl_socket_setsockopt(nl, NETLINK_NO_ENOBUFS, &ret, sizeof(int));
syslog(LOG_NOTICE, "UA2F has inited successful.");
while (1) {
ret = mnl_socket_recvfrom(nl, buf, sizeof_buf);
if (ret == -1) { //stop at failure
perror("mnl_socket_recvfrom");
syslog(LOG_ERR, "Exit at mnl_socket_recvfrom.");
exit(EXIT_FAILURE);
}
ret = mnl_cb_run(buf, ret, 0, portid, (mnl_cb_t) queue_cb, NULL);
if (ret < 0) { //stop at failure
perror("mnl_cb_run");
syslog(LOG_ERR, "Exit at mnl_cb_run.");
exit(EXIT_FAILURE);
while (!should_exit) {
if (nfqueue_receive(queue, buf, 0) == IO_READY) {
struct nf_packet packet[1];
while (nfqueue_next(buf, packet) == IO_READY) {
handle_packet(queue, packet);
}
}
}
free(buf->data);
nfqueue_close(queue);
}
#pragma clang diagnostic pop

View File

@ -3,6 +3,8 @@
#include <string.h>
#define QUEUE_NUM 10010
void *memncasemem(const void *l, size_t l_len, const void *s, size_t s_len);
#endif //UA2F_UTIL_H