The Evangeist .INFO

CCNP SWITCH 12: Diseño de red

Diseño de Red Jerárquico

Modelo de Red Predecible

Cisco ha definido un diseño de red jerárquico que está organizado en distintas capas de dispositivos que resulta es una red eficiente, inteligente, escalable y de fácil gestión. Estás capas están divididas en tres que son: Leer más »

CCNP SWITCH 11: Multilayer Switching

InterVLAN Routing

Para transportar paquetes ente VLANs se debe usar un dispositivo de Capa 3, que por lo general suele ser un router. El router debe tener una conexión lógica o física a cada VLAN para que pueda enviar paquetes entre ellas. A esto se le llama interVLAN routing.

Tipos de interfaces

Los switches multilayer  pueden operar tanto en Capa 2 como en Capa 3 (InterVLAN). En Capa 2 funciona entre interfaces asignados a VLANs de Capa 2 o a Trunks de Capa 2. En Capa 3 funciona entre cualquier tipo de interface mientras esta tenga una IP asignada.

Un switch multilayer puede asigna una IP a una interface física lo mismo que un router aunque también puede asigna una IP a una interface lógica (que se llaman Switched Virtual Interface SVI)que representa la VLAN entera. La IP que se configura se convierte en el Gateway por defecto para cualquier servidor que esté conectado a la interface o VLAN, con lo cual el servidor usará la interface de Capa 3 para comunicarse con el exterior de su dominio de broadcast. Leer más »

Consultando bases de datos relacionales

Consultando la BD: SELECT

En este apartado voy a explicar las consultas simples en SQL. La forma básica de un SELECT es:

SELECT [opciones] valores
FROM tablas
WHERE condiciones
[ORDER BY  campos]

Analizando la sintáxis tenemos

  • opciones: según el SGBD que usemos tendremos opciones para establecer la prioridad de la consulta, obtener resultados no duplicados, y opciones útiles como el SQL_CALC_FOUND_ROWS de MySQL, que calcula cúantos registros devuelve la consulta sin aplicarle un LIMIT (así podemos obtener por ejemplo 20 registros, ideal para una paginación, y saber cuántos registros devolvería sin el LIMIT lanzando después un SELECT FOUND_ROWS()).
  • valores: son las columnas con valores que devolverá la consulta. Aquí podemos indicar de todo: nombres de campos, operaciones aritméticas, valores constantes, funciones, subconsultas… y también así, podemos asignarles un alias para que se devuelvan con ese nombre de columna (“SELECT campo1 c1 FROM tabla” nos devolvería el campo1 con el nombre de columna c1). En los alias, dependiendo del SGBD, se puede usar el keyword opcional AS, que es algo más claro a la vista: SELECT campo1 AS c1 FROM tabla
  • tablas: aquí van todas las tablas que participarán en la consulta. Pueden ir tanto tablas (y vistas), como subconsultas, y se pueden usar alias también (el keyword AS es menos soportado aquí, ya que por ejemplo Oracle no lo admite). Una subconsulta significa poner una consulta entre paréntesis y usar su resultado como una tabla normal:
    SELECT campo1, sc1, sc2
    FROM tabla, (SELECT campo2 AS sc1, campo2  AS sc2 FROM tabla2)
    WHERE campo1 = sc1

    Algunos SGBD como MySQL permiten varias opciones en este parte, como indicar qué índice queremos que se use para recorrer la tabla. Esto último es muy raramente útil pues el optimizador de consultas no suele errar en el índice a usar; es más común que se equivoque en el orden en el que une las tablas, pero para eso hay opciones como STRAIGHT_JOIN en el SELECT que obligan al optimizador a unir las tablas en el orden en el que las hemos puesto nosotros.

    Las JOIN van en este lugar también pero más adelante las veremos.

  • condiciones: serie de condiciones que en conjunto tienen que evaluar a TRUE para que el registro sea devuelto. Debido al orden en que se procesan las consultas, aquí no se pueden usar los alias de los campos que hayamos indicado en la parte de “valores” (pero sí los alias de tablas), así que tenemos que usar el nombre del campo o poner de nuevo la operación entera si se trata de una operación. Son estándares expresiones como IS NULL/IS NOT NULL (devuelve TRUE si lo evaluado es/no es nulo), NOT (niega la expresión, de modo que si evalúa a TRUE no será devuelto el registro), BETWEEN valor1 AND valor2 (el operando precedente tiene que estar entre los valores indicados, inclusive), LIKE ‘patrón’ (el operando, que debe ser una cadena, debe coincidir con el patrón, que admite, comodines como el ‘%’ para cualquier carácter de 0 a N veces y el ‘_’ para cualquier carácter una sola vez)… y como no, también podemos usar funciones del SGBD y operaciones de cualquier tipo, además de los operadores >, >=, <>, <, <=.Como operadores lógicos tenemos el NOT, AND y OR (con ese orden de preferencia), y para no marearnos en las expresiones complicadas podemos meter entre paréntesis las expresiones:
    ... WHERE (condicion1 OR condicion2) AND condicion3 AND condicion4

    Aquí es donde filtraremos los resultados para obtener lo que nos interese, como los usuarios que tienen el nombre Miguel, los trabajadores que tienen más de 30 años, las líneas de pedido de un pedido concreto…

  • ORDER BY: aquí especificamos el orden de los registros segun el campo que queramos. Aquí sí se puede usar alias de campos, o el número de posición que ocupa el valor dentro de la fila:
    SELECT campo1 AS c1, campo2 FROM tabla ORDER BY 2

    En el ejemplo anterior se ordenarían los registros por campo2, que es la columna número 2 del resultado

    También se puede ordenar por varios campos, y en ascendente o descendente:

    ... ORDER BY campo1 DESC, campo2 ASC

    El ORDER BY en ocasiones puede hacer que el resultado tarde más en ser devuelto pues el SGBD puede necesitar realizar otra pasada para ordenarlo.

Limitando el número de registros devueltos

Es frecuente que al consultar queramos obtener un número máximo de registros, por ejemplo para realizar una paginación o simplemente porque queremos obtener el último registro insertado y no queremos andar con consultas complejas para sacarlo.

Todos los SGBD tienen su método para realizar esto: MySQL tiene el LIMIT (haciendo SELECT * FROM tabla WHERE … ORDER BY … LIMIT [10 | 5, 10] obtendríamos los 10 primeros registros o los 10 registros a partir del número 5 con la segunda sintaxis), MSSQL Server tiene el TOP (con SELECT TOP 10 … FROM tabla sacaríamos los 10 primeros registros), Oracle usa el ROW_NUMBER (mediante subconsultas; es un sistema bastante más incómodo que los demás, pero es más estándar), PostgreSQL usa también LIMIT (LIMIT 10 OFFSET 5)…

El método más estándar es mediante cursores pero no todos los SGBD siguen las directrices del ISO SQL.

Consultando la BD: INNER JOIN

SELECT ... FROM tabla1 INNER JOIN tabla2 ON tabla1.campo = tabla2.campo WHERE ...

Cuando realizamos una INNER JOIN lo que hacemos es unir dos tablas mediante una o varias condiciones, habitualmente porque están relacionadas por un campo clave. Se pueden concatenar tantas JOIN como queramos; de hecho es la manera habitual de seguir las relaciones de las tablas al realizar consultas (una tabla se relaciona con otra, que a su vez se relaciona con otra…).

INNER JOIN es exclusivo, de modo que la relación debe cumplirse siempre o sino no se devolverá el registro. Así, si consultamos todos los clientes y unimos los pedidos mediante el ID de cliente obtendremos sólo los que tengan pedidos.

Si hacemos:

SELECT campo1 FROM tabla t INNER JOIN tabla2 t2 ON t.id = t2.id_t

sería lo mismo que hacer:

SELECT campo1 FROM tabla t, tabla2 t2 WHERE t.id = t2.id_t

De hecho, los optimizadores de consultas (query optimizer) suelen cambiar las JOINS por consultas así. No es que sea más óptimo realizarlo así, pero para operar sobre las tablas para optimizar la consulta se manejarán mejor así. Pero del lado del programador de BD, es más fácil de ver las uniones mediante JOINs que mirando las tablas del FROM y luego las condiciones del WHERE (al menos para mí).

Las condiciones del JOIN (el ON …)

Cabe destacar que en la condición del JOIN se puede escribir cualquier tipo de expresión que evalúe a TRUE (o a FALSE, aunque en ese caso no se devolverá el registro): podemos usar LIKE, operaciones aritméticas, operadores >, =… pero no es recomendable pues por razones que el optimizador sabrá suele ser más lento que si las hubiéramos puesto en el WHERE. Por lo tanto:

SELECT campo1 FROM tabla t INNER JOIN tabla2 t2 ON t.id = t2.id_t AND t.campo2 > t2.campo2

es mejor traducirlo a:

SELECT campo1 FROM tabla t INNER JOIN tabla2 t2 ON t.id = t2.id_t WHERE t.campo2 > t2.campo2

Por lo demás, cualquier condición que no sea ligar las tablas por ID es mejor ponerla en el WHERE. También se puede omitir el ON con la condición, pero en ese caso el resultado será en la mayoría de los casos catastrófico, pues si usamos 2 tablas sin relacionarlas se realizará el producto cartesiano entre ellas, o sea que por cada registro de la tabla 1 se devolverán todos los registros de la tabla 2. Si son tablas de 10 y 5 registros no es mucho problema, se devolverían 50 registros, pero si son de 10000 y 50000…, además no sólo será un resultado grande, también muy lento y pesado en recursos para el servidor de BD.

Rendimiento de las JOIN: no olvidarse de las claves

Y por último, el rendimiento de las JOIN. Es conveniente realizar la JOIN mediante campos que sean claves (primarias, candidatas, externas…) pues el SGBD recorrerá las tablas para satisfacer las condiciones de la JOIN, y si se usan índices el recorrido es infinítamente más rápido. Así que es importante que todo campo referencial, que son los que se usan para unir las tablas en las consultas, sean claves.

Ejemplo

Vamos a suponer una BD con clientes, pedidos y productos (1 cliente tiene N pedidos, 1 pedido es de un único cliente, y 1 pedido tiene N productos que a su vez pueden estar en M pedidos). De la relación pedidos – productos nos sale la entidad lineas_pedidos.

clientes -> pedidos -> lineas_pedido <- productos

Tabla clientes:

id_cliente nombre
2 John
10 Edward
13 Rose

Tabla pedidos:

id_pedido id_cliente fecha
1 2 2009-01-24 08:00:15
2 2 2009-01-24 09:22:08
3 10 2009-01-27 13:10:22
4 2 2009-01-27 17:54:15
5 10 2009-01-29 08:13:01
6 10 2009-01-29 10:28:57
7 10 2009-01-29 18:26:59
8 2 2009-01-30 12:30:47
9 10 2009-01-30 12:31:18

Tabla productos:

id_producto nombre precio_base stock
1 frigorífico ACME 749.00 25
2 televisor plasma 799.00 87
3 minicadena HI-FI 129.00 19
4 aspirador ACME 89.00 28
5 climatizador ACME 1499.00 32

Tabla lineas_pedido:

id_pedido id_producto precio_unidad cantidad
1 1 709.00 1
1 4 89.00 2
2 2 729.00 1
3 2 699.00 1
4 2 729.00 1
4 3 129.00 1
4 4 89.00 2
5 2 699.00 7
6 5 1399.00 9
7 4 89.00 5
8 2 729.00 1
9 5 1399.00 8

SELECT * FROM clientes c INNER JOIN pedidos p ON c.id_cliente = p.id_cliente

id_cliente nombre id_pedido fecha
2 John 1 2009-01-24 08:00:15
2 John 2 2009-01-24 09:22:08
10 Edward 3 2009-01-27 13:10:22
2 John 4 2009-01-27 17:54:15
10 Edward 5 2009-01-29 08:13:01
10 Edward 6 2009-01-29 10:28:57
10 Edward 7 2009-01-29 18:26:59
2 John 8 2009-01-30 12:30:47
10 Edward 9 2009-01-30 12:31:18

Como se puede observar no se han devuelto registros del cliente 13 porque no tiene pedidos. Y también se ve que por cada cliente se han devuelto tantos registros como coincidencias tiene en la tabla de pedidos. Si quiseramos obtener sólo un registro por cliente usaríamos GROUP BY para agrupar los registros, pero eso lo veremos más adelante.

SELECT pr.nombre, l.precio_unidad, l.cantidad, (l.precio_unidad * l.cantidad) AS total_linea,

ROUND(100 - ((l.precio_unidad * 100) / pr.precio_base), 2) AS descuento

FROM pedidos p INNER JOIN lineas_pedido l ON p.id_pedido = l.id_pedido

INNER JOIN productos pr ON l.id_producto = pr.id_producto

WHERE p.id_cliente = 2 AND p.id_pedido = 4
nombre precio_unidad cantidad total_linea descuento
televisor plasma 729.00 1 729.00 8.76
minicadena HI-FI 129.00 1 129.00 0.00
aspirador ACME 89.00 2 178.00 0.00

Lo que hemos hecho ahora es, dado un cliente y un pedido (para asegurarnos de que el pedido es de un cliente en concreto), sacar el detalle de los productos solicitados en dicho pedido. Así podríamos preparar el detalle de la factura con el nombre del producto, su precio, su cantidad, su precio total y el % de descuento que ha tenido sobre el precio base.

Consultando la BD: LEFT JOIN

SELECT ... FROM tabla1 LEFT JOIN tabla2 ON tabla1.campo = tabla2.campo WHERE ...

La sintaxis de LEFT JOIN es idéntica a INNER JOIN, pero su comportamiento es muy distinto. Con LEFT JOIN obtenemos todos los registros de la izquierda de la expresión (“tabla1″ en el ejemplo de arriba) y luego se unirían con la parte derecha, si hubiera coincidencias.

Por ejemplo, si unimos clientes con pedidos y un cliente no tiene pedidos, se devolvería el
cliente en el resultado pero con todos los campos de la tabla pedidos a NULL. Así es muy fácil
obtener todos los clientes que no tienen pedidos:

SELECT ... FROM clientes c LEFT JOIN pedidos p ON c.id_cliente = p.id_cliente WHERE p.campo IS NULL

Por lo demás la LEFT JOIN tiene las mismas características que una INNER JOIN en cuanto a las condiciones que se pueden poner en el ON, y opciones que el SGBD permita.

La variante RIGHT JOIN

Hay una variante no estándar de LEFT JOIN, la RIGHT JOIN, que hace exactamente lo mismo pero devolviendo todos los registros de la derecha de la expresión. Al no ser estándar y ser perfectamentesustituible por la LEFT JOIN (lo único que cambia es el orden en el que pongamos las tablas) no se recomienda usarla.

Ejemplo

Tabla clientes:

id_cliente nombre
2 John
10 Edward
13 Rose

Tabla pedidos:

id_pedido id_cliente fecha
1 2 2009-01-24 08:00:15
2 2 2009-01-24 09:22:08
3 10 2009-01-27 13:10:22
4 2 2009-01-27 17:54:15
5 10 2009-01-29 08:13:01
6 10 2009-01-29 10:28:57
7 10 2009-01-29 18:26:59
8 2 2009-01-30 12:30:47
9 10 2009-01-30 12:31:18

SELECT * FROM clientes c LEFT JOIN pedidos p ON c.id_cliente = p.id_cliente

id_cliente nombre id_pedido fecha
2 John 1 2009-01-24 08:00:15
2 John 2 2009-01-24 09:22:08
10 Edward 3 2009-01-27 13:10:22
2 John 4 2009-01-27 17:54:15
10 Edward 5 2009-01-29 08:13:01
10 Edward 6 2009-01-29 10:28:57
10 Edward 7 2009-01-29 18:26:59
2 John 8 2009-01-30 12:30:47
10 Edward 9 2009-01-30 12:31:18
13 Rose NULL NULL

A diferencia del INNER JOIN, hemos obtenido también un registro del cliente
13 aunque no tiene coincidencias en la tabla de pedidos.

Ahora, para obtener los clientes que no tienen pedidos simplemente hacemos:

SELECT * FROM clientes c LEFT JOIN pedidos p ON c.id_cliente = p.id_cliente WHERE p.id_pedido IS NULL
id_cliente nombre id_pedido fecha
13 Rose NULL NULL

Consultando la BD: GROUP BY

SELECT ... FROM tabla1 INNER JOIN tabla2 ON tabla1.campo = tabla2.campo WHERE ... GROUP BY campo1 [HAVING condiciones]

Con GROUP BY podemos agrupar, en torno a uno o varios campos (algunos SGBD admiten alias en vez de campos), los registros devueltos. Opcionalmente podemos usar condiciones del agrupamiento en el HAVING, algo típicamente usado con las funciones de agregado.

Funciones de agregado (COUNT, SUM, MAX, AVG…)

Cuando agrupamos los registros (no hay por qué usar JOINs, se puede hacer con una sola tabla) podemos usar las funciones de agregado (COUNT para contar registros, SUM para sumar valores de un campo, MAX para obtener el valor máximo del campo, AVG para obtener el valor promedio del campo…) en el SELECT y en el HAVING. Si sólo usamos una función de agreagado en el SELECT, no es obligatorio usar GROUP BY, pero si además de eso indicamos que se devuelva un campo, el SGBD no lo permitirá y devolverá un error. Así, podemos hacer:
SELECT COUNT(*) FROM tabla

pero no:

SELECT COUNT(*), campo1 FROM tabla

Es algo ilógico que se pueda usar una función de agregado sin agrupar, pero lo que ocurre es que por simplicidad los SGBD lo permiten para cuando se quiere obtener el número de registros, o el valor mayor de un campo, cosas bastante frecuentes. En estos casos es el propio SGBD el que se encarga de agrupar y generalmente de optimizar estas consultas. Pero cuidado con el parámetro que ponemos en las funciones de agregado, porque si indicamos un valor que sea nulo no se aplicará; y tened en cuenta que COUNT(*) es una excepción, pues * no es ningún campo o valor, pero los SGBD lo admiten como atajo para simplemente contar todos los registros de la consulta… no intentéis hacer SUM(*) porque eso fallará.

Filtrando los grupos con HAVING

Junto con las funciones de agregado es habitual usar el HAVING para obtener, de los grupos de registros devueltos, los que cumplan una o varias condiciones: obtener los clientes que han facturado más de 60000 euros en un año, los que han realizado más de 20 pedidos en un mes, los que su media de importe de pedidos es superior a 1000 euros…

En el HAVING se puede poner cualquier tipo de condición, incluso las que pueden ir en el WHERE, pero es muy recomendable que si una condición puede ir en el WHERE vaya en el WHERE, pues esas condiciones se usan en la optimización para recorrer más rápido los registros implicados; el HAVING, debido al orden de procesamiento de una consulta, se ejecuta casi al final del todo. El orden de procesamiento de una consulta suele ser: se unen y filtran las tablas implicadas (mediante las condiciones WHERE y las ON de las JOIN), se aplica el GROUP BY si lo hubiera (en este punto algunos SGBD ya saben qué alias hemos asignado a los valores del SELECT y nos permiten usarlos) y el HAVING si también lo hubiera (algunos SGBD también permiten usar alias aquí) y finalmente se aplica el ORDER BY. Huelga decir que los optimizadores de consultas tienen mucho que decir en estos pasos, pues a veces no necesitan ordenar el resultado, otras veces tienen que repasar el resultado de nuevo para ordenarlo

Ejemplo

Tabla clientes:

id_cliente nombre
2 John
10 Edward
13 Rose

Ahora vamos a añadir el importe del pedido a la tabla de pedidos. El importe de un pedido es, generalmente, propiedad del pedido pues la suma de los productos que contiene podría variar y el precio del pedido no debería variar. Es discutible pues se puede argumentar que el cambio de precio de uno de los productos que pidió el cliente no debe implicar el cambio del precio del pedido, y también se puede argumentar que si se considera inviolable el precio de un pedido también lo tienen que ser los precios de los productos que contiene.

id_pedido id_cliente fecha importe
1 2 2009-01-24 08:00:15 940.00
2 2 2009-01-24 09:22:08 100.00
3 10 2009-01-27 13:10:22 64.00
4 2 2009-01-27 17:54:15 85.00
5 10 2009-01-29 08:13:01 540.00
6 10 2009-01-29 10:28:57 50.00
7 10 2009-01-29 18:26:59 35.00
8 2 2009-01-30 12:30:47 1420.00
9 10 2009-01-30 12:31:18 680.00
SELECT c.id_cliente, COUNT(*) num_pedidos, SUM(p.importe) total
FROM clientes c
INNER JOIN pedidos p ON c.id_cliente = p.id_cliente
GROUP BY c.id_cliente
id_cliente num_pedidos total
2 4 2545.00
10 5 1369.00

Como vemos, el cliente 2 tiene 4 pedidos y el 10 tiene 5. El 13 no aparece porque no tiene ningún pedido y hemos usado INNER JOIN, pero si hubieramos usado LEFT JOIN sí aparecería con num_pedidos a 0 (ojo, no NULL, pues es una función, no un campo). También hemos sacado, a la vez, la suma de importes de sus pedidos.

Si queremos saber cuántos pedidos al día hace un cliente, y de cuánto es su pedido con importe más pequeño, hacemos (MySQL):

SELECT c.id_cliente, DATE_FORMAT(p.fecha, '%Y-%m-%d') dia, COUNT(*) num_pedidos, MIN(p.importe) AS minimo
FROM clientes c
INNER JOIN pedidos p ON c.id_cliente = p.id_cliente
GROUP BY c.id_cliente, dia
id_cliente dia num_pedidos minimo
2 2009-01-24 2 100.00
2 2009-01-27 1 85.00
10 2009-01-27 1 64.00
10 2009-01-29 3 35.00
2 2009-01-30 1 1420.00
10 2009-01-30 1 680.00

Consultando la BD: optimizar consultas

Sobre la optimización hay muchos matices, que dependen siempre del SGBD. Principalmente cada vez que lanzamos una consulta a la BD el SGBD usa el optimizador para satisfacerla lo más rápido posible. Si bien es cierto que en ocasiones consigue todo lo contrario: que se ejecute más lenta.

Como esta información es muy específica para cada SGBD, intentaré dar los consejos más frecuentes para que las consultas vayan mejor, y luego ampliaré un poco con MySQL pues es un sistema que se deja optimizar muy bien (aunque sería preferible no tener que optimizarlo…).

Consejos
  • La primera regla de oro es que el optimizador usará los índices para buscar los registros según los filtros que le hayamos dado en el WHERE o el ON de las JOINs. Así, buscar por un nombre de cliente implicará que no usará ningún índice si el campo nombre no es índice. Esto no tiene por qué ser lento, pero relativamente lo será porque si ese campo fuera un índice la búsqueda sería mucho más rápida. Algunos SGBD no tienen penalizaciones por indizar muchos campos de una tabla, pero en MySQL sí que la hay ya que el tamaño del índice en general crece haciendo que sea inmensamente mayor y por lo tanto se pierde la ventaja de un índice ligero. La gestión de los índices es todo un mundo para los administradores de BD, eso cualquier DBA de Oracle os lo puede decir; así que ante la duda es mejor que consultéis Internet o a vuestro admin a ver qué os puede recomendar.
  • El tamaño de fila es un valor muy importante en las consultas pues, si no hay índice a usar, se recorrerá toda la fila para buscar los campos del WHERE y así filtrar los resultados. Este valor se puede calcular con los tipos de dato de la tabla, o consultando el esquema INFORMATION_SCHEMA que contiene información sobre los meta-datos de todas las BD a las que tenemos acceso. También suele haber herramientas tipo SHOW que pueden devolvernos esa información, todo es cuestión de buscar en la documentación del SGBD.
  • Es importante que los campos clave externa estén indizados pues las JOIN los usan para realizar las uniones de registros con sus relacionados. Si tenemos en cuenta que por cada registro de una tabla primaria en una subordinada puede haber N registros relacionados, ralentizará mucho la consulta no tener un índice en ese campo referencial.
  • Las subconsultas son lentas. Esto no es cierto al 100% pero mejor si creemos que sí, pues para las subconsultas los SGBD suelen hacer tablas temporales, en memoria o en disco según pueda o decida qué es lo mejor. No hay que subestimar al optomizador en estos casos pues suele ser muy listo a la hora de decidir el camino más rápido para seguir.
  • ORDER BY y GROUP BY suelen ser enemigos. Ordenar un resultado que se ha agrupado es tarea que los SGBD suelen hacer por ellos mismos, de modo que al agrupar por un campo casi seguro que aprovecha para ordenarlo (ascendementemente) por él. Si intentamos ordenar el resultado por otro campo, MySQL por ejemplo tendrá que dar otra vuelta sobre el resultado para re-ordenarlo (apostad porque otros SGBD lo harán también). Asímismo, se suelen usar los índices para el orden del resultado e indicar un orden distinto suele suponer una penalización en el tiempo de respuesta. De modo que usad el ORDER BY con la cabeza y cuando sea necesario, porque a veces ordenamos sin necesidad.
  • No uséis el HAVING como sustituto del WHERE. El HAVING puede servir de WHERE (pero no a la inversa), pero no significa que convenga suplantarlo pues recordad que el filtro HAVING se aplica una vez satisfecho todo lo demás de la consulta: aquí ya no valen las ventajas de los índices. Evidentemente si queremos agrupar por un campo y obtener sólo los grupos que tengan más de X elementos o que la suma de uno de sus campos sea menor que Y, no queda otra que usar el HAVING, pues en el tiempo del WHERE no hay manera de saber cuánto vale el resultado de una función de agregado.
  • El orden de las tablas del JOIN sí es importante. Cuando relacionamos tablas en una consulta, ya sea con JOINs o mediante condiciones del WHERE, es cierto que los optimizadores las unirán en el orden que ellos crean conveniente, pero a veces se equivocan. Sobre todo MySQL, que parece que cuando usamos JOIN su orden de unión es el que le damos… pero tenemos varias herramientas para solventar este problema: el consejo general es indicar los JOIN en el orden más lógico que se nos ocurra, y si aún así el optimizador lo cambia, el SGBD nos suele proporcionar herramientas para indicar en la consulta que se use el índice que le digamos o se use el orden de unión que explícitamente le indiquemos.
  • Es un buen consejo separar los campos consultados con frecuencia de los campos grandes apenas usados. A veces tenemos en la tabla de clientes un montón de información que no se usa casi nunca, pero resulta que esa tabla de clientes es usada con frecuencia. Cuando las consultas no usen un índice la penalización viene dada por el tamaño de fila así que tener filas más pequeñas y cortas nos ahorraría mucho tiempo de respuesta. La solución es dividir la tabla en dos, con los campos más frecuenemente consultados en una tabla y los demás en otra (los TEXT, BLOB, y grupos de un muchos campos tipo CHAR/VARCHAR); para esto en la tabla que apenas usaremos nos llevaremos el id de la otra y lo dejaremos como clave primaria y foránea (externa) a su vez, en una relación 1 a 1 donde la usada con frecuencia sera la fuerte y la otra la subordinada.
  • El tamaño de fila del índice mejor si es constante. Tener campos de tamaño variable, como VARCHAR, en un índice no suele ser buena idea si usas MySQL (sobre todo con el motor MyISAM) por cuestiones de rendimiento e incluso de consistencia. No sería la primera vez en la que yo haya visto que una tabla se corrompe por estas cosas… podemos echarle la culpa al SGBD, al sistema de ficheros del sistema o a lo que sea, pero es mejor que lo tengáis en cuenta sobre todo si se rompe con frecuencia una tabla (en mi caso cambiar el engine a InnoDB era posible y solucionó el problema).
  • Las transacciones seguras masivas son más rápidas que las no seguras. Es habitual que tengamos que hacer cambios masivos en la BD, y especialmente notaremos una buena lentitud cuando sean inserciones. Los SGBD rara vez nos permiten usar distintos motores de almacenamiento de los datos, pero MySQL es un servidor que sí nos da a elegir. Es bien sabido que una inserción masiva es más rápida en MyISAM que en InnoDB, y la razón es la característica de transacciones seguras de InnoDB. MyISAM en contra bloquea las tablas, que es en cierto modo peor si queremos que la tabla siga en uso mientras insertamos, pero la gran demora de InnoDB puede ser un problema… si no usamos transacciones seguras. Un buen truco es iniciar la transacción segura, START TRANSACTION, realizar los INSERT, y luego finalizar la transacción segura con COMMIT. Sinceramente no recuerdo si esto es aplicable a otros SGBD como SQL Server u Oracle, pero si os va lento podéis probarlo.
Optimizar gracias a EXPLAIN (MySQL)

En MySQL tenéis una herramienta muy útil para la depuración de consultas: EXPLAIN. Anteponiendo EXPLIAN a la consulta (EXPLAIN SELECT … FROM tabla WHERE …) obtendremos un detalle de la ejecución de ella, con datos valiosos como el índice que se usa para cada JOIN, si se realiza un escaneo completo de la tabla, si el sistema tiene que volver a recorrer el resultado para ordenar… es un tema muy amplio para explicarlo en este artículo (además de que es algo exclusivo de MySQL) así que conviene echarle un ojo a la documentación oficial para que os hagáis una idea de cómo interpretar el resultado de EXPLAIN. Cuando depuréis una consulta con EXPLAIN, a grandes rasgos, lo más importante es fijarse en la columna “type” que determina el tipo de JOIN que realiza el SGBD: “all” significaría un escaneo completo de la tabla, “const” que sólo hay un resultado pues se está filtrando por una clave única comparada con un valor constante, “eq_ref” cuando es una JOIN de tablas relacionadas 1 a N, “ref” cuando la JOIN es de tablas N a M… conviene estudiarlo porque si en una JOIN que une dos tablas el “type” es “all” debemos eperar un resultado lento. Es cierto que en toda consulta con JOINs alguna de las tablas deberá ser la primera en leerse y si no se usan filtros de índice en el WHERE obtendremos un “all”, pero quitando ese caso particular en ningún otra JOIN deberíamos tener algo que no sea “ref” o “eq_ref”. Al fin y al cabo esto es lo que explicábamos más atras: si no se usan índices referenciales para satisfacer las JOINs se realiza el producto cartesiano de las tablas con su consecuente consumo de CPU y memoria.

Detectando las consultas lentas

Por lo demás, cuando nuestras aplicaciones vayan muy lentas y detectemos que es por las consultas a la BD, debemos encontrar las consultas que las ralentizan. MySQL tiene el slow_query_log que una vez habilitado en el servidor almacenará todas las consultas que tarden en ejecutarse más del tiempo que le especifiquemos (un segundo es muy lento, pero medio también en una consulta muy frecuente). Una vez que sepamos las consultas podemos revisar los consejos de arriba para ver si estamos haciendo algo mejorable, o quizá tengamos un mal diseño de la BD. También, en ocasiones, nos podemos encontrar que se nos ha olvidado declarar un índice, y eso tiene una solución muy fácil. Porque decir que la BD, o una tabla, se nos ha quedado poequeña, es algo muy poco probable: mejor revisar el diseño y el uso que le damos porque dándole una vuelta podemos ver fallos en el diseño y mejorar sobremanera el rendimiento.

Si la búsqueda de las consultas lentas se hace muy complicada, siempre podremos buscar algún monitor específico para nuestro SGBD (en esto el administrador de la BD nos puede ayudar mucho: le conviene) pues estas herramientas suelen proporcionar información vital para hallar este tipo de problemas.


Artículo escrito por Eloy Bote Falcón y publicado originalmente en: php-hispano.net

Diseño y normalización de bases de datos relacionales

Diseñando la BD

Para diseñar una base de datos se parte de la recolección de atributos o campos que va a tener, y de la definición de sus tipos de dato. La manera más profesional es realizando el análisis de requisitos con todas las personas que van a hacer uso de los datos. Pero por experiencia ya sabéis que esto se hace muy a ojo: os piden realizar una aplicación y según los requisitos de la aplicación hacéis el diseño de la BD.

El primer método está más estandarizado, y suele ser más lento pero a cambio es improbable que el diseño salga mal. El segundo es más rápido porque directamente se piensa en las tablas y sus datos sobre la marcha. Se utiliza principalmente en la metodología de programación conocida como “programación extrema” y en las demás de la familia “desarrollo ágil de software”; y es más propenso a fallos de diseño, proporcionalmente inversos al tiempo que se dedique a su definición y valoración (más tiempo, menos probabilidad de fallos).

Normalizando la BD

La normalización es un método de análisis de BD para conseguir una BD relacional, que respete la integridad referencial, y que no tenga redundancia de datos. Se divide en formas normales, y aunque hay un montón y es toda una ciencia, explicaré por encima las 3 primeras ya que el nivel 3 es suficiente para la mayoría de casos.

Hay que destacar que la normalización se puede hacer a nivel completo de la BD, o a nivel de tablas o esquemas. La técnica es la misma: analizar el conjunto de campos y en base a eso designar una clave inicial que identifique a un grupo de datos. Por ejemplo si estamos normalizando todo un esquema de facturación podemos partir de los datos del cliente añadiendo la clave del cliente, y según vayamos normalizando nos saldrán todas las tablas y les iremos dando claves primarias nuevas. Si lo que normalizamos es una tabla, el procedimiento es el mismo y ya irán saliendo otras tablas subordinadas si acaso.

Las reglas de Codd

Además de la normalización hay unas reglas, las reglas de Codd, que ayudan a diseñar una BD relacional perfecta (desde el punto de vista de Codd, claro) que merece la pena estudiarlas pues son casi de lógica común y nos harán la vida más fácil en todos los sentidos. La idea de estas reglas surgió porque la normalización no era suficiente para que una BD fuera relacional, consistente e independiente.

Hay ocasiones en las que los diseñadores de las BD confeccionan la BD para satisfacer necesidades lógicas y funcionales de una aplicación, por ejemplo almacenando los datos en un formato que luego la aplicación se encarga de transformar. Esto es bastante típico cuando el diseñador es el programador de la aplicación, y lo hace por comodidad o falta de conocimiento.

La moraleja es que una BD debe ser independiente de la aplicación, y si lo pensáis bien es mejor así. Según las reglas de Codd la BD tiene que ser completamente operativa desde su lenguaje de consultas (típicamente SQL), y las restricciones en los datos deben ser propiedad de la BD (no vale controlar la entrada desde la aplicación). Con esto conseguiremos que mediante el SQL no se puedan realizar operaciones que hagan que la aplicación no funcione (introduciendo datos en un formato inesperado para la aplicación, por ejemplo), y entre otras cosas, que si tenemos que realizar informes puntuales o sacar listados los podremos hacer desde un simple cliente y sin tener que parsear nada ni realizar consultas sobre consultas.

Normalizando la BD: primera forma normal (1FN)

Se podría decir que al aplicarla hay que asegurarse de que:

  • No se permiten vectores de campos en una columnaUn ejemplo de esto es cuando en un campo de texto metemos varios valores del mismo dominio,
    como por ejemplo tres números de teléfono, o dos direcciones e-mail. Lo típico en estos casos
    es separar los datos por comas, espacios u otro carácter y depués procesarlo mediante la aplicación.
    Para evitar esto hay que definir una nueva tabla que tendrá el identificador de la tabla de la que parte y
    el campo multivaluado, haciendo juntos de clave única compuesta (se puede definir otra incremental si se desea,
    pero el conjunto de los otros dos campos tiene que ser único). Además en esta tabla se puede agregar campos
    que ayuden a describir el tipo de registro.

    Ejemplo

    Incorrecto

    clientes

    IDCliente Nombre Telefono
    45 Francisco 444444444
    275 Miguel 555555555,666666666

    Correcto

    clientes

    IDCliente Nombre
    45 Francisco
    275 Miguel

    telefonos_cliente

    IDCliente Telefono
    45 444444444
    275 555555555
    275 666666666
  • No se permiten grupos repetidos en varias columnas
    Esto es una variante de lo anterior: separamos los campos de un mismo dominio en varias columnas, haciendo un grupo difícilmente procesable a la hora de consultarlo. En el ejemplo anterior sería tener el campo telefono1, telefono2… y así. Es evidente que este fallo del diseño es incluso peor que el anterior pues habrá muchos campos nulos, y en caso de necesitar más tendríamos que redimensionar la tabla con un nuevo campo (telefono3). Pero la solución es sencilla: la misma que en el anterior caso.

    Ejemplo

    Incorrecto

    clientes

    IDCliente Nombre Telefono Telefono2 Telefono3
    45 Francisco 444444444 NULL NULL
    275 Miguel 555555555 666666666 NULL

    Correcto

    clientes

    IDCliente Nombre
    45 Francisco
    275 Miguel

    telefonos_cliente

    IDCliente Telefono
    45 444444444
    275 555555555
    275 666666666
  • No se permiten campos nulos
    Esta regla es algo discutible, pero tiene su lógica. Para empezar, si un campo va a tener valores nulos, ¿qué proporción de registros tendrán ese campo con valor nulo? En mi opinión esta regla nos ayuda a separar
    unas entidades de otras, porque si una cantidad de registros tienen unos atributos que otros no, ¿no será que pertenecen a otra clase? Por ejemplo, si en una tabla de productos definimos los campos talla, kilates y potencia se ve que los productos tendrán clases diversas y entonces habrá que crear una entidad para cada clase (ropas, joya y eléctricos, por ejemplo) construyendo lo que se llama una generalización.

    Ejemplo

    Incorrecto

    productos

    IDProducto Nombre Talla Kilates Potencia
    147 Blusa fashion 44 NULL NULL
    155 Broche duquesa NULL 24 NULL
    221 Subwoofer extreme NULL NULL 1500

    Correcto

    productos

    IDProducto Nombre
    147 Blusa fashion
    155 Broche duquesa
    221 Subwoofer extreme

    ropas

    IDProducto Talla
    147 44

    joyas

    IDProducto Kilates
    155 24

    electricos

    IDProducto Potencia
    221 1500

Normalizando la BD: segunda forma normal (2FN)

Una tabla está en segunda forma normal siempre que esté en primera forma normal y todos sus atributos (campos) dependan totalmente de la clave candidate sin ser parte de ella. Viene a ser que, si un campo de la tabla no depende totalmente de una clave única (que pueden ser compuestas), debe sacarse fuera con la parte de la clave principal de la que es dependiente.

Ejemplo

Incorrecto

lineas_pedido

IDCliente IDProducto Cantidad Nombre_producto
29 42 1 Zapatillas deportivas de tenis
46 9 5 Balón reglamentario de baloncesto
204 42 1 Zapatillas deportivas de tenis
144 10 1 Zapatillas deportivas de rugby

Correcto

lineas_pedido

IDCliente IDProducto Cantidad
29 42 1
46 9 5
204 42 1
144 10 1

productos

IDProducto Nombre_producto
9 Balón reglamentario de baloncesto
10 Zapatillas deportivas de rugby
42 Zapatillas deportivas de tenis

Como vemos en la tabla “lineas_pedido” del ejemplo incorrecto, la única clave candidata es IDCliente + IDProducto, ya que en conjunto son únicas en la tabla (podríamos tener un IDLinea_pedido único también, pero aún así esos dos campos segurían siendo una clave candidata). El campo Cantidad es dependiente de la clave candidata, pues el cliente ha pedido de ese producto una cantidad determinada de artículos, pero el nombre en cambio es dependiente sólo del producto, no del cliente. Si dejaramos esa tabla como está, tendríamos por una parte una redundancia de datos innecesaria pues el nombre del producto lo podemos sacar uniendo la tabla de productos, y además podrían darse inconsistencias de datos si cambiamos el nombre del producto en un registro… ¿cuál sería el nombre real del producto 42 si en varios registros tiene un nombre distinto?

Conclusiones

Por lo tanto los pasos para aplicar la segunda forma normal son muy sencillos: encontrar las claves candidatas (compuestas), que identifican de manera única el registro; comprobar que los campos que no forman parte de la clave candidata y no son parte de ella (en el ejemplo de antes ni IDCliente ni IDProducto deben ser analizados) dependen totalmente de la clave candidata. Para el segundo paso puede ayudar preguntarse lo siguiente:
¿puedo saber el valor del campo X sabiendo el valor del campo Y (siendo Y parte de la clave candidata y X no siendo parte de ella)? Pero como todo lo relacionado con el análisis esto requiere un mínimo de agudeza, pues puede que casualmente el valor de un campo se repita para una parte de la clave (por casualidad todos los que compran unas pelotas de tenis lo hacen en cantidades de 5) pero sabemos que no es dependiente de ella.
Por último, aclarar que hay ocasiones en las que el análisis no tiene que ser tan cerrado, ya que a veces las apariencias engañan. Un ejemplo de ello es una tabla de facturas que tiene el nombre, dirección, NIF, y demás datos del cliente: a simple vista esos datos están duplicados y dependen del cliente y no de la factura, pero resulta que esos datos deben permanecer ahí pues fiscalmente debemos saber a qué datos se emitió una factura; esos datos son realmente dependientes de la factura, no del cliente. Si no los incluyéramos en la tabla de facturas, al modificar el registro del cliente en la tabla de clientes no sabríamos a qué datos fiscales se emitió la factura. Así que una vez más, hay que utilizar un poco de ingenio y no aplicar normas como una máquina y sin pensar.

Normalizando la BD: tercera forma normal (3FN)

Una tabla está en tercera forma normal siempre que esté en segunda forma normal (y por consiguiente en primera) y todos sus campos no primarios (campos que no forman parte de una clave candidata) dependen únicamente de la clave candidata. Suena como la segunda forma normal, pero es muy distinta: ningún campo que no sea parte de la clave candidata puede depender
de otro campo que no sea la clave candidata
.

Ejemplo

Incorrecto

carga_diaria

IDServidor Fecha IDServicio Nombre_servicio Carga
21 2009-01-14 1 Oracle 100
21 2009-01-15 9 MySQL 100
21 2009-01-16 22 Apache 85
34 2009-01-14 3 PostgreSQL 74
34 2009-01-15 22 Apache 58
34 2009-01-16 22 Apache 67
66 2009-01-14 9 MySQL 98
66 2009-01-15 22 Apache 94
66 2009-01-16 1 Oracle 10g 84

Correcto

carga_diaria

IDServidor Fecha IDServicio Carga
21 2009-01-14 1 100
21 2009-01-15 9 100
21 2009-01-16 22 85
34 2009-01-14 3 74
34 2009-01-15 22 58
34 2009-01-16 22 67
66 2009-01-14 9 98
66 2009-01-15 22 94
66 2009-01-16 1 84

servicios

IDServicio Nombre_servicio
1 Oracle
9 MySQL
22 Apache
3 PostgreSQL
22 Apache
22 Apache
9 MySQL
22 Apache
1 Oracle 10g

Imaginad que una tabla se encarga de registrar el primer servicio que más carga los servidores cada día. Del ejemplo incorrecto deducimos que el IDServidor y la Fecha son la clave candidata, pues identifican de manera única los registros. Analizando vemos que el IDServicio, que no es un campo primario, depende únicamente de la clave candidata, y que la carga también. Pero resulta que el Nombre_servicio depende de esa clave candidata pero también depende del IDServicio, pues con el IDServicio podemos averiguar qué Nombre_servicio tiene el registro. Para solucionar esto sacamos el campo Nombre_servicio de la tabla, y nos llevamos el IDServicio para que sea la clave principal pues es el campo del que depende.

Y con este ejemplo vemos qué fácil es librarnos de las inconsistencias de no cumplir la tercera forma normal, y de la redundancia de datos. Si no hubieramos normalizado tendríamos que en un registro el IDServicio 22 es Apache y nadie nos asegura que en otro el IDServicio 22 también lo sea pues puede haberse modificado el campo Nombre_servicio. Y si resulta que la tabla fuese un histórico de 500 servidores durante 1000 días, tendríamos 500 mil registros con un campo innecesario que estaría duplicado muchísimas veces.

Diseñando la BD sobre la marcha

Si en vuestro desempeño habitual del trabajo os encontráis con que no podéis aplicar, de una manera formal y detallada, la normalización a la hora de diseñar BD, no os alarméis pues le pasa a mucha gente. Lo que puede ocurrir es que nos quede una BD no relacional, y eso es siempre negativo, pero a base de experiencia iréis adquiriendo una soltura y capacidad analítica automáticas.

La normalización, al basarse en reglas lógicas, se puede memorizar muy fácilmente y al final forma parte del instinto del diseñador: no necesitaréis bolígrafo y papel para ver que una tabla no está normalizada. De hecho cuando sepáis que datos necesita la aplicación pensaréis directamente en las tablas que saldrán.

Primero, documentarse

Lo más importante para diseñar la BD sobre la marcha es tener la mente amplia, conocer las bases de la normalización, y dejarse aconsejar por los expertos. Todo esto de las BD relacionales no es nuevo y hay muchos gurús (Codd, Edgar Frank Codd, es el padre de todos) que os pueden ayudar a entender qué características debe cumplir una BD para ser relacional. De modo que si no sabéis del tema, lo mejor es que os olvidéis de las malas enseñanazas que tengáis imbuidas: una BD con muchas tablas no está mal diseñada (una con pocas es más probable que sí lo esté); llevarse el código del cliente a todas las tablas (lo necesiten o no) no es la forma de tener una BD relacional; guardar los datos serializados en un campo no te hace la vida más fácil; tener un campo que hace referencia a una tabla unas veces y a otra en otras ocasiones no es un buen diseño (un campo referencial sólo puede referenciar a una tabla, así que usad la generalización en esas situaciones).

Errores habituales

Un error habitual a la hora de usar este método de diseño es tener un campo referencial que admite valores nulos o vacíos (típico clave_referencia 0 cuando no referencia a nada). Si la BD es relacional, un campo referencial tiene que apuntar a otro registro en la BD. A veces tenemos un campo IDPadre que hace referencia a un mismo registro de la tabla, o vale 0 cuando el registro es padre en sí… pero lo correcto en una relación reflexiva (una tabla relacionada consigo misma) que da lugar a otro tabla que contiene el IDHijo y el IDPadre; consultando esa tabla podemos saber si un registro es hijo (tiene entradas con su ID en IDHijo) o padre (su ID está en algún IDPadre) y sacar todos los hijos de un padre.

Consejos

Otro buen consejo, que no está limitado a este método de diseñar, es tener en cuenta el tamaño de las tablas en cuanto a longitud de fila (en bytes). De hecho recuerdo que me hablaron de una regla de diseño de BD que decía que una tabla no debía contener más de X campos… y bueno, siempre es discutible pero es más óptimo que la longitud de la fila no sea muy larga, sobre todo en las tablas que se consultan con frecuencia. Es una práctica muy recomendada que si tenemos campos grandes, tipo TEXT o BLOB, saquemos esos datos a otra tabla para que las búsquedas y JOIN generales que no necesiten ese campo sean más rápidas. Hay que tener en cuenta que el sistema de gestión de BD (SGBD) en ocasiones no puede optimizar las consultas y necesita escanear por completo la tabla, o tiene que volcarla a la memoria física o virtual.

En definitiva: recordad que una BD relacional no puede ser dependiente de la aplicación, sino al revés. Así que olvidaros de diseñarla pensando qué es lo mejor para la aplicación, y pensad qué es lo correcto para que sea más óptima y sencilla de manejar independientemente del cliente que la maneje (aplicación, consultas directas, herramientas de informe…).


Artículo escrito por Eloy Bote Falcón y publicado originalmente en: php-hispano.net

EIGRP: Balanceando carga

Objetivo del laboratorio:

  • Revisión de configuración básica de EIGRP.
  • Explorar la tabla de topología de EIGRP.
  • Aprender a identificar successors, feasible successors y feasible distances.
  • Aprender a usar los comandos DEBUG con la tabla de topología de EIGRP.
  • Configurar y verificar el balanceo de carga con igual coste con EIGRP.
  • Configurar y verificar el balanceo de carga con diferente coste con EIGRP.

Requisitos:

  • GNS3 (versión usada 0.5)
  • IOS: versión mínima: 12.3(2)T (versión usada 12.4-21)
  • Ficheros del laboratorio (descargar zip con el laboratorio completo)
    NOTA 1: se recomienda hacer el laboratorio desde cero, en vez de copiar y pegar, para aprender.
    NOTA 2: los path del fichero .net se deben ajustar a los de tu ordenador.

Esquema:
EIGRP: Balanceando carga

Paso 1: configurar las interfaces Loopback y las interfaces Serie.
Primero configuramos las interfaces Loopback de los 3 routers:

R1:
interface Loopback1
 ip address 10.10.10.1 255.255.255.252
!
interface Loopback5
 ip address 10.10.10.5 255.255.255.252
!
interface Loopback9
 ip address 10.10.10.9 255.255.255.252

R2:
interface Loopback1
 ip address 10.10.20.1 255.255.255.252
!
interface Loopback5
 ip address 10.10.20.5 255.255.255.252
!
interface Loopback9
 ip address 10.10.20.9 255.255.255.252

R3:
interface Loopback1
 ip address 10.10.30.1 255.255.255.252
!
interface Loopback5
 ip address 10.10.30.5 255.255.255.252
!
interface Loopback9
 ip address 10.10.30.9 255.255.255.252

Segundo configuramos las interfaces serie configurando el reloj a 64 kbps y el ancho de banda de las interfaces también a 64 kbps. Añadimos una descripción a las interfaces indicando que router une dicha interface. Para comprobar el estado de las interfaces en cada router podemos usar el comando “show ip interface bri“.

R1:
interface Serial1/2
 description R1-R2
 bandwidth 64
 ip address 10.10.12.1 255.255.255.248
 clock rate 64000
 no shutdown
!
interface Serial1/3
 description R1-R3
 bandwidth 64
 ip address 10.10.13.1 255.255.255.248
 no shutdown

R2:
interface Serial1/1
 description R2-R1
 bandwidth 64
 ip address 10.10.12.2 255.255.255.248
 no shutdown
!
interface Serial1/3
 description R2-R3
 bandwidth 64
 ip address 10.10.23.2 255.255.255.248
 clock rate 64000
 no shutdown

R3:
interface Serial1/1
 description R3-R1
 bandwidth 64
 ip address 10.10.13.3 255.255.255.248
 clock rate 64000
 no shutdown
!
interface Serial1/2
 description R3-R2
 bandwidth 64
 ip address 10.10.23.3 255.255.255.248
 no shutdown

Y tercero comprobamos con un script TCLSH (ver artículo) que hacemos ping a todas las IPs de todos los routers desde todos los routers. El script TCLSH es el siguiente (se omite la salida para evitar que se extienda el artículo):

foreach ips {
10.10.10.1
10.10.10.5
10.10.10.9
10.10.20.1
10.10.20.5
10.10.20.9
10.10.30.1
10.10.30.5
10.10.30.9
10.10.12.1
10.10.13.1
10.10.12.2
10.10.23.2
10.10.23.3
10.10.13.3
} {
ping $ips
}

Evidentemente como no hay ningún protocolo de rutado configurado no se alcanzan todas las IPs, cada router verá las suyas (Loopbacks y serie) y las serie de sus vecinos directos.

Paso 2: Configuración de EIGRP.
Configuramos EIGRP con sistema autónomo 100 como ya hemos visto en otros laboratorios:

R1,R2 y R3:
router eigrp 100
network 10.0.0.0

Ahora podemos comprobar que está todo correcto con diversos comandos, como por ejemplo mirar los vecinos de cada router:

R3#show ip eigrp neighbors 100
IP-EIGRP neighbors for process 100
H   Address                 Interface       Hold Uptime   SRTT   RTO  Q  Seq
                                            (sec)         (ms)       Cnt Num
1   10.10.13.1              Se1/1             10 00:06:17   28  2280  0  9
0   10.10.23.2              Se1/2             14 00:06:17   31  2280  0  9

También podemos comprobar que obtenemos todas las redes configuradas en todos los routers:

R3#show ip route eigrp 100
     10.0.0.0/8 is variably subnetted, 12 subnets, 2 masks
D       10.10.10.8/30 [90/40640000] via 10.10.13.1, 00:07:33, Serial1/1
D       10.10.10.0/30 [90/40640000] via 10.10.13.1, 00:07:33, Serial1/1
D       10.10.12.0/29 [90/41024000] via 10.10.23.2, 00:07:33, Serial1/2
                      [90/41024000] via 10.10.13.1, 00:07:33, Serial1/1
D       10.10.10.4/30 [90/40640000] via 10.10.13.1, 00:07:33, Serial1/1
D       10.10.20.4/30 [90/40640000] via 10.10.23.2, 00:07:33, Serial1/2
D       10.10.20.0/30 [90/40640000] via 10.10.23.2, 00:07:33, Serial1/2
D       10.10.20.8/30 [90/40640000] via 10.10.23.2, 00:07:33, Serial1/2

También podemos usar el script TCLSH que antes no veía todas las IPs y comprobar que ahora si las vé.
Y por último podemos activar el debug para ver todo el proceso de EIGRP (es interesante activar el debug en todos los router antes de configurar EIGRP para ver todo el proceso). El comando a usar para activar el debug es “debug ip eigrp 100” (se omite la salida por que es demasiado larga) y para desactivarlo “undebug all“.

Paso 3: Tabla de topología de EIGRP.
EIGRP construye una tabla de topología donde mantiene las rutas de todos los sucesores. Para ver la tabla de topología usamos el comando:

R2#show ip eigrp topology
IP-EIGRP Topology Table for AS(100)/ID(10.10.20.9)

Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply,
       r - reply Status, s - sia Status 

P 10.10.10.8/30, 1 successors, FD is 40640000
        via 10.10.12.1 (40640000/128256), Serial1/1
P 10.10.10.0/30, 1 successors, FD is 40640000
        via 10.10.12.1 (40640000/128256), Serial1/1
P 10.10.12.0/29, 1 successors, FD is 40512000
        via Connected, Serial1/1
P 10.10.13.0/29, 2 successors, FD is 41024000
        via 10.10.12.1 (41024000/40512000), Serial1/1
        via 10.10.23.3 (41024000/40512000), Serial1/3
P 10.10.10.4/30, 1 successors, FD is 40640000
        via 10.10.12.1 (40640000/128256), Serial1/1
P 10.10.20.4/30, 1 successors, FD is 128256
        via Connected, Loopback5
P 10.10.20.0/30, 1 successors, FD is 128256
        via Connected, Loopback1
P 10.10.30.8/30, 1 successors, FD is 40640000
        via 10.10.23.3 (40640000/128256), Serial1/3
P 10.10.23.0/29, 1 successors, FD is 40512000
        via Connected, Serial1/3
P 10.10.30.4/30, 1 successors, FD is 40640000
        via 10.10.23.3 (40640000/128256), Serial1/3
P 10.10.20.8/30, 1 successors, FD is 128256
        via Connected, Loopback9
P 10.10.30.0/30, 1 successors, FD is 40640000
        via 10.10.23.3 (40640000/128256), Serial1/3

Aquí podemos ver Feasible Distance (FD) de cada ruta y un punto importante son los 2 sucesores (marcados en negrita) de la ruta 10.10.13.0/29 que es anunciada tanto por R1 como R3 con la misma distancia (41024000) por lo que ambos sucesores están en la tabla de topología.
Para ver la información que EIGRP ha recibido sobre la red 10.10.13.0/29 de los routers R1 y R3 podemos usar el comando:

R2#show ip eigrp topology 10.10.13.0/29
IP-EIGRP (AS 100): Topology entry for 10.10.13.0/29
  State is Passive, Query origin flag is 1, 2 Successor(s), FD is 41024000
  Routing Descriptor Blocks:
  10.10.12.1 (Serial1/1), from 10.10.12.1, Send flag is 0x0
      Composite metric is (41024000/40512000), Route is Internal
      Vector metric:
        Minimum bandwidth is 64 Kbit
        Total delay is 40000 microseconds
        Reliability is 255/255
        Load is 1/255
        Minimum MTU is 1500
        Hop count is 1
  10.10.23.3 (Serial1/3), from 10.10.23.3, Send flag is 0x0
      Composite metric is (41024000/40512000), Route is Internal
      Vector metric:
        Minimum bandwidth is 64 Kbit
        Total delay is 40000 microseconds
        Reliability is 255/255
        Load is 3/255
        Minimum MTU is 1500
        Hop count is 1

Aquí vemos la métrica compuesta además de los valores individuales que a continuación se explica:

  • Bandwidth, representa el mínimo ancho de banda sobre el camino a la red de destino.
  • Delay metric, representa la espera total sobre el camino.
  • MTU, representa el mínimo de la unidad de trasmisión máxima sobre el camino.
  • The hop count, muestra el número de saltos o dispositivos que hay entre el router y la red de destino.

Paso 4: Balanceo de carga con mismo coste.
EIGRP produce balanceo de carga con mismo coste a la red de destino (en el caso anterior 10.10.12.1 desde R3) pero como desde que las últimas versiones de IOS viene activado por defecto CEF (Cisco Express Forwarding) que permite conmutación rápida de paquetes basado en arquitectura de conmutado por destino, El primer paquete en un flujo es enrutado y el resto son conmutados. Este comportamiento es el preferido en la mayoría de circunstancias. Pero en este caso desde R3 no vemos el balanceo de carga ya que CEF trata las 5 series de un ping como un solo flujo. por lo que si hacemos un ping desde R3 a 10.10.12.1 veremos que siempre va por el mismo camino, para ello activaremos “debug ip packet” antes del ping:

*Mar  1 00:00:55.767: IP: tableid=0, s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), routed via FIB
*Mar  1 00:00:55.767: IP: s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), len 100, sending
*Mar  1 00:00:55.771: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.23.3 (Serial1/2), routed via RIB
*Mar  1 00:00:55.771: IP: s=10.10.12.1 (Serial1/1), d=10.10.23.3, len 100, rcvd 4
*Mar  1 00:00:55.771: IP: tableid=0, s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), routed via FIB
*Mar  1 00:00:55.771: IP: s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), len 100, sending
*Mar  1 00:00:55.779: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.23.3 (Serial1/2), routed via RIB
*Mar  1 00:00:55.779: IP: s=10.10.12.1 (Serial1/1), d=10.10.23.3, len 100, rcvd 4
*Mar  1 00:00:55.779: IP: tableid=0, s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), routed via FIB
*Mar  1 00:00:55.779: IP: s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), len 100, sending
*Mar  1 00:00:55.787: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.23.3 (Serial1/2), routed via RIB
*Mar  1 00:00:55.787: IP: s=10.10.12.1 (Serial1/1), d=10.10.23.3, len 100, rcvd 4
*Mar  1 00:00:55.787: IP: tableid=0, s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), routed via FIB
*Mar  1 00:00:55.787: IP: s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), len 100, sending
*Mar  1 00:00:55.791: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.23.3 (Serial1/2), routed via RIB
*Mar  1 00:00:55.791: IP: s=10.10.12.1 (Serial1/1), d=10.10.23.3, len 100, rcvd 4
*Mar  1 00:00:55.791: IP: tableid=0, s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), routed via FIB
*Mar  1 00:00:55.791: IP: s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), len 100, sending
*Mar  1 00:00:55.799: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.23.3 (Serial1/2), routed via RIB
*Mar  1 00:00:55.799: IP: s=10.10.12.1 (Serial1/1), d=10.10.23.3, len 100, rcvd 4

Ahora primero desactivaremos CEF (para ver que realmente hay balanceo de carga pero en un entorno de producción lo dejaremos activado) con el comando “no ip cef” en modo configuración y repetiremos el ping (con el debug aún activado):

*Mar  1 00:04:59.103: IP: tableid=0, s=10.10.13.3 (local), d=10.10.12.1 (Serial1/1), routed via RIB
*Mar  1 00:04:59.107: IP: s=10.10.13.3 (local), d=10.10.12.1 (Serial1/1), len 100, sending
*Mar  1 00:04:59.111: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.13.3 (Serial1/1), routed via RIB
*Mar  1 00:04:59.111: IP: s=10.10.12.1 (Serial1/1), d=10.10.13.3 (Serial1/1), len 100, rcvd 3
*Mar  1 00:04:59.111: IP: tableid=0, s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), routed via RIB
*Mar  1 00:04:59.111: IP: s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), len 100, sending
*Mar  1 00:04:59.119: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.23.3 (Serial1/2), routed via RIB
*Mar  1 00:04:59.119: IP: s=10.10.12.1 (Serial1/1), d=10.10.23.3, len 100, rcvd 4
*Mar  1 00:04:59.119: IP: tableid=0, s=10.10.13.3 (local), d=10.10.12.1 (Serial1/1), routed via RIB
*Mar  1 00:04:59.119: IP: s=10.10.13.3 (local), d=10.10.12.1 (Serial1/1), len 100, sending
*Mar  1 00:04:59.123: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.13.3 (Serial1/1), routed via RIB
*Mar  1 00:04:59.123: IP: s=10.10.12.1 (Serial1/1), d=10.10.13.3 (Serial1/1), len 100, rcvd 3
*Mar  1 00:04:59.123: IP: tableid=0, s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), routed via RIB
*Mar  1 00:04:59.123: IP: s=10.10.23.3 (local), d=10.10.12.1 (Serial1/2), len 100, sending
*Mar  1 00:04:59.127: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.23.3 (Serial1/2), routed via RIB
*Mar  1 00:04:59.131: IP: s=10.10.12.1 (Serial1/1), d=10.10.23.3, len 100, rcvd 4
*Mar  1 00:04:59.131: IP: tableid=0, s=10.10.13.3 (local), d=10.10.12.1 (Serial1/1), routed via RIB
*Mar  1 00:04:59.131: IP: s=10.10.13.3 (local), d=10.10.12.1 (Serial1/1), len 100, sending
*Mar  1 00:04:59.135: IP: tableid=0, s=10.10.12.1 (Serial1/1), d=10.10.13.3 (Serial1/1), routed via RIB
*Mar  1 00:04:59.135: IP: s=10.10.12.1 (Serial1/1), d=10.10.13.3 (Serial1/1), len 100, rcvd 3

Aquí ya vemos como si hay balanceo de carga e intercala los 2 posibles caminos.

Paso 5: Caminos alternativos de EIGRP que NO están en la tabla de topología.
Antes en el paso 3 hemos visto la tabla de topología peor si nos fijamos bien no están TODOS los posible caminos, para verlos todos debemos añadir all-links al final:

R2#sh ip eigrp topology  all-links
IP-EIGRP Topology Table for AS(100)/ID(10.10.20.9)

Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply,
       r - reply Status, s - sia Status 

P 10.10.10.8/30, 1 successors, FD is 40640000, serno 9
        via 10.10.12.1 (40640000/128256), Serial1/1
        via 10.10.23.3 (41152000/40640000), Serial1/3
P 10.10.10.0/30, 1 successors, FD is 40640000, serno 7
        via 10.10.12.1 (40640000/128256), Serial1/1
        via 10.10.23.3 (41152000/40640000), Serial1/3
P 10.10.12.0/29, 1 successors, FD is 40512000, serno 1
        via Connected, Serial1/1
P 10.10.13.0/29, 2 successors, FD is 41024000, serno 10
        via 10.10.12.1 (41024000/40512000), Serial1/1
        via 10.10.23.3 (41024000/40512000), Serial1/3
P 10.10.10.4/30, 1 successors, FD is 40640000, serno 8
        via 10.10.12.1 (40640000/128256), Serial1/1
        via 10.10.23.3 (41152000/40640000), Serial1/3
P 10.10.20.4/30, 1 successors, FD is 128256, serno 4
        via Connected, Loopback5
P 10.10.20.0/30, 1 successors, FD is 128256, serno 3
        via Connected, Loopback1
P 10.10.30.8/30, 1 successors, FD is 40640000, serno 13
        via 10.10.23.3 (40640000/128256), Serial1/3
        via 10.10.12.1 (41152000/40640000), Serial1/1
P 10.10.23.0/29, 1 successors, FD is 40512000, serno 2
        via Connected, Serial1/3
P 10.10.30.4/30, 1 successors, FD is 40640000, serno 12
        via 10.10.23.3 (40640000/128256), Serial1/3
        via 10.10.12.1 (41152000/40640000), Serial1/1
P 10.10.20.8/30, 1 successors, FD is 128256, serno 5
        via Connected, Loopback9
P 10.10.30.0/30, 1 successors, FD is 40640000, serno 11
        via 10.10.23.3 (40640000/128256), Serial1/3
        via 10.10.12.1 (41152000/40640000), Serial1/1

Paso 6: Balanceo de carga con diferente coste.
Retomando la salida (reusmida) de la tabla de topología del paso 3, tenemos que:

R2#show ip eigrp topology
...
P 10.10.30.0/30, 1 successors, FD is 40640000
        via 10.10.23.3 (40640000/128256), Serial1/3
...

R2#sh ip eigrp topology 10.10.30.0/30
IP-EIGRP (AS 100): Topology entry for 10.10.30.0/30
  State is Passive, Query origin flag is 1, 1 Successor(s), FD is 40640000
  Routing Descriptor Blocks:
  10.10.23.3 (Serial1/3), from 10.10.23.3, Send flag is 0x0
      Composite metric is (40640000/128256), Route is Internal
      Vector metric:
        Minimum bandwidth is 64 Kbit
        Total delay is 25000 microseconds
        Reliability is 255/255
        Load is 1/255
        Minimum MTU is 1500
        Hop count is 1
  10.10.12.1 (Serial1/1), from 10.10.12.1, Send flag is 0x0
      Composite metric is (41152000/40640000), Route is Internal
      Vector metric:
        Minimum bandwidth is 64 Kbit
        Total delay is 45000 microseconds
        Reliability is 255/255
        Load is 1/255
        Minimum MTU is 1500
        Hop count is 2

Vemos que 10.10.12.1 no es es ruta sucesora debido a que feasible distance (FD) es superior.

EIGRP: Configuración, Ancho de banda y Adyacencias

Objetivo del laboratorio:

  • Configuración de EIGRP en una interface.
  • Configurar el ancho de banda (bandwidth) para limitar el ancho de banda de EIGRP
  • Verificar adyacencias EIGRP
  • Verificar intercambio de información de rutado de EIGRP
  • Utilizar comandos DEBUG para resolver problemas EIGRP
  • Comprobación de convergencia por cambio de topología.

Requisitos:

  • GNS3 (versión usada 0.5)
  • IOS: versión mínima: 12.3(2)T (versión usada 12.4-21)
  • Ficheros del laboratorio (descargar zip con el laboratorio completo)
    NOTA 1: se recomienda hacer el laboratorio desde cero en vez de copiar y pegar para aprender.
    NOTA 2: los path del fichero .net se deben ajustar a los de tu ordenador.

Esquema:
EIGRP: Configuración, Ancho de banda y Adyacencias
Paso 1: configurar las IPs de las interfaces FastEthernet y las Loopbacks según el esquema, de momento dejamos las 2 interfaces serials. Nota: es conveniente teclear los comandos y no hacer copiar y pegar para practicar.

R1:
hostname R1
!
interface Loopback1
 ip address 10.10.10.1 255.255.255.0
!
interface FastEthernet0/0
 ip address 10.1.1.1 255.255.255.0
 speed 100
 full-duplex
 no shutdown

R2:
hostname R2
!
interface Loopback1
 ip address 10.10.20.1 255.255.255.0
!
interface FastEthernet0/0
 ip address 10.1.1.2 255.255.255.0
 speed 100
 full-duplex
 no shutdown

R3:
hostname R3
!
interface Loopback1
 ip address 10.10.30.1 255.255.255.0
!
interface Loopback2
 ip address 192.168.1.1 255.255.255.252
!
interface Loopback3
 ip address 192.168.2.1 255.255.255.252
!
interface FastEthernet0/0
 ip address 10.1.1.3 255.255.255.0
 speed 100
 full-duplex
 no shutdown

SWITCH:
hostname SWITCH
!
interface range fastEthernet 1/0 - 15
 shutdown
!
interface FastEthernet1/1
 duplex full
 speed 100
 no shutdown
!
interface FastEthernet1/2
 duplex full
 speed 100
 no shutdown
!
interface FastEthernet1/3
 duplex full
 speed 100
 no shutdown

El dispositivo SWITCH realmente es un Cisco 3725 con un módulo de 16 puerto fastethernet que hace las veces de switch. Lo que hacemos es primero poner los 16 puerto en shutdown y luego con figurar los 3 puertos que se necesitan, como por defecto están en la VLAN 1 no hay que hace nada más.
El resto de routers los configuramos tal cual.
Para comprobar que están las interfaces bien configuradas y levantadas podemos usar el comando “show ip interface brief” en cada router, que nos da información de los interfaces. Salida de ejemplo en R2:

R2#show ip interface brief
Interface                  IP-Address      OK? Method Status                Protocol
FastEthernet0/0            10.1.1.2        YES manual up                    up
FastEthernet0/1            unassigned      YES NVRAM  administratively down down
Serial1/0                  unassigned      YES NVRAM  administratively down down
Serial1/1                  unassigned      YES NVRAM  administratively down down
Serial1/2                  unassigned      YES NVRAM  administratively down down
Serial1/3                  unassigned      YES NVRAM  administratively down down
Loopback1                  10.10.20.1      YES manual up                    up

Paso 2: configurar EIGRP a través de la VLAN 1.
Para ver todo el proceso antes de configurar EIGRP vamos a activar el debug para ver que paquetes se envían. Primero configuramos R1:

R1#debug eigrp packets
EIGRP Packets debugging is on
    (UPDATE, REQUEST, QUERY, REPLY, HELLO, IPXSAP, PROBE, ACK, STUB, SIAQUERY, SIAREPLY)
R1#conf t
R1(config)#router eigrp 100
R1(config-router)#network 10.0.0.0

Al configurar las redes con redes grandes causa que EIGRP empiece a enviar paquete Hello por todas las interfaces que están en dicha red (subredes de 10.0.0.0/8), en nuestro caso EIGRP empezará a enviar paquetes Hello por FastEthernet0/0 y por Loopback1 como se puede ver en el debug:

*Mar  1 00:58:52.767: EIGRP: Sending HELLO on FastEthernet0/0
*Mar  1 00:58:52.771:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0 iidbQ un/rely 0/0
*Mar  1 00:58:52.771: EIGRP: Sending HELLO on Loopback1
*Mar  1 00:58:52.771:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0 iidbQ un/rely 0/0
*Mar  1 00:58:52.775: EIGRP: Received HELLO on Loopback1 nbr 10.10.10.1
*Mar  1 00:58:52.775:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0
*Mar  1 00:58:52.775: EIGRP: Packet from ourselves ignored

Como todavía con hay más routers configurados no obtenemos respuesta y los paquetes Hello que llegan desde la interfaz Loopback1 son ignorados por venir del propio router.
Configuramos R2 de la misma forma y veremos como ya no solo se envían paquetes Hello si no que también se reciben:

*Mar  1 01:08:05.923: EIGRP: Sending HELLO on FastEthernet0/0
*Mar  1 01:08:05.923:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0 iidbQ un/rely 0/0
*Mar  1 01:08:05.927: EIGRP: Sending HELLO on Loopback1
*Mar  1 01:08:05.927:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0 iidbQ un/rely 0/0
*Mar  1 01:08:05.931: EIGRP: Received HELLO on Loopback1 nbr 10.10.20.1
*Mar  1 01:08:05.935:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0
*Mar  1 01:08:05.935: EIGRP: Packet from ourselves ignored
*Mar  1 01:08:05.959: EIGRP: Received HELLO on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:05.963:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0
*Mar  1 01:08:05.963: %DUAL-5-NBRCHANGE: IP-EIGRP(0) 100: Neighbor 10.1.1.1 (FastEthernet0/0) is up: new adjacency
*Mar  1 01:08:05.967: EIGRP: Enqueueing UPDATE on FastEthernet0/0 nbr 10.1.1.1 iidbQ un/rely 0/1 peerQ un/rely 0/0
*Mar  1 01:08:05.967: EIGRP: Received HELLO on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:05.967:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0 iidbQ un/rely 0/1 peerQ un/rely 0/0
*Mar  1 01:08:05.971: EIGRP: Received Sequence TLV from 10.1.1.1
*Mar  1 01:08:05.971:        10.1.1.2
*Mar  1 01:08:05.971:        address matched
*Mar  1 01:08:05.971:        clearing CR-mode
*Mar  1 01:08:05.975: EIGRP: Received CR sequence TLV from 10.1.1.1, sequence 2
*Mar  1 01:08:05.975: EIGRP: Received UPDATE on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:05.975:   AS 100, Flags 0xA, Seq 2/0 idbQ 0/0 iidbQ un/rely 0/1 peerQ un/rely 0/0, not in CR-mode, packet discarded
*Mar  1 01:08:05.979: EIGRP: Requeued unicast on FastEthernet0/0
*Mar  1 01:08:05.979: EIGRP: Enqueueing UPDATE on FastEthernet0/0 iidbQ un/rely 0/1 serno 1-2
*Mar  1 01:08:05.983: EIGRP: Sending HELLO on FastEthernet0/0
*Mar  1 01:08:05.983:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0 iidbQ un/rely 0/1
*Mar  1 01:08:05.987: EIGRP: Forcing multicast xmit on FastEthernet0/0
*Mar  1 01:08:05.987: EIGRP: Building Sequence TLV
*Mar  1 01:08:05.987: EIGRP: Enqueueing UPDATE on FastEthernet0/0 nbr 10.1.1.1 iidbQ un/rely 0/0 peerQ un/rely 0/1 serno 1-2
*Mar  1 01:08:05.991: EIGRP: Sending HELLO with Sequence TLV on FastEthernet0/0, seq 2
*Mar  1 01:08:05.991: EIGRP: Sending HELLO on FastEthernet0/0
*Mar  1 01:08:05.991:   AS 100, Flags 0x0, Seq 0/0 idbQ 0/0 iidbQ un/rely 0/0
*Mar  1 01:08:05.995: EIGRP: Sending UPDATE on FastEthernet0/0
*Mar  1 01:08:05.995:   AS 100, Flags 0xA, Seq 2/0 idbQ 0/0 iidbQ un/rely 0/0 serno 1-2
*Mar  1 01:08:05.999: EIGRP: Sending UPDATE on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:05.999:   AS 100, Flags 0x1, Seq 1/0 idbQ 0/0 iidbQ un/rely 0/0 peerQ un/rely 0/2
*Mar  1 01:08:07.967: EIGRP: Received UPDATE on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:07.967:   AS 100, Flags 0x1, Seq 1/1 idbQ 0/0 iidbQ un/rely 0/0 peerQ un/rely 0/2
*Mar  1 01:08:07.967: EIGRP: Sending UPDATE on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:07.967:   AS 100, Flags 0x8, Seq 2/0 idbQ 0/0 iidbQ un/rely 0/1 peerQ un/rely 0/1 serno 1-2
*Mar  1 01:08:07.967: EIGRP: Enqueueing ACK on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:07.967:   Ack seq 1 iidbQ un/rely 0/1 peerQ un/rely 1/1
*Mar  1 01:08:07.971: EIGRP: Sending ACK on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:07.971:   AS 100, Flags 0x0, Seq 0/1 idbQ 0/0 iidbQ un/rely 0/1 peerQ un/rely 1/1
*Mar  1 01:08:07.975: EIGRP: Requeued unicast on FastEthernet0/0
*Mar  1 01:08:07.987: EIGRP: Received UPDATE on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:07.991:   AS 100, Flags 0x8, Seq 2/2 idbQ 0/0 iidbQ un/rely 0/0 peerQ un/rely 0/2
*Mar  1 01:08:07.991: EIGRP: FastEthernet0/0 multicast flow blocking cleared
*Mar  1 01:08:07.995: EIGRP: Sending UPDATE on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:07.995:   AS 100, Flags 0x8, Seq 3/1 idbQ 0/0 iidbQ un/rely 0/0 peerQ un/rely 0/1 serno 1-2
*Mar  1 01:08:07.995: EIGRP: Enqueueing ACK on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:07.999:   Ack seq 2 iidbQ un/rely 0/0 peerQ un/rely 1/1
*Mar  1 01:08:08.003: EIGRP: Sending ACK on FastEthernet0/0 nbr 10.1.1.1
*Mar  1 01:08:08.003:   AS 100, Flags 0x0, Seq 0/2 idbQ 0/0 iidbQ un/rely 0/0 peerQ un/rely 1/1

Podemos ver en el debug los paquetes de EIGRP Hello, Update y ACK (marcados en negrita). También vemos que se responde con paquetes ACK a las actualizaciones ya que EIGRP usar el protocolo RTP (Reliable Transport Protocol).
Por último configuramos R3 igual que R1 y R2.
Para quitar la salida del debug utilizamos el comando:

undebug all

Para ver que interfaces están involucradas en el proceso de rutado de EIGRP podemos usar el comando:

R3#show ip eigrp interfaces
IP-EIGRP interfaces for process 100

                        Xmit Queue   Mean   Pacing Time   Multicast    Pending
Interface        Peers  Un/Reliable  SRTT   Un/Reliable   Flow Timer   Routes
Fa0/0              2        0/0       823       0/1         4092           0
Lo1                0        0/0         0       0/1            0           0

Paso 3: Verificar la configuración de EIGRP
Una vez que tengamos los 3 routers configurados con EIGRP podemos ver que adyacencias hay en cada uno (2 en cada router) con el comando “show ip eigrp neighbors“.

R1#show ip eigrp neighbors
IP-EIGRP neighbors for process 100
H   Address                 Interface       Hold Uptime   SRTT   RTO  Q  Seq
                                            (sec)         (ms)       Cnt Num
1   10.1.1.3                Fa0/0             13 00:12:18   13   200  0  7
0   10.1.1.2                Fa0/0             10 00:22:26   47   282  0  6

R2#show ip eigrp neighbors
IP-EIGRP neighbors for process 100
H   Address                 Interface       Hold Uptime   SRTT   RTO  Q  Seq
                                            (sec)         (ms)       Cnt Num
1   10.1.1.3                Fa0/0             13 00:12:22   36   216  0  7
0   10.1.1.1                Fa0/0             13 00:22:31 1020  5000  0  6

R3#show ip eigrp neighbors
IP-EIGRP neighbors for process 100
H   Address                 Interface       Hold Uptime   SRTT   RTO  Q  Seq
                                            (sec)         (ms)       Cnt Num
1   10.1.1.2                Fa0/0             11 00:12:26  824  4944  0  7
0   10.1.1.1                Fa0/0             14 00:12:26  822  4932  0  7

Ahora vamos a comprobar si las rutas EIGRP se intercambian entre los routers correctamente usando el comando “show ip eigrp topology“:

R3#show ip eigrp topology
IP-EIGRP Topology Table for AS(100)/ID(192.168.2.1)

Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply,
       r - reply Status, s - sia Status 

P 10.10.10.0/24, 1 successors, FD is 156160
        via 10.1.1.1 (156160/128256), FastEthernet0/0
P 10.1.1.0/24, 1 successors, FD is 28160
        via Connected, FastEthernet0/0
P 10.10.20.0/24, 1 successors, FD is 156160
        via 10.1.1.2 (156160/128256), FastEthernet0/0
P 10.10.30.0/24, 1 successors, FD is 128256
        via Connected, Loopback1

Debemos ver todas las redes configuradas en los Loopbacks y la red que une los 3 routers. Para ver la mejor ruta a la red de destino podemos usar el comando “show ip route eigrp“:

R3#show ip route eigrp
     10.0.0.0/24 is subnetted, 4 subnets
D       10.10.10.0 [90/156160] via 10.1.1.1, 00:18:53, FastEthernet0/0
D       10.10.20.0 [90/156160] via 10.1.1.2, 00:18:53, FastEthernet0/0

Ya por último solo nos queda comprobar que se alcanzan cada uno de las IPs de los Loopbacks y la de las interfaces (aunque si llegamos a los Loopbacks es evidente que llegamos a los interfaces) para ello podemos hacer ping o usar TCLSH (ver artículo):

foreach ips {
 10.1.1.1
 10.1.1.2
 10.1.1.3
 10.10.10.1
 10.10.20.1
 10.10.30.1
} {
ping $ips
}

Paso 4: Configurar EIGRP en los interfaces Serial
Configuramos las Interfaces serie con las IPs del esquema y el DCE:

R1:
interface Serial1/0
 ip address 10.10.40.1 255.255.255.0
 clock rate 64000
 no shutdown

R2:
interface Serial1/0
 ip address 10.10.40.2 255.255.255.0
 no shutdown

Si mostramos la interfaz S1/0 vemos que la interfaz sigue siendo un T1 (1544 kbps) a pesar de que hemos puesto el clock rate a 64000 (64kbps).

R2#sh interfaces serial 1/0
Serial1/0 is up, line protocol is up
  Hardware is M4T
  Internet address is 10.10.40.2/24
  MTU 1500 bytes, BW 1544 Kbit, DLY 20000 usec,
     reliability 255/255, txload 1/255, rxload 1/255

Por defecto EIGRP usa hasta el 50% del ancho de banda que la interface muestra (como hemos visto 1544 kbps), por lo que si tuvieses algún problema se usaría la mitad de 1544 kbps cuando nuestro enlace serie es de 64 kbps. También debemos tener en cuenta que EIGRP hace operaciones de ancho de banda usando una métrica compuesta en la que una de sus variables es el ancho de banda del interface, por lo que es necesario que se configura el ancho de banda correcto en ambos interfaces:

R1:
interface serial 1/0
 bandwidth 64

R2:
interface serial 1/0
 bandwidth 64

Ahora evidentemente en R1 y R2 tenemos un vecino EIGRP más, que es el interface seria:

R2#show ip eigrp neighbors
IP-EIGRP neighbors for process 100
H   Address                 Interface       Hold Uptime   SRTT   RTO  Q  Seq
                                            (sec)         (ms)       Cnt Num
1   10.1.1.1                Fa0/0             12 00:11:20   11   200  0  27
0   10.1.1.3                Fa0/0             14 00:11:20   11   200  0  19
2   10.10.40.1              Se1/0             11 00:23:56   30  2280  0  28

Paso 5: Configurar comandos network usando máscaras Wildcard.
Al principio configuramos los Loopbacks 2 y 3 en R3 con una red 192.168.1.x diferente a los demás interfaces 10.x.x.x. Si ahora queremos añadir esta red a EIGRP podemos poner el comando “network 192.168.1.0 0.0.0.7″ o incluso todo el /24 “network 192.168.1.0 0.0.0.255″ pero si solo queremos poner el Loopback 2 sin meter el Loopback 3 ? Para eso lo que tenemos que hacer es usa un wildcard para que solo se use Loopback2:

R3:
router eigrp 100
network 192.168.1.0 0.0.0.3

Para comprobar que efectivamente SOLO está Loopback2 y no Loopback3 usamos el comando de antes:

R3#show ip eigrp interfaces
IP-EIGRP interfaces for process 100

                        Xmit Queue   Mean   Pacing Time   Multicast    Pending
Interface        Peers  Un/Reliable  SRTT   Un/Reliable   Flow Timer   Routes
Fa0/0              2        0/0        68       0/1          308           0
Lo1                0        0/0         0       0/1            0           0
Lo2                0        0/0         0       0/1            0           0

Sin embargo si en R1 o R2 miramos las rutas EIGRP veremos que la red 192.168.1.x es un /24 en vez de ser un /30, debido a que por defecto R3 hace un auto-summary, por lo que para corregirlo debemos meter el comando “no auto-summary” en el AS 100 de EIGRP de R3.

R3:
router eigrp 100
 no auto-summary

Paso 6: Cambio de topología
Vamos a ver como EIGRP converge muy rápidamente y veremos que si es realmente más rápido que otros protocolos. Primero vamos a ver que para llegar al Lo1 en R1 (10.10.10.1) su ruta es a través de R3-SWITCH-R1:

R3#sh ip route eigrp
     10.0.0.0/24 is subnetted, 5 subnets
D       10.10.10.0 [90/156160] via 10.1.1.1, 01:46:19, FastEthernet0/0
D       10.10.20.0 [90/156160] via 10.1.1.2, 00:39:31, FastEthernet0/0
D       10.10.40.0 [90/40514560] via 10.1.1.2, 00:30:28, FastEthernet0/0
                   [90/40514560] via 10.1.1.1, 00:30:28, FastEthernet0/0
R3#traceroute 10.10.10.1

Type escape sequence to abort.
Tracing the route to 10.10.10.1

  1 10.1.1.1 24 msec 8 msec

Lo que vamos a hacer ahora es tirar la interfaz FastEthernet 0/0 de R1 para que el camino a Loopback1 de R1 desde R3 sea R3-SWITCH-R2-R1 (este último paso vía enlace serial). Para verlo mejor vamos a lanzar desde R3 un ping a 10.10.10.1 con un repetición de 10.000 veces para que nos dé tiempo a ver el efecto.

R3:
R3# ping 10.10.10.1 repeat 10000

Empezamos a ver en R3 un montón de admiraciones (!) que quiere decir que el ping responde.

R1:
interface FastEthernet 0/0
 shutdown

Veremos en R3 como empiezan a salir puntos (.) que indica que pierde el ping con R1 y enseguida vuelven las admiraciones (!) que indica que se ha restablecido la comunicación con R1 ahora a través de R2.
Podemos hacer una traza y veremos como el camino es a través de R2.
Si volvemos a levantar FastEthernet 0/0 en R1 veremos como pasa algo similar pero el corte es mucho menor.

R1:
interface FastEthernet 0/0
 no shutdown

TCLSH: comprobación de conectividad

Objetivo del laboratorio:

  • Aprendizaje básico de scripts en TCL
  • Rip versión 2 básico
  • Comprobación de conectividad

Requisitos:

  • GNS3 (versión usada 0.5)
  • IOS: versión mínima: 12.3(2)T (versión usada 12.4-21)
  • Ficheros del laboratorio (descargar zip con el laboratorio completo)
    NOTA 1: se recomienda hacer el laboratorio desde cero en vez de copiar y pegar para aprender.
    NOTA 2: los path del fichero .net se deben ajustar a los de tu ordenador.

Esquema:
Laboratorio TCLSH del comando foreach para comprobar conectividad con el comando ping
Explicación:

Para entrar en el modo de script de TCL debemos teclear en el router en modo privilegio y nos cambiará el prompt para reflejar que estamos en dicho modo:

router# tclsh
router(tcl)#

Para salir del modo script TCL tecleamos el siguiente comando:

router(tcl)# tclquit
router#

Uso de la instrucción foreach para repetir una acción de forma automática, como puede ser hacer ping a diversas IPs. El formato de la instrucción foreach es al siguiente:

foreach variable {
valor1
valor2
.
.
valorX
} {
instruccion1
instruccion2
.
.
instruccionY
}

En este ejemplo vamos a usar como variables direcciones IPs que queremos comprobar y como instrucción el comando ping para comprobar cada una de las direcciones IP. En este caso solo usamos una sola instrucción pero se podría hacer algún otro comando con las direcciones IP. Abrimos nuestro editor favorito y escribimos el script completo para luego pegarlo en el router.

foreach ips {
 192.168.1.1
 192.168.2.1
 192.168.3.1
 192.168.4.1
 192.168.10.1
 192.168.11.1
 192.168.101.1
 192.168.102.1
 192.168.103.1
 192.168.104.1
 192.168.10.2
 192.168.12.1
 192.168.101.1
 192.168.102.1
 192.168.103.1
 192.168.104.1
 192.168.10.2
 192.168.12.1
} {
ping $ips
}

Comandos sugeridos para comprobación de posible errores:

  • show ip protocols
  • debug ip icmp
  • debug ip packet

CCNP SWITCH 10: Protocolo Spanning Tree Avanzado

Protocolo Rapid Spanning Tree

Comportamiento de los puertos RSTP

El Root Bridge se elige de igual manera que con STP 802.1D mirando el Bridge ID más bajo. Los puertos en RSTP pueden ser:

  • Root Port: El el único puerto de cada switch el cual tiene el mejor camino hacia el Root Bridge por lo que es idéntico a 802.1D. (por definición el Root Bridge no tiene puerto Root).
  • Designated Port: El puerto de switch en un segmento de red que tiene el mejor coste (root path cost) hacía el Root.
  • Alternate Port: Es un puerto que tiene un camino alternativo hacía el Root diferente del Root Port, por lo que es menos deseable (por ejemplo dos uplinks de un swtch de acceso uno será el Root y el otro el Alternate).
  • Backup Port: Es un puerto que provee de redundancia (pero menos deseable) a un segmento donde ya hay otro enlace. (un switch puede o no tener un camino de backup).

RSTP define los estados de los puertos de acuerdo a lo que el puerto hace con las tramas entrantes. Los estados son:

  • Discarding: Las tramas entrantes son tiradas (dropped), por lo que no se aprenden MACs (en 802.1D sería una combinación de los estados Disabled, Blocking y Listening. El estado Listening no se necesita porque RSTP puede negociar rápidamente un cambio de estado sin escuchar primero las BPDUs).
  • Learning: Se tiran (dropped) las tramas entrantes , pero se aprenden MACs.
  • Forwarding: Se envían las tramas entrantes de acuerdo con la MACs aprendidas (y que se aprenden).

BPDUs en RSTP

Ya que RSTP distingue sus BPDUs de los BPDUs de 802.1D pueden coexistir ambos. Cada puerto intenta funcionar de acuerdo al BPDU que recibe, por ejemplo cuando se recibe en un puerto una BPDU 802.1D empieza a funcionar de acuerdo a las reglas de 802.1D.

Tipos de puerto

  • Edge Port: es un puerto en el extremo de la red donde se conecta solo un servidor. Es lo mismo que en STP un puerto PortFast que se mantiene el nombre para que sea familiar. Por definición este tipo de puerto no puede formar un bucle por lo que puede pasar directamente a Forwarding. Recordar que si se recibe una BPDU en un puerto Edge este puerto pierde su estado de Edge.
  • Root Port: Es el puerto que tiene mejor coste hacia el raiz y solo puede haber un puerto Root en cada switch (aunque puede haber caminos alternativos en otros puertos hacia el raiz, Alternate).
  • Point-to-Point Port: cualquier puerto que se conecta a otro switch y se convierte en Designated Port. El estado del puerto lo decide un acuerdo rápido ente ambos switches en vez de que un temporizador expire.

Sincronización

Para participar en la convergencia RSTP un switch debe decidir el estado de cada uno de sus puertos. Los puertos que no son Edge empiezan en estado Discarding. Después de que los switches (cada uno con su vecino) intercambian BPDUs se puede identificar el Root Bridge. Si un puerto recibe una BPDU superior de un vecino ese puerto se convierte en Root Port.

Cambios de topología y RSTP

RSTP detecta un cambio de topología solo cunado un puerto que no es Edge pasa su estado a Forwarding. Puede parce extraño que un fallo de topología no indique cambio de topología.
Configuración RSTP
Para configurar un puerto como Edge usamos:

Switch(config-if)# spanning-tree portfast

Para forzar un puerto que actue como point-to-point usamos:

Switch(config-if)# spanning-tree link-type point-to-point

Rapid Per-VLAN spaning Tree Protocol

Para pasar a modo Rapid PVST+ (o RPVST+) usamos:

Switch(config)# spanning-tree mode rapid-pvst

Multiple Spanning-Tree Protocol

Regiones MST

MST es diferente de 802.1D y PVST+ aunque puede interoperar con ellos. Cada switch configurado con MST debe saber como están sus vecinos y para ellos todos los switches con MST se configuran en regiones, donde en cada región todos los switches con MST tiene los mismos parámetros.

MST define los siguientes atributos:

  • Nombre de la configuración MST (32 caracteres)
  • Número de revisión de la configuración MST (de 0 a 65535). Se debe configurar manualmente en todos los switches el mismo número.
  • Tabla con la relación entre intancia MST y la(s) vlan(s) (4096 entradas). Indica que instancia que vlans contiene o que vlan está en que instancia.

Si dos o más switches tiene los mismos atributos están en la misma región.

IST Instances

Dentro de una única región MST corre una instancia Internal Spanning Tree (IST) para que no haya bucle entre los enlaces CST se une con la frontera de la región y todo los switches dentro de la región. Debemos pensar en IST como un gran switch que se conecta a otro que usa CST.

NOTA: ampliar este punto.

Instancias MST

La idea detrás de MST es agrupar multiples VLANs a un número pequeño de instancias de STP. Dentro de una región las instancias MST coexisten con IST. Cisco soporta un máximo de 16 instancias MST (MSTI) que van desde el 1 al 15 siendo la 0 utilizada por IST.

Configuración MST

Para definir y configurar una región MST debemos seguir estos 7 pasos:

  1. Activar MST en el switch:<
    Switch(config)# spanning-tree mode mst
  2. Entrar en el modo de configuración de MST:
    Switch(config)# spanning-tree mst configuration
  3. Asignar un nombre de configuración a la región (hasta 32 caracters):
    Switch(config-mst)# name name
  4. Asignar un número de revisión de configuración  (0 to 65,535):
    Switch(config-mst)# revision version

    Este número nos sirve a modo de seguimiento y cada vez que se haga un cambio en la configuración se debe incrementar en 1 manualmente y como la configuración dentro de una región debe coincidir en TODOS los switches debemos replicar este incremento en todos los switches de la misma región.

  5. Mapear VLANs a una instancia MST:
    Switch(config-mst)# instance instance-id vlan vlan-list

    el parámetro instance-id (0 to 15) lleva información de la topología de las VLANs listadas, que podemos incluirlas con rangos (usando el guión) o separadas por comas. Resaltar que por defecto TODAS las VLANs var por la instancia 0, el IST.

  6. Mostrar los cambios que hemos realizado:
    Switch(config-mst)# show pending
  7. Salir del modo de configuración de MST que inmediatamente aplica los cambios y los hace activos:
    Switch(config-mst)# exit

NOTA: ampliar este capítulo.

IP Forwarding

Como gestionar el IP Forwarding en diferentes sistemas operativos.

Comprobar como está el IP Forwarding;

Mac OS X

sysctl net.inet.ip.forwarding net.inet.ip.fastforwarding

Linux (general):

sysctl net.ipv4.ip_forward

Para activar el IP forwarding:

Mac OS X

sysctl -w net.inet.ip.forwarding=1 net.inet.ip.fastforwarding=1

Linux (general):

sysctl -w net.ipv4.ip_forward=1
echo 1 > /proc/sys/net/ipv4/ip_forward

Linux (Debian), para dejarlo permanente editar el fichero /etc/sysctl.conf y añadir o cambiar esta opción:

net.ipv4.ip_forward = 1

Para activar los cambio realizados en el fichero debemos ejecutar:

sysctl -p /etc/sysctl.conf

Para desactivar el IP Forwarding:

Mac OS X

sysctl -w net.inet.ip.forwarding=1 net.inet.ip.fastforwarding=0

Linux (general):

sysctl -w net.ipv4.ip_forward=0
echo 0 > /proc/sys/net/ipv4/ip_forward

Linux (Debian), para dejarlo permanente editar el fichero /etc/sysctl.conf y añadir o cambiar esta opción:

net.ipv4.ip_forward = 0

Para activar los cambio realizados en el fichero debemos ejecutar:

sysctl -p /etc/sysctl.conf

NOTA: En Mac OS X también podemos usar este script que lo hace de forma visual: IP Forward con Apple Script.

Habilitar el usuario “root” en Snow Leopard

El usuario de root en OSX está deshabilitado por defecto, además en Snow Leopard la aplicación de Utilidad de Directorio (Directory Utility) no está disponible en la carpeta de Utilidades (dentro de aplicaciones).

Para habilitar el usuario de root:

  • Abrir la aplicación Utilidad de Directorio (Directory Utility.app) que ahora está en “/System/Library/CoreServices”.
  • Desbloquear la aplicación pinchando en el candado e introduciendo la contraseña de nuestro usuario.
  • Seleccionar el menú Editar (Edit) y luego habilitar usuario Root (Enable Root User).
  • Volvemos a selecciona el el menú Editar (Edit) y ahora cambiar la contraseña de root (Change Root Password) e introducimos la nueva contraseña del usuario root.

Antes de cerrar la aplicación volvemos a pinchan en el candado (para cerrarlo).
Es importante avisar que habilitar el usuario root puede acarrear problemas de seguridad.