We just recently deployed four Blue Coat ProxySG 510 appliances along with two Blue Coat ProxyAV 810 appliances in our enterprise. The Blue Coat ProxySG appliance is essentially a proxy server that integrates with NTLM/AD authentication and content filtering. The Blue Coat ProxyAV appliance is essentially an Anti-Virus solution, you choose which AV engine to license from Kaspersky, Sophos, McAfee or Panda, for all Internet content.
We have two Internet links located at different geographically locations. We placed two Blue Coat ProxySG 510 appliances at one location along with a single Blue Coat ProxyAV 810 appliance. We setup each ProxySG with it’s own VIP and made each box a backup to the other. We duplicated that same configuration at the second geographic location with two Blue Coat ProxySG 510s and a single Blue Coat ProxyAV 810. The folks that are familiar with the Blue Coat product line might think that the 510 model is probably somewhat undersized for the size of our environment but our design was to use all four 510s in an active/active configuration as opposed to an active/standby configuration. In the event of a appliance failure we may have some high utilization but we won’t suffer an outage.
How did we achieve the load balancing and high availability?
It’s not really that difficult, although let’s not tell the folks writing the checks that. You’ll need to use a PAC file with some special JavaScript code to help provide the poor mans load balancing. The VIPs will provide the redundancy within each geographic site and we’ll trust the browser to provide the redundancy between the two geographic sites.
// // Blue Coat Proxy Auto-Configuration File (PAC) // // This PAC file will provide a poor mans load balancing solution by // hasing the IP address of the client to help spread the load of the // client across two different ProxySG appliances. The variables at // the top of the PAC file need to be adjusted to the specific VIP // addresses of each ProxySG appliance. // function FindProxyForURL(url, host) { // Declare Variables var OLDPROXY = "PROXY 10.1.20.100"; var HECTOR = "PROXY 10.1.127.60"; var FERGAL = "PROXY 10.1.127.65"; var MEG = "PROXY 10.2.127.60"; var SEAN = "PROXY 10.2.127.65"; var SITE1VIP1 = "PROXY 10.1.127.62"; var SITE1VIP2 = "PROXY 10.1.127.63"; var SITE2VIP1 = "PROXY 10.2.127.62"; var SITE2VIP2 = "PROXY 10.2.127.63"; var ipSubs = myIpAddress().split("."); var localIP = myIpAddress(); ret = URLhash(url); // Use DIRECT if the destination is just a plain WINS name if (isPlainHostName (host) || // Use DIRECT if the host is within the michaelfmcnamara.com domain space dnsDomainIs(host, ".michaelfmcnamara.com") || // Use DIRECT if the host is within the RFC1918 ten-dot-zero address space isInNet(host, "10.0.0.0", "255.255.0.0") || return "DIRECT"; // Use DIRECT if the URL starts with 10.* if (shExpMatch(url, "http://10.*") || shExpMatch(url, "https://10.*")) return "DIRECT"; // Use the Blue Coat Proxy for an remaining URL matching "http:" if( url.substring(0, 5) == "http:" ) { if (isInNet(myIpAddress(), "10.1.0.0", "255.255.0.0") { if ( (ipSubs[3] % 2) == 0 ) { return SITE1VIP1 + ":8080"; } else { return SITE1VIP2 + ":8080"; } } else { if ( (ipSubs[3] % 2) == 0 ) { return SITE2VIP1 + ":8080"; } else { return SITE2VIP2 + ":8080"; } } } else if( url.substring(0, 6) == "https:" ) if (isInNet(myIpAddress(), "10.1.0.0", "255.255.0.0") { if ( (ipSubs[3] % 2) == 0 ) { return SITE1VIP1 + ":8080"; } else { return SITE1VIP2 + ":8080"; } } else { if ( (ipSubs[3] % 2) == 0 ) { return SITE2VIP1 + ":8080"; } else { return SITE2VIP2 + ":8080"; } } } else if( url.substring(0, 5) == "ftp:" ) { if (isInNet(myIpAddress(), "10.1.0.0", "255.255.0.0") { if ( (ipSubs[3] % 2) == 0 ) { return SITE1VIP1 + ":8080"; } else { return SITE1VIP2 + ":8080"; } } else { if ( (ipSubs[3] % 2) == 0 ) { return SITE2VIP1 + ":8080"; } else { return SITE2VIP2 + ":8080"; } } } else if( url.substring(0, 5) == "mms:" ) { if (isInNet(myIpAddress(), "10.1.0.0", "255.255.0.0") { if ( (ipSubs[3] % 2) == 0 ) { return SITE1VIP1 + ":1775"; } else { return SITE1VIP2 + ":1775"; } } else { if ( (ipSubs[3] % 2) == 0 ) { return SITE2VIP1 + ":1775"; } else { return SITE2VIP2 + ":1775"; } } } else { return "DIRECT"; } } // This line is just for testing; you can ignore it. But, if you are having // problems where you think this PAC file isn't being loaded, then change this // to read "if (1)" and the alert box should appear when the browser loads this // file. // // This works for IE4, IE5, IE5.5, IE6 and Netscape 2.x, 3.x, and 4.x. // This does not work for Mozilla (and probably not for Netscape 6.x). // (For IE6, tested on Win2K) // if (0) { alert("DEBUG: PAC file LOADED"); } function URLhash(name) { var cnt=0; var str=name.toLowerCase(name); if ( str.length ==0) { return cnt; } for(var i=0;i < str.length ; i++) { var ch= atoi(str.substring(i,i + 1)); cnt = cnt + ch; } return cnt ; } /* URLhash2( ) for directory name hash computing version. written by SHARP Corp in Feb 1997 Objects in a same directory will be accessed via the same proxy. Use URLhash2( ) instead of URLhash( ) if you prefer to use persistent connection in HTTP 1.1 http://www.sharp.co.jp/sample/test/img/mebius.png http://www.sharp.co.jp/sample/test/img/zaurus.png http://www.sharp.co.jp/sample/test/img/wiz.png <-------------------------------------> directory name hashing here */ function URLhash2(name) { var cnt=0; var dirptr=0; var str=name.toLowerCase(name); if ( str.length ==0) { return cnt; } /* skip filename in directory */ for(var i=str.length - 1;i >=0 ; i--) { if ( str.substring(i,i +1) == '/' ) { dirptr = i+1 ; break; } } for(var i=0;i < dirptr; i++) { var ch= atoi(str.substring(i,i + 1)); cnt = cnt + ch; } return cnt ; } function atoi(charstring) { if ( charstring == "a" ) return 0x61; if ( charstring == "b" ) return 0x62; if ( charstring == "c" ) return 0x63; if ( charstring == "d" ) return 0x64; if ( charstring == "e" ) return 0x65; if ( charstring == "f" ) return 0x66; if ( charstring == "g" ) return 0x67; if ( charstring == "h" ) return 0x68; if ( charstring == "i" ) return 0x69; if ( charstring == "j" ) return 0x6a; if ( charstring == "k" ) return 0x6b; if ( charstring == "l" ) return 0x6c; if ( charstring == "m" ) return 0x6d; if ( charstring == "n" ) return 0x6e; if ( charstring == "o" ) return 0x6f; if ( charstring == "p" ) return 0x70; if ( charstring == "q" ) return 0x71; if ( charstring == "r" ) return 0x72; if ( charstring == "s" ) return 0x73; if ( charstring == "t" ) return 0x74; if ( charstring == "u" ) return 0x75; if ( charstring == "v" ) return 0x76; if ( charstring == "w" ) return 0x77; if ( charstring == "x" ) return 0x78; if ( charstring == "y" ) return 0x79; if ( charstring == "z" ) return 0x7a; if ( charstring == "0" ) return 0x30; if ( charstring == "1" ) return 0x31; if ( charstring == "2" ) return 0x32; if ( charstring == "3" ) return 0x33; if ( charstring == "4" ) return 0x34; if ( charstring == "5" ) return 0x35; if ( charstring == "6" ) return 0x36; if ( charstring == "7" ) return 0x37; if ( charstring == "8" ) return 0x38; if ( charstring == "9" ) return 0x39; if ( charstring == "." ) return 0x2e; return 0x20; }
Let me provide some explanation of the PAC file attached above. First you may notice that I have the function URLhash and URLhash2 included in the PAC file. I don’t actually use those functions although I may in the future.
There are two geographic sites (10.1.0.0/16 and 10.2.0.0/16) each with two Blue Coat ProxySG appliances and a single ProxyAV appliance. Overall there are four ProxySG appliances and two ProxyAV appliances. The PAC file will essentially route all requests “statically” to the local ProxySG appliances with the script load balancing the client requests, depending on the client’s IP address, to one of the two local ProxySG appliances. If you wanted geographic redundancy you could alter the “return” statement like so;
"PROXY 10.1.127.62:8080; 10.2.127.62:8080"
In this case the browser will attempt to contact the proxy server at 10.1.127.62:8080 and if the browser is unable to contact that proxy it will then try the proxy server at 10.2.127.62:8080. You’ll need to make sure that you include the (semicolon) “;” between each proxy server.
This solution has been working really well. The Blue Coat ProxySG appliance has a really impressive policy and rule base. You can do almost anything you want including coaching pages and warning banners. We current have our Blue Coat ProxySG appliances running Websense (onbox) for content filtering.
Cheers!
Jim says
Thanks for the information this should really help me out!
Michael McNamara says
Thanks for the comment Jim.
Dean Fuller says
How do you get over the PAC file hosting being the single point of failure now?
Michael McNamara says
Hi Dean,
There are a number of ways you could host the PAC file in a high availability fashion. You could placed the PAC file on two servers in a server farm that are front-ended by a Layer 7 load balancing switch. You could placed the PAC file on a Windows server that belongs to a Microsoft Cluster.
In my case I’m currently hosting the PAC file on my Intranet website. Since the Intranet is already in a high-availability configuration (two redundant Nortel Application Switch 2216s) it didn’t cost me anything. If you already have a similar solution you might want to host the PAC file within that solution and save yourself the effort and cost of building out a dedicated solution.
Good Luck!
Century says
Hy Michael,
you wrote “If you wanted geographic redundancy you could alter the “return” statement like so;
“PROXY 10.1.127.62:8080; 10.2.127.62:8080″
In this case the browser will attempt to contact the proxy server at 10.1.127.62:8080 and if the browser is unable to contact that proxy it will then try the proxy server at 10.2.127.62:8080. You’ll need to make sure that you include the (semicolon) “;” between each proxy server.”
How does the browser check that theres no proxy on 10.1.127.62. Does it check how long the proxy is unavaible or is it possible to check errors like http404 if the proxy is unavaiable ?
thanks
tom
Russell says
For one of my deployment, i want Active / Active configuration in two location Data center and Disaster recovery site. I don’t have much idea about this scripting. Can you please give me some idea? DC and DR are not in same broadcast or multicast domain. I just saw your wpad conf and write mine.
will my below wpad conf work?
BackupRoute=”DIRECT”;
UseDirectForLocal=true;
function MakeIPs(){
this[0]=”172.16.102.20″;
this[1]=”255.255.255.255″;
this[2]=”172.25.61.50″;
this[3]=”255.255.255.255″;
this[4]=”10.10.10.100″;
}
DirectNames=new MakeNames();
cDirectNames=24;
cNodes=1;
function MakeProxies(){
this[0]=new Node(“172.16.1.236:8080”,0,1.000000);
}
Proxies = new MakeProxies();
function Node(name, hash, load){
this.name = name;
this.hash = hash;
this.load = load;
this.score = 0;
return this;
}
function FindProxyForURL(url, host){
var urlhash, ibest, bestscore, list, i, j;
var DCVIP1 = “PROXY 172.16.1.236”;
var DRVIP1 = “PROXY 172.16.20.222”;
if (UseDirectForLocal && isPlainHostName(host))
return “DIRECT”;
if (cDirectNames > 0)
for (i = 0; i 0)
for (i = 0; i < cDirectIPs; i += 2)
if (isInNet(host, DirectIPs[i], DirectIPs[i+1]))
return "DIRECT";
urlhash = HashString(url);
for (i = 0; i < cNodes; i++)
Proxies[i].score = Proxies[i].load * Scramble(MakeInt(urlhash ^ Proxies[i].hash));
if ( (ipSubs[3] % 2) == 0 )
{
return DCVIP1 + ":8080";
} else
{
return DRVIP1 + ":8080";
}
list = "";
for (j = 0; j < cNodes; j++) {
for (bestscore = -1, i = 0; i bestscore) {
bestscore = Proxies[i].score;
ibest = i;
}
}
Proxies[ibest].score = -1;
list = list + “PROXY ” + Proxies[ibest].name + “; “;
}
list = list + BackupRoute;
return list;
}
function HashString(url){
var h = 0;
var slashes = 0;
for (var i = 0; i < url.length; i++) {
var c = url.charAt(i);
if (c == '/')
slashes++;
if (slashes < 3)
c = c.toLowerCase();
h += (((h & 0x1fff) <> 13) & 0x7ffff)) + CharToAscii(c);
h = MakeInt(h);
}
return h;
}
function Scramble(h){
h += ((h & 0xffff) * 0x1965) + ((((h >> 16) & 0xffff) * 0x1965) << 16) + (((h & 0xffff) * 0x6253) << 16);
h = MakeInt(h);
h += (((h & 0x7ff) <> 11) & 0x1fffff));
return MakeInt(h);
}
var Chars =” !\”#$%&\'()*+,-./0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~??????????????????????????????????????????????????????????????????a??????????????????????? “;
function CharToAscii(c){
return Chars.indexOf(c) + 32;
}
function MakeInt(x){
if (x = 4294967296) {
return x – 4294967296;
}
return x;
}
Michael McNamara says
Hi Russell,
As you probably know you can’t achieve a true active/active configuration but you can get pretty close by utilizing a PAC file with the appropriate JavaScript. In my example script above I evaluate the fourth octet of the client IP address to make a decision regarding which proxy server to return. This helps provide some “load-balancing” between the clients but assures that the same client always queries the same proxy server – easier for troubleshooting and caching of site related information.
You can achieve redundancy by returning two proxy servers in the string. Internet Explore will try the first and if unable to connect will try the second.
In the example above I’m using proxy.example.com but if that does not respond the browser will attempt a direct connection.
Good Luck!