Deploying CGN at ISP scale: Huawei NE8000 vs FortiGate 1000F
Carrier-Grade NAT solves one problem — sharing scarce IPv4 across many subscribers — but how you deploy it decides whether it's a calm utility or a 3 a.m. pager. This is how we deploy CGN / NAT444 properly at ISP scale on two cost-effective platforms: a carrier edge router with a dedicated NAT service board (Huawei NetEngine 8000 M8 + VSUP-100), and a next-gen firewall doing CGN alongside security (FortiGate 1000F, NP7-based). Both are shown side by side, but the important part isn't the box — it's the redundancy model, so we lead with that.
203.0.113.0/24, 198.51.100.0/24);
subscriber space is the real RFC 6598 range (100.64.0.0/10); ASNs use
documentation AS 64500; all secrets and identifiers are redacted. Private
10.x link/loopback addresses are illustrative.The redundancy model: two boxes, source-hashed, pure L3
The instinct is to reach for stateful HA — VRRP, an inter-chassis session-sync, an active/standby pair. We deliberately don't, for CGN. Instead we run two independent CGN nodes, each owning a different public subnet, and load-share subscribers across them with source-IP hash ECMP from the core/BNG. No VRRP, no shared session state, no split-brain — just L3 routing.
Why this beats VRRP/stateful HA for a CGN:
- Source-IP hashing pins a subscriber to one box, so its NAT mappings are stable — no per-flow flapping between nodes, no need to sync state.
- Failure is just routing. If
cgn-01dies, its pool and links withdraw from BGP/OSPF, the core reconverges, and those subscribers rehash ontocgn-02. They re-NAT behind a new public IP and reconnect in seconds — no split-brain, nothing to fail over by hand. - It scales by addition. Need more capacity? Add a third node and a third public subnet. The hash spreads onto it. No pair to rebuild.
- It's the same on both platforms. With the FortiGate you deploy multiple units anyway — this model means they're plain independent routers, not a fragile cluster.
service-ha, FortiGate FGCP) and preserves sessions through a
failover. We reach for it only when a few seconds of reconnect is genuinely
unacceptable. For most ISPs it adds operational complexity that this pure-L3 model
simply doesn't have.Colour code for the columns below: orange = the Huawei carrier way, green = the FortiGate way. Commands are verbatim-accurate; the surrounding explanation is ours.
In-box redundancy — and why we still run two
The two-box model above covers box and site failure. Within a single chassis, the two platforms are worlds apart in what they survive on their own — and that changes how much the "second box" is really carrying.
The M8 is a modular chassis that's already internally redundant: dual MPUs (active/standby control), redundant fabric / PSUs / fans, multiple line cards, and room for two VSUP service cards for 1+1 NAT-board backup. You enable it in-box:
! two VSUP cards — slot 7 active, slot 8 hot standby
service-location 7
location slot 7 backup slot 8
!
! license the NAT feature on BOTH slots (or the standby fails over cold)
license
active nat session-table size 3 slot 7
active nat session-table size 3 slot 8
!
! mirror the live NAT session table to the standby board
service-ha hot-backup enable
service-ha preempt-time 360
So one M8 already rides through a VSUP card failure (1+1, sessions intact), an MPU failure, and line-card / PSU / fan failures — no second box needed for any of those.
The 1000F is a fixed appliance — no line cards, no service boards. You get redundant PSUs and the NP7s, but there's no in-chassis NAT failover. Its "1+1" lives at the box level: a second unit in FGCP, or — in our model — a second independent source-hashed node.
That's the crux: on the FortiGate the second box is your only redundancy; on the Huawei it's an extra layer on top of a chassis that's already hardened internally.
1 — Where the NAT actually runs
The biggest architectural difference, and it colours everything else. Huawei offloads NAT to a purpose-built service card; FortiGate runs it in the NP7 network processors alongside firewalling, isolated in its own VDOM.
A dedicated VSUP-100 service board does the NAT. Everything binds to its service-location.
vsm on-board-mode disable
service-location 7
location slot 7 backup slot 8
service-instance-group CGN-SLOT-7
service-location 7
NAT runs in the NP7 ASICs; the CGN lives in its own VDOM. A caveat that lives in global config, not the CGN VDOM: NPU queue tuning so control/HA traffic is never starved by dataplane.
config system npu
set dedicated-management-cpu enable
config np-queues
config ip-service
edit "BGP"
set queue 9
next
edit "BFD-single-hop"
set queue 11
end
end
end
2 — Address pools & port allocation
Each node's public subnet is shared across its subscribers by handing each subscriber a block of ports (not per-flow ports) — that's what keeps CGN logging sane. The knobs line up almost one-to-one.
Port budget is set on the nat instance: a base block plus extended
blocks under load.
nat ip-pool CGN-GPON-FIBER-ACCESS-pool
section 0 203.0.113.0 mask 24
#
nat instance CGN id 1
port-range 2048 extended-port-range 8192 extended-times 3
nat ip access-user limit 16
nat filter mode full-cone
port-reuse enable
An ippool of type port-block-allocation: 1888 ports
per block, up to 4 blocks per subscriber, block idle-timeout 50s.
config firewall ippool
edit "CGN_pba_203.0.113.0"
set type port-block-allocation
set startip 203.0.113.0
set endip 203.0.113.255
set block-size 1888
set num-blocks-per-user 4
set pba-timeout 50
set permit-any-host enable
next
end
Same maths, different words: ~1888–2048 ports of headroom per subscriber, expandable
to 3–4× under load. permit-any-host enable is FortiGate's equivalent of
Huawei's full-cone — endpoint-independent mapping so P2P, gaming and VoIP
survive the CGN. Note each node's pool is a different subnet — that's
the source-hash model in the config.
5117–65533
(60,417 usable ports) that cannot be changed. 1888 is picked so exactly
32 blocks fit per public IP (1888 × 32 = 60,416) — i.e.
32 subscribers per public IP at one block each, with almost no wasted
ports. num-blocks-per-user 4 then lets a busy subscriber expand on demand (up
to 3 extra blocks); those heavier users draw down the 32, so the effective count falls as
subscribers grow. We target the FortiOS 7.4 release from v7.4.8 (build 2795)
onwards — earlier 7.4 builds were unstable with OSPF and BGP. Note
the fixed 5117–65533 range is version-specific, so re-check it on any
other release.3 — Selecting subscribers & applying NAT
Huawei matches inside prefixes with an ACL and binds NAT to the ingress interface. FortiGate does it as a firewall policy — which is actually an advantage: NAT and security are the same object, and it's trivial to add no-CGN passthrough policies for IPTV, DNS or already-public customers above the CGN rule.
acl number 3501
description GPON-FIBER-ACCESS
rule 1000 permit ip source 100.112.0.0 0.0.255.255
#
interface 100GE0/4/0
nat bind acl 3501 instance CGN precedence 0
config firewall address
edit "cgn-subscribers-01"
set subnet 100.64.0.0 255.255.128.0
next
end
config firewall policy
edit 1
set name "CGN_pba@isp"
set srcintf "vlan.2608" "vlan.2610"
set dstintf "vlan.2600" "vlan.2602"
set srcaddr "cgn-subscribers-01"
set dstaddr "all"
set logtraffic-start enable
set nat enable
set ippool enable
set poolname "CGN_pba_203.0.113.0" "CGN_pba_198.51.100.0"
set global-label "CGN"
next
end
precedence and by simply not binding those prefixes.Recommended block / allow posture
A CGN sits directly between your subscribers and the internet, which makes it the right place to enforce a hygiene policy — both to protect subscribers and to protect your IP reputation, since everyone on a pool shares the same public addresses. On the FortiGate this is an ordered set of firewall policies above and around the CGN rule; on the Huawei you'd express the same with interface ACLs. The posture we deploy:
Block
- Outbound SMTP (TCP/25), except sanctioned relays — the single most important rule. One compromised host behind CGN becomes a spam cannon and gets your shared public IPs blacklisted, hurting every subscriber on that pool. Permit only your own mail relays; block the rest.
- NetBIOS (135–139) & SMB (445) — worm / ransomware lateral-movement ports (the Sasser / Nimda / WannaCry lineage). They have no business crossing the internet edge.
- SSDP (1900) & other reflection/amplification sources — stop your subscribers being conscripted into DDoS reflection.
- Inbound SOCKS (1080) — open-proxy abuse.
- RIP (520) / any routing protocol from subscribers — nothing downstream should be injecting routes.
- Bogons & martians, both directions — drop RFC 1918, reserved and unallocated source/destination addresses.
Deny the abused ports, permit SMTP only to your relays, then apply it inbound on the subscriber-facing interface:
acl number 3900
description CGN-EDGE-HYGIENE
rule 10 permit tcp destination 198.51.100.240 0.0.0.15 destination-port eq smtp
rule 20 deny tcp destination-port eq smtp ! all other SMTP/25
rule 30 deny udp destination-port range 137 139 ! NetBIOS
rule 31 deny tcp destination-port eq 445 ! SMB
rule 40 deny udp destination-port eq 520 ! RIP
rule 50 deny tcp destination-port eq 1080 ! SOCKS
rule 60 deny udp destination-port eq 1900 ! SSDP
rule 1000 permit ip
#
interface 100GE0/4/0
traffic-filter inbound acl 3900
The reverse direction (inbound SOCKS, bogons from the internet) is a matching ACL
on the public interface; source-spoofing is usually handled with ip urpf
rather than an ACL.
FortiOS defaults action to deny, so a rule with no set action
accept blocks. dstaddr-negate flips "to the relays" into "to all
except the relays". (EXCLUDE_smtp = your relays,
BOGON-IPv4 = martian prefixes; subscriber side = vlan.2608/10,
internet side = vlan.2600/02.)
config firewall policy
edit 2 ! SMTP: block to all EXCEPT relays
set name "block_outgoing_SMTP"
set srcaddr "cgn-subscribers-01"
set dstaddr "EXCLUDE_smtp"
set dstaddr-negate enable
set service "SMTP"
next
edit 3
set name "block_NETBIOS"
set service "NETBIOS"
next
edit 4
set name "block_SMB" ! Sasser/Nimda/WannaCry
set service "SMB"
next
edit 5
set name "block_RIP"
set service "RIP"
next
edit 6 ! internet -> subscriber
set name "block_incoming_SOCKS"
set service "SOCKS"
next
edit 7
set name "block_SSDP" ! DDoS reflection
set service "SSDP"
next
edit 13
set name "block_BOGON-out"
set srcaddr "cgn-subscribers-01"
set dstaddr "BOGON-IPv4"
next
edit 17
set name "block_BOGON-in"
set srcaddr "BOGON-IPv4"
set dstaddr "cgn-subscribers-01"
next
end
Allow (and what to keep off the CGN)
- DNS to your resolvers — rate-limited per subscriber — allow it, but cap sessions so a compromised host can't run DNS amplification or hammer the resolvers.
- IPTV / managed multicast — routed straight through, no CGN.
- CPE management, RADIUS/RSSO accounting, loopback management — the control-plane bits the access network needs.
- Public-IP customers — passthrough without translation (these policies sit above the CGN rule).
- An overall per-subscriber session cap — the backstop from section 4, applied as a shaper on the catch-all.
The elegant part: anything that must skip CGN simply isn't in the NAT bind ACL, so IPTV, DNS and public customers route straight through — no explicit "allow" needed. DNS abuse is rate-limited with CAR:
! IPTV / DNS / public prefixes are NOT in acl 3501/3502,
! so they are never translated — no rule required.
#
! rate-limit DNS bandwidth per subscriber (CAR)
acl number 3910
rule 5 permit udp destination-port eq dns
traffic classifier DNS
if-match acl 3910
traffic behavior DNS-CAR
car cir 2000 pir 4000 green pass red discard
traffic policy SUB-EDGE
classifier DNS behavior DNS-CAR
interface 100GE0/4/0
traffic-policy SUB-EDGE inbound
Per-subscriber session caps come from the NAT session-limit
knobs in section 4, not a shaper.
config firewall policy ! all ABOVE the CGN catch-all
edit 8
set name "allow_RADIUS_RSSO"
set action accept
set srcaddr "rsso-proxy"
set dstaddr "lo2"
set service "RADIUS" "ALL_ICMP"
next
edit 9
set name "allow_IPTV" ! subs <-> iptv-core, no CGN
set action accept
set srcaddr "cgn-subscribers-01"
set dstaddr "iptv-core-01" "iptv-core-02"
set service "ALL"
next
edit 10
set name "allow_DNS" ! subs -> resolvers
set action accept
set srcaddr "cgn-subscribers-01"
set dstaddr "dns-resolvers"
set service "DNS" "ALL_ICMP"
next
edit 19
set name "allow_to_CPE_MGMT"
set action accept
set srcaddr "mgmt-jumphost"
set dstaddr "cgn-subscribers-01"
set service "HTTP" "HTTPS" "SSH"
next
! + reverse-path allows (allow_from_IPTV / _DNS)
! + allow_LOOPBACK_MGMT
end
#
config firewall shaping-policy ! per-subscriber caps (section 4 shapers)
edit 3
set name "outgoing-DNS_max_limit_2048"
set service "DNS"
set per-ip-shaper "conn_limit_2048-total"
next
edit 2
set name "outgoing-max_limit_7552"
set service "ALL"
set per-ip-shaper "conn_limit_7552"
next
end
traffic-filter plus
simply not binding NAT to what shouldn't be translated.4 — Per-subscriber session limits
A CGN's worst enemy is one compromised host opening hundreds of thousands of sessions. Both platforms cap sessions per subscriber; FortiGate borrows its traffic shaper to do it.
nat instance CGN id 1
nat session-limit total 12000
nat session-limit udp 8000
nat reverse-session-limit total 20000
config firewall shaper per-ip-shaper
edit "conn_limit_12k"
set max-concurrent-session 12288
next
edit "nat_pba_limit_4k"
set max-concurrent-session 7552
set max-concurrent-udp-session 4096
next
end
! then: set per-ip-shaper "conn_limit_12k" on the policy
5 — Session aging
nat instance CGN id 1
nat session aging-time tcp 600
nat session aging-time udp 300
nat session aging-time icmp 5
config system session-ttl
config port
edit 2
set protocol 17 ! UDP
set timeout 60
set refresh-direction outgoing
next
edit 3
set protocol 1 ! ICMP
set timeout 2
next
end
end
6 — Routing: OSPF for infra, BGP for policy
The routing design is deliberate and identical on both boxes — and it's what makes the two-box source-hash model clean:
- OSPF carries only point-to-point links and loopbacks. Nothing
else. Its only job is to make BGP next-hops and BFD reachable — a tiny, stable IGP
that almost never changes. Every uplink is
network-type point-to-point. - BGP carries the real traffic: the default route inbound (so translated subscribers reach the internet) and each node's public pools outbound (so return traffic lands on the right box). Pools are originated from a blackhole/NULL0 route and redistributed — advertise the aggregate, discard the unused.
! OSPF: p2p links + loopback only
ospf 1
area 0.0.0.0
network 169.254.0.10 0.0.0.1 ! /31 uplink
network 176.61.156.4 0.0.0.0 ! loopback
!
! BGP: pool out (blackhole), default in
ip route-static 203.0.113.0 255.255.255.0 NULL0
bgp 64500
ipv4-family unicast
import-route static route-policy NO-INSIDE-NAT-EXPORT
maximum load-balancing ebgp 8
config router ospf ! p2p + loopback only
config ospf-interface
edit "vlan.2608"
set network-type point-to-point
set bfd enable
end
config network
edit 5
set prefix 10.80.245.251 255.255.255.255 ! loopback
end
end
config router static
edit 1
set dst 203.0.113.0 255.255.255.0
set blackhole enable
next
end
config router bgp
set as 64500
set ebgp-multipath enable
set ibgp-multipath enable
config neighbor
edit "10.80.248.1"
set update-source "vlan.2600"
set prefix-list-in "default_route"
set prefix-list-out "cgn_public_prefixes"
set remote-as 64500
set password ENC <redacted>
next
end
config redistribute "static"
set status enable
end
end
Note the FortiGate neighbours: prefix-list-in "default_route" and
prefix-list-out "cgn_public_prefixes" — BGP takes in a default and puts out
only the pools, exactly the division above. Keeping the IGP to infra-only means a subnet
or pool change never perturbs OSPF, and troubleshooting is a two-layer problem instead
of one tangled mess.
7 — Logging & traceability
For a CGN this is not optional: when abuse or a lawful request arrives, you must be
able to answer "who held this public IP:port at this timestamp". Both stream
port-block allocation events to a collector. This is also where
regulatory data-retention and lawful-intercept obligations get fed —
how you retain, secure and serve those records is a subject in its own right, and one
we'll cover in a separate deep dive rather than here.
nat instance CGN id 1
nat log host 192.0.2.20 5514 source 203.0.113.4 5000
nat log user enable
config log syslogd3 setting
set status enable
set server "192.0.2.20"
set port 5514
set format csv
end
config log syslogd3 filter
config free-style
edit 100
set category event
set filter "logid 0100022015 0100022016" ! CGN session start/close
next
end
end
Operating & verifying
The commands you live in day to day. Watch two numbers above all: per-pool port-block utilisation (a full pool means new subscribers can't get out), and per-subscriber session count against the limits (a spike is usually a compromised host, not growth).
display service-location 7
display nat instance CGN
display nat address-usage instance CGN address-group CGN-GPON-FIBER-ACCESS-01
display nat statistics session-usage
display nat session table slot 7
display service-ha global-information
address-usage needs both instance and
address-group; the session table also accepts nat-instance
CGN. Watch pool-usage % and session-usage for capacity.
diagnose firewall ippool-all list
diagnose firewall ippool-all stats CGN_pba_203.0.113.0
diagnose firewall shaper per-ip-shaper list
diagnose sys session stat
get router info bgp summary
get router info ospf neighbor
Subscriber awareness & visibility (a FortiGate edge)
Operationally the FortiGate brings a lot of nice-to-haves a pure router doesn't — the other half of the "firewall, not just a router" story. Two we lean on:
RSSO subscriber awareness. We mirror RADIUS accounting from the BNG to
the FortiGate (RADIUS Single Sign-On). The box then maps every session to a
subscriber identity — a username — not just an inside 100.64/x
address, so logs, troubleshooting and abuse response are per-subscriber. (The
allow_RADIUS_RSSO policy from earlier is what lets those accounting copies
land.)
config user radius
edit "RSSO-isp-radius"
set rsso enable
set rsso-validate-request-secret enable
set rsso-secret ENC <redacted>
set rsso-endpoint-attribute User-Name
set sso-attribute NAS-Identifier
set rsso-flush-ip-session enable
set rsso-ep-one-ip-only enable
next
end
Per-subscriber port-block visibility. Every port-block allocation is logged with the subscriber and protocol, and RSSO ties it to a username — so you can see exactly how many TCP and UDP blocks each subscriber is holding. That turns "the pool is filling up" into "these specific subscribers are near port exhaustion", and lets you flag the heavy hitters who'd be better off on a dedicated public IP (1:1) — a targeted fix that improves their experience without growing the whole pool.
display nat user-information), but tying it to subscriber identity
and this depth of per-user, per-protocol port-block tracking is where the FortiGate's
firewall heritage pays off operationally.Gotchas
- Size the log pipeline, not just the pool. Port-block logging is light per event but real volume at scale — make sure the collector and its path keep up, or you lose the traceability the whole CGN depends on.
- Watch port-block exhaustion. Monitor pool utilisation and tune
block size / blocks-per-user (and Huawei's extended-port ratio) to your subscriber
base; on FortiGate
pba-timeoutgoverns how fast idle blocks are reclaimed. - Aging vs table pressure. Too-low UDP/TCP aging churns long-lived sessions (VPNs, IoT keep-alives); too-high fills the table with idle sessions.
- Endpoint-independent mapping is a trade. full-cone /
permit-any-hostfixes P2P and VoIP but is permissive — always pair it with the per-subscriber session limits. - Policy order (FortiGate). Keep no-CGN passthrough policies above
the catch-all CGN rule; on Huawei use ACL
precedenceand selective binding. - FortiOS version (FortiGate). Target the 7.4 train from v7.4.8 (build 2795) onwards — earlier 7.4 builds were unstable with OSPF and BGP in our production experience. On a routing-critical CGN edge, the code train matters as much as the config.
Licensing & scale ceilings
For "cost-effective", licensing is half the story — and it's where the two diverge sharply.
NAT is gated by licence, per service board:
- Throughput — bought in Gbps tiers, up to ~90 Gbps per board.
- Session scale — bought in millions of concurrent sessions, up to ~32 million.
license
active nat bandwidth-enhance 60 slot 7 ! Gbps throughput tier
active nat session-table size 3 slot 7 ! session-scale tier
active nat bandwidth-enhance 60 slot 8 ! license the standby board
active nat session-table size 3 slot 8 ! identically
You size — and pay for — the tier you need. Because licensing is per board, the standby VSUP must be licensed identically (or it fails over cold) — so a 1+1 board pair doubles the CGN licence cost.
The NP7 does NAT at hardware line rate with no per-Gbps or per-session unlock — you get the box's full capacity out of the box. There's simply no throughput licence to buy.
The only CGN-related licence sits on the larger models with hardware CGN (hyperscale), which need a hyperscale firewall licence to enable that mode. The 1000F itself needs none for the deployment shown here.
Practically: the FortiGate's ceiling is its hardware; the Huawei's ceiling is whatever tier you've licensed, up to the board maximum. For a growing ISP that's the difference between "upgrade the box" and "buy a bigger licence".
Which platform — economics, not scale
Raw scale rarely decides it: both the FortiGate 1000F and the NE8000 + VSUP handle millions of concurrent sessions. The real choice is about cost shape, operations, and taste. Weigh:
- Do you want a firewall too? The FortiGate is a firewall doing CGN — stateful security, IPS, DPI-based traffic shaping, RSSO subscriber awareness and per-subscriber port-block visibility, all in one box. The Huawei is a pure router; you'd add separate kit for those. If the nice-to-haves matter, that alone can decide it.
- Regulatory & vendor policy. Hardware price isn't the split — Huawei is often aggressively priced. What can decide it is policy: a growing number of countries restrict or forbid Huawei in ISP core networks, which can take it off the table regardless of technical merit.
- Licensing vs support cost. The 1000F has no throughput or session licence (only a hyperscale licence on the larger hardware-CGN models). Huawei licenses per board on throughput and sessions — so a 1+1 board pair doubles the CGN licence spend. Weigh both against each vendor's support-contract pricing.
- Know-how. Running a platform your team already knows — one vendor, one toolchain — often beats any spec-sheet edge. A second platform is a second set of skills to hire for and keep current.
- Existing estate & taste. Already run a Huawei carrier core (where permitted)? The NE8000 slots into your operational model. Beyond that, some of it is simply which platform you'd rather live with.
Either way the fundamentals are identical: distinct public subnet per node, source-hash ECMP, port-block allocation, per-subscriber limits, OSPF-for-infra / BGP-for-pools, and airtight logging. Get those right and the box underneath is a preference and economics call — not a correctness one.
Designing or scaling CGN for your ISP and need help? Get in touch.