Últimos Cambios |
||
Blog personal: El hilo del laberinto |
Última Actualización: 26 de Marzo de 1.999 - Viernes
La fase 2 del proyecto ESNET consiste en:
¿Qué significa "distribuir" y
por qué es necesario?
Tal y como se explica en el Libro Blanco de IRC ESNET, una red de IRC típica suele tener una estructura centralizada desde un punto de vista administrativo y de control, con un único nodo de control -o, como mucho, dos- encargado de supervisar toda la red. Este esquema plantea numerosos problemas, muchos de ellos ya comentados en el citado documento:
Disponer de nodos de control centralizados tiene, no obstante, una serie de ventajas a destacar:
Sopesando pros y contras parece razonable el intentar, al menos, la adopción de un esquema distribuído en aquellas áreas que más lo necesitan:
Adicionalmente surge el problema de cómo tratar a los usuarios que cambian de nick constantemente intentando evitar la expulsión, por ejemplo.
Con el esquema propuesto, un usuario nuevo con nick registrado "no existe" mientras no indique su clave. No será visible para ningún nodo o usuario de la red, ni tendrá acceso a ningún servicio. Asímismo cualquier cambio de nick a uno registrado será ignorado a menos que el usuario ponga la clave correcta.
Distribución de la Base de Datos
Un esquema distribuído de las características propuestas implica replicar las bases de datos básicas. Dicha replicación puede hacerse de varias formas, que se describirán dentro de un momento.
En el diseño inicial de la Fase UNO de ESNET se contemplaba la posibilidad de instalar ya en ese paso un control de clonos distribuído, cuyo funcionamiento se explicará en una sección posterior. En ESNET el número de clonos está limitado a DOS (y es posible que se limite a UNO en el futuro, si el esquema de control de nicks funciona como debe). No obstante siempre existen casos, como los cibercafés o las máquinas multiusuario de las universidades, donde hay que permitir un número mayor de conexiones simultaneas. Dichas "excepciones", en la terminología de IRC-Hispano, se denominan "I-Lines".
Es previsible que el número de "I-lines" que el sistema tenga que manejar sea reducido (menos de 50), al menos en una primera fase. Dado esto, sería posible utilizar el siguiente esquema de replicación:
Estudiando la casuística se puede comprobar que el esquema propuesto funciona perfectamente. Cuando un servidor arranca no tiene ninguna "I-Line" definida, por lo que no permitirá más de dos clones, pero en cuando se enlace con otro nodo, éste le suministrará la base de datos actual. Cualquier desincronía en la base de datos se soluciona tras un "netjoin" y la consiguiente cancelación por el nodo de control.
Aquellos famirializados con el mundo de las redes, reconocerán en este esquema un protocolo muy similar al protocolo de rutado RIP.
Lamentablemente este esquema, aunque muy elegante, no se presta al manejo de grandes bases de datos. Cualquier "netjoin" implica el viaje de la base de datos completa en ambas direcciones durante el "netburst", y cuando ésta crece en tamaño (por ejemplo, porque contiene también la base de datos de nicks), la sobrecarga lo convierte en inviable.
Cualquier "netjoin" empieza con un intercambio de claves y una identificación mutua. Es posible, por tanto, que en esa etapa se intercambien también el número de versión de sus bases de datos respectivas. Cada registro de la base de datos se numera en una sucesión estrictamente monótona creciente. Nunca se borran registros: el borrado de un registro consiste en la entrada de un registro posterior que indica el borrado del primero. Los mismo para las modificaciones.
Según esto es posible que el nodo con la base de datos más moderna actualice al otro sin más que enviar los registros nuevos (dichos registros pueden incluir, como se ha comentado, modificaciones y bajas de los ya existentes). Para evitar un crecimiento ilimitado de la base de datos, se realizarán operaciones de "compromiso" de forma periódica.
El funcionamiento actualizado sería el siguiente:
El estudio casuístico de este protocolo es bastante más complejo que el basado en RIP, sobre todo debido a los registros de compromiso utilizados para compactar la base de datos. Indudablemente resulta más sencillo enviar periódicamente a todos los nodos una copia de la base de datos por mail compactada hasta, por ejemplo, el mes anterior.
Es mi intención que los compromisos se utilicen de forma muy esporádica, y sólo cuando el espacio desperdiciado en la base de datos tenga una importancia significativa (por ejemplo, 25% de espacio desperdiciado). Idealmente, también, deberían expedirse compromisos cuando no haya nodos en split.
En principio existen varios posibles problemas con este esquema:
Es posible que entre el envío del número de versión de la base de datos y la recepción del del otro extremo lleguen nuevos registros. Ello no es problema aunque se trate de un compromiso, porque los netburst se generan de forma atómica (es decir, se mete toda la ráfaga en la cola de salida). Hay que tener en cuenta, eso sí, el enviar siempre la última versión de la base de datos, no hasta la versión que se haya indicado en la negociación inicial.
Un nodo terminal en sí no tiene problemas, ya que sólo tienen un enlace simultáneo por el que preocuparse; el único riesgo de inconsistencia son los HUBS. Por ejemplo: H1 y H2=hubs; O=Olimpo, T1 y T2=nodos terminales. Topología:
O-H1-H2 | | T1 T2
Si el enlace H1-H2 es lento (lag), Olimpo propaga un nuevo registro y T1 se va en split una vez recibido el registro y reconecta vía H2, H2 aceptará el nuevo registro de T1, y un tiempo después le llegará el mismo registro vía H1:
O-H1-H2-T1 | T2
Es debido a esto por lo que se necesita la regla 6. Un registro duplicado se ignora.
La cosa se complica si existen compromisos viajando por la red y ocurren reestructuraciones topológicas. Es necesario que los registros marcados como borrados de forma temporal sean comparados también con los registros nuevos que lleguen. Asímismo cualquier registro que llegue con versión inferior al último compromiso, debe ignorarse (regla 6).
Por último, existe el riesgo de "entrecruzamiento". Supongamos, por ejemplo, que se envía un compromiso a la red, y se produce el cambio topológico anterior. Posibles secuencias de eventos problemáticas:
Existe una "race condition" adicional, importante: mientras la base de datos viaja en un sentido, en el otro se transfieren nodos, usuarios y canales. Es posible, por ejemplo, que la base de datos transfiera un nuevo registro de nick y que dicho nick ya estuviese conectado en el otro lado. Hay que estudiar las implicaciones caso por caso.
Una base de datos distribuída es vulnerable a la corrupción de las copias locales. Para prevenir esto en lo posible es necesario que cuando el servidor cargue la base de datos local (reinicio o /rehash) verifique su integridad utilizando un hash de toda la base de datos almacenado en un fichero separado. Si se detecta algún problema, cumplimos la regla 8.
Cualquier manipulación de la base de datos local debe pasar la primera prueba de Hash, tal y como se comenta en el punto anterior. No obstante esta protección no basta contra un administrador malicioso con un mínimo de conocimientos, ya que el código del Hash será público. En todo caso, cualquier intento de propagar los cambios al resto de nodos acabará llegando a oídos del nodo de control, que procedería a tomar las medidas precisas. Asímismo, si no se modifica el servidor, éste detectará la manipulación de la base de datos en cuanto reciba un compromiso externo y si se usa una función hash criptográficamente segura.
No se puede hacer nada ante un administrador que modifique el servidor, ya que él tiene control total de su máquina. Todo lo que se puede garantizar es que dichos cambios no se propagan a servidores "sanos" y que, si se intenta, sea detectado.
Detectar la existencia de servidores "rogue" fuera de control implica el uso de un nodo administrativo encargado de supervisar el estado de las bases de datos, clonos existentes, nicks, registrados, etc. El esquema de auditoría propuesto se analizará con posterioridad.
Podría prevenirse (no simplemente detectarse y corregirse de forma automática) enteramente la difusión de registros maliciosos mediante el uso de firmas digitales en la base de datos, pero resulta completamente impráctico que Olimpo firme todos y cada uno de los registros. Otra posibilidad sería que Olimpo enviase cada registro tantas veces como nodos existan en la red, cada una de ellos cifrado con una clave privada sólo conocida por Olimpo y por cada nodo. De esta forma nadie podría inyectar en ningún nodo un registro que no hubiese sido generado por Olimpo. En vez de criptar cada registro con una clave distinta, se podría criptar hashes del registro y quedarnos, por ejemplo, sólo con un byte del resultado. De esta forma la probabilidad de que un enemigo fuese detectado sería, en el peor de los casos, del 99.61%.
Hasta qué punto son necesarias medidas tan drásticas es algo que habrá que evaluar a la luz de la experiencia.
Mantener la privacidad de una base de datos distribuída es tarea imposible, ya que los administradores locales tienen acceso al código fuente del servidor. Es necesario, por tanto:
Otro esquema de distribución posible, aparentemente muy sencillo, consiste en que cada nodo IRC abra una conexión directa con Olimpo y sólo reciba actualizaciones de la base de datos procedentes de él. Evidentemente ello garantiza que los registros no han sido manipulados, a menos que se hagan cosas como DNS Spoof, hijack y similares (contrarrestable usando criptografía). Por lo demás la reglas de actualización de la base de datos son virtualmente iguales (servirían las mismas, de hecho).
Como problemas tenemos el synflood, el connect flood, los conflictos con cortafuegos y demás, y la necesidad de coordinar dos conexiones independientes pero interrelacionadas. Algunos problemas se aminoran si es Olimpo quien se conecta a los nodos y no al revés.
La decisión de qué esquema implantar finalmente se tomará en los próximos días.
El esquema propuesto para desplegar en ESNET tiene las siguientes características:
La base de datos, por tanto, se gestiona y distribuye dentro de los propios enlaces IRC de la red, sin necesitar el despliegue de una red de control paralela. El formato de los mensajes intercambiados entre los nodos es el siguiente:
<Nodo Origen> DB <Nodo Destino> <Número de Serie> <identificador de la Base de datos> <Clave> [[:]Contenido]
En régimen estacionario, todos los servidores
tienen la misma base de datos. El único tráfico
entre nodos será el habitual de IRC. En un momento
dado se modifica la base de datos en Olimpo, y éste
envía una línea
<Nodo Origen> DB <Nodo Destino> <Número de Serie> <identificador de la Base de datos> <Clave> [[:]Contenido]
Cuando un nodo recibe una línea de base de datos:
Los registros con número de serie CERO tienen un tratamiento especial que se detalla más adelante.
Esto significa que ya tenemos el registro. Lo ignora.
Cuando se produce un "Net Join" es
preciso resincronizar las bases de datos de las dos partes
de la red que se fusionan. Para ello cada nodo envía
al otro lado una línea de la forma
<Nodo Origen> DB <Nodo Destino> 0 J
<Número de Serie Local>
Un nodo recibiendo un registro de esas características comprueba el número de serie recibido con el suyo. Si el otro lado tiene un número de serie más bajo, le transfiere los registros que le faltan. En caso contrario (número igual o mayor), simplemente no hace nada.
La transferencia de la base de datos no se realiza toda de una vez,
sino que el nodo con mayor número de serie envía sólo
los siguientes 100 registros, por ejemplo. Al final de la ráfaga
el nodo envía una línea:
<Nodo Origen> DB <Nodo Destino> 0 B
<Número de Serie Local>
Cuando el otro extremo recibe una línea de esta forma vuelve
a enviar un comando
<Nodo Origen> DB <Nodo Destino> 0 J
<Nuevo Número de Serie Local>
solicitando los siguientes 100 registros. De esta forma:
Para que el protocolo funcione, los nuevos registros que vaya recibiendo el nodo de mayor número de serie no deben transferirse hasta que la cola de registros para el nodo en "Net Join" se vacíe. Este evento se conoce porque cuando estamos transfiriendo una ráfaga de 100 registros sólo tenemos que transferir 100 registros exactos o menos. En ese momento se puede "Abrir el grifo".
Cuando un nodo arranca lee la base de datos en disco y la mantiene en memoria. Seguidamente se abre al servicio y se conecta a la red, dándose una situación de "Net Join". Durante la lectura de la base de datos se realizan diversos chequeos de integridad. En caso de problemas la borra completamente y recurre al "Net Join" para obtener una versión actualizada y correcta.
Dado que los números de registro no se reutilizan y que el borrado de un registro se efectúa propagando un registro nuevo, la base de datos crece de forma permanente. Debe existir un método que permita "compactar" la base de datos periódicamente, eliminando los registros borrados y verificando la integridad de las copias locales.
Ésta es, en principio, la mayor complicación que se vió en el análisis de la sección anterior.
En esta propuesta se elimina el problema asegurándose
de que todos los nodos tienen la misma base de datos antes de
proceder a comprometerla. Para ello Olimpo pregunta a cada
nodo de la red qué versión tiene:
<Nodo Origen> DB <Nodo Destino> 0 Q
<Número de Serie Actual>
Cuando un nodo recibe el mensaje correspondiente, comprueba el número de serie con el suyo:
En todo caso responde al nodo de control con un
<Nodo Origen> DB <Nodo Destino> 0 q
<Número de Serie Local> <Hash>
El "hash" es una especie de "suma de control" de la base de datos. Mediante este intercambio de mensajes, el nodo de control puede:
Cuando el nodo de control quiere "comprometer" la base de datos actual:
<Nodo Origen> DB <Nodo Destino> 0 Q <Número de Serie Global> <Hash>
Los nodos que reciben esta orden "limpiarán" sus bases de datos locales, y la graban en disco actualizadas, comprobando el nuevo hash.
Es importante que el compromiso de la base de datos se efectúe con todos los servidores manejando el mismo número de serie. En particular ello obliga a que durante un compromiso no se pueda actualizar la base de datos en Olimpo. Una posibilidad es obviar este problema y simplemente contrastar las respuestas que se reciben con la versión de Olimpo. En el momento que Olimpo vea que alguno de los nodos no está actualizado, abortará el compromiso y lo intentará más tarde. Esto causa problemas si el rastreo no sigue un orden topológico inverso, ya que pueden llegar registros nuevos a un nodo que ya hemos rastreado.
Una solución simple es abortar la transacción si se recibe algún registro nuevo (lo que puede ocasionar "starvation" si la base de datos es muy activa) o bien encolar los registros nuevos hasta que se resuelva la transacción (lo que puede ocasionar retardos muy importantes en la actualización de la base de datos en situaciones de lag).
A continuación se detalla el formato de mensajes intercambiados entre servidores utilizando los parches hasta DB3:
<Nodo Origen> DB <Nodo Destino> 0 Q <Número de Serie Global>
<Nodo Origen> DB <Nodo Destino> 0 q <Número de Serie Local> <Hash>
<Nodo Origen> DB <Nodo Destino> <Número de Serie> N <Nick> [Clave]
Más información sobre los OpenBadges
Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS