From b5fa007d6e86222afcdc72d6433791ceec1ea81c Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sun, 7 Dec 2025 10:23:38 -0700 Subject: [PATCH] Add Caddyfile unmarshaler support for SIPMatcher and SIPHandler The layer4 matchers and handlers must implement caddyfile.Unmarshaler to be usable in Caddyfile syntax. This enables proper parsing of: - @sip sip { methods ... } matchers - sip_guardian { ... } handlers --- l4handler.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/l4handler.go b/l4handler.go index e1edfd1..77833e5 100644 --- a/l4handler.go +++ b/l4handler.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/mholt/caddy-l4/layer4" "go.uber.org/zap" ) @@ -177,10 +178,75 @@ func min(a, b int) int { return b } +// UnmarshalCaddyfile implements caddyfile.Unmarshaler for SIPMatcher. +// Usage in Caddyfile: +// +// @sip sip { +// methods REGISTER INVITE OPTIONS +// } +// +// Or simply: @sip sip +func (m *SIPMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // Move past "sip" token + d.Next() + + // Check for block + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "methods": + m.Methods = d.RemainingArgs() + if len(m.Methods) == 0 { + return d.ArgErr() + } + default: + return d.Errf("unknown sip matcher directive: %s", d.Val()) + } + } + + return nil +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler for SIPHandler. +// Usage in Caddyfile: +// +// sip_guardian { +// max_failures 5 +// find_time 10m +// ban_time 1h +// whitelist 10.0.0.0/8 172.16.0.0/12 +// } +// +// Or simply: sip_guardian (uses defaults) +func (h *SIPHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // Move past "sip_guardian" token + d.Next() + + // The handler doesn't have its own configuration - it uses the shared SIPGuardian + // But we need to parse any configuration blocks that might be present + + // Check for inline args or block + for nesting := d.Nesting(); d.NextBlock(nesting); { + // For now, SIPHandler delegates to SIPGuardian + // In future, handler-specific config could go here + switch d.Val() { + case "max_failures", "find_time", "ban_time", "whitelist": + // These are handled by the embedded SIPGuardian + // Skip to allow flexibility in config placement + d.RemainingArgs() + default: + return d.Errf("unknown sip_guardian directive: %s", d.Val()) + } + } + + return nil +} + // Interface guards var ( - _ layer4.ConnMatcher = (*SIPMatcher)(nil) - _ layer4.NextHandler = (*SIPHandler)(nil) - _ caddy.Provisioner = (*SIPMatcher)(nil) - _ caddy.Provisioner = (*SIPHandler)(nil) + _ layer4.ConnMatcher = (*SIPMatcher)(nil) + _ layer4.NextHandler = (*SIPHandler)(nil) + _ caddy.Provisioner = (*SIPMatcher)(nil) + _ caddy.Provisioner = (*SIPHandler)(nil) + _ caddyfile.Unmarshaler = (*SIPMatcher)(nil) + _ caddyfile.Unmarshaler = (*SIPHandler)(nil) )