Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

Negociación en los enlaces servidor <-> servidor

Última Actualización: 24 de Julio de 2.000 - Lunes

En este documento se describe el sistema de negociación dinámica y asíncrona en los enlaces entre servidores, en producción en IRC-Hispano desde Mayo de 2.000.


Características:

  • La negociación es completamente asíncrona.
  • La negociación se realiza de forma independiente en cada dirección.
  • La negociación puede realizarse en cualquier momento de la conexión.
  • Es posible renegociar condiciones en cualquier momento.
  • Un extremo puede solicitar al otro que le pida negociar una propiedad determinada, o unos parámetros de configuración determinados.
  • Pueden negociarse varias propiedades de una conexión de forma simultanea, o una por una.


Descripción Informal:

El sistema de negociación está basado el esquema de negociación del protocolo PPP, usando los comandos REQ, ACK, NACK y REJ.

Cuando un extremo desea solicitar que el otro extremo utilice (o deje de utilizar) alguna propiedad, lo solicita mediante un comando REQ (request). El otro extremo tiene, pues, cuatro posibilidades:

  1. Ignorar la petición.
  2. Responder con un REJ (reject). El servidor está indicando que no soporta o no entiende la propiedad negociada.
  3. Responder con un ACK (acknowledge). El siguiente comando se recibirá bajo las nuevas condiciones acordadas.
  4. Responder con un NACK (non acknowledge). El servidor nos está indicando que o bien le solicitemos una negociación de la propiedad demandada, o bien ajustemos nuestra petición a los parámetros que nos proporciona. Es decir, nos está realizando una sugerencia.

Este ciclo puede repetirse las veces que sea preciso.

Existe más información sobre este tema en diversas propuestas que envié en su momento a la lista "client-server", en la que se intentaba consensuar un sistema de negociación entre clientes y servidores, como medio para evolucionar el protocolo. Las discusiones en la lista no llegaron a nada, pero mi propuesta está disponible online, en la sección de bibliografía.

La implementación final que se documenta aquí está basada en aquellas propuestas, pero existen un buen número de cambios.


Sintaxis:

NEGOCIACION = "CONFIG" COMANDO ":" OPCION["," OPCION]...
COMANDO = "REQ" | "ACK" | "NACK" | "REJ"
OPCION = ["!"][GRUPO":"]PROPIEDAD["("PARAM["," PARAM]...")"]
GRUPO = PALABRA
PALABRA = ALFA ALFANUM...
ALFA = "a".."z" | "A".."Z"
NUM = "0".."9"
ALFANUM = ALFA | NUM | "-" | "_"
PROPIEDAD = PALABRA
PARAM = ALFANUM...


Descripción Formal:

La negociación es bidireccional e independiente en cada sentido. La negociación es asíncrona, por lo que no es preciso detener el intercambio de datos hasta que ésta haya finalizado.

  1. El nodo recibe un REQ:

    • Si existe alguna propiedad a negociar que desconoce, responde con un REJ y las propiedades desconocidas. Esas propiedades son devueltas en el REJ sin ninguna modificación, aunque pueden enviarse en orden diferente.

      También se responde con un REJ cuando la propiedad negociada es inválida en el modo actual (por ejemplo, se está negociando compresión alternativa dentro de una conexión ya comprimida). Este caso debe valorarse dentro del contexto de los modos actuales y el resto de propiedades que se están negociando. Puede ser una opción, por ejemplo, emitir un NACK en su lugar.

    • Si alguna de las propiedades solicitadas es incompatible con el resto, o tiene algún parámetro que el receptor desea modificar, el receptor responde con un NACK. Se incluyen todas las propiedades deseadas por el receptor, con los parámetros más apropiados. La lista de propiedades devuelta puede incluir propiedades no solicitadas en un principio, y omitir propiedades solicitadas inicialmente pero que el receptor no desea.

    • Si todas las propiedades solicitadas, y sus parámetros, son apropiadas, el receptor responde con un ACK, reenviando las propiedades que se están adoptando. Tras el ACK, el nodo receptor adopta los modos de comunicaciones negociados y confirmados.

    • En algunos casos, un nodo puede retrasar la respuesta a un REQ hasta que obtenga información adicional, pero nunca debería hacerlo de forma indefinida.

      Un ejemplo típico de esta necesidad es la negociación de propiedades entre servidores ANTES de que se sepa si la conexión se corresponde a un servidor o no. Tan pronto el nodo sabe que la conexión se corresponde con un servidor, debe completar la negociación.

  2. El nodo recibe un ACK:

    • Si las propiedades que se están confirmando se solicitaron previamente con un REQ, el nodo receptor adopta las propiedades negociadas.

    • Si se confirman propiedades que no se solicitaron previamente, estamos ante una violación del protocolo, y el servidor debería cortar el enlace y publicitar este hecho.

  3. El nodo recibe un NACK:

    • Si estamos dentro de una negociación, el servidor debería reenviar su REQ con las propiedades y parámetros que se indican, siempre que sean compatibles con su propia programación.

    • Si no estamos en una negociación, el receptor es libre para solicitar un REQ acorde con las propiedades que se solicitan y su propia programación.

      Básicamente, el otro extremo nos está ofreciendo una sugerencia.

  4. El nodo recibe un REJ:

    • El receptor no debería enviar un REQ con las propiedades que se han rechazado, a menos que previamente se negocien otras propiedades .

  • REQ espontaneos

    Un nodo puede intentar renegociar propiedades en cualquier momento.

  • NACK espontaneos

    Un nodo puede intentar que el otro extremo inicie una renegociación en cualquier momento.


Propiedades:

Las propiedades son las características de un conexión unidireccional que se están negociando en un momento dado. Puede consultarse su sintaxis en la sección correspondiente.

  • Pueden negociarse varias propiedades de forma simultanea. En ese caso sólo se responderá con un ACK cuando todas ellas sean válidas y compatibles (o se aceptan todas, o no se acepta ninguna).

  • Una propiedad con el signo de admiración cerrada delante indica una negación. Es decir, se está negociando la supresión de una propiedad.

  • Una propiedad puede tener un número indeterminado de parámetros, cuyo significado incumbe únicamente al módulo que implementa dicha propiedad.

  • Una propiedad puede tener asociado un grupo. La utilidad del grupo es reducir el riesgo de polución del espacio de nombres. Por ejemplo, una red puede constituir su propio grupo, de forma que sus propiedades no entren en conflicto con las de otras redes.


Configuración:

En principio, cada nodo conoce las propiedades de negociación recomendadas y las permitidas, por lo que no se requiere ningún tipo de configuración para que una nueva versión de un nodo empiece a negociar nuevas propiedades.

Existen, no obstante, casos en los que una configuración manual resulta beneficiosa. El más habitual es cuando una propiedad negociada por defecto no resulta apropiada para un enlace en particular. El ejemplo típico es la compresión del enlace cuando los nodos están en una red local o disponen de abundante ancho de banda (y la CPU es un recurso escaso). El otro extremo es cuando un nodo soporta determinadas propiedades, pero no las negocia por defecto; gracias a la configuración manual, se puede hacer que un enlace determinado emplee dichas propiedades.

La configuración de la negociación se realiza a través de líneas F en el "ircd.conf", con el formato siguiente:

F:[propiedades_TX]:[propiedades_RX]:[nodo]

  • nodo: El nombre del nodo, tal y como se define en las líneas C/N, en las líneas H.

  • propiedades_TX: Parámetros de configuración de las propiedades que nos llegan por REQ.

  • propiedades_RX: Parámetros de configuración de las propiedades que solicitamos por NACK.

Las propiedades se definen como una letra (cada letra, una propiedad). La letra en mayúscula significa "siempre", y en minúscula significa "nunca".

Por ejemplo, a la compresión ZLIB le corresponde la letra ZETA ("Z"):

  • Si aparece en minúscula en el primer campo, cualquier REQ que recibamos para ZLIB será rechazado con un REJ. Si aparece en mayúscula en el primer campo, solicitaremos un REQ mediante el empleo de NACK.

  • Si aparece en minúscula en el segundo campo, no solicitaremos ZLIB mediante REQ, incluso aunque el otro extremo nos lo solicite mediante NACK. Si aparece en mayúscula en el segundo campo, pediremos ZLIB por REQ.

Cualquier propiedad poseerá unas características de negociación, que se definen junto a la misma. Dichas características indican su nivel de prioridad, incompatibilidades y configuraciones por defecto en ausencia de líneas F.

Obsérvese, asimismo, que si solicitamos un REQ forzado por línea F, pero el otro extremo nos deniega la propiedad o, sencillamente, no completa la negociación, el enlace no se romperá. Esto es así porque nunca podríamos saber cuándo se ha completado la negociación, sobre todo en situaciones de lag. Además, ello asegura que el enlace tendrá éxito aunque ambos extremos tengan configuraciones contradictorias (en cuyo caso se utilizará el máximo común divisor).

El estado de las "líneas F" es accesible a través del comando "stats f".


Estado de la Implementación:

Este documento define un "framework" genérico, y en esta sección describiremos el estado actual de la implementación, a Julio de 2.000:

  • Los cambios en el código pueden analizarse en detalle haciendo un "diff" entre la versión "db75-jcea" y la versión "NEGOCIACION_y_COMPRESION", disponibles en el CVS. Los cambios no se incluyen aquí por ser redundantes (ya están en el CVS) y porque ocupan más 160Kbytes, en formato "diff contextual".

  • Se incluyen buen número de decisiones de diseño y valoraciones en el fichero "CAMBIOS" de la distribución.

  • Existen lo que llamo "negociaciones especulativas": se trata de negociaciones en las que el servidor intenta negociar propiedades sin que ello modifique el estado de la conexión y sin saber, asimismo, si dichas propiedades serán toleradas por él mismo.

    El caso típico es la negociación de compresión en los enlaces antes de que el enlace se haya identificado como perteneciente a otro servidor: Debemos negociar la compresión antes de saber si se trata de un servidor para poder disponer de la compresión a la hora de transferir la ráfaga. Naturalmente el servidor no aceptará compresión en un enlace que no está establecido con otro servidor, pero intentará realizar una negociación especulativa de la siguiente manera:

    • Antes:
      PASS [clave]
      SERVER [servidor]
      [BURST]                 PASS [clave2]
                              SERVER [servidor2]
                              [BURST2]
      

    • Ahora:
      PASS [clave]
      CONFIG REQ [configuración]
      SERVER [servidor]
      [BURST]                                CONFIG REQ [configuración2]
      CONFIG ACK [configuración2]            PASS [clave2]
                                             SERVER [servidor2]
                                             CONFIG ACK [configuración]
                                             [BURST2]
      

    Como puede verse, el servidor que se conecta envía la petición de configuración (en particular, compresión) antes de que el otro extremo se haya identificado como servidor, aunque es esperable que lo sea, ya que nos estamos conectando a él (si no lo es, el menor de nuestros problemas será el negociar propiedades, ya que estaremos filtrando nuestra clave de enlace).

    Obsérvese que el extremo que recibe la conexión no sabe que lo que se le conecta es un servidor hasta recibir el comando "SERVER", y que la negociación se envía antes de enviar dicho comando (la razón de ello es que "server" es un comando que desencadena infinidad de acciones, y debemos negociar con anterioridad). En vez de responder inmediatamente con un error, el servidor receptor "espera" a ver si el otro extrema se identifica como servidor y, en ese caso, accede a la negociación.

    El efecto práctico es que el segundo servidor enviará su ráfaga, normalmente, ya codificada según las propiedades negociadas. Obsérvese que ello no es simétrico: la ráfaga del segundo servidor se envía cuando esa conexión ya se ha negociado, mientras que la ráfaga del primer servidor se manda en bruto. Por tanto, es muy recomendable que los servidores "pequeños" se conecten a los grandes, y no a la inversa, sobre todo por cuestiones como la compresión de los enlaces.

  • La implementación actual no soporta la negociación de varias propiedades de forma simultanea. La razón de ello es que, en el momento actual, sólo existe una propiedad a negociar.

  • La implementación actual no soporta parámetros en las propiedades. La razón de ello es que, en el momento actual, no existen propiedades con parámetros.

  • La implementación actual no soporta la negación de una propiedad. La razón de ello es que las propiedades implementadas hasta el momento no soportan el ser eliminadas una vez que se han activado.

  • La implementación actual no soporta la negociación de propiedades asociadas a grupos. La razón de ello es que, en el momento actual, no se ha implementado ningún grupo.

  • Si un cliente no servidor solicita negociación, se le ignora completamente, en vez de responderle un "REJ".

  • La implementación actual no aborta un enlace en el caso de que un cliente confirme propiedades que no han sido negociadas. En su lugar, la negociación es completamente ignorada. Esto es así, porque en el momento en que tenemos conocimiento de este hecho no es sencillo cortar en enlace, ya que ello dejaría el servidor en un estado inconsistente.


Nuevas Negociaciones

  • La inclusión de nuevas propiedades a negociar se realiza modificando el fichero "m_config.c". En dicho fichero se definen tanto las propiedades a negociar como toda la lógica involucrada en la toma de decisiones respecto a las propiedades: ¿se trata de un servidor?, ¿la propiedad negociada contradice el estado actual?, etc.

  • En este momento sólo se invoca este módulo cuando se conectan dos servidores o cuando se recibe un comando "config".En caso necesario, es perfectamente posible añadir nuevas llamadas a este módulo en determinadas partes del código del servidor (por ejemplo, leyendo una línea de configuración determinada o tras la ejecución de un comando emitido por un IRCop).

  • La gestión de las propiedades, a lo largo del código, sólo deben compilarse si el servidor se ha compilado con la opción de negociación activada y con la negociación de dicha propiedad también activada. De esta forma debe ser posible compilar una versión funcional del servidor sin dicha propiedad.

  • Las nuevas propiedades deben definir e implementar su "letra" para la "Línea F".

  • Salvo en casos muy justificados, cada propiedad debe negociarse de forma separada.


Bibliografía:


Historia:

  • 24/Jul/00: Se ha terminado el grueso de este documento.

  • 07/Jun/00: Primera versión de este documento.



Python Zope ©2000 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS