feat: smart zomboid traffic filtering with packet-size detection
Replace per-IP hashlimit with smarter filtering that distinguishes legitimate players from scanner bots based on packet behavior: - Players send varied packet sizes (53, 37, 1472 bytes) - Scanners only send 53-byte query packets New firewall rule chain: - Priority 2: Mark + ACCEPT non-query packets (verifies player) - Priority 3: ACCEPT queries from verified IPs (1 hour TTL) - Priority 4: LOG rate-limited queries from unverified IPs - Priority 5: DROP rate-limited queries (2 burst, then 1/hour) Also includes: - Fail2ban zomboid jail with tighter thresholds (5 retries/4h, 1w ban) - Graylog streams for zomboid-connections, zomboid-ratelimit, fail2ban - GeoIP pipeline enrichment for zomboid traffic - Fluent-bit inputs for ratelimit logs and fail2ban events - Remove Legendary Katana mod (Workshop 3418366499) - removed from Steam - Bump Immich to v2.5.0 - Fix fulfillr config (nil → null) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -291,6 +291,7 @@
|
||||
changed_when: "'already' not in firewall_result.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
- name: add firewall rule to log zomboid connections (runtime)
|
||||
become: true
|
||||
@@ -300,6 +301,209 @@
|
||||
-j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
tags: firewall
|
||||
|
||||
# =============================================================================
|
||||
# Add logging for port 16262 (mirrors existing 16261 logging)
|
||||
# =============================================================================
|
||||
- name: add firewall rule to log zomboid connections on 16262
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0
|
||||
-p udp --dport 16262 -m conntrack --ctstate NEW
|
||||
-j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4
|
||||
register: firewall_result_16262
|
||||
changed_when: "'already' not in firewall_result_16262.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
- name: add firewall rule to log zomboid connections on 16262 (runtime)
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --direct --add-rule ipv4 filter INPUT 0
|
||||
-p udp --dport 16262 -m conntrack --ctstate NEW
|
||||
-j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
tags: firewall
|
||||
|
||||
# =============================================================================
|
||||
# Zomboid Rate Limiting and Query Flood Protection
|
||||
# =============================================================================
|
||||
# These rules mitigate Steam server query floods while allowing legitimate play.
|
||||
# Query packets are typically 53 bytes; game traffic is larger and sustained.
|
||||
#
|
||||
# Rule priority: 0=logging (existing), 1=allow established, 2=rate limit queries
|
||||
|
||||
# Allow established/related connections without rate limiting
|
||||
# This ensures active players aren't affected by query rate limits
|
||||
- name: allow established zomboid connections on 16261
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1
|
||||
-p udp --dport 16261 -m conntrack --ctstate ESTABLISHED,RELATED
|
||||
-j ACCEPT
|
||||
register: established_result
|
||||
changed_when: "'already' not in established_result.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
- name: allow established zomboid connections on 16262
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1
|
||||
-p udp --dport 16262 -m conntrack --ctstate ESTABLISHED,RELATED
|
||||
-j ACCEPT
|
||||
register: established_result_16262
|
||||
changed_when: "'already' not in established_result_16262.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
# =============================================================================
|
||||
# Smart Zomboid Traffic Filtering (Packet-Size Based)
|
||||
# =============================================================================
|
||||
# Distinguishes legitimate players from scanner bots:
|
||||
# - Players send varied packet sizes (53, 37, 1472 bytes)
|
||||
# - Scanners only send 53-byte query packets
|
||||
#
|
||||
# Rule priority:
|
||||
# 0 = LOG all (existing above)
|
||||
# 1 = ACCEPT established (existing above)
|
||||
# 2 = Mark + ACCEPT non-query packets (verifies player)
|
||||
# 3 = ACCEPT queries from verified IPs
|
||||
# 4 = LOG rate-limited queries from unverified IPs
|
||||
# 5 = DROP rate-limited queries from unverified IPs
|
||||
|
||||
# Priority 2: Mark IPs sending non-query packets as verified (1 hour TTL)
|
||||
# Any packet NOT 53 bytes proves actual connection attempt
|
||||
- name: mark verified players on 16261 (non-query packets)
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 2
|
||||
-p udp --dport 16261 -m conntrack --ctstate NEW
|
||||
-m length ! --length 53
|
||||
-m recent --name zomboid_verified --set
|
||||
-j ACCEPT
|
||||
register: verify_result
|
||||
changed_when: "'already' not in verify_result.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
- name: mark verified players on 16262 (non-query packets)
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 2
|
||||
-p udp --dport 16262 -m conntrack --ctstate NEW
|
||||
-m length ! --length 53
|
||||
-m recent --name zomboid_verified --set
|
||||
-j ACCEPT
|
||||
register: verify_result_16262
|
||||
changed_when: "'already' not in verify_result_16262.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
# Priority 3: Allow queries from verified players (within 1 hour)
|
||||
- name: allow queries from verified players on 16261
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 3
|
||||
-p udp --dport 16261 -m conntrack --ctstate NEW
|
||||
-m length --length 53
|
||||
-m recent --name zomboid_verified --rcheck --seconds 3600
|
||||
-j ACCEPT
|
||||
register: verified_query_result
|
||||
changed_when: "'already' not in verified_query_result.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
- name: allow queries from verified players on 16262
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 3
|
||||
-p udp --dport 16262 -m conntrack --ctstate NEW
|
||||
-m length --length 53
|
||||
-m recent --name zomboid_verified --rcheck --seconds 3600
|
||||
-j ACCEPT
|
||||
register: verified_query_result_16262
|
||||
changed_when: "'already' not in verified_query_result_16262.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
# Priority 4: LOG rate-limited queries from unverified IPs
|
||||
# Very aggressive: 2 burst, then 1 per hour
|
||||
# Note: Uses same hashlimit name as DROP rule to share bucket
|
||||
- name: log rate-limited queries from unverified IPs on 16261
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 4
|
||||
-p udp --dport 16261 -m conntrack --ctstate NEW
|
||||
-m length --length 53
|
||||
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
||||
--hashlimit-mode srcip --hashlimit-name zomboid_query_16261
|
||||
--hashlimit-htable-expire 3600000
|
||||
-j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4
|
||||
register: unverified_log_result
|
||||
changed_when: "'already' not in unverified_log_result.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
- name: log rate-limited queries from unverified IPs on 16262
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 4
|
||||
-p udp --dport 16262 -m conntrack --ctstate NEW
|
||||
-m length --length 53
|
||||
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
||||
--hashlimit-mode srcip --hashlimit-name zomboid_query_16262
|
||||
--hashlimit-htable-expire 3600000
|
||||
-j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4
|
||||
register: unverified_log_result_16262
|
||||
changed_when: "'already' not in unverified_log_result_16262.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
# Priority 5: DROP rate-limited queries from unverified IPs
|
||||
# Note: Uses same hashlimit name as LOG rule to share bucket
|
||||
- name: drop rate-limited queries from unverified IPs on 16261
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 5
|
||||
-p udp --dport 16261 -m conntrack --ctstate NEW
|
||||
-m length --length 53
|
||||
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
||||
--hashlimit-mode srcip --hashlimit-name zomboid_query_16261
|
||||
--hashlimit-htable-expire 3600000
|
||||
-j DROP
|
||||
register: unverified_drop_result
|
||||
changed_when: "'already' not in unverified_drop_result.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
- name: drop rate-limited queries from unverified IPs on 16262
|
||||
become: true
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 5
|
||||
-p udp --dport 16262 -m conntrack --ctstate NEW
|
||||
-m length --length 53
|
||||
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
||||
--hashlimit-mode srcip --hashlimit-name zomboid_query_16262
|
||||
--hashlimit-htable-expire 3600000
|
||||
-j DROP
|
||||
register: unverified_drop_result_16262
|
||||
changed_when: "'already' not in unverified_drop_result_16262.stderr"
|
||||
failed_when: false
|
||||
notify: restart firewalld
|
||||
tags: firewall
|
||||
|
||||
# World reset is now triggered via Discord bot -> systemd path unit
|
||||
# See zomboid-world-reset.path and zomboid-world-reset.service
|
||||
|
||||
@@ -54,9 +54,9 @@
|
||||
- import_tasks: containers/home/photos.yml
|
||||
vars:
|
||||
db_image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
|
||||
ml_image: ghcr.io/immich-app/immich-machine-learning:v2.4.1
|
||||
ml_image: ghcr.io/immich-app/immich-machine-learning:v2.5.0
|
||||
redis_image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8
|
||||
image: ghcr.io/immich-app/immich-server:v2.4.1
|
||||
image: ghcr.io/immich-app/immich-server:v2.5.0
|
||||
tags: photos
|
||||
|
||||
- import_tasks: containers/home/cloud.yml
|
||||
|
||||
Reference in New Issue
Block a user