[PATCH v5 1/2] resolv: implement ipv4+ipv6 flags in resolv.conf (bug 30544)

Petr Menšík pemensik@redhat.com
Thu Oct 16 09:53:31 GMT 2025


On 15/10/2025 20:29, Adhemerval Zanella Netto wrote:
>>> The name of new options is for a discussion. I like ipv4 and ipv6 as more beginner friendly. It might make sense to rename ipv4 to "a" and ipv6 to "aaaa" instead. But it would be a bit confusing that aaaa is not just negative of no-aaaa option, but something slightly different.
>>>
> I don't have a strong preference, but I tend to prefer 'ipv4'/'ipv6'
> instead of the historical 'a'/'aaaa'.
>
>>> Also, it might help adding supporting AI_ALL flag to get all addresses, just like ipv4 and ipv6 options were specified, but per request. Reverting to current behaviour regarless of flags.
> Would it be the same as the current behavior (without flags)? Not sure
> if this is an improvement.
Yes, it would be same as without flags or with both flags. Improvement 
is that single application could request either all addresses, or only 
those considered useful. Per request. Without special flag, it would 
have to modify _res.options flags between requests. There is not a nice 
API for that, therefore I expect this should not be done by 
well-behaving application.
>>> Or adding similar new flag AI_ALLAF instead? Allowing to ignore those flags per-request only? What do you think about this change itself?
> It is not clear to me how different it would be from AI_ALL.

Current AI_ALL is about mapped IPv4 addresses. It makes them added into 
AF_INET6 response in mapped form. But it is unclear to me, whether it 
could do something useful with AI_V4MAPPED flags without AI_ALL.

Without mapped flag it does nothing, at least that is written in manual. 
Using it for modification of ipv4 and/or ipv6 flag presence behaviour 
would change existing behaviour of something unrelated.

It is described in original RFC introducing getaddrinfo() call: 
https://datatracker.ietf.org/doc/html/rfc3493#section-6.1

That flag might override ipv4 and ipv6 presence per query. For example 
diagnostic tools might want to print all available addresses, even if 
they are not trying to connect anywhere. If addresses should be 
displayed somewhere, it would allow override flags per request only. 
Without changing global _res.options flags.

But if the same application were doing normal connections also, it could 
use only useful addresses useful for that connection. Adding a new flag 
for different modification seems safer to me. But I admit 
AI_ALL|AI_V4MAPPED should probably result in both A and AAAA requests 
sent too, regardless only ipv4 flag present.

I would modify AF_UNSPEC behavior only. That flag modifies only 
family=AF_INET6 requests now. It would never be in conflict. So ok, 
AI_ALL can be safely reused also without AI_V4MAPPED for a bit different 
purpose.

Played with a test script in python:

from socket import *
def flags_test(flags, host='example.com', af=AF_INET6):
     for addr in getaddrinfo(host, 'http', family=af, proto=IPPROTO_TCP, 
flags=flags):
         print(addr)

flags_test(AI_V4MAPPED, host='redhat.com', af=AF_INET6) # gives IPv4 
mapped addresses, redhat.com does not have IPv6 addresses
flags_test(AI_V4MAPPED, host='redhat.com', af=AF_UNSPEC) # gives normal 
IPv4 addresses only

>
> The patch is sound, although I don't have much experience in this area.
The main purpose is to avoid first connect() failure visible in strace 
in commands like curl http://example.com if you have only ipv4 
connectivity. It is possible to use -4 flag in application. If you use 
no-aaaa, it will prevent even -6 flag from getting address.
>> ---
>>   resolv/nss_dns/dns-host.c             | 50 +++++++++++++++++----------
>>   resolv/res_debug.c                    |  2 ++
>>   resolv/res_init.c                     |  2 ++
>>   resolv/resolv.h                       |  2 ++
>>   resolv/tst-resolv-res_init-skeleton.c |  2 ++
>>   5 files changed, 40 insertions(+), 18 deletions(-)
>>
>> diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
>> index 14da73ee1d..99e8e5ce4c 100644
>> --- a/resolv/nss_dns/dns-host.c
>> +++ b/resolv/nss_dns/dns-host.c
>> @@ -379,6 +379,7 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
>>   {
>>     enum nss_status status = check_name (name, herrnop);
>>     char tmp[NS_MAXDNAME];
>> +
>>     if (status != NSS_STATUS_SUCCESS)
>>       return status;
>>     struct resolv_context *ctx = __resolv_context_get ();
>> @@ -412,26 +413,39 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
>>   
>>     int olderr = errno;
>>     int n;
>> +  int res_options = ctx->resp->options & (RES_NOAAAA|RES_IPV4|RES_IPV6);
> The 'struct __res_state' option is an unsigned long, using int might be
> problem with flags that exceeds INT_MAX.
ok, needs fixing.
>
>> +  int qtype;
>>   
>> -  if ((ctx->resp->options & RES_NOAAAA) == 0)
>> +  switch (res_options)
>>       {
>> -      n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
>> -				dns_packet_buffer, sizeof (dns_packet_buffer),
>> -				&alt_dns_packet_buffer, &ans2p, &nans2p,
>> -				&resplen2, &ans2p_malloced);
>> -      if (n >= 0)
>> -	status = gaih_getanswer (alt_dns_packet_buffer, n, ans2p, resplen2,
>> -				 &abuf, pat, errnop, herrnop, ttlp);
>> -    }
>> -  else
>> -    {
>> -      n = __res_context_search (ctx, name, C_IN, T_A,
>> -				dns_packet_buffer, sizeof (dns_packet_buffer),
>> -				&alt_dns_packet_buffer, NULL, NULL, NULL, NULL);
>> -      if (n >= 0)
>> -	status = gaih_getanswer_noaaaa (alt_dns_packet_buffer, n,
>> -					&abuf, pat, errnop, herrnop, ttlp);
>> -    }
>> +      case RES_IPV4:
>> +      case RES_IPV4|RES_NOAAAA:
>> +      case RES_NOAAAA:
>> +      case RES_IPV6|RES_NOAAAA: /*< this combination should never be used. */
> Double space after period.
>
>> +      case RES_IPV4|RES_IPV6|RES_NOAAAA: /*< this does not make sense. */
>> +      case RES_IPV6: /*< oh we want AAAA, but not A here. code is the same. */
>> +	qtype = (res_options == RES_IPV6 ? T_AAAA : T_A);
>> +	n = __res_context_search (ctx, name, C_IN, qtype,
>> +				  dns_packet_buffer, sizeof (dns_packet_buffer),
>> +				  &alt_dns_packet_buffer, NULL, NULL, NULL, NULL);
>> +	if (n >= 0)
>> +	  status = gaih_getanswer_noaaaa (alt_dns_packet_buffer, n,
>> +					  &abuf, pat, errnop, herrnop, ttlp);
>> +	break;
>> +
>> +      case 0:
>> +      case RES_IPV4|RES_IPV6:
>> +      default:
>> +	n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
>> +				  dns_packet_buffer, sizeof (dns_packet_buffer),
>> +				  &alt_dns_packet_buffer, &ans2p, &nans2p,
>> +				  &resplen2, &ans2p_malloced);
>> +	if (n >= 0)
>> +	  status = gaih_getanswer (alt_dns_packet_buffer, n, ans2p, resplen2,
>> +				  &abuf, pat, errnop, herrnop, ttlp);
>> +	break;
>> +  }
>> +
>>     if (n < 0)
>>       {
>>         switch (errno)
>> diff --git a/resolv/res_debug.c b/resolv/res_debug.c
>> index 73af0c72fe..f73bccd7bd 100644
>> --- a/resolv/res_debug.c
>> +++ b/resolv/res_debug.c
>> @@ -613,6 +613,8 @@ p_option(u_long option) {
>>   	case RES_NORELOAD:	return "no-reload";
>>   	case RES_TRUSTAD:	return "trust-ad";
>>   	case RES_NOAAAA:	return "no-aaaa";
>> +	case RES_IPV4:		return "ipv4";
>> +	case RES_IPV6:		return "ipv6";
>>   				/* XXX nonreentrant */
>>   	default:		sprintf(nbuf, "?0x%lx?", (u_long)option);
>>   				return (nbuf);
>> diff --git a/resolv/res_init.c b/resolv/res_init.c
>> index aed1902ec5..2e898d3fb3 100644
>> --- a/resolv/res_init.c
>> +++ b/resolv/res_init.c
>> @@ -696,6 +696,8 @@ res_setoptions (struct resolv_conf_parser *parser, const char *options)
>>               { STRnLEN ("trust-ad"), RES_TRUSTAD },
>>               { STRnLEN ("no-aaaa"), RES_NOAAAA },
>>               { STRnLEN ("strict-error"), RES_STRICTERR },
>> +            { STRnLEN ("ipv4"), RES_IPV4 },
>> +            { STRnLEN ("ipv6"), RES_IPV6 },
>>             };
>>   #define noptions (sizeof (options) / sizeof (options[0]))
>>             bool negate_option = *cp == '-';
>> diff --git a/resolv/resolv.h b/resolv/resolv.h
>> index b8a0f66a5f..647881243d 100644
>> --- a/resolv/resolv.h
>> +++ b/resolv/resolv.h
>> @@ -134,6 +134,8 @@ struct res_sym {
>>   #define RES_TRUSTAD     0x04000000 /* Request AD bit, keep it in responses.  */
>>   #define RES_NOAAAA      0x08000000 /* Suppress AAAA queries.  */
>>   #define RES_STRICTERR   0x10000000 /* Report more DNS errors as errors.  */
>> +#define RES_IPV4        0x20000000 /* Query A records on PF_UNSPEC hints. */
>> +#define RES_IPV6        0x40000000 /* Query AAAA records on PF_UNSPEC hints. */
> Double space after period.
Ah, I could not understood where I have multiple spaces. But from 
example of others, there should be double space at the end of comment. 
Ok, unexpected style, but why not? Fixing.
>
>>   
>>   #define RES_DEFAULT	(RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
>>   
>> diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
>> index 3ccbe71db9..3d99f4b5e5 100644
>> --- a/resolv/tst-resolv-res_init-skeleton.c
>> +++ b/resolv/tst-resolv-res_init-skeleton.c
>> @@ -130,6 +130,8 @@ print_resp (FILE *fp, res_state resp)
>>           print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");
>>           print_option_flag (fp, &options, RES_NOAAAA, "no-aaaa");
>>           print_option_flag (fp, &options, RES_STRICTERR, "strict-error");
>> +        print_option_flag (fp, &options, RES_IPV4, "ipv4");
>> +        print_option_flag (fp, &options, RES_IPV6, "ipv6");
>>           fputc ('\n', fp);
>>           if (options != 0)
>>             fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);

-- 
Petr Menšík
Senior Software Engineer, RHEL
Red Hat,https://www.redhat.com/
PGP: DFCF908DB7C87E8E529925BC4931CA5B6C9FC5CB
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20251016/2f6a2c4f/attachment-0001.htm>


More information about the Libc-alpha mailing list