diff --git a/ansible/roles/podman/defaults/main.yml b/ansible/roles/podman/defaults/main.yml index b1919fd..86d8f58 100644 --- a/ansible/roles/podman/defaults/main.yml +++ b/ansible/roles/podman/defaults/main.yml @@ -83,6 +83,14 @@ zomboid_mods_excluded: mod_id: IconsInventory reason: Sophie recommends removing Icon Inventory +# Per-source-IP ceiling on 53-byte Steam/PZ query packets before they are +# logged and dropped. Set well above anything a real client produces -- opening +# the server browser or retrying a connection is a handful of queries, while a +# scanner flood is thousands. The previous 1/hour burst 2 was below normal play +# and made the server unreachable. See containers/home/zomboid.yml. +zomboid_query_rate_limit: 60/minute +zomboid_query_burst: 120 + # How long to keep PZ's own log archive under data/Logs. # # PZ rolls logs into dated directories and never prunes them; logrotate cannot diff --git a/ansible/roles/podman/tasks/containers/home/zomboid.yml b/ansible/roles/podman/tasks/containers/home/zomboid.yml index b6dbc2e..bdb90aa 100644 --- a/ansible/roles/podman/tasks/containers/home/zomboid.yml +++ b/ansible/roles/podman/tasks/containers/home/zomboid.yml @@ -438,71 +438,198 @@ 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 +# Priority 4/5: query flood limiting. +# +# The original thresholds were 1/hour burst 2 per source IP, on the theory that +# only scanners send 53-byte query packets and that real players would first be +# marked verified by the priority-2 rule when they sent something else. That +# premise is inverted: a client's *first* contact is a 53-byte query, so nobody +# can be verified before they query, and nobody can query more than twice an +# hour without being dropped. The counters were unambiguous -- 5 packets ever +# matched the verified-accept rule against 30,563 drops -- and fail2ban then +# banned the dropped players for a week each, several an hour, all residential +# IPs. The server was unreachable for everyone. +# +# Remove the old rules so hosts carrying them converge, then re-add the same +# shape at a threshold no real client reaches. Opening the server browser sends +# a handful of queries; a flood sends thousands. + +- name: remove broken log query rate-limit rule on 16261 (permanent) become: true ansible.builtin.command: > - firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 4 + firewall-cmd --permanent --direct --remove-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" + changed_when: false failed_when: false - notify: restart firewalld tags: firewall -- name: log rate-limited queries from unverified IPs on 16262 +- name: remove broken log query rate-limit rule on 16261 (runtime) become: true ansible.builtin.command: > - firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 4 - -p udp --dport 16262 -m conntrack --ctstate NEW + firewall-cmd --direct --remove-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_16262 + --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_16262 - changed_when: "'already' not in unverified_log_result_16262.stderr" + changed_when: false 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 +- name: remove broken drop query rate-limit rule on 16261 (permanent) become: true ansible.builtin.command: > - firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 5 + firewall-cmd --permanent --direct --remove-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" + changed_when: false failed_when: false - notify: restart firewalld tags: firewall -- name: drop rate-limited queries from unverified IPs on 16262 +- name: remove broken drop query rate-limit rule on 16261 (runtime) become: true ansible.builtin.command: > - firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 5 + firewall-cmd --direct --remove-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 + changed_when: false + failed_when: false + tags: firewall + +- name: remove broken log query rate-limit rule on 16262 (permanent) + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --remove-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 + changed_when: false + failed_when: false + tags: firewall + +- name: remove broken log query rate-limit rule on 16262 (runtime) + become: true + ansible.builtin.command: > + firewall-cmd --direct --remove-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 + changed_when: false + failed_when: false + tags: firewall + +- name: remove broken drop query rate-limit rule on 16262 (permanent) + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --remove-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" + changed_when: false + failed_when: false + tags: firewall + +- name: remove broken drop query rate-limit rule on 16262 (runtime) + become: true + ansible.builtin.command: > + firewall-cmd --direct --remove-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 + changed_when: false + failed_when: false + tags: firewall + +- name: log query floods 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 {{ zomboid_query_rate_limit }} + --hashlimit-burst {{ zomboid_query_burst }} + --hashlimit-mode srcip --hashlimit-name zomboid_query_16261 + --hashlimit-htable-expire 3600000 + -j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4 + register: qflood_log_16261 + changed_when: "'already' not in qflood_log_16261.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: drop query floods 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 {{ zomboid_query_rate_limit }} + --hashlimit-burst {{ zomboid_query_burst }} + --hashlimit-mode srcip --hashlimit-name zomboid_query_16261 + --hashlimit-htable-expire 3600000 + -j DROP + register: qflood_drop_16261 + changed_when: "'already' not in qflood_drop_16261.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: log query floods 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 {{ zomboid_query_rate_limit }} + --hashlimit-burst {{ zomboid_query_burst }} + --hashlimit-mode srcip --hashlimit-name zomboid_query_16262 + --hashlimit-htable-expire 3600000 + -j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4 + register: qflood_log_16262 + changed_when: "'already' not in qflood_log_16262.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: drop query floods 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 {{ zomboid_query_rate_limit }} + --hashlimit-burst {{ zomboid_query_burst }} + --hashlimit-mode srcip --hashlimit-name zomboid_query_16262 + --hashlimit-htable-expire 3600000 + -j DROP + register: qflood_drop_16262 + changed_when: "'already' not in qflood_drop_16262.stderr" failed_when: false notify: restart firewalld tags: firewall diff --git a/ansible/roles/podman/tasks/main.yml b/ansible/roles/podman/tasks/main.yml index dd6a37c..324ac88 100644 --- a/ansible/roles/podman/tasks/main.yml +++ b/ansible/roles/podman/tasks/main.yml @@ -123,7 +123,7 @@ - import_tasks: containers/home/gregtime.yml vars: - image: localhost/greg-time-bot:3.15.0 + image: localhost/greg-time-bot:3.16.1 tags: gregtime # Gated on zomboid_enabled (roles/podman/defaults/main.yml) so it can be taken