19 de diciembre de 2013

¿Cómo obtener las conexiones INET de un proceso en solaris?

Hace unos días necesité, por motivos del trabajo, saber qué conexiones tcp hacía un proceso. La cosa es que este proceso tenía varias instancias corriendo. Por esto se me ocurrió hacer un script adaptado a mis necesidades.

Sé que esto se puede hacer con lsof o hasta con pfiles, pero vaya, esto me ayudó y me ahorró un poco de tiempo en el proceso de monitoreo.

Saludos!
-------------------------------------------------------------------------------------------------
#!/bin/bash
export PSIDS=$@
export PFILES="/bin/pfiles"
export EGREP="/bin/egrep"
export GREP="/bin/grep"
export AWK="/bin/awk"
export SED="/bin/sed"

for PSID in $PSIDS ; do
        echo "Para PID: $PSID"
        CONNLIST=`${PFILES} ${PSID} | ${EGREP} "peername|sockname"| ${GREP} -v \

"0.0.0.0"| ${AWK} '{print $3,":",$5}'| ${SED} -e 's/ : /:/'`
        echo $CONNLIST | ${AWK} '{for(i=1; i<=(NF/2); i++) print $((i*2)-1),"->",$(i*2);  }'
        echo "********************"
done

-------------------------------------------------------------------------------------------------
psInet.sh

 Para invocarlo, primero necesitamos sacar la lista de PID's en los que estoy interesado.

Suponiendo que un proceso llamado someProcess tiene 6 instancias corriendo, obviamente con PID's diferentes, puedo usar el siguiente comando para sacar la lista y pasarsela a mi script

$ ps -fea | grep someProcess | grep -v grep| awk '{print $2}'| xargs  ./psInet.sh

 la salida se verá más o menos así:

For PID: 25427
10.252.128.10:36557 -> 10.252.128.10:49911
10.252.128.12:36489 -> 10.252.128.12:5025
10.252.128.12:36495 -> 10.252.128.12:5025
********************
For PID: 13427
10.252.128.10:5550 -> 10.22.180.11:43854
10.252.128.10:5550 -> 10.22.180.11:43854
10.252.128.10:5550 -> 10.22.180.11:43854
10.252.128.10:31710 -> 10.252.128.10:49911
10.252.128.12:31495 -> 10.252.128.12:5025
10.252.128.12:31506 -> 10.252.128.12:5025
********************
For PID: 23876
10.252.128.10:44310 -> 10.252.128.10:49911
10.252.128.12:44147 -> 10.252.128.12:5025
10.252.128.12:44158 -> 10.252.128.12:5025
********************
For PID: 6766
10.252.128.10:38621 -> 10.252.128.10:49911
10.252.128.12:38522 -> 10.252.128.12:5025
10.252.128.12:38528 -> 10.252.128.12:5025
********************
For PID: 16489
10.252.128.10:20754 -> 10.252.128.10:49911
10.252.128.12:20640 -> 10.252.128.12:5025
10.252.128.12:20648 -> 10.252.128.12:5025
********************
For PID: 7279
10.252.128.10:20091 -> 10.252.128.10:49911
10.252.128.12:20011 -> 10.252.128.12:5025
10.252.128.12:20021 -> 10.252.128.12:5025
********************

Y claro, también funciona para un PID:

$ ./psInet.sh 7279
For PID: 7279
10.252.128.10:20091 -> 10.252.128.10:49911
10.252.128.12:20011 -> 10.252.128.12:5025
10.252.128.12:20021 -> 10.252.128.12:5025
********************


30 de noviembre de 2013

Introducción a DTrace

Pues bueno, paree que ha sido un poco de tiempo desde que noe scribo en mi propio blog. Muchas cosas han pasado de 2011 para acá. La vida ha dado vueltas y bueno. La cosa es que aquí sigo :)

De abril de 2013 a la fecha (dic de 2013), pienso que ha sido una etapa muy productiva, sobre todo en mi vida profesional.
Dentro de las cosas que he estudiado un poco está el uso de DTrace.
Antes de continuar debo advertir, que de ninguna forma soy un experto y que de hecho, estoy aprendiendo. Mi método es muy simple: leo el tutorial en inglés, hago los ejercicios, adapto lo leído a un curso que estoy preparando en mi trabajo y sobre eso, traduzco lo que escribí en inglpés y lo pongo en español aquí. Sí, lo leo una ves y luego lo escribo dos. A ver si cono eso me lo aprendo. Advierto que sigo la misma estructura del documento original y de hecho ocupo los mismos ejemplos. En cuanto vaya aprendiendo más quizá los cambie ;)

Sin más comencemos.

1. ¿Qué y para qué es DTRace?

DTrace es una herramienta que nos ayuda a entender el flujo del software mediante la recolección de datos desde lugares bien específicos del mismo. Esto se hae mediante unas cosas llamadas pruebas, que básicamente se encargan de sensar el comportamiento de un programa. Una ves lanzados, actúan ya sea obteniendo datos y reportándonoslos o simplemente haciendonos notar que se ha lleado a un punto del programa en el que se ha disparado ese sensor.

Cada prueba se identifica en el sistema operativo mediante un identificador numérico único y una cadena de texto que hace las veces de nombre, en u formato legible para el humano. Dos de las pruebas más básicas son BEGIN, que se lanza cuando se inicia una nueva solicitud de trazado; y END, que se dispara cuando la sesión termina.


Vamos a verlo con dos simples ejemplos.

Ej 1.

# dtrace -n BEGIN
dtrace: description 'BEGIN' matched 1 probe
CPU     ID              FUNCTION:NAME
  0      1                  :BEGIN
^C
#

Esta salida significa que la prueba llamada BEGIN, tiene un ID de 1 y que se ejecuta en el CPU 0 se ha activado. Para este ejemplo no se realiza ninguna acción más allá por lo que DTrace solamente informa que la prueba se ha disparado.
 Note el uso del modificador -n que indica a DTrace que lo que sigue a continuación es el nombre de una prueba y que como tal, debe invocarla.

Por otro lado, una sesión de trazado puede construírse con varias pruebas y acciones. Veamos el siguiente ejemplo en el que se emplea la prueba END.

Ej 2.

# dtrace -n BEGIN -n END
dtrace: description 'BEGIN' matched 1 probe
dtrace: description 'END' matched 1 probe
CPU     ID              FUNCTION:NAME
  0      1                  :BEGIN
^C
  0      2                    :END
#

Al cancelar la sesión de trazado, forzamos s finalzación por lo que la prueba END es disparada.
Por supuesto, una sesión de trazado puede y generalmente es, mucho más compleja que estos ejemplos anteriores. Para esto, DTrace acepta el uso de scripts escritos en su propio leguaje de programación: D.

Ej 3. hello.d

BEGIN
{
    trace("hello, world");
    exit(0);
}

Este es el ejemplo cásico de "Hello world" con D. Aquí además, definimos un par de acciones a ejecutar cuando la pruea BEGIN se dispare.

Para este ejemplo, la salida se verá más o menos así:

# dtrace -s hello.d
dtrace: script 'hello.d' matched 1 probe
CPU     ID              FUNCTION:NAME
  0        1                  :BEGIN   hello, world
#

Note el modificador -s, que instruye a DTrace para qe lea el cotenido del archivo hello.d
Cuando la prueba se dispara, escribe el mensae "Hello world" y sale.
Observe también la similitud con la sintáxis del lenguaje C. Esto se debe a que D es un derivado de C. De esta forma, si estás familiarizado con la sintáxis de C te será bastamte sencillo aprender la estructura de D.

2. Pruebas y proveedores

Las pruebas de DTrace vienen de difernetes módulos del kernel, llamados proveedores. Cada uno implementa diferentes instrumentaciones para crear sus pruebas.
Para ver una lista de todas las diferentes pruebas ejecute: 

# dtrace -l
  ID   PROVIDER            MODULE          FUNCTION NAME
   1     dtrace                                     BEGIN
   2     dtrace                                     END
   3     dtrace                                     ERROR
   4   lockstat           genunix       mutex_enter adaptive-acquire
   5   lockstat           genunix       mutex_enter adaptive-block
   6   lockstat           genunix       mutex_enter adaptive-spin
   7   lockstat           genunix       mutex_exit  adaptive-release


Para saber el número total de pruebas disponibles en su instalación de Solaris, ejecute:

# dtrace -l | wc -l
        30122

Reresando a la sesión de trazado, observe que las pruebas tienen un nombre leíble para el humano. Este nombre se compone de cuatro partes.

    proveedor:módulo:función:nombre

Proveedor: Regularmente es el nombre del módulo del kernel al que la prueba pertenece
Módulo: Especifica el lugar del programa en el que la prueba reside. Esto puede ser tanto en el módulo del kernel o en una librería de usuario.
Función: Si la prueba corresponde a un lugar específico de un prorama, el nombre de la función en la que se localiza la prueba.
Nombre: El componente final de la prueba es el nombre, que generalmente nos dará una idea de lo que hace, tal como BEGIN o END.

Note que algunas pruebas no tienen nombre de módulo o de función, esto es porque en realidad no corresponden a ninguna función de programa específico. Una prueba con módulo y función definidos se conoce como prueba anclada. Las que no, se llaman prueas no ancladas.

Por defecto, so no se define un proveedor, módulo o función, DTrace busca todas las pruebas que tengan el mismo nombre. Para los ejemplos anteriores hay sólo una prueba BEGIN y otra END, de forma que al lanzarse, el resultado es siempre el mismo.
El nombre completo de la prueba BEGIN es dtrace:::BEGIN, lo que indica que su proveedor es el mismo marco de trabajo de dtrace y no está anclada a ningún módulo o función.

Sabiendo esto, el programa hello.d se debe escribir así:

dtrace:::BEGIN
{
    trace("hello, world");
    exit(0);
}

3. Compilación e instrumentación.

A diferencia de un programa común en Solaris que, cuando se compila genera un código objeto ejecutable , un programa en D al compilars }e se envían directamente al kernel del sistema operativo para su ejecución. Estos programas se compilan en un código intermedio seguro, del mismo modo que un programa en java. Este código intermedio se revisa por seguridad en busca de probables errores de programación que pudieran causar corrupción que pudiera llevar a daños en el sistema operativo. 

Se pueden ejecutar múltiples sesiones de trazado n paralelo usando la misma librería de instrumntación sin temer a las interferncias entre sesiones, las corrupciones de datos o el mal funcionamiento del sistema.

4. Variables y expresiones aritméticas.

Para los siguientes ejemplos usaremos el proveedor profile para implementar un par de contadores básicos. El proveedor profile es capaz de crear pruebas al vuelo basado en la descripción de la prueba hallada en el programa D. Esto es, si definimos una prueba p}como profile:::tick-nsec, el proveedor va a crear una prueba qu se lance cada n segundos.

Ej. counter.d

/*
 * Count off and report the number of seconds elapsed
 */
dtrace:::BEGIN
{
    i = 0;
}

profile:::tick-1sec
{
    i = i + 1;
    trace(i);
}

dtrace:::END
{
    trace(i);
}

Note la sintáxis para los comentarios, justo de la manera en la que se haría en algún programa de C/C++ o Java


La cosa es, que al ejecutarse, el prorama va a contar el número de segundos que pasan desde que el programa se inicia hasta que se cancelado con Ctrl_C. La salida lucirá así:

# dtrace -s counter.d
dtrace: script 'counter.d' matched 3 probes
CPU     ID                    FUNCTION:NAME
  0  25499                       :tick-1sec         1
  0  25499                       :tick-1sec         2
  0  25499                       :tick-1sec         3
  0  25499                       :tick-1sec         4
  0  25499                       :tick-1sec         5
  0  25499                       :tick-1sec         6
^C
  0      2                             :END         6
#

A diferencia de C/Java y de la misma forma que cualquier lenguje de programación interpretado, las variables en D pueden crearse al vuelo al usarlas en cualquier declaración.
Los tipos de las variables se definen el la primera asignación. Para el caso del ejemplo anterior, a la variable i  se le asigna un valor entero por lo que conservará este valor hasta el final del tiempo de ejecución del programa.
DTrace incorpora los mismos tipos de entero básicos que tiene C: char, int, short, long y long long (un entero extendido), además de otros cientos de tipos definidos por los proveedores.

Note que también podemos usar el operador ++, de lamisma forma que en C++/Java.

profile:::tick-1sec
{
    trace(++i);
}

Otra caracter´stica interesante es que, podemos declarar explícitamente el tipo de una variable con una sintáxis similar a la que usaríamos para acer una conversión de tipo en C++ (cast)

dtrace:::BEGIN
{
    i = (char)0;
}

Después de ejecutar conter.d por un rato podremos observar que el contador crece y después de un tiempo vuelve a comenzar desde cero.
Para aquellos impacientes, la prueba se puede cambiar de manera que usemos la siguiente sentencia:  profile:::100msec para hacer que el contador cambien cada 100 milisegundos (10 veces por segundo).

5. Predicados

Una diferencia grande entre DTrace y la mayoría de los lenguajes de programación es su falta de estructuras de control del tipo if-else y la de los ciclos de control. En este punto es umportante entender que el flujo de las sentencias de DTrace se escrien y ejecutan en una forma lineal. Sobre esto, DTrace ofrece una característca para condicionar el trazado y modificar su flujo de ejecución por medio de expresiones lógicas llamadas predicados. Cada uno de estos predicados se usan para evaluar la prueba para decidir si se ejecuta el bloque de acciones definidas para esta. Si el predicado evaluado es verdadero (representado por cualquier valor diferente de cero), se ejecuta la lista declausulas; de lo contrario, si el predicado es falso, indicado por un valor igual a cero; ninguna de las clausulas asociadas a la prueba se ejecutara de manera que la prueba entera se ignora.

Ej. countdown.d

dtrace:::BEGIN
{
    i = 10;
}

profile:::tick-1sec
/i > 0/
{
    trace(i--);
}

profile:::tick-1sec
/i == 0/
{
    trace("blastoff!");
    exit(0);
}

La salida de countdown.d se verá algo así:

# dtrace -s countdown.d
dtrace: script 'countdown.d' matched 3 probes
CPU     ID                    FUNCTION:NAME
    0  25499                       :tick-1sec        10
    0  25499                       :tick-1sec         9
    0  25499                       :tick-1sec         8
    0  25499                       :tick-1sec         7
    0  25499                       :tick-1sec         6
    0  25499                       :tick-1sec         5
    0  25499                       :tick-1sec         4
    0  25499                       :tick-1sec         3
    0  25499                       :tick-1sec         2
    0  25499                       :tick-1sec         1
    0  25499                       :tick-1sec   blastoff!
#

Note la importancia de previsualizar el flujo del programa con antelación a su codificación. Esto ayudará a hacerlo de manera más eficiento y consiguientemente con menos errores de l


Para el siguiente ejemplo usaremos un nuevo proveedor a fin de crear un programa más realista en DTrace. Este proveedor se llama syscal y nos permitirá disparar pruebas cada ves que un programa entra o salga de una llamada de sistema en Solaris. Para esto, necesitamos prepara el terreno:

a. Abra una nueva ventana o un shell.
b. Revise el PID de este nuevo shell
    > echo $$
c. Regrese a la primera terminal y, asumiendo que el PID del nuevo shell es 12345, escriba el siguiente código en un archivo llamado rw.

Ej. rw.d

syscall::read:entry,
syscall::write:entry
/pid == 12345/
{

}

Note que la sección de clausulas para la prueba  está vacia pues sólo queremos, por el momento, saber cuando se hace una llamada a sistema o se regresa de ella dentro del PID 12345.

d. Vaya al nuevo shell y escriba algunos comandos.

Después de dos o tres comandos lanzados, rw.d lucirá má so menos así:

# dtrace -s rw.d
dtrace: script 'rw.d' matched 2 probes
CPU     ID                    FUNCTION:NAME
    0     34                      write:entry
    0     32                       read:entry
    0     34                      write:entry
    0     32                       read:entry
    0     34                      write:entry
    0     32                       read:entry
    0     34                      write:entry
    0     32                       read:entry
...

Esta es la salida esperada si meditamos un poco en lo que sucede cuando un usuario escribe un caracter para que luego el sistema operativo lo escriba en la terminal y el usuario pueda ver lo que tecleó.
Note también que este script tiene una sola clausula de prueba con varias pruebas separadas por coma y cada una en su propia línea, esto se hace para facilitar la lenctura.
Luego, el predicado usa la variable predefinida pid de DTrace a la cual se le asigna el PID del hilo que lanzó esta prueba.
Algunas de estas variables son: errno, execname, pid, tid, probeprov, probemod, probefunc y probename.

Ahora vamos a intentar un nuevo script con la misma funcionalidad basica del comando truss. Para esto basta con cambiar  rw.d y dejarlo como sigue:

Ej  rw2.d

syscall:::entry
/pid == 12345/
{

}

Ejecute algunos comandos como cd, ls y date para ver qué cosa se reporta.

6. Output formatting.

Ejecute:

> truss date

Verá que este comando meustra su salida en un formato específico que es muy sencillo de leer y entender.
 Ahora, para hacer que rw.d muesra una salida similar a la del comando truss, revise el siguiente código y captúrelo en un archivo llamado trussrw.d

Ej. trussrw.d

syscall::read:entry,
syscall::write:entry
/pid == $1/
{
    printf("%s(%d, 0x%x, %4d)", probefunc, arg0, arg1, arg2);
}

syscall::read:return,
syscall::write:return
/pid == $1/
{
    printf("\t\t = %d\n", arg1);
}


Esto que estoy aprendiendo está basado en "DTrace User Guide" publicado por Oracle.