Only track bannable (globally-routable) source IPs
The ban logic is per source IP, so it only works where the daemon can see the real client. Behind Docker's default bridge networking every client is SNAT'd to the bridge gateway (a 172.16/12 address), so a single IP would stand in for the whole internet -- counting offenses against it would block everyone at once. Add is_bannable_address(): only globally-routable unicast addresses are tracked. Loopback, RFC1918 private, CGNAT (100.64/10), link-local, IPv6 unique-local, and multicast all return false. main.cpp decides trackability from the accepted endpoint and skips both the block check and offense recording for non-global sources. Net effect: banning works where the real IP is visible (FreeBSD jail via pf rdr; Docker with host networking) and is inert -- not catastrophic -- where it is not (Docker bridge). Document the Docker client-IP caveat: docker-compose.yml now defaults to host networking, with the rationale and alternatives in DOCKER.md.
This commit is contained in:
@@ -64,6 +64,50 @@ docker run -d \
|
||||
ghcr.io/waffle2k/finger:latest
|
||||
```
|
||||
|
||||
## Abuse protection & client IPs (important)
|
||||
|
||||
The daemon bans source IPs that rack up repeated failed lookups (scanners, SIP/
|
||||
HTTP probes, username guessers) -- see the "Abuse protection" section in the
|
||||
main [README.md](README.md). That protection is **per source IP**, so it only
|
||||
works if the container can see the *real* client IP.
|
||||
|
||||
Under Docker's **default bridge networking this is not the case**: published
|
||||
ports are NAT'd so every external client arrives with the bridge gateway as its
|
||||
source (e.g. `172.20.0.1`). The daemon would see one IP for the entire internet.
|
||||
By design it treats private/RFC1918 addresses as untrackable, so rather than
|
||||
blocking everyone at once, banning simply becomes **inert** under bridge
|
||||
networking.
|
||||
|
||||
To make abuse protection actually work in Docker, give the container the real
|
||||
client IP. In order of preference:
|
||||
|
||||
1. **Host networking (recommended).** Add `network_mode: host` to the service
|
||||
(and drop the `ports:` mapping -- it's ignored). The daemon then binds the
|
||||
host's port 79 directly and sees real client IPs. Non-root bind of port 79
|
||||
still works because Docker grants `CAP_NET_BIND_SERVICE` by default. This is
|
||||
what `docker-compose.yml` in this repo now uses.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
finger:
|
||||
image: ghcr.io/waffle2k/finger:latest
|
||||
network_mode: host
|
||||
volumes:
|
||||
- ./users:/var/finger/users
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
2. **macvlan network.** Give the container its own IP on the LAN. More setup,
|
||||
but keeps the container off host networking.
|
||||
|
||||
3. **Disable the userland proxy host-wide** (`/etc/docker/daemon.json`:
|
||||
`{"userland-proxy": false}`, then restart dockerd). iptables DNAT then
|
||||
preserves the source IP on published ports. This is a host-wide change that
|
||||
restarts every container on the host -- avoid it on busy multi-service hosts.
|
||||
|
||||
Note: bans are in-memory, so they reset when the container restarts -- the same
|
||||
trade-off as any single-process deployment.
|
||||
|
||||
## Docker Architecture
|
||||
|
||||
### Multi-stage Build
|
||||
|
||||
Reference in New Issue
Block a user