Semana 2 — Principios de diseño y sockets
Objetivos
- Explicar los principios que dieron forma a Internet: conmutación de paquetes, end-to-end y modelo de reloj de arena.
- Reconocer tecnologías anteriores, como ATM y Frame Relay, y explicar qué lugar ocupan hoy.
- Distinguir los servicios de TCP y UDP desde el punto de vista de una aplicación.
- Escribir un cliente y un servidor en C con la API de sockets, y manejar sus errores.
Sesión A — Principios de diseño · ✂️ Historia: de ARPANET a ATM y Frame Relay
Lecturas:
- Kurose y Ross, secs. 1.7 (History of Computer Networking and the Internet) y 4.5 (Architectural Principles of the Internet).
- CS168, Network Architecture.
- Saltzer, Reed y Clark, End-to-End Arguments in System Design, secciones Introduction, End-to-end caretaking y A too-real example.
Temas:
- El servicio de IP: best effort. Por qué una red simple permitió crear aplicaciones que nadie había previsto.
- El principio end-to-end: la función que solo se puede hacer bien en los extremos se hace en los extremos. Ejemplo: la transferencia confiable de un archivo.
- El reloj de arena: muchas aplicaciones y muchos enlaces, con IP en el centro.
- Fate sharing y estado: qué guarda un router y qué guarda un host.
- De ARPANET a la Web. ATM y Frame Relay: circuitos virtuales, celdas de 53 bytes y por qué cedieron ante IP sobre Ethernet y MPLS.
- Quién define Internet: la IETF y los RFC. Cómo leer un RFC.
Sesión B — Programación con sockets en C: TCP y UDP
Lecturas:
- Kurose y Ross, secs. 2.1 (Principles of Network Applications) y 2.6.1–2.6.2 (Socket Programming with UDP and TCP).
- Beej, Guide to Network Programming, secciones What is a Socket?, IP Addresses, structs, and Data Munging, System Calls or Bust y Client-Server Background.
Temas:
- Procesos, puertos y sockets. Servicios de transporte: confiabilidad, throughput, tiempo y seguridad.
- Servidor TCP:
socket,bind,listen,accept. Cliente:getaddrinfo,socket,connect. Después:sendyrecv. - UDP:
socket,bind,sendto,recvfrom. No hay conexión ni garantías. - Orden de bytes:
htonsyntohs. - Error común: asumir que un
recvdevuelve un mensaje completo. TCP entrega un stream de bytes, sin fronteras entre mensajes. Hay que leer en un bucle y definir un formato. - Error común: no revisar lo que devuelven las llamadas.
sendpuede escribir menos bytes de los pedidos. - Atender a varios clientes:
poll, y la comparación con un proceso o un hilo por cliente. SO_REUSEADDRy por qué apareceAddress already in use.
Laboratorio — Un cliente y un servidor en C
Trabajen dentro del contenedor, en code/week-02/.
- Compilar y correr el servidor y el cliente del servicio de conteo por TCP de la clase. Capturar el tráfico con
tcpdumpy encontrar el handshake. - Cambiar el cliente para que envíe 100 KB y comprobar, con un contador, cuántas veces devuelve datos
recv. - Definir un formato con largo al inicio para atender varios pedidos por conexión, y corregir el programa para que reconstruya cada uno.
- Reescribir el servicio de conteo con UDP. Enviar mensajes con el servidor apagado y explicar por qué el cliente no se entera, salvo que use
connect. - Armar una sala de texto que atienda a dos clientes a la vez con
poll. - Avanzar con P0.
Preguntas de repaso
- Den un ejemplo de una función que, según el principio end-to-end, no debería hacerse solo en la red. ¿Cuándo sí conviene una ayuda en la red?
- ¿Qué diferencia a un circuito virtual de ATM de un datagrama de IP? ¿Qué estado guarda cada router en cada caso?
- ¿Qué devuelve
recvcuando el otro extremo cierra la conexión? ¿Y cuando hay un error? - Un cliente envía 3000 bytes con una sola llamada a
send. ¿Cuántas llamadas arecvpuede necesitar el servidor? Justifiquen. - ¿Por qué un servidor UDP no llama a
accept? ¿Cómo sabe a quién responder?
Código de la semana
Compilar y probar dentro del contenedor del curso con make y make test.
Ejemplos (24)Pruebas (3)Laboratorio (7)Compilación (1)
Ejemplos
Los programas que usan los apuntes y la clase.
// Muestra cómo guarda los enteros esta máquina y qué hace htons y htonl.
#define _POSIX_C_SOURCE 200809L
#include <arpa/inet.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void print_bytes(const char *label, const void *value, size_t n) {
const unsigned char *bytes = value;
printf("%-22s", label);
for (size_t i = 0; i < n; i++) {
printf(" %02x", bytes[i]);
}
printf("\n");
}
// Parece una cabecera de 3 bytes, pero el compilador puede agregar relleno.
struct wrong_header {
uint8_t type;
uint16_t port;
};
int main(void) {
uint16_t port = 9002; // 0x232a
uint16_t net_port = htons(port);
uint32_t addr = 0x0A020110; // 10.2.1.16
uint32_t net_addr = htonl(addr);
print_bytes("9002 en memoria:", &port, sizeof port);
print_bytes("htons(9002):", &net_port, sizeof net_port);
print_bytes("0x0a020110 en memoria:", &addr, sizeof addr);
print_bytes("htonl(0x0a020110):", &net_addr, sizeof net_addr);
// Copiar una struct a la red también copia el relleno que puso el compilador.
printf("sizeof(struct wrong_header) = %zu, aunque sus campos suman 3\n",
sizeof(struct wrong_header));
// Lo correcto: definir el formato byte a byte y armarlo campo por campo.
unsigned char wire[3];
wire[0] = 7;
memcpy(wire + 1, &net_port, sizeof net_port);
print_bytes("cabecera en 3 bytes:", wire, sizeof wire);
return EXIT_SUCCESS;
}// Recibe de un cliente y cuenta cuánto devuelve cada recv.
// chunk_recv [-v] [-x tamaño] <puerto>
// -v imprime lo que devuelve cada llamada
// -x tamaño usa recv_all para leer registros de tamaño fijo
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUF_SIZE (1024 * 1024)
int main(int argc, char *argv[]) {
bool verbose = false;
long exact = 0;
int opt;
while ((opt = getopt(argc, argv, "vx:")) != -1) {
if (opt == 'v') {
verbose = true;
} else if (opt == 'x') {
exact = strtol(optarg, NULL, 10);
} else {
return EXIT_FAILURE;
}
}
if (argc - optind != 1 || exact < 0 || exact > BUF_SIZE) {
fprintf(stderr, "uso: %s [-v] [-x tamaño] <puerto>\n", argv[0]);
return EXIT_FAILURE;
}
int listener = tcp_listen(parse_port(argv[optind]));
if (listener == -1 || print_bound_port(listener) == -1) {
return EXIT_FAILURE;
}
int conn = accept(listener, NULL, NULL);
if (conn == -1) {
perror("accept");
return EXIT_FAILURE;
}
char *buf = malloc(BUF_SIZE);
if (buf == NULL) {
perror("malloc");
return EXIT_FAILURE;
}
unsigned long long total = 0;
unsigned long long bad = 0;
long calls = 0;
long smallest = 0;
long largest = 0;
for (;;) {
ssize_t n = exact > 0 ? recv_all(conn, buf, (size_t)exact)
: recv(conn, buf, BUF_SIZE, 0);
if (n == -1) {
if (errno == EINTR) {
continue;
}
perror("recv");
return EXIT_FAILURE;
}
if (n == 0) {
break;
}
calls++;
if (calls == 1 || n < smallest) {
smallest = n;
}
if (n > largest) {
largest = n;
}
for (ssize_t i = 0; i < n; i++) {
if ((unsigned char)buf[i] != (total + (unsigned long long)i) % 251) {
bad++;
}
}
total += (unsigned long long)n;
if (verbose) {
printf("%s %ld: %zd bytes\n", exact > 0 ? "registro" : "recv", calls, n);
}
}
printf("%s: %ld llamadas, %llu bytes, menor %ld, mayor %ld\n",
exact > 0 ? "recv_all" : "recv", calls, total, smallest, largest);
printf("bytes fuera de lugar: %llu\n", bad);
free(buf);
close_checked(conn);
close_checked(listener);
return EXIT_SUCCESS;
}// Envía por TCP varios pedazos de datos conocidos, para estudiar el stream.
// chunk_send [-g pausa_ms] <ip> <puerto> <tamaño> <cantidad>
// Usa una dirección numérica, sin getaddrinfo, para poder compilarse de forma
// estática y correr en los nodos de la topología.
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
long gap_ms = 0;
int opt;
while ((opt = getopt(argc, argv, "g:")) != -1) {
if (opt != 'g') {
return EXIT_FAILURE;
}
gap_ms = strtol(optarg, NULL, 10);
}
if (argc - optind != 4) {
fprintf(stderr, "uso: %s [-g pausa_ms] <ip> <puerto> <tamaño> <cantidad>\n", argv[0]);
return EXIT_FAILURE;
}
long size = strtol(argv[optind + 2], NULL, 10);
long chunks = strtol(argv[optind + 3], NULL, 10);
if (size <= 0 || chunks <= 0) {
fprintf(stderr, "tamaño y cantidad deben ser positivos\n");
return EXIT_FAILURE;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(parse_port(argv[optind + 1]));
if (inet_pton(AF_INET, argv[optind], &addr.sin_addr) != 1) {
fprintf(stderr, "dirección no válida: %s\n", argv[optind]);
return EXIT_FAILURE;
}
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
return EXIT_FAILURE;
}
if (connect(fd, (struct sockaddr *)&addr, sizeof addr) == -1) {
perror("connect");
return EXIT_FAILURE;
}
char *buf = malloc((size_t)size);
if (buf == NULL) {
perror("malloc");
return EXIT_FAILURE;
}
// El byte en la posición p del stream vale p % 251. Así el receptor puede
// comprobar que nada se perdió, se repitió ni cambió de lugar.
unsigned long long offset = 0;
for (long i = 0; i < chunks; i++) {
for (long j = 0; j < size; j++) {
buf[j] = (char)(offset++ % 251);
}
if (send_all(fd, buf, (size_t)size) == -1) {
return EXIT_FAILURE;
}
if (gap_ms > 0) {
struct timespec pause = {gap_ms / 1000, (gap_ms % 1000) * 1000000L};
if (nanosleep(&pause, NULL) == -1) {
perror("nanosleep");
}
}
}
free(buf);
printf("enviados: %ld llamadas a send, %llu bytes\n", chunks, offset);
close_checked(fd);
return EXIT_SUCCESS;
}# Dos hosts unidos por dos caminos: r1-r2-r4 (preferido) y r1-r3-r4.
# Se usa para ver qué estado guarda cada nodo y qué sobrevive a una caída.
name: w02-fate
mgmt:
network: redes-w02
ipv4-subnet: 172.90.2.0/24
ipv6-subnet: 3fff:172:90:2::/64
topology:
defaults:
kind: linux
image: redes-router
nodes:
h1:
binds: [build/static:/lab:ro]
exec:
- ip address add 10.2.1.10/24 dev eth1
- ip route replace default via 10.2.1.1 dev eth1
h2:
binds: [build/static:/lab:ro]
exec:
- ip address add 10.2.4.10/24 dev eth1
- ip route replace default via 10.2.4.1 dev eth1
r1:
binds: [r1/frr.conf:/etc/frr/frr.conf:ro]
r2:
binds: [r2/frr.conf:/etc/frr/frr.conf:ro]
r3:
binds: [r3/frr.conf:/etc/frr/frr.conf:ro]
r4:
binds: [r4/frr.conf:/etc/frr/frr.conf:ro]
# MTU de 1500 bytes, como en Ethernet; Containerlab usa 9500 si no se indica.
links:
- {endpoints: ["h1:eth1", "r1:eth1"], mtu: 1500}
- {endpoints: ["r1:eth2", "r2:eth1"], mtu: 1500}
- {endpoints: ["r1:eth3", "r3:eth1"], mtu: 1500}
- {endpoints: ["r2:eth2", "r4:eth1"], mtu: 1500}
- {endpoints: ["r3:eth2", "r4:eth2"], mtu: 1500}
- {endpoints: ["r4:eth3", "h2:eth1"], mtu: 1500}// Un intermediario con una falla: copia los datos de un cliente hacia un
// servidor e intercambia un par de bytes cada cierta cantidad de bytes. Imita a
// un gateway con la memoria dañada, como el del artículo de Saltzer, Reed y Clark.
// Cada lado usa su propia conexión TCP, con su checksum y sus retransmisiones.
// flaky_relay <puerto> <ip_destino> <puerto_destino> <cada_n_bytes>
// Con cada_n_bytes = 0, no daña nada.
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 5) {
fprintf(stderr, "uso: %s <puerto> <ip_destino> <puerto_destino> <cada_n_bytes>\n",
argv[0]);
return EXIT_FAILURE;
}
long every = strtol(argv[4], NULL, 10);
if (every < 0) {
fprintf(stderr, "cada_n_bytes no puede ser negativo\n");
return EXIT_FAILURE;
}
int listener = tcp_listen(parse_port(argv[1]));
if (listener == -1 || print_bound_port(listener) == -1) {
return EXIT_FAILURE;
}
int in = accept(listener, NULL, NULL);
if (in == -1) {
perror("accept");
return EXIT_FAILURE;
}
struct sockaddr_in dst;
memset(&dst, 0, sizeof dst);
dst.sin_family = AF_INET;
dst.sin_port = htons(parse_port(argv[3]));
if (inet_pton(AF_INET, argv[2], &dst.sin_addr) != 1) {
fprintf(stderr, "dirección no válida: %s\n", argv[2]);
return EXIT_FAILURE;
}
int out = socket(AF_INET, SOCK_STREAM, 0);
if (out == -1) {
perror("socket");
return EXIT_FAILURE;
}
if (connect(out, (struct sockaddr *)&dst, sizeof dst) == -1) {
perror("connect");
return EXIT_FAILURE;
}
char buf[8192];
unsigned long long total = 0, damaged = 0;
long countdown = every;
for (;;) {
ssize_t n = recv(in, buf, sizeof buf, 0);
if (n == -1) {
if (errno == EINTR) {
continue;
}
perror("recv");
return EXIT_FAILURE;
}
if (n == 0) {
break;
}
// Aquí los datos no están protegidos por ningún checksum de TCP.
for (ssize_t i = 0; every > 0 && i + 1 < n; i++) {
if (--countdown == 0) {
char tmp = buf[i];
buf[i] = buf[i + 1];
buf[i + 1] = tmp;
countdown = every;
damaged++;
}
}
if (send_all(out, buf, (size_t)n) == -1) {
return EXIT_FAILURE;
}
total += (unsigned long long)n;
}
printf("copiados %llu bytes, %llu pares intercambiados\n", total, damaged);
close_checked(out);
close_checked(in);
close_checked(listener);
return EXIT_SUCCESS;
}// Funciones de apoyo para sockets TCP.
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int send_all(int fd, const void *buf, size_t n) {
const char *p = buf;
size_t left = n;
while (left > 0) {
// MSG_NOSIGNAL: si el otro extremo cerró, recibimos EPIPE y no la señal
// SIGPIPE, que por defecto mataría al proceso.
ssize_t sent = send(fd, p, left, MSG_NOSIGNAL);
if (sent == -1) {
if (errno == EINTR) {
continue;
}
perror("send");
return -1;
}
p += sent;
left -= (size_t)sent;
}
return 0;
}
ssize_t recv_all(int fd, void *buf, size_t n) {
char *p = buf;
size_t got = 0;
while (got < n) {
ssize_t r = recv(fd, p + got, n - got, 0);
if (r == -1) {
if (errno == EINTR) {
continue;
}
perror("recv");
return -1;
}
if (r == 0) {
break; // el otro extremo cerró
}
got += (size_t)r;
}
return (ssize_t)got;
}
int tcp_listen(uint16_t port) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
return -1;
}
int on = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) == -1) {
perror("setsockopt");
close_checked(fd);
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(fd, (struct sockaddr *)&addr, sizeof addr) == -1) {
perror("bind");
close_checked(fd);
return -1;
}
if (listen(fd, 16) == -1) {
perror("listen");
close_checked(fd);
return -1;
}
return fd;
}
int print_bound_port(int fd) {
struct sockaddr_in addr;
socklen_t len = sizeof addr;
if (getsockname(fd, (struct sockaddr *)&addr, &len) == -1) {
perror("getsockname");
return -1;
}
printf("escuchando en el puerto %u\n", ntohs(addr.sin_port));
fflush(stdout);
return 0;
}
uint16_t parse_port(const char *text) {
char *end;
errno = 0;
long value = strtol(text, &end, 10);
if (errno != 0 || *end != '\0' || end == text || value < 0 || value > 65535) {
fprintf(stderr, "puerto no válido: %s\n", text);
exit(EXIT_FAILURE);
}
return (uint16_t)value;
}
void close_checked(int fd) {
if (close(fd) == -1) {
perror("close");
}
}// Funciones de apoyo para sockets TCP que usan varios programas de la semana.
#ifndef IO_UTIL_H
#define IO_UTIL_H
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
// Envía los n bytes aunque send escriba menos. Devuelve 0, o -1 si hay error.
int send_all(int fd, const void *buf, size_t n);
// Lee hasta completar n bytes. Devuelve n, un número menor si el otro extremo
// cerró antes de completarlos, o -1 si hay error.
ssize_t recv_all(int fd, void *buf, size_t n);
// socket + SO_REUSEADDR + bind + listen en todas las interfaces. Con el puerto
// 0, el sistema elige uno libre. Devuelve el descriptor, o -1 si hay error.
int tcp_listen(uint16_t port);
// Imprime "escuchando en el puerto N": útil cuando el puerto pedido fue 0.
int print_bound_port(int fd);
// Convierte un argumento a puerto (0-65535). Termina el programa si no es válido.
uint16_t parse_port(const char *text);
// close que informa el error.
void close_checked(int fd);
#endif"""Apoyo para las pruebas: lanza servidores y recuerda sus procesos.
El contenedor del curso comparte la red y los procesos con la máquina. Por eso
las pruebas piden el puerto 0, leen el que eligió el sistema y solo matan a los
procesos que ellas mismas lanzaron.
"""
import re
import socket
import subprocess
import threading
import time
import unittest
from pathlib import Path
HERE = Path(__file__).resolve().parent
BUILD = HERE / "build"
def free_port(kind=socket.SOCK_STREAM):
"""Un puerto libre en este momento, para los programas que no aceptan 0."""
with socket.socket(socket.AF_INET, kind) as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
def connect(port, timeout=5.0):
s = socket.create_connection(("127.0.0.1", port), timeout=timeout)
s.settimeout(timeout)
return s
def recv_line(sock):
data = b""
while not data.endswith(b"\n"):
chunk = sock.recv(1)
if not chunk:
break
data += chunk
return data.decode()
def recv_until_close(sock):
data = b""
while True:
chunk = sock.recv(65536)
if not chunk:
return data
data += chunk
class Server:
"""Un servidor en segundo plano. Reúne su salida para poder revisarla."""
def __init__(self, path, *args):
self.process = subprocess.Popen(
[str(path), *map(str, args)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
)
line = self.process.stdout.readline()
match = re.search(r"puerto (\d+)", line)
if match is None:
self.process.kill()
self.process.wait()
self.process.stdout.close()
raise AssertionError(f"{path} no dijo su puerto: {line!r}")
self.port = int(match.group(1))
self.lines = []
self.reader = threading.Thread(target=self._read, daemon=True)
self.reader.start()
def _read(self):
for line in self.process.stdout:
self.lines.append(line.rstrip("\n"))
def alive(self):
return self.process.poll() is None
def wait_output(self, pattern, timeout=5.0):
"""Espera una línea de salida que cumpla pattern y devuelve su match."""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
for line in list(self.lines):
match = re.search(pattern, line)
if match:
return match
time.sleep(0.02)
raise AssertionError(f"no apareció {pattern!r}; salida: {self.lines}")
def wait(self, timeout=10.0):
code = self.process.wait(timeout=timeout)
self.reader.join(timeout=2)
return code
def stop(self):
if self.process.poll() is None:
self.process.kill()
self.process.wait()
self.reader.join(timeout=2)
self.process.stdout.close()
class NetTestCase(unittest.TestCase):
"""Detiene al terminar cada prueba a los servidores que esta lanzó."""
def server(self, name, *args):
path = name if isinstance(name, Path) else BUILD / name
server = Server(path, *args)
self.addCleanup(server.stop)
return server
def connect(self, port):
sock = connect(port)
self.addCleanup(sock.close)
return sock// Muestra por qué aparece "Address already in use". Atiende a un cliente, cierra
// primero (y por eso la conexión queda en TIME_WAIT de este lado) y termina.
// reuse_demo [-r] <puerto> -r activa SO_REUSEADDR
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
bool reuse = false;
int opt;
while ((opt = getopt(argc, argv, "r")) != -1) {
if (opt != 'r') {
return EXIT_FAILURE;
}
reuse = true;
}
if (argc - optind != 1) {
fprintf(stderr, "uso: %s [-r] <puerto>\n", argv[0]);
return EXIT_FAILURE;
}
int listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == -1) {
perror("socket");
return EXIT_FAILURE;
}
if (reuse) {
int on = 1;
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) == -1) {
perror("setsockopt");
return EXIT_FAILURE;
}
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(parse_port(argv[optind]));
if (bind(listener, (struct sockaddr *)&addr, sizeof addr) == -1) {
perror("bind");
return EXIT_FAILURE;
}
if (listen(listener, 1) == -1) {
perror("listen");
return EXIT_FAILURE;
}
if (print_bound_port(listener) == -1) {
return EXIT_FAILURE;
}
int conn = accept(listener, NULL, NULL);
if (conn == -1) {
perror("accept");
return EXIT_FAILURE;
}
if (send_all(conn, "chau\n", 5) == -1) {
return EXIT_FAILURE;
}
close_checked(conn);
close_checked(listener);
return EXIT_SUCCESS;
}// Recibe datagramas numerados y cuenta perdidos, desordenados y repetidos.
// seq_recv <puerto> <cantidad_esperada> <espera_ms>
// Termina cuando pasan espera_ms sin que llegue nada, después del primero.
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "uso: %s <puerto> <cantidad_esperada> <espera_ms>\n", argv[0]);
return EXIT_FAILURE;
}
long expected = strtol(argv[2], NULL, 10);
long wait_ms = strtol(argv[3], NULL, 10);
if (expected <= 0 || wait_ms <= 0) {
fprintf(stderr, "valores fuera de rango\n");
return EXIT_FAILURE;
}
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
perror("socket");
return EXIT_FAILURE;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(parse_port(argv[1]));
if (bind(fd, (struct sockaddr *)&addr, sizeof addr) == -1) {
perror("bind");
return EXIT_FAILURE;
}
if (print_bound_port(fd) == -1) {
return EXIT_FAILURE;
}
bool *seen = calloc((size_t)expected, sizeof *seen);
if (seen == NULL) {
perror("calloc");
return EXIT_FAILURE;
}
long received = 0, duplicates = 0, reordered = 0, other_size = 0;
long first_size = 0;
long highest = -1;
for (;;) {
char packet[2048];
ssize_t n = recv(fd, packet, sizeof packet, 0);
if (n == -1) {
if (errno == EINTR) {
continue;
}
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
perror("recv");
return EXIT_FAILURE;
}
if (received == 0 && duplicates == 0) {
// Ya empezó el envío: desde aquí, un silencio largo significa el fin.
struct timeval limit = {wait_ms / 1000, (wait_ms % 1000) * 1000};
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &limit, sizeof limit) == -1) {
perror("setsockopt");
return EXIT_FAILURE;
}
first_size = n;
}
if (n < (ssize_t)sizeof(uint32_t)) {
continue;
}
if (n != first_size) {
other_size++;
}
uint32_t net_seq;
memcpy(&net_seq, packet, sizeof net_seq);
long seq = (long)ntohl(net_seq);
if (seq >= expected) {
continue;
}
if (seen[seq]) {
duplicates++;
continue;
}
seen[seq] = true;
received++;
if (seq < highest) {
reordered++;
} else {
highest = seq;
}
}
printf("recibidos: %ld de %ld\n", received, expected);
printf("perdidos: %ld\n", expected - received);
printf("fuera de orden: %ld\n", reordered);
printf("repetidos: %ld\n", duplicates);
printf("tamaño de cada recv: %ld bytes, %ld distintos\n", first_size, other_size);
free(seen);
close_checked(fd);
return EXIT_SUCCESS;
}// Envía datagramas UDP numerados.
// seq_send <ip> <puerto> <cantidad> <tamaño> <pausa_us>
// Formato: número de secuencia de 4 bytes en orden de red, y relleno.
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#define MAX_DATAGRAM 1400
int main(int argc, char *argv[]) {
if (argc != 6) {
fprintf(stderr, "uso: %s <ip> <puerto> <cantidad> <tamaño> <pausa_us>\n", argv[0]);
return EXIT_FAILURE;
}
long count = strtol(argv[3], NULL, 10);
long size = strtol(argv[4], NULL, 10);
long gap_us = strtol(argv[5], NULL, 10);
if (count <= 0 || size < 4 || size > MAX_DATAGRAM || gap_us < 0) {
fprintf(stderr, "valores fuera de rango\n");
return EXIT_FAILURE;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(parse_port(argv[2]));
if (inet_pton(AF_INET, argv[1], &addr.sin_addr) != 1) {
fprintf(stderr, "dirección no válida: %s\n", argv[1]);
return EXIT_FAILURE;
}
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
perror("socket");
return EXIT_FAILURE;
}
char packet[MAX_DATAGRAM];
memset(packet, 'x', sizeof packet);
for (long i = 0; i < count; i++) {
uint32_t seq = htonl((uint32_t)i);
memcpy(packet, &seq, sizeof seq);
if (sendto(fd, packet, (size_t)size, 0,
(struct sockaddr *)&addr, sizeof addr) == -1) {
perror("sendto");
return EXIT_FAILURE;
}
if (gap_us > 0) {
struct timespec pause = {gap_us / 1000000, (gap_us % 1000000) * 1000L};
if (nanosleep(&pause, NULL) == -1) {
perror("nanosleep");
}
}
}
printf("enviados: %ld datagramas de %ld bytes\n", count, size);
close_checked(fd);
return EXIT_SUCCESS;
}// Muestra que send puede escribir menos bytes de los pedidos. Conecta un socket
// a un receptor que nunca lee, hasta que se llenan los buffers.
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#define REQUEST (8 * 1024 * 1024)
// Una conexión por loopback; el otro extremo queda en la cola de accept.
static int connect_to_self(int listener) {
struct sockaddr_in addr;
socklen_t len = sizeof addr;
if (getsockname(listener, (struct sockaddr *)&addr, &len) == -1) {
perror("getsockname");
return -1;
}
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
return -1;
}
if (connect(fd, (struct sockaddr *)&addr, len) == -1) {
perror("connect");
close_checked(fd);
return -1;
}
return fd;
}
static void report(const char *mode, ssize_t sent) {
if (sent == -1) {
printf("%s: send devuelve -1, errno = %s\n", mode, strerror(errno));
} else {
printf("%s: se pidieron %d bytes y send devuelve %zd\n", mode, REQUEST, sent);
}
}
int main(void) {
char *buf = calloc(REQUEST, 1);
if (buf == NULL) {
perror("calloc");
return EXIT_FAILURE;
}
int listener = tcp_listen(0);
if (listener == -1) {
return EXIT_FAILURE;
}
// Caso 1: socket no bloqueante. send copia lo que cabe y regresa.
int fd = connect_to_self(listener);
if (fd == -1) {
return EXIT_FAILURE;
}
int flags = fcntl(fd, F_GETFL);
if (flags == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl");
return EXIT_FAILURE;
}
report("no bloqueante, 1.ª llamada", send(fd, buf, REQUEST, MSG_NOSIGNAL));
report("no bloqueante, 2.ª llamada", send(fd, buf, REQUEST, MSG_NOSIGNAL));
close_checked(fd);
// Caso 2: socket bloqueante con plazo. Se agota el plazo a la mitad.
fd = connect_to_self(listener);
if (fd == -1) {
return EXIT_FAILURE;
}
struct timeval limit = {.tv_sec = 0, .tv_usec = 200000};
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &limit, sizeof limit) == -1) {
perror("setsockopt");
return EXIT_FAILURE;
}
report("bloqueante con SO_SNDTIMEO", send(fd, buf, REQUEST, MSG_NOSIGNAL));
close_checked(fd);
close_checked(listener);
free(buf);
return EXIT_SUCCESS;
}// Marcador compartido: un total que varios clientes modifican a la vez, con un
// solo hilo y poll. El protocolo es de líneas de texto:
// SUMA <n>\n suma n al total y responde "total <t>\n"
// TOTAL\n responde "total <t>\n"
// otra cosa responde "error\n"
//
// tally_server <puerto>
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define MAX_CLIENTS 64
#define MAX_LINE 128
// Lo que el servidor recuerda de cada conexión. Con un solo hilo no hay una
// pila por cliente: la línea a medio llegar tiene que guardarse aquí.
struct client {
char line[MAX_LINE];
size_t used;
};
static long total = 0;
// Procesa una línea completa y deja la respuesta en reply.
static void run_command(const char *line, char *reply, size_t size) {
long amount;
char extra;
if (sscanf(line, "SUMA %ld %c", &amount, &extra) == 1) {
total += amount;
snprintf(reply, size, "total %ld\n", total);
} else if (strcmp(line, "TOTAL") == 0) {
snprintf(reply, size, "total %ld\n", total);
} else {
snprintf(reply, size, "error\n");
}
}
// Envía sin bloquear. Si el cliente no lee y su buffer se llenó, se le corta:
// un cliente lento no puede detener a los demás. Otra política sería guardar
// la respuesta y esperar POLLOUT. Devuelve 0, o -1 si hay que cerrar.
static int reply_to(int fd, const char *text) {
size_t len = strlen(text);
ssize_t sent = send(fd, text, len, MSG_DONTWAIT | MSG_NOSIGNAL);
if (sent == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("send");
}
return -1;
}
return (size_t)sent == len ? 0 : -1;
}
// Agrega lo recibido al buffer del cliente y atiende cada línea completa.
// Devuelve 0, o -1 si hay que cerrar la conexión.
static int handle_bytes(int fd, struct client *c, const char *data, size_t n) {
for (size_t i = 0; i < n; i++) {
if (data[i] != '\n') {
if (c->used == MAX_LINE - 1) {
return -1; // línea demasiado larga: el buffer tiene un límite
}
c->line[c->used++] = data[i];
continue;
}
c->line[c->used] = '\0';
c->used = 0;
char reply[64];
run_command(c->line, reply, sizeof reply);
if (reply_to(fd, reply) == -1) {
return -1;
}
}
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "uso: %s <puerto>\n", argv[0]);
return EXIT_FAILURE;
}
int listener = tcp_listen(parse_port(argv[1]));
if (listener == -1 || print_bound_port(listener) == -1) {
return EXIT_FAILURE;
}
// fds[0] es el socket que escucha; fds[i] y clients[i] van juntos.
struct pollfd fds[MAX_CLIENTS + 1];
struct client clients[MAX_CLIENTS + 1];
nfds_t count = 1;
fds[0].fd = listener;
fds[0].events = POLLIN;
for (;;) {
// Dormir hasta que algún socket tenga algo. Es el único punto de espera.
if (poll(fds, count, -1) == -1) {
if (errno == EINTR) {
continue;
}
perror("poll");
return EXIT_FAILURE;
}
// Recorremos de atrás hacia adelante: quitar un cliente mueve al último
// a su lugar, y ese ya fue revisado.
for (nfds_t i = count; i-- > 1;) {
if ((fds[i].revents & (POLLIN | POLLERR | POLLHUP)) == 0) {
continue;
}
char buf[512];
ssize_t n = recv(fds[i].fd, buf, sizeof buf, 0);
if (n == -1 && errno == EINTR) {
continue;
}
if (n == -1) {
perror("recv");
}
if (n <= 0 || handle_bytes(fds[i].fd, &clients[i], buf, (size_t)n) == -1) {
close_checked(fds[i].fd);
count--;
fds[i] = fds[count];
clients[i] = clients[count];
}
}
// poll dijo que hay una conexión esperando en la cola de accept.
if (fds[0].revents & POLLIN) {
int conn = accept(listener, NULL, NULL);
if (conn == -1) {
perror("accept");
} else if (count == MAX_CLIENTS + 1) {
fprintf(stderr, "demasiados clientes\n");
close_checked(conn);
} else {
fds[count].fd = conn;
fds[count].events = POLLIN;
fds[count].revents = 0;
clients[count].used = 0;
count++;
}
}
}
}// Cliente UDP de hora.
// time_client [-c] [-w espera_ms] <host> <puerto>
// -c llama a connect antes de enviar
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <errno.h>
#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
bool use_connect = false;
long wait_ms = 1000;
int opt;
while ((opt = getopt(argc, argv, "cw:")) != -1) {
if (opt == 'c') {
use_connect = true;
} else if (opt == 'w') {
wait_ms = strtol(optarg, NULL, 10);
} else {
return EXIT_FAILURE;
}
}
if (argc - optind != 2 || wait_ms <= 0) {
fprintf(stderr, "uso: %s [-c] [-w espera_ms] <host> <puerto>\n", argv[0]);
return EXIT_FAILURE;
}
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
struct addrinfo *server;
int err = getaddrinfo(argv[optind], argv[optind + 1], &hints, &server);
if (err != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
return EXIT_FAILURE;
}
int fd = socket(server->ai_family, server->ai_socktype, server->ai_protocol);
if (fd == -1) {
perror("socket");
return EXIT_FAILURE;
}
// UDP no avisa si algo se pierde: sin un plazo, recv podría esperar para siempre.
struct timeval limit = {.tv_sec = wait_ms / 1000, .tv_usec = (wait_ms % 1000) * 1000};
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &limit, sizeof limit) == -1) {
perror("setsockopt");
return EXIT_FAILURE;
}
const char request[] = "hora?";
ssize_t sent;
if (use_connect) {
// connect en UDP no envía nada: fija el destino y filtra lo que llega.
if (connect(fd, server->ai_addr, server->ai_addrlen) == -1) {
perror("connect");
return EXIT_FAILURE;
}
sent = send(fd, request, sizeof request - 1, 0);
} else {
sent = sendto(fd, request, sizeof request - 1, 0,
server->ai_addr, server->ai_addrlen);
}
if (sent == -1) {
perror("send");
return EXIT_FAILURE;
}
printf("enviados %zd bytes\n", sent);
fflush(stdout);
char reply[128];
ssize_t n = recv(fd, reply, sizeof reply - 1, 0);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("sin respuesta después de %ld ms\n", wait_ms);
freeaddrinfo(server);
close_checked(fd);
return 2;
}
perror("recv");
return EXIT_FAILURE;
}
reply[n] = '\0';
printf("hora: %s", reply);
freeaddrinfo(server);
close_checked(fd);
return EXIT_SUCCESS;
}// Servidor UDP de hora: a cada datagrama responde con la hora del servidor.
// time_server <puerto>
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "uso: %s <puerto>\n", argv[0]);
return EXIT_FAILURE;
}
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
perror("socket");
return EXIT_FAILURE;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(parse_port(argv[1]));
if (bind(fd, (struct sockaddr *)&addr, sizeof addr) == -1) {
perror("bind");
return EXIT_FAILURE;
}
if (print_bound_port(fd) == -1) {
return EXIT_FAILURE;
}
// No hay listen ni accept: un solo socket recibe de todos los clientes.
for (;;) {
char request[512];
struct sockaddr_in peer;
socklen_t peer_len = sizeof peer;
ssize_t n = recvfrom(fd, request, sizeof request, 0,
(struct sockaddr *)&peer, &peer_len);
if (n == -1) {
if (errno != EINTR) {
perror("recvfrom");
}
continue;
}
char ip[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &peer.sin_addr, ip, sizeof ip) == NULL) {
perror("inet_ntop");
continue;
}
printf("datagrama de %zd bytes desde %s:%u\n", n, ip, ntohs(peer.sin_port));
fflush(stdout);
time_t now = time(NULL);
struct tm utc;
char reply[64];
if (now == (time_t)-1 || gmtime_r(&now, &utc) == NULL ||
strftime(reply, sizeof reply, "%H:%M:%S UTC\n", &utc) == 0) {
fprintf(stderr, "no se pudo obtener la hora\n");
continue;
}
// Cada datagrama lleva su destino: la dirección de quien preguntó.
if (sendto(fd, reply, strlen(reply), 0,
(struct sockaddr *)&peer, peer_len) == -1) {
perror("sendto");
}
}
}// Cliente TCP de conteo: envía la entrada estándar y muestra la respuesta.
// wc_client <host> <puerto> < archivo
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
// Prueba cada dirección que devuelve getaddrinfo hasta que una se conecte.
static int connect_to(const char *host, const char *port) {
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // IPv4 o IPv6
hints.ai_socktype = SOCK_STREAM; // TCP
struct addrinfo *results;
int err = getaddrinfo(host, port, &hints, &results);
if (err != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
return -1;
}
int fd = -1;
for (struct addrinfo *ai = results; ai != NULL; ai = ai->ai_next) {
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (fd == -1) {
perror("socket");
continue;
}
if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) {
break;
}
perror("connect");
close_checked(fd);
fd = -1;
}
freeaddrinfo(results);
return fd;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "uso: %s <host> <puerto>\n", argv[0]);
return EXIT_FAILURE;
}
int fd = connect_to(argv[1], argv[2]);
if (fd == -1) {
return EXIT_FAILURE;
}
char buf[4096];
size_t n;
while ((n = fread(buf, 1, sizeof buf, stdin)) > 0) {
if (send_all(fd, buf, n) == -1) {
close_checked(fd);
return EXIT_FAILURE;
}
}
if (ferror(stdin)) {
fprintf(stderr, "error al leer la entrada estándar\n");
close_checked(fd);
return EXIT_FAILURE;
}
// Avisamos que ya no enviaremos más; el socket sigue abierto para recibir.
if (shutdown(fd, SHUT_WR) == -1) {
perror("shutdown");
close_checked(fd);
return EXIT_FAILURE;
}
for (;;) {
ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r == -1) {
perror("recv");
close_checked(fd);
return EXIT_FAILURE;
}
if (r == 0) {
break;
}
if (fwrite(buf, 1, (size_t)r, stdout) != (size_t)r) {
fprintf(stderr, "error al escribir la salida\n");
close_checked(fd);
return EXIT_FAILURE;
}
}
close_checked(fd);
return EXIT_SUCCESS;
}// Servidor TCP de conteo con un proceso por cliente.
// wc_fork_server <puerto>
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include "wc_service.h"
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "uso: %s <puerto>\n", argv[0]);
return EXIT_FAILURE;
}
// Ignorar SIGCHLD: el kernel recoge a los hijos y no quedan zombis.
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
if (sigemptyset(&sa.sa_mask) == -1 || sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
return EXIT_FAILURE;
}
int listener = tcp_listen(parse_port(argv[1]));
if (listener == -1 || print_bound_port(listener) == -1) {
return EXIT_FAILURE;
}
for (;;) {
int conn = accept(listener, NULL, NULL);
if (conn == -1) {
perror("accept");
continue;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
close_checked(conn);
continue;
}
if (pid == 0) {
// Hijo: solo necesita la conexión.
close_checked(listener);
int rc = wc_serve_client(conn);
close_checked(conn);
_exit(rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
// Padre: solo necesita el socket que escucha.
close_checked(conn);
}
}// Servidor TCP de conteo: atiende a un cliente a la vez.
// wc_server <puerto> (con el puerto 0, el sistema elige uno libre)
#define _POSIX_C_SOURCE 200809L
#include "io_util.h"
#include "wc_service.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "uso: %s <puerto>\n", argv[0]);
return EXIT_FAILURE;
}
uint16_t port = parse_port(argv[1]);
// 1. Un socket TCP sobre IPv4.
int listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == -1) {
perror("socket");
return EXIT_FAILURE;
}
// 2. Permitir reusar el puerto aunque queden conexiones en TIME_WAIT.
int on = 1;
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) == -1) {
perror("setsockopt");
return EXIT_FAILURE;
}
// 3. Asociarlo a un puerto, en todas las interfaces. Orden de red.
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(listener, (struct sockaddr *)&addr, sizeof addr) == -1) {
perror("bind");
return EXIT_FAILURE;
}
// 4. Marcarlo como pasivo: desde aquí el kernel completa handshakes.
if (listen(listener, 16) == -1) {
perror("listen");
return EXIT_FAILURE;
}
if (print_bound_port(listener) == -1) {
return EXIT_FAILURE;
}
// 5. Un socket nuevo por cada conexión; listener sigue escuchando.
for (;;) {
struct sockaddr_in peer;
socklen_t peer_len = sizeof peer;
int conn = accept(listener, (struct sockaddr *)&peer, &peer_len);
if (conn == -1) {
perror("accept");
continue;
}
char ip[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &peer.sin_addr, ip, sizeof ip) == NULL) {
perror("inet_ntop");
close_checked(conn);
continue;
}
printf("cliente %s:%u\n", ip, ntohs(peer.sin_port));
fflush(stdout);
// Mientras se atiende a este cliente, nadie más es atendido.
if (wc_serve_client(conn) == -1) {
fprintf(stderr, "se abandona al cliente %s\n", ip);
}
close_checked(conn);
}
}// El servicio de conteo.
#define _POSIX_C_SOURCE 200809L
#include "wc_service.h"
#include "io_util.h"
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
void wc_update(struct wc_counts *counts, const char *buf, size_t n) {
for (size_t i = 0; i < n; i++) {
unsigned char c = (unsigned char)buf[i];
counts->bytes++;
if (c == '\n') {
counts->lines++;
}
if (isspace(c)) {
counts->in_word = false;
} else if (!counts->in_word) {
counts->in_word = true;
counts->words++;
}
}
}
int wc_serve_client(int conn) {
struct wc_counts counts = {0};
char buf[4096];
for (;;) {
ssize_t n = recv(conn, buf, sizeof buf, 0);
if (n == -1) {
if (errno == EINTR) {
continue;
}
perror("recv");
return -1;
}
if (n == 0) {
break; // fin del stream: el cliente ya no enviará más
}
wc_update(&counts, buf, (size_t)n);
}
char reply[128];
int len = snprintf(reply, sizeof reply,
"bytes=%" PRIu64 " words=%" PRIu64 " lines=%" PRIu64 "\n",
counts.bytes, counts.words, counts.lines);
return send_all(conn, reply, (size_t)len);
}// El servicio de conteo: cuántos bytes, palabras y líneas tiene lo recibido.
#ifndef WC_SERVICE_H
#define WC_SERVICE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct wc_counts {
uint64_t bytes;
uint64_t words;
uint64_t lines;
bool in_word; // ¿el pedazo anterior terminó a la mitad de una palabra?
};
// Agrega un pedazo al conteo. Guarda estado porque una palabra puede quedar
// partida entre dos recv.
void wc_update(struct wc_counts *counts, const char *buf, size_t n);
// Atiende a un cliente: lee hasta el fin del stream y responde con una línea.
// Devuelve 0, o -1 si hubo un error de red. No cierra conn.
int wc_serve_client(int conn);
#endiffrr defaults traditional
hostname r1
interface eth1
ip address 10.2.1.1/24
ip ospf area 0
ip ospf passive
interface eth2
ip address 10.2.12.1/24
ip ospf area 0
ip ospf hello-interval 1
ip ospf dead-interval 3
interface eth3
ip address 10.2.13.1/24
ip ospf area 0
ip ospf cost 100
ip ospf hello-interval 1
ip ospf dead-interval 3
router ospf
ospf router-id 10.2.255.1frr defaults traditional
hostname r2
interface eth1
ip address 10.2.12.2/24
ip ospf area 0
ip ospf hello-interval 1
ip ospf dead-interval 3
interface eth2
ip address 10.2.24.2/24
ip ospf area 0
ip ospf hello-interval 1
ip ospf dead-interval 3
router ospf
ospf router-id 10.2.255.2frr defaults traditional
hostname r3
interface eth1
ip address 10.2.13.3/24
ip ospf area 0
ip ospf cost 100
ip ospf hello-interval 1
ip ospf dead-interval 3
interface eth2
ip address 10.2.34.3/24
ip ospf area 0
ip ospf cost 100
ip ospf hello-interval 1
ip ospf dead-interval 3
router ospf
ospf router-id 10.2.255.3frr defaults traditional
hostname r4
interface eth1
ip address 10.2.24.4/24
ip ospf area 0
ip ospf hello-interval 1
ip ospf dead-interval 3
interface eth2
ip address 10.2.34.4/24
ip ospf area 0
ip ospf cost 100
ip ospf hello-interval 1
ip ospf dead-interval 3
interface eth3
ip address 10.2.4.1/24
ip ospf area 0
ip ospf passive
router ospf
ospf router-id 10.2.255.4Pruebas
Comprueban lo que afirman los apuntes; se ejecutan con make test.
"""Comprueba en la topología qué estado guarda cada nodo y qué sobrevive."""
import re
import subprocess
import time
import unittest
from netlab import HERE
TOPOLOGY = "fate.clab.yml"
PREFIX = "clab-w02-fate-"
H2_ADDR = "10.2.4.10"
def sh(*cmd, timeout=120, check=True):
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=check,
cwd=HERE)
def node(name, *cmd, timeout=60, check=True):
return sh("docker", "exec", PREFIX + name, *cmd, timeout=timeout, check=check)
def start(name, *cmd):
return subprocess.Popen(["docker", "exec", PREFIX + name, *cmd],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
def wait_for_route(via, timeout=90):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
out = node("r1", "ip", "route", "show", "10.2.4.0/24", check=False).stdout
if via in out:
return
time.sleep(1)
raise AssertionError(f"r1 no aprendió la ruta por {via}")
def hops():
out = node("h1", "traceroute", "-n", "-q", "1", "-w", "2", H2_ADDR).stdout
return re.findall(r"^\s*\d+\s+(\S+)", out, re.M)
class Fate(unittest.TestCase):
@classmethod
def setUpClass(cls):
sh("containerlab", "deploy", "--reconfigure", "-t", TOPOLOGY, timeout=240)
cls.addClassCleanup(cls.destroy)
wait_for_route("10.2.12.2")
@staticmethod
def destroy():
sh("containerlab", "destroy", "-t", TOPOLOGY, "--cleanup", timeout=120, check=False)
sh("docker", "network", "rm", "redes-w02", check=False)
@staticmethod
def stop(process):
process.kill()
process.communicate()
def tcp_transfer(self, port, size):
receiver = start("h2", "/lab/chunk_recv", str(port))
time.sleep(0.5)
sent = node("h1", "/lab/chunk_send", H2_ADDR, str(port), str(size), "1")
self.assertIn(f"{size} bytes", sent.stdout)
out, _ = receiver.communicate(timeout=60)
return out
def test_1_path_goes_through_r2(self):
self.assertEqual(hops(), ["10.2.1.1", "10.2.12.2", "10.2.24.4", H2_ADDR])
def test_2_mss_follows_the_link_mtu(self):
capture = start("r1", "timeout", "8", "tcpdump", "-ni", "eth1", "-c", "1",
f"tcp[tcpflags] & tcp-syn != 0 and dst host {H2_ADDR}")
time.sleep(1.5)
self.tcp_transfer(9010, 1000)
out, _ = capture.communicate(timeout=30)
self.assertIn("mss 1460", out)
def test_3_tcp_is_complete_and_udp_is_not_on_a_lossy_link(self):
node("r1", "tc", "qdisc", "add", "dev", "eth2", "root", "netem",
"delay", "5ms", "3ms", "loss", "10%")
self.addCleanup(node, "r1", "tc", "qdisc", "del", "dev", "eth2", "root", check=False)
out = self.tcp_transfer(9011, 102400)
self.assertRegex(out, r"recv: \d+ llamadas, 102400 bytes")
self.assertIn("fuera de lugar: 0", out)
receiver = start("h2", "/lab/seq_recv", "9012", "1000", "2000")
time.sleep(0.5)
node("h1", "/lab/seq_send", H2_ADDR, "9012", "1000", "200", "1000")
out, _ = receiver.communicate(timeout=60)
lost = int(re.search(r"perdidos: (\d+)", out).group(1))
reordered = int(re.search(r"fuera de orden: (\d+)", out).group(1))
self.assertGreater(lost, 30, out)
self.assertLess(lost, 250, out)
self.assertGreater(reordered, 0, out)
self.assertIn("200 bytes, 0 distintos", out)
def test_4_router_has_no_socket_of_the_connection(self):
receiver = start("h2", "/lab/chunk_recv", "9013")
time.sleep(0.5)
sender = start("h1", "/lab/chunk_send", "-g", "50", H2_ADDR, "9013", "1000", "60")
time.sleep(1)
at_host = node("h1", "ss", "-tn").stdout
at_router = node("r1", "ss", "-tn").stdout
sender.communicate(timeout=30)
receiver.communicate(timeout=30)
self.assertIn(f"{H2_ADDR}:9013", at_host)
self.assertNotIn(H2_ADDR, at_router)
def test_5_connection_survives_a_dead_router(self):
# Después de esta prueba r2 sigue caído: por eso las pruebas van numeradas.
receiver = start("h2", "/lab/chunk_recv", "9014")
time.sleep(0.5)
sender = start("h1", "/lab/chunk_send", "-g", "50", H2_ADDR, "9014", "1000", "120")
time.sleep(2)
sh("docker", "kill", PREFIX + "r2")
out_send, _ = sender.communicate(timeout=60)
out_recv, _ = receiver.communicate(timeout=60)
self.assertEqual(sender.returncode, 0, out_send)
self.assertIn("120000 bytes", out_recv)
self.assertIn("fuera de lugar: 0", out_recv)
wait_for_route("10.2.13.3", timeout=20)
self.assertIn("10.2.13.3", hops())
def test_6_connection_dies_with_its_end_process(self):
receiver = start("h2", "/lab/chunk_recv", "9015")
time.sleep(0.5)
sender = start("h1", "/lab/chunk_send", "-g", "50", H2_ADDR, "9015", "1000", "200")
time.sleep(1)
node("h2", "sh", "-c", "kill -9 $(pidof chunk_recv)")
receiver.communicate(timeout=10)
out, _ = sender.communicate(timeout=30)
self.assertNotEqual(sender.returncode, 0)
self.assertRegex(out, "Broken pipe|Connection reset")
def test_7_silent_end_host_leaves_the_sender_retransmitting(self):
receiver = start("h2", "/lab/chunk_recv", "9016")
time.sleep(0.5)
sender = start("h1", "/lab/chunk_send", "-g", "50", H2_ADDR, "9016", "1000", "400")
self.addCleanup(self.stop, sender)
self.addCleanup(self.stop, receiver)
time.sleep(1)
# Imita una máquina colgada: h2 deja de responder, sin avisar a nadie.
node("h2", "nft", "add table inet dead")
node("h2", "nft", "add chain inet dead in { type filter hook input priority 0; policy drop; }")
time.sleep(4)
out = node("h1", "ss", "-tno", f"dst {H2_ADDR}").stdout
self.assertIsNone(sender.poll(), "send no debe fallar todavía")
self.assertIn("timer:(on", out)
if __name__ == "__main__":
unittest.main()"""Pruebas del laboratorio. Corren sobre lab/ con `make test-lab`, y sobre
solution/ dentro de `make test`, si esa carpeta existe."""
import os
import re
import socket
import struct
import subprocess
import time
import unittest
from pathlib import Path
from netlab import BUILD, HERE, NetTestCase, connect, free_port, recv_line, recv_until_close
LAB_DIR = os.environ.get("LAB_DIR", "solution")
BIN = BUILD / LAB_DIR
TEXT, COUNTS, ERROR = 1, 2, 3
def run(name, *args, stdin=None, timeout=20):
return subprocess.run([str(BIN / name), *map(str, args)], input=stdin,
capture_output=True, timeout=timeout)
def record(kind, value):
return struct.pack("!BH", kind, len(value)) + value
def read_exact(sock, n):
data = b""
while len(data) < n:
chunk = sock.recv(n - len(data))
if not chunk:
break
data += chunk
return data
def read_record(sock):
header = read_exact(sock, 3)
kind, length = struct.unpack("!BH", header)
return kind, read_exact(sock, length)
@unittest.skipUnless((HERE / LAB_DIR).is_dir(), f"no existe {LAB_DIR}/")
class LabCase(NetTestCase):
def lab_server(self, name, *args):
return self.server(BIN / name, *args)
class Step2BigClient(LabCase):
def test_100_kb_arrive_and_recv_calls_are_counted(self):
server = self.lab_server("wc_counting_server", 0)
out = run("wc_big_client", "127.0.0.1", server.port)
self.assertEqual(out.returncode, 0, out)
self.assertIn("bytes=102400 ", out.stdout.decode())
calls = int(server.wait_output(r"recv: (\d+)").group(1))
# Con un buffer de 4096 bytes, como el de wc_server.c.
self.assertGreaterEqual(calls, 25)
def test_other_sizes(self):
server = self.lab_server("wc_counting_server", 0)
out = run("wc_big_client", "127.0.0.1", server.port, 5000)
self.assertIn("bytes=5000 ", out.stdout.decode())
class Step3Records(LabCase):
def test_one_request(self):
server = self.lab_server("wcf_server", 0)
sock = self.connect(server.port)
sock.sendall(record(TEXT, b"uno dos tres\n"))
kind, value = read_record(sock)
self.assertEqual(kind, COUNTS)
self.assertEqual(struct.unpack("!III", value), (13, 3, 1))
def test_record_split_byte_by_byte(self):
server = self.lab_server("wcf_server", 0)
sock = self.connect(server.port)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
for byte in record(TEXT, b"hola mundo"):
sock.sendall(bytes([byte]))
time.sleep(0.005)
kind, value = read_record(sock)
self.assertEqual(struct.unpack("!III", value), (10, 2, 0))
def test_records_glued_in_one_send(self):
server = self.lab_server("wcf_server", 0)
sock = self.connect(server.port)
sock.sendall(record(TEXT, b"a") + record(TEXT, b"b c") + record(TEXT, b""))
expected = [(1, 1, 0), (3, 2, 0), (0, 0, 0)]
for counts in expected:
kind, value = read_record(sock)
self.assertEqual(kind, COUNTS)
self.assertEqual(struct.unpack("!III", value), counts)
def test_large_record(self):
server = self.lab_server("wcf_server", 0)
sock = self.connect(server.port)
sock.sendall(record(TEXT, b"x" * 65535))
_, value = read_record(sock)
self.assertEqual(struct.unpack("!III", value), (65535, 1, 0))
def test_unknown_kind_gets_an_error_and_a_close(self):
server = self.lab_server("wcf_server", 0)
sock = self.connect(server.port)
sock.sendall(record(99, b"??"))
kind, _ = read_record(sock)
self.assertEqual(kind, ERROR)
self.assertEqual(recv_until_close(sock), b"")
def test_client_that_leaves_mid_record_does_not_stop_the_server(self):
server = self.lab_server("wcf_server", 0)
gone = connect(server.port)
gone.sendall(struct.pack("!BH", TEXT, 100) + b"solo un poco")
gone.close()
sock = self.connect(server.port)
sock.sendall(record(TEXT, b"sigue"))
kind, _ = read_record(sock)
self.assertEqual(kind, COUNTS)
def test_client_prints_one_answer_per_line(self):
server = self.lab_server("wcf_server", 0)
out = run("wcf_client", "127.0.0.1", server.port, stdin=b"uno dos\ntres\n")
self.assertEqual(out.returncode, 0, out)
self.assertEqual(out.stdout.decode().splitlines(),
["bytes=8 words=2 lines=1", "bytes=5 words=1 lines=1"])
class Step4Udp(LabCase):
def test_a_datagram_is_a_message(self):
server = self.lab_server("wc_udp_server", 0)
for flags in ([], ["-c"]):
out = run("wc_udp_client", *flags, "127.0.0.1", server.port, stdin=b"uno dos\n")
self.assertEqual(out.returncode, 0, out)
self.assertIn("bytes=8 words=2 lines=1", out.stdout.decode())
def test_server_off(self):
port = free_port(socket.SOCK_DGRAM)
out = run("wc_udp_client", "127.0.0.1", port, stdin=b"hola\n")
self.assertEqual(out.returncode, 2, out)
self.assertIn("sin respuesta", out.stdout.decode())
def test_server_off_with_connect(self):
port = free_port(socket.SOCK_DGRAM)
out = run("wc_udp_client", "-c", "127.0.0.1", port, stdin=b"hola\n")
self.assertEqual(out.returncode, 1, out)
self.assertIn(b"Connection refused", out.stderr)
class Step5Room(LabCase):
def room(self):
server = self.lab_server("room_server", 0)
a = self.connect(server.port)
b = self.connect(server.port)
time.sleep(0.2)
return server, a, b
def test_line_reaches_the_other_client_and_not_the_sender(self):
_, a, b = self.room()
a.sendall(b"hola\n")
self.assertRegex(recv_line(b), r"^\[\d+\] hola\n$")
a.settimeout(0.3)
with self.assertRaises(TimeoutError):
a.recv(100)
def test_both_clients_can_talk_in_any_order(self):
_, a, b = self.room()
b.sendall(b"uno\n")
self.assertIn("uno", recv_line(a))
a.sendall(b"dos\n")
self.assertIn("dos", recv_line(b))
b.sendall(b"tres\n")
self.assertIn("tres", recv_line(a))
def test_partial_line_waits_for_its_end(self):
_, a, b = self.room()
a.sendall(b"ho")
b.settimeout(0.3)
with self.assertRaises(TimeoutError):
b.recv(100)
b.settimeout(5)
a.sendall(b"la\n")
self.assertRegex(recv_line(b), r"hola\n$")
def test_two_lines_in_one_send(self):
_, a, b = self.room()
a.sendall(b"uno\ndos\n")
self.assertIn("uno", recv_line(b))
self.assertIn("dos", recv_line(b))
def test_room_survives_a_client_leaving(self):
server, a, b = self.room()
c = connect(server.port)
time.sleep(0.1)
c.sendall(b"medio")
c.close()
time.sleep(0.1)
a.sendall(b"sigue\n")
self.assertIn("sigue", recv_line(b))
if __name__ == "__main__":
unittest.main()"""Comprueba lo que dicen los apuntes sobre sockets, en la interfaz loopback."""
import re
import socket
import struct
import subprocess
import threading
import time
import unittest
import hashlib
from netlab import BUILD, NetTestCase, connect, free_port, recv_line, recv_until_close
def run(name, *args, stdin=None, timeout=20):
return subprocess.run(
[str(BUILD / name), *map(str, args)], input=stdin, capture_output=True,
timeout=timeout,
)
def wc_reply(port, data):
sock = connect(port)
with sock:
sock.sendall(data)
sock.shutdown(socket.SHUT_WR)
return recv_until_close(sock).decode()
class ByteOrder(unittest.TestCase):
def test_network_order_is_big_endian(self):
out = run("byte_order").stdout.decode()
self.assertRegex(out, r"htons\(9002\):\s+23 2a")
self.assertRegex(out, r"htonl\(0x0a020110\):\s+0a 02 01 10")
self.assertRegex(out, r"cabecera en 3 bytes:\s+07 23 2a")
class CountingService(NetTestCase):
def test_client_and_server(self):
server = self.server("wc_server", 0)
out = run("wc_client", "127.0.0.1", server.port, stdin=b"hola mundo\nsegunda linea\n")
self.assertEqual(out.returncode, 0)
self.assertEqual(out.stdout.decode(), "bytes=25 words=4 lines=2\n")
def test_client_accepts_a_name(self):
server = self.server("wc_server", 0)
out = run("wc_client", "localhost", server.port, stdin=b"uno dos\n")
self.assertEqual(out.stdout.decode(), "bytes=8 words=2 lines=1\n")
def test_word_split_between_recvs_counts_once(self):
server = self.server("wc_server", 0)
sock = self.connect(server.port)
sock.sendall(b"hola mu")
time.sleep(0.2)
sock.sendall(b"ndo\n")
sock.shutdown(socket.SHUT_WR)
self.assertEqual(recv_until_close(sock).decode(), "bytes=11 words=2 lines=1\n")
def test_large_input_arrives_complete(self):
server = self.server("wc_server", 0)
data = b"abc def\n" * 200000
out = run("wc_client", "127.0.0.1", server.port, stdin=data)
self.assertEqual(out.stdout.decode(), f"bytes={len(data)} words=400000 lines=200000\n")
def test_server_survives_a_client_that_resets(self):
server = self.server("wc_server", 0)
sock = connect(server.port)
# SO_LINGER en 0: close envía RST en vez de FIN.
sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
sock.sendall(b"medio mensaje")
sock.close()
self.assertEqual(wc_reply(server.port, b"x\n"), "bytes=2 words=1 lines=1\n")
self.assertTrue(server.alive())
def test_refused_connection_is_reported(self):
out = run("wc_client", "127.0.0.1", free_port(), stdin=b"x")
self.assertNotEqual(out.returncode, 0)
self.assertIn(b"Connection refused", out.stderr)
class Stream(NetTestCase):
def transfer(self, recv_args, send_args):
receiver = self.server("chunk_recv", *recv_args, 0)
sent = run("chunk_send", "127.0.0.1", receiver.port, *send_args)
self.assertEqual(sent.returncode, 0, sent.stderr)
receiver.wait()
return receiver
def test_bytes_arrive_complete_and_in_order(self):
for size, count in ((102400, 1), (1000, 100), (1, 2000)):
with self.subTest(size=size, count=count):
r = self.transfer([], [size, count])
r.wait_output(rf"recv: \d+ llamadas, {size * count} bytes")
r.wait_output(r"fuera de lugar: 0$")
def test_recv_all_rebuilds_fixed_records(self):
receiver = self.transfer(["-x", 100], [50, 10])
receiver.wait_output(r"recv_all: 5 llamadas, 500 bytes, menor 100, mayor 100")
def test_recv_does_not_see_send_boundaries(self):
# 2000 envíos de 1 byte seguidos no deberían dar 2000 recv. El kernel
# decide: la prueba solo exige que se junte algo, una sola vez en cinco.
for _ in range(5):
receiver = self.transfer([], [1, 2000])
calls = int(receiver.wait_output(r"recv: (\d+) llamadas").group(1))
if calls < 2000:
return
self.fail("cada send llegó en su propio recv en todos los intentos")
def test_recv_all_reports_early_close(self):
receiver = self.server("chunk_recv", "-x", 100, 0)
sock = connect(receiver.port)
sock.sendall(bytes(range(30)))
sock.close()
receiver.wait()
receiver.wait_output(r"recv_all: 1 llamadas, 30 bytes")
def test_send_may_write_less_than_requested(self):
out = run("short_send").stdout.decode().splitlines()
first = re.search(r"pidieron (\d+) bytes y send devuelve (\d+)", out[0])
self.assertIsNotNone(first, out)
self.assertLess(int(first.group(2)), int(first.group(1)))
self.assertGreater(int(first.group(2)), 0)
self.assertIn("Resource temporarily unavailable", out[1])
timed = re.search(r"pidieron (\d+) bytes y send devuelve (\d+)", out[2])
self.assertLess(int(timed.group(2)), int(timed.group(1)))
class Udp(NetTestCase):
def test_request_and_reply(self):
server = self.server("time_server", 0)
for flags in ([], ["-c"]):
out = run("time_client", *flags, "127.0.0.1", server.port)
self.assertEqual(out.returncode, 0, out)
self.assertRegex(out.stdout.decode(), r"hora: \d\d:\d\d:\d\d UTC")
def test_server_learns_the_client_from_each_datagram(self):
server = self.server("time_server", 0)
a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.addCleanup(a.close)
self.addCleanup(b.close)
for sock in (a, b):
sock.settimeout(5)
sock.sendto(b"x", ("127.0.0.1", server.port))
self.assertRegex(sock.recv(100).decode(), "UTC")
ports = {a.getsockname()[1], b.getsockname()[1]}
self.assertEqual(len(ports), 2)
for port in ports:
server.wait_output(rf"127\.0\.0\.1:{port}$")
def test_server_off_without_connect_goes_unnoticed(self):
out = run("time_client", "-w", 300, "127.0.0.1", free_port(socket.SOCK_DGRAM))
self.assertEqual(out.returncode, 2, out)
self.assertIn("enviados 5 bytes", out.stdout.decode())
self.assertIn("sin respuesta", out.stdout.decode())
def test_server_off_with_connect_is_refused_after_the_send(self):
out = run("time_client", "-c", "-w", 300, "127.0.0.1", free_port(socket.SOCK_DGRAM))
self.assertEqual(out.returncode, 1, out)
self.assertIn("enviados 5 bytes", out.stdout.decode())
self.assertIn(b"Connection refused", out.stderr)
def test_datagrams_keep_their_boundaries(self):
receiver = self.server("seq_recv", 0, 200, 500)
sent = run("seq_send", "127.0.0.1", receiver.port, 200, 100, 100)
self.assertEqual(sent.returncode, 0, sent.stderr)
receiver.wait()
receiver.wait_output(r"tamaño de cada recv: 100 bytes, 0 distintos")
class Concurrency(NetTestCase):
def blocked_client_is_answered(self, program):
"""¿Responde a B mientras A está conectado sin terminar?"""
server = self.server(program, 0)
idle = self.connect(server.port)
idle.sendall(b"medio")
time.sleep(0.2)
other = self.connect(server.port)
other.settimeout(1.0)
other.sendall(b"uno\n" if program != "tally_server" else b"TOTAL\n")
if program != "tally_server":
other.shutdown(socket.SHUT_WR)
try:
return bool(other.recv(100))
except TimeoutError:
return False
def test_iterative_server_makes_the_second_client_wait(self):
self.assertFalse(self.blocked_client_is_answered("wc_server"))
def test_fork_server_answers_the_second_client(self):
self.assertTrue(self.blocked_client_is_answered("wc_fork_server"))
def test_poll_server_answers_the_second_client(self):
self.assertTrue(self.blocked_client_is_answered("tally_server"))
class Tally(NetTestCase):
def test_total_is_shared(self):
server = self.server("tally_server", 0)
a = self.connect(server.port)
b = self.connect(server.port)
a.sendall(b"SUMA 5\n")
self.assertEqual(recv_line(a), "total 5\n")
b.sendall(b"SUMA -2\n")
self.assertEqual(recv_line(b), "total 3\n")
a.sendall(b"TOTAL\n")
self.assertEqual(recv_line(a), "total 3\n")
def test_partial_line_waits_without_blocking_others(self):
server = self.server("tally_server", 0)
a = self.connect(server.port)
b = self.connect(server.port)
b.sendall(b"SU")
time.sleep(0.1)
a.sendall(b"SUMA 2\n")
self.assertEqual(recv_line(a), "total 2\n")
b.sendall(b"MA 10\nTOTAL\n")
self.assertEqual(recv_line(b), "total 12\n")
self.assertEqual(recv_line(b), "total 12\n")
def test_bad_command(self):
server = self.server("tally_server", 0)
a = self.connect(server.port)
for line in (b"hola\n", b"SUMA\n", b"SUMA 1 2\n", b"SUMA x\n"):
a.sendall(line)
self.assertEqual(recv_line(a), "error\n")
def test_long_line_closes_only_that_client(self):
server = self.server("tally_server", 0)
a = self.connect(server.port)
b = self.connect(server.port)
a.sendall(b"x" * 300)
self.assertEqual(recv_until_close(a), b"")
b.sendall(b"TOTAL\n")
self.assertEqual(recv_line(b), "total 0\n")
def test_client_that_leaves_mid_line_does_not_matter(self):
server = self.server("tally_server", 0)
a = connect(server.port)
b = self.connect(server.port)
a.sendall(b"SUMA 1")
a.close()
b.sendall(b"TOTAL\n")
self.assertEqual(recv_line(b), "total 0\n")
def test_client_that_never_reads_is_cut_off(self):
server = self.server("tally_server", 0)
slow = connect(server.port)
self.addCleanup(slow.close)
other = self.connect(server.port)
cut = False
try:
for _ in range(4000):
slow.sendall(b"TOTAL\n" * 2000)
except (BrokenPipeError, ConnectionResetError, TimeoutError):
cut = True
self.assertTrue(cut, "el servidor debió cortar al cliente que no lee")
other.sendall(b"TOTAL\n")
self.assertEqual(recv_line(other), "total 0\n")
class ReuseAddress(NetTestCase):
def serve_once(self, port, *flags):
"""Corre reuse_demo hasta que atienda a un cliente. Devuelve bind's error."""
with subprocess.Popen(
[str(BUILD / "reuse_demo"), *flags, str(port)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
) as server:
line = server.stdout.readline()
if "Address already in use" in line:
return False
return self.finish_once(server, port)
def finish_once(self, server, port):
sock = connect(port)
with sock:
self.assertEqual(recv_until_close(sock), b"chau\n")
self.assertEqual(server.wait(timeout=5), 0)
return True
def test_time_wait_blocks_restart_without_the_option(self):
port = free_port()
self.assertTrue(self.serve_once(port))
self.assertFalse(self.serve_once(port))
def test_both_servers_need_the_option(self):
port = free_port()
self.assertTrue(self.serve_once(port))
self.assertFalse(self.serve_once(port, "-r"))
def test_option_allows_restart_during_time_wait(self):
port = free_port()
self.assertTrue(self.serve_once(port, "-r"))
self.assertTrue(self.serve_once(port, "-r"))
def test_option_does_not_share_a_port_being_listened_on(self):
server = self.server("wc_server", 0) # wc_server usa SO_REUSEADDR
out = run("reuse_demo", "-r", server.port)
self.assertEqual(out.returncode, 1)
self.assertIn(b"Address already in use", out.stderr)
class EndToEnd(NetTestCase):
"""Un intermediario daña los datos entre dos conexiones TCP correctas."""
def relay_digest(self, data, every):
sink = socket.socket()
self.addCleanup(sink.close)
sink.bind(("127.0.0.1", 0))
sink.listen(1)
received = []
def collect():
conn, _ = sink.accept()
with conn:
received.append(recv_until_close(conn))
thread = threading.Thread(target=collect, daemon=True)
thread.start()
relay = self.server("flaky_relay", 0, "127.0.0.1", sink.getsockname()[1], every)
sock = connect(relay.port)
with sock:
sock.sendall(data)
self.assertEqual(relay.wait(), 0)
thread.join(timeout=10)
return hashlib.sha256(received[0]).hexdigest(), len(received[0])
def test_tcp_delivers_damaged_bytes_and_only_the_end_notices(self):
data = bytes(range(251)) * 8000
digest, size = self.relay_digest(data, 500000)
self.assertEqual(size, len(data))
self.assertNotEqual(digest, hashlib.sha256(data).hexdigest())
def test_healthy_relay_keeps_the_digest(self):
data = bytes(range(251)) * 8000
digest, _ = self.relay_digest(data, 0)
self.assertEqual(digest, hashlib.sha256(data).hexdigest())
if __name__ == "__main__":
unittest.main()Laboratorio
Archivos de partida del laboratorio de la semana.
// Paso 5. Sala con poll: cada línea de un cliente les llega a los demás.
// room_server <puerto>
// Lo primero que imprime es la línea de print_bound_port (io_util.h).
// Se describe en la sección del laboratorio de los apuntes de la semana.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
int main(void) {
fprintf(stderr, "room_server: falta implementar\n");
return EXIT_FAILURE;
}// Paso 2. Copien wc_client.c; en vez de la entrada estándar, envíen <bytes> bytes
// (102400 si no se indica) en una sola llamada a send_all.
// wc_big_client <host> <puerto> [bytes]
// Se describe en la sección del laboratorio de los apuntes de la semana.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
int main(void) {
fprintf(stderr, "wc_big_client: falta implementar\n");
return EXIT_FAILURE;
}// Paso 2. Copien wc_server.c y cuenten las llamadas a recv que devuelven datos.
// Por cada cliente, impriman 'recv: N' antes de responder.
// wc_counting_server <puerto>
// Lo primero que imprime es la línea de print_bound_port (io_util.h).
// Se describe en la sección del laboratorio de los apuntes de la semana.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
int main(void) {
fprintf(stderr, "wc_counting_server: falta implementar\n");
return EXIT_FAILURE;
}// Paso 4. Cliente con UDP y la opción -c para llamar a connect.
// wc_udp_client [-c] <host> <puerto>
// Se describe en la sección del laboratorio de los apuntes de la semana.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
int main(void) {
fprintf(stderr, "wc_udp_client: falta implementar\n");
return EXIT_FAILURE;
}// Paso 4. Servidor de conteo con UDP: un datagrama, una respuesta.
// wc_udp_server <puerto>
// Lo primero que imprime es la línea de print_bound_port (io_util.h).
// Se describe en la sección del laboratorio de los apuntes de la semana.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
int main(void) {
fprintf(stderr, "wc_udp_server: falta implementar\n");
return EXIT_FAILURE;
}// Paso 3. Cliente: cada línea de la entrada estándar es un registro TEXT.
// wcf_client <host> <puerto>
// Se describe en la sección del laboratorio de los apuntes de la semana.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
int main(void) {
fprintf(stderr, "wcf_client: falta implementar\n");
return EXIT_FAILURE;
}// Paso 3. Servidor de conteo con registros kind + length + value.
// wcf_server <puerto>
// Lo primero que imprime es la línea de print_bound_port (io_util.h).
// Se describe en la sección del laboratorio de los apuntes de la semana.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
int main(void) {
fprintf(stderr, "wcf_server: falta implementar\n");
return EXIT_FAILURE;
}Compilación
Cómo se compilan y ejecutan los ejemplos.
# Semana 2: principios de diseño y sockets en C. Se usa dentro del contenedor del
# curso (env/run.sh).
#
# make compila los programas de la clase en build/
# make test corre las pruebas y destruye lo que hayan levantado, aunque
# alguna falle (incluye una topología: toma alrededor de un minuto)
# make test-lab prueba su laboratorio (lab/*.c); falla hasta que lo terminen
# make deploy levanta la topología fate.clab.yml
# make destroy la destruye y borra su red de Docker
# make clean borra lo generado y destruye la topología
CC = gcc
CFLAGS = -Wall -Wextra -std=c17 -g -I.
BUILD = build
STATIC = $(BUILD)/static
TOPOLOGY = fate.clab.yml
NETWORK = redes-w02
LAB_PROGRAMS = wc_big_client wc_counting_server wcf_server wcf_client \
wc_udp_server wc_udp_client room_server
PROGRAMS = byte_order flaky_relay reuse_demo short_send chunk_send chunk_recv \
wc_server wc_client wc_fork_server tally_server \
time_server time_client seq_send seq_recv
# Corren dentro de los nodos de la topología: la imagen de los routers usa otra
# biblioteca de C, así que van estáticos.
STATIC_PROGRAMS = chunk_send chunk_recv seq_send seq_recv
COMMON = io_util.c wc_service.c
HEADERS = io_util.h wc_service.h
.PHONY: all lab test test-lab deploy destroy clean
all: $(PROGRAMS:%=$(BUILD)/%) $(STATIC_PROGRAMS:%=$(STATIC)/%)
$(BUILD)/%: %.c $(COMMON) $(HEADERS) | $(BUILD)
$(CC) $(CFLAGS) -o $@ $< $(COMMON)
$(STATIC)/%: %.c io_util.c io_util.h | $(STATIC)
$(CC) $(CFLAGS) -static -o $@ $< io_util.c
$(BUILD)/lab/%: lab/%.c $(COMMON) $(HEADERS) | $(BUILD)/lab
$(CC) $(CFLAGS) -Ilab -o $@ $< $(COMMON)
$(BUILD)/solution/%: solution/%.c $(COMMON) $(HEADERS) | $(BUILD)/solution
$(CC) $(CFLAGS) -Isolution -o $@ $< $(COMMON)
$(BUILD) $(STATIC) $(BUILD)/lab $(BUILD)/solution:
mkdir -p $@
lab: $(LAB_PROGRAMS:%=$(BUILD)/lab/%)
# Si existe solution/, también se comprueba la solución del laboratorio.
test: all
@if [ -d solution ]; then $(MAKE) --no-print-directory \
$(LAB_PROGRAMS:%=$(BUILD)/solution/%); fi
@rc=0; python3 -m unittest discover -v -p 'test_*.py' || rc=$$?; \
$(MAKE) --no-print-directory destroy; exit $$rc
test-lab: all lab
LAB_DIR=lab python3 -m unittest -v test_lab
deploy: all
containerlab deploy --reconfigure -t $(TOPOLOGY)
destroy:
@containerlab destroy -t $(TOPOLOGY) --cleanup >/dev/null 2>&1 || true
@docker network rm $(NETWORK) >/dev/null 2>&1 || true
clean: destroy
rm -rf $(BUILD) __pycache__ clab-w02-fate .$(TOPOLOGY).bak