mirror of
https://github.com/Zxilly/UA2F.git
synced 2025-12-29 14:39:19 +00:00
parent
84e1ac90f1
commit
6c760a0807
42
.github/workflows/ci.yml
vendored
42
.github/workflows/ci.yml
vendored
@ -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*
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
10
Makefile
10
Makefile
@ -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
105
README.md
@ -1,17 +1,11 @@
|
||||

|
||||
[](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
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FZxilly%2FUA2F?ref=badge_large)
|
||||
|
||||
@ -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}')"
|
||||
|
||||
|
||||
98
src/cache.c
98
src/cache.c
@ -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);
|
||||
}
|
||||
|
||||
24
src/cache.h
24
src/cache.h
@ -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
|
||||
|
||||
61
src/child.c
61
src/child.c
@ -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);
|
||||
}
|
||||
10
src/child.h
10
src/child.h
@ -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
234
src/handler.c
Normal 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
10
src/handler.h
Normal 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
|
||||
718
src/hashmap.h
718
src/hashmap.h
@ -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
|
||||
@ -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
|
||||
@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
729
src/third/nfqueue-mnl.c
Normal 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), ¶ms))
|
||||
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
260
src/third/nfqueue-mnl.h
Normal 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
1140
src/third/uthash.h
Normal file
File diff suppressed because it is too large
Load Diff
327
src/ua2f.c
327
src/ua2f.c
@ -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
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user