From fd42de290b8d9593e3114af5734f889296f9edf0 Mon Sep 17 00:00:00 2001 From: baydakov-georgiy Date: Thu, 23 Apr 2026 21:43:44 +0300 Subject: [PATCH 1/7] docs(test_stand): instruction for using test stand --- wiki/using_test_stand.md | 297 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 wiki/using_test_stand.md diff --git a/wiki/using_test_stand.md b/wiki/using_test_stand.md new file mode 100644 index 0000000..fe7220a --- /dev/null +++ b/wiki/using_test_stand.md @@ -0,0 +1,297 @@ +# Using Test Stand + +The test stand runs QEMU RISC-V virtual machines (controller + filter workers) with traffic generators in Docker containers. It allows testing the full filtering pipeline: traffic generation, DNS interception, domain classification via Kaspersky API, and policy-based blocking. + +## Prerequisites + +- `qemu-system-riscv64` +- Docker and Docker Compose +- Bazel + +## Network Layout + +Bridges: +* br-mgmt (10.0.2.0/24) - SSH access, gRPC communication +* br-inet (10.0.3.0/24) - Internet access (controller) +* br-testnet1 (10.0.0.0/24) - Traffic to filter1 +* br-testnet2 (10.0.1.0/24) - Traffic to filter2 + +VMs: +* filter1 (10.0.2.1) - DPDK packet filter and gRPC worker +* filter2 (10.0.2.2) - DPDK packet filter and gRPC worker +* controller (10.0.2.3) - gRPC server and Kaspersky API classifier + +## 0. Yocto Image + +The test stand requires a RISC-V image `cluster-image` and SDK from `vm_build_risc_v` + +**Image** +```bash +cd vm_build_risc_v +make build +make qemu +``` + +`make build` creates the Docker container with the build environment. + +`make qemu` runs the full Yocto build for `qemuriscv64` inside it. + +**SDK** +```bash +make qemu-sdk +``` + +The SDK installer script will appear in `vm_build_risc_v/qemu/poky/build/tmp/deploy/sdk/`. Install it in `/opt/riscv-sdk`: + +```bash +sudo vm_build_risc_v/qemu/poky/build/tmp/deploy/sdk/poky-glibc-x86_64-cluster-image-riscv64-qemuriscv64-toolchain-*.sh -d /opt/riscv-sdk +``` + +Edit `test/virtual_load_network/.env`: +```env +YOCTO_DEPLOY_DIR=/path/to/deploy/images/qemuriscv64 +``` + + +## 1. Build Controller + +```bash +cd controller +make build-riscv +``` + +The binary is placed at `controller/bin/grpc_server`. + +## 2. Build Worker + +```bash +cd worker +bazel build --config=riscv64 //:worker +``` + +The binary is placed at `worker/bazel-bin/worker`. + +## 3. Start Test Stand + +```bash +cd test/virtual_load_network +make run +``` + +This will: +- Start traffic generator containers +- Create network bridges and TAP interfaces +- Launch 3 QEMU VMs: `filter1`, `filter2`, and `controller` +- Copy binaries and configs to `shared/` directory + +Expected output: +``` +➜ virtual_load_network git:(test_stand) ✗ sudo scripts/start.sh +[sudo] password for lespend: +[+] Building 2.9s (13/13) FINISHED + => [internal] load local bake definitions 0.0s + => => reading from stdin 1.21kB 0.0s + => [traffic-gen-1 internal] load build definition from Dockerfile.traffic-gen 0.0s + => => transferring dockerfile: 208B 0.0s + => [traffic-gen-2 internal] load metadata for docker.io/library/alpine:3.20 1.9s + => [traffic-gen-1 internal] load .dockerignore 0.0s + => => transferring context: 2B 0.0s + => [traffic-gen-1 internal] load build context 0.0s + => => transferring context: 500B 0.0s + => [traffic-gen-2 1/4] FROM docker.io/library/alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc 0.0s + => CACHED [traffic-gen-2 2/4] RUN apk add --no-cache curl bash 0.0s + => CACHED [traffic-gen-2 3/4] COPY ./scripts/traffic-gen.sh /entrypoint.sh 0.0s + => CACHED [traffic-gen-1 4/4] RUN chmod +x /entrypoint.sh 0.0s + => [traffic-gen-2] exporting to image 0.0s + => => exporting layers 0.0s + => => writing image sha256:e99e2c8bdbe3ed141026d06d3ececcadbc769171b650ac5359ff79940eb87889 0.0s + => => naming to docker.io/library/virtual_load_network-traffic-gen-2 0.0s + => [traffic-gen-1] exporting to image 0.0s + => => exporting layers 0.0s + => => writing image sha256:8528a890010cfd50b8c21c85de488da8e22070021e37709b7a7c9926c16629b2 0.0s + => => naming to docker.io/library/virtual_load_network-traffic-gen-1 0.0s + => [traffic-gen-2] resolving provenance for metadata file 0.0s + => [traffic-gen-1] resolving provenance for metadata file 0.0s +[+] up 8/8 + ✔ Image virtual_load_network-traffic-gen-2 Built 3.0s + ✔ Image virtual_load_network-traffic-gen-1 Built 3.0s + ✔ Network virtual_load_network_testnet1 Created 0.1s + ✔ Network virtual_load_network_testnet2 Created 0.1s + ✔ Container virtual_load_network-traffic-gen-2-2 Started 0.7s + ✔ Container virtual_load_network-traffic-gen-1-2 Started 0.7s + ✔ Container virtual_load_network-traffic-gen-2-1 Started 0.5s + ✔ Container virtual_load_network-traffic-gen-1-1 Started 0.5s +10.0.0.254 dev eth0 lladdr 1e:68:2a:65:96:99 ref 1 used 0/0/0 probes 4 REACHABLE + +*** Round 1, deleting 1 entries *** +*** Flush is complete after 1 round(s) *** +10.0.0.254 dev eth0 lladdr 1e:68:2a:65:96:99 ref 1 used 0/0/0 probes 4 REACHABLE + +*** Round 1, deleting 1 entries *** +*** Flush is complete after 1 round(s) *** +10.0.1.254 dev eth0 lladdr 36:e3:51:79:b3:3c ref 1 used 0/0/0 probes 4 REACHABLE + +*** Round 1, deleting 1 entries *** +*** Flush is complete after 1 round(s) *** +10.0.1.254 dev eth0 lladdr 36:e3:51:79:b3:3c ref 1 used 0/0/0 probes 4 REACHABLE + +*** Round 1, deleting 1 entries *** +*** Flush is complete after 1 round(s) *** +Test stand is running + Filter VMs: filter1 (pid 37833), filter2 (pid 37857) + Controller: pid 38215 + Traffic generators: 4 containers +``` +``` +``` + +### 1. Start Controller + +Connect to the controller VM via SSH: + +```bash +ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.0.2.3 +``` + +Set up internet access (required for Kaspersky API): + +```bash +sh /mnt/shared/setup-inet.sh +``` + +Start the controller: + +```bash +cd /mnt/shared +KASPERSKY_API_KEY= ./controller +KASPERSKY_API_KEY=wyo915OXTCe5stpLCtc5Ww== ./controller +``` + +### 2. Load Filtering Policy + +On the host, install dependencies and load a policy: + +```bash +cd admin +uv venv --python 3.12 .venv +source .venv/bin/activate +uv pip install -r requirements.txt +uv pip install "setuptools<75" +python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. admin_service.proto +``` + +Edit `admin/.env` to point to the controller: + +``` +SERVER_HOST=10.0.2.3 +SERVER_PORT=50051 +``` + +Edit `config.toml` with desired rules, then load: + +```bash +python admin.py --file config.toml +``` + +Expected output: +``` +(.venv) ➜ admin git:(test_stand) ✗ python admin.py --file config.toml +Config loaded +``` +``` +``` + +### 3. Start Worker + +Connect to the filter VM via SSH: + +```bash +ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.0.2.1 # filter1 +ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.0.2.2 # filter2 +``` + +Start the worker: + +```bash +cd /mnt/shared +WORKER_ID=1 METRICS_GATEWAY_ADDRESS=10.0.2.254 METRICS_GATEWAY_PORT=9091 CONTROLLER_GRPC_ADDR=10.0.2.3:50051 DPDK_PORT_IN=eth0 DPDK_PORT_OUT=eth1 ./worker +``` + +For `filter2`, use `WORKER_ID=2`. + +To redirect logs to a shared file (viewable from host): + +```bash +WORKER_ID=1 METRICS_GATEWAY_ADDRESS=10.0.2.254 METRICS_GATEWAY_PORT=9091 CONTROLLER_GRPC_ADDR=10.0.2.3:50051 DPDK_PORT_IN=eth0 DPDK_PORT_OUT=eth1 ./worker > /mnt/shared/filter1.log 2>&1 & +``` + +Expected output: +``` +root@qemuriscv64:~# cd /mnt/shared +root@qemuriscv64:/mnt/shared# WORKER_ID=1 METRICS_GATEWAY_ADDRESS=10.0.2.254 METRICS_GATEWAY_PORT=9091 CONTROLLER_GRPC_ADDR=10.0.2.3:50051 DPDK_PORT_IN=eth0 +DPDK_PORT_OUT=eth1 ./worker +[2026-04-23 16:46:28.534] [info] Initialize MetricsCollector with 10.0.2.254:9091 +[2026-04-23 16:46:28.823] [info] gRPC channel created to 10.0.2.3:50051 +[2026-04-23 16:46:28.826] [info] Signal handlers registered +[2026-04-23 16:46:28.828] [info] Worker 1 requests policy +[2026-04-23 16:46:29.321] [info] Policy received +EAL: Detected CPU lcores: 2 +EAL: Detected NUMA nodes: 1 +EAL: Detected shared linkage of DPDK +EAL: Multi-process socket /var/run/dpdk/rte/mp_socket +EAL: Selected IOVA mode 'PA' +EAL: TSC using RISC-V rdtime. +TELEMETRY: No legacy callbacks, legacy socket not created +libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. +libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. +libbpf: elf: skipping unrecognized data section(8) .xdp_run_config +libbpf: elf: skipping unrecognized data section(9) xdp_metadata +libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. +libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. +libbpf: elf: skipping unrecognized data section(7) xdp_metadata +libbpf: elf: skipping unrecognized data section(7) xdp_metadata +libxdp: No bpffs found at /sys/fs/bpf +libxdp: Can't use dispatcher without a working bpffs +libxdp: Falling back to loading single prog without dispatcher +libbpf: elf: skipping unrecognized data section(7) xdp_metadata +Port 0 initialized +libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. +libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. +libbpf: elf: skipping unrecognized data section(8) .xdp_run_config +libbpf: elf: skipping unrecognized data section(9) xdp_metadata +libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. +libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. +libbpf: elf: skipping unrecognized data section(7) xdp_metadata +libbpf: elf: skipping unrecognized data section(7) xdp_metadata +libxdp: No bpffs found at /sys/fs/bpf +libxdp: Can't use dispatcher without a working bpffs +libxdp: Falling back to loading single prog without dispatcher +libbpf: elf: skipping unrecognized data section(7) xdp_metadata +Port 1 initialized +Port 2 initialized +Port 0 started +Port 1 started +Port 2 started +[INFO] Loaded 0 records from SQLite, 0 expired skipped +[2026-04-23 16:46:30.684] [info] DPDK initialized: in_port=0, out_port=1 +[2026-04-23 16:46:31.204] [info] Worker 1 classifying domain 'betboom.ru' +[2026-04-23 16:46:33.307] [info] Domain 'betboom.ru' classified as category 'Gambling' with trust level 0 +This site has a locked category[INFO] Packet without dns request +``` +``` +``` + + +## 5. Monitoring +```sh +sudo tcpdump -i tap-f1-out port 53 # output packets +sudo tcpdump -i tap-f1-in port 53 # input packets +``` + +## 4. Stop Test Stand + +```bash +cd test/virtual_load_network +make stop +``` + From 7b12474c43e4f43d0b77762217dad363cc793d7b Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Wed, 27 May 2026 23:40:33 +0300 Subject: [PATCH 2/7] docs: update wiki-using_test_stand --- wiki/using_test_stand.md | 321 ++++++++++++++++++++------------------- 1 file changed, 164 insertions(+), 157 deletions(-) diff --git a/wiki/using_test_stand.md b/wiki/using_test_stand.md index fe7220a..ae4ba80 100644 --- a/wiki/using_test_stand.md +++ b/wiki/using_test_stand.md @@ -64,18 +64,31 @@ The binary is placed at `controller/bin/grpc_server`. ## 2. Build Worker +standard assembly: ```bash cd worker bazel build --config=riscv64 //:worker ``` +build with debug output: +```bash +cd worker +bazel build --config=riscv64 //:worker --copt="-DDEBUG=1" +``` + +If you experience a hang during assembly, limit the memory with a flag, for example: +```bash +cd worker +bazel build --config=riscv64 //:worker --copt="-DDEBUG=1" --local_ram_resources=4096 +``` + The binary is placed at `worker/bazel-bin/worker`. ## 3. Start Test Stand ```bash -cd test/virtual_load_network -make run +cd test/virtual_load_network/scripts +sudo ./start.sh ``` This will: @@ -86,212 +99,206 @@ This will: Expected output: ``` -➜ virtual_load_network git:(test_stand) ✗ sudo scripts/start.sh -[sudo] password for lespend: -[+] Building 2.9s (13/13) FINISHED - => [internal] load local bake definitions 0.0s - => => reading from stdin 1.21kB 0.0s - => [traffic-gen-1 internal] load build definition from Dockerfile.traffic-gen 0.0s - => => transferring dockerfile: 208B 0.0s - => [traffic-gen-2 internal] load metadata for docker.io/library/alpine:3.20 1.9s - => [traffic-gen-1 internal] load .dockerignore 0.0s - => => transferring context: 2B 0.0s - => [traffic-gen-1 internal] load build context 0.0s - => => transferring context: 500B 0.0s - => [traffic-gen-2 1/4] FROM docker.io/library/alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc 0.0s - => CACHED [traffic-gen-2 2/4] RUN apk add --no-cache curl bash 0.0s - => CACHED [traffic-gen-2 3/4] COPY ./scripts/traffic-gen.sh /entrypoint.sh 0.0s - => CACHED [traffic-gen-1 4/4] RUN chmod +x /entrypoint.sh 0.0s - => [traffic-gen-2] exporting to image 0.0s - => => exporting layers 0.0s - => => writing image sha256:e99e2c8bdbe3ed141026d06d3ececcadbc769171b650ac5359ff79940eb87889 0.0s - => => naming to docker.io/library/virtual_load_network-traffic-gen-2 0.0s - => [traffic-gen-1] exporting to image 0.0s - => => exporting layers 0.0s - => => writing image sha256:8528a890010cfd50b8c21c85de488da8e22070021e37709b7a7c9926c16629b2 0.0s - => => naming to docker.io/library/virtual_load_network-traffic-gen-1 0.0s - => [traffic-gen-2] resolving provenance for metadata file 0.0s - => [traffic-gen-1] resolving provenance for metadata file 0.0s -[+] up 8/8 - ✔ Image virtual_load_network-traffic-gen-2 Built 3.0s - ✔ Image virtual_load_network-traffic-gen-1 Built 3.0s - ✔ Network virtual_load_network_testnet1 Created 0.1s - ✔ Network virtual_load_network_testnet2 Created 0.1s - ✔ Container virtual_load_network-traffic-gen-2-2 Started 0.7s - ✔ Container virtual_load_network-traffic-gen-1-2 Started 0.7s - ✔ Container virtual_load_network-traffic-gen-2-1 Started 0.5s - ✔ Container virtual_load_network-traffic-gen-1-1 Started 0.5s -10.0.0.254 dev eth0 lladdr 1e:68:2a:65:96:99 ref 1 used 0/0/0 probes 4 REACHABLE - -*** Round 1, deleting 1 entries *** -*** Flush is complete after 1 round(s) *** -10.0.0.254 dev eth0 lladdr 1e:68:2a:65:96:99 ref 1 used 0/0/0 probes 4 REACHABLE - -*** Round 1, deleting 1 entries *** -*** Flush is complete after 1 round(s) *** -10.0.1.254 dev eth0 lladdr 36:e3:51:79:b3:3c ref 1 used 0/0/0 probes 4 REACHABLE - -*** Round 1, deleting 1 entries *** -*** Flush is complete after 1 round(s) *** -10.0.1.254 dev eth0 lladdr 36:e3:51:79:b3:3c ref 1 used 0/0/0 probes 4 REACHABLE - -*** Round 1, deleting 1 entries *** -*** Flush is complete after 1 round(s) *** + => [traffic-gen-1 internal] load build definition from Dockerfile.traffic-gen 0.0s + => => transferring dockerfile: 217B 0.0s + => [traffic-gen-2 internal] load build definition from Dockerfile.traffic-gen 0.0s + => => transferring dockerfile: 217B 0.0s + => [traffic-gen-1 internal] load metadata for docker.io/library/alpine:3.20 0.7s + => [traffic-gen-2 internal] load .dockerignore 0.0s + => => transferring context: 2B 0.0s + => [traffic-gen-1 internal] load .dockerignore 0.0s + => => transferring context: 2B 0.0s + => [traffic-gen-2 1/4] FROM docker.io/library/alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc 0.0s + => => resolve docker.io/library/alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc 0.0s + => [traffic-gen-2 internal] load build context 0.0s + => => transferring context: 71B 0.0s + => [traffic-gen-1 internal] load build context 0.0s + => => transferring context: 71B 0.0s + => CACHED [traffic-gen-1 2/4] RUN apk add --no-cache curl bash iproute2 0.0s + => CACHED [traffic-gen-1 3/4] COPY ./scripts/traffic-gen.sh /entrypoint.sh 0.0s + => CACHED [traffic-gen-1 4/4] RUN chmod +x /entrypoint.sh 0.0s + => [traffic-gen-2] exporting to image 0.0s + => => exporting layers 0.0s + => => exporting manifest sha256:2a5b7514893477d98546e00304bab0c5d3df5e691685413fdcd4c5d7f8a44b8e 0.0s + => => exporting config sha256:0820b0d32a4e01e75aa6f7fb86776dcb475af2f7513593360acbecaf13110a4c 0.0s + => => exporting attestation manifest sha256:47fe7dece22aee82abbc0e0fbad91b19cf38bea269f9c2a480c8d063a4b4391d 0.0s + => => exporting manifest list sha256:26a7229340f15481c403acf5125032cb86a70fcd48358256764d944d2a97a3c9 0.0s + => => naming to docker.io/library/virtual_load_network-traffic-gen-2:latest 0.0s + => => unpacking to docker.io/library/virtual_load_network-traffic-gen-2:latest 0.0s + => [traffic-gen-1] exporting to image 0.0s + => => exporting layers 0.0s + => => exporting manifest sha256:ba6645395e7cabb7c7babca4f5ae9e76586e524ee00b385e28b34ef6741d9964 0.0s + => => exporting config sha256:ab641cf5526440f2ce75212d98a7ce2c485b1a51d4215adfe0fc720d836c5774 0.0s + => => exporting attestation manifest sha256:4fd13be17b47ec34c6b497e30cbf496c9ebd542db2d7d1d03bee83e9dc8a4148 0.0s + => => exporting manifest list sha256:3f4c8d5955f564bcbf2b33634da0edab10ca65e99e21042d2ed671601e2078e1 0.0s + => => naming to docker.io/library/virtual_load_network-traffic-gen-1:latest 0.0s + => => unpacking to docker.io/library/virtual_load_network-traffic-gen-1:latest 0.0s + => [traffic-gen-1] resolving provenance for metadata file 0.0s + => [traffic-gen-2] resolving provenance for metadata file 0.0s +[+] Running 12/12 + ✔ traffic-gen-1 Built 0.0s + ✔ traffic-gen-2 Built 0.0s + ✔ Network virtual_load_network_testnet2 Created 0.0s + ✔ Network virtual_load_network_default Created 0.0s + ✔ Network virtual_load_network_testnet1 Created 0.0s + ✔ Container virtual_load_network-traffic-gen-2-2 Started 0.3s + ✔ Container virtual_load_network-pushgateway-1 Started 0.3s + ✔ Container virtual_load_network-traffic-gen-2-1 Started 0.4s + ✔ Container virtual_load_network-traffic-gen-1-2 Started 0.3s + ✔ Container virtual_load_network-traffic-gen-1-1 Started 0.4s + ✔ Container virtual_load_network-prometheus-1 Started 0.3s + ✔ Container virtual_load_network-grafana-1 Started 0.4s +Nothing to flush +Nothing to flush +Nothing to flush +Nothing to flush +Nothing to flush +Nothing to flush Test stand is running - Filter VMs: filter1 (pid 37833), filter2 (pid 37857) - Controller: pid 38215 - Traffic generators: 4 containers -``` -``` + Filter VMs: filter1 (pid 176929), filter2 (pid 177022) + Controller: pid 178189 + Traffic generators: 7 containers ``` -### 1. Start Controller +### 1. If you need to connect via SSH Connect to the controller VM via SSH: - ```bash ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.0.2.3 ``` -Set up internet access (required for Kaspersky API): - +Connect to the filter1 VM via SSH: ```bash -sh /mnt/shared/setup-inet.sh +ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.0.2.1 ``` -Start the controller: - +Connect to the filter2 VM via SSH: ```bash -cd /mnt/shared -KASPERSKY_API_KEY= ./controller -KASPERSKY_API_KEY=wyo915OXTCe5stpLCtc5Ww== ./controller +ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.0.2.2 ``` + ### 2. Load Filtering Policy -On the host, install dependencies and load a policy: +Detailed instructions for administering the policy are provided in grpc_server/admin/README.md +## Installing dependencies ```bash -cd admin -uv venv --python 3.12 .venv -source .venv/bin/activate -uv pip install -r requirements.txt -uv pip install "setuptools<75" -python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. admin_service.proto +pip install -r requirements.txt ``` -Edit `admin/.env` to point to the controller: - -``` -SERVER_HOST=10.0.2.3 -SERVER_PORT=50051 +## Generating proto files +```bash +python -m grpc_tools.protoc \ + --python_out=. \ + --grpc_python_out=. \ + -I . \ + admin_service.proto ``` -Edit `config.toml` with desired rules, then load: +## Example of a toml file with policy +```toml +[global.rules] # Глобальные правила для всех воркеров +block_categories = ["Gambling", "Weapons"] # Категории для блокировки +block_domains = ["youtube.com", "tiktok.com"] # Домены для блокировки +allow_domains = ["github.com", "stackoverflow.com"] # Разрешённые домены (приоритет над блокировкой) +block_ips = ["192.168.0.1", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"] # IP для блокировки (IPv4 и IPv6) +allow_ips = ["8.8.8.8"] # Разрешённые IP +ttl_ip = 604800 # TTL кэша IP +ttl_domain = 604800 # TTL кэша доменов +min_trust_level = 5 # Мин. уровень доверия + +[global.rules.block_by_trust] # Блокировка по уровню доверия +ENTERTAINMENT = 6 +NEWS = 4 + +[filters.filter_1] # Правила для воркера #1 +block_categories = ["Weapons", "Malware"] # Доп. категории к глобальным +block_domains = ["instagram.com"] # Доп. домены к глобальным +allow_domains = ["vk.com"] # Доп. разрешённые домены +min_trust_level = 0 # Переопределяет глобальный + +[filters.filter_1.block_by_trust] # Уровни доверия для воркера #1 +SOCIAL = 8 +ENTERTAINMENT = 7 + +[filters.filter_2] # Правила для воркера #2 +block_categories = ["Malware"] # Доп. категории к глобальным +allow_domains = ["github.com", "gitlab.com"] # Доп. разрешённые домены -```bash -python admin.py --file config.toml ``` -Expected output: -``` -(.venv) ➜ admin git:(test_stand) ✗ python admin.py --file config.toml -Config loaded -``` -``` -``` +### Send a new policy to the controller -### 3. Start Worker +```bash +python admin.py load --file .toml +``` -Connect to the filter VM via SSH: +### Get the current policy from the controller ```bash -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.0.2.1 # filter1 -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.0.2.2 # filter2 +python admin.py get --file .toml ``` -Start the worker: +### Enable/disable filtering on the worker ```bash -cd /mnt/shared -WORKER_ID=1 METRICS_GATEWAY_ADDRESS=10.0.2.254 METRICS_GATEWAY_PORT=9091 CONTROLLER_GRPC_ADDR=10.0.2.3:50051 DPDK_PORT_IN=eth0 DPDK_PORT_OUT=eth1 ./worker +python admin.py toogle --id 1 --on #id - worker id +python admin.py toogle --id 1 --off #id - worker id ``` -For `filter2`, use `WORKER_ID=2`. -To redirect logs to a shared file (viewable from host): +## 5. Monitoring + +### Conroller +Log monitoring ```bash -WORKER_ID=1 METRICS_GATEWAY_ADDRESS=10.0.2.254 METRICS_GATEWAY_PORT=9091 CONTROLLER_GRPC_ADDR=10.0.2.3:50051 DPDK_PORT_IN=eth0 DPDK_PORT_OUT=eth1 ./worker > /mnt/shared/filter1.log 2>&1 & +cd test/virtual_load_network/shared/logs +sudo tail -f controller.log ``` -Expected output: +### Worker 1 + +Log monitoring +```bash +cd test/virtual_load_network/shared/logs +sudo tail -f worker1.log ``` -root@qemuriscv64:~# cd /mnt/shared -root@qemuriscv64:/mnt/shared# WORKER_ID=1 METRICS_GATEWAY_ADDRESS=10.0.2.254 METRICS_GATEWAY_PORT=9091 CONTROLLER_GRPC_ADDR=10.0.2.3:50051 DPDK_PORT_IN=eth0 -DPDK_PORT_OUT=eth1 ./worker -[2026-04-23 16:46:28.534] [info] Initialize MetricsCollector with 10.0.2.254:9091 -[2026-04-23 16:46:28.823] [info] gRPC channel created to 10.0.2.3:50051 -[2026-04-23 16:46:28.826] [info] Signal handlers registered -[2026-04-23 16:46:28.828] [info] Worker 1 requests policy -[2026-04-23 16:46:29.321] [info] Policy received -EAL: Detected CPU lcores: 2 -EAL: Detected NUMA nodes: 1 -EAL: Detected shared linkage of DPDK -EAL: Multi-process socket /var/run/dpdk/rte/mp_socket -EAL: Selected IOVA mode 'PA' -EAL: TSC using RISC-V rdtime. -TELEMETRY: No legacy callbacks, legacy socket not created -libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. -libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. -libbpf: elf: skipping unrecognized data section(8) .xdp_run_config -libbpf: elf: skipping unrecognized data section(9) xdp_metadata -libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. -libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. -libbpf: elf: skipping unrecognized data section(7) xdp_metadata -libbpf: elf: skipping unrecognized data section(7) xdp_metadata -libxdp: No bpffs found at /sys/fs/bpf -libxdp: Can't use dispatcher without a working bpffs -libxdp: Falling back to loading single prog without dispatcher -libbpf: elf: skipping unrecognized data section(7) xdp_metadata -Port 0 initialized -libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. -libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. -libbpf: elf: skipping unrecognized data section(8) .xdp_run_config -libbpf: elf: skipping unrecognized data section(9) xdp_metadata -libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. -libbpf: Attribute of type 0x2a found multiple times in message, previous attribute is being ignored. -libbpf: elf: skipping unrecognized data section(7) xdp_metadata -libbpf: elf: skipping unrecognized data section(7) xdp_metadata -libxdp: No bpffs found at /sys/fs/bpf -libxdp: Can't use dispatcher without a working bpffs -libxdp: Falling back to loading single prog without dispatcher -libbpf: elf: skipping unrecognized data section(7) xdp_metadata -Port 1 initialized -Port 2 initialized -Port 0 started -Port 1 started -Port 2 started -[INFO] Loaded 0 records from SQLite, 0 expired skipped -[2026-04-23 16:46:30.684] [info] DPDK initialized: in_port=0, out_port=1 -[2026-04-23 16:46:31.204] [info] Worker 1 classifying domain 'betboom.ru' -[2026-04-23 16:46:33.307] [info] Domain 'betboom.ru' classified as category 'Gambling' with trust level 0 -This site has a locked category[INFO] Packet without dns request + +Monitoring the port on which packets are received +```bash +sudo tcpdump -i tap-f1-in port 53 ``` + +Monitoring the port from which packets are emitted +```bash +sudo tcpdump -i tap-f1-out port 53 ``` + +### Worker 2 + +Log monitoring +```bash +cd test/virtual_load_network/shared/logs +sudo tail -f worker2.log ``` +Monitoring the port on which packets are received +```bash +sudo tcpdump -i tap-f2-in port 53 +``` -## 5. Monitoring -```sh -sudo tcpdump -i tap-f1-out port 53 # output packets -sudo tcpdump -i tap-f1-in port 53 # input packets +Monitoring the port from which packets are emitted +```bash +sudo tcpdump -i tap-f2-out port 53 ``` + ## 4. Stop Test Stand ```bash -cd test/virtual_load_network -make stop +cd test/virtual_load_network/scripts +sudo ./stop.sh ``` From 5a5a82bc7d9652af6c9fc039491488b975b6fa72 Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Thu, 28 May 2026 01:38:04 +0300 Subject: [PATCH 3/7] docs: fix api python commands --- wiki/using_test_stand.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wiki/using_test_stand.md b/wiki/using_test_stand.md index ae4ba80..9264bed 100644 --- a/wiki/using_test_stand.md +++ b/wiki/using_test_stand.md @@ -237,14 +237,14 @@ python admin.py load --file .toml ### Get the current policy from the controller ```bash -python admin.py get --file .toml +python admin.py get --save .toml ``` ### Enable/disable filtering on the worker ```bash -python admin.py toogle --id 1 --on #id - worker id -python admin.py toogle --id 1 --off #id - worker id +python admin.py toggle --id 1 --on #id - worker id +python admin.py toggle --id 1 --off #id - worker id ``` From e79f72564fd737fc64c17290225b71a81dde1565 Mon Sep 17 00:00:00 2001 From: stepanrodimanov Date: Sun, 5 Jul 2026 18:51:37 +0300 Subject: [PATCH 4/7] feat: update main readme --- README.md | 48 ++++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 9100119..461cc87 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,26 @@ -# grpc_server -GRPC server with workers +# Network traffic filtering system +The network traffic filtering system consists of three components: -# Controller -A controller for distributed task processing via Unix sockets +- The controller is a management node that stores policies, processes requests for domain and IP address classification, and accesses external categorization providers. -## Requirements -- Go 1.20+ +- Worker is a filtering node that intercepts and filters traffic through DPDK. -## Build and Launch with Bazel -### Controller (bazel) -To build binaries for the x86_64 architecture: -`bazel build --platforms=@rules_go//go/toolchain:linux_amd64 //cmd/grpc_server:grpc_server` +- Admin CLI is a console interface for managing policies and enabling/disabling filtering. -To build binary files for the RISC-V architecture: -`bazel build --platforms=@rules_go//go/toolchain:linux_riscv64 //cmd/grpc_server:grpc_server` +The Worker is located between the client's subnet and the central router, intercepts all incoming packets and decides whether to skip or block based on the policies received from the controller. -To run locally for x86_64, need to use: -`bazel run //cmd/grpc_server:grpc_server` +## Assembly and launch on a test stand -p.s. before the new build, you should use the commands: -`rm -rf ~/.cache/bazel` and `bazel clean --expunge` +To run a full-fledged test stand with RISC-V virtual machines that emulate the operation of filters and a controller, see the instructions: +[test bench launch](wiki/using_test_stand.md). -### Worker (bazel) +## Assembly and launch on real boards -To build binaries for the x86_64 architecture (cross compile x86_64 to x86_64): -`bazel build //:worker --extra_toolchains=//toolchains/x86_64:cc_toolchain_for_linux_x86_64 --platforms=//platforms:x86_64_linux` -To build binary files for the RISC-V architecture (cross compile x86_64 to riscv): -`bazel build //:worker --extra_toolchains=//toolchains/x86_64:cc_toolchain_for_linux_x86_64 --platforms=//platforms:x86_64_linux` +## Policy management -Native build: -`bazel build //:worker` +Detailed instructions on how to administer policies via the CLI, a description of the TOML configuration, and a list of categories: [policy management](wiki/admin_client.md). -To run locally -`bazel run //:worker` or `./bazel-bin/worker` +## Worker-Controller Communication Protocol -### Controller (go build) - -To build binaries: -`make` - -To run locally: -`make run-server` - -p.s. Go build instruction is located in controller/Makefile, also generate proto files is needed (this instruction is also in Makefile). +The communication protocol between Worker (C++) and Controller (Go) is based on gRPC with Protocol Buffers for message serialization. The interaction is one-way: Worker always acts as client, Controller as server. [Full protocol description](wiki/worker_controller_communication_protocol.md). From 5f438d07914b415a4bb047b8f4948b0379618174 Mon Sep 17 00:00:00 2001 From: stepanrodimanov Date: Sun, 5 Jul 2026 18:52:31 +0300 Subject: [PATCH 5/7] fix: fix readme admin --- admin/README.md | 119 ------------------------------------------- wiki/admin_client.md | 119 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 119 deletions(-) delete mode 100644 admin/README.md create mode 100644 wiki/admin_client.md diff --git a/admin/README.md b/admin/README.md deleted file mode 100644 index 85f7c69..0000000 --- a/admin/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Admin Client - -## Установка зависимостей -```bash -pip install -r requirements.txt -``` - -## Генерация proto-файлов -```bash -python -m grpc_tools.protoc \ - --python_out=. \ - --grpc_python_out=. \ - -I . \ - admin_service.proto -``` - -## Запуск контроллера -```bash -cd ../controller -bazel run //cmd/grpc_server:grpc_server -``` - -## Пример toml файла с политикой -```toml -[global.rules] # Глобальные правила для всех воркеров -block_categories = ["Gambling", "Weapons"] # Категории для блокировки -block_domains = ["youtube.com", "tiktok.com"] # Домены для блокировки -allow_domains = ["github.com", "stackoverflow.com"] # Разрешённые домены (приоритет над блокировкой) -block_ips = ["192.168.0.1", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"] # IP для блокировки (IPv4 и IPv6) -allow_ips = ["8.8.8.8"] # Разрешённые IP -ttl_ip = 604800 # TTL кэша IP -ttl_domain = 604800 # TTL кэша доменов -min_trust_level = 5 # Мин. уровень доверия - -[global.rules.block_by_trust] # Блокировка по уровню доверия -ENTERTAINMENT = 6 -NEWS = 4 - -[filters.filter_1] # Правила для воркера #1 -block_categories = ["Weapons", "Malware"] # Доп. категории к глобальным -block_domains = ["instagram.com"] # Доп. домены к глобальным -allow_domains = ["vk.com"] # Доп. разрешённые домены -min_trust_level = 0 # Переопределяет глобальный - -[filters.filter_1.block_by_trust] # Уровни доверия для воркера #1 -SOCIAL = 8 -ENTERTAINMENT = 7 - -[filters.filter_2] # Правила для воркера #2 -block_categories = ["Malware"] # Доп. категории к глобальным -allow_domains = ["github.com", "gitlab.com"] # Доп. разрешённые домены - -``` - -### Возможные категории -- Adult Content (Pornography and adult entertainment) -- Gambling (Online casinos, betting, lotteries) -- Drugs (Illegal substances) -- Violence (Violent content) -- Weapons (Firearms, explosives) -- Malware (Viruses and malicious software) -- Social media -- Hate Speech (Discrimination, extremism) -- Anonymizers (VPN, proxies to bypass blocks) -- Online shop - - -## Запуск клиента -### Отправить новую политику на контроллер - -```bash -python admin.py load --file .toml -``` - -### Получить текущую политику с контроллера - -```bash -python admin.py get --save .toml -``` - -### Включить/отключить фильтрацию на воркере - -```bash -python admin.py toggle --id 1 --on #id - worker id -python admin.py toggle --id 1 --off #id - worker id -``` - - -## Описание применения политики из конфига -Политика для каждого фильтра формируется путём объединения глобальных правил и индивидуальных настроек конкретного фильтра. - -### Глобальный уровень -Сначала загружается общий TOML-файл конфигурации. В нём есть раздел [global.rules], который содержит правила, применяемые ко всем фильтрам без исключения: -- Какие категории сайтов блокировать всегда -- Какие домены и ip в чёрном списке -- Какие домены и ip в белом списке -- Пороги доверия для разных категорий -- Минимальный уровень доверия по умолчанию -- Время жизни кэша для ip и домена - -Эти правила образуют глобальную политику, одинаковую для всех фильтров. - -### Локальный уровень -В том же файле есть раздел [filters], где для каждого фильтра можно задать свои правила - например, [filters.filter_1], [filters.filter_2] и т.д. - -Локальные правила не заменяют глобальные, а дополняют и уточняют их: -- Если в локальных правилах указаны дополнительные категории для блокировки, они добавляются к глобальным -- Если указаны дополнительные домены или ip, они добавляются в соответствующие списки -- Пороги доверия для категорий могут переопределяться (если для той же категории указано новое значение) -- Минимальный уровень доверия может быть изменён для конкретного фильтра - -### Финальная политика -Когда фильтр запрашивает политику, контроллер делает следующее: -1. Берёт глобальные правила как основу -2. Накладывает поверх них локальные правила конкретного фильтра -3. Объединяет списки (добавляет новые элементы, не удаляя старые) -4. Перезаписывает отдельные параметры, если они заданы локально - -Таким образом, каждый фильтр получает индивидуальную политику, которая наследует общие правила, но может быть более строгой или более мягкой в зависимости от настроек его сегмента сети. diff --git a/wiki/admin_client.md b/wiki/admin_client.md new file mode 100644 index 0000000..28bc163 --- /dev/null +++ b/wiki/admin_client.md @@ -0,0 +1,119 @@ +# Admin Client + +## Installing dependencies +```bash +pip install -r requirements.txt +``` + +## Proto file generation +```bash +python -m grpc_tools.protoc \ + --python_out=. \ + --grpc_python_out=. \ + -I . \ + admin_service.proto +``` + +## Launching the controller +```bash +cd ../controller +bazel run //cmd/grpc_server:grpc_server +``` + +## Example of a toml file with a policy +```toml +[global.rules] # Global rules for all workers +block_categories = ["Gambling", "Weapons"] # Categories to block +block_domains = ["youtube.com", "tiktok.com"] # Domains to block +allow_domains = ["github.com ", "stackoverflow.com "] # Allowed domains (priority over blocking) +block_ips = ["192.168.0.1", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"] # Blocking IP (IPv4 and IPv6) +allow_ips = ["8.8.8.8"] # Allowed IP +ttl_ip = 604800 # TTL of the IP cache +ttl_domain = 604800 # TTL of the domain cache +min_trust_level = 5 # Min. the level of trust + +[global.rules.block_by_trust] # Blocking by trust level +ENTERTAINMENT = 6 +NEWS = 4 + +[filters.filter_1] # Rules for worker #1 +block_categories = ["Weapons", "Malware"] # Additional categories to global ones +block_domains = ["instagram.com"] # Additional domains to global +allow_domains = ["vk.com "] # Additional allowed domains +min_trust_level = 0 # Overrides the global + +[filters.filter_1.block_by_trust] # Trust levels for the worker #1 +SOCIAL = 8 +ENTERTAINMENT = 7 + +[filters.filter_2] # Rules for the worker #2 +block_categories = ["Malware"] # Additional categories to global +allow_domains = ["github.com ", "gitlab.com "] # Additional allowed domains + +``` + +### Possible categories +- Adult Content (Pornography and adult entertainment) +- Gambling (Online casinos, betting, lotteries) +- Drugs (Illegal substances) +- Violence (Violent content) +- Weapons (Firearms, explosives) +- Malware (Viruses and malicious software) +- Social media +- Hate Speech (Discrimination, extremism) +- Anonymizers (VPN, proxies to bypass blocks) +- Online shop + + +## Launching the client +### Send a new policy to the controller + +```bash +python admin.py load --file .toml +``` + +### Get the current policy from the controller + +```bash +python admin.py get --save .toml +``` + +### Enable/disable filtering on the worker + +```bash +python admin.py toggle --id 1 --on #id - worker id +python admin.py toggle --id 1 --off #id - worker id +``` + + +## Description of the policy application from the config +The policy for each filter is formed by combining global rules and individual settings of a specific filter. + +### Global level +First, the shared TOML configuration file is loaded. It has a [global.rules] section that contains rules that apply to all filters without exception.: +- Which categories of sites are always blocked +- Which domains and ip addresses are blacklisted +- Which domains and ip addresses are on the whitelist +- Trust thresholds for different categories +- Minimum trust level by default +- Cache lifetime for ip and domain + +These rules form a global policy that is the same for all filters. + +### Local level +There is a [filters] section in the same file where you can set your own rules for each filter, for example, [filters.filter_1], [filters.filter_2], etc. + +Local rules do not replace global ones, but complement and clarify them.: +- If the local rules specify additional categories for blocking, they are added to the global ones. +- If additional domains or ip addresses are specified, they are added to the corresponding lists. +- Trust thresholds for categories can be redefined (if a new value is specified for the same category) +- The minimum trust level can be changed for a specific filter + +### Final policy +When the filter requests a policy, the controller does the following: +1. Takes global rules as a basis +2. Imposes local rules of a specific filter on top of them. +3. Combines lists (adds new items without deleting old ones) +4. Overwrites individual parameters if they are set locally. + +Thus, each filter receives an individual policy that inherits the general rules, but may be stricter or more lenient depending on the settings of its network segment. From 291368298d30e1405f5f456dd821923d3e484de0 Mon Sep 17 00:00:00 2001 From: stepanrodimanov Date: Sun, 5 Jul 2026 18:53:02 +0300 Subject: [PATCH 6/7] fix: fix readme worker_communication_protocol --- wiki/worker_communication_protocol.md | 191 ++++++++------------------ 1 file changed, 56 insertions(+), 135 deletions(-) diff --git a/wiki/worker_communication_protocol.md b/wiki/worker_communication_protocol.md index 8a2c095..1b108b3 100644 --- a/wiki/worker_communication_protocol.md +++ b/wiki/worker_communication_protocol.md @@ -1,154 +1,75 @@ -# Worker-Controller communication protocol +## Worker-Controller Communication Protocol +The communication protocol between Worker and Controller is based on gRPC using Protocol Buffers to serialize messages. The interaction is one-way: the Worker always acts as a client, the Controller as a server. The choice of gRPC is justified by the high expected hit rate in the local cache of the Worker, which reduces the frequency of network calls to a minimum. -Будущий протокол коммуникации воркера и контроллера. +### Interface Specification -## Регистрация +The interaction contract is described in the file `worker/communication.proto`: -Код protobuf: ```proto -enum PulseType { - PULSE_REGISTER = 0; - PULSE_OK = 1; - PULSE_FETCH_ME = 2; - PULSE_SHUTDOWN = 3; -} - -enum ControllerError { - CTRL_ERR_OK = 0; - CTRL_ERR_UNKNOWN_TYPE = 1; - CTRL_ERR_UNKNOWN_ID = 2; - CTRL_ERR_FAILED = 3; +service DataService { + rpc GetPolicy(GetPolicyRequest) returns (GetPolicyResponse); + rpc Classify(ClassifyRequest) returns (ClassifyResponse); } +``` -message WorkerPulse { - PulseType type = 1; - uint64 worker_id = 2; - uint64 task_id = 3; - uint64 next_pulse = 4; -} +### GetPolicy - getting the filtering policy +Worker requests an up-to-date filtering policy at a specified frequency. -message PulseResponse { - ControllerError error = 1; - optional uint64 worker_id = 2; +Request +```proto +message GetPolicyRequest { +uint64 worker_id = 1; // Worker ID + uint64 config_version = 2; // the current configuration version for the Worker } ``` -Контроллер слушает Unix-сокет `/run/controller/main.sock`. При подключении получает от воркера сообщение WorkerPulse, посылает PulseResponse и завершает соединение. - -### REGISTER: - -Поля *worker_id* и *task_id* игнорируются. Контроллер регистрирует воркера. При ошибке посылает соответствующее сообщение. Иначе посылает сообщение OK и передаёт воркеру его worker_id. - -Если ошибок не произошло, то контроллер присваивает этому воркеру статус BOOTING. Воркер в это время создаёт `/run/controller/WORKER_ID.sock` и слушает подключения. -Контроллер переводит воркера из состояния *BOOTING* в состояние *FREE* как только получает от воркера очередной WorkerPulse. Поэтому воркер после успешного создания сокета посылает *OK WorkerPulse*. - -### OK: - -Оповещает контроллера о том, что воркер жив. Поле *next_pulse* -- время в секундах до следующего WorkerPulse. Если контроллер за это время не получает очередной WorkerPulse от этого воркера, то воркер считается "мёртвым" и удаляется из списка живых воркеров. - -При сообщениях *OK*, *FETCH_ME*, *SHUTDOWN*, если воркера с таким *worker_id* не существует (не зарегистрирован или был зарегистрирован и позже удалён), то посылается соответствующее сообщение об ошибке (UNKNOWN_ID). Если в интересах воркера продолжить коммуникацию с воркером, то ему стоит зарегистрироваться заново. - -Ошибка UNKNOWN_TYPE посылается в случае некорректного PulseType. - -Ошибка FAILED посылается в случае любой другой ошибки. - - -Поле *task_id* содержит ID выполняемого воркером задания. Нулевой *task_id* обозначает отсутствие задания (нумерация заданий начинается с единицы). Если оно не совпадает с информацией, которой владеет контроллер, то контроллер посылает воркеру сообщение *RESTART* (см. Коммунакация по выделенному сокету). - -В PulseResponse поле *worker_id* не задаётся и игнорируется воркером. - -### FETCH_ME: - -Аналогично `OK`, но ещё дополнительно уведомляет контроллера о том, что воркер хочет послать контроллеру сообщение (см. Коммуникация по выделенному сокету, *FETCH*) - -### SHUTDOWN: - -Поля *task_id* и *next_pulse* игнорируются. Воркер объявляет себя "мёртвым", т.е. завершает текущую сессию. Контроллер должен удалить воркера из списка живых воркеров. - -## Получение заданий - -Код protobuf: +Answer ```proto -enum ControlType { - CTRL_RESTART = 0; - CTRL_FETCH = 1; - CTRL_SET_TASK = 2; - CTRL_GET_STATUS = 3; +message GetPolicyResponse { + enum Result { +POLICY_PROVIDED = 0; // policy updated, policy field filled in + POLICY_UNCHANGED = 1; // the policy is current, the policy field is empty + } + Result result = 1; + WorkerPolicy policy = 2; // full filtering policy +bool filtering_enabled = 3; // filter on/off flag } +``` -message ControlMsg { - ControlType type = 1; - uint64 extra_size = 2; - optional uint64 task_id = 3; +The structure of the WorkerPolicy +``` proto +message WorkerPolicy { +repeated string block_categories = 1; // blocked categories (for example, ["Gambling", "Weapons"]) +map block_by_trust = 2; // categories with min. the level of trust (for example, {"ENTERTAINMENT": 6}) +repeated string block_domains = 3; // blocked domains + repeated string allow_domains = 4; // allowed domains (priority over blocking) + repeated string block_ips = 5; // blocked ips (IPv4 and IPv6) +repeated string allow_ips = 6; // allowed ips + int32 min_trust_level = 7; // global minimum trust level + int32 ttl_ip = 8; // TTL of the IP cache in seconds + int32 ttl_domain = 9; // TTL of the domain cache in seconds + uint64 config_version = 10; // configuration version +of google.protobuf.Struct extra = 11; // additional parameters (arbitrary JSON) } +``` -enum WorkerError { - WORKER_ERR_OK = 0; - WORKER_ERR_NO_FETCH = 1; - WORKER_ERR_BUSY = 2; - WORKER_ERR_TASK_FAILED = 3; - WORKER_ERR_FAILED = 4; -} +### Classify - classification of a domain or IP address +Appointment +Worker calls the method when the local cache is missed - when the domain or IP address extracted from the packet is not found in either the domain or IP cache. -message WorkerResponse { - WorkerError error = 1; - uint64 task_id = 2; - uint64 extra_size = 3; +Request +``` proto +message ClassifyRequest { +uint64 worker_id = 1; // Worker ID + string type = 2; // type of the classified object: "domain" or "ip" + string target = 3; // string representation of the domain or IP address } ``` -Воркер слушает `/run/controller/WORKER_ID.sock`. При подключении получает от контроллера сообщение ControlMsg. За ControlMsg следует последовательность байт длиной *extra_size*. Она тоже считывается. Далее воркер отправляет в ответ *WorkerResponse* и последовательность байт длиной *extra_size*. - -Как и в случае с *ControllerError*, *WorkerError::FAILED* обозначает любую ошибку, не подходяющую по критериям к другим видам. - -Если контроллер получает такую ошибку, значит воркера надо удалить из списка живых воркеров. Если воркер выполняет задание, то это задание надо переназначить на другого воркера. - -В случае любой ошибки extra данные либо не содержат ничего (*extra_size* = 0), либо содержат более детальное сообщение об ошибке. - -### RESTART: - -Контроллер требует воркера завершить работу. Extra данные игнорируются воркером. - -Контроллер игнорирует любые ошибки и удаляет воркера из списка живых. // TODO: FIX? - -Поле *ControlMsg::task_id* игнорируется. - -### FETCH: - -Контроллер запрашивает от воркера сообщение. Extra данные игнорируются воркером. -Контроллер в ответ посылает WorkerResponse с текущим *task_id* без extra данных. - -Если контроллер получает *NO_FETCH*, то с воркера нечего считывать. - -Если контроллер получает *TASK_FAILED*, значит воркеру не удалось выполнить задание. Контроллер снимает задание с этого и передает его другому. - -В случае успеха Extra данные содержат решение задания. - -Поле *ControlMsg::task_id* игнорируется. - -### SET_TASK: - -Контроллер устанавливает задание воркеру. Задание передаётся как extra данные. ID задания указан в *task_id*. - -Если контроллер получает *BUSY*, то воркер уже занят. В таком случае задание назначается на другого воркера. - - -### GET_STATUS: - -Контроллер запрашивает состояние воркера. Воркер посылает WorkerResponse без extra данных, указывает текущий *task_id*. - - -## ABI - -Формат передачи сообщений: - -| Offset | Type | Name | Meaning | -| ------ | ---------- | ---- | --------------------------- | -| 0 | uint64 | size | Message size | -| 8 | byte[size] | msg | Serialized protobuf message | - - -## TODO - -* Add full english translation (google translate does funny things) -* Protobuf messages do not have a fixed size. This is inconvenient. The *size* field could be removed if messages had a fixed size. +Answer +``` proto +message ClassifyResponse { + repeated string categories = 1; // list of categories + int32 trust_level = 2; // minimum level of trust among categories +} +``` From 9196d08183cebd054a148c7958aca34f94bac44a Mon Sep 17 00:00:00 2001 From: stepanrodimanov Date: Sun, 5 Jul 2026 18:55:13 +0300 Subject: [PATCH 7/7] fix: fix main readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 461cc87..5c8a664 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,4 @@ Detailed instructions on how to administer policies via the CLI, a description o ## Worker-Controller Communication Protocol -The communication protocol between Worker (C++) and Controller (Go) is based on gRPC with Protocol Buffers for message serialization. The interaction is one-way: Worker always acts as client, Controller as server. [Full protocol description](wiki/worker_controller_communication_protocol.md). +The communication protocol between Worker and Controller is based on gRPC with Protocol Buffers for message serialization. The interaction is one-way: Worker always acts as client, Controller as server. [Full protocol description](wiki/worker_controller_communication_protocol.md).