PCC load-balanced setup with Netwatch monitoring

Part 1: Netwatch Monitoring

πŸ”§ Objective

To monitor the health of 3 WAN interfaces (ISP1, ISP2, ISP3) using Netwatch, dynamically managing routing rules and default routes based on link status. This ensures that the PCC load balancing setup only uses active upstreams.


🧱 Step 1: Rename Interfaces

/interface ethernet
set [ find default-name=ether1 ] name=ether1::ISP1
set [ find default-name=ether2 ] name=ether2::ISP2
set [ find default-name=ether3 ] name=ether3::ISP3
set [ find default-name=ether4 ] name=ether4::MGMT
set [ find default-name=ether5 ] name=ether5::LAN

🌐 Step 2: Configure PPPoE on ISP3

/interface pppoe-client
add disabled=no interface=ether3::ISP3 name=pppoe-out1 user=ppp1

🧭 Step 3: Create Routing Tables

These are used for PCC and route isolation.

/routing table
add name=isp1
add name=isp2
add name=isp3

πŸ“‘ Step 4: DHCP Clients with Scripts for ISP1 & ISP2

This handles setting the gateway and Netwatch src-address dynamically.

ISP1:

/ip dhcp-client
add interface=ether1::ISP1 default-route-tables=isp1 script="
  if ((\$\"bound\")=1) do={
    :local gwip (\$\"gateway-address\")
    :local ip (\$\"lease-address\")
    ip route set gateway=\$gwip [find comment=\"isp1\"]
    tool netwatch set src-address=\$ip [find name=\"isp1_check\"]
    routing rule set src-address=\$ip [find table=isp1]
  }"
if (($"bound")=1) do={

:local gwip ($"gateway-address")
:local ip ($"lease-address")

ip route set gateway=$gwip [find comment="isp1"]
tool netwatch set src-address=$ip [find name="isp1_check"]
routing rule set src-address=$ip [find table=isp1]
}

ISP2:

add interface=ether2::ISP2 default-route-tables=isp2 script="
  if ((\$\"bound\")=1) do={
    :local gwip (\$\"gateway-address\")
    :local ip (\$\"lease-address\")
    ip route set gateway=\$gwip [find comment=\"isp2\"]
    tool netwatch set src-address=\$ip [find name=\"isp2_check\"]
    routing rule set src-address=\$ip [find table=isp2]
  }"
if (($"bound")=1) do={

:local gwip ($"gateway-address")
:local ip ($"lease-address")

ip route set gateway=$gwip [find comment="isp2"]
tool netwatch set src-address=$ip [find name="isp2_check"]
routing rule set src-address=$ip [find table=isp2]
}

πŸ“Ά Step 5: PPPoE Dynamic IP Update Script (ISP3)

Add a script and scheduler to update Netwatch when the PPPoE IP changes:

Script:

/system script
add name=pppoe-check source="
  interface/pppoe-client/monitor pppoe-out1 once do={
    :local ip (\$\"local-address\")
    :local current [/tool netwatch get value-name=src-address isp3_check]
    :if (\$current != \$ip) do={
      /tool netwatch set src-address=\$ip [find name=\"isp3_check\"]
      /routing/rule set src-address=\$ip [find table=isp3]
    }
  }"
interface/pppoe-client/monitor pppoe-out1 once do={

 :local ip ($"local-address")
 :local current [/tool netwatch get value-name=src-address isp3_check]

 :if ($current != $ip) do={

  /tool netwatch set src-address=$ip [find name="isp3_check"]
  /routing/rule set src-address=$ip [find table=isp3]

 }
 
}

Scheduler (runs every 10s):

/system scheduler
add interval=10s name=pppoe-check on-event=pppoe-check

πŸ›£οΈ Step 6: Add Static Routes for Monitoring

Routes to 8.8.8.8 in each routing table to allow Netwatch testing.

/ip route
add dst-address=8.8.8.8/32 gateway=10.200.22.1 routing-table=isp1 comment=isp1
add dst-address=8.8.8.8/32 gateway=10.200.23.1 routing-table=isp2 comment=isp2
add dst-address=8.8.8.8/32 gateway=pppoe-out1 routing-table=isp3 comment=isp3

Also a default route for ISP3:

add dst-address=0.0.0.0/0 gateway=pppoe-out1 routing-table=isp3 comment=isp3-gw

🚦 Step 7: Netwatch Configuration

ISP1:

/tool netwatch
add name=isp1_check host=8.8.8.8 src-address=10.200.22.196 interval=3s \
    type=icmp ignore-initial-up=yes ignore-initial-down=yes \
    down-script="ip dhcp-client set add-default-route=no [ find interface=ether1::ISP1 ]" \
    up-script="ip dhcp-client set add-default-route=yes [ find interface=ether1::ISP1 ]"
UP Script
ip dhcp-client set add-default-route=yes [ find interface=ether1::ISP1 ]

DOWN Script
ip dhcp-client set add-default-route=no [ find interface=ether1::ISP1 ]

ISP2:

add name=isp2_check host=8.8.8.8 src-address=10.200.23.197 interval=3s \
    type=icmp ignore-initial-up=yes ignore-initial-down=yes \
    down-script="ip dhcp-client set add-default-route=no [ find interface=ether2::ISP2 ]" \
    up-script="ip dhcp-client set add-default-route=yes [ find interface=ether2::ISP2 ]"
UP Script
ip dhcp-client set add-default-route=yes [ find interface=ether2::ISP2 ]

DOWN Script
ip dhcp-client set add-default-route=no [ find interface=ether2::ISP2 ]

ISP3:

add name=isp3_check host=8.8.8.8 src-address=203.0.113.2 interval=3s \
    type=icmp ignore-initial-up=yes ignore-initial-down=yes \
    down-script="ip route disable [find comment=\"isp3-gw\"]" \
    up-script="ip route enable [find comment=\"isp3-gw\"]"
UP Script
ip route enable [find comment="isp3-gw"]

DOWN Script
ip route disable [find comment="isp3-gw"]

πŸ“œ Step 8: Add Routing Rules for PCC/Source IPs

Ensure traffic uses correct routing tables.

/routing rule
add src-address=10.200.22.196 dst-address=8.8.8.8/32 table=isp1
add src-address=10.200.23.197 dst-address=8.8.8.8/32 table=isp2
add src-address=203.0.113.2 dst-address=8.8.8.8/32 table=isp3

βœ… Final Notes

  • Netwatch checks are ICMP-based and monitor 8.8.8.8 from each WAN’s public IP.
  • Down events prevent routes or disable DHCP default-route behavior to avoid blackholing traffic.
  • Each interface’s IP is dynamically updated for Netwatch via DHCP scripts or PPPoE script.

Part 2: PCC (Per Connection Classifier) Load Balancing

Here is a complete guide to adding PCC (Per Connection Classifier) load balancing on MikroTik using your existing WAN failover and Netwatch setup. The guide includes:

  1. PCC setup steps
  2. Line-by-line explanation of each mangle rule
  3. Why each rule matters

🎯 Objective

Use MikroTik’s PCC (Per Connection Classifier) to distribute LAN traffic across three ISPs (ISP1, ISP2, ISP3) based on source and destination addresses and ports, while preserving session consistency and allowing automatic failover via Netwatch.


πŸ”§ Prerequisites

  • 3 WAN links (ether1::ISP1, ether2::ISP2, pppoe-out1 for ISP3)
  • Routing tables already set: isp1, isp2, isp3
  • Netwatch configured to detect WAN health
  • Default routes present in each table

🧩 Step-by-Step PCC Setup with Explanation

πŸ“Œ 1. Mark New Incoming Connections from Each ISP

/ip firewall mangle
add chain=prerouting action=mark-connection in-interface=ether1::ISP1 connection-mark=no-mark connection-state=new new-connection-mark=isp1_conn passthrough=yes

βœ… What it does:
When new connections arrive from ISP1, they’re tagged with isp1_conn so they can be tracked and routed properly.

(Same for ISP2 and ISP3 below.)

add chain=prerouting action=mark-connection in-interface=ether2::ISP2 connection-mark=no-mark connection-state=new new-connection-mark=isp2_conn passthrough=yes

add chain=prerouting action=mark-connection in-interface=pppoe-out1 connection-mark=no-mark connection-state=new new-connection-mark=isp3_conn passthrough=yes

πŸ“Œ 2. Mark Routing for Outbound Traffic Originating from the Router (Output Chain)

add chain=output action=mark-routing connection-mark=isp1_conn new-routing-mark=isp1 passthrough=yes

βœ… What it does:
When the router itself (e.g., for DNS, NTP) initiates traffic that matches an existing marked connection (isp1_conn), route it via the isp1 table.

(Same logic for ISP2 and ISP3.)

add chain=output action=mark-routing connection-mark=isp2_conn new-routing-mark=isp2 passthrough=yes

add chain=output action=mark-routing connection-mark=isp3_conn new-routing-mark=isp3 passthrough=yes

πŸ“Œ 3. Apply PCC to LAN β†’ Internet Traffic

These are the core PCC load balancing rules.

add chain=prerouting action=mark-connection in-interface=LAN connection-mark=no-mark connection-state=new dst-address-type=!local new-connection-mark=isp1_conn per-connection-classifier=both-addresses-and-ports:3/0 passthrough=yes

βœ… What it does:
For new LAN-to-Internet traffic that’s not going to the router itself (!local), this classifies 1 in every 3 connections to go through ISP1.
The both-addresses-and-ports method ensures session stickiness (for web browsing, downloads, etc.).

(Same logic for the other two classifiers.)

add chain=prerouting action=mark-connection in-interface=LAN connection-mark=no-mark connection-state=new dst-address-type=!local new-connection-mark=isp2_conn per-connection-classifier=both-addresses-and-ports:3/1 passthrough=yes

add chain=prerouting action=mark-connection in-interface=LAN connection-mark=no-mark connection-state=new dst-address-type=!local new-connection-mark=isp3_conn per-connection-classifier=both-addresses-and-ports:3/2 passthrough=yes

🧠 Why 3/x?
This evenly splits traffic 3 ways:

  • 3/0 β†’ ISP1
  • 3/1 β†’ ISP2
  • 3/2 β†’ ISP3

You can adjust these ratios depending on bandwidth or priorities.


πŸ“Œ 4. Apply Routing Marks to Traffic from LAN Based on Connection Mark

add chain=prerouting action=mark-routing in-interface=LAN connection-mark=isp1_conn new-routing-mark=isp1 passthrough=yes

βœ… What it does:
This ensures that LAN traffic previously classified to go out via ISP1 is forced to use the isp1 routing table (which points to ISP1’s gateway).

(Same for the others.)

add chain=prerouting action=mark-routing in-interface=LAN connection-mark=isp2_conn new-routing-mark=isp2 passthrough=yes

add chain=prerouting action=mark-routing in-interface=LAN connection-mark=isp3_conn new-routing-mark=isp3 passthrough=yes

βœ… Summary of PCC Rule Roles

Rule PurposeChainDescription
Mark incoming connections from WANspreroutingSo return traffic uses correct interface
Mark routing for router-originated trafficoutputEnsures correct WAN is used for DNS, etc.
Classify and mark new LAN connectionsprerouting + PCCEvenly split LAN traffic over 3 WANs
Mark routing for LAN trafficpreroutingDirect LAN connections to appropriate WAN based on PCC mark

Full Mangle Rules

/ip firewall mangle
add action=mark-connection chain=prerouting connection-mark=no-mark connection-state=new in-interface=ether1::ISP1 new-connection-mark=isp1_conn passthrough=yes
add action=mark-connection chain=prerouting connection-mark=no-mark connection-state=new in-interface=ether2::ISP2 new-connection-mark=isp2_conn passthrough=yes
add action=mark-connection chain=prerouting connection-mark=no-mark connection-state=new in-interface=pppoe-out1 new-connection-mark=isp3_conn passthrough=yes

add action=mark-routing chain=output connection-mark=isp1_conn new-routing-mark=isp1 passthrough=yes
add action=mark-routing chain=output connection-mark=isp2_conn new-routing-mark=isp2 passthrough=yes
add action=mark-routing chain=output connection-mark=isp3_conn new-routing-mark=isp3 passthrough=yes

add action=mark-connection chain=prerouting connection-mark=no-mark connection-state=new dst-address-type=!local in-interface=LAN new-connection-mark=isp1_conn passthrough=yes per-connection-classifier=both-addresses-and-ports:3/0
add action=mark-connection chain=prerouting connection-mark=no-mark connection-state=new dst-address-type=!local in-interface=LAN new-connection-mark=isp2_conn passthrough=yes per-connection-classifier=both-addresses-and-ports:3/1
add action=mark-connection chain=prerouting connection-mark=no-mark connection-state=new dst-address-type=!local in-interface=LAN new-connection-mark=isp3_conn passthrough=yes per-connection-classifier=both-addresses-and-ports:3/2


add action=mark-routing chain=prerouting connection-mark=isp1_conn in-interface=LAN new-routing-mark=isp1 passthrough=yes
add action=mark-routing chain=prerouting connection-mark=isp2_conn in-interface=LAN new-routing-mark=isp2 passthrough=yes
add action=mark-routing chain=prerouting connection-mark=isp3_conn in-interface=LAN new-routing-mark=isp3 passthrough=yes

⚠️ Tips & Best Practices

  • Connection tracking must be enabled (/ip firewall connection tracking) β€” it is by default.
  • Always monitor performance and tune the classifier method if you have session-sensitive apps.
  • You may wish to include failover logic with Netwatch to disable classifiers or NAT rules dynamically (advanced).

Leave a Comment

Your email address will not be published. Required fields are marked *