Cisco ZBF Self Zone: Why One Zone-Pair Broke OSPF and BGP

SSH stayed reachable while unicast OSPF and loopback eBGP failed on IOSv. This Containerlab home lab follows the timer expiries, ineffective OSPF inspection, reverse-policy trap, and verified repair with OSPF pass and stateful TCP/ICMP.

ZBF self zone lab cover: SSH still works while OSPF and BGP fail, fixed by passing OSPF both ways and inspecting TCP and ICMP

The change was meant to restrict management access. SSH still worked afterward, but ping to the router failed, OSPF lost its neighbor, and the loopback eBGP session went down.

On br-edge, the clock read 04:44:08.546 UTC just after the restrictive WAN→self policy attached. OSPF logged its dead-timer expiry at 04:44:16.134. BGP logged Down at 04:44:22.621, following a hold-time-expired notification. A successful SSH login would have missed both failures.

This zone-based firewall (ZBF) self zone lab was built in my home lab in Containerlab, with the router guests running in a nested VM setup. The complete failure used unicast NON_BROADCAST OSPF on IOSv 15.9(3)M12. Broadcast-network multicast OSPF survived the earlier attached-policy test on this image. That observation does not establish that multicast hellos are always safe.

The zone-based firewall on IOS XE covers zones, the self-zone default, and an SSH-only policy that breaks ping on cat8000v; start there for the basics. Here the useful difference is routing: which adjacency dies first, why OSPF needs pass, and why fixing inbound traffic still leaves the router's own sessions broken.

Topology and addressing

The data path was lan-host → br-edge → upstream → noc-host. br-edge ran the firewall on IOSv; upstream ran IOL 17.18.2. OSPF across the WAN supplied reachability between the loopbacks used for eBGP.

Lab diagram: lan-host (eth1 10.34.10.10/24) connects to br-edge, an IOSv 15.9(3)M12 zone-based firewall router, on Gi0/1 10.34.10.1/24 in ZN-LAN. br-edge Gi0/2 100.64.34.1/30 in ZN-WAN connects to upstream (IOL 17.18.2) e0/1 100.64.34.2/30, and upstream e0/2 10.34.200.1/24 serves noc-host 10.34.200.10/24. OSPF process 34 area 0 runs non-broadcast on the WAN link with hello 2 and dead 8 seconds. eBGP runs between loopbacks, br-edge Lo0 10.34.255.1 in AS 65034 and upstream Lo0 192.0.2.34 in AS 65200, with multihop 2 and 5/15 second timers. NOC SSH on TCP/22 targets 100.64.34.1. Zone-pairs: ZP-LAN-WAN inspects TCP, UDP and ICMP; ZP-WAN-SELF and ZP-SELF-WAN inspect BGP and ICMP and pass OSPF, with SSH inspected inbound. A static 10.34.200.10/32 via 100.64.34.2 keeps the NOC return path, and distance bgp 200 keeps OSPF as the underlay.
Device Interface Address Role
br-edge, IOSv 15.9(3)M12 Gi0/1 10.34.10.1/24 lan-host gateway; ZN-LAN
br-edge Gi0/2 100.64.34.1/30 WAN toward upstream; ZN-WAN
br-edge Lo0 10.34.255.1/32 Router endpoint; AS 65034
upstream, IOL 17.18.2 Ethernet0/1 100.64.34.2/30 WAN toward br-edge
upstream Ethernet0/2 10.34.200.1/24 NOC gateway
upstream Lo0 192.0.2.34/32 eBGP peer; AS 65200
lan-host eth1 10.34.10.10/24 Gateway 10.34.10.1
noc-host eth1 10.34.200.10/24 Gateway 10.34.200.1

IOL 17.18.2 rejected the zone security, parameter-map type inspect, and zone-pair security commands with invalid-input errors, even from privileged configuration mode. I replaced only the firewall router with IOSv. These are IOSv ZBF results, not a working IOL firewall implementation. The Gi interface names in the output belong to that fallback.

OSPF process 34 used area 0, passive endpoint LANs and loopbacks, and a WAN hello/dead interval of 2/8 seconds. The complete reproduction changed both WAN interfaces to non-broadcast and configured explicit neighbors. (OSPF network types explains why NBMA needs neighbor statements.) During troubleshooting, neighbor poll intervals became two seconds for faster retries.

These resulting OSPF settings include that later poll-interval adjustment. On br-edge:

interface GigabitEthernet0/2
 ip address 100.64.34.1 255.255.255.252
 zone-member security ZN-WAN
 ip ospf network non-broadcast
 ip ospf dead-interval 8
 ip ospf hello-interval 2
 duplex auto
 speed auto
 media-type rj45
!
router ospf 34
 router-id 10.34.255.1
 passive-interface default
 no passive-interface GigabitEthernet0/2
 network 10.34.10.0 0.0.0.255 area 0
 network 10.34.255.1 0.0.0.0 area 0
 network 100.64.34.0 0.0.0.3 area 0
 neighbor 100.64.34.2 poll-interval 2
!

On upstream:

interface Ethernet0/1
 ip address 100.64.34.2 255.255.255.252
 ip ospf network non-broadcast
 ip ospf dead-interval 8
 ip ospf hello-interval 2
!
...
router ospf 34
 router-id 192.0.2.34
 passive-interface default
 no passive-interface Ethernet0/1
 network 10.34.200.0 0.0.0.255 area 0
 network 100.64.34.0 0.0.0.3 area 0
 network 192.0.2.34 0.0.0.0 area 0
 neighbor 100.64.34.1 poll-interval 2
!

BGP ran between the loopbacks with multihop 2 and keepalive/hold timers of 5/15 seconds (configure eBGP neighbors covers ebgp-multihop and update-source). That separates the eBGP session from the connected WAN link: losing the OSPF route also removes its underlay.

Two lab controls matter. A static route covered only the NOC host, preserving SSH's return path after OSPF failed; SSH targeted the connected WAN address. There was no static route to the BGP peer loopback. Also, early BGP flaps coincided with BGP replacing the peer's OSPF /32. Raising BGP distance on br-edge preserved OSPF as the underlay:

router bgp 65034
 bgp log-neighbor-changes
 network 10.34.10.0 mask 255.255.255.0
 neighbor 192.0.2.34 remote-as 65200
 neighbor 192.0.2.34 ebgp-multihop 2
 neighbor 192.0.2.34 update-source Loopback0
 neighbor 192.0.2.34 timers 5 15
 distance bgp 200 200 200
...
ip route 10.34.200.10 255.255.255.255 100.64.34.2 name NOC-TEST-CONTROL

The distance setting is a fixture for this topology, not a general BGP recommendation.

Baseline: no self zone-pair

The zone inventory and baseline pair output showed the LAN/WAN membership and one transit policy. There was no self pair:

br-edge#show zone security
zone self
Description: System Defined Zone 

zone ZN-LAN
 Member Interfaces: 
 GigabitEthernet0/1 

zone ZN-WAN
 Member Interfaces: 
 GigabitEthernet0/2 

br-edge#show zone-pair security
Zone-pair name ZP-LAN-WAN
    Source-Zone ZN-LAN  Destination-Zone ZN-WAN 
    service-policy PM-LAN-TO-WAN

The LAN→WAN policy inspected transit TCP, UDP, and ICMP. It did not govern packets terminating on br-edge's WAN address or loopback. The self zone has no member interfaces; it covers every router address, including Lo0 (Cisco ZBF configuration guide). Cisco documents the self zone as the one exception to ZBF's default deny: traffic to and from the router is permitted until a policy restricts it (Cisco ZBF design guide).

After rebuilding the unicast baseline, OSPF was FULL and BGP was Established with one received prefix. In the BGP summary, the numeric final column is the prefix count:

br-edge#show ip ospf neighbor

Neighbor ID     Pri   State           Dead Time   Address         Interface
192.0.2.34        1   FULL/DR         00:00:06    100.64.34.2     GigabitEthernet0/2
...
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
192.0.2.34      4        65200      20      20       17    0    0 00:01:10        1

The NOC host received all three ping replies from both router addresses, SSH v2 succeeded, and LAN ping/HTTP to the NOC worked. A router-originated loopback ping also passed:

br-edge#ping 10.34.200.10 source lo0 repeat 4 timeout 1
Type escape sequence to abort.
Sending 4, 100-byte ICMP Echos to 10.34.200.10, timeout is 1 seconds:
Packet sent with a source address of 10.34.255.1 
!!!!
Success rate is 100 percent (4/4), round-trip min/avg/max = 4/11/21 ms

I kept these separate tests because transit reachability, inbound management, and router-originated traffic exercise different policies.

The change: SSH survives while routing expires

There was a parser trap before the actual outage. IOSv rejected attaching the self policy when its class used SSH application inspection:

%Protocol configured in class-map CM-NOC-SSH cannot be configured for the self zone with inspect action. Please remove the protocol and retry

Cisco lists this limit: self-zone policies support TCP, UDP, ICMP and H.323 inspection only, with no application inspection (Cisco ZBF design guide). The bare zone-pair left by that rejected attachment did not reproduce the outage. Replacing SSH application matching with TCP plus an ACL allowed the service policy to attach. The relevant fault configuration, excerpted from the running configuration, was:

class-map type inspect match-all CM-NOC-SSH
 match access-group name ACL-NOC-SSH
 match protocol tcp
...
policy-map type inspect PM-WAN-TO-SELF
 class type inspect CM-NOC-SSH
  inspect 
 class class-default
  drop log
...
zone-pair security ZP-WAN-SELF source ZN-WAN destination self
 service-policy type inspect PM-WAN-TO-SELF
...
ip access-list extended ACL-NOC-SSH
 permit tcp 10.34.200.0 0.0.0.255 any eq 22

The SSH class used match-all, requiring both TCP and the NOC source/port ACL. With match-any, adding that ACL alongside a protocol match would express OR. The nested class-map lab covers more elaborate classification; this rule needed a simple conjunction. SSH setup itself is covered in enable SSH on Cisco.

In the first attached-policy test, multicast OSPF stayed FULL while BGP and ping failed. I then removed the self pair, changed both OSPF WAN interfaces to NON_BROADCAST with explicit neighbors, and verified the new baseline before applying the same restriction again. The full failure below belongs to that unicast test.

The successful attachment and its clock anchor were:

br-edge(config)#zone-pair security ZP-WAN-SELF source ZN-WAN destination self
br-edge(config-sec-zone-pair)#service-policy type inspect PM-WAN-TO-SELF
br-edge(config-sec-zone-pair)#end
br-edge#show clock
*04:44:08.546 UTC Wed Oct 21 2026

OSPF was still FULL immediately afterward. Existing neighbor state can outlive the packets maintaining it. With the lab's OSPF hello/dead timers, the failure appeared several seconds later:

*Oct 21 04:44:09.256: %FW-6-DROP_PKT: Dropping tcp session 192.0.2.34:179 10.34.255.1:41218 on zone-pair ZP-WAN-SELF class class-default due to  DROP action found in policy-map with ip ident 52216 
...
*Oct 21 04:44:16.134: %OSPF-5-ADJCHG: Process 34, Nbr 192.0.2.34 on GigabitEthernet0/2 from FULL to DOWN, Neighbor Down: Dead timer expired
...
*Oct 21 04:44:22.586: %BGP-3-NOTIFICATION: sent to neighbor 192.0.2.34 4/0 (hold time expired) 0 bytes 
...
*Oct 21 04:44:22.621: %BGP-5-ADJCHANGE: neighbor 192.0.2.34 Down BGP Notification sent

Those OSPF and BGP Down events were 7.588 and 14.075 seconds after the post-change clock reading. They are clock-anchor deltas, not exact configuration commit latencies. The routers' clocks were unsynchronized and drifted, so I do not subtract timestamps across devices.

The later neighbor snapshot showed an NBMA neighbor in ATTEMPT, not an empty OSPF table, and BGP Idle:

br-edge#show ip ospf neighbor

Neighbor ID     Pri   State           Dead Time   Address         Interface
N/A               0   ATTEMPT/DROTHER    -        100.64.34.2     GigabitEthernet0/2
...
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
192.0.2.34      4        65200       0       0        1    0    0 00:00:29 Idle

OSPF routes and the received BGP prefix disappeared. From noc-host, both router targets lost all replies:

PING 10.34.255.1 (10.34.255.1): 56 data bytes

--- 10.34.255.1 ping statistics ---
4 packets transmitted, 0 packets received, 100% packet loss
PING 100.64.34.1 (100.64.34.1): 56 data bytes

--- 100.64.34.1 ping statistics ---
4 packets transmitted, 0 packets received, 100% packet loss

The WAN-source control failed as well:

br-edge#ping 10.34.200.10 source 100.64.34.1 repeat 3 timeout 1
Type escape sequence to abort.
Sending 3, 100-byte ICMP Echos to 10.34.200.10, timeout is 1 seconds:
Packet sent with a source address of 100.64.34.1 
...
Success rate is 0 percent (0/3)

A fresh SSH v2 login to the WAN address still succeeded. Its /32 return route was the deliberate control that kept management reachable while the routing experiment failed.

Sessions and drops during the fault

The session view showed only the SSH class and the default drop. This snapshot followed the short SSH test closing, so it contained no live SSH entry; the earlier inspection statistics recorded one session creation.

br-edge#show policy-map type inspect zone-pair ZP-WAN-SELF sessions

policy exists on zp ZP-WAN-SELF
  Zone-pair: ZP-WAN-SELF 

  Service-policy inspect : PM-WAN-TO-SELF

    Class-map: CM-NOC-SSH (match-all)  
      Match: access-group name ACL-NOC-SSH
      Match: protocol tcp

   Inspect

    Class-map: class-default (match-any)  
      Match: any 
      Drop
        28 packets, 1668 bytes
br-edge#show policy-firewall stats drop-counters
DROP action found in policy-map                            82
Stray Segment                                              5

The 28 default drops were local to this pair. The global DROP-action total was 82 and included earlier experiments. Neither number alone identifies every dropped protocol. The tuple in the log, the neighbor transition, and the packet capture provide the context.

The closed WAN capture, taken outside IOS at the guest's WAN NIC, contained arriving unicast OSPF hellos, BGP retransmissions, and received ICMP echo replies during failed ping tests. Early replies reached br-edge before underlay routes disappeared. A WAN-source ping also failed despite received replies, isolating firewall return-path filtering from later loopback routing loss. All four captures reported zero kernel drops.

Why the ZBF self zone policy caught OSPF and BGP

For a zone-based firewall, the self zone covers the router's own endpoints. BGP between loopbacks is router traffic, just as SSH to the WAN IP is. Neither belongs to the LAN→WAN transit policy.

Attaching the restrictive WAN→self policy put unmatched inbound traffic into its default drop. It allowed NOC TCP/22, but omitted unicast OSPF, BGP TCP traffic, and ICMP. The BGP drop log even showed traffic sourced from TCP/179 toward the local ephemeral port of an existing connection.

At that point self→WAN still had no pair. The router could send echo requests, but that did not create inspected state for their replies. An inbound rule allowing echo requests also would not admit echo replies to a router-originated ping. This distinction explains why an apparently repaired inbound policy later left outbound ping broken.

Treat the multicast result as a limit of this observation. Cisco also states that ZBF has no stateful inspection for multicast, including to the self zone, and points to CoPP for multicast control-plane protection (Cisco ZBF configuration guide). That is context, not a proven cause here. I did not establish its internal cause or prove equivalent behavior on other images. The outage evidence is for unicast OSPF on this IOSv.

OSPF inspect on the self zone: accepted, no sessions

I first added an ACL matching OSPF from upstream and gave that class an inspect action:

br-edge(config)#ip access-list extended ACL-OSPF-IN
br-edge(config-ext-nacl)#permit ospf host 100.64.34.2 any
br-edge(config-ext-nacl)#exit
br-edge(config)#class-map type inspect match-any CM-OSPF
br-edge(config-cmap)#match access-group name ACL-OSPF-IN
br-edge(config-cmap)#exit
br-edge(config)#policy-map type inspect PM-WAN-TO-SELF
br-edge(config-pmap)#class type inspect CM-OSPF
br-edge(config-pmap-c)#inspect
br-edge(config-pmap-c)#end

The parser accepted it. Runtime behavior did not follow: 20 packets matched, no sessions were created, Invalid Segment reached 22, and the neighbor remained ATTEMPT.

    Class-map: CM-OSPF (match-any)  
      Match: access-group name ACL-OSPF-IN
        20 packets, 1600 bytes
        30 second rate 0 bps

   Inspect
        Session creations since subsystem startup or last reset 0
        Current session counts (estab/half-open/terminating) [0:0:0]
...
br-edge#show policy-firewall stats drop-counters
DROP action found in policy-map                            82
Invalid Segment                                            22
Stray Segment                                              5
br-edge#show ip ospf neighbor

Neighbor ID     Pri   State           Dead Time   Address         Interface
N/A               0   ATTEMPT/DROTHER    -        100.64.34.2     GigabitEthernet0/2

OSPF could not be inspected successfully in this test. Matching an ACL did not make it a stateful session. That fits Cisco's limits: an ACL-only inspect class gets basic TCP or UDP inspection, and self-zone inspection covers only TCP, UDP, ICMP and H.323 (Cisco ZBF design guide). OSPF is IP protocol 89. I changed only that class's action from inspect to pass:

br-edge(config)#policy-map type inspect PM-WAN-TO-SELF
br-edge(config-pmap)#class type inspect CM-OSPF
br-edge(config-pmap-c)#no inspect
br-edge(config-pmap-c)#pass
br-edge(config-pmap-c)#end

OSPF logged FULL at 04:47:18.179, 1.018 seconds after the post-change clock anchor. BGP remained blocked and appeared Active in the next snapshot:

br-edge#show ip ospf neighbor

Neighbor ID     Pri   State           Dead Time   Address         Interface
192.0.2.34        1   FULL/DR         00:00:06    100.64.34.2     GigabitEthernet0/2
...
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
192.0.2.34      4        65200       0       0        1    0    0 00:03:18 Active

The OSPF neighbor troubleshooting guide helps distinguish adjacency states; here the action change and recovering neighbor tied the failure to the firewall. BGP's continuing Idle/Active state was a separate permit problem, even with its OSPF underlay restored; BGP neighbor stuck in Idle or Active covers the general causes.

The fix: pass OSPF and inspect BGP on both self zone-pairs

Fix diagram: br-edge self-zone policy in both directions. ZP-WAN-SELF passes inbound OSPF from 100.64.34.2 (CM-OSPF) and ZP-SELF-WAN passes outbound OSPF (CM-SELF-OSPF). BGP TCP/179 to and from upstream Lo0 192.0.2.34 is inspected in both pairs (CM-BGP, CM-SELF-BGP), NOC SSH TCP/22 is inspected inbound (CM-NOC-SSH), and ICMP echo is inspected NOC to router and Lo0 to NOC (CM-NOC-ICMP, CM-SELF-ICMP). Everything else hits class-default drop log; a TCP/80 probe raised default drops from 45 to 48. Two traps: inspecting OSPF matched 20 packets with 0 sessions and left the neighbor in ATTEMPT, and a self-to-WAN pair without the OSPF pass left br-edge in INIT with BGP Idle. Result: OSPF FULL/DR, BGP up with 1 prefix, NOC and Lo0 pings 4/4 with min/avg/max 10/27/57 ms, and SSH v2 working.

The final policy passed OSPF in each direction and inspected the permitted TCP/ICMP initiations. Pass is one-way and creates no return-session allowance. Cisco documents both halves: pass allows traffic in one direction only (Cisco ZBF design guide), and passed traffic bypasses the zone firewall without creating sessions (Cisco ZBF configuration guide). Inspection supplied state for TCP and ICMP returns, including sessions started by the router.

Direction Permitted initiation Action
WAN→self NOC TCP/22 Inspect TCP with source/port ACL
WAN→self OSPF from 100.64.34.2 Pass
WAN→self BGP peer TCP tuples Inspect
WAN→self NOC echo to WAN IP or Lo0 Inspect ICMP
self→WAN Router BGP peer TCP tuples Inspect
self→WAN Lo0 echo to noc-host Inspect ICMP
self→WAN OSPF from 100.64.34.1 Pass

The second trap, before the complete configuration

Adding inbound BGP and NOC echo inspection recovered BGP and inbound ping. An inbound BGP session was visible:

        Session FAD4D00 (192.0.2.34:22168)=>(10.34.255.1:179) tcp SIS_OPEN/TCP_ESTAB 
          Created 00:00:37, Last heard 00:00:01
          Bytes sent (initiator:responder) [306:305]

But Lo0-sourced router ping remained 0/3. I added a reverse policy inspecting router-originated BGP and ICMP, initially leaving OSPF out. This was the second fault:

br-edge(config)#zone-pair security ZP-SELF-WAN source self destination ZN-WAN
br-edge(config-sec-zone-pair)#service-policy type inspect PM-SELF-TO-WAN
br-edge(config-sec-zone-pair)#end

The new restrictive direction dropped outgoing OSPF. Incoming hellos continued, leaving br-edge INIT while upstream lost FULL, the classic one-way hello INIT state, caused here by the firewall; BGP fell back to Idle. The outbound log called the flow Unknown-l4:

br-edge#show ip ospf neighbor

Neighbor ID     Pri   State           Dead Time   Address         Interface
192.0.2.34        1   INIT/DROTHER    00:00:06    100.64.34.2     GigabitEthernet0/2
...
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
192.0.2.34      4        65200       0       0        1    0    0 00:00:13 Idle
...
*Oct 21 04:49:09.205: %FW-6-DROP_PKT: Dropping Unknown-l4 session 100.64.34.1:0 100.64.34.2:0 on zone-pair ZP-SELF-WAN class class-default due to  DROP action found in policy-map with ip ident 1208 

The reverse pair's default counter grew from 1 packet just after attachment to 20 in the later diagnostic snapshot. The capture showed outbound hellos ceasing while inbound hellos continued. Passing inbound OSPF had never authorized outbound OSPF.

Adding the outbound OSPF ACL/class and pass action completed the repair:

br-edge(config)#ip access-list extended ACL-OSPF-OUT
br-edge(config-ext-nacl)#permit ospf host 100.64.34.1 any
br-edge(config-ext-nacl)#exit
br-edge(config)#class-map type inspect match-any CM-SELF-OSPF
br-edge(config-cmap)#match access-group name ACL-OSPF-OUT
br-edge(config-cmap)#exit
br-edge(config)#policy-map type inspect PM-SELF-TO-WAN
br-edge(config-pmap)#class type inspect CM-SELF-OSPF
br-edge(config-pmap-c)#pass
br-edge(config-pmap-c)#end

Final ACLs, classes, and both self pairs

These are the working firewall blocks from br-edge. The BGP ACLs include destination-port and source-port variants; IOS renders TCP/179 as eq bgp. The SSH ACL permits the NOC subnet to TCP/22 on router endpoints, rather than only one destination address. The ICMP rules are narrower and name the tested endpoints.

ip access-list extended ACL-BGP-IN
 permit tcp host 192.0.2.34 host 10.34.255.1 eq bgp
 permit tcp host 192.0.2.34 eq bgp host 10.34.255.1
ip access-list extended ACL-BGP-OUT
 permit tcp host 10.34.255.1 host 192.0.2.34 eq bgp
 permit tcp host 10.34.255.1 eq bgp host 192.0.2.34
ip access-list extended ACL-NOC-ICMP
 permit icmp 10.34.200.0 0.0.0.255 host 10.34.255.1 echo
 permit icmp 10.34.200.0 0.0.0.255 host 100.64.34.1 echo
ip access-list extended ACL-NOC-SSH
 permit tcp 10.34.200.0 0.0.0.255 any eq 22
ip access-list extended ACL-OSPF-IN
 permit ospf host 100.64.34.2 any
ip access-list extended ACL-OSPF-OUT
 permit ospf host 100.64.34.1 any
ip access-list extended ACL-SELF-ICMP
 permit icmp host 10.34.255.1 host 10.34.200.10 echo

The final class definitions also include the existing LAN transit class:

class-map type inspect match-all CM-NOC-SSH
 match access-group name ACL-NOC-SSH
 match protocol tcp
class-map type inspect match-all CM-SELF-ICMP
 match protocol icmp
 match access-group name ACL-SELF-ICMP
class-map type inspect match-any CM-SELF-OSPF
 match access-group name ACL-OSPF-OUT
class-map type inspect match-any CM-OSPF
 match access-group name ACL-OSPF-IN
class-map type inspect match-all CM-SELF-BGP
 match protocol tcp
 match access-group name ACL-BGP-OUT
class-map type inspect match-any CM-LAN-INTERNET
 match protocol tcp
 match protocol udp
 match protocol icmp
class-map type inspect match-all CM-NOC-ICMP
 match protocol icmp
 match access-group name ACL-NOC-ICMP
class-map type inspect match-all CM-BGP
 match protocol tcp
 match access-group name ACL-BGP-IN

Both self policies end with default drop logging. The existing LAN→WAN pair remains separate:

policy-map type inspect PM-WAN-TO-SELF
 class type inspect CM-NOC-SSH
  inspect 
 class type inspect CM-OSPF
  pass
 class type inspect CM-BGP
  inspect 
 class type inspect CM-NOC-ICMP
  inspect 
 class class-default
  drop log
policy-map type inspect PM-SELF-TO-WAN
 class type inspect CM-SELF-BGP
  inspect 
 class type inspect CM-SELF-ICMP
  inspect 
 class type inspect CM-SELF-OSPF
  pass
 class class-default
  drop log
...
zone-pair security ZP-WAN-SELF source ZN-WAN destination self
 service-policy type inspect PM-WAN-TO-SELF
zone-pair security ZP-SELF-WAN source self destination ZN-WAN
 service-policy type inspect PM-SELF-TO-WAN

This is TCP inspection selected by ACL, not SSH application inspection. ZBF port-maps address a different classification problem; changing application ports would not repair an omitted OSPF pass action here.

After the fix: neighbors, routes, and real return traffic

The final post-change clock read 04:50:08.826. OSPF logged FULL at 04:50:09.006 and BGP Up at 04:50:14.359: anchor deltas of 0.180 and 5.533 seconds, respectively.

*Oct 21 04:50:09.006: %OSPF-5-ADJCHG: Process 34, Nbr 192.0.2.34 on GigabitEthernet0/2 from LOADING to FULL, Loading Done
...
*Oct 21 04:50:14.359: %BGP-5-ADJCHANGE: neighbor 192.0.2.34 Up 

The subsequent snapshot confirmed FULL and an Established BGP session receiving one prefix:

br-edge#show ip ospf neighbor

Neighbor ID     Pri   State           Dead Time   Address         Interface
192.0.2.34        1   FULL/DR         00:00:07    100.64.34.2     GigabitEthernet0/2
...
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
192.0.2.34      4        65200      13      12       21    0    0 00:00:40        1

On br-edge, the peer prefix was best in BGP but marked RIB-failure because OSPF retained the route under the distance control. Upstream installed the advertised LAN prefix through BGP. These excerpts are from br-edge's BGP table, br-edge's route table, and upstream's route table, respectively:

     Network          Next Hop            Metric LocPrf Weight Path
 *>   10.34.10.0/24    0.0.0.0                  0         32768 i
 r>   192.0.2.34/32    192.0.2.34               0             0 65200 i
...
O        192.0.2.34 [110/2] via 100.64.34.2, 00:00:44, GigabitEthernet0/2
...
B        10.34.10.0/24 [20/0] via 10.34.255.1, 00:00:45

NOC pings to both router addresses returned 4/4, SSH v2 worked, and LAN ping/HTTP worked. The router's own Lo0-sourced ping now returned 4/4 too:

br-edge#ping 10.34.200.10 source lo0 repeat 4 timeout 1
Type escape sequence to abort.
Sending 4, 100-byte ICMP Echos to 10.34.200.10, timeout is 1 seconds:
Packet sent with a source address of 10.34.255.1 
!!!!
Success rate is 100 percent (4/4), round-trip min/avg/max = 10/27/57 ms

Read policy-map type inspect zone-pair sessions and counters

The session commands showed router-originated BGP, router-originated ICMP, and inbound SSH with bytes in both directions. These excerpts omit other classes:

br-edge#show policy-map type inspect zone-pair ZP-SELF-WAN sessions

policy exists on zp ZP-SELF-WAN
  Zone-pair: ZP-SELF-WAN 

  Service-policy inspect : PM-SELF-TO-WAN
...
        Session FAD5E30 (10.34.255.1:21032)=>(192.0.2.34:179) tcp SIS_OPEN/TCP_ESTAB 
          Created 00:00:48, Last heard 00:00:01
          Bytes sent (initiator:responder) [343:344]
...
        Session FAD7640 (10.34.255.1:8)=>(10.34.200.10:0) icmp SIS_OPEN
          Created 00:00:01, Last heard 00:00:00
          ECHO request
          Bytes sent (initiator:responder) [288:288]
...
br-edge#show policy-map type inspect zone-pair ZP-WAN-SELF sessions

policy exists on zp ZP-WAN-SELF
  Zone-pair: ZP-WAN-SELF 

  Service-policy inspect : PM-WAN-TO-SELF
...
        Session FAD72D0 (10.34.200.10:44514)=>(100.64.34.1:22) tcp SIS_OPEN/TCP_ESTAB 
          Created 00:00:13, Last heard 00:00:12
          Bytes sent (initiator:responder) [2878:5611]

The final BGP connection originated on br-edge toward upstream TCP/179. Earlier, the inbound pair held a connection initiated by upstream. Keeping BGP inspection in both policies supported those permitted initiations. SSH replies worked without a separate outbound SSH class because the inbound connection was inspected.

During a quiet comparison window, inbound OSPF pass counts rose 163→175 and outbound counts 63→75. Router BGP inspection packets rose 88→107. WAN→self and self→WAN default drops stayed at 45 and 34; the global DROP-action count stayed 133. Old cumulative drops were not continuing failures. The later per-class excerpt showed:

    Class-map: CM-OSPF (match-any)  
        211 packets, 16588 bytes
      Pass
        175 packets, 13708 bytes
...
    Class-map: CM-SELF-OSPF (match-any)  
        75 packets, 4652 bytes
      Pass
        75 packets, 4652 bytes
    Class-map: class-default (match-any)  
      Drop
        34 packets, 2040 bytes

A deliberate denied test still dropped

From noc-host, a TCP/80 attempt to the router loopback timed out with curl exit 28. The router logged the denied tuple:

*   Trying 10.34.255.1:80...
* Connection timed out after 3002 milliseconds
* closing connection #0
...
*Oct 21 04:52:30.353: %FW-6-DROP_PKT: Dropping tcp session 10.34.200.10:35684 10.34.255.1:80 on zone-pair ZP-WAN-SELF class class-default due to  DROP action found in policy-map with ip ident 17699 

WAN default drops rose 45→48 and global DROP action 133→136. The other global error counters stayed unchanged:

    Class-map: class-default (match-any)  
      Drop
        48 packets, 2396 bytes
br-edge#show policy-firewall stats drop-counters
DROP action found in policy-map                            136
Invalid Segment                                            36
Stray Segment                                              10
Invalid Flags                                              7
Retransmitted Segment with Invalid Flags                   2

That tied the new drop increments to the intentional probe while routing remained recovered. A timeout alone would not have supplied that proof.

Self zone rules I follow

  • Inventory router endpoints and initiations before attaching a self policy: routing peers, management sources, monitoring, and locally sourced probes.
  • Preserve a tested management return path. The NOC /32 made this experiment readable after OSPF disappeared.
  • Use explicit pass for OSPF in both governed directions. Do not mistake an accepted inspect command for working inspection.
  • Inspect permitted TCP/ICMP initiations where their return traffic needs state. Test from the router as well as toward it.
  • Recheck routing when adding the reverse pair. It introduces another restrictive direction and can undo an inbound repair.
  • Compare counter deltas alongside neighbor states and session tuples. Keep historical drop totals separate from new failures.

These observations concern Cisco ZBF router traffic on the tested image. Control-plane policing is a separate topic; this run did not test a CoPP change. The ZBF, ASA, and FTD comparison provides the wider firewall context.

💡
Test the router as a client, too. The free Cisco ASA Field Reference PDF puts the packet pipeline, NAT order, capture commands and a troubleshooting decision tree on nine printable pages. PingLabz Pro unlocks all 68 browser labs, 500 CCNA practice questions and two timed mock exams for $59/year or $7.99/month.
Coming soon · planned price $19
ASA Firewall Field Kit
A troubleshooting runbook for packet flow, packet-tracer, captures, asp-drop, NAT, failover and IPsec, plus the ASAv Containerlab topologies from the PingLabz ASA labs. It isn't built yet.
See what's planned and join the waitlist

Verification checklist

  1. Record zones and pairs before attaching policy; identify which direction will change.
  2. Confirm the OSPF network type and both peers' timers. Keep the multicast counterexample separate from the unicast failure.
  3. Check FULL and BGP's received-prefix count after waiting beyond the lab's dead/hold intervals.
  4. Verify the peer-loopback route, the management return route, and the advertised prefixes.
  5. Test NOC→router, router→NOC with an explicit source, and LAN transit separately.
  6. Read sessions on both self pairs and compare per-class pass/inspect/default-drop counters across two samples.
  7. Make one deliberate denied probe and correlate its tuple, log, and drop increment.

FAQ

Why did SSH work after OSPF and BGP went down?

The policy explicitly inspected NOC TCP/22. SSH targeted the connected WAN IP, and the static NOC /32 preserved the return route. That success did not depend on the missing OSPF peer-loopback route.

Should ZBF OSPF use pass or inspect?

Use the pass behavior demonstrated here. IOSv accepted ACL-based OSPF inspect but created no sessions and left the neighbor ATTEMPT. Pass restored it. Once both directions had restrictive self policies, both needed OSPF pass. Cisco's pass action is one-way and creates no session (Cisco ZBF design guide, Cisco ZBF configuration guide), so each restrictive direction needs its own OSPF pass.

Did the self policy also break multicast OSPF?

No. Broadcast-network multicast OSPF survived the earlier attached policy on this IOSv. The complete OSPF outage required NON_BROADCAST unicast neighbors. This run does not prove multicast safety on other images or configurations.

Why did inbound ping recover before router-originated ping?

The inbound ICMP class allowed NOC echo requests. It did not allow the echo replies needed by a router-originated request. Inspecting the permitted outbound echo in self→WAN created the return state; the final session showed request and response bytes. Cisco documents this exact behavior (Cisco ZBF design guide, Control Access to the Router).

Which protocols can a ZBF self zone policy inspect?

Cisco limits self-zone inspection to TCP, UDP, ICMP and H.323, with no application inspection (Cisco ZBF design guide, Self-Zone Policy Limitations). That's why IOSv rejected SSH application inspection here, and why OSPF, IP protocol 89, needs pass.

Does a zone-pair to self also block the router's own traffic?

Yes, for return traffic. Cisco documents that with only a zone-to-self policy, replies to router-originated traffic hit that policy and are blocked, so router-originated traffic must be inspected in a self-to-zone pair (Cisco ZBF design guide, Control Access to the Router).

Read next