Writing a PAC File

This section describes how to write a new PAC file from scratch. Zscaler highly recommends that you copy and paste its default PAC file and customize it as necessary. Build your PAC file one element at a time. Save the file and test it after each addition.

Before you begin, ensure that you review Best Practices for Writing PAC Files.

FindProxyForURL Function

A PAC file must start with the opening function FindProxyForURL(url,host) on the first line. This function identifies which URLs, host names or IP addresses are redirected to a proxy. The URL variable must contain the full URL of a destination (for example, http://www.zscaler.com), and the host variable must contain a domain name or its IP address (for example, zscaler.com or 72.249.144.174). Generally speaking, the host variable is preferable to the URL variable. If no URL or host is specified, all web requests will use the return argument.

Return Statements

Enter a return statement in curly brackets after each argument. The return statement directs requests to a proxy or to the specified destination. For example:

functionFindProxyForURL(url, host)
{ return "PROXY ${GATEWAY}:9400"; return "DIRECT"; }

A return statement accepts one of two values:

  • DIRECT tells the browser to bypass the proxy and go directly to the destination server.

    or
  • PROXY tells the browser to send the request to a proxy.

The DIRECT or PROXY values must be in uppercase characters and enclosed in quotation marks. Add a semicolon immediately after the closing quotation marks. For example:

return "DIRECT";
return "PROXY ${GATEWAY}:9400";

Each PROXY statement must specify the fully qualified host name or the IP address of the proxy and the port. IP addresses are generally discouraged because they can change at any time. Zscaler recommends that you use the variables ${GATEWAY} and ${SECONDARY_GATEWAY }instead. (If your organization uses a subcloud, use the variables ${GATEWAY.organization_name.zscaler.net} and ${SECONDARY_GATEWAY.organization_name.zscaler.net}.)

The service uses its geo-location technology to automatically find the ZEN closest to you and with the quickest response time. Naming a primary and secondary gateway provides failover in case one of the ZENs is unavailable for any reason.

ZENs accept web requests on ports 80, 443, 9400, 9480 and 9443.

  • Port 80 is the standard port used by almost all web servers.
  • Port 443 is the standard port used for encrypted (HTTPS) traffic. Port 9400 can be used instead, if another host between the end user and ZEN attempts to redirect the user’s traffic before it can reach the ZEN.
  • Port 9443 can be used for road warriors who need the service to proxy and inspect HTTPS transactions.

Write the opening function FindProxyForURL (url, host) { } followed by a return statement on the next line. For example:

functionFindProxyForURL(url, host)
{ return "PROXY ${GATEWAY}:9400"; return "DIRECT"; }

Save the text file and test it. Verify that the PAC file sends your browser to the Zscaler service with no other arguments, ‘if’ statements, or other elements. Confirm by navigating to ip.zscaler.com, which indicates if you reached the web server directly or through a ZEN.

Adding Arguments

You can add various arguments that identify which internal or external hosts must be proxied. Add one nested argument at a time, and then test and confirm that the PAC file works properly after each addition.

Following is a sample PAC file that contains arguments, each of which is preceded by a comment.

functionFindProxyForURL(url, host)
{
//
//Exclude FTP from proxy
//
if (url.substring(0, 4) == "ftp:")
{
return "DIRECT";
}
//
//Bypass proxy for internal hosts
//
if (isInNet(host, "0.0.0.0", "255.0.0.0")||
isInNet(host, "10.0.0.0", "255.0.0.0") ||
isInNet(host, "127.0.0.0", "255.0.0.0") ||
isInNet(host, "169.254.0.0", "255.255.0.0") ||
isInNet(host, "172.16.0.0", "255.240.0.0") ||
isInNet(host, "192.0.2.0", "255.255.255.0")||
isInNet(host, "64.206.157.136", "255.255.255.255"))
{
return "DIRECT";
}
//
//Bypass proxy for this server
//
if (isInNet(host.mail.domain.com)
{
return “DIRECT”;
}
return "PROXY ${GATEWAY}:9400; ${SECONDARY_GATEWAY}:9400; DIRECT";
}

For the arguments in the sample PAC file above, specify the following:

Exclude FTP traffic.

The following lines exclude FTP traffic from being redirected to the proxy (since the service does not support native FTP):

   //
   //Exclude FTP from proxy
   //
if (url.substring(0, 4) == "ftp:") 
    {
     return "DIRECT";
    }

The argument begins with a simple if statement using the built-in function called url.substring. If arguments are always followed by opening and closing parentheses—they describe the conditions for which the argument must be evaluated and for which a result is required. In the example, the argument specifies that if the URL contains the string ftp: in the first four characters of the URL (from 0 to 4), return DIRECT—bypass the proxy named in the line below. Note that the if argument’s result must be enclosed within its own set of open and closed curly brackets.

Save this change to the PAC file and upload it to the service portal. Reload the PAC file in your browser and navigate to an FTP download site such as ftp://ftp.hp.com/. After loading this page, log in to the Analytics portal to determine if this transaction was logged. The transaction should not appear in the logs.

Exclude requests for internal hosts.

The following lines in the PAC file example exclude requests for internal hosts from being redirected to a proxy.

//
 //Do not send traffic to the following network to Zscaler
 //
if (isInNet(host), "192.168.0.0", "255.255.0.0")
{
 return "DIRECT";
}

This argument uses the JavaScript function IsInNet(), which is typically used to identify either of the following:

  • Client IP address (If the request comes from this IP address, use this proxy.) Be aware that this argument returns the first IP address on your device, based on its OS. The first IP address, shown when you use the ipconfig command, may be the IPv6 address of the device or the IP address of virtual adapters and this can cause conflicts.
  • Host server IP address (If the request is going to this address, use this proxy.) Be aware that this argument results in a DNS lookup. It can impact performance if the DNS server is not available. Instead, you can use the following to constrain the IsInNet() function based on the host domains being accessed:
If dnsDomainIs(host,”internal.net”) {
If (isInNet(host,”10.0.0.0”,”255.0.0.0”)
Return “DIRECT”;
}

Save the PAC file and test again. Browse to an internal host and ensure that you can reach it. (If you were proxied through a ZEN, your request would be denied. The request first goes outside your network to the Zscaler proxy but is then blocked as it tries to access an internal host as it comes back in from outside the network.)

Exclude internal networks.

The following lines in the PAC file example exclude requests for multiple internal hosts from being redirected to a proxy.

  //
  //Bypass proxy for internal hosts
  //
 if(isInNet(host, "0.0.0.0", "255.0.0.0")||
 isInNet(host, "10.0.0.0", "255.0.0.0") ||
 isInNet(host, "127.0.0.0", "255.0.0.0") ||
 isInNet(host, "169.254.0.0", "255.255.0.0") ||
 isInNet(host, "172.16.0.0", "255.240.0.0") ||
 isInNet(host, "192.0.2.0", "255.255.255.0")||
 isInNet(host, "64.206.157.136", "255.255.255.255"))
 {
  return "DIRECT";
 }

 As shown in the example, before closing the IF argument with a final parenthesis, the Boolean “or” operator (||) is used to concatenate the internal networks. The last isInNet statement does not require the or (||) operator. Instead, it uses an additional parenthesis to close the opening parenthesis.

The last statement is a specific IP address, not a network. To bypass specific hosts, write the exact IP address with a subnet mask of 255.255.255.255.

Use of isInNet() is extremely effective when the host you are trying to reach is an actual IP address. If you try to reach a host by domain name, such as https://zscaler.com your browser must perform a DNS lookup for it. If the host name is not resolvable, the client needs to wait for DNS to time out before moving on. To avoid this and to avoid placing an undue strain on the name server, insert a variable that uses a regular expression immediately above the isInNet() argument to restrict the result just to IP addresses.

Therefore, instead of writing:

if (isInNet(host, "0.0.0.0", "255.0.0.0")||
isInNet(host, "10.0.0.0", "255.0.0.0") ||
isInNet(host, "127.0.0.0", "255.0.0.0") ||
...)
{
  return "DIRECT";
}
Write this:
reip = /^\d+\.\d+\.\d+\.\d+$/g;
if (reip.test(host))
{
 if (isInNet(host, "0.0.0.0", "255.0.0.0")||
 isInNet(host, "10.0.0.0", "255.0.0.0") ||
 isInNet(host, "127.0.0.0", "255.0.0.0") ||
    ...)
 {
  return "DIRECT";
 }      
}

Here, the variable reip = /^\d+\.\d+\.\d+\.\d+$/g; is used in the argument if(reip.test(host)) {, and the result is only used if the host is an IP address in one of the  networks specified in the nested if argument. Note that this can only be used for URLs that include IP addresses, such as http://192.0.2.3/example.

In some versions of IE, use of the preceding variable may not work. An alternative to this is to use the JavaScript shell expression match function:

if (shExpMatch(host, "/^\d+\.\d+\.\d+\.\d+$/g")) 
{
 if (isInNet(host, "10.0.0.0", "255.0.0.0") ||
 isInNet(host, "192.168.0.0", "255.255.0.0")) 
 {
  return "DIRECT";
 }      
}

Save this change, reload the PAC file in your browser, and then try browsing to an internal web server in the internal network. If you can reach the server, you have bypassed the Zscaler ZEN.

Exclude Servers

The following lines in the PAC file example exclude specific servers, such as mail.domain.com, from being redirected to a proxy. In the example, a separate if isInNet() argument lists internal host names.

    //
    //Bypass proxy for this server
    //
  if (isInNet(host mail.domain.com) 
  { 
    return “DIRECT”;
  }
//To bypass all hosts in a domain, use
dnsDomainIs(host, “host.com”)
 
You can also use the following to bypass specific internal hosts:
var bypassHosts = /(remote\.mydomain\.com|mail\.mydomain\.com)/; 
if (bypassHosts.test(host)) 
{
 
  return "DIRECT";
}

 The preceding argument includes a variable that contains two hosts: remote.mydomain.com and mail.mydomain.com. Using the JavaScript test(host) function, any host you enter here will return DIRECT, and will not require a DNS lookup. var is the JavaScript function to set a variable. bypassHosts is a JavaScript function. You must use this specific name/function. Forward slashes mark the beginning and ending boundary of the variable. Open and close parentheses in the variable match the parentheses in the argument (test(host)). The periods in the host names must be “escaped” with a back slash. The variable itself requires a semicolon to close the variable argument.