Últimos Cambios |
||
Blog personal: El hilo del laberinto |
Última Actualización:
30 de Junio de 1.996 - Domingo
El módulo TCP (Transport Control Protocol) [RFC793] es el equivalente al nivel OSI de transporte orientado a conexión. Sus características principales son:
En cuanto a los servicios que utilizan este módulo, destacan:
El protocolo TCP se define en [RFC793], texto notable por el gran
número de erratas y ambigedades que contiene. En [RFC813],
[RFC879], [RFC876], [RFC944] y [RFC1122] se concretan algunos
detalles oscuros y se eliminan diversos errores.
Interfaz
La interfaz de este módulo está constituida por procesos y por subrutinas ejecutadas en el contexto del llamante. Su utilización es muy simple.
PROC_TCP_BC
Este proceso es el encargado de inicializar este módulo. Debe ser invocado con un mensaje "MSG_INIT" o "MSG_QUIT". La inicialización de este módulo debe ser posterior a la del módulo IP.
PROC_TCP_SUPEste es el proceso que recibe los mensajes de las capas superiores y los gestiona adecuadamente. Los mensajes definidos son:
Este mensaje informa a la capa TCP que se acepta una conexión solicitada por una máquina remota. Los valores de los campos son:
campo1: Ignorado
campo2: Identificador de la conexión
campo3: Ignorado
El enlace podrá ser utilizado a partir de ese momento. Este mensaje es generado por una capa superior cuando ésta decide aceptar una conexión remota propuesta por el módulo TCP.
Este mensaje informa a la capa TCP que no tenemos más información que transmitir a través de una conexión dada. TCP se encarga de que cualquier dato pendiente en los búfferes internos sea correctamente entregado. La conexión sigue abierta para recibir, y no se cerrará hasta que el otro extremo lo decida o enviemos el mensaje definido a continuación. Los valores de los campos son:
campo1: Ignorado
campo2: Identificador de la conexión
campo3: Ignorado
Este mensaje cierra una conexión en ambas direcciones. Cualquier dato en tránsito o en los búfferes internos se perderá. Los parámetros de este mensaje son:
campo1: Ignorado
campo2: Identificador de la conexión
campo3: Ignorado
A través de este mensaje una capa superior informa al módulo TCP que hay nueva información para transmitir. La capa TCP se hará cargo de la entrega de los datos. Los parámetros del mensaje son:
campo1: Cadena de MBUFs conteniendo la información que se
desea transmitir
campo2: Identificador de la conexión
campo3: Puede contener los valores CERO, PUSH o MODO_URGENTE
Un valor de cero indica que los datos especificados no están sujetos a ningún tratamiento especial. Si su longitud es pequeña la capa TCP puede realizar un almacenamiento temporal en espera de nuevos datos en vez de enviar un segmento de tamaño reducido.
Si campo3 tiene el valor "PUSH" significa que los datos deben enviarse cuanto antes al otro extremo, y que éste debe entregarlos lo antes posible al proceso responsable. En cuanto a "MODO_URGENTE", supone un "PUSH" implícito (aunque es recomendable incluir la bandera "PUSH" para que sea señalada adecuadamente en el otro extremo) y su objetivo primordial consiste en la resincronización de la transmisión y el intercambio de datos "fuera de banda".
Este mensaje se genera cuando vence el número de reintentos en alguna conexión. Ello puede ser debido a que la red se ha roto, a que la máquina destino se ha caido o bien a que el tiempo de tránsito de los datagramas en la red es demasiado elevado.
campo1: Indeterminado
campo2: Identificador de la conexión
campo3: Indeterminado
La conexión se cierra de forma automática.
PROC_TCP_INF
Este proceso recibe los segmentos TCP y los gestiona adecuadamente. Está diseñado para intercomunicarse con los procesos en los módulos IP e ICMP. Los mensajes que espera recibir son:
Este mensaje indica a la capa TCP que alguna de sus conexiones transcurre a través de una red muy cargada (congestión) y que debería reducir su tasa de transferencia para aliviarlo. Los parámetros de este mensaje fueron definidos en el módulo ICMP.
Este mensaje informa a la capa TCP que alguna de sus conexiones (o intentos de conexión) no puede alcanzar a la máquina destino. Los parámetros de este mensaje fueron definidos en el módulo ICMP.
Este mensaje contiene un segmento TCP recibido por la capa IP. Su formato corresponde al definido en el capítulo dedicado al módulo IP.
En cuanto a los mensajes que transmite, tenemos:
Este mensaje informa a una capa superior que se ha recibido una petición de conexión a uno de sus puertos declarados como "LISTEN". Para que la conexión se establezca la capa superior debe responder con otro "MSG_TCP_OPEN" dirigido a "PROC_TCP_SUP", tal y como se ha visto previamente.
Este mensaje también se genera cuando somos nosotros los que iniciamos la conexión y la máquina remota lo acepta (ver más adelante).
El formato de este mensaje es:
campo1: Cabecera TCP interfaz
campo2: Identificador de la conexión
campo3: Indeterminado
La cabecera TCP interfaz se define como:
typedef struct { uint16 puerto_remoto; uint16 puerto_local; uint32 ip_remoto; uint32 dummy[4]; } tcp_header;
"puerto_remoto" y "puerto_local" indican los puertos a través de los cuales se ha establecido la conexión. "ip_remoto" contiene la dirección IP de la otra máquina. "dummy" contiene valores indeterminados.
Este mensaje es generado cuando la máquina remota no desea transmitir más información. Con ello se informa al nivel superior de que no hay más datos pendientes. No obstante nosotros podemos seguir transmitiendo.
Este mensaje también es generado cuando se aborta una conexión, ya sea en la negociación inicial o bien durante la fase de transferencia. En ese caso cualquier dato que queramos transmitir será ignorado.
El formato de este mensaje es:
campo1: Ignorado
campo2: Identificador de la conexión
campo3: Ignorado
Con este mensaje enviamos a las capas superiores los datos que se van recibiendo. El formato es idéntico al especificado para el proceso "PROC_TCP_SUP". No obstante resulta conveniente realizar algunas matizaciones:
Dado que tanto los procesos de transmisión como los de recepción TCP incorporan mecanismos de buffering no puede esperarse que cada segmento enviado a este módulo mediante "MSG_MBUF" genere uno y sólo un mensaje "MSG_MBUF" en el receptor. En un momento dado el transmisor puede decidir retrasar el envío de un segmento debido a su escasa longitud, congestión de la red, o al cierre de la ventana de transmisión. Por otra parte, un bloque de información puede suponer el envío de más de un segmento debido al MSS (Maximum Segment Size) negociado al principio de la conexión o la MTU de las redes intermedias. Además el receptor puede decidir concatenar varios segmentos en un sólo "MSG_MBUF" si la red cambia su secuenciamiento, etc.
La opción "PUSH" tiene como objetivo la entrega cuanto antes de los datos pendientes al proceso destino. No obstante tampoco sirve como delimitador de campos dentro de lo que es la propia secuencia de bytes. El proceso receptor puede no ver el "PUSH" en la misma posición que el transmisor, ni recibirse el mismo número de PUSHs que se transmitieron. De hecho en [RFC1122] se especifica que la bandera "PUSH" no necesita ser transferida al proceso receptor.
En cuanto a "MODO_URGENTE", tampoco sirve como delimitador claro. Su tarea consiste en conmutar de modo al proceso receptor. Su funcionamiento es inmediato: cuando el transmisor recibe un mensaje "MSG_MBUF" urgente, todos los datos previos todavía no entregados serán marcados como urgentes en el receptor. La utilidad habitual de todo esto consiste en la recuperación de sincronismo entre el transmisor y el receptor. El proceso receptor puede, por ejemplo, ignorar todos los datos marcados como urgentes. Se utiliza una técnica parecida en el protocolo TELNET [RFC854].
Lo único que se garantiza en "MODO_URGENTE" es que los datos que siguen a un mensaje urgente y que no están marcados con ese modo no son recibidos como urgentes en el proceso receptor, siempre y cuando no haya ningún segmento urgente posterior. Esa es la única forma de marcar campos que tiene este protocolo.
En cuanto a rutinas, tenemos:
uint16 tcp_puerto_libre(void);
Esta rutina devuelve un puerto TCP actualmente no utilizado. Por compatibilidad con protocolos superiores, el valor del puerto es igual o superior a 1024.
estado tcp_listen(uint16 puerto,proc_id proc_retorno,puint32 id);
Esta rutina activa un puerto y espera un intento de conexión remoto. "puerto" es el valor del puerto en el que nos ponemos a escuchar. "proc_retorno" contiene el identificador del proceso al cual hay que informar de cualquier evento. Por último "id" es un puntero al lugar donde hay que dejar el identificador de esta conexión. La rutina retorna "OK" si todo ha ido bien y "ERR_OVERFLOW" si hay demasiadas conexiones TCP. "id" contendrá el identificador que debemos utilizar para comunicarnos con dicha conexión.
Si el puerto especificado ya está en modo "LISTEN" devuelve "ERR_EN_USO". Si necesitamos aceptar varias conexiones a un mismo puerto tendremos que lanzar un nuevo "LISTEN" cuando se recibe una petición remota de conexión y se ocupa el anterior.
Cualquier intento de conexión genera un mensaje "MSG_TCP_OPEN" que puede ser aceptado con otro "MSG_TCP_OPEN" o denegado con un "MSG_TCP_CLOSE". Si se deniega, el puerto regresará al modo "LISTEN" y esperará un nuevo intento de conexión.
uint32 tcp_connect(uint16 puerto,uint32 ip,proc_id proc_retorno);
Esta rutina inicia una conexión al puerto "puerto" de la máquina "ip". "proc_retorno" es el proceso al cual hay que informar de cualquier eventualidad. La rutina retorna el identificador asociado a esa conexión. Si es cero indica que hay demasiadas conexiones abiertas. El puerto local utilizado en la conexión se elige de forma arbitraria.
Si la conexión es aceptada se produce un "MSG_TCP_OPEN", mientras que si se deniega se enviará un "MSG_TCP_CLOSE". Si se excede el número de reintentos se enviará "MSG_TCP_TIMEOUT".
tcp_estado tcp_status(uint32 id);
Esta rutina devuelve el estado de una conexión, a partir de su identificador. Los valores posibles son:
La conexión no existe.
Esperamos una petición remota de conexión.
Hemos recibido un intento de conexión que todavía no hemos aceptado o denegado. Este estado ha sido añadido en nuestra implementación, no estando recogido en el documento original [RFC793]. Su objetivo es el evitar que dos conexiones diferentes pero casi simultaneas al mismo puerto "LISTEN" causen problemas. La segunda conexión será ignorada y cuando el segmento original sea reenviado ya habremos decidido si crear o no un nuevo puerto "LISTEN".
Intentamos una conexión.
Hemos recibido un intento de conexión, lo hemos aceptado y ahora estamos esperando a que el otro extremo complete su inicialización.
Conexión establecida.
La conexión está abierta pero nosotros hemos decidido que no tenemos nada más que decir y estamos esperando a que el otro extremo tome nota de ese cambio de situación.
La conexión sigue abierta, pero nosotros no vamos a transmitir nada más y el otro extremo TCP ha informado de ello a sus niveles superiores.
La conexión sigue abierta, pero el otro extremo nos ha indicado que no piensa transmitir nada más.
Ambos extremos hemos decidido que no hay nada más que decir y estamos procediendo a cerrar la conexión
Estado equivalente a "CLOSING". Ver la sección de implementación para más detalles.
La conexión ya ha sido cerrada y no puede utilizarse, pero todavía no eliminamos las tablas internas asociadas por si permanecen todavía segmentos en tránsito en la red. Tras un tiempo prudencial en este estado se elimina el contexto que quedaba y se pasa al estado "CLOSED".
estado tcp_flujo(uint32 id,uint16 tamanho);
Esta rutina es la que implanta el mecanismo de control de flujo TCP. Cada vez que una capa superior desea enviar información a través de una conexión TCP "id", debe pedir permiso. La rutina devuelve "OK" si el flujo está abierto y "FLUJO_LLENO" si la conexión no se encuentra en el estado "ESTABLISHED" o "CLOSE_WAIT".
Es posible, aunque no recomendable, enviar información a través de una conexión aunque su control de flujo esté cerrado. Con ello se pretende simplificar la implementación de algunos protocolos superiores. No debería abusarse de esta característica si no se está completamente seguro de sus implicaciones.
Si el control de flujo está cerrado hay que volver a intentarlo tras un tiempo prudencial (por ejemplo, una décima de segundo).
void tcp_cerrar_flujo_rx(uint32 id);
A través de esta llamada se informa a la máquina remota que la conexión "id" no admite más datos. La capa TCP enviará un segmento vacío con el fin de actualizar la información de ventana del otro extremo. Aún cuando se reciban más segmentos no se enviarán hacia la capa superior. No obstante se entregará cualquier segmento que ya hubiese sido enviado al dispatcher.
Esta acción sólo debe solicitarse en casos de necesidad. En [RFC1122] se especifica claramente que debe evitarse su uso en lo posible.
void tcp_abrir_flujo_rx(uint32 id);
Esta rutina complementa la anterior e informa al otro extremo que estamos dispuestos a aceptar nuevos datos. La capa TCP enviará un segmento para que la máquina remota pueda actualizar su información de ventana.
void tcp_trace(uint32 id,ttcp_trace POINTER trace);
Esta rutina facilita diversa información de depuración y análisis sobre una conexión dada. "id" contiene el identificador de la conexión en la que estamos interesados. "trace" es un puntero a una estructura definida como:
typedef struct { tcp_estado estado; uint32 bytes_tx; uint32 bytes_rx; uint32 srtt; uint16 ventana_tx; uint16 bytes_cola_tx; uint16 mss; } ttcp_trace;
"estado" contiene el estado de la conexión, tal y como se vió
con anterioridad. "bytes_tx" y "bytes_rx" indican el número de
bytes transmitidos y recibidos, respectivamente. "srtt" es el
tiempo ida y vuelta estimado de la conexión, en milisegundos.
"ventana_tx" informa del tamaño de la ventana publicada por el
otro extremo. "bytes_cola_tx" es el número de bytes que
pendientes de confirmación. Por último
"mss"
es el tamaño máximo
de segmento que admite la máquina remota.
Implementación
Este módulo sigue escrupulosamente todas las guías definidas en [RFC793], implantándolo completamente. Se trata de un protocolo complejo y ambiguo. El texto original contiene dos errores graves:
En [RFC1122] se indican más errores en la especificación original, y se concretan muchos detalles oscuros.
En la implementación actual los mensajes de control de flujo, "MSG_ICMP_SOURCE_QUENCH", son ignorados. La razón de ello es que este Proyecto ha sido diseñado para trabajar en redes de baja velocidad (modems) y con acceso final a redes rápidas, por lo que la congestión que provocamos es nula. Se mantiene en estudio el utilizar un esquema sencillo de control de flujo mediante, por ejemplo, la paralización de las transmisiones TCP durante un tiempo determinado (por ejemplo, cinco o diez segundos). Los posibles mensajes "MSG_ICMP_DEST_UNREACHABLE" también son ignorados, confiando en el mecanismo de reintentos para o bien solucionar el problema o bien cerrar la conexión.
A la hora de calcular el tiempo de tránsito de la red se cronometra el tiempo transcurrido entre el envío de un segmento y su confirmación, sujeto a una exponencial 2^-x. Suponemos que el retardo de la red es aproximadamente igual en ambas direcciones. El mecanismo de retransmisiones se dispara cuando transcurre un período superior al 25% del RTT estimado sin que llegue alguna confirmación. Cada vez que se realiza una retransmisión crece el RTT, y la conexión se aborta cuando éste supera un valor crítico (actualmente unos 75 segundos).
Todo este mecanismo está siendo estudiado con cuidado, intentando acomodarlo lo máximo posible a las características de este Proyecto. En [RFC1122] se mencionan dos esquemas alternativos que dejan obsoleto a éste, pero desgraciadamente no han sido publicados como RFCs. Uno de nuestros mayores problemas es el hecho de que la longitud de los datagramas influye considerablemente en el RTT, ya que nuestro enlace es de muy baja velocidad (comparativamente). El otro problema es que si nosotros no estamos transmitiendo nada no podemos recalcular el RTT. De todos modos la implementación efectuada ha dado unos resultados bastante aceptables y, además, todo esto no influye en la recepción de datos, acción mucho más habitual en nuestro contexto.
No es necesario que los "MSG_MBUF" que se transmitan midan lo mismo que el MSS de la conexión. La capa TCP se encarga de realizar las correcciones necesarias. Lo que sí es imprescindible es que nunca se supere el valor "MEM_BLOQUE" declarado en el módulo de gestión de memoria. En la negociación del MSS se tiene en cuenta el MTU del canal asociado a nuestra dirección IP, con el fin de evitar la fragmentación de los segmentos.
El estado "TIME_WAIT" se emplea para conservar ciertas estructuras internas asociadas a una conexión que ya ha sido cerrada, en previsión de que la red pueda duplicar y/o desordenar datagramas. Según [RFC793] este estado debe mantenerse durante un tiempo mínimo que garantice que cualquier datagrama en tránsito agote su tiempo de vida. En la implementación actual se ha fijado una temporización de dos minutos. No obstante las estructuras asociadas a una conexión son grandes y, por consiguiente, ocupan una considerable cantidad de memoria. Por ello, si se intenta abrir una conexión y no hay suficiente sitio en las tablas internas para ello, se busca la conexión en estado "TIME_WAIT" más antigua y se elimina para acomodar los nuevos datos. Si no hay ninguna conexión en "TIME_WAIT" no se podrá crear el nuevo enlace.
Aún cuando el protocolo permite enlaces unidireccionales, cuando uno de los extremos cierra la conexión pero el otro no, la filosofía general de las aplicaciones que hacen uso de esta capa consiste en cerrar la conexión en cuanto el otro extremo lo hace.
Se han considerado algunas extensiones:
En el documento [RFC1263] se hace un análisis detallado del
impacto, a nivel de compatibilidad y ampliaciones futuras, de las
extensiones propuestas. No se ha incluido ninguna de ellas por
tratarse de modificaciones experimentales poco difundidas.
Bibliografía
[HTTP1.1] "HyperText Transfer Protocol" http://www.w3.org [RFC793] RFC793: "Transport Control Protocol" Jon Postel Septiembre 1.981 [RFC813] RFC813: "WINDOW AND ACKNOWLEDGEMENT STRATEGY IN TCP" David D. Clark Julio 1.982 [RFC821] RFC821: "SIMPLE MAIL TRANSFER PROTOCOL" Jonathan B. Postel Agosto 1.982 [RFC854] RFC854: "Telnet Protocol specification" Jon Postel Joyce Reynolds Mayo 1.983 [RFC862] RFC862: "Echo Protocol" Jon Postel Mayo 1.983 [RFC863] RFC863: "Discard Protocol" Jon Postel Mayo 1.983 [RFC879] RFC879: "The TCP Maximum Segment Size and Related Topics" Jon Postel Noviembre 1.983 [RFC896] RFC896: "Congestion Control in IP/TCP Internetworks" John Nagle Enero 1.984 [RFC944] RFC944: "Official ARPA-Internet protocols" Joyce Reynolds Jon Postel Abril 1.985 [RFC959] RFC959: "FILE TRANSFER PROTOCOL (FTP)" Joyce Reynolds Jon Postel Octubre 1.985 [RFC1072] RFC1072: "TCP Extensions for Long-Delay Paths" Van Jacobson R. Braden Octubre 1.988 [RFC1122] RFC1122: "Requirements for Internet Hosts -- Communication Layers" Robert Braden Octubre 1.989 [RFC1146] RFC1146: "TCP Alternate Checksum Options" Johnny Zweig Craig Partridge Marzo 1.990 [RFC1191] RFC1191: "Path MTU Discovery" Jeffrey Mogul Steve Deering Noviembre 1.990 [RFC1263] RFC1263: "TCP EXTENSIONS CONSIDERED HARMFUL" Larry L. Peterson Sean O'Malley Octubre 1.991 [RFC1323] RFC1323: "TCP Extensions for High Performance" Van Jacobson Bob Braden Dave Borman Mayo 1.992 [RFC1435] RFC1435: "IESG Advice from Experience with Path MTU Discovery" Stev Knowles Marzo 1.993 [RFC1693] RFC1693: "An Extension to TCP : Partial Order Service" Phill Conrad Paul D. Amer Tom Connolly Noviembre 1.994 [RFC1866] RFC1866: "Hypertext Markup Language - 2.0" T. Berners-Lee D. Connolly Noviembre 1.995
Más información sobre los OpenBadges
Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS