Apuntes DAM
Volver al inicio

Tabla MAC de un switch: aprender, reenviar e inundar

Ejercicio de PythonDifícilUnos 70 minutos

Simula cómo trabaja un switch Ethernet: aprende de qué puerto cuelga cada MAC de origen, reenvía las tramas al puerto correcto, inunda las de destino desconocido y las de difusión, filtra las del mismo puerto y borra las entradas que caducan. Diccionarios, validación de MAC y simulación por tiempos.

  • Tabla de direcciones MAC
  • Aprendizaje por la MAC de origen
  • Reenvío, filtrado e inundación
  • Difusión y multidifusión
  • Envejecimiento de entradas
  • Diccionarios

Enunciado

Un switch no sabe nada al encenderse. Aprende mirando las tramas que le llegan: si por el puerto 3 entra una trama con origen 00:1a:2b:00:00:07, apunta que esa MAC está detrás del puerto 3. Con esa tabla decide qué hacer con cada trama según su MAC de destino.

Si conoce el destino, la reenvía solo por su puerto (o la descarta si el destino está en el mismo puerto por el que entró: filtrado). Si no lo conoce, la saca por todos los puertos menos el de entrada (inundación). Las tramas de difusión (ff:ff:ff:ff:ff:ff) y de multidifusión van siempre a todos. Y como los equipos se mueven o se apagan, cada entrada caduca si no se ve en un tiempo (el envejecimiento, 300 segundos por defecto).

Qué tiene que hacer el programa

  1. PUERTOS n fija el número de puertos (del 1 al n) y ENVEJECIMIENTO s los segundos que dura una entrada sin verse (300 si no se indica).
  2. Cada trama es tiempo puerto origen destino. Las MAC se aceptan con : o - y en mayúsculas o minúsculas, y se escriben siempre en minúsculas con :.
  3. Antes de procesar una trama se borran las entradas que llevan más de s segundos sin verse (estrictamente más), escribiendo t=T: caduca MAC por cada una.
  4. Después se aprende el origen: si ya estaba en otro puerto, se escribe t=T: MAC se ha movido del puerto A al B.
  5. La decisión se escribe como t=T origen → destino (puerto P): … con uno de estos finales: difusión a 2, 3, 4, multidifusión a … (primer octeto impar), reenviada al puerto Q, filtrada (el destino está en el mismo puerto) o destino desconocido, inundación a … (los puertos menos el de entrada, en orden).
  6. Errores (la trama se ignora): Línea N: el puerto P no existe (el switch tiene X), Línea N: MAC no válida, Línea N: el origen MAC es una dirección de grupo; ninguna tarjeta la usa como origen, Línea N: el tiempo no puede ir hacia atrás (T < U) y Línea N: no se entiende «…».
  7. TABLA t borra lo caducado en ese instante y escribe Tabla MAC en t=T (E entradas): y una línea por entrada, ordenadas por puerto y MAC: dos espacios, la MAC, dos espacios, puerto P, dos espacios y hace S s.
  8. Al final: Resumen: N tramas, R reenviadas, I inundadas, F filtradas, D de difusión (las tramas con error no cuentan; la multidifusión cuenta como difusión).

Entrada

PUERTOS n y opcionalmente ENVEJECIMIENTO s al principio.

Una trama por línea: tiempo puerto mac_origen mac_destino (el tiempo en segundos, sin bajar).

TABLA t para ver la tabla en el instante t.

Ejemplos de ejecución

Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.

Una red de cuatro equipos

Entrada

PUERTOS 4
0 1 00:1a:2b:00:00:01 ff:ff:ff:ff:ff:ff
5 2 00:1A:2B:00:00:02 00:1a:2b:00:00:01
6 1 00:1a:2b:00:00:01 00:1a:2b:00:00:02
10 3 00-1a-2b-00-00-03 00:1a:2b:00:00:04
12 1 00:1a:2b:00:00:05 00:1a:2b:00:00:01
TABLA 20

Salida por consola

t=0 00:1a:2b:00:00:01 → ff:ff:ff:ff:ff:ff (puerto 1): difusión a 2, 3, 4
t=5 00:1a:2b:00:00:02 → 00:1a:2b:00:00:01 (puerto 2): reenviada al puerto 1
t=6 00:1a:2b:00:00:01 → 00:1a:2b:00:00:02 (puerto 1): reenviada al puerto 2
t=10 00:1a:2b:00:00:03 → 00:1a:2b:00:00:04 (puerto 3): destino desconocido, inundación a 1, 2, 4
t=12 00:1a:2b:00:00:05 → 00:1a:2b:00:00:01 (puerto 1): filtrada (el destino está en el mismo puerto)
Tabla MAC en t=20 (4 entradas):
  00:1a:2b:00:00:01  puerto 1  hace 14 s
  00:1a:2b:00:00:05  puerto 1  hace 8 s
  00:1a:2b:00:00:02  puerto 2  hace 15 s
  00:1a:2b:00:00:03  puerto 3  hace 10 s
Resumen: 5 tramas, 2 reenviadas, 1 inundadas, 1 filtradas, 1 de difusión

Envejecimiento, movimiento y multidifusión

Entrada

PUERTOS 3
ENVEJECIMIENTO 60
0 1 00:00:00:00:00:0a 00:00:00:00:00:0b
10 2 00:00:00:00:00:0b 00:00:00:00:00:0a
50 3 00:00:00:00:00:0a 01:00:5e:00:00:01
100 1 00:00:00:00:00:0c 00:00:00:00:00:0b
TABLA 120

Salida por consola

t=0 00:00:00:00:00:0a → 00:00:00:00:00:0b (puerto 1): destino desconocido, inundación a 2, 3
t=10 00:00:00:00:00:0b → 00:00:00:00:00:0a (puerto 2): reenviada al puerto 1
t=50: 00:00:00:00:00:0a se ha movido del puerto 1 al 3
t=50 00:00:00:00:00:0a → 01:00:5e:00:00:01 (puerto 3): multidifusión a 1, 2
t=100: caduca 00:00:00:00:00:0b
t=100 00:00:00:00:00:0c → 00:00:00:00:00:0b (puerto 1): destino desconocido, inundación a 2, 3
t=120: caduca 00:00:00:00:00:0a
Tabla MAC en t=120 (1 entrada):
  00:00:00:00:00:0c  puerto 1  hace 20 s
Resumen: 4 tramas, 1 reenviadas, 2 inundadas, 0 filtradas, 1 de difusión

Guía paso a paso

Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.

1. Normaliza las MAC

Pasa a minúsculas, cambia - por : y comprueba el formato con una expresión regular: seis pares de dígitos hexadecimales separados por :.

python
t = texto.strip().lower().replace("-", ":")
re.fullmatch(r"([0-9a-f]{2}:){5}[0-9a-f]{2}", t)
2. ¿Dirección de grupo?

El bit menos significativo del primer octeto dice si una MAC es individual (0) o de grupo (1): int(mac[:2], 16) & 1. La difusión ff:… es un caso particular de grupo.

3. La tabla

Un diccionario MAC → [puerto, instante]. Antes de cada trama, recorre una copia de las claves y borra las que llevan más de s segundos sin verse.

4. Aprender y decidir

Primero apunta el origen (por eso una respuesta posterior ya se reenvía en lugar de inundarse) y después decide con el destino: difusión, conocido en otro puerto, conocido en el mismo o desconocido.

Resuélvelo aquí

El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.

🐍PythonTabla MAC de un switch: aprender, reenviar e inundarDifícil

Ejemplo

Entrada (lo que se escribe por teclado)
PUERTOS 4
0 1 00:1a:2b:00:00:01 ff:ff:ff:ff:ff:ff
5 2 00:1A:2B:00:00:02 00:1a:2b:00:00:01
6 1 00:1a:2b:00:00:01 00:1a:2b:00:00:02
10 3 00-1a-2b-00-00-03 00:1a:2b:00:00:04
12 1 00:1a:2b:00:00:05 00:1a:2b:00:00:01
TABLA 20
Salida esperada
t=0 00:1a:2b:00:00:01 → ff:ff:ff:ff:ff:ff (puerto 1): difusión a 2, 3, 4
t=5 00:1a:2b:00:00:02 → 00:1a:2b:00:00:01 (puerto 2): reenviada al puerto 1
t=6 00:1a:2b:00:00:01 → 00:1a:2b:00:00:02 (puerto 1): reenviada al puerto 2
t=10 00:1a:2b:00:00:03 → 00:1a:2b:00:00:04 (puerto 3): destino desconocido, inundación a 1, 2, 4
t=12 00:1a:2b:00:00:05 → 00:1a:2b:00:00:01 (puerto 1): filtrada (el destino está en el mismo puerto)
Tabla MAC en t=20 (4 entradas):
  00:1a:2b:00:00:01  puerto 1  hace 14 s
  00:1a:2b:00:00:05  puerto 1  hace 8 s
  00:1a:2b:00:00:02  puerto 2  hace 15 s
  00:1a:2b:00:00:03  puerto 3  hace 10 s
Resumen: 5 tramas, 2 reenviadas, 1 inundadas, 1 filtradas, 1 de difusión
⏳
Test oculto #3
⏳
Test oculto #4
0/4 tests pasados · pulsa un test para ver su entrada y su salida esperada

Solución explicada

Ver la solución completa
python
1import re
2import sys
3
4DIFUSION = "ff:ff:ff:ff:ff:ff"
5
6
7def mac_normal(texto):
8    """00-1A-2B-00-00-01 o 00:1a:2b:00:00:01 → 00:1a:2b:00:00:01; None si no es una MAC."""
9    t = texto.strip().lower().replace("-", ":")
10    return t if re.fullmatch(r"([0-9a-f]{2}:){5}[0-9a-f]{2}", t) else None
11
12
13def es_de_grupo(mac):
14    # el bit menos significativo del primer octeto indica grupo (multidifusión o difusión)
15    return int(mac[:2], 16) & 1 == 1
16
17
18def main():
19    puertos = 0
20    envejecimiento = 300
21    tabla = {}                      # mac → [puerto, último instante en que se vio]
22    ultimo_t = None
23    cuentas = {"tramas": 0, "reenviadas": 0, "inundadas": 0, "filtradas": 0, "difusion": 0}
24
25    def purgar(t):
26        for mac in [m for m, (_, visto) in tabla.items() if t - visto > envejecimiento]:
27            del tabla[mac]
28            print(f"t={t}: caduca {mac}")
29
30    for n, linea in enumerate(sys.stdin.read().split("\n"), start=1):
31        partes = linea.split()
32        if not partes:
33            continue
34        orden = partes[0].upper()
35        if orden == "PUERTOS" and len(partes) == 2 and partes[1].isdigit():
36            puertos = int(partes[1])
37            continue
38        if orden == "ENVEJECIMIENTO" and len(partes) == 2 and partes[1].isdigit():
39            envejecimiento = int(partes[1])
40            continue
41        if orden == "TABLA" and len(partes) == 2 and partes[1].isdigit():
42            t = int(partes[1])
43            purgar(t)
44            filas = sorted(tabla.items(), key=lambda e: (e[1][0], e[0]))
45            print(f"Tabla MAC en t={t} ({len(filas)} entrada{'' if len(filas) == 1 else 's'}):")
46            for mac, (puerto, visto) in filas:
47                print(f"  {mac}  puerto {puerto}  hace {t - visto} s")
48            continue
49        if len(partes) != 4 or not partes[0].isdigit() or not partes[1].isdigit():
50            print(f"Línea {n}: no se entiende «{linea.strip()}»")
51            continue
52        t, entrada = int(partes[0]), int(partes[1])
53        origen, destino = mac_normal(partes[2]), mac_normal(partes[3])
54        if not 1 <= entrada <= puertos:
55            print(f"Línea {n}: el puerto {entrada} no existe (el switch tiene {puertos})")
56            continue
57        if origen is None or destino is None:
58            print(f"Línea {n}: MAC no válida")
59            continue
60        if es_de_grupo(origen):
61            print(f"Línea {n}: el origen {origen} es una dirección de grupo; ninguna tarjeta la usa como origen")
62            continue
63        if ultimo_t is not None and t < ultimo_t:
64            print(f"Línea {n}: el tiempo no puede ir hacia atrás ({t} < {ultimo_t})")
65            continue
66        ultimo_t = t
67        cuentas["tramas"] += 1
68        purgar(t)
69        # aprender: el origen está detrás del puerto por el que entra la trama
70        if origen in tabla and tabla[origen][0] != entrada:
71            print(f"t={t}: {origen} se ha movido del puerto {tabla[origen][0]} al {entrada}")
72        tabla[origen] = [entrada, t]
73        otros = ", ".join(str(p) for p in range(1, puertos + 1) if p != entrada)
74        cabecera = f"t={t} {origen} → {destino} (puerto {entrada}):"
75        if destino == DIFUSION or es_de_grupo(destino):
76            cuentas["difusion"] += 1
77            print(f"{cabecera} {'difusión' if destino == DIFUSION else 'multidifusión'} a {otros or 'ningún puerto'}")
78        elif destino in tabla:
79            salida = tabla[destino][0]
80            if salida == entrada:
81                cuentas["filtradas"] += 1
82                print(f"{cabecera} filtrada (el destino está en el mismo puerto)")
83            else:
84                cuentas["reenviadas"] += 1
85                print(f"{cabecera} reenviada al puerto {salida}")
86        else:
87            cuentas["inundadas"] += 1
88            print(f"{cabecera} destino desconocido, inundación a {otros or 'ningún puerto'}")
89    c = cuentas
90    print(f"Resumen: {c['tramas']} tramas, {c['reenviadas']} reenviadas, {c['inundadas']} inundadas, {c['filtradas']} filtradas, {c['difusion']} de difusión")
91
92
93main()

El switch aprende solo con las MAC de origen: nunca pregunta. Por eso la primera trama hacia un equipo que todavía no ha hablado se inunda, y en cuanto ese equipo responde, las siguientes ya van solo a su puerto.

El filtrado evita tráfico inútil: si dos equipos cuelgan de un hub conectado al mismo puerto del switch, sus tramas entre ellos no tienen que salir por ningún otro puerto.

El envejecimiento hace que la tabla se adapte: un portátil que cambia de puerto se reaprende en cuanto envía algo (la entrada se mueve), y un equipo apagado acaba desapareciendo de la tabla.

Un ataque clásico, el desbordamiento de la tabla MAC (MAC flooding), llena la tabla con orígenes falsos para que el switch inunde todo y actúe como un hub. Por eso los switches gestionables limitan las MAC por puerto (port security).

Para ir más allá

  • Limita la tabla a 8 entradas: cuando está llena, una MAC nueva no se aprende y se avisa (port security).
  • Añade VLAN: cada puerto pertenece a una VLAN y la inundación solo va a los puertos de la misma VLAN.
  • Simula dos switches unidos por un enlace y comprueba qué aprende cada uno.

Dónde se explica