P0 · Calentamiento
Dos programas en C para practicar sockets, medir la red y conocer la forma de entregar. Se usa lo visto en las semanas 1 y 2. Entrega en la semana 3.
1. eco: un servicio con TCP
eco_server <puerto> atiende a varios clientes a la vez, usando poll, y eco_client <host> <puerto> lee mensajes de la entrada estándar, uno por línea, y muestra lo que el
servidor devuelve.
Cada mensaje viaja así, porque TCP no conserva fronteras entre mensajes:
| Campo | Tamaño | Contenido |
|---|---|---|
length |
4 bytes | Largo de payload, sin signo y en orden de red |
payload |
length bytes |
Los datos, de hasta 1 MiB (1 048 576 bytes), incluido el 0 |
El payload son bytes cualquiera, no un string: puede traer ceros y fines de línea.
Reglas del servidor que revisan las pruebas
- Devuelve cada mensaje idéntico y completo, sin importar en cuántos pedazos llegue. Varios mensajes juntos en un solo
sendtambién valen, y vuelven en orden. - Atiende al menos 100 conexiones abiertas a la vez, y los mensajes de un cliente nunca se mezclan con los de otro.
- Un cliente lento no detiene a los demás. Eso incluye dos casos: uno que se detiene a la mitad de un mensaje, y uno que envía sin leer sus ecos.
- Un cliente que envía sin leer no infla el servidor: mientras su respuesta no salga, se deja de leer de esa conexión. Con un cliente así, el servidor se queda por debajo de 64 MiB de memoria. Cuando ese cliente vuelve a leer, recibe todos sus ecos, completos y en orden.
- Si
lengthpasa de 1 MiB, el servidor cierra solo esa conexión, apenas lee el encabezado, y sigue atendiendo. - Si un cliente se desconecta a la mitad de un mensaje, o sin leer su eco, el servidor libera sus recursos (descriptor y memoria) y sigue.
- Si un cliente cierra solo su lado de escritura (
shutdown), igual recibe los ecos pendientes; después, el servidor cierra. - Se puede reiniciar de inmediato en el mismo puerto (
SO_REUSEADDR). - Con argumentos inválidos (el puerto va de 1 a 65535) o con el puerto ocupado, termina con un código distinto de 0 y un mensaje en
stderr.
Reglas del cliente que revisan las pruebas
- Cada línea es un mensaje, sin su fin de línea. Una línea vacía es un mensaje de largo 0. La última línea cuenta aunque no termine en fin de línea.
- Muestra cada eco seguido de un fin de línea, apenas lo recibe, aunque la entrada siga abierta.
<host>puede ser una dirección o un nombre. Si el nombre tiene varias direcciones, las prueba hasta que una responda.- Al terminar la entrada y mostrar los ecos, termina con código 0.
- Termina con un código distinto de 0 y un mensaje en
stderrsi: no se puede conectar, los argumentos están incompletos, una línea pasa de 1 MiB (esa línea no se envía), o el servidor cierra a la mitad de un eco. También termina con error si el servidor anuncia unlengthmayor que 1 MiB.
2. traza: una versión propia de traceroute
traza [-m max_saltos] [-w espera_ms] <destino> envía datagramas UDP con TTL creciente y
lee las respuestas ICMP con un socket crudo. Solo IPv4. <destino> es una dirección o un
nombre.
Por defecto, max_saltos es 30 y espera_ms es 1000. max_saltos va de 1 a 255 y
espera_ms debe ser al menos 1. Se envía una sola sonda por salto. Cada salto imprime una
línea, y el programa termina al recibir Port Unreachable del destino o al llegar a
max_saltos:
<salto> <dirección> <rtt en ms, con 3 decimales>
<salto> *
Reglas que revisan las pruebas
- En los dos casos de término, el código de salida es 0. Con argumentos inválidos, un nombre que no se resuelve o sin permisos para abrir el socket crudo, termina con un código distinto de 0 y un mensaje en
stderr. <salto> *significa que no llegó respuesta enespera_ms, contados desde que se envió esa sonda. Los paquetes ajenos que lleguen en ese lapso no renuevan el plazo.- Cuentan como respuesta
Time Exceededy cualquierDestination Unreachableque citen una sonda de esta ejecución y de este salto.<dirección>es el origen del ICMP. Port Unreachabletermina el programa solo si viene del destino. Si viene de otro router, o si es otroDestination Unreachable, se imprime la línea y se sigue con el salto siguiente.- El socket crudo recibe todo el ICMP de la máquina. Una respuesta es de ustedes solo si el paquete citado dentro del ICMP coincide: destino, puerto de origen y puerto de destino. La respuesta a otro programa, por ejemplo un
pingu otrotraceroutecon los mismos puertos de destino, no cuenta. Tampoco cuenta una respuesta que llega tarde, cuando ya pasó su salto. - Cada línea sale apenas se conoce ese salto, no al final.
Sugerencia: usen el puerto de destino 33434 + salto - 1, como el traceroute clásico, y fijen
el puerto de origen con bind antes de la primera sonda. Lean el encabezado IP con su campo IHL:
no asuman que mide 20 bytes.
Archivos
| Archivo | Qué hace | ¿Lo modifican? |
|---|---|---|
src/eco_server.c, src/eco_client.c, src/traza.c |
Sus programas | Sí |
src/*.c, src/*.h adicionales |
Código común: cualquier otro .c se enlaza con los tres programas |
Si quieren |
Makefile |
Compila y corre las pruebas | No |
p0_public_test.py |
Pruebas públicas | No |
topo/ |
Topología de las pruebas de traza |
No |
Agreguen sus pruebas en archivos test_*.py, en esta misma carpeta: make test las corre. Corren después de las públicas, con la topología todavía arriba.
Cómo se corren las pruebas
Dentro del contenedor del curso (env/run.sh), desde esta carpeta:
make # compila en build/
make test # compila, levanta la topología, corre todo y destruye la topología
make test-eco # solo eco, sin topología
make test-traza # solo traza
make clean # destruye la topología y borra lo generado
make test compila con -Wall -Wextra -std=c17 y exige que no haya advertencias. Corre el
servicio con varios clientes, mensajes partidos y clientes que fallan, y corre los tres
programas con valgrind. Por último, compara la salida de traza con el camino que dicen las
tablas de los routers, también con un router callado, con un enlace lento y con otro tráfico
ICMP en paralelo. Las pruebas tienen márgenes amplios y ninguna depende de un tiempo exacto. Si una
se queda esperando, falla sola a los 120 s.
Las pruebas corren en una red propia, con buffers de TCP chicos a propósito. En loopback normal,
Linux da varios MiB de buffer a cada conexión y un mensaje de 1 MiB siempre cabe, así que un send
que se bloquea o escribe a medias pasaría desapercibido. Por eso, un servidor que funciona a mano
con nc o eco_client puede fallar aquí: no es la prueba, es lo que pasaría en una red real.
La revisión corre además pruebas privadas con otra topología, otras direcciones, otro largo de camino y otros tamaños. Las pruebas leen el camino de las tablas de los routers: no escriban direcciones a mano en su código.
Probar traza a mano
Los routers de la topología son contenedores Alpine, sin gcc y con otra biblioteca de C, así que
un programa compilado aquí no corre adentro. No hace falta: traza se compila y se corre en el
contenedor del curso, y solo entra a la red de un nodo con nsenter. Sigue usando los archivos
y las bibliotecas de este contenedor, así que funciona igual en amd64 y arm64:
make topo-up
make traza ARGS="-m 8 10.91.3.10" # corre en h1, hacia h2
h1 10.91.1.10 --- r1 --- r2 --- r3 --- 10.91.3.10 h2
El mismo truco sirve para gdb, valgrind o tcpdump en la red de h1:
H1=$(docker inspect -f '{{.State.Pid}}' clab-p0-traza-h1)
nsenter -n -t $H1 gdb --args build/traza 10.91.3.10
nsenter -n -t $H1 tcpdump -n -i eth1 icmp or udp
Para hacer ping o mirar las tablas de otro nodo: docker exec clab-p0-traza-r2 ip route. Al
terminar, make topo-down.
Si una prueba falla
eco_clientdemora unos 40 ms por mensaje. Es el algoritmo de Nagle junto con el ACK retrasado: unsenddel encabezado y otro del payload hacen esperar al segundo. Armen el mensaje en un solo buffer o activenTCP_NODELAY.- El servidor muere sin mensaje con clientes que se van. Es
SIGPIPE: revisensendconMSG_NOSIGNAL, o ignoren la señal. - La salida aparece recién al final. Si
stdoutes un pipe,printfguarda en un buffer: llamen afflush. trazaencuentra asteriscos en un camino que anda. Abran el socket crudo antes de enviar la primera sonda. Si no, se pierden respuestas.