Junos Static + Source NAT for subnet conflicts

I recently implemented a new network setup for vendor-managed cloud-hosted VoIP, and the vendor’s on-prem subnet conflicted with an existing on-prem subnet. I didn’t find any existing articles for the exact solution I used, so I wanted to post a quick writeup.

The phones are on-prem, there’s a dedicated circuit from the VoIP provider, and there’s a CPE router on-prem. This router hands out DHCP addresses to phones in 172.16.1.0/24 and passes voice traffic through the dedicated circuit back to the provider’s infrastructure. We have a preexisting on-prem network using the same subnet. This isn’t an issue for the VoIP setup, since the VoIP subnet’s L3 is handled by the CPE while the existing subnet’s L3 is handled by our enterprise firewalls (SRX), but it means we can’t easily monitor the CPE or devices on the VoIP subnet from devices behind the main firewalls.

The setup I built is as follows:

  1. New interface on the SRXes in the VoIP subnet (at 172.16.1.253), and new routing-instance for this interface
  2. Static/discard route for a ‘synthetic’ translation subnet (10.10.123.0/24) in the VoIP routing-instance
  3. Static NAT traffic from the default RI from translation subnet 10.10.123.0/24 to the VoIP subnet 172.16.1.0/24
  4. Source NAT traffic from the main firewalls to the VoIP subnet through the firewall’s interface (172.16.1.253)
  5. Security policies permitting traffic to the VoIP subnet

Step-by-step with config:

1, 2: Interface/routing-instance setup

We have zero control over the CPE, so there’s no way to do a routed interface or anything like that. We need to put our firewall directly inside the VoIP subnet. We’ll pick an address that is (hopefully) outside of the DHCP scope on the CPE. We need to put the interface in a non-default routing-instance because of the root cause of this setup: the conflict with an existing subnet in the default RI. We’ll include a discard static route in the new RI so that we can leak that route to the main RI with a policy-statement

interfaces {
    reth0 {
        unit 123 {
            description "VoIP";
            vlan-id 123;
            family inet {
                address 172.16.1.253/24;
            }
        }
    }
}

routing-instances {
    voip {
        instance-type virtual-router;
        routing-options {
            static {
                route 10.10.123.0/24 discard;
            }
        }
        interface reth0.123;
    }
}

security {
    zones {
        security-zone voip {
            host-inbound-traffic {
                system-services {
                    ping;
                }
            }
            interfaces {
                reth0.123;
            }
        }
    }
}

3: Static NAT

Now we need to create translation between our translation subnet (10.10.123.0/24) to the VoIP subnet (172.16.1.0/24). We want the SRX to map all addresses in these subnets to each other 1:1:

  • 10.10.123.1 -> 172.16.1.1
  • 10.10.123.50 -> 172.16.1.50
  • 10.10.123.222 -> 172.16.1.222
  • etc

We do this with static NAT:

security {
    nat {
        static {
            rule-set voip-static-nat {
                from zone [ corporate-endpoints corporate-servers ];
                rule voip-static {
                    match {
                        destination-address 10.10.123.0/24;
                    }
                    then {
                        static-nat {
                            prefix {
                                172.16.1.0/24;
                                routing-instance voip;
                            }
                        }
                    }
                }
            }
        }
    }
}

4: Source NAT

We’re still not done, though – the CPE (which we can’t configure) has a single default route: all traffic to external subnets gets routed out the VoIP circuit. Right now traffic from our firewall will have its true source IP (e.g. 10.10.50.100), which the CPE will route out its WAN interface to be dropped by the VoIP network. We have no way to install routes on the CPE like we’d normally do.

To deal with this, we’ll source NAT all traffic through our SRX’s interface on the VoIP subnet (172.16.1.253) – this way the CPE and all devices on the VoIP subnet will be responding within the same subnet, even though the source of the traffic is any arbitrary network behind the SRX.

security {
    nat {
        source {
            rule-set voip-source-nat {
                from zone [ corporate-endpoints corporate-servers ];
                to zone voip;
                rule voip-source {
                    match {
                        source-address-name [ Net-Corp-Servers Net-Corp-IT ];
                        destination-address-name Net-VoIP;
                    }
                    then {
                        source-nat {
                            interface;
                        }
                    }
                }
            }
        }
    }
}

5: Route leak

Now we just need to get the route for the translation subnet (10.10.123.0/24) into the default routing-instance. Lots of ways to do this, in this case we use an instance-import policy-statement in the default RI:

routing-options {
    instance-import import-to-inet;
}

policy-options {
    policy-statement import-to-inet {
        term voip-translate {
            from {
                instance voip;
                protocol static;
                route-filter 10.10.123.0/24 exact;
            }
            then accept;
        }
        term reject {
            then reject;
        }
    }
}

6: Security policies

No need to re-explain SRX security policy syntax. The only major concept to note is that security policy is evaluated after static NAT [see diagram], so your policies will need to refer to the true destination address, which in this case is the real VoIP subnet of 172.16.1.0/24. The security policies do not reference the translation subnet.

Done – now we can put the CPE’s gateway address into our NMS, ping phones from network team laptops, etc.