Setting up the network interaction of services is not the easiest task and is often carried out without a deep understanding of how to configure the system and what settings are affected. After migrating services to docker containers from centos 6 to centos 7, I ran into a strange web server behavior: he tried to join the IPv6 service, and the service only listened to the IPv4 address. The standard tip in this situation is to disable IPv6 support. But this will not help in some cases. What kind? In this article, I set out to collect and explain in detail how applications resolve
address addresses.
The publication will be useful for beginners administrators and developers.
After reading this article, you will learn:
- What is the Linux algorithm for resolving hostnames?
- how to override the hostname definition logic;
- what functions and libraries are used by the OS;
- what traps exist when configuring and how to prevent them;
The Linux operating system has several sources for determining the hostname address. All the necessary functionality for determining is in the GNU C Library (glibc) . glibc is essentially a framework and implements many useful functions for the developer, providing its own API to simplify development. Among other things, glibc implements POSIX . Functions such as open
, read
, write
, malloc
, printf
, getaddrinfo
, dlopen
, pthread_create
, crypt
, login
, exit
for Linux systems are provided by glibc.
host
, dig
and nslookup
utilities known to many use glibc, but are shipped separately.
Now that the developer has the ability to call the getaddrinfo
family getaddrinfo
from glibc to determine the address, there is a need to configure the return values. For example, whether to use /etc/hosts
or a query to the DNS server. In glibc, this configuration is done using a scheme called Name Service Switch (NSS).
If explained on the fingers, the NSS allows you to specify the database and the sequence of search in these databases to provide the service. In our case, the service is a search by hostname, and the database can be /etc/hosts
or DNS server. This is not the only service configured by NSS, alias mail services, a user and group search service are provided. You can see the list in the manual .
Thanks to NSS, it is possible, without reassembling applications, in runtime, to configure the mentioned databases. It is configured in the /etc/nsswitch.conf
file. Below is an example of a config from standard / /etc/nsswitch.conf
in Centos 7.
$ grep ^hosts /etc/nsswitch.conf hosts: files dns myhostname
files , dns and myhostname are database aliases for search. files on most systems imply the use of /etc/hosts
, the dns base is the DNS server to which the hostname search query will be made, and myhostname is the most unusual base that few people know about and it is not part of the standard delivery in glibc. In some distributions there is also the mdns4_minimal base. The following text provides a review of these databases.
The databases are used in the order in which they are declared in /etc/nsswitch.conf
and if an entry is found in the current database, then the output from the chain and the result are returned. In the absence of a result, a transition to the next base in the list occurs. If no result is found in any database, then such a response is given to the glibc request for the getaddrinfo
function. The behavior of the transition to the next base and the conditions of such a transition can be further configured, for example, when DNS is unavailable (not to be confused with the absence of a record) to complete the chain. A clear and simple explanation of how to configure the conditions for /etc/nsswitch.conf
given in this article .
The base files , and in particular /etc/hosts
, out of the box in Centos 7 looks like this:
$ cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
It can be noted that for localhost there are two entries: IPv4 and IPv6 address. This can play a cruel joke and at the end of the material I will tell you why.
The dns database uses the name server specified in the /etc/resolv.conf
config when defining the address. Here is an example of my /etc/resolv.conf
on a host system:
$ cat /etc/resolv.conf # Generated by NetworkManager nameserver 127.0.0.1 nameserver 192.168.100.1
Name server'a are also used in the chain and in the order of their announcement. In my case, the first is the local DNS server (I use dnsmasq ) to set the local addresses of the .priv
zones. If a match is found, then the address is returned from the local network. All other requests are sent to the primary DNS server with the address 192.168.100.1
.
The myhostname base is present in the Centos and Ubuntu distribution, but is not part of glibc
. Without knowing this fact, I spent a lot of time trying to figure out why IPv6 addresses are returned to me to determine the host. It works as follows:
hostname
command returns), the plug-in returns all IP addresses of the public interfaces (ie, all except the loopback), in the absence of such interfaces, the IPv4 address is 127.0.0.2
and the IPv6 address is ::1
;127.0.0.1
and the IPv6 address ::1
;127.0.0.1
and the IPv6 address ::1
;The manual also writes about a special logic with the processing of the _gateway hostname , but apparently this is some kind of patch, since I didn’t start up with Centos 7.
The mdns4_minimal base or mdns_minimal is required for Avahi to work correctly. If necessary, you can refer to the Arch documentation on Avahi , where brief and clear information is given
on use.
Now that information is given on the bases and principles of their work, it is worth noting differences in the definition of addresses in different tools, which leads to problems in runtime.
Usually administrators check the hostname using the host command. This is incorrect, so the host, like dig, uses only DNS rezolving, but does not use NSS. Nginx, for example, uses the getaddrinfo function, and it uses NSS. This leads to the fact that the hostname hooked into /etc/hosts
can work with nginx, but does not resolve it in other ways. It's worse when the hostname's IPv6 address is hammered into /etc/hosts
, and only the IPv4 address is returned in the DNS settings. In this case, the administrator can verify that the host command returns only the IPv4 address and calms down, and then the application using getaddrinfo
from glibc starts and finds the IPv4 and IPv6 address for the same hostname. Source of errors ...
To verify the results returned by each of the databases, the documentation recommends using the getent utility.
Below are some examples of working with getent
with IPv6 enabled.
The /etc/nsswitch.conf
contains the following database chain:
hosts: files dns myhostname
/etc/hosts
contains the following info
$ cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback
The getent ahosts <hostname>
command will display a list of all the addresses that were found. With these settings, it displays the following:
$ getent ahosts localhost ::1 STREAM localhost ::1 DGRAM ::1 RAW 127.0.0.1 STREAM 127.0.0.1 DGRAM 127.0.0.1 RAW
The team allows you to point to a specific database and find out what the base is cutting. Consider the return values for each database:
$ getent -s hosts:files ahosts localhost ::1 STREAM localhost ::1 DGRAM ::1 RAW 127.0.0.1 STREAM 127.0.0.1 DGRAM 127.0.0.1 RAW $ getent -s hosts:dns ahosts localhost ::1 STREAM localhost ::1 DGRAM ::1 RAW 127.0.0.1 STREAM 127.0.0.1 DGRAM 127.0.0.1 RAW $ getent -s hosts:myhostname ahosts localhost ::1 STREAM localhost ::1 DGRAM ::1 RAW 127.0.0.1 STREAM 127.0.0.1 DGRAM 127.0.0.1 RAW
If you remove the lines for localhost from /etc/hosts
, the output will change:
$ getent -s hosts:files ahosts localhost $ getent -s hosts:dns ahosts localhost ::1 STREAM localhost ::1 DGRAM ::1 RAW 127.0.0.1 STREAM 127.0.0.1 DGRAM 127.0.0.1 RAW $ getent -s hosts:myhostname ahosts localhost ::1 STREAM localhost ::1 DGRAM ::1 RAW 127.0.0.1 STREAM 127.0.0.1 DGRAM 127.0.0.1 RAW
Now the dns base and myhostname return answers, and the files database does not contain data. For DNS queries, the nameserver configured in /etc/resolv.conf
in my container is used, for example
$ cat /etc/resolv.conf nameserver 127.0.0.11 options ndots:0
Dnsmasq is installed on the host machine, which proxies and caches DNS server responses. The answer from DNS will depend on the DNS server settings to which the request came. RFC 1912 recommends in clause 4.1 to configure the DNS servers so that localhost points to 127.0.0.1.
Certain zones should always be present in nameserver configurations:
primary localhost localhost primary 0.0.127.in-addr.arpa 127.0 primary 255.in-addr.arpa 255 primary 0.in-addr.arpa 0
')
Nameservice for "special"
addresses or help
local address to be sent to the root nameservers. All of these
record files
you maintain your SOA
timers very long, since this data will never change.
The "localhost" address is a "special" address which always refers to
the local host. It should contain the following line:
localhost. IN A 127.0.0.1
In my case, the dnsmasq out of the box contains entries for localhost, as recommended by the RFC.
$ dig +noall +answer localhost ANY @127.0.0.1 localhost. 0 IN A 127.0.0.1 localhost. 0 IN AAAA ::1
It is disabled by either removing entries from /etc/hosts
on the DNS server itself, or by turning on the no-hosts
option in /etc/dnsmasq.conf
.
After enabling the getent option for the myhostname database, the result is non-empty, but as noted above, with myhostname enabled, the IPv4 and IPv6 address will be returned. On systems with static IP addresses, you can safely turn off the myhostname plugin and configure local hosts using /etc/hosts
. An alternative is to disable IPv6.
The IPv6 status on the server can be obtained from the kernel parameters. A value of 0 is returned when IPv6 is enabled, and 1 is turned off.
$ sysctl net.ipv6.conf.all.disable_ipv6 net.ipv6.conf.default.disable_ipv6 net.ipv6.conf.all.disable_ipv6 = 0 net.ipv6.conf.default.disable_ipv6 = 0
In the ifconfig output, the IPv6 listening interfaces contain the line inet6 . Below is an example of output with IPv6 turned off and on, respectively:
# diabled $ ifconfig -a eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.101.5 netmask 255.255.255.0 broadcast 0.0.0.0 ether 02:42:c0:a8:65:05 txqueuelen 0 (Ethernet) RX packets 15789549 bytes 2553533549 (2.3 GiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 9783999 bytes 1318627420 (1.2 GiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 loop txqueuelen 1 (Local Loopback) RX packets 606047 bytes 67810892 (64.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 606047 bytes 67810892 (64.6 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 # enabled $ ifconfig -a eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.101.5 netmask 255.255.255.0 broadcast 0.0.0.0 inet6 fe80::42:c0ff:fea8:6505 prefixlen 64 scopeid 0x20<link> ether 02:42:c0:a8:65:05 txqueuelen 0 (Ethernet) RX packets 15787641 bytes 2553216408 (2.3 GiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 9782965 bytes 1318487919 (1.2 GiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1 (Local Loopback) RX packets 605949 bytes 67799887 (64.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 605949 bytes 67799887 (64.6 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
You can turn off IPv6 by calling
$ sysctl -w net.ipv6.conf.all.disable_ipv6=1 $ sysctl -w net.ipv6.conf.default.disable_ipv6=1
What will change after shutdown? I rolled back all the configs to the standard ones: localhost with IPv4 and IPv6 addresses is present in /etc/hosts
, the no-hosts
option is disabled in dnsmasq. I disabled the IPv6 commands above and the output of getent was as follows:
$ getent -s hosts:files ahosts localhost 127.0.0.1 STREAM localhost 127.0.0.1 DGRAM 127.0.0.1 RAW 127.0.0.1 STREAM 127.0.0.1 DGRAM 127.0.0.1 RAW $ getent -s hosts:dns ahosts localhost 127.0.0.1 STREAM localhost 127.0.0.1 DGRAM 127.0.0.1 RAW $ getent -s hosts:myhostname ahosts localhost 127.0.0.1 STREAM localhost 127.0.0.1 DGRAM 127.0.0.1 RAW
Wow, in the first output we have duplicated the address 127.0.0.1. To figure out why this is happening, it’s worth referring to the glibc source code and the getent utility code. Below is a piece of getent utility code.
/* This is for hosts, but using getaddrinfo */ static int ahosts_keys_int (int af, int xflags, int number, char *key[]) { // ... hint.ai_flags = (AI_V4MAPPED | AI_ADDRCONFIG | AI_CANONNAME | idn_flags | xflags); hint.ai_family = af; for (i = 0; i < number; ++i) { struct addrinfo *res; if (getaddrinfo (key[i], NULL, &hint, &res) != 0) result = 2; else { struct addrinfo *runp = res; while (runp != NULL) { // printf goes here } freeaddrinfo (res); } } return result;
The AI_V4MAPPED flag of the getaddrinfo function performs mapping of IPv6 addresses to IPv4 if no IPv6 addresses were found as a result of polling the database. The AI_ADDRCONFIG flag will force getaddrinfo to check for the presence of IPv6 / IPv4 addresses configured in the system and in the absence of at least one IPv6 / IPv4 address, the IPv6 / IPv4 address will not be returned regardless of whether the specific base responds.
Since getent has both flags enabled, and in /etc/hosts
127.0.0.1
and ::1
addresses for localhost, getaddrinfo will receive from the NSS of the hosts database (in the example above we discussed this particular database), 127.0.0.1
and ::1
addresses ::1
, then not finding a single IPv6 address in the system (turned off by the kernel parameters) and will perform mapping ::1
-> 127.0.0.1
.
To better understand this concept, I will give examples with the output of getaddrinfo on the same system, with different settings ai_flags and ai_family . The /etc/hosts
are enabled for localhost IPv4 and IPv6 addresses.
Source code can be found on my github .
# ./getaddrinfo localhost IP addresses for localhost: [AF_INET] AI_V4MAPPED IPv4: 127.0.0.1 IPv4: 127.0.0.1 [AF_INET] AI_V4MAPPED AI_ALL IPv4: 127.0.0.1 IPv4: 127.0.0.1 [AF_INET] AI_ADDRCONFIG IPv4: 127.0.0.1 IPv4: 127.0.0.1 [AF_INET] AI_V4MAPPED AI_ADDRCONFIG IPv4: 127.0.0.1 IPv4: 127.0.0.1 [AF_INET] AI_V4MAPPED AI_ALL AI_ADDRCONFIG IPv4: 127.0.0.1 IPv4: 127.0.0.1 -------------- [AF_INET6] AI_V4MAPPED IPv6: ::1 [AF_INET6] AI_V4MAPPED AI_ALL IPv6: ::ffff:127.0.0.1 IPv6: ::ffff:127.0.0.1 IPv6: ::1 [AF_INET6] AI_ADDRCONFIG getaddrinfo: Name or service not known [AF_INET6] AI_V4MAPPED AI_ADDRCONFIG getaddrinfo: Name or service not known [AF_INET6] AI_V4MAPPED AI_ALL AI_ADDRCONFIG getaddrinfo: Name or service not known -------------- [AF_UNSPEC] AI_V4MAPPED IPv4: 127.0.0.1 IPv6: ::1 [AF_UNSPEC] AI_V4MAPPED AI_ALL IPv4: 127.0.0.1 IPv6: ::1 [AF_UNSPEC] AI_ADDRCONFIG IPv4: 127.0.0.1 IPv4: 127.0.0.1 [AF_UNSPEC] AI_V4MAPPED AI_ADDRCONFIG IPv4: 127.0.0.1 IPv4: 127.0.0.1 [AF_UNSPEC] AI_V4MAPPED AI_ALL AI_ADDRCONFIG IPv4: 127.0.0.1 IPv4: 127.0.0.1 --------------
From the output, it is clear that with the _ai family equal to _AI UNSPEC (return both IPv4 and IPv6) and without the AI_ADDRCONFIG flag, getaddrinfo
returns two addresses, IPv4 and IPv6, which many administrators do not expect to see. This happens regardless of whether IPv6 is disabled in the kernel parameters. If the address ::1
removed from /etc/hosts
, then IPv6 addresses will disappear completely from the output of getaddrinfo
(with the AF_UNSPEC
flag).
With IPv6 enabled and the presence of ::1
in /etc/hosts
will return IPv4 and IPv6. To prevent the return of an IPv6 address, you need to comment out the IPv6 address in /etc/hosts
. If the addresses are found in /etc/hosts
, then glibc will not work in dns and myhostname.
It remains to check how getaddrinfo
behaves for the dns database. To do this, I leave in /etc/nsswitch.conf
for hosts only dns base and cut google.com. The output below is with IPv6 enabled.
$ sysctl -w net.ipv6.conf.default.disable_ipv6=0 $ sysctl -w net.ipv6.conf.all.disable_ipv6=0 $ ./getaddrinfo google.com IP addresses for google.com: [AF_INET] AI_V4MAPPED IPv4: 216.58.215.78 [AF_INET] AI_V4MAPPED AI_ALL IPv4: 216.58.215.78 [AF_INET] AI_ADDRCONFIG IPv4: 216.58.215.78 [AF_INET] AI_V4MAPPED AI_ADDRCONFIG IPv4: 216.58.215.78 [AF_INET] AI_V4MAPPED AI_ALL AI_ADDRCONFIG IPv4: 216.58.215.78 -------------- [AF_INET6] AI_V4MAPPED IPv6: 2a00:1450:401b:806::200e [AF_INET6] AI_V4MAPPED AI_ALL IPv6: ::ffff:216.58.215.78 IPv6: 2a00:1450:401b:806::200e [AF_INET6] AI_ADDRCONFIG IPv6: 2a00:1450:401b:806::200e [AF_INET6] AI_V4MAPPED AI_ADDRCONFIG IPv6: 2a00:1450:401b:806::200e [AF_INET6] AI_V4MAPPED AI_ALL AI_ADDRCONFIG IPv6: ::ffff:216.58.215.78 IPv6: 2a00:1450:401b:806::200e -------------- [AF_UNSPEC] AI_V4MAPPED IPv4: 216.58.215.78 IPv6: 2a00:1450:401b:806::200e [AF_UNSPEC] AI_V4MAPPED AI_ALL IPv4: 216.58.215.78 IPv6: 2a00:1450:401b:806::200e [AF_UNSPEC] AI_ADDRCONFIG IPv4: 216.58.215.78 IPv6: 2a00:1450:401b:806::200e [AF_UNSPEC] AI_V4MAPPED AI_ADDRCONFIG IPv4: 216.58.215.78 IPv6: 2a00:1450:401b:806::200e [AF_UNSPEC] AI_V4MAPPED AI_ALL AI_ADDRCONFIG IPv4: 216.58.215.78 IPv6: 2a00:1450:401b:806::200e --------------
But the output with IPv6 off:
$ sysctl -w net.ipv6.conf.all.disable_ipv6=1 $ sysctl -w net.ipv6.conf.default.disable_ipv6=1 $ ./getaddrinfo google.com IP addresses for google.com: [AF_INET] AI_V4MAPPED IPv4: 216.58.215.78 [AF_INET] AI_V4MAPPED AI_ALL IPv4: 216.58.215.78 [AF_INET] AI_ADDRCONFIG IPv4: 216.58.215.78 [AF_INET] AI_V4MAPPED AI_ADDRCONFIG IPv4: 216.58.215.78 [AF_INET] AI_V4MAPPED AI_ALL AI_ADDRCONFIG IPv4: 216.58.215.78 -------------- [AF_INET6] AI_V4MAPPED IPv6: 2a00:1450:401b:806::200e [AF_INET6] AI_V4MAPPED AI_ALL IPv6: ::ffff:216.58.215.78 IPv6: 2a00:1450:401b:806::200e [AF_INET6] AI_ADDRCONFIG getaddrinfo: Name or service not known [AF_INET6] AI_V4MAPPED AI_ADDRCONFIG getaddrinfo: Name or service not known [AF_INET6] AI_V4MAPPED AI_ALL AI_ADDRCONFIG getaddrinfo: Name or service not known -------------- [AF_UNSPEC] AI_V4MAPPED IPv4: 216.58.215.78 IPv6: 2a00:1450:401b:806::200e [AF_UNSPEC] AI_V4MAPPED AI_ALL IPv4: 216.58.215.78 IPv6: 2a00:1450:401b:806::200e [AF_UNSPEC] AI_ADDRCONFIG IPv4: 216.58.215.78 [AF_UNSPEC] AI_V4MAPPED AI_ADDRCONFIG IPv4: 216.58.215.78 [AF_UNSPEC] AI_V4MAPPED AI_ALL AI_ADDRCONFIG IPv4: 216.58.215.78 --------------
As you can see, the situation with AI_ADDRCONFIG is very similar.
Finally, I will give an example of how not to consider all the above, plunge into problems. IPv6 enabled, /etc/nsswitch.conf
standard.
$ cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback $ grep hosts /etc/nsswitch.conf hosts: files dns myhostname $ cat /etc/resolv.conf nameserver 127.0.0.11 options ndots:0 $ sysctl net.ipv6.conf.all.disable_ipv6 net.ipv6.conf.default.disable_ipv6 net.ipv6.conf.all.disable_ipv6 = 0 net.ipv6.conf.default.disable_ipv6 = 0
What will return host localhost
or dig ANY localhost
? What will return getaddrinfo
, for example, with flags like nginx?
$ dig +noall +answer ANY localhost @127.0.0.11 localhost. 0 IN A 127.0.0.1 $ host localhost localhost has address 127.0.0.1 $ ./getaddrinfo localhost IP addresses for localhost: [AF_UNSPEC] AI_ADDRCONFIG IPv6: ::1 IPv4: 127.0.0.1
nginx will try to connect to two addresses: 127.0.0.1
and ::1
, and the application may not expect this. Source for errors.
What can be concluded from all written?
Source: https://habr.com/ru/post/352300/
All Articles