← CCNP Enterprise: núcleo ENCOR e operação
11 / 17 · 55 MIN

Laboratório OSPF e aceitação operacional

Executa um ensaio reproduzível com evidência por camada, rollback e limites explícitos.

Preparar uma experiência identificável

O runner cria três containers FRRouting 10.4.5 e duas bridges Docker dedicadas sem NAT, com as rotas por omissão removidas dos containers. R1 usa 172.30.241.11, R2 usa 172.30.241.12 e 172.30.242.12, e R3 usa 172.30.242.13. Os router IDs são 1.1.1.1, 2.2.2.2 e 3.3.3.3; não confundas estes identificadores com a origem de todos os pacotes. As loopbacks sintéticas são 10.255.1.1 em R1 e 10.50.1.1/10.50.2.1 em R3. A imagem é fixada por digest. O runner exige a imagem local, verifica a versão observada e remove apenas os recursos que criou, mesmo quando uma fase falha.

Caso: evidência para passagem a RUN

Antes de uma alteração de rede que suporta batch financeiro, acorda os resultados esperados com rede e APS: vizinhos necessários, prefixos de serviço, caminhos de retorno, sondas representativas e condição de reversão. O laboratório ilustra este processo com ICMP sintético, sem sistemas bancários reais. Quando o ping parte de 10.255.1.1, a resposta também precisa de alcançar essa origem. Um ping com origem no trânsito pode exercer outro caminho de retorno. No relatório, identifica origem e destino, fase da alteração e resultado. Isso permite ao próximo turno reproduzir a observação em vez de receber apenas a frase rede operacional.

Quatro camadas de observação

A vizinhança mostra relações OSPF; a LSDB mostra informação do protocolo; a tabela de routing e as rotas do kernel mostram decisões e instalação local; a sonda observa um fluxo concreto. Nenhuma destas camadas substitui todas as outras. Uma LSA pode continuar presente sem produzir a rota que esperas. Uma rota pode existir sem permitir a sessão da aplicação, devido a política, MTU, retorno ou serviço indisponível. O runner guarda estas camadas nas fases relevantes, mas não cria um servidor de aplicação. Por isso, a aceitação de uma transação ou de TLS continua a exigir uma sonda própria.

Executar e interpretar falhas

Guarda o script completo apresentado abaixo num ficheiro run.py. Obtém a imagem com docker pull quay.io/frrouting/frr@sha256:b0faf7c8f3d8b09aea1e38f77603c745a66dbf5e26ef9718527c2fbb53cc3aed e executa python3 run.py caminho-para-evidence.json numa pasta local com permissão de escrita. Requer Python 3, Docker a funcionar e as sub-redes 172.30.241.0/24 e 172.30.242.0/24 livres de conflitos. Cada mudança tem uma condição observável e um timeout. Se uma asserção falhar, o comando termina com erro; não transforma esse resultado em sucesso porque alguns pings passaram. O ficheiro de sucesso só é escrito depois de concluir as verificações e limpar os recursos. A versão inicial encontrou uma dependência de capacidades dos daemons; o runner usa NET_ADMIN, NET_RAW e SYS_ADMIN apenas dentro dos containers descartáveis, sem mounts, portas publicadas ou rede do anfitrião. Consulta estes requisitos antes de executar noutro ambiente.

Limites da evidência

Três routers lógicos no mesmo Mac não demonstram redundância física. Uma execução rápida não mede desempenho, convergência de produção ou resiliência entre centros de dados. O laboratório não configura autenticação OSPF, OSPFv3, eBGP ou proteção de aplicações. Também não executa Cisco IOS XE. Usa os resultados para explicar as falhas reproduzidas e planear verificações adicionais. Para o handover real, conserva a configuração aprovada, a evidência por fluxo, o rollback ensaiado, os responsáveis e as exceções por resolver. A conclusão desta unidade não equivale a estar preparado para todos os domínios ENCOR.

# Save this complete example as run.py. Requires Python 3 and running Docker.
# First pull the exact image named by IMAGE below.
# Then: python3 run.py /tmp/encor-routing-evidence.json
"""Disposable FRR OSPF lab. No host networking, mounts, or published ports."""
import datetime, hashlib, json, pathlib, subprocess, sys, time, uuid

IMAGE='quay.io/frrouting/frr@sha256:b0faf7c8f3d8b09aea1e38f77603c745a66dbf5e26ef9718527c2fbb53cc3aed'
ROOT=pathlib.Path(__file__).resolve().parent
prefix='dr-encor-'+uuid.uuid4().hex[:8]
containers=[]; networks=[]; phases=[]; checks=[]
def command(args, check=True):
    r=subprocess.run(['docker',*args],capture_output=True,text=True,timeout=45)
    if check and r.returncode: raise RuntimeError(str(args)+'\n'+r.stdout+r.stderr)
    return r
def ex(n,*args,check=True):return command(['exec',prefix+'-'+n,*args],check)
def cli(n,*lines):
    args=['vtysh']
    for line in lines:args+=['-c',line]
    return ex(n,*args).stdout
def snapshot(label):
    data={n:{'neighbors':json.loads(cli(n,'show ip ospf neighbor json')),
             'routes':json.loads(cli(n,'show ip route json')),
             'interfaces':json.loads(cli(n,'show ip ospf interface json')),
             'lsdb':cli(n,'show ip ospf database'),
             'kernelRoutes':ex(n,'ip','route').stdout} for n in ['r1','r2','r3']}
    phases.append({'name':label,'observedAt':datetime.datetime.now(datetime.timezone.utc).isoformat(),'routers':data})
    return data
def verify(name, condition):
    if not condition:raise AssertionError(name)
    checks.append(name)
def route(n,prefix_):return json.loads(cli(n,'show ip route json')).get(prefix_,[])
def ospf(n,prefix_):return any(r.get('protocol')=='ospf' for r in route(n,prefix_))
def ping(n,address,source):return ex(n,'ping','-c','1','-W','1','-I',source,address,check=False).returncode==0
def wait_for(name, predicate):
    end=time.monotonic()+40
    while time.monotonic()<end:
        if predicate():return
        time.sleep(1)
    raise AssertionError('Timed out: '+name)
try:
    meta=json.loads(command(['image','inspect',IMAGE]).stdout)[0]
    for suffix,subnet in [('backbone','172.30.241.0/24'),('branch','172.30.242.0/24')]:
        name=prefix+'-'+suffix
        command(['network','create','--opt','com.docker.network.bridge.enable_ip_masquerade=false','--label','dr.lab=encor-routing','--subnet',subnet,name]);networks.append(name)
    for n,net,ip in [('r1','backbone','172.30.241.11'),('r2','backbone','172.30.241.12'),('r3','branch','172.30.242.13')]:
        name=prefix+'-'+n
        command(['run','-d','--name',name,'--label','dr.lab=encor-routing','--network',prefix+'-'+net,'--ip',ip,
                 '--memory','192m','--cpus','0.5','--pids-limit','100','--cap-add','NET_ADMIN','--cap-add','NET_RAW','--cap-add','SYS_ADMIN',
                 '--entrypoint','/bin/sh',IMAGE,'-c','sleep 3600']);containers.append(name)
    command(['network','connect','--ip','172.30.242.12',prefix+'-branch',prefix+'-r2'])
    for n in ['r1','r2','r3']:
        ex(n,'ip','route','del','default',check=False)
        verify(n+' has no default route',not ex(n,'ip','route','show','default').stdout.strip())
        ex(n,'sed','-i','s/^ospfd=no/ospfd=yes/','/etc/frr/daemons')
        ex(n,'touch','/etc/frr/frr.conf','/etc/frr/vtysh.conf')
        ex(n,'chown','frr:frr','/etc/frr/frr.conf')
        ex(n,'/usr/lib/frr/zebra','-d','-A','127.0.0.1')
        ex(n,'/usr/lib/frr/ospfd','-d','-A','127.0.0.1')
        wait_for(n+' daemon',lambda n=n:ex(n,'vtysh','-c','show version',check=False).returncode==0)
        version=cli(n,'show version');verify(n+' runs FRR 10.4.5','10.4.5' in version)
        cli(n,'configure terminal','router ospf','ospf router-id '+{'r1':'1.1.1.1','r2':'2.2.2.2','r3':'3.3.3.3'}[n])
        interfaces=[('eth0','0' if n!='r3' else '10')]+([('eth1','10')] if n=='r2' else [])
        for iface,area in interfaces:
            cli(n,'configure terminal','interface '+iface,'ip ospf area '+area,'ip ospf network point-to-point','ip ospf hello-interval 1','ip ospf dead-interval 4')
        if n in ['r1','r3']:
            ips=['10.255.1.1/32'] if n=='r1' else ['10.50.1.1/32','10.50.2.1/32']
            for address in ips:ex(n,'ip','address','add',address,'dev','lo')
            cli(n,'configure terminal','interface lo','ip ospf area '+('0' if n=='r1' else '10'),'ip ospf passive')
    wait_for('inter-area route',lambda:ospf('r1','10.50.1.1/32') and ospf('r3','10.255.1.1/32'))
    baseline=snapshot('baseline-specific-prefixes')
    verify('baseline loopback-source ping crosses ABR',ping('r1','10.50.1.1','10.255.1.1'))
    cli('r2','configure terminal','interface eth1','ip ospf passive')
    wait_for('withdraw after passive interface',lambda:not ospf('r1','10.50.1.1/32'))
    snapshot('passive-breaks-transit')
    verify('passive transit loses remote ping',not ping('r1','10.50.1.1','10.255.1.1'))
    verify('passive retains connected transport reachability',ping('r2','172.30.242.13','172.30.242.12'))
    cli('r2','configure terminal','interface eth1','no ip ospf passive')
    wait_for('passive rollback',lambda:ospf('r1','10.50.1.1/32'))
    verify('passive rollback restores loopback ping',ping('r1','10.50.1.1','10.255.1.1'))
    snapshot('passive-recovered')
    cli('r3','configure terminal','interface eth0','ip ospf hello-interval 2')
    wait_for('hello mismatch withdrawal',lambda:not ospf('r1','10.50.1.1/32'))
    snapshot('hello-mismatch')
    verify('hello mismatch still permits connected ping',ping('r2','172.30.242.13','172.30.242.12'))
    verify('hello mismatch removes remote service reachability',not ping('r1','10.50.1.1','10.255.1.1'))
    cli('r3','configure terminal','interface eth0','ip ospf hello-interval 1')
    wait_for('timer rollback',lambda:ospf('r1','10.50.1.1/32'))
    verify('timer rollback restores remote ping',ping('r1','10.50.1.1','10.255.1.1'))
    cli('r2','configure terminal','router ospf','area 10 range 10.50.0.0/16')
    wait_for('summary route',lambda:ospf('r1','10.50.0.0/16') and not ospf('r1','10.50.1.1/32'))
    snapshot('summary-active')
    verify('summary retains real destination reachability',ping('r1','10.50.1.1','10.255.1.1'))
    ex('r3','ip','address','del','10.50.1.1/32','dev','lo')
    wait_for('component withdrawn',lambda:not ospf('r2','10.50.1.1/32'))
    snapshot('summary-with-one-component-missing')
    verify('aggregate persists with remaining component',ospf('r1','10.50.0.0/16'))
    verify('missing component fails despite aggregate',not ping('r1','10.50.1.1','10.255.1.1'))
    verify('remaining component still reachable',ping('r1','10.50.2.1','10.255.1.1'))
    ex('r3','ip','address','add','10.50.1.1/32','dev','lo')
    wait_for('component restored',lambda:ospf('r2','10.50.1.1/32'))
    verify('restored component reachable',ping('r1','10.50.1.1','10.255.1.1'))
    cli('r2','configure terminal','router ospf','no area 10 range 10.50.0.0/16')
    wait_for('summary rollback',lambda:ospf('r1','10.50.1.1/32') and not ospf('r1','10.50.0.0/16'))
    final=snapshot('all-changes-reverted')
    verify('final remote probe successful',ping('r1','10.50.1.1','10.255.1.1'))
    evidence={'checkedAt':datetime.datetime.now(datetime.timezone.utc).isoformat(),'image':IMAGE,'imageId':meta['Id'],'architecture':meta['Architecture'],'scriptSha256':hashlib.sha256(pathlib.Path(__file__).read_bytes()).hexdigest(),'checks':checks,'phases':phases,'limitations':['FRRouting on Docker Desktop, not Cisco IOS XE execution','Three logical routers share one physical host','FRR requires NET_ADMIN, NET_RAW and SYS_ADMIN inside disposable containers; no host mounts, devices, networking or privileged mode','Accelerated 1/4-second timers for teaching, not a production recommendation','ICMP evidence is not application or TLS acceptance','No OSPFv3, authentication, performance, or full ENCOR readiness verified'],'cleanup':None}
except Exception:
    try:
        snapshot('failure-diagnostic')
        pathlib.Path(sys.argv[1]+'.failure.json').write_text(json.dumps({'phases':phases,'configs':{n:cli(n,'show running-config') for n in ['r1','r2','r3']}},indent=2))
    except Exception as error: print('Diagnostic failure:',error)
    raise
finally:
    cleanup=[]
    for name in reversed(containers):cleanup.append({'container':name,'exit':command(['rm','-f',name],False).returncode})
    for name in reversed(networks):cleanup.append({'network':name,'exit':command(['network','rm',name],False).returncode})
if any(x['exit'] for x in cleanup):raise RuntimeError(cleanup)
evidence['cleanup']=cleanup
pathlib.Path(sys.argv[1]).write_text(json.dumps(evidence,indent=2)+'\n')
print(json.dumps({'checks':len(checks),'phases':len(phases),'imageId':meta['Id'],'cleanup':True}))
NA PRÁTICA

Vizinho Full e rota instalada permitem avançar para a sonda; a transação continua a precisar de validação própria.

Armadilhas comuns

Confundir ICMP com transação; omitir a origem; tratar timeout como sucesso; contar containers como diversidade física.

Tópicos relacionados: OSPF: adjacência e falhas controladas · OSPF: áreas, sumarização e destinos cobertos

Leva esta ideia contigo

Entrega evidência reproduzível com contexto e limites, além do resultado agregado.

Criar conta

Referência: FRRouting OSPFv2 · 350-401 ENCOR v1.2, effective 2026-03-19; core component of CCNP Enterprise

CCNP® e Cisco® são marcas registadas da Cisco Systems, Inc. e/ou das suas afiliadas. A dr.pt é uma plataforma de preparação independente e não está afiliada, associada, patrocinada, autorizada nem aprovada por Cisco. Os conteúdos e as perguntas são originais, não são perguntas oficiais de exame, e concluir os nossos testes não atribui nem garante qualquer certificação. Os nomes são usados apenas para identificar o tema. Todas as outras marcas pertencem aos respetivos titulares.