diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a40e5a..5a796c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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* diff --git a/CMakeLists.txt b/CMakeLists.txt index 67e16a8..a426cfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Makefile b/Makefile index b9645f9..9a7a88a 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index 6753c94..20224e2 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,11 @@ ![UA2F](https://socialify.git.ci/Zxilly/UA2F/image?description=1&descriptionEditable=Change%20User-agent%20to%20F-words%20on%20OpenWRT%20router.&font=Inter&language=1&pattern=Plus&stargazers=1&theme=Light) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FZxilly%2FUA2F.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FZxilly%2FUA2F?ref=badge_shield) -~~**当前 git HEAD 是一个高度实验性版本,请查找 commit 以获得可用版本**~~ - -**我相信当前版本已经足够可用,但是仍然有很多改进等待完成** - 暂时来说,懒得写 README,请先参照 [博客文章](https://learningman.top/archives/304) 完成操作 如果遇到了任何问题,欢迎提出 Issues,但是更欢迎直接提交 Pull Request -> 由于新加入的 CONNMARK 影响,编译内核时需要添加 `NETFILTER_NETLINK_GLUE_CT` flag,否则会出现 `mnl_cb_run:Not supported` 错误 - -> 由于新加入的 ipset 影响,需要确保你的内核支持 `hash:ip,port` 的 ipset 类型 +> 由于新加入的 CONNMARK 影响,编译内核时需要添加 `NETFILTER_NETLINK_GLUE_CT` flag # uci command @@ -32,108 +26,11 @@ service ua2f enable service ua2f start ``` -# Manual configure -## ipset command - -请确保添加此语句至开机自启 -```bash -ipset create nohttp hash:ip,port hashsize 16384 timeout 300 -``` -`UA2F` 运行时依赖名称为 `nohttp`,类型为 `hash:ip,port` 的 ipset - -## iptables rules -```shell -iptables -t mangle -N ua2f -iptables -t mangle -A ua2f -d 10.0.0.0/8 -j RETURN -iptables -t mangle -A ua2f -d 127.0.0.0/8 -j RETURN -iptables -t mangle -A ua2f -d 192.168.0.0/16 -j RETURN # 不处理流向保留地址的包 -iptables -t mangle -A ua2f -p tcp --dport 443 -j RETURN -iptables -t mangle -A ua2f -p tcp --dport 22 -j RETURN # 不处理 SSH 和 https -iptables -t mangle -A ua2f -p tcp --dport 80 -j CONNMARK --set-mark 44 -iptables -t mangle -A ua2f -m connmark --mark 43 -j RETURN # 不处理标记为非 http 的流 (实验性) -iptables -t mangle -A ua2f -m set --set nohttp dst,dst -j RETURN -iptables -t mangle -A ua2f -p tcp --dport 80 -m string --string "/mmtls/" --algo bm -j RETURN # 不处理微信的 mmtls -iptables -t mangle -A ua2f -j NFQUEUE --queue-num 10010 - -iptables -t mangle -A FORWARD -p tcp -m conntrack --ctdir ORIGINAL -j ua2f -``` - ## TODO -- [x] 灾难恢复 - [ ] pthread 支持,由不同线程完成入队出队 -- [x] 修复偶现的非法内存访问,定位错误是一个麻烦的问题 (疑似修复,继续观察) -- [x] 配合 CONNMARK 与 ipset,不再修改已被判定为非 http 的 tcp 连接,期望减少 80% 以上的负载 (高度实验性实现) - [ ] 清除 TCP Header 中的 timestamp,有论文认为这可以被用来识别 NAT 后的多设备,劫持 NTP 服务器并不一定有效 -## Helpful Log -http 头包占比观察 -```log -Sat Dec 5 23:57:23 2020 user.notice : UA2F try to start daemon parent at [10331], parent process will suicide. -Sat Dec 5 23:57:23 2020 user.notice : UA2F parent daemon start at [10331]. -Sat Dec 5 23:57:23 2020 user.notice : UA2F parent daemon set sid at [10331]. -Sat Dec 5 23:57:23 2020 user.notice : UA2F true daemon will start at [10332], daemon parent suicide. -Sat Dec 5 23:57:23 2020 user.notice : UA2F true daemon start at [10332]. -Sat Dec 5 23:57:23 2020 syslog.notice UA2F[10332]: UA2F has inited successful. -Sat Dec 5 23:57:47 2020 syslog.info UA2F[10332]: UA2F has handled 8 http packet and 243 tcp packet in 24s -Sat Dec 5 23:57:47 2020 syslog.info UA2F[10332]: UA2F has handled 16 http packet and 356 tcp packet in 24s -Sat Dec 5 23:57:47 2020 syslog.info UA2F[10332]: UA2F has handled 32 http packet and 440 tcp packet in 24s -Sat Dec 5 23:57:48 2020 syslog.info UA2F[10332]: UA2F has handled 64 http packet and 609 tcp packet in 25s -Sat Dec 5 23:57:49 2020 syslog.info UA2F[10332]: UA2F has handled 128 http packet and 1287 tcp packet in 26s -Sat Dec 5 23:58:58 2020 syslog.info UA2F[10332]: UA2F has handled 256 http packet and 6052 tcp packet in 95s -Sat Dec 5 23:59:01 2020 syslog.info UA2F[10332]: UA2F has handled 512 http packet and 9003 tcp packet in 98s -Sat Dec 5 23:59:39 2020 syslog.info UA2F[10332]: UA2F has handled 1024 http packet and 13764 tcp packet in 136s -Sun Dec 6 00:08:21 2020 syslog.info UA2F[10332]: UA2F has handled 2048 http packet and 48231 tcp packet in 658s -Sun Dec 6 00:31:57 2020 syslog.info UA2F[10332]: UA2F has handled 4096 http packet and 163337 tcp packet in 2074s -Sun Dec 6 11:31:39 2020 syslog.info UA2F[10332]: UA2F has handled 8192 http packet and 588216 tcp packet in 41656s -``` - -当前运行时间 -```log -Fri Jan 1 15:10:09 2021 syslog.notice UA2F[5219]: UA2F has inited successful. -Fri Jan 1 15:11:18 2021 syslog.info UA2F[5219]: UA2F has handled 8 http packet, 0 http packet without ua and 107 tcp packet in 1 minutes and 9 seconds -Fri Jan 1 15:12:23 2021 syslog.info UA2F[5219]: UA2F has handled 16 http packet, 4 http packet without ua and 370 tcp packet in 2 minutes and 14 seconds -Fri Jan 1 15:13:52 2021 syslog.info UA2F[5219]: UA2F has handled 32 http packet, 4 http packet without ua and 722 tcp packet in 3 minutes and 43 seconds -Fri Jan 1 15:13:57 2021 syslog.info UA2F[5219]: UA2F has handled 64 http packet, 4 http packet without ua and 850 tcp packet in 3 minutes and 48 seconds -Fri Jan 1 15:14:17 2021 syslog.info UA2F[5219]: UA2F has handled 128 http packet, 4 http packet without ua and 1243 tcp packet in 4 minutes and 8 seconds -Fri Jan 1 15:22:35 2021 syslog.info UA2F[5219]: UA2F has handled 256 http packet, 12 http packet without ua and 2565 tcp packet in 12 minutes and 26 seconds -Fri Jan 1 15:42:24 2021 syslog.info UA2F[5219]: UA2F has handled 512 http packet, 30 http packet without ua and 6491 tcp packet in 32 minutes and 15 seconds -Fri Jan 1 16:29:59 2021 syslog.info UA2F[5219]: UA2F has handled 1024 http packet, 68 http packet without ua and 19188 tcp packet in 1 hours, 19 minutes and 50 seconds -Fri Jan 1 18:06:01 2021 syslog.info UA2F[5219]: UA2F has handled 2048 http packet, 173 http packet without ua and 36951 tcp packet in 2 hours, 55 minutes and 52 seconds -Fri Jan 1 21:09:36 2021 syslog.info UA2F[5219]: UA2F has handled 4096 http packet, 849 http packet without ua and 137599 tcp packet in 5 hours, 59 minutes and 27 seconds -Sat Jan 2 01:39:39 2021 syslog.info UA2F[5219]: UA2F has handled 8192 http packet, 1747 http packet without ua and 249561 tcp packet in 10 hours, 29 minutes and 30 seconds -Sat Jan 2 15:06:43 2021 syslog.info UA2F[5219]: UA2F has handled 16384 http packet, 2844 http packet without ua and 551953 tcp packet in 23 hours, 56 minutes and 34 seconds -Sun Jan 3 10:22:28 2021 syslog.info UA2F[5219]: UA2F has handled 32768 http packet, 5047 http packet without ua and 1919845 tcp packet in 1 days, 19 hours, 12 minutes and 19 seconds -Mon Jan 4 13:25:04 2021 syslog.info UA2F[5219]: UA2F has handled 65536 http packet, 8435 http packet without ua and 3973193 tcp packet in 2 days, 22 hours, 14 minutes and 55 seconds -``` - -``` -Sat Mar 13 14:26:48 2021 user.notice : Try to start UA2F processor at [24049]. -Sat Mar 13 14:26:48 2021 user.notice : UA2F processor start at [24049]. -Sat Mar 13 14:26:48 2021 syslog.notice UA2F[24049]: ipset inited. -Sat Mar 13 14:26:48 2021 syslog.notice UA2F[24049]: UA2F has inited successful. -Sat Mar 13 14:26:51 2021 syslog.info UA2F[24049]: UA2F has handled 8 http, 0 http 1.0, 0 noua http, 58 tcp. Set 0 mark and 0 nohttp mark in 3 seconds -Sat Mar 13 14:26:57 2021 syslog.info UA2F[24049]: UA2F has handled 16 http, 0 http 1.0, 1 noua http, 140 tcp. Set 0 mark and 1 nohttp mark in 9 seconds -Sat Mar 13 14:27:23 2021 syslog.info UA2F[24049]: UA2F has handled 32 http, 0 http 1.0, 3 noua http, 1286 tcp. Set 2 mark and 3 nohttp mark in 35 seconds -Sat Mar 13 14:27:35 2021 syslog.info UA2F[24049]: UA2F has handled 64 http, 0 http 1.0, 3 noua http, 2042 tcp. Set 4 mark and 3 nohttp mark in 47 seconds -Sat Mar 13 14:28:55 2021 syslog.info UA2F[24049]: UA2F has handled 128 http, 0 http 1.0, 5 noua http, 7052 tcp. Set 13 mark and 8 nohttp mark in 2 minutes and 7 seconds -Sat Mar 13 14:33:45 2021 syslog.info UA2F[24049]: UA2F has handled 256 http, 2 http 1.0, 39 noua http, 12965 tcp. Set 19 mark and 25 nohttp mark in 6 minutes and 57 seconds -Sat Mar 13 14:50:22 2021 syslog.info UA2F[24049]: UA2F has handled 512 http, 4 http 1.0, 82 noua http, 25230 tcp. Set 58 mark and 45 nohttp mark in 23 minutes and 34 seconds -Sat Mar 13 15:05:10 2021 syslog.info UA2F[24049]: UA2F has handled 1024 http, 9 http 1.0, 154 noua http, 76718 tcp. Set 72 mark and 69 nohttp mark in 38 minutes and 22 seconds -Sat Mar 13 15:40:06 2021 syslog.info UA2F[24049]: UA2F has handled 2048 http, 218 http 1.0, 630 noua http, 118648 tcp. Set 151 mark and 162 nohttp mark in 1 hours, 13 minutes and 18 seconds -Sat Mar 13 16:56:16 2021 syslog.info UA2F[24049]: UA2F has handled 4096 http, 481 http 1.0, 1012 noua http, 222476 tcp. Set 368 mark and 291 nohttp mark in 2 hours, 29 minutes and 28 seconds -Sat Mar 13 21:49:04 2021 syslog.info UA2F[24049]: UA2F has handled 8192 http, 610 http 1.0, 1659 noua http, 355347 tcp. Set 673 mark and 789 nohttp mark in 7 hours, 22 minutes and 16 seconds -Sun Mar 14 00:46:13 2021 syslog.info UA2F[24049]: UA2F has handled 16384 http, 2260 http 1.0, 3479 noua http, 888912 tcp. Set 863 mark and 1052 nohttp mark in 10 hours, 19 minutes and 25 seconds -Sun Mar 14 02:39:43 2021 syslog.info UA2F[24049]: UA2F has handled 24576 http, 4570 http 1.0, 5854 noua http, 1288440 tcp. Set 1121 mark and 1121 nohttp mark in 12 hours, 12 minutes and 55 seconds -Sun Mar 14 09:38:22 2021 syslog.info UA2F[24049]: UA2F has handled 32768 http, 5663 http 1.0, 7167 noua http, 1550242 tcp. Set 1231 mark and 1306 nohttp mark in 19 hours, 11 minutes and 34 seconds -Sun Mar 14 09:45:30 2021 syslog.info UA2F[24049]: UA2F has handled 40960 http, 5665 http 1.0, 7170 noua http, 1623063 tcp. Set 1236 mark and 1317 nohttp mark in 19 hours, 18 minutes and 42 seconds -Sun Mar 14 11:23:40 2021 syslog.info UA2F[24049]: UA2F has handled 49152 http, 8001 http 1.0, 10014 noua http, 2138665 tcp. Set 1424 mark and 1585 nohttp mark in 20 hours, 56 minutes and 52 seconds -Sun Mar 14 18:50:18 2021 syslog.info UA2F[24049]: UA2F has handled 57344 http, 8632 http 1.0, 11863 noua http, 2705798 tcp. Set 1668 mark and 2281 nohttp mark in 1 days, 4 hours, 23 minutes and 30 seconds -Mon Mar 15 00:41:03 2021 syslog.info UA2F[24049]: UA2F has handled 65536 http, 8686 http 1.0, 12738 noua http, 3194659 tcp. Set 1830 mark and 2941 nohttp mark in 1 days, 10 hours, 14 minutes and 15 seconds -Mon Mar 15 11:39:34 2021 syslog.info UA2F[24049]: UA2F has handled 73728 http, 8691 http 1.0, 13282 noua http, 3360247 tcp. Set 1867 mark and 4219 nohttp mark in 1 days, 21 hours, 12 minutes and 46 seconds -``` - - ## License [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FZxilly%2FUA2F.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FZxilly%2FUA2F?ref=badge_large) diff --git a/files/ua2f.init b/files/ua2f.init index a610ad7..f8e27a5 100755 --- a/files/ua2f.init +++ b/files/ua2f.init @@ -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}')" diff --git a/src/cache.c b/src/cache.c index 68c81b2..15bdbfd 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,48 +1,42 @@ #include "cache.h" -#include "hashmap.h" -#include "rwmutex.h" +#include "third/uthash.h" #include -#include #include #include +#include -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; -} \ No newline at end of file + 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); +} diff --git a/src/cache.h b/src/cache.h index f85b036..b4b9189 100644 --- a/src/cache.h +++ b/src/cache.h @@ -5,4 +5,28 @@ #ifndef UA2F_CACHE_H #define UA2F_CACHE_H +#include +#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 diff --git a/src/child.c b/src/child.c deleted file mode 100644 index f2a0843..0000000 --- a/src/child.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "child.h" - -#include -#include -#include -#include -#include - -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); -} diff --git a/src/child.h b/src/child.h deleted file mode 100644 index 1095d94..0000000 --- a/src/child.h +++ /dev/null @@ -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 diff --git a/src/handler.c b/src/handler.c new file mode 100644 index 0000000..0278dc7 --- /dev/null +++ b/src/handler.c @@ -0,0 +1,234 @@ +// +// Created by zxilly on 2023/4/20. +// + +#include +#include "handler.h" +#include "cache.h" +#include "util.h" +#include "statistics.h" + +#include +#include +#include +#include + +#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); + } +} \ No newline at end of file diff --git a/src/handler.h b/src/handler.h new file mode 100644 index 0000000..8333fb5 --- /dev/null +++ b/src/handler.h @@ -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 diff --git a/src/hashmap.h b/src/hashmap.h deleted file mode 100644 index e424e3e..0000000 --- a/src/hashmap.h +++ /dev/null @@ -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 -*/ -#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 -#include - -#if (defined(_MSC_VER) && defined(__AVX__)) || \ - (!defined(_MSC_VER) && defined(__SSE4_2__)) -#define HASHMAP_X86_SSE42 -#endif - -#if defined(HASHMAP_X86_SSE42) -#include -#endif - -#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) -#define HASHMAP_ARM_CRC32 -#endif - -#if defined(HASHMAP_ARM_CRC32) -#include -#endif - -#if defined(_MSC_VER) -#include -#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 -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(x) -#define HASHMAP_PTR_CAST(type, x) reinterpret_cast(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 \ No newline at end of file diff --git a/src/rwmutex.h b/src/rwmutex.h deleted file mode 100644 index 2839d80..0000000 --- a/src/rwmutex.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef UA2F_RWMUTEX_H -#define UA2F_RWMUTEX_H - -#include -#include -#include - -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 diff --git a/src/statistics.c b/src/statistics.c index c7a3dc9..0e1b550 100644 --- a/src/statistics.c +++ b/src/statistics.c @@ -4,32 +4,33 @@ #include #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)) + ); } } diff --git a/src/statistics.h b/src/statistics.h index cd7e6a4..03cbc14 100644 --- a/src/statistics.h +++ b/src/statistics.h @@ -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(); diff --git a/src/third/nfqueue-mnl.c b/src/third/nfqueue-mnl.c new file mode 100644 index 0000000..4589caf --- /dev/null +++ b/src/third/nfqueue-mnl.c @@ -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 //fprintf +#include //malloc, free +#include //bool type +#include //pselect +#include //errno, EINTR, ... +#include //in_addr, in6_addr, ... +#include //timespec, clock_gettime +#include //memset, strerror +#include + +#include //NF_ACCEPT and NF_DROP +#include //NFQA_* and NFQNL_* +#include //CTA_* +#include //LOG_* +#include + +/* +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 + diff --git a/src/third/nfqueue-mnl.h b/src/third/nfqueue-mnl.h new file mode 100644 index 0000000..ea5cbc3 --- /dev/null +++ b/src/third/nfqueue-mnl.h @@ -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 //fprintf +#include //malloc, free +#include //bool type +#include //pselect +#include //errno, EINTR, ... +#include //in_addr, in6_addr, ... +#include //timespec, clock_gettime +#include //memset, strerror +#include + +#include //NF_ACCEPT and NF_DROP +#include //NFQA_* and NFQNL_* +#include //CTA_* +#include //LOG_* +#include + +/* +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 \ No newline at end of file diff --git a/src/third/uthash.h b/src/third/uthash.h new file mode 100644 index 0000000..40eb095 --- /dev/null +++ b/src/third/uthash.h @@ -0,0 +1,1140 @@ +/* +Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ \ No newline at end of file diff --git a/src/ua2f.c b/src/ua2f.c index 847ff0b..7c34020 100644 --- a/src/ua2f.c +++ b/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 #include #include -#include -#include -#include -#include #include +#include #include -#include +#pragma clang diagnostic push +#pragma ide diagnostic ignored "EndlessLoop" -#include -#include -#include -#include -#include -#include -#include +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 \ No newline at end of file diff --git a/src/util.h b/src/util.h index 6faf9ae..7f47058 100644 --- a/src/util.h +++ b/src/util.h @@ -3,6 +3,8 @@ #include +#define QUEUE_NUM 10010 + void *memncasemem(const void *l, size_t l_len, const void *s, size_t s_len); #endif //UA2F_UTIL_H