Hi Andre,

On Wed, Jul 22, 2026 at 10:53:17PM +0200, Andre Brait wrote:
> pat_parse_ip() calls str2net() with DNS resolution enabled before trying
> str62net(), so every IPv6 entry of a pattern file pays a blocking DNS
> resolution attempt (which necessarily fails) before being parsed as an
> IPv6 literal. On a configuration loading a 20k-entry IPv6 acl file on a
> host with a resolv.conf search domain, this added two DNS round trips
> per entry and made "haproxy -c" take 14 seconds instead of about 1.
> 
> Try both literal parsers first, then fall back to the resolving call of
> str2net(). An IPv6 literal can never be a valid host name (colons are
> not permitted in DNS labels) and host names keep resolving exactly as
> before, so no working configuration changes behaviour.
> 
> Diagnosed on pfSense, where large pfBlockerNG IP block lists are loaded
> through "acl ... -f" files:
> https://github.com/pfsense/FreeBSD-ports/pull/1450
> 
> This patch was authored with Claude Fable 5 and reviewed by GPT-5.6 Sol.
> ---
>  src/pattern.c | 15 +++++++++++++--
>  1 file changed, 13 insertions(+), 2 deletions(-)
> 
> diff --git a/src/pattern.c b/src/pattern.c
> index 17322e3a3..d62718210 100644
> --- a/src/pattern.c
> +++ b/src/pattern.c
> @@ -408,8 +408,14 @@ int pat_parse_dotted_ver(const char *text, struct 
> pattern *pattern, int mflags,
>   */
>  int pat_parse_ip(const char *text, struct pattern *pattern, int mflags, char 
> **err)
>  {
> -     if (str2net(text, !(mflags & PAT_MF_NO_DNS) && (global.mode & 
> MODE_STARTING),
> -                 &pattern->val.ipv4.addr, &pattern->val.ipv4.mask)) {
> +     /* Try literal parsing of both address families first, and only then
> +      * fall back to a DNS resolution attempt for the IPv4 case. Resolving
> +      * before trying the IPv6 literal parsing would cost a useless blocking
> +      * DNS round trip for every IPv6 entry of a pattern file, since an IPv6
> +      * literal can never be a valid host name (colons are not permitted in
> +      * DNS labels).
> +      */
> +     if (str2net(text, 0, &pattern->val.ipv4.addr, &pattern->val.ipv4.mask)) 
> {
>               pattern->type = SMP_T_IPV4;
>               return 1;
>       }
> @@ -417,6 +423,11 @@ int pat_parse_ip(const char *text, struct pattern 
> *pattern, int mflags, char **e
>               pattern->type = SMP_T_IPV6;
>               return 1;
>       }
> +     else if (!(mflags & PAT_MF_NO_DNS) && (global.mode & MODE_STARTING) &&
> +              str2net(text, 1, &pattern->val.ipv4.addr, 
> &pattern->val.ipv4.mask)) {
> +             pattern->type = SMP_T_IPV4;
> +             return 1;
> +     }
>       else {
>               memprintf(err, "'%s' is not a valid IPv4 or IPv6 address", 
> text);
>               return 0;
> -- 
> 2.55.0

Sorry for the delay. Your patch is fine and works well so I merged it.
One point worth noting is that it will attempt a second ipv4 parsing
for those using plain DNS names, but we really don't care as these ones
are very few in general (and the IPv4 parsing is much cheaper than a DNS
resolution anyway).

While testing your patch, I noticed ~5% CPU used in libc's malloc(),
which was caused by the strdup() early in str2net() and str62net(),
used to trim before the slash if any. However that's wasted because
we look for the slash, so we can avoid the strdup() when there's none.
Thus I adjusted this in the functions, and now loading an IPv4 ACL is
~5% faster, and an IPv6 one is ~8% faster. This could help for your
use case, feel free to pick commit 9bc6c4d18c ("OPTIM: tools/str2net:
only duplicate the string when a slash is present") if you need it.

thanks!
Willy


Reply via email to