Semana 1 — Qué es Internet y cómo medirla

Objetivos

  • Describir Internet como una red de redes: hosts, enlaces, routers e ISPs.
  • Distinguir conmutación de paquetes y conmutación de circuitos, y explicar por qué Internet usa la primera.
  • Calcular los cuatro componentes del retardo por salto, y estimar el retardo y el throughput de un camino.
  • Explicar qué hace cada capa y cómo funciona el encapsulamiento.
  • Preparar el entorno del curso y leer una primera captura en Wireshark.

Sesión A — Presentación del curso · Qué es Internet: bordes, núcleo e ISPs

Lecturas:

Lecturas complementarias:

Temas:

  • Cómo funciona el curso: partes, proyecto Qhapaq Ñan, laboratorios, uso de herramientas y por qué el orden no es el del libro.
  • Dos formas de describir Internet: por sus piezas (hosts, enlaces, routers, ISPs) y por el servicio que da a las aplicaciones.
  • Qué es un protocolo: formato de los mensajes, orden y acciones. Ejemplo: una petición web.
  • Redes de acceso: DSL, cable, fibra, Wi-Fi y redes móviles.
  • Conmutación de paquetes: store-and-forward, colas y pérdida. Conmutación de circuitos: reservar capacidad con FDM o TDM.
  • Multiplexación estadística. Ejemplo numérico: en un enlace de 1 Gbps, 10 usuarios de 100 Mbps caben con circuitos, y muchos más si solo transmiten parte del tiempo.
  • La red de redes: ISPs de acceso, regionales y de tránsito, IXPs y redes de contenido.

Sesión B — Retardo, pérdida y throughput · Capas y encapsulamiento

Lecturas:

  • Kurose y Ross, secs. 1.4–1.5 (Delay, Loss, and Throughput; Protocol Layers and Their Service Models).
  • CS168, Layers y Headers.

Temas:

  • Retardo nodal: procesamiento, cola, transmisión (L/R) y propagación (d/s).
  • Error común: confundir retardo de transmisión con retardo de propagación. El primero depende del tamaño del paquete y de la velocidad del enlace; el segundo, de la distancia.
  • Intensidad de tráfico (La/R): qué pasa con la cola cuando se acerca a 1, y por qué no se debe diseñar para llegar a más de 1.
  • Throughput de un camino: lo limita el enlace más lento, el cuello de botella (bottleneck).
  • Las cinco capas de Internet: aplicación, transporte, red, enlace y física. Qué servicio da cada una.
  • Encapsulamiento: mensaje, segmento, datagrama y frame. Qué lee un router y qué lee un switch.
  • El modelo OSI como vocabulario: siete capas y por qué Internet no lo sigue tal cual.

Laboratorio — Entorno: Docker, Wireshark y traceroute

  1. Instalar Docker y Wireshark siguiendo labs/README.md. Levantar el contenedor con env/run.sh y correr env/smoke/check.sh. Todas las líneas deben decir ok.
  2. En la terminal de su computadora, fuera del contenedor, correr traceroute (tracert en Windows) hacia un sitio del Perú y otro de otro continente. Docker Desktop no deja ver los saltos intermedios, así que dentro del contenedor esto no sirve.
  3. Comparar los dos caminos: cuántos saltos hay y en qué salto aparece el mayor aumento del RTT. ¿Pueden ubicar un cable submarino?
  4. Dentro del contenedor, capturar una petición web con tcpdump -i any -w web.pcap host example.com mientras corre curl. Esperar un par de segundos antes de detener tcpdump, para que escriba todo. Abrir web.pcap en Wireshark.
  5. En la captura, identificar las capas de un paquete: Ethernet o SLL, IP, TCP y aplicación. Anotar el tamaño de cada encabezado.
  6. Calcular a mano el retardo de transmisión de un paquete de 1500 bytes en un enlace de 100 Mbps y compararlo con el retardo de propagación entre Lima y Miami.
  7. Leer el enunciado de P0.

Las actividades de captura se basan en los laboratorios de Wireshark de Kurose y Ross.

Entregas

  • P0 publicado: Calentamiento (sockets en C y una versión propia de traceroute). Entrega en semana 3. Ver projects/README.md.

Preguntas de repaso

  1. Un paquete de 1000 bytes cruza un enlace de 10 Mbps de 2000 km, con una velocidad de propagación de 2×10⁸ m/s. Calculen los retardos de transmisión y propagación. ¿Cuál domina?
  2. ¿Por qué la conmutación de paquetes admite más usuarios que la de circuitos? ¿En qué caso es peor?
  3. Un camino tiene enlaces de 1 Gbps, 50 Mbps y 200 Mbps. ¿Cuál es el throughput máximo? ¿Cuánto tarda un archivo de 1 GB?
  4. ¿Qué encabezados debe leer un router para hacer su trabajo? ¿Y un switch?
  5. ¿Por qué un traceroute puede mostrar un RTT más alto en un salto intermedio que en uno posterior?

Código de la semana

Compilar y probar dentro del contenedor del curso con make y make test.

Ejemplos (18)Pruebas (2)Compilación (1)

Ejemplos

Los programas que usan los apuntes y la clase.

bottleneckDescargar bottleneck
ELF�	@�@8
@@@@00���������������������ppp$$���  P�tdlllDDQ�tdR�td������@@GNU��~�*#�7�/L���iF�/lib/ld-linux-aarch64.so.1
�@
c ." =� '&exitstrtod__libc_start_mainstderrfprintf__cxa_finalizeabortlibc.so.6GLIBC_2.34GLIBC_2.17_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTableC���M���X�� 
���	���HH��������	�� 	(
08
?#��{�������{���#��_��{�����G��?� � � � ��@�� ��@�"� ��
@�B� ��@�b� ��@��� ��@��� ��@��� ��@��� ��{�����[��*���
q
T�c��?m @���@`�
@���@`�q�T�c�X�R�S�s�� � � ����zs��������/@���`T@9 5 `�T o�Ts��!��T�SA��A`Q��,������A`d�@-��	!o�����cC��R�?Dm�[B��{ƨ�_�@`�*������G���@�!�+�@����� �R���X�R������G����! +�@����� �R���� � � � � � � � � � � � � � �_$��������@��#������G�����u����������G�@�|���_� � � � � � ��@��!@�?��T��!�G�a�����_��@��!@�!�"��A��!�A�����B�G�b�����_�?#��{�������`BA9@7���G����$@�I������� �R`B9�@��{¨�#��_� �_$���� � � � � � ��{����������/����@��T@9�5 `�T�@��{è�_����G����! +�@�2��� �R���?#��{�����{���#��_�bottleneck: no es un número positivo: %s
uso: %s <megabytes> <Mbps> [Mbps] ...
cuello de botella: enlace %d, a %g Mbps
tiempo para %g MB: %.2f s
;@��������X����l$����`��������������zRx4���4A,����0@����<$T����PA-A ��B�N���A-|���� �����dA0��C�K
���Ap����HA`��B��E��AONK�	�
U��M��BONB����A`�
�	������NOC����NOI��NOB�
�	GNU 
�	C�
�
�������o� �
������	���o���o����o���o����o�����������HGCC: (Debian 14.2.0-19) 14.2.0p��� ��	�
���
�
�
l������������@P��		��� �
��	4	 4		
�	
�
-��	
�	
�
4���
	@
A@
d	�@P��	`	[`	]�	Hp�	�P������ 
������PP�������������l����	�$4Q � @mP�"��X�P�
�
�X�@� �H��
X�	4 X(P4�H9PE f_
�eScrt1.o$x$d__abi_tagcrti.ocall_weak_fncrtn.obottleneck.cparse_positivecrtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.0__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entry__FRAME_END___DYNAMIC__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE_exit@GLIBC_2.17__libc_start_main@GLIBC_2.34_ITM_deregisterTMCloneTablestrtod@GLIBC_2.17__bss_start____cxa_finalize@GLIBC_2.17stderr@GLIBC_2.17_edata_fini__bss_end____data_start__gmon_start____dso_handleabort@GLIBC_2.17_IO_stdin_used_end__end____bss_startmain__TMC_END___ITM_registerTMCloneTable_initfprintf@GLIBC_2.17.symtab.strtab.shstrtab.note.gnu.build-id.interp.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.text.fini.rodata.eh_frame_hdr.eh_frame.note.ABI-tag.init_array.fini_array.dynamic.got.got.plt.data.bss.commentpp$.��6���o��@��PH  �P���o��]���o��0l�vB������{������$@��
�
��
�
��llD���,��� ���������������������8�����X�@@�PP�0Pp�D	X	x�
bottleneck.cDescargar bottleneck.c
// bottleneck.c: throughput máximo de un camino y tiempo para enviar un archivo.
//
//   ./bottleneck <megabytes> <Mbps del enlace 1> [Mbps del enlace 2] ...
//
// Aquí mega significa 10^6, como en las velocidades de los enlaces.
// El cálculo ignora los encabezados y supone que nadie más usa el camino.
#include <stdio.h>
#include <stdlib.h>

// Convierte un argumento a un número positivo, o termina con un mensaje.
static double parse_positive(const char *text) {
    char *end;
    double value = strtod(text, &end);
    if (end == text || *end != '\0' || value <= 0) {
        fprintf(stderr, "bottleneck: no es un número positivo: %s\n", text);
        exit(EXIT_FAILURE);
    }
    return value;
}

int main(int argc, char *argv[]) {
    if (argc < 3) {
        fprintf(stderr, "uso: %s <megabytes> <Mbps> [Mbps] ...\n", argv[0]);
        return EXIT_FAILURE;
    }
    double megabytes = parse_positive(argv[1]);
    int slowest = 2;
    double min_rate = parse_positive(argv[2]);
    for (int i = 3; i < argc; i++) {
        double rate = parse_positive(argv[i]);
        if (rate < min_rate) {
            min_rate = rate;
            slowest = i;
        }
    }
    printf("cuello de botella: enlace %d, a %g Mbps\n", slowest - 1, min_rate);
    printf("tiempo para %g MB: %.2f s\n", megabytes, megabytes * 8 / min_rate);
    return EXIT_SUCCESS;
}
delayDescargar delay
ELF�	@�@8
@@@@00���p
p
����������������ppp$$P
P
P
  P�tdDDQ�tdR�td������@@GNU���`�}T�V�!�[q�/lib/ld-linux-aarch64.so.1
�@
c ." =� '&exitstrtod__libc_start_mainstderrfprintf__cxa_finalizeabortlibc.so.6GLIBC_2.34GLIBC_2.17_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTableC���M���X�� 
���	���HH��������	�� 	(
08
?#��{�������{���#��_��{�����G��?� � � � ��@�� ��@�"� ��
@�B� ��@�b� ��@��� ��@��� ��@��� ��@��� ��{�����S��*Q��q�T�;m�!�,�`@�����d�`
@�!-�������%��?g�`@�
�!@-��������g��q`T�����4��g��m���`���
g���-��	m�����	m�@.������!o�T�n��/������;Bm�R�@��SA��{Ĩ�_����G��b@�!,�@����� �R����o��.��������`@��!�-�a���� � � � � � � � � � � � � � � �_$��������@��#������G�����u����������G�@�|���_� � � � � � ��@��!@�?��T��!�G�a�����_��@��!@�!�"��A��!�A�����B�G�b�����_�?#��{�������`BA9@7���G����$@�I������� �R`B9�@��{¨�#��_� �_$���� � � � � � ��{�����S�������.����@��T@9�5 `�T�SA��{è�_����G������!@+�@�0��� �R���?#��{�����{���#��_�delay: %s no es un número positivo: %s
uso: %s <bytes> <Mbps> <km> [velocidad en m/s]
el tamañola velocidadla distanciala propagacióntransmision L/R = %10.3f ms
propagacion d/s = %10.3f ms
domina la transmision, %.1f veces mayor
domina la propagacion, %.1f veces mayor
;@t��������XT���l��������������4����zRx����4A,����0@����<$T$���PA-A ��B�N���A-|P���$�X���lA0��B��M
����A@�p���DA@��B��FNMDOlNMBOB
����AIMNOGNU 
�	C�
�
�������o� �
������	���o���o����o���o����o�����������HGCC: (Debian 14.2.0-19) 14.2.0p��� ��	�
���
�
�
PP
����������@P��		dP
P
 �
��	4	 4		
�	
�
-��	
�	
�
4���
	@
<@
l	��K��	`	V`	X�	Hk�	�P������ 
�����xPK��L
�L
��������	����	�/L � @hzP�"��X�P�
�
�X�@� �H��
X�	4X#P/�D4P@ aZ
�`Scrt1.o$x$d__abi_tagcrti.ocall_weak_fncrtn.odelay.cparse_positivecrtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.0__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entry__FRAME_END___DYNAMIC__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE_exit@GLIBC_2.17__libc_start_main@GLIBC_2.34_ITM_deregisterTMCloneTablestrtod@GLIBC_2.17__bss_start____cxa_finalize@GLIBC_2.17stderr@GLIBC_2.17_edata_fini__bss_end____data_start__gmon_start____dso_handleabort@GLIBC_2.17_IO_stdin_used_end__end____bss_startmain__TMC_END___ITM_registerTMCloneTable_initfprintf@GLIBC_2.17.symtab.strtab.shstrtab.note.gnu.build-id.interp.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.text.fini.rodata.eh_frame_hdr.eh_frame.note.ABI-tag.init_array.fini_array.dynamic.got.got.plt.data.bss.commentpp$.��6���o��@��PH  �P���o��]���o��0l�vB������{������,@��
�
��
�
A�D�PP�P
P
 ���������������������8�����X�@@�PP�0Pp�D	X	s�
delay.cDescargar delay.c
// delay.c: retardo de transmisión y de propagación de un paquete en un enlace.
//
//   ./delay <bytes> <Mbps> <km> [velocidad en m/s]
//
// Por defecto la velocidad de propagación es 2e8 m/s, la de la luz en fibra.
#include <stdio.h>
#include <stdlib.h>

#define DEFAULT_SPEED 2e8

// Convierte un argumento a un número positivo, o termina con un mensaje.
static double parse_positive(const char *text, const char *name) {
    char *end;
    double value = strtod(text, &end);
    if (end == text || *end != '\0' || value <= 0) {
        fprintf(stderr, "delay: %s no es un número positivo: %s\n", name, text);
        exit(EXIT_FAILURE);
    }
    return value;
}

int main(int argc, char *argv[]) {
    if (argc != 4 && argc != 5) {
        fprintf(stderr, "uso: %s <bytes> <Mbps> <km> [velocidad en m/s]\n", argv[0]);
        return EXIT_FAILURE;
    }
    double bits = parse_positive(argv[1], "el tamaño") * 8;
    double rate = parse_positive(argv[2], "la velocidad") * 1e6;
    double meters = parse_positive(argv[3], "la distancia") * 1e3;
    double speed = argc == 5 ? parse_positive(argv[4], "la propagación") : DEFAULT_SPEED;

    double transmission = bits / rate;
    double propagation = meters / speed;

    printf("transmision L/R = %10.3f ms\n", transmission * 1e3);
    printf("propagacion d/s = %10.3f ms\n", propagation * 1e3);
    if (transmission > propagation) {
        printf("domina la transmision, %.1f veces mayor\n", transmission / propagation);
    } else {
        printf("domina la propagacion, %.1f veces mayor\n", propagation / transmission);
    }
    return EXIT_SUCCESS;
}
experiments.pyDescargar experiments.py
#!/usr/bin/env python3
"""Mediciones de la semana 1 sobre la topología topo/path.clab.yml.

    make up
    python3 experiments.py cable        # traceroute con un enlace largo
    python3 experiments.py size         # RTT según el tamaño del paquete
    python3 experiments.py bottleneck   # throughput según el enlace más lento
    python3 experiments.py queue        # cola y pérdida según la intensidad
    python3 experiments.py web          # qué cambia en un paquete al cruzar routers
    make down

Se corre dentro del contenedor del curso. Las cifras dependen de la máquina y
de lo que esté corriendo en ella. Las pruebas de test_path.py usan estas mismas
funciones, con márgenes amplios.
"""
import json
import re
import subprocess
import sys
import threading
import time
from pathlib import Path

HERE = Path(__file__).resolve().parent
PREFIX = "clab-w01-path-"
H2_ADDRESS = "10.2.0.2"

# Cada enlace, con los dos extremos que hay que configurar: tc controla lo que
# sale por una interfaz, así que un enlace necesita una regla en cada sentido.
LINKS = {
    "h1-r1": (("h1", "eth1"), ("r1", "eth1")),
    "r1-r2": (("r1", "eth2"), ("r2", "eth1")),
    "r2-r3": (("r2", "eth2"), ("r3", "eth1")),
    "r3-h2": (("r3", "eth2"), ("h2", "eth1")),
}
CABLE = "r2-r3"
CABLE_DELAY_MS = 21  # el retardo de propagación teórico de Lima a Miami


def sh(node, command, check=True):
    """Corre un comando en un nodo y devuelve su salida."""
    result = subprocess.run(
        ["docker", "exec", PREFIX + node, "sh", "-c", command],
        capture_output=True, text=True, check=False,
    )
    if check and result.returncode != 0:
        raise RuntimeError(f"{node}: {command}: {result.stderr.strip()}")
    return result.stdout


def start_detached(node, command):
    """Deja un comando corriendo en un nodo, en segundo plano."""
    subprocess.run(["docker", "exec", "-d", PREFIX + node, "sh", "-c", command], check=True)


def set_link(link, rate_mbit=None, delay_ms=None, limit=None):
    """Fija la velocidad, el retardo y el tamaño del buffer de un enlace."""
    options = ""
    if delay_ms is not None:
        options += f" delay {delay_ms}ms"
    if rate_mbit is not None:
        options += f" rate {rate_mbit}mbit"
    if limit is not None:
        options += f" limit {limit}"
    for node, device in LINKS[link]:
        sh(node, f"tc qdisc replace dev {device} root netem{options}")


def clear_links():
    """Quita todas las reglas: los enlaces vuelven a ser lo más rápidos posible."""
    for ends in LINKS.values():
        for node, device in ends:
            sh(node, f"tc qdisc del dev {device} root 2>/dev/null || true")


def wait_until_ready(timeout=60):
    """Espera a que FRRouting instale las rutas y h1 alcance a h2."""
    deadline = time.time() + timeout
    while time.time() < deadline:
        if sh("h1", f"ping -c 1 -W 1 {H2_ADDRESS} >/dev/null 2>&1 && echo ok", check=False).strip():
            return
        time.sleep(1)
    raise RuntimeError("h1 no alcanza a h2: revisen las rutas con ip route")


def traceroute():
    """Devuelve la salida de traceroute y, por salto, (dirección, RTT mínimo en ms)."""
    output = sh("h1", f"traceroute -n -w 2 {H2_ADDRESS}")
    hops = []
    for line in output.splitlines()[1:]:
        fields = line.split()
        times = [float(t) for t in re.findall(r"([\d.]+) ms", line)]
        hops.append((fields[1], min(times) if times else None))
    return output, hops


def min_rtt(size, count=40, interval=0.05):
    """RTT mínimo de varios ping: el mínimo descarta el ruido de la máquina."""
    output = sh("h1", f"ping -q -c {count} -i {interval} -s {size} {H2_ADDRESS}")
    return float(re.search(r"= ([\d.]+)/", output).group(1))


def fit_line(points):
    """Recta por mínimos cuadrados. Devuelve (pendiente, intercepto)."""
    n = len(points)
    mean_x = sum(x for x, _ in points) / n
    mean_y = sum(y for _, y in points) / n
    slope = sum((x - mean_x) * (y - mean_y) for x, y in points) / sum((x - mean_x) ** 2 for x, _ in points)
    return slope, mean_y - slope * mean_x


SIZES = [64, 400, 800, 1200, 1472]


def rtt_by_size(slow_links, rate_mbit=5, cable=False):
    """RTT mínimo para cada tamaño, con algunos enlaces lentos y quizá el cable."""
    clear_links()
    for link in slow_links:
        set_link(link, rate_mbit=rate_mbit)
    if cable:
        set_link(CABLE, delay_ms=CABLE_DELAY_MS, rate_mbit=rate_mbit if CABLE in slow_links else None)
    return {size: min_rtt(size) for size in SIZES}


def estimated_rate_mbit(rtts, links_crossed):
    """Velocidad que explica la pendiente del RTT: pendiente = 2 * enlaces * 8 / R."""
    slope_ms_per_byte, _ = fit_line(list(rtts.items()))
    return 2 * links_crossed * 8 / slope_ms_per_byte / 1000


def tcp_throughput_mbit(seconds=5):
    """Throughput de TCP de h1 a h2, medido por el receptor con iperf3."""
    output = sh("h1", f"iperf3 -c {H2_ADDRESS} -t {seconds} -J")
    return json.loads(output)["end"]["sum_received"]["bits_per_second"] / 1e6


def start_iperf_server():
    """Deja un servidor iperf3 en h2, si no hay uno."""
    sh("h2", "pgrep iperf3 >/dev/null || iperf3 -s -D")
    time.sleep(1)


FRAME_OVERHEAD = 8 + 20 + 14  # encabezados UDP, IP y Ethernet de cada datagrama
UDP_PAYLOAD = 1400
QUEUE_RATE_MBIT = 50
QUEUE_LIMIT = 200


def udp_load(rho, burst=None, seconds=7):
    """Envía UDP con intensidad rho por el enlace r1-r2 y mide el RTT mientras tanto.

    La intensidad se calcula con los bytes que de verdad cruzan el enlace,
    encabezados incluidos. Devuelve el RTT (promedio y máximo) y la pérdida de UDP.
    """
    payload_mbit = rho * QUEUE_RATE_MBIT * UDP_PAYLOAD / (UDP_PAYLOAD + FRAME_OVERHEAD)
    target = f"{payload_mbit:.2f}M" + (f"/{burst}" if burst else "")
    result = {}

    def send():
        output = sh("h1", f"iperf3 -c {H2_ADDRESS} -u -b {target} -l {UDP_PAYLOAD} -t {seconds} -J")
        result["lost"] = json.loads(output)["end"]["sum"]["lost_percent"]

    sender = threading.Thread(target=send)
    sender.start()
    time.sleep(2)  # que la cola llegue a su estado normal antes de medir
    output = sh("h1", f"ping -q -c 20 -i 0.2 -W 2 {H2_ADDRESS}", check=False)
    sender.join()
    summary = re.search(r"= ([\d.]+)/([\d.]+)/([\d.]+)", output)
    result["avg"] = float(summary.group(2)) if summary else None
    result["max"] = float(summary.group(3)) if summary else None
    return result


def capture_web():
    """Captura una petición web junto a h1 y junto a h2. Devuelve las rutas de las capturas."""
    clear_links()
    directory = HERE / "captures"
    directory.mkdir(exist_ok=True)
    # Los corchetes evitan que pgrep -f se encuentre a sí mismo.
    if not sh("h2", "pgrep -f '[h]ttp.server'", check=False).strip():
        start_detached("h2", "python3 -m http.server 80 --directory /srv/web")
    time.sleep(1)
    points = {"near-h1": ("r1", "eth1"), "near-h2": ("r3", "eth2")}
    for node, device in points.values():
        sh(node, "rm -f /tmp/web.pcap")
        start_detached(node, f"tcpdump -U -i {device} -w /tmp/web.pcap tcp port 80")
    time.sleep(2)
    sh("h1", f"curl -sS -o /dev/null http://{H2_ADDRESS}/")
    time.sleep(1)
    paths = {}
    for name, (node, _) in points.items():
        sh(node, "pkill -INT tcpdump")
        time.sleep(1)
        paths[name] = directory / f"{name}.pcap"
        subprocess.run(["docker", "cp", "-q", f"{PREFIX}{node}:/tmp/web.pcap", str(paths[name])], check=True)
    return paths


def packet_fields(path):
    """Campos de cada paquete de una captura, leídos con tshark."""
    names = ["eth.src", "eth.dst", "ip.ttl", "ip.src", "ip.dst", "ip.checksum", "tcp.seq_raw", "tcp.len"]
    command = ["tshark", "-r", str(path), "-T", "fields"]
    for name in names:
        command += ["-e", name]
    output = subprocess.run(command, capture_output=True, text=True, check=True).stdout
    return [dict(zip(names, line.split("\t"))) for line in output.splitlines()]


def show_cable():
    clear_links()
    set_link(CABLE, delay_ms=CABLE_DELAY_MS)
    print(f"Enlace {CABLE} con {CABLE_DELAY_MS} ms por sentido\n")
    print(traceroute()[0])


def show_size():
    runs = [
        ("un enlace a 5 Mbps", rtt_by_size(["r1-r2"]), 1),
        ("tres enlaces a 5 Mbps", rtt_by_size(["r1-r2", "h1-r1", "r3-h2"]), 3),
        ("un enlace a 5 Mbps y cable de 21 ms", rtt_by_size(["r1-r2"], cable=True), 1),
    ]
    print("RTT mínimo en ms, según los bytes de datos de ping\n")
    print(f"{'caso':<37}" + "".join(f"{size:>8}" for size in SIZES) + f"{'R estimada':>13}")
    for name, rtts, links in runs:
        row = "".join(f"{rtts[size]:8.2f}" for size in SIZES)
        print(f"{name:<37}{row}{estimated_rate_mbit(rtts, links):9.1f} Mbps")


def show_bottleneck():
    start_iperf_server()
    print("TCP de h1 a h2, 5 s, throughput en el receptor\n")
    print(f"{'r1-r2':>6}{'r2-r3':>7}{'r3-h2':>7}{'medido':>13}")
    for rates in [(50, 200, 200), (200, 50, 200), (200, 200, 50), (200, 200, 200), (20, 200, 200)]:
        clear_links()
        for link, rate in zip(["r1-r2", "r2-r3", "r3-h2"], rates):
            set_link(link, rate_mbit=rate)
        print(f"{rates[0]:>6}{rates[1]:>7}{rates[2]:>7}{tcp_throughput_mbit():>8.1f} Mbps")


def show_queue():
    start_iperf_server()
    clear_links()
    set_link("r1-r2", rate_mbit=QUEUE_RATE_MBIT, limit=QUEUE_LIMIT)
    print(f"Enlace r1-r2 a {QUEUE_RATE_MBIT} Mbps, buffer de {QUEUE_LIMIT} paquetes, UDP de h1 a h2\n")
    print(f"{'carga':<22}{'rho':>5}{'RTT prom.':>12}{'RTT max.':>11}{'pérdida UDP':>14}")
    for name, rho, burst in [("sin carga", 0, None), ("regular", 0.5, None), ("regular", 0.8, None),
                             ("regular", 0.95, None), ("regular", 1.2, None),
                             ("ráfagas de 100", 0.5, 100)]:
        if rho == 0:
            output = sh("h1", f"ping -q -c 10 -i 0.2 {H2_ADDRESS}")
            values = re.search(r"= ([\d.]+)/([\d.]+)/([\d.]+)", output)
            print(f"{name:<22}{rho:>5.2f}{float(values.group(2)):>9.2f} ms{float(values.group(3)):>8.2f} ms{'-':>14}")
            continue
        r = udp_load(rho, burst)
        print(f"{name:<22}{rho:>5.2f}{r['avg']:>9.2f} ms{r['max']:>8.2f} ms{r['lost']:>13.1f}%")


def show_web():
    paths = capture_web()
    near_h1, near_h2 = packet_fields(paths["near-h1"]), packet_fields(paths["near-h2"])
    print("El primer paquete de h1 a h2, visto a cada lado de los tres routers\n")
    print(f"{'campo':<14}{'junto a h1':<20}{'junto a h2':<20}")
    for field in near_h1[0]:
        changed = "  cambia" if near_h1[0][field] != near_h2[0][field] else ""
        print(f"{field:<14}{near_h1[0][field]:<20}{near_h2[0][field]:<20}{changed}")
    print(f"\nCapturas en {paths['near-h1'].parent.name}/; ábranlas en Wireshark o con ./layers")


COMMANDS = {
    "cable": show_cable,
    "size": show_size,
    "bottleneck": show_bottleneck,
    "queue": show_queue,
    "web": show_web,
}


def main():
    names = sys.argv[1:] or list(COMMANDS)
    unknown = [name for name in names if name not in COMMANDS]
    if unknown:
        sys.exit(f"uso: {sys.argv[0]} [{'|'.join(COMMANDS)}]")
    wait_until_ready()
    for name in names:
        COMMANDS[name]()
        print()
    clear_links()


if __name__ == "__main__":
    main()
layersDescargar layers
ELF��@�@8
@@@@00���������������������ppp$$���  P�td(((DDQ�tdR�td������HHGNUŀ�}@��>�/��C�	�o/lib/ld-linux-aarch64.so.1
��>� _"Pn)/6� |u� XW"putcputsperrorfreefreadexitferrorfopenstdoutrealloc__libc_start_mainstderrfprintf__cxa_finalizefclosefwriteabortlibc.so.6GLIBC_2.34GLIBC_2.17_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTable�����������������@
��������	������ (
08
@HPX`hpx�?#��{�������{���#��_��{�����G��?� � � � ��@�� ��@�"� ��
@�B� ��@�b� ��@��� ��@��� ��@��� ��@��� ��"@�� ��&@�"� ��*@�B� ��.@�b� ��2@��� ��6@��� ��:@��� ��>@��� ��B@�� ��C��{����q�T���G��"@�a��@����� �R�{C��C��_��S��[��c��k��s�3@��! 	����������%�����������*�#4�@��z�RB6�r���RkA6�rAzT!T�Ra��rk!T�R���rAz� T��@�B�Z_�q�T_Pq T_q�T�����������W�`C���4��������	���j��4�OX)�*w4�Zs
�Z�*?�hTY��S@������������W@�������������T�`��5�O@�������k��� 5�SD��[E��cF��kG��sH������@��R������G��!�
�@�������Y����SD��[E��cF��kG��sH�����O@���X����� ���������-��4�S@�������g���?�Tak|x!�Z!<? q�T���]����O������$�i��L�	Taku8 @y!~��Z?L�<@�"�@���A�T3%@99�q�
TFq�	Tq@T�*� �A����O�����O@������������5��c�G����!���`�c@�-�����'�������`
�+����O�����O�����
�����O�����ҕ��j�������g���B����$��cT*M@9�'5@9��&1@9%!@9�+� I@9�#����*E@9��*A@9��*=@9��)9@9�����q@T��s�G�@�Ra@������O�������B`�����˟4��T#3@9�B��cD�c�~������������O����97@9���s�G��������797y7�7� 7a@���R�������a@�
�R�������a@�@
�R������6���a@���R������6���a@�`
�R�����6�����
������O�e��a@� �R����������G����!�	�@����� �R{������G����!@	�@����� �Rr�����x��� �Rn����`�s���&��� �o����O@������� � � � � � � � � � � �_$��������@��#������G�����Q���p������G�@�h���_� � � � � � ���`���!`�?��T��!�G�a�����_���`���!`�!�"��A��!�A�����B�G�b�����_�?#��{��������`bB9@7���G�����H@�%������� �R`b9�@��{¨�#��_� �_$���� � � � � � ��{�����S�����������!��+�����@T����:����5S��@��SA��{è�_��@� �R�SA��{è�_���c�G����!�����c@���� �R����� ����� �R����?#��{�����{���#��_�layers: freadlayers: la captura termina a la mitad de un registro
udptcpicmpuso: %s <captura.pcap>
rblayers: %s está vacío
layers: %s no es una captura pcap clásica
layers: tipo de enlace %u no soportado
layers: realloclayers: la captura termina a la mitad de un paquete
%4ld total=%-5zu enlace=%zu recortado paquete IPv4 recortado ip=%zu mal formado
 ip=%zu tcp recortado
 ip=%zu protocolo=%u
 ip=%zu %s mal formado
 ip=%zu %s=%zu app=%zu ttl=%u %u.%u.%u.%u > %u.%u.%u.%u [ no es IPv4 (ethertype 0x%04x)
layers: fclose;D�������\����p(����d��������������zRx4���4A,����0@����<$T����PA-A ��B�N���A-|����4������A0��B��C�N
�����AD
�����A\����TA�A��M
��AA��A��A�
�A��A�	�
O
��A��A��A��A��AL
��A��A��A��A��AGNU����
��������o��
����HX�	���o���o(���o���o����o��@
																	�GCC: (Debian 14.2.0-19) 14.2.0p����(X	H
�	@

��(p���������������	���� ���	� �	
�	
�-��	
�	
�4���	=�	@
H��	 S UP�h�~���������������H�������������(����		,I[ $ �w��"�������
������"�/ >�K\�k{���(�4�����@
T���� ��
��Scrt1.o$x$d__abi_tagcrti.ocall_weak_fncrtn.olayers.cread_exactcrtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.0__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entry__FRAME_END___DYNAMIC__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE_exit@GLIBC_2.17__libc_start_main@GLIBC_2.34perror@GLIBC_2.17_ITM_deregisterTMCloneTable__bss_start____cxa_finalize@GLIBC_2.17putc@GLIBC_2.17stderr@GLIBC_2.17_edata_fini__bss_end__fclose@GLIBC_2.17fopen@GLIBC_2.17stdout@GLIBC_2.17realloc@GLIBC_2.17__data_start__gmon_start____dso_handleabort@GLIBC_2.17_IO_stdin_usedputs@GLIBC_2.17fread@GLIBC_2.17_endfree@GLIBC_2.17__end____bss_startmainfwrite@GLIBC_2.17__TMC_END___ITM_registerTMCloneTable_initfprintf@GLIBC_2.17ferror@GLIBC_2.17.symtab.strtab.shstrtab.note.gnu.build-id.interp.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.text.fini.rodata.eh_frame_hdr.eh_frame.note.ABI-tag.init_array.fini_array.dynamic.got.got.plt.data.bss.commentpp$.��6���o��@��@H�P���o��0]���o((0lXX�vBHH����{		0�@
@
d@������g�((D�pp,��� ���������������������@�������������0���	D	�
�

layers.cDescargar layers.c
// layers.c: abre una captura y muestra el tamaño de cada capa de cada paquete.
//
//   ./layers <captura.pcap>
//
// Lee el formato clásico de libpcap byte a byte, sin copiar structs desde el
// archivo: los encabezados de red tienen un orden de bytes y un formato definidos,
// y una struct de C no garantiza ninguno de los dos. Entiende capturas de Ethernet
// y de Linux cooked capture (SLL y SLL2), que es lo que produce tcpdump -i any.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define PCAP_HEADER_SIZE 24
#define RECORD_HEADER_SIZE 16
#define LINKTYPE_ETHERNET 1
#define LINKTYPE_SLL 113
#define LINKTYPE_SLL2 276
#define ETHERTYPE_IPV4 0x0800
#define IP_PROTO_ICMP 1
#define IP_PROTO_TCP 6
#define IP_PROTO_UDP 17
#define TCP_MIN_CAPTURED 14 // hasta el byte de flags
#define UDP_HEADER_SIZE 8
#define ICMP_HEADER_SIZE 8

// Los campos de red van con el byte más significativo primero.
static uint32_t read_be16(const uint8_t *p) {
    return (uint32_t)p[0] << 8 | p[1];
}

// El encabezado de la captura usa el orden de bytes de quien la escribió.
static uint32_t read_u32(const uint8_t *p, int big_endian) {
    if (big_endian) {
        return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | p[3];
    }
    return (uint32_t)p[3] << 24 | (uint32_t)p[2] << 16 | (uint32_t)p[1] << 8 | p[0];
}

// Tamaño del encabezado de enlace y posición del campo que dice qué lleva dentro.
static int link_layout(uint32_t linktype, size_t *size, size_t *ethertype_at) {
    switch (linktype) {
    case LINKTYPE_ETHERNET:
        *size = 14;
        *ethertype_at = 12;
        return 0;
    case LINKTYPE_SLL:
        *size = 16;
        *ethertype_at = 14;
        return 0;
    case LINKTYPE_SLL2:
        *size = 20;
        *ethertype_at = 0;
        return 0;
    default:
        return -1;
    }
}

// Imprime los flags de TCP más comunes, en una sola palabra.
static void print_tcp_flags(uint8_t flags) {
    printf(" [");
    if (flags & 0x02) putchar('S');
    if (flags & 0x01) putchar('F');
    if (flags & 0x04) putchar('R');
    if (flags & 0x08) putchar('P');
    if (flags & 0x10) putchar('A');
    putchar(']');
}

// Muestra el contenido de un paquete IPv4: `ip` apunta a su primer byte.
static void print_ipv4(const uint8_t *ip, size_t available) {
    if (available < 20) {
        printf(" paquete IPv4 recortado\n");
        return;
    }
    size_t ip_size = (size_t)(ip[0] & 0x0f) * 4;
    size_t total = read_be16(ip + 2);
    uint8_t protocol = ip[9];
    if (ip_size < 20 || total < ip_size || available < ip_size) {
        printf(" ip=%zu mal formado\n", ip_size);
        return;
    }
    const uint8_t *transport = ip + ip_size;
    size_t transport_available = available - ip_size;
    size_t transport_size;
    const char *name;
    switch (protocol) {
    case IP_PROTO_TCP:
        if (transport_available < TCP_MIN_CAPTURED) {
            printf(" ip=%zu tcp recortado\n", ip_size);
            return;
        }
        name = "tcp";
        transport_size = (size_t)(transport[12] >> 4) * 4;
        break;
    case IP_PROTO_UDP:
        name = "udp";
        transport_size = UDP_HEADER_SIZE;
        break;
    case IP_PROTO_ICMP:
        name = "icmp";
        transport_size = ICMP_HEADER_SIZE;
        break;
    default:
        printf(" ip=%zu protocolo=%u\n", ip_size, protocol);
        return;
    }
    // El largo de los datos sale de IP, no de la captura: un frame puede traer
    // relleno al final, y ese relleno no es de la aplicación.
    if (total < ip_size + transport_size) {
        printf(" ip=%zu %s mal formado\n", ip_size, name);
        return;
    }
    printf(" ip=%zu %s=%zu app=%zu ttl=%u %u.%u.%u.%u > %u.%u.%u.%u", ip_size, name, transport_size,
           total - ip_size - transport_size, ip[8], ip[12], ip[13], ip[14], ip[15], ip[16], ip[17],
           ip[18], ip[19]);
    if (protocol == IP_PROTO_TCP) {
        print_tcp_flags(transport[13]);
    }
    putchar('\n');
}

// Lee exactamente n bytes. Devuelve 1 si pudo, 0 si el archivo terminó justo
// antes, y termina el programa si quedó cortado a la mitad o hubo un error.
static int read_exact(FILE *file, void *buffer, size_t n) {
    size_t got = fread(buffer, 1, n, file);
    if (got == n) {
        return 1;
    }
    if (ferror(file)) {
        perror("layers: fread");
        exit(EXIT_FAILURE);
    }
    if (got == 0) {
        return 0;
    }
    fprintf(stderr, "layers: la captura termina a la mitad de un registro\n");
    exit(EXIT_FAILURE);
}

// Abre el archivo y lee su encabezado. Deja el tipo de enlace y el orden de bytes.
static FILE *open_capture(const char *path, uint32_t *linktype, int *big_endian) {
    FILE *file = fopen(path, "rb");
    if (file == NULL) {
        perror(path);
        exit(EXIT_FAILURE);
    }
    uint8_t header[PCAP_HEADER_SIZE];
    if (!read_exact(file, header, sizeof header)) {
        fprintf(stderr, "layers: %s está vacío\n", path);
        exit(EXIT_FAILURE);
    }
    uint32_t magic = read_u32(header, 0);
    if (magic == 0xa1b2c3d4 || magic == 0xa1b23c4d) {
        *big_endian = 0;
    } else if (magic == 0xd4c3b2a1 || magic == 0x4d3cb2a1) {
        *big_endian = 1;
    } else {
        fprintf(stderr, "layers: %s no es una captura pcap clásica\n", path);
        exit(EXIT_FAILURE);
    }
    *linktype = read_u32(header + 20, *big_endian);
    return file;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "uso: %s <captura.pcap>\n", argv[0]);
        return EXIT_FAILURE;
    }
    uint32_t linktype;
    int big_endian;
    FILE *file = open_capture(argv[1], &linktype, &big_endian);

    size_t link_size, ethertype_at;
    if (link_layout(linktype, &link_size, &ethertype_at) != 0) {
        fprintf(stderr, "layers: tipo de enlace %u no soportado\n", linktype);
        fclose(file);
        return EXIT_FAILURE;
    }

    uint8_t record[RECORD_HEADER_SIZE];
    uint8_t *packet = NULL;
    size_t capacity = 0;
    long number = 0;
    while (read_exact(file, record, sizeof record)) {
        size_t captured = read_u32(record + 8, big_endian);
        size_t on_wire = read_u32(record + 12, big_endian);
        if (captured > capacity) {
            uint8_t *bigger = realloc(packet, captured);
            if (bigger == NULL) {
                perror("layers: realloc");
                free(packet);
                fclose(file);
                return EXIT_FAILURE;
            }
            packet = bigger;
            capacity = captured;
        }
        if (captured > 0 && !read_exact(file, packet, captured)) {
            fprintf(stderr, "layers: la captura termina a la mitad de un paquete\n");
            free(packet);
            fclose(file);
            return EXIT_FAILURE;
        }
        number++;
        printf("%4ld total=%-5zu enlace=%zu", number, on_wire, link_size);
        if (captured < link_size) {
            printf(" recortado\n");
        } else if (read_be16(packet + ethertype_at) == ETHERTYPE_IPV4) {
            print_ipv4(packet + link_size, captured - link_size);
        } else {
            printf(" no es IPv4 (ethertype 0x%04x)\n", read_be16(packet + ethertype_at));
        }
    }
    free(packet);
    if (fclose(file) != 0) {
        perror("layers: fclose");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
multiplexDescargar multiplex
ELF�
@�@8
@@@@00����
�
����������������ppp$$�
�
�
  P�tdXXX<<Q�tdR�td������PPGNU��r�N�78&� "�<KL�/lib/ld-linux-aarch64.so.1
XPX Jy"jF �Q�, rq_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTablepowstrtodstrtol__libc_start_mainstderrfprintf__cxa_finalizefwriteabortlibm.so.6libc.so.6GLIBC_2.29GLIBC_2.17GLIBC_2.34� ��������������� ���
��@XX��������	��
 	(
08@H?#��{�������{���#��_��{�����G��?� � � � ��@�� ��@�"� ��
@�B� ��@�b� ��@��� ��@��� ��@��� ��@��� ��"@�� ��&@�"� ��{�����S���q�T���G��b@�! -�@����� �R�SA��{Ũ�_��7m���?m`@�����@``
@�������
@``@��ҿ���@``@�B�R�������!`���T�!`�T�!`ITn�!l�T���*qMT�m�@/��x�*����mb�*��/��	o�����9o�A`�A`����@`�/�R`K?kMT(~!b=b�}�o�l��k���T�*��0������@��R�7Cm�?Dm����@����c�G� .����!��c@������7Cm�?Dm��� � � � � � � � � � �_$��������@��#������G�����]���p������G�@�h���_� � � � � � �����!��?��T��!�G�a�����_�����!��!�"��A��!�A�����B�G�b�����_�?#��{�������`�A9@7���G����,@�1������� �R`�9�@��{¨�#��_� �_$����?#��{�����{���#��_�uso: %s <Mbps enlace> <Mbps usuario> <p> <usuarios>
multiplex: se necesita enlace y usuario > 0, 0 < p < 1, usuarios > 0
con circuitos caben: %d usuarios
con paquetes y %d usuarios: en promedio %.1f activos
P(mas de %d activos a la vez) = %.3e
;<���������T���h8���|t���������zRxL���4A,����0@����<$T����PA-A ��B�N���A-|���P�����AP�
�	B��M
����AAMLBONZ�h
�BMLAONAA�IMLAONGNU �
��X
(�������o�P�
����h��	���o���o@���o���o���o��@����������XGCC: (Debian 14.2.0-19) 14.2.0p���P@�	h
X�@
(@X��
����������P`��	
��
�
 @��	4
 4
	
X	
(-��	
h	
44��H	@(
@��	`
K`
M�
X`�
v`������ ������`@��|
�|
��������X�����	�1 � PM_`m"��h�`��
(�h�P� �X��@
h�
4!h)`5@�:L`X yr
XxScrt1.o$x$d__abi_tagcrti.ocall_weak_fncrtn.omultiplex.ccrtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.0__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entry__FRAME_END___DYNAMIC__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE___libc_start_main@GLIBC_2.34_ITM_deregisterTMCloneTablestrtod@GLIBC_2.17__bss_start____cxa_finalize@GLIBC_2.17stderr@GLIBC_2.17_edatapow@GLIBC_2.29_fini__bss_end____data_start__gmon_start____dso_handleabort@GLIBC_2.17_IO_stdin_usedstrtol@GLIBC_2.17_end__end____bss_startmainfwrite@GLIBC_2.17__TMC_END___ITM_registerTMCloneTable_initfprintf@GLIBC_2.17.symtab.strtab.shstrtab.note.gnu.build-id.interp.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.text.fini.rodata.eh_frame_hdr.eh_frame.note.ABI-tag.init_array.fini_array.dynamic.got.got.plt.data.bss.commentpp$.��6���o��@���HPP�P���o ]���o@@Pl���vBhh��XX{����@@�@�((�@@�XX<������
�
 ���������������������8�����h�PP�``�0`��B	h	��
multiplex.cDescargar multiplex.c
// multiplex.c: multiplexación estadística frente a circuitos.
//
//   ./multiplex <Mbps del enlace> <Mbps por usuario> <p> <usuarios>
//
// Cada usuario transmite a su velocidad una fracción p del tiempo, de forma
// independiente de los demás. El número de usuarios activos sigue entonces una
// distribución binomial. El programa calcula cuántos caben con circuitos y qué
// tan probable es que, con conmutación de paquetes, se activen más de los que
// el enlace puede atender.
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

// Devuelve P(X > limit) para X ~ Binomial(users, p), con 0 < p < 1.
// Cada término sale del anterior: P(k+1) = P(k) * (users-k)/(k+1) * p/(1-p).
static double tail_probability(int users, double p, int limit) {
    double term = pow(1 - p, users);
    double tail = 0;
    for (int k = 0; k <= users; k++) {
        if (k > limit) {
            tail += term;
        }
        term *= (double)(users - k) / (k + 1) * p / (1 - p);
    }
    return tail;
}

int main(int argc, char *argv[]) {
    if (argc != 5) {
        fprintf(stderr, "uso: %s <Mbps enlace> <Mbps usuario> <p> <usuarios>\n", argv[0]);
        return EXIT_FAILURE;
    }
    double link = atof(argv[1]);
    double per_user = atof(argv[2]);
    double p = atof(argv[3]);
    int users = atoi(argv[4]);
    if (link <= 0 || per_user <= 0 || p <= 0 || p >= 1 || users <= 0) {
        fprintf(stderr, "multiplex: se necesita enlace y usuario > 0, 0 < p < 1, usuarios > 0\n");
        return EXIT_FAILURE;
    }

    int circuits = (int)(link / per_user);
    printf("con circuitos caben: %d usuarios\n", circuits);
    printf("con paquetes y %d usuarios: en promedio %.1f activos\n", users, users * p);
    printf("P(mas de %d activos a la vez) = %.3e\n", circuits, tail_probability(users, p, circuits));
    return EXIT_SUCCESS;
}
queue_simDescargar queue_sim
ELF��@x
@8
@@@@00���������������������ppp$$���  P�td���<<Q�tdR�td������PPGNU�'e�ZT2�U���F-�s���/lib/ld-linux-aarch64.so.1
�`X Jy"j�F ��Q�, rq_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTablelogstrtodstrtol__libc_start_mainstderrfprintf__cxa_finalizefwritestrcmpabortstrncmplibm.so.6libc.so.6GLIBC_2.29GLIBC_2.17GLIBC_2.34� ����������������
��L
��	hh��������
�� 	(
08@
HPX?#��{����8��{���#��_��{�����G��?� � � � ��@�� ��@�"� ��
@�B� ��@�b� ��@��� ��@��� ��@��� ��@��� ��"@�� ��&@�"� ��*@�B� ��.@�b� ��{�����S��*Q��q(
T�[��! 7��c�u@��������
5���R�k��/m��`
@�����@`�qA
T `�TВ������+��'m�7m�?	m�/��ҔH�ҹ����w��
g���������9����A`�A`�A`XG�������4'����	n?k�9�!`Mo����d�b��#~*
T�!nJ)o���n�)is�_�T�q@
T�
q���T`ך�����b��k����[B��cC� � � �����G�!�7�b@�@����� �R�SA��{ʨ�_����!`7�z����4���!�7����d������5��B�R����r�������T��B�R��U�R�k��/mi����������5�R�k��/m���`@�B�R��^������q�TВ���x!`IT�����c�G��b���8�!��c@�R����/Gm�[B��cC��kD�����2TʔfʔnTʁ~�?g��u�a^m(l/���@a�k���{�����/�����b�Acb� ��g�b��A``A`��9�c~c2����+@��R�[B��cC��kD��'Fm�/Gm�7Hm�?Im���_�-��T�+��'m�7m�?	mX���/���`@�B�R���������� � � � � � � � � � � � �_$��������@��#������G����������������G�@�����_� � � � � � �����!��?��T��!�G�a�����_�����!��!�"��A��!�A�����B�G�b�����_�?#��{�������`�A9@7���G����4@��������� �R`�9�@��{¨�#��_� �_$����?#��{�����{���#��_�periodicpoissonburst:uso: %s <periodic|poisson|burst:N> <rho> [buffer] [paquetes]
queue_sim: se necesita rho > 0, buffer >= 0 y paquetes > 0
rho=%.2f  retardo_medio=%10.2f  retardo_max=%10.2f  perdida=%5.1f%%
��ؗ�Ҝ<;<H��������T(���hX���|����������zRxl���4A,����0@����<$T����PA-A ��B�N���A-|(���(�p���PA���B��F��C�
�G��AKJL�
AIHAMLAONp���HIJKLMNOA��A��M����A�������
��W
��AKJCC��AKJVKJA��A��A��A����
���
HIJKLMNO`�B��A��A��AIHAKJAMLAONA����
��JKC�
AIHAMLAONC�HILMNOGNU�
L
���
�
�������o���
��� ���	���o���o����o���oZ���o��	������������hGCC: (Debian 14.2.0-19) 14.2.0p����Z��	�
��	
�
�
�������������`p��	��� �
��	� �	
�	
�
-��	
�	
�
4���
		��@��	�K�M
h`L
vp�������
����� p@�������������������	�1 � `M_pm"��x�p�
�
�x���`� �h��
/Ax��4FxNpZ	P_qp} ��
��Scrt1.o$x$d__abi_tagcrti.ocall_weak_fncrtn.oqueue_sim.ccrtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.0__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entry__FRAME_END___DYNAMIC__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE___libc_start_main@GLIBC_2.34_ITM_deregisterTMCloneTablestrtod@GLIBC_2.17__bss_start____cxa_finalize@GLIBC_2.17stderr@GLIBC_2.17_edata_fini__bss_end__strncmp@GLIBC_2.17log@GLIBC_2.29__data_start__gmon_start____dso_handleabort@GLIBC_2.17_IO_stdin_usedstrcmp@GLIBC_2.17strtol@GLIBC_2.17_end__end____bss_startmainfwrite@GLIBC_2.17__TMC_END___ITM_registerTMCloneTable_initfprintf@GLIBC_2.17.symtab.strtab.shstrtab.note.gnu.build-id.interp.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.text.fini.rodata.eh_frame_hdr.eh_frame.note.ABI-tag.init_array.fini_array.dynamic.got.got.plt.data.bss.commentpp$.��6���o��@���H���P���oZZ$]���o��Pl���vB�� ���{����		�@��
�
��
�
����<������� ���������������������8�����x�``�pp�0p�0	C	�	�p
queue_sim.cDescargar queue_sim.c
// queue_sim.c: simula la cola de salida de un router.
//
//   ./queue_sim <llegadas> <rho> [buffer] [paquetes]
//
// Todos los paquetes tienen el mismo tamaño L y el enlace transmite a R, así que
// medimos el tiempo en unidades de L/R: transmitir un paquete toma exactamente 1.
//
//   llegadas   periodic   un paquete cada 1/rho, sin azar
//              poisson    tiempos entre llegadas exponenciales, de media 1/rho
//              burst:N    ráfagas de N paquetes juntos, una cada N/rho
//   rho        intensidad de tráfico La/R
//   buffer     cuántos paquetes caben, contando el que se transmite (0 = sin límite)
//   paquetes   cuántos paquetes simular (por defecto, diez millones)
//
// El retardo que se informa es solo el de cola: lo que el paquete espera antes
// de empezar a transmitirse. La semilla es fija, así que la salida se repite.
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEFAULT_PACKETS 10000000L
#define SEED 88172645463325252ULL

enum arrivals { PERIODIC, POISSON, BURST };

struct config {
    enum arrivals arrivals;
    long burst_size;
    double rho;
    long buffer;
    long packets;
};

// Generador xorshift64*: uniforme en (0, 1]. Propio para no depender de la
// biblioteca de C y obtener lo mismo en todas las máquinas.
static double next_uniform(uint64_t *state) {
    uint64_t x = *state;
    x ^= x >> 12;
    x ^= x << 25;
    x ^= x >> 27;
    *state = x;
    return ((x * 2685821657736338717ULL) >> 11) * (1.0 / 9007199254740992.0) + 1e-16;
}

// Tiempo entre la llegada del paquete i-1 y la del paquete i.
static double next_gap(const struct config *cfg, long i, uint64_t *state) {
    switch (cfg->arrivals) {
    case PERIODIC:
        return 1 / cfg->rho;
    case POISSON:
        return -log(next_uniform(state)) / cfg->rho;
    case BURST:
        return i % cfg->burst_size == 0 ? cfg->burst_size / cfg->rho : 0;
    }
    return 0;
}

static void simulate(const struct config *cfg) {
    uint64_t state = SEED;
    double backlog = 0; // trabajo pendiente en el enlace, en unidades de L/R
    double total_delay = 0;
    double max_delay = 0;
    long sent = 0;
    long dropped = 0;

    for (long i = 0; i < cfg->packets; i++) {
        backlog -= next_gap(cfg, i, &state);
        if (backlog < 0) {
            backlog = 0;
        }
        // Con paquetes iguales, ceil(backlog) es cuántos hay en el buffer.
        if (cfg->buffer > 0 && ceil(backlog) >= cfg->buffer) {
            dropped++;
            continue;
        }
        total_delay += backlog;
        if (backlog > max_delay) {
            max_delay = backlog;
        }
        backlog += 1;
        sent++;
    }
    printf("rho=%.2f  retardo_medio=%10.2f  retardo_max=%10.2f  perdida=%5.1f%%\n",
           cfg->rho, sent > 0 ? total_delay / sent : 0.0, max_delay,
           100.0 * dropped / cfg->packets);
}

// Lee el tipo de llegadas. Devuelve 0 si el texto es válido.
static int parse_arrivals(const char *text, struct config *cfg) {
    if (strcmp(text, "periodic") == 0) {
        cfg->arrivals = PERIODIC;
    } else if (strcmp(text, "poisson") == 0) {
        cfg->arrivals = POISSON;
    } else if (strncmp(text, "burst:", 6) == 0 && atol(text + 6) > 0) {
        cfg->arrivals = BURST;
        cfg->burst_size = atol(text + 6);
    } else {
        return -1;
    }
    return 0;
}

int main(int argc, char *argv[]) {
    struct config cfg = {.packets = DEFAULT_PACKETS};
    if (argc < 3 || argc > 5 || parse_arrivals(argv[1], &cfg) != 0) {
        fprintf(stderr, "uso: %s <periodic|poisson|burst:N> <rho> [buffer] [paquetes]\n", argv[0]);
        return EXIT_FAILURE;
    }
    cfg.rho = atof(argv[2]);
    if (argc >= 4) {
        cfg.buffer = atol(argv[3]);
    }
    if (argc == 5) {
        cfg.packets = atol(argv[4]);
    }
    if (cfg.rho <= 0 || cfg.buffer < 0 || cfg.packets <= 0) {
        fprintf(stderr, "queue_sim: se necesita rho > 0, buffer >= 0 y paquetes > 0\n");
        return EXIT_FAILURE;
    }
    simulate(&cfg);
    return EXIT_SUCCESS;
}
topo/path.clab.ymlDescargar topo/path.clab.yml
# Un camino de dos hosts y tres routers, para medir retardo, throughput y colas,
# y para ver qué cambia en un paquete cuando cruza un router.
#
#   h1 --- r1 --- r2 --- r3 --- h2
#
# Las velocidades y los retardos de los enlaces no se definen aquí: los pone
# ../experiments.py con tc, porque cada medición usa valores distintos.
# La ruta de cada nodo está en su frr.conf: rutas estáticas, escritas a mano.
name: w01-path
mgmt:
  network: redes-w01
  ipv4-subnet: 172.90.1.0/24
  ipv6-subnet: 3fff:172:90:1::/64
topology:
  defaults:
    kind: linux
    image: redes-router
  nodes:
    h1:
      binds: [h1/frr.conf:/etc/frr/frr.conf:ro]
    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]
    h2:
      binds:
        - h2/frr.conf:/etc/frr/frr.conf:ro
        - h2/web:/srv/web
  # mtu 1500: el tamaño de un frame Ethernet normal. Sin esto, Containerlab
  # usa 9500 y las capturas no se parecen a las de una red real.
  links:
    - type: veth
      mtu: 1500
      endpoints:
        - {node: h1, interface: eth1}
        - {node: r1, interface: eth1}
    - type: veth
      mtu: 1500
      endpoints:
        - {node: r1, interface: eth2}
        - {node: r2, interface: eth1}
    - type: veth
      mtu: 1500
      endpoints:
        - {node: r2, interface: eth2}
        - {node: r3, interface: eth1}
    - type: veth
      mtu: 1500
      endpoints:
        - {node: r3, interface: eth2}
        - {node: h2, interface: eth1}
topo/h1/frr.confDescargar topo/h1/frr.conf
frr defaults traditional
hostname h1
interface eth1
 ip address 10.1.0.2/24
ip route 10.0.0.0/8 10.1.0.1
topo/h2/frr.confDescargar topo/h2/frr.conf
frr defaults traditional
hostname h2
interface eth1
 ip address 10.2.0.2/24
ip route 10.0.0.0/8 10.2.0.1
topo/r1/frr.confDescargar topo/r1/frr.conf
frr defaults traditional
hostname r1
interface eth1
 ip address 10.1.0.1/24
interface eth2
 ip address 10.0.12.1/24
ip route 10.0.23.0/24 10.0.12.2
ip route 10.2.0.0/24 10.0.12.2
topo/r2/frr.confDescargar topo/r2/frr.conf
frr defaults traditional
hostname r2
interface eth1
 ip address 10.0.12.2/24
interface eth2
 ip address 10.0.23.2/24
ip route 10.1.0.0/24 10.0.12.1
ip route 10.2.0.0/24 10.0.23.3
topo/r3/frr.confDescargar topo/r3/frr.conf
frr defaults traditional
hostname r3
interface eth1
 ip address 10.0.23.3/24
interface eth2
 ip address 10.2.0.1/24
ip route 10.0.12.0/24 10.0.23.2
ip route 10.1.0.0/24 10.0.23.2
topo/h2/web/index.htmlDescargar topo/h2/web/index.html
<!DOCTYPE html>
<html>
<head><title>h2</title></head>
<body><p>Hola desde h2.</p></body>
</html>

Pruebas

Comprueban lo que afirman los apuntes; se ejecutan con make test.

test_path.pyDescargar test_path.py
"""Pruebas sobre la topología: lo que los apuntes dicen que se mide, se mide.

Otros programas de la máquina agregan ruido, así que los márgenes son amplios: se
revisan tendencias y órdenes de magnitud, no cifras exactas.
"""
import re
import subprocess
import unittest

import experiments as ex

TOPOLOGY = ex.HERE / "topo" / "path.clab.yml"


def destroy():
    subprocess.run(["containerlab", "destroy", "-t", str(TOPOLOGY), "--cleanup"], capture_output=True, check=False)
    subprocess.run(["docker", "network", "rm", "redes-w01"], capture_output=True, check=False)


class PathTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.addClassCleanup(destroy)
        destroy()
        subprocess.run(["containerlab", "deploy", "-t", str(TOPOLOGY)], capture_output=True, check=True)
        ex.wait_until_ready()
        ex.start_iperf_server()

    def setUp(self):
        ex.clear_links()

    def test_ttl_expires_at_each_router(self):
        _, hops = ex.traceroute()
        self.assertEqual([address for address, _ in hops], ["10.1.0.1", "10.0.12.2", "10.0.23.3", "10.2.0.2"])

    def test_rtt_jumps_at_the_long_link(self):
        ex.set_link(ex.CABLE, delay_ms=ex.CABLE_DELAY_MS)
        _, hops = ex.traceroute()
        rtts = [rtt for _, rtt in hops]
        self.assertLess(rtts[1], 15)
        self.assertGreater(rtts[2], 40)
        self.assertLess(rtts[2], 90)
        self.assertGreater(rtts[3], 40)

    def test_size_only_matters_through_transmission(self):
        rtts = ex.rtt_by_size(["r1-r2"])
        self.assertGreater(ex.estimated_rate_mbit(rtts, 1), 2.5)
        self.assertLess(ex.estimated_rate_mbit(rtts, 1), 10)

    def test_store_and_forward_adds_one_transmission_per_link(self):
        one = ex.rtt_by_size(["r1-r2"])
        three = ex.rtt_by_size(["r1-r2", "h1-r1", "r3-h2"])
        ratio = (three[1472] - three[64]) / (one[1472] - one[64])
        self.assertGreater(ratio, 2)
        self.assertLess(ratio, 5)

    def test_propagation_does_not_depend_on_size(self):
        without = ex.rtt_by_size(["r1-r2"])
        cable = ex.rtt_by_size(["r1-r2"], cable=True)
        differences = [cable[size] - without[size] for size in ex.SIZES]
        for difference in differences:
            self.assertGreater(difference, 35)
            self.assertLess(difference, 65)
        self.assertLess(max(differences) - min(differences), 12)

    def test_slowest_link_limits_throughput_wherever_it_is(self):
        for slow in ("r1-r2", "r2-r3", "r3-h2"):
            with self.subTest(slow=slow):
                ex.clear_links()
                for link in ("r1-r2", "r2-r3", "r3-h2"):
                    ex.set_link(link, rate_mbit=50 if link == slow else 200)
                measured = ex.tcp_throughput_mbit(seconds=4)
                self.assertGreater(measured, 25)
                self.assertLess(measured, 52)

    def test_faster_bottleneck_gives_more_throughput(self):
        ex.set_link("r1-r2", rate_mbit=20)
        slow = ex.tcp_throughput_mbit(seconds=3)
        ex.clear_links()
        ex.set_link("r1-r2", rate_mbit=100)
        fast = ex.tcp_throughput_mbit(seconds=3)
        self.assertLess(slow, 21)
        self.assertGreater(fast, 2 * slow)

    def test_queue_fills_and_drops_above_one(self):
        ex.set_link("r1-r2", rate_mbit=ex.QUEUE_RATE_MBIT, limit=ex.QUEUE_LIMIT)
        light = ex.udp_load(0.5)
        heavy = ex.udp_load(1.2)
        self.assertLess(light["lost"], 2)
        self.assertLess(light["avg"], 15)
        self.assertGreater(heavy["lost"], 8)
        self.assertLess(heavy["lost"], 30)
        # Buffer lleno: 200 paquetes de 1442 bytes a 50 Mbps son 46 ms.
        self.assertGreater(heavy["avg"], 30)
        self.assertLess(heavy["avg"], 90)

    def test_bursts_queue_at_the_same_intensity(self):
        ex.set_link("r1-r2", rate_mbit=ex.QUEUE_RATE_MBIT, limit=ex.QUEUE_LIMIT)
        bursts = ex.udp_load(0.5, burst=100)
        # Una ráfaga de 100 paquetes de 1442 bytes tarda 23 ms en salir.
        self.assertGreater(bursts["max"], 10)
        self.assertLess(bursts["lost"], 2)

    def test_router_changes_link_and_ttl_but_not_addresses_or_transport(self):
        paths = ex.capture_web()
        near_h1 = ex.packet_fields(paths["near-h1"])
        near_h2 = ex.packet_fields(paths["near-h2"])
        self.assertGreaterEqual(len(near_h1), 6)
        self.assertEqual(len(near_h1), len(near_h2))
        # Se comparan solo los paquetes de h1: en cada punto de captura, un paquete
        # de ida y uno de vuelta pueden verse en distinto orden.
        sent_near_h1 = [packet for packet in near_h1 if packet["ip.src"] == "10.1.0.2"]
        sent_near_h2 = [packet for packet in near_h2 if packet["ip.src"] == "10.1.0.2"]
        self.assertEqual(len(sent_near_h1), len(sent_near_h2))
        from_h1 = list(zip(sent_near_h1, sent_near_h2))
        self.assertTrue(from_h1)
        for before, after in from_h1:
            self.assertNotEqual(before["eth.src"], after["eth.src"])
            self.assertNotEqual(before["eth.dst"], after["eth.dst"])
            self.assertEqual(int(before["ip.ttl"]) - int(after["ip.ttl"]), 3)
            for field in ("ip.src", "ip.dst", "tcp.seq_raw", "tcp.len"):
                self.assertEqual(before[field], after[field])

    def test_layers_agrees_with_tshark(self):
        paths = ex.capture_web()
        out = subprocess.run([str(ex.HERE / "layers"), str(paths["near-h1"])], capture_output=True,
                             text=True, check=True).stdout
        command = ["tshark", "-r", str(paths["near-h1"]), "-T", "fields", "-e", "frame.len", "-e", "ip.hdr_len",
                   "-e", "tcp.hdr_len", "-e", "tcp.len", "-e", "ip.ttl"]
        reference = subprocess.run(command, capture_output=True, text=True, check=True).stdout.splitlines()
        lines = out.splitlines()
        self.assertEqual(len(lines), len(reference))
        for line, fields in zip(lines, reference):
            total, ip_size, tcp_size, app, ttl = fields.split("\t")
            pattern = rf"total={total}\s+enlace=14 ip={ip_size} tcp={tcp_size} app={app} ttl={ttl} "
            self.assertTrue(re.search(pattern, line), f"{line!r} no coincide con {fields!r}")


if __name__ == "__main__":
    unittest.main()
test_programs.pyDescargar test_programs.py
"""Pruebas de los programas en C: las cifras son las que aparecen en los apuntes."""
import re
import struct
import subprocess
import tempfile
import unittest
from pathlib import Path

HERE = Path(__file__).resolve().parent


def run(*args):
    return subprocess.run([str(HERE / args[0]), *map(str, args[1:])], capture_output=True, text=True, check=False)


def number_after(label, text):
    match = re.search(re.escape(label) + r"\s*=?\s*([\d.e+-]+)", text)
    return float(match.group(1))


class DelayTest(unittest.TestCase):
    def test_lima_miami(self):
        out = run("delay", 1500, 100, 4200).stdout
        self.assertAlmostEqual(number_after("transmision L/R", out), 0.120, places=3)
        self.assertAlmostEqual(number_after("propagacion d/s", out), 21.0, places=3)
        self.assertIn("domina la propagacion", out)

    def test_review_question(self):
        out = run("delay", 1000, 10, 2000).stdout
        self.assertAlmostEqual(number_after("transmision L/R", out), 0.8, places=3)
        self.assertAlmostEqual(number_after("propagacion d/s", out), 10.0, places=3)

    def test_short_slow_link_transmission_dominates(self):
        self.assertIn("domina la transmision", run("delay", 1500, 1, 1).stdout)

    def test_custom_speed(self):
        out = run("delay", 1000, 10, 3000, "3e8").stdout
        self.assertAlmostEqual(number_after("propagacion d/s", out), 10.0, places=3)

    def test_rejects_bad_input(self):
        self.assertNotEqual(run("delay", 1500, 0, 10).returncode, 0)
        self.assertNotEqual(run("delay", "mucho", 10, 10).returncode, 0)
        self.assertNotEqual(run("delay").returncode, 0)


class BottleneckTest(unittest.TestCase):
    def test_slowest_link_sets_time(self):
        out = run("bottleneck", 1000, 1000, 50, 200).stdout
        self.assertIn("enlace 2, a 50 Mbps", out)
        self.assertIn("160.00 s", out)

    def test_order_does_not_matter(self):
        for rates in [(50, 200, 1000), (1000, 200, 50)]:
            self.assertIn("160.00 s", run("bottleneck", 1000, *rates).stdout)

    def test_rejects_bad_input(self):
        self.assertNotEqual(run("bottleneck", 1000).returncode, 0)
        self.assertNotEqual(run("bottleneck", 1000, -5).returncode, 0)


class MultiplexTest(unittest.TestCase):
    def tail(self, users, p=0.1):
        return number_after("activos a la vez)", run("multiplex", 1000, 100, p, users).stdout)

    def test_ten_users_fit_with_circuits(self):
        self.assertIn("caben: 10 usuarios", run("multiplex", 1000, 100, 0.1, 40).stdout)

    def test_ten_users_never_overflow(self):
        self.assertEqual(self.tail(10), 0)

    def test_known_tails(self):
        # Calculadas aparte, sumando la binomial con enteros exactos.
        self.assertAlmostEqual(self.tail(20) / 7.089e-07, 1, places=2)
        self.assertAlmostEqual(self.tail(50) / 9.355e-03, 1, places=2)

    def test_more_users_more_risk(self):
        tails = [self.tail(n) for n in (20, 30, 40, 50, 100)]
        self.assertEqual(tails, sorted(tails))

    def test_rejects_bad_input(self):
        self.assertNotEqual(run("multiplex", 1000, 100, 1.5, 10).returncode, 0)
        self.assertNotEqual(run("multiplex", 1000, 100, 0, 10).returncode, 0)


class QueueSimTest(unittest.TestCase):
    def sim(self, *args):
        out = run("queue_sim", *args).stdout
        return (number_after("retardo_medio", out), number_after("retardo_max", out),
                number_after("perdida", out))

    def test_random_arrivals_match_theory(self):
        # Fórmula de la cola M/D/1: espera media = rho / (2 (1 - rho)), en unidades de L/R.
        for rho in (0.3, 0.5, 0.7, 0.8):
            mean, _, _ = self.sim("poisson", rho)
            self.assertAlmostEqual(mean / (rho / (2 * (1 - rho))), 1, delta=0.1)

    def test_delay_grows_with_intensity(self):
        means = [self.sim("poisson", rho)[0] for rho in (0.1, 0.5, 0.8, 0.9, 0.95)]
        self.assertEqual(means, sorted(means))

    def test_periodic_arrivals_do_not_queue(self):
        for rho in (0.5, 0.9, 0.99):
            self.assertEqual(self.sim("periodic", rho)[0], 0)

    def test_bursts_queue_at_low_intensity(self):
        mean, largest, _ = self.sim("burst:10", 0.5)
        self.assertAlmostEqual(mean, 4.5)  # los 10 de la ráfaga esperan 0, 1, ..., 9
        self.assertAlmostEqual(largest, 9)

    def test_above_one_without_limit_delay_keeps_growing(self):
        short = self.sim("periodic", 1.2, 0, 100000)[0]
        long = self.sim("periodic", 1.2, 0, 1000000)[0]
        self.assertAlmostEqual(long / short, 10, delta=0.5)

    def test_above_one_with_buffer_loses_the_excess(self):
        mean, largest, loss = self.sim("poisson", 1.2, 100)
        self.assertAlmostEqual(loss, 100 * (1 - 1 / 1.2), delta=1.5)
        self.assertLess(largest, 100)
        self.assertGreater(mean, 80)

    def test_same_seed_same_result(self):
        self.assertEqual(run("queue_sim", "poisson", 0.7).stdout, run("queue_sim", "poisson", 0.7).stdout)

    def test_rejects_bad_input(self):
        self.assertNotEqual(run("queue_sim", "poisson", 0).returncode, 0)
        self.assertNotEqual(run("queue_sim", "rara", 0.5).returncode, 0)


def build_ipv4_tcp(payload=b"hola", options=b""):
    tcp = struct.pack("!HHIIBBHHH", 40000, 80, 1, 2, (5 + len(options) // 4) << 4, 0x18, 1000, 0, 0) + options
    total = 20 + len(tcp) + len(payload)
    ip = struct.pack("!BBHHHBBH4B4B", 0x45, 0, total, 1, 0, 64, 6, 0, 10, 1, 0, 2, 10, 2, 0, 2)
    return ip + tcp + payload


def build_ipv4_udp(payload=b""):
    udp = struct.pack("!HHHH", 5000, 53, 8 + len(payload), 0)
    ip = struct.pack("!BBHHHBBH4B4B", 0x45, 0, 28 + len(payload), 1, 0, 9, 17, 0, 10, 0, 0, 1, 10, 0, 0, 2)
    return ip + udp + payload


def pcap_bytes(linktype, frames, endian="<"):
    data = struct.pack(endian + "IHHIIII", 0xa1b2c3d4, 2, 4, 0, 0, 65535, linktype)
    for frame in frames:
        data += struct.pack(endian + "IIII", 0, 0, len(frame), len(frame)) + frame
    return data


class LayersTest(unittest.TestCase):
    def layers(self, data):
        with tempfile.NamedTemporaryFile(suffix=".pcap") as file:
            file.write(data)
            file.flush()
            return run("layers", file.name)

    def test_ethernet(self):
        frame = bytes(12) + b"\x08\x00" + build_ipv4_tcp()
        line = self.layers(pcap_bytes(1, [frame])).stdout
        self.assertRegex(line, r"total=58\s+enlace=14 ip=20 tcp=20 app=4 ttl=64 10\.1\.0\.2 > 10\.2\.0\.2 \[PA\]")

    def test_tcp_options_count_in_the_header(self):
        frame = bytes(12) + b"\x08\x00" + build_ipv4_tcp(options=bytes(12))
        self.assertIn("tcp=32 app=4", self.layers(pcap_bytes(1, [frame])).stdout)

    def test_cooked_capture_v1(self):
        frame = bytes(14) + b"\x08\x00" + build_ipv4_tcp()
        self.assertIn("enlace=16 ip=20 tcp=20", self.layers(pcap_bytes(113, [frame])).stdout)

    def test_cooked_capture_v2(self):
        frame = b"\x08\x00" + bytes(18) + build_ipv4_tcp()
        self.assertIn("enlace=20 ip=20 tcp=20", self.layers(pcap_bytes(276, [frame])).stdout)

    def test_short_udp_packet(self):
        frame = bytes(12) + b"\x08\x00" + build_ipv4_udp()
        self.assertIn("udp=8 app=0 ttl=9", self.layers(pcap_bytes(1, [frame])).stdout)

    def test_ethernet_padding_is_not_application_data(self):
        frame = bytes(12) + b"\x08\x00" + build_ipv4_udp() + bytes(18)
        self.assertIn("udp=8 app=0", self.layers(pcap_bytes(1, [frame])).stdout)

    def test_big_endian_capture(self):
        frame = bytes(12) + b"\x08\x00" + build_ipv4_tcp()
        self.assertIn("tcp=20 app=4", self.layers(pcap_bytes(1, [frame], endian=">")).stdout)

    def test_not_ipv4(self):
        frame = bytes(12) + b"\x08\x06" + bytes(28)
        self.assertIn("0x0806", self.layers(pcap_bytes(1, [frame])).stdout)

    def test_truncated_ipv4(self):
        frame = bytes(12) + b"\x08\x00" + bytes(5)
        result = self.layers(pcap_bytes(1, [frame]))
        self.assertEqual(result.returncode, 0)
        self.assertIn("recortado", result.stdout)

    def test_rejects_other_files(self):
        self.assertNotEqual(self.layers(b"esto no es una captura, aunque sea largo").returncode, 0)
        self.assertNotEqual(self.layers(b"").returncode, 0)
        self.assertNotEqual(run("layers", "/no/existe.pcap").returncode, 0)

    def test_rejects_record_cut_in_half(self):
        frame = bytes(12) + b"\x08\x00" + build_ipv4_tcp()
        self.assertNotEqual(self.layers(pcap_bytes(1, [frame])[:-5]).returncode, 0)


if __name__ == "__main__":
    unittest.main()

Compilación

Cómo se compilan y ejecutan los ejemplos.

MakefileDescargar Makefile
# Semana 1: retardo, multiplexación estadística, colas y encapsulamiento.
# Se usa dentro del contenedor del curso.
#
#   make              compila los programas en C
#   make test         compila y corre las pruebas; levanta la topología, mide
#                     sobre ella y la destruye siempre, aunque algo falle
#   make up           levanta la topología topo/path.clab.yml para el laboratorio
#   make down         destruye la topología y su red de Docker
#   make clean        hace lo mismo que down y borra lo generado
#
# Con la topología arriba, las mediciones se repiten con
#   python3 experiments.py [cable|size|bottleneck|queue|web]

CC = gcc
CFLAGS = -Wall -Wextra -std=c17 -O2
LDLIBS = -lm
PROGRAMS = delay bottleneck multiplex queue_sim layers
TOPOLOGY = topo/path.clab.yml
NETWORK = redes-w01

.PHONY: all test up down clean

all: $(PROGRAMS)

test: all
	python3 -m unittest -v; status=$$?; $(MAKE) --no-print-directory down; exit $$status

up:
	containerlab deploy --reconfigure -t $(TOPOLOGY)

down:
	@containerlab destroy -t $(TOPOLOGY) --cleanup >/dev/null 2>&1 || true
	@docker network rm $(NETWORK) >/dev/null 2>&1 || true

clean: down
	rm -f $(PROGRAMS)
	rm -rf captures __pycache__