Capítulo 12 — Laboratorio contenido I
Mapa de la lección
Lección 12 publicada. Mapa, ejemplo, EX045.
flowchart TD
N0["Resultados de aprendizaje y conexión"]
N1["12.1 Qué significa «contenido»"]
N2["12.2 La tarea del respondedor"]
N3["12.3 El registro dentro del dispositivo"]
N4["12.5 Rúbrica de evaluación de un laboratorio conteni"]
N5["12.6 Lo que el laboratorio no modela"]
N6["12.7 Actividad práctica"]
N7["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7
N7 --> EX["Ejemplo resuelto"]
EX --> Q["Ejercicio EX045"]
Dibujo lógico
flowchart LR
N0["Resultados de aprendizaje y conexión"]
N1["12.1 Qué significa «contenido»"]
N2["12.2 La tarea del respondedor"]
N3["12.3 El registro dentro del dispositivo"]
N4["12.5 Rúbrica de evaluación de un laboratorio conteni"]
N5["12.6 Lo que el laboratorio no modela"]
N6["12.7 Actividad práctica"]
N7["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7 --> Q["EX045"]
[Resultados de aprendizaje y conexión]
|
v
[12.1 Qué significa «contenido»]
|
v
[12.2 La tarea del respondedor]
|
v
[12.3 El registro dentro del dispositivo]
|
v
[12.5 Rúbrica de evaluación de un laboratorio conteni]
|
v
[12.6 Lo que el laboratorio no modela]
|
v
[12.7 Actividad práctica]
|
v
[Síntesis del capítulo]
|
v
[Ejemplo resuelto] --> [Ejercicio EX045]
Resultados de aprendizaje y conexión
Al terminar este capítulo debería ser capaz de:
- construir un laboratorio contenido en el que se pueda estudiar el comportamiento de un protocolo sin ninguna red ni dispositivo real;
- implementar un respondedor Modbus/TCP mínimo con una política de escritura: qué registros pueden escribirse y dentro de qué rango de valores;
- generar y leer respuestas de excepción de Modbus (función ilegal, dirección de datos ilegal, valor de datos ilegal);
- evaluar un laboratorio con una rúbrica: contención, reproducibilidad, evidencia y honestidad sobre lo que el laboratorio no modela.
Los capítulos 1–11 analizaron el tráfico y la configuración. Este capítulo construye algo que se puede romper sin peligro. Es el primero de dos laboratorios evaluados; el capítulo 17 es el segundo.
12.1 Qué significa «contenido»
Un laboratorio contenido no puede afectar a nada fuera de sí mismo. En este libro eso se garantiza técnicamente, no con buenas intenciones: el Lab 12 usa socket.socketpair(), que crea dos sockets conectados dentro de un mismo proceso (Python socket). Nada escucha en un puerto, nada se encamina, y el programa no puede alcanzar un controlador real ni siquiera por error. Los laboratorios mayores usan redes virtuales aisladas sin ruta hacia fuera, o hardware de repuesto en un banco con su cable de red sin conectar a ningún otro sitio. Anote la propiedad de contención de cada laboratorio que construya y cómo la comprobó.
12.2 La tarea del respondedor
Un controlador real decide si ejecuta una escritura según su propia lógica, que en los dispositivos Modbus clásicos a menudo significa «siempre». Algunos dispositivos y pasarelas de protocolo permiten proteger contra escritura por rangos de registros; muchos no. El respondedor del laboratorio hace explícita la política:
- las lecturas de registros de retención (función 3) se permiten dentro de la tabla, con una cantidad de 1–125 (el límite del protocolo para una lectura);
- las escrituras de un registro (función 6) solo se permiten en el registro 7, el punto de consigna de la bomba, y solo para valores de 100–400;
- todo lo demás devuelve una excepción: el código de función con su bit alto activado (código + 0x80) y un código de excepción (01 función ilegal, 02 dirección de datos ilegal, 03 valor de datos ilegal), tal como los define la especificación Modbus Application Protocol (Modbus specifications).
Una respuesta normal a la función 6 repite la petición. Una respuesta normal a la función 3 lleva un recuento de bytes y los valores de los registros.
12.3 El registro dentro del dispositivo
El respondedor registra cada lectura, cada escritura aceptada con su valor anterior y el nuevo, y cada excepción. En los equipos reales, estos son exactamente los registros que habría deseado tener en el capítulo 7. Una ráfaga de respuestas de excepción 02 desde un mismo origen es una señal clara de que alguien está cartografiando el espacio de registros; una racha de excepciones 03 es alguien que prueba los límites. En los equipos reales esos registros suelen faltar, y por eso el laboratorio los hace visibles. Guarde el registro del laboratorio junto con las respuestas en hexadecimal para que un revisor pueda emparejar cada excepción con una petición.
12.4 Ejemplo resuelto: el respondedor de banco (sintético)
Lab 12 (guárdelo como lab12.py y ejecútelo con python3 lab12.py):
# Lab 12 - contained Modbus/TCP responder with a write policy (offline, standard library only)
# Uses socket.socketpair(): both ends live inside this process; nothing touches a real network.
import socket, struct
regs = [0] * 10; regs[0], regs[1], regs[7] = 342, 61, 250 # level cm, chlorine x100, pump setpoint
WRITABLE = {7: (100, 400)} # register: allowed value range
log = []
def respond(req: bytes) -> bytes:
tid, pid, length, unit, fc = struct.unpack(">HHHBB", req[:8])
def exc(code):
log.append(f"tid={tid} fc={fc} exception {code}")
return struct.pack(">HHHBBB", tid, 0, 3, unit, fc | 0x80, code)
if pid != 0 or length != len(req) - 6: return exc(3)
if fc == 3:
addr, qty = struct.unpack(">HH", req[8:12])
if not 1 <= qty <= 125 or addr + qty > len(regs): return exc(2)
data = struct.pack(f">{qty}H", *regs[addr:addr + qty])
log.append(f"tid={tid} read {addr}..{addr + qty - 1}")
return struct.pack(">HHHBBB", tid, 0, 3 + len(data), unit, 3, len(data)) + data
if fc == 6:
addr, val = struct.unpack(">HH", req[8:12])
if addr not in WRITABLE: return exc(2)
lo, hi = WRITABLE[addr]
if not lo <= val <= hi: return exc(3)
old, regs[addr] = regs[addr], val
log.append(f"tid={tid} WRITE reg {addr} {old}->{val}")
return req[:12] # echo is the normal fc 6 reply
return exc(1)
client, server = socket.socketpair()
def call(tid, fc, a, b):
client.sendall(struct.pack(">HHHBBHH", tid, 0, 6, 1, fc, a, b))
server.sendall(respond(server.recv(260)))
r = client.recv(260)
return r.hex()
print("read 0..2 ", call(1, 3, 0, 3))
print("write 7=300 ", call(2, 6, 7, 300))
print("write 7=900 ", call(3, 6, 7, 900))
print("write 0=0 ", call(4, 6, 0, 0))
print("fc 8 diag ", call(5, 8, 0, 0))
print("read 8..12 ", call(6, 3, 8, 5))
client.close(); server.close()
print("registers", regs[:3], "setpoint", regs[7])
for entry in log: print("log:", entry)
Salida esperada (Python 3.13.5):
read 0..2 0001000000090103060156003d0000
write 7=300 00020000000601060007012c
write 7=900 000300000003018603
write 0=0 000400000003018602
fc 8 diag 000500000003018801
read 8..12 000600000003018302
registers [342, 61, 0] setpoint 300
log: tid=1 read 0..2
log: tid=2 WRITE reg 7 250->300
log: tid=3 fc=6 exception 3
log: tid=4 fc=6 exception 2
log: tid=5 fc=8 exception 1
log: tid=6 fc=3 exception 2
Lea las respuestas en hexadecimal. La primera es una lectura normal: 03 06 (función 3, seis bytes) seguida de 0x0156 = 342, 0x003D = 61 y 0. La escritura de 300 en el registro 7 se repite y el registro anota 250 → 300. Escribir 900 devuelve 86 03: función 6 + 0x80, excepción 3, valor ilegal. Escribir el registro 0 devuelve 86 02, dirección ilegal: el registro 0 es una medición y no se puede escribir. La función 8 (diagnóstico) devuelve 88 01, función ilegal. Una lectura de cinco registros a partir del 8 se sale de la tabla de 10 registros y devuelve 83 02. Los registros del final muestran que solo surtió efecto la escritura aprobada.
12.5 Rúbrica de evaluación de un laboratorio contenido
| Criterio | 3 (excelente) | 1 (débil) |
|---|---|---|
| Contención | Es técnicamente imposible alcanzar un dispositivo real; la propiedad se declara y se comprueba | Depende de «no lo apuntaré a nada» |
| Reproducibilidad | Entradas fijas; salida exacta registrada con la versión de Python | La salida varía o no se registra |
| Declaración de fidelidad | Enumera lo que el laboratorio modela y lo que no | Afirma representar dispositivos reales |
| Evidencia | Registros y salidas guardados, con hash, vinculados a los ejercicios | Solo capturas de pantalla |
| Seguridad de las ampliaciones | Cambios revisados para que no puedan eliminar la contención | Añade código de red real sin revisión |
12.6 Lo que el laboratorio no modela
Los dispositivos reales difieren: algunos ignoran los identificadores de unidad, algunos aceptan escrituras en cualquier registro, algunos se bloquean con peticiones mal formadas, y pocos registran algo. El respondedor atiende una petición cada vez y no tiene concurrencia, tiempos de espera ni lecturas parciales. Su valor está en hacer visibles la política y las excepciones, no en predecir el comportamiento de un fabricante concreto. No use nunca los resultados de un laboratorio para decidir cómo responderá un dispositivo en servicio a una prueba.
Si el respondedor devuelve la excepción 2 para una lectura de los registros 8 a 12, eso no dice nada sobre lo que haría una RTU real de KW-04. Los dispositivos reales difieren en el tratamiento del identificador de unidad, la protección contra escritura y el registro, y muchos se bloquean con peticiones mal formadas. Indique esas lagunas en la evaluación y limite la verificación en servicio a los métodos delimitados y autorizados del capítulo 11.
12.7 Actividad práctica
Añada al respondedor la función 16 (escribir varios registros) con la misma política: todos los registros del rango deben poder escribirse y todos los valores deben estar dentro de sus límites, o se rechaza la escritura completa. Pruebe cuatro casos: todo permitido; una dirección que no se puede escribir; un valor fuera de rango; un recuento de bytes incoherente con la cantidad. Anote las cuatro respuestas en hexadecimal y, junto a ellas, las líneas del registro. Por último, envíe una escritura con la función 16 que incluya un registro que su tratamiento de la función 6 ya rechaza (como el registro 0), y confirme que el registro da el mismo motivo para ambos rechazos.
12.8 Errores comunes y límites
- Usar un socket de red real «solo para probar» y dejarlo enlazado a todas las interfaces.
- Aceptar una escritura parcial de varios registros que cambia algunos registros antes de rechazar otros.
- Olvidar que la respuesta a la función 6 es una repetición de la petición, y analizarla mal.
- Añadir la función 16 en la actividad 12.7 sin reutilizar la política de escritura individual, de modo que el límite de 100–400 del registro 7 se aplica en una vía de escritura y no en la otra.
- Probar solo la escritura permitida y nunca las rechazadas; el registro de las respuestas de excepción es la evidencia de que la política funciona.
- Tratar el comportamiento de las excepciones del laboratorio como el de un fabricante. Declare la contención como una propiedad técnica en todo informe de laboratorio.
Ejercicios — capítulo 12
EX045 — Lea las respuestas. Objetivo: decodificar respuestas normales y de excepción. Entradas: la salida del Lab 12. Pasos: para cada una de las seis respuestas, dé el identificador de transacción, la función (con o sin el bit de excepción) y su significado. Evidencia esperada: seis filas. Rúbrica: 3 = las seis correctas, incluidas 0x86 = excepción a la 6, 0x88 = excepción a la 8, 0x83 = excepción a la 3; 2 = una errónea; 1 = solo las respuestas normales; 0 = nada.
EX046 — Implemente la función 16. Objetivo: ampliar el respondedor con seguridad. Entradas: la actividad 12.7. Pasos: impleméntela y ejecute los cuatro casos. Evidencia esperada: código y salidas. Rúbrica: 3 = comportamiento de todo o nada; excepción 02 para una dirección que no se puede escribir, 03 para un valor fuera de rango, 03 (o un tratamiento de petición mal formada justificado) para un recuento de bytes incoherente; la respuesta normal repite solo la dirección y la cantidad; líneas de registro para cada caso; 2 = funciona con un código erróneo; 1 = acepta escrituras parciales; 0 = nada.
EX047 — Detecte intentos de cartografiado. Objetivo: usar los registros del lado del dispositivo. Entradas: el registro del respondedor. Pasos: escriba una regla que alerte del comportamiento de cartografiado o de prueba de límites y pruébela añadiendo cinco lecturas de direcciones fuera de rango desde una misma secuencia de transacciones. Evidencia esperada: regla y salida. Rúbrica: 3 = por ejemplo, «≥3 respuestas de excepción 02 en 60 s desde un mismo origen»; demuestra que se dispara con la prueba y no con las seis peticiones originales (que solo contienen dos respuestas de excepción 02; contar juntas la 02 y la 03 ya llegaría a tres y la dispararía); 2 = se dispara en ambos casos; 1 = solo la regla; 0 = nada.
EX048 — Evalúe un laboratorio. Objetivo: aplicar la rúbrica. Entradas: el laboratorio de un colega que ejecuta un servidor Modbus en 0.0.0.0:502 en un portátil que «solo se usa en la oficina». Pasos: puntúelo en contención y fidelidad y proponga una corrección. Evidencia esperada: puntuaciones y corrección. Rúbrica: 3 = contención 1 (alcanzable desde cualquier red a la que se una el portátil, y el portátil puede llevarse a las estaciones); corrección con socketpair o con enlace solo a la interfaz de bucle invertido más cortafuegos del equipo, o una red de máquinas virtuales aislada; se exige una declaración de fidelidad; 2 = corrección sin razonamiento; 1 = lo acepta; 0 = nada.
Síntesis del capítulo
Un laboratorio contenido es aquel en el que la seguridad es una propiedad técnica. El Lab 12 construye un respondedor Modbus que aplica una política de escritura y devuelve las excepciones adecuadas, haciendo visible lo que ocultan la mayoría de los dispositivos reales: qué peticiones se rechazaron y por qué. Evalúe todo laboratorio en cuanto a contención, reproducibilidad, evidencia y una declaración franca de lo que no modela.
Siga estudiando — capítulo 12 de 18
← Capítulo 11 · Índice de capítulos del curso · Capítulo 13 →
Continúe con la siguiente lección escrita.