Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Caddy L4 cannot close non-SMTP probes on 25 and 587 without stalling banner-first clients

Open Beginner friendly
#410 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
65/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active

Research direction

Read deploy/k8s/caddy/Caddyfile at lines 30–48 and review plan step 2 of #409. Keep ports 25 and 587 proxying unconditionally rather than adding the proposed data-matcher close routes. Done means banner-first SMTP clients can still reach the backend without waiting for a matching timeout.

Written by the indexing model from the issue text.

Description

enhancement real sec

The planned Caddy close rules for non-SMTP probes drop every real SMTP client. Step 2 of #409 (@x http and @x tls plus close on 25 and 587) cannot ship as written.

What I measured

A layer4 data matcher before the proxy route makes caddy-l4 read the first bytes of the client before it forwards the connection. A router with a matcher that still needs data skips the matcherless fallback. Real SMTP clients wait for the banner, so no bytes arrive. The 3 s matching_timeout hits, and the server closes the connection instead of proxying it.

I built Caddy v2.11.7 with github.com/mholt/caddy-l4 and ran the planned config in front of a banner-first backend:

:18025 {
	@http http
	@tls tls
	route @http @tls {
		close
	}
	route {
		proxy 127.0.0.1:18026
	}
}
Client Result
Passive client, waits for the banner (all real SMTP clients) closed after 3.00 s, no banner
HTTP probe closed immediately
TLS probe closed after 3.00 s
Pre-greet EHLO (10+ bytes on one line) proxied, got the banner

caddy-l4's RouteList.Compile returns nil on ErrMatchingTimeout (layer4/routes.go). Server.handle closes every connection it returns nil for (layer4/server.go). The matcherless fallback route is skipped while an earlier matcher reports ErrConsumedAllPrefetchedBytes (layer4/routes.go, the len(route.matcherSets) == 0 guard). No ordering saves the connection.

What follows

Keep :25 and :587 proxying unconditionally. The non-SMTP probe cost stays. The app-level guard from this work is what bounds it: per-IP connection limits in Redis, a deny set, and RELAY_PROXY_PROTOCOL_TIMEOUT. If proxy-level filtering is still wanted later, it needs a different tool. CrowdSec with a firewall bouncer, or nginx or HAProxy in front.

side quest: sec @x http/@x tls close routes on 25 and 587 drop every banner-first SMTP client. Cannot be implemented in caddy-l4 as planned. [deploy/k8s/caddy/Caddyfile:L30-L48]

Work reference: codingjoe/relay#409, branch codingjoe-mx-msa-connect-abuse-protection.

Dominant language
Python
Stars
4
Forks
0
Avg merge
2h 40m
Merged PRs (30d)
163

Getting set up

  • Ships a Dockerfile or Docker Compose file
  • Has a pull request template
  • No contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from codingjoe/relay

All issues in codingjoe/relay

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.