feat(handler/http,http2): support comma-separated hostnames in probeResistance knock

Knock previously accepted a single hostname. Now it accepts multiple
comma-separated hostnames; probe resistance is bypassed if the request
hostname matches any entry in the list. Matching is case-insensitive
and whitespace around entries is trimmed.
This commit is contained in:
ginuerzh
2026-05-28 23:20:27 +08:00
parent 3246b39c93
commit c246a1d4fa
6 changed files with 163 additions and 7 deletions
+19 -4
View File
@@ -26,9 +26,9 @@ import (
// - "web": fetch a URL and replay its response as the decoy.
// - "host": forward the raw request to a decoy host and relay the reply.
// - "file": serve a local file with Content-Type: text/html.
// - knock: when pr.Knock is set, probe resistance only activates if the
// request hostname does NOT match the knock address. Clients that know
// the knock hostname get the normal 407 Proxy-Auth-Required.
// - knock: when pr.Knock is set (comma-separated hostnames), probe resistance
// only activates if the request hostname matches NONE of the knock addresses.
// Clients that know any knock hostname get the normal 407 Proxy-Auth-Required.
//
// On success it returns the authenticated client ID. On failure it writes
// the response (407 or probe-resistance decoy) to conn and returns ok=false.
@@ -49,7 +49,7 @@ func (h *httpHandler) authenticate(ctx context.Context, conn net.Conn, req *http
// When pr.Knock matches the hostname, probe resistance is bypassed and
// the normal 407 Proxy-Auth-Required is returned, revealing the proxy
// only to clients that know the knock address.
if pr != nil && (pr.Knock == "" || !strings.EqualFold(req.URL.Hostname(), pr.Knock)) {
if pr != nil && (pr.Knock == "" || !knockMatch(req.URL.Hostname(), pr.Knock)) {
resp.StatusCode = http.StatusServiceUnavailable
switch pr.Type {
@@ -159,3 +159,18 @@ func (h *httpHandler) checkRateLimit(addr net.Addr) bool {
return true
}
// knockMatch reports whether hostname matches any entry in the
// comma-separated knock list. Matching is case-insensitive. An empty
// knock string returns false (no match).
func knockMatch(hostname, knock string) bool {
if knock == "" {
return false
}
for _, h := range strings.Split(knock, ",") {
if strings.EqualFold(hostname, strings.TrimSpace(h)) {
return true
}
}
return false
}