Capítulo 12 — Laboratório contido I
Mapa da aula
Aula 12 publicada. Mapa, exemplo, EX045.
flowchart TD
N0["12.1 O que contido significa"]
N1["12.2 O trabalho do respondedor"]
N2["12.3 Registro dentro do dispositivo"]
N3["12.5 Rubrica de avaliação de um laboratório contido"]
N4["12.6 O que o laboratório não modela"]
N5["12.7 Atividade prática"]
N6["Síntese do capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
N6 --> EX["Exemplo resolvido"]
EX --> Q["Exercício EX045"]
Desenho lógico
flowchart LR
N0["12.1 O que contido significa"]
N1["12.2 O trabalho do respondedor"]
N2["12.3 Registro dentro do dispositivo"]
N3["12.5 Rubrica de avaliação de um laboratório contido"]
N4["12.6 O que o laboratório não modela"]
N5["12.7 Atividade prática"]
N6["Síntese do capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX045"]
[12.1 O que contido significa]
|
v
[12.2 O trabalho do respondedor]
|
v
[12.3 Registro dentro do dispositivo]
|
v
[12.5 Rubrica de avaliação de um laboratório contido]
|
v
[12.6 O que o laboratório não modela]
|
v
[12.7 Atividade prática]
|
v
[Síntese do capítulo]
|
v
[Exemplo resolvido] --> [Exercício EX045]
Objetivos de aprendizagem e conexão
Ao final deste capítulo, você deverá ser capaz de:
- construir um laboratório contido, no qual o comportamento de um protocolo pode ser estudado sem rede nem dispositivo real;
- implementar um respondedor Modbus/TCP mínimo com uma política de escrita: quais registradores podem ser escritos, dentro de qual faixa de valores;
- gerar e ler respostas de exceção do Modbus (função ilegal, endereço de dados ilegal, valor de dados ilegal);
- avaliar um laboratório segundo uma rubrica: contenção, reprodutibilidade, evidências e honestidade sobre o que o laboratório não modela.
Os Capítulos 1–11 analisaram tráfego e configuração. Este capítulo constrói algo que você pode quebrar com segurança. É o primeiro de dois laboratórios avaliados; o Capítulo 17 é o segundo.
12.1 O que "contido" significa
Um laboratório contido não consegue afetar nada fora de si mesmo. Neste livro, isso é garantido tecnicamente, não por boas intenções: o Laboratório 12 usa socket.socketpair(), que cria dois sockets conectados dentro de um único processo (Python socket). Nada escuta numa porta, nada é roteado, e o programa não consegue alcançar um controlador real nem por engano. Laboratórios maiores usam redes virtuais isoladas, sem rota para fora, ou hardware reserva numa bancada, com o cabo de rede não indo a lugar nenhum. Anote a propriedade de contenção de cada laboratório que construir, e como você a verificou.
12.2 O trabalho do respondedor
Um controlador real decide se executa uma escrita com base na sua própria lógica, o que, nos dispositivos Modbus clássicos, muitas vezes significa "sempre". Alguns dispositivos e gateways de protocolo permitem proteção contra escrita por faixa de registradores; muitos não. O respondedor do laboratório torna a política explícita:
- leituras de registradores de retenção (função 3) são permitidas dentro da tabela, com quantidade de 1 a 125 (o limite do protocolo para uma leitura);
- escritas de um único registrador (função 6) são permitidas apenas no registrador 7, o setpoint da bomba, e apenas para valores de 100 a 400;
- qualquer outra coisa devolve uma exceção: o código de função com o bit mais alto ligado (código + 0x80) e um código de exceção — 01 função ilegal, 02 endereço de dados ilegal, 03 valor de dados ilegal —, conforme definido na especificação Modbus Application Protocol (Modbus specifications).
Uma resposta normal à função 6 ecoa a requisição. Uma resposta normal à função 3 carrega uma contagem de bytes e os valores dos registradores.
12.3 Registro dentro do dispositivo
O respondedor registra toda leitura, toda escrita aceita, com o valor antigo e o novo, e toda exceção. Em equipamentos reais, esses são exatamente os registros que você gostaria de ter tido no Capítulo 7. Uma rajada de respostas de exceção 02 vindas de uma única origem é um forte sinal de alguém mapeando o espaço de registradores; uma sequência de exceções 03 é alguém testando limites.
Em equipamentos reais, esses logs muitas vezes não existem, e é por isso que o laboratório os torna visíveis. Guarde o log do laboratório junto com as respostas em hexadecimal, para que um revisor possa associar cada exceção a uma requisição.
12.4 Exemplo resolvido: o respondedor de bancada (sintético)
Laboratório 12 (salve como lab12.py e execute com 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)
Saída 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
Leia as respostas em hexadecimal. A primeira é uma leitura normal: 03 06 (função 3, seis bytes), seguido de 0x0156 = 342, 0x003D = 61 e 0. A escrita de 300 no registrador 7 é ecoada, e o log registra 250 → 300. Escrever 900 devolve 86 03 — função 6 + 0x80, exceção 3, valor ilegal. Escrever no registrador 0 devolve 86 02, endereço ilegal — o registrador 0 é uma medição, não é gravável. A função 8 (diagnóstico) devolve 88 01, função ilegal. Uma leitura de cinco registradores a partir do 8 ultrapassa a tabela de 10 registradores e devolve 83 02. Os registradores no final mostram que só a escrita aprovada teve efeito.
12.5 Rubrica de avaliação de um laboratório contido
| Critério | 3 (excelente) | 1 (fraco) |
|---|---|---|
| Contenção | Tecnicamente impossível alcançar um dispositivo real; propriedade declarada e verificada | Depende de "não vou apontá-lo para nada" |
| Reprodutibilidade | Entradas fixas; saída exata registrada com a versão do Python | A saída varia ou não é registrada |
| Declaração de fidelidade | Lista o que o laboratório modela e o que não modela | Afirma representar dispositivos reais |
| Evidências | Logs e saídas guardados, com hash, vinculados aos exercícios | Apenas capturas de tela |
| Segurança das extensões | Mudanças revisadas para que não possam remover a contenção | Acrescenta código de rede real sem revisão |
12.6 O que o laboratório não modela
Os dispositivos reais diferem: alguns ignoram os identificadores de unidade, alguns aceitam escritas em qualquer registrador, alguns travam com requisições malformadas, e poucos registram alguma coisa. O respondedor trata uma requisição de cada vez, não tem concorrência, timeouts nem leituras parciais. O seu valor está em tornar visíveis a política e as exceções, não em prever o comportamento de um fornecedor em particular. Nunca use os resultados de um laboratório para decidir como um dispositivo em produção responderá a um teste.
Se o respondedor devolve a exceção 2 para uma leitura dos registradores 8 a 12, isso nada diz sobre o que uma RTU real da KW-04 faria. Os dispositivos reais diferem no tratamento do identificador de unidade, na proteção contra escrita e no registro, e muitos travam com requisições malformadas. Declare essas lacunas na avaliação e mantenha a verificação em produção restrita aos métodos com escopo e autorizados do Capítulo 11.
12.7 Atividade prática
Acrescente ao respondedor a função 16 (escrever vários registradores), com a mesma política: todo registrador da faixa precisa ser gravável e todo valor estar dentro dos limites, ou a escrita inteira é recusada. Teste quatro casos: tudo permitido; um endereço não gravável; um valor fora da faixa; contagem de bytes inconsistente com a quantidade.
Registre as quatro respostas em hexadecimal e as linhas de log ao lado delas. Por fim, envie uma escrita com a função 16 que inclua um registrador que o seu tratamento da função 6 já recusa (como o registrador 0), e confirme que o log dá o mesmo motivo para as duas recusas.
12.8 Erros comuns e limites
- Usar um socket de rede real "só para testar" e deixá-lo vinculado a todas as interfaces.
- Aceitar uma escrita parcial de vários registradores que muda alguns registradores antes de recusar outros.
- Esquecer que a resposta à função 6 é um eco, e então interpretá-la errado.
- Acrescentar a função 16 na atividade 12.7 sem reutilizar a política da escrita simples, de modo que o limite de 100–400 do registrador 7 vale para um caminho de escrita e não para o outro.
- Testar apenas a escrita permitida e nunca as recusadas; o registro das respostas de exceção é a evidência de que a política funciona.
- Tratar o comportamento de exceções do laboratório como comportamento de um fornecedor. Declare a contenção como propriedade técnica em todo relatório de laboratório.
Exercícios — capítulo 12
EX045 — Leia as respostas. Objetivo: decodificar respostas de exceção e normais. Insumos: a saída do Laboratório 12. Passos: para cada uma das seis respostas, dar o id de transação, a função (com ou sem o bit de exceção) e o significado. Evidência esperada: seis linhas. Rubrica: 3 = as seis corretas, incluindo 0x86 = exceção à 6, 0x88 = exceção à 8, 0x83 = exceção à 3; 2 = uma errada; 1 = apenas as respostas normais; 0 = nada.
EX046 — Implemente a função 16. Objetivo: estender o respondedor com segurança. Insumos: atividade 12.7. Passos: implementar e executar os quatro casos. Evidência esperada: código e saídas. Rubrica: 3 = comportamento tudo ou nada; exceção 02 para um endereço não gravável, 03 para um valor fora da faixa, 03 (ou um tratamento de requisição malformada, com justificativa) para uma contagem de bytes inconsistente; a resposta normal ecoa apenas o endereço e a quantidade; linhas de log para cada caso; 2 = funciona, com um código errado; 1 = aceita escritas parciais; 0 = nada.
EX047 — Detecte tentativas de mapeamento. Objetivo: usar logs do lado do dispositivo. Insumos: o log do respondedor. Passos: escrever uma regra que alerte sobre comportamento de mapeamento ou de teste de limites e testá-la acrescentando cinco leituras de endereços fora da faixa a partir de uma sequência de transações. Evidência esperada: regra e saída. Rubrica: 3 = por exemplo, "≥3 respostas de exceção 02 em 60 s vindas de uma única origem"; demonstra que ela dispara no teste e não nas seis requisições originais (que contêm apenas duas respostas de exceção 02; contar 02 e 03 juntas já chegaria a três e dispararia); 2 = dispara nos dois; 1 = apenas a regra; 0 = nada.
EX048 — Avalie um laboratório. Objetivo: aplicar a rubrica. Insumos: o laboratório de um colega que executa um servidor Modbus em 0.0.0.0:502 num notebook "usado só no escritório". Passos: pontuá-lo em contenção e fidelidade e propor uma correção. Evidência esperada: pontuações e correção. Rubrica: 3 = contenção 1 (alcançável a partir de qualquer rede em que o notebook entre, e o notebook pode ser levado às estações); correção com socketpair ou vinculação apenas ao loopback, mais firewall de host, ou uma rede de VMs isolada; declaração de fidelidade exigida; 2 = correção sem raciocínio; 1 = aceita; 0 = nada.
Síntese do capítulo
Um laboratório contido é aquele em que a segurança é uma propriedade técnica. O Laboratório 12 constrói um respondedor Modbus que aplica uma política de escrita e devolve exceções adequadas, tornando visível o que a maioria dos dispositivos reais esconde: quais requisições foram recusadas, e por quê. Avalie todo laboratório quanto a contenção, reprodutibilidade, evidências e uma declaração franca do que ele não modela.
Continue estudando — capítulo 12 de 18
← Capítulo 11 · Índice de capítulos do curso · Capítulo 13 →
Siga para a próxima lição escrita.