{"id":83,"date":"2008-12-14T21:42:29","date_gmt":"2008-12-15T05:42:29","guid":{"rendered":"http:\/\/greg.porter.name\/wordpress\/?p=83"},"modified":"2009-12-02T08:25:13","modified_gmt":"2009-12-02T16:25:13","slug":"secure-shell-load-balancing-on-the-cheap","status":"publish","type":"post","link":"https:\/\/greg.porter.name\/wordpress\/?p=83","title":{"rendered":"Secure Shell Load Balancing (On The Cheap!)"},"content":{"rendered":"<p><!-- @page { size: 8.5in 11in; margin: 0.79in } P { margin-bottom: 0.08in } --><\/p>\n<p style=\"margin-bottom: 0in;\">At work, I (Greg Porter) have a lot of students (hundreds) that need to use ssh to log into a couple of unix hosts.  Most of the students use one particular host, vogon.csc.calpoly.edu.  When vogon gets busy, or <a href=\"https:\/\/en.wikipedia.org\/wiki\/Fork_bomb\">fork bombed<\/a>, or hangs, all those users are <a href=\"https:\/\/www.urbandictionary.com\/define.php?defid=676823&amp;term=SOL\">SOL<\/a>.  It&#8217;d be nice if we had multiple ssh hosts behind some sort of ssh load balancer.  Of course, we can&#8217;t afford a real load balancer.<\/p>\n<p style=\"margin-bottom: 0in;\">We figured it out with <a href=\"https:\/\/netfilter.org\/\">iptables<\/a>, and so far it seems to work.<!--more--><\/p>\n<p>References (Thanks, Google!):<br \/>\n<a href=\"https:\/\/securepoint.com\/lists\/html\/NetFilter\/2006-11\/msg00091.html\">https:\/\/securepoint.com\/lists\/html\/NetFilter\/2006-11\/msg00091.html<\/a><br \/>\n<a href=\"https:\/\/lserinol.blogspot.com\/2008\/05\/simple-load-balancing-with-iptables.html\">https:\/\/lserinol.blogspot.com\/2008\/05\/simple-load-balancing-with-iptables.html<\/a><\/p>\n<p>I&#8217;m used to hardware load balancers.  Something like <a href=\"https:\/\/www.barracudanetworks.com\/ns\/products\/balancer_overview.php\">this Barracuda one<\/a>. These work as you might expect. Incoming traffic is routed to one of many back end servers. Fancier ones might even realize when a back end server stops responding, and stop sending traffic their way.<\/p>\n<p>The way we did it is with iptables.  Iptables is basically a packet filter (or more generically a packet mangler).<\/p>\n<p>To get this working you have to:<\/p>\n<p>Pick a name\/address for users to connect to. In this example, it&#8217;s unix0.csc.calpoly.edu. Unix0 doesn&#8217;t exist (in real life), it&#8217;s just a virtual name\/address for the cluster.<\/p>\n<p>Number each back end node. In this case the real back end nodes are unix1 through unix4.csc.calpoly.edu. Each node will &#8220;know&#8221; what cluster node number it is. Unix1 is node number 1, unix2 is node 2 and so on.<\/p>\n<p>Give each cluster member an additional &#8220;fake&#8221; cluster MAC address they share. This is a specially crafted &#8220;multicast&#8221; MAC address, which I had never even heard of. Once each member server has an additional address, you get this very strange result when you ping it (them?):<\/p>\n<p>[glporter@updates ~]$ ping -c 1 unix0<br \/>\nPING unix0.csc.calpoly.edu (129.65.158.160) 56(84) bytes of data.<br \/>\n64 bytes from unix0.csc.calpoly.edu (129.65.158.160): icmp_seq=1 ttl=64 time=0.241 ms<br \/>\n64 bytes from unix0.csc.calpoly.edu (129.65.158.160): icmp_seq=1 ttl=64 time=0.242 ms (DUP!)<br \/>\n64 bytes from unix0.csc.calpoly.edu (129.65.158.160): icmp_seq=1 ttl=64 time=0.242 ms (DUP!)<br \/>\n64 bytes from unix0.csc.calpoly.edu (129.65.158.160): icmp_seq=1 ttl=64 time=0.479 ms (DUP!)<\/p>\n<p>&#8212; unix0.csc.calpoly.edu ping statistics &#8212;<br \/>\n1 packet transmitted, 1 received, +3 duplicates, 0% packet loss, time 999ms<\/p>\n<p>Betcha never seen THAT before! I know I haven&#8217;t. Ping one address and get 4 responses. Hmm. We got four responses because unix1 responded from its additional (unix0) address, unix2 responded (from its unix0 address), unix3 responded and unix 4 responded. So each member node responds to a request sent to unix0.<\/p>\n<p>So far we don&#8217;t have a &#8220;load balancer&#8221;, it&#8217;s more like a &#8220;load spammer&#8221;.  Speak to one IP, 4 IPs hear you.<\/p>\n<p>Now even more magic happens.  We&#8217;re going to use some rules in iptables to:<\/p>\n<p>Take an incoming connection to unix0, look at the source IP address, and apply a hash rule to it. The hash we use will ALWAYS get a 1, 2, 3 or 4 (for our 4 node cluster) for any given source IP. No matter &#8220;who&#8221; uses the hash, given a particular source IP, you&#8217;ll always get the same answer (1, 2, 3, or 4).<\/p>\n<p>Decide if a node should handle the connection. Each node &#8220;hears&#8221; the incoming connection. Each node hashes the source IP. Each node gets the same answer (1, 2, 3, or 4 for our 4 node cluster). If the source IP hashes to their own node number (&#8220;I&#8217;m node 1 and this IP hashes to 1&#8221;) then they act on the request. If not, they drop the packet in the bit bucket.<\/p>\n<p>So although all nodes 4 &#8220;hear&#8221; any particular request, only one acts on it.  Voila!  Load balancing! (Sort of).<\/p>\n<p>This sort of handles state in a crude way. It assumes that the same user, for a particular session, will be coming from the same IP, and they will be serviced by the same node.<\/p>\n<p>It doesn&#8217;t address what happens when a node goes down. In this setup, if a node goes down, then 1 in 4 connections don&#8217;t get serviced, ever.<\/p>\n<p>It&#8217;s *VERY* strange.  It works, but wow, how weird.<\/p>\n<p>Here&#8217;s what the iptables rules look like on one of the nodes:<\/p>\n<p>[root@unix4 ~]# service iptables status<br \/>\nTable: filter<br \/>\nChain INPUT (policy ACCEPT)<br \/>\nnum\u00a0 target\u00a0\u00a0\u00a0\u00a0 prot opt source\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 destination<br \/>\n1\u00a0\u00a0\u00a0 CLUSTERIP\u00a0 tcp\u00a0 &#8212;\u00a0 0.0.0.0\/0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 129.65.158.160\u00a0\u00a0\u00a0\u00a0\u00a0 tcp dpt:22 CLUSTERIP hashmode=sourceip clustermac=01:00:5E:41:9E:A0 total_nodes=4 local_node=4 hash_init=0<br \/>\n2\u00a0\u00a0\u00a0 SSHRULES\u00a0\u00a0 tcp\u00a0 &#8212;\u00a0 0.0.0.0\/0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 0.0.0.0\/0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 state NEW tcp dpt:22<\/p>\n<p>Chain FORWARD (policy ACCEPT)<br \/>\nnum\u00a0 target\u00a0\u00a0\u00a0\u00a0 prot opt source\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 destination<\/p>\n<p>Chain OUTPUT (policy ACCEPT)<br \/>\nnum\u00a0 target\u00a0\u00a0\u00a0\u00a0 prot opt source\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 destination<\/p>\n<p>Chain SSHRULES (1 references)<br \/>\nnum\u00a0 target\u00a0\u00a0\u00a0\u00a0 prot opt source\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 destination<br \/>\n1\u00a0\u00a0\u00a0 REJECT\u00a0\u00a0\u00a0\u00a0 all\u00a0 &#8212;\u00a0 0.0.0.0\/0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 0.0.0.0\/0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 recent: UPDATE seconds: 7 hit_count: 2 name: DEFAULT side: source reject-with icmp-port-unreachable<br \/>\n2\u00a0\u00a0\u00a0 ACCEPT\u00a0\u00a0\u00a0\u00a0 all\u00a0 &#8212;\u00a0 0.0.0.0\/0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 0.0.0.0\/0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 recent: SET name: DEFAULT side: source<\/p>\n","protected":false},"excerpt":{"rendered":"<p>At work, I (Greg Porter) have a lot of students (hundreds) that need to use ssh to log into a couple of unix hosts. Most of the students use one particular host, vogon.csc.calpoly.edu. When vogon gets busy, or fork bombed,&hellip; <a href=\"https:\/\/greg.porter.name\/wordpress\/?p=83\" class=\"more-link\">Continue Reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-83","post","type-post","status-publish","format-standard","hentry","category-content"],"_links":{"self":[{"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/83","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=83"}],"version-history":[{"count":3,"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/83\/revisions"}],"predecessor-version":[{"id":240,"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/83\/revisions\/240"}],"wp:attachment":[{"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=83"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=83"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/greg.porter.name\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=83"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}