Tabla MAC de un switch: aprender, reenviar e inundar
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
PUERTOS nfija el número de puertos (del 1 al n) yENVEJECIMIENTO slos segundos que dura una entrada sin verse (300 si no se indica).- 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:. - Antes de procesar una trama se borran las entradas que llevan más de
ssegundos sin verse (estrictamente más), escribiendot=T: caduca MACpor cada una. - 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. - 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)odestino desconocido, inundación a …(los puertos menos el de entrada, en orden). - 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)yLínea N: no se entiende «…». TABLA tborra lo caducado en ese instante y escribeTabla 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 yhace S s.- 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 :.
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.
Ejemplo
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
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
Solución explicada
Ver la solución completa
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.