Showing posts with label trust. Show all posts
Showing posts with label trust. Show all posts

Thursday, September 04, 2014

PAM revisited / PAM revisitado

This article is written in English and Portuguese (original version here)
Este artigo está escrito em Inglês e Português (versão original aqui)

English version:

Introduction
I covered PAM authentication in Informix a few years ago. I'd say most of what is written in those articles is still true. But I recently had to run some tests due to a customer situation, that may render some previous facts as not true anymore. To be more specific, I believe there is the generalized idea that implicit connections are hard or impossible to setup using PAM. This article will show that's not true. Nevertheless there are a few considerations to keep in mind.
In a recent customer situation we faced the following issue. The customer is implementing a third party product that will act as a proxy for the Informix connections. The product is being installed on the database servers and remote clients connect to a TCP port under control of this software (let's imagine 1500 on external TCP interface). The software will make a connection to the real Informix port (let's assume 1501 on localhost). The customer is using implicit (trusted) and explicit (password) connections. For explicit connections this architecture doesn't raise any issue. But for implicit connections there was (3rd party provider apparently already solved it) a big security issue. Informix checks the trust relation against the socket origin. But now, instead of the remote client, the socket is originating in the database server itself by the 3rd party application. So, every implicit connection was being trusted by Informix, independently of the real origin.
This led me to do some tests with PAM. As you know PAM (Plugin Authentication Modules) is a framework that allows applications to use different authentication modules to accomplish the authentication of a user. Different platforms come with different modules already pre-installed. What I'm going to demonstrate was tested with Linux and AIX. But it should work with any other operation system that implements PAM (HP-UX and Solaris). But the catch is that one of the modules may not be installed by default. In the worst case scenario you'd need to grab the module source code and adapt it to your platform, and compile. That work is outside the scope of this article.

Goal
So, what I was trying to achieve was the simulation of the default Informix authentication but using PAM. By default, and depending on the $INFORMIXSQLHOSTS configuration, Informix accepts both password and trusted connections on the TCP ports. In order to achieve that I need to setup a PAM service that uses two different mechanisms. One for password and the other for trusts. The module for password is easy. I think every OS has one to do that. In Linux it's pam_unix.so, in AIX is pam_aix etc. To get the trusted hosts functionality we need another module, and this one will be pam_rhosts.so (on Linux) and pam_rhosts_auth on AIX. I couldn't find a ready module for HP-UX to do this, but it's possible to adapt the module from Linux. There are some changes that must be done, because Linux-PAM has some extensions over the standard PAM framework.
Once we identify the modules we need to define how to set them up.

Configuration
As mentioned in the goal section, the idea is to configure a PAM listener that will accept both types of connections. If you check on previous articles you'll find that PAM modules can be configured with a module type and a control flag.
The module type can be auth, account, session and password.
I explained also that Informix only uses auth and account. For each type we can specify more than one module. Auth module type will check the user identity (is the password correct, is the host trusted) and account will check if the user can access the service, if the user account and/or password is valid and so on.
The control flag defines the role of the module in the overall module stack. Since each module type can use more than one module, we should specify for each module if it's return is critical or not for the overall result. The options we can use are required, requisite, sufficient and optional. LinuxPAM is more feature reach, but you should be able to get information about all these by checking the pam or pam.conf manual on your system.
So, we already know we'll need two modules, the module type, the control flags and finnaly we'll need a service name. The service name is whatever we choose, and makes the bridge between Informix configuration and the PAM framework.
I'll start the configuration in the Informix level and I'll create a new listener port for this. I'll use the name cheetah_pam as the INFORMIXSERVER (DBSERVERALIAS to be more exact). As such I need a new entry in $INFORMIXSQLHOSTS:

cheetah_pam  onsoctcp     primary 1533 s=4,pam_serv=(pam_informix),pamauth=(password)

So, I'm using "pam_informix" as the service name and I'm defining the PAM Informix option "password" as the pamauth option. The values allowed here are "password" and "challenge".
"password" should be used by services where the clients connect to using a user and password and challenge for anything else. But as we shall see, there's more to it than what's explained in the manual
So, after adding it to the $INFORMIXSQLHOSTS we should also add the new name (cheetah_pam) to $INFORMIXDIR/etc/$ONCONFIG option DBSERVERALIAS.

Next step it to configure the PAM stack for this service. This is a bit different depending if you're on Linux, AIX or others. Basically Linux uses a file for each service in /etc/pam.d. So we would create a file named /etc/pam.d/pam_informix. This file would contain several lines specifying the module type, the control flag, the module location and the module flags.
On other systems like AIX, there is only one file, called /etc/pam.conf and the service name is added as the first column in the file. Although these two ways are very similar, I tend to like better the way Linux works. Why? Because either /etc/pam.conf or /etc/pam.d/* are controlled by root and a sysadmin will not grant you, the Informix DBA, control over the /etc/pam.conf file. But he may allow you to control a single file (called pam_informix) inside the /etc/pam.d folder.
Anyway.... Given what I mentioned above, this is the service configuration:
auth        sufficient  pam_rhosts.so debug
auth        sufficient  pam_unix.so
account     required    pam_unix.so

What am I saying here? As you can see, the "auth" module type included two modules, with the control flag "sufficient". This means that either of them is enough to make the stack accept the identity. If one fails and the other is ok that's enough. If both fail than the auth type will fail.
As for the account, we're using the module pam_unix.so which validates if the account is valid (not blocked etc.). For AIX the following lines would have to be added for /etc/pam.conf
pam_informix auth      sufficient /usr/lib/security/pam_rhosts_auth debug
pam_informix auth      sufficient /usr/lib/security/pam_aix
pam_informix account   sufficient /usr/lib/security/pam_aix

The differences are that we added the service name as the first column, and the modules names are slightly different.

Testing
I have run several tests to check this implementation. I used a virtual machine where I'm running the cheetah_pam service and used the native OS as a client. My client hostname is "PTxxxx" and I created a user with the same name on the Linux box. I also setup an Informix instance on an AIX machine and  used the Linux server as a client in order to test 4GL connectivity.
First scenario is Informix instance on Linux and client on PC (windows). I have two SQL files:
C:\Programas\InformixClientSDK_370_tc7_c86>type *.sql

test_explicit.sql

CONNECT TO 'stores' USER 'PTxxxxxx' USING 'mypassword';
SELECT USER FROM sysmaster:sysdual;

test_implicit.sql

CONNECT TO 'stores';
SELECT USER FROM sysmaster:sysdual;

C:\Programas\InformixClientSDK_370_tc7_c86>

Let's see what happens when I run these both tests:
C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_implicit.sql

 1809: Server rejected the connection.
Error in line 1
Near character position 1

C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_explicit.sql

Connected.

(expression)

PTxxxxxx
1 row(s) retrieved.

Disconnected.

C:\Programas\InformixClientSDK_370_tc7_c86>

Ok. Implicit connections are failing, but user and password are working. This is expected because I didn't create the trusted relation on the Linux machine. Let's check the syslog on that machine:
tail -20 /var/log/secure
[...]
Sep  4 06:52:07 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:07 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:09 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:09 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:12 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:12 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:14 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:14 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:17 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:17 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:32 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx

These lines belong to the implicit attempt and the explicit one (last line). As you can see, for the implicit attempt both modules report failure, but for the explicit attempt only the pam_rhosts reports a failure, because the pam_unix succeeded (and since we used "sufficient", that's enough to accept the connection.
Now, let's configure the trust relation. For that I'll try to add "PTxxxxxx   PTxxxxxx" to /etc/hosts.equiv. Let's repeat the test:
C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_implicit.sql

Connected.

(expression)

PTxxxxxx

1 row(s) retrieved.

Disconnected.

C:\Programas\InformixClientSDK_370_tc7_c86>

and now, I'll remove that line from /etc/hosts.equiv and I'll add it to ~PCxxxxxx/.rhosts
As expected:
C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_implicit.sql

Connected.

(expression)

PCxxxxxx

1 row(s) retrieved.

Disconnected.

C:\Programas\InformixClientSDK_370_tc7_c86>

A final test with this setup: Let's remove the trust and try with a wrong password (changed the test_explicit.sql file):
C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_explicit.sql

 1809: Server rejected the connection.
Error in line 1
Near character position 1

C:\Programas\InformixClientSDK_370_tc7_c86>

So, this proves that with dbaccess we can emulate the native behavior but using PAM. But how about for other connectivity layers, like ODBC, .NET, 4GL etc.?
Well... I tried everything I could think off and only OleDB failed:

  • ConnectTestDemo (ok)
    (connectivity test bundled with ClientSDK for windows
  • ODBC (ok)
    (normal ODBC manager bundled with Windows)
  • VBScript that uses ODBC data source (ok)
  • VBScript that uses OleDB (failed!)
  • C# client that uses .NET (ok)
  • Native 4GL 7.50.UC4 (ok)
As mentioned above, OleDB client failed, because it explicitly does not support PAM. In fact when I try to use it I get the following message in the online.log:

10:35:24  listener-thread: err = -1809: oserr = 0: errstr = Client without PAM support connecting to PAM port: Server rejected the connection.

So this is a known product limitation. I believe this could be removed if we just assumed it would work just for password, and not for challenge. We did that very recently for DRDA and PAM. I suspect, but I cannot be sure, that the limitation comes from the fact that in OleDB clients we can't specify a callback function as we do in JDBC or ESQL/C for example. And that may be the source of this limitation. It would be better to support just the use of password if that's the case.

Conclusions and caveats
This little exercise proves that we can simulate (with some differences mentioned below) that we can simulate the engine native behavior while using PAM. But why would we want to do that? Well, I can list a few different motives:
  1. We may want the user/password to validate against a repository different than the native OS (or simply change the encryption mechanism and Informix may be sensitive to that)
  2. We may setup the OS to work with LDAP, and although it may work for OS native utilities, it may turn the way Informix validates the users in the OS unusable. Informix uses getpwnam() and crypt(). If these two functions don't work exactly the same way after LDAP configuration, the Informix native authentication will fail.
  3. We may want to take advantage of the flexibility of PAM. After setting up these two modules in the PAM configuration there is nothing that prevents us from introducing other modules that implement extra functionality. The only thing to consider is that we can't send challenges to the application from those modules because we setup the pamauth to "password" instead of "challenge"

But as mentioned above, there are a few issues:
  1. When using native authentication, if we provide a password, it will validate the password or refuse the connection. In this example, if the password fails, it will try to validate the trust relation. I don't think this is a big issue... typically trust relations are created for users/servers that don't use passwords. And passwords are created for scenarios where the trust relation is not created
  2. By configuring the port with pamauth=(password) we give up the ability to use any module that sends a challenge that we would reply with a function setup with the callback mechanism
  3. The pam_rhosts module checks the trusts in the files /etc/hosts.equiv and ~utilizador/.rhosts
    As we know, starting with version 11.70, Informix can (and should) use it's own files. It would be possible, and not too complex I believe, to get the source code and change the references to these files (accepting the new ones as module parameters for example) to adapt it to version 11.7
  4. On Linux (and this didn't happen on AIX and may not happen on other Linux versions), the name passed by PAM_RHOST to the module pam_rhosts must be resolvable to an IP address. Otherwise the authentication in this module will fail


Versão Portuguesa:
Introdução
Eu abordei a a autenticação via PAM no Informix há uns anos atrás. Diria que a maioria do que está nesses artigos ainda se aplica. Mas recentemente tive de efetuar alguns testes, devido a uma situação num cliente, que podem tornar falsas algumas assunções anteriores. Para ser mais específico, acredito que exista a ideia generalizada que é impossível efetuar ligações implícitas usando PAM (por exemplo em 4GL). Este artigo irá mostrar que isso é falso. Há no entanto algumas considerações a ter em conta.
Numa situação recente ocorrida num cliente enfrentámos a seguinte situação: O cliente está a implementar um produto de terceiros que irá atuar como proxy para as ligações Informix. O produto está a ser instalado nos servidores de bases de dados e os clientes ligam-se a um porto TCP controlado por esse software (imagine o porto 1500 numa interface "externa"). O referido software irá fazer a conexão ao porto real do Informix (imagine 1501 na interface localhost). O cliente está a usar ligações implícitas (trusted connections) e explícitas (utilizador/password). Para ligações explícitas esta arquitetura não levanta qualquer questão. Mas para conexões implícitas existia (aparentemente já terá sido resolvido pelo fornecedor) um grave problema de segurança. O Informix verifica as relações de confiança (trusts) com base na origem do socket estabelecido pelo cliente. Mas agora, em vez de a origem ser a máquina clientes, a origem era a própria máquina da base de dados, feita pelo software que atua como proxy. Assim, todas as tentativas de conexão implícita estavam a ser consideradas como trusted independentemente da origem real.
Isto levou-me  a fazer alguns testes com PAM. Como saberá, o PAM é uma framework que permite às aplicações usar diferentes módulos de autenticação para efetuar a autenticação dos utilizadores. Plataformas diferentes trazem já módulos pré-instalados diferentes. O que vou demonstrar foi testado em Linux e AIX. Mas deverá funcionar com outros sistemas operativos que suportam PAM (HP-UX e Solaris). O problema é que um dos módulos poderá não estar instalado nas configurações base, ou no pior cenário pode não estar disponível para instalação. Nessa situação poderá ser necessário portar o módulo de Linux e adaptar o código fonte. Esse processo está fora do âmbito deste artigo.

Objectivo
Bem, o que se pretende alcançar é a emulação da autenticação nativa do Informix, mas usando PAM. Por omissão, mas dependendo da configuração das opções no $INFORMIXSQLHOSTS, o Informix aceita tanto ligações com utilizador e password como ligações implícitas. Para conseguir o que pretendo com PAM, necessito de configurar um serviço PAM que utilize dois mecanismos diferentes. Um para as relações de confiança e outro para a autenticação mais clássica com utilizador e password. O módulo PAM para utilizador e password é o mais fácil pois existe em todos os sistemas. Em Linux será o pam_unix e em AIX será o pam_aix. Para conseguir a funcionalidade das relações de confiança será o pam_rhosts (Linux) e pam_rhosts_auth (AIX). Não consegui encontrar este módulo para HP-UX, mas é possível adaptar a partir da versão para Linux. Existem algumas alterações que têm de ser feitas, pois o PAM em Linux tem algumas extensões ao PAM standard.
Após identificarmos os módulos temos de definir a sua configuração.

Configuração
Conforme mencionado na secção anterior, a ideia é configurar um listener usando PAM que aceite ambos os tipos de conexões. Se verificar os artigos anteriores, verifica que os módulos PAM podem ser configurados com um tipo e flag de controlo. O tipo pode ser auth, account, session ou password.
Foi também explicado que o Informix só utiliza o account e auth. Para cada tipo podemos especificar mais que um módulo. O tipo auth irá servir para verificar a identidade do utilizador (se a password está correta, se o par máquina/utilizar está trusted etc.) e o tipo account irá validar se a conta está válida etc.
A flag de controlo define o papel de cada módulo no resultado final da pilha de módulos para o mesmo tipo. Como cada tipo pode conter mais que um módulo, conseguimos assim definir por módulo de que forma o seu resultado afeta o resultado final. As opções que podemos usar são requisite, required, sufficient e optional. LinuxPAM é mais rico nas opções, mas irei manter-me no standard. Pode obter mais informações sobre tudo isto consultando as páginas do manual referentes a "pam" e "pam.conf" no seu sistema.
Portanto, já sabemos que necessitamos de módulos, tipo de módulos, flag de controlo e finalmente vamos precisar de um nome de serviço. O nome de serviço é o que quisermos usar e serve como ponte entre a configuração no Informix e no sistema de PAM.
Vou começar com a configuração de Informix e para tal vamos criar um novo porto para um listener. Vou usar o nome "cheetah_pam" como INFORMIXSERVER (ou para ser mais exato como DBSERVERALIAS). Nestes termos, necessitamos de uma nova entrada em $INFORMIXSQLHOSTS:
cheetah_pam  onsoctcp     primary 1533 s=4,pam_serv=(pam_informix),pamauth=(password)

Estou a usar "pam_informix" como nome de serviço e estou a utilizar o tipo de autenticação "password" na opção "pamauth". Os valores possíveis são "password" e "challenge".
"password" deveria ser usado em situações onde os clientes estão preparados para enviar a password e "challenge" para as outras situações (que implicariam a configuração de uma função de callback).
Mas como veremos, há algo mais sobre isto que aquilo que está explicado no manual.
Depois de adicionar o novo listener à configuração no $INFORMIXSQLHOSTS é necessário incluir isto também no $INFORMIXDIR/etc/$ONCONFIG, no parâmetro DBSERVERALIAS.

O próximo passo é configurar o stack PAM para este serviço. Isto é feito de forma ligeiramente diferente conforme estamos em Linux, AIX ou outros. Basicamente em Linux é usado um ficheiro com o nome do serviço criado em /etc/pam.d. Assim criaremos um ficheiro /etc/pam.d/pam_informix. Este ficheiro deverá conter várias linhas indicando o tipo, a flag de controlo, o nome do módulo e respetivos argumentos.
Em outros sistemas como AIX, existe apenas um ficheiro com o nome /etc/pam.conf e o nome de serviço é adicionado como primeira coluna desse ficheiro. Apesar de ambas as formas serem bastante semelhantes, julgo preferir a forma usada em Linux. Porquê? Porque tanto o /etc/pam.conf como os /etc/pam.d/* são controlados por root e um administrador se sistema não irá conceder-lhe a si, administrador de Informix, controlo sobre o ficheiro /etc/pam.conf. Mas poderá conceder controlo sobre um serviço (ficheiro - pam_informix) dentro do diretório /etc/pam.d
Enfim... Dado o que referi acima, esta será a configuração do serviço:
auth      sufficient  pam_rhosts.so debug
auth      sufficient  pam_unix.so
account   required    pam_unix.so

O que estou aqui a dizer? Como se pode ver, o tipo auth incluí dois módulos, ambos usando a flag de controlo sufficient. Isto significa que qualquer deles são suficientes para fazer o stack aceitar a autenticação. Se um falhar e o outro validar será suficiente. Se ambos falharem, então a autenticação falha.
Em relação ao account, estamos a usar o módulo pam_unix que valida se a conta é válida (não está bloqueada etc.). Para AIX as linhas seguintes teriam de ser adicionadas ao /etc/pam.conf:
pam_informix auth    sufficient /usr/lib/security/pam_rhosts_auth debug
pam_informix auth    sufficient /usr/lib/security/pam_aix
pam_informix account sufficient /usr/lib/security/pam_aix

TA diferença é que adicionámos o nome do serviço como primeira coluna do ficheiro, e os módulos são ligeiramente diferentes no nome.

Testes
Executei vários testes para validar esta implementação. Utilizei uma máquina virtual onde estou a correr a instância onde adicionei o listener cheetah_pam, e utilizei o SO nativo como cliente. O nome do meu cliente é "PTxxxxxx" e criei um utilizador com o mesmo nome no ambiente Linux. Também configurei uma instância numa máquina AIX e usei o Linux como cliente para executar alguns testes com conectividade 4GL.
O primeiro cenário é a instância Informix no Linux e o cliente no PC (Windows). Tenho dois ficheiros SQL:
C:\Programas\InformixClientSDK_370_tc7_c86>type *.sql

test_explicit.sql

CONNECT TO 'stores' USER 'PCxxxxxx' USING 'mypassword';
SELECT USER FROM sysmaster:sysdual;

test_implicit.sql

CONNECT TO 'stores';
SELECT USER FROM sysmaster:sysdual;

C:\Programas\InformixClientSDK_370_tc7_c86>

Vejamos o que acontece quando executo ambos os scripts:
C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_implicit.sql

 1809: Server rejected the connection.
Error in line 1
Near character position 1

C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_explicit.sql

Connected.

(expression)

PTxxxxxx
1 row(s) retrieved.

Disconnected.

C:\Programas\InformixClientSDK_370_tc7_c86>

Ok.As ligações implícitas estão a falhar, mas as que usam utilizador e password estão a funcionar. Isto é esperado porque não criei as relações de confiança na máquina Linux. Verifiquemos o syslog dessa máquina:
tail -20 /var/log/secure

[...]
Sep  4 06:52:07 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:07 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:09 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:09 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:12 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:12 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:14 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:14 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:17 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
Sep  4 06:52:17 primary oninit: pam_unix(pam_informix:auth): authentication failure; logname= uid=1002 euid=0 tty= ruser=PTxxxxxx rhost=192.168.142.1  user=PTxxxxxx
Sep  4 06:52:32 primary oninit: pam_rhosts(pam_informix:auth): denied access to PTxxxxxx@192.168.142.1 as PTxxxxxx
As primeiras pertencem à tentativa implícita e a última à explícita. Como pode ver, para a ligação implícita ambos os módulos reportam falha na autenticação, mas para a explícita apenas o pam_rhosts reporta falha. O pam_unix teve sucesso e como usamos sufficient isso basta para aceitar a conexão.
Agora vamos configurar a relação de confiânça. Para tal vou adicionar"PTxxxxxx   PTxxxxxx" ao ficheiro /etc/hosts.equiv.Vamos repetir o teste:

C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_implicit.sql

Connected.

(expression)

PCxxxxxx

1 row(s) retrieved.

Disconnected.

C:\Programas\InformixClientSDK_370_tc7_c86>

e agora vamos remover essa linha do /etc/hosts.equiv e adicioná-la ao ~PTxxxxxx/.rhosts
Conforme esperado:

C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_implicit.sql

Connected.

(expression)

PTxxxxxx

1 row(s) retrieved.

Disconnected.

C:\Programas\InformixClientSDK_370_tc7_c86>

Um teste final com este ambiente: Vamos remover a relação de confiança e tentar com uma password errada (modificando o ficheiro test_explicit.sql file):

C:\Programas\InformixClientSDK_370_tc7_c86>dbaccess - test_explicit.sql

 1809: Server rejected the connection.
Error in line 1
Near character position 1

C:\Programas\InformixClientSDK_370_tc7_c86>

Portanto, isto prova que no dbaccess podemos emular o comportamento nativo usando PAM. Mas e em relação às outras camadas de conectividade, como ODBC, .NET, 4GL etc.?
Bom... Tentei com tudo o que me ocorreu e só o OleDB falhou:
  • ConnectTestDemo (ok)
    (ferramenta de conectividade fornecida com o ClientSDK para Windows
  • ODBC (ok)
    (o habitual gestor de ODBC fornecido com o Windows)
  • VBScript que usa um data source ODBC (ok)
  • VBScript que usa OleDB (failhou!)
  • Ferramenta Java que usa JDBC (ok)
  • Cliente C# que usa  .NET (ok)
  • 4GL 7.50.UC4 nativo (ok)
Como referido acima, o cliente OleDB falhou, porque explicitamente não suporta PAM. Na verdade quando se tenta usar obtemos a seguinte mensagem no online.log:

10:35:24  listener-thread: err = -1809: oserr = 0: errstr = Client without PAM support connecting to PAM port: Server rejected the connection.

Portanto isto é declaradamente uma limitação no produto (atualmente). Acredito que isto poderia ser removido se assumirmos que passa a funcionar em modo de password e não em challenge. Fizémos isso muito recentemente para DRDA com PAM. Suspeito, mas não consigo ter a certeza, que a limitação deriva do facto de os clientes OleDB não podere especificar uma função de callback como fazemos em JDBC ou ESQL/C. E isso pode ser a fonte desta limitação. Seria melhor limitar o uso de PAM com OleDB a configurações exclusivamente com password.

Conclusões e problemas
Este pequeno exercício prova que podemos simular (com algumas diferenças mencionadas abaixo) o comportamento nativo usando PAM. Mas porque quereríamos fazer isso? Bom, consigo listar alguns motivos:
  1. Podemos querer validar o utilizador/password contra um repositório diferente do SO nativo (ou mais simplesmente mudar o mecanismo de encriptação e o Informix pode ser sensível a isso)
  2. Podemos configurar o SO para trabalhar com LDAP, e ainda que isso possa funcionar bem com utilitários nativos, pode quebrar a forma como o Informix valida os utilizadores no SO. O Informix utiliza as funções getpwnam() e crypt(). Se estas funções não funcionarem exatamente da mesma forma que antes da configuração para LDAP a autenticação Informix pode falhar
  3. Podemos querer aproveitar a flexibilidade do PAM. Depois de configurar estes dois módulos PAM, não há nada que nos impeça de introduzir outros módulos que forneçam outras funcionalidades. A única coisa que temos de manter em mente é que não podemos aceitar "challenges" desses módulos, dado que configurámos o serviço com a opção pamauth=(password) em vez de "challenge"
Mas como escrevi antes, existem alguns problemas:
  1. Ao usar a autenticação nativa, se fornecermos uma password, ira validar a password ou recusar a conexão. Neste exemplo, se a validação cotnra a password falhar vai tentar verificar a relação de confiança. Não me parece que isto seja um grande problema... Normalmente as relações de confiança são criadas para utilizadores que não têm password. E vice-versa, os utilizadores que usam passwords não costumam ter relações de confiança criadas
  2. Ao configurar o porto com pamauth=(password) abdicamos da possibilidade de usar algum módulo que envie um challenge, ao qual poderíamos responder com uma função configurada com o mecanismo de callback
  3. O módulo pam_rhosts valida as relações de confiança nos ficheiros /etc/hosts.equiv e ~utilizador/.rhosts
    Como sabemos, a partir da versão 11.70 o Informix pode (e deve) usar os seus próprios ficheiros. Seria possível e não demasiado complicado obter o código fonte do módulo, alterar estes ficheiros (aceitando-os como parâmetro do módulo por exemplo) para o adaptar à versão 11.7
  4. Em Linux (e isto não aconteceu em AIX e pode não acontecer noutras versões de Linux), o nome que é passado no PAM_RHOST ao módulo pam_rhosts tem de ser passível de resolução para endereço IP ou a autenticação neste módulo falhará

Monday, January 09, 2012

DNS impact on Informix / Impacto do DNS no Informix

This article is written in English and Portuguese
Este artigo está escrito em Inglês e Português

English version:

NOTE (1 Feb 2014): A new article presents a workaround for the issue covered in this post (http://informix-technology.blogspot.pt/2014/02/dns-changes-ok-mudancas-no-dns-ok.html)
 

You decided...

This article if the first "on-demand" topic I write about. I had a few options for topics I'd like to cover and I initiated a poll on a Facebook page. The impact of DNS on Informix was the most voted. So you asked for it, here it is. I will probably keep doing this from now on.
Personally I also like this topic, partially because I had several problems related to this in more than one customer. This is a relatively common issue in complex environments.
I have a public and generic disclaimer about the material I publish here, but for this case I'd like to stretch this just a bit... Beside the normal disclaimer, I'd like to state that most of the information presented here lies a bit far from my regular competencies. This is the result of a lot of digging and investigation over the time (not only from me), and there may be a few (hopefully minor) errors on the information provided. Feel free to comment, email me etc.

Very short introduction about DNS

DNS is the acronym for Domain Name System which is a protocol/service that is able to convert a hostname (e.g. onlinedomus.com)  into an IP address (e.g. 89.1.2.3) and the opposite. It can also do a lot of other stuff, like telling which mail server is responsible for a specific domain etc. but this is out of the scope of the article.
Without DNS there would be no Internet as we know it. It's a critical component of the Internet infra-structure and it's performance and security is crucial for us, the users.
In a small network you can work without DNS for the basic name resolution functions, by using files. But if your network is larger, it can be very hard to use those files.
The DNS system uses an hierarchical architecture and the UDP protocol (U does not stand for unreliable but it could be...) for performance reasons. Proper configuration of a DNS system can be a complex task, and my personal experience tells me it's not very easy to find who knows how to do it properly. Furthermore, many times people don't realize the terrible impact a DNS misconfiguration or malfunction may have on the systems.
I will not explain (and I wouldn't know how to) all the DNS configuration aspects, but I'd like to reference a few points:

  • /etc/nsswitch.conf
    This file (on Unix/Linux systems, but the name can vary) defines how the name resolution (and other services) are used. In particular it can define if the system uses files, NIS, the DNS servers or other mechanism and the order it uses. As an example, a line like:

    hosts: dns files

    indicates that for hostname lookups the system will first ask the DNS servers and then looks in the files

  • /etc/hosts
    This file can map IP addresses into hostnames (and vice-versa). As an example:

    89.1.2.3 www.onlinedomus.com onlinedomus.com

    This tells the system that the IP address 89.1.2.3 will map to "www.onlinedomus.com" (and vice-versa). As you can imagine, a lookup for "onlinedomus.com" will also map to the same IP address.

  • /etc/resolv.conf
    This contains the list of DNS servers that will be used for lookups and possibly a few other options (like requests timeout, names of domains that will be appended to simple hostnames, if the lookups for those hostnames fail etc.). An example:

    nameserver 192.168.112.2
    nameserver 9.64.162.21

How does Informix use the DNS?

From the DNS perspective, Informix is just another application. The /etc/nsswitch.conf file will tell if Informix will use the files (/etc/hosts) or the DNS servers (specified in /etc/resolv.conf). The first important thing to note is that all interaction between Informix and the DNS system goes through system calls. In particular these two functions or their equivalents or replacements:
  • gethostbyname()
    In short, this receives an hostname and returns a structure containing the IP address
  • gethostbyaddr()
    This receives an IP address and returns the hostname that matches it
So, if something is not working, we need to understand how/when Informix calls these two functions and how they work. Typically customers blame Informix when it's not to blame (not completely, but more on this later). Most (if not all) problems I've seen in DNS affecting Informix can affect other applications and are reproducible with very short C language programs. This does not mean we couldn't do a few things differently (better?). But again, I'll cover this later (keep in mind the disclaimer! :) )
This article is written mainly from the database server perspective. But the DNS has obvious implications for the client too... Let's start there and then I'll jump into the server.
When a client tool tries to connect to an Informix server, it starts by looking up the $INFORMIXSERVER (or equivalent given in the connection string) in the $INFORMIXSQLHOSTS file (for Java it can look up LDAP or HTTP servers for the info, but let's stick to the files for easier understanding). The file contains lines in the following format:
INFORMIXSERVER PROTOCOL HOSTNAME/IP_ADDRESS PORT_NUMBER/SERVICE_NAME OPTIONS
when the client libraries find the line matching the INFORMIXSERVER, they check the hostname (or IP  address) and the port number (or service name).
Typically, if a service name is used, we look the port number in /etc/services (this can be configured in /etc/nsswitch.conf). Personally I tend to use the port number to avoid that lookup...
Then, if a hostname is used, the client must map it to an IP address. For that it calls the gethostbyname() function. This function will behave as specified in /etc/nsswitch.conf, and will try to map the name to an IP address. A failure to do that will raise error -930. This can be reproduced:
cheetah@pacman.onlinedomus.com:fnunes-> echo $INFORMIXSERVER; grep $INFORMIXSERVER $INFORMIXSQLHOSTS; dbaccess sysmaster -
blogtest
blogtest     onsoctcp     nowhere.onlinedomus.com 1500

930: Cannot connect to database server (nowhere.onlinedomus.com).
cheetah@pacman.onlinedomus.com:fnunes->
and if you need evidences of what's going on behind the scenes we can use strace (or truss):
strace -o /tmp/strace.out dbaccess sysmaster -
This is an edited extract of /tmp/strace.out generated by the command above. If you have the patience, you can see it doing the following:
  1. Open /etc/nsswitch.conf
  2. Open $INFORMIXSQLHOSTS (/home/informix/etc/sqlhosts)
  3. Open /etc/services (exceptionally I used a name instead of a port number)
  4. Open /etc/resolv.conf to find out the configured nameservers
  5. Open a socket to 192.168.112.2 (my configured DNS server)
  6. Ask for nowhere.onlinedomus.com
  7. Open /etc/hosts (in /etc/nsswtich.conf I configured to search the files if the DNS lookup fails)
  8. Read the error message from the Informix message files
  9. Write the error message to stderr
  10. Exit with error code -1
cheetah@pacman.onlinedomus.com:fnunes-> cat /tmp/strace.out
[...]
open("/etc/nsswitch.conf", O_RDONLY)    = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=1803, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7862000
read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 4096) = 1803
read(3, "", 4096)                       = 0
close(3)                                = 0
[...]
open("/home/informix/etc/sqlhosts", O_RDONLY|O_LARGEFILE) = 4
_llseek(4, 0, [0], SEEK_SET)            = 0
read(4, "blogtest     onsoctcp     nowher"..., 4096) = 1389
[...]
open("/etc/services", O_RDONLY|O_CLOEXEC) = 4
fstat64(4, {st_mode=S_IFREG|0644, st_size=644327, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7862000
read(4, "# /etc/services:\n# $Id: services"..., 4096) = 4096
close(4)                                = 0
[...]
open("/etc/resolv.conf", O_RDONLY)      = 4
[...]
read(4, "", 4096)                       = 0
close(4)                                = 0
[...]
open("/lib/libresolv.so.2", O_RDONLY)   = 4
read(4, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0Pf\256\0004\0\0\0"..., 512) = 512
[...]
close(4)                                = 0
[...]
socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4
connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
gettimeofday({1325590403, 502576}, NULL) = 0
poll([{fd=4, events=POLLOUT}], 1, 0)    = 1 ([{fd=4, revents=POLLOUT}])
send(4, "tl\1\0\0\1\0\0\0\0\0\0\7nowhere\vonlinedomus"..., 41, MSG_NOSIGNAL) = 41
poll([{fd=4, events=POLLIN}], 1, 5000)  = 1 ([{fd=4, revents=POLLIN}])
ioctl(4, FIONREAD, [101])               = 0
recvfrom(4, "tl\201\203\0\1\0\0\0\1\0\0\7nowhere\vonlinedomus"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, [16]) = 101
close(4)                                = 0
[...]
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 4
[...]
read(4, "127.0.0.1\tpacman1.onlinedomus.ne"..., 4096) = 439
[...]
close(4)                                = 0
[...]
read(3, "Cannot connect to database serve"..., 40) = 40
write(2, "\n", 1)                       = 1
write(2, "  930: Cannot connect to databas"..., 68) = 68
exit_group(-1)                          = ?
cheetah@pacman.onlinedomus.com:fnunes->

Now, we should move to the Informix server side. This requires a bit more work and preliminary explanations. To start, we must understand what the engine needs in order to establish the connection. One of those things is to do a reverse name lookup (IP address to hostname). This is not essential, but it's always tried. Informix may need the hostname for trust relation validation and to provide information to the DBA.
As you know, the Informix database engine comprises several operating system processes. From the OS perspective they all look the same (oninit), but every one has a specific role and runs certain engine threads. We can see the threads with:
panther@pacman.onlinedomus.com:fnunes-> onstat -g ath

IBM Informix Dynamic Server Version 11.70.UC4 -- On-Line -- Up 00:17:01 -- 411500 Kbytes

Threads:
 tid     tcb      rstcb    prty status                vp-class       name
 2       5583fa38 0        1    IO Idle                 3lio*        lio vp 0
 3       558551f8 0        1    IO Idle                 4pio*        pio vp 0
 4       5586b1f8 0        1    IO Idle                 5aio*        aio vp 0
 5       558811f8 8f59dc0  1    IO Idle                 6msc*        msc vp 0
 6       558af1f8 0        1    IO Idle                 7fifo*       fifo vp 0
 7       558c9590 0        1    IO Idle                 9aio*        aio vp 1
 8       558df3b8 54267018 3    sleeping secs: 1        8cpu         main_loop()
 9       559276f8 0        1    running                10soc*        soctcppoll
 10      5593ed18 0        2    sleeping forever        1cpu*        soctcplst
 11      55927d20 542675fc 1    sleeping secs: 1        8cpu         flush_sub(0)
 12      55988018 54267be0 1    sleeping secs: 1        8cpu         flush_sub(1)
 13      559881f0 542681c4 1    sleeping secs: 1        8cpu         flush_sub(2)
 14      559883c8 542687a8 1    sleeping secs: 1        8cpu         flush_sub(3)
 15      559885a0 54268d8c 1    sleeping secs: 1        8cpu         flush_sub(4)
 16      55988778 54269370 1    sleeping secs: 1        8cpu         flush_sub(5)
 17      55988bf0 54269954 1    sleeping secs: 1        8cpu         flush_sub(6)
 18      559fb468 54269f38 1    sleeping secs: 1        8cpu         flush_sub(7)
 19      559fb640 0        3    IO Idle                 8cpu*        kaio
 20      55ab6018 5426a51c 2    sleeping secs: 1        8cpu         aslogflush
 21      55ab6960 5426ab00 1    sleeping secs: 92       1cpu         btscanner_0
 22      55b6a408 5426b0e4 3    cond wait  ReadAhead    1cpu         readahead_0
 39      55bcd5c8 0        3    IO Idle                 1cpu*        kaio
 40      55bcd7a0 5426bcac 3    sleeping secs: 1        1cpu*        onmode_mon
 41      55d3e148 5426c874 3    sleeping secs: 1        8cpu         periodic
 49      55e80a78 5426da20 1    sleeping secs: 177      1cpu         dbScheduler
 51      55f340f8 5426d43c 1    sleeping forever        1cpu         dbWorker1
 52      55f34d80 5426ce58 1    sleeping forever        8cpu         dbWorker2
 59      562ee228 5426e5e8 1    cond wait  bp_cond      1cpu         bf_priosweep()
And the OS processes with:
panther@pacman.onlinedomus.com:fnunes-> onstat -g glo

IBM Informix Dynamic Server Version 11.70.UC4 -- On-Line -- Up 00:18:48 -- 411500 Kbytes

MT global info:
sessions threads  vps      lngspins
0        29       10       3       

          sched calls     thread switches yield 0   yield n   yield forever
total:    9589515         8992470         597961    14485     4457836  
per sec:  0               0               0         0         0        

Virtual processor summary:
 class       vps       usercpu   syscpu    total   
 cpu         2         11.51     94.06     105.57  
 aio         2         3.57      75.44     79.01   
 lio         1         0.01      0.01      0.02    
 pio         1         0.00      0.01      0.01    
 adm         1         0.01      0.15      0.16    
 soc         1         0.04      0.15      0.19    
 msc         1         0.00      0.01      0.01    
 fifo        1         0.00      0.01      0.01    
 total       10        15.14     169.84    184.98  

Individual virtual processors:
 vp    pid       class       usercpu   syscpu    total     Thread    Eff  
 1     29395     cpu         5.63      46.80     52.43     66.41     78%
 2     29398     adm         0.01      0.15      0.16      0.00       0%
 3     29399     lio         0.01      0.01      0.02      0.02     100%
 4     29400     pio         0.00      0.01      0.01      0.01     100%
 5     29401     aio         3.29      74.30     77.59     77.59    100%
 6     29402     msc         0.00      0.01      0.01      0.03      31%
 7     29403     fifo        0.00      0.01      0.01      0.01     100%
 8     29404     cpu         5.88      47.26     53.14     64.45     82%
 9     29405     aio         0.28      1.14      1.42      1.42     100%
 10    29406     soc         0.04      0.15      0.19      NA         NA
                 tot         15.14     169.84    184.98
The threads that are listening on the engine TCP ports are the poll threads (soctcppoll) running on SOC class (this depends on the NETTYPE parameter). When a new request is received by them they call the listener threads (soctcplst) running on the cpu class to initiate the authentication process. Parts of this task are run by the MSC virtual processor. As we can see in the last output this has the PID 29402. So, in order to see what happens I'll trace that OS process. For reasons that I'll explain later, I will turn off the NS_CACHE feature (Informix 11.7) and I will restart the engine. So, for the first connection attempt we get (some parts cut off):
1      0.000000 semop(753664, {{5, -1, 0}}, 1) = 0
2      7.009868 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
3      0.000107 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
4      0.000242 close(3)                  = 0
5      0.000060 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
6      0.000063 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
7      0.000095 close(3)                  = 0
8      [...]
9      0.000000 open("/etc/resolv.conf", O_RDONLY) = 3
10     0.000000 fstat64(3, {st_mode=S_IFREG|0644, st_size=55, ...}) = 0
11     0.000000 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xab4000
12     0.000000 read(3, "# Generated by NetworkManager\nna"..., 4096) = 55
13     0.000926 read(3, "", 4096)         = 0
14     0.000050 close(3)                  = 0
15     [...]
16     0.000057 futex(0x29ab44, FUTEX_WAKE_PRIVATE, 2147483647) = 0
17     0.000256 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
18     0.000089 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
19     0.000107 gettimeofday({1325605320, 167025}, NULL) = 0
20     0.000072 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
21     0.000083 send(3, "\363\337\1\0\0\1\0\0\0\0\0\0\0011\003112\003168\003192\7in-ad"..., 44, MSG_NOSIGNAL) = 44
22     0.000322 poll([{fd=3, events=POLLIN}], 1, 5000) = 1 ([{fd=3, revents=POLLIN}])
23     2.061369 ioctl(3, FIONREAD, [121]) = 0
24     0.000111 recvfrom(3, "\363\337\201\203\0\1\0\0\0\1\0\0\0011\003112\003168\003192\7in-ad"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, [16]) = 121
25     0.000155 close(3)                  = 0
26     0.000090 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
27     0.000377 fstat64(3, {st_mode=S_IFREG|0644, st_size=439, ...}) = 0
28     0.000089 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf22000
29     0.000057 read(3, "127.0.0.1\tpacman1.onlinedomus.ne"..., 4096) = 439
30     0.000130 close(3)                  = 0
31     [...]
32     0.000072 semop(753664, {{7, 1, 0}}, 1) = 0
33     0.000069 semop(753664, {{7, 1, 0}}, 1) = 0
34     0.007558 semop(753664, {{5, -1, 0}}, 1 

Please note that for clarity I added line numbers and time differences between each call (this will be important later). Let's explain this.
  • On line 1) we have a semop() which is the way MSC VP stays idle. That was before the connection attempt.
  • 7 seconds later it tries to "talk" with nscd daemon (lines 2-8). This is kind of a Linux specific mechanism that I'm not running. Then accesses the /etc/nsswitch.conf.  Just keep in memory that it did this on the first attempt
  • Then it accesses /etc/resolv.conf (lines 9-15) and finds the nameserver address
  • On lines 16-25 it talks to the DNS server (192.168.112.2) and asks for the reverse name of the connecting IP address
  • Since the answer is inconclusive, it goes to /etc/hosts (lines 26-31)
  • I have cut the remaining part which is related to the authentication (opening the /etc/passwd, /etc/group, /etc/shadow etc.).
  • Finally it returns to the normal idle state
A few important points about this:
  1. It all happened pretty quick (values are in seconds)
  2. We don't see the gethostbyaddr() call. This is not a "system call" for strace. So we see the lower level calls, but not the gethostbyaddr() function. We can catch it by attaching a debugger to the same process. This is important because usually it's hard to discuss this issues with the network and OS administrators because they tend to assume all this is done by Informix. It isn't! Informix just calls gethostbyaddr() (or equivalent fiunctions)
Now, let's see the same for the second attempt. We would expect it to be the same, but it isn't. I have cut exactly the same parts:
1      0.000000 semop(753664, {{5, -1, 0}}, 1) = 0
2      6.452154 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
3      0.000099 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
4      0.008816 gettimeofday({1325605445, 534040}, NULL) = 0
5      0.000089 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
6      0.000100 send(3, "\233\t\1\0\0\1\0\0\0\0\0\0\0011\003112\003168\003192\7in-ad"..., 44, MSG_NOSIGNAL) = 44
7      0.000417 poll([{fd=3, events=POLLIN}], 1, 5000) = 1 ([{fd=3, revents=POLLIN}])
8      2.089726 ioctl(3, FIONREAD, [121]) = 0
9      0.000118 recvfrom(3, "\233\t\201\203\0\1\0\0\0\1\0\0\0011\003112\003168\003192\7in-ad"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, [16]) = 121
10     0.000132 close(3)                  = 0
11     0.000069 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
12     0.000102 fstat64(3, {st_mode=S_IFREG|0644, st_size=439, ...}) = 0
13     0.000092 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xe1f000
14     0.000064 read(3, "127.0.0.1\tpacman1.onlinedomus.ne"..., 4096) = 439
15     0.000099 close(3)                  = 0
16     [...]
17     0.000068 semop(753664, {{0, 1, 0}}, 1) = 0
18     0.000096 semop(753664, {{0, 1, 0}}, 1) = 0
19     0.000076 semop(753664, {{5, -1, 0}}, 1 

So, again the analysis:
  • The first part accessing the nscd daemon, the /etc/nsswitch.conf and the /etc/resolv.conf is completely gone.
    As you can see it starts by connecting to the DNS.
  • Then it reads the files (/etc/hosts)
  • Then I cut the authentication part as before
  • Finally it returns to the idle state
The important point to note here is that the first attempt is different from the others. And again, Informix just calls gethostbyaddr()... just the same call each time. Well... to be correct the function we call may depend on the platform. As I mentioned earlier, only by using a debugger we can find the gethostbyaddr() call. I've done it and here is the result:
(gdb) break connect
Breakpoint 1 at 0x95f640
(gdb) continue
Continuing.

Breakpoint 1, 0x0095f640 in connect () from /lib/libpthread.so.0
(gdb) where
#0  0x0095f640 in connect () from /lib/libpthread.so.0
#1  0x00aec9ab in reopen () from /lib/libresolv.so.2
#2  0x00aee542 in __libc_res_nsend () from /lib/libresolv.so.2
#3  0x00aeb24e in __libc_res_nquery () from /lib/libresolv.so.2
#4  0x002b6dc7 in _nss_dns_gethostbyaddr2_r () from /lib/libnss_dns.so.2
#5  0x002b6f1a in _nss_dns_gethostbyaddr_r () from /lib/libnss_dns.so.2
#6  0x0020890b in gethostbyaddr_r@@GLIBC_2.1.2 () from /lib/libc.so.6
#7  0x00211f77 in getnameinfo () from /lib/libc.so.6
#8  0x08c0e664 in ifx_getipnodebyaddr ()
#9  0x08c0f79c in ifx_gethostbyaddr ()
#10 0x08c0f8a2 in __osgethostbyaddr ()
#11 0x08b0c055 in aio_workon ()
#12 0x08b0c9c3 in aiothread ()
#13 0x08b0dbcb in iothread ()
#14 0x08b00762 in startup ()
#15 0x558749e8 in ?? ()
#16 0x00000000 in ?? ()
(gdb)


As you can see I've setup a breakpoint for connect(). Then I "continue" the execution and I try the connection. gdb stops the program at the breakpoint and I get the stack trace (read bottom up).
So, it shows we call getnameinfo() which in turn calls gethostbyaddr_r() etc. All this belongs to the system libraries, not to Informix code.

There are two additional considerations we need to be aware. First, the Informix MSC VP processes it's requests in a serial manner. For each connection it asks what it needs from the DNS servers and/or files and makes the authentication. By default we only have one MSC VP... so if one request gets stuck.... yes... the following connections will suffer delays. This delays can be a fraction of second, or a few seconds, but on some systems I've seen tens (heard about hundreds) of connections per second, so even a few seconds will have large impact.
The second consideration relates to the differences between the first call and the subsequent ones. As we've seen above, on the first call the process checks the configuration (the /etc/nsswitch.conf and /etc/resolv.conf files). After that first check it does not do that anymore. And this causes a problem. Again this is the behavior of the system functions (but not necessarily the end of the story....)

So, hopefully I was able to explain how Informix interacts with the DNS system. The important technical deep dive should be over. We'll proceed to the implications. It's important you understand all the above before proceeding to the next paragraphs.

What problems can we face?

Above I've tried to show you how things work when everything is ok. But what happens when something is wrong? Let's see what can go wrong first and then the implications. I'll also try to explain who to blame (and again the disclaimer...). The purpose of course is not to finger point, but knowing where the problem lies is the first step to solve it.
  1. Network problems prevent the connection to the DNS servers
    If this happens, the requests sent by the MSC VP will have to timeout (typically a few seconds) before the OS call returns. This delay will cause all the other connection requests to stay on hold (assuming we just have one MSC VP). If the network problems persist, it really doesn't matter how many MSC VPs we have, since they'll all get stuck and all our connection attempts will suffer delays

  2. The DNS server dies, is stopped, or is extremely slow
    The effect of this is very similar to the previous. Anything that causes delays in the DNS requests will potentially cause delays in the engine connections. Note that this can be a generic issue that affects all the requests, or it can affect only some requests. Due to the hierarchical and distributed nature of the DNS system, it may be able to answer most requests, but get slow or event try to "talk" to an unavailable server for some specific names or IP addresses. Needless to say this makes the debug of these problems very difficult to do

  3. Something goes wrong with the DSN system. The reverse lookups fail and this affects the trusted connections.
    You start to see -956 errors in the online.log and -951 is sent to the client side

  4. You need to change your DNS servers
    Then you''ll need to restart Informix. This is a nasty problem caused by Informix (we could do better). As I mentioned above, the OS function calls keep the configurations in memory for efficiency reasons (it would be very "expensive" to re-read that for each call). So, the problem in how it works is that if you want to change the policy (/etc/nsswitch.conf) or the DNS servers (/etc/resolv.conf), the Informix processes will not pick up the change. I can assure you that Informix is not the only daemon that suffers with this. The first time I had a similar problem with this, I noticed that for example sendmail was also suffering... (trying to talk with the older servers)
From the above problems it's easy to understand that the three first are not Informix's fault. When a customer decides (presumably because it needs) to use a DNS infra-structure people have to understand that it automatically becomes a critical component of the system. Any problems in it will cause problems in the layers above, specifically in the Informix system.
And that leaves us with the forth problem. I wrote before and it's true that this is how the functions work (and I'll show this in action), so why do I write that this is Informix's fault? Well, because there is a way to handle this. There is another system call named res_init() that does precisely what we need. From the Linux manual, I quote:

The res_init() function reads the configuration files (see resolv.conf(5)) to get the default domain name, search order and name server address(es).
If no server is given, the local host is tried. If no domain is given, that associated with the local host is used. It can be overridden with the environment variable LOCALDOMAIN. res_init() is normally executed by the first call to one of the other functions
.


So, my point is that Informix should provide a way by which the DBA would request all the MSC VPs to call this function. This would refresh the information that is cached by the first call.
In fact IBM has a pending feature request about this. I really hope this could be implemented in a future release. This is something that we may live without for years, but if you need it, it really would make a difference

Knowing if you have the problem

How do we know that we have a problem in DNS and that Informix is being impacted? Typically you'll get some sort of complain from the Informix clients. Usually something like "the database is terribly slow", or "it takes a long time to connect", or eventually you'll have connections refused with server side error -956 (not trusted). In extreme situations you can have weird errors in the online.log (-25xxx ). In all these situations you'll notice that the already existing sessions are working smoothly.
But in order to be sure you may follow two paths:
  1. The most simple is to run a "netstat -a" command on the database server. This shows all the TCP/UDP connections to and from the machine. By default it will go through all the socket connections and will try to reverse lookup the respective IP addresses. If you're having problems you'll see that the netstat output will be very slow or at least with some "bumps". But for this simple tests to provide meaningful conclusions, you must be sure that the system is still configured to use the same configuration (/etc/nsswitch.conf and /etc/resolv.conf) that was in place when the Informix engine was started. Otherwise you'll not be comparing apples to apples

  2. The most complex is to run a truss or strace command against the MSC VP with timings. This can show slow response from the calls to the DNS hosts. Be aware that running truss/strace requires root privileges and that even if everything is running fine, it will cause delays on systems with a large number of connects per second
I wrote earlier that I could demonstrate the facts presented here without using Informix. For that I created a simple program (line numbers included):

1  #include <sys/time.h>
2  #include <netdb.h>
3  #include <resolv.h>
4  #include <stdlib.h>
5  #include <string.h>
6  
7  int main(int argc, char **argv)
8  {
9    struct hostent *hp;
10    in_addr_t data;
11    char buff[100];
12    struct timeval ts_initial, ts_final;
13  
14    if (argc == 2) {
15      strcpy(buff,argv[1]);
16    }
17    else {
18      printf("Introduce an IP address: ");
19      if (fscanf(stdin,"%s", buff) == EOF)
20        exit(0);
21    }
22  
23    while (1 == 1) {
24      data = inet_addr(buff);
25      gettimeofday(&ts_initial, NULL);
26      hp = gethostbyaddr(&data, 4, AF_INET);
27      gettimeofday(&ts_final, NULL);
28  
29      if (hp == NULL) {
30        printf("Unknown host (%s). Took %f seconds\n", buff, (double)((ts_final.tv_sec * 1000000 + ts_final.tv_usec) - (ts_initial.tv_sec * 1000000 + ts_initial.tv_usec))/1000000);
31      }
32      else {
33        printf("Name (%s): %s Took %f seconds\n", buff, hp->h_name, (double)((ts_final.tv_sec * 1000000 + ts_final.tv_usec) - (ts_initial.tv_sec * 1000000 + ts_initial.tv_usec))/1000000);
34      }
35      printf("Next: ");
36      if (fscanf(stdin,"%s", buff) == EOF)
37        exit(0);
38      if ( strncmp("refresh", buff, 7) == 0 )
39      {
40         res_init();
41         printf("Called res_init()\n");
42         printf("Next: ");
43         if (fscanf(stdin,"%s", buff) == EOF)
44           exit(0);
45      }
46    }
47  }

This is basically a loop that reads an IP address (which are not validated, so it's easily breakable), and runs gethostbyaddr() on it. If the "ip" provided is "refresh" then it calls res_init(). It reports the information returned by the resolver subsystem and the time it took. You can use it interactively or redirect a file with one IP address per line.

I'll run it interactively with tracing in order to show the effect of calling res_init(). I have "hosts: dns,files" in /etc/nsswitch.conf and 192.168.112.2 on /etc/resolv.conf. This file also contains options do define the timeout to 2 seconds. So I call this with:
cheetah@pacman1.onlinedomus.net:fnunes-> strace -r -o test_resolv.trace ./test_resolv
Introduce an IP address: 1.1.1.1
Unknown host (1.1.1.1). Took 2.010235 seconds
Next: 1.1.1.2
Unknown host (1.1.1.2). Took 2.003324 seconds
Next: refresh
Called res_init()
Next: 1.1.1.3
Unknown host (1.1.1.3). Took 2.004002 seconds
Next: ^C
Before I introduce "refresh" I change the DNS nameserver in /etc/resolv.conf from 192.168.112.2 to 192.168.112.5.
The trace is this (line numbers and timmings added):
1        [...]
2        0.000000 write(1, "Introduce an IP address: ", 25) = 25
3        0.000000 read(0, "1.1.1.1\n", 1024) = 8
4        3.055699 gettimeofday({1325865284, 104186}, NULL) = 0
5        [...]
6        0.000160 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
7        0.000099 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ECONNREFUSED (Connection refused)
8        0.000128 close(3)                  = 0
9        0.000053 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
10       0.000123 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ECONNREFUSED (Connection refused)
11       0.000122 close(3)                  = 0
12       0.000095 open("/etc/nsswitch.conf", O_RDONLY) = 3
13       0.000108 fstat64(3, {st_mode=S_IFREG|0644, st_size=1803, ...}) = 0
14       0.000101 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
15       0.000057 read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 4096) = 1803
16       0.000001 read(3, "", 4096)         = 0
17       0.000000 close(3)                  = 0
18       [...]
19       0.000055 open("/etc/resolv.conf", O_RDONLY) = 3
20       0.000072 fstat64(3, {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
21       0.000095 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
22       0.000050 read(3, "# Generated by NetworkManager\nna"..., 4096) = 118
23       0.000086 read(3, "", 4096)         = 0
24       0.000046 close(3)                  = 0
25       [...]
26       0.000173 open("/etc/host.conf", O_RDONLY) = 3
27       0.000068 fstat64(3, {st_mode=S_IFREG|0644, st_size=26, ...}) = 0
28       0.000081 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
29       0.000179 read(3, "multi on\norder hosts,bind\n", 4096) = 26
30       0.000083 read(3, "", 4096)         = 0
31       0.000048 close(3)                  = 0
32       0.000049 munmap(0xb7843000, 4096)  = 0
33       0.000160 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
34       0.000075 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
35       0.000640 gettimeofday({1325865284, 108850}, NULL) = 0
36       0.000062 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
37       0.000094 send(3, "r\363\1\0\0\1\0\0\0\0\0\0\0011\0011\0011\0011\7in-addr\4arp"..., 38, MSG_NOSIGNAL) = 38
38       0.000889 poll([{fd=3, events=POLLIN}], 1, 2000) = 0 (Timeout)
39       2.003373 close(3)                  = 0
40       0.000109 open("/etc/ld.so.cache", O_RDONLY) = 3
41       0.000078 fstat64(3, {st_mode=S_IFREG|0644, st_size=72238, ...}) = 0
42       0.000093 mmap2(NULL, 72238, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7821000
43       0.000054 close(3)                  = 0
44       [...]
45       0.000105 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
46       0.000097 fcntl64(3, F_GETFD)       = 0x1 (flags FD_CLOEXEC)
47       0.000065 fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
48       0.000053 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
49       0.000035 read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
50       0.000137 read(3, "", 4096)         = 0
51       0.000130 close(3)                  = 0
52       [...]
53       0.000101 write(1, "Unknown host (1.1.1.1). Took 2.0"..., 46) = 46
54       0.000071 write(1, "Next: ", 6)     = 6
55       0.000267 read(0, "1.1.1.2\n", 1024) = 8
56       0.000071 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
57       0.000077 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
58       0.000049 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
59       0.000063 send(3, ":\243\1\0\0\1\0\0\0\0\0\0\0012\0011\0011\0011\7in-addr\4arp"..., 38, MSG_NOSIGNAL) = 38
60       0.000152 poll([{fd=3, events=POLLIN}], 1, 2000) = 0 (Timeout)
61       2.002550 close(3)                  = 0
62       0.000088 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
63       0.000077 fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
64       0.000092 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
65       0.000057 read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
66       0.000110 read(3, "", 4096)         = 0
67       0.000049 close(3)                  = 0
68       [...]
69       0.000060 write(1, "Unknown host (1.1.1.2). Took 2.0"..., 46) = 46
70       0.000076 write(1, "Next: ", 6)     = 6
71       0.000253 read(0, "refresh\n", 1024) = 8
72      17.639011 open("/etc/resolv.conf", O_RDONLY) = 3
73       0.000088 fstat64(3, {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
74       0.000087 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
75       0.000052 read(3, "# Generated by NetworkManager\n#n"..., 4096) = 118
76       0.000108 read(3, "", 4096)         = 0
77       0.000047 close(3)                  = 0
78       0.000048 munmap(0xb7843000, 4096)  = 0
79       0.000065 write(1, "Called res_init()\n", 18) = 18
80       0.000060 write(1, "Next: ", 6)     = 6
81       0.000051 read(0, "1.1.1.3\n", 1024) = 8
82       3.595382 gettimeofday({1325865312, 174933}, NULL) = 0
83       0.000075 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
84       0.000078 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.5")}, 16) = 0
85       0.000266 gettimeofday({1325865312, 175350}, NULL) = 0
86       0.000052 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
87       0.000069 send(3, "\321\234\1\0\0\1\0\0\0\0\0\0\0013\0011\0011\0011\7in-addr\4arp"..., 38, MSG_NOSIGNAL) = 38
88       0.000085 poll([{fd=3, events=POLLIN}], 1, 2000) = 0 (Timeout)
89       2.002271 close(3)                  = 0
90       0.000081 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
91       0.000076 fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
92       0.000087 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
93       0.000054 read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
94       0.000091 read(3, "", 4096)         = 0
95       0.000047 close(3)                  = 0
96       0.000048 munmap(0xb7843000, 4096)  = 0
97       0.000062 gettimeofday({1325865314, 178373}, NULL) = 0
98       0.000058 write(1, "Unknown host (1.1.1.3). Took 2.0"..., 46) = 46
99       0.000072 write(1, "Next: ", 6)     = 6
100      0.000053 read(0, 0xb7844000, 1024) = ? ERESTARTSYS (To be restarted)
101      0.918564 --- SIGINT (Interrupt) @ 0 (0) ---
102      0.000931 +++ killed by SIGINT +++

And the explanation:
  • Lines 1-5 the program starts and asks for the IP address. 1.1.1.1 is provided
  • Lines 6-18 it tries to contact the nscd (Linux caching daemon) and then opens and reads /etc/nsswitch.conf
  • Lines 19-31 opens the other two configuration files (/etc/resolv.conf and /etc/host.conf)
  • Lines 33-44 contacts the DNS server on 192.168.112.2 (timeout = 2s)
  • Lines 45-52 reads /etc/hosts and prints the result (Unknown host)
  • Lines 53-68 is just the same, but doesn't read the config files since it's not the first time
  • Lines 69-79 I insert "refresh" and it calls res_init() and re-reads /etc/resolv.conf. Meanwhile, just before that I changed the /etc/resolv.conf and put 192.168.112.5
  • Lines 80-99 I insert another IP address (1.1.1.3) and it goes to the new DNS server (192.168.112.5). Failing to get a proper answer re-reads the /etc/hosts.
  • Lines 100-102 it was asking for another IP address and I pressed Control+C
Hacking it just for fun!

Please don't try this at home!.. Really, the following is something risky, not supported, and for learning purposes only. I argued above that calling res_init() would allow you to change the DNS servers without restarting Informix. Let's prove it!
Again I traced a connection, looking at MSC VP. I get this:
socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
gettimeofday({1326038028, 174740}, NULL) = 0
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}])
send(3, "+\316\1\0\0\1\0\0\0\0\0\0\0011\003112\003168\003192\7in-ad"..., 44, MSG_NOSIGNAL) = 44
poll([{fd=3, events=POLLIN}], 1, 2000)  = 0 (Timeout)
close(3)                                = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x12f000
read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
close3)
It is connecting to 192.168.112.2, which is what I have on /etc/resolv.conf. If I change that on the file to 192.168.112.5 and try to connect again, the same thing happens (it's not aware of the change). But now, without further changes in the file I run a debugger against the MSC VP process:
[root@pacman tmp]# gdb -p 4649
GNU gdb (GDB) Fedora (7.1-18.fc13)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http: bugs="" gdb="" software="" www.gnu.org="">.
Attaching to process 4649
Reading symbols from /usr/informix/srvr1170uc4/bin/oninit...(no debugging symbols found)...done.
[...]
(gdb) call __res_init()
$1 = 0
(gdb) detach
Detaching from program: /usr/informix/srvr1170uc4/bin/oninit, process 4649
(gdb) quit
[root@pacman tmp]#
And I "call" the __res_init() function which I checked to be defined in the libc.so code. Let's trace another connection now:
socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.5")}, 16) = 0
gettimeofday({1326038409, 784712}, NULL) = 0
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}])
send(3, "\364K\1\0\0\1\0\0\0\0\0\0\0011\003112\003168\003192\7in-ad"..., 44, MSG_NOSIGNAL) = 44
poll([{fd=3, events=POLLIN}], 1, 2000)  = 0 (Timeout)
close(3)                                = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x12f000
read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
close(3)
Ups! Hacked!... Of course this is not supported. Don't try this on real servers. It just serves the purpose of proving a point.

Conclusions

What I tried to demonstrate in this article is how Informix interacts with the DNS system, if you configure DNS in your environment. I hope that I made clear that once DNS is in place it takes a very important role in the connection process. So much that a bad function in DNS will have major impact in the database system. There are a few points that are Informix responsibility that can contribute to the impact. In particular:
  1. The fact that the reverse DNS requests are made by the MSC VP(s) can generate delays in requests that would work ok, just because a previous one has a problem. Having more than one MSC VP can greatly alleviate this, but may not solve it

  2. The fact that Informix doesn't establish timeouts to the gethostbyname() calls - or equivalent - can also lead to unnecessary delays. But note that the functions signatures don't have any way to ask for this, so an alarm would have to be setup, so that the process would receive a signal and then check if it was still waiting on the function. This would cause additional overhead, that would not make sense specially because the timeouts are possible to configure in the generic DNS configuration

  3. The fact that the functions called, cache the configuration details, allied to the fact that Informix has no way to clear the cache, means that a change in the DNS addresses will require a stop in the database system. There is a feature request to allow this. I'd love to see this implemented.
On the other hand, Informix 11.7 introduced a great feature to ease all this. With the NS_CACHE parameter we can configure the cache times for several systems (DNS, users/passwords, services...). This can reduce to a minimum the number of requests. Of course, as with any other caching mechanism the risk is to have stalled entries. But we can clear any of the caches by using "onmode -wm NS_CACHE=..." and using a timeout of zero. This would be a great place to call res_init() by the way...

To roundup the article I'd like to mention a few best practices:
  1. Use Informix 11.7 if you can, so that you can take advantage of the caching. Your DNS servers and DNS admins will appreciate it if you have high connection rates

  2. Consider including "files" in the /etc/nsswitch.conf configuration. Some people consider this a bad idea, because it keeps the /etc/hosts file very busy. But if you keep it short and use the cache of Informix 11.7 it shouldn't be too bad. And it can save you if your DNS blows away (you could then add entries to the /etc/hosts file even temporarily). Note that at least in my test system (Linux) not even res_init makes the process re-read /etc/nsswitch.conf

  3. Make sure your DNS admins and DBA get along... They must work together and should be aware that their systems are tightly connected

  4. Use Informix 11.7 if you can, so that you can use REMOTE_SERVER_CFG. Many companies don't allow the DBA to manage the /etc/hosts.equiv files (system files). If you have a problem in DNS, and your reverse DNS queries start to fail, your trusted connections will fail if they use the names and not the IP addresses. So, in critical situations it may be useful that the DBAs can act immediately and add the IP addresses in the trusted lists. With REMOTE_SERVER_CFG they'll be able to do it.

  5. Use very small timeouts configured in /etc/resolvs.conf (1-2s) to minimize the waisted wait time (a successful query to a DNS is pretty quick

Versão Portuguesa:

Nota (13 Fev 2014): Um novo artigo apresenta uma forma de contornar o problema aqui descrito (http://informix-technology.blogspot.pt/2014/02/dns-changes-ok-mudancas-no-dns-ok.html)
Você decidiu...

Este artigo é o primeiro publicado após opinião dos leitores. Tinha algumas opções para assuntos a abordar e iniciei um inquérito numa página do Facebook. O impacto do DNS no Informix foi o mais votado. E a vontade expressa foi cumprida. Provavelmente continuarei a fazer isto de ora em diante.
Pessoalmente o assunto agrada-me porque já enfrentei problemas relacionados com o DNS em mais que um cliente É um assunto algo frequente em ambientes complexos.
O blog tem um termo de desresponsabilização público e genérico, mas neste caso gostaria de o enfatizar um pouco... Para além do normal, gostaria de deixar claro que a informação que se segue foge um pouco  às minhas competências "regulares". É o resultado de bastante investigação ao longo fo tempo (e não só minha) e pode conter alguns (espero que poucos e pouco importantes) erros. Não se acanhe de comentar, corrigir, sugerir alterações por aqui ou por email etc.

Pequena introdução ao DNS

DNS é um acrónimo de Domain Name System, que é o protocolo/serviço capaz de converter um nome (ex: onlinedomus.com) num endereço TCP/IP (ex: 89.1.2.3) e vice-versa. Pode ainda fazer uma série de outras coisas, como indicar qual o servidor de email responsável por um determinado domínio etc., mas isso já sai do âmbito deste artigo.
Sem DNS não haveria Internet como a conhecemos. É um componente crítico da infra-estrutura da Internet, e a sua eficiência e segurança é crucial para nós, os utilizadores
Numa pequena rede podemos passar sem DNS para as funções básicas de resolução de nomes, usando ficheiros. Mas em redes mais complexas e maiores, pode ser muito complicado e penoso gerir isso usando apenas ficheiros.
O sistema de DNS tem uma arquitectura hierárquica e usa o  protocolo UDP (U não significa unreliable mas podia...) por questões de performance. Configurar devidamente um sistema DNS pode ser uma tarefa complexa e a minha experiência diz-me que não é muito fácil encontrar quem o saiba fazer correctamente. Ainda mais, na maioria dos casos as pessoas não têm a correcta noção do terrível impacto que uma má configuração no DNS pode ter nos sistemas.
Não vou explicar (não o saberia fazer) todos os aspectos de configuração de DNS, mas quero referir alguns pontos:
  • /etc/nsswitch.conf
    Este ficheiro (em sistemas Unix/Linux, mas o nome pode mudar) define como a resolução de nomes (e outros serviços) é efectuada. Mais especificamente define se o sistema usa ficheiros, NIS, servidores de DNS ou outros mecanismos e a ordem porque o faz. Como exemplo, uma linha como:

    hosts: dns files

    indica que as pesquisas de nomes e IPs são feitas primeiro usando os servidores de DNS e depois ficheiros

  • /etc/hosts
    Este ficheiro mapeia os endereços IPs em nomes (e vice-versa). Por exemplo:

    89.1.2.3 www.onlinedomus.com onlinedomus.com

    Isto indica ao sistema que o endereço IP 89.1.2.3 mapeia para "www.onlinedomus.com" (e vice-versa). Uma pesquisa por "onlinedomus.com" irá mapear para o mesmo endereço IP.

  • /etc/resolv.conf
    Este ficheiro contém a lista de servidores de DNS que serão usados para fazer pesquisas e eventualmente mais algumas opções (como timeouts para os pedidos, domínios que serão adicionados aos nomes pedidos caso o nome simples não obtenha resultados etc.). Um exemplo:

    nameserver 192.168.112.2
    nameserver 9.64.162.21

Como é que o Informix usa o DNS?

Da perspectiva do DNS, o Informix é apenas mais uma aplicação. O ficheiro /etc/nsswitch.conf contém  configurações que definem se o Informix irá usar ficheiros (/etc/hosts) ou servidores de DNS (configurados em /etc/resolv.conf). A primeira coisa a notar é que toda a interacção do Informix com o DNS é feito através de funções de sistema. Em particular, o Informix usa duas funções ou as suas equivalente e/ou substitutas:
  • gethostbyname()
    Resumidamente recebe um nome de máquina e retorna uma estrutura contendo um endereço IP
  • gethostbyaddr()
     Esta faz o contrário, ou seja recebe uma estrutura com o endereço IP e retorna os campos relativos ao nome (ou nomes) preenchidos
Assim, se alguma coisa não estiver a funcionar, temos de entender quando e onde é que o Informix chama estas funções e como é que elas funcionam. É típico ver clientes a culpar o Informix quando tal não é justo (não completamente pelo menos, mas mais sobre isto adiante). A maioria (senão todos) dos problemas de DNS que afectam o Informix que presenciei, afectam também outras aplicações. E estes problemas são facilmente reproduzíveis com um pequeno programa em linguagem C. Quer isto dizer que não poderíamos fazer algumas coisas de forma diferente (melhor?) Não... Mas isto será abordado mais adiante (chamo a atenção para o termo de desresponsabilização :) )
Este artigo é escrito essencialmente da perspectiva do servidor de base de dados. Mas o DNS tem implicações óbvias no lado do cliente... Vamos começar por aqui e depois saltamos para o servidor.
Quando um cliente tenta conectar-se a um servidor Informix, começa por procurar o $INFORMIXSERVER (ou equivalente dado na string de conexão) no ficheiro $INFORMIXSQLHOSTS (em Java a informação pode ser obtida via LDAP ou HTTP, mas vamos assumir só ficheiros por simplificação). O ficheiro contém linhas com o seguinte formato:
INFORMIXSERVER PROTOCOLO NOME_MAQUINA/ENDERECO_IP NUMERO_PORTO/NOME_SERVICO OPCOES
Quando as funções das bibliotecas cliente encontram a linha que define o INFORMIXSERVER pretendido, obtêm o nome da máquina (ou endereço IP) e o número do porto (ou nome de serviço).
Se o nome de serviço é usado, procuramos o número do porto no ficheiro /etc/services (isto também pode ser configurado no /etc/nsswitch.conf). Pessoalmente habitualmente uso o número do porto directamente para evitar mais esta consulta..
Depois, se o nome de uma máquina foi usado, o cliente terá de o converter para um endereço IP. Para isso chama a função gethostbyname(). Esta função irá comportar-se como especificado no ficheiro /etc/nsswitch.conf e tentará mapear o nome recebido num endereço IP. Uma falha ao fazer isto irá resultar num erro -930. Isto pode ser provocado:
cheetah@pacman.onlinedomus.com:fnunes-> echo $INFORMIXSERVER; grep $INFORMIXSERVER $INFORMIXSQLHOSTS; dbaccess sysmaster -
blogtest
blogtest     onsoctcp     nowhere.onlinedomus.com 1500

930: Cannot connect to database server (nowhere.onlinedomus.com).
cheetah@pacman.onlinedomus.com:fnunes->

e se precisar de evidências do que se está a passar pode usar o strace (ou truss conforme a plataforma):
strace -o /tmp/strace.out dbaccess sysmaster -
O seguinte é um extracto editado do /tmp/strace.out gerado pelo comando acima. Se tiver paciência, pode vê-lo a fazer:
  1. Abrir o ficheiro etc/nsswitch.conf
  2. Abrir o ficheiro $INFORMIXSQLHOSTS (/home/informix/etc/sqlhosts)
  3. Abrir o ficheiro /etc/services (excepcionalmente usei um nome em vez de um porto)
  4. Abrir o /etc/resolv.conf para descobrir os servidores de DNS configurados
  5. Abrir um socket para 192.168.112.2 (o servidor de DNS que tenho configurado)
  6. Perguntar por nowhere.onlinedomus.com
  7. Abrir o ficheir /etc/hosts (no /etc/nsswtich.conf eu configurei a pesquisa nos ficheiros após a pesquisa nos servidores de DNS)
  8. Ler a mensagem de erro nos ficheiros de mensagens do Informix
  9. Escrever a mensagem de erro no stderr
  10. Sair com o erro -1
cheetah@pacman.onlinedomus.com:fnunes-> cat /tmp/strace.out
[...]
open("/etc/nsswitch.conf", O_RDONLY)    = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=1803, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7862000
read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 4096) = 1803
read(3, "", 4096)                       = 0
close(3)                                = 0
[...]
open("/home/informix/etc/sqlhosts", O_RDONLY|O_LARGEFILE) = 4
_llseek(4, 0, [0], SEEK_SET)            = 0
read(4, "blogtest     onsoctcp     nowher"..., 4096) = 1389
[...]
open("/etc/services", O_RDONLY|O_CLOEXEC) = 4
fstat64(4, {st_mode=S_IFREG|0644, st_size=644327, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7862000
read(4, "# /etc/services:\n# $Id: services"..., 4096) = 4096
close(4)                                = 0
[...]
open("/etc/resolv.conf", O_RDONLY)      = 4
[...]
read(4, "", 4096)                       = 0
close(4)                                = 0
[...]
open("/lib/libresolv.so.2", O_RDONLY)   = 4
read(4, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0Pf\256\0004\0\0\0"..., 512) = 512
[...]
close(4)                                = 0
[...]
socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4
connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
gettimeofday({1325590403, 502576}, NULL) = 0
poll([{fd=4, events=POLLOUT}], 1, 0)    = 1 ([{fd=4, revents=POLLOUT}])
send(4, "tl\1\0\0\1\0\0\0\0\0\0\7nowhere\vonlinedomus"..., 41, MSG_NOSIGNAL) = 41
poll([{fd=4, events=POLLIN}], 1, 5000)  = 1 ([{fd=4, revents=POLLIN}])
ioctl(4, FIONREAD, [101])               = 0
recvfrom(4, "tl\201\203\0\1\0\0\0\1\0\0\7nowhere\vonlinedomus"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, [16]) = 101
close(4)                                = 0
[...]
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 4
[...]
read(4, "127.0.0.1\tpacman1.onlinedomus.ne"..., 4096) = 439
[...]
close(4)                                = 0
[...]
read(3, "Cannot connect to database serve"..., 40) = 40
write(2, "\n", 1)                       = 1
write(2, "  930: Cannot connect to databas"..., 68) = 68
exit_group(-1)                          = ?
cheetah@pacman.onlinedomus.com:fnunes->

Agora devemos passar para o lado do servidor. Mas isso requer um pouco mais de trabalho e explicações preliminares. Para começar temos de entender o que o motor necessita para estabelecer uma ligação ou sessão. Uma dessas coisas é fazer o chamado "reverse DNS" ou DNS inverso que não é mais que converter um endereço IP no nome de uma máquina. Isto pode não ser absolutamente essencial, mas é sempre tentado. O Informix pode precisar do nome da máquina para validar as relações de confiança (ligações sem utilizador/password). O nome servirá também para informação do DBA de forma a mais facimente identificar a origem do cliente.
Como saberá, o motor de base de dados Informix é composto por vários processos de sistema operativo. Do ponto de vista do SO, todos estes processos parecem iguais (chamam-se oninit), mas cada um tem um trabalho específico e corre threads específicas do motor.
Podemos listar as threads com:
panther@pacman.onlinedomus.com:fnunes-> onstat -g ath

IBM Informix Dynamic Server Version 11.70.UC4 -- On-Line -- Up 00:17:01 -- 411500 Kbytes

Threads:
 tid     tcb      rstcb    prty status                vp-class       name
 2       5583fa38 0        1    IO Idle                 3lio*        lio vp 0
 3       558551f8 0        1    IO Idle                 4pio*        pio vp 0
 4       5586b1f8 0        1    IO Idle                 5aio*        aio vp 0
 5       558811f8 8f59dc0  1    IO Idle                 6msc*        msc vp 0
 6       558af1f8 0        1    IO Idle                 7fifo*       fifo vp 0
 7       558c9590 0        1    IO Idle                 9aio*        aio vp 1
 8       558df3b8 54267018 3    sleeping secs: 1        8cpu         main_loop()
 9       559276f8 0        1    running                10soc*        soctcppoll
 10      5593ed18 0        2    sleeping forever        1cpu*        soctcplst
 11      55927d20 542675fc 1    sleeping secs: 1        8cpu         flush_sub(0)
 12      55988018 54267be0 1    sleeping secs: 1        8cpu         flush_sub(1)
 13      559881f0 542681c4 1    sleeping secs: 1        8cpu         flush_sub(2)
 14      559883c8 542687a8 1    sleeping secs: 1        8cpu         flush_sub(3)
 15      559885a0 54268d8c 1    sleeping secs: 1        8cpu         flush_sub(4)
 16      55988778 54269370 1    sleeping secs: 1        8cpu         flush_sub(5)
 17      55988bf0 54269954 1    sleeping secs: 1        8cpu         flush_sub(6)
 18      559fb468 54269f38 1    sleeping secs: 1        8cpu         flush_sub(7)
 19      559fb640 0        3    IO Idle                 8cpu*        kaio
 20      55ab6018 5426a51c 2    sleeping secs: 1        8cpu         aslogflush
 21      55ab6960 5426ab00 1    sleeping secs: 92       1cpu         btscanner_0
 22      55b6a408 5426b0e4 3    cond wait  ReadAhead    1cpu         readahead_0
 39      55bcd5c8 0        3    IO Idle                 1cpu*        kaio
 40      55bcd7a0 5426bcac 3    sleeping secs: 1        1cpu*        onmode_mon
 41      55d3e148 5426c874 3    sleeping secs: 1        8cpu         periodic
 49      55e80a78 5426da20 1    sleeping secs: 177      1cpu         dbScheduler
 51      55f340f8 5426d43c 1    sleeping forever        1cpu         dbWorker1
 52      55f34d80 5426ce58 1    sleeping forever        8cpu         dbWorker2
 59      562ee228 5426e5e8 1    cond wait  bp_cond      1cpu         bf_priosweep()
E os processos de sistema operativo com:
panther@pacman.onlinedomus.com:fnunes-> onstat -g glo

IBM Informix Dynamic Server Version 11.70.UC4 -- On-Line -- Up 00:18:48 -- 411500 Kbytes

MT global info:
sessions threads  vps      lngspins
0        29       10       3       

          sched calls     thread switches yield 0   yield n   yield forever
total:    9589515         8992470         597961    14485     4457836  
per sec:  0               0               0         0         0        

Virtual processor summary:
 class       vps       usercpu   syscpu    total   
 cpu         2         11.51     94.06     105.57  
 aio         2         3.57      75.44     79.01   
 lio         1         0.01      0.01      0.02    
 pio         1         0.00      0.01      0.01    
 adm         1         0.01      0.15      0.16    
 soc         1         0.04      0.15      0.19    
 msc         1         0.00      0.01      0.01    
 fifo        1         0.00      0.01      0.01    
 total       10        15.14     169.84    184.98  

Individual virtual processors:
 vp    pid       class       usercpu   syscpu    total     Thread    Eff  
 1     29395     cpu         5.63      46.80     52.43     66.41     78%
 2     29398     adm         0.01      0.15      0.16      0.00       0%
 3     29399     lio         0.01      0.01      0.02      0.02     100%
 4     29400     pio         0.00      0.01      0.01      0.01     100%
 5     29401     aio         3.29      74.30     77.59     77.59    100%
 6     29402     msc         0.00      0.01      0.01      0.03      31%
 7     29403     fifo        0.00      0.01      0.01      0.01     100%
 8     29404     cpu         5.88      47.26     53.14     64.45     82%
 9     29405     aio         0.28      1.14      1.42      1.42     100%
 10    29406     soc         0.04      0.15      0.19      NA         NA
                 tot         15.14     169.84    184.98
As threads que estão à escuta nos portos TCP do motor são as poll threads (soctcppoll) que correm nos  VPs (virtual processors) de classe SOC (isto depende da configuração do parâmetro NETTYPE). Quando um novo pedido de ligação é recebido por elas, chama as listener threads (soctcplst) que correm na classe CPU, para iniciar o processo de autenticação. Partes deste processo são executadas pelo VP MSC. Como podemos ver na lista acima este tem o PID 29402. Portanto, para perceber o que se passa irei fazer o trace a este processo. Por razões que ficarão claras mais abaixo, vou desligar a funcionalidade NS_CACHE (11.7) e vou fazer uma paragem/arranque do motor. Após isto, para a primeira tentativa de conexão obtemos (algumas partes não relevantes foram cortadas):
1      0.000000 semop(753664, {{5, -1, 0}}, 1) = 0
2      7.009868 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
3      0.000107 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
4      0.000242 close(3)                  = 0
5      0.000060 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
6      0.000063 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
7      0.000095 close(3)                  = 0
8      [...]
9      0.000000 open("/etc/resolv.conf", O_RDONLY) = 3
10     0.000000 fstat64(3, {st_mode=S_IFREG|0644, st_size=55, ...}) = 0
11     0.000000 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xab4000
12     0.000000 read(3, "# Generated by NetworkManager\nna"..., 4096) = 55
13     0.000926 read(3, "", 4096)         = 0
14     0.000050 close(3)                  = 0
15     [...]
16     0.000057 futex(0x29ab44, FUTEX_WAKE_PRIVATE, 2147483647) = 0
17     0.000256 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
18     0.000089 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
19     0.000107 gettimeofday({1325605320, 167025}, NULL) = 0
20     0.000072 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
21     0.000083 send(3, "\363\337\1\0\0\1\0\0\0\0\0\0\0011\003112\003168\003192\7in-ad"..., 44, MSG_NOSIGNAL) = 44
22     0.000322 poll([{fd=3, events=POLLIN}], 1, 5000) = 1 ([{fd=3, revents=POLLIN}])
23     2.061369 ioctl(3, FIONREAD, [121]) = 0
24     0.000111 recvfrom(3, "\363\337\201\203\0\1\0\0\0\1\0\0\0011\003112\003168\003192\7in-ad"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, [16]) = 121
25     0.000155 close(3)                  = 0
26     0.000090 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
27     0.000377 fstat64(3, {st_mode=S_IFREG|0644, st_size=439, ...}) = 0
28     0.000089 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf22000
29     0.000057 read(3, "127.0.0.1\tpacman1.onlinedomus.ne"..., 4096) = 439
30     0.000130 close(3)                  = 0
31     [...]
32     0.000072 semop(753664, {{7, 1, 0}}, 1) = 0
33     0.000069 semop(753664, {{7, 1, 0}}, 1) = 0
34     0.007558 semop(753664, {{5, -1, 0}}, 1 

Note-se que para facilitar a análise, adicionei números de linhas e diferenças de tempos entre cada uma das chamadas a funções de sistema (isto será importante adiante). Vamos explicar o que vemos:
  • Na linha 1) tempos um semop() que é a forma de o VP MSC ficar inactivo, à espera de ser solicitado. Isto foi antes da tentativa de conexão
  • 7 segundos depois, acorda e tenta "falar" com o serviço nscd (linhas 2-8). Este serviço é algo específico do Linux e eu não o tenho a correr. Depois acede a /etc/nsswitch.conf. Fixe que isto foi feito na primeira tentativa de conexão.
  • Depois acede ao ficheiro /etc/resolv.conf (linhas 9-15) e descobre os endereços IP dos servidores de DNS
  • Nas linhas 16-25 fala com o servidor de DNS (192.168.112.2) e pede o nome do endereço IP que se está a tentar ligar (obtido pela estrutura do socket)
  • Como a resposta é inconclusiva, vai ao ficheiro /etc/hosts (linhas 26-31)
  • Cortei a parte restante, relativa à autenticação (abertura do /etc/passwd, /etc/group e /etc/shadow etc.)
  • Finalmente retorna ao normal estado de espera
Alguns pontos importantes sobre isto:
  1. Tudo aconteceu bastante depressa (valores em segundos)
  2. Não vemos a chamada à função gethostbyaddr() que eu garanti que era chamada pelo Informix. Na perspectiva do strace esta não é uma "system call". Só vemos chamadas de mais baixo nível, mas não a gethostbyaddr(). Podemos apanhá-la se ligar-mos um debugger (dbx, gdb, adb) ao processo. Isto é importante, pois é habitual ser difícil discutir este tema com os administradores de rede e sistema operativo, pois pensam que todas estas chamadas são feitas pelo Informix. Não são! O Informix apenas chama a gethostbyaddr() ou equivalente
Agora vamos ver o mesmo processo, mas para a segunda tentativa de conexão. Seria de esperar que o resultado fosse o mesmo, mas não é. Cortei exactamente as mesmas zonas:
1      0.000000 semop(753664, {{5, -1, 0}}, 1) = 0
2      6.452154 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
3      0.000099 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
4      0.008816 gettimeofday({1325605445, 534040}, NULL) = 0
5      0.000089 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
6      0.000100 send(3, "\233\t\1\0\0\1\0\0\0\0\0\0\0011\003112\003168\003192\7in-ad"..., 44, MSG_NOSIGNAL) = 44
7      0.000417 poll([{fd=3, events=POLLIN}], 1, 5000) = 1 ([{fd=3, revents=POLLIN}])
8      2.089726 ioctl(3, FIONREAD, [121]) = 0
9      0.000118 recvfrom(3, "\233\t\201\203\0\1\0\0\0\1\0\0\0011\003112\003168\003192\7in-ad"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, [16]) = 121
10     0.000132 close(3)                  = 0
11     0.000069 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
12     0.000102 fstat64(3, {st_mode=S_IFREG|0644, st_size=439, ...}) = 0
13     0.000092 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xe1f000
14     0.000064 read(3, "127.0.0.1\tpacman1.onlinedomus.ne"..., 4096) = 439
15     0.000099 close(3)                  = 0
16     [...]
17     0.000068 semop(753664, {{0, 1, 0}}, 1) = 0
18     0.000096 semop(753664, {{0, 1, 0}}, 1) = 0
19     0.000076 semop(753664, {{5, -1, 0}}, 1 

Novamente a análise:
  • A primeira parte de acesso ao servilo nscd, a abertura do /etc/nsswitch.conf e do /etc/resolv.conf não aparece. Como se pode ver começa logo com a consulta ao DNS
  • Depois lê o ficheiro /etc/hosts
  • Voltei a cortar a parte da autenticação como anteriormente
  • Finalmente regressa ao estado de espera, tal como anteriormente
O ponto importante a reter é que a primeira tentativa é diferente das subsequentes. E reforço novamente que o Informix apenas chama a gethostbyaddr()... exactamente a mesma chamada de cada vez. Bom... para ser exacto a função que chamamos pode depender da plataforma, mas é sempre a mesma em cada conexão. Como referi acima, apenas com a utilização de um debugger podemos capturar a chamada à gethostbyaddr(). Eu fi-lo e aqui está o resultado:

(gdb) break connect
Breakpoint 1 at 0x95f640
(gdb) continue
Continuing.

Breakpoint 1, 0x0095f640 in connect () from /lib/libpthread.so.0
(gdb) where
#0  0x0095f640 in connect () from /lib/libpthread.so.0
#1  0x00aec9ab in reopen () from /lib/libresolv.so.2
#2  0x00aee542 in __libc_res_nsend () from /lib/libresolv.so.2
#3  0x00aeb24e in __libc_res_nquery () from /lib/libresolv.so.2
#4  0x002b6dc7 in _nss_dns_gethostbyaddr2_r () from /lib/libnss_dns.so.2
#5  0x002b6f1a in _nss_dns_gethostbyaddr_r () from /lib/libnss_dns.so.2
#6  0x0020890b in gethostbyaddr_r@@GLIBC_2.1.2 () from /lib/libc.so.6
#7  0x00211f77 in getnameinfo () from /lib/libc.so.6
#8  0x08c0e664 in ifx_getipnodebyaddr ()
#9  0x08c0f79c in ifx_gethostbyaddr ()
#10 0x08c0f8a2 in __osgethostbyaddr ()
#11 0x08b0c055 in aio_workon ()
#12 0x08b0c9c3 in aiothread ()
#13 0x08b0dbcb in iothread ()
#14 0x08b00762 in startup ()
#15 0x558749e8 in ?? ()
#16 0x00000000 in ?? ()
(gdb)
Como pode ver, estabeleci um breakpoint na função connect(). Depois executei o continue para que o processo prosseguisse e tentei a conexão. O debugger interrompeu a execução quando chegou ao connect() e isso permitiu-me retirar um stack trace (ler de baixo para cima)
Portanto, mostra-nos que chamamos a função getnameinfo() que por sua vez chama a gethostbyaddr_r() etc. Tudo isto está contido em bibliotecas de sistema, não no código Informix.

Há mais dois pontos a salientar. Primeiro, o processador virtual do Informix da classe MSC processa os  pedidos de forma sequencial. Para cada conexão é-lhe pedido que efectue o DNS inverso (pedindo aos servidores DNS ou lendo o ficheiro /etc/hosts) e faça a autenticação. Por omissão apenas temos um MSC... portanto se um pedido ficar "preso"... sim... os que vierem a seguir irão sofrer atrasos. Estes atrasos podem ser uma fracção de segundo ou alguns segundos, mas em alguns sistemas já observei dezenas de conexões por segundo (já vi referências a centenas/segundo). Portanto mesmo um atraso de alguns segundos pode ter um impacto muito notório.
O segundo ponto refere-se à diferença entre a primeira chamada e as seguintes. Como vimos acima, na primeira chamada é verificada a configuração (ficheiros /etc/nsswitch.conf e /etc/resolv.conf). Nas seguintes tal não acontece por razões de performance. E isto causa um problema. Novamente, este comportamento é das funções de sistema, não do Informix (mas não será necessariamente o fim da história)

Portanto, espero que tenha conseguido explicar como é que o Informix interage com o sistema de DNS. O mergulho nos bits e bytes já terminou. Vamos prosseguir para as implicações, mas é importante que tenha ficado claro o que foi explicado acima antes de prosseguir para os próximos parágrafos.

Que problemas podemos enfrentar?

Acima tentei demonstrar como as coisas funcionam quando está tudo ok. Mas o que acontece quando algo está mal? Vamos ver o que pode correr mal e quais as implicações. Tentarei também tentar indicar de quem será a responsabilidade (mais uma vez relembro o termo de desresponsabilização...). O objectivo não é propriamente apontar o dedo a ninguém, mas saber onde é que o problema reside é meio caminho andado para o resolver.
  1. Problemas de rede impedem a conexão aos servidores de DNS
    Se isto acontecer, os pedidos enviados pelo VP MSC terão de dar timeout (tipicamente alguns segundos) antes que as chamadas às funções de sistema operativo retornem. Este atraso irá causar que todos os pedidos de conexão seguintes fiquem em espera (assumindo que só temos um MSC VP). Se os problemas de rede persistirem, não irá importar muito quantos processadores de classe MSC temos, pois à partida todos eles irão ficar presos e todos os pedidos de conexão sofrerão atrasos.

  2. O(s) servidor(es) de DNS caem, são parados ou ficam muito lentos
    O efeito disto é em tudo semelhante ao anterior. Qualquer coisa que cause atrasos nos pedidos aos DNS irá potenciar atrasos nas conexões ao motor. Note-se que isto pode ser um problema que afecte todos os pedidos, ou apenas alguns. Devido à natureza hierárquica e distribuída da estrutura de DNS, pode ser possível responder a alguns pedidos e não a outros por estes levarem a contactos com determinados servidores que possam não estar disponíveis. Escusado referir que isto torna a investigação destes problemas ainda mais difícil.

  3. Algo afecta o sistema de DNS. Os pedidos de DNS inverso falham e isto afecta as conexões trusted
    Neste caso começam a aparecer erros -956 no online.log da instância e os clientes começam a receber o erro -951. Naturalmente isto acontece se as relações de confiança estiverem definidas com nomes em vez de endereços IP (o uso de nomes é o mais normal e recomendado, dado que é mais frequente mudar um IP que o nome)

  4. Precisa de mudar os seus servidores de DNS
     Então terá de parar e arrancar o Informix. Isto é um problema causado pelo Informix (podía-mos fazer melhor). Como referido acima, as funções de sistema operativo fazem cache da configuração (seria muito ineficiente re-verificar a configuração em cada pedido). E esta situação leva a que o Informix não se "aperceba" que os servidores de DNS foram mudados. Portanto o problema é que se mudarmos a politica de resolução (/etc/nsswitch.conf) ou os servidores (/etc/resolv.conf) os processos de Informix não terão isso em conta
Dos problemas acima é fácil de entender que os três primeiros não são "culpa" do Informix. Quando um cliente decide usar uma infra-estrutura de DNS (supostamente porque necessita) terá de entender que isso passa automaticamente a ser um componente critico dos seus sistemas. Qualquer problema que essa infra-estrutura tenha vai afectar as camadas acima como os servidores de base de dados e outros.
E assim ficamos com o quarto problema. Escrevi atrás e é verdade que o comportamento é das funções de sistema operativo (e vou demonstrá-lo), portanto porque é que digo que a responsabilidade é do Informix? Bom, porque existe uma forma de lidar com isto. Há uma outra função de sistema chamada res_init() que faz aquilo que necessitamos. Do manual de Linux, cito (sem tradução):

The res_init() function reads the configuration files (see resolv.conf(5)) to get the default domain name, search order and name server address(es).
If no server is given, the local host is tried. If no domain is given, that associated with the local host is used. It can be overridden with the environment variable LOCALDOMAIN. res_init() is normally executed by the first call to one of the other functions
.


A minha opinião é que o Informix deveria providenciar uma forma pela qual o DBA poderia forçar que cada processador virtual da classe MSC chamasse esta função. Isto refrescaria a informação obtida na primeira chamada à gethostbyaddr() que é mantida em cache no espaço do processo.
Na verdade a IBM tem um pedido de funcioalidade pendente que refer isto explicitamente. Gostaria bastante de o ver implementado numa versão futura. Isto é algo sem o qual podemos viver durante anos, mas se a situação se coloca pode realmente fazer a diferença entre ter de parar o servidor de base de dados ou não.

Detectar se temos um problema

Como podemos saber se temos um problema de DNS e isso está a ter impacto no Informix? Habitualmente iremos receber algum tipo de queixa dos clientes/aplicações Informix. Tipicamente algo como "a base de dados está muito lenta", ou algo mais correcto como "leva muito tempo a estabelecer uma conexão", ou eventualmente algumas sessões serão recusadas com o erro -956 (do lado do servidor) que corresponde a um erro -951 retornado ao cliente. Nos casos mais extremos podem aparecer erros menos comuns no online.log (-25xxx). Em todos estes casos notará que depois de estabelecidas as ligações estas trabalham sem problemas.
Mas para ter a certeza absoluta pode seguir dois caminhos:
  1. O mais simples é correr um simples "netstat -a" na máquina onde reside o servidor de base de dados. Isto mostra todas as ligações TCP/UDP de e para a máquina. Por omissão, vai percorrer todas as ligações socket e tentará fazer o pedido de DNS inverso sobre o respectivo endereço IP para obter os nomes das máquinas. Se estiver a ter problemas de DNS o netstat irá correr muito lento ou pelo menos verificará alguns "soluços" no output do mesmo. Mas para que este teste seja conclusivo, tem de garantir que as configurações de DNS que o netstat vai usar são as mesmas que o motor Informix está a usar (que serão as que tinha quando o motor foi levantado)

  2. O mais complexo passa por executar um comando "truss" ou "strace" contra o(s) processo do processador virtual de classe MSC, com apresentação de tempos. Isto permitirá mostrar tempos de resposta lentos das funções que trocam informação com os servidores de DNS. Tenha em atenção que correr o truss/strace requer privilégios de root e que mesmo quando tudo está a correr bem, isto causará algum impacto em sistemas com uma taxa de novas ligações por segundo elevada.
Escrevia atrás que posso demonstrar os factos apresentados aqui sem usar o Informix. Para tal criei um pequeno programa em C (números de linha adicionados):
1  #include <sys/time.h>
2  #include <netdb.h>
3  #include <resolv.h>
4  #include <stdlib.h>
5  #include <string.h>
6  
7  int main(int argc, char **argv)
8  {
9    struct hostent *hp;
10    in_addr_t data;
11    char buff[100];
12    struct timeval ts_initial, ts_final;
13  
14    if (argc == 2) {
15      strcpy(buff,argv[1]);
16    }
17    else {
18      printf("Introduce an IP address: ");
19      if (fscanf(stdin,"%s", buff) == EOF)
20        exit(0);
21    }
22  
23    while (1 == 1) {
24      data = inet_addr(buff);
25      gettimeofday(&ts_initial, NULL);
26      hp = gethostbyaddr(&data, 4, AF_INET);
27      gettimeofday(&ts_final, NULL);
28  
29      if (hp == NULL) {
30        printf("Unknown host (%s). Took %f seconds\n", buff, (double)((ts_final.tv_sec * 1000000 + ts_final.tv_usec) - (ts_initial.tv_sec * 1000000 + ts_initial.tv_usec))/1000000);
31      }
32      else {
33        printf("Name (%s): %s Took %f seconds\n", buff, hp->h_name, (double)((ts_final.tv_sec * 1000000 + ts_final.tv_usec) - (ts_initial.tv_sec * 1000000 + ts_initial.tv_usec))/1000000);
34      }
35      printf("Next: ");
36      if (fscanf(stdin,"%s", buff) == EOF)
37        exit(0);
38      if ( strncmp("refresh", buff, 7) == 0 )
39      {
40         res_init();
41         printf("Called res_init()\n");
42         printf("Next: ");
43         if (fscanf(stdin,"%s", buff) == EOF)
44           exit(0);
45      }
46    }
47  }

Isto é basicamente um ciclo que lê um endereço IP (que não é validado, portanto é fácil de causar erros no programa) e corre a gethostbyaddr() sobre o mesmo. Se o "IP" dado fôr "refresh" então chama a função res_init(). Informa das respostas obtidas pelo sistema de resolução de nomes e o tempo que demorou. Pode ser usado interactivamente ou podemos chamá-lo redireccionando o input de um ficheiro que contenha um endereço IP em cada linha.

Vou executá-lo interactivamente com tracing para mostrar o efeito de chamar a função res_init(). Tenho "hosts: dns, files" no ficheiro /etc/nsswitch.conf e 192.168.112.2 no /etc/resolv.conf. Este último contém também opções para definir um timeout de 2 segundos. Portanto chamo-o com:
cheetah@pacman1.onlinedomus.net:fnunes-> strace -r -o test_resolv.trace ./test_resolv
Introduce an IP address: 1.1.1.1
Unknown host (1.1.1.1). Took 2.010235 seconds
Next: 1.1.1.2
Unknown host (1.1.1.2). Took 2.003324 seconds
Next: refresh
Called res_init()
Next: 1.1.1.3
Unknown host (1.1.1.3). Took 2.004002 seconds
Next: ^C
Antes de introduzir "refresh" mudo o servidor de DNS no ficheiro /etc/resolv.conf de 192.168.112.2 para 192.168.112.5.
O resultado do trace é este (números de linha e tempos adicionados):
1        [...]
2        0.000000 write(1, "Introduce an IP address: ", 25) = 25
3        0.000000 read(0, "1.1.1.1\n", 1024) = 8
4        3.055699 gettimeofday({1325865284, 104186}, NULL) = 0
5        [...]
6        0.000160 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
7        0.000099 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ECONNREFUSED (Connection refused)
8        0.000128 close(3)                  = 0
9        0.000053 socket(PF_FILE, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
10       0.000123 connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ECONNREFUSED (Connection refused)
11       0.000122 close(3)                  = 0
12       0.000095 open("/etc/nsswitch.conf", O_RDONLY) = 3
13       0.000108 fstat64(3, {st_mode=S_IFREG|0644, st_size=1803, ...}) = 0
14       0.000101 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
15       0.000057 read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 4096) = 1803
16       0.000001 read(3, "", 4096)         = 0
17       0.000000 close(3)                  = 0
18       [...]
19       0.000055 open("/etc/resolv.conf", O_RDONLY) = 3
20       0.000072 fstat64(3, {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
21       0.000095 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
22       0.000050 read(3, "# Generated by NetworkManager\nna"..., 4096) = 118
23       0.000086 read(3, "", 4096)         = 0
24       0.000046 close(3)                  = 0
25       [...]
26       0.000173 open("/etc/host.conf", O_RDONLY) = 3
27       0.000068 fstat64(3, {st_mode=S_IFREG|0644, st_size=26, ...}) = 0
28       0.000081 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
29       0.000179 read(3, "multi on\norder hosts,bind\n", 4096) = 26
30       0.000083 read(3, "", 4096)         = 0
31       0.000048 close(3)                  = 0
32       0.000049 munmap(0xb7843000, 4096)  = 0
33       0.000160 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
34       0.000075 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
35       0.000640 gettimeofday({1325865284, 108850}, NULL) = 0
36       0.000062 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
37       0.000094 send(3, "r\363\1\0\0\1\0\0\0\0\0\0\0011\0011\0011\0011\7in-addr\4arp"..., 38, MSG_NOSIGNAL) = 38
38       0.000889 poll([{fd=3, events=POLLIN}], 1, 2000) = 0 (Timeout)
39       2.003373 close(3)                  = 0
40       0.000109 open("/etc/ld.so.cache", O_RDONLY) = 3
41       0.000078 fstat64(3, {st_mode=S_IFREG|0644, st_size=72238, ...}) = 0
42       0.000093 mmap2(NULL, 72238, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7821000
43       0.000054 close(3)                  = 0
44       [...]
45       0.000105 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
46       0.000097 fcntl64(3, F_GETFD)       = 0x1 (flags FD_CLOEXEC)
47       0.000065 fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
48       0.000053 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
49       0.000035 read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
50       0.000137 read(3, "", 4096)         = 0
51       0.000130 close(3)                  = 0
52       [...]
53       0.000101 write(1, "Unknown host (1.1.1.1). Took 2.0"..., 46) = 46
54       0.000071 write(1, "Next: ", 6)     = 6
55       0.000267 read(0, "1.1.1.2\n", 1024) = 8
56       0.000071 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
57       0.000077 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
58       0.000049 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
59       0.000063 send(3, ":\243\1\0\0\1\0\0\0\0\0\0\0012\0011\0011\0011\7in-addr\4arp"..., 38, MSG_NOSIGNAL) = 38
60       0.000152 poll([{fd=3, events=POLLIN}], 1, 2000) = 0 (Timeout)
61       2.002550 close(3)                  = 0
62       0.000088 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
63       0.000077 fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
64       0.000092 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
65       0.000057 read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
66       0.000110 read(3, "", 4096)         = 0
67       0.000049 close(3)                  = 0
68       [...]
69       0.000060 write(1, "Unknown host (1.1.1.2). Took 2.0"..., 46) = 46
70       0.000076 write(1, "Next: ", 6)     = 6
71       0.000253 read(0, "refresh\n", 1024) = 8
72      17.639011 open("/etc/resolv.conf", O_RDONLY) = 3
73       0.000088 fstat64(3, {st_mode=S_IFREG|0644, st_size=118, ...}) = 0
74       0.000087 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
75       0.000052 read(3, "# Generated by NetworkManager\n#n"..., 4096) = 118
76       0.000108 read(3, "", 4096)         = 0
77       0.000047 close(3)                  = 0
78       0.000048 munmap(0xb7843000, 4096)  = 0
79       0.000065 write(1, "Called res_init()\n", 18) = 18
80       0.000060 write(1, "Next: ", 6)     = 6
81       0.000051 read(0, "1.1.1.3\n", 1024) = 8
82       3.595382 gettimeofday({1325865312, 174933}, NULL) = 0
83       0.000075 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
84       0.000078 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.5")}, 16) = 0
85       0.000266 gettimeofday({1325865312, 175350}, NULL) = 0
86       0.000052 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
87       0.000069 send(3, "\321\234\1\0\0\1\0\0\0\0\0\0\0013\0011\0011\0011\7in-addr\4arp"..., 38, MSG_NOSIGNAL) = 38
88       0.000085 poll([{fd=3, events=POLLIN}], 1, 2000) = 0 (Timeout)
89       2.002271 close(3)                  = 0
90       0.000081 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
91       0.000076 fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
92       0.000087 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7843000
93       0.000054 read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
94       0.000091 read(3, "", 4096)         = 0
95       0.000047 close(3)                  = 0
96       0.000048 munmap(0xb7843000, 4096)  = 0
97       0.000062 gettimeofday({1325865314, 178373}, NULL) = 0
98       0.000058 write(1, "Unknown host (1.1.1.3). Took 2.0"..., 46) = 46
99       0.000072 write(1, "Next: ", 6)     = 6
100      0.000053 read(0, 0xb7844000, 1024) = ? ERESTARTSYS (To be restarted)
101      0.918564 --- SIGINT (Interrupt) @ 0 (0) ---
102      0.000931 +++ killed by SIGINT +++

E a explicação:
  • Linhas 1-5 o programa arranca e pede o endereço IP. Dou-lhe 1.1.1.1
  • Linhas 6-18 tenta contactar o serviço nscd (serviço de caching em Linux) e depois abre e lê o ficheiro /etc/nsswitch.conf
  • Linhas 19-31 abre os outros dois ficheiros de configuração (/etc/resolv.conf e /etc/host.conf)
  • Linhas 33-44 contacta o servidor de DNS em 192.168.112.2 (timeout = 2s)
  • Linhas 45-52 lê /etc/hosts e imprime o resultado (Unknown host)
  • Linhas 53-68 é o mesmo, mas não lê os ficheiros de configuração pois já não é o primeiro pedido
  • Linhas 69-79 eu insiro "refresh" e o programa chama a res_init() e re-lê o /etc/resolv.conf. Entretanto, antes eu mudo no /etc/resolv.conf o servidor DNS para 192.168.112.5
  • Linhas 80-99 I insiro outro endereço IP (1.1.1.3) e vai contactar o novo servidor de DNS (192.168.112.5). Ao não obter uma resposta, re-lê o /etc/hosts.
  • Linhas 100-102 estava a pedir novamente um endereço IP e pressiono Control+C

Hacking só por brincadeira!

Por favor não tente isto em casa! Atenção, o que vem a seguir é arriscado, não suportado e apresentado apenas para provar um ponto de vista. Argumentei acima que chamar a função res_init() permitiria que se mudasse os endereços dos servidores DNS sem parar e arrancar o Informix. Vamos prová-lo!
Fiz novamente trace a uma conexão, olhando para o processo do processador virtual da classe MSC. Obtive isto:
socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.2")}, 16) = 0
gettimeofday({1326038028, 174740}, NULL) = 0
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}])
send(3, "+\316\1\0\0\1\0\0\0\0\0\0\0011\003112\003168\003192\7in-ad"..., 44, MSG_NOSIGNAL) = 44
poll([{fd=3, events=POLLIN}], 1, 2000)  = 0 (Timeout)
close(3)                                = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x12f000
read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
close3)
Está a ligar-se ao 192.168.112.2, que é o servidor DNS definido no /etc/resolv.conf. Se eu mudar isso no ficheiro para 192.168.112.5 e tentar novamente, acontece o mesmo (não se apercebe da mudança). Mas agora, sem mais alterações no ficheiro, se correr um debugger contra o processo do MSC:
[root@pacman tmp]# gdb -p 4649
GNU gdb (GDB) Fedora (7.1-18.fc13)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http: bugs="" gdb="" software="" www.gnu.org="">.
Attaching to process 4649
Reading symbols from /usr/informix/srvr1170uc4/bin/oninit...(no debugging symbols found)...done.
[...]
(gdb) call __res_init()
$1 = 0
(gdb) detach
Detaching from program: /usr/informix/srvr1170uc4/bin/oninit, process 4649
(gdb) quit
[root@pacman tmp]#
Se fizer um "call", ou seja chamar a função, a __res_init() que verifiquei ser o nome interno da função definida na libc.so e fizer um novo trace a uma conexão obtenho:
socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.112.5")}, 16) = 0
gettimeofday({1326038409, 784712}, NULL) = 0
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}])
send(3, "\364K\1\0\0\1\0\0\0\0\0\0\0011\003112\003168\003192\7in-ad"..., 44, MSG_NOSIGNAL) = 44
poll([{fd=3, events=POLLIN}], 1, 2000)  = 0 (Timeout)
close(3)                                = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=438, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x12f000
read(3, "127.0.0.1\tpacman.onlinedomus.net"..., 4096) = 438
close(3)
Ups! Hacked!... Claro que isto não é suportado. Não tente isto numa instância "real". Apenas serve para provar uma teoria.


Conclusões

O que tentei demonstrar neste artigo é como o Informix interage com os serviços de DNS, se os mesmos estiverem configurados no seu ambiente. Espero ter deixado claro que uma vez activo, o DNS toma um papel muito importante no estabelecimento de ligações. Tanto que um mau funcionamento do DNS terá um impacto muito significativo no sistema de gestão de base de dados. Há alguns pontos em que se pode atribuir responsabilidade ao Informix. Em particular:
  1. O facto de os pedidos de DNS inverso serem feitos pelo processador virtual da class MSC pode gerar atrasos em pedidos que poderiam correr bem, apenas porque um pedido anterior teve um problema. Ter mais de um processador virtual MSC pode aliviar isto significativamente, mas poderá não resolver por completo

  2. O facto de o Informix não estabelecer timeouts na chamada à gethostbyaddr() - ou equivalente -pode também causar atrasos desnecessários. Mas note-se que a assinatura das funções não tem nada que o permita, e portanto seria necessário criar um alarme que enviasse um sinal ao processo, e que este verificasse se ainda estava à espera da chamada. Isto traria um peso adicional que não faz muito sentido, especialmente quando os timeouts podem ser configurados na configuração geral dos servidores de DNS

  3. O facto de as funções chamadas fazerem cache das configurações, aliado ao facto de o Informix não proporcionar uma forma de limpar essa cache (havendo funcionalidades de baixo nível que o permitem), significa que uma mudança nos endereços de DNS requerem uma paragem e arranque do motor Informix. Existe um pedido de funcionalidade registado para isto e muito me agradaria que fosse implementado
Por outro lado, o Informix 11.7 introduziu uma excelente funcionalidade que permite aliviar quase todos os problemas.Com o parâmetro NS_CACHE podemos configurar tempos de caching para vários sistemas (DNS, utilizadores/passwords, serviços...). Isto pode reduzir ao mímino o número de pedidos que são feitos. Naturalmente sempre que lidamos com caches temos o risco de lidar com informação desactualizada, mas neste caso limpar as caches é tão simples quanto correr o comando "onmode -wm NS_CACHE=..." e indicar um timeout de zero segundos. Já agora, este seria um bom sitio/momento para forçar a chamada da função res_init()...

Para fechar o artigo gostaria de mencionar algumas boas práticas:
  1. Usar o Informix 11.7 se possível, para que possa tirar proveito do sistema de caching. Os administradores de sistema e/ou de DNS irão apreciar isto se tiver uma taxa de novas ligações muito alta

  2. Considerar incluir "files" no ficheiro de configuração /etc/nsswitch.conf. Algumas pessoas podem considerar isto uma má ideia, pois pode causar muita actividade sobre o ficheiro /etc/hosts. Mas se as consultas ao ficheiro só forem feitas se os DNS não responderem, e o ficheiro fôr mantido com poucas entradas, e idealmente se usar o sistema de cache do Informix 11.7 o impacto adicional será negligenciável. E isto pode salvá-lo, caso tenha problemas nos DNS, pois temporariamente poderia adicionar entradas a este ficheiro, permitindo assim que a resolução de nomes fosse feita. Note que pelo menos no meu sistema de testes (Linux), mesmo a chamada à função res_init() não força a leitura novamente do ficheiro /etc/nsswitch.conf

  3. Faça com que os seus administradores de DNS e os DBAs se entendam... Terão de trabalhar em conjunto e têm de entender que os seus serviços estão intimamente ligados

  4. Use o Informix 11.7 se puder, para que possa usar o parâmetro REMOTE_SERVER_CFG. Em muitas empresas os DBAs não têm permissões para gerir o /etc/hosts.equiv (ficheiro de sistema). Se tiver um problema de DNS que impossibilite as conversões de endereços IP em nomes, as suas relações de confiança irão ser afetadas se estiverem definidas com nomes.e não endereços (o normal). Numa situação de emergência poderá ser útil que o DBA possa agir imediatamente e temporariamente adicionar os endereços IP ao ficheiro que estabelece as reações de confiança. Com o REMOTE_SERVER_CFG isso será possível sem privilégios de administrador de sistema

  5. Use timeouts configurados no /etc/resolv.conf baixos de forma a que o tempo inútil de espera seja menor (uma consulta a um DNS é algo muito rápido)