All Categories Zscaler Internet Access Load Balancing for PAC Forwarded Traffic

Load Balancing for PAC Forwarded Traffic

Many organizations use only Zscaler Client Connector as the traffic forwarding method, which means that a lot of users from one physical location may connect from behind the same edge gateway, meaning they all appear to have the same egress IP address. With the normal load balancing calculation, this leads to all the users from the same location to connect to the same ZIA Public Service Edge in the data center. This can result in the overloading of a single Service Edge instance and can cause performance issues.

To resolve this, it is strongly recommended that you use the _FX suffix on any ${GATEWAY} or ${SECONDARY_GATEWAY} statements in your Client Connector PAC files. The _FX suffix provides automatic load balancing across multiple Virtual IPs depending on the HTTP headers (useragent, x-forwarded-for, and z-client). This variable is useful for Client Connector clients because the z-client ID is different for each user, meaning we can do a better job of distributing the traffic load.

Any forwarding statements in the App Profile PAC files that you apply to Client Connector users should include this suffix.

The PAC server uses the ${GATEWAY_FX} and ${GATEWAY_Fn} variables, (where n can take up to eight values from 0-7 ) in the Zscaler hosted PAC file to assign the VIPs for the incoming traffic:

Load balancing for PAC forwarded traffic can be classified as:

  1. Load Balancing for Zscaler Client Connector traffic

    For Zscaler Client Connector users, the PAC server allocates VIPs for devices through the ${GATEWAY_FX} variable in the Zscaler hosted PAC file. When Zscaler Client Connector connects to the PAC server to download a PAC file, it sends a unique device identifier with the request.

    The PAC server allocates one of the VIPs to the device based on the device identifier received from the HTTP request using the ${GATEWAY_FX} variable. When the same device connects again to download the PAC file, the PAC server allocates the same VIP that was allocated to the device earlier based on its unique identifier.

    Sample of how to write the PAC file for load balancing ZCC Traffic:

    function FindProxyForURL(url, host) {
    
        /* Default Traffic Forwarding - Goes to Zscaler */
        return "PROXY ${GATEWAY_FX}:80; ${SECONDARY_GATEWAY_FX}:80; DIRECT";
    }

  2. Load Balancing for non-Zscaler Client Connector traffic

    Each data center can have up to eight unique VIPs assigned to it. For non-Zscaler Client Connector users, admins can leverage the ${GATEWAY_Fn} variable in the Zscaler hosted PAC file to evenly distribute the clients across multiple VIPs.

    ${GATEWAY_Fn} represents eight different variables starting from ${GATEWAY_F0} to ${GATEWAY_F7} that are used to pick an available VIP from the data center. The PAC server returns the VIP value based on the variable used in the PAC file. If the number of VIPs assigned to a data center is less than eight, then the PAC server allocates the available VIPs to all the eight variables in a round-robin fashion.

    The following image illustrates how the VIPs are mapped to the variables based on the number of available VIPs in a data center:

    image.png

    Zscaler recommends the following PAC file format for non-Zscaler Client Connector users to leverage the multiple VIP feature using a single PAC file.

    Recommended PAC File Format for Non-Zscaler Client Connector Users:

    splitPublicTraffic() splits the client private IP address into its corresponding octets and performs last octet%8 to get the index between 0 to 7.  This function file uses multiple PAC functions to evenly distribute client traffic over the eight PAC variables.  Initially, the PAC file uses myIpAddress() function to determine the client source IP address. Then, it extracts the last octet of the client IP. This number is divided by 8 and the remainder is used to assign one of the eight gateway variables.

    function splitPublicTraffic() {
        var myip = myIpAddress();        /* get the client’s Private IP address */
        var ipbytes = myip.split(".");    /* split the IP into its octets and store in an array */
        var lastoctet = parseInt(ipbytes[3], 10);
    
        switch (lastoctet % 8) {        /* last octet % 8 returns the index to use */
        case 0:
            return "PROXY ${GATEWAY_F0}:80; PROXY ${SECONDARY_GATEWAY_F0}:80; DIRECT";
        case 1:
            return "PROXY ${GATEWAY_F1}:80; PROXY ${SECONDARY_GATEWAY_F1}:80; DIRECT";
        case 2:
            return "PROXY ${GATEWAY_F2}:80; PROXY ${SECONDARY_GATEWAY_F2}:80; DIRECT";
        case 3:
            return "PROXY ${GATEWAY_F3}:80; PROXY ${SECONDARY_GATEWAY_F3}:80; DIRECT";
        case 4:
            return "PROXY ${GATEWAY_F4}:80; PROXY ${SECONDARY_GATEWAY_F4}:80; DIRECT";
        case 5:
            return "PROXY ${GATEWAY_F5}:80; PROXY ${SECONDARY_GATEWAY_F5}:80; DIRECT";
        case 6:
            return "PROXY ${GATEWAY_F6}:80; PROXY ${SECONDARY_GATEWAY_F6}:80; DIRECT";
        case 7:
            return "PROXY ${GATEWAY_F7}:80; PROXY ${SECONDARY_GATEWAY_F7}:80; DIRECT";
        }
    }
    
    
    function FindProxyForURL(url, host) {
                var privateIP = /^(0|10|127|192\.168|172\.1[6789]|172\.2[0-9]|172\.3[01]|169\.254|192\.88\.99)\.[0-9.]+$/;
                var resolved_ip = dnsResolve(host);
    
                /* Don't send non-FQDN or private IP auths to us */
                if (isPlainHostName(host) || isInNet(resolved_ip, "192.0.2.0","255.255.255.0") || privateIP.test(resolved_ip))
                    return "DIRECT";
    
                /* FTP goes directly */
                if (url.substring(0,4) == "ftp:")
                    return "DIRECT";
                
        /* test with ZPA*/
        if (isInNet(resolved_ip, "100.64.0.0","255.255.0.0"))
            return "DIRECT";
                
                /* Updates are directly accessible */
                if (((localHostOrDomainIs(host, "trust.zscaler.com")) ||
                        (localHostOrDomainIs(host, "trust.zscaler.net")) ||
                        (localHostOrDomainIs(host, "trust.zscalerone.net")) ||
                        (localHostOrDomainIs(host, "trust.zscalertwo.net")) ||
                        (localHostOrDomainIs(host, "trust.zscalerthree.net")) ||
                        (localHostOrDomainIs(host, "trust.zscalergov.net")) ||
                        (localHostOrDomainIs(host, "trust.zsdemo.net")) ||
                        (localHostOrDomainIs(host, "trust.zscloud.net")) ) &&
                    (url.substring(0,5) == "http:" || url.substring(0,6) == "https:"))
                    return "DIRECT";
                
        /* use zscaler proxy */
    return splitPublicTraffic();